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=