library(DESeq2)
library(dplyr)
library(readr)
library(tibble)
library(tidyr)
library(ggplot2)
library(writexl)
library(decoupleR)
library(ComplexHeatmap)
library(circlize)

Aim

In Day 1 we asked which gene sets were enriched among the differential expression results. Today we ask a different question: which upstream pathways and transcription factors look active?

1. Load Differential Expression Results

res <- read_tsv("DESeq_result_full.tsv", show_col_types = FALSE)

res

Remove rows that cannot be used for activity inference. If a gene appears more than once, keep the row with the strongest DESeq2 statistic.

res_clean <- res |>
  filter(!is.na(gene_name), gene_name != "", !is.na(stat)) |>
  group_by(gene_name) |>
  slice_max(order_by = abs(stat), n = 1, with_ties = FALSE) |>
  ungroup()

nrow(res)
[1] 17666
nrow(res_clean)
[1] 17666

2. Make A Contrast-Level Input Matrix

For decoupleR, rows are genes and columns are samples or contrasts. First use the DESeq2 Wald statistic as a one-column contrast matrix.

# Practice:
# Start from res_clean.
# Keep gene_name and stat.
# Move gene_name into the row names with column_to_rownames().
# Convert the result to a matrix with as.matrix().
# Give the column the name MEKi_vs_control.
dim(contrast_matrix)
[1] 17666     1
head(contrast_matrix)
          MEKi_vs_control
5_8S_rRNA      1.36968445
7SK            0.08579961
A1BG           0.43044299
A1BG-AS1       0.09589767
A1CF           0.97165659
A2M            0.70371021

Positive values mean higher expression in MEKi. Negative values mean lower expression in MEKi.

3. Make A Sample-Level Input Matrix

For sample-level activity scores, use variance-stabilized counts, as in the PCA and GSVA workflows.

dds <- readRDS("dds.rds")

sample_info <- colData(dds) |>
  as.data.frame() |>
  select(sample, condition)

sample_info
vst_counts <- vst(dds, blind = FALSE)

expression_matrix <- assay(vst_counts) |>
  as.data.frame() |>
  rownames_to_column("gene_id") |>
  inner_join(res_clean |> select(gene_id, gene_name), by = "gene_id") |>
  select(gene_name, all_of(sample_info$sample)) |>
  group_by(gene_name) |>
  summarize(across(all_of(sample_info$sample), mean), .groups = "drop") |>
  column_to_rownames("gene_name") |>
  as.matrix()

dim(expression_matrix)
[1] 16634     6

4. Load Activity Models

PROGENy connects pathways to responsive genes. DoRothEA connects transcription factors to regulated target genes.

progeny_model <- read_tsv("progeny_human_top500.tsv", show_col_types = FALSE)
dorothea_network <- read_tsv("dorothea_human_ABC.tsv", show_col_types = FALSE)

progeny_model
dorothea_network

Check how many targets each pathway or transcription factor has.

progeny_model |>
  count(source, sort = TRUE)
dorothea_network |>
  count(source, sort = TRUE)

5. PROGENy On The DE Contrast

Run decoupleR on the contrast matrix. The score is an inferred pathway activity score for the MEKi versus control comparison.

# Practice:
# Use run_mlm().
# mat should be contrast_matrix.
# network should be progeny_model.
# Use weight as the direction and strength column.
# Arrange the result by score.
progeny_contrast
progeny_contrast |>
  ggplot(aes(x = score, y = reorder(source, score), fill = score > 0)) +
  geom_col() +
  scale_fill_manual(values = c("TRUE" = "firebrick", "FALSE" = "steelblue"), guide = "none") +
  labs(
    x = "PROGENy activity score",
    y = NULL,
    title = "Pathway activity from the DESeq2 statistic"
  ) +
  theme_minimal()

Task: Which pathways are lower in MEKi? Which pathways are higher? Does the MAPK result match the treatment?

6. PROGENy At Sample Level

Run the same model on the variance-stabilized expression matrix. This gives one pathway activity score per sample.

# Practice:
# Use run_mlm() again.
# This time mat should be expression_matrix.
# Convert the long result into a matrix with pivot_wider().
# Rows should be pathways and columns should be samples.
progeny_sample
condition_colors <- c(control = "grey70", MEKi = "firebrick")

sample_annotation <- HeatmapAnnotation(
  condition = sample_info$condition,
  col = list(condition = condition_colors)
)

Heatmap(
  progeny_sample_matrix,
  name = "activity",
  col = colorRamp2(c(-4, 0, 4), c("steelblue", "white", "firebrick")),
  top_annotation = sample_annotation,
  column_split = sample_info$condition,
  cluster_columns = FALSE,
  row_names_gp = grid::gpar(fontsize = 9),
  column_names_gp = grid::gpar(fontsize = 9)
)

Task: Which pathways separate control and MEKi samples? Are these the same pathways that were strongest in the contrast-level plot?

7. DoRothEA On The DE Contrast

Now infer transcription factor activity from the same DESeq2 statistic matrix.

# Practice:
# Use run_ulm().
# mat should be contrast_matrix.
# network should be dorothea_network.
# Arrange the result by the strongest absolute scores.
tf_contrast
tf_contrast_top |>
  ggplot(aes(x = score, y = reorder(source, score), fill = score > 0)) +
  geom_col() +
  scale_fill_manual(values = c("TRUE" = "firebrick", "FALSE" = "steelblue"), guide = "none") +
  labs(
    x = "TF activity score",
    y = NULL,
    title = "Top transcription factor activities from the DESeq2 statistic"
  ) +
  theme_minimal()

Positive scores mean that the target-gene pattern is higher in MEKi. Negative scores mean that the target-gene pattern is lower in MEKi.

8. Inspect One Transcription Factor

Pick the strongest transcription factor and look at its target genes in the differential expression table.

# Practice:
# Choose one transcription factor from tf_contrast.
# Filter dorothea_network to this transcription factor.
# Join the targets to res_clean.
# Arrange by padj.
selected_tf
[1] "STAT2"
STAT2
selected_tf_targets

Task: Are the target genes mostly moving in the same direction as the transcription factor activity score suggests?

9. DoRothEA At Sample Level

Run the transcription factor model on the variance-stabilized expression matrix. For the heatmap, show the transcription factors with the strongest contrast-level scores.

# Practice:
# Use run_ulm() again.
# This time mat should be expression_matrix.
# Convert the long result into a matrix with pivot_wider().
# Keep the strongest transcription factors from tf_contrast for the heatmap.
tf_sample
Heatmap(
  tf_heatmap_scaled,
  name = "TF\nz-score",
  col = colorRamp2(c(-2, 0, 2), c("steelblue", "white", "firebrick")),
  top_annotation = sample_annotation,
  column_split = sample_info$condition,
  cluster_columns = FALSE,
  row_names_gp = grid::gpar(fontsize = 8),
  column_names_gp = grid::gpar(fontsize = 9)
)

Task: Which transcription factors separate control and MEKi samples? Which ones are also strong in the contrast-level result?

10. Save Result Tables

write_tsv(progeny_contrast, "PROGENy_contrast_activity.tsv")
write_xlsx(progeny_contrast, "PROGENy_contrast_activity.xlsx")

write_tsv(progeny_sample, "PROGENy_sample_activity.tsv")
write_xlsx(progeny_sample, "PROGENy_sample_activity.xlsx")

write_tsv(tf_contrast, "DoRothEA_contrast_activity.tsv")
write_xlsx(tf_contrast, "DoRothEA_contrast_activity.xlsx")

write_tsv(tf_sample, "DoRothEA_sample_activity.tsv")
write_xlsx(tf_sample, "DoRothEA_sample_activity.xlsx")

11. Final Interpretation

Use the pathway and transcription factor activity results together with the Day 1 ORA, GSEA, and GSVA results.

Answer these questions:

  1. Which PROGENy pathways best explain the MEKi effect?
  2. Which transcription factors support the same interpretation?
  3. Which activity scores are visible both in the contrast-level results and at sample level?
  4. Which findings agree with the Hallmark and GO results from Day 1?
  5. Which findings are new compared with enrichment analysis?
  6. Which conclusions should be treated cautiously because they depend on one model or one transcription factor?
LS0tCnRpdGxlOiAiV003IFJlZ3VsYXRvciBBY3Rpdml0eSIKc3VidGl0bGU6ICJQYXRod2F5IGFuZCB0cmFuc2NyaXB0aW9uIGZhY3RvciBhY3Rpdml0eSB3aXRoIGRlY291cGxlUiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwotLS0KCmBgYHtyIHNldHVwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KERFU2VxMikKbGlicmFyeShkcGx5cikKbGlicmFyeShyZWFkcikKbGlicmFyeSh0aWJibGUpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeSh3cml0ZXhsKQpsaWJyYXJ5KGRlY291cGxlUikKbGlicmFyeShDb21wbGV4SGVhdG1hcCkKbGlicmFyeShjaXJjbGl6ZSkKYGBgCgojIyBBaW0KCkluIERheSAxIHdlIGFza2VkIHdoaWNoIGdlbmUgc2V0cyB3ZXJlIGVucmljaGVkIGFtb25nIHRoZSBkaWZmZXJlbnRpYWwKZXhwcmVzc2lvbiByZXN1bHRzLiBUb2RheSB3ZSBhc2sgYSBkaWZmZXJlbnQgcXVlc3Rpb246IHdoaWNoIHVwc3RyZWFtIHBhdGh3YXlzCmFuZCB0cmFuc2NyaXB0aW9uIGZhY3RvcnMgbG9vayBhY3RpdmU/CgotIGluZmVyIHBhdGh3YXkgYWN0aXZpdHkgd2l0aCBQUk9HRU55IGFuZCBgZGVjb3VwbGVSYAotIGluZmVyIHRyYW5zY3JpcHRpb24gZmFjdG9yIGFjdGl2aXR5IHdpdGggRG9Sb3RoRUEgYW5kIGBkZWNvdXBsZVJgCi0gY29tcGFyZSBjb250cmFzdC1sZXZlbCBhY3Rpdml0eSBzY29yZXMgd2l0aCBzYW1wbGUtbGV2ZWwgYWN0aXZpdHkgc2NvcmVzCi0gY29ubmVjdCBhY3Rpdml0eSBzY29yZXMgYmFjayB0byB0aGUgT1JBLCBHU0VBLCBhbmQgR1NWQSByZXN1bHRzIGZyb20gRGF5IDEKCiMjIDEuIExvYWQgRGlmZmVyZW50aWFsIEV4cHJlc3Npb24gUmVzdWx0cwoKYGBge3IgbG9hZC1yZXN1bHRzfQpyZXMgPC0gcmVhZF90c3YoIkRFU2VxX3Jlc3VsdF9mdWxsLnRzdiIsIHNob3dfY29sX3R5cGVzID0gRkFMU0UpCgpyZXMKYGBgCgpSZW1vdmUgcm93cyB0aGF0IGNhbm5vdCBiZSB1c2VkIGZvciBhY3Rpdml0eSBpbmZlcmVuY2UuIElmIGEgZ2VuZSBhcHBlYXJzIG1vcmUKdGhhbiBvbmNlLCBrZWVwIHRoZSByb3cgd2l0aCB0aGUgc3Ryb25nZXN0IERFU2VxMiBzdGF0aXN0aWMuCgpgYGB7ciBjbGVhbi1yZXN1bHRzfQpyZXNfY2xlYW4gPC0gcmVzIHw+CiAgZmlsdGVyKCFpcy5uYShnZW5lX25hbWUpLCBnZW5lX25hbWUgIT0gIiIsICFpcy5uYShzdGF0KSkgfD4KICBncm91cF9ieShnZW5lX25hbWUpIHw+CiAgc2xpY2VfbWF4KG9yZGVyX2J5ID0gYWJzKHN0YXQpLCBuID0gMSwgd2l0aF90aWVzID0gRkFMU0UpIHw+CiAgdW5ncm91cCgpCgpucm93KHJlcykKbnJvdyhyZXNfY2xlYW4pCmBgYAoKIyMgMi4gTWFrZSBBIENvbnRyYXN0LUxldmVsIElucHV0IE1hdHJpeAoKRm9yIGBkZWNvdXBsZVJgLCByb3dzIGFyZSBnZW5lcyBhbmQgY29sdW1ucyBhcmUgc2FtcGxlcyBvciBjb250cmFzdHMuIEZpcnN0IHVzZQp0aGUgREVTZXEyIFdhbGQgc3RhdGlzdGljIGFzIGEgb25lLWNvbHVtbiBjb250cmFzdCBtYXRyaXguCgpgYGB7ciBjb250cmFzdC1tYXRyaXgtaGlkZGVuLCBpbmNsdWRlPUZBTFNFfQpjb250cmFzdF9tYXRyaXggPC0gcmVzX2NsZWFuIHw+CiAgc2VsZWN0KGdlbmVfbmFtZSwgc3RhdCkgfD4KICBjb2x1bW5fdG9fcm93bmFtZXMoImdlbmVfbmFtZSIpIHw+CiAgYXMubWF0cml4KCkKCmNvbG5hbWVzKGNvbnRyYXN0X21hdHJpeCkgPC0gIk1FS2lfdnNfY29udHJvbCIKYGBgCgpgYGB7ciBjb250cmFzdC1tYXRyaXgsIGV2YWw9RkFMU0V9CiMgUHJhY3RpY2U6CiMgU3RhcnQgZnJvbSByZXNfY2xlYW4uCiMgS2VlcCBnZW5lX25hbWUgYW5kIHN0YXQuCiMgTW92ZSBnZW5lX25hbWUgaW50byB0aGUgcm93IG5hbWVzIHdpdGggY29sdW1uX3RvX3Jvd25hbWVzKCkuCiMgQ29udmVydCB0aGUgcmVzdWx0IHRvIGEgbWF0cml4IHdpdGggYXMubWF0cml4KCkuCiMgR2l2ZSB0aGUgY29sdW1uIHRoZSBuYW1lIE1FS2lfdnNfY29udHJvbC4KYGBgCgpgYGB7ciBzaG93LWNvbnRyYXN0LW1hdHJpeH0KZGltKGNvbnRyYXN0X21hdHJpeCkKaGVhZChjb250cmFzdF9tYXRyaXgpCmBgYAoKUG9zaXRpdmUgdmFsdWVzIG1lYW4gaGlnaGVyIGV4cHJlc3Npb24gaW4gTUVLaS4gTmVnYXRpdmUgdmFsdWVzIG1lYW4gbG93ZXIKZXhwcmVzc2lvbiBpbiBNRUtpLgoKIyMgMy4gTWFrZSBBIFNhbXBsZS1MZXZlbCBJbnB1dCBNYXRyaXgKCkZvciBzYW1wbGUtbGV2ZWwgYWN0aXZpdHkgc2NvcmVzLCB1c2UgdmFyaWFuY2Utc3RhYmlsaXplZCBjb3VudHMsIGFzIGluIHRoZSBQQ0EKYW5kIEdTVkEgd29ya2Zsb3dzLgoKYGBge3IgbG9hZC1kZHN9CmRkcyA8LSByZWFkUkRTKCJkZHMucmRzIikKCnNhbXBsZV9pbmZvIDwtIGNvbERhdGEoZGRzKSB8PgogIGFzLmRhdGEuZnJhbWUoKSB8PgogIHNlbGVjdChzYW1wbGUsIGNvbmRpdGlvbikKCnNhbXBsZV9pbmZvCmBgYAoKYGBge3IgcHJlcGFyZS12c3QtbWF0cml4fQp2c3RfY291bnRzIDwtIHZzdChkZHMsIGJsaW5kID0gRkFMU0UpCgpleHByZXNzaW9uX21hdHJpeCA8LSBhc3NheSh2c3RfY291bnRzKSB8PgogIGFzLmRhdGEuZnJhbWUoKSB8PgogIHJvd25hbWVzX3RvX2NvbHVtbigiZ2VuZV9pZCIpIHw+CiAgaW5uZXJfam9pbihyZXNfY2xlYW4gfD4gc2VsZWN0KGdlbmVfaWQsIGdlbmVfbmFtZSksIGJ5ID0gImdlbmVfaWQiKSB8PgogIHNlbGVjdChnZW5lX25hbWUsIGFsbF9vZihzYW1wbGVfaW5mbyRzYW1wbGUpKSB8PgogIGdyb3VwX2J5KGdlbmVfbmFtZSkgfD4KICBzdW1tYXJpemUoYWNyb3NzKGFsbF9vZihzYW1wbGVfaW5mbyRzYW1wbGUpLCBtZWFuKSwgLmdyb3VwcyA9ICJkcm9wIikgfD4KICBjb2x1bW5fdG9fcm93bmFtZXMoImdlbmVfbmFtZSIpIHw+CiAgYXMubWF0cml4KCkKCmRpbShleHByZXNzaW9uX21hdHJpeCkKYGBgCgojIyA0LiBMb2FkIEFjdGl2aXR5IE1vZGVscwoKUFJPR0VOeSBjb25uZWN0cyBwYXRod2F5cyB0byByZXNwb25zaXZlIGdlbmVzLiBEb1JvdGhFQSBjb25uZWN0cyB0cmFuc2NyaXB0aW9uCmZhY3RvcnMgdG8gcmVndWxhdGVkIHRhcmdldCBnZW5lcy4KCmBgYHtyIGxvYWQtbW9kZWxzfQpwcm9nZW55X21vZGVsIDwtIHJlYWRfdHN2KCJwcm9nZW55X2h1bWFuX3RvcDUwMC50c3YiLCBzaG93X2NvbF90eXBlcyA9IEZBTFNFKQpkb3JvdGhlYV9uZXR3b3JrIDwtIHJlYWRfdHN2KCJkb3JvdGhlYV9odW1hbl9BQkMudHN2Iiwgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkKCnByb2dlbnlfbW9kZWwKZG9yb3RoZWFfbmV0d29yawpgYGAKCkNoZWNrIGhvdyBtYW55IHRhcmdldHMgZWFjaCBwYXRod2F5IG9yIHRyYW5zY3JpcHRpb24gZmFjdG9yIGhhcy4KCmBgYHtyIG1vZGVsLXNpemVzfQpwcm9nZW55X21vZGVsIHw+CiAgY291bnQoc291cmNlLCBzb3J0ID0gVFJVRSkKCmRvcm90aGVhX25ldHdvcmsgfD4KICBjb3VudChzb3VyY2UsIHNvcnQgPSBUUlVFKQpgYGAKCiMjIDUuIFBST0dFTnkgT24gVGhlIERFIENvbnRyYXN0CgpSdW4gYGRlY291cGxlUmAgb24gdGhlIGNvbnRyYXN0IG1hdHJpeC4gVGhlIHNjb3JlIGlzIGFuIGluZmVycmVkIHBhdGh3YXkKYWN0aXZpdHkgc2NvcmUgZm9yIHRoZSBNRUtpIHZlcnN1cyBjb250cm9sIGNvbXBhcmlzb24uCgpgYGB7ciBwcm9nZW55LWNvbnRyYXN0LWhpZGRlbiwgaW5jbHVkZT1GQUxTRX0KcHJvZ2VueV9jb250cmFzdCA8LSBydW5fbWxtKAogIG1hdCA9IGNvbnRyYXN0X21hdHJpeCwKICBuZXR3b3JrID0gcHJvZ2VueV9tb2RlbCwKICAubW9yID0gd2VpZ2h0LAogIG1pbnNpemUgPSA1CikgfD4KICBhcnJhbmdlKHNjb3JlKQpgYGAKCmBgYHtyIHByb2dlbnktY29udHJhc3QsIGV2YWw9RkFMU0V9CiMgUHJhY3RpY2U6CiMgVXNlIHJ1bl9tbG0oKS4KIyBtYXQgc2hvdWxkIGJlIGNvbnRyYXN0X21hdHJpeC4KIyBuZXR3b3JrIHNob3VsZCBiZSBwcm9nZW55X21vZGVsLgojIFVzZSB3ZWlnaHQgYXMgdGhlIGRpcmVjdGlvbiBhbmQgc3RyZW5ndGggY29sdW1uLgojIEFycmFuZ2UgdGhlIHJlc3VsdCBieSBzY29yZS4KYGBgCgpgYGB7ciBzaG93LXByb2dlbnktY29udHJhc3R9CnByb2dlbnlfY29udHJhc3QKYGBgCgpgYGB7ciBwcm9nZW55LWNvbnRyYXN0LXBsb3QsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTh9CnByb2dlbnlfY29udHJhc3QgfD4KICBnZ3Bsb3QoYWVzKHggPSBzY29yZSwgeSA9IHJlb3JkZXIoc291cmNlLCBzY29yZSksIGZpbGwgPSBzY29yZSA+IDApKSArCiAgZ2VvbV9jb2woKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiVFJVRSIgPSAiZmlyZWJyaWNrIiwgIkZBTFNFIiA9ICJzdGVlbGJsdWUiKSwgZ3VpZGUgPSAibm9uZSIpICsKICBsYWJzKAogICAgeCA9ICJQUk9HRU55IGFjdGl2aXR5IHNjb3JlIiwKICAgIHkgPSBOVUxMLAogICAgdGl0bGUgPSAiUGF0aHdheSBhY3Rpdml0eSBmcm9tIHRoZSBERVNlcTIgc3RhdGlzdGljIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCioqVGFzazoqKiBXaGljaCBwYXRod2F5cyBhcmUgbG93ZXIgaW4gTUVLaT8gV2hpY2ggcGF0aHdheXMgYXJlIGhpZ2hlcj8gRG9lcyB0aGUKTUFQSyByZXN1bHQgbWF0Y2ggdGhlIHRyZWF0bWVudD8KCiMjIDYuIFBST0dFTnkgQXQgU2FtcGxlIExldmVsCgpSdW4gdGhlIHNhbWUgbW9kZWwgb24gdGhlIHZhcmlhbmNlLXN0YWJpbGl6ZWQgZXhwcmVzc2lvbiBtYXRyaXguIFRoaXMgZ2l2ZXMgb25lCnBhdGh3YXkgYWN0aXZpdHkgc2NvcmUgcGVyIHNhbXBsZS4KCmBgYHtyIHByb2dlbnktc2FtcGxlLWhpZGRlbiwgaW5jbHVkZT1GQUxTRX0KcHJvZ2VueV9zYW1wbGUgPC0gcnVuX21sbSgKICBtYXQgPSBleHByZXNzaW9uX21hdHJpeCwKICBuZXR3b3JrID0gcHJvZ2VueV9tb2RlbCwKICAubW9yID0gd2VpZ2h0LAogIG1pbnNpemUgPSA1CikKCnByb2dlbnlfc2FtcGxlX21hdHJpeCA8LSBwcm9nZW55X3NhbXBsZSB8PgogIHNlbGVjdChzb3VyY2UsIGNvbmRpdGlvbiwgc2NvcmUpIHw+CiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGNvbmRpdGlvbiwgdmFsdWVzX2Zyb20gPSBzY29yZSkgfD4KICBjb2x1bW5fdG9fcm93bmFtZXMoInNvdXJjZSIpIHw+CiAgYXMubWF0cml4KCkKCnByb2dlbnlfc2FtcGxlX21hdHJpeCA8LSBwcm9nZW55X3NhbXBsZV9tYXRyaXhbLCBzYW1wbGVfaW5mbyRzYW1wbGVdCmBgYAoKYGBge3IgcHJvZ2VueS1zYW1wbGUsIGV2YWw9RkFMU0V9CiMgUHJhY3RpY2U6CiMgVXNlIHJ1bl9tbG0oKSBhZ2Fpbi4KIyBUaGlzIHRpbWUgbWF0IHNob3VsZCBiZSBleHByZXNzaW9uX21hdHJpeC4KIyBDb252ZXJ0IHRoZSBsb25nIHJlc3VsdCBpbnRvIGEgbWF0cml4IHdpdGggcGl2b3Rfd2lkZXIoKS4KIyBSb3dzIHNob3VsZCBiZSBwYXRod2F5cyBhbmQgY29sdW1ucyBzaG91bGQgYmUgc2FtcGxlcy4KYGBgCgpgYGB7ciBzaG93LXByb2dlbnktc2FtcGxlfQpwcm9nZW55X3NhbXBsZQpgYGAKCmBgYHtyIHByb2dlbnktc2FtcGxlLWhlYXRtYXAsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTh9CmNvbmRpdGlvbl9jb2xvcnMgPC0gYyhjb250cm9sID0gImdyZXk3MCIsIE1FS2kgPSAiZmlyZWJyaWNrIikKCnNhbXBsZV9hbm5vdGF0aW9uIDwtIEhlYXRtYXBBbm5vdGF0aW9uKAogIGNvbmRpdGlvbiA9IHNhbXBsZV9pbmZvJGNvbmRpdGlvbiwKICBjb2wgPSBsaXN0KGNvbmRpdGlvbiA9IGNvbmRpdGlvbl9jb2xvcnMpCikKCkhlYXRtYXAoCiAgcHJvZ2VueV9zYW1wbGVfbWF0cml4LAogIG5hbWUgPSAiYWN0aXZpdHkiLAogIGNvbCA9IGNvbG9yUmFtcDIoYygtNCwgMCwgNCksIGMoInN0ZWVsYmx1ZSIsICJ3aGl0ZSIsICJmaXJlYnJpY2siKSksCiAgdG9wX2Fubm90YXRpb24gPSBzYW1wbGVfYW5ub3RhdGlvbiwKICBjb2x1bW5fc3BsaXQgPSBzYW1wbGVfaW5mbyRjb25kaXRpb24sCiAgY2x1c3Rlcl9jb2x1bW5zID0gRkFMU0UsCiAgcm93X25hbWVzX2dwID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IDkpLAogIGNvbHVtbl9uYW1lc19ncCA9IGdyaWQ6OmdwYXIoZm9udHNpemUgPSA5KQopCmBgYAoKKipUYXNrOioqIFdoaWNoIHBhdGh3YXlzIHNlcGFyYXRlIGNvbnRyb2wgYW5kIE1FS2kgc2FtcGxlcz8gQXJlIHRoZXNlIHRoZSBzYW1lCnBhdGh3YXlzIHRoYXQgd2VyZSBzdHJvbmdlc3QgaW4gdGhlIGNvbnRyYXN0LWxldmVsIHBsb3Q/CgojIyA3LiBEb1JvdGhFQSBPbiBUaGUgREUgQ29udHJhc3QKCk5vdyBpbmZlciB0cmFuc2NyaXB0aW9uIGZhY3RvciBhY3Rpdml0eSBmcm9tIHRoZSBzYW1lIERFU2VxMiBzdGF0aXN0aWMgbWF0cml4LgoKYGBge3IgdGYtY29udHJhc3QtaGlkZGVuLCBpbmNsdWRlPUZBTFNFfQp0Zl9jb250cmFzdCA8LSBydW5fdWxtKAogIG1hdCA9IGNvbnRyYXN0X21hdHJpeCwKICBuZXR3b3JrID0gZG9yb3RoZWFfbmV0d29yaywKICBtaW5zaXplID0gNQopIHw+CiAgYXJyYW5nZShkZXNjKGFicyhzY29yZSkpKQoKdGZfY29udHJhc3RfdG9wIDwtIGJpbmRfcm93cygKICB0Zl9jb250cmFzdCB8PiBzbGljZV9tYXgoc2NvcmUsIG4gPSAxNSksCiAgdGZfY29udHJhc3QgfD4gc2xpY2VfbWluKHNjb3JlLCBuID0gMTUpCikgfD4KICBkaXN0aW5jdChzb3VyY2UsIC5rZWVwX2FsbCA9IFRSVUUpCmBgYAoKYGBge3IgdGYtY29udHJhc3QsIGV2YWw9RkFMU0V9CiMgUHJhY3RpY2U6CiMgVXNlIHJ1bl91bG0oKS4KIyBtYXQgc2hvdWxkIGJlIGNvbnRyYXN0X21hdHJpeC4KIyBuZXR3b3JrIHNob3VsZCBiZSBkb3JvdGhlYV9uZXR3b3JrLgojIEFycmFuZ2UgdGhlIHJlc3VsdCBieSB0aGUgc3Ryb25nZXN0IGFic29sdXRlIHNjb3Jlcy4KYGBgCgpgYGB7ciBzaG93LXRmLWNvbnRyYXN0fQp0Zl9jb250cmFzdApgYGAKCmBgYHtyIHRmLWNvbnRyYXN0LXBsb3QsIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTh9CnRmX2NvbnRyYXN0X3RvcCB8PgogIGdncGxvdChhZXMoeCA9IHNjb3JlLCB5ID0gcmVvcmRlcihzb3VyY2UsIHNjb3JlKSwgZmlsbCA9IHNjb3JlID4gMCkpICsKICBnZW9tX2NvbCgpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKCJUUlVFIiA9ICJmaXJlYnJpY2siLCAiRkFMU0UiID0gInN0ZWVsYmx1ZSIpLCBndWlkZSA9ICJub25lIikgKwogIGxhYnMoCiAgICB4ID0gIlRGIGFjdGl2aXR5IHNjb3JlIiwKICAgIHkgPSBOVUxMLAogICAgdGl0bGUgPSAiVG9wIHRyYW5zY3JpcHRpb24gZmFjdG9yIGFjdGl2aXRpZXMgZnJvbSB0aGUgREVTZXEyIHN0YXRpc3RpYyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpQb3NpdGl2ZSBzY29yZXMgbWVhbiB0aGF0IHRoZSB0YXJnZXQtZ2VuZSBwYXR0ZXJuIGlzIGhpZ2hlciBpbiBNRUtpLiBOZWdhdGl2ZQpzY29yZXMgbWVhbiB0aGF0IHRoZSB0YXJnZXQtZ2VuZSBwYXR0ZXJuIGlzIGxvd2VyIGluIE1FS2kuCgojIyA4LiBJbnNwZWN0IE9uZSBUcmFuc2NyaXB0aW9uIEZhY3RvcgoKUGljayB0aGUgc3Ryb25nZXN0IHRyYW5zY3JpcHRpb24gZmFjdG9yIGFuZCBsb29rIGF0IGl0cyB0YXJnZXQgZ2VuZXMgaW4gdGhlCmRpZmZlcmVudGlhbCBleHByZXNzaW9uIHRhYmxlLgoKYGBge3IgdGYtdGFyZ2V0cy1oaWRkZW4sIGluY2x1ZGU9RkFMU0V9CnNlbGVjdGVkX3RmIDwtIHRmX2NvbnRyYXN0IHw+CiAgc2xpY2UoMSkgfD4KICBwdWxsKHNvdXJjZSkKCnNlbGVjdGVkX3RmX3RhcmdldHMgPC0gZG9yb3RoZWFfbmV0d29yayB8PgogIGZpbHRlcihzb3VyY2UgPT0gc2VsZWN0ZWRfdGYpIHw+CiAgaW5uZXJfam9pbihyZXNfY2xlYW4sIGJ5ID0gYygidGFyZ2V0IiA9ICJnZW5lX25hbWUiKSkgfD4KICBhcnJhbmdlKHBhZGopIHw+CiAgc2VsZWN0KHNvdXJjZSwgY29uZmlkZW5jZSwgdGFyZ2V0LCBtb3IsIGxvZzJGb2xkQ2hhbmdlLCBzdGF0LCBwYWRqKQpgYGAKCmBgYHtyIHRmLXRhcmdldHMsIGV2YWw9RkFMU0V9CiMgUHJhY3RpY2U6CiMgQ2hvb3NlIG9uZSB0cmFuc2NyaXB0aW9uIGZhY3RvciBmcm9tIHRmX2NvbnRyYXN0LgojIEZpbHRlciBkb3JvdGhlYV9uZXR3b3JrIHRvIHRoaXMgdHJhbnNjcmlwdGlvbiBmYWN0b3IuCiMgSm9pbiB0aGUgdGFyZ2V0cyB0byByZXNfY2xlYW4uCiMgQXJyYW5nZSBieSBwYWRqLgpgYGAKCmBgYHtyIHNob3ctdGYtdGFyZ2V0c30Kc2VsZWN0ZWRfdGYKc2VsZWN0ZWRfdGZfdGFyZ2V0cwpgYGAKCioqVGFzazoqKiBBcmUgdGhlIHRhcmdldCBnZW5lcyBtb3N0bHkgbW92aW5nIGluIHRoZSBzYW1lIGRpcmVjdGlvbiBhcyB0aGUKdHJhbnNjcmlwdGlvbiBmYWN0b3IgYWN0aXZpdHkgc2NvcmUgc3VnZ2VzdHM/CgojIyA5LiBEb1JvdGhFQSBBdCBTYW1wbGUgTGV2ZWwKClJ1biB0aGUgdHJhbnNjcmlwdGlvbiBmYWN0b3IgbW9kZWwgb24gdGhlIHZhcmlhbmNlLXN0YWJpbGl6ZWQgZXhwcmVzc2lvbiBtYXRyaXguCkZvciB0aGUgaGVhdG1hcCwgc2hvdyB0aGUgdHJhbnNjcmlwdGlvbiBmYWN0b3JzIHdpdGggdGhlIHN0cm9uZ2VzdCBjb250cmFzdC1sZXZlbApzY29yZXMuCgpgYGB7ciB0Zi1zYW1wbGUtaGlkZGVuLCBpbmNsdWRlPUZBTFNFfQp0Zl9zYW1wbGUgPC0gcnVuX3VsbSgKICBtYXQgPSBleHByZXNzaW9uX21hdHJpeCwKICBuZXR3b3JrID0gZG9yb3RoZWFfbmV0d29yaywKICBtaW5zaXplID0gNQopCgp0Zl9zYW1wbGVfbWF0cml4IDwtIHRmX3NhbXBsZSB8PgogIHNlbGVjdChzb3VyY2UsIGNvbmRpdGlvbiwgc2NvcmUpIHw+CiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IGNvbmRpdGlvbiwgdmFsdWVzX2Zyb20gPSBzY29yZSkgfD4KICBjb2x1bW5fdG9fcm93bmFtZXMoInNvdXJjZSIpIHw+CiAgYXMubWF0cml4KCkKCnRmX3NhbXBsZV9tYXRyaXggPC0gdGZfc2FtcGxlX21hdHJpeFssIHNhbXBsZV9pbmZvJHNhbXBsZV0KCnRvcF90ZnMgPC0gdGZfY29udHJhc3QgfD4KICBzbGljZV9oZWFkKG4gPSAzMCkgfD4KICBwdWxsKHNvdXJjZSkKCnRmX2hlYXRtYXBfbWF0cml4IDwtIHRmX3NhbXBsZV9tYXRyaXhbdG9wX3RmcywgXQp0Zl9oZWF0bWFwX3NjYWxlZCA8LSB0KHNjYWxlKHQodGZfaGVhdG1hcF9tYXRyaXgpKSkKdGZfaGVhdG1hcF9zY2FsZWRbaXMubmEodGZfaGVhdG1hcF9zY2FsZWQpXSA8LSAwCmBgYAoKYGBge3IgdGYtc2FtcGxlLCBldmFsPUZBTFNFfQojIFByYWN0aWNlOgojIFVzZSBydW5fdWxtKCkgYWdhaW4uCiMgVGhpcyB0aW1lIG1hdCBzaG91bGQgYmUgZXhwcmVzc2lvbl9tYXRyaXguCiMgQ29udmVydCB0aGUgbG9uZyByZXN1bHQgaW50byBhIG1hdHJpeCB3aXRoIHBpdm90X3dpZGVyKCkuCiMgS2VlcCB0aGUgc3Ryb25nZXN0IHRyYW5zY3JpcHRpb24gZmFjdG9ycyBmcm9tIHRmX2NvbnRyYXN0IGZvciB0aGUgaGVhdG1hcC4KYGBgCgpgYGB7ciBzaG93LXRmLXNhbXBsZX0KdGZfc2FtcGxlCmBgYAoKYGBge3IgdGYtc2FtcGxlLWhlYXRtYXAsIGZpZy5oZWlnaHQ9OSwgZmlnLndpZHRoPTh9CkhlYXRtYXAoCiAgdGZfaGVhdG1hcF9zY2FsZWQsCiAgbmFtZSA9ICJURlxuei1zY29yZSIsCiAgY29sID0gY29sb3JSYW1wMihjKC0yLCAwLCAyKSwgYygic3RlZWxibHVlIiwgIndoaXRlIiwgImZpcmVicmljayIpKSwKICB0b3BfYW5ub3RhdGlvbiA9IHNhbXBsZV9hbm5vdGF0aW9uLAogIGNvbHVtbl9zcGxpdCA9IHNhbXBsZV9pbmZvJGNvbmRpdGlvbiwKICBjbHVzdGVyX2NvbHVtbnMgPSBGQUxTRSwKICByb3dfbmFtZXNfZ3AgPSBncmlkOjpncGFyKGZvbnRzaXplID0gOCksCiAgY29sdW1uX25hbWVzX2dwID0gZ3JpZDo6Z3Bhcihmb250c2l6ZSA9IDkpCikKYGBgCgoqKlRhc2s6KiogV2hpY2ggdHJhbnNjcmlwdGlvbiBmYWN0b3JzIHNlcGFyYXRlIGNvbnRyb2wgYW5kIE1FS2kgc2FtcGxlcz8gV2hpY2gKb25lcyBhcmUgYWxzbyBzdHJvbmcgaW4gdGhlIGNvbnRyYXN0LWxldmVsIHJlc3VsdD8KCiMjIDEwLiBTYXZlIFJlc3VsdCBUYWJsZXMKCmBgYHtyIHNhdmUtcmVzdWx0c30Kd3JpdGVfdHN2KHByb2dlbnlfY29udHJhc3QsICJQUk9HRU55X2NvbnRyYXN0X2FjdGl2aXR5LnRzdiIpCndyaXRlX3hsc3gocHJvZ2VueV9jb250cmFzdCwgIlBST0dFTnlfY29udHJhc3RfYWN0aXZpdHkueGxzeCIpCgp3cml0ZV90c3YocHJvZ2VueV9zYW1wbGUsICJQUk9HRU55X3NhbXBsZV9hY3Rpdml0eS50c3YiKQp3cml0ZV94bHN4KHByb2dlbnlfc2FtcGxlLCAiUFJPR0VOeV9zYW1wbGVfYWN0aXZpdHkueGxzeCIpCgp3cml0ZV90c3YodGZfY29udHJhc3QsICJEb1JvdGhFQV9jb250cmFzdF9hY3Rpdml0eS50c3YiKQp3cml0ZV94bHN4KHRmX2NvbnRyYXN0LCAiRG9Sb3RoRUFfY29udHJhc3RfYWN0aXZpdHkueGxzeCIpCgp3cml0ZV90c3YodGZfc2FtcGxlLCAiRG9Sb3RoRUFfc2FtcGxlX2FjdGl2aXR5LnRzdiIpCndyaXRlX3hsc3godGZfc2FtcGxlLCAiRG9Sb3RoRUFfc2FtcGxlX2FjdGl2aXR5Lnhsc3giKQpgYGAKCiMjIDExLiBGaW5hbCBJbnRlcnByZXRhdGlvbgoKVXNlIHRoZSBwYXRod2F5IGFuZCB0cmFuc2NyaXB0aW9uIGZhY3RvciBhY3Rpdml0eSByZXN1bHRzIHRvZ2V0aGVyIHdpdGggdGhlCkRheSAxIE9SQSwgR1NFQSwgYW5kIEdTVkEgcmVzdWx0cy4KCkFuc3dlciB0aGVzZSBxdWVzdGlvbnM6CgoxLiBXaGljaCBQUk9HRU55IHBhdGh3YXlzIGJlc3QgZXhwbGFpbiB0aGUgTUVLaSBlZmZlY3Q/CjIuIFdoaWNoIHRyYW5zY3JpcHRpb24gZmFjdG9ycyBzdXBwb3J0IHRoZSBzYW1lIGludGVycHJldGF0aW9uPwozLiBXaGljaCBhY3Rpdml0eSBzY29yZXMgYXJlIHZpc2libGUgYm90aCBpbiB0aGUgY29udHJhc3QtbGV2ZWwgcmVzdWx0cyBhbmQgYXQKICAgc2FtcGxlIGxldmVsPwo0LiBXaGljaCBmaW5kaW5ncyBhZ3JlZSB3aXRoIHRoZSBIYWxsbWFyayBhbmQgR08gcmVzdWx0cyBmcm9tIERheSAxPwo1LiBXaGljaCBmaW5kaW5ncyBhcmUgbmV3IGNvbXBhcmVkIHdpdGggZW5yaWNobWVudCBhbmFseXNpcz8KNi4gV2hpY2ggY29uY2x1c2lvbnMgc2hvdWxkIGJlIHRyZWF0ZWQgY2F1dGlvdXNseSBiZWNhdXNlIHRoZXkgZGVwZW5kIG9uIG9uZQogICBtb2RlbCBvciBvbmUgdHJhbnNjcmlwdGlvbiBmYWN0b3I/Cg==