J. Taroni 2018

In this notebook, we will be plotting:

  • The number of latent variables for models trained on 1) different biological contexts 2) random subsampling (various sample sizes) and the 3) full recount2 model (MultiPLIER)

  • The pathway coverage and proportion of the latent variables associated with pathways from the same three conditions

Set up

# magrittr pipe
`%>%` <- dplyr::`%>%`

We’ll use ggplot2 for plotting.

library(ggplot2)

Custom functions

# custom ggplot2 theme
custom_theme <- function() {
  ggplot2::theme_bw() +
    ggplot2::theme(axis.text.x = ggplot2::element_text(angle = 45, 
                                                       hjust = 1),
                   legend.position = "none",
                   plot.title = ggplot2::element_text(hjust = 0.5),
                   axis.title.x = ggplot2::element_blank(),
                   text = ggplot2::element_text(size = 15)) 
}
# this expects that data.frames will already be cleaned and re-ordered!
# all data.frames must have the same column for use as the y.variable 
# (e.g., matching y.variable.name)
ThreePlotsWrapper <- function(size.df, context.df, recount2.df,
                              y.variable.name, y.label, y.limits = "c(0, 1)") {
  # Given results for sample size, biological context, and MultiPLIER make
  # plots for the three conditions (using custom_theme from above)
  # 
  # Args:
  #   size.df: data.frame with the sample size results, requires a column named
  #          "sample_size"
  #   context.df: data.frame with the biological context results, requires a 
  #             column named "biological_context"
  #   recount2.df: data.frame with multiplier results, requires a column named
  #              "condition"
  #   y.variable.name: the column name, shared by all three data.frames, that
  #                    will be used as the y variable in the plots
  #   y.label: what should the y-axis label be?
  #   y.limits: passed to ggplot2::ylim, should follow the format 
  #             "c(<LOWER LIMIT>, <UPPER LIMIT>)"
  #
  # Returns: 
  #   a list of the follow plots: size, context, recount2
  
  # standardized geom_boxplot + geom_jitter plot for sample size and biological
  # context plots
  BoxplotJitter <- function(df, x.var, y.var, y.lim){
    ggplot(data = df, aes_string(x = x.var, 
                                 y = y.var,
                                 group = x.var)) +
      # will be using jitter so including an outlier shape in the boxplot could
      # confuse things a bit
      geom_boxplot(outlier.shape = NA) +
      geom_jitter(alpha = 0.5, width = 0.3) +
      custom_theme() +
      ylim(eval(parse(text = y.lim)))
  }
  
  size.plot <- size.df %>%
    BoxplotJitter(x.var = "sample_size",
                  y.var = y.variable.name,
                  y.lim = y.limits) +
    # only the sample size plot which will be on the left in multipanel figures
    # will have a y-axis label
    labs(y = y.label, title = "Sample Size")
  
  context.plot <- context.df %>%
    BoxplotJitter(x.var = "biological_context",
                  y.var = y.variable.name,
                  y.lim = y.limits) +
      labs(y = NULL, title = "Biological Context")
  
  # the multiplier plot is a single point. the custom theme is shared.
  recount2.plot <- recount2.df %>%
    ggplot(aes_string(x = "condition", y = y.variable.name)) +
    geom_point(fill = "#0000FF", shape = 23, size = 4) +
    custom_theme() +
    ylim(eval(parse(text = y.limits))) +
    labs(y = NULL, title = "MultiPLIER")
  
  # return a list of the three plots
  return(list("size" = size.plot,
              "context" = context.plot,
              "recount2" = recount2.plot))
}
MultiPanelWrapper <- function(size.plot, context.plot, recount2.plot, 
                              plot.title, output.file) {
  # given the plots for the sample size experiment, the biological context
  # experiment and the full MultiPLIER model, respectively, make a multipanel
  # plot titled with the plot.title argument and saved as an 11"W x 5"H plot
  # in the location specified by output.file
  
  # for the three conditions, make a 3 column multipanel plot
  panels <- cowplot::plot_grid(size.plot, context.plot, recount2.plot, 
                               align = "h", 
                               # the size plot will have the y-axis label
                               # and the MultiPLIER panel should be quite s
                               # small
                               rel_widths = c(1.125, 1, 0.375), 
                               ncol = 3)
  
  # the title "panel"
  p.title <- cowplot::ggdraw() + 
    cowplot::draw_label(plot.title, fontface = "bold", 
                        size = 20)
  
  # combine to get the final plot
  final.plot <- cowplot::plot_grid(p.title, panels, ncol = 1, 
                                   rel_heights = c(0.1, 1))
  
  # save 11"W x 5"H file (the type of file is determined by the file extension
  # provided in output.file)
  ggsave(output.file, plot = final.plot, width = 11, height = 5)
  
}

Directory setup

# plot and result directory setup for this notebook
plot.dir <- file.path("plots", "31")
dir.create(plot.dir, recursive = TRUE, showWarnings = FALSE)
results.dir <- file.path("results", "31")
dir.create(results.dir, recursive = TRUE, showWarnings = FALSE)

Read in data

For both the biological context and sample size evaluations, the results we will be plotting are from 30-evaluate_sample_size_and_biological_context.

Biological context

# pathway coverage and proportion of LVs associated with pathways
context.coverage.file <- file.path("results", "30", 
                                   "biological_context_pathway_coverage.tsv")
# we'll want to reorder the biological contexts by sample size
context.levels <- c("blood", "cancer", "tissue", "cell line", "other tissues")
context.coverage.df <- readr::read_tsv(context.coverage.file) %>%
  # clean up the context information for plotting
  dplyr::mutate(biological_context = gsub("_", " ", biological_context)) %>%
  # reorder by the sample size (low -> high)
  dplyr::mutate(biological_context = factor(biological_context,
                                            levels = context.levels))
Parsed with column specification:
cols(
  value = col_double(),
  metric = col_character(),
  seed = col_integer(),
  biological_context = col_character()
)
# number of latent variables
context.num.file <- file.path("results", "30", 
                              "biological_context_number_of_lvs.tsv")
context.num.df <- readr::read_tsv(context.num.file) %>%
  # clean up the context information for plotting
  dplyr::mutate(biological_context = gsub("_", " ", biological_context)) %>%
  # reorder by the sample size (low -> high)
  dplyr::mutate(biological_context = factor(biological_context,
                                            levels = context.levels))
Parsed with column specification:
cols(
  number_of_latent_variables = col_integer(),
  seed = col_integer(),
  biological_context = col_character()
)

Sample size

# pathway coverage and proportion of LVs associated with pathways
size.coverage.file <- file.path("results", "30", 
                                "subsampled_pathway_coverage.tsv")
# order by sample size
size.levels <- c("500", "1000", "2000", "4000", "8000", "16000", "32000")
# for plotting, we want the sample size as a factor (otherwise it's hard to
# see what is happening on the lower end of things) but to order by the integer
# value
size.coverage.df <- readr::read_tsv(size.coverage.file) %>%
    dplyr::mutate(sample_size = factor(sample_size, levels = size.levels))
Parsed with column specification:
cols(
  value = col_double(),
  metric = col_character(),
  seed = col_integer(),
  sample_size = col_integer()
)
# Number of latent variables
size.num.file <- file.path("results", "30",
                           "subsampled_number_of_lvs.tsv")
size.num.df <- readr::read_tsv(size.num.file) %>%
      dplyr::mutate(sample_size = factor(sample_size, levels = size.levels))
Parsed with column specification:
cols(
  number_of_latent_variables = col_integer(),
  seed = col_integer(),
  sample_size = col_integer()
)

MultiPLIER

For the full recount2 bits, we’ll use the same values that are in figure_notebooks/subsampling_figures. (The full model was trained on ~37K samples, because we subset recount2 to include only those samples with metadata.)

# number of latent variables in the MultiPLIER model
recount2.num.lvs <- 987
# pathway coverage
recount2.coverage <- 0.4187898
# proportion of the latent variables associated with a pathway
recount2.lv.prop <- 0.2016211

Make into data.frames for plotting purposes. We’ll always use “condition” as one of the column names (see ThreePlotsWrapper above).

# proportion of LVs associated with pathways
recount2.prop.df <- data.frame(value = recount2.lv.prop,
                               condition = "full recount2")
# pathway coverage
recount2.coverage.df <- data.frame(value = recount2.coverage,
                                   condition = "full recount2")
# number of latent variables
recount2.num.df <- 
  data.frame(number_of_latent_variables = as.integer(recount2.num.lvs),
             condition = "full recount2")

Plotting

Number of latent variables

num.plots <- ThreePlotsWrapper(size.df = size.num.df,
                               context.df = context.num.df,
                               recount2.df = recount2.num.df,
                               y.variable.name = "number_of_latent_variables",
                               y.label = "number of latent variables",
                               y.limits = "c(0, 1000)")

Save to file

MultiPanelWrapper(size.plot = num.plots$size,
                  context.plot = num.plots$context,
                  recount2.plot = num.plots$recount2,
                  plot.title = "Number of Latent Variables",
                  output.file = file.path(plot.dir, 
                                          "number_of_latent_variables.pdf"))

Pathway coverage

The sample size and biological context data.frames contain the information about pathway coverage and the proportion of LVs associated with a pathway, so we’ll need to filter them to the correct metric.

coverage.plots <- 
  ThreePlotsWrapper(size.df = dplyr::filter(size.coverage.df,
                                            metric == "pathway coverage"),
                    context.df = dplyr::filter(context.coverage.df,
                                               metric == "pathway coverage"),
                    recount2.df = recount2.coverage.df,
                    y.variable.name = "value",
                    y.label = "proportion of input pathways")
MultiPanelWrapper(size.plot = coverage.plots$size,
                  context.plot = coverage.plots$context,
                  recount2.plot = coverage.plots$recount2,
                  plot.title = "Pathway Coverage",
                  output.file = file.path(plot.dir, "pathway_coverage.pdf"))

Latent variables significantly associated with pathways

# for line length/style purposes
metric.to.use <- "LV associated with pathways"
prop.plots <- 
  ThreePlotsWrapper(size.df = dplyr::filter(size.coverage.df,
                                            metric == metric.to.use),
                    context.df = dplyr::filter(context.coverage.df,
                                               metric == metric.to.use),
                    recount2.df = recount2.coverage.df,
                    y.variable.name = "value",
                    y.label = "proportion of latent variables")
MultiPanelWrapper(size.plot = prop.plots$size,
                  context.plot = prop.plots$context,
                  recount2.plot = prop.plots$recount2,
                  plot.title = "LVs significantly associated with pathways",
                  output.file = file.path(plot.dir, "lv_proportion.pdf"))

Following up on the proportion of LVs associated with pathways results

If we look at the prop.plots

prop.plots$size

prop.plots$context

prop.plots$recount2

We can see in the sample size plot that, generally speaking, the larger the sample size, the lower the proportion of latent variables that are associated with at least one pathway.

PLIER::PLIER has a parameter, frac, that determines the fraction of latent variables that meets that criterion. (frac = 0.7 by default in the version of PLIER we have on this Docker container; we did not specify frac.)

This parameter is used to set L3, which is the penalty on U that controls sparsity (Mao, et al. bioRxiv. 2017.). Based on my understanding, this controls the proportion of positive entries in U (i.e., the number of gene sets that an LV has some association with), but it does not guarantee that all of these associations will be significant (Mao, et al. bioRxiv. 2017.) and the cutoff we happen to be using (FDR < 0.05) is not taken into account.

For reference, this is the sample size information for these different training sets:

Biological condition Sample size
blood 3862
cancer 8807
tissue 12396
cell line 14532
other tissues (not blood) 32984

Once we take the biological context experiments into account, it gets a bit more interesting:

  • The blood training set has ~4000 samples, but models trained on blood have a much higher proportion of latent variables significantly associated with pathways than the models trained on 4000 randomly selected samples. We’d expect that there’d be less “technical noise” and more “biological signal” in the blood training set. It’s also likely to be a function of the pathways under consideration. Specifically, we supply immune cell-related gene sets to the models, and we expect blood samples to be particularly relevant for those gene sets.
  • It looks like the more latent variables, the lower proportion of LVs significantly associated with a pathway based on the sample size results alone. And it’s certainly the case that the more LVs -> the more pathways that are captured by the model (higher pathway coverage). However, tissue models have higher number of LVs and more pathway coverage than cell line models but they have approximately the same proportion of LVs significantly associated with a pathway. These values are also perhaps a bit lower than we would expect based on sample size alone (looking at 16000 from sample size plot).

Check U sparsity

To follow up on the observations above, let’s check that, in practice, the fraction of the LVs that have at least one association with a pathway should be ~0.7. First, removing everything from the workspace.

rm(list = ls())

Custom functions just for this

# this is intended to be used with lists of models output from
# scripts/subsampling_PLIER.R
GetSparsityList <- function(list.of.models) {
  # we assume that the list of models has names
  
  # these sparsity calculations are different from what we've done before
  CheckUSparsity <- function(plier.model) {
    u.matrix <- plier.model$U
    # what proportion of values are positive?
    total.positive <- sum(u.matrix > 0) / (dim(u.matrix)[1] * dim(u.matrix)[2])
    # what proportion of columns have at least 1 positive value (association)?
    col.wise <- sum(apply(u.matrix, 2, function(x) any(x > 0))) / ncol(u.matrix)
    return(list("overall" = total.positive,
                "column_wise" = col.wise))
  }
  
  sparsity.list <- lapply(list.of.models, 
                          function(x) CheckUSparsity(x$PLIER))
  
}
# we'll use this twice -- once for biological context and once for sample size
# we assume that the files.vector is named
GetResults <- function(files.vector) {
  lapply(files.vector, 
         function(x) {
           # for each file, read in the list of models
           model.list <- readRDS(x)
           # get the sparsity list for the current list of models
           GetSparsityList(model.list)
         })
}

Files

models.dir <- "models"
# sample size
size.model.files <- list.files(models.dir, pattern = "subsampled", 
                               full.names = TRUE)
names(size.model.files) <- sub(".RDS", "", sub(".*[_]", "", size.model.files))
# biological context
context.model.files <- list.files(models.dir, pattern = "accessions",
                                  full.names = TRUE)
names(context.model.files) <- 
  stringr::str_match(context.model.files, "recount2_(.*?)_accessions")[, 2]

Check sparsity

size.results <- GetResults(size.model.files)
size.df <- reshape2::melt(size.results)
summary(dplyr::filter(size.df, L3 == "column_wise")$value)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.6667  0.6983  0.7010  0.7019  0.7093  0.7273 
context.results <- GetResults(context.model.files)
context.df <- reshape2::melt(context.results)
summary(dplyr::filter(context.df, L3 == "column_wise")$value)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
 0.6824  0.6953  0.6989  0.6978  0.7016  0.7072 

These results are consistent with our expectations.

LS0tCnRpdGxlOiAiUGxvdHRpbmc6IHNhbXBsZSBzaXplIGFuZCBiaW9sb2dpY2FsIGNvbnRleHQgcGF0aHdheSBjb3ZlcmFnZSwgbnVtYmVyCm9mIGxhdGVudCB2YXJpYWJsZXMiCm91dHB1dDogICAKICBodG1sX25vdGVib29rOiAKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKKipKLiBUYXJvbmkgMjAxOCoqCgpJbiB0aGlzIG5vdGVib29rLCB3ZSB3aWxsIGJlIHBsb3R0aW5nOgoKKiBUaGUgbnVtYmVyIG9mIGxhdGVudCB2YXJpYWJsZXMgZm9yIG1vZGVscyB0cmFpbmVkIG9uIDEpIGRpZmZlcmVudCBiaW9sb2dpY2FsIApjb250ZXh0cyAyKSByYW5kb20gc3Vic2FtcGxpbmcgKHZhcmlvdXMgc2FtcGxlIHNpemVzKSBhbmQgdGhlIDMpIGZ1bGwgcmVjb3VudDIgCm1vZGVsIChNdWx0aVBMSUVSKQoKKiBUaGUgcGF0aHdheSBjb3ZlcmFnZSBhbmQgcHJvcG9ydGlvbiBvZiB0aGUgbGF0ZW50IHZhcmlhYmxlcyBhc3NvY2lhdGVkIHdpdGgKcGF0aHdheXMgZnJvbSB0aGUgc2FtZSB0aHJlZSBjb25kaXRpb25zCgojIyBTZXQgdXAKCmBgYHtyfQojIG1hZ3JpdHRyIHBpcGUKYCU+JWAgPC0gZHBseXI6OmAlPiVgCmBgYAoKV2UnbGwgdXNlIGBnZ3Bsb3QyYCBmb3IgcGxvdHRpbmcuCgpgYGB7cn0KbGlicmFyeShnZ3Bsb3QyKQpgYGAKCiMjIyMgQ3VzdG9tIGZ1bmN0aW9ucwoKYGBge3J9CiMgY3VzdG9tIGdncGxvdDIgdGhlbWUKY3VzdG9tX3RoZW1lIDwtIGZ1bmN0aW9uKCkgewogIGdncGxvdDI6OnRoZW1lX2J3KCkgKwogICAgZ2dwbG90Mjo6dGhlbWUoYXhpcy50ZXh0LnggPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoanVzdCA9IDEpLAogICAgICAgICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgICAgICAgICAgICAgcGxvdC50aXRsZSA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgICBheGlzLnRpdGxlLnggPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICB0ZXh0ID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KHNpemUgPSAxNSkpIAp9CmBgYAoKYGBge3J9CiMgdGhpcyBleHBlY3RzIHRoYXQgZGF0YS5mcmFtZXMgd2lsbCBhbHJlYWR5IGJlIGNsZWFuZWQgYW5kIHJlLW9yZGVyZWQhCiMgYWxsIGRhdGEuZnJhbWVzIG11c3QgaGF2ZSB0aGUgc2FtZSBjb2x1bW4gZm9yIHVzZSBhcyB0aGUgeS52YXJpYWJsZSAKIyAoZS5nLiwgbWF0Y2hpbmcgeS52YXJpYWJsZS5uYW1lKQpUaHJlZVBsb3RzV3JhcHBlciA8LSBmdW5jdGlvbihzaXplLmRmLCBjb250ZXh0LmRmLCByZWNvdW50Mi5kZiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeS52YXJpYWJsZS5uYW1lLCB5LmxhYmVsLCB5LmxpbWl0cyA9ICJjKDAsIDEpIikgewogICMgR2l2ZW4gcmVzdWx0cyBmb3Igc2FtcGxlIHNpemUsIGJpb2xvZ2ljYWwgY29udGV4dCwgYW5kIE11bHRpUExJRVIgbWFrZQogICMgcGxvdHMgZm9yIHRoZSB0aHJlZSBjb25kaXRpb25zICh1c2luZyBjdXN0b21fdGhlbWUgZnJvbSBhYm92ZSkKICAjIAogICMgQXJnczoKICAjICAgc2l6ZS5kZjogZGF0YS5mcmFtZSB3aXRoIHRoZSBzYW1wbGUgc2l6ZSByZXN1bHRzLCByZXF1aXJlcyBhIGNvbHVtbiBuYW1lZAogICMgICAgICAgICAgInNhbXBsZV9zaXplIgogICMgICBjb250ZXh0LmRmOiBkYXRhLmZyYW1lIHdpdGggdGhlIGJpb2xvZ2ljYWwgY29udGV4dCByZXN1bHRzLCByZXF1aXJlcyBhIAogICMgICAgICAgICAgICAgY29sdW1uIG5hbWVkICJiaW9sb2dpY2FsX2NvbnRleHQiCiAgIyAgIHJlY291bnQyLmRmOiBkYXRhLmZyYW1lIHdpdGggbXVsdGlwbGllciByZXN1bHRzLCByZXF1aXJlcyBhIGNvbHVtbiBuYW1lZAogICMgICAgICAgICAgICAgICJjb25kaXRpb24iCiAgIyAgIHkudmFyaWFibGUubmFtZTogdGhlIGNvbHVtbiBuYW1lLCBzaGFyZWQgYnkgYWxsIHRocmVlIGRhdGEuZnJhbWVzLCB0aGF0CiAgIyAgICAgICAgICAgICAgICAgICAgd2lsbCBiZSB1c2VkIGFzIHRoZSB5IHZhcmlhYmxlIGluIHRoZSBwbG90cwogICMgICB5LmxhYmVsOiB3aGF0IHNob3VsZCB0aGUgeS1heGlzIGxhYmVsIGJlPwogICMgICB5LmxpbWl0czogcGFzc2VkIHRvIGdncGxvdDI6OnlsaW0sIHNob3VsZCBmb2xsb3cgdGhlIGZvcm1hdCAKICAjICAgICAgICAgICAgICJjKDxMT1dFUiBMSU1JVD4sIDxVUFBFUiBMSU1JVD4pIgogICMKICAjIFJldHVybnM6IAogICMgICBhIGxpc3Qgb2YgdGhlIGZvbGxvdyBwbG90czogc2l6ZSwgY29udGV4dCwgcmVjb3VudDIKICAKICAjIHN0YW5kYXJkaXplZCBnZW9tX2JveHBsb3QgKyBnZW9tX2ppdHRlciBwbG90IGZvciBzYW1wbGUgc2l6ZSBhbmQgYmlvbG9naWNhbAogICMgY29udGV4dCBwbG90cwogIEJveHBsb3RKaXR0ZXIgPC0gZnVuY3Rpb24oZGYsIHgudmFyLCB5LnZhciwgeS5saW0pewogICAgZ2dwbG90KGRhdGEgPSBkZiwgYWVzX3N0cmluZyh4ID0geC52YXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0geS52YXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwID0geC52YXIpKSArCiAgICAgICMgd2lsbCBiZSB1c2luZyBqaXR0ZXIgc28gaW5jbHVkaW5nIGFuIG91dGxpZXIgc2hhcGUgaW4gdGhlIGJveHBsb3QgY291bGQKICAgICAgIyBjb25mdXNlIHRoaW5ncyBhIGJpdAogICAgICBnZW9tX2JveHBsb3Qob3V0bGllci5zaGFwZSA9IE5BKSArCiAgICAgIGdlb21faml0dGVyKGFscGhhID0gMC41LCB3aWR0aCA9IDAuMykgKwogICAgICBjdXN0b21fdGhlbWUoKSArCiAgICAgIHlsaW0oZXZhbChwYXJzZSh0ZXh0ID0geS5saW0pKSkKICB9CiAgCiAgc2l6ZS5wbG90IDwtIHNpemUuZGYgJT4lCiAgICBCb3hwbG90Sml0dGVyKHgudmFyID0gInNhbXBsZV9zaXplIiwKICAgICAgICAgICAgICAgICAgeS52YXIgPSB5LnZhcmlhYmxlLm5hbWUsCiAgICAgICAgICAgICAgICAgIHkubGltID0geS5saW1pdHMpICsKICAgICMgb25seSB0aGUgc2FtcGxlIHNpemUgcGxvdCB3aGljaCB3aWxsIGJlIG9uIHRoZSBsZWZ0IGluIG11bHRpcGFuZWwgZmlndXJlcwogICAgIyB3aWxsIGhhdmUgYSB5LWF4aXMgbGFiZWwKICAgIGxhYnMoeSA9IHkubGFiZWwsIHRpdGxlID0gIlNhbXBsZSBTaXplIikKICAKICBjb250ZXh0LnBsb3QgPC0gY29udGV4dC5kZiAlPiUKICAgIEJveHBsb3RKaXR0ZXIoeC52YXIgPSAiYmlvbG9naWNhbF9jb250ZXh0IiwKICAgICAgICAgICAgICAgICAgeS52YXIgPSB5LnZhcmlhYmxlLm5hbWUsCiAgICAgICAgICAgICAgICAgIHkubGltID0geS5saW1pdHMpICsKICAgICAgbGFicyh5ID0gTlVMTCwgdGl0bGUgPSAiQmlvbG9naWNhbCBDb250ZXh0IikKICAKICAjIHRoZSBtdWx0aXBsaWVyIHBsb3QgaXMgYSBzaW5nbGUgcG9pbnQuIHRoZSBjdXN0b20gdGhlbWUgaXMgc2hhcmVkLgogIHJlY291bnQyLnBsb3QgPC0gcmVjb3VudDIuZGYgJT4lCiAgICBnZ3Bsb3QoYWVzX3N0cmluZyh4ID0gImNvbmRpdGlvbiIsIHkgPSB5LnZhcmlhYmxlLm5hbWUpKSArCiAgICBnZW9tX3BvaW50KGZpbGwgPSAiIzAwMDBGRiIsIHNoYXBlID0gMjMsIHNpemUgPSA0KSArCiAgICBjdXN0b21fdGhlbWUoKSArCiAgICB5bGltKGV2YWwocGFyc2UodGV4dCA9IHkubGltaXRzKSkpICsKICAgIGxhYnMoeSA9IE5VTEwsIHRpdGxlID0gIk11bHRpUExJRVIiKQogIAogICMgcmV0dXJuIGEgbGlzdCBvZiB0aGUgdGhyZWUgcGxvdHMKICByZXR1cm4obGlzdCgic2l6ZSIgPSBzaXplLnBsb3QsCiAgICAgICAgICAgICAgImNvbnRleHQiID0gY29udGV4dC5wbG90LAogICAgICAgICAgICAgICJyZWNvdW50MiIgPSByZWNvdW50Mi5wbG90KSkKfQoKYGBgCgpgYGB7cn0KTXVsdGlQYW5lbFdyYXBwZXIgPC0gZnVuY3Rpb24oc2l6ZS5wbG90LCBjb250ZXh0LnBsb3QsIHJlY291bnQyLnBsb3QsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbG90LnRpdGxlLCBvdXRwdXQuZmlsZSkgewogICMgZ2l2ZW4gdGhlIHBsb3RzIGZvciB0aGUgc2FtcGxlIHNpemUgZXhwZXJpbWVudCwgdGhlIGJpb2xvZ2ljYWwgY29udGV4dAogICMgZXhwZXJpbWVudCBhbmQgdGhlIGZ1bGwgTXVsdGlQTElFUiBtb2RlbCwgcmVzcGVjdGl2ZWx5LCBtYWtlIGEgbXVsdGlwYW5lbAogICMgcGxvdCB0aXRsZWQgd2l0aCB0aGUgcGxvdC50aXRsZSBhcmd1bWVudCBhbmQgc2F2ZWQgYXMgYW4gMTEiVyB4IDUiSCBwbG90CiAgIyBpbiB0aGUgbG9jYXRpb24gc3BlY2lmaWVkIGJ5IG91dHB1dC5maWxlCiAgCiAgIyBmb3IgdGhlIHRocmVlIGNvbmRpdGlvbnMsIG1ha2UgYSAzIGNvbHVtbiBtdWx0aXBhbmVsIHBsb3QKICBwYW5lbHMgPC0gY293cGxvdDo6cGxvdF9ncmlkKHNpemUucGxvdCwgY29udGV4dC5wbG90LCByZWNvdW50Mi5wbG90LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduID0gImgiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgdGhlIHNpemUgcGxvdCB3aWxsIGhhdmUgdGhlIHktYXhpcyBsYWJlbAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBhbmQgdGhlIE11bHRpUExJRVIgcGFuZWwgc2hvdWxkIGJlIHF1aXRlIHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgc21hbGwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbF93aWR0aHMgPSBjKDEuMTI1LCAxLCAwLjM3NSksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmNvbCA9IDMpCiAgCiAgIyB0aGUgdGl0bGUgInBhbmVsIgogIHAudGl0bGUgPC0gY293cGxvdDo6Z2dkcmF3KCkgKyAKICAgIGNvd3Bsb3Q6OmRyYXdfbGFiZWwocGxvdC50aXRsZSwgZm9udGZhY2UgPSAiYm9sZCIsIAogICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMjApCiAgCiAgIyBjb21iaW5lIHRvIGdldCB0aGUgZmluYWwgcGxvdAogIGZpbmFsLnBsb3QgPC0gY293cGxvdDo6cGxvdF9ncmlkKHAudGl0bGUsIHBhbmVscywgbmNvbCA9IDEsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbF9oZWlnaHRzID0gYygwLjEsIDEpKQogIAogICMgc2F2ZSAxMSJXIHggNSJIIGZpbGUgKHRoZSB0eXBlIG9mIGZpbGUgaXMgZGV0ZXJtaW5lZCBieSB0aGUgZmlsZSBleHRlbnNpb24KICAjIHByb3ZpZGVkIGluIG91dHB1dC5maWxlKQogIGdnc2F2ZShvdXRwdXQuZmlsZSwgcGxvdCA9IGZpbmFsLnBsb3QsIHdpZHRoID0gMTEsIGhlaWdodCA9IDUpCiAgCn0KYGBgCgojIyMjIERpcmVjdG9yeSBzZXR1cAoKYGBge3J9CiMgcGxvdCBhbmQgcmVzdWx0IGRpcmVjdG9yeSBzZXR1cCBmb3IgdGhpcyBub3RlYm9vawpwbG90LmRpciA8LSBmaWxlLnBhdGgoInBsb3RzIiwgIjMxIikKZGlyLmNyZWF0ZShwbG90LmRpciwgcmVjdXJzaXZlID0gVFJVRSwgc2hvd1dhcm5pbmdzID0gRkFMU0UpCnJlc3VsdHMuZGlyIDwtIGZpbGUucGF0aCgicmVzdWx0cyIsICIzMSIpCmRpci5jcmVhdGUocmVzdWx0cy5kaXIsIHJlY3Vyc2l2ZSA9IFRSVUUsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQpgYGAKCiMjIFJlYWQgaW4gZGF0YQoKRm9yIGJvdGggdGhlIGJpb2xvZ2ljYWwgY29udGV4dCBhbmQgc2FtcGxlIHNpemUgZXZhbHVhdGlvbnMsIHRoZSByZXN1bHRzIHdlCndpbGwgYmUgcGxvdHRpbmcgYXJlIGZyb20gYDMwLWV2YWx1YXRlX3NhbXBsZV9zaXplX2FuZF9iaW9sb2dpY2FsX2NvbnRleHRgLgoKIyMjIEJpb2xvZ2ljYWwgY29udGV4dAoKYGBge3J9CiMgcGF0aHdheSBjb3ZlcmFnZSBhbmQgcHJvcG9ydGlvbiBvZiBMVnMgYXNzb2NpYXRlZCB3aXRoIHBhdGh3YXlzCmNvbnRleHQuY292ZXJhZ2UuZmlsZSA8LSBmaWxlLnBhdGgoInJlc3VsdHMiLCAiMzAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYmlvbG9naWNhbF9jb250ZXh0X3BhdGh3YXlfY292ZXJhZ2UudHN2IikKCiMgd2UnbGwgd2FudCB0byByZW9yZGVyIHRoZSBiaW9sb2dpY2FsIGNvbnRleHRzIGJ5IHNhbXBsZSBzaXplCmNvbnRleHQubGV2ZWxzIDwtIGMoImJsb29kIiwgImNhbmNlciIsICJ0aXNzdWUiLCAiY2VsbCBsaW5lIiwgIm90aGVyIHRpc3N1ZXMiKQoKY29udGV4dC5jb3ZlcmFnZS5kZiA8LSByZWFkcjo6cmVhZF90c3YoY29udGV4dC5jb3ZlcmFnZS5maWxlKSAlPiUKICAjIGNsZWFuIHVwIHRoZSBjb250ZXh0IGluZm9ybWF0aW9uIGZvciBwbG90dGluZwogIGRwbHlyOjptdXRhdGUoYmlvbG9naWNhbF9jb250ZXh0ID0gZ3N1YigiXyIsICIgIiwgYmlvbG9naWNhbF9jb250ZXh0KSkgJT4lCiAgIyByZW9yZGVyIGJ5IHRoZSBzYW1wbGUgc2l6ZSAobG93IC0+IGhpZ2gpCiAgZHBseXI6Om11dGF0ZShiaW9sb2dpY2FsX2NvbnRleHQgPSBmYWN0b3IoYmlvbG9naWNhbF9jb250ZXh0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGNvbnRleHQubGV2ZWxzKSkKIyBudW1iZXIgb2YgbGF0ZW50IHZhcmlhYmxlcwpjb250ZXh0Lm51bS5maWxlIDwtIGZpbGUucGF0aCgicmVzdWx0cyIsICIzMCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiYmlvbG9naWNhbF9jb250ZXh0X251bWJlcl9vZl9sdnMudHN2IikKY29udGV4dC5udW0uZGYgPC0gcmVhZHI6OnJlYWRfdHN2KGNvbnRleHQubnVtLmZpbGUpICU+JQogICMgY2xlYW4gdXAgdGhlIGNvbnRleHQgaW5mb3JtYXRpb24gZm9yIHBsb3R0aW5nCiAgZHBseXI6Om11dGF0ZShiaW9sb2dpY2FsX2NvbnRleHQgPSBnc3ViKCJfIiwgIiAiLCBiaW9sb2dpY2FsX2NvbnRleHQpKSAlPiUKICAjIHJlb3JkZXIgYnkgdGhlIHNhbXBsZSBzaXplIChsb3cgLT4gaGlnaCkKICBkcGx5cjo6bXV0YXRlKGJpb2xvZ2ljYWxfY29udGV4dCA9IGZhY3RvcihiaW9sb2dpY2FsX2NvbnRleHQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gY29udGV4dC5sZXZlbHMpKQpgYGAKCiMjIyBTYW1wbGUgc2l6ZSAKCmBgYHtyfQojIHBhdGh3YXkgY292ZXJhZ2UgYW5kIHByb3BvcnRpb24gb2YgTFZzIGFzc29jaWF0ZWQgd2l0aCBwYXRod2F5cwpzaXplLmNvdmVyYWdlLmZpbGUgPC0gZmlsZS5wYXRoKCJyZXN1bHRzIiwgIjMwIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInN1YnNhbXBsZWRfcGF0aHdheV9jb3ZlcmFnZS50c3YiKQoKIyBvcmRlciBieSBzYW1wbGUgc2l6ZQpzaXplLmxldmVscyA8LSBjKCI1MDAiLCAiMTAwMCIsICIyMDAwIiwgIjQwMDAiLCAiODAwMCIsICIxNjAwMCIsICIzMjAwMCIpCgojIGZvciBwbG90dGluZywgd2Ugd2FudCB0aGUgc2FtcGxlIHNpemUgYXMgYSBmYWN0b3IgKG90aGVyd2lzZSBpdCdzIGhhcmQgdG8KIyBzZWUgd2hhdCBpcyBoYXBwZW5pbmcgb24gdGhlIGxvd2VyIGVuZCBvZiB0aGluZ3MpIGJ1dCB0byBvcmRlciBieSB0aGUgaW50ZWdlcgojIHZhbHVlCnNpemUuY292ZXJhZ2UuZGYgPC0gcmVhZHI6OnJlYWRfdHN2KHNpemUuY292ZXJhZ2UuZmlsZSkgJT4lCiAgICBkcGx5cjo6bXV0YXRlKHNhbXBsZV9zaXplID0gZmFjdG9yKHNhbXBsZV9zaXplLCBsZXZlbHMgPSBzaXplLmxldmVscykpCgojIE51bWJlciBvZiBsYXRlbnQgdmFyaWFibGVzCnNpemUubnVtLmZpbGUgPC0gZmlsZS5wYXRoKCJyZXN1bHRzIiwgIjMwIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgInN1YnNhbXBsZWRfbnVtYmVyX29mX2x2cy50c3YiKQpzaXplLm51bS5kZiA8LSByZWFkcjo6cmVhZF90c3Yoc2l6ZS5udW0uZmlsZSkgJT4lCiAgICAgIGRwbHlyOjptdXRhdGUoc2FtcGxlX3NpemUgPSBmYWN0b3Ioc2FtcGxlX3NpemUsIGxldmVscyA9IHNpemUubGV2ZWxzKSkKYGBgCgojIyMgTXVsdGlQTElFUgoKRm9yIHRoZSBmdWxsIHJlY291bnQyIGJpdHMsIHdlJ2xsIHVzZSB0aGUgc2FtZSB2YWx1ZXMgdGhhdCBhcmUgaW4gCmBmaWd1cmVfbm90ZWJvb2tzL3N1YnNhbXBsaW5nX2ZpZ3VyZXNgLgooVGhlIGZ1bGwgbW9kZWwgd2FzIHRyYWluZWQgb24gfjM3SyBzYW1wbGVzLCBiZWNhdXNlIHdlIHN1YnNldCByZWNvdW50MiB0bwppbmNsdWRlIG9ubHkgdGhvc2Ugc2FtcGxlcyB3aXRoIG1ldGFkYXRhLikKCmBgYHtyfQojIG51bWJlciBvZiBsYXRlbnQgdmFyaWFibGVzIGluIHRoZSBNdWx0aVBMSUVSIG1vZGVsCnJlY291bnQyLm51bS5sdnMgPC0gOTg3CiMgcGF0aHdheSBjb3ZlcmFnZQpyZWNvdW50Mi5jb3ZlcmFnZSA8LSAwLjQxODc4OTgKIyBwcm9wb3J0aW9uIG9mIHRoZSBsYXRlbnQgdmFyaWFibGVzIGFzc29jaWF0ZWQgd2l0aCBhIHBhdGh3YXkKcmVjb3VudDIubHYucHJvcCA8LSAwLjIwMTYyMTEKYGBgCgpNYWtlIGludG8gYGRhdGEuZnJhbWVzYCBmb3IgcGxvdHRpbmcgcHVycG9zZXMuIApXZSdsbCBhbHdheXMgdXNlICJjb25kaXRpb24iIGFzIG9uZSBvZiB0aGUgY29sdW1uIG5hbWVzIChzZWUgCmBUaHJlZVBsb3RzV3JhcHBlcmAgYWJvdmUpLgoKYGBge3J9CiMgcHJvcG9ydGlvbiBvZiBMVnMgYXNzb2NpYXRlZCB3aXRoIHBhdGh3YXlzCnJlY291bnQyLnByb3AuZGYgPC0gZGF0YS5mcmFtZSh2YWx1ZSA9IHJlY291bnQyLmx2LnByb3AsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb25kaXRpb24gPSAiZnVsbCByZWNvdW50MiIpCiMgcGF0aHdheSBjb3ZlcmFnZQpyZWNvdW50Mi5jb3ZlcmFnZS5kZiA8LSBkYXRhLmZyYW1lKHZhbHVlID0gcmVjb3VudDIuY292ZXJhZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29uZGl0aW9uID0gImZ1bGwgcmVjb3VudDIiKQojIG51bWJlciBvZiBsYXRlbnQgdmFyaWFibGVzCnJlY291bnQyLm51bS5kZiA8LSAKICBkYXRhLmZyYW1lKG51bWJlcl9vZl9sYXRlbnRfdmFyaWFibGVzID0gYXMuaW50ZWdlcihyZWNvdW50Mi5udW0ubHZzKSwKICAgICAgICAgICAgIGNvbmRpdGlvbiA9ICJmdWxsIHJlY291bnQyIikKYGBgCgojIyBQbG90dGluZwoKIyMjIE51bWJlciBvZiBsYXRlbnQgdmFyaWFibGVzCgpgYGB7cn0KbnVtLnBsb3RzIDwtIFRocmVlUGxvdHNXcmFwcGVyKHNpemUuZGYgPSBzaXplLm51bS5kZiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbnRleHQuZGYgPSBjb250ZXh0Lm51bS5kZiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlY291bnQyLmRmID0gcmVjb3VudDIubnVtLmRmLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeS52YXJpYWJsZS5uYW1lID0gIm51bWJlcl9vZl9sYXRlbnRfdmFyaWFibGVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkubGFiZWwgPSAibnVtYmVyIG9mIGxhdGVudCB2YXJpYWJsZXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeS5saW1pdHMgPSAiYygwLCAxMDAwKSIpCmBgYAoKU2F2ZSB0byBmaWxlCgpgYGB7cn0KTXVsdGlQYW5lbFdyYXBwZXIoc2l6ZS5wbG90ID0gbnVtLnBsb3RzJHNpemUsCiAgICAgICAgICAgICAgICAgIGNvbnRleHQucGxvdCA9IG51bS5wbG90cyRjb250ZXh0LAogICAgICAgICAgICAgICAgICByZWNvdW50Mi5wbG90ID0gbnVtLnBsb3RzJHJlY291bnQyLAogICAgICAgICAgICAgICAgICBwbG90LnRpdGxlID0gIk51bWJlciBvZiBMYXRlbnQgVmFyaWFibGVzIiwKICAgICAgICAgICAgICAgICAgb3V0cHV0LmZpbGUgPSBmaWxlLnBhdGgocGxvdC5kaXIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAibnVtYmVyX29mX2xhdGVudF92YXJpYWJsZXMucGRmIikpCmBgYAoKIyMjIFBhdGh3YXkgY292ZXJhZ2UKClRoZSBzYW1wbGUgc2l6ZSBhbmQgYmlvbG9naWNhbCBjb250ZXh0IGBkYXRhLmZyYW1lc2AgY29udGFpbiB0aGUgaW5mb3JtYXRpb24KYWJvdXQgcGF0aHdheSBjb3ZlcmFnZSBfYW5kXyB0aGUgcHJvcG9ydGlvbiBvZiBMVnMgYXNzb2NpYXRlZCB3aXRoIGEgcGF0aHdheSwKc28gd2UnbGwgbmVlZCB0byBmaWx0ZXIgdGhlbSB0byB0aGUgY29ycmVjdCBtZXRyaWMuCgpgYGB7cn0KY292ZXJhZ2UucGxvdHMgPC0gCiAgVGhyZWVQbG90c1dyYXBwZXIoc2l6ZS5kZiA9IGRwbHlyOjpmaWx0ZXIoc2l6ZS5jb3ZlcmFnZS5kZiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRyaWMgPT0gInBhdGh3YXkgY292ZXJhZ2UiKSwKICAgICAgICAgICAgICAgICAgICBjb250ZXh0LmRmID0gZHBseXI6OmZpbHRlcihjb250ZXh0LmNvdmVyYWdlLmRmLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldHJpYyA9PSAicGF0aHdheSBjb3ZlcmFnZSIpLAogICAgICAgICAgICAgICAgICAgIHJlY291bnQyLmRmID0gcmVjb3VudDIuY292ZXJhZ2UuZGYsCiAgICAgICAgICAgICAgICAgICAgeS52YXJpYWJsZS5uYW1lID0gInZhbHVlIiwKICAgICAgICAgICAgICAgICAgICB5LmxhYmVsID0gInByb3BvcnRpb24gb2YgaW5wdXQgcGF0aHdheXMiKQpgYGAKCmBgYHtyfQpNdWx0aVBhbmVsV3JhcHBlcihzaXplLnBsb3QgPSBjb3ZlcmFnZS5wbG90cyRzaXplLAogICAgICAgICAgICAgICAgICBjb250ZXh0LnBsb3QgPSBjb3ZlcmFnZS5wbG90cyRjb250ZXh0LAogICAgICAgICAgICAgICAgICByZWNvdW50Mi5wbG90ID0gY292ZXJhZ2UucGxvdHMkcmVjb3VudDIsCiAgICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSAiUGF0aHdheSBDb3ZlcmFnZSIsCiAgICAgICAgICAgICAgICAgIG91dHB1dC5maWxlID0gZmlsZS5wYXRoKHBsb3QuZGlyLCAicGF0aHdheV9jb3ZlcmFnZS5wZGYiKSkKYGBgCgojIyMgTGF0ZW50IHZhcmlhYmxlcyBzaWduaWZpY2FudGx5IGFzc29jaWF0ZWQgd2l0aCBwYXRod2F5cwoKYGBge3J9CiMgZm9yIGxpbmUgbGVuZ3RoL3N0eWxlIHB1cnBvc2VzCm1ldHJpYy50by51c2UgPC0gIkxWIGFzc29jaWF0ZWQgd2l0aCBwYXRod2F5cyIKcHJvcC5wbG90cyA8LSAKICBUaHJlZVBsb3RzV3JhcHBlcihzaXplLmRmID0gZHBseXI6OmZpbHRlcihzaXplLmNvdmVyYWdlLmRmLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldHJpYyA9PSBtZXRyaWMudG8udXNlKSwKICAgICAgICAgICAgICAgICAgICBjb250ZXh0LmRmID0gZHBseXI6OmZpbHRlcihjb250ZXh0LmNvdmVyYWdlLmRmLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldHJpYyA9PSBtZXRyaWMudG8udXNlKSwKICAgICAgICAgICAgICAgICAgICByZWNvdW50Mi5kZiA9IHJlY291bnQyLmNvdmVyYWdlLmRmLAogICAgICAgICAgICAgICAgICAgIHkudmFyaWFibGUubmFtZSA9ICJ2YWx1ZSIsCiAgICAgICAgICAgICAgICAgICAgeS5sYWJlbCA9ICJwcm9wb3J0aW9uIG9mIGxhdGVudCB2YXJpYWJsZXMiKQpgYGAKCmBgYHtyfQpNdWx0aVBhbmVsV3JhcHBlcihzaXplLnBsb3QgPSBwcm9wLnBsb3RzJHNpemUsCiAgICAgICAgICAgICAgICAgIGNvbnRleHQucGxvdCA9IHByb3AucGxvdHMkY29udGV4dCwKICAgICAgICAgICAgICAgICAgcmVjb3VudDIucGxvdCA9IHByb3AucGxvdHMkcmVjb3VudDIsCiAgICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSAiTFZzIHNpZ25pZmljYW50bHkgYXNzb2NpYXRlZCB3aXRoIHBhdGh3YXlzIiwKICAgICAgICAgICAgICAgICAgb3V0cHV0LmZpbGUgPSBmaWxlLnBhdGgocGxvdC5kaXIsICJsdl9wcm9wb3J0aW9uLnBkZiIpKQoKYGBgCgojIyMgRm9sbG93aW5nIHVwIG9uIHRoZSBwcm9wb3J0aW9uIG9mIExWcyBhc3NvY2lhdGVkIHdpdGggcGF0aHdheXMgcmVzdWx0cwoKSWYgd2UgbG9vayBhdCB0aGUgYHByb3AucGxvdHNgCgpgYGB7cn0KcHJvcC5wbG90cyRzaXplCnByb3AucGxvdHMkY29udGV4dApwcm9wLnBsb3RzJHJlY291bnQyCmBgYAoKV2UgY2FuIHNlZSBpbiB0aGUgc2FtcGxlIHNpemUgcGxvdCB0aGF0LCBnZW5lcmFsbHkgc3BlYWtpbmcsIHRoZSBsYXJnZXIgdGhlIApzYW1wbGUgc2l6ZSwgdGhlIGxvd2VyIHRoZSBwcm9wb3J0aW9uIG9mIGxhdGVudCB2YXJpYWJsZXMgdGhhdCBhcmUgYXNzb2NpYXRlZCAKd2l0aCBhdCBsZWFzdCBvbmUgcGF0aHdheS4KCmBQTElFUjo6UExJRVJgIGhhcyBhIHBhcmFtZXRlciwgYGZyYWNgLCB0aGF0IGRldGVybWluZXMgdGhlIGZyYWN0aW9uIG9mIGxhdGVudAp2YXJpYWJsZXMgdGhhdCBtZWV0cyB0aGF0IGNyaXRlcmlvbi4gCihgZnJhYyA9IDAuN2AgYnkgZGVmYXVsdCBpbiB0aGUgdmVyc2lvbiBvZiBQTElFUiB3ZSBoYXZlIG9uIHRoaXMgRG9ja2VyIApjb250YWluZXI7IHdlIGRpZCBub3Qgc3BlY2lmeSBgZnJhY2AuKQoKVGhpcyBwYXJhbWV0ZXIgaXMgdXNlZCB0byBzZXQgYEwzYCwgd2hpY2ggaXMgdGhlIHBlbmFsdHkgb24gYFVgIHRoYXQgY29udHJvbHMKc3BhcnNpdHkgKFtNYW8sIGV0IGFsLiBfYmlvUnhpdl8uIDIwMTcuXShodHRwOi8vZHguZG9pLm9yZy8xMC4xMTAxLzExNjA2MSkpLgpCYXNlZCBvbiBteSB1bmRlcnN0YW5kaW5nLCB0aGlzIGNvbnRyb2xzIHRoZSBwcm9wb3J0aW9uIG9mIHBvc2l0aXZlIGVudHJpZXMgaW4gCmBVYCAoaS5lLiwgdGhlIG51bWJlciBvZiBnZW5lIHNldHMgdGhhdCBhbiBMViBoYXMgc29tZSBhc3NvY2lhdGlvbiB3aXRoKSwgYnV0IAppdCBfZG9lcyBub3RfIGd1YXJhbnRlZSB0aGF0IGFsbCBvZiB0aGVzZSBhc3NvY2lhdGlvbnMgd2lsbCBiZSBzaWduaWZpY2FudAooW01hbywgZXQgYWwuIF9iaW9SeGl2Xy4gMjAxNy5dKGh0dHA6Ly9keC5kb2kub3JnLzEwLjExMDEvMTE2MDYxKSkgYW5kIAp0aGUgY3V0b2ZmIHdlIGhhcHBlbiB0byBiZSB1c2luZyAoYEZEUiA8IDAuMDVgKSBpcyBub3QgdGFrZW4gaW50byBhY2NvdW50LiAKCkZvciByZWZlcmVuY2UsIHRoaXMgaXMgdGhlIHNhbXBsZSBzaXplIGluZm9ybWF0aW9uIGZvciB0aGVzZSBkaWZmZXJlbnQgdHJhaW5pbmcKc2V0czoKCnwgQmlvbG9naWNhbCBjb25kaXRpb24gfCBTYW1wbGUgc2l6ZSB8Cnw6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18Oi0tLS0tLS0tLS0tLS0tLS18CnwgYmxvb2QgfCAzODYyIHwKfCBjYW5jZXIgfCA4ODA3IHwKfCB0aXNzdWUgfCAxMjM5NiB8CnwgY2VsbCBsaW5lIHwgMTQ1MzIgfAp8IG90aGVyIHRpc3N1ZXMgKG5vdCBibG9vZCkgfCAzMjk4NCB8IAoKT25jZSB3ZSB0YWtlIHRoZSBiaW9sb2dpY2FsIGNvbnRleHQgZXhwZXJpbWVudHMgaW50byBhY2NvdW50LCBpdCBnZXRzIGEgYml0Cm1vcmUgaW50ZXJlc3Rpbmc6CgoqIFRoZSBgYmxvb2RgIHRyYWluaW5nIHNldCBoYXMgfjQwMDAgc2FtcGxlcywgYnV0IG1vZGVscyB0cmFpbmVkIG9uIGJsb29kIGhhdmUgYSAKbXVjaCBoaWdoZXIgcHJvcG9ydGlvbiBvZiBsYXRlbnQgdmFyaWFibGVzIHNpZ25pZmljYW50bHkgYXNzb2NpYXRlZCB3aXRoIApwYXRod2F5cyB0aGFuIHRoZSBtb2RlbHMgdHJhaW5lZCBvbiA0MDAwIHJhbmRvbWx5IHNlbGVjdGVkIHNhbXBsZXMuCldlJ2QgZXhwZWN0IHRoYXQgdGhlcmUnZCBiZSBsZXNzICJ0ZWNobmljYWwgbm9pc2UiIGFuZCBtb3JlICJiaW9sb2dpY2FsIHNpZ25hbCIKaW4gdGhlIGBibG9vZGAgdHJhaW5pbmcgc2V0LgpJdCdzIGFsc28gbGlrZWx5IHRvIGJlIGEgZnVuY3Rpb24gb2YgdGhlIHBhdGh3YXlzIHVuZGVyIGNvbnNpZGVyYXRpb24uIApTcGVjaWZpY2FsbHksIHdlIHN1cHBseSBpbW11bmUgY2VsbC1yZWxhdGVkIGdlbmUgc2V0cyB0byB0aGUgbW9kZWxzLCBhbmQKd2UgZXhwZWN0IGBibG9vZGAgc2FtcGxlcyB0byBiZSBfcGFydGljdWxhcmx5XyByZWxldmFudCBmb3IgdGhvc2UgZ2VuZSBzZXRzLgoqIEl0IGxvb2tzIGxpa2UgdGhlIF9tb3JlIGxhdGVudCB2YXJpYWJsZXNfLCB0aGUgX2xvd2VyIHByb3BvcnRpb24gb2YgTFZzCnNpZ25pZmljYW50bHkgYXNzb2NpYXRlZCB3aXRoIGEgcGF0aHdheV8gYmFzZWQgb24gdGhlIHNhbXBsZSBzaXplIHJlc3VsdHMgYWxvbmUuCkFuZCBpdCdzIGNlcnRhaW5seSB0aGUgY2FzZSB0aGF0IHRoZSBtb3JlIExWcyAtPiB0aGUgbW9yZSBwYXRod2F5cyB0aGF0IGFyZQpjYXB0dXJlZCBieSB0aGUgbW9kZWwgKGhpZ2hlciBwYXRod2F5IGNvdmVyYWdlKS4gCkhvd2V2ZXIsIGB0aXNzdWVgIG1vZGVscyBoYXZlIGhpZ2hlciBudW1iZXIgb2YgTFZzIGFuZCBtb3JlIHBhdGh3YXkgY292ZXJhZ2UKdGhhbiBgY2VsbCBsaW5lYCBtb2RlbHMgYnV0IHRoZXkgaGF2ZSBhcHByb3hpbWF0ZWx5IHRoZSBzYW1lIHByb3BvcnRpb24gb2YgTFZzCnNpZ25pZmljYW50bHkgYXNzb2NpYXRlZCB3aXRoIGEgcGF0aHdheS4KVGhlc2UgdmFsdWVzIGFyZSBhbHNvIHBlcmhhcHMgYSBiaXQgbG93ZXIgdGhhbiB3ZSB3b3VsZCBleHBlY3QgYmFzZWQgb24gc2FtcGxlIApzaXplIGFsb25lIChsb29raW5nIGF0IGAxNjAwMGAgZnJvbSBzYW1wbGUgc2l6ZSBwbG90KS4KCiMjIENoZWNrIGBVYCBzcGFyc2l0eQoKVG8gZm9sbG93IHVwIG9uIHRoZSBvYnNlcnZhdGlvbnMgYWJvdmUsIGxldCdzIGNoZWNrIHRoYXQsIF9pbiBwcmFjdGljZV8sIHRoZSAKZnJhY3Rpb24gb2YgdGhlIExWcyB0aGF0IGhhdmUgYXQgbGVhc3Qgb25lIGFzc29jaWF0aW9uIHdpdGggYSBwYXRod2F5IHNob3VsZCBiZQoqKn4wLjcqKi4KRmlyc3QsIHJlbW92aW5nIGV2ZXJ5dGhpbmcgZnJvbSB0aGUgd29ya3NwYWNlLgoKYGBge3J9CnJtKGxpc3QgPSBscygpKQpgYGAKCiMjIyMgQ3VzdG9tIGZ1bmN0aW9ucyBqdXN0IGZvciB0aGlzCgpgYGB7cn0KIyB0aGlzIGlzIGludGVuZGVkIHRvIGJlIHVzZWQgd2l0aCBsaXN0cyBvZiBtb2RlbHMgb3V0cHV0IGZyb20KIyBzY3JpcHRzL3N1YnNhbXBsaW5nX1BMSUVSLlIKR2V0U3BhcnNpdHlMaXN0IDwtIGZ1bmN0aW9uKGxpc3Qub2YubW9kZWxzKSB7CiAgIyB3ZSBhc3N1bWUgdGhhdCB0aGUgbGlzdCBvZiBtb2RlbHMgaGFzIG5hbWVzCiAgCiAgIyB0aGVzZSBzcGFyc2l0eSBjYWxjdWxhdGlvbnMgYXJlIGRpZmZlcmVudCBmcm9tIHdoYXQgd2UndmUgZG9uZSBiZWZvcmUKICBDaGVja1VTcGFyc2l0eSA8LSBmdW5jdGlvbihwbGllci5tb2RlbCkgewogICAgdS5tYXRyaXggPC0gcGxpZXIubW9kZWwkVQogICAgIyB3aGF0IHByb3BvcnRpb24gb2YgdmFsdWVzIGFyZSBwb3NpdGl2ZT8KICAgIHRvdGFsLnBvc2l0aXZlIDwtIHN1bSh1Lm1hdHJpeCA+IDApIC8gKGRpbSh1Lm1hdHJpeClbMV0gKiBkaW0odS5tYXRyaXgpWzJdKQogICAgIyB3aGF0IHByb3BvcnRpb24gb2YgY29sdW1ucyBoYXZlIGF0IGxlYXN0IDEgcG9zaXRpdmUgdmFsdWUgKGFzc29jaWF0aW9uKT8KICAgIGNvbC53aXNlIDwtIHN1bShhcHBseSh1Lm1hdHJpeCwgMiwgZnVuY3Rpb24oeCkgYW55KHggPiAwKSkpIC8gbmNvbCh1Lm1hdHJpeCkKICAgIHJldHVybihsaXN0KCJvdmVyYWxsIiA9IHRvdGFsLnBvc2l0aXZlLAogICAgICAgICAgICAgICAgImNvbHVtbl93aXNlIiA9IGNvbC53aXNlKSkKICB9CiAgCiAgc3BhcnNpdHkubGlzdCA8LSBsYXBwbHkobGlzdC5vZi5tb2RlbHMsIAogICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpIENoZWNrVVNwYXJzaXR5KHgkUExJRVIpKQogIAp9CgojIHdlJ2xsIHVzZSB0aGlzIHR3aWNlIC0tIG9uY2UgZm9yIGJpb2xvZ2ljYWwgY29udGV4dCBhbmQgb25jZSBmb3Igc2FtcGxlIHNpemUKIyB3ZSBhc3N1bWUgdGhhdCB0aGUgZmlsZXMudmVjdG9yIGlzIG5hbWVkCkdldFJlc3VsdHMgPC0gZnVuY3Rpb24oZmlsZXMudmVjdG9yKSB7CiAgbGFwcGx5KGZpbGVzLnZlY3RvciwgCiAgICAgICAgIGZ1bmN0aW9uKHgpIHsKICAgICAgICAgICAjIGZvciBlYWNoIGZpbGUsIHJlYWQgaW4gdGhlIGxpc3Qgb2YgbW9kZWxzCiAgICAgICAgICAgbW9kZWwubGlzdCA8LSByZWFkUkRTKHgpCiAgICAgICAgICAgIyBnZXQgdGhlIHNwYXJzaXR5IGxpc3QgZm9yIHRoZSBjdXJyZW50IGxpc3Qgb2YgbW9kZWxzCiAgICAgICAgICAgR2V0U3BhcnNpdHlMaXN0KG1vZGVsLmxpc3QpCiAgICAgICAgIH0pCn0KCmBgYAoKIyMjIyBGaWxlcwoKYGBge3J9Cm1vZGVscy5kaXIgPC0gIm1vZGVscyIKIyBzYW1wbGUgc2l6ZQpzaXplLm1vZGVsLmZpbGVzIDwtIGxpc3QuZmlsZXMobW9kZWxzLmRpciwgcGF0dGVybiA9ICJzdWJzYW1wbGVkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsLm5hbWVzID0gVFJVRSkKbmFtZXMoc2l6ZS5tb2RlbC5maWxlcykgPC0gc3ViKCIuUkRTIiwgIiIsIHN1YigiLipbX10iLCAiIiwgc2l6ZS5tb2RlbC5maWxlcykpCgojIGJpb2xvZ2ljYWwgY29udGV4dApjb250ZXh0Lm1vZGVsLmZpbGVzIDwtIGxpc3QuZmlsZXMobW9kZWxzLmRpciwgcGF0dGVybiA9ICJhY2Nlc3Npb25zIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bGwubmFtZXMgPSBUUlVFKQpuYW1lcyhjb250ZXh0Lm1vZGVsLmZpbGVzKSA8LSAKICBzdHJpbmdyOjpzdHJfbWF0Y2goY29udGV4dC5tb2RlbC5maWxlcywgInJlY291bnQyXyguKj8pX2FjY2Vzc2lvbnMiKVssIDJdCgpgYGAKCiMjIyMgQ2hlY2sgc3BhcnNpdHkKCmBgYHtyfQpzaXplLnJlc3VsdHMgPC0gR2V0UmVzdWx0cyhzaXplLm1vZGVsLmZpbGVzKQpzaXplLmRmIDwtIHJlc2hhcGUyOjptZWx0KHNpemUucmVzdWx0cykKc3VtbWFyeShkcGx5cjo6ZmlsdGVyKHNpemUuZGYsIEwzID09ICJjb2x1bW5fd2lzZSIpJHZhbHVlKQpgYGAKCmBgYHtyfQpjb250ZXh0LnJlc3VsdHMgPC0gR2V0UmVzdWx0cyhjb250ZXh0Lm1vZGVsLmZpbGVzKQpjb250ZXh0LmRmIDwtIHJlc2hhcGUyOjptZWx0KGNvbnRleHQucmVzdWx0cykKc3VtbWFyeShkcGx5cjo6ZmlsdGVyKGNvbnRleHQuZGYsIEwzID09ICJjb2x1bW5fd2lzZSIpJHZhbHVlKQpgYGAKClRoZXNlIHJlc3VsdHMgYXJlIGNvbnNpc3RlbnQgd2l0aCBvdXIgZXhwZWN0YXRpb25zLgo=