0.0.1 Install and load packages

library(tidyverse)
library(ggplot2)
library(WGCNA)
library(magrittr)
library(DESeq2)
library(genefilter)

Used Jennifer Chang’s Bioinformatic Workflow post on WGCNA analysis as a very helpful guide.

NOTE: WGCNA is recommended for use with at least 15 samples to draw meaningful conclusions. We only have 5 samples from each species in our deep-dive dataset, so this code is primarily intended to be a trial run, developing an RNA-sRNA WGCNA coexpression pipeline in preparation for our time series data.

1 Prep RNA data

1.1 Load count data

Load in the count matrix we generated after kallisto pseudoalignment using the Trinity abundance_estimates_to_matrix.pl script. We also need to slightly reformat the count matrix, as required for DESeq2.

# Read in counts data. This is a gene-level counts matrix generated from kallisto transcript abundances using Trinity
Apul_counts_RNA_data_OG <- read_delim("../../../deep-dive/D-Apul/output/14-Apul-RNAseq-kallisto/kallisto/kallisto.isoform.counts.matrix") 
head(Apul_counts_RNA_data_OG)
# A tibble: 6 × 6
  ...1      kallisto_quant_sampl…¹ kallisto_quant_sampl…² kallisto_quant_sampl…³
  <chr>                      <dbl>                  <dbl>                  <dbl>
1 XM_04431…                  108                     46                    63   
2 XR_00639…                    0                      0                     0   
3 XM_02934…                  467.                   337.                  362.  
4 XM_04432…                   43.5                   20.7                   5.14
5 XR_00639…                  108.                     0                     0   
6 XM_04431…                   20.4                    0                    21.3 
# ℹ abbreviated names: ¹​kallisto_quant_sample140, ²​kallisto_quant_sample145,
#   ³​kallisto_quant_sample150
# ℹ 2 more variables: kallisto_quant_sample173 <dbl>,
#   kallisto_quant_sample178 <dbl>

1.2 Count data munging

# We need to modify this data frame so that the row names are actually row names, instead of comprising the first column
Apul_counts_RNA <- Apul_counts_RNA_data_OG %>%
  column_to_rownames(var = "...1")

# Additional formatting
# Round all estimated counts to integers
Apul_counts_RNA <- round(Apul_counts_RNA, digits = 0)

# Remove all transcripts with 5 or fewer counts in all samples
Apul_counts_RNA <- Apul_counts_RNA[!apply(Apul_counts_RNA, 1, function(row) all(row < 6)), ]

# Remove the "kallisto_quant_" portion of the column names, to leave just the sample names
colnames(Apul_counts_RNA) <- sub("kallisto_quant_", "", colnames(Apul_counts_RNA))

Apul_sample_names <- names(Apul_counts_RNA)

head(Apul_counts_RNA)
               sample140 sample145 sample150 sample173 sample178
XM_044313592.1       108        46        63       105       131
XR_006395886.1         0         0         0         6       139
XM_029343024.2       467       337       362       570      1234
XM_044327972.1        43        21         5        12        41
XR_006392795.1       108         0         0         0         0
XM_044315346.1        20         0        21        18       232
Apul_sample_names
[1] "sample140" "sample145" "sample150" "sample173" "sample178"

1.3 Normalize counts with DESeq2

1.3.1 Plot unnormalized data

Apul_counts_RNA %>%
  pivot_longer( cols = everything(), names_to = "sample", values_to = "count") %>%
  ggplot(., aes(x = sample, y = count)) +
  geom_violin() + 
  geom_point(alpha = 0.2) +
  theme_minimal() +
  labs(title = "Unnormalized transcript counts",
       x = "Sample",
       y = "count")

We definitely need to normalize this data!

1.3.2 Metadata

DESeq2 requires a metadata data frame as input. I don’t have sample metadata though so, since we’re just doing DESeq2 for normalization purposes (not analysis purposes), I’m just going to create a dummy sheet

Apul_metadata_RNA <- data.frame(Sample = Apul_sample_names,
                            Species = rep("A.pulchra", 5))
rownames(Apul_metadata_RNA) <- Apul_sample_names

head(Apul_metadata_RNA)
             Sample   Species
sample140 sample140 A.pulchra
sample145 sample145 A.pulchra
sample150 sample150 A.pulchra
sample173 sample173 A.pulchra
sample178 sample178 A.pulchra

1.3.3 DESeq object

# Calculate DESeq object
dds_Apul_RNA <- DESeqDataSetFromMatrix(countData = Apul_counts_RNA,
                              colData = Apul_metadata_RNA,
                              design = ~ 1) 

# Run differential expression analysis 
# (Note that this DESeq() function runs all necessary steps, including data normalization, 
# estimating size factors, estimating dispersions, gene-wise dispersion estimates, mean-dispersion 
# relationship, final dispersion estimates, fitting model, and testing)
# Using design = ~1 because we don't have treatment groups

dds_Apul_RNA <- DESeq(dds_Apul_RNA)
vsd_Apul_RNA <- varianceStabilizingTransformation(dds_Apul_RNA, blind=TRUE)
wpn_vsd_Apul_RNA <- getVarianceStabilizedData(dds_Apul_RNA)
rv_wpn_Apul_RNA <- rowVars(wpn_vsd_Apul_RNA)

q75_wpn_Apul_RNA <- quantile( rowVars(wpn_vsd_Apul_RNA), .75)  # <= original
q95_wpn_Apul_RNA <- quantile( rowVars(wpn_vsd_Apul_RNA), .95)  # <= changed to 95 quantile to reduce dataset
Apul_counts_RNA_norm <- wpn_vsd_Apul_RNA[ rv_wpn_Apul_RNA > q95_wpn_Apul_RNA, ] # filter to retain only the most variable genes

Apul_counts_RNA_norm <- data.frame(Apul_counts_RNA_norm) 

write.table(Apul_counts_RNA_norm, file = "../output/04-Apul-RNA-sRNA-WGCNA/Apul_counts_RNA_normalized_q95.txt", sep = "\t", row.names = TRUE, quote = FALSE)

1.4 Plot normalized data

Apul_counts_RNA_norm_long <- Apul_counts_RNA_norm %>%
  mutate(
    Gene_id = row.names(Apul_counts_RNA_norm)
  ) %>%
  pivot_longer(-Gene_id)

Apul_counts_RNA_norm_long %>%
  ggplot(., aes(x = name, y = value)) +
  geom_violin() +
  geom_point() +
  theme_bw() +
  theme(
    axis.text.x = element_text( angle = 90)
  ) +
  ylim(0, NA) +
  labs(
    title = "Normalized and 95 quantile Expression",
    x = "Sample",
    y = "Normalized counts"
  )

2 Prep miRNA data

2.1 Load count data

Load in the miRNA count matrix generated by ShortStack during miRNA identification

# Read in sRNA counts data
Apul_counts_sRNA_data_OG <- read_delim("../../../deep-dive/D-Apul/output/13.2.1-Apul-sRNAseq-ShortStack-31bp-fastp-merged-cnidarian_miRBase/ShortStack_out/Counts.txt", delim="\t") 
head(Apul_counts_sRNA_data_OG)
# A tibble: 6 × 8
  Coords               Name  MIRNA sRNA-ACR-140-S1-TP2-…¹ sRNA-ACR-145-S1-TP2-…²
  <chr>                <chr> <chr>                  <dbl>                  <dbl>
1 NC_058066.1:152483-… Clus… N                          2                    131
2 NC_058066.1:161064-… Clus… N                         57                     48
3 NC_058066.1:172073-… Clus… N                         36                     31
4 NC_058066.1:203242-… Clus… N                         14                     28
5 NC_058066.1:204535-… Clus… N                          3                    234
6 NC_058066.1:205745-… Clus… N                        914                    432
# ℹ abbreviated names: ¹​`sRNA-ACR-140-S1-TP2-fastp-adapters-polyG-31bp-merged`,
#   ²​`sRNA-ACR-145-S1-TP2-fastp-adapters-polyG-31bp-merged`
# ℹ 3 more variables:
#   `sRNA-ACR-150-S1-TP2-fastp-adapters-polyG-31bp-merged` <dbl>,
#   `sRNA-ACR-173-S1-TP2-fastp-adapters-polyG-31bp-merged` <dbl>,
#   `sRNA-ACR-178-S1-TP2-fastp-adapters-polyG-31bp-merged` <dbl>

2.2 Count data munging

Apul_counts_sRNA <- Apul_counts_sRNA_data_OG

# Remove excess portions of sample column names to just "sample###"
colnames(Apul_counts_sRNA) <- sub("-S1-TP2-fastp-adapters-polyG-31bp-merged", "", colnames(Apul_counts_sRNA))
colnames(Apul_counts_sRNA) <- sub("sRNA-ACR-", "sample", colnames(Apul_counts_sRNA))

# Keep just the counts and cluster names
Apul_counts_sRNA <- Apul_counts_sRNA %>% select("sample140", "sample145", "sample150", "sample173", "sample178", "Name")

# Make the cluster names our new row names
Apul_counts_sRNA <- Apul_counts_sRNA %>% column_to_rownames(var = "Name")

head(Apul_counts_sRNA)
          sample140 sample145 sample150 sample173 sample178
Cluster_1         2       131         2         1         4
Cluster_2        57        48       219        32       193
Cluster_3        36        31         0        36         2
Cluster_4        14        28         3        17        38
Cluster_5         3       234        17        13        46
Cluster_6       914       432        78       247       259

2.3 Normalize counts with DESeq2

2.3.1 Plot unnormalized data

Apul_counts_sRNA %>%
  pivot_longer( cols = everything(), names_to = "sample", values_to = "count") %>%
  ggplot(., aes(x = sample, y = count)) +
  geom_violin() + 
  geom_point(alpha = 0.2) +
  theme_minimal() +
  labs(title = "Unnormalized transcript counts",
       x = "Sample",
       y = "count")

We definitely need to normalize this data!

2.3.2 Metadata

DESeq2 requires a metadata data frame as input. I don’t have sample metadata though so, since we’re just doing DESeq2 for normalization purposes (not analysis purposes), I’m just going to create a dummy sheet

Apul_metadata_RNA <- data.frame(Sample = Apul_sample_names,
                            Species = rep("A.pulchra", 5))
rownames(Apul_metadata_RNA) <- Apul_sample_names

head(Apul_metadata_RNA)
             Sample   Species
sample140 sample140 A.pulchra
sample145 sample145 A.pulchra
sample150 sample150 A.pulchra
sample173 sample173 A.pulchra
sample178 sample178 A.pulchra

2.3.3 DESeq object

# Calculate DESeq object
dds_Apul_sRNA <- DESeqDataSetFromMatrix(countData = Apul_counts_sRNA,
                              colData = Apul_metadata_RNA,
                              design = ~ 1) 

# Run differential expression analysis 
# (Note that this DESeq() function runs all necessary steps, including data normalization, 
# estimating size factors, estimating dispersions, gene-wise dispersion estimates, mean-dispersion 
# relationship, final dispersion estimates, fitting model, and testing)
# Using design = ~1 because we don't have treatment groups

dds_Apul_sRNA <- DESeq(dds_Apul_sRNA)
vsd_Apul_sRNA <- varianceStabilizingTransformation(dds_Apul_sRNA, blind=TRUE)
wpn_vsd_Apul_sRNA <- getVarianceStabilizedData(dds_Apul_sRNA)
rv_wpn_Apul_sRNA <- rowVars(wpn_vsd_Apul_sRNA)

# For now let's retain all sRNAs, regardless of expression level
# q75_wpn_Apul_sRNA <- quantile( rowVars(wpn_vsd_Apul_sRNA), .75)  
# q90_wpn_Apul_sRNA <- quantile( rowVars(wpn_vsd_Apul_sRNA), .9)  
# Apul_counts_sRNA_norm <- wpn_vsd_Apul_RNA[ rv_wpn_Apul_sRNA > q50_wpn_Apul_sRNA, ] # filter to retain only the more variable genes
Apul_counts_sRNA_norm <- wpn_vsd_Apul_sRNA

Apul_counts_sRNA_norm <- data.frame(Apul_counts_sRNA_norm)

write.table(Apul_counts_sRNA_norm, file = "../output/04-Apul-RNA-sRNA-WGCNA/Apul_counts_sRNA_normalized.txt", sep = "\t", row.names = TRUE, quote = FALSE)

2.4 Plot normalized data

Apul_counts_sRNA_norm_df_long <- Apul_counts_sRNA_norm %>%
  mutate(
    Gene_id = row.names(Apul_counts_sRNA_norm)
  ) %>%
  pivot_longer(-Gene_id)

Apul_counts_sRNA_norm_df_long %>%
  ggplot(., aes(x = name, y = value)) +
  geom_violin() +
  geom_point() +
  theme_bw() +
  theme(
    axis.text.x = element_text( angle = 90)
  ) +
  ylim(0, NA) +
  labs(
    title = "Normalized and 95 quantile Expression",
    x = "Sample",
    y = "Normalized counts"
  )

2.5 Normalized count data munging

# The ShortStack output Results.txt includes all clusters of sRNA reads, including those not annotated as valid miRNAs. Now that we've normalized the counts,we need to filter out all the clusters that are not miRNAs.

# Join with full metadata sheet, which only contains valid miRNAs
Apul_metadata_miRNA <- read_csv("../../../deep-dive/DEF-cross-species/output/10-shortRNA-ShortStack-comparison/Apul_results_mature_named.csv") 

Apul_counts_sRNA_norm <- rownames_to_column(Apul_counts_sRNA_norm, var = "Name")

Apul_counts_miRNA_norm <- left_join(Apul_metadata_miRNA, Apul_counts_sRNA_norm, by = c("Name" = "Name"))

# Keep just the counts and given miRNA names (e.g., based on match to previously described miRNA)
Apul_counts_miRNA_norm <- Apul_counts_miRNA_norm %>% select("sample140", "sample145", "sample150", "sample173", "sample178", "given_miRNA_name")

# Make the miRNA names our new row names
Apul_counts_miRNA_norm <- Apul_counts_miRNA_norm %>% column_to_rownames(var = "given_miRNA_name")

head(Apul_counts_miRNA_norm)
                 sample140 sample145 sample150 sample173 sample178
apul-mir-novel-2 15.017520 15.316693 16.004786 15.163512  16.04577
apul-mir-novel-1  2.812493  9.711462 10.284587  2.168756  12.82941
apul-mir-novel-7  5.548060  5.194907  4.618254  5.786394   4.68501
apul-mir-novel-4 10.705670 12.292874 12.759827 12.335792  13.40232
apul-mir-novel-6  9.839852 10.848441 11.050479 11.991578  11.78180
apul-mir-novel-3 10.410008 10.470898 10.476884 11.186923  11.08251

3 Merge RNA and miRNA normalized counts

Now that we have normalized counts of RNA and miRNA for all of our samples, let’s combine them into one dataset to feed into WGCNA

# Merge
Apul_counts_WGCNA <- bind_rows(Apul_counts_miRNA_norm, Apul_counts_RNA_norm)

# Convert from data frame to matrix
Apul_counts_WGCNA <- as.matrix(Apul_counts_WGCNA)

# Transpose the normalized count data to meet WGCNA required input format
Apul_counts_WGCNA = t(Apul_counts_WGCNA)

4 WGCNA

Now we’re ready to run WGCNA!

allowWGCNAThreads()          # allow multi-threading (optional)
Allowing multi-threading with up to 48 threads.
# Choose a set of soft-thresholding powers
powers = c(c(1:10), seq(from = 12, to = 20, by = 2))

# Call the network topology analysis function
sft_Apul = pickSoftThreshold(
  Apul_counts_WGCNA,             # <= Input data
  #blockSize = 30,
  powerVector = powers,
  verbose = 5
  )
pickSoftThreshold: will use block size 2129.
 pickSoftThreshold: calculating connectivity for given powers...
   ..working on genes 1 through 2129 of 2129
   Power SFT.R.sq   slope truncated.R.sq mean.k. median.k. max.k.
1      1 1.13e-01  3.4000          0.832   914.0     913.0  996.0
2      2 2.75e-02 -1.1000          0.909   540.0     537.0  632.0
3      3 1.05e-01 -1.5300          0.951   368.0     365.0  453.0
4      4 1.37e-01 -1.5600          0.930   273.0     269.0  351.0
5      5 8.03e-02 -1.1800          0.879   214.0     210.0  286.0
6      6 1.73e-02 -0.5880          0.793   175.0     171.0  242.0
7      7 1.07e-02 -0.4420          0.848   147.0     145.0  210.0
8      8 3.96e-03 -0.2540          0.899   126.0     124.0  186.0
9      9 2.13e-05  0.0173          0.930   110.0     108.0  167.0
10    10 4.37e-03  0.2340          0.951    97.9      95.5  152.0
11    12 1.05e-02  0.3010          0.965    79.6      76.8  129.0
12    14 1.64e-02  0.3050          0.954    66.9      64.8  112.0
13    16 5.37e-02  0.4400          0.938    57.5      55.5   98.4
14    18 6.11e-02  0.3980          0.915    50.3      48.6   88.3
15    20 3.59e-02  0.2610          0.867    44.7      42.9   81.2
par(mfrow = c(1,2));
cex1 = 0.9;

plot(sft_Apul$fitIndices[, 1],
     -sign(sft_Apul$fitIndices[, 3]) * sft_Apul$fitIndices[, 2],
     xlab = "Soft Threshold (power)",
     ylab = "Scale Free Topology Model Fit, signed R^2",
     main = paste("Scale independence")
)
text(sft_Apul$fitIndices[, 1],
     -sign(sft_Apul$fitIndices[, 3]) * sft_Apul$fitIndices[, 2],
     labels = powers, cex = cex1, col = "red"
)
abline(h = 0.90, col = "red")
plot(sft_Apul$fitIndices[, 1],
     sft_Apul$fitIndices[, 5],
     xlab = "Soft Threshold (power)",
     ylab = "Mean Connectivity",
     type = "n",
     main = paste("Mean connectivity")
)
text(sft_Apul$fitIndices[, 1],
     sft_Apul$fitIndices[, 5],
     labels = powers,
     cex = cex1, col = "red")

I’m a little weirded out by the double curve in the Scale Independence plot, but this is a prelim test so I’m not going to worry about it right now. We want to pick a soft threshold power near the curve of the plot, so maybe 6, 7, 8, or 9? Let’s pick 9 for now, but maybe experiment with other powers later.

picked_power = 9
temp_cor <- cor       
cor <- WGCNA::cor         # Force it to use WGCNA cor function (fix a namespace conflict issue)
netwk_Apul <- blockwiseModules(Apul_counts_WGCNA,                # <= input here

                          # == Adjacency Function ==
                          power = picked_power,                # <= power here
                          networkType = "signed",

                          # == Tree and Block Options ==
                          deepSplit = 2,
                          pamRespectsDendro = F,
                          # detectCutHeight = 0.75,
                          minModuleSize = 30,
                          maxBlockSize = 4000,

                          # == Module Adjustments ==
                          reassignThreshold = 0,
                          mergeCutHeight = 0.25,

                          # == TOM == Archive the run results in TOM file (saves time)
                          saveTOMs = T,
                          saveTOMFileBase = "ER",

                          # == Output Options
                          numericLabels = T,
                          verbose = 3)
 Calculating module eigengenes block-wise from all genes
   Flagging genes and samples with too many missing values...
    ..step 1
 ..Working on block 1 .
    TOM calculation: adjacency..
    ..will use 48 parallel threads.
     Fraction of slow calculations: 0.000000
    ..connectivity..
    ..matrix multiplication (system BLAS)..
    ..normalization..
    ..done.
   ..saving TOM for block 1 into file ER-block.1.RData
 ....clustering..
 ....detecting modules..
 ....calculating module eigengenes..
 ....checking kME in modules..
 ..merging modules that are too close..
     mergeCloseModules: Merging modules whose distance is less than 0.25
       Calculating new MEs...
cor <- temp_cor     # Return cor function to original namespace
# Check if there are any files starting with "ER" in the current directory
if ls ER-block* 1> /dev/null 2>&1; then
    # Move the files if they exist
    mv ER-block* ../output/04-Apul-RNA-sRNA-WGCNA
else
    echo "No files starting with 'ER' found."
fi
No files starting with 'ER' found.

Take a look

# Convert labels to colors for plotting
mergedColors = labels2colors(netwk_Apul$colors)
# Plot the dendrogram and the module colors underneath
plotDendroAndColors(
  netwk_Apul$dendrograms[[1]],
  mergedColors[netwk_Apul$blockGenes[[1]]],
  "Module colors",
  dendroLabels = FALSE,
  hang = 0.03,
  addGuide = TRUE,
  guideHang = 0.05 )

module_df_Apul <- data.frame(
  gene_id = names(netwk_Apul$colors),
  colors = labels2colors(netwk_Apul$colors)
)

module_df_Apul[1:5,]
           gene_id    colors
1 apul-mir-novel-2 turquoise
2 apul-mir-novel-1   darkred
3 apul-mir-novel-7       tan
4 apul-mir-novel-4     green
5 apul-mir-novel-6     green
# Get Module Eigengenes per cluster
MEs0_Apul <- moduleEigengenes(Apul_counts_WGCNA, mergedColors)$eigengenes

# # Reorder modules so similar modules are next to each other
# MEs0_Apul <- orderMEs(MEs0_Apul)
# module_order_Apul = names(MEs0_Apul) %>% gsub("ME","", .)

# Add treatment names
MEs0_Apul$sample = row.names(MEs0_Apul)

# tidy & plot data
mME_Apul = MEs0_Apul %>%
  pivot_longer(-sample) %>%
  mutate(
    name = gsub("ME", "", name),
    # name = factor(name, levels = module_order)
  )

mME_Apul %>% ggplot(., aes(x=sample, y=name, fill=value)) +
  geom_tile() +
  theme_bw() +
  scale_fill_gradient2(
    low = "blue",
    high = "red",
    mid = "white",
    midpoint = 0,
    limit = c(-1,1)) +
  theme(axis.text.x = element_text(angle=90)) +
  labs(title = "Module-trait Relationships", y = "Modules", fill="corr")

# Check which modules include miRNAs
module_df_Apul %>%
  filter(grepl("mir",gene_id)) %>%
  pull(colors) %>%
  unique()
 [1] "turquoise"   "darkred"     "tan"         "green"       "red"        
 [6] "grey60"      "darkgreen"   "blue"        "greenyellow" "royalblue"  
[11] "pink"        "magenta"     "yellow"      "lightcyan"   "lightgreen" 
[16] "lightyellow"
module_df_Apul %>%
  filter(grepl("mir",gene_id)) %>%
  arrange(colors) %>%
  head(n=38)
              gene_id      colors
1    apul-mir-novel-9        blue
2   apul-mir-novel-19        blue
3   apul-mir-novel-21        blue
4       apul-mir-2022   darkgreen
5   apul-mir-novel-16   darkgreen
6    apul-mir-novel-1     darkred
7   apul-mir-novel-10     darkred
8       apul-mir-2030     darkred
9    apul-mir-novel-4       green
10   apul-mir-novel-6       green
11  apul-mir-novel-8a       green
12  apul-mir-novel-13       green
13       apul-mir-100       green
14  apul-mir-novel-17       green
15  apul-mir-novel-20       green
16  apul-mir-novel-11 greenyellow
17  apul-mir-novel-15 greenyellow
18   apul-mir-novel-5      grey60
19  apul-mir-novel-22   lightcyan
20 apul-mir-novel-24b  lightgreen
21  apul-mir-novel-26 lightyellow
22  apul-mir-novel-14     magenta
23 apul-mir-novel-24a     magenta
24      apul-mir-9425        pink
25      apul-mir-2028        pink
26  apul-mir-novel-18        pink
27      apul-mir-2050        pink
28   apul-mir-novel-3         red
29  apul-mir-novel-8b         red
30      apul-mir-2025         red
31      apul-mir-2023         red
32 apul-mir-novel-25a         red
33 apul-mir-novel-25b         red
34  apul-mir-novel-12   royalblue
35   apul-mir-novel-7         tan
36   apul-mir-novel-2   turquoise
37  apul-mir-novel-23   turquoise
38      apul-mir-2036      yellow
# pick out a few modules of interest. Let's do some of the modules that contain previously described miRNAs, since we have the best idea of their function
modules_of_interest = c("green", "pink", "red")
# Define the colors corresponding to the modules of interest
module_colors <- c("green" = "green", "pink" = "pink", "red" = "red")

# Pull out list of genes in that module
submod_Apul = module_df_Apul %>%
  subset(colors %in% modules_of_interest)

row.names(module_df_Apul) = module_df_Apul$gene_id

subexpr_Apul = t(Apul_counts_WGCNA)[submod_Apul$gene_id,]

submod_df_Apul = data.frame(subexpr_Apul) %>%
  mutate(
    gene_id = row.names(.)
  ) %>%
  pivot_longer(-gene_id) %>%
  mutate(
    module = module_df_Apul[gene_id,]$colors
  )

submod_df_Apul %>% ggplot(., aes(x=name, y=value, group=gene_id)) +
  geom_line(aes(color = module),
            alpha = 0.2) +
  scale_color_manual(values = module_colors) +
  theme_bw() +
  theme(
    axis.text.x = element_text(angle = 90)
  ) +
  facet_grid(rows = vars(module)) +
  labs(x = "treatment",
       y = "normalized expression")

LS0tCnRpdGxlOiAiMDQtQXB1bC1STkEtc1JOQS1XR0NOQSIKYXV0aG9yOiAiS2F0aGxlZW4gRHVya2luIgpkYXRlOiAiMjAyNC0wOC0yNiIKYWx3YXlzX2FsbG93X2h0bWw6IHRydWUKb3V0cHV0OiAKICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6CiAgICB0aGVtZTogY29zbW8KICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGNvZGVfZm9sZGluZzogc2hvdwogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogIGdpdGh1Yl9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIGh0bWxfcHJldmlldzogdHJ1ZSAKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KbGlicmFyeShrbml0cikKa25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGVjaG8gPSBUUlVFLCAgICAgICAgICMgRGlzcGxheSBjb2RlIGNodW5rcwogIGV2YWwgPSBUUlVFLCAgICAgICAgICMgRXZhbHVhdGUgY29kZSBjaHVua3MKICB3YXJuaW5nID0gRkFMU0UsICAgICAjIEhpZGUgd2FybmluZ3MKICBtZXNzYWdlID0gRkFMU0UsICAgICAjIEhpZGUgbWVzc2FnZXMKICBjb21tZW50ID0gIiIgICAgICAgICAjIFByZXZlbnRzIGFwcGVuZGluZyAnIyMnIHRvIGJlZ2lubmluZyBvZiBsaW5lcyBpbiBjb2RlIG91dHB1dAopCmBgYAoKIyMjIEluc3RhbGwgYW5kIGxvYWQgcGFja2FnZXMKCmBgYHtyIGxvYWRfbGlicmFyaWVzLCBpbmxjdWRlID0gVFJVRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShXR0NOQSkKbGlicmFyeShtYWdyaXR0cikKbGlicmFyeShERVNlcTIpCmxpYnJhcnkoZ2VuZWZpbHRlcikKYGBgCgpVc2VkIEplbm5pZmVyIENoYW5nJ3MgQmlvaW5mb3JtYXRpYyBXb3JrZmxvdyBbcG9zdCBvbiBXR0NOQSBhbmFseXNpc10oaHR0cHM6Ly9iaW9pbmZvcm1hdGljc3dvcmtib29rLm9yZy90dXRvcmlhbHMvd2djbmEuaHRtbCNnc2MudGFiPTApIGFzIGEgdmVyeSBoZWxwZnVsIGd1aWRlLgoKKipOT1RFOiBXR0NOQSBpcyByZWNvbW1lbmRlZCBmb3IgdXNlIHdpdGggYXQgKmxlYXN0KiAxNSBzYW1wbGVzIHRvIGRyYXcgbWVhbmluZ2Z1bCBjb25jbHVzaW9ucy4gV2Ugb25seSBoYXZlIDUgc2FtcGxlcyBmcm9tIGVhY2ggc3BlY2llcyBpbiBvdXIgZGVlcC1kaXZlIGRhdGFzZXQsIHNvIHRoaXMgY29kZSBpcyBwcmltYXJpbHkgaW50ZW5kZWQgdG8gYmUgYSB0cmlhbCBydW4sIGRldmVsb3BpbmcgYW4gUk5BLXNSTkEgV0dDTkEgY29leHByZXNzaW9uIHBpcGVsaW5lIGluIHByZXBhcmF0aW9uIGZvciBvdXIgKnRpbWUgc2VyaWVzIGRhdGEqLioqCgojIFByZXAgUk5BIGRhdGEKCiMjIExvYWQgY291bnQgZGF0YQoKTG9hZCBpbiB0aGUgY291bnQgbWF0cml4IHdlIGdlbmVyYXRlZCBhZnRlciBrYWxsaXN0byBwc2V1ZG9hbGlnbm1lbnQgdXNpbmcgdGhlIFRyaW5pdHkgYWJ1bmRhbmNlX2VzdGltYXRlc190b19tYXRyaXgucGwgc2NyaXB0LiBXZSBhbHNvIG5lZWQgdG8gc2xpZ2h0bHkgcmVmb3JtYXQgdGhlIGNvdW50IG1hdHJpeCwgYXMgcmVxdWlyZWQgZm9yIERFU2VxMi4KCmBgYHtyIGxvYWQtUk5BLWNvdW50c30KIyBSZWFkIGluIGNvdW50cyBkYXRhLiBUaGlzIGlzIGEgZ2VuZS1sZXZlbCBjb3VudHMgbWF0cml4IGdlbmVyYXRlZCBmcm9tIGthbGxpc3RvIHRyYW5zY3JpcHQgYWJ1bmRhbmNlcyB1c2luZyBUcmluaXR5CkFwdWxfY291bnRzX1JOQV9kYXRhX09HIDwtIHJlYWRfZGVsaW0oIi4uLy4uLy4uL2RlZXAtZGl2ZS9ELUFwdWwvb3V0cHV0LzE0LUFwdWwtUk5Bc2VxLWthbGxpc3RvL2thbGxpc3RvL2thbGxpc3RvLmlzb2Zvcm0uY291bnRzLm1hdHJpeCIpIApoZWFkKEFwdWxfY291bnRzX1JOQV9kYXRhX09HKQpgYGAKCiMjIENvdW50IGRhdGEgbXVuZ2luZwoKYGBge3IgUk5BLWNvdW50LWRhdGEtbXVuZ2luZ30KIyBXZSBuZWVkIHRvIG1vZGlmeSB0aGlzIGRhdGEgZnJhbWUgc28gdGhhdCB0aGUgcm93IG5hbWVzIGFyZSBhY3R1YWxseSByb3cgbmFtZXMsIGluc3RlYWQgb2YgY29tcHJpc2luZyB0aGUgZmlyc3QgY29sdW1uCkFwdWxfY291bnRzX1JOQSA8LSBBcHVsX2NvdW50c19STkFfZGF0YV9PRyAlPiUKICBjb2x1bW5fdG9fcm93bmFtZXModmFyID0gIi4uLjEiKQoKIyBBZGRpdGlvbmFsIGZvcm1hdHRpbmcKIyBSb3VuZCBhbGwgZXN0aW1hdGVkIGNvdW50cyB0byBpbnRlZ2VycwpBcHVsX2NvdW50c19STkEgPC0gcm91bmQoQXB1bF9jb3VudHNfUk5BLCBkaWdpdHMgPSAwKQoKIyBSZW1vdmUgYWxsIHRyYW5zY3JpcHRzIHdpdGggNSBvciBmZXdlciBjb3VudHMgaW4gYWxsIHNhbXBsZXMKQXB1bF9jb3VudHNfUk5BIDwtIEFwdWxfY291bnRzX1JOQVshYXBwbHkoQXB1bF9jb3VudHNfUk5BLCAxLCBmdW5jdGlvbihyb3cpIGFsbChyb3cgPCA2KSksIF0KCiMgUmVtb3ZlIHRoZSAia2FsbGlzdG9fcXVhbnRfIiBwb3J0aW9uIG9mIHRoZSBjb2x1bW4gbmFtZXMsIHRvIGxlYXZlIGp1c3QgdGhlIHNhbXBsZSBuYW1lcwpjb2xuYW1lcyhBcHVsX2NvdW50c19STkEpIDwtIHN1Yigia2FsbGlzdG9fcXVhbnRfIiwgIiIsIGNvbG5hbWVzKEFwdWxfY291bnRzX1JOQSkpCgpBcHVsX3NhbXBsZV9uYW1lcyA8LSBuYW1lcyhBcHVsX2NvdW50c19STkEpCgpoZWFkKEFwdWxfY291bnRzX1JOQSkKQXB1bF9zYW1wbGVfbmFtZXMKYGBgCgojIyBOb3JtYWxpemUgY291bnRzIHdpdGggREVTZXEyCgojIyMgUGxvdCB1bm5vcm1hbGl6ZWQgZGF0YQoKYGBge3IgcGxvdC11bm5vcm1hbGl6ZWQtUk5BfQoKQXB1bF9jb3VudHNfUk5BICU+JQogIHBpdm90X2xvbmdlciggY29scyA9IGV2ZXJ5dGhpbmcoKSwgbmFtZXNfdG8gPSAic2FtcGxlIiwgdmFsdWVzX3RvID0gImNvdW50IikgJT4lCiAgZ2dwbG90KC4sIGFlcyh4ID0gc2FtcGxlLCB5ID0gY291bnQpKSArCiAgZ2VvbV92aW9saW4oKSArIAogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjIpICsKICB0aGVtZV9taW5pbWFsKCkgKwogIGxhYnModGl0bGUgPSAiVW5ub3JtYWxpemVkIHRyYW5zY3JpcHQgY291bnRzIiwKICAgICAgIHggPSAiU2FtcGxlIiwKICAgICAgIHkgPSAiY291bnQiKQpgYGAKCldlIGRlZmluaXRlbHkgbmVlZCB0byBub3JtYWxpemUgdGhpcyBkYXRhIQoKIyMjIE1ldGFkYXRhCgpERVNlcTIgcmVxdWlyZXMgYSBtZXRhZGF0YSBkYXRhIGZyYW1lIGFzIGlucHV0LiBJIGRvbid0IGhhdmUgc2FtcGxlIG1ldGFkYXRhIHRob3VnaCBzbywgc2luY2Ugd2UncmUganVzdCBkb2luZyBERVNlcTIgZm9yIG5vcm1hbGl6YXRpb24gcHVycG9zZXMgKG5vdCBhbmFseXNpcyBwdXJwb3NlcyksIEknbSBqdXN0IGdvaW5nIHRvIGNyZWF0ZSBhIGR1bW15IHNoZWV0CgpgYGB7ciBtYWtlLVJOQS1tZXRhZGF0YS1kYXRhZnJhbWV9CkFwdWxfbWV0YWRhdGFfUk5BIDwtIGRhdGEuZnJhbWUoU2FtcGxlID0gQXB1bF9zYW1wbGVfbmFtZXMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBTcGVjaWVzID0gcmVwKCJBLnB1bGNocmEiLCA1KSkKcm93bmFtZXMoQXB1bF9tZXRhZGF0YV9STkEpIDwtIEFwdWxfc2FtcGxlX25hbWVzCgpoZWFkKEFwdWxfbWV0YWRhdGFfUk5BKQpgYGAKCiMjIyBERVNlcSBvYmplY3QKCmBgYHtyIG1ha2UtUk5BLWRlc2VxLW9iamVjdCwgY2FjaGU9VFJVRX0KIyBDYWxjdWxhdGUgREVTZXEgb2JqZWN0CmRkc19BcHVsX1JOQSA8LSBERVNlcURhdGFTZXRGcm9tTWF0cml4KGNvdW50RGF0YSA9IEFwdWxfY291bnRzX1JOQSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sRGF0YSA9IEFwdWxfbWV0YWRhdGFfUk5BLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZXNpZ24gPSB+IDEpIAoKIyBSdW4gZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgCiMgKE5vdGUgdGhhdCB0aGlzIERFU2VxKCkgZnVuY3Rpb24gcnVucyBhbGwgbmVjZXNzYXJ5IHN0ZXBzLCBpbmNsdWRpbmcgZGF0YSBub3JtYWxpemF0aW9uLCAKIyBlc3RpbWF0aW5nIHNpemUgZmFjdG9ycywgZXN0aW1hdGluZyBkaXNwZXJzaW9ucywgZ2VuZS13aXNlIGRpc3BlcnNpb24gZXN0aW1hdGVzLCBtZWFuLWRpc3BlcnNpb24gCiMgcmVsYXRpb25zaGlwLCBmaW5hbCBkaXNwZXJzaW9uIGVzdGltYXRlcywgZml0dGluZyBtb2RlbCwgYW5kIHRlc3RpbmcpCiMgVXNpbmcgZGVzaWduID0gfjEgYmVjYXVzZSB3ZSBkb24ndCBoYXZlIHRyZWF0bWVudCBncm91cHMKCmRkc19BcHVsX1JOQSA8LSBERVNlcShkZHNfQXB1bF9STkEpCmBgYAoKYGBge3IgZ2V0LW5vcm1hbGl6ZWQtUk5BLWNvdW50cywgY2FjaGU9VFJVRX0KdnNkX0FwdWxfUk5BIDwtIHZhcmlhbmNlU3RhYmlsaXppbmdUcmFuc2Zvcm1hdGlvbihkZHNfQXB1bF9STkEsIGJsaW5kPVRSVUUpCndwbl92c2RfQXB1bF9STkEgPC0gZ2V0VmFyaWFuY2VTdGFiaWxpemVkRGF0YShkZHNfQXB1bF9STkEpCnJ2X3dwbl9BcHVsX1JOQSA8LSByb3dWYXJzKHdwbl92c2RfQXB1bF9STkEpCgpxNzVfd3BuX0FwdWxfUk5BIDwtIHF1YW50aWxlKCByb3dWYXJzKHdwbl92c2RfQXB1bF9STkEpLCAuNzUpICAjIDw9IG9yaWdpbmFsCnE5NV93cG5fQXB1bF9STkEgPC0gcXVhbnRpbGUoIHJvd1ZhcnMod3BuX3ZzZF9BcHVsX1JOQSksIC45NSkgICMgPD0gY2hhbmdlZCB0byA5NSBxdWFudGlsZSB0byByZWR1Y2UgZGF0YXNldApBcHVsX2NvdW50c19STkFfbm9ybSA8LSB3cG5fdnNkX0FwdWxfUk5BWyBydl93cG5fQXB1bF9STkEgPiBxOTVfd3BuX0FwdWxfUk5BLCBdICMgZmlsdGVyIHRvIHJldGFpbiBvbmx5IHRoZSBtb3N0IHZhcmlhYmxlIGdlbmVzCgpBcHVsX2NvdW50c19STkFfbm9ybSA8LSBkYXRhLmZyYW1lKEFwdWxfY291bnRzX1JOQV9ub3JtKSAKCndyaXRlLnRhYmxlKEFwdWxfY291bnRzX1JOQV9ub3JtLCBmaWxlID0gIi4uL291dHB1dC8wNC1BcHVsLVJOQS1zUk5BLVdHQ05BL0FwdWxfY291bnRzX1JOQV9ub3JtYWxpemVkX3E5NS50eHQiLCBzZXAgPSAiXHQiLCByb3cubmFtZXMgPSBUUlVFLCBxdW90ZSA9IEZBTFNFKQpgYGAKCiMjIFBsb3Qgbm9ybWFsaXplZCBkYXRhCgpgYGB7ciBwbG90LW5vcm1hbGl6ZWQtUk5BfQpBcHVsX2NvdW50c19STkFfbm9ybV9sb25nIDwtIEFwdWxfY291bnRzX1JOQV9ub3JtICU+JQogIG11dGF0ZSgKICAgIEdlbmVfaWQgPSByb3cubmFtZXMoQXB1bF9jb3VudHNfUk5BX25vcm0pCiAgKSAlPiUKICBwaXZvdF9sb25nZXIoLUdlbmVfaWQpCgpBcHVsX2NvdW50c19STkFfbm9ybV9sb25nICU+JQogIGdncGxvdCguLCBhZXMoeCA9IG5hbWUsIHkgPSB2YWx1ZSkpICsKICBnZW9tX3Zpb2xpbigpICsKICBnZW9tX3BvaW50KCkgKwogIHRoZW1lX2J3KCkgKwogIHRoZW1lKAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoIGFuZ2xlID0gOTApCiAgKSArCiAgeWxpbSgwLCBOQSkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJOb3JtYWxpemVkIGFuZCA5NSBxdWFudGlsZSBFeHByZXNzaW9uIiwKICAgIHggPSAiU2FtcGxlIiwKICAgIHkgPSAiTm9ybWFsaXplZCBjb3VudHMiCiAgKQpgYGAKCgojIFByZXAgbWlSTkEgZGF0YQoKIyMgTG9hZCBjb3VudCBkYXRhCgpMb2FkIGluIHRoZSBtaVJOQSBjb3VudCBtYXRyaXggZ2VuZXJhdGVkIGJ5IFNob3J0U3RhY2sgZHVyaW5nIG1pUk5BIGlkZW50aWZpY2F0aW9uCgpgYGB7ciBsb2FkLXNSTkEtY291bnRzfQojIFJlYWQgaW4gc1JOQSBjb3VudHMgZGF0YQpBcHVsX2NvdW50c19zUk5BX2RhdGFfT0cgPC0gcmVhZF9kZWxpbSgiLi4vLi4vLi4vZGVlcC1kaXZlL0QtQXB1bC9vdXRwdXQvMTMuMi4xLUFwdWwtc1JOQXNlcS1TaG9ydFN0YWNrLTMxYnAtZmFzdHAtbWVyZ2VkLWNuaWRhcmlhbl9taVJCYXNlL1Nob3J0U3RhY2tfb3V0L0NvdW50cy50eHQiLCBkZWxpbT0iXHQiKSAKaGVhZChBcHVsX2NvdW50c19zUk5BX2RhdGFfT0cpCmBgYAoKIyMgQ291bnQgZGF0YSBtdW5naW5nCgpgYGB7ciBzUk5BLWNvdW50LWRhdGEtbXVuZ2luZ30KQXB1bF9jb3VudHNfc1JOQSA8LSBBcHVsX2NvdW50c19zUk5BX2RhdGFfT0cKCiMgUmVtb3ZlIGV4Y2VzcyBwb3J0aW9ucyBvZiBzYW1wbGUgY29sdW1uIG5hbWVzIHRvIGp1c3QgInNhbXBsZSMjIyIKY29sbmFtZXMoQXB1bF9jb3VudHNfc1JOQSkgPC0gc3ViKCItUzEtVFAyLWZhc3RwLWFkYXB0ZXJzLXBvbHlHLTMxYnAtbWVyZ2VkIiwgIiIsIGNvbG5hbWVzKEFwdWxfY291bnRzX3NSTkEpKQpjb2xuYW1lcyhBcHVsX2NvdW50c19zUk5BKSA8LSBzdWIoInNSTkEtQUNSLSIsICJzYW1wbGUiLCBjb2xuYW1lcyhBcHVsX2NvdW50c19zUk5BKSkKCiMgS2VlcCBqdXN0IHRoZSBjb3VudHMgYW5kIGNsdXN0ZXIgbmFtZXMKQXB1bF9jb3VudHNfc1JOQSA8LSBBcHVsX2NvdW50c19zUk5BICU+JSBzZWxlY3QoInNhbXBsZTE0MCIsICJzYW1wbGUxNDUiLCAic2FtcGxlMTUwIiwgInNhbXBsZTE3MyIsICJzYW1wbGUxNzgiLCAiTmFtZSIpCgojIE1ha2UgdGhlIGNsdXN0ZXIgbmFtZXMgb3VyIG5ldyByb3cgbmFtZXMKQXB1bF9jb3VudHNfc1JOQSA8LSBBcHVsX2NvdW50c19zUk5BICU+JSBjb2x1bW5fdG9fcm93bmFtZXModmFyID0gIk5hbWUiKQoKaGVhZChBcHVsX2NvdW50c19zUk5BKQpgYGAKCiMjIE5vcm1hbGl6ZSBjb3VudHMgd2l0aCBERVNlcTIKCiMjIyBQbG90IHVubm9ybWFsaXplZCBkYXRhCgpgYGB7ciBwbG90LXVubm9ybWFsaXplZC1zUk5BfQoKQXB1bF9jb3VudHNfc1JOQSAlPiUKICBwaXZvdF9sb25nZXIoIGNvbHMgPSBldmVyeXRoaW5nKCksIG5hbWVzX3RvID0gInNhbXBsZSIsIHZhbHVlc190byA9ICJjb3VudCIpICU+JQogIGdncGxvdCguLCBhZXMoeCA9IHNhbXBsZSwgeSA9IGNvdW50KSkgKwogIGdlb21fdmlvbGluKCkgKyAKICBnZW9tX3BvaW50KGFscGhhID0gMC4yKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHRpdGxlID0gIlVubm9ybWFsaXplZCB0cmFuc2NyaXB0IGNvdW50cyIsCiAgICAgICB4ID0gIlNhbXBsZSIsCiAgICAgICB5ID0gImNvdW50IikKYGBgCgpXZSBkZWZpbml0ZWx5IG5lZWQgdG8gbm9ybWFsaXplIHRoaXMgZGF0YSEKCiMjIyBNZXRhZGF0YQoKREVTZXEyIHJlcXVpcmVzIGEgbWV0YWRhdGEgZGF0YSBmcmFtZSBhcyBpbnB1dC4gSSBkb24ndCBoYXZlIHNhbXBsZSBtZXRhZGF0YSB0aG91Z2ggc28sIHNpbmNlIHdlJ3JlIGp1c3QgZG9pbmcgREVTZXEyIGZvciBub3JtYWxpemF0aW9uIHB1cnBvc2VzIChub3QgYW5hbHlzaXMgcHVycG9zZXMpLCBJJ20ganVzdCBnb2luZyB0byBjcmVhdGUgYSBkdW1teSBzaGVldAoKYGBge3IgbWFrZS1zUk5BLW1ldGFkYXRhLWRhdGFmcmFtZX0KQXB1bF9tZXRhZGF0YV9STkEgPC0gZGF0YS5mcmFtZShTYW1wbGUgPSBBcHVsX3NhbXBsZV9uYW1lcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIFNwZWNpZXMgPSByZXAoIkEucHVsY2hyYSIsIDUpKQpyb3duYW1lcyhBcHVsX21ldGFkYXRhX1JOQSkgPC0gQXB1bF9zYW1wbGVfbmFtZXMKCmhlYWQoQXB1bF9tZXRhZGF0YV9STkEpCmBgYAoKIyMjIERFU2VxIG9iamVjdAoKYGBge3IgbWFrZS1zUk5BLWRlc2VxLW9iamVjdCwgY2FjaGU9VFJVRX0KIyBDYWxjdWxhdGUgREVTZXEgb2JqZWN0CmRkc19BcHVsX3NSTkEgPC0gREVTZXFEYXRhU2V0RnJvbU1hdHJpeChjb3VudERhdGEgPSBBcHVsX2NvdW50c19zUk5BLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xEYXRhID0gQXB1bF9tZXRhZGF0YV9STkEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlc2lnbiA9IH4gMSkgCgojIFJ1biBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBhbmFseXNpcyAKIyAoTm90ZSB0aGF0IHRoaXMgREVTZXEoKSBmdW5jdGlvbiBydW5zIGFsbCBuZWNlc3Nhcnkgc3RlcHMsIGluY2x1ZGluZyBkYXRhIG5vcm1hbGl6YXRpb24sIAojIGVzdGltYXRpbmcgc2l6ZSBmYWN0b3JzLCBlc3RpbWF0aW5nIGRpc3BlcnNpb25zLCBnZW5lLXdpc2UgZGlzcGVyc2lvbiBlc3RpbWF0ZXMsIG1lYW4tZGlzcGVyc2lvbiAKIyByZWxhdGlvbnNoaXAsIGZpbmFsIGRpc3BlcnNpb24gZXN0aW1hdGVzLCBmaXR0aW5nIG1vZGVsLCBhbmQgdGVzdGluZykKIyBVc2luZyBkZXNpZ24gPSB+MSBiZWNhdXNlIHdlIGRvbid0IGhhdmUgdHJlYXRtZW50IGdyb3VwcwoKZGRzX0FwdWxfc1JOQSA8LSBERVNlcShkZHNfQXB1bF9zUk5BKQpgYGAKCmBgYHtyIGdldC1ub3JtYWxpemVkLXNSTkEtY291bnRzLCBjYWNoZT1UUlVFfQp2c2RfQXB1bF9zUk5BIDwtIHZhcmlhbmNlU3RhYmlsaXppbmdUcmFuc2Zvcm1hdGlvbihkZHNfQXB1bF9zUk5BLCBibGluZD1UUlVFKQp3cG5fdnNkX0FwdWxfc1JOQSA8LSBnZXRWYXJpYW5jZVN0YWJpbGl6ZWREYXRhKGRkc19BcHVsX3NSTkEpCnJ2X3dwbl9BcHVsX3NSTkEgPC0gcm93VmFycyh3cG5fdnNkX0FwdWxfc1JOQSkKCiMgRm9yIG5vdyBsZXQncyByZXRhaW4gYWxsIHNSTkFzLCByZWdhcmRsZXNzIG9mIGV4cHJlc3Npb24gbGV2ZWwKIyBxNzVfd3BuX0FwdWxfc1JOQSA8LSBxdWFudGlsZSggcm93VmFycyh3cG5fdnNkX0FwdWxfc1JOQSksIC43NSkgIAojIHE5MF93cG5fQXB1bF9zUk5BIDwtIHF1YW50aWxlKCByb3dWYXJzKHdwbl92c2RfQXB1bF9zUk5BKSwgLjkpICAKIyBBcHVsX2NvdW50c19zUk5BX25vcm0gPC0gd3BuX3ZzZF9BcHVsX1JOQVsgcnZfd3BuX0FwdWxfc1JOQSA+IHE1MF93cG5fQXB1bF9zUk5BLCBdICMgZmlsdGVyIHRvIHJldGFpbiBvbmx5IHRoZSBtb3JlIHZhcmlhYmxlIGdlbmVzCkFwdWxfY291bnRzX3NSTkFfbm9ybSA8LSB3cG5fdnNkX0FwdWxfc1JOQQoKQXB1bF9jb3VudHNfc1JOQV9ub3JtIDwtIGRhdGEuZnJhbWUoQXB1bF9jb3VudHNfc1JOQV9ub3JtKQoKd3JpdGUudGFibGUoQXB1bF9jb3VudHNfc1JOQV9ub3JtLCBmaWxlID0gIi4uL291dHB1dC8wNC1BcHVsLVJOQS1zUk5BLVdHQ05BL0FwdWxfY291bnRzX3NSTkFfbm9ybWFsaXplZC50eHQiLCBzZXAgPSAiXHQiLCByb3cubmFtZXMgPSBUUlVFLCBxdW90ZSA9IEZBTFNFKQpgYGAKCiMjIFBsb3Qgbm9ybWFsaXplZCBkYXRhCgpgYGB7ciBwbG90LW5vcm1hbGl6ZWQtc1JOQX0KQXB1bF9jb3VudHNfc1JOQV9ub3JtX2RmX2xvbmcgPC0gQXB1bF9jb3VudHNfc1JOQV9ub3JtICU+JQogIG11dGF0ZSgKICAgIEdlbmVfaWQgPSByb3cubmFtZXMoQXB1bF9jb3VudHNfc1JOQV9ub3JtKQogICkgJT4lCiAgcGl2b3RfbG9uZ2VyKC1HZW5lX2lkKQoKQXB1bF9jb3VudHNfc1JOQV9ub3JtX2RmX2xvbmcgJT4lCiAgZ2dwbG90KC4sIGFlcyh4ID0gbmFtZSwgeSA9IHZhbHVlKSkgKwogIGdlb21fdmlvbGluKCkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfYncoKSArCiAgdGhlbWUoCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dCggYW5nbGUgPSA5MCkKICApICsKICB5bGltKDAsIE5BKSArCiAgbGFicygKICAgIHRpdGxlID0gIk5vcm1hbGl6ZWQgYW5kIDk1IHF1YW50aWxlIEV4cHJlc3Npb24iLAogICAgeCA9ICJTYW1wbGUiLAogICAgeSA9ICJOb3JtYWxpemVkIGNvdW50cyIKICApCmBgYAoKIyMgTm9ybWFsaXplZCBjb3VudCBkYXRhIG11bmdpbmcKCmBgYHtyIG1pUk5BLWNvdW50LWRhdGEtbXVuZ2luZ30KCiMgVGhlIFNob3J0U3RhY2sgb3V0cHV0IFJlc3VsdHMudHh0IGluY2x1ZGVzIGFsbCBjbHVzdGVycyBvZiBzUk5BIHJlYWRzLCBpbmNsdWRpbmcgdGhvc2Ugbm90IGFubm90YXRlZCBhcyB2YWxpZCBtaVJOQXMuIE5vdyB0aGF0IHdlJ3ZlIG5vcm1hbGl6ZWQgdGhlIGNvdW50cyx3ZSBuZWVkIHRvIGZpbHRlciBvdXQgYWxsIHRoZSBjbHVzdGVycyB0aGF0IGFyZSBub3QgbWlSTkFzLgoKIyBKb2luIHdpdGggZnVsbCBtZXRhZGF0YSBzaGVldCwgd2hpY2ggb25seSBjb250YWlucyB2YWxpZCBtaVJOQXMKQXB1bF9tZXRhZGF0YV9taVJOQSA8LSByZWFkX2NzdigiLi4vLi4vLi4vZGVlcC1kaXZlL0RFRi1jcm9zcy1zcGVjaWVzL291dHB1dC8xMC1zaG9ydFJOQS1TaG9ydFN0YWNrLWNvbXBhcmlzb24vQXB1bF9yZXN1bHRzX21hdHVyZV9uYW1lZC5jc3YiKSAKCkFwdWxfY291bnRzX3NSTkFfbm9ybSA8LSByb3duYW1lc190b19jb2x1bW4oQXB1bF9jb3VudHNfc1JOQV9ub3JtLCB2YXIgPSAiTmFtZSIpCgpBcHVsX2NvdW50c19taVJOQV9ub3JtIDwtIGxlZnRfam9pbihBcHVsX21ldGFkYXRhX21pUk5BLCBBcHVsX2NvdW50c19zUk5BX25vcm0sIGJ5ID0gYygiTmFtZSIgPSAiTmFtZSIpKQoKIyBLZWVwIGp1c3QgdGhlIGNvdW50cyBhbmQgZ2l2ZW4gbWlSTkEgbmFtZXMgKGUuZy4sIGJhc2VkIG9uIG1hdGNoIHRvIHByZXZpb3VzbHkgZGVzY3JpYmVkIG1pUk5BKQpBcHVsX2NvdW50c19taVJOQV9ub3JtIDwtIEFwdWxfY291bnRzX21pUk5BX25vcm0gJT4lIHNlbGVjdCgic2FtcGxlMTQwIiwgInNhbXBsZTE0NSIsICJzYW1wbGUxNTAiLCAic2FtcGxlMTczIiwgInNhbXBsZTE3OCIsICJnaXZlbl9taVJOQV9uYW1lIikKCiMgTWFrZSB0aGUgbWlSTkEgbmFtZXMgb3VyIG5ldyByb3cgbmFtZXMKQXB1bF9jb3VudHNfbWlSTkFfbm9ybSA8LSBBcHVsX2NvdW50c19taVJOQV9ub3JtICU+JSBjb2x1bW5fdG9fcm93bmFtZXModmFyID0gImdpdmVuX21pUk5BX25hbWUiKQoKaGVhZChBcHVsX2NvdW50c19taVJOQV9ub3JtKQpgYGAKCgojIE1lcmdlIFJOQSBhbmQgbWlSTkEgbm9ybWFsaXplZCBjb3VudHMKCk5vdyB0aGF0IHdlIGhhdmUgbm9ybWFsaXplZCBjb3VudHMgb2YgUk5BIGFuZCBtaVJOQSBmb3IgYWxsIG9mIG91ciBzYW1wbGVzLCBsZXQncyBjb21iaW5lIHRoZW0gaW50byBvbmUgZGF0YXNldCB0byBmZWVkIGludG8gV0dDTkEKCmBgYHtyfQojIE1lcmdlCkFwdWxfY291bnRzX1dHQ05BIDwtIGJpbmRfcm93cyhBcHVsX2NvdW50c19taVJOQV9ub3JtLCBBcHVsX2NvdW50c19STkFfbm9ybSkKCiMgQ29udmVydCBmcm9tIGRhdGEgZnJhbWUgdG8gbWF0cml4CkFwdWxfY291bnRzX1dHQ05BIDwtIGFzLm1hdHJpeChBcHVsX2NvdW50c19XR0NOQSkKCiMgVHJhbnNwb3NlIHRoZSBub3JtYWxpemVkIGNvdW50IGRhdGEgdG8gbWVldCBXR0NOQSByZXF1aXJlZCBpbnB1dCBmb3JtYXQKQXB1bF9jb3VudHNfV0dDTkEgPSB0KEFwdWxfY291bnRzX1dHQ05BKQpgYGAKCgojIFdHQ05BCgpOb3cgd2UncmUgcmVhZHkgdG8gcnVuIFdHQ05BIQoKYGBge3J9CmFsbG93V0dDTkFUaHJlYWRzKCkgICAgICAgICAgIyBhbGxvdyBtdWx0aS10aHJlYWRpbmcgKG9wdGlvbmFsKQojIENob29zZSBhIHNldCBvZiBzb2Z0LXRocmVzaG9sZGluZyBwb3dlcnMKcG93ZXJzID0gYyhjKDE6MTApLCBzZXEoZnJvbSA9IDEyLCB0byA9IDIwLCBieSA9IDIpKQoKIyBDYWxsIHRoZSBuZXR3b3JrIHRvcG9sb2d5IGFuYWx5c2lzIGZ1bmN0aW9uCnNmdF9BcHVsID0gcGlja1NvZnRUaHJlc2hvbGQoCiAgQXB1bF9jb3VudHNfV0dDTkEsICAgICAgICAgICAgICMgPD0gSW5wdXQgZGF0YQogICNibG9ja1NpemUgPSAzMCwKICBwb3dlclZlY3RvciA9IHBvd2VycywKICB2ZXJib3NlID0gNQogICkKCnBhcihtZnJvdyA9IGMoMSwyKSk7CmNleDEgPSAwLjk7CgpwbG90KHNmdF9BcHVsJGZpdEluZGljZXNbLCAxXSwKICAgICAtc2lnbihzZnRfQXB1bCRmaXRJbmRpY2VzWywgM10pICogc2Z0X0FwdWwkZml0SW5kaWNlc1ssIDJdLAogICAgIHhsYWIgPSAiU29mdCBUaHJlc2hvbGQgKHBvd2VyKSIsCiAgICAgeWxhYiA9ICJTY2FsZSBGcmVlIFRvcG9sb2d5IE1vZGVsIEZpdCwgc2lnbmVkIFJeMiIsCiAgICAgbWFpbiA9IHBhc3RlKCJTY2FsZSBpbmRlcGVuZGVuY2UiKQopCnRleHQoc2Z0X0FwdWwkZml0SW5kaWNlc1ssIDFdLAogICAgIC1zaWduKHNmdF9BcHVsJGZpdEluZGljZXNbLCAzXSkgKiBzZnRfQXB1bCRmaXRJbmRpY2VzWywgMl0sCiAgICAgbGFiZWxzID0gcG93ZXJzLCBjZXggPSBjZXgxLCBjb2wgPSAicmVkIgopCmFibGluZShoID0gMC45MCwgY29sID0gInJlZCIpCnBsb3Qoc2Z0X0FwdWwkZml0SW5kaWNlc1ssIDFdLAogICAgIHNmdF9BcHVsJGZpdEluZGljZXNbLCA1XSwKICAgICB4bGFiID0gIlNvZnQgVGhyZXNob2xkIChwb3dlcikiLAogICAgIHlsYWIgPSAiTWVhbiBDb25uZWN0aXZpdHkiLAogICAgIHR5cGUgPSAibiIsCiAgICAgbWFpbiA9IHBhc3RlKCJNZWFuIGNvbm5lY3Rpdml0eSIpCikKdGV4dChzZnRfQXB1bCRmaXRJbmRpY2VzWywgMV0sCiAgICAgc2Z0X0FwdWwkZml0SW5kaWNlc1ssIDVdLAogICAgIGxhYmVscyA9IHBvd2VycywKICAgICBjZXggPSBjZXgxLCBjb2wgPSAicmVkIikKYGBgCgpJJ20gYSBsaXR0bGUgd2VpcmRlZCBvdXQgYnkgdGhlIGRvdWJsZSBjdXJ2ZSBpbiB0aGUgU2NhbGUgSW5kZXBlbmRlbmNlIHBsb3QsIGJ1dCB0aGlzIGlzIGEgcHJlbGltIHRlc3Qgc28gSSdtIG5vdCBnb2luZyB0byB3b3JyeSBhYm91dCBpdCByaWdodCBub3cuIFdlIHdhbnQgdG8gcGljayBhIHNvZnQgdGhyZXNob2xkIHBvd2VyIG5lYXIgdGhlIGN1cnZlIG9mIHRoZSBwbG90LCBzbyBtYXliZSA2LCA3LCA4LCBvciA5PyBMZXQncyBwaWNrIDkgZm9yIG5vdywgYnV0IG1heWJlIGV4cGVyaW1lbnQgd2l0aCBvdGhlciBwb3dlcnMgbGF0ZXIuCgpgYGB7ciwgY2FjaGU9VFJVRX0KcGlja2VkX3Bvd2VyID0gOQp0ZW1wX2NvciA8LSBjb3IgICAgICAgCmNvciA8LSBXR0NOQTo6Y29yICAgICAgICAgIyBGb3JjZSBpdCB0byB1c2UgV0dDTkEgY29yIGZ1bmN0aW9uIChmaXggYSBuYW1lc3BhY2UgY29uZmxpY3QgaXNzdWUpCm5ldHdrX0FwdWwgPC0gYmxvY2t3aXNlTW9kdWxlcyhBcHVsX2NvdW50c19XR0NOQSwgICAgICAgICAgICAgICAgIyA8PSBpbnB1dCBoZXJlCgogICAgICAgICAgICAgICAgICAgICAgICAgICMgPT0gQWRqYWNlbmN5IEZ1bmN0aW9uID09CiAgICAgICAgICAgICAgICAgICAgICAgICAgcG93ZXIgPSBwaWNrZWRfcG93ZXIsICAgICAgICAgICAgICAgICMgPD0gcG93ZXIgaGVyZQogICAgICAgICAgICAgICAgICAgICAgICAgIG5ldHdvcmtUeXBlID0gInNpZ25lZCIsCgogICAgICAgICAgICAgICAgICAgICAgICAgICMgPT0gVHJlZSBhbmQgQmxvY2sgT3B0aW9ucyA9PQogICAgICAgICAgICAgICAgICAgICAgICAgIGRlZXBTcGxpdCA9IDIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgcGFtUmVzcGVjdHNEZW5kcm8gPSBGLAogICAgICAgICAgICAgICAgICAgICAgICAgICMgZGV0ZWN0Q3V0SGVpZ2h0ID0gMC43NSwKICAgICAgICAgICAgICAgICAgICAgICAgICBtaW5Nb2R1bGVTaXplID0gMzAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWF4QmxvY2tTaXplID0gNDAwMCwKCiAgICAgICAgICAgICAgICAgICAgICAgICAgIyA9PSBNb2R1bGUgQWRqdXN0bWVudHMgPT0KICAgICAgICAgICAgICAgICAgICAgICAgICByZWFzc2lnblRocmVzaG9sZCA9IDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWVyZ2VDdXRIZWlnaHQgPSAwLjI1LAoKICAgICAgICAgICAgICAgICAgICAgICAgICAjID09IFRPTSA9PSBBcmNoaXZlIHRoZSBydW4gcmVzdWx0cyBpbiBUT00gZmlsZSAoc2F2ZXMgdGltZSkKICAgICAgICAgICAgICAgICAgICAgICAgICBzYXZlVE9NcyA9IFQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2F2ZVRPTUZpbGVCYXNlID0gIkVSIiwKCiAgICAgICAgICAgICAgICAgICAgICAgICAgIyA9PSBPdXRwdXQgT3B0aW9ucwogICAgICAgICAgICAgICAgICAgICAgICAgIG51bWVyaWNMYWJlbHMgPSBULAogICAgICAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSAzKQoKCmNvciA8LSB0ZW1wX2NvciAgICAgIyBSZXR1cm4gY29yIGZ1bmN0aW9uIHRvIG9yaWdpbmFsIG5hbWVzcGFjZQoKYGBgCgpgYGB7ciBtb3ZlLVdHQ05BLW91dHB1dCwgZW5naW5lPSdiYXNoJ30KIyBDaGVjayBpZiB0aGVyZSBhcmUgYW55IGZpbGVzIHN0YXJ0aW5nIHdpdGggIkVSIiBpbiB0aGUgY3VycmVudCBkaXJlY3RvcnkKaWYgbHMgRVItYmxvY2sqIDE+IC9kZXYvbnVsbCAyPiYxOyB0aGVuCiAgICAjIE1vdmUgdGhlIGZpbGVzIGlmIHRoZXkgZXhpc3QKICAgIG12IEVSLWJsb2NrKiAuLi9vdXRwdXQvMDQtQXB1bC1STkEtc1JOQS1XR0NOQQplbHNlCiAgICBlY2hvICJObyBmaWxlcyBzdGFydGluZyB3aXRoICdFUicgZm91bmQuIgpmaQpgYGAKClRha2UgYSBsb29rCmBgYHtyfQojIENvbnZlcnQgbGFiZWxzIHRvIGNvbG9ycyBmb3IgcGxvdHRpbmcKbWVyZ2VkQ29sb3JzID0gbGFiZWxzMmNvbG9ycyhuZXR3a19BcHVsJGNvbG9ycykKIyBQbG90IHRoZSBkZW5kcm9ncmFtIGFuZCB0aGUgbW9kdWxlIGNvbG9ycyB1bmRlcm5lYXRoCnBsb3REZW5kcm9BbmRDb2xvcnMoCiAgbmV0d2tfQXB1bCRkZW5kcm9ncmFtc1tbMV1dLAogIG1lcmdlZENvbG9yc1tuZXR3a19BcHVsJGJsb2NrR2VuZXNbWzFdXV0sCiAgIk1vZHVsZSBjb2xvcnMiLAogIGRlbmRyb0xhYmVscyA9IEZBTFNFLAogIGhhbmcgPSAwLjAzLAogIGFkZEd1aWRlID0gVFJVRSwKICBndWlkZUhhbmcgPSAwLjA1ICkKYGBgCgpgYGB7cn0KbW9kdWxlX2RmX0FwdWwgPC0gZGF0YS5mcmFtZSgKICBnZW5lX2lkID0gbmFtZXMobmV0d2tfQXB1bCRjb2xvcnMpLAogIGNvbG9ycyA9IGxhYmVsczJjb2xvcnMobmV0d2tfQXB1bCRjb2xvcnMpCikKCm1vZHVsZV9kZl9BcHVsWzE6NSxdCmBgYAoKYGBge3J9CiMgR2V0IE1vZHVsZSBFaWdlbmdlbmVzIHBlciBjbHVzdGVyCk1FczBfQXB1bCA8LSBtb2R1bGVFaWdlbmdlbmVzKEFwdWxfY291bnRzX1dHQ05BLCBtZXJnZWRDb2xvcnMpJGVpZ2VuZ2VuZXMKCiMgIyBSZW9yZGVyIG1vZHVsZXMgc28gc2ltaWxhciBtb2R1bGVzIGFyZSBuZXh0IHRvIGVhY2ggb3RoZXIKIyBNRXMwX0FwdWwgPC0gb3JkZXJNRXMoTUVzMF9BcHVsKQojIG1vZHVsZV9vcmRlcl9BcHVsID0gbmFtZXMoTUVzMF9BcHVsKSAlPiUgZ3N1YigiTUUiLCIiLCAuKQoKIyBBZGQgdHJlYXRtZW50IG5hbWVzCk1FczBfQXB1bCRzYW1wbGUgPSByb3cubmFtZXMoTUVzMF9BcHVsKQoKIyB0aWR5ICYgcGxvdCBkYXRhCm1NRV9BcHVsID0gTUVzMF9BcHVsICU+JQogIHBpdm90X2xvbmdlcigtc2FtcGxlKSAlPiUKICBtdXRhdGUoCiAgICBuYW1lID0gZ3N1YigiTUUiLCAiIiwgbmFtZSksCiAgICAjIG5hbWUgPSBmYWN0b3IobmFtZSwgbGV2ZWxzID0gbW9kdWxlX29yZGVyKQogICkKCm1NRV9BcHVsICU+JSBnZ3Bsb3QoLiwgYWVzKHg9c2FtcGxlLCB5PW5hbWUsIGZpbGw9dmFsdWUpKSArCiAgZ2VvbV90aWxlKCkgKwogIHRoZW1lX2J3KCkgKwogIHNjYWxlX2ZpbGxfZ3JhZGllbnQyKAogICAgbG93ID0gImJsdWUiLAogICAgaGlnaCA9ICJyZWQiLAogICAgbWlkID0gIndoaXRlIiwKICAgIG1pZHBvaW50ID0gMCwKICAgIGxpbWl0ID0gYygtMSwxKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlPTkwKSkgKwogIGxhYnModGl0bGUgPSAiTW9kdWxlLXRyYWl0IFJlbGF0aW9uc2hpcHMiLCB5ID0gIk1vZHVsZXMiLCBmaWxsPSJjb3JyIikKYGBgCgpgYGB7cn0KIyBDaGVjayB3aGljaCBtb2R1bGVzIGluY2x1ZGUgbWlSTkFzCm1vZHVsZV9kZl9BcHVsICU+JQogIGZpbHRlcihncmVwbCgibWlyIixnZW5lX2lkKSkgJT4lCiAgcHVsbChjb2xvcnMpICU+JQogIHVuaXF1ZSgpCgptb2R1bGVfZGZfQXB1bCAlPiUKICBmaWx0ZXIoZ3JlcGwoIm1pciIsZ2VuZV9pZCkpICU+JQogIGFycmFuZ2UoY29sb3JzKSAlPiUKICBoZWFkKG49MzgpCmBgYAoKYGBge3J9CiMgcGljayBvdXQgYSBmZXcgbW9kdWxlcyBvZiBpbnRlcmVzdC4gTGV0J3MgZG8gc29tZSBvZiB0aGUgbW9kdWxlcyB0aGF0IGNvbnRhaW4gcHJldmlvdXNseSBkZXNjcmliZWQgbWlSTkFzLCBzaW5jZSB3ZSBoYXZlIHRoZSBiZXN0IGlkZWEgb2YgdGhlaXIgZnVuY3Rpb24KbW9kdWxlc19vZl9pbnRlcmVzdCA9IGMoImdyZWVuIiwgInBpbmsiLCAicmVkIikKIyBEZWZpbmUgdGhlIGNvbG9ycyBjb3JyZXNwb25kaW5nIHRvIHRoZSBtb2R1bGVzIG9mIGludGVyZXN0Cm1vZHVsZV9jb2xvcnMgPC0gYygiZ3JlZW4iID0gImdyZWVuIiwgInBpbmsiID0gInBpbmsiLCAicmVkIiA9ICJyZWQiKQoKIyBQdWxsIG91dCBsaXN0IG9mIGdlbmVzIGluIHRoYXQgbW9kdWxlCnN1Ym1vZF9BcHVsID0gbW9kdWxlX2RmX0FwdWwgJT4lCiAgc3Vic2V0KGNvbG9ycyAlaW4lIG1vZHVsZXNfb2ZfaW50ZXJlc3QpCgpyb3cubmFtZXMobW9kdWxlX2RmX0FwdWwpID0gbW9kdWxlX2RmX0FwdWwkZ2VuZV9pZAoKc3ViZXhwcl9BcHVsID0gdChBcHVsX2NvdW50c19XR0NOQSlbc3VibW9kX0FwdWwkZ2VuZV9pZCxdCgpzdWJtb2RfZGZfQXB1bCA9IGRhdGEuZnJhbWUoc3ViZXhwcl9BcHVsKSAlPiUKICBtdXRhdGUoCiAgICBnZW5lX2lkID0gcm93Lm5hbWVzKC4pCiAgKSAlPiUKICBwaXZvdF9sb25nZXIoLWdlbmVfaWQpICU+JQogIG11dGF0ZSgKICAgIG1vZHVsZSA9IG1vZHVsZV9kZl9BcHVsW2dlbmVfaWQsXSRjb2xvcnMKICApCgpzdWJtb2RfZGZfQXB1bCAlPiUgZ2dwbG90KC4sIGFlcyh4PW5hbWUsIHk9dmFsdWUsIGdyb3VwPWdlbmVfaWQpKSArCiAgZ2VvbV9saW5lKGFlcyhjb2xvciA9IG1vZHVsZSksCiAgICAgICAgICAgIGFscGhhID0gMC4yKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IG1vZHVsZV9jb2xvcnMpICsKICB0aGVtZV9idygpICsKICB0aGVtZSgKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTApCiAgKSArCiAgZmFjZXRfZ3JpZChyb3dzID0gdmFycyhtb2R1bGUpKSArCiAgbGFicyh4ID0gInRyZWF0bWVudCIsCiAgICAgICB5ID0gIm5vcm1hbGl6ZWQgZXhwcmVzc2lvbiIpCmBgYAoKCgoK