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.
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 ...
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")

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)
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)
| 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)
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)
| MSE |
30.66 |
29.89 |
30.66 |
| r.sq |
0.9108 |
0.8985 |
0.9108 |
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)
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)

MLP Regression
In this section, we do multilayer perceptron. MLP uses hidden layers,
which is helpful for nonlinear data.
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,])
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)
| 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)
)

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

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)
| 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)
| 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")

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==