Intro

This document includes the ensemble model analysis performed on the 7500 models generated by the Gitsbe module when running the DrugLogics computational pipeline for finding synergistic drug combinations. All these models were trained towards a consensus steady state signaling pattern. The input for the simulations and the output data are in the consensus-2500 directory (the 2500 number denotes the number of simulations executed). The analysis will be presented step by step in the sections below and it’s main focus is to investigate the association between the models’ stable states and their fitness to the consensus steady state activity pattern they are trained to.

Prerequisites

Firstly, we load the required libraries (you need to install them if you don’t have them):

library(circlize)
library(ComplexHeatmap)
library(usefun)
library(emba)

Input

Two inputs are used in this analysis:

  • The consensus steady state file from ~1000 cell lines, which lists the nodes of the signaling network along with their (predicted) activity state. The consensus steady state has been calculated using PARADIGM with the gene expression and CNV data from all cell lines of the CCLE, while the majority rule was used to derive the activity state of each node - meaning that more than half 1’s for one node in all cell lines would result in an activity state of 1 for that particular node in the consensus steady state profile.
  • The models directory, which is the same as the models directory produced by Gitsbe and has one .gitsbe file per model that includes the stable state of the boolean model. Note that a model can have 1 stable state or none in our simulations - but the models used in this analysis have been selected through a genetic evolution algorithm in Gitsbe and so in the end, only those with 1 stable state have higher fitness values and remain in the later generations. Higher fitness here means a better match of a model’s stable state to the consensus steady state (a perfect match would result in a fitness of 1)
data.dir = paste0(getwd(), "/")
models.dir = paste0(data.dir, "models")

Now, we parse the data into proper R objects. First, we get the full stable state per model:

models = get_model_names(models.dir)
nodes  = get_node_names(models.dir)

models.stable.state.file = paste0(data.dir, "models_stable_state")
models.stable.state = as.matrix(
  read.table(file = models.stable.state.file, check.names = FALSE)
)

# print the stable state of the first model (first 12 nodes)
pretty_print_vector_names_and_values(models.stable.state[1,], n = 12)

MAP3K7: 0, MAP2K6: 0, MAP2K3: 0, NLK: 0, MAP3K4: 0, MAP2K4: 1, IKBKG: 0, IKBKB: 1, AKT1: 0, BRAF: 0, SMAD3: 1, DAB2IP: 1

number.of.nodes = length(nodes)
number.of.models = length(models)

pretty_print_string(paste("Number of models:", number.of.models), with.gt = TRUE)

Number of models: 7500

pretty_print_string(paste("Number of nodes:", number.of.nodes), with.gt = TRUE)

Number of nodes: 139

Next, we get the consensus steady state. Note that the data and tools used (e.g.  PARADIGM, CCLE) to derive the activity states of the network nodes produce further nodes that are present in the consensus steady state vector (but not in our network) and so they need to be discarded. Also, we reorder the columns of the consensus vector so as to match the order of nodes as they are in the models.stable.state object:

consensus.steady.state.file = paste0(data.dir, "consensus_steady_state")
ss.df = read.table(consensus.steady.state.file, sep = "\t", stringsAsFactors = FALSE)
consensus.steady.state = ss.df[,2]
names(consensus.steady.state) = ss.df[,1]

consensus.steady.state = prune_and_reorder_vector(consensus.steady.state, nodes)

# print the consensus steady state (first 12 nodes)
pretty_print_vector_names_and_values(consensus.steady.state, n = 12)

MAP3K7: 0, MAP2K6: 0, MAP2K3: 0, NLK: 0, MAP3K4: 0, MAP2K4: 1, IKBKG: 0, IKBKB: 1, AKT1: 1, BRAF: 1, SMAD3: 1, DAB2IP: 0

As we can see, even for the first 12 nodes, the consensus steady state is different compared to the example stable state of the first model.

Analysis

Continuing, we compare each of the models’ stable state to the consensus one and find the percentage of matches for each model (fitness value). Then, we calculate the percentage of models that had a fitness match larger than a threshold value (e.g. \(0.55\) or more than \(55\%\) match) and we plot the models’ percentage results while ranging the fitness threshold value from 0 to \(100\%\):

models.fitness = 
  apply(models.stable.state, 1, get_percentage_of_matches, consensus.steady.state)

min.fitness = min(models.fitness)
max.fitness = max(models.fitness)
string = paste0("Minimum fitness: ", 
                specify_decimal(min.fitness, digits.to.keep = 4), ", ", 
                "Maximum fitness: ", 
                specify_decimal(max.fitness, digits.to.keep = 4))
pretty_print_string(string)

Minimum fitness: 0.5396, Maximum fitness: 0.6547

fitness.range = seq(0, 1, 0.001)
models.percentage = sapply(fitness.range, function(fitness.threshold) {
  return(sum(models.fitness > fitness.threshold)/number.of.models)
})

y.axis.values = pretty(models.percentage)
plot(fitness.range, models.percentage, type = "l", yaxt = "n",
     xlab = "Fitness Threshold value",
     ylab = "Percentage of models",
     main = "Model Exceedance of fitness threshold value")
axis(2, at = y.axis.values, lab = paste(y.axis.values * 100, "%"), las = 1)

As we can see in the figure above, the models generated by the evolutionary algorithm of Gitsbe - which are exactly the best fitting ones to the consensus steady state pattern across all simulations - have a fitness value between 0.54 and 0.65, meaning that in all models, more than half of the network nodes stabilize in the same activity state as the one from the consensus steady state (100% of the models exhibit a match of more than 50% to the consensus steady state). Using the same logic and the figure above, we can see that 50% of the models exhibit a match of more than 63.4%. So, the models produced in this analysis, while trained to a specific steady state signaling pattern, they cover a broader set of potential states, allowing thus a more robust drug response analysis in the respective module of the DrugLogics pipeline (Drabme).

states = apply(models.stable.state, 1, paste, collapse = "")

model.count.per.fitness = table(models.fitness)
number.of.unique.fitness.values = length(model.count.per.fitness)
number.of.unique.stable.states = length(unique(states))

pretty_print_string(paste0("Unique fitness values: ", 
  number.of.unique.fitness.values, ", Unique stable states found by the models: ", 
  number.of.unique.stable.states))

Unique fitness values: 17, Unique stable states found by the models: 1330

y.axis.values = pretty(model.count.per.fitness)
plot(names(model.count.per.fitness), model.count.per.fitness, 
     type = "b",  yaxt = "n",
     xlab = "Fitness",
     ylab = "Number of models",
     main = "Distribution of fitness among models")
axis(2, at = y.axis.values, las = 1)

Note that there are 17 unique fitness values included in the [0.54, 0.65] range, while the distribution of these values among the models can be seen in the figure above. Almost 3000 models had a fitness of ~0.64 to the consensus steady state pattern, marking the area of the highest fitness models in the figure. Furthermore, the number of unique stable states found by the models is 1330. This means that for a specific fitness value there will be many models that have a stable state corresponding to it (having the same percentage of matches for the activity states of the network nodes compared to the consensus state), but the activity of the nodes between these models will be different, allowing a more diverse attractor landscape to match a specific fitness. To see the difference between the stable states of the produced models relating to fitness, we produce the next heatmap:

# coloring
activity.colors = c("red", "lightyellow")
activity.col.fun = colorRamp2(breaks = c(0, 1), colors = activity.colors)
fitness.col.fun = 
  colorRamp2(breaks = c(min.fitness, (min.fitness + max.fitness)/2, max.fitness), 
             colors = c("red", "black", "green"))

# define the fitness color bar
fitness.annot = rowAnnotation(
  fitness = anno_simple(x = models.fitness, col = fitness.col.fun), 
            show_annotation_name = FALSE)

heatmap.ss = 
  Heatmap(matrix = models.stable.state, column_title = "Models Stable States",
    column_title_gp = gpar(fontsize = 20), column_names_gp = gpar(fontsize = 4),
    row_dend_width = unit(1, "inches"), column_dend_height = unit(1, "inches"),
    col = activity.col.fun, show_row_names = FALSE,
    left_annotation = fitness.annot, show_heatmap_legend = FALSE,
    use_raster = TRUE, raster_device = "png", raster_quality = 20)

# define the 2 legends
activity.state.legend = Legend(title = "Activity State",
                               labels = c("Inhibited", "Active"),
                               legend_gp = gpar(fill = activity.colors))
fitness.legend = Legend(title = "Fitness", col = fitness.col.fun)
legend.list = packLegend(activity.state.legend, fitness.legend, 
                         direction = "vertical")

draw(heatmap.ss, annotation_legend_list = legend.list, 
     annotation_legend_side = "right")

In the heatmap above, an activity state equal to zero is represented with red color while an activity state equal to 1 with light yellow. We note the prevalence of the highest fitness models across the heatmap as well their differences regarding the activity states of the network nodes. Though this sample of 7500 models represents the best models generated through a genetic algorithm that results in higher fitness models getting selected because they are more close to the consensus steady state pattern, they still show diverse individual stable state pattern characteristics.

R session info

xfun::session_info()
R version 3.6.1 (2019-07-05)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.3 LTS, RStudio 1.2.5001

Locale:
  LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
  LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8    
  LC_MONETARY=en_US.UTF-8    LC_MESSAGES=en_US.UTF-8   
  LC_PAPER=en_US.UTF-8       LC_NAME=C                 
  LC_ADDRESS=C               LC_TELEPHONE=C            
  LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

Package version:
  assertthat_0.2.1     backports_1.1.5      base64enc_0.1-3     
  BH_1.69.0.1          bibtex_0.4.2         circlize_0.4.8      
  Ckmeans.1d.dp_4.3.0  cli_1.1.0            clue_0.3-57         
  cluster_2.1.0        colorspace_1.4-1     compiler_3.6.1      
  ComplexHeatmap_2.0.0 crayon_1.3.4         digest_0.6.21       
  dplyr_0.8.3          ellipsis_0.3.0       emba_0.1.1          
  evaluate_0.14        fansi_0.4.0          gbRd_0.4-11         
  GetoptLong_0.1.7     GlobalOptions_0.1.1  glue_1.3.1          
  graphics_3.6.1       grDevices_3.6.1      grid_3.6.1          
  highr_0.8            htmltools_0.4.0      htmlwidgets_1.5.1   
  igraph_1.2.4.1       jsonlite_1.6         knitr_1.25          
  lattice_0.20.38      magrittr_1.5         markdown_1.1        
  Matrix_1.2.17        methods_3.6.1        mime_0.7            
  packrat_0.5.0        parallel_3.6.1       pillar_1.4.2        
  pkgconfig_2.0.3      plogr_0.2.0          png_0.1-7           
  purrr_0.3.2          R6_2.4.0             RColorBrewer_1.1-2  
  Rcpp_1.0.2           Rdpack_0.11-0        rje_1.10.10         
  rjson_0.2.20         rlang_0.4.0          rmarkdown_1.16      
  shape_1.4.4          stats_3.6.1          stringi_1.4.3       
  stringr_1.4.0        tibble_2.1.3         tidyselect_0.2.5    
  tinytex_0.16         tools_3.6.1          usefun_0.4.1        
  utf8_1.1.4           utils_3.6.1          vctrs_0.2.0         
  visNetwork_2.0.8     xfun_0.10            yaml_2.2.0          
  zeallot_0.1.0       
LS0tCnRpdGxlOiAiR2l0c2JlIENvbnNlbnN1cyBNb2RlbCBBbmFseXNpcyIKYXV0aG9yOiAiW0pvaG4gWm9ib2xhc10oaHR0cHM6Ly9naXRodWIuY29tL2JibG9kZm9uKSIKZGF0ZTogIkxhc3QgdXBkYXRlZDogYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgY3NzOiBzdHlsZS5jc3MKICAgIHRoZW1lOiB1bml0ZWQKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiAKICAgICAgY29sbGFwc2VkOiBmYWxzZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgpgYGB7ciBSZW5kZXIgY29tbWFuZCwgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KI3JtYXJrZG93bjo6cmVuZGVyKGlucHV0ID0gIi4vY29uc2Vuc3VzX21vZGVsX2FuYWx5c2lzLlJtZCIsIG91dHB1dF9mb3JtYXQgPSAiaHRtbF9kb2N1bWVudCIsIG91dHB1dF9kaXIgPSAiLi4vZG9jcy9jb25zZW5zdXMtMjUwMC8iKQpgYGAKCiMjIEludHJvIHstfQoKVGhpcyBkb2N1bWVudCBpbmNsdWRlcyB0aGUgZW5zZW1ibGUgbW9kZWwgYW5hbHlzaXMgcGVyZm9ybWVkIG9uIHRoZSA3NTAwIAptb2RlbHMgZ2VuZXJhdGVkIGJ5IHRoZSBgR2l0c2JlYCBtb2R1bGUgd2hlbiBydW5uaW5nIHRoZSBEcnVnTG9naWNzIApjb21wdXRhdGlvbmFsIHBpcGVsaW5lIGZvciBmaW5kaW5nIHN5bmVyZ2lzdGljIGRydWcgY29tYmluYXRpb25zLiBBbGwgdGhlc2UgCm1vZGVscyB3ZXJlIHRyYWluZWQgdG93YXJkcyBhIGNvbnNlbnN1cyBzdGVhZHkgc3RhdGUgc2lnbmFsaW5nIHBhdHRlcm4uIFRoZSAKaW5wdXQgZm9yIHRoZSBzaW11bGF0aW9ucyBhbmQgdGhlIG91dHB1dCBkYXRhIGFyZSBpbiB0aGUgYGNvbnNlbnN1cy0yNTAwYCAKZGlyZWN0b3J5ICh0aGUgMjUwMCBudW1iZXIgZGVub3RlcyB0aGUgbnVtYmVyIG9mIHNpbXVsYXRpb25zIGV4ZWN1dGVkKS4gVGhlIAphbmFseXNpcyB3aWxsIGJlIHByZXNlbnRlZCBzdGVwIGJ5IHN0ZXAgaW4gdGhlIHNlY3Rpb25zIGJlbG93IGFuZCBpdCdzICoqbWFpbgpmb2N1cyoqIGlzIHRvIGludmVzdGlnYXRlIHRoZSBhc3NvY2lhdGlvbiBiZXR3ZWVuIHRoZSBtb2RlbHMnIHN0YWJsZSBzdGF0ZXMgYW5kIAp0aGVpciBmaXRuZXNzIHRvIHRoZSBjb25zZW5zdXMgc3RlYWR5IHN0YXRlIGFjdGl2aXR5IHBhdHRlcm4gdGhleSBhcmUgdHJhaW5lZCB0by4KCiMjIFByZXJlcXVpc2l0ZXMgey19CgpGaXJzdGx5LCB3ZSBsb2FkIHRoZSByZXF1aXJlZCBsaWJyYXJpZXMgKHlvdSBuZWVkIHRvIGluc3RhbGwgdGhlbSBpZiB5b3UgZG9uJ3QKaGF2ZSB0aGVtKToKYGBge3IgTG9hZCBsaWJyYXJpZXMsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeShjaXJjbGl6ZSkKbGlicmFyeShDb21wbGV4SGVhdG1hcCkKbGlicmFyeSh1c2VmdW4pCmxpYnJhcnkoZW1iYSkKYGBgCgojIyBJbnB1dCB7LX0KCioqVHdvIGlucHV0cyoqIGFyZSB1c2VkIGluIHRoaXMgYW5hbHlzaXM6CgotIFRoZSBjb25zZW5zdXMgKipzdGVhZHkgc3RhdGUqKiBmaWxlIGZyb20gfjEwMDAgY2VsbCBsaW5lcywgd2hpY2ggbGlzdHMgdGhlIApub2RlcyBvZiB0aGUgc2lnbmFsaW5nIG5ldHdvcmsgYWxvbmcgd2l0aCB0aGVpciAocHJlZGljdGVkKSBhY3Rpdml0eSBzdGF0ZS4gVGhlCmNvbnNlbnN1cyBzdGVhZHkgc3RhdGUgaGFzIGJlZW4gY2FsY3VsYXRlZCB1c2luZyAKW1BBUkFESUdNXShodHRwczovL2RvaS5vcmcvMTAuMTA5My9iaW9pbmZvcm1hdGljcy9idHExODIpIHdpdGggdGhlIGdlbmUgCmV4cHJlc3Npb24gYW5kIENOViBkYXRhIGZyb20gYWxsIGNlbGwgbGluZXMgb2YgdGhlIApbQ0NMRV0oaHR0cHM6Ly93d3cuc2V2ZW5icmlkZ2VzLmNvbS9jY2xlLyksIHdoaWxlIHRoZSBtYWpvcml0eSBydWxlIHdhcyB1c2VkIHRvIApkZXJpdmUgdGhlIGFjdGl2aXR5IHN0YXRlIG9mIGVhY2ggbm9kZSAtIG1lYW5pbmcgdGhhdCAKbW9yZSB0aGFuIGhhbGYgMSdzIGZvciBvbmUgbm9kZSBpbiBhbGwgY2VsbCBsaW5lcyB3b3VsZCByZXN1bHQgaW4gYW4gYWN0aXZpdHkgCnN0YXRlIG9mIDEgZm9yIHRoYXQgcGFydGljdWxhciBub2RlIGluIHRoZSBjb25zZW5zdXMgc3RlYWR5IHN0YXRlIHByb2ZpbGUuCi0gVGhlICoqbW9kZWxzKiogZGlyZWN0b3J5LCB3aGljaCBpcyB0aGUgc2FtZSBhcyB0aGUgbW9kZWxzIGRpcmVjdG9yeSBwcm9kdWNlZCAKYnkgYEdpdHNiZWAgYW5kIGhhcyBvbmUgYC5naXRzYmVgIGZpbGUgcGVyIG1vZGVsIHRoYXQgaW5jbHVkZXMgdGhlICoqc3RhYmxlIApzdGF0ZSoqIG9mIHRoZSBib29sZWFuIG1vZGVsLiBOb3RlIHRoYXQgYSBtb2RlbCBjYW4gaGF2ZSAxIHN0YWJsZSBzdGF0ZSBvciBub25lIAppbiBvdXIgc2ltdWxhdGlvbnMgLSBidXQgdGhlIG1vZGVscyB1c2VkIGluIHRoaXMgYW5hbHlzaXMgaGF2ZSBiZWVuIHNlbGVjdGVkIAp0aHJvdWdoIGEgZ2VuZXRpYyBldm9sdXRpb24gYWxnb3JpdGhtIGluIGBHaXRzYmVgIGFuZCBzbyBpbiB0aGUgZW5kLCBvbmx5IHRob3NlIAp3aXRoIDEgc3RhYmxlIHN0YXRlIGhhdmUgaGlnaGVyIGZpdG5lc3MgdmFsdWVzIGFuZCByZW1haW4gaW4gdGhlIGxhdGVyIApnZW5lcmF0aW9ucy4gSGlnaGVyIGZpdG5lc3MgaGVyZSBtZWFucyBhIGJldHRlciBtYXRjaCBvZiBhIG1vZGVsJ3Mgc3RhYmxlIHN0YXRlCnRvIHRoZSBjb25zZW5zdXMgc3RlYWR5IHN0YXRlIChhIHBlcmZlY3QgbWF0Y2ggd291bGQgcmVzdWx0IGluIGEgZml0bmVzcyBvZiAxKQpgYGB7ciBJbnB1dDogZmlsZXN9CmRhdGEuZGlyID0gcGFzdGUwKGdldHdkKCksICIvIikKbW9kZWxzLmRpciA9IHBhc3RlMChkYXRhLmRpciwgIm1vZGVscyIpCmBgYApOb3csIHdlIHBhcnNlIHRoZSBkYXRhIGludG8gcHJvcGVyIFIgb2JqZWN0cy4gRmlyc3QsIHdlIGdldCB0aGUgZnVsbCBzdGFibGUgCnN0YXRlIHBlciBtb2RlbDoKYGBge3IgSW5wdXQ6IG1vZGVscyBzdGFibGUgc3RhdGVzLCByZXN1bHRzID0gJ2FzaXMnfQptb2RlbHMgPSBnZXRfbW9kZWxfbmFtZXMobW9kZWxzLmRpcikKbm9kZXMgID0gZ2V0X25vZGVfbmFtZXMobW9kZWxzLmRpcikKCm1vZGVscy5zdGFibGUuc3RhdGUuZmlsZSA9IHBhc3RlMChkYXRhLmRpciwgIm1vZGVsc19zdGFibGVfc3RhdGUiKQptb2RlbHMuc3RhYmxlLnN0YXRlID0gYXMubWF0cml4KAogIHJlYWQudGFibGUoZmlsZSA9IG1vZGVscy5zdGFibGUuc3RhdGUuZmlsZSwgY2hlY2submFtZXMgPSBGQUxTRSkKKQoKIyBwcmludCB0aGUgc3RhYmxlIHN0YXRlIG9mIHRoZSBmaXJzdCBtb2RlbCAoZmlyc3QgMTIgbm9kZXMpCnByZXR0eV9wcmludF92ZWN0b3JfbmFtZXNfYW5kX3ZhbHVlcyhtb2RlbHMuc3RhYmxlLnN0YXRlWzEsXSwgbiA9IDEyKQpgYGAKCmBgYHtyIE1vZGVsIFN0YXRzLCByZXN1bHRzPSdhc2lzJ30KbnVtYmVyLm9mLm5vZGVzID0gbGVuZ3RoKG5vZGVzKQpudW1iZXIub2YubW9kZWxzID0gbGVuZ3RoKG1vZGVscykKCnByZXR0eV9wcmludF9zdHJpbmcocGFzdGUoIk51bWJlciBvZiBtb2RlbHM6IiwgbnVtYmVyLm9mLm1vZGVscyksIHdpdGguZ3QgPSBUUlVFKQpwcmV0dHlfcHJpbnRfc3RyaW5nKHBhc3RlKCJOdW1iZXIgb2Ygbm9kZXM6IiwgbnVtYmVyLm9mLm5vZGVzKSwgd2l0aC5ndCA9IFRSVUUpCmBgYAoKTmV4dCwgd2UgZ2V0IHRoZSBjb25zZW5zdXMgc3RlYWR5IHN0YXRlLiBOb3RlIHRoYXQgdGhlIGRhdGEgYW5kIHRvb2xzIHVzZWQgKGUuZy4gClBBUkFESUdNLCBDQ0xFKSB0byBkZXJpdmUgdGhlIGFjdGl2aXR5IHN0YXRlcyBvZiB0aGUgbmV0d29yayBub2RlcyBwcm9kdWNlIGZ1cnRoZXIgCm5vZGVzIHRoYXQgYXJlIHByZXNlbnQgaW4gdGhlIGNvbnNlbnN1cyBzdGVhZHkgc3RhdGUgdmVjdG9yIChidXQgbm90IGluIG91ciAKbmV0d29yaykgYW5kIHNvIHRoZXkgbmVlZCB0byBiZSBkaXNjYXJkZWQuIEFsc28sIHdlIHJlb3JkZXIgdGhlIGNvbHVtbnMgb2YgdGhlIApjb25zZW5zdXMgdmVjdG9yIHNvIGFzIHRvIG1hdGNoIHRoZSBvcmRlciBvZiBub2RlcyBhcyB0aGV5IGFyZSBpbiB0aGUgCmBtb2RlbHMuc3RhYmxlLnN0YXRlYCBvYmplY3Q6CmBgYHtyIElucHV0OiBjb25zZW5zdXMgc3RlYWR5IHN0YXRlLCByZXN1bHRzID0gJ2FzaXMnfQpjb25zZW5zdXMuc3RlYWR5LnN0YXRlLmZpbGUgPSBwYXN0ZTAoZGF0YS5kaXIsICJjb25zZW5zdXNfc3RlYWR5X3N0YXRlIikKc3MuZGYgPSByZWFkLnRhYmxlKGNvbnNlbnN1cy5zdGVhZHkuc3RhdGUuZmlsZSwgc2VwID0gIlx0Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpjb25zZW5zdXMuc3RlYWR5LnN0YXRlID0gc3MuZGZbLDJdCm5hbWVzKGNvbnNlbnN1cy5zdGVhZHkuc3RhdGUpID0gc3MuZGZbLDFdCgpjb25zZW5zdXMuc3RlYWR5LnN0YXRlID0gcHJ1bmVfYW5kX3Jlb3JkZXJfdmVjdG9yKGNvbnNlbnN1cy5zdGVhZHkuc3RhdGUsIG5vZGVzKQoKIyBwcmludCB0aGUgY29uc2Vuc3VzIHN0ZWFkeSBzdGF0ZSAoZmlyc3QgMTIgbm9kZXMpCnByZXR0eV9wcmludF92ZWN0b3JfbmFtZXNfYW5kX3ZhbHVlcyhjb25zZW5zdXMuc3RlYWR5LnN0YXRlLCBuID0gMTIpCmBgYAoKQXMgd2UgY2FuIHNlZSwgZXZlbiBmb3IgdGhlIGZpcnN0IDEyIG5vZGVzLCB0aGUgY29uc2Vuc3VzIHN0ZWFkeSBzdGF0ZSBpcyAKZGlmZmVyZW50IGNvbXBhcmVkIHRvIHRoZSBleGFtcGxlIHN0YWJsZSBzdGF0ZSBvZiB0aGUgZmlyc3QgbW9kZWwuCgojIyBBbmFseXNpcyB7LX0KCkNvbnRpbnVpbmcsICoqd2UgY29tcGFyZSBlYWNoIG9mIHRoZSBtb2RlbHMnIHN0YWJsZSBzdGF0ZSB0byB0aGUgY29uc2Vuc3VzIG9uZSBhbmQgCmZpbmQgdGhlIHBlcmNlbnRhZ2Ugb2YgbWF0Y2hlcyBmb3IgZWFjaCBtb2RlbCAoZml0bmVzcyB2YWx1ZSkqKi4gVGhlbiwgd2UgCmNhbGN1bGF0ZSB0aGUgcGVyY2VudGFnZSBvZiBtb2RlbHMgdGhhdCBoYWQgYSBmaXRuZXNzIG1hdGNoIGxhcmdlciB0aGFuCmEgdGhyZXNob2xkIHZhbHVlIChlLmcuICQwLjU1JCBvciBtb3JlIHRoYW4gJDU1XCUkIG1hdGNoKSBhbmQgd2UgcGxvdCB0aGUgbW9kZWxzJyAKcGVyY2VudGFnZSByZXN1bHRzIHdoaWxlIHJhbmdpbmcgdGhlIGZpdG5lc3MgdGhyZXNob2xkIHZhbHVlIGZyb20gMCB0byAkMTAwXCUkOgpgYGB7ciBNb2RlbCBFeGNlZWRhbmNlLCByZXN1bHRzID0gJ2FzaXMnfQptb2RlbHMuZml0bmVzcyA9IAogIGFwcGx5KG1vZGVscy5zdGFibGUuc3RhdGUsIDEsIGdldF9wZXJjZW50YWdlX29mX21hdGNoZXMsIGNvbnNlbnN1cy5zdGVhZHkuc3RhdGUpCgptaW4uZml0bmVzcyA9IG1pbihtb2RlbHMuZml0bmVzcykKbWF4LmZpdG5lc3MgPSBtYXgobW9kZWxzLmZpdG5lc3MpCnN0cmluZyA9IHBhc3RlMCgiTWluaW11bSBmaXRuZXNzOiAiLCAKICAgICAgICAgICAgICAgIHNwZWNpZnlfZGVjaW1hbChtaW4uZml0bmVzcywgZGlnaXRzLnRvLmtlZXAgPSA0KSwgIiwgIiwgCiAgICAgICAgICAgICAgICAiTWF4aW11bSBmaXRuZXNzOiAiLCAKICAgICAgICAgICAgICAgIHNwZWNpZnlfZGVjaW1hbChtYXguZml0bmVzcywgZGlnaXRzLnRvLmtlZXAgPSA0KSkKcHJldHR5X3ByaW50X3N0cmluZyhzdHJpbmcpCmBgYAoKYGBge3IgTW9kZWwgRXhjZWVkYW5jZSBmaWd1cmUsIHJlc3VsdHMgPSAnYXNpcyd9CmZpdG5lc3MucmFuZ2UgPSBzZXEoMCwgMSwgMC4wMDEpCm1vZGVscy5wZXJjZW50YWdlID0gc2FwcGx5KGZpdG5lc3MucmFuZ2UsIGZ1bmN0aW9uKGZpdG5lc3MudGhyZXNob2xkKSB7CiAgcmV0dXJuKHN1bShtb2RlbHMuZml0bmVzcyA+IGZpdG5lc3MudGhyZXNob2xkKS9udW1iZXIub2YubW9kZWxzKQp9KQoKeS5heGlzLnZhbHVlcyA9IHByZXR0eShtb2RlbHMucGVyY2VudGFnZSkKcGxvdChmaXRuZXNzLnJhbmdlLCBtb2RlbHMucGVyY2VudGFnZSwgdHlwZSA9ICJsIiwgeWF4dCA9ICJuIiwKICAgICB4bGFiID0gIkZpdG5lc3MgVGhyZXNob2xkIHZhbHVlIiwKICAgICB5bGFiID0gIlBlcmNlbnRhZ2Ugb2YgbW9kZWxzIiwKICAgICBtYWluID0gIk1vZGVsIEV4Y2VlZGFuY2Ugb2YgZml0bmVzcyB0aHJlc2hvbGQgdmFsdWUiKQpheGlzKDIsIGF0ID0geS5heGlzLnZhbHVlcywgbGFiID0gcGFzdGUoeS5heGlzLnZhbHVlcyAqIDEwMCwgIiUiKSwgbGFzID0gMSkKYGBgCgpBcyB3ZSBjYW4gc2VlIGluIHRoZSBmaWd1cmUgYWJvdmUsIHRoZSBtb2RlbHMgZ2VuZXJhdGVkIGJ5IHRoZSBldm9sdXRpb25hcnkgCmFsZ29yaXRobSBvZiBgR2l0c2JlYCAtIHdoaWNoIGFyZSBleGFjdGx5IHRoZSBiZXN0IGZpdHRpbmcgb25lcyB0byB0aGUgY29uc2Vuc3VzCnN0ZWFkeSBzdGF0ZSBwYXR0ZXJuIGFjcm9zcyBhbGwgc2ltdWxhdGlvbnMgLSBoYXZlIGEgKipmaXRuZXNzIHZhbHVlIGJldHdlZW4gCjAuNTQgYW5kIDAuNjUqKiwgbWVhbmluZyB0aGF0IGluIGFsbCBtb2RlbHMsIG1vcmUgdGhhbiBoYWxmIG9mIHRoZSBuZXR3b3JrIG5vZGVzCnN0YWJpbGl6ZSBpbiB0aGUgc2FtZSBhY3Rpdml0eSBzdGF0ZSBhcyB0aGUgb25lIGZyb20gdGhlIGNvbnNlbnN1cyBzdGVhZHkgc3RhdGUgCigxMDAlIG9mIHRoZSBtb2RlbHMgZXhoaWJpdCBhIG1hdGNoIG9mIG1vcmUgdGhhbiA1MCUgdG8gdGhlIGNvbnNlbnN1cyBzdGVhZHkgCnN0YXRlKS4gVXNpbmcgdGhlIHNhbWUgbG9naWMgYW5kIHRoZSBmaWd1cmUgYWJvdmUsIHdlIGNhbiBzZWUgKip0aGF0IDUwJSBvZiB0aGUgCm1vZGVscyBleGhpYml0IGEgbWF0Y2ggb2YgbW9yZSB0aGFuIDYzLjQlKiouIFNvLCB0aGUgbW9kZWxzIHByb2R1Y2VkIGluIHRoaXMgCmFuYWx5c2lzLCB3aGlsZSB0cmFpbmVkIHRvIGEgc3BlY2lmaWMgc3RlYWR5IHN0YXRlIHNpZ25hbGluZyBwYXR0ZXJuLCB0aGV5IGNvdmVyCmEgYnJvYWRlciBzZXQgb2YgcG90ZW50aWFsIHN0YXRlcywgYWxsb3dpbmcgdGh1cyBhIG1vcmUgcm9idXN0IGRydWcgcmVzcG9uc2UgCmFuYWx5c2lzIGluIHRoZSByZXNwZWN0aXZlIG1vZHVsZSBvZiB0aGUgRHJ1Z0xvZ2ljcyBwaXBlbGluZSAoYERyYWJtZWApLgoKYGBge3IgRGlzdHJpYnV0aW9uIG9mIGZpdG5lc3MsIHJlc3VsdHMgPSAnYXNpcyd9CnN0YXRlcyA9IGFwcGx5KG1vZGVscy5zdGFibGUuc3RhdGUsIDEsIHBhc3RlLCBjb2xsYXBzZSA9ICIiKQoKbW9kZWwuY291bnQucGVyLmZpdG5lc3MgPSB0YWJsZShtb2RlbHMuZml0bmVzcykKbnVtYmVyLm9mLnVuaXF1ZS5maXRuZXNzLnZhbHVlcyA9IGxlbmd0aChtb2RlbC5jb3VudC5wZXIuZml0bmVzcykKbnVtYmVyLm9mLnVuaXF1ZS5zdGFibGUuc3RhdGVzID0gbGVuZ3RoKHVuaXF1ZShzdGF0ZXMpKQoKcHJldHR5X3ByaW50X3N0cmluZyhwYXN0ZTAoIlVuaXF1ZSBmaXRuZXNzIHZhbHVlczogIiwgCiAgbnVtYmVyLm9mLnVuaXF1ZS5maXRuZXNzLnZhbHVlcywgIiwgVW5pcXVlIHN0YWJsZSBzdGF0ZXMgZm91bmQgYnkgdGhlIG1vZGVsczogIiwgCiAgbnVtYmVyLm9mLnVuaXF1ZS5zdGFibGUuc3RhdGVzKSkKCnkuYXhpcy52YWx1ZXMgPSBwcmV0dHkobW9kZWwuY291bnQucGVyLmZpdG5lc3MpCnBsb3QobmFtZXMobW9kZWwuY291bnQucGVyLmZpdG5lc3MpLCBtb2RlbC5jb3VudC5wZXIuZml0bmVzcywgCiAgICAgdHlwZSA9ICJiIiwgIHlheHQgPSAibiIsCiAgICAgeGxhYiA9ICJGaXRuZXNzIiwKICAgICB5bGFiID0gIk51bWJlciBvZiBtb2RlbHMiLAogICAgIG1haW4gPSAiRGlzdHJpYnV0aW9uIG9mIGZpdG5lc3MgYW1vbmcgbW9kZWxzIikKYXhpcygyLCBhdCA9IHkuYXhpcy52YWx1ZXMsIGxhcyA9IDEpCmBgYAoKTm90ZSB0aGF0IHRoZXJlIGFyZSAqKmByIG51bWJlci5vZi51bmlxdWUuZml0bmVzcy52YWx1ZXNgIHVuaXF1ZSBmaXRuZXNzIHZhbHVlcyoqIAppbmNsdWRlZCBpbiB0aGUgWzAuNTQsIDAuNjVdIHJhbmdlLCB3aGlsZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZXNlIHZhbHVlcyBhbW9uZyAKdGhlIG1vZGVscyBjYW4gYmUgc2VlbiBpbiB0aGUgZmlndXJlIGFib3ZlLiBBbG1vc3QgKiozMDAwIG1vZGVscyBoYWQgYSBmaXRuZXNzIApvZiB+MC42NCoqIHRvIHRoZSBjb25zZW5zdXMgc3RlYWR5IHN0YXRlIHBhdHRlcm4sIG1hcmtpbmcgdGhlIGFyZWEgb2YgdGhlIGhpZ2hlc3QgCmZpdG5lc3MgbW9kZWxzIGluIHRoZSBmaWd1cmUuIEZ1cnRoZXJtb3JlLCAqKnRoZSBudW1iZXIgb2YgdW5pcXVlIHN0YWJsZSBzdGF0ZXMgCmZvdW5kIGJ5IHRoZSBtb2RlbHMgaXMgYHIgbnVtYmVyLm9mLnVuaXF1ZS5zdGFibGUuc3RhdGVzYCoqLiBUaGlzIG1lYW5zIHRoYXQgZm9yIAphIHNwZWNpZmljIGZpdG5lc3MgdmFsdWUgdGhlcmUgd2lsbCBiZSBtYW55IG1vZGVscyB0aGF0IGhhdmUgYSBzdGFibGUgc3RhdGUgCmNvcnJlc3BvbmRpbmcgdG8gaXQgKGhhdmluZyB0aGUgc2FtZSBwZXJjZW50YWdlIG9mIG1hdGNoZXMgZm9yIHRoZSBhY3Rpdml0eSAKc3RhdGVzIG9mIHRoZSBuZXR3b3JrIG5vZGVzIGNvbXBhcmVkIHRvIHRoZSBjb25zZW5zdXMgc3RhdGUpLCBidXQgdGhlIGFjdGl2aXR5IApvZiB0aGUgbm9kZXMgYmV0d2VlbiB0aGVzZSBtb2RlbHMgd2lsbCBiZSBkaWZmZXJlbnQsIGFsbG93aW5nIGEgbW9yZSBkaXZlcnNlIAphdHRyYWN0b3IgbGFuZHNjYXBlIHRvIG1hdGNoIGEgc3BlY2lmaWMgZml0bmVzcy4gVG8gc2VlIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gCnRoZSBzdGFibGUgc3RhdGVzIG9mIHRoZSBwcm9kdWNlZCBtb2RlbHMgcmVsYXRpbmcgdG8gZml0bmVzcywgd2UgcHJvZHVjZSB0aGUgCm5leHQgaGVhdG1hcDoKCmBgYHtyIE1vZGVscyBTdGFibGUgU3RhdGVzIEhlYXRtYXAsIGZpZy5oZWlnaHQgPSA3LCBmaWcud2lkdGggPSA5LCBkcGkgPSAzMDAsIGNhY2hlPVRSVUV9CiMgY29sb3JpbmcKYWN0aXZpdHkuY29sb3JzID0gYygicmVkIiwgImxpZ2h0eWVsbG93IikKYWN0aXZpdHkuY29sLmZ1biA9IGNvbG9yUmFtcDIoYnJlYWtzID0gYygwLCAxKSwgY29sb3JzID0gYWN0aXZpdHkuY29sb3JzKQpmaXRuZXNzLmNvbC5mdW4gPSAKICBjb2xvclJhbXAyKGJyZWFrcyA9IGMobWluLmZpdG5lc3MsIChtaW4uZml0bmVzcyArIG1heC5maXRuZXNzKS8yLCBtYXguZml0bmVzcyksIAogICAgICAgICAgICAgY29sb3JzID0gYygicmVkIiwgImJsYWNrIiwgImdyZWVuIikpCgojIGRlZmluZSB0aGUgZml0bmVzcyBjb2xvciBiYXIKZml0bmVzcy5hbm5vdCA9IHJvd0Fubm90YXRpb24oCiAgZml0bmVzcyA9IGFubm9fc2ltcGxlKHggPSBtb2RlbHMuZml0bmVzcywgY29sID0gZml0bmVzcy5jb2wuZnVuKSwgCiAgICAgICAgICAgIHNob3dfYW5ub3RhdGlvbl9uYW1lID0gRkFMU0UpCgpoZWF0bWFwLnNzID0gCiAgSGVhdG1hcChtYXRyaXggPSBtb2RlbHMuc3RhYmxlLnN0YXRlLCBjb2x1bW5fdGl0bGUgPSAiTW9kZWxzIFN0YWJsZSBTdGF0ZXMiLAogICAgY29sdW1uX3RpdGxlX2dwID0gZ3Bhcihmb250c2l6ZSA9IDIwKSwgY29sdW1uX25hbWVzX2dwID0gZ3Bhcihmb250c2l6ZSA9IDQpLAogICAgcm93X2RlbmRfd2lkdGggPSB1bml0KDEsICJpbmNoZXMiKSwgY29sdW1uX2RlbmRfaGVpZ2h0ID0gdW5pdCgxLCAiaW5jaGVzIiksCiAgICBjb2wgPSBhY3Rpdml0eS5jb2wuZnVuLCBzaG93X3Jvd19uYW1lcyA9IEZBTFNFLAogICAgbGVmdF9hbm5vdGF0aW9uID0gZml0bmVzcy5hbm5vdCwgc2hvd19oZWF0bWFwX2xlZ2VuZCA9IEZBTFNFLAogICAgdXNlX3Jhc3RlciA9IFRSVUUsIHJhc3Rlcl9kZXZpY2UgPSAicG5nIiwgcmFzdGVyX3F1YWxpdHkgPSAyMCkKCiMgZGVmaW5lIHRoZSAyIGxlZ2VuZHMKYWN0aXZpdHkuc3RhdGUubGVnZW5kID0gTGVnZW5kKHRpdGxlID0gIkFjdGl2aXR5IFN0YXRlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkluaGliaXRlZCIsICJBY3RpdmUiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlZ2VuZF9ncCA9IGdwYXIoZmlsbCA9IGFjdGl2aXR5LmNvbG9ycykpCmZpdG5lc3MubGVnZW5kID0gTGVnZW5kKHRpdGxlID0gIkZpdG5lc3MiLCBjb2wgPSBmaXRuZXNzLmNvbC5mdW4pCmxlZ2VuZC5saXN0ID0gcGFja0xlZ2VuZChhY3Rpdml0eS5zdGF0ZS5sZWdlbmQsIGZpdG5lc3MubGVnZW5kLCAKICAgICAgICAgICAgICAgICAgICAgICAgIGRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIpCgpkcmF3KGhlYXRtYXAuc3MsIGFubm90YXRpb25fbGVnZW5kX2xpc3QgPSBsZWdlbmQubGlzdCwgCiAgICAgYW5ub3RhdGlvbl9sZWdlbmRfc2lkZSA9ICJyaWdodCIpCmBgYAoKSW4gdGhlIGhlYXRtYXAgYWJvdmUsIGFuIGFjdGl2aXR5IHN0YXRlIGVxdWFsIHRvIHplcm8gaXMgcmVwcmVzZW50ZWQgd2l0aCByZWQgCmNvbG9yIHdoaWxlIGFuIGFjdGl2aXR5IHN0YXRlIGVxdWFsIHRvIDEgd2l0aCBsaWdodCB5ZWxsb3cuIFdlIG5vdGUgdGhlIHByZXZhbGVuY2Ugb2YgCnRoZSBoaWdoZXN0IGZpdG5lc3MgbW9kZWxzIGFjcm9zcyB0aGUgaGVhdG1hcCBhcyB3ZWxsIHRoZWlyIGRpZmZlcmVuY2VzIHJlZ2FyZGluZwp0aGUgYWN0aXZpdHkgc3RhdGVzIG9mIHRoZSBuZXR3b3JrIG5vZGVzLiBUaG91Z2ggdGhpcyBzYW1wbGUgb2YgNzUwMCBtb2RlbHMgCnJlcHJlc2VudHMgdGhlICpiZXN0KiBtb2RlbHMgZ2VuZXJhdGVkIHRocm91Z2ggYSBnZW5ldGljIGFsZ29yaXRobSB0aGF0IHJlc3VsdHMgCmluIGhpZ2hlciBmaXRuZXNzIG1vZGVscyBnZXR0aW5nIHNlbGVjdGVkIGJlY2F1c2UgdGhleSBhcmUgbW9yZSAqY2xvc2UqIHRvIHRoZSAKY29uc2Vuc3VzIHN0ZWFkeSBzdGF0ZSBwYXR0ZXJuLCB0aGV5IHN0aWxsIHNob3cgKipkaXZlcnNlIGluZGl2aWR1YWwgc3RhYmxlIHN0YXRlIApwYXR0ZXJuIGNoYXJhY3RlcmlzdGljcyoqLgoKIyMgUiBzZXNzaW9uIGluZm8gey19CgpgYGB7ciBzZXNzaW9uIGluZm8sIGNvbW1lbnQ9IiJ9CnhmdW46OnNlc3Npb25faW5mbygpCmBgYA==