1 Introduction

This project uses the same dataset as project 3.

This dataset is titled “Student Habits vs Academic Performance” and shows various explanatory variables and one target/response variable (exam_score). The purpose of collecting this dataset is to see what factors may affect exam scores. This dataset was collected from Kaggle. This dataset contains 1000 observations and 16 variables (15 feature variables and 1 target variable).

The student_id serves as the identifier. The age (numerical) shows each persons age. The gender (categorical) shows each persons gender. The study_hours_per_day shows hours studied per day. The social_media_hours shows hours used daily on social media. The netflix_hours shows hours used daily on netflix. The part_time_job (no/yes) shows if the person has a part time job. The attendance percentage shows the percentage of classes attended. The sleep_hours shows amount of sleep per night. The diet_quality (poor/fair/good) shows quality of ones diet. The exercise_frequency shows how many times one exercises per week. The parental_education_level (none/High School/bachelor/master) shows the highest level of education of ones parents. The internet_quality(poor/avg/good) shows how good ones wi-fi is. The mental_health_rating (1-10) shows how one rates their mental health. The extracurricular_participation (n/y) shows if one participates in extracurricular activities. The target/response variable is exam_score (continuous).

Based on this dataset, we can formulate a research question. The question we can form is what variables are the most important in predicting exam score. For regression, we can use exam_score (continuous) as our response variable. For classification, we can create a binary response variable with 1 (pass) and 0 (fail). We can use the condition if exam score is greater or less than 0.60.

2 Read in Dataset

Read in dataset. See dataset structure.

habits2 <- read.csv("student_habits_performance.csv")
str(habits2)
'data.frame':   1000 obs. of  16 variables:
 $ student_id                   : chr  "S1000" "S1001" "S1002" "S1003" ...
 $ age                          : int  23 20 21 23 19 24 21 21 23 18 ...
 $ gender                       : chr  "Female" "Female" "Male" "Female" ...
 $ study_hours_per_day          : num  0 6.9 1.4 1 5 7.2 5.6 4.3 4.4 4.8 ...
 $ social_media_hours           : num  1.2 2.8 3.1 3.9 4.4 1.3 1.5 1 2.2 3.1 ...
 $ netflix_hours                : num  1.1 2.3 1.3 1 0.5 0 1.4 2 1.7 1.3 ...
 $ part_time_job                : chr  "No" "No" "No" "No" ...
 $ attendance_percentage        : num  85 97.3 94.8 71 90.9 82.9 85.8 77.7 100 95.4 ...
 $ sleep_hours                  : num  8 4.6 8 9.2 4.9 7.4 6.5 4.6 7.1 7.5 ...
 $ diet_quality                 : chr  "Fair" "Good" "Poor" "Poor" ...
 $ exercise_frequency           : int  6 6 1 4 3 1 2 0 3 5 ...
 $ parental_education_level     : chr  "Master" "High School" "High School" "Master" ...
 $ internet_quality             : chr  "Average" "Average" "Poor" "Good" ...
 $ mental_health_rating         : int  8 8 1 1 1 4 4 8 1 10 ...
 $ extracurricular_participation: chr  "Yes" "No" "No" "Yes" ...
 $ exam_score                   : num  56.2 100 34.3 26.8 66.4 100 89.8 72.6 78.9 100 ...

3 EDA

The below figure shows the distribution of the gender variable.

ggplot(habits2, aes(x = gender)) + 
  
  geom_bar() +
  labs(title = "Gender")

The below figure shows the distribution of the part_time_job variable.

ggplot(habits2, aes(x = part_time_job)) + 
  
  geom_bar() +
  labs(title = "part_time_job")

The below figure shows the distribution of the diet_quality variable.

ggplot(habits2, aes(x = diet_quality)) + 
  
  geom_bar() +
  labs(title = "diet_quality")

The below figure shows the distribution of the parental_education_level variable.

ggplot(habits2, aes(x = parental_education_level)) + 
  
  geom_bar() +
  labs(title = "parental_education_level")

The below figure shows the distribution of the internet_quality variable.

ggplot(habits2, aes(x = internet_quality)) + 
  
  geom_bar() +
  labs(title = "internet_quality")

The below figure shows the distribution of the extracurricular_participation variable.

ggplot(habits2, aes(x = extracurricular_participation)) + 
  
  geom_bar() +
  labs(title = "extracurricular_participation")

The below figure shows the distribution of the age variable.

ggplot(data = habits2, aes(x = age)) + 
  geom_boxplot() + 
  
  labs(title = "age")

The below figure shows the distribution of the study_hours_per_day variable.

ggplot(data = habits2, aes(x = study_hours_per_day)) + 
  geom_boxplot() + 
  
  labs(title = "study_hours_per_day")

The below figure shows the distribution of the social_media_hours variable.

ggplot(data = habits2, aes(x = social_media_hours)) + 
  geom_boxplot() + 
  
  labs(title = "social_media_hours")

The below figure shows the distribution of the netflix_hours variable.

ggplot(data = habits2, aes(x = netflix_hours)) + 
  geom_boxplot() + 
  
  labs(title = "netflix_hours")

The below figure shows the distribution of the attendance_percentage variable.

ggplot(data = habits2, aes(x = attendance_percentage)) + 
  geom_boxplot() + 
  
  labs(title = "attendance_percentage")

The below figure shows the distribution of the sleep_hours variable.

ggplot(data = habits2, aes(x = sleep_hours)) + 
  geom_boxplot() + 
  
  labs(title = "sleep_hours")

The below figure shows the distribution of the exercise_frequency variable.

ggplot(data = habits2, aes(x = exercise_frequency)) + 
  geom_boxplot() + 
  
  labs(title = "exercise_frequency")

The below figure shows the distribution of the mental_health_rating variable.

ggplot(data = habits2, aes(x = mental_health_rating)) + 
  geom_boxplot() + 
  
  labs(title = "mental_health_rating")

4 Imputation/Feature Engineering.

The variables part_time_job, diet_quality, internet_quality, and extracurricular_participation are converted to categorical/factor.

habits2$part_time_job <- as.factor(habits2$part_time_job)
habits2$diet_quality <- factor(habits2$diet_quality,levels = c("Poor", "Fair", "Good"))

habits2$internet_quality <- factor(habits2$internet_quality, levels = c("Poor", "Average", "Good"))
habits2$extracurricular_participation <- as.factor(habits2$extracurricular_participation)

The variable gender contains the value ‘Other’. Since we only want to work with Male/female, we set ‘Other’ to missing and use MICE imputation.

habits2$gender[habits2$gender== "Other"] = NA
habits2$gender <- as.factor(habits2$gender)

init3 <- mice(habits2, maxit = 0)
init3$method
                   student_id                           age 
                           ""                            "" 
                       gender           study_hours_per_day 
                     "logreg"                            "" 
           social_media_hours                 netflix_hours 
                           ""                            "" 
                part_time_job         attendance_percentage 
                           ""                            "" 
                  sleep_hours                  diet_quality 
                           ""                            "" 
           exercise_frequency      parental_education_level 
                           ""                            "" 
             internet_quality          mental_health_rating 
                           ""                            "" 
extracurricular_participation                    exam_score 
                           ""                            "" 
imp3 <- mice(habits2, method = c("","", "logreg", "",  "", "", "", "", "", "", "", "", "", "", "", ""), 
                 maxit = 10,  
                 m = 5, 
                 seed=123,
                 print=F)     



complete_habits_data2 <- complete(imp3)

For parental_education_level, we convert both Bachelor and Master to College to reduce categories.

complete_habits_data2$parental_education_level[complete_habits_data2$parental_education_level == 'Bachelor'] <- 'College'
complete_habits_data2$parental_education_level[complete_habits_data2$parental_education_level == 'Master'] <- 'College'

complete_habits_data2$parental_education_level <- factor(complete_habits_data2$parental_education_level,levels = c("None", "High School", "College"))

str(complete_habits_data2)
'data.frame':   1000 obs. of  16 variables:
 $ student_id                   : chr  "S1000" "S1001" "S1002" "S1003" ...
 $ age                          : int  23 20 21 23 19 24 21 21 23 18 ...
 $ gender                       : Factor w/ 2 levels "Female","Male": 1 1 2 1 1 2 1 1 1 1 ...
 $ study_hours_per_day          : num  0 6.9 1.4 1 5 7.2 5.6 4.3 4.4 4.8 ...
 $ social_media_hours           : num  1.2 2.8 3.1 3.9 4.4 1.3 1.5 1 2.2 3.1 ...
 $ netflix_hours                : num  1.1 2.3 1.3 1 0.5 0 1.4 2 1.7 1.3 ...
 $ part_time_job                : Factor w/ 2 levels "No","Yes": 1 1 1 1 1 1 2 2 1 1 ...
 $ attendance_percentage        : num  85 97.3 94.8 71 90.9 82.9 85.8 77.7 100 95.4 ...
 $ sleep_hours                  : num  8 4.6 8 9.2 4.9 7.4 6.5 4.6 7.1 7.5 ...
 $ diet_quality                 : Factor w/ 3 levels "Poor","Fair",..: 2 3 1 1 2 2 3 2 3 3 ...
 $ exercise_frequency           : int  6 6 1 4 3 1 2 0 3 5 ...
 $ parental_education_level     : Factor w/ 3 levels "None","High School",..: 3 2 2 3 3 3 3 3 3 3 ...
 $ internet_quality             : Factor w/ 3 levels "Poor","Average",..: 2 2 1 3 3 2 1 2 3 3 ...
 $ mental_health_rating         : int  8 8 1 1 1 4 4 8 1 10 ...
 $ extracurricular_participation: Factor w/ 2 levels "No","Yes": 2 1 1 2 1 1 1 1 1 2 ...
 $ exam_score                   : num  56.2 100 34.3 26.8 66.4 100 89.8 72.6 78.9 100 ...

Since neural networks can only work with numerical variables, we need to transform all the categorical variables into numerical format using a mix of label encoding (ordinal) and one hot encoding (nominal).

complete_habits_data2 is the complete dataset that will be used for the project.

complete_habits_data2$diet_quality <- as.numeric(factor(complete_habits_data2$diet_quality))
complete_habits_data2$parental_education_level <- as.numeric(factor(complete_habits_data2$parental_education_level))
complete_habits_data2$internet_quality <- as.numeric(factor(complete_habits_data2$internet_quality))

encoded_data <- model.matrix(~ gender - 1, data = complete_habits_data2)
encoded_data2 <- model.matrix(~ part_time_job - 1, data = complete_habits_data2)
encoded_data3 <- model.matrix(~ extracurricular_participation - 1, data = complete_habits_data2)

complete_habits_data2 <- cbind(complete_habits_data2, encoded_data, encoded_data2, encoded_data3)

5 Perceptron Regression

In this section, we perform a perceptron model for the continuous response variable exam_score.

The perceptron is the simplest form of a neural network. The perceptron model takes input features, computes their weighted sum, and passes the result through an activation function to generate the output. The linear activation function is used.

The below code chunk performs the data splitting and the normalization for all variables.

set.seed(123)

complete_habits_data2 <- complete_habits_data2[ -c(1,3,7,15) ]

# Normalize data (0-1 range)
normalize <- function(x) {
  (x - min(x)) / (max(x) - min(x))
}
# normalize all features including the target variable
habits.scaled <- as.data.frame(lapply(complete_habits_data2, normalize))

# Train-test split (70-30)
#set.seed(123)
##
sample.size <- dim(habits.scaled)[1]
train.indices <- sample(1:sample.size, round(0.8*sample.size))
##

train.data.norm <- habits.scaled[train.indices, ]
test.data.norm <- habits.scaled[-train.indices, ]
##
train.orig <- complete_habits_data2[train.indices, ]
test.orig <- complete_habits_data2[-train.indices, ]

unscale <- function(x, original) {
  return(x * (max(original) - min(original)) + min(original))
}

The table below shows all possible combination of hyperparameter values of learningrate, threshold, stepmax, and RMSE.

# Define the tuning grid
tune.grid.nn <- expand.grid(
   learningrate = c(0.001, 0.01, 0.1, 0.5, 1),
   threshold = c(0.01, 0.05, 0.1, 0.5),
   stepmax = c(1e5, 1e6)  # Add stepmax to prevent infinite training
)

# Custom training function for neuralnet()
neuralnet.train <- function(learningrate, threshold, stepmax) {
  model <- neuralnet(
    exam_score ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale,
    data = train.data.norm,
    hidden = 0,  # Perceptron (no hidden layer)
    linear.output = TRUE,  # For regression
    learningrate = learningrate,
    threshold = threshold,
    stepmax = stepmax
  )
  # Calculate RMSE on training data
  pred <- predict(model, train.data.norm[, -ncol(train.data.norm)])
  

  rmse <- sqrt(mean((pred - train.data.norm$exam_score)^2))
  return(rmse)
}

# Perform grid search:
# using apply the () function to call neuralnet.train() using the components of the
# row vector in the tune.grid.nn (data frame of combinations of hyperparameters).
#
results <- apply(tune.grid.nn, 1, function(x) {
  neuralnet.train(x["learningrate"], x["threshold"], x["stepmax"])
})

##
# Combine results with parameter combinations
tune.results <- cbind(tune.grid.nn, RMSE = results)
##
pander(tune.results)
learningrate threshold stepmax RMSE
0.001 0.01 1e+05 0.5525
0.01 0.01 1e+05 0.4814
0.1 0.01 1e+05 0.7533
0.5 0.01 1e+05 0.7962
1 0.01 1e+05 1.134
0.001 0.05 1e+05 0.8093
0.01 0.05 1e+05 0.7343
0.1 0.05 1e+05 1.157
0.5 0.05 1e+05 0.2031
1 0.05 1e+05 0.6438
0.001 0.1 1e+05 0.4728
0.01 0.1 1e+05 0.907
0.1 0.1 1e+05 0.5015
0.5 0.1 1e+05 0.1872
1 0.1 1e+05 1.185
0.001 0.5 1e+05 0.968
0.01 0.5 1e+05 0.3787
0.1 0.5 1e+05 0.6809
0.5 0.5 1e+05 0.2296
1 0.5 1e+05 0.831
0.001 0.01 1e+06 1.137
0.01 0.01 1e+06 0.3102
0.1 0.01 1e+06 0.4608
0.5 0.01 1e+06 1.646
1 0.01 1e+06 0.6958
0.001 0.05 1e+06 0.3846
0.01 0.05 1e+06 1.085
0.1 0.05 1e+06 0.4987
0.5 0.05 1e+06 0.3708
1 0.05 1e+06 1.255
0.001 0.1 1e+06 0.7309
0.01 0.1 1e+06 0.6201
0.1 0.1 1e+06 0.4782
0.5 0.1 1e+06 0.9727
1 0.1 1e+06 0.5803
0.001 0.5 1e+06 0.9684
0.01 0.5 1e+06 0.1241
0.1 0.5 1e+06 0.7226
0.5 0.5 1e+06 0.9379
1 0.5 1e+06 0.3122

The table below shows the best combination of hyperparameter values.

# Find the best combination
best.params <- tune.results[which.min(tune.results$RMSE), ]
pander(best.params)
  learningrate threshold stepmax RMSE
37 0.01 0.5 1e+06 0.1241

The final.model.nn below trains the final neural network model using the best combination of hyperparameter values from above.

final.model.nn <- neuralnet(
  exam_score ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale,
  data = train.data.norm,
  hidden = 0,
  linear.output = TRUE,
  learningrate = best.params$learningrate,
  threshold = best.params$threshold,
  stepmax = best.params$stepmax,
  rep = 1  # Multiple repetitions for stability
)

Below we use the training model to make predictions on the test data. We also show how the perceptron performs compared to standard linear regression and we see that they are similar.

full.predictions <- predict(final.model.nn, test.data.norm)
##
pred.unscale <- unscale(full.predictions , complete_habits_data2$exam_score)
###
MSE.neuralnet <- mean((pred.unscale  -test.orig$exam_score)^2)
###
r.sq.neuralnet <- (cor(pred.unscale, test.orig$exam_score))^2


###############

# Convert predictions back to original scale
#predictions.original <- predictions * (max(test.orig$exam_score) - min(test.orig$exam_score)) + min(test.orig$exam_score)

actual.original <- test.orig$exam_score

# Calculate R-squared
r.squared <- (cor(pred.unscale,actual.original))^2


#cat("R-squared:", r.squared, "\n")
Perceptron.MSE <- mean((actual.original - pred.unscale)^2)
#cat("Perceptron MSE:", Perceptron.MSE, "\n")

# Compare with linear regression for benchmarking
lm.model <- lm(exam_score ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale, data = train.orig)
lm.pred <- predict(lm.model, test.orig)
lm.mse <- mean((test.orig$exam_score - lm.pred)^2)
#cat("Linear Regression MSE:", lm.mse, "\n")
lm.r.sq <- summary(lm.model)$r.squared

################
Perceptron <- c(Perceptron.MSE, r.squared)
LM <- c(lm.mse, lm.r.sq)
neuralnet <- c(MSE.neuralnet, r.sq.neuralnet)
Performace.metrics.all <- data.frame(Perceptron=Perceptron, 
                                 LM = LM, 
                                 neuralnet = neuralnet )
rownames(Performace.metrics.all) <- c("MSE", "r.sq")
pander(Performace.metrics.all)
  Perceptron LM neuralnet
MSE 30.66 29.89 30.66
r.sq 0.9108 0.8985 0.9108

6 Perceptron Classification

In this section, we do perceptron for the binary response variable ‘pass’. We create the pass variable below using the assumption that 60 or above is a pass (1, 0 otherwise).

The below code generates the optimal hyperparameters for learningrate, threshold, and accuracy.

complete_habits_data2 <- transform(complete_habits_data2, pass=ifelse(exam_score >= 60, 1, 0))



habits.scaled <- cbind(habits.scaled, complete_habits_data2$pass)

set.seed(123)



colnames(habits.scaled) <- c('age','study_hours_per_day', 'social_media_hours', 
                       'netflix_hours', 
                       'attendance_percentage', 'sleep_hours' , 'diet_quality',
                       'exercise_frequency', 'parental_education_level','internet_quality',
                       'mental_health_rating','exam_score','genderFemale','genderMale','part_time_jobNo', 'part_time_jobYes','extracurricular_participationNo','extracurricular_participationYes','pass')

sample.size2 <- dim(habits.scaled)[1]
train.indices2 <- sample(1:sample.size2, round(0.8*sample.size2))


train.data.cls <- habits.scaled[train.indices2, ]
test.data.cls <- habits.scaled[-train.indices2, ]

train.orig2 <- complete_habits_data2[train.indices2, ]
test.orig2 <- complete_habits_data2[-train.indices2, ]

#############

## Grid Search Setup
# Define the hyperparameter grid
hyper.grid.cls <- expand.grid(
  learningrate = c(0.001, 0.01, 0.05, 0.1, 0.2),
  threshold = c(0.01, 0.05)  # Stopping threshold for partial derivatives
 )


k <- 5
fold.size <- floor(dim(train.data.cls)[1]/k)
# Initialize results storage
results <- data.frame(
  learningrate = numeric(),
  threshold = numeric(),
  accuracy = numeric(),
  stringsAsFactors = FALSE
)


## Perform Grid Search with Cross-Validation
for(i in 1:nrow(hyper.grid.cls)) {
  lr <- hyper.grid.cls$learningrate[i]
  th <- hyper.grid.cls$threshold[i]
  
  #cat("\nTesting combination", i, "of", nrow(hyper.grid.cls), ": learningrate =", lr, ", threshold =", th, "\n")
  
  # 
  fold.accuracies <- numeric(k)
  
  # 
  for(fold in 1:k) {
    # Split into training and validation sets
    valid.indices <- (1 + (fold-1)*fold.size):(fold*fold.size)
    train.fold <- train.data.cls[-valid.indices, ]
    valid.fold <- train.data.cls[valid.indices, ]
    
    # Train the perceptron
    set.seed(123)
    model.sigmoid <- neuralnet(
      pass ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale,
      data = train.fold,
      hidden = 0,  # Perceptron has no hidden layers
      linear.output = FALSE,
      learningrate = lr,
      act.fct = "logistic",
      algorithm = "rprop+", # The resilient backpropagation with weight backtracking
      threshold = th,
      stepmax = 1e5  # Increased to ensure convergence
    )
    
    # Make predictions
    preds <- predict(model.sigmoid, valid.fold)
    pred.classes <- ifelse(preds > 0.6, 1, 0)  # default threshold 0.5
    
    # Calculate accuracy
    fold.accuracies[fold] <- mean(pred.classes == valid.fold$pass)
  }
  
  # Store average accuracy for this hyperparameter combination
  results <- rbind(results, data.frame(
    learningrate = lr,
    threshold = th,
    accuracy = mean(fold.accuracies)
  ))
}

## Analyze Results
# Find the best combination
best.combination <- results[which.max(results$accuracy), ]
#cat("\nBest hyperparameter combination:\n")
pander(best.combination)
  learningrate threshold accuracy
6 0.001 0.05 0.9012

The final.sigmoid.model below is the final trained model using the optimal hyperparameters from above.

## Train Final Model with Best Hyperparameters
final.sigmoid.model <- neuralnet(
  pass ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale,
  data = train.data.cls,
  hidden = 0,
  linear.output = FALSE,
  learningrate = best.combination$learningrate,
  threshold = best.combination$threshold,
  act.fct = "logistic",
  algorithm = "rprop+", # The resilient backpropagation with weight backtracking
  stepmax = 1e5
)

#plot(final.sigmoid.model)

Below we make predictions on the test set and compare the perceptron to standard logistic regression and find that they are similar in performance.

## Evaluate on Test Set
pred.sigmoid <- predict(final.sigmoid.model, test.data.cls)

###  logistic regression
logit.fit <- glm(pass ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale, data = train.data.cls, family = binomial)
AIC.logit <- step(logit.fit, direction = "both", trace = 0)
pred.logit <- predict(AIC.logit, test.data.cls, type = "response")
pred.full <- predict(logit.fit, test.data.cls, type = "response")


## roc
roc.full.logit <- roc(test.data.cls[, ncol(test.data.cls)], pred.full)
roc.AIC.logit <- roc(test.data.cls[, ncol(test.data.cls)], pred.logit)
roc.sigmoid <- roc(test.data.cls[, ncol(test.data.cls)], pred.sigmoid )
## AUC
auc.sigmoid <- roc.sigmoid$auc
auc.full.logit <- roc.full.logit$auc
auc.AIC.logit <- roc.AIC.logit$auc

## spe-sen
sigmoid.spe <- roc.sigmoid$specificities
sigmoid.sen <- roc.sigmoid$sensitivities

full.logit.spe <- roc.full.logit$specificities
full.logit.sen <- roc.full.logit$sensitivities

AIC.logit.spe <- roc.AIC.logit$specificities
AIC.logit.sen <- roc.AIC.logit$sensitivities


# ROC curve
plot(1-sigmoid.spe, sigmoid.sen, col = "blue", type = "l", lty = 1,
     xlab = "1 - specificity",
     ylab = "sensitivity",
     main = "ROC Curves of Perceptron and Logistic Models")
lines(1-full.logit.spe, full.logit.sen, lty = 1, col = "brown")
lines(1-AIC.logit.spe, AIC.logit.sen, lty = 1, col = "steelblue")
abline(0,1, lty =2, col = "red")
text(0.98, 0.3, paste("Perceptron AUC = ", round(auc.sigmoid,4)), col = "blue", cex = 0.8, pos = 2)
text(0.98, 0.25, paste("Full Logit AUC = ", round(auc.full.logit,4)), col = "brown", cex = 0.8, pos = 2)
text(0.98, 0.2, paste("AIC AUC = ", round(auc.AIC.logit,4)), col = "steelblue", cex = 0.8, pos = 2)

7 MLP Regression

In this section, we do multilayer perceptron. MLP uses hidden layers, which is helpful for nonlinear data.

7.1 One-hidden-layer Perceptron

In this section, we use one hidden layer. Below we split the data into train and test.

habits.scaled2 <- as.data.frame(lapply(complete_habits_data2, normalize))

# Set seed for reproducibility
set.seed(123)
N <- length(habits.scaled2$exam_score)
# Create train-test split (70-30)
train.reg.index <- sample(1:N,  floor(0.8*N), replace = FALSE)
train.reg.data <- habits.scaled2[train.reg.index, ]
test.reg.data <- habits.scaled2[-train.reg.index, ]

Below we get the optimal hyperparameters for layer1, learning.rate, activation, and rmse.

# Define grid of hyperparameters
hyper.grid.reg <- expand.grid(
  layer1 = c(5, 10, 15),
  learning.rate = c(0.01, 0.1),
  activation = c("logistic", "tanh")
)

# Initialize results storage
rmse = NULL
#layer1 = NULL
#learningrate = NULL
#activation = NULL

best.reg.rmse <- Inf
best.reg.model <- NULL

# Perform grid search
for(i in 1:nrow(hyper.grid.reg)) {
  # Get current configuration
  layer <- hyper.grid.reg$layer1[i]
  lr <- hyper.grid.reg$learning.rate[i]
  act <- hyper.grid.reg$activation[i]
  
  # Train model
  set.seed(123)
  model.reg <- neuralnet(
      exam_score ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale,
      data = train.reg.data,
      hidden = layer,
      act.fct = act,
      linear.output = TRUE,  # For regression
      learningrate = lr,
      algorithm = "rprop+",
      stepmax = 1e5 )
  

    # Make predictions
    preds.reg <- predict(model.reg, test.reg.data[, -ncol(test.reg.data)])
    
    # Calculate RMSE
    rmse.reg <- sqrt(mean((preds.reg - test.reg.data$exam_score)^2))
    
    # Store results
    rmse[i] = rmse.reg
    
    # Update best model
    if(rmse.reg < best.reg.rmse) {
      best.reg.rmse <- rmse.reg
      best.reg.model <- model.reg 
      best.reg.params <- hyper.grid.reg[i, ]
    }
}

results.regNN <- hyper.grid.reg
results.regNN$rmse <- rmse


# View results sorted by RMSE
pander(results.regNN[order(results.regNN$rmse), ][1,])
layer1 learning.rate activation rmse
5 0.01 logistic 0.07245

Using the optimal hyperparameters, we train the final model below.

final.reg.model <- neuralnet(
  exam_score ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale,
  data = train.reg.data,
  hidden = best.reg.params$layer1,
  act.fct = best.reg.params$activation,
  linear.output = TRUE,
  learningrate = best.reg.params$learning_rate,
  algorithm = "rprop+",
  stepmax = 1e5
)

plot(final.reg.model, rep="best")

Below we show a scatterplot of the actual vs predicted values. It does not perform very well due to some outliers.

# Make predictions on test set
pred.NN1 <- predict(final.reg.model, test.reg.data[, -ncol(test.reg.data)])

# Calculate performance metrics
rmse.NN1 <- sqrt(mean((pred.NN1  - test.reg.data$exam_score)^2))
mae.NN1 <- mean(abs(pred.NN1  - test.reg.data$exam_score))
r.squared.NN1 <- cor(pred.NN1 , test.reg.data$exam_score)^2

# cat("Performance Metrics:\n")
# cat("RMSE:", rmse, "\n")
# cat("MAE:", mae, "\n")
# cat("R-squared:", r_squared, "\n")

# Plot predictions vs actual
plot.NN1.data <- data.frame(
  Actual = test.reg.data$exam_score,
  Predicted = pred.NN1 
)

ggplot(plot.NN1.data, aes(x = Actual, y = Predicted)) +
  geom_point() +
  geom_abline(intercept = 0, slope = 1, color = "darkred") +
  annotate("text", x=0.85, y=-2, 
           label=paste("R.sq =", round(r.squared.NN1,4)), color="blue") +
  annotate("text", x=0.85, y=-1, 
           label=paste("RMSE =", round(rmse.NN1,4)), color="blue") +
  annotate("text", x=0.85, y=.06, 
           label=paste("  MAE =", round(mae.NN1,4)), color="blue") +
  ggtitle("Actual vs Predicted Values") +
  theme_minimal()

Below shows that the neural network does not perform as well as standard linear regression so 2 layers may help.

# Train linear regression model
lm.model2 <- lm(exam_score ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale, data = test.reg.data)

# Make predictions
lm.predictions <- predict(lm.model2, test.reg.data[, -ncol(test.reg.data)])

# Calculate performance metrics
lm.rmse <- sqrt(mean((lm.predictions - test.reg.data$exam_score)^2))
lm.mae <- mean(abs(lm.predictions - test.reg.data$exam_score))
lm.r.squared <- cor(lm.predictions, test.reg.data$exam_score)^2


## improvements
RMSE.imp <- round((lm.rmse - rmse.NN1)/lm.rmse * 100,2)
MAE.imp <- round((lm.mae - mae.NN1)/lm.mae * 100, 2)
Rsq.imp <- round((r.squared.NN1 - lm.r.squared)/lm.r.squared * 100,2)

##
Performance.table <- data.frame(
  LM = c(lm.rmse, lm.mae, lm.r.squared),
  NN.1 = c(rmse.NN1, mae.NN1, r.squared.NN1),
  Improvement.percentage = c(RMSE.imp, MAE.imp, Rsq.imp)
)
rownames(Performance.table) <- c("RMSE", "MAE", "R.square")
pander(Performance.table)
  LM NN.1 Improvement.percentage
RMSE 0.06178 0.3757 -508.1
MAE 0.04935 0.09442 -91.32
R.square 0.9216 0.1956 -78.78

Below shows the performance of both standard linear regression and NN with one layer.

# Plot both predictions
comparison.data <- data.frame(
  Actual = test.reg.data$exam_score,
  MLP = pred.NN1,
  Linear = lm.predictions
)

ggplot(comparison.data) +
  geom_point(aes(x = Actual, y = MLP, color = "MLP")) +
  geom_point(aes(x = Actual, y = Linear, color = "Linear Regression")) +
  geom_abline(intercept = 0, slope = 1, color = "black") +
  scale_color_manual(values = c("MLP" = "blue", "Linear Regression" = "red")) +
  labs(title = "Model Comparison: Actual vs Predicted",
       x = "Actual Values",
       y = "Predicted Values",
       color = "Model Type") +
  theme(
    plot.margin = ggplot2::margin(40, 20, 20, 20, unit = "pt"),
    plot.title = element_text(hjust = 0.5, 
                              lineheight = 1.1,
                              vjust = 10)
    )

7.2 Two-hidden-layer Perceptron

Below we see possible hyperparameters.

# Define grid of hyperparameters
hyper.grid.NN2 <- expand.grid(
  layer1 = c(5, 10, 15),
  layer2 = c(0, 3, 5),  # 0 means no second layer
  learning.rate = c(0.01, 0.1),
  activation = c("logistic", "tanh")
)

# Initialize results in storage
results <- data.frame()
best.rmse <- Inf
best.model <- NULL

# Perform grid search
for(i in 1:nrow(hyper.grid.NN2)) {
  # Get current configuration
  layer1 <- hyper.grid.NN2$layer1[i]
  layer2 <- hyper.grid.NN2$layer2[i]
  lr <- hyper.grid.NN2$learning.rate[i]
  act <- hyper.grid.NN2$activation[i]
  
  # Create hidden layers vector
  if(layer2 == 0) {
    hidden.layers <- c(layer1)
  } else {
    hidden.layers <- c(layer1, layer2)
  }
  
  # Train model
  set.seed(123)
  model.NN2 <- tryCatch({
    neuralnet(
      exam_score ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale,
      data = train.reg.data,
      hidden = hidden.layers,
      act.fct = act,
      linear.output = TRUE,  # For regression
      learningrate = lr,
      algorithm = "rprop+",
      stepmax = 1e5
    )
  }, error = function(e) NULL)
  
  if(!is.null(model.NN2)) {
    # Make predictions
    preds <- predict(model.NN2, test.reg.data[, -ncol(test.reg.data)])
    
    # Calculate RMSE
    rmse <- sqrt(mean((preds - test.reg.data$exam_score)^2))
    
    # Store results
    results <- rbind(results, data.frame(
      layer1 = layer1,
      layer2 = layer2,
      learning_rate = lr,
      activation = act,
      rmse = rmse
    ))
    
    # Update best model
    if(rmse < best.rmse) {
      best.rmse <- rmse
      best.model <- model.NN2
      best.params <- hyper.grid.NN2[i, ]
    }
  }
}

# View results sorted by RMSE
results[order(results$rmse), ]
   layer1 layer2 learning_rate activation       rmse
7       5      5          0.01   logistic 0.06945465
16      5      5          0.10   logistic 0.06945465
22      5      3          0.01       tanh 0.07200686
31      5      3          0.10       tanh 0.07200686
4       5      3          0.01   logistic 0.07225654
13      5      3          0.10   logistic 0.07225654
1       5      0          0.01   logistic 0.07245266
10      5      0          0.10   logistic 0.07245266
2      10      0          0.01   logistic 0.07481921
11     10      0          0.10   logistic 0.07481921
25      5      5          0.01       tanh 0.07675567
34      5      5          0.10       tanh 0.07675567
20     10      0          0.01       tanh 0.07835699
29     10      0          0.10       tanh 0.07835699
23     10      3          0.01       tanh 0.07888405
32     10      3          0.10       tanh 0.07888405
3      15      0          0.01   logistic 0.07943591
12     15      0          0.10   logistic 0.07943591
8      10      5          0.01   logistic 0.08117725
17     10      5          0.10   logistic 0.08117725
6      15      3          0.01   logistic 0.08352691
15     15      3          0.10   logistic 0.08352691
5      10      3          0.01   logistic 0.08462088
14     10      3          0.10   logistic 0.08462088
9      15      5          0.01   logistic 0.09212715
18     15      5          0.10   logistic 0.09212715
21     15      0          0.01       tanh 0.09573949
30     15      0          0.10       tanh 0.09573949
19      5      0          0.01       tanh 0.10622119
28      5      0          0.10       tanh 0.10622119
27     15      5          0.01       tanh 0.10843835
36     15      5          0.10       tanh 0.10843835
24     15      3          0.01       tanh 0.13060978
33     15      3          0.10       tanh 0.13060978
26     10      5          0.01       tanh 0.16913666
35     10      5          0.10       tanh 0.16913666

Below we train the final model.

final.model.NN2 <- neuralnet(
  exam_score ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale,
  data = train.reg.data,
  hidden = if(best.params$layer2 == 0) {
        c(best.params$layer1)} else {
        c(best.params$layer1, best.params$layer2)},
  act.fct = best.params$activation,
  linear.output = TRUE,
  learningrate = best.params$learning.rate,
  algorithm = "rprop+",
  stepmax = 1e5
)

plot(final.model.NN2, rep="best")

Below is the actual vs predicted scatterplot and it seems to perform better.

# Make predictions on test set
pred.NN2 <- predict(final.model.NN2, test.reg.data[, -ncol(test.reg.data)])

# Calculate performance metrics
rmse.NN2 <- sqrt(mean((pred.NN2  - test.reg.data$exam_score)^2))
mae.NN2 <- mean(abs(pred.NN2  - test.reg.data$exam_score))
r.squared.NN2 <- cor(pred.NN2 , test.reg.data$exam_score)^2

## vector of error metric
NN2 <- c(rmse.NN2, mae.NN2, r.squared.NN2)

# Plot predictions vs actual
plot.data.NN2 <- data.frame(
  Actual = test.reg.data$exam_score,
  Predicted = pred.NN2 
)

ggplot(plot.data.NN2, aes(x = Actual, y = Predicted)) +
  geom_point() +
  geom_abline(intercept = 0, slope = 1, color = "red") +
  ggtitle("Actual vs Predicted Values") +
  theme_minimal()

Below shows that the NN with two layers does significantly better than the NN with one.

# Train linear regression model
lm.model3 <- lm(exam_score ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale, data = train.reg.data)

# Make predictions
lm.predictions2 <- predict(lm.model3, test.reg.data[, -ncol(test.reg.data)])

# Calculate performance metrics
lm.rmse2 <- sqrt(mean((lm.predictions2 - test.reg.data$exam_score)^2))
lm.mae2 <- mean(abs(lm.predictions2 - test.reg.data$exam_score))
lm.r.squared2 <- cor(lm.predictions2, test.reg.data$exam_score)^2

###
rmse.NN2.imp <- (lm.rmse2 - rmse.NN2)/lm.rmse2*100
mae.NN2.imp <- (lm.mae2 - mae.NN2)/lm.mae2 * 100
Rsq.NN2.imp <- (r.squared.NN2 - lm.r.squared2)/lm.r.squared2 * 100

NN2.improve <-c(rmse.NN2.imp, mae.NN2.imp, Rsq.NN2.imp)

perf.matrix <-data.frame(
              LM = c(lm.rmse2, lm.mae2, lm.r.squared2),
              NN.1 = c(rmse.NN1, mae.NN1, r.squared.NN1),
              NN.2 = c(rmse.NN2, mae.NN2, r.squared.NN2)
         )

perf.matrix$NN1.Improve <- round(100*(perf.matrix$LM-perf.matrix$NN.1)/perf.matrix$LM,2)
perf.matrix$NN2.Improve <- round(100*(perf.matrix$LM-perf.matrix$NN.2)/perf.matrix$LM,2)

rownames(perf.matrix) <- c("RMSE", "MAE", "R.sq")
pander(perf.matrix)
  LM NN.1 NN.2 NN1.Improve NN2.Improve
RMSE 0.067 0.3757 0.0711 -460.7 -6.11
MAE 0.0537 0.09442 0.05661 -75.83 -5.42
R.sq 0.9117 0.1956 0.8977 78.55 1.54

Below shows the performance of both standard and NN with 2 layers.

# Plot both predictions
comparison.data.NN2 <- data.frame(
  Actual = test.reg.data$exam_score,
  MLP = pred.NN2 ,
  Linear = lm.predictions2
)

ggplot(comparison.data.NN2) +
  geom_point(aes(x = Actual, y = MLP, color = "MLP")) +
  geom_point(aes(x = Actual, y = Linear, color = "Linear Regression")) +
  geom_abline(intercept = 0, slope = 1, color = "black") +
  scale_color_manual(values = c("MLP" = "blue", "Linear Regression" = "red")) +
  labs(title = "Model Comparison: Actual vs Predicted",
       x = "Actual Values",
       y = "Predicted Values",
       color = "Model Type") +
  theme_minimal()

8 MLP Classification

Below we perform the grid search for the single hidden layer.

#habits.scaled <- cbind(habits.scaled, complete_habits_data2$pass)

set.seed(123)  # For reproducibility
sample.size.cls <- floor(0.80 * nrow(habits.scaled2))
train.indices.cls <- sample(1:sample.size.cls, size = sample.size.cls, replace = FALSE)

train.data.cls2 <- habits.scaled2[train.indices.cls, ]
test.data.cls2 <- habits.scaled2[-train.indices.cls, ]
# Function to perform grid search for neuralnet
neuralnet.grid.search <- function(train.data, test.data, hidden.layers = 1) {
  # Define the grid of hyperparameters
  if (hidden.layers == 1) {
    hidden.nodes <- c(2, 4, 6, 8, 10)
    grid <- expand.grid(hidden = hidden.nodes)
  } else {
    hidden.nodes <- c(2, 4, 6)
    grid <- expand.grid(hidden1 = hidden.nodes, hidden2 = hidden.nodes)
  }
  
  # Add columns to store results
  grid$accuracy <- NA
  grid$auc <- NA
  

  
  # Perform grid search
  for (i in 1:nrow(grid)) {
    if (hidden.layers == 1) {
      hidden <- c(grid$hidden[i])
    } else {
      hidden <- c(grid$hidden1[i], grid$hidden2[i])
    }
    
    # Train the model
    nn.model <- neuralnet(
      pass ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale,
      data = train.data,
      hidden = hidden,
      linear.output = FALSE,  # For classification
      act.fct = "logistic",   # Sigmoid activation
      stepmax = 1e6           # Increase max steps for convergence
    )
    
    # Make predictions
    predictions <- predict(nn.model, test.data)
    predicted.classes <- ifelse(predictions > 0.6, 1, 0)
    
    # Calculate accuracy
    accuracy <- mean(predicted.classes == test.data$pass)
    
    # Calculate AUC
    roc.obj <- roc(test.data$pass, predictions)
    auc.value <- auc(roc.obj)
    
    # Store results
    grid$accuracy[i] <- accuracy
    grid$auc[i] <- auc.value
  }
  return(grid)
}
# Perform grid search for single hidden layer
grid.results.1layer <- neuralnet.grid.search(train.data=train.data.cls2,
                                             test.data=test.data.cls2, 
                                             hidden.layers = 1)
pander(grid.results.1layer)
hidden accuracy auc
2 0.895 0.9034
4 0.88 0.9354
6 0.875 0.9437
8 0.88 0.9403
10 0.885 0.9421

Above is the results from grid search for single hidden layer. Below we do the grid search for the 2 hidden layers.

grid.results.2layer <- neuralnet.grid.search(train.data=train.data.cls2, 
                                             test.data=test.data.cls2, 
                                             hidden.layers = 2)
pander(grid.results.2layer)
hidden1 hidden2 accuracy auc
2 2 0.87 0.8445
4 2 0.885 0.9315
6 2 0.89 0.9413
2 4 0.9 0.9148
4 4 0.89 0.922
6 4 0.895 0.9488
2 6 0.905 0.9002
4 6 0.875 0.9319
6 6 0.885 0.9347

Below we train the 1-layer model.

# Train single hidden layer model (using best configuration from grid search)
best.1layer <- grid.results.1layer[which.max(grid.results.1layer$auc), ]

nn.1layer <- neuralnet(
  pass ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours
  + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale,
  data = train.data.cls2,
  hidden = best.1layer$hidden,
  linear.output = FALSE,
  act.fct = "logistic",
  stepmax = 1e6
)
##
plot(nn.1layer, rep="best")

Below we train the 2-layer model.

# Train two hidden layers model (using best configuration from grid search)
best.2layer <- grid.results.2layer[which.max(grid.results.2layer$auc), ]

nn.2layer <- neuralnet(
  pass ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours
   + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale,
  data = train.data.cls2,
  hidden = c(best.2layer$hidden1, best.2layer$hidden2),
  linear.output = FALSE,
  act.fct = "logistic",
  stepmax = 1e6
)
##
plot(nn.2layer, rep="best")

Below we make predictions on the test data.

# Function to evaluate model performance
evaluate.model <- function(model, test.data) {
  # Make predictions
  predictions <- predict(model, test.data)
  predicted.classes <- ifelse(predictions > 0.6, 1, 0)
  
  # Calculate metrics
  accuracy <- mean(predicted.classes == test.data$pass)
  confusion.matrix <- table(Predicted = predicted.classes, Actual = test.data$pass)
  roc.obj <- roc(test.data$pass, predictions)
  auc.value <- auc(roc.obj)
  
  return(list(
    accuracy = accuracy,
    confusion.matrix = confusion.matrix,
    roc.obj = roc.obj,
    auc = auc.value
  ))
}

# Evaluate single hidden layer model
perf.1layer <- evaluate.model(nn.1layer, test.data.cls2)
#print(perf.1layer[c("accuracy", "confusion_matrix", "auc")])

# Evaluate two hidden layers model
perf.2layer <- evaluate.model(nn.2layer, test.data.cls2)
#print(perf.2layer[c("accuracy", "confusion_matrix", "auc")])

Below we show the performance of MLP 1 layer, MLP 2 layer, and standard logistic. We see that they all perform similarly.

# Train logistic regression model (base model)
logit.model <- glm(pass ~ age + study_hours_per_day + social_media_hours + netflix_hours + attendance_percentage + sleep_hours + diet_quality + exercise_frequency + parental_education_level + internet_quality + mental_health_rating + extracurricular_participationNo + extracurricular_participationYes + part_time_jobNo + part_time_jobYes + genderFemale + genderMale, data = train.data.cls2, family = binomial)

# Evaluate logistic regression model
logit.pred <- predict(logit.model, test.data.cls2, type = "response")
logit.classes <- ifelse(logit.pred > 0.6, 1, 0)
logit.accuracy <- mean(logit.classes == test.data.cls2$pass)
logit.roc <- roc(test.data.cls2$pass, logit.pred)
logit.auc <- auc(logit.roc)

##
roc.1layer <- perf.1layer$roc.obj
roc.2layer <- perf.2layer$roc.obj
roc.logit <- logit.roc

## specificity and sensitivity
sen.1layer <- roc.1layer$sensitivities
spe.1layer <- roc.1layer$specificities
sen.2layer <- roc.2layer$sensitivities
spe.2layer <- roc.2layer$specificities
sen.logit <- roc.logit$sensitivities
spe.logit <- roc.logit$specificities

## AUC
auc.1layer <- roc.1layer$auc
auc.2layer <- roc.2layer$auc
auc.logit <- roc.logit$auc

## Plot ROC curves for comparison
par(pty = "s")   # make a square plot to avaoid distortion
plot(1-spe.1layer, sen.1layer, type = "l", lty = 1,
     col = "blue", 
     xlab = "1 - specificity",
     ylab = "sensitvity",
     main = "ROC Curve Comparison")

lines(1-spe.2layer, sen.2layer, lty = 1, col = "darkred")
lines(1-spe.logit, sen.logit, lty = 1, col = "darkgreen")
legend("bottomright", 
       legend = c(paste("1-layer MLP (AUC =", round(perf.1layer$auc, 3), ")"),
                  paste("2-layer MLP (AUC =", round(perf.2layer$auc, 3), ")"),
                  paste("Logistic Reg (AUC =", round(logit.auc, 3), ")")),
                col = c("blue", "darkred", "darkgreen"), 
                lty = 1, cex = 0.7, bty = "n")

9 Results/Conclusion

Perceptrons can help give you a general understanding of how neural networks can predict a response variable but may be too simple.

MLP can be better since they work well with complex/nonlinear data. However, 2 layer MLP may not necessarily be better than 1 layer. Two layers may be too complex, so 1 layer MLP is better since it is simpler and can potentially generate similar accuracies.

For classification, MLP may not necessarily be better than standard logistic. Even though MLP can work well with nonlinear data, it may be more prone to overfitting. Standard logistic has high interpretability, computational efficiency, and is robust to overfitting.

For regression, MLP can also help with nonlinear data but we can take simpler models and computational resources into account.

LS0tDQp0aXRsZTogIlN0dWRlbnQgSGFiaXRzIHZzIEFjYWRlbWljIFBlcmZvcm1hbmNlIE5ldXJhbCBOZXR3b3JrcyINCmF1dGhvcjogIkVyaWMgWmh1Ig0KZGF0ZTogIjIwMjUtMDUtMDUiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRvY19jb2xsYXBzZWQ6IHllcw0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHllcw0KICAgIHNtb290aF9zY3JvbGw6IHllcw0KICAgIHRoZW1lOiBsdW1lbg0KICBwZGZfZG9jdW1lbnQ6IA0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiA0DQogICAgZmlnX2NhcHRpb246IHllcw0KICAgIG51bWJlcl9zZWN0aW9uczogbm8NCiAgICBmaWdfd2lkdGg6IDMNCiAgICBmaWdfaGVpZ2h0OiAzDQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCi0tLQ0KDQpgYGB7PWh0bWx9DQoNCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoNCi8qIENhc2NhZGluZyBTdHlsZSBTaGVldHMgKENTUykgaXMgYSBzdHlsZXNoZWV0IGxhbmd1YWdlIHVzZWQgdG8gZGVzY3JpYmUgdGhlIHByZXNlbnRhdGlvbiBvZiBhIGRvY3VtZW50IHdyaXR0ZW4gaW4gSFRNTCBvciBYTUwuIGl0IGlzIGEgc2ltcGxlIG1lY2hhbmlzbSBmb3IgYWRkaW5nIHN0eWxlIChlLmcuLCBmb250cywgY29sb3JzLCBzcGFjaW5nKSB0byBXZWIgZG9jdW1lbnRzLiAqLw0KDQpoMS50aXRsZSB7ICAvKiBUaXRsZSAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgdGhlIHJlcG9ydCB0aXRsZSAqLw0KICBmb250LXNpemU6IDI0cHg7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KICBjb2xvcjogbmF2eTsNCiAgdGV4dC1hbGlnbjogY2VudGVyOw0KICBmb250LWZhbWlseTogIkdpbGwgU2FucyIsIHNhbnMtc2VyaWY7DQp9DQpoNC5hdXRob3IgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgZm9yIGF1dGhvcnMgICovDQogIGZvbnQtc2l6ZTogMThweDsNCiAgZm9udC1mYW1pbHk6IHN5c3RlbS11aTsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQogIGNvbG9yOiBuYXZ5Ow0KICB0ZXh0LWFsaWduOiBjZW50ZXI7DQp9DQpoNC5kYXRlIHsgLyogSGVhZGVyIDQgLSBmb250IHNwZWNpZmljYXRpb25zIGZvciB0aGUgZGF0ZSAgKi8NCiAgZm9udC1zaXplOiAxOHB4Ow0KICBmb250LWZhbWlseTogc3lzdGVtLXVpOw0KICBjb2xvcjogRGFya0JsdWU7DQogIHRleHQtYWxpZ246IGNlbnRlcjsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQpoMSB7IC8qIEhlYWRlciAxIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgbGV2ZWwgMSBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMjBweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogbmF2eTsNCiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQpoMiB7IC8qIEhlYWRlciAyIC0gZm9udCBzcGVjaWZpY2F0aW9ucyBmb3IgbGV2ZWwgMiBzZWN0aW9uIHRpdGxlICovDQogICAgZm9udC1zaXplOiAxOHB4Ow0KICAgIGZvbnQtZmFtaWx5OiAiVGltZXMgTmV3IFJvbWFuIiwgVGltZXMsIHNlcmlmOw0KICAgIGNvbG9yOiBuYXZ5Ow0KICAgIHRleHQtYWxpZ246IGxlZnQ7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQoNCmgzIHsgLyogSGVhZGVyIDMgLSBmb250IHNwZWNpZmljYXRpb25zIG9mIGxldmVsIDMgc2VjdGlvbiB0aXRsZSAgKi8NCiAgICBmb250LXNpemU6IDE2cHg7DQogICAgZm9udC1mYW1pbHk6ICJUaW1lcyBOZXcgUm9tYW4iLCBUaW1lcywgc2VyaWY7DQogICAgY29sb3I6IG5hdnk7DQogICAgdGV4dC1hbGlnbjogbGVmdDsNCn0NCg0KaDQgeyAvKiBIZWFkZXIgNCAtIGZvbnQgc3BlY2lmaWNhdGlvbnMgb2YgbGV2ZWwgNCBzZWN0aW9uIHRpdGxlICAqLw0KICAgIGZvbnQtc2l6ZTogMTRweDsNCiAgICBmb250LWZhbWlseTogIlRpbWVzIE5ldyBSb21hbiIsIFRpbWVzLCBzZXJpZjsNCiAgICBjb2xvcjogZGFya3JlZDsNCiAgICB0ZXh0LWFsaWduOiBsZWZ0Ow0KfQ0KDQpib2R5IHsgYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsgfQ0KDQouaGlnaGxpZ2h0bWUgeyBiYWNrZ3JvdW5kLWNvbG9yOnllbGxvdzsgfQ0KDQpwIHsgYmFja2dyb3VuZC1jb2xvcjp3aGl0ZTsgfQ0KDQo8L3N0eWxlPg0KDQpgYGANCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQojIGNvZGUgY2h1bmsgc3BlY2lmaWVzIHdoZXRoZXIgdGhlIFIgY29kZSwgd2FybmluZ3MsIGFuZCBvdXRwdXQgDQojIHdpbGwgYmUgaW5jbHVkZWQgaW4gdGhlIG91dHB1dCBmaWxlcy4NCmlmICghcmVxdWlyZSgia25pdHIiKSkgew0KICAgaW5zdGFsbC5wYWNrYWdlcygia25pdHIiKQ0KICAgbGlicmFyeShrbml0cikNCn0NCmlmICghcmVxdWlyZSgidGlkeXZlcnNlIikpIHsNCiAgIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCn0NCmlmICghcmVxdWlyZSgicmVhZHhsIikpIHsgIyBTVk0gbWV0aG9kb2xvZ3kNCiAgIGluc3RhbGwucGFja2FnZXMoInJlYWR4bCIpDQpsaWJyYXJ5KHJlYWR4bCkNCn0NCmlmICghcmVxdWlyZSgiZ2dwbG90MiIpKSB7ICMgU1ZNIG1ldGhvZG9sb2d5DQogICBpbnN0YWxsLnBhY2thZ2VzKCJnZ3Bsb3QyIikNCmxpYnJhcnkoZ2dwbG90MikNCn0NCmlmICghcmVxdWlyZSgiSVNMUiIpKSB7ICMgY29udGFpbnMgZXhhbXBsZSBkYXRhIHNldCAiS2hhbiINCiAgIGluc3RhbGwucGFja2FnZXMoIklTTFIiKQ0KbGlicmFyeShJU0xSKQ0KfQ0KaWYgKCFyZXF1aXJlKCJNQVNTRXh0cmEiKSkgeyAjIGNvbnRhaW5zIGV4YW1wbGUgZGF0YSBzZXQgIktoYW4iDQogICBpbnN0YWxsLnBhY2thZ2VzKCJNQVNTRXh0cmEiKQ0KbGlicmFyeShNQVNTRXh0cmEpDQp9DQppZiAoIXJlcXVpcmUoIm1pc3NNZXRob2RzIikpIHsgIyBjdXN0b21pemVkIGNvbG9yaW5nIG9mIHBsb3RzDQogICBpbnN0YWxsLnBhY2thZ2VzKCJtaXNzTWV0aG9kcyIpDQpsaWJyYXJ5KG1pc3NNZXRob2RzKQ0KfQ0KaWYgKCFyZXF1aXJlKCJIbWlzYyIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygiSG1pc2MiKQ0KbGlicmFyeShIbWlzYykNCn0NCmlmICghcmVxdWlyZSgiZ2xtbmV0IikpIHsgIyBjdXN0b21pemVkIGNvbG9yaW5nIG9mIHBsb3RzDQogICBpbnN0YWxsLnBhY2thZ2VzKCJnbG1uZXQiKQ0KbGlicmFyeShnbG1uZXQpDQp9DQppZiAoIXJlcXVpcmUoIkdHYWxseSIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygiR0dhbGx5IikNCmxpYnJhcnkoR0dhbGx5KQ0KfQ0KaWYgKCFyZXF1aXJlKCJtaWNlIikpIHsgIyBjdXN0b21pemVkIGNvbG9yaW5nIG9mIHBsb3RzDQogICBpbnN0YWxsLnBhY2thZ2VzKCJtaWNlIikNCmxpYnJhcnkobWljZSkNCn0NCmlmICghcmVxdWlyZSgicGxvdGx5IikpIHsgIyBjdXN0b21pemVkIGNvbG9yaW5nIG9mIHBsb3RzDQogICBpbnN0YWxsLnBhY2thZ2VzKCJwbG90bHkiKQ0KbGlicmFyeShwbG90bHkpDQp9DQppZiAoIXJlcXVpcmUoImNhcmV0IikpIHsgIyBjdXN0b21pemVkIGNvbG9yaW5nIG9mIHBsb3RzDQogICBpbnN0YWxsLnBhY2thZ2VzKCJjYXJldCIpDQpsaWJyYXJ5KGNhcmV0KQ0KfQ0KaWYgKCFyZXF1aXJlKCJwUk9DIikpIHsgIyBjdXN0b21pemVkIGNvbG9yaW5nIG9mIHBsb3RzDQogICBpbnN0YWxsLnBhY2thZ2VzKCJwUk9DIikNCmxpYnJhcnkocFJPQykNCn0NCmlmICghcmVxdWlyZSgicGFuZGVyIikpIHsgIyBjdXN0b21pemVkIGNvbG9yaW5nIG9mIHBsb3RzDQogICBpbnN0YWxsLnBhY2thZ2VzKCJwYW5kZXIiKQ0KbGlicmFyeShwYW5kZXIpDQp9DQppZiAoIXJlcXVpcmUoImUxMDcxIikpIHsgIyBjdXN0b21pemVkIGNvbG9yaW5nIG9mIHBsb3RzDQogICBpbnN0YWxsLnBhY2thZ2VzKCJlMTA3MSIpDQpsaWJyYXJ5KGUxMDcxKQ0KfQ0KaWYgKCFyZXF1aXJlKCJycGFydCIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygicnBhcnQiKQ0KbGlicmFyeShycGFydCkNCn0NCmlmICghcmVxdWlyZSgicnBhcnQucGxvdCIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygicnBhcnQucGxvdCIpDQpsaWJyYXJ5KHJwYXJ0LnBsb3QpDQp9DQppZiAoIXJlcXVpcmUoInJhbmRvbUZvcmVzdCIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygicmFuZG9tRm9yZXN0IikNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KfQ0KaWYgKCFyZXF1aXJlKCJmb3JjYXRzIikpIHsgIyBjdXN0b21pemVkIGNvbG9yaW5nIG9mIHBsb3RzDQogICBpbnN0YWxsLnBhY2thZ2VzKCJmb3JjYXRzIikNCmxpYnJhcnkoZm9yY2F0cykNCn0NCmlmICghcmVxdWlyZSgiaXByZWQiKSkgeyAjIGN1c3RvbWl6ZWQgY29sb3Jpbmcgb2YgcGxvdHMNCiAgIGluc3RhbGwucGFja2FnZXMoImlwcmVkIikNCmxpYnJhcnkoaXByZWQpDQp9DQppZiAoIXJlcXVpcmUoIm5ldXJhbG5ldCIpKSB7ICMgY3VzdG9taXplZCBjb2xvcmluZyBvZiBwbG90cw0KICAgaW5zdGFsbC5wYWNrYWdlcygibmV1cmFsbmV0IikNCmxpYnJhcnkobmV1cmFsbmV0KQ0KfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCAgICAgICAjIGluY2x1ZGUgY29kZSBjaHVuayBpbiB0aGUgb3V0cHV0IGZpbGUNCiAgICAgICAgICAgICAgICAgICAgICB3YXJuaW5nID0gRkFMU0UsICAgIyBzb21ldGltZXMsIHlvdSBjb2RlIG1heSBwcm9kdWNlIHdhcm5pbmcgbWVzc2FnZXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgeW91IGNhbiBjaG9vc2UgdG8gaW5jbHVkZSB0aGUgd2FybmluZyBtZXNzYWdlcyBpbg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHRoZSBvdXRwdXQgZmlsZS4gDQogICAgICAgICAgICAgICAgICAgICAgcmVzdWx0cyA9IFRSVUUsICAgICMgeW91IGNhbiBhbHNvIGRlY2lkZSB3aGV0aGVyIHRvIGluY2x1ZGUgdGhlIG91dHB1dA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGluIHRoZSBvdXRwdXQgZmlsZS4NCiAgICAgICAgICAgICAgICAgICAgICBtZXNzYWdlID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICAgY29tbWVudCA9IE5BDQogICAgICAgICAgICAgICAgICAgICAgKQ0KYGBgDQoNCiMgSW50cm9kdWN0aW9uDQoNClRoaXMgcHJvamVjdCB1c2VzIHRoZSBzYW1lIGRhdGFzZXQgYXMgcHJvamVjdCAzLg0KDQpUaGlzIGRhdGFzZXQgaXMgdGl0bGVkIOKAnFN0dWRlbnQgSGFiaXRzIHZzIEFjYWRlbWljIFBlcmZvcm1hbmNl4oCdIGFuZCBzaG93cyB2YXJpb3VzIGV4cGxhbmF0b3J5IHZhcmlhYmxlcyBhbmQgb25lIHRhcmdldC9yZXNwb25zZSB2YXJpYWJsZSAoZXhhbV9zY29yZSkuIFRoZSBwdXJwb3NlIG9mIGNvbGxlY3RpbmcgdGhpcyBkYXRhc2V0IGlzIHRvIHNlZSB3aGF0IGZhY3RvcnMgbWF5IGFmZmVjdCBleGFtIHNjb3Jlcy4gVGhpcyBkYXRhc2V0IHdhcyBjb2xsZWN0ZWQgZnJvbSBLYWdnbGUuIFRoaXMgZGF0YXNldCBjb250YWlucyAxMDAwIG9ic2VydmF0aW9ucyBhbmQgMTYgdmFyaWFibGVzICgxNSBmZWF0dXJlIHZhcmlhYmxlcyBhbmQgMSB0YXJnZXQgdmFyaWFibGUpLg0KDQpUaGUgc3R1ZGVudF9pZCBzZXJ2ZXMgYXMgdGhlIGlkZW50aWZpZXIuIFRoZSBhZ2UgKG51bWVyaWNhbCkgc2hvd3MgZWFjaCBwZXJzb25zIGFnZS4gVGhlIGdlbmRlciAoY2F0ZWdvcmljYWwpIHNob3dzIGVhY2ggcGVyc29ucyBnZW5kZXIuIFRoZSBzdHVkeV9ob3Vyc19wZXJfZGF5IHNob3dzIGhvdXJzIHN0dWRpZWQgcGVyIGRheS4gVGhlIHNvY2lhbF9tZWRpYV9ob3VycyBzaG93cyBob3VycyB1c2VkIGRhaWx5IG9uIHNvY2lhbCBtZWRpYS4gVGhlIG5ldGZsaXhfaG91cnMgc2hvd3MgaG91cnMgdXNlZCBkYWlseSBvbiBuZXRmbGl4LiBUaGUgcGFydF90aW1lX2pvYiAobm8veWVzKSBzaG93cyBpZiB0aGUgcGVyc29uIGhhcyBhIHBhcnQgdGltZSBqb2IuIFRoZSBhdHRlbmRhbmNlIHBlcmNlbnRhZ2Ugc2hvd3MgdGhlIHBlcmNlbnRhZ2Ugb2YgY2xhc3NlcyBhdHRlbmRlZC4gVGhlIHNsZWVwX2hvdXJzIHNob3dzIGFtb3VudCBvZiBzbGVlcCBwZXIgbmlnaHQuIFRoZSBkaWV0X3F1YWxpdHkgKHBvb3IvZmFpci9nb29kKSBzaG93cyBxdWFsaXR5IG9mIG9uZXMgZGlldC4gVGhlIGV4ZXJjaXNlX2ZyZXF1ZW5jeSBzaG93cyBob3cgbWFueSB0aW1lcyBvbmUgZXhlcmNpc2VzIHBlciB3ZWVrLiBUaGUgcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsIChub25lL0hpZ2ggU2Nob29sL2JhY2hlbG9yL21hc3Rlcikgc2hvd3MgdGhlIGhpZ2hlc3QgbGV2ZWwgb2YgZWR1Y2F0aW9uIG9mIG9uZXMgcGFyZW50cy4gVGhlIGludGVybmV0X3F1YWxpdHkocG9vci9hdmcvZ29vZCkgc2hvd3MgaG93IGdvb2Qgb25lcyB3aS1maSBpcy4gVGhlIG1lbnRhbF9oZWFsdGhfcmF0aW5nICgxLTEwKSBzaG93cyBob3cgb25lIHJhdGVzIHRoZWlyIG1lbnRhbCBoZWFsdGguIFRoZSBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiAobi95KSBzaG93cyBpZiBvbmUgcGFydGljaXBhdGVzIGluIGV4dHJhY3VycmljdWxhciBhY3Rpdml0aWVzLiBUaGUgdGFyZ2V0L3Jlc3BvbnNlIHZhcmlhYmxlIGlzIGV4YW1fc2NvcmUgKGNvbnRpbnVvdXMpLg0KDQpCYXNlZCBvbiB0aGlzIGRhdGFzZXQsIHdlIGNhbiBmb3JtdWxhdGUgYSByZXNlYXJjaCBxdWVzdGlvbi4gVGhlIHF1ZXN0aW9uIHdlIGNhbiBmb3JtIGlzIHdoYXQgdmFyaWFibGVzIGFyZSB0aGUgbW9zdCBpbXBvcnRhbnQgaW4gcHJlZGljdGluZyBleGFtIHNjb3JlLiBGb3IgcmVncmVzc2lvbiwgd2UgY2FuIHVzZSBleGFtX3Njb3JlIChjb250aW51b3VzKSBhcyBvdXIgcmVzcG9uc2UgdmFyaWFibGUuIEZvciBjbGFzc2lmaWNhdGlvbiwgd2UgY2FuIGNyZWF0ZSBhIGJpbmFyeSByZXNwb25zZSB2YXJpYWJsZSB3aXRoIDEgKHBhc3MpIGFuZCAwIChmYWlsKS4gV2UgY2FuIHVzZSB0aGUgY29uZGl0aW9uIGlmIGV4YW0gc2NvcmUgaXMgZ3JlYXRlciBvciBsZXNzIHRoYW4gMC42MC4NCg0KDQojIFJlYWQgaW4gRGF0YXNldA0KDQpSZWFkIGluIGRhdGFzZXQuIFNlZSBkYXRhc2V0IHN0cnVjdHVyZS4NCg0KYGBge3J9DQpoYWJpdHMyIDwtIHJlYWQuY3N2KCJzdHVkZW50X2hhYml0c19wZXJmb3JtYW5jZS5jc3YiKQ0Kc3RyKGhhYml0czIpDQpgYGANCg0KDQojIEVEQQ0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGdlbmRlciB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoaGFiaXRzMiwgYWVzKHggPSBnZW5kZXIpKSArIA0KICANCiAgZ2VvbV9iYXIoKSArDQogIGxhYnModGl0bGUgPSAiR2VuZGVyIikNCmBgYA0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHBhcnRfdGltZV9qb2IgdmFyaWFibGUuDQoNCmBgYHtyfQ0KZ2dwbG90KGhhYml0czIsIGFlcyh4ID0gcGFydF90aW1lX2pvYikpICsgDQogIA0KICBnZW9tX2JhcigpICsNCiAgbGFicyh0aXRsZSA9ICJwYXJ0X3RpbWVfam9iIikNCmBgYA0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGRpZXRfcXVhbGl0eSB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoaGFiaXRzMiwgYWVzKHggPSBkaWV0X3F1YWxpdHkpKSArIA0KICANCiAgZ2VvbV9iYXIoKSArDQogIGxhYnModGl0bGUgPSAiZGlldF9xdWFsaXR5IikNCmBgYA0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoaGFiaXRzMiwgYWVzKHggPSBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwpKSArIA0KICANCiAgZ2VvbV9iYXIoKSArDQogIGxhYnModGl0bGUgPSAicGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsIikNCmBgYA0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGludGVybmV0X3F1YWxpdHkgdmFyaWFibGUuDQoNCmBgYHtyfQ0KZ2dwbG90KGhhYml0czIsIGFlcyh4ID0gaW50ZXJuZXRfcXVhbGl0eSkpICsgDQogIA0KICBnZW9tX2JhcigpICsNCiAgbGFicyh0aXRsZSA9ICJpbnRlcm5ldF9xdWFsaXR5IikNCmBgYA0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uIHZhcmlhYmxlLg0KDQpgYGB7cn0NCmdncGxvdChoYWJpdHMyLCBhZXMoeCA9IGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uKSkgKyANCiAgDQogIGdlb21fYmFyKCkgKw0KICBsYWJzKHRpdGxlID0gImV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uIikNCmBgYA0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIGFnZSB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGhhYml0czIsIGFlcyh4ID0gYWdlKSkgKyANCiAgZ2VvbV9ib3hwbG90KCkgKyANCiAgDQogIGxhYnModGl0bGUgPSAiYWdlIikNCmBgYA0KDQpUaGUgYmVsb3cgZmlndXJlIHNob3dzIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlIHN0dWR5X2hvdXJzX3Blcl9kYXkgdmFyaWFibGUuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBoYWJpdHMyLCBhZXMoeCA9IHN0dWR5X2hvdXJzX3Blcl9kYXkpKSArIA0KICBnZW9tX2JveHBsb3QoKSArIA0KICANCiAgbGFicyh0aXRsZSA9ICJzdHVkeV9ob3Vyc19wZXJfZGF5IikNCmBgYA0KDQoNClRoZSBiZWxvdyBmaWd1cmUgc2hvd3MgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgc29jaWFsX21lZGlhX2hvdXJzIHZhcmlhYmxlLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gaGFiaXRzMiwgYWVzKHggPSBzb2NpYWxfbWVkaWFfaG91cnMpKSArIA0KICBnZW9tX2JveHBsb3QoKSArIA0KICANCiAgbGFicyh0aXRsZSA9ICJzb2NpYWxfbWVkaWFfaG91cnMiKQ0KYGBgDQoNCg0KVGhlIGJlbG93IGZpZ3VyZSBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBuZXRmbGl4X2hvdXJzIHZhcmlhYmxlLg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gaGFiaXRzMiwgYWVzKHggPSBuZXRmbGl4X2hvdXJzKSkgKyANCiAgZ2VvbV9ib3hwbG90KCkgKyANCiAgDQogIGxhYnModGl0bGUgPSAibmV0ZmxpeF9ob3VycyIpDQpgYGANCg0KVGhlIGJlbG93IGZpZ3VyZSBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgdmFyaWFibGUuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBoYWJpdHMyLCBhZXMoeCA9IGF0dGVuZGFuY2VfcGVyY2VudGFnZSkpICsgDQogIGdlb21fYm94cGxvdCgpICsgDQogIA0KICBsYWJzKHRpdGxlID0gImF0dGVuZGFuY2VfcGVyY2VudGFnZSIpDQpgYGANCg0KVGhlIGJlbG93IGZpZ3VyZSBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBzbGVlcF9ob3VycyB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGhhYml0czIsIGFlcyh4ID0gc2xlZXBfaG91cnMpKSArIA0KICBnZW9tX2JveHBsb3QoKSArIA0KICANCiAgbGFicyh0aXRsZSA9ICJzbGVlcF9ob3VycyIpDQpgYGANCg0KVGhlIGJlbG93IGZpZ3VyZSBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBleGVyY2lzZV9mcmVxdWVuY3kgdmFyaWFibGUuDQoNCmBgYHtyfQ0KZ2dwbG90KGRhdGEgPSBoYWJpdHMyLCBhZXMoeCA9IGV4ZXJjaXNlX2ZyZXF1ZW5jeSkpICsgDQogIGdlb21fYm94cGxvdCgpICsgDQogIA0KICBsYWJzKHRpdGxlID0gImV4ZXJjaXNlX2ZyZXF1ZW5jeSIpDQpgYGANCg0KVGhlIGJlbG93IGZpZ3VyZSBzaG93cyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBtZW50YWxfaGVhbHRoX3JhdGluZyB2YXJpYWJsZS4NCg0KYGBge3J9DQpnZ3Bsb3QoZGF0YSA9IGhhYml0czIsIGFlcyh4ID0gbWVudGFsX2hlYWx0aF9yYXRpbmcpKSArIA0KICBnZW9tX2JveHBsb3QoKSArIA0KICANCiAgbGFicyh0aXRsZSA9ICJtZW50YWxfaGVhbHRoX3JhdGluZyIpDQpgYGANCg0KDQojIEltcHV0YXRpb24vRmVhdHVyZSBFbmdpbmVlcmluZy4NCg0KVGhlIHZhcmlhYmxlcyBwYXJ0X3RpbWVfam9iLCBkaWV0X3F1YWxpdHksIGludGVybmV0X3F1YWxpdHksIGFuZCBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiBhcmUgY29udmVydGVkIHRvIGNhdGVnb3JpY2FsL2ZhY3Rvci4NCg0KYGBge3J9DQoNCmhhYml0czIkcGFydF90aW1lX2pvYiA8LSBhcy5mYWN0b3IoaGFiaXRzMiRwYXJ0X3RpbWVfam9iKQ0KaGFiaXRzMiRkaWV0X3F1YWxpdHkgPC0gZmFjdG9yKGhhYml0czIkZGlldF9xdWFsaXR5LGxldmVscyA9IGMoIlBvb3IiLCAiRmFpciIsICJHb29kIikpDQoNCmhhYml0czIkaW50ZXJuZXRfcXVhbGl0eSA8LSBmYWN0b3IoaGFiaXRzMiRpbnRlcm5ldF9xdWFsaXR5LCBsZXZlbHMgPSBjKCJQb29yIiwgIkF2ZXJhZ2UiLCAiR29vZCIpKQ0KaGFiaXRzMiRleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbiA8LSBhcy5mYWN0b3IoaGFiaXRzMiRleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbikNCmBgYA0KDQpUaGUgdmFyaWFibGUgZ2VuZGVyIGNvbnRhaW5zIHRoZSB2YWx1ZSDigJhPdGhlcuKAmS4gU2luY2Ugd2Ugb25seSB3YW50IHRvIHdvcmsgd2l0aCBNYWxlL2ZlbWFsZSwgd2Ugc2V0IOKAmE90aGVy4oCZIHRvIG1pc3NpbmcgYW5kIHVzZSBNSUNFIGltcHV0YXRpb24uDQoNCmBgYHtyfQ0KaGFiaXRzMiRnZW5kZXJbaGFiaXRzMiRnZW5kZXI9PSAiT3RoZXIiXSA9IE5BDQpoYWJpdHMyJGdlbmRlciA8LSBhcy5mYWN0b3IoaGFiaXRzMiRnZW5kZXIpDQoNCmluaXQzIDwtIG1pY2UoaGFiaXRzMiwgbWF4aXQgPSAwKQ0KaW5pdDMkbWV0aG9kDQpgYGANCg0KDQpgYGB7cn0NCmltcDMgPC0gbWljZShoYWJpdHMyLCBtZXRob2QgPSBjKCIiLCIiLCAibG9ncmVnIiwgIiIsICAiIiwgIiIsICIiLCAiIiwgIiIsICIiLCAiIiwgIiIsICIiLCAiIiwgIiIsICIiKSwgDQogICAgICAgICAgICAgICAgIG1heGl0ID0gMTAsICANCiAgICAgICAgICAgICAgICAgbSA9IDUsIA0KICAgICAgICAgICAgICAgICBzZWVkPTEyMywNCiAgICAgICAgICAgICAgICAgcHJpbnQ9RikgICAgIA0KDQoNCg0KY29tcGxldGVfaGFiaXRzX2RhdGEyIDwtIGNvbXBsZXRlKGltcDMpDQpgYGANCg0KRm9yIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCwgd2UgY29udmVydCBib3RoIEJhY2hlbG9yIGFuZCBNYXN0ZXIgdG8gQ29sbGVnZSB0byByZWR1Y2UgY2F0ZWdvcmllcy4NCg0KYGBge3J9DQpjb21wbGV0ZV9oYWJpdHNfZGF0YTIkcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsW2NvbXBsZXRlX2hhYml0c19kYXRhMiRwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgPT0gJ0JhY2hlbG9yJ10gPC0gJ0NvbGxlZ2UnDQpjb21wbGV0ZV9oYWJpdHNfZGF0YTIkcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsW2NvbXBsZXRlX2hhYml0c19kYXRhMiRwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgPT0gJ01hc3RlciddIDwtICdDb2xsZWdlJw0KDQpjb21wbGV0ZV9oYWJpdHNfZGF0YTIkcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsIDwtIGZhY3Rvcihjb21wbGV0ZV9oYWJpdHNfZGF0YTIkcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsLGxldmVscyA9IGMoIk5vbmUiLCAiSGlnaCBTY2hvb2wiLCAiQ29sbGVnZSIpKQ0KDQpzdHIoY29tcGxldGVfaGFiaXRzX2RhdGEyKQ0KYGBgDQoNClNpbmNlIG5ldXJhbCBuZXR3b3JrcyBjYW4gb25seSB3b3JrIHdpdGggbnVtZXJpY2FsIHZhcmlhYmxlcywgd2UgbmVlZCB0byB0cmFuc2Zvcm0gYWxsIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaW50byBudW1lcmljYWwgZm9ybWF0IHVzaW5nIGEgbWl4IG9mIGxhYmVsIGVuY29kaW5nIChvcmRpbmFsKSBhbmQgb25lIGhvdCBlbmNvZGluZyAobm9taW5hbCkuDQoNCmNvbXBsZXRlX2hhYml0c19kYXRhMiBpcyB0aGUgY29tcGxldGUgZGF0YXNldCB0aGF0IHdpbGwgYmUgdXNlZCBmb3IgdGhlIHByb2plY3QuDQoNCmBgYHtyfQ0KY29tcGxldGVfaGFiaXRzX2RhdGEyJGRpZXRfcXVhbGl0eSA8LSBhcy5udW1lcmljKGZhY3Rvcihjb21wbGV0ZV9oYWJpdHNfZGF0YTIkZGlldF9xdWFsaXR5KSkNCmNvbXBsZXRlX2hhYml0c19kYXRhMiRwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgPC0gYXMubnVtZXJpYyhmYWN0b3IoY29tcGxldGVfaGFiaXRzX2RhdGEyJHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCkpDQpjb21wbGV0ZV9oYWJpdHNfZGF0YTIkaW50ZXJuZXRfcXVhbGl0eSA8LSBhcy5udW1lcmljKGZhY3Rvcihjb21wbGV0ZV9oYWJpdHNfZGF0YTIkaW50ZXJuZXRfcXVhbGl0eSkpDQoNCmVuY29kZWRfZGF0YSA8LSBtb2RlbC5tYXRyaXgofiBnZW5kZXIgLSAxLCBkYXRhID0gY29tcGxldGVfaGFiaXRzX2RhdGEyKQ0KZW5jb2RlZF9kYXRhMiA8LSBtb2RlbC5tYXRyaXgofiBwYXJ0X3RpbWVfam9iIC0gMSwgZGF0YSA9IGNvbXBsZXRlX2hhYml0c19kYXRhMikNCmVuY29kZWRfZGF0YTMgPC0gbW9kZWwubWF0cml4KH4gZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb24gLSAxLCBkYXRhID0gY29tcGxldGVfaGFiaXRzX2RhdGEyKQ0KDQpjb21wbGV0ZV9oYWJpdHNfZGF0YTIgPC0gY2JpbmQoY29tcGxldGVfaGFiaXRzX2RhdGEyLCBlbmNvZGVkX2RhdGEsIGVuY29kZWRfZGF0YTIsIGVuY29kZWRfZGF0YTMpDQpgYGANCg0KDQojIFBlcmNlcHRyb24gUmVncmVzc2lvbg0KDQpJbiB0aGlzIHNlY3Rpb24sIHdlIHBlcmZvcm0gYSBwZXJjZXB0cm9uIG1vZGVsIGZvciB0aGUgY29udGludW91cyByZXNwb25zZSB2YXJpYWJsZSBleGFtX3Njb3JlLg0KDQpUaGUgcGVyY2VwdHJvbiBpcyB0aGUgc2ltcGxlc3QgZm9ybSBvZiBhIG5ldXJhbCBuZXR3b3JrLiBUaGUgcGVyY2VwdHJvbiBtb2RlbCB0YWtlcyBpbnB1dCBmZWF0dXJlcywgY29tcHV0ZXMgdGhlaXIgd2VpZ2h0ZWQgc3VtLCBhbmQgcGFzc2VzIHRoZSByZXN1bHQgdGhyb3VnaCBhbiBhY3RpdmF0aW9uIGZ1bmN0aW9uIHRvIGdlbmVyYXRlIHRoZSBvdXRwdXQuIFRoZSBsaW5lYXIgYWN0aXZhdGlvbiBmdW5jdGlvbiBpcyB1c2VkLg0KDQpUaGUgYmVsb3cgY29kZSBjaHVuayBwZXJmb3JtcyB0aGUgZGF0YSBzcGxpdHRpbmcgYW5kIHRoZSBub3JtYWxpemF0aW9uIGZvciBhbGwgdmFyaWFibGVzLg0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCg0KY29tcGxldGVfaGFiaXRzX2RhdGEyIDwtIGNvbXBsZXRlX2hhYml0c19kYXRhMlsgLWMoMSwzLDcsMTUpIF0NCg0KIyBOb3JtYWxpemUgZGF0YSAoMC0xIHJhbmdlKQ0Kbm9ybWFsaXplIDwtIGZ1bmN0aW9uKHgpIHsNCiAgKHggLSBtaW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSkNCn0NCiMgbm9ybWFsaXplIGFsbCBmZWF0dXJlcyBpbmNsdWRpbmcgdGhlIHRhcmdldCB2YXJpYWJsZQ0KaGFiaXRzLnNjYWxlZCA8LSBhcy5kYXRhLmZyYW1lKGxhcHBseShjb21wbGV0ZV9oYWJpdHNfZGF0YTIsIG5vcm1hbGl6ZSkpDQoNCiMgVHJhaW4tdGVzdCBzcGxpdCAoNzAtMzApDQojc2V0LnNlZWQoMTIzKQ0KIyMNCnNhbXBsZS5zaXplIDwtIGRpbShoYWJpdHMuc2NhbGVkKVsxXQ0KdHJhaW4uaW5kaWNlcyA8LSBzYW1wbGUoMTpzYW1wbGUuc2l6ZSwgcm91bmQoMC44KnNhbXBsZS5zaXplKSkNCiMjDQoNCnRyYWluLmRhdGEubm9ybSA8LSBoYWJpdHMuc2NhbGVkW3RyYWluLmluZGljZXMsIF0NCnRlc3QuZGF0YS5ub3JtIDwtIGhhYml0cy5zY2FsZWRbLXRyYWluLmluZGljZXMsIF0NCiMjDQp0cmFpbi5vcmlnIDwtIGNvbXBsZXRlX2hhYml0c19kYXRhMlt0cmFpbi5pbmRpY2VzLCBdDQp0ZXN0Lm9yaWcgPC0gY29tcGxldGVfaGFiaXRzX2RhdGEyWy10cmFpbi5pbmRpY2VzLCBdDQoNCnVuc2NhbGUgPC0gZnVuY3Rpb24oeCwgb3JpZ2luYWwpIHsNCiAgcmV0dXJuKHggKiAobWF4KG9yaWdpbmFsKSAtIG1pbihvcmlnaW5hbCkpICsgbWluKG9yaWdpbmFsKSkNCn0NCmBgYA0KDQpUaGUgdGFibGUgYmVsb3cgc2hvd3MgYWxsIHBvc3NpYmxlIGNvbWJpbmF0aW9uIG9mIGh5cGVycGFyYW1ldGVyIHZhbHVlcyBvZiBsZWFybmluZ3JhdGUsIHRocmVzaG9sZCwgc3RlcG1heCwgYW5kIFJNU0UuDQoNCmBgYHtyfQ0KIyBEZWZpbmUgdGhlIHR1bmluZyBncmlkDQp0dW5lLmdyaWQubm4gPC0gZXhwYW5kLmdyaWQoDQogICBsZWFybmluZ3JhdGUgPSBjKDAuMDAxLCAwLjAxLCAwLjEsIDAuNSwgMSksDQogICB0aHJlc2hvbGQgPSBjKDAuMDEsIDAuMDUsIDAuMSwgMC41KSwNCiAgIHN0ZXBtYXggPSBjKDFlNSwgMWU2KSAgIyBBZGQgc3RlcG1heCB0byBwcmV2ZW50IGluZmluaXRlIHRyYWluaW5nDQopDQoNCiMgQ3VzdG9tIHRyYWluaW5nIGZ1bmN0aW9uIGZvciBuZXVyYWxuZXQoKQ0KbmV1cmFsbmV0LnRyYWluIDwtIGZ1bmN0aW9uKGxlYXJuaW5ncmF0ZSwgdGhyZXNob2xkLCBzdGVwbWF4KSB7DQogIG1vZGVsIDwtIG5ldXJhbG5ldCgNCiAgICBleGFtX3Njb3JlIH4gYWdlICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3VycyArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uTm8gKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvblllcyArIHBhcnRfdGltZV9qb2JObyArIHBhcnRfdGltZV9qb2JZZXMgKyBnZW5kZXJGZW1hbGUgKyBnZW5kZXJNYWxlLA0KICAgIGRhdGEgPSB0cmFpbi5kYXRhLm5vcm0sDQogICAgaGlkZGVuID0gMCwgICMgUGVyY2VwdHJvbiAobm8gaGlkZGVuIGxheWVyKQ0KICAgIGxpbmVhci5vdXRwdXQgPSBUUlVFLCAgIyBGb3IgcmVncmVzc2lvbg0KICAgIGxlYXJuaW5ncmF0ZSA9IGxlYXJuaW5ncmF0ZSwNCiAgICB0aHJlc2hvbGQgPSB0aHJlc2hvbGQsDQogICAgc3RlcG1heCA9IHN0ZXBtYXgNCiAgKQ0KICAjIENhbGN1bGF0ZSBSTVNFIG9uIHRyYWluaW5nIGRhdGENCiAgcHJlZCA8LSBwcmVkaWN0KG1vZGVsLCB0cmFpbi5kYXRhLm5vcm1bLCAtbmNvbCh0cmFpbi5kYXRhLm5vcm0pXSkNCiAgDQoNCiAgcm1zZSA8LSBzcXJ0KG1lYW4oKHByZWQgLSB0cmFpbi5kYXRhLm5vcm0kZXhhbV9zY29yZSleMikpDQogIHJldHVybihybXNlKQ0KfQ0KDQojIFBlcmZvcm0gZ3JpZCBzZWFyY2g6DQojIHVzaW5nIGFwcGx5IHRoZSAoKSBmdW5jdGlvbiB0byBjYWxsIG5ldXJhbG5ldC50cmFpbigpIHVzaW5nIHRoZSBjb21wb25lbnRzIG9mIHRoZQ0KIyByb3cgdmVjdG9yIGluIHRoZSB0dW5lLmdyaWQubm4gKGRhdGEgZnJhbWUgb2YgY29tYmluYXRpb25zIG9mIGh5cGVycGFyYW1ldGVycykuDQojDQpyZXN1bHRzIDwtIGFwcGx5KHR1bmUuZ3JpZC5ubiwgMSwgZnVuY3Rpb24oeCkgew0KICBuZXVyYWxuZXQudHJhaW4oeFsibGVhcm5pbmdyYXRlIl0sIHhbInRocmVzaG9sZCJdLCB4WyJzdGVwbWF4Il0pDQp9KQ0KDQojIw0KIyBDb21iaW5lIHJlc3VsdHMgd2l0aCBwYXJhbWV0ZXIgY29tYmluYXRpb25zDQp0dW5lLnJlc3VsdHMgPC0gY2JpbmQodHVuZS5ncmlkLm5uLCBSTVNFID0gcmVzdWx0cykNCiMjDQpwYW5kZXIodHVuZS5yZXN1bHRzKQ0KYGBgDQoNClRoZSB0YWJsZSBiZWxvdyBzaG93cyB0aGUgYmVzdCBjb21iaW5hdGlvbiBvZiBoeXBlcnBhcmFtZXRlciB2YWx1ZXMuDQoNCmBgYHtyfQ0KIyBGaW5kIHRoZSBiZXN0IGNvbWJpbmF0aW9uDQpiZXN0LnBhcmFtcyA8LSB0dW5lLnJlc3VsdHNbd2hpY2gubWluKHR1bmUucmVzdWx0cyRSTVNFKSwgXQ0KcGFuZGVyKGJlc3QucGFyYW1zKQ0KYGBgDQoNClRoZSBmaW5hbC5tb2RlbC5ubiBiZWxvdyB0cmFpbnMgdGhlIGZpbmFsIG5ldXJhbCBuZXR3b3JrIG1vZGVsIHVzaW5nIHRoZSBiZXN0IGNvbWJpbmF0aW9uIG9mIGh5cGVycGFyYW1ldGVyIHZhbHVlcyBmcm9tIGFib3ZlLg0KDQpgYGB7cn0NCmZpbmFsLm1vZGVsLm5uIDwtIG5ldXJhbG5ldCgNCiAgZXhhbV9zY29yZSB+IGFnZSArIHN0dWR5X2hvdXJzX3Blcl9kYXkgKyBzb2NpYWxfbWVkaWFfaG91cnMgKyBuZXRmbGl4X2hvdXJzICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbk5vICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb25ZZXMgKyBwYXJ0X3RpbWVfam9iTm8gKyBwYXJ0X3RpbWVfam9iWWVzICsgZ2VuZGVyRmVtYWxlICsgZ2VuZGVyTWFsZSwNCiAgZGF0YSA9IHRyYWluLmRhdGEubm9ybSwNCiAgaGlkZGVuID0gMCwNCiAgbGluZWFyLm91dHB1dCA9IFRSVUUsDQogIGxlYXJuaW5ncmF0ZSA9IGJlc3QucGFyYW1zJGxlYXJuaW5ncmF0ZSwNCiAgdGhyZXNob2xkID0gYmVzdC5wYXJhbXMkdGhyZXNob2xkLA0KICBzdGVwbWF4ID0gYmVzdC5wYXJhbXMkc3RlcG1heCwNCiAgcmVwID0gMSAgIyBNdWx0aXBsZSByZXBldGl0aW9ucyBmb3Igc3RhYmlsaXR5DQopDQpgYGANCg0KDQpCZWxvdyB3ZSB1c2UgdGhlIHRyYWluaW5nIG1vZGVsIHRvIG1ha2UgcHJlZGljdGlvbnMgb24gdGhlIHRlc3QgZGF0YS4gV2UgYWxzbyBzaG93IGhvdyB0aGUgcGVyY2VwdHJvbiBwZXJmb3JtcyBjb21wYXJlZCB0byBzdGFuZGFyZCBsaW5lYXIgcmVncmVzc2lvbiBhbmQgd2Ugc2VlIHRoYXQgdGhleSBhcmUgc2ltaWxhci4NCg0KDQpgYGB7cn0NCmZ1bGwucHJlZGljdGlvbnMgPC0gcHJlZGljdChmaW5hbC5tb2RlbC5ubiwgdGVzdC5kYXRhLm5vcm0pDQojIw0KcHJlZC51bnNjYWxlIDwtIHVuc2NhbGUoZnVsbC5wcmVkaWN0aW9ucyAsIGNvbXBsZXRlX2hhYml0c19kYXRhMiRleGFtX3Njb3JlKQ0KIyMjDQpNU0UubmV1cmFsbmV0IDwtIG1lYW4oKHByZWQudW5zY2FsZSAgLXRlc3Qub3JpZyRleGFtX3Njb3JlKV4yKQ0KIyMjDQpyLnNxLm5ldXJhbG5ldCA8LSAoY29yKHByZWQudW5zY2FsZSwgdGVzdC5vcmlnJGV4YW1fc2NvcmUpKV4yDQoNCg0KIyMjIyMjIyMjIyMjIyMjDQoNCiMgQ29udmVydCBwcmVkaWN0aW9ucyBiYWNrIHRvIG9yaWdpbmFsIHNjYWxlDQojcHJlZGljdGlvbnMub3JpZ2luYWwgPC0gcHJlZGljdGlvbnMgKiAobWF4KHRlc3Qub3JpZyRleGFtX3Njb3JlKSAtIG1pbih0ZXN0Lm9yaWckZXhhbV9zY29yZSkpICsgbWluKHRlc3Qub3JpZyRleGFtX3Njb3JlKQ0KDQphY3R1YWwub3JpZ2luYWwgPC0gdGVzdC5vcmlnJGV4YW1fc2NvcmUNCg0KIyBDYWxjdWxhdGUgUi1zcXVhcmVkDQpyLnNxdWFyZWQgPC0gKGNvcihwcmVkLnVuc2NhbGUsYWN0dWFsLm9yaWdpbmFsKSleMg0KDQoNCiNjYXQoIlItc3F1YXJlZDoiLCByLnNxdWFyZWQsICJcbiIpDQpQZXJjZXB0cm9uLk1TRSA8LSBtZWFuKChhY3R1YWwub3JpZ2luYWwgLSBwcmVkLnVuc2NhbGUpXjIpDQojY2F0KCJQZXJjZXB0cm9uIE1TRToiLCBQZXJjZXB0cm9uLk1TRSwgIlxuIikNCg0KIyBDb21wYXJlIHdpdGggbGluZWFyIHJlZ3Jlc3Npb24gZm9yIGJlbmNobWFya2luZw0KbG0ubW9kZWwgPC0gbG0oZXhhbV9zY29yZSB+IGFnZSArIHN0dWR5X2hvdXJzX3Blcl9kYXkgKyBzb2NpYWxfbWVkaWFfaG91cnMgKyBuZXRmbGl4X2hvdXJzICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbk5vICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb25ZZXMgKyBwYXJ0X3RpbWVfam9iTm8gKyBwYXJ0X3RpbWVfam9iWWVzICsgZ2VuZGVyRmVtYWxlICsgZ2VuZGVyTWFsZSwgZGF0YSA9IHRyYWluLm9yaWcpDQpsbS5wcmVkIDwtIHByZWRpY3QobG0ubW9kZWwsIHRlc3Qub3JpZykNCmxtLm1zZSA8LSBtZWFuKCh0ZXN0Lm9yaWckZXhhbV9zY29yZSAtIGxtLnByZWQpXjIpDQojY2F0KCJMaW5lYXIgUmVncmVzc2lvbiBNU0U6IiwgbG0ubXNlLCAiXG4iKQ0KbG0uci5zcSA8LSBzdW1tYXJ5KGxtLm1vZGVsKSRyLnNxdWFyZWQNCg0KIyMjIyMjIyMjIyMjIyMjIw0KUGVyY2VwdHJvbiA8LSBjKFBlcmNlcHRyb24uTVNFLCByLnNxdWFyZWQpDQpMTSA8LSBjKGxtLm1zZSwgbG0uci5zcSkNCm5ldXJhbG5ldCA8LSBjKE1TRS5uZXVyYWxuZXQsIHIuc3EubmV1cmFsbmV0KQ0KUGVyZm9ybWFjZS5tZXRyaWNzLmFsbCA8LSBkYXRhLmZyYW1lKFBlcmNlcHRyb249UGVyY2VwdHJvbiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBMTSA9IExNLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ldXJhbG5ldCA9IG5ldXJhbG5ldCApDQpyb3duYW1lcyhQZXJmb3JtYWNlLm1ldHJpY3MuYWxsKSA8LSBjKCJNU0UiLCAici5zcSIpDQpwYW5kZXIoUGVyZm9ybWFjZS5tZXRyaWNzLmFsbCkNCmBgYA0KDQojIFBlcmNlcHRyb24gQ2xhc3NpZmljYXRpb24NCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSBkbyBwZXJjZXB0cm9uIGZvciB0aGUgYmluYXJ5IHJlc3BvbnNlIHZhcmlhYmxlICdwYXNzJy4gV2UgY3JlYXRlIHRoZSBwYXNzIHZhcmlhYmxlIGJlbG93IHVzaW5nIHRoZSBhc3N1bXB0aW9uIHRoYXQgNjAgb3IgYWJvdmUgaXMgYSBwYXNzICgxLCAwIG90aGVyd2lzZSkuDQoNClRoZSBiZWxvdyBjb2RlIGdlbmVyYXRlcyB0aGUgb3B0aW1hbCBoeXBlcnBhcmFtZXRlcnMgZm9yIGxlYXJuaW5ncmF0ZSwgdGhyZXNob2xkLCBhbmQgYWNjdXJhY3kuDQoNCmBgYHtyfQ0KY29tcGxldGVfaGFiaXRzX2RhdGEyIDwtIHRyYW5zZm9ybShjb21wbGV0ZV9oYWJpdHNfZGF0YTIsIHBhc3M9aWZlbHNlKGV4YW1fc2NvcmUgPj0gNjAsIDEsIDApKQ0KDQoNCg0KaGFiaXRzLnNjYWxlZCA8LSBjYmluZChoYWJpdHMuc2NhbGVkLCBjb21wbGV0ZV9oYWJpdHNfZGF0YTIkcGFzcykNCg0Kc2V0LnNlZWQoMTIzKQ0KDQoNCg0KY29sbmFtZXMoaGFiaXRzLnNjYWxlZCkgPC0gYygnYWdlJywnc3R1ZHlfaG91cnNfcGVyX2RheScsICdzb2NpYWxfbWVkaWFfaG91cnMnLCANCiAgICAgICAgICAgICAgICAgICAgICAgJ25ldGZsaXhfaG91cnMnLCANCiAgICAgICAgICAgICAgICAgICAgICAgJ2F0dGVuZGFuY2VfcGVyY2VudGFnZScsICdzbGVlcF9ob3VycycgLCAnZGlldF9xdWFsaXR5JywNCiAgICAgICAgICAgICAgICAgICAgICAgJ2V4ZXJjaXNlX2ZyZXF1ZW5jeScsICdwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwnLCdpbnRlcm5ldF9xdWFsaXR5JywNCiAgICAgICAgICAgICAgICAgICAgICAgJ21lbnRhbF9oZWFsdGhfcmF0aW5nJywnZXhhbV9zY29yZScsJ2dlbmRlckZlbWFsZScsJ2dlbmRlck1hbGUnLCdwYXJ0X3RpbWVfam9iTm8nLCAncGFydF90aW1lX2pvYlllcycsJ2V4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uTm8nLCdleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvblllcycsJ3Bhc3MnKQ0KDQpzYW1wbGUuc2l6ZTIgPC0gZGltKGhhYml0cy5zY2FsZWQpWzFdDQp0cmFpbi5pbmRpY2VzMiA8LSBzYW1wbGUoMTpzYW1wbGUuc2l6ZTIsIHJvdW5kKDAuOCpzYW1wbGUuc2l6ZTIpKQ0KDQoNCnRyYWluLmRhdGEuY2xzIDwtIGhhYml0cy5zY2FsZWRbdHJhaW4uaW5kaWNlczIsIF0NCnRlc3QuZGF0YS5jbHMgPC0gaGFiaXRzLnNjYWxlZFstdHJhaW4uaW5kaWNlczIsIF0NCg0KdHJhaW4ub3JpZzIgPC0gY29tcGxldGVfaGFiaXRzX2RhdGEyW3RyYWluLmluZGljZXMyLCBdDQp0ZXN0Lm9yaWcyIDwtIGNvbXBsZXRlX2hhYml0c19kYXRhMlstdHJhaW4uaW5kaWNlczIsIF0NCg0KIyMjIyMjIyMjIyMjIw0KDQojIyBHcmlkIFNlYXJjaCBTZXR1cA0KIyBEZWZpbmUgdGhlIGh5cGVycGFyYW1ldGVyIGdyaWQNCmh5cGVyLmdyaWQuY2xzIDwtIGV4cGFuZC5ncmlkKA0KICBsZWFybmluZ3JhdGUgPSBjKDAuMDAxLCAwLjAxLCAwLjA1LCAwLjEsIDAuMiksDQogIHRocmVzaG9sZCA9IGMoMC4wMSwgMC4wNSkgICMgU3RvcHBpbmcgdGhyZXNob2xkIGZvciBwYXJ0aWFsIGRlcml2YXRpdmVzDQogKQ0KDQoNCmsgPC0gNQ0KZm9sZC5zaXplIDwtIGZsb29yKGRpbSh0cmFpbi5kYXRhLmNscylbMV0vaykNCiMgSW5pdGlhbGl6ZSByZXN1bHRzIHN0b3JhZ2UNCnJlc3VsdHMgPC0gZGF0YS5mcmFtZSgNCiAgbGVhcm5pbmdyYXRlID0gbnVtZXJpYygpLA0KICB0aHJlc2hvbGQgPSBudW1lcmljKCksDQogIGFjY3VyYWN5ID0gbnVtZXJpYygpLA0KICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCikNCg0KDQojIyBQZXJmb3JtIEdyaWQgU2VhcmNoIHdpdGggQ3Jvc3MtVmFsaWRhdGlvbg0KZm9yKGkgaW4gMTpucm93KGh5cGVyLmdyaWQuY2xzKSkgew0KICBsciA8LSBoeXBlci5ncmlkLmNscyRsZWFybmluZ3JhdGVbaV0NCiAgdGggPC0gaHlwZXIuZ3JpZC5jbHMkdGhyZXNob2xkW2ldDQogIA0KICAjY2F0KCJcblRlc3RpbmcgY29tYmluYXRpb24iLCBpLCAib2YiLCBucm93KGh5cGVyLmdyaWQuY2xzKSwgIjogbGVhcm5pbmdyYXRlID0iLCBsciwgIiwgdGhyZXNob2xkID0iLCB0aCwgIlxuIikNCiAgDQogICMgDQogIGZvbGQuYWNjdXJhY2llcyA8LSBudW1lcmljKGspDQogIA0KICAjIA0KICBmb3IoZm9sZCBpbiAxOmspIHsNCiAgICAjIFNwbGl0IGludG8gdHJhaW5pbmcgYW5kIHZhbGlkYXRpb24gc2V0cw0KICAgIHZhbGlkLmluZGljZXMgPC0gKDEgKyAoZm9sZC0xKSpmb2xkLnNpemUpOihmb2xkKmZvbGQuc2l6ZSkNCiAgICB0cmFpbi5mb2xkIDwtIHRyYWluLmRhdGEuY2xzWy12YWxpZC5pbmRpY2VzLCBdDQogICAgdmFsaWQuZm9sZCA8LSB0cmFpbi5kYXRhLmNsc1t2YWxpZC5pbmRpY2VzLCBdDQogICAgDQogICAgIyBUcmFpbiB0aGUgcGVyY2VwdHJvbg0KICAgIHNldC5zZWVkKDEyMykNCiAgICBtb2RlbC5zaWdtb2lkIDwtIG5ldXJhbG5ldCgNCiAgICAgIHBhc3MgfiBhZ2UgKyBzdHVkeV9ob3Vyc19wZXJfZGF5ICsgc29jaWFsX21lZGlhX2hvdXJzICsgbmV0ZmxpeF9ob3VycyArIGF0dGVuZGFuY2VfcGVyY2VudGFnZSArIHNsZWVwX2hvdXJzICsgZGlldF9xdWFsaXR5ICsgZXhlcmNpc2VfZnJlcXVlbmN5ICsgcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsICsgaW50ZXJuZXRfcXVhbGl0eSArIG1lbnRhbF9oZWFsdGhfcmF0aW5nICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb25ObyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uWWVzICsgcGFydF90aW1lX2pvYk5vICsgcGFydF90aW1lX2pvYlllcyArIGdlbmRlckZlbWFsZSArIGdlbmRlck1hbGUsDQogICAgICBkYXRhID0gdHJhaW4uZm9sZCwNCiAgICAgIGhpZGRlbiA9IDAsICAjIFBlcmNlcHRyb24gaGFzIG5vIGhpZGRlbiBsYXllcnMNCiAgICAgIGxpbmVhci5vdXRwdXQgPSBGQUxTRSwNCiAgICAgIGxlYXJuaW5ncmF0ZSA9IGxyLA0KICAgICAgYWN0LmZjdCA9ICJsb2dpc3RpYyIsDQogICAgICBhbGdvcml0aG0gPSAicnByb3ArIiwgIyBUaGUgcmVzaWxpZW50IGJhY2twcm9wYWdhdGlvbiB3aXRoIHdlaWdodCBiYWNrdHJhY2tpbmcNCiAgICAgIHRocmVzaG9sZCA9IHRoLA0KICAgICAgc3RlcG1heCA9IDFlNSAgIyBJbmNyZWFzZWQgdG8gZW5zdXJlIGNvbnZlcmdlbmNlDQogICAgKQ0KICAgIA0KICAgICMgTWFrZSBwcmVkaWN0aW9ucw0KICAgIHByZWRzIDwtIHByZWRpY3QobW9kZWwuc2lnbW9pZCwgdmFsaWQuZm9sZCkNCiAgICBwcmVkLmNsYXNzZXMgPC0gaWZlbHNlKHByZWRzID4gMC42LCAxLCAwKSAgIyBkZWZhdWx0IHRocmVzaG9sZCAwLjUNCiAgICANCiAgICAjIENhbGN1bGF0ZSBhY2N1cmFjeQ0KICAgIGZvbGQuYWNjdXJhY2llc1tmb2xkXSA8LSBtZWFuKHByZWQuY2xhc3NlcyA9PSB2YWxpZC5mb2xkJHBhc3MpDQogIH0NCiAgDQogICMgU3RvcmUgYXZlcmFnZSBhY2N1cmFjeSBmb3IgdGhpcyBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbg0KICByZXN1bHRzIDwtIHJiaW5kKHJlc3VsdHMsIGRhdGEuZnJhbWUoDQogICAgbGVhcm5pbmdyYXRlID0gbHIsDQogICAgdGhyZXNob2xkID0gdGgsDQogICAgYWNjdXJhY3kgPSBtZWFuKGZvbGQuYWNjdXJhY2llcykNCiAgKSkNCn0NCg0KIyMgQW5hbHl6ZSBSZXN1bHRzDQojIEZpbmQgdGhlIGJlc3QgY29tYmluYXRpb24NCmJlc3QuY29tYmluYXRpb24gPC0gcmVzdWx0c1t3aGljaC5tYXgocmVzdWx0cyRhY2N1cmFjeSksIF0NCiNjYXQoIlxuQmVzdCBoeXBlcnBhcmFtZXRlciBjb21iaW5hdGlvbjpcbiIpDQpwYW5kZXIoYmVzdC5jb21iaW5hdGlvbikNCg0KYGBgDQoNClRoZSBmaW5hbC5zaWdtb2lkLm1vZGVsIGJlbG93IGlzIHRoZSBmaW5hbCB0cmFpbmVkIG1vZGVsIHVzaW5nIHRoZSBvcHRpbWFsIGh5cGVycGFyYW1ldGVycyBmcm9tIGFib3ZlLg0KDQpgYGB7cn0NCiMjIFRyYWluIEZpbmFsIE1vZGVsIHdpdGggQmVzdCBIeXBlcnBhcmFtZXRlcnMNCmZpbmFsLnNpZ21vaWQubW9kZWwgPC0gbmV1cmFsbmV0KA0KICBwYXNzIH4gYWdlICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3VycyArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uTm8gKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvblllcyArIHBhcnRfdGltZV9qb2JObyArIHBhcnRfdGltZV9qb2JZZXMgKyBnZW5kZXJGZW1hbGUgKyBnZW5kZXJNYWxlLA0KICBkYXRhID0gdHJhaW4uZGF0YS5jbHMsDQogIGhpZGRlbiA9IDAsDQogIGxpbmVhci5vdXRwdXQgPSBGQUxTRSwNCiAgbGVhcm5pbmdyYXRlID0gYmVzdC5jb21iaW5hdGlvbiRsZWFybmluZ3JhdGUsDQogIHRocmVzaG9sZCA9IGJlc3QuY29tYmluYXRpb24kdGhyZXNob2xkLA0KICBhY3QuZmN0ID0gImxvZ2lzdGljIiwNCiAgYWxnb3JpdGhtID0gInJwcm9wKyIsICMgVGhlIHJlc2lsaWVudCBiYWNrcHJvcGFnYXRpb24gd2l0aCB3ZWlnaHQgYmFja3RyYWNraW5nDQogIHN0ZXBtYXggPSAxZTUNCikNCg0KI3Bsb3QoZmluYWwuc2lnbW9pZC5tb2RlbCkNCmBgYA0KDQpCZWxvdyB3ZSBtYWtlIHByZWRpY3Rpb25zIG9uIHRoZSB0ZXN0IHNldCBhbmQgY29tcGFyZSB0aGUgcGVyY2VwdHJvbiB0byBzdGFuZGFyZCBsb2dpc3RpYyByZWdyZXNzaW9uIGFuZCBmaW5kIHRoYXQgdGhleSBhcmUgc2ltaWxhciBpbiBwZXJmb3JtYW5jZS4NCg0KYGBge3J9DQojIyBFdmFsdWF0ZSBvbiBUZXN0IFNldA0KcHJlZC5zaWdtb2lkIDwtIHByZWRpY3QoZmluYWwuc2lnbW9pZC5tb2RlbCwgdGVzdC5kYXRhLmNscykNCg0KIyMjICBsb2dpc3RpYyByZWdyZXNzaW9uDQpsb2dpdC5maXQgPC0gZ2xtKHBhc3MgfiBhZ2UgKyBzdHVkeV9ob3Vyc19wZXJfZGF5ICsgc29jaWFsX21lZGlhX2hvdXJzICsgbmV0ZmxpeF9ob3VycyArIGF0dGVuZGFuY2VfcGVyY2VudGFnZSArIHNsZWVwX2hvdXJzICsgZGlldF9xdWFsaXR5ICsgZXhlcmNpc2VfZnJlcXVlbmN5ICsgcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsICsgaW50ZXJuZXRfcXVhbGl0eSArIG1lbnRhbF9oZWFsdGhfcmF0aW5nICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb25ObyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uWWVzICsgcGFydF90aW1lX2pvYk5vICsgcGFydF90aW1lX2pvYlllcyArIGdlbmRlckZlbWFsZSArIGdlbmRlck1hbGUsIGRhdGEgPSB0cmFpbi5kYXRhLmNscywgZmFtaWx5ID0gYmlub21pYWwpDQpBSUMubG9naXQgPC0gc3RlcChsb2dpdC5maXQsIGRpcmVjdGlvbiA9ICJib3RoIiwgdHJhY2UgPSAwKQ0KcHJlZC5sb2dpdCA8LSBwcmVkaWN0KEFJQy5sb2dpdCwgdGVzdC5kYXRhLmNscywgdHlwZSA9ICJyZXNwb25zZSIpDQpwcmVkLmZ1bGwgPC0gcHJlZGljdChsb2dpdC5maXQsIHRlc3QuZGF0YS5jbHMsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQoNCiMjIHJvYw0Kcm9jLmZ1bGwubG9naXQgPC0gcm9jKHRlc3QuZGF0YS5jbHNbLCBuY29sKHRlc3QuZGF0YS5jbHMpXSwgcHJlZC5mdWxsKQ0Kcm9jLkFJQy5sb2dpdCA8LSByb2ModGVzdC5kYXRhLmNsc1ssIG5jb2wodGVzdC5kYXRhLmNscyldLCBwcmVkLmxvZ2l0KQ0Kcm9jLnNpZ21vaWQgPC0gcm9jKHRlc3QuZGF0YS5jbHNbLCBuY29sKHRlc3QuZGF0YS5jbHMpXSwgcHJlZC5zaWdtb2lkICkNCiMjIEFVQw0KYXVjLnNpZ21vaWQgPC0gcm9jLnNpZ21vaWQkYXVjDQphdWMuZnVsbC5sb2dpdCA8LSByb2MuZnVsbC5sb2dpdCRhdWMNCmF1Yy5BSUMubG9naXQgPC0gcm9jLkFJQy5sb2dpdCRhdWMNCg0KIyMgc3BlLXNlbg0Kc2lnbW9pZC5zcGUgPC0gcm9jLnNpZ21vaWQkc3BlY2lmaWNpdGllcw0Kc2lnbW9pZC5zZW4gPC0gcm9jLnNpZ21vaWQkc2Vuc2l0aXZpdGllcw0KDQpmdWxsLmxvZ2l0LnNwZSA8LSByb2MuZnVsbC5sb2dpdCRzcGVjaWZpY2l0aWVzDQpmdWxsLmxvZ2l0LnNlbiA8LSByb2MuZnVsbC5sb2dpdCRzZW5zaXRpdml0aWVzDQoNCkFJQy5sb2dpdC5zcGUgPC0gcm9jLkFJQy5sb2dpdCRzcGVjaWZpY2l0aWVzDQpBSUMubG9naXQuc2VuIDwtIHJvYy5BSUMubG9naXQkc2Vuc2l0aXZpdGllcw0KDQoNCiMgUk9DIGN1cnZlDQpwbG90KDEtc2lnbW9pZC5zcGUsIHNpZ21vaWQuc2VuLCBjb2wgPSAiYmx1ZSIsIHR5cGUgPSAibCIsIGx0eSA9IDEsDQogICAgIHhsYWIgPSAiMSAtIHNwZWNpZmljaXR5IiwNCiAgICAgeWxhYiA9ICJzZW5zaXRpdml0eSIsDQogICAgIG1haW4gPSAiUk9DIEN1cnZlcyBvZiBQZXJjZXB0cm9uIGFuZCBMb2dpc3RpYyBNb2RlbHMiKQ0KbGluZXMoMS1mdWxsLmxvZ2l0LnNwZSwgZnVsbC5sb2dpdC5zZW4sIGx0eSA9IDEsIGNvbCA9ICJicm93biIpDQpsaW5lcygxLUFJQy5sb2dpdC5zcGUsIEFJQy5sb2dpdC5zZW4sIGx0eSA9IDEsIGNvbCA9ICJzdGVlbGJsdWUiKQ0KYWJsaW5lKDAsMSwgbHR5ID0yLCBjb2wgPSAicmVkIikNCnRleHQoMC45OCwgMC4zLCBwYXN0ZSgiUGVyY2VwdHJvbiBBVUMgPSAiLCByb3VuZChhdWMuc2lnbW9pZCw0KSksIGNvbCA9ICJibHVlIiwgY2V4ID0gMC44LCBwb3MgPSAyKQ0KdGV4dCgwLjk4LCAwLjI1LCBwYXN0ZSgiRnVsbCBMb2dpdCBBVUMgPSAiLCByb3VuZChhdWMuZnVsbC5sb2dpdCw0KSksIGNvbCA9ICJicm93biIsIGNleCA9IDAuOCwgcG9zID0gMikNCnRleHQoMC45OCwgMC4yLCBwYXN0ZSgiQUlDIEFVQyA9ICIsIHJvdW5kKGF1Yy5BSUMubG9naXQsNCkpLCBjb2wgPSAic3RlZWxibHVlIiwgY2V4ID0gMC44LCBwb3MgPSAyKQ0KYGBgDQoNCg0KIyBNTFAgUmVncmVzc2lvbg0KDQpJbiB0aGlzIHNlY3Rpb24sIHdlIGRvIG11bHRpbGF5ZXIgcGVyY2VwdHJvbi4gTUxQIHVzZXMgaGlkZGVuIGxheWVycywgd2hpY2ggaXMgaGVscGZ1bCBmb3Igbm9ubGluZWFyIGRhdGEuDQoNCiMjIE9uZS1oaWRkZW4tbGF5ZXIgUGVyY2VwdHJvbg0KDQpJbiB0aGlzIHNlY3Rpb24sIHdlIHVzZSBvbmUgaGlkZGVuIGxheWVyLiBCZWxvdyB3ZSBzcGxpdCB0aGUgZGF0YSBpbnRvIHRyYWluIGFuZCB0ZXN0Lg0KDQpgYGB7cn0NCmhhYml0cy5zY2FsZWQyIDwtIGFzLmRhdGEuZnJhbWUobGFwcGx5KGNvbXBsZXRlX2hhYml0c19kYXRhMiwgbm9ybWFsaXplKSkNCg0KIyBTZXQgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5DQpzZXQuc2VlZCgxMjMpDQpOIDwtIGxlbmd0aChoYWJpdHMuc2NhbGVkMiRleGFtX3Njb3JlKQ0KIyBDcmVhdGUgdHJhaW4tdGVzdCBzcGxpdCAoNzAtMzApDQp0cmFpbi5yZWcuaW5kZXggPC0gc2FtcGxlKDE6TiwgIGZsb29yKDAuOCpOKSwgcmVwbGFjZSA9IEZBTFNFKQ0KdHJhaW4ucmVnLmRhdGEgPC0gaGFiaXRzLnNjYWxlZDJbdHJhaW4ucmVnLmluZGV4LCBdDQp0ZXN0LnJlZy5kYXRhIDwtIGhhYml0cy5zY2FsZWQyWy10cmFpbi5yZWcuaW5kZXgsIF0NCg0KYGBgDQoNCkJlbG93IHdlIGdldCB0aGUgb3B0aW1hbCBoeXBlcnBhcmFtZXRlcnMgZm9yIGxheWVyMSwgbGVhcm5pbmcucmF0ZSwgYWN0aXZhdGlvbiwgYW5kIHJtc2UuDQoNCmBgYHtyfQ0KIyBEZWZpbmUgZ3JpZCBvZiBoeXBlcnBhcmFtZXRlcnMNCmh5cGVyLmdyaWQucmVnIDwtIGV4cGFuZC5ncmlkKA0KICBsYXllcjEgPSBjKDUsIDEwLCAxNSksDQogIGxlYXJuaW5nLnJhdGUgPSBjKDAuMDEsIDAuMSksDQogIGFjdGl2YXRpb24gPSBjKCJsb2dpc3RpYyIsICJ0YW5oIikNCikNCg0KIyBJbml0aWFsaXplIHJlc3VsdHMgc3RvcmFnZQ0Kcm1zZSA9IE5VTEwNCiNsYXllcjEgPSBOVUxMDQojbGVhcm5pbmdyYXRlID0gTlVMTA0KI2FjdGl2YXRpb24gPSBOVUxMDQoNCmJlc3QucmVnLnJtc2UgPC0gSW5mDQpiZXN0LnJlZy5tb2RlbCA8LSBOVUxMDQoNCiMgUGVyZm9ybSBncmlkIHNlYXJjaA0KZm9yKGkgaW4gMTpucm93KGh5cGVyLmdyaWQucmVnKSkgew0KICAjIEdldCBjdXJyZW50IGNvbmZpZ3VyYXRpb24NCiAgbGF5ZXIgPC0gaHlwZXIuZ3JpZC5yZWckbGF5ZXIxW2ldDQogIGxyIDwtIGh5cGVyLmdyaWQucmVnJGxlYXJuaW5nLnJhdGVbaV0NCiAgYWN0IDwtIGh5cGVyLmdyaWQucmVnJGFjdGl2YXRpb25baV0NCiAgDQogICMgVHJhaW4gbW9kZWwNCiAgc2V0LnNlZWQoMTIzKQ0KICBtb2RlbC5yZWcgPC0gbmV1cmFsbmV0KA0KICAgICAgZXhhbV9zY29yZSB+IGFnZSArIHN0dWR5X2hvdXJzX3Blcl9kYXkgKyBzb2NpYWxfbWVkaWFfaG91cnMgKyBuZXRmbGl4X2hvdXJzICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbk5vICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb25ZZXMgKyBwYXJ0X3RpbWVfam9iTm8gKyBwYXJ0X3RpbWVfam9iWWVzICsgZ2VuZGVyRmVtYWxlICsgZ2VuZGVyTWFsZSwNCiAgICAgIGRhdGEgPSB0cmFpbi5yZWcuZGF0YSwNCiAgICAgIGhpZGRlbiA9IGxheWVyLA0KICAgICAgYWN0LmZjdCA9IGFjdCwNCiAgICAgIGxpbmVhci5vdXRwdXQgPSBUUlVFLCAgIyBGb3IgcmVncmVzc2lvbg0KICAgICAgbGVhcm5pbmdyYXRlID0gbHIsDQogICAgICBhbGdvcml0aG0gPSAicnByb3ArIiwNCiAgICAgIHN0ZXBtYXggPSAxZTUgKQ0KICANCg0KICAgICMgTWFrZSBwcmVkaWN0aW9ucw0KICAgIHByZWRzLnJlZyA8LSBwcmVkaWN0KG1vZGVsLnJlZywgdGVzdC5yZWcuZGF0YVssIC1uY29sKHRlc3QucmVnLmRhdGEpXSkNCiAgICANCiAgICAjIENhbGN1bGF0ZSBSTVNFDQogICAgcm1zZS5yZWcgPC0gc3FydChtZWFuKChwcmVkcy5yZWcgLSB0ZXN0LnJlZy5kYXRhJGV4YW1fc2NvcmUpXjIpKQ0KICAgIA0KICAgICMgU3RvcmUgcmVzdWx0cw0KICAgIHJtc2VbaV0gPSBybXNlLnJlZw0KICAgIA0KICAgICMgVXBkYXRlIGJlc3QgbW9kZWwNCiAgICBpZihybXNlLnJlZyA8IGJlc3QucmVnLnJtc2UpIHsNCiAgICAgIGJlc3QucmVnLnJtc2UgPC0gcm1zZS5yZWcNCiAgICAgIGJlc3QucmVnLm1vZGVsIDwtIG1vZGVsLnJlZyANCiAgICAgIGJlc3QucmVnLnBhcmFtcyA8LSBoeXBlci5ncmlkLnJlZ1tpLCBdDQogICAgfQ0KfQ0KDQpyZXN1bHRzLnJlZ05OIDwtIGh5cGVyLmdyaWQucmVnDQpyZXN1bHRzLnJlZ05OJHJtc2UgPC0gcm1zZQ0KDQoNCiMgVmlldyByZXN1bHRzIHNvcnRlZCBieSBSTVNFDQpwYW5kZXIocmVzdWx0cy5yZWdOTltvcmRlcihyZXN1bHRzLnJlZ05OJHJtc2UpLCBdWzEsXSkNCmBgYA0KDQpVc2luZyB0aGUgb3B0aW1hbCBoeXBlcnBhcmFtZXRlcnMsIHdlIHRyYWluIHRoZSBmaW5hbCBtb2RlbCBiZWxvdy4NCg0KDQpgYGB7cn0NCmZpbmFsLnJlZy5tb2RlbCA8LSBuZXVyYWxuZXQoDQogIGV4YW1fc2NvcmUgfiBhZ2UgKyBzdHVkeV9ob3Vyc19wZXJfZGF5ICsgc29jaWFsX21lZGlhX2hvdXJzICsgbmV0ZmxpeF9ob3VycyArIGF0dGVuZGFuY2VfcGVyY2VudGFnZSArIHNsZWVwX2hvdXJzICsgZGlldF9xdWFsaXR5ICsgZXhlcmNpc2VfZnJlcXVlbmN5ICsgcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsICsgaW50ZXJuZXRfcXVhbGl0eSArIG1lbnRhbF9oZWFsdGhfcmF0aW5nICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb25ObyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uWWVzICsgcGFydF90aW1lX2pvYk5vICsgcGFydF90aW1lX2pvYlllcyArIGdlbmRlckZlbWFsZSArIGdlbmRlck1hbGUsDQogIGRhdGEgPSB0cmFpbi5yZWcuZGF0YSwNCiAgaGlkZGVuID0gYmVzdC5yZWcucGFyYW1zJGxheWVyMSwNCiAgYWN0LmZjdCA9IGJlc3QucmVnLnBhcmFtcyRhY3RpdmF0aW9uLA0KICBsaW5lYXIub3V0cHV0ID0gVFJVRSwNCiAgbGVhcm5pbmdyYXRlID0gYmVzdC5yZWcucGFyYW1zJGxlYXJuaW5nX3JhdGUsDQogIGFsZ29yaXRobSA9ICJycHJvcCsiLA0KICBzdGVwbWF4ID0gMWU1DQopDQoNCnBsb3QoZmluYWwucmVnLm1vZGVsLCByZXA9ImJlc3QiKQ0KYGBgDQoNCkJlbG93IHdlIHNob3cgYSBzY2F0dGVycGxvdCBvZiB0aGUgYWN0dWFsIHZzIHByZWRpY3RlZCB2YWx1ZXMuIEl0IGRvZXMgbm90IHBlcmZvcm0gdmVyeSB3ZWxsIGR1ZSB0byBzb21lIG91dGxpZXJzLg0KDQpgYGB7cn0NCiMgTWFrZSBwcmVkaWN0aW9ucyBvbiB0ZXN0IHNldA0KcHJlZC5OTjEgPC0gcHJlZGljdChmaW5hbC5yZWcubW9kZWwsIHRlc3QucmVnLmRhdGFbLCAtbmNvbCh0ZXN0LnJlZy5kYXRhKV0pDQoNCiMgQ2FsY3VsYXRlIHBlcmZvcm1hbmNlIG1ldHJpY3MNCnJtc2UuTk4xIDwtIHNxcnQobWVhbigocHJlZC5OTjEgIC0gdGVzdC5yZWcuZGF0YSRleGFtX3Njb3JlKV4yKSkNCm1hZS5OTjEgPC0gbWVhbihhYnMocHJlZC5OTjEgIC0gdGVzdC5yZWcuZGF0YSRleGFtX3Njb3JlKSkNCnIuc3F1YXJlZC5OTjEgPC0gY29yKHByZWQuTk4xICwgdGVzdC5yZWcuZGF0YSRleGFtX3Njb3JlKV4yDQoNCiMgY2F0KCJQZXJmb3JtYW5jZSBNZXRyaWNzOlxuIikNCiMgY2F0KCJSTVNFOiIsIHJtc2UsICJcbiIpDQojIGNhdCgiTUFFOiIsIG1hZSwgIlxuIikNCiMgY2F0KCJSLXNxdWFyZWQ6Iiwgcl9zcXVhcmVkLCAiXG4iKQ0KDQojIFBsb3QgcHJlZGljdGlvbnMgdnMgYWN0dWFsDQpwbG90Lk5OMS5kYXRhIDwtIGRhdGEuZnJhbWUoDQogIEFjdHVhbCA9IHRlc3QucmVnLmRhdGEkZXhhbV9zY29yZSwNCiAgUHJlZGljdGVkID0gcHJlZC5OTjEgDQopDQoNCmdncGxvdChwbG90Lk5OMS5kYXRhLCBhZXMoeCA9IEFjdHVhbCwgeSA9IFByZWRpY3RlZCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJkYXJrcmVkIikgKw0KICBhbm5vdGF0ZSgidGV4dCIsIHg9MC44NSwgeT0tMiwgDQogICAgICAgICAgIGxhYmVsPXBhc3RlKCJSLnNxID0iLCByb3VuZChyLnNxdWFyZWQuTk4xLDQpKSwgY29sb3I9ImJsdWUiKSArDQogIGFubm90YXRlKCJ0ZXh0IiwgeD0wLjg1LCB5PS0xLCANCiAgICAgICAgICAgbGFiZWw9cGFzdGUoIlJNU0UgPSIsIHJvdW5kKHJtc2UuTk4xLDQpKSwgY29sb3I9ImJsdWUiKSArDQogIGFubm90YXRlKCJ0ZXh0IiwgeD0wLjg1LCB5PS4wNiwgDQogICAgICAgICAgIGxhYmVsPXBhc3RlKCIgIE1BRSA9Iiwgcm91bmQobWFlLk5OMSw0KSksIGNvbG9yPSJibHVlIikgKw0KICBnZ3RpdGxlKCJBY3R1YWwgdnMgUHJlZGljdGVkIFZhbHVlcyIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KQmVsb3cgc2hvd3MgdGhhdCB0aGUgbmV1cmFsIG5ldHdvcmsgZG9lcyBub3QgcGVyZm9ybSBhcyB3ZWxsIGFzIHN0YW5kYXJkIGxpbmVhciByZWdyZXNzaW9uIHNvIDIgbGF5ZXJzIG1heSBoZWxwLg0KDQpgYGB7cn0NCiMgVHJhaW4gbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwNCmxtLm1vZGVsMiA8LSBsbShleGFtX3Njb3JlIH4gYWdlICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3VycyArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uTm8gKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvblllcyArIHBhcnRfdGltZV9qb2JObyArIHBhcnRfdGltZV9qb2JZZXMgKyBnZW5kZXJGZW1hbGUgKyBnZW5kZXJNYWxlLCBkYXRhID0gdGVzdC5yZWcuZGF0YSkNCg0KIyBNYWtlIHByZWRpY3Rpb25zDQpsbS5wcmVkaWN0aW9ucyA8LSBwcmVkaWN0KGxtLm1vZGVsMiwgdGVzdC5yZWcuZGF0YVssIC1uY29sKHRlc3QucmVnLmRhdGEpXSkNCg0KIyBDYWxjdWxhdGUgcGVyZm9ybWFuY2UgbWV0cmljcw0KbG0ucm1zZSA8LSBzcXJ0KG1lYW4oKGxtLnByZWRpY3Rpb25zIC0gdGVzdC5yZWcuZGF0YSRleGFtX3Njb3JlKV4yKSkNCmxtLm1hZSA8LSBtZWFuKGFicyhsbS5wcmVkaWN0aW9ucyAtIHRlc3QucmVnLmRhdGEkZXhhbV9zY29yZSkpDQpsbS5yLnNxdWFyZWQgPC0gY29yKGxtLnByZWRpY3Rpb25zLCB0ZXN0LnJlZy5kYXRhJGV4YW1fc2NvcmUpXjINCg0KDQojIyBpbXByb3ZlbWVudHMNClJNU0UuaW1wIDwtIHJvdW5kKChsbS5ybXNlIC0gcm1zZS5OTjEpL2xtLnJtc2UgKiAxMDAsMikNCk1BRS5pbXAgPC0gcm91bmQoKGxtLm1hZSAtIG1hZS5OTjEpL2xtLm1hZSAqIDEwMCwgMikNClJzcS5pbXAgPC0gcm91bmQoKHIuc3F1YXJlZC5OTjEgLSBsbS5yLnNxdWFyZWQpL2xtLnIuc3F1YXJlZCAqIDEwMCwyKQ0KDQojIw0KUGVyZm9ybWFuY2UudGFibGUgPC0gZGF0YS5mcmFtZSgNCiAgTE0gPSBjKGxtLnJtc2UsIGxtLm1hZSwgbG0uci5zcXVhcmVkKSwNCiAgTk4uMSA9IGMocm1zZS5OTjEsIG1hZS5OTjEsIHIuc3F1YXJlZC5OTjEpLA0KICBJbXByb3ZlbWVudC5wZXJjZW50YWdlID0gYyhSTVNFLmltcCwgTUFFLmltcCwgUnNxLmltcCkNCikNCnJvd25hbWVzKFBlcmZvcm1hbmNlLnRhYmxlKSA8LSBjKCJSTVNFIiwgIk1BRSIsICJSLnNxdWFyZSIpDQpwYW5kZXIoUGVyZm9ybWFuY2UudGFibGUpDQpgYGANCg0KQmVsb3cgc2hvd3MgdGhlIHBlcmZvcm1hbmNlIG9mIGJvdGggc3RhbmRhcmQgbGluZWFyIHJlZ3Jlc3Npb24gYW5kIE5OIHdpdGggb25lIGxheWVyLg0KDQpgYGB7cn0NCiMgUGxvdCBib3RoIHByZWRpY3Rpb25zDQpjb21wYXJpc29uLmRhdGEgPC0gZGF0YS5mcmFtZSgNCiAgQWN0dWFsID0gdGVzdC5yZWcuZGF0YSRleGFtX3Njb3JlLA0KICBNTFAgPSBwcmVkLk5OMSwNCiAgTGluZWFyID0gbG0ucHJlZGljdGlvbnMNCikNCg0KZ2dwbG90KGNvbXBhcmlzb24uZGF0YSkgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gQWN0dWFsLCB5ID0gTUxQLCBjb2xvciA9ICJNTFAiKSkgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gQWN0dWFsLCB5ID0gTGluZWFyLCBjb2xvciA9ICJMaW5lYXIgUmVncmVzc2lvbiIpKSArDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAiYmxhY2siKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJNTFAiID0gImJsdWUiLCAiTGluZWFyIFJlZ3Jlc3Npb24iID0gInJlZCIpKSArDQogIGxhYnModGl0bGUgPSAiTW9kZWwgQ29tcGFyaXNvbjogQWN0dWFsIHZzIFByZWRpY3RlZCIsDQogICAgICAgeCA9ICJBY3R1YWwgVmFsdWVzIiwNCiAgICAgICB5ID0gIlByZWRpY3RlZCBWYWx1ZXMiLA0KICAgICAgIGNvbG9yID0gIk1vZGVsIFR5cGUiKSArDQogIHRoZW1lKA0KICAgIHBsb3QubWFyZ2luID0gZ2dwbG90Mjo6bWFyZ2luKDQwLCAyMCwgMjAsIDIwLCB1bml0ID0gInB0IiksDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsaW5laGVpZ2h0ID0gMS4xLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmp1c3QgPSAxMCkNCiAgICApDQpgYGANCg0KDQojIyBUd28taGlkZGVuLWxheWVyIFBlcmNlcHRyb24NCg0KQmVsb3cgd2Ugc2VlIHBvc3NpYmxlIGh5cGVycGFyYW1ldGVycy4NCg0KYGBge3J9DQojIERlZmluZSBncmlkIG9mIGh5cGVycGFyYW1ldGVycw0KaHlwZXIuZ3JpZC5OTjIgPC0gZXhwYW5kLmdyaWQoDQogIGxheWVyMSA9IGMoNSwgMTAsIDE1KSwNCiAgbGF5ZXIyID0gYygwLCAzLCA1KSwgICMgMCBtZWFucyBubyBzZWNvbmQgbGF5ZXINCiAgbGVhcm5pbmcucmF0ZSA9IGMoMC4wMSwgMC4xKSwNCiAgYWN0aXZhdGlvbiA9IGMoImxvZ2lzdGljIiwgInRhbmgiKQ0KKQ0KDQojIEluaXRpYWxpemUgcmVzdWx0cyBpbiBzdG9yYWdlDQpyZXN1bHRzIDwtIGRhdGEuZnJhbWUoKQ0KYmVzdC5ybXNlIDwtIEluZg0KYmVzdC5tb2RlbCA8LSBOVUxMDQoNCiMgUGVyZm9ybSBncmlkIHNlYXJjaA0KZm9yKGkgaW4gMTpucm93KGh5cGVyLmdyaWQuTk4yKSkgew0KICAjIEdldCBjdXJyZW50IGNvbmZpZ3VyYXRpb24NCiAgbGF5ZXIxIDwtIGh5cGVyLmdyaWQuTk4yJGxheWVyMVtpXQ0KICBsYXllcjIgPC0gaHlwZXIuZ3JpZC5OTjIkbGF5ZXIyW2ldDQogIGxyIDwtIGh5cGVyLmdyaWQuTk4yJGxlYXJuaW5nLnJhdGVbaV0NCiAgYWN0IDwtIGh5cGVyLmdyaWQuTk4yJGFjdGl2YXRpb25baV0NCiAgDQogICMgQ3JlYXRlIGhpZGRlbiBsYXllcnMgdmVjdG9yDQogIGlmKGxheWVyMiA9PSAwKSB7DQogICAgaGlkZGVuLmxheWVycyA8LSBjKGxheWVyMSkNCiAgfSBlbHNlIHsNCiAgICBoaWRkZW4ubGF5ZXJzIDwtIGMobGF5ZXIxLCBsYXllcjIpDQogIH0NCiAgDQogICMgVHJhaW4gbW9kZWwNCiAgc2V0LnNlZWQoMTIzKQ0KICBtb2RlbC5OTjIgPC0gdHJ5Q2F0Y2goew0KICAgIG5ldXJhbG5ldCgNCiAgICAgIGV4YW1fc2NvcmUgfiBhZ2UgKyBzdHVkeV9ob3Vyc19wZXJfZGF5ICsgc29jaWFsX21lZGlhX2hvdXJzICsgbmV0ZmxpeF9ob3VycyArIGF0dGVuZGFuY2VfcGVyY2VudGFnZSArIHNsZWVwX2hvdXJzICsgZGlldF9xdWFsaXR5ICsgZXhlcmNpc2VfZnJlcXVlbmN5ICsgcGFyZW50YWxfZWR1Y2F0aW9uX2xldmVsICsgaW50ZXJuZXRfcXVhbGl0eSArIG1lbnRhbF9oZWFsdGhfcmF0aW5nICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb25ObyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uWWVzICsgcGFydF90aW1lX2pvYk5vICsgcGFydF90aW1lX2pvYlllcyArIGdlbmRlckZlbWFsZSArIGdlbmRlck1hbGUsDQogICAgICBkYXRhID0gdHJhaW4ucmVnLmRhdGEsDQogICAgICBoaWRkZW4gPSBoaWRkZW4ubGF5ZXJzLA0KICAgICAgYWN0LmZjdCA9IGFjdCwNCiAgICAgIGxpbmVhci5vdXRwdXQgPSBUUlVFLCAgIyBGb3IgcmVncmVzc2lvbg0KICAgICAgbGVhcm5pbmdyYXRlID0gbHIsDQogICAgICBhbGdvcml0aG0gPSAicnByb3ArIiwNCiAgICAgIHN0ZXBtYXggPSAxZTUNCiAgICApDQogIH0sIGVycm9yID0gZnVuY3Rpb24oZSkgTlVMTCkNCiAgDQogIGlmKCFpcy5udWxsKG1vZGVsLk5OMikpIHsNCiAgICAjIE1ha2UgcHJlZGljdGlvbnMNCiAgICBwcmVkcyA8LSBwcmVkaWN0KG1vZGVsLk5OMiwgdGVzdC5yZWcuZGF0YVssIC1uY29sKHRlc3QucmVnLmRhdGEpXSkNCiAgICANCiAgICAjIENhbGN1bGF0ZSBSTVNFDQogICAgcm1zZSA8LSBzcXJ0KG1lYW4oKHByZWRzIC0gdGVzdC5yZWcuZGF0YSRleGFtX3Njb3JlKV4yKSkNCiAgICANCiAgICAjIFN0b3JlIHJlc3VsdHMNCiAgICByZXN1bHRzIDwtIHJiaW5kKHJlc3VsdHMsIGRhdGEuZnJhbWUoDQogICAgICBsYXllcjEgPSBsYXllcjEsDQogICAgICBsYXllcjIgPSBsYXllcjIsDQogICAgICBsZWFybmluZ19yYXRlID0gbHIsDQogICAgICBhY3RpdmF0aW9uID0gYWN0LA0KICAgICAgcm1zZSA9IHJtc2UNCiAgICApKQ0KICAgIA0KICAgICMgVXBkYXRlIGJlc3QgbW9kZWwNCiAgICBpZihybXNlIDwgYmVzdC5ybXNlKSB7DQogICAgICBiZXN0LnJtc2UgPC0gcm1zZQ0KICAgICAgYmVzdC5tb2RlbCA8LSBtb2RlbC5OTjINCiAgICAgIGJlc3QucGFyYW1zIDwtIGh5cGVyLmdyaWQuTk4yW2ksIF0NCiAgICB9DQogIH0NCn0NCg0KIyBWaWV3IHJlc3VsdHMgc29ydGVkIGJ5IFJNU0UNCnJlc3VsdHNbb3JkZXIocmVzdWx0cyRybXNlKSwgXQ0KYGBgDQoNCkJlbG93IHdlIHRyYWluIHRoZSBmaW5hbCBtb2RlbC4NCg0KYGBge3J9DQpmaW5hbC5tb2RlbC5OTjIgPC0gbmV1cmFsbmV0KA0KICBleGFtX3Njb3JlIH4gYWdlICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3VycyArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uTm8gKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvblllcyArIHBhcnRfdGltZV9qb2JObyArIHBhcnRfdGltZV9qb2JZZXMgKyBnZW5kZXJGZW1hbGUgKyBnZW5kZXJNYWxlLA0KICBkYXRhID0gdHJhaW4ucmVnLmRhdGEsDQogIGhpZGRlbiA9IGlmKGJlc3QucGFyYW1zJGxheWVyMiA9PSAwKSB7DQogICAgICAgIGMoYmVzdC5wYXJhbXMkbGF5ZXIxKX0gZWxzZSB7DQogICAgICAgIGMoYmVzdC5wYXJhbXMkbGF5ZXIxLCBiZXN0LnBhcmFtcyRsYXllcjIpfSwNCiAgYWN0LmZjdCA9IGJlc3QucGFyYW1zJGFjdGl2YXRpb24sDQogIGxpbmVhci5vdXRwdXQgPSBUUlVFLA0KICBsZWFybmluZ3JhdGUgPSBiZXN0LnBhcmFtcyRsZWFybmluZy5yYXRlLA0KICBhbGdvcml0aG0gPSAicnByb3ArIiwNCiAgc3RlcG1heCA9IDFlNQ0KKQ0KDQpwbG90KGZpbmFsLm1vZGVsLk5OMiwgcmVwPSJiZXN0IikNCmBgYA0KDQpCZWxvdyBpcyB0aGUgYWN0dWFsIHZzIHByZWRpY3RlZCBzY2F0dGVycGxvdCBhbmQgaXQgc2VlbXMgdG8gcGVyZm9ybSBiZXR0ZXIuDQoNCmBgYHtyfQ0KIyBNYWtlIHByZWRpY3Rpb25zIG9uIHRlc3Qgc2V0DQpwcmVkLk5OMiA8LSBwcmVkaWN0KGZpbmFsLm1vZGVsLk5OMiwgdGVzdC5yZWcuZGF0YVssIC1uY29sKHRlc3QucmVnLmRhdGEpXSkNCg0KIyBDYWxjdWxhdGUgcGVyZm9ybWFuY2UgbWV0cmljcw0Kcm1zZS5OTjIgPC0gc3FydChtZWFuKChwcmVkLk5OMiAgLSB0ZXN0LnJlZy5kYXRhJGV4YW1fc2NvcmUpXjIpKQ0KbWFlLk5OMiA8LSBtZWFuKGFicyhwcmVkLk5OMiAgLSB0ZXN0LnJlZy5kYXRhJGV4YW1fc2NvcmUpKQ0Kci5zcXVhcmVkLk5OMiA8LSBjb3IocHJlZC5OTjIgLCB0ZXN0LnJlZy5kYXRhJGV4YW1fc2NvcmUpXjINCg0KIyMgdmVjdG9yIG9mIGVycm9yIG1ldHJpYw0KTk4yIDwtIGMocm1zZS5OTjIsIG1hZS5OTjIsIHIuc3F1YXJlZC5OTjIpDQoNCiMgUGxvdCBwcmVkaWN0aW9ucyB2cyBhY3R1YWwNCnBsb3QuZGF0YS5OTjIgPC0gZGF0YS5mcmFtZSgNCiAgQWN0dWFsID0gdGVzdC5yZWcuZGF0YSRleGFtX3Njb3JlLA0KICBQcmVkaWN0ZWQgPSBwcmVkLk5OMiANCikNCg0KZ2dwbG90KHBsb3QuZGF0YS5OTjIsIGFlcyh4ID0gQWN0dWFsLCB5ID0gUHJlZGljdGVkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIpICsNCiAgZ2d0aXRsZSgiQWN0dWFsIHZzIFByZWRpY3RlZCBWYWx1ZXMiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCkJlbG93IHNob3dzIHRoYXQgdGhlIE5OIHdpdGggdHdvIGxheWVycyBkb2VzIHNpZ25pZmljYW50bHkgYmV0dGVyIHRoYW4gdGhlIE5OIHdpdGggb25lLg0KDQpgYGB7cn0NCiMgVHJhaW4gbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwNCmxtLm1vZGVsMyA8LSBsbShleGFtX3Njb3JlIH4gYWdlICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3VycyArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uTm8gKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvblllcyArIHBhcnRfdGltZV9qb2JObyArIHBhcnRfdGltZV9qb2JZZXMgKyBnZW5kZXJGZW1hbGUgKyBnZW5kZXJNYWxlLCBkYXRhID0gdHJhaW4ucmVnLmRhdGEpDQoNCiMgTWFrZSBwcmVkaWN0aW9ucw0KbG0ucHJlZGljdGlvbnMyIDwtIHByZWRpY3QobG0ubW9kZWwzLCB0ZXN0LnJlZy5kYXRhWywgLW5jb2wodGVzdC5yZWcuZGF0YSldKQ0KDQojIENhbGN1bGF0ZSBwZXJmb3JtYW5jZSBtZXRyaWNzDQpsbS5ybXNlMiA8LSBzcXJ0KG1lYW4oKGxtLnByZWRpY3Rpb25zMiAtIHRlc3QucmVnLmRhdGEkZXhhbV9zY29yZSleMikpDQpsbS5tYWUyIDwtIG1lYW4oYWJzKGxtLnByZWRpY3Rpb25zMiAtIHRlc3QucmVnLmRhdGEkZXhhbV9zY29yZSkpDQpsbS5yLnNxdWFyZWQyIDwtIGNvcihsbS5wcmVkaWN0aW9uczIsIHRlc3QucmVnLmRhdGEkZXhhbV9zY29yZSleMg0KDQojIyMNCnJtc2UuTk4yLmltcCA8LSAobG0ucm1zZTIgLSBybXNlLk5OMikvbG0ucm1zZTIqMTAwDQptYWUuTk4yLmltcCA8LSAobG0ubWFlMiAtIG1hZS5OTjIpL2xtLm1hZTIgKiAxMDANClJzcS5OTjIuaW1wIDwtIChyLnNxdWFyZWQuTk4yIC0gbG0uci5zcXVhcmVkMikvbG0uci5zcXVhcmVkMiAqIDEwMA0KDQpOTjIuaW1wcm92ZSA8LWMocm1zZS5OTjIuaW1wLCBtYWUuTk4yLmltcCwgUnNxLk5OMi5pbXApDQoNCnBlcmYubWF0cml4IDwtZGF0YS5mcmFtZSgNCiAgICAgICAgICAgICAgTE0gPSBjKGxtLnJtc2UyLCBsbS5tYWUyLCBsbS5yLnNxdWFyZWQyKSwNCiAgICAgICAgICAgICAgTk4uMSA9IGMocm1zZS5OTjEsIG1hZS5OTjEsIHIuc3F1YXJlZC5OTjEpLA0KICAgICAgICAgICAgICBOTi4yID0gYyhybXNlLk5OMiwgbWFlLk5OMiwgci5zcXVhcmVkLk5OMikNCiAgICAgICAgICkNCg0KcGVyZi5tYXRyaXgkTk4xLkltcHJvdmUgPC0gcm91bmQoMTAwKihwZXJmLm1hdHJpeCRMTS1wZXJmLm1hdHJpeCROTi4xKS9wZXJmLm1hdHJpeCRMTSwyKQ0KcGVyZi5tYXRyaXgkTk4yLkltcHJvdmUgPC0gcm91bmQoMTAwKihwZXJmLm1hdHJpeCRMTS1wZXJmLm1hdHJpeCROTi4yKS9wZXJmLm1hdHJpeCRMTSwyKQ0KDQpyb3duYW1lcyhwZXJmLm1hdHJpeCkgPC0gYygiUk1TRSIsICJNQUUiLCAiUi5zcSIpDQpwYW5kZXIocGVyZi5tYXRyaXgpDQpgYGANCg0KQmVsb3cgc2hvd3MgdGhlIHBlcmZvcm1hbmNlIG9mIGJvdGggc3RhbmRhcmQgYW5kIE5OIHdpdGggMiBsYXllcnMuDQoNCmBgYHtyfQ0KIyBQbG90IGJvdGggcHJlZGljdGlvbnMNCmNvbXBhcmlzb24uZGF0YS5OTjIgPC0gZGF0YS5mcmFtZSgNCiAgQWN0dWFsID0gdGVzdC5yZWcuZGF0YSRleGFtX3Njb3JlLA0KICBNTFAgPSBwcmVkLk5OMiAsDQogIExpbmVhciA9IGxtLnByZWRpY3Rpb25zMg0KKQ0KDQpnZ3Bsb3QoY29tcGFyaXNvbi5kYXRhLk5OMikgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gQWN0dWFsLCB5ID0gTUxQLCBjb2xvciA9ICJNTFAiKSkgKw0KICBnZW9tX3BvaW50KGFlcyh4ID0gQWN0dWFsLCB5ID0gTGluZWFyLCBjb2xvciA9ICJMaW5lYXIgUmVncmVzc2lvbiIpKSArDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAiYmxhY2siKSArDQogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCJNTFAiID0gImJsdWUiLCAiTGluZWFyIFJlZ3Jlc3Npb24iID0gInJlZCIpKSArDQogIGxhYnModGl0bGUgPSAiTW9kZWwgQ29tcGFyaXNvbjogQWN0dWFsIHZzIFByZWRpY3RlZCIsDQogICAgICAgeCA9ICJBY3R1YWwgVmFsdWVzIiwNCiAgICAgICB5ID0gIlByZWRpY3RlZCBWYWx1ZXMiLA0KICAgICAgIGNvbG9yID0gIk1vZGVsIFR5cGUiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCg0KIyBNTFAgQ2xhc3NpZmljYXRpb24NCg0KQmVsb3cgd2UgcGVyZm9ybSB0aGUgZ3JpZCBzZWFyY2ggZm9yIHRoZSBzaW5nbGUgaGlkZGVuIGxheWVyLg0KDQpgYGB7cn0NCiNoYWJpdHMuc2NhbGVkIDwtIGNiaW5kKGhhYml0cy5zY2FsZWQsIGNvbXBsZXRlX2hhYml0c19kYXRhMiRwYXNzKQ0KDQpzZXQuc2VlZCgxMjMpICAjIEZvciByZXByb2R1Y2liaWxpdHkNCnNhbXBsZS5zaXplLmNscyA8LSBmbG9vcigwLjgwICogbnJvdyhoYWJpdHMuc2NhbGVkMikpDQp0cmFpbi5pbmRpY2VzLmNscyA8LSBzYW1wbGUoMTpzYW1wbGUuc2l6ZS5jbHMsIHNpemUgPSBzYW1wbGUuc2l6ZS5jbHMsIHJlcGxhY2UgPSBGQUxTRSkNCg0KdHJhaW4uZGF0YS5jbHMyIDwtIGhhYml0cy5zY2FsZWQyW3RyYWluLmluZGljZXMuY2xzLCBdDQp0ZXN0LmRhdGEuY2xzMiA8LSBoYWJpdHMuc2NhbGVkMlstdHJhaW4uaW5kaWNlcy5jbHMsIF0NCmBgYA0KDQpgYGB7cn0NCiMgRnVuY3Rpb24gdG8gcGVyZm9ybSBncmlkIHNlYXJjaCBmb3IgbmV1cmFsbmV0DQpuZXVyYWxuZXQuZ3JpZC5zZWFyY2ggPC0gZnVuY3Rpb24odHJhaW4uZGF0YSwgdGVzdC5kYXRhLCBoaWRkZW4ubGF5ZXJzID0gMSkgew0KICAjIERlZmluZSB0aGUgZ3JpZCBvZiBoeXBlcnBhcmFtZXRlcnMNCiAgaWYgKGhpZGRlbi5sYXllcnMgPT0gMSkgew0KICAgIGhpZGRlbi5ub2RlcyA8LSBjKDIsIDQsIDYsIDgsIDEwKQ0KICAgIGdyaWQgPC0gZXhwYW5kLmdyaWQoaGlkZGVuID0gaGlkZGVuLm5vZGVzKQ0KICB9IGVsc2Ugew0KICAgIGhpZGRlbi5ub2RlcyA8LSBjKDIsIDQsIDYpDQogICAgZ3JpZCA8LSBleHBhbmQuZ3JpZChoaWRkZW4xID0gaGlkZGVuLm5vZGVzLCBoaWRkZW4yID0gaGlkZGVuLm5vZGVzKQ0KICB9DQogIA0KICAjIEFkZCBjb2x1bW5zIHRvIHN0b3JlIHJlc3VsdHMNCiAgZ3JpZCRhY2N1cmFjeSA8LSBOQQ0KICBncmlkJGF1YyA8LSBOQQ0KICANCg0KICANCiAgIyBQZXJmb3JtIGdyaWQgc2VhcmNoDQogIGZvciAoaSBpbiAxOm5yb3coZ3JpZCkpIHsNCiAgICBpZiAoaGlkZGVuLmxheWVycyA9PSAxKSB7DQogICAgICBoaWRkZW4gPC0gYyhncmlkJGhpZGRlbltpXSkNCiAgICB9IGVsc2Ugew0KICAgICAgaGlkZGVuIDwtIGMoZ3JpZCRoaWRkZW4xW2ldLCBncmlkJGhpZGRlbjJbaV0pDQogICAgfQ0KICAgIA0KICAgICMgVHJhaW4gdGhlIG1vZGVsDQogICAgbm4ubW9kZWwgPC0gbmV1cmFsbmV0KA0KICAgICAgcGFzcyB+IGFnZSArIHN0dWR5X2hvdXJzX3Blcl9kYXkgKyBzb2NpYWxfbWVkaWFfaG91cnMgKyBuZXRmbGl4X2hvdXJzICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbk5vICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb25ZZXMgKyBwYXJ0X3RpbWVfam9iTm8gKyBwYXJ0X3RpbWVfam9iWWVzICsgZ2VuZGVyRmVtYWxlICsgZ2VuZGVyTWFsZSwNCiAgICAgIGRhdGEgPSB0cmFpbi5kYXRhLA0KICAgICAgaGlkZGVuID0gaGlkZGVuLA0KICAgICAgbGluZWFyLm91dHB1dCA9IEZBTFNFLCAgIyBGb3IgY2xhc3NpZmljYXRpb24NCiAgICAgIGFjdC5mY3QgPSAibG9naXN0aWMiLCAgICMgU2lnbW9pZCBhY3RpdmF0aW9uDQogICAgICBzdGVwbWF4ID0gMWU2ICAgICAgICAgICAjIEluY3JlYXNlIG1heCBzdGVwcyBmb3IgY29udmVyZ2VuY2UNCiAgICApDQogICAgDQogICAgIyBNYWtlIHByZWRpY3Rpb25zDQogICAgcHJlZGljdGlvbnMgPC0gcHJlZGljdChubi5tb2RlbCwgdGVzdC5kYXRhKQ0KICAgIHByZWRpY3RlZC5jbGFzc2VzIDwtIGlmZWxzZShwcmVkaWN0aW9ucyA+IDAuNiwgMSwgMCkNCiAgICANCiAgICAjIENhbGN1bGF0ZSBhY2N1cmFjeQ0KICAgIGFjY3VyYWN5IDwtIG1lYW4ocHJlZGljdGVkLmNsYXNzZXMgPT0gdGVzdC5kYXRhJHBhc3MpDQogICAgDQogICAgIyBDYWxjdWxhdGUgQVVDDQogICAgcm9jLm9iaiA8LSByb2ModGVzdC5kYXRhJHBhc3MsIHByZWRpY3Rpb25zKQ0KICAgIGF1Yy52YWx1ZSA8LSBhdWMocm9jLm9iaikNCiAgICANCiAgICAjIFN0b3JlIHJlc3VsdHMNCiAgICBncmlkJGFjY3VyYWN5W2ldIDwtIGFjY3VyYWN5DQogICAgZ3JpZCRhdWNbaV0gPC0gYXVjLnZhbHVlDQogIH0NCiAgcmV0dXJuKGdyaWQpDQp9DQpgYGANCg0KDQpgYGB7cn0NCiMgUGVyZm9ybSBncmlkIHNlYXJjaCBmb3Igc2luZ2xlIGhpZGRlbiBsYXllcg0KZ3JpZC5yZXN1bHRzLjFsYXllciA8LSBuZXVyYWxuZXQuZ3JpZC5zZWFyY2godHJhaW4uZGF0YT10cmFpbi5kYXRhLmNsczIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0LmRhdGE9dGVzdC5kYXRhLmNsczIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGlkZGVuLmxheWVycyA9IDEpDQpwYW5kZXIoZ3JpZC5yZXN1bHRzLjFsYXllcikNCmBgYA0KDQpBYm92ZSBpcyB0aGUgcmVzdWx0cyBmcm9tIGdyaWQgc2VhcmNoIGZvciBzaW5nbGUgaGlkZGVuIGxheWVyLiBCZWxvdyB3ZSBkbyB0aGUgZ3JpZCBzZWFyY2ggZm9yIHRoZSAyIGhpZGRlbiBsYXllcnMuDQoNCmBgYHtyfQ0KZ3JpZC5yZXN1bHRzLjJsYXllciA8LSBuZXVyYWxuZXQuZ3JpZC5zZWFyY2godHJhaW4uZGF0YT10cmFpbi5kYXRhLmNsczIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGVzdC5kYXRhPXRlc3QuZGF0YS5jbHMyLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhpZGRlbi5sYXllcnMgPSAyKQ0KcGFuZGVyKGdyaWQucmVzdWx0cy4ybGF5ZXIpDQpgYGANCg0KQmVsb3cgd2UgdHJhaW4gdGhlIDEtbGF5ZXIgbW9kZWwuDQoNCmBgYHtyfQ0KIyBUcmFpbiBzaW5nbGUgaGlkZGVuIGxheWVyIG1vZGVsICh1c2luZyBiZXN0IGNvbmZpZ3VyYXRpb24gZnJvbSBncmlkIHNlYXJjaCkNCmJlc3QuMWxheWVyIDwtIGdyaWQucmVzdWx0cy4xbGF5ZXJbd2hpY2gubWF4KGdyaWQucmVzdWx0cy4xbGF5ZXIkYXVjKSwgXQ0KDQpubi4xbGF5ZXIgPC0gbmV1cmFsbmV0KA0KICBwYXNzIH4gYWdlICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3Vycw0KICArIGRpZXRfcXVhbGl0eSArIGV4ZXJjaXNlX2ZyZXF1ZW5jeSArIHBhcmVudGFsX2VkdWNhdGlvbl9sZXZlbCArIGludGVybmV0X3F1YWxpdHkgKyBtZW50YWxfaGVhbHRoX3JhdGluZyArIGV4dHJhY3VycmljdWxhcl9wYXJ0aWNpcGF0aW9uTm8gKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvblllcyArIHBhcnRfdGltZV9qb2JObyArIHBhcnRfdGltZV9qb2JZZXMgKyBnZW5kZXJGZW1hbGUgKyBnZW5kZXJNYWxlLA0KICBkYXRhID0gdHJhaW4uZGF0YS5jbHMyLA0KICBoaWRkZW4gPSBiZXN0LjFsYXllciRoaWRkZW4sDQogIGxpbmVhci5vdXRwdXQgPSBGQUxTRSwNCiAgYWN0LmZjdCA9ICJsb2dpc3RpYyIsDQogIHN0ZXBtYXggPSAxZTYNCikNCiMjDQpwbG90KG5uLjFsYXllciwgcmVwPSJiZXN0IikNCmBgYA0KDQpCZWxvdyB3ZSB0cmFpbiB0aGUgMi1sYXllciBtb2RlbC4NCg0KYGBge3J9DQojIFRyYWluIHR3byBoaWRkZW4gbGF5ZXJzIG1vZGVsICh1c2luZyBiZXN0IGNvbmZpZ3VyYXRpb24gZnJvbSBncmlkIHNlYXJjaCkNCmJlc3QuMmxheWVyIDwtIGdyaWQucmVzdWx0cy4ybGF5ZXJbd2hpY2gubWF4KGdyaWQucmVzdWx0cy4ybGF5ZXIkYXVjKSwgXQ0KDQpubi4ybGF5ZXIgPC0gbmV1cmFsbmV0KA0KICBwYXNzIH4gYWdlICsgc3R1ZHlfaG91cnNfcGVyX2RheSArIHNvY2lhbF9tZWRpYV9ob3VycyArIG5ldGZsaXhfaG91cnMgKyBhdHRlbmRhbmNlX3BlcmNlbnRhZ2UgKyBzbGVlcF9ob3Vycw0KICAgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbk5vICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb25ZZXMgKyBwYXJ0X3RpbWVfam9iTm8gKyBwYXJ0X3RpbWVfam9iWWVzICsgZ2VuZGVyRmVtYWxlICsgZ2VuZGVyTWFsZSwNCiAgZGF0YSA9IHRyYWluLmRhdGEuY2xzMiwNCiAgaGlkZGVuID0gYyhiZXN0LjJsYXllciRoaWRkZW4xLCBiZXN0LjJsYXllciRoaWRkZW4yKSwNCiAgbGluZWFyLm91dHB1dCA9IEZBTFNFLA0KICBhY3QuZmN0ID0gImxvZ2lzdGljIiwNCiAgc3RlcG1heCA9IDFlNg0KKQ0KIyMNCnBsb3Qobm4uMmxheWVyLCByZXA9ImJlc3QiKQ0KYGBgDQoNCkJlbG93IHdlIG1ha2UgcHJlZGljdGlvbnMgb24gdGhlIHRlc3QgZGF0YS4NCg0KYGBge3J9DQojIEZ1bmN0aW9uIHRvIGV2YWx1YXRlIG1vZGVsIHBlcmZvcm1hbmNlDQpldmFsdWF0ZS5tb2RlbCA8LSBmdW5jdGlvbihtb2RlbCwgdGVzdC5kYXRhKSB7DQogICMgTWFrZSBwcmVkaWN0aW9ucw0KICBwcmVkaWN0aW9ucyA8LSBwcmVkaWN0KG1vZGVsLCB0ZXN0LmRhdGEpDQogIHByZWRpY3RlZC5jbGFzc2VzIDwtIGlmZWxzZShwcmVkaWN0aW9ucyA+IDAuNiwgMSwgMCkNCiAgDQogICMgQ2FsY3VsYXRlIG1ldHJpY3MNCiAgYWNjdXJhY3kgPC0gbWVhbihwcmVkaWN0ZWQuY2xhc3NlcyA9PSB0ZXN0LmRhdGEkcGFzcykNCiAgY29uZnVzaW9uLm1hdHJpeCA8LSB0YWJsZShQcmVkaWN0ZWQgPSBwcmVkaWN0ZWQuY2xhc3NlcywgQWN0dWFsID0gdGVzdC5kYXRhJHBhc3MpDQogIHJvYy5vYmogPC0gcm9jKHRlc3QuZGF0YSRwYXNzLCBwcmVkaWN0aW9ucykNCiAgYXVjLnZhbHVlIDwtIGF1Yyhyb2Mub2JqKQ0KICANCiAgcmV0dXJuKGxpc3QoDQogICAgYWNjdXJhY3kgPSBhY2N1cmFjeSwNCiAgICBjb25mdXNpb24ubWF0cml4ID0gY29uZnVzaW9uLm1hdHJpeCwNCiAgICByb2Mub2JqID0gcm9jLm9iaiwNCiAgICBhdWMgPSBhdWMudmFsdWUNCiAgKSkNCn0NCg0KIyBFdmFsdWF0ZSBzaW5nbGUgaGlkZGVuIGxheWVyIG1vZGVsDQpwZXJmLjFsYXllciA8LSBldmFsdWF0ZS5tb2RlbChubi4xbGF5ZXIsIHRlc3QuZGF0YS5jbHMyKQ0KI3ByaW50KHBlcmYuMWxheWVyW2MoImFjY3VyYWN5IiwgImNvbmZ1c2lvbl9tYXRyaXgiLCAiYXVjIildKQ0KDQojIEV2YWx1YXRlIHR3byBoaWRkZW4gbGF5ZXJzIG1vZGVsDQpwZXJmLjJsYXllciA8LSBldmFsdWF0ZS5tb2RlbChubi4ybGF5ZXIsIHRlc3QuZGF0YS5jbHMyKQ0KI3ByaW50KHBlcmYuMmxheWVyW2MoImFjY3VyYWN5IiwgImNvbmZ1c2lvbl9tYXRyaXgiLCAiYXVjIildKQ0KYGBgDQoNCkJlbG93IHdlIHNob3cgdGhlIHBlcmZvcm1hbmNlIG9mIE1MUCAxIGxheWVyLCBNTFAgMiBsYXllciwgYW5kIHN0YW5kYXJkIGxvZ2lzdGljLiBXZSBzZWUgdGhhdCB0aGV5IGFsbCBwZXJmb3JtIHNpbWlsYXJseS4NCg0KYGBge3J9DQojIFRyYWluIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgKGJhc2UgbW9kZWwpDQpsb2dpdC5tb2RlbCA8LSBnbG0ocGFzcyB+IGFnZSArIHN0dWR5X2hvdXJzX3Blcl9kYXkgKyBzb2NpYWxfbWVkaWFfaG91cnMgKyBuZXRmbGl4X2hvdXJzICsgYXR0ZW5kYW5jZV9wZXJjZW50YWdlICsgc2xlZXBfaG91cnMgKyBkaWV0X3F1YWxpdHkgKyBleGVyY2lzZV9mcmVxdWVuY3kgKyBwYXJlbnRhbF9lZHVjYXRpb25fbGV2ZWwgKyBpbnRlcm5ldF9xdWFsaXR5ICsgbWVudGFsX2hlYWx0aF9yYXRpbmcgKyBleHRyYWN1cnJpY3VsYXJfcGFydGljaXBhdGlvbk5vICsgZXh0cmFjdXJyaWN1bGFyX3BhcnRpY2lwYXRpb25ZZXMgKyBwYXJ0X3RpbWVfam9iTm8gKyBwYXJ0X3RpbWVfam9iWWVzICsgZ2VuZGVyRmVtYWxlICsgZ2VuZGVyTWFsZSwgZGF0YSA9IHRyYWluLmRhdGEuY2xzMiwgZmFtaWx5ID0gYmlub21pYWwpDQoNCiMgRXZhbHVhdGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbA0KbG9naXQucHJlZCA8LSBwcmVkaWN0KGxvZ2l0Lm1vZGVsLCB0ZXN0LmRhdGEuY2xzMiwgdHlwZSA9ICJyZXNwb25zZSIpDQpsb2dpdC5jbGFzc2VzIDwtIGlmZWxzZShsb2dpdC5wcmVkID4gMC42LCAxLCAwKQ0KbG9naXQuYWNjdXJhY3kgPC0gbWVhbihsb2dpdC5jbGFzc2VzID09IHRlc3QuZGF0YS5jbHMyJHBhc3MpDQpsb2dpdC5yb2MgPC0gcm9jKHRlc3QuZGF0YS5jbHMyJHBhc3MsIGxvZ2l0LnByZWQpDQpsb2dpdC5hdWMgPC0gYXVjKGxvZ2l0LnJvYykNCg0KIyMNCnJvYy4xbGF5ZXIgPC0gcGVyZi4xbGF5ZXIkcm9jLm9iag0Kcm9jLjJsYXllciA8LSBwZXJmLjJsYXllciRyb2Mub2JqDQpyb2MubG9naXQgPC0gbG9naXQucm9jDQoNCiMjIHNwZWNpZmljaXR5IGFuZCBzZW5zaXRpdml0eQ0Kc2VuLjFsYXllciA8LSByb2MuMWxheWVyJHNlbnNpdGl2aXRpZXMNCnNwZS4xbGF5ZXIgPC0gcm9jLjFsYXllciRzcGVjaWZpY2l0aWVzDQpzZW4uMmxheWVyIDwtIHJvYy4ybGF5ZXIkc2Vuc2l0aXZpdGllcw0Kc3BlLjJsYXllciA8LSByb2MuMmxheWVyJHNwZWNpZmljaXRpZXMNCnNlbi5sb2dpdCA8LSByb2MubG9naXQkc2Vuc2l0aXZpdGllcw0Kc3BlLmxvZ2l0IDwtIHJvYy5sb2dpdCRzcGVjaWZpY2l0aWVzDQoNCiMjIEFVQw0KYXVjLjFsYXllciA8LSByb2MuMWxheWVyJGF1Yw0KYXVjLjJsYXllciA8LSByb2MuMmxheWVyJGF1Yw0KYXVjLmxvZ2l0IDwtIHJvYy5sb2dpdCRhdWMNCg0KIyMgUGxvdCBST0MgY3VydmVzIGZvciBjb21wYXJpc29uDQpwYXIocHR5ID0gInMiKSAgICMgbWFrZSBhIHNxdWFyZSBwbG90IHRvIGF2YW9pZCBkaXN0b3J0aW9uDQpwbG90KDEtc3BlLjFsYXllciwgc2VuLjFsYXllciwgdHlwZSA9ICJsIiwgbHR5ID0gMSwNCiAgICAgY29sID0gImJsdWUiLCANCiAgICAgeGxhYiA9ICIxIC0gc3BlY2lmaWNpdHkiLA0KICAgICB5bGFiID0gInNlbnNpdHZpdHkiLA0KICAgICBtYWluID0gIlJPQyBDdXJ2ZSBDb21wYXJpc29uIikNCg0KbGluZXMoMS1zcGUuMmxheWVyLCBzZW4uMmxheWVyLCBsdHkgPSAxLCBjb2wgPSAiZGFya3JlZCIpDQpsaW5lcygxLXNwZS5sb2dpdCwgc2VuLmxvZ2l0LCBsdHkgPSAxLCBjb2wgPSAiZGFya2dyZWVuIikNCmxlZ2VuZCgiYm90dG9tcmlnaHQiLCANCiAgICAgICBsZWdlbmQgPSBjKHBhc3RlKCIxLWxheWVyIE1MUCAoQVVDID0iLCByb3VuZChwZXJmLjFsYXllciRhdWMsIDMpLCAiKSIpLA0KICAgICAgICAgICAgICAgICAgcGFzdGUoIjItbGF5ZXIgTUxQIChBVUMgPSIsIHJvdW5kKHBlcmYuMmxheWVyJGF1YywgMyksICIpIiksDQogICAgICAgICAgICAgICAgICBwYXN0ZSgiTG9naXN0aWMgUmVnIChBVUMgPSIsIHJvdW5kKGxvZ2l0LmF1YywgMyksICIpIikpLA0KICAgICAgICAgICAgICAgIGNvbCA9IGMoImJsdWUiLCAiZGFya3JlZCIsICJkYXJrZ3JlZW4iKSwgDQogICAgICAgICAgICAgICAgbHR5ID0gMSwgY2V4ID0gMC43LCBidHkgPSAibiIpDQpgYGANCg0KDQojIFJlc3VsdHMvQ29uY2x1c2lvbg0KDQpQZXJjZXB0cm9ucyBjYW4gaGVscCBnaXZlIHlvdSBhIGdlbmVyYWwgdW5kZXJzdGFuZGluZyBvZiBob3cgbmV1cmFsIG5ldHdvcmtzIGNhbiBwcmVkaWN0IGEgcmVzcG9uc2UgdmFyaWFibGUgYnV0IG1heSBiZSB0b28gc2ltcGxlLg0KDQpNTFAgY2FuIGJlIGJldHRlciBzaW5jZSB0aGV5IHdvcmsgd2VsbCB3aXRoIGNvbXBsZXgvbm9ubGluZWFyIGRhdGEuIEhvd2V2ZXIsIDIgbGF5ZXIgTUxQIG1heSBub3QgbmVjZXNzYXJpbHkgYmUgYmV0dGVyIHRoYW4gMSBsYXllci4gVHdvIGxheWVycyBtYXkgYmUgdG9vIGNvbXBsZXgsIHNvIDEgbGF5ZXIgTUxQIGlzIGJldHRlciBzaW5jZSBpdCBpcyBzaW1wbGVyIGFuZCBjYW4gcG90ZW50aWFsbHkgZ2VuZXJhdGUgc2ltaWxhciBhY2N1cmFjaWVzLg0KDQpGb3IgY2xhc3NpZmljYXRpb24sIE1MUCBtYXkgbm90IG5lY2Vzc2FyaWx5IGJlIGJldHRlciB0aGFuIHN0YW5kYXJkIGxvZ2lzdGljLiBFdmVuIHRob3VnaCBNTFAgY2FuIHdvcmsgd2VsbCB3aXRoIG5vbmxpbmVhciBkYXRhLCBpdCBtYXkgYmUgbW9yZSBwcm9uZSB0byBvdmVyZml0dGluZy4gU3RhbmRhcmQgbG9naXN0aWMgaGFzIGhpZ2ggaW50ZXJwcmV0YWJpbGl0eSwgY29tcHV0YXRpb25hbCBlZmZpY2llbmN5LCBhbmQgaXMgcm9idXN0IHRvIG92ZXJmaXR0aW5nLg0KDQpGb3IgcmVncmVzc2lvbiwgTUxQIGNhbiBhbHNvIGhlbHAgd2l0aCBub25saW5lYXIgZGF0YSBidXQgd2UgY2FuIHRha2Ugc2ltcGxlciBtb2RlbHMgYW5kIGNvbXB1dGF0aW9uYWwgcmVzb3VyY2VzIGludG8gYWNjb3VudC4NCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg==