Objectives

This notebook will demonstrate how to:

  • Read Cell Ranger data into R
  • Filter post-quantification cells using emptyDropsCellRanger()
  • Apply dimensionality reduction methods to single cell data
  • Visualize samples in reduced dimensional space

In this notebook, we’ll try out some dimension reduction techniques on single-cell RNA-seq data.

Visualizing highly dimensional data is a common challenge in genomics, and especially with RNA-seq data. The expression of every gene we look at is another dimension describing a sample. When we also have hundreds or thousands of individual samples, as in the case of single-cell analysis, figuring out how to clearly display all of the data in a meaningful way is difficult.

A common practice is to common to use dimension reduction techniques so all of the data is in a more manageable form for plotting, clustering, and other downstream analyses.

Set Up

# Load libraries
library(ggplot2)
library(scater)
Loading required package: SingleCellExperiment
Loading required package: SummarizedExperiment
Loading required package: MatrixGenerics
Loading required package: matrixStats

Attaching package: 'MatrixGenerics'
The following objects are masked from 'package:matrixStats':

    colAlls, colAnyNAs, colAnys, colAvgsPerRowSet, colCollapse,
    colCounts, colCummaxs, colCummins, colCumprods, colCumsums,
    colDiffs, colIQRDiffs, colIQRs, colLogSumExps, colMadDiffs,
    colMads, colMaxs, colMeans2, colMedians, colMins, colOrderStats,
    colProds, colQuantiles, colRanges, colRanks, colSdDiffs, colSds,
    colSums2, colTabulates, colVarDiffs, colVars, colWeightedMads,
    colWeightedMeans, colWeightedMedians, colWeightedSds,
    colWeightedVars, rowAlls, rowAnyNAs, rowAnys, rowAvgsPerColSet,
    rowCollapse, rowCounts, rowCummaxs, rowCummins, rowCumprods,
    rowCumsums, rowDiffs, rowIQRDiffs, rowIQRs, rowLogSumExps,
    rowMadDiffs, rowMads, rowMaxs, rowMeans2, rowMedians, rowMins,
    rowOrderStats, rowProds, rowQuantiles, rowRanges, rowRanks,
    rowSdDiffs, rowSds, rowSums2, rowTabulates, rowVarDiffs, rowVars,
    rowWeightedMads, rowWeightedMeans, rowWeightedMedians,
    rowWeightedSds, rowWeightedVars
Loading required package: GenomicRanges
Loading required package: stats4
Loading required package: BiocGenerics

Attaching package: 'BiocGenerics'
The following objects are masked from 'package:stats':

    IQR, mad, sd, var, xtabs
The following objects are masked from 'package:base':

    anyDuplicated, aperm, append, as.data.frame, basename, cbind,
    colnames, dirname, do.call, duplicated, eval, evalq, Filter, Find,
    get, grep, grepl, intersect, is.unsorted, lapply, Map, mapply,
    match, mget, order, paste, pmax, pmax.int, pmin, pmin.int,
    Position, rank, rbind, Reduce, rownames, sapply, setdiff, table,
    tapply, union, unique, unsplit, which.max, which.min
Loading required package: S4Vectors

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

    findMatches
The following objects are masked from 'package:base':

    expand.grid, I, unname
Loading required package: IRanges
Loading required package: GenomeInfoDb
Loading required package: Biobase
Welcome to Bioconductor

    Vignettes contain introductory material; view with
    'browseVignettes()'. To cite Bioconductor, see
    'citation("Biobase")', and for packages 'citation("pkgname")'.

Attaching package: 'Biobase'
The following object is masked from 'package:MatrixGenerics':

    rowMedians
The following objects are masked from 'package:matrixStats':

    anyMissing, rowMedians
Warning: replacing previous import 'S4Arrays::makeNindexFromArrayViewport' by
'DelayedArray::makeNindexFromArrayViewport' when loading 'SummarizedExperiment'
Loading required package: scuttle
library(scran)

# Setting the seed for reproducibility
set.seed(12345)

Directories and files

The data we will be using for this module comes from a a 10x Genomics data set of expression data from a Hodgkin’s Lymphoma tumor. The data was generated with the 10Xv3.1 chemistry, and processed with Cell Ranger and 10x Genomics standard pipeline.

There are a variety of files that you will often see as part of the standard output from Cell Ranger, which are described in detail in 10x Genomics documentation. We have included some of these in the data/hodkins/cellranger directory, including the web_summary.html file that includes some similar QC statistics to those we generated with alevinQC. The main file we will be working with are the feature by barcode matrices. Cell Ranger does some filtering on its own, but we will start with the raw data.

# main data directory
data_dir <- file.path("data", "hodgkins")
# reference files
ref_dir <- file.path("data", "reference")

# Path to the Cell Ranger matrix
raw_matrix_dir <- file.path(data_dir, "cellranger",
                            "raw_feature_bc_matrix")

# Path to mitochondrial genes table
mito_file <- file.path(ref_dir, "hs_mitochondrial_genes.tsv")

# Directory and file to save output
normalized_dir <- file.path(data_dir, "normalized")
fs::dir_create(normalized_dir)

output_sce_file <- file.path(normalized_dir, "normalized_hodgkins_sce.rds")

Reading Cell Ranger data

Cell Ranger output includes count data in two main formats. The first is a folder with a feature list, a barcode list, and a sparse matrix in “Matrix Exchange” format. The DropletUtils::read10xCounts() function takes this directory and reads in the data from these three files, assembling the SingleCellExperiment object we have worked with before.

Alternatively, we could use the HDF5 format file that Cell Ranger outputs as a file with the .h5 extension, which contains the same data. For whatever reason, the way you read the data affects how it is stored in R. Reading from the directory results in smaller objects in R, so that is what we will do here.

Cell Ranger also outputs both filtered and raw matrices; today we will start with the raw matrix and perform our own filtering.

Roadmap: Preprocessing and Import
Roadmap: Preprocessing and Import
hodgkins_sce <- DropletUtils::read10xCounts(raw_matrix_dir)
Warning: replacing previous import 'S4Arrays::makeNindexFromArrayViewport' by
'DelayedArray::makeNindexFromArrayViewport' when loading 'HDF5Array'

How many potential cells are there here?

dim(hodgkins_sce)
[1]   36601 6794880

That is a lot of cells! In fact, it is really every possible barcode, whether there were reads associated with it or not. We should probably do something about that.

QC and normalization

Roadmap: QC and filtering
Roadmap: QC and filtering

Basic QC stats

We will start by calculating the basic QC stats as we have done previously, adding those to our SingleCellExperiment object.

The first step again is reading in our table of mitochondrial genes and finding the ones that were quantified our data set.

mito_genes <- readr::read_tsv(mito_file) |>
  dplyr::filter(gene_id %in% rownames(hodgkins_sce)) |>
  dplyr::pull(gene_id)
Rows: 37 Columns: 13
── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (9): gene_id, gene_name, seqnames, strand, gene_biotype, seq_coord_syste...
dbl (4): start, end, width, entrezid

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Next we will calculate the QC stats that we used before. Note that this is much slower than before, as we have many more genes in the unfiltered set!

hodgkins_sce <- scater::addPerCellQC(
  hodgkins_sce,
  subsets = list(mito = mito_genes))

We can now do the most basic level of filtering: getting rid of “cells” with no reads.

hodgkins_sce <- hodgkins_sce[, hodgkins_sce$total > 0]
dim(hodgkins_sce)
[1]  36601 549931

Filtering with emptyDropsCellRanger()

The DropletUtils package that we used to read in the 10x data has a number of other useful features. One is the emptyDropsCellRanger() function, which uses the overall gene expression patterns in the sample to identify droplets that are likely to not contain an intact cell, but may simply have contained loose ambient RNA released during cell separation. This method was originally developed by Lun et al. (2019) and implemented as the function emptyDrops(), but has since been adapted as the main filtering method used by Cell Ranger. The emptyDropsCellRanger() function emulates the variant of this function that used by Cell Ranger, making the results more comparable between the two methods.

The Empty Drops method uses the droplets with very low UMI counts to estimate the “ambient” expression pattern of RNA content outside of cells. It then scores the remaining cells based how much they deviate from that pattern, assigning a small P value when the droplet’s expression deviates from the ambient expression pattern. Because it uses the low UMI count droplets, this method should not be used when other filtering has already been performed (which is unfortunately the case with the version of salmon alevin we used).

This method seems to perform well to exclude false “cells” while retaining cells with distinct expression profiles but low counts that might have failed a simple cutoff. Note that this method also requires that the data has already been quantified, with reads assigned to genes, as compared to a simple total UMI count filter which can be performed much earlier in the pipeline.

The emptyDropsCellRanger() function takes the counts matrix from our SingleCellExperiment, and returns a data frame with the statistics it calculates. This will take a few minutes to run, but we can speed it up by allowing parallel processing.

droplet_stats <- DropletUtils::emptyDropsCellRanger(
  counts(hodgkins_sce),
  BPPARAM = BiocParallel::MulticoreParam(4)) # use multiprocessing

We will use a false discovery rate (FDR) of 0.01 as our cutoff for “real” cells. Since emptyDropsCellRanger() uses low count cells to estimate the “ambient” expression pattern, those cells are not assigned an FDR value, and have a value of NA. These NAs can be a problem for filtering with a Boolean vector, as we did above, so instead we will use the which() function to get the positions of the cells that pass our filter and select the columns we want using that.

cells_to_retain <- which(droplet_stats$FDR <= 0.01)

filtered_sce <- hodgkins_sce[, cells_to_retain]
dim(filtered_sce)
[1] 36601  3401

How does this compare to the number of cells in the Cell Ranger filtered data? Looking the web_summary.html report from Cell Ranger, it seems that it would have kept 3,394 cells, so we seem to be getting broadly similar results.

Checking mitochondrial content

While emptyDropsCellRanger() should have filtered out droplets containing no cells, it will not necessarily filter out damaged cells. For that we will still want to look at mitochondrial content, as we did previously. The statistics we calculated earlier with addPerCellQC() are retained in our new object, so we can plot those directly.

# Plot the mitochondrial percents stored in `filtered_sce`
ggplot(mapping = aes(x = filtered_sce$subsets_mito_percent)) +
  geom_histogram(bins = 100)

There are certainly some cells with high mitochondrial percentages! For now, we will use a cutoff of 20% to filter out the worst of the cells.

filtered_sce <- filtered_sce[, filtered_sce$subsets_mito_percent < 20]

We can also filter by features (genes in our case) using scater::addPerFeatureQC() which will compute the number of samples where each gene is detected and the mean count across all genes. We can then use those data (stored in rowData) to filter by row to only the genes that are detected in at least 5% of cells, and with a mean count > 0.1.

filtered_sce <- scater::addPerFeatureQC(filtered_sce)
detected <- rowData(filtered_sce)$detected > 5
expressed <- rowData(filtered_sce)$mean > 0.1

# filter the genes (rows) this time
filtered_sce <- filtered_sce[detected & expressed, ]

How many cells do we have now?

dim(filtered_sce)
[1] 7445 2747

Normalize

Now we will perform the same normalization steps we did in a previous dataset, using scran::computeSumFactors() and scater::logNormCounts(). You might recall that there is a bit of randomness in some of these calculations, so we should be sure to have used set.seed() earlier in the notebook for reproducibility.

# Cluster similar cells
qclust <- scran::quickCluster(filtered_sce)

# Compute sum factors for each cell cluster grouping.
filtered_sce <- scran::computeSumFactors(filtered_sce, clusters = qclust, positive = FALSE)
Warning in (function (x, sizes, min.mean = NULL, positive = FALSE, scaling =
NULL) : encountered non-positive size factor estimates

It turns out in this case we end up with some negative size factors. This is usually an indication that our filtering was not stringent enough, and there remain a number of cells or genes with nearly zero counts. This probably happened when we removed the infrequently-expressed genes; cells which had high counts from those particular genes (and few others) could have had their total counts dramatically reduced.

To account for this, we will recalculate the per-cell stats and filter out low counts. Unfortunately, to do this, we need to first remove the previously calculated statistics, which we will do by setting them to NULL.

# remove previous calculations
filtered_sce$sum <- NULL
filtered_sce$detected <- NULL
filtered_sce$total <- NULL
filtered_sce$subsets_mito_sum <- NULL
filtered_sce$subsets_mito_detected <- NULL
filtered_sce$subsets_mito_sum <- NULL

# recalculate cell stats
filtered_sce <- scater::addPerCellQC(filtered_sce, subsets = list(mito = mito_genes))

# print the number of cells with fewer than 500 UMIs
sum(filtered_sce$sum < 500)
[1] 13

Now we can filter again. In this case, we will keep cells with at least 500 UMIs after removing the lowly expressed genes. Then we will redo the size factor calculation, hopefully with no more warnings.

filtered_sce <- filtered_sce[, filtered_sce$sum >= 500]

qclust <- scran::quickCluster(filtered_sce)

filtered_sce <- scran::computeSumFactors(filtered_sce, clusters = qclust)

Looks good! Now we’ll do the normalization.

# Normalize and log transform.
normalized_sce <- scater::logNormCounts(filtered_sce)

At this point, we have a few different versions of our SingleCellExperiment object. The original (mostly) unfiltered version is in hodgkins_sce, the filtered version in filtered_sce, and the normalized version in normalized_sce. We can clean those up a bit to save memory, keeping only the latest normalized_sce version, which now has two assays: counts with the raw data and logcounts with the normalized and transformed data.

assayNames(normalized_sce)
[1] "counts"    "logcounts"
counts
logcounts
rm(hodgkins_sce, filtered_sce)

Dimensionality reduction and display

Roadmap: Dimensionality reduction
Roadmap: Dimensionality reduction

Principal Components Analysis

Principal component analysis (PCA) is a dimensionality reduction technique that allows us to identify the largest components of variation in a complex dataset. Our expression data can be thought of as mapping each sample in a multidimensional space defined by the expression level of each gene. The expression of many of those genes are correlated, so we can often get a better, simpler picture of the data by combining the information from those correlated genes.

PCA rotates and transforms this space so that each axis is now a combination of multiple correlated genes, ordered so the first axes capture the most variation from the data. These new axes are the “principal components.” If we look at the first few components, we can often get a nice overview of relationships among the samples in the data.

Storing PCA results with the raw data

We will store the PCA results in our SingleCellExperiment object, as we will want to use them later. To do this, we will use the runPCA() function from scater, which performs the PCA calculations and returns a new object with the results stored in the reducedDim slot. If we wanted to, we could get the raw results as a matrix instead with calculatePCA() function, as we did in a previous notebook.

We will also use the ntop argument to calculate the PCA using 2000 genes with the highest variance. The default is ntop = 500.

# calculate PCA using the top 2000 genes
normalized_sce <- runPCA(normalized_sce, ntop = 2000)

We can see what reduced dimensionality matrices are stored in the object with the reducedDimNames() function.

# print the reduced dimensionality tables available
reducedDimNames(normalized_sce)
[1] "PCA"
PCA

To extract them by name, we use the reducedDim() function, much like the assay() function to extract original data.

# print the top corner of the PCA matrix
reducedDim(normalized_sce, "PCA")[1:10, 1:5]
            PC1        PC2        PC3         PC4        PC5
 [1,] 15.379889   7.929537  11.790972   3.9845847   8.232603
 [2,] -1.220424   6.053443   2.100161 -10.7532071  -6.769888
 [3,]  0.647626  -7.566098 -12.004547  -0.9862535   8.936328
 [4,]  7.922958 -21.975167   4.156634 -10.0798546   5.383158
 [5,]  1.577685 -26.510992  11.089218  10.5259441 -11.660812
 [6,]  5.676435 -27.571708  13.655945  -9.3006045   2.594320
 [7,] -3.623704  -2.052362  -9.061048  -5.7941810   9.112434
 [8,]  9.577588  12.977445  -4.125885  -0.1949252 -10.757970
 [9,]  9.393483  -7.288896 -12.661552   6.4502827  -2.371315
[10,] -7.917621  -8.052548  -9.061915   0.8675157   9.034390

Plotting PCA results

If we have the PCA results stored in the SingleCellExperiment object, we can use the scater::plotReducedDim() function to plot it with some nice defaults easily. One nice thing about this function is that it uses ggplot2 under the hood, so if we wanted to customize it later, we could.

# plot PCA results
plotReducedDim(normalized_sce, "PCA")

PCA gives us a matrix with more than just two dimensions, and we might want to look at some higher dimensions too. We can do that with the ncomponents argument.

# plot PC3 and PC4
plotReducedDim(normalized_sce, "PCA", ncomponents = c(3,4))

Modeling variance

The variation in gene expression we see among cells comes from a combination of variation due to technical effects and the biology we really care about. In order to roughly account for this we could just take the largest variance genes, on the assumption that low variance genes are mostly just noise. This is the default approach that runPCA() and calculatePCA() take, using the genes with the greatest variance across cells to calculate the PCA matrix.

If we want to be a bit more careful about it, we can model the variance in expression of each gene as a function of the mean expression for that gene. This is useful because we generally expect the variance to increase as mean expression increases, even if there is no biological signal in the expression variation.

We will do this modeling of variance by expression with the scran::modelGeneVar() function, saving the results to a new variable.

gene_variance <- scran::modelGeneVar(normalized_sce)

Now let’s plot the relationship between gene expression and variance we were discussing. Here we will also add the fitting curve that scran::modelGeneVar() created, which is stored as function in the $trend slot of the gene_variance object. We can add a function like that curve to a ggplot with a stat_function layer.

ggplot(as.data.frame(gene_variance), aes(x = mean, y = total)) +
  geom_point(alpha = 0.1) +
  stat_function(fun = metadata(gene_variance)$trend, color = "blue") +
  labs(
    x = "Mean log-expression",
    y = "Variance") +
  theme_bw()

Now we can use scran::getTopHVGs() to select the genes that have the most biological variation (according to the model) and recalculate PCA scores using only those genes. (In practice, we are selecting the genes with the largest residual variation after removing technical variation modeled by the mean/variance relationship.)

Here we are picking the 2000 top genes to match the number of genes from our earlier calculations.

# select the most variable genes
highvar_genes <- scran::getTopHVGs(gene_variance, n = 2000)
# calculate a PCA matrix using those genes
normalized_sce <- runPCA(normalized_sce, subset_row = highvar_genes)

Now we can plot our new PCA values for comparison. You might realize that our old PCA values were replaced when we ran runPCA() again, so we can’t recreate the earlier plots at this stage of the notebook. You will have to scroll up to your earlier plots to compare.

# plot the new PCA results
plotReducedDim(normalized_sce, "PCA")

plotReducedDim(normalized_sce, "PCA", ncomponents = c(3,4))

UMAP

UMAP (Uniform Manifold Approximation and Projection) is a machine learning technique designed to provide more detail in highly dimensional data than a typical principal components analysis. While PCA assumes that the variation we care about has a particular distribution (normal, broadly speaking), UMAP allows more complicated distributions that it learns from the data. The underlying mathematics are beyond me, but if you are more ambitious than I, you can look at the paper by McInnes, Healy, & Melville (2018). The main advantage of this change in underlying assumptions is that UMAP can do a better job separating clusters, especially when some of those clusters may be more similar to each other than others.

Another dimensionality reduction technique that you may have heard of is t-SNE (t-distributed Stochastic Neighbor Embedding), which has similar properties to UMAP, and often produces similar results. There is some ongoing debate about which of these two techniques is superior, and whether the differences are due to the underlying algorithm or to implementation and parameter initialization defaults. Regardless of why, in our experience, UMAP seems to produce slightly better results and run a bit faster, but the differences can be subtle.

Default parameters

For ease of use with this data, we will be using the scater::calculateUMAP() and scater::runUMAP() function to apply UMAP to our single cell data, but similar functions the uwot package (notably uwot::umap()) can be used to apply UMAP to any numerical matrix.

UMAP can be slow for a large data set with lots of parameters. It is worth noting that the scater::calculateUMAP() implementation actually does PCA first, and then runs UMAP on the top 50 PCs. If we have already calculated PCA (as we have) we can tell it to use those results with the dimred argument.

As with PCA, there are two functions we could use: scater::calculateUMAP() will return a matrix of results, with one row for each sample, and a column for each of the UMAP dimensions returned. scater::runUMAP() performs the same function, but returns the results in a SingleCellExperiment object.

Let’s see how it looks with the (mostly) default parameters:

# Run UMAP
normalized_sce <- runUMAP(normalized_sce,
                          dimred = "PCA") # use already stored PCA results

Now we can plot with the same plotReducedDim() function, specifying we want to plot the UMAP results this time. We will also add some color this time with the color_by argument, using the number of genes detected in each cell to assign a hue.

# make a UMAP plot with `plotReducedDim()`
plotReducedDim(normalized_sce, "UMAP", color_by = "detected")

There is clearly a lot of structure in there, but is it meaningful? Do the clusters we see differentiate cell types? How should we divide them up?

We will come back to this question later!

UMAP experiments

Now that we have an idea of what a UMAP plot with the default parameters looks like, let’s try experimenting with the n_neighbors parameter. First, we should see what this parameter is, and what the default value is. In the console, run ?scater::calculateUMAP to see what this (and other parameters) are. For even more parameters, you can look at the underlying implementation code that calculateUMAP() uses, which is the function uwot::umap()

In order to make our experimentation easier, we will create a function that allows us to rerun the same code easily, but create an argument that allows us to change one variable: the n_neighbors variable. Here we are saving only a line of code, but we could apply this to a much more complex series of operations if we wanted to!

UMAP_plot_wrapper <- function(sce = normalized_sce, nn_param = 15) {
  # Purpose: Run UMAP and plot the output
  # Args: nn_param: a single numeric argument that will change the
  #                 n_neighbors variable in the calculateUMAP() function.
  # Output: a scatterplot with the two UMAP coordinates plotted and
  #         cell-types labeled with data point colors.

  # Run UMAP with a specified n_neighbors parameter
  sce_umap <- scater::runUMAP(sce, dimred = "PCA", n_neighbors = nn_param)
  scater::plotReducedDim(sce_umap, "UMAP", color_by = "detected") +
    # make the legend label more informative (this is ggplot2 code!)
    guides(color = guide_colorbar(title="genes\nexpressed"))
}

Let’s make sure that works and gives the same result as before when we use the default parameters.

UMAP_plot_wrapper(nn_param = 15)

Kind of?

This isn’t your fault! UMAP is a non-deterministic function, which means that there is a random component to the results. We can use set.seed() to be sure that an individual run (or set of runs) is the same every time you run your analysis, but it is important to check your results a few times with different random starting points to be sure that the random component is not giving you anomalous results. Setting a different random number seed with set.seed() is one way to do this, or you can run the analysis multiple times in the same session, as we have done here.

Fill in the next few code chunks with the function and the n_neighbors argument you would like to use for each. (Feel free to add more tests!) Then run the chunks and compare your output graphs.

# Try something low?
UMAP_plot_wrapper(nn_param = 3)

# Try something high?
UMAP_plot_wrapper(nn_param = 100)

# Try whatever you like!
UMAP_plot_wrapper(nn_param = 5)

Some ‘big picture’ thoughts to take from this experiment:

  1. Analyses such as UMAP have various limitations for interpretability. The coordinates of UMAP output for any given cell can change dramatically depending on parameters, and even run to run with the same parameters. This probably means that you shouldn’t rely on the exact values of UMAP’s output.

    • One particular limitation of UMAP (and t-SNE) is that while observed clusters have some meaning, the distance between clusters usually does not (nor does cluster density). The fact that two clusters are near each other should NOT be interpreted to mean that they are more related to each other than to more distant clusters. (There is some disagreement about whether UMAP distances have more meaning, but it is probably safer to assume they don’t.)
  2. Playing with parameters so you can fine-tune them is a good way to give you more information about a particular analysis as well as the data itself.

  3. Where results are consistent, they are more likely to have meaning. While we do not have labeled cell types in this case, there does seem to be some consistency of the overall patterns that we see (if not precise values), and this likely reflects biological information (or technical artifacts).

In summary, if the results of an analysis can be completely changed by changing its parameters, you should be more cautious when it comes to the conclusions you draw from it as well as having good rationale for the parameters you choose.

t-SNE comparison

In the block below is a similar analysis and plot with t-SNE (t-distributed Stochastic Neighbor Embedding). Note that this analysis also uses PCA before moving on to the fancy machine learning.

# Run TSNE
normalized_sce <- runTSNE(normalized_sce, dimred = "PCA")

# plot with scater function
plotReducedDim(normalized_sce, "TSNE", color_by = "detected")

Different! (Slower!) Is it better or worse? Hard to say! Different people like different things, and one plot might illustrate a particular point better than another.

Save results

We are going to use this data more in the next notebook, so let’s save it as an RDS file.

readr::write_rds(normalized_sce, file = output_sce_file)

Some further reading on dimension reduction:

  • This website explains PCA visually.
  • Becht et al. (2018) discusses using UMAP for single-cell data.
  • Wattenberg et al. (2016) discuss how to use t-SNE properly with great visuals. (The lessons apply to UMAP as well, with a broad substitution of the n_neighbors parameter for perplexity.)
  • Nguyen & Holmes (2019) lay out guidelines on choosing dimensions reduction methods.
  • Freitag (2019) is a nice explanation and comparison of many different dimensionality reduction techniques that you may encounter.

Session Info

sessionInfo()
R version 4.4.0 (2024-04-24)
Platform: x86_64-pc-linux-gnu
Running under: Ubuntu 22.04.4 LTS

Matrix products: default
BLAS:   /usr/lib/x86_64-linux-gnu/openblas-pthread/libblas.so.3 
LAPACK: /usr/lib/x86_64-linux-gnu/openblas-pthread/libopenblasp-r0.3.20.so;  LAPACK version 3.10.0

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

time zone: Etc/UTC
tzcode source: system (glibc)

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

other attached packages:
 [1] scran_1.32.0                scater_1.32.0              
 [3] scuttle_1.14.0              SingleCellExperiment_1.26.0
 [5] SummarizedExperiment_1.34.0 Biobase_2.64.0             
 [7] GenomicRanges_1.56.0        GenomeInfoDb_1.40.0        
 [9] IRanges_2.38.0              S4Vectors_0.42.0           
[11] BiocGenerics_0.50.0         MatrixGenerics_1.16.0      
[13] matrixStats_1.3.0           ggplot2_3.5.1              
[15] optparse_1.7.5             

loaded via a namespace (and not attached):
  [1] gridExtra_2.3             rlang_1.1.3              
  [3] magrittr_2.0.3            compiler_4.4.0           
  [5] DelayedMatrixStats_1.26.0 vctrs_0.6.5              
  [7] stringr_1.5.1             pkgconfig_2.0.3          
  [9] crayon_1.5.2              fastmap_1.1.1            
 [11] XVector_0.44.0            labeling_0.4.3           
 [13] utf8_1.2.4                rmarkdown_2.26           
 [15] tzdb_0.4.0                UCSC.utils_1.0.0         
 [17] ggbeeswarm_0.7.2          bit_4.0.5                
 [19] xfun_0.43                 bluster_1.14.0           
 [21] zlibbioc_1.50.0           cachem_1.0.8             
 [23] beachmat_2.20.0           jsonlite_1.8.8           
 [25] highr_0.10                rhdf5filters_1.16.0      
 [27] DelayedArray_0.30.0       Rhdf5lib_1.26.0          
 [29] BiocParallel_1.38.0       irlba_2.3.5.1            
 [31] parallel_4.4.0            cluster_2.1.6            
 [33] R6_2.5.1                  bslib_0.7.0              
 [35] stringi_1.8.3             limma_3.60.0             
 [37] jquerylib_0.1.4           Rcpp_1.0.12              
 [39] knitr_1.46                R.utils_2.12.3           
 [41] FNN_1.1.4                 readr_2.1.5              
 [43] Matrix_1.7-0              igraph_2.0.3             
 [45] tidyselect_1.2.1          abind_1.4-5              
 [47] yaml_2.3.8                viridis_0.6.5            
 [49] codetools_0.2-20          lattice_0.22-6           
 [51] tibble_3.2.1              withr_3.0.0              
 [53] Rtsne_0.17                evaluate_0.23            
 [55] getopt_1.20.4             pillar_1.9.0             
 [57] generics_0.1.3            vroom_1.6.5              
 [59] hms_1.1.3                 sparseMatrixStats_1.16.0 
 [61] munsell_0.5.1             scales_1.3.0             
 [63] glue_1.7.0                metapod_1.12.0           
 [65] tools_4.4.0               BiocNeighbors_1.22.0     
 [67] ScaledMatrix_1.12.0       locfit_1.5-9.9           
 [69] fs_1.6.4                  cowplot_1.1.3            
 [71] rhdf5_2.48.0              grid_4.4.0               
 [73] DropletUtils_1.24.0       edgeR_4.2.0              
 [75] colorspace_2.1-0          GenomeInfoDbData_1.2.12  
 [77] beeswarm_0.4.0            BiocSingular_1.20.0      
 [79] HDF5Array_1.32.0          vipor_0.4.7              
 [81] cli_3.6.2                 rsvd_1.0.5               
 [83] fansi_1.0.6               S4Arrays_1.4.0           
 [85] viridisLite_0.4.2         dplyr_1.1.4              
 [87] uwot_0.2.2                gtable_0.3.5             
 [89] R.methodsS3_1.8.2         sass_0.4.9               
 [91] digest_0.6.35             SparseArray_1.4.0        
 [93] ggrepel_0.9.5             dqrng_0.3.2              
 [95] farver_2.1.1              htmltools_0.5.8.1        
 [97] R.oo_1.26.0               lifecycle_1.0.4          
 [99] httr_1.4.7                statmod_1.5.0            
[101] bit64_4.0.5              
LS0tCnRpdGxlOiAiRGltZW5zaW9uIFJlZHVjdGlvbiB3aXRoIHNjUk5BLXNlcSBkYXRhIgphdXRob3I6IENDREwgZm9yIEFMU0YKZGF0ZTogMjAyMQpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKIyMgT2JqZWN0aXZlcwoKVGhpcyBub3RlYm9vayB3aWxsIGRlbW9uc3RyYXRlIGhvdyB0bzoKCi0gUmVhZCBDZWxsIFJhbmdlciBkYXRhIGludG8gUgotIEZpbHRlciBwb3N0LXF1YW50aWZpY2F0aW9uIGNlbGxzIHVzaW5nIGBlbXB0eURyb3BzQ2VsbFJhbmdlcigpYAotIEFwcGx5IGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBtZXRob2RzIHRvIHNpbmdsZSBjZWxsIGRhdGEKLSBWaXN1YWxpemUgc2FtcGxlcyBpbiByZWR1Y2VkIGRpbWVuc2lvbmFsIHNwYWNlCgotLS0KCkluIHRoaXMgbm90ZWJvb2ssIHdlJ2xsIHRyeSBvdXQgc29tZSBkaW1lbnNpb24gcmVkdWN0aW9uIHRlY2huaXF1ZXMgb24gc2luZ2xlLWNlbGwgUk5BLXNlcSBkYXRhLgoKVmlzdWFsaXppbmcgaGlnaGx5IGRpbWVuc2lvbmFsIGRhdGEgaXMgYSBjb21tb24gY2hhbGxlbmdlIGluIGdlbm9taWNzLCBhbmQgZXNwZWNpYWxseSB3aXRoIFJOQS1zZXEgZGF0YS4KVGhlIGV4cHJlc3Npb24gb2YgZXZlcnkgZ2VuZSB3ZSBsb29rIGF0IGlzIGFub3RoZXIgZGltZW5zaW9uIGRlc2NyaWJpbmcgYSBzYW1wbGUuCldoZW4gd2UgYWxzbyBoYXZlIGh1bmRyZWRzIG9yIHRob3VzYW5kcyBvZiBpbmRpdmlkdWFsIHNhbXBsZXMsIGFzIGluIHRoZSBjYXNlIG9mIHNpbmdsZS1jZWxsIGFuYWx5c2lzLCBmaWd1cmluZyBvdXQgaG93IHRvIGNsZWFybHkgZGlzcGxheSBhbGwgb2YgdGhlIGRhdGEgaW4gYSBtZWFuaW5nZnVsIHdheSBpcyBkaWZmaWN1bHQuCgpBIGNvbW1vbiBwcmFjdGljZSBpcyB0byBjb21tb24gdG8gdXNlIGRpbWVuc2lvbiByZWR1Y3Rpb24gdGVjaG5pcXVlcyBzbyBhbGwgb2YgdGhlIGRhdGEgaXMgaW4gYSBtb3JlIG1hbmFnZWFibGUgZm9ybSBmb3IgcGxvdHRpbmcsIGNsdXN0ZXJpbmcsIGFuZCBvdGhlciBkb3duc3RyZWFtIGFuYWx5c2VzLgoKIyMgU2V0IFVwCgpgYGB7ciBzZXR1cH0KIyBMb2FkIGxpYnJhcmllcwpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoc2NhdGVyKQpsaWJyYXJ5KHNjcmFuKQoKIyBTZXR0aW5nIHRoZSBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkKc2V0LnNlZWQoMTIzNDUpCmBgYAoKIyMjIERpcmVjdG9yaWVzIGFuZCBmaWxlcwoKVGhlIGRhdGEgd2Ugd2lsbCBiZSB1c2luZyBmb3IgdGhpcyBtb2R1bGUgY29tZXMgZnJvbSBhIGEgMTB4IEdlbm9taWNzIGRhdGEgc2V0IG9mIFtleHByZXNzaW9uIGRhdGEgZnJvbSBhIEhvZGdraW4ncyBMeW1waG9tYSB0dW1vcl0oaHR0cHM6Ly9zdXBwb3J0LjEweGdlbm9taWNzLmNvbS9zaW5nbGUtY2VsbC1nZW5lLWV4cHJlc3Npb24vZGF0YXNldHMvNC4wLjAvUGFyZW50X05HU0MzX0RJX0hvZGdraW5zTHltcGhvbWEpLgpUaGUgZGF0YSB3YXMgZ2VuZXJhdGVkIHdpdGggdGhlIDEwWHYzLjEgY2hlbWlzdHJ5LCBhbmQgcHJvY2Vzc2VkIHdpdGggQ2VsbCBSYW5nZXIgYW5kIDEweCBHZW5vbWljcyBzdGFuZGFyZCBwaXBlbGluZS4KCgpUaGVyZSBhcmUgYSB2YXJpZXR5IG9mIGZpbGVzIHRoYXQgeW91IHdpbGwgb2Z0ZW4gc2VlIGFzIHBhcnQgb2YgdGhlIHN0YW5kYXJkIG91dHB1dCBmcm9tIENlbGwgUmFuZ2VyLCB3aGljaCBhcmUgZGVzY3JpYmVkIGluIGRldGFpbCBpbiBbMTB4IEdlbm9taWNzIGRvY3VtZW50YXRpb25dKGh0dHBzOi8vc3VwcG9ydC4xMHhnZW5vbWljcy5jb20vc2luZ2xlLWNlbGwtZ2VuZS1leHByZXNzaW9uL3NvZnR3YXJlL3BpcGVsaW5lcy9sYXRlc3Qvb3V0cHV0L292ZXJ2aWV3KS4KV2UgaGF2ZSBpbmNsdWRlZCBzb21lIG9mIHRoZXNlIGluIHRoZSBgZGF0YS9ob2RraW5zL2NlbGxyYW5nZXJgIGRpcmVjdG9yeSwgaW5jbHVkaW5nIHRoZSBgd2ViX3N1bW1hcnkuaHRtbGAgZmlsZSB0aGF0IGluY2x1ZGVzIHNvbWUgc2ltaWxhciBRQyBzdGF0aXN0aWNzIHRvIHRob3NlIHdlIGdlbmVyYXRlZCB3aXRoIGBhbGV2aW5RQ2AuClRoZSBtYWluIGZpbGUgd2Ugd2lsbCBiZSB3b3JraW5nIHdpdGggYXJlIHRoZSBmZWF0dXJlIGJ5IGJhcmNvZGUgbWF0cmljZXMuCkNlbGwgUmFuZ2VyIGRvZXMgc29tZSBmaWx0ZXJpbmcgb24gaXRzIG93biwgYnV0IHdlIHdpbGwgc3RhcnQgd2l0aCB0aGUgcmF3IGRhdGEuCgpgYGB7ciBmaWxlcGF0aHN9CiMgbWFpbiBkYXRhIGRpcmVjdG9yeQpkYXRhX2RpciA8LSBmaWxlLnBhdGgoImRhdGEiLCAiaG9kZ2tpbnMiKQojIHJlZmVyZW5jZSBmaWxlcwpyZWZfZGlyIDwtIGZpbGUucGF0aCgiZGF0YSIsICJyZWZlcmVuY2UiKQoKIyBQYXRoIHRvIHRoZSBDZWxsIFJhbmdlciBtYXRyaXgKcmF3X21hdHJpeF9kaXIgPC0gZmlsZS5wYXRoKGRhdGFfZGlyLCAiY2VsbHJhbmdlciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAicmF3X2ZlYXR1cmVfYmNfbWF0cml4IikKCiMgUGF0aCB0byBtaXRvY2hvbmRyaWFsIGdlbmVzIHRhYmxlCm1pdG9fZmlsZSA8LSBmaWxlLnBhdGgocmVmX2RpciwgImhzX21pdG9jaG9uZHJpYWxfZ2VuZXMudHN2IikKCiMgRGlyZWN0b3J5IGFuZCBmaWxlIHRvIHNhdmUgb3V0cHV0Cm5vcm1hbGl6ZWRfZGlyIDwtIGZpbGUucGF0aChkYXRhX2RpciwgIm5vcm1hbGl6ZWQiKQpmczo6ZGlyX2NyZWF0ZShub3JtYWxpemVkX2RpcikKCm91dHB1dF9zY2VfZmlsZSA8LSBmaWxlLnBhdGgobm9ybWFsaXplZF9kaXIsICJub3JtYWxpemVkX2hvZGdraW5zX3NjZS5yZHMiKQoKYGBgCgojIyBSZWFkaW5nIENlbGwgUmFuZ2VyIGRhdGEKCkNlbGwgUmFuZ2VyIG91dHB1dCBpbmNsdWRlcyBjb3VudCBkYXRhIGluIHR3byBtYWluIGZvcm1hdHMuClRoZSBmaXJzdCBpcyBhIGZvbGRlciB3aXRoIGEgZmVhdHVyZSBsaXN0LCBhIGJhcmNvZGUgbGlzdCwgYW5kIGEgc3BhcnNlIG1hdHJpeCBpbiBbIk1hdHJpeCBFeGNoYW5nZSIgZm9ybWF0XShodHRwczovL21hdGgubmlzdC5nb3YvTWF0cml4TWFya2V0L2Zvcm1hdHMuaHRtbCkuClRoZSBgRHJvcGxldFV0aWxzOjpyZWFkMTB4Q291bnRzKClgIGZ1bmN0aW9uIHRha2VzIHRoaXMgZGlyZWN0b3J5IGFuZCByZWFkcyBpbiB0aGUgZGF0YSBmcm9tIHRoZXNlIHRocmVlIGZpbGVzLCBhc3NlbWJsaW5nIHRoZSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIG9iamVjdCB3ZSBoYXZlIHdvcmtlZCB3aXRoIGJlZm9yZS4KCkFsdGVybmF0aXZlbHksIHdlIGNvdWxkIHVzZSB0aGUgYEhERjVgIGZvcm1hdCBmaWxlIHRoYXQgQ2VsbCBSYW5nZXIgb3V0cHV0cyBhcyBhIGZpbGUgd2l0aCB0aGUgYC5oNWAgZXh0ZW5zaW9uLCB3aGljaCBjb250YWlucyB0aGUgc2FtZSBkYXRhLgpGb3Igd2hhdGV2ZXIgcmVhc29uLCB0aGUgd2F5IHlvdSByZWFkIHRoZSBkYXRhIGFmZmVjdHMgaG93IGl0IGlzIHN0b3JlZCBpbiBSLgpSZWFkaW5nIGZyb20gdGhlIGRpcmVjdG9yeSByZXN1bHRzIGluIHNtYWxsZXIgb2JqZWN0cyBpbiBSLCBzbyB0aGF0IGlzIHdoYXQgd2Ugd2lsbCBkbyBoZXJlLgoKQ2VsbCBSYW5nZXIgYWxzbyBvdXRwdXRzIGJvdGggZmlsdGVyZWQgYW5kIHJhdyBtYXRyaWNlczsgdG9kYXkgd2Ugd2lsbCBzdGFydCB3aXRoIHRoZSByYXcgbWF0cml4IGFuZCBwZXJmb3JtIG91ciBvd24gZmlsdGVyaW5nLgoKIVtSb2FkbWFwOiBQcmVwcm9jZXNzaW5nIGFuZCBJbXBvcnRdKGRpYWdyYW1zL3JvYWRtYXBfc2luZ2xlX3ByZXByb2Nlc3NfYWxldmluLnBuZykKCmBgYHtyIHJlYWQxMHgsIGxpdmUgPSBUUlVFfQpob2Rna2luc19zY2UgPC0gRHJvcGxldFV0aWxzOjpyZWFkMTB4Q291bnRzKHJhd19tYXRyaXhfZGlyKQpgYGAKCkhvdyBtYW55IHBvdGVudGlhbCBjZWxscyBhcmUgdGhlcmUgaGVyZT8KCmBgYHtyIGNlbGxjb3VudCwgbGl2ZSA9IFRSVUV9CmRpbShob2Rna2luc19zY2UpCmBgYAoKVGhhdCBpcyBhIGxvdCBvZiBjZWxscyEKSW4gZmFjdCwgaXQgaXMgcmVhbGx5IGV2ZXJ5IHBvc3NpYmxlIGJhcmNvZGUsIHdoZXRoZXIgdGhlcmUgd2VyZSByZWFkcyBhc3NvY2lhdGVkIHdpdGggaXQgb3Igbm90LgpXZSBzaG91bGQgcHJvYmFibHkgZG8gc29tZXRoaW5nIGFib3V0IHRoYXQuCgoKIyMgUUMgYW5kIG5vcm1hbGl6YXRpb24KCiFbUm9hZG1hcDogUUMgYW5kIGZpbHRlcmluZ10oZGlhZ3JhbXMvcm9hZG1hcF9zaW5nbGVfcWNfbm9ybS5wbmcpCgojIyMgQmFzaWMgUUMgc3RhdHMKCldlIHdpbGwgc3RhcnQgYnkgY2FsY3VsYXRpbmcgdGhlIGJhc2ljIFFDIHN0YXRzIGFzIHdlIGhhdmUgZG9uZSBwcmV2aW91c2x5LCBhZGRpbmcgdGhvc2UgdG8gb3VyIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0LgoKVGhlIGZpcnN0IHN0ZXAgYWdhaW4gaXMgcmVhZGluZyBpbiBvdXIgdGFibGUgb2YgbWl0b2Nob25kcmlhbCBnZW5lcyBhbmQgZmluZGluZyB0aGUgb25lcyB0aGF0IHdlcmUgcXVhbnRpZmllZCBvdXIgZGF0YSBzZXQuCgpgYGB7ciBtaXRvZ2VuZXN9Cm1pdG9fZ2VuZXMgPC0gcmVhZHI6OnJlYWRfdHN2KG1pdG9fZmlsZSkgfD4KICBkcGx5cjo6ZmlsdGVyKGdlbmVfaWQgJWluJSByb3duYW1lcyhob2Rna2luc19zY2UpKSB8PgogIGRwbHlyOjpwdWxsKGdlbmVfaWQpCmBgYAoKTmV4dCB3ZSB3aWxsIGNhbGN1bGF0ZSB0aGUgUUMgc3RhdHMgdGhhdCB3ZSB1c2VkIGJlZm9yZS4KTm90ZSB0aGF0IHRoaXMgaXMgbXVjaCBzbG93ZXIgdGhhbiBiZWZvcmUsIGFzIHdlIGhhdmUgbWFueSBtb3JlIGdlbmVzIGluIHRoZSB1bmZpbHRlcmVkIHNldCEKCmBgYHtyIGNhbGN1bGF0ZVFDfQpob2Rna2luc19zY2UgPC0gc2NhdGVyOjphZGRQZXJDZWxsUUMoCiAgaG9kZ2tpbnNfc2NlLAogIHN1YnNldHMgPSBsaXN0KG1pdG8gPSBtaXRvX2dlbmVzKSkKYGBgCgpXZSBjYW4gbm93IGRvIHRoZSBtb3N0IGJhc2ljIGxldmVsIG9mIGZpbHRlcmluZzogZ2V0dGluZyByaWQgb2YgImNlbGxzIiB3aXRoIG5vIHJlYWRzLgoKYGBge3IgcmVtb3ZlX3plcm8sIGxpdmUgPSBUUlVFfQpob2Rna2luc19zY2UgPC0gaG9kZ2tpbnNfc2NlWywgaG9kZ2tpbnNfc2NlJHRvdGFsID4gMF0KZGltKGhvZGdraW5zX3NjZSkKYGBgCgojIyMgRmlsdGVyaW5nIHdpdGggYGVtcHR5RHJvcHNDZWxsUmFuZ2VyKClgCgpUaGUgYERyb3BsZXRVdGlsc2AgcGFja2FnZSB0aGF0IHdlIHVzZWQgdG8gcmVhZCBpbiB0aGUgMTB4IGRhdGEgaGFzIGEgbnVtYmVyIG9mIG90aGVyIHVzZWZ1bCBmZWF0dXJlcy4KT25lIGlzIHRoZSBgZW1wdHlEcm9wc0NlbGxSYW5nZXIoKWAgZnVuY3Rpb24sIHdoaWNoIHVzZXMgdGhlIG92ZXJhbGwgZ2VuZSBleHByZXNzaW9uIHBhdHRlcm5zIGluIHRoZSBzYW1wbGUgdG8gaWRlbnRpZnkgZHJvcGxldHMgdGhhdCBhcmUgbGlrZWx5IHRvIG5vdCBjb250YWluIGFuIGludGFjdCBjZWxsLCBidXQgbWF5IHNpbXBseSBoYXZlIGNvbnRhaW5lZCBsb29zZSBhbWJpZW50IFJOQSByZWxlYXNlZCBkdXJpbmcgY2VsbCBzZXBhcmF0aW9uLgpUaGlzIG1ldGhvZCB3YXMgb3JpZ2luYWxseSBkZXZlbG9wZWQgYnkgW0x1biAqZXQgYWwuKiAoMjAxOSldKGh0dHBzOi8vZG9pLm9yZy8xMC4xMTg2L3MxMzA1OS0wMTktMTY2Mi15KSBhbmQgaW1wbGVtZW50ZWQgYXMgdGhlIGZ1bmN0aW9uIGBlbXB0eURyb3BzKClgLCBidXQgaGFzIHNpbmNlIGJlZW4gYWRhcHRlZCBhcyB0aGUgbWFpbiBmaWx0ZXJpbmcgbWV0aG9kIHVzZWQgYnkgQ2VsbCBSYW5nZXIuIApUaGUgYGVtcHR5RHJvcHNDZWxsUmFuZ2VyKClgIGZ1bmN0aW9uIGVtdWxhdGVzIHRoZSB2YXJpYW50IG9mIHRoaXMgZnVuY3Rpb24gdGhhdCB1c2VkIGJ5IENlbGwgUmFuZ2VyLCBtYWtpbmcgdGhlIHJlc3VsdHMgbW9yZSBjb21wYXJhYmxlIGJldHdlZW4gdGhlIHR3byBtZXRob2RzLgoKVGhlIEVtcHR5IERyb3BzIG1ldGhvZCB1c2VzIHRoZSBkcm9wbGV0cyB3aXRoIHZlcnkgbG93IFVNSSBjb3VudHMgdG8gZXN0aW1hdGUgdGhlICJhbWJpZW50IiBleHByZXNzaW9uIHBhdHRlcm4gb2YgUk5BIGNvbnRlbnQgb3V0c2lkZSBvZiBjZWxscy4KSXQgdGhlbiBzY29yZXMgdGhlIHJlbWFpbmluZyBjZWxscyBiYXNlZCBob3cgbXVjaCB0aGV5IGRldmlhdGUgZnJvbSB0aGF0IHBhdHRlcm4sIGFzc2lnbmluZyBhIHNtYWxsIFAgdmFsdWUgd2hlbiB0aGUgZHJvcGxldCdzIGV4cHJlc3Npb24gZGV2aWF0ZXMgZnJvbSB0aGUgYW1iaWVudCBleHByZXNzaW9uIHBhdHRlcm4uCkJlY2F1c2UgaXQgdXNlcyB0aGUgbG93IFVNSSBjb3VudCBkcm9wbGV0cywgdGhpcyBtZXRob2Qgc2hvdWxkIG5vdCBiZSB1c2VkIHdoZW4gb3RoZXIgZmlsdGVyaW5nIGhhcyBhbHJlYWR5IGJlZW4gcGVyZm9ybWVkICh3aGljaCBpcyB1bmZvcnR1bmF0ZWx5IHRoZSBjYXNlIHdpdGggdGhlIHZlcnNpb24gb2YgYHNhbG1vbiBhbGV2aW5gIHdlIHVzZWQpLgoKVGhpcyBtZXRob2Qgc2VlbXMgdG8gcGVyZm9ybSB3ZWxsIHRvIGV4Y2x1ZGUgZmFsc2UgImNlbGxzIiB3aGlsZSByZXRhaW5pbmcgY2VsbHMgd2l0aCBkaXN0aW5jdCBleHByZXNzaW9uIHByb2ZpbGVzIGJ1dCBsb3cgY291bnRzIHRoYXQgbWlnaHQgaGF2ZSBmYWlsZWQgYSBzaW1wbGUgY3V0b2ZmLgpOb3RlIHRoYXQgdGhpcyBtZXRob2QgYWxzbyByZXF1aXJlcyB0aGF0IHRoZSBkYXRhIGhhcyBhbHJlYWR5IGJlZW4gcXVhbnRpZmllZCwgd2l0aCByZWFkcyBhc3NpZ25lZCB0byBnZW5lcywgYXMgY29tcGFyZWQgdG8gYSBzaW1wbGUgdG90YWwgVU1JIGNvdW50IGZpbHRlciB3aGljaCBjYW4gYmUgcGVyZm9ybWVkIG11Y2ggZWFybGllciBpbiB0aGUgcGlwZWxpbmUuCgpUaGUgYGVtcHR5RHJvcHNDZWxsUmFuZ2VyKClgIGZ1bmN0aW9uIHRha2VzIHRoZSBgY291bnRzYCBtYXRyaXggZnJvbSBvdXIgU2luZ2xlQ2VsbEV4cGVyaW1lbnQsIGFuZCByZXR1cm5zIGEgZGF0YSBmcmFtZSB3aXRoIHRoZSBzdGF0aXN0aWNzIGl0IGNhbGN1bGF0ZXMuClRoaXMgd2lsbCB0YWtlIGEgZmV3IG1pbnV0ZXMgdG8gcnVuLCBidXQgd2UgY2FuIHNwZWVkIGl0IHVwIGJ5IGFsbG93aW5nIHBhcmFsbGVsIHByb2Nlc3NpbmcuCgpgYGB7ciBlbXB0eWRyb3BzLCBsaXZlID0gVFJVRX0KZHJvcGxldF9zdGF0cyA8LSBEcm9wbGV0VXRpbHM6OmVtcHR5RHJvcHNDZWxsUmFuZ2VyKAogIGNvdW50cyhob2Rna2luc19zY2UpLAogIEJQUEFSQU0gPSBCaW9jUGFyYWxsZWw6Ok11bHRpY29yZVBhcmFtKDQpKSAjIHVzZSBtdWx0aXByb2Nlc3NpbmcKYGBgCgpXZSB3aWxsIHVzZSBhIGZhbHNlIGRpc2NvdmVyeSByYXRlIChGRFIpIG9mIDAuMDEgYXMgb3VyIGN1dG9mZiBmb3IgInJlYWwiIGNlbGxzLgpTaW5jZSBgZW1wdHlEcm9wc0NlbGxSYW5nZXIoKWAgdXNlcyBsb3cgY291bnQgY2VsbHMgdG8gZXN0aW1hdGUgdGhlICJhbWJpZW50IiBleHByZXNzaW9uIHBhdHRlcm4sIHRob3NlIGNlbGxzIGFyZSBub3QgYXNzaWduZWQgYW4gRkRSIHZhbHVlLCBhbmQgaGF2ZSBhIHZhbHVlIG9mIE5BLgpUaGVzZSBOQXMgY2FuIGJlIGEgcHJvYmxlbSBmb3IgZmlsdGVyaW5nIHdpdGggYSBCb29sZWFuIHZlY3RvciwgYXMgd2UgZGlkIGFib3ZlLCBzbyBpbnN0ZWFkIHdlIHdpbGwgdXNlIHRoZSBgd2hpY2goKWAgZnVuY3Rpb24gdG8gZ2V0IHRoZSAqcG9zaXRpb25zKiBvZiB0aGUgY2VsbHMgdGhhdCBwYXNzIG91ciBmaWx0ZXIgYW5kIHNlbGVjdCB0aGUgY29sdW1ucyB3ZSB3YW50IHVzaW5nIHRoYXQuCgpgYGB7ciBmaWx0ZXJfZW1wdHksIGxpdmUgPSBUUlVFfQpjZWxsc190b19yZXRhaW4gPC0gd2hpY2goZHJvcGxldF9zdGF0cyRGRFIgPD0gMC4wMSkKCmZpbHRlcmVkX3NjZSA8LSBob2Rna2luc19zY2VbLCBjZWxsc190b19yZXRhaW5dCmRpbShmaWx0ZXJlZF9zY2UpCmBgYAoKSG93IGRvZXMgdGhpcyBjb21wYXJlIHRvIHRoZSBudW1iZXIgb2YgY2VsbHMgaW4gdGhlIENlbGwgUmFuZ2VyIGZpbHRlcmVkIGRhdGE/Ckxvb2tpbmcgdGhlIGB3ZWJfc3VtbWFyeS5odG1sYCByZXBvcnQgZnJvbSBDZWxsIFJhbmdlciwgaXQgc2VlbXMgdGhhdCBpdCB3b3VsZCBoYXZlIGtlcHQgMywzOTQgY2VsbHMsIHNvIHdlIHNlZW0gdG8gYmUgZ2V0dGluZyBicm9hZGx5IHNpbWlsYXIgcmVzdWx0cy4KCiMjIyBDaGVja2luZyBtaXRvY2hvbmRyaWFsIGNvbnRlbnQKCldoaWxlIGBlbXB0eURyb3BzQ2VsbFJhbmdlcigpYCBzaG91bGQgaGF2ZSBmaWx0ZXJlZCBvdXQgZHJvcGxldHMgY29udGFpbmluZyBubyBjZWxscywgaXQgd2lsbCBub3QgbmVjZXNzYXJpbHkgZmlsdGVyIG91dCBkYW1hZ2VkIGNlbGxzLgpGb3IgdGhhdCB3ZSB3aWxsIHN0aWxsIHdhbnQgdG8gbG9vayBhdCBtaXRvY2hvbmRyaWFsIGNvbnRlbnQsIGFzIHdlIGRpZCBwcmV2aW91c2x5LgpUaGUgc3RhdGlzdGljcyB3ZSBjYWxjdWxhdGVkIGVhcmxpZXIgd2l0aCBgYWRkUGVyQ2VsbFFDKClgIGFyZSByZXRhaW5lZCBpbiBvdXIgbmV3IG9iamVjdCwgc28gd2UgY2FuIHBsb3QgdGhvc2UgZGlyZWN0bHkuCgpgYGB7ciBtaXRvX3BlcmNlbnRfcGxvdH0KIyBQbG90IHRoZSBtaXRvY2hvbmRyaWFsIHBlcmNlbnRzIHN0b3JlZCBpbiBgZmlsdGVyZWRfc2NlYApnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gZmlsdGVyZWRfc2NlJHN1YnNldHNfbWl0b19wZXJjZW50KSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAxMDApCmBgYApUaGVyZSBhcmUgY2VydGFpbmx5IHNvbWUgY2VsbHMgd2l0aCBoaWdoIG1pdG9jaG9uZHJpYWwgcGVyY2VudGFnZXMhCkZvciBub3csIHdlIHdpbGwgdXNlIGEgY3V0b2ZmIG9mIDIwJSB0byBmaWx0ZXIgb3V0IHRoZSB3b3JzdCBvZiB0aGUgY2VsbHMuCgpgYGB7ciBsaXZlID0gVFJVRX0KZmlsdGVyZWRfc2NlIDwtIGZpbHRlcmVkX3NjZVssIGZpbHRlcmVkX3NjZSRzdWJzZXRzX21pdG9fcGVyY2VudCA8IDIwXQpgYGAKCgpXZSBjYW4gYWxzbyBmaWx0ZXIgYnkgZmVhdHVyZXMgKGdlbmVzIGluIG91ciBjYXNlKSB1c2luZyBgc2NhdGVyOjphZGRQZXJGZWF0dXJlUUMoKWAgd2hpY2ggd2lsbCBjb21wdXRlIHRoZSBudW1iZXIgb2Ygc2FtcGxlcyB3aGVyZSBlYWNoIGdlbmUgaXMgZGV0ZWN0ZWQgYW5kIHRoZSBtZWFuIGNvdW50IGFjcm9zcyBhbGwgZ2VuZXMuCldlIGNhbiB0aGVuIHVzZSB0aG9zZSBkYXRhIChzdG9yZWQgaW4gYHJvd0RhdGFgKSB0byBmaWx0ZXIgYnkgcm93IHRvIG9ubHkgdGhlIGdlbmVzIHRoYXQgYXJlIGRldGVjdGVkIGluIGF0IGxlYXN0IDUlIG9mIGNlbGxzLCBhbmQgd2l0aCBhIG1lYW4gY291bnQgPiAwLjEuCgpgYGB7ciBnZW5lX3FjfQpmaWx0ZXJlZF9zY2UgPC0gc2NhdGVyOjphZGRQZXJGZWF0dXJlUUMoZmlsdGVyZWRfc2NlKQpkZXRlY3RlZCA8LSByb3dEYXRhKGZpbHRlcmVkX3NjZSkkZGV0ZWN0ZWQgPiA1CmV4cHJlc3NlZCA8LSByb3dEYXRhKGZpbHRlcmVkX3NjZSkkbWVhbiA+IDAuMQoKIyBmaWx0ZXIgdGhlIGdlbmVzIChyb3dzKSB0aGlzIHRpbWUKZmlsdGVyZWRfc2NlIDwtIGZpbHRlcmVkX3NjZVtkZXRlY3RlZCAmIGV4cHJlc3NlZCwgXQpgYGAKCkhvdyBtYW55IGNlbGxzIGRvIHdlIGhhdmUgbm93PwoKYGBge3IgZmlsdGVyZWRfZGltfQpkaW0oZmlsdGVyZWRfc2NlKQpgYGAKCgojIyMgTm9ybWFsaXplCgpOb3cgd2Ugd2lsbCBwZXJmb3JtIHRoZSBzYW1lIG5vcm1hbGl6YXRpb24gc3RlcHMgd2UgZGlkIGluIGEgcHJldmlvdXMgZGF0YXNldCwgdXNpbmcgYHNjcmFuOjpjb21wdXRlU3VtRmFjdG9ycygpYCBhbmQgYHNjYXRlcjo6bG9nTm9ybUNvdW50cygpYC4KWW91IG1pZ2h0IHJlY2FsbCB0aGF0IHRoZXJlIGlzIGEgYml0IG9mIHJhbmRvbW5lc3MgaW4gc29tZSBvZiB0aGVzZSBjYWxjdWxhdGlvbnMsIHNvIHdlIHNob3VsZCBiZSBzdXJlIHRvIGhhdmUgdXNlZCBgc2V0LnNlZWQoKWAgZWFybGllciBpbiB0aGUgbm90ZWJvb2sgZm9yIHJlcHJvZHVjaWJpbGl0eS4KCmBgYHtyIHN1bWZhY3RvcnN9CiMgQ2x1c3RlciBzaW1pbGFyIGNlbGxzCnFjbHVzdCA8LSBzY3Jhbjo6cXVpY2tDbHVzdGVyKGZpbHRlcmVkX3NjZSkKCiMgQ29tcHV0ZSBzdW0gZmFjdG9ycyBmb3IgZWFjaCBjZWxsIGNsdXN0ZXIgZ3JvdXBpbmcuCmZpbHRlcmVkX3NjZSA8LSBzY3Jhbjo6Y29tcHV0ZVN1bUZhY3RvcnMoZmlsdGVyZWRfc2NlLCBjbHVzdGVycyA9IHFjbHVzdCwgcG9zaXRpdmUgPSBGQUxTRSkKYGBgCgpJdCB0dXJucyBvdXQgaW4gdGhpcyBjYXNlIHdlIGVuZCB1cCB3aXRoIHNvbWUgbmVnYXRpdmUgc2l6ZSBmYWN0b3JzLgpUaGlzIGlzIHVzdWFsbHkgYW4gaW5kaWNhdGlvbiB0aGF0IG91ciBmaWx0ZXJpbmcgd2FzIG5vdCBzdHJpbmdlbnQgZW5vdWdoLCBhbmQgdGhlcmUgcmVtYWluIGEgbnVtYmVyIG9mIGNlbGxzIG9yIGdlbmVzIHdpdGggbmVhcmx5IHplcm8gY291bnRzLgpUaGlzIHByb2JhYmx5IGhhcHBlbmVkIHdoZW4gd2UgcmVtb3ZlZCB0aGUgaW5mcmVxdWVudGx5LWV4cHJlc3NlZCBnZW5lczsgY2VsbHMgd2hpY2ggaGFkIGhpZ2ggY291bnRzIGZyb20gdGhvc2UgcGFydGljdWxhciBnZW5lcyAoYW5kIGZldyBvdGhlcnMpIGNvdWxkIGhhdmUgaGFkIHRoZWlyIHRvdGFsIGNvdW50cyBkcmFtYXRpY2FsbHkgcmVkdWNlZC4KClRvIGFjY291bnQgZm9yIHRoaXMsIHdlIHdpbGwgcmVjYWxjdWxhdGUgdGhlIHBlci1jZWxsIHN0YXRzIGFuZCBmaWx0ZXIgb3V0IGxvdyBjb3VudHMuClVuZm9ydHVuYXRlbHksIHRvIGRvIHRoaXMsIHdlIG5lZWQgdG8gZmlyc3QgcmVtb3ZlIHRoZSBwcmV2aW91c2x5IGNhbGN1bGF0ZWQgc3RhdGlzdGljcywgd2hpY2ggd2Ugd2lsbCBkbyBieSBzZXR0aW5nIHRoZW0gdG8gYE5VTExgLgoKYGBge3IgcmVRQ30KIyByZW1vdmUgcHJldmlvdXMgY2FsY3VsYXRpb25zCmZpbHRlcmVkX3NjZSRzdW0gPC0gTlVMTApmaWx0ZXJlZF9zY2UkZGV0ZWN0ZWQgPC0gTlVMTApmaWx0ZXJlZF9zY2UkdG90YWwgPC0gTlVMTApmaWx0ZXJlZF9zY2Ukc3Vic2V0c19taXRvX3N1bSA8LSBOVUxMCmZpbHRlcmVkX3NjZSRzdWJzZXRzX21pdG9fZGV0ZWN0ZWQgPC0gTlVMTApmaWx0ZXJlZF9zY2Ukc3Vic2V0c19taXRvX3N1bSA8LSBOVUxMCgojIHJlY2FsY3VsYXRlIGNlbGwgc3RhdHMKZmlsdGVyZWRfc2NlIDwtIHNjYXRlcjo6YWRkUGVyQ2VsbFFDKGZpbHRlcmVkX3NjZSwgc3Vic2V0cyA9IGxpc3QobWl0byA9IG1pdG9fZ2VuZXMpKQoKIyBwcmludCB0aGUgbnVtYmVyIG9mIGNlbGxzIHdpdGggZmV3ZXIgdGhhbiA1MDAgVU1JcwpzdW0oZmlsdGVyZWRfc2NlJHN1bSA8IDUwMCkKYGBgCgpOb3cgd2UgY2FuIGZpbHRlciBhZ2Fpbi4KSW4gdGhpcyBjYXNlLCB3ZSB3aWxsIGtlZXAgY2VsbHMgd2l0aCBhdCBsZWFzdCA1MDAgVU1JcyBhZnRlciByZW1vdmluZyB0aGUgbG93bHkgZXhwcmVzc2VkIGdlbmVzLgpUaGVuIHdlIHdpbGwgcmVkbyB0aGUgc2l6ZSBmYWN0b3IgY2FsY3VsYXRpb24sIGhvcGVmdWxseSB3aXRoIG5vIG1vcmUgd2FybmluZ3MuCgoKYGBge3IgcmVmaWx0ZXJ9CmZpbHRlcmVkX3NjZSA8LSBmaWx0ZXJlZF9zY2VbLCBmaWx0ZXJlZF9zY2Ukc3VtID49IDUwMF0KCnFjbHVzdCA8LSBzY3Jhbjo6cXVpY2tDbHVzdGVyKGZpbHRlcmVkX3NjZSkKCmZpbHRlcmVkX3NjZSA8LSBzY3Jhbjo6Y29tcHV0ZVN1bUZhY3RvcnMoZmlsdGVyZWRfc2NlLCBjbHVzdGVycyA9IHFjbHVzdCkKYGBgCgpMb29rcyBnb29kISBOb3cgd2UnbGwgZG8gdGhlIG5vcm1hbGl6YXRpb24uCgpgYGB7ciBub3JtYWxpemV9CiMgTm9ybWFsaXplIGFuZCBsb2cgdHJhbnNmb3JtLgpub3JtYWxpemVkX3NjZSA8LSBzY2F0ZXI6OmxvZ05vcm1Db3VudHMoZmlsdGVyZWRfc2NlKQpgYGAKCkF0IHRoaXMgcG9pbnQsIHdlIGhhdmUgYSBmZXcgZGlmZmVyZW50IHZlcnNpb25zIG9mIG91ciBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIG9iamVjdC4KVGhlIG9yaWdpbmFsIChtb3N0bHkpIHVuZmlsdGVyZWQgdmVyc2lvbiBpcyBpbiBgaG9kZ2tpbnNfc2NlYCwgdGhlIGZpbHRlcmVkIHZlcnNpb24gaW4gYGZpbHRlcmVkX3NjZWAsIGFuZCB0aGUgbm9ybWFsaXplZCB2ZXJzaW9uIGluIGBub3JtYWxpemVkX3NjZWAuCldlIGNhbiBjbGVhbiB0aG9zZSB1cCBhIGJpdCB0byBzYXZlIG1lbW9yeSwga2VlcGluZyBvbmx5IHRoZSBsYXRlc3QgYG5vcm1hbGl6ZWRfc2NlYCB2ZXJzaW9uLCB3aGljaCBub3cgaGFzIHR3byBgYXNzYXlgczoKYGNvdW50c2Agd2l0aCB0aGUgcmF3IGRhdGEgYW5kIGBsb2djb3VudHNgIHdpdGggdGhlIG5vcm1hbGl6ZWQgYW5kIHRyYW5zZm9ybWVkIGRhdGEuCgpgYGB7ciBjbGVhbl91cCwgbGl2ZSA9IFRSVUV9CmFzc2F5TmFtZXMobm9ybWFsaXplZF9zY2UpCnJtKGhvZGdraW5zX3NjZSwgZmlsdGVyZWRfc2NlKQpgYGAKCgojIyBEaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gYW5kIGRpc3BsYXkKCiFbUm9hZG1hcDogRGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uXShkaWFncmFtcy9yb2FkbWFwX3NpbmdsZV9kaW1lbnNpb25fcmVkdWN0aW9uLnBuZykKCiMjIyBQcmluY2lwYWwgQ29tcG9uZW50cyBBbmFseXNpcwoKUHJpbmNpcGFsIGNvbXBvbmVudCBhbmFseXNpcyAoUENBKSBpcyBhIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiB0ZWNobmlxdWUgdGhhdCBhbGxvd3MgdXMgdG8gaWRlbnRpZnkgdGhlIGxhcmdlc3QgY29tcG9uZW50cyBvZiB2YXJpYXRpb24gaW4gYSBjb21wbGV4IGRhdGFzZXQuCk91ciBleHByZXNzaW9uIGRhdGEgY2FuIGJlIHRob3VnaHQgb2YgYXMgbWFwcGluZyBlYWNoIHNhbXBsZSBpbiBhIG11bHRpZGltZW5zaW9uYWwgc3BhY2UgZGVmaW5lZCBieSB0aGUgZXhwcmVzc2lvbiBsZXZlbCBvZiBlYWNoIGdlbmUuClRoZSBleHByZXNzaW9uIG9mIG1hbnkgb2YgdGhvc2UgZ2VuZXMgYXJlIGNvcnJlbGF0ZWQsIHNvIHdlIGNhbiBvZnRlbiBnZXQgYSBiZXR0ZXIsIHNpbXBsZXIgcGljdHVyZSBvZiB0aGUgZGF0YSBieSBjb21iaW5pbmcgdGhlIGluZm9ybWF0aW9uIGZyb20gdGhvc2UgY29ycmVsYXRlZCBnZW5lcy4KClBDQSByb3RhdGVzIGFuZCB0cmFuc2Zvcm1zIHRoaXMgc3BhY2Ugc28gdGhhdCBlYWNoIGF4aXMgaXMgbm93IGEgY29tYmluYXRpb24gb2YgbXVsdGlwbGUgY29ycmVsYXRlZCBnZW5lcywgb3JkZXJlZCBzbyB0aGUgZmlyc3QgYXhlcyBjYXB0dXJlIHRoZSBtb3N0IHZhcmlhdGlvbiBmcm9tIHRoZSBkYXRhLgpUaGVzZSBuZXcgYXhlcyBhcmUgdGhlICJwcmluY2lwYWwgY29tcG9uZW50cy4iCklmIHdlIGxvb2sgYXQgdGhlIGZpcnN0IGZldyBjb21wb25lbnRzLCB3ZSBjYW4gb2Z0ZW4gZ2V0IGEgbmljZSBvdmVydmlldyBvZiByZWxhdGlvbnNoaXBzIGFtb25nIHRoZSBzYW1wbGVzIGluIHRoZSBkYXRhLgoKIyMjIyBTdG9yaW5nIFBDQSByZXN1bHRzIHdpdGggdGhlIHJhdyBkYXRhCgpXZSB3aWxsIHN0b3JlIHRoZSBQQ0EgcmVzdWx0cyBpbiBvdXIgYFNpbmdsZUNlbGxFeHBlcmltZW50YCBvYmplY3QsIGFzIHdlIHdpbGwgd2FudCB0byB1c2UgdGhlbSBsYXRlci4KVG8gZG8gdGhpcywgd2Ugd2lsbCB1c2UgdGhlIGBydW5QQ0EoKWAgZnVuY3Rpb24gZnJvbSBgc2NhdGVyYCwgd2hpY2ggcGVyZm9ybXMgdGhlIFBDQSBjYWxjdWxhdGlvbnMgYW5kIHJldHVybnMgYSBuZXcgb2JqZWN0IHdpdGggdGhlIHJlc3VsdHMgc3RvcmVkIGluIHRoZSBgcmVkdWNlZERpbWAgc2xvdC4KSWYgd2Ugd2FudGVkIHRvLCB3ZSBjb3VsZCBnZXQgdGhlIHJhdyByZXN1bHRzIGFzIGEgbWF0cml4IGluc3RlYWQgd2l0aCBgY2FsY3VsYXRlUENBKClgIGZ1bmN0aW9uLCBhcyB3ZSBkaWQgaW4gYSBwcmV2aW91cyBub3RlYm9vay4KCldlIHdpbGwgYWxzbyB1c2UgdGhlIGBudG9wYCBhcmd1bWVudCB0byBjYWxjdWxhdGUgdGhlIFBDQSB1c2luZyAyMDAwIGdlbmVzIHdpdGggdGhlIGhpZ2hlc3QgdmFyaWFuY2UuClRoZSBkZWZhdWx0IGlzIGBudG9wID0gNTAwYC4KCmBgYHtyIHJ1blBDQSwgbGl2ZSA9IFRSVUV9CiMgY2FsY3VsYXRlIFBDQSB1c2luZyB0aGUgdG9wIDIwMDAgZ2VuZXMKbm9ybWFsaXplZF9zY2UgPC0gcnVuUENBKG5vcm1hbGl6ZWRfc2NlLCBudG9wID0gMjAwMCkKYGBgCgpXZSBjYW4gc2VlIHdoYXQgcmVkdWNlZCBkaW1lbnNpb25hbGl0eSBtYXRyaWNlcyBhcmUgc3RvcmVkIGluIHRoZSBvYmplY3Qgd2l0aCB0aGUgYHJlZHVjZWREaW1OYW1lcygpYCBmdW5jdGlvbi4KCmBgYHtyIHJlZHVjZWRfZGltX25hbWVzLCBsaXZlID0gVFJVRX0KIyBwcmludCB0aGUgcmVkdWNlZCBkaW1lbnNpb25hbGl0eSB0YWJsZXMgYXZhaWxhYmxlCnJlZHVjZWREaW1OYW1lcyhub3JtYWxpemVkX3NjZSkKYGBgCgpUbyBleHRyYWN0IHRoZW0gYnkgbmFtZSwgd2UgdXNlIHRoZSBgcmVkdWNlZERpbSgpYCBmdW5jdGlvbiwgbXVjaCBsaWtlIHRoZSBgYXNzYXkoKWAgZnVuY3Rpb24gdG8gZXh0cmFjdCBvcmlnaW5hbCBkYXRhLgoKYGBge3IgZXh0cmFjdF9yZWR1Y2VkLCBsaXZlID0gVFJVRX0KIyBwcmludCB0aGUgdG9wIGNvcm5lciBvZiB0aGUgUENBIG1hdHJpeApyZWR1Y2VkRGltKG5vcm1hbGl6ZWRfc2NlLCAiUENBIilbMToxMCwgMTo1XQpgYGAKCiMjIyMgUGxvdHRpbmcgUENBIHJlc3VsdHMKCklmIHdlIGhhdmUgdGhlIFBDQSByZXN1bHRzIHN0b3JlZCBpbiB0aGUgYFNpbmdsZUNlbGxFeHBlcmltZW50YCBvYmplY3QsIHdlIGNhbiB1c2UgdGhlIGBzY2F0ZXI6OnBsb3RSZWR1Y2VkRGltKClgIGZ1bmN0aW9uIHRvIHBsb3QgaXQgd2l0aCBzb21lIG5pY2UgZGVmYXVsdHMgZWFzaWx5LgpPbmUgbmljZSB0aGluZyBhYm91dCB0aGlzIGZ1bmN0aW9uIGlzIHRoYXQgaXQgdXNlcyBgZ2dwbG90MmAgdW5kZXIgdGhlIGhvb2QsIHNvIGlmIHdlIHdhbnRlZCB0byBjdXN0b21pemUgaXQgbGF0ZXIsIHdlIGNvdWxkLgoKYGBge3IgcGxvdFBDQSwgbGl2ZSA9IFRSVUV9CiMgcGxvdCBQQ0EgcmVzdWx0cwpwbG90UmVkdWNlZERpbShub3JtYWxpemVkX3NjZSwgIlBDQSIpCmBgYApQQ0EgZ2l2ZXMgdXMgYSBtYXRyaXggd2l0aCBtb3JlIHRoYW4ganVzdCB0d28gZGltZW5zaW9ucywgYW5kIHdlIG1pZ2h0IHdhbnQgdG8gbG9vayBhdCBzb21lIGhpZ2hlciBkaW1lbnNpb25zIHRvby4KV2UgY2FuIGRvIHRoYXQgd2l0aCB0aGUgYG5jb21wb25lbnRzYCBhcmd1bWVudC4KCmBgYHtyIHBsb3RQQ0EzNCwgbGl2ZSA9IFRSVUV9CiMgcGxvdCBQQzMgYW5kIFBDNApwbG90UmVkdWNlZERpbShub3JtYWxpemVkX3NjZSwgIlBDQSIsIG5jb21wb25lbnRzID0gYygzLDQpKQpgYGAKCiMjIyBNb2RlbGluZyB2YXJpYW5jZQoKVGhlIHZhcmlhdGlvbiBpbiBnZW5lIGV4cHJlc3Npb24gd2Ugc2VlIGFtb25nIGNlbGxzIGNvbWVzIGZyb20gYSBjb21iaW5hdGlvbiBvZiB2YXJpYXRpb24gZHVlIHRvIHRlY2huaWNhbCBlZmZlY3RzIGFuZCB0aGUgYmlvbG9neSB3ZSByZWFsbHkgY2FyZSBhYm91dC4KSW4gb3JkZXIgdG8gcm91Z2hseSBhY2NvdW50IGZvciB0aGlzIHdlIGNvdWxkIGp1c3QgdGFrZSB0aGUgbGFyZ2VzdCB2YXJpYW5jZSBnZW5lcywgb24gdGhlIGFzc3VtcHRpb24gdGhhdCBsb3cgdmFyaWFuY2UgZ2VuZXMgYXJlIG1vc3RseSBqdXN0IG5vaXNlLgpUaGlzIGlzIHRoZSBkZWZhdWx0IGFwcHJvYWNoIHRoYXQgYHJ1blBDQSgpYCBhbmQgYGNhbGN1bGF0ZVBDQSgpYCB0YWtlLCB1c2luZyB0aGUgZ2VuZXMgd2l0aCB0aGUgZ3JlYXRlc3QgdmFyaWFuY2UgYWNyb3NzIGNlbGxzIHRvIGNhbGN1bGF0ZSB0aGUgUENBIG1hdHJpeC4KCklmIHdlIHdhbnQgdG8gYmUgYSBiaXQgbW9yZSBjYXJlZnVsIGFib3V0IGl0LCB3ZSBjYW4gbW9kZWwgdGhlIHZhcmlhbmNlIGluIGV4cHJlc3Npb24gb2YgZWFjaCBnZW5lIGFzIGEgZnVuY3Rpb24gb2YgdGhlIG1lYW4gZXhwcmVzc2lvbiBmb3IgdGhhdCBnZW5lLgpUaGlzIGlzIHVzZWZ1bCBiZWNhdXNlIHdlIGdlbmVyYWxseSBleHBlY3QgdGhlIHZhcmlhbmNlIHRvIGluY3JlYXNlIGFzIG1lYW4gZXhwcmVzc2lvbiBpbmNyZWFzZXMsIGV2ZW4gaWYgdGhlcmUgaXMgbm8gYmlvbG9naWNhbCBzaWduYWwgaW4gdGhlIGV4cHJlc3Npb24gdmFyaWF0aW9uLgoKV2Ugd2lsbCBkbyB0aGlzIG1vZGVsaW5nIG9mIHZhcmlhbmNlIGJ5IGV4cHJlc3Npb24gd2l0aCB0aGUgYHNjcmFuOjptb2RlbEdlbmVWYXIoKWAgZnVuY3Rpb24sIHNhdmluZyB0aGUgcmVzdWx0cyB0byBhIG5ldyB2YXJpYWJsZS4KCmBgYHtyIG1vZGVsX3ZhcmlhbmNlfQpnZW5lX3ZhcmlhbmNlIDwtIHNjcmFuOjptb2RlbEdlbmVWYXIobm9ybWFsaXplZF9zY2UpCmBgYAoKTm93IGxldCdzIHBsb3QgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGdlbmUgZXhwcmVzc2lvbiBhbmQgdmFyaWFuY2Ugd2Ugd2VyZSBkaXNjdXNzaW5nLgpIZXJlIHdlIHdpbGwgYWxzbyBhZGQgdGhlIGZpdHRpbmcgY3VydmUgdGhhdCBgc2NyYW46Om1vZGVsR2VuZVZhcigpYCBjcmVhdGVkLCB3aGljaCBpcyBzdG9yZWQgYXMgZnVuY3Rpb24gaW4gdGhlICBgJHRyZW5kYCBzbG90IG9mIHRoZSBgZ2VuZV92YXJpYW5jZWAgb2JqZWN0LgpXZSBjYW4gYWRkIGEgZnVuY3Rpb24gbGlrZSB0aGF0IGN1cnZlIHRvIGEgYGdncGxvdGAgd2l0aCBhIGBzdGF0X2Z1bmN0aW9uYCBsYXllci4KCmBgYHtyIHBsb3RfdmFyaWFuY2V9CmdncGxvdChhcy5kYXRhLmZyYW1lKGdlbmVfdmFyaWFuY2UpLCBhZXMoeCA9IG1lYW4sIHkgPSB0b3RhbCkpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArCiAgc3RhdF9mdW5jdGlvbihmdW4gPSBtZXRhZGF0YShnZW5lX3ZhcmlhbmNlKSR0cmVuZCwgY29sb3IgPSAiYmx1ZSIpICsKICBsYWJzKAogICAgeCA9ICJNZWFuIGxvZy1leHByZXNzaW9uIiwKICAgIHkgPSAiVmFyaWFuY2UiKSArCiAgdGhlbWVfYncoKQpgYGAKCk5vdyB3ZSBjYW4gdXNlIGBzY3Jhbjo6Z2V0VG9wSFZHcygpYCB0byBzZWxlY3QgdGhlIGdlbmVzIHRoYXQgaGF2ZSB0aGUgbW9zdCBiaW9sb2dpY2FsIHZhcmlhdGlvbiAoYWNjb3JkaW5nIHRvIHRoZSBtb2RlbCkgYW5kIHJlY2FsY3VsYXRlIFBDQSBzY29yZXMgdXNpbmcgb25seSB0aG9zZSBnZW5lcy4KKEluIHByYWN0aWNlLCB3ZSBhcmUgc2VsZWN0aW5nIHRoZSBnZW5lcyB3aXRoIHRoZSBsYXJnZXN0IHJlc2lkdWFsIHZhcmlhdGlvbiBhZnRlciByZW1vdmluZyB0ZWNobmljYWwgdmFyaWF0aW9uIG1vZGVsZWQgYnkgdGhlIG1lYW4vdmFyaWFuY2UgcmVsYXRpb25zaGlwLikKCkhlcmUgd2UgYXJlIHBpY2tpbmcgdGhlIDIwMDAgdG9wIGdlbmVzIHRvIG1hdGNoIHRoZSBudW1iZXIgb2YgZ2VuZXMgZnJvbSBvdXIgZWFybGllciBjYWxjdWxhdGlvbnMuCgpgYGB7ciBnZXRfaGlnaHZhciwgbGl2ZSA9IFRSVUV9CiMgc2VsZWN0IHRoZSBtb3N0IHZhcmlhYmxlIGdlbmVzCmhpZ2h2YXJfZ2VuZXMgPC0gc2NyYW46OmdldFRvcEhWR3MoZ2VuZV92YXJpYW5jZSwgbiA9IDIwMDApCiMgY2FsY3VsYXRlIGEgUENBIG1hdHJpeCB1c2luZyB0aG9zZSBnZW5lcwpub3JtYWxpemVkX3NjZSA8LSBydW5QQ0Eobm9ybWFsaXplZF9zY2UsIHN1YnNldF9yb3cgPSBoaWdodmFyX2dlbmVzKQpgYGAKCk5vdyB3ZSBjYW4gcGxvdCBvdXIgbmV3IFBDQSB2YWx1ZXMgZm9yIGNvbXBhcmlzb24uCllvdSBtaWdodCByZWFsaXplIHRoYXQgb3VyIG9sZCBQQ0EgdmFsdWVzIHdlcmUgcmVwbGFjZWQgd2hlbiB3ZSByYW4gYHJ1blBDQSgpYCBhZ2Fpbiwgc28gd2UgY2FuJ3QgcmVjcmVhdGUgdGhlIGVhcmxpZXIgcGxvdHMgYXQgdGhpcyBzdGFnZSBvZiB0aGUgbm90ZWJvb2suCllvdSB3aWxsIGhhdmUgdG8gc2Nyb2xsIHVwIHRvIHlvdXIgZWFybGllciBwbG90cyB0byBjb21wYXJlLgoKYGBge3IgcGxvdFBDQV9oaWdodmFyLCBsaXZlID0gVFJVRX0KIyBwbG90IHRoZSBuZXcgUENBIHJlc3VsdHMKcGxvdFJlZHVjZWREaW0obm9ybWFsaXplZF9zY2UsICJQQ0EiKQpwbG90UmVkdWNlZERpbShub3JtYWxpemVkX3NjZSwgIlBDQSIsIG5jb21wb25lbnRzID0gYygzLDQpKQpgYGAKCiMjIyBVTUFQCgoqKlVNQVAqKiAoVW5pZm9ybSBNYW5pZm9sZCBBcHByb3hpbWF0aW9uIGFuZCBQcm9qZWN0aW9uKSBpcyBhIG1hY2hpbmUgbGVhcm5pbmcgdGVjaG5pcXVlIGRlc2lnbmVkIHRvIHByb3ZpZGUgbW9yZSBkZXRhaWwgaW4gaGlnaGx5IGRpbWVuc2lvbmFsIGRhdGEgdGhhbiBhIHR5cGljYWwgcHJpbmNpcGFsIGNvbXBvbmVudHMgYW5hbHlzaXMuCldoaWxlIFBDQSBhc3N1bWVzIHRoYXQgdGhlIHZhcmlhdGlvbiB3ZSBjYXJlIGFib3V0IGhhcyBhIHBhcnRpY3VsYXIgZGlzdHJpYnV0aW9uIChub3JtYWwsIGJyb2FkbHkgc3BlYWtpbmcpLCBVTUFQIGFsbG93cyBtb3JlIGNvbXBsaWNhdGVkIGRpc3RyaWJ1dGlvbnMgdGhhdCBpdCBsZWFybnMgZnJvbSB0aGUgZGF0YS4KVGhlIHVuZGVybHlpbmcgbWF0aGVtYXRpY3MgYXJlIGJleW9uZCBtZSwgYnV0IGlmIHlvdSBhcmUgbW9yZSBhbWJpdGlvdXMgdGhhbiBJLCB5b3UgY2FuIGxvb2sgYXQgdGhlIHBhcGVyIGJ5IFtNY0lubmVzLCBIZWFseSwgJiBNZWx2aWxsZSAoMjAxOCldKGh0dHBzOi8vYXJ4aXYub3JnL2Ficy8xODAyLjAzNDI2KS4KVGhlIG1haW4gYWR2YW50YWdlIG9mIHRoaXMgY2hhbmdlIGluIHVuZGVybHlpbmcgYXNzdW1wdGlvbnMgaXMgdGhhdCBVTUFQIGNhbiBkbyBhIGJldHRlciBqb2Igc2VwYXJhdGluZyBjbHVzdGVycywgZXNwZWNpYWxseSB3aGVuIHNvbWUgb2YgdGhvc2UgY2x1c3RlcnMgbWF5IGJlIG1vcmUgc2ltaWxhciB0byBlYWNoIG90aGVyIHRoYW4gb3RoZXJzLgoKQW5vdGhlciBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdGVjaG5pcXVlIHRoYXQgeW91IG1heSBoYXZlIGhlYXJkIG9mIGlzICoqdC1TTkUqKiAodC1kaXN0cmlidXRlZCBTdG9jaGFzdGljIE5laWdoYm9yIEVtYmVkZGluZyksIHdoaWNoIGhhcyBzaW1pbGFyIHByb3BlcnRpZXMgdG8gVU1BUCwgYW5kIG9mdGVuIHByb2R1Y2VzIHNpbWlsYXIgcmVzdWx0cy4KVGhlcmUgaXMgc29tZSBvbmdvaW5nIGRlYmF0ZSBhYm91dCB3aGljaCBvZiB0aGVzZSB0d28gdGVjaG5pcXVlcyBpcyBzdXBlcmlvciwgYW5kIHdoZXRoZXIgdGhlIGRpZmZlcmVuY2VzIGFyZSBkdWUgdG8gdGhlIHVuZGVybHlpbmcgYWxnb3JpdGhtIG9yIHRvIGltcGxlbWVudGF0aW9uIGFuZCBwYXJhbWV0ZXIgaW5pdGlhbGl6YXRpb24gZGVmYXVsdHMuClJlZ2FyZGxlc3Mgb2Ygd2h5LCBpbiBvdXIgZXhwZXJpZW5jZSwgVU1BUCBzZWVtcyB0byBwcm9kdWNlIHNsaWdodGx5IGJldHRlciByZXN1bHRzIGFuZCBydW4gYSBiaXQgZmFzdGVyLCBidXQgdGhlIGRpZmZlcmVuY2VzIGNhbiBiZSBzdWJ0bGUuCgojIyMjIERlZmF1bHQgcGFyYW1ldGVycwoKRm9yIGVhc2Ugb2YgdXNlIHdpdGggdGhpcyBkYXRhLCB3ZSB3aWxsIGJlIHVzaW5nIHRoZSBgc2NhdGVyOjpjYWxjdWxhdGVVTUFQKClgIGFuZCBgc2NhdGVyOjpydW5VTUFQKClgIGZ1bmN0aW9uIHRvIGFwcGx5IFVNQVAgdG8gb3VyIHNpbmdsZSBjZWxsIGRhdGEsIGJ1dCBzaW1pbGFyIGZ1bmN0aW9ucyB0aGUgYHV3b3RgIHBhY2thZ2UgKG5vdGFibHkgYHV3b3Q6OnVtYXAoKWApIGNhbiBiZSB1c2VkIHRvIGFwcGx5IFVNQVAgdG8gYW55IG51bWVyaWNhbCBtYXRyaXguCgpVTUFQIGNhbiBiZSBzbG93IGZvciBhIGxhcmdlIGRhdGEgc2V0IHdpdGggbG90cyBvZiBwYXJhbWV0ZXJzLgpJdCBpcyB3b3J0aCBub3RpbmcgdGhhdCB0aGUgYHNjYXRlcjo6Y2FsY3VsYXRlVU1BUCgpYCBpbXBsZW1lbnRhdGlvbiBhY3R1YWxseSBkb2VzIFBDQSBmaXJzdCwgYW5kIHRoZW4gcnVucyBVTUFQIG9uIHRoZSB0b3AgNTAgUENzLgpJZiB3ZSBoYXZlIGFscmVhZHkgY2FsY3VsYXRlZCBQQ0EgKGFzIHdlIGhhdmUpIHdlIGNhbiB0ZWxsIGl0IHRvIHVzZSB0aG9zZSByZXN1bHRzIHdpdGggdGhlIGBkaW1yZWRgIGFyZ3VtZW50LgoKQXMgd2l0aCBQQ0EsIHRoZXJlIGFyZSB0d28gZnVuY3Rpb25zIHdlIGNvdWxkIHVzZToKYHNjYXRlcjo6Y2FsY3VsYXRlVU1BUCgpYCB3aWxsIHJldHVybiBhIG1hdHJpeCBvZiByZXN1bHRzLCB3aXRoIG9uZSByb3cgZm9yIGVhY2ggc2FtcGxlLCBhbmQgYSBjb2x1bW4gZm9yIGVhY2ggb2YgdGhlIFVNQVAgZGltZW5zaW9ucyByZXR1cm5lZC4KYHNjYXRlcjo6cnVuVU1BUCgpYCBwZXJmb3JtcyB0aGUgc2FtZSBmdW5jdGlvbiwgYnV0IHJldHVybnMgdGhlIHJlc3VsdHMgaW4gYSBTaW5nbGVDZWxsRXhwZXJpbWVudCBvYmplY3QuCgpMZXQncyBzZWUgaG93IGl0IGxvb2tzIHdpdGggdGhlIChtb3N0bHkpIGRlZmF1bHQgcGFyYW1ldGVyczoKCmBgYHtyIGNhbGN1bGF0ZV91bWFwLCBsaXZlID0gVFJVRX0KIyBSdW4gVU1BUApub3JtYWxpemVkX3NjZSA8LSBydW5VTUFQKG5vcm1hbGl6ZWRfc2NlLAogICAgICAgICAgICAgICAgICAgICAgICAgIGRpbXJlZCA9ICJQQ0EiKSAjIHVzZSBhbHJlYWR5IHN0b3JlZCBQQ0EgcmVzdWx0cwpgYGAKCk5vdyB3ZSBjYW4gcGxvdCB3aXRoIHRoZSBzYW1lIGBwbG90UmVkdWNlZERpbSgpYCBmdW5jdGlvbiwgc3BlY2lmeWluZyB3ZSB3YW50IHRvIHBsb3QgdGhlIFVNQVAgcmVzdWx0cyB0aGlzIHRpbWUuCldlIHdpbGwgYWxzbyBhZGQgc29tZSBjb2xvciB0aGlzIHRpbWUgd2l0aCB0aGUgYGNvbG9yX2J5YCBhcmd1bWVudCwgdXNpbmcgdGhlIG51bWJlciBvZiBnZW5lcyBkZXRlY3RlZCBpbiBlYWNoIGNlbGwgdG8gYXNzaWduIGEgaHVlLgoKYGBge3IgcGxvdF91bWFwLCBsaXZlID0gVFJVRX0KIyBtYWtlIGEgVU1BUCBwbG90IHdpdGggYHBsb3RSZWR1Y2VkRGltKClgCnBsb3RSZWR1Y2VkRGltKG5vcm1hbGl6ZWRfc2NlLCAiVU1BUCIsIGNvbG9yX2J5ID0gImRldGVjdGVkIikKYGBgCgpUaGVyZSBpcyBjbGVhcmx5IGEgbG90IG9mIHN0cnVjdHVyZSBpbiB0aGVyZSwgYnV0IGlzIGl0IG1lYW5pbmdmdWw/CkRvIHRoZSBjbHVzdGVycyB3ZSBzZWUgZGlmZmVyZW50aWF0ZSBjZWxsIHR5cGVzPyBIb3cgc2hvdWxkIHdlIGRpdmlkZSB0aGVtIHVwPwoKV2Ugd2lsbCBjb21lIGJhY2sgdG8gdGhpcyBxdWVzdGlvbiBsYXRlciEKCiMjIyBVTUFQIGV4cGVyaW1lbnRzCgpOb3cgdGhhdCB3ZSBoYXZlIGFuIGlkZWEgb2Ygd2hhdCBhIFVNQVAgcGxvdCB3aXRoIHRoZSBkZWZhdWx0IHBhcmFtZXRlcnMgbG9va3MgbGlrZSwgbGV0J3MgdHJ5IGV4cGVyaW1lbnRpbmcgd2l0aCB0aGUgYG5fbmVpZ2hib3JzYCBwYXJhbWV0ZXIuCkZpcnN0LCB3ZSBzaG91bGQgc2VlIHdoYXQgdGhpcyBwYXJhbWV0ZXIgaXMsIGFuZCB3aGF0IHRoZSBkZWZhdWx0IHZhbHVlIGlzLgpJbiB0aGUgY29uc29sZSwgcnVuIGA/c2NhdGVyOjpjYWxjdWxhdGVVTUFQYCB0byBzZWUgd2hhdCB0aGlzIChhbmQgb3RoZXIgcGFyYW1ldGVycykgYXJlLgpGb3IgZXZlbiBtb3JlIHBhcmFtZXRlcnMsIHlvdSBjYW4gbG9vayBhdCB0aGUgdW5kZXJseWluZyBpbXBsZW1lbnRhdGlvbiBjb2RlIHRoYXQgYGNhbGN1bGF0ZVVNQVAoKWAgdXNlcywgd2hpY2ggaXMgdGhlIGZ1bmN0aW9uIGB1d290Ojp1bWFwKClgCgpJbiBvcmRlciB0byBtYWtlIG91ciBleHBlcmltZW50YXRpb24gZWFzaWVyLCB3ZSB3aWxsIGNyZWF0ZSBhICpmdW5jdGlvbiogdGhhdCBhbGxvd3MgdXMgdG8gcmVydW4gdGhlIHNhbWUgY29kZSBlYXNpbHksIGJ1dCBjcmVhdGUgYW4gYXJndW1lbnQgdGhhdCBhbGxvd3MgdXMgdG8gY2hhbmdlIG9uZSB2YXJpYWJsZTogdGhlIGBuX25laWdoYm9yc2AgdmFyaWFibGUuCkhlcmUgd2UgYXJlIHNhdmluZyBvbmx5IGEgbGluZSBvZiBjb2RlLCBidXQgd2UgY291bGQgYXBwbHkgdGhpcyB0byBhIG11Y2ggbW9yZSBjb21wbGV4IHNlcmllcyBvZiBvcGVyYXRpb25zIGlmIHdlIHdhbnRlZCB0byEKCmBgYHtyIFVNQVAtZnVuY3Rpb259ClVNQVBfcGxvdF93cmFwcGVyIDwtIGZ1bmN0aW9uKHNjZSA9IG5vcm1hbGl6ZWRfc2NlLCBubl9wYXJhbSA9IDE1KSB7CiAgIyBQdXJwb3NlOiBSdW4gVU1BUCBhbmQgcGxvdCB0aGUgb3V0cHV0CiAgIyBBcmdzOiBubl9wYXJhbTogYSBzaW5nbGUgbnVtZXJpYyBhcmd1bWVudCB0aGF0IHdpbGwgY2hhbmdlIHRoZQogICMgICAgICAgICAgICAgICAgIG5fbmVpZ2hib3JzIHZhcmlhYmxlIGluIHRoZSBjYWxjdWxhdGVVTUFQKCkgZnVuY3Rpb24uCiAgIyBPdXRwdXQ6IGEgc2NhdHRlcnBsb3Qgd2l0aCB0aGUgdHdvIFVNQVAgY29vcmRpbmF0ZXMgcGxvdHRlZCBhbmQKICAjICAgICAgICAgY2VsbC10eXBlcyBsYWJlbGVkIHdpdGggZGF0YSBwb2ludCBjb2xvcnMuCgogICMgUnVuIFVNQVAgd2l0aCBhIHNwZWNpZmllZCBuX25laWdoYm9ycyBwYXJhbWV0ZXIKICBzY2VfdW1hcCA8LSBzY2F0ZXI6OnJ1blVNQVAoc2NlLCBkaW1yZWQgPSAiUENBIiwgbl9uZWlnaGJvcnMgPSBubl9wYXJhbSkKICBzY2F0ZXI6OnBsb3RSZWR1Y2VkRGltKHNjZV91bWFwLCAiVU1BUCIsIGNvbG9yX2J5ID0gImRldGVjdGVkIikgKwogICAgIyBtYWtlIHRoZSBsZWdlbmQgbGFiZWwgbW9yZSBpbmZvcm1hdGl2ZSAodGhpcyBpcyBnZ3Bsb3QyIGNvZGUhKQogICAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfY29sb3JiYXIodGl0bGU9ImdlbmVzXG5leHByZXNzZWQiKSkKfQpgYGAKCkxldCdzIG1ha2Ugc3VyZSB0aGF0IHdvcmtzIGFuZCBnaXZlcyB0aGUgc2FtZSByZXN1bHQgYXMgYmVmb3JlIHdoZW4gd2UgdXNlIHRoZSBkZWZhdWx0IHBhcmFtZXRlcnMuCgpgYGB7ciBmdW5jdGlvbi10ZXN0fQpVTUFQX3Bsb3Rfd3JhcHBlcihubl9wYXJhbSA9IDE1KQpgYGAKCipLaW5kIG9mPyoKClRoaXMgaXNuJ3QgeW91ciBmYXVsdCEKVU1BUCBpcyBhIG5vbi1kZXRlcm1pbmlzdGljIGZ1bmN0aW9uLCB3aGljaCBtZWFucyB0aGF0IHRoZXJlIGlzIGEgcmFuZG9tIGNvbXBvbmVudCB0byB0aGUgcmVzdWx0cy4KV2UgY2FuIHVzZSBgc2V0LnNlZWQoKWAgdG8gYmUgc3VyZSB0aGF0IGFuIGluZGl2aWR1YWwgcnVuIChvciBzZXQgb2YgcnVucykgaXMgdGhlIHNhbWUgZXZlcnkgdGltZSB5b3UgcnVuIHlvdXIgYW5hbHlzaXMsIGJ1dCBpdCBpcyBpbXBvcnRhbnQgdG8gY2hlY2sgeW91ciByZXN1bHRzIGEgZmV3IHRpbWVzIHdpdGggZGlmZmVyZW50IHJhbmRvbSBzdGFydGluZyBwb2ludHMgdG8gYmUgc3VyZSB0aGF0IHRoZSByYW5kb20gY29tcG9uZW50IGlzIG5vdCBnaXZpbmcgeW91IGFub21hbG91cyByZXN1bHRzLgpTZXR0aW5nIGEgZGlmZmVyZW50IHJhbmRvbSBudW1iZXIgc2VlZCB3aXRoIGBzZXQuc2VlZCgpYCBpcyBvbmUgd2F5IHRvIGRvIHRoaXMsIG9yIHlvdSBjYW4gcnVuIHRoZSBhbmFseXNpcyBtdWx0aXBsZSB0aW1lcyBpbiB0aGUgc2FtZSBzZXNzaW9uLCBhcyB3ZSBoYXZlIGRvbmUgaGVyZS4KCkZpbGwgaW4gdGhlIG5leHQgZmV3IGNvZGUgY2h1bmtzIHdpdGggdGhlIGZ1bmN0aW9uIGFuZCB0aGUgYG5fbmVpZ2hib3JzYCBhcmd1bWVudCB5b3Ugd291bGQgbGlrZSB0byB1c2UgZm9yIGVhY2guCihGZWVsIGZyZWUgdG8gYWRkIG1vcmUgdGVzdHMhKQpUaGVuIHJ1biB0aGUgY2h1bmtzIGFuZCBjb21wYXJlIHlvdXIgb3V0cHV0IGdyYXBocy4KCmBgYHtyIHJ1bi1VTUFQLTEsIGxpdmUgPSBUUlVFfQojIFRyeSBzb21ldGhpbmcgbG93PwpVTUFQX3Bsb3Rfd3JhcHBlcihubl9wYXJhbSA9IDMpCmBgYAoKYGBge3IgcnVuLVVNQVAtMiwgbGl2ZSA9IFRSVUV9CiMgVHJ5IHNvbWV0aGluZyBoaWdoPwpVTUFQX3Bsb3Rfd3JhcHBlcihubl9wYXJhbSA9IDEwMCkKYGBgCgpgYGB7ciBydW4tVU1BUC0zLCBsaXZlID0gVFJVRX0KIyBUcnkgd2hhdGV2ZXIgeW91IGxpa2UhClVNQVBfcGxvdF93cmFwcGVyKG5uX3BhcmFtID0gNSkKYGBgCgojIyMjIFNvbWUgJ2JpZyBwaWN0dXJlJyB0aG91Z2h0cyB0byB0YWtlIGZyb20gdGhpcyBleHBlcmltZW50OgoKMS4gQW5hbHlzZXMgc3VjaCBhcyBVTUFQIGhhdmUgdmFyaW91cyBsaW1pdGF0aW9ucyBmb3IgaW50ZXJwcmV0YWJpbGl0eS4KVGhlIGNvb3JkaW5hdGVzIG9mIFVNQVAgb3V0cHV0IGZvciBhbnkgZ2l2ZW4gY2VsbCBjYW4gY2hhbmdlIGRyYW1hdGljYWxseSBkZXBlbmRpbmcgb24gcGFyYW1ldGVycywgYW5kIGV2ZW4gcnVuIHRvIHJ1biB3aXRoIHRoZSBzYW1lIHBhcmFtZXRlcnMuClRoaXMgcHJvYmFibHkgbWVhbnMgdGhhdCB5b3Ugc2hvdWxkbid0IHJlbHkgb24gdGhlIGV4YWN0IHZhbHVlcyBvZiBVTUFQJ3Mgb3V0cHV0LgoKICAgIC0gT25lIHBhcnRpY3VsYXIgbGltaXRhdGlvbiBvZiBVTUFQIChhbmQgdC1TTkUpIGlzIHRoYXQgd2hpbGUgb2JzZXJ2ZWQgY2x1c3RlcnMgaGF2ZSBzb21lIG1lYW5pbmcsIHRoZSBkaXN0YW5jZSAqYmV0d2VlbiogY2x1c3RlcnMgdXN1YWxseSBkb2VzIG5vdCAobm9yIGRvZXMgY2x1c3RlciBkZW5zaXR5KS4KICAgIFRoZSBmYWN0IHRoYXQgdHdvIGNsdXN0ZXJzIGFyZSBuZWFyIGVhY2ggb3RoZXIgc2hvdWxkIE5PVCBiZSBpbnRlcnByZXRlZCB0byBtZWFuIHRoYXQgdGhleSBhcmUgbW9yZSByZWxhdGVkIHRvIGVhY2ggb3RoZXIgdGhhbiB0byBtb3JlIGRpc3RhbnQgY2x1c3RlcnMuCiAgICAoVGhlcmUgaXMgc29tZSBkaXNhZ3JlZW1lbnQgYWJvdXQgd2hldGhlciBVTUFQIGRpc3RhbmNlcyBoYXZlIG1vcmUgbWVhbmluZywgYnV0IGl0IGlzIHByb2JhYmx5IHNhZmVyIHRvIGFzc3VtZSB0aGV5IGRvbid0LikKCgoyLiBQbGF5aW5nIHdpdGggcGFyYW1ldGVycyBzbyB5b3UgY2FuIGZpbmUtdHVuZSB0aGVtIGlzIGEgZ29vZCB3YXkgdG8gZ2l2ZSB5b3UgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCBhIHBhcnRpY3VsYXIgYW5hbHlzaXMgYXMgd2VsbCBhcyB0aGUgZGF0YSBpdHNlbGYuCgozLiBXaGVyZSByZXN1bHRzIGFyZSBjb25zaXN0ZW50LCB0aGV5IGFyZSBtb3JlIGxpa2VseSB0byBoYXZlIG1lYW5pbmcuCldoaWxlIHdlIGRvIG5vdCBoYXZlIGxhYmVsZWQgY2VsbCB0eXBlcyBpbiB0aGlzIGNhc2UsIHRoZXJlIGRvZXMgc2VlbSB0byBiZSBzb21lIGNvbnNpc3RlbmN5IG9mIHRoZSBvdmVyYWxsIHBhdHRlcm5zIHRoYXQgd2Ugc2VlIChpZiBub3QgcHJlY2lzZSB2YWx1ZXMpLCBhbmQgdGhpcyBsaWtlbHkgcmVmbGVjdHMgYmlvbG9naWNhbCBpbmZvcm1hdGlvbiAob3IgdGVjaG5pY2FsIGFydGlmYWN0cykuCgpJbiBzdW1tYXJ5LCBpZiB0aGUgcmVzdWx0cyBvZiBhbiBhbmFseXNpcyBjYW4gYmUgY29tcGxldGVseSBjaGFuZ2VkIGJ5IGNoYW5naW5nIGl0cyBwYXJhbWV0ZXJzLCB5b3Ugc2hvdWxkIGJlIG1vcmUgY2F1dGlvdXMgd2hlbiBpdCBjb21lcyB0byB0aGUgY29uY2x1c2lvbnMgeW91IGRyYXcgZnJvbSBpdCBhcyB3ZWxsIGFzIGhhdmluZyBnb29kIHJhdGlvbmFsZSBmb3IgdGhlIHBhcmFtZXRlcnMgeW91IGNob29zZS4KCiMjIyB0LVNORSBjb21wYXJpc29uCgpJbiB0aGUgYmxvY2sgYmVsb3cgaXMgYSBzaW1pbGFyIGFuYWx5c2lzIGFuZCBwbG90IHdpdGggdC1TTkUgKHQtZGlzdHJpYnV0ZWQgU3RvY2hhc3RpYyBOZWlnaGJvciBFbWJlZGRpbmcpLgpOb3RlIHRoYXQgdGhpcyBhbmFseXNpcyBhbHNvIHVzZXMgUENBIGJlZm9yZSBtb3Zpbmcgb24gdG8gdGhlIGZhbmN5IG1hY2hpbmUgbGVhcm5pbmcuCgpgYGB7ciB0c25lLCBsaXZlID0gVFJVRX0KIyBSdW4gVFNORQpub3JtYWxpemVkX3NjZSA8LSBydW5UU05FKG5vcm1hbGl6ZWRfc2NlLCBkaW1yZWQgPSAiUENBIikKCiMgcGxvdCB3aXRoIHNjYXRlciBmdW5jdGlvbgpwbG90UmVkdWNlZERpbShub3JtYWxpemVkX3NjZSwgIlRTTkUiLCBjb2xvcl9ieSA9ICJkZXRlY3RlZCIpCmBgYAoKRGlmZmVyZW50ISAoU2xvd2VyISkgSXMgaXQgYmV0dGVyIG9yIHdvcnNlPyBIYXJkIHRvIHNheSEKRGlmZmVyZW50IHBlb3BsZSBsaWtlIGRpZmZlcmVudCB0aGluZ3MsIGFuZCBvbmUgcGxvdCBtaWdodCBpbGx1c3RyYXRlIGEgcGFydGljdWxhciBwb2ludCBiZXR0ZXIgdGhhbiBhbm90aGVyLgoKIyMgU2F2ZSByZXN1bHRzCgpXZSBhcmUgZ29pbmcgdG8gdXNlIHRoaXMgZGF0YSBtb3JlIGluIHRoZSBuZXh0IG5vdGVib29rLCBzbyBsZXQncyBzYXZlIGl0IGFzIGFuIGBSRFNgIGZpbGUuCgpgYGB7ciBzYXZlfQpyZWFkcjo6d3JpdGVfcmRzKG5vcm1hbGl6ZWRfc2NlLCBmaWxlID0gb3V0cHV0X3NjZV9maWxlKQpgYGAKCgojIyMgU29tZSBmdXJ0aGVyIHJlYWRpbmcgb24gZGltZW5zaW9uIHJlZHVjdGlvbjoKCi0gVGhpcyB3ZWJzaXRlIGV4cGxhaW5zIFtQQ0EgdmlzdWFsbHldKGh0dHA6Ly9zZXRvc2EuaW8vZXYvcHJpbmNpcGFsLWNvbXBvbmVudC1hbmFseXNpcy8pLgotIFtCZWNodCAqZXQgYWwuKiAoMjAxOCldKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvbmJ0LjQzMTQpIGRpc2N1c3NlcyB1c2luZyBbVU1BUF0oaHR0cHM6Ly9naXRodWIuY29tL2xtY2lubmVzL3VtYXApIGZvciBzaW5nbGUtY2VsbCBkYXRhLgotIFtXYXR0ZW5iZXJnICpldCBhbC4qICgyMDE2KV0oaHR0cHM6Ly9kaXN0aWxsLnB1Yi8yMDE2L21pc3JlYWQtdHNuZS8pIGRpc2N1c3MgaG93IHRvIHVzZSB0LVNORSBwcm9wZXJseSB3aXRoIGdyZWF0IHZpc3VhbHMuCihUaGUgbGVzc29ucyBhcHBseSB0byBVTUFQIGFzIHdlbGwsIHdpdGggYSBicm9hZCBzdWJzdGl0dXRpb24gb2YgdGhlIGBuX25laWdoYm9yc2AgcGFyYW1ldGVyIGZvciBgcGVycGxleGl0eWAuKQotIFtOZ3V5ZW4gJiBIb2xtZXMgKDIwMTkpXShodHRwczovL2pvdXJuYWxzLnBsb3Mub3JnL3Bsb3Njb21wYmlvbC9hcnRpY2xlP2lkPTEwLjEzNzEvam91cm5hbC5wY2JpLjEwMDY5MDcpIGxheSBvdXQgZ3VpZGVsaW5lcyBvbiBjaG9vc2luZyBkaW1lbnNpb25zIHJlZHVjdGlvbiBtZXRob2RzLgotIFtGcmVpdGFnICgyMDE5KV0oaHR0cHM6Ly9ycHVicy5jb20vU2Fza2lhLzUyMDIxNikgaXMgYSBuaWNlIGV4cGxhbmF0aW9uIGFuZCBjb21wYXJpc29uIG9mIG1hbnkgZGlmZmVyZW50IGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiB0ZWNobmlxdWVzIHRoYXQgeW91IG1heSBlbmNvdW50ZXIuCgoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9ufQpzZXNzaW9uSW5mbygpCmBgYAo=