J. Taroni 2018
We’ve noted that pathways or genesets that are similar to one another, such as cell types of the myeloid lineage (e.g., neutrophils and monocytes/macrophages), can tend to get “lumped together” in PLIER models trained on training sets with smaller sample sizes or less relevant biological contexts. In contrast, the MultiPLIER model (~37K samples) tends to “separate” similar pathways into different latent variables. (See 06-sle-wb_cell_type
and 07-sle_cell_type_recount2_model
.)
Here, we’re interested in the notion of “pathway separation” when different datasets are used for training.
We’ll group related pathways/genesets into pathway sets. For instance, we’ll group monocyte- and macrophage-related gene sets such as SVM Monocytes
and DMAP_MONO2
into a “monocyte-related pathway set.”
We define pathway separation as the following:
- For each pathway set, at least one latent variable is significantly associated (
FDR < 0.05
) with a pathway in that set. We consider capturing only one pathway set to be insufficient for this evaluation. In essence, we want to make sure each set of pathways is represented in or captured by the model.
- Each pathway set is uniquely and significantly associated with at least one latent variable. In the neutrophil vs. monocyte/macrophage example, it’s not sufficient to have a neutrophil-associated latent variable and a latent variable associated with both neutrophils and monocytes/macrophages; we also want the monocyte/macrophage set to be captured in a latent variable that is not also associated with the neutrophil set.
Set up
library(ComplexHeatmap)
Loading required package: grid
========================================
ComplexHeatmap version 1.17.1
Bioconductor page: http://bioconductor.org/packages/ComplexHeatmap/
Github page: https://github.com/jokergoo/ComplexHeatmap
Documentation: http://bioconductor.org/packages/ComplexHeatmap/
If you use it in published research, please cite:
Gu, Z. Complex heatmaps reveal patterns and correlations in multidimensional
genomic data. Bioinformatics 2016.
========================================
Functions
`%>%` <- dplyr::`%>%`
Custom function for detecting pathway separation
CheckPathwaySeparation <- function(plier.model, pathway.set1, pathway.set2,
fdr.cutoff = 0.05) {
# Given PLIER model and two sets of pathways, check if the model is able
# to "separate" them -- i.e., does an LV exist that is significantly
# associated with pathways in one set BUT NOT the other set -- and return
# a logical
#
# Args:
# plier.model: the output list from PLIER::PLIER
# pathway.set1: a character vector of one set of related pathways
# pathway.set2: a character vector of the other set of related pathways
# fdr.cutoff: what value should serve as the threshold for the "significant"
# associations? 0.05 by default
#
# Returns:
# A logical constant (TRUE or FALSE) that indicates whether or not the
# conditions for pathway separation have been met for pathway.set1 and
# pathway.set2
#
# takes a vector of pathway names and the data.frame that only includes
# significant associations between pathways and LVs (summary data.frame
# from PLIER::PLIER)
GetAssociatedLVs <- function(set.of.pathways, filtered.df) {
# this collapsing step will not be problematic if there is
search.pattern <- paste(set.of.pathways, collapse = "|")
search.index <- grep(search.pattern, filtered.df$pathway)
associated.lvs <- unique(filtered.df$`LV index`[search.index])
}
# magrittr pipe
`%>%` <- dplyr::`%>%`
# filter the summary data.frame output from PLIER::PLIER to only associations
# that meet the fdr.cutoff threshold
sig.summary.df <- plier.model$summary %>%
dplyr::filter(FDR < fdr.cutoff)
# which LVs are associated with set 1?
lvs.set1 <- GetAssociatedLVs(set.of.pathways = pathway.set1,
filtered.df = sig.summary.df)
# which LVs are associated with set 2?
lvs.set2 <- GetAssociatedLVs(set.of.pathways = pathway.set2,
filtered.df = sig.summary.df)
# are both sets of pathways captured?
captured <- all(c(length(lvs.set1) > 0, length(lvs.set2) > 0))
# if not, this doesn't qualify as separation
if (!captured) {
return(FALSE)
} else {
# is there at least one latent variable that is only associated with
# that set -- for both sets
set1.unique <- length(setdiff(lvs.set1, lvs.set2)) > 0
set2.unique <- length(setdiff(lvs.set2, lvs.set1)) > 0
# if so, return TRUE
return(all(set1.unique, set2.unique))
}
}
Files
Directory setup
# plot and result directory setup for this notebook
plot.dir <- file.path("plots", "32")
dir.create(plot.dir, recursive = TRUE, showWarnings = FALSE)
results.dir <- file.path("results", "32")
dir.create(results.dir, recursive = TRUE, showWarnings = FALSE)
All subsampling and biological context models we will be evaluating are in models/
models.dir <- "models"
Sets of pathways for pathway separation
Type I and type II interferon
Interferon (IFN)
ifn.alpha.set <- c("REACTOME_INTERFERON_ALPHA_BETA_SIGNALING")
ifn.gamma.set <- c("REACTOME_INTERFERON_GAMMA_SIGNALING")
Myeloid lineage
neutrophil.set <- c("DMAP_GRAN3", "IRIS_Neutrophil-Resting", "SVM Neutrophils")
monocyte.set <- c("IRIS_Monocyte-Day0", "IRIS_Monocyte-Day1",
"IRIS_Monocyte-Day7", "DMAP_MONO2", "SVM Monocytes",
"SVM Macrophages M0", "SVM Macrophages M1",
"SVM Macrophages M2")
Proliferation
Molecular processes we would associate with proliferating cells, namely the G1 and G2 phases of the cell cycle.
g1.set <- c("REACTOME_G1_S_TRANSITION", "REACTOME_M_G1_TRANSITION",
"REACTOME_APC_C_CDH1_MEDIATED_DEGRADATION_OF_CDC20_AND_OTHER_APC_C_CDH1_TARGETED_PROTEINS_IN_LATE_MITOSIS_EARLY_G1",
"REACTOME_CYCLIN_E_ASSOCIATED_EVENTS_DURING_G1_S_TRANSITION_",
"REACTOME_G1_PHASE", "REACTOME_MITOTIC_M_M_G1_PHASES",
"REACTOME_P53_DEPENDENT_G1_DNA_DAMAGE_RESPONSE",
"REACTOME_MITOTIC_G1_G1_S_PHASES",
"REACTOME_P53_INDEPENDENT_G1_S_DNA_DAMAGE_CHECKPOINT")
g2.set <- c("REACTOME_MITOTIC_G2_G2_M_PHASES", "REACTOME_G2_M_CHECKPOINTS")
Wrapper functions
These are wrapper functions that are not intended to be used outside of the context of this notebook, i.e., we expect ifn.alpha.set
, etc. to be in the global environment and we’ve essentially hard-coded this to use FDR < 0.05
.
# check pathway separation of all pairs of pathways -- IFN, myeloid,
# 'proliferation'
AllPairs <- function(plier.model) {
ifn.results <- CheckPathwaySeparation(plier.model = plier.model,
pathway.set1 = ifn.alpha.set,
pathway.set2 = ifn.gamma.set)
myeloid.results <- CheckPathwaySeparation(plier.model = plier.model,
pathway.set1 = neutrophil.set,
pathway.set2 = monocyte.set)
proliferation.results <-
CheckPathwaySeparation(plier.model = plier.model,
pathway.set1 = g1.set,
pathway.set2 = g2.set)
return(list(IFN = ifn.results, MYELOID = myeloid.results,
PROLIFERATION = proliferation.results))
}
# this is specifically designed for RDS files that are the output from
# scripts/subsampling_PLIER.R (e.g., have repeats, elements named PLIER)
GetAllPairsDataFrame <- function(model.files, id.name) {
# Given a named vector of filenames, get a data.frame of AllPairs results
#
# Args:
# model.files: named vector of filenames
# id.name: what should the colname of the identifier be (e.g.,
# "sample_size")
#
# Returns:
# A data.frame of AllPairs results
# no names? get outta here
if(is.null(names(model.files))) {
stop("model.files must be a named vector!")
}
# for each file, read in the RDS (output of scripts/subsampling_PLIER.R) and
# run AllPairs
results.list <- lapply(model.files,
function(x) {
# read in the list of 5 models
models.list <- readRDS(x)
lapply(models.list,
function(y) {
# we need to specifically extract the
# `PLIER` element of the list and test all
# pairof pathways
AllPairs(plier.model = y$PLIER)
})
})
# melt and bind the AllPairs pathway separation results, using the identifier
# supplied as id.name
results.df <- dplyr::bind_rows(lapply(results.list, reshape2::melt),
.id = id.name)
colnames(results.df)[3:4] <- c("pathway", "seed")
# return the results data.frame
return(results.df)
}
# given the output of AllPairsDataFrame, get it suitable shape for heatmaps
WrangleForHeatmap <- function(results.df, group.colname, group.order) {
wrangled.df <- results.df %>%
# for each group, pathway pair
dplyr::group_by(!!rlang::sym(group.colname), pathway) %>%
# count how many models where there's separation
dplyr::summarize(model_count = sum(value)) %>%
# spread the columns
tidyr::spread(!!rlang::sym(group.colname), model_count) %>%
# reorder using the vector of "levels" from group.order
dplyr::select(c("pathway", group.order)) %>%
# the pathway names should be rownames rather than an
# individual column
tibble::column_to_rownames("pathway") %>%
as.data.frame()
}
Sample size
Evaluations for the effect of sample size of pathway separation, with the following sample sizes: 500
, 1000
, 2000
, 4000
, 8000
, 16000
, 32000
We performed 5 repeats with different random seeds (see 29-train_models_different_sample_size.sh
and scripts/subsampling_PLIER.R
).
# we can derive useful names from the names of the model files
# themselves
names(size.model.files) <- sub(".RDS", "", sub(".*[_]", "", size.model.files))
# detect pathway separation
size.results.df <- GetAllPairsDataFrame(size.model.files,
id.name = "sample_size")
We’re going to represent this as a heatmap, so we’ll need to wrangle the results into a data.frame
that looks like this
Where we’re counting how many of the 5 models (repeats) the pairs of pathways are separated.
We’ve written WrangleForHeatmap
(see above) for this purpose.
size.df <- WrangleForHeatmap(results.df = size.results.df,
group.colname = "sample_size",
group.order = c("500", "1000", "2000", "4000",
"8000", "16000", "32000"))
Setting row names on a tibble is deprecated.
# let's take a look at the resulting data.frame
size.df
size.heatmap <-
ComplexHeatmap::Heatmap(as.matrix(size.df),
cluster_rows = FALSE,
cluster_columns = FALSE,
row_names_side = "left",
column_names_side = "top",
col = colorRampPalette(c("white", "blue3"))(6),
rect_gp = grid::gpar(col = "black"),
show_heatmap_legend = TRUE,
name = "number of models")
size.heatmap

Biological context
names(context.model.files) <-
stringr::str_match(context.model.files, "recount2_(.*?)_accessions")[, 2]
# detect for pathway separation
context.results.df <- GetAllPairsDataFrame(context.model.files,
id.name = "biological_context")
Wrangle data for heatmap
context.df <- WrangleForHeatmap(results.df = context.results.df,
group.colname = "biological_context",
group.order = c("blood", "cancer", "tissue",
"cell_line", "other_tissues"))
Setting row names on a tibble is deprecated.
context.heatmap <-
ComplexHeatmap::Heatmap(as.matrix(context.df),
cluster_rows = FALSE,
cluster_columns = FALSE,
row_names_side = "left",
column_names_side = "top",
col = colorRampPalette(c("white", "blue3"))(6),
rect_gp = grid::gpar(col = "black"),
show_heatmap_legend = FALSE,
name = "number of models",
show_row_names = FALSE)
MultiPLIER
Now repeat this with the full model.
# read in the model
multiplier.model <- readRDS(recount2.model.file)
# check all pairs for separation
multiplier.results <- AllPairs(plier.model = multiplier.model)
# there's only one model -- so this is a binary outcome!
multiplier.df <- reshape2::melt(multiplier.results) %>% # melt the results
# the variable name is pathway
dplyr::mutate(pathway = L1,
MultiPLIER = as.integer(value)) %>%
dplyr::select(c("pathway", "MultiPLIER")) %>%
tibble::column_to_rownames("pathway") %>%
as.data.frame()
Heatmap itself
multiplier.heatmap <-
ComplexHeatmap::Heatmap(as.matrix(multiplier.df),
cluster_rows = FALSE,
cluster_columns = FALSE,
column_names_side = "top",
col = "blue4",
rect_gp = grid::gpar(col = "black"),
name = "pathway separation",
show_heatmap_legend = TRUE,
show_row_names = FALSE)
Final plotting
heatmap.list <- size.heatmap + context.heatmap + multiplier.heatmap
Heatmap/row annotation names are duplicated: number of modelsHeatmap/row annotation names are duplicated: number of models
pdf(file.path(plot.dir, "multiplier_separation.pdf"))
ComplexHeatmap::draw(heatmap.list)
dev.off()
null device
1
LS0tCnRpdGxlOiAiUGF0aHdheSAnc2VwYXJhdGlvbiciCm91dHB1dDogICAKICBodG1sX25vdGVib29rOiAKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKKipKLiBUYXJvbmkgMjAxOCoqCgpXZSd2ZSBub3RlZCB0aGF0IHBhdGh3YXlzIG9yIGdlbmVzZXRzIHRoYXQgYXJlIHNpbWlsYXIgdG8gb25lIGFub3RoZXIsIHN1Y2ggYXMgCmNlbGwgdHlwZXMgb2YgdGhlIG15ZWxvaWQgbGluZWFnZSAoZS5nLiwgbmV1dHJvcGhpbHMgYW5kIG1vbm9jeXRlcy9tYWNyb3BoYWdlcyksCmNhbiB0ZW5kIHRvIGdldCAibHVtcGVkIHRvZ2V0aGVyIiBpbiBQTElFUiBtb2RlbHMgdHJhaW5lZCBvbiB0cmFpbmluZyBzZXRzIHdpdGgKc21hbGxlciBzYW1wbGUgc2l6ZXMgb3IgbGVzcyByZWxldmFudCBiaW9sb2dpY2FsIGNvbnRleHRzLgpJbiBjb250cmFzdCwgdGhlIE11bHRpUExJRVIgbW9kZWwgKH4zN0sgc2FtcGxlcykgdGVuZHMgdG8gInNlcGFyYXRlIiBzaW1pbGFyIApwYXRod2F5cyBpbnRvIGRpZmZlcmVudCBsYXRlbnQgdmFyaWFibGVzLgooU2VlIGAwNi1zbGUtd2JfY2VsbF90eXBlYCBhbmQgYDA3LXNsZV9jZWxsX3R5cGVfcmVjb3VudDJfbW9kZWxgLikKCioqSGVyZSwgd2UncmUgaW50ZXJlc3RlZCBpbiB0aGUgbm90aW9uIG9mICJwYXRod2F5IHNlcGFyYXRpb24iIHdoZW4gZGlmZmVyZW50IApkYXRhc2V0cyBhcmUgdXNlZCBmb3IgdHJhaW5pbmcuKioKCldlJ2xsIGdyb3VwIHJlbGF0ZWQgcGF0aHdheXMvZ2VuZXNldHMgaW50byBwYXRod2F5IHNldHMuCkZvciBpbnN0YW5jZSwgd2UnbGwgZ3JvdXAgbW9ub2N5dGUtIGFuZCBtYWNyb3BoYWdlLXJlbGF0ZWQgZ2VuZSBzZXRzIHN1Y2ggYXMKYFNWTSBNb25vY3l0ZXNgIGFuZCBgRE1BUF9NT05PMmAgaW50byBhICJtb25vY3l0ZS1yZWxhdGVkIHBhdGh3YXkgc2V0LiIKCldlIGRlZmluZSAqKnBhdGh3YXkgc2VwYXJhdGlvbioqIGFzIHRoZSBmb2xsb3dpbmc6CgoqIEZvciBlYWNoIHBhdGh3YXkgc2V0LCBhdCBsZWFzdCBvbmUgbGF0ZW50IHZhcmlhYmxlIGlzIHNpZ25pZmljYW50bHkKYXNzb2NpYXRlZCAoYEZEUiA8IDAuMDVgKSB3aXRoIGEgcGF0aHdheSBpbiB0aGF0IHNldC4KV2UgY29uc2lkZXIgY2FwdHVyaW5nIF9vbmx5IG9uZSBwYXRod2F5IHNldF8gdG8gYmUgaW5zdWZmaWNpZW50IGZvciB0aGlzIApldmFsdWF0aW9uLgpJbiBlc3NlbmNlLCB3ZSB3YW50IHRvIG1ha2Ugc3VyZSBlYWNoIHNldCBvZiBwYXRod2F5cyBpcyByZXByZXNlbnRlZCBpbiBvciAKY2FwdHVyZWQgYnkgdGhlIG1vZGVsLiAKKiBFYWNoIHBhdGh3YXkgc2V0IGlzIF91bmlxdWVseV8gYW5kIHNpZ25pZmljYW50bHkgYXNzb2NpYXRlZCB3aXRoIGF0IGxlYXN0IG9uZQpsYXRlbnQgdmFyaWFibGUuIApJbiB0aGUgbmV1dHJvcGhpbCB2cy4gbW9ub2N5dGUvbWFjcm9waGFnZSBleGFtcGxlLCBpdCdzIG5vdCBzdWZmaWNpZW50IHRvIGhhdmUgYQpuZXV0cm9waGlsLWFzc29jaWF0ZWQgbGF0ZW50IHZhcmlhYmxlIGFuZCBhIGxhdGVudCB2YXJpYWJsZSBhc3NvY2lhdGVkIHdpdGggCmJvdGggbmV1dHJvcGhpbHMgYW5kIG1vbm9jeXRlcy9tYWNyb3BoYWdlczsgd2UgYWxzbyB3YW50IHRoZSBtb25vY3l0ZS9tYWNyb3BoYWdlCnNldCB0byBiZSBjYXB0dXJlZCBpbiBhIGxhdGVudCB2YXJpYWJsZSB0aGF0IGlzIF9ub3QgYWxzbyBhc3NvY2lhdGVkXyB3aXRoCnRoZSBuZXV0cm9waGlsIHNldC4KCiMjIFNldCB1cAoKYGBge3J9CmxpYnJhcnkoQ29tcGxleEhlYXRtYXApCmBgYAoKIyMjIEZ1bmN0aW9ucwoKYGBge3J9CmAlPiVgIDwtIGRwbHlyOjpgJT4lYApgYGAKCiMjIyMgQ3VzdG9tIGZ1bmN0aW9uIGZvciBkZXRlY3RpbmcgcGF0aHdheSBzZXBhcmF0aW9uCgpgYGB7cn0KQ2hlY2tQYXRod2F5U2VwYXJhdGlvbiA8LSBmdW5jdGlvbihwbGllci5tb2RlbCwgcGF0aHdheS5zZXQxLCBwYXRod2F5LnNldDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmRyLmN1dG9mZiA9IDAuMDUpIHsKICAjIEdpdmVuIFBMSUVSIG1vZGVsIGFuZCB0d28gc2V0cyBvZiBwYXRod2F5cywgY2hlY2sgaWYgdGhlIG1vZGVsIGlzIGFibGUKICAjIHRvICJzZXBhcmF0ZSIgdGhlbSAtLSBpLmUuLCBkb2VzIGFuIExWIGV4aXN0IHRoYXQgaXMgc2lnbmlmaWNhbnRseSAKICAjIGFzc29jaWF0ZWQgd2l0aCBwYXRod2F5cyBpbiBvbmUgc2V0IEJVVCBOT1QgdGhlIG90aGVyIHNldCAtLSBhbmQgcmV0dXJuCiAgIyBhIGxvZ2ljYWwKICAjCiAgIyBBcmdzOgogICMgIHBsaWVyLm1vZGVsOiB0aGUgb3V0cHV0IGxpc3QgZnJvbSBQTElFUjo6UExJRVIKICAjICBwYXRod2F5LnNldDE6IGEgY2hhcmFjdGVyIHZlY3RvciBvZiBvbmUgc2V0IG9mIHJlbGF0ZWQgcGF0aHdheXMKICAjICBwYXRod2F5LnNldDI6IGEgY2hhcmFjdGVyIHZlY3RvciBvZiB0aGUgb3RoZXIgc2V0IG9mIHJlbGF0ZWQgcGF0aHdheXMKICAjICBmZHIuY3V0b2ZmOiB3aGF0IHZhbHVlIHNob3VsZCBzZXJ2ZSBhcyB0aGUgdGhyZXNob2xkIGZvciB0aGUgInNpZ25pZmljYW50IgogICMgICAgICAgICAgICAgIGFzc29jaWF0aW9ucz8gMC4wNSBieSBkZWZhdWx0CiAgIyAKICAjIFJldHVybnM6IAogICMgICBBIGxvZ2ljYWwgY29uc3RhbnQgKFRSVUUgb3IgRkFMU0UpIHRoYXQgaW5kaWNhdGVzIHdoZXRoZXIgb3Igbm90IHRoZSAKICAjICAgY29uZGl0aW9ucyBmb3IgcGF0aHdheSBzZXBhcmF0aW9uIGhhdmUgYmVlbiBtZXQgZm9yIHBhdGh3YXkuc2V0MSBhbmQKICAjICAgcGF0aHdheS5zZXQyIAogICMgCiAgCiAgIyB0YWtlcyBhIHZlY3RvciBvZiBwYXRod2F5IG5hbWVzIGFuZCB0aGUgZGF0YS5mcmFtZSB0aGF0IG9ubHkgaW5jbHVkZXMKICAjIHNpZ25pZmljYW50IGFzc29jaWF0aW9ucyBiZXR3ZWVuIHBhdGh3YXlzIGFuZCBMVnMgKHN1bW1hcnkgZGF0YS5mcmFtZQogICMgZnJvbSBQTElFUjo6UExJRVIpCiAgR2V0QXNzb2NpYXRlZExWcyA8LSBmdW5jdGlvbihzZXQub2YucGF0aHdheXMsIGZpbHRlcmVkLmRmKSB7CiAgICAjIHRoaXMgY29sbGFwc2luZyBzdGVwIHdpbGwgbm90IGJlIHByb2JsZW1hdGljIGlmIHRoZXJlIGlzIAogICAgc2VhcmNoLnBhdHRlcm4gPC0gcGFzdGUoc2V0Lm9mLnBhdGh3YXlzLCBjb2xsYXBzZSA9ICJ8IikKICAgIHNlYXJjaC5pbmRleCA8LSBncmVwKHNlYXJjaC5wYXR0ZXJuLCBmaWx0ZXJlZC5kZiRwYXRod2F5KQogICAgYXNzb2NpYXRlZC5sdnMgPC0gdW5pcXVlKGZpbHRlcmVkLmRmJGBMViBpbmRleGBbc2VhcmNoLmluZGV4XSkKICB9CiAgCiAgIyBtYWdyaXR0ciBwaXBlCiAgYCU+JWAgPC0gZHBseXI6OmAlPiVgCiAgCiAgIyBmaWx0ZXIgdGhlIHN1bW1hcnkgZGF0YS5mcmFtZSBvdXRwdXQgZnJvbSBQTElFUjo6UExJRVIgdG8gb25seSBhc3NvY2lhdGlvbnMKICAjIHRoYXQgbWVldCB0aGUgZmRyLmN1dG9mZiB0aHJlc2hvbGQKICBzaWcuc3VtbWFyeS5kZiA8LSBwbGllci5tb2RlbCRzdW1tYXJ5ICU+JQogICAgZHBseXI6OmZpbHRlcihGRFIgPCBmZHIuY3V0b2ZmKQogIAogICMgd2hpY2ggTFZzIGFyZSBhc3NvY2lhdGVkIHdpdGggc2V0IDE/CiAgbHZzLnNldDEgPC0gR2V0QXNzb2NpYXRlZExWcyhzZXQub2YucGF0aHdheXMgPSBwYXRod2F5LnNldDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXJlZC5kZiA9IHNpZy5zdW1tYXJ5LmRmKQogIAogICMgd2hpY2ggTFZzIGFyZSBhc3NvY2lhdGVkIHdpdGggc2V0IDI/IAogIGx2cy5zZXQyIDwtIEdldEFzc29jaWF0ZWRMVnMoc2V0Lm9mLnBhdGh3YXlzID0gcGF0aHdheS5zZXQyLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsdGVyZWQuZGYgPSBzaWcuc3VtbWFyeS5kZikKICAKICAjIGFyZSBib3RoIHNldHMgb2YgcGF0aHdheXMgY2FwdHVyZWQ/CiAgY2FwdHVyZWQgPC0gYWxsKGMobGVuZ3RoKGx2cy5zZXQxKSA+IDAsIGxlbmd0aChsdnMuc2V0MikgPiAwKSkKICAjIGlmIG5vdCwgdGhpcyBkb2Vzbid0IHF1YWxpZnkgYXMgc2VwYXJhdGlvbgogIGlmICghY2FwdHVyZWQpIHsKICAgIHJldHVybihGQUxTRSkKICB9IGVsc2UgewogICAgIyBpcyB0aGVyZSBhdCBsZWFzdCBvbmUgbGF0ZW50IHZhcmlhYmxlIHRoYXQgaXMgb25seSBhc3NvY2lhdGVkIHdpdGgKICAgICMgdGhhdCBzZXQgLS0gZm9yIGJvdGggc2V0cwogICAgc2V0MS51bmlxdWUgPC0gbGVuZ3RoKHNldGRpZmYobHZzLnNldDEsIGx2cy5zZXQyKSkgPiAwCiAgICBzZXQyLnVuaXF1ZSA8LSBsZW5ndGgoc2V0ZGlmZihsdnMuc2V0MiwgbHZzLnNldDEpKSA+IDAKICAgICMgaWYgc28sIHJldHVybiBUUlVFCiAgICByZXR1cm4oYWxsKHNldDEudW5pcXVlLCBzZXQyLnVuaXF1ZSkpIAogIH0KfQpgYGAKCiMjIyBGaWxlcwoKIyMjIyBEaXJlY3Rvcnkgc2V0dXAKCmBgYHtyfQojIHBsb3QgYW5kIHJlc3VsdCBkaXJlY3Rvcnkgc2V0dXAgZm9yIHRoaXMgbm90ZWJvb2sKcGxvdC5kaXIgPC0gZmlsZS5wYXRoKCJwbG90cyIsICIzMiIpCmRpci5jcmVhdGUocGxvdC5kaXIsIHJlY3Vyc2l2ZSA9IFRSVUUsIHNob3dXYXJuaW5ncyA9IEZBTFNFKQpyZXN1bHRzLmRpciA8LSBmaWxlLnBhdGgoInJlc3VsdHMiLCAiMzIiKQpkaXIuY3JlYXRlKHJlc3VsdHMuZGlyLCByZWN1cnNpdmUgPSBUUlVFLCBzaG93V2FybmluZ3MgPSBGQUxTRSkKYGBgCgpBbGwgc3Vic2FtcGxpbmcgYW5kIGJpb2xvZ2ljYWwgY29udGV4dCBtb2RlbHMgd2Ugd2lsbCBiZSBldmFsdWF0aW5nIGFyZSBpbiAKYG1vZGVscy9gCgpgYGB7cn0KbW9kZWxzLmRpciA8LSAibW9kZWxzIgpgYGAKCiMjIyMgSW5wdXQKCk1vZGVscyBmcm9tIHRoZSBjb25kaXRpb25zIGJlaW5nIHRlc3RlZDogc2FtcGxlIHNpemUgYW5kIGJpb2xvZ2ljYWwgY29udGV4dAoKYGBge3J9CiMgbW9kZWxzIHdpdGggZGlmZmVyZW50IHNhbXBsZSBzaXplcyAtLSAic3Vic2FtcGxlZCIgaXMgaW4gdGhlIFJEUyBvYmplY3QgCiMgZmlsZSBuYW1lcwpzaXplLm1vZGVsLmZpbGVzIDwtIGxpc3QuZmlsZXMobW9kZWxzLmRpciwgcGF0dGVybiA9ICJzdWJzYW1wbGVkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsLm5hbWVzID0gVFJVRSkKc2l6ZS5tb2RlbC5maWxlcwpgYGAKCmBgYHtyfQojIG1vZGVscyBmb3IgZGlmZmVyZW50IGJpb2xvZ2ljYWwgY29udGV4dHMgLS0gImFjY2Vzc2lvbnMiIGlzIGluIHRoZSBSRFMgb2JqZWN0IAojIGZpbGUgbmFtZXMKY29udGV4dC5tb2RlbC5maWxlcyA8LSBsaXN0LmZpbGVzKG1vZGVscy5kaXIsIHBhdHRlcm4gPSAiYWNjZXNzaW9ucyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmdWxsLm5hbWVzID0gVFJVRSkKY29udGV4dC5tb2RlbC5maWxlcwpgYGAKCkFsc28gd2FudCB0aGUgTXVsdGlQTElFUiAvIGZ1bGwgYHJlY291bnQyYCBtb2RlbAoKYGBge3J9CnJlY291bnQyLm1vZGVsLmZpbGUgPC0gZmlsZS5wYXRoKCJkYXRhIiwgInJlY291bnQyX1BMSUVSX2RhdGEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgInJlY291bnRfUExJRVJfbW9kZWwuUkRTIikKYGBgCgojIyBTZXRzIG9mIHBhdGh3YXlzIGZvciBwYXRod2F5IHNlcGFyYXRpb24KCiMjIyMgVHlwZSBJIGFuZCB0eXBlIElJIGludGVyZmVyb24KCkludGVyZmVyb24gKElGTikKCmBgYHtyfQppZm4uYWxwaGEuc2V0IDwtIGMoIlJFQUNUT01FX0lOVEVSRkVST05fQUxQSEFfQkVUQV9TSUdOQUxJTkciKQppZm4uZ2FtbWEuc2V0IDwtIGMoIlJFQUNUT01FX0lOVEVSRkVST05fR0FNTUFfU0lHTkFMSU5HIikKYGBgCgojIyMjIE15ZWxvaWQgbGluZWFnZQoKYGBge3J9Cm5ldXRyb3BoaWwuc2V0IDwtIGMoIkRNQVBfR1JBTjMiLCAiSVJJU19OZXV0cm9waGlsLVJlc3RpbmciLCAiU1ZNIE5ldXRyb3BoaWxzIikKbW9ub2N5dGUuc2V0IDwtIGMoIklSSVNfTW9ub2N5dGUtRGF5MCIsICJJUklTX01vbm9jeXRlLURheTEiLCAKICAgICAgICAgICAgICAgICAgIklSSVNfTW9ub2N5dGUtRGF5NyIsICJETUFQX01PTk8yIiwgIlNWTSBNb25vY3l0ZXMiLAogICAgICAgICAgICAgICAgICAiU1ZNIE1hY3JvcGhhZ2VzIE0wIiwgIlNWTSBNYWNyb3BoYWdlcyBNMSIsIAogICAgICAgICAgICAgICAgICAiU1ZNIE1hY3JvcGhhZ2VzIE0yIikKYGBgCgojIyMjIFByb2xpZmVyYXRpb24KCk1vbGVjdWxhciBwcm9jZXNzZXMgd2Ugd291bGQgYXNzb2NpYXRlIHdpdGggcHJvbGlmZXJhdGluZyBjZWxscywgbmFtZWx5IHRoZQpHMSBhbmQgRzIgcGhhc2VzIG9mIHRoZSBjZWxsIGN5Y2xlLgoKYGBge3J9CmcxLnNldCA8LSBjKCJSRUFDVE9NRV9HMV9TX1RSQU5TSVRJT04iLCAiUkVBQ1RPTUVfTV9HMV9UUkFOU0lUSU9OIiwKICAgICAgICAgICAgIlJFQUNUT01FX0FQQ19DX0NESDFfTUVESUFURURfREVHUkFEQVRJT05fT0ZfQ0RDMjBfQU5EX09USEVSX0FQQ19DX0NESDFfVEFSR0VURURfUFJPVEVJTlNfSU5fTEFURV9NSVRPU0lTX0VBUkxZX0cxIiwgCiAgICAgICAgICAgICJSRUFDVE9NRV9DWUNMSU5fRV9BU1NPQ0lBVEVEX0VWRU5UU19EVVJJTkdfRzFfU19UUkFOU0lUSU9OXyIsIAogICAgICAgICAgICAiUkVBQ1RPTUVfRzFfUEhBU0UiLCAiUkVBQ1RPTUVfTUlUT1RJQ19NX01fRzFfUEhBU0VTIiwKICAgICAgICAgICAgIlJFQUNUT01FX1A1M19ERVBFTkRFTlRfRzFfRE5BX0RBTUFHRV9SRVNQT05TRSIsIAogICAgICAgICAgICAiUkVBQ1RPTUVfTUlUT1RJQ19HMV9HMV9TX1BIQVNFUyIsIAogICAgICAgICAgICAiUkVBQ1RPTUVfUDUzX0lOREVQRU5ERU5UX0cxX1NfRE5BX0RBTUFHRV9DSEVDS1BPSU5UIikKZzIuc2V0IDwtIGMoIlJFQUNUT01FX01JVE9USUNfRzJfRzJfTV9QSEFTRVMiLCAiUkVBQ1RPTUVfRzJfTV9DSEVDS1BPSU5UUyIpCmBgYAoKIyMjIyBXcmFwcGVyIGZ1bmN0aW9ucwoKVGhlc2UgYXJlIHdyYXBwZXIgZnVuY3Rpb25zIHRoYXQgYXJlIG5vdCBpbnRlbmRlZCB0byBiZSB1c2VkIG91dHNpZGUgb2YgdGhlCmNvbnRleHQgb2YgdGhpcyBub3RlYm9vaywgaS5lLiwgd2UgZXhwZWN0IGBpZm4uYWxwaGEuc2V0YCwgZXRjLiB0byBiZQppbiB0aGUgZ2xvYmFsIGVudmlyb25tZW50IGFuZCB3ZSd2ZSBlc3NlbnRpYWxseSBoYXJkLWNvZGVkIHRoaXMgdG8gdXNlIApgRkRSIDwgMC4wNWAuCgpgYGB7cn0KIyBjaGVjayBwYXRod2F5IHNlcGFyYXRpb24gb2YgYWxsIHBhaXJzIG9mIHBhdGh3YXlzIC0tIElGTiwgbXllbG9pZCwKIyAncHJvbGlmZXJhdGlvbicKQWxsUGFpcnMgPC0gZnVuY3Rpb24ocGxpZXIubW9kZWwpIHsKICBpZm4ucmVzdWx0cyA8LSBDaGVja1BhdGh3YXlTZXBhcmF0aW9uKHBsaWVyLm1vZGVsID0gcGxpZXIubW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXRod2F5LnNldDEgPSBpZm4uYWxwaGEuc2V0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0aHdheS5zZXQyID0gaWZuLmdhbW1hLnNldCkKICBteWVsb2lkLnJlc3VsdHMgPC0gQ2hlY2tQYXRod2F5U2VwYXJhdGlvbihwbGllci5tb2RlbCA9IHBsaWVyLm1vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0aHdheS5zZXQxID0gbmV1dHJvcGhpbC5zZXQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXRod2F5LnNldDIgPSBtb25vY3l0ZS5zZXQpCiAgcHJvbGlmZXJhdGlvbi5yZXN1bHRzIDwtIAogICAgQ2hlY2tQYXRod2F5U2VwYXJhdGlvbihwbGllci5tb2RlbCA9IHBsaWVyLm1vZGVsLAogICAgICAgICAgICAgICAgICAgICAgICAgICBwYXRod2F5LnNldDEgPSBnMS5zZXQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdGh3YXkuc2V0MiA9IGcyLnNldCkKCiAgcmV0dXJuKGxpc3QoSUZOID0gaWZuLnJlc3VsdHMsIE1ZRUxPSUQgPSBteWVsb2lkLnJlc3VsdHMsCiAgICAgICAgICAgICAgUFJPTElGRVJBVElPTiA9IHByb2xpZmVyYXRpb24ucmVzdWx0cykpCn0KYGBgCgpgYGB7cn0KIyB0aGlzIGlzIHNwZWNpZmljYWxseSBkZXNpZ25lZCBmb3IgUkRTIGZpbGVzIHRoYXQgYXJlIHRoZSBvdXRwdXQgZnJvbQojIHNjcmlwdHMvc3Vic2FtcGxpbmdfUExJRVIuUiAoZS5nLiwgaGF2ZSByZXBlYXRzLCBlbGVtZW50cyBuYW1lZCBQTElFUikgCkdldEFsbFBhaXJzRGF0YUZyYW1lIDwtIGZ1bmN0aW9uKG1vZGVsLmZpbGVzLCBpZC5uYW1lKSB7CiAgIyBHaXZlbiBhIG5hbWVkIHZlY3RvciBvZiBmaWxlbmFtZXMsIGdldCBhIGRhdGEuZnJhbWUgb2YgQWxsUGFpcnMgcmVzdWx0cwogICMgCiAgIyBBcmdzOgogICMgICBtb2RlbC5maWxlczogbmFtZWQgdmVjdG9yIG9mIGZpbGVuYW1lcwogICMgICBpZC5uYW1lOiB3aGF0IHNob3VsZCB0aGUgY29sbmFtZSBvZiB0aGUgaWRlbnRpZmllciBiZSAoZS5nLiwgCiAgIyAgICAgICAgICAgICJzYW1wbGVfc2l6ZSIpCiAgIyAKICAjIFJldHVybnM6CiAgIyAgIEEgZGF0YS5mcmFtZSBvZiBBbGxQYWlycyByZXN1bHRzCiAgCiAgIyBubyBuYW1lcz8gZ2V0IG91dHRhIGhlcmUKICBpZihpcy5udWxsKG5hbWVzKG1vZGVsLmZpbGVzKSkpIHsKICAgIHN0b3AoIm1vZGVsLmZpbGVzIG11c3QgYmUgYSBuYW1lZCB2ZWN0b3IhIikKICB9CiAgCiAgIyBmb3IgZWFjaCBmaWxlLCByZWFkIGluIHRoZSBSRFMgKG91dHB1dCBvZiBzY3JpcHRzL3N1YnNhbXBsaW5nX1BMSUVSLlIpIGFuZAogICMgcnVuIEFsbFBhaXJzCiAgcmVzdWx0cy5saXN0IDwtIGxhcHBseShtb2RlbC5maWxlcywgIAogICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeCkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAjIHJlYWQgaW4gdGhlIGxpc3Qgb2YgNSBtb2RlbHMKICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWxzLmxpc3QgPC0gcmVhZFJEUyh4KQogICAgICAgICAgICAgICAgICAgICAgICAgICBsYXBwbHkobW9kZWxzLmxpc3QsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oeSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHdlIG5lZWQgdG8gc3BlY2lmaWNhbGx5IGV4dHJhY3QgdGhlIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGBQTElFUmAgZWxlbWVudCBvZiB0aGUgbGlzdCBhbmQgdGVzdCBhbGwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgcGFpcm9mIHBhdGh3YXlzCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEFsbFBhaXJzKHBsaWVyLm1vZGVsID0geSRQTElFUikKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0pCiAgICAgICAgICAgICAgICAgICAgICAgICB9KQogIAogICMgbWVsdCBhbmQgYmluZCB0aGUgQWxsUGFpcnMgcGF0aHdheSBzZXBhcmF0aW9uIHJlc3VsdHMsIHVzaW5nIHRoZSBpZGVudGlmaWVyIAogICMgc3VwcGxpZWQgYXMgaWQubmFtZQogIHJlc3VsdHMuZGYgPC0gZHBseXI6OmJpbmRfcm93cyhsYXBwbHkocmVzdWx0cy5saXN0LCByZXNoYXBlMjo6bWVsdCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAuaWQgPSBpZC5uYW1lKQogIGNvbG5hbWVzKHJlc3VsdHMuZGYpWzM6NF0gPC0gYygicGF0aHdheSIsICJzZWVkIikKICAKICAjIHJldHVybiB0aGUgcmVzdWx0cyBkYXRhLmZyYW1lCiAgcmV0dXJuKHJlc3VsdHMuZGYpCiAgCn0KYGBgCgpgYGB7cn0KIyBnaXZlbiB0aGUgb3V0cHV0IG9mIEFsbFBhaXJzRGF0YUZyYW1lLCBnZXQgaXQgc3VpdGFibGUgc2hhcGUgZm9yIGhlYXRtYXBzCldyYW5nbGVGb3JIZWF0bWFwIDwtIGZ1bmN0aW9uKHJlc3VsdHMuZGYsIGdyb3VwLmNvbG5hbWUsIGdyb3VwLm9yZGVyKSB7CiAgd3JhbmdsZWQuZGYgPC0gcmVzdWx0cy5kZiAlPiUKICAgICMgZm9yIGVhY2ggZ3JvdXAsIHBhdGh3YXkgcGFpcgogICAgZHBseXI6Omdyb3VwX2J5KCEhcmxhbmc6OnN5bShncm91cC5jb2xuYW1lKSwgcGF0aHdheSkgJT4lCiAgICAjIGNvdW50IGhvdyBtYW55IG1vZGVscyB3aGVyZSB0aGVyZSdzIHNlcGFyYXRpb24KICAgIGRwbHlyOjpzdW1tYXJpemUobW9kZWxfY291bnQgPSBzdW0odmFsdWUpKSAlPiUKICAgICMgc3ByZWFkIHRoZSBjb2x1bW5zCiAgICB0aWR5cjo6c3ByZWFkKCEhcmxhbmc6OnN5bShncm91cC5jb2xuYW1lKSwgbW9kZWxfY291bnQpICU+JQogICAgIyByZW9yZGVyIHVzaW5nIHRoZSB2ZWN0b3Igb2YgImxldmVscyIgZnJvbSBncm91cC5vcmRlcgogICAgZHBseXI6OnNlbGVjdChjKCJwYXRod2F5IiwgZ3JvdXAub3JkZXIpKSAlPiUKICAgICMgdGhlIHBhdGh3YXkgbmFtZXMgc2hvdWxkIGJlIHJvd25hbWVzIHJhdGhlciB0aGFuIGFuIAogICAgIyBpbmRpdmlkdWFsIGNvbHVtbgogICAgdGliYmxlOjpjb2x1bW5fdG9fcm93bmFtZXMoInBhdGh3YXkiKSAlPiUKICAgIGFzLmRhdGEuZnJhbWUoKQp9CmBgYAoKIyMgU2FtcGxlIHNpemUKCkV2YWx1YXRpb25zIGZvciB0aGUgZWZmZWN0IG9mIHNhbXBsZSBzaXplIG9mIHBhdGh3YXkgc2VwYXJhdGlvbiwgd2l0aCB0aGUKZm9sbG93aW5nIHNhbXBsZSBzaXplczogYDUwMGAsIGAxMDAwYCwgYDIwMDBgLCBgNDAwMGAsIGA4MDAwYCwgYDE2MDAwYCwgYDMyMDAwYApXZSBwZXJmb3JtZWQgNSByZXBlYXRzIHdpdGggZGlmZmVyZW50IHJhbmRvbSBzZWVkcyAKKHNlZSBgMjktdHJhaW5fbW9kZWxzX2RpZmZlcmVudF9zYW1wbGVfc2l6ZS5zaGAgYW5kIApgc2NyaXB0cy9zdWJzYW1wbGluZ19QTElFUi5SYCkuCgpgYGB7cn0KIyB3ZSBjYW4gZGVyaXZlIHVzZWZ1bCBuYW1lcyBmcm9tIHRoZSBuYW1lcyBvZiB0aGUgbW9kZWwgZmlsZXMKIyB0aGVtc2VsdmVzCm5hbWVzKHNpemUubW9kZWwuZmlsZXMpIDwtIHN1YigiLlJEUyIsICIiLCBzdWIoIi4qW19dIiwgIiIsIHNpemUubW9kZWwuZmlsZXMpKQojIGRldGVjdCBwYXRod2F5IHNlcGFyYXRpb24Kc2l6ZS5yZXN1bHRzLmRmIDwtIEdldEFsbFBhaXJzRGF0YUZyYW1lKHNpemUubW9kZWwuZmlsZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZC5uYW1lID0gInNhbXBsZV9zaXplIikKYGBgCgpXZSdyZSBnb2luZyB0byByZXByZXNlbnQgdGhpcyBhcyBhIGhlYXRtYXAsIHNvIHdlJ2xsIG5lZWQgdG8gd3JhbmdsZSB0aGUKcmVzdWx0cyBpbnRvIGEgYGRhdGEuZnJhbWVgIHRoYXQgbG9va3MgbGlrZSB0aGlzCgp8ICAgfCA1MDAgfCAuLi4gfCAzMjAwMCB8Cnw6LTp8Oi0tLTp8Oi0tLTp8Oi0tLS0tOnwKfElGTnwgIDAgIHwgLi4uIHwgMyAgICAgfAp8TVlFTE9JRHwgIDAgIHwgLi4uIHwgNSAgIHwKCldoZXJlIHdlJ3JlIGNvdW50aW5nIGhvdyBtYW55IG9mIHRoZSA1IG1vZGVscyAocmVwZWF0cykgdGhlIHBhaXJzIG9mIHBhdGh3YXlzCmFyZSBzZXBhcmF0ZWQuCgpXZSd2ZSB3cml0dGVuIGBXcmFuZ2xlRm9ySGVhdG1hcGAgKHNlZSBhYm92ZSkgZm9yIHRoaXMgcHVycG9zZS4KCmBgYHtyfQpzaXplLmRmIDwtIFdyYW5nbGVGb3JIZWF0bWFwKHJlc3VsdHMuZGYgPSBzaXplLnJlc3VsdHMuZGYsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAuY29sbmFtZSA9ICJzYW1wbGVfc2l6ZSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAub3JkZXIgPSBjKCI1MDAiLCAiMTAwMCIsICIyMDAwIiwgIjQwMDAiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjgwMDAiLCAiMTYwMDAiLCAiMzIwMDAiKSkKIyBsZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgcmVzdWx0aW5nIGRhdGEuZnJhbWUKc2l6ZS5kZgpgYGAKCmBgYHtyfQpzaXplLmhlYXRtYXAgPC0gCiAgQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAoYXMubWF0cml4KHNpemUuZGYpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfcm93cyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfY29sdW1ucyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgIHJvd19uYW1lc19zaWRlID0gImxlZnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl9uYW1lc19zaWRlID0gInRvcCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sID0gY29sb3JSYW1wUGFsZXR0ZShjKCJ3aGl0ZSIsICJibHVlMyIpKSg2KSwKICAgICAgICAgICAgICAgICAgICAgICAgICByZWN0X2dwID0gZ3JpZDo6Z3Bhcihjb2wgPSAiYmxhY2siKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBzaG93X2hlYXRtYXBfbGVnZW5kID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIm51bWJlciBvZiBtb2RlbHMiKQpzaXplLmhlYXRtYXAKYGBgCgojIyBCaW9sb2dpY2FsIGNvbnRleHQKCmBgYHtyfQpuYW1lcyhjb250ZXh0Lm1vZGVsLmZpbGVzKSA8LSAKICBzdHJpbmdyOjpzdHJfbWF0Y2goY29udGV4dC5tb2RlbC5maWxlcywgInJlY291bnQyXyguKj8pX2FjY2Vzc2lvbnMiKVssIDJdCiMgZGV0ZWN0IGZvciBwYXRod2F5IHNlcGFyYXRpb24KY29udGV4dC5yZXN1bHRzLmRmIDwtIEdldEFsbFBhaXJzRGF0YUZyYW1lKGNvbnRleHQubW9kZWwuZmlsZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBpZC5uYW1lID0gImJpb2xvZ2ljYWxfY29udGV4dCIpCmBgYAoKV3JhbmdsZSBkYXRhIGZvciBoZWF0bWFwCgpgYGB7cn0KY29udGV4dC5kZiA8LSBXcmFuZ2xlRm9ySGVhdG1hcChyZXN1bHRzLmRmID0gY29udGV4dC5yZXN1bHRzLmRmLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdyb3VwLmNvbG5hbWUgPSAiYmlvbG9naWNhbF9jb250ZXh0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cC5vcmRlciA9IGMoImJsb29kIiwgImNhbmNlciIsICJ0aXNzdWUiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImNlbGxfbGluZSIsICJvdGhlcl90aXNzdWVzIikpCmBgYAoKYGBge3J9CmNvbnRleHQuaGVhdG1hcCA8LSAKICBDb21wbGV4SGVhdG1hcDo6SGVhdG1hcChhcy5tYXRyaXgoY29udGV4dC5kZiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9jb2x1bW5zID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgcm93X25hbWVzX3NpZGUgPSAibGVmdCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sdW1uX25hbWVzX3NpZGUgPSAidG9wIiwKICAgICAgICAgICAgICAgICAgICAgICAgICBjb2wgPSBjb2xvclJhbXBQYWxldHRlKGMoIndoaXRlIiwgImJsdWUzIikpKDYpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHJlY3RfZ3AgPSBncmlkOjpncGFyKGNvbCA9ICJibGFjayIpLAogICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfaGVhdG1hcF9sZWdlbmQgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIm51bWJlciBvZiBtb2RlbHMiLAogICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfcm93X25hbWVzID0gRkFMU0UpCmBgYAoKIyMgTXVsdGlQTElFUgoKTm93IHJlcGVhdCB0aGlzIHdpdGggdGhlIGZ1bGwgbW9kZWwuCgpgYGB7cn0KIyByZWFkIGluIHRoZSBtb2RlbAptdWx0aXBsaWVyLm1vZGVsIDwtIHJlYWRSRFMocmVjb3VudDIubW9kZWwuZmlsZSkKIyBjaGVjayBhbGwgcGFpcnMgZm9yIHNlcGFyYXRpb24KbXVsdGlwbGllci5yZXN1bHRzIDwtIEFsbFBhaXJzKHBsaWVyLm1vZGVsID0gbXVsdGlwbGllci5tb2RlbCkKIyB0aGVyZSdzIG9ubHkgb25lIG1vZGVsIC0tIHNvIHRoaXMgaXMgYSBiaW5hcnkgb3V0Y29tZSEKbXVsdGlwbGllci5kZiA8LSByZXNoYXBlMjo6bWVsdChtdWx0aXBsaWVyLnJlc3VsdHMpICU+JSAgIyBtZWx0IHRoZSByZXN1bHRzCiAgIyB0aGUgdmFyaWFibGUgbmFtZSBpcyBwYXRod2F5CiAgZHBseXI6Om11dGF0ZShwYXRod2F5ID0gTDEsCiAgICAgICAgICAgICAgICBNdWx0aVBMSUVSID0gYXMuaW50ZWdlcih2YWx1ZSkpICU+JQogIGRwbHlyOjpzZWxlY3QoYygicGF0aHdheSIsICJNdWx0aVBMSUVSIikpICU+JQogIHRpYmJsZTo6Y29sdW1uX3RvX3Jvd25hbWVzKCJwYXRod2F5IikgJT4lCiAgYXMuZGF0YS5mcmFtZSgpCmBgYAoKSGVhdG1hcCBpdHNlbGYKCmBgYHtyfQptdWx0aXBsaWVyLmhlYXRtYXAgPC0gCiAgQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAoYXMubWF0cml4KG11bHRpcGxpZXIuZGYpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfcm93cyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJfY29sdW1ucyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl9uYW1lc19zaWRlID0gInRvcCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgY29sID0gImJsdWU0IiwKICAgICAgICAgICAgICAgICAgICAgICAgICByZWN0X2dwID0gZ3JpZDo6Z3Bhcihjb2wgPSAiYmxhY2siKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gInBhdGh3YXkgc2VwYXJhdGlvbiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvd19oZWF0bWFwX2xlZ2VuZCA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvd19yb3dfbmFtZXMgPSBGQUxTRSkKYGBgCgojIyBGaW5hbCBwbG90dGluZwoKYGBge3J9CmhlYXRtYXAubGlzdCA8LSBzaXplLmhlYXRtYXAgKyBjb250ZXh0LmhlYXRtYXAgKyBtdWx0aXBsaWVyLmhlYXRtYXAgCnBkZihmaWxlLnBhdGgocGxvdC5kaXIsICJtdWx0aXBsaWVyX3NlcGFyYXRpb24ucGRmIikpCkNvbXBsZXhIZWF0bWFwOjpkcmF3KGhlYXRtYXAubGlzdCkKZGV2Lm9mZigpCmBgYAoKCg==