Dataset
The Salaries data contain the 2008–09 nine-month academic salaries of
397 faculty members at a college in the United States. The same data are
available in R as carData::Salaries (Fox & Weisberg,
2019).
rank |
categorical (3 levels) |
Academic rank: AsstProf,
AssocProf, Prof |
discipline |
categorical (2 levels) |
Type of department: A (theoretical) or
B (applied) |
yrs.since.phd |
numeric |
Years since the PhD |
yrs.service |
numeric |
Years of service |
sex |
categorical (2 levels) |
Female, Male |
salary |
numeric |
Nine-month salary (US dollars) |
path <- "https://gist.githubusercontent.com/novrisuhermi/b0157aecf4e826b16698a3393fce73fe/raw/55fd29ee76f61016e75eee9b47e8d50455d2a350/salaries.csv"
df <- read.csv(path) %>%
mutate(rank = factor(rank, levels = c("AsstProf", "AssocProf", "Prof")),
discipline = factor(discipline),
sex = factor(sex))
num_vars <- names(select(df, where(is.numeric))) # yrs.since.phd, yrs.service, salary
cat_vars <- names(select(df, where(is.factor))) # rank, discipline, sex
df
summary(df)
rank discipline yrs.since.phd yrs.service sex salary
AsstProf : 67 A:181 Min. : 1.00 Min. : 0.00 Female: 39 Min. : 57800
AssocProf: 64 B:216 1st Qu.:12.00 1st Qu.: 7.00 Male :358 1st Qu.: 91000
Prof :266 Median :21.00 Median :16.00 Median :107300
Mean :22.31 Mean :17.61 Mean :113706
3rd Qu.:32.00 3rd Qu.:27.00 3rd Qu.:134185
Max. :56.00 Max. :60.00 Max. :231545
The data have no missing values, so they serve as the ground truth
for the experiment.
Relationships Between Variables
Imputation models can only exploit relationships that exist in the
data.
Distributions
p_num <- df %>%
pivot_longer(all_of(num_vars), names_to = "variable") %>%
mutate(variable = factor(variable, levels = num_vars)) %>%
ggplot(aes(value)) +
geom_histogram(bins = 25, fill = "steelblue4", colour = "white") +
facet_wrap(~ variable, scales = "free") +
scale_x_continuous(labels = scales::label_number(scale_cut = scales::cut_short_scale())) +
labs(title = "Numeric variables", x = NULL, y = "Count")
p_cat <- df %>%
pivot_longer(all_of(cat_vars), names_to = "variable", values_to = "category") %>%
mutate(variable = factor(variable, levels = cat_vars)) %>%
ggplot(aes(category)) +
geom_bar(fill = "steelblue4") +
facet_wrap(~ variable, scales = "free_x") +
labs(title = "Categorical variables", x = NULL, y = "Count")
p_num / p_cat

Salary, Rank and Seniority
p_scatter <- ggplot(df, aes(yrs.since.phd, salary, colour = rank)) +
geom_point(alpha = 0.7) +
scale_y_continuous(labels = scales::dollar) +
scale_colour_brewer(palette = "Dark2") +
labs(title = "Salary by seniority and rank",
x = "Years since PhD", y = "Salary", colour = "Rank") +
theme(legend.position = "bottom")
p_box <- df %>%
pivot_longer(all_of(cat_vars), names_to = "variable", values_to = "category") %>%
mutate(variable = factor(variable, levels = cat_vars)) %>%
ggplot(aes(category, salary)) +
geom_boxplot(fill = "steelblue4", alpha = 0.3, outlier.size = 0.8) +
facet_wrap(~ variable, scales = "free_x") +
scale_y_continuous(labels = scales::dollar) +
labs(title = "Salary by category", x = NULL, y = "Salary")
p_scatter | p_box

Association Tests
# H0: the two variables are not associated
# numeric - numeric : Spearman correlation test (effect size: |rho|)
# numeric - categorical : Kruskal-Wallis test (effect size: epsilon^2 = H / (n - 1))
# categorical - categorical : chi-squared test (effect size: Cramer's V)
association_test <- function(x, y) {
n <- length(x)
if (is.numeric(x) && is.numeric(y)) {
res <- cor.test(x, y, method = "spearman", exact = FALSE)
tibble(test = "Spearman", effect = abs(unname(res$estimate)), p_value = res$p.value)
} else if (is.factor(x) && is.factor(y)) {
res <- chisq.test(table(x, y), correct = FALSE)
k <- min(nlevels(x), nlevels(y))
tibble(test = "Chi-squared", effect = sqrt(unname(res$statistic) / (n * (k - 1))),
p_value = res$p.value)
} else {
res <- if (is.factor(x)) kruskal.test(y, x) else kruskal.test(x, y)
tibble(test = "Kruskal-Wallis", effect = unname(res$statistic) / (n - 1),
p_value = res$p.value)
}
}
associations <- combn(names(df), 2, simplify = FALSE) %>%
map_dfr(~ bind_cols(tibble(variable_1 = .x[1], variable_2 = .x[2]),
association_test(df[[.x[1]]], df[[.x[2]]]))) %>%
arrange(p_value)
associations %>%
mutate(effect = round(effect, 3))
Predictability of Each Variable
MICE imputes each variable from all the others, so it helps to know
how well each variable can be predicted from the rest.
# numeric : R-squared of a linear regression on all other variables
# categorical : accuracy of the most likely category from a (multinomial) logistic regression,
# next to the baseline of always predicting the most frequent category
predictability <- map_dfr(names(df), function(v) {
f <- reformulate(setdiff(names(df), v), response = v)
if (is.numeric(df[[v]])) {
tibble(variable = v, measure = "R-squared",
model = summary(lm(f, data = df))$r.squared, baseline = NA_real_)
} else {
fit <- nnet::multinom(f, data = df, trace = FALSE)
tibble(variable = v, measure = "Accuracy",
model = mean(predict(fit, df) == df[[v]]),
baseline = max(prop.table(table(df[[v]]))))
}
})
predictability %>%
mutate(across(c(model, baseline), ~ round(.x, 3)))
Simulating Missing Values
inject_missing <- function(data, prop = 0.10, mech = "MAR", type = "RIGHT") {
vars <- names(data)
p <- length(vars)
if (prop * p >= 1) stop("prop times the number of variables must be below 1.")
patterns <- matrix(1, p, p, dimnames = list(vars, vars))
diag(patterns) <- 0
weights <- matrix(0, p, p, dimnames = list(vars, vars))
weights[, "yrs.since.phd"] <- 1
weights["yrs.since.phd", c("yrs.since.phd", "yrs.service")] <- c(0, 1)
data_num <- mutate(data, across(where(is.factor), as.integer))
amp <- ampute(data_num, prop = prop * p, patterns = patterns, freq = rep(1 / p, p),
mech = mech, weights = if (mech == "MAR") weights else NULL, type = type)
data[is.na(amp$amp)] <- NA
data
}
set.seed(2026)
df_missing <- inject_missing(df, prop = 0.10)
df_missing
{
print(miss_var_summary(df_missing)) # missing cells per variable
print(mean(complete.cases(df_missing))) # share of complete rows
}
# A tibble: 6 × 3
variable n_miss pct_miss
<chr> <int> <num>
1 discipline 40 10.1
2 yrs.since.phd 40 10.1
3 rank 39 9.82
4 sex 39 9.82
5 yrs.service 34 8.56
6 salary 34 8.56
[1] 0.4307305
Although each variable misses only about 10% of its values, fewer
than half of the rows are complete. Complete-case analysis (the default
of lm()) would discard all the other rows, including the
values they do contain.
# Left: location of the missing cells
p_vis <- vis_miss(df_missing) +
labs(title = "Missing cells") +
theme(plot.title = element_text(face = "bold"))
# Right: seniority of the rows where each variable is missing versus observed
p_mar <- df_missing %>%
bind_shadow() %>%
select(yrs.since.phd, ends_with("_NA"), -yrs.since.phd_NA) %>%
drop_na(yrs.since.phd) %>%
pivot_longer(-yrs.since.phd, names_to = "variable", values_to = "status") %>%
mutate(variable = factor(str_remove(variable, "_NA$"), levels = names(df)),
status = if_else(status == "NA", "Missing", "Observed")) %>%
ggplot(aes(status, yrs.since.phd, fill = status)) +
geom_boxplot(alpha = 0.7, outlier.size = 0.8) +
facet_wrap(~ variable, nrow = 1) +
scale_fill_manual(values = c(Missing = "firebrick", Observed = "steelblue4"), guide = "none") +
labs(title = "Years since PhD by missingness status", x = NULL, y = "Years since PhD") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
p_vis | p_mar

# Missing-data patterns: blue = observed, red = missing;
# left = number of rows, right = number of missing variables, bottom = missing cells per variable
{md.pattern(df_missing, rotate.names = TRUE)}
yrs.service salary rank sex discipline yrs.since.phd
171 1 1 1 1 1 1 0
40 1 1 1 1 1 0 1
40 1 1 1 1 0 1 1
39 1 1 1 0 1 1 1
39 1 1 0 1 1 1 1
34 1 0 1 1 1 1 1
34 0 1 1 1 1 1 1
34 34 39 39 40 40 226

# Little's test. H0: the data are missing completely at random (MCAR)
mcar_test(df_missing)
Imputation
Methods
| Mean-based |
mean |
mean (mean) |
mode (mode*) |
1 |
|
median |
median (median*) |
mode (mode*) |
1 |
| Random |
random |
random draw from the observed values
(sample) |
same |
20 |
| Regression |
regression |
predicted value (norm.predict) |
logistic regression, most likely category
(polyreg.predict*) |
1 |
|
stochastic_regression |
prediction + random residual
(norm.nob) |
logistic regression, draw from the predicted
probabilities (logreg / polyreg) |
20 |
|
bayesian_regression |
Bayesian linear regression (norm) |
logistic regression, draw (logreg /
polyreg) |
20 |
| Predictive Mean Matching |
pmm |
predictive mean matching (pmm) |
logistic regression, draw (logreg /
polyreg) |
20 |
|
pmm_polr |
predictive mean matching (pmm) |
as pmm, but ordinal logistic regression
for rank (polr) |
20 |
| Tree-based |
cart |
regression tree (cart) |
classification tree (cart) |
20 |
|
rf |
random forest (rf) |
random forest (rf) |
20 |
# Arguments passed by mice:
# y : the variable to impute ry : TRUE where y is observed
# x : matrix of predictors wy : TRUE where y must be imputed
mice.impute.median <- function(y, ry, x, wy = !ry, ...) {
rep(median(y[ry]), sum(wy))
}
mice.impute.mode <- function(y, ry, x, wy = !ry, ...) {
counts <- table(y[ry])
rep(names(counts)[which.max(counts)], sum(wy))
}
# Most likely category from a multinomial logit (a logistic regression for two levels)
mice.impute.polyreg.predict <- function(y, ry, x, wy = !ry, ...) {
xy <- data.frame(y = y, x)
fit <- nnet::multinom(y ~ ., data = xy[ry, , drop = FALSE], trace = FALSE)
as.character(predict(fit, newdata = xy[wy, , drop = FALSE], type = "class"))
}
Running the Imputations
A dry run with maxit = 0 imputes nothing but returns the
settings mice would use:
ini <- mice(df_missing, maxit = 0)
ini$method # default method per variable
rank discipline yrs.since.phd yrs.service sex salary
"polyreg" "logreg" "pmm" "pmm" "logreg" "pmm"
ini$predictorMatrix # rows = imputed variables, columns = predictors (1 = used)
rank discipline yrs.since.phd yrs.service sex salary
rank 0 1 1 1 1 1
discipline 1 0 1 1 1 1
yrs.since.phd 1 1 0 1 1 1
yrs.service 1 1 1 0 1 1
sex 1 1 1 1 0 1
salary 1 1 1 1 1 0
# One mice run per method: m = 20 imputations and 10 iterations unless stated otherwise
impute <- function(method, m = 20, maxit = 10) {
mice(df_missing, method = method, m = m, maxit = maxit, printFlag = FALSE)
}
# One method for the numeric variables and one for the factors
# (categorical = NULL keeps the defaults logreg / polyreg)
use_methods <- function(numeric, categorical = NULL) {
meth <- ini$method
meth[num_vars] <- numeric
if (!is.null(categorical)) meth[cat_vars] <- categorical
meth
}
set.seed(2026) # mice draws random values
imputations <- list(
# Mean-based (single imputation): numeric -> mean or median, categorical -> mode
mean = impute(use_methods("mean", "mode"), m = 1, maxit = 1),
median = impute(use_methods("median", "mode"), m = 1, maxit = 1),
# Random draws from the observed values of each variable
random = impute("sample", maxit = 1),
# Regression: deterministic prediction, prediction + noise, Bayesian draws
regression = impute(use_methods("norm.predict", "polyreg.predict"), m = 1),
stochastic_regression = impute(use_methods("norm.nob")),
bayesian_regression = impute(use_methods("norm")),
# Predictive mean matching (the mice default)
pmm = impute(use_methods("pmm")),
# Tree-based
cart = impute("cart"),
rf = impute("rf"),
# PMM with rank as an ordinal variable: proportional-odds logistic regression
# (added last, so that the random draws of the runs above stay the same)
pmm_polr = impute(replace(use_methods("pmm"), "rank", "polr"))
)
imputations$pmm
Class: mids
Number of multiple imputations: 20
Imputation methods:
rank discipline yrs.since.phd yrs.service sex salary
"polyreg" "logreg" "pmm" "pmm" "logreg" "pmm"
PredictorMatrix:
rank discipline yrs.since.phd yrs.service sex salary
rank 0 1 1 1 1 1
discipline 1 0 1 1 1 1
yrs.since.phd 1 1 0 1 1 1
yrs.service 1 1 1 0 1 1
sex 1 1 1 1 0 1
salary 1 1 1 1 1 0
The imputations are stored in $imp: one row per missing
cell and one column per completed dataset. The spread within a row shows
the uncertainty about that cell.
imputations$pmm$imp$salary[1:6, 1:8]
Convergence
Every imputation is a separate chain. Healthy chains are freely
intermingled and show no trend over the iterations. mice
stores the mean of the imputed values per iteration and chain in
chainMean (for factors, the mean of the category codes);
plot(imp) draws the same trace plots with
lattice.
iterative <- c("stochastic_regression", "bayesian_regression", "pmm", "pmm_polr", "cart", "rf")
chain_means <- imputations[iterative] %>%
map_dfr(~ as.data.frame.table(.x$chainMean, responseName = "mean"), .id = "method") %>%
rename(variable = Var1, iteration = Var2, chain = Var3) %>%
mutate(method = factor(method, levels = iterative),
variable = factor(variable, levels = names(df)),
iteration = as.integer(as.character(iteration)))
ggplot(chain_means, aes(iteration, mean, group = chain, colour = chain)) +
geom_line(alpha = 0.6, linewidth = 0.4) +
facet_grid(variable ~ method, scales = "free_y") +
scale_colour_viridis_d(guide = "none") +
scale_x_continuous(breaks = c(1, 5, 10)) +
scale_y_continuous(labels = scales::label_number(scale_cut = scales::cut_short_scale())) +
labs(title = "Trace plots: mean of the imputed values per iteration",
subtitle = "One line per chain (imputation)", x = "Iteration", y = NULL)

The chains are well mixed and show no clear trend, so 10 iterations
are sufficient.
Plausibility
Without the ground truth, the next check is whether the imputed
values are plausible.
mice::complete(imp, "long", include = TRUE) stacks the
incomplete data (.imp = 0) and all completed datasets
(.imp = 1, ..., m); densityplot(imp) draws the
lattice version of the plot below.
# TRUE where a cell was removed, in long format
miss_long <- as_tibble(is.na(df_missing)) %>%
mutate(.id = row_number()) %>%
pivot_longer(-.id, names_to = "variable", values_to = "was_missing")
density_data <- mice::complete(imputations$pmm, action = "long", include = TRUE) %>%
mutate(.id = as.integer(.id)) %>%
select(.imp, .id, all_of(num_vars)) %>%
pivot_longer(all_of(num_vars), names_to = "variable") %>%
left_join(miss_long, by = c(".id", "variable")) %>%
mutate(variable = factor(variable, levels = num_vars))
ggplot() +
geom_density(data = filter(density_data, .imp > 0, was_missing),
aes(value, group = .imp, colour = "Imputed"), linewidth = 0.3, key_glyph = "path") +
geom_density(data = filter(density_data, .imp == 0, !was_missing),
aes(value, colour = "Observed"), linewidth = 1.1, key_glyph = "path") +
facet_wrap(~ variable, scales = "free") +
scale_colour_manual(values = c(Observed = "steelblue4", Imputed = "firebrick"),
breaks = c("Observed", "Imputed")) +
scale_x_continuous(labels = scales::label_number(scale_cut = scales::cut_short_scale())) +
labs(title = "PMM: observed values versus imputed values",
subtitle = "One red line per completed dataset", x = NULL, y = "Density", colour = NULL) +
theme(legend.position = "bottom", axis.text.y = element_blank())

Evaluation
The removed values are known, so every method can be compared with
the truth. For the set \mathcal{M} of
the n_{\mathcal{M}} removed cells of a
numeric variable:
\text{RMSE} = \sqrt{\frac{1}{n_{\mathcal{M}}}\sum_{i \in \mathcal{M}}
(\hat{y}_i - y_i)^2}, \qquad
\text{MAE} = \frac{1}{n_{\mathcal{M}}}\sum_{i \in \mathcal{M}} \lvert
\hat{y}_i - y_i \rvert, \qquad
\text{Bias} = \frac{1}{n_{\mathcal{M}}}\sum_{i \in \mathcal{M}}
(\hat{y}_i - y_i)
MAPE is not used because yrs.service contains zeros. The
SD ratio (SD of the imputed values divided by the SD of
the true values) shows whether the spread is preserved. Categorical
variables are scored with the accuracy (share of
correctly imputed categories) and the macro-F1, the
mean of \text{F1} = 2\,\text{TP} /
(2\,\text{TP} + \text{FP} + \text{FN}) over the categories. For
methods with m = 20, every metric is
computed for each completed dataset and then averaged.
# True and imputed values of the removed cells: one row per method, completed dataset and cell
removed_cells <- function(vars, convert = identity) {
map_dfr(imputations, .id = "method", function(imp) {
map_dfr(vars, function(v) {
rows <- as.integer(rownames(imp$imp[[v]]))
imp$imp[[v]] %>%
mutate(across(everything(), convert),
variable = v, row = rows, actual = convert(df[[v]][rows])) %>%
pivot_longer(-c(variable, row, actual), names_to = ".imp", values_to = "imputed",
names_transform = as.integer)
})
})
}
df_imputed_num <- removed_cells(num_vars)
df_imputed_cat <- removed_cells(cat_vars, convert = as.character)
df_imputed_num %>%
mutate(across(c(actual, imputed), ~ round(.x, 2)))
Numerical Variables: RMSE, MAE and Bias
method_groups <- list(
"Mean-based" = c("mean", "median"),
"Random" = "random",
"Regression" = c("regression", "stochastic_regression", "bayesian_regression"),
"Predictive Mean Matching" = c("pmm", "pmm_polr"),
"Tree-based" = c("cart", "rf")
)
families <- enframe(method_groups, name = "family", value = "method") %>%
unnest(method)
evaluation_num <- df_imputed_num %>%
group_by(method, variable, .imp) %>%
summarise(RMSE = sqrt(mean((imputed - actual)^2)),
MAE = mean(abs(imputed - actual)),
Bias = mean(imputed - actual),
`SD ratio` = sd(imputed) / sd(actual),
.groups = "drop") %>%
group_by(method, variable) %>%
summarise(across(c(RMSE, MAE, Bias, `SD ratio`), mean), .groups = "drop") %>% # average over the m datasets
left_join(families, by = "method") %>%
mutate(variable = factor(variable, levels = num_vars)) %>%
arrange(variable, RMSE)
# Display: ranked by RMSE within each variable
evaluation_num %>%
group_by(variable) %>%
mutate(Rank = row_number()) %>%
ungroup() %>%
mutate(across(c(RMSE, MAE, Bias, `SD ratio`), ~ round(.x, 2))) %>%
select(Variable = variable, Rank, Family = family, Method = method, RMSE, MAE, Bias, `SD ratio`)
Categorical Variables: Accuracy and Macro-F1
macro_f1 <- function(actual, imputed) {
classes <- union(actual, imputed)
mean(map_dbl(classes, function(cl) {
tp <- sum(imputed == cl & actual == cl)
fp <- sum(imputed == cl & actual != cl)
fn <- sum(imputed != cl & actual == cl)
if (tp == 0) 0 else 2 * tp / (2 * tp + fp + fn)
}))
}
# Method that mice actually used for each categorical variable in each run
cat_methods <- imputations %>%
map_dfr(~ tibble(variable = cat_vars, model = unname(.x$method[cat_vars])), .id = "method")
evaluation_cat <- df_imputed_cat %>%
group_by(method, variable, .imp) %>%
summarise(Accuracy = mean(imputed == actual),
`Macro-F1` = macro_f1(actual, imputed),
.groups = "drop") %>%
group_by(method, variable) %>%
summarise(across(c(Accuracy, `Macro-F1`), mean), .groups = "drop") %>%
left_join(families, by = "method") %>%
left_join(cat_methods, by = c("method", "variable")) %>%
mutate(variable = factor(variable, levels = cat_vars)) %>%
arrange(variable, desc(Accuracy))
# Display: ranked by accuracy within each variable
evaluation_cat %>%
group_by(variable) %>%
mutate(Rank = row_number()) %>%
ungroup() %>%
mutate(across(c(Accuracy, `Macro-F1`), ~ round(.x, 3))) %>%
select(Variable = variable, Rank, Family = family, Method = method,
`Imputed with` = model, Accuracy, `Macro-F1`)
# Proportional-odds check: slope of yrs.since.phd in a separate logit for each cut-off of rank
# (the first split is almost perfectly separated, hence suppressWarnings)
slope <- function(event) {
fit <- suppressWarnings(glm(event ~ yrs.since.phd, family = binomial, data = df))
coef(fit)[["yrs.since.phd"]]
}
c(`AsstProf | AssocProf, Prof` = slope(df$rank != "AsstProf"),
`AsstProf, AssocProf | Prof` = slope(df$rank == "Prof"))
AsstProf | AssocProf, Prof AsstProf, AssocProf | Prof
0.9745747 0.2300914
Imputed Values Visualization
plot_imputations <- function(group) {
methods <- method_groups[[group]]
data <- df_imputed_num %>%
filter(method %in% methods, .imp == 1) %>% # first completed dataset
left_join(select(evaluation_num, method, variable, RMSE), by = c("method", "variable")) %>%
mutate(method = factor(method, levels = methods),
variable = factor(variable, levels = num_vars)) %>%
arrange(method, variable) %>%
mutate(rmse = if_else(RMSE >= 100, scales::comma(RMSE, accuracy = 1), sprintf("%.2f", RMSE)),
panel = fct_inorder(sprintf("%s\n%s (RMSE %s)", method, variable, rmse)))
# Same range on both axes, per variable, so that the dashed line is the diagonal
limits <- data %>%
group_by(variable) %>%
summarise(low = min(actual, imputed), high = max(actual, imputed)) %>%
right_join(distinct(data, panel, variable), by = "variable") %>%
pivot_longer(c(low, high), values_to = "value")
ggplot(data, aes(actual, imputed)) +
geom_blank(data = limits, aes(value, value)) +
geom_abline(linetype = "dashed", colour = "grey50") +
geom_point(colour = "firebrick", alpha = 0.7, size = 1.5) +
facet_wrap(~ panel, scales = "free", ncol = 3) +
scale_x_continuous(labels = scales::label_number(scale_cut = scales::cut_short_scale())) +
scale_y_continuous(labels = scales::label_number(scale_cut = scales::cut_short_scale())) +
labs(title = group,
subtitle = "Removed cells, first completed dataset. Dashed line = perfect imputation",
x = "True value", y = "Imputed value") +
theme(aspect.ratio = 1)
}
Mean-based
plot_imputations("Mean-based")

Random
plot_imputations("Random")

Regression
plot_imputations("Regression")

Predictive Mean Matching
plot_imputations("Predictive Mean Matching")

Tree-based
plot_imputations("Tree-based")

Distributions and Category Shares
A good imputation method should also reproduce the
distribution of the missing values, not only their
centre.
family_colours <- c("Mean-based" = "#E69F00", "Random" = "#CC79A7", "Regression" = "#D55E00",
"Predictive Mean Matching" = "#0072B2", "Tree-based" = "#009E73",
"True values" = "grey70")
method_levels <- unlist(method_groups, use.names = FALSE) # methods ordered by family
true_num <- df_imputed_num %>%
distinct(variable, row, actual) %>%
transmute(method = "true values", family = "True values", variable, value = actual)
df_imputed_num %>%
left_join(families, by = "method") %>%
transmute(method, family, variable, value = imputed) %>% # all completed datasets
bind_rows(true_num) %>%
mutate(method = factor(method, levels = rev(c("true values", method_levels))),
family = factor(family, levels = names(family_colours)),
variable = factor(variable, levels = num_vars)) %>%
ggplot(aes(value, method, fill = family)) +
geom_boxplot(outlier.size = 0.6, alpha = 0.85) +
facet_wrap(~ variable, scales = "free_x") +
scale_fill_manual(values = family_colours) +
scale_x_continuous(labels = scales::label_number(scale_cut = scales::cut_short_scale())) +
labs(title = "Imputed values versus the removed true values",
x = NULL, y = NULL, fill = NULL) +
theme(legend.position = "bottom")

category_colours <- c(AsstProf = "#c6dbef", AssocProf = "#6baed6", Prof = "#08519c",
A = "#a1d99b", B = "#238b45", Female = "#fdae6b", Male = "#d94801")
true_cat <- df_imputed_cat %>%
distinct(variable, row, actual) %>%
transmute(method = "true values", variable, category = actual)
# Reference: categories of the observed (not removed) cells
obs_cat <- map_dfr(cat_vars, ~ tibble(method = "observed values", variable = .x,
category = as.character(na.omit(df_missing[[.x]]))))
# Labels with the method used for the categorical variables, e.g. "pmm (polyreg/logreg)"
cat_labels <- cat_methods %>%
group_by(method) %>%
summarise(models = paste(unique(model), collapse = "/")) %>%
mutate(label = if_else(models == method, method, paste0(method, " (", models, ")")))
plot_levels <- c("true values", "observed values", method_levels)
plot_labels <- c("true values", "observed values",
cat_labels$label[match(method_levels, cat_labels$method)])
df_imputed_cat %>%
transmute(method, variable, category = imputed) %>%
bind_rows(true_cat, obs_cat) %>%
count(method, variable, category) %>%
group_by(method, variable) %>%
mutate(share = n / sum(n)) %>%
ungroup() %>%
mutate(method = factor(method, levels = rev(plot_levels), labels = rev(plot_labels)),
variable = factor(variable, levels = cat_vars),
category = factor(category, levels = names(category_colours))) %>%
ggplot(aes(share, method, fill = category)) +
geom_col(width = 0.75, position = position_stack(reverse = TRUE)) +
facet_wrap(~ variable) +
scale_x_continuous(labels = scales::percent) +
scale_fill_manual(values = category_colours) +
guides(fill = guide_legend(nrow = 1)) +
labs(title = "Category shares among the imputed cells",
subtitle = "In brackets: the method used for the categorical variables",
x = "Share", y = NULL, fill = NULL) +
theme(legend.position = "bottom")

LS0tCnRpdGxlOiAiQ3Jvc3MtU2VjdGlvbmFsIERhdGEgSW1wdXRhdGlvbiB3aXRoIE1JQ0UiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIHRvY19jb2xsYXBzZWQ6IHRydWUKICAgIG1hdGhfbWV0aG9kOiBrYXRleAotLS0KCiMjIExpYnJhcmllcwoKYGBge3Igd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobWljZSkKbGlicmFyeShuYW5pYXIpCmxpYnJhcnkocGF0Y2h3b3JrKQoKdGhlbWVfc2V0KAogIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpICsKICAgIHRoZW1lKHBsb3QudGl0bGUgICAgICAgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksCiAgICAgICAgICBzdHJpcC50ZXh0ICAgICAgID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLAogICAgICAgICAgcGFuZWwuZ3JpZC5taW5vciA9IGVsZW1lbnRfYmxhbmsoKSkKKQpgYGAKCiMjIERhdGFzZXQKClRoZSBTYWxhcmllcyBkYXRhIGNvbnRhaW4gdGhlIDIwMDjigJMwOSBuaW5lLW1vbnRoIGFjYWRlbWljIHNhbGFyaWVzIG9mIDM5NyBmYWN1bHR5IG1lbWJlcnMgYXQgYSBjb2xsZWdlIGluIHRoZSBVbml0ZWQgU3RhdGVzLiBUaGUgc2FtZSBkYXRhIGFyZSBhdmFpbGFibGUgaW4gUiBhcyBgY2FyRGF0YTo6U2FsYXJpZXNgIChGb3ggJiBXZWlzYmVyZywgMjAxOSkuCgp8IFZhcmlhYmxlIHwgVHlwZSB8IERlc2NyaXB0aW9uIHwKfDotLS18Oi0tLXw6LS0tfAp8IGByYW5rYCB8IGNhdGVnb3JpY2FsICgzIGxldmVscykgfCBBY2FkZW1pYyByYW5rOiBgQXNzdFByb2ZgLCBgQXNzb2NQcm9mYCwgYFByb2ZgIHwKfCBgZGlzY2lwbGluZWAgfCBjYXRlZ29yaWNhbCAoMiBsZXZlbHMpIHwgVHlwZSBvZiBkZXBhcnRtZW50OiBgQWAgKHRoZW9yZXRpY2FsKSBvciBgQmAgKGFwcGxpZWQpIHwKfCBgeXJzLnNpbmNlLnBoZGAgfCBudW1lcmljIHwgWWVhcnMgc2luY2UgdGhlIFBoRCB8CnwgYHlycy5zZXJ2aWNlYCB8IG51bWVyaWMgfCBZZWFycyBvZiBzZXJ2aWNlIHwKfCBgc2V4YCB8IGNhdGVnb3JpY2FsICgyIGxldmVscykgfCBgRmVtYWxlYCwgYE1hbGVgIHwKfCBgc2FsYXJ5YCB8IG51bWVyaWMgfCBOaW5lLW1vbnRoIHNhbGFyeSAoVVMgZG9sbGFycykgfAoKYGBge3J9CnBhdGggPC0gImh0dHBzOi8vZ2lzdC5naXRodWJ1c2VyY29udGVudC5jb20vbm92cmlzdWhlcm1pL2IwMTU3YWVjZjRlODI2YjE2Njk4YTMzOTNmY2U3M2ZlL3Jhdy81NWZkMjllZTc2ZjYxMDE2ZTc1ZWVlOWI0N2U4ZDUwNDU1ZDJhMzUwL3NhbGFyaWVzLmNzdiIKZGYgPC0gcmVhZC5jc3YocGF0aCkgJT4lCiAgbXV0YXRlKHJhbmsgICAgICAgPSBmYWN0b3IocmFuaywgbGV2ZWxzID0gYygiQXNzdFByb2YiLCAiQXNzb2NQcm9mIiwgIlByb2YiKSksCiAgICAgICAgIGRpc2NpcGxpbmUgPSBmYWN0b3IoZGlzY2lwbGluZSksCiAgICAgICAgIHNleCAgICAgICAgPSBmYWN0b3Ioc2V4KSkKCm51bV92YXJzIDwtIG5hbWVzKHNlbGVjdChkZiwgd2hlcmUoaXMubnVtZXJpYykpKSAgICMgeXJzLnNpbmNlLnBoZCwgeXJzLnNlcnZpY2UsIHNhbGFyeQpjYXRfdmFycyA8LSBuYW1lcyhzZWxlY3QoZGYsIHdoZXJlKGlzLmZhY3RvcikpKSAgICAjIHJhbmssIGRpc2NpcGxpbmUsIHNleAoKZGYKYGBgCgpgYGB7cn0Kc3VtbWFyeShkZikKYGBgCgpUaGUgZGF0YSBoYXZlIG5vIG1pc3NpbmcgdmFsdWVzLCBzbyB0aGV5IHNlcnZlIGFzIHRoZSBncm91bmQgdHJ1dGggZm9yIHRoZSBleHBlcmltZW50LgoKIyMgUmVsYXRpb25zaGlwcyBCZXR3ZWVuIFZhcmlhYmxlcwoKSW1wdXRhdGlvbiBtb2RlbHMgY2FuIG9ubHkgZXhwbG9pdCByZWxhdGlvbnNoaXBzIHRoYXQgZXhpc3QgaW4gdGhlIGRhdGEuCgojIyMgRGlzdHJpYnV0aW9ucwoKYGBge3IgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTYuNX0KcF9udW0gPC0gZGYgJT4lCiAgcGl2b3RfbG9uZ2VyKGFsbF9vZihudW1fdmFycyksIG5hbWVzX3RvID0gInZhcmlhYmxlIikgJT4lCiAgbXV0YXRlKHZhcmlhYmxlID0gZmFjdG9yKHZhcmlhYmxlLCBsZXZlbHMgPSBudW1fdmFycykpICU+JQogIGdncGxvdChhZXModmFsdWUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDI1LCBmaWxsID0gInN0ZWVsYmx1ZTQiLCBjb2xvdXIgPSAid2hpdGUiKSArCiAgZmFjZXRfd3JhcCh+IHZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoc2NhbGVfY3V0ID0gc2NhbGVzOjpjdXRfc2hvcnRfc2NhbGUoKSkpICsKICBsYWJzKHRpdGxlID0gIk51bWVyaWMgdmFyaWFibGVzIiwgeCA9IE5VTEwsIHkgPSAiQ291bnQiKQoKcF9jYXQgPC0gZGYgJT4lCiAgcGl2b3RfbG9uZ2VyKGFsbF9vZihjYXRfdmFycyksIG5hbWVzX3RvID0gInZhcmlhYmxlIiwgdmFsdWVzX3RvID0gImNhdGVnb3J5IikgJT4lCiAgbXV0YXRlKHZhcmlhYmxlID0gZmFjdG9yKHZhcmlhYmxlLCBsZXZlbHMgPSBjYXRfdmFycykpICU+JQogIGdncGxvdChhZXMoY2F0ZWdvcnkpKSArCiAgZ2VvbV9iYXIoZmlsbCA9ICJzdGVlbGJsdWU0IikgKwogIGZhY2V0X3dyYXAofiB2YXJpYWJsZSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBsYWJzKHRpdGxlID0gIkNhdGVnb3JpY2FsIHZhcmlhYmxlcyIsIHggPSBOVUxMLCB5ID0gIkNvdW50IikKCnBfbnVtIC8gcF9jYXQKYGBgCgojIyMgU2FsYXJ5LCBSYW5rIGFuZCBTZW5pb3JpdHkKCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD00LjV9CnBfc2NhdHRlciA8LSBnZ3Bsb3QoZGYsIGFlcyh5cnMuc2luY2UucGhkLCBzYWxhcnksIGNvbG91ciA9IHJhbmspKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNykgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmRvbGxhcikgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpICsKICBsYWJzKHRpdGxlID0gIlNhbGFyeSBieSBzZW5pb3JpdHkgYW5kIHJhbmsiLAogICAgICAgeCA9ICJZZWFycyBzaW5jZSBQaEQiLCB5ID0gIlNhbGFyeSIsIGNvbG91ciA9ICJSYW5rIikgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQoKcF9ib3ggPC0gZGYgJT4lCiAgcGl2b3RfbG9uZ2VyKGFsbF9vZihjYXRfdmFycyksIG5hbWVzX3RvID0gInZhcmlhYmxlIiwgdmFsdWVzX3RvID0gImNhdGVnb3J5IikgJT4lCiAgbXV0YXRlKHZhcmlhYmxlID0gZmFjdG9yKHZhcmlhYmxlLCBsZXZlbHMgPSBjYXRfdmFycykpICU+JQogIGdncGxvdChhZXMoY2F0ZWdvcnksIHNhbGFyeSkpICsKICBnZW9tX2JveHBsb3QoZmlsbCA9ICJzdGVlbGJsdWU0IiwgYWxwaGEgPSAwLjMsIG91dGxpZXIuc2l6ZSA9IDAuOCkgKwogIGZhY2V0X3dyYXAofiB2YXJpYWJsZSwgc2NhbGVzID0gImZyZWVfeCIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpkb2xsYXIpICsKICBsYWJzKHRpdGxlID0gIlNhbGFyeSBieSBjYXRlZ29yeSIsIHggPSBOVUxMLCB5ID0gIlNhbGFyeSIpCgpwX3NjYXR0ZXIgfCBwX2JveApgYGAKCiMjIyBBc3NvY2lhdGlvbiBUZXN0cwoKYGBge3J9CiMgSDA6IHRoZSB0d28gdmFyaWFibGVzIGFyZSBub3QgYXNzb2NpYXRlZAojICAgbnVtZXJpYyAtIG51bWVyaWMgICAgICAgICA6IFNwZWFybWFuIGNvcnJlbGF0aW9uIHRlc3QgIChlZmZlY3Qgc2l6ZTogfHJob3wpCiMgICBudW1lcmljIC0gY2F0ZWdvcmljYWwgICAgIDogS3J1c2thbC1XYWxsaXMgdGVzdCAgICAgICAgKGVmZmVjdCBzaXplOiBlcHNpbG9uXjIgPSBIIC8gKG4gLSAxKSkKIyAgIGNhdGVnb3JpY2FsIC0gY2F0ZWdvcmljYWwgOiBjaGktc3F1YXJlZCB0ZXN0ICAgICAgICAgICAoZWZmZWN0IHNpemU6IENyYW1lcidzIFYpCmFzc29jaWF0aW9uX3Rlc3QgPC0gZnVuY3Rpb24oeCwgeSkgewogIG4gPC0gbGVuZ3RoKHgpCiAgaWYgKGlzLm51bWVyaWMoeCkgJiYgaXMubnVtZXJpYyh5KSkgewogICAgcmVzIDwtIGNvci50ZXN0KHgsIHksIG1ldGhvZCA9ICJzcGVhcm1hbiIsIGV4YWN0ID0gRkFMU0UpCiAgICB0aWJibGUodGVzdCA9ICJTcGVhcm1hbiIsIGVmZmVjdCA9IGFicyh1bm5hbWUocmVzJGVzdGltYXRlKSksIHBfdmFsdWUgPSByZXMkcC52YWx1ZSkKICB9IGVsc2UgaWYgKGlzLmZhY3Rvcih4KSAmJiBpcy5mYWN0b3IoeSkpIHsKICAgIHJlcyA8LSBjaGlzcS50ZXN0KHRhYmxlKHgsIHkpLCBjb3JyZWN0ID0gRkFMU0UpCiAgICBrICAgPC0gbWluKG5sZXZlbHMoeCksIG5sZXZlbHMoeSkpCiAgICB0aWJibGUodGVzdCA9ICJDaGktc3F1YXJlZCIsIGVmZmVjdCA9IHNxcnQodW5uYW1lKHJlcyRzdGF0aXN0aWMpIC8gKG4gKiAoayAtIDEpKSksCiAgICAgICAgICAgcF92YWx1ZSA9IHJlcyRwLnZhbHVlKQogIH0gZWxzZSB7CiAgICByZXMgPC0gaWYgKGlzLmZhY3Rvcih4KSkga3J1c2thbC50ZXN0KHksIHgpIGVsc2Uga3J1c2thbC50ZXN0KHgsIHkpCiAgICB0aWJibGUodGVzdCA9ICJLcnVza2FsLVdhbGxpcyIsIGVmZmVjdCA9IHVubmFtZShyZXMkc3RhdGlzdGljKSAvIChuIC0gMSksCiAgICAgICAgICAgcF92YWx1ZSA9IHJlcyRwLnZhbHVlKQogIH0KfQoKYXNzb2NpYXRpb25zIDwtIGNvbWJuKG5hbWVzKGRmKSwgMiwgc2ltcGxpZnkgPSBGQUxTRSkgJT4lCiAgbWFwX2Rmcih+IGJpbmRfY29scyh0aWJibGUodmFyaWFibGVfMSA9IC54WzFdLCB2YXJpYWJsZV8yID0gLnhbMl0pLAogICAgICAgICAgICAgICAgICAgICAgYXNzb2NpYXRpb25fdGVzdChkZltbLnhbMV1dXSwgZGZbWy54WzJdXV0pKSkgJT4lCiAgYXJyYW5nZShwX3ZhbHVlKQoKYXNzb2NpYXRpb25zICU+JQogIG11dGF0ZShlZmZlY3QgPSByb3VuZChlZmZlY3QsIDMpKQpgYGAKCgojIyMgUHJlZGljdGFiaWxpdHkgb2YgRWFjaCBWYXJpYWJsZQoKTUlDRSBpbXB1dGVzIGVhY2ggdmFyaWFibGUgZnJvbSBhbGwgdGhlIG90aGVycywgc28gaXQgaGVscHMgdG8ga25vdyBob3cgd2VsbCBlYWNoIHZhcmlhYmxlIGNhbiBiZSBwcmVkaWN0ZWQgZnJvbSB0aGUgcmVzdC4KCmBgYHtyfQojIG51bWVyaWMgICAgIDogUi1zcXVhcmVkIG9mIGEgbGluZWFyIHJlZ3Jlc3Npb24gb24gYWxsIG90aGVyIHZhcmlhYmxlcwojIGNhdGVnb3JpY2FsIDogYWNjdXJhY3kgb2YgdGhlIG1vc3QgbGlrZWx5IGNhdGVnb3J5IGZyb20gYSAobXVsdGlub21pYWwpIGxvZ2lzdGljIHJlZ3Jlc3Npb24sCiMgICAgICAgICAgICAgICBuZXh0IHRvIHRoZSBiYXNlbGluZSBvZiBhbHdheXMgcHJlZGljdGluZyB0aGUgbW9zdCBmcmVxdWVudCBjYXRlZ29yeQpwcmVkaWN0YWJpbGl0eSA8LSBtYXBfZGZyKG5hbWVzKGRmKSwgZnVuY3Rpb24odikgewogIGYgPC0gcmVmb3JtdWxhdGUoc2V0ZGlmZihuYW1lcyhkZiksIHYpLCByZXNwb25zZSA9IHYpCiAgaWYgKGlzLm51bWVyaWMoZGZbW3ZdXSkpIHsKICAgIHRpYmJsZSh2YXJpYWJsZSA9IHYsIG1lYXN1cmUgPSAiUi1zcXVhcmVkIiwKICAgICAgICAgICBtb2RlbCA9IHN1bW1hcnkobG0oZiwgZGF0YSA9IGRmKSkkci5zcXVhcmVkLCBiYXNlbGluZSA9IE5BX3JlYWxfKQogIH0gZWxzZSB7CiAgICBmaXQgPC0gbm5ldDo6bXVsdGlub20oZiwgZGF0YSA9IGRmLCB0cmFjZSA9IEZBTFNFKQogICAgdGliYmxlKHZhcmlhYmxlID0gdiwgbWVhc3VyZSA9ICJBY2N1cmFjeSIsCiAgICAgICAgICAgbW9kZWwgICAgPSBtZWFuKHByZWRpY3QoZml0LCBkZikgPT0gZGZbW3ZdXSksCiAgICAgICAgICAgYmFzZWxpbmUgPSBtYXgocHJvcC50YWJsZSh0YWJsZShkZltbdl1dKSkpKQogIH0KfSkKCnByZWRpY3RhYmlsaXR5ICU+JQogIG11dGF0ZShhY3Jvc3MoYyhtb2RlbCwgYmFzZWxpbmUpLCB+IHJvdW5kKC54LCAzKSkpCmBgYAoKCiMjIFNpbXVsYXRpbmcgTWlzc2luZyBWYWx1ZXMKCgpgYGB7cn0KaW5qZWN0X21pc3NpbmcgPC0gZnVuY3Rpb24oZGF0YSwgcHJvcCA9IDAuMTAsIG1lY2ggPSAiTUFSIiwgdHlwZSA9ICJSSUdIVCIpIHsKICB2YXJzIDwtIG5hbWVzKGRhdGEpCiAgcCAgICA8LSBsZW5ndGgodmFycykKICBpZiAocHJvcCAqIHAgPj0gMSkgc3RvcCgicHJvcCB0aW1lcyB0aGUgbnVtYmVyIG9mIHZhcmlhYmxlcyBtdXN0IGJlIGJlbG93IDEuIikKCiAgcGF0dGVybnMgPC0gbWF0cml4KDEsIHAsIHAsIGRpbW5hbWVzID0gbGlzdCh2YXJzLCB2YXJzKSkKICBkaWFnKHBhdHRlcm5zKSA8LSAwCgogIHdlaWdodHMgPC0gbWF0cml4KDAsIHAsIHAsIGRpbW5hbWVzID0gbGlzdCh2YXJzLCB2YXJzKSkKICB3ZWlnaHRzWywgInlycy5zaW5jZS5waGQiXSA8LSAxCiAgd2VpZ2h0c1sieXJzLnNpbmNlLnBoZCIsIGMoInlycy5zaW5jZS5waGQiLCAieXJzLnNlcnZpY2UiKV0gPC0gYygwLCAxKQoKICBkYXRhX251bSA8LSBtdXRhdGUoZGF0YSwgYWNyb3NzKHdoZXJlKGlzLmZhY3RvciksIGFzLmludGVnZXIpKQogIGFtcCA8LSBhbXB1dGUoZGF0YV9udW0sIHByb3AgPSBwcm9wICogcCwgcGF0dGVybnMgPSBwYXR0ZXJucywgZnJlcSA9IHJlcCgxIC8gcCwgcCksCiAgICAgICAgICAgICAgICBtZWNoID0gbWVjaCwgd2VpZ2h0cyA9IGlmIChtZWNoID09ICJNQVIiKSB3ZWlnaHRzIGVsc2UgTlVMTCwgdHlwZSA9IHR5cGUpCgogIGRhdGFbaXMubmEoYW1wJGFtcCldIDwtIE5BCiAgZGF0YQp9CmBgYAoKYGBge3J9CnNldC5zZWVkKDIwMjYpCmRmX21pc3NpbmcgPC0gaW5qZWN0X21pc3NpbmcoZGYsIHByb3AgPSAwLjEwKQoKZGZfbWlzc2luZwpgYGAKCmBgYHtyfQp7CnByaW50KG1pc3NfdmFyX3N1bW1hcnkoZGZfbWlzc2luZykpICAgICAgICMgbWlzc2luZyBjZWxscyBwZXIgdmFyaWFibGUKcHJpbnQobWVhbihjb21wbGV0ZS5jYXNlcyhkZl9taXNzaW5nKSkpICAgIyBzaGFyZSBvZiBjb21wbGV0ZSByb3dzCn0KYGBgCgpBbHRob3VnaCBlYWNoIHZhcmlhYmxlIG1pc3NlcyBvbmx5IGFib3V0IDEwJSBvZiBpdHMgdmFsdWVzLCBmZXdlciB0aGFuIGhhbGYgb2YgdGhlIHJvd3MgYXJlIGNvbXBsZXRlLiBDb21wbGV0ZS1jYXNlIGFuYWx5c2lzICh0aGUgZGVmYXVsdCBvZiBgbG0oKWApIHdvdWxkIGRpc2NhcmQgYWxsIHRoZSBvdGhlciByb3dzLCBpbmNsdWRpbmcgdGhlIHZhbHVlcyB0aGV5IGRvIGNvbnRhaW4uCgpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9NC41fQojIExlZnQ6IGxvY2F0aW9uIG9mIHRoZSBtaXNzaW5nIGNlbGxzCnBfdmlzIDwtIHZpc19taXNzKGRmX21pc3NpbmcpICsKICBsYWJzKHRpdGxlID0gIk1pc3NpbmcgY2VsbHMiKSArCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSkKCiMgUmlnaHQ6IHNlbmlvcml0eSBvZiB0aGUgcm93cyB3aGVyZSBlYWNoIHZhcmlhYmxlIGlzIG1pc3NpbmcgdmVyc3VzIG9ic2VydmVkCnBfbWFyIDwtIGRmX21pc3NpbmcgJT4lCiAgYmluZF9zaGFkb3coKSAlPiUKICBzZWxlY3QoeXJzLnNpbmNlLnBoZCwgZW5kc193aXRoKCJfTkEiKSwgLXlycy5zaW5jZS5waGRfTkEpICU+JQogIGRyb3BfbmEoeXJzLnNpbmNlLnBoZCkgJT4lCiAgcGl2b3RfbG9uZ2VyKC15cnMuc2luY2UucGhkLCBuYW1lc190byA9ICJ2YXJpYWJsZSIsIHZhbHVlc190byA9ICJzdGF0dXMiKSAlPiUKICBtdXRhdGUodmFyaWFibGUgPSBmYWN0b3Ioc3RyX3JlbW92ZSh2YXJpYWJsZSwgIl9OQSQiKSwgbGV2ZWxzID0gbmFtZXMoZGYpKSwKICAgICAgICAgc3RhdHVzICAgPSBpZl9lbHNlKHN0YXR1cyA9PSAiTkEiLCAiTWlzc2luZyIsICJPYnNlcnZlZCIpKSAlPiUKICBnZ3Bsb3QoYWVzKHN0YXR1cywgeXJzLnNpbmNlLnBoZCwgZmlsbCA9IHN0YXR1cykpICsKICBnZW9tX2JveHBsb3QoYWxwaGEgPSAwLjcsIG91dGxpZXIuc2l6ZSA9IDAuOCkgKwogIGZhY2V0X3dyYXAofiB2YXJpYWJsZSwgbnJvdyA9IDEpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKE1pc3NpbmcgPSAiZmlyZWJyaWNrIiwgT2JzZXJ2ZWQgPSAic3RlZWxibHVlNCIpLCBndWlkZSA9ICJub25lIikgKwogIGxhYnModGl0bGUgPSAiWWVhcnMgc2luY2UgUGhEIGJ5IG1pc3NpbmduZXNzIHN0YXR1cyIsIHggPSBOVUxMLCB5ID0gIlllYXJzIHNpbmNlIFBoRCIpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQoKcF92aXMgfCBwX21hcgpgYGAKCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTV9CiMgTWlzc2luZy1kYXRhIHBhdHRlcm5zOiBibHVlID0gb2JzZXJ2ZWQsIHJlZCA9IG1pc3Npbmc7CiMgbGVmdCA9IG51bWJlciBvZiByb3dzLCByaWdodCA9IG51bWJlciBvZiBtaXNzaW5nIHZhcmlhYmxlcywgYm90dG9tID0gbWlzc2luZyBjZWxscyBwZXIgdmFyaWFibGUKe21kLnBhdHRlcm4oZGZfbWlzc2luZywgcm90YXRlLm5hbWVzID0gVFJVRSl9CmBgYAoKYGBge3J9CiMgTGl0dGxlJ3MgdGVzdC4gSDA6IHRoZSBkYXRhIGFyZSBtaXNzaW5nIGNvbXBsZXRlbHkgYXQgcmFuZG9tIChNQ0FSKQptY2FyX3Rlc3QoZGZfbWlzc2luZykKYGBgCgoKIyMgSW1wdXRhdGlvbgoKIyMjIE1ldGhvZHMKCnwgRmFtaWx5IHwgTWV0aG9kIHwgTnVtZXJpYyB2YXJpYWJsZXMgfCBDYXRlZ29yaWNhbCB2YXJpYWJsZXMgfCAkbSQgfAp8Oi0tLXw6LS0tfDotLS18Oi0tLXw6LS0tOnwKfCBNZWFuLWJhc2VkIHwgYG1lYW5gIHwgbWVhbiAoYG1lYW5gKSB8IG1vZGUgKGBtb2RlYCopIHwgMSB8CnwgfCBgbWVkaWFuYCB8IG1lZGlhbiAoYG1lZGlhbmAqKSB8IG1vZGUgKGBtb2RlYCopIHwgMSB8CnwgUmFuZG9tIHwgYHJhbmRvbWAgfCByYW5kb20gZHJhdyBmcm9tIHRoZSBvYnNlcnZlZCB2YWx1ZXMgKGBzYW1wbGVgKSB8IHNhbWUgfCAyMCB8CnwgUmVncmVzc2lvbiB8IGByZWdyZXNzaW9uYCB8IHByZWRpY3RlZCB2YWx1ZSAoYG5vcm0ucHJlZGljdGApIHwgbG9naXN0aWMgcmVncmVzc2lvbiwgbW9zdCBsaWtlbHkgY2F0ZWdvcnkgKGBwb2x5cmVnLnByZWRpY3RgKikgfCAxIHwKfCB8IGBzdG9jaGFzdGljX3JlZ3Jlc3Npb25gIHwgcHJlZGljdGlvbiArIHJhbmRvbSByZXNpZHVhbCAoYG5vcm0ubm9iYCkgfCBsb2dpc3RpYyByZWdyZXNzaW9uLCBkcmF3IGZyb20gdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIChgbG9ncmVnYCAvIGBwb2x5cmVnYCkgfCAyMCB8CnwgfCBgYmF5ZXNpYW5fcmVncmVzc2lvbmAgfCBCYXllc2lhbiBsaW5lYXIgcmVncmVzc2lvbiAoYG5vcm1gKSB8IGxvZ2lzdGljIHJlZ3Jlc3Npb24sIGRyYXcgKGBsb2dyZWdgIC8gYHBvbHlyZWdgKSB8IDIwIHwKfCBQcmVkaWN0aXZlIE1lYW4gTWF0Y2hpbmcgfCBgcG1tYCB8IHByZWRpY3RpdmUgbWVhbiBtYXRjaGluZyAoYHBtbWApIHwgbG9naXN0aWMgcmVncmVzc2lvbiwgZHJhdyAoYGxvZ3JlZ2AgLyBgcG9seXJlZ2ApIHwgMjAgfAp8IHwgYHBtbV9wb2xyYCB8IHByZWRpY3RpdmUgbWVhbiBtYXRjaGluZyAoYHBtbWApIHwgYXMgYHBtbWAsIGJ1dCBvcmRpbmFsIGxvZ2lzdGljIHJlZ3Jlc3Npb24gZm9yIGByYW5rYCAoYHBvbHJgKSB8IDIwIHwKfCBUcmVlLWJhc2VkIHwgYGNhcnRgIHwgcmVncmVzc2lvbiB0cmVlIChgY2FydGApIHwgY2xhc3NpZmljYXRpb24gdHJlZSAoYGNhcnRgKSB8IDIwIHwKfCB8IGByZmAgfCByYW5kb20gZm9yZXN0IChgcmZgKSB8IHJhbmRvbSBmb3Jlc3QgKGByZmApIHwgMjAgfAoKCgpgYGB7cn0KIyBBcmd1bWVudHMgcGFzc2VkIGJ5IG1pY2U6CiMgICB5IDogdGhlIHZhcmlhYmxlIHRvIGltcHV0ZSAgICAgICAgcnkgOiBUUlVFIHdoZXJlIHkgaXMgb2JzZXJ2ZWQKIyAgIHggOiBtYXRyaXggb2YgcHJlZGljdG9ycyAgICAgICAgICB3eSA6IFRSVUUgd2hlcmUgeSBtdXN0IGJlIGltcHV0ZWQKbWljZS5pbXB1dGUubWVkaWFuIDwtIGZ1bmN0aW9uKHksIHJ5LCB4LCB3eSA9ICFyeSwgLi4uKSB7CiAgcmVwKG1lZGlhbih5W3J5XSksIHN1bSh3eSkpCn0KCm1pY2UuaW1wdXRlLm1vZGUgPC0gZnVuY3Rpb24oeSwgcnksIHgsIHd5ID0gIXJ5LCAuLi4pIHsKICBjb3VudHMgPC0gdGFibGUoeVtyeV0pCiAgcmVwKG5hbWVzKGNvdW50cylbd2hpY2gubWF4KGNvdW50cyldLCBzdW0od3kpKQp9CgojIE1vc3QgbGlrZWx5IGNhdGVnb3J5IGZyb20gYSBtdWx0aW5vbWlhbCBsb2dpdCAoYSBsb2dpc3RpYyByZWdyZXNzaW9uIGZvciB0d28gbGV2ZWxzKQptaWNlLmltcHV0ZS5wb2x5cmVnLnByZWRpY3QgPC0gZnVuY3Rpb24oeSwgcnksIHgsIHd5ID0gIXJ5LCAuLi4pIHsKICB4eSAgPC0gZGF0YS5mcmFtZSh5ID0geSwgeCkKICBmaXQgPC0gbm5ldDo6bXVsdGlub20oeSB+IC4sIGRhdGEgPSB4eVtyeSwgLCBkcm9wID0gRkFMU0VdLCB0cmFjZSA9IEZBTFNFKQogIGFzLmNoYXJhY3RlcihwcmVkaWN0KGZpdCwgbmV3ZGF0YSA9IHh5W3d5LCAsIGRyb3AgPSBGQUxTRV0sIHR5cGUgPSAiY2xhc3MiKSkKfQpgYGAKCiMjIyBSdW5uaW5nIHRoZSBJbXB1dGF0aW9ucwoKQSBkcnkgcnVuIHdpdGggYG1heGl0ID0gMGAgaW1wdXRlcyBub3RoaW5nIGJ1dCByZXR1cm5zIHRoZSBzZXR0aW5ncyBgbWljZWAgd291bGQgdXNlOgoKYGBge3J9CmluaSA8LSBtaWNlKGRmX21pc3NpbmcsIG1heGl0ID0gMCkKCmluaSRtZXRob2QgICAgICAgICAgICAjIGRlZmF1bHQgbWV0aG9kIHBlciB2YXJpYWJsZQppbmkkcHJlZGljdG9yTWF0cml4ICAgIyByb3dzID0gaW1wdXRlZCB2YXJpYWJsZXMsIGNvbHVtbnMgPSBwcmVkaWN0b3JzICgxID0gdXNlZCkKYGBgCgoKYGBge3J9CiMgT25lIG1pY2UgcnVuIHBlciBtZXRob2Q6IG0gPSAyMCBpbXB1dGF0aW9ucyBhbmQgMTAgaXRlcmF0aW9ucyB1bmxlc3Mgc3RhdGVkIG90aGVyd2lzZQppbXB1dGUgPC0gZnVuY3Rpb24obWV0aG9kLCBtID0gMjAsIG1heGl0ID0gMTApIHsKICBtaWNlKGRmX21pc3NpbmcsIG1ldGhvZCA9IG1ldGhvZCwgbSA9IG0sIG1heGl0ID0gbWF4aXQsIHByaW50RmxhZyA9IEZBTFNFKQp9CgojIE9uZSBtZXRob2QgZm9yIHRoZSBudW1lcmljIHZhcmlhYmxlcyBhbmQgb25lIGZvciB0aGUgZmFjdG9ycwojIChjYXRlZ29yaWNhbCA9IE5VTEwga2VlcHMgdGhlIGRlZmF1bHRzIGxvZ3JlZyAvIHBvbHlyZWcpCnVzZV9tZXRob2RzIDwtIGZ1bmN0aW9uKG51bWVyaWMsIGNhdGVnb3JpY2FsID0gTlVMTCkgewogIG1ldGggPC0gaW5pJG1ldGhvZAogIG1ldGhbbnVtX3ZhcnNdIDwtIG51bWVyaWMKICBpZiAoIWlzLm51bGwoY2F0ZWdvcmljYWwpKSBtZXRoW2NhdF92YXJzXSA8LSBjYXRlZ29yaWNhbAogIG1ldGgKfQoKc2V0LnNlZWQoMjAyNikgICAjIG1pY2UgZHJhd3MgcmFuZG9tIHZhbHVlcwppbXB1dGF0aW9ucyA8LSBsaXN0KAogICMgTWVhbi1iYXNlZCAoc2luZ2xlIGltcHV0YXRpb24pOiBudW1lcmljIC0+IG1lYW4gb3IgbWVkaWFuLCBjYXRlZ29yaWNhbCAtPiBtb2RlCiAgbWVhbiAgID0gaW1wdXRlKHVzZV9tZXRob2RzKCJtZWFuIiwgIm1vZGUiKSwgICBtID0gMSwgbWF4aXQgPSAxKSwKICBtZWRpYW4gPSBpbXB1dGUodXNlX21ldGhvZHMoIm1lZGlhbiIsICJtb2RlIiksIG0gPSAxLCBtYXhpdCA9IDEpLAoKICAjIFJhbmRvbSBkcmF3cyBmcm9tIHRoZSBvYnNlcnZlZCB2YWx1ZXMgb2YgZWFjaCB2YXJpYWJsZQogIHJhbmRvbSA9IGltcHV0ZSgic2FtcGxlIiwgbWF4aXQgPSAxKSwKCiAgIyBSZWdyZXNzaW9uOiBkZXRlcm1pbmlzdGljIHByZWRpY3Rpb24sIHByZWRpY3Rpb24gKyBub2lzZSwgQmF5ZXNpYW4gZHJhd3MKICByZWdyZXNzaW9uICAgICAgICAgICAgPSBpbXB1dGUodXNlX21ldGhvZHMoIm5vcm0ucHJlZGljdCIsICJwb2x5cmVnLnByZWRpY3QiKSwgbSA9IDEpLAogIHN0b2NoYXN0aWNfcmVncmVzc2lvbiA9IGltcHV0ZSh1c2VfbWV0aG9kcygibm9ybS5ub2IiKSksCiAgYmF5ZXNpYW5fcmVncmVzc2lvbiAgID0gaW1wdXRlKHVzZV9tZXRob2RzKCJub3JtIikpLAoKICAjIFByZWRpY3RpdmUgbWVhbiBtYXRjaGluZyAodGhlIG1pY2UgZGVmYXVsdCkKICBwbW0gPSBpbXB1dGUodXNlX21ldGhvZHMoInBtbSIpKSwKCiAgIyBUcmVlLWJhc2VkCiAgY2FydCA9IGltcHV0ZSgiY2FydCIpLAogIHJmICAgPSBpbXB1dGUoInJmIiksCgogICMgUE1NIHdpdGggcmFuayBhcyBhbiBvcmRpbmFsIHZhcmlhYmxlOiBwcm9wb3J0aW9uYWwtb2RkcyBsb2dpc3RpYyByZWdyZXNzaW9uCiAgIyAoYWRkZWQgbGFzdCwgc28gdGhhdCB0aGUgcmFuZG9tIGRyYXdzIG9mIHRoZSBydW5zIGFib3ZlIHN0YXkgdGhlIHNhbWUpCiAgcG1tX3BvbHIgPSBpbXB1dGUocmVwbGFjZSh1c2VfbWV0aG9kcygicG1tIiksICJyYW5rIiwgInBvbHIiKSkKKQpgYGAKCgpgYGB7cn0KaW1wdXRhdGlvbnMkcG1tCmBgYAoKVGhlIGltcHV0YXRpb25zIGFyZSBzdG9yZWQgaW4gYCRpbXBgOiBvbmUgcm93IHBlciBtaXNzaW5nIGNlbGwgYW5kIG9uZSBjb2x1bW4gcGVyIGNvbXBsZXRlZCBkYXRhc2V0LiBUaGUgc3ByZWFkIHdpdGhpbiBhIHJvdyBzaG93cyB0aGUgdW5jZXJ0YWludHkgYWJvdXQgdGhhdCBjZWxsLgoKYGBge3J9CmltcHV0YXRpb25zJHBtbSRpbXAkc2FsYXJ5WzE6NiwgMTo4XQpgYGAKCiMjIyBDb252ZXJnZW5jZQoKRXZlcnkgaW1wdXRhdGlvbiBpcyBhIHNlcGFyYXRlIGNoYWluLiBIZWFsdGh5IGNoYWlucyBhcmUgZnJlZWx5IGludGVybWluZ2xlZCBhbmQgc2hvdyBubyB0cmVuZCBvdmVyIHRoZSBpdGVyYXRpb25zLiBgbWljZWAgc3RvcmVzIHRoZSBtZWFuIG9mIHRoZSBpbXB1dGVkIHZhbHVlcyBwZXIgaXRlcmF0aW9uIGFuZCBjaGFpbiBpbiBgY2hhaW5NZWFuYCAoZm9yIGZhY3RvcnMsIHRoZSBtZWFuIG9mIHRoZSBjYXRlZ29yeSBjb2Rlcyk7IGBwbG90KGltcClgIGRyYXdzIHRoZSBzYW1lIHRyYWNlIHBsb3RzIHdpdGggYGxhdHRpY2VgLgoKYGBge3IgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEwfQppdGVyYXRpdmUgPC0gYygic3RvY2hhc3RpY19yZWdyZXNzaW9uIiwgImJheWVzaWFuX3JlZ3Jlc3Npb24iLCAicG1tIiwgInBtbV9wb2xyIiwgImNhcnQiLCAicmYiKQoKY2hhaW5fbWVhbnMgPC0gaW1wdXRhdGlvbnNbaXRlcmF0aXZlXSAlPiUKICBtYXBfZGZyKH4gYXMuZGF0YS5mcmFtZS50YWJsZSgueCRjaGFpbk1lYW4sIHJlc3BvbnNlTmFtZSA9ICJtZWFuIiksIC5pZCA9ICJtZXRob2QiKSAlPiUKICByZW5hbWUodmFyaWFibGUgPSBWYXIxLCBpdGVyYXRpb24gPSBWYXIyLCBjaGFpbiA9IFZhcjMpICU+JQogIG11dGF0ZShtZXRob2QgICAgPSBmYWN0b3IobWV0aG9kLCBsZXZlbHMgPSBpdGVyYXRpdmUpLAogICAgICAgICB2YXJpYWJsZSAgPSBmYWN0b3IodmFyaWFibGUsIGxldmVscyA9IG5hbWVzKGRmKSksCiAgICAgICAgIGl0ZXJhdGlvbiA9IGFzLmludGVnZXIoYXMuY2hhcmFjdGVyKGl0ZXJhdGlvbikpKQoKZ2dwbG90KGNoYWluX21lYW5zLCBhZXMoaXRlcmF0aW9uLCBtZWFuLCBncm91cCA9IGNoYWluLCBjb2xvdXIgPSBjaGFpbikpICsKICBnZW9tX2xpbmUoYWxwaGEgPSAwLjYsIGxpbmV3aWR0aCA9IDAuNCkgKwogIGZhY2V0X2dyaWQodmFyaWFibGUgfiBtZXRob2QsIHNjYWxlcyA9ICJmcmVlX3kiKSArCiAgc2NhbGVfY29sb3VyX3ZpcmlkaXNfZChndWlkZSA9ICJub25lIikgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBjKDEsIDUsIDEwKSkgKwogIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX251bWJlcihzY2FsZV9jdXQgPSBzY2FsZXM6OmN1dF9zaG9ydF9zY2FsZSgpKSkgKwogIGxhYnModGl0bGUgPSAiVHJhY2UgcGxvdHM6IG1lYW4gb2YgdGhlIGltcHV0ZWQgdmFsdWVzIHBlciBpdGVyYXRpb24iLAogICAgICAgc3VidGl0bGUgPSAiT25lIGxpbmUgcGVyIGNoYWluIChpbXB1dGF0aW9uKSIsIHggPSAiSXRlcmF0aW9uIiwgeSA9IE5VTEwpCmBgYAoKVGhlIGNoYWlucyBhcmUgd2VsbCBtaXhlZCBhbmQgc2hvdyBubyBjbGVhciB0cmVuZCwgc28gMTAgaXRlcmF0aW9ucyBhcmUgc3VmZmljaWVudC4KCiMjIyBQbGF1c2liaWxpdHkKCldpdGhvdXQgdGhlIGdyb3VuZCB0cnV0aCwgdGhlIG5leHQgY2hlY2sgaXMgd2hldGhlciB0aGUgaW1wdXRlZCB2YWx1ZXMgYXJlIHBsYXVzaWJsZS4gYG1pY2U6OmNvbXBsZXRlKGltcCwgImxvbmciLCBpbmNsdWRlID0gVFJVRSlgIHN0YWNrcyB0aGUgaW5jb21wbGV0ZSBkYXRhIChgLmltcCA9IDBgKSBhbmQgYWxsIGNvbXBsZXRlZCBkYXRhc2V0cyAoYC5pbXAgPSAxLCAuLi4sIG1gKTsgYGRlbnNpdHlwbG90KGltcClgIGRyYXdzIHRoZSBgbGF0dGljZWAgdmVyc2lvbiBvZiB0aGUgcGxvdCBiZWxvdy4KCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD00fQojIFRSVUUgd2hlcmUgYSBjZWxsIHdhcyByZW1vdmVkLCBpbiBsb25nIGZvcm1hdAptaXNzX2xvbmcgPC0gYXNfdGliYmxlKGlzLm5hKGRmX21pc3NpbmcpKSAlPiUKICBtdXRhdGUoLmlkID0gcm93X251bWJlcigpKSAlPiUKICBwaXZvdF9sb25nZXIoLS5pZCwgbmFtZXNfdG8gPSAidmFyaWFibGUiLCB2YWx1ZXNfdG8gPSAid2FzX21pc3NpbmciKQoKZGVuc2l0eV9kYXRhIDwtIG1pY2U6OmNvbXBsZXRlKGltcHV0YXRpb25zJHBtbSwgYWN0aW9uID0gImxvbmciLCBpbmNsdWRlID0gVFJVRSkgJT4lCiAgbXV0YXRlKC5pZCA9IGFzLmludGVnZXIoLmlkKSkgJT4lCiAgc2VsZWN0KC5pbXAsIC5pZCwgYWxsX29mKG51bV92YXJzKSkgJT4lCiAgcGl2b3RfbG9uZ2VyKGFsbF9vZihudW1fdmFycyksIG5hbWVzX3RvID0gInZhcmlhYmxlIikgJT4lCiAgbGVmdF9qb2luKG1pc3NfbG9uZywgYnkgPSBjKCIuaWQiLCAidmFyaWFibGUiKSkgJT4lCiAgbXV0YXRlKHZhcmlhYmxlID0gZmFjdG9yKHZhcmlhYmxlLCBsZXZlbHMgPSBudW1fdmFycykpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9kZW5zaXR5KGRhdGEgPSBmaWx0ZXIoZGVuc2l0eV9kYXRhLCAuaW1wID4gMCwgd2FzX21pc3NpbmcpLAogICAgICAgICAgICAgICBhZXModmFsdWUsIGdyb3VwID0gLmltcCwgY29sb3VyID0gIkltcHV0ZWQiKSwgbGluZXdpZHRoID0gMC4zLCBrZXlfZ2x5cGggPSAicGF0aCIpICsKICBnZW9tX2RlbnNpdHkoZGF0YSA9IGZpbHRlcihkZW5zaXR5X2RhdGEsIC5pbXAgPT0gMCwgIXdhc19taXNzaW5nKSwKICAgICAgICAgICAgICAgYWVzKHZhbHVlLCBjb2xvdXIgPSAiT2JzZXJ2ZWQiKSwgbGluZXdpZHRoID0gMS4xLCBrZXlfZ2x5cGggPSAicGF0aCIpICsKICBmYWNldF93cmFwKH4gdmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlIikgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzID0gYyhPYnNlcnZlZCA9ICJzdGVlbGJsdWU0IiwgSW1wdXRlZCA9ICJmaXJlYnJpY2siKSwKICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoIk9ic2VydmVkIiwgIkltcHV0ZWQiKSkgKwogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX251bWJlcihzY2FsZV9jdXQgPSBzY2FsZXM6OmN1dF9zaG9ydF9zY2FsZSgpKSkgKwogIGxhYnModGl0bGUgPSAiUE1NOiBvYnNlcnZlZCB2YWx1ZXMgdmVyc3VzIGltcHV0ZWQgdmFsdWVzIiwKICAgICAgIHN1YnRpdGxlID0gIk9uZSByZWQgbGluZSBwZXIgY29tcGxldGVkIGRhdGFzZXQiLCB4ID0gTlVMTCwgeSA9ICJEZW5zaXR5IiwgY29sb3VyID0gTlVMTCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCBheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgoKIyMgRXZhbHVhdGlvbgoKVGhlIHJlbW92ZWQgdmFsdWVzIGFyZSBrbm93biwgc28gZXZlcnkgbWV0aG9kIGNhbiBiZSBjb21wYXJlZCB3aXRoIHRoZSB0cnV0aC4gRm9yIHRoZSBzZXQgJFxtYXRoY2Fse019JCBvZiB0aGUgJG5fe1xtYXRoY2Fse019fSQgcmVtb3ZlZCBjZWxscyBvZiBhIG51bWVyaWMgdmFyaWFibGU6CgokJApcdGV4dHtSTVNFfSA9IFxzcXJ0e1xmcmFjezF9e25fe1xtYXRoY2Fse019fX1cc3VtX3tpIFxpbiBcbWF0aGNhbHtNfX0gKFxoYXR7eX1faSAtIHlfaSleMn0sIFxxcXVhZApcdGV4dHtNQUV9ID0gXGZyYWN7MX17bl97XG1hdGhjYWx7TX19fVxzdW1fe2kgXGluIFxtYXRoY2Fse019fSBcbHZlcnQgXGhhdHt5fV9pIC0geV9pIFxydmVydCwgXHFxdWFkClx0ZXh0e0JpYXN9ID0gXGZyYWN7MX17bl97XG1hdGhjYWx7TX19fVxzdW1fe2kgXGluIFxtYXRoY2Fse019fSAoXGhhdHt5fV9pIC0geV9pKQokJAoKTUFQRSBpcyBub3QgdXNlZCBiZWNhdXNlIGB5cnMuc2VydmljZWAgY29udGFpbnMgemVyb3MuIFRoZSAqKlNEIHJhdGlvKiogKFNEIG9mIHRoZSBpbXB1dGVkIHZhbHVlcyBkaXZpZGVkIGJ5IHRoZSBTRCBvZiB0aGUgdHJ1ZSB2YWx1ZXMpIHNob3dzIHdoZXRoZXIgdGhlIHNwcmVhZCBpcyBwcmVzZXJ2ZWQuIENhdGVnb3JpY2FsIHZhcmlhYmxlcyBhcmUgc2NvcmVkIHdpdGggdGhlICoqYWNjdXJhY3kqKiAoc2hhcmUgb2YgY29ycmVjdGx5IGltcHV0ZWQgY2F0ZWdvcmllcykgYW5kIHRoZSAqKm1hY3JvLUYxKiosIHRoZSBtZWFuIG9mICRcdGV4dHtGMX0gPSAyXCxcdGV4dHtUUH0gLyAoMlwsXHRleHR7VFB9ICsgXHRleHR7RlB9ICsgXHRleHR7Rk59KSQgb3ZlciB0aGUgY2F0ZWdvcmllcy4gRm9yIG1ldGhvZHMgd2l0aCAkbSA9IDIwJCwgZXZlcnkgbWV0cmljIGlzIGNvbXB1dGVkIGZvciBlYWNoIGNvbXBsZXRlZCBkYXRhc2V0IGFuZCB0aGVuIGF2ZXJhZ2VkLgoKYGBge3J9CiMgVHJ1ZSBhbmQgaW1wdXRlZCB2YWx1ZXMgb2YgdGhlIHJlbW92ZWQgY2VsbHM6IG9uZSByb3cgcGVyIG1ldGhvZCwgY29tcGxldGVkIGRhdGFzZXQgYW5kIGNlbGwKcmVtb3ZlZF9jZWxscyA8LSBmdW5jdGlvbih2YXJzLCBjb252ZXJ0ID0gaWRlbnRpdHkpIHsKICBtYXBfZGZyKGltcHV0YXRpb25zLCAuaWQgPSAibWV0aG9kIiwgZnVuY3Rpb24oaW1wKSB7CiAgICBtYXBfZGZyKHZhcnMsIGZ1bmN0aW9uKHYpIHsKICAgICAgcm93cyA8LSBhcy5pbnRlZ2VyKHJvd25hbWVzKGltcCRpbXBbW3ZdXSkpCiAgICAgIGltcCRpbXBbW3ZdXSAlPiUKICAgICAgICBtdXRhdGUoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwgY29udmVydCksCiAgICAgICAgICAgICAgIHZhcmlhYmxlID0gdiwgcm93ID0gcm93cywgYWN0dWFsID0gY29udmVydChkZltbdl1dW3Jvd3NdKSkgJT4lCiAgICAgICAgcGl2b3RfbG9uZ2VyKC1jKHZhcmlhYmxlLCByb3csIGFjdHVhbCksIG5hbWVzX3RvID0gIi5pbXAiLCB2YWx1ZXNfdG8gPSAiaW1wdXRlZCIsCiAgICAgICAgICAgICAgICAgICAgIG5hbWVzX3RyYW5zZm9ybSA9IGFzLmludGVnZXIpCiAgICB9KQogIH0pCn0KCmRmX2ltcHV0ZWRfbnVtIDwtIHJlbW92ZWRfY2VsbHMobnVtX3ZhcnMpCmRmX2ltcHV0ZWRfY2F0IDwtIHJlbW92ZWRfY2VsbHMoY2F0X3ZhcnMsIGNvbnZlcnQgPSBhcy5jaGFyYWN0ZXIpCgpkZl9pbXB1dGVkX251bSAlPiUKICBtdXRhdGUoYWNyb3NzKGMoYWN0dWFsLCBpbXB1dGVkKSwgfiByb3VuZCgueCwgMikpKQpgYGAKCiMjIyBOdW1lcmljYWwgVmFyaWFibGVzOiBSTVNFLCBNQUUgYW5kIEJpYXMKCmBgYHtyIHJvd3MucHJpbnQ9MzB9Cm1ldGhvZF9ncm91cHMgPC0gbGlzdCgKICAiTWVhbi1iYXNlZCIgICAgICAgICAgICAgICA9IGMoIm1lYW4iLCAibWVkaWFuIiksCiAgIlJhbmRvbSIgICAgICAgICAgICAgICAgICAgPSAicmFuZG9tIiwKICAiUmVncmVzc2lvbiIgICAgICAgICAgICAgICA9IGMoInJlZ3Jlc3Npb24iLCAic3RvY2hhc3RpY19yZWdyZXNzaW9uIiwgImJheWVzaWFuX3JlZ3Jlc3Npb24iKSwKICAiUHJlZGljdGl2ZSBNZWFuIE1hdGNoaW5nIiA9IGMoInBtbSIsICJwbW1fcG9sciIpLAogICJUcmVlLWJhc2VkIiAgICAgICAgICAgICAgID0gYygiY2FydCIsICJyZiIpCikKCmZhbWlsaWVzIDwtIGVuZnJhbWUobWV0aG9kX2dyb3VwcywgbmFtZSA9ICJmYW1pbHkiLCB2YWx1ZSA9ICJtZXRob2QiKSAlPiUKICB1bm5lc3QobWV0aG9kKQoKZXZhbHVhdGlvbl9udW0gPC0gZGZfaW1wdXRlZF9udW0gJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCB2YXJpYWJsZSwgLmltcCkgJT4lCiAgc3VtbWFyaXNlKFJNU0UgICAgICAgPSBzcXJ0KG1lYW4oKGltcHV0ZWQgLSBhY3R1YWwpXjIpKSwKICAgICAgICAgICAgTUFFICAgICAgICA9IG1lYW4oYWJzKGltcHV0ZWQgLSBhY3R1YWwpKSwKICAgICAgICAgICAgQmlhcyAgICAgICA9IG1lYW4oaW1wdXRlZCAtIGFjdHVhbCksCiAgICAgICAgICAgIGBTRCByYXRpb2AgPSBzZChpbXB1dGVkKSAvIHNkKGFjdHVhbCksCiAgICAgICAgICAgIC5ncm91cHMgPSAiZHJvcCIpICU+JQogIGdyb3VwX2J5KG1ldGhvZCwgdmFyaWFibGUpICU+JQogIHN1bW1hcmlzZShhY3Jvc3MoYyhSTVNFLCBNQUUsIEJpYXMsIGBTRCByYXRpb2ApLCBtZWFuKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lICAgIyBhdmVyYWdlIG92ZXIgdGhlIG0gZGF0YXNldHMKICBsZWZ0X2pvaW4oZmFtaWxpZXMsIGJ5ID0gIm1ldGhvZCIpICU+JQogIG11dGF0ZSh2YXJpYWJsZSA9IGZhY3Rvcih2YXJpYWJsZSwgbGV2ZWxzID0gbnVtX3ZhcnMpKSAlPiUKICBhcnJhbmdlKHZhcmlhYmxlLCBSTVNFKQoKIyBEaXNwbGF5OiByYW5rZWQgYnkgUk1TRSB3aXRoaW4gZWFjaCB2YXJpYWJsZQpldmFsdWF0aW9uX251bSAlPiUKICBncm91cF9ieSh2YXJpYWJsZSkgJT4lCiAgbXV0YXRlKFJhbmsgPSByb3dfbnVtYmVyKCkpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBtdXRhdGUoYWNyb3NzKGMoUk1TRSwgTUFFLCBCaWFzLCBgU0QgcmF0aW9gKSwgfiByb3VuZCgueCwgMikpKSAlPiUKICBzZWxlY3QoVmFyaWFibGUgPSB2YXJpYWJsZSwgUmFuaywgRmFtaWx5ID0gZmFtaWx5LCBNZXRob2QgPSBtZXRob2QsIFJNU0UsIE1BRSwgQmlhcywgYFNEIHJhdGlvYCkKYGBgCgojIyMgQ2F0ZWdvcmljYWwgVmFyaWFibGVzOiBBY2N1cmFjeSBhbmQgTWFjcm8tRjEKCmBgYHtyIHJvd3MucHJpbnQ9MzB9Cm1hY3JvX2YxIDwtIGZ1bmN0aW9uKGFjdHVhbCwgaW1wdXRlZCkgewogIGNsYXNzZXMgPC0gdW5pb24oYWN0dWFsLCBpbXB1dGVkKQogIG1lYW4obWFwX2RibChjbGFzc2VzLCBmdW5jdGlvbihjbCkgewogICAgdHAgPC0gc3VtKGltcHV0ZWQgPT0gY2wgJiBhY3R1YWwgPT0gY2wpCiAgICBmcCA8LSBzdW0oaW1wdXRlZCA9PSBjbCAmIGFjdHVhbCAhPSBjbCkKICAgIGZuIDwtIHN1bShpbXB1dGVkICE9IGNsICYgYWN0dWFsID09IGNsKQogICAgaWYgKHRwID09IDApIDAgZWxzZSAyICogdHAgLyAoMiAqIHRwICsgZnAgKyBmbikKICB9KSkKfQoKIyBNZXRob2QgdGhhdCBtaWNlIGFjdHVhbGx5IHVzZWQgZm9yIGVhY2ggY2F0ZWdvcmljYWwgdmFyaWFibGUgaW4gZWFjaCBydW4KY2F0X21ldGhvZHMgPC0gaW1wdXRhdGlvbnMgJT4lCiAgbWFwX2Rmcih+IHRpYmJsZSh2YXJpYWJsZSA9IGNhdF92YXJzLCBtb2RlbCA9IHVubmFtZSgueCRtZXRob2RbY2F0X3ZhcnNdKSksIC5pZCA9ICJtZXRob2QiKQoKZXZhbHVhdGlvbl9jYXQgPC0gZGZfaW1wdXRlZF9jYXQgJT4lCiAgZ3JvdXBfYnkobWV0aG9kLCB2YXJpYWJsZSwgLmltcCkgJT4lCiAgc3VtbWFyaXNlKEFjY3VyYWN5ICAgPSBtZWFuKGltcHV0ZWQgPT0gYWN0dWFsKSwKICAgICAgICAgICAgYE1hY3JvLUYxYCA9IG1hY3JvX2YxKGFjdHVhbCwgaW1wdXRlZCksCiAgICAgICAgICAgIC5ncm91cHMgPSAiZHJvcCIpICU+JQogIGdyb3VwX2J5KG1ldGhvZCwgdmFyaWFibGUpICU+JQogIHN1bW1hcmlzZShhY3Jvc3MoYyhBY2N1cmFjeSwgYE1hY3JvLUYxYCksIG1lYW4pLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUKICBsZWZ0X2pvaW4oZmFtaWxpZXMsIGJ5ID0gIm1ldGhvZCIpICU+JQogIGxlZnRfam9pbihjYXRfbWV0aG9kcywgYnkgPSBjKCJtZXRob2QiLCAidmFyaWFibGUiKSkgJT4lCiAgbXV0YXRlKHZhcmlhYmxlID0gZmFjdG9yKHZhcmlhYmxlLCBsZXZlbHMgPSBjYXRfdmFycykpICU+JQogIGFycmFuZ2UodmFyaWFibGUsIGRlc2MoQWNjdXJhY3kpKQoKIyBEaXNwbGF5OiByYW5rZWQgYnkgYWNjdXJhY3kgd2l0aGluIGVhY2ggdmFyaWFibGUKZXZhbHVhdGlvbl9jYXQgJT4lCiAgZ3JvdXBfYnkodmFyaWFibGUpICU+JQogIG11dGF0ZShSYW5rID0gcm93X251bWJlcigpKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKGFjcm9zcyhjKEFjY3VyYWN5LCBgTWFjcm8tRjFgKSwgfiByb3VuZCgueCwgMykpKSAlPiUKICBzZWxlY3QoVmFyaWFibGUgPSB2YXJpYWJsZSwgUmFuaywgRmFtaWx5ID0gZmFtaWx5LCBNZXRob2QgPSBtZXRob2QsCiAgICAgICAgIGBJbXB1dGVkIHdpdGhgID0gbW9kZWwsIEFjY3VyYWN5LCBgTWFjcm8tRjFgKQpgYGAKCgpgYGB7cn0KIyBQcm9wb3J0aW9uYWwtb2RkcyBjaGVjazogc2xvcGUgb2YgeXJzLnNpbmNlLnBoZCBpbiBhIHNlcGFyYXRlIGxvZ2l0IGZvciBlYWNoIGN1dC1vZmYgb2YgcmFuawojICh0aGUgZmlyc3Qgc3BsaXQgaXMgYWxtb3N0IHBlcmZlY3RseSBzZXBhcmF0ZWQsIGhlbmNlIHN1cHByZXNzV2FybmluZ3MpCnNsb3BlIDwtIGZ1bmN0aW9uKGV2ZW50KSB7CiAgZml0IDwtIHN1cHByZXNzV2FybmluZ3MoZ2xtKGV2ZW50IH4geXJzLnNpbmNlLnBoZCwgZmFtaWx5ID0gYmlub21pYWwsIGRhdGEgPSBkZikpCiAgY29lZihmaXQpW1sieXJzLnNpbmNlLnBoZCJdXQp9CgpjKGBBc3N0UHJvZiB8IEFzc29jUHJvZiwgUHJvZmAgPSBzbG9wZShkZiRyYW5rICE9ICJBc3N0UHJvZiIpLAogIGBBc3N0UHJvZiwgQXNzb2NQcm9mIHwgUHJvZmAgPSBzbG9wZShkZiRyYW5rID09ICJQcm9mIikpCmBgYAoKCiMjIyBJbXB1dGVkIFZhbHVlcyBWaXN1YWxpemF0aW9uCgpgYGB7cn0KcGxvdF9pbXB1dGF0aW9ucyA8LSBmdW5jdGlvbihncm91cCkgewogIG1ldGhvZHMgPC0gbWV0aG9kX2dyb3Vwc1tbZ3JvdXBdXQoKICBkYXRhIDwtIGRmX2ltcHV0ZWRfbnVtICU+JQogICAgZmlsdGVyKG1ldGhvZCAlaW4lIG1ldGhvZHMsIC5pbXAgPT0gMSkgJT4lICAgICMgZmlyc3QgY29tcGxldGVkIGRhdGFzZXQKICAgIGxlZnRfam9pbihzZWxlY3QoZXZhbHVhdGlvbl9udW0sIG1ldGhvZCwgdmFyaWFibGUsIFJNU0UpLCBieSA9IGMoIm1ldGhvZCIsICJ2YXJpYWJsZSIpKSAlPiUKICAgIG11dGF0ZShtZXRob2QgICA9IGZhY3RvcihtZXRob2QsIGxldmVscyA9IG1ldGhvZHMpLAogICAgICAgICAgIHZhcmlhYmxlID0gZmFjdG9yKHZhcmlhYmxlLCBsZXZlbHMgPSBudW1fdmFycykpICU+JQogICAgYXJyYW5nZShtZXRob2QsIHZhcmlhYmxlKSAlPiUKICAgIG11dGF0ZShybXNlICA9IGlmX2Vsc2UoUk1TRSA+PSAxMDAsIHNjYWxlczo6Y29tbWEoUk1TRSwgYWNjdXJhY3kgPSAxKSwgc3ByaW50ZigiJS4yZiIsIFJNU0UpKSwKICAgICAgICAgICBwYW5lbCA9IGZjdF9pbm9yZGVyKHNwcmludGYoIiVzXG4lcyAoUk1TRSAlcykiLCBtZXRob2QsIHZhcmlhYmxlLCBybXNlKSkpCgogICMgU2FtZSByYW5nZSBvbiBib3RoIGF4ZXMsIHBlciB2YXJpYWJsZSwgc28gdGhhdCB0aGUgZGFzaGVkIGxpbmUgaXMgdGhlIGRpYWdvbmFsCiAgbGltaXRzIDwtIGRhdGEgJT4lCiAgICBncm91cF9ieSh2YXJpYWJsZSkgJT4lCiAgICBzdW1tYXJpc2UobG93ID0gbWluKGFjdHVhbCwgaW1wdXRlZCksIGhpZ2ggPSBtYXgoYWN0dWFsLCBpbXB1dGVkKSkgJT4lCiAgICByaWdodF9qb2luKGRpc3RpbmN0KGRhdGEsIHBhbmVsLCB2YXJpYWJsZSksIGJ5ID0gInZhcmlhYmxlIikgJT4lCiAgICBwaXZvdF9sb25nZXIoYyhsb3csIGhpZ2gpLCB2YWx1ZXNfdG8gPSAidmFsdWUiKQoKICBnZ3Bsb3QoZGF0YSwgYWVzKGFjdHVhbCwgaW1wdXRlZCkpICsKICAgIGdlb21fYmxhbmsoZGF0YSA9IGxpbWl0cywgYWVzKHZhbHVlLCB2YWx1ZSkpICsKICAgIGdlb21fYWJsaW5lKGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG91ciA9ICJncmV5NTAiKSArCiAgICBnZW9tX3BvaW50KGNvbG91ciA9ICJmaXJlYnJpY2siLCBhbHBoYSA9IDAuNywgc2l6ZSA9IDEuNSkgKwogICAgZmFjZXRfd3JhcCh+IHBhbmVsLCBzY2FsZXMgPSAiZnJlZSIsIG5jb2wgPSAzKSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpsYWJlbF9udW1iZXIoc2NhbGVfY3V0ID0gc2NhbGVzOjpjdXRfc2hvcnRfc2NhbGUoKSkpICsKICAgIHNjYWxlX3lfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OmxhYmVsX251bWJlcihzY2FsZV9jdXQgPSBzY2FsZXM6OmN1dF9zaG9ydF9zY2FsZSgpKSkgKwogICAgbGFicyh0aXRsZSAgICA9IGdyb3VwLAogICAgICAgICBzdWJ0aXRsZSA9ICJSZW1vdmVkIGNlbGxzLCBmaXJzdCBjb21wbGV0ZWQgZGF0YXNldC4gRGFzaGVkIGxpbmUgPSBwZXJmZWN0IGltcHV0YXRpb24iLAogICAgICAgICB4ID0gIlRydWUgdmFsdWUiLCB5ID0gIkltcHV0ZWQgdmFsdWUiKSArCiAgICB0aGVtZShhc3BlY3QucmF0aW8gPSAxKQp9CmBgYAoKIyMjIyBNZWFuLWJhc2VkCgpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9OH0KcGxvdF9pbXB1dGF0aW9ucygiTWVhbi1iYXNlZCIpCmBgYAoKIyMjIyBSYW5kb20KCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD00LjN9CnBsb3RfaW1wdXRhdGlvbnMoIlJhbmRvbSIpCmBgYAoKIyMjIyBSZWdyZXNzaW9uCgpgYGB7ciBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTEuNX0KcGxvdF9pbXB1dGF0aW9ucygiUmVncmVzc2lvbiIpCmBgYAoKIyMjIyBQcmVkaWN0aXZlIE1lYW4gTWF0Y2hpbmcKCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD04fQpwbG90X2ltcHV0YXRpb25zKCJQcmVkaWN0aXZlIE1lYW4gTWF0Y2hpbmciKQpgYGAKCiMjIyMgVHJlZS1iYXNlZAoKYGBge3IgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTh9CnBsb3RfaW1wdXRhdGlvbnMoIlRyZWUtYmFzZWQiKQpgYGAKCgoKIyMjIERpc3RyaWJ1dGlvbnMgYW5kIENhdGVnb3J5IFNoYXJlcwoKQSBnb29kIGltcHV0YXRpb24gbWV0aG9kIHNob3VsZCBhbHNvIHJlcHJvZHVjZSB0aGUgKipkaXN0cmlidXRpb24qKiBvZiB0aGUgbWlzc2luZyB2YWx1ZXMsIG5vdCBvbmx5IHRoZWlyIGNlbnRyZS4KCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02fQpmYW1pbHlfY29sb3VycyA8LSBjKCJNZWFuLWJhc2VkIiA9ICIjRTY5RjAwIiwgIlJhbmRvbSIgPSAiI0NDNzlBNyIsICJSZWdyZXNzaW9uIiA9ICIjRDU1RTAwIiwKICAgICAgICAgICAgICAgICAgICAiUHJlZGljdGl2ZSBNZWFuIE1hdGNoaW5nIiA9ICIjMDA3MkIyIiwgIlRyZWUtYmFzZWQiID0gIiMwMDlFNzMiLAogICAgICAgICAgICAgICAgICAgICJUcnVlIHZhbHVlcyIgPSAiZ3JleTcwIikKbWV0aG9kX2xldmVscyA8LSB1bmxpc3QobWV0aG9kX2dyb3VwcywgdXNlLm5hbWVzID0gRkFMU0UpICAgIyBtZXRob2RzIG9yZGVyZWQgYnkgZmFtaWx5Cgp0cnVlX251bSA8LSBkZl9pbXB1dGVkX251bSAlPiUKICBkaXN0aW5jdCh2YXJpYWJsZSwgcm93LCBhY3R1YWwpICU+JQogIHRyYW5zbXV0ZShtZXRob2QgPSAidHJ1ZSB2YWx1ZXMiLCBmYW1pbHkgPSAiVHJ1ZSB2YWx1ZXMiLCB2YXJpYWJsZSwgdmFsdWUgPSBhY3R1YWwpCgpkZl9pbXB1dGVkX251bSAlPiUKICBsZWZ0X2pvaW4oZmFtaWxpZXMsIGJ5ID0gIm1ldGhvZCIpICU+JQogIHRyYW5zbXV0ZShtZXRob2QsIGZhbWlseSwgdmFyaWFibGUsIHZhbHVlID0gaW1wdXRlZCkgJT4lICAgICMgYWxsIGNvbXBsZXRlZCBkYXRhc2V0cwogIGJpbmRfcm93cyh0cnVlX251bSkgJT4lCiAgbXV0YXRlKG1ldGhvZCAgID0gZmFjdG9yKG1ldGhvZCwgbGV2ZWxzID0gcmV2KGMoInRydWUgdmFsdWVzIiwgbWV0aG9kX2xldmVscykpKSwKICAgICAgICAgZmFtaWx5ICAgPSBmYWN0b3IoZmFtaWx5LCBsZXZlbHMgPSBuYW1lcyhmYW1pbHlfY29sb3VycykpLAogICAgICAgICB2YXJpYWJsZSA9IGZhY3Rvcih2YXJpYWJsZSwgbGV2ZWxzID0gbnVtX3ZhcnMpKSAlPiUKICBnZ3Bsb3QoYWVzKHZhbHVlLCBtZXRob2QsIGZpbGwgPSBmYW1pbHkpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2l6ZSA9IDAuNiwgYWxwaGEgPSAwLjg1KSArCiAgZmFjZXRfd3JhcCh+IHZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZV94IikgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGZhbWlseV9jb2xvdXJzKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxhYmVscyA9IHNjYWxlczo6bGFiZWxfbnVtYmVyKHNjYWxlX2N1dCA9IHNjYWxlczo6Y3V0X3Nob3J0X3NjYWxlKCkpKSArCiAgbGFicyh0aXRsZSA9ICJJbXB1dGVkIHZhbHVlcyB2ZXJzdXMgdGhlIHJlbW92ZWQgdHJ1ZSB2YWx1ZXMiLAogICAgICAgeCA9IE5VTEwsIHkgPSBOVUxMLCBmaWxsID0gTlVMTCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xMiwgZmlnLmhlaWdodD02fQpjYXRlZ29yeV9jb2xvdXJzIDwtIGMoQXNzdFByb2YgPSAiI2M2ZGJlZiIsIEFzc29jUHJvZiA9ICIjNmJhZWQ2IiwgUHJvZiA9ICIjMDg1MTljIiwKICAgICAgICAgICAgICAgICAgICAgIEEgPSAiI2ExZDk5YiIsIEIgPSAiIzIzOGI0NSIsIEZlbWFsZSA9ICIjZmRhZTZiIiwgTWFsZSA9ICIjZDk0ODAxIikKCnRydWVfY2F0IDwtIGRmX2ltcHV0ZWRfY2F0ICU+JQogIGRpc3RpbmN0KHZhcmlhYmxlLCByb3csIGFjdHVhbCkgJT4lCiAgdHJhbnNtdXRlKG1ldGhvZCA9ICJ0cnVlIHZhbHVlcyIsIHZhcmlhYmxlLCBjYXRlZ29yeSA9IGFjdHVhbCkKCiMgUmVmZXJlbmNlOiBjYXRlZ29yaWVzIG9mIHRoZSBvYnNlcnZlZCAobm90IHJlbW92ZWQpIGNlbGxzCm9ic19jYXQgPC0gbWFwX2RmcihjYXRfdmFycywgfiB0aWJibGUobWV0aG9kID0gIm9ic2VydmVkIHZhbHVlcyIsIHZhcmlhYmxlID0gLngsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNhdGVnb3J5ID0gYXMuY2hhcmFjdGVyKG5hLm9taXQoZGZfbWlzc2luZ1tbLnhdXSkpKSkKCiMgTGFiZWxzIHdpdGggdGhlIG1ldGhvZCB1c2VkIGZvciB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzLCBlLmcuICJwbW0gKHBvbHlyZWcvbG9ncmVnKSIKY2F0X2xhYmVscyA8LSBjYXRfbWV0aG9kcyAlPiUKICBncm91cF9ieShtZXRob2QpICU+JQogIHN1bW1hcmlzZShtb2RlbHMgPSBwYXN0ZSh1bmlxdWUobW9kZWwpLCBjb2xsYXBzZSA9ICIvIikpICU+JQogIG11dGF0ZShsYWJlbCA9IGlmX2Vsc2UobW9kZWxzID09IG1ldGhvZCwgbWV0aG9kLCBwYXN0ZTAobWV0aG9kLCAiICgiLCBtb2RlbHMsICIpIikpKQoKcGxvdF9sZXZlbHMgPC0gYygidHJ1ZSB2YWx1ZXMiLCAib2JzZXJ2ZWQgdmFsdWVzIiwgbWV0aG9kX2xldmVscykKcGxvdF9sYWJlbHMgPC0gYygidHJ1ZSB2YWx1ZXMiLCAib2JzZXJ2ZWQgdmFsdWVzIiwKICAgICAgICAgICAgICAgICBjYXRfbGFiZWxzJGxhYmVsW21hdGNoKG1ldGhvZF9sZXZlbHMsIGNhdF9sYWJlbHMkbWV0aG9kKV0pCgpkZl9pbXB1dGVkX2NhdCAlPiUKICB0cmFuc211dGUobWV0aG9kLCB2YXJpYWJsZSwgY2F0ZWdvcnkgPSBpbXB1dGVkKSAlPiUKICBiaW5kX3Jvd3ModHJ1ZV9jYXQsIG9ic19jYXQpICU+JQogIGNvdW50KG1ldGhvZCwgdmFyaWFibGUsIGNhdGVnb3J5KSAlPiUKICBncm91cF9ieShtZXRob2QsIHZhcmlhYmxlKSAlPiUKICBtdXRhdGUoc2hhcmUgPSBuIC8gc3VtKG4pKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKG1ldGhvZCAgID0gZmFjdG9yKG1ldGhvZCwgbGV2ZWxzID0gcmV2KHBsb3RfbGV2ZWxzKSwgbGFiZWxzID0gcmV2KHBsb3RfbGFiZWxzKSksCiAgICAgICAgIHZhcmlhYmxlID0gZmFjdG9yKHZhcmlhYmxlLCBsZXZlbHMgPSBjYXRfdmFycyksCiAgICAgICAgIGNhdGVnb3J5ID0gZmFjdG9yKGNhdGVnb3J5LCBsZXZlbHMgPSBuYW1lcyhjYXRlZ29yeV9jb2xvdXJzKSkpICU+JQogIGdncGxvdChhZXMoc2hhcmUsIG1ldGhvZCwgZmlsbCA9IGNhdGVnb3J5KSkgKwogIGdlb21fY29sKHdpZHRoID0gMC43NSwgcG9zaXRpb24gPSBwb3NpdGlvbl9zdGFjayhyZXZlcnNlID0gVFJVRSkpICsKICBmYWNldF93cmFwKH4gdmFyaWFibGUpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGFiZWxzID0gc2NhbGVzOjpwZXJjZW50KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY2F0ZWdvcnlfY29sb3VycykgKwogIGd1aWRlcyhmaWxsID0gZ3VpZGVfbGVnZW5kKG5yb3cgPSAxKSkgKwogIGxhYnModGl0bGUgPSAiQ2F0ZWdvcnkgc2hhcmVzIGFtb25nIHRoZSBpbXB1dGVkIGNlbGxzIiwKICAgICAgIHN1YnRpdGxlID0gIkluIGJyYWNrZXRzOiB0aGUgbWV0aG9kIHVzZWQgZm9yIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMiLAogICAgICAgeCA9ICJTaGFyZSIsIHkgPSBOVUxMLCBmaWxsID0gTlVMTCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQpgYGA=