Setup

ihc_table_path <- snakemake@input$ihc_table
xcr_table_path <- snakemake@input$xcr_table
molecular_subtype_file <- snakemake@input$molsubtypes
graph_rds_path <- snakemake@input$xcr_graphs

db_path <- snakemake@params$db
tils_for_cluster <- unlist(snakemake@params$tils_for_cluster)
all_tiltypes <- unlist(snakemake@params$all_tiltypes)
library(ithi.utils)
load_base_libs()

library(igraph)
library(tidygraph)
library(ggraph)
library(entropy)
library(vegan)

library(stepPlr)
library(nnet)
library(caret)

library(ithi.meta)
library(ithi.xcr)
annotation_colours <- ithi.figures::get_annotation_colours()

ihc_table <- fread(ihc_table_path)
xcr_table <- read_clonotypes(xcr_table_path, duplicates = FALSE, db_path)

Read 0.0% of 304822 rows
Read 32.8% of 304822 rows
Read 72.2% of 304822 rows
Read 304822 rows and 18 (of 18) columns from 0.070 GB file in 00:00:06
molsubtypes <- fread(molecular_subtype_file)

til_clusters <- ithi.figures:::get_til_clusters(ihc_table, molsubtypes, tils_for_cluster = tils_for_cluster, 
    nclusts = 3)
til_clusters$patient_id <- ithi.meta::map_id(til_clusters$condensed_id, from = "condensed_id", 
    to = "patient_id", db_path)

Graph creation

The structure of these graphs is:

  • Vertices/nodes are SHARED TCR/BCR clonotypes (separate graphs for TCR/BCR and one for each patient). Private clonotypes (those present in only 1 sample) are not included.
  • Edges connect each pair of clonotypes that are shared in the same samples. e.g. if clonotype A is found in samples Om1 and Om2, and clonotype B is found in samples Om1, Om2, and ROv1, there’s an edge between A and B.
## Refactor this if it works Probably best to plot top X per sample -- works
## more effectively
construct_xcr_graph <- function(xcr_clones, nodes = "xcrclone", top_n = Inf) {
    sample_membership <- xcr_clones %>% group_by(id) %>% do(samples = unique(as.character(.$condensed_id)), 
        total_count = sum(.$count), total_freq = sum(.$count)/sum(xcr_clones$count))
    sample_membership$total_count <- unlist(sample_membership$total_count)
    sample_membership$total_freq <- unlist(sample_membership$total_freq)
    sample_membership$numeric_id <- paste0("id_", factor(sample_membership$id) %>% 
        as.numeric)
    
    top_n <- min(top_n, nrow(sample_membership))
    
    
    sample_membership_shared <- sample_membership[sapply(sample_membership$samples, 
        length) > 1, ]
    sample_membership_shared <- sample_membership_shared[order(sample_membership_shared$total_freq), 
        ][1:top_n, ]
    sample_membership_shared$index <- as.numeric(factor(sample_membership_shared$numeric_id, 
        levels = sort(unique(sample_membership_shared$numeric_id))))
    
    nodes <- sample_membership_shared %>% subset(select = c("index", "numeric_id", 
        "id")) %>% plyr::rename(c(id = "clonotype"))
    
    
    unique_samples <- xcr_clones$condensed_id %>% unique %>% as.character
    sample_pairs <- combn(unique_samples, 2)
    
    edges <- lapply(1:ncol(sample_pairs), function(i) {
        pair <- sample_pairs[, i]
        contained_nodes <- sample_membership_shared[sapply(sample_membership_shared$samples, 
            function(x) all(pair %in% x)), ]
        if (length(contained_nodes$index) > 1) {
            pair_edges <- data.frame(combn(contained_nodes$index, 2) %>% t, 
                stringsAsFactors = FALSE) %>% plyr::rename(c(X1 = "from", X2 = "to"))
            return(data.frame(pair_edges, pair = paste(sort(pair), collapse = ","), 
                stringsAsFactors = FALSE))
        } else {
            return(data.frame())
        }
    }) %>% rbind.fill
    
    g <- tbl_graph(nodes = nodes, edges = edges, directed = TRUE)
    
    return(g)
}
calculate_graph_measures <- function(g) {
  ig <- as.undirected(as.igraph(g))
  
  louvain_cluster <- cluster_louvain(ig)
  #spinglass_cluster <- cluster_spinglass(ig) ## doesn't work on unconnected graphs
  maximal_cliques <- maximal.cliques(ig)
  
  
  graph_attributes <- list(
    ## Basic properties, i.e. # edges, # vertices
    num_vertices=length(V(ig)),
    num_edges=length(E(ig)),
    num_simple_edges=length(E(simplify(ig))),
    
    ## Centralization measures
    centr_degree=centr_degree(ig)$centralization,
    centr_degree_max=centr_degree_tmax(ig),
    centr_eigen=centr_eigen(ig)$centralization,
    centr_eigen_max=centr_eigen_tmax(ig),
    centr_clo=centr_clo(ig)$centralization,
    centr_clo_tmax=centr_clo_tmax(ig),
    centr_betw=centr_betw(ig, directed = FALSE)$centralization,
    centr_betw_tmax=centr_betw_tmax(ig, directed = FALSE),
    
    ## Community measures
    cluster_louvain_n=length(louvain_cluster),
    #cluster_spinglass_n=length(spinglass_cluster),
    cluster_louvain_size=unname(max(table(louvain_cluster$membership))),
    #cluster_spinglass_size=unname(max(table(spinglass_cluster$membership))),
    cluster_louvain_entropy=entropy::entropy(table(louvain_cluster$membership)),
    cluster_louvain_simpson=vegan::diversity(table(louvain_cluster$membership), index = "invsimpson"),
    
    ## Global transitivity
    transitivity_undirected=transitivity(ig, type = "globalundirected"),
    
    ## Assortativity
    assortativity_degree=assortativity_degree(ig),
    
    ## Density
    density=edge_density(ig),
    
    ## Cliques
    maximal_cliques_n=maximal.cliques.count(ig),
    maximal_clique_size=clique.number(ig),
    maximal_clique_entropy=entropy::entropy(sapply(maximal_cliques, length)),
    maximal_clique_simpson=vegan::diversity(sapply(maximal_cliques, length), index = "invsimpson"),
    
    ## Automorphisms
    #automorphisms_n=as.numeric(automorphisms(ig)$group_size),
    
    ## Multiple edges
    multiple_prop=length(which(igraph::count.multiple(ig) > 1))/length(igraph::count.multiple(ig))
  )
  
  return(graph_attributes)
}

plot_xcr_graph <- function(g) {
  g %>% ggraph(layout = 'kk') + geom_edge_link(aes(colour=pair)) + geom_node_point() + scale_colour_continuous(guide = 'legend') + theme_graph()
}
segments <- unique(xcr_table$type)
patients <- sort(unique(xcr_table$patient_id))

seg_results <- lapply(segments, function(segtype) {
    print(segtype)
    pat_results <- lapply(patients, function(pat) {
        print(pat)
        xcr_clones <- subset(xcr_table, patient_id == pat & type == segtype)
        g_small <- construct_xcr_graph(xcr_clones, nodes = "xcrclone", top_n = 50)
        g_full <- construct_xcr_graph(xcr_clones, nodes = "xcrclone")
        attributes <- calculate_graph_measures(g_full)
        return(list(g_small = g_small, g_full = g_full, attributes = attributes))
    })
    names(pat_results) <- patients
    return(pat_results)
})
names(seg_results) <- segments
saveRDS(seg_results, file = "~/shahlab/projects/ITH_Immune/paper/review/data_objects/xcr_graph_results.rds")

We don’t have time to wait for that to finish (it takes 30 minutes or so), so:

seg_results <- readRDS(graph_rds_path)

Example graph

This is a graph subsampled from patient 2’s TCRs. These are the 50 most prevalent (according to read count) shared clonotypes.

plot_xcr_graph(seg_results$TRB$`2`$g_small)

Graph attribute analysis

graph_attribute_table <- lapply(names(seg_results), function(seg) {
    x <- seg_results[[seg]]
    patient_attribute_table <- lapply(names(x), function(pat) {
        y <- x[[pat]]
        data.frame(patient_id = pat, as.data.frame(y$attributes), stringsAsFactors = FALSE)
    }) %>% rbind.fill
    return(data.frame(type = seg, patient_attribute_table, stringsAsFactors = FALSE))
}) %>% rbind.fill

graph_attribute_table$patient_id <- ithi.meta::factor_id(graph_attribute_table$patient_id, 
    type = "patient_id", db_path)
graph_attribute_table$centr_degree_normalized <- with(graph_attribute_table, 
    centr_degree/centr_degree_max)
graph_attribute_table$centr_eigen_normalized <- with(graph_attribute_table, 
    centr_eigen/centr_eigen_max)
graph_attribute_table$centr_betw_normalized <- with(graph_attribute_table, centr_betw/centr_betw_tmax)
graph_attribute_table$centr_clo_normalized <- with(graph_attribute_table, centr_clo/centr_clo_tmax)

## NOTE that simpson = inverse_simpson here.  Should add entropy and simpson
## for cluster_louvain

id_vars <- c("type", "patient_id")
measure_vars <- setdiff(colnames(graph_attribute_table), id_vars)
graph_attribute_table_melted <- reshape2::melt(graph_attribute_table, id.vars = id_vars, 
    measure.vars = measure_vars, variable.name = "measure", value.name = "value")

Comparison with other covariates

TIL densities

ihc_table_melted <- reshape2::melt(ihc_table, id.vars = c("condensed_id", "patient_id"), 
    measure.vars = all_tiltypes, variable.name = "tiltype", value.name = "density")
xcr_samples <- xcr_table$condensed_id %>% unique
ihc_table_melted <- subset(ihc_table_melted, condensed_id %in% xcr_samples)

ihc_table_means <- ihc_table_melted %>% group_by(patient_id, tiltype) %>% summarise(density = mean(density, 
    na.rm = TRUE))
ihc_table_means$patient_id <- ithi.meta::factor_id(ihc_table_means$patient_id, 
    type = "patient_id", db_path)
ihc_graph <- plyr::join(ihc_table_means %>% as.data.frame, graph_attribute_table_melted %>% 
    as.data.frame, by = "patient_id")
pvals <- ithi.utils::compute_pvals_subsets(ihc_graph, facet_vars = c("tiltype", 
    "type", "measure"), formula = ~density + value, corfun = cor.test, method = "spearman")

subset(pvals, p.adj < 0.05)

Example plot of one of the significant correlations

ggplot(ihc_graph %>% subset(type == "TRB" & tiltype == "E_CD8_density" & measure == 
    "cluster_louvain_size"), aes(x = density, y = value)) + geom_point(aes(colour = factor(patient_id))) + 
    scale_color_manual(values = annotation_colours$patient_id) + theme_bw() + 
    theme_Publication() + theme_nature()

It seems like several of these graph measures correlate very well with each TIL type’s density.

with(subset(pvals, p.adj < 0.05), table(measure))
measure
           num_vertices               num_edges        num_simple_edges 
                     10                      12                      12 
           centr_degree        centr_degree_max             centr_eigen 
                      8                      10                       0 
        centr_eigen_max               centr_clo          centr_clo_tmax 
                     10                       3                      10 
             centr_betw         centr_betw_tmax       cluster_louvain_n 
                      0                      10                      10 
   cluster_louvain_size cluster_louvain_entropy cluster_louvain_simpson 
                     12                       6                       7 
transitivity_undirected    assortativity_degree                 density 
                      1                       1                       6 
      maximal_cliques_n     maximal_clique_size  maximal_clique_entropy 
                     10                      11                       0 
 maximal_clique_simpson           multiple_prop centr_degree_normalized 
                      0                       0                       6 
 centr_eigen_normalized   centr_betw_normalized    centr_clo_normalized 
                     10                       3                      10 

And these seem to be the same measures. Simple measures like the number of vertices (total # unique clonotypes for a patient) and the number of edges (number of pairwise ‘sharings’ between samples for the same clonotype – this is biased by # of samples per patient) do very well.

In terms of what seems to be important:

  • Centralization doesn’t seem to be correlated with the most here. The theoretical maximum of these values for a star phylogeny with the same number of vertices (*_max and *_tmax) have more correlations than the centralization values themselves – indicating that the influence of the number of vertices/edges likely dominates here.
  • Likewise, while maximum clique size is well correlated, entropy and Inverse simpson indices of the clique size distribution are not. The purpose of these latter measures is to reduce/eliminate bias from the graph size influencing clique size. After accounting for that, clique structure isn’t corrleated either.
  • I’m currently re-running with similar, normalized measures for graph modularity (cluster_louvain) – will update/look at the above when that’s done. I’d wager it’s going to be the same as the above point.

I think it’s safe to say that ‘simple’ measures like the number of vertices/edges are better predictors of TIL density. That explains as well why TCR/BCR repertoire similarity (which is effectively related to the number of edges) does so well in correlations with TIL density.

TIL cluster type

We might be interested in predicting whether or not a patient is ES_pure, ES_mixed, or ES_none – since we think these potentially have prognostic implications.

range01 <- function(tab, ...) {
    apply(tab, 2, function(x) (x - min(x, ...))/(max(x, ...) - min(x, ...)))
}
til_clusters_subset <- til_clusters %>% subset(condensed_id %in% xcr_samples)
recurrence_samples <- c("7_BrnM", "7_RPvM", "11_Pv1", "11_Rct1", "11_Rct2", 
    "23_LOv1")
til_clusters_subset <- subset(til_clusters_subset, !condensed_id %in% recurrence_samples)

es_subtypes <- til_clusters_subset %>% group_by(patient_id) %>% summarise(ES_pure = all(cluster == 
    "ES-TIL"), ES_none = !any(cluster == "ES-TIL"), ES_mixed = (any(cluster == 
    "ES-TIL") & !all(cluster == "ES-TIL")))
es_subtypes$subtype <- colnames(es_subtypes)[2:4][sapply(1:nrow(es_subtypes), 
    function(i) which(as.logical(es_subtypes[i, 2:4])))]
es_subtypes$subtype_simple <- es_subtypes$subtype %>% plyr::mapvalues(c("ES_pure", 
    "ES_mixed", "ES_none"), c("ES", "ES", "notES"))
feature_predictors <- c("centr_eigen_normalized", "density", "cluster_louvain_size", 
    "maximal_clique_simpson", "transitivity_undirected", "assortativity_degree")

feature_list <- setdiff(feature_predictors, id_vars)
formula_str <- paste(feature_list, collapse = " + ")

trb_features <- subset(graph_attribute_table, type == "TRB", select = -c(type))
es_subtypes_trb <- plyr::join(trb_features, es_subtypes, by = "patient_id")

es_subtypes_trb <- es_subtypes_trb %>% subset(!is.na(subtype))
es_subtypes_trb[, feature_list] <- range01(es_subtypes_trb[, feature_list])

Penalized multinomial regression

es_subtypes_trb_multinom <- nnet::multinom(formula = as.formula(paste0("subtype ~ ", 
    formula_str)), data = es_subtypes_trb, decay = 0.01)
# weights:  24 (14 variable)
initial  value 19.775021 
iter  10 value 11.815428
iter  20 value 11.732515
iter  30 value 11.731501
iter  40 value 11.731469
final  value 11.731468 
converged
es_subtypes_trb_multinom
Call:
nnet::multinom(formula = as.formula(paste0("subtype ~ ", formula_str)), 
    data = es_subtypes_trb, decay = 0.01)

Coefficients:
        (Intercept) centr_eigen_normalized   density cluster_louvain_size
ES_none  0.07050054               3.465603 -1.606186           -2.2384131
ES_pure  0.28390962              -2.233819  1.578687            0.5057187
        maximal_clique_simpson transitivity_undirected
ES_none              0.1940997                4.032051
ES_pure              1.5492960               -3.632376
        assortativity_degree
ES_none            -2.473398
ES_pure            -1.963789

Residual Deviance: 23.46294 
AIC: 51.46294 
es_subtypes_trb_multinom$fitted.values %>% apply(1, which.max)
 1  2  3  4  6  7  8 10 11 12 13 14 15 16 17 19 20 21 
 3  2  2  1  3  2  2  2  2  2  3  2  2  2  2  2  3  2 

When decay isn’t added, seems to be overfitting. Rarely predicts ES-mixed (class 1) if decay is added.

varImp(es_subtypes_trb_multinom) %>% rownames_to_column(var = "feature")

Transitivity does the best, which is interesting. Also, we tend to see LOWER transitivity in patients with higher TIL.

So let’s do this more rigorously. Using 4-fold cross validation, 5 times, to fit a multinomial model:

message("Training multinomial model ...")

fitControl <- trainControl(method = "repeatedcv", number = 4, repeats = 5, classProbs = TRUE, 
    summaryFunction = multiClassSummary)
nnetGrid <- expand.grid(decay = 10^seq(from = -5, to = 1, by = 1))  ## regularization

nnetFit <- train(form = as.formula(paste0("subtype ~ ", formula_str)), data = es_subtypes_trb %>% 
    select(c(feature_list, "subtype")) %>% mutate(subtype = factor(subtype)), 
    method = "multinom", metric = "Accuracy", maximize = TRUE, trControl = fitControl, 
    tuneGrid = nnetGrid, verbose = TRUE)
nnetFit
Penalized Multinomial Regression 

18 samples
 6 predictor
 3 classes: 'ES_mixed', 'ES_none', 'ES_pure' 

No pre-processing
Resampling: Cross-Validated (4 fold, repeated 5 times) 
Summary of sample sizes: 14, 14, 13, 13, 13, 13, ... 
Resampling results across tuning parameters:

  decay  logLoss    AUC        prAUC      Accuracy  Kappa        
  1e-05  9.8113597  0.5222222  0.2480556  0.3950    -9.469697e-05
  1e-04  5.7205051  0.5250000  0.2475926  0.3925    -6.721681e-03
  1e-03  2.4503777  0.5722222  0.2355093  0.4475     8.145202e-02
  1e-02  1.1853415  0.6902778  0.2271759  0.5400     1.769544e-01
  1e-01  0.8653243  0.7263889  0.2271296  0.6025     2.302976e-01
  1e+00  0.9373209  0.7180556  0.2331944  0.5700     5.833333e-02
  1e+01  1.0513313  0.7250000  0.2315741  0.5500     0.000000e+00
  Mean_F1    Mean_Sensitivity  Mean_Specificity  Mean_Pos_Pred_Value
  0.6111111  0.3250000         0.6777778         0.4047619          
  0.6111111  0.3222222         0.6763889         0.4047619          
        NaN  0.3555556         0.7111111         0.3796296          
        NaN  0.4250000         0.7347222         0.4000000          
        NaN  0.4694444         0.7416667               NaN          
        NaN  0.3666667         0.6833333               NaN          
        NaN  0.3333333         0.6666667               NaN          
  Mean_Neg_Pred_Value  Mean_Precision  Mean_Recall  Mean_Detection_Rate
  0.6731481            0.4047619       0.3250000    0.1316667          
  0.6623457            0.4047619       0.3222222    0.1308333          
  0.7101852            0.3796296       0.3555556    0.1491667          
  0.7869048            0.4000000       0.4250000    0.1800000          
  0.8192308                  NaN       0.4694444    0.2008333          
  0.9333333                  NaN       0.3666667    0.1900000          
        NaN                  NaN       0.3333333    0.1833333          
  Mean_Balanced_Accuracy
  0.5013889             
  0.4993056             
  0.5333333             
  0.5798611             
  0.6055556             
  0.5250000             
  0.5000000             

Accuracy was used to select the optimal model using the largest value.
The final value used for the model was decay = 0.1.
1e-05

1e-04

1e-03

1e-02

1e-01

1e+00

1e+01

9.8113597

5.7205051

2.4503777

1.1853415

0.8653243

0.9373209

1.0513313

0.5222222

0.5250000

0.5722222

0.6902778

0.7263889

0.7180556

0.7250000

0.2480556

0.2475926

0.2355093

0.2271759

0.2271296

0.2331944

0.2315741

0.3950

0.3925

0.4475

0.5400

0.6025

0.5700

0.5500

-9.469697e-05

-6.721681e-03

 8.145202e-02

 1.769544e-01

 2.302976e-01

 5.833333e-02

 0.000000e+00

0.6111111

0.6111111

      NaN

      NaN

      NaN

      NaN

      NaN

0.3250000

0.3222222

0.3555556

0.4250000

0.4694444

0.3666667

0.3333333

0.6777778

0.6763889

0.7111111

0.7347222

0.7416667

0.6833333

0.6666667

0.4047619

0.4047619

0.3796296

0.4000000

      NaN

      NaN

      NaN

0.6731481

0.6623457

0.7101852

0.7869048

0.8192308

0.9333333

      NaN

0.4047619

0.4047619

0.3796296

0.4000000

      NaN

      NaN

      NaN

0.3250000

0.3222222

0.3555556

0.4250000

0.4694444

0.3666667

0.3333333

0.1316667

0.1308333

0.1491667

0.1800000

0.2008333

0.1900000

0.1833333

0.5013889

0.4993056

0.5333333

0.5798611

0.6055556

0.5250000

0.5000000

We’re not doing so well here. Our best model has an accuracy around 0.6. Looking at the results, it consistently has problems with predicting the ES-mixed cluster, indicating that it can’t really get down to that ‘deep’ of a level. What if we only want to look at 2 clusters – either ES_pure/ES_mixed or ES_none?

Penalized logistic regression

message("Training logistic model ...")

fitControl <- trainControl(method = "repeatedcv", number = 4, repeats = 5, classProbs = TRUE, 
    summaryFunction = twoClassSummary)

plrGrid <- expand.grid(lambda = 10^seq(from = -5, to = 5, by = 1), cp = c("aic", 
    "bic"))  ## regularization

plrFit <- train(form = as.formula(paste0("subtype_simple ~ ", formula_str)), 
    data = es_subtypes_trb %>% select(c(feature_list, "subtype_simple")) %>% 
        mutate(subtype_simple = factor(subtype_simple)), method = "plr", metric = "ROC", 
    maximize = TRUE, trControl = fitControl, tuneGrid = plrGrid)
plrFit
Penalized Logistic Regression 

18 samples
 6 predictor
 2 classes: 'ES', 'notES' 

No pre-processing
Resampling: Cross-Validated (4 fold, repeated 5 times) 
Summary of sample sizes: 14, 13, 13, 14, 13, 14, ... 
Resampling results across tuning parameters:

  lambda  cp   ROC        Sens   Spec     
  1e-05   aic  0.5291667  0.600  0.5250000
  1e-05   bic  0.5291667  0.600  0.5250000
  1e-04   aic  0.5916667  0.600  0.5166667
  1e-04   bic  0.5916667  0.600  0.5166667
  1e-03   aic  0.6875000  0.650  0.6083333
  1e-03   bic  0.6875000  0.650  0.6083333
  1e-02   aic  0.7666667  0.550  0.6666667
  1e-02   bic  0.7666667  0.550  0.6666667
  1e-01   aic  0.8291667  0.475  0.8083333
  1e-01   bic  0.8291667  0.475  0.8083333
  1e+00   aic  0.8750000  0.300  0.9083333
  1e+00   bic  0.8750000  0.300  0.9083333
  1e+01   aic  0.8750000  0.000  1.0000000
  1e+01   bic  0.8750000  0.000  1.0000000
  1e+02   aic  0.8750000  0.000  1.0000000
  1e+02   bic  0.8750000  0.000  1.0000000
  1e+03   aic  0.8750000  0.000  1.0000000
  1e+03   bic  0.8750000  0.000  1.0000000
  1e+04   aic  0.8750000  0.000  1.0000000
  1e+04   bic  0.8750000  0.000  1.0000000
  1e+05   aic  0.8750000  0.000  1.0000000
  1e+05   bic  0.8750000  0.000  1.0000000

ROC was used to select the optimal model using the largest value.
The final values used for the model were lambda = 1e+05 and cp = aic.
1e-05

1e-05

1e-04

1e-04

1e-03

1e-03

1e-02

1e-02

1e-01

1e-01

1e+00

1e+00

1e+01

1e+01

1e+02

1e+02

1e+03

1e+03

1e+04

1e+04

1e+05

1e+05

aic

bic

aic

bic

aic

bic

aic

bic

aic

bic

aic

bic

aic

bic

aic

bic

aic

bic

aic

bic

aic

bic

0.5291667

0.5291667

0.5916667

0.5916667

0.6875000

0.6875000

0.7666667

0.7666667

0.8291667

0.8291667

0.8750000

0.8750000

0.8750000

0.8750000

0.8750000

0.8750000

0.8750000

0.8750000

0.8750000

0.8750000

0.8750000

0.8750000

0.600

0.600

0.600

0.600

0.650

0.650

0.550

0.550

0.475

0.475

0.300

0.300

0.000

0.000

0.000

0.000

0.000

0.000

0.000

0.000

0.000

0.000

0.5250000

0.5250000

0.5166667

0.5166667

0.6083333

0.6083333

0.6666667

0.6666667

0.8083333

0.8083333

0.9083333

0.9083333

1.0000000

1.0000000

1.0000000

1.0000000

1.0000000

1.0000000

1.0000000

1.0000000

1.0000000

1.0000000

Yeah, this does pretty bad at predicting TIL subtype too – and that’s just with binary classes.

So the graph measures we’re using, i.e.

feature_list
[1] "centr_eigen_normalized"  "density"                
[3] "cluster_louvain_size"    "maximal_clique_simpson" 
[5] "transitivity_undirected" "assortativity_degree"   
centr_eigen_normalized

density

cluster_louvain_size

maximal_clique_simpson

transitivity_undirected

assortativity_degree

aren’t very good predictors of TIL subtype.

Summary

  • Critical question: What else can/should we try to predict with these graph-based measures? These are done at the level of entire graphs at the moment (i.e. one graph for each patient). One idea would be to compute ‘vertex set graph measures’ for each tumour sample, using the clonotypes belonging to each sample. That would get us some measure of ‘how does this sample’s clones relate to those from other samples’? Do you have any other ideas/thoughts on this?
LS0tCnRpdGxlOiAiWENSIGdyYXBoLWJhc2VkIGFuYWx5c2lzIgotLS0KICAgICAgICAgICAgICAgICAgICAgICAgYGBge3IsIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgojIyMjIyMjIyBTbmFrZW1ha2UgaGVhZGVyICMjIyMjIyMjCmxpYnJhcnkobWV0aG9kcykKU25ha2VtYWtlIDwtIHNldENsYXNzKAogICAgIlNuYWtlbWFrZSIsCiAgICBzbG90cyA9IGMoCiAgICAgICAgaW5wdXQgPSAibGlzdCIsCiAgICAgICAgb3V0cHV0ID0gImxpc3QiLAogICAgICAgIHBhcmFtcyA9ICJsaXN0IiwKICAgICAgICB3aWxkY2FyZHMgPSAibGlzdCIsCiAgICAgICAgdGhyZWFkcyA9ICJudW1lcmljIiwKICAgICAgICBsb2cgPSAibGlzdCIsCiAgICAgICAgcmVzb3VyY2VzID0gImxpc3QiLAogICAgICAgIGNvbmZpZyA9ICJsaXN0IiwKICAgICAgICBydWxlID0gImNoYXJhY3RlciIKICAgICkKKQpzbmFrZW1ha2UgPC0gU25ha2VtYWtlKAogICAgaW5wdXQgPSBsaXN0KCcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmV2aWV3L2RhdGFfb2JqZWN0cy94Y3JfZ3JhcGhfcmVzdWx0cy5yZHMnLCAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvbW9sc3VidHlwZXMudHN2JywgJ25vdGVib29rcy94Y3JfZ3JhcGguUm1kJywgJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL3hjcl90YWJsZS50c3YnLCAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvaWhjX3RhYmxlLnRzdicsICJ4Y3JfZ3JhcGhzIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmV2aWV3L2RhdGFfb2JqZWN0cy94Y3JfZ3JhcGhfcmVzdWx0cy5yZHMnLCAibm90ZWJvb2siID0gJ25vdGVib29rcy94Y3JfZ3JhcGguUm1kJywgIm1vbHN1YnR5cGVzIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi9tb2xzdWJ0eXBlcy50c3YnLCAieGNyX3RhYmxlIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy90YWJsZXMvcnVuMi94Y3JfdGFibGUudHN2JywgImloY190YWJsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvaWhjX3RhYmxlLnRzdicpLAogICAgb3V0cHV0ID0gbGlzdCgnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvcmV2aWV3L25vdGVib29rcy9ydW4yL3hjcl9ncmFwaC5uYi5odG1sJyksCiAgICBwYXJhbXMgPSBsaXN0KGMoJ0VfQ0Q4X2RlbnNpdHknLCAnRV9DRDRfZGVuc2l0eScsICdFX0NEMjBfZGVuc2l0eScsICdFX1BsYXNtYV9kZW5zaXR5JywgJ1NfQ0Q4X2RlbnNpdHknLCAnU19DRDRfZGVuc2l0eScsICdTX0NEMjBfZGVuc2l0eScsICdTX1BsYXNtYV9kZW5zaXR5JyksICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvbWV0YWRhdGEvZGIvaW1tdW5lX3Byb2plY3Quc3FsaXRlMycsIGMoJ1RfQ0Q4X2RlbnNpdHknLCAnVF9DRDRfZGVuc2l0eScsICdUX0NEMjBfZGVuc2l0eScsICdUX1BsYXNtYV9kZW5zaXR5JywgJ0VfQ0Q4X2RlbnNpdHknLCAnRV9DRDRfZGVuc2l0eScsICdFX0NEMjBfZGVuc2l0eScsICdFX1BsYXNtYV9kZW5zaXR5JywgJ1NfQ0Q4X2RlbnNpdHknLCAnU19DRDRfZGVuc2l0eScsICdTX0NEMjBfZGVuc2l0eScsICdTX1BsYXNtYV9kZW5zaXR5JyksICd4Y3JfZ3JhcGhfYW5hbHlzaXMnLCAidGlsc19mb3JfY2x1c3RlciIgPSBjKCdFX0NEOF9kZW5zaXR5JywgJ0VfQ0Q0X2RlbnNpdHknLCAnRV9DRDIwX2RlbnNpdHknLCAnRV9QbGFzbWFfZGVuc2l0eScsICdTX0NEOF9kZW5zaXR5JywgJ1NfQ0Q0X2RlbnNpdHknLCAnU19DRDIwX2RlbnNpdHknLCAnU19QbGFzbWFfZGVuc2l0eScpLCAiZGIiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9tZXRhZGF0YS9kYi9pbW11bmVfcHJvamVjdC5zcWxpdGUzJywgImFsbF90aWx0eXBlcyIgPSBjKCdUX0NEOF9kZW5zaXR5JywgJ1RfQ0Q0X2RlbnNpdHknLCAnVF9DRDIwX2RlbnNpdHknLCAnVF9QbGFzbWFfZGVuc2l0eScsICdFX0NEOF9kZW5zaXR5JywgJ0VfQ0Q0X2RlbnNpdHknLCAnRV9DRDIwX2RlbnNpdHknLCAnRV9QbGFzbWFfZGVuc2l0eScsICdTX0NEOF9kZW5zaXR5JywgJ1NfQ0Q0X2RlbnNpdHknLCAnU19DRDIwX2RlbnNpdHknLCAnU19QbGFzbWFfZGVuc2l0eScpLCAibmFtZSIgPSAneGNyX2dyYXBoX2FuYWx5c2lzJyksCiAgICB3aWxkY2FyZHMgPSBsaXN0KCksCiAgICB0aHJlYWRzID0gMSwKICAgIGxvZyA9IGxpc3QoJy9zaGFobGFiL2FsemhhbmcvY2x1c3R0bXAvcGFwZXJyZXZpZXcyL25vdGVib29rcy94Y3JfZ3JhcGhfYW5hbHlzaXMubG9nJyksCiAgICByZXNvdXJjZXMgPSBsaXN0KCksCiAgICBjb25maWcgPSBsaXN0KCJiZW5jaG1hcmtkaXIiID0gJy9zaGFobGFiL2FsemhhbmcvYmVuY2htYXJrcy9wYXBlcnJldmlldzInLCAiaWNnY19leHByX21hdCIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL0lDR0MvT1ZBVV9leHByX21hdHJpeC50c3YnLCAiY2xvbGFfcmVzdWx0X2ZpbGUiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL2Nsb2xhL3J1bjUvY2xvbGFfY29uZGVuc2VkX3Jlc3VsdHMvbXVsdF9mYWN0b3IvdHJ1bmNub3JtYWwvY2xvbGFfcmVzdWx0cy50c3YnLCAidGNnYV9wYXBlcl9zdWJ0eXBlc19yYXciID0gJy9zaGFobGFiL2FsemhhbmcvZGF0YS9UQ0dBL1RDR0FfNDg5X1VFLms0LnR4dCcsICJwcmV2YWxlbmNlX3RocmVzaG9sZCIgPSAwLjAxLCAiYXJyYXlfbm1mX3N1YnR5cGVzIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvZGF0YS9leHByZXNzaW9uL2FycmF5L3N1YnR5cGVzL25tZl9zdWJ0eXBlcy50eHQnLCAibG9oaGxhX21pc21hdGNoX3NpdGVfdGhyZXNob2xkIiA9IDUuMCwgImxvZ2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9jbHVzdHRtcC9wYXBlcnJldmlldzInLCAiY2xvbmVfdHJlZXMiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2Nsb25lcy90cmVlX2RhdGEudHN2JywgInRjZ2FfcGFwZXJfc3VidHlwZXNfcG9zc2lsaG91ZXR0ZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL1RDR0EvVENHQV80ODlfVUUuazQucG9zU2lsaG91ZXR0ZS50eHQnLCAiYnJlYWtwb2ludF90YWJsZSIgPSAnL3NoYWhsYWIvYW1jcGhlcnNvbi9wcm9qZWN0cy9pdGgzL2l0aDMvbm90ZWJvb2tzL2Jlc3Bva2UvaXRoX2JyZWFrcG9pbnRzLnRzdicsICJ0aWxzX2Zvcl9jbHVzdGVyIiA9IGMoJ0VfQ0Q4X2RlbnNpdHknLCAnRV9DRDRfZGVuc2l0eScsICdFX0NEMjBfZGVuc2l0eScsICdFX1BsYXNtYV9kZW5zaXR5JywgJ1NfQ0Q4X2RlbnNpdHknLCAnU19DRDRfZGVuc2l0eScsICdTX0NEMjBfZGVuc2l0eScsICdTX1BsYXNtYV9kZW5zaXR5JyksICJjbG9uZV9wcmV2YWxlbmNlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvY2xvbmVzL2Nsb25lX2RhdGEudHN2JywgInZhcmlhYmlsaXR5X3R5cGUiID0gJ3N0YWJpbGl6ZScsICJwYXRpZW50c19mb3JfY2xvbmFsIiA9IGMoMSwgMiwgMywgNCwgNywgOSwgMTAsIDExLCAxMiwgMTMsIDE0LCAxNSwgMTYsIDE3KSwgImZpbm5oZV9waXBlbGluZV9yZXN1bHRzX2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvZmlubmhlL3J1bjEnLCAibm90ZWJvb2tfZGlyIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmVzdWx0cy9yZXZpZXcvbm90ZWJvb2tzL3J1bjInLCAiY2xvbmVfYnJhbmNoX2xlbmd0aHMiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2Nsb25lcy9icmFuY2hfZGF0YS50c3YnLCAidHVtb3VyX3B1cml0eSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvdHVtb3VyX3B1cml0eS50c3YnLCAieGNyX2dyYXBoc19yZHMiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXZpZXcvZGF0YV9vYmplY3RzL3hjcl9ncmFwaF9yZXN1bHRzLnJkcycsICJtbWN0bV9maW5hbF9wYXRpZW50X2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3Jlc3VsdHMvbW1jdG1fcmVzdWx0cy9pdGhfYnktcGF0aWVudF93aXRoLW92JywgInRhYmxlX2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvcmV2aWV3L3RhYmxlcy9ydW4yJywgImxvaGhsYV9pY2djX291dGRpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvbG9oaGxhL3J1bjFfaWNnYycsICJhbGxfdGlsdHlwZXMiID0gYygnVF9DRDhfZGVuc2l0eScsICdUX0NENF9kZW5zaXR5JywgJ1RfQ0QyMF9kZW5zaXR5JywgJ1RfUGxhc21hX2RlbnNpdHknLCAnRV9DRDhfZGVuc2l0eScsICdFX0NENF9kZW5zaXR5JywgJ0VfQ0QyMF9kZW5zaXR5JywgJ0VfUGxhc21hX2RlbnNpdHknLCAnU19DRDhfZGVuc2l0eScsICdTX0NENF9kZW5zaXR5JywgJ1NfQ0QyMF9kZW5zaXR5JywgJ1NfUGxhc21hX2RlbnNpdHknKSwgImltYWdlX3N1bW1hcnkiID0gJy9zaGFobGFiL2FsemhhbmcvZGF0YS9pdGhpL3l1YW5faGVjcl9pbWFnZV9yZXN1bHRzLmNzdicsICJpbWFnZV9zdW1tYXJ5MiIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL2l0aGkveXVhbl9oZWNyX2ltYWdlX3Jlc3VsdHNfMi5jc3YnLCAiaWhjX2ZlYXR1cmVzX291dHB1dCIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvaW50ZXJtZWRpYXRlcy9ydW4yL2loY19mZWF0dXJlc19vdXRwdXQudHh0JywgIml0aF9zdGF0cyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvaXRoX3N0YXRpc3RpY3MudHN2JywgInRvdGFsX3RpbHR5cGVzIiA9IGMoJ1RfQ0Q4X2RlbnNpdHknLCAnVF9DRDRfZGVuc2l0eScsICdUX0NEMjBfZGVuc2l0eScsICdUX1BsYXNtYV9kZW5zaXR5JyksICJtb2xzdWJ0eXBlcyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvbW9sc3VidHlwZXMudHN2JywgInRpbHNfZm9yX3ZhcmlhYmlsaXR5IiA9IGMoJ1RfQ0Q4X2RlbnNpdHknLCAnVF9DRDRfZGVuc2l0eScsICdUX0NEMjBfZGVuc2l0eScsICdUX1BsYXNtYV9kZW5zaXR5JyksICJpY2djX3NwZWNpbWVuIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2RhdGEvSUNHQy9zcGVjaW1lbi50c3YnLCAic252X3RhYmxlIiA9ICcvc2hhaGxhYi9hbWNwaGVyc29uL3Byb2plY3RzL2l0aDMvaXRoMy9ub3RlYm9va3MvYmVzcG9rZS9pdGhfc252cy50c3YnLCAic29tYXRpY19jb2RpbmdfcmVzdWx0X2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvc29tYXRpY19jb2RpbmdfdmFyaWFudHMnLCAidGNnYV9vdl9hbm5vdGF0aW9ucyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL1RDR0EvdGNnYV9vdl9hbm5vdGF0aW9uX3N1cDEzLnR4dCcsICJ4Y3JfdGFibGUiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL3hjcl90YWJsZS50c3YnLCAibmFub3N0cmluZ19hbm5vdGF0aW9ucyIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2RhdGEvZXhwcmVzc2lvbi9uYW5vc3RyaW5nL3BhbmNhbmNlcl9hbm5vdGF0aW9ucy50c3YnLCAicmVtaXh0X2NlbGx1bGFyaXR5X3Bsb2lkeSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvcmVtaXh0X2NlbGx1bGFyaXR5X3Bsb2lkeS50c3YnLCAiaXRoX2ljZ2NfYmMiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2l0aF9pY2djX21lcmdlZF9iYy50c3YnLCAidGlsX2NsdXN0ZXJzX291dHB1dCIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvaW50ZXJtZWRpYXRlcy9ydW4yL3RpbF9jbHVzdGVyc19vdXRwdXQudHh0JywgInRjZ2Ffb3ZfYmFtX2RpciIgPSAnL3NoYWhsYWIvYXJjaGl2ZS9pbW11bmVfcHJvamVjdC9UQ0dBLU9WL2JhbScsICJlcGl0b3Blc191bmlxdWVfZmlsdGVyZWQiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9wYXBlci9yZXN1bHRzL3RhYmxlcy9ydW4yL2VwaXRvcGVzX3VuaXF1ZV9maWx0ZXJlZC50c3YnLCAibmFub3N0cmluZ19kYXRhIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcmVzdWx0cy9uYW5vc3RyaW5nX3Jlc3VsdHMvaXRoX2Z1bGwvcWMvbGltbWFfcXVhbnRpbGUvbm9ybWFsaXplZF9leHByZXNzaW9uX3ZvYV9sYWJlbHNfZmlsdGVyZWQudHN2JywgImtub3duX3N1YnR5cGVzX2FycmF5IiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvZGF0YS9leHByZXNzaW9uL2FycmF5L3N1YnR5cGVzL2tub3duX3N1YnR5cGVzLnRzdicsICJjb3B5bnVtYmVyX3RhYmxlIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2RhdGEvaXRoaS9tYXN0ZXJfY29weW51bWJlcl9maWxlLnRzdicsICJyZWZzZXFfZ2VuZV9maWxlIiA9ICcvc2hhaGxhYi9hbHpoYW5nL2RhdGEvZ2Vub21lL2hnMTkvcmVmc2VxX2dlbmVzLmJlZCcsICJpZ3BhcnRpdGlvbl9vdXRkaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL2lncGFydGl0aW9uL3J1bjIyJywgImRiIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvbWV0YWRhdGEvZGIvaW1tdW5lX3Byb2plY3Quc3FsaXRlMycsICJhcnJheV9leHByZXNzaW9uX2ZpbGUiID0gJy9zaGFobGFiL2FsemhhbmcvcHJvamVjdHMvSVRIX0ltbXVuZS9kYXRhL2V4cHJlc3Npb24vYXJyYXkvZ2VuZV9leHByc19ybWFfYmF0Y2hfY29ycmVjdGVkLnR4dCcsICJuZW9lZGl0aW5nX291dGRpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvbmVvZWRpdGluZy9ydW42JywgInRjcl9kaXZlcnNpdHkiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL21peGNyL21peGNyX3J1bnMvaXRoXzFfMl8zL21peGNyNS9wb3N0cHJvY2Vzcy9UUkIvcG9zdGZpbHRlcl9kaXZlcnNpdHlfc3RhdHMvZGl2ZXJzaXR5LnN0cmljdC5yZXNhbXBsZWQudHh0JywgImloY190YWJsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvaWhjX3RhYmxlLnRzdicsICJyb29uZXlfbXV0c2lnY3ZfZmlsZSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL2V4dGVybmFsL290aGVyX3BhcGVycy9tbWM2Lnhsc3gnLCAidGlsY2x1c3Rlcl9zdXBlcnZpc2VkX2lweW5iIiA9ICcvc2hhaGxhYi9hbHpoYW5nL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmV2aWV3L2lweS90aWxjbHVzdGVyX3N1cGVydmlzZWRtdWx0aWNsYXNzLmlweW5iJywgImljZ2Nfc3VidHlwZXMiID0gJy9zaGFobGFiL2FsemhhbmcvZGF0YS9JQ0dDL2ljZ2NfcHJpbWFyeV90dW1vdXJfc3VidHlwZXMudHN2JywgInNudl9jbHVzdGVyX2RpciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9wcm9qZWN0cy9JVEhfSW1tdW5lL3BhcGVyL3Jlc3VsdHMvdGFibGVzL3J1bjIvY2xvbmVzL3Nudl9jbHVzdGVyJywgImRpc3RhbmNlX21ldGhvZCIgPSAnaG9ybicsICJsb2hobGFfdGNnYV9vdXRkaXIiID0gJy9zaGFobGFiL2FsemhhbmcvcGlwZWxpbmVfb3V0cHV0cy9pdGhfaW1tdW5lL2xvaGhsYS9ydW44X3RjZ2EnLCAiYmNyX2RpdmVyc2l0eSIgPSAnL3NoYWhsYWIvYWx6aGFuZy9waXBlbGluZV9vdXRwdXRzL2l0aF9pbW11bmUvbWl4Y3IvbWl4Y3JfcnVucy9pdGhfMV8yXzMvbWl4Y3I1L3Bvc3Rwcm9jZXNzL0lHSC9wb3N0ZmlsdGVyX2RpdmVyc2l0eV9zdGF0cy9kaXZlcnNpdHkuc3RyaWN0LnJlc2FtcGxlZC50eHQnLCAidGNnYV9ub25zdGRfZXhwciIgPSAnL3NoYWhsYWIvYWx6aGFuZy9kYXRhL1RDR0EvZXhwcl9tYXRyaXhfbm9ybWFsaXplX25vZHVwbGljYXRlcy50c3YnLCAiaGVfcmVzdWx0c19kaXIiID0gJy9zaGFobGFiL2FsemhhbmcvZGF0YS9pdGhpL2Zpbm5fcmVzdWx0cy9oZV9vdXRwdXRfTm92MjknLCAiaXRoX3N0YXRfdHlwZXMiID0gYygnZW50cm9weScsICdwb3N0cHJvY2Vzc2VkX2RpdmVyZ2VuY2UnLCAnY29tYmluZWRfaXRoX25vcm1hbGl6ZWQnLCAncHJvcG9ydGlvbl9zdWJjbG9uYWwnKSwgImxvaGhsYV9zdXBwb3J0aXZlX3NpdGVfdGhyZXNob2xkIiA9IDkwLjApLAogICAgcnVsZSA9ICd4Y3JfZ3JhcGhfYW5hbHlzaXMnCikKIyMjIyMjIyMgT3JpZ2luYWwgc2NyaXB0ICMjIyMjIyMjIwoKICAgICAgICAgICAgICAgICAgICAgICAgYGBgCgoKIyMgU2V0dXAKCmBgYHtyIGdsb2JhbF9jaHVua19vcHRpb25zLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHRpZHk9VFJVRSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgY2FjaGU9VFJVRSkgI2NhY2hlPVRSVUUKYGBgCgpgYGB7cn0KaWhjX3RhYmxlX3BhdGggPC0gc25ha2VtYWtlQGlucHV0JGloY190YWJsZQp4Y3JfdGFibGVfcGF0aCA8LSBzbmFrZW1ha2VAaW5wdXQkeGNyX3RhYmxlCm1vbGVjdWxhcl9zdWJ0eXBlX2ZpbGUgPC0gc25ha2VtYWtlQGlucHV0JG1vbHN1YnR5cGVzCmdyYXBoX3Jkc19wYXRoIDwtIHNuYWtlbWFrZUBpbnB1dCR4Y3JfZ3JhcGhzCgpkYl9wYXRoIDwtIHNuYWtlbWFrZUBwYXJhbXMkZGIKdGlsc19mb3JfY2x1c3RlciA8LSB1bmxpc3Qoc25ha2VtYWtlQHBhcmFtcyR0aWxzX2Zvcl9jbHVzdGVyKQphbGxfdGlsdHlwZXMgPC0gdW5saXN0KHNuYWtlbWFrZUBwYXJhbXMkYWxsX3RpbHR5cGVzKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KGl0aGkudXRpbHMpCmxvYWRfYmFzZV9saWJzKCkKCmxpYnJhcnkoaWdyYXBoKQpsaWJyYXJ5KHRpZHlncmFwaCkKbGlicmFyeShnZ3JhcGgpCmxpYnJhcnkoZW50cm9weSkKbGlicmFyeSh2ZWdhbikKCmxpYnJhcnkoc3RlcFBscikKbGlicmFyeShubmV0KQpsaWJyYXJ5KGNhcmV0KQoKbGlicmFyeShpdGhpLm1ldGEpCmxpYnJhcnkoaXRoaS54Y3IpCmBgYAoKYGBge3J9CmFubm90YXRpb25fY29sb3VycyA8LSBpdGhpLmZpZ3VyZXM6OmdldF9hbm5vdGF0aW9uX2NvbG91cnMoKQoKaWhjX3RhYmxlIDwtIGZyZWFkKGloY190YWJsZV9wYXRoKQp4Y3JfdGFibGUgPC0gcmVhZF9jbG9ub3R5cGVzKHhjcl90YWJsZV9wYXRoLCBkdXBsaWNhdGVzID0gRkFMU0UsIGRiX3BhdGgpCm1vbHN1YnR5cGVzIDwtIGZyZWFkKG1vbGVjdWxhcl9zdWJ0eXBlX2ZpbGUpCgp0aWxfY2x1c3RlcnMgPC0gaXRoaS5maWd1cmVzOjo6Z2V0X3RpbF9jbHVzdGVycyhpaGNfdGFibGUsIG1vbHN1YnR5cGVzLCB0aWxzX2Zvcl9jbHVzdGVyID0gdGlsc19mb3JfY2x1c3RlciwgbmNsdXN0cyA9IDMpCnRpbF9jbHVzdGVycyRwYXRpZW50X2lkIDwtIGl0aGkubWV0YTo6bWFwX2lkKHRpbF9jbHVzdGVycyRjb25kZW5zZWRfaWQsIGZyb20gPSAiY29uZGVuc2VkX2lkIiwgdG8gPSAicGF0aWVudF9pZCIsIGRiX3BhdGgpCmBgYAoKIyMgR3JhcGggY3JlYXRpb24KClRoZSBzdHJ1Y3R1cmUgb2YgdGhlc2UgZ3JhcGhzIGlzOgoKKiBWZXJ0aWNlcy9ub2RlcyBhcmUgU0hBUkVEIFRDUi9CQ1IgY2xvbm90eXBlcyAoc2VwYXJhdGUgZ3JhcGhzIGZvciBUQ1IvQkNSIGFuZCBvbmUgZm9yIGVhY2ggcGF0aWVudCkuIFByaXZhdGUgY2xvbm90eXBlcyAodGhvc2UgcHJlc2VudCBpbiBvbmx5IDEgc2FtcGxlKSBhcmUgbm90IGluY2x1ZGVkLiAKKiBFZGdlcyBjb25uZWN0IGVhY2ggcGFpciBvZiBjbG9ub3R5cGVzIHRoYXQgYXJlIHNoYXJlZCBpbiB0aGUgc2FtZSBzYW1wbGVzLiBlLmcuIGlmIGNsb25vdHlwZSBBIGlzIGZvdW5kIGluIHNhbXBsZXMgT20xIGFuZCBPbTIsIGFuZCBjbG9ub3R5cGUgQiBpcyBmb3VuZCBpbiBzYW1wbGVzIE9tMSwgT20yLCBhbmQgUk92MSwgdGhlcmUncyBhbiBlZGdlIGJldHdlZW4gQSBhbmQgQi4gCgpgYGB7cn0KIyMgUmVmYWN0b3IgdGhpcyBpZiBpdCB3b3JrcwojIyBQcm9iYWJseSBiZXN0IHRvIHBsb3QgdG9wIFggcGVyIHNhbXBsZSAtLSB3b3JrcyBtb3JlIGVmZmVjdGl2ZWx5CmNvbnN0cnVjdF94Y3JfZ3JhcGggPC0gZnVuY3Rpb24oeGNyX2Nsb25lcywgbm9kZXMgPSAieGNyY2xvbmUiLCB0b3BfbiA9IEluZikgewogIHNhbXBsZV9tZW1iZXJzaGlwIDwtIHhjcl9jbG9uZXMgJT4lIGdyb3VwX2J5KGlkKSAlPiUgZG8oc2FtcGxlcz11bmlxdWUoYXMuY2hhcmFjdGVyKC4kY29uZGVuc2VkX2lkKSksIHRvdGFsX2NvdW50ID0gc3VtKC4kY291bnQpLCB0b3RhbF9mcmVxPXN1bSguJGNvdW50KS9zdW0oeGNyX2Nsb25lcyRjb3VudCkpCiAgc2FtcGxlX21lbWJlcnNoaXAkdG90YWxfY291bnQgPC0gdW5saXN0KHNhbXBsZV9tZW1iZXJzaGlwJHRvdGFsX2NvdW50KQogIHNhbXBsZV9tZW1iZXJzaGlwJHRvdGFsX2ZyZXEgPC0gdW5saXN0KHNhbXBsZV9tZW1iZXJzaGlwJHRvdGFsX2ZyZXEpCiAgc2FtcGxlX21lbWJlcnNoaXAkbnVtZXJpY19pZCA8LSBwYXN0ZTAoImlkXyIsIGZhY3RvcihzYW1wbGVfbWVtYmVyc2hpcCRpZCkgJT4lIGFzLm51bWVyaWMpCiAgCiAgdG9wX24gPC0gbWluKHRvcF9uLCBucm93KHNhbXBsZV9tZW1iZXJzaGlwKSkKICAKICAKICBzYW1wbGVfbWVtYmVyc2hpcF9zaGFyZWQgPC0gc2FtcGxlX21lbWJlcnNoaXBbc2FwcGx5KHNhbXBsZV9tZW1iZXJzaGlwJHNhbXBsZXMsIGxlbmd0aCkgPiAxLF0KICBzYW1wbGVfbWVtYmVyc2hpcF9zaGFyZWQgPC0gc2FtcGxlX21lbWJlcnNoaXBfc2hhcmVkW29yZGVyKHNhbXBsZV9tZW1iZXJzaGlwX3NoYXJlZCR0b3RhbF9mcmVxKSxdWzE6dG9wX24sXQogIHNhbXBsZV9tZW1iZXJzaGlwX3NoYXJlZCRpbmRleCA8LSBhcy5udW1lcmljKGZhY3RvcihzYW1wbGVfbWVtYmVyc2hpcF9zaGFyZWQkbnVtZXJpY19pZCwgbGV2ZWxzID0gc29ydCh1bmlxdWUoc2FtcGxlX21lbWJlcnNoaXBfc2hhcmVkJG51bWVyaWNfaWQpKSkpCiAgCiAgbm9kZXMgPC0gc2FtcGxlX21lbWJlcnNoaXBfc2hhcmVkICU+JSBzdWJzZXQoc2VsZWN0ID0gYygiaW5kZXgiLCAibnVtZXJpY19pZCIsICJpZCIpKSAlPiUgcGx5cjo6cmVuYW1lKGMoJ2lkJz0nY2xvbm90eXBlJykpCiAgCiAgCiAgdW5pcXVlX3NhbXBsZXMgPC0geGNyX2Nsb25lcyRjb25kZW5zZWRfaWQgJT4lIHVuaXF1ZSAlPiUgYXMuY2hhcmFjdGVyCiAgc2FtcGxlX3BhaXJzIDwtIGNvbWJuKHVuaXF1ZV9zYW1wbGVzLCAyKQogIAogIGVkZ2VzIDwtIGxhcHBseSgxOm5jb2woc2FtcGxlX3BhaXJzKSwgZnVuY3Rpb24oaSkgewogICAgcGFpciA8LSBzYW1wbGVfcGFpcnNbLGldCiAgICBjb250YWluZWRfbm9kZXMgPC0gc2FtcGxlX21lbWJlcnNoaXBfc2hhcmVkW3NhcHBseShzYW1wbGVfbWVtYmVyc2hpcF9zaGFyZWQkc2FtcGxlcywgZnVuY3Rpb24oeCkgYWxsKHBhaXIgJWluJSB4KSksXQogICAgaWYgKGxlbmd0aChjb250YWluZWRfbm9kZXMkaW5kZXgpID4gMSkgewogICAgICBwYWlyX2VkZ2VzIDwtIGRhdGEuZnJhbWUoY29tYm4oY29udGFpbmVkX25vZGVzJGluZGV4LCAyKSAlPiUgdCwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKSAlPiUgcGx5cjo6cmVuYW1lKGMoJ1gxJz0nZnJvbScsICdYMic9J3RvJykpCiAgICAgIHJldHVybihkYXRhLmZyYW1lKHBhaXJfZWRnZXMsIHBhaXI9cGFzdGUoc29ydChwYWlyKSwgY29sbGFwc2U9IiwiKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKSkKICAgIH0gZWxzZSB7CiAgICAgIHJldHVybihkYXRhLmZyYW1lKCkpCiAgICB9CiAgfSkgJT4lIHJiaW5kLmZpbGwKICAKICBnIDwtIHRibF9ncmFwaChub2RlcyA9IG5vZGVzLCBlZGdlcyA9IGVkZ2VzLCBkaXJlY3RlZCA9IFRSVUUpCiAgCiAgcmV0dXJuKGcpCn0KYGBgCgoKYGBge3J9CmNhbGN1bGF0ZV9ncmFwaF9tZWFzdXJlcyA8LSBmdW5jdGlvbihnKSB7CiAgaWcgPC0gYXMudW5kaXJlY3RlZChhcy5pZ3JhcGgoZykpCiAgCiAgbG91dmFpbl9jbHVzdGVyIDwtIGNsdXN0ZXJfbG91dmFpbihpZykKICAjc3BpbmdsYXNzX2NsdXN0ZXIgPC0gY2x1c3Rlcl9zcGluZ2xhc3MoaWcpICMjIGRvZXNuJ3Qgd29yayBvbiB1bmNvbm5lY3RlZCBncmFwaHMKICBtYXhpbWFsX2NsaXF1ZXMgPC0gbWF4aW1hbC5jbGlxdWVzKGlnKQogIAogIAogIGdyYXBoX2F0dHJpYnV0ZXMgPC0gbGlzdCgKICAgICMjIEJhc2ljIHByb3BlcnRpZXMsIGkuZS4gIyBlZGdlcywgIyB2ZXJ0aWNlcwogICAgbnVtX3ZlcnRpY2VzPWxlbmd0aChWKGlnKSksCiAgICBudW1fZWRnZXM9bGVuZ3RoKEUoaWcpKSwKICAgIG51bV9zaW1wbGVfZWRnZXM9bGVuZ3RoKEUoc2ltcGxpZnkoaWcpKSksCiAgICAKICAgICMjIENlbnRyYWxpemF0aW9uIG1lYXN1cmVzCiAgICBjZW50cl9kZWdyZWU9Y2VudHJfZGVncmVlKGlnKSRjZW50cmFsaXphdGlvbiwKICAgIGNlbnRyX2RlZ3JlZV9tYXg9Y2VudHJfZGVncmVlX3RtYXgoaWcpLAogICAgY2VudHJfZWlnZW49Y2VudHJfZWlnZW4oaWcpJGNlbnRyYWxpemF0aW9uLAogICAgY2VudHJfZWlnZW5fbWF4PWNlbnRyX2VpZ2VuX3RtYXgoaWcpLAogICAgY2VudHJfY2xvPWNlbnRyX2NsbyhpZykkY2VudHJhbGl6YXRpb24sCiAgICBjZW50cl9jbG9fdG1heD1jZW50cl9jbG9fdG1heChpZyksCiAgICBjZW50cl9iZXR3PWNlbnRyX2JldHcoaWcsIGRpcmVjdGVkID0gRkFMU0UpJGNlbnRyYWxpemF0aW9uLAogICAgY2VudHJfYmV0d190bWF4PWNlbnRyX2JldHdfdG1heChpZywgZGlyZWN0ZWQgPSBGQUxTRSksCiAgICAKICAgICMjIENvbW11bml0eSBtZWFzdXJlcwogICAgY2x1c3Rlcl9sb3V2YWluX249bGVuZ3RoKGxvdXZhaW5fY2x1c3RlciksCiAgICAjY2x1c3Rlcl9zcGluZ2xhc3Nfbj1sZW5ndGgoc3BpbmdsYXNzX2NsdXN0ZXIpLAogICAgY2x1c3Rlcl9sb3V2YWluX3NpemU9dW5uYW1lKG1heCh0YWJsZShsb3V2YWluX2NsdXN0ZXIkbWVtYmVyc2hpcCkpKSwKICAgICNjbHVzdGVyX3NwaW5nbGFzc19zaXplPXVubmFtZShtYXgodGFibGUoc3BpbmdsYXNzX2NsdXN0ZXIkbWVtYmVyc2hpcCkpKSwKICAgIGNsdXN0ZXJfbG91dmFpbl9lbnRyb3B5PWVudHJvcHk6OmVudHJvcHkodGFibGUobG91dmFpbl9jbHVzdGVyJG1lbWJlcnNoaXApKSwKICAgIGNsdXN0ZXJfbG91dmFpbl9zaW1wc29uPXZlZ2FuOjpkaXZlcnNpdHkodGFibGUobG91dmFpbl9jbHVzdGVyJG1lbWJlcnNoaXApLCBpbmRleCA9ICJpbnZzaW1wc29uIiksCiAgICAKICAgICMjIEdsb2JhbCB0cmFuc2l0aXZpdHkKICAgIHRyYW5zaXRpdml0eV91bmRpcmVjdGVkPXRyYW5zaXRpdml0eShpZywgdHlwZSA9ICJnbG9iYWx1bmRpcmVjdGVkIiksCiAgICAKICAgICMjIEFzc29ydGF0aXZpdHkKICAgIGFzc29ydGF0aXZpdHlfZGVncmVlPWFzc29ydGF0aXZpdHlfZGVncmVlKGlnKSwKICAgIAogICAgIyMgRGVuc2l0eQogICAgZGVuc2l0eT1lZGdlX2RlbnNpdHkoaWcpLAogICAgCiAgICAjIyBDbGlxdWVzCiAgICBtYXhpbWFsX2NsaXF1ZXNfbj1tYXhpbWFsLmNsaXF1ZXMuY291bnQoaWcpLAogICAgbWF4aW1hbF9jbGlxdWVfc2l6ZT1jbGlxdWUubnVtYmVyKGlnKSwKICAgIG1heGltYWxfY2xpcXVlX2VudHJvcHk9ZW50cm9weTo6ZW50cm9weShzYXBwbHkobWF4aW1hbF9jbGlxdWVzLCBsZW5ndGgpKSwKICAgIG1heGltYWxfY2xpcXVlX3NpbXBzb249dmVnYW46OmRpdmVyc2l0eShzYXBwbHkobWF4aW1hbF9jbGlxdWVzLCBsZW5ndGgpLCBpbmRleCA9ICJpbnZzaW1wc29uIiksCiAgICAKICAgICMjIEF1dG9tb3JwaGlzbXMKICAgICNhdXRvbW9ycGhpc21zX249YXMubnVtZXJpYyhhdXRvbW9ycGhpc21zKGlnKSRncm91cF9zaXplKSwKICAgIAogICAgIyMgTXVsdGlwbGUgZWRnZXMKICAgIG11bHRpcGxlX3Byb3A9bGVuZ3RoKHdoaWNoKGlncmFwaDo6Y291bnQubXVsdGlwbGUoaWcpID4gMSkpL2xlbmd0aChpZ3JhcGg6OmNvdW50Lm11bHRpcGxlKGlnKSkKICApCiAgCiAgcmV0dXJuKGdyYXBoX2F0dHJpYnV0ZXMpCn0KCnBsb3RfeGNyX2dyYXBoIDwtIGZ1bmN0aW9uKGcpIHsKICBnICU+JSBnZ3JhcGgobGF5b3V0ID0gJ2trJykgKyBnZW9tX2VkZ2VfbGluayhhZXMoY29sb3VyPXBhaXIpKSArIGdlb21fbm9kZV9wb2ludCgpICsgc2NhbGVfY29sb3VyX2NvbnRpbnVvdXMoZ3VpZGUgPSAnbGVnZW5kJykgKyB0aGVtZV9ncmFwaCgpCn0KYGBgCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpzZWdtZW50cyA8LSB1bmlxdWUoeGNyX3RhYmxlJHR5cGUpCnBhdGllbnRzIDwtIHNvcnQodW5pcXVlKHhjcl90YWJsZSRwYXRpZW50X2lkKSkKCnNlZ19yZXN1bHRzIDwtIGxhcHBseShzZWdtZW50cywgZnVuY3Rpb24oc2VndHlwZSkgewogIHByaW50KHNlZ3R5cGUpCiAgcGF0X3Jlc3VsdHMgPC0gbGFwcGx5KHBhdGllbnRzLCBmdW5jdGlvbihwYXQpIHsKICAgIHByaW50KHBhdCkKICAgIHhjcl9jbG9uZXMgPC0gc3Vic2V0KHhjcl90YWJsZSwgcGF0aWVudF9pZCA9PSBwYXQgJiB0eXBlID09IHNlZ3R5cGUpCiAgICBnX3NtYWxsIDwtIGNvbnN0cnVjdF94Y3JfZ3JhcGgoeGNyX2Nsb25lcywgbm9kZXMgPSAieGNyY2xvbmUiLCB0b3BfbiA9ICA1MCkKICAgIGdfZnVsbCA8LSBjb25zdHJ1Y3RfeGNyX2dyYXBoKHhjcl9jbG9uZXMsIG5vZGVzID0gInhjcmNsb25lIikKICAgIGF0dHJpYnV0ZXMgPC0gY2FsY3VsYXRlX2dyYXBoX21lYXN1cmVzKGdfZnVsbCkKICAgIHJldHVybihsaXN0KGdfc21hbGw9Z19zbWFsbCwgZ19mdWxsPWdfZnVsbCwgYXR0cmlidXRlcz1hdHRyaWJ1dGVzKSkKICB9KQogIG5hbWVzKHBhdF9yZXN1bHRzKSA8LSBwYXRpZW50cwogIHJldHVybihwYXRfcmVzdWx0cykKfSkKbmFtZXMoc2VnX3Jlc3VsdHMpIDwtIHNlZ21lbnRzCnNhdmVSRFMoc2VnX3Jlc3VsdHMsIGZpbGUgPSAifi9zaGFobGFiL3Byb2plY3RzL0lUSF9JbW11bmUvcGFwZXIvcmV2aWV3L2RhdGFfb2JqZWN0cy94Y3JfZ3JhcGhfcmVzdWx0cy5yZHMiKQpgYGAKCldlIGRvbid0IGhhdmUgdGltZSB0byB3YWl0IGZvciB0aGF0IHRvIGZpbmlzaCAoaXQgdGFrZXMgMzAgbWludXRlcyBvciBzbyksIHNvOgoKYGBge3J9CnNlZ19yZXN1bHRzIDwtIHJlYWRSRFMoZ3JhcGhfcmRzX3BhdGgpCmBgYAoKIyMjIEV4YW1wbGUgZ3JhcGgKClRoaXMgaXMgYSBncmFwaCBzdWJzYW1wbGVkIGZyb20gcGF0aWVudCAyJ3MgVENScy4gVGhlc2UgYXJlIHRoZSA1MCBtb3N0IHByZXZhbGVudCAoYWNjb3JkaW5nIHRvIHJlYWQgY291bnQpIHNoYXJlZCBjbG9ub3R5cGVzLiAKCmBgYHtyfQpwbG90X3hjcl9ncmFwaChzZWdfcmVzdWx0cyRUUkIkYDJgJGdfc21hbGwpCmBgYAoKIyMgR3JhcGggYXR0cmlidXRlIGFuYWx5c2lzCgpgYGB7cn0KZ3JhcGhfYXR0cmlidXRlX3RhYmxlIDwtIGxhcHBseShuYW1lcyhzZWdfcmVzdWx0cyksIGZ1bmN0aW9uKHNlZykgewogIHggPC0gc2VnX3Jlc3VsdHNbW3NlZ11dCiAgcGF0aWVudF9hdHRyaWJ1dGVfdGFibGUgPC0gbGFwcGx5KG5hbWVzKHgpLCBmdW5jdGlvbihwYXQpIHsKICAgIHkgPC0geFtbcGF0XV0KICAgIGRhdGEuZnJhbWUocGF0aWVudF9pZD1wYXQsIGFzLmRhdGEuZnJhbWUoeSRhdHRyaWJ1dGVzKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQogIH0pICU+JSByYmluZC5maWxsCiAgcmV0dXJuKGRhdGEuZnJhbWUodHlwZT1zZWcsIHBhdGllbnRfYXR0cmlidXRlX3RhYmxlLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpKQp9KSAlPiUgcmJpbmQuZmlsbAoKZ3JhcGhfYXR0cmlidXRlX3RhYmxlJHBhdGllbnRfaWQgPC0gaXRoaS5tZXRhOjpmYWN0b3JfaWQoZ3JhcGhfYXR0cmlidXRlX3RhYmxlJHBhdGllbnRfaWQsIHR5cGUgPSAicGF0aWVudF9pZCIsIGRiX3BhdGgpCmdyYXBoX2F0dHJpYnV0ZV90YWJsZSRjZW50cl9kZWdyZWVfbm9ybWFsaXplZCA8LSB3aXRoKGdyYXBoX2F0dHJpYnV0ZV90YWJsZSwgY2VudHJfZGVncmVlL2NlbnRyX2RlZ3JlZV9tYXgpCmdyYXBoX2F0dHJpYnV0ZV90YWJsZSRjZW50cl9laWdlbl9ub3JtYWxpemVkIDwtIHdpdGgoZ3JhcGhfYXR0cmlidXRlX3RhYmxlLCBjZW50cl9laWdlbi9jZW50cl9laWdlbl9tYXgpCmdyYXBoX2F0dHJpYnV0ZV90YWJsZSRjZW50cl9iZXR3X25vcm1hbGl6ZWQgPC0gd2l0aChncmFwaF9hdHRyaWJ1dGVfdGFibGUsIGNlbnRyX2JldHcvY2VudHJfYmV0d190bWF4KQpncmFwaF9hdHRyaWJ1dGVfdGFibGUkY2VudHJfY2xvX25vcm1hbGl6ZWQgPC0gd2l0aChncmFwaF9hdHRyaWJ1dGVfdGFibGUsIGNlbnRyX2Nsby9jZW50cl9jbG9fdG1heCkKCiMjIE5PVEUgdGhhdCBzaW1wc29uID0gaW52ZXJzZV9zaW1wc29uIGhlcmUuIAojIyBTaG91bGQgYWRkIGVudHJvcHkgYW5kIHNpbXBzb24gZm9yIGNsdXN0ZXJfbG91dmFpbgoKaWRfdmFycyA8LSBjKCJ0eXBlIiwgInBhdGllbnRfaWQiKQptZWFzdXJlX3ZhcnMgPC0gc2V0ZGlmZihjb2xuYW1lcyhncmFwaF9hdHRyaWJ1dGVfdGFibGUpLCBpZF92YXJzKQpncmFwaF9hdHRyaWJ1dGVfdGFibGVfbWVsdGVkIDwtIHJlc2hhcGUyOjptZWx0KGdyYXBoX2F0dHJpYnV0ZV90YWJsZSwgaWQudmFycyA9IGlkX3ZhcnMsIG1lYXN1cmUudmFycyA9IG1lYXN1cmVfdmFycywgdmFyaWFibGUubmFtZSA9ICJtZWFzdXJlIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlLm5hbWUgPSAidmFsdWUiKQoKYGBgCgojIyBDb21wYXJpc29uIHdpdGggb3RoZXIgY292YXJpYXRlcwoKIyMjIFRJTCBkZW5zaXRpZXMKCmBgYHtyfQppaGNfdGFibGVfbWVsdGVkIDwtIHJlc2hhcGUyOjptZWx0KGloY190YWJsZSwgaWQudmFycyA9IGMoImNvbmRlbnNlZF9pZCIsICJwYXRpZW50X2lkIiksIG1lYXN1cmUudmFycyA9IGFsbF90aWx0eXBlcywgdmFyaWFibGUubmFtZSA9ICJ0aWx0eXBlIiwgdmFsdWUubmFtZSA9ICJkZW5zaXR5IikKeGNyX3NhbXBsZXMgPC0geGNyX3RhYmxlJGNvbmRlbnNlZF9pZCAlPiUgdW5pcXVlCmloY190YWJsZV9tZWx0ZWQgPC0gc3Vic2V0KGloY190YWJsZV9tZWx0ZWQsIGNvbmRlbnNlZF9pZCAlaW4lIHhjcl9zYW1wbGVzKQoKaWhjX3RhYmxlX21lYW5zIDwtIGloY190YWJsZV9tZWx0ZWQgJT4lIGdyb3VwX2J5KHBhdGllbnRfaWQsIHRpbHR5cGUpICU+JSBzdW1tYXJpc2UoZGVuc2l0eT1tZWFuKGRlbnNpdHksIG5hLnJtPVRSVUUpKQppaGNfdGFibGVfbWVhbnMkcGF0aWVudF9pZCA8LSBpdGhpLm1ldGE6OmZhY3Rvcl9pZChpaGNfdGFibGVfbWVhbnMkcGF0aWVudF9pZCwgdHlwZSA9ICJwYXRpZW50X2lkIiwgZGJfcGF0aCkKYGBgCgpgYGB7cn0KaWhjX2dyYXBoIDwtIHBseXI6OmpvaW4oaWhjX3RhYmxlX21lYW5zICU+JSBhcy5kYXRhLmZyYW1lLCBncmFwaF9hdHRyaWJ1dGVfdGFibGVfbWVsdGVkICU+JSBhcy5kYXRhLmZyYW1lLCBieSA9ICJwYXRpZW50X2lkIikKYGBgCgpgYGB7cn0KcHZhbHMgPC0gaXRoaS51dGlsczo6Y29tcHV0ZV9wdmFsc19zdWJzZXRzKGloY19ncmFwaCwgZmFjZXRfdmFycyA9IGMoInRpbHR5cGUiLCAidHlwZSIsICJtZWFzdXJlIiksIGZvcm11bGEgPSB+IGRlbnNpdHkgKyB2YWx1ZSwgY29yZnVuID0gY29yLnRlc3QsIG1ldGhvZCA9ICJzcGVhcm1hbiIpCgpzdWJzZXQocHZhbHMsIHAuYWRqIDwgMC4wNSkKYGBgCgojIyMjIEV4YW1wbGUgcGxvdCBvZiBvbmUgb2YgdGhlIHNpZ25pZmljYW50IGNvcnJlbGF0aW9ucwoKYGBge3J9CmdncGxvdChpaGNfZ3JhcGggJT4lIHN1YnNldCh0eXBlID09ICJUUkIiICYgdGlsdHlwZSA9PSAiRV9DRDhfZGVuc2l0eSIgJiBtZWFzdXJlID09ICJjbHVzdGVyX2xvdXZhaW5fc2l6ZSIpLCBhZXMoeD1kZW5zaXR5LCB5ID0gdmFsdWUpKSArIGdlb21fcG9pbnQoYWVzKGNvbG91cj1mYWN0b3IocGF0aWVudF9pZCkpKSArIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBhbm5vdGF0aW9uX2NvbG91cnMkcGF0aWVudF9pZCkgKyB0aGVtZV9idygpICsgdGhlbWVfUHVibGljYXRpb24oKSArIHRoZW1lX25hdHVyZSgpCmBgYAoKSXQgc2VlbXMgbGlrZSBzZXZlcmFsIG9mIHRoZXNlIGdyYXBoIG1lYXN1cmVzIGNvcnJlbGF0ZSB2ZXJ5IHdlbGwgd2l0aCBlYWNoIFRJTCB0eXBlJ3MgZGVuc2l0eS4gCgpgYGB7cn0Kd2l0aChzdWJzZXQocHZhbHMsIHAuYWRqIDwgMC4wNSksIHRhYmxlKG1lYXN1cmUpKQpgYGAKCkFuZCB0aGVzZSBzZWVtIHRvIGJlIHRoZSBzYW1lIG1lYXN1cmVzLiBTaW1wbGUgbWVhc3VyZXMgbGlrZSB0aGUgbnVtYmVyIG9mIHZlcnRpY2VzICh0b3RhbCAjIHVuaXF1ZSBjbG9ub3R5cGVzIGZvciBhIHBhdGllbnQpIGFuZCB0aGUgbnVtYmVyIG9mIGVkZ2VzIChudW1iZXIgb2YgcGFpcndpc2UgJ3NoYXJpbmdzJyBiZXR3ZWVuIHNhbXBsZXMgZm9yIHRoZSBzYW1lIGNsb25vdHlwZSAtLSB0aGlzIGlzIGJpYXNlZCBieSAjIG9mIHNhbXBsZXMgcGVyIHBhdGllbnQpIGRvIHZlcnkgd2VsbC4gCgpJbiB0ZXJtcyBvZiB3aGF0IHNlZW1zIHRvIGJlIGltcG9ydGFudDoKCiogQ2VudHJhbGl6YXRpb24gZG9lc24ndCBzZWVtIHRvIGJlIGNvcnJlbGF0ZWQgd2l0aCB0aGUgbW9zdCBoZXJlLiBUaGUgdGhlb3JldGljYWwgbWF4aW11bSBvZiB0aGVzZSB2YWx1ZXMgZm9yIGEgc3RhciBwaHlsb2dlbnkgd2l0aCB0aGUgc2FtZSBudW1iZXIgb2YgdmVydGljZXMgKFwqX21heCBhbmQgXCpfdG1heCkgaGF2ZSBtb3JlIGNvcnJlbGF0aW9ucyB0aGFuIHRoZSBjZW50cmFsaXphdGlvbiB2YWx1ZXMgdGhlbXNlbHZlcyAtLSBpbmRpY2F0aW5nIHRoYXQgdGhlIGluZmx1ZW5jZSBvZiB0aGUgbnVtYmVyIG9mIHZlcnRpY2VzL2VkZ2VzIGxpa2VseSBkb21pbmF0ZXMgaGVyZS4gCiogTGlrZXdpc2UsIHdoaWxlIG1heGltdW0gY2xpcXVlIHNpemUgaXMgd2VsbCBjb3JyZWxhdGVkLCBlbnRyb3B5IGFuZCBJbnZlcnNlIHNpbXBzb24gaW5kaWNlcyBvZiB0aGUgY2xpcXVlIHNpemUgZGlzdHJpYnV0aW9uIGFyZSBub3QuIFRoZSBwdXJwb3NlIG9mIHRoZXNlIGxhdHRlciBtZWFzdXJlcyBpcyB0byByZWR1Y2UvZWxpbWluYXRlIGJpYXMgZnJvbSB0aGUgZ3JhcGggc2l6ZSBpbmZsdWVuY2luZyBjbGlxdWUgc2l6ZS4gQWZ0ZXIgYWNjb3VudGluZyBmb3IgdGhhdCwgY2xpcXVlIHN0cnVjdHVyZSBpc24ndCBjb3JybGVhdGVkIGVpdGhlci4gCiogSSdtIGN1cnJlbnRseSByZS1ydW5uaW5nIHdpdGggc2ltaWxhciwgbm9ybWFsaXplZCBtZWFzdXJlcyBmb3IgZ3JhcGggbW9kdWxhcml0eSAoY2x1c3Rlcl9sb3V2YWluKSAtLSB3aWxsIHVwZGF0ZS9sb29rIGF0IHRoZSBhYm92ZSB3aGVuIHRoYXQncyBkb25lLiBJJ2Qgd2FnZXIgaXQncyBnb2luZyB0byBiZSB0aGUgc2FtZSBhcyB0aGUgYWJvdmUgcG9pbnQuIAoKSSB0aGluayBpdCdzIHNhZmUgdG8gc2F5IHRoYXQgJ3NpbXBsZScgbWVhc3VyZXMgbGlrZSB0aGUgbnVtYmVyIG9mIHZlcnRpY2VzL2VkZ2VzIGFyZSBiZXR0ZXIgcHJlZGljdG9ycyBvZiBUSUwgZGVuc2l0eS4gVGhhdCBleHBsYWlucyBhcyB3ZWxsIHdoeSBUQ1IvQkNSIHJlcGVydG9pcmUgc2ltaWxhcml0eSAod2hpY2ggaXMgZWZmZWN0aXZlbHkgcmVsYXRlZCB0byB0aGUgbnVtYmVyIG9mIGVkZ2VzKSBkb2VzIHNvIHdlbGwgaW4gY29ycmVsYXRpb25zIHdpdGggVElMIGRlbnNpdHkuIAoKIyMjIFRJTCBjbHVzdGVyIHR5cGUKCldlIG1pZ2h0IGJlIGludGVyZXN0ZWQgaW4gcHJlZGljdGluZyB3aGV0aGVyIG9yIG5vdCBhIHBhdGllbnQgaXMgRVNfcHVyZSwgRVNfbWl4ZWQsIG9yIEVTX25vbmUgLS0gc2luY2Ugd2UgdGhpbmsgdGhlc2UgcG90ZW50aWFsbHkgaGF2ZSBwcm9nbm9zdGljIGltcGxpY2F0aW9ucy4gCgpgYGB7cn0KcmFuZ2UwMSA8LSBmdW5jdGlvbih0YWIsIC4uLil7YXBwbHkodGFiLCAyLCBmdW5jdGlvbih4KSAoeCAtIG1pbih4LCAuLi4pKS8obWF4KHgsIC4uLikgLSBtaW4oeCwgLi4uKSkpfQpgYGAKCmBgYHtyfQp0aWxfY2x1c3RlcnNfc3Vic2V0IDwtIHRpbF9jbHVzdGVycyAlPiUgc3Vic2V0KGNvbmRlbnNlZF9pZCAlaW4lIHhjcl9zYW1wbGVzKQpyZWN1cnJlbmNlX3NhbXBsZXMgPC0gYygiN19Ccm5NIiwgIjdfUlB2TSIsICIxMV9QdjEiLCAiMTFfUmN0MSIsICIxMV9SY3QyIiwgIjIzX0xPdjEiKQp0aWxfY2x1c3RlcnNfc3Vic2V0IDwtIHN1YnNldCh0aWxfY2x1c3RlcnNfc3Vic2V0LCAhY29uZGVuc2VkX2lkICVpbiUgcmVjdXJyZW5jZV9zYW1wbGVzKQoKZXNfc3VidHlwZXMgPC0gdGlsX2NsdXN0ZXJzX3N1YnNldCAlPiUgZ3JvdXBfYnkocGF0aWVudF9pZCkgJT4lIHN1bW1hcmlzZShFU19wdXJlPWFsbChjbHVzdGVyID09ICJFUy1USUwiKSwgRVNfbm9uZT0hYW55KGNsdXN0ZXIgPT0gIkVTLVRJTCIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEVTX21peGVkPShhbnkoY2x1c3RlciA9PSAiRVMtVElMIikgJiAhYWxsKGNsdXN0ZXIgPT0gIkVTLVRJTCIpKSkKZXNfc3VidHlwZXMkc3VidHlwZSA8LSBjb2xuYW1lcyhlc19zdWJ0eXBlcylbMjo0XVtzYXBwbHkoMTpucm93KGVzX3N1YnR5cGVzKSwgZnVuY3Rpb24oaSkgd2hpY2goYXMubG9naWNhbChlc19zdWJ0eXBlc1tpLDI6NF0pKSldCmVzX3N1YnR5cGVzJHN1YnR5cGVfc2ltcGxlIDwtIGVzX3N1YnR5cGVzJHN1YnR5cGUgJT4lIHBseXI6Om1hcHZhbHVlcyhjKCJFU19wdXJlIiwgIkVTX21peGVkIiwgIkVTX25vbmUiKSwgYygiRVMiLCAiRVMiLCAibm90RVMiKSkKYGBgCgpgYGB7cn0KZmVhdHVyZV9wcmVkaWN0b3JzIDwtIGMoImNlbnRyX2VpZ2VuX25vcm1hbGl6ZWQiLCAiZGVuc2l0eSIsICJjbHVzdGVyX2xvdXZhaW5fc2l6ZSIsICJtYXhpbWFsX2NsaXF1ZV9zaW1wc29uIiwgInRyYW5zaXRpdml0eV91bmRpcmVjdGVkIiwgImFzc29ydGF0aXZpdHlfZGVncmVlIikKCmZlYXR1cmVfbGlzdCA8LSBzZXRkaWZmKGZlYXR1cmVfcHJlZGljdG9ycywgaWRfdmFycykKZm9ybXVsYV9zdHIgPC0gcGFzdGUoZmVhdHVyZV9saXN0LCBjb2xsYXBzZSA9ICIgKyAiKQoKdHJiX2ZlYXR1cmVzIDwtIHN1YnNldChncmFwaF9hdHRyaWJ1dGVfdGFibGUsIHR5cGUgPT0gIlRSQiIsIHNlbGVjdCA9IC1jKHR5cGUpKQplc19zdWJ0eXBlc190cmIgPC0gcGx5cjo6am9pbih0cmJfZmVhdHVyZXMsIGVzX3N1YnR5cGVzLCBieSA9ICJwYXRpZW50X2lkIikKCmVzX3N1YnR5cGVzX3RyYiA8LSBlc19zdWJ0eXBlc190cmIgJT4lIHN1YnNldCghaXMubmEoc3VidHlwZSkpCmVzX3N1YnR5cGVzX3RyYlssZmVhdHVyZV9saXN0XSA8LSByYW5nZTAxKGVzX3N1YnR5cGVzX3RyYlssZmVhdHVyZV9saXN0XSkKYGBgCgojIyMjIFBlbmFsaXplZCBtdWx0aW5vbWlhbCByZWdyZXNzaW9uCgpgYGB7cn0KZXNfc3VidHlwZXNfdHJiX211bHRpbm9tIDwtIG5uZXQ6Om11bHRpbm9tKGZvcm11bGEgPSBhcy5mb3JtdWxhKHBhc3RlMCgic3VidHlwZSB+ICIsIGZvcm11bGFfc3RyKSksIGRhdGEgPSBlc19zdWJ0eXBlc190cmIsIGRlY2F5ID0gMC4wMSkKYGBgCgpgYGB7cn0KZXNfc3VidHlwZXNfdHJiX211bHRpbm9tCmBgYAoKYGBge3J9CmVzX3N1YnR5cGVzX3RyYl9tdWx0aW5vbSRmaXR0ZWQudmFsdWVzICU+JSBhcHBseSgxLCB3aGljaC5tYXgpCmBgYAoKV2hlbiBkZWNheSBpc24ndCBhZGRlZCwgc2VlbXMgdG8gYmUgb3ZlcmZpdHRpbmcuIFJhcmVseSBwcmVkaWN0cyBFUy1taXhlZCAoY2xhc3MgMSkgaWYgZGVjYXkgaXMgYWRkZWQuIAoKYGBge3J9CnZhckltcChlc19zdWJ0eXBlc190cmJfbXVsdGlub20pICU+JSByb3duYW1lc190b19jb2x1bW4odmFyID0gImZlYXR1cmUiKQpgYGAKClRyYW5zaXRpdml0eSBkb2VzIHRoZSBiZXN0LCB3aGljaCBpcyBpbnRlcmVzdGluZy4gQWxzbywgd2UgdGVuZCB0byBzZWUgTE9XRVIgdHJhbnNpdGl2aXR5IGluIHBhdGllbnRzIHdpdGggaGlnaGVyIFRJTC4gCgpTbyBsZXQncyBkbyB0aGlzIG1vcmUgcmlnb3JvdXNseS4gVXNpbmcgNC1mb2xkIGNyb3NzIHZhbGlkYXRpb24sIDUgdGltZXMsIHRvIGZpdCBhIG11bHRpbm9taWFsIG1vZGVsOgoKYGBge3IsIHJlc3VsdHMgPSAiaGlkZSJ9Cm1lc3NhZ2UoIlRyYWluaW5nIG11bHRpbm9taWFsIG1vZGVsIC4uLiIpCgpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAicmVwZWF0ZWRjdiIsIG51bWJlciA9IDQsIHJlcGVhdHMgPSA1LCBjbGFzc1Byb2JzID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgc3VtbWFyeUZ1bmN0aW9uID0gbXVsdGlDbGFzc1N1bW1hcnkpCm5uZXRHcmlkIDwtIGV4cGFuZC5ncmlkKGRlY2F5ID0gMTBec2VxKGZyb20gPSAtNSwgdG8gPSAxLCBieSA9IDEpKSAjIyByZWd1bGFyaXphdGlvbgoKbm5ldEZpdCA8LSB0cmFpbihmb3JtID0gYXMuZm9ybXVsYShwYXN0ZTAoInN1YnR5cGUgfiAiLCBmb3JtdWxhX3N0cikpLAogICAgICAgICAgICAgICAgIGRhdGEgPSBlc19zdWJ0eXBlc190cmIgJT4lIHNlbGVjdChjKGZlYXR1cmVfbGlzdCwgInN1YnR5cGUiKSkgJT4lIG11dGF0ZShzdWJ0eXBlID0gZmFjdG9yKHN1YnR5cGUpKSwKICAgICAgICAgICAgICAgICBtZXRob2QgPSAibXVsdGlub20iLAogICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJBY2N1cmFjeSIsCiAgICAgICAgICAgICAgICAgbWF4aW1pemUgPSBUUlVFLAogICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wsCiAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBubmV0R3JpZCwKICAgICAgICAgICAgICAgICB2ZXJib3NlPVRSVUUpCmBgYAoKYGBge3J9Cm5uZXRGaXQKYGBgCgoKV2UncmUgbm90IGRvaW5nIHNvIHdlbGwgaGVyZS4gT3VyIGJlc3QgbW9kZWwgaGFzIGFuIGFjY3VyYWN5IGFyb3VuZCAwLjYuIExvb2tpbmcgYXQgdGhlIHJlc3VsdHMsIGl0IGNvbnNpc3RlbnRseSBoYXMgcHJvYmxlbXMgd2l0aCBwcmVkaWN0aW5nIHRoZSBFUy1taXhlZCBjbHVzdGVyLCBpbmRpY2F0aW5nIHRoYXQgaXQgY2FuJ3QgcmVhbGx5IGdldCBkb3duIHRvIHRoYXQgJ2RlZXAnIG9mIGEgbGV2ZWwuIFdoYXQgaWYgd2Ugb25seSB3YW50IHRvIGxvb2sgYXQgMiBjbHVzdGVycyAtLSBlaXRoZXIgRVNfcHVyZS9FU19taXhlZCBvciBFU19ub25lPyAKCiMjIyMgUGVuYWxpemVkIGxvZ2lzdGljIHJlZ3Jlc3Npb24KCgpgYGB7ciwgcmVzdWx0cyA9ICJoaWRlIn0KbWVzc2FnZSgiVHJhaW5pbmcgbG9naXN0aWMgbW9kZWwgLi4uIikKCmZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgbnVtYmVyID0gNCwgcmVwZWF0cyA9IDUsIGNsYXNzUHJvYnMgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJ5RnVuY3Rpb24gPSB0d29DbGFzc1N1bW1hcnkpCgpwbHJHcmlkIDwtIGV4cGFuZC5ncmlkKGxhbWJkYSA9IDEwXnNlcShmcm9tID0gLTUsIHRvID0gNSwgYnkgPSAxKSwgY3AgPSBjKCJhaWMiLCAiYmljIikpICMjIHJlZ3VsYXJpemF0aW9uCgpwbHJGaXQgPC0gdHJhaW4oZm9ybSA9IGFzLmZvcm11bGEocGFzdGUwKCJzdWJ0eXBlX3NpbXBsZSB+ICIsIGZvcm11bGFfc3RyKSksCiAgICAgICAgICAgICAgICAgZGF0YSA9IGVzX3N1YnR5cGVzX3RyYiAlPiUgc2VsZWN0KGMoZmVhdHVyZV9saXN0LCAic3VidHlwZV9zaW1wbGUiKSkgJT4lIG11dGF0ZShzdWJ0eXBlX3NpbXBsZSA9IGZhY3RvcihzdWJ0eXBlX3NpbXBsZSkpLAogICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJwbHIiLAogICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJST0MiLAogICAgICAgICAgICAgICAgIG1heGltaXplID0gVFJVRSwKICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sLAogICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gcGxyR3JpZCkKYGBgCgpgYGB7cn0KcGxyRml0CmBgYAoKWWVhaCwgdGhpcyBkb2VzIHByZXR0eSBiYWQgYXQgcHJlZGljdGluZyBUSUwgc3VidHlwZSB0b28gLS0gYW5kIHRoYXQncyBqdXN0IHdpdGggYmluYXJ5IGNsYXNzZXMuIAoKU28gdGhlIGdyYXBoIG1lYXN1cmVzIHdlJ3JlIHVzaW5nLCBpLmUuCgpgYGB7cn0KZmVhdHVyZV9saXN0CmBgYAoKYXJlbid0IHZlcnkgZ29vZCBwcmVkaWN0b3JzIG9mIFRJTCBzdWJ0eXBlLiAKCiMjIFN1bW1hcnkKCiogKipDcml0aWNhbCBxdWVzdGlvbioqOiBXaGF0IGVsc2UgY2FuL3Nob3VsZCB3ZSB0cnkgdG8gcHJlZGljdCB3aXRoIHRoZXNlIGdyYXBoLWJhc2VkIG1lYXN1cmVzPyBUaGVzZSBhcmUgZG9uZSBhdCB0aGUgbGV2ZWwgb2YgZW50aXJlIGdyYXBocyBhdCB0aGUgbW9tZW50IChpLmUuIG9uZSBncmFwaCBmb3IgZWFjaCBwYXRpZW50KS4gT25lIGlkZWEgd291bGQgYmUgdG8gY29tcHV0ZSAndmVydGV4IHNldCBncmFwaCBtZWFzdXJlcycgZm9yIGVhY2ggdHVtb3VyIHNhbXBsZSwgdXNpbmcgdGhlIGNsb25vdHlwZXMgYmVsb25naW5nIHRvIGVhY2ggc2FtcGxlLiBUaGF0IHdvdWxkIGdldCB1cyBzb21lIG1lYXN1cmUgb2YgJ2hvdyBkb2VzIHRoaXMgc2FtcGxlJ3MgY2xvbmVzIHJlbGF0ZSB0byB0aG9zZSBmcm9tIG90aGVyIHNhbXBsZXMnPyBEbyB5b3UgaGF2ZSBhbnkgb3RoZXIgaWRlYXMvdGhvdWdodHMgb24gdGhpcz8gCgoKCgoKCgo=