On p-values

Coin Example

Coin example: is it fair or not?

knitr::include_graphics(path = 'img/coin.jpeg')

Let’s conduct an experiment => throw \(100\) times and count #heads. Let’s say we observed an extreme \(64\) heads. Do we have enough evidence to say coin if unfair?

4 things to remember when calculating p-values:

  1. Null hypothesis \(H_0\): fair coin (#heads ~ \(50\))
  2. Test statistic: #heads => distribution (mean \(50\)?)
  3. Compute p-value as:

\(Prob(Obs|H_0) = Prob(64 \text{ heads}|H_0:\text{coin is fair})\)

  1. Decide on significance threshold (\(0.05\))

Simulation

Let’s do a simulation:

  • Throw \(100\) times a fair coin (\(H_0\)) and count #heads
  • Repeat \(10000\) times! => make distribution of #heads
set.seed(42)
repeats = 10000
res = sapply(1:repeats, function(num) {
  stat = sample(x = c('heads', 'tails'), replace = T, size = 100)
  sum(stat == 'heads')
})

Draw the distribution of #heads:

main1 = '10000 x throw coin 100 times experiment! (coin is FAIR)'
hist(res, main = main1, xlab = 'Number of heads')

# plot(density(res), main = main1) # smoothed out
  • The above simulation is formally called Monte Carlo Random Sampling [wiki]
  • This is the empirical null distribution of our test statistic (#heads)!

Empirical p-value

Where does the observation (\(64\) heads) ‘sit’ in this distribution?

hist(res, xlim = c(20,80), main = 'Null distr', xlab = 'Number of heads')
abline(v = 64, col = 'red')

The empirical p-value is the proportion of experiments that yielded equal or more than the observed #heads (\(64\)):

p_val = sum(sort(res) >= 64)/length(res) # length(res) = 10000
p_val
[1] 0.0029

Since the above p-value is lower than a predefined threshold (e.g. \(0.05\)) I can deduce that the coin is unfair. More extreme observations would yield smaller p-values, e.g. for #heads = \(80\):

sum(sort(res) > 80)/length(res) # length(res) = 10000
[1] 0

Exact p-value

For this simple example, we could use statistical theory to calculate exact p-value (fair coin toss follows binomial distribution):

\[P(\text{#heads} \ge 64 | 100 \text{ tosses of a fair coin}) = \\ 1 - P(\text{#heads} \le 63)\]

In R it’s easy to calculate using the (cumulative) distribution function of the binomial pbinom:

1 - pbinom(q = 63, size = 100, prob = 0.5)
[1] 0.00331856

For \(80\) heads the p-value can be exactly found now:

1 - pbinom(q = 79, size = 100, prob = 0.5)
[1] 5.579545e-10

Omics Ranking

Benchmark Description

ranks = readRDS(file = 'ranks.rds')

We have conducted a benchmark as follows:

  • We trained and tested several ML models on many datasets
  • The datasets are dependent (different omic combinations)
    • All single omics: mRNA, miRNA, Clinical, etc.
    • All pairs of omics: Clinical + mRNA, miRNA + mRNA
    • All triplets and so on… (powerset = every possible omic (feature type) combination)
  • Performance measure: C-index (higher is better)
    • We take the median C-index across \(1000\) bootstrap resamples of the test set

E.g. for the CoxNet model the performance across all omic combinations is:

coxnet_scores = 
  ranks$mat['coxnet',] %>% 
  tibble::enframe(name = 'Task', value = 'Cindex') %>%
  mutate(Task = forcats::fct_reorder(Task, Cindex, .desc = TRUE))

coxnet_scores %>%
  ggplot(aes(x = Task, y = Cindex)) +
    geom_point() +
    geom_hline(yintercept = 0.5, linetype = 'dotted', color = 'red') +
    labs(x = 'Omic Combinations', y = 'Harrell\'s C-index', title = 'CoxNet') +
    ylim(c(0.4,0.6)) +
    geom_text(aes(label = rank(-Cindex)), hjust = 0.5, vjust = -1) +
    theme_bw(base_size = 14) +
    theme(axis.text.x = element_text(angle = 60, hjust = 1))

The task

Can I define a statistic/score per omic that will tell me how important that particular omic is (and strength of importance with the p-value)?

How to define importance in the above context?

Rank-Sum score

Deriving the score

Let’s use some toy data with \(3\) omics and all combos (\(2^3 - 1 = 7\)) - why not \(8\)?:

set1 = LETTERS[1:3]
set1
[1] "A" "B" "C"
get_subsets = function(set) {
  lapply(1:length(set), combinat::combn, x = set, simplify = FALSE) %>% 
  unlist(recursive = FALSE) %>%
  lapply(function(sub_set) {
    paste0(sub_set, collapse = '-') 
  }) %>% 
  unlist(use.names = FALSE)
}

subsets = get_subsets(set1)
subsets
[1] "A"     "B"     "C"     "A-B"   "A-C"   "B-C"   "A-B-C"

How many times every omic appears in the above list? For \(n\) omics?

# Answer: $2^{(n-1)}$
# In the example: n = 3 => 2^2 = 4 times

Example rankings - 3 x same figure, change color if omic combo has:

  • A
  • B
  • C
set.seed(42)
scores = rnorm(1:7, mean = 0.5, sd = 0.1)
tbl = tibble(Task = subsets, meas = scores) %>%
      mutate(Task = forcats::fct_reorder(Task, meas, .desc = TRUE))

p = tbl %>%
  ggplot(aes(x = Task, y = meas)) +
    geom_point(size = 3) +
    geom_hline(yintercept = 0.5, linetype = 'dotted', color = 'red') +
    labs(x = 'Omic Combinations', y = 'Harrell\'s C-index', title = 'Toy Example') +
    ylim(c(0.4,0.7)) +
    geom_text(aes(label = rank(-meas)), size = 10, hjust = 0.5, vjust = -1) +
    theme_bw(base_size = 20)
mycol1 = ifelse(grepl(pattern = 'A', x = levels(tbl$Task)), 'red', 'black')
mycol2 = ifelse(grepl(pattern = 'B', x = levels(tbl$Task)), 'blue', 'black')
mycol3 = ifelse(grepl(pattern = 'C', x = levels(tbl$Task)), 'purple', 'black')

# A more important
p + theme(axis.text.x = element_text(color = mycol1))
# B next 
p + theme(axis.text.x = element_text(color = mycol2))
# C last
p + theme(axis.text.x = element_text(color = mycol3))

A more important omic will be in more combos towards the left of the above figures!

Let’s define based on the more left-idea!

Define as score the sum of ranks (the lower the better):

  • A => \(1+2+3+4=10\) (best possible, \(4\) most-left ranks)
  • B => \(1+3+6+7=17\)
  • C => \(1+4+5+6=16\) (a little better than \(8\))



Worst score would be?

  • \(4\) most-right ranks => \(4+5+6+7=22\)



    I can easily normalize the score so that best possible value becomes \(1\) and worst possible \(0\), as:

\[score = 1 - \frac{sum(ranks) - best(score)}{worst(score) - best(score)}\]

Therefore we have:

  • A: \(score = 1 - \frac{10 - 10}{22 - 10}=1\)
  • B: \(score = 1 - \frac{17 - 10}{22 - 10}=0.41\)
  • C: \(score = 1 - \frac{16 - 10}{22 - 10}=0.5\)







Get a p-value

  • How high needs this score to be to be actually important?
  • How to construct the null distribution of our derived score/statistic and get an empirical p-value?





Observation: every omic in the toy example will take \(4\) values from the below rankings:

nomics = 3
ranks = 1:(2^nomics-1)
ranks
[1] 1 2 3 4 5 6 7

We just need to randomly select \(4\) of these and add them (+normalization step) to get a possible rank-sum score. Of course we will repeat this procedure multiple times:

nsets = 2^(nomics-1) # 4

# smaller possible rank sum (all left ranks)
best_rs  = sum(1:2^(nomics-1)) # 10
# larger possible rank sum (all right ranks)
worst_rs = sum(seq(to = 2^nomics-1, length.out = 2^(nomics-1))) # 22

# generate null dist - same for each omic
# use simple Monte Carlo random sampling
set.seed(42)
nsamples = 1000
null_nrs = sapply(1:nsamples, function(s) {
  rank_sum = sum(sample(x = ranks, size = nsets, replace = FALSE))
  1 - (rank_sum - best_rs)/(worst_rs - best_rs)
})
hist(null_nrs)

A omic is important at the \(0.05\) significance level:

scoreA = 1
pval_A = (sum(null_nrs >= scoreA)+1)/(length(null_nrs)+1)
pval_A
[1] 0.03996004
scoreB = 0.41
pval_B = (sum(null_nrs >= scoreB)+1)/(length(null_nrs)+1)
pval_B
[1] 0.7052947
scoreC = 0.5
pval_C = (sum(null_nrs >= scoreC)+1)/(length(null_nrs)+1)
pval_C
[1] 0.5864136

Another hypothetical example with a \(score = 0.92\):

score = 0.92
pval = (sum(null_nrs >= score)+1)/(length(null_nrs)+1)
pval
[1] 0.03996004
  • Discuss importance of number of omics \(n\) (the more the better!)

Unique scores in the null distribution very few:

unique(null_nrs)
 [1] 0.25000000 0.33333333 0.41666667 0.50000000 1.00000000 0.91666667
 [7] 0.16666667 0.58333333 0.83333333 0.00000000 0.66666667 0.75000000
[13] 0.08333333

Relation to Wilcoxon Rank Sum Test

Let’s focus on A omic and focus not on rankings but on the actual performance scores:

tbl %>%
  ggplot(aes(x = Task, y = meas)) +
    geom_point(size = 3) +
    geom_hline(yintercept = 0.5, linetype = 'dotted', color = 'red') +
    labs(x = 'Omic Combinations', y = 'Harrell\'s C-index', title = 'Toy Example') +
    ylim(c(0.4,0.7)) +
    geom_text(aes(label = sprintf("%0.3f", round(meas, digits = 3))), size = 7, hjust = 0.5, vjust = -1) +
    theme_bw(base_size = 20) +
    theme(axis.text.x = element_text(color = mycol1))

tbl = tbl %>% 
  mutate(grp = case_when(
    grepl(pattern = 'A', x = Task) ~ 'A', 
    TRUE ~ 'not A')
  )

tbl %>%
  ggplot(aes(x = grp, y = meas, fill = grp)) +
    geom_boxplot() +
    theme_bw(base_size = 20)

A    = tbl %>% filter(grp == 'A') %>% pull(meas)
notA = tbl %>% filter(grp == 'not A') %>% pull(meas)

# is performance scores from 'A' distribution less than 'notA'?
wilcox.test(x = A, y = notA, alternative = 'greater')

    Wilcoxon rank sum exact test

data:  A and notA
W = 12, p-value = 0.02857
alternative hypothesis: true location shift is greater than 0

Results are close - the calculation of W statistic is similar to our logic (sum of ranks)

Kolmogorov-Smirnov statistic

The idea for this statistic came from viewing this as a gene enrichment problem (see References).

  • KS test: check if 2 data samples come from the same probability distribution or not. It uses the eCDF (Empirical Cumulative Density Function) [wikipedia]

Let’s view an example using the research results and consider the Clinical as the omic of interest:

coxnet_scores = coxnet_scores %>% 
  mutate(grp = case_when(
    grepl(pattern = 'Clinical', x = Task) ~ 'Clin', 
    TRUE ~ 'not-Clin')
  )

clin     = coxnet_scores %>% filter(grp == 'Clin') %>% pull(Cindex)
not_clin = coxnet_scores %>% filter(grp == 'not-Clin') %>% pull(Cindex)

Histograms/Density of the performance scores (C-indexes), comparing omic-combos that had Clinical features included vs those that did not:

ggplot(coxnet_scores, aes(y = Cindex, fill = grp)) +
  geom_histogram(bins = 20) +
  geom_density(alpha = 0.3) +
  ylim(c(0.4, 0.6)) +
  coord_flip() +
  theme_bw(base_size = 14)
Warning: Removed 4 rows containing missing values (`geom_bar()`).

The empirical Cumulative Distribution Function for each group is:

ggplot(coxnet_scores, aes(Cindex, colour = grp)) +
  stat_ecdf() +
  labs(y = 'Empirical CDF') +
  theme_bw(base_size = 14)

ks.test(x = clin, y = not_clin, alternative = 'less')

    Exact two-sample Kolmogorov-Smirnov test

data:  clin and not_clin
D^- = 0.62083, p-value = 0.0009361
alternative hypothesis: the CDF of x lies below that of y
  • Compare our statistic vs KS statistic

Usually it’s better to not re-invent the wheel but sometimes we need to!

References

  • Chapter 13 of 2nd edition of An Introduction to Statistical Learning, Springer
  • Subramanian, A., Tamayo, P., Mootha, V. K., Mukherjee, S., Ebert, B. L., Gillette, M. A., Paulovich, A., Pomeroy, S. L., Golub, T. R., Lander, E. S., & Mesirov, J. P. (2005). Gene set enrichment analysis: A knowledge-based approach for interpreting genome-wide expression profiles. Proceedings of the National Academy of Sciences, 102(43), 15545–15550. https://doi.org/10.1073/PNAS.0506580102
LS0tCnRpdGxlOiAiUC12YWx1ZXMsIE51bGwgZGlzdHJpYnV0aW9ucywgT21pY3MgcmFua2luZyIKYXV0aG9yOiAiW0pvaG4gWm9ib2xhc10oaHR0cHM6Ly9naXRodWIuY29tL2JibG9kZm9uKSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjc3M6IHN0eWxlLmNzcwogICAgdGhlbWU6IHVuaXRlZAogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogZmFsc2UKICAgICAgc21vb3RoX3Njcm9sbDogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKZGF0ZTogIkxhc3QgdXBkYXRlZDogYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgY29tbWVudCA9ICcnKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpYmJsZSkKbGlicmFyeShmb3JjYXRzKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY29tYmluYXQpCmBgYAoKIyBPbiBwLXZhbHVlcyB7LX0KCiMjIENvaW4gRXhhbXBsZSB7LX0KCioqQ29pbiBleGFtcGxlKio6IGlzIGl0IGZhaXIgb3Igbm90PwpgYGB7cn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MocGF0aCA9ICdpbWcvY29pbi5qcGVnJykKYGBgCgpMZXQncyBjb25kdWN0IGFuIGV4cGVyaW1lbnQgPT4gdGhyb3cgJDEwMCQgdGltZXMgYW5kIGNvdW50ICNoZWFkcy4KTGV0J3Mgc2F5IHdlICoqb2JzZXJ2ZWQgYW4gZXh0cmVtZSAkNjQkIGhlYWRzKiouCkRvIHdlIGhhdmUgZW5vdWdoIGV2aWRlbmNlIHRvIHNheSBjb2luIGlmIHVuZmFpcj8KCjo6OnsuaW5mby1ib3ggLm5vdGV9CjQgdGhpbmdzIHRvIHJlbWVtYmVyIHdoZW4gY2FsY3VsYXRpbmcgcC12YWx1ZXM6CgoxLiBOdWxsIGh5cG90aGVzaXMgJEhfMCQ6ICoqZmFpciBjb2luKiogKCNoZWFkcyB+ICQ1MCQpCjIuIFRlc3Qgc3RhdGlzdGljOiAjaGVhZHMgPT4gZGlzdHJpYnV0aW9uIChtZWFuICQ1MCQ/KQozLiBDb21wdXRlIHAtdmFsdWUgYXM6CgokUHJvYihPYnN8SF8wKSA9IFByb2IoNjQgXHRleHR7IGhlYWRzfXxIXzA6XHRleHR7Y29pbiBpcyBmYWlyfSkkCgo0LiBEZWNpZGUgb24gc2lnbmlmaWNhbmNlIHRocmVzaG9sZCAoJDAuMDUkKQo6OjoKCiMjIFNpbXVsYXRpb24gey19CgpMZXQncyBkbyBhICoqc2ltdWxhdGlvbioqOgoKLSBUaHJvdyAkMTAwJCB0aW1lcyBhIGZhaXIgY29pbiAoJEhfMCQpIGFuZCBjb3VudCAjaGVhZHMKLSBSZXBlYXQgJDEwMDAwJCB0aW1lcyEgPT4gbWFrZSBkaXN0cmlidXRpb24gb2YgI2hlYWRzCgpgYGB7ciwgY2FjaGU9VFJVRX0Kc2V0LnNlZWQoNDIpCnJlcGVhdHMgPSAxMDAwMApyZXMgPSBzYXBwbHkoMTpyZXBlYXRzLCBmdW5jdGlvbihudW0pIHsKICBzdGF0ID0gc2FtcGxlKHggPSBjKCdoZWFkcycsICd0YWlscycpLCByZXBsYWNlID0gVCwgc2l6ZSA9IDEwMCkKICBzdW0oc3RhdCA9PSAnaGVhZHMnKQp9KQpgYGAKCkRyYXcgdGhlIGRpc3RyaWJ1dGlvbiBvZiAjaGVhZHM6CmBgYHtyfQptYWluMSA9ICcxMDAwMCB4IHRocm93IGNvaW4gMTAwIHRpbWVzIGV4cGVyaW1lbnQhIChjb2luIGlzIEZBSVIpJwpoaXN0KHJlcywgbWFpbiA9IG1haW4xLCB4bGFiID0gJ051bWJlciBvZiBoZWFkcycpCiMgcGxvdChkZW5zaXR5KHJlcyksIG1haW4gPSBtYWluMSkgIyBzbW9vdGhlZCBvdXQKYGBgCgo6Ojp7LmluZm8tYm94IC5ub3RlfQotIFRoZSBhYm92ZSBzaW11bGF0aW9uIGlzIGZvcm1hbGx5IGNhbGxlZCAqTW9udGUgQ2FybG8gUmFuZG9tIFNhbXBsaW5nKiBbW3dpa2ldKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL01vbnRlX0NhcmxvX21ldGhvZCldCi0gVGhpcyBpcyB0aGUgKiplbXBpcmljYWwgbnVsbCBkaXN0cmlidXRpb24qKiBvZiBvdXIgdGVzdCBzdGF0aXN0aWMgKCNoZWFkcykhCjo6OgoKIyMgRW1waXJpY2FsIHAtdmFsdWUgey19CgpXaGVyZSBkb2VzIHRoZSBvYnNlcnZhdGlvbiAoJDY0JCBoZWFkcykgJ3NpdCcgaW4gdGhpcyBkaXN0cmlidXRpb24/CgpgYGB7cn0KaGlzdChyZXMsIHhsaW0gPSBjKDIwLDgwKSwgbWFpbiA9ICdOdWxsIGRpc3RyJywgeGxhYiA9ICdOdW1iZXIgb2YgaGVhZHMnKQphYmxpbmUodiA9IDY0LCBjb2wgPSAncmVkJykKYGBgCgpUaGUgKiplbXBpcmljYWwgcC12YWx1ZSoqIGlzIHRoZSBwcm9wb3J0aW9uIG9mIGV4cGVyaW1lbnRzIHRoYXQgeWllbGRlZCAqKmVxdWFsIG9yIG1vcmUqKiB0aGFuIHRoZSBvYnNlcnZlZCAjaGVhZHMgKCQ2NCQpOgpgYGB7ciwgY2xhc3Muc291cmNlID0gJ2ZvbGQtc2hvdyd9CnBfdmFsID0gc3VtKHNvcnQocmVzKSA+PSA2NCkvbGVuZ3RoKHJlcykgIyBsZW5ndGgocmVzKSA9IDEwMDAwCnBfdmFsCmBgYAoKU2luY2UgdGhlIGFib3ZlIHAtdmFsdWUgaXMgbG93ZXIgdGhhbiBhIHByZWRlZmluZWQgdGhyZXNob2xkIChlLmcuICQwLjA1JCkgSSBjYW4gZGVkdWNlIHRoYXQgKip0aGUgY29pbiBpcyB1bmZhaXIqKi4KTW9yZSBleHRyZW1lIG9ic2VydmF0aW9ucyB3b3VsZCB5aWVsZCBzbWFsbGVyIHAtdmFsdWVzLCBlLmcuIGZvciAjaGVhZHMgPSAkODAkOgpgYGB7cn0Kc3VtKHNvcnQocmVzKSA+IDgwKS9sZW5ndGgocmVzKSAjIGxlbmd0aChyZXMpID0gMTAwMDAKYGBgCgojIyBFeGFjdCBwLXZhbHVlIHstfQoKOjo6ey5ncmVlbi1ib3h9CkZvciB0aGlzIHNpbXBsZSBleGFtcGxlLCB3ZSBjb3VsZCB1c2Ugc3RhdGlzdGljYWwgdGhlb3J5IHRvIGNhbGN1bGF0ZSAqKmV4YWN0IHAtdmFsdWUqKiAoZmFpciBjb2luIHRvc3MgZm9sbG93cyAqYmlub21pYWwgZGlzdHJpYnV0aW9uKik6CgokJFAoXHRleHR7I2hlYWRzfSBcZ2UgNjQgfCAxMDAgXHRleHR7IHRvc3NlcyBvZiBhIGZhaXIgY29pbn0pID0gXFwKMSAtIFAoXHRleHR7I2hlYWRzfSBcbGUgNjMpJCQKOjo6CgpJbiBgUmAgaXQncyBlYXN5IHRvIGNhbGN1bGF0ZSB1c2luZyB0aGUgKGN1bXVsYXRpdmUpIGRpc3RyaWJ1dGlvbiBmdW5jdGlvbiBvZiB0aGUgYmlub21pYWwgYHBiaW5vbWA6CmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAnZm9sZC1zaG93J30KMSAtIHBiaW5vbShxID0gNjMsIHNpemUgPSAxMDAsIHByb2IgPSAwLjUpCmBgYAoKRm9yICQ4MCQgaGVhZHMgdGhlIHAtdmFsdWUgY2FuIGJlIGV4YWN0bHkgZm91bmQgbm93OgpgYGB7ciwgY2xhc3Muc291cmNlID0gJ2ZvbGQtc2hvdyd9CjEgLSBwYmlub20ocSA9IDc5LCBzaXplID0gMTAwLCBwcm9iID0gMC41KQpgYGAKCiMgT21pY3MgUmFua2luZyB7LX0KCiMjIEJlbmNobWFyayBEZXNjcmlwdGlvbiB7LSNiZW5jaH0KCmBgYHtyfQpyYW5rcyA9IHJlYWRSRFMoZmlsZSA9ICdyYW5rcy5yZHMnKQpgYGAKCldlIGhhdmUgY29uZHVjdGVkIGEgYmVuY2htYXJrIGFzIGZvbGxvd3M6Cgo6Ojp7LmJsdWUtYm94fQotIFdlIHRyYWluZWQgYW5kIHRlc3RlZCBzZXZlcmFsIE1MIG1vZGVscyBvbiBtYW55IGRhdGFzZXRzCi0gVGhlIGRhdGFzZXRzIGFyZSAqKmRlcGVuZGVudCoqIChkaWZmZXJlbnQgb21pYyBjb21iaW5hdGlvbnMpCiAgLSBBbGwgc2luZ2xlIG9taWNzOiBtUk5BLCBtaVJOQSwgQ2xpbmljYWwsIGV0Yy4KICAtIEFsbCBwYWlycyBvZiBvbWljczogQ2xpbmljYWwgKyBtUk5BLCBtaVJOQSArIG1STkEKICAtIEFsbCB0cmlwbGV0cyBhbmQgc28gb24uLi4gKCpwb3dlcnNldCogPSAqKmV2ZXJ5IHBvc3NpYmxlIG9taWMgKGZlYXR1cmUgdHlwZSkgY29tYmluYXRpb24qKikKLSAqKlBlcmZvcm1hbmNlIG1lYXN1cmUqKjogQy1pbmRleCAoaGlnaGVyIGlzIGJldHRlcikKICAtIFdlIHRha2UgdGhlIG1lZGlhbiBDLWluZGV4IGFjcm9zcyAkMTAwMCQgYm9vdHN0cmFwIHJlc2FtcGxlcyBvZiB0aGUgdGVzdCBzZXQKOjo6CgpFLmcuIGZvciB0aGUgYENveE5ldGAgbW9kZWwgdGhlIHBlcmZvcm1hbmNlIGFjcm9zcyBhbGwgb21pYyBjb21iaW5hdGlvbnMgaXM6CmBgYHtyfQpjb3huZXRfc2NvcmVzID0gCiAgcmFua3MkbWF0Wydjb3huZXQnLF0gJT4lIAogIHRpYmJsZTo6ZW5mcmFtZShuYW1lID0gJ1Rhc2snLCB2YWx1ZSA9ICdDaW5kZXgnKSAlPiUKICBtdXRhdGUoVGFzayA9IGZvcmNhdHM6OmZjdF9yZW9yZGVyKFRhc2ssIENpbmRleCwgLmRlc2MgPSBUUlVFKSkKCmNveG5ldF9zY29yZXMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gVGFzaywgeSA9IENpbmRleCkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLjUsIGxpbmV0eXBlID0gJ2RvdHRlZCcsIGNvbG9yID0gJ3JlZCcpICsKICAgIGxhYnMoeCA9ICdPbWljIENvbWJpbmF0aW9ucycsIHkgPSAnSGFycmVsbFwncyBDLWluZGV4JywgdGl0bGUgPSAnQ294TmV0JykgKwogICAgeWxpbShjKDAuNCwwLjYpKSArCiAgICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcmFuaygtQ2luZGV4KSksIGhqdXN0ID0gMC41LCB2anVzdCA9IC0xKSArCiAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkgKwogICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA2MCwgaGp1c3QgPSAxKSkKYGBgCgojIyBUaGUgdGFzayB7LX0KCjo6OnsuZ3JlZW4tYm94fQpDYW4gSSBkZWZpbmUgYSAqKnN0YXRpc3RpYy9zY29yZSoqIHBlciBvbWljIHRoYXQgd2lsbCB0ZWxsIG1lICpob3cgaW1wb3J0YW50KiB0aGF0IHBhcnRpY3VsYXIgb21pYyBpcyAoYW5kICpzdHJlbmd0aCBvZiBpbXBvcnRhbmNlKiB3aXRoIHRoZSBwLXZhbHVlKT8KOjo6CgpIb3cgdG8gZGVmaW5lICppbXBvcnRhbmNlKiBpbiB0aGUgYWJvdmUgY29udGV4dD8KCiMjIFJhbmstU3VtIHNjb3JlIHstfQoKIyMjIERlcml2aW5nIHRoZSBzY29yZSB7LX0KCkxldCdzIHVzZSBzb21lIHRveSBkYXRhIHdpdGggJDMkIG9taWNzIGFuZCBhbGwgY29tYm9zICgkMl4zIC0gMSA9IDckKSAtIHdoeSBub3QgJDgkPzoKYGBge3J9CnNldDEgPSBMRVRURVJTWzE6M10Kc2V0MQpgYGAKCmBgYHtyfQpnZXRfc3Vic2V0cyA9IGZ1bmN0aW9uKHNldCkgewogIGxhcHBseSgxOmxlbmd0aChzZXQpLCBjb21iaW5hdDo6Y29tYm4sIHggPSBzZXQsIHNpbXBsaWZ5ID0gRkFMU0UpICU+JSAKICB1bmxpc3QocmVjdXJzaXZlID0gRkFMU0UpICU+JQogIGxhcHBseShmdW5jdGlvbihzdWJfc2V0KSB7CiAgICBwYXN0ZTAoc3ViX3NldCwgY29sbGFwc2UgPSAnLScpIAogIH0pICU+JSAKICB1bmxpc3QodXNlLm5hbWVzID0gRkFMU0UpCn0KCnN1YnNldHMgPSBnZXRfc3Vic2V0cyhzZXQxKQpzdWJzZXRzCmBgYAoKOjo6ey5pbmZvLWJveCAubm90ZX0KSG93IG1hbnkgdGltZXMgZXZlcnkgb21pYyBhcHBlYXJzIGluIHRoZSBhYm92ZSBsaXN0PyBGb3IgJG4kIG9taWNzPwo6OjoKCmBgYHtyfQojIEFuc3dlcjogJDJeeyhuLTEpfSQKIyBJbiB0aGUgZXhhbXBsZTogbiA9IDMgPT4gMl4yID0gNCB0aW1lcwpgYGAKCkV4YW1wbGUgcmFua2luZ3MgLSAzIHggc2FtZSBmaWd1cmUsIGNoYW5nZSBjb2xvciBpZiBvbWljIGNvbWJvIGhhczoKCi0gPHNwYW4gc3R5bGU9ImNvbG9yOiByZWQ7Ij5BPC9zcGFuPgotIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZTsiPkI8L3NwYW4+Ci0gPHNwYW4gc3R5bGU9ImNvbG9yOiBwdXJwbGU7Ij5DPC9zcGFuPgoKYGBge3J9CnNldC5zZWVkKDQyKQpzY29yZXMgPSBybm9ybSgxOjcsIG1lYW4gPSAwLjUsIHNkID0gMC4xKQp0YmwgPSB0aWJibGUoVGFzayA9IHN1YnNldHMsIG1lYXMgPSBzY29yZXMpICU+JQogICAgICBtdXRhdGUoVGFzayA9IGZvcmNhdHM6OmZjdF9yZW9yZGVyKFRhc2ssIG1lYXMsIC5kZXNjID0gVFJVRSkpCgpwID0gdGJsICU+JQogIGdncGxvdChhZXMoeCA9IFRhc2ssIHkgPSBtZWFzKSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMykgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZSA9ICdkb3R0ZWQnLCBjb2xvciA9ICdyZWQnKSArCiAgICBsYWJzKHggPSAnT21pYyBDb21iaW5hdGlvbnMnLCB5ID0gJ0hhcnJlbGxcJ3MgQy1pbmRleCcsIHRpdGxlID0gJ1RveSBFeGFtcGxlJykgKwogICAgeWxpbShjKDAuNCwwLjcpKSArCiAgICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcmFuaygtbWVhcykpLCBzaXplID0gMTAsIGhqdXN0ID0gMC41LCB2anVzdCA9IC0xKSArCiAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAyMCkKYGBgCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgZmlnLnNob3c9ImhvbGQiLCBvdXQud2lkdGg9IjUwJSIsIGNhY2hlPVR9Cm15Y29sMSA9IGlmZWxzZShncmVwbChwYXR0ZXJuID0gJ0EnLCB4ID0gbGV2ZWxzKHRibCRUYXNrKSksICdyZWQnLCAnYmxhY2snKQpteWNvbDIgPSBpZmVsc2UoZ3JlcGwocGF0dGVybiA9ICdCJywgeCA9IGxldmVscyh0YmwkVGFzaykpLCAnYmx1ZScsICdibGFjaycpCm15Y29sMyA9IGlmZWxzZShncmVwbChwYXR0ZXJuID0gJ0MnLCB4ID0gbGV2ZWxzKHRibCRUYXNrKSksICdwdXJwbGUnLCAnYmxhY2snKQoKIyBBIG1vcmUgaW1wb3J0YW50CnAgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChjb2xvciA9IG15Y29sMSkpCiMgQiBuZXh0IApwICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3IgPSBteWNvbDIpKQojIEMgbGFzdApwICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3IgPSBteWNvbDMpKQpgYGAKCjo6OnsuaW5mby1ib3ggLnRpcH0KQSBtb3JlIGltcG9ydGFudCBvbWljIHdpbGwgYmUgaW4gbW9yZSBjb21ib3MgKip0b3dhcmRzIHRoZSBsZWZ0Kiogb2YgdGhlIGFib3ZlIGZpZ3VyZXMhCjo6OgoKTGV0J3MgZGVmaW5lIGJhc2VkIG9uIHRoZSAqbW9yZSBsZWZ0Ki1pZGVhIQoKRGVmaW5lIGFzIHNjb3JlIHRoZSAqKnN1bSBvZiByYW5rcyAodGhlIGxvd2VyIHRoZSBiZXR0ZXIpKio6CgotIGBBYCA9PiAkMSsyKzMrND0xMCQgKGJlc3QgcG9zc2libGUsICQ0JCBtb3N0LWxlZnQgcmFua3MpCi0gYEJgID0+ICQxKzMrNis3PTE3JAotIGBDYCA9PiAkMSs0KzUrNj0xNiQgKGEgbGl0dGxlIGJldHRlciB0aGFuICQ4JCkKPGJyPjxicj48YnI+PGJyPgoKV29yc3Qgc2NvcmUgd291bGQgYmU/CgotICQ0JCBtb3N0LXJpZ2h0IHJhbmtzID0+ICQ0KzUrNis3PTIyJAo8YnI+PGJyPjxicj48YnI+CkkgY2FuIGVhc2lseSAqKm5vcm1hbGl6ZSoqIHRoZSBzY29yZSBzbyB0aGF0IGJlc3QgcG9zc2libGUgdmFsdWUgYmVjb21lcyAkMSQgYW5kIHdvcnN0IHBvc3NpYmxlICQwJCwgYXM6CgokJHNjb3JlID0gMSAtIFxmcmFje3N1bShyYW5rcykgLSBiZXN0KHNjb3JlKX17d29yc3Qoc2NvcmUpIC0gYmVzdChzY29yZSl9JCQKClRoZXJlZm9yZSB3ZSBoYXZlOgoKLSBgQWA6ICRzY29yZSA9IDEgLSBcZnJhY3sxMCAtIDEwfXsyMiAtIDEwfT0xJAotIGBCYDogJHNjb3JlID0gMSAtIFxmcmFjezE3IC0gMTB9ezIyIC0gMTB9PTAuNDEkCi0gYENgOiAkc2NvcmUgPSAxIC0gXGZyYWN7MTYgLSAxMH17MjIgLSAxMH09MC41JAo8YnI+PGJyPjxicj48YnI+Cjxicj48YnI+PGJyPjxicj4KCiMjIyBHZXQgYSBwLXZhbHVlIHstfQoKOjo6ey5pbmZvLWJveCAub3JhbmdlLWJveH0KLSAqKkhvdyBoaWdoKiogbmVlZHMgdGhpcyBzY29yZSB0byBiZSB0byBiZSBhY3R1YWxseSBpbXBvcnRhbnQ/Ci0gSG93IHRvIGNvbnN0cnVjdCB0aGUgKipudWxsIGRpc3RyaWJ1dGlvbioqIG9mIG91ciBkZXJpdmVkIHNjb3JlL3N0YXRpc3RpYyBhbmQgZ2V0IGFuIGVtcGlyaWNhbCBwLXZhbHVlPwo6OjoKCjxicj48YnI+PGJyPjxicj4KCk9ic2VydmF0aW9uOiBldmVyeSBvbWljIGluIHRoZSB0b3kgZXhhbXBsZSB3aWxsIHRha2UgJDQkIHZhbHVlcyBmcm9tIHRoZSBiZWxvdyByYW5raW5nczoKYGBge3J9Cm5vbWljcyA9IDMKcmFua3MgPSAxOigyXm5vbWljcy0xKQpyYW5rcwpgYGAKCldlIGp1c3QgbmVlZCB0byByYW5kb21seSBzZWxlY3QgJDQkIG9mIHRoZXNlIGFuZCBhZGQgdGhlbSAoK25vcm1hbGl6YXRpb24gc3RlcCkgdG8gZ2V0IGEgcG9zc2libGUgcmFuay1zdW0gc2NvcmUuIApPZiBjb3Vyc2Ugd2Ugd2lsbCByZXBlYXQgdGhpcyBwcm9jZWR1cmUgKiptdWx0aXBsZSB0aW1lcyoqOgpgYGB7cn0KbnNldHMgPSAyXihub21pY3MtMSkgIyA0CgojIHNtYWxsZXIgcG9zc2libGUgcmFuayBzdW0gKGFsbCBsZWZ0IHJhbmtzKQpiZXN0X3JzICA9IHN1bSgxOjJeKG5vbWljcy0xKSkgIyAxMAojIGxhcmdlciBwb3NzaWJsZSByYW5rIHN1bSAoYWxsIHJpZ2h0IHJhbmtzKQp3b3JzdF9ycyA9IHN1bShzZXEodG8gPSAyXm5vbWljcy0xLCBsZW5ndGgub3V0ID0gMl4obm9taWNzLTEpKSkgIyAyMgoKIyBnZW5lcmF0ZSBudWxsIGRpc3QgLSBzYW1lIGZvciBlYWNoIG9taWMKIyB1c2Ugc2ltcGxlIE1vbnRlIENhcmxvIHJhbmRvbSBzYW1wbGluZwpzZXQuc2VlZCg0MikKbnNhbXBsZXMgPSAxMDAwCm51bGxfbnJzID0gc2FwcGx5KDE6bnNhbXBsZXMsIGZ1bmN0aW9uKHMpIHsKICByYW5rX3N1bSA9IHN1bShzYW1wbGUoeCA9IHJhbmtzLCBzaXplID0gbnNldHMsIHJlcGxhY2UgPSBGQUxTRSkpCiAgMSAtIChyYW5rX3N1bSAtIGJlc3RfcnMpLyh3b3JzdF9ycyAtIGJlc3RfcnMpCn0pCmBgYAoKYGBge3J9Cmhpc3QobnVsbF9ucnMpCmBgYAoKYEFgIG9taWMgaXMgaW1wb3J0YW50IGF0IHRoZSAkMC4wNSQgc2lnbmlmaWNhbmNlIGxldmVsOgpgYGB7ciwgY2xhc3Muc291cmNlID0gJ2ZvbGQtc2hvdyd9CnNjb3JlQSA9IDEKcHZhbF9BID0gKHN1bShudWxsX25ycyA+PSBzY29yZUEpKzEpLyhsZW5ndGgobnVsbF9ucnMpKzEpCnB2YWxfQQpgYGAKCmBgYHtyLCBjbGFzcy5zb3VyY2UgPSAnZm9sZC1zaG93J30Kc2NvcmVCID0gMC40MQpwdmFsX0IgPSAoc3VtKG51bGxfbnJzID49IHNjb3JlQikrMSkvKGxlbmd0aChudWxsX25ycykrMSkKcHZhbF9CCmBgYAoKYGBge3IsIGNsYXNzLnNvdXJjZSA9ICdmb2xkLXNob3cnfQpzY29yZUMgPSAwLjUKcHZhbF9DID0gKHN1bShudWxsX25ycyA+PSBzY29yZUMpKzEpLyhsZW5ndGgobnVsbF9ucnMpKzEpCnB2YWxfQwpgYGAKCkFub3RoZXIgaHlwb3RoZXRpY2FsIGV4YW1wbGUgd2l0aCBhICRzY29yZSA9IDAuOTIkOgpgYGB7ciwgY2xhc3Muc291cmNlID0gJ2ZvbGQtc2hvdyd9CnNjb3JlID0gMC45MgpwdmFsID0gKHN1bShudWxsX25ycyA+PSBzY29yZSkrMSkvKGxlbmd0aChudWxsX25ycykrMSkKcHZhbApgYGAKCi0gRGlzY3VzcyBpbXBvcnRhbmNlIG9mIG51bWJlciBvZiBvbWljcyAkbiQgKHRoZSBtb3JlIHRoZSBiZXR0ZXIhKQoKKipVbmlxdWUgc2NvcmVzKiogaW4gdGhlIG51bGwgZGlzdHJpYnV0aW9uIHZlcnkgZmV3OgpgYGB7cn0KdW5pcXVlKG51bGxfbnJzKQpgYGAKCiMjIyBSZWxhdGlvbiB0byBXaWxjb3hvbiBSYW5rIFN1bSBUZXN0IHstfQoKTGV0J3MgZm9jdXMgb24gYEFgIG9taWMgYW5kIGZvY3VzIG5vdCBvbiByYW5raW5ncyBidXQgb24gdGhlIGFjdHVhbCBwZXJmb3JtYW5jZSBzY29yZXM6CmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBmaWcuc2hvdz0iaG9sZCIsIG91dC53aWR0aD0iNTAlIiwgY2FjaGU9VH0KdGJsICU+JQogIGdncGxvdChhZXMoeCA9IFRhc2ssIHkgPSBtZWFzKSkgKwogICAgZ2VvbV9wb2ludChzaXplID0gMykgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMC41LCBsaW5ldHlwZSA9ICdkb3R0ZWQnLCBjb2xvciA9ICdyZWQnKSArCiAgICBsYWJzKHggPSAnT21pYyBDb21iaW5hdGlvbnMnLCB5ID0gJ0hhcnJlbGxcJ3MgQy1pbmRleCcsIHRpdGxlID0gJ1RveSBFeGFtcGxlJykgKwogICAgeWxpbShjKDAuNCwwLjcpKSArCiAgICBnZW9tX3RleHQoYWVzKGxhYmVsID0gc3ByaW50ZigiJTAuM2YiLCByb3VuZChtZWFzLCBkaWdpdHMgPSAzKSkpLCBzaXplID0gNywgaGp1c3QgPSAwLjUsIHZqdXN0ID0gLTEpICsKICAgIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDIwKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChjb2xvciA9IG15Y29sMSkpCgp0YmwgPSB0YmwgJT4lIAogIG11dGF0ZShncnAgPSBjYXNlX3doZW4oCiAgICBncmVwbChwYXR0ZXJuID0gJ0EnLCB4ID0gVGFzaykgfiAnQScsIAogICAgVFJVRSB+ICdub3QgQScpCiAgKQoKdGJsICU+JQogIGdncGxvdChhZXMoeCA9IGdycCwgeSA9IG1lYXMsIGZpbGwgPSBncnApKSArCiAgICBnZW9tX2JveHBsb3QoKSArCiAgICB0aGVtZV9idyhiYXNlX3NpemUgPSAyMCkKYGBgCgpgYGB7ciwgY2xhc3Muc291cmNlID0gJ2ZvbGQtc2hvdyd9CkEgICAgPSB0YmwgJT4lIGZpbHRlcihncnAgPT0gJ0EnKSAlPiUgcHVsbChtZWFzKQpub3RBID0gdGJsICU+JSBmaWx0ZXIoZ3JwID09ICdub3QgQScpICU+JSBwdWxsKG1lYXMpCgojIGlzIHBlcmZvcm1hbmNlIHNjb3JlcyBmcm9tICdBJyBkaXN0cmlidXRpb24gbGVzcyB0aGFuICdub3RBJz8Kd2lsY294LnRlc3QoeCA9IEEsIHkgPSBub3RBLCBhbHRlcm5hdGl2ZSA9ICdncmVhdGVyJykKYGBgCgo6Ojp7LmluZm8tYm94IC5ub3RlfQpSZXN1bHRzIGFyZSBjbG9zZSAtIHRoZSBjYWxjdWxhdGlvbiBvZiBgV2Agc3RhdGlzdGljIGlzIHNpbWlsYXIgdG8gb3VyIGxvZ2ljIChzdW0gb2YgcmFua3MpCjo6OgoKIyMgS29sbW9nb3Jvdi1TbWlybm92IHN0YXRpc3RpYyB7LX0KClRoZSBpZGVhIGZvciB0aGlzIHN0YXRpc3RpYyBjYW1lIGZyb20gdmlld2luZyB0aGlzIGFzIGEgKmdlbmUgZW5yaWNobWVudCogcHJvYmxlbSAoc2VlIFtSZWZlcmVuY2VzXSkuCgo6Ojp7LmdyZWVuLWJveH0KLSAqKktTIHRlc3QqKjogY2hlY2sgaWYgMiBkYXRhIHNhbXBsZXMgY29tZSBmcm9tIHRoZSBzYW1lIHByb2JhYmlsaXR5IGRpc3RyaWJ1dGlvbiBvciBub3QuCkl0IHVzZXMgdGhlIGVDREYgKEVtcGlyaWNhbCBDdW11bGF0aXZlIERlbnNpdHkgRnVuY3Rpb24pIFtbd2lraXBlZGlhXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Lb2xtb2dvcm92LVNtaXJub3ZfdGVzdCldCjo6OgoKTGV0J3MgdmlldyBhbiBleGFtcGxlIHVzaW5nIHRoZSByZXNlYXJjaCBbcmVzdWx0c10oI2JlbmNoKSBhbmQgY29uc2lkZXIgdGhlIGBDbGluaWNhbGAgYXMgdGhlIG9taWMgb2YgaW50ZXJlc3Q6CmBgYHtyfQpjb3huZXRfc2NvcmVzID0gY294bmV0X3Njb3JlcyAlPiUgCiAgbXV0YXRlKGdycCA9IGNhc2Vfd2hlbigKICAgIGdyZXBsKHBhdHRlcm4gPSAnQ2xpbmljYWwnLCB4ID0gVGFzaykgfiAnQ2xpbicsIAogICAgVFJVRSB+ICdub3QtQ2xpbicpCiAgKQoKY2xpbiAgICAgPSBjb3huZXRfc2NvcmVzICU+JSBmaWx0ZXIoZ3JwID09ICdDbGluJykgJT4lIHB1bGwoQ2luZGV4KQpub3RfY2xpbiA9IGNveG5ldF9zY29yZXMgJT4lIGZpbHRlcihncnAgPT0gJ25vdC1DbGluJykgJT4lIHB1bGwoQ2luZGV4KQpgYGAKCkhpc3RvZ3JhbXMvRGVuc2l0eSBvZiB0aGUgcGVyZm9ybWFuY2Ugc2NvcmVzIChDLWluZGV4ZXMpLCBjb21wYXJpbmcgb21pYy1jb21ib3MgdGhhdCBoYWQgKipDbGluaWNhbCoqIGZlYXR1cmVzIGluY2x1ZGVkIHZzIHRob3NlIHRoYXQgZGlkIG5vdDoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmdncGxvdChjb3huZXRfc2NvcmVzLCBhZXMoeSA9IENpbmRleCwgZmlsbCA9IGdycCkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW5zID0gMjApICsKICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjMpICsKICB5bGltKGMoMC40LCAwLjYpKSArCiAgY29vcmRfZmxpcCgpICsKICB0aGVtZV9idyhiYXNlX3NpemUgPSAxNCkKYGBgCgpUaGUgZW1waXJpY2FsIEN1bXVsYXRpdmUgRGlzdHJpYnV0aW9uIEZ1bmN0aW9uIGZvciBlYWNoIGdyb3VwIGlzOgpgYGB7cn0KZ2dwbG90KGNveG5ldF9zY29yZXMsIGFlcyhDaW5kZXgsIGNvbG91ciA9IGdycCkpICsKICBzdGF0X2VjZGYoKSArCiAgbGFicyh5ID0gJ0VtcGlyaWNhbCBDREYnKSArCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQpCmBgYAoKYGBge3J9CmtzLnRlc3QoeCA9IGNsaW4sIHkgPSBub3RfY2xpbiwgYWx0ZXJuYXRpdmUgPSAnbGVzcycpCmBgYAoKOjo6ey5pbmZvLWJveCAubm90ZX0KLSBDb21wYXJlIG91ciBzdGF0aXN0aWMgdnMgS1Mgc3RhdGlzdGljCgpVc3VhbGx5IGl0J3MgYmV0dGVyIHRvIG5vdCByZS1pbnZlbnQgdGhlIHdoZWVsIGJ1dCBzb21ldGltZXMgd2UgbmVlZCB0byEKOjo6CgojIFJlZmVyZW5jZXMgey19CgotIENoYXB0ZXIgMTMgb2YgMm5kIGVkaXRpb24gb2YgKkFuIEludHJvZHVjdGlvbiB0byBTdGF0aXN0aWNhbCBMZWFybmluZyosIFNwcmluZ2VyCi0gU3VicmFtYW5pYW4sIEEuLCBUYW1heW8sIFAuLCBNb290aGEsIFYuIEsuLCBNdWtoZXJqZWUsIFMuLCBFYmVydCwgQi4gTC4sIEdpbGxldHRlLCBNLiBBLiwgUGF1bG92aWNoLCBBLiwgUG9tZXJveSwgUy4gTC4sIEdvbHViLCBULiBSLiwgTGFuZGVyLCBFLiBTLiwgJiBNZXNpcm92LCBKLiBQLiAoMjAwNSkuIEdlbmUgc2V0IGVucmljaG1lbnQgYW5hbHlzaXM6IEEga25vd2xlZGdlLWJhc2VkIGFwcHJvYWNoIGZvciBpbnRlcnByZXRpbmcgZ2Vub21lLXdpZGUgZXhwcmVzc2lvbiBwcm9maWxlcy4gUHJvY2VlZGluZ3Mgb2YgdGhlIE5hdGlvbmFsIEFjYWRlbXkgb2YgU2NpZW5jZXMsIDEwMig0MyksIDE1NTQ14oCTMTU1NTAuIGh0dHBzOi8vZG9pLm9yZy8xMC4xMDczL1BOQVMuMDUwNjU4MDEwMg==