Carl Mathias Kobel (carl.mathias.kobel near nmbu.no) Wed Jan 17
17:52:56 2024
Technical setup and parameters for reproducibility
Technical note: We recommend that you use
“docker://rocker/tidyverse:4.3.2” (https://rocker-project.org/images/versioned/rstudio.html)
to ensure successful reproducibility.
library(tidyverse)
R.version # v4.2.2
_
platform x86_64-pc-linux-gnu
arch x86_64
os linux-gnu
system x86_64, linux-gnu
status Patched
major 4
minor 2.2
year 2022
month 11
day 10
svn rev 83330
language R
version.string R version 4.2.2 Patched (2022-11-10 r83330)
nickname Innocent and Trusting
packageVersion("tidyverse") # v2.0.0
[1] ‘2.0.0’
knitr::opts_chunk$set(
echo = T,
results = 'show',
message = F,
warning = F
)
Problem?
Let us consider a hypothetical holo-omics study, where we have
measured the host transcriptome of the rumen wall in 100 cows (n = 100)
and the meta-transcriptome of the rumen content in those same
individuals (p = 20 000 host genes + average 3000 microbial genes x 200
microbial species = 620 000 features). Let us further assume that the
experiment is set up to measure methane emission, and that half of the
cows were given a methane inhibiting feed additive (treatment) that
indeed reduced emissions. This dataset would pose a massive challenge
for data analysis, and not primarily because it would require
considerable computational resources to assemble and annotate metagenome
assembled genomes (MAGs) and estimate expression (read mapping). The
main challenge is related to the large number of features compared to
samples. Naively one would think that this data set could be analysed
using multivariate- or machine learning-based prediction methods, where
the predictive model could be queried for features or combination of
features that contributed significantly to the prediction, e.g. IF gene
G on MAG5 is up and host gene H is down THEN low methane. However, with
this many features there will be an enormous number of feature
combinations that could separate low and high emitting cows, and with
only 100 examples (cows) to constrain them, we would never be able to
discern real biological feature-combinations from spurious ones (see
supplement for R code showing perfect prediction of a randomly generated
data set with n << p). This phenomenon is referred to as
overfitting and is a consequence of the curse of dimensionality – the
number of examples (cows) needed to identify the biologically meaningful
features grows exponentially with the number of features.
Generate data
List n cow samples.
n = 20 # number of samples (cows)
samples = tibble(
sample = paste0("cow_", str_pad(1:n, 3, pad = 0)) # Formatting string prefixed with "cow_" and a numerical identifier from 1 through n.
)
samples
NA
Generate normal data from a random omic layer.
#set.seed(1324) # Use this to get equivalent results through space and time.
set.seed(26*65535)
p = 10000 # number of features for a hypothesized omic layer
omic_layer = samples %>%
bind_cols(
matrix( # Generating m vectors (biological features) with one value for each n samples.
rnorm(n*p, mean = 1.5, sd = 1) %>% # Using a low mean, means that we will get some negative values, which might become "missing values" if transforming with sqrt or log.
sqrt() %>% # Trying to add some "noise" by squarerooting it .
identity(),
n,
p
) %>%
as_tibble()
)
Warning: NaNs produced
# Quick visualizations
omic_layer %>%
pivot_longer(-sample) %>%
ggplot(aes(sample, name, fill = value)) +
geom_raster() +
theme(
axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5)
)

omic_layer %>%
head(7) %>%
pivot_longer(-sample) %>%
ggplot(aes(value)) +
geom_histogram() +
facet_wrap(~sample)

NA
NA
NA
NA
NA
NA
Generate a random trait vector “methane”.
trait = samples %>%
mutate(methane = rnorm(n))
trait %>% glimpse()
Rows: 20
Columns: 2
$ sample <chr> "cow_001", "cow_002", "cow_003", "cow_004", "cow_005", "cow_006", "cow_007", "cow_008", "cow_009", "cow_010", "cow_011", "cow_012", "cow_013", "cow_014", …
$ methane <dbl> 0.01976174, 0.08003521, 0.91722540, -0.99243056, -0.24581664, 1.38859292, -1.24070365, 0.39399783, -0.13013399, -0.32952038, -0.68669781, -1.01283368, 0.7…
Make models
Calculate linear models between omic layer features and methane
trait
data = trait %>%
left_join(omic_layer, by = "sample") %>%
column_to_rownames("sample")
# Perform multiple testing
tests = lapply(
omic_layer[-1], # %>% as.data.frame(),
function(x) {
#x
data = bind_cols(trait, feature = x) %>%
column_to_rownames("sample")
lm(
formula = "methane ~ feature",
data = data
) %>%
summary() # The summary() function calculates F statistic and pvalue.
}
)
# Extract pvalues alone.
pvals_raw = lapply(
tests,
function(x) {
x$coefficients[2,4] # pvalue of the slope is at coordinate 2,4 in the table.
}
) %>% unlist() %>%
as_tibble(rownames = "feature") %>%
rename(p_raw = value)
# Checking that the p-values are uniformly distributed which I think should be an assumption of multiple testing adjustment algorithms.
pvals_raw %>%
ggplot(aes(p_raw)) +
geom_histogram()

NA
NA
Adjust for multiple testing
Adjust for multiple testing by using benjamini hochberg
correction.
pvals_adjusted = p.adjust(
pvals_raw$p_raw,
method = "BH"
)
alpha_raw = 0.05
alpha_adjusted = 0.05
pvals = bind_cols(
pvals_raw, p_adjusted = pvals_adjusted) %>%
mutate(
p_raw_significant = p_raw < alpha_raw,
p_adjusted_significant = p_adjusted < alpha_adjusted
)
# Examine relationship between raw and adjusted pvalues.
pvals %>%
mutate(label = case_when(
p_adjusted_significant ~ feature,
.default = ""
)) %>%
ggplot(aes(p_raw, p_adjusted, color = paste(p_raw_significant, p_adjusted_significant), label = label )) +
geom_point() +
geom_text(color = "black", alpha = 0.5, size = 3, hjust = 0, vjust = 0) + # Put labels on the significant features.
geom_vline(xintercept = alpha_raw, linetype = "dashed", color = "red", alpha = 0.2) +
geom_hline(yintercept = alpha_adjusted, linetype = "dashed", color = "red", alpha = 0.2)

NA
NA
NA
NA
NA
NA
NA
NA
Investigating “significant” results.
Depending on your seed and setting, this might contain a spuriously
correlated gene that “explains” methane. Investigate the statistically
significant results (which are in fact random and spurious)
# Stop this script if there are no significant results.
stopifnot(
(pvals %>%
filter(p_adjusted_significant) %>%
nrow()) >= 1
)
significant_features = pvals %>%
filter(p_adjusted_significant)
significant_features
omic_layer %>%
select(sample, significant_features %>% pull(feature)) %>%
left_join(trait, by = "sample") %>%
pivot_longer(-c(sample, methane)) %>%
ggplot(aes(value, methane)) +
facet_wrap(~name) +
geom_point() +
geom_smooth(method = "lm") +
labs(
caption = paste("These spurious results exist even though we did adjustment for multiple testing.\n", n, "samples (cows), and", p, "biological features in the hypothetical omic layer.")
)

Just to collect html documents. Be aware that this chunk has a
circular dependency, because it requires that this document is saved and
knitted as both html and pdf.
LS0tCnRpdGxlOiAiSG9sbyBvbWljIHNhbXBsZSBwcm9ibGVtIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKLS0tCgpDYXJsIE1hdGhpYXMgS29iZWwgKGNhcmwubWF0aGlhcy5rb2JlbCBuZWFyIG5tYnUubm8pIGByIGRhdGUoKWAKCiMjIFRlY2huaWNhbCBzZXR1cCBhbmQgcGFyYW1ldGVycyBmb3IgcmVwcm9kdWNpYmlsaXR5CgpUZWNobmljYWwgbm90ZTogV2UgcmVjb21tZW5kIHRoYXQgeW91IHVzZSAiZG9ja2VyOi8vcm9ja2VyL3RpZHl2ZXJzZTo0LjMuMiIgKGh0dHBzOi8vcm9ja2VyLXByb2plY3Qub3JnL2ltYWdlcy92ZXJzaW9uZWQvcnN0dWRpby5odG1sKSB0byBlbnN1cmUgc3VjY2Vzc2Z1bCByZXByb2R1Y2liaWxpdHkuCgpgYGB7cn0KCmxpYnJhcnkodGlkeXZlcnNlKQoKCgpSLnZlcnNpb24gIyB2NC4yLjIKcGFja2FnZVZlcnNpb24oInRpZHl2ZXJzZSIpICMgdjIuMC4wCgprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgICBlY2hvID0gVCwKICAgIHJlc3VsdHMgPSAnc2hvdycsIAogICAgbWVzc2FnZSA9IEYsIAogICAgd2FybmluZyA9IEYKICAgICkKCgpgYGAKCgojIyBQcm9ibGVtPwoKTGV0IHVzIGNvbnNpZGVyIGEgaHlwb3RoZXRpY2FsIGhvbG8tb21pY3Mgc3R1ZHksIHdoZXJlIHdlIGhhdmUgbWVhc3VyZWQgdGhlIGhvc3QgdHJhbnNjcmlwdG9tZSBvZiB0aGUgcnVtZW4gd2FsbCBpbiAxMDAgY293cyAobiA9IDEwMCkgYW5kIHRoZSBtZXRhLXRyYW5zY3JpcHRvbWUgb2YgdGhlIHJ1bWVuIGNvbnRlbnQgaW4gdGhvc2Ugc2FtZSBpbmRpdmlkdWFscyAocCA9IDIwIDAwMCBob3N0IGdlbmVzICsgYXZlcmFnZSAzMDAwIG1pY3JvYmlhbCBnZW5lcyB4IDIwMCBtaWNyb2JpYWwgc3BlY2llcyA9IDYyMCAwMDAgZmVhdHVyZXMpLiBMZXQgdXMgZnVydGhlciBhc3N1bWUgdGhhdCB0aGUgZXhwZXJpbWVudCBpcyBzZXQgdXAgdG8gbWVhc3VyZSBtZXRoYW5lIGVtaXNzaW9uLCBhbmQgdGhhdCBoYWxmIG9mIHRoZSBjb3dzIHdlcmUgZ2l2ZW4gYSBtZXRoYW5lIGluaGliaXRpbmcgZmVlZCBhZGRpdGl2ZSAodHJlYXRtZW50KSB0aGF0IGluZGVlZCByZWR1Y2VkIGVtaXNzaW9ucy4gVGhpcyBkYXRhc2V0IHdvdWxkIHBvc2UgYSBtYXNzaXZlIGNoYWxsZW5nZSBmb3IgZGF0YSBhbmFseXNpcywgYW5kIG5vdCBwcmltYXJpbHkgYmVjYXVzZSBpdCB3b3VsZCByZXF1aXJlIGNvbnNpZGVyYWJsZSBjb21wdXRhdGlvbmFsIHJlc291cmNlcyB0byBhc3NlbWJsZSBhbmQgYW5ub3RhdGUgbWV0YWdlbm9tZSBhc3NlbWJsZWQgZ2Vub21lcyAoTUFHcykgYW5kIGVzdGltYXRlIGV4cHJlc3Npb24gKHJlYWQgbWFwcGluZykuIFRoZSBtYWluIGNoYWxsZW5nZSBpcyByZWxhdGVkIHRvIHRoZSBsYXJnZSBudW1iZXIgb2YgZmVhdHVyZXMgY29tcGFyZWQgdG8gc2FtcGxlcy4gTmFpdmVseSBvbmUgd291bGQgdGhpbmsgdGhhdCB0aGlzIGRhdGEgc2V0IGNvdWxkIGJlIGFuYWx5c2VkIHVzaW5nIG11bHRpdmFyaWF0ZS0gb3IgbWFjaGluZSBsZWFybmluZy1iYXNlZCBwcmVkaWN0aW9uIG1ldGhvZHMsIHdoZXJlIHRoZSBwcmVkaWN0aXZlIG1vZGVsIGNvdWxkIGJlIHF1ZXJpZWQgZm9yIGZlYXR1cmVzIG9yIGNvbWJpbmF0aW9uIG9mIGZlYXR1cmVzIHRoYXQgY29udHJpYnV0ZWQgc2lnbmlmaWNhbnRseSB0byB0aGUgcHJlZGljdGlvbiwgZS5nLiBJRiBnZW5lIEcgb24gTUFHNSBpcyB1cCBhbmQgaG9zdCBnZW5lIEggaXMgZG93biBUSEVOIGxvdyBtZXRoYW5lLiBIb3dldmVyLCB3aXRoIHRoaXMgbWFueSBmZWF0dXJlcyB0aGVyZSB3aWxsIGJlIGFuIGVub3Jtb3VzIG51bWJlciBvZiBmZWF0dXJlIGNvbWJpbmF0aW9ucyB0aGF0IGNvdWxkIHNlcGFyYXRlIGxvdyBhbmQgaGlnaCBlbWl0dGluZyBjb3dzLCBhbmQgd2l0aCBvbmx5IDEwMCBleGFtcGxlcyAoY293cykgdG8gY29uc3RyYWluIHRoZW0sIHdlIHdvdWxkIG5ldmVyIGJlIGFibGUgdG8gZGlzY2VybiByZWFsIGJpb2xvZ2ljYWwgZmVhdHVyZS1jb21iaW5hdGlvbnMgZnJvbSBzcHVyaW91cyBvbmVzIChzZWUgc3VwcGxlbWVudCBmb3IgUiBjb2RlIHNob3dpbmcgcGVyZmVjdCBwcmVkaWN0aW9uIG9mIGEgcmFuZG9tbHkgZ2VuZXJhdGVkIGRhdGEgc2V0IHdpdGggbiA8PCBwKS4gVGhpcyBwaGVub21lbm9uIGlzIHJlZmVycmVkIHRvIGFzIG92ZXJmaXR0aW5nIGFuZCBpcyBhIGNvbnNlcXVlbmNlIG9mIHRoZSBjdXJzZSBvZiBkaW1lbnNpb25hbGl0eSDigJMgdGhlIG51bWJlciBvZiBleGFtcGxlcyAoY293cykgbmVlZGVkIHRvIGlkZW50aWZ5IHRoZSBiaW9sb2dpY2FsbHkgbWVhbmluZ2Z1bCBmZWF0dXJlcyBncm93cyBleHBvbmVudGlhbGx5IHdpdGggdGhlIG51bWJlciBvZiBmZWF0dXJlcy4gCgoKCiMjIEdlbmVyYXRlIGRhdGEKCkxpc3QgbiBjb3cgc2FtcGxlcy4KYGBge3J9CgoKbiA9IDIwICMgbnVtYmVyIG9mIHNhbXBsZXMgKGNvd3MpCgpzYW1wbGVzID0gdGliYmxlKAogICAgc2FtcGxlID0gcGFzdGUwKCJjb3dfIiwgc3RyX3BhZCgxOm4sIDMsIHBhZCA9IDApKSAjIEZvcm1hdHRpbmcgc3RyaW5nIHByZWZpeGVkIHdpdGggImNvd18iIGFuZCBhIG51bWVyaWNhbCBpZGVudGlmaWVyIGZyb20gMSB0aHJvdWdoIG4uCikKCnNhbXBsZXMKCmBgYAoKR2VuZXJhdGUgbm9ybWFsIGRhdGEgZnJvbSBhIHJhbmRvbSBvbWljIGxheWVyLgpgYGB7cn0KI3NldC5zZWVkKDEzMjQpICMgVXNlIHRoaXMgdG8gZ2V0IGVxdWl2YWxlbnQgcmVzdWx0cyB0aHJvdWdoIHNwYWNlIGFuZCB0aW1lLgpzZXQuc2VlZCgyNio2NTUzNSkKcCA9IDEwMDAwICMgbnVtYmVyIG9mIGZlYXR1cmVzIGZvciBhIGh5cG90aGVzaXplZCBvbWljIGxheWVyCgpvbWljX2xheWVyID0gc2FtcGxlcyAlPiUgCiAgICBiaW5kX2NvbHMoCiAgICAgICAgbWF0cml4KCAjIEdlbmVyYXRpbmcgbSB2ZWN0b3JzIChiaW9sb2dpY2FsIGZlYXR1cmVzKSB3aXRoIG9uZSB2YWx1ZSBmb3IgZWFjaCBuIHNhbXBsZXMuICAKICAgICAgICAgICAgcm5vcm0obipwLCBtZWFuID0gMS41LCBzZCA9IDEpICU+JSAjIFVzaW5nIGEgbG93IG1lYW4sIG1lYW5zIHRoYXQgd2Ugd2lsbCBnZXQgc29tZSBuZWdhdGl2ZSB2YWx1ZXMsIHdoaWNoIG1pZ2h0IGJlY29tZSAibWlzc2luZyB2YWx1ZXMiIGlmIHRyYW5zZm9ybWluZyB3aXRoIHNxcnQgb3IgbG9nLgogICAgICAgICAgICAgICAgc3FydCgpICU+JSAgIyBUcnlpbmcgdG8gYWRkIHNvbWUgIm5vaXNlIiBieSBzcXVhcmVyb290aW5nIGl0IC4KICAgICAgICAgICAgICAgIGlkZW50aXR5KCksCiAgICAgICAgICAgIG4sCiAgICAgICAgICAgIHAgCiAgICAgICAgKSAlPiUKICAgICAgICAgICAgYXNfdGliYmxlKCkKICAgICkgCgoKCgojIFF1aWNrIHZpc3VhbGl6YXRpb25zCm9taWNfbGF5ZXIgJT4lIAogICAgcGl2b3RfbG9uZ2VyKC1zYW1wbGUpICU+JSAKICAgIGdncGxvdChhZXMoc2FtcGxlLCBuYW1lLCBmaWxsID0gdmFsdWUpKSArIAogICAgZ2VvbV9yYXN0ZXIoKSArIAogICAgdGhlbWUoCiAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IDAuNSkKICAgICkKCm9taWNfbGF5ZXIgJT4lIAogICAgaGVhZCg3KSAlPiUgCiAgICBwaXZvdF9sb25nZXIoLXNhbXBsZSkgJT4lIAogICAgCiAgICBnZ3Bsb3QoYWVzKHZhbHVlKSkgKwogICAgZ2VvbV9oaXN0b2dyYW0oKSArIAogICAgZmFjZXRfd3JhcCh+c2FtcGxlKQogICAgCgoKCgoKYGBgCgpHZW5lcmF0ZSBhIHJhbmRvbSB0cmFpdCB2ZWN0b3IgIm1ldGhhbmUiLgpgYGB7cn0KCnRyYWl0ID0gc2FtcGxlcyAlPiUgCiAgICBtdXRhdGUobWV0aGFuZSA9IHJub3JtKG4pKQoKdHJhaXQgJT4lIGdsaW1wc2UoKQpgYGAKCiMjIE1ha2UgbW9kZWxzCgpDYWxjdWxhdGUgbGluZWFyIG1vZGVscyBiZXR3ZWVuIG9taWMgbGF5ZXIgZmVhdHVyZXMgYW5kIG1ldGhhbmUgdHJhaXQKYGBge3J9CmRhdGEgPSB0cmFpdCAlPiUgCiAgICBsZWZ0X2pvaW4ob21pY19sYXllciwgYnkgPSAic2FtcGxlIikgICU+JSAKICAgIGNvbHVtbl90b19yb3duYW1lcygic2FtcGxlIikKCiMgUGVyZm9ybSBtdWx0aXBsZSB0ZXN0aW5nIAp0ZXN0cyA9IGxhcHBseSgKICAgIG9taWNfbGF5ZXJbLTFdLCAjICU+JSBhcy5kYXRhLmZyYW1lKCksCiAgICBmdW5jdGlvbih4KSB7CiAgICAgICAgI3gKICAgICAgICBkYXRhID0gYmluZF9jb2xzKHRyYWl0LCBmZWF0dXJlID0geCkgJT4lIAogICAgICAgICAgICBjb2x1bW5fdG9fcm93bmFtZXMoInNhbXBsZSIpCiAgICAgICAgCiAgICAgICAgbG0oCiAgICAgICAgICAgIGZvcm11bGEgPSAibWV0aGFuZSB+IGZlYXR1cmUiLAogICAgICAgICAgICBkYXRhID0gZGF0YQogICAgICAgICkgJT4lCiAgICAgICAgICAgIHN1bW1hcnkoKSAjIFRoZSBzdW1tYXJ5KCkgZnVuY3Rpb24gY2FsY3VsYXRlcyBGIHN0YXRpc3RpYyBhbmQgcHZhbHVlLgogICAgfQopCgoKCiMgRXh0cmFjdCBwdmFsdWVzIGFsb25lLgpwdmFsc19yYXcgPSBsYXBwbHkoCiAgICB0ZXN0cywKICAgIGZ1bmN0aW9uKHgpIHsKICAgICAgICB4JGNvZWZmaWNpZW50c1syLDRdICMgcHZhbHVlIG9mIHRoZSBzbG9wZSBpcyBhdCBjb29yZGluYXRlIDIsNCBpbiB0aGUgdGFibGUuCiAgICB9CikgJT4lIHVubGlzdCgpICAgJT4lIAogICAgYXNfdGliYmxlKHJvd25hbWVzID0gImZlYXR1cmUiKSAgJT4lIAogICAgcmVuYW1lKHBfcmF3ID0gdmFsdWUpCgoKIyBDaGVja2luZyB0aGF0IHRoZSBwLXZhbHVlcyBhcmUgdW5pZm9ybWx5IGRpc3RyaWJ1dGVkIHdoaWNoIEkgdGhpbmsgc2hvdWxkIGJlIGFuIGFzc3VtcHRpb24gb2YgbXVsdGlwbGUgdGVzdGluZyBhZGp1c3RtZW50IGFsZ29yaXRobXMuCgpwdmFsc19yYXcgJT4lIAogICAgZ2dwbG90KGFlcyhwX3JhdykpICsgCiAgICBnZW9tX2hpc3RvZ3JhbSgpCgoKYGBgCgojIyBBZGp1c3QgZm9yIG11bHRpcGxlIHRlc3RpbmcKCkFkanVzdCBmb3IgbXVsdGlwbGUgdGVzdGluZyBieSB1c2luZyBiZW5qYW1pbmkgaG9jaGJlcmcgY29ycmVjdGlvbi4KCmBgYHtyfQpwdmFsc19hZGp1c3RlZCA9IHAuYWRqdXN0KAogICAgcHZhbHNfcmF3JHBfcmF3LAogICAgbWV0aG9kID0gIkJIIgopCgphbHBoYV9yYXcgPSAwLjA1CmFscGhhX2FkanVzdGVkID0gMC4wNQoKcHZhbHMgPSBiaW5kX2NvbHMoCiAgICBwdmFsc19yYXcsIHBfYWRqdXN0ZWQgPSBwdmFsc19hZGp1c3RlZCkgJT4lIAogICAgbXV0YXRlKAogICAgICAgIHBfcmF3X3NpZ25pZmljYW50ID0gcF9yYXcgPCBhbHBoYV9yYXcsCiAgICAgICAgcF9hZGp1c3RlZF9zaWduaWZpY2FudCA9IHBfYWRqdXN0ZWQgPCBhbHBoYV9hZGp1c3RlZAogICAgKQoKCiMgRXhhbWluZSByZWxhdGlvbnNoaXAgYmV0d2VlbiByYXcgYW5kIGFkanVzdGVkIHB2YWx1ZXMuCnB2YWxzICU+JSAKICAgIAogICAgbXV0YXRlKGxhYmVsID0gY2FzZV93aGVuKAogICAgICAgIHBfYWRqdXN0ZWRfc2lnbmlmaWNhbnQgfiBmZWF0dXJlLAogICAgICAgIC5kZWZhdWx0ID0gIiIKICAgICkpICU+JSAKCiAgICBnZ3Bsb3QoYWVzKHBfcmF3LCBwX2FkanVzdGVkLCBjb2xvciA9IHBhc3RlKHBfcmF3X3NpZ25pZmljYW50LCBwX2FkanVzdGVkX3NpZ25pZmljYW50KSwgbGFiZWwgPSBsYWJlbCApKSArIAogICAgZ2VvbV9wb2ludCgpICsgCiAgICBnZW9tX3RleHQoY29sb3IgPSAiYmxhY2siLCBhbHBoYSA9IDAuNSwgc2l6ZSA9IDMsIGhqdXN0ID0gMCwgdmp1c3QgPSAwKSArICMgUHV0IGxhYmVscyBvbiB0aGUgc2lnbmlmaWNhbnQgZmVhdHVyZXMuCiAgICAKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGFscGhhX3JhdywgbGluZXR5cGUgPSAiZGFzaGVkIiwgY29sb3IgPSAicmVkIiwgYWxwaGEgPSAwLjIpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IGFscGhhX2FkanVzdGVkLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJyZWQiLCBhbHBoYSA9IDAuMikKCgoKCgogICAgCgoKYGBgCgoKIyMgSW52ZXN0aWdhdGluZyAic2lnbmlmaWNhbnQiIHJlc3VsdHMuCgpEZXBlbmRpbmcgb24geW91ciBzZWVkIGFuZCBzZXR0aW5nLCB0aGlzIG1pZ2h0IGNvbnRhaW4gYSBzcHVyaW91c2x5IGNvcnJlbGF0ZWQgZ2VuZSB0aGF0ICJleHBsYWlucyIgbWV0aGFuZS4KSW52ZXN0aWdhdGUgdGhlIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgcmVzdWx0cyAod2hpY2ggYXJlIGluIGZhY3QgcmFuZG9tIGFuZCBzcHVyaW91cykKCmBgYHtyfQoKCiMgU3RvcCB0aGlzIHNjcmlwdCBpZiB0aGVyZSBhcmUgbm8gc2lnbmlmaWNhbnQgcmVzdWx0cy4Kc3RvcGlmbm90KAogICAgKHB2YWxzICU+JSAKICAgIGZpbHRlcihwX2FkanVzdGVkX3NpZ25pZmljYW50KSAlPiUgCiAgICBucm93KCkpID49IDEKKQoKCnNpZ25pZmljYW50X2ZlYXR1cmVzID0gcHZhbHMgJT4lIAogICAgZmlsdGVyKHBfYWRqdXN0ZWRfc2lnbmlmaWNhbnQpIAoKc2lnbmlmaWNhbnRfZmVhdHVyZXMKCm9taWNfbGF5ZXIgJT4lIAogICAgc2VsZWN0KHNhbXBsZSwgc2lnbmlmaWNhbnRfZmVhdHVyZXMgJT4lIHB1bGwoZmVhdHVyZSkpICU+JSAKICAgIGxlZnRfam9pbih0cmFpdCwgYnkgPSAic2FtcGxlIikgJT4lIAogICAgCiAgICBwaXZvdF9sb25nZXIoLWMoc2FtcGxlLCBtZXRoYW5lKSkgJT4lIAogICAgCiAgICBnZ3Bsb3QoYWVzKHZhbHVlLCBtZXRoYW5lKSkgKyAKICAgIGZhY2V0X3dyYXAofm5hbWUpICsgCiAgICBnZW9tX3BvaW50KCkgKyAKICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsKICAgIGxhYnMoCiAgICAgICAgY2FwdGlvbiA9IHBhc3RlKCJUaGVzZSBzcHVyaW91cyByZXN1bHRzIGV4aXN0IGV2ZW4gdGhvdWdoIHdlIGRpZCBhZGp1c3RtZW50IGZvciBtdWx0aXBsZSB0ZXN0aW5nLlxuIiwgbiwgInNhbXBsZXMgKGNvd3MpLCAgYW5kIiwgcCwgImJpb2xvZ2ljYWwgZmVhdHVyZXMgaW4gdGhlIGh5cG90aGV0aWNhbCBvbWljIGxheWVyLiIpCiAgICApCgpgYGAKCgotLS0KCgpKdXN0IHRvIGNvbGxlY3QgaHRtbCBkb2N1bWVudHMuIEJlIGF3YXJlIHRoYXQgdGhpcyBjaHVuayBoYXMgYSBjaXJjdWxhciBkZXBlbmRlbmN5LCBiZWNhdXNlIGl0IHJlcXVpcmVzIHRoYXQgdGhpcyBkb2N1bWVudCBpcyBzYXZlZCBhbmQga25pdHRlZCBhcyBib3RoIGh0bWwgYW5kIHBkZi4KYGBge2Jhc2h9CnppcCBwb3J0YWJsZV9kb2N1bWVudHMuemlwICouaHRtbCAqLnBkZiAqLlJtZApgYGAKCgo=