Libraries

library(tidyverse)
library(mice)
library(naniar)
library(patchwork)

theme_set(
  theme_minimal(base_size = 12) +
    theme(plot.title       = element_text(face = "bold"),
          strip.text       = element_text(face = "bold"),
          panel.grid.minor = element_blank())
)

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

Variable Type Description
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

Family Method Numeric variables Categorical variables m
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=