projDir <- "/mnt/scratcha/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020"
outDirBit <- "AnaWiSce/Attempt1"
nbPcToComp <- 50

1 Data integration

Source: Integrating Datasets of the OSCA book and the fastMNN manual.

1.1 Motivation

Large single-cell RNA sequencing (scRNA-seq) projects usually need to generate data across multiple batches due to logistical constraints. However, the processing of different batches is often subject to uncontrollable differences, e.g., changes in operator, differences in reagent quality. This results in systematic differences in the observed expression in cells from different batches, which we refer to as “batch effects”. Batch effects are problematic as they can be major drivers of heterogeneity in the data, masking the relevant biological differences and complicating interpretation of the results.

Computational correction of these effects is critical for eliminating batch-to-batch variation, allowing data across multiple batches to be combined for common downstream analysis. However, existing methods based on linear models (Ritchie et al. 2015; Leek et al. 2012) assume that the composition of cell populations are either known or the same across batches. To overcome these limitations, bespoke methods have been developed for batch correction of single-cell data (Haghverdi et al. 2018; Butler et al. 2018; Lin et al. 2019) that do not require a priori knowledge about the composition of the population. This allows them to be used in workflows for exploratory analyses of scRNA-seq data where such knowledge is usually unavailable.

1.2 Load data

We will load the R file keeping the SCE object with the normalised counts.

setName <- "caron"
# Read object in:
setSuf <- ""
tmpFn <- sprintf("%s/%s/Robjects/%s_sce_nz_postDeconv%s.Rds", projDir, outDirBit, setName, setSuf)
print(tmpFn)
[1] "/mnt/scratcha/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020/AnaWiSce/Attempt1/Robjects/caron_sce_nz_postDeconv.Rds"
if(!file.exists(tmpFn))
{
    knitr::knit_exit()
}
sce <- readRDS(tmpFn)
sce
class: SingleCellExperiment 
dim: 18372 45669 
metadata(0):
assays(1): counts
rownames(18372): ENSG00000238009 ENSG00000237491 ... ENSG00000275063
  ENSG00000271254
rowData names(11): ensembl_gene_id external_gene_name ... detected
  gene_sparsity
colnames: NULL
colData names(19): Sample Barcode ... discard cell_sparsity
reducedDimNames(0):
altExpNames(0):
colnames(rowData(sce))[colnames(rowData(sce)) == "strand"] <- "strandNum" # to avoid error later

#head(rowData(sce))
#head(colData(sce))
#assayNames(sce)
#reducedDimNames(sce)

Read in the sample sheet:

# CaronBourque2020
cb_sampleSheetFn <- file.path(projDir, "Data/CaronBourque2020/SraRunTable.txt")
cb_sampleSheet <- read.table(cb_sampleSheetFn, header=T, sep=",")
cb_sampleSheet <-  cb_sampleSheet %>% filter(!Run == "SRR9264351")
cb_sampleSheet

Have sample names:

cb_sampleSheet$Sample.Name2 <- "NA"
typeVec <- unique(cb_sampleSheet$source_name)
for (tx in typeVec)
{
    tmpInd <- which(cb_sampleSheet$source_name == tx)
    for (i in 1:length(tmpInd))
    {
        cb_sampleSheet$Sample.Name2[tmpInd[i]] <- sprintf("%s_%s", tx, i)
    }
}

colData(sce)$Sample.Name2 <- colData(sce) %>%
    data.frame() %>%
    left_join( cb_sampleSheet, by="Sample.Name") %>%
    pull(Sample.Name2)

splVec <- cb_sampleSheet %>%
    #filter(source_name == "ETV6-RUNX1") %>%
    pull(Sample.Name2)
splVec
 [1] "ETV6-RUNX1_1" "ETV6-RUNX1_2" "ETV6-RUNX1_3" "ETV6-RUNX1_4" "HHD_1"       
 [6] "HHD_2"        "PRE-T_1"      "PRE-T_2"      "PBMMC_1"      "PBMMC_2"     
[11] "PBMMC_3"     
ETV6-RUNX1_1

ETV6-RUNX1_2

ETV6-RUNX1_3

ETV6-RUNX1_4

HHD_1

HHD_2

PRE-T_1

PRE-T_2

PBMMC_1

PBMMC_2

PBMMC_3

Subset cells (1000 per sample for a faster analysis):

sceOrig <- sce
nbCells <- 1000
setSuf <- "_1kCps" # "_1kCellPerSpl"

all.sce <- list()
for(spx in splVec)
{
    vec.bc <- colData(sce) %>%
        data.frame() %>%
        filter(Sample.Name2 == spx) %>%
        sample_n(nbCells) %>%
        pull(Barcode)
    tmpInd <- which(colData(sce)$Barcode %in% vec.bc)
    all.sce[[spx]] <- sce[,tmpInd]
}
# show size of sets:
lapply(all.sce, dim)
$`ETV6-RUNX1_1`
[1] 18372  1000

$`ETV6-RUNX1_2`
[1] 18372  1000

$`ETV6-RUNX1_3`
[1] 18372  1000

$`ETV6-RUNX1_4`
[1] 18372  1000

$HHD_1
[1] 18372  1000

$HHD_2
[1] 18372  1000

$`PRE-T_1`
[1] 18372  1000

$`PRE-T_2`
[1] 18372  1000

$PBMMC_1
[1] 18372  1000

$PBMMC_2
[1] 18372  1000

$PBMMC_3
[1] 18372  1000
# sizeFactors(all.sce[[1]])

We will analyse each sample separately, namely:

  • normalise counts
  • model gene expression variance
  • identify highly variable genes
  • perform dimensionality reduction
  • cluster cells
#--- normalization ---#
all.sce <- lapply(all.sce, logNormCounts)

#--- variance-modelling ---#
library(scran)
all.dec <- lapply(all.sce, modelGeneVar)
all.hvgs <- lapply(all.dec, getTopHVGs, prop=0.1)

#--- dimensionality-reduction ---#
library(BiocSingular)
set.seed(10000)
all.sce <- mapply(FUN=runPCA, x=all.sce, subset_row=all.hvgs, 
    MoreArgs=list(ncomponents=25, BSPARAM=RandomParam()), 
    SIMPLIFY=FALSE)

# TSNE
#set.seed(100000)
#all.sce <- lapply(all.sce, runTSNE, dimred="PCA")

# UMAP
#set.seed(1000000)
#all.sce <- lapply(all.sce, runUMAP, dimred="PCA")

#--- clustering ---#
for (n in names(all.sce)) {
    g <- buildSNNGraph(all.sce[[n]], k=10, use.dimred='PCA')
    clust <- igraph::cluster_walktrap(g)$membership
    colLabels(all.sce[[n]])  <- factor(clust)
}

To prepare for the batch correction, we need to:

  • subset all batches to the common “universe” of features
  • rescale each batch to adjust for differences in sequencing depth between batches
  • perform feature selection by averaging the variance components across all batches

We subset all batches to the common “universe” of features. In this case, it is straightforward as both batches use Ensembl gene annotation.

allNames <- unlist(lapply(all.sce, function(x){rownames(x)}))
allNamesNb <- table(allNames)
universe <- names(allNamesNb)[allNamesNb==length(splVec)] 
length(universe)
[1] 18372
# Subsetting the SingleCellExperiment object.
uni.sce <- lapply(all.sce, function(x){x[universe,]})
# Also subsetting the variance modelling results, for convenience.
uni.dec <- lapply(all.dec, function(x){x[universe,]})

We rescale each batch to adjust for differences in sequencing depth between batches. The multiBatchNorm() function recomputes log-normalized expression values after adjusting the size factors for systematic differences in coverage between SingleCellExperiment objects. (Size factors only remove biases between cells within a single batch.) This improves the quality of the correction by removing one aspect of the technical differences between batches.

library(batchelor)
rescaled.mbn <- multiBatchNorm(uni.sce, batch = "Sample.Name2")

We perform feature selection by averaging the variance components across all batches with the combineVar() function. We compute the average as it is responsive to batch-specific HVGs while still preserving the within-batch ranking of genes. This allows us to use the same strategies described in Section 8.3 to select genes of interest. In contrast, approaches based on taking the intersection or union of HVGs across batches become increasingly conservative or liberal, respectively, with an increasing number of batches.

library(scran)
combined.dec <- combineVar(uni.dec[[1]], uni.dec[[2]], uni.dec[[3]], uni.dec[[4]],
               uni.dec[[5]], uni.dec[[6]],
               uni.dec[[7]], uni.dec[[8]],
               uni.dec[[9]], uni.dec[[10]], uni.dec[[11]]
            )
chosen.hvgs <- combined.dec$bio > 0
sum(chosen.hvgs)
[1] 12317

When integrating datasets of variable composition, it is generally safer to err on the side of including more genes than are used in a single dataset analysis, to ensure that markers are retained for any dataset-specific subpopulations that might be present. For a top X selection, this means using a larger X (say, ~5000), or in this case, we simply take all genes above the trend. That said, many of the signal-to-noise considerations described in Section 8.3 still apply here, so some experimentation may be necessary for best results.

Alternatively, a more forceful approach to feature selection can be used based on marker genes from within-batch comparisons.

1.3 Diagnosing batch effects

Before we actually perform any correction, it is worth examining whether there is any batch effect in this dataset. We combine the SingleCellExperiments and perform a PCA on the log-expression values for all genes with positive (average) biological components.

# Synchronizing the metadata for cbind()ing.
#identical(rowData(rescaled.mbn[[1]]), rowData(rescaled.mbn[[2]]))
#identical(rowData(rescaled.mbn[[1]]), rowData(rescaled.mbn[[3]]))
#identical(rowData(rescaled.mbn[[1]]), rowData(rescaled.mbn[[4]]))

rescaled2 <- lapply(rescaled.mbn, function(x){x$batch <- x$Sample.Name2; x})
rescaled.mbn <- rescaled2
rm(rescaled2)

uncorrected <- do.call(cbind, rescaled.mbn)

# Using RandomParam() as it is more efficient for file-backed matrices.
library(scater)
set.seed(0010101010)
uncorrected <- runPCA(uncorrected, subset_row=chosen.hvgs,
    BSPARAM=BiocSingular::RandomParam())

We use graph-based clustering on the components to obtain a summary of the population structure. As our each sample group isrepresented by at least two replicates, each cluster should ideally consist of cells from several batches. This is the case for some but not all clusters. Some clusters comprise of cells from a single sample. This may indicate that cells of the same type are artificially separated due to technical differences between batches. They may also be cancer cell population private to samples.

library(scran)
snn.gr <- buildSNNGraph(uncorrected, use.dimred="PCA")
clusters <- igraph::cluster_walktrap(snn.gr)$membership
tab <- table(Cluster=clusters, Batch=uncorrected$batch)
tab
       Batch
Cluster ETV6-RUNX1_1 ETV6-RUNX1_2 ETV6-RUNX1_3 ETV6-RUNX1_4 HHD_1 HHD_2 PBMMC_1
     1             0            0            0            0     0     0       0
     2             0            0            0            0   828     0       0
     3             1            1            0            1     2     0      98
     4             0            0            0            0     0     0     185
     5             0            0            0            0     0     0       0
     6             2            0            2            1     0     0     179
     7             0            0            1            0     1     0     100
     8             1            0           24           31     0     2      16
     9             0            0            0          688     0     0       0
     10            4          941            5            2     0     0       2
     11          122            6            0            0     0     0       0
     12            1            5           21          104     1     1       3
     13            4           19          334           52    59    18      99
     14            4            0           16           52     0     2       0
     15            0            0            0            0     0   211       0
     16            0            0            1           12    13     1       1
     17            0            0            0            0     0   746       0
     18            0            0            2            1     0     0      11
     19            0            2          459            1     0     0       0
     20            0            0            0            0     0     0       0
     21            4            2           18           12    39     9      42
     22          854            2            1            0     0     0       0
     23            2            0            0            0     1     0       2
     24            1           19          103           15    54     7      30
     25            0            0            8           21     0     0       0
     26            0            0            0            0     0     0      11
     27            0            0            0            5     0     0       0
     28            0            0            0            0     0     0       0
     29            0            0            0            0     0     0     215
     30            0            3            5            0     2     3       6
     31            0            0            0            2     0     0       0
     32            0            0            0            0     0     0       0
       Batch
Cluster PBMMC_2 PBMMC_3 PRE-T_1 PRE-T_2
     1        0       2       1     661
     2        0       1       0       0
     3       38      52       1      16
     4       33     170       1       1
     5        0       0     682       0
     6       35     106       0       0
     7       33     165       1      67
     8       76      15       0      19
     9        0       0       0       0
     10       0       0       0       0
     11       0       0       0       0
     12      95       3       1       2
     13       7     231       7      98
     14     124       7       1       7
     15       0       0       0       0
     16       1       7       1       2
     17       0       0       0       0
     18      49       9       0       7
     19       1       0       0       0
     20       0       0     153       1
     21      28      83       7      63
     22       0       0       0       0
     23       1       0     134      10
     24       0      92       8      37
     25       4       1       0       1
     26      11      44       1       0
     27      87       0       0       0
     28      61       0       0       0
     29       0       4       0       2
     30       7       6       1       6
     31     255       2       0       0
     32      54       0       0       0

We can also visualize the corrected coordinates using a t-SNE plot (Figure 13.1). The strong separation between cells from different batches is consistent with the clustering results.

set.seed(1111001)
uncorrected <- runTSNE(uncorrected, dimred="PCA")
plotTSNE(uncorrected, colour_by="batch")

set.seed(1111001)
uncorrected <- runUMAP(uncorrected, dimred="PCA")
plotUMAP(uncorrected, colour_by="batch")

Of course, the other explanation for batch-specific clusters is that there are cell types that are unique to each batch. The degree of intermingling of cells from different batches is not an effective diagnostic when the batches involved might actually contain unique cell subpopulations. If a cluster only contains cells from a single batch, one can always debate whether that is caused by a failure of the correction method or if there is truly a batch-specific subpopulation. For example, do batch-specific metabolic or differentiation states represent distinct subpopulations? Or should they be merged together? We will not attempt to answer this here, only noting that each batch correction algorithm will make different (and possibly inappropriate) decisions on what constitutes “shared” and “unique” populations.

1.4 Linear regression

Batch effects in bulk RNA sequencing studies are commonly removed with linear regression. This involves fitting a linear model to each gene’s expression profile, setting the undesirable batch term to zero and recomputing the observations sans the batch effect, yielding a set of corrected expression values for downstream analyses. Linear modelling is the basis of the removeBatchEffect() function from the limma package (Ritchie et al. 2015) as well the comBat() function from the sva package (Leek et al. 2012).

To use this approach in a scRNA-seq context, we assume that the composition of cell subpopulations is the same across batches. We also assume that the batch effect is additive, i.e., any batch-induced fold-change in expression is the same across different cell subpopulations for any given gene. These are strong assumptions as batches derived from different individuals will naturally exhibit variation in cell type abundances and expression. Nonetheless, they may be acceptable when dealing with batches that are technical replicates generated from the same population of cells. (In fact, when its assumptions hold, linear regression is the most statistically efficient as it uses information from all cells to compute the common batch vector.) Linear modelling can also accommodate situations where the composition is known a priori by including the cell type as a factor in the linear model, but this situation is even less common.

We use the rescaleBatches() function from the batchelor package to remove the batch effect. This is roughly equivalent to applying a linear regression to the log-expression values per gene, with some adjustments to improve performance and efficiency. For each gene, the mean expression in each batch is scaled down until it is equal to the lowest mean across all batches. We deliberately choose to scale all expression values down as this mitigates differences in variance when batches lie at different positions on the mean-variance trend. (Specifically, the shrinkage effect of the pseudo-count is greater for smaller counts, suppressing any differences in variance across batches.) An additional feature of rescaleBatches() is that it will preserve sparsity in the input matrix for greater efficiency, whereas other methods like removeBatchEffect() will always return a dense matrix.

library(batchelor)
rescaled.rb <- rescaleBatches(rescaled.mbn)
rescaled.rb
class: SingleCellExperiment 
dim: 18372 11000 
metadata(0):
assays(1): corrected
rownames(18372): ENSG00000000003 ENSG00000000419 ... ENSG00000285486
  ENSG00000285492
rowData names(0):
colnames: NULL
colData names(1): batch
reducedDimNames(0):
altExpNames(0):

After clustering, we observe fewer clusters and these consist of mixtures of cells from the several replicates, consistent with the removal of the batch effect. This conclusion is supported by the apparent mixing of cells from different batches on the TSNE plot below. However, at least one batch-specific cluster is still present, indicating that the correction is not entirely complete. This is attributable to violation of one of the aforementioned assumptions, even in this simple case involving replicated batches.

set.seed(1010101010) # To ensure reproducibility of IRLBA.
rescaled.rb <- runPCA(rescaled.rb, subset_row=chosen.hvgs, exprs_values="corrected")

snn.gr <- buildSNNGraph(rescaled.rb, use.dimred="PCA")
clusters.resc <- igraph::cluster_walktrap(snn.gr)$membership
tab.resc <- table(Cluster=clusters.resc, Batch=rescaled.rb$batch)
tab.resc
       Batch
Cluster ETV6-RUNX1_1 ETV6-RUNX1_2 ETV6-RUNX1_3 ETV6-RUNX1_4 HHD_1 HHD_2 PBMMC_1
     1            21            9          138           21    10     9      55
     2           108           74           37           82    48    63      24
     3             7           17           16            4     7     2     122
     4             5           14          102           29    86    18      51
     5             0            0            2           11    14     1      77
     6           124          301          139           98   275   306     157
     7           625          461          173          378   346   475     137
     8             4            1            0            0     0     0       3
     9             1            0           17            0     0     1       3
     10            8           33          277           60    65    18     132
     11            0            0            0           99     0     0       0
     12            0            0            0           12     0     0       0
     13            6           12           11            2     6     2       4
     14            0            0            6           61     1     0       0
     15            0            0            0            1     0     0       0
     16           90           74           43          127   140   101      39
     17            0            0            0            0     0     1      14
     18            0            0           32            0     0     0       0
     19            0            0            2            0     0     0       0
     20            0            0            3            1     0     0      10
     21            0            0            0            0     1     0     172
     22            0            0            2           14     0     0       0
     23            0            0            0            0     0     0       0
     24            1            4            0            0     1     3       0
       Batch
Cluster PBMMC_2 PBMMC_3 PRE-T_1 PRE-T_2
     1       14      30       8      10
     2        0      20     132      19
     3       54      77       1      25
     4      131     111      16     108
     5       69     136       2      61
     6       42     179      84      73
     7       82     138     561     509
     8        0       7       0       9
     9        0      11       0      10
     10     232     239       8      83
     11     139       0       0       0
     12      50       0       0       0
     13       2       0      71      16
     14      81       0       0       0
     15      37       3       0       3
     16       8      27     115      59
     17       0       2       0       8
     18       0       5       0       0
     19      14       3       0       1
     20       3       5       0       5
     21       7       7       0       1
     22      21       0       0       0
     23      14       0       0       0
     24       0       0       2       0
rescaled.rb <- runTSNE(rescaled.rb, dimred="PCA")
rescaled.rb$batch <- factor(rescaled.rb$batch)
plotTSNE(rescaled.rb, colour_by="batch")

1.5 Performing MNN correction

1.5.1  Algorithm overview

Consider a cell a in batch A, and identify the cells in batch B that are nearest neighbors to a in the expression space defined by the selected features. Repeat this for a cell b in batch B, identifying its nearest neighbors in A. Mutual nearest neighbors (MNNs) are pairs of cells from different batches that belong in each other’s set of nearest neighbors. The reasoning is that MNN pairs represent cells from the same biological state prior to the application of a batch effect - see Haghverdi et al. (2018) for full theoretical details. Thus, the difference between cells in MNN pairs can be used as an estimate of the batch effect, the subtraction of which yields batch-corrected values.

Compared to linear regression, MNN correction does not assume that the population composition is the same or known beforehand. This is because it learns the shared population structure via identification of MNN pairs and uses this information to obtain an appropriate estimate of the batch effect. Instead, the key assumption of MNN-based approaches is that the batch effect is orthogonal to the biology in high-dimensional expression space. Violations reduce the effectiveness and accuracy of the correction, with the most common case arising from variations in the direction of the batch effect between clusters. Nonetheless, the assumption is usually reasonable as a random vector is very likely to be orthogonal in high-dimensional space.

1.5.2 Application to the data

The batchelor package provides an implementation of the MNN approach via the fastMNN() function. Unlike the MNN method originally described by Haghverdi et al. (2018), the fastMNN() function performs PCA to reduce the dimensions beforehand and speed up the downstream neighbor detection steps.

We apply it to our two PBMC batches to remove the batch effect across the highly variable genes in chosen.hvgs. To reduce computational work and technical noise, all cells in all batches are projected into the low-dimensional space defined by the top d principal components. Identification of MNNs and calculation of correction vectors are then performed in this low-dimensional space.

# Using randomized SVD here, as this is faster than 
# irlba for file-backed matrices.
set.seed(1000101001)
mnn.out <- fastMNN(rescaled.mbn, auto.merge=TRUE, d=50, k=20, subset.row=chosen.hvgs,
    BSPARAM=BiocSingular::RandomParam(deferred=TRUE))
mnn.out
class: SingleCellExperiment 
dim: 12317 11000 
metadata(2): merge.info pca.info
assays(1): reconstructed
rownames(12317): ENSG00000000003 ENSG00000000457 ... ENSG00000285458
  ENSG00000285476
rowData names(1): rotation
colnames: NULL
colData names(1): batch
reducedDimNames(1): corrected
altExpNames(0):
mnn.out$batch <- factor(mnn.out$batch)
mnn.out$type <- gsub("_[1-4]","",mnn.out$batch)

The function returns a SingleCellExperiment object containing corrected values for downstream analyses like clustering or visualization. Each column of mnn.out corresponds to a cell in one of the batches, while each row corresponds to an input gene in chosen.hvgs. The batch field in the column metadata contains a vector specifying the batch of origin of each cell.

head(mnn.out$batch) 
[1] ETV6-RUNX1_1 ETV6-RUNX1_1 ETV6-RUNX1_1 ETV6-RUNX1_1 ETV6-RUNX1_1
[6] ETV6-RUNX1_1
11 Levels: ETV6-RUNX1_1 ETV6-RUNX1_2 ETV6-RUNX1_3 ETV6-RUNX1_4 HHD_1 ... PRE-T_2

The corrected matrix in the reducedDims() contains the low-dimensional corrected coordinates for all cells, which we will use in place of the PCs in our downstream analyses.

dim(reducedDim(mnn.out, "corrected"))
[1] 11000    50

A reconstructed matrix in the assays() contains the corrected expression values for each gene in each cell, obtained by projecting the low-dimensional coordinates in corrected back into gene expression space. We do not recommend using this for anything other than visualization.

dim(assay(mnn.out, "reconstructed"))
[1] 12317 11000
print(assay(mnn.out, "reconstructed")[1:5,1:3])
<5 x 3> matrix of class LowRankMatrix and type "double":
                         [,1]          [,2]          [,3]
ENSG00000000003 -3.773369e-05 -8.300428e-05 -3.456565e-05
ENSG00000000457  2.106775e-04  3.885394e-04  5.365413e-06
ENSG00000000938 -1.976560e-04  2.491227e-04  6.767908e-04
ENSG00000000971  1.002027e-04  2.324994e-04  2.100986e-04
ENSG00000001167  7.467781e-04 -2.202970e-04  6.354969e-04

The most relevant parameter for tuning fastMNN() is k, which specifies the number of nearest neighbors to consider when defining MNN pairs. This can be interpreted as the minimum anticipated frequency of any shared cell type or state in each batch. Increasing k will generally result in more aggressive merging as the algorithm is more generous in matching subpopulations across batches. It can occasionally be desirable to increase k if one clearly sees that the same cell types are not being adequately merged across batches.

1.5.3 Correction diagnostics

We cluster on the low-dimensional corrected coordinates to obtain a partitioning of the cells that serves as a proxy for the population structure. If the batch effect is successfully corrected, clusters corresponding to shared cell types or states should contain cells from multiple batches. We see that all clusters contain contributions from each batch after correction, consistent with our expectation that the two batches are replicates of each other.

library(scran)
snn.gr <- buildSNNGraph(mnn.out, use.dimred="corrected")
clusters.mnn <- igraph::cluster_walktrap(snn.gr)$membership
tab.mnn <- table(Cluster=clusters.mnn, Batch=mnn.out$batch)
tab.mnn
       Batch
Cluster ETV6-RUNX1_1 ETV6-RUNX1_2 ETV6-RUNX1_3 ETV6-RUNX1_4 HHD_1 HHD_2 PBMMC_1
     1             3           20          341           56    65    20     319
     2             1            1            0            1    12     0      71
     3             4            2           15           11    32     7      34
     4             0            0           11           57     0     1       0
     5             0            0            0            0     2     0      16
     6             1            5           16           46     1     0       2
     7             0            2            5            0     2     3       6
     8            78          568          274          110   391   464      21
     9             1           18          103           20    54     7      35
     10            1            0            9           22     0     0       1
     11           23            1            1            0    39    13       7
     12            0            0            1            0     2     0      60
     13           55           41            6           47    23    21       8
     14          106           68           45          141    70    75      50
     15            0            0            1            1     0     0      11
     16           54           48           17           42    26    69      25
     17           21            4            5            6    28    16      33
     18          465          194           96          330   111   140      12
     19            0            1            0            0     5     1     163
     20            0            0            0            1     0     0       0
     21            0            0            1           12    12     1       2
     22            1            0            7           15     0     1      10
     23            0            0            0            0     0     0      21
     24            3            0            3           31     0     2       0
     25            0            6            3            3     2     0      60
     26            0            0            2            0     0     0       2
     27          178           12           12           10   121   156       9
     28            0            0            2            1     0     0       4
     29            0            0            2            0     0     0       1
     30            0            0            7            8     0     1       2
     31            0            0            5            7     0     0       1
     32            5            9            5            5     2     2       3
     33            0            0            0            0     0     0      11
     34            0            0            5           17     0     0       0
       Batch
Cluster PBMMC_2 PBMMC_3 PRE-T_1 PRE-T_2
     1      263     251       8     109
     2       20      40       2      59
     3       76      66       6      53
     4       78       5       0       2
     5       16      15       0       5
     6       58       3       0       2
     7        7       6       1       6
     8       12      35      90      26
     9       83      92       7      37
     10      24       0       1       0
     11       4       6     164     160
     12      66     127       1      57
     13       2      10      49       4
     14       9      40      38       3
     15      19       8       0       6
     16       4       8      91       7
     17      10      11     121      41
     18       9      18       5       0
     19      38     167       1       1
     20       0       0     332      99
     21       2      14       1       3
     22      26       3       0       9
     23      11      17       0       7
     24      59       2       1       5
     25      10      32       0       0
     26       0       0      11     273
     27       2       2       1       4
     28      17       3       0       5
     29      13       4       0       1
     30      26       5       0       4
     31      23       4       0       3
     32       2       0      69       9
     33       9       6       0       0
     34       2       0       0       0

We can also visualize the corrected coordinates using a t-SNE plot (Figure 13.3). The presence of visual clusters containing cells from both batches provides a comforting illusion that the correction was successful.

library(scater)
set.seed(0010101010)
mnn.out <- runTSNE(mnn.out, dimred="corrected")

plotTSNE(mnn.out, colour_by="batch")

For fastMNN(), one useful diagnostic is the proportion of variance within each batch that is lost during MNN correction. Specifically, this refers to the within-batch variance that is removed during orthogonalization with respect to the average correction vector at each merge step. This is returned via the lost.var field in the metadata of mnn.out, which contains a matrix of the variance lost in each batch (column) at each merge step (row).

metadata(mnn.out)$merge.info$lost.var
      ETV6-RUNX1_1 ETV6-RUNX1_2 ETV6-RUNX1_3 ETV6-RUNX1_4       HHD_1
 [1,]  0.000000000  0.000000000  0.000000000  0.000000000 0.000000000
 [2,]  0.000000000  0.000000000  0.000000000  0.000000000 0.000000000
 [3,]  0.000000000  0.000000000  0.155115756  0.000000000 0.000000000
 [4,]  0.000000000  0.000000000  0.005909417  0.203539937 0.000000000
 [5,]  0.000000000  0.000000000  0.005937804  0.003367620 0.096239302
 [6,]  0.000000000  0.116399134  0.009803825  0.016737279 0.003499484
 [7,]  0.127044959  0.004622797  0.007733567  0.013218294 0.002314790
 [8,]  0.007310957  0.009827945  0.019269202  0.003779890 0.016254615
 [9,]  0.006604805  0.006685228  0.015948590  0.029239246 0.004494844
[10,]  0.003413216  0.004391660  0.005873768  0.005836308 0.006564319
            HHD_2 PRE-T_1     PRE-T_2     PBMMC_1     PBMMC_2     PBMMC_3
 [1,] 0.000000000 0.00000 0.000000000 0.041751796 0.000000000 0.050961815
 [2,] 0.000000000 0.00000 0.000000000 0.016917085 0.165545763 0.019631472
 [3,] 0.000000000 0.00000 0.000000000 0.009512430 0.016987176 0.012093444
 [4,] 0.000000000 0.00000 0.000000000 0.004664088 0.004804665 0.003634476
 [5,] 0.000000000 0.00000 0.000000000 0.005765378 0.003832367 0.004898184
 [6,] 0.000000000 0.00000 0.000000000 0.004800169 0.013825906 0.005876827
 [7,] 0.000000000 0.00000 0.000000000 0.004015535 0.012593871 0.003753828
 [8,] 0.000000000 0.00000 0.178268013 0.014477662 0.006114257 0.012885908
 [9,] 0.143930254 0.00000 0.010217644 0.006399640 0.027330714 0.007537288
[10,] 0.005799986 0.11074 0.005461576 0.004163632 0.005704912 0.004261348

Large proportions of lost variance (>10%) suggest that correction is removing genuine biological heterogeneity. This would occur due to violations of the assumption of orthogonality between the batch effect and the biological subspace (Haghverdi et al. 2018). In this case, the proportion of lost variance is small, indicating that non-orthogonality is not a major concern.

1.6 Preserving biological heterogeneity

1.6.1 Comparison to within-batch clusters

Another useful diagnostic check is to compare the clustering within each batch to the clustering of the merged data. Accurate data integration should preserve variance within each batch as there should be nothing to remove between cells in the same batch. This check complements the previously mentioned diagnostics that only focus on the removal of differences between batches. Specifically, it protects us against cases where the correction method simply aggregates all cells together, which would achieve perfect mixing but also discard the biological heterogeneity of interest.

Ideally, we should see a many-to-1 mapping where the across-batch clustering is nested inside the within-batch clusterings. This indicates that any within-batch structure was preserved after correction while acknowledging that greater resolution is possible with more cells. In practice, more discrepancies can be expected even when the correction is perfect, due to the existence of closely related clusters that were arbitrarily separated in the within-batch clustering. As a general rule, we can be satisfied with the correction if the vast majority of entries in Figure 13.4 are zero, though this may depend on whether specific clusters of interest are gained or lost.

One heatmap is generated for each of the PBMC 3K and 4K datasets, where each entry is colored according to the number of cells with each pair of labels (before and after correction).

library(pheatmap)

# For the first batch (adding +10 for a smoother color transition
# from zero to non-zero counts for any given matrix entry).
batchPlace <- 1
tmpInd <- mnn.out$batch==levels(mnn.out$batch)[batchPlace]
tab <- table(paste("after", clusters.mnn[tmpInd]),
             paste("before", colLabels(rescaled.mbn[[batchPlace]])))
heat1 <- pheatmap(log10(tab+10), cluster_row=FALSE, cluster_col=FALSE,
    main=sprintf("%s comparison", splVec[batchPlace]), silent=TRUE)

# For the second batch.
batchPlace <- 2
tmpInd <- mnn.out$batch==levels(mnn.out$batch)[batchPlace]
tab <- table(paste("after", clusters.mnn[tmpInd]),
    paste("before", colLabels(rescaled.mbn[[batchPlace]])))
heat2 <- pheatmap(log10(tab+10), cluster_row=FALSE, cluster_col=FALSE,
    main=sprintf("%s comparison", splVec[batchPlace]), silent=TRUE)

gridExtra::grid.arrange(heat1[[4]], heat2[[4]])

Another evaluation approach is to compute the coassignment probabilities (Section 10.6), i.e., the probability that cells from two within-batch clusters are clustered together in the across-batch clustering. High probabilities off the diagonal in Figure 13.5 indicate that within-batch clusters are merged in the across-batch analysis. We would generally expect low off-diagonal probabilities for most pairs of clusters, though this may not be reasonably possible if the within-batch clusters were poorly separated in the first place.

Coassignment probabilities for the within-batch clusters, based on coassignment of cells in the across-batch clusters obtained after MNN correction. One heatmap is generated for each of the GSM3872434 and GSM3872435, where each entry is colored according to the coassignment probability between each pair of within-batch clusters:

# For the first batch.
batchPlace <- 1
tmpInd <- mnn.out$batch==levels(mnn.out$batch)[batchPlace]
tab <- coassignProb(colLabels(rescaled.mbn[[batchPlace]]), clusters.mnn[tmpInd])
heat1 <- pheatmap(tab, cluster_row=FALSE, cluster_col=FALSE,
    col=rev(viridis::magma(100)), main=sprintf("%s probabilities", splVec[batchPlace]), silent=TRUE)

# For the second batch.
batchPlace <- 2
tmpInd <- mnn.out$batch==levels(mnn.out$batch)[batchPlace]
tab <- coassignProb(colLabels(rescaled.mbn[[batchPlace]]), clusters.mnn[tmpInd])
heat2 <- pheatmap(tab, cluster_row=FALSE, cluster_col=FALSE,
    col=rev(viridis::magma(100)), main=sprintf("%s probabilities", splVec[batchPlace]), silent=TRUE)

gridExtra::grid.arrange(heat1[[4]], heat2[[4]])

Finally, we can summarize the agreement between clusterings by computing the Rand index. This provides a simple metric that we can use to assess the preservation of variation by different correction methods. Larger rand indices (i.e., closer to 1) are more desirable, though this must be balanced against the ability of each method to actually remove the batch effect.

suppressMessages(library(fossil))
batchPlace <- 1
tmpInd <- mnn.out$batch==levels(mnn.out$batch)[batchPlace]
ri1 <- rand.index(as.integer(clusters.mnn[tmpInd]),
    as.integer(colLabels(rescaled.mbn[[batchPlace]])))
ri1
[1] 0.7513013
batchPlace <- 2
tmpInd <- mnn.out$batch==levels(mnn.out$batch)[batchPlace]
ri2 <- rand.index(as.integer(clusters.mnn[tmpInd]),
    as.integer(colLabels(rescaled.mbn[[batchPlace]])))
ri2
[1] 0.7077918

1.6.2 Encouraging consistency with marker genes

In some situations, we will already have performed within-batch analyses to characterize salient aspects of population heterogeneity. This is not uncommon when merging datasets from different sources where each dataset has already been analyzed, annotated and interpreted separately. It is subsequently desirable for the integration procedure to retain these “known interesting” aspects of each dataset in the merged dataset. We can encourage this outcome by using the marker genes within each dataset as our selected feature set for fastMNN() and related methods. This focuses on the relevant heterogeneity and represents a semi-supervised approach that is a natural extension of the strategy described in Section 8.4.

To illustrate, we apply this strategy to our PBMC datasets. We identify the top marker genes from pairwise Wilcoxon ranked sum tests between every pair of clusters within each batch, analogous to the method used by SingleR (Chapter 12). In this case, we use the top 10 marker genes but any value can be used depending on the acceptable trade-off between signal and noise (and speed). We then take the union across all comparisons in all batches and use that in place of our HVG set in fastMNN().

# Recall that groups for marker detection
# are automatically defined from 'colLabels()'. 
stats1 <- pairwiseWilcox(rescaled.mbn[[1]], direction="up")
markers1 <- getTopMarkers(stats1[[1]], stats1[[2]], n=10)

stats2 <- pairwiseWilcox(rescaled.mbn[[2]], direction="up")
markers2 <- getTopMarkers(stats2[[1]], stats2[[2]], n=10)

stats3 <- pairwiseWilcox(rescaled.mbn[[3]], direction="up")
markers3 <- getTopMarkers(stats3[[1]], stats3[[2]], n=10)

stats4 <- pairwiseWilcox(rescaled.mbn[[4]], direction="up")
markers4 <- getTopMarkers(stats4[[1]], stats4[[2]], n=10)


marker.set <- unique(unlist(c(unlist(markers1), unlist(markers2), unlist(markers3), unlist(markers4))))
length(marker.set) # getting the total number of genes selected in this manner.
[1] 616
set.seed(1000110)
mnn.out2 <- fastMNN(rescaled.mbn[1:4], subset.row=marker.set,
    BSPARAM=BiocSingular::RandomParam(deferred=TRUE))

A quick inspection indicates that the original within-batch structure is indeed preserved in the corrected data. This highlights the utility of a marker-based feature set for integrating datasets that have already been characterized separately in a manner that preserves existing interpretations of each dataset. We note that some within-batch clusters have merged, most likely due to the lack of robust separation in the first place, though this may also be treated as a diagnostic on the appropriateness of the integration depending on the context.

mnn.out2 <- runTSNE(mnn.out2, dimred="corrected")
batchVec <- levels(mnn.out$batch)
gridExtra::grid.arrange(
    plotTSNE(mnn.out2[,mnn.out2$batch==batchVec[1]], colour_by=I(colLabels(rescaled.mbn[[1]]))),
    plotTSNE(mnn.out2[,mnn.out2$batch==batchVec[2]], colour_by=I(colLabels(rescaled.mbn[[2]]))),
    plotTSNE(mnn.out2[,mnn.out2$batch==batchVec[3]], colour_by=I(colLabels(rescaled.mbn[[3]]))),
    plotTSNE(mnn.out2[,mnn.out2$batch==batchVec[4]], colour_by=I(colLabels(rescaled.mbn[[4]]))),
    ncol=2
)

1.6.3 Using the corrected values

it is preferable to perform DE analyses using the uncorrected expression values with blocking on the batch, as discussed in Section 11.4. This strategy is based on the expectation that any genuine DE between clusters should still be present in a within-batch comparison where batch effects are absent. It penalizes genes that exhibit inconsistent DE across batches, thus protecting against misleading conclusions when a population in one batch is aligned to a similar-but-not-identical population in another batch. We demonstrate this approach below using a blocked t-test to detect markers in the PBMC dataset, where the presence of the same pattern across clusters within each batch (Figure 13.7) is reassuring. If integration is performed across multiple conditions, it is even more important to use the uncorrected expression values for downstream analyses - see Section 14.5.2 for a discussion.

m.out <- findMarkers(uncorrected, clusters.mnn, block=uncorrected$batch,
    direction="up", lfc=1, row.data=rowData(uncorrected)[,c("ensembl_gene_id","Symbol"),drop=FALSE])
Warning in FUN(...): no within-block comparison between 20 and 10
Warning in FUN(...): no within-block comparison between 20 and 19
Warning in FUN(...): no within-block comparison between 25 and 20
Warning in FUN(...): no within-block comparison between 29 and 20
Warning in FUN(...): no within-block comparison between 33 and 20
Warning in FUN(...): no within-block comparison between 34 and 20
demo <- m.out[["1"]]
as.data.frame(demo[1:20,c("Symbol", "Top", "p.value", "FDR")]) 
#as.data.frame(demo[1:20,c("external_gene_name", "Top", "p.value", "FDR")]) 

Expression level for the top gene, ENSG00000008517:

geneEnsId <- rownames(demo)[1]
plotExpression(uncorrected, x=I(factor(clusters.mnn)), 
    features=geneEnsId, colour_by="batch") + facet_wrap(~colour_by)

1.7 Same but with an ordered merging

We will first merge replicates in each sample group separately, then sample groups, starting with the group with the larger number of ‘cell types’.

# Using randomized SVD here, as this is faster than 
# irlba for file-backed matrices.
set.seed(1000101001)
rm(mnn.out)
mnn.out <- fastMNN(rescaled.mbn,
           merge.order=list( list(1,2,3,4), list(9,10,11), list(5,6), list(7,8) ),
           d=50, k=20, subset.row=chosen.hvgs,
    BSPARAM=BiocSingular::RandomParam(deferred=TRUE))
mnn.out
class: SingleCellExperiment 
dim: 12317 11000 
metadata(2): merge.info pca.info
assays(1): reconstructed
rownames(12317): ENSG00000000003 ENSG00000000457 ... ENSG00000285458
  ENSG00000285476
rowData names(1): rotation
colnames: NULL
colData names(1): batch
reducedDimNames(1): corrected
altExpNames(0):
mnn.out$batch <- factor(mnn.out$batch)
mnn.out$type <- gsub("_[1-4]","",mnn.out$batch)
mnn.out$type <- factor(mnn.out$type)
#class(mnn.out$batch)
#head(mnn.out$batch)
#dim(reducedDim(mnn.out, "corrected"))
#assay(mnn.out, "reconstructed")
print(dim(assay(mnn.out, "reconstructed")))
[1] 12317 11000
print(assay(mnn.out, "reconstructed")[1:5,1:3])
<5 x 3> matrix of class LowRankMatrix and type "double":
                         [,1]          [,2]          [,3]
ENSG00000000003 -2.536382e-05 -1.602522e-04 -4.321568e-05
ENSG00000000457  5.065654e-04  7.128008e-04  2.080907e-04
ENSG00000000938 -7.889729e-04 -1.404166e-03 -3.766279e-04
ENSG00000000971  2.850208e-05  9.862104e-05  1.354787e-04
ENSG00000001167  2.090608e-04 -3.784854e-04  1.733540e-05

Diagnostic table and plots:

library(scran)
snn.gr <- buildSNNGraph(mnn.out, use.dimred="corrected")
clusters.mnn <- igraph::cluster_walktrap(snn.gr)$membership
mnn.out$clusters.mnn <- sprintf("c%s", clusters.mnn)
tab.mnn <- table(Cluster=mnn.out$clusters.mnn, Batch=mnn.out$batch)
tab.mnn
       Batch
Cluster ETV6-RUNX1_1 ETV6-RUNX1_2 ETV6-RUNX1_3 ETV6-RUNX1_4 HHD_1 HHD_2 PBMMC_1
    c1             1            0           10           16     0     1      15
    c10            4            6           76           17    39     9      42
    c11            1            3           14           54     0     1       1
    c12            0            0            5           19     0     0       0
    c13            1            2           12           42     1     0       2
    c14            0            0            0            1     1     0      15
    c15           15            0            1            3    34    20      21
    c16          227           20            9           16   160   118      27
    c17            4            1            2            1     6     0      59
    c18            3            0           15           62     0     2       0
    c19            0            0            1            0     0     0       1
    c2             0            3            5            0     2     3       6
    c20            0            0            3            1     0     0      11
    c21          106           82           34          167    59    82       6
    c22          426          309           96          323   133   229       8
    c23            6           21           10            5     8     3       7
    c24            1           19          104           20    54     7      40
    c25            0            0            1            1     0     0     204
    c26            3           16          277           49    57    18     109
    c27            0            0            0            0     0     0      11
    c28            0            0            1            9    11     1       3
    c3            65          420          290           92   349   403     175
    c4             0            0            9           14     0     1       1
    c5            65           54           13           35    43    72      81
    c6             8            1            2            1    18     8      60
    c7            64           43            8           50    22    22      15
    c8             0            0            1            1     3     0      45
    c9             0            0            1            1     0     0      35
       Batch
Cluster PBMMC_2 PBMMC_3 PRE-T_1 PRE-T_2
    c1       43       6       0       0
    c10      94      89       7      73
    c11      60       3       1      13
    c12       2       1       0       8
    c13      56       3       0       0
    c14      14      14       0       3
    c15       7      11     162      45
    c16      11      33       2      14
    c17       1      14      40       9
    c18     105       4       1       7
    c19       0       0      18     301
    c2        7       6       2       6
    c20      30       9       0       7
    c21       4      12       0       0
    c22       9      18       0       1
    c23       3       0      73      30
    c24      86     112       8      37
    c25      28      11       1       2
    c26     217     219       7      90
    c27      10       6       0       0
    c28       6      19       1      10
    c3       44     179      63      43
    c4       47       9       0       0
    c5       21      54     134      17
    c6       13      13     465     224
    c7        8      18      14       3
    c8       54     113       1      49
    c9       20      24       0       8
library(scater)
set.seed(0010101010)
mnn.out <- runTSNE(mnn.out, dimred="corrected")

p <- plotTSNE(mnn.out, colour_by="batch")
p + facet_wrap(~mnn.out$type)

Write mnn.out object to file

colData(mnn.out) <- cbind(colData(uncorrected),colData(mnn.out)[,c("type", "clusters.mnn")])
# Write object to file
# fastMnnWholeByList -> Fmwbl
tmpFn <- sprintf("%s/%s/Robjects/%s_sce_nz_postDeconv%s_Fmwbl.Rds", projDir, outDirBit, setName, setSuf)
saveRDS(mnn.out, tmpFn)

tmpFn <- sprintf("%s/%s/Robjects/%s_sce_nz_postDeconv%s_Fmwbl2.Rds", projDir, outDirBit, setName, setSuf)
saveRDS(list("chosen.hvgs"=chosen.hvgs, "uncorrected"=uncorrected,"rescaled.mbn"=rescaled.mbn), tmpFn)

Proportions of lost variance

metadata(mnn.out)$merge.info$lost.var
      ETV6-RUNX1_1 ETV6-RUNX1_2 ETV6-RUNX1_3 ETV6-RUNX1_4      HHD_1      HHD_2
 [1,]  0.000000000  0.000000000  0.000000000   0.00000000 0.00000000 0.00000000
 [2,]  0.000000000  0.000000000  0.000000000   0.00000000 0.01241817 0.03496573
 [3,]  0.000000000  0.000000000  0.000000000   0.00000000 0.00000000 0.00000000
 [4,]  0.000000000  0.000000000  0.000000000   0.00000000 0.00000000 0.00000000
 [5,]  0.022394503  0.028853804  0.000000000   0.00000000 0.00000000 0.00000000
 [6,]  0.015196722  0.011555716  0.036390422   0.00000000 0.00000000 0.00000000
 [7,]  0.002680773  0.002628968  0.005573193   0.05103570 0.00000000 0.00000000
 [8,]  0.067038671  0.054243590  0.126698638   0.17689035 0.00000000 0.00000000
 [9,]  0.019917382  0.021405755  0.033839111   0.03791951 0.09551676 0.10790694
[10,]  0.019784082  0.022209371  0.029477449   0.02976371 0.02634447 0.02664557
         PRE-T_1    PRE-T_2     PBMMC_1     PBMMC_2    PBMMC_3
 [1,] 0.02600436 0.01581972 0.000000000 0.000000000 0.00000000
 [2,] 0.00000000 0.00000000 0.000000000 0.000000000 0.00000000
 [3,] 0.00000000 0.00000000 0.045459704 0.187809533 0.00000000
 [4,] 0.00000000 0.00000000 0.007615442 0.005678275 0.06605941
 [5,] 0.00000000 0.00000000 0.000000000 0.000000000 0.00000000
 [6,] 0.00000000 0.00000000 0.000000000 0.000000000 0.00000000
 [7,] 0.00000000 0.00000000 0.000000000 0.000000000 0.00000000
 [8,] 0.00000000 0.00000000 0.027822831 0.021451890 0.02269959
 [9,] 0.00000000 0.00000000 0.018912623 0.044243631 0.02074165
[10,] 0.10883444 0.18154633 0.023707234 0.024626696 0.02033936

Comparison to within-batch clusters

library(pheatmap)
mnn.out$batch <- factor(mnn.out$batch) # somehow need to re-factor batch
levels(mnn.out$batch)
 [1] "ETV6-RUNX1_1" "ETV6-RUNX1_2" "ETV6-RUNX1_3" "ETV6-RUNX1_4" "HHD_1"       
 [6] "HHD_2"        "PBMMC_1"      "PBMMC_2"      "PBMMC_3"      "PRE-T_1"     
[11] "PRE-T_2"     
ETV6-RUNX1_1

ETV6-RUNX1_2

ETV6-RUNX1_3

ETV6-RUNX1_4

HHD_1

HHD_2

PBMMC_1

PBMMC_2

PBMMC_3

PRE-T_1

PRE-T_2
# For the first batch (adding +10 for a smoother color transition
# from zero to non-zero counts for any given matrix entry).
batchPlace <- 1
tmpInd <- mnn.out$batch==levels(mnn.out$batch)[batchPlace]
length(paste("after", clusters.mnn[tmpInd]))
[1] 1000
rescaled.mbn[[batchPlace]]
class: SingleCellExperiment 
dim: 18372 1000 
metadata(0):
assays(2): counts logcounts
rownames(18372): ENSG00000000003 ENSG00000000419 ... ENSG00000285486
  ENSG00000285492
rowData names(11): ensembl_gene_id external_gene_name ... detected
  gene_sparsity
colnames: NULL
colData names(23): Sample Barcode ... label batch
reducedDimNames(1): PCA
altExpNames(0):
rescaled.mbn[[batchPlace]] %>% colData %>% head
DataFrame with 6 rows and 23 columns
                                                                                                                                                       Sample
                                                                                                                                                  <character>
1 /mnt/scratchb/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020/CaronBourque2020/grch38300/SRR9264343/SRR9264343/outs/filtered_feature_bc_matrix/
2 /mnt/scratchb/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020/CaronBourque2020/grch38300/SRR9264343/SRR9264343/outs/filtered_feature_bc_matrix/
3 /mnt/scratchb/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020/CaronBourque2020/grch38300/SRR9264343/SRR9264343/outs/filtered_feature_bc_matrix/
4 /mnt/scratchb/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020/CaronBourque2020/grch38300/SRR9264343/SRR9264343/outs/filtered_feature_bc_matrix/
5 /mnt/scratchb/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020/CaronBourque2020/grch38300/SRR9264343/SRR9264343/outs/filtered_feature_bc_matrix/
6 /mnt/scratchb/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020/CaronBourque2020/grch38300/SRR9264343/SRR9264343/outs/filtered_feature_bc_matrix/
             Barcode         Run Sample.Name source_name      block       sum
         <character> <character> <character>    <factor>   <factor> <numeric>
1 AAACCTGGTCTTCAAG-1  SRR9264343  GSM3872434  ETV6-RUNX1 ETV6-RUNX1     11706
2 AAACCTGTCCCAAGTA-1  SRR9264343  GSM3872434  ETV6-RUNX1 ETV6-RUNX1      8354
3 AAACGGGCAGTTCATG-1  SRR9264343  GSM3872434  ETV6-RUNX1 ETV6-RUNX1      7839
4 AAACGGGGTAAGCACG-1  SRR9264343  GSM3872434  ETV6-RUNX1 ETV6-RUNX1      5438
5 AAACGGGGTTCACCTC-1  SRR9264343  GSM3872434  ETV6-RUNX1 ETV6-RUNX1      9280
6 AAACGGGTCGGTTCGG-1  SRR9264343  GSM3872434  ETV6-RUNX1 ETV6-RUNX1      9949
   detected percent_top_50 percent_top_100 percent_top_200 percent_top_500
  <integer>      <numeric>       <numeric>       <numeric>       <numeric>
1      3079        32.3595         45.7116         56.5437         68.9219
2      2307        37.4791         53.0285         63.3948         74.5870
3      2223        34.7111         49.4706         60.6710         73.9125
4      1759        37.0541         52.2986         62.7804         75.6896
5      2764        32.7802         45.9483         56.0345         68.7608
6      3015        28.5355         41.4212         52.1560         65.4538
  subsets_Mito_sum subsets_Mito_detected subsets_Mito_percent     total
         <numeric>             <integer>            <numeric> <numeric>
1              567                    12              4.84367     11706
2              519                    13              6.21259      8354
3              428                    11              5.45988      7839
4              352                    11              6.47297      5438
5              740                    12              7.97414      9280
6              397                    11              3.99035      9949
      setName   discard cell_sparsity Sample.Name2 sizeFactor    label
  <character> <logical>     <numeric>  <character>  <numeric> <factor>
1       Caron     FALSE      0.856837 ETV6-RUNX1_1    4.77137        1
2       Caron     FALSE      0.892698 ETV6-RUNX1_1    3.40539        4
3       Caron     FALSE      0.896605 ETV6-RUNX1_1    3.19464        1
4       Caron     FALSE      0.918233 ETV6-RUNX1_1    2.21631        5
5       Caron     FALSE      0.871442 ETV6-RUNX1_1    3.78286        1
6       Caron     FALSE      0.859814 ETV6-RUNX1_1    4.05475        2
         batch
   <character>
1 ETV6-RUNX1_1
2 ETV6-RUNX1_1
3 ETV6-RUNX1_1
4 ETV6-RUNX1_1
5 ETV6-RUNX1_1
6 ETV6-RUNX1_1
length(paste("before", colLabels(rescaled.mbn[[batchPlace]])))
[1] 1000
save.image("dataSetIntegrationWhole.debug.Rdata")

table(paste("after", clusters.mnn[tmpInd]))

 after 1 after 10 after 11 after 13 after 15 after 16 after 17 after 18 
       1        4        1        1       15      227        4        3 
after 21 after 22 after 23 after 24 after 26  after 3  after 5  after 6 
     106      426        6        1        3       65       65        8 
 after 7 
      64 
table(paste("before", colLabels(rescaled.mbn[[batchPlace]])))

before 1 before 2 before 3 before 4 before 5 before 6 before 7 
      62      126      362      253      134       15       48 
tab <- table(paste("after", clusters.mnn[tmpInd]),
             paste("before", colLabels(rescaled.mbn[[batchPlace]])))
heat1 <- pheatmap(log10(tab+10), cluster_row=FALSE, cluster_col=FALSE,
    main=sprintf("%s comparison", splVec[batchPlace]), silent=TRUE)

# For the second batch.
batchPlace <- 2
tmpInd <- mnn.out$batch==levels(mnn.out$batch)[batchPlace]
tab <- table(paste("after", clusters.mnn[tmpInd]),
    paste("before", colLabels(rescaled.mbn[[batchPlace]])))
heat2 <- pheatmap(log10(tab+10), cluster_row=FALSE, cluster_col=FALSE,
    main=sprintf("%s comparison", splVec[batchPlace]), silent=TRUE)

gridExtra::grid.arrange(heat1[[4]], heat2[[4]])

Co-assignment probabilities

# For the first batch.
batchPlace <- 1
tmpInd <- mnn.out$batch==levels(mnn.out$batch)[batchPlace]
tab <- coassignProb(colLabels(rescaled.mbn[[batchPlace]]), clusters.mnn[tmpInd])
heat1 <- pheatmap(tab, cluster_row=FALSE, cluster_col=FALSE,
    col=rev(viridis::magma(100)), main=sprintf("%s probabilities", splVec[batchPlace]), silent=TRUE)

# For the second batch.
batchPlace <- 2
tmpInd <- mnn.out$batch==levels(mnn.out$batch)[batchPlace]
tab <- coassignProb(colLabels(rescaled.mbn[[batchPlace]]), clusters.mnn[tmpInd])
heat2 <- pheatmap(tab, cluster_row=FALSE, cluster_col=FALSE,
    col=rev(viridis::magma(100)), main=sprintf("%s probabilities", splVec[batchPlace]), silent=TRUE)

gridExtra::grid.arrange(heat1[[4]], heat2[[4]])

Rand index:

library(fossil)
batchPlace <- 1
tmpInd <- mnn.out$batch==levels(mnn.out$batch)[batchPlace]
ri1 <- rand.index(as.integer(clusters.mnn[tmpInd]),
    as.integer(colLabels(rescaled.mbn[[batchPlace]])))
ri1
[1] 0.745986
batchPlace <- 2
tmpInd <- mnn.out$batch==levels(mnn.out$batch)[batchPlace]
ri2 <- rand.index(as.integer(clusters.mnn[tmpInd]),
    as.integer(colLabels(rescaled.mbn[[batchPlace]])))
ri2
[1] 0.7172673

Cluster markers:

m.out <- findMarkers(uncorrected, clusters.mnn, block=uncorrected$batch,
    direction="up", lfc=1, row.data=rowData(uncorrected)[,c("ensembl_gene_id","Symbol"),drop=FALSE])
Warning in FUN(...): no within-block comparison between 19 and 1
Warning in FUN(...): no within-block comparison between 19 and 4
Warning in FUN(...): no within-block comparison between 19 and 13
Warning in FUN(...): no within-block comparison between 21 and 19
Warning in FUN(...): no within-block comparison between 22 and 19
Warning in FUN(...): no within-block comparison between 27 and 19
demo <- m.out[["1"]]
as.data.frame(demo[1:20,c("Symbol", "Top", "p.value", "FDR")]) 

Expression level fot the top gene, ENSG00000090013:

geneEnsId <- rownames(demo)[1]

plotExpression(uncorrected, x=I(factor(clusters.mnn)), 
    features=geneEnsId, colour_by="batch") + facet_wrap(~colour_by)

1.8 Session information

sessionInfo()
R version 4.0.0 (2020-04-24)
Platform: x86_64-pc-linux-gnu (64-bit)
Running under: CentOS Linux 7 (Core)

Matrix products: default
BLAS:   /home/bioinformatics/software/R/R-4.0.0/lib64/R/lib/libRblas.so
LAPACK: /home/bioinformatics/software/R/R-4.0.0/lib64/R/lib/libRlapack.so

locale:
 [1] LC_CTYPE=en_GB.UTF-8       LC_NUMERIC=C              
 [3] LC_TIME=en_GB.UTF-8        LC_COLLATE=en_GB.UTF-8    
 [5] LC_MONETARY=en_GB.UTF-8    LC_MESSAGES=en_GB.UTF-8   
 [7] LC_PAPER=en_GB.UTF-8       LC_NAME=C                 
 [9] LC_ADDRESS=C               LC_TELEPHONE=C            
[11] LC_MEASUREMENT=en_GB.UTF-8 LC_IDENTIFICATION=C       

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

other attached packages:
 [1] fossil_0.4.0                shapefiles_0.7             
 [3] foreign_0.8-79              maps_3.3.0                 
 [5] sp_1.4-1                    pheatmap_1.0.12            
 [7] batchelor_1.4.0             BiocSingular_1.4.0         
 [9] dplyr_1.0.0                 scran_1.16.0               
[11] scater_1.16.0               SingleCellExperiment_1.10.1
[13] SummarizedExperiment_1.18.2 DelayedArray_0.14.1        
[15] matrixStats_0.56.0          Biobase_2.48.0             
[17] GenomicRanges_1.40.0        GenomeInfoDb_1.24.2        
[19] IRanges_2.22.2              S4Vectors_0.26.1           
[21] BiocGenerics_0.34.0         ggplot2_3.3.2              

loaded via a namespace (and not attached):
 [1] viridis_0.5.1             edgeR_3.30.0             
 [3] jsonlite_1.6.1            viridisLite_0.3.0        
 [5] DelayedMatrixStats_1.10.0 statmod_1.4.34           
 [7] dqrng_0.2.1               GenomeInfoDbData_1.2.3   
 [9] vipor_0.4.5               yaml_2.2.1               
[11] pillar_1.4.6              lattice_0.20-41          
[13] glue_1.4.1                limma_3.44.1             
[15] digest_0.6.25             RColorBrewer_1.1-2       
[17] XVector_0.28.0            colorspace_1.4-1         
[19] cowplot_1.0.0             htmltools_0.4.0          
[21] Matrix_1.2-18             pkgconfig_2.0.3          
[23] zlibbioc_1.34.0           purrr_0.3.4              
[25] scales_1.1.1              RSpectra_0.16-0          
[27] Rtsne_0.15                BiocParallel_1.22.0      
[29] tibble_3.0.3              generics_0.0.2           
[31] farver_2.0.3              ellipsis_0.3.1           
[33] withr_2.2.0               magrittr_1.5             
[35] crayon_1.3.4              evaluate_0.14            
[37] beeswarm_0.2.3            tools_4.0.0              
[39] lifecycle_0.2.0           stringr_1.4.0            
[41] munsell_0.5.0             locfit_1.5-9.4           
[43] irlba_2.3.3               compiler_4.0.0           
[45] rsvd_1.0.3                rlang_0.4.7              
[47] grid_4.0.0                RCurl_1.98-1.2           
[49] BiocNeighbors_1.6.0       RcppAnnoy_0.0.16         
[51] igraph_1.2.5              bitops_1.0-6             
[53] base64enc_0.1-3           labeling_0.3             
[55] rmarkdown_2.1             gtable_0.3.0             
[57] codetools_0.2-16          R6_2.4.1                 
[59] gridExtra_2.3             knitr_1.28               
[61] uwot_0.1.8                stringi_1.4.6            
[63] ggbeeswarm_0.6.0          Rcpp_1.0.5               
[65] vctrs_0.3.2               tidyselect_1.1.0         
[67] xfun_0.13                
LS0tCnRpdGxlOiAiQ1JVSyBDSSBTdW1tZXIgU2Nob29sIDIwMjAgLSBpbnRyb2R1Y3Rpb24gdG8gc2luZ2xlLWNlbGwgUk5BLXNlcSBhbmFseXNpcyIKc3VidGl0bGU6ICdEYXRhIGludGVncmF0aW9uJwoKYXV0aG9yOiAiU3RlcGhhbmUgQmFsbGVyZWF1IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHllcwogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICBodG1sX2Jvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKcGFyYW1zOgogIG91dERpckJpdDogIkFuYVdpU2NlL0F0dGVtcHQxIgotLS0KCgpgYGB7cn0KYGBgCgpgYGB7cn0KcHJvakRpciA8LSAiL21udC9zY3JhdGNoYS9iaW9pbmZvcm1hdGljcy9iYWxsZXIwMS8yMDIwMDUxMV9GZXJuYW5kZXNNX01FX2NydWtCaVNzMjAyMCIKb3V0RGlyQml0IDwtICJBbmFXaVNjZS9BdHRlbXB0MSIKbmJQY1RvQ29tcCA8LSA1MApgYGAKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFLCBlY2hvPUZBTFNFfQojIEZpcnN0LCBzZXQgc29tZSB2YXJpYWJsZXM6CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKb3B0aW9ucyhzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCnNldC5zZWVkKDEyMykgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CmtuaXRyOjpvcHRzX2NodW5rJHNldChldmFsID0gVFJVRSkgCmBgYAoKYGBge3IsIGluY2x1ZGU9RkFMU0V9CmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShzY2F0ZXIpCmxpYnJhcnkoc2NyYW4pCmxpYnJhcnkoZHBseXIpCmZvbnRzaXplIDwtIHRoZW1lKGF4aXMudGV4dD1lbGVtZW50X3RleHQoc2l6ZT0xMiksIGF4aXMudGl0bGU9ZWxlbWVudF90ZXh0KHNpemU9MTYpKQpgYGAKCiMgRGF0YSBpbnRlZ3JhdGlvbgoKU291cmNlOiBbSW50ZWdyYXRpbmcgRGF0YXNldHNdKGh0dHBzOi8vb3NjYS5iaW9jb25kdWN0b3Iub3JnL2ludGVncmF0aW5nLWRhdGFzZXRzLmh0bWwpIG9mIHRoZSBPU0NBIGJvb2sgYW5kIHRoZSBbZmFzdE1OTiBtYW51YWxdKGh0dHBzOi8vcmRyci5pby9naXRodWIvTFRMQS9iYXRjaGVsb3IvbWFuL2Zhc3RNTk4uaHRtbCkuCgojIyBNb3RpdmF0aW9uCgpMYXJnZSBzaW5nbGUtY2VsbCBSTkEgc2VxdWVuY2luZyAoc2NSTkEtc2VxKSBwcm9qZWN0cyB1c3VhbGx5IG5lZWQgdG8gZ2VuZXJhdGUgZGF0YSBhY3Jvc3MgbXVsdGlwbGUgYmF0Y2hlcyBkdWUgdG8gbG9naXN0aWNhbCBjb25zdHJhaW50cy4gSG93ZXZlciwgdGhlIHByb2Nlc3Npbmcgb2YgZGlmZmVyZW50IGJhdGNoZXMgaXMgb2Z0ZW4gc3ViamVjdCB0byB1bmNvbnRyb2xsYWJsZSBkaWZmZXJlbmNlcywgZS5nLiwgY2hhbmdlcyBpbiBvcGVyYXRvciwgZGlmZmVyZW5jZXMgaW4gcmVhZ2VudCBxdWFsaXR5LiBUaGlzIHJlc3VsdHMgaW4gc3lzdGVtYXRpYyBkaWZmZXJlbmNlcyBpbiB0aGUgb2JzZXJ2ZWQgZXhwcmVzc2lvbiBpbiBjZWxscyBmcm9tIGRpZmZlcmVudCBiYXRjaGVzLCB3aGljaCB3ZSByZWZlciB0byBhcyDigJxiYXRjaCBlZmZlY3Rz4oCdLiBCYXRjaCBlZmZlY3RzIGFyZSBwcm9ibGVtYXRpYyBhcyB0aGV5IGNhbiBiZSBtYWpvciBkcml2ZXJzIG9mIGhldGVyb2dlbmVpdHkgaW4gdGhlIGRhdGEsIG1hc2tpbmcgdGhlIHJlbGV2YW50IGJpb2xvZ2ljYWwgZGlmZmVyZW5jZXMgYW5kIGNvbXBsaWNhdGluZyBpbnRlcnByZXRhdGlvbiBvZiB0aGUgcmVzdWx0cy4KCkNvbXB1dGF0aW9uYWwgY29ycmVjdGlvbiBvZiB0aGVzZSBlZmZlY3RzIGlzIGNyaXRpY2FsIGZvciBlbGltaW5hdGluZyBiYXRjaC10by1iYXRjaCB2YXJpYXRpb24sIGFsbG93aW5nIGRhdGEgYWNyb3NzIG11bHRpcGxlIGJhdGNoZXMgdG8gYmUgY29tYmluZWQgZm9yIGNvbW1vbiBkb3duc3RyZWFtIGFuYWx5c2lzLiBIb3dldmVyLCBleGlzdGluZyBtZXRob2RzIGJhc2VkIG9uIGxpbmVhciBtb2RlbHMgKFJpdGNoaWUgZXQgYWwuIDIwMTU7IExlZWsgZXQgYWwuIDIwMTIpIGFzc3VtZSB0aGF0IHRoZSBjb21wb3NpdGlvbiBvZiBjZWxsIHBvcHVsYXRpb25zIGFyZSBlaXRoZXIga25vd24gb3IgdGhlIHNhbWUgYWNyb3NzIGJhdGNoZXMuIFRvIG92ZXJjb21lIHRoZXNlIGxpbWl0YXRpb25zLCBiZXNwb2tlIG1ldGhvZHMgaGF2ZSBiZWVuIGRldmVsb3BlZCBmb3IgYmF0Y2ggY29ycmVjdGlvbiBvZiBzaW5nbGUtY2VsbCBkYXRhIChIYWdodmVyZGkgZXQgYWwuIDIwMTg7IEJ1dGxlciBldCBhbC4gMjAxODsgTGluIGV0IGFsLiAyMDE5KSB0aGF0IGRvIG5vdCByZXF1aXJlIGEgcHJpb3JpIGtub3dsZWRnZSBhYm91dCB0aGUgY29tcG9zaXRpb24gb2YgdGhlIHBvcHVsYXRpb24uIFRoaXMgYWxsb3dzIHRoZW0gdG8gYmUgdXNlZCBpbiB3b3JrZmxvd3MgZm9yIGV4cGxvcmF0b3J5IGFuYWx5c2VzIG9mIHNjUk5BLXNlcSBkYXRhIHdoZXJlIHN1Y2gga25vd2xlZGdlIGlzIHVzdWFsbHkgdW5hdmFpbGFibGUuCgojIyBMb2FkIGRhdGEKCldlIHdpbGwgbG9hZCB0aGUgUiBmaWxlIGtlZXBpbmcgdGhlIFNDRSBvYmplY3Qgd2l0aCB0aGUgbm9ybWFsaXNlZCBjb3VudHMuCgpgYGB7cn0Kc2V0TmFtZSA8LSAiY2Fyb24iCiMgUmVhZCBvYmplY3QgaW46CnNldFN1ZiA8LSAiIgp0bXBGbiA8LSBzcHJpbnRmKCIlcy8lcy9Sb2JqZWN0cy8lc19zY2VfbnpfcG9zdERlY29udiVzLlJkcyIsIHByb2pEaXIsIG91dERpckJpdCwgc2V0TmFtZSwgc2V0U3VmKQpwcmludCh0bXBGbikKaWYoIWZpbGUuZXhpc3RzKHRtcEZuKSkKewoJa25pdHI6OmtuaXRfZXhpdCgpCn0Kc2NlIDwtIHJlYWRSRFModG1wRm4pCnNjZQpjb2xuYW1lcyhyb3dEYXRhKHNjZSkpW2NvbG5hbWVzKHJvd0RhdGEoc2NlKSkgPT0gInN0cmFuZCJdIDwtICJzdHJhbmROdW0iICMgdG8gYXZvaWQgZXJyb3IgbGF0ZXIKCiNoZWFkKHJvd0RhdGEoc2NlKSkKI2hlYWQoY29sRGF0YShzY2UpKQojYXNzYXlOYW1lcyhzY2UpCiNyZWR1Y2VkRGltTmFtZXMoc2NlKQpgYGAKClJlYWQgaW4gdGhlIHNhbXBsZSBzaGVldDoKCmBgYHtyfQojwqBDYXJvbkJvdXJxdWUyMDIwCmNiX3NhbXBsZVNoZWV0Rm4gPC0gZmlsZS5wYXRoKHByb2pEaXIsICJEYXRhL0Nhcm9uQm91cnF1ZTIwMjAvU3JhUnVuVGFibGUudHh0IikKY2Jfc2FtcGxlU2hlZXQgPC0gcmVhZC50YWJsZShjYl9zYW1wbGVTaGVldEZuLCBoZWFkZXI9VCwgc2VwPSIsIikKY2Jfc2FtcGxlU2hlZXQgPC0gIGNiX3NhbXBsZVNoZWV0ICU+JSBmaWx0ZXIoIVJ1biA9PSAiU1JSOTI2NDM1MSIpCmNiX3NhbXBsZVNoZWV0CmBgYAoKSGF2ZSBzYW1wbGUgbmFtZXM6CgpgYGB7cn0KY2Jfc2FtcGxlU2hlZXQkU2FtcGxlLk5hbWUyIDwtICJOQSIKdHlwZVZlYyA8LSB1bmlxdWUoY2Jfc2FtcGxlU2hlZXQkc291cmNlX25hbWUpCmZvciAodHggaW4gdHlwZVZlYykKewoJdG1wSW5kIDwtIHdoaWNoKGNiX3NhbXBsZVNoZWV0JHNvdXJjZV9uYW1lID09IHR4KQoJZm9yIChpIGluIDE6bGVuZ3RoKHRtcEluZCkpCgl7CgkJY2Jfc2FtcGxlU2hlZXQkU2FtcGxlLk5hbWUyW3RtcEluZFtpXV0gPC0gc3ByaW50ZigiJXNfJXMiLCB0eCwgaSkKCX0KfQoKY29sRGF0YShzY2UpJFNhbXBsZS5OYW1lMiA8LSBjb2xEYXRhKHNjZSkgJT4lCglkYXRhLmZyYW1lKCkgJT4lCglsZWZ0X2pvaW4oIGNiX3NhbXBsZVNoZWV0LCBieT0iU2FtcGxlLk5hbWUiKSAlPiUKCXB1bGwoU2FtcGxlLk5hbWUyKQoKc3BsVmVjIDwtIGNiX3NhbXBsZVNoZWV0ICU+JQoJI2ZpbHRlcihzb3VyY2VfbmFtZSA9PSAiRVRWNi1SVU5YMSIpICU+JQoJcHVsbChTYW1wbGUuTmFtZTIpCnNwbFZlYwpgYGAKClN1YnNldCBjZWxscyAoMTAwMCBwZXIgc2FtcGxlIGZvciBhIGZhc3RlciBhbmFseXNpcyk6CgpgYGB7cn0Kc2NlT3JpZyA8LSBzY2UKbmJDZWxscyA8LSAxMDAwCnNldFN1ZiA8LSAiXzFrQ3BzIiAjICJfMWtDZWxsUGVyU3BsIgoKYWxsLnNjZSA8LSBsaXN0KCkKZm9yKHNweCBpbiBzcGxWZWMpCnsKCXZlYy5iYyA8LSBjb2xEYXRhKHNjZSkgJT4lCgkJZGF0YS5mcmFtZSgpICU+JQoJCWZpbHRlcihTYW1wbGUuTmFtZTIgPT0gc3B4KSAlPiUKCQlzYW1wbGVfbihuYkNlbGxzKSAlPiUKCQlwdWxsKEJhcmNvZGUpCgl0bXBJbmQgPC0gd2hpY2goY29sRGF0YShzY2UpJEJhcmNvZGUgJWluJSB2ZWMuYmMpCglhbGwuc2NlW1tzcHhdXSA8LSBzY2VbLHRtcEluZF0KfQojIHNob3cgc2l6ZSBvZiBzZXRzOgpsYXBwbHkoYWxsLnNjZSwgZGltKQojIHNpemVGYWN0b3JzKGFsbC5zY2VbWzFdXSkKYGBgCgpXZSB3aWxsIGFuYWx5c2UgZWFjaCBzYW1wbGUgc2VwYXJhdGVseSwgbmFtZWx5OgoKKiBub3JtYWxpc2UgY291bnRzCiogbW9kZWwgZ2VuZSBleHByZXNzaW9uIHZhcmlhbmNlCiogaWRlbnRpZnkgaGlnaGx5IHZhcmlhYmxlIGdlbmVzCiogcGVyZm9ybSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24KKiBjbHVzdGVyIGNlbGxzCgpgYGB7cn0KIy0tLSBub3JtYWxpemF0aW9uIC0tLSMKYWxsLnNjZSA8LSBsYXBwbHkoYWxsLnNjZSwgbG9nTm9ybUNvdW50cykKCiMtLS0gdmFyaWFuY2UtbW9kZWxsaW5nIC0tLSMKbGlicmFyeShzY3JhbikKYWxsLmRlYyA8LSBsYXBwbHkoYWxsLnNjZSwgbW9kZWxHZW5lVmFyKQphbGwuaHZncyA8LSBsYXBwbHkoYWxsLmRlYywgZ2V0VG9wSFZHcywgcHJvcD0wLjEpCgojLS0tIGRpbWVuc2lvbmFsaXR5LXJlZHVjdGlvbiAtLS0jCmxpYnJhcnkoQmlvY1Npbmd1bGFyKQpzZXQuc2VlZCgxMDAwMCkKYWxsLnNjZSA8LSBtYXBwbHkoRlVOPXJ1blBDQSwgeD1hbGwuc2NlLCBzdWJzZXRfcm93PWFsbC5odmdzLCAKICAgIE1vcmVBcmdzPWxpc3QobmNvbXBvbmVudHM9MjUsIEJTUEFSQU09UmFuZG9tUGFyYW0oKSksIAogICAgU0lNUExJRlk9RkFMU0UpCgojIFRTTkUKI3NldC5zZWVkKDEwMDAwMCkKI2FsbC5zY2UgPC0gbGFwcGx5KGFsbC5zY2UsIHJ1blRTTkUsIGRpbXJlZD0iUENBIikKCiMgVU1BUAojc2V0LnNlZWQoMTAwMDAwMCkKI2FsbC5zY2UgPC0gbGFwcGx5KGFsbC5zY2UsIHJ1blVNQVAsIGRpbXJlZD0iUENBIikKCiMtLS0gY2x1c3RlcmluZyAtLS0jCmZvciAobiBpbiBuYW1lcyhhbGwuc2NlKSkgewogICAgZyA8LSBidWlsZFNOTkdyYXBoKGFsbC5zY2VbW25dXSwgaz0xMCwgdXNlLmRpbXJlZD0nUENBJykKICAgIGNsdXN0IDwtIGlncmFwaDo6Y2x1c3Rlcl93YWxrdHJhcChnKSRtZW1iZXJzaGlwCiAgICBjb2xMYWJlbHMoYWxsLnNjZVtbbl1dKSAgPC0gZmFjdG9yKGNsdXN0KQp9CmBgYAoKVG8gcHJlcGFyZSBmb3IgdGhlIGJhdGNoIGNvcnJlY3Rpb24sIHdlIG5lZWQgdG86CgoqIHN1YnNldCBhbGwgYmF0Y2hlcyB0byB0aGUgY29tbW9uIOKAnHVuaXZlcnNl4oCdIG9mIGZlYXR1cmVzCiogcmVzY2FsZSBlYWNoIGJhdGNoIHRvIGFkanVzdCBmb3IgZGlmZmVyZW5jZXMgaW4gc2VxdWVuY2luZyBkZXB0aCBiZXR3ZWVuIGJhdGNoZXMKKiBwZXJmb3JtIGZlYXR1cmUgc2VsZWN0aW9uIGJ5IGF2ZXJhZ2luZyB0aGUgdmFyaWFuY2UgY29tcG9uZW50cyBhY3Jvc3MgYWxsIGJhdGNoZXMgIAoKV2Ugc3Vic2V0IGFsbCBiYXRjaGVzIHRvIHRoZSBjb21tb24g4oCcdW5pdmVyc2XigJ0gb2YgZmVhdHVyZXMuIEluIHRoaXMgY2FzZSwgaXQgaXMgc3RyYWlnaHRmb3J3YXJkIGFzIGJvdGggYmF0Y2hlcyB1c2UgRW5zZW1ibCBnZW5lIGFubm90YXRpb24uCgpgYGB7cn0KYWxsTmFtZXMgPC0gdW5saXN0KGxhcHBseShhbGwuc2NlLCBmdW5jdGlvbih4KXtyb3duYW1lcyh4KX0pKQphbGxOYW1lc05iIDwtIHRhYmxlKGFsbE5hbWVzKQp1bml2ZXJzZSA8LSBuYW1lcyhhbGxOYW1lc05iKVthbGxOYW1lc05iPT1sZW5ndGgoc3BsVmVjKV0gCmxlbmd0aCh1bml2ZXJzZSkKYGBgCgpgYGB7cn0KIyBTdWJzZXR0aW5nIHRoZSBTaW5nbGVDZWxsRXhwZXJpbWVudCBvYmplY3QuCnVuaS5zY2UgPC0gbGFwcGx5KGFsbC5zY2UsIGZ1bmN0aW9uKHgpe3hbdW5pdmVyc2UsXX0pCiMgQWxzbyBzdWJzZXR0aW5nIHRoZSB2YXJpYW5jZSBtb2RlbGxpbmcgcmVzdWx0cywgZm9yIGNvbnZlbmllbmNlLgp1bmkuZGVjIDwtIGxhcHBseShhbGwuZGVjLCBmdW5jdGlvbih4KXt4W3VuaXZlcnNlLF19KQpgYGAKCldlIHJlc2NhbGUgZWFjaCBiYXRjaCB0byBhZGp1c3QgZm9yIGRpZmZlcmVuY2VzIGluIHNlcXVlbmNpbmcgZGVwdGggYmV0d2VlbiBiYXRjaGVzLiBUaGUgbXVsdGlCYXRjaE5vcm0oKSBmdW5jdGlvbiByZWNvbXB1dGVzIGxvZy1ub3JtYWxpemVkIGV4cHJlc3Npb24gdmFsdWVzIGFmdGVyIGFkanVzdGluZyB0aGUgc2l6ZSBmYWN0b3JzIGZvciBzeXN0ZW1hdGljIGRpZmZlcmVuY2VzIGluIGNvdmVyYWdlIGJldHdlZW4gU2luZ2xlQ2VsbEV4cGVyaW1lbnQgb2JqZWN0cy4gKFNpemUgZmFjdG9ycyBvbmx5IHJlbW92ZSBiaWFzZXMgYmV0d2VlbiBjZWxscyB3aXRoaW4gYSBzaW5nbGUgYmF0Y2guKSBUaGlzIGltcHJvdmVzIHRoZSBxdWFsaXR5IG9mIHRoZSBjb3JyZWN0aW9uIGJ5IHJlbW92aW5nIG9uZSBhc3BlY3Qgb2YgdGhlIHRlY2huaWNhbCBkaWZmZXJlbmNlcyBiZXR3ZWVuIGJhdGNoZXMuCgpgYGB7cn0KbGlicmFyeShiYXRjaGVsb3IpCnJlc2NhbGVkLm1ibiA8LSBtdWx0aUJhdGNoTm9ybSh1bmkuc2NlLCBiYXRjaCA9ICJTYW1wbGUuTmFtZTIiKQpgYGAKCldlIHBlcmZvcm0gZmVhdHVyZSBzZWxlY3Rpb24gYnkgYXZlcmFnaW5nIHRoZSB2YXJpYW5jZSBjb21wb25lbnRzIGFjcm9zcyBhbGwgYmF0Y2hlcyB3aXRoIHRoZSBjb21iaW5lVmFyKCkgZnVuY3Rpb24uIFdlIGNvbXB1dGUgdGhlIGF2ZXJhZ2UgYXMgaXQgaXMgcmVzcG9uc2l2ZSB0byBiYXRjaC1zcGVjaWZpYyBIVkdzIHdoaWxlIHN0aWxsIHByZXNlcnZpbmcgdGhlIHdpdGhpbi1iYXRjaCByYW5raW5nIG9mIGdlbmVzLiBUaGlzIGFsbG93cyB1cyB0byB1c2UgdGhlIHNhbWUgc3RyYXRlZ2llcyBkZXNjcmliZWQgaW4gU2VjdGlvbiA4LjMgdG8gc2VsZWN0IGdlbmVzIG9mIGludGVyZXN0LiBJbiBjb250cmFzdCwgYXBwcm9hY2hlcyBiYXNlZCBvbiB0YWtpbmcgdGhlIGludGVyc2VjdGlvbiBvciB1bmlvbiBvZiBIVkdzIGFjcm9zcyBiYXRjaGVzIGJlY29tZSBpbmNyZWFzaW5nbHkgY29uc2VydmF0aXZlIG9yIGxpYmVyYWwsIHJlc3BlY3RpdmVseSwgd2l0aCBhbiBpbmNyZWFzaW5nIG51bWJlciBvZiBiYXRjaGVzLgoKYGBge3J9CmxpYnJhcnkoc2NyYW4pCmNvbWJpbmVkLmRlYyA8LSBjb21iaW5lVmFyKHVuaS5kZWNbWzFdXSwgdW5pLmRlY1tbMl1dLCB1bmkuZGVjW1szXV0sIHVuaS5kZWNbWzRdXSwKCQkJICAgdW5pLmRlY1tbNV1dLCB1bmkuZGVjW1s2XV0sCgkJCSAgIHVuaS5kZWNbWzddXSwgdW5pLmRlY1tbOF1dLAoJCQkgICB1bmkuZGVjW1s5XV0sIHVuaS5kZWNbWzEwXV0sIHVuaS5kZWNbWzExXV0KCQkJKQpjaG9zZW4uaHZncyA8LSBjb21iaW5lZC5kZWMkYmlvID4gMApzdW0oY2hvc2VuLmh2Z3MpCmBgYAoKV2hlbiBpbnRlZ3JhdGluZyBkYXRhc2V0cyBvZiB2YXJpYWJsZSBjb21wb3NpdGlvbiwgaXQgaXMgZ2VuZXJhbGx5IHNhZmVyIHRvIGVyciBvbiB0aGUgc2lkZSBvZiBpbmNsdWRpbmcgbW9yZSBnZW5lcyB0aGFuIGFyZSB1c2VkIGluIGEgc2luZ2xlIGRhdGFzZXQgYW5hbHlzaXMsIHRvIGVuc3VyZSB0aGF0IG1hcmtlcnMgYXJlIHJldGFpbmVkIGZvciBhbnkgZGF0YXNldC1zcGVjaWZpYyBzdWJwb3B1bGF0aW9ucyB0aGF0IG1pZ2h0IGJlIHByZXNlbnQuIEZvciBhIHRvcCBYIHNlbGVjdGlvbiwgdGhpcyBtZWFucyB1c2luZyBhIGxhcmdlciBYIChzYXksIH41MDAwKSwgb3IgaW4gdGhpcyBjYXNlLCB3ZSBzaW1wbHkgdGFrZSBhbGwgZ2VuZXMgYWJvdmUgdGhlIHRyZW5kLiBUaGF0IHNhaWQsIG1hbnkgb2YgdGhlIHNpZ25hbC10by1ub2lzZSBjb25zaWRlcmF0aW9ucyBkZXNjcmliZWQgaW4gU2VjdGlvbiA4LjMgc3RpbGwgYXBwbHkgaGVyZSwgc28gc29tZSBleHBlcmltZW50YXRpb24gbWF5IGJlIG5lY2Vzc2FyeSBmb3IgYmVzdCByZXN1bHRzLgoKQWx0ZXJuYXRpdmVseSwgYSBtb3JlIGZvcmNlZnVsIGFwcHJvYWNoIHRvIGZlYXR1cmUgc2VsZWN0aW9uIGNhbiBiZSB1c2VkIGJhc2VkIG9uIG1hcmtlciBnZW5lcyBmcm9tIHdpdGhpbi1iYXRjaCBjb21wYXJpc29ucy4KCgojIyBEaWFnbm9zaW5nIGJhdGNoIGVmZmVjdHMKCkJlZm9yZSB3ZSBhY3R1YWxseSBwZXJmb3JtIGFueSBjb3JyZWN0aW9uLCBpdCBpcyB3b3J0aCBleGFtaW5pbmcgd2hldGhlciB0aGVyZSBpcyBhbnkgYmF0Y2ggZWZmZWN0IGluIHRoaXMgZGF0YXNldC4gV2UgY29tYmluZSB0aGUgU2luZ2xlQ2VsbEV4cGVyaW1lbnRzIGFuZCBwZXJmb3JtIGEgUENBIG9uIHRoZSBsb2ctZXhwcmVzc2lvbiB2YWx1ZXMgZm9yIGFsbCBnZW5lcyB3aXRoIHBvc2l0aXZlIChhdmVyYWdlKSBiaW9sb2dpY2FsIGNvbXBvbmVudHMuCgpgYGB7cn0KIyBTeW5jaHJvbml6aW5nIHRoZSBtZXRhZGF0YSBmb3IgY2JpbmQoKWluZy4KI2lkZW50aWNhbChyb3dEYXRhKHJlc2NhbGVkLm1ibltbMV1dKSwgcm93RGF0YShyZXNjYWxlZC5tYm5bWzJdXSkpCiNpZGVudGljYWwocm93RGF0YShyZXNjYWxlZC5tYm5bWzFdXSksIHJvd0RhdGEocmVzY2FsZWQubWJuW1szXV0pKQojaWRlbnRpY2FsKHJvd0RhdGEocmVzY2FsZWQubWJuW1sxXV0pLCByb3dEYXRhKHJlc2NhbGVkLm1ibltbNF1dKSkKCnJlc2NhbGVkMiA8LSBsYXBwbHkocmVzY2FsZWQubWJuLCBmdW5jdGlvbih4KXt4JGJhdGNoIDwtIHgkU2FtcGxlLk5hbWUyOyB4fSkKcmVzY2FsZWQubWJuIDwtIHJlc2NhbGVkMgpybShyZXNjYWxlZDIpCgp1bmNvcnJlY3RlZCA8LSBkby5jYWxsKGNiaW5kLCByZXNjYWxlZC5tYm4pCgojIFVzaW5nIFJhbmRvbVBhcmFtKCkgYXMgaXQgaXMgbW9yZSBlZmZpY2llbnQgZm9yIGZpbGUtYmFja2VkIG1hdHJpY2VzLgpsaWJyYXJ5KHNjYXRlcikKc2V0LnNlZWQoMDAxMDEwMTAxMCkKdW5jb3JyZWN0ZWQgPC0gcnVuUENBKHVuY29ycmVjdGVkLCBzdWJzZXRfcm93PWNob3Nlbi5odmdzLAogICAgQlNQQVJBTT1CaW9jU2luZ3VsYXI6OlJhbmRvbVBhcmFtKCkpCmBgYAoKV2UgdXNlIGdyYXBoLWJhc2VkIGNsdXN0ZXJpbmcgb24gdGhlIGNvbXBvbmVudHMgdG8gb2J0YWluIGEgc3VtbWFyeSBvZiB0aGUgcG9wdWxhdGlvbiBzdHJ1Y3R1cmUuIEFzIG91ciBlYWNoIHNhbXBsZSBncm91cCBpc3JlcHJlc2VudGVkIGJ5IGF0IGxlYXN0IHR3byByZXBsaWNhdGVzLCBlYWNoIGNsdXN0ZXIgc2hvdWxkIGlkZWFsbHkgY29uc2lzdCBvZiBjZWxscyBmcm9tIHNldmVyYWwgYmF0Y2hlcy4gVGhpcyBpcyB0aGUgY2FzZSBmb3Igc29tZSBidXQgbm90IGFsbCBjbHVzdGVycy4gU29tZSBjbHVzdGVycyBjb21wcmlzZSBvZiBjZWxscyBmcm9tIGEgc2luZ2xlIHNhbXBsZS4gVGhpcyBtYXkgaW5kaWNhdGUgdGhhdCBjZWxscyBvZiB0aGUgc2FtZSB0eXBlIGFyZSBhcnRpZmljaWFsbHkgc2VwYXJhdGVkIGR1ZSB0byB0ZWNobmljYWwgZGlmZmVyZW5jZXMgYmV0d2VlbiBiYXRjaGVzLiBUaGV5IG1heSBhbHNvIGJlIGNhbmNlciBjZWxsIHBvcHVsYXRpb24gcHJpdmF0ZSB0byBzYW1wbGVzLiAKCmBgYHtyfQpsaWJyYXJ5KHNjcmFuKQpzbm4uZ3IgPC0gYnVpbGRTTk5HcmFwaCh1bmNvcnJlY3RlZCwgdXNlLmRpbXJlZD0iUENBIikKY2x1c3RlcnMgPC0gaWdyYXBoOjpjbHVzdGVyX3dhbGt0cmFwKHNubi5ncikkbWVtYmVyc2hpcAp0YWIgPC0gdGFibGUoQ2x1c3Rlcj1jbHVzdGVycywgQmF0Y2g9dW5jb3JyZWN0ZWQkYmF0Y2gpCnRhYgpgYGAKCldlIGNhbiBhbHNvIHZpc3VhbGl6ZSB0aGUgY29ycmVjdGVkIGNvb3JkaW5hdGVzIHVzaW5nIGEgdC1TTkUgcGxvdCAoRmlndXJlIDEzLjEpLiBUaGUgc3Ryb25nIHNlcGFyYXRpb24gYmV0d2VlbiBjZWxscyBmcm9tIGRpZmZlcmVudCBiYXRjaGVzIGlzIGNvbnNpc3RlbnQgd2l0aCB0aGUgY2x1c3RlcmluZyByZXN1bHRzLgoKYGBge3J9CnNldC5zZWVkKDExMTEwMDEpCnVuY29ycmVjdGVkIDwtIHJ1blRTTkUodW5jb3JyZWN0ZWQsIGRpbXJlZD0iUENBIikKcGxvdFRTTkUodW5jb3JyZWN0ZWQsIGNvbG91cl9ieT0iYmF0Y2giKQpgYGAKCmBgYHtyfQpzZXQuc2VlZCgxMTExMDAxKQp1bmNvcnJlY3RlZCA8LSBydW5VTUFQKHVuY29ycmVjdGVkLCBkaW1yZWQ9IlBDQSIpCnBsb3RVTUFQKHVuY29ycmVjdGVkLCBjb2xvdXJfYnk9ImJhdGNoIikKYGBgCgpPZiBjb3Vyc2UsIHRoZSBvdGhlciBleHBsYW5hdGlvbiBmb3IgYmF0Y2gtc3BlY2lmaWMgY2x1c3RlcnMgaXMgdGhhdCB0aGVyZSBhcmUgY2VsbCB0eXBlcyB0aGF0IGFyZSB1bmlxdWUgdG8gZWFjaCBiYXRjaC4gVGhlIGRlZ3JlZSBvZiBpbnRlcm1pbmdsaW5nIG9mIGNlbGxzIGZyb20gZGlmZmVyZW50IGJhdGNoZXMgaXMgbm90IGFuIGVmZmVjdGl2ZSBkaWFnbm9zdGljIHdoZW4gdGhlIGJhdGNoZXMgaW52b2x2ZWQgbWlnaHQgYWN0dWFsbHkgY29udGFpbiB1bmlxdWUgY2VsbCBzdWJwb3B1bGF0aW9ucy4gSWYgYSBjbHVzdGVyIG9ubHkgY29udGFpbnMgY2VsbHMgZnJvbSBhIHNpbmdsZSBiYXRjaCwgb25lIGNhbiBhbHdheXMgZGViYXRlIHdoZXRoZXIgdGhhdCBpcyBjYXVzZWQgYnkgYSBmYWlsdXJlIG9mIHRoZSBjb3JyZWN0aW9uIG1ldGhvZCBvciBpZiB0aGVyZSBpcyB0cnVseSBhIGJhdGNoLXNwZWNpZmljIHN1YnBvcHVsYXRpb24uIEZvciBleGFtcGxlLCBkbyBiYXRjaC1zcGVjaWZpYyBtZXRhYm9saWMgb3IgZGlmZmVyZW50aWF0aW9uIHN0YXRlcyByZXByZXNlbnQgZGlzdGluY3Qgc3VicG9wdWxhdGlvbnM/IE9yIHNob3VsZCB0aGV5IGJlIG1lcmdlZCB0b2dldGhlcj8gV2Ugd2lsbCBub3QgYXR0ZW1wdCB0byBhbnN3ZXIgdGhpcyBoZXJlLCBvbmx5IG5vdGluZyB0aGF0IGVhY2ggYmF0Y2ggY29ycmVjdGlvbiBhbGdvcml0aG0gd2lsbCBtYWtlIGRpZmZlcmVudCAoYW5kIHBvc3NpYmx5IGluYXBwcm9wcmlhdGUpIGRlY2lzaW9ucyBvbiB3aGF0IGNvbnN0aXR1dGVzIOKAnHNoYXJlZOKAnSBhbmQg4oCcdW5pcXVl4oCdIHBvcHVsYXRpb25zLgoKIyMgTGluZWFyIHJlZ3Jlc3Npb24KCkJhdGNoIGVmZmVjdHMgaW4gYnVsayBSTkEgc2VxdWVuY2luZyBzdHVkaWVzIGFyZSBjb21tb25seSByZW1vdmVkIHdpdGggbGluZWFyIHJlZ3Jlc3Npb24uIFRoaXMgaW52b2x2ZXMgZml0dGluZyBhIGxpbmVhciBtb2RlbCB0byBlYWNoIGdlbmXigJlzIGV4cHJlc3Npb24gcHJvZmlsZSwgc2V0dGluZyB0aGUgdW5kZXNpcmFibGUgYmF0Y2ggdGVybSB0byB6ZXJvIGFuZCByZWNvbXB1dGluZyB0aGUgb2JzZXJ2YXRpb25zIHNhbnMgdGhlIGJhdGNoIGVmZmVjdCwgeWllbGRpbmcgYSBzZXQgb2YgY29ycmVjdGVkIGV4cHJlc3Npb24gdmFsdWVzIGZvciBkb3duc3RyZWFtIGFuYWx5c2VzLiBMaW5lYXIgbW9kZWxsaW5nIGlzIHRoZSBiYXNpcyBvZiB0aGUgcmVtb3ZlQmF0Y2hFZmZlY3QoKSBmdW5jdGlvbiBmcm9tIHRoZSBsaW1tYSBwYWNrYWdlIChSaXRjaGllIGV0IGFsLiAyMDE1KSBhcyB3ZWxsIHRoZSBjb21CYXQoKSBmdW5jdGlvbiBmcm9tIHRoZSBzdmEgcGFja2FnZSAoTGVlayBldCBhbC4gMjAxMikuCgpUbyB1c2UgdGhpcyBhcHByb2FjaCBpbiBhIHNjUk5BLXNlcSBjb250ZXh0LCB3ZSBhc3N1bWUgdGhhdCB0aGUgY29tcG9zaXRpb24gb2YgY2VsbCBzdWJwb3B1bGF0aW9ucyBpcyB0aGUgc2FtZSBhY3Jvc3MgYmF0Y2hlcy4gV2UgYWxzbyBhc3N1bWUgdGhhdCB0aGUgYmF0Y2ggZWZmZWN0IGlzIGFkZGl0aXZlLCBpLmUuLCBhbnkgYmF0Y2gtaW5kdWNlZCBmb2xkLWNoYW5nZSBpbiBleHByZXNzaW9uIGlzIHRoZSBzYW1lIGFjcm9zcyBkaWZmZXJlbnQgY2VsbCBzdWJwb3B1bGF0aW9ucyBmb3IgYW55IGdpdmVuIGdlbmUuIFRoZXNlIGFyZSBzdHJvbmcgYXNzdW1wdGlvbnMgYXMgYmF0Y2hlcyBkZXJpdmVkIGZyb20gZGlmZmVyZW50IGluZGl2aWR1YWxzIHdpbGwgbmF0dXJhbGx5IGV4aGliaXQgdmFyaWF0aW9uIGluIGNlbGwgdHlwZSBhYnVuZGFuY2VzIGFuZCBleHByZXNzaW9uLiBOb25ldGhlbGVzcywgdGhleSBtYXkgYmUgYWNjZXB0YWJsZSB3aGVuIGRlYWxpbmcgd2l0aCBiYXRjaGVzIHRoYXQgYXJlIHRlY2huaWNhbCByZXBsaWNhdGVzIGdlbmVyYXRlZCBmcm9tIHRoZSBzYW1lIHBvcHVsYXRpb24gb2YgY2VsbHMuIChJbiBmYWN0LCB3aGVuIGl0cyBhc3N1bXB0aW9ucyBob2xkLCBsaW5lYXIgcmVncmVzc2lvbiBpcyB0aGUgbW9zdCBzdGF0aXN0aWNhbGx5IGVmZmljaWVudCBhcyBpdCB1c2VzIGluZm9ybWF0aW9uIGZyb20gYWxsIGNlbGxzIHRvIGNvbXB1dGUgdGhlIGNvbW1vbiBiYXRjaCB2ZWN0b3IuKSBMaW5lYXIgbW9kZWxsaW5nIGNhbiBhbHNvIGFjY29tbW9kYXRlIHNpdHVhdGlvbnMgd2hlcmUgdGhlIGNvbXBvc2l0aW9uIGlzIGtub3duIGEgcHJpb3JpIGJ5IGluY2x1ZGluZyB0aGUgY2VsbCB0eXBlIGFzIGEgZmFjdG9yIGluIHRoZSBsaW5lYXIgbW9kZWwsIGJ1dCB0aGlzIHNpdHVhdGlvbiBpcyBldmVuIGxlc3MgY29tbW9uLgoKV2UgdXNlIHRoZSByZXNjYWxlQmF0Y2hlcygpIGZ1bmN0aW9uIGZyb20gdGhlIGJhdGNoZWxvciBwYWNrYWdlIHRvIHJlbW92ZSB0aGUgYmF0Y2ggZWZmZWN0LiBUaGlzIGlzIHJvdWdobHkgZXF1aXZhbGVudCB0byBhcHBseWluZyBhIGxpbmVhciByZWdyZXNzaW9uIHRvIHRoZSBsb2ctZXhwcmVzc2lvbiB2YWx1ZXMgcGVyIGdlbmUsIHdpdGggc29tZSBhZGp1c3RtZW50cyB0byBpbXByb3ZlIHBlcmZvcm1hbmNlIGFuZCBlZmZpY2llbmN5LiBGb3IgZWFjaCBnZW5lLCB0aGUgbWVhbiBleHByZXNzaW9uIGluIGVhY2ggYmF0Y2ggaXMgc2NhbGVkIGRvd24gdW50aWwgaXQgaXMgZXF1YWwgdG8gdGhlIGxvd2VzdCBtZWFuIGFjcm9zcyBhbGwgYmF0Y2hlcy4gV2UgZGVsaWJlcmF0ZWx5IGNob29zZSB0byBzY2FsZSBhbGwgZXhwcmVzc2lvbiB2YWx1ZXMgZG93biBhcyB0aGlzIG1pdGlnYXRlcyBkaWZmZXJlbmNlcyBpbiB2YXJpYW5jZSB3aGVuIGJhdGNoZXMgbGllIGF0IGRpZmZlcmVudCBwb3NpdGlvbnMgb24gdGhlIG1lYW4tdmFyaWFuY2UgdHJlbmQuIChTcGVjaWZpY2FsbHksIHRoZSBzaHJpbmthZ2UgZWZmZWN0IG9mIHRoZSBwc2V1ZG8tY291bnQgaXMgZ3JlYXRlciBmb3Igc21hbGxlciBjb3VudHMsIHN1cHByZXNzaW5nIGFueSBkaWZmZXJlbmNlcyBpbiB2YXJpYW5jZSBhY3Jvc3MgYmF0Y2hlcy4pIEFuIGFkZGl0aW9uYWwgZmVhdHVyZSBvZiByZXNjYWxlQmF0Y2hlcygpIGlzIHRoYXQgaXQgd2lsbCBwcmVzZXJ2ZSBzcGFyc2l0eSBpbiB0aGUgaW5wdXQgbWF0cml4IGZvciBncmVhdGVyIGVmZmljaWVuY3ksIHdoZXJlYXMgb3RoZXIgbWV0aG9kcyBsaWtlIHJlbW92ZUJhdGNoRWZmZWN0KCkgd2lsbCBhbHdheXMgcmV0dXJuIGEgZGVuc2UgbWF0cml4LgoKYGBge3J9CmxpYnJhcnkoYmF0Y2hlbG9yKQpyZXNjYWxlZC5yYiA8LSByZXNjYWxlQmF0Y2hlcyhyZXNjYWxlZC5tYm4pCnJlc2NhbGVkLnJiCmBgYAoKQWZ0ZXIgY2x1c3RlcmluZywgd2Ugb2JzZXJ2ZSBmZXdlciBjbHVzdGVycyBhbmQgdGhlc2UgY29uc2lzdCBvZiBtaXh0dXJlcyBvZiBjZWxscyBmcm9tIHRoZSBzZXZlcmFsIHJlcGxpY2F0ZXMsIGNvbnNpc3RlbnQgd2l0aCB0aGUgcmVtb3ZhbCBvZiB0aGUgYmF0Y2ggZWZmZWN0LiBUaGlzIGNvbmNsdXNpb24gaXMgc3VwcG9ydGVkIGJ5IHRoZSBhcHBhcmVudCBtaXhpbmcgb2YgY2VsbHMgZnJvbSBkaWZmZXJlbnQgYmF0Y2hlcyBvbiB0aGUgVFNORSBwbG90IGJlbG93LiBIb3dldmVyLCBhdCBsZWFzdCBvbmUgYmF0Y2gtc3BlY2lmaWMgY2x1c3RlciBpcyBzdGlsbCBwcmVzZW50LCBpbmRpY2F0aW5nIHRoYXQgdGhlIGNvcnJlY3Rpb24gaXMgbm90IGVudGlyZWx5IGNvbXBsZXRlLiBUaGlzIGlzIGF0dHJpYnV0YWJsZSB0byB2aW9sYXRpb24gb2Ygb25lIG9mIHRoZSBhZm9yZW1lbnRpb25lZCBhc3N1bXB0aW9ucywgZXZlbiBpbiB0aGlzIHNpbXBsZSBjYXNlIGludm9sdmluZyByZXBsaWNhdGVkIGJhdGNoZXMuCgpgYGB7cn0Kc2V0LnNlZWQoMTAxMDEwMTAxMCkgIyBUbyBlbnN1cmUgcmVwcm9kdWNpYmlsaXR5IG9mIElSTEJBLgpyZXNjYWxlZC5yYiA8LSBydW5QQ0EocmVzY2FsZWQucmIsIHN1YnNldF9yb3c9Y2hvc2VuLmh2Z3MsIGV4cHJzX3ZhbHVlcz0iY29ycmVjdGVkIikKCnNubi5nciA8LSBidWlsZFNOTkdyYXBoKHJlc2NhbGVkLnJiLCB1c2UuZGltcmVkPSJQQ0EiKQpjbHVzdGVycy5yZXNjIDwtIGlncmFwaDo6Y2x1c3Rlcl93YWxrdHJhcChzbm4uZ3IpJG1lbWJlcnNoaXAKdGFiLnJlc2MgPC0gdGFibGUoQ2x1c3Rlcj1jbHVzdGVycy5yZXNjLCBCYXRjaD1yZXNjYWxlZC5yYiRiYXRjaCkKdGFiLnJlc2MKYGBgCgpgYGB7cn0KcmVzY2FsZWQucmIgPC0gcnVuVFNORShyZXNjYWxlZC5yYiwgZGltcmVkPSJQQ0EiKQpyZXNjYWxlZC5yYiRiYXRjaCA8LSBmYWN0b3IocmVzY2FsZWQucmIkYmF0Y2gpCnBsb3RUU05FKHJlc2NhbGVkLnJiLCBjb2xvdXJfYnk9ImJhdGNoIikKYGBgCgojIyBQZXJmb3JtaW5nIE1OTiBjb3JyZWN0aW9uCgojIyPCoEFsZ29yaXRobSBvdmVydmlldwoKQ29uc2lkZXIgYSBjZWxsIGEgaW4gYmF0Y2ggQSwgYW5kIGlkZW50aWZ5IHRoZSBjZWxscyBpbiBiYXRjaCBCIHRoYXQgYXJlIG5lYXJlc3QgbmVpZ2hib3JzIHRvIGEgaW4gdGhlIGV4cHJlc3Npb24gc3BhY2UgZGVmaW5lZCBieSB0aGUgc2VsZWN0ZWQgZmVhdHVyZXMuIFJlcGVhdCB0aGlzIGZvciBhIGNlbGwgYiBpbiBiYXRjaCBCLCBpZGVudGlmeWluZyBpdHMgbmVhcmVzdCBuZWlnaGJvcnMgaW4gQS4gTXV0dWFsIG5lYXJlc3QgbmVpZ2hib3JzIChNTk5zKSBhcmUgcGFpcnMgb2YgY2VsbHMgZnJvbSBkaWZmZXJlbnQgYmF0Y2hlcyB0aGF0IGJlbG9uZyBpbiBlYWNoIG90aGVy4oCZcyBzZXQgb2YgbmVhcmVzdCBuZWlnaGJvcnMuIFRoZSByZWFzb25pbmcgaXMgdGhhdCBNTk4gcGFpcnMgcmVwcmVzZW50IGNlbGxzIGZyb20gdGhlIHNhbWUgYmlvbG9naWNhbCBzdGF0ZSBwcmlvciB0byB0aGUgYXBwbGljYXRpb24gb2YgYSBiYXRjaCBlZmZlY3QgLSBzZWUgSGFnaHZlcmRpIGV0IGFsLiAoMjAxOCkgZm9yIGZ1bGwgdGhlb3JldGljYWwgZGV0YWlscy4gVGh1cywgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBjZWxscyBpbiBNTk4gcGFpcnMgY2FuIGJlIHVzZWQgYXMgYW4gZXN0aW1hdGUgb2YgdGhlIGJhdGNoIGVmZmVjdCwgdGhlIHN1YnRyYWN0aW9uIG9mIHdoaWNoIHlpZWxkcyBiYXRjaC1jb3JyZWN0ZWQgdmFsdWVzLgoKQ29tcGFyZWQgdG8gbGluZWFyIHJlZ3Jlc3Npb24sIE1OTiBjb3JyZWN0aW9uIGRvZXMgbm90IGFzc3VtZSB0aGF0IHRoZSBwb3B1bGF0aW9uIGNvbXBvc2l0aW9uIGlzIHRoZSBzYW1lIG9yIGtub3duIGJlZm9yZWhhbmQuIFRoaXMgaXMgYmVjYXVzZSBpdCBsZWFybnMgdGhlIHNoYXJlZCBwb3B1bGF0aW9uIHN0cnVjdHVyZSB2aWEgaWRlbnRpZmljYXRpb24gb2YgTU5OIHBhaXJzIGFuZCB1c2VzIHRoaXMgaW5mb3JtYXRpb24gdG8gb2J0YWluIGFuIGFwcHJvcHJpYXRlIGVzdGltYXRlIG9mIHRoZSBiYXRjaCBlZmZlY3QuIEluc3RlYWQsIHRoZSBrZXkgYXNzdW1wdGlvbiBvZiBNTk4tYmFzZWQgYXBwcm9hY2hlcyBpcyB0aGF0IHRoZSBiYXRjaCBlZmZlY3QgaXMgb3J0aG9nb25hbCB0byB0aGUgYmlvbG9neSBpbiBoaWdoLWRpbWVuc2lvbmFsIGV4cHJlc3Npb24gc3BhY2UuIFZpb2xhdGlvbnMgcmVkdWNlIHRoZSBlZmZlY3RpdmVuZXNzIGFuZCBhY2N1cmFjeSBvZiB0aGUgY29ycmVjdGlvbiwgd2l0aCB0aGUgbW9zdCBjb21tb24gY2FzZSBhcmlzaW5nIGZyb20gdmFyaWF0aW9ucyBpbiB0aGUgZGlyZWN0aW9uIG9mIHRoZSBiYXRjaCBlZmZlY3QgYmV0d2VlbiBjbHVzdGVycy4gTm9uZXRoZWxlc3MsIHRoZSBhc3N1bXB0aW9uIGlzIHVzdWFsbHkgcmVhc29uYWJsZSBhcyBhIHJhbmRvbSB2ZWN0b3IgaXMgdmVyeSBsaWtlbHkgdG8gYmUgb3J0aG9nb25hbCBpbiBoaWdoLWRpbWVuc2lvbmFsIHNwYWNlLgoKIyMjIEFwcGxpY2F0aW9uIHRvIHRoZSBkYXRhCgpUaGUgYmF0Y2hlbG9yIHBhY2thZ2UgcHJvdmlkZXMgYW4gaW1wbGVtZW50YXRpb24gb2YgdGhlIE1OTiBhcHByb2FjaCB2aWEgdGhlIGZhc3RNTk4oKSBmdW5jdGlvbi4gVW5saWtlIHRoZSBNTk4gbWV0aG9kIG9yaWdpbmFsbHkgZGVzY3JpYmVkIGJ5IEhhZ2h2ZXJkaSBldCBhbC4gKDIwMTgpLCB0aGUgZmFzdE1OTigpIGZ1bmN0aW9uIHBlcmZvcm1zIFBDQSB0byByZWR1Y2UgdGhlIGRpbWVuc2lvbnMgYmVmb3JlaGFuZCBhbmQgc3BlZWQgdXAgdGhlIGRvd25zdHJlYW0gbmVpZ2hib3IgZGV0ZWN0aW9uIHN0ZXBzLgoKV2UgYXBwbHkgaXQgdG8gb3VyIHR3byBQQk1DIGJhdGNoZXMgdG8gcmVtb3ZlIHRoZSBiYXRjaCBlZmZlY3QgYWNyb3NzIHRoZSBoaWdobHkgdmFyaWFibGUgZ2VuZXMgaW4gY2hvc2VuLmh2Z3MuIFRvIHJlZHVjZSBjb21wdXRhdGlvbmFsIHdvcmsgYW5kIHRlY2huaWNhbCBub2lzZSwgYWxsIGNlbGxzIGluIGFsbCBiYXRjaGVzIGFyZSBwcm9qZWN0ZWQgaW50byB0aGUgbG93LWRpbWVuc2lvbmFsIHNwYWNlIGRlZmluZWQgYnkgdGhlIHRvcCBkIHByaW5jaXBhbCBjb21wb25lbnRzLiBJZGVudGlmaWNhdGlvbiBvZiBNTk5zIGFuZCBjYWxjdWxhdGlvbiBvZiBjb3JyZWN0aW9uIHZlY3RvcnMgYXJlIHRoZW4gcGVyZm9ybWVkIGluIHRoaXMgbG93LWRpbWVuc2lvbmFsIHNwYWNlLgoKYGBge3J9CiMgVXNpbmcgcmFuZG9taXplZCBTVkQgaGVyZSwgYXMgdGhpcyBpcyBmYXN0ZXIgdGhhbiAKIyBpcmxiYSBmb3IgZmlsZS1iYWNrZWQgbWF0cmljZXMuCnNldC5zZWVkKDEwMDAxMDEwMDEpCm1ubi5vdXQgPC0gZmFzdE1OTihyZXNjYWxlZC5tYm4sIGF1dG8ubWVyZ2U9VFJVRSwgZD01MCwgaz0yMCwgc3Vic2V0LnJvdz1jaG9zZW4uaHZncywKICAgIEJTUEFSQU09QmlvY1Npbmd1bGFyOjpSYW5kb21QYXJhbShkZWZlcnJlZD1UUlVFKSkKbW5uLm91dAptbm4ub3V0JGJhdGNoIDwtIGZhY3Rvcihtbm4ub3V0JGJhdGNoKQptbm4ub3V0JHR5cGUgPC0gZ3N1YigiX1sxLTRdIiwiIixtbm4ub3V0JGJhdGNoKQpgYGAKClRoZSBmdW5jdGlvbiByZXR1cm5zIGEgU2luZ2xlQ2VsbEV4cGVyaW1lbnQgb2JqZWN0IGNvbnRhaW5pbmcgY29ycmVjdGVkIHZhbHVlcyBmb3IgZG93bnN0cmVhbSBhbmFseXNlcyBsaWtlIGNsdXN0ZXJpbmcgb3IgdmlzdWFsaXphdGlvbi4gRWFjaCBjb2x1bW4gb2YgbW5uLm91dCBjb3JyZXNwb25kcyB0byBhIGNlbGwgaW4gb25lIG9mIHRoZSBiYXRjaGVzLCB3aGlsZSBlYWNoIHJvdyBjb3JyZXNwb25kcyB0byBhbiBpbnB1dCBnZW5lIGluIGNob3Nlbi5odmdzLiBUaGUgYmF0Y2ggZmllbGQgaW4gdGhlIGNvbHVtbiBtZXRhZGF0YSBjb250YWlucyBhIHZlY3RvciBzcGVjaWZ5aW5nIHRoZSBiYXRjaCBvZiBvcmlnaW4gb2YgZWFjaCBjZWxsLgoKYGBge3J9CmhlYWQobW5uLm91dCRiYXRjaCkgCmBgYAoKVGhlIGNvcnJlY3RlZCBtYXRyaXggaW4gdGhlIHJlZHVjZWREaW1zKCkgY29udGFpbnMgdGhlIGxvdy1kaW1lbnNpb25hbCBjb3JyZWN0ZWQgY29vcmRpbmF0ZXMgZm9yIGFsbCBjZWxscywgd2hpY2ggd2Ugd2lsbCB1c2UgaW4gcGxhY2Ugb2YgdGhlIFBDcyBpbiBvdXIgZG93bnN0cmVhbSBhbmFseXNlcy4KCmBgYHtyfQpkaW0ocmVkdWNlZERpbShtbm4ub3V0LCAiY29ycmVjdGVkIikpCmBgYAoKQSByZWNvbnN0cnVjdGVkIG1hdHJpeCBpbiB0aGUgYXNzYXlzKCkgY29udGFpbnMgdGhlIGNvcnJlY3RlZCBleHByZXNzaW9uIHZhbHVlcyBmb3IgZWFjaCBnZW5lIGluIGVhY2ggY2VsbCwgb2J0YWluZWQgYnkgcHJvamVjdGluZyB0aGUgbG93LWRpbWVuc2lvbmFsIGNvb3JkaW5hdGVzIGluIGNvcnJlY3RlZCBiYWNrIGludG8gZ2VuZSBleHByZXNzaW9uIHNwYWNlLiBXZSBkbyBub3QgcmVjb21tZW5kIHVzaW5nIHRoaXMgZm9yIGFueXRoaW5nIG90aGVyIHRoYW4gdmlzdWFsaXphdGlvbi4KCmBgYHtyfQpkaW0oYXNzYXkobW5uLm91dCwgInJlY29uc3RydWN0ZWQiKSkKcHJpbnQoYXNzYXkobW5uLm91dCwgInJlY29uc3RydWN0ZWQiKVsxOjUsMTozXSkKYGBgCgpUaGUgbW9zdCByZWxldmFudCBwYXJhbWV0ZXIgZm9yIHR1bmluZyBmYXN0TU5OKCkgaXMgaywgd2hpY2ggc3BlY2lmaWVzIHRoZSBudW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMgdG8gY29uc2lkZXIgd2hlbiBkZWZpbmluZyBNTk4gcGFpcnMuIFRoaXMgY2FuIGJlIGludGVycHJldGVkIGFzIHRoZSBtaW5pbXVtIGFudGljaXBhdGVkIGZyZXF1ZW5jeSBvZiBhbnkgc2hhcmVkIGNlbGwgdHlwZSBvciBzdGF0ZSBpbiBlYWNoIGJhdGNoLiBJbmNyZWFzaW5nIGsgd2lsbCBnZW5lcmFsbHkgcmVzdWx0IGluIG1vcmUgYWdncmVzc2l2ZSBtZXJnaW5nIGFzIHRoZSBhbGdvcml0aG0gaXMgbW9yZSBnZW5lcm91cyBpbiBtYXRjaGluZyBzdWJwb3B1bGF0aW9ucyBhY3Jvc3MgYmF0Y2hlcy4gSXQgY2FuIG9jY2FzaW9uYWxseSBiZSBkZXNpcmFibGUgdG8gaW5jcmVhc2UgayBpZiBvbmUgY2xlYXJseSBzZWVzIHRoYXQgdGhlIHNhbWUgY2VsbCB0eXBlcyBhcmUgbm90IGJlaW5nIGFkZXF1YXRlbHkgbWVyZ2VkIGFjcm9zcyBiYXRjaGVzLgoKPCEtLQpTZWUgQ2hhcHRlciAzMiBmb3IgYW4gZXhhbXBsZSBvZiBhIG1vcmUgY29tcGxleCBmYXN0TU5OKCkgbWVyZ2UgaW52b2x2aW5nIHNldmVyYWwgaHVtYW4gcGFuY3JlYXMgZGF0YXNldHMgZ2VuZXJhdGVkIGJ5IGRpZmZlcmVudCBhdXRob3JzIG9uIGRpZmZlcmVudCBwYXRpZW50cyB3aXRoIGRpZmZlcmVudCB0ZWNobm9sb2dpZXMuCi0tPgoKCiMjIyBDb3JyZWN0aW9uIGRpYWdub3N0aWNzCgpXZSBjbHVzdGVyIG9uIHRoZSBsb3ctZGltZW5zaW9uYWwgY29ycmVjdGVkIGNvb3JkaW5hdGVzIHRvIG9idGFpbiBhIHBhcnRpdGlvbmluZyBvZiB0aGUgY2VsbHMgdGhhdCBzZXJ2ZXMgYXMgYSBwcm94eSBmb3IgdGhlIHBvcHVsYXRpb24gc3RydWN0dXJlLiBJZiB0aGUgYmF0Y2ggZWZmZWN0IGlzIHN1Y2Nlc3NmdWxseSBjb3JyZWN0ZWQsIGNsdXN0ZXJzIGNvcnJlc3BvbmRpbmcgdG8gc2hhcmVkIGNlbGwgdHlwZXMgb3Igc3RhdGVzIHNob3VsZCBjb250YWluIGNlbGxzIGZyb20gbXVsdGlwbGUgYmF0Y2hlcy4gV2Ugc2VlIHRoYXQgYWxsIGNsdXN0ZXJzIGNvbnRhaW4gY29udHJpYnV0aW9ucyBmcm9tIGVhY2ggYmF0Y2ggYWZ0ZXIgY29ycmVjdGlvbiwgY29uc2lzdGVudCB3aXRoIG91ciBleHBlY3RhdGlvbiB0aGF0IHRoZSB0d28gYmF0Y2hlcyBhcmUgcmVwbGljYXRlcyBvZiBlYWNoIG90aGVyLgoKYGBge3J9CmxpYnJhcnkoc2NyYW4pCnNubi5nciA8LSBidWlsZFNOTkdyYXBoKG1ubi5vdXQsIHVzZS5kaW1yZWQ9ImNvcnJlY3RlZCIpCmNsdXN0ZXJzLm1ubiA8LSBpZ3JhcGg6OmNsdXN0ZXJfd2Fsa3RyYXAoc25uLmdyKSRtZW1iZXJzaGlwCnRhYi5tbm4gPC0gdGFibGUoQ2x1c3Rlcj1jbHVzdGVycy5tbm4sIEJhdGNoPW1ubi5vdXQkYmF0Y2gpCnRhYi5tbm4KYGBgCgpXZSBjYW4gYWxzbyB2aXN1YWxpemUgdGhlIGNvcnJlY3RlZCBjb29yZGluYXRlcyB1c2luZyBhIHQtU05FIHBsb3QgKEZpZ3VyZSAxMy4zKS4gVGhlIHByZXNlbmNlIG9mIHZpc3VhbCBjbHVzdGVycyBjb250YWluaW5nIGNlbGxzIGZyb20gYm90aCBiYXRjaGVzIHByb3ZpZGVzIGEgY29tZm9ydGluZyBpbGx1c2lvbiB0aGF0IHRoZSBjb3JyZWN0aW9uIHdhcyBzdWNjZXNzZnVsLgoKYGBge3J9CmxpYnJhcnkoc2NhdGVyKQpzZXQuc2VlZCgwMDEwMTAxMDEwKQptbm4ub3V0IDwtIHJ1blRTTkUobW5uLm91dCwgZGltcmVkPSJjb3JyZWN0ZWQiKQoKcGxvdFRTTkUobW5uLm91dCwgY29sb3VyX2J5PSJiYXRjaCIpCmBgYAoKRm9yIGZhc3RNTk4oKSwgb25lIHVzZWZ1bCBkaWFnbm9zdGljIGlzIHRoZSBwcm9wb3J0aW9uIG9mIHZhcmlhbmNlIHdpdGhpbiBlYWNoIGJhdGNoIHRoYXQgaXMgbG9zdCBkdXJpbmcgTU5OIGNvcnJlY3Rpb24uIFNwZWNpZmljYWxseSwgdGhpcyByZWZlcnMgdG8gdGhlIHdpdGhpbi1iYXRjaCB2YXJpYW5jZSB0aGF0IGlzIHJlbW92ZWQgZHVyaW5nIG9ydGhvZ29uYWxpemF0aW9uIHdpdGggcmVzcGVjdCB0byB0aGUgYXZlcmFnZSBjb3JyZWN0aW9uIHZlY3RvciBhdCBlYWNoIG1lcmdlIHN0ZXAuIFRoaXMgaXMgcmV0dXJuZWQgdmlhIHRoZSBsb3N0LnZhciBmaWVsZCBpbiB0aGUgbWV0YWRhdGEgb2YgbW5uLm91dCwgd2hpY2ggY29udGFpbnMgYSBtYXRyaXggb2YgdGhlIHZhcmlhbmNlIGxvc3QgaW4gZWFjaCBiYXRjaCAoY29sdW1uKSBhdCBlYWNoIG1lcmdlIHN0ZXAgKHJvdykuCgpgYGB7cn0KbWV0YWRhdGEobW5uLm91dCkkbWVyZ2UuaW5mbyRsb3N0LnZhcgpgYGAKCkxhcmdlIHByb3BvcnRpb25zIG9mIGxvc3QgdmFyaWFuY2UgKD4xMCUpIHN1Z2dlc3QgdGhhdCBjb3JyZWN0aW9uIGlzIHJlbW92aW5nIGdlbnVpbmUgYmlvbG9naWNhbCBoZXRlcm9nZW5laXR5LiBUaGlzIHdvdWxkIG9jY3VyIGR1ZSB0byB2aW9sYXRpb25zIG9mIHRoZSBhc3N1bXB0aW9uIG9mIG9ydGhvZ29uYWxpdHkgYmV0d2VlbiB0aGUgYmF0Y2ggZWZmZWN0IGFuZCB0aGUgYmlvbG9naWNhbCBzdWJzcGFjZSAoSGFnaHZlcmRpIGV0IGFsLiAyMDE4KS4gSW4gdGhpcyBjYXNlLCB0aGUgcHJvcG9ydGlvbiBvZiBsb3N0IHZhcmlhbmNlIGlzIHNtYWxsLCBpbmRpY2F0aW5nIHRoYXQgbm9uLW9ydGhvZ29uYWxpdHkgaXMgbm90IGEgbWFqb3IgY29uY2Vybi4KCiMjIFByZXNlcnZpbmcgYmlvbG9naWNhbCBoZXRlcm9nZW5laXR5CgojIyMgQ29tcGFyaXNvbiB0byB3aXRoaW4tYmF0Y2ggY2x1c3RlcnMKCkFub3RoZXIgdXNlZnVsIGRpYWdub3N0aWMgY2hlY2sgaXMgdG8gY29tcGFyZSB0aGUgY2x1c3RlcmluZyB3aXRoaW4gZWFjaCBiYXRjaCB0byB0aGUgY2x1c3RlcmluZyBvZiB0aGUgbWVyZ2VkIGRhdGEuIEFjY3VyYXRlIGRhdGEgaW50ZWdyYXRpb24gc2hvdWxkIHByZXNlcnZlIHZhcmlhbmNlIHdpdGhpbiBlYWNoIGJhdGNoIGFzIHRoZXJlIHNob3VsZCBiZSBub3RoaW5nIHRvIHJlbW92ZSBiZXR3ZWVuIGNlbGxzIGluIHRoZSBzYW1lIGJhdGNoLiBUaGlzIGNoZWNrIGNvbXBsZW1lbnRzIHRoZSBwcmV2aW91c2x5IG1lbnRpb25lZCBkaWFnbm9zdGljcyB0aGF0IG9ubHkgZm9jdXMgb24gdGhlIHJlbW92YWwgb2YgZGlmZmVyZW5jZXMgYmV0d2VlbiBiYXRjaGVzLiBTcGVjaWZpY2FsbHksIGl0IHByb3RlY3RzIHVzIGFnYWluc3QgY2FzZXMgd2hlcmUgdGhlIGNvcnJlY3Rpb24gbWV0aG9kIHNpbXBseSBhZ2dyZWdhdGVzIGFsbCBjZWxscyB0b2dldGhlciwgd2hpY2ggd291bGQgYWNoaWV2ZSBwZXJmZWN0IG1peGluZyBidXQgYWxzbyBkaXNjYXJkIHRoZSBiaW9sb2dpY2FsIGhldGVyb2dlbmVpdHkgb2YgaW50ZXJlc3QuCgpJZGVhbGx5LCB3ZSBzaG91bGQgc2VlIGEgbWFueS10by0xIG1hcHBpbmcgd2hlcmUgdGhlIGFjcm9zcy1iYXRjaCBjbHVzdGVyaW5nIGlzIG5lc3RlZCBpbnNpZGUgdGhlIHdpdGhpbi1iYXRjaCBjbHVzdGVyaW5ncy4gVGhpcyBpbmRpY2F0ZXMgdGhhdCBhbnkgd2l0aGluLWJhdGNoIHN0cnVjdHVyZSB3YXMgcHJlc2VydmVkIGFmdGVyIGNvcnJlY3Rpb24gd2hpbGUgYWNrbm93bGVkZ2luZyB0aGF0IGdyZWF0ZXIgcmVzb2x1dGlvbiBpcyBwb3NzaWJsZSB3aXRoIG1vcmUgY2VsbHMuIEluIHByYWN0aWNlLCBtb3JlIGRpc2NyZXBhbmNpZXMgY2FuIGJlIGV4cGVjdGVkIGV2ZW4gd2hlbiB0aGUgY29ycmVjdGlvbiBpcyBwZXJmZWN0LCBkdWUgdG8gdGhlIGV4aXN0ZW5jZSBvZiBjbG9zZWx5IHJlbGF0ZWQgY2x1c3RlcnMgdGhhdCB3ZXJlIGFyYml0cmFyaWx5IHNlcGFyYXRlZCBpbiB0aGUgd2l0aGluLWJhdGNoIGNsdXN0ZXJpbmcuIEFzIGEgZ2VuZXJhbCBydWxlLCB3ZSBjYW4gYmUgc2F0aXNmaWVkIHdpdGggdGhlIGNvcnJlY3Rpb24gaWYgdGhlIHZhc3QgbWFqb3JpdHkgb2YgZW50cmllcyBpbiBGaWd1cmUgMTMuNCBhcmUgemVybywgdGhvdWdoIHRoaXMgbWF5IGRlcGVuZCBvbiB3aGV0aGVyIHNwZWNpZmljIGNsdXN0ZXJzIG9mIGludGVyZXN0IGFyZSBnYWluZWQgb3IgbG9zdC4KCk9uZSBoZWF0bWFwIGlzIGdlbmVyYXRlZCBmb3IgZWFjaCBvZiB0aGUgUEJNQyAzSyBhbmQgNEsgZGF0YXNldHMsIHdoZXJlIGVhY2ggZW50cnkgaXMgY29sb3JlZCBhY2NvcmRpbmcgdG8gdGhlIG51bWJlciBvZiBjZWxscyB3aXRoIGVhY2ggcGFpciBvZiBsYWJlbHMgKGJlZm9yZSBhbmQgYWZ0ZXIgY29ycmVjdGlvbikuIAoKYGBge3J9CmxpYnJhcnkocGhlYXRtYXApCgojIEZvciB0aGUgZmlyc3QgYmF0Y2ggKGFkZGluZyArMTAgZm9yIGEgc21vb3RoZXIgY29sb3IgdHJhbnNpdGlvbgojIGZyb20gemVybyB0byBub24temVybyBjb3VudHMgZm9yIGFueSBnaXZlbiBtYXRyaXggZW50cnkpLgpiYXRjaFBsYWNlIDwtIDEKdG1wSW5kIDwtIG1ubi5vdXQkYmF0Y2g9PWxldmVscyhtbm4ub3V0JGJhdGNoKVtiYXRjaFBsYWNlXQp0YWIgPC0gdGFibGUocGFzdGUoImFmdGVyIiwgY2x1c3RlcnMubW5uW3RtcEluZF0pLAogICAgICAgICAgICAgcGFzdGUoImJlZm9yZSIsIGNvbExhYmVscyhyZXNjYWxlZC5tYm5bW2JhdGNoUGxhY2VdXSkpKQpoZWF0MSA8LSBwaGVhdG1hcChsb2cxMCh0YWIrMTApLCBjbHVzdGVyX3Jvdz1GQUxTRSwgY2x1c3Rlcl9jb2w9RkFMU0UsCiAgICBtYWluPXNwcmludGYoIiVzIGNvbXBhcmlzb24iLCBzcGxWZWNbYmF0Y2hQbGFjZV0pLCBzaWxlbnQ9VFJVRSkKCiMgRm9yIHRoZSBzZWNvbmQgYmF0Y2guCmJhdGNoUGxhY2UgPC0gMgp0bXBJbmQgPC0gbW5uLm91dCRiYXRjaD09bGV2ZWxzKG1ubi5vdXQkYmF0Y2gpW2JhdGNoUGxhY2VdCnRhYiA8LSB0YWJsZShwYXN0ZSgiYWZ0ZXIiLCBjbHVzdGVycy5tbm5bdG1wSW5kXSksCiAgICBwYXN0ZSgiYmVmb3JlIiwgY29sTGFiZWxzKHJlc2NhbGVkLm1ibltbYmF0Y2hQbGFjZV1dKSkpCmhlYXQyIDwtIHBoZWF0bWFwKGxvZzEwKHRhYisxMCksIGNsdXN0ZXJfcm93PUZBTFNFLCBjbHVzdGVyX2NvbD1GQUxTRSwKICAgIG1haW49c3ByaW50ZigiJXMgY29tcGFyaXNvbiIsIHNwbFZlY1tiYXRjaFBsYWNlXSksIHNpbGVudD1UUlVFKQoKZ3JpZEV4dHJhOjpncmlkLmFycmFuZ2UoaGVhdDFbWzRdXSwgaGVhdDJbWzRdXSkKYGBgCgpBbm90aGVyIGV2YWx1YXRpb24gYXBwcm9hY2ggaXMgdG8gY29tcHV0ZSB0aGUgY29hc3NpZ25tZW50IHByb2JhYmlsaXRpZXMgKFNlY3Rpb24gMTAuNiksIGkuZS4sIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGNlbGxzIGZyb20gdHdvIHdpdGhpbi1iYXRjaCBjbHVzdGVycyBhcmUgY2x1c3RlcmVkIHRvZ2V0aGVyIGluIHRoZSBhY3Jvc3MtYmF0Y2ggY2x1c3RlcmluZy4gSGlnaCBwcm9iYWJpbGl0aWVzIG9mZiB0aGUgZGlhZ29uYWwgaW4gRmlndXJlIDEzLjUgaW5kaWNhdGUgdGhhdCB3aXRoaW4tYmF0Y2ggY2x1c3RlcnMgYXJlIG1lcmdlZCBpbiB0aGUgYWNyb3NzLWJhdGNoIGFuYWx5c2lzLiBXZSB3b3VsZCBnZW5lcmFsbHkgZXhwZWN0IGxvdyBvZmYtZGlhZ29uYWwgcHJvYmFiaWxpdGllcyBmb3IgbW9zdCBwYWlycyBvZiBjbHVzdGVycywgdGhvdWdoIHRoaXMgbWF5IG5vdCBiZSByZWFzb25hYmx5IHBvc3NpYmxlIGlmIHRoZSB3aXRoaW4tYmF0Y2ggY2x1c3RlcnMgd2VyZSBwb29ybHkgc2VwYXJhdGVkIGluIHRoZSBmaXJzdCBwbGFjZS4KCkNvYXNzaWdubWVudCBwcm9iYWJpbGl0aWVzIGZvciB0aGUgd2l0aGluLWJhdGNoIGNsdXN0ZXJzLCBiYXNlZCBvbiBjb2Fzc2lnbm1lbnQgb2YgY2VsbHMgaW4gdGhlIGFjcm9zcy1iYXRjaCBjbHVzdGVycyBvYnRhaW5lZCBhZnRlciBNTk4gY29ycmVjdGlvbi4gT25lIGhlYXRtYXAgaXMgZ2VuZXJhdGVkIGZvciBlYWNoIG9mIHRoZSBHU00zODcyNDM0IGFuZCBHU00zODcyNDM1LCB3aGVyZSBlYWNoIGVudHJ5IGlzIGNvbG9yZWQgYWNjb3JkaW5nIHRvIHRoZSBjb2Fzc2lnbm1lbnQgcHJvYmFiaWxpdHkgYmV0d2VlbiBlYWNoIHBhaXIgb2Ygd2l0aGluLWJhdGNoIGNsdXN0ZXJzOgoKYGBge3J9CiMgRm9yIHRoZSBmaXJzdCBiYXRjaC4KYmF0Y2hQbGFjZSA8LSAxCnRtcEluZCA8LSBtbm4ub3V0JGJhdGNoPT1sZXZlbHMobW5uLm91dCRiYXRjaClbYmF0Y2hQbGFjZV0KdGFiIDwtIGNvYXNzaWduUHJvYihjb2xMYWJlbHMocmVzY2FsZWQubWJuW1tiYXRjaFBsYWNlXV0pLCBjbHVzdGVycy5tbm5bdG1wSW5kXSkKaGVhdDEgPC0gcGhlYXRtYXAodGFiLCBjbHVzdGVyX3Jvdz1GQUxTRSwgY2x1c3Rlcl9jb2w9RkFMU0UsCiAgICBjb2w9cmV2KHZpcmlkaXM6Om1hZ21hKDEwMCkpLCBtYWluPXNwcmludGYoIiVzIHByb2JhYmlsaXRpZXMiLCBzcGxWZWNbYmF0Y2hQbGFjZV0pLCBzaWxlbnQ9VFJVRSkKCiMgRm9yIHRoZSBzZWNvbmQgYmF0Y2guCmJhdGNoUGxhY2UgPC0gMgp0bXBJbmQgPC0gbW5uLm91dCRiYXRjaD09bGV2ZWxzKG1ubi5vdXQkYmF0Y2gpW2JhdGNoUGxhY2VdCnRhYiA8LSBjb2Fzc2lnblByb2IoY29sTGFiZWxzKHJlc2NhbGVkLm1ibltbYmF0Y2hQbGFjZV1dKSwgY2x1c3RlcnMubW5uW3RtcEluZF0pCmhlYXQyIDwtIHBoZWF0bWFwKHRhYiwgY2x1c3Rlcl9yb3c9RkFMU0UsIGNsdXN0ZXJfY29sPUZBTFNFLAogICAgY29sPXJldih2aXJpZGlzOjptYWdtYSgxMDApKSwgbWFpbj1zcHJpbnRmKCIlcyBwcm9iYWJpbGl0aWVzIiwgc3BsVmVjW2JhdGNoUGxhY2VdKSwgc2lsZW50PVRSVUUpCgpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZShoZWF0MVtbNF1dLCBoZWF0MltbNF1dKQpgYGAKCkZpbmFsbHksIHdlIGNhbiBzdW1tYXJpemUgdGhlIGFncmVlbWVudCBiZXR3ZWVuIGNsdXN0ZXJpbmdzIGJ5IGNvbXB1dGluZyB0aGUgUmFuZCBpbmRleC4gVGhpcyBwcm92aWRlcyBhIHNpbXBsZSBtZXRyaWMgdGhhdCB3ZSBjYW4gdXNlIHRvIGFzc2VzcyB0aGUgcHJlc2VydmF0aW9uIG9mIHZhcmlhdGlvbiBieSBkaWZmZXJlbnQgY29ycmVjdGlvbiBtZXRob2RzLiBMYXJnZXIgcmFuZCBpbmRpY2VzIChpLmUuLCBjbG9zZXIgdG8gMSkgYXJlIG1vcmUgZGVzaXJhYmxlLCB0aG91Z2ggdGhpcyBtdXN0IGJlIGJhbGFuY2VkIGFnYWluc3QgdGhlIGFiaWxpdHkgb2YgZWFjaCBtZXRob2QgdG8gYWN0dWFsbHkgcmVtb3ZlIHRoZSBiYXRjaCBlZmZlY3QuCgpgYGB7cn0Kc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KGZvc3NpbCkpCmJhdGNoUGxhY2UgPC0gMQp0bXBJbmQgPC0gbW5uLm91dCRiYXRjaD09bGV2ZWxzKG1ubi5vdXQkYmF0Y2gpW2JhdGNoUGxhY2VdCnJpMSA8LSByYW5kLmluZGV4KGFzLmludGVnZXIoY2x1c3RlcnMubW5uW3RtcEluZF0pLAogICAgYXMuaW50ZWdlcihjb2xMYWJlbHMocmVzY2FsZWQubWJuW1tiYXRjaFBsYWNlXV0pKSkKcmkxCgpiYXRjaFBsYWNlIDwtIDIKdG1wSW5kIDwtIG1ubi5vdXQkYmF0Y2g9PWxldmVscyhtbm4ub3V0JGJhdGNoKVtiYXRjaFBsYWNlXQpyaTIgPC0gcmFuZC5pbmRleChhcy5pbnRlZ2VyKGNsdXN0ZXJzLm1ublt0bXBJbmRdKSwKICAgIGFzLmludGVnZXIoY29sTGFiZWxzKHJlc2NhbGVkLm1ibltbYmF0Y2hQbGFjZV1dKSkpCnJpMgpgYGAKCiMjIyBFbmNvdXJhZ2luZyBjb25zaXN0ZW5jeSB3aXRoIG1hcmtlciBnZW5lcwoKSW4gc29tZSBzaXR1YXRpb25zLCB3ZSB3aWxsIGFscmVhZHkgaGF2ZSBwZXJmb3JtZWQgd2l0aGluLWJhdGNoIGFuYWx5c2VzIHRvIGNoYXJhY3Rlcml6ZSBzYWxpZW50IGFzcGVjdHMgb2YgcG9wdWxhdGlvbiBoZXRlcm9nZW5laXR5LiBUaGlzIGlzIG5vdCB1bmNvbW1vbiB3aGVuIG1lcmdpbmcgZGF0YXNldHMgZnJvbSBkaWZmZXJlbnQgc291cmNlcyB3aGVyZSBlYWNoIGRhdGFzZXQgaGFzIGFscmVhZHkgYmVlbiBhbmFseXplZCwgYW5ub3RhdGVkIGFuZCBpbnRlcnByZXRlZCBzZXBhcmF0ZWx5LiBJdCBpcyBzdWJzZXF1ZW50bHkgZGVzaXJhYmxlIGZvciB0aGUgaW50ZWdyYXRpb24gcHJvY2VkdXJlIHRvIHJldGFpbiB0aGVzZSDigJxrbm93biBpbnRlcmVzdGluZ+KAnSBhc3BlY3RzIG9mIGVhY2ggZGF0YXNldCBpbiB0aGUgbWVyZ2VkIGRhdGFzZXQuIFdlIGNhbiBlbmNvdXJhZ2UgdGhpcyBvdXRjb21lIGJ5IHVzaW5nIHRoZSBtYXJrZXIgZ2VuZXMgd2l0aGluIGVhY2ggZGF0YXNldCBhcyBvdXIgc2VsZWN0ZWQgZmVhdHVyZSBzZXQgZm9yIGZhc3RNTk4oKSBhbmQgcmVsYXRlZCBtZXRob2RzLiBUaGlzIGZvY3VzZXMgb24gdGhlIHJlbGV2YW50IGhldGVyb2dlbmVpdHkgYW5kIHJlcHJlc2VudHMgYSBzZW1pLXN1cGVydmlzZWQgYXBwcm9hY2ggdGhhdCBpcyBhIG5hdHVyYWwgZXh0ZW5zaW9uIG9mIHRoZSBzdHJhdGVneSBkZXNjcmliZWQgaW4gU2VjdGlvbiA4LjQuCgpUbyBpbGx1c3RyYXRlLCB3ZSBhcHBseSB0aGlzIHN0cmF0ZWd5IHRvIG91ciBQQk1DIGRhdGFzZXRzLiBXZSBpZGVudGlmeSB0aGUgdG9wIG1hcmtlciBnZW5lcyBmcm9tIHBhaXJ3aXNlIFdpbGNveG9uIHJhbmtlZCBzdW0gdGVzdHMgYmV0d2VlbiBldmVyeSBwYWlyIG9mIGNsdXN0ZXJzIHdpdGhpbiBlYWNoIGJhdGNoLCBhbmFsb2dvdXMgdG8gdGhlIG1ldGhvZCB1c2VkIGJ5IFNpbmdsZVIgKENoYXB0ZXIgMTIpLiBJbiB0aGlzIGNhc2UsIHdlIHVzZSB0aGUgdG9wIDEwIG1hcmtlciBnZW5lcyBidXQgYW55IHZhbHVlIGNhbiBiZSB1c2VkIGRlcGVuZGluZyBvbiB0aGUgYWNjZXB0YWJsZSB0cmFkZS1vZmYgYmV0d2VlbiBzaWduYWwgYW5kIG5vaXNlIChhbmQgc3BlZWQpLiBXZSB0aGVuIHRha2UgdGhlIHVuaW9uIGFjcm9zcyBhbGwgY29tcGFyaXNvbnMgaW4gYWxsIGJhdGNoZXMgYW5kIHVzZSB0aGF0IGluIHBsYWNlIG9mIG91ciBIVkcgc2V0IGluIGZhc3RNTk4oKS4KCmBgYHtyfQojIFJlY2FsbCB0aGF0IGdyb3VwcyBmb3IgbWFya2VyIGRldGVjdGlvbgojIGFyZSBhdXRvbWF0aWNhbGx5IGRlZmluZWQgZnJvbSAnY29sTGFiZWxzKCknLiAKc3RhdHMxIDwtIHBhaXJ3aXNlV2lsY294KHJlc2NhbGVkLm1ibltbMV1dLCBkaXJlY3Rpb249InVwIikKbWFya2VyczEgPC0gZ2V0VG9wTWFya2VycyhzdGF0czFbWzFdXSwgc3RhdHMxW1syXV0sIG49MTApCgpzdGF0czIgPC0gcGFpcndpc2VXaWxjb3gocmVzY2FsZWQubWJuW1syXV0sIGRpcmVjdGlvbj0idXAiKQptYXJrZXJzMiA8LSBnZXRUb3BNYXJrZXJzKHN0YXRzMltbMV1dLCBzdGF0czJbWzJdXSwgbj0xMCkKCnN0YXRzMyA8LSBwYWlyd2lzZVdpbGNveChyZXNjYWxlZC5tYm5bWzNdXSwgZGlyZWN0aW9uPSJ1cCIpCm1hcmtlcnMzIDwtIGdldFRvcE1hcmtlcnMoc3RhdHMzW1sxXV0sIHN0YXRzM1tbMl1dLCBuPTEwKQoKc3RhdHM0IDwtIHBhaXJ3aXNlV2lsY294KHJlc2NhbGVkLm1ibltbNF1dLCBkaXJlY3Rpb249InVwIikKbWFya2VyczQgPC0gZ2V0VG9wTWFya2VycyhzdGF0czRbWzFdXSwgc3RhdHM0W1syXV0sIG49MTApCgoKbWFya2VyLnNldCA8LSB1bmlxdWUodW5saXN0KGModW5saXN0KG1hcmtlcnMxKSwgdW5saXN0KG1hcmtlcnMyKSwgdW5saXN0KG1hcmtlcnMzKSwgdW5saXN0KG1hcmtlcnM0KSkpKQpsZW5ndGgobWFya2VyLnNldCkgIyBnZXR0aW5nIHRoZSB0b3RhbCBudW1iZXIgb2YgZ2VuZXMgc2VsZWN0ZWQgaW4gdGhpcyBtYW5uZXIuCmBgYAoKPCEtLSBNSU5EIG1hcmtlci5zZXQgaXMgZnJvbSB0aHIgUlVOIGV0YyBhbmQgdXNlZCBmb3IgYWxsIDExIHNhbXBsZXMgLS0+CgpgYGB7cn0Kc2V0LnNlZWQoMTAwMDExMCkKbW5uLm91dDIgPC0gZmFzdE1OTihyZXNjYWxlZC5tYm5bMTo0XSwgc3Vic2V0LnJvdz1tYXJrZXIuc2V0LAogICAgQlNQQVJBTT1CaW9jU2luZ3VsYXI6OlJhbmRvbVBhcmFtKGRlZmVycmVkPVRSVUUpKQpgYGAKCkEgcXVpY2sgaW5zcGVjdGlvbiBvZiBGaWd1cmUgMTMuNiBpbmRpY2F0ZXMgdGhhdCB0aGUgb3JpZ2luYWwgd2l0aGluLWJhdGNoIHN0cnVjdHVyZSBpcyBpbmRlZWQgcHJlc2VydmVkIGluIHRoZSBjb3JyZWN0ZWQgZGF0YS4gVGhpcyBoaWdobGlnaHRzIHRoZSB1dGlsaXR5IG9mIGEgbWFya2VyLWJhc2VkIGZlYXR1cmUgc2V0IGZvciBpbnRlZ3JhdGluZyBkYXRhc2V0cyB0aGF0IGhhdmUgYWxyZWFkeSBiZWVuIGNoYXJhY3Rlcml6ZWQgc2VwYXJhdGVseSBpbiBhIG1hbm5lciB0aGF0IHByZXNlcnZlcyBleGlzdGluZyBpbnRlcnByZXRhdGlvbnMgb2YgZWFjaCBkYXRhc2V0LiBXZSBub3RlIHRoYXQgc29tZSB3aXRoaW4tYmF0Y2ggY2x1c3RlcnMgaGF2ZSBtZXJnZWQsIG1vc3QgbGlrZWx5IGR1ZSB0byB0aGUgbGFjayBvZiByb2J1c3Qgc2VwYXJhdGlvbiBpbiB0aGUgZmlyc3QgcGxhY2UsIHRob3VnaCB0aGlzIG1heSBhbHNvIGJlIHRyZWF0ZWQgYXMgYSBkaWFnbm9zdGljIG9uIHRoZSBhcHByb3ByaWF0ZW5lc3Mgb2YgdGhlIGludGVncmF0aW9uIGRlcGVuZGluZyBvbiB0aGUgY29udGV4dC4KCmBgYHtyfQptbm4ub3V0MiA8LSBydW5UU05FKG1ubi5vdXQyLCBkaW1yZWQ9ImNvcnJlY3RlZCIpCmJhdGNoVmVjIDwtIGxldmVscyhtbm4ub3V0JGJhdGNoKQpncmlkRXh0cmE6OmdyaWQuYXJyYW5nZSgKICAgIHBsb3RUU05FKG1ubi5vdXQyWyxtbm4ub3V0MiRiYXRjaD09YmF0Y2hWZWNbMV1dLCBjb2xvdXJfYnk9SShjb2xMYWJlbHMocmVzY2FsZWQubWJuW1sxXV0pKSksCiAgICBwbG90VFNORShtbm4ub3V0MlssbW5uLm91dDIkYmF0Y2g9PWJhdGNoVmVjWzJdXSwgY29sb3VyX2J5PUkoY29sTGFiZWxzKHJlc2NhbGVkLm1ibltbMl1dKSkpLAogICAgcGxvdFRTTkUobW5uLm91dDJbLG1ubi5vdXQyJGJhdGNoPT1iYXRjaFZlY1szXV0sIGNvbG91cl9ieT1JKGNvbExhYmVscyhyZXNjYWxlZC5tYm5bWzNdXSkpKSwKICAgIHBsb3RUU05FKG1ubi5vdXQyWyxtbm4ub3V0MiRiYXRjaD09YmF0Y2hWZWNbNF1dLCBjb2xvdXJfYnk9SShjb2xMYWJlbHMocmVzY2FsZWQubWJuW1s0XV0pKSksCiAgICBuY29sPTIKKQpgYGAKCiMjIyBVc2luZyB0aGUgY29ycmVjdGVkIHZhbHVlcwoKaXQgaXMgcHJlZmVyYWJsZSB0byBwZXJmb3JtIERFIGFuYWx5c2VzIHVzaW5nIHRoZSB1bmNvcnJlY3RlZCBleHByZXNzaW9uIHZhbHVlcyB3aXRoIGJsb2NraW5nIG9uIHRoZSBiYXRjaCwgYXMgZGlzY3Vzc2VkIGluIFNlY3Rpb24gMTEuNC4gVGhpcyBzdHJhdGVneSBpcyBiYXNlZCBvbiB0aGUgZXhwZWN0YXRpb24gdGhhdCBhbnkgZ2VudWluZSBERSBiZXR3ZWVuIGNsdXN0ZXJzIHNob3VsZCBzdGlsbCBiZSBwcmVzZW50IGluIGEgd2l0aGluLWJhdGNoIGNvbXBhcmlzb24gd2hlcmUgYmF0Y2ggZWZmZWN0cyBhcmUgYWJzZW50LiBJdCBwZW5hbGl6ZXMgZ2VuZXMgdGhhdCBleGhpYml0IGluY29uc2lzdGVudCBERSBhY3Jvc3MgYmF0Y2hlcywgdGh1cyBwcm90ZWN0aW5nIGFnYWluc3QgbWlzbGVhZGluZyBjb25jbHVzaW9ucyB3aGVuIGEgcG9wdWxhdGlvbiBpbiBvbmUgYmF0Y2ggaXMgYWxpZ25lZCB0byBhIHNpbWlsYXItYnV0LW5vdC1pZGVudGljYWwgcG9wdWxhdGlvbiBpbiBhbm90aGVyIGJhdGNoLiBXZSBkZW1vbnN0cmF0ZSB0aGlzIGFwcHJvYWNoIGJlbG93IHVzaW5nIGEgYmxvY2tlZCB0LXRlc3QgdG8gZGV0ZWN0IG1hcmtlcnMgaW4gdGhlIFBCTUMgZGF0YXNldCwgd2hlcmUgdGhlIHByZXNlbmNlIG9mIHRoZSBzYW1lIHBhdHRlcm4gYWNyb3NzIGNsdXN0ZXJzIHdpdGhpbiBlYWNoIGJhdGNoIChGaWd1cmUgMTMuNykgaXMgcmVhc3N1cmluZy4gSWYgaW50ZWdyYXRpb24gaXMgcGVyZm9ybWVkIGFjcm9zcyBtdWx0aXBsZSBjb25kaXRpb25zLCBpdCBpcyBldmVuIG1vcmUgaW1wb3J0YW50IHRvIHVzZSB0aGUgdW5jb3JyZWN0ZWQgZXhwcmVzc2lvbiB2YWx1ZXMgZm9yIGRvd25zdHJlYW0gYW5hbHlzZXMgLSBzZWUgU2VjdGlvbiAxNC41LjIgZm9yIGEgZGlzY3Vzc2lvbi4KCmBgYHtyfQptLm91dCA8LSBmaW5kTWFya2Vycyh1bmNvcnJlY3RlZCwgY2x1c3RlcnMubW5uLCBibG9jaz11bmNvcnJlY3RlZCRiYXRjaCwKICAgIGRpcmVjdGlvbj0idXAiLCBsZmM9MSwgcm93LmRhdGE9cm93RGF0YSh1bmNvcnJlY3RlZClbLGMoImVuc2VtYmxfZ2VuZV9pZCIsIlN5bWJvbCIpLGRyb3A9RkFMU0VdKQpgYGAKCmBgYHtyfQpkZW1vIDwtIG0ub3V0W1siMSJdXQphcy5kYXRhLmZyYW1lKGRlbW9bMToyMCxjKCJTeW1ib2wiLCAiVG9wIiwgInAudmFsdWUiLCAiRkRSIildKSAKI2FzLmRhdGEuZnJhbWUoZGVtb1sxOjIwLGMoImV4dGVybmFsX2dlbmVfbmFtZSIsICJUb3AiLCAicC52YWx1ZSIsICJGRFIiKV0pIApgYGAKCkV4cHJlc3Npb24gbGV2ZWwgZm9yIHRoZSB0b3AgZ2VuZSwgYHIgcm93bmFtZXMoZGVtbylbMV1gOgoKYGBge3J9CmdlbmVFbnNJZCA8LSByb3duYW1lcyhkZW1vKVsxXQpwbG90RXhwcmVzc2lvbih1bmNvcnJlY3RlZCwgeD1JKGZhY3RvcihjbHVzdGVycy5tbm4pKSwgCiAgICBmZWF0dXJlcz1nZW5lRW5zSWQsIGNvbG91cl9ieT0iYmF0Y2giKSArIGZhY2V0X3dyYXAofmNvbG91cl9ieSkKYGBgCgojIyBTYW1lIGJ1dCB3aXRoIGFuIG9yZGVyZWQgbWVyZ2luZwoKV2Ugd2lsbCBmaXJzdCBtZXJnZSByZXBsaWNhdGVzIGluIGVhY2ggc2FtcGxlIGdyb3VwIHNlcGFyYXRlbHksIHRoZW4gc2FtcGxlIGdyb3Vwcywgc3RhcnRpbmcgd2l0aCB0aGUgZ3JvdXAgd2l0aCB0aGUgbGFyZ2VyIG51bWJlciBvZiAnY2VsbCB0eXBlcycuCgpgYGB7cn0KIyBVc2luZyByYW5kb21pemVkIFNWRCBoZXJlLCBhcyB0aGlzIGlzIGZhc3RlciB0aGFuIAojIGlybGJhIGZvciBmaWxlLWJhY2tlZCBtYXRyaWNlcy4Kc2V0LnNlZWQoMTAwMDEwMTAwMSkKcm0obW5uLm91dCkKbW5uLm91dCA8LSBmYXN0TU5OKHJlc2NhbGVkLm1ibiwKCQkgICBtZXJnZS5vcmRlcj1saXN0KCBsaXN0KDEsMiwzLDQpLCBsaXN0KDksMTAsMTEpLCBsaXN0KDUsNiksIGxpc3QoNyw4KSApLAoJCSAgIGQ9NTAsIGs9MjAsIHN1YnNldC5yb3c9Y2hvc2VuLmh2Z3MsCiAgICBCU1BBUkFNPUJpb2NTaW5ndWxhcjo6UmFuZG9tUGFyYW0oZGVmZXJyZWQ9VFJVRSkpCm1ubi5vdXQKbW5uLm91dCRiYXRjaCA8LSBmYWN0b3IobW5uLm91dCRiYXRjaCkKbW5uLm91dCR0eXBlIDwtIGdzdWIoIl9bMS00XSIsIiIsbW5uLm91dCRiYXRjaCkKbW5uLm91dCR0eXBlIDwtIGZhY3Rvcihtbm4ub3V0JHR5cGUpCmBgYAoKYGBge3J9CiNjbGFzcyhtbm4ub3V0JGJhdGNoKQojaGVhZChtbm4ub3V0JGJhdGNoKQojZGltKHJlZHVjZWREaW0obW5uLm91dCwgImNvcnJlY3RlZCIpKQojYXNzYXkobW5uLm91dCwgInJlY29uc3RydWN0ZWQiKQpwcmludChkaW0oYXNzYXkobW5uLm91dCwgInJlY29uc3RydWN0ZWQiKSkpCnByaW50KGFzc2F5KG1ubi5vdXQsICJyZWNvbnN0cnVjdGVkIilbMTo1LDE6M10pCmBgYAoKRGlhZ25vc3RpYyB0YWJsZSBhbmQgcGxvdHM6CgpgYGB7cn0KbGlicmFyeShzY3JhbikKc25uLmdyIDwtIGJ1aWxkU05OR3JhcGgobW5uLm91dCwgdXNlLmRpbXJlZD0iY29ycmVjdGVkIikKY2x1c3RlcnMubW5uIDwtIGlncmFwaDo6Y2x1c3Rlcl93YWxrdHJhcChzbm4uZ3IpJG1lbWJlcnNoaXAKbW5uLm91dCRjbHVzdGVycy5tbm4gPC0gc3ByaW50ZigiYyVzIiwgY2x1c3RlcnMubW5uKQp0YWIubW5uIDwtIHRhYmxlKENsdXN0ZXI9bW5uLm91dCRjbHVzdGVycy5tbm4sIEJhdGNoPW1ubi5vdXQkYmF0Y2gpCnRhYi5tbm4KYGBgCgpgYGB7cn0KbGlicmFyeShzY2F0ZXIpCnNldC5zZWVkKDAwMTAxMDEwMTApCm1ubi5vdXQgPC0gcnVuVFNORShtbm4ub3V0LCBkaW1yZWQ9ImNvcnJlY3RlZCIpCgpwIDwtIHBsb3RUU05FKG1ubi5vdXQsIGNvbG91cl9ieT0iYmF0Y2giKQpwICsgZmFjZXRfd3JhcCh+bW5uLm91dCR0eXBlKQpgYGAKCldyaXRlIG1ubi5vdXQgb2JqZWN0IHRvIGZpbGUKCmBgYHtyIGZhc3RNbm5XaG9sZUJ5TGlzdF93cml0ZVJkc30KY29sRGF0YShtbm4ub3V0KSA8LSBjYmluZChjb2xEYXRhKHVuY29ycmVjdGVkKSxjb2xEYXRhKG1ubi5vdXQpWyxjKCJ0eXBlIiwgImNsdXN0ZXJzLm1ubiIpXSkKIyBXcml0ZSBvYmplY3QgdG8gZmlsZQojIGZhc3RNbm5XaG9sZUJ5TGlzdCAtPiBGbXdibAp0bXBGbiA8LSBzcHJpbnRmKCIlcy8lcy9Sb2JqZWN0cy8lc19zY2VfbnpfcG9zdERlY29udiVzX0Ztd2JsLlJkcyIsIHByb2pEaXIsIG91dERpckJpdCwgc2V0TmFtZSwgc2V0U3VmKQpzYXZlUkRTKG1ubi5vdXQsIHRtcEZuKQoKdG1wRm4gPC0gc3ByaW50ZigiJXMvJXMvUm9iamVjdHMvJXNfc2NlX256X3Bvc3REZWNvbnYlc19GbXdibDIuUmRzIiwgcHJvakRpciwgb3V0RGlyQml0LCBzZXROYW1lLCBzZXRTdWYpCnNhdmVSRFMobGlzdCgiY2hvc2VuLmh2Z3MiPWNob3Nlbi5odmdzLCAidW5jb3JyZWN0ZWQiPXVuY29ycmVjdGVkLCJyZXNjYWxlZC5tYm4iPXJlc2NhbGVkLm1ibiksIHRtcEZuKQpgYGAKClByb3BvcnRpb25zIG9mIGxvc3QgdmFyaWFuY2UKCmBgYHtyfQptZXRhZGF0YShtbm4ub3V0KSRtZXJnZS5pbmZvJGxvc3QudmFyCmBgYAoKQ29tcGFyaXNvbiB0byB3aXRoaW4tYmF0Y2ggY2x1c3RlcnMKCmBgYHtyfQpsaWJyYXJ5KHBoZWF0bWFwKQptbm4ub3V0JGJhdGNoIDwtIGZhY3Rvcihtbm4ub3V0JGJhdGNoKSAjIHNvbWVob3cgbmVlZCB0byByZS1mYWN0b3IgYmF0Y2gKbGV2ZWxzKG1ubi5vdXQkYmF0Y2gpCgojIEZvciB0aGUgZmlyc3QgYmF0Y2ggKGFkZGluZyArMTAgZm9yIGEgc21vb3RoZXIgY29sb3IgdHJhbnNpdGlvbgojIGZyb20gemVybyB0byBub24temVybyBjb3VudHMgZm9yIGFueSBnaXZlbiBtYXRyaXggZW50cnkpLgpiYXRjaFBsYWNlIDwtIDEKdG1wSW5kIDwtIG1ubi5vdXQkYmF0Y2g9PWxldmVscyhtbm4ub3V0JGJhdGNoKVtiYXRjaFBsYWNlXQpsZW5ndGgocGFzdGUoImFmdGVyIiwgY2x1c3RlcnMubW5uW3RtcEluZF0pKQpyZXNjYWxlZC5tYm5bW2JhdGNoUGxhY2VdXQpyZXNjYWxlZC5tYm5bW2JhdGNoUGxhY2VdXSAlPiUgY29sRGF0YSAlPiUgaGVhZAoKbGVuZ3RoKHBhc3RlKCJiZWZvcmUiLCBjb2xMYWJlbHMocmVzY2FsZWQubWJuW1tiYXRjaFBsYWNlXV0pKSkKc2F2ZS5pbWFnZSgiZGF0YVNldEludGVncmF0aW9uV2hvbGUuZGVidWcuUmRhdGEiKQoKdGFibGUocGFzdGUoImFmdGVyIiwgY2x1c3RlcnMubW5uW3RtcEluZF0pKQp0YWJsZShwYXN0ZSgiYmVmb3JlIiwgY29sTGFiZWxzKHJlc2NhbGVkLm1ibltbYmF0Y2hQbGFjZV1dKSkpCmBgYAoKYHIgI2tuaXRyOjprbml0X2V4aXQoKWAKCgpgYGB7cn0KdGFiIDwtIHRhYmxlKHBhc3RlKCJhZnRlciIsIGNsdXN0ZXJzLm1ublt0bXBJbmRdKSwKICAgICAgICAgICAgIHBhc3RlKCJiZWZvcmUiLCBjb2xMYWJlbHMocmVzY2FsZWQubWJuW1tiYXRjaFBsYWNlXV0pKSkKaGVhdDEgPC0gcGhlYXRtYXAobG9nMTAodGFiKzEwKSwgY2x1c3Rlcl9yb3c9RkFMU0UsIGNsdXN0ZXJfY29sPUZBTFNFLAogICAgbWFpbj1zcHJpbnRmKCIlcyBjb21wYXJpc29uIiwgc3BsVmVjW2JhdGNoUGxhY2VdKSwgc2lsZW50PVRSVUUpCgojIEZvciB0aGUgc2Vjb25kIGJhdGNoLgpiYXRjaFBsYWNlIDwtIDIKdG1wSW5kIDwtIG1ubi5vdXQkYmF0Y2g9PWxldmVscyhtbm4ub3V0JGJhdGNoKVtiYXRjaFBsYWNlXQp0YWIgPC0gdGFibGUocGFzdGUoImFmdGVyIiwgY2x1c3RlcnMubW5uW3RtcEluZF0pLAogICAgcGFzdGUoImJlZm9yZSIsIGNvbExhYmVscyhyZXNjYWxlZC5tYm5bW2JhdGNoUGxhY2VdXSkpKQpoZWF0MiA8LSBwaGVhdG1hcChsb2cxMCh0YWIrMTApLCBjbHVzdGVyX3Jvdz1GQUxTRSwgY2x1c3Rlcl9jb2w9RkFMU0UsCiAgICBtYWluPXNwcmludGYoIiVzIGNvbXBhcmlzb24iLCBzcGxWZWNbYmF0Y2hQbGFjZV0pLCBzaWxlbnQ9VFJVRSkKCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKGhlYXQxW1s0XV0sIGhlYXQyW1s0XV0pCmBgYAoKQ28tYXNzaWdubWVudCBwcm9iYWJpbGl0aWVzCgpgYGB7cn0KIyBGb3IgdGhlIGZpcnN0IGJhdGNoLgpiYXRjaFBsYWNlIDwtIDEKdG1wSW5kIDwtIG1ubi5vdXQkYmF0Y2g9PWxldmVscyhtbm4ub3V0JGJhdGNoKVtiYXRjaFBsYWNlXQp0YWIgPC0gY29hc3NpZ25Qcm9iKGNvbExhYmVscyhyZXNjYWxlZC5tYm5bW2JhdGNoUGxhY2VdXSksIGNsdXN0ZXJzLm1ublt0bXBJbmRdKQpoZWF0MSA8LSBwaGVhdG1hcCh0YWIsIGNsdXN0ZXJfcm93PUZBTFNFLCBjbHVzdGVyX2NvbD1GQUxTRSwKICAgIGNvbD1yZXYodmlyaWRpczo6bWFnbWEoMTAwKSksIG1haW49c3ByaW50ZigiJXMgcHJvYmFiaWxpdGllcyIsIHNwbFZlY1tiYXRjaFBsYWNlXSksIHNpbGVudD1UUlVFKQoKIyBGb3IgdGhlIHNlY29uZCBiYXRjaC4KYmF0Y2hQbGFjZSA8LSAyCnRtcEluZCA8LSBtbm4ub3V0JGJhdGNoPT1sZXZlbHMobW5uLm91dCRiYXRjaClbYmF0Y2hQbGFjZV0KdGFiIDwtIGNvYXNzaWduUHJvYihjb2xMYWJlbHMocmVzY2FsZWQubWJuW1tiYXRjaFBsYWNlXV0pLCBjbHVzdGVycy5tbm5bdG1wSW5kXSkKaGVhdDIgPC0gcGhlYXRtYXAodGFiLCBjbHVzdGVyX3Jvdz1GQUxTRSwgY2x1c3Rlcl9jb2w9RkFMU0UsCiAgICBjb2w9cmV2KHZpcmlkaXM6Om1hZ21hKDEwMCkpLCBtYWluPXNwcmludGYoIiVzIHByb2JhYmlsaXRpZXMiLCBzcGxWZWNbYmF0Y2hQbGFjZV0pLCBzaWxlbnQ9VFJVRSkKCmdyaWRFeHRyYTo6Z3JpZC5hcnJhbmdlKGhlYXQxW1s0XV0sIGhlYXQyW1s0XV0pCmBgYAoKUmFuZCBpbmRleDoKCmBgYHtyfQpsaWJyYXJ5KGZvc3NpbCkKYmF0Y2hQbGFjZSA8LSAxCnRtcEluZCA8LSBtbm4ub3V0JGJhdGNoPT1sZXZlbHMobW5uLm91dCRiYXRjaClbYmF0Y2hQbGFjZV0KcmkxIDwtIHJhbmQuaW5kZXgoYXMuaW50ZWdlcihjbHVzdGVycy5tbm5bdG1wSW5kXSksCiAgICBhcy5pbnRlZ2VyKGNvbExhYmVscyhyZXNjYWxlZC5tYm5bW2JhdGNoUGxhY2VdXSkpKQpyaTEKCmJhdGNoUGxhY2UgPC0gMgp0bXBJbmQgPC0gbW5uLm91dCRiYXRjaD09bGV2ZWxzKG1ubi5vdXQkYmF0Y2gpW2JhdGNoUGxhY2VdCnJpMiA8LSByYW5kLmluZGV4KGFzLmludGVnZXIoY2x1c3RlcnMubW5uW3RtcEluZF0pLAogICAgYXMuaW50ZWdlcihjb2xMYWJlbHMocmVzY2FsZWQubWJuW1tiYXRjaFBsYWNlXV0pKSkKcmkyCmBgYAoKQ2x1c3RlciBtYXJrZXJzOgoKYGBge3J9Cm0ub3V0IDwtIGZpbmRNYXJrZXJzKHVuY29ycmVjdGVkLCBjbHVzdGVycy5tbm4sIGJsb2NrPXVuY29ycmVjdGVkJGJhdGNoLAogICAgZGlyZWN0aW9uPSJ1cCIsIGxmYz0xLCByb3cuZGF0YT1yb3dEYXRhKHVuY29ycmVjdGVkKVssYygiZW5zZW1ibF9nZW5lX2lkIiwiU3ltYm9sIiksZHJvcD1GQUxTRV0pCmBgYAoKYGBge3J9CmRlbW8gPC0gbS5vdXRbWyIxIl1dCmFzLmRhdGEuZnJhbWUoZGVtb1sxOjIwLGMoIlN5bWJvbCIsICJUb3AiLCAicC52YWx1ZSIsICJGRFIiKV0pIApgYGAKCkV4cHJlc3Npb24gbGV2ZWwgZm90IHRoZSB0b3AgZ2VuZSwgYHIgcm93bmFtZXMoZGVtbylbMV1gOgoKYGBge3J9CmdlbmVFbnNJZCA8LSByb3duYW1lcyhkZW1vKVsxXQoKcGxvdEV4cHJlc3Npb24odW5jb3JyZWN0ZWQsIHg9SShmYWN0b3IoY2x1c3RlcnMubW5uKSksIAogICAgZmVhdHVyZXM9Z2VuZUVuc0lkLCBjb2xvdXJfYnk9ImJhdGNoIikgKyBmYWNldF93cmFwKH5jb2xvdXJfYnkpCmBgYAoKIyMgU2Vzc2lvbiBpbmZvcm1hdGlvbgoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCg==