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(ggplot2)
library(Matrix)
library(magrittr)

Read data

read.velo <- function(path,prefix='') {
  t <- readr:::read_delim(paste(path,"matrix.mtx",sep='/'),delim=' ',skip=3,col_names=F)
  f <- read.delim(paste(path,'features.tsv',sep='/'),header=F,stringsAsFactors=F)
  b <- read.delim(paste(path,'barcodes.tsv',sep='/'),header=F,stringsAsFactors=F)
  
  dims <- c(nrow(f),nrow(b))
  spliced <- Matrix::sparseMatrix(i=t$X1,j=t$X2,x=t$X3,dims=dims)
  unspliced <- Matrix::sparseMatrix(i=t$X1,j=t$X2,x=t$X4,dims=dims)
  
  colnames(spliced) <- colnames(unspliced) <- paste0(prefix,b[,1]);
  rownames(spliced) <- rownames(unspliced) <- make.unique(f[,2]);
  list(spliced=spliced,unspliced=unspliced)
}

m1 <- read.velo("r8/Velocyto/raw",prefix='r8')
Parsed with column specification:
cols(
  X1 = col_double(),
  X2 = col_double(),
  X3 = col_double(),
  X4 = col_double(),
  X5 = col_double()
)
#m2 <- read.velo("r9/Velocyto/raw",prefix='r9')

#cd <- cbind(m1$spliced,m2$spliced)
#batch.f <- setNames(ifelse(grepl("r8",colnames(cd)),'r8','r9'),colnames(cd)) %>% as.factor

Initial processing - to select the relevant populations

r <-  basicP2proc(m1$spliced,n.cores=20,nPcs=50,make.geneknn=F,n.odgenes=5e3,get.tsne = F, get.largevis = F)
5747 cells, 31053 genes; normalizing ... using plain model winsorizing ... log scale ... done.
calculating variance fit ... using gam 637 overdispersed genes ... 637persisting ... done.
running PCA using 5000 OD genes .... done
creating space of type angular done
adding data ... done
building index ... done
querying ... done
r$getKnnClusters(type='PCA',method=function(x) conos::leiden.community(x,r=1),name='leiden')
set.seed(1)
r$getEmbedding(type='PCA',embeddingType='tSNE',perplexity=70)
calculating distance ... pearson ...running tSNE using 20 cores:
r$plotEmbedding(type = 'PCA', embedding = 'tSNE', clusterType = 'leiden', mark.clusters = T,alpha=0.2,cex=0.3,mark.cluster.cex = 1)

set.seed(1)
r$getEmbedding(type='PCA',embeddingType='UMAP',n_neighbors=100,min_dist=0.4,spread=1,n.cores=30)
14:30:37 UMAP embedding parameters a = 0.7669 b = 1.223
14:30:37 Read 5747 rows and found 50 numeric columns
14:30:37 Using Annoy for neighbor search, n_neighbors = 100
14:30:37 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
14:30:38 Writing NN index file to temp file /tmp/Rtmpaayt91/filed2515364e31f
14:30:38 Searching Annoy index using 30 threads, search_k = 10000
14:30:39 Annoy recall = 100%
14:30:40 Commencing smooth kNN distance calibration using 30 threads
14:30:42 Initializing from normalized Laplacian + noise
14:30:43 Commencing optimization for 500 epochs, with 838026 positive edges using 30 threads
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
14:30:47 Optimization finished
r$plotEmbedding(type = 'PCA', embedding = 'UMAP', clusterType = 'leiden', mark.clusters = T,alpha=0.2,cex=0.3,mark.cluster.cex = 1)

gl <- c('Mki67', # cc
        # progenitors 
        'Fos',
        'Sox2',
        
        # neroblasts
        'Prc1',
        'Sstr2',
        'Penk',
        
        #rgc
        'Isl1',
        'Pou4f2',
        
        #photoreceptors
        'Crx',
        
        # amacrine/horizontal
        'Lhx1',
        'Onecut2'
        
        )

lapply(gl,function(gn) {
  r$plotEmbedding(type = 'PCA', embedding = 'UMAP', colors=r$counts[,gn],alpha=0.5,cex=0.2,main=gn)
})
treating colors as a gradient with zlim: 0 0.5648841 
treating colors as a gradient with zlim: 0 1.861343 

treating colors as a gradient with zlim: 0 0.3296107 

treating colors as a gradient with zlim: 0 0.7482324 

treating colors as a gradient with zlim: 0 1.465659 

treating colors as a gradient with zlim: 0 1.383004 

treating colors as a gradient with zlim: 0 1.243379 

treating colors as a gradient with zlim: 0 0.1277146 

treating colors as a gradient with zlim: 0 1.62815 

treating colors as a gradient with zlim: 0 0.5593899 

treating colors as a gradient with zlim: 0 0.6832949 

[[1]]
NULL

[[2]]
NULL

[[3]]
NULL

[[4]]
NULL

[[5]]
NULL

[[6]]
NULL

[[7]]
NULL

[[8]]
NULL

[[9]]
NULL

[[10]]
NULL

[[11]]
NULL

vic <- names(r$clusters$PCA$leiden)[!r$clusters$PCA$leiden %in% c(13,1,3,7,9,6)]
r2 <-  basicP2proc(m1$spliced[,colnames(m1$spliced) %in% vic],n.cores=20,nPcs=30,make.geneknn=F,get.tsne = F, get.largevis = F)
2797 cells, 31053 genes; normalizing ... using plain model winsorizing ... log scale ... done.
calculating variance fit ... using gam 392 overdispersed genes ... 392persisting ... done.
running PCA using 3000 OD genes .... done
creating space of type angular done
adding data ... done
building index ... done
querying ... done
r2$getKnnClusters(type='PCA',method=function(x) conos::leiden.community(x,r=1.5),name='leiden')
r2$getEmbedding(type='PCA',embeddingType='tSNE',perplexity=50)
calculating distance ... pearson ...running tSNE using 20 cores:
r2$plotEmbedding(type = 'PCA', embedding = 'tSNE', clusterType = 'leiden', mark.clusters = T,alpha=0.2,cex=0.3,mark.cluster.cex = 1)

r2$plotEmbedding(type = 'PCA', embedding = 'tSNE', groups=r$clusters$PCA$leiden,alpha=0.5,cex=0.2,mark.clusters = T,mark.cluster.cex = 1)
using provided groups as a factor

set.seed(1)
r2$getEmbedding(type='PCA',embeddingType='UMAP',n_neighbors=100,min_dist=0.4,spread=1,n.cores=30)
14:33:37 UMAP embedding parameters a = 0.7669 b = 1.223
14:33:37 Read 2797 rows and found 30 numeric columns
14:33:37 Using Annoy for neighbor search, n_neighbors = 100
14:33:37 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
14:33:38 Writing NN index file to temp file /tmp/Rtmpaayt91/filed25111e51eef
14:33:38 Searching Annoy index using 30 threads, search_k = 10000
14:33:38 Annoy recall = 100%
14:33:39 Commencing smooth kNN distance calibration using 30 threads
14:33:41 Initializing from normalized Laplacian + noise
14:33:41 Commencing optimization for 500 epochs, with 324128 positive edges using 30 threads
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
14:33:45 Optimization finished
r2$getKnnClusters(type='PCA',method=function(x) conos::leiden.community(x,r=5),name='leiden2')
r2$plotEmbedding(type = 'PCA', embedding = 'UMAP', clusterType = 'leiden', mark.clusters = T,alpha=0.2,cex=0.3,mark.cluster.cex = 1)

r2$plotEmbedding(type = 'PCA', embedding = 'UMAP', groups=r$clusters$PCA$leiden, mark.clusters = T,alpha=0.2,cex=0.3,mark.cluster.cex = 1)
using provided groups as a factor

r2$plotEmbedding(type = 'PCA', embedding = 'UMAP', groups=r2$clusters$PCA$leiden2, mark.clusters = T,alpha=0.2,cex=0.3,mark.cluster.cex = 1)
using provided groups as a factor

r2$plotEmbedding(type = 'PCA', embedding = 'UMAP', groups=ann, mark.clusters = T,alpha=0.2,cex=0.3,mark.cluster.cex = 1)
using provided groups as a factor

r2$plotEmbedding(type = 'PCA', embedding = 'UMAP', groups=batch.f, mark.clusters = T,alpha=0.2,cex=0.3,mark.cluster.cex = 1)
using provided groups as a factor

Remove mature cluster 15

vic2 <- names(r2$clusters$PCA$leiden)[!r2$clusters$PCA$leiden2 %in% c(26,28)]
r3 <-  basicP2proc(m1$spliced[,colnames(m1$spliced) %in% vic2],n.cores=20,nPcs=20,make.geneknn=F,get.tsne = F, get.largevis = F)
2726 cells, 31053 genes; normalizing ... using plain model winsorizing ... log scale ... done.
calculating variance fit ... using gam 323 overdispersed genes ... 323persisting ... done.
running PCA using 3000 OD genes .... done
creating space of type angular done
adding data ... done
building index ... done
querying ... done
set.seed(0)
r3$getKnnClusters(type='PCA',method=function(x) conos::leiden.community(x,r=1.5),name='leiden')
set.seed(8) # 5/100/0.4
emb <- r3$getEmbedding(type='PCA',embeddingType='UMAP',n_neighbors=100,min_dist=0.4,spread=1,n.cores=1)
18:26:17 UMAP embedding parameters a = 0.7669 b = 1.223
18:26:17 Read 2726 rows and found 20 numeric columns
18:26:17 Using Annoy for neighbor search, n_neighbors = 100
18:26:17 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
18:26:18 Writing NN index file to temp file /tmp/Rtmpaayt91/filed2512ac48430
18:26:18 Searching Annoy index using 1 thread, search_k = 10000
18:26:20 Annoy recall = 100%
18:26:21 Commencing smooth kNN distance calibration using 1 thread
18:26:23 Initializing from normalized Laplacian + noise
18:26:23 Commencing optimization for 500 epochs, with 285834 positive edges using 1 thread
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
18:26:32 Optimization finished
emb[,1] <- -1*emb[,1]; emb[,2]  <- -1*emb[,2]
r3$embeddings$PCA$UMAP <- emb
embs <- mclapply(1:30,function(x) { set.seed(x); r3$getEmbedding(type='PCA',embeddingType='UMAP',n_neighbors=100,min_dist=0.4,spread=1,n.cores=1)},mc.cores=30)
lapply(1:length(embs),function(i){ 
  emb <- embs[[i]]
  #emb[,1] <- -1*emb[,1]; emb[,2]  <- -1*emb[,2]
  r3$embeddings$PCA$UMAP <- emb;
  r3$plotEmbedding(type = 'PCA', embedding = 'UMAP', clusterType = 'leiden', mark.clusters = T,alpha=0.2,cex=0.3,mark.cluster.cex = 1,main=i)})

[[1]]
NULL

[[2]]
NULL

[[3]]
NULL

[[4]]
NULL

[[5]]
NULL

[[6]]
NULL

[[7]]
NULL

[[8]]
NULL

[[9]]
NULL

[[10]]
NULL

[[11]]
NULL

[[12]]
NULL

[[13]]
NULL

[[14]]
NULL

[[15]]
NULL

[[16]]
NULL

[[17]]
NULL

[[18]]
NULL

[[19]]
NULL

[[20]]
NULL

[[21]]
NULL

[[22]]
NULL

[[23]]
NULL

[[24]]
NULL

[[25]]
NULL

[[26]]
NULL

[[27]]
NULL

[[28]]
NULL

[[29]]
NULL

[[30]]
NULL

r3$plotEmbedding(type = 'PCA', embedding = 'UMAP', clusterType = 'leiden', mark.clusters = T,alpha=0.2,cex=0.3,mark.cluster.cex = 1)

r3$plotEmbedding(type = 'PCA', embedding = 'UMAP', groups=r$clusters$PCA$leiden, mark.clusters = T,alpha=0.2,cex=0.3,mark.cluster.cex = 1)
using provided groups as a factor

r3$plotEmbedding(type = 'PCA', embedding = 'UMAP', groups=ann, mark.clusters = T,alpha=0.2,cex=0.3,mark.cluster.cex = 1)
using provided groups as a factor

gn <- 'Mki67'; r3$plotEmbedding(type = 'PCA', embedding = 'UMAP', colors=r3$counts[,gn],alpha=0.5,cex=0.2,main=gn)
treating colors as a gradient with zlim: 0 0.6875693 

Clean plots

require(entropy)
entropy.scores <- apply(r3$misc$rawCounts,1,entropy)
# with cell depth subsampling
# subsample columns of a sparse matrix to approximately a desired depth
subsample.cell.depth <- function(m,depth=1e3) {
  p.sample <- pmin(1,rep(depth/Matrix::colSums(m),diff(m@p)))
  m@x <- as.numeric(rbinom(length(m@x),m@x,p.sample))
  m
}
entropy.scores.sub <- apply(subsample.cell.depth(t(r3$misc$rawCounts)),2,entropy)
r3$plotEmbedding(type = 'PCA', embedding = 'UMAP', colors=pagoda2:::val2col(entropy.scores.sub,zlim=c(6.1,6.2)),alpha=0.5,cex=0.2)
using supplied colors as is

x <- entropy.scores.sub
x <- x-mean(x)
r3$plotEmbedding(type = 'PCA', embedding = 'UMAP', colors=x,alpha=0.2,cex=0.5,gradient.range.quantile=0.7)
treating colors as a gradient with zlim: -0.1487894 0.1487894 

p2c.plot <- function(emb, ...) {
  theme <- theme_void() + theme(panel.border = element_rect(fill=NA,color = 1, size=0.2,linetype=1),axis.line=element_blank(),plot.margin = margin(.1, .1, .1, .1, "cm"))
  conos::embeddingPlot(emb, plot.theme=theme, ...)
}
emb <- r3$embeddings$PCA$UMAP
p2c.plot(emb,groups=r3$clusters$PCA$leiden, alpha=0.2,size=1)

Make an annotation

fann <- setNames(rep('Progenitor',length(r3$clusters$PCA$leiden)),names(r3$clusters$PCA$leiden))
fann[r3$clusters$PCA$leiden %in% c('10','9')] <- 'Neuroblast'
fann[r3$clusters$PCA$leiden %in% c('2','1')] <- 'RGC'
fann[r3$clusters$PCA$leiden %in% c('14','6','8')] <- 'AC/HC'
fann[r3$clusters$PCA$leiden %in% c('12','11')] <- 'PR'
fann <- as.factor(fann)
set.seed(0)
fann.pal <- setNames(sample(rainbow(length(levels(fann)),s=0.8,v=0.8)),levels(fann))
p <- p2c.plot(emb,groups=fann, alpha=0.5,size=0.02,palette=fann.pal,font.size=c(4,5),raster.width=3,raster.height=3,raster=T)
pdf(file='ret_ann.pdf',width=3,height=3); print(p); dev.off();
null device 
          1 
p

Export for scanpy:

save(raw.count.matrix.merged,metadata.df,embedding.df,file=paste0(output.path,'/data.RData'))
Warning message:
package ‘igraph’ was built under R version 3.5.3 

Cell cycle genes

gns <- c('Mcm6','Ccne2',"Esco2","Cdk1","Aurka","Cenpa")
pp <- lapply(gns,function(g) p2c.plot(emb,colors=r3$counts[,g],alpha=0.2,size=0.5,gradient.range.quantile=0.9,raster.width=2,raster.height=2,raster=T)+annotate('text',x=Inf,y=Inf,hjust=1.05,vjust=1.1,label=g,size=9))
#pp <- lapply(gns,function(g) p2c.plot(emb,color=r3$counts[g,],gradient.range.quantile=0.9)+annotate('text',x=Inf,y=Inf,vjust=1,hjust=0,label=g))
p <- cowplot::plot_grid(plotlist=pp,nrow=1)
pdf(file='ret_cc_genes.pdf',width=15,height=2.5); print(p); dev.off();
png 
  2 
p

pdf(file='ret_cc_genes2.pdf',width=5.2,height=1); print(p); dev.off();
null device 
          1 
p2c.plot2 <- function(emb, ...) {
  theme <- theme_void() + theme(axis.line=element_blank(),plot.margin = margin(.1, .1, .1, .1, "cm"))
  conos::embeddingPlot(emb, plot.theme=theme, ...)
}
gns <- c('Mcm6','Ccne2',"Esco2","Cdk1","Aurka","Cenpa")
pp <- lapply(gns,function(g) p2c.plot2(emb,colors=r3$counts[,g],alpha=0.2,size=0.5,gradient.range.quantile=0.9,raster.width=1,raster.height=1,raster=T)+
               xlim(-6,3) + ylim(-9,4) +
               annotate('text',x=-Inf,y=Inf,hjust=0,vjust=1.1,label=g,size=6))
#pp <- lapply(gns,function(g) p2c.plot(emb,color=r3$counts[g,],gradient.range.quantile=0.9)+annotate('text',x=Inf,y=Inf,vjust=1,hjust=0,label=g))
p <- cowplot::plot_grid(plotlist=pp,nrow=1)
Removed 1220 rows containing missing values (geom_point_rast).Removed 1220 rows containing missing values (geom_point_rast).Removed 1220 rows containing missing values (geom_point_rast).Removed 1220 rows containing missing values (geom_point_rast).Removed 1220 rows containing missing values (geom_point_rast).Removed 1220 rows containing missing values (geom_point_rast).
pdf(file='ret_cc_genes3.pdf',width=5.2,height=1); print(p); dev.off();
png 
  2 
p

p2c.plot(emb,colors=r3$counts[,'Cdk1'],alpha=0.2,size=0.5,gradient.range.quantile=0.9,raster.width=1,raster.height=1,raster=T) + xlim(-6,3) + ylim(-9,4) 

Reduced version:

gns <- c('Mcm6','Ccne2',"Esco2","Cdk1","Aurka","Cenpa")
pp <- lapply(gns,function(g) p2c.plot(emb,colors=r3$counts[,g],alpha=0.2,size=0.5,gradient.range.quantile=0.9,raster.width=2,raster.height=2,raster=T)+annotate('text',x=Inf,y=Inf,hjust=1.05,vjust=1.1,label=g,size=9))
#pp <- lapply(gns,function(g) p2c.plot(emb,color=r3$counts[g,],gradient.range.quantile=0.9)+annotate('text',x=Inf,y=Inf,vjust=1,hjust=0,label=g))
p <- cowplot::plot_grid(plotlist=pp,nrow=1)
pdf(file='ret_cc_genes.pdf',width=15,height=2.5); print(p); dev.off();
p

Marker genes:

gl <- c(#'Mki67', # cc
        # progenitors 
        'Fos',
        #'Sox2',
        
        # neroblasts
        #'Prc1',
        'Sstr2',
        'Penk',
        
        #rgc
        'Isl1',
        'Pou4f2',
        
        #photoreceptors
        'Crx',
        
        # amacrine/horizontal
        'Lhx1',
        'Onecut2'
        
        )
pp <- lapply(gl,function(g) p2c.plot(emb,colors=r3$counts[,g],alpha=0.2,size=0.5,gradient.range.quantile=0.9,raster.width=2,raster.height=2,raster=T)+annotate('text',x=Inf,y=Inf,hjust=1.05,vjust=1.1,label=g,size=9))
p <- cowplot::plot_grid(plotlist=pp,nrow=2)
p

pdf(file='ret_genes.pdf',width=10,height=5); print(p); dev.off();
set.seed(2)
x <- entropy.scores.sub
x <- x-mean(x)
a1 <- p2c.plot(emb,colors=x[rownames(emb)],alpha=0.2,size=0.5,gradient.range.quantile=0.8,raster.width=2,raster.height=2,raster=T) + theme(legend.position=c(0.75,0.8)) + 
  guides(color = guide_colourbar(barwidth = 6, barheight = 0.8,  direction="horizontal", frame.colour=c("black"),title.position='top',ticks=F,label.theme=element_text(size=12))) +
  theme(legend.title=element_text(size=15))+
  scale_color_gradient(name='Entropy',low='darkgreen',high='darkorange',limits=c(-0.1,0.1),oob=scales::squish,breaks=c(-0.08,0.08),labels=c('low','high'))
Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.
pdf(file='ret_entropy.pdf',width=3,height=3); print(a1); dev.off()
null device 
          1 
a1

Tree

p <- p2c.plot(emb,groups=fann, alpha=0.5,size=0.5,palette=fann.pal,font.size=c(3,4),raster.width=3,raster.height=3,raster=T)
pdf(file='ret_ann2.pdf',width=1.5,height=1.5); print(p); dev.off();
p
source("functions_copy.R")

in 2 dimensions

z2 <- t.ppt.tree(X=t(emb),emb=emb,M=200,lambda=100,sigma=0.02,metrics='euclidean',plot=F,output=F)
a1 <- p <- p2c.plot(emb,groups=fann, alpha=0.2,size=0.5,palette=fann.pal,font.size=c(4,5),raster.width=3,raster.height=3,raster=T,mark.groups=F)+
  t.ggplot.ppt.tree(z2,emb,size=0.65,col=adjustcolor(1,alpha=0.9))
pdf(file='ret_ann_tree.pdf',width=1.5,height=1.5); print(a1); dev.off();
null device 
          1 
a1

High-dimensional tree

hde <- r3$getEmbedding(type='PCA',name='hd',embeddingType = 'UMAP',n_neighbors=100,min_dist=0.4,spread=1,n_components = 20)
z20 <- t.ppt.tree(X=t(hde),emb=emb,M=200,lambda=100,sigma=0.02,metrics='euclidean',plot=F,output=F)
zb <- t.bootstrap.ppt(X=t(hde),emb=emb, M=200,err.cut=1e-3,n.steps=100,plot=F,output=F,lambda=200,sigma=0.01, seed=9,metrics='euclidean',n.cores=30,n.samples=30)
a1 <- p2c.plot(emb,groups=fann, alpha=0.2,size=0.5,palette=fann.pal,font.size=c(4,5),raster.width=3,raster.height=3,raster=T,mark.groups=F)
for(i in 1:30) a1 <- a1+t.ggplot.ppt.tree(zb[[i]],emb,size=0.5,col=adjustcolor(1,alpha=0.05))
pdf(file='ret_ann_trees.pdf',width=1.5,height=1.5); print(a1); dev.off();
null device 
          1 
a1

LS0tCnRpdGxlOiAiUmV0aW5hIGV4YW1wbGUiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyfQpsaWJyYXJ5KHBhZ29kYTIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShNYXRyaXgpCmxpYnJhcnkobWFncml0dHIpCmBgYAoKClJlYWQgZGF0YQpgYGB7cn0KcmVhZC52ZWxvIDwtIGZ1bmN0aW9uKHBhdGgscHJlZml4PScnKSB7CiAgdCA8LSByZWFkcjo6OnJlYWRfZGVsaW0ocGFzdGUocGF0aCwibWF0cml4Lm10eCIsc2VwPScvJyksZGVsaW09JyAnLHNraXA9Myxjb2xfbmFtZXM9RikKICBmIDwtIHJlYWQuZGVsaW0ocGFzdGUocGF0aCwnZmVhdHVyZXMudHN2JyxzZXA9Jy8nKSxoZWFkZXI9RixzdHJpbmdzQXNGYWN0b3JzPUYpCiAgYiA8LSByZWFkLmRlbGltKHBhc3RlKHBhdGgsJ2JhcmNvZGVzLnRzdicsc2VwPScvJyksaGVhZGVyPUYsc3RyaW5nc0FzRmFjdG9ycz1GKQogIAogIGRpbXMgPC0gYyhucm93KGYpLG5yb3coYikpCiAgc3BsaWNlZCA8LSBNYXRyaXg6OnNwYXJzZU1hdHJpeChpPXQkWDEsaj10JFgyLHg9dCRYMyxkaW1zPWRpbXMpCiAgdW5zcGxpY2VkIDwtIE1hdHJpeDo6c3BhcnNlTWF0cml4KGk9dCRYMSxqPXQkWDIseD10JFg0LGRpbXM9ZGltcykKICAKICBjb2xuYW1lcyhzcGxpY2VkKSA8LSBjb2xuYW1lcyh1bnNwbGljZWQpIDwtIHBhc3RlMChwcmVmaXgsYlssMV0pOwogIHJvd25hbWVzKHNwbGljZWQpIDwtIHJvd25hbWVzKHVuc3BsaWNlZCkgPC0gbWFrZS51bmlxdWUoZlssMl0pOwogIGxpc3Qoc3BsaWNlZD1zcGxpY2VkLHVuc3BsaWNlZD11bnNwbGljZWQpCn0KCm0xIDwtIHJlYWQudmVsbygicjgvVmVsb2N5dG8vcmF3IixwcmVmaXg9J3I4JykKI20yIDwtIHJlYWQudmVsbygicjkvVmVsb2N5dG8vcmF3IixwcmVmaXg9J3I5JykKCiNjZCA8LSBjYmluZChtMSRzcGxpY2VkLG0yJHNwbGljZWQpCiNiYXRjaC5mIDwtIHNldE5hbWVzKGlmZWxzZShncmVwbCgicjgiLGNvbG5hbWVzKGNkKSksJ3I4JywncjknKSxjb2xuYW1lcyhjZCkpICU+JSBhcy5mYWN0b3IKYGBgCgoKSW5pdGlhbCBwcm9jZXNzaW5nIC0gdG8gc2VsZWN0IHRoZSByZWxldmFudCBwb3B1bGF0aW9ucwpgYGB7cn0KciA8LSAgYmFzaWNQMnByb2MobTEkc3BsaWNlZCxuLmNvcmVzPTIwLG5QY3M9NTAsbWFrZS5nZW5la25uPUYsbi5vZGdlbmVzPTVlMyxnZXQudHNuZSA9IEYsIGdldC5sYXJnZXZpcyA9IEYpCnIkZ2V0S25uQ2x1c3RlcnModHlwZT0nUENBJyxtZXRob2Q9ZnVuY3Rpb24oeCkgY29ub3M6OmxlaWRlbi5jb21tdW5pdHkoeCxyPTEuMiksbmFtZT0nbGVpZGVuJykKYGBgCgpgYGB7cn0Kc2V0LnNlZWQoMSkKciRnZXRFbWJlZGRpbmcodHlwZT0nUENBJyxlbWJlZGRpbmdUeXBlPSd0U05FJyxwZXJwbGV4aXR5PTcwKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0zLCBmaWcuaGVpZ2h0PTN9CnIkcGxvdEVtYmVkZGluZyh0eXBlID0gJ1BDQScsIGVtYmVkZGluZyA9ICd0U05FJywgY2x1c3RlclR5cGUgPSAnbGVpZGVuJywgbWFyay5jbHVzdGVycyA9IFQsYWxwaGE9MC4yLGNleD0wLjMsbWFyay5jbHVzdGVyLmNleCA9IDEpCmBgYAoKYGBge3J9CnNldC5zZWVkKDEpCnIkZ2V0RW1iZWRkaW5nKHR5cGU9J1BDQScsZW1iZWRkaW5nVHlwZT0nVU1BUCcsbl9uZWlnaGJvcnM9MTAwLG1pbl9kaXN0PTAuNCxzcHJlYWQ9MSxuLmNvcmVzPTMwKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0zLCBmaWcuaGVpZ2h0PTN9CnIkcGxvdEVtYmVkZGluZyh0eXBlID0gJ1BDQScsIGVtYmVkZGluZyA9ICdVTUFQJywgY2x1c3RlclR5cGUgPSAnbGVpZGVuJywgbWFyay5jbHVzdGVycyA9IFQsYWxwaGE9MC4yLGNleD0wLjMsbWFyay5jbHVzdGVyLmNleCA9IDEpCmBgYAoKYGBge3IgZmlnLndpZHRoPTMsIGZpZy5oZWlnaHQ9M30KZ2wgPC0gYygnTWtpNjcnLCAjIGNjCiAgICAgICAgIyBwcm9nZW5pdG9ycyAKICAgICAgICAnRm9zJywKICAgICAgICAnU294MicsCiAgICAgICAgCiAgICAgICAgIyBuZXJvYmxhc3RzCiAgICAgICAgJ1ByYzEnLAogICAgICAgICdTc3RyMicsCiAgICAgICAgJ1BlbmsnLAogICAgICAgIAogICAgICAgICNyZ2MKICAgICAgICAnSXNsMScsCiAgICAgICAgJ1BvdTRmMicsCiAgICAgICAgCiAgICAgICAgI3Bob3RvcmVjZXB0b3JzCiAgICAgICAgJ0NyeCcsCiAgICAgICAgCiAgICAgICAgIyBhbWFjcmluZS9ob3Jpem9udGFsCiAgICAgICAgJ0xoeDEnLAogICAgICAgICdPbmVjdXQyJwogICAgICAgIAogICAgICAgICkKCmxhcHBseShnbCxmdW5jdGlvbihnbikgewogIHIkcGxvdEVtYmVkZGluZyh0eXBlID0gJ1BDQScsIGVtYmVkZGluZyA9ICdVTUFQJywgY29sb3JzPXIkY291bnRzWyxnbl0sYWxwaGE9MC41LGNleD0wLjIsbWFpbj1nbikKfSkKYGBgCgoKYGBge3J9CnZpYyA8LSBuYW1lcyhyJGNsdXN0ZXJzJFBDQSRsZWlkZW4pWyFyJGNsdXN0ZXJzJFBDQSRsZWlkZW4gJWluJSBjKDEzLDEsMyw3LDksNildCnIyIDwtICBiYXNpY1AycHJvYyhtMSRzcGxpY2VkWyxjb2xuYW1lcyhtMSRzcGxpY2VkKSAlaW4lIHZpY10sbi5jb3Jlcz0yMCxuUGNzPTMwLG1ha2UuZ2VuZWtubj1GLGdldC50c25lID0gRiwgZ2V0LmxhcmdldmlzID0gRikKcjIkZ2V0S25uQ2x1c3RlcnModHlwZT0nUENBJyxtZXRob2Q9ZnVuY3Rpb24oeCkgY29ub3M6OmxlaWRlbi5jb21tdW5pdHkoeCxyPTEuNSksbmFtZT0nbGVpZGVuJykKYGBgCgoKYGBge3J9CnIyJGdldEVtYmVkZGluZyh0eXBlPSdQQ0EnLGVtYmVkZGluZ1R5cGU9J3RTTkUnLHBlcnBsZXhpdHk9NTApCmBgYAoKCmBgYHtyIGZpZy53aWR0aD0zLCBmaWcuaGVpZ2h0PTN9CnIyJHBsb3RFbWJlZGRpbmcodHlwZSA9ICdQQ0EnLCBlbWJlZGRpbmcgPSAndFNORScsIGNsdXN0ZXJUeXBlID0gJ2xlaWRlbicsIG1hcmsuY2x1c3RlcnMgPSBULGFscGhhPTAuMixjZXg9MC4zLG1hcmsuY2x1c3Rlci5jZXggPSAxKQpyMiRwbG90RW1iZWRkaW5nKHR5cGUgPSAnUENBJywgZW1iZWRkaW5nID0gJ3RTTkUnLCBncm91cHM9ciRjbHVzdGVycyRQQ0EkbGVpZGVuLGFscGhhPTAuNSxjZXg9MC4yLG1hcmsuY2x1c3RlcnMgPSBULG1hcmsuY2x1c3Rlci5jZXggPSAxKQpgYGAKCmBgYHtyfQpzZXQuc2VlZCgxKQpyMiRnZXRFbWJlZGRpbmcodHlwZT0nUENBJyxlbWJlZGRpbmdUeXBlPSdVTUFQJyxuX25laWdoYm9ycz0xMDAsbWluX2Rpc3Q9MC40LHNwcmVhZD0xLG4uY29yZXM9MzApCmBgYAoKYGBge3J9CnIyJGdldEtubkNsdXN0ZXJzKHR5cGU9J1BDQScsbWV0aG9kPWZ1bmN0aW9uKHgpIGNvbm9zOjpsZWlkZW4uY29tbXVuaXR5KHgscj01KSxuYW1lPSdsZWlkZW4yJykKYGBgCgoKYGBge3IgZmlnLndpZHRoPTMsZmlnLmhlaWdodD0zfQpyMiRwbG90RW1iZWRkaW5nKHR5cGUgPSAnUENBJywgZW1iZWRkaW5nID0gJ1VNQVAnLCBjbHVzdGVyVHlwZSA9ICdsZWlkZW4nLCBtYXJrLmNsdXN0ZXJzID0gVCxhbHBoYT0wLjIsY2V4PTAuMyxtYXJrLmNsdXN0ZXIuY2V4ID0gMSkKcjIkcGxvdEVtYmVkZGluZyh0eXBlID0gJ1BDQScsIGVtYmVkZGluZyA9ICdVTUFQJywgZ3JvdXBzPXIkY2x1c3RlcnMkUENBJGxlaWRlbiwgbWFyay5jbHVzdGVycyA9IFQsYWxwaGE9MC4yLGNleD0wLjMsbWFyay5jbHVzdGVyLmNleCA9IDEpCnIyJHBsb3RFbWJlZGRpbmcodHlwZSA9ICdQQ0EnLCBlbWJlZGRpbmcgPSAnVU1BUCcsIGdyb3Vwcz1yMiRjbHVzdGVycyRQQ0EkbGVpZGVuMiwgbWFyay5jbHVzdGVycyA9IFQsYWxwaGE9MC4yLGNleD0wLjMsbWFyay5jbHVzdGVyLmNleCA9IDEpCnIyJHBsb3RFbWJlZGRpbmcodHlwZSA9ICdQQ0EnLCBlbWJlZGRpbmcgPSAnVU1BUCcsIGdyb3Vwcz1hbm4sIG1hcmsuY2x1c3RlcnMgPSBULGFscGhhPTAuMixjZXg9MC4zLG1hcmsuY2x1c3Rlci5jZXggPSAxKQpyMiRwbG90RW1iZWRkaW5nKHR5cGUgPSAnUENBJywgZW1iZWRkaW5nID0gJ1VNQVAnLCBncm91cHM9YmF0Y2guZiwgbWFyay5jbHVzdGVycyA9IFQsYWxwaGE9MC4yLGNleD0wLjMsbWFyay5jbHVzdGVyLmNleCA9IDEpCmBgYAoKClJlbW92ZSBtYXR1cmUgY2x1c3RlciAxNQoKYGBge3J9CnZpYzIgPC0gbmFtZXMocjIkY2x1c3RlcnMkUENBJGxlaWRlbilbIXIyJGNsdXN0ZXJzJFBDQSRsZWlkZW4yICVpbiUgYygyNiwyOCldCnIzIDwtICBiYXNpY1AycHJvYyhtMSRzcGxpY2VkWyxjb2xuYW1lcyhtMSRzcGxpY2VkKSAlaW4lIHZpYzJdLG4uY29yZXM9MjAsblBjcz0yMCxtYWtlLmdlbmVrbm49RixnZXQudHNuZSA9IEYsIGdldC5sYXJnZXZpcyA9IEYpCmBgYAoKYGBge3J9CnNldC5zZWVkKDApCnIzJGdldEtubkNsdXN0ZXJzKHR5cGU9J1BDQScsbWV0aG9kPWZ1bmN0aW9uKHgpIGNvbm9zOjpsZWlkZW4uY29tbXVuaXR5KHgscj0xLjUpLG5hbWU9J2xlaWRlbicpCmBgYAoKCmBgYHtyfQpzZXQuc2VlZCg4KSAjIDUvMTAwLzAuNAplbWIgPC0gcjMkZ2V0RW1iZWRkaW5nKHR5cGU9J1BDQScsZW1iZWRkaW5nVHlwZT0nVU1BUCcsbl9uZWlnaGJvcnM9MTAwLG1pbl9kaXN0PTAuNCxzcHJlYWQ9MSxuLmNvcmVzPTEpCiNlbWJbLDFdIDwtIC0xKmVtYlssMV07IGVtYlssMl0gIDwtIC0xKmVtYlssMl0KcjMkZW1iZWRkaW5ncyRQQ0EkVU1BUCA8LSBlbWIKYGBgCgpgYGB7cn0KZW1icyA8LSBtY2xhcHBseSgxOjMwLGZ1bmN0aW9uKHgpIHsgc2V0LnNlZWQoeCk7IHIzJGdldEVtYmVkZGluZyh0eXBlPSdQQ0EnLGVtYmVkZGluZ1R5cGU9J1VNQVAnLG5fbmVpZ2hib3JzPTEwMCxtaW5fZGlzdD0wLjQsc3ByZWFkPTEsbi5jb3Jlcz0xKX0sbWMuY29yZXM9MzApCmBgYAoKYGBge3IgZmlnLndpZHRoPTMsZmlnLmhlaWdodD0zfQpsYXBwbHkoMTpsZW5ndGgoZW1icyksZnVuY3Rpb24oaSl7IAogIGVtYiA8LSBlbWJzW1tpXV0KICAjZW1iWywxXSA8LSAtMSplbWJbLDFdOyBlbWJbLDJdICA8LSAtMSplbWJbLDJdCiAgcjMkZW1iZWRkaW5ncyRQQ0EkVU1BUCA8LSBlbWI7CiAgcjMkcGxvdEVtYmVkZGluZyh0eXBlID0gJ1BDQScsIGVtYmVkZGluZyA9ICdVTUFQJywgY2x1c3RlclR5cGUgPSAnbGVpZGVuJywgbWFyay5jbHVzdGVycyA9IFQsYWxwaGE9MC4yLGNleD0wLjMsbWFyay5jbHVzdGVyLmNleCA9IDEsbWFpbj1pKX0pCmBgYAoKCgpgYGB7ciBmaWcud2lkdGg9MyxmaWcuaGVpZ2h0PTN9CnIzJHBsb3RFbWJlZGRpbmcodHlwZSA9ICdQQ0EnLCBlbWJlZGRpbmcgPSAnVU1BUCcsIGNsdXN0ZXJUeXBlID0gJ2xlaWRlbicsIG1hcmsuY2x1c3RlcnMgPSBULGFscGhhPTAuMixjZXg9MC4zLG1hcmsuY2x1c3Rlci5jZXggPSAxKQpyMyRwbG90RW1iZWRkaW5nKHR5cGUgPSAnUENBJywgZW1iZWRkaW5nID0gJ1VNQVAnLCBncm91cHM9ciRjbHVzdGVycyRQQ0EkbGVpZGVuLCBtYXJrLmNsdXN0ZXJzID0gVCxhbHBoYT0wLjIsY2V4PTAuMyxtYXJrLmNsdXN0ZXIuY2V4ID0gMSkKcjMkcGxvdEVtYmVkZGluZyh0eXBlID0gJ1BDQScsIGVtYmVkZGluZyA9ICdVTUFQJywgZ3JvdXBzPWFubiwgbWFyay5jbHVzdGVycyA9IFQsYWxwaGE9MC4yLGNleD0wLjMsbWFyay5jbHVzdGVyLmNleCA9IDEpCmduIDwtICdNa2k2Nyc7IHIzJHBsb3RFbWJlZGRpbmcodHlwZSA9ICdQQ0EnLCBlbWJlZGRpbmcgPSAnVU1BUCcsIGNvbG9ycz1yMyRjb3VudHNbLGduXSxhbHBoYT0wLjUsY2V4PTAuMixtYWluPWduKQpgYGAKCgoKCkNsZWFuIHBsb3RzCmBgYHtyfQpyZXF1aXJlKGVudHJvcHkpCmVudHJvcHkuc2NvcmVzIDwtIGFwcGx5KHIzJG1pc2MkcmF3Q291bnRzLDEsZW50cm9weSkKIyB3aXRoIGNlbGwgZGVwdGggc3Vic2FtcGxpbmcKIyBzdWJzYW1wbGUgY29sdW1ucyBvZiBhIHNwYXJzZSBtYXRyaXggdG8gYXBwcm94aW1hdGVseSBhIGRlc2lyZWQgZGVwdGgKc3Vic2FtcGxlLmNlbGwuZGVwdGggPC0gZnVuY3Rpb24obSxkZXB0aD0xZTMpIHsKICBwLnNhbXBsZSA8LSBwbWluKDEscmVwKGRlcHRoL01hdHJpeDo6Y29sU3VtcyhtKSxkaWZmKG1AcCkpKQogIG1AeCA8LSBhcy5udW1lcmljKHJiaW5vbShsZW5ndGgobUB4KSxtQHgscC5zYW1wbGUpKQogIG0KfQplbnRyb3B5LnNjb3Jlcy5zdWIgPC0gYXBwbHkoc3Vic2FtcGxlLmNlbGwuZGVwdGgodChyMyRtaXNjJHJhd0NvdW50cykpLDIsZW50cm9weSkKYGBgCgoKYGBge3IgZmlnLndpZHRoPTMsIGZpZy5oZWlnaHQ9M30KcjMkcGxvdEVtYmVkZGluZyh0eXBlID0gJ1BDQScsIGVtYmVkZGluZyA9ICdVTUFQJywgY29sb3JzPXBhZ29kYTI6Ojp2YWwyY29sKGVudHJvcHkuc2NvcmVzLnN1Yix6bGltPWMoNi4xLDYuMikpLGFscGhhPTAuNSxjZXg9MC4yKQp4IDwtIGVudHJvcHkuc2NvcmVzLnN1Ygp4IDwtIHgtbWVhbih4KQpyMyRwbG90RW1iZWRkaW5nKHR5cGUgPSAnUENBJywgZW1iZWRkaW5nID0gJ1VNQVAnLCBjb2xvcnM9eCxhbHBoYT0wLjIsY2V4PTAuNSxncmFkaWVudC5yYW5nZS5xdWFudGlsZT0wLjcpCmBgYAoKCmBgYHtyfQpwMmMucGxvdCA8LSBmdW5jdGlvbihlbWIsIC4uLikgewogIHRoZW1lIDwtIHRoZW1lX3ZvaWQoKSArIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfcmVjdChmaWxsPU5BLGNvbG9yID0gMSwgc2l6ZT0wLjIsbGluZXR5cGU9MSksYXhpcy5saW5lPWVsZW1lbnRfYmxhbmsoKSxwbG90Lm1hcmdpbiA9IG1hcmdpbiguMSwgLjEsIC4xLCAuMSwgImNtIikpCiAgY29ub3M6OmVtYmVkZGluZ1Bsb3QoZW1iLCBwbG90LnRoZW1lPXRoZW1lLCAuLi4pCn0KYGBgCgpgYGB7cn0KZW1iIDwtIHIzJGVtYmVkZGluZ3MkUENBJFVNQVAKYGBgCgoKYGBge3IgZmlnLndpZHRoPTMsIGZpZy5oZWlnaHQ9M30KcDJjLnBsb3QoZW1iLGdyb3Vwcz1yMyRjbHVzdGVycyRQQ0EkbGVpZGVuLCBhbHBoYT0wLjIsc2l6ZT0xKQpgYGAKCk1ha2UgYW4gYW5ub3RhdGlvbgpgYGB7cn0KZmFubiA8LSBzZXROYW1lcyhyZXAoJ1Byb2dlbml0b3InLGxlbmd0aChyMyRjbHVzdGVycyRQQ0EkbGVpZGVuKSksbmFtZXMocjMkY2x1c3RlcnMkUENBJGxlaWRlbikpCmZhbm5bcjMkY2x1c3RlcnMkUENBJGxlaWRlbiAlaW4lIGMoJzEwJywnOScpXSA8LSAnTmV1cm9ibGFzdCcKZmFubltyMyRjbHVzdGVycyRQQ0EkbGVpZGVuICVpbiUgYygnMicsJzEnKV0gPC0gJ1JHQycKZmFubltyMyRjbHVzdGVycyRQQ0EkbGVpZGVuICVpbiUgYygnMTQnLCc2JywnOCcpXSA8LSAnQUMvSEMnCmZhbm5bcjMkY2x1c3RlcnMkUENBJGxlaWRlbiAlaW4lIGMoJzEyJywnMTEnKV0gPC0gJ1BSJwpmYW5uIDwtIGFzLmZhY3RvcihmYW5uKQpzZXQuc2VlZCgwKQpmYW5uLnBhbCA8LSBzZXROYW1lcyhzYW1wbGUocmFpbmJvdyhsZW5ndGgobGV2ZWxzKGZhbm4pKSxzPTAuOCx2PTAuOCkpLGxldmVscyhmYW5uKSkKYGBgCgpgYGB7ciBmaWcud2lkdGg9MywgZmlnLmhlaWdodD0zfQpwIDwtIHAyYy5wbG90KGVtYixncm91cHM9ZmFubiwgYWxwaGE9MC41LHNpemU9MC4wMixwYWxldHRlPWZhbm4ucGFsLGZvbnQuc2l6ZT1jKDQsNSkscmFzdGVyLndpZHRoPTMscmFzdGVyLmhlaWdodD0zLHJhc3Rlcj1UKQpwZGYoZmlsZT0ncmV0X2Fubi5wZGYnLHdpZHRoPTMsaGVpZ2h0PTMpOyBwcmludChwKTsgZGV2Lm9mZigpOwpwCmBgYAoKCgpFeHBvcnQgZm9yIHNjYW5weToKCmBgYHtyfQpvdXRwdXQucGF0aCA8LSAnc2N2ZWxvL3AyZXhwb3J0JwpyYXcuY291bnQubWF0cml4Lm1lcmdlZCA8LSByMyRtaXNjJHJhd0NvdW50cwoKY2VsbC5pZHMgPC0gcm93bmFtZXMocmF3LmNvdW50Lm1hdHJpeC5tZXJnZWQpCnVuc3BsaWNlZC5jb3VudC5tYXRyaXgubWVyZ2VkIDwtIHQobTEkdW5zcGxpY2VkW2NvbG5hbWVzKHJhdy5jb3VudC5tYXRyaXgubWVyZ2VkKSxyb3duYW1lcyhyYXcuY291bnQubWF0cml4Lm1lcmdlZCldKQoKZ2VuZS5kZiA8LSBkYXRhLmZyYW1lKGdlbmUgPSBjb2xuYW1lcyhyYXcuY291bnQubWF0cml4Lm1lcmdlZCkpCm1ldGFkYXRhLmRmIDwtIHRpYmJsZTo6dGliYmxlKENlbGxJZCA9IGNlbGwuaWRzKQptZXRhZGF0YS5kZiRDbHVzdGVycyA8LSByMyRjbHVzdGVycyRQQ0EkbGVpZGVuW2NlbGwuaWRzXQptZXRhZGF0YS5kZiRBbm5vdGF0aW9uIDwtIGZhbm5bY2VsbC5pZHNdCmVtYmVkZGluZy5kZiA8LSByMyRlbWJlZGRpbmdzJFBDQSRVTUFQW2NlbGwuaWRzLCBdICU+JSBhcy5kYXRhLmZyYW1lKCkKTWF0cml4Ojp3cml0ZU1NKHJhdy5jb3VudC5tYXRyaXgubWVyZ2VkLCBwYXN0ZTAob3V0cHV0LnBhdGgsICIvc3BsaWNlZF9jb3VudF9tYXRyaXgubXR4IikpCk1hdHJpeDo6d3JpdGVNTSh1bnNwbGljZWQuY291bnQubWF0cml4Lm1lcmdlZCwgcGFzdGUwKG91dHB1dC5wYXRoLCAiL3Vuc3BsaWNlZF9jb3VudF9tYXRyaXgubXR4IikpCmRhdGEudGFibGU6OmZ3cml0ZShtZXRhZGF0YS5kZiwgcGFzdGUwKG91dHB1dC5wYXRoLCAiL21ldGFkYXRhLmNzdiIpKQpkYXRhLnRhYmxlOjpmd3JpdGUoZ2VuZS5kZiwgcGFzdGUwKG91dHB1dC5wYXRoLCAiL2dlbmVzLmNzdiIpKQpkYXRhLnRhYmxlOjpmd3JpdGUoZW1iZWRkaW5nLmRmLCBwYXN0ZTAob3V0cHV0LnBhdGgsICIvZW1iZWRkaW5nLmNzdiIpKQpzYXZlKHJhdy5jb3VudC5tYXRyaXgubWVyZ2VkLG1ldGFkYXRhLmRmLGVtYmVkZGluZy5kZixmaWxlPXBhc3RlMChvdXRwdXQucGF0aCwnL2RhdGEuUkRhdGEnKSkKYGBgCgoKQ2VsbCBjeWNsZSBnZW5lcwpgYGB7ciBmaWcud2lkdGg9MTUsZmlnLmhlaWdodD0yLjV9CmducyA8LSBjKCdNY202JywnQ2NuZTInLCJFc2NvMiIsIkNkazEiLCJBdXJrYSIsIkNlbnBhIikKcHAgPC0gbGFwcGx5KGducyxmdW5jdGlvbihnKSBwMmMucGxvdChlbWIsY29sb3JzPXIzJGNvdW50c1ssZ10sYWxwaGE9MC4yLHNpemU9MC41LGdyYWRpZW50LnJhbmdlLnF1YW50aWxlPTAuOSxyYXN0ZXIud2lkdGg9MixyYXN0ZXIuaGVpZ2h0PTIscmFzdGVyPVQpK2Fubm90YXRlKCd0ZXh0Jyx4PUluZix5PUluZixoanVzdD0xLjA1LHZqdXN0PTEuMSxsYWJlbD1nLHNpemU9OSkpCiNwcCA8LSBsYXBwbHkoZ25zLGZ1bmN0aW9uKGcpIHAyYy5wbG90KGVtYixjb2xvcj1yMyRjb3VudHNbZyxdLGdyYWRpZW50LnJhbmdlLnF1YW50aWxlPTAuOSkrYW5ub3RhdGUoJ3RleHQnLHg9SW5mLHk9SW5mLHZqdXN0PTEsaGp1c3Q9MCxsYWJlbD1nKSkKcCA8LSBjb3dwbG90OjpwbG90X2dyaWQocGxvdGxpc3Q9cHAsbnJvdz0xKQpwZGYoZmlsZT0ncmV0X2NjX2dlbmVzLnBkZicsd2lkdGg9MTUsaGVpZ2h0PTIuNSk7IHByaW50KHApOyBkZXYub2ZmKCk7CnAKYGBgCgpgYGB7ciBmaWcud2lkdGg9NS4yLGZpZy5oZWlnaHQ9MX0KZ25zIDwtIGMoJ01jbTYnLCdDY25lMicsIkVzY28yIiwiQ2RrMSIsIkF1cmthIiwiQ2VucGEiKQpwcCA8LSBsYXBwbHkoZ25zLGZ1bmN0aW9uKGcpIHAyYy5wbG90KGVtYixjb2xvcnM9cjMkY291bnRzWyxnXSxhbHBoYT0wLjIsc2l6ZT0wLjUsZ3JhZGllbnQucmFuZ2UucXVhbnRpbGU9MC45LHJhc3Rlci53aWR0aD0xLHJhc3Rlci5oZWlnaHQ9MSxyYXN0ZXI9VCkrCiAgICAgICAgICAgICAgIHhsaW0oLTYsMykgKyB5bGltKC05LDQpICsKICAgICAgICAgICAgICAgYW5ub3RhdGUoJ3RleHQnLHg9SW5mLHk9SW5mLGhqdXN0PTEuMDUsdmp1c3Q9MS4xLGxhYmVsPWcsc2l6ZT0zKSkKI3BwIDwtIGxhcHBseShnbnMsZnVuY3Rpb24oZykgcDJjLnBsb3QoZW1iLGNvbG9yPXIzJGNvdW50c1tnLF0sZ3JhZGllbnQucmFuZ2UucXVhbnRpbGU9MC45KSthbm5vdGF0ZSgndGV4dCcseD1JbmYseT1JbmYsdmp1c3Q9MSxoanVzdD0wLGxhYmVsPWcpKQpwIDwtIGNvd3Bsb3Q6OnBsb3RfZ3JpZChwbG90bGlzdD1wcCxucm93PTEpCnBkZihmaWxlPSdyZXRfY2NfZ2VuZXMyLnBkZicsd2lkdGg9NS4yLGhlaWdodD0xKTsgcHJpbnQocCk7IGRldi5vZmYoKTsKcApgYGAKCmBgYHtyfQpwMmMucGxvdDIgPC0gZnVuY3Rpb24oZW1iLCAuLi4pIHsKICB0aGVtZSA8LSB0aGVtZV92b2lkKCkgKyB0aGVtZShheGlzLmxpbmU9ZWxlbWVudF9ibGFuaygpLHBsb3QubWFyZ2luID0gbWFyZ2luKC4xLCAuMSwgLjEsIC4xLCAiY20iKSkKICBjb25vczo6ZW1iZWRkaW5nUGxvdChlbWIsIHBsb3QudGhlbWU9dGhlbWUsIC4uLikKfQpgYGAKCmBgYHtyIGZpZy53aWR0aD01LjIsZmlnLmhlaWdodD0xfQpnbnMgPC0gYygnTWNtNicsJ0NjbmUyJywiRXNjbzIiLCJDZGsxIiwiQXVya2EiLCJDZW5wYSIpCnBwIDwtIGxhcHBseShnbnMsZnVuY3Rpb24oZykgcDJjLnBsb3QyKGVtYixjb2xvcnM9cjMkY291bnRzWyxnXSxhbHBoYT0wLjIsc2l6ZT0wLjUsZ3JhZGllbnQucmFuZ2UucXVhbnRpbGU9MC45LHJhc3Rlci53aWR0aD0xLHJhc3Rlci5oZWlnaHQ9MSxyYXN0ZXI9VCkrCiAgICAgICAgICAgICAgIHhsaW0oLTYsMykgKyB5bGltKC05LDQpICsKICAgICAgICAgICAgICAgYW5ub3RhdGUoJ3RleHQnLHg9LUluZix5PUluZixoanVzdD0wLHZqdXN0PTEuMSxsYWJlbD1nLHNpemU9NikpCiNwcCA8LSBsYXBwbHkoZ25zLGZ1bmN0aW9uKGcpIHAyYy5wbG90KGVtYixjb2xvcj1yMyRjb3VudHNbZyxdLGdyYWRpZW50LnJhbmdlLnF1YW50aWxlPTAuOSkrYW5ub3RhdGUoJ3RleHQnLHg9SW5mLHk9SW5mLHZqdXN0PTEsaGp1c3Q9MCxsYWJlbD1nKSkKcCA8LSBjb3dwbG90OjpwbG90X2dyaWQocGxvdGxpc3Q9cHAsbnJvdz0xKQpwZGYoZmlsZT0ncmV0X2NjX2dlbmVzMy5wZGYnLHdpZHRoPTUuMixoZWlnaHQ9MSk7IHByaW50KHApOyBkZXYub2ZmKCk7CnAKYGBgCgoKYGBge3IgZmlnLndpZHRoPTEsIGZpZy5oZWlnaHQ9MSwgd2FybmluZz1GfQpwMmMucGxvdChlbWIsY29sb3JzPXIzJGNvdW50c1ssJ0NkazEnXSxhbHBoYT0wLjIsc2l6ZT0wLjUsZ3JhZGllbnQucmFuZ2UucXVhbnRpbGU9MC45LHJhc3Rlci53aWR0aD0xLHJhc3Rlci5oZWlnaHQ9MSxyYXN0ZXI9VCkgKyB4bGltKC02LDMpICsgeWxpbSgtOSw0KSAKYGBgCgoKUmVkdWNlZCB2ZXJzaW9uOgpgYGB7ciBmaWcud2lkdGg9MTUsZmlnLmhlaWdodD0yLjV9CmducyA8LSBjKCdNY202JywnQ2NuZTInLCJFc2NvMiIsIkNkazEiLCJBdXJrYSIsIkNlbnBhIikKcHAgPC0gbGFwcGx5KGducyxmdW5jdGlvbihnKSBwMmMucGxvdChlbWIsY29sb3JzPXIzJGNvdW50c1ssZ10sYWxwaGE9MC4yLHNpemU9MC41LGdyYWRpZW50LnJhbmdlLnF1YW50aWxlPTAuOSxyYXN0ZXIud2lkdGg9MixyYXN0ZXIuaGVpZ2h0PTIscmFzdGVyPVQpK2Fubm90YXRlKCd0ZXh0Jyx4PUluZix5PUluZixoanVzdD0xLjA1LHZqdXN0PTEuMSxsYWJlbD1nLHNpemU9OSkpCiNwcCA8LSBsYXBwbHkoZ25zLGZ1bmN0aW9uKGcpIHAyYy5wbG90KGVtYixjb2xvcj1yMyRjb3VudHNbZyxdLGdyYWRpZW50LnJhbmdlLnF1YW50aWxlPTAuOSkrYW5ub3RhdGUoJ3RleHQnLHg9SW5mLHk9SW5mLHZqdXN0PTEsaGp1c3Q9MCxsYWJlbD1nKSkKcCA8LSBjb3dwbG90OjpwbG90X2dyaWQocGxvdGxpc3Q9cHAsbnJvdz0xKQpwZGYoZmlsZT0ncmV0X2NjX2dlbmVzLnBkZicsd2lkdGg9MTUsaGVpZ2h0PTIuNSk7IHByaW50KHApOyBkZXYub2ZmKCk7CnAKYGBgCgoKCk1hcmtlciBnZW5lczoKCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD01fQpnbCA8LSBjKCMnTWtpNjcnLCAjIGNjCiAgICAgICAgIyBwcm9nZW5pdG9ycyAKICAgICAgICAnRm9zJywKICAgICAgICAjJ1NveDInLAogICAgICAgIAogICAgICAgICMgbmVyb2JsYXN0cwogICAgICAgICMnUHJjMScsCiAgICAgICAgJ1NzdHIyJywKICAgICAgICAnUGVuaycsCiAgICAgICAgCiAgICAgICAgI3JnYwogICAgICAgICdJc2wxJywKICAgICAgICAnUG91NGYyJywKICAgICAgICAKICAgICAgICAjcGhvdG9yZWNlcHRvcnMKICAgICAgICAnQ3J4JywKICAgICAgICAKICAgICAgICAjIGFtYWNyaW5lL2hvcml6b250YWwKICAgICAgICAnTGh4MScsCiAgICAgICAgJ09uZWN1dDInCiAgICAgICAgCiAgICAgICAgKQpwcCA8LSBsYXBwbHkoZ2wsZnVuY3Rpb24oZykgcDJjLnBsb3QoZW1iLGNvbG9ycz1yMyRjb3VudHNbLGddLGFscGhhPTAuMixzaXplPTAuNSxncmFkaWVudC5yYW5nZS5xdWFudGlsZT0wLjkscmFzdGVyLndpZHRoPTIscmFzdGVyLmhlaWdodD0yLHJhc3Rlcj1UKSthbm5vdGF0ZSgndGV4dCcseD1JbmYseT1JbmYsaGp1c3Q9MS4wNSx2anVzdD0xLjEsbGFiZWw9ZyxzaXplPTkpKQpwIDwtIGNvd3Bsb3Q6OnBsb3RfZ3JpZChwbG90bGlzdD1wcCxucm93PTIpCnAKYGBgCgpgYGB7cn0KcGRmKGZpbGU9J3JldF9nZW5lcy5wZGYnLHdpZHRoPTEwLGhlaWdodD01KTsgcHJpbnQocCk7IGRldi5vZmYoKTsKYGBgCgoKCmBgYHtyIGZpZy53aWR0aD0zLCBmaWcuaGVpZ2h0PTN9CnNldC5zZWVkKDIpCnggPC0gZW50cm9weS5zY29yZXMuc3ViCnggPC0geC1tZWFuKHgpCmExIDwtIHAyYy5wbG90KGVtYixjb2xvcnM9eFtyb3duYW1lcyhlbWIpXSxhbHBoYT0wLjIsc2l6ZT0wLjUsZ3JhZGllbnQucmFuZ2UucXVhbnRpbGU9MC44LHJhc3Rlci53aWR0aD0yLHJhc3Rlci5oZWlnaHQ9MixyYXN0ZXI9VCkgKyB0aGVtZShsZWdlbmQucG9zaXRpb249YygwLjc1LDAuOCkpICsgCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfY29sb3VyYmFyKGJhcndpZHRoID0gNiwgYmFyaGVpZ2h0ID0gMC44LCAgZGlyZWN0aW9uPSJob3Jpem9udGFsIiwgZnJhbWUuY29sb3VyPWMoImJsYWNrIiksdGl0bGUucG9zaXRpb249J3RvcCcsdGlja3M9RixsYWJlbC50aGVtZT1lbGVtZW50X3RleHQoc2l6ZT0xMikpKSArCiAgdGhlbWUobGVnZW5kLnRpdGxlPWVsZW1lbnRfdGV4dChzaXplPTE1KSkrCiAgc2NhbGVfY29sb3JfZ3JhZGllbnQobmFtZT0nRW50cm9weScsbG93PSdkYXJrZ3JlZW4nLGhpZ2g9J2RhcmtvcmFuZ2UnLGxpbWl0cz1jKC0wLjEsMC4xKSxvb2I9c2NhbGVzOjpzcXVpc2gsYnJlYWtzPWMoLTAuMDgsMC4wOCksbGFiZWxzPWMoJ2xvdycsJ2hpZ2gnKSkKcGRmKGZpbGU9J3JldF9lbnRyb3B5LnBkZicsd2lkdGg9MyxoZWlnaHQ9Myk7IHByaW50KGExKTsgZGV2Lm9mZigpCmExCmBgYAoKCiMjIFRyZWUKYGBge3IgZmlnLndpZHRoPTMsIGZpZy5oZWlnaHQ9M30KcCA8LSBwMmMucGxvdChlbWIsZ3JvdXBzPWZhbm4sIGFscGhhPTAuNSxzaXplPTAuNSxwYWxldHRlPWZhbm4ucGFsLGZvbnQuc2l6ZT1jKDMsNCkscmFzdGVyLndpZHRoPTMscmFzdGVyLmhlaWdodD0zLHJhc3Rlcj1UKQpwZGYoZmlsZT0ncmV0X2FubjIucGRmJyx3aWR0aD0xLjUsaGVpZ2h0PTEuNSk7IHByaW50KHApOyBkZXYub2ZmKCk7CnAKYGBgCgpgYGB7cn0Kc291cmNlKCJmdW5jdGlvbnNfY29weS5SIikKYGBgCmluIDIgZGltZW5zaW9ucwpgYGB7cn0KejIgPC0gdC5wcHQudHJlZShYPXQoZW1iKSxlbWI9ZW1iLE09MjAwLGxhbWJkYT0xMDAsc2lnbWE9MC4wMixtZXRyaWNzPSdldWNsaWRlYW4nLHBsb3Q9RixvdXRwdXQ9RikKYGBgCgpgYGB7ciBmaWcud2lkdGg9MS41LGZpZy5oZWlnaHQ9MS41fQphMSA8LSBwIDwtIHAyYy5wbG90KGVtYixncm91cHM9ZmFubiwgYWxwaGE9MC4yLHNpemU9MC41LHBhbGV0dGU9ZmFubi5wYWwsZm9udC5zaXplPWMoNCw1KSxyYXN0ZXIud2lkdGg9MyxyYXN0ZXIuaGVpZ2h0PTMscmFzdGVyPVQsbWFyay5ncm91cHM9RikrCiAgdC5nZ3Bsb3QucHB0LnRyZWUoejIsZW1iLHNpemU9MC42NSxjb2w9YWRqdXN0Y29sb3IoMSxhbHBoYT0wLjkpKQpwZGYoZmlsZT0ncmV0X2Fubl90cmVlLnBkZicsd2lkdGg9MS41LGhlaWdodD0xLjUpOyBwcmludChhMSk7IGRldi5vZmYoKTsKYTEKYGBgCgpIaWdoLWRpbWVuc2lvbmFsIHRyZWUKYGBge3J9CmhkZSA8LSByMyRnZXRFbWJlZGRpbmcodHlwZT0nUENBJyxuYW1lPSdoZCcsZW1iZWRkaW5nVHlwZSA9ICdVTUFQJyxuX25laWdoYm9ycz0xMDAsbWluX2Rpc3Q9MC40LHNwcmVhZD0xLG5fY29tcG9uZW50cyA9IDIwKQpgYGAKCgpgYGB7cn0KejIwIDwtIHQucHB0LnRyZWUoWD10KGhkZSksZW1iPWVtYixNPTIwMCxsYW1iZGE9MTAwLHNpZ21hPTAuMDIsbWV0cmljcz0nZXVjbGlkZWFuJyxwbG90PUYsb3V0cHV0PUYpCmBgYAoKCmBgYHtyfQp6YiA8LSB0LmJvb3RzdHJhcC5wcHQoWD10KGhkZSksZW1iPWVtYiwgTT0yMDAsZXJyLmN1dD0xZS0zLG4uc3RlcHM9MTAwLHBsb3Q9RixvdXRwdXQ9RixsYW1iZGE9MjAwLHNpZ21hPTAuMDEsIHNlZWQ9OSxtZXRyaWNzPSdldWNsaWRlYW4nLG4uY29yZXM9MzAsbi5zYW1wbGVzPTMwKQpgYGAKCgpgYGB7ciBmaWcud2lkdGg9MS41LGZpZy5oZWlnaHQ9MS41fQphMSA8LSBwMmMucGxvdChlbWIsZ3JvdXBzPWZhbm4sIGFscGhhPTAuMixzaXplPTAuNSxwYWxldHRlPWZhbm4ucGFsLGZvbnQuc2l6ZT1jKDQsNSkscmFzdGVyLndpZHRoPTMscmFzdGVyLmhlaWdodD0zLHJhc3Rlcj1ULG1hcmsuZ3JvdXBzPUYpCmZvcihpIGluIDE6MzApIGExIDwtIGExK3QuZ2dwbG90LnBwdC50cmVlKHpiW1tpXV0sZW1iLHNpemU9MC41LGNvbD1hZGp1c3Rjb2xvcigxLGFscGhhPTAuMDUpKQpwZGYoZmlsZT0ncmV0X2Fubl90cmVlcy5wZGYnLHdpZHRoPTEuNSxoZWlnaHQ9MS41KTsgcHJpbnQoYTEpOyBkZXYub2ZmKCk7CmExCmBgYAoKCg==