library(pagoda2)
Loading required package: Matrix
Loading required package: igraph
package ‘igraph’ was built under R version 3.5.3
Attaching package: ‘igraph’

The following objects are masked from ‘package:stats’:

    decompose, spectrum

The following object is masked from ‘package:base’:

    union
library(Matrix)
library(ggplot2)
library(cowplot)

Attaching package: ‘cowplot’

The following object is masked from ‘package:ggplot2’:

    ggsave

Read in data

library(hdf5r)
h5_data <- H5File$new("5k_pbmc_NGSC3_aggr_filtered_feature_bc_matrix.h5")
cd <- Matrix::sparseMatrix(
  i = h5_data[['matrix/indices']][],
  p = h5_data[['matrix/indptr']][],
  x = h5_data[['matrix/data']][],
  dimnames = list(
    h5_data[['matrix/features/name']][],
    h5_data[['matrix/barcodes']][]
  ),
  dims = h5_data[['matrix/shape']][],
  index1 = FALSE
)
h5_data$close_all()

hist(log10(rowSums(counts)+1),main='Molecules per gene',xlab='molecules (log10)',col='cornsilk')
abline(v=1,lty=2,col=2)

counts <- counts[rowSums(counts)>=100,]
dim(counts)
[1] 15612 65833

Doublets

ds <- get.scrublet.scores(counts)

Written 5.2% of 65833 rows in 2 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 37 secs.      
Written 8.6% of 65833 rows in 3 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 32 secs.      
Written 12.0% of 65833 rows in 4 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 30 secs.      
Written 15.4% of 65833 rows in 5 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 28 secs.      
Written 18.8% of 65833 rows in 6 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 26 secs.      
Written 21.5% of 65833 rows in 7 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 26 secs.      
Written 24.7% of 65833 rows in 8 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 25 secs.      
Written 27.9% of 65833 rows in 9 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 24 secs.      
Written 31.1% of 65833 rows in 10 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 22 secs.      
Written 34.5% of 65833 rows in 11 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 21 secs.      
Written 37.9% of 65833 rows in 12 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 20 secs.      
Written 41.1% of 65833 rows in 13 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 19 secs.      
Written 44.5% of 65833 rows in 14 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 18 secs.      
Written 47.9% of 65833 rows in 15 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 16 secs.      
Written 51.5% of 65833 rows in 16 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 15 secs.      
Written 54.9% of 65833 rows in 17 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 14 secs.      
Written 58.3% of 65833 rows in 18 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 13 secs.      
Written 61.8% of 65833 rows in 19 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 12 secs.      
Written 65.2% of 65833 rows in 20 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 11 secs.      
Written 68.6% of 65833 rows in 21 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 9 secs.      
Written 72.0% of 65833 rows in 22 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 8 secs.      
Written 75.4% of 65833 rows in 23 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 7 secs.      
Written 78.8% of 65833 rows in 24 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 6 secs.      
Written 82.2% of 65833 rows in 25 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 5 secs.      
Written 85.6% of 65833 rows in 26 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 4 secs.      
Written 89.0% of 65833 rows in 27 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 3 secs.      
Written 92.4% of 65833 rows in 28 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 2 secs.      
Written 96.0% of 65833 rows in 29 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 1 secs.      
Written 99.7% of 65833 rows in 30 secs using 1 thread. anyBufferGrown=no; maxBuffUsed=49%. ETA 0 secs.      
                                                                                                                                     

Annotation

ann <- read.delim('68k_pbmc_barcodes_annotation.tsv',header=T,sep='\t')
cannot open file '68k_pbmc_barcodes_annotation.tsv': No such file or directoryError in file(file, "rt") : cannot open the connection

Conos processing together with an annotated PBMC dataset

library(pagoda2)
library(dplyr)

Attaching package: ‘dplyr’

The following objects are masked from ‘package:igraph’:

    as_data_frame, groups, union

The following objects are masked from ‘package:stats’:

    filter, lag

The following objects are masked from ‘package:base’:

    intersect, setdiff, setequal, union
library(conos)
library(parallel)
library(cowplot)
library(ggrepel)
library(Matrix)

Co-align with Rahul’s 10k PBMCs

load("../pbmc10k_v3/seurat.RData") # cd, cd.var,meta, umap
satija.ann <- as.factor(setNames(meta$celltype,rownames(meta)))

Pre-process, align and annotate

cdl.p2 <- lapply(list(pbmc60=counts,pbmc10=cd), basicP2proc, n.cores=30, min.cells.per.gene=0, n.odgenes=2e3, get.largevis=FALSE, make.geneknn=FALSE,get.tsne=T)
con <- Conos$new(cdl.p2,n.cores=30)
con$buildGraph(k=15, k.self=5, space='PCA', ncomps=30, n.odgenes=2000, matching.method='mNN', metric='angular', verbose=TRUE)
found 0 out of 1 cached PCA  space pairs ... running 1 additional PCA  space pairs . done
inter-sample links using  mNN  . done
local pairs local pairs  done
building graph ..done

Cluster and generate a 2D ebmedding

con$findCommunities(r=2)
con$embedGraph(method='UMAP'); 
Convert graph to adjacency list...
Done
Estimate nearest neighbors and commute times...
Estimating hitting distances: 17:26:54.
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Done.
Estimating commute distances: 17:28:32.
Hashing adjacency list: 17:28:32.
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**********************
Done.
****************************|
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
Estimating distances: 17:28:43.
**************************************************|
Done
Done.
All done!: 17:28:56.
Done
Estimate UMAP embedding...
Maximal number of estimated neighbors is 31. Consider increasing min.visited.verts, min.prob or min.prob.lower.
17:28:56 UMAP embedding parameters a = 0.0267 b = 0.7906
17:28:56 Read 75265 rows and found 1 numeric columns
17:28:57 Commencing smooth kNN distance calibration using 30 threads
17:29:00 Initializing from normalized Laplacian + noise
17:29:22 Commencing optimization for 1000 epochs, with 2092332 positive edges using 30 threads
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
17:29:48 Optimization finished
Done
emb <- con$embedding;

con$misc$embeddings <- list(UMAP=emb)
con$embedGraph(method='largeVis'); 
Estimating embeddings.
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
con$misc$embeddings <- list(largeVis=emb)

propagate labels

new.ann <- con$propagateLabels(labels = satija.ann, verbose=T,fixed.initial.labels=TRUE)
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
*********
Stop after 15 iterations. Norm: 0.0237014
Min weight: 1.67017e-05, max weight: 0.367879, fading: (10, 0.1)
*****************************************|
new.ann <- new.ann$labels

Take a look:

alpha <- 0.1; size <- 0.1;
p1 <- con$plotGraph(title='clusters',alpha=alpha,size=size);
p2 <- con$plotGraph(color.by='sample',title='samples',mark.groups=F,alpha=alpha,size=size);
p3 <- con$plotGraph(groups=new.ann,alpha=alpha,size=size,title='annotation',plot.na=F)
p4 <- con$plotGraph(groups=satija.ann,alpha=alpha,size=size,title='old annotation',plot.na=F)
cowplot::plot_grid(plotlist=list(p1,p2,p3,p4),nrow=2)

alpha <- 0.1; size <- 0.1;
p2 <- con$plotGraph(color.by='sample',title='samples',mark.groups=F,alpha=alpha,size=size);
p3 <- con$plotGraph(groups=new.ann,alpha=alpha,size=size,title='annotation',plot.na=F)
p4 <- con$plotGraph(groups=satija.ann,alpha=alpha,size=size,title='old annotation',plot.na=F)
p1 <- con$plotGraph(gene='CD14')
cowplot::plot_grid(plotlist=list(p1,p2,p3,p4),nrow=2)

p2 processing

r <- Pagoda2$new(counts[,names(ds)[ds<0.2]],log.scale=T, n.cores=30)
59525 cells, 15612 genes; normalizing ... using plain model log scale ... done.
#p2 <- con$samples$pbmc60
r$adjustVariance(plot=T,gam.k=10)
calculating variance fit ... using gam 2837 overdispersed genes ... 2837persisting ... done.

r$calculatePcaReduction(nPcs=50,n.odgenes=3e3)
running PCA using 3000 OD genes .... done
r$makeKnnGraph(k=40,type='PCA',center=T,distance='cosine');
creating space of type angular done
adding data ... done
building index ... done
querying ... done
r$getEmbedding(type='PCA',embeddingType='tSNE',n.cores=30)
Too many cells to pre-calcualte correlation distances, switching to L2. Please, consider using UMAP.
running tSNE using 30 cores:
 - point 10000 of 59525
 - point 20000 of 59525
 - point 30000 of 59525
 - point 40000 of 59525
 - point 50000 of 59525
r$getKnnClusters(type='PCA',method=leiden.community,r=1,name='leiden')
r$plotEmbedding(type='PCA',embeddingType='tSNE',clusterType='leiden',show.legend=F,mark.clusters=T,min.group.size=1,shuffle.colors=F,mark.cluster.cex=1,alpha=0.1,cex=0.01,main='clusters (tSNE)')

r$plotEmbedding(type='PCA',embeddingType='tSNE',groups=new.ann,show.legend=F,mark.clusters=T,min.group.size=1,shuffle.colors=F,mark.cluster.cex=1,alpha=0.01)
using provided groups as a factor

r$plotEmbedding(type='PCA',embeddingType='tSNE',colors=r$counts[,'FOXP3'],show.legend=F,mark.clusters=T,min.group.size=1,shuffle.colors=F,mark.cluster.cex=1,alpha=0.01)
treating colors as a gradient with zlim: 0 1.04557 

de <- r$getDifferentialGenes(type='PCA',verbose=T,clusterType='leiden',append.auc=T,upregulated.only = T)
running differential expression with  20  clusters ... adjusting p-values ... done.
conos:::plotDEheatmap(r,r$clusters$PCA$leiden,de)

Show evidence for cluster 1 and 2

p1 <- conos::embeddingPlot(r$embeddings$PCA$tSNE,groups=as.factor(r$clusters$PCA$leiden),size=0.1,alpha=0.2,raster=T,raster.height = 3,raster.width=3)+
  theme(panel.border = element_rect(color = 1, size=0.2,linetype=1),axis.line=element_blank())
p1

p2 <- conos::embeddingPlot(r$embeddings$PCA$tSNE,groups=new.ann,size=0.1,alpha=0.2,raster=T,raster.height = 3,raster.width=3,font.size=c(3,4))+
  theme(panel.border = element_rect(color = 1, size=0.2,linetype=1),axis.line=element_blank())
p2

gns <- c("CD14",'CD3E',"CD8A")
po <- lapply(gns,function(g) {
  conos::embeddingPlot(r$embeddings$PCA$tSNE,colors=r$counts[,g],size=0.1,alpha=0.6,raster=T,raster.height = 3,raster.width=3)+
  annotate('text',x=-Inf,y=Inf,vjust=1.2,hjust=0,label=g,size=6)+
  theme(panel.border = element_rect(color = 1, size=0.2,linetype=1),axis.line=element_blank())
})
pp <- plot_grid(plotlist=c(list(p1),po),nrow=1)
pp

pdf(file='pbmc60_ann.pdf',width=12,height=3); print(pp); dev.off()
null device 
          1 

Differential expression ladder

cell.groups <- r$clusters$PCA$leiden
cell.groups <- tapply(names(cell.groups),cell.groups,I)
cell.groups <- cell.groups[c('1','2')]
names(cell.groups) <- c("CD4+ T cells","CD14+ Monocytes")
sig.thr <- qnorm(0.01,lower.tail=F);

cell.grid <- 10^(seq(1,log10(1e4),length.out=100))

ngl <- mclapply(cell.grid,function(n.cells) {
  cells <- lapply(cell.groups,sample,n.cells)
  cf <- as.factor(setNames(rep(names(cells),each=n.cells),unlist(cells)))
  suppressWarnings(x <- r$getDifferentialGenes(groups = cf,append.specificity.metrics=F)[[1]])
},mc.cores=30)

ng <- lapply(ngl,function(x) {sum(abs(x$Z)>=sig.thr)})
x <- r$counts[rownames(r$counts)%in% unlist(cell.groups),]
exp5 <- colnames(x)[colSums(x>0) >= nrow(x)*0.05]
exp10 <- colnames(x)[colSums(x>0) >= nrow(x)*0.1]

ng5 <- lapply(ngl,function(x) {sum(abs(x$Z[rownames(x) %in% exp5])>=sig.thr)})
df <- data.frame(n=cell.grid,nde=unlist(ng5),ndf=unlist(ng5)/length(exp5))
p <- ggplot(df,aes(x=log10(n),y=nde))+geom_point(size=0.2)+geom_smooth(color=adjustcolor('red',alpha=0.6),span = 0.5)+theme_bw() +
  xlab("log10[ number of cells ]")+ylab('number of DE genes') +
  scale_y_continuous(sec.axis = sec_axis(~./length(exp5), name = "DE gene fraction")) +
  theme(axis.text.y = element_text(angle = 90, hjust = 0.5),axis.text.y.right = element_text(angle = 90, hjust = 0.5))
p

pdf(file='de.fraction.pdf',height=2,width=2.3); print(p); dev.off()
null device 
          1 
LS0tCnRpdGxlOiAiUEJNQyBleGFtcGxlcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCmBgYHtyfQpsaWJyYXJ5KHBhZ29kYTIpCmxpYnJhcnkoTWF0cml4KQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoY293cGxvdCkKYGBgCgpSZWFkIGluIGRhdGEKYGBge3J9CmxpYnJhcnkoaGRmNXIpCmg1X2RhdGEgPC0gSDVGaWxlJG5ldygiNWtfcGJtY19OR1NDM19hZ2dyX2ZpbHRlcmVkX2ZlYXR1cmVfYmNfbWF0cml4Lmg1IikKY2QgPC0gTWF0cml4OjpzcGFyc2VNYXRyaXgoCiAgaSA9IGg1X2RhdGFbWydtYXRyaXgvaW5kaWNlcyddXVtdLAogIHAgPSBoNV9kYXRhW1snbWF0cml4L2luZHB0ciddXVtdLAogIHggPSBoNV9kYXRhW1snbWF0cml4L2RhdGEnXV1bXSwKICBkaW1uYW1lcyA9IGxpc3QoCiAgICBoNV9kYXRhW1snbWF0cml4L2ZlYXR1cmVzL25hbWUnXV1bXSwKICAgIGg1X2RhdGFbWydtYXRyaXgvYmFyY29kZXMnXV1bXQogICksCiAgZGltcyA9IGg1X2RhdGFbWydtYXRyaXgvc2hhcGUnXV1bXSwKICBpbmRleDEgPSBGQUxTRQopCmg1X2RhdGEkY2xvc2VfYWxsKCkKYGBgCgoKYGBge3J9CmNvdW50cyA8LSBnZW5lLnZzLm1vbGVjdWxlLmNlbGwuZmlsdGVyKGNkLG1pbi5jZWxsLnNpemU9NTAwKQpgYGAKCgpgYGB7cn0KaGlzdChsb2cxMChyb3dTdW1zKGNvdW50cykrMSksbWFpbj0nTW9sZWN1bGVzIHBlciBnZW5lJyx4bGFiPSdtb2xlY3VsZXMgKGxvZzEwKScsY29sPSdjb3Juc2lsaycpCmFibGluZSh2PTEsbHR5PTIsY29sPTIpCmBgYAoKCgpgYGB7cn0KY291bnRzIDwtIGNvdW50c1tyb3dTdW1zKGNvdW50cyk+PTEwMCxdCnJvd25hbWVzKGNvdW50cykgPC0gbWFrZS51bmlxdWUocm93bmFtZXMoY291bnRzKSkKZGltKGNvdW50cykKYGBgCgpEb3VibGV0cwpgYGB7cn0Kc291cmNlKCJ+L20vcGF2YW4vRExJL2NvbnAyLnIiKQpkcyA8LSBnZXQuc2NydWJsZXQuc2NvcmVzKGNvdW50cykKYGBgCgpBbm5vdGF0aW9uCmBgYHtyfQphbm4gPC0gcmVhZC5kZWxpbSgnNjhrX3BibWNfYmFyY29kZXNfYW5ub3RhdGlvbi50c3YnLGhlYWRlcj1ULHNlcD0nXHQnKQpwdWIuZW1iIDwtIGFublssYygxLDIpXQphbm4gPC0gYXMuZmFjdG9yKHNldE5hbWVzKGFublssNF0sYW5uWywzXSkpCmBgYAoKCiMjIENvbm9zIHByb2Nlc3NpbmcgdG9nZXRoZXIgd2l0aCBhbiBhbm5vdGF0ZWQgUEJNQyBkYXRhc2V0CmBgYHtyfQpsaWJyYXJ5KHBhZ29kYTIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoY29ub3MpCmxpYnJhcnkocGFyYWxsZWwpCmxpYnJhcnkoY293cGxvdCkKbGlicmFyeShnZ3JlcGVsKQpsaWJyYXJ5KE1hdHJpeCkKYGBgCgpDby1hbGlnbiB3aXRoIFJhaHVsJ3MgMTBrIFBCTUNzCmBgYHtyfQpsb2FkKCIuLi9wYm1jMTBrX3YzL3NldXJhdC5SRGF0YSIpICMgY2QsIGNkLnZhcixtZXRhLCB1bWFwCmBgYAoKYGBge3J9CnNhdGlqYS5hbm4gPC0gYXMuZmFjdG9yKHNldE5hbWVzKG1ldGEkY2VsbHR5cGUscm93bmFtZXMobWV0YSkpKQpgYGAKCgpQcmUtcHJvY2VzcywgYWxpZ24gYW5kIGFubm90YXRlCmBgYHtyfQpjZGwucDIgPC0gbGFwcGx5KGxpc3QocGJtYzYwPWNvdW50cyxwYm1jMTA9Y2QpLCBiYXNpY1AycHJvYywgbi5jb3Jlcz0zMCwgbWluLmNlbGxzLnBlci5nZW5lPTAsIG4ub2RnZW5lcz0yZTMsIGdldC5sYXJnZXZpcz1GQUxTRSwgbWFrZS5nZW5la25uPUZBTFNFLGdldC50c25lPVQpCmBgYAoKYGBge3J9CmNvbiA8LSBDb25vcyRuZXcoY2RsLnAyLG4uY29yZXM9MzApCmNvbiRidWlsZEdyYXBoKGs9MTUsIGsuc2VsZj01LCBzcGFjZT0nUENBJywgbmNvbXBzPTMwLCBuLm9kZ2VuZXM9MjAwMCwgbWF0Y2hpbmcubWV0aG9kPSdtTk4nLCBtZXRyaWM9J2FuZ3VsYXInLCB2ZXJib3NlPVRSVUUpCmBgYAoKQ2x1c3RlciBhbmQgZ2VuZXJhdGUgYSAyRCBlYm1lZGRpbmcKYGBge3J9CmNvbiRmaW5kQ29tbXVuaXRpZXMocj0yKQpjb24kZW1iZWRHcmFwaChtZXRob2Q9J1VNQVAnKTsgCmVtYiA8LSBjb24kZW1iZWRkaW5nOwoKY29uJG1pc2MkZW1iZWRkaW5ncyA8LSBsaXN0KFVNQVA9ZW1iKQpgYGAKCmBgYHtyfQpjb24kZW1iZWRHcmFwaChtZXRob2Q9J2xhcmdlVmlzJyk7IApjb24kbWlzYyRlbWJlZGRpbmdzIDwtIGxpc3QobGFyZ2VWaXM9ZW1iKQpgYGAKCgpwcm9wYWdhdGUgbGFiZWxzCmBgYHtyfQpuZXcuYW5uIDwtIGNvbiRwcm9wYWdhdGVMYWJlbHMobGFiZWxzID0gc2F0aWphLmFubiwgdmVyYm9zZT1ULGZpeGVkLmluaXRpYWwubGFiZWxzPVRSVUUpCm5ldy5hbm4gPC0gbmV3LmFubiRsYWJlbHMKYGBgCgoKVGFrZSBhIGxvb2s6CmBgYHtyIGZpZy53aWR0aD05LGZpZy5oZWlnaHQ9MTB9CmFscGhhIDwtIDAuMTsgc2l6ZSA8LSAwLjE7CnAxIDwtIGNvbiRwbG90R3JhcGgodGl0bGU9J2NsdXN0ZXJzJyxhbHBoYT1hbHBoYSxzaXplPXNpemUpOwpwMiA8LSBjb24kcGxvdEdyYXBoKGNvbG9yLmJ5PSdzYW1wbGUnLHRpdGxlPSdzYW1wbGVzJyxtYXJrLmdyb3Vwcz1GLGFscGhhPWFscGhhLHNpemU9c2l6ZSk7CnAzIDwtIGNvbiRwbG90R3JhcGgoZ3JvdXBzPW5ldy5hbm4sYWxwaGE9YWxwaGEsc2l6ZT1zaXplLHRpdGxlPSdhbm5vdGF0aW9uJyxwbG90Lm5hPUYpCnA0IDwtIGNvbiRwbG90R3JhcGgoZ3JvdXBzPXNhdGlqYS5hbm4sYWxwaGE9YWxwaGEsc2l6ZT1zaXplLHRpdGxlPSdvbGQgYW5ub3RhdGlvbicscGxvdC5uYT1GKQpjb3dwbG90OjpwbG90X2dyaWQocGxvdGxpc3Q9bGlzdChwMSxwMixwMyxwNCksbnJvdz0yKQpgYGAKCmBgYHtyIGZpZy53aWR0aD05LGZpZy5oZWlnaHQ9MTB9CmFscGhhIDwtIDAuMTsgc2l6ZSA8LSAwLjE7CnAyIDwtIGNvbiRwbG90R3JhcGgoY29sb3IuYnk9J3NhbXBsZScsdGl0bGU9J3NhbXBsZXMnLG1hcmsuZ3JvdXBzPUYsYWxwaGE9YWxwaGEsc2l6ZT1zaXplKTsKcDMgPC0gY29uJHBsb3RHcmFwaChncm91cHM9bmV3LmFubixhbHBoYT1hbHBoYSxzaXplPXNpemUsdGl0bGU9J2Fubm90YXRpb24nLHBsb3QubmE9RikKcDQgPC0gY29uJHBsb3RHcmFwaChncm91cHM9c2F0aWphLmFubixhbHBoYT1hbHBoYSxzaXplPXNpemUsdGl0bGU9J29sZCBhbm5vdGF0aW9uJyxwbG90Lm5hPUYpCnAxIDwtIGNvbiRwbG90R3JhcGgoZ2VuZT0nQ0QxNCcpCmNvd3Bsb3Q6OnBsb3RfZ3JpZChwbG90bGlzdD1saXN0KHAxLHAyLHAzLHA0KSxucm93PTIpCmBgYAoKCgojIyBwMiBwcm9jZXNzaW5nCgpgYGB7cn0KciA8LSBQYWdvZGEyJG5ldyhjb3VudHNbLG5hbWVzKGRzKVtkczwwLjJdXSxsb2cuc2NhbGU9VCwgbi5jb3Jlcz0zMCkKI3AyIDwtIGNvbiRzYW1wbGVzJHBibWM2MApgYGAKCmBgYHtyfQpyJGFkanVzdFZhcmlhbmNlKHBsb3Q9VCxnYW0uaz0xMCkKYGBgCgpgYGB7cn0KciRjYWxjdWxhdGVQY2FSZWR1Y3Rpb24oblBjcz01MCxuLm9kZ2VuZXM9M2UzKQpyJG1ha2VLbm5HcmFwaChrPTQwLHR5cGU9J1BDQScsY2VudGVyPVQsZGlzdGFuY2U9J2Nvc2luZScpOwpgYGAKCmBgYHtyfQpyJGdldEVtYmVkZGluZyh0eXBlPSdQQ0EnLGVtYmVkZGluZ1R5cGU9J3RTTkUnLG4uY29yZXM9MzApCmBgYAoKCmBgYHtyfQpyJGdldEtubkNsdXN0ZXJzKHR5cGU9J1BDQScsbWV0aG9kPWxlaWRlbi5jb21tdW5pdHkscj0xLG5hbWU9J2xlaWRlbicpCmBgYAoKCmBgYHtyIGZpZy53aWR0aD01LGZpZy5oZWlnaHQ9NX0KciRwbG90RW1iZWRkaW5nKHR5cGU9J1BDQScsZW1iZWRkaW5nVHlwZT0ndFNORScsY2x1c3RlclR5cGU9J2xlaWRlbicsc2hvdy5sZWdlbmQ9RixtYXJrLmNsdXN0ZXJzPVQsbWluLmdyb3VwLnNpemU9MSxzaHVmZmxlLmNvbG9ycz1GLG1hcmsuY2x1c3Rlci5jZXg9MSxhbHBoYT0wLjEsY2V4PTAuMDEsbWFpbj0nY2x1c3RlcnMgKHRTTkUpJykKYGBgCgoKYGBge3IgZmlnLndpZHRoPTUsZmlnLmhlaWdodD01fQpyJHBsb3RFbWJlZGRpbmcodHlwZT0nUENBJyxlbWJlZGRpbmdUeXBlPSd0U05FJyxncm91cHM9bmV3LmFubixzaG93LmxlZ2VuZD1GLG1hcmsuY2x1c3RlcnM9VCxtaW4uZ3JvdXAuc2l6ZT0xLHNodWZmbGUuY29sb3JzPUYsbWFyay5jbHVzdGVyLmNleD0xLGFscGhhPTAuMDEpCmBgYAoKCgpgYGB7ciBmaWcud2lkdGg9NSxmaWcuaGVpZ2h0PTV9CnIkcGxvdEVtYmVkZGluZyh0eXBlPSdQQ0EnLGVtYmVkZGluZ1R5cGU9J3RTTkUnLGNvbG9ycz1yJGNvdW50c1ssJ0ZPWFAzJ10sc2hvdy5sZWdlbmQ9RixtYXJrLmNsdXN0ZXJzPVQsbWluLmdyb3VwLnNpemU9MSxzaHVmZmxlLmNvbG9ycz1GLG1hcmsuY2x1c3Rlci5jZXg9MSxhbHBoYT0wLjAxKQpgYGAKCgpgYGB7cn0KZGUgPC0gciRnZXREaWZmZXJlbnRpYWxHZW5lcyh0eXBlPSdQQ0EnLHZlcmJvc2U9VCxjbHVzdGVyVHlwZT0nbGVpZGVuJyxhcHBlbmQuYXVjPVQsdXByZWd1bGF0ZWQub25seSA9IFQpCmBgYAoKYGBge3IgZmlnLmhlaWdodD0xNSwgZmlnLndpZHRoPTEwfQpjb25vczo6OnBsb3RERWhlYXRtYXAocixyJGNsdXN0ZXJzJFBDQSRsZWlkZW4sZGUpCmBgYAoKU2hvdyBldmlkZW5jZSBmb3IgY2x1c3RlciAxIGFuZCAyCmBgYHtyIGZpZy53aWR0aD0zLGZpZy5oZWlnaHQ9M30KcDEgPC0gY29ub3M6OmVtYmVkZGluZ1Bsb3QociRlbWJlZGRpbmdzJFBDQSR0U05FLGdyb3Vwcz1hcy5mYWN0b3IociRjbHVzdGVycyRQQ0EkbGVpZGVuKSxzaXplPTAuMSxhbHBoYT0wLjIscmFzdGVyPVQscmFzdGVyLmhlaWdodCA9IDMscmFzdGVyLndpZHRoPTMpKwogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvciA9IDEsIHNpemU9MC4yLGxpbmV0eXBlPTEpLGF4aXMubGluZT1lbGVtZW50X2JsYW5rKCkpCnAxCmBgYAoKYGBge3IgZmlnLndpZHRoPTMsZmlnLmhlaWdodD0zfQpwMiA8LSBjb25vczo6ZW1iZWRkaW5nUGxvdChyJGVtYmVkZGluZ3MkUENBJHRTTkUsZ3JvdXBzPW5ldy5hbm4sc2l6ZT0wLjEsYWxwaGE9MC4yLHJhc3Rlcj1ULHJhc3Rlci5oZWlnaHQgPSAzLHJhc3Rlci53aWR0aD0zLGZvbnQuc2l6ZT1jKDMsNCkpKwogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChjb2xvciA9IDEsIHNpemU9MC4yLGxpbmV0eXBlPTEpLGF4aXMubGluZT1lbGVtZW50X2JsYW5rKCkpCnAyCmBgYAoKYGBge3IgZmlnLndpZHRoPTEyLGZpZy5oZWlnaHQ9M30KZ25zIDwtIGMoIkNEMTQiLCdDRDNFJywiQ0Q4QSIpCnBvIDwtIGxhcHBseShnbnMsZnVuY3Rpb24oZykgewogIGNvbm9zOjplbWJlZGRpbmdQbG90KHIkZW1iZWRkaW5ncyRQQ0EkdFNORSxjb2xvcnM9ciRjb3VudHNbLGddLHNpemU9MC4xLGFscGhhPTAuNixyYXN0ZXI9VCxyYXN0ZXIuaGVpZ2h0ID0gMyxyYXN0ZXIud2lkdGg9MykrCiAgYW5ub3RhdGUoJ3RleHQnLHg9LUluZix5PUluZix2anVzdD0xLjIsaGp1c3Q9MCxsYWJlbD1nLHNpemU9NikrCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9yZWN0KGNvbG9yID0gMSwgc2l6ZT0wLjIsbGluZXR5cGU9MSksYXhpcy5saW5lPWVsZW1lbnRfYmxhbmsoKSkKfSkKcHAgPC0gcGxvdF9ncmlkKHBsb3RsaXN0PWMobGlzdChwMSkscG8pLG5yb3c9MSkKcHAKYGBgCgpgYGB7cn0KcGRmKGZpbGU9J3BibWM2MF9hbm4ucGRmJyx3aWR0aD0xMixoZWlnaHQ9Myk7IHByaW50KHBwKTsgZGV2Lm9mZigpCmBgYAoKIyMgRGlmZmVyZW50aWFsIGV4cHJlc3Npb24gbGFkZGVyCgpgYGB7cn0KY2VsbC5ncm91cHMgPC0gciRjbHVzdGVycyRQQ0EkbGVpZGVuCmNlbGwuZ3JvdXBzIDwtIHRhcHBseShuYW1lcyhjZWxsLmdyb3VwcyksY2VsbC5ncm91cHMsSSkKY2VsbC5ncm91cHMgPC0gY2VsbC5ncm91cHNbYygnMScsJzInKV0KbmFtZXMoY2VsbC5ncm91cHMpIDwtIGMoIkNENCsgVCBjZWxscyIsIkNEMTQrIE1vbm9jeXRlcyIpCmBgYAoKYGBge3J9CnNpZy50aHIgPC0gcW5vcm0oMC4wMSxsb3dlci50YWlsPUYpOwoKY2VsbC5ncmlkIDwtIDEwXihzZXEoMSxsb2cxMCgxZTQpLGxlbmd0aC5vdXQ9MTAwKSkKCm5nbCA8LSBtY2xhcHBseShjZWxsLmdyaWQsZnVuY3Rpb24obi5jZWxscykgewogIGNlbGxzIDwtIGxhcHBseShjZWxsLmdyb3VwcyxzYW1wbGUsbi5jZWxscykKICBjZiA8LSBhcy5mYWN0b3Ioc2V0TmFtZXMocmVwKG5hbWVzKGNlbGxzKSxlYWNoPW4uY2VsbHMpLHVubGlzdChjZWxscykpKQogIHN1cHByZXNzV2FybmluZ3MoeCA8LSByJGdldERpZmZlcmVudGlhbEdlbmVzKGdyb3VwcyA9IGNmLGFwcGVuZC5zcGVjaWZpY2l0eS5tZXRyaWNzPUYpW1sxXV0pCn0sbWMuY29yZXM9MzApCgpuZyA8LSBsYXBwbHkobmdsLGZ1bmN0aW9uKHgpIHtzdW0oYWJzKHgkWik+PXNpZy50aHIpfSkKYGBgCgpgYGB7cn0KeCA8LSByJGNvdW50c1tyb3duYW1lcyhyJGNvdW50cyklaW4lIHVubGlzdChjZWxsLmdyb3VwcyksXQpleHA1IDwtIGNvbG5hbWVzKHgpW2NvbFN1bXMoeD4wKSA+PSBucm93KHgpKjAuMDVdCmV4cDEwIDwtIGNvbG5hbWVzKHgpW2NvbFN1bXMoeD4wKSA+PSBucm93KHgpKjAuMV0KYGBgCgoKYGBge3IgZmlnLndpZHRoPTMsZmlnLmhlaWdodD0zLG1lc3NhZ2U9Rn0KZ2dwbG90KGRhdGEuZnJhbWUobj1jZWxsLmdyaWQsbmRlPXVubGlzdChuZykpLGFlcyh4PWxvZzEwKG4pLHk9bmRlKSkrZ2VvbV9wb2ludChzaXplPTAuMikrZ2VvbV9zbW9vdGgoKSt0aGVtZV9idygpCmBgYAoKCgpgYGB7cn0Kbmc1IDwtIGxhcHBseShuZ2wsZnVuY3Rpb24oeCkge3N1bShhYnMoeCRaW3Jvd25hbWVzKHgpICVpbiUgZXhwNV0pPj1zaWcudGhyKX0pCmBgYAoKCmBgYHtyIGZpZy53aWR0aD0zLjMsZmlnLmhlaWdodD0zLG1lc3NhZ2U9Rn0KZGYgPC0gZGF0YS5mcmFtZShuPWNlbGwuZ3JpZCxuZGU9dW5saXN0KG5nNSksbmRmPXVubGlzdChuZzUpL2xlbmd0aChleHA1KSkKcCA8LSBnZ3Bsb3QoZGYsYWVzKHg9bG9nMTAobikseT1uZGUpKStnZW9tX3BvaW50KHNpemU9MC4yKStnZW9tX3Ntb290aChjb2xvcj1hZGp1c3Rjb2xvcigncmVkJyxhbHBoYT0wLjYpLHNwYW4gPSAwLjUpK3RoZW1lX2J3KCkgKwogIHhsYWIoImxvZzEwWyBudW1iZXIgb2YgY2VsbHMgXSIpK3lsYWIoJ251bWJlciBvZiBERSBnZW5lcycpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoc2VjLmF4aXMgPSBzZWNfYXhpcyh+Li9sZW5ndGgoZXhwNSksIG5hbWUgPSAiREUgZ2VuZSBmcmFjdGlvbiIpKSArCiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAwLjUpLGF4aXMudGV4dC55LnJpZ2h0ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMC41KSkKcApgYGAKCmBgYHtyfQpwZGYoZmlsZT0nZGUuZnJhY3Rpb24ucGRmJyxoZWlnaHQ9Mix3aWR0aD0yLjMpOyBwcmludChwKTsgZGV2Lm9mZigpCmBgYAoKCg==