1 Parameters

2 Functions


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

no_neg <- function(x) {
  x = x + abs(min(x))
  x
}

sum_2_one <- function(x) {
  x =x/sum(x)
  x
}
# import python functions:
import types

get_norm_counts  = r.get_norm_counts
code_obj = compile(get_norm_counts, '<string>', 'exec')
get_norm_counts = types.FunctionType(code_obj.co_consts[0], globals())

get_usage_from_score  = r.get_usage_from_score
code_obj = compile(get_usage_from_score, '<string>', 'exec')
get_usage_from_score = types.FunctionType(code_obj.co_consts[0], globals())

3 Data

xeno = readRDS("./Data/10x_xeno_1000.Rds")
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)

4 Models 2K vargenes

suffix = r.suffix
import pickle
from cnmf import cNMF
f = open('./Data/cnmf/cnmf_objects/models_2Kvargenes_cnmf_obj.pckl', 'rb')
cnmf_obj = pickle.load(f)
f.close()
gep_scores = readRDS("/sci/labs/yotamd/lab_share/avishai.wizel/R_projects/EGFR/Data/cnmf/harmony_models_gep_scores.rds")
selected_k = 3
density_threshold = 0.1
# cnmf_obj.consensus(k=selected_k, density_threshold=density_threshold)
usage_norm, gep_scores, gep_tpm, topgenes = cnmf_obj.load_results(K=selected_k, density_threshold=density_threshold)

4.1 programs enrichment

gep_scores = py$gep_scores
gep_tpm = py$gep_tpm
all_metagenes= py$usage_norm
for (program  in names (gep_scores)) {
  print(
    ggplot(gep_scores, aes(gep_scores[,program])) + stat_ecdf(geom = "step")

  )
}

ntop = 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),ntop) #take top top_genes_num
  res = genes_vec_enrichment(genes = top,background = rownames(gep_scores),homer = T,title = 
                    i,silent = T,return_all = T,custom_pathways  = NULL)
   
  plt_list[[i]] = res$plt
}
gridExtra::grid.arrange(grobs = plt_list)


xeno = FindVariableFeatures(object = xeno,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%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
vargenes = VariableFeatures(object = xeno)
xeno = DietSeurat(xeno)
gc()
            used   (Mb) gc trigger    (Mb)   max used    (Mb)
Ncells  12013833  641.7   20862907  1114.2   20862907  1114.2
Vcells 513038452 3914.2 1751618077 13363.8 1524897220 11634.1
xeno_expression = t(as.matrix(GetAssayData(xeno,slot='counts'))) 
xeno_expression = xeno_expression[,vargenes] %>% as.data.frame()

all_0_genes = colnames(xeno_expression)[colSums(xeno_expression==0, na.rm=TRUE)==nrow(xeno_expression)] #delete rows that have all 0
vargenes = vargenes[!vargenes %in% all_0_genes]
gc()
            used   (Mb) gc trigger    (Mb)   max used    (Mb)
Ncells  12013886  641.7   20862907  1114.2   20862907  1114.2
Vcells 513040458 3914.2 2309359557 17619.1 2798942453 21354.3
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 as 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 
      return(usage_by_calc)

5 Calculate usage

import numpy as np
import scanpy as sc
xeno_expression = r.xeno_expression
vargenes = r.vargenes

ad = sc.read_h5ad('./Data/cnmf/xeno_Harmony_NoNeg_2Kvargenes.h5ad')
ad = ad.to_df()
def compute_tpm(input_counts):
    """
    Default TPM normalization
    """
    tpm = input_counts.copy()
    tpm = sc.pp.normalize_per_cell(tpm, counts_per_cell_after=1e6,copy = True)
    return(tpm)


tpm =  compute_tpm(xeno_expression)
cnmf_genes = ad.keys().to_list()
usage_by_calc = get_usage_from_score(counts=xeno_expression,tpm=tpm,genes=vargenes,cnmf_obj=cnmf_obj,k=3)
/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.
# cnmf_genes = py$cnmf_genes %>% as.data.frame()
# a = vargenes %>% as.data.frame()
usage_by_calc = py$usage_by_calc
usage_by_calc = usage_by_calc[,c(3,2,1)]
usage_norm = py$usage_norm
cor(usage_by_calc,usage_norm)
           1          2          3
3  0.7706987 -0.3294875 -0.4757492
2 -0.4583164  0.8211901 -0.4757045
1 -0.3128294 -0.4755204  0.9323277
all_metagenes = usage_by_calc

6 programs expression

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)
  xeno = AddMetaData(object = xeno,metadata = metage_metadata)
}

print_tab(plt = FeaturePlot(object = xeno,features = colnames(all_metagenes)),title = "umap expression")

umap expression

metagenes_mean_compare(dataset = xeno,time.point_var = "treatment",prefix = "model",patient.ident_var = "orig.ident",pre_on = c("NT","OSI"))

Hypoxia per patient

Hypoxia

TNFa per patient

TNFa

NA

6.1 program assignment

larger_by = 1.25
xeno = program_assignment(dataset = xeno,larger_by = larger_by,program_names = colnames(all_metagenes))
print_tab(plt = 
            DimPlot(xeno,group.by = "program.assignment",cols = c(Hypoxia = "red",TNFa = "green",Cell_cycle = "blue","NA" = "grey"))
          ,title = "program.assignment",subtitle_num = 3)

program.assignment

print_tab(plt = 
              DimPlot(xeno,group.by = "orig.ident")
          ,title = "orig.ident",subtitle_num = 3)

orig.ident

print_tab(plt = 
            DimPlot(xeno,group.by = "treatment")
          ,title = "treatment",subtitle_num = 3)

treatment

p = cell_percentage(dataset = xeno,time.point_var = "treatment",by_program = T,x_order = c("NT","OSI","res"))
print_tab(plt = p,title = "by program",subtitle_num = 3)

by program

p = cell_percentage(dataset = xeno,time.point_var = "treatment",by_tp  = T,x_order =c("Hypoxia","TNFa","Cell_cycle","NA"))
print_tab(plt = p,title = "by time point",subtitle_num = 3)

by time point

NA

7 Patients programs expression


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()
usage_by_calc = get_usage_from_score(counts=lung_expression,tpm=lung_expression,genes=genes,cnmf_obj=cnmf_obj,k=3)
/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.
all_metagenes = py$usage_by_calc
all_metagenes = all_metagenes[,c(3,2,1)]

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

FeaturePlot(object = lung,features = colnames(all_metagenes))


metagenes_mean_compare(dataset = lung,time.point_var = "time.point",prefix = "patient",patient.ident_var = "patient.ident",pre_on = c("pre-treatment","on-treatment"))

Hypoxia per patient

Hypoxia

TNFa per patient

TNFa

NA

7.1 lung program assignment

larger_by = 1.25
lung = program_assignment(dataset = lung,larger_by = larger_by,program_names = colnames(all_metagenes))
print_tab(plt = 
            DimPlot(lung,group.by = "program.assignment",cols = c(Hypoxia = "red",TNFa = "green",Cell_cycle = "blue","NA" = "grey"))
          ,title = "program.assignment",subtitle_num = 3)

program.assignment

print_tab(plt = 
              DimPlot(lung,group.by = "patient.ident")
          ,title = "patient.ident",subtitle_num = 3)

patient.ident

print_tab(plt = 
            DimPlot(lung,group.by = "time.point")
          ,title = "time.point",subtitle_num = 3)

time.point

p = cell_percentage(dataset = lung,time.point_var = "time.point",by_program = T,x_order = c("pre-treatment","on-treatment","resistant"))
print_tab(plt = p,title = "by program",subtitle_num = 3)

by program

p = cell_percentage(dataset = lung,time.point_var = "time.point",by_tp  = T,x_order =c("Hypoxia","TNFa","Cell_cycle","NA"))
print_tab(plt = p,title = "by time point",subtitle_num = 3)

by time point

NA

  top_genes = gep_scores  %>%  arrange(desc(gep_scores["Hypoxia"])) #sort by score a
  top = head(rownames(top_genes),200) #take top top_genes_num
  expr = xeno_expression[,colnames(xeno_expression) %in% top]
  expr_cor = cor(expr)

  pht1 = pheatmap(expr_cor,show_colnames = F,show_rownames = F, silent = T)
      
  
  num_of_clusters = 4
clustering_distance = "euclidean"
myannotation = as.data.frame(cutree(pht1[["tree_row"]], k = num_of_clusters)) #split into k clusters
 
names(myannotation)[1] = "cluster"
  myannotation$cluster = as.factor(myannotation$cluster)
  
  palette1 <-brewer.pal(num_of_clusters, "Paired")

  names(palette1) = unique(myannotation$cluster)
  ann_colors = list (cluster = palette1)
  annotation = list(ann_colors = ann_colors, myannotation = myannotation)
  
  colors <- c(seq(-1,1,by=0.01))
  my_palette <- c("blue",colorRampPalette(colors = c("blue", "white", "red"))
                                                   (n = length(colors)-3), "red")


  print_tab(plt = 
                pheatmap(mat = expr_cor,annotation_col =  annotation[["myannotation"]], annotation_colors = annotation[["ann_colors"]], clustering_distance_rows = clustering_distance,clustering_distance_cols = clustering_distance,color = my_palette,breaks = colors,show_rownames = F,show_colnames = F)
            ,title = "genes expression heatmap")
##   genes expression heatmap {.unnumbered }  

NA
NA
  top_genes = gep_scores  %>%  arrange(desc(gep_scores["TNFa"])) #sort by score a
  top = head(rownames(top_genes),200) #take top top_genes_num
  expr = xeno_expression[,colnames(xeno_expression) %in% top]
  expr_cor = cor(expr)

  pht1 = pheatmap(expr_cor,show_colnames = F,show_rownames = F, silent = T)
      
  
  num_of_clusters = 4
clustering_distance = "euclidean"
myannotation = as.data.frame(cutree(pht1[["tree_row"]], k = num_of_clusters)) #split into k clusters
 
names(myannotation)[1] = "cluster"
  myannotation$cluster = as.factor(myannotation$cluster)
  
  palette1 <-brewer.pal(num_of_clusters, "Paired")

  names(palette1) = unique(myannotation$cluster)
  ann_colors = list (cluster = palette1)
  annotation = list(ann_colors = ann_colors, myannotation = myannotation)
  
  colors <- c(seq(-1,1,by=0.01))
  my_palette <- c("blue",colorRampPalette(colors = c("blue", "white", "red"))
                                                   (n = length(colors)-3), "red")


  print_tab(plt = 
                pheatmap(mat = expr_cor,annotation_col =  annotation[["myannotation"]], annotation_colors = annotation[["ann_colors"]], clustering_distance_rows = clustering_distance,clustering_distance_cols = clustering_distance,color = my_palette,breaks = colors,show_rownames = F,show_colnames = F)
            ,title = "genes expression heatmap")
##   genes expression heatmap {.unnumbered }  

NA
NA
  top_genes = gep_scores  %>%  arrange(desc(gep_scores["Cell_cycle"])) #sort by score a
  top = head(rownames(top_genes),200) #take top top_genes_num
  expr = xeno_expression[,colnames(xeno_expression) %in% top]
  expr_cor = cor(expr)

  pht1 = pheatmap(expr_cor,show_colnames = F,show_rownames = F, silent = T)
      
  
  num_of_clusters = 4
clustering_distance = "euclidean"
myannotation = as.data.frame(cutree(pht1[["tree_row"]], k = num_of_clusters)) #split into k clusters
 
names(myannotation)[1] = "cluster"
  myannotation$cluster = as.factor(myannotation$cluster)
  
  palette1 <-brewer.pal(num_of_clusters, "Paired")

  names(palette1) = unique(myannotation$cluster)
  ann_colors = list (cluster = palette1)
  annotation = list(ann_colors = ann_colors, myannotation = myannotation)
  
  colors <- c(seq(-1,1,by=0.01))
  my_palette <- c("blue",colorRampPalette(colors = c("blue", "white", "red"))
                                                   (n = length(colors)-3), "red")


  print_tab(plt = 
                pheatmap(mat = expr_cor,annotation_col =  annotation[["myannotation"]], annotation_colors = annotation[["ann_colors"]], clustering_distance_rows = clustering_distance,clustering_distance_cols = clustering_distance,color = my_palette,breaks = colors,show_rownames = F,show_colnames = F)
            ,title = "Cell_cycle")
##   Cell_cycle {.unnumbered }  

NA
NA
intersect(hif_targets,hypoxia_genes)
 [1] "ERO1A"   "ENO1"    "HILPDA"  "PGK1"    "ENO2"    "EGLN3"   "NDRG1"   "STC2"    "P4HA1"   "OSMR"    "SLC16A3"

LS0tCnRpdGxlOiAnYHIgcnN0dWRpb2FwaTo6Z2V0U291cmNlRWRpdG9yQ29udGV4dCgpJHBhdGggJT4lIGJhc2VuYW1lKCkgJT4lIGdzdWIocGF0dGVybiA9ICJcXC5SbWQiLHJlcGxhY2VtZW50ID0gIiIpYCcgCmF1dGhvcjogIkF2aXNoYWkgV2l6ZWwiCmRhdGU6ICdgciBTeXMudGltZSgpYCcKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdG9jOiB5ZXMKICAgIHRvY19jb2xsYXBzZTogeWVzCiAgICB0b2NfZmxvYXQ6IAogICAgICBjb2xsYXBzZWQ6IEZBTFNFCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHRvY19kZXB0aDogMQotLS0KCiMgUGFyYW1ldGVycwoKYGBge3Igd2FybmluZz1GQUxTRX0KCmBgYAoKCiMgRnVuY3Rpb25zCgpgYGB7ciB3YXJuaW5nPUZBTFNFfQoKbGlicmFyeShzdHJpbmdpKQpsaWJyYXJ5KHJldGljdWxhdGUpCnNvdXJjZV9mcm9tX2dpdGh1YihyZXBvc2l0b3kgPSAiREVHX2Z1bmN0aW9ucyIsdmVyc2lvbiA9ICIwLjIuMjQiKQpzb3VyY2VfZnJvbV9naXRodWIocmVwb3NpdG95ID0gImNOTUZfZnVuY3Rpb25zIix2ZXJzaW9uID0gIjAuMy43NyIsc2NyaXB0X25hbWUgPSAiY25tZl9mdW5jdGlvbl9IYXJtb255LlIiKQoKbm9fbmVnIDwtIGZ1bmN0aW9uKHgpIHsKICB4ID0geCArIGFicyhtaW4oeCkpCiAgeAp9CgpzdW1fMl9vbmUgPC0gZnVuY3Rpb24oeCkgewogIHggPXgvc3VtKHgpCiAgeAp9CmBgYAoKYGBge3B5dGhvbn0KIyBpbXBvcnQgcHl0aG9uIGZ1bmN0aW9uczoKaW1wb3J0IHR5cGVzCgpnZXRfbm9ybV9jb3VudHMgID0gci5nZXRfbm9ybV9jb3VudHMKY29kZV9vYmogPSBjb21waWxlKGdldF9ub3JtX2NvdW50cywgJzxzdHJpbmc+JywgJ2V4ZWMnKQpnZXRfbm9ybV9jb3VudHMgPSB0eXBlcy5GdW5jdGlvblR5cGUoY29kZV9vYmouY29fY29uc3RzWzBdLCBnbG9iYWxzKCkpCgpnZXRfdXNhZ2VfZnJvbV9zY29yZSAgPSByLmdldF91c2FnZV9mcm9tX3Njb3JlCmNvZGVfb2JqID0gY29tcGlsZShnZXRfdXNhZ2VfZnJvbV9zY29yZSwgJzxzdHJpbmc+JywgJ2V4ZWMnKQpnZXRfdXNhZ2VfZnJvbV9zY29yZSA9IHR5cGVzLkZ1bmN0aW9uVHlwZShjb2RlX29iai5jb19jb25zdHNbMF0sIGdsb2JhbHMoKSkKYGBgCiMgRGF0YQoKYGBge3J9Cnhlbm8gPSByZWFkUkRTKCIuL0RhdGEvMTB4X3hlbm9fMTAwMC5SZHMiKQpsdW5nID0gcmVhZFJEUygiLi9EYXRhL2x1bmdfY2FuY2VyY2VsbHNfd2l0aFRQX29ubHlQYXRpZW50cy5yZHMiKQpsdW5nX3BhdGllbnRzID0gbHVuZyRwYXRpZW50LmlkZW50ICU+JSB1bmlxdWUoKSAlPiUgYXMuY2hhcmFjdGVyKCkKbHVuZ19wYXRpZW50c19maWx0ZXJlZCA9IGx1bmdfcGF0aWVudHNbIShsdW5nX3BhdGllbnRzICVpbiUgYygiWDEwNTVuZXciLCJYMTA5OSIpKV0gIyByZW1vdmUgcGF0aWVudHMgd2l0aCBsZXNzIHRoYW4gMTAwIG1hbGlnbmFudCBjZWxscwpsdW5nID0gc3Vic2V0KHggPSBsdW5nLHN1YnNldCA9IHBhdGllbnQuaWRlbnQgJWluJSBsdW5nX3BhdGllbnRzX2ZpbHRlcmVkKQpgYGAKCiMgTW9kZWxzIDJLIHZhcmdlbmVzIAoKYGBge3B5dGhvbn0Kc3VmZml4ID0gci5zdWZmaXgKaW1wb3J0IHBpY2tsZQpmcm9tIGNubWYgaW1wb3J0IGNOTUYKZiA9IG9wZW4oJy4vRGF0YS9jbm1mL2NubWZfb2JqZWN0cy9tb2RlbHNfMkt2YXJnZW5lc19jbm1mX29iai5wY2tsJywgJ3JiJykKY25tZl9vYmogPSBwaWNrbGUubG9hZChmKQpmLmNsb3NlKCkKYGBgCgpgYGB7cn0KIyBnZXBfc2NvcmVzID0gcmVhZFJEUygiL3NjaS9sYWJzL3lvdGFtZC9sYWJfc2hhcmUvYXZpc2hhaS53aXplbC9SX3Byb2plY3RzL0VHRlIvRGF0YS9jbm1mL2hhcm1vbnlfbW9kZWxzX2dlcF9zY29yZXMucmRzIikKYGBgCgoKYGBge3B5dGhvbn0Kc2VsZWN0ZWRfayA9IDMKZGVuc2l0eV90aHJlc2hvbGQgPSAwLjEKIyBjbm1mX29iai5jb25zZW5zdXMoaz1zZWxlY3RlZF9rLCBkZW5zaXR5X3RocmVzaG9sZD1kZW5zaXR5X3RocmVzaG9sZCkKdXNhZ2Vfbm9ybSwgZ2VwX3Njb3JlcywgZ2VwX3RwbSwgdG9wZ2VuZXMgPSBjbm1mX29iai5sb2FkX3Jlc3VsdHMoSz1zZWxlY3RlZF9rLCBkZW5zaXR5X3RocmVzaG9sZD1kZW5zaXR5X3RocmVzaG9sZCkKYGBgCgojIyBwcm9ncmFtcyBlbnJpY2htZW50CgoKCmBgYHtyfQpnZXBfc2NvcmVzID0gcHkkZ2VwX3Njb3JlcwpnZXBfdHBtID0gcHkkZ2VwX3RwbQphbGxfbWV0YWdlbmVzPSBweSR1c2FnZV9ub3JtCmBgYAoKYGBge3J9Cm5hbWVzIChnZXBfc2NvcmVzKSA9IGMoIkh5cG94aWEiLCJUTkZhIiwiQ2VsbF9jeWNsZSIpCgpsaWJyYXJ5KGhpZ2hjaGFydGVyKSAKb3B0aW9ucyhoaWdoY2hhcnRlci50aGVtZSA9IGhjX3RoZW1lX3NtcGwodG9vbHRpcCA9IGxpc3QodmFsdWVEZWNpbWFscyA9IDIpKSkKCmZvciAocHJvZ3JhbSAgaW4gbmFtZXMgKGdlcF9zY29yZXMpKSB7CiAgcHJpbnQoCiAgICAgICAgaGNoYXJ0KAogICAgICBkZW5zaXR5KGdlcF9zY29yZXNbLHByb2dyYW1dKSwgCiAgICAgIHR5cGUgPSAiYXJlYSIsIG5hbWUgPSBwcm9ncmFtCiAgICAgICkKICApCn0KCgpgYGAKYGBge3J9CmZvciAocHJvZ3JhbSAgaW4gbmFtZXMgKGdlcF9zY29yZXMpKSB7CiAgcHJpbnQoCiAgICBnZ3Bsb3QoZ2VwX3Njb3JlcywgYWVzKGdlcF9zY29yZXNbLHByb2dyYW1dKSkgKyBzdGF0X2VjZGYoZ2VvbSA9ICJzdGVwIikKCiAgKQp9CmBgYAoKYGBge3IgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9OCwgcmVzdWx0cz0naGlkZSd9Cm50b3AgPSAyMDAKcGx0X2xpc3QgPSBsaXN0KCkKZm9yIChpIGluIDE6bmNvbChnZXBfc2NvcmVzKSkgewogIHRvcF9nZW5lcyA9IGdlcF9zY29yZXMgICU+JSAgYXJyYW5nZShkZXNjKGdlcF9zY29yZXNbaV0pKSAjc29ydCBieSBzY29yZSBhCiAgdG9wID0gaGVhZChyb3duYW1lcyh0b3BfZ2VuZXMpLG50b3ApICN0YWtlIHRvcCB0b3BfZ2VuZXNfbnVtCiAgcmVzID0gZ2VuZXNfdmVjX2VucmljaG1lbnQoZ2VuZXMgPSB0b3AsYmFja2dyb3VuZCA9IHJvd25hbWVzKGdlcF9zY29yZXMpLGhvbWVyID0gVCx0aXRsZSA9IAogICAgICAgICAgICAgICAgICAgIGksc2lsZW50ID0gVCxyZXR1cm5fYWxsID0gVCxjdXN0b21fcGF0aHdheXMgID0gTlVMTCkKICAgCiAgcGx0X2xpc3RbW2ldXSA9IHJlcyRwbHQKfQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShncm9icyA9IHBsdF9saXN0KQpgYGAKCgoKYGBge3J9Cgp4ZW5vID0gRmluZFZhcmlhYmxlRmVhdHVyZXMob2JqZWN0ID0geGVubyxuZmVhdHVyZXMgPSAyMDAwKQp2YXJnZW5lcyA9IFZhcmlhYmxlRmVhdHVyZXMob2JqZWN0ID0geGVubykKeGVubyA9IERpZXRTZXVyYXQoeGVubykKZ2MoKQp4ZW5vX2V4cHJlc3Npb24gPSB0KGFzLm1hdHJpeChHZXRBc3NheURhdGEoeGVubyxzbG90PSdjb3VudHMnKSkpIAp4ZW5vX2V4cHJlc3Npb24gPSB4ZW5vX2V4cHJlc3Npb25bLHZhcmdlbmVzXSAlPiUgYXMuZGF0YS5mcmFtZSgpCgphbGxfMF9nZW5lcyA9IGNvbG5hbWVzKHhlbm9fZXhwcmVzc2lvbilbY29sU3Vtcyh4ZW5vX2V4cHJlc3Npb249PTAsIG5hLnJtPVRSVUUpPT1ucm93KHhlbm9fZXhwcmVzc2lvbildICNkZWxldGUgcm93cyB0aGF0IGhhdmUgYWxsIDAKdmFyZ2VuZXMgPSB2YXJnZW5lc1shdmFyZ2VuZXMgJWluJSBhbGxfMF9nZW5lc10KZ2MoKQpgYGAKYGBge3B5dGhvbn0KZGVmIGdldF91c2FnZV9mcm9tX3Njb3JlKGNvdW50cyx0cG0sIGdlbmVzLGNubWZfb2JqLGspOgogICAgICBpbXBvcnQgYW5uZGF0YSBhcyBhZAogICAgICBpbXBvcnQgc2NhbnB5IGFzIHNjCiAgICAgIGltcG9ydCBudW1weSBhcyBucAogICAgICBmcm9tIHNrbGVhcm4uZGVjb21wb3NpdGlvbiBpbXBvcnQgbm9uX25lZ2F0aXZlX2ZhY3Rvcml6YXRpb24KICAgICAgaW1wb3J0IHBhbmRhcyBhcyBwZAogICAgICBjb3VudHNfYWRhdGEgPSBhZC5Bbm5EYXRhKGNvdW50cykKICAgICAgdHBtX2FkYXRhID0gYWQuQW5uRGF0YSh0cG0pCiAgICAgIG5vcm1fY291bnRzID0gZ2V0X25vcm1fY291bnRzKGNvdW50cz1jb3VudHNfYWRhdGEsdHBtPXRwbV9hZGF0YSxoaWdoX3ZhcmlhbmNlX2dlbmVzX2ZpbHRlcj1ucC5hcnJheShnZW5lcykpICNub3JtIGNvdW50cyBhcyBjbm1mCiAgICAgIHNwZWN0cmEgPSBjbm1mX29iai5nZXRfbWVkaWFuX3NwZWN0cmEoaz1rKSAjZ2V0IHNjb3JlIAogICAgICBzcGVjdHJhID0gc3BlY3RyYVtzcGVjdHJhLmNvbHVtbnMuaW50ZXJzZWN0aW9uKGdlbmVzKV0gI3JlbW92ZSBnZW5lcyBub3QgaW4gQGdlbmVzCiAgICAgIHNwZWN0cmEgPSBzcGVjdHJhLlQucmVpbmRleChub3JtX2NvdW50cy50b19kZigpLmNvbHVtbnMpLlQgI3Jlb3JkZXIgc3BlY3RyYSBnZW5lcyBsaWtlIG5vcm1fY291bnRzCiAgICAgIAogICAgICB1c2FnZV9ieV9jYWxjLF8sXyA9IG5vbl9uZWdhdGl2ZV9mYWN0b3JpemF0aW9uKFg9bm9ybV9jb3VudHMuWCwgSCA9IHNwZWN0cmEudmFsdWVzLCB1cGRhdGVfSD1GYWxzZSxuX2NvbXBvbmVudHMgPSBrLG1heF9pdGVyPTEwMDAsaW5pdCA9J3JhbmRvbScpCiAgICAgIHVzYWdlX2J5X2NhbGMgPSBwZC5EYXRhRnJhbWUodXNhZ2VfYnlfY2FsYywgaW5kZXg9Y291bnRzLmluZGV4LCBjb2x1bW5zPXNwZWN0cmEuaW5kZXgpICNpbnNlcnQgdG8gZGYrYWRkIG5hbWVzCiAgICAgIHVzYWdlX2J5X2NhbGMgPSB1c2FnZV9ieV9jYWxjLmRpdih1c2FnZV9ieV9jYWxjLnN1bShheGlzPTEpLCBheGlzPTApICMgc3VtIHJvd3MgdG8gMSAKICAgICAgcmV0dXJuKHVzYWdlX2J5X2NhbGMpCmBgYAoKIyBDYWxjdWxhdGUgdXNhZ2UKYGBge3B5dGhvbn0KaW1wb3J0IG51bXB5IGFzIG5wCmltcG9ydCBzY2FucHkgYXMgc2MKeGVub19leHByZXNzaW9uID0gci54ZW5vX2V4cHJlc3Npb24KdmFyZ2VuZXMgPSByLnZhcmdlbmVzCgphZCA9IHNjLnJlYWRfaDVhZCgnLi9EYXRhL2NubWYveGVub19IYXJtb255X05vTmVnXzJLdmFyZ2VuZXMuaDVhZCcpCmFkID0gYWQudG9fZGYoKQpkZWYgY29tcHV0ZV90cG0oaW5wdXRfY291bnRzKToKICAgICIiIgogICAgRGVmYXVsdCBUUE0gbm9ybWFsaXphdGlvbgogICAgIiIiCiAgICB0cG0gPSBpbnB1dF9jb3VudHMuY29weSgpCiAgICB0cG0gPSBzYy5wcC5ub3JtYWxpemVfcGVyX2NlbGwodHBtLCBjb3VudHNfcGVyX2NlbGxfYWZ0ZXI9MWU2LGNvcHkgPSBUcnVlKQogICAgcmV0dXJuKHRwbSkKCmBgYAoKYGBge3B5dGhvbn0KCgp0cG0gPSAgY29tcHV0ZV90cG0oeGVub19leHByZXNzaW9uKQpjbm1mX2dlbmVzID0gYWQua2V5cygpLnRvX2xpc3QoKQp1c2FnZV9ieV9jYWxjID0gZ2V0X3VzYWdlX2Zyb21fc2NvcmUoY291bnRzPXhlbm9fZXhwcmVzc2lvbix0cG09dHBtLGdlbmVzPXZhcmdlbmVzLGNubWZfb2JqPWNubWZfb2JqLGs9MykKYGBgCgpgYGB7cn0KIyBjbm1mX2dlbmVzID0gcHkkY25tZl9nZW5lcyAlPiUgYXMuZGF0YS5mcmFtZSgpCiMgYSA9IHZhcmdlbmVzICU+JSBhcy5kYXRhLmZyYW1lKCkKdXNhZ2VfYnlfY2FsYyA9IHB5JHVzYWdlX2J5X2NhbGMKdXNhZ2VfYnlfY2FsYyA9IHVzYWdlX2J5X2NhbGNbLGMoMywyLDEpXQp1c2FnZV9ub3JtID0gcHkkdXNhZ2Vfbm9ybQpjb3IodXNhZ2VfYnlfY2FsYyx1c2FnZV9ub3JtKQphbGxfbWV0YWdlbmVzID0gdXNhZ2VfYnlfY2FsYwpgYGAKCgoKCiMgcHJvZ3JhbXMgZXhwcmVzc2lvbiB7LnRhYnNldH0KYGBge3IgZWNobz1UUlVFLCByZXN1bHRzPSdhc2lzJ30KbmFtZXMgKGFsbF9tZXRhZ2VuZXMpID0gYygiSHlwb3hpYSIsIlRORmEiLCJDZWxsX2N5Y2xlIikKI2FkZCBlYWNoIG1ldGFnZW5lIHRvIG1ldGFkYXRhCmZvciAoaSAgaW4gMTpuY29sKGFsbF9tZXRhZ2VuZXMpKSB7CiAgbWV0YWdlX21ldGFkYXRhID0gYWxsX21ldGFnZW5lcyAlPiUgZHBseXI6OnNlbGVjdChpKQogIHhlbm8gPSBBZGRNZXRhRGF0YShvYmplY3QgPSB4ZW5vLG1ldGFkYXRhID0gbWV0YWdlX21ldGFkYXRhKQp9CgpwcmludF90YWIocGx0ID0gRmVhdHVyZVBsb3Qob2JqZWN0ID0geGVubyxmZWF0dXJlcyA9IGNvbG5hbWVzKGFsbF9tZXRhZ2VuZXMpKSx0aXRsZSA9ICJ1bWFwIGV4cHJlc3Npb24iKQoKbWV0YWdlbmVzX21lYW5fY29tcGFyZShkYXRhc2V0ID0geGVubyx0aW1lLnBvaW50X3ZhciA9ICJ0cmVhdG1lbnQiLHByZWZpeCA9ICJtb2RlbCIscGF0aWVudC5pZGVudF92YXIgPSAib3JpZy5pZGVudCIscHJlX29uID0gYygiTlQiLCJPU0kiKSkKCmBgYAoKIyMgcHJvZ3JhbSBhc3NpZ25tZW50ey50YWJzZXR9CmBgYHtyfQpsYXJnZXJfYnkgPSAxLjI1Cnhlbm8gPSBwcm9ncmFtX2Fzc2lnbm1lbnQoZGF0YXNldCA9IHhlbm8sbGFyZ2VyX2J5ID0gbGFyZ2VyX2J5LHByb2dyYW1fbmFtZXMgPSBjb2xuYW1lcyhhbGxfbWV0YWdlbmVzKSkKYGBgIAoKYGBge3IgZWNobz1UUlVFLCByZXN1bHRzPSdhc2lzJ30KcHJpbnRfdGFiKHBsdCA9IAogICAgICAgICAgICBEaW1QbG90KHhlbm8sZ3JvdXAuYnkgPSAicHJvZ3JhbS5hc3NpZ25tZW50Iixjb2xzID0gYyhIeXBveGlhID0gInJlZCIsVE5GYSA9ICJncmVlbiIsQ2VsbF9jeWNsZSA9ICJibHVlIiwiTkEiID0gImdyZXkiKSkKICAgICAgICAgICx0aXRsZSA9ICJwcm9ncmFtLmFzc2lnbm1lbnQiLHN1YnRpdGxlX251bSA9IDMpCnByaW50X3RhYihwbHQgPSAKICAgICAgICAgICAgICBEaW1QbG90KHhlbm8sZ3JvdXAuYnkgPSAib3JpZy5pZGVudCIpCiAgICAgICAgICAsdGl0bGUgPSAib3JpZy5pZGVudCIsc3VidGl0bGVfbnVtID0gMykKcHJpbnRfdGFiKHBsdCA9IAogICAgICAgICAgICBEaW1QbG90KHhlbm8sZ3JvdXAuYnkgPSAidHJlYXRtZW50IikKICAgICAgICAgICx0aXRsZSA9ICJ0cmVhdG1lbnQiLHN1YnRpdGxlX251bSA9IDMpCgpwID0gY2VsbF9wZXJjZW50YWdlKGRhdGFzZXQgPSB4ZW5vLHRpbWUucG9pbnRfdmFyID0gInRyZWF0bWVudCIsYnlfcHJvZ3JhbSA9IFQseF9vcmRlciA9IGMoIk5UIiwiT1NJIiwicmVzIikpCnByaW50X3RhYihwbHQgPSBwLHRpdGxlID0gImJ5IHByb2dyYW0iLHN1YnRpdGxlX251bSA9IDMpCgpwID0gY2VsbF9wZXJjZW50YWdlKGRhdGFzZXQgPSB4ZW5vLHRpbWUucG9pbnRfdmFyID0gInRyZWF0bWVudCIsYnlfdHAgID0gVCx4X29yZGVyID1jKCJIeXBveGlhIiwiVE5GYSIsIkNlbGxfY3ljbGUiLCJOQSIpKQpwcmludF90YWIocGx0ID0gcCx0aXRsZSA9ICJieSB0aW1lIHBvaW50IixzdWJ0aXRsZV9udW0gPSAzKQoKCmBgYAoKIyBQYXRpZW50cyBwcm9ncmFtcyBleHByZXNzaW9uIHsudGFic2V0fQpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2FzaXMnfQoKbHVuZyA9IEZpbmRWYXJpYWJsZUZlYXR1cmVzKG9iamVjdCA9IGx1bmcsbmZlYXR1cmVzID0gMjAwMCkKZ2VuZXMgPSByb3duYW1lcyhsdW5nKVtyb3duYW1lcyhsdW5nKSAlaW4lIFZhcmlhYmxlRmVhdHVyZXMob2JqZWN0ID0geGVubylbMToyMDAwXV0KbHVuZ19leHByZXNzaW9uID0gdChhcy5tYXRyaXgoR2V0QXNzYXlEYXRhKGx1bmcsc2xvdD0nZGF0YScpKSkgCmx1bmdfZXhwcmVzc2lvbiA9IDIqKmx1bmdfZXhwcmVzc2lvbiAjY29udmVydCBmcm9tIGxvZzIodHBtKzEpIHRvIHRwbQpsdW5nX2V4cHJlc3Npb24gPSBsdW5nX2V4cHJlc3Npb24tMQpsdW5nX2V4cHJlc3Npb24gPSBsdW5nX2V4cHJlc3Npb25bLGdlbmVzXSAlPiUgYXMuZGF0YS5mcmFtZSgpCgphbGxfMF9nZW5lcyA9IGNvbG5hbWVzKGx1bmdfZXhwcmVzc2lvbilbY29sU3VtcyhsdW5nX2V4cHJlc3Npb249PTAsIG5hLnJtPVRSVUUpPT1ucm93KGx1bmdfZXhwcmVzc2lvbildICNkZWxldGUgcm93cyB0aGF0IGhhdmUgYWxsIDAKZ2VuZXMgPSBnZW5lc1shZ2VuZXMgJWluJSBhbGxfMF9nZW5lc10KbHVuZ19leHByZXNzaW9uID0gbHVuZ19leHByZXNzaW9uWywhY29sbmFtZXMobHVuZ19leHByZXNzaW9uKSAlaW4lIGFsbF8wX2dlbmVzXQpnYygpCmBgYAoKYGBge3B5dGhvbn0KbHVuZ19leHByZXNzaW9uID0gci5sdW5nX2V4cHJlc3Npb24KZ2VuZXMgPSByLmdlbmVzCgp1c2FnZV9ieV9jYWxjID0gZ2V0X3VzYWdlX2Zyb21fc2NvcmUoY291bnRzPWx1bmdfZXhwcmVzc2lvbix0cG09bHVuZ19leHByZXNzaW9uLGdlbmVzPWdlbmVzLGNubWZfb2JqPWNubWZfb2JqLGs9MykKYGBgCgpgYGB7ciBlY2hvPVRSVUUsIHJlc3VsdHM9J2FzaXMnfQphbGxfbWV0YWdlbmVzID0gcHkkdXNhZ2VfYnlfY2FsYwphbGxfbWV0YWdlbmVzID0gYWxsX21ldGFnZW5lc1ssYygzLDIsMSldCgpuYW1lcyAoYWxsX21ldGFnZW5lcykgPSBjKCJIeXBveGlhIiwiVE5GYSIsIkNlbGxfY3ljbGUiKQojYWRkIGVhY2ggbWV0YWdlbmUgdG8gbWV0YWRhdGEKZm9yIChpICBpbiAxOm5jb2woYWxsX21ldGFnZW5lcykpIHsKICBtZXRhZ2VfbWV0YWRhdGEgPSBhbGxfbWV0YWdlbmVzICU+JSBkcGx5cjo6c2VsZWN0KGkpCiAgbHVuZyA9IEFkZE1ldGFEYXRhKG9iamVjdCA9IGx1bmcsbWV0YWRhdGEgPSBtZXRhZ2VfbWV0YWRhdGEpCn0KCkZlYXR1cmVQbG90KG9iamVjdCA9IGx1bmcsZmVhdHVyZXMgPSBjb2xuYW1lcyhhbGxfbWV0YWdlbmVzKSkKCm1ldGFnZW5lc19tZWFuX2NvbXBhcmUoZGF0YXNldCA9IGx1bmcsdGltZS5wb2ludF92YXIgPSAidGltZS5wb2ludCIscHJlZml4ID0gInBhdGllbnQiLHBhdGllbnQuaWRlbnRfdmFyID0gInBhdGllbnQuaWRlbnQiLHByZV9vbiA9IGMoInByZS10cmVhdG1lbnQiLCJvbi10cmVhdG1lbnQiKSkKCmBgYAojIyBsdW5nIHByb2dyYW0gYXNzaWdubWVudHsudGFic2V0fQpgYGB7cn0KbGFyZ2VyX2J5ID0gMS4yNQpsdW5nID0gcHJvZ3JhbV9hc3NpZ25tZW50KGRhdGFzZXQgPSBsdW5nLGxhcmdlcl9ieSA9IGxhcmdlcl9ieSxwcm9ncmFtX25hbWVzID0gY29sbmFtZXMoYWxsX21ldGFnZW5lcykpCmBgYCAKCmBgYHtyIGVjaG89VFJVRSwgcmVzdWx0cz0nYXNpcyd9CnByaW50X3RhYihwbHQgPSAKICAgICAgICAgICAgRGltUGxvdChsdW5nLGdyb3VwLmJ5ID0gInByb2dyYW0uYXNzaWdubWVudCIsY29scyA9IGMoSHlwb3hpYSA9ICJyZWQiLFRORmEgPSAiZ3JlZW4iLENlbGxfY3ljbGUgPSAiYmx1ZSIsIk5BIiA9ICJncmV5IikpCiAgICAgICAgICAsdGl0bGUgPSAicHJvZ3JhbS5hc3NpZ25tZW50IixzdWJ0aXRsZV9udW0gPSAzKQpwcmludF90YWIocGx0ID0gCiAgICAgICAgICAgICAgRGltUGxvdChsdW5nLGdyb3VwLmJ5ID0gInBhdGllbnQuaWRlbnQiKQogICAgICAgICAgLHRpdGxlID0gInBhdGllbnQuaWRlbnQiLHN1YnRpdGxlX251bSA9IDMpCnByaW50X3RhYihwbHQgPSAKICAgICAgICAgICAgRGltUGxvdChsdW5nLGdyb3VwLmJ5ID0gInRpbWUucG9pbnQiKQogICAgICAgICAgLHRpdGxlID0gInRpbWUucG9pbnQiLHN1YnRpdGxlX251bSA9IDMpCgpwID0gY2VsbF9wZXJjZW50YWdlKGRhdGFzZXQgPSBsdW5nLHRpbWUucG9pbnRfdmFyID0gInRpbWUucG9pbnQiLGJ5X3Byb2dyYW0gPSBULHhfb3JkZXIgPSBjKCJwcmUtdHJlYXRtZW50Iiwib24tdHJlYXRtZW50IiwicmVzaXN0YW50IikpCnByaW50X3RhYihwbHQgPSBwLHRpdGxlID0gImJ5IHByb2dyYW0iLHN1YnRpdGxlX251bSA9IDMpCgpwID0gY2VsbF9wZXJjZW50YWdlKGRhdGFzZXQgPSBsdW5nLHRpbWUucG9pbnRfdmFyID0gInRpbWUucG9pbnQiLGJ5X3RwICA9IFQseF9vcmRlciA9YygiSHlwb3hpYSIsIlRORmEiLCJDZWxsX2N5Y2xlIiwiTkEiKSkKcHJpbnRfdGFiKHBsdCA9IHAsdGl0bGUgPSAiYnkgdGltZSBwb2ludCIsc3VidGl0bGVfbnVtID0gMykKCgpgYGAKYGBge3J9CiAgdG9wX2dlbmVzID0gZ2VwX3Njb3JlcyAgJT4lICBhcnJhbmdlKGRlc2MoZ2VwX3Njb3Jlc1siSHlwb3hpYSJdKSkgI3NvcnQgYnkgc2NvcmUgYQogIHRvcCA9IGhlYWQocm93bmFtZXModG9wX2dlbmVzKSwyMDApICN0YWtlIHRvcCB0b3BfZ2VuZXNfbnVtCiAgZXhwciA9IHhlbm9fZXhwcmVzc2lvblssY29sbmFtZXMoeGVub19leHByZXNzaW9uKSAlaW4lIHRvcF0KICBleHByX2NvciA9IGNvcihleHByKQoKICBwaHQxID0gcGhlYXRtYXAoZXhwcl9jb3Isc2hvd19jb2xuYW1lcyA9IEYsc2hvd19yb3duYW1lcyA9IEYsIHNpbGVudCA9IFQpCiAgICAgIAogIAogIG51bV9vZl9jbHVzdGVycyA9IDQKY2x1c3RlcmluZ19kaXN0YW5jZSA9ICJldWNsaWRlYW4iCm15YW5ub3RhdGlvbiA9IGFzLmRhdGEuZnJhbWUoY3V0cmVlKHBodDFbWyJ0cmVlX3JvdyJdXSwgayA9IG51bV9vZl9jbHVzdGVycykpICNzcGxpdCBpbnRvIGsgY2x1c3RlcnMKIApuYW1lcyhteWFubm90YXRpb24pWzFdID0gImNsdXN0ZXIiCiAgbXlhbm5vdGF0aW9uJGNsdXN0ZXIgPSBhcy5mYWN0b3IobXlhbm5vdGF0aW9uJGNsdXN0ZXIpCiAgCiAgcGFsZXR0ZTEgPC1icmV3ZXIucGFsKG51bV9vZl9jbHVzdGVycywgIlBhaXJlZCIpCgogIG5hbWVzKHBhbGV0dGUxKSA9IHVuaXF1ZShteWFubm90YXRpb24kY2x1c3RlcikKICBhbm5fY29sb3JzID0gbGlzdCAoY2x1c3RlciA9IHBhbGV0dGUxKQogIGFubm90YXRpb24gPSBsaXN0KGFubl9jb2xvcnMgPSBhbm5fY29sb3JzLCBteWFubm90YXRpb24gPSBteWFubm90YXRpb24pCiAgCiAgY29sb3JzIDwtIGMoc2VxKC0xLDEsYnk9MC4wMSkpCiAgbXlfcGFsZXR0ZSA8LSBjKCJibHVlIixjb2xvclJhbXBQYWxldHRlKGNvbG9ycyA9IGMoImJsdWUiLCAid2hpdGUiLCAicmVkIikpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChuID0gbGVuZ3RoKGNvbG9ycyktMyksICJyZWQiKQoKCiAgcHJpbnRfdGFiKHBsdCA9IAogICAgICAgICAgICAgICAgcGhlYXRtYXAobWF0ID0gZXhwcl9jb3IsYW5ub3RhdGlvbl9jb2wgPSAgYW5ub3RhdGlvbltbIm15YW5ub3RhdGlvbiJdXSwgYW5ub3RhdGlvbl9jb2xvcnMgPSBhbm5vdGF0aW9uW1siYW5uX2NvbG9ycyJdXSwgY2x1c3RlcmluZ19kaXN0YW5jZV9yb3dzID0gY2x1c3RlcmluZ19kaXN0YW5jZSxjbHVzdGVyaW5nX2Rpc3RhbmNlX2NvbHMgPSBjbHVzdGVyaW5nX2Rpc3RhbmNlLGNvbG9yID0gbXlfcGFsZXR0ZSxicmVha3MgPSBjb2xvcnMsc2hvd19yb3duYW1lcyA9IEYsc2hvd19jb2xuYW1lcyA9IEYpCiAgICAgICAgICAgICx0aXRsZSA9ICJIeXBveGlhIikKICAKYGBgCgpgYGB7cn0KICB0b3BfZ2VuZXMgPSBnZXBfc2NvcmVzICAlPiUgIGFycmFuZ2UoZGVzYyhnZXBfc2NvcmVzWyJUTkZhIl0pKSAjc29ydCBieSBzY29yZSBhCiAgdG9wID0gaGVhZChyb3duYW1lcyh0b3BfZ2VuZXMpLDIwMCkgI3Rha2UgdG9wIHRvcF9nZW5lc19udW0KICBleHByID0geGVub19leHByZXNzaW9uWyxjb2xuYW1lcyh4ZW5vX2V4cHJlc3Npb24pICVpbiUgdG9wXQogIGV4cHJfY29yID0gY29yKGV4cHIpCgogIHBodDEgPSBwaGVhdG1hcChleHByX2NvcixzaG93X2NvbG5hbWVzID0gRixzaG93X3Jvd25hbWVzID0gRiwgc2lsZW50ID0gVCkKICAgICAgCiAgCiAgbnVtX29mX2NsdXN0ZXJzID0gNApjbHVzdGVyaW5nX2Rpc3RhbmNlID0gImV1Y2xpZGVhbiIKbXlhbm5vdGF0aW9uID0gYXMuZGF0YS5mcmFtZShjdXRyZWUocGh0MVtbInRyZWVfcm93Il1dLCBrID0gbnVtX29mX2NsdXN0ZXJzKSkgI3NwbGl0IGludG8gayBjbHVzdGVycwogCm5hbWVzKG15YW5ub3RhdGlvbilbMV0gPSAiY2x1c3RlciIKICBteWFubm90YXRpb24kY2x1c3RlciA9IGFzLmZhY3RvcihteWFubm90YXRpb24kY2x1c3RlcikKICAKICBwYWxldHRlMSA8LWJyZXdlci5wYWwobnVtX29mX2NsdXN0ZXJzLCAiUGFpcmVkIikKCiAgbmFtZXMocGFsZXR0ZTEpID0gdW5pcXVlKG15YW5ub3RhdGlvbiRjbHVzdGVyKQogIGFubl9jb2xvcnMgPSBsaXN0IChjbHVzdGVyID0gcGFsZXR0ZTEpCiAgYW5ub3RhdGlvbiA9IGxpc3QoYW5uX2NvbG9ycyA9IGFubl9jb2xvcnMsIG15YW5ub3RhdGlvbiA9IG15YW5ub3RhdGlvbikKICAKICBjb2xvcnMgPC0gYyhzZXEoLTEsMSxieT0wLjAxKSkKICBteV9wYWxldHRlIDwtIGMoImJsdWUiLGNvbG9yUmFtcFBhbGV0dGUoY29sb3JzID0gYygiYmx1ZSIsICJ3aGl0ZSIsICJyZWQiKSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKG4gPSBsZW5ndGgoY29sb3JzKS0zKSwgInJlZCIpCgoKICBwcmludF90YWIocGx0ID0gCiAgICAgICAgICAgICAgICBwaGVhdG1hcChtYXQgPSBleHByX2Nvcixhbm5vdGF0aW9uX2NvbCA9ICBhbm5vdGF0aW9uW1sibXlhbm5vdGF0aW9uIl1dLCBhbm5vdGF0aW9uX2NvbG9ycyA9IGFubm90YXRpb25bWyJhbm5fY29sb3JzIl1dLCBjbHVzdGVyaW5nX2Rpc3RhbmNlX3Jvd3MgPSBjbHVzdGVyaW5nX2Rpc3RhbmNlLGNsdXN0ZXJpbmdfZGlzdGFuY2VfY29scyA9IGNsdXN0ZXJpbmdfZGlzdGFuY2UsY29sb3IgPSBteV9wYWxldHRlLGJyZWFrcyA9IGNvbG9ycyxzaG93X3Jvd25hbWVzID0gRixzaG93X2NvbG5hbWVzID0gRikKICAgICAgICAgICAgLHRpdGxlID0gIlRORmEiKQogIApgYGAKCmBgYHtyfQogIHRvcF9nZW5lcyA9IGdlcF9zY29yZXMgICU+JSAgYXJyYW5nZShkZXNjKGdlcF9zY29yZXNbIkNlbGxfY3ljbGUiXSkpICNzb3J0IGJ5IHNjb3JlIGEKICB0b3AgPSBoZWFkKHJvd25hbWVzKHRvcF9nZW5lcyksMjAwKSAjdGFrZSB0b3AgdG9wX2dlbmVzX251bQogIGV4cHIgPSB4ZW5vX2V4cHJlc3Npb25bLGNvbG5hbWVzKHhlbm9fZXhwcmVzc2lvbikgJWluJSB0b3BdCiAgZXhwcl9jb3IgPSBjb3IoZXhwcikKCiAgcGh0MSA9IHBoZWF0bWFwKGV4cHJfY29yLHNob3dfY29sbmFtZXMgPSBGLHNob3dfcm93bmFtZXMgPSBGLCBzaWxlbnQgPSBUKQogICAgICAKICAKICBudW1fb2ZfY2x1c3RlcnMgPSA0CmNsdXN0ZXJpbmdfZGlzdGFuY2UgPSAiZXVjbGlkZWFuIgpteWFubm90YXRpb24gPSBhcy5kYXRhLmZyYW1lKGN1dHJlZShwaHQxW1sidHJlZV9yb3ciXV0sIGsgPSBudW1fb2ZfY2x1c3RlcnMpKSAjc3BsaXQgaW50byBrIGNsdXN0ZXJzCiAKbmFtZXMobXlhbm5vdGF0aW9uKVsxXSA9ICJjbHVzdGVyIgogIG15YW5ub3RhdGlvbiRjbHVzdGVyID0gYXMuZmFjdG9yKG15YW5ub3RhdGlvbiRjbHVzdGVyKQogIAogIHBhbGV0dGUxIDwtYnJld2VyLnBhbChudW1fb2ZfY2x1c3RlcnMsICJQYWlyZWQiKQoKICBuYW1lcyhwYWxldHRlMSkgPSB1bmlxdWUobXlhbm5vdGF0aW9uJGNsdXN0ZXIpCiAgYW5uX2NvbG9ycyA9IGxpc3QgKGNsdXN0ZXIgPSBwYWxldHRlMSkKICBhbm5vdGF0aW9uID0gbGlzdChhbm5fY29sb3JzID0gYW5uX2NvbG9ycywgbXlhbm5vdGF0aW9uID0gbXlhbm5vdGF0aW9uKQogIAogIGNvbG9ycyA8LSBjKHNlcSgtMSwxLGJ5PTAuMDEpKQogIG15X3BhbGV0dGUgPC0gYygiYmx1ZSIsY29sb3JSYW1wUGFsZXR0ZShjb2xvcnMgPSBjKCJibHVlIiwgIndoaXRlIiwgInJlZCIpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAobiA9IGxlbmd0aChjb2xvcnMpLTMpLCAicmVkIikKCgogIHByaW50X3RhYihwbHQgPSAKICAgICAgICAgICAgICAgIHBoZWF0bWFwKG1hdCA9IGV4cHJfY29yLGFubm90YXRpb25fY29sID0gIGFubm90YXRpb25bWyJteWFubm90YXRpb24iXV0sIGFubm90YXRpb25fY29sb3JzID0gYW5ub3RhdGlvbltbImFubl9jb2xvcnMiXV0sIGNsdXN0ZXJpbmdfZGlzdGFuY2Vfcm93cyA9IGNsdXN0ZXJpbmdfZGlzdGFuY2UsY2x1c3RlcmluZ19kaXN0YW5jZV9jb2xzID0gY2x1c3RlcmluZ19kaXN0YW5jZSxjb2xvciA9IG15X3BhbGV0dGUsYnJlYWtzID0gY29sb3JzLHNob3dfcm93bmFtZXMgPSBGLHNob3dfY29sbmFtZXMgPSBGKQogICAgICAgICAgICAsdGl0bGUgPSAiQ2VsbF9jeWNsZSIpCiAgCmBgYAoKYGBge3J9CnRvcF9nZW5lcyA9IGdlcF9zY29yZXMgICU+JSAgYXJyYW5nZShkZXNjKGdlcF9zY29yZXNbIkh5cG94aWEiXSkpICNzb3J0IGJ5IHNjb3JlIGEKaHlwb3hpYV9nZW5lcyA9IGhlYWQocm93bmFtZXModG9wX2dlbmVzKSwyMDApICN0YWtlIHRvcCB0b3BfZ2VuZXNfbnVtCmludGVyc2VjdChoaWZfdGFyZ2V0cyxoeXBveGlhX2dlbmVzKQpgYGAKYGBge3J9CmxpYnJhcnkoZ2d2ZW5uKQphbGwgPSBsaXN0KGh5cG94aWFfZ2VuZXMgPSBoeXBveGlhX2dlbmVzLCBoaWZfdGFyZ2V0cyA9IGhpZl90YXJnZXRzKQpnZ3Zlbm4oCiAgYWxsCikKYGBgCgo=