1 Dimensionality reduction for analysis

projDir <- "/mnt/scratcha/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020"
outDirBit <- "AnaWiSce/Attempt1"
nbPcToComp <- 50
suppressMessages(library(ggplot2))
suppressMessages(library(scater))
suppressMessages(library(scran))
suppressMessages(library(dplyr))
fontsize <- theme(axis.text=element_text(size=12), axis.title=element_text(size=16))

1.1 Load data

We will load the R file keeping the SCE object with the normalised counts for 500 cells per sample.

setName <- "caron"
setSuf <- "_5hCellPerSpl"

# Read object in:
tmpFn <- sprintf("%s/%s/Robjects/%s_sce_nz_postDeconv%s_dimRed.Rds", projDir, outDirBit, setName, setSuf)
print(tmpFn)
[1] "/mnt/scratcha/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020/AnaWiSce/Attempt1/Robjects/caron_sce_nz_postDeconv_5hCellPerSpl_dimRed.Rds"
if(!file.exists(tmpFn))
{
    knitr::knit_exit()
}
sce <- readRDS(tmpFn)
sce
class: SingleCellExperiment 
dim: 18372 5500 
metadata(0):
assays(2): counts logcounts
rownames(18372): ENSG00000238009 ENSG00000237491 ... ENSG00000275063
  ENSG00000271254
rowData names(11): ensembl_gene_id external_gene_name ... detected
  gene_sparsity
colnames: NULL
colData names(20): Sample Barcode ... cell_sparsity sizeFactor
reducedDimNames(3): PCA TSNE UMAP
altExpNames(0):
#any(duplicated(rowData(sce)$ensembl_gene_id))
# some function(s) used below complain about 'strand' already being used in row data,
# so rename that column now:
colnames(rowData(sce))[colnames(rowData(sce)) == "strand"] <- "strandNum"

# also get feature selection outcome
tmpFn <- sprintf("%s/%s/Robjects/%s_sce_nz_postDeconv%s_featSel.Rds", projDir, outDirBit, setName, setSuf)
tmpList <- readRDS(tmpFn)
dec.sce <- tmpList$dec.sce
hvg.index <- tmpList$hvg.index

1.2 Denoising expression values using PCA

Aim: use the trend fitted above to identify PCs linked to biology.

Assumption: biology drives most of the variance hence should be captured by the first PCs, while technical noise affects each gene independently, hence is captured by later PCs.

Logic: Compute the sum of the technical component across genes used in the PCA, use it as the amount of variance not related to biology and that we should therefore remove. Later PCs are excluded until the amount of variance they account for matches that corresponding to the technical component.

var.fit <- metadata(dec.sce)

# remove uninteresting PCs:
#options(BiocSingularParam.default=IrlbaParam())
sce <- denoisePCA(sce, technical=var.fit$trend, assay.type="logcounts")
# check assay names, should see 'PCA':
##assayNames(sce)
# check dimension of the PC table:
##dim(reducedDim(sce, "PCA")) 

sce.pca <- reducedDim(sce, "PCA") # get copy of PCA matrix
tmpCol <- rep("grey", nbPcToComp) # set colours to show selected PCs in green
tmpCol[1:dim(sce.pca)[2]] <- "green"
barplot(attributes(sce.pca)$percentVar[1:nbPcToComp],
        main=sprintf("Scree plot for the %s first PCs", nbPcToComp),
        names.arg=1:nbPcToComp,
        col=tmpCol,
        cex.names = 0.8)

# cumulative proportion of variance explained by selected PCs
cumsum(attributes(sce.pca)$percentVar)[1:dim(sce.pca)[2]]
[1]  9.320395 13.790811 16.381012 18.028636 19.582663 20.817129 21.836472
# plot on PC1 and PC2 plane:
plotPCA(sce, colour_by = "Sample.Name")

#require(knitr); knit_exit()
rm(tmpCol)

Show cells on plane for PC1 and PC2:

plotReducedDim(sce, dimred = "PCA", ncomponents = 3, 
        colour_by = "Sample.Name") + fontsize

1.3 Visualise expression patterns of some HVGs

o <- order(dec.sce$bio, decreasing=TRUE)
chosen.genes.index <- o[1:20]
dec.sce %>%
    data.frame() %>%
    tibble::rownames_to_column("ensembl_gene_id") %>%
    right_join(data.frame(rowData(sce)[chosen.genes.index,]), by="ensembl_gene_id") %>%
    select(ensembl_gene_id, Symbol, bio, FDR) %>%
    arrange(-bio)

On PCA plot:

# make and store PCA plot for top HVG 1:
pca1 <- plotReducedDim(sce, dimred="PCA", colour_by=rowData(sce)[chosen.genes.index[1],"ensembl_gene_id"]) + fontsize  # + coord_fixed()
# make and store PCA plot for top HVG 2:
pca2 <- plotReducedDim(sce, dimred="PCA", colour_by=rowData(sce)[chosen.genes.index[2],"ensembl_gene_id"]) + fontsize # + coord_fixed()

pca1

pca2

# display plots next to each other:
# multiplot(pca1, pca2, cols=2)

pca1 + facet_wrap(. ~ sce$source_name) + coord_fixed()

pca2 + facet_wrap(. ~ sce$source_name) + coord_fixed()

On t-SNE plot:

# plot TSNE, accessing counts for the gene of interest with the ID used to name rows in the count matrix:
# make and store TSNE plot for top HVG 1:
tsne1 <- plotTSNE(sce, colour_by=rowData(sce)[chosen.genes.index[1],"ensembl_gene_id"]) + fontsize
# make and store TSNE plot for top HVG 2:
tsne2 <- plotTSNE(sce, colour_by=rowData(sce)[chosen.genes.index[2],"ensembl_gene_id"]) + fontsize

tsne1

tsne2

# display plots next to each other:
#multiplot(tsne1, tsne2, cols=2)

tsne1 + facet_wrap(. ~ sce$source_name)

tsne2 + facet_wrap(. ~ sce$source_name)

# display plots next to each other, splitting each by sample:
#multiplot(tsne1 + facet_grid(. ~ sce$Sample2), tsne2 + facet_grid(. ~ sce$Sample2), cols=2)

Write R object to file

tmpFn <- sprintf("%s/%s/Robjects/%s_sce_nz_postDeconv%s_denoised.Rds", projDir, outDirBit, setName, setSuf)
print(tmpFn)
[1] "/mnt/scratcha/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020/AnaWiSce/Attempt1/Robjects/caron_sce_nz_postDeconv_5hCellPerSpl_denoised.Rds"
saveRDS(sce, file=tmpFn)
rownames(sce) <- uniquifyFeatureNames(rowData(sce)$ensembl_gene_id, rowData(sce)$Symbol)
pc1 <- reducedDim(sce, "PCA")[,1]
design <- model.matrix(~pc1)
library(limma)

Attaching package: 'limma'
The following object is masked from 'package:scater':

    plotMDS
The following object is masked from 'package:BiocGenerics':

    plotMA
fit <- lmFit(logcounts(sce), design)
fit <- eBayes(fit, trend=TRUE, robust=TRUE)
Warning: 59 very small variances detected, have been offset away from zero
topTab <- topTable(fit)
Removing intercept from test coefficients
library(pheatmap)
de.genes <- rownames(topTable(fit, coef=2, n=50))
heat.vals <- logcounts(sce)[de.genes,]
heat.vals <- heat.vals - rowMeans(heat.vals)
heat.vals[heat.vals > 2] <- 2
heat.vals[heat.vals < -2] <- -2
pheatmap(heat.vals[,order(pc1)], cluster_cols=FALSE)

LS0tCnRpdGxlOiAiQ1JVSyBDSSBTdW1tZXIgU2Nob29sIDIwMjAgLSBpbnRyb2R1Y3Rpb24gdG8gc2luZ2xlLWNlbGwgUk5BLXNlcSBhbmFseXNpcyIKc3VidGl0bGU6ICdEaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gZm9yIGFuYWx5c2lzJwoKYXV0aG9yOiAiU3RlcGhhbmUgQmFsbGVyZWF1LCBaZXluZXAgS2FsZW5kZXIgQXRhaywgS2F0YXJ6eW5hIEthbmlhIgojZGF0ZTogJ2ByIHN0cmZ0aW1lKFN5cy50aW1lKCksIGZvcm1hdCA9ICIlQiAlZCwgJVkiKWAnCmRhdGU6IEp1bHkgMjAyMAojYmlibGlvZ3JhcGh5OiBiaWJsaW9ncmFwaHkuYmliCiNjc2w6IGJpb21lZC1jZW50cmFsLmNzbApvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGZpZ19jYXB0aW9uOiB5ZXMKICAgIHNlbGZfY29udGFpbmVkOiB0cnVlCiAgICBmaWdfd2lkdGg6IDYKICAgIGZpZ19oZWlnaHQ6IDQKLS0tCgojIERpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBmb3IgYW5hbHlzaXMKCmBgYHtyfQpwcm9qRGlyIDwtICIvbW50L3NjcmF0Y2hhL2Jpb2luZm9ybWF0aWNzL2JhbGxlcjAxLzIwMjAwNTExX0Zlcm5hbmRlc01fTUVfY3J1a0JpU3MyMDIwIgpvdXREaXJCaXQgPC0gIkFuYVdpU2NlL0F0dGVtcHQxIgpuYlBjVG9Db21wIDwtIDUwCmBgYAoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0UsIGVjaG89RkFMU0V9CiMgRmlyc3QsIHNldCBzb21lIHZhcmlhYmxlczoKa25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpvcHRpb25zKHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKc2V0LnNlZWQoMTIzKSAjIGZvciByZXByb2R1Y2liaWxpdHkKa25pdHI6Om9wdHNfY2h1bmskc2V0KGV2YWwgPSBUUlVFKSAKYGBgCgpgYGB7ciwgd2FybmluZz1GQUxTRX0Kc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGdncGxvdDIpKQpzdXBwcmVzc01lc3NhZ2VzKGxpYnJhcnkoc2NhdGVyKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHNjcmFuKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGRwbHlyKSkKZm9udHNpemUgPC0gdGhlbWUoYXhpcy50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNikpCmBgYAoKIyMgTG9hZCBkYXRhCgpXZSB3aWxsIGxvYWQgdGhlIFIgZmlsZSBrZWVwaW5nIHRoZSBTQ0Ugb2JqZWN0IHdpdGggdGhlIG5vcm1hbGlzZWQgY291bnRzIGZvciA1MDAgY2VsbHMgcGVyIHNhbXBsZS4KCmBgYHtyfQpzZXROYW1lIDwtICJjYXJvbiIKc2V0U3VmIDwtICJfNWhDZWxsUGVyU3BsIgoKIyBSZWFkIG9iamVjdCBpbjoKdG1wRm4gPC0gc3ByaW50ZigiJXMvJXMvUm9iamVjdHMvJXNfc2NlX256X3Bvc3REZWNvbnYlc19kaW1SZWQuUmRzIiwgcHJvakRpciwgb3V0RGlyQml0LCBzZXROYW1lLCBzZXRTdWYpCnByaW50KHRtcEZuKQppZighZmlsZS5leGlzdHModG1wRm4pKQp7Cglrbml0cjo6a25pdF9leGl0KCkKfQpzY2UgPC0gcmVhZFJEUyh0bXBGbikKc2NlCgojYW55KGR1cGxpY2F0ZWQocm93RGF0YShzY2UpJGVuc2VtYmxfZ2VuZV9pZCkpCiMgc29tZSBmdW5jdGlvbihzKSB1c2VkIGJlbG93IGNvbXBsYWluIGFib3V0ICdzdHJhbmQnIGFscmVhZHkgYmVpbmcgdXNlZCBpbiByb3cgZGF0YSwKIyBzbyByZW5hbWUgdGhhdCBjb2x1bW4gbm93Ogpjb2xuYW1lcyhyb3dEYXRhKHNjZSkpW2NvbG5hbWVzKHJvd0RhdGEoc2NlKSkgPT0gInN0cmFuZCJdIDwtICJzdHJhbmROdW0iCgojIGFsc28gZ2V0IGZlYXR1cmUgc2VsZWN0aW9uIG91dGNvbWUKdG1wRm4gPC0gc3ByaW50ZigiJXMvJXMvUm9iamVjdHMvJXNfc2NlX256X3Bvc3REZWNvbnYlc19mZWF0U2VsLlJkcyIsIHByb2pEaXIsIG91dERpckJpdCwgc2V0TmFtZSwgc2V0U3VmKQp0bXBMaXN0IDwtIHJlYWRSRFModG1wRm4pCmRlYy5zY2UgPC0gdG1wTGlzdCRkZWMuc2NlCmh2Zy5pbmRleCA8LSB0bXBMaXN0JGh2Zy5pbmRleApgYGAKCiMjIERlbm9pc2luZyBleHByZXNzaW9uIHZhbHVlcyB1c2luZyBQQ0EKCkFpbTogdXNlIHRoZSB0cmVuZCBmaXR0ZWQgYWJvdmUgdG8gaWRlbnRpZnkgUENzIGxpbmtlZCB0byBiaW9sb2d5LgoKQXNzdW1wdGlvbjogYmlvbG9neSBkcml2ZXMgbW9zdCBvZiB0aGUgdmFyaWFuY2UgaGVuY2Ugc2hvdWxkIGJlIGNhcHR1cmVkIGJ5IHRoZSBmaXJzdCBQQ3MsIHdoaWxlIHRlY2huaWNhbCBub2lzZSBhZmZlY3RzIGVhY2ggZ2VuZSBpbmRlcGVuZGVudGx5LCBoZW5jZSBpcyBjYXB0dXJlZCBieSBsYXRlciBQQ3MuCgpMb2dpYzogQ29tcHV0ZSB0aGUgc3VtIG9mIHRoZSB0ZWNobmljYWwgY29tcG9uZW50IGFjcm9zcyBnZW5lcyB1c2VkIGluIHRoZSBQQ0EsIHVzZSBpdCBhcyB0aGUgYW1vdW50IG9mIHZhcmlhbmNlIG5vdCByZWxhdGVkIHRvIGJpb2xvZ3kgYW5kIHRoYXQgd2Ugc2hvdWxkIHRoZXJlZm9yZSByZW1vdmUuIExhdGVyIFBDcyBhcmUgZXhjbHVkZWQgdW50aWwgdGhlIGFtb3VudCBvZiB2YXJpYW5jZSB0aGV5IGFjY291bnQgZm9yIG1hdGNoZXMgdGhhdCBjb3JyZXNwb25kaW5nIHRvIHRoZSB0ZWNobmljYWwgY29tcG9uZW50LiAKCmBgYHtyIGNvbXBfZGVub2lzZVBDQSwgaW5jbHVkZT1UUlVFfQp2YXIuZml0IDwtIG1ldGFkYXRhKGRlYy5zY2UpCgojIHJlbW92ZSB1bmludGVyZXN0aW5nIFBDczoKI29wdGlvbnMoQmlvY1Npbmd1bGFyUGFyYW0uZGVmYXVsdD1JcmxiYVBhcmFtKCkpCnNjZSA8LSBkZW5vaXNlUENBKHNjZSwgdGVjaG5pY2FsPXZhci5maXQkdHJlbmQsIGFzc2F5LnR5cGU9ImxvZ2NvdW50cyIpCiMgY2hlY2sgYXNzYXkgbmFtZXMsIHNob3VsZCBzZWUgJ1BDQSc6CiMjYXNzYXlOYW1lcyhzY2UpCiMgY2hlY2sgZGltZW5zaW9uIG9mIHRoZSBQQyB0YWJsZToKIyNkaW0ocmVkdWNlZERpbShzY2UsICJQQ0EiKSkgCgpzY2UucGNhIDwtIHJlZHVjZWREaW0oc2NlLCAiUENBIikgIyBnZXQgY29weSBvZiBQQ0EgbWF0cml4CnRtcENvbCA8LSByZXAoImdyZXkiLCBuYlBjVG9Db21wKSAjIHNldCBjb2xvdXJzIHRvIHNob3cgc2VsZWN0ZWQgUENzIGluIGdyZWVuCnRtcENvbFsxOmRpbShzY2UucGNhKVsyXV0gPC0gImdyZWVuIgpiYXJwbG90KGF0dHJpYnV0ZXMoc2NlLnBjYSkkcGVyY2VudFZhclsxOm5iUGNUb0NvbXBdLAogICAgICAgIG1haW49c3ByaW50ZigiU2NyZWUgcGxvdCBmb3IgdGhlICVzIGZpcnN0IFBDcyIsIG5iUGNUb0NvbXApLAogICAgICAgIG5hbWVzLmFyZz0xOm5iUGNUb0NvbXAsCiAgICAgICAgY29sPXRtcENvbCwKICAgICAgICBjZXgubmFtZXMgPSAwLjgpCgojIGN1bXVsYXRpdmUgcHJvcG9ydGlvbiBvZiB2YXJpYW5jZSBleHBsYWluZWQgYnkgc2VsZWN0ZWQgUENzCmN1bXN1bShhdHRyaWJ1dGVzKHNjZS5wY2EpJHBlcmNlbnRWYXIpWzE6ZGltKHNjZS5wY2EpWzJdXQoKIyBwbG90IG9uIFBDMSBhbmQgUEMyIHBsYW5lOgpwbG90UENBKHNjZSwgY29sb3VyX2J5ID0gIlNhbXBsZS5OYW1lIikKI3JlcXVpcmUoa25pdHIpOyBrbml0X2V4aXQoKQpybSh0bXBDb2wpCmBgYAoKU2hvdyBjZWxscyBvbiBwbGFuZSBmb3IgUEMxIGFuZCBQQzI6CgpgYGB7ciBwbG90X2Rlbm9pc2VQQ0F9CnBsb3RSZWR1Y2VkRGltKHNjZSwgZGltcmVkID0gIlBDQSIsIG5jb21wb25lbnRzID0gMywgCgkJY29sb3VyX2J5ID0gIlNhbXBsZS5OYW1lIikgKyBmb250c2l6ZQpgYGAKCiMjIFZpc3VhbGlzZSBleHByZXNzaW9uIHBhdHRlcm5zIG9mIHNvbWUgSFZHcwoKYGBge3J9Cm8gPC0gb3JkZXIoZGVjLnNjZSRiaW8sIGRlY3JlYXNpbmc9VFJVRSkKY2hvc2VuLmdlbmVzLmluZGV4IDwtIG9bMToyMF0KZGVjLnNjZSAlPiUKCWRhdGEuZnJhbWUoKSAlPiUKCXRpYmJsZTo6cm93bmFtZXNfdG9fY29sdW1uKCJlbnNlbWJsX2dlbmVfaWQiKSAlPiUKCXJpZ2h0X2pvaW4oZGF0YS5mcmFtZShyb3dEYXRhKHNjZSlbY2hvc2VuLmdlbmVzLmluZGV4LF0pLCBieT0iZW5zZW1ibF9nZW5lX2lkIikgJT4lCglzZWxlY3QoZW5zZW1ibF9nZW5lX2lkLCBTeW1ib2wsIGJpbywgRkRSKSAlPiUKCWFycmFuZ2UoLWJpbykKYGBgCgpPbiBQQ0EgcGxvdDoKCmBgYHtyIHBsb3RfY291bnRfcGNhX0hWR3RvcDJ9CiMgbWFrZSBhbmQgc3RvcmUgUENBIHBsb3QgZm9yIHRvcCBIVkcgMToKcGNhMSA8LSBwbG90UmVkdWNlZERpbShzY2UsIGRpbXJlZD0iUENBIiwgY29sb3VyX2J5PXJvd0RhdGEoc2NlKVtjaG9zZW4uZ2VuZXMuaW5kZXhbMV0sImVuc2VtYmxfZ2VuZV9pZCJdKSArIGZvbnRzaXplICAjICsgY29vcmRfZml4ZWQoKQojIG1ha2UgYW5kIHN0b3JlIFBDQSBwbG90IGZvciB0b3AgSFZHIDI6CnBjYTIgPC0gcGxvdFJlZHVjZWREaW0oc2NlLCBkaW1yZWQ9IlBDQSIsIGNvbG91cl9ieT1yb3dEYXRhKHNjZSlbY2hvc2VuLmdlbmVzLmluZGV4WzJdLCJlbnNlbWJsX2dlbmVfaWQiXSkgKyBmb250c2l6ZSAjICsgY29vcmRfZml4ZWQoKQoKcGNhMQpwY2EyCmBgYAoKYGBge3IgcGxvdF9jb3VudF9wY2FfSFZHdG9wMl9mYWNldCwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTZ9CiMgZGlzcGxheSBwbG90cyBuZXh0IHRvIGVhY2ggb3RoZXI6CiMgbXVsdGlwbG90KHBjYTEsIHBjYTIsIGNvbHM9MikKCnBjYTEgKyBmYWNldF93cmFwKC4gfiBzY2Ukc291cmNlX25hbWUpICsgY29vcmRfZml4ZWQoKQpwY2EyICsgZmFjZXRfd3JhcCguIH4gc2NlJHNvdXJjZV9uYW1lKSArIGNvb3JkX2ZpeGVkKCkKYGBgCgpPbiB0LVNORSBwbG90OgoKYGBge3IgcGxvdF9jb3VudF90c25lX0hWR3RvcDJ9CiMgcGxvdCBUU05FLCBhY2Nlc3NpbmcgY291bnRzIGZvciB0aGUgZ2VuZSBvZiBpbnRlcmVzdCB3aXRoIHRoZSBJRCB1c2VkIHRvIG5hbWUgcm93cyBpbiB0aGUgY291bnQgbWF0cml4OgojIG1ha2UgYW5kIHN0b3JlIFRTTkUgcGxvdCBmb3IgdG9wIEhWRyAxOgp0c25lMSA8LSBwbG90VFNORShzY2UsIGNvbG91cl9ieT1yb3dEYXRhKHNjZSlbY2hvc2VuLmdlbmVzLmluZGV4WzFdLCJlbnNlbWJsX2dlbmVfaWQiXSkgKyBmb250c2l6ZQojIG1ha2UgYW5kIHN0b3JlIFRTTkUgcGxvdCBmb3IgdG9wIEhWRyAyOgp0c25lMiA8LSBwbG90VFNORShzY2UsIGNvbG91cl9ieT1yb3dEYXRhKHNjZSlbY2hvc2VuLmdlbmVzLmluZGV4WzJdLCJlbnNlbWJsX2dlbmVfaWQiXSkgKyBmb250c2l6ZQoKdHNuZTEKdHNuZTIKYGBgCgpgYGB7ciBwbG90X2NvdW50X3RzbmVfSFZHdG9wMl9mYWNldCwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTZ9CiMgZGlzcGxheSBwbG90cyBuZXh0IHRvIGVhY2ggb3RoZXI6CiNtdWx0aXBsb3QodHNuZTEsIHRzbmUyLCBjb2xzPTIpCgp0c25lMSArIGZhY2V0X3dyYXAoLiB+IHNjZSRzb3VyY2VfbmFtZSkKdHNuZTIgKyBmYWNldF93cmFwKC4gfiBzY2Ukc291cmNlX25hbWUpCgojIGRpc3BsYXkgcGxvdHMgbmV4dCB0byBlYWNoIG90aGVyLCBzcGxpdHRpbmcgZWFjaCBieSBzYW1wbGU6CiNtdWx0aXBsb3QodHNuZTEgKyBmYWNldF9ncmlkKC4gfiBzY2UkU2FtcGxlMiksIHRzbmUyICsgZmFjZXRfZ3JpZCguIH4gc2NlJFNhbXBsZTIpLCBjb2xzPTIpCmBgYAoKV3JpdGUgUiBvYmplY3QgdG8gZmlsZQoKYGBge3J9CnRtcEZuIDwtIHNwcmludGYoIiVzLyVzL1JvYmplY3RzLyVzX3NjZV9uel9wb3N0RGVjb252JXNfZGVub2lzZWQuUmRzIiwgcHJvakRpciwgb3V0RGlyQml0LCBzZXROYW1lLCBzZXRTdWYpCnByaW50KHRtcEZuKQpzYXZlUkRTKHNjZSwgZmlsZT10bXBGbikKYGBgCgo8IS0tIGh0dHA6Ly9iaW9pbmZvcm1hdGljcy5hZ2UubXBnLmRlL3ByZXNlbnRhdGlvbnMtdHV0b3JpYWxzL3ByZXNlbnRhdGlvbnMvbW9kdWxlcy9zaW5nbGUtY2VsbC8vYmlvY29uZHVjdG9yX3R1dG9yaWFsLmh0bWwgLS0+CgpgYGB7cn0Kcm93bmFtZXMoc2NlKSA8LSB1bmlxdWlmeUZlYXR1cmVOYW1lcyhyb3dEYXRhKHNjZSkkZW5zZW1ibF9nZW5lX2lkLCByb3dEYXRhKHNjZSkkU3ltYm9sKQpwYzEgPC0gcmVkdWNlZERpbShzY2UsICJQQ0EiKVssMV0KZGVzaWduIDwtIG1vZGVsLm1hdHJpeCh+cGMxKQpsaWJyYXJ5KGxpbW1hKQpmaXQgPC0gbG1GaXQobG9nY291bnRzKHNjZSksIGRlc2lnbikKZml0IDwtIGVCYXllcyhmaXQsIHRyZW5kPVRSVUUsIHJvYnVzdD1UUlVFKQp0b3BUYWIgPC0gdG9wVGFibGUoZml0KQpgYGAKCmBgYHtyfQpsaWJyYXJ5KHBoZWF0bWFwKQpkZS5nZW5lcyA8LSByb3duYW1lcyh0b3BUYWJsZShmaXQsIGNvZWY9Miwgbj01MCkpCmhlYXQudmFscyA8LSBsb2djb3VudHMoc2NlKVtkZS5nZW5lcyxdCmhlYXQudmFscyA8LSBoZWF0LnZhbHMgLSByb3dNZWFucyhoZWF0LnZhbHMpCmhlYXQudmFsc1toZWF0LnZhbHMgPiAyXSA8LSAyCmhlYXQudmFsc1toZWF0LnZhbHMgPCAtMl0gPC0gLTIKcGhlYXRtYXAoaGVhdC52YWxzWyxvcmRlcihwYzEpXSwgY2x1c3Rlcl9jb2xzPUZBTFNFKQpgYGAK