Prepare analysis workflow

Set filepaths and parameters

set.seed(42)
knitr::opts_knit$set(root.dir = rprojroot::find_rstudio_root_file())
options(
  readr.show_progress = FALSE,
  digits = 2
)

Load packages

suppressPackageStartupMessages({
  library(scater)
  library(scran)
  library(tidyverse)
  library(DESeq2)
  library(ashr)
  library(EnhancedVolcano)
  library(BiocParallel)
  library(zinbwave)
  theme_set(theme_bw())
})

Define file paths

data_dir <- "./data"
figures_dir <- file.path("./figures")
get_counts_stats <- function(sce10x_macrophage, cluster_to_remove) {
  gene_counts_by_cluster <-
    t(assay(sce10x_macrophage)) %>%
    as.matrix() %>%
    as_tibble() %>%
    group_by(cluster = colData(sce10x_macrophage)$clusters_condition) %>%
    summarise_all(list(~ mean(.)))

  umi_counts_max <-
    gene_counts_by_cluster[, -1] %>%
    summarize_all(list(~ max(.))) %>%
    mutate(dummy = "dummy") %>%
    pivot_longer(-dummy, names_to = "gene_name", values_to = "max_expr") %>%
    select(-dummy)

  stats <-
    perFeatureQCMetrics(sce10x_macrophage,
      exprs_values = c("counts"),
      flatten = TRUE
    ) %>%
    as_tibble() %>%
    bind_cols(., rowData(sce10x_macrophage) %>%
      as_tibble()) %>%
    bind_cols(., umi_counts_max)

  return(stats)
}

get_lfc <- function(contrast, name, dds) {
  lfcShrink(dds,
    contrast = contrast,
    type = "ashr",
    svalue = T
  ) %>%
    as_tibble() %>%
    dplyr::select(-baseMean) %>%
    mutate(svalue = abs(svalue)) %>%
    set_names(paste(c("lfc", "lfc_se", "svalue"), name, sep = "_"))
}

plot_expression <- function(gene) {
  plotExpression(sce10x_macrophage,
    features = gene,
    x = "sample",
    exprs_values = "logcounts",
    colour_by = "condition",
    point_size = 1
  ) +
    facet_wrap(~ colData(sce10x_macrophage)$clusters, ncol = 1)
}

plot_volcano <- function(var_name, lfc_thresh, svalue_thresh, lfc, suffix, label = NULL) {
  p <- EnhancedVolcano(lfc,
    lab = lfc %>% pull(gene_id),
    x = paste0("lfc_", var_name),
    selectLab = label,
    y = paste0("svalue_", var_name),
    xlab = bquote(~ italic(Moderated) ~ Log[2] ~ FC),
    ylab = bquote(~ -Log[10] ~ italic(svalue)),
    col = c("grey30", "forestgreen", "red2", "royalblue"),
    pCutoff = svalue_thresh,
    FCcutoff = lfc_thresh,
    legendLabels = c(
      "NS",
      expression(Log[2] ~ FC),
      "s-value",
      expression(s - value ~ and ~ log[2] ~ FC)
    )
  )

  ggsave(file.path(figures_dir, paste0("volcano_sc", var_name, "_effect_", suffix, ".pdf")), p)
  return(p)
}

Load data

sce10x <-
  readRDS(file.path(
    data_dir,
    "preprocessed",
    "sce10x_filtered_final.rds"
  ))
sce10x_macrophage <- sce10x[, colData(sce10x)$celltype == "macrophage"]
rm(sce10x)
table(colData(sce10x_macrophage)$clusters, colData(sce10x_macrophage)$sample)
            
             yng1 yng2 yng3 aged1 aged2 aged3 aged4
  macrophage  282  274  398    58   245   149   189

Discard low counts genes

n_exprs_genes <-
  nexprs(sce10x_macrophage,
    detection_limit = 5,
    byrow = TRUE
  )
keep <- n_exprs_genes >= 10
table(keep)
keep
FALSE  TRUE 
30428  3046 
sce10x_macrophage <- sce10x_macrophage[keep, ]
batch_genes_remove <-
  c(
    "Ccr7", "Cd209c", "Ifit1", "Slc40a1",
    "Fgf13", "Igfbp5", "Gpnmb", "Upp1", "Irgm2",
    "S100a8", "Chil3", "Saa3", "S100a9", "Apol7c", "F5", "Upp1"
  )
map(batch_genes_remove, plot_expression)
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

[[6]]

[[7]]

[[8]]

[[9]]

[[10]]

[[11]]

[[12]]

[[13]]

[[14]]

[[15]]

[[16]]

sce10x_macrophage <- sce10x_macrophage[!(rownames(sce10x_macrophage) %in% batch_genes_remove), ]

Discard low cluster mean expression genes

stats <- get_counts_stats(sce10x_macrophage)
stats
max_thresh <- .3
stats %>%
  count(max_expr > max_thresh)
ggplot(stats) +
  geom_histogram(aes(max_expr), bins = 100) +
  scale_x_log10() +
  facet_wrap(~top_hvg_macrophage) +
  geom_vline(xintercept = max_thresh)

genes_to_plot <-
  stats %>%
  filter(max_expr <= max_thresh) %>%
  arrange(-max_expr) %>%
  pull(gene_name)
map(genes_to_plot[1:5], plot_expression)
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

genes_keep <-
  stats %>%
  filter(max_expr > max_thresh) %>%
  pull(gene_name)
length(genes_keep)
[1] 2977
sce10x_macrophage <- sce10x_macrophage[genes_keep, ]
ggplot(
  stats,
  aes(
    mean,
    detected
  )
) +
  scale_x_log10() +
  geom_point(size = 0.3, aes(color = max_expr < 1)) +
  geom_text(aes(
    label = gene_name
  ),
  check_overlap = TRUE, nudge_y = -0.1, size = 2.5
  )

Create Design Matrix

design_macrophage <-
  model.matrix(~ -1 + condition + sample,
    data = colData(sce10x_macrophage)
  )[, -c(5)]
colnames(design_macrophage) <- str_replace(colnames(design_macrophage), "condition|sample", "")

design_macrophage[1:3, ]
                    yng aged yng2 yng3 aged2 aged3 aged4
ATGGTTGGTTGTAAAG_d1   0    1    0    0     0     0     0
GATGAGGGTTCAGTAC_d1   0    1    0    0     0     0     0
GCAACCGTCACCTTGC_d1   0    1    0    0     0     0     0

Compute Observational Weights

assay(sce10x_macrophage, "counts") <- round(assay(sce10x_macrophage, "counts"))
system.time({
  zinb <-
    zinbFit(sce10x_macrophage,
      K = 0,
      X = design_macrophage,
      verbose = TRUE,
      BPPARAM = MulticoreParam(3),
      epsilon = 1e12
    )
})
Create model:
ok
Initialize parameters:
ok
Optimize parameters:
Iteration 1
penalized log-likelihood = -10786111.760099
After dispersion optimization = -23318712.4269814
   user  system elapsed 
   45.7     2.2    24.6 
After right optimization = -23318260.1799767
After orthogonalization = -23318260.1799767
   user  system elapsed 
   30.8     1.3    17.4 
After left optimization = -20653340.6726067
After orthogonalization = -20653340.6726067
Iteration 2
penalized log-likelihood = -20653340.6726067
After dispersion optimization = -20653340.6757374
   user  system elapsed 
   32.3     1.1    33.8 
After right optimization = -20653321.6706135
After orthogonalization = -20653321.6706135
   user  system elapsed 
   6.78    0.74    3.76 
After left optimization = -20653321.6429825
After orthogonalization = -20653321.6429825
Iteration 3
penalized log-likelihood = -20653321.6429825
ok
   user  system elapsed 
    333      22     158 
weights <- computeObservationalWeights(zinb, as.matrix(assay(sce10x_macrophage)))
dimnames(weights) <- dimnames(sce10x_macrophage)
assay(sce10x_macrophage, "weights") <- weights

convert to SCE to DESeqDataSet object

dds_macrophage <-
  convertTo(sce10x_macrophage, type = c("DESeq2"))
converting counts to integer mode
design(dds_macrophage) <- design_macrophage
assay(dds_macrophage, "weights") <- assay(sce10x_macrophage, "weights")
dds_macrophage <- estimateSizeFactors(dds_macrophage, type = "poscounts")
dds_macrophage <-
  DESeq(dds_macrophage,
    test = "LRT",
    useT = TRUE,
    reduced = design_macrophage[, 1:2],
    minmu = 1e-6,
    parallel = TRUE,
    BPPARAM = MulticoreParam(3),
    minRep = Inf
  )
using supplied model matrix
using pre-existing size factors
estimating dispersions
gene-wise dispersion estimates: 3 workers
mean-dispersion relationship
-- note: fitType='parametric', but the dispersion trend was not well captured by the
   function: y = a/x + b, and a local regression fit was automatically substituted.
   specify fitType='local' or 'mean' to avoid this message next time.
final dispersion estimates, fitting model and testing: 3 workers
plotDispEsts(dds_macrophage)

resultsNames(dds_macrophage)
[1] "yng"   "aged"  "yng2"  "yng3"  "aged2" "aged3" "aged4"

Plot batch effects

batch_dt <-
  rowData(dds_macrophage) %>%
  as_tibble(rownames = "gene_id") %>%
  select("gene_id", "yng2":"aged4")
ggplot(
  batch_dt,
  aes(
    x = yng2,
    y = yng3
  )
) +
  geom_point(
    size = .2,
    alpha = 0.3
  ) +
  geom_text(aes(
    label = gene_id
  ),
  check_overlap = TRUE, nudge_y = -0.15, size = 3
  )

ggplot(
  batch_dt,
  aes(
    x = aged2,
    y = aged3
  )
) +
  geom_point(
    size = .2,
    alpha = 0.3
  ) +
  geom_text(aes(
    label = gene_id
  ),
  check_overlap = TRUE, nudge_y = -0.15, size = 3
  )

ggplot(
  batch_dt,
  aes(
    x = aged2,
    y = aged4
  )
) +
  geom_point(
    size = .2,
    alpha = 0.3
  ) +
  geom_text(aes(
    label = gene_id
  ),
  check_overlap = TRUE, nudge_y = -0.15, size = 3
  )

Test contrasts

c1 <- c(1, -1, 0, 0, 0, 0, 0)

macrophage_contrast_vec <- list(
  macrophage_aging = c1
)
lfc_macrophage <-
  imap_dfc(macrophage_contrast_vec,
    get_lfc,
    dds = dds_macrophage
  ) %>%
  bind_cols(
    gene_id = rownames(dds_macrophage),
    .
  )
using 'ashr' for LFC shrinkage. If used in published research, please cite:
    Stephens, M. (2016) False discovery rates: a new deal. Biostatistics, 18:2.
    https://doi.org/10.1093/biostatistics/kxw041
lfc_macrophage <-
  left_join(lfc_macrophage,
    rowData(dds_macrophage) %>%
      as_tibble(rownames = "gene_id"),
    by = "gene_id"
  )
lfc_macrophage

Save data

write_tsv(
  lfc_macrophage,
  file.path(
    data_dir,
    "preprocessed",
    "macrophage_scrnaseq_moderated_lfc.txt"
  )
)
rowData(sce10x_macrophage) <-
  left_join(rowData(sce10x_macrophage) %>%
    as_tibble(rownames = "gene_id") %>%
    select(gene_id), lfc_macrophage, by = "gene_id") %>%
  DataFrame(.)

rownames(rowData(sce10x_macrophage)) <- rowData(sce10x_macrophage)$gene_id
saveRDS(
  sce10x_macrophage,
  file.path(
    data_dir,
    "preprocessed",
    "sce10x_macrophage_filtered_final.rds"
  )
)

Make volcano plots

lfc_thresh <- 1
svalue_thresh <- 10e-8
volcano_plots <-
  map(names(macrophage_contrast_vec),
    plot_volcano,
    lfc_thresh = lfc_thresh,
    svalue_thresh = svalue_thresh,
    lfc = lfc_macrophage,
    suffix = "macrophage_labeled"
  )
One or more p-values is 0. Converting to 10^-1 * current lowest non-zero p-value...Saving 7 x 7 in image
volcano_plots
[[1]]

lfc_thresh <- 1
svalue_thresh <- 10e-8
volcano_plots_unlabeled <-
  map(names(macrophage_contrast_vec),
    plot_volcano,
    lfc_thresh = lfc_thresh,
    svalue_thresh = svalue_thresh,
    lfc = lfc_macrophage,
    suffix = "macrophage_unlabeled",
    label = c("")
  )
One or more p-values is 0. Converting to 10^-1 * current lowest non-zero p-value...Saving 7 x 7 in image
volcano_plots_unlabeled
[[1]]

genes_hits_aging_up <-
  lfc_macrophage %>%
  filter(lfc_macrophage_aging > 1) %>%
  arrange(-lfc_macrophage_aging) %>%
  pull(gene_id)
genes_hits_aging_up
 [1] "mt-Nd3"                "Zfp36"                 "Ifi27l2a"              "Rnase4"                "Folr2"                 "Apoe"                 
 [7] "F13a1"                 "mt-Co2"                "Cst3"                  "Cd83"                  "ENSMUSG00000034708.11" "Rps23"                
map(genes_hits_aging_up, plot_expression)
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

[[6]]

[[7]]

[[8]]

[[9]]

[[10]]

[[11]]

[[12]]

genes_hits_aging_down <-
  lfc_macrophage %>%
  filter(lfc_macrophage_aging < -1) %>%
  arrange(lfc_macrophage_aging) %>%
  pull(gene_id)
genes_hits_aging_down
 [1] "Plcb1"                          "Lyz1"                           "Gsr"                            "Tmsb10"                        
 [5] "Vcan"                           "Msrb1"                          "Thbs1"                          "Prdx5"                         
 [9] "Smpdl3a"                        "Grk5"                           "Slc41a2"                        "Arhgap26_ENSMUSG00000036452.18"
[13] "Adgre4"                         "Rbm3"                           "Ern1"                           "Samhd1"                        
[17] "Cytip"                          "Emilin2"                        "Atp11b"                         "Sipa1l1"                       
[21] "Cers6"                          "Pbx1"                           "Abtb2"                          "Gda"                           
[25] "Jarid2"                         "Nrg1"                           "Runx2"                         
map(genes_hits_aging_down, plot_expression)
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

[[6]]

[[7]]

[[8]]

[[9]]

[[10]]

[[11]]

[[12]]

[[13]]

[[14]]

[[15]]

[[16]]

[[17]]

[[18]]

[[19]]

[[20]]

[[21]]

[[22]]

[[23]]

[[24]]

[[25]]

[[26]]

[[27]]

sessionInfo()
R version 4.0.2 (2020-06-22)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: Ubuntu 18.04.5 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.7.1
LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.7.1

locale:
 [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C               LC_TIME=en_US.UTF-8        LC_COLLATE=en_US.UTF-8     LC_MONETARY=en_US.UTF-8   
 [6] LC_MESSAGES=en_US.UTF-8    LC_PAPER=en_US.UTF-8       LC_NAME=C                  LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C       

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

other attached packages:
 [1] zinbwave_1.10.0             BiocParallel_1.22.0         EnhancedVolcano_1.6.0       ggrepel_0.8.2               ashr_2.2-47                
 [6] DESeq2_1.28.1               forcats_0.5.0               stringr_1.4.0               dplyr_1.0.2                 purrr_0.3.4                
[11] readr_1.3.1                 tidyr_1.1.2                 tibble_3.0.3                tidyverse_1.3.0             scran_1.16.0               
[16] scater_1.16.2               ggplot2_3.3.2               SingleCellExperiment_1.10.1 SummarizedExperiment_1.18.2 DelayedArray_0.14.1        
[21] matrixStats_0.56.0          Biobase_2.48.0              GenomicRanges_1.40.0        GenomeInfoDb_1.24.2         IRanges_2.22.2             
[26] S4Vectors_0.26.1            BiocGenerics_0.34.0        

loaded via a namespace (and not attached):
  [1] ggbeeswarm_0.6.0          colorspace_1.4-1          ellipsis_0.3.1            rprojroot_1.3-2           XVector_0.28.0           
  [6] base64enc_0.1-3           BiocNeighbors_1.6.0       fs_1.5.0                  rstudioapi_0.11           farver_2.0.3             
 [11] bit64_4.0.5               AnnotationDbi_1.50.3      fansi_0.4.1               lubridate_1.7.9           xml2_1.3.2               
 [16] splines_4.0.2             R.methodsS3_1.8.1         geneplotter_1.66.0        knitr_1.29                jsonlite_1.7.1           
 [21] packrat_0.5.0             broom_0.7.0               annotate_1.66.0           dbplyr_1.4.4              R.oo_1.24.0              
 [26] compiler_4.0.2            httr_1.4.2                dqrng_0.2.1               backports_1.1.9           assertthat_0.2.1         
 [31] Matrix_1.2-18             limma_3.44.3              cli_2.0.2                 BiocSingular_1.4.0        htmltools_0.5.0          
 [36] tools_4.0.2               rsvd_1.0.3                igraph_1.2.5              gtable_0.3.0              glue_1.4.2               
 [41] GenomeInfoDbData_1.2.3    Rcpp_1.0.5                softImpute_1.4            cellranger_1.1.0          styler_1.3.2             
 [46] vctrs_0.3.4               DelayedMatrixStats_1.10.1 xfun_0.17                 rvest_0.3.6               lifecycle_0.2.0          
 [51] irlba_2.3.3               statmod_1.4.34            XML_3.99-0.5              edgeR_3.30.3              zlibbioc_1.34.0          
 [56] scales_1.1.1              hms_0.5.3                 RColorBrewer_1.1-2        rematch2_2.1.2            yaml_2.2.1               
 [61] memoise_1.1.0             gridExtra_2.3             SQUAREM_2020.4            stringi_1.5.3             RSQLite_2.2.0            
 [66] genefilter_1.70.0         truncnorm_1.0-8           rlang_0.4.7               pkgconfig_2.0.3           bitops_1.0-6             
 [71] invgamma_1.1              evaluate_0.14             lattice_0.20-41           labeling_0.3              cowplot_1.1.0            
 [76] bit_4.0.4                 tidyselect_1.1.0          magrittr_1.5              R6_2.4.1                  generics_0.0.2           
 [81] DBI_1.1.0                 pillar_1.4.6              haven_2.3.1               withr_2.2.0               mixsqp_0.3-43            
 [86] survival_3.1-12           RCurl_1.98-1.2            modelr_0.1.8              crayon_1.3.4              rmarkdown_2.3            
 [91] viridis_0.5.1             locfit_1.5-9.4            grid_4.0.2                readxl_1.3.1              blob_1.2.1               
 [96] reprex_0.3.0              digest_0.6.25             xtable_1.8-4              R.cache_0.14.0            R.utils_2.10.1           
[101] munsell_0.5.0             beeswarm_0.2.3            viridisLite_0.3.0         vipor_0.4.5              
LS0tCnRpdGxlOiAiTW91c2UgTXVzY2xlIFN0ZW0gQ2VsbCBQcm9qZWN0ICIKc3VidGl0bGU6ICJQYXJ0IDVjOiBydW4gZGlmZmVyZW50aWFsIGV4cHJlc3Npb24gYW5hbHlzaXMgb24gbWFjcm9waGFnZSBjZWxscyIKYXV0aG9yOiAKLSBuYW1lOiBSaWNrIEZhcm91bmkKICBhZmZpbGlhdGlvbjoKICAtICZjcnVrIEfDqW5vbWUgUXXDqWJlYyBJbm5vdmF0aW9uIENlbnRyZSwgTWNHaWxsIFVuaXZlcnNpdHksIE1vbnRyZWFsLCBDYW5hZGEKZGF0ZTogJ2ByIGZvcm1hdChTeXMuRGF0ZSgpLCAiJVktJUItJWQiKWAnCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIHRvYzogbm8KICAgIHRvY19mbG9hdDogCiAgICAgIGNvbGxhcHNlZDogZmFsc2UKICAgICAgc21vb3RoX3Njcm9sbDogZmFsc2UKLS0tCgoKIyBQcmVwYXJlIGFuYWx5c2lzIHdvcmtmbG93CgojIyBTZXQgZmlsZXBhdGhzIGFuZCBwYXJhbWV0ZXJzCgpgYGB7ciBzZXR1cH0Kc2V0LnNlZWQoNDIpCmtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gcnByb2pyb290OjpmaW5kX3JzdHVkaW9fcm9vdF9maWxlKCkpCm9wdGlvbnMoCiAgcmVhZHIuc2hvd19wcm9ncmVzcyA9IEZBTFNFLAogIGRpZ2l0cyA9IDIKKQpgYGAKCiMjIExvYWQgcGFja2FnZXMKYGBge3J9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShzY2F0ZXIpCiAgbGlicmFyeShzY3JhbikKICBsaWJyYXJ5KHRpZHl2ZXJzZSkKICBsaWJyYXJ5KERFU2VxMikKICBsaWJyYXJ5KGFzaHIpCiAgbGlicmFyeShFbmhhbmNlZFZvbGNhbm8pCiAgbGlicmFyeShCaW9jUGFyYWxsZWwpCiAgbGlicmFyeSh6aW5id2F2ZSkKICB0aGVtZV9zZXQodGhlbWVfYncoKSkKfSkKYGBgCgojIyBEZWZpbmUgZmlsZSBwYXRocwoKYGBge3J9CmRhdGFfZGlyIDwtICIuL2RhdGEiCmZpZ3VyZXNfZGlyIDwtIGZpbGUucGF0aCgiLi9maWd1cmVzIikKYGBgCgpgYGB7cn0KZ2V0X2NvdW50c19zdGF0cyA8LSBmdW5jdGlvbihzY2UxMHhfbWFjcm9waGFnZSwgY2x1c3Rlcl90b19yZW1vdmUpIHsKICBnZW5lX2NvdW50c19ieV9jbHVzdGVyIDwtCiAgICB0KGFzc2F5KHNjZTEweF9tYWNyb3BoYWdlKSkgJT4lCiAgICBhcy5tYXRyaXgoKSAlPiUKICAgIGFzX3RpYmJsZSgpICU+JQogICAgZ3JvdXBfYnkoY2x1c3RlciA9IGNvbERhdGEoc2NlMTB4X21hY3JvcGhhZ2UpJGNsdXN0ZXJzX2NvbmRpdGlvbikgJT4lCiAgICBzdW1tYXJpc2VfYWxsKGxpc3QofiBtZWFuKC4pKSkKCiAgdW1pX2NvdW50c19tYXggPC0KICAgIGdlbmVfY291bnRzX2J5X2NsdXN0ZXJbLCAtMV0gJT4lCiAgICBzdW1tYXJpemVfYWxsKGxpc3QofiBtYXgoLikpKSAlPiUKICAgIG11dGF0ZShkdW1teSA9ICJkdW1teSIpICU+JQogICAgcGl2b3RfbG9uZ2VyKC1kdW1teSwgbmFtZXNfdG8gPSAiZ2VuZV9uYW1lIiwgdmFsdWVzX3RvID0gIm1heF9leHByIikgJT4lCiAgICBzZWxlY3QoLWR1bW15KQoKICBzdGF0cyA8LQogICAgcGVyRmVhdHVyZVFDTWV0cmljcyhzY2UxMHhfbWFjcm9waGFnZSwKICAgICAgZXhwcnNfdmFsdWVzID0gYygiY291bnRzIiksCiAgICAgIGZsYXR0ZW4gPSBUUlVFCiAgICApICU+JQogICAgYXNfdGliYmxlKCkgJT4lCiAgICBiaW5kX2NvbHMoLiwgcm93RGF0YShzY2UxMHhfbWFjcm9waGFnZSkgJT4lCiAgICAgIGFzX3RpYmJsZSgpKSAlPiUKICAgIGJpbmRfY29scyguLCB1bWlfY291bnRzX21heCkKCiAgcmV0dXJuKHN0YXRzKQp9CgpnZXRfbGZjIDwtIGZ1bmN0aW9uKGNvbnRyYXN0LCBuYW1lLCBkZHMpIHsKICBsZmNTaHJpbmsoZGRzLAogICAgY29udHJhc3QgPSBjb250cmFzdCwKICAgIHR5cGUgPSAiYXNociIsCiAgICBzdmFsdWUgPSBUCiAgKSAlPiUKICAgIGFzX3RpYmJsZSgpICU+JQogICAgZHBseXI6OnNlbGVjdCgtYmFzZU1lYW4pICU+JQogICAgbXV0YXRlKHN2YWx1ZSA9IGFicyhzdmFsdWUpKSAlPiUKICAgIHNldF9uYW1lcyhwYXN0ZShjKCJsZmMiLCAibGZjX3NlIiwgInN2YWx1ZSIpLCBuYW1lLCBzZXAgPSAiXyIpKQp9CgpwbG90X2V4cHJlc3Npb24gPC0gZnVuY3Rpb24oZ2VuZSkgewogIHBsb3RFeHByZXNzaW9uKHNjZTEweF9tYWNyb3BoYWdlLAogICAgZmVhdHVyZXMgPSBnZW5lLAogICAgeCA9ICJzYW1wbGUiLAogICAgZXhwcnNfdmFsdWVzID0gImxvZ2NvdW50cyIsCiAgICBjb2xvdXJfYnkgPSAiY29uZGl0aW9uIiwKICAgIHBvaW50X3NpemUgPSAxCiAgKSArCiAgICBmYWNldF93cmFwKH4gY29sRGF0YShzY2UxMHhfbWFjcm9waGFnZSkkY2x1c3RlcnMsIG5jb2wgPSAxKQp9CgpwbG90X3ZvbGNhbm8gPC0gZnVuY3Rpb24odmFyX25hbWUsIGxmY190aHJlc2gsIHN2YWx1ZV90aHJlc2gsIGxmYywgc3VmZml4LCBsYWJlbCA9IE5VTEwpIHsKICBwIDwtIEVuaGFuY2VkVm9sY2FubyhsZmMsCiAgICBsYWIgPSBsZmMgJT4lIHB1bGwoZ2VuZV9pZCksCiAgICB4ID0gcGFzdGUwKCJsZmNfIiwgdmFyX25hbWUpLAogICAgc2VsZWN0TGFiID0gbGFiZWwsCiAgICB5ID0gcGFzdGUwKCJzdmFsdWVfIiwgdmFyX25hbWUpLAogICAgeGxhYiA9IGJxdW90ZSh+IGl0YWxpYyhNb2RlcmF0ZWQpIH4gTG9nWzJdIH4gRkMpLAogICAgeWxhYiA9IGJxdW90ZSh+IC1Mb2dbMTBdIH4gaXRhbGljKHN2YWx1ZSkpLAogICAgY29sID0gYygiZ3JleTMwIiwgImZvcmVzdGdyZWVuIiwgInJlZDIiLCAicm95YWxibHVlIiksCiAgICBwQ3V0b2ZmID0gc3ZhbHVlX3RocmVzaCwKICAgIEZDY3V0b2ZmID0gbGZjX3RocmVzaCwKICAgIGxlZ2VuZExhYmVscyA9IGMoCiAgICAgICJOUyIsCiAgICAgIGV4cHJlc3Npb24oTG9nWzJdIH4gRkMpLAogICAgICAicy12YWx1ZSIsCiAgICAgIGV4cHJlc3Npb24ocyAtIHZhbHVlIH4gYW5kIH4gbG9nWzJdIH4gRkMpCiAgICApCiAgKQoKICBnZ3NhdmUoZmlsZS5wYXRoKGZpZ3VyZXNfZGlyLCBwYXN0ZTAoInZvbGNhbm9fc2MiLCB2YXJfbmFtZSwgIl9lZmZlY3RfIiwgc3VmZml4LCAiLnBkZiIpKSwgcCkKICByZXR1cm4ocCkKfQpgYGAKCgoKIyMgTG9hZCBkYXRhCgoKYGBge3J9CnNjZTEweCA8LQogIHJlYWRSRFMoZmlsZS5wYXRoKAogICAgZGF0YV9kaXIsCiAgICAicHJlcHJvY2Vzc2VkIiwKICAgICJzY2UxMHhfZmlsdGVyZWRfZmluYWwucmRzIgogICkpCmBgYAoKCmBgYHtyfQpzY2UxMHhfbWFjcm9waGFnZSA8LSBzY2UxMHhbLCBjb2xEYXRhKHNjZTEweCkkY2VsbHR5cGUgPT0gIm1hY3JvcGhhZ2UiXQpybShzY2UxMHgpCmBgYAoKYGBge3J9CnRhYmxlKGNvbERhdGEoc2NlMTB4X21hY3JvcGhhZ2UpJGNsdXN0ZXJzLCBjb2xEYXRhKHNjZTEweF9tYWNyb3BoYWdlKSRzYW1wbGUpCmBgYAoKCgojIyBEaXNjYXJkIGxvdyBjb3VudHMgZ2VuZXMKCmBgYHtyfQpuX2V4cHJzX2dlbmVzIDwtCiAgbmV4cHJzKHNjZTEweF9tYWNyb3BoYWdlLAogICAgZGV0ZWN0aW9uX2xpbWl0ID0gNSwKICAgIGJ5cm93ID0gVFJVRQogICkKa2VlcCA8LSBuX2V4cHJzX2dlbmVzID49IDEwCnRhYmxlKGtlZXApCmBgYAoKYGBge3J9CnNjZTEweF9tYWNyb3BoYWdlIDwtIHNjZTEweF9tYWNyb3BoYWdlW2tlZXAsIF0KYGBgCgpgYGB7cn0KYmF0Y2hfZ2VuZXNfcmVtb3ZlIDwtCiAgYygKICAgICJDY3I3IiwgIkNkMjA5YyIsICJJZml0MSIsICJTbGM0MGExIiwKICAgICJGZ2YxMyIsICJJZ2ZicDUiLCAiR3BubWIiLCAiVXBwMSIsICJJcmdtMiIsCiAgICAiUzEwMGE4IiwgIkNoaWwzIiwgIlNhYTMiLCAiUzEwMGE5IiwgIkFwb2w3YyIsICJGNSIsICJVcHAxIgogICkKYGBgCgoKYGBge3J9Cm1hcChiYXRjaF9nZW5lc19yZW1vdmUsIHBsb3RfZXhwcmVzc2lvbikKYGBgCgpgYGB7cn0Kc2NlMTB4X21hY3JvcGhhZ2UgPC0gc2NlMTB4X21hY3JvcGhhZ2VbIShyb3duYW1lcyhzY2UxMHhfbWFjcm9waGFnZSkgJWluJSBiYXRjaF9nZW5lc19yZW1vdmUpLCBdCmBgYAoKIyMgIERpc2NhcmQgbG93IGNsdXN0ZXIgbWVhbiBleHByZXNzaW9uIGdlbmVzCgpgYGB7cn0Kc3RhdHMgPC0gZ2V0X2NvdW50c19zdGF0cyhzY2UxMHhfbWFjcm9waGFnZSkKc3RhdHMKYGBgCgoKCgpgYGB7cn0KbWF4X3RocmVzaCA8LSAuMwpzdGF0cyAlPiUKICBjb3VudChtYXhfZXhwciA+IG1heF90aHJlc2gpCmBgYAoKCgpgYGB7ciBmaWcud2lkdGg9N30KZ2dwbG90KHN0YXRzKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKG1heF9leHByKSwgYmlucyA9IDEwMCkgKwogIHNjYWxlX3hfbG9nMTAoKSArCiAgZmFjZXRfd3JhcCh+dG9wX2h2Z19tYWNyb3BoYWdlKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWF4X3RocmVzaCkKYGBgCgoKCmBgYHtyfQpnZW5lc190b19wbG90IDwtCiAgc3RhdHMgJT4lCiAgZmlsdGVyKG1heF9leHByIDw9IG1heF90aHJlc2gpICU+JQogIGFycmFuZ2UoLW1heF9leHByKSAlPiUKICBwdWxsKGdlbmVfbmFtZSkKYGBgCgpgYGB7cn0KbWFwKGdlbmVzX3RvX3Bsb3RbMTo1XSwgcGxvdF9leHByZXNzaW9uKQpgYGAKCgoKYGBge3J9CmdlbmVzX2tlZXAgPC0KICBzdGF0cyAlPiUKICBmaWx0ZXIobWF4X2V4cHIgPiBtYXhfdGhyZXNoKSAlPiUKICBwdWxsKGdlbmVfbmFtZSkKbGVuZ3RoKGdlbmVzX2tlZXApCmBgYAoKYGBge3J9CnNjZTEweF9tYWNyb3BoYWdlIDwtIHNjZTEweF9tYWNyb3BoYWdlW2dlbmVzX2tlZXAsIF0KYGBgCgpgYGB7ciBmaWcud2lkdGg9MTJ9CmdncGxvdCgKICBzdGF0cywKICBhZXMoCiAgICBtZWFuLAogICAgZGV0ZWN0ZWQKICApCikgKwogIHNjYWxlX3hfbG9nMTAoKSArCiAgZ2VvbV9wb2ludChzaXplID0gMC4zLCBhZXMoY29sb3IgPSBtYXhfZXhwciA8IDEpKSArCiAgZ2VvbV90ZXh0KGFlcygKICAgIGxhYmVsID0gZ2VuZV9uYW1lCiAgKSwKICBjaGVja19vdmVybGFwID0gVFJVRSwgbnVkZ2VfeSA9IC0wLjEsIHNpemUgPSAyLjUKICApCmBgYAojIyBDcmVhdGUgRGVzaWduIE1hdHJpeAoKYGBge3J9CmRlc2lnbl9tYWNyb3BoYWdlIDwtCiAgbW9kZWwubWF0cml4KH4gLTEgKyBjb25kaXRpb24gKyBzYW1wbGUsCiAgICBkYXRhID0gY29sRGF0YShzY2UxMHhfbWFjcm9waGFnZSkKICApWywgLWMoNSldCmNvbG5hbWVzKGRlc2lnbl9tYWNyb3BoYWdlKSA8LSBzdHJfcmVwbGFjZShjb2xuYW1lcyhkZXNpZ25fbWFjcm9waGFnZSksICJjb25kaXRpb258c2FtcGxlIiwgIiIpCgpkZXNpZ25fbWFjcm9waGFnZVsxOjMsIF0KYGBgCiMjIENvbXB1dGUgT2JzZXJ2YXRpb25hbCBXZWlnaHRzCgpgYGB7cn0KYXNzYXkoc2NlMTB4X21hY3JvcGhhZ2UsICJjb3VudHMiKSA8LSByb3VuZChhc3NheShzY2UxMHhfbWFjcm9waGFnZSwgImNvdW50cyIpKQpgYGAKCmBgYHtyfQpzeXN0ZW0udGltZSh7CiAgemluYiA8LQogICAgemluYkZpdChzY2UxMHhfbWFjcm9waGFnZSwKICAgICAgSyA9IDAsCiAgICAgIFggPSBkZXNpZ25fbWFjcm9waGFnZSwKICAgICAgdmVyYm9zZSA9IFRSVUUsCiAgICAgIEJQUEFSQU0gPSBNdWx0aWNvcmVQYXJhbSgzKSwKICAgICAgZXBzaWxvbiA9IDFlMTIKICAgICkKfSkKYGBgCgoKYGBge3J9CndlaWdodHMgPC0gY29tcHV0ZU9ic2VydmF0aW9uYWxXZWlnaHRzKHppbmIsIGFzLm1hdHJpeChhc3NheShzY2UxMHhfbWFjcm9waGFnZSkpKQpkaW1uYW1lcyh3ZWlnaHRzKSA8LSBkaW1uYW1lcyhzY2UxMHhfbWFjcm9waGFnZSkKYXNzYXkoc2NlMTB4X21hY3JvcGhhZ2UsICJ3ZWlnaHRzIikgPC0gd2VpZ2h0cwpgYGAKCiMjIGNvbnZlcnQgdG8gU0NFIHRvIERFU2VxRGF0YVNldCBvYmplY3QKCmBgYHtyfQpkZHNfbWFjcm9waGFnZSA8LQogIGNvbnZlcnRUbyhzY2UxMHhfbWFjcm9waGFnZSwgdHlwZSA9IGMoIkRFU2VxMiIpKQpkZXNpZ24oZGRzX21hY3JvcGhhZ2UpIDwtIGRlc2lnbl9tYWNyb3BoYWdlCmFzc2F5KGRkc19tYWNyb3BoYWdlLCAid2VpZ2h0cyIpIDwtIGFzc2F5KHNjZTEweF9tYWNyb3BoYWdlLCAid2VpZ2h0cyIpCmBgYAoKCgpgYGB7cn0KZGRzX21hY3JvcGhhZ2UgPC0gZXN0aW1hdGVTaXplRmFjdG9ycyhkZHNfbWFjcm9waGFnZSwgdHlwZSA9ICJwb3Njb3VudHMiKQpkZHNfbWFjcm9waGFnZSA8LQogIERFU2VxKGRkc19tYWNyb3BoYWdlLAogICAgdGVzdCA9ICJMUlQiLAogICAgdXNlVCA9IFRSVUUsCiAgICByZWR1Y2VkID0gZGVzaWduX21hY3JvcGhhZ2VbLCAxOjJdLAogICAgbWlubXUgPSAxZS02LAogICAgcGFyYWxsZWwgPSBUUlVFLAogICAgQlBQQVJBTSA9IE11bHRpY29yZVBhcmFtKDMpLAogICAgbWluUmVwID0gSW5mCiAgKQpgYGAKCmBgYHtyfQpwbG90RGlzcEVzdHMoZGRzX21hY3JvcGhhZ2UpCmBgYApgYGB7cn0KcmVzdWx0c05hbWVzKGRkc19tYWNyb3BoYWdlKQpgYGAKCgojIyMgUGxvdCBiYXRjaCBlZmZlY3RzCgpgYGB7cn0KYmF0Y2hfZHQgPC0KICByb3dEYXRhKGRkc19tYWNyb3BoYWdlKSAlPiUKICBhc190aWJibGUocm93bmFtZXMgPSAiZ2VuZV9pZCIpICU+JQogIHNlbGVjdCgiZ2VuZV9pZCIsICJ5bmcyIjoiYWdlZDQiKQpgYGAKCgpgYGB7ciBmaWcud2lkdGg9MTJ9CmdncGxvdCgKICBiYXRjaF9kdCwKICBhZXMoCiAgICB4ID0geW5nMiwKICAgIHkgPSB5bmczCiAgKQopICsKICBnZW9tX3BvaW50KAogICAgc2l6ZSA9IC4yLAogICAgYWxwaGEgPSAwLjMKICApICsKICBnZW9tX3RleHQoYWVzKAogICAgbGFiZWwgPSBnZW5lX2lkCiAgKSwKICBjaGVja19vdmVybGFwID0gVFJVRSwgbnVkZ2VfeSA9IC0wLjE1LCBzaXplID0gMwogICkKYGBgCgoKYGBge3IgZmlnLndpZHRoPTEyfQpnZ3Bsb3QoCiAgYmF0Y2hfZHQsCiAgYWVzKAogICAgeCA9IGFnZWQyLAogICAgeSA9IGFnZWQzCiAgKQopICsKICBnZW9tX3BvaW50KAogICAgc2l6ZSA9IC4yLAogICAgYWxwaGEgPSAwLjMKICApICsKICBnZW9tX3RleHQoYWVzKAogICAgbGFiZWwgPSBnZW5lX2lkCiAgKSwKICBjaGVja19vdmVybGFwID0gVFJVRSwgbnVkZ2VfeSA9IC0wLjE1LCBzaXplID0gMwogICkKYGBgCgoKYGBge3IgZmlnLndpZHRoPTEyfQpnZ3Bsb3QoCiAgYmF0Y2hfZHQsCiAgYWVzKAogICAgeCA9IGFnZWQyLAogICAgeSA9IGFnZWQ0CiAgKQopICsKICBnZW9tX3BvaW50KAogICAgc2l6ZSA9IC4yLAogICAgYWxwaGEgPSAwLjMKICApICsKICBnZW9tX3RleHQoYWVzKAogICAgbGFiZWwgPSBnZW5lX2lkCiAgKSwKICBjaGVja19vdmVybGFwID0gVFJVRSwgbnVkZ2VfeSA9IC0wLjE1LCBzaXplID0gMwogICkKYGBgCgojIyBUZXN0IGNvbnRyYXN0cyAKCmBgYHtyfQpjMSA8LSBjKDEsIC0xLCAwLCAwLCAwLCAwLCAwKQoKbWFjcm9waGFnZV9jb250cmFzdF92ZWMgPC0gbGlzdCgKICBtYWNyb3BoYWdlX2FnaW5nID0gYzEKKQpgYGAKCmBgYHtyfQpsZmNfbWFjcm9waGFnZSA8LQogIGltYXBfZGZjKG1hY3JvcGhhZ2VfY29udHJhc3RfdmVjLAogICAgZ2V0X2xmYywKICAgIGRkcyA9IGRkc19tYWNyb3BoYWdlCiAgKSAlPiUKICBiaW5kX2NvbHMoCiAgICBnZW5lX2lkID0gcm93bmFtZXMoZGRzX21hY3JvcGhhZ2UpLAogICAgLgogICkKYGBgCgpgYGB7cn0KbGZjX21hY3JvcGhhZ2UgPC0KICBsZWZ0X2pvaW4obGZjX21hY3JvcGhhZ2UsCiAgICByb3dEYXRhKGRkc19tYWNyb3BoYWdlKSAlPiUKICAgICAgYXNfdGliYmxlKHJvd25hbWVzID0gImdlbmVfaWQiKSwKICAgIGJ5ID0gImdlbmVfaWQiCiAgKQpgYGAKCmBgYHtyfQpsZmNfbWFjcm9waGFnZQpgYGAKCiMjIFNhdmUgZGF0YQoKYGBge3J9CndyaXRlX3RzdigKICBsZmNfbWFjcm9waGFnZSwKICBmaWxlLnBhdGgoCiAgICBkYXRhX2RpciwKICAgICJwcmVwcm9jZXNzZWQiLAogICAgIm1hY3JvcGhhZ2Vfc2NybmFzZXFfbW9kZXJhdGVkX2xmYy50eHQiCiAgKQopCmBgYAoKYGBge3J9CnJvd0RhdGEoc2NlMTB4X21hY3JvcGhhZ2UpIDwtCiAgbGVmdF9qb2luKHJvd0RhdGEoc2NlMTB4X21hY3JvcGhhZ2UpICU+JQogICAgYXNfdGliYmxlKHJvd25hbWVzID0gImdlbmVfaWQiKSAlPiUKICAgIHNlbGVjdChnZW5lX2lkKSwgbGZjX21hY3JvcGhhZ2UsIGJ5ID0gImdlbmVfaWQiKSAlPiUKICBEYXRhRnJhbWUoLikKCnJvd25hbWVzKHJvd0RhdGEoc2NlMTB4X21hY3JvcGhhZ2UpKSA8LSByb3dEYXRhKHNjZTEweF9tYWNyb3BoYWdlKSRnZW5lX2lkCmBgYAoKYGBge3J9CnNhdmVSRFMoCiAgc2NlMTB4X21hY3JvcGhhZ2UsCiAgZmlsZS5wYXRoKAogICAgZGF0YV9kaXIsCiAgICAicHJlcHJvY2Vzc2VkIiwKICAgICJzY2UxMHhfbWFjcm9waGFnZV9maWx0ZXJlZF9maW5hbC5yZHMiCiAgKQopCmBgYAoKCgojIyMgTWFrZSB2b2xjYW5vIHBsb3RzCgoKCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xNH0KbGZjX3RocmVzaCA8LSAxCnN2YWx1ZV90aHJlc2ggPC0gMTBlLTgKdm9sY2Fub19wbG90cyA8LQogIG1hcChuYW1lcyhtYWNyb3BoYWdlX2NvbnRyYXN0X3ZlYyksCiAgICBwbG90X3ZvbGNhbm8sCiAgICBsZmNfdGhyZXNoID0gbGZjX3RocmVzaCwKICAgIHN2YWx1ZV90aHJlc2ggPSBzdmFsdWVfdGhyZXNoLAogICAgbGZjID0gbGZjX21hY3JvcGhhZ2UsCiAgICBzdWZmaXggPSAibWFjcm9waGFnZV9sYWJlbGVkIgogICkKdm9sY2Fub19wbG90cwpgYGAKCmBgYHtyIGZpZy5oZWlnaHQ9MTAsIGZpZy53aWR0aD0xNH0KbGZjX3RocmVzaCA8LSAxCnN2YWx1ZV90aHJlc2ggPC0gMTBlLTgKdm9sY2Fub19wbG90c191bmxhYmVsZWQgPC0KICBtYXAobmFtZXMobWFjcm9waGFnZV9jb250cmFzdF92ZWMpLAogICAgcGxvdF92b2xjYW5vLAogICAgbGZjX3RocmVzaCA9IGxmY190aHJlc2gsCiAgICBzdmFsdWVfdGhyZXNoID0gc3ZhbHVlX3RocmVzaCwKICAgIGxmYyA9IGxmY19tYWNyb3BoYWdlLAogICAgc3VmZml4ID0gIm1hY3JvcGhhZ2VfdW5sYWJlbGVkIiwKICAgIGxhYmVsID0gYygiIikKICApCnZvbGNhbm9fcGxvdHNfdW5sYWJlbGVkCmBgYAoKYGBge3J9CmdlbmVzX2hpdHNfYWdpbmdfdXAgPC0KICBsZmNfbWFjcm9waGFnZSAlPiUKICBmaWx0ZXIobGZjX21hY3JvcGhhZ2VfYWdpbmcgPiAxKSAlPiUKICBhcnJhbmdlKC1sZmNfbWFjcm9waGFnZV9hZ2luZykgJT4lCiAgcHVsbChnZW5lX2lkKQpnZW5lc19oaXRzX2FnaW5nX3VwCmBgYAoKYGBge3J9Cm1hcChnZW5lc19oaXRzX2FnaW5nX3VwLCBwbG90X2V4cHJlc3Npb24pCmBgYAoKYGBge3J9CmdlbmVzX2hpdHNfYWdpbmdfZG93biA8LQogIGxmY19tYWNyb3BoYWdlICU+JQogIGZpbHRlcihsZmNfbWFjcm9waGFnZV9hZ2luZyA8IC0xKSAlPiUKICBhcnJhbmdlKGxmY19tYWNyb3BoYWdlX2FnaW5nKSAlPiUKICBwdWxsKGdlbmVfaWQpCmdlbmVzX2hpdHNfYWdpbmdfZG93bgpgYGAKCmBgYHtyfQptYXAoZ2VuZXNfaGl0c19hZ2luZ19kb3duLCBwbG90X2V4cHJlc3Npb24pCmBgYAoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCg==