Objectives

  • Understand advantages of using gene ids when analyzing data.
  • Given a list of ENSEMBL gene ids, add gene symbols and Entrez accessions.
  • Generate common visualizations for differential expression comparisons
  • Understand choosing thresholds for calling differentially expressed genes
  • Discuss options for functional enrichments

Differential Expression Workflow

Here we will generate summary figures for our results and annotate our DE tables.


Summarizing DE results

Part of differential expression analysis is generating visualizations and summaries to share our results. While the DESeq2 tutorial provides examples of other visualizations, a common visualization to summarize DE comparisons are volcano plots.

Tabular DE summary

To summarize DE genes, we first need thresholds for determining significance. A reasonable threshold for linear fold-change is less than -1.5 or greater than 1.5 (which corresponds to log2 fold-change -0.585 and 0.585, respectively. A standard threshold for the adjusted p-value is less than 0.05.

Let’s set these as variables to reuse. This is generally good practice because if these thresholds change upon later consideration, you only have to change them in one location of your script, which will reduce errors from missing some instances in your code.

fc = 1.5
fdr = 0.05

Note: Choosing thresholds

Thresholding on adjusted p-values < 0.05 is a standard threshold, but depending on the research question and/or how the results will be used, other thresholds might be reasonable.

There is a nice Biostar post that discusses choosing adjusted p-value thresholds, including cases where a more relaxed threshold might be appropriate and (some heated) discussion of the dangers of adjusting the choosen threshold after running an analysis. Additionally, there is a Dalmon et al 2012 paper about p-value and fold-change thresholds for microarray data that may help provide some context.

To generate a general summary of the DE results, we can use the summary function to generate a basic summary by DESeq2.

summary(results_minus_vs_plus)

out of 16249 with nonzero total read count
adjusted p-value < 0.1
LFC > 0 (up)       : 53, 0.33%
LFC < 0 (down)     : 38, 0.23%
outliers [1]       : 72, 0.44%
low counts [2]     : 3781, 23%
(mean count < 7)
[1] see 'cooksCutoff' argument of ?results
[2] see 'independentFiltering' argument of ?results

However, this summary is simply a text output that we are unable to manipulate. Moreover, notice that the thresholds are not quite as we would like them.

We can use conditional statements to determine the number of genes that pass our thresholds for each comparison, which we can then use to add information to the results table and plots.

Exercise

How would we identify the number of genes with adjusted p-values < 0.05 and a fold-change above 1.5 (or below -1.5)?

Solution

Here is one possible answer:

sum(results_minus_vs_plus$padj < fdr & abs(results_minus_vs_plus$log2FoldChange) >= log2(fc), na.rm = TRUE)
[1] 56


Let’s now create a new column in results_minus_vs_plus to record the significance “call” based on these thresholds. And let’s separate the call by “Up” or “Down”, noting that these are relative to our “Case” condition of “plus”, or iron replete mice. There are many ways to accomplish this, but the following will work:

First define all values as “NS” or “not significant”:

results_minus_vs_plus$call = 'NS'
head(results_minus_vs_plus)
log2 fold change (MLE): condition minus vs plus 
Wald test p-value: condition minus vs plus 
DataFrame with 6 rows and 7 columns
                      baseMean log2FoldChange     lfcSE      stat    pvalue
                     <numeric>      <numeric> <numeric> <numeric> <numeric>
ENSMUSG00000000001  1489.83039       0.297760  0.210310  1.415815  0.156830
ENSMUSG00000000028  1748.93544       0.226421  0.176795  1.280695  0.200301
ENSMUSG00000000031  2151.87715       0.457635  0.764579  0.598545  0.549476
ENSMUSG00000000037    24.91672       0.579130  0.561259  1.031840  0.302147
ENSMUSG00000000049     7.78377      -0.899483  1.553063 -0.579167  0.562476
ENSMUSG00000000056 19653.54030      -0.174048  0.203529 -0.855151  0.392468
                        padj        call
                   <numeric> <character>
ENSMUSG00000000001  0.868573          NS
ENSMUSG00000000028  0.902900          NS
ENSMUSG00000000031  0.995391          NS
ENSMUSG00000000037  0.950613          NS
ENSMUSG00000000049  0.998043          NS
ENSMUSG00000000056  0.982479          NS

Next, determine the “Up” and “Down” indices:

up_idx = results_minus_vs_plus$padj < fdr & results_minus_vs_plus$log2FoldChange > log2(fc)
down_idx = results_minus_vs_plus$padj < fdr & results_minus_vs_plus$log2FoldChange < -log2(fc)

Last, use those indices to assign the correct “Up” or “Down” values to the correct indices, and look at the head of the result:

results_minus_vs_plus$call[up_idx] = 'Up'
results_minus_vs_plus$call[down_idx] = 'Down'
head(results_minus_vs_plus)
log2 fold change (MLE): condition minus vs plus 
Wald test p-value: condition minus vs plus 
DataFrame with 6 rows and 7 columns
                      baseMean log2FoldChange     lfcSE      stat    pvalue
                     <numeric>      <numeric> <numeric> <numeric> <numeric>
ENSMUSG00000000001  1489.83039       0.297760  0.210310  1.415815  0.156830
ENSMUSG00000000028  1748.93544       0.226421  0.176795  1.280695  0.200301
ENSMUSG00000000031  2151.87715       0.457635  0.764579  0.598545  0.549476
ENSMUSG00000000037    24.91672       0.579130  0.561259  1.031840  0.302147
ENSMUSG00000000049     7.78377      -0.899483  1.553063 -0.579167  0.562476
ENSMUSG00000000056 19653.54030      -0.174048  0.203529 -0.855151  0.392468
                        padj        call
                   <numeric> <character>
ENSMUSG00000000001  0.868573          NS
ENSMUSG00000000028  0.902900          NS
ENSMUSG00000000031  0.995391          NS
ENSMUSG00000000037  0.950613          NS
ENSMUSG00000000049  0.998043          NS
ENSMUSG00000000056  0.982479          NS

Finally, looking ahead to when we plot these values as colors in a volcano plot, let’s make this call column a factor and specify the level ordering:

results_minus_vs_plus$call = factor(results_minus_vs_plus$call, levels = c('Up', 'Down', 'NS'))
head(results_minus_vs_plus)
log2 fold change (MLE): condition minus vs plus 
Wald test p-value: condition minus vs plus 
DataFrame with 6 rows and 7 columns
                      baseMean log2FoldChange     lfcSE      stat    pvalue
                     <numeric>      <numeric> <numeric> <numeric> <numeric>
ENSMUSG00000000001  1489.83039       0.297760  0.210310  1.415815  0.156830
ENSMUSG00000000028  1748.93544       0.226421  0.176795  1.280695  0.200301
ENSMUSG00000000031  2151.87715       0.457635  0.764579  0.598545  0.549476
ENSMUSG00000000037    24.91672       0.579130  0.561259  1.031840  0.302147
ENSMUSG00000000049     7.78377      -0.899483  1.553063 -0.579167  0.562476
ENSMUSG00000000056 19653.54030      -0.174048  0.203529 -0.855151  0.392468
                        padj     call
                   <numeric> <factor>
ENSMUSG00000000001  0.868573       NS
ENSMUSG00000000028  0.902900       NS
ENSMUSG00000000031  0.995391       NS
ENSMUSG00000000037  0.950613       NS
ENSMUSG00000000049  0.998043       NS
ENSMUSG00000000056  0.982479       NS

Tip

It is often helpful to include code like this in differential expression analyses so there is a clearly labelled column that makes subsetting and summarizing the results easier.

Now we are in a position to quickly summarize our differential expression results:

table(results_minus_vs_plus$call)

   Up  Down    NS 
   41    15 16193 

We see quickly how many genes were “Up” in iron replete, how many were “Down” in iron replete, and how many were not significant.

Checkpoint: If you successfully added the call column and got the same table result as above, please indicate with a green check. Otherwise use a red x.

Visual DE summary

As described by this Galaxy project tutorial, a volcano plot is a type of scatterplot that shows statistical significance (adjusted p-value) versus magnitude of change (fold change). In a volcano plot, the most upregulated genes are towards the right, the most downregulated genes are towards the left, and the most statistically significant genes are towards the top.

Let’s coerce the DataFrame which was returned by DESeq2::results() into a tibble in anticipation of using the ggplot2 library to plot. We’re also going to modify our results table so that the row names become a separate column, and so that it’s ordered by adjusted p-value.

# Use the rownames argument to create a new column of gene IDs
# Also arrange by adjusted p-value
results_minus_vs_plus = as_tibble(results_minus_vs_plus, rownames = 'id') %>% arrange(padj)

Let’s start with a very simple volcano plot that plots the log2FoldChange on the x-axis, and -log10(padj) on the y-axis.

# Initialize the plot, saving as object 'p' and specifying the plot type as 'geom_point'
p = ggplot(results_minus_vs_plus, aes(x = log2FoldChange, y = -log10(padj))) +
    geom_point()
p
Warning: Removed 3853 rows containing missing values (`geom_point()`).

This is a good start, but, as usual it’s nice to add better labels to the plot with the labs() function:

# Add plot labels and change the theme
p = ggplot(results_minus_vs_plus, aes(x = log2FoldChange, y = -log10(padj))) +
    geom_point() +
    theme_bw() +
    labs(
        title = 'Volcano Plot',
        subtitle = 'Plus vs Minus',
        x = 'log2 fold-change',
        y = '-log10 FDR'
    )
p
Warning: Removed 3853 rows containing missing values (`geom_point()`).

This is a nice improvement. What if we now added some visual guides to indicate where the significant genes are? We can uset he geom_vline() and geom_hline() functions to accomplish this:

# Add threshold lines
p = p +
    geom_vline(
        xintercept = c(0, -log2(fc), log2(fc)),
        linetype = c(1, 2, 2)) +
    geom_hline(
        yintercept = -log10(fdr),
        linetype = 2)
p
Warning: Removed 3853 rows containing missing values (`geom_point()`).

Finally, why not color the points by their significance status? We already created the call column that has the correct values. In this case we can get away with adding a last geom_point() and specifying the correct aesthetic:

p = p + geom_point(aes(color = call))
p
Warning: Removed 3853 rows containing missing values (`geom_point()`).
Removed 3853 rows containing missing values (`geom_point()`).

For additional visualizations for our DE results, we included some example code in the Bonus Content module and this HBC tutorial includes some nice examples.

Subsetting significant genes

You may be interested in identifying only the genes that pass your significance thresholds. A useful way to do this is to conditionally subset your results. Again, we already created the call column, which makes this relatively simple to do.

Note: The tidyverse functions you learned in Software Carpentry could also be alternatively used here.

res_sig = results_minus_vs_plus[results_minus_vs_plus$call != 'NS', ]
head(res_sig)
# A tibble: 6 × 8
  id                 baseMean log2FoldChange lfcSE  stat   pvalue     padj call 
  <chr>                 <dbl>          <dbl> <dbl> <dbl>    <dbl>    <dbl> <fct>
1 ENSMUSG00000032715   1178.           3.64  0.450  8.10 5.71e-16 7.08e-12 Up   
2 ENSMUSG00000101645     77.3        -10.1   1.33  -7.58 3.42e-14 2.12e-10 Down 
3 ENSMUSG00000027313    243.           2.67  0.410  6.50 7.96e-11 2.47e- 7 Up   
4 ENSMUSG00000027889   3250.           0.946 0.145  6.52 7.00e-11 2.47e- 7 Up   
5 ENSMUSG00000054136     92.6          3.39  0.526  6.44 1.19e-10 2.95e- 7 Up   
6 ENSMUSG00000020142    625.           1.72  0.287  5.99 2.07e- 9 4.28e- 6 Up   
dim(res_sig)
[1] 56  8

Adding genome annotations

Since, gene symbols can change over time or be ambiguous we use, and recommend, using the EMSEMBL reference genome and ENSEMBL IDs for alignments and we’ve been working with tables and data where all genes are labeled only by their long ENSEMBL ID (you will notice this in the GTF we used in the reference). However, this can make it difficult to quickly look at results for genes of interest.

Luckily, Bioconductor provides many tools and resources to facilitate access to genomic annotation resources.

To start, we will first load the biomaRt library and choose what reference we want to access. For a more detailed walk through of using biomaRt, this training module might be useful, including what to do when annotations are not 1:1 mappings.

We’ll start by loading the biomaRt library and calling the useEnsembl() function to select the database we’ll use to extract the information we need. This will download the mapping of ENSEMBL IDs to gene symbols, enabling us to eventually add the gene symbol column we want.

library('biomaRt')
ensembl = useEnsembl(dataset = 'mmusculus_gene_ensembl', biomart='ensembl')

Note that this process takes some time and will take up a larger amount of working memory so proceed with caution if you try to run these commands on a laptop with less than 4G of memory

To identify possible filters to restrict our data, we can use the listFilters function. To identify the attributes we want to retrive, we can use the listAttributes function. The best approach is to use list or search functions to help narrow down the available options.

head(listFilters(mart = ensembl), n = 20)
head(listAttributes(ensembl), n = 30)

We can access additional genomic annotations using the bioMart package. To identify we’ll structure our ‘query’ or search of the bioMart resources to use the ENSEMBL id from our alignment to add the gene symbols and gene description for each gene.

id_mapping = getBM(attributes=c('ensembl_gene_id', 'external_gene_name'),
      filters = 'ensembl_gene_id',
      values = row.names(assay(dds)),
      mart = ensembl)
# will take some time to run

# Preview the result
head(id_mapping)
     ensembl_gene_id external_gene_name
1 ENSMUSG00000000001              Gnai3
2 ENSMUSG00000000028              Cdc45
3 ENSMUSG00000000031                H19
4 ENSMUSG00000000037              Scml2
5 ENSMUSG00000000049               Apoh
6 ENSMUSG00000000056               Narf

Now that we have the ENSEMBL information and a gene symbol to match to our results, we can proceed in the smaller groups. As with the previous exercise, we have broken it into small steps with hints as needed.

Note: For additional information regarding bioMart, please consult the ENSEMBL bioMart vignette or the broader Bioconductor Annotation Resources vignette.

Look at the two data frames that are going to be needed: id_mapping and results_minus_vs_plus.

head(id_mapping)
     ensembl_gene_id external_gene_name
1 ENSMUSG00000000001              Gnai3
2 ENSMUSG00000000028              Cdc45
3 ENSMUSG00000000031                H19
4 ENSMUSG00000000037              Scml2
5 ENSMUSG00000000049               Apoh
6 ENSMUSG00000000056               Narf
head(results_minus_vs_plus)
# A tibble: 6 × 8
  id                 baseMean log2FoldChange lfcSE  stat   pvalue     padj call 
  <chr>                 <dbl>          <dbl> <dbl> <dbl>    <dbl>    <dbl> <fct>
1 ENSMUSG00000032715   1178.           3.64  0.450  8.10 5.71e-16 7.08e-12 Up   
2 ENSMUSG00000101645     77.3        -10.1   1.33  -7.58 3.42e-14 2.12e-10 Down 
3 ENSMUSG00000027313    243.           2.67  0.410  6.50 7.96e-11 2.47e- 7 Up   
4 ENSMUSG00000027889   3250.           0.946 0.145  6.52 7.00e-11 2.47e- 7 Up   
5 ENSMUSG00000054136     92.6          3.39  0.526  6.44 1.19e-10 2.95e- 7 Up   
6 ENSMUSG00000020142    625.           1.72  0.287  5.99 2.07e- 9 4.28e- 6 Up   

We want to match the id column of results_minus_vs_plus to the ensembl_gene_id column of id_mapping, and once that match is found, we want to extract the external_gene_name column of id_mapping to get the gene symbol. Next, look at the documentation for dplyr::left_join() and merge the id_mapping table into the results_minus_vs_plus table on the columns ensembl_gene_id and external_gene_name.

results_minus_vs_plus_annotated = results_minus_vs_plus %>%
    left_join(id_mapping, by = c('id' = 'ensembl_gene_id'))
head(results_minus_vs_plus_annotated)
# A tibble: 6 × 9
  id                 baseM…¹ log2F…² lfcSE  stat   pvalue     padj call  exter…³
  <chr>                <dbl>   <dbl> <dbl> <dbl>    <dbl>    <dbl> <fct> <chr>  
1 ENSMUSG00000032715  1178.    3.64  0.450  8.10 5.71e-16 7.08e-12 Up    Trib3  
2 ENSMUSG00000101645    77.3 -10.1   1.33  -7.58 3.42e-14 2.12e-10 Down  Gm28635
3 ENSMUSG00000027313   243.    2.67  0.410  6.50 7.96e-11 2.47e- 7 Up    Chac1  
4 ENSMUSG00000027889  3250.    0.946 0.145  6.52 7.00e-11 2.47e- 7 Up    Ampd2  
5 ENSMUSG00000054136    92.6   3.39  0.526  6.44 1.19e-10 2.95e- 7 Up    Adm2   
6 ENSMUSG00000020142   625.    1.72  0.287  5.99 2.07e- 9 4.28e- 6 Up    Slc1a4 
# … with abbreviated variable names ¹​baseMean, ²​log2FoldChange,
#   ³​external_gene_name

We can use some of the tidyverse functions we’ve encountered previously to rename the external_gene_name column to symbol and to move it into the second column position? Hint: Because of the order of the packages we may have loaded, we’ll use dplyr::rename() and dplyr::select() instead of just the select() function. We can discuss this in a moment.

results_minus_vs_plus_annotated = results_minus_vs_plus_annotated %>%
    dplyr::rename('symbol' = 'external_gene_name') %>%
    dplyr::select(id, symbol, everything())
results_minus_vs_plus_annotated
# A tibble: 16,249 × 9
   id                 symbol baseM…¹ log2F…² lfcSE  stat   pvalue     padj call 
   <chr>              <chr>    <dbl>   <dbl> <dbl> <dbl>    <dbl>    <dbl> <fct>
 1 ENSMUSG00000032715 Trib3   1178.    3.64  0.450  8.10 5.71e-16 7.08e-12 Up   
 2 ENSMUSG00000101645 Gm286…    77.3 -10.1   1.33  -7.58 3.42e-14 2.12e-10 Down 
 3 ENSMUSG00000027313 Chac1    243.    2.67  0.410  6.50 7.96e-11 2.47e- 7 Up   
 4 ENSMUSG00000027889 Ampd2   3250.    0.946 0.145  6.52 7.00e-11 2.47e- 7 Up   
 5 ENSMUSG00000054136 Adm2      92.6   3.39  0.526  6.44 1.19e-10 2.95e- 7 Up   
 6 ENSMUSG00000020142 Slc1a4   625.    1.72  0.287  5.99 2.07e- 9 4.28e- 6 Up   
 7 ENSMUSG00000040280 Ndufa…    85.9   3.62  0.628  5.75 8.68e- 9 1.54e- 5 Up   
 8 ENSMUSG00000023951 Vegfa    350.    2.44  0.432  5.64 1.67e- 8 2.59e- 5 Up   
 9 ENSMUSG00000115420 Rmrp      16.0 -21.9   3.91  -5.60 2.11e- 8 2.90e- 5 Down 
10 ENSMUSG00000027737 Slc7a…   119.    2.32  0.429  5.41 6.47e- 8 8.02e- 5 Up   
# … with 16,239 more rows, and abbreviated variable names ¹​baseMean,
#   ²​log2FoldChange

And now we have our differential expression results annotated with gene symbols, which can help in the interpretation of the results, and can be used in downstream analysis such as functional analysis.

Outputting results to file

A key aspect of our analysis is preserving the relevant datasets for both our records and for downstream applications, such as functional enrichments.

DE results table

Next we’ll write out our DE results, now that we’ve added information to the table to help us interpret the results, and share with collaborators.

write.csv(results_minus_vs_plus,
          row.names = FALSE,
          na = ".",
          file="outputs/tables/DE_results_minus_vs_plus.csv")

Count tables

The most relevant count tables are the raw, filtered count table that we used as the input for our analysis and the rlog normalized count table that we used for our quality control visualizations.

To output the raw counts, we will need to use the counts function to access the count table from within its larger DESeqDataSet object.

write.csv(counts(dds, normalized = FALSE), file="outputs/tables/DESeq2_raw_counts.csv")

Then we’ll output the rlog count table, using the assay function to access the normalized count table from within its larger DESeqDataSet object.

write.csv(assay(rld), file="outputs/tables/DESeq2_rlog_normalized_counts.csv")

Summary

In this section, we:

  • Generated a volcano plot for our differential expression results
  • Summarized our differential expression results
  • Discussed choosing thresholds
  • Annotated our tables of results to map gene IDs to gene symbols
  • Saved our results to file

Key takeaways

Overall, we’ve run through most of the building blocks needed to run a differential expression analysis and hopefully built up a better understanding of how differential expression comparisons work, particularly how experimental design can impact our results.

What to consider moving forward:

  • How can I control for technical variation in my experimental design?
  • How much variation is expected with a treatment group?
  • What is my RNA quality, and how can that be optimized?
  • Are there quality concerns for my sequencing data?
  • What comparisons are relevant to my biological question?
  • Are there covariates that should be considered?
  • What will a differential expression analysis tell me?

Let’s pause here for general questions


What can we do with our DE results?

A way to determine possible broader biological interpretations from the observed DE results, is functional enrichments. There are many options, such as some included in this discussion thread. Other common functional enrichments approaches are gene set enrichment analysis, aka GSEA, Database for Annotation, Visualization and Integrated Discovery, aka DAVID, Ingenity, and [iPathway Guide]

The University of Michigan has license and support for additional tools, such as Cytoscape, so we recommend reaching out to staff with Taubman Library to learn more about resources that might be application toyour research.


Session Info

sessionInfo()
R version 4.2.1 (2022-06-23)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Big Sur ... 10.16

Matrix products: default
BLAS:   /Library/Frameworks/R.framework/Versions/4.2/Resources/lib/libRblas.0.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.2/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats4    stats     graphics  grDevices utils     datasets  methods  
[8] base     

other attached packages:
 [1] forcats_0.5.2               stringr_1.4.1              
 [3] purrr_0.3.5                 readr_2.1.3                
 [5] tibble_3.1.8                tidyverse_1.3.2            
 [7] biomaRt_2.52.0              data.table_1.14.4          
 [9] RColorBrewer_1.1-3          pheatmap_1.0.12            
[11] ggrepel_0.9.2               dplyr_1.0.10               
[13] tidyr_1.2.1                 ggplot2_3.4.0              
[15] DESeq2_1.36.0               SummarizedExperiment_1.26.1
[17] Biobase_2.56.0              MatrixGenerics_1.8.1       
[19] matrixStats_0.62.0          GenomicRanges_1.48.0       
[21] GenomeInfoDb_1.32.4         IRanges_2.30.1             
[23] S4Vectors_0.34.0            BiocGenerics_0.42.0        
[25] knitr_1.40                  rmarkdown_2.17             

loaded via a namespace (and not attached):
 [1] googledrive_2.0.0      colorspace_2.0-3       ellipsis_0.3.2        
 [4] XVector_0.36.0         fs_1.5.2               farver_2.1.1          
 [7] bit64_4.0.5            AnnotationDbi_1.58.0   fansi_1.0.3           
[10] lubridate_1.9.0        xml2_1.3.3             codetools_0.2-18      
[13] splines_4.2.1          cachem_1.0.6           geneplotter_1.74.0    
[16] jsonlite_1.8.3         broom_1.0.1            annotate_1.74.0       
[19] dbplyr_2.2.1           png_0.1-7              compiler_4.2.1        
[22] httr_1.4.4             backports_1.4.1        assertthat_0.2.1      
[25] Matrix_1.5-1           fastmap_1.1.0          gargle_1.2.1          
[28] cli_3.4.1              htmltools_0.5.3        prettyunits_1.1.1     
[31] tools_4.2.1            gtable_0.3.1           glue_1.6.2            
[34] GenomeInfoDbData_1.2.8 rappdirs_0.3.3         Rcpp_1.0.9            
[37] cellranger_1.1.0       jquerylib_0.1.4        vctrs_0.5.0           
[40] Biostrings_2.64.1      xfun_0.34              rvest_1.0.3           
[43] timechange_0.1.1       lifecycle_1.0.3        XML_3.99-0.12         
[46] googlesheets4_1.0.1    zlibbioc_1.42.0        scales_1.2.1          
[49] hms_1.1.2              parallel_4.2.1         yaml_2.3.6            
[52] curl_4.3.3             memoise_2.0.1          sass_0.4.2            
[55] stringi_1.7.8          RSQLite_2.2.18         highr_0.9             
[58] genefilter_1.78.0      filelock_1.0.2         BiocParallel_1.30.4   
[61] rlang_1.0.6            pkgconfig_2.0.3        bitops_1.0-7          
[64] evaluate_0.18          lattice_0.20-45        labeling_0.4.2        
[67] bit_4.0.4              tidyselect_1.2.0       magrittr_2.0.3        
[70] R6_2.5.1               generics_0.1.3         DelayedArray_0.22.0   
[73] DBI_1.1.3              pillar_1.8.1           haven_2.5.1           
[76] withr_2.5.0            survival_3.4-0         KEGGREST_1.36.3       
[79] RCurl_1.98-1.9         modelr_0.1.9           crayon_1.5.2          
[82] utf8_1.2.2             BiocFileCache_2.4.0    tzdb_0.3.0            
[85] progress_1.2.2         readxl_1.4.1           locfit_1.5-9.6        
[88] grid_4.2.1             blob_1.2.3             reprex_2.0.2          
[91] digest_0.6.30          xtable_1.8-4           munsell_0.5.0         
[94] bslib_0.4.1           

These materials have been adapted and extended from materials listed above. These are open access materials distributed under the terms of the Creative Commons Attribution license (CC BY 4.0), which permits unrestricted use, distribution, and reproduction in any medium, provided the original author and source are credited.

LS0tCnRpdGxlOiAiTW9kdWxlIDExOiBERSBWaXN1YWxpemF0aW9uIGFuZCBBbm5vdGF0aW9uIgphdXRob3I6ICJVTSBCaW9pbmZvcm1hdGljcyBDb3JlIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICAgICAgICBodG1sX2RvY3VtZW50OgogICAgICAgICAgICBpbmNsdWRlczoKICAgICAgICAgICAgICAgIGluX2hlYWRlcjogaGVhZGVyLmh0bWwKICAgICAgICAgICAgdGhlbWU6IHBhcGVyCiAgICAgICAgICAgIHRvYzogdHJ1ZQogICAgICAgICAgICB0b2NfZGVwdGg6IDQKICAgICAgICAgICAgdG9jX2Zsb2F0OiB0cnVlCiAgICAgICAgICAgIG51bWJlcl9zZWN0aW9uczogZmFsc2UKICAgICAgICAgICAgZmlnX2NhcHRpb246IHRydWUKICAgICAgICAgICAgbWFya2Rvd246IEdGTQogICAgICAgICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KYm9keSwgdGQgewogICBmb250LXNpemU6IDE4cHg7Cn0KY29kZS5yewogIGZvbnQtc2l6ZTogMTJweDsKfQpwcmUgewogIGZvbnQtc2l6ZTogMTJweAp9Cjwvc3R5bGU+CgpgYGB7ciwgaW5jbHVkZSA9IEZBTFNFfQpzb3VyY2UoIi4uL2Jpbi9jaHVuay1vcHRpb25zLlIiKQprbml0cl9maWdfcGF0aCgiMTEtIikKYGBgCgo+ICMgT2JqZWN0aXZlcyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQo+ICogVW5kZXJzdGFuZCBhZHZhbnRhZ2VzIG9mIHVzaW5nIGdlbmUgaWRzIHdoZW4gYW5hbHl6aW5nIGRhdGEuCj4gKiBHaXZlbiBhIGxpc3Qgb2YgRU5TRU1CTCBnZW5lIGlkcywgYWRkIGdlbmUgc3ltYm9scyBhbmQgRW50cmV6IGFjY2Vzc2lvbnMuCj4gKiBHZW5lcmF0ZSBjb21tb24gdmlzdWFsaXphdGlvbnMgZm9yIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGNvbXBhcmlzb25zCj4gKiBVbmRlcnN0YW5kIGNob29zaW5nIHRocmVzaG9sZHMgZm9yIGNhbGxpbmcgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzCj4gKiBEaXNjdXNzIG9wdGlvbnMgZm9yIGZ1bmN0aW9uYWwgZW5yaWNobWVudHMKCmBgYHtyIE1vZHVsZXMsIGV2YWw9VFJVRSwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShERVNlcTIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeSh0aWR5cikKbGlicmFyeShkcGx5cikKbGlicmFyeShtYXRyaXhTdGF0cykKbGlicmFyeShnZ3JlcGVsKQpsaWJyYXJ5KHBoZWF0bWFwKQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKIyBsb2FkKCJyZGF0YS9SdW5uaW5nRGF0YS5SRGF0YSIpCmBgYAoKIyBEaWZmZXJlbnRpYWwgRXhwcmVzc2lvbiBXb3JrZmxvdyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQoKSGVyZSB3ZSB3aWxsIGdlbmVyYXRlIHN1bW1hcnkgZmlndXJlcyBmb3Igb3VyIHJlc3VsdHMgYW5kIGFubm90YXRlIG91ciBERSB0YWJsZXMuCgohW10oLi9pbWFnZXMvd2F5ZmluZGVyL3dheWZpbmRlci1ERVZpc3VhbGl6YXRpb25zLnBuZyl7d2lkdGg9NzUlfQoKLS0tCgojIFN1bW1hcml6aW5nIERFIHJlc3VsdHMKClBhcnQgb2YgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgaXMgZ2VuZXJhdGluZyB2aXN1YWxpemF0aW9ucyBhbmQgc3VtbWFyaWVzIHRvIHNoYXJlIG91ciByZXN1bHRzLiBXaGlsZSB0aGUgREVTZXEyIHR1dG9yaWFsIHByb3ZpZGVzIFtleGFtcGxlcyBvZiBvdGhlciB2aXN1YWxpemF0aW9uc10oaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvZGV2ZWwvYmlvYy92aWduZXR0ZXMvREVTZXEyL2luc3QvZG9jL0RFU2VxMi5odG1sI2V4cGxvcmluZy1hbmQtZXhwb3J0aW5nLXJlc3VsdHMpLCBhIGNvbW1vbiB2aXN1YWxpemF0aW9uIHRvIHN1bW1hcml6ZSBERSBjb21wYXJpc29ucyBhcmUgW3ZvbGNhbm8gcGxvdHNdKGh0dHA6Ly9yZXNvdXJjZXMucWlhZ2VuYmlvaW5mb3JtYXRpY3MuY29tL21hbnVhbHMvY2xjZ2Vub21pY3N3b3JrYmVuY2gvNzUyL2luZGV4LnBocD9tYW51YWw9Vm9sY2Fub19wbG90c19pbnNwZWN0aW5nX3Jlc3VsdF9zdGF0aXN0aWNhbF9hbmFseXNpcy5odG1sKS4KCiMjIFRhYnVsYXIgREUgc3VtbWFyeQoKVG8gc3VtbWFyaXplIERFIGdlbmVzLCB3ZSBmaXJzdCBuZWVkIHRocmVzaG9sZHMgZm9yIGRldGVybWluaW5nIHNpZ25pZmljYW5jZS4gQSByZWFzb25hYmxlIHRocmVzaG9sZCBmb3IgbGluZWFyIGZvbGQtY2hhbmdlIGlzIGxlc3MgdGhhbiAtMS41IG9yIGdyZWF0ZXIgdGhhbiAxLjUgKHdoaWNoIGNvcnJlc3BvbmRzIHRvIGxvZzIgZm9sZC1jaGFuZ2UgLTAuNTg1IGFuZCAwLjU4NSwgcmVzcGVjdGl2ZWx5LiBBIHN0YW5kYXJkIHRocmVzaG9sZCBmb3IgdGhlIGFkanVzdGVkIHAtdmFsdWUgaXMgbGVzcyB0aGFuIDAuMDUuCgpMZXQncyBzZXQgdGhlc2UgYXMgdmFyaWFibGVzIHRvIHJldXNlLiBUaGlzIGlzIGdlbmVyYWxseSBnb29kIHByYWN0aWNlIGJlY2F1c2UgaWYgdGhlc2UgdGhyZXNob2xkcyBjaGFuZ2UgdXBvbiBsYXRlciBjb25zaWRlcmF0aW9uLCB5b3Ugb25seSBoYXZlIHRvIGNoYW5nZSB0aGVtIGluIG9uZSBsb2NhdGlvbiBvZiB5b3VyIHNjcmlwdCwgd2hpY2ggd2lsbCByZWR1Y2UgZXJyb3JzIGZyb20gbWlzc2luZyBzb21lIGluc3RhbmNlcyBpbiB5b3VyIGNvZGUuCgpgYGB7ciBQbG90U2V0dXAxfQpmYyA9IDEuNQpmZHIgPSAwLjA1CmBgYAoKPiAjIE5vdGU6IENob29zaW5nIHRocmVzaG9sZHMgey51bmxpc3RlZCAudW5udW1iZXJlZH0KPgo+IFRocmVzaG9sZGluZyBvbiBhZGp1c3RlZCBwLXZhbHVlcyA8IDAuMDUgaXMgYSBzdGFuZGFyZCB0aHJlc2hvbGQsIGJ1dCBkZXBlbmRpbmcgb24gdGhlIHJlc2VhcmNoIHF1ZXN0aW9uIGFuZC9vciBob3cgdGhlIHJlc3VsdHMgd2lsbCBiZSB1c2VkLCBvdGhlciB0aHJlc2hvbGRzIG1pZ2h0IGJlIHJlYXNvbmFibGUuCj4KPiBUaGVyZSBpcyBhIG5pY2UgW0Jpb3N0YXIgcG9zdCB0aGF0IGRpc2N1c3NlcyBjaG9vc2luZyBhZGp1c3RlZCBwLXZhbHVlIHRocmVzaG9sZHNdKGh0dHBzOi8vd3d3LmJpb3N0YXJzLm9yZy9wLzIwOTExOC8pLCBpbmNsdWRpbmcgY2FzZXMgd2hlcmUgYSBtb3JlIHJlbGF4ZWQgdGhyZXNob2xkIG1pZ2h0IGJlIGFwcHJvcHJpYXRlIGFuZCAoc29tZSBoZWF0ZWQpIGRpc2N1c3Npb24gb2YgdGhlIGRhbmdlcnMgb2YgYWRqdXN0aW5nIHRoZSBjaG9vc2VuIHRocmVzaG9sZCBhZnRlciBydW5uaW5nIGFuIGFuYWx5c2lzLiBBZGRpdGlvbmFsbHksIHRoZXJlIGlzIGEgW0RhbG1vbiBldCBhbCAyMDEyXShodHRwczovL2JtY2Jpb2luZm9ybWF0aWNzLmJpb21lZGNlbnRyYWwuY29tL2FydGljbGVzLzEwLjExODYvMTQ3MS0yMTA1LTEzLVMyLVMxMSkgcGFwZXIgYWJvdXQgcC12YWx1ZSBhbmQgZm9sZC1jaGFuZ2UgdGhyZXNob2xkcyBmb3IgbWljcm9hcnJheSBkYXRhIHRoYXQgbWF5IGhlbHAgcHJvdmlkZSBzb21lIGNvbnRleHQuCgpUbyBnZW5lcmF0ZSBhIGdlbmVyYWwgc3VtbWFyeSBvZiB0aGUgREUgcmVzdWx0cywgd2UgY2FuIHVzZSB0aGUgYHN1bW1hcnlgIGZ1bmN0aW9uIHRvIGdlbmVyYXRlIGEgYmFzaWMgc3VtbWFyeSBieSBERVNlcTIuCgpgYGB7ciBERVNlcTJTdW1tYXJ5fQpzdW1tYXJ5KHJlc3VsdHNfbWludXNfdnNfcGx1cykKYGBgCgpIb3dldmVyLCB0aGlzIHN1bW1hcnkgaXMgc2ltcGx5IGEgdGV4dCBvdXRwdXQgdGhhdCB3ZSBhcmUgdW5hYmxlIHRvIG1hbmlwdWxhdGUuIE1vcmVvdmVyLCBub3RpY2UgdGhhdCB0aGUgdGhyZXNob2xkcyBhcmUgbm90IHF1aXRlIGFzIHdlIHdvdWxkIGxpa2UgdGhlbS4KCldlIGNhbiB1c2UgY29uZGl0aW9uYWwgc3RhdGVtZW50cyB0byBkZXRlcm1pbmUgdGhlIG51bWJlciBvZiBnZW5lcyB0aGF0IHBhc3Mgb3VyIHRocmVzaG9sZHMgZm9yIGVhY2ggY29tcGFyaXNvbiwgd2hpY2ggd2UgY2FuIHRoZW4gdXNlIHRvIGFkZCBpbmZvcm1hdGlvbiB0byB0aGUgcmVzdWx0cyB0YWJsZSBhbmQgcGxvdHMuCgo+ICMgRXhlcmNpc2Ugey51bmxpc3RlZCAudW5udW1iZXJlZH0KPiBIb3cgd291bGQgd2UgaWRlbnRpZnkgdGhlIG51bWJlciBvZiBnZW5lcyB3aXRoIGFkanVzdGVkIHAtdmFsdWVzIDwgMC4wNSBhbmQgYSBmb2xkLWNoYW5nZSBhYm92ZSAxLjUgKG9yIGJlbG93IC0xLjUpPwoKPGRldGFpbHM+CjxzdW1tYXJ5PlNvbHV0aW9uPC9zdW1tYXJ5PgoKSGVyZSBpcyBvbmUgcG9zc2libGUgYW5zd2VyOgoKYGBge3IgU3RhdFNpZ0dlbmVzMn0Kc3VtKHJlc3VsdHNfbWludXNfdnNfcGx1cyRwYWRqIDwgZmRyICYgYWJzKHJlc3VsdHNfbWludXNfdnNfcGx1cyRsb2cyRm9sZENoYW5nZSkgPj0gbG9nMihmYyksIG5hLnJtID0gVFJVRSkKYGBgCjwvZGV0YWlscz4KPGJyPgoKTGV0J3Mgbm93IGNyZWF0ZSBhIG5ldyBjb2x1bW4gaW4gYHJlc3VsdHNfbWludXNfdnNfcGx1c2AgdG8gcmVjb3JkIHRoZSBzaWduaWZpY2FuY2UgImNhbGwiIGJhc2VkIG9uIHRoZXNlIHRocmVzaG9sZHMuIEFuZCBsZXQncyBzZXBhcmF0ZSB0aGUgY2FsbCBieSAiVXAiIG9yICJEb3duIiwgbm90aW5nIHRoYXQgdGhlc2UgYXJlIHJlbGF0aXZlIHRvIG91ciAiQ2FzZSIgY29uZGl0aW9uIG9mICJwbHVzIiwgb3IgaXJvbiByZXBsZXRlIG1pY2UuIFRoZXJlIGFyZSBtYW55IHdheXMgdG8gYWNjb21wbGlzaCB0aGlzLCBidXQgdGhlIGZvbGxvd2luZyB3aWxsIHdvcms6CgpGaXJzdCBkZWZpbmUgYWxsIHZhbHVlcyBhcyAiTlMiIG9yICJub3Qgc2lnbmlmaWNhbnQiOgoKYGBge3IgTlNDb2x1bW59CnJlc3VsdHNfbWludXNfdnNfcGx1cyRjYWxsID0gJ05TJwpoZWFkKHJlc3VsdHNfbWludXNfdnNfcGx1cykKYGBgCgpOZXh0LCBkZXRlcm1pbmUgdGhlICJVcCIgYW5kICJEb3duIiBpbmRpY2VzOgoKYGBge3IgVXBEb3duSW5kaWNlc30KdXBfaWR4ID0gcmVzdWx0c19taW51c192c19wbHVzJHBhZGogPCBmZHIgJiByZXN1bHRzX21pbnVzX3ZzX3BsdXMkbG9nMkZvbGRDaGFuZ2UgPiBsb2cyKGZjKQpkb3duX2lkeCA9IHJlc3VsdHNfbWludXNfdnNfcGx1cyRwYWRqIDwgZmRyICYgcmVzdWx0c19taW51c192c19wbHVzJGxvZzJGb2xkQ2hhbmdlIDwgLWxvZzIoZmMpCmBgYAoKTGFzdCwgdXNlIHRob3NlIGluZGljZXMgdG8gYXNzaWduIHRoZSBjb3JyZWN0ICJVcCIgb3IgIkRvd24iIHZhbHVlcyB0byB0aGUgY29ycmVjdCBpbmRpY2VzLCBhbmQgbG9vayBhdCB0aGUgaGVhZCBvZiB0aGUgcmVzdWx0OgoKYGBge3IgQ2FsbENvbHVtbn0KcmVzdWx0c19taW51c192c19wbHVzJGNhbGxbdXBfaWR4XSA9ICdVcCcKcmVzdWx0c19taW51c192c19wbHVzJGNhbGxbZG93bl9pZHhdID0gJ0Rvd24nCmhlYWQocmVzdWx0c19taW51c192c19wbHVzKQpgYGAKCkZpbmFsbHksIGxvb2tpbmcgYWhlYWQgdG8gd2hlbiB3ZSBwbG90IHRoZXNlIHZhbHVlcyBhcyBjb2xvcnMgaW4gYSB2b2xjYW5vIHBsb3QsIGxldCdzIG1ha2UgdGhpcyBgY2FsbGAgY29sdW1uIGEgZmFjdG9yIGFuZCBzcGVjaWZ5IHRoZSBsZXZlbCBvcmRlcmluZzoKCmBgYHtyIEZhY3RvckNhbGx9CnJlc3VsdHNfbWludXNfdnNfcGx1cyRjYWxsID0gZmFjdG9yKHJlc3VsdHNfbWludXNfdnNfcGx1cyRjYWxsLCBsZXZlbHMgPSBjKCdVcCcsICdEb3duJywgJ05TJykpCmhlYWQocmVzdWx0c19taW51c192c19wbHVzKQpgYGAKCj4gIyBUaXAgey51bmxpc3RlZCAudW5udW1iZXJlZH0KPiBJdCBpcyBvZnRlbiBoZWxwZnVsIHRvIGluY2x1ZGUgY29kZSBsaWtlIHRoaXMgaW4gZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzZXMgc28gdGhlcmUgaXMgYSBjbGVhcmx5IGxhYmVsbGVkIGNvbHVtbiB0aGF0IG1ha2VzIHN1YnNldHRpbmcgYW5kIHN1bW1hcml6aW5nIHRoZSByZXN1bHRzIGVhc2llci4KCk5vdyB3ZSBhcmUgaW4gYSBwb3NpdGlvbiB0byBxdWlja2x5IHN1bW1hcml6ZSBvdXIgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gcmVzdWx0czoKCmBgYHtyIFRhYmxlQ2FsbH0KdGFibGUocmVzdWx0c19taW51c192c19wbHVzJGNhbGwpCmBgYAoKV2Ugc2VlIHF1aWNrbHkgaG93IG1hbnkgZ2VuZXMgd2VyZSAiVXAiIGluIGlyb24gcmVwbGV0ZSwgaG93IG1hbnkgd2VyZSAiRG93biIgaW4gaXJvbiByZXBsZXRlLCBhbmQgaG93IG1hbnkgd2VyZSBub3Qgc2lnbmlmaWNhbnQuCgoqKkNoZWNrcG9pbnQqKjogKklmIHlvdSBzdWNjZXNzZnVsbHkgYWRkZWQgdGhlIGBjYWxsYCBjb2x1bW4gYW5kIGdvdCB0aGUgc2FtZSB0YWJsZSByZXN1bHQgYXMgYWJvdmUsIHBsZWFzZSBpbmRpY2F0ZSB3aXRoIGEgZ3JlZW4gY2hlY2suIE90aGVyd2lzZSB1c2UgYSByZWQgeC4qCgojIyBWaXN1YWwgREUgc3VtbWFyeQoKQXMgZGVzY3JpYmVkIGJ5IHRoaXMgW0dhbGF4eSBwcm9qZWN0IHR1dG9yaWFsXShodHRwczovL2dhbGF4eXByb2plY3QuZ2l0aHViLmlvL3RyYWluaW5nLW1hdGVyaWFsL3RvcGljcy90cmFuc2NyaXB0b21pY3MvdHV0b3JpYWxzL3JuYS1zZXEtdml6LXdpdGgtdm9sY2Fub3Bsb3QvdHV0b3JpYWwuaHRtbCksIGEgdm9sY2FubyBwbG90IGlzIGEgdHlwZSBvZiBzY2F0dGVycGxvdCB0aGF0IHNob3dzIHN0YXRpc3RpY2FsIHNpZ25pZmljYW5jZSAoYWRqdXN0ZWQgcC12YWx1ZSkgdmVyc3VzIG1hZ25pdHVkZSBvZiBjaGFuZ2UgKGZvbGQgY2hhbmdlKS4gSW4gYSB2b2xjYW5vIHBsb3QsIHRoZSBtb3N0IHVwcmVndWxhdGVkIGdlbmVzIGFyZSB0b3dhcmRzIHRoZSByaWdodCwgdGhlIG1vc3QgZG93bnJlZ3VsYXRlZCBnZW5lcyBhcmUgdG93YXJkcyB0aGUgbGVmdCwgYW5kIHRoZSBtb3N0IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQgZ2VuZXMgYXJlIHRvd2FyZHMgdGhlIHRvcC4KCkxldCdzIGNvZXJjZSB0aGUgYERhdGFGcmFtZWAgd2hpY2ggd2FzIHJldHVybmVkIGJ5IGBERVNlcTI6OnJlc3VsdHMoKWAgaW50byBhIGB0aWJibGVgIGluIGFudGljaXBhdGlvbiBvZiB1c2luZyB0aGUgYGdncGxvdDJgIGxpYnJhcnkgdG8gcGxvdC4gV2UncmUgYWxzbyBnb2luZyB0byBtb2RpZnkgb3VyIHJlc3VsdHMgdGFibGUgc28gdGhhdCB0aGUgcm93IG5hbWVzIGJlY29tZSBhIHNlcGFyYXRlIGNvbHVtbiwgYW5kIHNvIHRoYXQgaXQncyBvcmRlcmVkIGJ5IGFkanVzdGVkIHAtdmFsdWUuCgpgYGB7ciBQbG90U2V0dXAyfQojIFVzZSB0aGUgcm93bmFtZXMgYXJndW1lbnQgdG8gY3JlYXRlIGEgbmV3IGNvbHVtbiBvZiBnZW5lIElEcwojIEFsc28gYXJyYW5nZSBieSBhZGp1c3RlZCBwLXZhbHVlCnJlc3VsdHNfbWludXNfdnNfcGx1cyA9IGFzX3RpYmJsZShyZXN1bHRzX21pbnVzX3ZzX3BsdXMsIHJvd25hbWVzID0gJ2lkJykgJT4lIGFycmFuZ2UocGFkaikKYGBgCgpMZXQncyBzdGFydCB3aXRoIGEgdmVyeSBzaW1wbGUgdm9sY2FubyBwbG90IHRoYXQgcGxvdHMgdGhlIGBsb2cyRm9sZENoYW5nZWAgb24gdGhlIHgtYXhpcywgYW5kIGAtbG9nMTAocGFkailgIG9uIHRoZSB5LWF4aXMuCgpgYGB7ciBWb2xjYW5vUGxvdH0KIyBJbml0aWFsaXplIHRoZSBwbG90LCBzYXZpbmcgYXMgb2JqZWN0ICdwJyBhbmQgc3BlY2lmeWluZyB0aGUgcGxvdCB0eXBlIGFzICdnZW9tX3BvaW50JwpwID0gZ2dwbG90KHJlc3VsdHNfbWludXNfdnNfcGx1cywgYWVzKHggPSBsb2cyRm9sZENoYW5nZSwgeSA9IC1sb2cxMChwYWRqKSkpICsKICAgIGdlb21fcG9pbnQoKQpwCmBgYAoKVGhpcyBpcyBhIGdvb2Qgc3RhcnQsIGJ1dCwgYXMgdXN1YWwgaXQncyBuaWNlIHRvIGFkZCBiZXR0ZXIgbGFiZWxzIHRvIHRoZSBwbG90IHdpdGggdGhlIGBsYWJzKClgIGZ1bmN0aW9uOgoKYGBge3IgVm9sY2Fub1Bsb3QyfQojIEFkZCBwbG90IGxhYmVscyBhbmQgY2hhbmdlIHRoZSB0aGVtZQpwID0gZ2dwbG90KHJlc3VsdHNfbWludXNfdnNfcGx1cywgYWVzKHggPSBsb2cyRm9sZENoYW5nZSwgeSA9IC1sb2cxMChwYWRqKSkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICB0aGVtZV9idygpICsKICAgIGxhYnMoCiAgICAgICAgdGl0bGUgPSAnVm9sY2FubyBQbG90JywKICAgICAgICBzdWJ0aXRsZSA9ICdQbHVzIHZzIE1pbnVzJywKICAgICAgICB4ID0gJ2xvZzIgZm9sZC1jaGFuZ2UnLAogICAgICAgIHkgPSAnLWxvZzEwIEZEUicKICAgICkKcApgYGAKClRoaXMgaXMgYSBuaWNlIGltcHJvdmVtZW50LiBXaGF0IGlmIHdlIG5vdyBhZGRlZCBzb21lIHZpc3VhbCBndWlkZXMgdG8gaW5kaWNhdGUgd2hlcmUgdGhlIHNpZ25pZmljYW50IGdlbmVzIGFyZT8gV2UgY2FuIHVzZXQgaGUgYGdlb21fdmxpbmUoKWAgYW5kIGBnZW9tX2hsaW5lKClgIGZ1bmN0aW9ucyB0byBhY2NvbXBsaXNoIHRoaXM6CgpgYGB7ciBWb2xjYW5vUGxvdDN9CiMgQWRkIHRocmVzaG9sZCBsaW5lcwpwID0gcCArCiAgICBnZW9tX3ZsaW5lKAogICAgICAgIHhpbnRlcmNlcHQgPSBjKDAsIC1sb2cyKGZjKSwgbG9nMihmYykpLAogICAgICAgIGxpbmV0eXBlID0gYygxLCAyLCAyKSkgKwogICAgZ2VvbV9obGluZSgKICAgICAgICB5aW50ZXJjZXB0ID0gLWxvZzEwKGZkciksCiAgICAgICAgbGluZXR5cGUgPSAyKQpwCmBgYAoKRmluYWxseSwgd2h5IG5vdCBjb2xvciB0aGUgcG9pbnRzIGJ5IHRoZWlyIHNpZ25pZmljYW5jZSBzdGF0dXM/IFdlIGFscmVhZHkgY3JlYXRlZCB0aGUgYGNhbGxgIGNvbHVtbiB0aGF0IGhhcyB0aGUgY29ycmVjdCB2YWx1ZXMuIEluIHRoaXMgY2FzZSB3ZSBjYW4gZ2V0IGF3YXkgd2l0aCBhZGRpbmcgYSBsYXN0IGBnZW9tX3BvaW50KClgIGFuZCBzcGVjaWZ5aW5nIHRoZSBjb3JyZWN0IGFlc3RoZXRpYzoKCmBgYHtyIFZvbGNhbm9QbG90NH0KcCA9IHAgKyBnZW9tX3BvaW50KGFlcyhjb2xvciA9IGNhbGwpKQpwCmBgYAoKRm9yIGFkZGl0aW9uYWwgdmlzdWFsaXphdGlvbnMgZm9yIG91ciBERSByZXN1bHRzLCB3ZSBpbmNsdWRlZCBzb21lIGV4YW1wbGUgY29kZSBpbiB0aGUgQm9udXMgQ29udGVudCBtb2R1bGUgYW5kIHRoaXMgW0hCQyB0dXRvcmlhbF0oaHR0cHM6Ly9oYmN0cmFpbmluZy5naXRodWIuaW8vREdFX3dvcmtzaG9wL2xlc3NvbnMvMDZfREdFX3Zpc3VhbGl6aW5nX3Jlc3VsdHMuaHRtbCkgaW5jbHVkZXMgc29tZSBuaWNlIGV4YW1wbGVzLgoKIyMjIFN1YnNldHRpbmcgc2lnbmlmaWNhbnQgZ2VuZXMKCllvdSBtYXkgYmUgaW50ZXJlc3RlZCBpbiBpZGVudGlmeWluZyBvbmx5IHRoZSBnZW5lcyB0aGF0IHBhc3MgeW91ciBzaWduaWZpY2FuY2UgdGhyZXNob2xkcy4gQSB1c2VmdWwgd2F5IHRvIGRvIHRoaXMgaXMgdG8gY29uZGl0aW9uYWxseSBzdWJzZXQgeW91ciByZXN1bHRzLiBBZ2Fpbiwgd2UgYWxyZWFkeSBjcmVhdGVkIHRoZSBgY2FsbGAgY29sdW1uLCB3aGljaCBtYWtlcyB0aGlzIHJlbGF0aXZlbHkgc2ltcGxlIHRvIGRvLgoKKk5vdGU6IFRoZSB0aWR5dmVyc2UgZnVuY3Rpb25zIHlvdSBsZWFybmVkIGluIFNvZnR3YXJlIENhcnBlbnRyeSBjb3VsZCBhbHNvIGJlIGFsdGVybmF0aXZlbHkgdXNlZCBoZXJlLioKCmBgYHtyIENvbmRpdGlvbmFsU3Vic2V0fQpyZXNfc2lnID0gcmVzdWx0c19taW51c192c19wbHVzW3Jlc3VsdHNfbWludXNfdnNfcGx1cyRjYWxsICE9ICdOUycsIF0KaGVhZChyZXNfc2lnKQpkaW0ocmVzX3NpZykKYGBgCgojIyBBZGRpbmcgZ2Vub21lIGFubm90YXRpb25zCgpTaW5jZSwgZ2VuZSBzeW1ib2xzIGNhbiBjaGFuZ2Ugb3ZlciB0aW1lIG9yIGJlIGFtYmlndW91cyB3ZSB1c2UsIGFuZCByZWNvbW1lbmQsIHVzaW5nIHRoZSBFTVNFTUJMIHJlZmVyZW5jZSBnZW5vbWUgYW5kIEVOU0VNQkwgSURzIGZvciBhbGlnbm1lbnRzIGFuZCB3ZSd2ZSBiZWVuIHdvcmtpbmcgd2l0aCB0YWJsZXMgYW5kIGRhdGEgd2hlcmUgYWxsIGdlbmVzIGFyZSBsYWJlbGVkIG9ubHkgYnkgdGhlaXIgbG9uZyBFTlNFTUJMIElEICh5b3Ugd2lsbCBub3RpY2UgdGhpcyBpbiB0aGUgR1RGIHdlIHVzZWQgaW4gdGhlIHJlZmVyZW5jZSkuIEhvd2V2ZXIsIHRoaXMgY2FuIG1ha2UgaXQgZGlmZmljdWx0IHRvIHF1aWNrbHkgbG9vayBhdCByZXN1bHRzIGZvciBnZW5lcyBvZiBpbnRlcmVzdC4KCkx1Y2tpbHksIEJpb2NvbmR1Y3RvciBwcm92aWRlcyBtYW55IHRvb2xzIGFuZCByZXNvdXJjZXMgdG8gZmFjaWxpdGF0ZSBhY2Nlc3MgdG8gW2dlbm9taWMgYW5ub3RhdGlvbiByZXNvdXJjZXNdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL2RldmVsL3dvcmtmbG93cy92aWduZXR0ZXMvYW5ub3RhdGlvbi9pbnN0L2RvYy9Bbm5vdGF0aW9uX1Jlc291cmNlcy5odG1sKS4KClRvIHN0YXJ0LCB3ZSB3aWxsIGZpcnN0IGxvYWQgdGhlIFtiaW9tYVJ0IGxpYnJhcnldKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy8zLjE0L2Jpb2MvaHRtbC9iaW9tYVJ0Lmh0bWwpIGFuZCBjaG9vc2Ugd2hhdCByZWZlcmVuY2Ugd2Ugd2FudCB0byBhY2Nlc3MuIEZvciBhIG1vcmUgZGV0YWlsZWQgd2FsayB0aHJvdWdoIG9mIHVzaW5nIGJpb21hUnQsIFt0aGlzIHRyYWluaW5nIG1vZHVsZV0oaHR0cHM6Ly9iaW9pbmZvcm1hdGljcy1jb3JlLXNoYXJlZC10cmFpbmluZy5naXRodWIuaW8vY3J1ay1zdW1tZXItc2Nob29sLTIwMTkvUk5Bc2VxL2h0bWwvMDVfQW5ub3RhdGlvbl9hbmRfVmlzdWFsaXNhdGlvbi5odG1sKSBtaWdodCBiZSB1c2VmdWwsIGluY2x1ZGluZyB3aGF0IHRvIGRvIHdoZW4gYW5ub3RhdGlvbnMgYXJlIG5vdCAxOjEgbWFwcGluZ3MuCgpXZSdsbCBzdGFydCBieSBsb2FkaW5nIHRoZSBgYmlvbWFSdGAgbGlicmFyeSBhbmQgY2FsbGluZyB0aGUgYHVzZUVuc2VtYmwoKWAgZnVuY3Rpb24gdG8gc2VsZWN0IHRoZSBkYXRhYmFzZSB3ZSdsbCB1c2UgdG8gZXh0cmFjdCB0aGUgaW5mb3JtYXRpb24gd2UgbmVlZC4gVGhpcyB3aWxsIGRvd25sb2FkIHRoZSBtYXBwaW5nIG9mIEVOU0VNQkwgSURzIHRvIGdlbmUgc3ltYm9scywgZW5hYmxpbmcgdXMgdG8gZXZlbnR1YWxseSBhZGQgdGhlIGdlbmUgc3ltYm9sIGNvbHVtbiB3ZSB3YW50LgoKYGBge3IgUHVsbG1hcnQsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoJ2Jpb21hUnQnKQplbnNlbWJsID0gdXNlRW5zZW1ibChkYXRhc2V0ID0gJ21tdXNjdWx1c19nZW5lX2Vuc2VtYmwnLCBiaW9tYXJ0PSdlbnNlbWJsJykKYGBgCgoqTm90ZSB0aGF0IHRoaXMgcHJvY2VzcyB0YWtlcyBzb21lIHRpbWUgYW5kIHdpbGwgdGFrZSB1cCBhIGxhcmdlciBhbW91bnQgb2Ygd29ya2luZyBtZW1vcnkgc28gcHJvY2VlZCB3aXRoIGNhdXRpb24gaWYgeW91IHRyeSB0byBydW4gdGhlc2UgY29tbWFuZHMgb24gYSBsYXB0b3Agd2l0aCBsZXNzIHRoYW4gNEcgb2YgbWVtb3J5KgoKVG8gaWRlbnRpZnkgcG9zc2libGUgKipmaWx0ZXJzKiogdG8gcmVzdHJpY3Qgb3VyIGRhdGEsIHdlIGNhbiB1c2UgdGhlIGBsaXN0RmlsdGVyc2AgZnVuY3Rpb24uIFRvIGlkZW50aWZ5IHRoZSAqKmF0dHJpYnV0ZXMqKiB3ZSB3YW50IHRvIHJldHJpdmUsIHdlIGNhbiB1c2UgdGhlIGBsaXN0QXR0cmlidXRlc2AgZnVuY3Rpb24uIFRoZSBiZXN0IGFwcHJvYWNoIGlzIHRvIHVzZSBbbGlzdCBvciBzZWFyY2ggZnVuY3Rpb25zXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9iaW9jL3ZpZ25ldHRlcy9iaW9tYVJ0L2luc3QvZG9jL2FjY2Vzc2luZ19lbnNlbWJsLmh0bWwjaG93LXRvLWJ1aWxkLWEtYmlvbWFydC1xdWVyeSkgdG8gaGVscCBuYXJyb3cgZG93biB0aGUgYXZhaWxhYmxlIG9wdGlvbnMuCgpgYGB7ciBBZGRBbm5vdGF0aW9uczIsIHdhcm5pbmc9RkFMU0UsIGV2YWw9RkFMU0V9CmhlYWQobGlzdEZpbHRlcnMobWFydCA9IGVuc2VtYmwpLCBuID0gMjApCmhlYWQobGlzdEF0dHJpYnV0ZXMoZW5zZW1ibCksIG4gPSAzMCkKYGBgCgpXZSBjYW4gYWNjZXNzIGFkZGl0aW9uYWwgZ2Vub21pYyBhbm5vdGF0aW9ucyB1c2luZyB0aGUgW2BiaW9NYXJ0YCBwYWNrYWdlXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9iaW9jL2h0bWwvYmlvbWFSdC5odG1sKS4gVG8gaWRlbnRpZnkgIHdlJ2xsIHN0cnVjdHVyZSBvdXIgJ3F1ZXJ5JyBvciBzZWFyY2ggb2YgdGhlIGJpb01hcnQgcmVzb3VyY2VzIHRvIHVzZSB0aGUgW0VOU0VNQkwgaWRdKGh0dHBzOi8vbS5lbnNlbWJsLm9yZy9pbmZvL2dlbm9tZS9nZW5lYnVpbGQvZ2VuZV9uYW1lcy5odG1sKSBmcm9tIG91ciBhbGlnbm1lbnQgdG8gYWRkIHRoZSBnZW5lIHN5bWJvbHMgYW5kIGdlbmUgZGVzY3JpcHRpb24gZm9yIGVhY2ggZ2VuZS4KCmBgYHtyIEFkZEFub3RhdGlvbjMsIHdhcm5pbmc9RkFMU0V9CmlkX21hcHBpbmcgPSBnZXRCTShhdHRyaWJ1dGVzPWMoJ2Vuc2VtYmxfZ2VuZV9pZCcsICdleHRlcm5hbF9nZW5lX25hbWUnKSwKICAgICAgZmlsdGVycyA9ICdlbnNlbWJsX2dlbmVfaWQnLAogICAgICB2YWx1ZXMgPSByb3cubmFtZXMoYXNzYXkoZGRzKSksCiAgICAgIG1hcnQgPSBlbnNlbWJsKQojIHdpbGwgdGFrZSBzb21lIHRpbWUgdG8gcnVuCgojIFByZXZpZXcgdGhlIHJlc3VsdApoZWFkKGlkX21hcHBpbmcpCmBgYAoKTm93IHRoYXQgd2UgaGF2ZSB0aGUgRU5TRU1CTCBpbmZvcm1hdGlvbiBhbmQgYSBnZW5lIHN5bWJvbCB0byBtYXRjaCB0byBvdXIgcmVzdWx0cywgd2UgY2FuIHByb2NlZWQgaW4gdGhlIHNtYWxsZXIgZ3JvdXBzLiBBcyB3aXRoIHRoZSBwcmV2aW91cyBleGVyY2lzZSwgd2UgaGF2ZSBicm9rZW4gaXQgaW50byBzbWFsbCBzdGVwcyB3aXRoIGhpbnRzIGFzIG5lZWRlZC4KCioqTm90ZSoqOiBGb3IgYWRkaXRpb25hbCBpbmZvcm1hdGlvbiByZWdhcmRpbmcgYmlvTWFydCwgcGxlYXNlIGNvbnN1bHQgdGhlIFtFTlNFTUJMIGJpb01hcnQgdmlnbmV0dGVdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvdmlnbmV0dGVzL2Jpb21hUnQvaW5zdC9kb2MvYWNjZXNzaW5nX2Vuc2VtYmwuaHRtbCkgb3IgdGhlIGJyb2FkZXIgW0Jpb2NvbmR1Y3RvciBBbm5vdGF0aW9uIFJlc291cmNlcyB2aWduZXR0ZSBdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL2RldmVsL3dvcmtmbG93cy92aWduZXR0ZXMvYW5ub3RhdGlvbi9pbnN0L2RvYy9Bbm5vdGF0aW9uX1Jlc291cmNlcy5odG1sKS4KCkxvb2sgYXQgdGhlIHR3byBkYXRhIGZyYW1lcyB0aGF0IGFyZSBnb2luZyB0byBiZSBuZWVkZWQ6IGBpZF9tYXBwaW5nYCBhbmQgYHJlc3VsdHNfbWludXNfdnNfcGx1c2AuCgpgYGB7ciBwcmV2aWV3X3RhYmxlc30KaGVhZChpZF9tYXBwaW5nKQpoZWFkKHJlc3VsdHNfbWludXNfdnNfcGx1cykKYGBgCgpXZSB3YW50IHRvIG1hdGNoIHRoZSBgaWRgIGNvbHVtbiBvZiBgcmVzdWx0c19taW51c192c19wbHVzYCB0byB0aGUgYGVuc2VtYmxfZ2VuZV9pZGAgY29sdW1uIG9mIGBpZF9tYXBwaW5nYCwgYW5kIG9uY2UgdGhhdCBtYXRjaCBpcyBmb3VuZCwgd2Ugd2FudCB0byBleHRyYWN0IHRoZSBgZXh0ZXJuYWxfZ2VuZV9uYW1lYCBjb2x1bW4gb2YgYGlkX21hcHBpbmdgIHRvIGdldCB0aGUgZ2VuZSBzeW1ib2wuIE5leHQsIGxvb2sgYXQgdGhlIGRvY3VtZW50YXRpb24gZm9yIGBkcGx5cjo6bGVmdF9qb2luKClgIGFuZCBtZXJnZSB0aGUgYGlkX21hcHBpbmdgIHRhYmxlIGludG8gdGhlIGByZXN1bHRzX21pbnVzX3ZzX3BsdXNgIHRhYmxlIG9uIHRoZSBjb2x1bW5zIGBlbnNlbWJsX2dlbmVfaWRgIGFuZCBgZXh0ZXJuYWxfZ2VuZV9uYW1lYC4KCmBgYHtyIGxlZnRfam9pbn0KcmVzdWx0c19taW51c192c19wbHVzX2Fubm90YXRlZCA9IHJlc3VsdHNfbWludXNfdnNfcGx1cyAlPiUKICAgIGxlZnRfam9pbihpZF9tYXBwaW5nLCBieSA9IGMoJ2lkJyA9ICdlbnNlbWJsX2dlbmVfaWQnKSkKaGVhZChyZXN1bHRzX21pbnVzX3ZzX3BsdXNfYW5ub3RhdGVkKQpgYGAKCldlIGNhbiB1c2Ugc29tZSBvZiB0aGUgYHRpZHl2ZXJzZWAgZnVuY3Rpb25zIHdlJ3ZlIGVuY291bnRlcmVkIHByZXZpb3VzbHkgdG8gcmVuYW1lIHRoZSBgZXh0ZXJuYWxfZ2VuZV9uYW1lYCBjb2x1bW4gdG8gYHN5bWJvbGAgYW5kIHRvIG1vdmUgaXQgaW50byB0aGUgc2Vjb25kIGNvbHVtbiBwb3NpdGlvbj8gSGludDogQmVjYXVzZSBvZiB0aGUgb3JkZXIgb2YgdGhlIHBhY2thZ2VzIHdlIG1heSBoYXZlIGxvYWRlZCwgd2UnbGwgdXNlIGBkcGx5cjo6cmVuYW1lKClgIGFuZCBgZHBseXI6OnNlbGVjdCgpYCBpbnN0ZWFkIG9mIGp1c3QgdGhlIGBzZWxlY3QoKWAgZnVuY3Rpb24uIFdlIGNhbiBkaXNjdXNzIHRoaXMgaW4gYSBtb21lbnQuCgpgYGB7ciByZW5hbWVfcmVhcnJhbmdlfQpyZXN1bHRzX21pbnVzX3ZzX3BsdXNfYW5ub3RhdGVkID0gcmVzdWx0c19taW51c192c19wbHVzX2Fubm90YXRlZCAlPiUKICAgIGRwbHlyOjpyZW5hbWUoJ3N5bWJvbCcgPSAnZXh0ZXJuYWxfZ2VuZV9uYW1lJykgJT4lCiAgICBkcGx5cjo6c2VsZWN0KGlkLCBzeW1ib2wsIGV2ZXJ5dGhpbmcoKSkKcmVzdWx0c19taW51c192c19wbHVzX2Fubm90YXRlZApgYGAKCkFuZCBub3cgd2UgaGF2ZSBvdXIgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gcmVzdWx0cyBhbm5vdGF0ZWQgd2l0aCBnZW5lIHN5bWJvbHMsIHdoaWNoIGNhbiBoZWxwIGluIHRoZSBpbnRlcnByZXRhdGlvbiBvZiB0aGUgcmVzdWx0cywgYW5kIGNhbiBiZSB1c2VkIGluIGRvd25zdHJlYW0gYW5hbHlzaXMgc3VjaCBhcyBmdW5jdGlvbmFsIGFuYWx5c2lzLgoKIyBPdXRwdXR0aW5nIHJlc3VsdHMgdG8gZmlsZQoKQSBrZXkgYXNwZWN0IG9mIG91ciBhbmFseXNpcyBpcyBwcmVzZXJ2aW5nIHRoZSByZWxldmFudCBkYXRhc2V0cyBmb3IgYm90aCBvdXIgcmVjb3JkcyBhbmQgZm9yIGRvd25zdHJlYW0gYXBwbGljYXRpb25zLCBzdWNoIGFzIGZ1bmN0aW9uYWwgZW5yaWNobWVudHMuCgoKIyMgREUgcmVzdWx0cyB0YWJsZQoKTmV4dCB3ZSdsbCB3cml0ZSBvdXQgb3VyIERFIHJlc3VsdHMsIG5vdyB0aGF0IHdlJ3ZlIGFkZGVkIGluZm9ybWF0aW9uIHRvIHRoZSB0YWJsZSB0byBoZWxwIHVzIGludGVycHJldCB0aGUgcmVzdWx0cywgYW5kIHNoYXJlIHdpdGggY29sbGFib3JhdG9ycy4KCmBgYHtyIERFUmVzdWx0c091cHV0LCBldmFsID0gRkFMU0V9CndyaXRlLmNzdihyZXN1bHRzX21pbnVzX3ZzX3BsdXMsCiAgICAgICAgICByb3cubmFtZXMgPSBGQUxTRSwKICAgICAgICAgIG5hID0gIi4iLAogICAgICAgICAgZmlsZT0ib3V0cHV0cy90YWJsZXMvREVfcmVzdWx0c19taW51c192c19wbHVzLmNzdiIpCmBgYAoKIyMgQ291bnQgdGFibGVzCgpUaGUgbW9zdCByZWxldmFudCBjb3VudCB0YWJsZXMgYXJlIHRoZSByYXcsIGZpbHRlcmVkIGNvdW50IHRhYmxlIHRoYXQgd2UgdXNlZCBhcyB0aGUgaW5wdXQgZm9yIG91ciBhbmFseXNpcyBhbmQgdGhlIHJsb2cgbm9ybWFsaXplZCBjb3VudCB0YWJsZSB0aGF0IHdlIHVzZWQgZm9yIG91ciBxdWFsaXR5IGNvbnRyb2wgdmlzdWFsaXphdGlvbnMuCgpUbyBvdXRwdXQgdGhlIHJhdyBjb3VudHMsIHdlIHdpbGwgbmVlZCB0byB1c2UgdGhlIGBjb3VudHNgIGZ1bmN0aW9uIHRvIGFjY2VzcyB0aGUgY291bnQgdGFibGUgZnJvbSB3aXRoaW4gaXRzIGxhcmdlciBgREVTZXFEYXRhU2V0YCBvYmplY3QuCgpgYGB7ciBPdXRwdXRDb3VudHNSYXcsIGV2YWwgPSBGQUxTRX0Kd3JpdGUuY3N2KGNvdW50cyhkZHMsIG5vcm1hbGl6ZWQgPSBGQUxTRSksIGZpbGU9Im91dHB1dHMvdGFibGVzL0RFU2VxMl9yYXdfY291bnRzLmNzdiIpCmBgYAoKVGhlbiB3ZSdsbCBvdXRwdXQgdGhlIHJsb2cgY291bnQgdGFibGUsIHVzaW5nIHRoZSBgYXNzYXlgIGZ1bmN0aW9uIHRvIGFjY2VzcyB0aGUgbm9ybWFsaXplZCBjb3VudCB0YWJsZSBmcm9tIHdpdGhpbiBpdHMgbGFyZ2VyIGBERVNlcURhdGFTZXRgIG9iamVjdC4KCmBgYHtyIE91dHB1dENvdW50c1Jsb2csIGV2YWwgPSBGQUxTRX0Kd3JpdGUuY3N2KGFzc2F5KHJsZCksIGZpbGU9Im91dHB1dHMvdGFibGVzL0RFU2VxMl9ybG9nX25vcm1hbGl6ZWRfY291bnRzLmNzdiIpCmBgYAoKIyBTdW1tYXJ5CgpJbiB0aGlzIHNlY3Rpb24sIHdlOgoKKiBHZW5lcmF0ZWQgYSB2b2xjYW5vIHBsb3QgZm9yIG91ciBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiByZXN1bHRzCiogU3VtbWFyaXplZCBvdXIgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gcmVzdWx0cwoqIERpc2N1c3NlZCBjaG9vc2luZyB0aHJlc2hvbGRzCiogQW5ub3RhdGVkIG91ciB0YWJsZXMgb2YgcmVzdWx0cyB0byBtYXAgZ2VuZSBJRHMgdG8gZ2VuZSBzeW1ib2xzCiogU2F2ZWQgb3VyIHJlc3VsdHMgdG8gZmlsZQoKCiMgS2V5IHRha2Vhd2F5cwoKT3ZlcmFsbCwgd2UndmUgcnVuIHRocm91Z2ggbW9zdCBvZiB0aGUgYnVpbGRpbmcgYmxvY2tzIG5lZWRlZCB0byBydW4gYSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyBhbmQgaG9wZWZ1bGx5IGJ1aWx0IHVwIGEgYmV0dGVyIHVuZGVyc3RhbmRpbmcgb2YgaG93IGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGNvbXBhcmlzb25zIHdvcmssIHBhcnRpY3VsYXJseSBob3cgZXhwZXJpbWVudGFsIGRlc2lnbiBjYW4gaW1wYWN0IG91ciByZXN1bHRzLgoKV2hhdCB0byBjb25zaWRlciBtb3ZpbmcgZm9yd2FyZDoKCiogSG93IGNhbiBJIGNvbnRyb2wgZm9yIHRlY2huaWNhbCB2YXJpYXRpb24gaW4gbXkgZXhwZXJpbWVudGFsIGRlc2lnbj8KKiBIb3cgbXVjaCB2YXJpYXRpb24gaXMgZXhwZWN0ZWQgd2l0aCBhIHRyZWF0bWVudCBncm91cD8KKiBXaGF0IGlzIG15IFJOQSBxdWFsaXR5LCBhbmQgaG93IGNhbiB0aGF0IGJlIG9wdGltaXplZD8KKiBBcmUgdGhlcmUgcXVhbGl0eSBjb25jZXJucyBmb3IgbXkgc2VxdWVuY2luZyBkYXRhPwoqIFdoYXQgY29tcGFyaXNvbnMgYXJlIHJlbGV2YW50IHRvIG15IGJpb2xvZ2ljYWwgcXVlc3Rpb24/CiogQXJlIHRoZXJlIGNvdmFyaWF0ZXMgdGhhdCBzaG91bGQgYmUgY29uc2lkZXJlZD8KKiBXaGF0IHdpbGwgYSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyB0ZWxsIG1lPwoKCioqTGV0J3MgcGF1c2UgaGVyZSBmb3IgZ2VuZXJhbCBxdWVzdGlvbnMqKgoKLS0tCgojIFdoYXQgY2FuIHdlIGRvIHdpdGggb3VyIERFIHJlc3VsdHM/CgpBIHdheSB0byBkZXRlcm1pbmUgcG9zc2libGUgW2Jyb2FkZXIgYmlvbG9naWNhbCBpbnRlcnByZXRhdGlvbnNdKGh0dHBzOi8vd3d3LmViaS5hYy51ay90cmFpbmluZy1iZXRhL29ubGluZS9jb3Vyc2VzL2Z1bmN0aW9uYWwtZ2Vub21pY3MtaWktY29tbW9uLXRlY2hub2xvZ2llcy1hbmQtZGF0YS1hbmFseXNpcy1tZXRob2RzL2Jpb2xvZ2ljYWwtaW50ZXJwcmV0YXRpb24tb2YtZ2VuZS1leHByZXNzaW9uLWRhdGEtMi8pIGZyb20gdGhlIG9ic2VydmVkIERFIHJlc3VsdHMsIGlzIGZ1bmN0aW9uYWwgZW5yaWNobWVudHMuIFRoZXJlIGFyZSBtYW55IG9wdGlvbnMsIHN1Y2ggYXMgc29tZSBpbmNsdWRlZCBpbiB0aGlzIFtkaXNjdXNzaW9uIHRocmVhZF0oaHR0cHM6Ly93d3cucmVzZWFyY2hnYXRlLm5ldC9wb3N0L0hvd19jYW5fSV9hbmFseXplX2Ffc2V0X29mX0RFR3NfZGlmZmVyZW50aWFsbHlfZXhwcmVzc2VkX2dlbmVzX3RvX29idGFpbl9pbmZvcm1hdGlvbl9mcm9tX3RoZW0pLiBPdGhlciBjb21tb24gZnVuY3Rpb25hbCBlbnJpY2htZW50cyBhcHByb2FjaGVzIGFyZSBnZW5lIHNldCBlbnJpY2htZW50IGFuYWx5c2lzLCBha2EgW0dTRUFdKGh0dHA6Ly9zb2Z0d2FyZS5icm9hZGluc3RpdHV0ZS5vcmcvZ3NlYS9pbmRleC5qc3ApLCBEYXRhYmFzZSBmb3IgQW5ub3RhdGlvbiwgVmlzdWFsaXphdGlvbiBhbmQgSW50ZWdyYXRlZCBEaXNjb3ZlcnksIGFrYSBbREFWSURdKGh0dHBzOi8vZGF2aWQubmNpZmNyZi5nb3YvKSwgW0luZ2VuaXR5XShodHRwczovL2RpZ2l0YWxpbnNpZ2h0cy5xaWFnZW4uY29tLyksIGFuZCBbaVBhdGh3YXkgR3VpZGVdCgpUaGUgVW5pdmVyc2l0eSBvZiBNaWNoaWdhbiBoYXMgbGljZW5zZSBhbmQgc3VwcG9ydCBmb3IgYWRkaXRpb25hbCB0b29scywgc3VjaCBhcyBDeXRvc2NhcGUsIHNvIHdlIHJlY29tbWVuZCByZWFjaGluZyBvdXQgdG8gc3RhZmYgd2l0aCBbVGF1Ym1hbiBMaWJyYXJ5XShodHRwczovL3d3dy5saWIudW1pY2guZWR1L2xvY2F0aW9ucy1hbmQtaG91cnMvdGF1Ym1hbi1oZWFsdGgtc2NpZW5jZXMtbGlicmFyeS9yZXNlYXJjaC1hbmQtY2xpbmljYWwtc3VwcG9ydCkgdG8gbGVhcm4gbW9yZSBhYm91dCByZXNvdXJjZXMgdGhhdCBtaWdodCBiZSBhcHBsaWNhdGlvbiB0b3lvdXIgcmVzZWFyY2guCgotLS0KCiMgU291cmNlcwoKKiBIQkMgREdFIHRyYWluaW5nIG1vZHVsZSwgcGFydCAxOiBodHRwczovL2hiY3RyYWluaW5nLmdpdGh1Yi5pby9ER0Vfd29ya3Nob3AvbGVzc29ucy8wNF9ER0VfREVTZXEyX2FuYWx5c2lzLmh0bWwKKiBIQkMgREdFIHRyYWluaW5nIG1vZHVsZSwgcGFydCAyOiBodHRwczovL2hiY3RyYWluaW5nLmdpdGh1Yi5pby9ER0Vfd29ya3Nob3AvbGVzc29ucy8wNV9ER0VfREVTZXEyX2FuYWx5c2lzMi5odG1sCiogREVTZXEyIHZpZ25ldHRlOiBodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9kZXZlbC9iaW9jL3ZpZ25ldHRlcy9ERVNlcTIvaW5zdC9kb2MvREVTZXEyLmh0bWwjZGlmZmVyZW50aWFsLWV4cHJlc3Npb24tYW5hbHlzaXMKKiBCaW9jb25kdWN0b3IgR2Vub21pYyBBbm5vdGF0aW9uIHJlc291cmNlczogaHR0cDovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvZGV2ZWwvd29ya2Zsb3dzL3ZpZ25ldHRlcy9hbm5vdGF0aW9uL2luc3QvZG9jL0Fubm90YXRpb25fUmVzb3VyY2VzLmh0bWwKKiBCaW9NYXJ0IHZpZ25ldHRlOiBodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvcGFja2FnZXMvcmVsZWFzZS9iaW9jL3ZpZ25ldHRlcy9iaW9tYVJ0L2luc3QvZG9jL2FjY2Vzc2luZ19lbnNlbWJsLmh0bWwKCiMgQWRkaXRpb25hbCBSZXNvdXJjZXMKKiBNSURBUyBSZXByb2R1Y2libGl0eSBIdWI6IGh0dHBzOi8vbWlkYXMudW1pY2guZWR1L3JlcHJvZHVjaWJpbGl0eS1vdmVydmlldy8KKiBBUkMgcmVzb3VyY2VzOiBodHRwczovL2FyYy10cy51bWljaC5lZHUvCiogR2VuZSBTZXQgRW5yaWNobWVudCBSZXNvdXJjZXMgZnJvbSBCaW9jb25kdWN0b3I6IGh0dHBzOi8vYmlvaW5mb3JtYXRpY3MtY29yZS1zaGFyZWQtdHJhaW5pbmcuZ2l0aHViLmlvL2NydWstc3VtbWVyLXNjaG9vbC0yMDE4L1JOQVNlcTIwMTgvaHRtbC8wNl9HZW5lX3NldF90ZXN0aW5nLm5iLmh0bWwKKiBVc2luZyBIVFNlcSBkYXRhIHdpdGggREVTZXEyOiBodHRwczovL2FuZ3VzLnJlYWR0aGVkb2NzLmlvL2VuLzIwMTkvZGlmZi1leC1hbmQtdml6Lmh0bWwKKiBEZXRhaWxlZCBSTkEtc2VxIGFuYWx5c2lzIHBhcGVyOiBodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM2MDk2MzQ2LwoqIE92ZXJ2aWV3IG9mIFJOQS1zZXEgYW5hbHlzaXMgY29uc2lkZXJhdGlvbnM6IGh0dHBzOi8vYWNhZGVtaWMtb3VwLWNvbS5wcm94eS5saWIudW1pY2guZWR1L2JmZy9hcnRpY2xlLzE0LzIvMTMwLzI1NzM3MAoqIEFsdGVybmF0aXZlIG92ZXJ2aWV3IG9mIERFU2VxMiwgaW5jbHVkaW5nIHZpc3VhbGl6YXRpb25zIGFuZCBmdW5jdGlvbmFsIGVucmljaG1lbnRzOiBodHRwOi8vZHB1dGhpZXIuZ2l0aHViLmlvL2pnYjcxZS1wb2x5dGVjaC1iaW9pbmZvLWFwcC9wcmFjdGljYWwvcm5hLXNlcV9SL3JuYXNlcV9kaWZmX1NuZjIuaHRtbAoKLS0tCgpgYGB7ciBXcml0ZU91dC5SRGF0YSwgZXZhbD1UUlVFLCBlY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIEhpZGRlbiBjb2RlIGJsb2NrIHRvIHdyaXRlIG91dCBkYXRhIGZvciBrbml0dGluZwojIHNhdmUuaW1hZ2UoZmlsZSA9ICJyZGF0YS9SdW5uaW5nRGF0YV9GdWxsLlJEYXRhIikKYGBgCgojIFNlc3Npb24gSW5mbwpgYGB7ciBTZXNzaW9uSW5mb30Kc2Vzc2lvbkluZm8oKQpgYGAKCi0tLQoKVGhlc2UgbWF0ZXJpYWxzIGhhdmUgYmVlbiBhZGFwdGVkIGFuZCBleHRlbmRlZCBmcm9tIG1hdGVyaWFscyBsaXN0ZWQgYWJvdmUuIFRoZXNlIGFyZSBvcGVuIGFjY2VzcyBtYXRlcmlhbHMgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIHRlcm1zIG9mIHRoZSBbQ3JlYXRpdmUgQ29tbW9ucyBBdHRyaWJ1dGlvbiBsaWNlbnNlIChDQyBCWSA0LjApXShodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9saWNlbnNlcy9ieS80LjAvKSwgd2hpY2ggcGVybWl0cyB1bnJlc3RyaWN0ZWQgdXNlLCBkaXN0cmlidXRpb24sIGFuZCByZXByb2R1Y3Rpb24gaW4gYW55IG1lZGl1bSwgcHJvdmlkZWQgdGhlIG9yaWdpbmFsIGF1dGhvciBhbmQgc291cmNlIGFyZSBjcmVkaXRlZC4K