Note: Some results may differ from the hard copy book due to the changing of sampling procedures introduced in R 3.6.0. See http://bit.ly/35D1SW7 for more details. Access and run the source code for this notebook here. Do to output size, most of this chapter’s code chunks should not be ran on RStudio Cloud.
Hidden chapter requirements used in the book to set the plotting theme and load packages used in hidden code chunks:
knitr::opts_chunk$set(
message = FALSE,
warning = FALSE,
cache = FALSE
)
# Set the graphical theme
ggplot2::theme_set(ggplot2::theme_light())
Prerequisites
For this chapter we’ll use the following packages:
# Helper packages
library(dplyr) # for data manipulation
library(ggplot2) # for data visualization
# Modeling packages
library(h2o) # for fitting autoencoders
To illustrate autoencoder concepts we’ll continue with the mnist
data set:
mnist <- dslabs::read_mnist()
names(mnist)
[1] "train" "test"
Since we will be using h2o we’ll also go ahead and initialize our H2O session:
h2o.no_progress() # turn off progress bars
h2o.init(max_mem_size = "5g") # initialize H2O instance
Undercomplete autoencoders
Figure 19.1:
knitr::include_graphics("images/Autoencoder_structure.png")
Comparing PCA to an autoencoder
# Convert mnist features to an h2o input data set
features <- as.h2o(mnist$train$images)
# Train an autoencoder
ae1 <- h2o.deeplearning(
x = seq_along(features),
training_frame = features,
autoencoder = TRUE,
hidden = 2,
activation = 'Tanh',
sparse = TRUE
)
# Extract the deep features
ae1_codings <- h2o.deepfeatures(ae1, features, layer = 1)
ae1_codings
[60000 rows x 2 columns]
Figure 19.2:
features <- as.h2o(mnist$train$images)
# Perform PCA
pca1 <- h2o.prcomp(
training_frame = features,
k = 2,
transform = 'STANDARDIZE'
)
# Plot resulting PCs
pca_plot <- predict(pca1, features) %>%
as.data.frame() %>%
select(PC1, PC2) %>%
mutate(response = factor(mnist$train$labels)) %>%
ggplot(aes(PC1, PC2)) +
geom_point(aes(color = response), size = 0.5, alpha = 0.25) +
ggtitle('(A) PCA projection')
# Plot results from autoencoder
ae_plot <- ae1_codings %>%
as.data.frame() %>%
select(DF1 = 'DF.L1.C1', DF2 = 'DF.L1.C2') %>%
mutate(response = factor(mnist$train$labels)) %>%
ggplot(aes(DF1, DF2, color = response)) +
geom_point(size = .5, alpha = .25) +
ggtitle('(B) Autoencoder projection')
# DIsplay plots side by side
gridExtra::grid.arrange(pca_plot, ae_plot, nrow = 1)
Stacked autoencoders
knitr::include_graphics('images/autoencoder-symmetry.png')
# Hyperparameter search grid
hyper_grid <- list(hidden = list(
c(50),
c(100),
c(300, 100, 300),
c(100, 50, 100),
c(250, 100, 50, 100, 250)
))
# Execute grid search
ae_grid <- h2o.grid(
algorithm = 'deeplearning',
x = seq_along(features),
training_frame = features,
grid_id = 'autoencoder_grid',
autoencoder = TRUE,
activation = 'Tanh',
hyper_params = hyper_grid,
sparse = TRUE,
ignore_const_cols = FALSE,
seed = 123
)
# Print grid details
h2o.getGrid('autoencoder_grid', sort_by = 'mse', decreasing = FALSE)
H2O Grid Details
================
Grid ID: autoencoder_grid
Used hyper parameters:
- hidden
Number of models: 5
Number of failed models: 0
Hyper-Parameter Search Summary: ordered by increasing mse
Visualizing the reconstruction
# Get sampled test images
index <- sample(1:nrow(mnist$test$images), 4)
sampled_digits <- mnist$test$images[index, ]
colnames(sampled_digits) <- paste0("V", seq_len(ncol(sampled_digits)))
# Predict reconstructed pixel values
best_model_id <- ae_grid@model_ids[[1]]
best_model <- h2o.getModel(best_model_id)
reconstructed_digits <- predict(best_model, as.h2o(sampled_digits))
names(reconstructed_digits) <- paste0("V", seq_len(ncol(reconstructed_digits)))
combine <- rbind(sampled_digits, as.matrix(reconstructed_digits))
# Plot original versus reconstructed
par(mfrow = c(1, 3), mar = c(0, 0.5, 2, 0.5))
layout(matrix(seq_len(nrow(combine)), 4, 2, byrow = FALSE))
for (i in seq_len(nrow(combine))) {
title <- switch(as.character(i), "1" = "Original digits\n",
"5" = "Autoencoder reconstruction\n", NULL)
image(matrix(combine[i, ], 28, 28)[, 28:1],
xaxt = "n", yaxt = "n", col = gray.colors(12, rev = TRUE),
main = title)
}
Sparse autoencoders
ae100_codings <- h2o.deepfeatures(best_model, features, layer = 1)
ae100_codings %>%
as.data.frame() %>%
tidyr::gather() %>%
summarize(average_activation = mean(value))
Figure 19.5:
codings <- ae100_codings %>%
as.data.frame() %>%
tidyr::gather() %>%
mutate(key = stringr::str_replace(key, 'DF.L1.', '')) %>%
group_by(key) %>%
summarize(average_activation = mean(value)) %>%
arrange(desc(average_activation))
avg_activation <- summarize(codings, avg = mean(average_activation))
ggplot(codings, aes(average_activation, reorder(key, average_activation),
color = average_activation > avg_activation$avg)) +
geom_vline(xintercept = avg_activation$avg, lty = 'dashed') +
geom_point(show.legend = FALSE, size = .75) +
ylab("Deep feature codings") +
xlab("Average activation") +
theme(axis.text.y = element_text(size = 3))
# Hyperparameter search grid
hyper_grid <- list(sparsity_beta = c(0.01, 0.05, 0.1, 0.2))
# Execute grid search
ae_sparsity_grid <- h2o.grid(
algorithm = 'deeplearning',
x = seq_along(features),
training_frame = features,
grid_id = 'sparsity_grid',
autoencoder = TRUE,
hidden = 100,
activation = 'Tanh',
hyper_params = hyper_grid,
sparse = TRUE,
average_activation = -0.1,
ignore_const_cols = FALSE,
seed = 123
)
# Print grid details
h2o.getGrid('sparsity_grid', sort_by = 'mse', decreasing = FALSE)
H2O Grid Details
================
Grid ID: sparsity_grid
Used hyper parameters:
- sparsity_beta
Number of models: 4
Number of failed models: 0
Hyper-Parameter Search Summary: ordered by increasing mse
Figure 19.6:
best_sparse_model <- ae_sparsity_grid@model_ids[[1]] %>%
h2o.getModel()
sparse_codings <- h2o.deepfeatures(best_sparse_model, features, layer = 1)
sparse_codings <- sparse_codings %>%
as.data.frame() %>%
tidyr::gather() %>%
mutate(key = stringr::str_replace(key, 'DF.L1.', '')) %>%
group_by(key) %>%
summarize(average_activation = mean(value)) %>%
arrange(desc(average_activation))
avg_activation <- summarize(sparse_codings, avg = mean(average_activation))
ggplot(sparse_codings, aes(average_activation, reorder(key, average_activation),
color = average_activation > avg_activation$avg)) +
geom_vline(xintercept = avg_activation$avg, lty = 'dashed') +
geom_point(show.legend = FALSE, size = .75) +
ylab("Deep feature codings") +
xlab("Average activation") +
theme(axis.text.y = element_text(size = 3))
Figure 19.7:
# Hyperparameter search grid
hyper_grid <- list(
sparsity_beta = c(0.01, 0.05, 0.1, 0.2),
average_activation = c(-0.75, -0.5, -0.25)
)
# Execute grid search
ae_sparsity_grid2 <- h2o.grid(
algorithm = 'deeplearning',
x = seq_along(features),
training_frame = features,
grid_id = 'sparsity_grid2',
autoencoder = TRUE,
hidden = 100,
activation = 'Tanh',
hyper_params = hyper_grid,
sparse = TRUE,
ignore_const_cols = FALSE,
seed = 123
)
grid_perf <- h2o.getGrid('sparsity_grid2', sort_by = 'mse', decreasing = FALSE)
# get best models for each level of sparsity
sparse_25 <- grid_perf@model_ids[[3]] %>% h2o.getModel()
sparse_50 <- grid_perf@model_ids[[1]] %>% h2o.getModel()
sparse_75 <- grid_perf@model_ids[[2]] %>% h2o.getModel()
# get sampled test images
sampled_digits <- mnist$test$images[index, ]
# predict reconstructed pixel values
reconstructed_digits_25 <- predict(sparse_25, as.h2o(sampled_digits))
reconstructed_digits_50 <- predict(sparse_50, as.h2o(sampled_digits))
reconstructed_digits_75 <- predict(sparse_75, as.h2o(sampled_digits))
# consistent naming
names(reconstructed_digits_25) <- paste0("V", seq_len(ncol(reconstructed_digits_25)))
names(reconstructed_digits_50) <- paste0("V", seq_len(ncol(reconstructed_digits_50)))
names(reconstructed_digits_75) <- paste0("V", seq_len(ncol(reconstructed_digits_75)))
sparsity_levels <- sampled_digits %>%
rbind(as.matrix(reconstructed_digits_25)) %>%
rbind(as.matrix(reconstructed_digits_50)) %>%
rbind(as.matrix(reconstructed_digits_75))
with_sparsity <- combine %>% rbind(as.matrix(reconstructed_digits_75))
# plot
par(mfrow = c(1, 3), mar=c(0, 0.5, 2, 0.5))
layout(matrix(seq_len(nrow(with_sparsity)), 4, 3, byrow = FALSE))
for(i in seq_len(nrow(with_sparsity))) {
title <- NULL
if (i == 1) title <- "Original digits\n"
if (i == 5) title <- "Autoencoder without sparsity\n"
if (i == 9) title <- "Autoencoder with sparsity\n"
image(matrix(with_sparsity[i, ], 28, 28)[, 28:1],
xaxt="n", yaxt="n", col = gray.colors(4, rev = TRUE),
main = title)
}
Denoising autoencoders
Figure 19.8:
set.seed(123)
single_sample_index <- sample(seq_len(nrow(features)), 1)
# original feature
original <- features %>%
as.data.frame() %>%
.[single_sample_index, ]
# on-of corruption
corrupt_with_ones <- function(x) {
n_to_sample <- floor(length(x) * .3)
elements_to_corrupt <- sample(seq_along(x), n_to_sample, replace = FALSE)
x[elements_to_corrupt] <- 0
return(x)
}
inputs_currupted_ones <- features %>%
as.data.frame() %>%
purrr::map_df(corrupt_with_ones) %>%
.[single_sample_index, ]
# Gaussian corruption
avg <- mean(as.matrix(features))
sd <- sd(as.matrix(features))
corrupt_with_gaussian <- function(x, mean, sd) {
n_to_sample <- floor(length(x) * .3)
elements_to_corrupt <- sample(seq_along(x), n_to_sample, replace = FALSE)
random_norm_values <- rnorm(n_to_sample, mean, sd) %>% round()
for (i in seq_along(random_norm_values)) {
if (random_norm_values[i] < 0) random_norm_values[i] <- 0
if (random_norm_values[i] > 255) random_norm_values[i] <- 255
}
x[elements_to_corrupt] <- random_norm_values
return(x)
}
inputs_currupted_gaussian <- features %>%
as.data.frame() %>%
purrr::map_df(~ corrupt_with_gaussian(.x, avg, sd)) %>%
.[single_sample_index, ]
# combine and save for later use
corrupted_inputs <- original %>%
rbind(inputs_currupted_ones) %>%
rbind(inputs_currupted_gaussian) %>%
as.matrix()
# plot
par(mfrow = c(1, 3), mar=c(0, 0.5, 2, 0.5))
layout(matrix(seq_len(nrow(corrupted_inputs)), 1, 3, byrow = FALSE))
for(i in seq_len(nrow(corrupted_inputs))) {
if (i == 1) title <- "Original digit\n"
if (i == 2) title <- "Corrupted with on/off imputation\n"
if (i == 3) title <- "Corrupted with Gaussian imputation\n"
image(matrix(corrupted_inputs[i, ], 28, 28)[, 28:1],
xaxt="n", yaxt="n", col = gray.colors(4, rev = TRUE),
main = title)
}
inputs_currupted_gaussian <- features %>%
as.data.frame() %>%
purrr::map_df(~ corrupt_with_gaussian(.x, avg, sd)) %>%
as.h2o()
# Train a denoise autoencoder
denoise_ae <- h2o.deeplearning(
x = names(features),
training_frame = inputs_currupted_gaussian,
validation_frame = features,
autoencoder = TRUE,
hidden = 100,
activation = 'Tanh',
sparse = TRUE
)
# Print performance
h2o.performance(denoise_ae, valid = TRUE)
H2OAutoEncoderMetrics: deeplearning
** Reported on validation data. **
Validation Set Metrics:
=====================
MSE: (Extract with `h2o.mse`) 0.02005279
RMSE: (Extract with `h2o.rmse`) 0.1416079
Figure 19.9:
# get sampled test images
set.seed(8039)
denoise_index <- sample(1:nrow(features), 4, replace = FALSE)
sampled_originals <- as.matrix(features)[index, ]
sampled_corrupted <- as.matrix(inputs_currupted_gaussian)[index, ]
# predict reconstructed pixel values
reconstructed_from_corrupted <- predict(denoise_ae, as.h2o(sampled_originals))
# consistent naming
names(reconstructed_from_corrupted) <- paste0("V", seq_len(ncol(reconstructed_from_corrupted)))
corruption_comparison <- sampled_originals %>%
rbind(sampled_corrupted) %>%
rbind(as.matrix(reconstructed_from_corrupted))
# plot
par(mfrow = c(1, 3), mar = c(0, 0.5, 2, 0.5))
layout(matrix(seq_len(nrow(corruption_comparison)), 4, 3, byrow = FALSE))
for (i in seq_len(nrow(corruption_comparison))) {
title <- NULL
if (i == 1) title <- "Original digits\n"
if (i == 5) title <- "Corrupted digits\n"
if (i == 9) title <- "Reconstructed digits\n"
image(matrix(corruption_comparison[i, ], 28, 28)[, 28:1],
xaxt = "n", yaxt = "n", col = gray.colors(4, rev = TRUE),
main = title)
}
Anomaly detection
# Extract reconstruction errors
(reconstruction_errors <- h2o.anomaly(best_model, features))
[60000 rows x 1 column]
# Plot distribution
reconstruction_errors <- as.data.frame(reconstruction_errors)
ggplot(reconstruction_errors, aes(Reconstruction.MSE)) +
geom_histogram(bins = 500)
Figure 19.11:
big_error_index <- reconstruction_errors %>%
mutate(obs = row_number()) %>%
arrange(desc(Reconstruction.MSE)) %>%
top_n(5, wt = Reconstruction.MSE) %>%
pull(obs)
big_error_inputs <- as.h2o(as.data.frame(features)[big_error_index, ])
big_errors <- predict(best_model, big_error_inputs) %>%
as.matrix()
original_inputs <- as.matrix(features)[big_error_index, ]
colnames(big_errors) <- colnames(original_inputs)
original_vs_big_errors <- rbind(original_inputs, big_errors)
# plot
par(mfrow = c(5, 3), mar = c(0, 0.5, 2, 0.5))
layout(matrix(seq_len(nrow(original_vs_big_errors)), 5, 2, byrow = FALSE))
for (i in seq_len(nrow(original_vs_big_errors))) {
title <- NULL
if (i == 1) title <- "Original digits\n"
if (i == 6) title <- "Reconstructed digits\n"
image(matrix(original_vs_big_errors[i, ], 28, 28)[, 28:1],
xaxt = "n", yaxt = "n", col = gray.colors(4, rev = TRUE),
main = title)
}
h2o.shutdown(prompt = FALSE)
[1] TRUE
LS0tCnRpdGxlOiAiQ2hhcHRlciAxOTogQXV0b2VuY29kZXJzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpfX05vdGVfXzogU29tZSByZXN1bHRzIG1heSBkaWZmZXIgZnJvbSB0aGUgaGFyZCBjb3B5IGJvb2sgZHVlIHRvIHRoZSBjaGFuZ2luZyBvZgpzYW1wbGluZyBwcm9jZWR1cmVzIGludHJvZHVjZWQgaW4gUiAzLjYuMC4gU2VlIGh0dHA6Ly9iaXQubHkvMzVEMVNXNyBmb3IgbW9yZQpkZXRhaWxzLiBBY2Nlc3MgYW5kIHJ1biB0aGUgc291cmNlIGNvZGUgZm9yIHRoaXMgbm90ZWJvb2sgW2hlcmVdKGh0dHBzOi8vcnN0dWRpby5jbG91ZC9wcm9qZWN0LzgwMTE4NSkuCiBEbyB0byBvdXRwdXQgc2l6ZSwgbW9zdCBvZiB0aGlzIGNoYXB0ZXIncyBjb2RlIGNodW5rcyBzaG91bGQgbm90IGJlIHJhbiBvbgogUlN0dWRpbyBDbG91ZC4KIApIaWRkZW4gY2hhcHRlciByZXF1aXJlbWVudHMgdXNlZCBpbiB0aGUgYm9vayB0byBzZXQgdGhlIHBsb3R0aW5nIHRoZW1lIGFuZCBsb2FkCnBhY2thZ2VzIHVzZWQgaW4gaGlkZGVuIGNvZGUgY2h1bmtzOgoKYGBge3Igc2V0dXB9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBtZXNzYWdlID0gRkFMU0UsIAogIHdhcm5pbmcgPSBGQUxTRSwgCiAgY2FjaGUgPSBGQUxTRQopCgojIFNldCB0aGUgZ3JhcGhpY2FsIHRoZW1lCmdncGxvdDI6OnRoZW1lX3NldChnZ3Bsb3QyOjp0aGVtZV9saWdodCgpKQpgYGAKCiMjIFByZXJlcXVpc2l0ZXMKCkZvciB0aGlzIGNoYXB0ZXIgd2XigJlsbCB1c2UgdGhlIGZvbGxvd2luZyBwYWNrYWdlczoKCmBgYHtyIGF1dG9lbmNvZGVyLXBrZ3N9CiMgSGVscGVyIHBhY2thZ2VzCmxpYnJhcnkoZHBseXIpICAgICMgZm9yIGRhdGEgbWFuaXB1bGF0aW9uCmxpYnJhcnkoZ2dwbG90MikgICMgZm9yIGRhdGEgdmlzdWFsaXphdGlvbgoKIyBNb2RlbGluZyBwYWNrYWdlcwpsaWJyYXJ5KGgybykgICMgZm9yIGZpdHRpbmcgYXV0b2VuY29kZXJzCmBgYAoKVG8gaWxsdXN0cmF0ZSBhdXRvZW5jb2RlciBjb25jZXB0cyB3ZSdsbCBjb250aW51ZSB3aXRoIHRoZSBgbW5pc3RgIGRhdGEgc2V0OgoKYGBge3IgYXV0b2VuY29kZXItZGF0YX0KbW5pc3QgPC0gZHNsYWJzOjpyZWFkX21uaXN0KCkKbmFtZXMobW5pc3QpCmBgYAoKU2luY2Ugd2Ugd2lsbCBiZSB1c2luZyBfX2gyb19fIHdlJ2xsIGFsc28gZ28gYWhlYWQgYW5kIGluaXRpYWxpemUgb3VyIEgyTyBzZXNzaW9uOgoKYGBge3IgaDJvLXN0YXJ0dXB9Cmgyby5ub19wcm9ncmVzcygpICAjIHR1cm4gb2ZmIHByb2dyZXNzIGJhcnMKaDJvLmluaXQobWF4X21lbV9zaXplID0gIjVnIikgICMgaW5pdGlhbGl6ZSBIMk8gaW5zdGFuY2UKYGBgCgoKIyMgVW5kZXJjb21wbGV0ZSBhdXRvZW5jb2RlcnMKCkZpZ3VyZSAxOS4xOgoKYGBge3IgdW5kZXJjb21wbGV0ZS1hcmNoaXRlY3R1cmUsIGZpZy5jYXA9IlNjaGVtYXRpYyBzdHJ1Y3R1cmUgb2YgYW4gdW5kZXJjb21wbGV0ZSBhdXRvZW5jb2RlciB3aXRoIHRocmVlIGZ1bGx5IGNvbm5lY3RlZCBoaWRkZW4gbGF5ZXJzIFxcY2l0ZXB7d2lraWF1dG9lbmNvZGVyc30uIiwgb3V0LmhlaWdodD0nNzUlJywgb3V0LndpZHRoPSc3NSUnfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaW1hZ2VzL0F1dG9lbmNvZGVyX3N0cnVjdHVyZS5wbmciKQpgYGAKCiMjIyBDb21wYXJpbmcgUENBIHRvIGFuIGF1dG9lbmNvZGVyCgpgYGB7ciBhdXRvZW5jb2RlcjF9CiMgQ29udmVydCBtbmlzdCBmZWF0dXJlcyB0byBhbiBoMm8gaW5wdXQgZGF0YSBzZXQKZmVhdHVyZXMgPC0gYXMuaDJvKG1uaXN0JHRyYWluJGltYWdlcykKCiMgVHJhaW4gYW4gYXV0b2VuY29kZXIKYWUxIDwtIGgyby5kZWVwbGVhcm5pbmcoCiAgeCA9IHNlcV9hbG9uZyhmZWF0dXJlcyksCiAgdHJhaW5pbmdfZnJhbWUgPSBmZWF0dXJlcywKICBhdXRvZW5jb2RlciA9IFRSVUUsCiAgaGlkZGVuID0gMiwKICBhY3RpdmF0aW9uID0gJ1RhbmgnLAogIHNwYXJzZSA9IFRSVUUKKQoKIyBFeHRyYWN0IHRoZSBkZWVwIGZlYXR1cmVzCmFlMV9jb2RpbmdzIDwtIGgyby5kZWVwZmVhdHVyZXMoYWUxLCBmZWF0dXJlcywgbGF5ZXIgPSAxKQphZTFfY29kaW5ncwpgYGAKCkZpZ3VyZSAxOS4yOgoKYGBge3IgcGNhLWF1dG9lbmNvZGVyLXByb2plY3Rpb24sIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTExLCBmaWcuY2FwPSJNTklTVCByZXNwb25zZSB2YXJpYWJsZSBwcm9qZWN0ZWQgb250byBhIHJlZHVjZWQgZmVhdHVyZSBzcGFjZSBjb250YWluaW4gb25seSB0d28gZGltZW5zaW9ucy4gUENBIChsZWZ0KSBmb3JjZXMgYSBsaW5lYXIgcHJvamVjdGlvbiB3aGVyZWFzIGFuIGF1dG9lbmNvZGVyIHdpdGggbm9uLWxpbmVhciBhY3RpdmF0aW9uIGZ1bmN0aW9ucyBhbGxvd3Mgbm9uLWxpbmVhciBwcm9qZWN0LiJ9CmZlYXR1cmVzIDwtIGFzLmgybyhtbmlzdCR0cmFpbiRpbWFnZXMpCgojIFBlcmZvcm0gUENBCnBjYTEgPC0gaDJvLnByY29tcCgKICB0cmFpbmluZ19mcmFtZSA9IGZlYXR1cmVzLAogIGsgPSAyLAogIHRyYW5zZm9ybSA9ICdTVEFOREFSRElaRScKKQoKIyBQbG90IHJlc3VsdGluZyBQQ3MKcGNhX3Bsb3QgPC0gcHJlZGljdChwY2ExLCBmZWF0dXJlcykgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHNlbGVjdChQQzEsIFBDMikgJT4lCiAgbXV0YXRlKHJlc3BvbnNlID0gZmFjdG9yKG1uaXN0JHRyYWluJGxhYmVscykpICU+JQogIGdncGxvdChhZXMoUEMxLCBQQzIpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSByZXNwb25zZSksIHNpemUgPSAwLjUsIGFscGhhID0gMC4yNSkgKwogIGdndGl0bGUoJyhBKSBQQ0EgcHJvamVjdGlvbicpCgojIFBsb3QgcmVzdWx0cyBmcm9tIGF1dG9lbmNvZGVyCmFlX3Bsb3QgPC0gYWUxX2NvZGluZ3MgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHNlbGVjdChERjEgPSAnREYuTDEuQzEnLCBERjIgPSAnREYuTDEuQzInKSAlPiUKICBtdXRhdGUocmVzcG9uc2UgPSBmYWN0b3IobW5pc3QkdHJhaW4kbGFiZWxzKSkgJT4lCiAgZ2dwbG90KGFlcyhERjEsIERGMiwgY29sb3IgPSByZXNwb25zZSkpICsKICBnZW9tX3BvaW50KHNpemUgPSAuNSwgYWxwaGEgPSAuMjUpICsKICBnZ3RpdGxlKCcoQikgQXV0b2VuY29kZXIgcHJvamVjdGlvbicpCgojIERJc3BsYXkgcGxvdHMgc2lkZSBieSBzaWRlCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKHBjYV9wbG90LCBhZV9wbG90LCBucm93ID0gMSkKYGBgCgojIyMgU3RhY2tlZCBhdXRvZW5jb2RlcnMKCmBgYHtyIGF1dG9lbmNvZGVyc3ltbWV0cnksIG91dC5oZWlnaHQ9Ijk4JSIsIG91dC53aWR0aD0iOTglIiwgZmlnLmNhcD0iQXMgeW91IGFkZCBoaWRkZW4gbGF5ZXJzIHRvIGF1dG9lbmNvZGVycywgaXQgaXMgY29tbW9uIHByYWN0aWNlIHRvIGhhdmUgc3ltbWV0cmljIGhpZGRlbiBsYXllciBzaXplcyBiZXR3ZWVuIHRoZSBlbmNvZGVyIGFuZCBkZWNvZGVyIGxheWVycy4ifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygnaW1hZ2VzL2F1dG9lbmNvZGVyLXN5bW1ldHJ5LnBuZycpCmBgYAoKYGBge3IgYXV0b2VuY29kZXItZ3JpZC1zZWFyY2h9CiMgSHlwZXJwYXJhbWV0ZXIgc2VhcmNoIGdyaWQKaHlwZXJfZ3JpZCA8LSBsaXN0KGhpZGRlbiA9IGxpc3QoCiAgYyg1MCksCiAgYygxMDApLCAKICBjKDMwMCwgMTAwLCAzMDApLAogIGMoMTAwLCA1MCwgMTAwKSwKICBjKDI1MCwgMTAwLCA1MCwgMTAwLCAyNTApCikpCgojIEV4ZWN1dGUgZ3JpZCBzZWFyY2gKYWVfZ3JpZCA8LSBoMm8uZ3JpZCgKICBhbGdvcml0aG0gPSAnZGVlcGxlYXJuaW5nJywKICB4ID0gc2VxX2Fsb25nKGZlYXR1cmVzKSwKICB0cmFpbmluZ19mcmFtZSA9IGZlYXR1cmVzLAogIGdyaWRfaWQgPSAnYXV0b2VuY29kZXJfZ3JpZCcsCiAgYXV0b2VuY29kZXIgPSBUUlVFLAogIGFjdGl2YXRpb24gPSAnVGFuaCcsCiAgaHlwZXJfcGFyYW1zID0gaHlwZXJfZ3JpZCwKICBzcGFyc2UgPSBUUlVFLAogIGlnbm9yZV9jb25zdF9jb2xzID0gRkFMU0UsCiAgc2VlZCA9IDEyMwopCgojIFByaW50IGdyaWQgZGV0YWlscwpoMm8uZ2V0R3JpZCgnYXV0b2VuY29kZXJfZ3JpZCcsIHNvcnRfYnkgPSAnbXNlJywgZGVjcmVhc2luZyA9IEZBTFNFKQpgYGAKCgojIyMgVmlzdWFsaXppbmcgdGhlIHJlY29uc3RydWN0aW9uCgpgYGB7ciByZWNvbnN0cnVjdGVkLWltYWdlcywgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9IDUsIGZpZy5jYXA9J09yaWdpbmFsIGRpZ2l0cyAobGVmdCkgYW5kIHRoZWlyIHJlY29uc3RydWN0aW9ucyAocmlnaHQpLid9CiMgR2V0IHNhbXBsZWQgdGVzdCBpbWFnZXMKaW5kZXggPC0gc2FtcGxlKDE6bnJvdyhtbmlzdCR0ZXN0JGltYWdlcyksIDQpCnNhbXBsZWRfZGlnaXRzIDwtIG1uaXN0JHRlc3QkaW1hZ2VzW2luZGV4LCBdCmNvbG5hbWVzKHNhbXBsZWRfZGlnaXRzKSA8LSBwYXN0ZTAoIlYiLCBzZXFfbGVuKG5jb2woc2FtcGxlZF9kaWdpdHMpKSkKCiMgUHJlZGljdCByZWNvbnN0cnVjdGVkIHBpeGVsIHZhbHVlcwpiZXN0X21vZGVsX2lkIDwtIGFlX2dyaWRAbW9kZWxfaWRzW1sxXV0KYmVzdF9tb2RlbCA8LSBoMm8uZ2V0TW9kZWwoYmVzdF9tb2RlbF9pZCkKcmVjb25zdHJ1Y3RlZF9kaWdpdHMgPC0gcHJlZGljdChiZXN0X21vZGVsLCBhcy5oMm8oc2FtcGxlZF9kaWdpdHMpKQpuYW1lcyhyZWNvbnN0cnVjdGVkX2RpZ2l0cykgPC0gcGFzdGUwKCJWIiwgc2VxX2xlbihuY29sKHJlY29uc3RydWN0ZWRfZGlnaXRzKSkpCgpjb21iaW5lIDwtIHJiaW5kKHNhbXBsZWRfZGlnaXRzLCBhcy5tYXRyaXgocmVjb25zdHJ1Y3RlZF9kaWdpdHMpKQoKIyBQbG90IG9yaWdpbmFsIHZlcnN1cyByZWNvbnN0cnVjdGVkCnBhcihtZnJvdyA9IGMoMSwgMyksIG1hciA9IGMoMCwgMC41LCAyLCAwLjUpKQpsYXlvdXQobWF0cml4KHNlcV9sZW4obnJvdyhjb21iaW5lKSksIDQsIDIsIGJ5cm93ID0gRkFMU0UpKQpmb3IgKGkgaW4gc2VxX2xlbihucm93KGNvbWJpbmUpKSkgewogIHRpdGxlIDwtIHN3aXRjaChhcy5jaGFyYWN0ZXIoaSksICIxIiA9ICJPcmlnaW5hbCBkaWdpdHNcbiIsIAogICAgICAgICAgICAgICAgICAiNSIgPSAiQXV0b2VuY29kZXIgcmVjb25zdHJ1Y3Rpb25cbiIsIE5VTEwpCiAgaW1hZ2UobWF0cml4KGNvbWJpbmVbaSwgXSwgMjgsIDI4KVssIDI4OjFdLCAKICAgICAgICB4YXh0ID0gIm4iLCB5YXh0ID0gIm4iLCBjb2wgPSBncmF5LmNvbG9ycygxMiwgcmV2ID0gVFJVRSksCiAgICAgICAgbWFpbiA9IHRpdGxlKQp9IApgYGAKCiMjIFNwYXJzZSBhdXRvZW5jb2RlcnMKCmBgYHtyIGN1cnJlbnQtc3BhcnNpdHl9CmFlMTAwX2NvZGluZ3MgPC0gaDJvLmRlZXBmZWF0dXJlcyhiZXN0X21vZGVsLCBmZWF0dXJlcywgbGF5ZXIgPSAxKQphZTEwMF9jb2RpbmdzICU+JSAKICAgIGFzLmRhdGEuZnJhbWUoKSAlPiUgCiAgICB0aWR5cjo6Z2F0aGVyKCkgJT4lCiAgICBzdW1tYXJpemUoYXZlcmFnZV9hY3RpdmF0aW9uID0gbWVhbih2YWx1ZSkpCmBgYAoKRmlndXJlIDE5LjU6CgpgYGB7ciBhdmVyYWdlLWFjdGl2YXRpb24sIGZpZy5oZWlnaHQ9NiwgZmlnLmNhcD0iVGhlIGF2ZXJhZ2UgYWN0aXZhdGlvbiBvZiB0aGUgY29kaW5nIG5ldXJvbnMgaW4gb3VyIGRlZmF1bHQgYXV0b2VuY29kZXIgdXNpbmcgYSBUYW5oIGFjdGl2YXRpb24gZnVuY3Rpb24uIn0KY29kaW5ncyA8LSBhZTEwMF9jb2RpbmdzICU+JSAKICBhcy5kYXRhLmZyYW1lKCkgJT4lIAogIHRpZHlyOjpnYXRoZXIoKSAlPiUKICBtdXRhdGUoa2V5ID0gc3RyaW5ncjo6c3RyX3JlcGxhY2Uoa2V5LCAnREYuTDEuJywgJycpKSAlPiUKICBncm91cF9ieShrZXkpICU+JSAKICBzdW1tYXJpemUoYXZlcmFnZV9hY3RpdmF0aW9uID0gbWVhbih2YWx1ZSkpICU+JSAKICBhcnJhbmdlKGRlc2MoYXZlcmFnZV9hY3RpdmF0aW9uKSkKYXZnX2FjdGl2YXRpb24gPC0gc3VtbWFyaXplKGNvZGluZ3MsIGF2ZyA9IG1lYW4oYXZlcmFnZV9hY3RpdmF0aW9uKSkKCmdncGxvdChjb2RpbmdzLCBhZXMoYXZlcmFnZV9hY3RpdmF0aW9uLCByZW9yZGVyKGtleSwgYXZlcmFnZV9hY3RpdmF0aW9uKSwgCiAgICAgICAgICAgICAgICAgICAgY29sb3IgPSBhdmVyYWdlX2FjdGl2YXRpb24gPiBhdmdfYWN0aXZhdGlvbiRhdmcpKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gYXZnX2FjdGl2YXRpb24kYXZnLCBsdHkgPSAnZGFzaGVkJykgKwogIGdlb21fcG9pbnQoc2hvdy5sZWdlbmQgPSBGQUxTRSwgc2l6ZSA9IC43NSkgKwogIHlsYWIoIkRlZXAgZmVhdHVyZSBjb2RpbmdzIikgKwogIHhsYWIoIkF2ZXJhZ2UgYWN0aXZhdGlvbiIpICsKICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChzaXplID0gMykpCmBgYAoKYGBge3Igc3BhcnNpdHktZ3JpZC1zZWFyY2h9CiMgSHlwZXJwYXJhbWV0ZXIgc2VhcmNoIGdyaWQKaHlwZXJfZ3JpZCA8LSBsaXN0KHNwYXJzaXR5X2JldGEgPSBjKDAuMDEsIDAuMDUsIDAuMSwgMC4yKSkKCiMgRXhlY3V0ZSBncmlkIHNlYXJjaAphZV9zcGFyc2l0eV9ncmlkIDwtIGgyby5ncmlkKAogIGFsZ29yaXRobSA9ICdkZWVwbGVhcm5pbmcnLAogIHggPSBzZXFfYWxvbmcoZmVhdHVyZXMpLAogIHRyYWluaW5nX2ZyYW1lID0gZmVhdHVyZXMsCiAgZ3JpZF9pZCA9ICdzcGFyc2l0eV9ncmlkJywKICBhdXRvZW5jb2RlciA9IFRSVUUsCiAgaGlkZGVuID0gMTAwLAogIGFjdGl2YXRpb24gPSAnVGFuaCcsCiAgaHlwZXJfcGFyYW1zID0gaHlwZXJfZ3JpZCwKICBzcGFyc2UgPSBUUlVFLAogIGF2ZXJhZ2VfYWN0aXZhdGlvbiA9IC0wLjEsCiAgaWdub3JlX2NvbnN0X2NvbHMgPSBGQUxTRSwKICBzZWVkID0gMTIzCikKCiMgUHJpbnQgZ3JpZCBkZXRhaWxzCmgyby5nZXRHcmlkKCdzcGFyc2l0eV9ncmlkJywgc29ydF9ieSA9ICdtc2UnLCBkZWNyZWFzaW5nID0gRkFMU0UpCmBgYAoKRmlndXJlIDE5LjY6IAoKYGBge3Igc3BhcnNlLWNvZGluZ3MtaW1hZ2VzLXBsb3QsIGZpZy5oZWlnaHQ9NiwgZmlnLmNhcD0iVGhlIGF2ZXJhZ2UgYWN0aXZhdGlvbiBvZiB0aGUgY29kaW5nIG5ldXJvbnMgaW4gb3VyIHNwYXJzZSBhdXRvZW5jb2RlciBpcyBub3cgLTAuMTA4LiJ9CmJlc3Rfc3BhcnNlX21vZGVsIDwtIGFlX3NwYXJzaXR5X2dyaWRAbW9kZWxfaWRzW1sxXV0gJT4lCiAgaDJvLmdldE1vZGVsKCkKc3BhcnNlX2NvZGluZ3MgPC0gaDJvLmRlZXBmZWF0dXJlcyhiZXN0X3NwYXJzZV9tb2RlbCwgZmVhdHVyZXMsIGxheWVyID0gMSkKc3BhcnNlX2NvZGluZ3MgPC0gc3BhcnNlX2NvZGluZ3MgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHRpZHlyOjpnYXRoZXIoKSAlPiUKICBtdXRhdGUoa2V5ID0gc3RyaW5ncjo6c3RyX3JlcGxhY2Uoa2V5LCAnREYuTDEuJywgJycpKSAlPiUKICBncm91cF9ieShrZXkpICU+JQogIHN1bW1hcml6ZShhdmVyYWdlX2FjdGl2YXRpb24gPSBtZWFuKHZhbHVlKSkgJT4lCiAgYXJyYW5nZShkZXNjKGF2ZXJhZ2VfYWN0aXZhdGlvbikpCgphdmdfYWN0aXZhdGlvbiA8LSBzdW1tYXJpemUoc3BhcnNlX2NvZGluZ3MsIGF2ZyA9IG1lYW4oYXZlcmFnZV9hY3RpdmF0aW9uKSkKCmdncGxvdChzcGFyc2VfY29kaW5ncywgYWVzKGF2ZXJhZ2VfYWN0aXZhdGlvbiwgcmVvcmRlcihrZXksIGF2ZXJhZ2VfYWN0aXZhdGlvbiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvciA9IGF2ZXJhZ2VfYWN0aXZhdGlvbiA+IGF2Z19hY3RpdmF0aW9uJGF2ZykpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBhdmdfYWN0aXZhdGlvbiRhdmcsIGx0eSA9ICdkYXNoZWQnKSArCiAgZ2VvbV9wb2ludChzaG93LmxlZ2VuZCA9IEZBTFNFLCBzaXplID0gLjc1KSArCiAgeWxhYigiRGVlcCBmZWF0dXJlIGNvZGluZ3MiKSArCiAgeGxhYigiQXZlcmFnZSBhY3RpdmF0aW9uIikgKwogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSAzKSkKYGBgCgpGaWd1cmUgMTkuNzoKCmBgYHtyIHBsb3Qtc3BhcnNpdHktY29tcGFyaXNvbnMsIGZpZy5oZWlnaHQ9NiwgZmlnLmNhcD0nT3JpZ2luYWwgZGlnaXRzIHNhbXBsZWQgZnJvbSB0aGUgTU5JU1QgdGVzdCBzZXQgKGxlZnQpLCByZWNvbnN0cnVjdGlvbiBvZiBzYW1wbGVkIGRpZ2l0cyB3aXRoIGEgbm9uLXNwYXJzZSBhdXRvZW5jb2RlciAobWlkZGxlKSwgYW5kIHJlY29uc3RydWN0aW9uIHdpdGggYSBzcGFyc2UgYXV0b2VuY29kZXIgKHJpZ2h0KS4nfSAKIyBIeXBlcnBhcmFtZXRlciBzZWFyY2ggZ3JpZApoeXBlcl9ncmlkIDwtIGxpc3QoCiAgc3BhcnNpdHlfYmV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xLCAwLjIpLAogIGF2ZXJhZ2VfYWN0aXZhdGlvbiA9IGMoLTAuNzUsIC0wLjUsIC0wLjI1KQopCgojIEV4ZWN1dGUgZ3JpZCBzZWFyY2gKYWVfc3BhcnNpdHlfZ3JpZDIgPC0gaDJvLmdyaWQoCiAgYWxnb3JpdGhtID0gJ2RlZXBsZWFybmluZycsCiAgeCA9IHNlcV9hbG9uZyhmZWF0dXJlcyksCiAgdHJhaW5pbmdfZnJhbWUgPSBmZWF0dXJlcywKICBncmlkX2lkID0gJ3NwYXJzaXR5X2dyaWQyJywKICBhdXRvZW5jb2RlciA9IFRSVUUsCiAgaGlkZGVuID0gMTAwLAogIGFjdGl2YXRpb24gPSAnVGFuaCcsCiAgaHlwZXJfcGFyYW1zID0gaHlwZXJfZ3JpZCwKICBzcGFyc2UgPSBUUlVFLAogIGlnbm9yZV9jb25zdF9jb2xzID0gRkFMU0UsCiAgc2VlZCA9IDEyMwopCgpncmlkX3BlcmYgPC0gaDJvLmdldEdyaWQoJ3NwYXJzaXR5X2dyaWQyJywgc29ydF9ieSA9ICdtc2UnLCBkZWNyZWFzaW5nID0gRkFMU0UpCgojIGdldCBiZXN0IG1vZGVscyBmb3IgZWFjaCBsZXZlbCBvZiBzcGFyc2l0eQpzcGFyc2VfMjUgPC0gZ3JpZF9wZXJmQG1vZGVsX2lkc1tbM11dICU+JSBoMm8uZ2V0TW9kZWwoKQpzcGFyc2VfNTAgPC0gZ3JpZF9wZXJmQG1vZGVsX2lkc1tbMV1dICU+JSBoMm8uZ2V0TW9kZWwoKQpzcGFyc2VfNzUgPC0gZ3JpZF9wZXJmQG1vZGVsX2lkc1tbMl1dICU+JSBoMm8uZ2V0TW9kZWwoKQoKIyBnZXQgc2FtcGxlZCB0ZXN0IGltYWdlcwpzYW1wbGVkX2RpZ2l0cyA8LSBtbmlzdCR0ZXN0JGltYWdlc1tpbmRleCwgXQoKIyBwcmVkaWN0IHJlY29uc3RydWN0ZWQgcGl4ZWwgdmFsdWVzCnJlY29uc3RydWN0ZWRfZGlnaXRzXzI1IDwtIHByZWRpY3Qoc3BhcnNlXzI1LCBhcy5oMm8oc2FtcGxlZF9kaWdpdHMpKQpyZWNvbnN0cnVjdGVkX2RpZ2l0c181MCA8LSBwcmVkaWN0KHNwYXJzZV81MCwgYXMuaDJvKHNhbXBsZWRfZGlnaXRzKSkKcmVjb25zdHJ1Y3RlZF9kaWdpdHNfNzUgPC0gcHJlZGljdChzcGFyc2VfNzUsIGFzLmgybyhzYW1wbGVkX2RpZ2l0cykpCgojIGNvbnNpc3RlbnQgbmFtaW5nCm5hbWVzKHJlY29uc3RydWN0ZWRfZGlnaXRzXzI1KSA8LSBwYXN0ZTAoIlYiLCBzZXFfbGVuKG5jb2wocmVjb25zdHJ1Y3RlZF9kaWdpdHNfMjUpKSkKbmFtZXMocmVjb25zdHJ1Y3RlZF9kaWdpdHNfNTApIDwtIHBhc3RlMCgiViIsIHNlcV9sZW4obmNvbChyZWNvbnN0cnVjdGVkX2RpZ2l0c181MCkpKQpuYW1lcyhyZWNvbnN0cnVjdGVkX2RpZ2l0c183NSkgPC0gcGFzdGUwKCJWIiwgc2VxX2xlbihuY29sKHJlY29uc3RydWN0ZWRfZGlnaXRzXzc1KSkpCgpzcGFyc2l0eV9sZXZlbHMgPC0gc2FtcGxlZF9kaWdpdHMgJT4lCiAgcmJpbmQoYXMubWF0cml4KHJlY29uc3RydWN0ZWRfZGlnaXRzXzI1KSkgJT4lCiAgcmJpbmQoYXMubWF0cml4KHJlY29uc3RydWN0ZWRfZGlnaXRzXzUwKSkgJT4lCiAgcmJpbmQoYXMubWF0cml4KHJlY29uc3RydWN0ZWRfZGlnaXRzXzc1KSkKCndpdGhfc3BhcnNpdHkgPC0gY29tYmluZSAlPiUgcmJpbmQoYXMubWF0cml4KHJlY29uc3RydWN0ZWRfZGlnaXRzXzc1KSkKCiMgcGxvdCAKcGFyKG1mcm93ID0gYygxLCAzKSwgbWFyPWMoMCwgMC41LCAyLCAwLjUpKQpsYXlvdXQobWF0cml4KHNlcV9sZW4obnJvdyh3aXRoX3NwYXJzaXR5KSksIDQsIDMsIGJ5cm93ID0gRkFMU0UpKQpmb3IoaSBpbiBzZXFfbGVuKG5yb3cod2l0aF9zcGFyc2l0eSkpKSB7CiAgdGl0bGUgPC0gTlVMTAogIGlmIChpID09IDEpIHRpdGxlIDwtICJPcmlnaW5hbCBkaWdpdHNcbiIKICBpZiAoaSA9PSA1KSB0aXRsZSA8LSAiQXV0b2VuY29kZXIgd2l0aG91dCBzcGFyc2l0eVxuIgogIGlmIChpID09IDkpIHRpdGxlIDwtICJBdXRvZW5jb2RlciB3aXRoIHNwYXJzaXR5XG4iCiAgCiAgaW1hZ2UobWF0cml4KHdpdGhfc3BhcnNpdHlbaSwgXSwgMjgsIDI4KVssIDI4OjFdLCAKICAgICAgICB4YXh0PSJuIiwgeWF4dD0ibiIsIGNvbCA9IGdyYXkuY29sb3JzKDQsIHJldiA9IFRSVUUpLAogICAgICAgIG1haW4gPSB0aXRsZSkKfSAgCmBgYAoKIyMgRGVub2lzaW5nIGF1dG9lbmNvZGVycwoKRmlndXJlIDE5Ljg6CgpgYGB7ciBwbG90LWNvcnJ1cHRlZC1pbnB1dHMsIGZpZy5oZWlnaHQ9Mi41LCBmaWcuY2FwPSdPcmlnaW5hbCBkaWdpdCBzYW1wbGVkIGZyb20gdGhlIE1OSVNUIHRlc3Qgc2V0IChsZWZ0KSwgY29ycnVwdGVkIGRhdGEgd2l0aCBvbi9vZmYgaW1wdXRhdGlvbiAobWlkZGxlKSwgYW5kIGNvcnJ1cHRlZCBkYXRhIHdpdGggR2F1c3NpYW4gaW1wdXRhdGlvbiAocmlnaHQpLid9CnNldC5zZWVkKDEyMykKc2luZ2xlX3NhbXBsZV9pbmRleCA8LSBzYW1wbGUoc2VxX2xlbihucm93KGZlYXR1cmVzKSksIDEpCgojIG9yaWdpbmFsIGZlYXR1cmUKb3JpZ2luYWwgPC0gZmVhdHVyZXMgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIC5bc2luZ2xlX3NhbXBsZV9pbmRleCwgXQoKIyBvbi1vZiBjb3JydXB0aW9uCmNvcnJ1cHRfd2l0aF9vbmVzIDwtIGZ1bmN0aW9uKHgpIHsKICBuX3RvX3NhbXBsZSA8LSBmbG9vcihsZW5ndGgoeCkgKiAuMykKICBlbGVtZW50c190b19jb3JydXB0IDwtIHNhbXBsZShzZXFfYWxvbmcoeCksIG5fdG9fc2FtcGxlLCByZXBsYWNlID0gRkFMU0UpCiAgeFtlbGVtZW50c190b19jb3JydXB0XSA8LSAwCiAgcmV0dXJuKHgpCn0KCmlucHV0c19jdXJydXB0ZWRfb25lcyA8LSBmZWF0dXJlcyAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgcHVycnI6Om1hcF9kZihjb3JydXB0X3dpdGhfb25lcykgJT4lCiAgLltzaW5nbGVfc2FtcGxlX2luZGV4LCBdCgojIEdhdXNzaWFuIGNvcnJ1cHRpb24KYXZnIDwtIG1lYW4oYXMubWF0cml4KGZlYXR1cmVzKSkKc2QgPC0gc2QoYXMubWF0cml4KGZlYXR1cmVzKSkKY29ycnVwdF93aXRoX2dhdXNzaWFuIDwtIGZ1bmN0aW9uKHgsIG1lYW4sIHNkKSB7CiAgbl90b19zYW1wbGUgPC0gZmxvb3IobGVuZ3RoKHgpICogLjMpCiAgZWxlbWVudHNfdG9fY29ycnVwdCA8LSBzYW1wbGUoc2VxX2Fsb25nKHgpLCBuX3RvX3NhbXBsZSwgcmVwbGFjZSA9IEZBTFNFKQogIHJhbmRvbV9ub3JtX3ZhbHVlcyA8LSBybm9ybShuX3RvX3NhbXBsZSwgbWVhbiwgc2QpICU+JSByb3VuZCgpCiAgZm9yIChpIGluIHNlcV9hbG9uZyhyYW5kb21fbm9ybV92YWx1ZXMpKSB7CiAgICBpZiAocmFuZG9tX25vcm1fdmFsdWVzW2ldIDwgMCkgcmFuZG9tX25vcm1fdmFsdWVzW2ldIDwtIDAKICAgIGlmIChyYW5kb21fbm9ybV92YWx1ZXNbaV0gPiAyNTUpIHJhbmRvbV9ub3JtX3ZhbHVlc1tpXSA8LSAyNTUKICB9CiAgCiAgeFtlbGVtZW50c190b19jb3JydXB0XSA8LSByYW5kb21fbm9ybV92YWx1ZXMKICByZXR1cm4oeCkKfQoKaW5wdXRzX2N1cnJ1cHRlZF9nYXVzc2lhbiA8LSBmZWF0dXJlcyAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgcHVycnI6Om1hcF9kZih+IGNvcnJ1cHRfd2l0aF9nYXVzc2lhbigueCwgYXZnLCBzZCkpICU+JQogIC5bc2luZ2xlX3NhbXBsZV9pbmRleCwgXQoKIyBjb21iaW5lIGFuZCBzYXZlIGZvciBsYXRlciB1c2UKY29ycnVwdGVkX2lucHV0cyA8LSBvcmlnaW5hbCAlPiUKICByYmluZChpbnB1dHNfY3VycnVwdGVkX29uZXMpICU+JQogIHJiaW5kKGlucHV0c19jdXJydXB0ZWRfZ2F1c3NpYW4pICU+JQogIGFzLm1hdHJpeCgpCgojIHBsb3QgCnBhcihtZnJvdyA9IGMoMSwgMyksIG1hcj1jKDAsIDAuNSwgMiwgMC41KSkKbGF5b3V0KG1hdHJpeChzZXFfbGVuKG5yb3coY29ycnVwdGVkX2lucHV0cykpLCAxLCAzLCBieXJvdyA9IEZBTFNFKSkKZm9yKGkgaW4gc2VxX2xlbihucm93KGNvcnJ1cHRlZF9pbnB1dHMpKSkgewogIGlmIChpID09IDEpIHRpdGxlIDwtICJPcmlnaW5hbCBkaWdpdFxuIgogIGlmIChpID09IDIpIHRpdGxlIDwtICJDb3JydXB0ZWQgd2l0aCBvbi9vZmYgaW1wdXRhdGlvblxuIgogIGlmIChpID09IDMpIHRpdGxlIDwtICJDb3JydXB0ZWQgd2l0aCBHYXVzc2lhbiBpbXB1dGF0aW9uXG4iCiAgCiAgaW1hZ2UobWF0cml4KGNvcnJ1cHRlZF9pbnB1dHNbaSwgXSwgMjgsIDI4KVssIDI4OjFdLCAKICAgICAgICB4YXh0PSJuIiwgeWF4dD0ibiIsIGNvbCA9IGdyYXkuY29sb3JzKDQsIHJldiA9IFRSVUUpLAogICAgICAgIG1haW4gPSB0aXRsZSkKfSAgCmBgYAoKYGBge3IgbW9kZWwtZGVub2lzaW5nLWF1dG9lbmNvZGVyfQppbnB1dHNfY3VycnVwdGVkX2dhdXNzaWFuIDwtIGZlYXR1cmVzICU+JQogIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBwdXJycjo6bWFwX2RmKH4gY29ycnVwdF93aXRoX2dhdXNzaWFuKC54LCBhdmcsIHNkKSkgJT4lCiAgYXMuaDJvKCkKCiMgVHJhaW4gYSBkZW5vaXNlIGF1dG9lbmNvZGVyCmRlbm9pc2VfYWUgPC0gaDJvLmRlZXBsZWFybmluZygKICB4ID0gbmFtZXMoZmVhdHVyZXMpLAogIHRyYWluaW5nX2ZyYW1lID0gaW5wdXRzX2N1cnJ1cHRlZF9nYXVzc2lhbiwKICB2YWxpZGF0aW9uX2ZyYW1lID0gZmVhdHVyZXMsCiAgYXV0b2VuY29kZXIgPSBUUlVFLAogIGhpZGRlbiA9IDEwMCwKICBhY3RpdmF0aW9uID0gJ1RhbmgnLAogIHNwYXJzZSA9IFRSVUUKKQoKIyBQcmludCBwZXJmb3JtYW5jZQpoMm8ucGVyZm9ybWFuY2UoZGVub2lzZV9hZSwgdmFsaWQgPSBUUlVFKQpgYGAKCkZpZ3VyZSAxOS45OgoKYGBge3IgcGxvdC1kZW5vaXNlLXJlc3VsdHMsIGZpZy5oZWlnaHQ9NiwgZmlnLmNhcD0nT3JpZ2luYWwgZGlnaXRzIHNhbXBsZWQgZnJvbSB0aGUgTU5JU1QgdGVzdCBzZXQgKGxlZnQpLCBjb3JydXB0ZWQgaW5wdXQgZGlnaXRzIChtaWRkbGUpLCBhbmQgcmVjb25zdHJ1Y3RlZCBvdXRwdXRzIChyaWdodCkuJ30gCiMgZ2V0IHNhbXBsZWQgdGVzdCBpbWFnZXMKc2V0LnNlZWQoODAzOSkKZGVub2lzZV9pbmRleCA8LSBzYW1wbGUoMTpucm93KGZlYXR1cmVzKSwgNCwgcmVwbGFjZSA9IEZBTFNFKQpzYW1wbGVkX29yaWdpbmFscyA8LSBhcy5tYXRyaXgoZmVhdHVyZXMpW2luZGV4LCBdCnNhbXBsZWRfY29ycnVwdGVkIDwtIGFzLm1hdHJpeChpbnB1dHNfY3VycnVwdGVkX2dhdXNzaWFuKVtpbmRleCwgXQoKIyBwcmVkaWN0IHJlY29uc3RydWN0ZWQgcGl4ZWwgdmFsdWVzCnJlY29uc3RydWN0ZWRfZnJvbV9jb3JydXB0ZWQgPC0gcHJlZGljdChkZW5vaXNlX2FlLCBhcy5oMm8oc2FtcGxlZF9vcmlnaW5hbHMpKQoKIyBjb25zaXN0ZW50IG5hbWluZwpuYW1lcyhyZWNvbnN0cnVjdGVkX2Zyb21fY29ycnVwdGVkKSA8LSBwYXN0ZTAoIlYiLCBzZXFfbGVuKG5jb2wocmVjb25zdHJ1Y3RlZF9mcm9tX2NvcnJ1cHRlZCkpKQoKY29ycnVwdGlvbl9jb21wYXJpc29uIDwtIHNhbXBsZWRfb3JpZ2luYWxzICU+JQogIHJiaW5kKHNhbXBsZWRfY29ycnVwdGVkKSAlPiUKICByYmluZChhcy5tYXRyaXgocmVjb25zdHJ1Y3RlZF9mcm9tX2NvcnJ1cHRlZCkpCgojIHBsb3QgCnBhcihtZnJvdyA9IGMoMSwgMyksIG1hciA9IGMoMCwgMC41LCAyLCAwLjUpKQpsYXlvdXQobWF0cml4KHNlcV9sZW4obnJvdyhjb3JydXB0aW9uX2NvbXBhcmlzb24pKSwgNCwgMywgYnlyb3cgPSBGQUxTRSkpCmZvciAoaSBpbiBzZXFfbGVuKG5yb3coY29ycnVwdGlvbl9jb21wYXJpc29uKSkpIHsKICB0aXRsZSA8LSBOVUxMCiAgaWYgKGkgPT0gMSkgdGl0bGUgPC0gIk9yaWdpbmFsIGRpZ2l0c1xuIgogIGlmIChpID09IDUpIHRpdGxlIDwtICJDb3JydXB0ZWQgZGlnaXRzXG4iCiAgaWYgKGkgPT0gOSkgdGl0bGUgPC0gIlJlY29uc3RydWN0ZWQgZGlnaXRzXG4iCiAgCiAgaW1hZ2UobWF0cml4KGNvcnJ1cHRpb25fY29tcGFyaXNvbltpLCBdLCAyOCwgMjgpWywgMjg6MV0sIAogICAgICAgIHhheHQgPSAibiIsIHlheHQgPSAibiIsIGNvbCA9IGdyYXkuY29sb3JzKDQsIHJldiA9IFRSVUUpLAogICAgICAgIG1haW4gPSB0aXRsZSkKfSAgCmBgYAoKIyMgQW5vbWFseSBkZXRlY3Rpb24KCmBgYHtyIGFub21hbHktZGV0ZWN0aW9uLCBmaWcud2lkdGg9NSwgZmlnLmhlaWdodD0zLCBmaWcuY2FwPSdEaXN0cmlidXRpb24gb2YgcmVjb25zdHJ1Y3Rpb24gZXJyb3JzLid9CiMgRXh0cmFjdCByZWNvbnN0cnVjdGlvbiBlcnJvcnMKKHJlY29uc3RydWN0aW9uX2Vycm9ycyA8LSBoMm8uYW5vbWFseShiZXN0X21vZGVsLCBmZWF0dXJlcykpCgojIFBsb3QgZGlzdHJpYnV0aW9uCnJlY29uc3RydWN0aW9uX2Vycm9ycyA8LSBhcy5kYXRhLmZyYW1lKHJlY29uc3RydWN0aW9uX2Vycm9ycykKZ2dwbG90KHJlY29uc3RydWN0aW9uX2Vycm9ycywgYWVzKFJlY29uc3RydWN0aW9uLk1TRSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gNTAwKQpgYGAKCkZpZ3VyZSAxOS4xMToKCmBgYHtyIGJhZC1yZWNvbnN0cnVjdGlvbi1lcnJvcnMtcGxvdCwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9NSwgZmlnLmNhcD0nT3JpZ2luYWwgZGlnaXRzIChsZWZ0KSBhbmQgdGhlaXIgcmVjb25zdHJ1Y3Rpb25zIChyaWdodCkgZm9yIHRoZSBvYnNlcnZhdGlvbnMgd2l0aCB0aGUgZml2ZSBsYXJnZXN0IHJlY29uc3RydWN0aW9uIGVycm9ycy4nfQpiaWdfZXJyb3JfaW5kZXggPC0gcmVjb25zdHJ1Y3Rpb25fZXJyb3JzICU+JQogIG11dGF0ZShvYnMgPSByb3dfbnVtYmVyKCkpICU+JQogIGFycmFuZ2UoZGVzYyhSZWNvbnN0cnVjdGlvbi5NU0UpKSAlPiUgCiAgdG9wX24oNSwgd3QgPSBSZWNvbnN0cnVjdGlvbi5NU0UpICU+JQogIHB1bGwob2JzKQoKYmlnX2Vycm9yX2lucHV0cyA8LSBhcy5oMm8oYXMuZGF0YS5mcmFtZShmZWF0dXJlcylbYmlnX2Vycm9yX2luZGV4LCBdKQpiaWdfZXJyb3JzIDwtIHByZWRpY3QoYmVzdF9tb2RlbCwgYmlnX2Vycm9yX2lucHV0cykgJT4lCiAgYXMubWF0cml4KCkKCm9yaWdpbmFsX2lucHV0cyA8LSBhcy5tYXRyaXgoZmVhdHVyZXMpW2JpZ19lcnJvcl9pbmRleCwgXQpjb2xuYW1lcyhiaWdfZXJyb3JzKSA8LSBjb2xuYW1lcyhvcmlnaW5hbF9pbnB1dHMpCm9yaWdpbmFsX3ZzX2JpZ19lcnJvcnMgPC0gcmJpbmQob3JpZ2luYWxfaW5wdXRzLCBiaWdfZXJyb3JzKQoKIyBwbG90IApwYXIobWZyb3cgPSBjKDUsIDMpLCBtYXIgPSBjKDAsIDAuNSwgMiwgMC41KSkKbGF5b3V0KG1hdHJpeChzZXFfbGVuKG5yb3cob3JpZ2luYWxfdnNfYmlnX2Vycm9ycykpLCA1LCAyLCBieXJvdyA9IEZBTFNFKSkKZm9yIChpIGluIHNlcV9sZW4obnJvdyhvcmlnaW5hbF92c19iaWdfZXJyb3JzKSkpIHsKICB0aXRsZSA8LSBOVUxMCiAgaWYgKGkgPT0gMSkgdGl0bGUgPC0gIk9yaWdpbmFsIGRpZ2l0c1xuIgogIGlmIChpID09IDYpIHRpdGxlIDwtICJSZWNvbnN0cnVjdGVkIGRpZ2l0c1xuIgogIAogIGltYWdlKG1hdHJpeChvcmlnaW5hbF92c19iaWdfZXJyb3JzW2ksIF0sIDI4LCAyOClbLCAyODoxXSwgCiAgICAgICAgeGF4dCA9ICJuIiwgeWF4dCA9ICJuIiwgY29sID0gZ3JheS5jb2xvcnMoNCwgcmV2ID0gVFJVRSksCiAgICAgICAgbWFpbiA9IHRpdGxlKQp9ICAKYGBgCgpgYGB7cn0KaDJvLnNodXRkb3duKHByb21wdCA9IEZBTFNFKQpgYGAKCg==