The notebook shows an example of basic velocity anlaysis of E12.5 chromaffin data, which is a SMART-seq2 dataset. It shows how to estimate gene-relative velocity (with different pooling options), as well as how to estimate using alternative gene-structure based model.

Data pre-processing

We assume that the SMART-seq2 data has been demultiplexed and aligned, so that we have one bam file per cell. The recommended processing step is then to use velocyto.py command line tool to annotate spliced, unspliced and spanning reads in the measured cells:

velocyto run_smartseq2 -d 1 `find ./ data/e12.5.bams/ -name "*.bam" -print` mm10.genes.gtf

If you want to re-run this command, please download bam files (and genes.refFlat) from here, and extract it (tar xvf bams.tar) in the working directory. note: the file is fairly large - 5.6 GB! Running this command will produce a velocyto directory with two files:

  1. onefilepercell_A1_unique_and_others_J2CH1.loom - a loom file containing spliced, unspliced and spanned matrices
  2. dump/onefilepercell_A1_unique_and_others_J2CH1.hdf5 - an HDF5 file containing detailed molecular mapping information which we will use to estimate parameters for the gene-structure based model. You don’t need this for regular velocity estimation, and in this case -d 1 option may be skipped when running velocyto.py

note: if you don’t want to re-run the command, you can download the velocyto.py output folder here

Data loading

Load the velocyto package:

library(velocyto.R)

Load the data and genome annotations:

Read in count matrices from the loom file:

ldat <- read.loom.matrices("data/velocyto/onefilepercell_A1_unique_and_others_J2CH1.loom")

(instead, for the purposes of the example we’ll just read in the resulting structure from the rds file)

ldat <- readRDS(url("http://pklab.med.harvard.edu/velocyto/chromaffin/ldat.rds"))
str(ldat)
List of 4
 $ spliced  :Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
  .. ..@ i       : int [1:2724270] 3 9 10 13 14 17 19 20 28 29 ...
  .. ..@ p       : int [1:385] 0 7100 14272 21376 28491 36017 43375 50956 57637 64998 ...
  .. ..@ Dim     : int [1:2] 23420 384
  .. ..@ Dimnames:List of 2
  .. .. ..$ : chr [1:23420] "Xkr4" "Rp1" "Sox17" "Mrpl15" ...
  .. .. ..$ : chr [1:384] "onefilepercell_A1_unique_and_others_J2CH1:A1_unique.bam" "onefilepercell_A1_unique_and_others_J2CH1:A10_unique.bam" "onefilepercell_A1_unique_and_others_J2CH1:A11_unique.bam" "onefilepercell_A1_unique_and_others_J2CH1:A12_unique.bam" ...
  .. ..@ x       : num [1:2724270] 24 3 22 136 48 21 15 32 109 13 ...
  .. ..@ factors : list()
 $ unspliced:Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
  .. ..@ i       : int [1:1528882] 0 4 10 13 14 17 19 20 23 29 ...
  .. ..@ p       : int [1:385] 0 3911 8043 12057 16176 20795 25046 29389 33438 37382 ...
  .. ..@ Dim     : int [1:2] 23420 384
  .. ..@ Dimnames:List of 2
  .. .. ..$ : chr [1:23420] "Xkr4" "Rp1" "Sox17" "Mrpl15" ...
  .. .. ..$ : chr [1:384] "onefilepercell_A1_unique_and_others_J2CH1:A1_unique.bam" "onefilepercell_A1_unique_and_others_J2CH1:A10_unique.bam" "onefilepercell_A1_unique_and_others_J2CH1:A11_unique.bam" "onefilepercell_A1_unique_and_others_J2CH1:A12_unique.bam" ...
  .. ..@ x       : num [1:1528882] 5 23 4 6 8 2 70 14 1 15 ...
  .. ..@ factors : list()
 $ ambiguous:Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
  .. ..@ i       : int [1:198206] 3 19 30 121 144 145 188 295 366 387 ...
  .. ..@ p       : int [1:385] 0 499 993 1562 2070 2639 3184 3785 4321 4821 ...
  .. ..@ Dim     : int [1:2] 23420 384
  .. ..@ Dimnames:List of 2
  .. .. ..$ : chr [1:23420] "Xkr4" "Rp1" "Sox17" "Mrpl15" ...
  .. .. ..$ : chr [1:384] "onefilepercell_A1_unique_and_others_J2CH1:A1_unique.bam" "onefilepercell_A1_unique_and_others_J2CH1:A10_unique.bam" "onefilepercell_A1_unique_and_others_J2CH1:A11_unique.bam" "onefilepercell_A1_unique_and_others_J2CH1:A12_unique.bam" ...
  .. ..@ x       : num [1:198206] 17 1 14 24 9 1 5 23 1 2 ...
  .. ..@ factors : list()
 $ spanning :Formal class 'dgCMatrix' [package "Matrix"] with 6 slots
  .. ..@ i       : int [1:443099] 3 17 40 80 111 132 146 161 200 216 ...
  .. ..@ p       : int [1:385] 0 1089 2250 3556 4768 6201 7494 8878 10237 11349 ...
  .. ..@ Dim     : int [1:2] 23420 384
  .. ..@ Dimnames:List of 2
  .. .. ..$ : chr [1:23420] "Xkr4" "Rp1" "Sox17" "Mrpl15" ...
  .. .. ..$ : chr [1:384] "onefilepercell_A1_unique_and_others_J2CH1:A1_unique.bam" "onefilepercell_A1_unique_and_others_J2CH1:A10_unique.bam" "onefilepercell_A1_unique_and_others_J2CH1:A11_unique.bam" "onefilepercell_A1_unique_and_others_J2CH1:A12_unique.bam" ...
  .. ..@ x       : num [1:443099] 1 2 1 3 2 1 1 1 1 1 ...
  .. ..@ factors : list()

Let’s reduce the cell names to the short well labels:

ldat <- lapply(ldat,function(x) {
  colnames(x) <-  gsub("_unique.bam","",gsub(".*:","",colnames(x)))
  x
})

Read in cell cluster assignment and tSNE embedding used in the Furlan et al. (Science’17).

cell.colors <- readRDS(url("http://pklab.med.harvard.edu/velocyto/chromaffin/cell.colors.rds"))
emb <- readRDS(url("http://pklab.med.harvard.edu/velocyto/chromaffin/embedding.rds"))

Gene filtering

Spliced expression magnitude distribution across genes:

hist(log10(rowSums(ldat$spliced)+1),col='wheat',xlab='log10[ number of reads + 1]',main='number of reads per gene')

Set up expression matrices, filtering genes to leave those that exceed some pre-defined g to the average expression magnitude

# exonic read (spliced) expression matrix
emat <- ldat$spliced;
# intronic read (unspliced) expression matrix
nmat <- ldat$unspliced
# spanning read (intron+exon) expression matrix
smat <- ldat$spanning;
# filter expression matrices based on some minimum max-cluster averages
emat <- filter.genes.by.cluster.expression(emat,cell.colors,min.max.cluster.average = 5)
nmat <- filter.genes.by.cluster.expression(nmat,cell.colors,min.max.cluster.average = 1)
smat <- filter.genes.by.cluster.expression(smat,cell.colors,min.max.cluster.average = 0.5)
# look at the resulting gene set
length(intersect(rownames(emat),rownames(nmat)))
[1] 8548
# and if we use spanning reads (smat)
length(intersect(intersect(rownames(emat),rownames(nmat)),rownames(smat)))
[1] 1696

Several variants of velocity estimates using gene-relative model

We’ll start with what is perhaps the most robust estimate, that combines cell kNN pooling with the gamma fit based on an extreme quantiles:

Using min/max quantile fit, in which case gene-specific offsets do not require spanning read (smat) fit. Here the fit is based on the top/bottom 5% of cells (by spliced expression magnitude).

fit.quantile <- 0.05;
rvel.qf <- gene.relative.velocity.estimates(emat,nmat,deltaT=1,kCells = 5,fit.quantile = fit.quantile)
calculating cell knn ... done
calculating convolved matrices ... done
fitting gamma coefficients ... done. succesfful fit for 8548 genes
filtered out 1306 out of 8548 genes due to low nmat-emat correlation
filtered out 754 out of 7242 genes due to low nmat-emat slope
calculating RNA velocity shift ... done
calculating extrapolated cell state ... done

We visualize the velocities by projecting observed and extrapolated cells onto the first 5 PCs:

pca.velocity.plot(rvel.qf,nPcs=5,plot.cols=2,cell.colors=ac(cell.colors,alpha=0.7),cex=1.2,pcount=0.1,pc.multipliers=c(1,-1,-1,-1,-1))
log ... pca ... pc multipliers ... delta norm ... done
done

Fitting of individual genes can be visualized using “show.gene” option. To save time, we’ll pass previously-calculated velocity (rvel.qf) to save calculation time:

# define custom pallet for expression magnitude
gene.relative.velocity.estimates(emat,nmat, kCells = 5,fit.quantile = fit.quantile,old.fit=rvel.qf,show.gene='Chga',cell.emb=emb,cell.colors=cell.colors)
calculating convolved matrices ... done
[1] 1

Alternatively, wen ca use spanning reads (smat) to fit the gene offsets. This will result in more accurate offset estimates, but for much fewer genes (spanning reads are rare). Note that here we also use optional diagona.quantiles setting to estimate extreme quantiles on a normalized sum of splcied and unspliced signal:

rvel <- gene.relative.velocity.estimates(emat,nmat,smat=smat, kCells = 5, fit.quantile=fit.quantile, diagonal.quantiles = TRUE)
calculating cell knn ... done
calculating convolved matrices ... done
fitting smat-based offsets ... done
fitting gamma coefficients ... done. succesfful fit for 1696 genes
filtered out 26 out of 1696 genes due to low nmat-smat correlation
filtered out 138 out of 1670 genes due to low nmat-emat correlation
filtered out 14 out of 1532 genes due to low nmat-emat slope
calculating RNA velocity shift ... done
calculating extrapolated cell state ... done

We can visualize the velocity in PCA space:

pca.velocity.plot(rvel,nPcs=5,plot.cols=2,cell.colors=ac(cell.colors,alpha=0.7),cex=1.2,pcount=0.1,pc.multipliers=c(1,-1,1,1,1))
log ... pca ... pc multipliers ... delta norm ... done
done

Here we calculate the most basic version of velocity estimates, using relative gamma fit, without cell kNN smoothing (i.e. actual single-cell velocity):

rvel1 <- gene.relative.velocity.estimates(emat,nmat,deltaT=1,deltaT2 = 1,kCells = 1, fit.quantile=fit.quantile)
fitting gamma coefficients ... done. succesfful fit for 8548 genes
filtered out 783 out of 8548 genes due to low nmat-emat correlation
filtered out 1330 out of 7765 genes due to low nmat-emat slope
calculating RNA velocity shift ... done
calculating extrapolated cell state ... done
pca.velocity.plot(rvel1,nPcs=5,plot.cols=2,cell.colors=ac(cell.colors,alpha=0.7),cex=1.2,pcount=0.1,pc.multipliers=c(1,-1,1,1,1))
log ... pca ... pc multipliers ... delta norm ... done
done

Visualization on an existing embedding

Here we use t-SNE embedding from the original publication (in emb variable).

vel <- rvel; arrow.scale=3; cell.alpha=0.4; cell.cex=1; fig.height=4; fig.width=4.5;
show.velocity.on.embedding.cor(emb,vel,n=100,scale='sqrt',cell.colors=ac(cell.colors,alpha=cell.alpha),cex=cell.cex,arrow.scale=arrow.scale,arrow.lwd=1)
delta projections ... sqrt knn ... transition probs ... done
calculating arrows ... done

Alternatively, the same function can be used to calculate a velocity vector field:

show.velocity.on.embedding.cor(emb,vel,n=100,scale='sqrt',cell.colors=ac(cell.colors,alpha=cell.alpha),cex=cell.cex,arrow.scale=arrow.scale,show.grid.flow=TRUE,min.grid.cell.mass=0.5,grid.n=20,arrow.lwd=2)
delta projections ... sqrt knn ... transition probs ... done
calculating arrows ... done
grid estimates ... grid.sd= 1.731696  min.arrow.size= 0.03463392  max.grid.arrow.length= 0.09156871  done

Velocity estimate based on gene structure

To estimate velocity based on gene structure parameters, we need to parse the gtf file as well as the debug hdf5 output of velocyto.py (-d option) which contains per-exon mapping information. You can fetch the exact gtf file used here. First, however, we will compile information on the internal priming sites expected in this genome (mouse UCSC mm10 assembly):

require(BSgenome.Mmusculus.UCSC.mm10)
ip.mm10 <- find.ip.sites('data/genes.gtf',Mmusculus,'mm10')

This needs to be ran once per genome, and can be saved for the future. To skip calculations, we just load the pre-calculated internal priming info from an rds file:

ip.mm10 <- readRDS(url("http://pklab.med.harvard.edu/velocyto/chromaffin/ip.mm10.rds"))

Now we will read velocyto.py HDF5 output:

gene.info <- read.gene.mapping.info("data/velocyto/dump/onefilepercell_A1_unique_and_others_J2CH1.hdf5",internal.priming.info=ip.mm10,min.exon.count=10);

Will load pre-calculated result instead of evaluating above: This needs to be ran once per genome, and can be saved for the future. To skip calculations, we just load the pre-calculated internal priming info from an rds file:

gene.info <- readRDS(url("http://pklab.med.harvard.edu/velocyto/chromaffin/gene.info.rds"))

Genome-wide model fit:

# start with unfiltered matrices, as we can use more genes in these types of estimates
emat <- ldat$spliced; nmat <- ldat$unspliced; smat <- ldat$spanning
emat <- filter.genes.by.cluster.expression(emat,cell.colors,min.max.cluster.average = 7)
gvel <- global.velcoity.estimates(emat, nmat, rvel, base.df=gene.info$gene.df, smat=smat, deltaT=1, kCells=5, kGenes = 15, kGenes.trim = 5, min.gene.cells = 0, min.gene.conuts = 500)
filtered out 4 out of 8990 genes due to low emat levels
filtered out 1072 out of 8986 genes due to insufficient exonic or intronic lengths
filtered out 204 out of 7914 genes due to excessive nascent counts
using relative slopes for 1221 genes to fit structure-based model ... with internal priming info ... 80.5% deviance explained.
predicting gamma ... done
refitting offsets ... calculating cell knn ... done
calculating convolved matrices ... done
fitting smat-based offsets ... done
fitting gamma coefficients ... done. succesfful fit for 7694 genes
filtered out 1337 out of 7551 genes due to low nmat-smat correlation
filtered out 899 out of 6214 genes due to low nmat-emat correlation
filtered out 440 out of 5315 genes due to low nmat-emat slope
calculating RNA velocity shift ... done
calculating extrapolated cell state ... done
re-estimated offsets for 6214 out of 7710 genes
calculating convolved matrices ... done
calculating gene knn ... done
estimating M values ... adjusting mval offsets ... re-estimating gamma ... done
calculating RNA velocity shift ... done
calculating extrapolated cell state ... done
pca.velocity.plot(gvel,nPcs=5,plot.cols=2,cell.colors=ac(cell.colors,alpha=0.7),cex=1.2,pcount=0.1,pc.multipliers=c(1,-1,-1,1,1))
log ... pca ... pc multipliers ... delta norm ... done
done

We can visualize the two estimates side-by side on a pre-calculated (published) tSNE embedding:

par(mfrow=c(1,2), mar = c(2.5,2.5,2.5,1.5), mgp = c(2,0.65,0), cex = 0.85);
arrow.scale=3; cell.alpha=0.4; cell.cex=1; fig.height=4; fig.width=4.5;
#pdf(file='tsne.rvel_gvel.plots.pdf',height=6,width=12)
show.velocity.on.embedding.cor(emb,rvel,n=100,scale='sqrt',cell.colors=ac(cell.colors,alpha=cell.alpha),cex=cell.cex,arrow.scale=arrow.scale,arrow.lwd=1,main='gene-relative esitmate',do.par=F)
delta projections ... sqrt knn ... transition probs ... done
calculating arrows ... done
show.velocity.on.embedding.cor(emb,gvel,n=100,scale='sqrt',cell.colors=ac(cell.colors,alpha=cell.alpha),cex=cell.cex,arrow.scale=arrow.scale,arrow.lwd=1,main='gene-structure estimate',do.par=F)
delta projections ... sqrt knn ... transition probs ... done
calculating arrows ... done

#dev.off()

Or by jointly embedding both observed and extrapolated cells into the same tSNE space

#pdf(file='tsne.shift.plots.pdf',height=6,width=12)
par(mfrow=c(1,2), mar = c(2.5,2.5,2.5,1.5), mgp = c(2,0.65,0), cex = 0.85);
x <- tSNE.velocity.plot(rvel,nPcs=15,cell.colors=cell.colors,cex=0.9,perplexity=200,norm.nPcs=NA,pcount=0.1,scale='log',do.par=F,main='gene-relative estimate')
rescaling ... log ... pca ... delta norm ... tSNE ...Read the 768 x 15 data matrix successfully!
OpenMP is working...
Using no_dims = 2, perplexity = 200.000000, and theta = 0.500000
Computing input similarities...
Normalizing input...
Building tree...
Done in 1.46 seconds (sparsity = 0.903880)!
Learning embedding...
Iteration 50: error is 45.227904 (50 iterations in 1.37 seconds)
Iteration 100: error is 45.218709 (50 iterations in 1.42 seconds)
Iteration 150: error is 44.385833 (50 iterations in 1.29 seconds)
Iteration 200: error is 44.370694 (50 iterations in 1.60 seconds)
Iteration 250: error is 44.369845 (50 iterations in 1.70 seconds)
Iteration 300: error is 0.164786 (50 iterations in 1.22 seconds)
Iteration 350: error is 0.154593 (50 iterations in 1.20 seconds)
Iteration 400: error is 0.154770 (50 iterations in 1.17 seconds)
Iteration 450: error is 0.154346 (50 iterations in 1.17 seconds)
Iteration 500: error is 0.154502 (50 iterations in 1.18 seconds)
Iteration 550: error is 0.154393 (50 iterations in 1.19 seconds)
Iteration 600: error is 0.154297 (50 iterations in 1.18 seconds)
Iteration 650: error is 0.154556 (50 iterations in 1.16 seconds)
Iteration 700: error is 0.155218 (50 iterations in 1.17 seconds)
Iteration 750: error is 0.154701 (50 iterations in 1.18 seconds)
Iteration 800: error is 0.154848 (50 iterations in 1.20 seconds)
Iteration 850: error is 0.155505 (50 iterations in 1.21 seconds)
Iteration 900: error is 0.155638 (50 iterations in 1.20 seconds)
Iteration 950: error is 0.155412 (50 iterations in 1.21 seconds)
Iteration 1000: error is 0.155655 (50 iterations in 1.19 seconds)
Fitting performed in 25.22 seconds.
delta norm ... done
x <- tSNE.velocity.plot(gvel,nPcs=15,cell.colors=cell.colors,cex=0.9,perplexity=200,norm.nPcs=NA,pcount=0.1,scale='log',do.par=F,main='gene-structure estimate')
rescaling ... log ... pca ... delta norm ... tSNE ...Read the 768 x 15 data matrix successfully!
OpenMP is working...
Using no_dims = 2, perplexity = 200.000000, and theta = 0.500000
Computing input similarities...
Normalizing input...
Building tree...
Done in 1.29 seconds (sparsity = 0.905877)!
Learning embedding...
Iteration 50: error is 45.043481 (50 iterations in 1.33 seconds)
Iteration 100: error is 45.043481 (50 iterations in 1.32 seconds)
Iteration 150: error is 45.043481 (50 iterations in 1.25 seconds)
Iteration 200: error is 45.043481 (50 iterations in 1.27 seconds)
Iteration 250: error is 45.043481 (50 iterations in 1.26 seconds)
Iteration 300: error is 0.172803 (50 iterations in 1.17 seconds)
Iteration 350: error is 0.156917 (50 iterations in 1.28 seconds)
Iteration 400: error is 0.157191 (50 iterations in 1.50 seconds)
Iteration 450: error is 0.157446 (50 iterations in 1.17 seconds)
Iteration 500: error is 0.156811 (50 iterations in 1.11 seconds)
Iteration 550: error is 0.156514 (50 iterations in 1.27 seconds)
Iteration 600: error is 0.156040 (50 iterations in 1.12 seconds)
Iteration 650: error is 0.156383 (50 iterations in 1.11 seconds)
Iteration 700: error is 0.155723 (50 iterations in 1.08 seconds)
Iteration 750: error is 0.155851 (50 iterations in 0.89 seconds)
Iteration 800: error is 0.156389 (50 iterations in 1.06 seconds)
Iteration 850: error is 0.156084 (50 iterations in 1.04 seconds)
Iteration 900: error is 0.156497 (50 iterations in 1.06 seconds)
Iteration 950: error is 0.156577 (50 iterations in 1.01 seconds)
Iteration 1000: error is 0.157346 (50 iterations in 1.06 seconds)
Fitting performed in 23.33 seconds.
delta norm ... done

#dev.off()

Cell trajectory modeling

A similar function can be used to model central trajectories by directed diffusion on embedding. The main parameters are set up by sigma (which limits the range of how far a cell can jump in terms of distance) and n (how many nearest neighbors are being considered for jumps). The results are sensitive to these parameters, as we don’t have a good way of assessing how much the directional velocity component should compare with random Brownian motion of a cell with the manifold. For instance, relaxing (increasing) sigma, in particular will eventually lead to sympathoblast cells “jumping” the gap into the into the chromaffin differentiation part.

Warning: this simulation takes some time (e.g. a couple of minutes on 40 cores).

x <- show.velocity.on.embedding.eu(emb,rvel,n=40,scale='sqrt',cell.colors=ac(cell.colors,alpha=cell.alpha),cex=cell.cex,nPcs=30,sigma=2.5,show.trajectories=TRUE,diffusion.steps=400,n.trajectory.clusters=15,ntop.trajectories=1,embedding.knn=T,control.for.neighborhood.density=TRUE,n.cores=40) 
sqrt scale ... reducing to 30 PCs ... distance ... sigma= 2.5  beta= 1  transition probs ... embedding kNN ... done
simulating diffusion ... constructing path graph ... tracing shortest trajectories ... clustering ... done.

LS0tCnRpdGxlOiAiQ2hyb21hZmZpbiAgZGlmZmVyZW50aWF0aW9uIGFuYWx5c2lzIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKLS0tCgpUaGUgbm90ZWJvb2sgc2hvd3MgYW4gZXhhbXBsZSBvZiBiYXNpYyB2ZWxvY2l0eSBhbmxheXNpcyBvZiBFMTIuNSBjaHJvbWFmZmluIGRhdGEsIHdoaWNoIGlzIGEgU01BUlQtc2VxMiBkYXRhc2V0LgpJdCBzaG93cyBob3cgdG8gZXN0aW1hdGUgZ2VuZS1yZWxhdGl2ZSB2ZWxvY2l0eSAod2l0aCBkaWZmZXJlbnQgcG9vbGluZyBvcHRpb25zKSwgYXMgd2VsbCBhcyBob3cgdG8gZXN0aW1hdGUgdXNpbmcgYWx0ZXJuYXRpdmUgZ2VuZS1zdHJ1Y3R1cmUgYmFzZWQgbW9kZWwuCgojIyBEYXRhIHByZS1wcm9jZXNzaW5nCldlIGFzc3VtZSB0aGF0IHRoZSBTTUFSVC1zZXEyIGRhdGEgaGFzIGJlZW4gZGVtdWx0aXBsZXhlZCBhbmQgYWxpZ25lZCwgc28gdGhhdCB3ZSBoYXZlIG9uZSBiYW0gZmlsZSBwZXIgY2VsbC4KVGhlIHJlY29tbWVuZGVkIHByb2Nlc3Npbmcgc3RlcCBpcyB0aGVuIHRvIHVzZSBgdmVsb2N5dG8ucHlgIGNvbW1hbmQgbGluZSB0b29sIHRvIGFubm90YXRlIHNwbGljZWQsIHVuc3BsaWNlZCBhbmQgc3Bhbm5pbmcgcmVhZHMgaW4gdGhlIG1lYXN1cmVkIGNlbGxzOgpgYGB7ZXZhbD1GQUxTRX0KdmVsb2N5dG8gcnVuX3NtYXJ0c2VxMiAtZCAxIGBmaW5kIC4vIGRhdGEvZTEyLjUuYmFtcy8gLW5hbWUgIiouYmFtIiAtcHJpbnRgIG1tMTAuZ2VuZXMuZ3RmCmBgYApJZiB5b3Ugd2FudCB0byByZS1ydW4gdGhpcyBjb21tYW5kLCBwbGVhc2UgZG93bmxvYWQgYmFtIGZpbGVzIChhbmQgZ2VuZXMucmVmRmxhdCkgZnJvbSBbaGVyZV0oaHR0cDovL3BrbGFiLm1lZC5oYXJ2YXJkLmVkdS92ZWxvY3l0by9jaHJvbWFmZmluL2JhbXMudGFyKSwgYW5kIGV4dHJhY3QgaXQgKGB0YXIgeHZmIGJhbXMudGFyYCkgaW4gdGhlIHdvcmtpbmcgZGlyZWN0b3J5LiBub3RlOiB0aGUgZmlsZSBpcyBmYWlybHkgbGFyZ2UgLSA1LjYgR0IhIApSdW5uaW5nIHRoaXMgY29tbWFuZCB3aWxsIHByb2R1Y2UgYSB2ZWxvY3l0byBkaXJlY3Rvcnkgd2l0aCB0d28gZmlsZXM6CgoxLiBgb25lZmlsZXBlcmNlbGxfQTFfdW5pcXVlX2FuZF9vdGhlcnNfSjJDSDEubG9vbWAgLSBhIGxvb20gZmlsZSBjb250YWluaW5nIHNwbGljZWQsIHVuc3BsaWNlZCBhbmQgc3Bhbm5lZCBtYXRyaWNlcwoyLiBgZHVtcC9vbmVmaWxlcGVyY2VsbF9BMV91bmlxdWVfYW5kX290aGVyc19KMkNIMS5oZGY1YCAtIGFuIEhERjUgZmlsZSBjb250YWluaW5nIGRldGFpbGVkIG1vbGVjdWxhciBtYXBwaW5nIGluZm9ybWF0aW9uIHdoaWNoIHdlIHdpbGwgdXNlIHRvIGVzdGltYXRlIHBhcmFtZXRlcnMgZm9yIHRoZSBnZW5lLXN0cnVjdHVyZSBiYXNlZCBtb2RlbC4gWW91IGRvbid0IG5lZWQgdGhpcyBmb3IgcmVndWxhciB2ZWxvY2l0eSBlc3RpbWF0aW9uLCBhbmQgaW4gdGhpcyBjYXNlIGAtZCAxYCBvcHRpb24gbWF5IGJlIHNraXBwZWQgd2hlbiBydW5uaW5nIHZlbG9jeXRvLnB5Cgpub3RlOiBpZiB5b3UgZG9uJ3Qgd2FudCB0byByZS1ydW4gdGhlIGNvbW1hbmQsIHlvdSBjYW4gZG93bmxvYWQgdGhlIHZlbG9jeXRvLnB5IG91dHB1dCBmb2xkZXIgW2hlcmVdKGh0dHA6Ly9wa2xhYi5tZWQuaGFydmFyZC5lZHUvdmVsb2N5dG8vY2hyb21hZmZpbi92ZWxvY3l0by50YXIpCgojIyBEYXRhIGxvYWRpbmcKCkxvYWQgdGhlIHZlbG9jeXRvIHBhY2thZ2U6CmBgYHtyfQpsaWJyYXJ5KHZlbG9jeXRvLlIpCmBgYAoKTG9hZCB0aGUgZGF0YSBhbmQgZ2Vub21lIGFubm90YXRpb25zOgoKUmVhZCBpbiBjb3VudCBtYXRyaWNlcyBmcm9tIHRoZSBsb29tIGZpbGU6CgpgYGB7ciBldmFsPUZBTFNFfQpsZGF0IDwtIHJlYWQubG9vbS5tYXRyaWNlcygiZGF0YS92ZWxvY3l0by9vbmVmaWxlcGVyY2VsbF9BMV91bmlxdWVfYW5kX290aGVyc19KMkNIMS5sb29tIikKYGBgCgooaW5zdGVhZCwgZm9yIHRoZSBwdXJwb3NlcyBvZiB0aGUgZXhhbXBsZSB3ZSdsbCBqdXN0IHJlYWQgaW4gdGhlIHJlc3VsdGluZyBzdHJ1Y3R1cmUgZnJvbSB0aGUgcmRzIGZpbGUpCmBgYHtyfQpsZGF0IDwtIHJlYWRSRFModXJsKCJodHRwOi8vcGtsYWIubWVkLmhhcnZhcmQuZWR1L3ZlbG9jeXRvL2Nocm9tYWZmaW4vbGRhdC5yZHMiKSkKc3RyKGxkYXQpCmBgYAoKCkxldCdzIHJlZHVjZSB0aGUgY2VsbCBuYW1lcyB0byB0aGUgc2hvcnQgd2VsbCBsYWJlbHM6CmBgYHtyfQpsZGF0IDwtIGxhcHBseShsZGF0LGZ1bmN0aW9uKHgpIHsKICBjb2xuYW1lcyh4KSA8LSAgZ3N1YigiX3VuaXF1ZS5iYW0iLCIiLGdzdWIoIi4qOiIsIiIsY29sbmFtZXMoeCkpKQogIHgKfSkKYGBgCgpSZWFkIGluIGNlbGwgY2x1c3RlciBhc3NpZ25tZW50IGFuZCB0U05FIGVtYmVkZGluZyB1c2VkIGluIHRoZSBGdXJsYW4gZXQgYWwuIChTY2llbmNlJzE3KS4KYGBge3J9CmNlbGwuY29sb3JzIDwtIHJlYWRSRFModXJsKCJodHRwOi8vcGtsYWIubWVkLmhhcnZhcmQuZWR1L3ZlbG9jeXRvL2Nocm9tYWZmaW4vY2VsbC5jb2xvcnMucmRzIikpCmVtYiA8LSByZWFkUkRTKHVybCgiaHR0cDovL3BrbGFiLm1lZC5oYXJ2YXJkLmVkdS92ZWxvY3l0by9jaHJvbWFmZmluL2VtYmVkZGluZy5yZHMiKSkKYGBgCgojIyBHZW5lIGZpbHRlcmluZwpTcGxpY2VkIGV4cHJlc3Npb24gbWFnbml0dWRlIGRpc3RyaWJ1dGlvbiBhY3Jvc3MgZ2VuZXM6CmBgYHtyfQpoaXN0KGxvZzEwKHJvd1N1bXMobGRhdCRzcGxpY2VkKSsxKSxjb2w9J3doZWF0Jyx4bGFiPSdsb2cxMFsgbnVtYmVyIG9mIHJlYWRzICsgMV0nLG1haW49J251bWJlciBvZiByZWFkcyBwZXIgZ2VuZScpCmBgYAoKClNldCB1cCBleHByZXNzaW9uIG1hdHJpY2VzLCBmaWx0ZXJpbmcgZ2VuZXMgdG8gbGVhdmUgdGhvc2UgdGhhdCBleGNlZWQgc29tZSBwcmUtZGVmaW5lZCBnIHRvIHRoZSBhdmVyYWdlIGV4cHJlc3Npb24gbWFnbml0dWRlCmBgYHtyfQojIGV4b25pYyByZWFkIChzcGxpY2VkKSBleHByZXNzaW9uIG1hdHJpeAplbWF0IDwtIGxkYXQkc3BsaWNlZDsKIyBpbnRyb25pYyByZWFkICh1bnNwbGljZWQpIGV4cHJlc3Npb24gbWF0cml4Cm5tYXQgPC0gbGRhdCR1bnNwbGljZWQKIyBzcGFubmluZyByZWFkIChpbnRyb24rZXhvbikgZXhwcmVzc2lvbiBtYXRyaXgKc21hdCA8LSBsZGF0JHNwYW5uaW5nOwojIGZpbHRlciBleHByZXNzaW9uIG1hdHJpY2VzIGJhc2VkIG9uIHNvbWUgbWluaW11bSBtYXgtY2x1c3RlciBhdmVyYWdlcwplbWF0IDwtIGZpbHRlci5nZW5lcy5ieS5jbHVzdGVyLmV4cHJlc3Npb24oZW1hdCxjZWxsLmNvbG9ycyxtaW4ubWF4LmNsdXN0ZXIuYXZlcmFnZSA9IDUpCm5tYXQgPC0gZmlsdGVyLmdlbmVzLmJ5LmNsdXN0ZXIuZXhwcmVzc2lvbihubWF0LGNlbGwuY29sb3JzLG1pbi5tYXguY2x1c3Rlci5hdmVyYWdlID0gMSkKc21hdCA8LSBmaWx0ZXIuZ2VuZXMuYnkuY2x1c3Rlci5leHByZXNzaW9uKHNtYXQsY2VsbC5jb2xvcnMsbWluLm1heC5jbHVzdGVyLmF2ZXJhZ2UgPSAwLjUpCgojIGxvb2sgYXQgdGhlIHJlc3VsdGluZyBnZW5lIHNldApsZW5ndGgoaW50ZXJzZWN0KHJvd25hbWVzKGVtYXQpLHJvd25hbWVzKG5tYXQpKSkKIyBhbmQgaWYgd2UgdXNlIHNwYW5uaW5nIHJlYWRzIChzbWF0KQpsZW5ndGgoaW50ZXJzZWN0KGludGVyc2VjdChyb3duYW1lcyhlbWF0KSxyb3duYW1lcyhubWF0KSkscm93bmFtZXMoc21hdCkpKQoKYGBgCgojIyBTZXZlcmFsIHZhcmlhbnRzIG9mIHZlbG9jaXR5IGVzdGltYXRlcyB1c2luZyBnZW5lLXJlbGF0aXZlIG1vZGVsCgpXZSdsbCBzdGFydCB3aXRoIHdoYXQgaXMgcGVyaGFwcyB0aGUgbW9zdCByb2J1c3QgZXN0aW1hdGUsIHRoYXQgY29tYmluZXMgY2VsbCBrTk4gcG9vbGluZyB3aXRoIHRoZSBnYW1tYSBmaXQgYmFzZWQgb24gYW4gZXh0cmVtZSBxdWFudGlsZXM6CgpVc2luZyBtaW4vbWF4IHF1YW50aWxlIGZpdCwgaW4gd2hpY2ggY2FzZSBnZW5lLXNwZWNpZmljIG9mZnNldHMgZG8gbm90IHJlcXVpcmUgc3Bhbm5pbmcgcmVhZCAoc21hdCkgZml0LgpIZXJlIHRoZSBmaXQgaXMgYmFzZWQgb24gdGhlIHRvcC9ib3R0b20gNSUgb2YgY2VsbHMgKGJ5IHNwbGljZWQgZXhwcmVzc2lvbiBtYWduaXR1ZGUpLiAKYGBge3J9CmZpdC5xdWFudGlsZSA8LSAwLjA1OwpydmVsLnFmIDwtIGdlbmUucmVsYXRpdmUudmVsb2NpdHkuZXN0aW1hdGVzKGVtYXQsbm1hdCxkZWx0YVQ9MSxrQ2VsbHMgPSA1LGZpdC5xdWFudGlsZSA9IGZpdC5xdWFudGlsZSkKYGBgCgpXZSB2aXN1YWxpemUgdGhlIHZlbG9jaXRpZXMgYnkgcHJvamVjdGluZyBvYnNlcnZlZCBhbmQgZXh0cmFwb2xhdGVkIGNlbGxzIG9udG8gdGhlIGZpcnN0IDUgUENzOgpgYGB7ciBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD04fQpwY2EudmVsb2NpdHkucGxvdChydmVsLnFmLG5QY3M9NSxwbG90LmNvbHM9MixjZWxsLmNvbG9ycz1hYyhjZWxsLmNvbG9ycyxhbHBoYT0wLjcpLGNleD0xLjIscGNvdW50PTAuMSxwYy5tdWx0aXBsaWVycz1jKDEsLTEsLTEsLTEsLTEpKQpgYGAKCgpGaXR0aW5nIG9mIGluZGl2aWR1YWwgZ2VuZXMgY2FuIGJlIHZpc3VhbGl6ZWQgdXNpbmcgInNob3cuZ2VuZSIgb3B0aW9uLiBUbyBzYXZlIHRpbWUsIHdlJ2xsIHBhc3MgcHJldmlvdXNseS1jYWxjdWxhdGVkIHZlbG9jaXR5IChydmVsLnFmKSB0byBzYXZlIGNhbGN1bGF0aW9uIHRpbWU6CmBgYHtyIGZpZy53aWR0aD04LGZpZy5oZWlnaHQ9Mi4yfQojIGRlZmluZSBjdXN0b20gcGFsbGV0IGZvciBleHByZXNzaW9uIG1hZ25pdHVkZQpnZW5lLnJlbGF0aXZlLnZlbG9jaXR5LmVzdGltYXRlcyhlbWF0LG5tYXQsIGtDZWxscyA9IDUsZml0LnF1YW50aWxlID0gZml0LnF1YW50aWxlLG9sZC5maXQ9cnZlbC5xZixzaG93LmdlbmU9J0NoZ2EnLGNlbGwuZW1iPWVtYixjZWxsLmNvbG9ycz1jZWxsLmNvbG9ycykKYGBgCgoKCgoKQWx0ZXJuYXRpdmVseSwgd2VuIGNhIHVzZSBzcGFubmluZyByZWFkcyAoc21hdCkgdG8gZml0IHRoZSBnZW5lIG9mZnNldHMuIFRoaXMgd2lsbCByZXN1bHQgaW4gbW9yZSBhY2N1cmF0ZSBvZmZzZXQgZXN0aW1hdGVzLCBidXQgZm9yIG11Y2ggZmV3ZXIgZ2VuZXMgKHNwYW5uaW5nIHJlYWRzIGFyZSByYXJlKS4gTm90ZSB0aGF0IGhlcmUgd2UgYWxzbyB1c2Ugb3B0aW9uYWwgYGRpYWdvbmEucXVhbnRpbGVzYCBzZXR0aW5nIHRvIGVzdGltYXRlIGV4dHJlbWUgcXVhbnRpbGVzIG9uIGEgbm9ybWFsaXplZCBzdW0gb2Ygc3BsY2llZCBhbmQgdW5zcGxpY2VkIHNpZ25hbDoKYGBge3J9CnJ2ZWwgPC0gZ2VuZS5yZWxhdGl2ZS52ZWxvY2l0eS5lc3RpbWF0ZXMoZW1hdCxubWF0LHNtYXQ9c21hdCwga0NlbGxzID0gNSwgZml0LnF1YW50aWxlPWZpdC5xdWFudGlsZSwgZGlhZ29uYWwucXVhbnRpbGVzID0gVFJVRSkKYGBgCgpXZSBjYW4gdmlzdWFsaXplIHRoZSB2ZWxvY2l0eSBpbiBQQ0Egc3BhY2U6CmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9CnBjYS52ZWxvY2l0eS5wbG90KHJ2ZWwsblBjcz01LHBsb3QuY29scz0yLGNlbGwuY29sb3JzPWFjKGNlbGwuY29sb3JzLGFscGhhPTAuNyksY2V4PTEuMixwY291bnQ9MC4xLHBjLm11bHRpcGxpZXJzPWMoMSwtMSwxLDEsMSkpCmBgYAoKSGVyZSB3ZSBjYWxjdWxhdGUgdGhlIG1vc3QgYmFzaWMgdmVyc2lvbiBvZiB2ZWxvY2l0eSBlc3RpbWF0ZXMsIHVzaW5nIHJlbGF0aXZlIGdhbW1hIGZpdCwgd2l0aG91dCBjZWxsIGtOTiBzbW9vdGhpbmcgKGkuZS4gYWN0dWFsIHNpbmdsZS1jZWxsIHZlbG9jaXR5KToKYGBge3J9CnJ2ZWwxIDwtIGdlbmUucmVsYXRpdmUudmVsb2NpdHkuZXN0aW1hdGVzKGVtYXQsbm1hdCxkZWx0YVQ9MSxkZWx0YVQyID0gMSxrQ2VsbHMgPSAxLCBmaXQucXVhbnRpbGU9Zml0LnF1YW50aWxlKQpgYGAKCmBgYHtyIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9CnBjYS52ZWxvY2l0eS5wbG90KHJ2ZWwxLG5QY3M9NSxwbG90LmNvbHM9MixjZWxsLmNvbG9ycz1hYyhjZWxsLmNvbG9ycyxhbHBoYT0wLjcpLGNleD0xLjIscGNvdW50PTAuMSxwYy5tdWx0aXBsaWVycz1jKDEsLTEsMSwxLDEpKQpgYGAKCiMjIyBWaXN1YWxpemF0aW9uIG9uIGFuIGV4aXN0aW5nIGVtYmVkZGluZwoKSGVyZSB3ZSB1c2UgdC1TTkUgZW1iZWRkaW5nIGZyb20gdGhlIG9yaWdpbmFsIHB1YmxpY2F0aW9uIChpbiBlbWIgdmFyaWFibGUpLgoKYGBge3IgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NX0KdmVsIDwtIHJ2ZWw7IGFycm93LnNjYWxlPTM7IGNlbGwuYWxwaGE9MC40OyBjZWxsLmNleD0xOyBmaWcuaGVpZ2h0PTQ7IGZpZy53aWR0aD00LjU7CnNob3cudmVsb2NpdHkub24uZW1iZWRkaW5nLmNvcihlbWIsdmVsLG49MTAwLHNjYWxlPSdzcXJ0JyxjZWxsLmNvbG9ycz1hYyhjZWxsLmNvbG9ycyxhbHBoYT1jZWxsLmFscGhhKSxjZXg9Y2VsbC5jZXgsYXJyb3cuc2NhbGU9YXJyb3cuc2NhbGUsYXJyb3cubHdkPTEpCmBgYAoKQWx0ZXJuYXRpdmVseSwgdGhlIHNhbWUgZnVuY3Rpb24gY2FuIGJlIHVzZWQgdG8gY2FsY3VsYXRlIGEgdmVsb2NpdHkgdmVjdG9yIGZpZWxkOgpgYGB7ciBmaWcud2lkdGg9NywgZmlnLmhlaWdodD01fQpzaG93LnZlbG9jaXR5Lm9uLmVtYmVkZGluZy5jb3IoZW1iLHZlbCxuPTEwMCxzY2FsZT0nc3FydCcsY2VsbC5jb2xvcnM9YWMoY2VsbC5jb2xvcnMsYWxwaGE9Y2VsbC5hbHBoYSksY2V4PWNlbGwuY2V4LGFycm93LnNjYWxlPWFycm93LnNjYWxlLHNob3cuZ3JpZC5mbG93PVRSVUUsbWluLmdyaWQuY2VsbC5tYXNzPTAuNSxncmlkLm49MjAsYXJyb3cubHdkPTIpCmBgYAoKIyMgVmVsb2NpdHkgZXN0aW1hdGUgYmFzZWQgb24gZ2VuZSBzdHJ1Y3R1cmUKClRvIGVzdGltYXRlIHZlbG9jaXR5IGJhc2VkIG9uIGdlbmUgc3RydWN0dXJlIHBhcmFtZXRlcnMsIHdlIG5lZWQgdG8gcGFyc2UgdGhlIGd0ZiBmaWxlIGFzIHdlbGwgYXMgdGhlIGRlYnVnIGhkZjUgb3V0cHV0IG9mIHZlbG9jeXRvLnB5IChgLWRgIG9wdGlvbikgd2hpY2ggY29udGFpbnMgcGVyLWV4b24gbWFwcGluZyBpbmZvcm1hdGlvbi4KWW91IGNhbiBmZXRjaCB0aGUgZXhhY3QgZ3RmIGZpbGUgdXNlZCBbaGVyZV0oaHR0cDovL3BrbGFiLm1lZC5oYXJ2YXJkLmVkdS92ZWxvY3l0by9jaHJvbWFmZmluL2dlbmVzLmd0Zi5neikuCkZpcnN0LCBob3dldmVyLCB3ZSB3aWxsIGNvbXBpbGUgaW5mb3JtYXRpb24gb24gdGhlIGludGVybmFsIHByaW1pbmcgc2l0ZXMgZXhwZWN0ZWQgaW4gdGhpcyBnZW5vbWUgKG1vdXNlIFVDU0MgbW0xMCBhc3NlbWJseSk6CmBgYHtyIGV2YWw9RkFMU0V9CnJlcXVpcmUoQlNnZW5vbWUuTW11c2N1bHVzLlVDU0MubW0xMCkKaXAubW0xMCA8LSBmaW5kLmlwLnNpdGVzKCdkYXRhL2dlbmVzLmd0ZicsTW11c2N1bHVzLCdtbTEwJykKYGBgClRoaXMgbmVlZHMgdG8gYmUgcmFuIG9uY2UgcGVyIGdlbm9tZSwgYW5kIGNhbiBiZSBzYXZlZCBmb3IgdGhlIGZ1dHVyZS4gVG8gc2tpcCBjYWxjdWxhdGlvbnMsIHdlIGp1c3QgbG9hZCB0aGUgcHJlLWNhbGN1bGF0ZWQgaW50ZXJuYWwgcHJpbWluZyBpbmZvIGZyb20gYW4gcmRzIGZpbGU6CmBgYHtyfQppcC5tbTEwIDwtIHJlYWRSRFModXJsKCJodHRwOi8vcGtsYWIubWVkLmhhcnZhcmQuZWR1L3ZlbG9jeXRvL2Nocm9tYWZmaW4vaXAubW0xMC5yZHMiKSkKYGBgCgpOb3cgd2Ugd2lsbCByZWFkIGB2ZWxvY3l0by5weWAgSERGNSBvdXRwdXQ6CmBgYHtyIGV2YWw9RkFMU0V9CmdlbmUuaW5mbyA8LSByZWFkLmdlbmUubWFwcGluZy5pbmZvKCJkYXRhL3ZlbG9jeXRvL2R1bXAvb25lZmlsZXBlcmNlbGxfQTFfdW5pcXVlX2FuZF9vdGhlcnNfSjJDSDEuaGRmNSIsaW50ZXJuYWwucHJpbWluZy5pbmZvPWlwLm1tMTAsbWluLmV4b24uY291bnQ9MTApOwpgYGAKV2lsbCBsb2FkIHByZS1jYWxjdWxhdGVkIHJlc3VsdCBpbnN0ZWFkIG9mIGV2YWx1YXRpbmcgYWJvdmU6ClRoaXMgbmVlZHMgdG8gYmUgcmFuIG9uY2UgcGVyIGdlbm9tZSwgYW5kIGNhbiBiZSBzYXZlZCBmb3IgdGhlIGZ1dHVyZS4gVG8gc2tpcCBjYWxjdWxhdGlvbnMsIHdlIGp1c3QgbG9hZCB0aGUgcHJlLWNhbGN1bGF0ZWQgaW50ZXJuYWwgcHJpbWluZyBpbmZvIGZyb20gYW4gcmRzIGZpbGU6CmBgYHtyfQpnZW5lLmluZm8gPC0gcmVhZFJEUyh1cmwoImh0dHA6Ly9wa2xhYi5tZWQuaGFydmFyZC5lZHUvdmVsb2N5dG8vY2hyb21hZmZpbi9nZW5lLmluZm8ucmRzIikpCmBgYAoKR2Vub21lLXdpZGUgbW9kZWwgZml0OgpgYGB7ciB3YXJuaW5nPUZBTFNFfQojIHN0YXJ0IHdpdGggdW5maWx0ZXJlZCBtYXRyaWNlcywgYXMgd2UgY2FuIHVzZSBtb3JlIGdlbmVzIGluIHRoZXNlIHR5cGVzIG9mIGVzdGltYXRlcwplbWF0IDwtIGxkYXQkc3BsaWNlZDsgbm1hdCA8LSBsZGF0JHVuc3BsaWNlZDsgc21hdCA8LSBsZGF0JHNwYW5uaW5nCmVtYXQgPC0gZmlsdGVyLmdlbmVzLmJ5LmNsdXN0ZXIuZXhwcmVzc2lvbihlbWF0LGNlbGwuY29sb3JzLG1pbi5tYXguY2x1c3Rlci5hdmVyYWdlID0gNykKZ3ZlbCA8LSBnbG9iYWwudmVsY29pdHkuZXN0aW1hdGVzKGVtYXQsIG5tYXQsIHJ2ZWwsIGJhc2UuZGY9Z2VuZS5pbmZvJGdlbmUuZGYsIHNtYXQ9c21hdCwgZGVsdGFUPTEsIGtDZWxscz01LCBrR2VuZXMgPSAxNSwga0dlbmVzLnRyaW0gPSA1LCBtaW4uZ2VuZS5jZWxscyA9IDAsIG1pbi5nZW5lLmNvbnV0cyA9IDUwMCkKYGBgCgoKYGBge3IgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9OH0KcGNhLnZlbG9jaXR5LnBsb3QoZ3ZlbCxuUGNzPTUscGxvdC5jb2xzPTIsY2VsbC5jb2xvcnM9YWMoY2VsbC5jb2xvcnMsYWxwaGE9MC43KSxjZXg9MS4yLHBjb3VudD0wLjEscGMubXVsdGlwbGllcnM9YygxLC0xLC0xLDEsMSkpCmBgYAoKCldlIGNhbiB2aXN1YWxpemUgdGhlIHR3byBlc3RpbWF0ZXMgc2lkZS1ieSBzaWRlIG9uIGEgcHJlLWNhbGN1bGF0ZWQgKHB1Ymxpc2hlZCkgdFNORSBlbWJlZGRpbmc6CgpgYGB7ciBmaWcud2lkdGg9MTAsIGZpZy5oZWlnaHQ9NX0KcGFyKG1mcm93PWMoMSwyKSwgbWFyID0gYygyLjUsMi41LDIuNSwxLjUpLCBtZ3AgPSBjKDIsMC42NSwwKSwgY2V4ID0gMC44NSk7CmFycm93LnNjYWxlPTM7IGNlbGwuYWxwaGE9MC40OyBjZWxsLmNleD0xOyBmaWcuaGVpZ2h0PTQ7IGZpZy53aWR0aD00LjU7CiNwZGYoZmlsZT0ndHNuZS5ydmVsX2d2ZWwucGxvdHMucGRmJyxoZWlnaHQ9Nix3aWR0aD0xMikKc2hvdy52ZWxvY2l0eS5vbi5lbWJlZGRpbmcuY29yKGVtYixydmVsLG49MTAwLHNjYWxlPSdzcXJ0JyxjZWxsLmNvbG9ycz1hYyhjZWxsLmNvbG9ycyxhbHBoYT1jZWxsLmFscGhhKSxjZXg9Y2VsbC5jZXgsYXJyb3cuc2NhbGU9YXJyb3cuc2NhbGUsYXJyb3cubHdkPTEsbWFpbj0nZ2VuZS1yZWxhdGl2ZSBlc2l0bWF0ZScsZG8ucGFyPUYpCnNob3cudmVsb2NpdHkub24uZW1iZWRkaW5nLmNvcihlbWIsZ3ZlbCxuPTEwMCxzY2FsZT0nc3FydCcsY2VsbC5jb2xvcnM9YWMoY2VsbC5jb2xvcnMsYWxwaGE9Y2VsbC5hbHBoYSksY2V4PWNlbGwuY2V4LGFycm93LnNjYWxlPWFycm93LnNjYWxlLGFycm93Lmx3ZD0xLG1haW49J2dlbmUtc3RydWN0dXJlIGVzdGltYXRlJyxkby5wYXI9RikKI2Rldi5vZmYoKQpgYGAKCk9yIGJ5IGpvaW50bHkgZW1iZWRkaW5nIGJvdGggb2JzZXJ2ZWQgYW5kIGV4dHJhcG9sYXRlZCBjZWxscyBpbnRvIHRoZSBzYW1lIHRTTkUgc3BhY2UKYGBge3IgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTV9CiNwZGYoZmlsZT0ndHNuZS5zaGlmdC5wbG90cy5wZGYnLGhlaWdodD02LHdpZHRoPTEyKQpwYXIobWZyb3c9YygxLDIpLCBtYXIgPSBjKDIuNSwyLjUsMi41LDEuNSksIG1ncCA9IGMoMiwwLjY1LDApLCBjZXggPSAwLjg1KTsKeCA8LSB0U05FLnZlbG9jaXR5LnBsb3QocnZlbCxuUGNzPTE1LGNlbGwuY29sb3JzPWNlbGwuY29sb3JzLGNleD0wLjkscGVycGxleGl0eT0yMDAsbm9ybS5uUGNzPU5BLHBjb3VudD0wLjEsc2NhbGU9J2xvZycsZG8ucGFyPUYsbWFpbj0nZ2VuZS1yZWxhdGl2ZSBlc3RpbWF0ZScpCnggPC0gdFNORS52ZWxvY2l0eS5wbG90KGd2ZWwsblBjcz0xNSxjZWxsLmNvbG9ycz1jZWxsLmNvbG9ycyxjZXg9MC45LHBlcnBsZXhpdHk9MjAwLG5vcm0ublBjcz1OQSxwY291bnQ9MC4xLHNjYWxlPSdsb2cnLGRvLnBhcj1GLG1haW49J2dlbmUtc3RydWN0dXJlIGVzdGltYXRlJykKI2Rldi5vZmYoKQpgYGAKCgoKCgojIyBDZWxsIHRyYWplY3RvcnkgbW9kZWxpbmcKQSBzaW1pbGFyIGZ1bmN0aW9uIGNhbiBiZSB1c2VkIHRvIG1vZGVsIGNlbnRyYWwgdHJhamVjdG9yaWVzIGJ5IGRpcmVjdGVkIGRpZmZ1c2lvbiBvbiBlbWJlZGRpbmcuIApUaGUgbWFpbiBwYXJhbWV0ZXJzIGFyZSBzZXQgdXAgYnkgc2lnbWEgKHdoaWNoIGxpbWl0cyB0aGUgcmFuZ2Ugb2YgaG93IGZhciBhIGNlbGwgY2FuIGp1bXAgaW4gdGVybXMgb2YgZGlzdGFuY2UpIGFuZCBuIChob3cgbWFueSBuZWFyZXN0IG5laWdoYm9ycyBhcmUgYmVpbmcgY29uc2lkZXJlZCBmb3IganVtcHMpLiBUaGUgcmVzdWx0cyBhcmUgc2Vuc2l0aXZlIHRvIHRoZXNlIHBhcmFtZXRlcnMsIGFzIHdlIGRvbid0IGhhdmUgYSBnb29kIHdheSBvZiBhc3Nlc3NpbmcgaG93IG11Y2ggdGhlIGRpcmVjdGlvbmFsIHZlbG9jaXR5IGNvbXBvbmVudCBzaG91bGQgY29tcGFyZSB3aXRoIHJhbmRvbSBCcm93bmlhbiBtb3Rpb24gb2YgYSBjZWxsIHdpdGggdGhlIG1hbmlmb2xkLiBGb3IgaW5zdGFuY2UsIHJlbGF4aW5nIChpbmNyZWFzaW5nKSBzaWdtYSwgaW4gcGFydGljdWxhciB3aWxsIGV2ZW50dWFsbHkgbGVhZCB0byBzeW1wYXRob2JsYXN0IGNlbGxzICJqdW1waW5nIiB0aGUgZ2FwIGludG8gdGhlIGludG8gdGhlIGNocm9tYWZmaW4gZGlmZmVyZW50aWF0aW9uIHBhcnQuCgpXYXJuaW5nOiB0aGlzIHNpbXVsYXRpb24gdGFrZXMgc29tZSB0aW1lIChlLmcuIGEgY291cGxlIG9mIG1pbnV0ZXMgb24gNDAgY29yZXMpLgoKYGBge3IgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NX0KeCA8LSBzaG93LnZlbG9jaXR5Lm9uLmVtYmVkZGluZy5ldShlbWIscnZlbCxuPTQwLHNjYWxlPSdzcXJ0JyxjZWxsLmNvbG9ycz1hYyhjZWxsLmNvbG9ycyxhbHBoYT1jZWxsLmFscGhhKSxjZXg9Y2VsbC5jZXgsblBjcz0zMCxzaWdtYT0yLjUsc2hvdy50cmFqZWN0b3JpZXM9VFJVRSxkaWZmdXNpb24uc3RlcHM9NDAwLG4udHJhamVjdG9yeS5jbHVzdGVycz0xNSxudG9wLnRyYWplY3Rvcmllcz0xLGVtYmVkZGluZy5rbm49VCxjb250cm9sLmZvci5uZWlnaGJvcmhvb2QuZGVuc2l0eT1UUlVFLG4uY29yZXM9NDApIAoKYGBgCgo=