J. Taroni 2018

Our goal is to map LVs between different PLIER models based on the gene loadings (Z matrix). The rationale is that LVs with the most similar gene loadings map to each other. In reality, a clear one-to-one mapping may not exist. Here, we’re looking the NARES PLIER model and the recount2 PLIER model.

We’d like to examine whether or not this transfer learning approach (e.g., using the recount2 PLIER model with the NARES data) is likely to give us very different LV (B matrix) values. If we get very different values, that should give us pause about using the transfer learning approach (e.g., multi-PLIER). We’re particularly concerned with LVs we consider to be “interpretable”, i.e., they have some non-random association with a pathway. For instance, do the trends we see in the “Neutrophil LV” from the NARES model hold true in the “Neutrophil” LV from the recount2 model?

Functions and directory setup

`%>%` <- dplyr::`%>%`
source(file.path("util", "plier_util.R"))
# plot and result directory setup for this notebook
plot.dir <- file.path("plots", "13")
dir.create(plot.dir, recursive = TRUE, showWarnings = FALSE)
results.dir <- file.path("results", "13")
dir.create(results.dir, recursive = TRUE, showWarnings = FALSE)

Load PLIER models, Z matrices

NARES PLIER

# Load NARES PLIER model
nares.plier.file <- file.path("results", "12", "NARES_PLIER_model.RDS")
nares.plier <- readRDS(nares.plier.file)
nares.summary <- nares.plier$summary

recount2 PLIER

# recount2 model
# load recount2 PLIER model, rename
recount.plier <- readRDS(file.path("data", "recount2_PLIER_data", 
                                   "recount_PLIER_model.RDS"))

Z matrices

# get Z matrices from both models
recount.z <- as.data.frame(recount.plier$Z)
nares.z <- as.data.frame(nares.plier$Z)
# we'll need to add the gene identifiers (symbols in this case) to a column
# rather than as rownames -- this will facilitate joining
recount.z <- tibble::rownames_to_column(recount.z, var = "Gene")
colnames(recount.z)[2:ncol(recount.z)] <- paste0("recountLV", 
                                                 1:(ncol(recount.z) - 1))
nares.z <- tibble::rownames_to_column(nares.z, var = "Gene")
colnames(nares.z)[2:ncol(nares.z)] <- paste0("naresLV", 
                                                 1:(ncol(nares.z) - 1))
# join -- only genes present in both models
z.df <- dplyr::inner_join(recount.z, nares.z, by = "Gene")
# need matrix to calculate correlation
z.matrix <- as.matrix(z.df[, 2:ncol(z.df)])
rownames(z.matrix) <- z.df$Gene
head(z.df)

Calculate correlation between the Z matrices

# calculate pearson correlation between LVs -- can we map between models using
# this distance metric?
cor.z.mat <- cor(z.matrix, method = "pearson")
# set diagonal to 0 to help find max correlation between LVs
diag(cor.z.mat) <- 0
# indices for each model
nares.indx <- grep("nares", rownames(cor.z.mat))
recount.indx <- grep("recount", rownames(cor.z.mat))
# pertinent indices
impt.cor.z.mat <- cor.z.mat[recount.indx, nares.indx] 
# for each NARES model LV, want the highest correlated LV from recount
mapping.df <- reshape2::melt(impt.cor.z.mat, 
                              varnames = c("recount_LV", "NARES_LV"),
                              value.name = "pearson_Z") %>%
                dplyr::group_by(NARES_LV) %>%
                dplyr::top_n(1, pearson_Z)
mapping.df

NARES data in recount2 latent space

We’ll need to calculate this to compare B matrices.

# get NARES expression matrix
exprs.file <- file.path("data", "expression_data", 
                        "NARES_SCANfast_ComBat_with_GeneSymbol.pcl")
exprs.df <- data.table::fread(exprs.file, data.table = FALSE)
exprs.mat <- dplyr::select(exprs.df, -(EntrezID:GeneSymbol))
rownames(exprs.mat) <- exprs.df$GeneSymbol
rm(exprs.df)
recount.nares.b <- GetNewDataB(exprs.mat = as.matrix(exprs.mat),
                               plier.model = recount.plier)
Loading required package: PLIER
Loading required package: RColorBrewer
Loading required package: gplots

Attaching package: ‘gplots’

The following object is masked from ‘package:stats’:

    lowess

Loading required package: pheatmap
Loading required package: glmnet
Loading required package: Matrix
Loading required package: foreach
Loaded glmnet 2.0-13

Loading required package: knitr
Loading required package: rsvd
Loading required package: qvalue
# save to file
recount.b.file <- file.path(results.dir, "NARES_recount2_B.RDS")
saveRDS(recount.nares.b, file = recount.b.file)

Compare B matrices

# get the NARES B matrices from both models, relabel the latent variables to
# indicate which model they are from
nares.b <- nares.plier$B
rownames(nares.b) <- paste0("naresLV", 1:nrow(nares.b))
rownames(recount.nares.b) <- paste0("recountLV", 1:nrow(recount.nares.b))
# correlation
cor.b.mat <- cor(t(recount.nares.b), t(nares.b))
# melt
cor.b.df <- reshape2::melt(cor.b.mat, varnames = c("recount_LV", "NARES_LV"),
                           value.name = "pearson_B")
mapping.df <- dplyr::inner_join(mapping.df, cor.b.df, 
                                by = c("recount_LV" = "recount_LV", 
                                       "NARES_LV" = "NARES_LV"))
mapping.df

Plotting results

First, let’s identify which of the NARES LVs are significantly associated with pathways. We’ll want to highlight these in our plots, as we expect that these are most likely to be “preserved” between models because they are related to the input gene sets (which were constant between models).

sig.summary <- nares.summary %>%
                dplyr::filter(FDR < 0.05)
sig.summary
sig.lvs <- as.integer(unique(sig.summary$`LV index`))

Heatmap of correlation values

# annotation bar indicating significant association with a pathway
row.color.bar <- rep("#FFFFFF", nrow(mapping.df))
row.color.bar[sig.lvs] <- "#0000FF"
cor.map.mat <- as.matrix(mapping.df[, c("pearson_Z", "pearson_B")])
rownames(cor.map.mat) <- mapping.df$NARES_LV
gplots::heatmap.2(cor.map.mat,
                  Rowv = NA, Colv = NA, trace = "none", dendrogram = "none",
                  col = colorRampPalette(colors = c("#FFFFFF", "#FF0000")),
                  cellnote = round(x = cor.map.mat, 2), notecol = "#000000",
                  notecex = 0.5, margins = c(5, 9),
                  cexCol = 0.75, cexRow = 0.7,
                  RowSideColors = row.color.bar, key = FALSE,
                  labRow = sub("nares", "NARES ", rownames(cor.map.mat)),
                  labCol = c("Z", "B"),
                  srtCol = 45)
legend("topleft", 
       legend = c("significant"),
       col = c("#0000FF"), cex = 0.75,
       lty = 1, lwd = 6)

# save plot to file
pdf(file.path(plot.dir, "NARES_Z_B_correlation_heatmap.pdf"), width = 5,
    height = 7)
gplots::heatmap.2(cor.map.mat,
                  Rowv = NA, Colv = NA, trace = "none", dendrogram = "none",
                  col = colorRampPalette(colors = c("#FFFFFF", "#FF0000")),
                  cellnote = round(x = cor.map.mat, 2), notecol = "#000000",
                  notecex = 0.5, margins = c(5, 9),
                  cexCol = 0.75, cexRow = 0.7,
                  RowSideColors = row.color.bar, key = FALSE,
                  labRow = sub("nares", "NARES ", rownames(cor.map.mat)),
                  labCol = c("Z", "B"))
legend("topleft", 
       legend = c("significant"),
       col = c("#0000FF"), cex = 0.75,
       lty = 1, lwd = 6)
dev.off()
null device 
          1 

Density plots

Let’s look at the entire distribution of cor.b.mat. It’s possible that the correlation of B between the “best match” (as determined by Z matrix correlation) LVs is not particularly notable.

# add LV type information
cor.b.df$LV_type <- rep("all", nrow(cor.b.df))
# add best match information
best.match.df <- dplyr::select(mapping.df, -pearson_Z) %>%
                  dplyr::mutate(LV_type = "best match")
# we'll add points for the significant lvs in blue
best.match.df$Significance <- NA
best.match.df$Significance[sig.lvs] <- "Significant"
b.cor.df <- dplyr::bind_rows(cor.b.df, best.match.df) 
b.cor.df %>%
  ggplot2::ggplot(ggplot2::aes(x = pearson_B,
                               group = LV_type,
                               fill = LV_type)) +
  ggplot2::geom_density(alpha = 0.3) +
  ggplot2::scale_fill_manual(values = c("#FFFFFF", "#5E5E5E")) +
  ggplot2::theme_bw() +
  ggplot2::geom_point(ggplot2::aes(y = 0, colour = Significance),
                      shape = 15, size = 3, alpha = 0.7) +
  ggplot2::labs(title = "Latent Space Agreement",
                subtitle = "NARES-MultiPLIER correlation",
                x = "Correlation") +
  ggplot2::theme(text = ggplot2::element_text(size = 15),
                 plot.title = ggplot2::element_text(hjust = 0.5, 
                                                    face = "bold"),
                 plot.subtitle = ggplot2::element_text(hjust = 0.5)) +
  ggplot2::scale_colour_manual(breaks = "Significant", values = c("#0000FF")) +
  ggplot2::guides(fill = ggplot2::guide_legend(override.aes = list(shape = NA),
                                               title = "LV Type"))

plot.file <- file.path(plot.dir, "NARES_B_correlation_density.pdf")
ggplot2::ggsave(plot.file, plot = ggplot2::last_plot())
Saving 7 x 7 in image

Summary

Some ambiguity in mapping latent variables across models suggests that the transfer learning using a model trained on gene expression data from diverse biological conditions (e.g., recount2 data) could be valuable for meta-analyses where curating a large enough compendium specific to one’s biological domain of interest is infeasible (e.g., in ANCA-associated vasculitis).

This analysis suggests that LVs that can be mapped across models (e.g., “best match”) have reasonably correlated sample values, much more so than random pairs of LVs. Pathway-associated LVs are particularly well-correlated.

The neutrophil (NARES LV3) and ECM (NARES LV14) LVs results from the NARES model suggests that these are among the strongest signals in this expression data (and this makes a lot of biological sense given the study at hand). These LVs had correlation values > 0.9 with their best matches in the recount2 PLIER model.

NARES LV2 values have no association with their best match in the recount2 model. The gene sets significantly associated with NARES LV2 are macrophage, dendritic cell, and mast cell gene sets. This is a broad swath of the myeloid lineage, and may suggest that the lack of specificity contributes to the poor correlation.

LS0tCnRpdGxlOiAiTkFSRVMgdi4gcmVjb3VudDIgTFYgY29tcGFyc2lvbiIKb3V0cHV0OiAgIAogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKLS0tCgoqKkouIFRhcm9uaSAyMDE4KioKCk91ciBnb2FsIGlzIHRvIG1hcCBMVnMgYmV0d2VlbiBkaWZmZXJlbnQgUExJRVIgbW9kZWxzIGJhc2VkIG9uIHRoZSBnZW5lIGxvYWRpbmdzCihaIG1hdHJpeCkuIApUaGUgcmF0aW9uYWxlIGlzIHRoYXQgTFZzIHdpdGggdGhlIF9tb3N0IHNpbWlsYXJfIGdlbmUgbG9hZGluZ3MgbWFwIHRvIGVhY2ggCm90aGVyLgpJbiByZWFsaXR5LCBhIGNsZWFyIG9uZS10by1vbmUgbWFwcGluZyBtYXkgbm90IGV4aXN0LgpIZXJlLCB3ZSdyZSBsb29raW5nIHRoZSBOQVJFUyBQTElFUiBtb2RlbCBhbmQgdGhlIHJlY291bnQyIFBMSUVSIG1vZGVsLgoKV2UnZCBsaWtlIHRvIGV4YW1pbmUgd2hldGhlciBvciBub3QgdGhpcyB0cmFuc2ZlciBsZWFybmluZyBhcHByb2FjaCAoZS5nLiwgCnVzaW5nIHRoZSByZWNvdW50MiBQTElFUiBtb2RlbCB3aXRoIHRoZSBOQVJFUyBkYXRhKSBpcyBsaWtlbHkgdG8gZ2l2ZSB1cyB2ZXJ5CmRpZmZlcmVudCBMViAoQiBtYXRyaXgpIHZhbHVlcy4gCklmIHdlIGdldCB2ZXJ5IGRpZmZlcmVudCB2YWx1ZXMsIHRoYXQgc2hvdWxkIGdpdmUgdXMgcGF1c2UgYWJvdXQgdXNpbmcgdGhlCnRyYW5zZmVyIGxlYXJuaW5nIGFwcHJvYWNoIChlLmcuLCBtdWx0aS1QTElFUikuIApXZSdyZSBwYXJ0aWN1bGFybHkgY29uY2VybmVkIHdpdGggTFZzIHdlIGNvbnNpZGVyIHRvIGJlICJpbnRlcnByZXRhYmxlIiwgaS5lLiwKdGhleSBoYXZlIHNvbWUgbm9uLXJhbmRvbSBhc3NvY2lhdGlvbiB3aXRoIGEgcGF0aHdheS4KRm9yIGluc3RhbmNlLCBkbyB0aGUgdHJlbmRzIHdlIHNlZSBpbiB0aGUgIk5ldXRyb3BoaWwgTFYiIGZyb20gdGhlIE5BUkVTIG1vZGVsIApob2xkIHRydWUgaW4gdGhlICJOZXV0cm9waGlsIiBMViBmcm9tIHRoZSByZWNvdW50MiBtb2RlbD8KCiMjIEZ1bmN0aW9ucyBhbmQgZGlyZWN0b3J5IHNldHVwCgpgYGB7cn0KYCU+JWAgPC0gZHBseXI6OmAlPiVgCnNvdXJjZShmaWxlLnBhdGgoInV0aWwiLCAicGxpZXJfdXRpbC5SIikpCmBgYApgYGB7cn0KIyBwbG90IGFuZCByZXN1bHQgZGlyZWN0b3J5IHNldHVwIGZvciB0aGlzIG5vdGVib29rCnBsb3QuZGlyIDwtIGZpbGUucGF0aCgicGxvdHMiLCAiMTMiKQpkaXIuY3JlYXRlKHBsb3QuZGlyLCByZWN1cnNpdmUgPSBUUlVFLCBzaG93V2FybmluZ3MgPSBGQUxTRSkKcmVzdWx0cy5kaXIgPC0gZmlsZS5wYXRoKCJyZXN1bHRzIiwgIjEzIikKZGlyLmNyZWF0ZShyZXN1bHRzLmRpciwgcmVjdXJzaXZlID0gVFJVRSwgc2hvd1dhcm5pbmdzID0gRkFMU0UpCmBgYAoKIyMgTG9hZCBQTElFUiBtb2RlbHMsIGBaYCBtYXRyaWNlcwoKIyMjIE5BUkVTIFBMSUVSCgpgYGB7cn0KIyBMb2FkIE5BUkVTIFBMSUVSIG1vZGVsCm5hcmVzLnBsaWVyLmZpbGUgPC0gZmlsZS5wYXRoKCJyZXN1bHRzIiwgIjEyIiwgIk5BUkVTX1BMSUVSX21vZGVsLlJEUyIpCm5hcmVzLnBsaWVyIDwtIHJlYWRSRFMobmFyZXMucGxpZXIuZmlsZSkKbmFyZXMuc3VtbWFyeSA8LSBuYXJlcy5wbGllciRzdW1tYXJ5CmBgYAoKIyMjIHJlY291bnQyIFBMSUVSCgpgYGB7cn0KIyByZWNvdW50MiBtb2RlbAojIGxvYWQgcmVjb3VudDIgUExJRVIgbW9kZWwsIHJlbmFtZQpyZWNvdW50LnBsaWVyIDwtIHJlYWRSRFMoZmlsZS5wYXRoKCJkYXRhIiwgInJlY291bnQyX1BMSUVSX2RhdGEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAicmVjb3VudF9QTElFUl9tb2RlbC5SRFMiKSkKYGBgCgojIyMgYFpgIG1hdHJpY2VzCgpgYGB7cn0KIyBnZXQgWiBtYXRyaWNlcyBmcm9tIGJvdGggbW9kZWxzCnJlY291bnQueiA8LSBhcy5kYXRhLmZyYW1lKHJlY291bnQucGxpZXIkWikKbmFyZXMueiA8LSBhcy5kYXRhLmZyYW1lKG5hcmVzLnBsaWVyJFopCgojIHdlJ2xsIG5lZWQgdG8gYWRkIHRoZSBnZW5lIGlkZW50aWZpZXJzIChzeW1ib2xzIGluIHRoaXMgY2FzZSkgdG8gYSBjb2x1bW4KIyByYXRoZXIgdGhhbiBhcyByb3duYW1lcyAtLSB0aGlzIHdpbGwgZmFjaWxpdGF0ZSBqb2luaW5nCnJlY291bnQueiA8LSB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbihyZWNvdW50LnosIHZhciA9ICJHZW5lIikKY29sbmFtZXMocmVjb3VudC56KVsyOm5jb2wocmVjb3VudC56KV0gPC0gcGFzdGUwKCJyZWNvdW50TFYiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDE6KG5jb2wocmVjb3VudC56KSAtIDEpKQpuYXJlcy56IDwtIHRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKG5hcmVzLnosIHZhciA9ICJHZW5lIikKY29sbmFtZXMobmFyZXMueilbMjpuY29sKG5hcmVzLnopXSA8LSBwYXN0ZTAoIm5hcmVzTFYiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDE6KG5jb2wobmFyZXMueikgLSAxKSkKCiMgam9pbiAtLSBvbmx5IGdlbmVzIHByZXNlbnQgaW4gYm90aCBtb2RlbHMKei5kZiA8LSBkcGx5cjo6aW5uZXJfam9pbihyZWNvdW50LnosIG5hcmVzLnosIGJ5ID0gIkdlbmUiKQoKIyBuZWVkIG1hdHJpeCB0byBjYWxjdWxhdGUgY29ycmVsYXRpb24Kei5tYXRyaXggPC0gYXMubWF0cml4KHouZGZbLCAyOm5jb2woei5kZildKQpyb3duYW1lcyh6Lm1hdHJpeCkgPC0gei5kZiRHZW5lCmBgYAoKYGBge3J9CmhlYWQoei5kZikKYGBgCgojIyBDYWxjdWxhdGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgYFpgIG1hdHJpY2VzCgpgYGB7cn0KIyBjYWxjdWxhdGUgcGVhcnNvbiBjb3JyZWxhdGlvbiBiZXR3ZWVuIExWcyAtLSBjYW4gd2UgbWFwIGJldHdlZW4gbW9kZWxzIHVzaW5nCiMgdGhpcyBkaXN0YW5jZSBtZXRyaWM/CmNvci56Lm1hdCA8LSBjb3Ioei5tYXRyaXgsIG1ldGhvZCA9ICJwZWFyc29uIikKCiMgc2V0IGRpYWdvbmFsIHRvIDAgdG8gaGVscCBmaW5kIG1heCBjb3JyZWxhdGlvbiBiZXR3ZWVuIExWcwpkaWFnKGNvci56Lm1hdCkgPC0gMAoKIyBpbmRpY2VzIGZvciBlYWNoIG1vZGVsCm5hcmVzLmluZHggPC0gZ3JlcCgibmFyZXMiLCByb3duYW1lcyhjb3Iuei5tYXQpKQpyZWNvdW50LmluZHggPC0gZ3JlcCgicmVjb3VudCIsIHJvd25hbWVzKGNvci56Lm1hdCkpCgojIHBlcnRpbmVudCBpbmRpY2VzCmltcHQuY29yLnoubWF0IDwtIGNvci56Lm1hdFtyZWNvdW50LmluZHgsIG5hcmVzLmluZHhdIAoKIyBmb3IgZWFjaCBOQVJFUyBtb2RlbCBMViwgd2FudCB0aGUgaGlnaGVzdCBjb3JyZWxhdGVkIExWIGZyb20gcmVjb3VudAptYXBwaW5nLmRmIDwtIHJlc2hhcGUyOjptZWx0KGltcHQuY29yLnoubWF0LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFybmFtZXMgPSBjKCJyZWNvdW50X0xWIiwgIk5BUkVTX0xWIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlLm5hbWUgPSAicGVhcnNvbl9aIikgJT4lCiAgICAgICAgICAgICAgICBkcGx5cjo6Z3JvdXBfYnkoTkFSRVNfTFYpICU+JQogICAgICAgICAgICAgICAgZHBseXI6OnRvcF9uKDEsIHBlYXJzb25fWikKCm1hcHBpbmcuZGYKYGBgCgojIyBOQVJFUyBkYXRhIGluIHJlY291bnQyIGxhdGVudCBzcGFjZQoKV2UnbGwgbmVlZCB0byBjYWxjdWxhdGUgdGhpcyB0byBjb21wYXJlIGBCYCBtYXRyaWNlcy4KCmBgYHtyfQojIGdldCBOQVJFUyBleHByZXNzaW9uIG1hdHJpeApleHBycy5maWxlIDwtIGZpbGUucGF0aCgiZGF0YSIsICJleHByZXNzaW9uX2RhdGEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgIk5BUkVTX1NDQU5mYXN0X0NvbUJhdF93aXRoX0dlbmVTeW1ib2wucGNsIikKZXhwcnMuZGYgPC0gZGF0YS50YWJsZTo6ZnJlYWQoZXhwcnMuZmlsZSwgZGF0YS50YWJsZSA9IEZBTFNFKQpleHBycy5tYXQgPC0gZHBseXI6OnNlbGVjdChleHBycy5kZiwgLShFbnRyZXpJRDpHZW5lU3ltYm9sKSkKcm93bmFtZXMoZXhwcnMubWF0KSA8LSBleHBycy5kZiRHZW5lU3ltYm9sCnJtKGV4cHJzLmRmKQpgYGAKCmBgYHtyfQpyZWNvdW50Lm5hcmVzLmIgPC0gR2V0TmV3RGF0YUIoZXhwcnMubWF0ID0gYXMubWF0cml4KGV4cHJzLm1hdCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwbGllci5tb2RlbCA9IHJlY291bnQucGxpZXIpCmBgYAoKYGBge3J9CiMgc2F2ZSB0byBmaWxlCnJlY291bnQuYi5maWxlIDwtIGZpbGUucGF0aChyZXN1bHRzLmRpciwgIk5BUkVTX3JlY291bnQyX0IuUkRTIikKc2F2ZVJEUyhyZWNvdW50Lm5hcmVzLmIsIGZpbGUgPSByZWNvdW50LmIuZmlsZSkKYGBgCgojIyBDb21wYXJlIGBCYCBtYXRyaWNlcwoKYGBge3J9CiMgZ2V0IHRoZSBOQVJFUyBCIG1hdHJpY2VzIGZyb20gYm90aCBtb2RlbHMsIHJlbGFiZWwgdGhlIGxhdGVudCB2YXJpYWJsZXMgdG8KIyBpbmRpY2F0ZSB3aGljaCBtb2RlbCB0aGV5IGFyZSBmcm9tCm5hcmVzLmIgPC0gbmFyZXMucGxpZXIkQgpyb3duYW1lcyhuYXJlcy5iKSA8LSBwYXN0ZTAoIm5hcmVzTFYiLCAxOm5yb3cobmFyZXMuYikpCnJvd25hbWVzKHJlY291bnQubmFyZXMuYikgPC0gcGFzdGUwKCJyZWNvdW50TFYiLCAxOm5yb3cocmVjb3VudC5uYXJlcy5iKSkKCiMgY29ycmVsYXRpb24KY29yLmIubWF0IDwtIGNvcih0KHJlY291bnQubmFyZXMuYiksIHQobmFyZXMuYikpCgojIG1lbHQKY29yLmIuZGYgPC0gcmVzaGFwZTI6Om1lbHQoY29yLmIubWF0LCB2YXJuYW1lcyA9IGMoInJlY291bnRfTFYiLCAiTkFSRVNfTFYiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFsdWUubmFtZSA9ICJwZWFyc29uX0IiKQoKbWFwcGluZy5kZiA8LSBkcGx5cjo6aW5uZXJfam9pbihtYXBwaW5nLmRmLCBjb3IuYi5kZiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnkgPSBjKCJyZWNvdW50X0xWIiA9ICJyZWNvdW50X0xWIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJOQVJFU19MViIgPSAiTkFSRVNfTFYiKSkKbWFwcGluZy5kZgpgYGAKCiMjIFBsb3R0aW5nIHJlc3VsdHMKCkZpcnN0LCBsZXQncyBpZGVudGlmeSB3aGljaCBvZiB0aGUgTkFSRVMgTFZzIGFyZSBzaWduaWZpY2FudGx5IGFzc29jaWF0ZWQgd2l0aApwYXRod2F5cy4gCldlJ2xsIHdhbnQgdG8gaGlnaGxpZ2h0IHRoZXNlIGluIG91ciBwbG90cywgYXMgd2UgZXhwZWN0IHRoYXQgdGhlc2UgYXJlCm1vc3QgbGlrZWx5IHRvIGJlICJwcmVzZXJ2ZWQiIGJldHdlZW4gbW9kZWxzIGJlY2F1c2UgdGhleSBhcmUgcmVsYXRlZCB0bwp0aGUgaW5wdXQgZ2VuZSBzZXRzICh3aGljaCB3ZXJlIGNvbnN0YW50IGJldHdlZW4gbW9kZWxzKS4KCmBgYHtyfQpzaWcuc3VtbWFyeSA8LSBuYXJlcy5zdW1tYXJ5ICU+JQogICAgICAgICAgICAgICAgZHBseXI6OmZpbHRlcihGRFIgPCAwLjA1KQpzaWcuc3VtbWFyeQpgYGAKCmBgYHtyfQpzaWcubHZzIDwtIGFzLmludGVnZXIodW5pcXVlKHNpZy5zdW1tYXJ5JGBMViBpbmRleGApKQpgYGAKCiMjIyBIZWF0bWFwIG9mIGNvcnJlbGF0aW9uIHZhbHVlcwoKYGBge3J9CiMgYW5ub3RhdGlvbiBiYXIgaW5kaWNhdGluZyBzaWduaWZpY2FudCBhc3NvY2lhdGlvbiB3aXRoIGEgcGF0aHdheQpyb3cuY29sb3IuYmFyIDwtIHJlcCgiI0ZGRkZGRiIsIG5yb3cobWFwcGluZy5kZikpCnJvdy5jb2xvci5iYXJbc2lnLmx2c10gPC0gIiMwMDAwRkYiCmBgYAoKYGBge3J9CmNvci5tYXAubWF0IDwtIGFzLm1hdHJpeChtYXBwaW5nLmRmWywgYygicGVhcnNvbl9aIiwgInBlYXJzb25fQiIpXSkKcm93bmFtZXMoY29yLm1hcC5tYXQpIDwtIG1hcHBpbmcuZGYkTkFSRVNfTFYKZ3Bsb3RzOjpoZWF0bWFwLjIoY29yLm1hcC5tYXQsCiAgICAgICAgICAgICAgICAgIFJvd3YgPSBOQSwgQ29sdiA9IE5BLCB0cmFjZSA9ICJub25lIiwgZGVuZHJvZ3JhbSA9ICJub25lIiwKICAgICAgICAgICAgICAgICAgY29sID0gY29sb3JSYW1wUGFsZXR0ZShjb2xvcnMgPSBjKCIjRkZGRkZGIiwgIiNGRjAwMDAiKSksCiAgICAgICAgICAgICAgICAgIGNlbGxub3RlID0gcm91bmQoeCA9IGNvci5tYXAubWF0LCAyKSwgbm90ZWNvbCA9ICIjMDAwMDAwIiwKICAgICAgICAgICAgICAgICAgbm90ZWNleCA9IDAuNSwgbWFyZ2lucyA9IGMoNSwgOSksCiAgICAgICAgICAgICAgICAgIGNleENvbCA9IDAuNzUsIGNleFJvdyA9IDAuNywKICAgICAgICAgICAgICAgICAgUm93U2lkZUNvbG9ycyA9IHJvdy5jb2xvci5iYXIsIGtleSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICBsYWJSb3cgPSBzdWIoIm5hcmVzIiwgIk5BUkVTICIsIHJvd25hbWVzKGNvci5tYXAubWF0KSksCiAgICAgICAgICAgICAgICAgIGxhYkNvbCA9IGMoIloiLCAiQiIpLAogICAgICAgICAgICAgICAgICBzcnRDb2wgPSA0NSkKbGVnZW5kKCJ0b3BsZWZ0IiwgCiAgICAgICBsZWdlbmQgPSBjKCJzaWduaWZpY2FudCIpLAogICAgICAgY29sID0gYygiIzAwMDBGRiIpLCBjZXggPSAwLjc1LAogICAgICAgbHR5ID0gMSwgbHdkID0gNikKYGBgCgpgYGB7cn0KIyBzYXZlIHBsb3QgdG8gZmlsZQpwZGYoZmlsZS5wYXRoKHBsb3QuZGlyLCAiTkFSRVNfWl9CX2NvcnJlbGF0aW9uX2hlYXRtYXAucGRmIiksIHdpZHRoID0gNSwKICAgIGhlaWdodCA9IDcpCmdwbG90czo6aGVhdG1hcC4yKGNvci5tYXAubWF0LAogICAgICAgICAgICAgICAgICBSb3d2ID0gTkEsIENvbHYgPSBOQSwgdHJhY2UgPSAibm9uZSIsIGRlbmRyb2dyYW0gPSAibm9uZSIsCiAgICAgICAgICAgICAgICAgIGNvbCA9IGNvbG9yUmFtcFBhbGV0dGUoY29sb3JzID0gYygiI0ZGRkZGRiIsICIjRkYwMDAwIikpLAogICAgICAgICAgICAgICAgICBjZWxsbm90ZSA9IHJvdW5kKHggPSBjb3IubWFwLm1hdCwgMiksIG5vdGVjb2wgPSAiIzAwMDAwMCIsCiAgICAgICAgICAgICAgICAgIG5vdGVjZXggPSAwLjUsIG1hcmdpbnMgPSBjKDUsIDkpLAogICAgICAgICAgICAgICAgICBjZXhDb2wgPSAwLjc1LCBjZXhSb3cgPSAwLjcsCiAgICAgICAgICAgICAgICAgIFJvd1NpZGVDb2xvcnMgPSByb3cuY29sb3IuYmFyLCBrZXkgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgbGFiUm93ID0gc3ViKCJuYXJlcyIsICJOQVJFUyAiLCByb3duYW1lcyhjb3IubWFwLm1hdCkpLAogICAgICAgICAgICAgICAgICBsYWJDb2wgPSBjKCJaIiwgIkIiKSkKbGVnZW5kKCJ0b3BsZWZ0IiwgCiAgICAgICBsZWdlbmQgPSBjKCJzaWduaWZpY2FudCIpLAogICAgICAgY29sID0gYygiIzAwMDBGRiIpLCBjZXggPSAwLjc1LAogICAgICAgbHR5ID0gMSwgbHdkID0gNikKZGV2Lm9mZigpCmBgYAoKIyMjIERlbnNpdHkgcGxvdHMKCkxldCdzIGxvb2sgYXQgdGhlIGVudGlyZSBkaXN0cmlidXRpb24gb2YgYGNvci5iLm1hdGAuIApJdCdzIHBvc3NpYmxlIHRoYXQgdGhlIGNvcnJlbGF0aW9uIG9mIGBCYCBiZXR3ZWVuIHRoZSAiYmVzdCBtYXRjaCIgKGFzIApkZXRlcm1pbmVkIGJ5IGBaYCBtYXRyaXggY29ycmVsYXRpb24pIExWcyBpcyBub3QgcGFydGljdWxhcmx5IG5vdGFibGUuCgpgYGB7cn0KIyBhZGQgTFYgdHlwZSBpbmZvcm1hdGlvbgpjb3IuYi5kZiRMVl90eXBlIDwtIHJlcCgiYWxsIiwgbnJvdyhjb3IuYi5kZikpCmBgYAoKYGBge3J9CiMgYWRkIGJlc3QgbWF0Y2ggaW5mb3JtYXRpb24KYmVzdC5tYXRjaC5kZiA8LSBkcGx5cjo6c2VsZWN0KG1hcHBpbmcuZGYsIC1wZWFyc29uX1opICU+JQogICAgICAgICAgICAgICAgICBkcGx5cjo6bXV0YXRlKExWX3R5cGUgPSAiYmVzdCBtYXRjaCIpCgojIHdlJ2xsIGFkZCBwb2ludHMgZm9yIHRoZSBzaWduaWZpY2FudCBsdnMgaW4gYmx1ZQpiZXN0Lm1hdGNoLmRmJFNpZ25pZmljYW5jZSA8LSBOQQpiZXN0Lm1hdGNoLmRmJFNpZ25pZmljYW5jZVtzaWcubHZzXSA8LSAiU2lnbmlmaWNhbnQiCgpiLmNvci5kZiA8LSBkcGx5cjo6YmluZF9yb3dzKGNvci5iLmRmLCBiZXN0Lm1hdGNoLmRmKSAKYGBgCgpgYGB7cn0KYi5jb3IuZGYgJT4lCiAgZ2dwbG90Mjo6Z2dwbG90KGdncGxvdDI6OmFlcyh4ID0gcGVhcnNvbl9CLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBMVl90eXBlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmlsbCA9IExWX3R5cGUpKSArCiAgZ2dwbG90Mjo6Z2VvbV9kZW5zaXR5KGFscGhhID0gMC4zKSArCiAgZ2dwbG90Mjo6c2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI0ZGRkZGRiIsICIjNUU1RTVFIikpICsKICBnZ3Bsb3QyOjp0aGVtZV9idygpICsKICBnZ3Bsb3QyOjpnZW9tX3BvaW50KGdncGxvdDI6OmFlcyh5ID0gMCwgY29sb3VyID0gU2lnbmlmaWNhbmNlKSwKICAgICAgICAgICAgICAgICAgICAgIHNoYXBlID0gMTUsIHNpemUgPSAzLCBhbHBoYSA9IDAuNykgKwogIGdncGxvdDI6OmxhYnModGl0bGUgPSAiTGF0ZW50IFNwYWNlIEFncmVlbWVudCIsCiAgICAgICAgICAgICAgICBzdWJ0aXRsZSA9ICJOQVJFUy1NdWx0aVBMSUVSIGNvcnJlbGF0aW9uIiwKICAgICAgICAgICAgICAgIHggPSAiQ29ycmVsYXRpb24iKSArCiAgZ2dwbG90Mjo6dGhlbWUodGV4dCA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChzaXplID0gMTUpLAogICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoaGp1c3QgPSAwLjUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFjZSA9ICJib2xkIiksCiAgICAgICAgICAgICAgICAgcGxvdC5zdWJ0aXRsZSA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpICsKICBnZ3Bsb3QyOjpzY2FsZV9jb2xvdXJfbWFudWFsKGJyZWFrcyA9ICJTaWduaWZpY2FudCIsIHZhbHVlcyA9IGMoIiMwMDAwRkYiKSkgKwogIGdncGxvdDI6Omd1aWRlcyhmaWxsID0gZ2dwbG90Mjo6Z3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2hhcGUgPSBOQSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAiTFYgVHlwZSIpKQpgYGAKCmBgYHtyfQpwbG90LmZpbGUgPC0gZmlsZS5wYXRoKHBsb3QuZGlyLCAiTkFSRVNfQl9jb3JyZWxhdGlvbl9kZW5zaXR5LnBkZiIpCmdncGxvdDI6Omdnc2F2ZShwbG90LmZpbGUsIHBsb3QgPSBnZ3Bsb3QyOjpsYXN0X3Bsb3QoKSkKYGBgCgojIyBTdW1tYXJ5CgpTb21lIGFtYmlndWl0eSBpbiBtYXBwaW5nIGxhdGVudCB2YXJpYWJsZXMgYWNyb3NzIG1vZGVscyBzdWdnZXN0cyB0aGF0IHRoZQp0cmFuc2ZlciBsZWFybmluZyB1c2luZyBhIG1vZGVsIHRyYWluZWQgb24gZ2VuZSBleHByZXNzaW9uIGRhdGEgZnJvbSBkaXZlcnNlCmJpb2xvZ2ljYWwgY29uZGl0aW9ucyAoZS5nLiwgcmVjb3VudDIgZGF0YSkKY291bGQgYmUgdmFsdWFibGUgZm9yIG1ldGEtYW5hbHlzZXMgd2hlcmUgY3VyYXRpbmcgYSBsYXJnZSBlbm91Z2ggY29tcGVuZGl1bSAKc3BlY2lmaWMgdG8gb25lJ3MgYmlvbG9naWNhbCBkb21haW4gb2YgaW50ZXJlc3QgaXMgaW5mZWFzaWJsZQooZS5nLiwgaW4gQU5DQS1hc3NvY2lhdGVkIHZhc2N1bGl0aXMpLgoKVGhpcyBhbmFseXNpcyBzdWdnZXN0cyB0aGF0IExWcyB0aGF0IGNhbiBiZSBtYXBwZWQgYWNyb3NzIG1vZGVscyAoZS5nLiwgCiJiZXN0IG1hdGNoIikgaGF2ZSByZWFzb25hYmx5IGNvcnJlbGF0ZWQgc2FtcGxlIHZhbHVlcywgbXVjaCBtb3JlIHNvIHRoYW4gCnJhbmRvbSBwYWlycyBvZiBMVnMuIApQYXRod2F5LWFzc29jaWF0ZWQgTFZzIGFyZSBwYXJ0aWN1bGFybHkgd2VsbC1jb3JyZWxhdGVkLgoKVGhlIG5ldXRyb3BoaWwgKGBOQVJFUyBMVjNgKSBhbmQgRUNNIChgTkFSRVMgTFYxNGApIExWcyByZXN1bHRzIGZyb20gdGhlIE5BUkVTCm1vZGVsIHN1Z2dlc3RzIHRoYXQgdGhlc2UgYXJlIGFtb25nIHRoZSBzdHJvbmdlc3Qgc2lnbmFscyBpbiB0aGlzIGV4cHJlc3Npb24gCmRhdGEgKGFuZCB0aGlzIG1ha2VzIGEgbG90IG9mIGJpb2xvZ2ljYWwgc2Vuc2UgZ2l2ZW4gdGhlIHN0dWR5IGF0IGhhbmQpLiAKVGhlc2UgTFZzIGhhZCBjb3JyZWxhdGlvbiB2YWx1ZXMgPiBgMC45YCB3aXRoIHRoZWlyIGJlc3QgbWF0Y2hlcyBpbiB0aGUKcmVjb3VudDIgUExJRVIgbW9kZWwuCgpgTkFSRVMgTFYyYCB2YWx1ZXMgaGF2ZSBubyBhc3NvY2lhdGlvbiB3aXRoIHRoZWlyIGJlc3QgbWF0Y2ggaW4gdGhlIHJlY291bnQyIAptb2RlbC4gClRoZSBnZW5lIHNldHMgc2lnbmlmaWNhbnRseSBhc3NvY2lhdGVkIHdpdGggYE5BUkVTIExWMmAgYXJlIG1hY3JvcGhhZ2UsCmRlbmRyaXRpYyBjZWxsLCBhbmQgbWFzdCBjZWxsIGdlbmUgc2V0cy4gClRoaXMgaXMgYSBicm9hZCBzd2F0aCBvZiB0aGUgbXllbG9pZCBsaW5lYWdlLCBhbmQgbWF5IHN1Z2dlc3QgdGhhdCB0aGUgbGFjayBvZgpzcGVjaWZpY2l0eSBjb250cmlidXRlcyB0byB0aGUgcG9vciBjb3JyZWxhdGlvbi4KCg==