Functions
library(stringi)
library(reticulate)
source_from_github(repositoy = "DEG_functions",version = "0.2.24")
source_from_github(repositoy = "cNMF_functions",version = "0.3.91",script_name = "cnmf_function_Harmony.R")
Data
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 selection plot
plot_path = paste0("/sci/labs/yotamd/lab_share/avishai.wizel/R_projects/EGFR/Data/cNMF/cNMF_models_Varnorm_Harmony_2Kvargenes_all_K/cNMF_models_Varnorm_Harmony_2Kvargenes_all_K.k_selection.png")
knitr::include_graphics(plot_path)

k = 5
density_threshold = 0.1
cnmf_obj.consensus(k=k, density_threshold=density_threshold,show_clustering=True)
usage_norm5, gep_scores5, _, _ = cnmf_obj.load_results(K=k, density_threshold=density_threshold)
usage_norm5 = py$usage_norm5
gep_scores5 = py$gep_scores5
NMF usage

Programs
GSEA
for (col in seq_along(gep_scores5_xeno)) {
ranked_vec = gep_scores5_xeno[,col] %>% setNames(rownames(gep_scores5_xeno)) %>% sort(decreasing = TRUE)
hyp_obj <- fgsea.wrapper(ranked_vec, genesets)
# hyp_list[[paste0("gep",col)]] = hyp_obj
print_tab(hyp_dots(hyp_obj),title = paste0("gep",col))
}
gep1

gep2

gep3

gep4

gep5

NA
xeno = FindVariableFeatures(object = xeno,nfeatures = 2000)
xeno_vargenes = VariableFeatures(object = xeno)
xeno_expression = FetchData(object = xeno,vars = xeno_vargenes,slot='counts')
all_0_genes = colnames(xeno_expression)[colSums(xeno_expression==0, na.rm=TRUE)==nrow(xeno_expression)] #delete rows that have all 0
xeno_vargenes = xeno_vargenes[!xeno_vargenes %in% all_0_genes]
calculate score for
Xeno
import numpy as np
import scanpy as sc
xeno_expression = r.xeno_expression
xeno_vargenes = r.xeno_vargenes
tpm = compute_tpm(xeno_expression)
usage_by_calc = get_usage_from_score(counts=xeno_expression,tpm=tpm,genes=xeno_vargenes, cnmf_obj=cnmf_obj,k=5)
/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.
xeno_5_metagenes = py$usage_by_calc
colnames(xeno_5_metagenes) = c("IFNa","immune_response", "hypoxia","cell_cycle","unknown")
programs
expression
#add each metagene to metadata
for (i in 1:ncol(xeno_5_metagenes)) {
metagene_metadata = xeno_5_metagenes[,i,drop=F]
xeno = AddMetaData(object = xeno,metadata = metagene_metadata,col.name = names(xeno_5_metagenes)[i])
}
FeaturePlot(object = xeno,features = colnames(xeno_5_metagenes),ncol = 3)

NA
NA
Programs dotplot
DotPlot(object = xeno, features = colnames(xeno_5_metagenes),group.by = 'treatment')

NMF
programs regulation
metagenes_mean_compare(dataset = xeno,time.point_var = "treatment",prefix = "model",patient.ident_var = "orig.ident",pre_on = c("NT","OSI"),test = "wilcox.test",programs = colnames(all_metagenes)[1:4])
IFNa per patient

IFNa

immune_response per patient

immune_response

hypoxia per patient

hypoxia

cell_cycle per patient

cell_cycle

NA
Top program 2 genes
expression correlation
top_ot = gep_scores5_xeno [order(gep_scores5_xeno [,2],decreasing = T),2,drop = F]%>% head(200) %>% rownames()
num_of_clusters = 7
annotation = plot_genes_cor(dataset = xeno,hallmark_name = NULL,num_of_clusters = num_of_clusters,geneIds = top_ot)
## genes expression heatmap {.unnumbered }

NA
program
2 all clusters expression
for (chosen_clusters in 1:num_of_clusters) {
chosen_genes = annotation[["myannotation"]] %>% dplyr::filter(cluster == chosen_clusters) %>% rownames() #take relevant genes
# print(chosen_genes)
hyp_obj <- hypeR(chosen_genes, genesets_env, test = "hypergeometric", fdr=1, plotting=F,background = rownames(xeno_5_gep_scores))
scoresAndIndices <- getPathwayScores(xeno@assays$RNA@data, chosen_genes)
xeno=AddMetaData(xeno,scoresAndIndices$pathwayScores,paste0("cluster",chosen_clusters))
print_tab(plt =
hyp_dots(hyp_obj,size_by = "none",title = paste0("cluster",chosen_clusters))+
FeaturePlot(object = xeno,features = paste0("cluster",chosen_clusters)),
title = chosen_clusters)
}
1

2

3

4

5

6

7

NA
Correlation of
clusters
for (chosen_clusters in 1:num_of_clusters) {
cor_res = cor(xeno$TNFa,xeno[[paste0("cluster",chosen_clusters)]])
print(paste("correlation of TNFa program to", paste0("cluster",chosen_clusters),":", cor_res))
}
[1] "correlation of TNFa program to cluster1 : 0.339424898015558"
[1] "correlation of TNFa program to cluster2 : 0.187545650255459"
[1] "correlation of TNFa program to cluster3 : 0.644457044249512"
[1] "correlation of TNFa program to cluster4 : 0.667805824488597"
[1] "correlation of TNFa program to cluster5 : 0.403323740001404"
[1] "correlation of TNFa program to cluster6 : 0.399837119655965"
[1] "correlation of TNFa program to cluster7 : -0.0285178052065907"
clusters_idents = c("cluster1", "KEGG_OXIDATIVE_PHOSPHORYLATION","HALLMARK_TNFA_SIGNALING_VIA_NFKB","KEGG_ANTIGEN_PROCESSING_AND_PRESENTATION","HALLMARK_P53_PATHWAY","cluster6","cluster7")
metagenes_mean_compare <- function(dataset,time.point_var,prefix = "",patient.ident_var,pre_on = c("OSI","NT"),axis.text.x = 11,test = "t.test", programs = c("Hypoxia","TNFa","Cell_cycle"), with_split = T, without_split = T){
for (metegene in programs) {
#create data:
genes_by_tp = FetchData(object = dataset,vars = metegene) %>% rowSums() %>% as.data.frame() #mean expression
names(genes_by_tp)[1] = "Metagene_mean"
genes_by_tp = cbind(genes_by_tp,FetchData(object = dataset,vars = c(patient.ident_var,time.point_var))) # add id and time points
genes_by_tp_forPlot = genes_by_tp %>% mutate(!!ensym(patient.ident_var) := paste(prefix,genes_by_tp[,patient.ident_var])) #add "model" before each model/patient
fm <- as.formula(paste("Metagene_mean", "~", time.point_var)) #make formula to plot
#plot and split by patient:
stat.test = compare_means(formula = fm ,data = genes_by_tp_forPlot,method = test,group.by = patient.ident_var)%>% # Add pairwise comparisons p-value
dplyr::filter(group1 == pre_on[1] & group2 == pre_on[2]) #filter for pre vs on treatment only
plt = ggboxplot(genes_by_tp_forPlot, x = time.point_var, y = "Metagene_mean", color = time.point_var) + #plot
stat_pvalue_manual(stat.test, label = "p = {p.adj}", #add p value
y.position = max(genes_by_tp_forPlot$Metagene_mean))+ # set position at the top value
grids()+
ylab(paste(metegene,"mean"))+
theme(axis.text.x = element_text(size = axis.text.x))+
ylim(0, max(genes_by_tp_forPlot$Metagene_mean)*1.2) # extend y axis to show p value
plt = facet(plt, facet.by = patient.ident_var) #split by patients
print_tab(plt = plt,title = c(metegene,"per patient"))
#plot = without split by patient:
if(without_split){
stat.test = compare_means(formula = fm ,data = genes_by_tp_forPlot,comparisons = my_comparisons,method = test)%>%
dplyr::filter(group1 == pre_on[1] & group2 == pre_on[2]) # Add pairwise comparisons p-value
plt = ggboxplot(genes_by_tp_forPlot, x = time.point_var, y = "Metagene_mean", color = time.point_var) +
stat_pvalue_manual(stat.test, label = "p = {p.adj}", #add p value
y.position = max(genes_by_tp_forPlot$Metagene_mean))+ # set position at the top value
grids()+
ylab(paste(metegene,"mean"))+
ylim(0, max(genes_by_tp_forPlot$Metagene_mean)*1.2) # extend y axis to show p value
print_tab(plt = plt,title = metegene)
}
}
}
program 2 intersected
genes
programs_of_cluster = c()
for (chosen_clusters in 1:num_of_clusters) {
chosen_genes = annotation[["myannotation"]] %>% dplyr::filter(cluster == chosen_clusters) %>% rownames() #take relevant genes
pathway_name = clusters_idents[chosen_clusters]
if (!startsWith(x = pathway_name,prefix = "cluster")){
chosen_genes = (chosen_genes) %>% intersect(genesets[[pathway_name]])
pathway_name = paste0(pathway_name,"_cluster")
}
programs_of_cluster = c(programs_of_cluster,pathway_name)
print(pathway_name)
print(chosen_genes)
cat("\n")
scoresAndIndices <- getPathwayScores(xeno@assays$RNA@data, chosen_genes)
xeno=AddMetaData(xeno,scoresAndIndices$pathwayScores,pathway_name)
}
[1] "cluster1"
[1] "STAC2" "TACR1" "TRPM4" "SCPEP1" "VIM" "XBP1" "GLYATL2" "RCN3" "TNIP3" "PTN"
[11] "RASSF9" "PIK3R1" "SOX4" "SOX2" "RUNX3" "ZNF208" "CLDN8" "CACNB4" "TSPAN8" "PLEKHS1"
[21] "PLA2G4A" "LPAR3" "MUCL1" "TLE4" "ALDH2" "FSIP2" "LINC00624" "MTRNR2L3" "PDE4B" "A1BG"
[31] "DEFB1" "ADGRV1"
[1] "KEGG_OXIDATIVE_PHOSPHORYLATION_cluster"
[1] "MT-ND3" "MT-ATP8" "MT-ND1" "MT-ND2"
[1] "HALLMARK_TNFA_SIGNALING_VIA_NFKB_cluster"
[1] "ZFP36" "JUNB" "IER2" "GADD45B" "CXCL2" "BCL6" "NR4A2" "NR4A3" "SOCS3" "EGR2" "NFKBIA" "IL6"
[13] "IRF1" "ETS2"
[1] "KEGG_ANTIGEN_PROCESSING_AND_PRESENTATION_cluster"
[1] "CD74" "HLA-DRA" "HLA-DRB1" "HLA-DMA" "HLA-DPB1" "HLA-DPA1" "HLA-DRB5"
[1] "HALLMARK_P53_PATHWAY_cluster"
[1] "FOS" "ZFP36L1" "JUN" "ATF3" "INHBB"
[1] "cluster6"
[1] "SIX1" "ANKRD36C" "CP" "PIGR" "CNGA1" "COLEC12" "AC092683.1" "CHST9" "NFIB"
[10] "PIK3IP1" "REL" "LINC00342" "MEF2C" "BMP3" "SV2B" "SLC38A3" "WFDC2" "AP000851.1"
[1] "cluster7"
[1] "CFD" "CASP14" "KRT13"
program
2 intersected pathway regulation
metagenes_mean_compare(dataset = xeno,time.point_var = "treatment",prefix = "model",patient.ident_var = "orig.ident",pre_on = c("NT","OSI"),test = "wilcox.test",programs = programs_of_cluster,without_split = F)
cluster1 per patient

KEGG_OXIDATIVE_PHOSPHORYLATION_cluster per
patient

HALLMARK_TNFA_SIGNALING_VIA_NFKB_cluster per
patient

KEGG_ANTIGEN_PROCESSING_AND_PRESENTATION_cluster
per patient

HALLMARK_P53_PATHWAY_cluster per patient

cluster6 per patient

cluster7 per patient

NA
program 2 significant
plot

Top program 3 genes
expression correlation
top_hypoxia = gep_scores5_xeno [order(gep_scores5_xeno [,3],decreasing = T),2,drop = F]%>% head(200) %>% rownames()
num_of_clusters = 4
annotation = plot_genes_cor(dataset = xeno,hallmark_name = NULL,num_of_clusters = num_of_clusters,geneIds = top_hypoxia)
## genes expression heatmap {.unnumbered }

NA
program
3 all clusters expression
for (chosen_clusters in 1:num_of_clusters) {
chosen_genes = annotation[["myannotation"]] %>% dplyr::filter(cluster == chosen_clusters) %>% rownames() #take relevant genes
# print(chosen_genes)
hyp_obj <- hypeR(chosen_genes, genesets_env, test = "hypergeometric", fdr=1, plotting=F,background = rownames(xeno_5_gep_scores))
scoresAndIndices <- getPathwayScores(xeno@assays$RNA@data, chosen_genes)
xeno=AddMetaData(xeno,scoresAndIndices$pathwayScores,paste0("cluster",chosen_clusters))
print_tab(plt =
hyp_dots(hyp_obj,size_by = "none",title = paste0("cluster",chosen_clusters))+
FeaturePlot(object = xeno,features = paste0("cluster",chosen_clusters)),
title = chosen_clusters)
}
1

2

3

4

NA
Correlation of
clusters
for (chosen_clusters in 1:num_of_clusters) {
cor_res = cor(xeno$hypoxia,xeno[[paste0("cluster",chosen_clusters)]])
print(paste("correlation of hypoxia program to", paste0("cluster",chosen_clusters),":", cor_res))
}
[1] "correlation of hypoxia program to cluster1 : 0.826481794630301"
[1] "correlation of hypoxia program to cluster2 : 0.533339523425782"
[1] "correlation of hypoxia program to cluster3 : 0.419138131097487"
[1] "correlation of hypoxia program to cluster4 : 0.0551682337077848"
clusters_idents = c("HALLMARK_HYPOXIA", "HIF_targets","cluster3","cluster4")
programs_of_cluster = c()
for (chosen_clusters in 1:num_of_clusters) {
chosen_genes = annotation[["myannotation"]] %>% dplyr::filter(cluster == chosen_clusters) %>% rownames() #take relevant genes
pathway_name = clusters_idents[chosen_clusters]
if (!startsWith(x = pathway_name,prefix = "cluster")){
chosen_genes = (chosen_genes) %>% intersect(genesets[[pathway_name]])
pathway_name = paste0(pathway_name,"_cluster")
}
programs_of_cluster = c(programs_of_cluster,pathway_name)
print(pathway_name)
print(chosen_genes)
cat("\n")
scoresAndIndices <- getPathwayScores(xeno@assays$RNA@data, chosen_genes)
xeno=AddMetaData(xeno,scoresAndIndices$pathwayScores,pathway_name)
}
[1] "HALLMARK_HYPOXIA_cluster"
[1] "VEGFA" "NDRG1" "IGFBP3" "P4HA1" "ADM" "DDIT4" "HK2" "STC2" "HSPA5" "SERPINE1"
[11] "AKAP12" "PDGFB" "STC1" "CAV1" "TNFAIP3" "COL5A1" "PLAUR" "SLC2A3" "TGFBI"
[1] "HIF_targets_cluster"
[1] "ERO1A" "PGK1" "SLC16A3" "ENO1"
[1] "cluster3"
[1] "ENO2" "AL133453.1" "PLIN2" "CA12" "BHLHE40" "OSMR" "SPAG4" "ZNF395" "P4HB"
[10] "PIM3" "G0S2" "HIF1A-AS2" "ATP1B1" "KATNBL1" "SAMD4A" "LRP1" "SLC6A8" "IL1RAP"
[19] "HIST1H2BC" "MT1F" "IER3" "FLNA" "FAM69C" "MKNK2" "HIST1H2AE" "ELL2" "HIST1H1C"
[28] "CA2" "LPCAT1" "SLITRK6" "BMP2" "FZD8" "MIF" "ATP8B3" "BIK" "TTYH3"
[37] "MEIOB" "APOL2" "IFNGR2" "HIST1H4H" "SPINK1" "GRIN2A"
[1] "cluster4"
[1] "ADAM8" "MAF" "DDIT3" "NUPR1" "PDCD1" "MIR155HG" "FAM13A" "ETS2" "DNAJB9"
[10] "PKP4-AS1" "GDF15" "SNHG12" "TNS1" "HERPUD1" "SEMA5B" "ARRDC3" "WFIKKN1" "CLEC2B"
[19] "XIST" "CDH7" "FLNC" "HLA-DQB1" "SEC61G" "AC068672.2" "GPNMB" "CP" "PNPLA5"
[28] "PROX1" "CASC15" "RARRES1" "NME8" "FBXO32" "MIAT" "PGF" "DEPP1" "THEMIS2"
[37] "SMAD3" "CDH2" "SPOCK3" "SOD2" "IFFO1" "HLX" "SYNJ2" "CHI3L1" "AC034213.1"
[46] "HTR3C" "HMOX1" "PLTP"
program
3 intersected pathway regulation
metagenes_mean_compare(dataset = xeno,time.point_var = "treatment",prefix = "model",patient.ident_var = "orig.ident",pre_on = c("NT","OSI"),test = "wilcox.test",programs = programs_of_cluster,without_split = F)
HALLMARK_HYPOXIA_cluster per patient

HIF_targets_cluster per patient

cluster3 per patient

cluster4 per patient

NA
Top program 3 genes
expression correlation
top_cc = gep_scores5_xeno [order(gep_scores5_xeno [,4],decreasing = T),2,drop = F]%>% head(200) %>% rownames()
num_of_clusters = 4
annotation = plot_genes_cor(dataset = xeno,hallmark_name = NULL,num_of_clusters = num_of_clusters,geneIds = top_cc)
## genes expression heatmap {.unnumbered }

NA
program
3 all clusters expression
for (chosen_clusters in 1:num_of_clusters) {
chosen_genes = annotation[["myannotation"]] %>% dplyr::filter(cluster == chosen_clusters) %>% rownames() #take relevant genes
# print(chosen_genes)
hyp_obj <- hypeR(chosen_genes, genesets_env, test = "hypergeometric", fdr=1, plotting=F,background = rownames(xeno_5_gep_scores))
scoresAndIndices <- getPathwayScores(xeno@assays$RNA@data, chosen_genes)
xeno=AddMetaData(xeno,scoresAndIndices$pathwayScores,paste0("cluster",chosen_clusters))
print_tab(plt =
hyp_dots(hyp_obj,size_by = "none",title = paste0("cluster",chosen_clusters))+
FeaturePlot(object = xeno,features = paste0("cluster",chosen_clusters)),
title = chosen_clusters)
}
1

2

3

4

NA
LS0tCnRpdGxlOiAnYHIgcnN0dWRpb2FwaTo6Z2V0U291cmNlRWRpdG9yQ29udGV4dCgpJHBhdGggJT4lIGJhc2VuYW1lKCkgJT4lIGdzdWIocGF0dGVybiA9ICJcXC5SbWQiLHJlcGxhY2VtZW50ID0gIiIpYCcgCmF1dGhvcjogIkF2aXNoYWkgV2l6ZWwiCmRhdGU6ICdgciBTeXMudGltZSgpYCcKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdG9jOiB5ZXMKICAgIHRvY19jb2xsYXBzZTogeWVzCiAgICB0b2NfZmxvYXQ6IAogICAgICBjb2xsYXBzZWQ6IEZBTFNFCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvY19kZXB0aDogMQotLS0KCgoKIyBGdW5jdGlvbnMKCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQoKbGlicmFyeShzdHJpbmdpKQpsaWJyYXJ5KHJldGljdWxhdGUpCnNvdXJjZV9mcm9tX2dpdGh1YihyZXBvc2l0b3kgPSAiREVHX2Z1bmN0aW9ucyIsdmVyc2lvbiA9ICIwLjIuMjQiKQpzb3VyY2VfZnJvbV9naXRodWIocmVwb3NpdG95ID0gImNOTUZfZnVuY3Rpb25zIix2ZXJzaW9uID0gIjAuMy45MSIsc2NyaXB0X25hbWUgPSAiY25tZl9mdW5jdGlvbl9IYXJtb255LlIiKQoKYGBgCgojIERhdGEKCmBgYHtyfQoKYGBgCgoKYGBge3B5dGhvbn0KZnJvbSBjbm1mIGltcG9ydCBjTk1GCmltcG9ydCBwaWNrbGUKZiA9IG9wZW4oJy4vRGF0YS9jbm1mL2NubWZfb2JqZWN0cy9tb2RlbHNfMkt2YXJnZW5lc19hbGxfS19jbm1mX29iai5wY2tsJywgJ3JiJykKY25tZl9vYmogPSBwaWNrbGUubG9hZChmKQpmLmNsb3NlKCkKYGBgCgojIEsgc2VsZWN0aW9uIHBsb3QKYGBge3IgZmlnLmhlaWdodD0yLCBmaWcud2lkdGg9Mn0KcGxvdF9wYXRoID0gcGFzdGUwKCIvc2NpL2xhYnMveW90YW1kL2xhYl9zaGFyZS9hdmlzaGFpLndpemVsL1JfcHJvamVjdHMvRUdGUi9EYXRhL2NOTUYvY05NRl9tb2RlbHNfVmFybm9ybV9IYXJtb255XzJLdmFyZ2VuZXNfYWxsX0svY05NRl9tb2RlbHNfVmFybm9ybV9IYXJtb255XzJLdmFyZ2VuZXNfYWxsX0sua19zZWxlY3Rpb24ucG5nIikKa25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGxvdF9wYXRoKQpgYGAKCmBgYHtweXRob259CmsgPSA1CmRlbnNpdHlfdGhyZXNob2xkID0gMC4xIApjbm1mX29iai5jb25zZW5zdXMoaz1rLCBkZW5zaXR5X3RocmVzaG9sZD1kZW5zaXR5X3RocmVzaG9sZCxzaG93X2NsdXN0ZXJpbmc9VHJ1ZSkKdXNhZ2Vfbm9ybTUsIGdlcF9zY29yZXM1X3hlbm8sIF8sIF8gPSBjbm1mX29iai5sb2FkX3Jlc3VsdHMoSz1rLCBkZW5zaXR5X3RocmVzaG9sZD1kZW5zaXR5X3RocmVzaG9sZCkKCmBgYAoKYGBge3J9CnVzYWdlX25vcm01ID0gcHkkdXNhZ2Vfbm9ybTUKZ2VwX3Njb3JlczVfeGVubyA9IHB5JGdlcF9zY29yZXM1X3hlbm8KCmBgYAoKIyBOTUYgdXNhZ2UKYGBge3IgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTgsIHJlc3VsdHM9J2FzaXMnfQogIGZvciAoaSBpbiAxOm5jb2wodXNhZ2Vfbm9ybTUpKSB7CiAgICBtZXRhZ2VfbWV0YWRhdGEgPSB1c2FnZV9ub3JtNSAlPiUgZHBseXI6OnNlbGVjdChpKQogICAgeGVubyA9IEFkZE1ldGFEYXRhKG9iamVjdCA9IHhlbm8sbWV0YWRhdGEgPSBtZXRhZ2VfbWV0YWRhdGEsY29sLm5hbWUgPSBwYXN0ZTAoImdlcCIsaSkpCiAgfQogIAogIEZlYXR1cmVQbG90KG9iamVjdCA9IHhlbm8sZmVhdHVyZXMgPSBwYXN0ZTAoImdlcCIsMTpuY29sKHVzYWdlX25vcm01KSksbmNvbCA9IDIpCgoKYGBgCiMgUHJvZ3JhbXMgR1NFQSB7LnRhYnNldH0KCmBgYHtyIHJlc3VsdHM9J2FzaXMnfQogIGZvciAoY29sIGluIHNlcV9hbG9uZyhnZXBfc2NvcmVzNV94ZW5vKSkgewogICAgIHJhbmtlZF92ZWMgPSBnZXBfc2NvcmVzNV94ZW5vWyxjb2xdICU+JSBzZXROYW1lcyhyb3duYW1lcyhnZXBfc2NvcmVzNV94ZW5vKSkgJT4lIHNvcnQoZGVjcmVhc2luZyA9IFRSVUUpIAogICAgIGh5cF9vYmogPC0gZmdzZWEud3JhcHBlcihyYW5rZWRfdmVjLCBnZW5lc2V0cykKICAgICMgaHlwX2xpc3RbW3Bhc3RlMCgiZ2VwIixjb2wpXV0gPSBoeXBfb2JqCiAgICAgICBwcmludF90YWIoaHlwX2RvdHMoaHlwX29iaiksdGl0bGUgPSBwYXN0ZTAoImdlcCIsY29sKSkKICB9CmBgYApgYGB7cn0KeGVubyA9IEZpbmRWYXJpYWJsZUZlYXR1cmVzKG9iamVjdCA9IHhlbm8sbmZlYXR1cmVzID0gMjAwMCkKeGVub192YXJnZW5lcyA9IFZhcmlhYmxlRmVhdHVyZXMob2JqZWN0ID0geGVubykKCnhlbm9fZXhwcmVzc2lvbiA9IEZldGNoRGF0YShvYmplY3QgPSB4ZW5vLHZhcnMgPSB4ZW5vX3ZhcmdlbmVzLHNsb3Q9J2NvdW50cycpCmFsbF8wX2dlbmVzID0gY29sbmFtZXMoeGVub19leHByZXNzaW9uKVtjb2xTdW1zKHhlbm9fZXhwcmVzc2lvbj09MCwgbmEucm09VFJVRSk9PW5yb3coeGVub19leHByZXNzaW9uKV0gI2RlbGV0ZSByb3dzIHRoYXQgaGF2ZSBhbGwgMAp4ZW5vX3ZhcmdlbmVzID0geGVub192YXJnZW5lc1sheGVub192YXJnZW5lcyAlaW4lIGFsbF8wX2dlbmVzXQoKYGBgCgoKIyBjYWxjdWxhdGUgc2NvcmUgZm9yIFhlbm8KYGBge3B5dGhvbn0KaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBzY2FucHkgYXMgc2MKeGVub19leHByZXNzaW9uID0gci54ZW5vX2V4cHJlc3Npb24KeGVub192YXJnZW5lcyA9IHIueGVub192YXJnZW5lcwp0cG0gPSAgY29tcHV0ZV90cG0oeGVub19leHByZXNzaW9uKQp1c2FnZV9ieV9jYWxjID0gZ2V0X3VzYWdlX2Zyb21fc2NvcmUoY291bnRzPXhlbm9fZXhwcmVzc2lvbix0cG09dHBtLGdlbmVzPXhlbm9fdmFyZ2VuZXMsIGNubWZfb2JqPWNubWZfb2JqLGs9NSkKYGBgCgpgYGB7cn0KeGVub181X21ldGFnZW5lcyA9IHB5JHVzYWdlX2J5X2NhbGMKY29sbmFtZXMoeGVub181X21ldGFnZW5lcykgPSBjKCJJRk5hIiwiaW1tdW5lX3Jlc3BvbnNlIiwgImh5cG94aWEiLCJjZWxsX2N5Y2xlIiwidW5rbm93biIpCmBgYAoKCiMgcHJvZ3JhbXMgZXhwcmVzc2lvbgpgYGB7ciBlY2hvPVRSVUUsIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTEyLCByZXN1bHRzPSdhc2lzJ30KCiNhZGQgZWFjaCBtZXRhZ2VuZSB0byBtZXRhZGF0YQpmb3IgKGkgIGluIDE6bmNvbCh4ZW5vXzVfbWV0YWdlbmVzKSkgewogIG1ldGFnZW5lX21ldGFkYXRhID0geGVub181X21ldGFnZW5lc1ssaSxkcm9wPUZdCiAgeGVubyA9IEFkZE1ldGFEYXRhKG9iamVjdCA9IHhlbm8sbWV0YWRhdGEgPSBtZXRhZ2VuZV9tZXRhZGF0YSxjb2wubmFtZSA9IG5hbWVzKHhlbm9fNV9tZXRhZ2VuZXMpW2ldKQp9CgpGZWF0dXJlUGxvdChvYmplY3QgPSB4ZW5vLGZlYXR1cmVzID0gY29sbmFtZXMoeGVub181X21ldGFnZW5lcyksbmNvbCA9IDMpCgoKYGBgCiMgUHJvZ3JhbXMgZG90cGxvdApgYGB7ciBmaWcud2lkdGg9OH0KRG90UGxvdChvYmplY3QgPSB4ZW5vLCBmZWF0dXJlcyA9ICBjb2xuYW1lcyh4ZW5vXzVfbWV0YWdlbmVzKSxncm91cC5ieSAgPSAndHJlYXRtZW50JykKYGBgCgoKIyBOTUYgcHJvZ3JhbXMgcmVndWxhdGlvbiAgey50YWJzZXR9CmBgYHtyIGVjaG89VFJVRSwgIHJlc3VsdHM9J2FzaXMnfQptZXRhZ2VuZXNfbWVhbl9jb21wYXJlKGRhdGFzZXQgPSB4ZW5vLHRpbWUucG9pbnRfdmFyID0gInRyZWF0bWVudCIscHJlZml4ID0gIm1vZGVsIixwYXRpZW50LmlkZW50X3ZhciA9ICJvcmlnLmlkZW50IixwcmVfb24gPSBjKCJOVCIsIk9TSSIpLHRlc3QgPSAid2lsY294LnRlc3QiLHByb2dyYW1zID0gY29sbmFtZXMoYWxsX21ldGFnZW5lcylbMTo0XSkKYGBgCgojIFRvcCBwcm9ncmFtIDIgZ2VuZXMgZXhwcmVzc2lvbiBjb3JyZWxhdGlvbgpgYGB7cn0KdG9wX290ID0gZ2VwX3Njb3JlczVfeGVubyBbb3JkZXIoZ2VwX3Njb3JlczVfeGVubyBbLDJdLGRlY3JlYXNpbmcgPSBUKSwyLGRyb3AgPSBGXSU+JSBoZWFkKDIwMCkgJT4lIHJvd25hbWVzKCkKCm51bV9vZl9jbHVzdGVycyA9IDcKYW5ub3RhdGlvbiA9IHBsb3RfZ2VuZXNfY29yKGRhdGFzZXQgPSB4ZW5vLGhhbGxtYXJrX25hbWUgPSBOVUxMLG51bV9vZl9jbHVzdGVycyA9IG51bV9vZl9jbHVzdGVycyxnZW5lSWRzID0gdG9wX290KQoKYGBgCgojICBwcm9ncmFtIDIgYWxsIGNsdXN0ZXJzIGV4cHJlc3Npb24gey50YWJzZXR9CmBgYHtyIHJlc3VsdHM9J2FzaXMnLGZpZy53aWR0aD0xNH0KZm9yIChjaG9zZW5fY2x1c3RlcnMgaW4gMTpudW1fb2ZfY2x1c3RlcnMpIHsKICBjaG9zZW5fZ2VuZXMgPSBhbm5vdGF0aW9uW1sibXlhbm5vdGF0aW9uIl1dICU+JSBkcGx5cjo6ZmlsdGVyKGNsdXN0ZXIgPT0gY2hvc2VuX2NsdXN0ZXJzKSAlPiUgcm93bmFtZXMoKSAjdGFrZSByZWxldmFudCBnZW5lcwogICMgcHJpbnQoY2hvc2VuX2dlbmVzKQogIGh5cF9vYmogPC0gaHlwZVIoY2hvc2VuX2dlbmVzLCBnZW5lc2V0c19lbnYsIHRlc3QgPSAiaHlwZXJnZW9tZXRyaWMiLCBmZHI9MSwgcGxvdHRpbmc9RixiYWNrZ3JvdW5kID0gcm93bmFtZXMoeGVub181X2dlcF9zY29yZXMpKQoKICAgc2NvcmVzQW5kSW5kaWNlcyA8LSBnZXRQYXRod2F5U2NvcmVzKHhlbm9AYXNzYXlzJFJOQUBkYXRhLCBjaG9zZW5fZ2VuZXMpCiAgeGVubz1BZGRNZXRhRGF0YSh4ZW5vLHNjb3Jlc0FuZEluZGljZXMkcGF0aHdheVNjb3JlcyxwYXN0ZTAoImNsdXN0ZXIiLGNob3Nlbl9jbHVzdGVycykpCgogIAogIHByaW50X3RhYihwbHQgPSAKICAgICAgICAgICAgICBoeXBfZG90cyhoeXBfb2JqLHNpemVfYnkgPSAibm9uZSIsdGl0bGUgPSBwYXN0ZTAoImNsdXN0ZXIiLGNob3Nlbl9jbHVzdGVycykpKwogICAgICAgICAgICAgIEZlYXR1cmVQbG90KG9iamVjdCA9IHhlbm8sZmVhdHVyZXMgPSBwYXN0ZTAoImNsdXN0ZXIiLGNob3Nlbl9jbHVzdGVycykpLAogICAgICAgICAgICB0aXRsZSA9IGNob3Nlbl9jbHVzdGVycykKfQoKCmBgYAoKIyBDb3JyZWxhdGlvbiBvZiBjbHVzdGVycwpgYGB7cn0KZm9yIChjaG9zZW5fY2x1c3RlcnMgaW4gMTpudW1fb2ZfY2x1c3RlcnMpIHsKICAKICBjb3JfcmVzID0gY29yKHhlbm8kVE5GYSx4ZW5vW1twYXN0ZTAoImNsdXN0ZXIiLGNob3Nlbl9jbHVzdGVycyldXSkKcHJpbnQocGFzdGUoImNvcnJlbGF0aW9uIG9mIFRORmEgcHJvZ3JhbSB0byIsIHBhc3RlMCgiY2x1c3RlciIsY2hvc2VuX2NsdXN0ZXJzKSwiOiIsIGNvcl9yZXMpKQoKfQpgYGAKCgpgYGB7cn0KY2x1c3RlcnNfaWRlbnRzID0gYygiY2x1c3RlcjEiLCAiS0VHR19PWElEQVRJVkVfUEhPU1BIT1JZTEFUSU9OIiwiSEFMTE1BUktfVE5GQV9TSUdOQUxJTkdfVklBX05GS0IiLCJLRUdHX0FOVElHRU5fUFJPQ0VTU0lOR19BTkRfUFJFU0VOVEFUSU9OIiwiSEFMTE1BUktfUDUzX1BBVEhXQVkiLCJjbHVzdGVyNiIsImNsdXN0ZXI3IikKYGBgCgpgYGB7cn0KbWV0YWdlbmVzX21lYW5fY29tcGFyZSA8LSBmdW5jdGlvbihkYXRhc2V0LHRpbWUucG9pbnRfdmFyLHByZWZpeCA9ICIiLHBhdGllbnQuaWRlbnRfdmFyLHByZV9vbiA9IGMoIk9TSSIsIk5UIiksYXhpcy50ZXh0LnggPSAxMSx0ZXN0ID0gInQudGVzdCIsIHByb2dyYW1zID0gYygiSHlwb3hpYSIsIlRORmEiLCJDZWxsX2N5Y2xlIiksIHdpdGhfc3BsaXQgPSBULCB3aXRob3V0X3NwbGl0ID0gVCl7CiAgCiAgZm9yIChtZXRlZ2VuZSBpbiBwcm9ncmFtcykgewogICAgI2NyZWF0ZSBkYXRhOgogICAgZ2VuZXNfYnlfdHAgPSBGZXRjaERhdGEob2JqZWN0ID0gZGF0YXNldCx2YXJzID0gbWV0ZWdlbmUpICU+JSByb3dTdW1zKCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAjbWVhbiBleHByZXNzaW9uCiAgICBuYW1lcyhnZW5lc19ieV90cClbMV0gPSAiTWV0YWdlbmVfbWVhbiIKICAgIGdlbmVzX2J5X3RwID0gY2JpbmQoZ2VuZXNfYnlfdHAsRmV0Y2hEYXRhKG9iamVjdCA9IGRhdGFzZXQsdmFycyA9IGMocGF0aWVudC5pZGVudF92YXIsdGltZS5wb2ludF92YXIpKSkgIyBhZGQgaWQgYW5kIHRpbWUgcG9pbnRzCiAgICAKICAgIAogICAgZ2VuZXNfYnlfdHBfZm9yUGxvdCA9ICBnZW5lc19ieV90cCAlPiUgbXV0YXRlKCEhZW5zeW0ocGF0aWVudC5pZGVudF92YXIpIDo9IHBhc3RlKHByZWZpeCxnZW5lc19ieV90cFsscGF0aWVudC5pZGVudF92YXJdKSkgI2FkZCAibW9kZWwiIGJlZm9yZSAgZWFjaCBtb2RlbC9wYXRpZW50CiAgICBmbSA8LSBhcy5mb3JtdWxhKHBhc3RlKCJNZXRhZ2VuZV9tZWFuIiwgIn4iLCB0aW1lLnBvaW50X3ZhcikpICNtYWtlIGZvcm11bGEgdG8gcGxvdAogICAgCiAgICAjcGxvdCBhbmQgc3BsaXQgYnkgcGF0aWVudDogICAKICAgIHN0YXQudGVzdCA9IGNvbXBhcmVfbWVhbnMoZm9ybXVsYSA9IGZtICxkYXRhID0gZ2VuZXNfYnlfdHBfZm9yUGxvdCxtZXRob2QgPSB0ZXN0LGdyb3VwLmJ5ID0gcGF0aWVudC5pZGVudF92YXIpJT4lICMgQWRkIHBhaXJ3aXNlIGNvbXBhcmlzb25zIHAtdmFsdWUKICAgICAgZHBseXI6OmZpbHRlcihncm91cDEgPT0gcHJlX29uWzFdICYgZ3JvdXAyID09IHByZV9vblsyXSkgICNmaWx0ZXIgZm9yIHByZSB2cyBvbiB0cmVhdG1lbnQgb25seQogICAgCiAgICBwbHQgPSBnZ2JveHBsb3QoZ2VuZXNfYnlfdHBfZm9yUGxvdCwgeCA9IHRpbWUucG9pbnRfdmFyLCB5ID0gIk1ldGFnZW5lX21lYW4iLCBjb2xvciA9IHRpbWUucG9pbnRfdmFyKSArICNwbG90CiAgICAgIHN0YXRfcHZhbHVlX21hbnVhbChzdGF0LnRlc3QsIGxhYmVsID0gInAgPSB7cC5hZGp9IiwgICNhZGQgcCB2YWx1ZQogICAgICAgICAgICAgICAgICAgICAgICAgeS5wb3NpdGlvbiA9IG1heChnZW5lc19ieV90cF9mb3JQbG90JE1ldGFnZW5lX21lYW4pKSsgIyBzZXQgcG9zaXRpb24gYXQgdGhlIHRvcCB2YWx1ZQogICAgICBncmlkcygpKyAgCiAgICAgIHlsYWIocGFzdGUobWV0ZWdlbmUsIm1lYW4iKSkrCiAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KHNpemUgPSBheGlzLnRleHQueCkpKwogICAgICB5bGltKDAsIG1heChnZW5lc19ieV90cF9mb3JQbG90JE1ldGFnZW5lX21lYW4pKjEuMikgIyBleHRlbmQgeSBheGlzIHRvIHNob3cgcCB2YWx1ZQogICAgCiAgICBwbHQgPSBmYWNldChwbHQsIGZhY2V0LmJ5ID0gcGF0aWVudC5pZGVudF92YXIpICNzcGxpdCBieSBwYXRpZW50cwogICAgcHJpbnRfdGFiKHBsdCA9IHBsdCx0aXRsZSA9IGMobWV0ZWdlbmUsInBlciBwYXRpZW50IikpIAogICAgCiAgICAKICAgICNwbG90ID0gd2l0aG91dCBzcGxpdCBieSBwYXRpZW50OgogICAgaWYod2l0aG91dF9zcGxpdCl7CiAgICAgICAgICBzdGF0LnRlc3QgPSBjb21wYXJlX21lYW5zKGZvcm11bGEgPSBmbSAsZGF0YSA9IGdlbmVzX2J5X3RwX2ZvclBsb3QsY29tcGFyaXNvbnMgPSBteV9jb21wYXJpc29ucyxtZXRob2QgPSB0ZXN0KSU+JSAKICAgICAgZHBseXI6OmZpbHRlcihncm91cDEgPT0gcHJlX29uWzFdICYgZ3JvdXAyID09IHByZV9vblsyXSkgIyBBZGQgcGFpcndpc2UgY29tcGFyaXNvbnMgcC12YWx1ZQogICAgCiAgICBwbHQgPSBnZ2JveHBsb3QoZ2VuZXNfYnlfdHBfZm9yUGxvdCwgeCA9IHRpbWUucG9pbnRfdmFyLCB5ID0gIk1ldGFnZW5lX21lYW4iLCBjb2xvciA9IHRpbWUucG9pbnRfdmFyKSArCiAgICAgIHN0YXRfcHZhbHVlX21hbnVhbChzdGF0LnRlc3QsIGxhYmVsID0gInAgPSB7cC5hZGp9IiwgICNhZGQgcCB2YWx1ZQogICAgICAgICAgICAgICAgICAgICAgICAgeS5wb3NpdGlvbiA9IG1heChnZW5lc19ieV90cF9mb3JQbG90JE1ldGFnZW5lX21lYW4pKSsgIyBzZXQgcG9zaXRpb24gYXQgdGhlIHRvcCB2YWx1ZQogICAgICBncmlkcygpKyAgCiAgICAgIHlsYWIocGFzdGUobWV0ZWdlbmUsIm1lYW4iKSkrCiAgICAgIHlsaW0oMCwgbWF4KGdlbmVzX2J5X3RwX2ZvclBsb3QkTWV0YWdlbmVfbWVhbikqMS4yKSAjIGV4dGVuZCB5IGF4aXMgdG8gc2hvdyBwIHZhbHVlCiAgICAKICAgIAogICAgcHJpbnRfdGFiKHBsdCA9IHBsdCx0aXRsZSA9IG1ldGVnZW5lKQogICAgfQoKICB9CiAgCiAgCn0KYGBgCiMgIHByb2dyYW0gMiBpbnRlcnNlY3RlZCBnZW5lcwoKCgpgYGB7cn0KcHJvZ3JhbXNfb2ZfY2x1c3RlciA9IGMoKQpmb3IgKGNob3Nlbl9jbHVzdGVycyBpbiAxOm51bV9vZl9jbHVzdGVycykgewogIGNob3Nlbl9nZW5lcyA9IGFubm90YXRpb25bWyJteWFubm90YXRpb24iXV0gJT4lIGRwbHlyOjpmaWx0ZXIoY2x1c3RlciA9PSBjaG9zZW5fY2x1c3RlcnMpICU+JSByb3duYW1lcygpICN0YWtlIHJlbGV2YW50IGdlbmVzCiAgcGF0aHdheV9uYW1lID0gY2x1c3RlcnNfaWRlbnRzW2Nob3Nlbl9jbHVzdGVyc10KICBpZiAoIXN0YXJ0c1dpdGgoeCA9IHBhdGh3YXlfbmFtZSxwcmVmaXggPSAiY2x1c3RlciIpKXsKICAgICAgY2hvc2VuX2dlbmVzICA9IChjaG9zZW5fZ2VuZXMpICU+JSBpbnRlcnNlY3QoZ2VuZXNldHNbW3BhdGh3YXlfbmFtZV1dKQogICAgICBwYXRod2F5X25hbWUgPSBwYXN0ZTAocGF0aHdheV9uYW1lLCJfY2x1c3RlciIpCiAgfQogIHByb2dyYW1zX29mX2NsdXN0ZXIgPSBjKHByb2dyYW1zX29mX2NsdXN0ZXIscGF0aHdheV9uYW1lKQogIHByaW50KHBhdGh3YXlfbmFtZSkKICBwcmludChjaG9zZW5fZ2VuZXMpCiAgY2F0KCJcbiIpCiAgc2NvcmVzQW5kSW5kaWNlcyA8LSBnZXRQYXRod2F5U2NvcmVzKHhlbm9AYXNzYXlzJFJOQUBkYXRhLCBjaG9zZW5fZ2VuZXMpCiAgeGVubz1BZGRNZXRhRGF0YSh4ZW5vLHNjb3Jlc0FuZEluZGljZXMkcGF0aHdheVNjb3JlcyxwYXRod2F5X25hbWUpCgp9CmBgYAojICBwcm9ncmFtIDIgaW50ZXJzZWN0ZWQgcGF0aHdheSByZWd1bGF0aW9uIHsudGFic2V0fSAgICAgCgpgYGB7ciAgcmVzdWx0cz0nYXNpcyd9Cm1ldGFnZW5lc19tZWFuX2NvbXBhcmUoZGF0YXNldCA9IHhlbm8sdGltZS5wb2ludF92YXIgPSAidHJlYXRtZW50IixwcmVmaXggPSAibW9kZWwiLHBhdGllbnQuaWRlbnRfdmFyID0gIm9yaWcuaWRlbnQiLHByZV9vbiA9IGMoIk5UIiwiT1NJIiksdGVzdCA9ICJ3aWxjb3gudGVzdCIscHJvZ3JhbXMgPSBwcm9ncmFtc19vZl9jbHVzdGVyLHdpdGhvdXRfc3BsaXQgPSBGKQpgYGAKIyAgcHJvZ3JhbSAyIHNpZ25pZmljYW50IHBsb3QgIAoKYGBge3IgZmlnLmhlaWdodD0xMn0Kc2lnbmZfcGxvdF9wcmVfdnNfb248LSBmdW5jdGlvbihkYXRhc2V0LHByb2dyYW1zLHBhdGllbnQuaWRlbnRfdmFyLHByZWZpeCxwcmVfb24sdGVzdCx0aW1lLnBvaW50X3ZhcikgewogICAgZmluYWxfZGYgPSBkYXRhLmZyYW1lKCkKICAgIGZvciAobWV0ZWdlbmUgaW4gcHJvZ3JhbXMpIHsKICAgICAgZ2VuZXNfYnlfdHAgPSBGZXRjaERhdGEob2JqZWN0ID0gZGF0YXNldCx2YXJzID0gbWV0ZWdlbmUpICU+JSByb3dTdW1zKCkgJT4lIGFzLmRhdGEuZnJhbWUoKSAjbWVhbiBleHByZXNzaW9uCiAgICAgIG5hbWVzKGdlbmVzX2J5X3RwKVsxXSA9IG1ldGVnZW5lCiAgICAgIGdlbmVzX2J5X3RwID0gY2JpbmQoZ2VuZXNfYnlfdHAsRmV0Y2hEYXRhKG9iamVjdCA9IGRhdGFzZXQsdmFycyA9IGMocGF0aWVudC5pZGVudF92YXIsdGltZS5wb2ludF92YXIpKSkgIyBhZGQgaWQgYW5kIHRpbWUgcG9pbnRzCiAgICAgIAogICAgICAKICAgICAgZ2VuZXNfYnlfdHBfZm9yUGxvdCA9ICBnZW5lc19ieV90cCAlPiUgbXV0YXRlKCEhZW5zeW0ocGF0aWVudC5pZGVudF92YXIpIDo9IHBhc3RlKHByZWZpeCxnZW5lc19ieV90cFsscGF0aWVudC5pZGVudF92YXJdKSkgI2FkZCAibW9kZWwiIGJlZm9yZSAgZWFjaCBtb2RlbC9wYXRpZW50CiAgICAgIGZtIDwtIGFzLmZvcm11bGEocGFzdGUobWV0ZWdlbmUsICJ+IiwgdGltZS5wb2ludF92YXIpKSAjbWFrZSBmb3JtdWxhIHRvIHBsb3QKICAgICAgCiAgICAgICNwbG90IGFuZCBzcGxpdCBieSBwYXRpZW50OiAgIAogICAgICBzdGF0LnRlc3QgPSBjb21wYXJlX21lYW5zKGZvcm11bGEgPSBmbSAsZGF0YSA9IGdlbmVzX2J5X3RwX2ZvclBsb3QsbWV0aG9kID0gdGVzdCxncm91cC5ieSA9IHBhdGllbnQuaWRlbnRfdmFyKSU+JSAKICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKGdyb3VwMSA9PSBwcmVfb25bMV0gJiBncm91cDIgPT0gcHJlX29uWzJdKSAgI2ZpbHRlciBmb3IgcHJlIHZzIG9uIHRyZWF0bWVudCBvbmx5CiAgICAgIGZpbmFsX2RmID0gcmJpbmQoZmluYWxfZGYsc3RhdC50ZXN0KQogICAgfQogICAgcmV0dXJuKGZpbmFsX2RmKQp9Cgp1bmRlYnVnKHNpZ25mX3Bsb3RfcHJlX3ZzX29uKQpmaW5hbF9kZiA9IHNpZ25mX3Bsb3RfcHJlX3ZzX29uKGRhdGFzZXQgPSB4ZW5vLHRpbWUucG9pbnRfdmFyID0gInRyZWF0bWVudCIscHJlZml4ID0gIm1vZGVsIixwYXRpZW50LmlkZW50X3ZhciA9ICJvcmlnLmlkZW50IixwcmVfb24gPSBjKCJOVCIsIk9TSSIpLHRlc3QgPSAid2lsY294LnRlc3QiLHByb2dyYW1zID0gcHJvZ3JhbXNfb2ZfY2x1c3RlciApCmZpbmFsX2RmID0gcmVzaGFwZTI6OmRjYXN0KGZpbmFsX2RmLCBvcmlnLmlkZW50ICB+LnkuLHZhbHVlLnZhciA9ICJwLmFkaiIpICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoIm9yaWcuaWRlbnQiKQoKc2lnX2hlYXRtYXAoYWxsX3BhdGllbnRzX3Jlc3VsdCA9IGZpbmFsX2RmLHRpdGxlID0gImFkIikKYGBgCiMgVG9wIHByb2dyYW0gMyBnZW5lcyBleHByZXNzaW9uIGNvcnJlbGF0aW9uCmBgYHtyfQp0b3BfaHlwb3hpYSA9IGdlcF9zY29yZXM1X3hlbm8gW29yZGVyKGdlcF9zY29yZXM1X3hlbm8gWywzXSxkZWNyZWFzaW5nID0gVCksMixkcm9wID0gRl0lPiUgaGVhZCgyMDApICU+JSByb3duYW1lcygpCgpudW1fb2ZfY2x1c3RlcnMgPSA0CmFubm90YXRpb24gPSBwbG90X2dlbmVzX2NvcihkYXRhc2V0ID0geGVubyxoYWxsbWFya19uYW1lID0gTlVMTCxudW1fb2ZfY2x1c3RlcnMgPSBudW1fb2ZfY2x1c3RlcnMsZ2VuZUlkcyA9IHRvcF9oeXBveGlhKQoKYGBgCiMgIHByb2dyYW0gMyBhbGwgY2x1c3RlcnMgZXhwcmVzc2lvbiB7LnRhYnNldH0KCmBgYHtyIHJlc3VsdHM9J2FzaXMnLGZpZy53aWR0aD0xNH0KZm9yIChjaG9zZW5fY2x1c3RlcnMgaW4gMTpudW1fb2ZfY2x1c3RlcnMpIHsKICBjaG9zZW5fZ2VuZXMgPSBhbm5vdGF0aW9uW1sibXlhbm5vdGF0aW9uIl1dICU+JSBkcGx5cjo6ZmlsdGVyKGNsdXN0ZXIgPT0gY2hvc2VuX2NsdXN0ZXJzKSAlPiUgcm93bmFtZXMoKSAjdGFrZSByZWxldmFudCBnZW5lcwogICMgcHJpbnQoY2hvc2VuX2dlbmVzKQogIGh5cF9vYmogPC0gaHlwZVIoY2hvc2VuX2dlbmVzLCBnZW5lc2V0c19lbnYsIHRlc3QgPSAiaHlwZXJnZW9tZXRyaWMiLCBmZHI9MSwgcGxvdHRpbmc9RixiYWNrZ3JvdW5kID0gcm93bmFtZXMoeGVub181X2dlcF9zY29yZXMpKQoKICAgc2NvcmVzQW5kSW5kaWNlcyA8LSBnZXRQYXRod2F5U2NvcmVzKHhlbm9AYXNzYXlzJFJOQUBkYXRhLCBjaG9zZW5fZ2VuZXMpCiAgeGVubz1BZGRNZXRhRGF0YSh4ZW5vLHNjb3Jlc0FuZEluZGljZXMkcGF0aHdheVNjb3JlcyxwYXN0ZTAoImNsdXN0ZXIiLGNob3Nlbl9jbHVzdGVycykpCgogIAogIHByaW50X3RhYihwbHQgPSAKICAgICAgICAgICAgICBoeXBfZG90cyhoeXBfb2JqLHNpemVfYnkgPSAibm9uZSIsdGl0bGUgPSBwYXN0ZTAoImNsdXN0ZXIiLGNob3Nlbl9jbHVzdGVycykpKwogICAgICAgICAgICAgIEZlYXR1cmVQbG90KG9iamVjdCA9IHhlbm8sZmVhdHVyZXMgPSBwYXN0ZTAoImNsdXN0ZXIiLGNob3Nlbl9jbHVzdGVycykpLAogICAgICAgICAgICB0aXRsZSA9IGNob3Nlbl9jbHVzdGVycykKCgp9CgoKYGBgCgojIENvcnJlbGF0aW9uIG9mIGNsdXN0ZXJzCmBgYHtyfQpmb3IgKGNob3Nlbl9jbHVzdGVycyBpbiAxOm51bV9vZl9jbHVzdGVycykgewogIAogIGNvcl9yZXMgPSBjb3IoeGVubyRoeXBveGlhLHhlbm9bW3Bhc3RlMCgiY2x1c3RlciIsY2hvc2VuX2NsdXN0ZXJzKV1dKQpwcmludChwYXN0ZSgiY29ycmVsYXRpb24gb2YgaHlwb3hpYSBwcm9ncmFtIHRvIiwgcGFzdGUwKCJjbHVzdGVyIixjaG9zZW5fY2x1c3RlcnMpLCI6IiwgY29yX3JlcykpCgp9CmBgYAoKYGBge3J9CmNsdXN0ZXJzX2lkZW50cyA9IGMoIkhBTExNQVJLX0hZUE9YSUEiLCAiSElGX3RhcmdldHMiLCJjbHVzdGVyMyIsImNsdXN0ZXI0IikKYGBgCgpgYGB7cn0KcHJvZ3JhbXNfb2ZfY2x1c3RlciA9IGMoKQpmb3IgKGNob3Nlbl9jbHVzdGVycyBpbiAxOm51bV9vZl9jbHVzdGVycykgewogIGNob3Nlbl9nZW5lcyA9IGFubm90YXRpb25bWyJteWFubm90YXRpb24iXV0gJT4lIGRwbHlyOjpmaWx0ZXIoY2x1c3RlciA9PSBjaG9zZW5fY2x1c3RlcnMpICU+JSByb3duYW1lcygpICN0YWtlIHJlbGV2YW50IGdlbmVzCiAgcGF0aHdheV9uYW1lID0gY2x1c3RlcnNfaWRlbnRzW2Nob3Nlbl9jbHVzdGVyc10KICBpZiAoIXN0YXJ0c1dpdGgoeCA9IHBhdGh3YXlfbmFtZSxwcmVmaXggPSAiY2x1c3RlciIpKXsKICAgICAgY2hvc2VuX2dlbmVzICA9IChjaG9zZW5fZ2VuZXMpICU+JSBpbnRlcnNlY3QoZ2VuZXNldHNbW3BhdGh3YXlfbmFtZV1dKQogICAgICBwYXRod2F5X25hbWUgPSBwYXN0ZTAocGF0aHdheV9uYW1lLCJfY2x1c3RlciIpCiAgfQogIHByb2dyYW1zX29mX2NsdXN0ZXIgPSBjKHByb2dyYW1zX29mX2NsdXN0ZXIscGF0aHdheV9uYW1lKQogIHByaW50KHBhdGh3YXlfbmFtZSkKICBwcmludChjaG9zZW5fZ2VuZXMpCiAgY2F0KCJcbiIpCiAgc2NvcmVzQW5kSW5kaWNlcyA8LSBnZXRQYXRod2F5U2NvcmVzKHhlbm9AYXNzYXlzJFJOQUBkYXRhLCBjaG9zZW5fZ2VuZXMpCiAgeGVubz1BZGRNZXRhRGF0YSh4ZW5vLHNjb3Jlc0FuZEluZGljZXMkcGF0aHdheVNjb3JlcyxwYXRod2F5X25hbWUpCgp9CmBgYAojICBwcm9ncmFtIDMgaW50ZXJzZWN0ZWQgcGF0aHdheSByZWd1bGF0aW9uIHsudGFic2V0fSAgICAgCgpgYGB7ciByZXN1bHRzPSdhc2lzJ30KbWV0YWdlbmVzX21lYW5fY29tcGFyZShkYXRhc2V0ID0geGVubyx0aW1lLnBvaW50X3ZhciA9ICJ0cmVhdG1lbnQiLHByZWZpeCA9ICJtb2RlbCIscGF0aWVudC5pZGVudF92YXIgPSAib3JpZy5pZGVudCIscHJlX29uID0gYygiTlQiLCJPU0kiKSx0ZXN0ID0gIndpbGNveC50ZXN0Iixwcm9ncmFtcyA9IHByb2dyYW1zX29mX2NsdXN0ZXIsd2l0aG91dF9zcGxpdCA9IEYpCmBgYAoKIyBUb3AgcHJvZ3JhbSAzIGdlbmVzIGV4cHJlc3Npb24gY29ycmVsYXRpb24KCmBgYHtyfQp0b3BfY2MgPSBnZXBfc2NvcmVzNV94ZW5vIFtvcmRlcihnZXBfc2NvcmVzNV94ZW5vIFssNF0sZGVjcmVhc2luZyA9IFQpLDIsZHJvcCA9IEZdJT4lIGhlYWQoMjAwKSAlPiUgcm93bmFtZXMoKQoKbnVtX29mX2NsdXN0ZXJzID0gNAphbm5vdGF0aW9uID0gcGxvdF9nZW5lc19jb3IoZGF0YXNldCA9IHhlbm8saGFsbG1hcmtfbmFtZSA9IE5VTEwsbnVtX29mX2NsdXN0ZXJzID0gbnVtX29mX2NsdXN0ZXJzLGdlbmVJZHMgPSB0b3BfY2MpCgpgYGAKIyAgcHJvZ3JhbSAzIGFsbCBjbHVzdGVycyBleHByZXNzaW9uIHsudGFic2V0fQoKYGBge3IgcmVzdWx0cz0nYXNpcycsZmlnLndpZHRoPTE0fQpmb3IgKGNob3Nlbl9jbHVzdGVycyBpbiAxOm51bV9vZl9jbHVzdGVycykgewogIGNob3Nlbl9nZW5lcyA9IGFubm90YXRpb25bWyJteWFubm90YXRpb24iXV0gJT4lIGRwbHlyOjpmaWx0ZXIoY2x1c3RlciA9PSBjaG9zZW5fY2x1c3RlcnMpICU+JSByb3duYW1lcygpICN0YWtlIHJlbGV2YW50IGdlbmVzCiAgIyBwcmludChjaG9zZW5fZ2VuZXMpCiAgaHlwX29iaiA8LSBoeXBlUihjaG9zZW5fZ2VuZXMsIGdlbmVzZXRzX2VudiwgdGVzdCA9ICJoeXBlcmdlb21ldHJpYyIsIGZkcj0xLCBwbG90dGluZz1GLGJhY2tncm91bmQgPSByb3duYW1lcyh4ZW5vXzVfZ2VwX3Njb3JlcykpCgogICBzY29yZXNBbmRJbmRpY2VzIDwtIGdldFBhdGh3YXlTY29yZXMoeGVub0Bhc3NheXMkUk5BQGRhdGEsIGNob3Nlbl9nZW5lcykKICB4ZW5vPUFkZE1ldGFEYXRhKHhlbm8sc2NvcmVzQW5kSW5kaWNlcyRwYXRod2F5U2NvcmVzLHBhc3RlMCgiY2x1c3RlciIsY2hvc2VuX2NsdXN0ZXJzKSkKCiAgCiAgcHJpbnRfdGFiKHBsdCA9IAogICAgICAgICAgICAgIGh5cF9kb3RzKGh5cF9vYmosc2l6ZV9ieSA9ICJub25lIix0aXRsZSA9IHBhc3RlMCgiY2x1c3RlciIsY2hvc2VuX2NsdXN0ZXJzKSkrCiAgICAgICAgICAgICAgRmVhdHVyZVBsb3Qob2JqZWN0ID0geGVubyxmZWF0dXJlcyA9IHBhc3RlMCgiY2x1c3RlciIsY2hvc2VuX2NsdXN0ZXJzKSksCiAgICAgICAgICAgIHRpdGxlID0gY2hvc2VuX2NsdXN0ZXJzKQogIAp9CgoKYGBgCgo8c2NyaXB0IHNyYz0iaHR0cHM6Ly9oeXBvdGhlcy5pcy9lbWJlZC5qcyIgYXN5bmM+PC9zY3JpcHQ+CgoKCg==