1 Data

lung = readRDS("./Data/lung_cancercells_withTP_onlyPatients.rds")
lung_patients = lung$patient.ident %>% unique() %>% as.character()
lung_patients_filtered = lung_patients[!(lung_patients %in% c("X1055new","X1099"))] # remove patients with less than 100 malignant cells
lung = subset(x = lung,subset = patient.ident %in% lung_patients_filtered)
suffix ="xeno_genes_normalized_0-5sigma_2-7theta"
by_expression = T #calculate metagenes by multiplie expression in genes coef, or by cnmf usage
from cnmf import cNMF
suffix = r.suffix
import pickle
f = open('./Data/cnmf/cnmf_objects/patients_' + suffix + '_cnmf_obj.pckl', 'rb')
cnmf_obj = pickle.load(f)
f.close()

2 Functions

library(stringi)
library(reticulate)
source_from_github(repositoy = "DEG_functions",version = "0.2.24")
ℹ SHA-1 hash of file is a183df1c565702ecd8ed338bb2abfb0e13415d8e
source_from_github(repositoy = "cNMF_functions",version = "0.3.88",script_name = "cnmf_function_Harmony.R") 
ℹ SHA-1 hash of file is 3e4ca171702a700577edf6399c789fdf505397c2

3 K selection plot

4 gep scores for all NMF k’s

density_threshold = 0.1
usage_norm, gep_scores3, gep_tpm, topgenes = cnmf_obj.load_results(K=3, density_threshold=density_threshold)
usage_norm, gep_scores4, gep_tpm, topgenes = cnmf_obj.load_results(K=4, density_threshold=density_threshold)
usage_norm, gep_scores5, gep_tpm, topgenes = cnmf_obj.load_results(K=5, density_threshold=density_threshold)
usage_norm, gep_scores6, gep_tpm, topgenes = cnmf_obj.load_results(K=6, density_threshold=density_threshold)
usage_norm, gep_scores7, gep_tpm, topgenes = cnmf_obj.load_results(K=7, density_threshold=density_threshold)
usage_norm, gep_scores8, gep_tpm, topgenes = cnmf_obj.load_results(K=8, density_threshold=density_threshold)
usage_norm, gep_scores9, gep_tpm, topgenes = cnmf_obj.load_results(K=9, density_threshold=density_threshold)

5 Enrichment analysis by top 200 genes of each program

gep_scores3 = py$gep_scores3
gep_scores4 = py$gep_scores4
gep_scores5 = py$gep_scores5
gep_scores6 = py$gep_scores6
gep_scores7 = py$gep_scores7
gep_scores8 = py$gep_scores8
gep_scores9 = py$gep_scores9

all_gep_scores =  list(gep_scores3 = gep_scores3, gep_scores4 = gep_scores4, gep_scores5 = gep_scores5, gep_scores6 = gep_scores6, gep_scores7= gep_scores7, gep_scores8 = gep_scores8, gep_scores9 = gep_scores9)
# canonical_pathways = msigdbr(species = "Homo sapiens", category = "C2") %>% dplyr::filter(gs_subcat != "CGP") %>%  dplyr::distinct(gs_name, gene_symbol) 
for (gep_name in names(all_gep_scores)) {
  gep_scores = all_gep_scores[[gep_name]]
  top_genes_num = 200
  plt_list = list()
  for (i in 1:ncol(gep_scores)) {
    top_genes = gep_scores  %>%  arrange(desc(gep_scores[i])) #sort by score a
    top = head(rownames(top_genes),top_genes_num) #take top top_genes_num
    res = genes_vec_enrichment(genes = top,background = rownames(gep_scores),homer = T,title = 
                      names(gep_scores)[i],silent = T,return_all = T,custom_pathways = NULL )
     
    plt_list[[i]] = res$plt
  }
  print_tab(plt =   ggarrange(plotlist = plt_list),
            title = gep_name)
}

gep_scores3

Warning in grSoftVersion() : unable to load shared object ‘/usr/local/lib/R/modules//R_X11.so’: libXt.so.6: cannot open shared object file: No such file or directory

gep_scores4

gep_scores5

gep_scores6

gep_scores7

gep_scores8

gep_scores9

NA

6 Chosen K

selected_k = 8
density_threshold = 0.1
cnmf_obj.consensus(k=selected_k, density_threshold=density_threshold,show_clustering=True)
usage_norm, gep_scores, gep_tpm, topgenes = cnmf_obj.load_results(K=selected_k, density_threshold=density_threshold)
gep_scores = py$gep_scores

# canonical_pathways = msigdbr(species = "Homo sapiens", category = "C2") %>% dplyr::filter(gs_subcat != "CGP") %>%  dplyr::distinct(gs_name, gene_symbol) 

top_genes_num = 200
plt_list = list()
for (i in 1:ncol(gep_scores)) {
  top_genes = gep_scores  %>%  arrange(desc(gep_scores[i])) #sort by score a
  top = head(rownames(top_genes),top_genes_num) #take top top_genes_num
  res = genes_vec_enrichment(genes = top,background = rownames(gep_scores),homer = T,title = 
                    names(gep_scores)[i],silent = T,return_all = T,custom_pathways = NULL )
   
  plt_list[[i]] = res$plt
}
gridExtra::grid.arrange(grobs = plt_list)

7 Correlation of programs

gep_scores = py$gep_scores
cor_res = cor(gep_scores)
breaks <- c(seq(-1,1,by=0.01))
colors <- c("blue",colorRampPalette(colors = c("blue", "white", "red"))
                                                 (n = length(colors)-3), "red")

pheatmap(cor_res,color = colors,breaks = breaks)

8 correlation by top 150 combined

all_top= c()
for (i in 1:ncol(gep_scores)) {
  top_genes = gep_scores  %>%  arrange(desc(gep_scores[i])) #sort by score a
  top = head(rownames(top_genes),150) #take top top_genes_num
all_top = c(all_top,top)
}
gep_scores_top = gep_scores[all_top,]
cor_res = cor(gep_scores_top)
pheatmap(cor_res)

9 Combine similar programs

gep_scores = py$gep_scores
# groups_list = list(c(4,5,3),c(1),c(2),c(6))
# groups_list = list(c(4,5,6),c(1),c(2),c(3),c(7),c(9),c(8))
# groups_list = list(c(4,5,7),c(1),c(2),c(3),c(7))
groups_list = list(c(4,3,6),c(1),c(2),c(5),c(7),c(8))

gep_scores = union_programs(groups_list = groups_list,all_metagenes = gep_scores)
top_genes_num = 200
plt_list = list()
for (i in 1:ncol(gep_scores)) {
  top_genes = gep_scores  %>%  arrange(desc(gep_scores[i])) #sort by score a
  top = head(rownames(top_genes),top_genes_num) #take top top_genes_num
  res = genes_vec_enrichment(genes = top,background = rownames(gep_scores),homer = T,title = 
                     names(gep_scores)[i],silent = T,return_all = T)
   
  plt_list[[i]] = res$plt
}
gridExtra::grid.arrange(grobs = plt_list)

10 Calculate usage

# get expression with genes in cnmf input
lung = FindVariableFeatures(object = lung,nfeatures = 2000)

Calculating gene variances 0% 10 20 30 40 50 60 70 80 90 100% [—-|—-|—-|—-|—-|—-|—-|—-|—-|—-| **************************************************| Calculating feature variances of standardized and clipped values 0% 10 20 30 40 50 60 70 80 90 100% [—-|—-|—-|—-|—-|—-|—-|—-|—-|—-| **************************************************|

genes = rownames(lung)[rownames(lung) %in% VariableFeatures(object = xeno)[1:2000]]

lung_expression = t(as.matrix(GetAssayData(lung,slot='data'))) 
lung_expression = 2**lung_expression #convert from log2(tpm+1) to tpm
lung_expression = lung_expression-1
lung_expression = lung_expression[,genes] %>% as.data.frame()

all_0_genes = colnames(lung_expression)[colSums(lung_expression==0, na.rm=TRUE)==nrow(lung_expression)] #delete rows that have all 0
genes = genes[!genes %in% all_0_genes]
lung_expression = lung_expression[,!colnames(lung_expression) %in% all_0_genes]
gc()
         used    (Mb) gc trigger    (Mb)   max used    (Mb)

Ncells 11889314 635.0 20979452 1120.5 17004190 908.2 Vcells 1641096627 12520.6 2627951469 20049.7 1997200248 15237.5

def get_usage_from_score(counts,tpm, genes,cnmf_obj,k, sumTo1 = True):
      import anndata as ad
      import scanpy as sc
      import numpy as np
      from sklearn.decomposition import non_negative_factorization
      import pandas as pd
      counts_adata = ad.AnnData(counts)
      tpm_adata = ad.AnnData(tpm)
      norm_counts = get_norm_counts(counts=counts_adata,tpm=tpm_adata,high_variance_genes_filter=np.array(genes)) #norm counts like cnmf
      spectra = cnmf_obj.get_median_spectra(k=k) #get score 
      spectra = spectra[spectra.columns.intersection(genes)] #remove genes not in @genes
      spectra = spectra.T.reindex(norm_counts.to_df().columns).T #reorder spectra genes like norm_counts
      
      usage_by_calc,_,_ = non_negative_factorization(X=norm_counts.X, H = spectra.values, update_H=False,n_components = k,max_iter=1000,init ='random')
      usage_by_calc = pd.DataFrame(usage_by_calc, index=counts.index, columns=spectra.index) #insert to df+add names
      if(sumTo1):
          usage_by_calc = usage_by_calc.div(usage_by_calc.sum(axis=1), axis=0) # sum rows to 1 and assign to main df
      usage_by_calc_sumTo1 = usage_by_calc.div(usage_by_calc.sum(axis=1), axis=0) # sum rows to 1
      reorder = usage_by_calc_sumTo1.sum(axis=0).sort_values(ascending=False) #reorder after sum to 1
      usage_by_calc = usage_by_calc.loc[:, reorder.index]
      return(usage_by_calc)

lung_expression = r.lung_expression
genes = r.genes
gep_scores = r.gep_scores
usage_by_calc = get_usage_from_score(counts=lung_expression,tpm=lung_expression,genes=genes,cnmf_obj=cnmf_obj,k=selected_k,sumTo1=False)
/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.
usage_by_calc2 =usage_by_calc %>% rename(gep4.3.6 = cell_cycle)
Error in `chr_as_locations()`:
! Can't rename columns that don't exist.
✖ Column `cell_cycle` doesn't exist.
Backtrace:
  1. usage_by_calc %>% rename(gep4.3.6 = cell_cycle)
  3. dplyr:::rename.data.frame(., gep4.3.6 = cell_cycle)
  4. tidyselect::eval_rename(expr(c(...)), .data)
  5. tidyselect:::rename_impl(...)
  6. tidyselect:::eval_select_impl(...)
     ...
 18. tidyselect:::reduce_sels(node, data_mask, context_mask, init = init)
 19. tidyselect:::walk_data_tree(new, data_mask, context_mask)
 20. tidyselect:::as_indices_sel_impl(...)
 21. tidyselect:::as_indices_impl(x, vars, call = call, strict = strict)
 22. tidyselect:::chr_as_locations(x, vars, call = call)

11 Usgage UMAP

all_metagenes= usage_by_calc

#add each metagene to metadata
for (i in 1:ncol(all_metagenes)) {
  metage_metadata = all_metagenes %>% dplyr::select(i)
  lung = AddMetaData(object = lung,metadata = metage_metadata)
}
FeaturePlot(object = lung,features = colnames(all_metagenes),max.cutoff = 70)

12 Assignment

larger_by = 1.25
lung = program_assignment(dataset = lung,larger_by = larger_by,program_names = colnames(all_metagenes))
p = cell_percentage(dataset = lung,time.point_var = "time.point",by_program = T)
print_tab(plt = p,title = "by program")
p = cell_percentage(dataset = lung,time.point_var = "time.point",by_tp = T,x_order = NULL)
print_tab(plt = p,title = "by timepoint")

12.1 program.assignment

# colors =  rainbow(ncol(all_metagenes))
# fc <- colorRampPalette(c("lightgreen", "darkgreen"))
# greens = fc(4)
# colors[1] = "blue"
# colors[2:3] = greens[1:2]
# colors[4] = "red"
# colors[5:6] = greens[3:4]
# colors = c(colors,"grey")
# DimPlot(lung,group.by = "program.assignment",pt.size = 0.5,cols =colors)


# colors =  rainbow(ncol(all_metagenes))
# colors = c(colors,"grey")
# DimPlot(lung,group.by = "program.assignment",pt.size = 0.5,cols =colors)

DimPlot(lung,group.by = "program.assignment",pt.size = 0.5)
DimPlot(lung,group.by = "patient.ident",pt.size = 0.5)
DimPlot(lung,group.by = "time.point",pt.size = 0.5)

13 Score regulation

metagenes_mean_compare(dataset = lung, time.point_var = "time.point",prefix = "patient",patient.ident_var = "patient.ident",pre_on = c("pre-treatment","on-treatment"),axis.text.x = 8,programs = c("hypoxia_like","interferon_like","cell_cycle"))

hypoxia_like per patient

hypoxia_like

interferon_like per patient

interferon_like

cell_cycle per patient

cell_cycle

NA

 df  = FetchData(object = lung,vars = c("program.assignment","time.point")) %>% 
    mutate(program.assignment = if_else(!(program.assignment %in% "metagene.3"), 
                       "not_hypoxia", 
                       program.assignment)) %>% 
    filter (time.point %in% c("pre-treatment","on-treatment")) %>% 
    droplevels() 
  test = fisher.test(table(df))
    
  library(ggstatsplot)
print(
    ggbarstats(
    df, program.assignment, time.point,
    results.subtitle = FALSE,
    subtitle = paste0(
      "Fisher's exact test", ", p-value = ",
      ifelse(test$p.value < 0.001, "< 0.001", round(test$p.value, 3))
    )
  )
)

14 per patient fisher test

patients_vector = lung$patient.ident %>% unique()
for (patient_name in patients_vector) {
  df  = FetchData(object = lung,vars = c("program.assignment","patient.ident","time.point")) %>% 
    filter (patient.ident == patient_name) %>% 
    filter (program.assignment %in% c("metagene.1","metagene.2")) %>% 
    filter (time.point %in% c("pre-treatment","on-treatment")) %>% 
    select(-patient.ident) %>% 
    droplevels() 
  test = fisher.test(table(df))
    
  library(ggstatsplot)
print(
    ggbarstats(
    df, program.assignment, time.point,
    results.subtitle = FALSE,
    subtitle = paste0(
      "Fisher's exact test", ", p-value = ",
      ifelse(test$p.value < 0.001, "< 0.001", round(test$p.value, 3))
    ),title = patient_name
  )
)
}
LS0tCnRpdGxlOiAnYHIgcnN0dWRpb2FwaTo6Z2V0U291cmNlRWRpdG9yQ29udGV4dCgpJHBhdGggJT4lIGJhc2VuYW1lKCkgJT4lIGdzdWIocGF0dGVybiA9ICJcXC5SbWQiLHJlcGxhY2VtZW50ID0gIiIpYCcgCmF1dGhvcjogIkF2aXNoYWkgV2l6ZWwiCmRhdGU6ICdgciBTeXMudGltZSgpYCcKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdG9jOiB5ZXMKICAgIHRvY19jb2xsYXBzZTogeWVzCiAgICB0b2NfZmxvYXQ6IAogICAgICBjb2xsYXBzZWQ6IEZBTFNFCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvY19kZXB0aDogMQotLS0KCiMgRGF0YQpgYGB7cn0KbHVuZyA9IHJlYWRSRFMoIi4vRGF0YS9sdW5nX2NhbmNlcmNlbGxzX3dpdGhUUF9vbmx5UGF0aWVudHMucmRzIikKbHVuZ19wYXRpZW50cyA9IGx1bmckcGF0aWVudC5pZGVudCAlPiUgdW5pcXVlKCkgJT4lIGFzLmNoYXJhY3RlcigpCmx1bmdfcGF0aWVudHNfZmlsdGVyZWQgPSBsdW5nX3BhdGllbnRzWyEobHVuZ19wYXRpZW50cyAlaW4lIGMoIlgxMDU1bmV3IiwiWDEwOTkiKSldICMgcmVtb3ZlIHBhdGllbnRzIHdpdGggbGVzcyB0aGFuIDEwMCBtYWxpZ25hbnQgY2VsbHMKbHVuZyA9IHN1YnNldCh4ID0gbHVuZyxzdWJzZXQgPSBwYXRpZW50LmlkZW50ICVpbiUgbHVuZ19wYXRpZW50c19maWx0ZXJlZCkKYGBgCgpgYGB7cn0Kc3VmZml4ID0ieGVub19nZW5lc19ub3JtYWxpemVkXzAtNXNpZ21hXzItN3RoZXRhIgpieV9leHByZXNzaW9uID0gVCAjY2FsY3VsYXRlIG1ldGFnZW5lcyBieSBtdWx0aXBsaWUgZXhwcmVzc2lvbiBpbiBnZW5lcyBjb2VmLCBvciBieSBjbm1mIHVzYWdlCmBgYAoKYGBge3B5dGhvbn0KZnJvbSBjbm1mIGltcG9ydCBjTk1GCnN1ZmZpeCA9IHIuc3VmZml4CmltcG9ydCBwaWNrbGUKZiA9IG9wZW4oJy4vRGF0YS9jbm1mL2NubWZfb2JqZWN0cy9wYXRpZW50c18nICsgc3VmZml4ICsgJ19jbm1mX29iai5wY2tsJywgJ3JiJykKY25tZl9vYmogPSBwaWNrbGUubG9hZChmKQpmLmNsb3NlKCkKYGBgCgoKIyBGdW5jdGlvbnMKCmBgYHtyIH0KbGlicmFyeShzdHJpbmdpKQpsaWJyYXJ5KHJldGljdWxhdGUpCnNvdXJjZV9mcm9tX2dpdGh1YihyZXBvc2l0b3kgPSAiREVHX2Z1bmN0aW9ucyIsdmVyc2lvbiA9ICIwLjIuMjQiKQpzb3VyY2VfZnJvbV9naXRodWIocmVwb3NpdG95ID0gImNOTUZfZnVuY3Rpb25zIix2ZXJzaW9uID0gIjAuMy44OCIsc2NyaXB0X25hbWUgPSAiY25tZl9mdW5jdGlvbl9IYXJtb255LlIiKSAKYGBgCgojIEsgc2VsZWN0aW9uIHBsb3QKYGBge3J9CnBsb3RfcGF0aCA9IHBhc3RlMCgiL3NjaS9sYWJzL3lvdGFtZC9sYWJfc2hhcmUvYXZpc2hhaS53aXplbC9SX3Byb2plY3RzL0VHRlIvRGF0YS9jbm1mL2NOTUZfcGF0aWVudHNfVmFybm9ybV9IYXJtb255XyIsc3VmZml4LCIvY05NRl9wYXRpZW50c19WYXJub3JtX0hhcm1vbnlfIixzdWZmaXgsIi5rX3NlbGVjdGlvbi5wbmciKQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwbG90X3BhdGgpCmBgYAoKCgojIGdlcCBzY29yZXMgZm9yIGFsbCBOTUYgaydzCmBgYHtweXRob259CmRlbnNpdHlfdGhyZXNob2xkID0gMC4xCnVzYWdlX25vcm0sIGdlcF9zY29yZXMzLCBnZXBfdHBtLCB0b3BnZW5lcyA9IGNubWZfb2JqLmxvYWRfcmVzdWx0cyhLPTMsIGRlbnNpdHlfdGhyZXNob2xkPWRlbnNpdHlfdGhyZXNob2xkKQp1c2FnZV9ub3JtLCBnZXBfc2NvcmVzNCwgZ2VwX3RwbSwgdG9wZ2VuZXMgPSBjbm1mX29iai5sb2FkX3Jlc3VsdHMoSz00LCBkZW5zaXR5X3RocmVzaG9sZD1kZW5zaXR5X3RocmVzaG9sZCkKdXNhZ2Vfbm9ybSwgZ2VwX3Njb3JlczUsIGdlcF90cG0sIHRvcGdlbmVzID0gY25tZl9vYmoubG9hZF9yZXN1bHRzKEs9NSwgZGVuc2l0eV90aHJlc2hvbGQ9ZGVuc2l0eV90aHJlc2hvbGQpCnVzYWdlX25vcm0sIGdlcF9zY29yZXM2LCBnZXBfdHBtLCB0b3BnZW5lcyA9IGNubWZfb2JqLmxvYWRfcmVzdWx0cyhLPTYsIGRlbnNpdHlfdGhyZXNob2xkPWRlbnNpdHlfdGhyZXNob2xkKQp1c2FnZV9ub3JtLCBnZXBfc2NvcmVzNywgZ2VwX3RwbSwgdG9wZ2VuZXMgPSBjbm1mX29iai5sb2FkX3Jlc3VsdHMoSz03LCBkZW5zaXR5X3RocmVzaG9sZD1kZW5zaXR5X3RocmVzaG9sZCkKdXNhZ2Vfbm9ybSwgZ2VwX3Njb3JlczgsIGdlcF90cG0sIHRvcGdlbmVzID0gY25tZl9vYmoubG9hZF9yZXN1bHRzKEs9OCwgZGVuc2l0eV90aHJlc2hvbGQ9ZGVuc2l0eV90aHJlc2hvbGQpCnVzYWdlX25vcm0sIGdlcF9zY29yZXM5LCBnZXBfdHBtLCB0b3BnZW5lcyA9IGNubWZfb2JqLmxvYWRfcmVzdWx0cyhLPTksIGRlbnNpdHlfdGhyZXNob2xkPWRlbnNpdHlfdGhyZXNob2xkKQoKYGBgCgoKIyBFbnJpY2htZW50IGFuYWx5c2lzIGJ5IHRvcCAyMDAgZ2VuZXMgb2YgZWFjaCBwcm9ncmFtIHsudGFic2V0fQpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04LCByZXN1bHRzPSdhc2lzJ30KZ2VwX3Njb3JlczMgPSBweSRnZXBfc2NvcmVzMwpnZXBfc2NvcmVzNCA9IHB5JGdlcF9zY29yZXM0CmdlcF9zY29yZXM1ID0gcHkkZ2VwX3Njb3JlczUKZ2VwX3Njb3JlczYgPSBweSRnZXBfc2NvcmVzNgpnZXBfc2NvcmVzNyA9IHB5JGdlcF9zY29yZXM3CmdlcF9zY29yZXM4ID0gcHkkZ2VwX3Njb3JlczgKZ2VwX3Njb3JlczkgPSBweSRnZXBfc2NvcmVzOQoKYWxsX2dlcF9zY29yZXMgPSAgbGlzdChnZXBfc2NvcmVzMyA9IGdlcF9zY29yZXMzLCBnZXBfc2NvcmVzNCA9IGdlcF9zY29yZXM0LCBnZXBfc2NvcmVzNSA9IGdlcF9zY29yZXM1LCBnZXBfc2NvcmVzNiA9IGdlcF9zY29yZXM2LCBnZXBfc2NvcmVzNz0gZ2VwX3Njb3JlczcsIGdlcF9zY29yZXM4ID0gZ2VwX3Njb3JlczgsIGdlcF9zY29yZXM5ID0gZ2VwX3Njb3JlczkpCiMgY2Fub25pY2FsX3BhdGh3YXlzID0gbXNpZ2RicihzcGVjaWVzID0gIkhvbW8gc2FwaWVucyIsIGNhdGVnb3J5ID0gIkMyIikgJT4lIGRwbHlyOjpmaWx0ZXIoZ3Nfc3ViY2F0ICE9ICJDR1AiKSAlPiUgIGRwbHlyOjpkaXN0aW5jdChnc19uYW1lLCBnZW5lX3N5bWJvbCkgCmZvciAoZ2VwX25hbWUgaW4gbmFtZXMoYWxsX2dlcF9zY29yZXMpKSB7CiAgZ2VwX3Njb3JlcyA9IGFsbF9nZXBfc2NvcmVzW1tnZXBfbmFtZV1dCiAgdG9wX2dlbmVzX251bSA9IDIwMAogIHBsdF9saXN0ID0gbGlzdCgpCiAgZm9yIChpIGluIDE6bmNvbChnZXBfc2NvcmVzKSkgewogICAgdG9wX2dlbmVzID0gZ2VwX3Njb3JlcyAgJT4lICBhcnJhbmdlKGRlc2MoZ2VwX3Njb3Jlc1tpXSkpICNzb3J0IGJ5IHNjb3JlIGEKICAgIHRvcCA9IGhlYWQocm93bmFtZXModG9wX2dlbmVzKSx0b3BfZ2VuZXNfbnVtKSAjdGFrZSB0b3AgdG9wX2dlbmVzX251bQogICAgcmVzID0gZ2VuZXNfdmVjX2VucmljaG1lbnQoZ2VuZXMgPSB0b3AsYmFja2dyb3VuZCA9IHJvd25hbWVzKGdlcF9zY29yZXMpLGhvbWVyID0gVCx0aXRsZSA9IAogICAgICAgICAgICAgICAgICAgICAgbmFtZXMoZ2VwX3Njb3JlcylbaV0sc2lsZW50ID0gVCxyZXR1cm5fYWxsID0gVCxjdXN0b21fcGF0aHdheXMgPSBOVUxMICkKICAgICAKICAgIHBsdF9saXN0W1tpXV0gPSByZXMkcGx0CiAgfQogIHByaW50X3RhYihwbHQgPSAgIGdnYXJyYW5nZShwbG90bGlzdCA9IHBsdF9saXN0KSwKICAgICAgICAgICAgdGl0bGUgPSBnZXBfbmFtZSkKfQpgYGAKCiMgQ2hvc2VuIEsKYGBge3B5dGhvbn0Kc2VsZWN0ZWRfayA9IDgKcHJpbnQoInNlbGVjdGVkIGsgPSAiLHNlbGVjdGVkX2spCmRlbnNpdHlfdGhyZXNob2xkID0gMC4xCmNubWZfb2JqLmNvbnNlbnN1cyhrPXNlbGVjdGVkX2ssIGRlbnNpdHlfdGhyZXNob2xkPWRlbnNpdHlfdGhyZXNob2xkLHNob3dfY2x1c3RlcmluZz1UcnVlKQp1c2FnZV9ub3JtLCBnZXBfc2NvcmVzLCBnZXBfdHBtLCB0b3BnZW5lcyA9IGNubWZfb2JqLmxvYWRfcmVzdWx0cyhLPXNlbGVjdGVkX2ssIGRlbnNpdHlfdGhyZXNob2xkPWRlbnNpdHlfdGhyZXNob2xkKQpgYGAKCgpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04LCByZXN1bHRzPSdoaWRlJ30KZ2VwX3Njb3JlcyA9IHB5JGdlcF9zY29yZXMKCiMgY2Fub25pY2FsX3BhdGh3YXlzID0gbXNpZ2RicihzcGVjaWVzID0gIkhvbW8gc2FwaWVucyIsIGNhdGVnb3J5ID0gIkMyIikgJT4lIGRwbHlyOjpmaWx0ZXIoZ3Nfc3ViY2F0ICE9ICJDR1AiKSAlPiUgIGRwbHlyOjpkaXN0aW5jdChnc19uYW1lLCBnZW5lX3N5bWJvbCkgCgp0b3BfZ2VuZXNfbnVtID0gMjAwCnBsdF9saXN0ID0gbGlzdCgpCmZvciAoaSBpbiAxOm5jb2woZ2VwX3Njb3JlcykpIHsKICB0b3BfZ2VuZXMgPSBnZXBfc2NvcmVzICAlPiUgIGFycmFuZ2UoZGVzYyhnZXBfc2NvcmVzW2ldKSkgI3NvcnQgYnkgc2NvcmUgYQogIHRvcCA9IGhlYWQocm93bmFtZXModG9wX2dlbmVzKSx0b3BfZ2VuZXNfbnVtKSAjdGFrZSB0b3AgdG9wX2dlbmVzX251bQogIHJlcyA9IGdlbmVzX3ZlY19lbnJpY2htZW50KGdlbmVzID0gdG9wLGJhY2tncm91bmQgPSByb3duYW1lcyhnZXBfc2NvcmVzKSxob21lciA9IFQsdGl0bGUgPSAKICAgICAgICAgICAgICAgICAgICBuYW1lcyhnZXBfc2NvcmVzKVtpXSxzaWxlbnQgPSBULHJldHVybl9hbGwgPSBULGN1c3RvbV9wYXRod2F5cyA9IE5VTEwgKQogICAKICBwbHRfbGlzdFtbaV1dID0gcmVzJHBsdAp9CmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKGdyb2JzID0gcGx0X2xpc3QpCmBgYAoKIyBDb3JyZWxhdGlvbiBvZiBwcm9ncmFtcwoKYGBge3J9CmdlcF9zY29yZXMgPSBweSRnZXBfc2NvcmVzCmNvcl9yZXMgPSBjb3IoZ2VwX3Njb3JlcykKYnJlYWtzIDwtIGMoc2VxKC0xLDEsYnk9MC4wMSkpCmNvbG9ycyA8LSBjKCJibHVlIixjb2xvclJhbXBQYWxldHRlKGNvbG9ycyA9IGMoImJsdWUiLCAid2hpdGUiLCAicmVkIikpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAobiA9IGxlbmd0aChjb2xvcnMpLTMpLCAicmVkIikKCnBoZWF0bWFwKGNvcl9yZXMsY29sb3IgPSBjb2xvcnMsYnJlYWtzID0gYnJlYWtzKQpgYGAKIyBjb3JyZWxhdGlvbiBieSB0b3AgMTUwIGNvbWJpbmVkCmBgYHtyfQphbGxfdG9wPSBjKCkKZm9yIChpIGluIDE6bmNvbChnZXBfc2NvcmVzKSkgewogIHRvcF9nZW5lcyA9IGdlcF9zY29yZXMgICU+JSAgYXJyYW5nZShkZXNjKGdlcF9zY29yZXNbaV0pKSAjc29ydCBieSBzY29yZSBhCiAgdG9wID0gaGVhZChyb3duYW1lcyh0b3BfZ2VuZXMpLDE1MCkgI3Rha2UgdG9wIHRvcF9nZW5lc19udW0KYWxsX3RvcCA9IGMoYWxsX3RvcCx0b3ApCn0KZ2VwX3Njb3Jlc190b3AgPSBnZXBfc2NvcmVzW2FsbF90b3AsXQpjb3JfcmVzID0gY29yKGdlcF9zY29yZXNfdG9wKQpwaGVhdG1hcChjb3JfcmVzKQpgYGAKCiMgQ29tYmluZSBzaW1pbGFyIHByb2dyYW1zCmBgYHtyfQpnZXBfc2NvcmVzID0gcHkkZ2VwX3Njb3JlcwojIGdyb3Vwc19saXN0ID0gbGlzdChjKDQsNSwzKSxjKDEpLGMoMiksYyg2KSkKIyBncm91cHNfbGlzdCA9IGxpc3QoYyg0LDUsNiksYygxKSxjKDIpLGMoMyksYyg3KSxjKDkpLGMoOCkpCiMgZ3JvdXBzX2xpc3QgPSBsaXN0KGMoNCw1LDcpLGMoMSksYygyKSxjKDMpLGMoNykpCmdyb3Vwc19saXN0ID0gbGlzdChjKDQsMyw2KSxjKDEpLGMoMiksYyg1KSxjKDcpLGMoOCkpCgpnZXBfc2NvcmVzID0gdW5pb25fcHJvZ3JhbXMoZ3JvdXBzX2xpc3QgPSBncm91cHNfbGlzdCxhbGxfbWV0YWdlbmVzID0gZ2VwX3Njb3JlcykKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04LCByZXN1bHRzPSdoaWRlJ30KdG9wX2dlbmVzX251bSA9IDIwMApwbHRfbGlzdCA9IGxpc3QoKQpmb3IgKGkgaW4gMTpuY29sKGdlcF9zY29yZXMpKSB7CiAgdG9wX2dlbmVzID0gZ2VwX3Njb3JlcyAgJT4lICBhcnJhbmdlKGRlc2MoZ2VwX3Njb3Jlc1tpXSkpICNzb3J0IGJ5IHNjb3JlIGEKICB0b3AgPSBoZWFkKHJvd25hbWVzKHRvcF9nZW5lcyksdG9wX2dlbmVzX251bSkgI3Rha2UgdG9wIHRvcF9nZW5lc19udW0KICByZXMgPSBnZW5lc192ZWNfZW5yaWNobWVudChnZW5lcyA9IHRvcCxiYWNrZ3JvdW5kID0gcm93bmFtZXMoZ2VwX3Njb3JlcyksaG9tZXIgPSBULHRpdGxlID0gCiAgICAgICAgICAgICAgICAgICAgIG5hbWVzKGdlcF9zY29yZXMpW2ldLHNpbGVudCA9IFQscmV0dXJuX2FsbCA9IFQpCiAgIAogIHBsdF9saXN0W1tpXV0gPSByZXMkcGx0Cn0KZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UoZ3JvYnMgPSBwbHRfbGlzdCkKYGBgCgojIENhbGN1bGF0ZSB1c2FnZQpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2FzaXMnfQojIGdldCBleHByZXNzaW9uIHdpdGggZ2VuZXMgaW4gY25tZiBpbnB1dApsdW5nID0gRmluZFZhcmlhYmxlRmVhdHVyZXMob2JqZWN0ID0gbHVuZyxuZmVhdHVyZXMgPSAyMDAwKQpnZW5lcyA9IHJvd25hbWVzKGx1bmcpW3Jvd25hbWVzKGx1bmcpICVpbiUgVmFyaWFibGVGZWF0dXJlcyhvYmplY3QgPSB4ZW5vKVsxOjIwMDBdXQoKbHVuZ19leHByZXNzaW9uID0gdChhcy5tYXRyaXgoR2V0QXNzYXlEYXRhKGx1bmcsc2xvdD0nZGF0YScpKSkgCmx1bmdfZXhwcmVzc2lvbiA9IDIqKmx1bmdfZXhwcmVzc2lvbiAjY29udmVydCBmcm9tIGxvZzIodHBtKzEpIHRvIHRwbQpsdW5nX2V4cHJlc3Npb24gPSBsdW5nX2V4cHJlc3Npb24tMQpsdW5nX2V4cHJlc3Npb24gPSBsdW5nX2V4cHJlc3Npb25bLGdlbmVzXSAlPiUgYXMuZGF0YS5mcmFtZSgpCgphbGxfMF9nZW5lcyA9IGNvbG5hbWVzKGx1bmdfZXhwcmVzc2lvbilbY29sU3VtcyhsdW5nX2V4cHJlc3Npb249PTAsIG5hLnJtPVRSVUUpPT1ucm93KGx1bmdfZXhwcmVzc2lvbildICNkZWxldGUgcm93cyB0aGF0IGhhdmUgYWxsIDAKZ2VuZXMgPSBnZW5lc1shZ2VuZXMgJWluJSBhbGxfMF9nZW5lc10KbHVuZ19leHByZXNzaW9uID0gbHVuZ19leHByZXNzaW9uWywhY29sbmFtZXMobHVuZ19leHByZXNzaW9uKSAlaW4lIGFsbF8wX2dlbmVzXQpnYygpCmBgYApgYGB7cHl0aG9ufQpkZWYgZ2V0X3VzYWdlX2Zyb21fc2NvcmUoY291bnRzLHRwbSwgZ2VuZXMsY25tZl9vYmosaywgc3VtVG8xID0gVHJ1ZSk6CiAgICAgIGltcG9ydCBhbm5kYXRhIGFzIGFkCiAgICAgIGltcG9ydCBzY2FucHkgYXMgc2MKICAgICAgaW1wb3J0IG51bXB5IGFzIG5wCiAgICAgIGZyb20gc2tsZWFybi5kZWNvbXBvc2l0aW9uIGltcG9ydCBub25fbmVnYXRpdmVfZmFjdG9yaXphdGlvbgogICAgICBpbXBvcnQgcGFuZGFzIGFzIHBkCiAgICAgIGNvdW50c19hZGF0YSA9IGFkLkFubkRhdGEoY291bnRzKQogICAgICB0cG1fYWRhdGEgPSBhZC5Bbm5EYXRhKHRwbSkKICAgICAgbm9ybV9jb3VudHMgPSBnZXRfbm9ybV9jb3VudHMoY291bnRzPWNvdW50c19hZGF0YSx0cG09dHBtX2FkYXRhLGhpZ2hfdmFyaWFuY2VfZ2VuZXNfZmlsdGVyPW5wLmFycmF5KGdlbmVzKSkgI25vcm0gY291bnRzIGxpa2UgY25tZgogICAgICBzcGVjdHJhID0gY25tZl9vYmouZ2V0X21lZGlhbl9zcGVjdHJhKGs9aykgI2dldCBzY29yZSAKICAgICAgc3BlY3RyYSA9IHNwZWN0cmFbc3BlY3RyYS5jb2x1bW5zLmludGVyc2VjdGlvbihnZW5lcyldICNyZW1vdmUgZ2VuZXMgbm90IGluIEBnZW5lcwogICAgICBzcGVjdHJhID0gc3BlY3RyYS5ULnJlaW5kZXgobm9ybV9jb3VudHMudG9fZGYoKS5jb2x1bW5zKS5UICNyZW9yZGVyIHNwZWN0cmEgZ2VuZXMgbGlrZSBub3JtX2NvdW50cwogICAgICAKICAgICAgdXNhZ2VfYnlfY2FsYyxfLF8gPSBub25fbmVnYXRpdmVfZmFjdG9yaXphdGlvbihYPW5vcm1fY291bnRzLlgsIEggPSBzcGVjdHJhLnZhbHVlcywgdXBkYXRlX0g9RmFsc2Usbl9jb21wb25lbnRzID0gayxtYXhfaXRlcj0xMDAwLGluaXQgPSdyYW5kb20nKQogICAgICB1c2FnZV9ieV9jYWxjID0gcGQuRGF0YUZyYW1lKHVzYWdlX2J5X2NhbGMsIGluZGV4PWNvdW50cy5pbmRleCwgY29sdW1ucz1zcGVjdHJhLmluZGV4KSAjaW5zZXJ0IHRvIGRmK2FkZCBuYW1lcwogICAgICBpZihzdW1UbzEpOgogICAgICAgICAgdXNhZ2VfYnlfY2FsYyA9IHVzYWdlX2J5X2NhbGMuZGl2KHVzYWdlX2J5X2NhbGMuc3VtKGF4aXM9MSksIGF4aXM9MCkgIyBzdW0gcm93cyB0byAxIGFuZCBhc3NpZ24gdG8gbWFpbiBkZgogICAgICB1c2FnZV9ieV9jYWxjX3N1bVRvMSA9IHVzYWdlX2J5X2NhbGMuZGl2KHVzYWdlX2J5X2NhbGMuc3VtKGF4aXM9MSksIGF4aXM9MCkgIyBzdW0gcm93cyB0byAxCiAgICAgIHJlb3JkZXIgPSB1c2FnZV9ieV9jYWxjX3N1bVRvMS5zdW0oYXhpcz0wKS5zb3J0X3ZhbHVlcyhhc2NlbmRpbmc9RmFsc2UpICNyZW9yZGVyIGFmdGVyIHN1bSB0byAxCiAgICAgIHVzYWdlX2J5X2NhbGMgPSB1c2FnZV9ieV9jYWxjLmxvY1s6LCByZW9yZGVyLmluZGV4XQogICAgICByZXR1cm4odXNhZ2VfYnlfY2FsYykKCmBgYAoKCmBgYHtweXRob259CgpsdW5nX2V4cHJlc3Npb24gPSByLmx1bmdfZXhwcmVzc2lvbgpnZW5lcyA9IHIuZ2VuZXMKZ2VwX3Njb3JlcyA9IHIuZ2VwX3Njb3Jlcwp1c2FnZV9ieV9jYWxjID0gZ2V0X3VzYWdlX2Zyb21fc2NvcmUoY291bnRzPWx1bmdfZXhwcmVzc2lvbix0cG09bHVuZ19leHByZXNzaW9uLGdlbmVzPWdlbmVzLGNubWZfb2JqPWNubWZfb2JqLGs9c2VsZWN0ZWRfayxzdW1UbzE9RmFsc2UpCmBgYAoKCmBgYHtyfQpncm91cHNfbGlzdCA9IGxpc3QoYyg0LDMsNiksYygxKSxjKDIpLGMoNSksYyg3KSxjKDgpKQp1c2FnZV9ieV9jYWxjID0gdW5pb25fcHJvZ3JhbXMoZ3JvdXBzX2xpc3QgPSBncm91cHNfbGlzdCxhbGxfbWV0YWdlbmVzID0gdXNhZ2VfYnlfY2FsYykKdXNhZ2VfYnlfY2FsYyA9dXNhZ2VfYnlfY2FsYyAlPiUgcmVuYW1lKGNlbGxfY3ljbGUgPSBnZXA0LjMuNiwgaHlwb3hpYV9saWtlID0gZ2VwMiwgaW50ZXJmZXJvbl9saWtlID0gZ2VwMSApCmBgYAoKIyBVc2dhZ2UgVU1BUApgYGB7ciBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTJ9CmFsbF9tZXRhZ2VuZXM9IHVzYWdlX2J5X2NhbGMKCiNhZGQgZWFjaCBtZXRhZ2VuZSB0byBtZXRhZGF0YQpmb3IgKGkgaW4gMTpuY29sKGFsbF9tZXRhZ2VuZXMpKSB7CiAgbWV0YWdlX21ldGFkYXRhID0gYWxsX21ldGFnZW5lcyAlPiUgZHBseXI6OnNlbGVjdChpKQogIGx1bmcgPSBBZGRNZXRhRGF0YShvYmplY3QgPSBsdW5nLG1ldGFkYXRhID0gbWV0YWdlX21ldGFkYXRhKQp9CkZlYXR1cmVQbG90KG9iamVjdCA9IGx1bmcsZmVhdHVyZXMgPSBjb2xuYW1lcyhhbGxfbWV0YWdlbmVzKSxtYXguY3V0b2ZmID0gNzApCgpgYGAKCgoKCgoKCgoKCgoKCgoKCiMgQXNzaWdubWVudCAKYGBge3J9Cmxhcmdlcl9ieSA9IDEuMjUKbHVuZyA9IHByb2dyYW1fYXNzaWdubWVudChkYXRhc2V0ID0gbHVuZyxsYXJnZXJfYnkgPSBsYXJnZXJfYnkscHJvZ3JhbV9uYW1lcyA9IGNvbG5hbWVzKGFsbF9tZXRhZ2VuZXMpKQpgYGAgICAKCgoKCmBgYHtyIGVjaG89VFJVRSwgZmlnLndpZHRoPTEwLCByZXN1bHRzPSdhc2lzJ30KcCA9IGNlbGxfcGVyY2VudGFnZShkYXRhc2V0ID0gbHVuZyx0aW1lLnBvaW50X3ZhciA9ICJ0aW1lLnBvaW50IixieV9wcm9ncmFtID0gVCkKcHJpbnRfdGFiKHBsdCA9IHAsdGl0bGUgPSAiYnkgcHJvZ3JhbSIpCnAgPSBjZWxsX3BlcmNlbnRhZ2UoZGF0YXNldCA9IGx1bmcsdGltZS5wb2ludF92YXIgPSAidGltZS5wb2ludCIsYnlfdHAgPSBULHhfb3JkZXIgPSBOVUxMKQpwcmludF90YWIocGx0ID0gcCx0aXRsZSA9ICJieSB0aW1lcG9pbnQiKQoKCmBgYAoKIyMgcHJvZ3JhbS5hc3NpZ25tZW50CmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEwfQojIGNvbG9ycyA9ICByYWluYm93KG5jb2woYWxsX21ldGFnZW5lcykpCiMgZmMgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJsaWdodGdyZWVuIiwgImRhcmtncmVlbiIpKQojIGdyZWVucyA9IGZjKDQpCiMgY29sb3JzWzFdID0gImJsdWUiCiMgY29sb3JzWzI6M10gPSBncmVlbnNbMToyXQojIGNvbG9yc1s0XSA9ICJyZWQiCiMgY29sb3JzWzU6Nl0gPSBncmVlbnNbMzo0XQojIGNvbG9ycyA9IGMoY29sb3JzLCJncmV5IikKIyBEaW1QbG90KGx1bmcsZ3JvdXAuYnkgPSAicHJvZ3JhbS5hc3NpZ25tZW50IixwdC5zaXplID0gMC41LGNvbHMgPWNvbG9ycykKCgojIGNvbG9ycyA9ICByYWluYm93KG5jb2woYWxsX21ldGFnZW5lcykpCiMgY29sb3JzID0gYyhjb2xvcnMsImdyZXkiKQojIERpbVBsb3QobHVuZyxncm91cC5ieSA9ICJwcm9ncmFtLmFzc2lnbm1lbnQiLHB0LnNpemUgPSAwLjUsY29scyA9Y29sb3JzKQoKRGltUGxvdChsdW5nLGdyb3VwLmJ5ID0gInByb2dyYW0uYXNzaWdubWVudCIscHQuc2l6ZSA9IDAuNSkKRGltUGxvdChsdW5nLGdyb3VwLmJ5ID0gInBhdGllbnQuaWRlbnQiLHB0LnNpemUgPSAwLjUpCkRpbVBsb3QobHVuZyxncm91cC5ieSA9ICJ0aW1lLnBvaW50IixwdC5zaXplID0gMC41KQpgYGAKCgojIFNjb3JlIHJlZ3VsYXRpb24gey50YWJzZXR9CgpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2FzaXMnfQptZXRhZ2VuZXNfbWVhbl9jb21wYXJlKGRhdGFzZXQgPSBsdW5nLCB0aW1lLnBvaW50X3ZhciA9ICJ0aW1lLnBvaW50IixwcmVmaXggPSAicGF0aWVudCIscGF0aWVudC5pZGVudF92YXIgPSAicGF0aWVudC5pZGVudCIscHJlX29uID0gYygicHJlLXRyZWF0bWVudCIsIm9uLXRyZWF0bWVudCIpLGF4aXMudGV4dC54ID0gOCxwcm9ncmFtcyA9IGMoImh5cG94aWFfbGlrZSIsImludGVyZmVyb25fbGlrZSIsImNlbGxfY3ljbGUiKSkKYGBgCgoKCgoKYGBge3J9CiBkZiAgPSBGZXRjaERhdGEob2JqZWN0ID0gbHVuZyx2YXJzID0gYygicHJvZ3JhbS5hc3NpZ25tZW50IiwidGltZS5wb2ludCIpKSAlPiUgCiAgICBtdXRhdGUocHJvZ3JhbS5hc3NpZ25tZW50ID0gaWZfZWxzZSghKHByb2dyYW0uYXNzaWdubWVudCAlaW4lICJtZXRhZ2VuZS4zIiksIAogICAgICAgICAgICAgICAgICAgICAgICJub3RfaHlwb3hpYSIsIAogICAgICAgICAgICAgICAgICAgICAgIHByb2dyYW0uYXNzaWdubWVudCkpICU+JSAKICAgIGZpbHRlciAodGltZS5wb2ludCAlaW4lIGMoInByZS10cmVhdG1lbnQiLCJvbi10cmVhdG1lbnQiKSkgJT4lIAogICAgZHJvcGxldmVscygpIAogIHRlc3QgPSBmaXNoZXIudGVzdCh0YWJsZShkZikpCiAgICAKICBsaWJyYXJ5KGdnc3RhdHNwbG90KQpwcmludCgKICAgIGdnYmFyc3RhdHMoCiAgICBkZiwgcHJvZ3JhbS5hc3NpZ25tZW50LCB0aW1lLnBvaW50LAogICAgcmVzdWx0cy5zdWJ0aXRsZSA9IEZBTFNFLAogICAgc3VidGl0bGUgPSBwYXN0ZTAoCiAgICAgICJGaXNoZXIncyBleGFjdCB0ZXN0IiwgIiwgcC12YWx1ZSA9ICIsCiAgICAgIGlmZWxzZSh0ZXN0JHAudmFsdWUgPCAwLjAwMSwgIjwgMC4wMDEiLCByb3VuZCh0ZXN0JHAudmFsdWUsIDMpKQogICAgKQogICkKKQpgYGAKCgojIHBlciBwYXRpZW50IGZpc2hlciB0ZXN0CgpgYGB7cn0KcGF0aWVudHNfdmVjdG9yID0gbHVuZyRwYXRpZW50LmlkZW50ICU+JSB1bmlxdWUoKQpmb3IgKHBhdGllbnRfbmFtZSBpbiBwYXRpZW50c192ZWN0b3IpIHsKICBkZiAgPSBGZXRjaERhdGEob2JqZWN0ID0gbHVuZyx2YXJzID0gYygicHJvZ3JhbS5hc3NpZ25tZW50IiwicGF0aWVudC5pZGVudCIsInRpbWUucG9pbnQiKSkgJT4lIAogICAgZmlsdGVyIChwYXRpZW50LmlkZW50ID09IHBhdGllbnRfbmFtZSkgJT4lIAogICAgZmlsdGVyIChwcm9ncmFtLmFzc2lnbm1lbnQgJWluJSBjKCJtZXRhZ2VuZS4xIiwibWV0YWdlbmUuMiIpKSAlPiUgCiAgICBmaWx0ZXIgKHRpbWUucG9pbnQgJWluJSBjKCJwcmUtdHJlYXRtZW50Iiwib24tdHJlYXRtZW50IikpICU+JSAKICAgIHNlbGVjdCgtcGF0aWVudC5pZGVudCkgJT4lIAogICAgZHJvcGxldmVscygpIAogIHRlc3QgPSBmaXNoZXIudGVzdCh0YWJsZShkZikpCiAgICAKICBsaWJyYXJ5KGdnc3RhdHNwbG90KQpwcmludCgKICAgIGdnYmFyc3RhdHMoCiAgICBkZiwgcHJvZ3JhbS5hc3NpZ25tZW50LCB0aW1lLnBvaW50LAogICAgcmVzdWx0cy5zdWJ0aXRsZSA9IEZBTFNFLAogICAgc3VidGl0bGUgPSBwYXN0ZTAoCiAgICAgICJGaXNoZXIncyBleGFjdCB0ZXN0IiwgIiwgcC12YWx1ZSA9ICIsCiAgICAgIGlmZWxzZSh0ZXN0JHAudmFsdWUgPCAwLjAwMSwgIjwgMC4wMDEiLCByb3VuZCh0ZXN0JHAudmFsdWUsIDMpKQogICAgKSx0aXRsZSA9IHBhdGllbnRfbmFtZQogICkKKQp9CmBgYAoKCgoKCgo=