Project Background

Based on Rezaei-Dehaghani, Keshvari and Paki (2018), family relationships are vital to a student’s academic performance. Utilizing the Student Alcohol Consumption dataset from Kaggle which contains information on student academic performance and family relationships information, we aim to create a model that could predict whether a student will pass an exam and also predict the marks they will acquire. By doing so, we aim to filter and assist students to perform better academically based on how well they perform.

Import Library

library(caret)
Warning: package ‘caret’ was built under R version 4.3.1Loading required package: ggplot2
Warning: package ‘ggplot2’ was built under R version 4.3.1Loading required package: lattice
library(superml)
Warning: package ‘superml’ was built under R version 4.3.1Loading required package: R6
Warning: package ‘R6’ was built under R version 4.3.1
library(dplyr)
Warning: package ‘dplyr’ was built under R version 4.3.1
Attaching package: ‘dplyr’

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(ggplot2)
library(corrplot)
Warning: package ‘corrplot’ was built under R version 4.3.2corrplot 0.92 loaded
library(patchwork)
Warning: package ‘patchwork’ was built under R version 4.3.2
library(randomForest)
Warning: package ‘randomForest’ was built under R version 4.3.2randomForest 4.7-1.1
Type rfNews() to see new features/changes/bug fixes.

Attaching package: ‘randomForest’

The following object is masked from ‘package:dplyr’:

    combine

The following object is masked from ‘package:ggplot2’:

    margin
library(e1071)
Warning: package ‘e1071’ was built under R version 4.3.1
set.seed(42)

We will first import the required libraries. dplyr is used for data manipulation, ggplot2, patchwork and corrplot are used for data visualization while caret, randomForest, e1071 and superml are used for predictive modelling. At the same time, the seed is seeded to 42 to ensure reproducibility.

Import dataset

df_mat<-read.csv("student-mat.csv")
df_por<-read.csv("student-por.csv")
df<-rbind(df_mat,df_por)
df

The data is stored in 2 separate CSV based on the subject that the student is taking. Due to the limited samples of data, we will combine the 2 dataset to create a more comprehensive dataset. The resulting dataset consists of 1044 rows and 33 columns. We will then check whether there is any missing data in the dataset.

df %>%
  select(everything()) %>%  
  summarise_all(funs(sum(is.na(.))))
Warning: `funs()` was deprecated in dplyr 0.8.0.
Please use a list of either functions or lambdas: 

  # Simple named list: 
  list(mean = mean, median = median)

  # Auto named with `tibble::lst()`: 
  tibble::lst(mean, median)

  # Using lambdas
  list(~ mean(., trim = .2), ~ median(., na.rm = TRUE))

Upon analysis, we verified that there are no missing values across the columns, thus the data imputation step can be omitted later on.

sample <- sample.int(n = nrow(df), size = floor(.8*nrow(df)), replace = F)
train <- df[sample, ]
test  <- df[-sample, ]

The data is split in 8:2 ratio into train and test data. The resulting data will always be the same since we have seeded it. The train data will be used for EDA and training while the test data will be used to evaluate our models.

Data Cleaning

str(train)
'data.frame':   835 obs. of  33 variables:
 $ school    : chr  "GP" "GP" "GP" "GP" ...
 $ sex       : chr  "F" "F" "F" "M" ...
 $ age       : int  15 17 16 15 16 18 17 16 15 16 ...
 $ address   : chr  "R" "U" "U" "U" ...
 $ famsize   : chr  "GT3" "GT3" "GT3" "GT3" ...
 $ Pstatus   : chr  "T" "A" "T" "T" ...
 $ Medu      : int  3 4 4 4 2 3 1 2 2 1 ...
 $ Fedu      : int  3 3 4 2 2 3 1 2 2 1 ...
 $ Mjob      : chr  "services" "services" "teacher" "teacher" ...
 $ Fjob      : chr  "services" "services" "services" "other" ...
 $ reason    : chr  "reputation" "course" "home" "home" ...
 $ guardian  : chr  "other" "mother" "mother" "mother" ...
 $ traveltime: int  2 1 1 1 2 1 4 1 1 1 ...
 $ studytime : int  3 2 3 2 2 2 2 2 3 4 ...
 $ failures  : int  0 0 0 0 0 0 3 0 0 0 ...
 $ schoolsup : chr  "no" "no" "no" "no" ...
 $ famsup    : chr  "yes" "yes" "yes" "yes" ...
 $ paid      : chr  "yes" "yes" "no" "yes" ...
 $ activities: chr  "yes" "no" "yes" "no" ...
 $ nursery   : chr  "yes" "yes" "no" "yes" ...
 $ higher    : chr  "yes" "yes" "yes" "yes" ...
 $ internet  : chr  "yes" "yes" "yes" "no" ...
 $ romantic  : chr  "yes" "yes" "no" "no" ...
 $ famrel    : int  4 5 5 4 5 5 5 3 4 2 ...
 $ freetime  : int  2 2 3 3 4 3 3 3 5 2 ...
 $ goout     : int  1 2 2 3 4 4 5 4 2 1 ...
 $ Dalc      : int  2 1 1 2 2 1 1 1 1 1 ...
 $ Walc      : int  3 2 1 2 4 1 5 1 1 1 ...
 $ health    : int  3 5 5 5 5 5 5 4 3 5 ...
 $ absences  : int  2 23 4 2 0 0 0 0 0 0 ...
 $ G1        : int  13 13 15 15 13 10 5 13 14 14 ...
 $ G2        : int  13 13 16 15 13 9 8 13 14 14 ...
 $ G3        : int  13 13 16 14 12 9 7 13 15 14 ...

Firstly, we have a look at the overview structure of the dataset to understand the values of each feature and their respective data type. There are columns which are logical, integer, and character. Some of the character columns have only 2 values thus we can encode them into 0 or 1.

label_encoders<-c()
binary_col<-c()
for (col in names(train)){
  if (nrow(unique(train[col]))==2){
    label_encoders<-c(label_encoders,LabelEncoder$new()) 
    binary_col<-c(binary_col,col)
    train[col]<-label_encoders[[length(label_encoders)]]$fit_transform(train[[col]])
  }
}
names(label_encoders)<-binary_col

Each of the columns with only 2 values will be encoded into 0 or 1 to allow the relationship between variables to be easily visualized. A label encoder will be used to fit each columns and transform it. The label encoders trained will be stored in a vector so that we could reuse it with the test data.

Exploratory Data Analysis

M<-cor(mutate(subset(train,select=-c(Mjob,Fjob,reason,guardian))))
corrplot(M,method = 'color',tl.cex = 0.8, cl.cex = 0.8)

In the above figure, we can see the correlation between all the numerical features. We can see that G1. G2 and G3 seems to be highly correlated.

ggplot(train,aes(G3>=10))+labs(x="G3",y="Number of Students",title="Student Performance in G3")+geom_bar()

In the figure above, we can see that around 1 out of 4 students failed the G3 exam. We will look into some of the features that might have contributed to it.

grouped<-subset(train,select=-c(Mjob,Fjob,reason,guardian))
grouped["G1"]<-grouped["G1"]>=10
grouped["G2"]<-grouped["G2"]>=10
grouped["G1_2"]<-grouped["G1"]+grouped["G2"]
ggplot(grouped %>% group_by(G1_2) %>% summarise(total=sum(G3>=10)/n()),aes(x=G1_2,y=total))+geom_bar(stat='identity',fill = "steelblue")+labs(x="Total tests passed",y="Percentage of students who passed G3",title="Percentage of students who passed G3 grouped by total tests passed")

In the correlation graph, we have observed that G1 and G2 are highly correlated to G3. As shown in the figure above, almost all of the students who have passed both the previous tests passed G3. For those who have not passed any tests previously, only about 20% passed G3. Thus, we will analyze what makes the two groups perform so differently.

numeric_cols<-c()
for (col in names(grouped)){
  if (is.numeric(grouped[[col]])){
    numeric_cols<-c(numeric_cols,col)
  }
}
grouped_scaled<-scale(grouped[numeric_cols])
grouped[numeric_cols]<-grouped_scaled
summarised<-group_by(grouped,G1_2) %>% summarise(across(everything(), mean))
summarised<-subset(summarised,select=-c(G1,G2,G1_2,G3))
diff2_0<-t(abs(summarised[3,]-summarised[1,]))
colnames(diff2_0)<-c("Difference")
barchart(diff2_0[order(diff2_0),],xlab="Normalized Mean Difference",ylab="Characteristic",main = "Normalized Mean Difference between Different Characteristics")

We grouped the students based on the number of previous tests passed and calculate the difference of standardized mean between different features and visualized in the figure above. The main feature that contributed to their difference is the number of failures further before in the class, which corresponded to our figure previously. Another key difference is the desire to pursue further education, followed by the parents’ education level and the time they have spent in studies.

grouped<-subset(train,select=-c(Mjob,Fjob,reason,guardian))
grouped["G1"]<-grouped["G1"]>=10
grouped["G2"]<-grouped["G2"]>=10
grouped["G1_2"]<-grouped["G1"]+grouped["G2"]
ggplot(grouped %>% group_by(G1_2) %>% summarise(failures=mean(failures)),aes(x=G1_2,y=failures))+geom_bar(stat='identity')+labs(x="Total tests passed",y="Percentage of student with previous failures",title="Percentage of student with previous failures based on tests passed")

As we can observe in the figure above, almost 80% of students who have failed G1 and G2 have failures previously.

ggplot(grouped %>% group_by(G1_2) %>% summarise(higher=mean(higher)),aes(x=G1_2,y=higher))+geom_bar(stat='identity')+labs(x="Total tests passed",y="Percentage of student desiring further education",title="Percentage of student desiring further education based on tests passed")

At the same time, more than 20% of the students who have not passed any tests do not desire to further their studies.

ggplot(grouped %>% group_by(G1_2) %>% summarise(studytime=mean(studytime)),aes(x=G1_2,y=studytime))+geom_bar(stat='identity')+labs(x="Total tests passed",y="Mean studytime",title="Mean studytime based on tests passed")

They also study half an hour less compared to those who pass all the test.

ggplot(grouped %>%  group_by(G1_2) %>% summarise(Pedu=mean(Medu+Fedu)),aes(x=G1_2,y=Pedu))+geom_bar(stat='identity')+labs(x="Total tests passed",y="Parents' average education level",title="Parents' average education level based on tests passed")

The students’ parents also have a lower average education level compared to the students who are performing better.

ggplot(train,aes(x=famrel))+geom_histogram()+labs(x="Family relationship",y="Number of students",title="Family relationship distribution")

The figure above shows the distribution of the students’ family relationships. Most of the students have a satisfactory relationship with their families as 4 is the mode of the feature.

grouped<-subset(train,select=-c(Mjob,Fjob,reason,guardian))
grouped["G1"]<-grouped["G1"]>=10
grouped["G2"]<-grouped["G2"]>=10
grouped["G1_2"]<-grouped["G1"]+grouped["G2"]
grouped["famrel"]<-grouped["famrel"]>=4
#Scale numerical columns
numeric_cols<-c()
for (col in names(grouped)){
  if (is.numeric(grouped[[col]])){
    numeric_cols<-c(numeric_cols,col)
  }
}
grouped_scaled<-scale(grouped[numeric_cols])
grouped[numeric_cols]<-grouped_scaled
summarised<-group_by(grouped,famrel) %>% summarise(across(everything(), mean))
summarised<-subset(summarised,select=-(famrel))
difference<-t(summarised[2,]-summarised[1,])
colnames(difference)<-c("Difference")
barchart(difference[order(difference),],xlab="Normalized Mean Difference",ylab="Characteristic")

If we compare those students with good family relationship (>=4) with those who have worse, we can observe that these students consume more amounts of alcohol in the weekends. They also have less free time and study time on hand.

# Create custom color palette
custom.col <- c("#abebad", "#ffff99", "#ADC2FF", "#f0b2f0", "#ff9999")

# Create the plots for weekend alcohol consumption
plot_weekend <- ggplot(df, aes(x = as.factor(Walc), y = G1, fill = as.factor(Walc))) +
  geom_boxplot() +
  scale_fill_manual(values = custom.col) +
  theme_bw() +
  theme(legend.position = "none") +
  xlab("Alcohol Consumption Level") +
  ylab("First Period Grade (G1)") +
  ggtitle("Weekends") +
  scale_x_discrete(labels = c("Very Low", "Low", "Moderate", "High", "Very High"))

# Create the plots for weekday alcohol consumption
plot_weekday <- ggplot(df, aes(x = as.factor(Dalc), y = G1, fill = as.factor(Dalc))) +
  geom_boxplot() +
  scale_fill_manual(values = custom.col) +
  theme_bw() +
  theme(legend.position = "none") +
  xlab("Alcohol Consumption Level") +
  ylab("First Period Grade (G1)") +
  ggtitle("Weekdays") +
  scale_x_discrete(labels = c("Very Low", "Low", "Moderate", "High", "Very High"))

# Create common title
common_title <- plot_annotation(title = "G1 Grade vs Alcohol Consumption Level for Weekdays and Weekends ",
                                theme = theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold")))

# Combine the plots and add the common title
combined_plot <- plot_weekend + plot_weekday + plot_layout(ncol = 2, widths = c(2, 2)) + common_title

# Display the combined plot
print(combined_plot)

Median grades tend to decrease as alcohol consumption increases for both weekends and weekdays. The ‘Very High’ and ‘High’ alcohol consumption groups appear to have the lowest median grade, as well as the smaller interquartile range, indicating that students who consume a lot of alcohol tend to have lower and more consistent grades.

# Create custom color palette
custom.col <- c("#abebad", "#ffff99", "#ADC2FF", "#f0b2f0", "#ff9999")

# Create the plots for weekend alcohol consumption
plot_weekend <- ggplot(df, aes(x = as.factor(Dalc), y = G2, fill = as.factor(Dalc))) +
  geom_boxplot() +
  scale_fill_manual(values = custom.col)+
  theme_bw() +
  theme(legend.position = "none") +
  xlab("Alcohol Consumption Level") +
  ylab("Second Period Grade (G2)") +
  ggtitle("Weekends") +
  scale_x_discrete(labels = c("Very Low", "Low", "Moderate", "High", "Very High"))

# Create the plots for weekday alcohol consumption
plot_weekday <- ggplot(df, aes(x = as.factor(Walc), y = G2, fill = as.factor(Walc))) +
  geom_boxplot() +
  scale_fill_manual(values = custom.col)+
  theme_bw() +
  theme(legend.position = "none") +
  xlab("Alcohol Consumption Level") +
  ylab("Second Period Grade (G2)") +
  ggtitle("Weekdays") +
  scale_x_discrete(labels = c("Very Low", "Low", "Moderate", "High", "Very High"))

# Create common title
common_title <- plot_annotation(title = "G2 Grade vs Alcohol Consumption Level for Weekdays and Weekends ",
                                theme = theme(plot.title = element_text(hjust = 0.5, size = 14, face = "bold")))

# Combine the plots and add the common title
combined_plot <- plot_weekend + plot_weekday + plot_layout(ncol = 2, widths = c(2, 2)) + common_title

# Display the combined plot
print(combined_plot)

Similar to Grade 1, the ‘Very High’, ‘High’, and ‘Moderate’ alcohol consumption groups are associated with lower median grades. This suggests that increased alcohol consumption tends to correspond with lower academic performance.

X_train<-subset(train,select=-c(G3))
y_train<-train$G3
X_test<-subset(test,select=-c(G3))
y_test<-test$G3
y_train<-ifelse(y_train>=10,TRUE,FALSE)
y_test<-ifelse(y_test>=10,TRUE,FALSE)

The target feature is used as the y while the rest of the features are used as the X. The y feature will be G3. For regression, it will be the score from 0-20, while for classification, it will be true or false based on whether the students passed the exam.

Preprocess Train Data

#Scale numerical columns
numeric_cols<-c()
for (col in names(X_train)){
  if (is.numeric(X_train[[col]])){
    numeric_cols<-c(numeric_cols,col)
  }
}
X_train_scaled<-scale(X_train[numeric_cols])
X_train[numeric_cols]<-X_train_scaled

The numerical columns in X_train is scaled so that each of the different features have the same scale. This allow better interpretation of the data and speeds up training as the range is reduced. A copy of the scaled data is stored to allow the scale to be reused on the testing data.

# One hot encoding
dmy <- dummyVars(" ~ .",data=X_train)
X_train <- data.frame(predict(dmy,newdata=X_train))
X_train

The remaining categorical features in the training features will be encoded using one-hot encoding. The encoder is stored to be reused on testing data.

Transform Test Data

for (col in names(label_encoders)){
  X_test[col]<-label_encoders[[col]]$transform(X_test[[col]])
}
X_test[numeric_cols]<-scale(X_test[numeric_cols],center=attr(X_train_scaled, "scaled:center"), 
                              scale=attr(X_train_scaled, "scaled:scale"))
X_test <- data.frame(predict(dmy,newdata=X_test,na.action = na.pass))
X_test

The same transformation is applied to the test data so it is of the same scale with the train data and have the same features. It will similarly undergo data encoding and data normalization.

Classification Model

In our classification analysis, we employed four machine learning models which were Random Forest, Support Vector Machine (SVM), k-Nearest Neighbors (k-NN), and Naive Bayes to predict whether the students will pass or fail in the third grade (G3). Each model underwent training on the X_train with the target variable in y_train. The models were then evaluated with a test set. The evaluation metrics include a confusion matrix, along with key performance metrics like accuracy, precision, recall, and F1 score.

evaluate_model <- function(predictions, actual, model_name) {
  confusion_matrix <- table(Actual = as.factor(actual), Predicted = as.factor(predictions))
  cat("Confusion Matrix for", model_name,"\n")
  print.table(confusion_matrix)
  cat("\n")
  
  accuracy <- sum(diag(confusion_matrix)) / sum(confusion_matrix)
  precision <- confusion_matrix["TRUE", "TRUE"] / sum(confusion_matrix[, "TRUE"])
  recall <- confusion_matrix["TRUE", "TRUE"] / sum(confusion_matrix["TRUE", ])
  f1_score <- 2 * (precision * recall) / (precision + recall)
  
  metrics <- list(Accuracy = accuracy, Precision = precision, Recall = recall, F1_Score = f1_score)
  return(metrics)
}

perform_classification <- function(X_train, y_train, X_test, y_test) {
  set.seed(42)
  model_names <- c("Random_Forest", "SVM", "k_NN", "Naive_Bayes")
  results <- list()
  models<-list()
  
  for (model_name in model_names) {
    if (model_name == "Random_Forest") {
      model <- randomForest(x = X_train, y = as.factor(y_train),
                            ntree = 100,
                            mtry = sqrt(ncol(X_train) - 1),
                            nodesize = 1,
                            importance = TRUE,
                            type = "classification")
      
    } else if (model_name == "SVM") {
      model <- svm(as.factor(y_train) ~ ., data = X_train, kernel = "radial", cost = 1)
      
    } else if (model_name == "k_NN") {
      model <- train(x = X_train, y = as.factor(y_train),
                     method = "knn",
                     trControl = trainControl(method = "cv", number = 10),
                     tuneGrid = data.frame(k = 15))
      
    } else if (model_name == "Naive_Bayes") {
      model <- naiveBayes(x = X_train, y = as.factor(y_train))
    }
    predictions <- predict(model, X_test)
    metrics <- evaluate_model(predictions, y_test, model_name)
    results[[model_name]] <- metrics
  }
  results_df <- do.call(rbind, results)
  return(results_df)
}

results <- perform_classification(X_train, y_train, X_test, y_test)
Confusion Matrix for Random_Forest 
       Predicted
Actual  FALSE TRUE
  FALSE    32   10
  TRUE      8  159

Confusion Matrix for SVM 
       Predicted
Actual  FALSE TRUE
  FALSE    22   20
  TRUE      7  160

Confusion Matrix for k_NN 
       Predicted
Actual  FALSE TRUE
  FALSE     9   33
  TRUE      2  165

Confusion Matrix for Naive_Bayes 
       Predicted
Actual  FALSE TRUE
  FALSE    29   13
  TRUE     22  145
print(results)
              Accuracy  Precision Recall    F1_Score 
Random_Forest 0.9138756 0.9408284 0.9520958 0.9464286
SVM           0.8708134 0.8888889 0.9580838 0.9221902
k_NN          0.8325359 0.8333333 0.988024  0.9041096
Naive_Bayes   0.8325359 0.9177215 0.8682635 0.8923077

Among the models employed, the Random Forest emerged as the top performer for the given classification task. It achieved an impressive accuracy of 91.39%, showcasing a well-balanced precision of 94.08%, recall of 95.21% and F1 score of 94.64%. These results indicate the model’s ability to effectively classify the students’ grades.

Feature Importance for Random Forest

model <- randomForest(x = X_train, y = as.factor(y_train),  # Convert y_train to factor for classification
                      ntree = 100,                             
                      mtry = sqrt(ncol(X_train) - 1),         
                      nodesize = 1,                            
                      importance = TRUE,                      
                      type = "classification") 

# Calculate feature importance for Rf (best model)
feature_importance <- importance(model)

# Create a data frame for feature importance
feature_importance_df <- data.frame(
  Features = row.names(feature_importance),
  Importance = feature_importance[, "MeanDecreaseAccuracy"]
)

# Sort the data frame by importance (descending order)
feature_importance_df <- feature_importance_df[order(-feature_importance_df$Importance), ]

# Create a bar plot of feature importance
ggplot(data = feature_importance_df, aes(x = Importance, y = reorder(Features, Importance))) +
  geom_bar(stat = "identity", fill = "steelblue", width = 0.8) +
  geom_text(aes(label = round(Importance, 2)), hjust = -0.1, vjust = 0.5, size = 2, color = "black") +
  labs(x = "Importance", y = "Features") +
  theme(axis.text.y = element_text(hjust = 1)) +
  ggtitle("Feature Importance")

The bar plot provides a visual representation of feature importance in our trained Random Forest model. Longer bars indicate greater importance in predicting outcomes. From the plot, Grade 1 and Grade 2 stand out as substantial contributors to the model’s performance. This observation is consistent with the understanding that early grades often serve as fundamental indicators for the following academic success.

Regression Model

In our study, G3 has been identified as the only appropriate predictor for our regression models due to the limited predictive capability of other features. Initially, the focus was to predict the binary outcome of students’ success or failure. However, to gain a deeper insight into student performance, we shifted our attention to predicting the actual G3 scores using regression models. These models, including Linear Regression, Random Forest, and Support Vector Regression (SVR), are meticulously evaluated using metrics like RMSE and R². Our approach encompasses data preparation, model building, and in-depth analysis of feature importance, aimed at providing a comprehensive understanding of factors influencing G3 scores.

Linear Regression

set.seed(42)
y_train<-train$G3
y_test<-test$G3
train_new<-cbind(X_train,G3=y_train)
train_new
linear_model <- lm(G3 ~ ., train_new)
summary(linear_model)

Call:
lm(formula = G3 ~ ., data = train_new)

Residuals:
    Min      1Q  Median      3Q     Max 
-9.0153 -0.5077  0.1268  0.7718  5.4630 

Coefficients: (4 not defined because of singularities)
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)      11.0941620  0.3841245  28.882  < 2e-16 ***
school            0.0461210  0.0658997   0.700 0.484216    
sex              -0.0159701  0.0648252  -0.246 0.805470    
age              -0.0516032  0.0659433  -0.783 0.434132    
address           0.0675822  0.0623198   1.084 0.278498    
famsize          -0.0009285  0.0579925  -0.016 0.987230    
Pstatus           0.0594224  0.0589365   1.008 0.313644    
Medu              0.0267174  0.0893644   0.299 0.765040    
Fedu             -0.0277902  0.0778462  -0.357 0.721195    
Mjobat_home      -0.0806190  0.2644034  -0.305 0.760515    
Mjobhealth        0.0150217  0.2658821   0.056 0.954960    
Mjobother        -0.0658686  0.2229282  -0.295 0.767712    
Mjobservices      0.0646415  0.2142298   0.302 0.762930    
Mjobteacher              NA         NA      NA       NA    
Fjobat_home       0.5911588  0.3612100   1.637 0.102109    
Fjobhealth        0.4200493  0.3677385   1.142 0.253695    
Fjobother         0.3059813  0.2670139   1.146 0.252167    
Fjobservices      0.1132040  0.2700200   0.419 0.675152    
Fjobteacher              NA         NA      NA       NA    
reasoncourse      0.2579934  0.1515152   1.703 0.089006 .  
reasonhome        0.1369730  0.1668578   0.821 0.411952    
reasonother       0.1080025  0.2203820   0.490 0.624220    
reasonreputation         NA         NA      NA       NA    
guardianfather   -0.1271201  0.2643065  -0.481 0.630680    
guardianmother   -0.1517286  0.2408441  -0.630 0.528885    
guardianother            NA         NA      NA       NA    
traveltime        0.0832493  0.0622915   1.336 0.181786    
studytime        -0.0109625  0.0617616  -0.177 0.859164    
failures         -0.1701065  0.0646666  -2.631 0.008691 ** 
schoolsup         0.0117131  0.0586787   0.200 0.841833    
famsup           -0.0926991  0.0588590  -1.575 0.115670    
paid              0.1797759  0.0581375   3.092 0.002056 ** 
activities        0.0481574  0.0574982   0.838 0.402538    
nursery           0.0203182  0.0565288   0.359 0.719368    
higher           -0.0527546  0.0608266  -0.867 0.386043    
internet          0.0024146  0.0594991   0.041 0.967639    
romantic          0.0061503  0.0579977   0.106 0.915575    
famrel            0.0925326  0.0583883   1.585 0.113415    
freetime          0.0108051  0.0611041   0.177 0.859686    
goout            -0.0468975  0.0647874  -0.724 0.469361    
Dalc             -0.0587007  0.0737803  -0.796 0.426494    
Walc              0.1208723  0.0807811   1.496 0.134975    
health           -0.0354293  0.0584351  -0.606 0.544488    
absences          0.2048107  0.0594102   3.447 0.000596 ***
G1                0.4392294  0.1139398   3.855 0.000125 ***
G2                3.1119953  0.1114711  27.918  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 1.565 on 793 degrees of freedom
Multiple R-squared:  0.8443,    Adjusted R-squared:  0.8362 
F-statistic: 104.9 on 41 and 793 DF,  p-value: < 2.2e-16
linear_model_summary <- summary(linear_model)
r_squared <- linear_model_summary$r.squared
print(r_squared)
[1] 0.8442896
predictions <- predict(linear_model, newdata = X_test)
rmse <- sqrt(mean((predictions - y_test)^2))
r_squared <- 1 - sum((predictions - y_test)^2) / sum((y_test - mean(y_test))^2)
cat("RMSE:", rmse, "\n")
RMSE: 1.624044 
cat("R²:", r_squared, "\n")
R²: 0.8228375 
comparison_df <- data.frame(Actual = y_test, Predicted = predictions)
ggplot(comparison_df, aes(x = Actual, y = Predicted)) +
    geom_point() +  # Add Scatter Plot
    geom_abline(intercept = 0, slope = 1, linetype = "dashed", color = "red") +  # Add y=x Diagonal Line
    labs(title = "Linear Regression", x = "Actual G3", y = "Predicted G3")  # Add Title and Axis Labels

The scatter plot shows a comparison between the actual G3 grades and the predicted grades from the Linear Regression model. The red dashed line represents the ideal where the predicted grades perfectly match the actual grades. Points clustering close to this line suggest good model accuracy. The general trend indicates that the Linear Regression model predicts lower and mid-range grades quite well, as points in these areas are closer to the line. The spread of points away from the line at the extremities could indicate less accuracy for very high or very low grades. This pattern might suggest that the model is better at predicting average performances than extremes.

Random forest

rf_model <- randomForest(G3 ~ ., data = train_new, ntree = 500)
print(rf_model)

Call:
 randomForest(formula = G3 ~ ., data = train_new, ntree = 500) 
               Type of random forest: regression
                     Number of trees: 500
No. of variables tried at each split: 15

          Mean of squared residuals: 2.29217
                    % Var explained: 84.65
r_squared_train <- rf_model$rsq
r_squared_train_last <- tail(r_squared_train, 1)
cat("R² for Training Set:", r_squared_train_last, "\n")
R² for Training Set: 0.8464827 
rf_predictions <- predict(rf_model, newdata = X_test)
rf_rmse <- sqrt(mean((rf_predictions - y_test)^2))
r_squared_test <- 1 - sum((rf_predictions - y_test)^2) / sum((y_test - mean(y_test))^2)
cat("R² for Test Set:", r_squared_test, "\n")
R² for Test Set: 0.8132012 
cat("RMSE:", rf_rmse, "\n")
RMSE: 1.667627 
feature_importance_raw <- importance(rf_model)
feature_importance <- data.frame(Feature = rownames(feature_importance_raw), Importance = feature_importance_raw[, "IncNodePurity"])
ggplot(feature_importance, aes(x = reorder(Feature, Importance), y = Importance)) +
    geom_bar(stat = "identity") +
    coord_flip() +  # Flipping the coordinates for better readability of feature names
    theme_minimal() +
    labs(title = "Feature Importance in Random Forest Model", x = "Features", y = "Importance")

This horizontal bar chart displays the feature importance as determined by a Random Forest model. The features are listed on the y-axis and their importance scores are on the x-axis. It’s clear from the chart that ‘G2’ and ‘G1’ are the most significant predictors of the target variable, which, in the context of student performance, are likely previous grades. ‘Absences’ also appears to be a significant feature, followed by ‘failures’. Other variables such as ‘studytime’, ‘Medu’ (mother’s education), ‘Fedu’ (father’s education), and ‘traveltime’ show relatively less importance. The length of the bars represents how influential each feature is in the model’s predictions; longer bars indicate greater importance.

SVR MODEL

svr_model <- svm(G3 ~ ., data = train_new, type = "eps-regression", kernel = "radial")
svr_predictions <- predict(svr_model, newdata = X_test)
svr_rmse <- sqrt(mean((svr_predictions - y_test)^2))
cat("SVR RMSE:", svr_rmse, "\n")
SVR RMSE: 2.037617 
svr_r_squared <- 1 - sum((svr_predictions - y_test)^2) / sum((y_test - mean(y_test))^2)
cat("SVR R²:", svr_r_squared, "\n")
SVR R²: 0.7211175 

Regression Model Performance

model_comparison <- data.frame(
  Model = c("Linear Regression", "Random Forest", "SVR"),
  RMSE = c(rmse, rf_rmse, svr_rmse),
  R_Squared = c(r_squared, r_squared_test, svr_r_squared)
)
print(model_comparison)

In the comparison of the three models, the linear regression model outperformed the other models with the lowest RMSE and highest R-square, indicating that it had the best predictive accuracy and explanatory power for the G3 score. Random forest comes second, but with slightly less accuracy and explanatory power. SVR had the highest RMSE and lowest R-squared, making it the least effective at predicting and explaining variance in G3 scores. The results showed that the linear regression model was the most effective for predicting students’ final exam scores (G3).

Reference

Rezaei-Dehaghani, A., Keshvari, M., & Paki, S. (2018). The relationship between family functioning and academic achievement in female high school students of Isfahan, Iran, in 2013–2014. Iranian Journal of Nursing and Midwifery Research, 23(3), 183. https://doi.org/10.4103/ijnmr.ijnmr_87_17

LS0tDQp0aXRsZTogIlN0dWRlbnQgQWNhZGVtaWMgUGVyZm9ybWFuY2UgUHJlZGljdGlvbiB1c2luZyA8YnIvPiBDbGFzc2lmaWNhdGlvbiBhbmQgUmVncmVzc2lvbiINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQojIyMgUHJvamVjdCBCYWNrZ3JvdW5kDQpCYXNlZCBvbiBSZXphZWktRGVoYWdoYW5pLCBLZXNodmFyaSBhbmQgUGFraSAoMjAxOCksIGZhbWlseSByZWxhdGlvbnNoaXBzIGFyZSB2aXRhbCB0byBhIHN0dWRlbnQncyBhY2FkZW1pYyBwZXJmb3JtYW5jZS4gVXRpbGl6aW5nIHRoZSBTdHVkZW50IEFsY29ob2wgQ29uc3VtcHRpb24gZGF0YXNldCBmcm9tIEthZ2dsZSB3aGljaCBjb250YWlucyBpbmZvcm1hdGlvbiBvbiBzdHVkZW50IGFjYWRlbWljIHBlcmZvcm1hbmNlIGFuZCBmYW1pbHkgcmVsYXRpb25zaGlwcyBpbmZvcm1hdGlvbiwgd2UgYWltIHRvIGNyZWF0ZSBhIG1vZGVsIHRoYXQgY291bGQgcHJlZGljdCB3aGV0aGVyIGEgc3R1ZGVudCB3aWxsIHBhc3MgYW4gZXhhbSBhbmQgYWxzbyBwcmVkaWN0IHRoZSBtYXJrcyB0aGV5IHdpbGwgYWNxdWlyZS4gQnkgZG9pbmcgc28sIHdlIGFpbSB0byBmaWx0ZXIgYW5kIGFzc2lzdCBzdHVkZW50cyB0byBwZXJmb3JtIGJldHRlciBhY2FkZW1pY2FsbHkgYmFzZWQgb24gaG93IHdlbGwgdGhleSBwZXJmb3JtLg0KDQojIyMgSW1wb3J0IExpYnJhcnkNCmBgYHtyfQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoc3VwZXJtbCkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGNvcnJwbG90KQ0KbGlicmFyeShwYXRjaHdvcmspDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkoZTEwNzEpDQpzZXQuc2VlZCg0MikNCmBgYA0KV2Ugd2lsbCBmaXJzdCBpbXBvcnQgdGhlIHJlcXVpcmVkIGxpYnJhcmllcy4gZHBseXIgaXMgdXNlZCBmb3IgZGF0YSBtYW5pcHVsYXRpb24sIGdncGxvdDIsIHBhdGNod29yayBhbmQgY29ycnBsb3QgYXJlIHVzZWQgZm9yIGRhdGEgdmlzdWFsaXphdGlvbiB3aGlsZSBjYXJldCwgcmFuZG9tRm9yZXN0LCBlMTA3MSBhbmQgc3VwZXJtbCBhcmUgdXNlZCBmb3IgcHJlZGljdGl2ZSBtb2RlbGxpbmcuIEF0IHRoZSBzYW1lIHRpbWUsIHRoZSBzZWVkIGlzIHNlZWRlZCB0byA0MiB0byBlbnN1cmUgcmVwcm9kdWNpYmlsaXR5Lg0KDQojIyMgSW1wb3J0IGRhdGFzZXQNCmBgYHtyfQ0KZGZfbWF0PC1yZWFkLmNzdigic3R1ZGVudC1tYXQuY3N2IikNCmRmX3BvcjwtcmVhZC5jc3YoInN0dWRlbnQtcG9yLmNzdiIpDQpkZjwtcmJpbmQoZGZfbWF0LGRmX3BvcikNCmRmDQpgYGANClRoZSBkYXRhIGlzIHN0b3JlZCBpbiAyIHNlcGFyYXRlIENTViBiYXNlZCBvbiB0aGUgc3ViamVjdCB0aGF0IHRoZSBzdHVkZW50IGlzIHRha2luZy4gRHVlIHRvIHRoZSBsaW1pdGVkIHNhbXBsZXMgb2YgZGF0YSwgd2Ugd2lsbCBjb21iaW5lIHRoZSAyIGRhdGFzZXQgdG8gY3JlYXRlIGEgbW9yZSBjb21wcmVoZW5zaXZlIGRhdGFzZXQuIFRoZSByZXN1bHRpbmcgZGF0YXNldCBjb25zaXN0cyBvZiAxMDQ0IHJvd3MgYW5kIDMzIGNvbHVtbnMuIFdlIHdpbGwgdGhlbiBjaGVjayB3aGV0aGVyIHRoZXJlIGlzIGFueSBtaXNzaW5nIGRhdGEgaW4gdGhlIGRhdGFzZXQuDQoNCmBgYHtyfQ0KZGYgJT4lDQogIHNlbGVjdChldmVyeXRoaW5nKCkpICU+JSAgDQogIHN1bW1hcmlzZV9hbGwoZnVucyhzdW0oaXMubmEoLikpKSkNCmBgYA0KVXBvbiBhbmFseXNpcywgd2UgdmVyaWZpZWQgdGhhdCB0aGVyZSBhcmUgbm8gbWlzc2luZyB2YWx1ZXMgYWNyb3NzIHRoZSBjb2x1bW5zLCB0aHVzIHRoZSBkYXRhIGltcHV0YXRpb24gc3RlcCBjYW4gYmUgb21pdHRlZCBsYXRlciBvbi4NCg0KYGBge3J9DQpzYW1wbGUgPC0gc2FtcGxlLmludChuID0gbnJvdyhkZiksIHNpemUgPSBmbG9vciguOCpucm93KGRmKSksIHJlcGxhY2UgPSBGKQ0KdHJhaW4gPC0gZGZbc2FtcGxlLCBdDQp0ZXN0ICA8LSBkZlstc2FtcGxlLCBdDQpgYGANClRoZSBkYXRhIGlzIHNwbGl0IGluIDg6MiByYXRpbyBpbnRvIHRyYWluIGFuZCB0ZXN0IGRhdGEuIFRoZSByZXN1bHRpbmcgZGF0YSB3aWxsIGFsd2F5cyBiZSB0aGUgc2FtZSBzaW5jZSB3ZSBoYXZlIHNlZWRlZCBpdC4gVGhlIHRyYWluIGRhdGEgd2lsbCBiZSB1c2VkIGZvciBFREEgYW5kIHRyYWluaW5nIHdoaWxlIHRoZSB0ZXN0IGRhdGEgd2lsbCBiZSB1c2VkIHRvIGV2YWx1YXRlIG91ciBtb2RlbHMuDQoNCiMjIyBEYXRhIENsZWFuaW5nDQpgYGB7cn0NCnN0cih0cmFpbikNCmBgYA0KRmlyc3RseSwgd2UgaGF2ZSBhIGxvb2sgYXQgdGhlIG92ZXJ2aWV3IHN0cnVjdHVyZSBvZiB0aGUgZGF0YXNldCB0byB1bmRlcnN0YW5kIHRoZSB2YWx1ZXMgb2YgZWFjaCBmZWF0dXJlIGFuZCB0aGVpciByZXNwZWN0aXZlIGRhdGEgdHlwZS4gVGhlcmUgYXJlIGNvbHVtbnMgd2hpY2ggYXJlIGxvZ2ljYWwsIGludGVnZXIsIGFuZCBjaGFyYWN0ZXIuIFNvbWUgb2YgdGhlIGNoYXJhY3RlciBjb2x1bW5zIGhhdmUgb25seSAyIHZhbHVlcyB0aHVzIHdlIGNhbiBlbmNvZGUgdGhlbSBpbnRvIDAgb3IgMS4NCg0KYGBge3J9DQpsYWJlbF9lbmNvZGVyczwtYygpDQpiaW5hcnlfY29sPC1jKCkNCmZvciAoY29sIGluIG5hbWVzKHRyYWluKSl7DQogIGlmIChucm93KHVuaXF1ZSh0cmFpbltjb2xdKSk9PTIpew0KICAgIGxhYmVsX2VuY29kZXJzPC1jKGxhYmVsX2VuY29kZXJzLExhYmVsRW5jb2RlciRuZXcoKSkgDQogICAgYmluYXJ5X2NvbDwtYyhiaW5hcnlfY29sLGNvbCkNCiAgICB0cmFpbltjb2xdPC1sYWJlbF9lbmNvZGVyc1tbbGVuZ3RoKGxhYmVsX2VuY29kZXJzKV1dJGZpdF90cmFuc2Zvcm0odHJhaW5bW2NvbF1dKQ0KICB9DQp9DQpuYW1lcyhsYWJlbF9lbmNvZGVycyk8LWJpbmFyeV9jb2wNCmBgYA0KRWFjaCBvZiB0aGUgY29sdW1ucyB3aXRoIG9ubHkgMiB2YWx1ZXMgd2lsbCBiZSBlbmNvZGVkIGludG8gMCBvciAxIHRvIGFsbG93IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB2YXJpYWJsZXMgdG8gYmUgZWFzaWx5IHZpc3VhbGl6ZWQuIEEgbGFiZWwgZW5jb2RlciB3aWxsIGJlIHVzZWQgdG8gZml0IGVhY2ggY29sdW1ucyBhbmQgdHJhbnNmb3JtIGl0LiBUaGUgbGFiZWwgZW5jb2RlcnMgdHJhaW5lZCB3aWxsIGJlIHN0b3JlZCBpbiBhIHZlY3RvciBzbyB0aGF0IHdlIGNvdWxkIHJldXNlIGl0IHdpdGggdGhlIHRlc3QgZGF0YS4NCg0KIyMjIEV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMNCmBgYHtyfQ0KTTwtY29yKG11dGF0ZShzdWJzZXQodHJhaW4sc2VsZWN0PS1jKE1qb2IsRmpvYixyZWFzb24sZ3VhcmRpYW4pKSkpDQpjb3JycGxvdChNLG1ldGhvZCA9ICdjb2xvcicsdGwuY2V4ID0gMC44LCBjbC5jZXggPSAwLjgpDQpgYGANCkluIHRoZSBhYm92ZSBmaWd1cmUsIHdlIGNhbiBzZWUgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gYWxsIHRoZSBudW1lcmljYWwgZmVhdHVyZXMuIFdlIGNhbiBzZWUgdGhhdCBHMS4gRzIgYW5kIEczIHNlZW1zIHRvIGJlIGhpZ2hseSBjb3JyZWxhdGVkLg0KDQoNCmBgYHtyfQ0KZ2dwbG90KHRyYWluLGFlcyhHMz49MTApKStsYWJzKHg9IkczIix5PSJOdW1iZXIgb2YgU3R1ZGVudHMiLHRpdGxlPSJTdHVkZW50IFBlcmZvcm1hbmNlIGluIEczIikrZ2VvbV9iYXIoKQ0KYGBgDQpJbiB0aGUgZmlndXJlIGFib3ZlLCB3ZSBjYW4gc2VlIHRoYXQgYXJvdW5kIDEgb3V0IG9mIDQgc3R1ZGVudHMgZmFpbGVkIHRoZSBHMyBleGFtLiBXZSB3aWxsIGxvb2sgaW50byBzb21lIG9mIHRoZSBmZWF0dXJlcyB0aGF0IG1pZ2h0IGhhdmUgY29udHJpYnV0ZWQgdG8gaXQuDQoNCmBgYHtyfQ0KZ3JvdXBlZDwtc3Vic2V0KHRyYWluLHNlbGVjdD0tYyhNam9iLEZqb2IscmVhc29uLGd1YXJkaWFuKSkNCmdyb3VwZWRbIkcxIl08LWdyb3VwZWRbIkcxIl0+PTEwDQpncm91cGVkWyJHMiJdPC1ncm91cGVkWyJHMiJdPj0xMA0KZ3JvdXBlZFsiRzFfMiJdPC1ncm91cGVkWyJHMSJdK2dyb3VwZWRbIkcyIl0NCmdncGxvdChncm91cGVkICU+JSBncm91cF9ieShHMV8yKSAlPiUgc3VtbWFyaXNlKHRvdGFsPXN1bShHMz49MTApL24oKSksYWVzKHg9RzFfMix5PXRvdGFsKSkrZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknLGZpbGwgPSAic3RlZWxibHVlIikrbGFicyh4PSJUb3RhbCB0ZXN0cyBwYXNzZWQiLHk9IlBlcmNlbnRhZ2Ugb2Ygc3R1ZGVudHMgd2hvIHBhc3NlZCBHMyIsdGl0bGU9IlBlcmNlbnRhZ2Ugb2Ygc3R1ZGVudHMgd2hvIHBhc3NlZCBHMyBncm91cGVkIGJ5IHRvdGFsIHRlc3RzIHBhc3NlZCIpDQpgYGANCkluIHRoZSBjb3JyZWxhdGlvbiBncmFwaCwgd2UgaGF2ZSBvYnNlcnZlZCB0aGF0IEcxIGFuZCBHMiBhcmUgaGlnaGx5IGNvcnJlbGF0ZWQgdG8gRzMuIEFzIHNob3duIGluIHRoZSBmaWd1cmUgYWJvdmUsIGFsbW9zdCBhbGwgb2YgdGhlIHN0dWRlbnRzIHdobyBoYXZlIHBhc3NlZCBib3RoIHRoZSBwcmV2aW91cyB0ZXN0cyBwYXNzZWQgRzMuIEZvciB0aG9zZSB3aG8gaGF2ZSBub3QgcGFzc2VkIGFueSB0ZXN0cyBwcmV2aW91c2x5LCBvbmx5IGFib3V0IDIwJSBwYXNzZWQgRzMuIFRodXMsIHdlIHdpbGwgYW5hbHl6ZSB3aGF0IG1ha2VzIHRoZSB0d28gZ3JvdXBzIHBlcmZvcm0gc28gZGlmZmVyZW50bHkuDQoNCmBgYHtyfQ0KbnVtZXJpY19jb2xzPC1jKCkNCmZvciAoY29sIGluIG5hbWVzKGdyb3VwZWQpKXsNCiAgaWYgKGlzLm51bWVyaWMoZ3JvdXBlZFtbY29sXV0pKXsNCiAgICBudW1lcmljX2NvbHM8LWMobnVtZXJpY19jb2xzLGNvbCkNCiAgfQ0KfQ0KZ3JvdXBlZF9zY2FsZWQ8LXNjYWxlKGdyb3VwZWRbbnVtZXJpY19jb2xzXSkNCmdyb3VwZWRbbnVtZXJpY19jb2xzXTwtZ3JvdXBlZF9zY2FsZWQNCnN1bW1hcmlzZWQ8LWdyb3VwX2J5KGdyb3VwZWQsRzFfMikgJT4lIHN1bW1hcmlzZShhY3Jvc3MoZXZlcnl0aGluZygpLCBtZWFuKSkNCnN1bW1hcmlzZWQ8LXN1YnNldChzdW1tYXJpc2VkLHNlbGVjdD0tYyhHMSxHMixHMV8yLEczKSkNCmRpZmYyXzA8LXQoYWJzKHN1bW1hcmlzZWRbMyxdLXN1bW1hcmlzZWRbMSxdKSkNCmNvbG5hbWVzKGRpZmYyXzApPC1jKCJEaWZmZXJlbmNlIikNCmJhcmNoYXJ0KGRpZmYyXzBbb3JkZXIoZGlmZjJfMCksXSx4bGFiPSJOb3JtYWxpemVkIE1lYW4gRGlmZmVyZW5jZSIseWxhYj0iQ2hhcmFjdGVyaXN0aWMiLG1haW4gPSAiTm9ybWFsaXplZCBNZWFuIERpZmZlcmVuY2UgYmV0d2VlbiBEaWZmZXJlbnQgQ2hhcmFjdGVyaXN0aWNzIikNCmBgYA0KV2UgZ3JvdXBlZCB0aGUgc3R1ZGVudHMgYmFzZWQgb24gdGhlIG51bWJlciBvZiBwcmV2aW91cyB0ZXN0cyBwYXNzZWQgYW5kIGNhbGN1bGF0ZSB0aGUgZGlmZmVyZW5jZSBvZiBzdGFuZGFyZGl6ZWQgbWVhbiBiZXR3ZWVuIGRpZmZlcmVudCBmZWF0dXJlcyBhbmQgdmlzdWFsaXplZCBpbiB0aGUgZmlndXJlIGFib3ZlLiBUaGUgbWFpbiBmZWF0dXJlIHRoYXQgY29udHJpYnV0ZWQgdG8gdGhlaXIgZGlmZmVyZW5jZSBpcyB0aGUgbnVtYmVyIG9mIGZhaWx1cmVzIGZ1cnRoZXIgYmVmb3JlIGluIHRoZSBjbGFzcywgd2hpY2ggY29ycmVzcG9uZGVkIHRvIG91ciBmaWd1cmUgcHJldmlvdXNseS4gQW5vdGhlciBrZXkgZGlmZmVyZW5jZSBpcyB0aGUgZGVzaXJlIHRvIHB1cnN1ZSBmdXJ0aGVyIGVkdWNhdGlvbiwgZm9sbG93ZWQgYnkgdGhlIHBhcmVudHMnIGVkdWNhdGlvbiBsZXZlbCBhbmQgdGhlIHRpbWUgdGhleSBoYXZlIHNwZW50IGluIHN0dWRpZXMuDQoNCmBgYHtyfQ0KZ3JvdXBlZDwtc3Vic2V0KHRyYWluLHNlbGVjdD0tYyhNam9iLEZqb2IscmVhc29uLGd1YXJkaWFuKSkNCmdyb3VwZWRbIkcxIl08LWdyb3VwZWRbIkcxIl0+PTEwDQpncm91cGVkWyJHMiJdPC1ncm91cGVkWyJHMiJdPj0xMA0KZ3JvdXBlZFsiRzFfMiJdPC1ncm91cGVkWyJHMSJdK2dyb3VwZWRbIkcyIl0NCmdncGxvdChncm91cGVkICU+JSBncm91cF9ieShHMV8yKSAlPiUgc3VtbWFyaXNlKGZhaWx1cmVzPW1lYW4oZmFpbHVyZXMpKSxhZXMoeD1HMV8yLHk9ZmFpbHVyZXMpKStnZW9tX2JhcihzdGF0PSdpZGVudGl0eScpK2xhYnMoeD0iVG90YWwgdGVzdHMgcGFzc2VkIix5PSJQZXJjZW50YWdlIG9mIHN0dWRlbnQgd2l0aCBwcmV2aW91cyBmYWlsdXJlcyIsdGl0bGU9IlBlcmNlbnRhZ2Ugb2Ygc3R1ZGVudCB3aXRoIHByZXZpb3VzIGZhaWx1cmVzIGJhc2VkIG9uIHRlc3RzIHBhc3NlZCIpDQpgYGANCkFzIHdlIGNhbiBvYnNlcnZlIGluIHRoZSBmaWd1cmUgYWJvdmUsIGFsbW9zdCA4MCUgb2Ygc3R1ZGVudHMgd2hvIGhhdmUgZmFpbGVkIEcxIGFuZCBHMiBoYXZlIGZhaWx1cmVzIHByZXZpb3VzbHkuDQoNCmBgYHtyfQ0KZ2dwbG90KGdyb3VwZWQgJT4lIGdyb3VwX2J5KEcxXzIpICU+JSBzdW1tYXJpc2UoaGlnaGVyPW1lYW4oaGlnaGVyKSksYWVzKHg9RzFfMix5PWhpZ2hlcikpK2dlb21fYmFyKHN0YXQ9J2lkZW50aXR5JykrbGFicyh4PSJUb3RhbCB0ZXN0cyBwYXNzZWQiLHk9IlBlcmNlbnRhZ2Ugb2Ygc3R1ZGVudCBkZXNpcmluZyBmdXJ0aGVyIGVkdWNhdGlvbiIsdGl0bGU9IlBlcmNlbnRhZ2Ugb2Ygc3R1ZGVudCBkZXNpcmluZyBmdXJ0aGVyIGVkdWNhdGlvbiBiYXNlZCBvbiB0ZXN0cyBwYXNzZWQiKQ0KYGBgDQpBdCB0aGUgc2FtZSB0aW1lLCBtb3JlIHRoYW4gMjAlIG9mIHRoZSBzdHVkZW50cyB3aG8gaGF2ZSBub3QgcGFzc2VkIGFueSB0ZXN0cyBkbyBub3QgZGVzaXJlIHRvIGZ1cnRoZXIgdGhlaXIgc3R1ZGllcy4NCg0KYGBge3J9DQpnZ3Bsb3QoZ3JvdXBlZCAlPiUgZ3JvdXBfYnkoRzFfMikgJT4lIHN1bW1hcmlzZShzdHVkeXRpbWU9bWVhbihzdHVkeXRpbWUpKSxhZXMoeD1HMV8yLHk9c3R1ZHl0aW1lKSkrZ2VvbV9iYXIoc3RhdD0naWRlbnRpdHknKStsYWJzKHg9IlRvdGFsIHRlc3RzIHBhc3NlZCIseT0iTWVhbiBzdHVkeXRpbWUiLHRpdGxlPSJNZWFuIHN0dWR5dGltZSBiYXNlZCBvbiB0ZXN0cyBwYXNzZWQiKQ0KYGBgDQpUaGV5IGFsc28gc3R1ZHkgaGFsZiBhbiBob3VyIGxlc3MgY29tcGFyZWQgdG8gdGhvc2Ugd2hvIHBhc3MgYWxsIHRoZSB0ZXN0Lg0KDQpgYGB7cn0NCmdncGxvdChncm91cGVkICU+JSAgZ3JvdXBfYnkoRzFfMikgJT4lIHN1bW1hcmlzZShQZWR1PW1lYW4oTWVkdStGZWR1KSksYWVzKHg9RzFfMix5PVBlZHUpKStnZW9tX2JhcihzdGF0PSdpZGVudGl0eScpK2xhYnMoeD0iVG90YWwgdGVzdHMgcGFzc2VkIix5PSJQYXJlbnRzJyBhdmVyYWdlIGVkdWNhdGlvbiBsZXZlbCIsdGl0bGU9IlBhcmVudHMnIGF2ZXJhZ2UgZWR1Y2F0aW9uIGxldmVsIGJhc2VkIG9uIHRlc3RzIHBhc3NlZCIpDQpgYGANClRoZSBzdHVkZW50cycgcGFyZW50cyBhbHNvIGhhdmUgYSBsb3dlciBhdmVyYWdlIGVkdWNhdGlvbiBsZXZlbCBjb21wYXJlZCB0byB0aGUgc3R1ZGVudHMgd2hvIGFyZSBwZXJmb3JtaW5nIGJldHRlci4NCg0KYGBge3J9DQpnZ3Bsb3QodHJhaW4sYWVzKHg9ZmFtcmVsKSkrZ2VvbV9oaXN0b2dyYW0oKStsYWJzKHg9IkZhbWlseSByZWxhdGlvbnNoaXAiLHk9Ik51bWJlciBvZiBzdHVkZW50cyIsdGl0bGU9IkZhbWlseSByZWxhdGlvbnNoaXAgZGlzdHJpYnV0aW9uIikNCmBgYA0KVGhlIGZpZ3VyZSBhYm92ZSBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBzdHVkZW50cycgZmFtaWx5IHJlbGF0aW9uc2hpcHMuIE1vc3Qgb2YgdGhlIHN0dWRlbnRzIGhhdmUgYSBzYXRpc2ZhY3RvcnkgcmVsYXRpb25zaGlwIHdpdGggdGhlaXIgZmFtaWxpZXMgYXMgNCBpcyB0aGUgbW9kZSBvZiB0aGUgZmVhdHVyZS4NCg0KYGBge3J9DQpncm91cGVkPC1zdWJzZXQodHJhaW4sc2VsZWN0PS1jKE1qb2IsRmpvYixyZWFzb24sZ3VhcmRpYW4pKQ0KZ3JvdXBlZFsiRzEiXTwtZ3JvdXBlZFsiRzEiXT49MTANCmdyb3VwZWRbIkcyIl08LWdyb3VwZWRbIkcyIl0+PTEwDQpncm91cGVkWyJHMV8yIl08LWdyb3VwZWRbIkcxIl0rZ3JvdXBlZFsiRzIiXQ0KZ3JvdXBlZFsiZmFtcmVsIl08LWdyb3VwZWRbImZhbXJlbCJdPj00DQojU2NhbGUgbnVtZXJpY2FsIGNvbHVtbnMNCm51bWVyaWNfY29sczwtYygpDQpmb3IgKGNvbCBpbiBuYW1lcyhncm91cGVkKSl7DQogIGlmIChpcy5udW1lcmljKGdyb3VwZWRbW2NvbF1dKSl7DQogICAgbnVtZXJpY19jb2xzPC1jKG51bWVyaWNfY29scyxjb2wpDQogIH0NCn0NCmdyb3VwZWRfc2NhbGVkPC1zY2FsZShncm91cGVkW251bWVyaWNfY29sc10pDQpncm91cGVkW251bWVyaWNfY29sc108LWdyb3VwZWRfc2NhbGVkDQpzdW1tYXJpc2VkPC1ncm91cF9ieShncm91cGVkLGZhbXJlbCkgJT4lIHN1bW1hcmlzZShhY3Jvc3MoZXZlcnl0aGluZygpLCBtZWFuKSkNCnN1bW1hcmlzZWQ8LXN1YnNldChzdW1tYXJpc2VkLHNlbGVjdD0tKGZhbXJlbCkpDQpkaWZmZXJlbmNlPC10KHN1bW1hcmlzZWRbMixdLXN1bW1hcmlzZWRbMSxdKQ0KY29sbmFtZXMoZGlmZmVyZW5jZSk8LWMoIkRpZmZlcmVuY2UiKQ0KYmFyY2hhcnQoZGlmZmVyZW5jZVtvcmRlcihkaWZmZXJlbmNlKSxdLHhsYWI9Ik5vcm1hbGl6ZWQgTWVhbiBEaWZmZXJlbmNlIix5bGFiPSJDaGFyYWN0ZXJpc3RpYyIpDQpgYGANCklmIHdlIGNvbXBhcmUgdGhvc2Ugc3R1ZGVudHMgd2l0aCBnb29kIGZhbWlseSByZWxhdGlvbnNoaXAgKD49NCkgd2l0aCB0aG9zZSB3aG8gaGF2ZSB3b3JzZSwgd2UgY2FuIG9ic2VydmUgdGhhdCB0aGVzZSBzdHVkZW50cyBjb25zdW1lIG1vcmUgYW1vdW50cyBvZiBhbGNvaG9sIGluIHRoZSB3ZWVrZW5kcy4gVGhleSBhbHNvIGhhdmUgbGVzcyBmcmVlIHRpbWUgYW5kIHN0dWR5IHRpbWUgb24gaGFuZC4NCg0KYGBge3J9DQojIENyZWF0ZSBjdXN0b20gY29sb3IgcGFsZXR0ZQ0KY3VzdG9tLmNvbCA8LSBjKCIjYWJlYmFkIiwgIiNmZmZmOTkiLCAiI0FEQzJGRiIsICIjZjBiMmYwIiwgIiNmZjk5OTkiKQ0KDQojIENyZWF0ZSB0aGUgcGxvdHMgZm9yIHdlZWtlbmQgYWxjb2hvbCBjb25zdW1wdGlvbg0KcGxvdF93ZWVrZW5kIDwtIGdncGxvdChkZiwgYWVzKHggPSBhcy5mYWN0b3IoV2FsYyksIHkgPSBHMSwgZmlsbCA9IGFzLmZhY3RvcihXYWxjKSkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjdXN0b20uY29sKSArDQogIHRoZW1lX2J3KCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgeGxhYigiQWxjb2hvbCBDb25zdW1wdGlvbiBMZXZlbCIpICsNCiAgeWxhYigiRmlyc3QgUGVyaW9kIEdyYWRlIChHMSkiKSArDQogIGdndGl0bGUoIldlZWtlbmRzIikgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGMoIlZlcnkgTG93IiwgIkxvdyIsICJNb2RlcmF0ZSIsICJIaWdoIiwgIlZlcnkgSGlnaCIpKQ0KDQojIENyZWF0ZSB0aGUgcGxvdHMgZm9yIHdlZWtkYXkgYWxjb2hvbCBjb25zdW1wdGlvbg0KcGxvdF93ZWVrZGF5IDwtIGdncGxvdChkZiwgYWVzKHggPSBhcy5mYWN0b3IoRGFsYyksIHkgPSBHMSwgZmlsbCA9IGFzLmZhY3RvcihEYWxjKSkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjdXN0b20uY29sKSArDQogIHRoZW1lX2J3KCkgKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsNCiAgeGxhYigiQWxjb2hvbCBDb25zdW1wdGlvbiBMZXZlbCIpICsNCiAgeWxhYigiRmlyc3QgUGVyaW9kIEdyYWRlIChHMSkiKSArDQogIGdndGl0bGUoIldlZWtkYXlzIikgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGMoIlZlcnkgTG93IiwgIkxvdyIsICJNb2RlcmF0ZSIsICJIaWdoIiwgIlZlcnkgSGlnaCIpKQ0KDQojIENyZWF0ZSBjb21tb24gdGl0bGUNCmNvbW1vbl90aXRsZSA8LSBwbG90X2Fubm90YXRpb24odGl0bGUgPSAiRzEgR3JhZGUgdnMgQWxjb2hvbCBDb25zdW1wdGlvbiBMZXZlbCBmb3IgV2Vla2RheXMgYW5kIFdlZWtlbmRzICIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lID0gdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZSA9IDE0LCBmYWNlID0gImJvbGQiKSkpDQoNCiMgQ29tYmluZSB0aGUgcGxvdHMgYW5kIGFkZCB0aGUgY29tbW9uIHRpdGxlDQpjb21iaW5lZF9wbG90IDwtIHBsb3Rfd2Vla2VuZCArIHBsb3Rfd2Vla2RheSArIHBsb3RfbGF5b3V0KG5jb2wgPSAyLCB3aWR0aHMgPSBjKDIsIDIpKSArIGNvbW1vbl90aXRsZQ0KDQojIERpc3BsYXkgdGhlIGNvbWJpbmVkIHBsb3QNCnByaW50KGNvbWJpbmVkX3Bsb3QpDQpgYGANCk1lZGlhbiBncmFkZXMgdGVuZCB0byBkZWNyZWFzZSBhcyBhbGNvaG9sIGNvbnN1bXB0aW9uIGluY3JlYXNlcyBmb3IgYm90aCB3ZWVrZW5kcyBhbmQgd2Vla2RheXMuIFRoZSAnVmVyeSBIaWdoJyBhbmQgJ0hpZ2gnIGFsY29ob2wgY29uc3VtcHRpb24gZ3JvdXBzIGFwcGVhciB0byBoYXZlIHRoZSBsb3dlc3QgbWVkaWFuIGdyYWRlLCBhcyB3ZWxsIGFzIHRoZSBzbWFsbGVyIGludGVycXVhcnRpbGUgcmFuZ2UsIGluZGljYXRpbmcgdGhhdCBzdHVkZW50cyB3aG8gY29uc3VtZSBhIGxvdCBvZiBhbGNvaG9sIHRlbmQgdG8gaGF2ZSBsb3dlciBhbmQgbW9yZSBjb25zaXN0ZW50IGdyYWRlcy4NCg0KYGBge3J9DQojIENyZWF0ZSBjdXN0b20gY29sb3IgcGFsZXR0ZQ0KY3VzdG9tLmNvbCA8LSBjKCIjYWJlYmFkIiwgIiNmZmZmOTkiLCAiI0FEQzJGRiIsICIjZjBiMmYwIiwgIiNmZjk5OTkiKQ0KDQojIENyZWF0ZSB0aGUgcGxvdHMgZm9yIHdlZWtlbmQgYWxjb2hvbCBjb25zdW1wdGlvbg0KcGxvdF93ZWVrZW5kIDwtIGdncGxvdChkZiwgYWVzKHggPSBhcy5mYWN0b3IoRGFsYyksIHkgPSBHMiwgZmlsbCA9IGFzLmZhY3RvcihEYWxjKSkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjdXN0b20uY29sKSsNCiAgdGhlbWVfYncoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICB4bGFiKCJBbGNvaG9sIENvbnN1bXB0aW9uIExldmVsIikgKw0KICB5bGFiKCJTZWNvbmQgUGVyaW9kIEdyYWRlIChHMikiKSArDQogIGdndGl0bGUoIldlZWtlbmRzIikgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGMoIlZlcnkgTG93IiwgIkxvdyIsICJNb2RlcmF0ZSIsICJIaWdoIiwgIlZlcnkgSGlnaCIpKQ0KDQojIENyZWF0ZSB0aGUgcGxvdHMgZm9yIHdlZWtkYXkgYWxjb2hvbCBjb25zdW1wdGlvbg0KcGxvdF93ZWVrZGF5IDwtIGdncGxvdChkZiwgYWVzKHggPSBhcy5mYWN0b3IoV2FsYyksIHkgPSBHMiwgZmlsbCA9IGFzLmZhY3RvcihXYWxjKSkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjdXN0b20uY29sKSsNCiAgdGhlbWVfYncoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICB4bGFiKCJBbGNvaG9sIENvbnN1bXB0aW9uIExldmVsIikgKw0KICB5bGFiKCJTZWNvbmQgUGVyaW9kIEdyYWRlIChHMikiKSArDQogIGdndGl0bGUoIldlZWtkYXlzIikgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGMoIlZlcnkgTG93IiwgIkxvdyIsICJNb2RlcmF0ZSIsICJIaWdoIiwgIlZlcnkgSGlnaCIpKQ0KDQojIENyZWF0ZSBjb21tb24gdGl0bGUNCmNvbW1vbl90aXRsZSA8LSBwbG90X2Fubm90YXRpb24odGl0bGUgPSAiRzIgR3JhZGUgdnMgQWxjb2hvbCBDb25zdW1wdGlvbiBMZXZlbCBmb3IgV2Vla2RheXMgYW5kIFdlZWtlbmRzICIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoZW1lID0gdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgc2l6ZSA9IDE0LCBmYWNlID0gImJvbGQiKSkpDQoNCiMgQ29tYmluZSB0aGUgcGxvdHMgYW5kIGFkZCB0aGUgY29tbW9uIHRpdGxlDQpjb21iaW5lZF9wbG90IDwtIHBsb3Rfd2Vla2VuZCArIHBsb3Rfd2Vla2RheSArIHBsb3RfbGF5b3V0KG5jb2wgPSAyLCB3aWR0aHMgPSBjKDIsIDIpKSArIGNvbW1vbl90aXRsZQ0KDQojIERpc3BsYXkgdGhlIGNvbWJpbmVkIHBsb3QNCnByaW50KGNvbWJpbmVkX3Bsb3QpDQpgYGANClNpbWlsYXIgdG8gR3JhZGUgMSwgdGhlICdWZXJ5IEhpZ2gnLCAnSGlnaCcsIGFuZCAnTW9kZXJhdGUnIGFsY29ob2wgY29uc3VtcHRpb24gZ3JvdXBzIGFyZSBhc3NvY2lhdGVkIHdpdGggbG93ZXIgbWVkaWFuIGdyYWRlcy4gVGhpcyBzdWdnZXN0cyB0aGF0IGluY3JlYXNlZCBhbGNvaG9sIGNvbnN1bXB0aW9uIHRlbmRzIHRvIGNvcnJlc3BvbmQgd2l0aCBsb3dlciBhY2FkZW1pYyBwZXJmb3JtYW5jZS4NCg0KYGBge3J9DQpYX3RyYWluPC1zdWJzZXQodHJhaW4sc2VsZWN0PS1jKEczKSkNCnlfdHJhaW48LXRyYWluJEczDQpYX3Rlc3Q8LXN1YnNldCh0ZXN0LHNlbGVjdD0tYyhHMykpDQp5X3Rlc3Q8LXRlc3QkRzMNCnlfdHJhaW48LWlmZWxzZSh5X3RyYWluPj0xMCxUUlVFLEZBTFNFKQ0KeV90ZXN0PC1pZmVsc2UoeV90ZXN0Pj0xMCxUUlVFLEZBTFNFKQ0KYGBgDQpUaGUgdGFyZ2V0IGZlYXR1cmUgaXMgdXNlZCBhcyB0aGUgeSB3aGlsZSB0aGUgcmVzdCBvZiB0aGUgZmVhdHVyZXMgYXJlIHVzZWQgYXMgdGhlIFguIFRoZSB5IGZlYXR1cmUgd2lsbCBiZSBHMy4gRm9yIHJlZ3Jlc3Npb24sIGl0IHdpbGwgYmUgdGhlIHNjb3JlIGZyb20gMC0yMCwgd2hpbGUgZm9yIGNsYXNzaWZpY2F0aW9uLCBpdCB3aWxsIGJlIHRydWUgb3IgZmFsc2UgYmFzZWQgb24gd2hldGhlciB0aGUgc3R1ZGVudHMgcGFzc2VkIHRoZSBleGFtLg0KIA0KIyMjIFByZXByb2Nlc3MgVHJhaW4gRGF0YQ0KYGBge3J9DQojU2NhbGUgbnVtZXJpY2FsIGNvbHVtbnMNCm51bWVyaWNfY29sczwtYygpDQpmb3IgKGNvbCBpbiBuYW1lcyhYX3RyYWluKSl7DQogIGlmIChpcy5udW1lcmljKFhfdHJhaW5bW2NvbF1dKSl7DQogICAgbnVtZXJpY19jb2xzPC1jKG51bWVyaWNfY29scyxjb2wpDQogIH0NCn0NClhfdHJhaW5fc2NhbGVkPC1zY2FsZShYX3RyYWluW251bWVyaWNfY29sc10pDQpYX3RyYWluW251bWVyaWNfY29sc108LVhfdHJhaW5fc2NhbGVkDQpgYGANCg0KVGhlIG51bWVyaWNhbCBjb2x1bW5zIGluIFhfdHJhaW4gaXMgc2NhbGVkIHNvIHRoYXQgZWFjaCBvZiB0aGUgZGlmZmVyZW50IGZlYXR1cmVzIGhhdmUgdGhlIHNhbWUgc2NhbGUuIFRoaXMgYWxsb3cgYmV0dGVyIGludGVycHJldGF0aW9uIG9mIHRoZSBkYXRhIGFuZCBzcGVlZHMgdXAgdHJhaW5pbmcgYXMgdGhlIHJhbmdlIGlzIHJlZHVjZWQuIEEgY29weSBvZiB0aGUgc2NhbGVkIGRhdGEgaXMgc3RvcmVkIHRvIGFsbG93IHRoZSBzY2FsZSB0byBiZSByZXVzZWQgb24gdGhlIHRlc3RpbmcgZGF0YS4NCg0KYGBge3J9DQojIE9uZSBob3QgZW5jb2RpbmcNCmRteSA8LSBkdW1teVZhcnMoIiB+IC4iLGRhdGE9WF90cmFpbikNClhfdHJhaW4gPC0gZGF0YS5mcmFtZShwcmVkaWN0KGRteSxuZXdkYXRhPVhfdHJhaW4pKQ0KWF90cmFpbg0KYGBgDQpUaGUgcmVtYWluaW5nIGNhdGVnb3JpY2FsIGZlYXR1cmVzIGluIHRoZSB0cmFpbmluZyBmZWF0dXJlcyB3aWxsIGJlIGVuY29kZWQgdXNpbmcgb25lLWhvdCBlbmNvZGluZy4gVGhlIGVuY29kZXIgaXMgc3RvcmVkIHRvIGJlIHJldXNlZCBvbiB0ZXN0aW5nIGRhdGEuDQoNCiMjIyBUcmFuc2Zvcm0gVGVzdCBEYXRhDQpgYGB7cn0NCmZvciAoY29sIGluIG5hbWVzKGxhYmVsX2VuY29kZXJzKSl7DQogIFhfdGVzdFtjb2xdPC1sYWJlbF9lbmNvZGVyc1tbY29sXV0kdHJhbnNmb3JtKFhfdGVzdFtbY29sXV0pDQp9DQpYX3Rlc3RbbnVtZXJpY19jb2xzXTwtc2NhbGUoWF90ZXN0W251bWVyaWNfY29sc10sY2VudGVyPWF0dHIoWF90cmFpbl9zY2FsZWQsICJzY2FsZWQ6Y2VudGVyIiksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGU9YXR0cihYX3RyYWluX3NjYWxlZCwgInNjYWxlZDpzY2FsZSIpKQ0KWF90ZXN0IDwtIGRhdGEuZnJhbWUocHJlZGljdChkbXksbmV3ZGF0YT1YX3Rlc3QsbmEuYWN0aW9uID0gbmEucGFzcykpDQpYX3Rlc3QNCmBgYA0KVGhlIHNhbWUgdHJhbnNmb3JtYXRpb24gaXMgYXBwbGllZCB0byB0aGUgdGVzdCBkYXRhIHNvIGl0IGlzIG9mIHRoZSBzYW1lIHNjYWxlIHdpdGggdGhlIHRyYWluIGRhdGEgYW5kIGhhdmUgdGhlIHNhbWUgZmVhdHVyZXMuIEl0IHdpbGwgc2ltaWxhcmx5IHVuZGVyZ28gZGF0YSBlbmNvZGluZyBhbmQgZGF0YSBub3JtYWxpemF0aW9uLg0KDQojIyMgQ2xhc3NpZmljYXRpb24gTW9kZWwNCkluIG91ciBjbGFzc2lmaWNhdGlvbiBhbmFseXNpcywgd2UgZW1wbG95ZWQgZm91ciBtYWNoaW5lIGxlYXJuaW5nIG1vZGVscyB3aGljaCB3ZXJlIDxiPlJhbmRvbSBGb3Jlc3QsIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmUgKFNWTSksIGstTmVhcmVzdCBOZWlnaGJvcnMgKGstTk4pLCBhbmQgTmFpdmUgQmF5ZXM8L2I+IHRvIHByZWRpY3Qgd2hldGhlciB0aGUgc3R1ZGVudHMgd2lsbCBwYXNzIG9yIGZhaWwgaW4gdGhlIHRoaXJkIGdyYWRlIChHMykuIEVhY2ggbW9kZWwgdW5kZXJ3ZW50IHRyYWluaW5nIG9uIHRoZSBYX3RyYWluIHdpdGggdGhlIHRhcmdldCB2YXJpYWJsZSBpbiB5X3RyYWluLiBUaGUgbW9kZWxzIHdlcmUgdGhlbiBldmFsdWF0ZWQgd2l0aCBhIHRlc3Qgc2V0LiBUaGUgZXZhbHVhdGlvbiBtZXRyaWNzIGluY2x1ZGUgYSBjb25mdXNpb24gbWF0cml4LCBhbG9uZyB3aXRoIGtleSBwZXJmb3JtYW5jZSBtZXRyaWNzIGxpa2UgYWNjdXJhY3ksIHByZWNpc2lvbiwgcmVjYWxsLCBhbmQgRjEgc2NvcmUuIA0KDQpgYGB7cn0NCmV2YWx1YXRlX21vZGVsIDwtIGZ1bmN0aW9uKHByZWRpY3Rpb25zLCBhY3R1YWwsIG1vZGVsX25hbWUpIHsNCiAgY29uZnVzaW9uX21hdHJpeCA8LSB0YWJsZShBY3R1YWwgPSBhcy5mYWN0b3IoYWN0dWFsKSwgUHJlZGljdGVkID0gYXMuZmFjdG9yKHByZWRpY3Rpb25zKSkNCiAgY2F0KCJDb25mdXNpb24gTWF0cml4IGZvciIsIG1vZGVsX25hbWUsIlxuIikNCiAgcHJpbnQudGFibGUoY29uZnVzaW9uX21hdHJpeCkNCiAgY2F0KCJcbiIpDQogIA0KICBhY2N1cmFjeSA8LSBzdW0oZGlhZyhjb25mdXNpb25fbWF0cml4KSkgLyBzdW0oY29uZnVzaW9uX21hdHJpeCkNCiAgcHJlY2lzaW9uIDwtIGNvbmZ1c2lvbl9tYXRyaXhbIlRSVUUiLCAiVFJVRSJdIC8gc3VtKGNvbmZ1c2lvbl9tYXRyaXhbLCAiVFJVRSJdKQ0KICByZWNhbGwgPC0gY29uZnVzaW9uX21hdHJpeFsiVFJVRSIsICJUUlVFIl0gLyBzdW0oY29uZnVzaW9uX21hdHJpeFsiVFJVRSIsIF0pDQogIGYxX3Njb3JlIDwtIDIgKiAocHJlY2lzaW9uICogcmVjYWxsKSAvIChwcmVjaXNpb24gKyByZWNhbGwpDQogIA0KICBtZXRyaWNzIDwtIGxpc3QoQWNjdXJhY3kgPSBhY2N1cmFjeSwgUHJlY2lzaW9uID0gcHJlY2lzaW9uLCBSZWNhbGwgPSByZWNhbGwsIEYxX1Njb3JlID0gZjFfc2NvcmUpDQogIHJldHVybihtZXRyaWNzKQ0KfQ0KDQpwZXJmb3JtX2NsYXNzaWZpY2F0aW9uIDwtIGZ1bmN0aW9uKFhfdHJhaW4sIHlfdHJhaW4sIFhfdGVzdCwgeV90ZXN0KSB7DQogIHNldC5zZWVkKDQyKQ0KICBtb2RlbF9uYW1lcyA8LSBjKCJSYW5kb21fRm9yZXN0IiwgIlNWTSIsICJrX05OIiwgIk5haXZlX0JheWVzIikNCiAgcmVzdWx0cyA8LSBsaXN0KCkNCiAgbW9kZWxzPC1saXN0KCkNCiAgDQogIGZvciAobW9kZWxfbmFtZSBpbiBtb2RlbF9uYW1lcykgew0KICAgIGlmIChtb2RlbF9uYW1lID09ICJSYW5kb21fRm9yZXN0Iikgew0KICAgICAgbW9kZWwgPC0gcmFuZG9tRm9yZXN0KHggPSBYX3RyYWluLCB5ID0gYXMuZmFjdG9yKHlfdHJhaW4pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gMTAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG10cnkgPSBzcXJ0KG5jb2woWF90cmFpbikgLSAxKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBub2Rlc2l6ZSA9IDEsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgaW1wb3J0YW5jZSA9IFRSVUUsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJjbGFzc2lmaWNhdGlvbiIpDQogICAgICANCiAgICB9IGVsc2UgaWYgKG1vZGVsX25hbWUgPT0gIlNWTSIpIHsNCiAgICAgIG1vZGVsIDwtIHN2bShhcy5mYWN0b3IoeV90cmFpbikgfiAuLCBkYXRhID0gWF90cmFpbiwga2VybmVsID0gInJhZGlhbCIsIGNvc3QgPSAxKQ0KICAgICAgDQogICAgfSBlbHNlIGlmIChtb2RlbF9uYW1lID09ICJrX05OIikgew0KICAgICAgbW9kZWwgPC0gdHJhaW4oeCA9IFhfdHJhaW4sIHkgPSBhcy5mYWN0b3IoeV90cmFpbiksDQogICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAia25uIiwNCiAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCksDQogICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUoayA9IDE1KSkNCiAgICAgIA0KICAgIH0gZWxzZSBpZiAobW9kZWxfbmFtZSA9PSAiTmFpdmVfQmF5ZXMiKSB7DQogICAgICBtb2RlbCA8LSBuYWl2ZUJheWVzKHggPSBYX3RyYWluLCB5ID0gYXMuZmFjdG9yKHlfdHJhaW4pKQ0KICAgIH0NCiAgICBwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG1vZGVsLCBYX3Rlc3QpDQogICAgbWV0cmljcyA8LSBldmFsdWF0ZV9tb2RlbChwcmVkaWN0aW9ucywgeV90ZXN0LCBtb2RlbF9uYW1lKQ0KICAgIHJlc3VsdHNbW21vZGVsX25hbWVdXSA8LSBtZXRyaWNzDQogIH0NCiAgcmVzdWx0c19kZiA8LSBkby5jYWxsKHJiaW5kLCByZXN1bHRzKQ0KICByZXR1cm4ocmVzdWx0c19kZikNCn0NCg0KcmVzdWx0cyA8LSBwZXJmb3JtX2NsYXNzaWZpY2F0aW9uKFhfdHJhaW4sIHlfdHJhaW4sIFhfdGVzdCwgeV90ZXN0KQ0KcHJpbnQocmVzdWx0cykNCmBgYA0KQW1vbmcgdGhlIG1vZGVscyBlbXBsb3llZCwgdGhlIDxiPlJhbmRvbSBGb3Jlc3QgZW1lcmdlZCBhcyB0aGUgdG9wIHBlcmZvcm1lcjwvYj4gZm9yIHRoZSBnaXZlbiBjbGFzc2lmaWNhdGlvbiB0YXNrLiBJdCBhY2hpZXZlZCBhbiBpbXByZXNzaXZlIGFjY3VyYWN5IG9mIDkxLjM5JSwgc2hvd2Nhc2luZyBhIHdlbGwtYmFsYW5jZWQgcHJlY2lzaW9uIG9mIDk0LjA4JSwgcmVjYWxsIG9mIDk1LjIxJSBhbmQgRjEgc2NvcmUgb2YgOTQuNjQlLiBUaGVzZSByZXN1bHRzIGluZGljYXRlIHRoZSBtb2RlbCdzIGFiaWxpdHkgdG8gZWZmZWN0aXZlbHkgY2xhc3NpZnkgdGhlIHN0dWRlbnRzJyBncmFkZXMuDQoNCiMjIyBGZWF0dXJlIEltcG9ydGFuY2UgZm9yIFJhbmRvbSBGb3Jlc3QNCmBgYHtyLCBmaWcuaGVpZ2h0PTZ9DQptb2RlbCA8LSByYW5kb21Gb3Jlc3QoeCA9IFhfdHJhaW4sIHkgPSBhcy5mYWN0b3IoeV90cmFpbiksICAjIENvbnZlcnQgeV90cmFpbiB0byBmYWN0b3IgZm9yIGNsYXNzaWZpY2F0aW9uDQogICAgICAgICAgICAgICAgICAgICAgbnRyZWUgPSAxMDAsICAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICBtdHJ5ID0gc3FydChuY29sKFhfdHJhaW4pIC0gMSksICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgbm9kZXNpemUgPSAxLCAgICAgICAgICAgICAgICAgICAgICAgICAgICANCiAgICAgICAgICAgICAgICAgICAgICBpbXBvcnRhbmNlID0gVFJVRSwgICAgICAgICAgICAgICAgICAgICAgDQogICAgICAgICAgICAgICAgICAgICAgdHlwZSA9ICJjbGFzc2lmaWNhdGlvbiIpIA0KDQojIENhbGN1bGF0ZSBmZWF0dXJlIGltcG9ydGFuY2UgZm9yIFJmIChiZXN0IG1vZGVsKQ0KZmVhdHVyZV9pbXBvcnRhbmNlIDwtIGltcG9ydGFuY2UobW9kZWwpDQoNCiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSBmb3IgZmVhdHVyZSBpbXBvcnRhbmNlDQpmZWF0dXJlX2ltcG9ydGFuY2VfZGYgPC0gZGF0YS5mcmFtZSgNCiAgRmVhdHVyZXMgPSByb3cubmFtZXMoZmVhdHVyZV9pbXBvcnRhbmNlKSwNCiAgSW1wb3J0YW5jZSA9IGZlYXR1cmVfaW1wb3J0YW5jZVssICJNZWFuRGVjcmVhc2VBY2N1cmFjeSJdDQopDQoNCiMgU29ydCB0aGUgZGF0YSBmcmFtZSBieSBpbXBvcnRhbmNlIChkZXNjZW5kaW5nIG9yZGVyKQ0KZmVhdHVyZV9pbXBvcnRhbmNlX2RmIDwtIGZlYXR1cmVfaW1wb3J0YW5jZV9kZltvcmRlcigtZmVhdHVyZV9pbXBvcnRhbmNlX2RmJEltcG9ydGFuY2UpLCBdDQoNCiMgQ3JlYXRlIGEgYmFyIHBsb3Qgb2YgZmVhdHVyZSBpbXBvcnRhbmNlDQpnZ3Bsb3QoZGF0YSA9IGZlYXR1cmVfaW1wb3J0YW5jZV9kZiwgYWVzKHggPSBJbXBvcnRhbmNlLCB5ID0gcmVvcmRlcihGZWF0dXJlcywgSW1wb3J0YW5jZSkpKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCBmaWxsID0gInN0ZWVsYmx1ZSIsIHdpZHRoID0gMC44KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChJbXBvcnRhbmNlLCAyKSksIGhqdXN0ID0gLTAuMSwgdmp1c3QgPSAwLjUsIHNpemUgPSAyLCBjb2xvciA9ICJibGFjayIpICsNCiAgbGFicyh4ID0gIkltcG9ydGFuY2UiLCB5ID0gIkZlYXR1cmVzIikgKw0KICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDEpKSArDQogIGdndGl0bGUoIkZlYXR1cmUgSW1wb3J0YW5jZSIpDQpgYGANClRoZSBiYXIgcGxvdCBwcm92aWRlcyBhIHZpc3VhbCByZXByZXNlbnRhdGlvbiBvZiBmZWF0dXJlIGltcG9ydGFuY2UgaW4gb3VyIHRyYWluZWQgUmFuZG9tIEZvcmVzdCBtb2RlbC4gTG9uZ2VyIGJhcnMgaW5kaWNhdGUgZ3JlYXRlciBpbXBvcnRhbmNlIGluIHByZWRpY3Rpbmcgb3V0Y29tZXMuIEZyb20gdGhlIHBsb3QsIDxiPkdyYWRlIDEgYW5kIEdyYWRlIDIgc3RhbmQgb3V0IGFzIHN1YnN0YW50aWFsIGNvbnRyaWJ1dG9ycyB0byB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZTwvYj4uIFRoaXMgb2JzZXJ2YXRpb24gaXMgY29uc2lzdGVudCB3aXRoIHRoZSB1bmRlcnN0YW5kaW5nIHRoYXQgZWFybHkgZ3JhZGVzIG9mdGVuIHNlcnZlIGFzIGZ1bmRhbWVudGFsIGluZGljYXRvcnMgZm9yIHRoZSBmb2xsb3dpbmcgYWNhZGVtaWMgc3VjY2Vzcy4NCg0KIyMjIFJlZ3Jlc3Npb24gTW9kZWwNCg0KSW4gb3VyIHN0dWR5LCBHMyBoYXMgYmVlbiBpZGVudGlmaWVkIGFzIHRoZSBvbmx5IGFwcHJvcHJpYXRlIHByZWRpY3RvciBmb3Igb3VyIHJlZ3Jlc3Npb24gbW9kZWxzIGR1ZSB0byB0aGUgbGltaXRlZCBwcmVkaWN0aXZlIGNhcGFiaWxpdHkgb2Ygb3RoZXIgZmVhdHVyZXMuIEluaXRpYWxseSwgdGhlIGZvY3VzIHdhcyB0byBwcmVkaWN0IHRoZSBiaW5hcnkgb3V0Y29tZSBvZiBzdHVkZW50cycgc3VjY2VzcyBvciBmYWlsdXJlLiBIb3dldmVyLCB0byBnYWluIGEgZGVlcGVyIGluc2lnaHQgaW50byBzdHVkZW50IHBlcmZvcm1hbmNlLCB3ZSBzaGlmdGVkIG91ciBhdHRlbnRpb24gdG8gcHJlZGljdGluZyB0aGUgYWN0dWFsIEczIHNjb3JlcyB1c2luZyByZWdyZXNzaW9uIG1vZGVscy4gVGhlc2UgbW9kZWxzLCBpbmNsdWRpbmcgTGluZWFyIFJlZ3Jlc3Npb24sIFJhbmRvbSBGb3Jlc3QsIGFuZCBTdXBwb3J0IFZlY3RvciBSZWdyZXNzaW9uIChTVlIpLCBhcmUgbWV0aWN1bG91c2x5IGV2YWx1YXRlZCB1c2luZyBtZXRyaWNzIGxpa2UgUk1TRSBhbmQgUsKyLiBPdXIgYXBwcm9hY2ggZW5jb21wYXNzZXMgZGF0YSBwcmVwYXJhdGlvbiwgbW9kZWwgYnVpbGRpbmcsIGFuZCBpbi1kZXB0aCBhbmFseXNpcyBvZiBmZWF0dXJlIGltcG9ydGFuY2UsIGFpbWVkIGF0IHByb3ZpZGluZyBhIGNvbXByZWhlbnNpdmUgdW5kZXJzdGFuZGluZyBvZiBmYWN0b3JzIGluZmx1ZW5jaW5nIEczIHNjb3Jlcy4NCg0KIyMgTGluZWFyIFJlZ3Jlc3Npb24NCg0KYGBge3J9DQojIERhdGEgUHJlcGFyYXRpb246IENvbWJpbmUgRGF0YSBmb3IgVHJhaW5pbmcNCnlfdHJhaW48LXRyYWluJEczDQp5X3Rlc3Q8LXRlc3QkRzMNCnRyYWluX25ldzwtY2JpbmQoWF90cmFpbixHMz15X3RyYWluKQ0KdHJhaW5fbmV3DQpgYGANCg0KYGBge3J9DQojIEJ1aWxkIExpbmVhciBSZWdyZXNzaW9uIE1vZGVsDQpsaW5lYXJfbW9kZWwgPC0gbG0oRzMgfiAuLCB0cmFpbl9uZXcpDQpzdW1tYXJ5KGxpbmVhcl9tb2RlbCkNCmxpbmVhcl9tb2RlbF9zdW1tYXJ5IDwtIHN1bW1hcnkobGluZWFyX21vZGVsKQ0Kcl9zcXVhcmVkIDwtIGxpbmVhcl9tb2RlbF9zdW1tYXJ5JHIuc3F1YXJlZA0KcHJpbnQocl9zcXVhcmVkKQ0KYGBgDQoNCmBgYHtyfQ0KIyBNb2RlbCBQcmVkaWN0aW9uOiBQcmVkaWN0IG9uIFRlc3QgU2V0DQpwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGxpbmVhcl9tb2RlbCwgbmV3ZGF0YSA9IFhfdGVzdCkNCnJtc2UgPC0gc3FydChtZWFuKChwcmVkaWN0aW9ucyAtIHlfdGVzdCleMikpDQpyX3NxdWFyZWQgPC0gMSAtIHN1bSgocHJlZGljdGlvbnMgLSB5X3Rlc3QpXjIpIC8gc3VtKCh5X3Rlc3QgLSBtZWFuKHlfdGVzdCkpXjIpDQpjYXQoIlJNU0U6Iiwgcm1zZSwgIlxuIikNCmNhdCgiUsKyOiIsIHJfc3F1YXJlZCwgIlxuIikNCmBgYA0KDQpgYGB7cn0NCiMgUmVzdWx0cyBWaXN1YWxpemF0aW9uOiBDb21wYXJlIEFjdHVhbCBhbmQgUHJlZGljdGVkIFZhbHVlcw0KIyBDcmVhdGUgYSBEYXRhIEZyYW1lIENvbnRhaW5pbmcgeV90ZXN0IGFuZCBQcmVkaWN0aW9ucw0KIyBDcmVhdGUgU2NhdHRlciBQbG90IGFuZCBBZGQgUGVyZmVjdCBQcmVkaWN0aW9uIERpYWdvbmFsIExpbmUNCmNvbXBhcmlzb25fZGYgPC0gZGF0YS5mcmFtZShBY3R1YWwgPSB5X3Rlc3QsIFByZWRpY3RlZCA9IHByZWRpY3Rpb25zKQ0KZ2dwbG90KGNvbXBhcmlzb25fZGYsIGFlcyh4ID0gQWN0dWFsLCB5ID0gUHJlZGljdGVkKSkgKw0KICAgIGdlb21fcG9pbnQoKSArICAjIEFkZCBTY2F0dGVyIFBsb3QNCiAgICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsgICMgQWRkIHk9eCBEaWFnb25hbCBMaW5lDQogICAgbGFicyh0aXRsZSA9ICJMaW5lYXIgUmVncmVzc2lvbiIsIHggPSAiQWN0dWFsIEczIiwgeSA9ICJQcmVkaWN0ZWQgRzMiKSAgIyBBZGQgVGl0bGUgYW5kIEF4aXMgTGFiZWxzDQpgYGANCg0KVGhlIHNjYXR0ZXIgcGxvdCBzaG93cyBhIGNvbXBhcmlzb24gYmV0d2VlbiB0aGUgYWN0dWFsIEczIGdyYWRlcyBhbmQgdGhlIHByZWRpY3RlZCBncmFkZXMgZnJvbSB0aGUgTGluZWFyIFJlZ3Jlc3Npb24gbW9kZWwuIFRoZSByZWQgZGFzaGVkIGxpbmUgcmVwcmVzZW50cyB0aGUgaWRlYWwgd2hlcmUgdGhlIHByZWRpY3RlZCBncmFkZXMgcGVyZmVjdGx5IG1hdGNoIHRoZSBhY3R1YWwgZ3JhZGVzLiBQb2ludHMgY2x1c3RlcmluZyBjbG9zZSB0byB0aGlzIGxpbmUgc3VnZ2VzdCBnb29kIG1vZGVsIGFjY3VyYWN5LiBUaGUgZ2VuZXJhbCB0cmVuZCBpbmRpY2F0ZXMgdGhhdCB0aGUgTGluZWFyIFJlZ3Jlc3Npb24gbW9kZWwgcHJlZGljdHMgbG93ZXIgYW5kIG1pZC1yYW5nZSBncmFkZXMgcXVpdGUgd2VsbCwgYXMgcG9pbnRzIGluIHRoZXNlIGFyZWFzIGFyZSBjbG9zZXIgdG8gdGhlIGxpbmUuIFRoZSBzcHJlYWQgb2YgcG9pbnRzIGF3YXkgZnJvbSB0aGUgbGluZSBhdCB0aGUgZXh0cmVtaXRpZXMgY291bGQgaW5kaWNhdGUgbGVzcyBhY2N1cmFjeSBmb3IgdmVyeSBoaWdoIG9yIHZlcnkgbG93IGdyYWRlcy4gVGhpcyBwYXR0ZXJuIG1pZ2h0IHN1Z2dlc3QgdGhhdCB0aGUgbW9kZWwgaXMgYmV0dGVyIGF0IHByZWRpY3RpbmcgYXZlcmFnZSBwZXJmb3JtYW5jZXMgdGhhbiBleHRyZW1lcy4NCiANCiMjIFJhbmRvbSBmb3Jlc3QNCmBgYHtyfQ0KIyBCdWlsZCBSYW5kb20gRm9yZXN0IE1vZGVsDQojIE1vZGVsIFN1bW1hcnkNCiMgRXh0cmFjdCBSwrIgVmFsdWUgZm9yIFRyYWluaW5nIFNldA0KIyBSZXRyaWV2aW5nIHRoZSBsYXN0IFLCsiB2YWx1ZSBmcm9tIHRoZSByYW5kb20gZm9yZXN0IG1vZGVsICh0cmFpbmluZyBwZXJmb3JtYW5jZSkNCnJmX21vZGVsIDwtIHJhbmRvbUZvcmVzdChHMyB+IC4sIGRhdGEgPSB0cmFpbl9uZXcsIG50cmVlID0gNTAwKQ0KcHJpbnQocmZfbW9kZWwpDQpyX3NxdWFyZWRfdHJhaW4gPC0gcmZfbW9kZWwkcnNxDQpyX3NxdWFyZWRfdHJhaW5fbGFzdCA8LSB0YWlsKHJfc3F1YXJlZF90cmFpbiwgMSkNCmNhdCgiUsKyIGZvciBUcmFpbmluZyBTZXQ6Iiwgcl9zcXVhcmVkX3RyYWluX2xhc3QsICJcbiIpDQpgYGANCg0KYGBge3J9DQojIFBlcmZvcm1hbmNlIEV2YWx1YXRpb246IENhbGN1bGF0ZSBSTVNFIGFuZCBSwrIgZm9yIFRlc3QgU2V0DQojIENhbGN1bGF0aW5nIFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yIChSTVNFKSBmb3IgdGVzdCBzZXQgcHJlZGljdGlvbnMNCiMgT3V0cHV0IFBlcmZvcm1hbmNlIE1ldHJpY3MNCiMgUHJpbnRpbmcgUk1TRSBhbmQgUsKyIGZvciB0aGUgdGVzdCBzZXQNCnJmX3ByZWRpY3Rpb25zIDwtIHByZWRpY3QocmZfbW9kZWwsIG5ld2RhdGEgPSBYX3Rlc3QpDQpyZl9ybXNlIDwtIHNxcnQobWVhbigocmZfcHJlZGljdGlvbnMgLSB5X3Rlc3QpXjIpKQ0Kcl9zcXVhcmVkX3Rlc3QgPC0gMSAtIHN1bSgocmZfcHJlZGljdGlvbnMgLSB5X3Rlc3QpXjIpIC8gc3VtKCh5X3Rlc3QgLSBtZWFuKHlfdGVzdCkpXjIpDQpjYXQoIlLCsiBmb3IgVGVzdCBTZXQ6Iiwgcl9zcXVhcmVkX3Rlc3QsICJcbiIpDQpjYXQoIlJNU0U6IiwgcmZfcm1zZSwgIlxuIikNCmBgYA0KDQpgYGB7cn0NCiMgQ2FsY3VsYXRlIEZlYXR1cmUgSW1wb3J0YW5jZSBmcm9tIHRoZSBSYW5kb20gRm9yZXN0IE1vZGVsDQojIENhbGN1bGF0aW5nIGZlYXR1cmUgaW1wb3J0YW5jZSBmcm9tIHRoZSBtb2RlbA0KIyBDb252ZXJ0IEZlYXR1cmUgSW1wb3J0YW5jZSB0byBEYXRhIEZyYW1lIGZvciBWaXN1YWxpemF0aW9uDQojIENyZWF0aW5nIGEgZGF0YSBmcmFtZSBmb3IgZmVhdHVyZSBpbXBvcnRhbmNlIHRvIGJlIHVzZWQgaW4gcGxvdHRpbmcNCiMgQ3JlYXRlIGFuZCBQbG90IEZlYXR1cmUgSW1wb3J0YW5jZSB1c2luZyBnZ3Bsb3QyDQojIFBsb3R0aW5nIGZlYXR1cmUgaW1wb3J0YW5jZSB1c2luZyBnZ3Bsb3QyDQpmZWF0dXJlX2ltcG9ydGFuY2VfcmF3IDwtIGltcG9ydGFuY2UocmZfbW9kZWwpDQpmZWF0dXJlX2ltcG9ydGFuY2UgPC0gZGF0YS5mcmFtZShGZWF0dXJlID0gcm93bmFtZXMoZmVhdHVyZV9pbXBvcnRhbmNlX3JhdyksIEltcG9ydGFuY2UgPSBmZWF0dXJlX2ltcG9ydGFuY2VfcmF3WywgIkluY05vZGVQdXJpdHkiXSkNCmdncGxvdChmZWF0dXJlX2ltcG9ydGFuY2UsIGFlcyh4ID0gcmVvcmRlcihGZWF0dXJlLCBJbXBvcnRhbmNlKSwgeSA9IEltcG9ydGFuY2UpKSArDQogICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgICBjb29yZF9mbGlwKCkgKyAgIyBGbGlwcGluZyB0aGUgY29vcmRpbmF0ZXMgZm9yIGJldHRlciByZWFkYWJpbGl0eSBvZiBmZWF0dXJlIG5hbWVzDQogICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICBsYWJzKHRpdGxlID0gIkZlYXR1cmUgSW1wb3J0YW5jZSBpbiBSYW5kb20gRm9yZXN0IE1vZGVsIiwgeCA9ICJGZWF0dXJlcyIsIHkgPSAiSW1wb3J0YW5jZSIpDQpgYGANClRoaXMgaG9yaXpvbnRhbCBiYXIgY2hhcnQgZGlzcGxheXMgdGhlIGZlYXR1cmUgaW1wb3J0YW5jZSBhcyBkZXRlcm1pbmVkIGJ5IGEgUmFuZG9tIEZvcmVzdCBtb2RlbC4gVGhlIGZlYXR1cmVzIGFyZSBsaXN0ZWQgb24gdGhlIHktYXhpcyBhbmQgdGhlaXIgaW1wb3J0YW5jZSBzY29yZXMgYXJlIG9uIHRoZSB4LWF4aXMuIEl0J3MgY2xlYXIgZnJvbSB0aGUgY2hhcnQgdGhhdCAnRzInIGFuZCAnRzEnIGFyZSB0aGUgbW9zdCBzaWduaWZpY2FudCBwcmVkaWN0b3JzIG9mIHRoZSB0YXJnZXQgdmFyaWFibGUsIHdoaWNoLCBpbiB0aGUgY29udGV4dCBvZiBzdHVkZW50IHBlcmZvcm1hbmNlLCBhcmUgbGlrZWx5IHByZXZpb3VzIGdyYWRlcy4gJ0Fic2VuY2VzJyBhbHNvIGFwcGVhcnMgdG8gYmUgYSBzaWduaWZpY2FudCBmZWF0dXJlLCBmb2xsb3dlZCBieSAnZmFpbHVyZXMnLiBPdGhlciB2YXJpYWJsZXMgc3VjaCBhcyAnc3R1ZHl0aW1lJywgJ01lZHUnIChtb3RoZXIncyBlZHVjYXRpb24pLCAnRmVkdScgKGZhdGhlcidzIGVkdWNhdGlvbiksIGFuZCAndHJhdmVsdGltZScgc2hvdyByZWxhdGl2ZWx5IGxlc3MgaW1wb3J0YW5jZS4gVGhlIGxlbmd0aCBvZiB0aGUgYmFycyByZXByZXNlbnRzIGhvdyBpbmZsdWVudGlhbCBlYWNoIGZlYXR1cmUgaXMgaW4gdGhlIG1vZGVs4oCZcyBwcmVkaWN0aW9uczsgbG9uZ2VyIGJhcnMgaW5kaWNhdGUgZ3JlYXRlciBpbXBvcnRhbmNlLg0KDQoNCiMjIFNWUiBNT0RFTA0KYGBge3J9DQojIEJ1aWxkIHRoZSBTdXBwb3J0IFZlY3RvciBSZWdyZXNzaW9uIE1vZGVsDQojIFVzaW5nIHRoZSByYWRpYWwgYmFzaXMgZnVuY3Rpb24gKFJCRikga2VybmVsDQojIE1vZGVsIFByZWRpY3Rpb24gYW5kIFBlcmZvcm1hbmNlIEV2YWx1YXRpb24NCnN2cl9tb2RlbCA8LSBzdm0oRzMgfiAuLCBkYXRhID0gdHJhaW5fbmV3LCB0eXBlID0gImVwcy1yZWdyZXNzaW9uIiwga2VybmVsID0gInJhZGlhbCIpDQpzdnJfcHJlZGljdGlvbnMgPC0gcHJlZGljdChzdnJfbW9kZWwsIG5ld2RhdGEgPSBYX3Rlc3QpDQpzdnJfcm1zZSA8LSBzcXJ0KG1lYW4oKHN2cl9wcmVkaWN0aW9ucyAtIHlfdGVzdCleMikpDQpjYXQoIlNWUiBSTVNFOiIsIHN2cl9ybXNlLCAiXG4iKQ0Kc3ZyX3Jfc3F1YXJlZCA8LSAxIC0gc3VtKChzdnJfcHJlZGljdGlvbnMgLSB5X3Rlc3QpXjIpIC8gc3VtKCh5X3Rlc3QgLSBtZWFuKHlfdGVzdCkpXjIpDQpjYXQoIlNWUiBSwrI6Iiwgc3ZyX3Jfc3F1YXJlZCwgIlxuIikNCmBgYA0KDQojIyMgUmVncmVzc2lvbiBNb2RlbCBQZXJmb3JtYW5jZQ0KYGBge3J9DQptb2RlbF9jb21wYXJpc29uIDwtIGRhdGEuZnJhbWUoDQogIE1vZGVsID0gYygiTGluZWFyIFJlZ3Jlc3Npb24iLCAiUmFuZG9tIEZvcmVzdCIsICJTVlIiKSwNCiAgUk1TRSA9IGMocm1zZSwgcmZfcm1zZSwgc3ZyX3Jtc2UpLA0KICBSX1NxdWFyZWQgPSBjKHJfc3F1YXJlZCwgcl9zcXVhcmVkX3Rlc3QsIHN2cl9yX3NxdWFyZWQpDQopDQpwcmludChtb2RlbF9jb21wYXJpc29uKQ0KYGBgDQpJbiB0aGUgY29tcGFyaXNvbiBvZiB0aGUgdGhyZWUgbW9kZWxzLCB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgb3V0cGVyZm9ybWVkIHRoZSBvdGhlciBtb2RlbHMgd2l0aCB0aGUgbG93ZXN0IFJNU0UgYW5kIGhpZ2hlc3QgUi1zcXVhcmUsIGluZGljYXRpbmcgdGhhdCBpdCBoYWQgdGhlIGJlc3QgcHJlZGljdGl2ZSBhY2N1cmFjeSBhbmQgZXhwbGFuYXRvcnkgcG93ZXIgZm9yIHRoZSBHMyBzY29yZS4gUmFuZG9tIGZvcmVzdCBjb21lcyBzZWNvbmQsIGJ1dCB3aXRoIHNsaWdodGx5IGxlc3MgYWNjdXJhY3kgYW5kIGV4cGxhbmF0b3J5IHBvd2VyLiBTVlIgaGFkIHRoZSBoaWdoZXN0IFJNU0UgYW5kIGxvd2VzdCBSLXNxdWFyZWQsIG1ha2luZyBpdCB0aGUgbGVhc3QgZWZmZWN0aXZlIGF0IHByZWRpY3RpbmcgYW5kIGV4cGxhaW5pbmcgdmFyaWFuY2UgaW4gRzMgc2NvcmVzLiBUaGUgcmVzdWx0cyBzaG93ZWQgdGhhdCB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgd2FzIHRoZSBtb3N0IGVmZmVjdGl2ZSBmb3IgcHJlZGljdGluZyBzdHVkZW50cycgZmluYWwgZXhhbSBzY29yZXMgKEczKS4NCg0KDQojIyMgUmVmZXJlbmNlDQpSZXphZWktRGVoYWdoYW5pLCBBLiwgS2VzaHZhcmksIE0uLCAmIFBha2ksIFMuICgyMDE4KS4gVGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGZhbWlseSBmdW5jdGlvbmluZyBhbmQgYWNhZGVtaWMgYWNoaWV2ZW1lbnQgaW4gZmVtYWxlIGhpZ2ggc2Nob29sIHN0dWRlbnRzIG9mIElzZmFoYW4sIElyYW4sIGluIDIwMTPigJMyMDE0LiBJcmFuaWFuIEpvdXJuYWwgb2YgTnVyc2luZyBhbmQgTWlkd2lmZXJ5IFJlc2VhcmNoLCAyMygzKSwgMTgzLiBodHRwczovL2RvaS5vcmcvMTAuNDEwMy9pam5tci5pam5tcl84N18xNw0KDQpgYGB7Y3NzLCBlY2hvPUZBTFNFfQ0KLm1haW4tY29udGFpbmVyIHsNCiAgYm9yZGVyOiAxcHggc29saWQ7DQogIG1hcmdpbi10b3A6IDE1cHg7DQogIG1hcmdpbi1ib3R0b206IDE1cHg7DQp9DQpoMyB7DQogIGJhY2tncm91bmQ6IGxpZ2h0Ymx1ZTsNCiAgcGFkZGluZzogNXB4IDAgNXB4IDA7DQp9DQpwIHsNCiAgdGV4dC1hbGlnbjoganVzdGlmeTsNCn0NCmBgYA0K