1 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")

2 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()

3 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

4 NMF usage

5 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]

6 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")

7 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

8 Programs dotplot

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

9 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

10 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

11 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

12 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)
    }

  }
  
  
}

13 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" 

14 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

15 program 2 significant plot

16 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

17 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

18 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"      

19 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

20 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

21 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==