projDir <- "/mnt/scratcha/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020"
outDirBit <- "AnaWiSce/Attempt1"
nbPcToComp <- 50
suppressMessages(library(ggplot2))
suppressMessages(library(scater))
suppressMessages(library(scran))
suppressMessages(library(dplyr))
suppressMessages(library(dynamicTreeCut))
suppressMessages(library(cluster)) # for silhouette

fontsize <- theme(axis.text=element_text(size=12), axis.title=element_text(size=16))

1 Clustering

Source: clustering methods in the Hemberg group material and its ‘biocellgen variant’, with some of its text copied with few edits only. Also see the OSCA chapter on clustering.

Once we have normalized the data and removed confounders we can carry out analyses that are relevant to the biological questions at hand. The exact nature of the analysis depends on the dataset. One of the most promising applications of scRNA-seq is de novo discovery and annotation of cell-types based on transcription profiles. This requires the identification of groups of cells based on the similarities of the transcriptomes without any prior knowledge of the labels, or unsupervised clustering. To avoid the challenges caused byt the noise and high dimensionality of the scRNA-seq data, clusring is performed after feature selection and dimensionality reduction, usually on the PCA output.

We will introduce three widely used clustering methods: 1) hierarchical, 2) k-means and 3) graph-based clustering, apply them on the data set studied and measure their quality.

We will use the denoised log-expression values to cluster cells.

1.1 Load data

We will load the R file keeping the SCE (SingleCellExperiment) object with the normalised counts for 500 cells per sample and the outcome of feature selection followed by dimensionality reduction.

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

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

1.2 Clustering cells into putative subpopulations

1.2.1 Defining cell clusters from expression data

1.2.1.1 Hierarchical clustering

Hierarchical clustering builds a hierarchy of clusters yielding a dendrogram that groups together cells with similar expression patterns across the chosen genes.

There are two types of strategies: - Agglomerative (bottom-up): each observation starts in its own cluster, and pairs of clusters are merged as one moves up the hierarchy. - Divisive (top-down): all observations start in one cluster, and splits are performed recursively as one moves down the hierarchy.

1.2.1.1.1 Clustering

Here we will use hierarchical clustering on the Euclidean distances between cells, using Ward D2 criterion to minimize the total variance within each cluster.

# get PCs
pcs <- reducedDim(sce, "PCA")
# compute distance:
my.dist <- dist(pcs)
# derive tree:
my.tree <- hclust(my.dist, method="ward.D2")

Show tree:

plot(my.tree, labels = FALSE)

Clusters are identified in the dendrogram using a dynamic tree cut [@doi:10.1093/bioinformatics/btm563].

# identify clustering by cutting branches, requesting a minimum cluster size of 20 cells.
my.clusters <- unname(cutreeDynamic(my.tree, distM=as.matrix(my.dist), minClusterSize=20, verbose=0))

Let us count cells for each cluster and each sample group and for each sample.

# per sample group
table(my.clusters, sce$source_name)
           
my.clusters ABMMC ETV6-RUNX1  HHD PBMMC PRE-T
         1      0       1078    1     8     0
         2      0          0  883     0     3
         3      0          0    0     0   808
         4      0        177   51   429    76
         5      0        128   50   417    61
         6      0        463    0     0     1
         7      0          4    3   249     6
         8      0        106    0   116     5
         9      0          7    7   180    30
         10     0         37    5   101    10
# per sample
table(my.clusters, sce$Sample.Name)
           
my.clusters GSM3872434 GSM3872435 GSM3872436 GSM3872437 GSM3872438 GSM3872439
         1          37        462        230        349          1          0
         2           0          0          0          0        404        479
         3           0          0          0          0          0          0
         4           3         12        140         22         41         10
         5           2         13         94         19         44          6
         6         451          9          3          0          0          0
         7           2          1          0          1          3          0
         8           2          2         17         85          0          0
         9           1          1          0          5          7          0
         10          2          0         16         19          0          5
           
my.clusters GSM3872440 GSM3872441 GSM3872442 GSM3872443 GSM3872444
         1           0          0          0          0          8
         2           3          0          0          0          0
         3         486        322          0          0          0
         4           4         72        190        114        125
         5           4         57        103        135        179
         6           1          0          0          0          0
         7           0          6        164         27         58
         8           0          5          0        113          3
         9           2         28         30         47        103
         10          0         10         13         64         24

Clusters mostly include cells from one sample or the other. This suggests that the samples differ, and/or the presence of batch effect.

Let us show cluster assignments on the t-SNE.

# store cluster assignemnt in SCE object:
sce$cluster <- factor(my.clusters)
# make, store and show TSNE plot:
g <- plotTSNE(sce, colour_by = "cluster", size_by = "sum")
g

Split by sample group:

# split by sample and show:
g <- g + facet_wrap(. ~ sce$source_name)
g

In some areas cells are not all assigned to the same cluster.

1.2.1.1.2 Separatedness

The congruence of clusters may be assessed by computing the sillhouette for each cell. The larger the value the closer the cell to cells in its cluster than to cells in other clusters. Cells closer to cells in other clusters have a negative value. Good cluster separation is indicated by clusters whose cells have large silhouette values.

Compute silhouette:

sil <- silhouette(my.clusters, dist = my.dist)

Plot silhouettes with one color per cluster and cells with a negative silhouette with the color of their closest cluster. Add the average silhouette for each cluster and all cells.

# prepare colours:
clust.col <- scater:::.get_palette("tableau10medium") # hidden scater colours
sil.cols <- clust.col[ifelse(sil[,3] > 0, sil[,1], sil[,2])]
sil.cols <- sil.cols[order(-sil[,1], sil[,3])]
# 
plot(sil, main = paste(length(unique(my.clusters)), "clusters"), 
    border=sil.cols, col=sil.cols, do.col.sort=FALSE) 

The plot shows cells with negative silhoutette indicating too many clusters were defined. The method and parameters used defined clusters with properties that may not fit the data set, eg clusters with the same diameter.

1.2.1.2 k-means

In k-means clustering, the goal is to partition N cells into k different clusters. In an iterative manner, cluster centers are assigned and each cell is assigned to its nearest cluster:

This approach assumes a pre-determined number of round equally-sized clusters.

The dendogram built above suggests there may be 6 large populations.

Let us define 6 clusters.

# define clusters:
kclust <- kmeans(pcs, centers=6)

# compute silhouette
sil <- silhouette(kclust$cluster, dist(pcs))

# plot silhouette:
clust.col <- scater:::.get_palette("tableau10medium") # hidden scater colours
sil.cols <- clust.col[ifelse(sil[,3] > 0, sil[,1], sil[,2])]
sil.cols <- sil.cols[order(-sil[,1], sil[,3])]
plot(sil, main = paste(length(unique(kclust$cluster)), "clusters"), 
    border=sil.cols, col=sil.cols, do.col.sort=FALSE) 

Show clusters on t-SNE:

tSneCoord <- as.data.frame(reducedDim(sce, "TSNE"))
colnames(tSneCoord) <- c("x", "y")
p2 <- ggplot(tSneCoord, aes(x, y)) +
    geom_point(aes(color = as.factor(kclust$cluster)))
p2

Split by sample type:

p2 + facet_wrap(~ sce$source_name)

To find the most appropriate number of clusters, one performs the analysis for a series of k values, computes a measure of fit of the clusters defined: the within cluster sum-of-square. This value decreases as k increases, by an amount that decreases with k. Choose k at the inflexion point of the curve.

library(broom)
require(tibble)
Loading required package: tibble
require(dplyr)
require(tidyr)
Loading required package: tidyr

Attaching package: 'tidyr'
The following object is masked from 'package:S4Vectors':

    expand
library(purrr)

Attaching package: 'purrr'
The following object is masked from 'package:DelayedArray':

    simplify
The following object is masked from 'package:GenomicRanges':

    reduce
The following object is masked from 'package:IRanges':

    reduce
points <- as_tibble(pcs)

kclusts <- tibble(k = 1:20) %>%
  mutate(
    kclust = map(k, ~kmeans(points, .x)),
    tidied = map(kclust, tidy),
    glanced = map(kclust, glance),
    augmented = map(kclust, augment, points)
  )

clusters <- kclusts %>%
  unnest(tidied)

assignments <- kclusts %>% 
  unnest(augmented)

clusterings <- kclusts %>%
  unnest(glanced)

Plot the total within cluster sum-of-squares and decide on k.

ggplot(clusterings, aes(k, tot.withinss)) +
  geom_line()

Copy the cluster assignment to the SCE object.

df <- as.data.frame(assignments)
sce$kmeans10 <- as.numeric(df[df$k == 10, ".cluster"])

Check silhouette for a k of 10.

library(cluster)
clust.col <- scater:::.get_palette("tableau10medium") # hidden scater colours
sil <- silhouette(sce$kmeans10, dist = my.dist)
sil.cols <- clust.col[ifelse(sil[,3] > 0, sil[,1], sil[,2])]
sil.cols <- sil.cols[order(-sil[,1], sil[,3])]
plot(sil, main = paste(length(unique(sce$kmeans10)), "clusters"), 
    border=sil.cols, col=sil.cols, do.col.sort=FALSE) 

1.2.1.3 Graph-based clustering

Graph-based clustering entails building a shared nearest-neighbour graph using cells as nodes and their similarity as edges, then identifying ‘communities’ of cells within the network.

We will: build the graph, define clusters, check membership across samples, show membership on t-SNE and assess its quality.

#compute graph
snn.gr <- buildSNNGraph(sce, use.dimred="PCA")
# derive clusters
cluster.out <- igraph::cluster_walktrap(snn.gr)
# count cell in each cluster for each sample
my.clusters <- cluster.out$membership
table(my.clusters, sce$source_name)
           
my.clusters ABMMC ETV6-RUNX1 HHD PBMMC PRE-T
         1      0          0 687     1     4
         2      0          3   1   162     0
         3      0          0   0     0   288
         4      0          0   0     0   206
         5      0        186  53   253    79
         6      0        106   0   119     5
         7      0          5   9   193    16
         8      0          0   0     0   259
         9      0         83  37   115    25
         10     0        547   0    14     0
         11     0          0 197     0     0
         12     0        402   0     0     0
         13     0         86   0     0     0
         14     0          2   2   155     2
         15     0        504   0     0     0
         16     0         17   1    57     5
         17     0          3   0    30     5
         18     0         35   7    97    28
         19     0          5   1   121    23
         20     0          0   0     0    55
         21     0          0   1   179     0
         22     0         16   4     4     0
table(my.clusters, sce$Sample.Name)
           
my.clusters GSM3872434 GSM3872435 GSM3872436 GSM3872437 GSM3872438 GSM3872439
         1           0          0          0          0        212        475
         2           0          0          3          0          1          0
         3           0          0          0          0          0          0
         4           0          0          0          0          0          0
         5           4         12        147         23         43         10
         6           2          2         17         85          0          0
         7           1          1          2          1          9          0
         8           0          0          0          0          0          0
         9           1          7         66          9         32          5
         10          4         71        130        342          0          0
         11          0          0          0          0        193          4
         12        388         13          1          0          0          0
         13         85          1          0          0          0          0
         14          1          0          0          1          2          0
         15         12        386         99          7          0          0
         16          0          0          7         10          0          1
         17          1          0          1          1          0          0
         18          0          6         20          9          6          1
         19          0          1          0          4          1          0
         20          0          0          0          0          0          0
         21          0          0          0          0          1          0
         22          1          0          7          8          0          4
           
my.clusters GSM3872440 GSM3872441 GSM3872442 GSM3872443 GSM3872444
         1           3          1          0          0          1
         2           0          0         71         25         66
         3           6        282          0          0          0
         4         176         30          0          0          0
         5           3         76         14        112        127
         6           0          5          0        116          3
         7           0         16         96         41         56
         8         255          4          0          0          0
         9           3         22          6         46         63
         10          0          0          0          4         10
         11          0          0          0          0          0
         12          0          0          0          0          0
         13          0          0          0          0          0
         14          0          2         99         17         39
         15          0          0          0          0          0
         16          0          5          2         42         13
         17          0          5         10         13          7
         18          3         25         19         46         32
         19          2         21          4         36         81
         20         49          6          0          0          0
         21          0          0        179          0          0
         22          0          0          0          2          2
# store membership
sce$cluster <- factor(my.clusters)
# shoe clusters on TSNE
p <- plotTSNE(sce, colour_by="cluster") + fontsize
p

p + facet_wrap(~ sce$source_name)

Compute modularity to assess clusters quality. The closer to 1 the better.

igraph::modularity(cluster.out)
[1] 0.8546914
mod.out <- clusterModularity(snn.gr, my.clusters, get.weights=TRUE)
ratio <- mod.out$observed/mod.out$expected
lratio <- log10(ratio + 1)

library(pheatmap)
pheatmap(lratio, cluster_rows=FALSE, cluster_cols=FALSE, 
    color=colorRampPalette(c("white", "blue"))(100))

Show similarity between clusters on a network.

cluster.gr <- igraph::graph_from_adjacency_matrix(ratio, 
    mode="undirected", weighted=TRUE, diag=FALSE)
plot(cluster.gr, edge.width=igraph::E(cluster.gr)$weight*10)  

Write SCE object to file.

tmpFn <- sprintf("%s/%s/Robjects/%s_sce_nz_postDeconv%s_clustered.Rds", projDir, outDirBit, setName, setSuf)
print(tmpFn)
[1] "/mnt/scratcha/bioinformatics/baller01/20200511_FernandesM_ME_crukBiSs2020/AnaWiSce/Attempt1/Robjects/caron_sce_nz_postDeconv_5hCellPerSpl_clustered.Rds"
saveRDS(sce, file=tmpFn)

Challenge Apply graph based clustering on a single sample (group?).

# eg SRR9264354, GSM3872444, a PBMMC

SplToGet <- "GSM3872444"

# extract cells for this sample:
cellsToGet <- colData(sce) %>%
    data.frame() %>%
    filter(Sample.Name == SplToGet) %>%
    pull(Barcode)
sce_c <- sce[, cellsToGet]
# normalise
# PCA
# cluster
LS0tCnRpdGxlOiAiQ1JVSyBDSSBTdW1tZXIgU2Nob29sIDIwMjAgLSBpbnRyb2R1Y3Rpb24gdG8gc2luZ2xlLWNlbGwgUk5BLXNlcSBhbmFseXNpcyIKc3VidGl0bGU6ICdDbHVzdGVyaW5nJwoKYXV0aG9yOiAiU3RlcGhhbmUgQmFsbGVyZWF1LCBaZXluZXAgS2FsZW5kZXIgQXRhaywgS2F0YXJ6eW5hIEthbmlhIgojZGF0ZTogJ2ByIHN0cmZ0aW1lKFN5cy50aW1lKCksIGZvcm1hdCA9ICIlQiAlZCwgJVkiKWAnCmRhdGU6IEp1bHkgMjAyMAojYmlibGlvZ3JhcGh5OiBiaWJsaW9ncmFwaHkuYmliCiNjc2w6IGJpb21lZC1jZW50cmFsLmNzbApvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIG51bWJlcl9zZWN0aW9uczogeWVzCiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGZpZ19jYXB0aW9uOiB5ZXMKICAgIHNlbGZfY29udGFpbmVkOiB0cnVlCiAgICBmaWdfd2lkdGg6IDYKICAgIGZpZ19oZWlnaHQ6IDQKLS0tCgpgYGB7cn0KcHJvakRpciA8LSAiL21udC9zY3JhdGNoYS9iaW9pbmZvcm1hdGljcy9iYWxsZXIwMS8yMDIwMDUxMV9GZXJuYW5kZXNNX01FX2NydWtCaVNzMjAyMCIKb3V0RGlyQml0IDwtICJBbmFXaVNjZS9BdHRlbXB0MSIKbmJQY1RvQ29tcCA8LSA1MApgYGAKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFLCBlY2hvPUZBTFNFfQojIEZpcnN0LCBzZXQgc29tZSB2YXJpYWJsZXM6CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkKb3B0aW9ucyhzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCnNldC5zZWVkKDEyMykgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CmtuaXRyOjpvcHRzX2NodW5rJHNldChldmFsID0gVFJVRSkgCmBgYAoKYGBge3IsIHdhcm5pbmc9RkFMU0V9CnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShnZ3Bsb3QyKSkKc3VwcHJlc3NNZXNzYWdlcyhsaWJyYXJ5KHNjYXRlcikpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShzY3JhbikpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShkcGx5cikpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShkeW5hbWljVHJlZUN1dCkpCnN1cHByZXNzTWVzc2FnZXMobGlicmFyeShjbHVzdGVyKSkgIyBmb3Igc2lsaG91ZXR0ZQoKZm9udHNpemUgPC0gdGhlbWUoYXhpcy50ZXh0PWVsZW1lbnRfdGV4dChzaXplPTEyKSwgYXhpcy50aXRsZT1lbGVtZW50X3RleHQoc2l6ZT0xNikpCmBgYAoKIyBDbHVzdGVyaW5nCgo8aW1nIHNyYz0iLi4vLi4vSW1hZ2VzL0FuZHJld3MyMDE3X0ZpZzEucG5nIiBzdHlsZT0ibWFyZ2luOmF1dG87IGRpc3BsYXk6YmxvY2siIC8+CgpTb3VyY2U6IFtjbHVzdGVyaW5nIG1ldGhvZHNdKGh0dHBzOi8vaGVtYmVyZy1sYWIuZ2l0aHViLmlvL3NjUk5BLnNlcS5jb3Vyc2UvYmlvbG9naWNhbC1hbmFseXNpcy5odG1sIyNjbHVzdGVyaW5nLW1ldGhvZHMpIGluIHRoZSBIZW1iZXJnIGdyb3VwIG1hdGVyaWFsIGFuZCBbaXRzICdiaW9jZWxsZ2VuIHZhcmlhbnQnXShodHRwczovL2Jpb2NlbGxnZW4tcHVibGljLnN2aS5lZHUuYXUvbWlnXzIwMTlfc2NybmFzZXEtd29ya3Nob3AvcHVibGljL2NsdXN0ZXJpbmctYW5kLWNlbGwtYW5ub3RhdGlvbi5odG1sI2NsdXN0ZXJpbmctbWV0aG9kcy4pLCB3aXRoIHNvbWUgb2YgaXRzIHRleHQgY29waWVkIHdpdGggZmV3IGVkaXRzIG9ubHkuIEFsc28gc2VlIHRoZSBbT1NDQSBjaGFwdGVyIG9uIGNsdXN0ZXJpbmddKGh0dHBzOi8vb3NjYS5iaW9jb25kdWN0b3Iub3JnL2NsdXN0ZXJpbmcuaHRtbCNrLW1lYW5zLWNsdXN0ZXJpbmcpLgoKT25jZSB3ZSBoYXZlIG5vcm1hbGl6ZWQgdGhlIGRhdGEgYW5kIHJlbW92ZWQgY29uZm91bmRlcnMgd2UgY2FuIGNhcnJ5IG91dCBhbmFseXNlcyB0aGF0IGFyZSByZWxldmFudCB0byB0aGUgYmlvbG9naWNhbCBxdWVzdGlvbnMgYXQgaGFuZC4gVGhlIGV4YWN0IG5hdHVyZSBvZiB0aGUgYW5hbHlzaXMgZGVwZW5kcyBvbiB0aGUgZGF0YXNldC4gT25lIG9mIHRoZSBtb3N0IHByb21pc2luZyBhcHBsaWNhdGlvbnMgb2Ygc2NSTkEtc2VxIGlzIGRlIG5vdm8gZGlzY292ZXJ5IGFuZCBhbm5vdGF0aW9uIG9mIGNlbGwtdHlwZXMgYmFzZWQgb24gdHJhbnNjcmlwdGlvbiBwcm9maWxlcy4gVGhpcyByZXF1aXJlcyB0aGUgaWRlbnRpZmljYXRpb24gb2YgZ3JvdXBzIG9mIGNlbGxzIGJhc2VkIG9uIHRoZSBzaW1pbGFyaXRpZXMgb2YgdGhlIHRyYW5zY3JpcHRvbWVzIHdpdGhvdXQgYW55IHByaW9yIGtub3dsZWRnZSBvZiB0aGUgbGFiZWxzLCBvciB1bnN1cGVydmlzZWQgY2x1c3RlcmluZy4gVG8gYXZvaWQgdGhlIGNoYWxsZW5nZXMgY2F1c2VkIGJ5dCB0aGUgbm9pc2UgYW5kIGhpZ2ggZGltZW5zaW9uYWxpdHkgb2YgdGhlIHNjUk5BLXNlcSBkYXRhLCBjbHVzcmluZyBpcyBwZXJmb3JtZWQgYWZ0ZXIgZmVhdHVyZSBzZWxlY3Rpb24gYW5kIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiwgdXN1YWxseSBvbiB0aGUgUENBIG91dHB1dC4KCldlIHdpbGwgaW50cm9kdWNlIHRocmVlIHdpZGVseSB1c2VkIGNsdXN0ZXJpbmcgbWV0aG9kczogMSkgaGllcmFyY2hpY2FsLCAyKSBrLW1lYW5zIGFuZCAzKSBncmFwaC1iYXNlZCBjbHVzdGVyaW5nLCBhcHBseSB0aGVtIG9uIHRoZSBkYXRhIHNldCBzdHVkaWVkIGFuZCBtZWFzdXJlIHRoZWlyIHF1YWxpdHkuCgpXZSB3aWxsIHVzZSB0aGUgZGVub2lzZWQgbG9nLWV4cHJlc3Npb24gdmFsdWVzIHRvIGNsdXN0ZXIgY2VsbHMuCgojIyBMb2FkIGRhdGEKCldlIHdpbGwgbG9hZCB0aGUgUiBmaWxlIGtlZXBpbmcgdGhlIFNDRSAoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpIG9iamVjdCB3aXRoIHRoZSBub3JtYWxpc2VkIGNvdW50cyBmb3IgNTAwIGNlbGxzIHBlciBzYW1wbGUgYW5kIHRoZSBvdXRjb21lIG9mIGZlYXR1cmUgc2VsZWN0aW9uIGZvbGxvd2VkIGJ5IGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbi4KCmBgYHtyfQpzZXROYW1lIDwtICJjYXJvbiIKc2V0U3VmIDwtICJfNWhDZWxsUGVyU3BsIgoKIyBSZWFkIG9iamVjdCBpbjoKdG1wRm4gPC0gc3ByaW50ZigiJXMvJXMvUm9iamVjdHMvJXNfc2NlX256X3Bvc3REZWNvbnYlc19kZW5vaXNlZC5SZHMiLCBwcm9qRGlyLCBvdXREaXJCaXQsIHNldE5hbWUsIHNldFN1ZikKcHJpbnQodG1wRm4pCmlmKCFmaWxlLmV4aXN0cyh0bXBGbikpCnsKCWtuaXRyOjprbml0X2V4aXQoKQp9CnNjZSA8LSByZWFkUkRTKHRtcEZuKQpzY2UKCiMgaGVhZChyb3dEYXRhKHNjZSkpCiNhbnkoZHVwbGljYXRlZChyb3dEYXRhKHNjZSkkZW5zZW1ibF9nZW5lX2lkKSkKIyBzb21lIGZ1bmN0aW9uKHMpIHVzZWQgYmVsb3cgY29tcGxhaW4gYWJvdXQgJ3N0cmFuZCcgYWxyZWFkeSBiZWluZyB1c2VkIGluIHJvdyBkYXRhLAojIHNvIHJlbmFtZSB0aGF0IGNvbHVtbiBub3c6CiNjb2xuYW1lcyhyb3dEYXRhKHNjZSkpW2NvbG5hbWVzKHJvd0RhdGEoc2NlKSkgPT0gInN0cmFuZCJdIDwtICJzdHJhbmROdW0iCiNhc3NheU5hbWVzKHNjZSkKI3JlZHVjZWREaW1OYW1lcyhzY2UpCmBgYAoKIyMgQ2x1c3RlcmluZyBjZWxscyBpbnRvIHB1dGF0aXZlIHN1YnBvcHVsYXRpb25zCgo8IS0tClNlZSBodHRwczovL2hlbWJlcmctbGFiLmdpdGh1Yi5pby9zY1JOQS5zZXEuY291cnNlL2luZGV4Lmh0bWwgZm9yIHRocmVlIHR5cGVzIG9mIGNsdXN0ZXJpbmcuClNlZSBodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3B1Ym1lZC8yNzMwMzA1NyBmb3IgcmV2aWV3Ci0tPgoKIyMjIERlZmluaW5nIGNlbGwgY2x1c3RlcnMgZnJvbSBleHByZXNzaW9uIGRhdGEKCiMjIyMgSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcKCkhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGJ1aWxkcyBhIGhpZXJhcmNoeSBvZiBjbHVzdGVycyB5aWVsZGluZyBhIGRlbmRyb2dyYW0gdGhhdCBncm91cHMgdG9nZXRoZXIgY2VsbHMgd2l0aCBzaW1pbGFyIGV4cHJlc3Npb24gcGF0dGVybnMgYWNyb3NzIHRoZSBjaG9zZW4gZ2VuZXMuCgpUaGVyZSBhcmUgdHdvIHR5cGVzIG9mIHN0cmF0ZWdpZXM6Ci0gQWdnbG9tZXJhdGl2ZSAoYm90dG9tLXVwKTogZWFjaCBvYnNlcnZhdGlvbiBzdGFydHMgaW4gaXRzIG93biBjbHVzdGVyLCBhbmQgcGFpcnMgb2YgY2x1c3RlcnMgYXJlIG1lcmdlZCBhcyBvbmUgbW92ZXMgdXAgdGhlIGhpZXJhcmNoeS4KLSBEaXZpc2l2ZSAodG9wLWRvd24pOiBhbGwgb2JzZXJ2YXRpb25zIHN0YXJ0IGluIG9uZSBjbHVzdGVyLCBhbmQgc3BsaXRzIGFyZSBwZXJmb3JtZWQgcmVjdXJzaXZlbHkgYXMgb25lIG1vdmVzIGRvd24gdGhlIGhpZXJhcmNoeS4KCjxpbWcgc3JjPSIuLi8uLi9JbWFnZXMvYmlvQ2VsbEdlbkhpZXJhci5wbmciIHN0eWxlPSJtYXJnaW46YXV0bzsgZGlzcGxheTpibG9jayIgLz4KCiMjIyMjIENsdXN0ZXJpbmcKCkhlcmUgd2Ugd2lsbCB1c2UgaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgb24gdGhlIEV1Y2xpZGVhbiBkaXN0YW5jZXMgYmV0d2VlbiBjZWxscywgdXNpbmcgV2FyZCBEMiBjcml0ZXJpb24gdG8gbWluaW1pemUgdGhlIHRvdGFsIHZhcmlhbmNlIHdpdGhpbiBlYWNoIGNsdXN0ZXIuCgpgYGB7ciBjb21wX2hpZXJhcn0KIyBnZXQgUENzCnBjcyA8LSByZWR1Y2VkRGltKHNjZSwgIlBDQSIpCiMgY29tcHV0ZSBkaXN0YW5jZToKbXkuZGlzdCA8LSBkaXN0KHBjcykKIyBkZXJpdmUgdHJlZToKbXkudHJlZSA8LSBoY2x1c3QobXkuZGlzdCwgbWV0aG9kPSJ3YXJkLkQyIikKYGBgCgpTaG93IHRyZWU6CgpgYGB7ciBwbG90X3RyZWVfaGllcmFyfQpwbG90KG15LnRyZWUsIGxhYmVscyA9IEZBTFNFKQpgYGAKCkNsdXN0ZXJzIGFyZSBpZGVudGlmaWVkIGluIHRoZSBkZW5kcm9ncmFtIHVzaW5nIGEgZHluYW1pYyB0cmVlIGN1dCBbQGRvaToxMC4xMDkzL2Jpb2luZm9ybWF0aWNzL2J0bTU2M10uCgpgYGB7ciBjdXRUcmVlX2hpZXJhcn0KIyBpZGVudGlmeSBjbHVzdGVyaW5nIGJ5IGN1dHRpbmcgYnJhbmNoZXMsIHJlcXVlc3RpbmcgYSBtaW5pbXVtIGNsdXN0ZXIgc2l6ZSBvZiAyMCBjZWxscy4KbXkuY2x1c3RlcnMgPC0gdW5uYW1lKGN1dHJlZUR5bmFtaWMobXkudHJlZSwgZGlzdE09YXMubWF0cml4KG15LmRpc3QpLCBtaW5DbHVzdGVyU2l6ZT0yMCwgdmVyYm9zZT0wKSkKYGBgCgpMZXQgdXMgY291bnQgY2VsbHMgZm9yIGVhY2ggY2x1c3RlciBhbmQgZWFjaCBzYW1wbGUgZ3JvdXAgYW5kIGZvciBlYWNoIHNhbXBsZS4KCmBgYHtyIHRhYmxlX2hpZXJhcn0KIyBwZXIgc2FtcGxlIGdyb3VwCnRhYmxlKG15LmNsdXN0ZXJzLCBzY2Ukc291cmNlX25hbWUpCiMgcGVyIHNhbXBsZQp0YWJsZShteS5jbHVzdGVycywgc2NlJFNhbXBsZS5OYW1lKQpgYGAKCkNsdXN0ZXJzIG1vc3RseSBpbmNsdWRlIGNlbGxzIGZyb20gb25lIHNhbXBsZSBvciB0aGUgb3RoZXIuIFRoaXMgc3VnZ2VzdHMgdGhhdCB0aGUgc2FtcGxlcyBkaWZmZXIsIGFuZC9vciB0aGUgcHJlc2VuY2Ugb2YgYmF0Y2ggZWZmZWN0LgoKTGV0IHVzIHNob3cgY2x1c3RlciBhc3NpZ25tZW50cyBvbiB0aGUgdC1TTkUuCgpgYGB7ciBwbG90X3RzbmVfaGllcmFyLCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD02fQojIHN0b3JlIGNsdXN0ZXIgYXNzaWduZW1udCBpbiBTQ0Ugb2JqZWN0OgpzY2UkY2x1c3RlciA8LSBmYWN0b3IobXkuY2x1c3RlcnMpCiMgbWFrZSwgc3RvcmUgYW5kIHNob3cgVFNORSBwbG90OgpnIDwtIHBsb3RUU05FKHNjZSwgY29sb3VyX2J5ID0gImNsdXN0ZXIiLCBzaXplX2J5ID0gInN1bSIpCmcKYGBgCgpTcGxpdCBieSBzYW1wbGUgZ3JvdXA6CgpgYGB7ciBwbG90X3RzbmVfaGllcmFyX2ZhY2V0LCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTJ9CiMgc3BsaXQgYnkgc2FtcGxlIGFuZCBzaG93OgpnIDwtIGcgKyBmYWNldF93cmFwKC4gfiBzY2Ukc291cmNlX25hbWUpCmcKYGBgCgpJbiBzb21lIGFyZWFzIGNlbGxzIGFyZSBub3QgYWxsIGFzc2lnbmVkIHRvIHRoZSBzYW1lIGNsdXN0ZXIuCgojIyMjIyBTZXBhcmF0ZWRuZXNzCgpUaGUgY29uZ3J1ZW5jZSBvZiBjbHVzdGVycyBtYXkgYmUgYXNzZXNzZWQgYnkgY29tcHV0aW5nIHRoZSBzaWxsaG91ZXR0ZSBmb3IgZWFjaCBjZWxsLgpUaGUgbGFyZ2VyIHRoZSB2YWx1ZSB0aGUgY2xvc2VyIHRoZSBjZWxsIHRvIGNlbGxzIGluIGl0cyBjbHVzdGVyIHRoYW4gdG8gY2VsbHMgaW4gb3RoZXIgY2x1c3RlcnMuCkNlbGxzIGNsb3NlciB0byBjZWxscyBpbiBvdGhlciBjbHVzdGVycyBoYXZlIGEgbmVnYXRpdmUgdmFsdWUuCkdvb2QgY2x1c3RlciBzZXBhcmF0aW9uIGlzIGluZGljYXRlZCBieSBjbHVzdGVycyB3aG9zZSBjZWxscyBoYXZlIGxhcmdlIHNpbGhvdWV0dGUgdmFsdWVzLgoKQ29tcHV0ZSBzaWxob3VldHRlOiAKCmBgYHtyIGNvbXBfc2lsaG91ZXR0ZV9oaWVyYXJ9CnNpbCA8LSBzaWxob3VldHRlKG15LmNsdXN0ZXJzLCBkaXN0ID0gbXkuZGlzdCkKYGBgCgpQbG90IHNpbGhvdWV0dGVzIHdpdGggb25lIGNvbG9yIHBlciBjbHVzdGVyIGFuZCBjZWxscyB3aXRoIGEgbmVnYXRpdmUgc2lsaG91ZXR0ZSB3aXRoIHRoZSBjb2xvciBvZiB0aGVpciBjbG9zZXN0IGNsdXN0ZXIuCkFkZCB0aGUgYXZlcmFnZSBzaWxob3VldHRlIGZvciBlYWNoIGNsdXN0ZXIgYW5kIGFsbCBjZWxscy4gCgpgYGB7ciBwbG90X3NpbGhvdWV0dGVfaGllcmFyfQojIHByZXBhcmUgY29sb3VyczoKY2x1c3QuY29sIDwtIHNjYXRlcjo6Oi5nZXRfcGFsZXR0ZSgidGFibGVhdTEwbWVkaXVtIikgIyBoaWRkZW4gc2NhdGVyIGNvbG91cnMKc2lsLmNvbHMgPC0gY2x1c3QuY29sW2lmZWxzZShzaWxbLDNdID4gMCwgc2lsWywxXSwgc2lsWywyXSldCnNpbC5jb2xzIDwtIHNpbC5jb2xzW29yZGVyKC1zaWxbLDFdLCBzaWxbLDNdKV0KIyAKcGxvdChzaWwsIG1haW4gPSBwYXN0ZShsZW5ndGgodW5pcXVlKG15LmNsdXN0ZXJzKSksICJjbHVzdGVycyIpLCAKCWJvcmRlcj1zaWwuY29scywgY29sPXNpbC5jb2xzLCBkby5jb2wuc29ydD1GQUxTRSkgCmBgYAoKVGhlIHBsb3Qgc2hvd3MgY2VsbHMgd2l0aCBuZWdhdGl2ZSBzaWxob3V0ZXR0ZSBpbmRpY2F0aW5nIHRvbyBtYW55IGNsdXN0ZXJzIHdlcmUgZGVmaW5lZC4KVGhlIG1ldGhvZCBhbmQgcGFyYW1ldGVycyB1c2VkIGRlZmluZWQgY2x1c3RlcnMgd2l0aCBwcm9wZXJ0aWVzIHRoYXQgbWF5IG5vdCBmaXQgdGhlIGRhdGEgc2V0LCBlZyBjbHVzdGVycyB3aXRoIHRoZSBzYW1lIGRpYW1ldGVyLgoKIyMjIyBrLW1lYW5zCgpJbiBrLW1lYW5zIGNsdXN0ZXJpbmcsIHRoZSBnb2FsIGlzIHRvIHBhcnRpdGlvbiBOIGNlbGxzIGludG8gayBkaWZmZXJlbnQgY2x1c3RlcnMuIEluIGFuIGl0ZXJhdGl2ZSBtYW5uZXIsIGNsdXN0ZXIgY2VudGVycyBhcmUgYXNzaWduZWQgYW5kIGVhY2ggY2VsbCBpcyBhc3NpZ25lZCB0byBpdHMgbmVhcmVzdCBjbHVzdGVyOgoKPGltZyBzcmM9Ii4uLy4uL0ltYWdlcy9iaW9DZWxsR2VuS21lYW4ucG5nIiBzdHlsZT0ibWFyZ2luOmF1dG87IGRpc3BsYXk6YmxvY2siIC8+CgpUaGlzIGFwcHJvYWNoIGFzc3VtZXMgYSBwcmUtZGV0ZXJtaW5lZCBudW1iZXIgb2Ygcm91bmQgZXF1YWxseS1zaXplZCBjbHVzdGVycy4KClRoZSBkZW5kb2dyYW0gYnVpbHQgYWJvdmUgc3VnZ2VzdHMgdGhlcmUgbWF5IGJlIDYgbGFyZ2UgcG9wdWxhdGlvbnMuCgpMZXQgdXMgZGVmaW5lIDYgY2x1c3RlcnMuCgpgYGB7ciBjb21wX2ttZWFuc19rNn0KIyBkZWZpbmUgY2x1c3RlcnM6CmtjbHVzdCA8LSBrbWVhbnMocGNzLCBjZW50ZXJzPTYpCgojIGNvbXB1dGUgc2lsaG91ZXR0ZQpzaWwgPC0gc2lsaG91ZXR0ZShrY2x1c3QkY2x1c3RlciwgZGlzdChwY3MpKQoKIyBwbG90IHNpbGhvdWV0dGU6CmNsdXN0LmNvbCA8LSBzY2F0ZXI6OjouZ2V0X3BhbGV0dGUoInRhYmxlYXUxMG1lZGl1bSIpICMgaGlkZGVuIHNjYXRlciBjb2xvdXJzCnNpbC5jb2xzIDwtIGNsdXN0LmNvbFtpZmVsc2Uoc2lsWywzXSA+IDAsIHNpbFssMV0sIHNpbFssMl0pXQpzaWwuY29scyA8LSBzaWwuY29sc1tvcmRlcigtc2lsWywxXSwgc2lsWywzXSldCnBsb3Qoc2lsLCBtYWluID0gcGFzdGUobGVuZ3RoKHVuaXF1ZShrY2x1c3QkY2x1c3RlcikpLCAiY2x1c3RlcnMiKSwgCiAgICBib3JkZXI9c2lsLmNvbHMsIGNvbD1zaWwuY29scywgZG8uY29sLnNvcnQ9RkFMU0UpIApgYGAKClNob3cgY2x1c3RlcnMgb24gdC1TTkU6CgpgYGB7ciBwbG90X3RTTkVfa21lYW5zX2s2LCBmaWcud2lkdGg9NiwgZmlnLmhlaWdodD02fQp0U25lQ29vcmQgPC0gYXMuZGF0YS5mcmFtZShyZWR1Y2VkRGltKHNjZSwgIlRTTkUiKSkKY29sbmFtZXModFNuZUNvb3JkKSA8LSBjKCJ4IiwgInkiKQpwMiA8LSBnZ3Bsb3QodFNuZUNvb3JkLCBhZXMoeCwgeSkpICsKCWdlb21fcG9pbnQoYWVzKGNvbG9yID0gYXMuZmFjdG9yKGtjbHVzdCRjbHVzdGVyKSkpCnAyCmBgYAoKU3BsaXQgYnkgc2FtcGxlIHR5cGU6CgpgYGB7ciBwbG90X3RTTkVfa21lYW5zX2s2X3NwbGl0LCBmaWcud2lkdGg9MTIsIGZpZy5oZWlnaHQ9MTJ9CnAyICsgZmFjZXRfd3JhcCh+IHNjZSRzb3VyY2VfbmFtZSkKYGBgCgpUbyBmaW5kIHRoZSBtb3N0IGFwcHJvcHJpYXRlIG51bWJlciBvZiBjbHVzdGVycywgb25lIHBlcmZvcm1zIHRoZSBhbmFseXNpcyBmb3IgYSBzZXJpZXMgb2YgayB2YWx1ZXMsIGNvbXB1dGVzIGEgbWVhc3VyZSBvZiBmaXQgb2YgdGhlIGNsdXN0ZXJzIGRlZmluZWQ6IHRoZSB3aXRoaW4gY2x1c3RlciBzdW0tb2Ytc3F1YXJlLiBUaGlzIHZhbHVlIGRlY3JlYXNlcyBhcyBrIGluY3JlYXNlcywgYnkgYW4gYW1vdW50IHRoYXQgZGVjcmVhc2VzIHdpdGggay4gQ2hvb3NlIGsgYXQgdGhlIGluZmxleGlvbiBwb2ludCBvZiB0aGUgY3VydmUuIAoKYGBge3IgY2hvb3NlX2ttZWFucywgd2FybmluZz1GQUxTRX0KbGlicmFyeShicm9vbSkKcmVxdWlyZSh0aWJibGUpCnJlcXVpcmUoZHBseXIpCnJlcXVpcmUodGlkeXIpCmxpYnJhcnkocHVycnIpCnBvaW50cyA8LSBhc190aWJibGUocGNzKQoKa2NsdXN0cyA8LSB0aWJibGUoayA9IDE6MjApICU+JQogIG11dGF0ZSgKICAgIGtjbHVzdCA9IG1hcChrLCB+a21lYW5zKHBvaW50cywgLngpKSwKICAgIHRpZGllZCA9IG1hcChrY2x1c3QsIHRpZHkpLAogICAgZ2xhbmNlZCA9IG1hcChrY2x1c3QsIGdsYW5jZSksCiAgICBhdWdtZW50ZWQgPSBtYXAoa2NsdXN0LCBhdWdtZW50LCBwb2ludHMpCiAgKQoKY2x1c3RlcnMgPC0ga2NsdXN0cyAlPiUKICB1bm5lc3QodGlkaWVkKQoKYXNzaWdubWVudHMgPC0ga2NsdXN0cyAlPiUgCiAgdW5uZXN0KGF1Z21lbnRlZCkKCmNsdXN0ZXJpbmdzIDwtIGtjbHVzdHMgJT4lCiAgdW5uZXN0KGdsYW5jZWQpCmBgYAoKUGxvdCB0aGUgdG90YWwgd2l0aGluIGNsdXN0ZXIgc3VtLW9mLXNxdWFyZXMgYW5kIGRlY2lkZSBvbiBrLgoKYGBge3IgcGxvdF93aXRoaW5zc30KZ2dwbG90KGNsdXN0ZXJpbmdzLCBhZXMoaywgdG90LndpdGhpbnNzKSkgKwogIGdlb21fbGluZSgpCmBgYAoKQ29weSB0aGUgY2x1c3RlciBhc3NpZ25tZW50IHRvIHRoZSBTQ0Ugb2JqZWN0LgoKYGBge3IgY29weV9rMTB9CmRmIDwtIGFzLmRhdGEuZnJhbWUoYXNzaWdubWVudHMpCnNjZSRrbWVhbnMxMCA8LSBhcy5udW1lcmljKGRmW2RmJGsgPT0gMTAsICIuY2x1c3RlciJdKQpgYGAKCkNoZWNrIHNpbGhvdWV0dGUgZm9yIGEgayBvZiAxMC4KCmBgYHtyIHNpbGhvdWV0dGVfa21lYW5zX2sxMH0KbGlicmFyeShjbHVzdGVyKQpjbHVzdC5jb2wgPC0gc2NhdGVyOjo6LmdldF9wYWxldHRlKCJ0YWJsZWF1MTBtZWRpdW0iKSAjIGhpZGRlbiBzY2F0ZXIgY29sb3VycwpzaWwgPC0gc2lsaG91ZXR0ZShzY2Uka21lYW5zMTAsIGRpc3QgPSBteS5kaXN0KQpzaWwuY29scyA8LSBjbHVzdC5jb2xbaWZlbHNlKHNpbFssM10gPiAwLCBzaWxbLDFdLCBzaWxbLDJdKV0Kc2lsLmNvbHMgPC0gc2lsLmNvbHNbb3JkZXIoLXNpbFssMV0sIHNpbFssM10pXQpwbG90KHNpbCwgbWFpbiA9IHBhc3RlKGxlbmd0aCh1bmlxdWUoc2NlJGttZWFuczEwKSksICJjbHVzdGVycyIpLCAKICAgIGJvcmRlcj1zaWwuY29scywgY29sPXNpbC5jb2xzLCBkby5jb2wuc29ydD1GQUxTRSkgCmBgYAoKIyMjIyBHcmFwaC1iYXNlZCBjbHVzdGVyaW5nCgpHcmFwaC1iYXNlZCBjbHVzdGVyaW5nIGVudGFpbHMgYnVpbGRpbmcgYSBzaGFyZWQgbmVhcmVzdC1uZWlnaGJvdXIgZ3JhcGggdXNpbmcgY2VsbHMgYXMgbm9kZXMgYW5kIHRoZWlyIHNpbWlsYXJpdHkgYXMgZWRnZXMsIHRoZW4gaWRlbnRpZnlpbmcgJ2NvbW11bml0aWVzJyBvZiBjZWxscyB3aXRoaW4gdGhlIG5ldHdvcmsuCgo8aW1nIHNyYz0iLi4vLi4vSW1hZ2VzL2Jpb0NlbGxHZW5HcmFwaERlbmcucG5nIiBzdHlsZT0ibWFyZ2luOmF1dG87IGRpc3BsYXk6YmxvY2siIC8+CgpXZSB3aWxsOiBidWlsZCB0aGUgZ3JhcGgsIGRlZmluZSBjbHVzdGVycywgY2hlY2sgbWVtYmVyc2hpcCBhY3Jvc3Mgc2FtcGxlcywgc2hvdyBtZW1iZXJzaGlwIG9uIHQtU05FIGFuZCBhc3Nlc3MgaXRzIHF1YWxpdHkuCgpgYGB7ciBjb21wX3Nubn0KI2NvbXB1dGUgZ3JhcGgKc25uLmdyIDwtIGJ1aWxkU05OR3JhcGgoc2NlLCB1c2UuZGltcmVkPSJQQ0EiKQojIGRlcml2ZSBjbHVzdGVycwpjbHVzdGVyLm91dCA8LSBpZ3JhcGg6OmNsdXN0ZXJfd2Fsa3RyYXAoc25uLmdyKQojIGNvdW50IGNlbGwgaW4gZWFjaCBjbHVzdGVyIGZvciBlYWNoIHNhbXBsZQpteS5jbHVzdGVycyA8LSBjbHVzdGVyLm91dCRtZW1iZXJzaGlwCnRhYmxlKG15LmNsdXN0ZXJzLCBzY2Ukc291cmNlX25hbWUpCnRhYmxlKG15LmNsdXN0ZXJzLCBzY2UkU2FtcGxlLk5hbWUpCiMgc3RvcmUgbWVtYmVyc2hpcApzY2UkY2x1c3RlciA8LSBmYWN0b3IobXkuY2x1c3RlcnMpCiMgc2hvZSBjbHVzdGVycyBvbiBUU05FCnAgPC0gcGxvdFRTTkUoc2NlLCBjb2xvdXJfYnk9ImNsdXN0ZXIiKSArIGZvbnRzaXplCnAKcCArIGZhY2V0X3dyYXAofiBzY2Ukc291cmNlX25hbWUpCmBgYAoKQ29tcHV0ZSBtb2R1bGFyaXR5IHRvIGFzc2VzcyBjbHVzdGVycyBxdWFsaXR5LiBUaGUgY2xvc2VyIHRvIDEgdGhlIGJldHRlci4KCmBgYHtyIG1vZHVsYXJpdHlfc25ufQppZ3JhcGg6Om1vZHVsYXJpdHkoY2x1c3Rlci5vdXQpCmBgYAoKYGBge3IgY2x1c3Rlck1vZHVsYXJpdHlfc25uLCBpbmNsdWRlID0gVFJVRX0KbW9kLm91dCA8LSBjbHVzdGVyTW9kdWxhcml0eShzbm4uZ3IsIG15LmNsdXN0ZXJzLCBnZXQud2VpZ2h0cz1UUlVFKQpyYXRpbyA8LSBtb2Qub3V0JG9ic2VydmVkL21vZC5vdXQkZXhwZWN0ZWQKbHJhdGlvIDwtIGxvZzEwKHJhdGlvICsgMSkKCmxpYnJhcnkocGhlYXRtYXApCnBoZWF0bWFwKGxyYXRpbywgY2x1c3Rlcl9yb3dzPUZBTFNFLCBjbHVzdGVyX2NvbHM9RkFMU0UsIAogICAgY29sb3I9Y29sb3JSYW1wUGFsZXR0ZShjKCJ3aGl0ZSIsICJibHVlIikpKDEwMCkpCmBgYAoKU2hvdyBzaW1pbGFyaXR5IGJldHdlZW4gY2x1c3RlcnMgb24gYSBuZXR3b3JrLiAKCmBgYHtyIHBsb3RfY2x1c3Rlck5ldHdvcmtfc25ufQpjbHVzdGVyLmdyIDwtIGlncmFwaDo6Z3JhcGhfZnJvbV9hZGphY2VuY3lfbWF0cml4KHJhdGlvLCAKICAgIG1vZGU9InVuZGlyZWN0ZWQiLCB3ZWlnaHRlZD1UUlVFLCBkaWFnPUZBTFNFKQpwbG90KGNsdXN0ZXIuZ3IsIGVkZ2Uud2lkdGg9aWdyYXBoOjpFKGNsdXN0ZXIuZ3IpJHdlaWdodCoxMCkgIApgYGAKCldyaXRlIFNDRSBvYmplY3QgdG8gZmlsZS4KCmBgYHtyfQp0bXBGbiA8LSBzcHJpbnRmKCIlcy8lcy9Sb2JqZWN0cy8lc19zY2VfbnpfcG9zdERlY29udiVzX2NsdXN0ZXJlZC5SZHMiLCBwcm9qRGlyLCBvdXREaXJCaXQsIHNldE5hbWUsIHNldFN1ZikKcHJpbnQodG1wRm4pCnNhdmVSRFMoc2NlLCBmaWxlPXRtcEZuKQpgYGAKCioqQ2hhbGxlbmdlKiogQXBwbHkgZ3JhcGggYmFzZWQgY2x1c3RlcmluZyBvbiBhIHNpbmdsZSBzYW1wbGUgKGdyb3VwPykuCgpgYGB7ciwgZXZhbD1GQUxTRX0KIyBlZyBTUlI5MjY0MzU0LCBHU00zODcyNDQ0LCBhIFBCTU1DCgpTcGxUb0dldCA8LSAiR1NNMzg3MjQ0NCIKCiMgZXh0cmFjdCBjZWxscyBmb3IgdGhpcyBzYW1wbGU6CmNlbGxzVG9HZXQgPC0gY29sRGF0YShzY2UpICU+JQoJZGF0YS5mcmFtZSgpICU+JQoJZmlsdGVyKFNhbXBsZS5OYW1lID09IFNwbFRvR2V0KSAlPiUKCXB1bGwoQmFyY29kZSkKc2NlX2MgPC0gc2NlWywgY2VsbHNUb0dldF0KIyBub3JtYWxpc2UKIyBQQ0EKIyBjbHVzdGVyCgpgYGAK