Differential expression analysis


Project: GSE107401


The aim of this document is to present the data and differential expression analysis between samples from the project GSE107401.

The main steps are:

For a quick access to the figures, go to:
- Normalisation > Visualisation for normalised counts
- Results subsections for the pairwise comparisons

All figures are saved in PNG and PDF format and accessible in the ./project_GSE107401/deseq2_figures/ folder. You can download the code behind this analysis through the “Code” button in the top right corner of the document.

1 Settings

The aim of this section is to:

  • load the tools of interest
  • define custom functions
  • load the input parameters

1.1 Environment

We load the tools to handle data and figures.

library(dplyr)
library(ggplot2)

1.2 Custom functions

We further define custom functions to avoid repetitions in the code.

# -----------------------------------------------------------------------------
# buildCountMatrix
# Create a matrix of reads count
#
# Input:
#   files : a vector of files names
#   sampleLabel : a vector of sample names
#   projectPath: path to the project directory
#
# Output:
#   countMatrix : a reads count matrix
#
# Original author : Vivien DESHAIES
# -----------------------------------------------------------------------------
buildCountMatrix = function(files, sampleLabel, expHeader) {
  
  # read first file and create countMatrix
  countMatrix = read.table(files[1],
                           header = expHeader,
                           stringsAsFactors = F,
                           quote = "")
  colnames(countMatrix) = c("id", "count1")
  
  # read and merge all remaining files with the first
  for (i in 2:length(files)) {
    
    # read files
    exp = read.table(files[i],
                     header = expHeader,
                     stringsAsFactors = F,
                     quote = "")
    
    # lowercase exp columns names
    colnames(exp) = c("id", paste0("count", i))
    
    # merge file data to count matrix by id
    countMatrix = merge(countMatrix, exp, by = "id", suffixes = "_")
  }
  
  # name rows
  rownames(countMatrix) = countMatrix[,1]
  
  # delete first row containing row names
  countMatrix = countMatrix[,-1]
  
  # name columns
  colnames(countMatrix) = sampleLabel
  return(countMatrix)
}

###############################################################################
# -----------------------------------------------------------------------------
# saveRawCountMatrix
#
#   A specific treatment is needed for the raw count matrix because the object
#   DESeq2::counts(dds) does not include the names of the samples as column names
#
#   input: dds -> DESeq object
#          fileName -> character (name of the file to output)
#   output: rawCountMatrix -> file
#
# -----------------------------------------------------------------------------

saveRawCountMatrix = function(dds,fileName) {
  
  countMatrix = DESeq2::counts(dds)
  countMatrix = cbind(countMatrix, row.names(countMatrix))
  
  # Add the samples names as column names + add column Id
  colData = SummarizedExperiment::colData(dds)
  colnames(countMatrix) = c(colData$Name, "Id")
  
  # Put Ids on the first column
  countMatrix = countMatrix[, c("Id", colData$Name)]
  
  # Write the count matrix in a file
  write.table(countMatrix,
              file = paste0(fileName),
              sep = "\t",
              row.names = FALSE,
              quote = FALSE)
}

###############################################################################
# -----------------------------------------------------------------------------
# saveTable
#
#   input: dataframe -> data frame (count matrix)
#          fileName -> character (name of the file to output)
#   output: countMatrix -> file
# -----------------------------------------------------------------------------

saveTable = function(dataframe, fileName) {
  # Add column Id
  column_names = colnames(dataframe)
  dataframe = cbind(row.names(dataframe), dataframe)
  colnames(dataframe) = c("Id", column_names)
  
  # Write the table in a file
  write.table(dataframe,
              file = paste0(fileName),
              sep = "\t",
              row.names = FALSE,
              quote = FALSE)
}

# -----------------------------------------------------------------------------
# plotDendrogram
# Create a ggplot object corresponding to a dendrogram
#
# Input:
#   hc_data : object obtained with the ggdendro::dendro_data function
#   fig_title : figure title
#   col_vector: named vector of colors
#
# Output:
#   p : ggplot object
#
# Original author : Audrey ONFROY
# -----------------------------------------------------------------------------
plotDendrogram = function(hc_data,
                          fig_title = "",
                          col_vector = colors_condition) {
  
  p = ggplot2::ggplot() +
    # Draw dendrogram
    ggplot2::geom_segment(data = ggdendro::segment(hcdata), 
                          aes(x = x, y = y, xend = xend, yend = yend)) +
    # Leaf label
    ggplot2::geom_label(data = ggdendro::label(hcdata), 
                        aes(x = x, y = y, label = label),
                        linewidth = 0, fill = NA,
                        size = 3, angle = 90, hjust = 1,
                        nudge_y = -0.02*max(ggdendro::segment(hcdata)$yend)) +
    # Leaf colors
    ggplot2::geom_point(data = ggdendro::label(hcdata),
                        ggplot2::aes(x = x, y = y, col = Condition),
                        size = 3, pch = 19) +
    ggplot2::scale_color_manual(values = col_vector,
                                breaks = names(col_vector)) +
    # Style
    ggplot2::labs(y = "Distance",
                  title = fig_title) +
    ggplot2::coord_cartesian(clip = "off") +
    ggplot2::theme_minimal() +
    ggplot2::theme(axis.text.x = element_blank(),
                   axis.ticks.x = element_blank(),
                   axis.title.x = element_blank(),
                   axis.line.y = element_line(),
                   axis.ticks.y = element_line(),
                   panel.grid = element_blank(),
                   plot.title = element_text(hjust = 0.5),
                   plot.margin = ggplot2::unit(c(0.5, 0.5, max(nchar(hcdata$labels$label))/6, 0.5), "cm"),
                   # Transparent background
                   panel.background = ggplot2::element_blank(),
                   plot.background = ggplot2::element_blank(),
                   legend.background = ggplot2::element_blank())
  
  return(p)
}

# -----------------------------------------------------------------------------
# plotPCA
# Create a ggplot object corresponding to the PCA plot of individuals
#
# Input:
#   pca_df : dataframe
#   fig_title : figure title
#   col_vector: named vector of colors
#
# Output:
#   p : ggplot object
# -----------------------------------------------------------------------------
plotPCA = function(pca_df,
                   fig_title = "",
                   col_vector = colors_condition) {
  
  p = ggplot2::ggplot(pca_df, aes(x = Dim.1, y = Dim.2, col = Condition, label = sample)) +
    ggplot2::geom_hline(yintercept = 0, lty = 2) +
    ggplot2::geom_vline(xintercept = 0, lty = 2) +
    # Samples
    ggplot2::geom_point(size = 3, pch = 19) +
    ggrepel::geom_label_repel(fill = NA, label.size = 0) +
    ggplot2::scale_color_manual(values = col_vector,
                                breaks = names(col_vector)) +
    # Style
    ggplot2::labs(x = paste0("Dim1 (", round(pcaCount$eig[1, 2], 2), "%)"),
                  y = paste0("Dim2 (", round(pcaCount$eig[2, 2], 2), "%)"),
                  title = fig_title) +
    ggplot2::theme_minimal() +
    ggplot2::theme(plot.title = element_text(hjust = 0.5),
                   # Transparent background
                   panel.background = ggplot2::element_blank(),
                   plot.background = ggplot2::element_blank(),
                   legend.background = ggplot2::element_blank())
  
  return(p)
}

# -----------------------------------------------------------------------------
# plotBarplot
# Create a ggplot object corresponding to a barplot
#
# Input:
#   df : dataframe
#   y_column : column in df for the Y-axis
#   y_title : Y-axis title
#   fig_title : figure title
#   col_vector: named vector of colors
#
# Output:
#   p : ggplot object
# -----------------------------------------------------------------------------
plotBarplot = function(df,
                       y_column,
                       y_title = "",
                       fig_title = "",
                       col_vector = colors_condition) {
  df$y_value = df[, y_column]
  
  p = ggplot2::ggplot(df, aes(x = sample, y = y_value, fill = Condition)) +
    ggplot2::geom_bar(stat = "identity", col = "black") +
    ggplot2::scale_fill_manual(values = col_vector,
                               breaks = names(col_vector)) +
    # Style
    ggplot2::labs(x = "",
                  y = y_title,
                  title = fig_title) +
    ggplot2::scale_y_continuous(expand = c(0,0)) +
    ggplot2::theme_classic() +
    ggplot2::theme(plot.title = element_text(hjust = 0.5),
                   axis.text.x = element_text(angle = 90, hjust = 1),
                   # Transparent background
                   panel.background = ggplot2::element_blank(),
                   plot.background = ggplot2::element_blank(),
                   legend.background = ggplot2::element_blank())
  
  return(p)
}

# -----------------------------------------------------------------------------
# plotBoxplot
# Create a ggplot object corresponding to a boxplot
#
# Input:
#   df : dataframe
#   y_column : column in df for the Y-axis
#   y_title : Y-axis title
#   fig_title : figure title
#   colors_condition: named vector of colors
#
# Output:
#   p : ggplot object
# -----------------------------------------------------------------------------
plotBoxplot = function(df,
                       y_column,
                       y_title = "",
                       fig_title = "",
                       col_vector = colors_condition) {
  df$y_value = df[, y_column]
  
  p = ggplot2::ggplot(df, aes(x = sample, y = y_value, fill = Condition)) +
    ggplot2::geom_boxplot(col = "black") +
    ggplot2::scale_fill_manual(values = col_vector,
                               breaks = names(col_vector)) +
    # Style
    ggplot2::labs(x = "",
                  y = y_title,
                  title = fig_title) +
    ggplot2::scale_y_continuous(expand = c(0,0)) +
    ggplot2::theme_classic() +
    ggplot2::theme(plot.title = element_text(hjust = 0.5),
                   axis.text.x = element_text(angle = 90, hjust = 1),
                   # Transparent background
                   panel.background = ggplot2::element_blank(),
                   plot.background = ggplot2::element_blank(),
                   legend.background = ggplot2::element_blank())
  
  return(p)
}

# -----------------------------------------------------------------------------
# expressionColors
# Define a color panel for the heatmap
#
# Input:
#   range_values : a numerical vector with two values (min and max)
#
# Output:
#   expression_colors : a color or a function built with circlize::colorRamp2
# -----------------------------------------------------------------------------
expressionColors = function(range_values) {
  min_value = range_values[1]
  max_value = range_values[2]
  
  # min_value < 0 < max_value
  if (min_value < 0 & 0 < max_value) {
    expression_colors = circlize::colorRamp2(
      breaks = c(min_value, 0, max_value),
      colors = c("#224897", "#F7F7F7", "#D32A24"))
  } else
    
    # min_value < max_value < 0
    if (min_value < max_value & max_value < 0) {
      expression_colors = circlize::colorRamp2(
        breaks = c(min_value, 0),
        colors = c("#224897", "#F7F7F7"))
    } else
      
      # 0 < min_value < max_value
      if (0 < min_value & min_value < max_value) {
        expression_colors = circlize::colorRamp2(
          breaks = c(0, max_value),
          colors = c("#F7F7F7", "#D32A24"))
      } else {
        
        # min_value == max_value
        expression_colors = "#F7F7F7"
      }
  
  return(expression_colors)
}

# -----------------------------------------------------------------------------
# buildContrast
# Define the contrast vector to extract DE results from the dds object
#
# Input:
#   dds       : DESeqDataSet
#   group1    : list of character vectors, for instance:
#                           list(c("sexe", "F"), c("genotype", "HETERO")
#   group2    : list of character vectors, for instance:
#                           list(c("sexe", "F"), c("genotype", "HOMO")
#   weighted  : boolean     whether to weight the contrast matrix by the
#                           number of replicates per condition or not
#
# Output: a list of 3 elements
#   contrast        : a numerical vector
#   samples_group1  : a character vector containing sample names in group 1
#   samples_group2  : a character vector containing sample names in group 2
#
# Source:
# Modified from: https://www.r-bloggers.com/2024/05/a-guide-to-designs-and-contrasts-in-deseq2/
# -----------------------------------------------------------------------------
buildContrast = function(dds,
                         group1,
                         group2,
                         weighted = FALSE){
  #-- Model matrix for each sample 
  mod_mat = model.matrix(DESeq2::design(dds),
                         SummarizedExperiment::colData(dds))
  
  #-- Samples in group1
  # A list of TRUE / FALSE
  # Each element of the list is of length the number of samples
  # The i-th element of the list corresponds to the i-th condition in group1
  grp1_rows = lapply(c(1:length(group1)), FUN = function(group1_i) {
    group1_i_name = group1[[group1_i]][1]
    group1_i_values = group1[[group1_i]][2:length(group1_i)]
    
    samples_in_group1_i = (SummarizedExperiment::colData(dds)[[group1_i_name]] %in% group1_i_values) # TRUE / FALSE
    
    return(samples_in_group1_i)
  })
  grp1_rows = Reduce(function(x, y) x & y, grp1_rows)
  
  grp2_rows = lapply(c(1:length(group2)), FUN = function(group2_i) {
    group2_i_name = group2[[group2_i]][1]
    group2_i_values = group2[[group2_i]][2:length(group2_i)]
    
    samples_in_group2_i = (SummarizedExperiment::colData(dds)[[group2_i_name]] %in% group2_i_values) # TRUE / FALSE
    
    return(samples_in_group2_i)
  })
  grp2_rows = Reduce(function(x, y) x & y, grp2_rows)
  
  #-- Model matrix with only samples of interest
  mod_mat1 = mod_mat[grp1_rows, , drop = FALSE]
  mod_mat2 = mod_mat[grp2_rows, , drop = FALSE]
  
  #-- Consider the weight or not
  if(!weighted){
    mod_mat1 = mod_mat1[!duplicated(mod_mat1), , drop = FALSE]
    mod_mat2 = mod_mat2[!duplicated(mod_mat2), , drop = FALSE]
  }
  
  #-- Output
  contrast = colMeans(mod_mat1)-colMeans(mod_mat2)
  samples_group1 = colnames(dds)[grp1_rows]
  samples_group2 = colnames(dds)[grp2_rows]
  output = list(contrast = contrast,
                samples_group1 = samples_group1,
                samples_group2 = samples_group2)
  
  return(output)
}

# -----------------------------------------------------------------------------
# missingSubstring
# Identify and color the variable of interest in the context of nested models
#
# Input:
#   A : the text behind a formula
#   B : the text behind a formula
#
# Output:
#   A_color : A, with in red, the substring from A which is absent in B
# -----------------------------------------------------------------------------
missingSubstring = function(A, B) {
  # Remove the ~
  A = base::substr(x = A, start = 2, stop = nchar(A))
  B = base::substr(x = B, start = 2, stop = nchar(B))
  
  # Split by +
  A_split = stringr::str_split(string = A,
                               pattern = "\\+")[[1]]
  
  B_split = stringr::str_split(string = B,
                               pattern = "\\+")[[1]]
  
  # Difference
  id_absent_in_B = which(!(A_split %in% B_split))
  colored_substring = paste0("<span style='color:red'>",
                             A_split[id_absent_in_B],
                             "</span>")
  
  # Rebuild A
  A_color = stringr::str_replace(string = A,
                                 pattern = A_split[id_absent_in_B],
                                 replacement = colored_substring)
  A_color = paste0("~", A_color)
  
  return(A_color)
}

These functions are listed below.

  • buildContrast
  • buildCountMatrix
  • current_version
  • expressionColors
  • missingSubstring
  • plotBarplot
  • plotBoxplot
  • plotDendrogram
  • plotPCA
  • prefix
  • saveRawCountMatrix
  • saveTable
  • version_tested

1.3 Parameters

The table below summarizes all the input parameters.

projectName     = params$projectName
designPath      = params$designPath
comparisonPath  = params$comparisonPath
correspPath     = params$correspPath
diffanaTest     = as.logical(toupper(params$diffanaTest))
expHeader       = as.logical(toupper(params$expHeader))
deseqModel      = params$deseqModel
nestedModel     = params$nestedModel
sizeFactorType  = params$sizeFactorType
fitType         = params$fitType
statisticTest   = params$statisticTest
weightContrast  = params$weightContrast
padjLimit       = params$padjLimit
log2FCLimit     = params$log2FCLimit
plotInteractive = params$plotInteractive
leaveOnError    = params$leaveOnError
saveRDS         = params$saveRDS

# Display them as a table
c("Project name (projectName)" = projectName,
  "Path to the design file (designPath)" = designPath,
  "Path to the comparison file (comparisonPath)" = comparisonPath,
  "Path to the correspondence file (correspPath)" = correspPath,
  "Whether to perform the differential expression analysis or not (diffanaTest)" = diffanaTest,
  "Whether the design table has a header or not (expHeader)" = expHeader,
  "DESeq2 model (deseqModel)" = deseqModel,
  "Whether to perform a test of nested models (nestedModel)" = nestedModel,
  "Size factor type (sizeFactorType)" = sizeFactorType,
  "Fit type (fitType)" = fitType,
  "Statistic test (statisticTest)" = statisticTest,
  "Whether to weight the contrast vector by the number of samples or not (weightContrast)" = weightContrast,
  "Adjusted p-value threshold to consider a gene as differentially expressed (padjLimit)" = padjLimit,
  "Absolute log2 fold-change threshold to consider a gene as differentially expressed (log2FCLimit)" = log2FCLimit,
  "Prefix to save files (prefix)" = prefix,
  "Whether to make interactive plots or not (plotInteractive)" = plotInteractive,
  "Whether to stop the rendering in case of error (leaveOnError)" = leaveOnError,
  "Whether to save the data as RDS files (saveRDS)" = saveRDS) %>%
  data.frame(Parameter = names(.),
             Value     = .,
             row.names = NULL) %>%
  knitr::kable("html", escape = FALSE) %>%
  kableExtra::kable_styling("striped", full_width = FALSE) %>%
  kableExtra::column_spec(1, bold = TRUE)
Parameter Value
Project name (projectName) GSE107401
Path to the design file (designPath) ./project_GSE107401/deseq2_GSE107401-deseq2Design.txt
Path to the comparison file (comparisonPath) ./project_GSE107401/deseq2_GSE107401-comparisonFile.txt
Path to the correspondence file (correspPath) ./project_GSE107401/ensembl_to_symbols.tsv
Whether to perform the differential expression analysis or not (diffanaTest) TRUE
Whether the design table has a header or not (expHeader) TRUE
DESeq2 model (deseqModel) ~Condition+FooBar+Condition:FooBar
Size factor type (sizeFactorType) ratio
Fit type (fitType) parametric
Statistic test (statisticTest) Wald
Whether to weight the contrast vector by the number of samples or not (weightContrast) FALSE
Adjusted p-value threshold to consider a gene as differentially expressed (padjLimit) 0.05
Absolute log2 fold-change threshold to consider a gene as differentially expressed (log2FCLimit) 2
Prefix to save files (prefix) ./project_GSE107401/deseq2_
Whether to make interactive plots or not (plotInteractive) TRUE
Whether to stop the rendering in case of error (leaveOnError) TRUE
Whether to save the data as RDS files (saveRDS) TRUE

2 Data preparation

The purpose of this section is to:

  • load the design file, containing metadata about each sample ;
  • load the individual sample count data, assemble them as a gene-by-sample count matrix, and save it as a TSV file ; and,
  • build a dataset in a format compatible with the differential expression analysis (DESeq2-related format).

2.1 Load metadata

We load the design file and display the 6 first rows.

design = read.table(designPath,
                    sep = "\t",
                    header = TRUE,
                    dec = ".",
                    stringsAsFactors = FALSE)

# About a warning in DESeq2
design$Condition = stringr::str_replace_all(
  design$Condition,
  pattern = "-",
  replacement = "_")

# Add Reference if does not exist
if (!("Reference" %in% colnames(design))) {
  design$Reference = Inf
  complexMode = TRUE
} else {
  complexMode = FALSE
}

# Reorder samples based on Condition
design = design %>%
  dplyr::arrange(Condition, RepTechGroup, Name) %>%
  dplyr::mutate(Condition = factor(Condition, levels = unique(Condition))) %>%
  dplyr::mutate(RepTechGroup = factor(RepTechGroup, levels = unique(RepTechGroup))) %>%
  dplyr::mutate(Name = factor(Name, levels = unique(Name)))

# Remove columns out of interest
design = design[, !(colnames(design) %in% c("SampleId", "Description", "Date", "FastqFormat"))]

# Display
head(design) %>%
  dplyr::mutate_all(as.character) %>%
  rbind.data.frame(., "..." = rep("...", ncol(.))) %>%
  knitr::kable("html") 
Name expressionFile RepTechGroup Condition FooBar Reference
GSM2866306 ./project_GSE107401/GSM2866306_expression_output_expression_20130267.tsv KO1 KO FOO Inf
GSM2866307 ./project_GSE107401/GSM2866307_expression_output_expression_20130268.tsv KO2 KO BAR Inf
GSM2866308 ./project_GSE107401/GSM2866308_expression_output_expression_20130269.tsv KO3 KO BAR Inf
GSM2866303 ./project_GSE107401/GSM2866303_expression_output_expression_20130270.tsv WT1 WT FOO Inf
GSM2866304 ./project_GSE107401/GSM2866304_expression_output_expression_20130271.tsv WT2 WT FOO Inf
GSM2866305 ./project_GSE107401/GSM2866305_expression_output_expression_20130272.tsv WT3 WT BAR Inf

Description of the columns:

  • Name: Unique identifier of each technical replicate.
  • expressionFile: File path to the count expression data. The TSV file contains two columns: one for the gene identifiers and one for the number of reads mapping to their genomic location. For compatibility with Excel, please use the XLSX file instead of the TSV file (read more). Furthermore, the XLSX file contains additional columns, such as a description of each gene.
  • RepTechGroup: Samples with the same value in the RepTechGroup column are technical replicates. This occurs when the sequencing library is split over sequencing lanes. Each lane generates a technical replicate from the same biological library.
  • Condition: Samples with the same value in the Condition column are biological replicates. They are produced by the experimenter.
  • Reference: Numerical values used to define the pairwise comparisons between conditions in the differential expression analysis.

We associate a color to each condition.

colors_condition = unique(design$Condition) %>%
  setNames(nm = .,
           grDevices::hcl.colors(length(.), "Spectral"))

data.frame(Color = unname(colors_condition),
           Condition = names(colors_condition)) %>%
  knitr::kable("html",
               col.names = c("Color (hex code)", "Condition")) %>%
  kableExtra::column_spec(1, color = colors_condition) %>%
  kableExtra::kable_styling(full_width = FALSE)
Color (hex code) Condition
#A71B4B KO
#584B9F WT

2.2 Build count matrix

From the individual count data, we build a gene-by-sample count matrix. The count value for a given gene corresponds to the number (integer value) of sequencing reads that map to its genomic region. We display part of this matrix, meaning 5 genes and 5 samples. The complete matrix is saved as a TSV file (quick access).

count_mat = buildCountMatrix(files = design$expressionFile,
                             sampleLabel = design$Name,
                             expHeader = expHeader)

# Reorder columns
count_mat = count_mat[, levels(design$Name)]

# Define an order to display the matrix
top5_to_print = order(count_mat[, 1], decreasing = TRUE)[c(1:5)]

# Display
count_mat[top5_to_print, c(1:5)] %>%
  rbind.data.frame(., "..." = rep("...", ncol(.))) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
GSM2866306 GSM2866307 GSM2866308 GSM2866303 GSM2866304
ENSMUSG00000064351 602444 555649 566740 505759 620255
ENSMUSG00000037742 452751 358719 402908 174736 196874
ENSMUSG00000026043 444784 335779 800483 849969 649180
ENSMUSG00000022548 415951 478597 174835 87921 118643
ENSMUSG00000029661 308601 249060 550519 417405 357593

The count matrix contains 46500 genes, 6 samples and 41.94% of null values.

2.3 Create DESeqDataSet object

We create the DESeqDataSet object that includes the count matrix and the design file.

dds = DESeq2::DESeqDataSetFromMatrix(
  countData = count_mat,
  colData = design,
  design = as.formula(deseqModel))
cat('## Identifiers to symbols\n\n')

2.4 Identifiers to symbols

cat('We load the table containing the correspondence between genes identifiers and symbols.\n')

We load the table containing the correspondence between genes identifiers and symbols.

correspIdSymbol = read.delim(file = correspPath) %>%
  # Force column names
  `colnames<-`(c("ID", "symbol")) %>%
  # Unique ID
  dplyr::filter(!duplicated(ID)) %>%
  # Force all ID to be present
  dplyr::left_join(x = data.frame(ID = rownames(count_mat)),
                   y = .,
                   by = "ID") %>%
  # Change missing symbols to ID
  dplyr::mutate(symbol = ifelse(is.na(symbol),
                                yes = ID,
                                no = symbol))
rownames(correspIdSymbol) = correspIdSymbol$ID

# Display
head(correspIdSymbol) %>%
  rbind.data.frame(., "..." = rep("...", ncol(.))) %>%
  knitr::kable("html",
               row.names = FALSE) %>%
  kableExtra::kable_styling(full_width = FALSE)
ID symbol
ENSMUSG00000000001 Gnai3
ENSMUSG00000000003 Pbsn
ENSMUSG00000000028 Cdc45
ENSMUSG00000000031 H19
ENSMUSG00000000037 Scml2
ENSMUSG00000000049 Apoh

2.5 Save count matrix

We save the count matrix as a TSV file.

filename = paste0(prefix, projectName, "-normalisation_rawCountMatrix.tsv")

saveRawCountMatrix(dds,
                   file = filename)

Output file name:
./project_GSE107401/deseq2_GSE107401-normalisation_rawCountMatrix.tsv

cat("We also save the object in RDS format. The file name is the same. The extension is '.rds'.\n")

We also save the object in RDS format. The file name is the same. The extension is ‘.rds’.

saveRDS(dds,
        file = stringr::str_replace(string = filename,
                                    pattern = "tsv$",
                                    replacement = "rds"))

3 Quality control

The aim of this section is to better apprehend the variability between the technical replicates. For information on the correspondence between the identifiers of technical replicates and sample names, please refer to the design file.

3.1 Raw counts dendrogram

We compute euclidean distances between each pair of samples, considering the raw count values.

dist.mat = dist(t(count_mat))
hc = stats::hclust(dist.mat)
hcdata = ggdendro::dendro_data(hc, type = "rectangle")

# Add grouping for colors
hcdata$labels = dplyr::left_join(x = hcdata$labels,
                                 y = design[, c("Name", "Condition")],
                                 by = c("label" = "Name"))

We visualise these distances through a dendrogram. It informs whether technical replicates or closely related conditions are closer together than with other samples.

plotDendrogram(hc_data,
               fig_title = paste0("Dendrogram on the raw count matrix - ",
                                  projectName))

3.2 Raw counts PCA

The 6 samples are represented in a space of 46500 dimensions, corresponding to genes. We perform a Principal Component Analysis (PCA) to visualise samples on a two-dimension space such that it represents the highest variability between samples.

pcaCount = count_mat %>%
  t() %>%
  FactoMineR::PCA(., graph = FALSE)

We visualise the samples on the two-dimensional projection. The coordinates of each sample depend on those of the other samples. This projection captures 69.93% of the total variability between samples. Therefore, it lacks 30.07% of the initial information. In this representation, two points (samples) are close together if they have similar global transcriptomic profiles. We expect the technical and biological replicates to be closer to each other than to the other samples and conditions.

pcaCount$ind$coord %>%
  as.data.frame() %>%
  dplyr::mutate(sample = rownames(.)) %>%
  dplyr::left_join(x = .,
                   y = unique(design[, c("Name", "Condition")]),
                   by = c("sample" = "Name")) %>%
  plotPCA(.,
          fig_title =  paste0("PCA of the raw count matrix - ", projectName))

3.3 Null counts barplot

This figure displays the proportion of null counts per sample, meaning, the genes that are not being expressed or not captured by the protocol, within the biological samples. We expect samples from the same condition to be similar.

colMeans(count_mat == 0) %>%
  data.frame(sample = factor(names(.), levels = levels(design$Name)),
             prop_null_counts = .) %>%
  dplyr::mutate(prop_null_counts = 100*prop_null_counts) %>%
  dplyr::left_join(x = .,
                   y = design[, c("Name", "Condition")],
                   by = c("sample" = "Name")) %>%
  # Plot
  plotBarplot(.,
              y_column = "prop_null_counts",
              y_title = "Proportion of null counts (%)",
              fig_title = paste0("Proportion of null counts per sample - ",
                                 projectName))

3.4 Raw counts barplot

This figure displays the total number of reads per sample, also known as library size. We can assess if some samples have a low library size compared to others, and whether technical replicates share a similar number of reads or not.

colSums(DESeq2::counts(dds)) %>%
  data.frame(sample = factor(names(.), levels = levels(design$Name)),
             read_counts = .) %>%
  dplyr::left_join(x = .,
                   y = design[, c("Name", "Condition")],
                   by = c("sample" = "Name")) %>%
  # Plot
  plotBarplot(.,
              y_column = "read_counts",
              y_title = "Total read counts",
              fig_title = paste0("Number of reads per sample - ", projectName))

3.5 Raw counts boxplot

This figure displays, as a boxplot, the number of raw counts for each gene, per sample. It informs whether the genes show a similar count distribution between samples and conditions.

log2(DESeq2::counts(dds)+1) %>%
  reshape2::melt() %>%
  `colnames<-`(c("gene_ID", "sample", "log2_counts")) %>%
  dplyr::mutate(sample = factor(sample, levels = levels(design$Name))) %>%
  dplyr::left_join(x = .,
                   y = design[, c("Name", "Condition")],
                   by = c("sample" = "Name")) %>%
  # Plot
  plotBoxplot(.,
              y_column = "log2_counts",
              y_title = "log_2 (counts+1)",
              fig_title = paste0("Raw counts distribution per sample - ", projectName))


In this context, there is no technical replicate.

4 Normalisation

The aim of this section is to normalise the count matrix, before performing the differential expression analysis. The normalisation method considers the sequencing depth and RNA composition of each sample to compute normalised count values.

For more details, please visit:

https://hbctraining.github.io/DGE_workshop/lessons/02_DGE_count_normalization.html

4.1 Data handling

We normalise the count matrix. We display part of this matrix, meaning 5 genes and 5 samples. Unlike count data, normalised count values are not necessarily integers.

dds = DESeq2::estimateSizeFactors(dds,
                                  type = sizeFactorType)

# Display
DESeq2::counts(dds, normalized = TRUE)[top5_to_print, c(1:5)] %>%
  rbind.data.frame(., "..." = rep("...", ncol(.))) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
KO1 KO2 KO3 WT1 WT2
ENSMUSG00000064351 505864.540975153 610878.699656916 457193.509594576 598214.902799665 546068.434452876
ENSMUSG00000037742 380169.238619758 394374.4994812 325028.977244824 206678.831727369 173326.57852734
ENSMUSG00000026043 373479.450360685 369154.338246086 645755.782441322 1005348.63980222 571533.814766698
ENSMUSG00000022548 349268.748104647 526167.981980893 141040.736933987 103993.507716223 104452.519155497
ENSMUSG00000029661 259128.322648202 273815.75227626 444107.90434502 493709.239980098 314822.532154206

We save the normalised count matrix as a TSV file.

filename = paste0(prefix, projectName,
                  "-normalisation_normalisedCountMatrix.tsv")

saveTable(DESeq2::counts(dds, normalized = TRUE),
          fileName = filename)

Output file name:
./project_GSE107401/deseq2_GSE107401-normalisation_normalisedCountMatrix.tsv

cat("We also save the object in RDS format. The file name is the same. The extension is '.rds'.\n")

We also save the object in RDS format. The file name is the same. The extension is ‘.rds’.

saveRDS(dds,
        file = stringr::str_replace(string = filename,
                                    pattern = "tsv$",
                                    replacement = "rds"))

4.2 Visualisation

4.2.1 Normalised counts dendrogram

We compute euclidean distances between each pair of samples, considering the normalised count values.

ddsStabilized = dds %>%
  DESeq2::varianceStabilizingTransformation() %>%
  SummarizedExperiment::assay()
dist.mat = dist(t(ddsStabilized))
hc = stats::hclust(dist.mat)
hcdata = ggdendro::dendro_data(hc, type = "rectangle")

# Add grouping for colors
hcdata$labels = dplyr::left_join(
  x = hcdata$labels,
  y = unique(design[, c("RepTechGroup", "Condition")]),
  by = c("label" = "RepTechGroup"))

We visualise these distances through a dendrogram. It informs whether the samples share a similar transcriptomic profile per condition or not.

plotDendrogram(
  hc_data,
  fig_title = paste0("Dendrogram on the normalised count matrix - ", projectName))

4.2.2 Normalised counts PCA

We perform a PCA for all samples using the normalised count matrix.

pcaCount = dds %>%
  DESeq2::counts(., normalized = TRUE) %>%
  t() %>%
  FactoMineR::PCA(., graph = FALSE)

We visualise the samples on the two-dimensional projection. The coordinates of each sample depend on those of the other samples. This projection captures 65.42% of the total variability between samples. Therefore, it lacks 34.58% of the initial information. In this representation, two points (samples) are close together if they have similar global transcriptomic profiles. We expect samples from the same biological condition to be closer to each other than to samples from other conditions.

pcaCount$ind$coord %>%
  as.data.frame() %>%
  dplyr::mutate(sample = rownames(.)) %>%
  dplyr::left_join(x = .,
                   y = unique(design[, c("RepTechGroup", "Condition")]),
                   by = c("sample" = "RepTechGroup")) %>%
  plotPCA(.,
          fig_title =  paste0("PCA of the normalised count matrix - ", projectName))

4.2.3 Normalised counts boxplot

This figure displays, as a boxplot, the number of normalised counts for each gene, per sample.

log2(DESeq2::counts(dds, normalized = TRUE)+1) %>%
  reshape2::melt() %>%
  `colnames<-`(c("gene_ID", "sample", "log2_counts")) %>%
  dplyr::mutate(sample = factor(sample, levels = levels(design$RepTechGroup))) %>%
  dplyr::left_join(x = .,
                   y = unique(design[, c("RepTechGroup", "Condition")]),
                   by = c("sample" = "RepTechGroup")) %>%
  # Plot
  plotBoxplot(
    .,
    y_column = "log2_counts",
    y_title = "log_2 (counts+1)",
    fig_title = paste0("Normalised counts distribution per sample - ", projectName))

4.2.4 Most expressed genes plot

We identified the most expressed gene in each sample.

# preparation of 2 data frame with the same number of column than
# the dds count matrix
maxCounts = DESeq2::counts(dds)[1,]
transcriptNames = DESeq2::counts(dds)[1,]

# for each sample (column)
for (i in 1:ncol(DESeq2::counts(dds))) {
  
  # selection of the maximum number of count
  maxCounts[i] = (max(DESeq2::counts(dds, normalized = TRUE)[,i])/
                    sum(DESeq2::counts(dds, normalized = TRUE)[,i]))*100
  
  # selection of the name of the genes this the maximum of count
  transcriptNames[i] = row.names(
    subset(DESeq2::counts(dds, normalized = TRUE),
           DESeq2::counts(dds, normalized = TRUE)[,i] == 
             max(DESeq2::counts(dds, normalized = TRUE)[,i])))
}

We prepare a dataframe for the graphical representation.

most_expresssed_df = dplyr::left_join(x = data.frame(sample = names(maxCounts),
                                                     max_counts = maxCounts),
                                      y = data.frame(sample = names(transcriptNames),
                                                     gene_id = transcriptNames,
                                                     gene_symbol = correspIdSymbol[transcriptNames, 2]),
                                      by = "sample") %>%
  dplyr::mutate(gene_id = paste0(gene_id, " (", gene_symbol, ")")) %>%
  dplyr::mutate(sample = factor(sample, levels = levels(design$RepTechGroup))) %>%
  dplyr::left_join(x = .,
                   y = unique(design[, c("RepTechGroup", "Condition")]),
                   by = c("sample" = "RepTechGroup"))

We represent the information as a barplot.

ggplot2::ggplot(most_expresssed_df, aes(x = sample, y = max_counts,
                                        fill = Condition, label = gene_id)) +
  ggplot2::geom_bar(stat = "identity", col = "black") +
  ggplot2::geom_label(mapping = aes(x = sample, y = 0),
                      linewidth = 0, fill = NA, hjust = 0) +
  ggplot2::scale_fill_manual(values = colors_condition,
                             breaks = names(colors_condition)) +
  # Style
  ggplot2::labs(y = "Proportion of reads (%)",
                title = paste0("Most expressed genes - ", projectName)) +
  ggplot2::scale_y_continuous(expand = c(0,0)) +
  ggplot2::coord_flip() +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 axis.title.y = element_blank(),
                 axis.text.x = element_text(angle = 90, hjust = 1),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

5 Nested models evaluation

The aim of this section is to evaluate the relevance of the terms in the formula. The design formula is: ~Condition+FooBar+Condition:FooBar. The terms of interest are: Condition, FooBar.


The code behind this section can be downloaded at:

5.1 Formula pairs

We automatically determine the full and reduced formula pairs. The full formulas are defined such that they match one of the following patterns:

  • all terms and all interactions
  • all terms and one interaction
  • all terms
  • one term
all_interactions = combn(all_terms, m = 2) %>%
  apply(., 2, FUN = paste, collapse = ":")

# Models with all interactions
nested_model = list()
i_start = 0

for (i in c(1:length(all_interactions))) {
  full_formula = c(all_terms, all_interactions) %>%
    paste(., collapse = "+")
  reduced_formula = c(all_terms, all_interactions[-i]) %>%
    paste(., collapse = "+")
  
  nested_model[[i_start + i]] = c(full_formula,
                                  reduced_formula)
}

# Models with a single interaction
i_start = i_start + length(all_interactions)

for (i in c(1:length(all_interactions))) {
  full_formula = c(all_terms, all_interactions[i]) %>%
    paste(., collapse = "+")
  reduced_formula = all_terms %>%
    paste(., collapse = "+")
  
  nested_model[[i_start + i]] = c(full_formula,
                                  reduced_formula)
}

# Models with all terms
i_start = i_start + length(all_terms)

for (i in c(1:length(all_terms))) {
  full_formula = all_terms %>%
    paste(., collapse = "+")
  reduced_formula = all_terms[-i] %>%
    paste(., collapse = "+")
  
  nested_model[[i_start + i]] = c(full_formula,
                                  reduced_formula)
}

# Models with 1 term
i_start = i_start + length(all_terms)

for (i in c(1:length(all_terms))) {
  full_formula = all_terms[i]
  reduced_formula = "1"
  
  nested_model[[i_start + i]] = c(full_formula,
                                  reduced_formula)
}

# Make as a dataframe
nested_model = nested_model %>%
  do.call(rbind.data.frame, .) %>%
  unique %>%
  `colnames<-`(c("full_formula", "reduced_formula")) %>%
  dplyr::mutate(full_formula = paste0("~", full_formula)) %>%
  dplyr::mutate(reduced_formula = paste0("~", reduced_formula))

5.2 Likelihood Ratio Tests

For each pair, we build the DESeq2 dataset, perform the LRT analysis and check how many genes are differentially expressed between the two models. It informs whether the removed term in the formula is to be considered in the definitive model or not. The differentially expressed genes are the ones with an adjusted p-value smaller than 0.05. Note that the fold change is not considered.

nested_model[, c("nb_genes", "decision")] = apply(nested_model, 1, FUN = function(models) {
  # Get the formula
  full_formula    = as.formula(models[[1]])
  reduced_formula = as.formula(models[[2]])
  
  # DESeq2 object with full formula
  dds = DESeq2::DESeqDataSetFromMatrix(
    countData = DESeq2::counts(dds, normalized = FALSE),
    colData = design,
    design = full_formula)
  
  # Nested model
  dds_res = DESeq2::DESeq(dds,
                          test = "LRT",
                          full = full_formula,
                          reduced = reduced_formula)
  
  # Number of DE genes between models
  res_LRT = DESeq2::results(dds_res)
  signif_or_not = (res_LRT$padj < padjLimit) # TRUE / FALSE
  nb_genes = sum(signif_or_not, na.rm = TRUE)
  prop_genes = mean(signif_or_not, na.rm = TRUE)  # between 0 and 1
  percent_genes = 100*(round(prop_genes, 2))      # between 0 and 100
  
  # Decision
  decision = "irrelevant"
  if (prop_genes > 0.05) {
    decision = "relevant"
  }
  
  # Output
  nb_prop_genes = paste0(nb_genes, " (", percent_genes, "%)")
  
  return(c(nb_prop_genes, decision))
}) %>% t() %>%
  rbind.data.frame()

# Color the variable being evaluated
nested_model$full_formula = apply(
  X = nested_model,
  MARGIN = 1,
  FUN = function(row_i) {
    missingSubstring(A = row_i[[1]],
                     B = row_i[[2]]) 
  })

# Display
nested_model %>%
  knitr::kable("html",
               col.names = c("Full formula",
                             "Reduced formula",
                             "Nb DE genes",
                             "The colored term is..."),
               escape = FALSE,
               row.names = FALSE) %>%
  kableExtra::kable_styling(full_width = FALSE)
Full formula Reduced formula Nb DE genes The colored term is…
~Condition+FooBar+Condition:FooBar ~Condition+FooBar 8 (0%) irrelevant
~Condition+FooBar ~FooBar 10040 (42%) relevant
~Condition+FooBar ~Condition 2 (0%) irrelevant
~Condition ~1 11082 (45%) relevant
~FooBar ~1 0 (0%) irrelevant

The best model should identify many differentially expressed genes without including terms in the formula that, when removed, result in a small number of differentially expressed genes.


6 Differential analysis

The aim of this section is to perform the differential expression analysis.

The code behind this section can be downloaded at:

6.1 Dispersion estimates

After the normalisation, the first step of the differential expression analysis is the estimation of gene-wise dispersion. The dispersion estimates reflect the variability in the expression of a given gene across samples.

dds = DESeq2::estimateDispersions(dds,
                                  fitType = fitType)

6.2 Dispersion plot

We visualise the dispersion estimates of each gene (black dots) as a function of the mean of normalised counts across samples. The red curve corresponds to the expected dispersion value for genes, based on their mean normalised counts. Then, a shrinkage of the gene-wise dispersion estimates consists in computed a final dispersion value for each gene (blue dots). This is important to reduce the false positives in the differential expression analysis.

DESeq2::plotDispEsts(
  dds,
  main = paste0("Dispersion estimation scatter plot\n", projectName))

For more details, please visit:

https://hbctraining.github.io/DGE_workshop/lessons/04_DGE_DESeq2_analysis.html

6.3 Generalized linear model

The last step of the differential expression analysis pipeline is to fit a model to each gene to compute their log2 fold change. A Wald statistical test is then used to evaluate if the values are distinct from 0, meaning if the genes are differentially expressed or not.

dds = DESeq2::DESeq(dds,
                    test = statisticTest,
                    fitType = fitType,
                    betaPrior = FALSE)
cat("## Save object\n\n")

6.4 Save object

cat("We save the dds object in RDS format.\n")

We save the dds object in RDS format.

filename = paste0(prefix, projectName,
                  "-processed_dds_object.rds")

saveRDS(dds,
        file = filename)

cat(paste0("Output file name:<br>*", filename, "*\n"))

Output file name:
./project_GSE107401/deseq2_GSE107401-processed_dds_object.rds

7 Results

The aim of this section is to extract the results for each paired comparison and build relevant figures.

List of comparisons

The table below is a summary of the conditions of interest.

Formal definition
WT_vs_KO ConditionWT_vs_ConditionKO
Foo_vs_Bar FooBarFOO_vs_FooBarBAR
FooWT_vs_FooKO FooBarFOO%ConditionWT_vs_FooBarFOO%ConditionKO

The code behind each section below can be downloaded at:

7.1 WT vs KO

Details:

  • number of samples in the WT condition: 3

  • number of samples in the KO condition: 3

  • contrast matrix to extract the results:

    Intercept

    Condition_WT_vs_KO

    FooBar_FOO_vs_BAR

    ConditionWT.FooBarFOO

    0

    1

    0

    0.5

7.1.1 Results table

We extract the dataframe containing the differential analysis results between condition WT and condition KO. We display the 6 first rows of this table. The complete table is saved as a TSV file (quick access).

res = DESeq2::results(dds,
                      contrast = comparison_contrast) %>%
  as.data.frame() %>%
  dplyr::mutate(ID = rownames(.)) %>%
  dplyr::relocate(ID)

# Display
na.omit(res[, -1]) %>%
  head() %>%
  round(., 4) %>%
  rbind.data.frame(., "..." = rep("...", ncol(.))) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
baseMean log2FoldChange lfcSE stat pvalue padj
ENSMUSG00000000001 8545.2007 0.0329 0.1486 0.2213 0.8248 0.8932
ENSMUSG00000000028 588.143 -1.7408 0.234 -7.4399 0 0
ENSMUSG00000000031 67477.2131 0.419 0.2478 1.6912 0.0908 0.1824
ENSMUSG00000000037 98.8549 0.6003 0.5472 1.097 0.2726 0.4229
ENSMUSG00000000049 163.723 2.745 0.5438 5.0479 0 0
ENSMUSG00000000056 1969.6068 0.6117 0.2122 2.8829 0.0039 0.0139

Description of the columns:

  • The rownames correspond to the gene identifiers.
  • baseMean: Average normalised expression of the gene across all samples, after correcting for sequencing depth (size factors). It informs about how much the gene is expressed overall.
  • log2FoldChange: log2 of the ratio of the average expression of samples from the condition WT to samples from the condition KO. Positive values correspond to a higher expression in the condition WT, and conversely. A value of 1 means that the gene is twice more expressed in the condition WT compared to the condition KO.
  • lfcSE: Standard error of the log2 fold change. It reflects the uncertainty in the estimated fold change.
  • stat: Test statistic, corresponding to the ratio between log2FoldChange and lfcSE.
  • pvalue: Raw p-value assessing if the log2 fold change is different from 0 or not, meaning if the gene is differentially expressed or not.
  • padj: Adjusted p-value, corrected for multiple testing using the Benjamini-Hochberg method. It controls the false discovery rate and must be used to decide significance.

7.1.2 p-value distribution

Among 46500 genes, there are 31876 (69%) genes with an available p-value and 14624 (31%) with a non-available (NA) p-value. Below is the summary for the available values and a figure depicting the distribution of the raw p-values.

Value
Min. 0.0000000
1st Qu. 0.0173555
Median 0.3348020
Mean 0.3859297
3rd Qu. 0.7164153
Max. 0.9999943
ggplot2::ggplot(res, aes(x = pvalue)) +
  ggplot2::geom_histogram(col = "black", fill = "#A20F58", bins = 50) +
  # Style
  ggplot2::labs(x = "Raw p-value",
                y = "Number of genes",
                title = "Raw p-value distribution",
                subtitle = comparison_subtitle) +
  ggplot2::scale_y_continuous(expand = c(0,0)) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

7.1.3 Adjusted p-value distribution

Among 46500 genes, there are 22227 (48%) genes with an available adjusted p-value and 24273 (52%) with a non-available (NA) adjusted p-value. Below is the summary for the available values and a figure depicting the distribution of the adjusted p-values.

Value
Min. 0.0000000
1st Qu. 0.0062925
Median 0.1854730
Mean 0.3184019
3rd Qu. 0.6065964
Max. 0.9999943
ggplot2::ggplot(res, aes(x = padj)) +
  ggplot2::geom_histogram(col = "black", fill = "#77B241", bins = 50) +
  # Style
  ggplot2::labs(x = "Adjusted p-value",
                y = "Number of genes",
                title = "Adjusted p-value distribution",
                subtitle = comparison_subtitle) +
  ggplot2::scale_y_continuous(expand = c(0,0)) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

7.1.4 MA plot

The MA plot represents the log2 fold change of each gene (dot) as a function of their mean normalised expression value across all samples. Dots are colored based on the significance of the statistical test, using a significant threshold of 0.05 for the adjusted p-value (padj).

ggplot2::ggplot(res, aes(x = baseMean, y = log2FoldChange,
                         col = padj < padjLimit)) +
  ggplot2::geom_point(size = 1) +
  ggplot2::geom_hline(yintercept = 0, lty = 1, col = "lightgray") +
  ggplot2::scale_color_manual(name = paste0("padj < ", padjLimit),
                              breaks = c(FALSE, TRUE, NA),
                              values = c("black", "#D32A24", "lightgray"),
                              na.value = "lightgray") +
  # Style
  ggplot2::scale_x_log10() +
  ggplot2::labs(x = "Mean of normalised counts",
                y = "Log_2 fold change",
                title = "MA plot",
                subtitle = comparison_subtitle) +
  ggplot2::theme_minimal() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

For more details, please visit:

https://hbctraining.github.io/DGE_workshop/lessons/05_DGE_DESeq2_analysis2.html

7.1.5 Number of differentially expressed genes

We compute the number of significantly differentially expressed genes for varying significant adjusted p-value thresholds.

signif_limit_vec = c(1e-6, 1e-5, 1e-4, 1e-3, 0.01, 0.05)
nb_DE_genes = lapply(signif_limit_vec, FUN = function(signif_limit) {
  nb_genes = res %>%
    # Differentially expressed genes
    dplyr::filter(abs(log2FoldChange) > 1) %>%
    # Significantly, based on the threshold
    dplyr::filter(padj < signif_limit) %>%
    # Number of genes
    nrow()
  
  return(nb_genes)
}) %>% unlist() %>%
  data.frame(signif_limit = signif_limit_vec,
             nb_genes = .)

The barplot indicates the number of significantly differentially expressed genes according to the significant adjusted p-value threshold. Only genes with a log2 fold change greater than 1 or smaller than -1 are considered.

Considering an adjusted p-value threshold of 1e-6, 1929 genes are significantly differentially expressed. When considering an adjusted p-value threshold of 0.05, 4478 genes are significantly differentially expressed, meaning, 2549 additional genes.

p = ggplot2::ggplot(nb_DE_genes, aes(x = signif_limit, y = nb_genes)) +
  ggplot2::geom_bar(stat = "identity", col = "black", fill = "#A3ADB8") +
  ggplot2::geom_label(aes(x = signif_limit,
                          y = nb_genes + 0.03*nb_DE_genes[6, 2],
                          label = nb_genes),
                      linewidth = 0, size = 5, fill = NA) +
  # Style
  ggplot2::scale_x_log10(breaks = signif_limit_vec,
                         labels = signif_limit_vec) +
  ggplot2::labs(x = "Adjusted p-value threshold",
                y = "Number of differentially expressed genes",
                title = "Impact of the adjusted p-value threshold on the number of differentially expressed genes",
                subtitle = comparison_subtitle) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

if (nb_DE_genes[6, 2] != 0) {
  p = p +
    ggplot2::scale_y_continuous(expand = c(0,0),
                                limits = c(0, 1.1*nb_DE_genes[6, 2]))
}

p

7.1.6 Volcano plot

The volcano plot represents the adjusted p-value as a function of the log2 fold change between condition WT and condition KO. We label a maximum of 10 genes in both directions. We identify such genes.

genes_up = res %>%
  dplyr::filter(padj < padjLimit) %>%
  dplyr::filter(log2FoldChange > 0) %>%
  dplyr::top_n(n = 10, wt = log2FoldChange) %>%
  dplyr::pull(ID)

genes_dn = res %>%
  dplyr::filter(padj < padjLimit) %>%
  dplyr::filter(log2FoldChange < 0) %>%
  dplyr::top_n(n = 10, wt = -log2FoldChange) %>%
  dplyr::pull(ID)

non_zero_min = min(res$padj[res$padj != 0], na.rm = TRUE)

We label 10 significantly over-regulated and 10 significantly down-regulated genes, considering an adjusted p-value of 0.05.

res = res %>%
  dplyr::mutate(symbol = correspIdSymbol[ID, 2])

p = res %>%
  dplyr::filter(!is.na(padj)) %>%
  ggplot2::ggplot(.,
                  aes(x = log2FoldChange,
                      y = ifelse(padj == 0, # -log10(0) = Inf
                                 yes = -log10(non_zero_min),
                                 no = -log10(padj)),
                      label = symbol,
                      text = paste0(symbol, "<br>",
                                    "ID: ", ID, "<br>",
                                    "log_2 (fold change): ", round(log2FoldChange, 2), "<br>",
                                    "adjusted p-value: ", round(padj, 4))))
p = p +
  # Lines
  ggplot2::geom_hline(yintercept = 0, col = "lightgray") +
  ggplot2::geom_hline(yintercept = -log10(padjLimit), lty = 2, col = "#A3ADB8") +
  ggplot2::geom_vline(xintercept = log2FCLimit, lty = 2, col = "#A3ADB8") +
  ggplot2::geom_vline(xintercept = -log2FCLimit, lty = 2, col = "#A3ADB8") +
  # Points
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(padj >= padjLimit),
                      mapping = aes(col = "Others")) +
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(log2FoldChange > -log2FCLimit &
                                        log2FoldChange < log2FCLimit),
                      mapping = aes(col = "Others")) +
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(padj < padjLimit) %>%
                        dplyr::filter(log2FoldChange >= log2FCLimit),
                      mapping = aes(col = "Significantly up-regulated")) +
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(padj < padjLimit) %>%
                        dplyr::filter(log2FoldChange <= -log2FCLimit),
                      mapping = aes(col = "Significantly down-regulated")) +
  ggplot2::scale_color_manual(name = "genes",
                              values = c("#D32A24", "#224897", "#A3ADB8"),
                              breaks = c("Significantly up-regulated",
                                         "Significantly down-regulated",
                                         "Others")) +
  # Labels
  ggplot2::labs(x = "log_2 (fold change)",
                y = "-log10 (adjusted p-value)",
                title = "Volcano plot",
                subtitle = comparison_subtitle) +
  # Style
  ggplot2::theme_minimal() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 legend.position = "bottom",
                 legend.direction = "horizontal",
                 legend.text = element_text(margin = margin(l = 0.5)),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

Static

p + ggrepel::geom_label_repel(data = ~ dplyr::filter(., ID %in% c(genes_up, genes_dn)),
                              fill = NA, label.size = 0,
                              size = 3, max.overlaps = 50)

Interactive

p = p + ggplot2::theme(legend.position = "none")

plotly::ggplotly(p, tooltip = "text") %>%
  plotly::layout(paper_bgcolor = "rgba(0,0,0,0)",
                 plot_bgcolor  = "rgba(0,0,0,0)")

7.1.7 Heatmap

We draw a heatmap for the 20 gene(s) presented above. We compute Z-scores from the normalised count matrix and visualise part of the resulting table, meaning the 20 gene(s) and their Z-scores in two samples. The Z-scores are only used for visualisation. The Z-score matrix is not saved.

samples_of_interest = c(comparison_samples_group1,
                        comparison_samples_group2)

# if-else in case on 0 differential expression gene
nb_genes = length(c(genes_up, genes_dn))
if (nb_genes < 1) {
  zscore_mat = matrix(data = 0,
                      nrow = 1,
                      ncol = length(samples_of_interest),
                      dimnames = list(c(""),
                                      samples_of_interest))
} else {
  zscore_mat = DESeq2::counts(dds, normalized = TRUE)[c(genes_up, genes_dn), samples_of_interest]
  
  # Special case if there is only one gene: vector instead of matrix
  if (nb_genes == 1) {
    zscore_mat = matrix(zscore_mat,
                        nrow = nb_genes,
                        dimnames = list(row_names = c(genes_up, genes_dn),
                                        col_names = samples_of_interest))
  }
  
  # Z-score
  zscore_mat = zscore_mat %>%
    t() %>% scale() %>% t()
}
# The code below is compatible with 1 gene
# Otherwise, zscore_mat[, c(1:2)] would have sufficed and been much simpler
matrix(zscore_mat[, c(1:2)],
       nrow = max(1, nb_genes),
       dimnames = list(row_names = c(genes_up, genes_dn),
                       col_names = samples_of_interest[c(1:2)])) %>%
  as.data.frame() %>%
  dplyr::mutate(Symbol = correspIdSymbol[rownames(.), 2]) %>%
  dplyr::relocate(Symbol) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
Symbol WT1 WT2
ENSMUSG00000025189 Cnnm1 -0.4180154 2.0410628
ENSMUSG00000026535 Ifi202b 0.8613316 0.5735289
ENSMUSG00000028148 Them5 0.3255144 0.9880309
ENSMUSG00000030500 Slc17a6 -0.4099415 2.0412393
ENSMUSG00000031376 Atp2b3 -0.4141087 2.0412058
ENSMUSG00000032517 Mobp 0.1639095 0.4006232
ENSMUSG00000032769 Trpa1 -0.4128075 2.0412010
ENSMUSG00000050917 Fgf4 0.7584254 1.1950946
ENSMUSG00000056673 Kdm5d -0.8875735 0.5289264
ENSMUSG00000058975 Kcnc1 -0.4164269 2.0411670
ENSMUSG00000005994 Tyrp1 -0.8654099 -0.8595989
ENSMUSG00000015437 Gzmb -0.5751065 -0.5751065
ENSMUSG00000028222 Calb1 -0.8673990 -0.8173714
ENSMUSG00000043549 Fam90a1b -0.8201627 -0.8085855
ENSMUSG00000060882 Kcnd2 -0.9140203 -0.8914250
ENSMUSG00000075217 Fads2b -0.5909259 -0.5912369
ENSMUSG00000093400 ENSMUSG00000093400 -0.7520888 -0.7509161
ENSMUSG00000098743 ENSMUSG00000098743 1.3816144 -0.6238464
ENSMUSG00000099312 ENSMUSG00000099312 0.3777270 -0.7311925
ENSMUSG00000099372 ENSMUSG00000099372 -0.4082483 -0.4082483

In case a gene is expressed in a single sample, the normalisation outputs NaN values. We remove them.

NaN_rows = (which(rowSums(is.nan(zscore_mat)) > 0))

if (length(NaN_rows) > 0) {
  nb_genes = nb_genes - length(NaN_rows)
  zscore_mat = zscore_mat[-(NaN_rows), ]
}

On the heatmap, each gene is one row and each sample is one column. Samples are annotated for their condition. The sample dendrogram is calculated based on the distances between samples considering only the genes represented on the heatmap. The condition associated to each sample is not considered by the method (unsupervised method). The genes dendrogram is calculated based on the Z-score of that gene among all samples.

# Heatmap top annotation
sub_design = design %>%
  dplyr::select(RepTechGroup, Condition) %>%
  dplyr::filter(RepTechGroup %in% samples_of_interest) %>%
  unique() %>%
  `rownames<-`(.$RepTechGroup)

if (complexMode) {
  sub_design = sub_design %>%
    # Add the complex condition
    `colnames<-`(c("RepTechGroup", "SubCondition")) %>%
    dplyr::mutate(Condition = ifelse(RepTechGroup %in% comparison_samples_group1,
                                     yes = condition1,
                                     no = condition2)) %>%
    dplyr::select(Condition, SubCondition)
  sub_design = sub_design[samples_of_interest, ] # re-order
  
  # Annotation
  ha_top = ComplexHeatmap::HeatmapAnnotation(
    df = sub_design,
    col = list(Condition = setNames(nm = c(condition1, condition2),
                                    c("#3AA5C1", "#FF6800")),
               SubCondition = colors_condition[unique(sub_design$SubCondition)]),
    na_col = "#F7F7F7")
} else {
  # Re-order
  sub_design = sub_design %>%
    dplyr::select(Condition)
  sub_design = sub_design[samples_of_interest, ] # re-order
  
  # Annotation
  ha_top = ComplexHeatmap::HeatmapAnnotation(
    Condition = sub_design,
    col = list(Condition = colors_condition[c(condition1, condition2)]),
    na_col = "#F7F7F7")
}

# Heatmap title
if (nb_genes > 1) {
  ht_title = paste0("Heatmap of ", nb_genes,
                    " differentially expressed genes\n",
                    comparison_subtitle)
} else {
  ht_title = paste0("Heatmap of ", nb_genes,
                    " differentially expressed gene\n",
                    comparison_subtitle)
}

# Rownames
if (!is.null(correspPath)) {
  row_labels = correspIdSymbol[rownames(zscore_mat), 2]
} else {
  row_labels = rownames(zscore_mat)
}

# Heatmap main
ht = ComplexHeatmap::Heatmap(
  as.matrix(zscore_mat),
  # Expression colors
  col = expressionColors(range(zscore_mat, na.rm = TRUE)),
  heatmap_legend_param = list(title = "Z-score"),
  # Annotation
  top_annotation = ha_top,
  # Sample order
  cluster_columns = TRUE,
  # Gene order
  cluster_rows = TRUE,
  row_labels = row_labels,
  # Visual aspect
  show_heatmap_legend = TRUE,
  border = TRUE,
  use_raster = FALSE,
  # Title
  column_title = ht_title)

# Draw
ComplexHeatmap::draw(ht,
                     merge_legend = TRUE,
                     heatmap_legend_side = "left",
                     annotation_legend_side = "left",
                     padding = unit(c(samples_padding, 2, 2, 5), "mm"),
                     background = "transparent")

Note: The clustering of samples may change, depending on the genes that are considered.

7.1.8 Save

We re-order the genes based on the adjusted p-value and edit the column names for enhanced readability. We display the 6 first rows of this table.

res = res %>%
  dplyr::mutate(dispersions.dds. = DESeq2::dispersions(dds)) %>%
  dplyr::arrange(padj) %>%
  `colnames<-`( c("ID",
                  "baseMean",
                  paste0("log2foldchange ", condition1, " vs ", condition2),
                  "standard error",
                  "Wald statistic",
                  "Wald test p-value",
                  "BH adjusted p-values",
                  "dispersions.dds.") )

# Display
na.omit(res[, -1]) %>%
  head() %>%
  round(., 4) %>%
  rbind.data.frame(., "..." = rep("...", ncol(.))) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
baseMean log2foldchange WT vs KO standard error Wald statistic Wald test p-value BH adjusted p-values dispersions.dds.
ENSMUSG00000000223 69095.1118 8.9428 0.2091 42.7642 0 0 0.0263
ENSMUSG00000021061 11643.2985 7.2656 0.1713 42.4179 0 0 0.0159
ENSMUSG00000033361 24278.6244 6.2849 0.1375 45.7122 0 0 0.0114
ENSMUSG00000059213 10761.636 9.4824 0.2531 37.4713 0 0 0.0258
ENSMUSG00000022456 5115.165 6.8379 0.1877 36.4243 0 0 0.0172
ENSMUSG00000056569 2192423.4899 5.056 0.143 35.3458 0 0 0.0131

We save the table as a TSV file.

filename = paste0(prefix, projectName, "-diffana_",
                  condition1, "_vs_", condition2,".tsv")

saveTable(dataframe = res,
          fileName = filename)

Output file name:
./project_GSE107401/deseq2_GSE107401-diffana_WT_vs_KO.tsv


7.2 Foo vs Bar

Details:

  • number of samples in the Foo condition: 3

  • number of samples in the Bar condition: 3

  • contrast matrix to extract the results:

    Intercept

    Condition_WT_vs_KO

    FooBar_FOO_vs_BAR

    ConditionWT.FooBarFOO

    0

    0

    1

    0.5

7.2.1 Results table

We extract the dataframe containing the differential analysis results between condition Foo and condition Bar. We display the 6 first rows of this table. The complete table is saved as a TSV file (quick access).

res = DESeq2::results(dds,
                      contrast = comparison_contrast) %>%
  as.data.frame() %>%
  dplyr::mutate(ID = rownames(.)) %>%
  dplyr::relocate(ID)

# Display
na.omit(res[, -1]) %>%
  head() %>%
  round(., 4) %>%
  rbind.data.frame(., "..." = rep("...", ncol(.))) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
baseMean log2FoldChange lfcSE stat pvalue padj
ENSMUSG00000000001 8545.2007 -0.086 0.1486 -0.5789 0.5627 1
ENSMUSG00000000028 588.143 -0.1714 0.234 -0.7323 0.464 1
ENSMUSG00000000031 67477.2131 -0.115 0.2478 -0.4642 0.6425 1
ENSMUSG00000000037 98.8549 -0.248 0.5472 -0.4532 0.6504 1
ENSMUSG00000000049 163.723 -0.2923 0.5438 -0.5376 0.5909 1
ENSMUSG00000000056 1969.6068 0.2708 0.2122 1.2762 0.2019 1

Description of the columns:

  • The rownames correspond to the gene identifiers.
  • baseMean: Average normalised expression of the gene across all samples, after correcting for sequencing depth (size factors). It informs about how much the gene is expressed overall.
  • log2FoldChange: log2 of the ratio of the average expression of samples from the condition Foo to samples from the condition Bar. Positive values correspond to a higher expression in the condition Foo, and conversely. A value of 1 means that the gene is twice more expressed in the condition Foo compared to the condition Bar.
  • lfcSE: Standard error of the log2 fold change. It reflects the uncertainty in the estimated fold change.
  • stat: Test statistic, corresponding to the ratio between log2FoldChange and lfcSE.
  • pvalue: Raw p-value assessing if the log2 fold change is different from 0 or not, meaning if the gene is differentially expressed or not.
  • padj: Adjusted p-value, corrected for multiple testing using the Benjamini-Hochberg method. It controls the false discovery rate and must be used to decide significance.

7.2.2 p-value distribution

Among 46500 genes, there are 31876 (69%) genes with an available p-value and 14624 (31%) with a non-available (NA) p-value. Below is the summary for the available values and a figure depicting the distribution of the raw p-values.

Value
Min. 0.0000000
1st Qu. 0.5330929
Median 0.7307890
Mean 0.6789622
3rd Qu. 0.8681502
Max. 0.9999785
ggplot2::ggplot(res, aes(x = pvalue)) +
  ggplot2::geom_histogram(col = "black", fill = "#A20F58", bins = 50) +
  # Style
  ggplot2::labs(x = "Raw p-value",
                y = "Number of genes",
                title = "Raw p-value distribution",
                subtitle = comparison_subtitle) +
  ggplot2::scale_y_continuous(expand = c(0,0)) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

7.2.3 Adjusted p-value distribution

Among 46500 genes, there are 27051 (58%) genes with an available adjusted p-value and 19449 (42%) with a non-available (NA) adjusted p-value. Below is the summary for the available values and a figure depicting the distribution of the adjusted p-values.

Value
Min. 0.0000000
1st Qu. 0.9999673
Median 0.9999673
Mean 0.9992233
3rd Qu. 0.9999673
Max. 0.9999785
ggplot2::ggplot(res, aes(x = padj)) +
  ggplot2::geom_histogram(col = "black", fill = "#77B241", bins = 50) +
  # Style
  ggplot2::labs(x = "Adjusted p-value",
                y = "Number of genes",
                title = "Adjusted p-value distribution",
                subtitle = comparison_subtitle) +
  ggplot2::scale_y_continuous(expand = c(0,0)) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

7.2.4 MA plot

The MA plot represents the log2 fold change of each gene (dot) as a function of their mean normalised expression value across all samples. Dots are colored based on the significance of the statistical test, using a significant threshold of 0.05 for the adjusted p-value (padj).

ggplot2::ggplot(res, aes(x = baseMean, y = log2FoldChange,
                         col = padj < padjLimit)) +
  ggplot2::geom_point(size = 1) +
  ggplot2::geom_hline(yintercept = 0, lty = 1, col = "lightgray") +
  ggplot2::scale_color_manual(name = paste0("padj < ", padjLimit),
                              breaks = c(FALSE, TRUE, NA),
                              values = c("black", "#D32A24", "lightgray"),
                              na.value = "lightgray") +
  # Style
  ggplot2::scale_x_log10() +
  ggplot2::labs(x = "Mean of normalised counts",
                y = "Log_2 fold change",
                title = "MA plot",
                subtitle = comparison_subtitle) +
  ggplot2::theme_minimal() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

For more details, please visit:

https://hbctraining.github.io/DGE_workshop/lessons/05_DGE_DESeq2_analysis2.html

7.2.5 Number of differentially expressed genes

We compute the number of significantly differentially expressed genes for varying significant adjusted p-value thresholds.

signif_limit_vec = c(1e-6, 1e-5, 1e-4, 1e-3, 0.01, 0.05)
nb_DE_genes = lapply(signif_limit_vec, FUN = function(signif_limit) {
  nb_genes = res %>%
    # Differentially expressed genes
    dplyr::filter(abs(log2FoldChange) > 1) %>%
    # Significantly, based on the threshold
    dplyr::filter(padj < signif_limit) %>%
    # Number of genes
    nrow()
  
  return(nb_genes)
}) %>% unlist() %>%
  data.frame(signif_limit = signif_limit_vec,
             nb_genes = .)

The barplot indicates the number of significantly differentially expressed genes according to the significant adjusted p-value threshold. Only genes with a log2 fold change greater than 1 or smaller than -1 are considered.

Considering an adjusted p-value threshold of 1e-6, 2 genes are significantly differentially expressed. When considering an adjusted p-value threshold of 0.05, 9 genes are significantly differentially expressed, meaning, 7 additional genes.

p = ggplot2::ggplot(nb_DE_genes, aes(x = signif_limit, y = nb_genes)) +
  ggplot2::geom_bar(stat = "identity", col = "black", fill = "#A3ADB8") +
  ggplot2::geom_label(aes(x = signif_limit,
                          y = nb_genes + 0.03*nb_DE_genes[6, 2],
                          label = nb_genes),
                      linewidth = 0, size = 5, fill = NA) +
  # Style
  ggplot2::scale_x_log10(breaks = signif_limit_vec,
                         labels = signif_limit_vec) +
  ggplot2::labs(x = "Adjusted p-value threshold",
                y = "Number of differentially expressed genes",
                title = "Impact of the adjusted p-value threshold on the number of differentially expressed genes",
                subtitle = comparison_subtitle) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

if (nb_DE_genes[6, 2] != 0) {
  p = p +
    ggplot2::scale_y_continuous(expand = c(0,0),
                                limits = c(0, 1.1*nb_DE_genes[6, 2]))
}

p

7.2.6 Volcano plot

The volcano plot represents the adjusted p-value as a function of the log2 fold change between condition Foo and condition Bar. We label a maximum of 10 genes in both directions. We identify such genes.

genes_up = res %>%
  dplyr::filter(padj < padjLimit) %>%
  dplyr::filter(log2FoldChange > 0) %>%
  dplyr::top_n(n = 10, wt = log2FoldChange) %>%
  dplyr::pull(ID)

genes_dn = res %>%
  dplyr::filter(padj < padjLimit) %>%
  dplyr::filter(log2FoldChange < 0) %>%
  dplyr::top_n(n = 10, wt = -log2FoldChange) %>%
  dplyr::pull(ID)

non_zero_min = min(res$padj[res$padj != 0], na.rm = TRUE)

We label 5 significantly over-regulated and 8 significantly down-regulated genes, considering an adjusted p-value of 0.05.

res = res %>%
  dplyr::mutate(symbol = correspIdSymbol[ID, 2])

p = res %>%
  dplyr::filter(!is.na(padj)) %>%
  ggplot2::ggplot(.,
                  aes(x = log2FoldChange,
                      y = ifelse(padj == 0, # -log10(0) = Inf
                                 yes = -log10(non_zero_min),
                                 no = -log10(padj)),
                      label = symbol,
                      text = paste0(symbol, "<br>",
                                    "ID: ", ID, "<br>",
                                    "log_2 (fold change): ", round(log2FoldChange, 2), "<br>",
                                    "adjusted p-value: ", round(padj, 4))))
p = p +
  # Lines
  ggplot2::geom_hline(yintercept = 0, col = "lightgray") +
  ggplot2::geom_hline(yintercept = -log10(padjLimit), lty = 2, col = "#A3ADB8") +
  ggplot2::geom_vline(xintercept = log2FCLimit, lty = 2, col = "#A3ADB8") +
  ggplot2::geom_vline(xintercept = -log2FCLimit, lty = 2, col = "#A3ADB8") +
  # Points
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(padj >= padjLimit),
                      mapping = aes(col = "Others")) +
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(log2FoldChange > -log2FCLimit &
                                        log2FoldChange < log2FCLimit),
                      mapping = aes(col = "Others")) +
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(padj < padjLimit) %>%
                        dplyr::filter(log2FoldChange >= log2FCLimit),
                      mapping = aes(col = "Significantly up-regulated")) +
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(padj < padjLimit) %>%
                        dplyr::filter(log2FoldChange <= -log2FCLimit),
                      mapping = aes(col = "Significantly down-regulated")) +
  ggplot2::scale_color_manual(name = "genes",
                              values = c("#D32A24", "#224897", "#A3ADB8"),
                              breaks = c("Significantly up-regulated",
                                         "Significantly down-regulated",
                                         "Others")) +
  # Labels
  ggplot2::labs(x = "log_2 (fold change)",
                y = "-log10 (adjusted p-value)",
                title = "Volcano plot",
                subtitle = comparison_subtitle) +
  # Style
  ggplot2::theme_minimal() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 legend.position = "bottom",
                 legend.direction = "horizontal",
                 legend.text = element_text(margin = margin(l = 0.5)),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

Static

p + ggrepel::geom_label_repel(data = ~ dplyr::filter(., ID %in% c(genes_up, genes_dn)),
                              fill = NA, label.size = 0,
                              size = 3, max.overlaps = 50)

Interactive

p = p + ggplot2::theme(legend.position = "none")

plotly::ggplotly(p, tooltip = "text") %>%
  plotly::layout(paper_bgcolor = "rgba(0,0,0,0)",
                 plot_bgcolor  = "rgba(0,0,0,0)")

7.2.7 Heatmap

We draw a heatmap for the 13 gene(s) presented above. We compute Z-scores from the normalised count matrix and visualise part of the resulting table, meaning the 13 gene(s) and their Z-scores in two samples. The Z-scores are only used for visualisation. The Z-score matrix is not saved.

samples_of_interest = c(comparison_samples_group1,
                        comparison_samples_group2)

# if-else in case on 0 differential expression gene
nb_genes = length(c(genes_up, genes_dn))
if (nb_genes < 1) {
  zscore_mat = matrix(data = 0,
                      nrow = 1,
                      ncol = length(samples_of_interest),
                      dimnames = list(c(""),
                                      samples_of_interest))
} else {
  zscore_mat = DESeq2::counts(dds, normalized = TRUE)[c(genes_up, genes_dn), samples_of_interest]
  
  # Special case if there is only one gene: vector instead of matrix
  if (nb_genes == 1) {
    zscore_mat = matrix(zscore_mat,
                        nrow = nb_genes,
                        dimnames = list(row_names = c(genes_up, genes_dn),
                                        col_names = samples_of_interest))
  }
  
  # Z-score
  zscore_mat = zscore_mat %>%
    t() %>% scale() %>% t()
}
# The code below is compatible with 1 gene
# Otherwise, zscore_mat[, c(1:2)] would have sufficed and been much simpler
matrix(zscore_mat[, c(1:2)],
       nrow = max(1, nb_genes),
       dimnames = list(row_names = c(genes_up, genes_dn),
                       col_names = samples_of_interest[c(1:2)])) %>%
  as.data.frame() %>%
  dplyr::mutate(Symbol = correspIdSymbol[rownames(.), 2]) %>%
  dplyr::relocate(Symbol) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
Symbol KO1 WT1
ENSMUSG00000024114 Prss41 2.0190918 -0.4558731
ENSMUSG00000027737 Slc7a11 0.1393530 0.5943415
ENSMUSG00000032036 Kirrel3 1.8876205 -0.5230548
ENSMUSG00000057262 ENSMUSG00000057262 1.9088641 0.1405817
ENSMUSG00000098915 ENSMUSG00000098915 1.8226714 0.3025388
ENSMUSG00000024121 Atp6v0c -1.9960296 0.0971032
ENSMUSG00000034681 Rnps1 -0.9654174 -0.4685995
ENSMUSG00000038141 Tmem181a -1.8910002 0.3711205
ENSMUSG00000044533 Rps2 -0.5564445 -0.6558642
ENSMUSG00000085666 ENSMUSG00000085666 -0.2042498 -0.6469808
ENSMUSG00000099372 ENSMUSG00000099372 -0.4082483 -0.4082483
ENSMUSG00000105703 Gm43305 -0.9386705 -0.7051199
ENSMUSG00000107169 ENSMUSG00000107169 -1.9975851 0.0430704

In case a gene is expressed in a single sample, the normalisation outputs NaN values. We remove them.

NaN_rows = (which(rowSums(is.nan(zscore_mat)) > 0))

if (length(NaN_rows) > 0) {
  nb_genes = nb_genes - length(NaN_rows)
  zscore_mat = zscore_mat[-(NaN_rows), ]
}

On the heatmap, each gene is one row and each sample is one column. Samples are annotated for their condition. The sample dendrogram is calculated based on the distances between samples considering only the genes represented on the heatmap. The condition associated to each sample is not considered by the method (unsupervised method). The genes dendrogram is calculated based on the Z-score of that gene among all samples.

# Heatmap top annotation
sub_design = design %>%
  dplyr::select(RepTechGroup, Condition) %>%
  dplyr::filter(RepTechGroup %in% samples_of_interest) %>%
  unique() %>%
  `rownames<-`(.$RepTechGroup)

if (complexMode) {
  sub_design = sub_design %>%
    # Add the complex condition
    `colnames<-`(c("RepTechGroup", "SubCondition")) %>%
    dplyr::mutate(Condition = ifelse(RepTechGroup %in% comparison_samples_group1,
                                     yes = condition1,
                                     no = condition2)) %>%
    dplyr::select(Condition, SubCondition)
  sub_design = sub_design[samples_of_interest, ] # re-order
  
  # Annotation
  ha_top = ComplexHeatmap::HeatmapAnnotation(
    df = sub_design,
    col = list(Condition = setNames(nm = c(condition1, condition2),
                                    c("#3AA5C1", "#FF6800")),
               SubCondition = colors_condition[unique(sub_design$SubCondition)]),
    na_col = "#F7F7F7")
} else {
  # Re-order
  sub_design = sub_design %>%
    dplyr::select(Condition)
  sub_design = sub_design[samples_of_interest, ] # re-order
  
  # Annotation
  ha_top = ComplexHeatmap::HeatmapAnnotation(
    Condition = sub_design,
    col = list(Condition = colors_condition[c(condition1, condition2)]),
    na_col = "#F7F7F7")
}

# Heatmap title
if (nb_genes > 1) {
  ht_title = paste0("Heatmap of ", nb_genes,
                    " differentially expressed genes\n",
                    comparison_subtitle)
} else {
  ht_title = paste0("Heatmap of ", nb_genes,
                    " differentially expressed gene\n",
                    comparison_subtitle)
}

# Rownames
if (!is.null(correspPath)) {
  row_labels = correspIdSymbol[rownames(zscore_mat), 2]
} else {
  row_labels = rownames(zscore_mat)
}

# Heatmap main
ht = ComplexHeatmap::Heatmap(
  as.matrix(zscore_mat),
  # Expression colors
  col = expressionColors(range(zscore_mat, na.rm = TRUE)),
  heatmap_legend_param = list(title = "Z-score"),
  # Annotation
  top_annotation = ha_top,
  # Sample order
  cluster_columns = TRUE,
  # Gene order
  cluster_rows = TRUE,
  row_labels = row_labels,
  # Visual aspect
  show_heatmap_legend = TRUE,
  border = TRUE,
  use_raster = FALSE,
  # Title
  column_title = ht_title)

# Draw
ComplexHeatmap::draw(ht,
                     merge_legend = TRUE,
                     heatmap_legend_side = "left",
                     annotation_legend_side = "left",
                     padding = unit(c(samples_padding, 2, 2, 5), "mm"),
                     background = "transparent")

Note: The clustering of samples may change, depending on the genes that are considered.

7.2.8 Save

We re-order the genes based on the adjusted p-value and edit the column names for enhanced readability. We display the 6 first rows of this table.

res = res %>%
  dplyr::mutate(dispersions.dds. = DESeq2::dispersions(dds)) %>%
  dplyr::arrange(padj) %>%
  `colnames<-`( c("ID",
                  "baseMean",
                  paste0("log2foldchange ", condition1, " vs ", condition2),
                  "standard error",
                  "Wald statistic",
                  "Wald test p-value",
                  "BH adjusted p-values",
                  "dispersions.dds.") )

# Display
na.omit(res[, -1]) %>%
  head() %>%
  round(., 4) %>%
  rbind.data.frame(., "..." = rep("...", ncol(.))) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
baseMean log2foldchange Foo vs Bar standard error Wald statistic Wald test p-value BH adjusted p-values dispersions.dds.
ENSMUSG00000027737 1437.8789 1.8388 0.2242 8.2019 0 0 0.0298
ENSMUSG00000024121 3418.0238 -1.0826 0.151 -7.1697 0 0 0.0141
ENSMUSG00000034681 2301.8633 -0.8688 0.1698 -5.1175 0 0.0021 0.0178
ENSMUSG00000099372 55.1531 -21.9482 4.2676 -5.1429 0 0.0021 10
ENSMUSG00000098915 488.3315 1.8767 0.3924 4.7828 0 0.0094 0.0947
ENSMUSG00000044533 12721.196 -0.8078 0.1749 -4.6187 0 0.0136 0.0195

We save the table as a TSV file.

filename = paste0(prefix, projectName, "-diffana_",
                  condition1, "_vs_", condition2,".tsv")

saveTable(dataframe = res,
          fileName = filename)

Output file name:
./project_GSE107401/deseq2_GSE107401-diffana_Foo_vs_Bar.tsv


7.3 FooWT vs FooKO

Details:

  • number of samples in the FooWT condition: 2

  • number of samples in the FooKO condition: 1

  • contrast matrix to extract the results:

    Intercept

    Condition_WT_vs_KO

    FooBar_FOO_vs_BAR

    ConditionWT.FooBarFOO

    0

    1

    0

    1

7.3.1 Results table

We extract the dataframe containing the differential analysis results between condition FooWT and condition FooKO. We display the 6 first rows of this table. The complete table is saved as a TSV file (quick access).

res = DESeq2::results(dds,
                      contrast = comparison_contrast) %>%
  as.data.frame() %>%
  dplyr::mutate(ID = rownames(.)) %>%
  dplyr::relocate(ID)

# Display
na.omit(res[, -1]) %>%
  head() %>%
  round(., 4) %>%
  rbind.data.frame(., "..." = rep("...", ncol(.))) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
baseMean log2FoldChange lfcSE stat pvalue padj
ENSMUSG00000000001 8545.2007 -0.0583 0.2101 -0.2774 0.7814 0.8819
ENSMUSG00000000028 588.143 -1.5886 0.3273 -4.8539 0 0
ENSMUSG00000000031 67477.2131 0.4082 0.3504 1.165 0.244 0.4287
ENSMUSG00000000037 98.8549 0.5252 0.7744 0.6782 0.4976 0.6776
ENSMUSG00000000049 163.723 2.5838 0.7744 3.3363 0.0008 0.0056
ENSMUSG00000000056 1969.6068 0.4757 0.2997 1.5873 0.1124 0.2524

Description of the columns:

  • The rownames correspond to the gene identifiers.
  • baseMean: Average normalised expression of the gene across all samples, after correcting for sequencing depth (size factors). It informs about how much the gene is expressed overall.
  • log2FoldChange: log2 of the ratio of the average expression of samples from the condition FooWT to samples from the condition FooKO. Positive values correspond to a higher expression in the condition FooWT, and conversely. A value of 1 means that the gene is twice more expressed in the condition FooWT compared to the condition FooKO.
  • lfcSE: Standard error of the log2 fold change. It reflects the uncertainty in the estimated fold change.
  • stat: Test statistic, corresponding to the ratio between log2FoldChange and lfcSE.
  • pvalue: Raw p-value assessing if the log2 fold change is different from 0 or not, meaning if the gene is differentially expressed or not.
  • padj: Adjusted p-value, corrected for multiple testing using the Benjamini-Hochberg method. It controls the false discovery rate and must be used to decide significance.

7.3.2 p-value distribution

Among 46500 genes, there are 31876 (69%) genes with an available p-value and 14624 (31%) with a non-available (NA) p-value. Below is the summary for the available values and a figure depicting the distribution of the raw p-values.

Value
Min. 0.0000000
1st Qu. 0.0717004
Median 0.4445897
Mean 0.4377452
3rd Qu. 0.7578964
Max. 0.9999993
ggplot2::ggplot(res, aes(x = pvalue)) +
  ggplot2::geom_histogram(col = "black", fill = "#A20F58", bins = 50) +
  # Style
  ggplot2::labs(x = "Raw p-value",
                y = "Number of genes",
                title = "Raw p-value distribution",
                subtitle = comparison_subtitle) +
  ggplot2::scale_y_continuous(expand = c(0,0)) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

7.3.3 Adjusted p-value distribution

Among 46500 genes, there are 19814 (43%) genes with an available adjusted p-value and 26686 (57%) with a non-available (NA) adjusted p-value. Below is the summary for the available values and a figure depicting the distribution of the adjusted p-values.

Value
Min. 0.0000000
1st Qu. 0.0473405
Median 0.3307701
Mean 0.3885243
3rd Qu. 0.6975517
Max. 0.9999993
ggplot2::ggplot(res, aes(x = padj)) +
  ggplot2::geom_histogram(col = "black", fill = "#77B241", bins = 50) +
  # Style
  ggplot2::labs(x = "Adjusted p-value",
                y = "Number of genes",
                title = "Adjusted p-value distribution",
                subtitle = comparison_subtitle) +
  ggplot2::scale_y_continuous(expand = c(0,0)) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

7.3.4 MA plot

The MA plot represents the log2 fold change of each gene (dot) as a function of their mean normalised expression value across all samples. Dots are colored based on the significance of the statistical test, using a significant threshold of 0.05 for the adjusted p-value (padj).

ggplot2::ggplot(res, aes(x = baseMean, y = log2FoldChange,
                         col = padj < padjLimit)) +
  ggplot2::geom_point(size = 1) +
  ggplot2::geom_hline(yintercept = 0, lty = 1, col = "lightgray") +
  ggplot2::scale_color_manual(name = paste0("padj < ", padjLimit),
                              breaks = c(FALSE, TRUE, NA),
                              values = c("black", "#D32A24", "lightgray"),
                              na.value = "lightgray") +
  # Style
  ggplot2::scale_x_log10() +
  ggplot2::labs(x = "Mean of normalised counts",
                y = "Log_2 fold change",
                title = "MA plot",
                subtitle = comparison_subtitle) +
  ggplot2::theme_minimal() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

For more details, please visit:

https://hbctraining.github.io/DGE_workshop/lessons/05_DGE_DESeq2_analysis2.html

7.3.5 Number of differentially expressed genes

We compute the number of significantly differentially expressed genes for varying significant adjusted p-value thresholds.

signif_limit_vec = c(1e-6, 1e-5, 1e-4, 1e-3, 0.01, 0.05)
nb_DE_genes = lapply(signif_limit_vec, FUN = function(signif_limit) {
  nb_genes = res %>%
    # Differentially expressed genes
    dplyr::filter(abs(log2FoldChange) > 1) %>%
    # Significantly, based on the threshold
    dplyr::filter(padj < signif_limit) %>%
    # Number of genes
    nrow()
  
  return(nb_genes)
}) %>% unlist() %>%
  data.frame(signif_limit = signif_limit_vec,
             nb_genes = .)

The barplot indicates the number of significantly differentially expressed genes according to the significant adjusted p-value threshold. Only genes with a log2 fold change greater than 1 or smaller than -1 are considered.

Considering an adjusted p-value threshold of 1e-6, 971 genes are significantly differentially expressed. When considering an adjusted p-value threshold of 0.05, 3624 genes are significantly differentially expressed, meaning, 2653 additional genes.

p = ggplot2::ggplot(nb_DE_genes, aes(x = signif_limit, y = nb_genes)) +
  ggplot2::geom_bar(stat = "identity", col = "black", fill = "#A3ADB8") +
  ggplot2::geom_label(aes(x = signif_limit,
                          y = nb_genes + 0.03*nb_DE_genes[6, 2],
                          label = nb_genes),
                      linewidth = 0, size = 5, fill = NA) +
  # Style
  ggplot2::scale_x_log10(breaks = signif_limit_vec,
                         labels = signif_limit_vec) +
  ggplot2::labs(x = "Adjusted p-value threshold",
                y = "Number of differentially expressed genes",
                title = "Impact of the adjusted p-value threshold on the number of differentially expressed genes",
                subtitle = comparison_subtitle) +
  ggplot2::theme_classic() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

if (nb_DE_genes[6, 2] != 0) {
  p = p +
    ggplot2::scale_y_continuous(expand = c(0,0),
                                limits = c(0, 1.1*nb_DE_genes[6, 2]))
}

p

7.3.6 Volcano plot

The volcano plot represents the adjusted p-value as a function of the log2 fold change between condition FooWT and condition FooKO. We label a maximum of 10 genes in both directions. We identify such genes.

genes_up = res %>%
  dplyr::filter(padj < padjLimit) %>%
  dplyr::filter(log2FoldChange > 0) %>%
  dplyr::top_n(n = 10, wt = log2FoldChange) %>%
  dplyr::pull(ID)

genes_dn = res %>%
  dplyr::filter(padj < padjLimit) %>%
  dplyr::filter(log2FoldChange < 0) %>%
  dplyr::top_n(n = 10, wt = -log2FoldChange) %>%
  dplyr::pull(ID)

non_zero_min = min(res$padj[res$padj != 0], na.rm = TRUE)

We label 10 significantly over-regulated and 10 significantly down-regulated genes, considering an adjusted p-value of 0.05.

res = res %>%
  dplyr::mutate(symbol = correspIdSymbol[ID, 2])

p = res %>%
  dplyr::filter(!is.na(padj)) %>%
  ggplot2::ggplot(.,
                  aes(x = log2FoldChange,
                      y = ifelse(padj == 0, # -log10(0) = Inf
                                 yes = -log10(non_zero_min),
                                 no = -log10(padj)),
                      label = symbol,
                      text = paste0(symbol, "<br>",
                                    "ID: ", ID, "<br>",
                                    "log_2 (fold change): ", round(log2FoldChange, 2), "<br>",
                                    "adjusted p-value: ", round(padj, 4))))
p = p +
  # Lines
  ggplot2::geom_hline(yintercept = 0, col = "lightgray") +
  ggplot2::geom_hline(yintercept = -log10(padjLimit), lty = 2, col = "#A3ADB8") +
  ggplot2::geom_vline(xintercept = log2FCLimit, lty = 2, col = "#A3ADB8") +
  ggplot2::geom_vline(xintercept = -log2FCLimit, lty = 2, col = "#A3ADB8") +
  # Points
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(padj >= padjLimit),
                      mapping = aes(col = "Others")) +
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(log2FoldChange > -log2FCLimit &
                                        log2FoldChange < log2FCLimit),
                      mapping = aes(col = "Others")) +
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(padj < padjLimit) %>%
                        dplyr::filter(log2FoldChange >= log2FCLimit),
                      mapping = aes(col = "Significantly up-regulated")) +
  ggplot2::geom_point(data = res %>%
                        dplyr::filter(padj < padjLimit) %>%
                        dplyr::filter(log2FoldChange <= -log2FCLimit),
                      mapping = aes(col = "Significantly down-regulated")) +
  ggplot2::scale_color_manual(name = "genes",
                              values = c("#D32A24", "#224897", "#A3ADB8"),
                              breaks = c("Significantly up-regulated",
                                         "Significantly down-regulated",
                                         "Others")) +
  # Labels
  ggplot2::labs(x = "log_2 (fold change)",
                y = "-log10 (adjusted p-value)",
                title = "Volcano plot",
                subtitle = comparison_subtitle) +
  # Style
  ggplot2::theme_minimal() +
  ggplot2::theme(plot.title = element_text(hjust = 0.5),
                 plot.subtitle = element_text(hjust = 0.5),
                 legend.position = "bottom",
                 legend.direction = "horizontal",
                 legend.text = element_text(margin = margin(l = 0.5)),
                 # Transparent background
                 panel.background = ggplot2::element_blank(),
                 plot.background = ggplot2::element_blank(),
                 legend.background = ggplot2::element_blank())

Static

p + ggrepel::geom_label_repel(data = ~ dplyr::filter(., ID %in% c(genes_up, genes_dn)),
                              fill = NA, label.size = 0,
                              size = 3, max.overlaps = 50)

Interactive

p = p + ggplot2::theme(legend.position = "none")

plotly::ggplotly(p, tooltip = "text") %>%
  plotly::layout(paper_bgcolor = "rgba(0,0,0,0)",
                 plot_bgcolor  = "rgba(0,0,0,0)")

7.3.7 Heatmap

We draw a heatmap for the 20 gene(s) presented above. We compute Z-scores from the normalised count matrix and visualise part of the resulting table, meaning the 20 gene(s) and their Z-scores in two samples. The Z-scores are only used for visualisation. The Z-score matrix is not saved.

samples_of_interest = c(comparison_samples_group1,
                        comparison_samples_group2)

# if-else in case on 0 differential expression gene
nb_genes = length(c(genes_up, genes_dn))
if (nb_genes < 1) {
  zscore_mat = matrix(data = 0,
                      nrow = 1,
                      ncol = length(samples_of_interest),
                      dimnames = list(c(""),
                                      samples_of_interest))
} else {
  zscore_mat = DESeq2::counts(dds, normalized = TRUE)[c(genes_up, genes_dn), samples_of_interest]
  
  # Special case if there is only one gene: vector instead of matrix
  if (nb_genes == 1) {
    zscore_mat = matrix(zscore_mat,
                        nrow = nb_genes,
                        dimnames = list(row_names = c(genes_up, genes_dn),
                                        col_names = samples_of_interest))
  }
  
  # Z-score
  zscore_mat = zscore_mat %>%
    t() %>% scale() %>% t()
}
# The code below is compatible with 1 gene
# Otherwise, zscore_mat[, c(1:2)] would have sufficed and been much simpler
matrix(zscore_mat[, c(1:2)],
       nrow = max(1, nb_genes),
       dimnames = list(row_names = c(genes_up, genes_dn),
                       col_names = samples_of_interest[c(1:2)])) %>%
  as.data.frame() %>%
  dplyr::mutate(Symbol = correspIdSymbol[rownames(.), 2]) %>%
  dplyr::relocate(Symbol) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
Symbol WT1 WT2
ENSMUSG00000024256 Adcyap1 -0.5773503 1.1547005
ENSMUSG00000025189 Cnnm1 -0.5773503 1.1547005
ENSMUSG00000030350 Prmt8 -0.5773503 1.1547005
ENSMUSG00000030500 Slc17a6 -0.5773503 1.1547005
ENSMUSG00000031376 Atp2b3 -0.5773503 1.1547005
ENSMUSG00000032769 Trpa1 -0.5773503 1.1547005
ENSMUSG00000056673 Kdm5d -0.5773503 1.1547005
ENSMUSG00000058975 Kcnc1 -0.5773503 1.1547005
ENSMUSG00000070552 Mrgprx1 -0.5773503 1.1547005
ENSMUSG00000079466 Prdm12 -0.5773503 1.1547005
ENSMUSG00000005994 Tyrp1 -0.5799532 -0.5747434
ENSMUSG00000015437 Gzmb -0.5773503 -0.5773503
ENSMUSG00000019828 Grm1 -0.5792925 -0.5754059
ENSMUSG00000026579 F5 -0.5756896 -0.5790094
ENSMUSG00000043549 Fam90a1b -0.5822653 -0.5724212
ENSMUSG00000046160 Olig1 -0.5757146 -0.5789844
ENSMUSG00000060882 Kcnd2 -0.5877599 -0.5668777
ENSMUSG00000075217 Fads2b -0.5769177 -0.5777828
ENSMUSG00000093400 ENSMUSG00000093400 -0.5785670 -0.5761327
ENSMUSG00000099372 ENSMUSG00000099372 NaN NaN

In case a gene is expressed in a single sample, the normalisation outputs NaN values. We remove them.

NaN_rows = (which(rowSums(is.nan(zscore_mat)) > 0))

if (length(NaN_rows) > 0) {
  nb_genes = nb_genes - length(NaN_rows)
  zscore_mat = zscore_mat[-(NaN_rows), ]
}

On the heatmap, each gene is one row and each sample is one column. Samples are annotated for their condition. The sample dendrogram is calculated based on the distances between samples considering only the genes represented on the heatmap. The condition associated to each sample is not considered by the method (unsupervised method). The genes dendrogram is calculated based on the Z-score of that gene among all samples.

# Heatmap top annotation
sub_design = design %>%
  dplyr::select(RepTechGroup, Condition) %>%
  dplyr::filter(RepTechGroup %in% samples_of_interest) %>%
  unique() %>%
  `rownames<-`(.$RepTechGroup)

if (complexMode) {
  sub_design = sub_design %>%
    # Add the complex condition
    `colnames<-`(c("RepTechGroup", "SubCondition")) %>%
    dplyr::mutate(Condition = ifelse(RepTechGroup %in% comparison_samples_group1,
                                     yes = condition1,
                                     no = condition2)) %>%
    dplyr::select(Condition, SubCondition)
  sub_design = sub_design[samples_of_interest, ] # re-order
  
  # Annotation
  ha_top = ComplexHeatmap::HeatmapAnnotation(
    df = sub_design,
    col = list(Condition = setNames(nm = c(condition1, condition2),
                                    c("#3AA5C1", "#FF6800")),
               SubCondition = colors_condition[unique(sub_design$SubCondition)]),
    na_col = "#F7F7F7")
} else {
  # Re-order
  sub_design = sub_design %>%
    dplyr::select(Condition)
  sub_design = sub_design[samples_of_interest, ] # re-order
  
  # Annotation
  ha_top = ComplexHeatmap::HeatmapAnnotation(
    Condition = sub_design,
    col = list(Condition = colors_condition[c(condition1, condition2)]),
    na_col = "#F7F7F7")
}

# Heatmap title
if (nb_genes > 1) {
  ht_title = paste0("Heatmap of ", nb_genes,
                    " differentially expressed genes\n",
                    comparison_subtitle)
} else {
  ht_title = paste0("Heatmap of ", nb_genes,
                    " differentially expressed gene\n",
                    comparison_subtitle)
}

# Rownames
if (!is.null(correspPath)) {
  row_labels = correspIdSymbol[rownames(zscore_mat), 2]
} else {
  row_labels = rownames(zscore_mat)
}

# Heatmap main
ht = ComplexHeatmap::Heatmap(
  as.matrix(zscore_mat),
  # Expression colors
  col = expressionColors(range(zscore_mat, na.rm = TRUE)),
  heatmap_legend_param = list(title = "Z-score"),
  # Annotation
  top_annotation = ha_top,
  # Sample order
  cluster_columns = TRUE,
  # Gene order
  cluster_rows = TRUE,
  row_labels = row_labels,
  # Visual aspect
  show_heatmap_legend = TRUE,
  border = TRUE,
  use_raster = FALSE,
  # Title
  column_title = ht_title)

# Draw
ComplexHeatmap::draw(ht,
                     merge_legend = TRUE,
                     heatmap_legend_side = "left",
                     annotation_legend_side = "left",
                     padding = unit(c(samples_padding, 2, 2, 5), "mm"),
                     background = "transparent")

Note: The clustering of samples may change, depending on the genes that are considered.

7.3.8 Save

We re-order the genes based on the adjusted p-value and edit the column names for enhanced readability. We display the 6 first rows of this table.

res = res %>%
  dplyr::mutate(dispersions.dds. = DESeq2::dispersions(dds)) %>%
  dplyr::arrange(padj) %>%
  `colnames<-`( c("ID",
                  "baseMean",
                  paste0("log2foldchange ", condition1, " vs ", condition2),
                  "standard error",
                  "Wald statistic",
                  "Wald test p-value",
                  "BH adjusted p-values",
                  "dispersions.dds.") )

# Display
na.omit(res[, -1]) %>%
  head() %>%
  round(., 4) %>%
  rbind.data.frame(., "..." = rep("...", ncol(.))) %>%
  knitr::kable("html") %>%
  kableExtra::kable_styling(full_width = FALSE)
baseMean log2foldchange FooWT vs FooKO standard error Wald statistic Wald test p-value BH adjusted p-values dispersions.dds.
ENSMUSG00000033361 24278.6244 6.2958 0.1966 32.0234 0 0 0.0114
ENSMUSG00000000223 69095.1118 9.2009 0.2996 30.7109 0 0 0.0263
ENSMUSG00000021061 11643.2985 7.2796 0.2479 29.3708 0 0 0.0159
ENSMUSG00000022456 5115.165 7.0959 0.279 25.4314 0 0 0.0172
ENSMUSG00000056569 2192423.4899 5.0902 0.2023 25.1613 0 0 0.0131
ENSMUSG00000059213 10761.636 9.6464 0.3845 25.0862 0 0 0.0258

We save the table as a TSV file.

filename = paste0(prefix, projectName, "-diffana_",
                  condition1, "_vs_", condition2,".tsv")

saveTable(dataframe = res,
          fileName = filename)

Output file name:
./project_GSE107401/deseq2_GSE107401-diffana_FooWT_vs_FooKO.tsv


8 Versions

▼ show
## [1] "Packages location:"
## [1] "/usr/local/lib/R/library"
## [1] ""
## [1] "Session Information:"
## R version 4.5.1 (2025-06-13)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 24.04.3 LTS
## 
## Matrix products: default
## BLAS:   /usr/local/lib/R/lib/libRblas.so 
## LAPACK: /usr/local/lib/R/lib/libRlapack.so;  LAPACK version 3.12.1
## 
## locale:
## [1] C
## 
## time zone: Etc/UTC
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] ggplot2_4.0.0 dplyr_1.1.4  
## 
## loaded via a namespace (and not attached):
##   [1] rlang_1.1.6                 magrittr_2.0.3             
##   [3] clue_0.3-66                 GetoptLong_1.0.5           
##   [5] matrixStats_1.5.0           compiler_4.5.1             
##   [7] png_0.1-8                   systemfonts_1.3.1          
##   [9] vctrs_0.6.5                 reshape2_1.4.4             
##  [11] stringr_1.5.2               shape_1.4.6.1              
##  [13] pkgconfig_2.0.3             crayon_1.5.3               
##  [15] fastmap_1.2.0               XVector_0.48.0             
##  [17] rmdformats_1.0.4            labeling_0.4.3             
##  [19] rmarkdown_2.29              UCSC.utils_1.4.0           
##  [21] purrr_1.0.4                 xfun_0.52                  
##  [23] cachem_1.1.0                GenomeInfoDb_1.44.0        
##  [25] jsonlite_2.0.0              flashClust_1.01-2          
##  [27] DelayedArray_0.34.1         BiocParallel_1.42.0        
##  [29] parallel_4.5.1              cluster_2.1.8.1            
##  [31] R6_2.6.1                    bslib_0.9.0                
##  [33] stringi_1.8.7               RColorBrewer_1.1-3         
##  [35] GenomicRanges_1.60.0        jquerylib_0.1.4            
##  [37] estimability_1.5.1          Rcpp_1.0.14                
##  [39] bookdown_0.46               SummarizedExperiment_1.38.0
##  [41] iterators_1.0.14            knitr_1.50                 
##  [43] base64enc_0.1-3             IRanges_2.42.0             
##  [45] Matrix_1.7-3                tidyselect_1.2.1           
##  [47] rstudioapi_0.17.1           abind_1.4-8                
##  [49] yaml_2.3.10                 doParallel_1.0.17          
##  [51] codetools_0.2-20            lattice_0.22-7             
##  [53] tibble_3.2.1                plyr_1.8.9                 
##  [55] Biobase_2.68.0              withr_3.0.2                
##  [57] S7_0.2.0                    evaluate_1.0.3             
##  [59] xml2_1.3.8                  circlize_0.4.16            
##  [61] pillar_1.10.2               MatrixGenerics_1.20.0      
##  [63] DT_0.34.0                   foreach_1.5.2              
##  [65] stats4_4.5.1                plotly_4.11.0              
##  [67] generics_0.1.3              S4Vectors_0.46.0           
##  [69] scales_1.4.0                leaps_3.2                  
##  [71] glue_1.8.0                  emmeans_2.0.0              
##  [73] scatterplot3d_0.3-44        lazyeval_0.2.2             
##  [75] tools_4.5.1                 data.table_1.17.0          
##  [77] locfit_1.5-9.12             mvtnorm_1.3-3              
##  [79] grid_4.5.1                  tidyr_1.3.1                
##  [81] crosstalk_1.2.1             colorspace_2.1-1           
##  [83] GenomeInfoDbData_1.2.14     cli_3.6.5                  
##  [85] kableExtra_1.4.0            textshaping_1.0.0          
##  [87] S4Arrays_1.8.0              viridisLite_0.4.2          
##  [89] svglite_2.2.2               ComplexHeatmap_2.24.0      
##  [91] ggdendro_0.2.0              gtable_0.3.6               
##  [93] DESeq2_1.48.0               sass_0.4.10                
##  [95] digest_0.6.37               BiocGenerics_0.54.0        
##  [97] SparseArray_1.8.0           ggrepel_0.9.6              
##  [99] rjson_0.2.23                FactoMineR_2.12            
## [101] htmlwidgets_1.6.4           farver_2.1.2               
## [103] htmltools_0.5.8.1           lifecycle_1.0.4            
## [105] httr_1.4.7                  multcompView_0.1-10        
## [107] GlobalOptions_0.1.2         MASS_7.3-65

9 Reference

The differential expression analysis uses the DESeq2 R package (vignette).

Love, M.I., Huber, W. & Anders, S., Moderated estimation of fold change and dispersion for RNA-seq data with DESeq2. Genome Biol 15, 550 (2014). DOI: 10.1186/s13059-014-0550-8

LS0tCnBhcmFtczoKICBwcm9qZWN0TmFtZTogIlByb2plY3QiCiAgZGVzaWduUGF0aDogTlVMTAogIGNvbXBhcmlzb25QYXRoOiBOVUxMCiAgY29ycmVzcFBhdGg6IE5VTEwKICBkaWZmYW5hVGVzdDogVFJVRQogIGV4cEhlYWRlcjogVFJVRQogIGRlc2VxTW9kZWw6ICJ+Q29uZGl0aW9uIgogIG5lc3RlZE1vZGVsOiBOVUxMCiAgc2l6ZUZhY3RvclR5cGU6ICJyYXRpbyIKICBmaXRUeXBlOiAicGFyYW1ldHJpYyIKICBzdGF0aXN0aWNUZXN0OiAiV2FsZCIKICB3ZWlnaHRDb250cmFzdDogRkFMU0UKICBwYWRqTGltaXQ6IDAuMDUKICBsb2cyRkNMaW1pdDogMgogIHByZWZpeDogIi4vZGVzZXEyXyIKICBwbG90SW50ZXJhY3RpdmU6IFRSVUUKICBsb2dvVXJsOiAiaHR0cHM6Ly93d3cub3V0aWxzLmdlbm9taXF1ZS5iaW9sb2dpZS5lbnMuZnIvZW91bHNhbi9pbWFnZXMvZW91bHNhbi5wbmciCiAgYXV0aG9yTmFtZTogIkVvdWxzYW4iCiAgYXV0aG9yRW1haWw6ICJlb3Vsc2FuQGJpby5lbnMucHNsLmV1IgogIGxlYXZlT25FcnJvcjogVFJVRQogIHNhdmVSRFM6IFRSVUUKdGl0bGU6ICI8Y2VudGVyPkRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzPC9jZW50ZXI+IgpzdWJ0aXRsZTogIjxjZW50ZXI+YHIgcGFzdGUwKCdQcm9qZWN0OiAnLCBwYXJhbXMkcHJvamVjdE5hbWUpYDwvY2VudGVyPiIKZGF0ZTogImByIGludmlzaWJsZShTeXMuc2V0bG9jYWxlKCdMQ19USU1FJywgJ0MnKSkgOyBwYXN0ZTAoJ0FuYWx5c2lzIHJ1biBvbjogJywgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiAlWScpKWAKPGJyPjxzcGFuIGNsYXNzPSdnbHlwaGljb24gZ2x5cGhpY29uLXB1c2hwaW4nPjwvc3Bhbj5gciBwYXN0ZTAoJyBQcm9qZWN0OiAnLCBwYXJhbXMkcHJvamVjdE5hbWUpYAo8YnI+PHNwYW4gY2xhc3M9J2dseXBoaWNvbiBnbHlwaGljb24tbWVudS1yaWdodCc+PC9zcGFuPiBWZXJzaW9uIDIuMwo8YnI+PHNwYW4gY2xhc3M9J2dseXBoaWNvbiBnbHlwaGljb24tbGluayc+PC9zcGFuPiBHZW5lcmF0ZWQgYnkgPGEgaHJlZj0naHR0cHM6Ly93d3cub3V0aWxzLmdlbm9taXF1ZS5iaW9sb2dpZS5lbnMuZnIvZW91bHNhbi8nPkVvdWxzYW48L2E+IgojIGFib3ZlIGlzIGEgc21hbGwgaGFjayB0byBhZGQgbmV3IGxpbmVzIGluIHRoZSBwb3N0YW1wbGUgd2l0aG91dCBlZGl0aW5nIHRoZSB0ZW1wbGF0ZQphdXRob3I6CiAgLSBuYW1lOiAiYHIgcGFyYW1zJGF1dGhvck5hbWVgIgogICAgZW1haWw6ICJgciBwYXJhbXMkYXV0aG9yRW1haWxgIgpvdXRwdXQ6CiAgcm1kZm9ybWF0czo6cmVhZHRoZWRvd246CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCi0tLQoKPCEtLSBDU1MgdG8gY3VzdG9taXplIHRoZSBjb2xvcnMgLS0+CmBgYHtjc3Mgc3R5bGVfc2VjdGlvbjEsIGVjaG8gPSBGQUxTRX0KYm9keSB7CnRleHQtYWxpZ246IGp1c3RpZnkKfQoKLyogV2lkdGggb2YgdGhlIG1haW4gZGl2ICovCiNjb250ZW50IHsKbWF4LXdpZHRoOiA4MCU7Cn0KYGBgCgpgYGB7ciBzdHlsZV9zZWN0aW9uMiwgZWNobyA9IEZBTFNFLCByZXN1bHRzID0gImFzaXMifQojIE5PVEU6IEEgQ1NTIGNodW5rIGNhbm5vdCBpbnRlcnByZXQgdGhlIFIgaW5wdXQgcGFyYW1ldGVyLiBJbiBvcmRlciB0byBwYXJzZSB0aGUgJ3BhcmFtcyRsb2dvVXJsJyBwYXJhbWV0ZXIsIHdlIHJlcXVpcmUgYW4gUiBjaHVuay4gVGhlcmVmb3JlLCB0aGUgQ1NTIGNvbnRlbnQgaXMgZGlzcGxheWVkIHRocm91Z2ggYW4gUiBjaHVuay4KCiMgSGVhZGVyIGluIHRoZSBzaWRlYmFyCmNhdChwYXN0ZTAoIgo8c3R5bGU+CiNzaWRlYmFyIGgyIHsKYmFja2dyb3VuZC1jb2xvcjojQTNBREI4OwpiYWNrZ3JvdW5kLXNpemU6IDMyJTsKYmFja2dyb3VuZC1yZXBlYXQ6IG5vLXJlcGVhdDsKYmFja2dyb3VuZC1wb3NpdGlvbjogY2VudGVyIGJvdHRvbTsKYmFja2dyb3VuZC1pbWFnZTp1cmwoJyIsIHBhcmFtcyRsb2dvVXJsLCAiJyk7CmJhY2tncm91bmQtb3JpZ2luOiBjb250ZW50LWJveDsKaGVpZ2h0OiAxMyU7Cn0KPC9zdHlsZT4KIikpCmBgYAoKYGBge2NzcyBzdHlsZV9zZWN0aW9uMywgZWNobyA9IEZBTFNFfQovKiBTaWRlYmFyIGJvZHkgKi8KI3NpZGViYXIgewpiYWNrZ3JvdW5kOiM1MzVGNkE7Cn0KCi8qIFNtYWxsIHNwYWNlIGFib3V0IHRoZSBhdXRob3Jpbmcgc2VjdGlvbiAqLwojcG9zdGFtYmxlIHsKYm9yZGVyLXRvcDpzb2xpZCAxMHB4ICNBM0FEQjg7Cn0KCi8qIFRpdGxlcyAqLwpoMSwgaDIsIGgzLCBoNCwgaDUsIGg2LCBsZWdlbmQgewpjb2xvcjogIzAwMDAzODsKfQoKLyogVHdvLWNvbHVtbiBtb2RlICovCi5jb2xzIHsKZGlzcGxheTogZmxleDsKYWxpZ24taXRlbXM6IGNlbnRlcjsKfQoKLmNvbC1jZW50ZXIgewpqdXN0aWZ5LWNvbnRlbnQ6IGNlbnRlcjsKfQoKLmNvbC1yaWdodCB7Cm1hcmdpbi1sZWZ0OiBhdXRvOwp9CgovKiBCdXR0b24gKi8KLmJ0bi1jdXN0b20gewpmb250LWZhbWlseTogaW5oZXJpdDsKZm9udC1zaXplOiBpbmhlcml0Owpjb2xvcjogaW5oZXJpdDsKYmFja2dyb3VuZC1jb2xvcjogI0UzRTNFMzsKYm9yZGVyLWNvbG9yOiAjRTNFM0UzOwpwYWRkaW5nOiAxcHggMnB4Owpib3JkZXItcmFkaXVzOiA1cHg7CmJvcmRlci1zdHlsZTogc29saWQ7CmNvbG9yOiAjMDAwMDM4Owp9CgouYnRuLWN1c3RvbTpob3ZlciB7CmJhY2tncm91bmQtY29sb3I6ICM1MzVGNkE7CmJvcmRlci1jb2xvcjogIzUzNUY2QTsKY29sb3I6ICNGQ0ZDRkM7Cn0KYGBgCgo8IS0tIEF1dG9tYXRpY2FsbHkgY29tcHV0ZXMgYW5kIHByaW50cyBpbiB0aGUgb3V0cHV0IHRoZSBydW5uaW5nIHRpbWUgZm9yIGFueSBjb2RlIGNodW5rIC0tPgpgYGB7ciwgZWNobyA9IEZBTFNFfQpob29rcyA9IGtuaXRyOjprbml0X2hvb2tzJGdldCgpCmhvb2tfZm9sZGFibGUgPSBmdW5jdGlvbih0eXBlKSB7CiAgZm9yY2UodHlwZSkKICBmdW5jdGlvbih4LCBvcHRpb25zKSB7CiAgICByZXMgPSBob29rc1tbdHlwZV1dKHgsIG9wdGlvbnMpCiAgICAKICAgIGlmIChpc0ZBTFNFKG9wdGlvbnNbW3Bhc3RlMCgiZm9sZF8iLCB0eXBlKV1dKSkgcmV0dXJuKHJlcykKICAgIAogICAgcGFzdGUwKAogICAgICAiPGRldGFpbHM+PHN1bW1hcnk+IiwgIlxVMjVCQyBzaG93IiwgIjwvc3VtbWFyeT5cblxuIiwKICAgICAgcmVzLAogICAgICAiXG5cbjwvZGV0YWlscz4iCiAgICApCiAgfQp9CmtuaXRyOjprbml0X2hvb2tzJHNldCgKICBvdXRwdXQgPSBob29rX2ZvbGRhYmxlKCJvdXRwdXQiKSwKICBwbG90ID0gaG9va19mb2xkYWJsZSgicGxvdCIpCikKYGBgCgo8IS0tIGtuaXQgc2V0dXAgLS0+CmBgYHtyIGtuaXRfc2V0dXAsIGVjaG8gPSBGQUxTRX0KcHJlZml4ID0gaWZlbHNlKGdyZXBsKCJbWzphbG51bTpdXSQiLCBwYXJhbXMkcHJlZml4KSwKICAgICAgICAgICAgICAgIHllcyA9IHBhc3RlMChwYXJhbXMkcHJlZml4LCAiXyIpLAogICAgICAgICAgICAgICAgbm8gPSBwYXJhbXMkcHJlZml4KQpmaWdfc2F2ZV9mb2xkZXIgPSBwYXN0ZTAocHJlZml4LCAiZmlndXJlcy8iKQoKa25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGVjaG8gPSBUUlVFLAogIGV2YWwgPSBUUlVFLAogIGNhY2hlLmxhenkgPSBGQUxTRSwKICBtZXNzYWdlID0gRkFMU0UsCiAgd2FybmluZyA9IEZBTFNFLAogIGZvbGRfb3V0cHV0ID0gRkFMU0UsCiAgZm9sZF9wbG90ID0gRkFMU0UsCiAgZmlnLmFsaWduID0gImNlbnRlciIsCiAgY2xhc3Muc291cmNlID0gImZvbGQtaGlkZSIsCiAgIyBSIGJhc2UgcGxvdHMgb24gdHJhbnNwYXJlbnQgYmFja2dyb3VuZAogIGRldi5hcmdzID0gbGlzdChiZyA9ICd0cmFuc3BhcmVudCcpLAogICMgRXhwb3J0IGZpZ3VyZXMKICBkZXYgPSBjKCdwbmcnLCAncGRmJyksCiAgZmlnLnBhdGggPSBmaWdfc2F2ZV9mb2xkZXIsCiAgcGRmLm9wdGlvbnMoZW5jb2RpbmcgPSAiSVNPTGF0aW45LmVuYyIpKQpgYGAKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKVGhlIGFpbSBvZiB0aGlzIGRvY3VtZW50IGlzIHRvIHByZXNlbnQgdGhlIGRhdGEgYW5kIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzIGJldHdlZW4gc2FtcGxlcyBmcm9tIHRoZSBwcm9qZWN0ICoqYHIgcGFyYW1zJHByb2plY3ROYW1lYCoqLgoKVGhlIG1haW4gc3RlcHMgYXJlOgoKLSBEYXRhIHByZXBhcmF0aW9uCi0gUXVhbGl0eSBjb250cm9sCi0gTm9ybWFsaXNhdGlvbgoKYGBge3IgZGlzcGxheV9tb2RlbHMsIGV2YWwgPSAhaXMubnVsbChwYXJhbXMkbmVzdGVkTW9kZWwpLCBlY2hvID0gIWlzLm51bGwocGFyYW1zJG5lc3RlZE1vZGVsKSwgcmVzdWx0cyA9ICdhc2lzJ30KY2F0KCItIE5lc3RlZCBtb2RlbHMgZXZhbHVhdGlvblxuIikKYGBgCgotIERpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzCgpgYGB7ciBlY2hvID0gRkFMU0UsIHJlc3VsdHMgPSAiYXNpcyJ9CiMgTk9URTogVGhpcyBzZWN0aW9uIGlzIHZlcnkgdWdseS4gRHVlIHRvIHRoZSBjb25kaXRpb25hbCBzZWN0aW9uLCB3ZSBuZWVkIHRvIGV2YWx1YXRlIGluIGFkdmFuY2Ugd2hldGhlciB0aGUgc2VjdGlvbiB3aWxsIGJlIGV4ZWN1dGVkIG9yIG5vdCwgaW4gb3JkZXIgdG8gY3JlYXRlIGEgc2hvcnQgbGluayB0byB0aGUgY29uZGl0aW9uYWxseS1leGVjdXRlZCBzZWN0aW9uLiBUaGlzIHJlcXVpcmVzIHRoZSBkZXNpZ24gZmlsZSB0byBiZSBsb2FkZWQuIFRoaXMgc2VjdGlvbiBpcyBoaWRkZW4gaW4gdGhlIGZpbmFsIHJlbmRlcmVkIG5vdGVib29rLiBXZSBvbmx5IG1ha2UgY2FsY3VsYXRpb25zIGhlcmUgdG8gcHJpbnQgYSBtZXNzYWdlLiBCdXQsIGNhbGN1bGF0aW9ucyBhcmUgcGVyZm9ybWVkIGFnYWluIGluIGEgdmlzaWJsZSB3YXkgaW4gdGhlIG5leHQgc2VjdGlvbnMuIE5vdCBlbGVnYW50IGFuZCAobG93KSByZXNvdXJjZS1jb25zdW1pbmcuCgp0ZXh0X3RvX3ByaW50ID0gIkZvciBhIHF1aWNrIGFjY2VzcyB0byB0aGUgZmlndXJlcywgZ28gdG8iCgpkZXNpZ25fdG1wID0gcmVhZC50YWJsZShwYXJhbXMkZGVzaWduUGF0aCwKICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIlx0IiwKICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICAgZGVjID0gIi4iLAogICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgppZiAoISgiUmVmZXJlbmNlIiAlaW4lIGNvbG5hbWVzKGRlc2lnbl90bXApKSkgewogICMgQ29tcGxleCBkZXNpZ24sIG5vIHJlZmVyZW5jZSBjb2x1bW4KICBkZXNpZ25fdG1wID0gYygwLDEpCn0gZWxzZSB7CiAgIyBTaW1wbGUgZGVzaWduLCBjaGVjayBpZiB0aGVyZSBhcmUgZGl2ZXJzZSBSZWZlcmVuY2UgdmFsdWVzIGFib3ZlIDAKICBkZXNpZ25fdG1wID0gZGVzaWduX3RtcCB8PgogICAgZHBseXI6OmZpbHRlcihSZWZlcmVuY2UgPj0gMCkgfD4KICAgIGRwbHlyOjpwdWxsKFJlZmVyZW5jZSkgfD4KICAgIHVuaXF1ZSgpCn0KCmlmIChwYXJhbXMkZGlmZmFuYVRlc3QgJiBsZW5ndGgoZGVzaWduX3RtcCkgPiAxKSB7CiAgdGV4dF90b19wcmludCA9IHBhc3RlMCgKICAgIHRleHRfdG9fcHJpbnQsICI6PGJyPiIsCiAgICAiLSBbTm9ybWFsaXNhdGlvbiA+IFZpc3VhbGlzYXRpb25dKCNub3JtYWxpc2F0aW9uX3Zpc3UpIGZvciBub3JtYWxpc2VkIGNvdW50czxicj4iLAogICAgIi0gW1Jlc3VsdHMgc3Vic2VjdGlvbnNdKCNyZXN1bHRzX3N1YnNlY3Rpb24pIGZvciB0aGUgcGFpcndpc2UgY29tcGFyaXNvbnMiKQp9IGVsc2UgewogIHRleHRfdG9fcHJpbnQgPSBwYXN0ZTAoCiAgICB0ZXh0X3RvX3ByaW50LCAiIHRoZSBzZWN0aW9uIFtOb3JtYWxpc2F0aW9uID4gVmlzdWFsaXNhdGlvbl0oI25vcm1hbGlzYXRpb25fdmlzdSkuIikKfQoKY2F0KHBhc3RlMCh0ZXh0X3RvX3ByaW50LCAiXG4iKSkKYGBgCgpBbGwgZmlndXJlcyBhcmUgc2F2ZWQgaW4gUE5HIGFuZCBQREYgZm9ybWF0IGFuZCBhY2Nlc3NpYmxlIGluIHRoZSAqYHIgZmlnX3NhdmVfZm9sZGVyYCogZm9sZGVyLiBZb3UgY2FuIGRvd25sb2FkIHRoZSBjb2RlIGJlaGluZCB0aGlzIGFuYWx5c2lzIHRocm91Z2ggdGhlICJDb2RlIiBidXR0b24gaW4gdGhlIHRvcCByaWdodCBjb3JuZXIgb2YgdGhlIGRvY3VtZW50LgoKIyBTZXR0aW5ncwoKVGhlIGFpbSBvZiB0aGlzIHNlY3Rpb24gaXMgdG86CgotIGxvYWQgdGhlIHRvb2xzIG9mIGludGVyZXN0Ci0gZGVmaW5lIGN1c3RvbSBmdW5jdGlvbnMKLSBsb2FkIHRoZSBpbnB1dCBwYXJhbWV0ZXJzCgojIyBFbnZpcm9ubWVudAoKV2UgbG9hZCB0aGUgdG9vbHMgdG8gaGFuZGxlIGRhdGEgYW5kIGZpZ3VyZXMuCgpgYGB7ciBsaWJyYXJ5LCBmb2xkX291dHB1dD1UUlVFfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGdncGxvdDIpCmBgYAoKYGBge3IgY2hlY2tfZGVzZXEyX3ZlcnNpb24sIGVjaG8gPSBGQUxTRX0KdmVyc2lvbl90ZXN0ZWQgPSBjKDEsIDQ4LCAwKSAjIHZlcnNpb24gMS40OC4wCgpjdXJyZW50X3ZlcnNpb24gPSB1dGlsczo6cGFja2FnZVZlcnNpb24oIkRFU2VxMiIpICU+JSAjIEEuQi5DCiAgYXMuY2hhcmFjdGVyKCkgJT4lCiAgYmFzZTo6c3Ryc3BsaXQoLiwgc3BsaXQgPSAiW14wLTldKyIpICU+JQogIHVubGlzdCgpICU+JQogIGFzLm51bWVyaWMoKQoKaWYgKGN1cnJlbnRfdmVyc2lvbltbMV1dIDwgdmVyc2lvbl90ZXN0ZWRbWzFdXSB8CiAgICAoY3VycmVudF92ZXJzaW9uW1sxXV0gPD0gdmVyc2lvbl90ZXN0ZWRbWzFdXSAmCiAgICAgY3VycmVudF92ZXJzaW9uW1syXV0gPCB2ZXJzaW9uX3Rlc3RlZFtbMl1dKSkgewogIHdhcm5pbmcoIlRoZSBub3RlYm9vayBoYXMgYmVlbiB0ZXN0ZWQgdXNpbmcgdmVyc2lvbiAxLjQ4LjAgb2YgREVTZXEyIiwKICAgICAgICAgICIgWW91IGFyZSB1c2luZyBhbiBvbGRlciB2ZXJzaW9uICgiLAogICAgICAgICAgdXRpbHM6OnBhY2thZ2VWZXJzaW9uKCJERVNlcTIiKSwKICAgICAgICAgICIpIHNvIGl0IGNhbm5vdCBiZSBndWFyYW50ZWVkIHRoYXQgdGhlIHNjcmlwdCB3aWxsIHdvcmsuLi4iKQp9CmBgYAoKIyMgQ3VzdG9tIGZ1bmN0aW9ucwoKV2UgZnVydGhlciBkZWZpbmUgY3VzdG9tIGZ1bmN0aW9ucyB0byBhdm9pZCByZXBldGl0aW9ucyBpbiB0aGUgY29kZS4KCmBgYHtyIGN1c3RvbV9mdW5jdGlvbnMsIGNsYXNzLnNvdXJjZSA9ICJmb2xkLWhpZGUifQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgYnVpbGRDb3VudE1hdHJpeAojIENyZWF0ZSBhIG1hdHJpeCBvZiByZWFkcyBjb3VudAojCiMgSW5wdXQ6CiMgICBmaWxlcyA6IGEgdmVjdG9yIG9mIGZpbGVzIG5hbWVzCiMgICBzYW1wbGVMYWJlbCA6IGEgdmVjdG9yIG9mIHNhbXBsZSBuYW1lcwojICAgcHJvamVjdFBhdGg6IHBhdGggdG8gdGhlIHByb2plY3QgZGlyZWN0b3J5CiMKIyBPdXRwdXQ6CiMgICBjb3VudE1hdHJpeCA6IGEgcmVhZHMgY291bnQgbWF0cml4CiMKIyBPcmlnaW5hbCBhdXRob3IgOiBWaXZpZW4gREVTSEFJRVMKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpidWlsZENvdW50TWF0cml4ID0gZnVuY3Rpb24oZmlsZXMsIHNhbXBsZUxhYmVsLCBleHBIZWFkZXIpIHsKICAKICAjIHJlYWQgZmlyc3QgZmlsZSBhbmQgY3JlYXRlIGNvdW50TWF0cml4CiAgY291bnRNYXRyaXggPSByZWFkLnRhYmxlKGZpbGVzWzFdLAogICAgICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBleHBIZWFkZXIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICBxdW90ZSA9ICIiKQogIGNvbG5hbWVzKGNvdW50TWF0cml4KSA9IGMoImlkIiwgImNvdW50MSIpCiAgCiAgIyByZWFkIGFuZCBtZXJnZSBhbGwgcmVtYWluaW5nIGZpbGVzIHdpdGggdGhlIGZpcnN0CiAgZm9yIChpIGluIDI6bGVuZ3RoKGZpbGVzKSkgewogICAgCiAgICAjIHJlYWQgZmlsZXMKICAgIGV4cCA9IHJlYWQudGFibGUoZmlsZXNbaV0sCiAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IGV4cEhlYWRlciwKICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYsCiAgICAgICAgICAgICAgICAgICAgIHF1b3RlID0gIiIpCiAgICAKICAgICMgbG93ZXJjYXNlIGV4cCBjb2x1bW5zIG5hbWVzCiAgICBjb2xuYW1lcyhleHApID0gYygiaWQiLCBwYXN0ZTAoImNvdW50IiwgaSkpCiAgICAKICAgICMgbWVyZ2UgZmlsZSBkYXRhIHRvIGNvdW50IG1hdHJpeCBieSBpZAogICAgY291bnRNYXRyaXggPSBtZXJnZShjb3VudE1hdHJpeCwgZXhwLCBieSA9ICJpZCIsIHN1ZmZpeGVzID0gIl8iKQogIH0KICAKICAjIG5hbWUgcm93cwogIHJvd25hbWVzKGNvdW50TWF0cml4KSA9IGNvdW50TWF0cml4WywxXQogIAogICMgZGVsZXRlIGZpcnN0IHJvdyBjb250YWluaW5nIHJvdyBuYW1lcwogIGNvdW50TWF0cml4ID0gY291bnRNYXRyaXhbLC0xXQogIAogICMgbmFtZSBjb2x1bW5zCiAgY29sbmFtZXMoY291bnRNYXRyaXgpID0gc2FtcGxlTGFiZWwKICByZXR1cm4oY291bnRNYXRyaXgpCn0KCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIHNhdmVSYXdDb3VudE1hdHJpeAojCiMgICBBIHNwZWNpZmljIHRyZWF0bWVudCBpcyBuZWVkZWQgZm9yIHRoZSByYXcgY291bnQgbWF0cml4IGJlY2F1c2UgdGhlIG9iamVjdAojICAgREVTZXEyOjpjb3VudHMoZGRzKSBkb2VzIG5vdCBpbmNsdWRlIHRoZSBuYW1lcyBvZiB0aGUgc2FtcGxlcyBhcyBjb2x1bW4gbmFtZXMKIwojICAgaW5wdXQ6IGRkcyAtPiBERVNlcSBvYmplY3QKIyAgICAgICAgICBmaWxlTmFtZSAtPiBjaGFyYWN0ZXIgKG5hbWUgb2YgdGhlIGZpbGUgdG8gb3V0cHV0KQojICAgb3V0cHV0OiByYXdDb3VudE1hdHJpeCAtPiBmaWxlCiMKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKc2F2ZVJhd0NvdW50TWF0cml4ID0gZnVuY3Rpb24oZGRzLGZpbGVOYW1lKSB7CiAgCiAgY291bnRNYXRyaXggPSBERVNlcTI6OmNvdW50cyhkZHMpCiAgY291bnRNYXRyaXggPSBjYmluZChjb3VudE1hdHJpeCwgcm93Lm5hbWVzKGNvdW50TWF0cml4KSkKICAKICAjIEFkZCB0aGUgc2FtcGxlcyBuYW1lcyBhcyBjb2x1bW4gbmFtZXMgKyBhZGQgY29sdW1uIElkCiAgY29sRGF0YSA9IFN1bW1hcml6ZWRFeHBlcmltZW50Ojpjb2xEYXRhKGRkcykKICBjb2xuYW1lcyhjb3VudE1hdHJpeCkgPSBjKGNvbERhdGEkTmFtZSwgIklkIikKICAKICAjIFB1dCBJZHMgb24gdGhlIGZpcnN0IGNvbHVtbgogIGNvdW50TWF0cml4ID0gY291bnRNYXRyaXhbLCBjKCJJZCIsIGNvbERhdGEkTmFtZSldCiAgCiAgIyBXcml0ZSB0aGUgY291bnQgbWF0cml4IGluIGEgZmlsZQogIHdyaXRlLnRhYmxlKGNvdW50TWF0cml4LAogICAgICAgICAgICAgIGZpbGUgPSBwYXN0ZTAoZmlsZU5hbWUpLAogICAgICAgICAgICAgIHNlcCA9ICJcdCIsCiAgICAgICAgICAgICAgcm93Lm5hbWVzID0gRkFMU0UsCiAgICAgICAgICAgICAgcXVvdGUgPSBGQUxTRSkKfQoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgc2F2ZVRhYmxlCiMKIyAgIGlucHV0OiBkYXRhZnJhbWUgLT4gZGF0YSBmcmFtZSAoY291bnQgbWF0cml4KQojICAgICAgICAgIGZpbGVOYW1lIC0+IGNoYXJhY3RlciAobmFtZSBvZiB0aGUgZmlsZSB0byBvdXRwdXQpCiMgICBvdXRwdXQ6IGNvdW50TWF0cml4IC0+IGZpbGUKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKc2F2ZVRhYmxlID0gZnVuY3Rpb24oZGF0YWZyYW1lLCBmaWxlTmFtZSkgewogICMgQWRkIGNvbHVtbiBJZAogIGNvbHVtbl9uYW1lcyA9IGNvbG5hbWVzKGRhdGFmcmFtZSkKICBkYXRhZnJhbWUgPSBjYmluZChyb3cubmFtZXMoZGF0YWZyYW1lKSwgZGF0YWZyYW1lKQogIGNvbG5hbWVzKGRhdGFmcmFtZSkgPSBjKCJJZCIsIGNvbHVtbl9uYW1lcykKICAKICAjIFdyaXRlIHRoZSB0YWJsZSBpbiBhIGZpbGUKICB3cml0ZS50YWJsZShkYXRhZnJhbWUsCiAgICAgICAgICAgICAgZmlsZSA9IHBhc3RlMChmaWxlTmFtZSksCiAgICAgICAgICAgICAgc2VwID0gIlx0IiwKICAgICAgICAgICAgICByb3cubmFtZXMgPSBGQUxTRSwKICAgICAgICAgICAgICBxdW90ZSA9IEZBTFNFKQp9CgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgcGxvdERlbmRyb2dyYW0KIyBDcmVhdGUgYSBnZ3Bsb3Qgb2JqZWN0IGNvcnJlc3BvbmRpbmcgdG8gYSBkZW5kcm9ncmFtCiMKIyBJbnB1dDoKIyAgIGhjX2RhdGEgOiBvYmplY3Qgb2J0YWluZWQgd2l0aCB0aGUgZ2dkZW5kcm86OmRlbmRyb19kYXRhIGZ1bmN0aW9uCiMgICBmaWdfdGl0bGUgOiBmaWd1cmUgdGl0bGUKIyAgIGNvbF92ZWN0b3I6IG5hbWVkIHZlY3RvciBvZiBjb2xvcnMKIwojIE91dHB1dDoKIyAgIHAgOiBnZ3Bsb3Qgb2JqZWN0CiMKIyBPcmlnaW5hbCBhdXRob3IgOiBBdWRyZXkgT05GUk9ZCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KcGxvdERlbmRyb2dyYW0gPSBmdW5jdGlvbihoY19kYXRhLAogICAgICAgICAgICAgICAgICAgICAgICAgIGZpZ190aXRsZSA9ICIiLAogICAgICAgICAgICAgICAgICAgICAgICAgIGNvbF92ZWN0b3IgPSBjb2xvcnNfY29uZGl0aW9uKSB7CiAgCiAgcCA9IGdncGxvdDI6OmdncGxvdCgpICsKICAgICMgRHJhdyBkZW5kcm9ncmFtCiAgICBnZ3Bsb3QyOjpnZW9tX3NlZ21lbnQoZGF0YSA9IGdnZGVuZHJvOjpzZWdtZW50KGhjZGF0YSksIAogICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0geCwgeSA9IHksIHhlbmQgPSB4ZW5kLCB5ZW5kID0geWVuZCkpICsKICAgICMgTGVhZiBsYWJlbAogICAgZ2dwbG90Mjo6Z2VvbV9sYWJlbChkYXRhID0gZ2dkZW5kcm86OmxhYmVsKGhjZGF0YSksIAogICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IHgsIHkgPSB5LCBsYWJlbCA9IGxhYmVsKSwKICAgICAgICAgICAgICAgICAgICAgICAgbGluZXdpZHRoID0gMCwgZmlsbCA9IE5BLAogICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMywgYW5nbGUgPSA5MCwgaGp1c3QgPSAxLAogICAgICAgICAgICAgICAgICAgICAgICBudWRnZV95ID0gLTAuMDIqbWF4KGdnZGVuZHJvOjpzZWdtZW50KGhjZGF0YSkkeWVuZCkpICsKICAgICMgTGVhZiBjb2xvcnMKICAgIGdncGxvdDI6Omdlb21fcG9pbnQoZGF0YSA9IGdnZGVuZHJvOjpsYWJlbChoY2RhdGEpLAogICAgICAgICAgICAgICAgICAgICAgICBnZ3Bsb3QyOjphZXMoeCA9IHgsIHkgPSB5LCBjb2wgPSBDb25kaXRpb24pLAogICAgICAgICAgICAgICAgICAgICAgICBzaXplID0gMywgcGNoID0gMTkpICsKICAgIGdncGxvdDI6OnNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjb2xfdmVjdG9yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IG5hbWVzKGNvbF92ZWN0b3IpKSArCiAgICAjIFN0eWxlCiAgICBnZ3Bsb3QyOjpsYWJzKHkgPSAiRGlzdGFuY2UiLAogICAgICAgICAgICAgICAgICB0aXRsZSA9IGZpZ190aXRsZSkgKwogICAgZ2dwbG90Mjo6Y29vcmRfY2FydGVzaWFuKGNsaXAgPSAib2ZmIikgKwogICAgZ2dwbG90Mjo6dGhlbWVfbWluaW1hbCgpICsKICAgIGdncGxvdDI6OnRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgYXhpcy50aWNrcy54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS54ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgYXhpcy5saW5lLnkgPSBlbGVtZW50X2xpbmUoKSwKICAgICAgICAgICAgICAgICAgIGF4aXMudGlja3MueSA9IGVsZW1lbnRfbGluZSgpLAogICAgICAgICAgICAgICAgICAgcGFuZWwuZ3JpZCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgICAgICAgICAgICAgICAgcGxvdC5tYXJnaW4gPSBnZ3Bsb3QyOjp1bml0KGMoMC41LCAwLjUsIG1heChuY2hhcihoY2RhdGEkbGFiZWxzJGxhYmVsKSkvNiwgMC41KSwgImNtIiksCiAgICAgICAgICAgICAgICAgICAjIFRyYW5zcGFyZW50IGJhY2tncm91bmQKICAgICAgICAgICAgICAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBwbG90LmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSkKICAKICByZXR1cm4ocCkKfQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIHBsb3RQQ0EKIyBDcmVhdGUgYSBnZ3Bsb3Qgb2JqZWN0IGNvcnJlc3BvbmRpbmcgdG8gdGhlIFBDQSBwbG90IG9mIGluZGl2aWR1YWxzCiMKIyBJbnB1dDoKIyAgIHBjYV9kZiA6IGRhdGFmcmFtZQojICAgZmlnX3RpdGxlIDogZmlndXJlIHRpdGxlCiMgICBjb2xfdmVjdG9yOiBuYW1lZCB2ZWN0b3Igb2YgY29sb3JzCiMKIyBPdXRwdXQ6CiMgICBwIDogZ2dwbG90IG9iamVjdAojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCnBsb3RQQ0EgPSBmdW5jdGlvbihwY2FfZGYsCiAgICAgICAgICAgICAgICAgICBmaWdfdGl0bGUgPSAiIiwKICAgICAgICAgICAgICAgICAgIGNvbF92ZWN0b3IgPSBjb2xvcnNfY29uZGl0aW9uKSB7CiAgCiAgcCA9IGdncGxvdDI6OmdncGxvdChwY2FfZGYsIGFlcyh4ID0gRGltLjEsIHkgPSBEaW0uMiwgY29sID0gQ29uZGl0aW9uLCBsYWJlbCA9IHNhbXBsZSkpICsKICAgIGdncGxvdDI6Omdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGx0eSA9IDIpICsKICAgIGdncGxvdDI6Omdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGx0eSA9IDIpICsKICAgICMgU2FtcGxlcwogICAgZ2dwbG90Mjo6Z2VvbV9wb2ludChzaXplID0gMywgcGNoID0gMTkpICsKICAgIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZmlsbCA9IE5BLCBsYWJlbC5zaXplID0gMCkgKwogICAgZ2dwbG90Mjo6c2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNvbF92ZWN0b3IsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gbmFtZXMoY29sX3ZlY3RvcikpICsKICAgICMgU3R5bGUKICAgIGdncGxvdDI6OmxhYnMoeCA9IHBhc3RlMCgiRGltMSAoIiwgcm91bmQocGNhQ291bnQkZWlnWzEsIDJdLCAyKSwgIiUpIiksCiAgICAgICAgICAgICAgICAgIHkgPSBwYXN0ZTAoIkRpbTIgKCIsIHJvdW5kKHBjYUNvdW50JGVpZ1syLCAyXSwgMiksICIlKSIpLAogICAgICAgICAgICAgICAgICB0aXRsZSA9IGZpZ190aXRsZSkgKwogICAgZ2dwbG90Mjo6dGhlbWVfbWluaW1hbCgpICsKICAgIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpLAogICAgICAgICAgICAgICAgICAgIyBUcmFuc3BhcmVudCBiYWNrZ3JvdW5kCiAgICAgICAgICAgICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCkpCiAgCiAgcmV0dXJuKHApCn0KCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBwbG90QmFycGxvdAojIENyZWF0ZSBhIGdncGxvdCBvYmplY3QgY29ycmVzcG9uZGluZyB0byBhIGJhcnBsb3QKIwojIElucHV0OgojICAgZGYgOiBkYXRhZnJhbWUKIyAgIHlfY29sdW1uIDogY29sdW1uIGluIGRmIGZvciB0aGUgWS1heGlzCiMgICB5X3RpdGxlIDogWS1heGlzIHRpdGxlCiMgICBmaWdfdGl0bGUgOiBmaWd1cmUgdGl0bGUKIyAgIGNvbF92ZWN0b3I6IG5hbWVkIHZlY3RvciBvZiBjb2xvcnMKIwojIE91dHB1dDoKIyAgIHAgOiBnZ3Bsb3Qgb2JqZWN0CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KcGxvdEJhcnBsb3QgPSBmdW5jdGlvbihkZiwKICAgICAgICAgICAgICAgICAgICAgICB5X2NvbHVtbiwKICAgICAgICAgICAgICAgICAgICAgICB5X3RpdGxlID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICAgZmlnX3RpdGxlID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICAgY29sX3ZlY3RvciA9IGNvbG9yc19jb25kaXRpb24pIHsKICBkZiR5X3ZhbHVlID0gZGZbLCB5X2NvbHVtbl0KICAKICBwID0gZ2dwbG90Mjo6Z2dwbG90KGRmLCBhZXMoeCA9IHNhbXBsZSwgeSA9IHlfdmFsdWUsIGZpbGwgPSBDb25kaXRpb24pKSArCiAgICBnZ3Bsb3QyOjpnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgY29sID0gImJsYWNrIikgKwogICAgZ2dwbG90Mjo6c2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sX3ZlY3RvciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IG5hbWVzKGNvbF92ZWN0b3IpKSArCiAgICAjIFN0eWxlCiAgICBnZ3Bsb3QyOjpsYWJzKHggPSAiIiwKICAgICAgICAgICAgICAgICAgeSA9IHlfdGl0bGUsCiAgICAgICAgICAgICAgICAgIHRpdGxlID0gZmlnX3RpdGxlKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApKSArCiAgICBnZ3Bsb3QyOjp0aGVtZV9jbGFzc2ljKCkgKwogICAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpLAogICAgICAgICAgICAgICAgICAgIyBUcmFuc3BhcmVudCBiYWNrZ3JvdW5kCiAgICAgICAgICAgICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCkpCiAgCiAgcmV0dXJuKHApCn0KCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBwbG90Qm94cGxvdAojIENyZWF0ZSBhIGdncGxvdCBvYmplY3QgY29ycmVzcG9uZGluZyB0byBhIGJveHBsb3QKIwojIElucHV0OgojICAgZGYgOiBkYXRhZnJhbWUKIyAgIHlfY29sdW1uIDogY29sdW1uIGluIGRmIGZvciB0aGUgWS1heGlzCiMgICB5X3RpdGxlIDogWS1heGlzIHRpdGxlCiMgICBmaWdfdGl0bGUgOiBmaWd1cmUgdGl0bGUKIyAgIGNvbG9yc19jb25kaXRpb246IG5hbWVkIHZlY3RvciBvZiBjb2xvcnMKIwojIE91dHB1dDoKIyAgIHAgOiBnZ3Bsb3Qgb2JqZWN0CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KcGxvdEJveHBsb3QgPSBmdW5jdGlvbihkZiwKICAgICAgICAgICAgICAgICAgICAgICB5X2NvbHVtbiwKICAgICAgICAgICAgICAgICAgICAgICB5X3RpdGxlID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICAgZmlnX3RpdGxlID0gIiIsCiAgICAgICAgICAgICAgICAgICAgICAgY29sX3ZlY3RvciA9IGNvbG9yc19jb25kaXRpb24pIHsKICBkZiR5X3ZhbHVlID0gZGZbLCB5X2NvbHVtbl0KICAKICBwID0gZ2dwbG90Mjo6Z2dwbG90KGRmLCBhZXMoeCA9IHNhbXBsZSwgeSA9IHlfdmFsdWUsIGZpbGwgPSBDb25kaXRpb24pKSArCiAgICBnZ3Bsb3QyOjpnZW9tX2JveHBsb3QoY29sID0gImJsYWNrIikgKwogICAgZ2dwbG90Mjo6c2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sX3ZlY3RvciwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IG5hbWVzKGNvbF92ZWN0b3IpKSArCiAgICAjIFN0eWxlCiAgICBnZ3Bsb3QyOjpsYWJzKHggPSAiIiwKICAgICAgICAgICAgICAgICAgeSA9IHlfdGl0bGUsCiAgICAgICAgICAgICAgICAgIHRpdGxlID0gZmlnX3RpdGxlKSArCiAgICBnZ3Bsb3QyOjpzY2FsZV95X2NvbnRpbnVvdXMoZXhwYW5kID0gYygwLDApKSArCiAgICBnZ3Bsb3QyOjp0aGVtZV9jbGFzc2ljKCkgKwogICAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpLAogICAgICAgICAgICAgICAgICAgIyBUcmFuc3BhcmVudCBiYWNrZ3JvdW5kCiAgICAgICAgICAgICAgICAgICBwYW5lbC5iYWNrZ3JvdW5kID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgcGxvdC5iYWNrZ3JvdW5kID0gZ2dwbG90Mjo6ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCkpCiAgCiAgcmV0dXJuKHApCn0KCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBleHByZXNzaW9uQ29sb3JzCiMgRGVmaW5lIGEgY29sb3IgcGFuZWwgZm9yIHRoZSBoZWF0bWFwCiMKIyBJbnB1dDoKIyAgIHJhbmdlX3ZhbHVlcyA6IGEgbnVtZXJpY2FsIHZlY3RvciB3aXRoIHR3byB2YWx1ZXMgKG1pbiBhbmQgbWF4KQojCiMgT3V0cHV0OgojICAgZXhwcmVzc2lvbl9jb2xvcnMgOiBhIGNvbG9yIG9yIGEgZnVuY3Rpb24gYnVpbHQgd2l0aCBjaXJjbGl6ZTo6Y29sb3JSYW1wMgojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmV4cHJlc3Npb25Db2xvcnMgPSBmdW5jdGlvbihyYW5nZV92YWx1ZXMpIHsKICBtaW5fdmFsdWUgPSByYW5nZV92YWx1ZXNbMV0KICBtYXhfdmFsdWUgPSByYW5nZV92YWx1ZXNbMl0KICAKICAjIG1pbl92YWx1ZSA8IDAgPCBtYXhfdmFsdWUKICBpZiAobWluX3ZhbHVlIDwgMCAmIDAgPCBtYXhfdmFsdWUpIHsKICAgIGV4cHJlc3Npb25fY29sb3JzID0gY2lyY2xpemU6OmNvbG9yUmFtcDIoCiAgICAgIGJyZWFrcyA9IGMobWluX3ZhbHVlLCAwLCBtYXhfdmFsdWUpLAogICAgICBjb2xvcnMgPSBjKCIjMjI0ODk3IiwgIiNGN0Y3RjciLCAiI0QzMkEyNCIpKQogIH0gZWxzZQogICAgCiAgICAjIG1pbl92YWx1ZSA8IG1heF92YWx1ZSA8IDAKICAgIGlmIChtaW5fdmFsdWUgPCBtYXhfdmFsdWUgJiBtYXhfdmFsdWUgPCAwKSB7CiAgICAgIGV4cHJlc3Npb25fY29sb3JzID0gY2lyY2xpemU6OmNvbG9yUmFtcDIoCiAgICAgICAgYnJlYWtzID0gYyhtaW5fdmFsdWUsIDApLAogICAgICAgIGNvbG9ycyA9IGMoIiMyMjQ4OTciLCAiI0Y3RjdGNyIpKQogICAgfSBlbHNlCiAgICAgIAogICAgICAjIDAgPCBtaW5fdmFsdWUgPCBtYXhfdmFsdWUKICAgICAgaWYgKDAgPCBtaW5fdmFsdWUgJiBtaW5fdmFsdWUgPCBtYXhfdmFsdWUpIHsKICAgICAgICBleHByZXNzaW9uX2NvbG9ycyA9IGNpcmNsaXplOjpjb2xvclJhbXAyKAogICAgICAgICAgYnJlYWtzID0gYygwLCBtYXhfdmFsdWUpLAogICAgICAgICAgY29sb3JzID0gYygiI0Y3RjdGNyIsICIjRDMyQTI0IikpCiAgICAgIH0gZWxzZSB7CiAgICAgICAgCiAgICAgICAgIyBtaW5fdmFsdWUgPT0gbWF4X3ZhbHVlCiAgICAgICAgZXhwcmVzc2lvbl9jb2xvcnMgPSAiI0Y3RjdGNyIKICAgICAgfQogIAogIHJldHVybihleHByZXNzaW9uX2NvbG9ycykKfQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIGJ1aWxkQ29udHJhc3QKIyBEZWZpbmUgdGhlIGNvbnRyYXN0IHZlY3RvciB0byBleHRyYWN0IERFIHJlc3VsdHMgZnJvbSB0aGUgZGRzIG9iamVjdAojCiMgSW5wdXQ6CiMgICBkZHMgICAgICAgOiBERVNlcURhdGFTZXQKIyAgIGdyb3VwMSAgICA6IGxpc3Qgb2YgY2hhcmFjdGVyIHZlY3RvcnMsIGZvciBpbnN0YW5jZToKIyAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpc3QoYygic2V4ZSIsICJGIiksIGMoImdlbm90eXBlIiwgIkhFVEVSTyIpCiMgICBncm91cDIgICAgOiBsaXN0IG9mIGNoYXJhY3RlciB2ZWN0b3JzLCBmb3IgaW5zdGFuY2U6CiMgICAgICAgICAgICAgICAgICAgICAgICAgICBsaXN0KGMoInNleGUiLCAiRiIpLCBjKCJnZW5vdHlwZSIsICJIT01PIikKIyAgIHdlaWdodGVkICA6IGJvb2xlYW4gICAgIHdoZXRoZXIgdG8gd2VpZ2h0IHRoZSBjb250cmFzdCBtYXRyaXggYnkgdGhlCiMgICAgICAgICAgICAgICAgICAgICAgICAgICBudW1iZXIgb2YgcmVwbGljYXRlcyBwZXIgY29uZGl0aW9uIG9yIG5vdAojCiMgT3V0cHV0OiBhIGxpc3Qgb2YgMyBlbGVtZW50cwojICAgY29udHJhc3QgICAgICAgIDogYSBudW1lcmljYWwgdmVjdG9yCiMgICBzYW1wbGVzX2dyb3VwMSAgOiBhIGNoYXJhY3RlciB2ZWN0b3IgY29udGFpbmluZyBzYW1wbGUgbmFtZXMgaW4gZ3JvdXAgMQojICAgc2FtcGxlc19ncm91cDIgIDogYSBjaGFyYWN0ZXIgdmVjdG9yIGNvbnRhaW5pbmcgc2FtcGxlIG5hbWVzIGluIGdyb3VwIDIKIwojIFNvdXJjZToKIyBNb2RpZmllZCBmcm9tOiBodHRwczovL3d3dy5yLWJsb2dnZXJzLmNvbS8yMDI0LzA1L2EtZ3VpZGUtdG8tZGVzaWducy1hbmQtY29udHJhc3RzLWluLWRlc2VxMi8KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpidWlsZENvbnRyYXN0ID0gZnVuY3Rpb24oZGRzLAogICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAxLAogICAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAyLAogICAgICAgICAgICAgICAgICAgICAgICAgd2VpZ2h0ZWQgPSBGQUxTRSl7CiAgIy0tIE1vZGVsIG1hdHJpeCBmb3IgZWFjaCBzYW1wbGUgCiAgbW9kX21hdCA9IG1vZGVsLm1hdHJpeChERVNlcTI6OmRlc2lnbihkZHMpLAogICAgICAgICAgICAgICAgICAgICAgICAgU3VtbWFyaXplZEV4cGVyaW1lbnQ6OmNvbERhdGEoZGRzKSkKICAKICAjLS0gU2FtcGxlcyBpbiBncm91cDEKICAjIEEgbGlzdCBvZiBUUlVFIC8gRkFMU0UKICAjIEVhY2ggZWxlbWVudCBvZiB0aGUgbGlzdCBpcyBvZiBsZW5ndGggdGhlIG51bWJlciBvZiBzYW1wbGVzCiAgIyBUaGUgaS10aCBlbGVtZW50IG9mIHRoZSBsaXN0IGNvcnJlc3BvbmRzIHRvIHRoZSBpLXRoIGNvbmRpdGlvbiBpbiBncm91cDEKICBncnAxX3Jvd3MgPSBsYXBwbHkoYygxOmxlbmd0aChncm91cDEpKSwgRlVOID0gZnVuY3Rpb24oZ3JvdXAxX2kpIHsKICAgIGdyb3VwMV9pX25hbWUgPSBncm91cDFbW2dyb3VwMV9pXV1bMV0KICAgIGdyb3VwMV9pX3ZhbHVlcyA9IGdyb3VwMVtbZ3JvdXAxX2ldXVsyOmxlbmd0aChncm91cDFfaSldCiAgICAKICAgIHNhbXBsZXNfaW5fZ3JvdXAxX2kgPSAoU3VtbWFyaXplZEV4cGVyaW1lbnQ6OmNvbERhdGEoZGRzKVtbZ3JvdXAxX2lfbmFtZV1dICVpbiUgZ3JvdXAxX2lfdmFsdWVzKSAjIFRSVUUgLyBGQUxTRQogICAgCiAgICByZXR1cm4oc2FtcGxlc19pbl9ncm91cDFfaSkKICB9KQogIGdycDFfcm93cyA9IFJlZHVjZShmdW5jdGlvbih4LCB5KSB4ICYgeSwgZ3JwMV9yb3dzKQogIAogIGdycDJfcm93cyA9IGxhcHBseShjKDE6bGVuZ3RoKGdyb3VwMikpLCBGVU4gPSBmdW5jdGlvbihncm91cDJfaSkgewogICAgZ3JvdXAyX2lfbmFtZSA9IGdyb3VwMltbZ3JvdXAyX2ldXVsxXQogICAgZ3JvdXAyX2lfdmFsdWVzID0gZ3JvdXAyW1tncm91cDJfaV1dWzI6bGVuZ3RoKGdyb3VwMl9pKV0KICAgIAogICAgc2FtcGxlc19pbl9ncm91cDJfaSA9IChTdW1tYXJpemVkRXhwZXJpbWVudDo6Y29sRGF0YShkZHMpW1tncm91cDJfaV9uYW1lXV0gJWluJSBncm91cDJfaV92YWx1ZXMpICMgVFJVRSAvIEZBTFNFCiAgICAKICAgIHJldHVybihzYW1wbGVzX2luX2dyb3VwMl9pKQogIH0pCiAgZ3JwMl9yb3dzID0gUmVkdWNlKGZ1bmN0aW9uKHgsIHkpIHggJiB5LCBncnAyX3Jvd3MpCiAgCiAgIy0tIE1vZGVsIG1hdHJpeCB3aXRoIG9ubHkgc2FtcGxlcyBvZiBpbnRlcmVzdAogIG1vZF9tYXQxID0gbW9kX21hdFtncnAxX3Jvd3MsICwgZHJvcCA9IEZBTFNFXQogIG1vZF9tYXQyID0gbW9kX21hdFtncnAyX3Jvd3MsICwgZHJvcCA9IEZBTFNFXQogIAogICMtLSBDb25zaWRlciB0aGUgd2VpZ2h0IG9yIG5vdAogIGlmKCF3ZWlnaHRlZCl7CiAgICBtb2RfbWF0MSA9IG1vZF9tYXQxWyFkdXBsaWNhdGVkKG1vZF9tYXQxKSwgLCBkcm9wID0gRkFMU0VdCiAgICBtb2RfbWF0MiA9IG1vZF9tYXQyWyFkdXBsaWNhdGVkKG1vZF9tYXQyKSwgLCBkcm9wID0gRkFMU0VdCiAgfQogIAogICMtLSBPdXRwdXQKICBjb250cmFzdCA9IGNvbE1lYW5zKG1vZF9tYXQxKS1jb2xNZWFucyhtb2RfbWF0MikKICBzYW1wbGVzX2dyb3VwMSA9IGNvbG5hbWVzKGRkcylbZ3JwMV9yb3dzXQogIHNhbXBsZXNfZ3JvdXAyID0gY29sbmFtZXMoZGRzKVtncnAyX3Jvd3NdCiAgb3V0cHV0ID0gbGlzdChjb250cmFzdCA9IGNvbnRyYXN0LAogICAgICAgICAgICAgICAgc2FtcGxlc19ncm91cDEgPSBzYW1wbGVzX2dyb3VwMSwKICAgICAgICAgICAgICAgIHNhbXBsZXNfZ3JvdXAyID0gc2FtcGxlc19ncm91cDIpCiAgCiAgcmV0dXJuKG91dHB1dCkKfQoKIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIG1pc3NpbmdTdWJzdHJpbmcKIyBJZGVudGlmeSBhbmQgY29sb3IgdGhlIHZhcmlhYmxlIG9mIGludGVyZXN0IGluIHRoZSBjb250ZXh0IG9mIG5lc3RlZCBtb2RlbHMKIwojIElucHV0OgojICAgQSA6IHRoZSB0ZXh0IGJlaGluZCBhIGZvcm11bGEKIyAgIEIgOiB0aGUgdGV4dCBiZWhpbmQgYSBmb3JtdWxhCiMKIyBPdXRwdXQ6CiMgICBBX2NvbG9yIDogQSwgd2l0aCBpbiByZWQsIHRoZSBzdWJzdHJpbmcgZnJvbSBBIHdoaWNoIGlzIGFic2VudCBpbiBCCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KbWlzc2luZ1N1YnN0cmluZyA9IGZ1bmN0aW9uKEEsIEIpIHsKICAjIFJlbW92ZSB0aGUgfgogIEEgPSBiYXNlOjpzdWJzdHIoeCA9IEEsIHN0YXJ0ID0gMiwgc3RvcCA9IG5jaGFyKEEpKQogIEIgPSBiYXNlOjpzdWJzdHIoeCA9IEIsIHN0YXJ0ID0gMiwgc3RvcCA9IG5jaGFyKEIpKQogIAogICMgU3BsaXQgYnkgKwogIEFfc3BsaXQgPSBzdHJpbmdyOjpzdHJfc3BsaXQoc3RyaW5nID0gQSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdHRlcm4gPSAiXFwrIilbWzFdXQogIAogIEJfc3BsaXQgPSBzdHJpbmdyOjpzdHJfc3BsaXQoc3RyaW5nID0gQiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdHRlcm4gPSAiXFwrIilbWzFdXQogIAogICMgRGlmZmVyZW5jZQogIGlkX2Fic2VudF9pbl9CID0gd2hpY2goIShBX3NwbGl0ICVpbiUgQl9zcGxpdCkpCiAgY29sb3JlZF9zdWJzdHJpbmcgPSBwYXN0ZTAoIjxzcGFuIHN0eWxlPSdjb2xvcjpyZWQnPiIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQV9zcGxpdFtpZF9hYnNlbnRfaW5fQl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjwvc3Bhbj4iKQogIAogICMgUmVidWlsZCBBCiAgQV9jb2xvciA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlKHN0cmluZyA9IEEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBhdHRlcm4gPSBBX3NwbGl0W2lkX2Fic2VudF9pbl9CXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZW1lbnQgPSBjb2xvcmVkX3N1YnN0cmluZykKICBBX2NvbG9yID0gcGFzdGUwKCJ+IiwgQV9jb2xvcikKICAKICByZXR1cm4oQV9jb2xvcikKfQpgYGAKClRoZXNlIGZ1bmN0aW9ucyBhcmUgbGlzdGVkIGJlbG93LgoKYGBge3IgcHJpbnRfZnVuY3Rpb25zLCBlY2hvID0gRkFMU0UsIHJlc3VsdHMgPSAnYXNpcyd9CmNhdChwYXN0ZTAoIi0gIiwgc2V0ZGlmZihscygpLCBjKCJob29rcyIsICJwYXJhbXMiLCAiaG9va19mb2xkYWJsZSIsICJkZXNpZ25fdG1wIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImZpZ19zYXZlX2ZvbGRlciIsICJ0ZXh0X3RvX3ByaW50IikpKSwKICAgIHNlcCA9ICJcbiIpCmBgYAoKIyMgUGFyYW1ldGVycwoKVGhlIHRhYmxlIGJlbG93IHN1bW1hcml6ZXMgYWxsIHRoZSBpbnB1dCBwYXJhbWV0ZXJzLgoKYGBge3IgaW5wdXRfcGFyYW1ldGVyLCBjbGFzcy5zb3VyY2UgPSAiZm9sZC1oaWRlIn0KcHJvamVjdE5hbWUgICAgID0gcGFyYW1zJHByb2plY3ROYW1lCmRlc2lnblBhdGggICAgICA9IHBhcmFtcyRkZXNpZ25QYXRoCmNvbXBhcmlzb25QYXRoICA9IHBhcmFtcyRjb21wYXJpc29uUGF0aApjb3JyZXNwUGF0aCAgICAgPSBwYXJhbXMkY29ycmVzcFBhdGgKZGlmZmFuYVRlc3QgICAgID0gYXMubG9naWNhbCh0b3VwcGVyKHBhcmFtcyRkaWZmYW5hVGVzdCkpCmV4cEhlYWRlciAgICAgICA9IGFzLmxvZ2ljYWwodG91cHBlcihwYXJhbXMkZXhwSGVhZGVyKSkKZGVzZXFNb2RlbCAgICAgID0gcGFyYW1zJGRlc2VxTW9kZWwKbmVzdGVkTW9kZWwgICAgID0gcGFyYW1zJG5lc3RlZE1vZGVsCnNpemVGYWN0b3JUeXBlICA9IHBhcmFtcyRzaXplRmFjdG9yVHlwZQpmaXRUeXBlICAgICAgICAgPSBwYXJhbXMkZml0VHlwZQpzdGF0aXN0aWNUZXN0ICAgPSBwYXJhbXMkc3RhdGlzdGljVGVzdAp3ZWlnaHRDb250cmFzdCAgPSBwYXJhbXMkd2VpZ2h0Q29udHJhc3QKcGFkakxpbWl0ICAgICAgID0gcGFyYW1zJHBhZGpMaW1pdApsb2cyRkNMaW1pdCAgICAgPSBwYXJhbXMkbG9nMkZDTGltaXQKcGxvdEludGVyYWN0aXZlID0gcGFyYW1zJHBsb3RJbnRlcmFjdGl2ZQpsZWF2ZU9uRXJyb3IgICAgPSBwYXJhbXMkbGVhdmVPbkVycm9yCnNhdmVSRFMgICAgICAgICA9IHBhcmFtcyRzYXZlUkRTCgojIERpc3BsYXkgdGhlbSBhcyBhIHRhYmxlCmMoIlByb2plY3QgbmFtZSAocHJvamVjdE5hbWUpIiA9IHByb2plY3ROYW1lLAogICJQYXRoIHRvIHRoZSBkZXNpZ24gZmlsZSAoZGVzaWduUGF0aCkiID0gZGVzaWduUGF0aCwKICAiUGF0aCB0byB0aGUgY29tcGFyaXNvbiBmaWxlIChjb21wYXJpc29uUGF0aCkiID0gY29tcGFyaXNvblBhdGgsCiAgIlBhdGggdG8gdGhlIGNvcnJlc3BvbmRlbmNlIGZpbGUgKGNvcnJlc3BQYXRoKSIgPSBjb3JyZXNwUGF0aCwKICAiV2hldGhlciB0byBwZXJmb3JtIHRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyBvciBub3QgKGRpZmZhbmFUZXN0KSIgPSBkaWZmYW5hVGVzdCwKICAiV2hldGhlciB0aGUgZGVzaWduIHRhYmxlIGhhcyBhIGhlYWRlciBvciBub3QgKGV4cEhlYWRlcikiID0gZXhwSGVhZGVyLAogICJERVNlcTIgbW9kZWwgKGRlc2VxTW9kZWwpIiA9IGRlc2VxTW9kZWwsCiAgIldoZXRoZXIgdG8gcGVyZm9ybSBhIHRlc3Qgb2YgbmVzdGVkIG1vZGVscyAobmVzdGVkTW9kZWwpIiA9IG5lc3RlZE1vZGVsLAogICJTaXplIGZhY3RvciB0eXBlIChzaXplRmFjdG9yVHlwZSkiID0gc2l6ZUZhY3RvclR5cGUsCiAgIkZpdCB0eXBlIChmaXRUeXBlKSIgPSBmaXRUeXBlLAogICJTdGF0aXN0aWMgdGVzdCAoc3RhdGlzdGljVGVzdCkiID0gc3RhdGlzdGljVGVzdCwKICAiV2hldGhlciB0byB3ZWlnaHQgdGhlIGNvbnRyYXN0IHZlY3RvciBieSB0aGUgbnVtYmVyIG9mIHNhbXBsZXMgb3Igbm90ICh3ZWlnaHRDb250cmFzdCkiID0gd2VpZ2h0Q29udHJhc3QsCiAgIkFkanVzdGVkIHAtdmFsdWUgdGhyZXNob2xkIHRvIGNvbnNpZGVyIGEgZ2VuZSBhcyBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgKHBhZGpMaW1pdCkiID0gcGFkakxpbWl0LAogICJBYnNvbHV0ZSBsb2cyIGZvbGQtY2hhbmdlIHRocmVzaG9sZCB0byBjb25zaWRlciBhIGdlbmUgYXMgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIChsb2cyRkNMaW1pdCkiID0gbG9nMkZDTGltaXQsCiAgIlByZWZpeCB0byBzYXZlIGZpbGVzIChwcmVmaXgpIiA9IHByZWZpeCwKICAiV2hldGhlciB0byBtYWtlIGludGVyYWN0aXZlIHBsb3RzIG9yIG5vdCAocGxvdEludGVyYWN0aXZlKSIgPSBwbG90SW50ZXJhY3RpdmUsCiAgIldoZXRoZXIgdG8gc3RvcCB0aGUgcmVuZGVyaW5nIGluIGNhc2Ugb2YgZXJyb3IgKGxlYXZlT25FcnJvcikiID0gbGVhdmVPbkVycm9yLAogICJXaGV0aGVyIHRvIHNhdmUgdGhlIGRhdGEgYXMgUkRTIGZpbGVzIChzYXZlUkRTKSIgPSBzYXZlUkRTKSAlPiUKICBkYXRhLmZyYW1lKFBhcmFtZXRlciA9IG5hbWVzKC4pLAogICAgICAgICAgICAgVmFsdWUgICAgID0gLiwKICAgICAgICAgICAgIHJvdy5uYW1lcyA9IE5VTEwpICU+JQogIGtuaXRyOjprYWJsZSgiaHRtbCIsIGVzY2FwZSA9IEZBTFNFKSAlPiUKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKCJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEZBTFNFKSAlPiUKICBrYWJsZUV4dHJhOjpjb2x1bW5fc3BlYygxLCBib2xkID0gVFJVRSkKYGBgCgojIERhdGEgcHJlcGFyYXRpb24KClRoZSBwdXJwb3NlIG9mIHRoaXMgc2VjdGlvbiBpcyB0bzoKCiogbG9hZCB0aGUgZGVzaWduIGZpbGUsIGNvbnRhaW5pbmcgbWV0YWRhdGEgYWJvdXQgZWFjaCBzYW1wbGUgOwoqIGxvYWQgdGhlIGluZGl2aWR1YWwgc2FtcGxlIGNvdW50IGRhdGEsIGFzc2VtYmxlIHRoZW0gYXMgYSBnZW5lLWJ5LXNhbXBsZSBjb3VudCBtYXRyaXgsIGFuZCBzYXZlIGl0IGFzIGEgVFNWIGZpbGUgOyBhbmQsCiogYnVpbGQgYSBkYXRhc2V0IGluIGEgZm9ybWF0IGNvbXBhdGlibGUgd2l0aCB0aGUgZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgKERFU2VxMi1yZWxhdGVkIGZvcm1hdCkuCgojIyBMb2FkIG1ldGFkYXRhCgpXZSBsb2FkIHRoZSBkZXNpZ24gZmlsZSBhbmQgZGlzcGxheSB0aGUgNiBmaXJzdCByb3dzLiAKCmBgYHtyIGxvYWRfZGVzaWdufQpkZXNpZ24gPSByZWFkLnRhYmxlKGRlc2lnblBhdGgsCiAgICAgICAgICAgICAgICAgICAgc2VwID0gIlx0IiwKICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgIGRlYyA9ICIuIiwKICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgojIEFib3V0IGEgd2FybmluZyBpbiBERVNlcTIKZGVzaWduJENvbmRpdGlvbiA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlX2FsbCgKICBkZXNpZ24kQ29uZGl0aW9uLAogIHBhdHRlcm4gPSAiLSIsCiAgcmVwbGFjZW1lbnQgPSAiXyIpCgojIEFkZCBSZWZlcmVuY2UgaWYgZG9lcyBub3QgZXhpc3QKaWYgKCEoIlJlZmVyZW5jZSIgJWluJSBjb2xuYW1lcyhkZXNpZ24pKSkgewogIGRlc2lnbiRSZWZlcmVuY2UgPSBJbmYKICBjb21wbGV4TW9kZSA9IFRSVUUKfSBlbHNlIHsKICBjb21wbGV4TW9kZSA9IEZBTFNFCn0KCiMgUmVvcmRlciBzYW1wbGVzIGJhc2VkIG9uIENvbmRpdGlvbgpkZXNpZ24gPSBkZXNpZ24gJT4lCiAgZHBseXI6OmFycmFuZ2UoQ29uZGl0aW9uLCBSZXBUZWNoR3JvdXAsIE5hbWUpICU+JQogIGRwbHlyOjptdXRhdGUoQ29uZGl0aW9uID0gZmFjdG9yKENvbmRpdGlvbiwgbGV2ZWxzID0gdW5pcXVlKENvbmRpdGlvbikpKSAlPiUKICBkcGx5cjo6bXV0YXRlKFJlcFRlY2hHcm91cCA9IGZhY3RvcihSZXBUZWNoR3JvdXAsIGxldmVscyA9IHVuaXF1ZShSZXBUZWNoR3JvdXApKSkgJT4lCiAgZHBseXI6Om11dGF0ZShOYW1lID0gZmFjdG9yKE5hbWUsIGxldmVscyA9IHVuaXF1ZShOYW1lKSkpCgojIFJlbW92ZSBjb2x1bW5zIG91dCBvZiBpbnRlcmVzdApkZXNpZ24gPSBkZXNpZ25bLCAhKGNvbG5hbWVzKGRlc2lnbikgJWluJSBjKCJTYW1wbGVJZCIsICJEZXNjcmlwdGlvbiIsICJEYXRlIiwgIkZhc3RxRm9ybWF0IikpXQoKIyBEaXNwbGF5CmhlYWQoZGVzaWduKSAlPiUKICBkcGx5cjo6bXV0YXRlX2FsbChhcy5jaGFyYWN0ZXIpICU+JQogIHJiaW5kLmRhdGEuZnJhbWUoLiwgIi4uLiIgPSByZXAoIi4uLiIsIG5jb2woLikpKSAlPiUKICBrbml0cjo6a2FibGUoImh0bWwiKSAKYGBgCgoKRGVzY3JpcHRpb24gb2YgdGhlIGNvbHVtbnM6CgpgYGB7ciBwcmludF9tZXRhZGF0YV9oZWxwLCBlY2hvID0gRkFMU0UsIHJlc3VsdHMgPSAnYXNpcyd9CnRoZV9tZXNzYWdlID0gYyhOYW1lID0gIi0gKipgTmFtZWAqKjogVW5pcXVlIGlkZW50aWZpZXIgb2YgZWFjaCB0ZWNobmljYWwgcmVwbGljYXRlLiIsCiAgICAgICAgICAgICAgICBleHByZXNzaW9uRmlsZSA9ICItICoqYGV4cHJlc3Npb25GaWxlYCoqOiBGaWxlIHBhdGggdG8gdGhlIGNvdW50IGV4cHJlc3Npb24gZGF0YS4gVGhlICoqVFNWKiogZmlsZSBjb250YWlucyB0d28gY29sdW1uczogb25lIGZvciB0aGUgZ2VuZSBpZGVudGlmaWVycyBhbmQgb25lIGZvciB0aGUgbnVtYmVyIG9mIHJlYWRzIG1hcHBpbmcgdG8gdGhlaXIgZ2Vub21pYyBsb2NhdGlvbi4gRm9yIGNvbXBhdGliaWxpdHkgd2l0aCBFeGNlbCwgcGxlYXNlIHVzZSB0aGUgKipYTFNYKiogZmlsZSBpbnN0ZWFkIG9mIHRoZSBUU1YgZmlsZSAoW3JlYWQgbW9yZV0oaHR0cHM6Ly9wbWMubmNiaS5ubG0ubmloLmdvdi9hcnRpY2xlcy9QTUM4MzU3MTQwLykpLiBGdXJ0aGVybW9yZSwgdGhlIFhMU1ggZmlsZSBjb250YWlucyBhZGRpdGlvbmFsIGNvbHVtbnMsIHN1Y2ggYXMgYSBkZXNjcmlwdGlvbiBvZiBlYWNoIGdlbmUuIiwKICAgICAgICAgICAgICAgIFJlYWRzID0gIi0gKipgUmVhZHNgKio6IEZpbGUgcGF0aCB0byB0aGUgc2VxdWVuY2luZyByZWFkcywgaW4gRkFTVFEgZm9ybWF0IChbcmVhZCBtb3JlXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9GQVNUUV9mb3JtYXQpKS4iLAogICAgICAgICAgICAgICAgUmVwVGVjaEdyb3VwID0gIi0gKipgUmVwVGVjaEdyb3VwYCoqOiBTYW1wbGVzIHdpdGggdGhlIHNhbWUgdmFsdWUgaW4gdGhlIGBSZXBUZWNoR3JvdXBgIGNvbHVtbiBhcmUgKip0ZWNobmljYWwgcmVwbGljYXRlcyoqLiBUaGlzIG9jY3VycyB3aGVuIHRoZSBzZXF1ZW5jaW5nIGxpYnJhcnkgaXMgc3BsaXQgb3ZlciBzZXF1ZW5jaW5nIGxhbmVzLiBFYWNoIGxhbmUgZ2VuZXJhdGVzIGEgdGVjaG5pY2FsIHJlcGxpY2F0ZSBmcm9tIHRoZSBzYW1lIGJpb2xvZ2ljYWwgbGlicmFyeS4iLAogICAgICAgICAgICAgICAgQ29uZGl0aW9uID0gIi0gKipgQ29uZGl0aW9uYCoqOiBTYW1wbGVzIHdpdGggdGhlIHNhbWUgdmFsdWUgaW4gdGhlIGBDb25kaXRpb25gIGNvbHVtbiBhcmUgKipiaW9sb2dpY2FsIHJlcGxpY2F0ZXMqKi4gVGhleSBhcmUgcHJvZHVjZWQgYnkgdGhlIGV4cGVyaW1lbnRlci4iLAogICAgICAgICAgICAgICAgUmVmZXJlbmNlID0gIi0gKipgUmVmZXJlbmNlYCoqOiBOdW1lcmljYWwgdmFsdWVzIHVzZWQgdG8gZGVmaW5lIHRoZSBwYWlyd2lzZSBjb21wYXJpc29ucyBiZXR3ZWVuIGNvbmRpdGlvbnMgaW4gdGhlIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzLiIpCnRoZV9tZXNzYWdlID0gdGhlX21lc3NhZ2VbbmFtZXModGhlX21lc3NhZ2UpICVpbiUgY29sbmFtZXMoZGVzaWduKV0KCmNhdChwYXN0ZTAodGhlX21lc3NhZ2UpLCBzZXAgPSAiXG4iKQpgYGAKCldlIGFzc29jaWF0ZSBhIGNvbG9yIHRvIGVhY2ggY29uZGl0aW9uLgoKYGBge3IgYWRkX2NvbG9yc30KY29sb3JzX2NvbmRpdGlvbiA9IHVuaXF1ZShkZXNpZ24kQ29uZGl0aW9uKSAlPiUKICBzZXROYW1lcyhubSA9IC4sCiAgICAgICAgICAgZ3JEZXZpY2VzOjpoY2wuY29sb3JzKGxlbmd0aCguKSwgIlNwZWN0cmFsIikpCgpkYXRhLmZyYW1lKENvbG9yID0gdW5uYW1lKGNvbG9yc19jb25kaXRpb24pLAogICAgICAgICAgIENvbmRpdGlvbiA9IG5hbWVzKGNvbG9yc19jb25kaXRpb24pKSAlPiUKICBrbml0cjo6a2FibGUoImh0bWwiLAogICAgICAgICAgICAgICBjb2wubmFtZXMgPSBjKCJDb2xvciAoaGV4IGNvZGUpIiwgIkNvbmRpdGlvbiIpKSAlPiUKICBrYWJsZUV4dHJhOjpjb2x1bW5fc3BlYygxLCBjb2xvciA9IGNvbG9yc19jb25kaXRpb24pICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCiMjIEJ1aWxkIGNvdW50IG1hdHJpeAoKRnJvbSB0aGUgaW5kaXZpZHVhbCBjb3VudCBkYXRhLCB3ZSBidWlsZCBhIGdlbmUtYnktc2FtcGxlIGNvdW50IG1hdHJpeC4gVGhlIGNvdW50IHZhbHVlIGZvciBhIGdpdmVuIGdlbmUgY29ycmVzcG9uZHMgdG8gdGhlIG51bWJlciAoaW50ZWdlciB2YWx1ZSkgb2Ygc2VxdWVuY2luZyByZWFkcyB0aGF0IG1hcCB0byBpdHMgZ2Vub21pYyByZWdpb24uIFdlIGRpc3BsYXkgcGFydCBvZiB0aGlzIG1hdHJpeCwgbWVhbmluZyA1IGdlbmVzIGFuZCA1IHNhbXBsZXMuIFRoZSBjb21wbGV0ZSBtYXRyaXggaXMgc2F2ZWQgYXMgYSBUU1YgZmlsZSAoW3F1aWNrIGFjY2Vzc10oI3NhdmVfY291bnRfbWF0cml4KSkuCgpgYGB7ciBjb3VudF9tYXR9CmNvdW50X21hdCA9IGJ1aWxkQ291bnRNYXRyaXgoZmlsZXMgPSBkZXNpZ24kZXhwcmVzc2lvbkZpbGUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2FtcGxlTGFiZWwgPSBkZXNpZ24kTmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBleHBIZWFkZXIgPSBleHBIZWFkZXIpCgojIFJlb3JkZXIgY29sdW1ucwpjb3VudF9tYXQgPSBjb3VudF9tYXRbLCBsZXZlbHMoZGVzaWduJE5hbWUpXQoKIyBEZWZpbmUgYW4gb3JkZXIgdG8gZGlzcGxheSB0aGUgbWF0cml4CnRvcDVfdG9fcHJpbnQgPSBvcmRlcihjb3VudF9tYXRbLCAxXSwgZGVjcmVhc2luZyA9IFRSVUUpW2MoMTo1KV0KCiMgRGlzcGxheQpjb3VudF9tYXRbdG9wNV90b19wcmludCwgYygxOjUpXSAlPiUKICByYmluZC5kYXRhLmZyYW1lKC4sICIuLi4iID0gcmVwKCIuLi4iLCBuY29sKC4pKSkgJT4lCiAga25pdHI6OmthYmxlKCJodG1sIikgJT4lCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpCmBgYAoKVGhlIGNvdW50IG1hdHJpeCBjb250YWlucyAqKmByIG5yb3coY291bnRfbWF0KWAqKiBnZW5lcywgKipgciBuY29sKGNvdW50X21hdClgKiogc2FtcGxlcyBhbmQgKipgciByb3VuZCgxMDAqbWVhbihjb3VudF9tYXQgPT0gMCksMilgKiolIG9mIG51bGwgdmFsdWVzLgoKIyMgQ3JlYXRlIERFU2VxRGF0YVNldCBvYmplY3QKCldlIGNyZWF0ZSB0aGUgREVTZXFEYXRhU2V0IG9iamVjdCB0aGF0IGluY2x1ZGVzIHRoZSBjb3VudCBtYXRyaXggYW5kIHRoZSBkZXNpZ24gZmlsZS4KCmBgYHtyIGRkc19vYmplY3R9CmRkcyA9IERFU2VxMjo6REVTZXFEYXRhU2V0RnJvbU1hdHJpeCgKICBjb3VudERhdGEgPSBjb3VudF9tYXQsCiAgY29sRGF0YSA9IGRlc2lnbiwKICBkZXNpZ24gPSBhcy5mb3JtdWxhKGRlc2VxTW9kZWwpKQpgYGAKCgpgYGB7ciBsb2FkX2NvcnJlc3BQYXRoLCBldmFsID0gIWlzLm51bGwoY29ycmVzcFBhdGgpLCBlY2hvID0gIWlzLm51bGwoY29ycmVzcFBhdGgpLCByZXN1bHRzID0gJ2FzaXMnfQpjYXQoJyMjIElkZW50aWZpZXJzIHRvIHN5bWJvbHNcblxuJykKY2F0KCdXZSBsb2FkIHRoZSB0YWJsZSBjb250YWluaW5nIHRoZSBjb3JyZXNwb25kZW5jZSBiZXR3ZWVuIGdlbmVzIGlkZW50aWZpZXJzIGFuZCBzeW1ib2xzLlxuJykKCmNvcnJlc3BJZFN5bWJvbCA9IHJlYWQuZGVsaW0oZmlsZSA9IGNvcnJlc3BQYXRoKSAlPiUKICAjIEZvcmNlIGNvbHVtbiBuYW1lcwogIGBjb2xuYW1lczwtYChjKCJJRCIsICJzeW1ib2wiKSkgJT4lCiAgIyBVbmlxdWUgSUQKICBkcGx5cjo6ZmlsdGVyKCFkdXBsaWNhdGVkKElEKSkgJT4lCiAgIyBGb3JjZSBhbGwgSUQgdG8gYmUgcHJlc2VudAogIGRwbHlyOjpsZWZ0X2pvaW4oeCA9IGRhdGEuZnJhbWUoSUQgPSByb3duYW1lcyhjb3VudF9tYXQpKSwKICAgICAgICAgICAgICAgICAgIHkgPSAuLAogICAgICAgICAgICAgICAgICAgYnkgPSAiSUQiKSAlPiUKICAjIENoYW5nZSBtaXNzaW5nIHN5bWJvbHMgdG8gSUQKICBkcGx5cjo6bXV0YXRlKHN5bWJvbCA9IGlmZWxzZShpcy5uYShzeW1ib2wpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHllcyA9IElELAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5vID0gc3ltYm9sKSkKcm93bmFtZXMoY29ycmVzcElkU3ltYm9sKSA9IGNvcnJlc3BJZFN5bWJvbCRJRAoKIyBEaXNwbGF5CmhlYWQoY29ycmVzcElkU3ltYm9sKSAlPiUKICByYmluZC5kYXRhLmZyYW1lKC4sICIuLi4iID0gcmVwKCIuLi4iLCBuY29sKC4pKSkgJT4lCiAga25pdHI6OmthYmxlKCJodG1sIiwKICAgICAgICAgICAgICAgcm93Lm5hbWVzID0gRkFMU0UpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCiMjIFNhdmUgY291bnQgbWF0cml4IHsjc2F2ZV9jb3VudF9tYXRyaXh9CgpXZSBzYXZlIHRoZSBjb3VudCBtYXRyaXggYXMgYSBUU1YgZmlsZS4KCmBgYHtyIHNhdmVfY291bnRfbWF0cml4fQpmaWxlbmFtZSA9IHBhc3RlMChwcmVmaXgsIHByb2plY3ROYW1lLCAiLW5vcm1hbGlzYXRpb25fcmF3Q291bnRNYXRyaXgudHN2IikKCnNhdmVSYXdDb3VudE1hdHJpeChkZHMsCiAgICAgICAgICAgICAgICAgICBmaWxlID0gZmlsZW5hbWUpCmBgYAoKT3V0cHV0IGZpbGUgbmFtZTo8YnI+KmByIGZpbGVuYW1lYCoKCmBgYHtyIHNhdmVfY291bnRfbWF0cml4X3JkcywgZXZhbCA9IHNhdmVSRFMsIHJlc3VsdHMgPSAnYXNpcyd9CmNhdCgiV2UgYWxzbyBzYXZlIHRoZSBvYmplY3QgaW4gUkRTIGZvcm1hdC4gVGhlIGZpbGUgbmFtZSBpcyB0aGUgc2FtZS4gVGhlIGV4dGVuc2lvbiBpcyAnLnJkcycuXG4iKQoKc2F2ZVJEUyhkZHMsCiAgICAgICAgZmlsZSA9IHN0cmluZ3I6OnN0cl9yZXBsYWNlKHN0cmluZyA9IGZpbGVuYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwYXR0ZXJuID0gInRzdiQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXBsYWNlbWVudCA9ICJyZHMiKSkKYGBgCgojIFF1YWxpdHkgY29udHJvbAoKVGhlIGFpbSBvZiB0aGlzIHNlY3Rpb24gaXMgdG8gYmV0dGVyIGFwcHJlaGVuZCB0aGUgdmFyaWFiaWxpdHkgYmV0d2VlbiB0aGUgdGVjaG5pY2FsIHJlcGxpY2F0ZXMuIEZvciBpbmZvcm1hdGlvbiBvbiB0aGUgY29ycmVzcG9uZGVuY2UgYmV0d2VlbiB0aGUgaWRlbnRpZmllcnMgb2YgdGVjaG5pY2FsIHJlcGxpY2F0ZXMgYW5kIHNhbXBsZSBuYW1lcywgcGxlYXNlIHJlZmVyIHRvIHRoZSBkZXNpZ24gZmlsZS4KCiMjIFJhdyBjb3VudHMgZGVuZHJvZ3JhbQoKV2UgY29tcHV0ZSBldWNsaWRlYW4gZGlzdGFuY2VzIGJldHdlZW4gZWFjaCBwYWlyIG9mIHNhbXBsZXMsIGNvbnNpZGVyaW5nIHRoZSByYXcgY291bnQgdmFsdWVzLgoKYGBge3IgZGVuZHJvZ3JhbV9yYXdfY291bnRfbWF0cml4fQpkaXN0Lm1hdCA9IGRpc3QodChjb3VudF9tYXQpKQpoYyA9IHN0YXRzOjpoY2x1c3QoZGlzdC5tYXQpCmhjZGF0YSA9IGdnZGVuZHJvOjpkZW5kcm9fZGF0YShoYywgdHlwZSA9ICJyZWN0YW5nbGUiKQoKIyBBZGQgZ3JvdXBpbmcgZm9yIGNvbG9ycwpoY2RhdGEkbGFiZWxzID0gZHBseXI6OmxlZnRfam9pbih4ID0gaGNkYXRhJGxhYmVscywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGRlc2lnblssIGMoIk5hbWUiLCAiQ29uZGl0aW9uIildLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9IGMoImxhYmVsIiA9ICJOYW1lIikpCmBgYAoKV2UgdmlzdWFsaXNlIHRoZXNlIGRpc3RhbmNlcyB0aHJvdWdoIGEgZGVuZHJvZ3JhbS4gSXQgaW5mb3JtcyB3aGV0aGVyIHRlY2huaWNhbCByZXBsaWNhdGVzIG9yIGNsb3NlbHkgcmVsYXRlZCBjb25kaXRpb25zIGFyZSBjbG9zZXIgdG9nZXRoZXIgdGhhbiB3aXRoIG90aGVyIHNhbXBsZXMuCgpgYGB7ciBmaWctbm9ybWFsaXNhdGlvbl91bnBvb2xlZF9jbHVzdGVyaW5nLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDZ9CnBsb3REZW5kcm9ncmFtKGhjX2RhdGEsCiAgICAgICAgICAgICAgIGZpZ190aXRsZSA9IHBhc3RlMCgiRGVuZHJvZ3JhbSBvbiB0aGUgcmF3IGNvdW50IG1hdHJpeCAtICIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9qZWN0TmFtZSkpCmBgYAoKIyMgUmF3IGNvdW50cyBQQ0EKClRoZSAqKmByIG5jb2woY291bnRfbWF0KWAqKiBzYW1wbGVzIGFyZSByZXByZXNlbnRlZCBpbiBhIHNwYWNlIG9mICoqYHIgbnJvdyhjb3VudF9tYXQpYCoqIGRpbWVuc2lvbnMsIGNvcnJlc3BvbmRpbmcgdG8gZ2VuZXMuIFdlIHBlcmZvcm0gYSBQcmluY2lwYWwgQ29tcG9uZW50IEFuYWx5c2lzIChQQ0EpIHRvIHZpc3VhbGlzZSBzYW1wbGVzIG9uIGEgdHdvLWRpbWVuc2lvbiBzcGFjZSBzdWNoIHRoYXQgaXQgcmVwcmVzZW50cyB0aGUgaGlnaGVzdCB2YXJpYWJpbGl0eSBiZXR3ZWVuIHNhbXBsZXMuCgpgYGB7ciBydW5fcGNhfQpwY2FDb3VudCA9IGNvdW50X21hdCAlPiUKICB0KCkgJT4lCiAgRmFjdG9NaW5lUjo6UENBKC4sIGdyYXBoID0gRkFMU0UpCmBgYAoKV2UgdmlzdWFsaXNlIHRoZSBzYW1wbGVzIG9uIHRoZSB0d28tZGltZW5zaW9uYWwgcHJvamVjdGlvbi4gVGhlIGNvb3JkaW5hdGVzIG9mIGVhY2ggc2FtcGxlIGRlcGVuZCBvbiB0aG9zZSBvZiB0aGUgb3RoZXIgc2FtcGxlcy4gVGhpcyBwcm9qZWN0aW9uIGNhcHR1cmVzICoqYHIgcm91bmQocGNhQ291bnQkZWlnWzIsIDNdLCAyKWAqKiUgb2YgdGhlIHRvdGFsIHZhcmlhYmlsaXR5IGJldHdlZW4gc2FtcGxlcy4gVGhlcmVmb3JlLCBpdCBsYWNrcyAqKmByIDEwMC1yb3VuZChwY2FDb3VudCRlaWdbMiwgM10sIDIpYCoqJSBvZiB0aGUgaW5pdGlhbCBpbmZvcm1hdGlvbi4gSW4gdGhpcyByZXByZXNlbnRhdGlvbiwgdHdvIHBvaW50cyAoc2FtcGxlcykgYXJlIGNsb3NlIHRvZ2V0aGVyIGlmIHRoZXkgaGF2ZSBzaW1pbGFyIGdsb2JhbCB0cmFuc2NyaXB0b21pYyBwcm9maWxlcy4gV2UgZXhwZWN0IHRoZSB0ZWNobmljYWwgYW5kIGJpb2xvZ2ljYWwgcmVwbGljYXRlcyB0byBiZSBjbG9zZXIgdG8gZWFjaCBvdGhlciB0aGFuIHRvIHRoZSBvdGhlciBzYW1wbGVzIGFuZCBjb25kaXRpb25zLgoKYGBge3IgZmlnLW5vcm1hbGlzYXRpb25fdW5wb29sZWRfUENBLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDZ9CnBjYUNvdW50JGluZCRjb29yZCAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgZHBseXI6Om11dGF0ZShzYW1wbGUgPSByb3duYW1lcyguKSkgJT4lCiAgZHBseXI6OmxlZnRfam9pbih4ID0gLiwKICAgICAgICAgICAgICAgICAgIHkgPSB1bmlxdWUoZGVzaWduWywgYygiTmFtZSIsICJDb25kaXRpb24iKV0pLAogICAgICAgICAgICAgICAgICAgYnkgPSBjKCJzYW1wbGUiID0gIk5hbWUiKSkgJT4lCiAgcGxvdFBDQSguLAogICAgICAgICAgZmlnX3RpdGxlID0gIHBhc3RlMCgiUENBIG9mIHRoZSByYXcgY291bnQgbWF0cml4IC0gIiwgcHJvamVjdE5hbWUpKQpgYGAKCiMjIE51bGwgY291bnRzIGJhcnBsb3QKClRoaXMgZmlndXJlIGRpc3BsYXlzIHRoZSBwcm9wb3J0aW9uIG9mIG51bGwgY291bnRzIHBlciBzYW1wbGUsIG1lYW5pbmcsIHRoZSBnZW5lcyB0aGF0IGFyZSBub3QgYmVpbmcgZXhwcmVzc2VkIG9yIG5vdCBjYXB0dXJlZCBieSB0aGUgcHJvdG9jb2wsIHdpdGhpbiB0aGUgYmlvbG9naWNhbCBzYW1wbGVzLiBXZSBleHBlY3Qgc2FtcGxlcyBmcm9tIHRoZSBzYW1lIGNvbmRpdGlvbiB0byBiZSBzaW1pbGFyLgoKYGBge3IgZmlnLW5vcm1hbGlzYXRpb25fbnVsbF9jb3VudHMsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gNn0KY29sTWVhbnMoY291bnRfbWF0ID09IDApICU+JQogIGRhdGEuZnJhbWUoc2FtcGxlID0gZmFjdG9yKG5hbWVzKC4pLCBsZXZlbHMgPSBsZXZlbHMoZGVzaWduJE5hbWUpKSwKICAgICAgICAgICAgIHByb3BfbnVsbF9jb3VudHMgPSAuKSAlPiUKICBkcGx5cjo6bXV0YXRlKHByb3BfbnVsbF9jb3VudHMgPSAxMDAqcHJvcF9udWxsX2NvdW50cykgJT4lCiAgZHBseXI6OmxlZnRfam9pbih4ID0gLiwKICAgICAgICAgICAgICAgICAgIHkgPSBkZXNpZ25bLCBjKCJOYW1lIiwgIkNvbmRpdGlvbiIpXSwKICAgICAgICAgICAgICAgICAgIGJ5ID0gYygic2FtcGxlIiA9ICJOYW1lIikpICU+JQogICMgUGxvdAogIHBsb3RCYXJwbG90KC4sCiAgICAgICAgICAgICAgeV9jb2x1bW4gPSAicHJvcF9udWxsX2NvdW50cyIsCiAgICAgICAgICAgICAgeV90aXRsZSA9ICJQcm9wb3J0aW9uIG9mIG51bGwgY291bnRzICglKSIsCiAgICAgICAgICAgICAgZmlnX3RpdGxlID0gcGFzdGUwKCJQcm9wb3J0aW9uIG9mIG51bGwgY291bnRzIHBlciBzYW1wbGUgLSAiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcm9qZWN0TmFtZSkpCmBgYAoKIyMgUmF3IGNvdW50cyBiYXJwbG90CgpUaGlzIGZpZ3VyZSBkaXNwbGF5cyB0aGUgdG90YWwgbnVtYmVyIG9mIHJlYWRzIHBlciBzYW1wbGUsIGFsc28ga25vd24gYXMgbGlicmFyeSBzaXplLiBXZSBjYW4gYXNzZXNzIGlmIHNvbWUgc2FtcGxlcyBoYXZlIGEgbG93IGxpYnJhcnkgc2l6ZSBjb21wYXJlZCB0byBvdGhlcnMsIGFuZCB3aGV0aGVyIHRlY2huaWNhbCByZXBsaWNhdGVzIHNoYXJlIGEgc2ltaWxhciBudW1iZXIgb2YgcmVhZHMgb3Igbm90LgoKYGBge3IgZmlnLW5vcm1hbGlzYXRpb25fYmFycGxvdF9jb3VudHMsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gNn0KY29sU3VtcyhERVNlcTI6OmNvdW50cyhkZHMpKSAlPiUKICBkYXRhLmZyYW1lKHNhbXBsZSA9IGZhY3RvcihuYW1lcyguKSwgbGV2ZWxzID0gbGV2ZWxzKGRlc2lnbiROYW1lKSksCiAgICAgICAgICAgICByZWFkX2NvdW50cyA9IC4pICU+JQogIGRwbHlyOjpsZWZ0X2pvaW4oeCA9IC4sCiAgICAgICAgICAgICAgICAgICB5ID0gZGVzaWduWywgYygiTmFtZSIsICJDb25kaXRpb24iKV0sCiAgICAgICAgICAgICAgICAgICBieSA9IGMoInNhbXBsZSIgPSAiTmFtZSIpKSAlPiUKICAjIFBsb3QKICBwbG90QmFycGxvdCguLAogICAgICAgICAgICAgIHlfY29sdW1uID0gInJlYWRfY291bnRzIiwKICAgICAgICAgICAgICB5X3RpdGxlID0gIlRvdGFsIHJlYWQgY291bnRzIiwKICAgICAgICAgICAgICBmaWdfdGl0bGUgPSBwYXN0ZTAoIk51bWJlciBvZiByZWFkcyBwZXIgc2FtcGxlIC0gIiwgcHJvamVjdE5hbWUpKQpgYGAKCiMjIFJhdyBjb3VudHMgYm94cGxvdAoKVGhpcyBmaWd1cmUgZGlzcGxheXMsIGFzIGEgYm94cGxvdCwgdGhlIG51bWJlciBvZiByYXcgY291bnRzIGZvciBlYWNoIGdlbmUsIHBlciBzYW1wbGUuIEl0IGluZm9ybXMgd2hldGhlciB0aGUgZ2VuZXMgc2hvdyBhIHNpbWlsYXIgY291bnQgZGlzdHJpYnV0aW9uIGJldHdlZW4gc2FtcGxlcyBhbmQgY29uZGl0aW9ucy4KCmBgYHtyIGZpZy1ub3JtYWxpc2F0aW9uX2JveHBsb3RfY291bnQsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gNn0KbG9nMihERVNlcTI6OmNvdW50cyhkZHMpKzEpICU+JQogIHJlc2hhcGUyOjptZWx0KCkgJT4lCiAgYGNvbG5hbWVzPC1gKGMoImdlbmVfSUQiLCAic2FtcGxlIiwgImxvZzJfY291bnRzIikpICU+JQogIGRwbHlyOjptdXRhdGUoc2FtcGxlID0gZmFjdG9yKHNhbXBsZSwgbGV2ZWxzID0gbGV2ZWxzKGRlc2lnbiROYW1lKSkpICU+JQogIGRwbHlyOjpsZWZ0X2pvaW4oeCA9IC4sCiAgICAgICAgICAgICAgICAgICB5ID0gZGVzaWduWywgYygiTmFtZSIsICJDb25kaXRpb24iKV0sCiAgICAgICAgICAgICAgICAgICBieSA9IGMoInNhbXBsZSIgPSAiTmFtZSIpKSAlPiUKICAjIFBsb3QKICBwbG90Qm94cGxvdCguLAogICAgICAgICAgICAgIHlfY29sdW1uID0gImxvZzJfY291bnRzIiwKICAgICAgICAgICAgICB5X3RpdGxlID0gImxvZ18yIChjb3VudHMrMSkiLAogICAgICAgICAgICAgIGZpZ190aXRsZSA9IHBhc3RlMCgiUmF3IGNvdW50cyBkaXN0cmlidXRpb24gcGVyIHNhbXBsZSAtICIsIHByb2plY3ROYW1lKSkKYGBgCgoKPCEtLSBDb25kaXRpb25hbCBleGVjdXRpb24gaW4gY2FzZSB0ZWNobmljYWwgcmVwbGljYXRlcyBhcmUgcHJlc2VudCAtLT4KPGJyPgoKYGBge3IgdGVjaF9yZXBfcHJlc2VudCwgZWNobyA9IEZBTFNFfQp0ZWNoX3JlcF9wcmVzZW50ID0gKG1heCh0YWJsZShkZXNpZ24kUmVwVGVjaEdyb3VwLCBkZXNpZ24kQ29uZGl0aW9uKSkgPiAxKQoKaWYgKHRlY2hfcmVwX3ByZXNlbnQpIHsKICB0aGVfbWVzc2FnZSA9ICJJbiB0aGlzIGNvbnRleHQsIHRoZXJlIGFyZSB0ZWNobmljYWwgcmVwbGljYXRlcy4iCn0gZWxzZSB7CiAgdGhlX21lc3NhZ2UgPSAiSW4gdGhpcyBjb250ZXh0LCB0aGVyZSBpcyBubyB0ZWNobmljYWwgcmVwbGljYXRlLiIKICAKICAjIEFueXdheSwgd2UgY2hhbmdlIHRoZSBzYW1wbGUgbGFiZWxzIHRvIHRoZSBiaW9sb2dpY2FsbHkgbWVhbmluZ2Z1bCBvbmVzLgogIGRkcyA9IERFU2VxMjo6Y29sbGFwc2VSZXBsaWNhdGVzKGRkcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncm91cGJ5ID0gZGRzJFJlcFRlY2hHcm91cCkKfQpgYGAKCmByIHRoZV9tZXNzYWdlYAoKYGBge3IgY29sbGFwc2VfcmVwbGljYXRlcywgZWNobyA9IEZBTFNFLCByZXN1bHRzID0gJ2FzaXMnLCBldmFsID0gdGVjaF9yZXBfcHJlc2VudH0KcmVzID0ga25pdHI6OmtuaXRfY2hpbGQoCiAgaW5wdXQgPSAnMDJfY2hpbGRfY29sbGFwc2VfcmVwbGljYXRlcy5SbWQnLAogIHF1aWV0ID0gVFJVRSkKCmNhdChyZXMsIHNlcCA9ICdcbicpCmBgYAoKIyBOb3JtYWxpc2F0aW9uCgpUaGUgYWltIG9mIHRoaXMgc2VjdGlvbiBpcyB0byBub3JtYWxpc2UgdGhlIGNvdW50IG1hdHJpeCwgYmVmb3JlIHBlcmZvcm1pbmcgdGhlIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIGFuYWx5c2lzLiBUaGUgbm9ybWFsaXNhdGlvbiBtZXRob2QgY29uc2lkZXJzIHRoZSBzZXF1ZW5jaW5nIGRlcHRoIGFuZCBSTkEgY29tcG9zaXRpb24gb2YgZWFjaCBzYW1wbGUgdG8gY29tcHV0ZSBub3JtYWxpc2VkIGNvdW50IHZhbHVlcy4KCkZvciBtb3JlIGRldGFpbHMsIHBsZWFzZSB2aXNpdDoKCltodHRwczovL2hiY3RyYWluaW5nLmdpdGh1Yi5pby9ER0Vfd29ya3Nob3AvbGVzc29ucy8wMl9ER0VfY291bnRfbm9ybWFsaXphdGlvbi5odG1sXShodHRwczovL2hiY3RyYWluaW5nLmdpdGh1Yi5pby9ER0Vfd29ya3Nob3AvbGVzc29ucy8wMl9ER0VfY291bnRfbm9ybWFsaXphdGlvbi5odG1sKQoKIyMgRGF0YSBoYW5kbGluZwoKV2Ugbm9ybWFsaXNlIHRoZSBjb3VudCBtYXRyaXguIFdlIGRpc3BsYXkgcGFydCBvZiB0aGlzIG1hdHJpeCwgbWVhbmluZyA1IGdlbmVzIGFuZCA1IHNhbXBsZXMuIFVubGlrZSBjb3VudCBkYXRhLCBub3JtYWxpc2VkIGNvdW50IHZhbHVlcyBhcmUgbm90IG5lY2Vzc2FyaWx5IGludGVnZXJzLgoKYGBge3IgZXN0aW1hdGVTaXplRmFjdG9yc30KZGRzID0gREVTZXEyOjplc3RpbWF0ZVNpemVGYWN0b3JzKGRkcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR5cGUgPSBzaXplRmFjdG9yVHlwZSkKCiMgRGlzcGxheQpERVNlcTI6OmNvdW50cyhkZHMsIG5vcm1hbGl6ZWQgPSBUUlVFKVt0b3A1X3RvX3ByaW50LCBjKDE6NSldICU+JQogIHJiaW5kLmRhdGEuZnJhbWUoLiwgIi4uLiIgPSByZXAoIi4uLiIsIG5jb2woLikpKSAlPiUKICBrbml0cjo6a2FibGUoImh0bWwiKSAlPiUKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgpXZSBzYXZlIHRoZSBub3JtYWxpc2VkIGNvdW50IG1hdHJpeCBhcyBhIFRTViBmaWxlLgoKYGBge3Igc2F2ZV9ub3JtX2NvdW50X21hdHJpeH0KZmlsZW5hbWUgPSBwYXN0ZTAocHJlZml4LCBwcm9qZWN0TmFtZSwKICAgICAgICAgICAgICAgICAgIi1ub3JtYWxpc2F0aW9uX25vcm1hbGlzZWRDb3VudE1hdHJpeC50c3YiKQoKc2F2ZVRhYmxlKERFU2VxMjo6Y291bnRzKGRkcywgbm9ybWFsaXplZCA9IFRSVUUpLAogICAgICAgICAgZmlsZU5hbWUgPSBmaWxlbmFtZSkKYGBgCgpPdXRwdXQgZmlsZSBuYW1lOjxicj4qYHIgZmlsZW5hbWVgKgoKYGBge3Igc2F2ZV9ub3JtX2NvdW50X21hdHJpeF9yZHMsIGV2YWwgPSBzYXZlUkRTLCByZXN1bHRzID0gJ2FzaXMnfQpjYXQoIldlIGFsc28gc2F2ZSB0aGUgb2JqZWN0IGluIFJEUyBmb3JtYXQuIFRoZSBmaWxlIG5hbWUgaXMgdGhlIHNhbWUuIFRoZSBleHRlbnNpb24gaXMgJy5yZHMnLlxuIikKCnNhdmVSRFMoZGRzLAogICAgICAgIGZpbGUgPSBzdHJpbmdyOjpzdHJfcmVwbGFjZShzdHJpbmcgPSBmaWxlbmFtZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0dGVybiA9ICJ0c3YkIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwbGFjZW1lbnQgPSAicmRzIikpCmBgYAoKIyMgVmlzdWFsaXNhdGlvbiB7I25vcm1hbGlzYXRpb25fdmlzdX0KCiMjIyBOb3JtYWxpc2VkIGNvdW50cyBkZW5kcm9ncmFtCgpXZSBjb21wdXRlIGV1Y2xpZGVhbiBkaXN0YW5jZXMgYmV0d2VlbiBlYWNoIHBhaXIgb2Ygc2FtcGxlcywgY29uc2lkZXJpbmcgdGhlIG5vcm1hbGlzZWQgY291bnQgdmFsdWVzLgoKYGBge3IgZGVuZHJvZ3JhbV9wb29sZWRfbm9ybWFsaXNlZF9jb3VudF9tYXRyaXh9CmRkc1N0YWJpbGl6ZWQgPSBkZHMgJT4lCiAgREVTZXEyOjp2YXJpYW5jZVN0YWJpbGl6aW5nVHJhbnNmb3JtYXRpb24oKSAlPiUKICBTdW1tYXJpemVkRXhwZXJpbWVudDo6YXNzYXkoKQpkaXN0Lm1hdCA9IGRpc3QodChkZHNTdGFiaWxpemVkKSkKaGMgPSBzdGF0czo6aGNsdXN0KGRpc3QubWF0KQpoY2RhdGEgPSBnZ2RlbmRybzo6ZGVuZHJvX2RhdGEoaGMsIHR5cGUgPSAicmVjdGFuZ2xlIikKCiMgQWRkIGdyb3VwaW5nIGZvciBjb2xvcnMKaGNkYXRhJGxhYmVscyA9IGRwbHlyOjpsZWZ0X2pvaW4oCiAgeCA9IGhjZGF0YSRsYWJlbHMsCiAgeSA9IHVuaXF1ZShkZXNpZ25bLCBjKCJSZXBUZWNoR3JvdXAiLCAiQ29uZGl0aW9uIildKSwKICBieSA9IGMoImxhYmVsIiA9ICJSZXBUZWNoR3JvdXAiKSkKYGBgCgpXZSB2aXN1YWxpc2UgdGhlc2UgZGlzdGFuY2VzIHRocm91Z2ggYSBkZW5kcm9ncmFtLiBJdCBpbmZvcm1zIHdoZXRoZXIgdGhlIHNhbXBsZXMgc2hhcmUgYSBzaW1pbGFyIHRyYW5zY3JpcHRvbWljIHByb2ZpbGUgcGVyIGNvbmRpdGlvbiBvciBub3QuCgpgYGB7ciBmaWctbm9ybWFsaXNhdGlvbl9wb29sZWRfY2x1c3RlcmluZywgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA2fQpwbG90RGVuZHJvZ3JhbSgKICBoY19kYXRhLAogIGZpZ190aXRsZSA9IHBhc3RlMCgiRGVuZHJvZ3JhbSBvbiB0aGUgbm9ybWFsaXNlZCBjb3VudCBtYXRyaXggLSAiLCBwcm9qZWN0TmFtZSkpCmBgYAoKIyMjIE5vcm1hbGlzZWQgY291bnRzIFBDQQoKV2UgcGVyZm9ybSBhIFBDQSBmb3IgYWxsIHNhbXBsZXMgdXNpbmcgdGhlIG5vcm1hbGlzZWQgY291bnQgbWF0cml4LgoKYGBge3IgcnVuX3BjYTJ9CnBjYUNvdW50ID0gZGRzICU+JQogIERFU2VxMjo6Y291bnRzKC4sIG5vcm1hbGl6ZWQgPSBUUlVFKSAlPiUKICB0KCkgJT4lCiAgRmFjdG9NaW5lUjo6UENBKC4sIGdyYXBoID0gRkFMU0UpCmBgYAoKV2UgdmlzdWFsaXNlIHRoZSBzYW1wbGVzIG9uIHRoZSB0d28tZGltZW5zaW9uYWwgcHJvamVjdGlvbi4gVGhlIGNvb3JkaW5hdGVzIG9mIGVhY2ggc2FtcGxlIGRlcGVuZCBvbiB0aG9zZSBvZiB0aGUgb3RoZXIgc2FtcGxlcy4gVGhpcyBwcm9qZWN0aW9uIGNhcHR1cmVzICoqYHIgcm91bmQocGNhQ291bnQkZWlnWzIsIDNdLCAyKWAqKiUgb2YgdGhlIHRvdGFsIHZhcmlhYmlsaXR5IGJldHdlZW4gc2FtcGxlcy4gVGhlcmVmb3JlLCBpdCBsYWNrcyAqKmByIDEwMC1yb3VuZChwY2FDb3VudCRlaWdbMiwgM10sIDIpYCoqJSBvZiB0aGUgaW5pdGlhbCBpbmZvcm1hdGlvbi4gSW4gdGhpcyByZXByZXNlbnRhdGlvbiwgdHdvIHBvaW50cyAoc2FtcGxlcykgYXJlIGNsb3NlIHRvZ2V0aGVyIGlmIHRoZXkgaGF2ZSBzaW1pbGFyIGdsb2JhbCB0cmFuc2NyaXB0b21pYyBwcm9maWxlcy4gV2UgZXhwZWN0IHNhbXBsZXMgZnJvbSB0aGUgc2FtZSBiaW9sb2dpY2FsIGNvbmRpdGlvbiB0byBiZSBjbG9zZXIgdG8gZWFjaCBvdGhlciB0aGFuIHRvIHNhbXBsZXMgZnJvbSBvdGhlciBjb25kaXRpb25zLgoKYGBge3IgZmlnLW5vcm1hbGlzYXRpb25fcG9vbGVkX1BDQSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA2fQpwY2FDb3VudCRpbmQkY29vcmQgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIGRwbHlyOjptdXRhdGUoc2FtcGxlID0gcm93bmFtZXMoLikpICU+JQogIGRwbHlyOjpsZWZ0X2pvaW4oeCA9IC4sCiAgICAgICAgICAgICAgICAgICB5ID0gdW5pcXVlKGRlc2lnblssIGMoIlJlcFRlY2hHcm91cCIsICJDb25kaXRpb24iKV0pLAogICAgICAgICAgICAgICAgICAgYnkgPSBjKCJzYW1wbGUiID0gIlJlcFRlY2hHcm91cCIpKSAlPiUKICBwbG90UENBKC4sCiAgICAgICAgICBmaWdfdGl0bGUgPSAgcGFzdGUwKCJQQ0Egb2YgdGhlIG5vcm1hbGlzZWQgY291bnQgbWF0cml4IC0gIiwgcHJvamVjdE5hbWUpKQpgYGAKCiMjIyBOb3JtYWxpc2VkIGNvdW50cyBib3hwbG90CgpUaGlzIGZpZ3VyZSBkaXNwbGF5cywgYXMgYSBib3hwbG90LCB0aGUgbnVtYmVyIG9mIG5vcm1hbGlzZWQgY291bnRzIGZvciBlYWNoIGdlbmUsIHBlciBzYW1wbGUuCgpgYGB7ciBmaWctbm9ybWFsaXNhdGlvbl9ub3JtYWxpc2VkX2JveHBsb3RfY291bnQsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gNn0KbG9nMihERVNlcTI6OmNvdW50cyhkZHMsIG5vcm1hbGl6ZWQgPSBUUlVFKSsxKSAlPiUKICByZXNoYXBlMjo6bWVsdCgpICU+JQogIGBjb2xuYW1lczwtYChjKCJnZW5lX0lEIiwgInNhbXBsZSIsICJsb2cyX2NvdW50cyIpKSAlPiUKICBkcGx5cjo6bXV0YXRlKHNhbXBsZSA9IGZhY3RvcihzYW1wbGUsIGxldmVscyA9IGxldmVscyhkZXNpZ24kUmVwVGVjaEdyb3VwKSkpICU+JQogIGRwbHlyOjpsZWZ0X2pvaW4oeCA9IC4sCiAgICAgICAgICAgICAgICAgICB5ID0gdW5pcXVlKGRlc2lnblssIGMoIlJlcFRlY2hHcm91cCIsICJDb25kaXRpb24iKV0pLAogICAgICAgICAgICAgICAgICAgYnkgPSBjKCJzYW1wbGUiID0gIlJlcFRlY2hHcm91cCIpKSAlPiUKICAjIFBsb3QKICBwbG90Qm94cGxvdCgKICAgIC4sCiAgICB5X2NvbHVtbiA9ICJsb2cyX2NvdW50cyIsCiAgICB5X3RpdGxlID0gImxvZ18yIChjb3VudHMrMSkiLAogICAgZmlnX3RpdGxlID0gcGFzdGUwKCJOb3JtYWxpc2VkIGNvdW50cyBkaXN0cmlidXRpb24gcGVyIHNhbXBsZSAtICIsIHByb2plY3ROYW1lKSkKYGBgCgojIyMgTW9zdCBleHByZXNzZWQgZ2VuZXMgcGxvdAoKV2UgaWRlbnRpZmllZCB0aGUgbW9zdCBleHByZXNzZWQgZ2VuZSBpbiBlYWNoIHNhbXBsZS4KCmBgYHtyIGlkZW50aWZ5X21vc3RfZXhwcmVzc2VkX2dlbmVzfQojIHByZXBhcmF0aW9uIG9mIDIgZGF0YSBmcmFtZSB3aXRoIHRoZSBzYW1lIG51bWJlciBvZiBjb2x1bW4gdGhhbgojIHRoZSBkZHMgY291bnQgbWF0cml4Cm1heENvdW50cyA9IERFU2VxMjo6Y291bnRzKGRkcylbMSxdCnRyYW5zY3JpcHROYW1lcyA9IERFU2VxMjo6Y291bnRzKGRkcylbMSxdCgojIGZvciBlYWNoIHNhbXBsZSAoY29sdW1uKQpmb3IgKGkgaW4gMTpuY29sKERFU2VxMjo6Y291bnRzKGRkcykpKSB7CiAgCiAgIyBzZWxlY3Rpb24gb2YgdGhlIG1heGltdW0gbnVtYmVyIG9mIGNvdW50CiAgbWF4Q291bnRzW2ldID0gKG1heChERVNlcTI6OmNvdW50cyhkZHMsIG5vcm1hbGl6ZWQgPSBUUlVFKVssaV0pLwogICAgICAgICAgICAgICAgICAgIHN1bShERVNlcTI6OmNvdW50cyhkZHMsIG5vcm1hbGl6ZWQgPSBUUlVFKVssaV0pKSoxMDAKICAKICAjIHNlbGVjdGlvbiBvZiB0aGUgbmFtZSBvZiB0aGUgZ2VuZXMgdGhpcyB0aGUgbWF4aW11bSBvZiBjb3VudAogIHRyYW5zY3JpcHROYW1lc1tpXSA9IHJvdy5uYW1lcygKICAgIHN1YnNldChERVNlcTI6OmNvdW50cyhkZHMsIG5vcm1hbGl6ZWQgPSBUUlVFKSwKICAgICAgICAgICBERVNlcTI6OmNvdW50cyhkZHMsIG5vcm1hbGl6ZWQgPSBUUlVFKVssaV0gPT0gCiAgICAgICAgICAgICBtYXgoREVTZXEyOjpjb3VudHMoZGRzLCBub3JtYWxpemVkID0gVFJVRSlbLGldKSkpCn0KYGBgCgpXZSBwcmVwYXJlIGEgZGF0YWZyYW1lIGZvciB0aGUgZ3JhcGhpY2FsIHJlcHJlc2VudGF0aW9uLgoKYGBge3IgbW9zdF9leHByZXNzc2VkX2RmXzEsIGV2YWwgPSBpcy5udWxsKGNvcnJlc3BQYXRoKSwgZWNobyA9IGlzLm51bGwoY29ycmVzcFBhdGgpfQptb3N0X2V4cHJlc3NzZWRfZGYgPSBkcGx5cjo6bGVmdF9qb2luKHggPSBkYXRhLmZyYW1lKHNhbXBsZSA9IG5hbWVzKG1heENvdW50cyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2NvdW50cyA9IG1heENvdW50cyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGRhdGEuZnJhbWUoc2FtcGxlID0gbmFtZXModHJhbnNjcmlwdE5hbWVzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX2lkID0gdHJhbnNjcmlwdE5hbWVzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBieSA9ICJzYW1wbGUiKSAlPiUKICBkcGx5cjo6bXV0YXRlKHNhbXBsZSA9IGZhY3RvcihzYW1wbGUsIGxldmVscyA9IGxldmVscyhkZXNpZ24kUmVwVGVjaEdyb3VwKSkpICU+JQogIGRwbHlyOjpsZWZ0X2pvaW4oeCA9IC4sCiAgICAgICAgICAgICAgICAgICB5ID0gdW5pcXVlKGRlc2lnblssIGMoIlJlcFRlY2hHcm91cCIsICJDb25kaXRpb24iKV0pLAogICAgICAgICAgICAgICAgICAgYnkgPSBjKCJzYW1wbGUiID0gIlJlcFRlY2hHcm91cCIpKQpgYGAKCmBgYHtyIG1vc3RfZXhwcmVzc3NlZF9kZl8yLCBldmFsID0gIWlzLm51bGwoY29ycmVzcFBhdGgpLCBlY2hvID0gIWlzLm51bGwoY29ycmVzcFBhdGgpfQptb3N0X2V4cHJlc3NzZWRfZGYgPSBkcGx5cjo6bGVmdF9qb2luKHggPSBkYXRhLmZyYW1lKHNhbXBsZSA9IG5hbWVzKG1heENvdW50cyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4X2NvdW50cyA9IG1heENvdW50cyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IGRhdGEuZnJhbWUoc2FtcGxlID0gbmFtZXModHJhbnNjcmlwdE5hbWVzKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lX2lkID0gdHJhbnNjcmlwdE5hbWVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVfc3ltYm9sID0gY29ycmVzcElkU3ltYm9sW3RyYW5zY3JpcHROYW1lcywgMl0pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJ5ID0gInNhbXBsZSIpICU+JQogIGRwbHlyOjptdXRhdGUoZ2VuZV9pZCA9IHBhc3RlMChnZW5lX2lkLCAiICgiLCBnZW5lX3N5bWJvbCwgIikiKSkgJT4lCiAgZHBseXI6Om11dGF0ZShzYW1wbGUgPSBmYWN0b3Ioc2FtcGxlLCBsZXZlbHMgPSBsZXZlbHMoZGVzaWduJFJlcFRlY2hHcm91cCkpKSAlPiUKICBkcGx5cjo6bGVmdF9qb2luKHggPSAuLAogICAgICAgICAgICAgICAgICAgeSA9IHVuaXF1ZShkZXNpZ25bLCBjKCJSZXBUZWNoR3JvdXAiLCAiQ29uZGl0aW9uIildKSwKICAgICAgICAgICAgICAgICAgIGJ5ID0gYygic2FtcGxlIiA9ICJSZXBUZWNoR3JvdXAiKSkKYGBgCgpXZSByZXByZXNlbnQgdGhlIGluZm9ybWF0aW9uIGFzIGEgYmFycGxvdC4KCmBgYHtyIGZpZy1ub3JtYWxpc2F0aW9uX21vc3RfZXhwcmVzc2VkX2dlbmVzLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDIrbmNvbChjb3VudF9tYXQpLzR9CmdncGxvdDI6OmdncGxvdChtb3N0X2V4cHJlc3NzZWRfZGYsIGFlcyh4ID0gc2FtcGxlLCB5ID0gbWF4X2NvdW50cywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGwgPSBDb25kaXRpb24sIGxhYmVsID0gZ2VuZV9pZCkpICsKICBnZ3Bsb3QyOjpnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgY29sID0gImJsYWNrIikgKwogIGdncGxvdDI6Omdlb21fbGFiZWwobWFwcGluZyA9IGFlcyh4ID0gc2FtcGxlLCB5ID0gMCksCiAgICAgICAgICAgICAgICAgICAgICBsaW5ld2lkdGggPSAwLCBmaWxsID0gTkEsIGhqdXN0ID0gMCkgKwogIGdncGxvdDI6OnNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yc19jb25kaXRpb24sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWtzID0gbmFtZXMoY29sb3JzX2NvbmRpdGlvbikpICsKICAjIFN0eWxlCiAgZ2dwbG90Mjo6bGFicyh5ID0gIlByb3BvcnRpb24gb2YgcmVhZHMgKCUpIiwKICAgICAgICAgICAgICAgIHRpdGxlID0gcGFzdGUwKCJNb3N0IGV4cHJlc3NlZCBnZW5lcyAtICIsIHByb2plY3ROYW1lKSkgKwogIGdncGxvdDI6OnNjYWxlX3lfY29udGludW91cyhleHBhbmQgPSBjKDAsMCkpICsKICBnZ3Bsb3QyOjpjb29yZF9mbGlwKCkgKwogIGdncGxvdDI6OnRoZW1lX2NsYXNzaWMoKSArCiAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSksCiAgICAgICAgICAgICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSksCiAgICAgICAgICAgICAgICAgIyBUcmFuc3BhcmVudCBiYWNrZ3JvdW5kCiAgICAgICAgICAgICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGdncGxvdDI6OmVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICBwbG90LmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgICAgICAgbGVnZW5kLmJhY2tncm91bmQgPSBnZ3Bsb3QyOjplbGVtZW50X2JsYW5rKCkpCmBgYAoKPCEtLSBDb25kaXRpb25hbCBleGVjdXRpb24gaW4gY2FzZSB0aGUgZm9ybXVsYSBjb250YWlucyBtb3JlIHRoYW4gMiB0ZXJtcyAtLT4KCmBgYHtyIGNoZWNrX25lc3RlZF9tb2RlbHMsIHJlc3VsdHMgPSAnYXNpcycsIGVjaG8gPSBGQUxTRX0KYWxsX3Rlcm1zID0gYmFzZTo6c3Ryc3BsaXQoeCA9IGRlc2VxTW9kZWwsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwbGl0ID0gIlt+Kyo6XSIpICU+JQogIHVubGlzdCgpICU+JQogIC5bLiAhPSAiIl0gJT4lCiAgdW5pcXVlKCkKCmlmIChpcy5udWxsKG5lc3RlZE1vZGVsKSkgewogIG5lc3RlZE1vZGVsID0gKGxlbmd0aChhbGxfdGVybXMpID4gMSkKfQoKaWYgKG5lc3RlZE1vZGVsICYgbGVuZ3RoKGFsbF90ZXJtcykgPT0gMSkgewogIG5lc3RlZE1vZGVsID0gRkFMU0UKICAKICBjYXQoIiMgTmVzdGVkIG1vZGVscyBldmFsdWF0aW9uXG5cbiIpCiAgY2F0KCI8c3BhbiBzdHlsZT0nY29sb3I6cmVkOyc+VGhlIGBuZXN0ZWRNb2RlbGAgcGFyYW1ldGVyIGlzIHNldCB0byBUUlVFIGJ1dCB0aGVyZSBpcyBvbmx5IG9uZSB0ZXJtIGluIHRoZSBmb3JtdWxhLiBUaGUgc3RlcCBpcyBza2lwcGVkLjwvc3Bhbj5cbiIpCn0KYGBgCgpgYGB7ciBuZXN0ZWRfbW9kZWxzLCBlY2hvID0gRkFMU0UsIHJlc3VsdHMgPSAnYXNpcycsIGV2YWwgPSBuZXN0ZWRNb2RlbH0KcmVzID0ga25pdHI6OmtuaXRfY2hpbGQoCiAgaW5wdXQgPSAnMDVfY2hpbGRfbmVzdGVkX21vZGVscy5SbWQnLAogIHF1aWV0ID0gVFJVRSkKCmNhdChyZXMsIHNlcCA9ICdcbicpCmBgYAoKPCEtLSBDb25kaXRpb25hbCBleGVjdXRpb24gaW4gY2FzZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyBzaG91bGQgYmUgZG9uZSAtLT4KCmBgYHtyIGRpZmZhbmEsIGVjaG8gPSBGQUxTRSwgcmVzdWx0cyA9ICdhc2lzJywgZXZhbCA9IGRpZmZhbmFUZXN0fQpyZXMgPSBrbml0cjo6a25pdF9jaGlsZCgKICBpbnB1dCA9ICcwM19jaGlsZF9kaWZmZXJlbnRpYWxfZXhwcmVzc2lvbi5SbWQnLAogIHF1aWV0ID0gVFJVRSkKCmNhdChyZXMsIHNlcCA9ICdcbicpCmBgYAoKCiMgVmVyc2lvbnMKCmBgYHtyIHNlc3Npb25pbmZvLCBlY2hvID0gRkFMU0UsIGZvbGRfb3V0cHV0ID0gVFJVRSwgcmVzdWx0cyA9ICJob2xkIn0KcHJpbnQoIlBhY2thZ2VzIGxvY2F0aW9uOiIpCi5saWJQYXRocygpCnByaW50KCIiKQpwcmludCgiU2Vzc2lvbiBJbmZvcm1hdGlvbjoiKQpzZXNzaW9uSW5mbygpCmBgYAoKIyBSZWZlcmVuY2UKClRoZSBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyB1c2VzIHRoZSBgREVTZXEyYCBSIHBhY2thZ2UgKFt2aWduZXR0ZV0oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL3BhY2thZ2VzL2RldmVsL2Jpb2MvdmlnbmV0dGVzL0RFU2VxMi9pbnN0L2RvYy9ERVNlcTIuaHRtbCkpLgoKPiBMb3ZlLCBNLkkuLCBIdWJlciwgVy4gJiBBbmRlcnMsIFMuLCBNb2RlcmF0ZWQgZXN0aW1hdGlvbiBvZiBmb2xkIGNoYW5nZSBhbmQgZGlzcGVyc2lvbiBmb3IgUk5BLXNlcSBkYXRhIHdpdGggREVTZXEyLiBHZW5vbWUgQmlvbCAxNSwgNTUwICgyMDE0KS4gRE9JOiBbMTAuMTE4Ni9zMTMwNTktMDE0LTA1NTAtOF0oaHR0cHM6Ly9kb2kub3JnLzEwLjExODYvczEzMDU5LTAxNC0wNTUwLTgpCg==