Data loading and preprocessing
Loading the MOFA model
First we load all the count data sets that we will be using.
## TODO: Load from RDS file
mofa_path <- here("saved_data", "mofa", glue_data(params, "mofa-model_{genome}_{transcriptome}_{mofa_views}.RDS"))
mofa <- readRDS(mofa_path)
Loading the TFBS overlap sets
We load the TFBS overlap data for all promoters. For RNA-seq genes, we say that a gene has a TFBS if any of its promoters overlap one.
overlap_sets <- readRDS(here("saved_data", glue_data(params, "promoter-tfbs-overlap_{genome}_{transcriptome}.RDS")))
overlap_sets$RNA <- bplapply(names(overlap_sets[[1]]), function(tf) {
lapply(overlap_sets, . %>% .[[tf]] %>% str_replace("-P[0-9]+$", "")) %>%
unlist %>% sort %>% unique
}) %>% set_names(names(overlap_sets[[1]]))
Filtering TFBS overlap sets
We must discard any mention of promoters or genes that are not included in the mofa object.
overlap_matrices <- lapply(names(overlap_sets), function(view) {
mofa_features <- rownames(experiments(mofa@InputData)[[view]])
lapply(overlap_sets[[view]], `%in%`, x=mofa_features) %>%
do.call(what=rbind) %>% add(0) %>%
set_colnames(mofa_features)
}) %>% setNames(names(overlap_sets))
Analysis
Basic MOFA model QC
We start by plotting the fraction of variance explained by each factor in each view.
plotVarianceExplained(mofa)

The plot shows that factors 1, 5, and 6 are shared across all 4 views, while all the others are specific to just 1 or 2 views.
Next, we plot the values of each latent factor for each sample, distinguishing time point and cell type by color and shape of the points, so we can see which factors correlate with the experimental effects of interest.
factor_table <- getFactors(mofa, as.data.frame = TRUE, include_intercept = FALSE) %>%
mutate(sample=as.character(sample)) %>%
inner_join(as.data.frame(colData(mofa@InputData)), by=c(sample="PrimarySample")) %>%
arrange(cell_type, days_after_activation) %>%
mutate(time_point = fct_inorder(time_point))
p <- ggplot(factor_table) +
facet_wrap(~factor, scales="free") +
aes(x=0, y=value, color=time_point, shape=cell_type) +
geom_quasirandom() +
scale_x_continuous(breaks=NULL) +
labs(title="Latent factor distributions",
x = "", y = "Factor value") +
theme_bw()
ggprint(p)

This plot shows that factors 1 and 5 are associated with time point, while factor 6 is clearly associated with cell type. Other factors have weaker associations, but these are the main factors of interest. Factor 2 is clearly the RNA batch effect.
Based on the above, we identify factors 1, 5, and 6 as the most biologically interesting factors. Happily, these are the same factors that explain a non-zero fraction of variance in every view. To further characterize these factors, we plot them against each other, resulting in a series of PCA-like plots.
factors_of_interest <- c(1,5,6)
plotFactorScatters(mofa, factors=factors_of_interest, color_by="time_point", shape_by = "cell_type")

These plots closely match the sva-corrected (or ComBat-corrected) MDS plots from the individual data sets, which is a good sign, since no explicit batch correction has been performed on this data.
Feature set enrichment analysis for TFBS overlaps
We are now ready to conduct feature set enrichment tests for the factors of interest.
fsea_results <- names(overlap_sets) %>% set_names %>% lapply(function(view) {
message("")
message(glue("Testing {view} for feature set enrichment"))
FeatureSetEnrichmentAnalysis(mofa, view, overlap_matrices[[view]])
})
Testing H3K4me2 for feature set enrichment
Testing H3K4me3 for feature set enrichment
Testing H3K27me3 for feature set enrichment
Testing RNA for feature set enrichment
sigCounts <- lapply(names(fsea_results), function(view) {
fsea_results[[view]] %$%
tibble(Test = "Enrichment",
View = view,
Factor = colnames(pval.adj),
SigSets = colSums(pval.adj <= 0.05))
}) %>% do.call(what=rbind) %>%
mutate_if(is.character, fct_inorder)
p <- ggplot(sigCounts) +
aes(x=Factor, y=SigSets) +
geom_bar(stat="identity") +
facet_wrap(~View)
ggprint(p)

Batch correction of RNA-seq data
The RNA-seq data in this data set is known to have a severe batch effect that is confounded with the time points, making standard batch subtraction problematic. We can attempt to do better by subtracting out the predicted effect of latent factor 2, which corresponds to this batch effect.
rnaseq_data <- list(None=getTrainData(mofa)$RNA)
sample.table <- colData(mofa@InputData)
sample.table$rna_batch <- getFactors(mofa, "2") %>% as.vector %>% is_less_than(0) %>% ifelse(., "A", "B")
design.NoTime <- model.matrix(~ cell_type + donor_id, sample.table)
rnaseq_data$BatchSubtract <- removeBatchEffect(rnaseq_data$None, batch=sample.table$rna_batch, design=design.NoTime)
rnaseq_data$ComBat <- ComBat(rnaseq_data$None, batch=sample.table$rna_batch, mod=design.NoTime, par.prior=TRUE)
Found2batches
Adjusting for4covariate(s) or covariate level(s)
Standardizing Data across genes
Fitting L/S model and finding priors
Finding parametric adjustments
Adjusting the Data
rnaseq_data$MOFASubtract <- rnaseq_data$None - predict(mofa, views="RNA", factors="2")$RNA
rnaseq_data$MOFAPredict <- predict(mofa, view="RNA", factors=factors_of_interest)$RNA
doMDS <- function(x, k) {
dmat <- suppressPlot(plotMDS(x, top=5000)$distance.matrix) %>% as.dist
if (missing(k)) {
k <- attr(dmat, "Size") - 1
}
mds <- cmdscale(dmat, k=k, eig=TRUE)
mds$points %>% add.numbered.colnames("Dim") %>% cbind(sample.table, .)
}
mds <- lapply(rnaseq_data, doMDS, k=10)
ggmdsbatch <- function(dat, dims=1:2) {
if (length(dims) == 1) {
dims <- dims + c(0,1)
}
assert_that(length(dims) == 2)
ggplot(as.data.frame(dat)) +
aes_string(x=str_c("Dim", dims[1]), y=str_c("Dim", dims[2])) +
aes(color=rna_batch, label=PrimarySample) +
geom_text() +
scale_x_continuous(expand=c(0.15, 0)) +
coord_equal()
}
p <- list()
for (i in names(mds)) {
p[[i]] <- ggmdsbatch(mds[[i]]) +
ggtitle("MDS plot for RNA-seq data",
subtitle = glue("Batch correction method: {i}"))
}
ggprint(p)





It seems that batch correction using MOFA gives a very similar result to the ordinary batch subtraction method, with no obvious improvement. However, since MOFA is implicitly aware of the time point variable (via the 1st and 5th latent factors) when performing this batch correction, while the ordinary batch correction ignores the time point variable, this similarity indicates that ignoring the time point has not had a major effect on the batch correction.
LS0tCnRpdGxlOiAiTU9GQSBhbmFseXNpcyBvZiBSTkEtc2VxIGFuZCBwcm9tb3RlciBoaXN0b25lIENoSVAtc2VxIGRhdGEiCmF1dGhvcjogIlJ5YW4gQy4gVGhvbXBzb24iCmRhdGU6ICdgciBnc3ViKCJcXHMrIiwgIiAiLCBmb3JtYXQoU3lzLnRpbWUoKSwgIiVCICVlLCAlWSIpKWAnCm91dHB1dDoKICAgIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQKICAgIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKcGFyYW1zOgogICAgZ2Vub21lOgogICAgICAgIHZhbHVlOiBoZzM4LmFuYWx5c2lzU2V0CiAgICB0cmFuc2NyaXB0b21lOgogICAgICAgIHZhbHVlOiBlbnNlbWJsLjg1CiAgICBtb2ZhX3ZpZXdzOiAicm5hK3Byb21vdGVyIgotLS0KCiMgUHJlbGltaW5hcnkgU2V0dXAKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFLCBjYWNoZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCByZXRpbmE9MiwgY2FjaGU9VFJVRSwgYXV0b2RlcD1UUlVFLAogICAgICAgICAgICAgICAgICAgICAgY2FjaGUuZXh0cmEgPSBsaXN0KHBhcmFtcz1wYXJhbXMpLAogICAgICAgICAgICAgICAgICAgICAgIyBodHRwczovL2dpdGh1Yi5jb20veWlodWkva25pdHIvaXNzdWVzLzU3MgogICAgICAgICAgICAgICAgICAgICAgY2FjaGUubGF6eT1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTgsCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZS5wYXRoID0gcGFzdGUwKAogICAgICAgICAgICAgICAgICAgICAgICAgIGhlcmU6OmhlcmUoImNhY2hlIiwgInByb21vdGVyLW1vZmEtYW5hbHl6ZSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgIC5QbGF0Zm9ybSRmaWxlLnNlcCkpCmBgYAoKRmlyc3Qgd2UgbG9hZCB0aGUgbmVjZXNzYXJ5IGxpYnJhcmllcywgYWxvbmcgd2l0aCBhIHNldCBvZiB1dGlsaXR5IGZ1bmN0aW9ucy4KCmBgYHtyIGxvYWRfcGFja2FnZXMsIG1lc3NhZ2U9RkFMU0UsIGNhY2hlPUZBTFNFfQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkoZ2x1ZSkKbGlicmFyeShmb3JjYXRzKQpsaWJyYXJ5KG1hZ3JpdHRyKQpsaWJyYXJ5KG9wZW54bHN4KQpsaWJyYXJ5KFN1bW1hcml6ZWRFeHBlcmltZW50KQpsaWJyYXJ5KE11bHRpQXNzYXlFeHBlcmltZW50KQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGVkZ2VSKQpsaWJyYXJ5KGxpbW1hKQpsaWJyYXJ5KERFU2VxMikKbGlicmFyeShjc2F3KQpsaWJyYXJ5KHN2YSkKbGlicmFyeShNT0ZBdG9vbHMpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KGdnYWx0KQpsaWJyYXJ5KGdndGhlbWVzKQpsaWJyYXJ5KGdnYmVlc3dhcm0pCmxpYnJhcnkoc3BsaW5lcykKbGlicmFyeShyZXNoYXBlMikKbGlicmFyeShhc3NlcnR0aGF0KQpsaWJyYXJ5KGdnZm9ydGlmeSkKbGlicmFyeShicm9vbSkKbGlicmFyeShrcykKbGlicmFyeShSQ29sb3JCcmV3ZXIpCmxpYnJhcnkoaGVyZSkKCmxpYnJhcnkoQlNnZW5vbWUuSHNhcGllbnMuVUNTQy5oZzM4KQoKbGlicmFyeShkb1BhcmFsbGVsKQpuY29yZXMgPC0gZ2V0T3B0aW9uKCJtYy5jb3JlcyIsIGRlZmF1bHQ9cGFyYWxsZWw6OmRldGVjdENvcmVzKGxvZ2ljYWwgPSBGQUxTRSkpCm9wdGlvbnMobWMuY29yZXM9bmNvcmVzKQpyZWdpc3RlckRvUGFyYWxsZWwoY29yZXM9bmNvcmVzKQpsaWJyYXJ5KEJpb2NQYXJhbGxlbCkKcmVnaXN0ZXIoU2VyaWFsUGFyYW0oKSkKcmVnaXN0ZXIoRG9wYXJQYXJhbSgpKQoKb3B0aW9ucyhmdXR1cmUuZ2xvYmFscy5tYXhTaXplPTQgKiAxMDI0XjMpCmxpYnJhcnkoZnV0dXJlKQpwbGFuKG11bHRpY29yZSkKCnNvdXJjZShoZXJlKCJzY3JpcHRzL3V0aWxpdGllcy5SIikpCgojIFJlcXVpcmVkIGluIG9yZGVyIHRvIHVzZSBER0VMaXN0IG9iamVjdHMgd2l0aCBmdXR1cmUKbGVuZ3RoLkRHRUxpc3QgPC0gZnVuY3Rpb24oeCkgewogICAgbGVuZ3RoKHVuY2xhc3MoeCkpCn0KYGBgCgojIERhdGEgbG9hZGluZyBhbmQgcHJlcHJvY2Vzc2luZwoKIyMgTG9hZGluZyB0aGUgTU9GQSBtb2RlbAoKRmlyc3Qgd2UgbG9hZCBhbGwgdGhlIGNvdW50IGRhdGEgc2V0cyB0aGF0IHdlIHdpbGwgYmUgdXNpbmcuCgpgYGB7ciBsb2FkX21vZmF9CiMjIFRPRE86IExvYWQgZnJvbSBSRFMgZmlsZQptb2ZhX3BhdGggPC0gaGVyZSgic2F2ZWRfZGF0YSIsICJtb2ZhIiwgZ2x1ZV9kYXRhKHBhcmFtcywgIm1vZmEtbW9kZWxfe2dlbm9tZX1fe3RyYW5zY3JpcHRvbWV9X3ttb2ZhX3ZpZXdzfS5SRFMiKSkKbW9mYSA8LSByZWFkUkRTKG1vZmFfcGF0aCkKYGBgCgojIyBMb2FkaW5nIHRoZSBURkJTIG92ZXJsYXAgc2V0cwoKV2UgbG9hZCB0aGUgVEZCUyBvdmVybGFwIGRhdGEgZm9yIGFsbCBwcm9tb3RlcnMuIEZvciBSTkEtc2VxIGdlbmVzLCB3ZSBzYXkgdGhhdCBhIGdlbmUgaGFzIGEgVEZCUyBpZiBhbnkgb2YgaXRzIHByb21vdGVycyBvdmVybGFwIG9uZS4KCmBgYHtyIGxvYWRfdGZic19vdmVybGFwc30Kb3ZlcmxhcF9zZXRzIDwtIHJlYWRSRFMoaGVyZSgic2F2ZWRfZGF0YSIsIGdsdWVfZGF0YShwYXJhbXMsICJwcm9tb3Rlci10ZmJzLW92ZXJsYXBfe2dlbm9tZX1fe3RyYW5zY3JpcHRvbWV9LlJEUyIpKSkKb3ZlcmxhcF9zZXRzJFJOQSA8LSBicGxhcHBseShuYW1lcyhvdmVybGFwX3NldHNbWzFdXSksIGZ1bmN0aW9uKHRmKSB7CiAgICBsYXBwbHkob3ZlcmxhcF9zZXRzLCAuICU+JSAuW1t0Zl1dICU+JSBzdHJfcmVwbGFjZSgiLVBbMC05XSskIiwgIiIpKSAlPiUKICAgICAgICB1bmxpc3QgJT4lIHNvcnQgJT4lIHVuaXF1ZQp9KSAlPiUgc2V0X25hbWVzKG5hbWVzKG92ZXJsYXBfc2V0c1tbMV1dKSkKYGBgCgojIyBGaWx0ZXJpbmcgVEZCUyBvdmVybGFwIHNldHMKCldlIG11c3QgZGlzY2FyZCBhbnkgbWVudGlvbiBvZiBwcm9tb3RlcnMgb3IgZ2VuZXMgdGhhdCBhcmUgbm90IGluY2x1ZGVkIGluIHRoZSBtb2ZhIG9iamVjdC4KCmBgYHtyIGdlbmVyYXRlX292ZXJsYXBfbWF0cmljZXN9Cm92ZXJsYXBfbWF0cmljZXMgPC0gbGFwcGx5KG5hbWVzKG92ZXJsYXBfc2V0cyksIGZ1bmN0aW9uKHZpZXcpIHsKICAgIG1vZmFfZmVhdHVyZXMgPC0gcm93bmFtZXMoZXhwZXJpbWVudHMobW9mYUBJbnB1dERhdGEpW1t2aWV3XV0pCiAgICBsYXBwbHkob3ZlcmxhcF9zZXRzW1t2aWV3XV0sIGAlaW4lYCwgeD1tb2ZhX2ZlYXR1cmVzKSAlPiUKICAgICAgICBkby5jYWxsKHdoYXQ9cmJpbmQpICU+JSBhZGQoMCkgJT4lCiAgICAgICAgc2V0X2NvbG5hbWVzKG1vZmFfZmVhdHVyZXMpCn0pICU+JSBzZXROYW1lcyhuYW1lcyhvdmVybGFwX3NldHMpKQpgYGAKCiMgQW5hbHlzaXMKCiMjIEJhc2ljIE1PRkEgbW9kZWwgUUMKCldlIHN0YXJ0IGJ5IHBsb3R0aW5nIHRoZSBmcmFjdGlvbiBvZiB2YXJpYW5jZSBleHBsYWluZWQgYnkgZWFjaCBmYWN0b3IgaW4gZWFjaCB2aWV3LgoKYGBge3IgcGxvdF9yMiwgd2FybmluZyA9IEZBTFNFfQpwbG90VmFyaWFuY2VFeHBsYWluZWQobW9mYSkKYGBgCgpUaGUgcGxvdCBzaG93cyB0aGF0IGZhY3RvcnMgMSwgNSwgYW5kIDYgYXJlIHNoYXJlZCBhY3Jvc3MgYWxsIDQgdmlld3MsIHdoaWxlIGFsbCB0aGUgb3RoZXJzIGFyZSBzcGVjaWZpYyB0byBqdXN0IDEgb3IgMiB2aWV3cy4KCk5leHQsIHdlIHBsb3QgdGhlIHZhbHVlcyBvZiBlYWNoIGxhdGVudCBmYWN0b3IgZm9yIGVhY2ggc2FtcGxlLCBkaXN0aW5ndWlzaGluZyB0aW1lIHBvaW50IGFuZCBjZWxsIHR5cGUgYnkgY29sb3IgYW5kIHNoYXBlIG9mIHRoZSBwb2ludHMsIHNvIHdlIGNhbiBzZWUgd2hpY2ggZmFjdG9ycyBjb3JyZWxhdGUgd2l0aCB0aGUgZXhwZXJpbWVudGFsIGVmZmVjdHMgb2YgaW50ZXJlc3QuCgpgYGB7ciBiZWVfc3dhcm1fcGxvdHMsIHdhcm5pbmc9RkFMU0V9CmZhY3Rvcl90YWJsZSA8LSBnZXRGYWN0b3JzKG1vZmEsIGFzLmRhdGEuZnJhbWUgPSBUUlVFLCBpbmNsdWRlX2ludGVyY2VwdCA9IEZBTFNFKSAgJT4lCiAgICBtdXRhdGUoc2FtcGxlPWFzLmNoYXJhY3RlcihzYW1wbGUpKSAlPiUKICAgIGlubmVyX2pvaW4oYXMuZGF0YS5mcmFtZShjb2xEYXRhKG1vZmFASW5wdXREYXRhKSksIGJ5PWMoc2FtcGxlPSJQcmltYXJ5U2FtcGxlIikpICU+JQogICAgYXJyYW5nZShjZWxsX3R5cGUsIGRheXNfYWZ0ZXJfYWN0aXZhdGlvbikgJT4lCiAgICBtdXRhdGUodGltZV9wb2ludCA9IGZjdF9pbm9yZGVyKHRpbWVfcG9pbnQpKQpwIDwtIGdncGxvdChmYWN0b3JfdGFibGUpICsKICAgIGZhY2V0X3dyYXAofmZhY3Rvciwgc2NhbGVzPSJmcmVlIikgKwogICAgYWVzKHg9MCwgeT12YWx1ZSwgY29sb3I9dGltZV9wb2ludCwgc2hhcGU9Y2VsbF90eXBlKSArCiAgICBnZW9tX3F1YXNpcmFuZG9tKCkgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz1OVUxMKSArCiAgICBsYWJzKHRpdGxlPSJMYXRlbnQgZmFjdG9yIGRpc3RyaWJ1dGlvbnMiLAogICAgICAgICB4ID0gIiIsIHkgPSAiRmFjdG9yIHZhbHVlIikgKwogICAgdGhlbWVfYncoKQpnZ3ByaW50KHApCmBgYAoKVGhpcyBwbG90IHNob3dzIHRoYXQgZmFjdG9ycyAxIGFuZCA1IGFyZSBhc3NvY2lhdGVkIHdpdGggdGltZSBwb2ludCwgd2hpbGUgZmFjdG9yIDYgaXMgY2xlYXJseSBhc3NvY2lhdGVkIHdpdGggY2VsbCB0eXBlLiBPdGhlciBmYWN0b3JzIGhhdmUgd2Vha2VyIGFzc29jaWF0aW9ucywgYnV0IHRoZXNlIGFyZSB0aGUgbWFpbiBmYWN0b3JzIG9mIGludGVyZXN0LiBGYWN0b3IgMiBpcyBjbGVhcmx5IHRoZSBSTkEgYmF0Y2ggZWZmZWN0LgoKQmFzZWQgb24gdGhlIGFib3ZlLCB3ZSBpZGVudGlmeSBmYWN0b3JzIDEsIDUsIGFuZCA2IGFzIHRoZSBtb3N0IGJpb2xvZ2ljYWxseSBpbnRlcmVzdGluZyBmYWN0b3JzLiBIYXBwaWx5LCB0aGVzZSBhcmUgdGhlIHNhbWUgZmFjdG9ycyB0aGF0IGV4cGxhaW4gYSBub24temVybyBmcmFjdGlvbiBvZiB2YXJpYW5jZSBpbiBldmVyeSB2aWV3LiBUbyBmdXJ0aGVyIGNoYXJhY3Rlcml6ZSB0aGVzZSBmYWN0b3JzLCB3ZSBwbG90IHRoZW0gYWdhaW5zdCBlYWNoIG90aGVyLCByZXN1bHRpbmcgaW4gYSBzZXJpZXMgb2YgUENBLWxpa2UgcGxvdHMuCgpgYGB7ciBmYWN0b3JfcGFpcnNfcGxvdCwgd2FybmluZz1GQUxTRX0KZmFjdG9yc19vZl9pbnRlcmVzdCA8LSBjKDEsNSw2KQpwbG90RmFjdG9yU2NhdHRlcnMobW9mYSwgZmFjdG9ycz1mYWN0b3JzX29mX2ludGVyZXN0LCBjb2xvcl9ieT0idGltZV9wb2ludCIsIHNoYXBlX2J5ID0gImNlbGxfdHlwZSIpCmBgYAoKVGhlc2UgcGxvdHMgY2xvc2VseSBtYXRjaCB0aGUgc3ZhLWNvcnJlY3RlZCAob3IgQ29tQmF0LWNvcnJlY3RlZCkgTURTIHBsb3RzIGZyb20gdGhlIGluZGl2aWR1YWwgZGF0YSBzZXRzLCB3aGljaCBpcyBhIGdvb2Qgc2lnbiwgc2luY2Ugbm8gZXhwbGljaXQgYmF0Y2ggY29ycmVjdGlvbiBoYXMgYmVlbiBwZXJmb3JtZWQgb24gdGhpcyBkYXRhLgoKIyMgRmVhdHVyZSBzZXQgZW5yaWNobWVudCBhbmFseXNpcyBmb3IgVEZCUyBvdmVybGFwcwoKV2UgYXJlIG5vdyByZWFkeSB0byBjb25kdWN0IGZlYXR1cmUgc2V0IGVucmljaG1lbnQgdGVzdHMgZm9yIHRoZSBmYWN0b3JzIG9mIGludGVyZXN0LgoKYGBge3IgZnNlYV9ydW59CmZzZWFfcmVzdWx0cyA8LSBuYW1lcyhvdmVybGFwX3NldHMpICU+JSBzZXRfbmFtZXMgJT4lIGxhcHBseShmdW5jdGlvbih2aWV3KSB7CiAgICBtZXNzYWdlKCIiKQogICAgbWVzc2FnZShnbHVlKCJUZXN0aW5nIHt2aWV3fSBmb3IgZmVhdHVyZSBzZXQgZW5yaWNobWVudCIpKQogICAgRmVhdHVyZVNldEVucmljaG1lbnRBbmFseXNpcyhtb2ZhLCB2aWV3LCBvdmVybGFwX21hdHJpY2VzW1t2aWV3XV0pCn0pCmBgYAoKYGBge3IgZnNlYV9wbG90fQpzaWdDb3VudHMgPC0gbGFwcGx5KG5hbWVzKGZzZWFfcmVzdWx0cyksIGZ1bmN0aW9uKHZpZXcpIHsKICAgIGZzZWFfcmVzdWx0c1tbdmlld11dICUkJQogICAgICAgIHRpYmJsZShUZXN0ID0gIkVucmljaG1lbnQiLAogICAgICAgICAgICAgICBWaWV3ID0gdmlldywKICAgICAgICAgICBGYWN0b3IgPSBjb2xuYW1lcyhwdmFsLmFkaiksCiAgICAgICAgICAgICAgIFNpZ1NldHMgPSBjb2xTdW1zKHB2YWwuYWRqIDw9IDAuMDUpKQp9KSAlPiUgZG8uY2FsbCh3aGF0PXJiaW5kKSAlPiUKICAgIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsIGZjdF9pbm9yZGVyKQpwIDwtIGdncGxvdChzaWdDb3VudHMpICsKICAgIGFlcyh4PUZhY3RvciwgeT1TaWdTZXRzKSArCiAgICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpICsKICAgIGZhY2V0X3dyYXAoflZpZXcpCmdncHJpbnQocCkKYGBgCgojIyBCYXRjaCBjb3JyZWN0aW9uIG9mIFJOQS1zZXEgZGF0YQoKVGhlIFJOQS1zZXEgZGF0YSBpbiB0aGlzIGRhdGEgc2V0IGlzIGtub3duIHRvIGhhdmUgYSBzZXZlcmUgYmF0Y2ggZWZmZWN0IHRoYXQgaXMgY29uZm91bmRlZCB3aXRoIHRoZSB0aW1lIHBvaW50cywgbWFraW5nIHN0YW5kYXJkIGJhdGNoIHN1YnRyYWN0aW9uIHByb2JsZW1hdGljLiBXZSBjYW4gYXR0ZW1wdCB0byBkbyBiZXR0ZXIgYnkgc3VidHJhY3Rpbmcgb3V0IHRoZSBwcmVkaWN0ZWQgZWZmZWN0IG9mIGxhdGVudCBmYWN0b3IgMiwgd2hpY2ggY29ycmVzcG9uZHMgdG8gdGhpcyBiYXRjaCBlZmZlY3QuCgpgYGB7ciBtb2ZhX2JhdGNoX2NvcnJlY3Rfcm5hc2VxfQpybmFzZXFfZGF0YSA8LSBsaXN0KE5vbmU9Z2V0VHJhaW5EYXRhKG1vZmEpJFJOQSkKc2FtcGxlLnRhYmxlIDwtIGNvbERhdGEobW9mYUBJbnB1dERhdGEpCnNhbXBsZS50YWJsZSRybmFfYmF0Y2ggPC0gZ2V0RmFjdG9ycyhtb2ZhLCAiMiIpICU+JSBhcy52ZWN0b3IgJT4lIGlzX2xlc3NfdGhhbigwKSAlPiUgaWZlbHNlKC4sICJBIiwgIkIiKQpkZXNpZ24uTm9UaW1lIDwtIG1vZGVsLm1hdHJpeCh+IGNlbGxfdHlwZSArIGRvbm9yX2lkLCBzYW1wbGUudGFibGUpCgpybmFzZXFfZGF0YSRCYXRjaFN1YnRyYWN0IDwtIHJlbW92ZUJhdGNoRWZmZWN0KHJuYXNlcV9kYXRhJE5vbmUsIGJhdGNoPXNhbXBsZS50YWJsZSRybmFfYmF0Y2gsIGRlc2lnbj1kZXNpZ24uTm9UaW1lKQpybmFzZXFfZGF0YSRDb21CYXQgPC0gQ29tQmF0KHJuYXNlcV9kYXRhJE5vbmUsIGJhdGNoPXNhbXBsZS50YWJsZSRybmFfYmF0Y2gsIG1vZD1kZXNpZ24uTm9UaW1lLCBwYXIucHJpb3I9VFJVRSkKcm5hc2VxX2RhdGEkTU9GQVN1YnRyYWN0IDwtIHJuYXNlcV9kYXRhJE5vbmUgLSBwcmVkaWN0KG1vZmEsIHZpZXdzPSJSTkEiLCBmYWN0b3JzPSIyIikkUk5BCnJuYXNlcV9kYXRhJE1PRkFQcmVkaWN0IDwtIHByZWRpY3QobW9mYSwgdmlldz0iUk5BIiwgZmFjdG9ycz1mYWN0b3JzX29mX2ludGVyZXN0KSRSTkEKCmRvTURTIDwtIGZ1bmN0aW9uKHgsIGspIHsKICAgIGRtYXQgPC0gc3VwcHJlc3NQbG90KHBsb3RNRFMoeCwgdG9wPTUwMDApJGRpc3RhbmNlLm1hdHJpeCkgJT4lIGFzLmRpc3QKICAgIGlmIChtaXNzaW5nKGspKSB7CiAgICAgICAgayA8LSBhdHRyKGRtYXQsICJTaXplIikgLSAxCiAgICB9CiAgICBtZHMgPC0gY21kc2NhbGUoZG1hdCwgaz1rLCBlaWc9VFJVRSkKICAgIG1kcyRwb2ludHMgJT4lIGFkZC5udW1iZXJlZC5jb2xuYW1lcygiRGltIikgJT4lIGNiaW5kKHNhbXBsZS50YWJsZSwgLikKfQptZHMgPC0gbGFwcGx5KHJuYXNlcV9kYXRhLCBkb01EUywgaz0xMCkKCmdnbWRzYmF0Y2ggPC0gZnVuY3Rpb24oZGF0LCBkaW1zPTE6MikgewogICAgaWYgKGxlbmd0aChkaW1zKSA9PSAxKSB7CiAgICAgICAgZGltcyA8LSBkaW1zICsgYygwLDEpCiAgICB9CiAgICBhc3NlcnRfdGhhdChsZW5ndGgoZGltcykgPT0gMikKICAgIGdncGxvdChhcy5kYXRhLmZyYW1lKGRhdCkpICsKICAgICAgICBhZXNfc3RyaW5nKHg9c3RyX2MoIkRpbSIsIGRpbXNbMV0pLCB5PXN0cl9jKCJEaW0iLCBkaW1zWzJdKSkgKwogICAgICAgIGFlcyhjb2xvcj1ybmFfYmF0Y2gsIGxhYmVsPVByaW1hcnlTYW1wbGUpICsKICAgICAgICBnZW9tX3RleHQoKSArCiAgICAgICAgc2NhbGVfeF9jb250aW51b3VzKGV4cGFuZD1jKDAuMTUsIDApKSArCiAgICAgICAgY29vcmRfZXF1YWwoKQp9CgpwIDwtIGxpc3QoKQpmb3IgKGkgaW4gbmFtZXMobWRzKSkgewogICAgcFtbaV1dIDwtIGdnbWRzYmF0Y2gobWRzW1tpXV0pICsKICAgICAgICBnZ3RpdGxlKCJNRFMgcGxvdCBmb3IgUk5BLXNlcSBkYXRhIiwKICAgICAgICAgICAgICAgIHN1YnRpdGxlID0gZ2x1ZSgiQmF0Y2ggY29ycmVjdGlvbiBtZXRob2Q6IHtpfSIpKQp9CmdncHJpbnQocCkKYGBgCgpJdCBzZWVtcyB0aGF0IGJhdGNoIGNvcnJlY3Rpb24gdXNpbmcgTU9GQSBnaXZlcyBhIHZlcnkgc2ltaWxhciByZXN1bHQgdG8gdGhlIG9yZGluYXJ5IGJhdGNoIHN1YnRyYWN0aW9uIG1ldGhvZCwgd2l0aCBubyBvYnZpb3VzIGltcHJvdmVtZW50LiBIb3dldmVyLCBzaW5jZSBNT0ZBIGlzIGltcGxpY2l0bHkgYXdhcmUgb2YgdGhlIHRpbWUgcG9pbnQgdmFyaWFibGUgKHZpYSB0aGUgMXN0IGFuZCA1dGggbGF0ZW50IGZhY3RvcnMpIHdoZW4gcGVyZm9ybWluZyB0aGlzIGJhdGNoIGNvcnJlY3Rpb24sIHdoaWxlIHRoZSBvcmRpbmFyeSBiYXRjaCBjb3JyZWN0aW9uIGlnbm9yZXMgdGhlIHRpbWUgcG9pbnQgdmFyaWFibGUsIHRoaXMgc2ltaWxhcml0eSBpbmRpY2F0ZXMgdGhhdCBpZ25vcmluZyB0aGUgdGltZSBwb2ludCBoYXMgbm90IGhhZCBhIG1ham9yIGVmZmVjdCBvbiB0aGUgYmF0Y2ggY29ycmVjdGlvbi4K