Functions
source_from_github(repositoy = "cNMF_functions",version = "0.4.11",script_name = "cnmf_functions_V3.R")
ℹ SHA-1 hash of file is 29c04ac248b96d9a0ee5b6f3b8745d3df770eb54
Loading required package: facefuns
Data
load("./Data/Bivona_scRNAseq/NI04_tumor_seurat_object.RData")
tiss_subset_tumor2 = UpdateSeuratObject(tiss_subset_tumor2)
Validating object structure
Updating object slots
wer
Error: object 'wer' not found
tiss_subset_tumor2
calculate nmf metagenes
from cnmf import cNMF
import pickle
f = open('./Data/cnmf/cnmf_objects/models_2Kvargenes_all_K_cnmf_obj.pckl', 'rb')
cnmf_obj = pickle.load(f)
f.close()
k = 5
density_threshold = 0.1
_, gep_scores, _, _ = cnmf_obj.load_results(K=k, density_threshold=density_threshold)
Calculate usage by
counts before Harmony
# get expression with genes in cnmf input
genes = rownames(py$gep_scores)
genes = genes [genes %in% rownames(tiss_subset_tumor2)]
bivona_expression = t(as.matrix(GetAssayData(tiss_subset_tumor2,slot='data')))
bivona_expression = 2**bivona_expression #convert from log2(tpm+1) to tpm
bivona_expression = bivona_expression-1
bivona_expression = bivona_expression[,genes] %>% as.data.frame()
all_0_genes = colnames(bivona_expression)[colSums(bivona_expression==0, na.rm=TRUE)==nrow(bivona_expression)] #delete rows that have all 0
genes = genes[!genes %in% all_0_genes]
bivona_expression = bivona_expression[,!colnames(bivona_expression) %in% all_0_genes]
gc(verbose = F)
reticulate::repl_python()
usage_by_calc = get_usage_from_score(counts=bivona_expression,tpm=bivona_expression,genes=genes,cnmf_obj=cnmf_obj,k=5,sumTo1=True)
/sci/labs/yotamd/lab_share/avishai.wizel/python_envs/miniconda/envs/cnmf_env_6/bin/python3.7:7: FutureWarning: X.dtype being converted to np.float32 from float64. In the next version of anndata (0.9) conversion will not be automatic. Pass dtype explicitly to avoid this warning. Pass `AnnData(X, dtype=X.dtype, ...)` to get the future behavour.
/sci/labs/yotamd/lab_share/avishai.wizel/python_envs/miniconda/envs/cnmf_env_6/bin/python3.7:8: FutureWarning: X.dtype being converted to np.float32 from float64. In the next version of anndata (0.9) conversion will not be automatic. Pass dtype explicitly to avoid this warning. Pass `AnnData(X, dtype=X.dtype, ...)` to get the future behavour.
bivona_5_metagenes = py$usage_by_calc
all_metagenes = bivona_5_metagenes
colnames(all_metagenes) = c("IFNa_nmf","immune_response_nmf", "hypoxia_nmf","cell_cycle_nmf","unknown")
programs
expression

metagenes_violin_compare.2 = function(dataset,prefix = "",pre_on = c("OSI","NT"),axis.text.x = 11,test = "t.test", programs = c("Hypoxia","TNFa","Cell_cycle"),return_list = F,combine_patients = F){
plt.lst = list()
if(combine_patients){
genes_by_tp = FetchData(object = dataset,vars = c("analysis",programs)) %>% filter(analysis %in% pre_on) %>% as.data.frame() #mean expression
formula <- as.formula( paste("c(", paste(programs, collapse = ","), ")~ analysis ") )
#plot and split by patient:
stat.test = compare_means(formula = formula ,data = genes_by_tp,method = test,p.adjust.method = "fdr")%>% # Add pairwise comparisons p-value
dplyr::filter(group1 == pre_on[1] & group2 == pre_on[2]) #filter for pre vs on analysis only
stat.test$p.format =stat.test$p.adj #modift 0 pvalue to be lowest possible float
stat.test$p.format[!stat.test$p.format == 0 ] <- paste("=",stat.test$p.format[!stat.test$p.format == 0 ])
stat.test$p.format[stat.test$p.format == 0 ] <- paste("<",.Machine$double.xmin %>% signif(digits = 3))
genes_by_tp = reshape2::melt(genes_by_tp, id.vars = c("analysis"),value.name = "score")
plt = ggplot(genes_by_tp, aes(x = variable, y = score,fill = analysis)) + geom_split_violin(scale = 'width')+
geom_boxplot(width = 0.25, notch = FALSE, notchwidth = .4, outlier.shape = NA, coef=0)+
ylim(min(genes_by_tp$score),max(genes_by_tp$score)*1.25)
plt = plt +stat_pvalue_manual(stat.test, label = "p {p.format}", #add p value
y.position = max(genes_by_tp$score)*1.08,inherit.aes = F,size = 3.3,x = ".y.") # set position at the top value
return(plt)
}
for (metegene in programs) {
#create data:
genes_by_tp = FetchData(object = dataset,vars = c("orig.ident","treatment",metegene)) %>% filter(treatment %in% pre_on) %>% as.data.frame() #mean expression
names(genes_by_tp)[3] = "Metagene_mean"
fm <- as.formula(paste("Metagene_mean", "~", "treatment")) #make formula to plot
#plot and split by patient:
stat.test = compare_means(formula = fm ,data = genes_by_tp,method = test,group.by = "orig.ident",p.adjust.method = "fdr")%>% # Add pairwise comparisons p-value
dplyr::filter(group1 == pre_on[1] & group2 == pre_on[2]) #filter for pre vs on treatment only
stat.test$p.format =stat.test$p.adj #modift 0 pvalue to be lowest possible float
stat.test$p.format[!stat.test$p.format == 0 ] <- paste("=",stat.test$p.format[!stat.test$p.format == 0 ])
stat.test$p.format[stat.test$p.format == 0 ] <- paste("<",.Machine$double.xmin %>% signif(digits = 3))
plt = ggplot(genes_by_tp, aes(x = orig.ident, y = Metagene_mean,fill = treatment)) + geom_split_violin(scale = 'width')+ylab(metegene)+
geom_boxplot(width = 0.25, notch = FALSE, notchwidth = .4, outlier.shape = NA, coef=0)+
ylim(min(genes_by_tp$Metagene_mean),max(genes_by_tp$Metagene_mean)*1.25)
plt = plt +stat_pvalue_manual(stat.test, label = "p {p.format}", #add p value
y.position = max(genes_by_tp$Metagene_mean)*1.08,x = "orig.ident",inherit.aes = F,size = 3.3) # set position at the top value
plt.lst[[metegene]] = plt
if (!return_list) {
print(plt)
}
}
if (return_list) {
return(plt.lst)
}
}
NMF programs
metagenes_violin_compare.2(dataset = tiss_subset_tumor2,prefix = "patient",pre_on = c("naive","grouped_pr"),test = "wilcox.test",programs = colnames(all_metagenes)[1:3], return_list = F,combine_patients = T)

genesets <- msigdb_download("Homo sapiens",category="H")
gene_list = list(IFNa_genes = genesets$HALLMARK_INTERFERON_ALPHA_RESPONSE, TNFa_genes = genesets$HALLMARK_TNFA_SIGNALING_VIA_NFKB, hif_targets = hif_targets,E2F_genes = genesets$HALLMARK_E2F_TARGETS)
for (i in seq_along(gene_list)) {
genes = gene_list[[i]]
genes = genes[genes %in% rownames(tiss_subset_tumor2)]
name = names(gene_list)[i]
scores = FetchData(object = tiss_subset_tumor2,vars = c(genes))
scores = scores %>% rowMeans() %>% as.data.frame()
tiss_subset_tumor2 %<>% AddMetaData(metadata = scores,col.name = name)
}
Signatures
metagenes_violin_compare.2(dataset = tiss_subset_tumor2,prefix = "patient",pre_on = c("naive","grouped_pr"),test = "wilcox.test",programs = names(gene_list), return_list = F,combine_patients = T)

LS0tCnRpdGxlOiAnYHIgcnN0dWRpb2FwaTo6Z2V0U291cmNlRWRpdG9yQ29udGV4dCgpJHBhdGggJT4lIGJhc2VuYW1lKCkgJT4lIGdzdWIocGF0dGVybiA9ICJcXC5SbWQiLHJlcGxhY2VtZW50ID0gIiIpYCcgCmF1dGhvcjogIkF2aXNoYWkgV2l6ZWwiCmRhdGU6ICdgciBTeXMudGltZSgpYCcKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdG9jOiB5ZXMKICAgIHRvY19jb2xsYXBzZTogeWVzCiAgICB0b2NfZmxvYXQ6IAogICAgICBjb2xsYXBzZWQ6IEZBTFNFCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvY19kZXB0aDogMQotLS0KCgoKIyBGdW5jdGlvbnMKCmBgYHtyIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoc3RyaW5naSkKbGlicmFyeShyZXRpY3VsYXRlKQpzb3VyY2VfZnJvbV9naXRodWIocmVwb3NpdG95ID0gIkRFR19mdW5jdGlvbnMiLHZlcnNpb24gPSAiMC4yLjUzIikKc291cmNlX2Zyb21fZ2l0aHViKHJlcG9zaXRveSA9ICJjTk1GX2Z1bmN0aW9ucyIsdmVyc2lvbiA9ICIwLjQuMTEiLHNjcmlwdF9uYW1lID0gImNubWZfZnVuY3Rpb25zX1YzLlIiKQpzb3VyY2VfZnJvbV9naXRodWIocmVwb3NpdG95ID0gInNjX2dlbmVyYWxfZnVuY3Rpb25zIix2ZXJzaW9uID0gIjAuMS4zNCIsc2NyaXB0X25hbWUgPSAiZnVuY3Rpb25zLlIiKQoKYGBgCgojIERhdGEKCmBgYHtyfQpsb2FkKCIuL0RhdGEvQml2b25hX3NjUk5Bc2VxL05JMDRfdHVtb3Jfc2V1cmF0X29iamVjdC5SRGF0YSIpCnRpc3Nfc3Vic2V0X3R1bW9yMiA9IFVwZGF0ZVNldXJhdE9iamVjdCh0aXNzX3N1YnNldF90dW1vcjIpCnRpc3Nfc3Vic2V0X3R1bW9yMiR0cmVhdG1lbnQgPSB0aXNzX3N1YnNldF90dW1vcjIkYW5hbHlzaXMKdGlzc19zdWJzZXRfdHVtb3IyQG1ldGEuZGF0YVtbImFuYWx5c2lzIl1dID0gZmFjdG9yKHRpc3Nfc3Vic2V0X3R1bW9yMiRhbmFseXNpcywgbGV2ZWxzID0gYygibmFpdmUiLCAiZ3JvdXBlZF9wciIsImdyb3VwZWRfcGQiKSkKYGBgCgpgYGB7cn0KdGlzc19zdWJzZXRfdHVtb3IyCmBgYAoKY2FsY3VsYXRlIG5tZiBtZXRhZ2VuZXMKYGBge3B5dGhvbn0KZnJvbSBjbm1mIGltcG9ydCBjTk1GCmltcG9ydCBwaWNrbGUKZiA9IG9wZW4oJy4vRGF0YS9jbm1mL2NubWZfb2JqZWN0cy9tb2RlbHNfMkt2YXJnZW5lc19hbGxfS19jbm1mX29iai5wY2tsJywgJ3JiJykKY25tZl9vYmogPSBwaWNrbGUubG9hZChmKQpmLmNsb3NlKCkKYGBgCgpgYGB7cHl0aG9ufQprID0gNQpkZW5zaXR5X3RocmVzaG9sZCA9IDAuMSAKXywgZ2VwX3Njb3JlcywgXywgXyA9IGNubWZfb2JqLmxvYWRfcmVzdWx0cyhLPWssIGRlbnNpdHlfdGhyZXNob2xkPWRlbnNpdHlfdGhyZXNob2xkKQpgYGAKCiMgQ2FsY3VsYXRlIHVzYWdlIGJ5IGNvdW50cyBiZWZvcmUgSGFybW9ueQpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2FzaXMnfQojIGdldCBleHByZXNzaW9uIHdpdGggZ2VuZXMgaW4gY25tZiBpbnB1dApnZW5lcyA9IHJvd25hbWVzKHB5JGdlcF9zY29yZXMpCmdlbmVzID0gZ2VuZXMgW2dlbmVzICVpbiUgcm93bmFtZXModGlzc19zdWJzZXRfdHVtb3IyKV0KYml2b25hX2V4cHJlc3Npb24gPSB0KGFzLm1hdHJpeChHZXRBc3NheURhdGEodGlzc19zdWJzZXRfdHVtb3IyLHNsb3Q9J2RhdGEnKSkpIApiaXZvbmFfZXhwcmVzc2lvbiA9IDIqKmJpdm9uYV9leHByZXNzaW9uICNjb252ZXJ0IGZyb20gbG9nMih0cG0rMSkgdG8gdHBtCmJpdm9uYV9leHByZXNzaW9uID0gYml2b25hX2V4cHJlc3Npb24tMQpiaXZvbmFfZXhwcmVzc2lvbiA9IGJpdm9uYV9leHByZXNzaW9uWyxnZW5lc10gJT4lIGFzLmRhdGEuZnJhbWUoKQoKYWxsXzBfZ2VuZXMgPSBjb2xuYW1lcyhiaXZvbmFfZXhwcmVzc2lvbilbY29sU3VtcyhiaXZvbmFfZXhwcmVzc2lvbj09MCwgbmEucm09VFJVRSk9PW5yb3coYml2b25hX2V4cHJlc3Npb24pXSAjZGVsZXRlIHJvd3MgdGhhdCBoYXZlIGFsbCAwCmdlbmVzID0gZ2VuZXNbIWdlbmVzICVpbiUgYWxsXzBfZ2VuZXNdCmJpdm9uYV9leHByZXNzaW9uID0gYml2b25hX2V4cHJlc3Npb25bLCFjb2xuYW1lcyhiaXZvbmFfZXhwcmVzc2lvbikgJWluJSBhbGxfMF9nZW5lc10KZ2ModmVyYm9zZSA9IEYpCmBgYAoKCmBgYHtweXRob259CmJpdm9uYV9leHByZXNzaW9uID0gci5iaXZvbmFfZXhwcmVzc2lvbgpnZW5lcyA9IHIuZ2VuZXMKdXNhZ2VfYnlfY2FsYyA9IGdldF91c2FnZV9mcm9tX3Njb3JlKGNvdW50cz1iaXZvbmFfZXhwcmVzc2lvbix0cG09Yml2b25hX2V4cHJlc3Npb24sZ2VuZXM9Z2VuZXMsY25tZl9vYmo9Y25tZl9vYmosaz01LHN1bVRvMT1UcnVlKQoKYGBgCgpgYGB7cn0KYml2b25hXzVfbWV0YWdlbmVzID0gcHkkdXNhZ2VfYnlfY2FsYwpgYGAKCmBgYHtyfQphbGxfbWV0YWdlbmVzID0gYml2b25hXzVfbWV0YWdlbmVzCmNvbG5hbWVzKGFsbF9tZXRhZ2VuZXMpID0gYygiSUZOYV9ubWYiLCJpbW11bmVfcmVzcG9uc2Vfbm1mIiwgImh5cG94aWFfbm1mIiwiY2VsbF9jeWNsZV9ubWYiLCJ1bmtub3duIikKCmBgYAoKIyBwcm9ncmFtcyBleHByZXNzaW9uCmBgYHtyIGVjaG89VFJVRSwgZmlnLmhlaWdodD03LCBmaWcud2lkdGg9MTIsIHJlc3VsdHM9J2FzaXMnfQoKI2FkZCBlYWNoIG1ldGFnZW5lIHRvIG1ldGFkYXRhCmZvciAoaSAgaW4gMTpuY29sKGFsbF9tZXRhZ2VuZXMpKSB7CiAgbWV0YWdlbmVfbWV0YWRhdGEgPSBhbGxfbWV0YWdlbmVzWyxpLGRyb3A9Rl0KICB0aXNzX3N1YnNldF90dW1vcjIgPSBBZGRNZXRhRGF0YShvYmplY3QgPSB0aXNzX3N1YnNldF90dW1vcjIsbWV0YWRhdGEgPSBtZXRhZ2VuZV9tZXRhZGF0YSxjb2wubmFtZSA9IG5hbWVzKGFsbF9tZXRhZ2VuZXMpW2ldKQp9CgpGZWF0dXJlUGxvdChvYmplY3QgPSB0aXNzX3N1YnNldF90dW1vcjIsZmVhdHVyZXMgPSBjb2xuYW1lcyhhbGxfbWV0YWdlbmVzKSxuY29sID0gMykrRGltUGxvdChvYmplY3QgPSB0aXNzX3N1YnNldF90dW1vcjIsZ3JvdXAuYnkgPSAiYW5hbHlzaXMiKQoKCmBgYAoKCgpgYGB7cn0KbWV0YWdlbmVzX3Zpb2xpbl9jb21wYXJlLjIgPSBmdW5jdGlvbihkYXRhc2V0LHByZWZpeCA9ICIiLHByZV9vbiA9IGMoIk9TSSIsIk5UIiksYXhpcy50ZXh0LnggPSAxMSx0ZXN0ID0gInQudGVzdCIsIHByb2dyYW1zID0gYygiSHlwb3hpYSIsIlRORmEiLCJDZWxsX2N5Y2xlIikscmV0dXJuX2xpc3QgPSBGLGNvbWJpbmVfcGF0aWVudHMgPSBGKXsKICBwbHQubHN0ID0gbGlzdCgpCiAgaWYoY29tYmluZV9wYXRpZW50cyl7CiAgICBnZW5lc19ieV90cCA9IEZldGNoRGF0YShvYmplY3QgPSBkYXRhc2V0LHZhcnMgPSAgYygiYW5hbHlzaXMiLHByb2dyYW1zKSkgJT4lIGZpbHRlcihhbmFseXNpcyAlaW4lIHByZV9vbikgICU+JSBhcy5kYXRhLmZyYW1lKCkgI21lYW4gZXhwcmVzc2lvbgogICAgZm9ybXVsYSA8LSBhcy5mb3JtdWxhKCBwYXN0ZSgiYygiLCBwYXN0ZShwcm9ncmFtcywgY29sbGFwc2UgPSAiLCIpLCAiKX4gYW5hbHlzaXMgIikgKQogICAgCiAgICAjcGxvdCBhbmQgc3BsaXQgYnkgcGF0aWVudDogICAKICAgIHN0YXQudGVzdCA9IGNvbXBhcmVfbWVhbnMoZm9ybXVsYSA9IGZvcm11bGEgLGRhdGEgPSBnZW5lc19ieV90cCxtZXRob2QgPSB0ZXN0LHAuYWRqdXN0Lm1ldGhvZCA9ICJmZHIiKSU+JSAjIEFkZCBwYWlyd2lzZSBjb21wYXJpc29ucyBwLXZhbHVlCiAgICAgIGRwbHlyOjpmaWx0ZXIoZ3JvdXAxID09IHByZV9vblsxXSAmIGdyb3VwMiA9PSBwcmVfb25bMl0pICAjZmlsdGVyIGZvciBwcmUgdnMgb24gYW5hbHlzaXMgb25seQogICAgCiAgICBzdGF0LnRlc3QkcC5mb3JtYXQgPXN0YXQudGVzdCRwLmFkaiAjbW9kaWZ0IDAgcHZhbHVlIHRvIGJlIGxvd2VzdCBwb3NzaWJsZSBmbG9hdAogICAgc3RhdC50ZXN0JHAuZm9ybWF0WyFzdGF0LnRlc3QkcC5mb3JtYXQgPT0gMCBdIDwtIHBhc3RlKCI9IixzdGF0LnRlc3QkcC5mb3JtYXRbIXN0YXQudGVzdCRwLmZvcm1hdCA9PSAwIF0pCiAgICBzdGF0LnRlc3QkcC5mb3JtYXRbc3RhdC50ZXN0JHAuZm9ybWF0ID09IDAgXSA8LSBwYXN0ZSgiPCIsLk1hY2hpbmUkZG91YmxlLnhtaW4gJT4lIHNpZ25pZihkaWdpdHMgPSAzKSkKICAgIAogICAgCiAgICBnZW5lc19ieV90cCA9IHJlc2hhcGUyOjptZWx0KGdlbmVzX2J5X3RwLCBpZC52YXJzID0gYygiYW5hbHlzaXMiKSx2YWx1ZS5uYW1lID0gInNjb3JlIikKICAgIHBsdCA9IGdncGxvdChnZW5lc19ieV90cCwgYWVzKHggPSB2YXJpYWJsZSwgeSA9IHNjb3JlLGZpbGwgPSBhbmFseXNpcykpICsgZ2VvbV9zcGxpdF92aW9saW4oc2NhbGUgPSAnd2lkdGgnKSsgCiAgICAgIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuMjUsIG5vdGNoID0gRkFMU0UsIG5vdGNod2lkdGggPSAuNCwgb3V0bGllci5zaGFwZSA9IE5BLCBjb2VmPTApKwogICAgICB5bGltKG1pbihnZW5lc19ieV90cCRzY29yZSksbWF4KGdlbmVzX2J5X3RwJHNjb3JlKSoxLjI1KQogICAgcGx0ID0gcGx0ICtzdGF0X3B2YWx1ZV9tYW51YWwoc3RhdC50ZXN0LCBsYWJlbCA9ICJwIHtwLmZvcm1hdH0iLCAgI2FkZCBwIHZhbHVlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB5LnBvc2l0aW9uID0gbWF4KGdlbmVzX2J5X3RwJHNjb3JlKSoxLjA4LGluaGVyaXQuYWVzID0gRixzaXplID0gMy4zLHggPSAiLnkuIikgIyBzZXQgcG9zaXRpb24gYXQgdGhlIHRvcCB2YWx1ZQogICAgcmV0dXJuKHBsdCkKICB9CiAgCiAgZm9yIChtZXRlZ2VuZSBpbiBwcm9ncmFtcykgewogICAgI2NyZWF0ZSBkYXRhOgogICAgZ2VuZXNfYnlfdHAgPSBGZXRjaERhdGEob2JqZWN0ID0gZGF0YXNldCx2YXJzID0gIGMoIm9yaWcuaWRlbnQiLCJ0cmVhdG1lbnQiLG1ldGVnZW5lKSkgJT4lIGZpbHRlcih0cmVhdG1lbnQgJWluJSBwcmVfb24pICAlPiUgYXMuZGF0YS5mcmFtZSgpICNtZWFuIGV4cHJlc3Npb24KICAgIG5hbWVzKGdlbmVzX2J5X3RwKVszXSA9ICJNZXRhZ2VuZV9tZWFuIgogICAgCiAgICBmbSA8LSBhcy5mb3JtdWxhKHBhc3RlKCJNZXRhZ2VuZV9tZWFuIiwgIn4iLCAidHJlYXRtZW50IikpICNtYWtlIGZvcm11bGEgdG8gcGxvdAogICAgCiAgICAjcGxvdCBhbmQgc3BsaXQgYnkgcGF0aWVudDogICAKICAgIHN0YXQudGVzdCA9IGNvbXBhcmVfbWVhbnMoZm9ybXVsYSA9IGZtICxkYXRhID0gZ2VuZXNfYnlfdHAsbWV0aG9kID0gdGVzdCxncm91cC5ieSA9ICJvcmlnLmlkZW50IixwLmFkanVzdC5tZXRob2QgPSAiZmRyIiklPiUgIyBBZGQgcGFpcndpc2UgY29tcGFyaXNvbnMgcC12YWx1ZQogICAgICBkcGx5cjo6ZmlsdGVyKGdyb3VwMSA9PSBwcmVfb25bMV0gJiBncm91cDIgPT0gcHJlX29uWzJdKSAgI2ZpbHRlciBmb3IgcHJlIHZzIG9uIHRyZWF0bWVudCBvbmx5CiAgICAKICAgIHN0YXQudGVzdCRwLmZvcm1hdCA9c3RhdC50ZXN0JHAuYWRqICNtb2RpZnQgMCBwdmFsdWUgdG8gYmUgbG93ZXN0IHBvc3NpYmxlIGZsb2F0CiAgICBzdGF0LnRlc3QkcC5mb3JtYXRbIXN0YXQudGVzdCRwLmZvcm1hdCA9PSAwIF0gPC0gcGFzdGUoIj0iLHN0YXQudGVzdCRwLmZvcm1hdFshc3RhdC50ZXN0JHAuZm9ybWF0ID09IDAgXSkKICAgIHN0YXQudGVzdCRwLmZvcm1hdFtzdGF0LnRlc3QkcC5mb3JtYXQgPT0gMCBdIDwtIHBhc3RlKCI8IiwuTWFjaGluZSRkb3VibGUueG1pbiAlPiUgc2lnbmlmKGRpZ2l0cyA9IDMpKQogICAgCiAgICBwbHQgPSBnZ3Bsb3QoZ2VuZXNfYnlfdHAsIGFlcyh4ID0gb3JpZy5pZGVudCwgeSA9IE1ldGFnZW5lX21lYW4sZmlsbCA9IHRyZWF0bWVudCkpICsgZ2VvbV9zcGxpdF92aW9saW4oc2NhbGUgPSAnd2lkdGgnKSt5bGFiKG1ldGVnZW5lKSsgCiAgICAgIGdlb21fYm94cGxvdCh3aWR0aCA9IDAuMjUsIG5vdGNoID0gRkFMU0UsIG5vdGNod2lkdGggPSAuNCwgb3V0bGllci5zaGFwZSA9IE5BLCBjb2VmPTApKwogICAgICB5bGltKG1pbihnZW5lc19ieV90cCRNZXRhZ2VuZV9tZWFuKSxtYXgoZ2VuZXNfYnlfdHAkTWV0YWdlbmVfbWVhbikqMS4yNSkKICAgIHBsdCA9IHBsdCArc3RhdF9wdmFsdWVfbWFudWFsKHN0YXQudGVzdCwgbGFiZWwgPSAicCB7cC5mb3JtYXR9IiwgICNhZGQgcCB2YWx1ZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeS5wb3NpdGlvbiA9IG1heChnZW5lc19ieV90cCRNZXRhZ2VuZV9tZWFuKSoxLjA4LHggPSAib3JpZy5pZGVudCIsaW5oZXJpdC5hZXMgPSBGLHNpemUgPSAzLjMpICMgc2V0IHBvc2l0aW9uIGF0IHRoZSB0b3AgdmFsdWUKICAgIAogICAgcGx0LmxzdFtbbWV0ZWdlbmVdXSA9IHBsdAogICAgaWYgKCFyZXR1cm5fbGlzdCkgewogICAgICBwcmludChwbHQpCiAgICB9CiAgfQogIAogIAogIAogIAogIGlmIChyZXR1cm5fbGlzdCkgewogICAgcmV0dXJuKHBsdC5sc3QpCiAgfQp9CmBgYAoKIyBOTUYgcHJvZ3JhbXMKYGBge3J9Cm1ldGFnZW5lc192aW9saW5fY29tcGFyZS4yKGRhdGFzZXQgPSB0aXNzX3N1YnNldF90dW1vcjIscHJlZml4ID0gInBhdGllbnQiLHByZV9vbiA9IGMoIm5haXZlIiwiZ3JvdXBlZF9wciIpLHRlc3QgPSAid2lsY294LnRlc3QiLHByb2dyYW1zID0gY29sbmFtZXMoYWxsX21ldGFnZW5lcylbMTozXSwgcmV0dXJuX2xpc3QgPSBGLGNvbWJpbmVfcGF0aWVudHMgPSBUKQpgYGAKYGBge3J9CmdlbmVzZXRzIDwtIG1zaWdkYl9kb3dubG9hZCgiSG9tbyBzYXBpZW5zIixjYXRlZ29yeT0iSCIpIApnZW5lX2xpc3QgPSBsaXN0KElGTmFfZ2VuZXMgPSBnZW5lc2V0cyRIQUxMTUFSS19JTlRFUkZFUk9OX0FMUEhBX1JFU1BPTlNFLCBUTkZhX2dlbmVzID0gZ2VuZXNldHMkSEFMTE1BUktfVE5GQV9TSUdOQUxJTkdfVklBX05GS0IsIGhpZl90YXJnZXRzID0gaGlmX3RhcmdldHMsRTJGX2dlbmVzID0gZ2VuZXNldHMkSEFMTE1BUktfRTJGX1RBUkdFVFMpCgpmb3IgKGkgaW4gc2VxX2Fsb25nKGdlbmVfbGlzdCkpIHsKICBnZW5lcyA9IGdlbmVfbGlzdFtbaV1dCiAgZ2VuZXMgPSBnZW5lc1tnZW5lcyAlaW4lIHJvd25hbWVzKHRpc3Nfc3Vic2V0X3R1bW9yMildCiAgbmFtZSA9IG5hbWVzKGdlbmVfbGlzdClbaV0KICBzY29yZXMgPSBGZXRjaERhdGEob2JqZWN0ID0gdGlzc19zdWJzZXRfdHVtb3IyLHZhcnMgPSBjKGdlbmVzKSkKICBzY29yZXMgPSBzY29yZXMgJT4lIHJvd01lYW5zKCkgJT4lIGFzLmRhdGEuZnJhbWUoKQogIHRpc3Nfc3Vic2V0X3R1bW9yMiAlPD4lIEFkZE1ldGFEYXRhKG1ldGFkYXRhID0gc2NvcmVzLGNvbC5uYW1lID0gbmFtZSkKICAKfQpgYGAKCiMgU2lnbmF0dXJlcwoKYGBge3J9Cm1ldGFnZW5lc192aW9saW5fY29tcGFyZS4yKGRhdGFzZXQgPSB0aXNzX3N1YnNldF90dW1vcjIscHJlZml4ID0gInBhdGllbnQiLHByZV9vbiA9IGMoIm5haXZlIiwiZ3JvdXBlZF9wciIpLHRlc3QgPSAid2lsY294LnRlc3QiLHByb2dyYW1zID0gbmFtZXMoZ2VuZV9saXN0KSwgcmV0dXJuX2xpc3QgPSBGLGNvbWJpbmVfcGF0aWVudHMgPSBUKQpgYGAKIyBtZXRhZGF0YQpgYGB7cn0KdGlzc19zdWJzZXRfdHVtb3IyQG1ldGEuZGF0YQpgYGAKCg==