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

Functions

library(stringi)
library(reticulate)
source_from_github(repositoy = "DEG_functions",version = "0.2.24")
source_from_github(repositoy = "cNMF_functions",version = "0.3.84",script_name = "cnmf_function_Harmony.R") 

K selection plot

selected_k = 8
suffix = paste(suffix,paste0(selected_k,"nmfK"),sep="_")
print(suffix)
[1] "xeno_genes_normalized_0-5sigma_2-7theta_8nmfK"
selected_k = int(r.selected_k)
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)
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)

Enrichment analysis by top 200 genes of each program

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_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_scores5 {.unnumbered }

|————————————————–| |==================================================| ## gep_scores6 {.unnumbered }

gep_scores7

|————————————————–| |==================================================| ## gep_scores8 {.unnumbered }

gep_scores9

NA

Correlation of programs

gep_scores = py$gep_scores
cor_res = cor(gep_scores)
pheatmap(cor_res)

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)

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)

Calculate usage


lung = FindVariableFeatures(object = lung,nfeatures = 2000)
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()
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
if not (gep_scores is None):
  spectra = gep_scores
  print ("a")
else:
  print ("b")
  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
usage_by_calc = usage_by_calc.div(usage_by_calc.sum(axis=1), axis=0) # sum rows to 1 
def get_usage_from_score(counts,tpm, genes,cnmf_obj,k):
    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
    usage_by_calc = usage_by_calc.div(usage_by_calc.sum(axis=1), axis=0) # sum rows to 1 
    reorder = usage_by_calc.sum(axis=0).sort_values(ascending=False)
    usage_by_calc = usage_by_calc.loc[:, reorder.index]
    return(usage_by_calc)
usage_by_calc = get_usage_from_score(counts=lung_expression,tpm=lung_expression,genes=genes,cnmf_obj=cnmf_obj,k=9)
/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_calc = py$usage_by_calc

Usgage UMAP

all_metagenes= usage_by_calc
# Make metagene names
for (i in 1:ncol(all_metagenes)) {
  colnames(all_metagenes)[i] = "metagene." %>% paste0(i)
}
# names (all_metagenes) = c("Hypoxia","TNFa","Cell_cycle")

#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)
}
Note: Using an external vector in selections is ambiguous.
ℹ Use `all_of(i)` instead of `i` to silence this message.
ℹ See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
This message is displayed once per session.
FeaturePlot(object = lung,features = colnames(all_metagenes),max.cutoff = 70)
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

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

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

by timepoint

NA

program.assignment

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 = "metagene.3")

metagene.3 per patient

metagene.3

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)
Registered S3 method overwritten by 'parameters':
  method                         from      
  format.parameters_distribution datawizard
You can cite this package as:
     Patil, I. (2021). Visualizations with statistical details: The 'ggstatsplot' approach.
     Journal of Open Source Software, 6(61), 3167, doi:10.21105/joss.03167
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))
    )
  )
)

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
  )
)
}
patients_vector = lung$patient.ident %>% unique()
for (patient_name in patients_vector) {
  patient_data = subset(x = lung, subset = patient.ident == patient_name)
  cell_percentage(dataset = patient_data,time.point_var = "time.point")

}
  
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGNvZGVfZm9sZGluZzogaGlkZQplZGl0b3Jfb3B0aW9uczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGlubGluZQotLS0KCiMgRGF0YQpgYGB7cn0KbHVuZyA9IHJlYWRSRFMoIi4vRGF0YS9sdW5nX2NhbmNlcmNlbGxzX3dpdGhUUF9vbmx5UGF0aWVudHMucmRzIikKbHVuZ19wYXRpZW50cyA9IGx1bmckcGF0aWVudC5pZGVudCAlPiUgdW5pcXVlKCkgJT4lIGFzLmNoYXJhY3RlcigpCmx1bmdfcGF0aWVudHNfZmlsdGVyZWQgPSBsdW5nX3BhdGllbnRzWyEobHVuZ19wYXRpZW50cyAlaW4lIGMoIlgxMDU1bmV3IiwiWDEwOTkiKSldICMgcmVtb3ZlIHBhdGllbnRzIHdpdGggbGVzcyB0aGFuIDEwMCBtYWxpZ25hbnQgY2VsbHMKbHVuZyA9IHN1YnNldCh4ID0gbHVuZyxzdWJzZXQgPSBwYXRpZW50LmlkZW50ICVpbiUgbHVuZ19wYXRpZW50c19maWx0ZXJlZCkKYGBgCgpgYGB7cn0Kc3VmZml4ID0ieGVub19nZW5lc19ub3JtYWxpemVkXzAtNXNpZ21hXzItN3RoZXRhIgpieV9leHByZXNzaW9uID0gVCAjY2FsY3VsYXRlIG1ldGFnZW5lcyBieSBtdWx0aXBsaWUgZXhwcmVzc2lvbiBpbiBnZW5lcyBjb2VmLCBvciBieSBjbm1mIHVzYWdlCmBgYAoKYGBge3B5dGhvbn0KZnJvbSBjbm1mIGltcG9ydCBjTk1GCnN1ZmZpeCA9IHIuc3VmZml4CmltcG9ydCBwaWNrbGUKZiA9IG9wZW4oJy4vRGF0YS9jbm1mL2NubWZfb2JqZWN0cy9wYXRpZW50c18nICsgc3VmZml4ICsgJ19jbm1mX29iai5wY2tsJywgJ3JiJykKY25tZl9vYmogPSBwaWNrbGUubG9hZChmKQpmLmNsb3NlKCkKYGBgCgoKIyBGdW5jdGlvbnMKCmBgYHtyIH0KbGlicmFyeShzdHJpbmdpKQpsaWJyYXJ5KHJldGljdWxhdGUpCnNvdXJjZV9mcm9tX2dpdGh1YihyZXBvc2l0b3kgPSAiREVHX2Z1bmN0aW9ucyIsdmVyc2lvbiA9ICIwLjIuMjQiKQpzb3VyY2VfZnJvbV9naXRodWIocmVwb3NpdG95ID0gImNOTUZfZnVuY3Rpb25zIix2ZXJzaW9uID0gIjAuMy44NCIsc2NyaXB0X25hbWUgPSAiY25tZl9mdW5jdGlvbl9IYXJtb255LlIiKSAKYGBgCgojIEsgc2VsZWN0aW9uIHBsb3QKYGBge3J9CnBsb3RfcGF0aCA9IHBhc3RlMCgiL3NjaS9sYWJzL3lvdGFtZC9sYWJfc2hhcmUvYXZpc2hhaS53aXplbC9SX3Byb2plY3RzL0VHRlIvRGF0YS9jbm1mL2NOTUZfcGF0aWVudHNfVmFybm9ybV9IYXJtb255XyIsc3VmZml4LCIvY05NRl9wYXRpZW50c19WYXJub3JtX0hhcm1vbnlfIixzdWZmaXgsIi5rX3NlbGVjdGlvbi5wbmciKQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhwbG90X3BhdGgpCmBgYAoKYGBge3J9CnNlbGVjdGVkX2sgPSA4CnN1ZmZpeCA9IHBhc3RlKHN1ZmZpeCxwYXN0ZTAoc2VsZWN0ZWRfaywibm1mSyIpLHNlcD0iXyIpCnByaW50KHN1ZmZpeCkKYGBgCgpgYGB7cHl0aG9ufQpzZWxlY3RlZF9rID0gaW50KHIuc2VsZWN0ZWRfaykKZGVuc2l0eV90aHJlc2hvbGQgPSAwLjEKY25tZl9vYmouY29uc2Vuc3VzKGs9c2VsZWN0ZWRfaywgZGVuc2l0eV90aHJlc2hvbGQ9ZGVuc2l0eV90aHJlc2hvbGQsc2hvd19jbHVzdGVyaW5nPVRydWUpCnVzYWdlX25vcm0sIGdlcF9zY29yZXMsIGdlcF90cG0sIHRvcGdlbmVzID0gY25tZl9vYmoubG9hZF9yZXN1bHRzKEs9c2VsZWN0ZWRfaywgZGVuc2l0eV90aHJlc2hvbGQ9ZGVuc2l0eV90aHJlc2hvbGQpCmBgYAoKYGBge3B5dGhvbn0KdXNhZ2Vfbm9ybSwgZ2VwX3Njb3JlczUsIGdlcF90cG0sIHRvcGdlbmVzID0gY25tZl9vYmoubG9hZF9yZXN1bHRzKEs9NSwgZGVuc2l0eV90aHJlc2hvbGQ9ZGVuc2l0eV90aHJlc2hvbGQpCnVzYWdlX25vcm0sIGdlcF9zY29yZXM2LCBnZXBfdHBtLCB0b3BnZW5lcyA9IGNubWZfb2JqLmxvYWRfcmVzdWx0cyhLPTYsIGRlbnNpdHlfdGhyZXNob2xkPWRlbnNpdHlfdGhyZXNob2xkKQp1c2FnZV9ub3JtLCBnZXBfc2NvcmVzNywgZ2VwX3RwbSwgdG9wZ2VuZXMgPSBjbm1mX29iai5sb2FkX3Jlc3VsdHMoSz03LCBkZW5zaXR5X3RocmVzaG9sZD1kZW5zaXR5X3RocmVzaG9sZCkKdXNhZ2Vfbm9ybSwgZ2VwX3Njb3JlczgsIGdlcF90cG0sIHRvcGdlbmVzID0gY25tZl9vYmoubG9hZF9yZXN1bHRzKEs9OCwgZGVuc2l0eV90aHJlc2hvbGQ9ZGVuc2l0eV90aHJlc2hvbGQpCnVzYWdlX25vcm0sIGdlcF9zY29yZXM5LCBnZXBfdHBtLCB0b3BnZW5lcyA9IGNubWZfb2JqLmxvYWRfcmVzdWx0cyhLPTksIGRlbnNpdHlfdGhyZXNob2xkPWRlbnNpdHlfdGhyZXNob2xkKQoKYGBgCgoKIyBFbnJpY2htZW50IGFuYWx5c2lzIGJ5IHRvcCAyMDAgZ2VuZXMgb2YgZWFjaCBwcm9ncmFtIHsudGFic2V0fQpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04LCByZXN1bHRzPSdhc2lzJ30KZ2VwX3Njb3JlczUgPSBweSRnZXBfc2NvcmVzNQpnZXBfc2NvcmVzNiA9IHB5JGdlcF9zY29yZXM2CmdlcF9zY29yZXM3ID0gcHkkZ2VwX3Njb3JlczcKZ2VwX3Njb3JlczggPSBweSRnZXBfc2NvcmVzOApnZXBfc2NvcmVzOSA9IHB5JGdlcF9zY29yZXM5CgphbGxfZ2VwX3Njb3JlcyA9ICBsaXN0KGdlcF9zY29yZXM1ID0gZ2VwX3Njb3JlczUsIGdlcF9zY29yZXM2ID0gZ2VwX3Njb3JlczYsIGdlcF9zY29yZXM3PSBnZXBfc2NvcmVzNywgZ2VwX3Njb3JlczggPSBnZXBfc2NvcmVzOCwgZ2VwX3Njb3JlczkgPSBnZXBfc2NvcmVzOSkKIyBjYW5vbmljYWxfcGF0aHdheXMgPSBtc2lnZGJyKHNwZWNpZXMgPSAiSG9tbyBzYXBpZW5zIiwgY2F0ZWdvcnkgPSAiQzIiKSAlPiUgZHBseXI6OmZpbHRlcihnc19zdWJjYXQgIT0gIkNHUCIpICU+JSAgZHBseXI6OmRpc3RpbmN0KGdzX25hbWUsIGdlbmVfc3ltYm9sKSAKZm9yIChnZXBfbmFtZSBpbiBuYW1lcyhhbGxfZ2VwX3Njb3JlcykpIHsKICBnZXBfc2NvcmVzID0gYWxsX2dlcF9zY29yZXNbW2dlcF9uYW1lXV0KICB0b3BfZ2VuZXNfbnVtID0gMjAwCiAgcGx0X2xpc3QgPSBsaXN0KCkKICBmb3IgKGkgaW4gMTpuY29sKGdlcF9zY29yZXMpKSB7CiAgICB0b3BfZ2VuZXMgPSBnZXBfc2NvcmVzICAlPiUgIGFycmFuZ2UoZGVzYyhnZXBfc2NvcmVzW2ldKSkgI3NvcnQgYnkgc2NvcmUgYQogICAgdG9wID0gaGVhZChyb3duYW1lcyh0b3BfZ2VuZXMpLHRvcF9nZW5lc19udW0pICN0YWtlIHRvcCB0b3BfZ2VuZXNfbnVtCiAgICByZXMgPSBnZW5lc192ZWNfZW5yaWNobWVudChnZW5lcyA9IHRvcCxiYWNrZ3JvdW5kID0gcm93bmFtZXMoZ2VwX3Njb3JlcyksaG9tZXIgPSBULHRpdGxlID0gCiAgICAgICAgICAgICAgICAgICAgICBuYW1lcyhnZXBfc2NvcmVzKVtpXSxzaWxlbnQgPSBULHJldHVybl9hbGwgPSBULGN1c3RvbV9wYXRod2F5cyA9IE5VTEwgKQogICAgIAogICAgcGx0X2xpc3RbW2ldXSA9IHJlcyRwbHQKICB9CiAgcHJpbnRfdGFiKHBsdCA9ICAgZ2dhcnJhbmdlKHBsb3RsaXN0ID0gcGx0X2xpc3QpLAogICAgICAgICAgICB0aXRsZSA9IGdlcF9uYW1lKQp9CmBgYAoKCmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTgsIHJlc3VsdHM9J2hpZGUnfQpnZXBfc2NvcmVzID0gcHkkZ2VwX3Njb3JlcwoKIyBjYW5vbmljYWxfcGF0aHdheXMgPSBtc2lnZGJyKHNwZWNpZXMgPSAiSG9tbyBzYXBpZW5zIiwgY2F0ZWdvcnkgPSAiQzIiKSAlPiUgZHBseXI6OmZpbHRlcihnc19zdWJjYXQgIT0gIkNHUCIpICU+JSAgZHBseXI6OmRpc3RpbmN0KGdzX25hbWUsIGdlbmVfc3ltYm9sKSAKCnRvcF9nZW5lc19udW0gPSAyMDAKcGx0X2xpc3QgPSBsaXN0KCkKZm9yIChpIGluIDE6bmNvbChnZXBfc2NvcmVzKSkgewogIHRvcF9nZW5lcyA9IGdlcF9zY29yZXMgICU+JSAgYXJyYW5nZShkZXNjKGdlcF9zY29yZXNbaV0pKSAjc29ydCBieSBzY29yZSBhCiAgdG9wID0gaGVhZChyb3duYW1lcyh0b3BfZ2VuZXMpLHRvcF9nZW5lc19udW0pICN0YWtlIHRvcCB0b3BfZ2VuZXNfbnVtCiAgcmVzID0gZ2VuZXNfdmVjX2VucmljaG1lbnQoZ2VuZXMgPSB0b3AsYmFja2dyb3VuZCA9IHJvd25hbWVzKGdlcF9zY29yZXMpLGhvbWVyID0gVCx0aXRsZSA9IAogICAgICAgICAgICAgICAgICAgIG5hbWVzKGdlcF9zY29yZXMpW2ldLHNpbGVudCA9IFQscmV0dXJuX2FsbCA9IFQsY3VzdG9tX3BhdGh3YXlzID0gTlVMTCApCiAgIAogIHBsdF9saXN0W1tpXV0gPSByZXMkcGx0Cn0KZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UoZ3JvYnMgPSBwbHRfbGlzdCkKYGBgCgojIENvcnJlbGF0aW9uIG9mIHByb2dyYW1zCgpgYGB7cn0KZ2VwX3Njb3JlcyA9IHB5JGdlcF9zY29yZXMKY29yX3JlcyA9IGNvcihnZXBfc2NvcmVzKQpwaGVhdG1hcChjb3JfcmVzKQpgYGAKCmBgYHtyfQphbGxfdG9wPSBjKCkKZm9yIChpIGluIDE6bmNvbChnZXBfc2NvcmVzKSkgewogIHRvcF9nZW5lcyA9IGdlcF9zY29yZXMgICU+JSAgYXJyYW5nZShkZXNjKGdlcF9zY29yZXNbaV0pKSAjc29ydCBieSBzY29yZSBhCiAgdG9wID0gaGVhZChyb3duYW1lcyh0b3BfZ2VuZXMpLDE1MCkgI3Rha2UgdG9wIHRvcF9nZW5lc19udW0KYWxsX3RvcCA9IGMoYWxsX3RvcCx0b3ApCn0KZ2VwX3Njb3Jlc190b3AgPSBnZXBfc2NvcmVzW2FsbF90b3AsXQpjb3JfcmVzID0gY29yKGdlcF9zY29yZXNfdG9wKQpwaGVhdG1hcChjb3JfcmVzKQpgYGAKCiMgQ29tYmluZSBzaW1pbGFyIHByb2dyYW1zCmBgYHtyfQpnZXBfc2NvcmVzID0gcHkkZ2VwX3Njb3JlcwojIGdyb3Vwc19saXN0ID0gbGlzdChjKDQsNSwzKSxjKDEpLGMoMiksYyg2KSkKIyBncm91cHNfbGlzdCA9IGxpc3QoYyg0LDUsNiksYygxKSxjKDIpLGMoMyksYyg3KSxjKDkpLGMoOCkpCiMgZ3JvdXBzX2xpc3QgPSBsaXN0KGMoNCw1LDcpLGMoMSksYygyKSxjKDMpLGMoNykpCmdyb3Vwc19saXN0ID0gbGlzdChjKDQsMyw2KSxjKDEpLGMoMiksYyg1KSxjKDcpLGMoOCkpCgpnZXBfc2NvcmVzID0gdW5pb25fcHJvZ3JhbXMoZ3JvdXBzX2xpc3QgPSBncm91cHNfbGlzdCxhbGxfbWV0YWdlbmVzID0gZ2VwX3Njb3JlcykKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD04LCByZXN1bHRzPSdoaWRlJ30KdG9wX2dlbmVzX251bSA9IDIwMApwbHRfbGlzdCA9IGxpc3QoKQpmb3IgKGkgaW4gMTpuY29sKGdlcF9zY29yZXMpKSB7CiAgdG9wX2dlbmVzID0gZ2VwX3Njb3JlcyAgJT4lICBhcnJhbmdlKGRlc2MoZ2VwX3Njb3Jlc1tpXSkpICNzb3J0IGJ5IHNjb3JlIGEKICB0b3AgPSBoZWFkKHJvd25hbWVzKHRvcF9nZW5lcyksdG9wX2dlbmVzX251bSkgI3Rha2UgdG9wIHRvcF9nZW5lc19udW0KICByZXMgPSBnZW5lc192ZWNfZW5yaWNobWVudChnZW5lcyA9IHRvcCxiYWNrZ3JvdW5kID0gcm93bmFtZXMoZ2VwX3Njb3JlcyksaG9tZXIgPSBULHRpdGxlID0gCiAgICAgICAgICAgICAgICAgICAgIG5hbWVzKGdlcF9zY29yZXMpW2ldLHNpbGVudCA9IFQscmV0dXJuX2FsbCA9IFQpCiAgIAogIHBsdF9saXN0W1tpXV0gPSByZXMkcGx0Cn0KZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UoZ3JvYnMgPSBwbHRfbGlzdCkKYGBgCgojIENhbGN1bGF0ZSB1c2FnZQpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2FzaXMnfQoKbHVuZyA9IEZpbmRWYXJpYWJsZUZlYXR1cmVzKG9iamVjdCA9IGx1bmcsbmZlYXR1cmVzID0gMjAwMCkKZ2VuZXMgPSByb3duYW1lcyhsdW5nKVtyb3duYW1lcyhsdW5nKSAlaW4lIFZhcmlhYmxlRmVhdHVyZXMob2JqZWN0ID0geGVubylbMToyMDAwXV0KbHVuZ19leHByZXNzaW9uID0gdChhcy5tYXRyaXgoR2V0QXNzYXlEYXRhKGx1bmcsc2xvdD0nZGF0YScpKSkgCmx1bmdfZXhwcmVzc2lvbiA9IDIqKmx1bmdfZXhwcmVzc2lvbiAjY29udmVydCBmcm9tIGxvZzIodHBtKzEpIHRvIHRwbQpsdW5nX2V4cHJlc3Npb24gPSBsdW5nX2V4cHJlc3Npb24tMQpsdW5nX2V4cHJlc3Npb24gPSBsdW5nX2V4cHJlc3Npb25bLGdlbmVzXSAlPiUgYXMuZGF0YS5mcmFtZSgpCgphbGxfMF9nZW5lcyA9IGNvbG5hbWVzKGx1bmdfZXhwcmVzc2lvbilbY29sU3VtcyhsdW5nX2V4cHJlc3Npb249PTAsIG5hLnJtPVRSVUUpPT1ucm93KGx1bmdfZXhwcmVzc2lvbildICNkZWxldGUgcm93cyB0aGF0IGhhdmUgYWxsIDAKZ2VuZXMgPSBnZW5lc1shZ2VuZXMgJWluJSBhbGxfMF9nZW5lc10KbHVuZ19leHByZXNzaW9uID0gbHVuZ19leHByZXNzaW9uWywhY29sbmFtZXMobHVuZ19leHByZXNzaW9uKSAlaW4lIGFsbF8wX2dlbmVzXQpnYygpCmBgYAoKYGBge3B5dGhvbn0KaW1wb3J0IGFubmRhdGEgYXMgYWQKaW1wb3J0IHNjYW5weSBhcyBzYwppbXBvcnQgbnVtcHkgYXMgbnAKZnJvbSBza2xlYXJuLmRlY29tcG9zaXRpb24gaW1wb3J0IG5vbl9uZWdhdGl2ZV9mYWN0b3JpemF0aW9uCmltcG9ydCBwYW5kYXMgYXMgcGQKY291bnRzX2FkYXRhID0gYWQuQW5uRGF0YShjb3VudHMpCnRwbV9hZGF0YSA9IGFkLkFubkRhdGEodHBtKQpub3JtX2NvdW50cyA9IGdldF9ub3JtX2NvdW50cyhjb3VudHM9Y291bnRzX2FkYXRhLHRwbT10cG1fYWRhdGEsaGlnaF92YXJpYW5jZV9nZW5lc19maWx0ZXI9bnAuYXJyYXkoZ2VuZXMpKSAjbm9ybSBjb3VudHMgbGlrZSBjbm1mCmlmIG5vdCAoZ2VwX3Njb3JlcyBpcyBOb25lKToKICBzcGVjdHJhID0gZ2VwX3Njb3JlcwogIHByaW50ICgiYSIpCmVsc2U6CiAgcHJpbnQgKCJiIikKICBzcGVjdHJhID0gY25tZl9vYmouZ2V0X21lZGlhbl9zcGVjdHJhKGs9aykgI2dldCBzY29yZSAKc3BlY3RyYSA9IHNwZWN0cmFbc3BlY3RyYS5jb2x1bW5zLmludGVyc2VjdGlvbihnZW5lcyldICNyZW1vdmUgZ2VuZXMgbm90IGluIEBnZW5lcwpzcGVjdHJhID0gc3BlY3RyYS5ULnJlaW5kZXgobm9ybV9jb3VudHMudG9fZGYoKS5jb2x1bW5zKS5UICNyZW9yZGVyIHNwZWN0cmEgZ2VuZXMgbGlrZSBub3JtX2NvdW50cwoKdXNhZ2VfYnlfY2FsYyxfLF8gPSBub25fbmVnYXRpdmVfZmFjdG9yaXphdGlvbihYPW5vcm1fY291bnRzLlgsIEggPSBzcGVjdHJhLnZhbHVlcywgdXBkYXRlX0g9RmFsc2Usbl9jb21wb25lbnRzID0gayxtYXhfaXRlcj0xMDAwLGluaXQgPSdyYW5kb20nKQp1c2FnZV9ieV9jYWxjID0gcGQuRGF0YUZyYW1lKHVzYWdlX2J5X2NhbGMsIGluZGV4PWNvdW50cy5pbmRleCwgY29sdW1ucz1zcGVjdHJhLmluZGV4KSAjaW5zZXJ0IHRvIGRmK2FkZCBuYW1lcwp1c2FnZV9ieV9jYWxjID0gdXNhZ2VfYnlfY2FsYy5kaXYodXNhZ2VfYnlfY2FsYy5zdW0oYXhpcz0xKSwgYXhpcz0wKSAjIHN1bSByb3dzIHRvIDEgCmBgYAoKYGBge3B5dGhvbn0KZGVmIGdldF91c2FnZV9mcm9tX3Njb3JlKGNvdW50cyx0cG0sIGdlbmVzLGNubWZfb2JqLGspOgogICAgaW1wb3J0IGFubmRhdGEgYXMgYWQKICAgIGltcG9ydCBzY2FucHkgYXMgc2MKICAgIGltcG9ydCBudW1weSBhcyBucAogICAgZnJvbSBza2xlYXJuLmRlY29tcG9zaXRpb24gaW1wb3J0IG5vbl9uZWdhdGl2ZV9mYWN0b3JpemF0aW9uCiAgICBpbXBvcnQgcGFuZGFzIGFzIHBkCiAgICBjb3VudHNfYWRhdGEgPSBhZC5Bbm5EYXRhKGNvdW50cykKICAgIHRwbV9hZGF0YSA9IGFkLkFubkRhdGEodHBtKQogICAgbm9ybV9jb3VudHMgPSBnZXRfbm9ybV9jb3VudHMoY291bnRzPWNvdW50c19hZGF0YSx0cG09dHBtX2FkYXRhLGhpZ2hfdmFyaWFuY2VfZ2VuZXNfZmlsdGVyPW5wLmFycmF5KGdlbmVzKSkgI25vcm0gY291bnRzIGxpa2UgY25tZgogICAgCiAgICBzcGVjdHJhID0gY25tZl9vYmouZ2V0X21lZGlhbl9zcGVjdHJhKGs9aykgI2dldCBzY29yZSAKICAgIHNwZWN0cmEgPSBzcGVjdHJhW3NwZWN0cmEuY29sdW1ucy5pbnRlcnNlY3Rpb24oZ2VuZXMpXSAjcmVtb3ZlIGdlbmVzIG5vdCBpbiBAZ2VuZXMKICAgIHNwZWN0cmEgPSBzcGVjdHJhLlQucmVpbmRleChub3JtX2NvdW50cy50b19kZigpLmNvbHVtbnMpLlQgI3Jlb3JkZXIgc3BlY3RyYSBnZW5lcyBsaWtlIG5vcm1fY291bnRzCiAgICAKICAgIHVzYWdlX2J5X2NhbGMsXyxfID0gbm9uX25lZ2F0aXZlX2ZhY3Rvcml6YXRpb24oWD1ub3JtX2NvdW50cy5YLCBIID0gc3BlY3RyYS52YWx1ZXMsIHVwZGF0ZV9IPUZhbHNlLG5fY29tcG9uZW50cyA9IGssbWF4X2l0ZXI9MTAwMCxpbml0PSdyYW5kb20nKQogICAgdXNhZ2VfYnlfY2FsYyA9IHBkLkRhdGFGcmFtZSh1c2FnZV9ieV9jYWxjLCBpbmRleD1jb3VudHMuaW5kZXgsIGNvbHVtbnM9c3BlY3RyYS5pbmRleCkgI2luc2VydCB0byBkZithZGQgbmFtZXMKICAgIHVzYWdlX2J5X2NhbGMgPSB1c2FnZV9ieV9jYWxjLmRpdih1c2FnZV9ieV9jYWxjLnN1bShheGlzPTEpLCBheGlzPTApICMgc3VtIHJvd3MgdG8gMSAKICAgIHJlb3JkZXIgPSB1c2FnZV9ieV9jYWxjLnN1bShheGlzPTApLnNvcnRfdmFsdWVzKGFzY2VuZGluZz1GYWxzZSkKICAgIHVzYWdlX2J5X2NhbGMgPSB1c2FnZV9ieV9jYWxjLmxvY1s6LCByZW9yZGVyLmluZGV4XQogICAgcmV0dXJuKHVzYWdlX2J5X2NhbGMpCgoKYGBgCgpgYGB7cHl0aG9ufQoKbHVuZ19leHByZXNzaW9uID0gci5sdW5nX2V4cHJlc3Npb24KZ2VuZXMgPSByLmdlbmVzCmdlcF9zY29yZXMgPSByLmdlcF9zY29yZXMKdXNhZ2VfYnlfY2FsYyA9IGdldF91c2FnZV9mcm9tX3Njb3JlKGNvdW50cz1sdW5nX2V4cHJlc3Npb24sdHBtPWx1bmdfZXhwcmVzc2lvbixnZW5lcz1nZW5lcyxjbm1mX29iaj1jbm1mX29iaixrPTkpCmBgYApgYGB7cn0KdXNhZ2VfYnlfY2FsYyA9IHB5JHVzYWdlX2J5X2NhbGMKYGBgCgojIFVzZ2FnZSBVTUFQCmBgYHtyIGZpZy5oZWlnaHQ9MTIsIGZpZy53aWR0aD0xMn0KYWxsX21ldGFnZW5lcz0gdXNhZ2VfYnlfY2FsYwojIE1ha2UgbWV0YWdlbmUgbmFtZXMKZm9yIChpIGluIDE6bmNvbChhbGxfbWV0YWdlbmVzKSkgewogIGNvbG5hbWVzKGFsbF9tZXRhZ2VuZXMpW2ldID0gIm1ldGFnZW5lLiIgJT4lIHBhc3RlMChpKQp9CiMgbmFtZXMgKGFsbF9tZXRhZ2VuZXMpID0gYygiSHlwb3hpYSIsIlRORmEiLCJDZWxsX2N5Y2xlIikKCiNhZGQgZWFjaCBtZXRhZ2VuZSB0byBtZXRhZGF0YQpmb3IgKGkgaW4gMTpuY29sKGFsbF9tZXRhZ2VuZXMpKSB7CiAgbWV0YWdlX21ldGFkYXRhID0gYWxsX21ldGFnZW5lcyAlPiUgZHBseXI6OnNlbGVjdChpKQogIGx1bmcgPSBBZGRNZXRhRGF0YShvYmplY3QgPSBsdW5nLG1ldGFkYXRhID0gbWV0YWdlX21ldGFkYXRhKQp9CkZlYXR1cmVQbG90KG9iamVjdCA9IGx1bmcsZmVhdHVyZXMgPSBjb2xuYW1lcyhhbGxfbWV0YWdlbmVzKSxtYXguY3V0b2ZmID0gNzApCgpgYGAKCgoKCgoKCgoKCgoKCgoKCiMgQXNzaWdubWVudCAKYGBge3J9Cmxhcmdlcl9ieSA9IDEuMjUKbHVuZyA9IHByb2dyYW1fYXNzaWdubWVudChkYXRhc2V0ID0gbHVuZyxsYXJnZXJfYnkgPSBsYXJnZXJfYnkscHJvZ3JhbV9uYW1lcyA9IGNvbG5hbWVzKGFsbF9tZXRhZ2VuZXMpKQpgYGAgICAKCgoKCmBgYHtyIGVjaG89VFJVRSwgZmlnLndpZHRoPTEwLCByZXN1bHRzPSdhc2lzJ30KcCA9IGNlbGxfcGVyY2VudGFnZShkYXRhc2V0ID0gbHVuZyx0aW1lLnBvaW50X3ZhciA9ICJ0aW1lLnBvaW50IixieV9wcm9ncmFtID0gVCkKcHJpbnRfdGFiKHBsdCA9IHAsdGl0bGUgPSAiYnkgcHJvZ3JhbSIpCnAgPSBjZWxsX3BlcmNlbnRhZ2UoZGF0YXNldCA9IGx1bmcsdGltZS5wb2ludF92YXIgPSAidGltZS5wb2ludCIsYnlfdHAgPSBULHhfb3JkZXIgPSBOVUxMKQpwcmludF90YWIocGx0ID0gcCx0aXRsZSA9ICJieSB0aW1lcG9pbnQiKQoKCmBgYAoKIyMgcHJvZ3JhbS5hc3NpZ25tZW50CmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTEwfQojIGNvbG9ycyA9ICByYWluYm93KG5jb2woYWxsX21ldGFnZW5lcykpCiMgZmMgPC0gY29sb3JSYW1wUGFsZXR0ZShjKCJsaWdodGdyZWVuIiwgImRhcmtncmVlbiIpKQojIGdyZWVucyA9IGZjKDQpCiMgY29sb3JzWzFdID0gImJsdWUiCiMgY29sb3JzWzI6M10gPSBncmVlbnNbMToyXQojIGNvbG9yc1s0XSA9ICJyZWQiCiMgY29sb3JzWzU6Nl0gPSBncmVlbnNbMzo0XQojIGNvbG9ycyA9IGMoY29sb3JzLCJncmV5IikKIyBEaW1QbG90KGx1bmcsZ3JvdXAuYnkgPSAicHJvZ3JhbS5hc3NpZ25tZW50IixwdC5zaXplID0gMC41LGNvbHMgPWNvbG9ycykKCgojIGNvbG9ycyA9ICByYWluYm93KG5jb2woYWxsX21ldGFnZW5lcykpCiMgY29sb3JzID0gYyhjb2xvcnMsImdyZXkiKQojIERpbVBsb3QobHVuZyxncm91cC5ieSA9ICJwcm9ncmFtLmFzc2lnbm1lbnQiLHB0LnNpemUgPSAwLjUsY29scyA9Y29sb3JzKQoKRGltUGxvdChsdW5nLGdyb3VwLmJ5ID0gInByb2dyYW0uYXNzaWdubWVudCIscHQuc2l6ZSA9IDAuNSkKRGltUGxvdChsdW5nLGdyb3VwLmJ5ID0gInBhdGllbnQuaWRlbnQiLHB0LnNpemUgPSAwLjUpCkRpbVBsb3QobHVuZyxncm91cC5ieSA9ICJ0aW1lLnBvaW50IixwdC5zaXplID0gMC41KQpgYGAKCiMgU2NvcmUgcmVndWxhdGlvbiB7LnRhYnNldH0KCmBgYHtyIGVjaG89VFJVRSwgcmVzdWx0cz0nYXNpcyd9Cm1ldGFnZW5lc19tZWFuX2NvbXBhcmUoZGF0YXNldCA9IGx1bmcsdGltZS5wb2ludF92YXIgPSAidGltZS5wb2ludCIscHJlZml4ID0gInBhdGllbnQiLHBhdGllbnQuaWRlbnRfdmFyID0gInBhdGllbnQuaWRlbnQiLHByZV9vbiA9IGMoInByZS10cmVhdG1lbnQiLCJvbi10cmVhdG1lbnQiKSxheGlzLnRleHQueCA9IDgscHJvZ3JhbXMgPSAibWV0YWdlbmUuMyIpCmBgYAoKCgoKCmBgYHtyfQogZGYgID0gRmV0Y2hEYXRhKG9iamVjdCA9IGx1bmcsdmFycyA9IGMoInByb2dyYW0uYXNzaWdubWVudCIsInRpbWUucG9pbnQiKSkgJT4lIAogICAgbXV0YXRlKHByb2dyYW0uYXNzaWdubWVudCA9IGlmX2Vsc2UoIShwcm9ncmFtLmFzc2lnbm1lbnQgJWluJSAibWV0YWdlbmUuMyIpLCAKICAgICAgICAgICAgICAgICAgICAgICAibm90X2h5cG94aWEiLCAKICAgICAgICAgICAgICAgICAgICAgICBwcm9ncmFtLmFzc2lnbm1lbnQpKSAlPiUgCiAgICBmaWx0ZXIgKHRpbWUucG9pbnQgJWluJSBjKCJwcmUtdHJlYXRtZW50Iiwib24tdHJlYXRtZW50IikpICU+JSAKICAgIGRyb3BsZXZlbHMoKSAKICB0ZXN0ID0gZmlzaGVyLnRlc3QodGFibGUoZGYpKQogICAgCiAgbGlicmFyeShnZ3N0YXRzcGxvdCkKcHJpbnQoCiAgICBnZ2JhcnN0YXRzKAogICAgZGYsIHByb2dyYW0uYXNzaWdubWVudCwgdGltZS5wb2ludCwKICAgIHJlc3VsdHMuc3VidGl0bGUgPSBGQUxTRSwKICAgIHN1YnRpdGxlID0gcGFzdGUwKAogICAgICAiRmlzaGVyJ3MgZXhhY3QgdGVzdCIsICIsIHAtdmFsdWUgPSAiLAogICAgICBpZmVsc2UodGVzdCRwLnZhbHVlIDwgMC4wMDEsICI8IDAuMDAxIiwgcm91bmQodGVzdCRwLnZhbHVlLCAzKSkKICAgICkKICApCikKYGBgCgoKIyBwZXIgcGF0aWVudCBmaXNoZXIgdGVzdAoKYGBge3J9CnBhdGllbnRzX3ZlY3RvciA9IGx1bmckcGF0aWVudC5pZGVudCAlPiUgdW5pcXVlKCkKZm9yIChwYXRpZW50X25hbWUgaW4gcGF0aWVudHNfdmVjdG9yKSB7CiAgZGYgID0gRmV0Y2hEYXRhKG9iamVjdCA9IGx1bmcsdmFycyA9IGMoInByb2dyYW0uYXNzaWdubWVudCIsInBhdGllbnQuaWRlbnQiLCJ0aW1lLnBvaW50IikpICU+JSAKICAgIGZpbHRlciAocGF0aWVudC5pZGVudCA9PSBwYXRpZW50X25hbWUpICU+JSAKICAgIGZpbHRlciAocHJvZ3JhbS5hc3NpZ25tZW50ICVpbiUgYygibWV0YWdlbmUuMSIsIm1ldGFnZW5lLjIiKSkgJT4lIAogICAgZmlsdGVyICh0aW1lLnBvaW50ICVpbiUgYygicHJlLXRyZWF0bWVudCIsIm9uLXRyZWF0bWVudCIpKSAlPiUgCiAgICBzZWxlY3QoLXBhdGllbnQuaWRlbnQpICU+JSAKICAgIGRyb3BsZXZlbHMoKSAKICB0ZXN0ID0gZmlzaGVyLnRlc3QodGFibGUoZGYpKQogICAgCiAgbGlicmFyeShnZ3N0YXRzcGxvdCkKcHJpbnQoCiAgICBnZ2JhcnN0YXRzKAogICAgZGYsIHByb2dyYW0uYXNzaWdubWVudCwgdGltZS5wb2ludCwKICAgIHJlc3VsdHMuc3VidGl0bGUgPSBGQUxTRSwKICAgIHN1YnRpdGxlID0gcGFzdGUwKAogICAgICAiRmlzaGVyJ3MgZXhhY3QgdGVzdCIsICIsIHAtdmFsdWUgPSAiLAogICAgICBpZmVsc2UodGVzdCRwLnZhbHVlIDwgMC4wMDEsICI8IDAuMDAxIiwgcm91bmQodGVzdCRwLnZhbHVlLCAzKSkKICAgICksdGl0bGUgPSBwYXRpZW50X25hbWUKICApCikKfQpgYGAKCgpgYGB7cn0KcGF0aWVudHNfdmVjdG9yID0gbHVuZyRwYXRpZW50LmlkZW50ICU+JSB1bmlxdWUoKQpmb3IgKHBhdGllbnRfbmFtZSBpbiBwYXRpZW50c192ZWN0b3IpIHsKICBwYXRpZW50X2RhdGEgPSBzdWJzZXQoeCA9IGx1bmcsIHN1YnNldCA9IHBhdGllbnQuaWRlbnQgPT0gcGF0aWVudF9uYW1lKQogIGNlbGxfcGVyY2VudGFnZShkYXRhc2V0ID0gcGF0aWVudF9kYXRhLHRpbWUucG9pbnRfdmFyID0gInRpbWUucG9pbnQiKQoKfQogIAoKYGBgCgoKCg==