# Set the seed for reproducibility
set.seed(1)
# Set the locale of the session so languages other than English can be used
invisible(Sys.setlocale("LC_ALL", "en_US.UTF-8"))
# Prevent printing in scientific notation
options(digits = 4, width = 220)
# Create a logger function
logger <- function(msg, level = "info", file = log_file) {
cat(paste0("[", format(Sys.time(), "%Y-%m-%d %H:%M:%S.%OS"), "][", level, "] ", msg, "\n"), file = stdout())
}
# Set the project directory
base_dir <- ''
data_dir <- paste0(base_dir, "data/")
code_dir <- paste0(base_dir, "code/")
viz_dir <- paste0(base_dir, "viz/")
dir.create(data_dir, showWarnings = FALSE)
dir.create(code_dir, showWarnings = FALSE)
dir.create(viz_dir, showWarnings = FALSE)
# To the font second font, run the following two lines of code and add name of user to vector
# system(paste0("cp -r ",viz_dir,"fonts/. ~/Library/Fonts/")) # instantaneous
# font_import() # takes approximately 5-10 min
users_v <- c("Jordan")
# Create a color palette
pal538 <- ggthemes_data$fivethirtyeight
# Create a theme to use throughout the analysis
theme_jrf <- function(base_size = 8, base_family = ifelse(Sys.info()[['user']] %in% users_v, "DecimaMonoPro", "Helvetica")) {
theme(
plot.background = element_rect(fill = "#F0F0F0", colour = "#606063"),
panel.background = element_rect(fill = "#F0F0F0", colour = NA),
panel.border = element_blank(),
panel.grid.major = element_line(colour = "#D7D7D8"),
panel.grid.minor = element_line(colour = "#D7D7D8", size = 0.25),
panel.margin = unit(0.25, "lines"),
panel.margin.x = NULL,
panel.margin.y = NULL,
axis.ticks.x = element_blank(),
axis.ticks.y = element_blank(),
axis.title = element_text(colour = "#A0A0A3"),
axis.text.x = element_text(vjust = 1, colour = '#3C3C3C',
family = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica")),
axis.text.y = element_text(hjust = 1, colour = '#3C3C3C',
family = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica")),
legend.background = element_blank(),
legend.key = element_blank(),
plot.title = element_text(face = 'bold', colour = '#3C3C3C', hjust = 0),
text = element_text(size = 9, family = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica")),
title = element_text(family = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica"))
)
}
fn_plot_cv_glmnet <- function(cv_glmnet, main) {
data <-
tidy(cv_glmnet) %>% as_tibble() %>%
mutate(log_lambda = log(lambda))
data2 <-
data %>%
filter(row_number() %% 4 == 0)
data3 <-
data_frame(
log_lambda = c(log(cv_glmnet$lambda.min), log(cv_glmnet$lambda.1se))
, name = c("Min", "1se")
)
ggplot() +
geom_errorbar(data = data, aes(x = log_lambda, ymin = conf.low, ymax = conf.high),
colour = pal538['dkgray'][[1]], alpha = 0.6) +
geom_point(data = data, aes(x = log_lambda, y = estimate), colour = pal538['red'][[1]]) +
geom_vline(xintercept = log(cv_glmnet$lambda.min), colour = pal538['dkgray'][[1]], alpha = 0.6) +
geom_vline(xintercept = log(cv_glmnet$lambda.1se), colour = pal538['dkgray'][[1]], alpha = 0.6) +
theme_jrf() +
labs(title = main, x = expression(log(lambda)), y = cv_glmnet$name) +
geom_text(data = data2, aes(x = log_lambda, y = Inf, label = nzero), vjust = 1, colour = '#3C3C3C',
family = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica"),
size = 2.25) +
geom_label(data = data3, aes(x = log_lambda, y = Inf, label = name), vjust = 5, colour = '#3C3C3C',
family = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica"))
}
Problem 1
(1)
set.seed(1)
x.temp <- ceiling(runif(40, min=0, max=100))
data1<- matrix(x.temp,ncol=2, byrow=TRUE )
y <- round(rexp(nrow(data1), rate=2), 2)
data1 <- data.frame(data1, y)
names(data1) <- c("X1", "X2", "Y1")
data2 <- data1
set.seed(1)
data2$Y2 <- ifelse((data1$X1+data1$X2 > 70), rbinom(1,1,.62), rbinom(1,1, .31))
data2 <- data2 %>% as_tibble()
data2 <-
data2 %>%
mutate(region = ifelse(X1 >= 40 & X1 <= 75, "R1",
ifelse(X1 >= 75 & X2 <= 25, "R4",
ifelse(X1 >= 75 & X2 >= 25, "R3",
ifelse(X1 <= 40 & X2 >=75, "R2",
ifelse(X1 <= 20, "R6", "R5"))))))
plot(data2$X1, data2$X2, xlim = c(0,100), ylim = c(0,100), xlab = "X1", ylab = "X2")
title("data2")
lines(x = c(40,40), y = c(0,100))
lines(x = c(0,40), y = c(75,75))
lines(x = c(75,75), y = c(0,100))
lines(x = c(20,20), y = c(0,75))
lines(x = c(75,100), y = c(25,25))
text(x = (40+75)/2, y = 50, labels = c("R1\n0.4838"))
text(x = 20, y = (100+75)/2, labels = c("R2\n0.5000"))
text(x = (75+100)/2, y = (100+25)/2, labels = c("R3\n0.3825"))
text(x = (75+100)/2, y = 25/2, labels = c("R4\n0.0700"))
text(x = 30, y = 75/2, labels = c("R5\n0.4233"))
text(x = 10, y = 75/2, labels = c("R6\n0.1000"))
(2)
- This is a top-down, recursive tree
- If x1 = 60 and x2 = 30, then we predict Y1 to be 0.4838.
- If x1 = 60 and x2 = 30, then we predict Y1 to be 0.07.
(3)
fit1 <- tree(Y1 ~ X1 + X2, data = data2)
plot(fit1, main = "Hello")
text(fit1)
title("Regression Tree: Y1 ~ X1 + X2")
This tree is quite different in that it depends only on the variable X2. Empirically, we see that the predicted values do not closely match those in the diagram of data2 above.
(4)
plot(data2$X1, data2$X2, xlim = c(0,100), ylim = c(0,100), xlab = "X1", ylab = "X2", pch = as.character(data2$Y2))
title("data2")
lines(x = c(40,40), y = c(0,100))
lines(x = c(0,40), y = c(75,75))
lines(x = c(75,75), y = c(0,100))
lines(x = c(20,20), y = c(0,75))
lines(x = c(75,100), y = c(25,25))
text(x = (40+75)/2, y = 50, labels = c("R1"))
text(x = 20, y = (100+75)/2, labels = c("R2"))
text(x = (75+100)/2, y = (100+25)/2, labels = c("R3"))
text(x = (75+100)/2, y = 25/2, labels = c("R4"))
text(x = 30, y = 75/2, labels = c("R5"))
text(x = 10, y = 75/2, labels = c("R6"))
- The predicted Prob(Y2 = 1) for x1 = 60, x2 = 30 is 0.75.
- Y2’s label for x1 = 60, x2 = 30 by majority vote is 1.
(5)
fit2 <- rpart(factor(Y2) ~ X1 + X2, data = data2)
fancyRpartPlot(fit2, main = "Classification Tree for data2", sub = "")
Yes, this tree is different from our original tree because it only depends on X1 whereas the original tree depended on X1 and X2.
Problem 2
yelp <- read_csv(paste0(data_dir, "yelp_subset.csv"), progress = FALSE)
yelp %>%
select(votes.cool, votes.funny, votes.useful, stars, date, type) %>%
mutate(type = factor(type)) %>%
summary() %>%
pander(split.table = Inf)
Min. : 0.00 |
Min. : 0.00 |
Min. : 0.00 |
Min. :1.00 |
Min. :2004-10-12 |
review:100000 |
1st Qu.: 0.00 |
1st Qu.: 0.00 |
1st Qu.: 0.00 |
1st Qu.:3.00 |
1st Qu.:2009-05-27 |
NA |
Median : 0.00 |
Median : 0.00 |
Median : 0.00 |
Median :4.00 |
Median :2010-11-23 |
NA |
Mean : 0.59 |
Mean : 0.49 |
Mean : 0.92 |
Mean :3.65 |
Mean :2010-07-11 |
NA |
3rd Qu.: 1.00 |
3rd Qu.: 0.00 |
3rd Qu.: 1.00 |
3rd Qu.:5.00 |
3rd Qu.:2011-11-14 |
NA |
Max. :68.00 |
Max. :139.00 |
Max. :71.00 |
Max. :5.00 |
Max. :2012-10-15 |
NA |
set.seed(1)
n_words <- 20000
yelp_sample <- sample_n(yelp, n_words)
corp1 <- VCorpus(VectorSource(yelp_sample$text))
corp2 <- tm_map(corp1, stripWhitespace)
corp3 <- tm_map(corp2, removePunctuation)
corp4 <- tm_map(corp3, content_transformer(tolower))
corp5 <- tm_map(corp4, removeWords, stopwords("english"))
corp6 <- tm_map(corp5, stemDocument, lazy = TRUE)
dtm1 <- DocumentTermMatrix(corp6, control = list(bounds = list(global = c(n_words * 0.02,Inf))))
saveRDS(yelp_sample, paste0(data_dir, "yelp_sample.RDS"))
saveRDS(corp2, paste0(data_dir, "corp2.RDS"))
saveRDS(dtm1, paste0(data_dir, "dtm1.RDS"))
(1)
yelp_sample <- readRDS(paste0(data_dir, "yelp_sample.RDS"))
corp2 <- readRDS(paste0(data_dir, "corp2.RDS"))
dtm1 <- readRDS(paste0(data_dir, "dtm1.RDS"))
(i)
The Document-Term Matrix is a data structure in which rows are the documents (Yelp review) and each column is a unique word (term after processing). Each cell represents frequency the term from the column appears in the document (row). Below is row 100 and column 405:
dtm1[100, 405] %>% as.matrix() %>%
pander()
The value (0) represents the number of times the term seat appears in the document
It sort of looks like a burrito, except for the fact that they roll them by essentially mashing it into a ball. It smells kinda like a burrito, except that instead of big flavors you just smell unseasoned meat. Seriously, if you want a flavorless, expensive, Americanized, chain “Mexicanish” burrito, go to Chipotle. All others should go to La Burrita just one block away and get an actual real burrito.
(ii)
The sparsity of the Document-Term Matrix is 94% as shown by the output below. These means that of the 11,060,000 cells in the matrix, 10,393,625 or 94% are 0’s.
dtm1
<<DocumentTermMatrix (documents: 20000, terms: 553)>>
Non-/sparse entries: 666375/10393625
Sparsity : 94%
Maximal term length: 10
Weighting : term frequency (tf)
(2)
yelp_sample2 <-
yelp_sample %>%
mutate(rating = factor(ifelse(stars >= 4,1, 0))) %>%
select(rating) %>%
bind_cols(
dtm1 %>% as.matrix() %>% as_tibble()
)
yelp_sample2_train <- sample_n(yelp_sample2, 15000)
yelp_sample2_test <- dplyr::setdiff(yelp_sample2, yelp_sample2_train)
(3)
x_matrix <- model.matrix(rating ~ ., data = yelp_sample2_train)[, -1]
y <- yelp_sample2_train %>% select(rating) %>% unlist()
cv_glmnet <- cv.glmnet(x_matrix, y, family = 'binomial', alpha = 1, nfolds = 10)
saveRDS(cv_glmnet, paste0(data_dir, "cv_glmnet.RDS"))
cv_glmnet <- readRDS(paste0(data_dir, "cv_glmnet.RDS"))
fn_plot_cv_glmnet(cv_glmnet, "Lasso Model")
beta_lasso <-
coef(cv_glmnet, s = "lambda.1se") %>% tidy() %>%
dplyr::rename(term = row) %>%
filter(term != "(Intercept)")
(4)
lr_formula <- as.formula(paste0("rating ~ ", paste(beta_lasso$term, collapse = " + ")))
lr_fit <- glm(lr_formula, data = yelp_sample2_train, family = "binomial")
coefficients <-
tidy(lr_fit) %>%
as_tibble() %>%
filter(term != "(Intercept)")
top_two_coefficients <-
coefficients %>%
arrange(desc(estimate)) %>%
mutate(
odds = exp(estimate)
, prob = odds / (1 + odds)
, percentage = paste0(round(prob * 100, 2), "%")
) %>%
head(2)
top_two_coefficients %>%
select(term, estimate, odds, prob, std.error, statistic, p.value) %>%
pander()
awesom |
1.0499 |
2.857 |
0.7408 |
0.10180 |
10.31 |
6.121e-25 |
amaz |
0.9928 |
2.699 |
0.7296 |
0.09047 |
10.97 |
5.091e-28 |
(i)
All else remaining constant, the two coefficients are the change in log odds of a rating of 4 or 5 stars (vs 1,2,3) for the additional appearance of the term awesom or amaz.
In other words, all else remaining fixed,
- Adding one additional appearence of the term awesom to the Yelp review, increases the probability that the reviewer gave 4 or 5 stars by 74.08%.
- Adding one additional appearence of the term amaz to the Yelp review, increases the probability that the reviewer gave 4 or 5 stars by 72.96%.
(ii)
positive_words <-
coefficients %>%
arrange(desc(estimate)) %>%
head(100) %>%
mutate(freq = round(estimate * 100, 0)) %>%
select(word = term, freq)
wordcloud2(positive_words
, fontFamily = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica")
, color = pal538[['green']]
)
(iii)
negative_words <-
coefficients %>%
arrange(estimate) %>%
head(1000) %>%
mutate(freq = -1 * round(estimate * 100, 0)) %>%
select(word = term, freq)
wordcloud2(negative_words
, fontFamily = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica")
, color = pal538[['red']]
)
(iv)
The two word clouds (positive and negative bag of words) contains terms we would expect. The terms positively correlated with 4-5 star reviews include the stems awesom, amaz, excel and perfect. The terms negatively correlated with 4-5 star reviews include the stems terribl, bland, unfortun and overpr. Lastly, we build a word cloud in the shape of the Yelp logo that includes all terms appearing in at least 2% of the reviews by frequency of appearance.
word_freq <-
tidytext::tidy(dtm1) %>%
group_by(term) %>%
dplyr::summarise(freq = sum(count)) %>%
select(word = term, freq)
wordcloud2(word_freq, figPath = paste0(viz_dir, "yelp.png"), size = 1
, fontFamily = ifelse(Sys.info()[['user']] %in% users_v,"DecimaMonoPro", "Helvetica")
, color = "#D00B03"
)
knitr::include_graphics(paste0(viz_dir, "yelp_words.png"))
(5)
x_matrix_test <- model.matrix(rating ~ ., data = yelp_sample2_test)[, -1]
testing_errors <-
data_frame(
rating = yelp_sample2_test$rating
, predict_lasso = predict(cv_glmnet, x_matrix_test, s=c("lambda.1se"), type = 'response')[, 1]
, predict_lr = predict(lr_fit, yelp_sample2_test, type = 'response')
) %>%
mutate(
predict2_lasso = ifelse(predict_lasso > .5, 1, 0)
, predict2_lr = ifelse(predict_lr > .5, 1, 0)
) %>%
summarise(
`Error Lasso` = sum(predict2_lasso != rating) / n()
, `Error Log Reg` = sum(predict2_lr != rating) / n()
)
testing_errors %>%
pander()
We see that the testing error for the LASSO model is smaller than the testing error of the logistic regression model. This is expected as the LASSO model contains more predictors.
(6) RTextTools
(i) Logistic Regression
yelp_sample2_combined <- bind_rows(yelp_sample2_train, yelp_sample2_test)
rating <- yelp_sample2_combined %>% select(rating) %>% unlist()
yelp_n <- nrow(yelp_sample2_train)
container <- create_container(data2_combined %>% select(-rating),
labels = rating,
trainSize = 1:yelp_n,
testSize = (yelp_n+1):nrow(data2_combined)),
virgin=FALSE)
model_glmnet <- train_model(container, "GLMNET")
glmnet_out <- classify_model(container, model_glmnet) # prediction
glmnet_mce <- mean(rating[(yelp_n+1):nrow(data2_combined)] != glmnet_out[, 1])
saveRDS(glmnet_mce, paste0(data_dir, "glmnet_mce.RDS"))
glmnet_mce <- readRDS(paste0(data_dir, "glmnet_mce.RDS"))
The testing error for logistic regression using RTextTools is 0.2334 which is larger than in (5) (ii). These two logistic models do not have the same predictors. In (5) (ii) we limited our predictors to those with non-zero coefficients in the LASSO model than 1se from the lambda that minimized the MSE. We can assume that the logistic regression model produced by RTextTools overfit the training dataset and thus has a lower testing error.
(ii) Random Forest
model_RF <- train_model(container, "RF")
RF_out <- classify_model(container, model_RF)
RF_mce <- mean(rating[(yelp_n+1):nrow(data2_combined)] != RF_out[, 1])
saveRDS(RF_mce, paste0(data_dir, "RF_mce.RDS"))
RF_mce <- readRDS(paste0(data_dir, "RF_mce.RDS"))
The testing error for the RTextTools Random Forest is 0.2292.
(iii) SVM
model_SVM <- train_model(container, "SVM")
SVM_out <- classify_model(container, model_SVM)
SVM_mce <- mean(rating[(yelp_n+1):nrow(data2_combined)] != SVM_out[, 1])
saveRDS(SVM_mce, paste0(data_dir, "SVM_mce.RDS"))
SVM_mce <- readRDS(paste0(data_dir, "SVM_mce.RDS"))
The testing error for the RTextTools SVM is 0.2332.
(iv) Boosting
model_BOOSTING <- train_model(container, "BOOSTING")
BOOSTING_out <- classify_model(container, model_BOOSTING)
BOOSTING_mce <- mean(rating[(yelp_n+1):nrow(data2_combined)] != BOOSTING_out[, 1])
saveRDS(BOOSTING_mce, paste0(data_dir, "BOOSTING_mce.RDS"))
BOOSTING_mce <- readRDS(paste0(data_dir, "BOOSTING_mce.RDS"))
The testing error for the RTextTools Boosting is 0.4622.
We find that the random forest classifier has the least testing error. We are not surprised by this result as random forest classifiers are very good at not overfitting and incorporating a large number of predictors.
data_frame(
`Model` = c("Logistic Regression", "Random Forest", "SVM", "Boosting")
, `Testing MCE` = c(glmnet_mce, RF_mce, SVM_mce, BOOSTING_mce)
) %>%
arrange(`Testing MCE`) %>%
pander()
Random Forest |
0.2292 |
SVM |
0.2332 |
Logistic Regression |
0.2334 |
Boosting |
0.4622 |
(7)
If given a review, in order to predict its rating (4 and 5 vs 1, 2, or 3), we would need to perform the text clensing we did to our original Yelp dataset, including
- Removing whitespace
- Removing punctuation
- Converting all characters to lowercase
- Removing stopwords
- Stemming terms
Then, we would need to find the frequency of each of the 4 terms used our model and then feed this input into our final Random Forest model.
Problem 3
(1)
iq_data <- read_csv(paste0(data_dir, "IQ.full.csv"))
set.seed(10)
iq_sample <- sample_n(iq_data, 100)
iq_sample_tests <-
iq_sample %>%
select(Science, Arith, Word, Parag, Numer, Coding, Auto, Math, Mechanic, Elec)
test_names <- names(iq_sample_tests)
pca_tests <- prcomp(iq_sample_tests, center = TRUE, scale. = TRUE)
(a)
Below are the PC1 and PC2 loadings for test variables:
pca_tests$rotation[, c(1,2)] %>% pander()
Science |
-0.3413 |
0.172 |
Arith |
-0.3448 |
0.01656 |
Word |
-0.3427 |
-0.1387 |
Parag |
-0.3232 |
-0.2378 |
Numer |
-0.3087 |
-0.3854 |
Coding |
-0.247 |
-0.5103 |
Auto |
-0.2686 |
0.506 |
Math |
-0.3244 |
-0.1201 |
Mechanic |
-0.3107 |
0.3746 |
Elec |
-0.3354 |
0.2732 |
By definition, these loadings are unit vectors and we show that below:
pca_tests$rotation[, c(1,2)] %>%
as.data.frame() %>%
rownames_to_column("Variable") %>%
as_tibble() %>%
mutate(
`PC1^2` = PC1^2
, `PC2^2` = PC2^2
) %>%
bind_rows(
data_frame(
Variable = ""
, PC1 = NA
, PC2 = NA
, `PC1^2` = sum(.$`PC1^2`)
, `PC2^2` = sum(.$`PC2^2`)
)
) %>%
select(Variable, PC1, `PC1^2`, PC2, `PC2^2`) %>%
pander(missing = "")
Science |
-0.3413 |
0.11651 |
0.17201 |
0.0295883 |
Arith |
-0.3448 |
0.11888 |
0.01656 |
0.0002741 |
Word |
-0.3427 |
0.11746 |
-0.13873 |
0.0192459 |
Parag |
-0.3232 |
0.10443 |
-0.23780 |
0.0565477 |
Numer |
-0.3087 |
0.09527 |
-0.38536 |
0.1485054 |
Coding |
-0.2470 |
0.06101 |
-0.51029 |
0.2603992 |
Auto |
-0.2686 |
0.07214 |
0.50603 |
0.2560662 |
Math |
-0.3244 |
0.10524 |
-0.12015 |
0.0144354 |
Mechanic |
-0.3107 |
0.09654 |
0.37457 |
0.1403003 |
Elec |
-0.3354 |
0.11252 |
0.27320 |
0.0746375 |
|
|
1.00000 |
|
1.0000000 |
The correlation of PC1 and PC2 is -0.1592 which is nearly uncorrelated.
(b)
The PCA1 score for each subject is obtained from the product of the PCA1 loadings (\(\phi^1\)’s) and the variable values from each subject. In our dataset, it looks like pc \[PC1 = \phi^1_{Science}Science +\phi^1_{Arith}Arith + \dotsm + \phi^1_{Auto}Auto\]
(c)
By definition, the principle compoent scores are uncorrelated. In practice we see that the correlation between PC1 and PC2 scores is 1.998410^{-16}.
(d)
Below we plot the percent variance explained. We see that first principal componenet explains nearly 2/3 of all variance among the observations.
data_frame(
variance_explained = (pca_tests$sdev)^2 / sum ((pca_tests$sdev)^2)
) %>%
mutate(pc = 1:10) %>%
ggplot(aes(x = pc, y = variance_explained)) +
geom_bar(stat = 'identity', fill = pal538[['blue']]) +
labs(title = "Percent Variance Explained", y = "Variance Explained", x = "Principal Component") +
scale_y_continuous(labels = scales::percent) +
theme_jrf()
(e)
We see that nearly 77% of variance in the data is explained by the first two principal components.
data_frame(
variance_explained = (pca_tests$sdev)^2 / sum ((pca_tests$sdev)^2)
) %>%
mutate(
cumsum = cumsum(variance_explained)
, pc = 1:10
) %>%
ggplot(aes(x = pc, y = cumsum)) +
geom_line(colour = pal538[['blue']]) +
geom_point(colour = pal538[['blue']]) +
labs(title = "Percent Variance Explained", y = "Variance Explained", x = "Principal Component") +
scale_y_continuous(limits = c(0, 1), labels = scales::percent) +
theme_jrf()
(f)
In the biplot below, we see that the length of each vector is nearly the same and that the vectors all points in the same direction on the PC1 axis. The differences are seen on the PC2 axis. In one direction, there is the auto, mechanic, electric, and science test and in the other direction is the numerical and coding, numerical, paragraph, and writing test (with arithmetic in neither).
ggbiplot(pca_tests, obs.scale = 1, var.scale = 1) +
theme_jrf() +
labs(title = "Biplot of PC1 and PC2")
(g)
We then color the biplot by gender. We see a strong systematic separation between male and female in the biplot with more female observations on the negative direction of PC2 and more male observations on the positive direction of PC2. We can interpret this as males performing better on auto, mechanic, electric, and science test than females in 1981, who performed better on coding, numerical, paragraph, and writing tests.
ggbiplot(pca_tests, groups = factor(iq_sample$Gender), obs.scale = 1, var.scale = 1) +
theme_jrf() +
labs(title = "Biplot of PC1 and PC2 by Gender") +
scale_colour_manual("Gender", values = c("male" = pal538[['blue']], "female" = pal538[['red']]))
(2)
(a)
iq_sample_esteem <-
iq_sample %>%
select(starts_with("Esteem")) %>%
mutate(
Esteem1 = 5 - Esteem1
, Esteem2 = 5 - Esteem2
, Esteem4 = 5 - Esteem4
, Esteem6 = 5 - Esteem6
, Esteem7 = 5 - Esteem7
)
test_esteem <- names(iq_sample_esteem)
pca_esteem <- prcomp(iq_sample_esteem, center = TRUE, scale. = TRUE)
(b)
Below are the PC1 loadings
pca_esteem$rotation[, c(1,2)] %>%
as.data.frame() %>%
rownames_to_column("Variable") %>%
as_tibble() %>%
select(Variable, PC1) %>%
pander()
Esteem1 |
-0.3549 |
Esteem2 |
-0.3425 |
Esteem3 |
-0.3469 |
Esteem4 |
-0.3409 |
Esteem5 |
-0.2713 |
Esteem6 |
-0.3349 |
Esteem7 |
-0.2716 |
Esteem8 |
-0.3022 |
Esteem9 |
-0.2449 |
Esteem10 |
-0.3307 |
(c)
We see that 47.76% of variance is explained by PC1.
data_frame(
variance_explained = (pca_esteem$sdev)^2 / sum ((pca_esteem$sdev)^2)
) %>%
mutate(pc = 1:10) %>%
ggplot(aes(x = pc, y = variance_explained)) +
geom_bar(stat = 'identity', fill = pal538[['blue']]) +
labs(title = "Percent Variance Explained", y = "Variance Explained", x = "Principal Component") +
scale_y_continuous(labels = scales::percent) +
theme_jrf()
data_frame(
variance_explained = (pca_esteem$sdev)^2 / sum ((pca_esteem$sdev)^2)
) %>%
mutate(
cumsum = cumsum(variance_explained)
, pc = 1:10
) %>%
ggplot(aes(x = pc, y = cumsum)) +
geom_line(colour = pal538[['blue']]) +
geom_point(colour = pal538[['blue']]) +
labs(title = "Percent Variance Explained", y = "Variance Explained", x = "Principal Component") +
scale_y_continuous(limits = c(0, 1), labels = scales::percent) +
theme_jrf()
(d)
From the charts in (c) we recognize that the first PC explains nearly 50% of the variance in the data. From the biplot below we see that all esteem questions point in the same direction on the PC1 axis. There are differences in the length of the vectors, with the question “I am satisfied with myself being the shortest”. We also see that questions 1, 2, and 4 are bunched together: these are questions about positive features of oneself. We see that question 9 pointing in the opposite direction and deals with “feeling useless”. The other vectors that point in this direction on the PC2 axis all deal with negative feature or components of oneself.
ggbiplot(pca_esteem, obs.scale = 1, var.scale=1) +
theme_jrf() +
labs(title = "Biplot of PC1 and PC2")
(3)
(a)
It important to create a logarithmic transformation of income because the variable is strongly positively skewed.
iq_sample %>%
ggplot(aes(x = Income2005)) +
geom_histogram(bins = 50, fill = pal538[['blue']]) +
labs(title = "Histogram of Income (2005)", x = "Income (2005)", y = "Observations") +
scale_x_continuous(labels = scales::dollar) +
theme_jrf()
(b)
We resuse the results from (1): pca_tests
.
(c)
iq_sample_income <-
iq_sample %>%
bind_cols(
pca_tests$x[, c(1,2,3)] %>%
as.data.frame() %>%
as_tibble()
) %>%
mutate(income = log(Income2005))
fit1 <- lm(income ~ PC1, data = iq_sample_income)
fit2 <- lm(income ~ PC1+PC2+PC3, data = iq_sample_income)
tidy(fit1) %>% pander(caption = "income ~ PC1")
income ~ PC1
(Intercept) |
10.31 |
0.1082 |
95.31 |
1.957e-98 |
PC1 |
-0.2137 |
0.04311 |
-4.958 |
2.993e-06 |
tidy(fit2) %>% pander(caption = "income ~ PC1 + PC2 + PC3")
income ~ PC1 + PC2 + PC3
(Intercept) |
10.31 |
0.1049 |
98.31 |
3.689e-98 |
PC1 |
-0.2137 |
0.0418 |
-5.113 |
1.616e-06 |
PC2 |
0.2521 |
0.09146 |
2.756 |
0.007006 |
PC3 |
0.1133 |
0.1402 |
0.8082 |
0.421 |
We see that the LS estimates of PC1 in both fit1 and fit2 are identical. This is due to the fact that the PC’s are uncorrelated variables. Below is the correlation matrix of the 3 PC’s:
cor(pca_tests$x[, c(1,2,3)]) %>% pander()
PC1 |
1 |
1.998e-16 |
6.914e-16 |
PC2 |
1.998e-16 |
1 |
2.76e-16 |
PC3 |
6.914e-16 |
2.76e-16 |
1 |
We see from the table above that PC1 and PC2 are significant in predicting income, but PC3 is not. Additionally, based on the percent variance explained charts in (1), we see an elbow after the second PC and would thus prefer a model with only two PCs.
(d)
Controlling for personal demographic variables and household environment, we find that the leading two PC’s of ASVAB are significant variables to predict income at .01 level.
fit3 <- lm(income ~ PC1+PC2, data = iq_sample_income)
tidy(fit3) %>% pander(caption = "income ~ PC1 + PC2")
income ~ PC1 + PC2
(Intercept) |
10.31 |
0.1047 |
98.48 |
5.041e-99 |
PC1 |
-0.2137 |
0.04172 |
-5.122 |
1.533e-06 |
PC2 |
0.2521 |
0.0913 |
2.761 |
0.006896 |
Our model to predict income is:
\[Income = 10.3147 +
-0.2137PC1 +
0.2521PC2\]
The two principal components are from the ASVAB (Armed Services Vocational Aptitude Battery) test scores, which includes tests in
- Science,
- Arithmetic reasoning
- Word knowledge
- Paragraph comprehension
- Numerical operation
- Coding speed
- Automative and Shop information
- Math knowledge
- Mechanic Comprehension
- Electronic information
LS0tCnRpdGxlOiAiU1RBVFM3MDEgSG9tZXdvcmsgNCIKYXV0aG9yOiAiSm9yZGFuIEZhcnJlciIKZGF0ZTogJzIwMTYtMTItMDQnCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBoaWRlCiAgICBjc3M6IHN0eWxlLmNzcwogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiBmbGF0bHkKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwotLS0KCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyBTZXQgb3B0aW9ucyBmb3IgdGhlIHJtYXJrZG93biBmaWxlCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIGZpZy5hbGlnbiA9ICdjZW50ZXInLCB3aWR0aCA9IDEwMCkKYGBgCgpgYGB7ciBzZXR1cDJ9CiMgU2V0IHRoZSBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoMSkKIyBTZXQgdGhlIGxvY2FsZSBvZiB0aGUgc2Vzc2lvbiBzbyBsYW5ndWFnZXMgb3RoZXIgdGhhbiBFbmdsaXNoIGNhbiBiZSB1c2VkCmludmlzaWJsZShTeXMuc2V0bG9jYWxlKCJMQ19BTEwiLCAiZW5fVVMuVVRGLTgiKSkKIyBQcmV2ZW50IHByaW50aW5nIGluIHNjaWVudGlmaWMgbm90YXRpb24Kb3B0aW9ucyhkaWdpdHMgPSA0LCB3aWR0aCA9IDIyMCkKCiMgQ3JlYXRlIGEgbG9nZ2VyIGZ1bmN0aW9uCmxvZ2dlciA8LSBmdW5jdGlvbihtc2csIGxldmVsID0gImluZm8iLCBmaWxlID0gbG9nX2ZpbGUpIHsKICAgIGNhdChwYXN0ZTAoIlsiLCBmb3JtYXQoU3lzLnRpbWUoKSwgIiVZLSVtLSVkICVIOiVNOiVTLiVPUyIpLCAiXVsiLCBsZXZlbCwgIl0gIiwgbXNnLCAiXG4iKSwgZmlsZSA9IHN0ZG91dCgpKQp9CgojIFNldCB0aGUgcHJvamVjdCBkaXJlY3RvcnkKYmFzZV9kaXIgPC0gJycKZGF0YV9kaXIgPC0gcGFzdGUwKGJhc2VfZGlyLCAiZGF0YS8iKQpjb2RlX2RpciA8LSBwYXN0ZTAoYmFzZV9kaXIsICJjb2RlLyIpCnZpel9kaXIgPC0gcGFzdGUwKGJhc2VfZGlyLCAidml6LyIpCgpkaXIuY3JlYXRlKGRhdGFfZGlyLCBzaG93V2FybmluZ3MgPSBGQUxTRSkKZGlyLmNyZWF0ZShjb2RlX2Rpciwgc2hvd1dhcm5pbmdzID0gRkFMU0UpCmRpci5jcmVhdGUodml6X2Rpciwgc2hvd1dhcm5pbmdzID0gRkFMU0UpCmBgYAoKYGBge3IgTG9hZCBQYWNrYWdlcywgaW5jbHVkZSA9IEZBTFNFfQojIENyZWF0ZSBhIGZ1bmN0aW9uIHRoYXQgd2lsbCBiZSB1c2VkIHRvIGxvYWQvaW5zdGFsbCBwYWNrYWdlcwpmbl9sb2FkX3BhY2thZ2VzIDwtIGZ1bmN0aW9uKHApIHsKICBpZiAoIWlzLmVsZW1lbnQocCwgaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdKSB8fCAocCA9PSJEVCIgJiYgIShwYWNrYWdlVmVyc2lvbihwKSA+ICIwLjEiKSkpIHsKICAgIGlmIChwID09ICJEVCIpIHsKICAgICAgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCdyc3R1ZGlvL0RUJykKICAgIH0gZWxzZSB7CiAgICAgIGluc3RhbGwucGFja2FnZXMocCwgZGVwID0gVFJVRSwgcmVwb3MgPSAnaHR0cDovL2NyYW4udXMuci1wcm9qZWN0Lm9yZycpCiAgICB9CiAgfQogIGEgPC0gc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHJlcXVpcmUocCwgY2hhcmFjdGVyLm9ubHkgPSBUUlVFKSkKICBpZiAoYSkgewogICAgbG9nZ2VyKHBhc3RlMCgiTG9hZGVkIHBhY2thZ2UgIiwgcCwgIiB2ZXJzaW9uICIsIHBhY2thZ2VWZXJzaW9uKHApKSkKICB9IGVsc2UgewogICAgbG9nZ2VyKHBhc3RlMCgiVW5hYmxlIHRvIGxvYWQgcGFja2FnZXMgIiwgcCkpCiAgfQp9CiMgQ3JlYXRlIGEgdmVjdG9yIG9mIHBhY2thZ2VzCnBhY2thZ2VzIDwtIGMoJ2dnYmlwbG90JywndGlkeXZlcnNlJywnZ2d0aGVtZXMnLCdrbml0cicsJ2V4dHJhZm9udCcsJ2Jyb29tJywndHJlZScsJ3JwYXJ0JywKICAgICAgICAgICAgICAncmF0dGxlJywncGFuZGVyJywndG0nLCdnbG1uZXQnLCd0aWR5dGV4dCcsJ3dvcmRjbG91ZDInLCdSVGV4dFRvb2xzJykKIyBVc2UgZnVuY3Rpb24gdG8gbG9hZCB0aGUgcmVxdWlyZWQgcGFja2FnZXMKaW52aXNpYmxlKGxhcHBseShwYWNrYWdlcywgZm5fbG9hZF9wYWNrYWdlcykpCmBgYAoKYGBge3IgSW1wb3J0IEZvbnRzfQojIFRvIHRoZSBmb250IHNlY29uZCBmb250LCBydW4gdGhlIGZvbGxvd2luZyB0d28gbGluZXMgb2YgY29kZSBhbmQgYWRkIG5hbWUgb2YgdXNlciB0byB2ZWN0b3IKIyBzeXN0ZW0ocGFzdGUwKCJjcCAtciAiLHZpel9kaXIsImZvbnRzLy4gfi9MaWJyYXJ5L0ZvbnRzLyIpKSAjIGluc3RhbnRhbmVvdXMKIyBmb250X2ltcG9ydCgpICMgdGFrZXMgYXBwcm94aW1hdGVseSA1LTEwIG1pbgp1c2Vyc192IDwtIGMoIkpvcmRhbiIpCmBgYAoKYGBge3IgQ3JlYXRlIHBhbGV0dGUgYW5kIHRoZW1lfQojIENyZWF0ZSBhIGNvbG9yIHBhbGV0dGUKcGFsNTM4IDwtIGdndGhlbWVzX2RhdGEkZml2ZXRoaXJ0eWVpZ2h0CgojIENyZWF0ZSBhIHRoZW1lIHRvIHVzZSB0aHJvdWdob3V0IHRoZSBhbmFseXNpcwp0aGVtZV9qcmYgPC0gZnVuY3Rpb24oYmFzZV9zaXplID0gOCwgYmFzZV9mYW1pbHkgPSBpZmVsc2UoU3lzLmluZm8oKVtbJ3VzZXInXV0gJWluJSB1c2Vyc192LCAiRGVjaW1hTW9ub1BybyIsICJIZWx2ZXRpY2EiKSkgewogICAgdGhlbWUoCiAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAiI0YwRjBGMCIsIGNvbG91ciA9ICIjNjA2MDYzIiksIAogICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICIjRjBGMEYwIiwgY29sb3VyID0gTkEpLCAKICAgICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9ICAgZWxlbWVudF9saW5lKGNvbG91ciA9ICIjRDdEN0Q4IiksCiAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9ICAgZWxlbWVudF9saW5lKGNvbG91ciA9ICIjRDdEN0Q4Iiwgc2l6ZSA9IDAuMjUpLAogICAgICAgIHBhbmVsLm1hcmdpbiA9ICAgICAgIHVuaXQoMC4yNSwgImxpbmVzIiksCiAgICAgICAgcGFuZWwubWFyZ2luLnggPSAgICAgTlVMTCwKICAgICAgICBwYW5lbC5tYXJnaW4ueSA9ICAgICBOVUxMLAogICAgICAgIGF4aXMudGlja3MueCA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgYXhpcy50aWNrcy55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoY29sb3VyID0gIiNBMEEwQTMiKSwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCh2anVzdCA9IDEsIGNvbG91ciA9ICcjM0MzQzNDJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSBpZmVsc2UoU3lzLmluZm8oKVtbJ3VzZXInXV0gJWluJSB1c2Vyc192LCJEZWNpbWFNb25vUHJvIiwgIkhlbHZldGljYSIpKSwKICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDEsIGNvbG91ciA9ICcjM0MzQzNDJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gaWZlbHNlKFN5cy5pbmZvKClbWyd1c2VyJ11dICVpbiUgdXNlcnNfdiwiRGVjaW1hTW9ub1BybyIsICJIZWx2ZXRpY2EiKSksCiAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgbGVnZW5kLmtleSA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gJ2JvbGQnLCBjb2xvdXIgPSAnIzNDM0MzQycsIGhqdXN0ID0gMCksCiAgICAgICAgdGV4dCA9IGVsZW1lbnRfdGV4dChzaXplID0gOSwgZmFtaWx5ID0gaWZlbHNlKFN5cy5pbmZvKClbWyd1c2VyJ11dICVpbiUgdXNlcnNfdiwiRGVjaW1hTW9ub1BybyIsICJIZWx2ZXRpY2EiKSksCiAgICAgICAgdGl0bGUgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gaWZlbHNlKFN5cy5pbmZvKClbWyd1c2VyJ11dICVpbiUgdXNlcnNfdiwiRGVjaW1hTW9ub1BybyIsICJIZWx2ZXRpY2EiKSkKICAgICAgICAKICAgICkKfQpgYGAKCmBgYHtyIGZuX3Bsb3RfY3ZfZ2xtbmV0fQpmbl9wbG90X2N2X2dsbW5ldCA8LSBmdW5jdGlvbihjdl9nbG1uZXQsIG1haW4pIHsKCiAgICBkYXRhIDwtIAogICAgICAgIHRpZHkoY3ZfZ2xtbmV0KSAlPiUgYXNfdGliYmxlKCkgJT4lCiAgICAgICAgbXV0YXRlKGxvZ19sYW1iZGEgPSBsb2cobGFtYmRhKSkgCiAgICAKICAgIGRhdGEyIDwtCiAgICAgICAgZGF0YSAlPiUKICAgICAgICBmaWx0ZXIocm93X251bWJlcigpICUlIDQgPT0gMCkKICAgIAogICAgZGF0YTMgPC0KICAgICAgICBkYXRhX2ZyYW1lKAogICAgICAgICAgICBsb2dfbGFtYmRhID0gYyhsb2coY3ZfZ2xtbmV0JGxhbWJkYS5taW4pLCBsb2coY3ZfZ2xtbmV0JGxhbWJkYS4xc2UpKQogICAgICAgICAgICAsIG5hbWUgPSBjKCJNaW4iLCAiMXNlIikKICAgICAgICApCiAgICAKICAgIGdncGxvdCgpICsKICAgICAgICBnZW9tX2Vycm9yYmFyKGRhdGEgPSBkYXRhLCBhZXMoeCA9IGxvZ19sYW1iZGEsIHltaW4gPSBjb25mLmxvdywgeW1heCA9IGNvbmYuaGlnaCksIAogICAgICAgICAgICAgICAgICAgICAgY29sb3VyID0gcGFsNTM4Wydka2dyYXknXVtbMV1dLCBhbHBoYSA9IDAuNikgKwogICAgICAgIGdlb21fcG9pbnQoZGF0YSA9IGRhdGEsIGFlcyh4ID0gbG9nX2xhbWJkYSwgeSA9IGVzdGltYXRlKSwgY29sb3VyID0gcGFsNTM4WydyZWQnXVtbMV1dKSArCiAgICAgICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbG9nKGN2X2dsbW5ldCRsYW1iZGEubWluKSwgY29sb3VyID0gcGFsNTM4Wydka2dyYXknXVtbMV1dLCBhbHBoYSA9IDAuNikgKwogICAgICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGxvZyhjdl9nbG1uZXQkbGFtYmRhLjFzZSksIGNvbG91ciA9IHBhbDUzOFsnZGtncmF5J11bWzFdXSwgYWxwaGEgPSAwLjYpICsgCiAgICAgICAgdGhlbWVfanJmKCkgKwogICAgICAgIGxhYnModGl0bGUgPSBtYWluLCB4ID0gZXhwcmVzc2lvbihsb2cobGFtYmRhKSksIHkgPSBjdl9nbG1uZXQkbmFtZSkgKwogICAgICAgIGdlb21fdGV4dChkYXRhID0gZGF0YTIsIGFlcyh4ID0gbG9nX2xhbWJkYSwgeSA9IEluZiwgbGFiZWwgPSBuemVybyksIHZqdXN0ID0gMSwgY29sb3VyID0gJyMzQzNDM0MnLAogICAgICAgICAgICAgICAgICBmYW1pbHkgPSBpZmVsc2UoU3lzLmluZm8oKVtbJ3VzZXInXV0gJWluJSB1c2Vyc192LCJEZWNpbWFNb25vUHJvIiwgIkhlbHZldGljYSIpLAogICAgICAgICAgICAgICAgICBzaXplID0gMi4yNSkgKwogICAgICAgIGdlb21fbGFiZWwoZGF0YSA9IGRhdGEzLCBhZXMoeCA9IGxvZ19sYW1iZGEsIHkgPSBJbmYsIGxhYmVsID0gbmFtZSksIHZqdXN0ID0gNSwgY29sb3VyID0gJyMzQzNDM0MnLAogICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gaWZlbHNlKFN5cy5pbmZvKClbWyd1c2VyJ11dICVpbiUgdXNlcnNfdiwiRGVjaW1hTW9ub1BybyIsICJIZWx2ZXRpY2EiKSkKfSAgIApgYGAKCiMgUHJvYmxlbSAxCgojIyAoMSkKCmBgYHtyfQpzZXQuc2VlZCgxKQp4LnRlbXAgPC0gY2VpbGluZyhydW5pZig0MCwgbWluPTAsIG1heD0xMDApKQpkYXRhMTwtIG1hdHJpeCh4LnRlbXAsbmNvbD0yLCBieXJvdz1UUlVFICkKeSA8LSByb3VuZChyZXhwKG5yb3coZGF0YTEpLCByYXRlPTIpLCAyKQpkYXRhMSA8LSBkYXRhLmZyYW1lKGRhdGExLCB5KQpuYW1lcyhkYXRhMSkgIDwtIGMoIlgxIiwgIlgyIiwgIlkxIikKCmRhdGEyIDwtIGRhdGExCnNldC5zZWVkKDEpCmRhdGEyJFkyIDwtIGlmZWxzZSgoZGF0YTEkWDErZGF0YTEkWDIgPiA3MCksIHJiaW5vbSgxLDEsLjYyKSwgcmJpbm9tKDEsMSwgLjMxKSkKZGF0YTIgPC0gZGF0YTIgJT4lIGFzX3RpYmJsZSgpCgoKZGF0YTIgPC0KICAgIGRhdGEyICU+JQogICAgICAgIG11dGF0ZShyZWdpb24gPSBpZmVsc2UoWDEgPj0gNDAgJiBYMSA8PSA3NSwgIlIxIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoWDEgPj0gNzUgJiBYMiA8PSAyNSwgIlI0IiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKFgxID49IDc1ICYgWDIgPj0gMjUsICJSMyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZmVsc2UoWDEgPD0gNDAgJiBYMiA+PTc1LCAiUjIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaWZlbHNlKFgxIDw9IDIwLCAiUjYiLCAiUjUiKSkpKSkpCgoKICAgICAgICAKcGxvdChkYXRhMiRYMSwgZGF0YTIkWDIsIHhsaW0gPSBjKDAsMTAwKSwgeWxpbSA9IGMoMCwxMDApLCB4bGFiID0gIlgxIiwgeWxhYiA9ICJYMiIpCnRpdGxlKCJkYXRhMiIpCmxpbmVzKHggPSBjKDQwLDQwKSwgeSA9IGMoMCwxMDApKQpsaW5lcyh4ID0gYygwLDQwKSwgeSA9IGMoNzUsNzUpKQpsaW5lcyh4ID0gYyg3NSw3NSksIHkgPSBjKDAsMTAwKSkKbGluZXMoeCA9IGMoMjAsMjApLCB5ID0gYygwLDc1KSkKbGluZXMoeCA9IGMoNzUsMTAwKSwgeSA9IGMoMjUsMjUpKQoKdGV4dCh4ID0gKDQwKzc1KS8yLCB5ID0gNTAsIGxhYmVscyA9IGMoIlIxXG4wLjQ4MzgiKSkKdGV4dCh4ID0gMjAsIHkgPSAoMTAwKzc1KS8yLCBsYWJlbHMgPSBjKCJSMlxuMC41MDAwIikpCnRleHQoeCA9ICg3NSsxMDApLzIsIHkgPSAoMTAwKzI1KS8yLCBsYWJlbHMgPSBjKCJSM1xuMC4zODI1IikpCnRleHQoeCA9ICg3NSsxMDApLzIsIHkgPSAyNS8yLCBsYWJlbHMgPSBjKCJSNFxuMC4wNzAwIikpCnRleHQoeCA9IDMwLCB5ID0gNzUvMiwgbGFiZWxzID0gYygiUjVcbjAuNDIzMyIpKQp0ZXh0KHggPSAxMCwgeSA9IDc1LzIsIGxhYmVscyA9IGMoIlI2XG4wLjEwMDAiKSkKYGBgCgojIyAoMikKCi0gaSkgVGhpcyBpcyBhIHRvcC1kb3duLCByZWN1cnNpdmUgdHJlZQotIGlpKSBJZiB4MSA9IDYwIGFuZCB4MiA9IDMwLCB0aGVuIHdlIHByZWRpY3QgWTEgdG8gYmUgKipgciBkYXRhMiAlPiUgZmlsdGVyKFgxID49IDQwICYgWDEgPD0gNzUpICU+JSBzdW1tYXJpc2UobWVhbihZMSkpICU+JSB1bmxpc3QoKWAqKi4KLSBpaWkpIElmIHgxID0gNjAgYW5kIHgyID0gMzAsIHRoZW4gd2UgcHJlZGljdCBZMSB0byBiZSAqKmByIGRhdGEyICU+JSBmaWx0ZXIoWDEgPj0gNzUgJiBYMiA8PSAyNSkgJT4lIHN1bW1hcmlzZShtZWFuKFkxKSkgJT4lIHVubGlzdCgpYCoqLgoKIyMgKDMpCgpgYGB7cn0KZml0MSA8LSB0cmVlKFkxIH4gWDEgKyBYMiwgZGF0YSA9IGRhdGEyKQpwbG90KGZpdDEsIG1haW4gPSAiSGVsbG8iKQp0ZXh0KGZpdDEpCnRpdGxlKCJSZWdyZXNzaW9uIFRyZWU6IFkxIH4gWDEgKyBYMiIpCmBgYAoKVGhpcyB0cmVlIGlzIHF1aXRlIGRpZmZlcmVudCBpbiB0aGF0IGl0IGRlcGVuZHMgb25seSBvbiB0aGUgdmFyaWFibGUgWDIuIEVtcGlyaWNhbGx5LCB3ZSBzZWUgdGhhdCB0aGUgcHJlZGljdGVkIHZhbHVlcyBkbyBub3QgY2xvc2VseSBtYXRjaCB0aG9zZSBpbiB0aGUgZGlhZ3JhbSBvZiBkYXRhMiBhYm92ZS4KCiMjICg0KQoKaSkKCmBgYHtyfQpwbG90KGRhdGEyJFgxLCBkYXRhMiRYMiwgeGxpbSA9IGMoMCwxMDApLCB5bGltID0gYygwLDEwMCksIHhsYWIgPSAiWDEiLCB5bGFiID0gIlgyIiwgcGNoID0gYXMuY2hhcmFjdGVyKGRhdGEyJFkyKSkKdGl0bGUoImRhdGEyIikKbGluZXMoeCA9IGMoNDAsNDApLCB5ID0gYygwLDEwMCkpCmxpbmVzKHggPSBjKDAsNDApLCB5ID0gYyg3NSw3NSkpCmxpbmVzKHggPSBjKDc1LDc1KSwgeSA9IGMoMCwxMDApKQpsaW5lcyh4ID0gYygyMCwyMCksIHkgPSBjKDAsNzUpKQpsaW5lcyh4ID0gYyg3NSwxMDApLCB5ID0gYygyNSwyNSkpCgp0ZXh0KHggPSAoNDArNzUpLzIsIHkgPSA1MCwgbGFiZWxzID0gYygiUjEiKSkKdGV4dCh4ID0gMjAsIHkgPSAoMTAwKzc1KS8yLCBsYWJlbHMgPSBjKCJSMiIpKQp0ZXh0KHggPSAoNzUrMTAwKS8yLCB5ID0gKDEwMCsyNSkvMiwgbGFiZWxzID0gYygiUjMiKSkKdGV4dCh4ID0gKDc1KzEwMCkvMiwgeSA9IDI1LzIsIGxhYmVscyA9IGMoIlI0IikpCnRleHQoeCA9IDMwLCB5ID0gNzUvMiwgbGFiZWxzID0gYygiUjUiKSkKdGV4dCh4ID0gMTAsIHkgPSA3NS8yLCBsYWJlbHMgPSBjKCJSNiIpKQoKYGBgCgppaSkgVGhlIHByZWRpY3RlZCBQcm9iKFkyID0gMSkgZm9yIHgxID0gNjAsIHgyID0gMzAgaXMgICoqYHIgZGF0YTIgJT4lIGZpbHRlcihYMSA+PSA0MCAmIFgxIDw9IDc1KSAlPiUgc3VtbWFyaXNlKHN1bShZMiA9PSAxKSAvICBuKCkpICU+JSB1bmxpc3QoKWAqKi4KaWlpKSBZMidzIGxhYmVsIGZvciB4MSA9IDYwLCB4MiA9IDMwIGJ5IG1ham9yaXR5IHZvdGUgaXMgKipgciBkYXRhMiAlPiUgZmlsdGVyKFgxID49IDQwICYgWDEgPD0gNzUpICU+JSBncm91cF9ieShZMikgJT4lIGNvdW50KCkgJT4lIGFycmFuZ2UoZGVzYyhuKSkgJT4lIGZpbHRlcihyb3dfbnVtYmVyKCkgPT0gMSkgJT4lIHNlbGVjdChZMikgJT4lIHVubGlzdCgpYCoqLgoKIyMgKDUpCgpgYGB7cn0KZml0MiA8LSBycGFydChmYWN0b3IoWTIpIH4gWDEgKyBYMiwgZGF0YSA9IGRhdGEyKQpmYW5jeVJwYXJ0UGxvdChmaXQyLCBtYWluID0gIkNsYXNzaWZpY2F0aW9uIFRyZWUgZm9yIGRhdGEyIiwgc3ViID0gIiIpCmBgYAoKWWVzLCB0aGlzIHRyZWUgaXMgZGlmZmVyZW50IGZyb20gb3VyIG9yaWdpbmFsIHRyZWUgYmVjYXVzZSBpdCBvbmx5IGRlcGVuZHMgb24gWDEgd2hlcmVhcyB0aGUgb3JpZ2luYWwgdHJlZSBkZXBlbmRlZCBvbiBYMSBhbmQgWDIuCgojIFByb2JsZW0gMgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KeWVscCA8LSByZWFkX2NzdihwYXN0ZTAoZGF0YV9kaXIsICJ5ZWxwX3N1YnNldC5jc3YiKSwgcHJvZ3Jlc3MgPSBGQUxTRSkKCnllbHAgJT4lCiAgICBzZWxlY3Qodm90ZXMuY29vbCwgdm90ZXMuZnVubnksIHZvdGVzLnVzZWZ1bCwgc3RhcnMsIGRhdGUsIHR5cGUpICU+JQogICAgbXV0YXRlKHR5cGUgPSBmYWN0b3IodHlwZSkpICU+JQogICAgc3VtbWFyeSgpICU+JQogICAgcGFuZGVyKHNwbGl0LnRhYmxlID0gSW5mKQpgYGAKCgpgYGB7ciBldmFsID0gRkFMU0V9CnNldC5zZWVkKDEpCm5fd29yZHMgPC0gMjAwMDAKeWVscF9zYW1wbGUgPC0gc2FtcGxlX24oeWVscCwgbl93b3JkcykKCmNvcnAxIDwtIFZDb3JwdXMoVmVjdG9yU291cmNlKHllbHBfc2FtcGxlJHRleHQpKQpjb3JwMiA8LSB0bV9tYXAoY29ycDEsIHN0cmlwV2hpdGVzcGFjZSkKY29ycDMgPC0gdG1fbWFwKGNvcnAyLCByZW1vdmVQdW5jdHVhdGlvbikKY29ycDQgPC0gdG1fbWFwKGNvcnAzLCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKQpjb3JwNSA8LSB0bV9tYXAoY29ycDQsIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoImVuZ2xpc2giKSkKY29ycDYgPC0gdG1fbWFwKGNvcnA1LCBzdGVtRG9jdW1lbnQsIGxhenkgPSBUUlVFKQoKZHRtMSA8LSBEb2N1bWVudFRlcm1NYXRyaXgoY29ycDYsIGNvbnRyb2wgPSBsaXN0KGJvdW5kcyA9IGxpc3QoZ2xvYmFsID0gYyhuX3dvcmRzICogMC4wMixJbmYpKSkpCgpzYXZlUkRTKHllbHBfc2FtcGxlLCBwYXN0ZTAoZGF0YV9kaXIsICJ5ZWxwX3NhbXBsZS5SRFMiKSkKc2F2ZVJEUyhjb3JwMiwgcGFzdGUwKGRhdGFfZGlyLCAiY29ycDIuUkRTIikpCnNhdmVSRFMoZHRtMSwgcGFzdGUwKGRhdGFfZGlyLCAiZHRtMS5SRFMiKSkKYGBgCgojIyAoMSkKCmBgYHtyfQp5ZWxwX3NhbXBsZSA8LSByZWFkUkRTKHBhc3RlMChkYXRhX2RpciwgInllbHBfc2FtcGxlLlJEUyIpKQpjb3JwMiA8LSByZWFkUkRTKHBhc3RlMChkYXRhX2RpciwgImNvcnAyLlJEUyIpKQpkdG0xIDwtIHJlYWRSRFMocGFzdGUwKGRhdGFfZGlyLCAiZHRtMS5SRFMiKSkKYGBgCgoKIyMjIChpKQoKVGhlIERvY3VtZW50LVRlcm0gTWF0cml4IGlzIGEgZGF0YSBzdHJ1Y3R1cmUgaW4gd2hpY2ggcm93cyBhcmUgdGhlIGRvY3VtZW50cyAoWWVscCByZXZpZXcpIGFuZCBlYWNoIGNvbHVtbiBpcyBhIHVuaXF1ZSB3b3JkICh0ZXJtIGFmdGVyIHByb2Nlc3NpbmcpLiBFYWNoIGNlbGwgcmVwcmVzZW50cyBmcmVxdWVuY3kgdGhlIHRlcm0gZnJvbSB0aGUgY29sdW1uIGFwcGVhcnMgaW4gdGhlIGRvY3VtZW50IChyb3cpLiBCZWxvdyBpcyByb3cgMTAwIGFuZCBjb2x1bW4gNDA1OgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KZHRtMVsxMDAsIDQwNV0gJT4lIGFzLm1hdHJpeCgpICU+JQogICAgcGFuZGVyKCkKYGBgCgpUaGUgdmFsdWUgKDApIHJlcHJlc2VudHMgdGhlIG51bWJlciBvZiB0aW1lcyB0aGUgdGVybSAqKnNlYXQqKiBhcHBlYXJzIGluIHRoZSBkb2N1bWVudCAKCj5gciBjb3JwMltbMTAwXV0kY29udGVudGAKCgojIyMgKGlpKQoKVGhlIHNwYXJzaXR5IG9mIHRoZSBEb2N1bWVudC1UZXJtIE1hdHJpeCBpcyAqKjk0JSoqIGFzIHNob3duIGJ5IHRoZSBvdXRwdXQgYmVsb3cuIFRoZXNlIG1lYW5zIHRoYXQgb2YgdGhlICAxMSwwNjAsMDAwIGNlbGxzIGluIHRoZSBtYXRyaXgsIDEwLDM5Myw2MjUgb3IgOTQlIGFyZSAwJ3MuCgpgYGB7cn0KZHRtMQpgYGAKCgojIyAoMikKCmBgYHtyfQp5ZWxwX3NhbXBsZTIgPC0KICAgIHllbHBfc2FtcGxlICU+JQogICAgbXV0YXRlKHJhdGluZyA9IGZhY3RvcihpZmVsc2Uoc3RhcnMgPj0gNCwxLCAwKSkpICU+JQogICAgc2VsZWN0KHJhdGluZykgJT4lCiAgICBiaW5kX2NvbHMoCiAgICAgICAgZHRtMSAlPiUgYXMubWF0cml4KCkgJT4lIGFzX3RpYmJsZSgpCiAgICApCgp5ZWxwX3NhbXBsZTJfdHJhaW4gPC0gc2FtcGxlX24oeWVscF9zYW1wbGUyLCAxNTAwMCkKeWVscF9zYW1wbGUyX3Rlc3QgPC0gZHBseXI6OnNldGRpZmYoeWVscF9zYW1wbGUyLCB5ZWxwX3NhbXBsZTJfdHJhaW4pCmBgYAoKIyMgKDMpCgpgYGB7ciBldmFsID0gRkFMU0V9CnhfbWF0cml4IDwtIG1vZGVsLm1hdHJpeChyYXRpbmcgfiAuLCBkYXRhID0geWVscF9zYW1wbGUyX3RyYWluKVssIC0xXQp5IDwtIHllbHBfc2FtcGxlMl90cmFpbiAlPiUgc2VsZWN0KHJhdGluZykgJT4lIHVubGlzdCgpCmN2X2dsbW5ldCA8LSBjdi5nbG1uZXQoeF9tYXRyaXgsIHksIGZhbWlseSA9ICdiaW5vbWlhbCcsIGFscGhhID0gMSwgbmZvbGRzID0gMTApCnNhdmVSRFMoY3ZfZ2xtbmV0LCBwYXN0ZTAoZGF0YV9kaXIsICJjdl9nbG1uZXQuUkRTIikpCmBgYAoKYGBge3J9CmN2X2dsbW5ldCA8LSByZWFkUkRTKHBhc3RlMChkYXRhX2RpciwgImN2X2dsbW5ldC5SRFMiKSkKZm5fcGxvdF9jdl9nbG1uZXQoY3ZfZ2xtbmV0LCAiTGFzc28gTW9kZWwiKQpgYGAKCgpgYGB7cn0KYmV0YV9sYXNzbyA8LSAKICAgIGNvZWYoY3ZfZ2xtbmV0LCBzID0gImxhbWJkYS4xc2UiKSAlPiUgdGlkeSgpICU+JQogICAgZHBseXI6OnJlbmFtZSh0ZXJtID0gcm93KSAlPiUKICAgIGZpbHRlcih0ZXJtICE9ICIoSW50ZXJjZXB0KSIpCmBgYAoKIyMgKDQpCgpgYGB7ciByZXN1bHRzID0gJ2FzaXMnfQpscl9mb3JtdWxhIDwtIGFzLmZvcm11bGEocGFzdGUwKCJyYXRpbmcgfiAiLCBwYXN0ZShiZXRhX2xhc3NvJHRlcm0sIGNvbGxhcHNlID0gIiArICIpKSkKbHJfZml0IDwtIGdsbShscl9mb3JtdWxhLCBkYXRhID0geWVscF9zYW1wbGUyX3RyYWluLCBmYW1pbHkgPSAiYmlub21pYWwiKQpjb2VmZmljaWVudHMgPC0gCiAgICB0aWR5KGxyX2ZpdCkgJT4lIAogICAgYXNfdGliYmxlKCkgJT4lCiAgICBmaWx0ZXIodGVybSAhPSAiKEludGVyY2VwdCkiKQoKdG9wX3R3b19jb2VmZmljaWVudHMgPC0gCiAgICBjb2VmZmljaWVudHMgJT4lCiAgICBhcnJhbmdlKGRlc2MoZXN0aW1hdGUpKSAlPiUKICAgIG11dGF0ZSgKICAgICAgICBvZGRzID0gZXhwKGVzdGltYXRlKQogICAgICAgICwgcHJvYiA9IG9kZHMgLyAoMSArIG9kZHMpCiAgICAgICAgLCBwZXJjZW50YWdlID0gcGFzdGUwKHJvdW5kKHByb2IgKiAxMDAsIDIpLCAiJSIpCiAgICApICU+JQogICAgaGVhZCgyKSAKCnRvcF90d29fY29lZmZpY2llbnRzICU+JQogICAgc2VsZWN0KHRlcm0sIGVzdGltYXRlLCBvZGRzLCBwcm9iLCBzdGQuZXJyb3IsIHN0YXRpc3RpYywgcC52YWx1ZSkgJT4lCiAgICBwYW5kZXIoKQpgYGAKCiMjIyAoaSkKCkFsbCBlbHNlIHJlbWFpbmluZyBjb25zdGFudCwgdGhlIHR3byBjb2VmZmljaWVudHMgYXJlIHRoZSBjaGFuZ2UgaW4gbG9nIG9kZHMgb2YgYSByYXRpbmcgb2YgNCBvciA1IHN0YXJzICh2cyAxLDIsMykgZm9yIHRoZSBhZGRpdGlvbmFsIGFwcGVhcmFuY2Ugb2YgdGhlIHRlcm0gKipgciB0b3BfdHdvX2NvZWZmaWNpZW50cyR0ZXJtWzFdYCoqIG9yICoqYHIgdG9wX3R3b19jb2VmZmljaWVudHMkdGVybVsyXWAqKi4KCkluIG90aGVyIHdvcmRzLCBhbGwgZWxzZSByZW1haW5pbmcgZml4ZWQsCgorIEFkZGluZyBvbmUgYWRkaXRpb25hbCBhcHBlYXJlbmNlIG9mIHRoZSB0ZXJtICoqYHIgdG9wX3R3b19jb2VmZmljaWVudHMkdGVybVsxXWAqKiB0byB0aGUgWWVscCByZXZpZXcsIGluY3JlYXNlcyB0aGUgcHJvYmFiaWxpdHkgdGhhdCB0aGUgcmV2aWV3ZXIgZ2F2ZSA0IG9yIDUgc3RhcnMgYnkgICoqYHIgdG9wX3R3b19jb2VmZmljaWVudHMkcGVyY2VudGFnZVsxXWAqKi4KKyBBZGRpbmcgb25lIGFkZGl0aW9uYWwgYXBwZWFyZW5jZSBvZiB0aGUgdGVybSAqKmByIHRvcF90d29fY29lZmZpY2llbnRzJHRlcm1bMl1gKiogdG8gdGhlIFllbHAgcmV2aWV3LCBpbmNyZWFzZXMgdGhlIHByb2JhYmlsaXR5IHRoYXQgdGhlIHJldmlld2VyIGdhdmUgNCBvciA1IHN0YXJzIGJ5ICAqKmByIHRvcF90d29fY29lZmZpY2llbnRzJHBlcmNlbnRhZ2VbMl1gKiouCgoKIyMjIChpaSkKCmBgYHtyfQpwb3NpdGl2ZV93b3JkcyA8LQogICAgY29lZmZpY2llbnRzICU+JQogICAgICAgIGFycmFuZ2UoZGVzYyhlc3RpbWF0ZSkpICU+JQogICAgICAgIGhlYWQoMTAwKSAlPiUKICAgICAgICBtdXRhdGUoZnJlcSA9IHJvdW5kKGVzdGltYXRlICogMTAwLCAwKSkgJT4lCiAgICAgICAgc2VsZWN0KHdvcmQgPSB0ZXJtLCBmcmVxKQoKd29yZGNsb3VkMihwb3NpdGl2ZV93b3JkcwogICAgICAgICAgICwgZm9udEZhbWlseSA9IGlmZWxzZShTeXMuaW5mbygpW1sndXNlciddXSAlaW4lIHVzZXJzX3YsIkRlY2ltYU1vbm9Qcm8iLCAiSGVsdmV0aWNhIikKICAgICAgICAgICAsIGNvbG9yID0gcGFsNTM4W1snZ3JlZW4nXV0KICAgICAgICAgICApCmBgYAoKIyMjIChpaWkpCgpgYGB7cn0KbmVnYXRpdmVfd29yZHMgPC0KICAgIGNvZWZmaWNpZW50cyAlPiUKICAgICAgICBhcnJhbmdlKGVzdGltYXRlKSAlPiUKICAgICAgICBoZWFkKDEwMDApICU+JQogICAgICAgIG11dGF0ZShmcmVxID0gLTEgKiByb3VuZChlc3RpbWF0ZSAqIDEwMCwgMCkpICU+JQogICAgICAgIHNlbGVjdCh3b3JkID0gdGVybSwgZnJlcSkKCndvcmRjbG91ZDIobmVnYXRpdmVfd29yZHMKICAgICAgICAgICAsIGZvbnRGYW1pbHkgPSBpZmVsc2UoU3lzLmluZm8oKVtbJ3VzZXInXV0gJWluJSB1c2Vyc192LCJEZWNpbWFNb25vUHJvIiwgIkhlbHZldGljYSIpCiAgICAgICAgICAgLCBjb2xvciA9IHBhbDUzOFtbJ3JlZCddXQogICAgICAgICAgICkKYGBgCgojIyMgKGl2KQoKVGhlIHR3byB3b3JkIGNsb3VkcyAocG9zaXRpdmUgYW5kIG5lZ2F0aXZlIGJhZyBvZiB3b3JkcykgY29udGFpbnMgdGVybXMgd2Ugd291bGQgZXhwZWN0LiBUaGUgdGVybXMgcG9zaXRpdmVseSBjb3JyZWxhdGVkIHdpdGggNC01IHN0YXIgcmV2aWV3cyBpbmNsdWRlIHRoZSBzdGVtcyBgciBwYW5kZXIocG9zaXRpdmVfd29yZHMkd29yZFsxOjRdKWAuIFRoZSB0ZXJtcyBuZWdhdGl2ZWx5IGNvcnJlbGF0ZWQgd2l0aCA0LTUgc3RhciByZXZpZXdzIGluY2x1ZGUgdGhlIHN0ZW1zIGByIHBhbmRlcihuZWdhdGl2ZV93b3JkcyR3b3JkWzE6NF0pYC4gTGFzdGx5LCB3ZSBidWlsZCBhIHdvcmQgY2xvdWQgaW4gdGhlIHNoYXBlIG9mIHRoZSBZZWxwIGxvZ28gdGhhdCBpbmNsdWRlcyBhbGwgdGVybXMgYXBwZWFyaW5nIGluIGF0IGxlYXN0IDIlIG9mIHRoZSByZXZpZXdzIGJ5IGZyZXF1ZW5jeSBvZiBhcHBlYXJhbmNlLgoKYGBge3IgZXZhbCA9IEZBTFNFfQp3b3JkX2ZyZXEgPC0gCiAgICB0aWR5dGV4dDo6dGlkeShkdG0xKSAlPiUgCiAgICBncm91cF9ieSh0ZXJtKSAlPiUgCiAgICBkcGx5cjo6c3VtbWFyaXNlKGZyZXEgPSBzdW0oY291bnQpKSAlPiUKICAgIHNlbGVjdCh3b3JkID0gdGVybSwgZnJlcSkKCndvcmRjbG91ZDIod29yZF9mcmVxLCBmaWdQYXRoID0gcGFzdGUwKHZpel9kaXIsICJ5ZWxwLnBuZyIpLCBzaXplID0gMQogICAgICAgICAgICwgZm9udEZhbWlseSA9IGlmZWxzZShTeXMuaW5mbygpW1sndXNlciddXSAlaW4lIHVzZXJzX3YsIkRlY2ltYU1vbm9Qcm8iLCAiSGVsdmV0aWNhIikKICAgICAgICAgICAsIGNvbG9yID0gIiNEMDBCMDMiCiAgICAgICAgICAgKQpgYGAKCmBgYHtyfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwYXN0ZTAodml6X2RpciwgInllbHBfd29yZHMucG5nIikpCmBgYAoKCiMjICg1KSAgCgpgYGB7ciByZXN1bHRzID0gJ2FzaXMnfQp4X21hdHJpeF90ZXN0IDwtIG1vZGVsLm1hdHJpeChyYXRpbmcgfiAuLCBkYXRhID0geWVscF9zYW1wbGUyX3Rlc3QpWywgLTFdCgp0ZXN0aW5nX2Vycm9ycyA8LSAKICAgIGRhdGFfZnJhbWUoCiAgICAgICAgcmF0aW5nID0geWVscF9zYW1wbGUyX3Rlc3QkcmF0aW5nCiAgICAgICAgLCBwcmVkaWN0X2xhc3NvID0gcHJlZGljdChjdl9nbG1uZXQsIHhfbWF0cml4X3Rlc3QsIHM9YygibGFtYmRhLjFzZSIpLCAgdHlwZSA9ICdyZXNwb25zZScpWywgMV0KICAgICAgICAsIHByZWRpY3RfbHIgPSBwcmVkaWN0KGxyX2ZpdCwgeWVscF9zYW1wbGUyX3Rlc3QsIHR5cGUgPSAncmVzcG9uc2UnKQogICAgCiAgICApICU+JQogICAgbXV0YXRlKAogICAgICAgIHByZWRpY3QyX2xhc3NvID0gaWZlbHNlKHByZWRpY3RfbGFzc28gPiAuNSwgMSwgMCkKICAgICAgICAsIHByZWRpY3QyX2xyID0gaWZlbHNlKHByZWRpY3RfbHIgPiAuNSwgMSwgMCkKICAgICAgICAKICAgICkgJT4lCiAgICBzdW1tYXJpc2UoCiAgICAgICAgYEVycm9yIExhc3NvYCA9IHN1bShwcmVkaWN0Ml9sYXNzbyAhPSByYXRpbmcpIC8gbigpCiAgICAgICAgLCBgRXJyb3IgTG9nIFJlZ2AgPSBzdW0ocHJlZGljdDJfbHIgIT0gcmF0aW5nKSAvIG4oKQogICAgKQogICAgCnRlc3RpbmdfZXJyb3JzICU+JQogICAgcGFuZGVyKCkKYGBgCgpXZSBzZWUgdGhhdCB0aGUgdGVzdGluZyBlcnJvciBmb3IgdGhlIExBU1NPIG1vZGVsIGlzIHNtYWxsZXIgdGhhbiB0aGUgdGVzdGluZyBlcnJvciBvZiB0aGUgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbC4gVGhpcyBpcyBleHBlY3RlZCBhcyB0aGUgTEFTU08gbW9kZWwgY29udGFpbnMgbW9yZSBwcmVkaWN0b3JzLgoKCiMjICg2KSBSVGV4dFRvb2xzCgojIyMgKGkpIExvZ2lzdGljIFJlZ3Jlc3Npb24KCmBgYHtyIGV2YWwgPSBGQUxTRX0KeWVscF9zYW1wbGUyX2NvbWJpbmVkIDwtIGJpbmRfcm93cyh5ZWxwX3NhbXBsZTJfdHJhaW4sIHllbHBfc2FtcGxlMl90ZXN0KQpyYXRpbmcgPC0geWVscF9zYW1wbGUyX2NvbWJpbmVkICU+JSBzZWxlY3QocmF0aW5nKSAlPiUgdW5saXN0KCkKeWVscF9uIDwtIG5yb3coeWVscF9zYW1wbGUyX3RyYWluKQoKY29udGFpbmVyIDwtIGNyZWF0ZV9jb250YWluZXIoZGF0YTJfY29tYmluZWQgJT4lIHNlbGVjdCgtcmF0aW5nKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IHJhdGluZywKICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJhaW5TaXplID0gMTp5ZWxwX24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHRlc3RTaXplID0gKHllbHBfbisxKTpucm93KGRhdGEyX2NvbWJpbmVkKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHZpcmdpbj1GQUxTRSkKCm1vZGVsX2dsbW5ldCA8LSB0cmFpbl9tb2RlbChjb250YWluZXIsICJHTE1ORVQiKQpnbG1uZXRfb3V0IDwtIGNsYXNzaWZ5X21vZGVsKGNvbnRhaW5lciwgbW9kZWxfZ2xtbmV0KSAjIHByZWRpY3Rpb24KZ2xtbmV0X21jZSA8LSBtZWFuKHJhdGluZ1soeWVscF9uKzEpOm5yb3coZGF0YTJfY29tYmluZWQpXSAhPSBnbG1uZXRfb3V0WywgMV0pCnNhdmVSRFMoZ2xtbmV0X21jZSwgcGFzdGUwKGRhdGFfZGlyLCAiZ2xtbmV0X21jZS5SRFMiKSkKYGBgCgpgYGB7cn0KZ2xtbmV0X21jZSA8LSByZWFkUkRTKHBhc3RlMChkYXRhX2RpciwgImdsbW5ldF9tY2UuUkRTIikpCmBgYAoKClRoZSB0ZXN0aW5nIGVycm9yIGZvciBsb2dpc3RpYyByZWdyZXNzaW9uIHVzaW5nIFJUZXh0VG9vbHMgaXMgKipgciBnbG1uZXRfbWNlYCoqIHdoaWNoIGlzIGxhcmdlciB0aGFuIGluICg1KSAoaWkpLiBUaGVzZSB0d28gbG9naXN0aWMgbW9kZWxzIGRvIG5vdCBoYXZlIHRoZSBzYW1lIHByZWRpY3RvcnMuIEluICg1KSAoaWkpIHdlIGxpbWl0ZWQgb3VyIHByZWRpY3RvcnMgdG8gdGhvc2Ugd2l0aCBub24temVybyBjb2VmZmljaWVudHMgaW4gdGhlIExBU1NPIG1vZGVsIHRoYW4gMXNlIGZyb20gdGhlIGxhbWJkYSB0aGF0IG1pbmltaXplZCB0aGUgTVNFLiBXZSBjYW4gYXNzdW1lIHRoYXQgdGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgcHJvZHVjZWQgYnkgUlRleHRUb29scyBvdmVyZml0IHRoZSB0cmFpbmluZyBkYXRhc2V0IGFuZCB0aHVzIGhhcyBhIGxvd2VyIHRlc3RpbmcgZXJyb3IuCgojIyMgKGlpKSBSYW5kb20gRm9yZXN0CgpgYGB7ciBldmFsID0gRkFMU0V9Cm1vZGVsX1JGIDwtIHRyYWluX21vZGVsKGNvbnRhaW5lciwgIlJGIikgIApSRl9vdXQgPC0gY2xhc3NpZnlfbW9kZWwoY29udGFpbmVyLCBtb2RlbF9SRikKUkZfbWNlIDwtIG1lYW4ocmF0aW5nWyh5ZWxwX24rMSk6bnJvdyhkYXRhMl9jb21iaW5lZCldICE9IFJGX291dFssIDFdKQpzYXZlUkRTKFJGX21jZSwgcGFzdGUwKGRhdGFfZGlyLCAiUkZfbWNlLlJEUyIpKQpgYGAKCmBgYHtyfQpSRl9tY2UgPC0gcmVhZFJEUyhwYXN0ZTAoZGF0YV9kaXIsICJSRl9tY2UuUkRTIikpCmBgYAoKVGhlIHRlc3RpbmcgZXJyb3IgZm9yIHRoZSBSVGV4dFRvb2xzIFJhbmRvbSBGb3Jlc3QgaXMgKipgciBSRl9tY2VgKiouCgojIyMgKGlpaSkgU1ZNCgpgYGB7ciBldmFsID0gRkFMU0V9Cm1vZGVsX1NWTSA8LSB0cmFpbl9tb2RlbChjb250YWluZXIsICJTVk0iKSAgIApTVk1fb3V0IDwtIGNsYXNzaWZ5X21vZGVsKGNvbnRhaW5lciwgbW9kZWxfU1ZNKQpTVk1fbWNlIDwtIG1lYW4ocmF0aW5nWyh5ZWxwX24rMSk6bnJvdyhkYXRhMl9jb21iaW5lZCldICE9IFNWTV9vdXRbLCAxXSkKc2F2ZVJEUyhTVk1fbWNlLCBwYXN0ZTAoZGF0YV9kaXIsICJTVk1fbWNlLlJEUyIpKQpgYGAKCmBgYHtyfQpTVk1fbWNlIDwtIHJlYWRSRFMocGFzdGUwKGRhdGFfZGlyLCAiU1ZNX21jZS5SRFMiKSkKYGBgCgpUaGUgdGVzdGluZyBlcnJvciBmb3IgdGhlIFJUZXh0VG9vbHMgU1ZNIGlzICoqYHIgU1ZNX21jZWAqKi4KCiMjIyAoaXYpIEJvb3N0aW5nCgpgYGB7ciBldmFsID0gRkFMU0V9Cm1vZGVsX0JPT1NUSU5HIDwtIHRyYWluX21vZGVsKGNvbnRhaW5lciwgIkJPT1NUSU5HIikgCkJPT1NUSU5HX291dCA8LSBjbGFzc2lmeV9tb2RlbChjb250YWluZXIsIG1vZGVsX0JPT1NUSU5HKQpCT09TVElOR19tY2UgPC0gbWVhbihyYXRpbmdbKHllbHBfbisxKTpucm93KGRhdGEyX2NvbWJpbmVkKV0gIT0gQk9PU1RJTkdfb3V0WywgMV0pCnNhdmVSRFMoQk9PU1RJTkdfbWNlLCBwYXN0ZTAoZGF0YV9kaXIsICJCT09TVElOR19tY2UuUkRTIikpCmBgYAoKYGBge3J9CkJPT1NUSU5HX21jZSA8LSByZWFkUkRTKHBhc3RlMChkYXRhX2RpciwgIkJPT1NUSU5HX21jZS5SRFMiKSkKYGBgCgpUaGUgdGVzdGluZyBlcnJvciBmb3IgdGhlIFJUZXh0VG9vbHMgQm9vc3RpbmcgaXMgKipgciBCT09TVElOR19tY2VgKiouCgpXZSBmaW5kIHRoYXQgdGhlIHJhbmRvbSBmb3Jlc3QgY2xhc3NpZmllciBoYXMgdGhlIGxlYXN0IHRlc3RpbmcgZXJyb3IuIFdlIGFyZSBub3Qgc3VycHJpc2VkIGJ5IHRoaXMgcmVzdWx0IGFzIHJhbmRvbSBmb3Jlc3QgY2xhc3NpZmllcnMgYXJlIHZlcnkgZ29vZCBhdCBub3Qgb3ZlcmZpdHRpbmcgYW5kIGluY29ycG9yYXRpbmcgYSBsYXJnZSBudW1iZXIgb2YgcHJlZGljdG9ycy4KCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CmRhdGFfZnJhbWUoCiAgICBgTW9kZWxgID0gYygiTG9naXN0aWMgUmVncmVzc2lvbiIsICJSYW5kb20gRm9yZXN0IiwgIlNWTSIsICJCb29zdGluZyIpCiAgICAsIGBUZXN0aW5nIE1DRWAgPSBjKGdsbW5ldF9tY2UsIFJGX21jZSwgU1ZNX21jZSwgQk9PU1RJTkdfbWNlKQopICU+JQogICAgYXJyYW5nZShgVGVzdGluZyBNQ0VgKSAlPiUKICAgIHBhbmRlcigpCmBgYAoKIyMgKDcpCgpJZiBnaXZlbiBhIHJldmlldywgaW4gb3JkZXIgdG8gcHJlZGljdCBpdHMgcmF0aW5nICg0IGFuZCA1IHZzIDEsIDIsIG9yIDMpLCB3ZSB3b3VsZCBuZWVkIHRvIHBlcmZvcm0gdGhlIHRleHQgY2xlbnNpbmcgd2UgZGlkIHRvIG91ciBvcmlnaW5hbCBZZWxwIGRhdGFzZXQsIGluY2x1ZGluZwoKMS4gUmVtb3Zpbmcgd2hpdGVzcGFjZQoyLiBSZW1vdmluZyBwdW5jdHVhdGlvbgozLiBDb252ZXJ0aW5nIGFsbCBjaGFyYWN0ZXJzIHRvIGxvd2VyY2FzZQo0LiBSZW1vdmluZyBzdG9wd29yZHMKNS4gU3RlbW1pbmcgdGVybXMKClRoZW4sIHdlIHdvdWxkIG5lZWQgdG8gZmluZCB0aGUgZnJlcXVlbmN5IG9mIGVhY2ggb2YgdGhlIGByIG5jb2woZGF0YTIpIC0gMWAgdGVybXMgdXNlZCBvdXIgbW9kZWwgYW5kIHRoZW4gZmVlZCB0aGlzIGlucHV0IGludG8gb3VyIGZpbmFsIFJhbmRvbSBGb3Jlc3QgbW9kZWwuCgojIFByb2JsZW0gMwoKIyMgKDEpCgoKYGBge3J9CmlxX2RhdGEgPC0gcmVhZF9jc3YocGFzdGUwKGRhdGFfZGlyLCAiSVEuZnVsbC5jc3YiKSkKc2V0LnNlZWQoMTApCmlxX3NhbXBsZSA8LSBzYW1wbGVfbihpcV9kYXRhLCAxMDApCgoKaXFfc2FtcGxlX3Rlc3RzIDwtCiAgICBpcV9zYW1wbGUgJT4lCiAgICBzZWxlY3QoU2NpZW5jZSwgQXJpdGgsIFdvcmQsIFBhcmFnLCBOdW1lciwgQ29kaW5nLCBBdXRvLCBNYXRoLCBNZWNoYW5pYywgRWxlYykKCnRlc3RfbmFtZXMgPC0gbmFtZXMoaXFfc2FtcGxlX3Rlc3RzKQoKcGNhX3Rlc3RzIDwtIHByY29tcChpcV9zYW1wbGVfdGVzdHMsIGNlbnRlciA9IFRSVUUsIHNjYWxlLiA9IFRSVUUpCmBgYAoKIyMjIChhKQoKQmVsb3cgYXJlIHRoZSBQQzEgYW5kIFBDMiBsb2FkaW5ncyBmb3IgdGVzdCB2YXJpYWJsZXM6CgpgYGB7ciByZXN1bHRzID0gJ2FzaXMnfQpwY2FfdGVzdHMkcm90YXRpb25bLCBjKDEsMildICU+JSBwYW5kZXIoKQpgYGAKCkJ5IGRlZmluaXRpb24sIHRoZXNlIGxvYWRpbmdzIGFyZSB1bml0IHZlY3RvcnMgYW5kIHdlIHNob3cgdGhhdCBiZWxvdzoKCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CnBjYV90ZXN0cyRyb3RhdGlvblssIGMoMSwyKV0gJT4lCiAgICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgICByb3duYW1lc190b19jb2x1bW4oIlZhcmlhYmxlIikgJT4lIAogICAgYXNfdGliYmxlKCkgJT4lCiAgICBtdXRhdGUoCiAgICAgICAgYFBDMV4yYCA9IFBDMV4yCiAgICAgICAgLCBgUEMyXjJgID0gUEMyXjIKICAgICkgJT4lCiAgICBiaW5kX3Jvd3MoCiAgICAgICAgZGF0YV9mcmFtZSgKICAgICAgICAgICAgVmFyaWFibGUgPSAiIgogICAgICAgICAgICAsIFBDMSA9IE5BCiAgICAgICAgICAgICwgUEMyID0gTkEKICAgICAgICAgICAgLCBgUEMxXjJgID0gc3VtKC4kYFBDMV4yYCkKICAgICAgICAgICAgLCBgUEMyXjJgID0gc3VtKC4kYFBDMl4yYCkKICAgICAgICApCiAgICApICU+JQogICAgc2VsZWN0KFZhcmlhYmxlLCBQQzEsIGBQQzFeMmAsIFBDMiwgYFBDMl4yYCkgJT4lCiAgICBwYW5kZXIobWlzc2luZyA9ICIiKQpgYGAKClRoZSBjb3JyZWxhdGlvbiBvZiBQQzEgYW5kIFBDMiBpcyAqKmByIGNvcihwY2FfdGVzdHMkcm90YXRpb25bLCBjKDEpXSwgcGNhX3Rlc3RzJHJvdGF0aW9uWywgYygyKV0pYCoqIHdoaWNoIGlzIG5lYXJseSB1bmNvcnJlbGF0ZWQuCgojIyMgKGIpCgpUaGUgUENBMSBzY29yZSBmb3IgZWFjaCBzdWJqZWN0IGlzIG9idGFpbmVkIGZyb20gdGhlIHByb2R1Y3Qgb2YgdGhlIFBDQTEgbG9hZGluZ3MgKCRccGhpXjEkJ3MpIGFuZCB0aGUgdmFyaWFibGUgdmFsdWVzIGZyb20gZWFjaCBzdWJqZWN0LiBJbiBvdXIgZGF0YXNldCwgaXQgbG9va3MgbGlrZQpwYwokJFBDMSA9IFxwaGleMV97YHIgdGVzdF9uYW1lc1sxXWB9YHIgdGVzdF9uYW1lc1sxXWAgK1xwaGleMV97YHIgdGVzdF9uYW1lc1syXWB9YHIgdGVzdF9uYW1lc1syXWAgKyBcZG90c20gKyBccGhpXjFfe2ByIHRlc3RfbmFtZXNbN11gfWByIHRlc3RfbmFtZXNbN11gJCQgIAoKIyMjIChjKQoKQnkgZGVmaW5pdGlvbiwgdGhlIHByaW5jaXBsZSBjb21wb2VudCBzY29yZXMgYXJlIHVuY29ycmVsYXRlZC4gSW4gcHJhY3RpY2Ugd2Ugc2VlIHRoYXQgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gUEMxIGFuZCBQQzIgc2NvcmVzIGlzICoqYHIgY29yKHBjYV90ZXN0cyR4WywxXSwgcGNhX3Rlc3RzJHhbLDJdKWAqKi4KCiMjIyAoZCkKCkJlbG93IHdlIHBsb3QgdGhlIHBlcmNlbnQgdmFyaWFuY2UgZXhwbGFpbmVkLiBXZSBzZWUgdGhhdCBmaXJzdCBwcmluY2lwYWwgY29tcG9uZW5ldCBleHBsYWlucyBuZWFybHkgKioyLzMqKiBvZiBhbGwgdmFyaWFuY2UgYW1vbmcgdGhlIG9ic2VydmF0aW9ucy4KCmBgYHtyfQpkYXRhX2ZyYW1lKAogICAgdmFyaWFuY2VfZXhwbGFpbmVkID0gKHBjYV90ZXN0cyRzZGV2KV4yIC8gc3VtICgocGNhX3Rlc3RzJHNkZXYpXjIpCikgJT4lCiAgICBtdXRhdGUocGMgPSAxOjEwKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IHBjLCB5ID0gdmFyaWFuY2VfZXhwbGFpbmVkKSkgKwogICAgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsIGZpbGwgPSBwYWw1MzhbWydibHVlJ11dKSArCiAgICBsYWJzKHRpdGxlID0gIlBlcmNlbnQgVmFyaWFuY2UgRXhwbGFpbmVkIiwgeSA9ICJWYXJpYW5jZSBFeHBsYWluZWQiLCB4ID0gIlByaW5jaXBhbCBDb21wb25lbnQiKSArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArCiAgICB0aGVtZV9qcmYoKQpgYGAKCgojIyMgKGUpCgpXZSBzZWUgdGhhdCBuZWFybHkgKio3NyUqKiBvZiB2YXJpYW5jZSBpbiB0aGUgZGF0YSBpcyBleHBsYWluZWQgYnkgdGhlIGZpcnN0IHR3byBwcmluY2lwYWwgY29tcG9uZW50cy4KCgpgYGB7cn0KZGF0YV9mcmFtZSgKICAgIHZhcmlhbmNlX2V4cGxhaW5lZCA9IChwY2FfdGVzdHMkc2RldileMiAvIHN1bSAoKHBjYV90ZXN0cyRzZGV2KV4yKQopICU+JQogICAgbXV0YXRlKAogICAgICAgIGN1bXN1bSA9IGN1bXN1bSh2YXJpYW5jZV9leHBsYWluZWQpCiAgICAgICAgLCBwYyA9IDE6MTAKICAgICkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBwYywgeSA9IGN1bXN1bSkpICsKICAgIGdlb21fbGluZShjb2xvdXIgPSBwYWw1MzhbWydibHVlJ11dKSArCiAgICBnZW9tX3BvaW50KGNvbG91ciA9IHBhbDUzOFtbJ2JsdWUnXV0pICsKICAgIGxhYnModGl0bGUgPSAiUGVyY2VudCBWYXJpYW5jZSBFeHBsYWluZWQiLCB5ID0gIlZhcmlhbmNlIEV4cGxhaW5lZCIsIHggPSAiUHJpbmNpcGFsIENvbXBvbmVudCIpICsKICAgIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsIDEpLCBsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsKICAgIHRoZW1lX2pyZigpCmBgYAoKIyMjIChmKQoKSW4gdGhlIGJpcGxvdCBiZWxvdywgd2Ugc2VlIHRoYXQgdGhlIGxlbmd0aCBvZiBlYWNoIHZlY3RvciBpcyBuZWFybHkgdGhlIHNhbWUgYW5kIHRoYXQgdGhlIHZlY3RvcnMgYWxsIHBvaW50cyBpbiB0aGUgc2FtZSBkaXJlY3Rpb24gb24gdGhlIFBDMSBheGlzLiBUaGUgZGlmZmVyZW5jZXMgYXJlIHNlZW4gb24gdGhlIFBDMiBheGlzLiBJbiBvbmUgZGlyZWN0aW9uLCB0aGVyZSBpcyB0aGUgYXV0bywgbWVjaGFuaWMsIGVsZWN0cmljLCBhbmQgc2NpZW5jZSB0ZXN0IGFuZCBpbiB0aGUgb3RoZXIgZGlyZWN0aW9uIGlzIHRoZSBudW1lcmljYWwgYW5kIGNvZGluZywgbnVtZXJpY2FsLCBwYXJhZ3JhcGgsIGFuZCB3cml0aW5nIHRlc3QgKHdpdGggYXJpdGhtZXRpYyBpbiBuZWl0aGVyKS4gCgpgYGB7cn0KZ2diaXBsb3QocGNhX3Rlc3RzLCBvYnMuc2NhbGUgPSAxLCB2YXIuc2NhbGUgPSAxKSArIAogICAgdGhlbWVfanJmKCkgKwogICAgbGFicyh0aXRsZSA9ICJCaXBsb3Qgb2YgUEMxIGFuZCBQQzIiKQpgYGAKCiMjIyAoZykKCldlIHRoZW4gY29sb3IgdGhlIGJpcGxvdCBieSBnZW5kZXIuIFdlIHNlZSBhIHN0cm9uZyBzeXN0ZW1hdGljIHNlcGFyYXRpb24gYmV0d2VlbiBtYWxlIGFuZCBmZW1hbGUgaW4gdGhlIGJpcGxvdCB3aXRoIG1vcmUgZmVtYWxlIG9ic2VydmF0aW9ucyBvbiB0aGUgbmVnYXRpdmUgZGlyZWN0aW9uIG9mIFBDMiBhbmQgbW9yZSBtYWxlIG9ic2VydmF0aW9ucyBvbiB0aGUgcG9zaXRpdmUgZGlyZWN0aW9uIG9mIFBDMi4gV2UgY2FuIGludGVycHJldCB0aGlzIGFzIG1hbGVzIHBlcmZvcm1pbmcgYmV0dGVyIG9uIGF1dG8sIG1lY2hhbmljLCBlbGVjdHJpYywgYW5kIHNjaWVuY2UgdGVzdCB0aGFuIGZlbWFsZXMgaW4gMTk4MSwgd2hvIHBlcmZvcm1lZCBiZXR0ZXIgb24gY29kaW5nLCBudW1lcmljYWwsIHBhcmFncmFwaCwgYW5kIHdyaXRpbmcgdGVzdHMuCgpgYGB7cn0KZ2diaXBsb3QocGNhX3Rlc3RzLCBncm91cHMgPSBmYWN0b3IoaXFfc2FtcGxlJEdlbmRlciksIG9icy5zY2FsZSA9IDEsIHZhci5zY2FsZSA9IDEpICsgCiAgICB0aGVtZV9qcmYoKSArCiAgICBsYWJzKHRpdGxlID0gIkJpcGxvdCBvZiBQQzEgYW5kIFBDMiBieSBHZW5kZXIiKSArCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKCJHZW5kZXIiLCB2YWx1ZXMgPSBjKCJtYWxlIiA9IHBhbDUzOFtbJ2JsdWUnXV0sICJmZW1hbGUiID0gcGFsNTM4W1sncmVkJ11dKSkKYGBgCgojIyAoMikKCgojIyMgKGEpCgpgYGB7cn0KaXFfc2FtcGxlX2VzdGVlbSA8LQogICAgaXFfc2FtcGxlICU+JQogICAgc2VsZWN0KHN0YXJ0c193aXRoKCJFc3RlZW0iKSkgJT4lCiAgICBtdXRhdGUoCiAgICAgICAgRXN0ZWVtMSA9IDUgLSBFc3RlZW0xCiAgICAgICAgLCBFc3RlZW0yID0gNSAtIEVzdGVlbTIKICAgICAgICAsIEVzdGVlbTQgPSA1IC0gRXN0ZWVtNAogICAgICAgICwgRXN0ZWVtNiA9IDUgLSBFc3RlZW02CiAgICAgICAgLCBFc3RlZW03ID0gNSAtIEVzdGVlbTcKICAgICkKICAgIAoKdGVzdF9lc3RlZW0gPC0gbmFtZXMoaXFfc2FtcGxlX2VzdGVlbSkKCnBjYV9lc3RlZW0gPC0gcHJjb21wKGlxX3NhbXBsZV9lc3RlZW0sIGNlbnRlciA9IFRSVUUsIHNjYWxlLiA9IFRSVUUpCmBgYAoKIyMjIChiKQoKQmVsb3cgYXJlIHRoZSBQQzEgbG9hZGluZ3MKCmBgYHtyIHJlc3VsdHMgPSAnYXNpcyd9CnBjYV9lc3RlZW0kcm90YXRpb25bLCBjKDEsMildICU+JQogICAgYXMuZGF0YS5mcmFtZSgpICU+JQogICAgcm93bmFtZXNfdG9fY29sdW1uKCJWYXJpYWJsZSIpICU+JSAKICAgIGFzX3RpYmJsZSgpICU+JQogICAgc2VsZWN0KFZhcmlhYmxlLCBQQzEpICU+JQogICAgcGFuZGVyKCkKYGBgCgojIyMgKGMpCgpXZSBzZWUgdGhhdCAqKmByIHJvdW5kKDEwMCAqICgocGNhX2VzdGVlbSRzZGV2KV4yIC8gc3VtICgocGNhX2VzdGVlbSRzZGV2KV4yKSlbMV0sMilgJSoqIG9mIHZhcmlhbmNlIGlzIGV4cGxhaW5lZCBieSBQQzEuIAoKYGBge3J9CmRhdGFfZnJhbWUoCiAgICB2YXJpYW5jZV9leHBsYWluZWQgPSAocGNhX2VzdGVlbSRzZGV2KV4yIC8gc3VtICgocGNhX2VzdGVlbSRzZGV2KV4yKQopICU+JQogICAgbXV0YXRlKHBjID0gMToxMCkgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBwYywgeSA9IHZhcmlhbmNlX2V4cGxhaW5lZCkpICsKICAgIGdlb21fYmFyKHN0YXQgPSAnaWRlbnRpdHknLCBmaWxsID0gcGFsNTM4W1snYmx1ZSddXSkgKwogICAgbGFicyh0aXRsZSA9ICJQZXJjZW50IFZhcmlhbmNlIEV4cGxhaW5lZCIsIHkgPSAiVmFyaWFuY2UgRXhwbGFpbmVkIiwgeCA9ICJQcmluY2lwYWwgQ29tcG9uZW50IikgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKwogICAgdGhlbWVfanJmKCkKYGBgCgpgYGB7cn0KZGF0YV9mcmFtZSgKICAgIHZhcmlhbmNlX2V4cGxhaW5lZCA9IChwY2FfZXN0ZWVtJHNkZXYpXjIgLyBzdW0gKChwY2FfZXN0ZWVtJHNkZXYpXjIpCikgJT4lCiAgICBtdXRhdGUoCiAgICAgICAgY3Vtc3VtID0gY3Vtc3VtKHZhcmlhbmNlX2V4cGxhaW5lZCkKICAgICAgICAsIHBjID0gMToxMAogICAgKSAlPiUKICAgIGdncGxvdChhZXMoeCA9IHBjLCB5ID0gY3Vtc3VtKSkgKwogICAgZ2VvbV9saW5lKGNvbG91ciA9IHBhbDUzOFtbJ2JsdWUnXV0pICsKICAgIGdlb21fcG9pbnQoY29sb3VyID0gcGFsNTM4W1snYmx1ZSddXSkgKwogICAgbGFicyh0aXRsZSA9ICJQZXJjZW50IFZhcmlhbmNlIEV4cGxhaW5lZCIsIHkgPSAiVmFyaWFuY2UgRXhwbGFpbmVkIiwgeCA9ICJQcmluY2lwYWwgQ29tcG9uZW50IikgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwgMSksIGxhYmVscyA9IHNjYWxlczo6cGVyY2VudCkgKwogICAgdGhlbWVfanJmKCkKYGBgCgojIyMgKGQpCgpGcm9tIHRoZSBjaGFydHMgaW4gKGMpIHdlIHJlY29nbml6ZSB0aGF0IHRoZSBmaXJzdCBQQyBleHBsYWlucyBuZWFybHkgNTAlIG9mIHRoZSB2YXJpYW5jZSBpbiB0aGUgZGF0YS4gRnJvbSB0aGUgYmlwbG90IGJlbG93IHdlIHNlZSB0aGF0IGFsbCBlc3RlZW0gcXVlc3Rpb25zIHBvaW50IGluIHRoZSBzYW1lIGRpcmVjdGlvbiBvbiB0aGUgUEMxIGF4aXMuIFRoZXJlIGFyZSBkaWZmZXJlbmNlcyBpbiB0aGUgbGVuZ3RoIG9mIHRoZSB2ZWN0b3JzLCB3aXRoIHRoZSBxdWVzdGlvbiAiSSBhbSBzYXRpc2ZpZWQgd2l0aCBteXNlbGYgYmVpbmcgdGhlIHNob3J0ZXN0Ii4gV2UgYWxzbyBzZWUgdGhhdCBxdWVzdGlvbnMgMSwgMiwgYW5kIDQgYXJlIGJ1bmNoZWQgdG9nZXRoZXI6IHRoZXNlIGFyZSBxdWVzdGlvbnMgYWJvdXQgcG9zaXRpdmUgZmVhdHVyZXMgb2Ygb25lc2VsZi4gV2Ugc2VlIHRoYXQgcXVlc3Rpb24gOSBwb2ludGluZyBpbiB0aGUgb3Bwb3NpdGUgZGlyZWN0aW9uIGFuZCBkZWFscyB3aXRoICJmZWVsaW5nIHVzZWxlc3MiLiBUaGUgb3RoZXIgdmVjdG9ycyB0aGF0IHBvaW50IGluIHRoaXMgZGlyZWN0aW9uIG9uIHRoZSBQQzIgYXhpcyBhbGwgZGVhbCB3aXRoIG5lZ2F0aXZlIGZlYXR1cmUgb3IgY29tcG9uZW50cyBvZiBvbmVzZWxmLgoKYGBge3J9CmdnYmlwbG90KHBjYV9lc3RlZW0sIG9icy5zY2FsZSA9IDEsIHZhci5zY2FsZSA9IDEpICsgCiAgICB0aGVtZV9qcmYoKSArCiAgICBsYWJzKHRpdGxlID0gIkJpcGxvdCBvZiBQQzEgYW5kIFBDMiIpCmBgYAoKIyMgKDMpCgojIyMgKGEpCgpJdCBpbXBvcnRhbnQgdG8gY3JlYXRlIGEgbG9nYXJpdGhtaWMgdHJhbnNmb3JtYXRpb24gb2YgaW5jb21lIGJlY2F1c2UgdGhlIHZhcmlhYmxlIGlzIHN0cm9uZ2x5IHBvc2l0aXZlbHkgc2tld2VkLiAKCmBgYHtyfQppcV9zYW1wbGUgJT4lCiAgICBnZ3Bsb3QoYWVzKHggPSBJbmNvbWUyMDA1KSkgKwogICAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDUwLCBmaWxsID0gcGFsNTM4W1snYmx1ZSddXSkgKwogICAgbGFicyh0aXRsZSA9ICJIaXN0b2dyYW0gb2YgSW5jb21lICgyMDA1KSIsIHggPSAiSW5jb21lICgyMDA1KSIsIHkgPSAiT2JzZXJ2YXRpb25zIikgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6ZG9sbGFyKSArIAogICAgdGhlbWVfanJmKCkKYGBgCgojIyMgKGIpCgpXZSByZXN1c2UgdGhlIHJlc3VsdHMgZnJvbSAoMSk6IGBwY2FfdGVzdHNgLgoKIyMjIChjKQoKYGBge3J9CmlxX3NhbXBsZV9pbmNvbWUgPC0gCiAgICBpcV9zYW1wbGUgJT4lCiAgICAgICAgYmluZF9jb2xzKAogICAgICAgICAgICBwY2FfdGVzdHMkeFssIGMoMSwyLDMpXSAlPiUKICAgICAgICAgICAgICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUKICAgICAgICAgICAgICAgIGFzX3RpYmJsZSgpCiAgICAgICAgKSAlPiUKICAgICAgICBtdXRhdGUoaW5jb21lID0gbG9nKEluY29tZTIwMDUpKQoKZml0MSA8LSBsbShpbmNvbWUgfiBQQzEsIGRhdGEgPSBpcV9zYW1wbGVfaW5jb21lKQpmaXQyIDwtIGxtKGluY29tZSB+IFBDMStQQzIrUEMzLCBkYXRhID0gaXFfc2FtcGxlX2luY29tZSkKYGBgCgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KdGlkeShmaXQxKSAlPiUgcGFuZGVyKGNhcHRpb24gPSAiaW5jb21lIH4gUEMxIikKYGBgCgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KdGlkeShmaXQyKSAlPiUgcGFuZGVyKGNhcHRpb24gPSAiaW5jb21lIH4gUEMxICsgUEMyICsgUEMzIikKYGBgCgpXZSBzZWUgdGhhdCB0aGUgTFMgZXN0aW1hdGVzIG9mIFBDMSBpbiBib3RoIGZpdDEgYW5kIGZpdDIgYXJlIGlkZW50aWNhbC4gVGhpcyBpcyBkdWUgdG8gdGhlIGZhY3QgdGhhdCB0aGUgUEMncyBhcmUgdW5jb3JyZWxhdGVkIHZhcmlhYmxlcy4gQmVsb3cgaXMgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeCBvZiB0aGUgMyBQQydzOgoKYGBge3IgcmVzdWx0cyA9ICdhc2lzJ30KY29yKHBjYV90ZXN0cyR4WywgYygxLDIsMyldKSAlPiUgcGFuZGVyKCkKYGBgCgpXZSBzZWUgZnJvbSB0aGUgdGFibGUgYWJvdmUgdGhhdCBQQzEgYW5kIFBDMiBhcmUgc2lnbmlmaWNhbnQgaW4gcHJlZGljdGluZyBpbmNvbWUsIGJ1dCBQQzMgaXMgbm90LiBBZGRpdGlvbmFsbHksIGJhc2VkIG9uIHRoZSBwZXJjZW50IHZhcmlhbmNlIGV4cGxhaW5lZCBjaGFydHMgaW4gKDEpLCB3ZSBzZWUgYW4gZWxib3cgYWZ0ZXIgdGhlIHNlY29uZCBQQyBhbmQgd291bGQgdGh1cyBwcmVmZXIgYSBtb2RlbCB3aXRoIG9ubHkgdHdvIFBDcy4KCiMjIyAoZCkKCkNvbnRyb2xsaW5nIGZvciBwZXJzb25hbCBkZW1vZ3JhcGhpYyB2YXJpYWJsZXMgYW5kIGhvdXNlaG9sZCBlbnZpcm9ubWVudCwgd2UgZmluZCB0aGF0IHRoZSBsZWFkaW5nIHR3byBQQ+KAmXMgb2YgQVNWQUIgYXJlIHNpZ25pZmljYW50IHZhcmlhYmxlcyB0byBwcmVkaWN0IGluY29tZSBhdCAuMDEgbGV2ZWwuCgpgYGB7ciByZXN1bHRzID0gJ2FzaXMnfQpmaXQzIDwtIGxtKGluY29tZSB+IFBDMStQQzIsIGRhdGEgPSBpcV9zYW1wbGVfaW5jb21lKQp0aWR5KGZpdDMpICU+JSBwYW5kZXIoY2FwdGlvbiA9ICJpbmNvbWUgfiBQQzEgKyBQQzIiKQpgYGAKCk91ciBtb2RlbCB0byBwcmVkaWN0IGluY29tZSBpczoKCiQkSW5jb21lID0gYHIgdGlkeShmaXQzKVsxLCAyXWAgKyAKYHIgdGlkeShmaXQzKVsyLCAyXWBgciB0aWR5KGZpdDMpWzIsIDFdYCArCmByIHRpZHkoZml0MylbMywgMl1gYHIgdGlkeShmaXQzKVszLCAxXWAkJAoKVGhlIHR3byBwcmluY2lwYWwgY29tcG9uZW50cyBhcmUgZnJvbSB0aGUgQVNWQUIgKEFybWVkIFNlcnZpY2VzIFZvY2F0aW9uYWwgQXB0aXR1ZGUgQmF0dGVyeSkgdGVzdCBzY29yZXMsIHdoaWNoIGluY2x1ZGVzIHRlc3RzIGluCgorIFNjaWVuY2UsIAorIEFyaXRobWV0aWMgcmVhc29uaW5nCisgV29yZCBrbm93bGVkZ2UKKyBQYXJhZ3JhcGggY29tcHJlaGVuc2lvbgorIE51bWVyaWNhbCBvcGVyYXRpb24KKyBDb2Rpbmcgc3BlZWQKKyBBdXRvbWF0aXZlIGFuZCBTaG9wIGluZm9ybWF0aW9uCisgTWF0aCBrbm93bGVkZ2UgCisgTWVjaGFuaWMgQ29tcHJlaGVuc2lvbgorIEVsZWN0cm9uaWMgaW5mb3JtYXRpb24KCg==