Objectives

This notebook will demonstrate how to:

  • Prepare SCE objects for integration
  • Apply integration methods including fastMNN and harmony
  • Visually explore the results of integration
  • Use purrr::map() functions for iterating over lists

In this notebook, we’ll perform integration on scRNA-seq datasets from the Single-cell Pediatric Cancer Atlas (ScPCA), a database of uniformly-processed pediatric scRNA-seq data built and maintained by the Data Lab. The ScPCA database currently hosts single-cell pediatric cancer transcriptomic data generated by ALSF-funded labs, with the goal of making this data easily accessible to investigators (like you!). The expression data in ScPCA were mapping and quantified with alevin-fry, followed by processing with Bioconductor tools using the same general procedures that we have covered in this workshop. The processing pipeline used emptyDropsCellRanger() and miQC to filter the raw counts matrix, scuttle to log-normalize the counts, and scater for dimension reduction. The processed data are stored as .rds files containing SingleCellExperiment objects. You can read more about how data in the ScPCA is processed in the associated documentation.

Single-cell roadmap: Integration Overview
Single-cell roadmap: Integration Overview

To learn about integration, we’ll have a look at four samples from the SCPCP000005 project (Patel et al. 2022), an investigation of pediatric solid tumors led by the Dyer and Chen labs at St. Jude Children’s Research Hospital. The particular libraries we’ll integrate come from two rhabdomyosarcoma (RMS) patients, with two samples from each of two patients, all sequenced with 10x Chromium v3 technology. Each library is from a separate biological sample.

We’ll be integrating these samples with two different tools, fastMNN (Haghverdi et al. 2018) and harmony (Korsunsky et al. 2019). Integration corrects for batch effects that arise from different library preparations, genetic backgrounds, and other sample-specific factors, so that datasets can be jointly analyzed at the cell level. fastMNN corrects for batch effects using a faster variant of the mutual-nearest neighbors algorithm, the technical details of which you can learn more about from this vignette by Lun (2019). harmony, on the other hand, corrects for batch effects using an iterative clustering approach, and unlike fastMNN, it is also able to consider additional covariates beyond just the batch groupings.

Regardless of which integration tool is used, the SingleCellExperiment (SCE) objects first need to be reformatted and merged into a single (uncorrected!) SCE object that contains all cells from all samples. This merged SCE can then be used for integration to obtain a formally batch-corrected SCE object.

Set up

# Load libraries
library(ggplot2)  # plotting tools
library(SingleCellExperiment) # work with SCE objects
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'
# Set the seed for reproducibility
set.seed(12345)

Define directories and files

We have already prepared count data for the four samples we’ll be integrating (i.e., filtered cells, normalized counts, and calculated PCA & UMAP). These SCE objects, stored as RDS files, are available in the data/rms/processed/ directory and are named according to their ScPCA library ids :

  • SCPCL000479.rds (Patient A)
  • SCPCL000480.rds (Patient A)
  • SCPCL000481.rds (Patient B)
  • SCPCL000482.rds (Patient B)

Both Patient A (18 year old male) and Patient B (4 year old female) had recurrent embryonal rhabdomyosarcoma when samples were taken.

To begin, let’s set up our directories and files:

# Define directory where processed SCE objects to be integrated are stored
input_dir <- file.path("data", "rms", "processed")

# Define directory to save integrated SCE object to
output_dir <- file.path("data", "rms", "integrated")

# Create output directory if it doesn't exist
fs::dir_create(output_dir)

# Define output file name for the integrated object
integrated_sce_file <- file.path(output_dir, "rms_integrated_subset.rds")

We can use the dir() function to list all contents of a given directory, for example to see all the files in our input_dir:

dir(input_dir)
 [1] "SCPCL000478.rds" "SCPCL000479.rds" "SCPCL000480.rds" "SCPCL000481.rds"
 [5] "SCPCL000482.rds" "SCPCL000483.rds" "SCPCL000484.rds" "SCPCL000488.rds"
 [9] "SCPCL000491.rds" "SCPCL000492.rds" "SCPCL000495.rds" "SCPCL000498.rds"
[13] "SCPCL000502.rds" "SCPCL000510.rds" "SCPCL000516.rds"
SCPCL000478.rds
SCPCL000479.rds
SCPCL000480.rds
SCPCL000481.rds
SCPCL000482.rds
SCPCL000483.rds
SCPCL000484.rds
SCPCL000488.rds
SCPCL000491.rds
SCPCL000492.rds
SCPCL000495.rds
SCPCL000498.rds
SCPCL000502.rds
SCPCL000510.rds
SCPCL000516.rds

We want to read in just four of these files, as listed previously. To read in these files, we could use the readr::read_rds() function (or the base R readRDS()) four times, once for each of the files. We could also use a for loop, which is the approach that many programming languages would lean toward. A different and more modular coding approach to reading in these files (and more!) is to leverage the purrr tidyverse package, which provides a convenient set of functions for operating on lists. You can read more about the purrr functions and their power and utility in R in the “Functionals” chapter of the Advanced R e-book.

Of particular interest is the purrr::map() family of functions, which can be used to run a given function on each element of a list (or vector) in one call. The general syntax for purrr::map() and friends is:

# Syntax for using the map function:
purrr::map(<input list or vector>,
           <function to apply to each item in the input>,
           <any additional arguments to the function can go here>,
           <and also here if there are even more arguments, and so on>)

The output from running purrr::map() is always a list (but note that there are other purrr::map() relatives which return other object types, as you can read about in the purrr::map() documentation). If this concept sounds a little familiar to you, that’s because it probably is! Base R’s lapply() function can provide similar utility, and the purrr::map() family of functions can (in part) be thought of as an alternative to some of the base R apply functions, with more consistent behavior.

Let’s see a very simple example of purrr::map() in action, inspired by cancer groups the Data Lab has analyzed through the OpenPBTA project:

# Define a list of cancer histologies
histologies <- list(
  "low-grade gliomas"  = c("SEGA", "PA", "GNG", "PXA"),
  "high-grade gliomas" = c("DMG", "DIPG"),
  "embryonal tumors"   = c("MB", "ATRT", "ETMR")
 )

# The overall length of the list is 3
length(histologies)
[1] 3
# How can we run `length()` on each item of the list?
# We can use our new friend purrr::map():
purrr::map(histologies, length)
$`low-grade gliomas`
[1] 4

$`high-grade gliomas`
[1] 2

$`embryonal tumors`
[1] 3

Let’s use purrr::map() to read in our SCE objects so that they are immediately stored together in a list.

We’ll first need to define a vector of the file paths to read in. We’ll start by creating a vector of sample names themselves and then formatting them into the correct paths. This way (foreshadowing!) we also have a stand-alone vector of just sample names, which will come in handy!

# Vector of all the samples to read in:
sample_names <- c("SCPCL000479",
                  "SCPCL000480",
                  "SCPCL000481",
                  "SCPCL000482")
# Now, convert these to file paths: <input_dir>/<sample_name>.rds
sce_paths <- file.path(input_dir,
                       glue::glue("{sample_names}.rds")
)
# Print the sce_paths vector
sce_paths
[1] "data/rms/processed/SCPCL000479.rds" "data/rms/processed/SCPCL000480.rds"
[3] "data/rms/processed/SCPCL000481.rds" "data/rms/processed/SCPCL000482.rds"
data/rms/processed/SCPCL000479.rds
data/rms/processed/SCPCL000480.rds
data/rms/processed/SCPCL000481.rds
data/rms/processed/SCPCL000482.rds

Let’s make this a named vector using the sample names. This will help us keep track of which objects are which after we read the SCE objects in:

# Assign the sample names as the names for sce_paths
names(sce_paths) <- sample_names

We can now read these files in and create a list of four SCE objects. Since readr::read_rds() can only operate on one input at a time, we’ll need to use purrr::map() to run it on all input file paths in one command. Although sce_paths is a vector (not a list), it will still work as input to purrr:map(). The output from this code will still be a list, since that’s what purrr::map() always returns, and it will retain the sample names as the list names for convenient bookkeeping:

# Use purrr::map() to read all files into a list at once
sce_list <- purrr::map(
  sce_paths,
  readr::read_rds
)

Let’s have a look at our named list of SCE objects:

# Print sce_list
sce_list
$SCPCL000479
class: SingleCellExperiment 
dim: 60319 1918 
metadata(14): salmon_version reference_index ... filtering_method
  miQC_model
assays(2): counts logcounts
rownames(60319): ENSG00000000003 ENSG00000000005 ... ENSG00000288724
  ENSG00000288725
rowData names(3): gene_symbol mean detected
colnames(1918): GGGACCTCAAGCGGAT CACAGATAGTGAGTGC ... GTTGTCCCACGTACAT
  TCCGATCGTCGTGCCA
colData names(12): sum detected ... celltype_fine celltype_broad
reducedDimNames(2): PCA UMAP
mainExpName: NULL
altExpNames(0):

$SCPCL000480
class: SingleCellExperiment 
dim: 60319 4428 
metadata(14): salmon_version reference_index ... filtering_method
  miQC_model
assays(2): counts logcounts
rownames(60319): ENSG00000000003 ENSG00000000005 ... ENSG00000288724
  ENSG00000288725
rowData names(3): gene_symbol mean detected
colnames(4428): TAGGGTTAGCAAGCCA AACTTCTTCCCTCAAC ... AGGGAGTAGCCTCATA
  TCGGATACATTGCAAC
colData names(12): sum detected ... celltype_fine celltype_broad
reducedDimNames(2): PCA UMAP
mainExpName: NULL
altExpNames(0):

$SCPCL000481
class: SingleCellExperiment 
dim: 60319 5236 
metadata(14): salmon_version reference_index ... filtering_method
  miQC_model
assays(2): counts logcounts
rownames(60319): ENSG00000000003 ENSG00000000005 ... ENSG00000288724
  ENSG00000288725
rowData names(3): gene_symbol mean detected
colnames(5236): TCATGAGAGGCCCACT GGGTATTTCGTTGTGA ... AAAGAACCACTTCAAG
  CAGCAGCTCGTGCATA
colData names(12): sum detected ... celltype_fine celltype_broad
reducedDimNames(2): PCA UMAP
mainExpName: NULL
altExpNames(0):

$SCPCL000482
class: SingleCellExperiment 
dim: 60319 4372 
metadata(14): salmon_version reference_index ... filtering_method
  miQC_model
assays(2): counts logcounts
rownames(60319): ENSG00000000003 ENSG00000000005 ... ENSG00000288724
  ENSG00000288725
rowData names(3): gene_symbol mean detected
colnames(4372): GAGAAATCAGATTAAG CAACCTCTCCGATCGG ... TGATTTCCACAAGTTC
  ACCAAACGTTCTCAGA
colData names(12): sum detected ... celltype_fine celltype_broad
reducedDimNames(2): PCA UMAP
mainExpName: NULL
altExpNames(0):

If you look closely at the printed SCE objects, you may notice that they all contain colData table columns celltype_fine and celltype_broad. These columns (which we added to SCE objects during pre-processing) contain putative cell type annotations as assigned by Patel et al. (2022):

For each cell subset identified by clustering, we used a combination of SingleR version 1.0.1 (Aran et al., 2019) and manual inspection of differentially expressed genes to annotate whether a cluster belongs to stromal, immune or malignant subpopulations. Malignant cells were confirmed in patient tumor data by inference of copy-number variation using inferCNV version 1.1.3 of the TrinityCTAT Project (https://github.com/broadinstitute/infercnv).

We will end up leveraging these cell type annotations to explore the integration results; after integration, we expect cell types from different samples to group together, rather than being separated by batches.

That said, the integration methods we will be applying do not actually use any existing cell type annotations. If we have annotations, they are a helpful “bonus” for assessing the integration’s performance, but they are not part of the integration itself.

Merge the SCE list into one object

Single-cell roadmap: Merge
Single-cell roadmap: Merge

Now that we have a list of processed SCE objects, we need to merge the objects into one overall SCE object for input to integration. A word of caution before we begin: This merged SCE object is NOT an integrated SCE! Merging SCEs does not perform any batch correction, but just reorganizes the data to allow us to proceed to integration next.

To merge SCE objects, we do need to do some wrangling and bookkeeping to ensure compatibility and that we don’t lose important information. Overall, we’ll want to make sure that:

  1. All objects have compatible dimensions. This means that all objects should…
    • Have the same genes (aka row names), in the same order
    • Have the same colData slot columns, in the same order
    • Have the same assays
  2. After merging, we’ll still be able to identify which sample different pieces of information came from As we saw in the slides, this means we’ll have to…
    • Attach sample names to the barcodes (aka column names) This also ensures that column names are unique; while a single sample (library) is guaranteed to have unique barcodes, technically they can be repeated across samples!
    • Attach sample names to rowData slot column names and metadata field names (if you care to keep this information around - today, we will!)
    • Add a new column indicating the sample to the colData slot

We’ll approach this merge in two parts:

  • First, we’ll take some time to thoroughly explore the our SCE objects to determine what wrangling we need to do to make all the objects compatible for merging
  • Then, we’ll write (ok, we’ve written it for you) a custom function to format each SCE object for merging, including:
    • Making any changes to ensure objects are compatible
    • Adding in identifying information so we know which sample the cells and other metadata came from
    • Removing the existing reduced dimension matrices (PCA and UMAP). This is because we’ll want to recalculate these matrices on the merged objects, taking batch into account

When merging objects on your own, don’t skip these data exploration steps! The steps we take to prepare our SCEs will probably be different from the steps you need to take with other SCEs, and only by carefully exploring the objects can you figure out what steps you’ll need to take to meet all of our conditions.

Prepare to merge SCEs

Create unique cell identifiers

As part of the custom function we’ll write, we’ll include a step to create unique cell identifiers by attaching sample names to the SCE column names (cell barcodes). For example, we would update the column name for a cell from Sample1 with the barcode ACGT to Sample1-ACGT.

When merging, there can’t be any duplicate column names (barcodes) across all the objects or R will throw an error. While you’re guaranteed to have unique barcodes in a given SCE object, there is no guarantee that they are unique across multiple samples - it is absolutely possible to have cells from two different samples share the same barcode (and we’ve seen it happen!).

Adding the sample id to the column names (barcodes) is therefore a crucial step in our merging bookkeeping.

Explore the SCE objects

Check the genes

First, we’ll compare the object’s genes (aka, their row names). We can use some purrr magic to help us find the set of shared genes among all objects:

# Define vector of shared genes
shared_genes <- sce_list |>
  # get rownames (genes) for each SCE in sce_list
  purrr::map(rownames) |>
  # reduce to the _intersection_ among lists
  purrr::reduce(intersect)

# How many shared genes are there?
length(shared_genes)
[1] 60319

That’s quite a lot! In fact, because these objects were all uniformly processed by the same workflow (which did not filter out any genes!), we expect them to all have the same genes. We can map over the list to confirm that indeed, they have the same number of rows (genes):

# The number of genes in an SCE corresponds to its number of rows:
sce_list |>
  purrr::map(nrow)
$SCPCL000479
[1] 60319

$SCPCL000480
[1] 60319

$SCPCL000481
[1] 60319

$SCPCL000482
[1] 60319

Even though we know the genes already match, we need to also be sure they are in the same order among all objects. So, we’ll hold onto that shared_genes variable we defined and use it soon in our custom formatting function to make sure all objects fully match.

It’s worth noting that the intersection isn’t the only option here, though! Using the intersection means a lot of genes will get discarded if the objects have different genes. We could instead take the union of genes so nothing gets thrown out. In this case, you’d need to create “dummy” assay rows for genes that a given SCE doesn’t have and fill it with NA expression values. You’ll still have to make sure the SCEs have the same rows in the same order before merging, so you may need to do a decent bit of matrix wrangling.

Check the colData column names

Next up, we’ll check the colData columns: we need these to be the same, and in the same order. Let’s print out each object’s colData column name to see where we stand:

sce_list |>
  purrr::map(
    \(sce) colnames(colData(sce)) 
  ) 
$SCPCL000479
 [1] "sum"                   "detected"              "subsets_mito_sum"     
 [4] "subsets_mito_detected" "subsets_mito_percent"  "total"                
 [7] "prob_compromised"      "miQC_pass"             "sizeFactor"           
[10] "barcode"               "celltype_fine"         "celltype_broad"       

$SCPCL000480
 [1] "sum"                   "detected"              "subsets_mito_sum"     
 [4] "subsets_mito_detected" "subsets_mito_percent"  "total"                
 [7] "prob_compromised"      "miQC_pass"             "sizeFactor"           
[10] "barcode"               "celltype_fine"         "celltype_broad"       

$SCPCL000481
 [1] "sum"                   "detected"              "subsets_mito_sum"     
 [4] "subsets_mito_detected" "subsets_mito_percent"  "total"                
 [7] "prob_compromised"      "miQC_pass"             "sizeFactor"           
[10] "barcode"               "celltype_fine"         "celltype_broad"       

$SCPCL000482
 [1] "sum"                   "detected"              "subsets_mito_sum"     
 [4] "subsets_mito_detected" "subsets_mito_percent"  "total"                
 [7] "prob_compromised"      "miQC_pass"             "sizeFactor"           
[10] "barcode"               "celltype_fine"         "celltype_broad"       
sum
detected
subsets_mito_sum
subsets_mito_detected
subsets_mito_percent
total
prob_compromised
miQC_pass
sizeFactor
barcode
celltype_fine
celltype_broad
sum
detected
subsets_mito_sum
subsets_mito_detected
subsets_mito_percent
total
prob_compromised
miQC_pass
sizeFactor
barcode
celltype_fine
celltype_broad
sum
detected
subsets_mito_sum
subsets_mito_detected
subsets_mito_percent
total
prob_compromised
miQC_pass
sizeFactor
barcode
celltype_fine
celltype_broad
sum
detected
subsets_mito_sum
subsets_mito_detected
subsets_mito_percent
total
prob_compromised
miQC_pass
sizeFactor
barcode
celltype_fine
celltype_broad

We see the same columns all around in the same order, which is great!

But what if there were different columns across objects, or they were differently ordered? In that case, we could find the intersection of column names like we did above for genes, and use that to re-order and subset all colData slots in our custom formatting function.

Check the assays

Next, we’ll make sure that all objects share the same assays:

# print all the assay names
sce_list |>
  purrr::map(assayNames)
$SCPCL000479
[1] "counts"    "logcounts"

$SCPCL000480
[1] "counts"    "logcounts"

$SCPCL000481
[1] "counts"    "logcounts"

$SCPCL000482
[1] "counts"    "logcounts"
counts
logcounts
counts
logcounts
counts
logcounts
counts
logcounts

Again, all objects are compatible already with both having a counts and logcounts assay.

In your own data exploration, if you encounter SCEs to merge that have extraneous assays that you don’t need, you can remove them by setting them to NULL in your custom formatting function, e.g. assay(sce, "assay_to_remove") <- NULL.

Check the rowData contents

One of the other items we said we’d need to think about is the rowData, which contains gene metadata. This slot is interesting because some of its columns are specific to the given sample, while others are general:

sce_list |>
  purrr::map(
    \(sce) head(rowData(sce), 3) # only print 3 rows for space!
  )
$SCPCL000479
DataFrame with 3 rows and 3 columns
                gene_symbol      mean  detected
                <character> <numeric> <numeric>
ENSG00000000003      TSPAN6 0.0177202  1.639778
ENSG00000000005        TNMD 0.0026448  0.158688
ENSG00000000419        DPM1 0.0729966  6.109495

$SCPCL000480
DataFrame with 3 rows and 3 columns
                gene_symbol       mean  detected
                <character>  <numeric> <numeric>
ENSG00000000003      TSPAN6 0.05209841  4.828312
ENSG00000000005        TNMD 0.00855151  0.828838
ENSG00000000419        DPM1 0.08919879  8.301539

$SCPCL000481
DataFrame with 3 rows and 3 columns
                gene_symbol      mean  detected
                <character> <numeric> <numeric>
ENSG00000000003      TSPAN6 0.0637984  5.989666
ENSG00000000005        TNMD 0.0054835  0.495624
ENSG00000000419        DPM1 0.2401139 20.056944

$SCPCL000482
DataFrame with 3 rows and 3 columns
                gene_symbol      mean  detected
                <character> <numeric> <numeric>
ENSG00000000003      TSPAN6 0.0289113  2.659838
ENSG00000000005        TNMD 0.0100776  0.759954
ENSG00000000419        DPM1 0.1391046 11.217578

The column gene_symbol is not sample-specific - it just provides the corresponding gene symbol to the Ensembl ids seen here as row names. The columns mean and detected, however, are sample-specific - they contain sample-specific statistics about gene expression.

This means we definitely need to update the column names mean and detected to include the sample id. But, we don’t need a separate gene_symbol column for each sample, so we can leave that one alone as just gene_symbol. Once we eventually merge, only one gene_symbol column will be left in the final object since it is the same across all the SCEs.

We’ll show one way to do this in our custom function, but it’s worth noting there’s nothing wrong with also adding the sample id to the gene_symbol column; you’ll just end up with a bunch of redundant gene symbol columns.

Reformat the SCE objects

As you can see, there’s a lot of moving parts to consider! Again, these moving parts may (will!) differ for SCEs that you are working with, so you have to explore your own SCEs in depth to prepare for merging.

Based on our exploration, here is a schematic of how one of the SCE objects will ultimately be modified into the final merged SCE:

We’ll write a custom function (seen in the chunk below) tailored to our wrangling steps that prepares a single SCE object for merging. We’ll then use our new purrr::map() programming skills to run this function over the sce_list. This will give us a new list of formatted SCEs that we can proceed to merge. It’s important to remember that the format_sce() function written below is not a function for general use – it’s been precisely written to match the processing we need to do for these SCEs, and different SCEs you work with will require different types of processing.

We also include roxygen-style comments for this function, which can be a helpful consistent way to document your code if you like it - we’ve even written a blog post about it :) (https://www.ccdatalab.org/blog/dont-make-me-write-tips-for-avoiding-typing-in-rstudio).

#' Custom function to format an SCE before merging
#'
#' @param sce SCE object to format
#' @param sample_name Name of the sample
#' @param shared_genes Vector of shared genes across all SCE objects
#'
#' @returns An updated SCE object ready for merging
format_sce <- function(
  sce, 
  sample_name, 
  shared_genes
) {
  
  ### Remove the single-sample reduced dimensions 
  # We do this first since it makes the object a lot smaller for the rest of this code!
  reducedDims(sce) <- NULL
  
  ### Add dedicated sample indicator column to the colData slot
  # Recall, the `sce$` shortcut points to the colData
  sce$sample <- sample_name

  ### Ensure objects have the same genes in the same order
  # Use the shared_genes vector to index genes to the intersection
  # Doing this both subsets to just those genes, and reorders!
  sce <- sce[shared_genes, ]
  
  ### There is no additional wrangling to do for the colData column names or assays.
  ### But if there were, you could add your custom code to do so here.
  ### Your custom function may need additional arguments for this, too.

  ### Ensure cell ids are identifiable and fully unique 
  # Update the SCE object column names (cell ids) by prepending the `sample_name`
  colnames(sce) <- glue::glue("{sample_name}-{colnames(sce)}")

  ### Ensure the rowData columns can be identified
  # Recall, we want to leave `gene_symbol` alone, but add the `sample_name` to the rest
  rowdata_names <- colnames(rowData(sce))
  # prefix rowData names with the sample name, except for gene symbols
  new_rowdata_names <- ifelse(
    rowdata_names == "gene_symbol",
    "gene_symbol",
    glue::glue("{sample_name}-{rowdata_names}")
  )
  colnames(rowData(sce)) <- new_rowdata_names
  
  ### Ensure metadata slot fields can be identified
  # We'll simply prepend the `sample_name` to all fields for this slot
  names(metadata(sce)) <- glue::glue("{sample_name}-{names(metadata(sce))}")
  
  
  ### Finally, we can return the formatted SCE object
  return(sce)  
}

To run this function, we’ll use the purrr::map2() function, a relative of purrr::map() that allows you to loop over two input lists/vectors. In our case, we want to run format_sce() over paired sce_list items and sce_list names.

# We can use `purrr::map2()` to loop over two list/vector arguments
sce_list_formatted <- purrr::map2(
  # Each "iteration" will march down the first two
  #  arguments `sce_list` and `names(sce_list)` in order
  sce_list,
  names(sce_list),
  \(sce, sample_name) format_sce(sce, sample_name, shared_genes) 
)

# Print formatted SCE list
sce_list_formatted
$SCPCL000479
class: SingleCellExperiment 
dim: 60319 1918 
metadata(14): SCPCL000479-salmon_version SCPCL000479-reference_index
  ... SCPCL000479-filtering_method SCPCL000479-miQC_model
assays(2): counts logcounts
rownames(60319): ENSG00000000003 ENSG00000000005 ... ENSG00000288724
  ENSG00000288725
rowData names(3): gene_symbol SCPCL000479-mean SCPCL000479-detected
colnames(1918): SCPCL000479-GGGACCTCAAGCGGAT
  SCPCL000479-CACAGATAGTGAGTGC ... SCPCL000479-GTTGTCCCACGTACAT
  SCPCL000479-TCCGATCGTCGTGCCA
colData names(13): sum detected ... celltype_broad sample
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):

$SCPCL000480
class: SingleCellExperiment 
dim: 60319 4428 
metadata(14): SCPCL000480-salmon_version SCPCL000480-reference_index
  ... SCPCL000480-filtering_method SCPCL000480-miQC_model
assays(2): counts logcounts
rownames(60319): ENSG00000000003 ENSG00000000005 ... ENSG00000288724
  ENSG00000288725
rowData names(3): gene_symbol SCPCL000480-mean SCPCL000480-detected
colnames(4428): SCPCL000480-TAGGGTTAGCAAGCCA
  SCPCL000480-AACTTCTTCCCTCAAC ... SCPCL000480-AGGGAGTAGCCTCATA
  SCPCL000480-TCGGATACATTGCAAC
colData names(13): sum detected ... celltype_broad sample
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):

$SCPCL000481
class: SingleCellExperiment 
dim: 60319 5236 
metadata(14): SCPCL000481-salmon_version SCPCL000481-reference_index
  ... SCPCL000481-filtering_method SCPCL000481-miQC_model
assays(2): counts logcounts
rownames(60319): ENSG00000000003 ENSG00000000005 ... ENSG00000288724
  ENSG00000288725
rowData names(3): gene_symbol SCPCL000481-mean SCPCL000481-detected
colnames(5236): SCPCL000481-TCATGAGAGGCCCACT
  SCPCL000481-GGGTATTTCGTTGTGA ... SCPCL000481-AAAGAACCACTTCAAG
  SCPCL000481-CAGCAGCTCGTGCATA
colData names(13): sum detected ... celltype_broad sample
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):

$SCPCL000482
class: SingleCellExperiment 
dim: 60319 4372 
metadata(14): SCPCL000482-salmon_version SCPCL000482-reference_index
  ... SCPCL000482-filtering_method SCPCL000482-miQC_model
assays(2): counts logcounts
rownames(60319): ENSG00000000003 ENSG00000000005 ... ENSG00000288724
  ENSG00000288725
rowData names(3): gene_symbol SCPCL000482-mean SCPCL000482-detected
colnames(4372): SCPCL000482-GAGAAATCAGATTAAG
  SCPCL000482-CAACCTCTCCGATCGG ... SCPCL000482-TGATTTCCACAAGTTC
  SCPCL000482-ACCAAACGTTCTCAGA
colData names(13): sum detected ... celltype_broad sample
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):

(Psst, like purrr and want to dive deeper? Check out the purrr::imap() function!)

Perform the merging

At long last, we are ready to merge the SCEs, which we’ll do using the R function cbind(). The cbind() function is often used to combine data frames or matrices by column, i.e. “stack” them next to each other. The same principle applies here, but when run on SCE objects, cbind() will create a new SCE object by combining counts and logcounts matrices by column. Following that structure, other SCE slots (colData, rowData, reduced dimensions, and other metadata) are combined appropriately.

Since we need to apply cbind() to a list of objects, we need to use some slightly-gnarly syntax: We’ll use the function do.call(), which allows the cbind() input to be a list of objects to combine.

# Merge SCE objects
merged_sce <- do.call(cbind, sce_list_formatted)

# Print the merged_sce object
merged_sce
class: SingleCellExperiment 
dim: 60319 15954 
metadata(56): SCPCL000479-salmon_version SCPCL000479-reference_index
  ... SCPCL000482-filtering_method SCPCL000482-miQC_model
assays(2): counts logcounts
rownames(60319): ENSG00000000003 ENSG00000000005 ... ENSG00000288724
  ENSG00000288725
rowData names(9): gene_symbol SCPCL000479-mean ... SCPCL000482-mean
  SCPCL000482-detected
colnames(15954): SCPCL000479-GGGACCTCAAGCGGAT
  SCPCL000479-CACAGATAGTGAGTGC ... SCPCL000482-TGATTTCCACAAGTTC
  SCPCL000482-ACCAAACGTTCTCAGA
colData names(13): sum detected ... celltype_broad sample
reducedDimNames(0):
mainExpName: NULL
altExpNames(0):

We now have a single merged SCE object that contains all cells from all samples we’d like to integrate.

Let’s take a peek at some of the innards of this new SCE object:

# How many samples, and cells per sample?
table(colData(merged_sce)$sample)

SCPCL000479 SCPCL000480 SCPCL000481 SCPCL000482 
       1918        4428        5236        4372 
# What are the new cell ids (column names)?
head(colnames(merged_sce))
[1] "SCPCL000479-GGGACCTCAAGCGGAT" "SCPCL000479-CACAGATAGTGAGTGC"
[3] "SCPCL000479-TGTGGCGGTGAATTGA" "SCPCL000479-GCCGATGGTACATACC"
[5] "SCPCL000479-ATTATCCCAGTTGGTT" "SCPCL000479-TCCGAAATCACACCGG"
SCPCL000479-GGGACCTCAAGCGGAT
SCPCL000479-CACAGATAGTGAGTGC
SCPCL000479-TGTGGCGGTGAATTGA
SCPCL000479-GCCGATGGTACATACC
SCPCL000479-ATTATCCCAGTTGGTT
SCPCL000479-TCCGAAATCACACCGG
tail(colnames(merged_sce))
[1] "SCPCL000482-GATCACACAGCTAACT" "SCPCL000482-GACGCTGAGACTCTAC"
[3] "SCPCL000482-GTGAGGAGTCAACCTA" "SCPCL000482-ATTCCTAGTGTACATC"
[5] "SCPCL000482-TGATTTCCACAAGTTC" "SCPCL000482-ACCAAACGTTCTCAGA"
SCPCL000482-GATCACACAGCTAACT
SCPCL000482-GACGCTGAGACTCTAC
SCPCL000482-GTGAGGAGTCAACCTA
SCPCL000482-ATTCCTAGTGTACATC
SCPCL000482-TGATTTCCACAAGTTC
SCPCL000482-ACCAAACGTTCTCAGA
# What does rowData look like?
head(rowData(merged_sce))
DataFrame with 6 rows and 9 columns
                gene_symbol SCPCL000479-mean SCPCL000479-detected
                <character>        <numeric>            <numeric>
ENSG00000000003      TSPAN6       0.01772018             1.639778
ENSG00000000005        TNMD       0.00264480             0.158688
ENSG00000000419        DPM1       0.07299656             6.109495
ENSG00000000457       SCYL3       0.02300979             1.983602
ENSG00000000460    C1orf112       0.08119545             6.426871
ENSG00000000938         FGR       0.00317376             0.238032
                SCPCL000480-mean SCPCL000480-detected SCPCL000481-mean
                       <numeric>            <numeric>        <numeric>
ENSG00000000003       0.05209841             4.828312       0.06379838
ENSG00000000005       0.00855151             0.828838       0.00548350
ENSG00000000419       0.08919879             8.301539       0.24011389
ENSG00000000457       0.05262465             5.065123       0.13972372
ENSG00000000460       0.10906460             9.656624       0.26911315
ENSG00000000938       0.00197342             0.197342       0.00400717
                SCPCL000481-detected SCPCL000482-mean SCPCL000482-detected
                           <numeric>        <numeric>            <numeric>
ENSG00000000003             5.989666        0.0289113             2.659838
ENSG00000000005             0.495624        0.0100776             0.759954
ENSG00000000419            20.056944        0.1391046            11.217578
ENSG00000000457            12.411684        0.0827689             7.054353
ENSG00000000460            21.206369        0.3092681            19.824880
ENSG00000000938             0.358536        0.0173468             1.387742

Integrate samples

Single-cell roadmap: Integrate
Single-cell roadmap: Integrate

So far, we’ve created a merged_sce object which is (almost!) ready for integration.

The integration methods we’ll be using here actually perform batch correction on a reduced dimension representation of the normalized gene expression values, which is more efficient. fastMNN and harmony specifically use PCA for this, but be aware that different integration methods may use other kinds of reduced dimensions.

Before merging, our objects had reduced dimension representations calculated on each individual SCE, and we removed them when preparing for merge. We removed them because we don’t actually want to use them anymore! This is because part of their calculation involves scaling the raw data to center the mean. When samples are separately centered, all of them will be centered at zero, making it look like the datasets are already pretty overlapping when you plot their UMAPs together. But, this is just a mathematical artifact of how dimension reduction is performed.

So, we’ll begin by re-calculating PCA and UMAP on the merged object in a way that takes batches into consideration. For input to integration, we’ll want the reduced dimension calculations to consider normalized gene expression values from all samples simultaneously. So we’ll need to recalculate PCA (and UMAP for visualization) on the merged object.

First, as usual, we’ll determine the high-variance genes to use for PCA from the merged_sce object. For this, we’ll need to provide the argument block = merged_sce$sample when modeling gene variance, which tells scran::modelGeneVar() to first model variance separately for each batch and then combine those modeling statistics. (Psst: isn’t it handy we created that sample column when merging?!)

# Specify the number of genes to identify
num_genes <- 2000

# Calculate variation for each gene
gene_variance <- scran::modelGeneVar(merged_sce,
                                     # specify the grouping column:
                                     block = merged_sce$sample)

# Get the top `num_genes` high-variance genes to use for dimension reduction
hv_genes <- scran::getTopHVGs(gene_variance,
                              n = num_genes)

To calculate the PCA matrix itself, we’ll use an approach from the batchelor package, which is the R package that contains the fastMNN method. The batchelor::multiBatchPCA() function calculates a batch-weighted PCA matrix. This weighting ensures that all batches, which may have very different numbers of cells, contribute equally to the overall scaling.

# Use batchelor to calculate PCA for merged_sce, considering only
#  the high-variance genes
# We'll need to include the argument `preserve.single = TRUE` to get
#  a single matrix with all samples and not separate matrices for each sample
merged_pca <- batchelor::multiBatchPCA(merged_sce,
                                       subset.row = hv_genes,
                                       batch = merged_sce$sample,
                                       preserve.single = TRUE)

Let’s have a look at the output:

# This output is not very interesting!
merged_pca
List of length 1

We can use indexing [[1]] to see the PCA matrix calculated, looking at a small subset for convenience:

merged_pca[[1]][1:5,1:5]
                                  [,1]      [,2]       [,3]      [,4]      [,5]
SCPCL000479-GGGACCTCAAGCGGAT -8.553693 -7.508773  -8.286024 3.9288287 -3.353172
SCPCL000479-CACAGATAGTGAGTGC -9.741452 -7.712208 -10.310277 4.7186301 -4.271634
SCPCL000479-TGTGGCGGTGAATTGA -9.578361 -6.841931  -6.185011 2.7051343 -3.083831
SCPCL000479-GCCGATGGTACATACC -8.287781 -7.592143 -10.407848 0.2007613 -8.194913
SCPCL000479-ATTATCCCAGTTGGTT -7.035384 -4.938422  -6.042540 0.4756373 -2.014974

We can now include this PCA matrix in our merged_sce object:

# add PCA results to merged SCE object
reducedDim(merged_sce, "PCA") <- merged_pca[[1]]

Now that we have the PCA matrix, we can proceed to calculate UMAP to visualize the uncorrected merged data.

merged_sce <- scater::runUMAP(merged_sce)
# UMAPs scaled together when calculated from the merged SCE
scater::plotUMAP(
  merged_sce,
  color_by = "sample",
  # Some styling to help us see the points:
  point_size = 0.5,
  point_alpha = 0.2
) +
  scale_color_brewer(palette = "Dark2", name = "sample") +
  guides(color = guide_legend(override.aes = list(size = 3, alpha = 1))) +
  ggtitle("UMAP calculated on merged_sce")
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

We see (mostly) four separate clumps representing the four different merged but not yet integrated samples. We can think of this UMAP as our “before” UMAP, and we can compare this to the “after” UMAP we see post-integration.

Let’s discuss a little first: What visual differences do you think the UMAP on the integrated version of data will have? What similarities do you think the integrated UMAP will have to this plot?

Integrate with fastMNN

Finally, we’re ready to integrate! To start, we’ll use the fastMNN approach from the Bioconductor batchelor package.

fastMNN takes as input the merged_sce object to integrate, and the first step it performs is actually to run batchelor::multiBatchPCA() on that SCE. It then uses that batch-weighted PCA matrix to perform the actual batch correction. The batch argument is used to specify the different groupings within the merged_sce (i.e. the original sample that each cell belongs to), and the subset.row argument can optionally be used to provide a vector of high-variance genes that should be considered for this PCA calculation. fastMNN will return an SCE object that contains a batch-corrected PCA. Let’s run it and save the result to a variable called integrated_sce.

# integrate with fastMNN, again specifying only our high-variance genes
integrated_sce <- batchelor::fastMNN(
  merged_sce,
  batch = merged_sce$sample,
  subset.row = hv_genes
)

Let’s have a look at the result:

# Print the integrated_sce object
integrated_sce
class: SingleCellExperiment 
dim: 2000 15954 
metadata(2): merge.info pca.info
assays(1): reconstructed
rownames(2000): ENSG00000278996 ENSG00000157168 ... ENSG00000227232
  ENSG00000258984
rowData names(1): rotation
colnames(15954): SCPCL000479-GGGACCTCAAGCGGAT
  SCPCL000479-CACAGATAGTGAGTGC ... SCPCL000482-TGATTTCCACAAGTTC
  SCPCL000482-ACCAAACGTTCTCAGA
colData names(1): batch
reducedDimNames(1): corrected
mainExpName: NULL
altExpNames(0):

There are couple pieces of information here of interest:

  • The corrected reduced dimension represents the batch-corrected PCA that fastMNN calculated.
  • The reconstructed assay represents the batch-corrected normalized expression values, which fastMNN “back-calculated” from the batch-corrected PCA (corrected). Generally speaking, these expression values are not stand-alone values that you should use for other applications like differential gene expression, as described in Orchestrating Single Cell Analyses. If the subset.row argument is provided (as it was here), only genes present in subset.row will be included in these reconstructed expression values, but this setting can be overridden so that all genes have reconstructed expression with the argument correct.all = TRUE.

We’re mostly interested in the PCA that fastMNN calculated, so let’s save that information (with an informative and unique name) into our merged_sce object:

# Make a new reducedDim named fastmnn_PCA from the corrected reducedDim in integrated_sce
reducedDim(merged_sce, "fastmnn_PCA") <- reducedDim(integrated_sce, "corrected")

Finally, we’ll calculate UMAP from these corrected PCA matrix for visualization. In this case we need to specify two additional arguments since we’re working with non-standard reduced dimension names:

  • dimred = "fastmnn_PCA", which specifies the existing reduced dimension to use for the calculation
  • name = "fastmnn_UMAP", which names the final UMAP that this function calculates
# Calculate UMAP
merged_sce <- scater::runUMAP(
  merged_sce,
  dimred = "fastmnn_PCA",
  name = "fastmnn_UMAP"
)

First, let’s plot the integrated UMAP highlighting the different batches. A well-integrated dataset will show batch mixing, but a poorly-integrated dataset will show more separation among batches, similar to the uncorrected UMAP. Note that this is a more qualitative way to assess the success of integration, but there are formal metrics one can use to assess batch mixing, which you can read more about in this chapter of OSCA.

scater::plotReducedDim(merged_sce,
                       # plot the fastMNN coordinates
                       dimred = "fastmnn_UMAP",
                       # color by sample
                       color_by = "sample",
                       # Some styling to help us see the points:
                       point_size = 0.5,
                       point_alpha = 0.2) +
  scale_color_brewer(palette = "Dark2", name = "sample") +
  guides(color = guide_legend(override.aes = list(size = 3, alpha = 1))) +
  ggtitle("UMAP after integration with fastMNN")
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

This fastmnn_UMAP certainly looks different from the one we made before integrating! What different trends do you see? Do all samples look “equally well” integrated, from a first look?

Importantly, one reason that batches may still appear separated in the corrected UMAP is if they should be separated - for example, maybe two batches contain very different cell types, have very different diagnoses, or may be from different patients.

Recall from earlier that we conveniently have cell type annotations in our SCEs, so we can explore those here! Let’s take a quick detour to see what kinds of cell types are in this data by making a barplot of the cell types across samples:

# Cell types are in the `celltype_broad` and `celltype_fine` columns
merged_sce_df <- as.data.frame(colData(merged_sce))

# Use ggplot2 to make a barplot the cell types across samples
ggplot(merged_sce_df,
       aes(x = sample,
           fill = celltype_broad)) +
  # Barplot of celltype proportions
  geom_bar(position = "fill") +
  # Use a CVD-friendly color scheme
  scale_fill_brewer(palette = "Dark2", na.value = "grey80") +
  # customize y-axis label
  labs(y = "Proportion") +
  # nicer theme
  theme_bw()

We see that Tumor cell types are by far the most prevalent across all samples, and normal tissue cell types are not very common. We see also that SCPCL000481 has a larger Tumor_Myocyte population, while all other samples have larger Tumor_Mesoderm populations. This difference may explain why we observe that SCPCL000481 is somewhat more separated from the other samples in the fastMNN UMAP.

Let’s re-plot this UMAP to highlight cell types:

scater::plotReducedDim(merged_sce,
                       dimred = "fastmnn_UMAP",
                       # color by broad celltypes
                       color_by = "celltype_broad",
                       point_size = 0.5,
                       point_alpha = 0.2) +
  # include argument to specify color of NA values
  scale_color_brewer(palette = "Dark2", name = "Broad celltype", na.value = "grey80") +
  guides(color = guide_legend(override.aes = list(size = 3, alpha = 1))) +
  ggtitle("UMAP after integration with fastMNN")
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

This UMAP shows that the normal tissue cell types (mostly vascular endothelium, muscle cells, and monocytes) tend to cluster together and are generally separated from the tumor cell types, which is an encouraging pattern! Tumor cell types from different samples are all also clustering together, which is even more encouraging that we had successful integration.

However, it’s a bit challenging to see all the points given the amount of overlap in the plot. One way we can see all the points a bit better is to facet the plot by sample, using facet_wrap() from the ggplot2 package (which we can do because scater::plotReducedDim() returns a ggplot2 object):

scater::plotReducedDim(merged_sce,
                       dimred = "fastmnn_UMAP",
                       color_by = "celltype_broad",
                       point_size = 0.5,
                       point_alpha = 0.2,
                       # Allow for faceting by a variable using `other_fields`:
                       other_fields = "sample") +
  scale_color_brewer(palette = "Dark2", name = "Broad celltype", na.value = "grey80") +
  guides(color = guide_legend(override.aes = list(size = 3, alpha = 1))) +
  ggtitle("UMAP after integration with fastMNN") +
  # Facet by sample
  facet_wrap(vars(sample)) +
  # Use a theme with background grid to more easily compare panel coordinates
  theme_bw()
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

What trends do you observe between tumor and healthy tissues among these integrated samples?

Integrate with harmony

fastMNN is only one of many approaches to perform integration, and different methods have different capabilities and may give different results. For example, some methods can accommodate additional covariates (e.g., technology, patient, diagnosis, etc.) that can influence integration. In fact the data we are using has a known patient covariate; SCPCL000479 and SCPCL000480 are from Patient A, and SCPCL000481 and SCPCL000482 are from Patient B.

So, let’s perform integration with a method that can use this information - harmony!

To begin setting up for harmony integration, we need to add explicit patient information into our merged SCE. We’ll create a new column patient whose value is either “A” or “B” depending on the given sample name, using the dplyr::case_when() function. We provide this function with a set of logical expressions and each assigned value is designated by ~. The expressions are evaluated in order, stopping at the first one that evaluates as TRUE and returning the associated value.

# Create patient column with values "A" or "B" for the two patients
merged_sce$patient <- dplyr::case_when(
  merged_sce$sample %in% c("SCPCL000479", "SCPCL000480") ~ "A",
  merged_sce$sample %in% c("SCPCL000481", "SCPCL000482") ~ "B",
)

Unlike fastMNN, harmony does not calculate corrected expression values nor does it return an SCE object. Like fastMNN, harmony performs integration on a merged PCA matrix. However, unlike fastMNN, harmony does not “back-calculate” corrected expression from the corrected PCA matrix and it only returns the corrected PCA matrix itself. For input, harmony needs a couple pieces of information:

  • First, harmony takes a batch-weighted PCA matrix to perform integration. We already calculated a batch-weighted PCA matrix so we’ll provide this as the the input.
  • Second, we need to tell harmony about the covariates to use - sample and patient. To do this, we provide two arguments:
    • meta_data, a data frame that contains covariates across samples. We can simply specify the SCE colData here since it contains sample and patient columns.
    • vars_use, a vector of which column names in meta_data should actually be used as covariates. Other columns in meta_data which are not in vars_use are ignored.

Let’s go!

# Run harmony integration
harmony_pca <- harmony::RunHarmony(
  data_mat = reducedDim(merged_sce, "PCA"),
  meta_data = colData(merged_sce),
  vars_use = c("sample", "patient")
)
Transposing data matrix
Initializing state using k-means centroids initialization
Harmony 1/10
Harmony 2/10
Harmony converged after 2 iterations

The result is a PCA matrix. Let’s print a subset of this matrix to see it:

# Print the harmony result
harmony_pca[1:5, 1:5]
                                  [,1]      [,2]      [,3]     [,4]       [,5]
SCPCL000479-GGGACCTCAAGCGGAT -7.036850 -7.280253 -3.825712 5.564675  0.5984375
SCPCL000479-CACAGATAGTGAGTGC -8.232417 -7.422768 -5.641379 6.128005 -0.2208036
SCPCL000479-TGTGGCGGTGAATTGA -7.928602 -6.591079 -2.140173 4.775223  0.8639596
SCPCL000479-GCCGATGGTACATACC -6.821359 -7.465958 -5.872961 2.313765 -4.1492218
SCPCL000479-ATTATCCCAGTTGGTT -5.611779 -4.916862 -1.894365 2.626925  1.5535017

As we did with fastMNN results, let’s store this PCA matrix directly in our merged_sce object with an informative name that won’t overwrite any of the existing PCA matrices. We’ll also calculate UMAP from it.

# Store PCA as `harmony_PCA`
reducedDim(merged_sce, "harmony_PCA") <- harmony_pca

# As before, calculate UMAP on this PCA matrix with appropriate names
merged_sce <- scater::runUMAP(merged_sce,
                              dimred = "harmony_PCA",
                              name   = "harmony_UMAP")

Let’s see how the harmony UMAP, colored by sample, looks compared to the fastMNN UMAP:

scater::plotReducedDim(merged_sce,
                       dimred = "harmony_UMAP",
                       color_by = "sample",
                       point_size = 0.5,
                       point_alpha = 0.2) +
  scale_color_brewer(palette = "Dark2", name = "sample") +
  guides(color = guide_legend(override.aes = list(size = 3, alpha = 1))) +
  ggtitle("UMAP after integration with harmony")
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

How do you think this harmony UMAP compares to that from fastMNN integration?

Let’s see how this UMAP looks colored by cell type, and faceted for visibility:

scater::plotReducedDim(merged_sce,
                       dimred = "harmony_UMAP",
                       color_by = "celltype_broad",
                       point_size = 0.5,
                       point_alpha = 0.2,
                       # Specify variable for faceting
                       other_fields = "sample") +
  scale_color_brewer(palette = "Dark2", name = "Broad celltype", na.value = "grey80") +
  guides(color = guide_legend(override.aes = list(size = 3, alpha = 1))) +
  ggtitle("UMAP after integration with harmony") +
  facet_wrap(vars(sample))
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

What do you now notice in this faceted view that wasn’t clear previously? Are there other patterns you see that are similar or different from the fastMNN UMAP? How do you think fastMNN vs. harmony performed in integrating these samples?

Export

Finally, we’ll export the final SCE object with both fastMNN and harmony integration to a file. Since this object is very large (over 1 GB!), we’ll export it to a file with some compression, which, in this case, will reduce the final size to a smaller ~360 MB. This will take a couple minutes to save while compression is performed.

# Export to RDS file with "gz" compression
readr::write_rds(
  merged_sce,
  integrated_sce_file,
  compress = "gz"
)
LS0tCnRpdGxlOiAiSW50ZWdyYXRpbmcgc2NSTkEtc2VxIGRhdGFzZXRzIgphdXRob3I6IERhdGEgTGFiIGZvciBBTFNGCmRhdGU6IDIwMjMKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKIyMgT2JqZWN0aXZlcwoKVGhpcyBub3RlYm9vayB3aWxsIGRlbW9uc3RyYXRlIGhvdyB0bzoKCi0gUHJlcGFyZSBTQ0Ugb2JqZWN0cyBmb3IgaW50ZWdyYXRpb24KLSBBcHBseSBpbnRlZ3JhdGlvbiBtZXRob2RzIGluY2x1ZGluZyBgZmFzdE1OTmAgYW5kIGBoYXJtb255YAotIFZpc3VhbGx5IGV4cGxvcmUgdGhlIHJlc3VsdHMgb2YgaW50ZWdyYXRpb24KLSBVc2UgYHB1cnJyOjptYXAoKWAgZnVuY3Rpb25zIGZvciBpdGVyYXRpbmcgb3ZlciBsaXN0cwoKLS0tCgpJbiB0aGlzIG5vdGVib29rLCB3ZSdsbCBwZXJmb3JtIGludGVncmF0aW9uIG9uIHNjUk5BLXNlcSBkYXRhc2V0cyBmcm9tIHRoZSBbU2luZ2xlLWNlbGwgUGVkaWF0cmljIENhbmNlciBBdGxhcyAoYFNjUENBYCldKGh0dHBzOi8vc2NwY2EuYWxleHNsZW1vbmFkZS5vcmcvKSwgYSBkYXRhYmFzZSBvZiB1bmlmb3JtbHktcHJvY2Vzc2VkIHBlZGlhdHJpYyBzY1JOQS1zZXEgZGF0YSBidWlsdCBhbmQgbWFpbnRhaW5lZCBieSB0aGUgRGF0YSBMYWIuClRoZSBgU2NQQ0FgIGRhdGFiYXNlIGN1cnJlbnRseSBob3N0cyBzaW5nbGUtY2VsbCBwZWRpYXRyaWMgY2FuY2VyIHRyYW5zY3JpcHRvbWljIGRhdGEgZ2VuZXJhdGVkIGJ5IEFMU0YtZnVuZGVkIGxhYnMsIHdpdGggdGhlIGdvYWwgb2YgbWFraW5nIHRoaXMgZGF0YSBlYXNpbHkgYWNjZXNzaWJsZSB0byBpbnZlc3RpZ2F0b3JzIChsaWtlIHlvdSEpLgpUaGUgZXhwcmVzc2lvbiBkYXRhIGluIGBTY1BDQWAgd2VyZSBtYXBwaW5nIGFuZCBxdWFudGlmaWVkIHdpdGggW2BhbGV2aW4tZnJ5YF0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMzgvczQxNTkyLTAyMi0wMTQwOC0zKSwgZm9sbG93ZWQgYnkgcHJvY2Vzc2luZyB3aXRoIEJpb2NvbmR1Y3RvciB0b29scyB1c2luZyB0aGUgc2FtZSBnZW5lcmFsIHByb2NlZHVyZXMgdGhhdCB3ZSBoYXZlIGNvdmVyZWQgaW4gdGhpcyB3b3Jrc2hvcC4KVGhlIHByb2Nlc3NpbmcgcGlwZWxpbmUgdXNlZCBgZW1wdHlEcm9wc0NlbGxSYW5nZXIoKWAgYW5kIGBtaVFDYCB0byBmaWx0ZXIgdGhlIHJhdyBjb3VudHMgbWF0cml4LCBgc2N1dHRsZWAgdG8gbG9nLW5vcm1hbGl6ZSB0aGUgY291bnRzLCBhbmQgYHNjYXRlcmAgZm9yIGRpbWVuc2lvbiByZWR1Y3Rpb24uClRoZSBwcm9jZXNzZWQgZGF0YSBhcmUgc3RvcmVkIGFzIGAucmRzYCBmaWxlcyBjb250YWluaW5nIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0cy4KWW91IGNhbiByZWFkIG1vcmUgYWJvdXQgaG93IGRhdGEgaW4gdGhlIGBTY1BDQWAgaXMgcHJvY2Vzc2VkIGluIFt0aGUgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uXShodHRwczovL3NjcGNhLnJlYWR0aGVkb2NzLmlvL2VuL2xhdGVzdC8pLgoKCiFbU2luZ2xlLWNlbGwgcm9hZG1hcDogSW50ZWdyYXRpb24gT3ZlcnZpZXddKGRpYWdyYW1zL3JvYWRtYXBfbXVsdGlfbWVyZ2UtaW50ZWdyYXRlLnBuZykKClRvIGxlYXJuIGFib3V0IGludGVncmF0aW9uLCB3ZSdsbCBoYXZlIGEgbG9vayBhdCBmb3VyIHNhbXBsZXMgZnJvbSB0aGUgW2BTQ1BDUDAwMDAwNWAgcHJvamVjdF0oaHR0cHM6Ly9zY3BjYS5hbGV4c2xlbW9uYWRlLm9yZy9wcm9qZWN0cy9TQ1BDUDAwMDAwNSkgKFtQYXRlbCBfZXQgYWwuXyAyMDIyXShodHRwczovL2RvaS5vcmcvMTAuMTAxNi9qLmRldmNlbC4yMDIyLjA0LjAwMykpLCBhbiBpbnZlc3RpZ2F0aW9uIG9mIHBlZGlhdHJpYyBzb2xpZCB0dW1vcnMgbGVkIGJ5IHRoZSBbRHllcl0oaHR0cHM6Ly93d3cuc3RqdWRlLm9yZy9yZXNlYXJjaC9sYWJzL2R5ZXItbGFiLmh0bWwpIGFuZCBbQ2hlbl0oaHR0cHM6Ly93d3cuc3RqdWRlLm9yZy9yZXNlYXJjaC9sYWJzL2NoZW4tbGFiLXRhb3NoZW5nLmh0bWwpIGxhYnMgYXQgU3QuIEp1ZGUgQ2hpbGRyZW4ncyBSZXNlYXJjaCBIb3NwaXRhbC4KVGhlIHBhcnRpY3VsYXIgbGlicmFyaWVzIHdlJ2xsIGludGVncmF0ZSBjb21lIGZyb20gdHdvIHJoYWJkb215b3NhcmNvbWEgKFJNUykgcGF0aWVudHMsIHdpdGggdHdvIHNhbXBsZXMgZnJvbSBlYWNoIG9mIHR3byBwYXRpZW50cywgYWxsIHNlcXVlbmNlZCB3aXRoIDEweCBDaHJvbWl1bSB2MyB0ZWNobm9sb2d5LgpFYWNoIGxpYnJhcnkgaXMgZnJvbSBhIHNlcGFyYXRlIGJpb2xvZ2ljYWwgc2FtcGxlLgoKV2UnbGwgYmUgaW50ZWdyYXRpbmcgdGhlc2Ugc2FtcGxlcyB3aXRoIHR3byBkaWZmZXJlbnQgdG9vbHMsIFtgZmFzdE1OTmBdKGh0dHA6Ly93d3cuYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvaHRtbC9iYXRjaGVsb3IuaHRtbCkgKFtIYWdodmVyZGkgX2V0IGFsLl8gMjAxOF0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMzgvbmJ0LjQwOTEpKSBhbmQgW2BoYXJtb255YF0oaHR0cHM6Ly9wb3J0YWxzLmJyb2FkaW5zdGl0dXRlLm9yZy9oYXJtb255LykgKFtLb3JzdW5za3kgX2V0IGFsLl8gMjAxOV0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMzgvczQxNTkyLTAxOS0wNjE5LTApKS4KSW50ZWdyYXRpb24gY29ycmVjdHMgZm9yIGJhdGNoIGVmZmVjdHMgdGhhdCBhcmlzZSBmcm9tIGRpZmZlcmVudCBsaWJyYXJ5IHByZXBhcmF0aW9ucywgZ2VuZXRpYyBiYWNrZ3JvdW5kcywgYW5kIG90aGVyIHNhbXBsZS1zcGVjaWZpYyBmYWN0b3JzLCBzbyB0aGF0IGRhdGFzZXRzIGNhbiBiZSBqb2ludGx5IGFuYWx5emVkIGF0IHRoZSBjZWxsIGxldmVsLgpgZmFzdE1OTmAgY29ycmVjdHMgZm9yIGJhdGNoIGVmZmVjdHMgdXNpbmcgYSBmYXN0ZXIgdmFyaWFudCBvZiB0aGUgbXV0dWFsLW5lYXJlc3QgbmVpZ2hib3JzIGFsZ29yaXRobSwgdGhlIHRlY2huaWNhbCBkZXRhaWxzIG9mIHdoaWNoIHlvdSBjYW4gbGVhcm4gbW9yZSBhYm91dCBmcm9tIHRoaXMgW3ZpZ25ldHRlIGJ5IEx1biAoMjAxOSldKGh0dHBzOi8vbWFyaW9uaWxhYi5naXRodWIuaW8vRnVydGhlck1OTjIwMTgvdGhlb3J5L2Rlc2NyaXB0aW9uLmh0bWwpLgpgaGFybW9ueWAsIG9uIHRoZSBvdGhlciBoYW5kLCBjb3JyZWN0cyBmb3IgYmF0Y2ggZWZmZWN0cyB1c2luZyBhbiBpdGVyYXRpdmUgY2x1c3RlcmluZyBhcHByb2FjaCwgYW5kIHVubGlrZSBgZmFzdE1OTmAsIGl0IGlzIGFsc28gYWJsZSB0byBjb25zaWRlciBhZGRpdGlvbmFsIGNvdmFyaWF0ZXMgYmV5b25kIGp1c3QgdGhlIGJhdGNoIGdyb3VwaW5ncy4KClJlZ2FyZGxlc3Mgb2Ygd2hpY2ggaW50ZWdyYXRpb24gdG9vbCBpcyB1c2VkLCB0aGUgYFNpbmdsZUNlbGxFeHBlcmltZW50YCAoU0NFKSBvYmplY3RzIGZpcnN0IG5lZWQgdG8gYmUgcmVmb3JtYXR0ZWQgYW5kIG1lcmdlZCBpbnRvIGEgc2luZ2xlICh1bmNvcnJlY3RlZCEpIFNDRSBvYmplY3QgdGhhdCBjb250YWlucyBhbGwgY2VsbHMgZnJvbSBhbGwgc2FtcGxlcy4KVGhpcyBtZXJnZWQgU0NFIGNhbiB0aGVuIGJlIHVzZWQgZm9yIGludGVncmF0aW9uIHRvIG9idGFpbiBhIGZvcm1hbGx5IGJhdGNoLWNvcnJlY3RlZCBTQ0Ugb2JqZWN0LgoKCiMjIFNldCB1cAoKYGBge3Igc2V0dXB9CiMgTG9hZCBsaWJyYXJpZXMKbGlicmFyeShnZ3Bsb3QyKSAgIyBwbG90dGluZyB0b29scwpsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KSAjIHdvcmsgd2l0aCBTQ0Ugb2JqZWN0cwoKIyBTZXQgdGhlIHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eQpzZXQuc2VlZCgxMjM0NSkKYGBgCgoKIyMjIERlZmluZSBkaXJlY3RvcmllcyBhbmQgZmlsZXMKCgpXZSBoYXZlIGFscmVhZHkgcHJlcGFyZWQgY291bnQgZGF0YSBmb3IgdGhlIGZvdXIgc2FtcGxlcyB3ZSdsbCBiZSBpbnRlZ3JhdGluZyAoaS5lLiwgZmlsdGVyZWQgY2VsbHMsIG5vcm1hbGl6ZWQgY291bnRzLCBhbmQgY2FsY3VsYXRlZCBQQ0EgJiBVTUFQKS4KVGhlc2UgU0NFIG9iamVjdHMsIHN0b3JlZCBhcyBSRFMgZmlsZXMsIGFyZSBhdmFpbGFibGUgaW4gdGhlIGBkYXRhL3Jtcy9wcm9jZXNzZWQvYCBkaXJlY3RvcnkgYW5kIGFyZSBuYW1lZCBhY2NvcmRpbmcgdG8gdGhlaXIgYFNjUENBYCBsaWJyYXJ5IGlkcyA6CgotIGBTQ1BDTDAwMDQ3OS5yZHNgIChQYXRpZW50IEEpCi0gYFNDUENMMDAwNDgwLnJkc2AgKFBhdGllbnQgQSkKLSBgU0NQQ0wwMDA0ODEucmRzYCAoUGF0aWVudCBCKQotIGBTQ1BDTDAwMDQ4Mi5yZHNgIChQYXRpZW50IEIpCgpCb3RoIFBhdGllbnQgQSAoMTggeWVhciBvbGQgbWFsZSkgYW5kIFBhdGllbnQgQiAoNCB5ZWFyIG9sZCBmZW1hbGUpIGhhZCByZWN1cnJlbnQgZW1icnlvbmFsIHJoYWJkb215b3NhcmNvbWEgd2hlbiBzYW1wbGVzIHdlcmUgdGFrZW4uCgpUbyBiZWdpbiwgbGV0J3Mgc2V0IHVwIG91ciBkaXJlY3RvcmllcyBhbmQgZmlsZXM6CgpgYGB7ciBkaXJlY3Rvcmllc30KIyBEZWZpbmUgZGlyZWN0b3J5IHdoZXJlIHByb2Nlc3NlZCBTQ0Ugb2JqZWN0cyB0byBiZSBpbnRlZ3JhdGVkIGFyZSBzdG9yZWQKaW5wdXRfZGlyIDwtIGZpbGUucGF0aCgiZGF0YSIsICJybXMiLCAicHJvY2Vzc2VkIikKCiMgRGVmaW5lIGRpcmVjdG9yeSB0byBzYXZlIGludGVncmF0ZWQgU0NFIG9iamVjdCB0bwpvdXRwdXRfZGlyIDwtIGZpbGUucGF0aCgiZGF0YSIsICJybXMiLCAiaW50ZWdyYXRlZCIpCgojIENyZWF0ZSBvdXRwdXQgZGlyZWN0b3J5IGlmIGl0IGRvZXNuJ3QgZXhpc3QKZnM6OmRpcl9jcmVhdGUob3V0cHV0X2RpcikKCiMgRGVmaW5lIG91dHB1dCBmaWxlIG5hbWUgZm9yIHRoZSBpbnRlZ3JhdGVkIG9iamVjdAppbnRlZ3JhdGVkX3NjZV9maWxlIDwtIGZpbGUucGF0aChvdXRwdXRfZGlyLCAicm1zX2ludGVncmF0ZWRfc3Vic2V0LnJkcyIpCmBgYAoKCldlIGNhbiB1c2UgdGhlIGBkaXIoKWAgZnVuY3Rpb24gdG8gbGlzdCBhbGwgY29udGVudHMgb2YgYSBnaXZlbiBkaXJlY3RvcnksIGZvciBleGFtcGxlIHRvIHNlZSBhbGwgdGhlIGZpbGVzIGluIG91ciBgaW5wdXRfZGlyYDoKCmBgYHtyIGlucHV0IGRpciwgbGl2ZSA9IFRSVUV9CmRpcihpbnB1dF9kaXIpCmBgYAoKV2Ugd2FudCB0byByZWFkIGluIGp1c3QgZm91ciBvZiB0aGVzZSBmaWxlcywgYXMgbGlzdGVkIHByZXZpb3VzbHkuClRvIHJlYWQgaW4gdGhlc2UgZmlsZXMsIHdlIGNvdWxkIHVzZSB0aGUgYHJlYWRyOjpyZWFkX3JkcygpYCBmdW5jdGlvbiAob3IgdGhlIGJhc2UgUiBgcmVhZFJEUygpYCkgZm91ciB0aW1lcywgb25jZSBmb3IgZWFjaCBvZiB0aGUgZmlsZXMuCldlIGNvdWxkIGFsc28gdXNlIGEgYGZvcmAgbG9vcCwgd2hpY2ggaXMgdGhlIGFwcHJvYWNoIHRoYXQgbWFueSBwcm9ncmFtbWluZyBsYW5ndWFnZXMgd291bGQgbGVhbiB0b3dhcmQuCkEgZGlmZmVyZW50IGFuZCBtb3JlIG1vZHVsYXIgY29kaW5nIGFwcHJvYWNoIHRvIHJlYWRpbmcgaW4gdGhlc2UgZmlsZXMgKGFuZCBtb3JlISkgaXMgdG8gbGV2ZXJhZ2UgdGhlIFtgcHVycnJgXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvKSBgdGlkeXZlcnNlYCBwYWNrYWdlLCB3aGljaCBwcm92aWRlcyBhIGNvbnZlbmllbnQgc2V0IG9mIGZ1bmN0aW9ucyBmb3Igb3BlcmF0aW5nIG9uIGxpc3RzLgpZb3UgY2FuIHJlYWQgbW9yZSBhYm91dCB0aGUgYHB1cnJyYCBmdW5jdGlvbnMgYW5kIHRoZWlyIHBvd2VyIGFuZCB1dGlsaXR5IGluIFIgaW4gW3RoZSAiRnVuY3Rpb25hbHMiIGNoYXB0ZXIgb2YgdGhlIF9BZHZhbmNlZCBSXyBlLWJvb2tdKGh0dHBzOi8vYWR2LXIuaGFkbGV5Lm56L2Z1bmN0aW9uYWxzLmh0bWwpLgoKT2YgcGFydGljdWxhciBpbnRlcmVzdCBpcyB0aGUgW2BwdXJycjo6bWFwKClgXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL21hcC5odG1sKSBmYW1pbHkgb2YgZnVuY3Rpb25zLCB3aGljaCBjYW4gYmUgdXNlZCB0byBydW4gYSBnaXZlbiBmdW5jdGlvbiBvbiBlYWNoIGVsZW1lbnQgb2YgYSBsaXN0IChvciB2ZWN0b3IpIGluIG9uZSBjYWxsLgpUaGUgZ2VuZXJhbCBzeW50YXggZm9yIGBwdXJycjo6bWFwKClgIGFuZCBmcmllbmRzIGlzOgoKYGBgCiMgU3ludGF4IGZvciB1c2luZyB0aGUgbWFwIGZ1bmN0aW9uOgpwdXJycjo6bWFwKDxpbnB1dCBsaXN0IG9yIHZlY3Rvcj4sCiAgICAgICAgICAgPGZ1bmN0aW9uIHRvIGFwcGx5IHRvIGVhY2ggaXRlbSBpbiB0aGUgaW5wdXQ+LAogICAgICAgICAgIDxhbnkgYWRkaXRpb25hbCBhcmd1bWVudHMgdG8gdGhlIGZ1bmN0aW9uIGNhbiBnbyBoZXJlPiwKICAgICAgICAgICA8YW5kIGFsc28gaGVyZSBpZiB0aGVyZSBhcmUgZXZlbiBtb3JlIGFyZ3VtZW50cywgYW5kIHNvIG9uPikKYGBgCgoKVGhlIG91dHB1dCBmcm9tIHJ1bm5pbmcgYHB1cnJyOjptYXAoKWAgaXMgYWx3YXlzIGEgbGlzdCAoYnV0IG5vdGUgdGhhdCB0aGVyZSBhcmUgb3RoZXIgYHB1cnJyOjptYXAoKWAgcmVsYXRpdmVzIHdoaWNoIHJldHVybiBvdGhlciBvYmplY3QgdHlwZXMsIGFzIHlvdSBjYW4gcmVhZCBhYm91dCBpbiBbdGhlIGBwdXJycjo6bWFwKClgIGRvY3VtZW50YXRpb25dKGh0dHBzOi8vcHVycnIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvaW5kZXguaHRtbCkpLgpJZiB0aGlzIGNvbmNlcHQgc291bmRzIGEgbGl0dGxlIGZhbWlsaWFyIHRvIHlvdSwgdGhhdCdzIGJlY2F1c2UgaXQgcHJvYmFibHkgaXMhCkJhc2UgUidzIGBsYXBwbHkoKWAgZnVuY3Rpb24gY2FuIHByb3ZpZGUgc2ltaWxhciB1dGlsaXR5LCBhbmQgdGhlIGBwdXJycjo6bWFwKClgIGZhbWlseSBvZiBmdW5jdGlvbnMgY2FuIChpbiBwYXJ0KSBiZSB0aG91Z2h0IG9mIGFzIGFuIGFsdGVybmF0aXZlIHRvIHNvbWUgb2YgdGhlIGJhc2UgUiBgYXBwbHlgIGZ1bmN0aW9ucywgd2l0aCBtb3JlIGNvbnNpc3RlbnQgYmVoYXZpb3IuCgpMZXQncyBzZWUgYSB2ZXJ5IHNpbXBsZSBleGFtcGxlIG9mIGBwdXJycjo6bWFwKClgIGluIGFjdGlvbiwgaW5zcGlyZWQgYnkgY2FuY2VyIGdyb3VwcyB0aGUgRGF0YSBMYWIgaGFzIGFuYWx5emVkIHRocm91Z2ggdGhlIFtPcGVuUEJUQV0oaHR0cHM6Ly9naXRodWIuY29tL0FsZXhzTGVtb25hZGUvT3BlblBCVEEtYW5hbHlzaXMvKSBwcm9qZWN0OgoKYGBge3IgbWFwIGV4YW1wbGV9CiMgRGVmaW5lIGEgbGlzdCBvZiBjYW5jZXIgaGlzdG9sb2dpZXMKaGlzdG9sb2dpZXMgPC0gbGlzdCgKICAibG93LWdyYWRlIGdsaW9tYXMiICA9IGMoIlNFR0EiLCAiUEEiLCAiR05HIiwgIlBYQSIpLAogICJoaWdoLWdyYWRlIGdsaW9tYXMiID0gYygiRE1HIiwgIkRJUEciKSwKICAiZW1icnlvbmFsIHR1bW9ycyIgICA9IGMoIk1CIiwgIkFUUlQiLCAiRVRNUiIpCiApCgojIFRoZSBvdmVyYWxsIGxlbmd0aCBvZiB0aGUgbGlzdCBpcyAzCmxlbmd0aChoaXN0b2xvZ2llcykKCiMgSG93IGNhbiB3ZSBydW4gYGxlbmd0aCgpYCBvbiBlYWNoIGl0ZW0gb2YgdGhlIGxpc3Q/CiMgV2UgY2FuIHVzZSBvdXIgbmV3IGZyaWVuZCBwdXJycjo6bWFwKCk6CnB1cnJyOjptYXAoaGlzdG9sb2dpZXMsIGxlbmd0aCkKYGBgCgpMZXQncyB1c2UgYHB1cnJyOjptYXAoKWAgdG8gcmVhZCBpbiBvdXIgU0NFIG9iamVjdHMgc28gdGhhdCB0aGV5IGFyZSBpbW1lZGlhdGVseSBzdG9yZWQgdG9nZXRoZXIgaW4gYSBsaXN0LgoKCldlJ2xsIGZpcnN0IG5lZWQgdG8gZGVmaW5lIGEgdmVjdG9yIG9mIHRoZSBmaWxlIHBhdGhzIHRvIHJlYWQgaW4uCldlJ2xsIHN0YXJ0IGJ5IGNyZWF0aW5nIGEgdmVjdG9yIG9mIHNhbXBsZSBuYW1lcyB0aGVtc2VsdmVzIGFuZCB0aGVuIGZvcm1hdHRpbmcgdGhlbSBpbnRvIHRoZSBjb3JyZWN0IHBhdGhzLgpUaGlzIHdheSAoZm9yZXNoYWRvd2luZyEpIHdlIGFsc28gaGF2ZSBhIHN0YW5kLWFsb25lIHZlY3RvciBvZiBqdXN0IHNhbXBsZSBuYW1lcywgd2hpY2ggd2lsbCBjb21lIGluIGhhbmR5IQoKYGBge3Igc2FtcGxlIG5hbWVzfQojIFZlY3RvciBvZiBhbGwgdGhlIHNhbXBsZXMgdG8gcmVhZCBpbjoKc2FtcGxlX25hbWVzIDwtIGMoIlNDUENMMDAwNDc5IiwKICAgICAgICAgICAgICAgICAgIlNDUENMMDAwNDgwIiwKICAgICAgICAgICAgICAgICAgIlNDUENMMDAwNDgxIiwKICAgICAgICAgICAgICAgICAgIlNDUENMMDAwNDgyIikKYGBgCgoKYGBge3IgZGVmaW5lIHNjZV9wYXRocywgbGl2ZSA9IFRSVUV9CiMgTm93LCBjb252ZXJ0IHRoZXNlIHRvIGZpbGUgcGF0aHM6IDxpbnB1dF9kaXI+LzxzYW1wbGVfbmFtZT4ucmRzCnNjZV9wYXRocyA8LSBmaWxlLnBhdGgoaW5wdXRfZGlyLAogICAgICAgICAgICAgICAgICAgICAgIGdsdWU6OmdsdWUoIntzYW1wbGVfbmFtZXN9LnJkcyIpCikKIyBQcmludCB0aGUgc2NlX3BhdGhzIHZlY3RvcgpzY2VfcGF0aHMKYGBgCgpMZXQncyBtYWtlIHRoaXMgYSBuYW1lZCB2ZWN0b3IgdXNpbmcgdGhlIHNhbXBsZSBuYW1lcy4KVGhpcyB3aWxsIGhlbHAgdXMga2VlcCB0cmFjayBvZiB3aGljaCBvYmplY3RzIGFyZSB3aGljaCBhZnRlciB3ZSByZWFkIHRoZSBTQ0Ugb2JqZWN0cyBpbjoKCmBgYHtyIGFkZCBsaXN0IG5hbWVzLCBsaXZlID0gVFJVRX0KIyBBc3NpZ24gdGhlIHNhbXBsZSBuYW1lcyBhcyB0aGUgbmFtZXMgZm9yIHNjZV9wYXRocwpuYW1lcyhzY2VfcGF0aHMpIDwtIHNhbXBsZV9uYW1lcwpgYGAKCldlIGNhbiBub3cgcmVhZCB0aGVzZSBmaWxlcyBpbiBhbmQgY3JlYXRlIGEgbGlzdCBvZiBmb3VyIFNDRSBvYmplY3RzLiAKU2luY2UgYHJlYWRyOjpyZWFkX3JkcygpYCBjYW4gb25seSBvcGVyYXRlIG9uIG9uZSBpbnB1dCBhdCBhIHRpbWUsIHdlJ2xsIG5lZWQgdG8gdXNlIGBwdXJycjo6bWFwKClgIHRvIHJ1biBpdCBvbiBhbGwgaW5wdXQgZmlsZSBwYXRocyBpbiBvbmUgY29tbWFuZC4KQWx0aG91Z2ggYHNjZV9wYXRoc2AgaXMgYSB2ZWN0b3IgKG5vdCBhIGxpc3QpLCBpdCB3aWxsIHN0aWxsIHdvcmsgYXMgaW5wdXQgdG8gYHB1cnJyOm1hcCgpYC4KVGhlIG91dHB1dCBmcm9tIHRoaXMgY29kZSB3aWxsIHN0aWxsIGJlIGEgbGlzdCwgc2luY2UgdGhhdCdzIHdoYXQgYHB1cnJyOjptYXAoKWAgYWx3YXlzIHJldHVybnMsIGFuZCBpdCB3aWxsIHJldGFpbiB0aGUgc2FtcGxlIG5hbWVzIGFzIHRoZSBsaXN0IG5hbWVzIGZvciBjb252ZW5pZW50IGJvb2trZWVwaW5nOgoKYGBge3IgcmVhZCBzY2UgcGF0aHMsIGxpdmUgPSBUUlVFfQojIFVzZSBwdXJycjo6bWFwKCkgdG8gcmVhZCBhbGwgZmlsZXMgaW50byBhIGxpc3QgYXQgb25jZQpzY2VfbGlzdCA8LSBwdXJycjo6bWFwKAogIHNjZV9wYXRocywKICByZWFkcjo6cmVhZF9yZHMKKQpgYGAKCkxldCdzIGhhdmUgYSBsb29rIGF0IG91ciBuYW1lZCBsaXN0IG9mIFNDRSBvYmplY3RzOgoKYGBge3IgcHJpbnQgc2NlIGxpc3QsIGxpdmU9VFJVRX0KIyBQcmludCBzY2VfbGlzdApzY2VfbGlzdApgYGAKCklmIHlvdSBsb29rIGNsb3NlbHkgYXQgdGhlIHByaW50ZWQgU0NFIG9iamVjdHMsIHlvdSBtYXkgbm90aWNlIHRoYXQgdGhleSBhbGwgY29udGFpbiBgY29sRGF0YWAgdGFibGUgY29sdW1ucyBgY2VsbHR5cGVfZmluZWAgYW5kIGBjZWxsdHlwZV9icm9hZGAuClRoZXNlIGNvbHVtbnMgKHdoaWNoIHdlIGFkZGVkIHRvIFNDRSBvYmplY3RzIGR1cmluZyBbcHJlLXByb2Nlc3NpbmddKGh0dHBzOi8vZ2l0aHViLmNvbS9BbGV4c0xlbW9uYWRlL3RyYWluaW5nLW1vZHVsZXMvdHJlZS9tYXN0ZXIvc2NSTkEtc2VxLWFkdmFuY2VkL3NldHVwL3JtcykpIGNvbnRhaW4gcHV0YXRpdmUgY2VsbCB0eXBlIGFubm90YXRpb25zIGFzIGFzc2lnbmVkIGJ5IFtQYXRlbCBfZXQgYWwuXyAoMjAyMildKGh0dHBzOi8vZG9pLm9yZy8xMC4xMDE2L2ouZGV2Y2VsLjIwMjIuMDQuMDAzKToKCgo+IEZvciBlYWNoIGNlbGwgc3Vic2V0IGlkZW50aWZpZWQgYnkgY2x1c3RlcmluZywgd2UgdXNlZCBhIGNvbWJpbmF0aW9uIG9mIGBTaW5nbGVSYCB2ZXJzaW9uIDEuMC4xIChbQXJhbiBldCBhbC4sIDIwMTldKGh0dHBzOi8vd3d3Lm5hdHVyZS5jb20vYXJ0aWNsZXMvczQxNTkwLTAxOC0wMjc2LXkpKSBhbmQgbWFudWFsIGluc3BlY3Rpb24gb2YgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGdlbmVzIHRvIGFubm90YXRlIHdoZXRoZXIgYSBjbHVzdGVyIGJlbG9uZ3MgdG8gc3Ryb21hbCwgaW1tdW5lIG9yIG1hbGlnbmFudCBzdWJwb3B1bGF0aW9ucy4gCk1hbGlnbmFudCBjZWxscyB3ZXJlIGNvbmZpcm1lZCBpbiBwYXRpZW50IHR1bW9yIGRhdGEgYnkgaW5mZXJlbmNlIG9mIGNvcHktbnVtYmVyIHZhcmlhdGlvbiB1c2luZyBgaW5mZXJDTlZgIHZlcnNpb24gMS4xLjMgb2YgdGhlIFRyaW5pdHlDVEFUIFByb2plY3QgKGh0dHBzOi8vZ2l0aHViLmNvbS9icm9hZGluc3RpdHV0ZS9pbmZlcmNudikuIAoKV2Ugd2lsbCBlbmQgdXAgbGV2ZXJhZ2luZyB0aGVzZSBjZWxsIHR5cGUgYW5ub3RhdGlvbnMgdG8gZXhwbG9yZSB0aGUgaW50ZWdyYXRpb24gcmVzdWx0czsgYWZ0ZXIgaW50ZWdyYXRpb24sIHdlIGV4cGVjdCBjZWxsIHR5cGVzIGZyb20gZGlmZmVyZW50IHNhbXBsZXMgdG8gZ3JvdXAgdG9nZXRoZXIsIHJhdGhlciB0aGFuIGJlaW5nIHNlcGFyYXRlZCBieSBiYXRjaGVzLiAKClRoYXQgc2FpZCwgdGhlIGludGVncmF0aW9uIG1ldGhvZHMgd2Ugd2lsbCBiZSBhcHBseWluZyBfZG8gbm90IGFjdHVhbGx5IHVzZV8gYW55IGV4aXN0aW5nIGNlbGwgdHlwZSBhbm5vdGF0aW9ucy4KSWYgd2UgaGF2ZSBhbm5vdGF0aW9ucywgdGhleSBhcmUgYSBoZWxwZnVsICJib251cyIgZm9yIGFzc2Vzc2luZyB0aGUgaW50ZWdyYXRpb24ncyBwZXJmb3JtYW5jZSwgYnV0IHRoZXkgYXJlIG5vdCBwYXJ0IG9mIHRoZSBpbnRlZ3JhdGlvbiBpdHNlbGYuCgoKIyMgTWVyZ2UgdGhlIFNDRSBsaXN0IGludG8gb25lIG9iamVjdAoKIVtTaW5nbGUtY2VsbCByb2FkbWFwOiBNZXJnZV0oZGlhZ3JhbXMvcm9hZG1hcF9tdWx0aV9tZXJnZS5wbmcpCgoKTm93IHRoYXQgd2UgaGF2ZSBhIGxpc3Qgb2YgcHJvY2Vzc2VkIFNDRSBvYmplY3RzLCB3ZSBuZWVkIHRvIG1lcmdlIHRoZSBvYmplY3RzIGludG8gb25lIG92ZXJhbGwgU0NFIG9iamVjdCBmb3IgaW5wdXQgdG8gaW50ZWdyYXRpb24uCkEgd29yZCBvZiBjYXV0aW9uIGJlZm9yZSB3ZSBiZWdpbjogKipUaGlzIG1lcmdlZCBTQ0Ugb2JqZWN0IGlzIE5PVCBhbiBpbnRlZ3JhdGVkIFNDRSEqKgpNZXJnaW5nIFNDRXMgZG9lcyBub3QgcGVyZm9ybSBhbnkgYmF0Y2ggY29ycmVjdGlvbiwgYnV0IGp1c3QgcmVvcmdhbml6ZXMgdGhlIGRhdGEgdG8gYWxsb3cgdXMgdG8gcHJvY2VlZCB0byBpbnRlZ3JhdGlvbiBuZXh0LgoKVG8gbWVyZ2UgU0NFIG9iamVjdHMsIHdlIGRvIG5lZWQgdG8gZG8gc29tZSB3cmFuZ2xpbmcgYW5kIGJvb2trZWVwaW5nIHRvIGVuc3VyZSBjb21wYXRpYmlsaXR5IGFuZCB0aGF0IHdlIGRvbid0IGxvc2UgaW1wb3J0YW50IGluZm9ybWF0aW9uLgpPdmVyYWxsLCB3ZSdsbCB3YW50IHRvIG1ha2Ugc3VyZSB0aGF0OgoKMS4gQWxsIG9iamVjdHMgaGF2ZSBjb21wYXRpYmxlIGRpbWVuc2lvbnMuClRoaXMgbWVhbnMgdGhhdCBhbGwgb2JqZWN0cyBzaG91bGQuLi4KICAgICsgSGF2ZSB0aGUgc2FtZSBnZW5lcyAoYWthIHJvdyBuYW1lcyksIGluIHRoZSBzYW1lIG9yZGVyCiAgICArIEhhdmUgdGhlIHNhbWUgYGNvbERhdGFgIHNsb3QgY29sdW1ucywgaW4gdGhlIHNhbWUgb3JkZXIKICAgICsgSGF2ZSB0aGUgc2FtZSBhc3NheXMKMi4gQWZ0ZXIgbWVyZ2luZywgd2UnbGwgc3RpbGwgYmUgYWJsZSB0byBpZGVudGlmeSB3aGljaCBzYW1wbGUgZGlmZmVyZW50IHBpZWNlcyBvZiBpbmZvcm1hdGlvbiBjYW1lIGZyb20KQXMgd2Ugc2F3IGluIHRoZSBzbGlkZXMsIHRoaXMgbWVhbnMgd2UnbGwgaGF2ZSB0by4uLgogICAgKyBBdHRhY2ggc2FtcGxlIG5hbWVzIHRvIHRoZSBiYXJjb2RlcyAoYWthIGNvbHVtbiBuYW1lcykKICAgIFRoaXMgYWxzbyBlbnN1cmVzIHRoYXQgY29sdW1uIG5hbWVzIGFyZSB1bmlxdWU7IHdoaWxlIGEgc2luZ2xlIHNhbXBsZSAobGlicmFyeSkgaXMgZ3VhcmFudGVlZCB0byBoYXZlIHVuaXF1ZSBiYXJjb2RlcywgdGVjaG5pY2FsbHkgdGhleSBjYW4gYmUgcmVwZWF0ZWQgYWNyb3NzIHNhbXBsZXMhCiAgICArIEF0dGFjaCBzYW1wbGUgbmFtZXMgdG8gYHJvd0RhdGFgIHNsb3QgY29sdW1uIG5hbWVzIGFuZCBgbWV0YWRhdGFgIGZpZWxkIG5hbWVzIChpZiB5b3UgY2FyZSB0byBrZWVwIHRoaXMgaW5mb3JtYXRpb24gYXJvdW5kIC0gdG9kYXksIHdlIHdpbGwhKQogICAgKyBBZGQgYSBuZXcgY29sdW1uIGluZGljYXRpbmcgdGhlIHNhbXBsZSB0byB0aGUgYGNvbERhdGFgIHNsb3QKCldlJ2xsIGFwcHJvYWNoIHRoaXMgbWVyZ2UgaW4gdHdvIHBhcnRzOgoKKyBGaXJzdCwgd2UnbGwgdGFrZSBzb21lIHRpbWUgdG8gdGhvcm91Z2hseSBleHBsb3JlIHRoZSBvdXIgU0NFIG9iamVjdHMgdG8gZGV0ZXJtaW5lIHdoYXQgd3JhbmdsaW5nIHdlIG5lZWQgdG8gZG8gdG8gbWFrZSBhbGwgdGhlIG9iamVjdHMgX2NvbXBhdGlibGVfIGZvciBtZXJnaW5nCisgVGhlbiwgd2UnbGwgd3JpdGUgKG9rLCB3ZSd2ZSB3cml0dGVuIGl0IGZvciB5b3UpIGEgX2N1c3RvbSBmdW5jdGlvbl8gdG8gZm9ybWF0IGVhY2ggU0NFIG9iamVjdCBmb3IgbWVyZ2luZywgaW5jbHVkaW5nOgogICAgKyBNYWtpbmcgYW55IGNoYW5nZXMgdG8gZW5zdXJlIG9iamVjdHMgYXJlIGNvbXBhdGlibGUKICAgICsgQWRkaW5nIGluIGlkZW50aWZ5aW5nIGluZm9ybWF0aW9uIHNvIHdlIGtub3cgd2hpY2ggc2FtcGxlIHRoZSBjZWxscyBhbmQgb3RoZXIgbWV0YWRhdGEgY2FtZSBmcm9tIAogICAgKyBSZW1vdmluZyB0aGUgZXhpc3RpbmcgcmVkdWNlZCBkaW1lbnNpb24gbWF0cmljZXMgKFBDQSBhbmQgVU1BUCkuCiAgICBUaGlzIGlzIGJlY2F1c2Ugd2UnbGwgd2FudCB0byByZWNhbGN1bGF0ZSB0aGVzZSBtYXRyaWNlcyBvbiB0aGUgbWVyZ2VkIG9iamVjdHMsIHRha2luZyBiYXRjaCBpbnRvIGFjY291bnQKCldoZW4gbWVyZ2luZyBvYmplY3RzIG9uIHlvdXIgb3duLCBkb24ndCBza2lwIHRoZXNlIGRhdGEgZXhwbG9yYXRpb24gc3RlcHMhClRoZSBzdGVwcyB3ZSB0YWtlIHRvIHByZXBhcmUgb3VyIFNDRXMgd2lsbCBwcm9iYWJseSBiZSBkaWZmZXJlbnQgZnJvbSB0aGUgc3RlcHMgeW91IG5lZWQgdG8gdGFrZSB3aXRoIG90aGVyIFNDRXMsIGFuZCBvbmx5IGJ5IGNhcmVmdWxseSBleHBsb3JpbmcgdGhlIG9iamVjdHMgY2FuIHlvdSBmaWd1cmUgb3V0IHdoYXQgc3RlcHMgeW91J2xsIG5lZWQgdG8gdGFrZSB0byBtZWV0IGFsbCBvZiBvdXIgY29uZGl0aW9ucy4KCgojIyMgUHJlcGFyZSB0byBtZXJnZSBTQ0VzCgojIyMjIENyZWF0ZSB1bmlxdWUgY2VsbCBpZGVudGlmaWVycwoKQXMgcGFydCBvZiB0aGUgY3VzdG9tIGZ1bmN0aW9uIHdlJ2xsIHdyaXRlLCB3ZSdsbCBpbmNsdWRlIGEgc3RlcCB0byBjcmVhdGUgdW5pcXVlIGNlbGwgaWRlbnRpZmllcnMgYnkgYXR0YWNoaW5nIHNhbXBsZSBuYW1lcyB0byB0aGUgU0NFIGNvbHVtbiBuYW1lcyAoY2VsbCBiYXJjb2RlcykuCkZvciBleGFtcGxlLCB3ZSB3b3VsZCB1cGRhdGUgdGhlIGNvbHVtbiBuYW1lIGZvciBhIGNlbGwgZnJvbSBgU2FtcGxlMWAgd2l0aCB0aGUgYmFyY29kZSBgQUNHVGAgdG8gYFNhbXBsZTEtQUNHVGAuCgpXaGVuIG1lcmdpbmcsIHRoZXJlIGNhbid0IGJlIGFueSBkdXBsaWNhdGUgY29sdW1uIG5hbWVzIChiYXJjb2RlcykgYWNyb3NzIF9hbGxfIHRoZSBvYmplY3RzIG9yIFIgd2lsbCB0aHJvdyBhbiBlcnJvci4KV2hpbGUgeW91J3JlIGd1YXJhbnRlZWQgdG8gaGF2ZSB1bmlxdWUgYmFyY29kZXMgaW4gYSBnaXZlbiBTQ0Ugb2JqZWN0LCB0aGVyZSBpcyBfbm8gZ3VhcmFudGVlXyB0aGF0IHRoZXkgYXJlIHVuaXF1ZSBhY3Jvc3MgbXVsdGlwbGUgc2FtcGxlcyAtIGl0IGlzIGFic29sdXRlbHkgcG9zc2libGUgdG8gaGF2ZSBjZWxscyBmcm9tIHR3byBkaWZmZXJlbnQgc2FtcGxlcyBzaGFyZSB0aGUgc2FtZSBiYXJjb2RlIChhbmQgd2UndmUgc2VlbiBpdCBoYXBwZW4hKS4KCkFkZGluZyB0aGUgc2FtcGxlIGlkIHRvIHRoZSBjb2x1bW4gbmFtZXMgKGJhcmNvZGVzKSBpcyB0aGVyZWZvcmUgYSBjcnVjaWFsIHN0ZXAgaW4gb3VyIG1lcmdpbmcgYm9va2tlZXBpbmcuCgoKIyMjIyBFeHBsb3JlIHRoZSBTQ0Ugb2JqZWN0cwoKIyMjIyMgQ2hlY2sgdGhlIGdlbmVzCgpGaXJzdCwgd2UnbGwgY29tcGFyZSB0aGUgb2JqZWN0J3MgZ2VuZXMgKGFrYSwgdGhlaXIgcm93IG5hbWVzKS4KV2UgY2FuIHVzZSBzb21lIGBwdXJycmAgbWFnaWMgdG8gaGVscCB1cyBmaW5kIHRoZSBzZXQgb2Ygc2hhcmVkIGdlbmVzIGFtb25nIGFsbCBvYmplY3RzOgoKYGBge3Igc2hhcmVkIGdlbmVzfQojIERlZmluZSB2ZWN0b3Igb2Ygc2hhcmVkIGdlbmVzCnNoYXJlZF9nZW5lcyA8LSBzY2VfbGlzdCB8PgogICMgZ2V0IHJvd25hbWVzIChnZW5lcykgZm9yIGVhY2ggU0NFIGluIHNjZV9saXN0CiAgcHVycnI6Om1hcChyb3duYW1lcykgfD4KICAjIHJlZHVjZSB0byB0aGUgX2ludGVyc2VjdGlvbl8gYW1vbmcgbGlzdHMKICBwdXJycjo6cmVkdWNlKGludGVyc2VjdCkKCiMgSG93IG1hbnkgc2hhcmVkIGdlbmVzIGFyZSB0aGVyZT8KbGVuZ3RoKHNoYXJlZF9nZW5lcykKYGBgCgpUaGF0J3MgcXVpdGUgYSBsb3QhCkluIGZhY3QsIGJlY2F1c2UgdGhlc2Ugb2JqZWN0cyB3ZXJlIGFsbCB1bmlmb3JtbHkgcHJvY2Vzc2VkIGJ5IHRoZSBzYW1lIHdvcmtmbG93ICh3aGljaCBkaWQgbm90IGZpbHRlciBvdXQgYW55IGdlbmVzISksIHdlIGV4cGVjdCB0aGVtIHRvIGFsbCBoYXZlIHRoZSBzYW1lIGdlbmVzLgpXZSBjYW4gbWFwIG92ZXIgdGhlIGxpc3QgdG8gY29uZmlybSB0aGF0IGluZGVlZCwgdGhleSBoYXZlIHRoZSBzYW1lIG51bWJlciBvZiByb3dzIChnZW5lcyk6CgoKYGBge3IgY2hlY2sgc2hhcmVkIGdlbmVzLCBsaXZlID0gVFJVRX0KIyBUaGUgbnVtYmVyIG9mIGdlbmVzIGluIGFuIFNDRSBjb3JyZXNwb25kcyB0byBpdHMgbnVtYmVyIG9mIHJvd3M6CnNjZV9saXN0IHw+CiAgcHVycnI6Om1hcChucm93KQpgYGAKCkV2ZW4gdGhvdWdoIHdlIGtub3cgdGhlIGdlbmVzIGFscmVhZHkgbWF0Y2gsIHdlIG5lZWQgdG8gYWxzbyBiZSBzdXJlIHRoZXkgYXJlIGluIHRoZSBzYW1lIF9vcmRlcl8gYW1vbmcgYWxsIG9iamVjdHMuClNvLCB3ZSdsbCBob2xkIG9udG8gdGhhdCBgc2hhcmVkX2dlbmVzYCB2YXJpYWJsZSB3ZSBkZWZpbmVkIGFuZCB1c2UgaXQgc29vbiBpbiBvdXIgY3VzdG9tIGZvcm1hdHRpbmcgZnVuY3Rpb24gdG8gbWFrZSBzdXJlIGFsbCBvYmplY3RzIGZ1bGx5IG1hdGNoLgoKSXQncyB3b3J0aCBub3RpbmcgdGhhdCB0aGUgaW50ZXJzZWN0aW9uIGlzbid0IHRoZSBvbmx5IG9wdGlvbiBoZXJlLCB0aG91Z2ghClVzaW5nIHRoZSBpbnRlcnNlY3Rpb24gbWVhbnMgYSBsb3Qgb2YgZ2VuZXMgd2lsbCBnZXQgZGlzY2FyZGVkIGlmIHRoZSBvYmplY3RzIGhhdmUgZGlmZmVyZW50IGdlbmVzLgpXZSBjb3VsZCBpbnN0ZWFkIHRha2UgdGhlIF91bmlvbl8gb2YgZ2VuZXMgc28gbm90aGluZyBnZXRzIHRocm93biBvdXQuCkluIHRoaXMgY2FzZSwgeW91J2QgbmVlZCB0byBjcmVhdGUgImR1bW15IiBhc3NheSByb3dzIGZvciBnZW5lcyB0aGF0IGEgZ2l2ZW4gU0NFIGRvZXNuJ3QgaGF2ZSBhbmQgZmlsbCBpdCB3aXRoIGBOQWAgZXhwcmVzc2lvbiB2YWx1ZXMuCllvdSdsbCBzdGlsbCBoYXZlIHRvIG1ha2Ugc3VyZSB0aGUgU0NFcyBoYXZlIHRoZSBzYW1lIHJvd3MgaW4gdGhlIHNhbWUgb3JkZXIgYmVmb3JlIG1lcmdpbmcsIHNvIHlvdSBtYXkgbmVlZCB0byBkbyBhIGRlY2VudCBiaXQgb2YgbWF0cml4IHdyYW5nbGluZy4KCiMjIyMjIENoZWNrIHRoZSBgY29sRGF0YWAgY29sdW1uIG5hbWVzCgpOZXh0IHVwLCB3ZSdsbCBjaGVjayB0aGUgYGNvbERhdGFgIGNvbHVtbnM6IHdlIG5lZWQgdGhlc2UgdG8gYmUgdGhlIHNhbWUsIGFuZCBpbiB0aGUgc2FtZSBvcmRlci4KTGV0J3MgcHJpbnQgb3V0IGVhY2ggb2JqZWN0J3MgYGNvbERhdGFgIGNvbHVtbiBuYW1lIHRvIHNlZSB3aGVyZSB3ZSBzdGFuZDoKCmBgYHtyIGNvbGRhdGEgY29sbmFtZXN9CnNjZV9saXN0IHw+CiAgcHVycnI6Om1hcCgKICAgIFwoc2NlKSBjb2xuYW1lcyhjb2xEYXRhKHNjZSkpIAogICkgCmBgYApXZSBzZWUgdGhlIHNhbWUgY29sdW1ucyBhbGwgYXJvdW5kIGluIHRoZSBzYW1lIG9yZGVyLCB3aGljaCBpcyBncmVhdCEKCkJ1dCB3aGF0IGlmIHRoZXJlIHdlcmUgZGlmZmVyZW50IGNvbHVtbnMgYWNyb3NzIG9iamVjdHMsIG9yIHRoZXkgd2VyZSBkaWZmZXJlbnRseSBvcmRlcmVkPwpJbiB0aGF0IGNhc2UsIHdlIGNvdWxkIGZpbmQgdGhlIGludGVyc2VjdGlvbiBvZiBjb2x1bW4gbmFtZXMgbGlrZSB3ZSBkaWQgYWJvdmUgZm9yIGdlbmVzLCBhbmQgdXNlIHRoYXQgdG8gcmUtb3JkZXIgYW5kIHN1YnNldCBhbGwgYGNvbERhdGFgIHNsb3RzIGluIG91ciBjdXN0b20gZm9ybWF0dGluZyBmdW5jdGlvbi4KCgojIyMjIyBDaGVjayB0aGUgYXNzYXlzCgpOZXh0LCB3ZSdsbCBtYWtlIHN1cmUgdGhhdCBhbGwgb2JqZWN0cyBzaGFyZSB0aGUgc2FtZSBhc3NheXM6CgpgYGB7ciBhc3NheSBuYW1lcywgbGl2ZSA9IFRSVUV9CiMgcHJpbnQgYWxsIHRoZSBhc3NheSBuYW1lcwpzY2VfbGlzdCB8PgogIHB1cnJyOjptYXAoYXNzYXlOYW1lcykKYGBgCkFnYWluLCBhbGwgb2JqZWN0cyBhcmUgY29tcGF0aWJsZSBhbHJlYWR5IHdpdGggYm90aCBoYXZpbmcgYSBgY291bnRzYCBhbmQgYGxvZ2NvdW50c2AgYXNzYXkuCgpJbiB5b3VyIG93biBkYXRhIGV4cGxvcmF0aW9uLCBpZiB5b3UgZW5jb3VudGVyIFNDRXMgdG8gbWVyZ2UgdGhhdCBoYXZlIGV4dHJhbmVvdXMgYXNzYXlzIHRoYXQgeW91IGRvbid0IG5lZWQsIHlvdSBjYW4gcmVtb3ZlIHRoZW0gYnkgc2V0dGluZyB0aGVtIHRvIGBOVUxMYCBpbiB5b3VyIGN1c3RvbSBmb3JtYXR0aW5nIGZ1bmN0aW9uLCBlLmcuIGBhc3NheShzY2UsICJhc3NheV90b19yZW1vdmUiKSA8LSBOVUxMYC4KCiMjIyMjIENoZWNrIHRoZSBgcm93RGF0YWAgY29udGVudHMKCk9uZSBvZiB0aGUgb3RoZXIgaXRlbXMgd2Ugc2FpZCB3ZSdkIG5lZWQgdG8gdGhpbmsgYWJvdXQgaXMgdGhlIGByb3dEYXRhYCwgd2hpY2ggY29udGFpbnMgZ2VuZSBtZXRhZGF0YS4KVGhpcyBzbG90IGlzIGludGVyZXN0aW5nIGJlY2F1c2Ugc29tZSBvZiBpdHMgY29sdW1ucyBhcmUgc3BlY2lmaWMgdG8gdGhlIGdpdmVuIHNhbXBsZSwgd2hpbGUgb3RoZXJzIGFyZSBnZW5lcmFsOgoKYGBge3IgbGl0dGxlIGhlYWQgcm93ZGF0YX0Kc2NlX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChzY2UpIGhlYWQocm93RGF0YShzY2UpLCAzKSAjIG9ubHkgcHJpbnQgMyByb3dzIGZvciBzcGFjZSEKICApCmBgYAoKVGhlIGNvbHVtbiBgZ2VuZV9zeW1ib2xgIGlzIG5vdCBzYW1wbGUtc3BlY2lmaWMgLSBpdCBqdXN0IHByb3ZpZGVzIHRoZSBjb3JyZXNwb25kaW5nIGdlbmUgc3ltYm9sIHRvIHRoZSBFbnNlbWJsIGlkcyBzZWVuIGhlcmUgYXMgcm93IG5hbWVzLgpUaGUgY29sdW1ucyBgbWVhbmAgYW5kIGBkZXRlY3RlZGAsIGhvd2V2ZXIsIGFyZSBzYW1wbGUtc3BlY2lmaWMgLSB0aGV5IGNvbnRhaW4gc2FtcGxlLXNwZWNpZmljIHN0YXRpc3RpY3MgYWJvdXQgZ2VuZSBleHByZXNzaW9uLgoKVGhpcyBtZWFucyB3ZSBkZWZpbml0ZWx5IG5lZWQgdG8gdXBkYXRlIHRoZSBjb2x1bW4gbmFtZXMgYG1lYW5gIGFuZCBgZGV0ZWN0ZWRgIHRvIGluY2x1ZGUgdGhlIHNhbXBsZSBpZC4KQnV0LCB3ZSBkb24ndCBuZWVkIGEgc2VwYXJhdGUgYGdlbmVfc3ltYm9sYCBjb2x1bW4gZm9yIGVhY2ggc2FtcGxlLCBzbyB3ZSBjYW4gbGVhdmUgdGhhdCBvbmUgYWxvbmUgYXMganVzdCBgZ2VuZV9zeW1ib2xgLiAKT25jZSB3ZSBldmVudHVhbGx5IG1lcmdlLCBvbmx5IG9uZSBgZ2VuZV9zeW1ib2xgIGNvbHVtbiB3aWxsIGJlIGxlZnQgaW4gdGhlIGZpbmFsIG9iamVjdCBzaW5jZSBpdCBpcyB0aGUgc2FtZSBhY3Jvc3MgYWxsIHRoZSBTQ0VzLgoKV2UnbGwgc2hvdyBvbmUgd2F5IHRvIGRvIHRoaXMgaW4gb3VyIGN1c3RvbSBmdW5jdGlvbiwgYnV0IGl0J3Mgd29ydGggbm90aW5nIHRoZXJlJ3Mgbm90aGluZyBfd3JvbmdfIHdpdGggYWxzbyBhZGRpbmcgdGhlIHNhbXBsZSBpZCB0byB0aGUgYGdlbmVfc3ltYm9sYCBjb2x1bW47IHlvdSdsbCBqdXN0IGVuZCB1cCB3aXRoIGEgYnVuY2ggb2YgcmVkdW5kYW50IGdlbmUgc3ltYm9sIGNvbHVtbnMuCgoKIyMjIyBSZWZvcm1hdCB0aGUgU0NFIG9iamVjdHMKCkFzIHlvdSBjYW4gc2VlLCB0aGVyZSdzIGEgbG90IG9mIG1vdmluZyBwYXJ0cyB0byBjb25zaWRlciEKQWdhaW4sIHRoZXNlIG1vdmluZyBwYXJ0cyBtYXkgKHdpbGwhKSBkaWZmZXIgZm9yIFNDRXMgdGhhdCB5b3UgYXJlIHdvcmtpbmcgd2l0aCwgc28geW91IGhhdmUgdG8gZXhwbG9yZSB5b3VyIG93biBTQ0VzIGluIGRlcHRoIHRvIHByZXBhcmUgZm9yIG1lcmdpbmcuCgpCYXNlZCBvbiBvdXIgZXhwbG9yYXRpb24sIGhlcmUgaXMgYSBzY2hlbWF0aWMgb2YgaG93IG9uZSBvZiB0aGUgU0NFIG9iamVjdHMgd2lsbCB1bHRpbWF0ZWx5IGJlIG1vZGlmaWVkIGludG8gdGhlIGZpbmFsIG1lcmdlZCBTQ0U6CgohW10oZGlhZ3JhbXMvdGVjaG5pY2FsX21lcmdlX3NjZS5wbmcpCgoKV2UnbGwgd3JpdGUgYSBfY3VzdG9tIGZ1bmN0aW9uXyAoc2VlbiBpbiB0aGUgY2h1bmsgYmVsb3cpIHRhaWxvcmVkIHRvIG91ciB3cmFuZ2xpbmcgc3RlcHMgdGhhdCBwcmVwYXJlcyBhIHNpbmdsZSBTQ0Ugb2JqZWN0IGZvciBtZXJnaW5nLgpXZSdsbCB0aGVuIHVzZSBvdXIgbmV3IGBwdXJycjo6bWFwKClgIHByb2dyYW1taW5nIHNraWxscyB0byBydW4gdGhpcyBmdW5jdGlvbiBvdmVyIHRoZSBgc2NlX2xpc3RgLgpUaGlzIHdpbGwgZ2l2ZSB1cyBhIG5ldyBsaXN0IG9mIGZvcm1hdHRlZCBTQ0VzIHRoYXQgd2UgY2FuIHByb2NlZWQgdG8gbWVyZ2UuCkl0J3MgaW1wb3J0YW50IHRvIHJlbWVtYmVyIHRoYXQgdGhlIGBmb3JtYXRfc2NlKClgIGZ1bmN0aW9uIHdyaXR0ZW4gYmVsb3cgaXMgbm90IGEgZnVuY3Rpb24gZm9yIGdlbmVyYWwgdXNlIOKAkyBpdCdzIGJlZW4gcHJlY2lzZWx5IHdyaXR0ZW4gdG8gbWF0Y2ggdGhlIHByb2Nlc3Npbmcgd2UgbmVlZCB0byBkbyBmb3IgX3RoZXNlXyBTQ0VzLCBhbmQgZGlmZmVyZW50IFNDRXMgeW91IHdvcmsgd2l0aCB3aWxsIHJlcXVpcmUgZGlmZmVyZW50IHR5cGVzIG9mIHByb2Nlc3NpbmcuCgpXZSBhbHNvIGluY2x1ZGUgcm94eWdlbi1zdHlsZSBjb21tZW50cyBmb3IgdGhpcyBmdW5jdGlvbiwgd2hpY2ggY2FuIGJlIGEgaGVscGZ1bCBjb25zaXN0ZW50IHdheSB0byBkb2N1bWVudCB5b3VyIGNvZGUgaWYgeW91IGxpa2UgaXQgLSB3ZSd2ZSBldmVuIHdyaXR0ZW4gYSBibG9nIHBvc3QgYWJvdXQgaXQgOikgKDxodHRwczovL3d3dy5jY2RhdGFsYWIub3JnL2Jsb2cvZG9udC1tYWtlLW1lLXdyaXRlLXRpcHMtZm9yLWF2b2lkaW5nLXR5cGluZy1pbi1yc3R1ZGlvPikuCgpgYGB7ciBmb3JtYXRfc2NlIGZ1bmN0aW9ufQojJyBDdXN0b20gZnVuY3Rpb24gdG8gZm9ybWF0IGFuIFNDRSBiZWZvcmUgbWVyZ2luZwojJwojJyBAcGFyYW0gc2NlIFNDRSBvYmplY3QgdG8gZm9ybWF0CiMnIEBwYXJhbSBzYW1wbGVfbmFtZSBOYW1lIG9mIHRoZSBzYW1wbGUKIycgQHBhcmFtIHNoYXJlZF9nZW5lcyBWZWN0b3Igb2Ygc2hhcmVkIGdlbmVzIGFjcm9zcyBhbGwgU0NFIG9iamVjdHMKIycKIycgQHJldHVybnMgQW4gdXBkYXRlZCBTQ0Ugb2JqZWN0IHJlYWR5IGZvciBtZXJnaW5nCmZvcm1hdF9zY2UgPC0gZnVuY3Rpb24oCiAgc2NlLCAKICBzYW1wbGVfbmFtZSwgCiAgc2hhcmVkX2dlbmVzCikgewogIAogICMjIyBSZW1vdmUgdGhlIHNpbmdsZS1zYW1wbGUgcmVkdWNlZCBkaW1lbnNpb25zIAogICMgV2UgZG8gdGhpcyBmaXJzdCBzaW5jZSBpdCBtYWtlcyB0aGUgb2JqZWN0IGEgbG90IHNtYWxsZXIgZm9yIHRoZSByZXN0IG9mIHRoaXMgY29kZSEKICByZWR1Y2VkRGltcyhzY2UpIDwtIE5VTEwKICAKICAjIyMgQWRkIGRlZGljYXRlZCBzYW1wbGUgaW5kaWNhdG9yIGNvbHVtbiB0byB0aGUgY29sRGF0YSBzbG90CiAgIyBSZWNhbGwsIHRoZSBgc2NlJGAgc2hvcnRjdXQgcG9pbnRzIHRvIHRoZSBjb2xEYXRhCiAgc2NlJHNhbXBsZSA8LSBzYW1wbGVfbmFtZQoKICAjIyMgRW5zdXJlIG9iamVjdHMgaGF2ZSB0aGUgc2FtZSBnZW5lcyBpbiB0aGUgc2FtZSBvcmRlcgogICMgVXNlIHRoZSBzaGFyZWRfZ2VuZXMgdmVjdG9yIHRvIGluZGV4IGdlbmVzIHRvIHRoZSBpbnRlcnNlY3Rpb24KICAjIERvaW5nIHRoaXMgYm90aCBzdWJzZXRzIHRvIGp1c3QgdGhvc2UgZ2VuZXMsIGFuZCByZW9yZGVycyEKICBzY2UgPC0gc2NlW3NoYXJlZF9nZW5lcywgXQogIAogICMjIyBUaGVyZSBpcyBubyBhZGRpdGlvbmFsIHdyYW5nbGluZyB0byBkbyBmb3IgdGhlIGNvbERhdGEgY29sdW1uIG5hbWVzIG9yIGFzc2F5cy4KICAjIyMgQnV0IGlmIHRoZXJlIHdlcmUsIHlvdSBjb3VsZCBhZGQgeW91ciBjdXN0b20gY29kZSB0byBkbyBzbyBoZXJlLgogICMjIyBZb3VyIGN1c3RvbSBmdW5jdGlvbiBtYXkgbmVlZCBhZGRpdGlvbmFsIGFyZ3VtZW50cyBmb3IgdGhpcywgdG9vLgoKICAjIyMgRW5zdXJlIGNlbGwgaWRzIGFyZSBpZGVudGlmaWFibGUgYW5kIGZ1bGx5IHVuaXF1ZSAKICAjIFVwZGF0ZSB0aGUgU0NFIG9iamVjdCBjb2x1bW4gbmFtZXMgKGNlbGwgaWRzKSBieSBwcmVwZW5kaW5nIHRoZSBgc2FtcGxlX25hbWVgCiAgY29sbmFtZXMoc2NlKSA8LSBnbHVlOjpnbHVlKCJ7c2FtcGxlX25hbWV9LXtjb2xuYW1lcyhzY2UpfSIpCgogICMjIyBFbnN1cmUgdGhlIHJvd0RhdGEgY29sdW1ucyBjYW4gYmUgaWRlbnRpZmllZAogICMgUmVjYWxsLCB3ZSB3YW50IHRvIGxlYXZlIGBnZW5lX3N5bWJvbGAgYWxvbmUsIGJ1dCBhZGQgdGhlIGBzYW1wbGVfbmFtZWAgdG8gdGhlIHJlc3QKICByb3dkYXRhX25hbWVzIDwtIGNvbG5hbWVzKHJvd0RhdGEoc2NlKSkKICAjIHByZWZpeCByb3dEYXRhIG5hbWVzIHdpdGggdGhlIHNhbXBsZSBuYW1lLCBleGNlcHQgZm9yIGdlbmUgc3ltYm9scwogIG5ld19yb3dkYXRhX25hbWVzIDwtIGlmZWxzZSgKICAgIHJvd2RhdGFfbmFtZXMgPT0gImdlbmVfc3ltYm9sIiwKICAgICJnZW5lX3N5bWJvbCIsCiAgICBnbHVlOjpnbHVlKCJ7c2FtcGxlX25hbWV9LXtyb3dkYXRhX25hbWVzfSIpCiAgKQogIGNvbG5hbWVzKHJvd0RhdGEoc2NlKSkgPC0gbmV3X3Jvd2RhdGFfbmFtZXMKICAKICAjIyMgRW5zdXJlIG1ldGFkYXRhIHNsb3QgZmllbGRzIGNhbiBiZSBpZGVudGlmaWVkCiAgIyBXZSdsbCBzaW1wbHkgcHJlcGVuZCB0aGUgYHNhbXBsZV9uYW1lYCB0byBhbGwgZmllbGRzIGZvciB0aGlzIHNsb3QKICBuYW1lcyhtZXRhZGF0YShzY2UpKSA8LSBnbHVlOjpnbHVlKCJ7c2FtcGxlX25hbWV9LXtuYW1lcyhtZXRhZGF0YShzY2UpKX0iKQogIAogIAogICMjIyBGaW5hbGx5LCB3ZSBjYW4gcmV0dXJuIHRoZSBmb3JtYXR0ZWQgU0NFIG9iamVjdAogIHJldHVybihzY2UpICAKfQpgYGAKClRvIHJ1biB0aGlzIGZ1bmN0aW9uLCB3ZSdsbCB1c2UgdGhlIGBwdXJycjo6bWFwMigpYCBmdW5jdGlvbiwgYSByZWxhdGl2ZSBvZiBgcHVycnI6Om1hcCgpYCB0aGF0IGFsbG93cyB5b3UgdG8gbG9vcCBvdmVyIF90d29fIGlucHV0IGxpc3RzL3ZlY3RvcnMuCkluIG91ciBjYXNlLCB3ZSB3YW50IHRvIHJ1biBgZm9ybWF0X3NjZSgpYCBvdmVyIHBhaXJlZCBgc2NlX2xpc3RgIGl0ZW1zIGFuZCBgc2NlX2xpc3RgIG5hbWVzLgoKYGBge3IgZm9ybWF0IHNjZXMgZm9yIG1lcmdlLCBsaXZlID0gVFJVRX0KIyBXZSBjYW4gdXNlIGBwdXJycjo6bWFwMigpYCB0byBsb29wIG92ZXIgdHdvIGxpc3QvdmVjdG9yIGFyZ3VtZW50cwpzY2VfbGlzdF9mb3JtYXR0ZWQgPC0gcHVycnI6Om1hcDIoCiAgIyBFYWNoICJpdGVyYXRpb24iIHdpbGwgbWFyY2ggZG93biB0aGUgZmlyc3QgdHdvCiAgIyAgYXJndW1lbnRzIGBzY2VfbGlzdGAgYW5kIGBuYW1lcyhzY2VfbGlzdClgIGluIG9yZGVyCiAgc2NlX2xpc3QsCiAgbmFtZXMoc2NlX2xpc3QpLAogIFwoc2NlLCBzYW1wbGVfbmFtZSkgZm9ybWF0X3NjZShzY2UsIHNhbXBsZV9uYW1lLCBzaGFyZWRfZ2VuZXMpIAopCgojIFByaW50IGZvcm1hdHRlZCBTQ0UgbGlzdApzY2VfbGlzdF9mb3JtYXR0ZWQKYGBgCgooUHNzdCwgbGlrZSBgcHVycnJgIGFuZCB3YW50IHRvIGRpdmUgZGVlcGVyPyBDaGVjayBvdXQgW3RoZSBgcHVycnI6OmltYXAoKWAgZnVuY3Rpb25dKGh0dHBzOi8vcHVycnIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvaW1hcC5odG1sKSEpCgoKIyMjIFBlcmZvcm0gdGhlIG1lcmdpbmcKCgpBdCBsb25nIGxhc3QsIHdlIGFyZSByZWFkeSB0byBtZXJnZSB0aGUgU0NFcywgd2hpY2ggd2UnbGwgZG8gdXNpbmcgdGhlIFIgZnVuY3Rpb24gYGNiaW5kKClgLgpUaGUgYGNiaW5kKClgIGZ1bmN0aW9uIGlzIG9mdGVuIHVzZWQgdG8gY29tYmluZSBkYXRhIGZyYW1lcyBvciBtYXRyaWNlcyBieSBjb2x1bW4sIGkuZS4gInN0YWNrIiB0aGVtIG5leHQgdG8gZWFjaCBvdGhlci4KVGhlIHNhbWUgcHJpbmNpcGxlIGFwcGxpZXMgaGVyZSwgYnV0IHdoZW4gcnVuIG9uIFNDRSBvYmplY3RzLCBgY2JpbmQoKWAgd2lsbCBjcmVhdGUgYSBuZXcgU0NFIG9iamVjdCBieSBjb21iaW5pbmcgYGNvdW50c2AgYW5kIGBsb2djb3VudHNgIG1hdHJpY2VzIGJ5IGNvbHVtbi4KRm9sbG93aW5nIHRoYXQgc3RydWN0dXJlLCBvdGhlciBTQ0Ugc2xvdHMgKGBjb2xEYXRhYCwgYHJvd0RhdGFgLCByZWR1Y2VkIGRpbWVuc2lvbnMsIGFuZCBvdGhlciBtZXRhZGF0YSkgYXJlIGNvbWJpbmVkIGFwcHJvcHJpYXRlbHkuCgpTaW5jZSB3ZSBuZWVkIHRvIGFwcGx5IGBjYmluZCgpYCB0byBhIF9saXN0XyBvZiBvYmplY3RzLCB3ZSBuZWVkIHRvIHVzZSBzb21lIHNsaWdodGx5LWduYXJseSBzeW50YXg6IFdlJ2xsIHVzZSB0aGUgZnVuY3Rpb24gYGRvLmNhbGwoKWAsIHdoaWNoIGFsbG93cyB0aGUgYGNiaW5kKClgIGlucHV0IHRvIGJlIGEgbGlzdCBvZiBvYmplY3RzIHRvIGNvbWJpbmUuCgpgYGB7ciBtZXJnZXMgc2NlcywgbGl2ZSA9IFRSVUV9CiMgTWVyZ2UgU0NFIG9iamVjdHMKbWVyZ2VkX3NjZSA8LSBkby5jYWxsKGNiaW5kLCBzY2VfbGlzdF9mb3JtYXR0ZWQpCgojIFByaW50IHRoZSBtZXJnZWRfc2NlIG9iamVjdAptZXJnZWRfc2NlCmBgYAoKV2Ugbm93IGhhdmUgYSBzaW5nbGUgbWVyZ2VkIFNDRSBvYmplY3QgdGhhdCBjb250YWlucyBhbGwgY2VsbHMgZnJvbSBhbGwgc2FtcGxlcyB3ZSdkIGxpa2UgdG8gaW50ZWdyYXRlLgoKTGV0J3MgdGFrZSBhIHBlZWsgYXQgc29tZSBvZiB0aGUgaW5uYXJkcyBvZiB0aGlzIG5ldyBTQ0Ugb2JqZWN0OgoKYGBge3IgZXhwbG9yZSBtZXJnZWRfc2NlLCBsaXZlID0gVFJVRX0KIyBIb3cgbWFueSBzYW1wbGVzLCBhbmQgY2VsbHMgcGVyIHNhbXBsZT8KdGFibGUoY29sRGF0YShtZXJnZWRfc2NlKSRzYW1wbGUpCgojIFdoYXQgYXJlIHRoZSBuZXcgY2VsbCBpZHMgKGNvbHVtbiBuYW1lcyk/CmhlYWQoY29sbmFtZXMobWVyZ2VkX3NjZSkpCnRhaWwoY29sbmFtZXMobWVyZ2VkX3NjZSkpCgojIFdoYXQgZG9lcyByb3dEYXRhIGxvb2sgbGlrZT8KaGVhZChyb3dEYXRhKG1lcmdlZF9zY2UpKQpgYGAKCgojIyBJbnRlZ3JhdGUgc2FtcGxlcwoKIVtTaW5nbGUtY2VsbCByb2FkbWFwOiBJbnRlZ3JhdGVdKGRpYWdyYW1zL3JvYWRtYXBfbXVsdGlfaW50ZWdyYXRlLnBuZykKClNvIGZhciwgd2UndmUgY3JlYXRlZCBhIGBtZXJnZWRfc2NlYCBvYmplY3Qgd2hpY2ggaXMgKGFsbW9zdCEpIHJlYWR5IGZvciBpbnRlZ3JhdGlvbi4KClRoZSBpbnRlZ3JhdGlvbiBtZXRob2RzIHdlJ2xsIGJlIHVzaW5nIGhlcmUgYWN0dWFsbHkgcGVyZm9ybSBiYXRjaCBjb3JyZWN0aW9uIG9uIGEgcmVkdWNlZCBkaW1lbnNpb24gcmVwcmVzZW50YXRpb24gb2YgdGhlIG5vcm1hbGl6ZWQgZ2VuZSBleHByZXNzaW9uIHZhbHVlcywgd2hpY2ggaXMgbW9yZSBlZmZpY2llbnQuCmBmYXN0TU5OYCBhbmQgYGhhcm1vbnlgIHNwZWNpZmljYWxseSB1c2UgUENBIGZvciB0aGlzLCBidXQgYmUgYXdhcmUgdGhhdCBkaWZmZXJlbnQgaW50ZWdyYXRpb24gbWV0aG9kcyBtYXkgdXNlIG90aGVyIGtpbmRzIG9mIHJlZHVjZWQgZGltZW5zaW9ucy4KCkJlZm9yZSBtZXJnaW5nLCBvdXIgb2JqZWN0cyBoYWQgcmVkdWNlZCBkaW1lbnNpb24gcmVwcmVzZW50YXRpb25zIGNhbGN1bGF0ZWQgb24gZWFjaCBpbmRpdmlkdWFsIFNDRSwgYW5kIHdlIHJlbW92ZWQgdGhlbSB3aGVuIHByZXBhcmluZyBmb3IgbWVyZ2UuCldlIHJlbW92ZWQgdGhlbSBiZWNhdXNlIHdlIGRvbid0IGFjdHVhbGx5IHdhbnQgdG8gdXNlIHRoZW0gYW55bW9yZSEKVGhpcyBpcyBiZWNhdXNlIHBhcnQgb2YgdGhlaXIgY2FsY3VsYXRpb24gaW52b2x2ZXMgc2NhbGluZyB0aGUgcmF3IGRhdGEgdG8gY2VudGVyIHRoZSBtZWFuLgpXaGVuIHNhbXBsZXMgYXJlIHNlcGFyYXRlbHkgY2VudGVyZWQsIF9hbGxfIG9mIHRoZW0gd2lsbCBiZSBjZW50ZXJlZCBhdCB6ZXJvLCBtYWtpbmcgaXQgbG9vayBsaWtlIHRoZSBkYXRhc2V0cyBhcmUgYWxyZWFkeSBwcmV0dHkgb3ZlcmxhcHBpbmcgd2hlbiB5b3UgcGxvdCB0aGVpciBVTUFQcyB0b2dldGhlci4KQnV0LCB0aGlzIGlzIGp1c3QgYSBtYXRoZW1hdGljYWwgYXJ0aWZhY3Qgb2YgaG93IGRpbWVuc2lvbiByZWR1Y3Rpb24gaXMgcGVyZm9ybWVkLgoKU28sIHdlJ2xsIGJlZ2luIGJ5IHJlLWNhbGN1bGF0aW5nIFBDQSBhbmQgVU1BUCBvbiB0aGUgbWVyZ2VkIG9iamVjdCBpbiBhIHdheSB0aGF0IHRha2VzIGJhdGNoZXMgaW50byBjb25zaWRlcmF0aW9uLiAKRm9yIGlucHV0IHRvIGludGVncmF0aW9uLCB3ZSdsbCB3YW50IHRoZSByZWR1Y2VkIGRpbWVuc2lvbiBjYWxjdWxhdGlvbnMgdG8gY29uc2lkZXIgbm9ybWFsaXplZCBnZW5lIGV4cHJlc3Npb24gdmFsdWVzIGZyb20gYWxsIHNhbXBsZXMgc2ltdWx0YW5lb3VzbHkuClNvIHdlJ2xsIG5lZWQgdG8gcmVjYWxjdWxhdGUgUENBIChhbmQgVU1BUCBmb3IgdmlzdWFsaXphdGlvbikgb24gdGhlIG1lcmdlZCBvYmplY3QuCgpGaXJzdCwgYXMgdXN1YWwsIHdlJ2xsIGRldGVybWluZSB0aGUgaGlnaC12YXJpYW5jZSBnZW5lcyB0byB1c2UgZm9yIFBDQSBmcm9tIHRoZSBgbWVyZ2VkX3NjZWAgb2JqZWN0LgpGb3IgdGhpcywgd2UnbGwgbmVlZCB0byBwcm92aWRlIHRoZSBhcmd1bWVudCBgYmxvY2sgPSBtZXJnZWRfc2NlJHNhbXBsZWAgd2hlbiBtb2RlbGluZyBnZW5lIHZhcmlhbmNlLCB3aGljaCB0ZWxscyBgc2NyYW46Om1vZGVsR2VuZVZhcigpYCB0byBmaXJzdCBtb2RlbCB2YXJpYW5jZSBzZXBhcmF0ZWx5IGZvciBlYWNoIGJhdGNoIGFuZCB0aGVuIGNvbWJpbmUgdGhvc2UgbW9kZWxpbmcgc3RhdGlzdGljcy4KKFBzc3Q6IGlzbid0IGl0IGhhbmR5IHdlIGNyZWF0ZWQgdGhhdCBgc2FtcGxlYCBjb2x1bW4gd2hlbiBtZXJnaW5nPyEpCgpgYGB7ciBjYWxjIG1lcmdlZCBodiBnZW5lc30KIyBTcGVjaWZ5IHRoZSBudW1iZXIgb2YgZ2VuZXMgdG8gaWRlbnRpZnkKbnVtX2dlbmVzIDwtIDIwMDAKCiMgQ2FsY3VsYXRlIHZhcmlhdGlvbiBmb3IgZWFjaCBnZW5lCmdlbmVfdmFyaWFuY2UgPC0gc2NyYW46Om1vZGVsR2VuZVZhcihtZXJnZWRfc2NlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBzcGVjaWZ5IHRoZSBncm91cGluZyBjb2x1bW46CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBibG9jayA9IG1lcmdlZF9zY2Ukc2FtcGxlKQoKIyBHZXQgdGhlIHRvcCBgbnVtX2dlbmVzYCBoaWdoLXZhcmlhbmNlIGdlbmVzIHRvIHVzZSBmb3IgZGltZW5zaW9uIHJlZHVjdGlvbgpodl9nZW5lcyA8LSBzY3Jhbjo6Z2V0VG9wSFZHcyhnZW5lX3ZhcmlhbmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gbnVtX2dlbmVzKQpgYGAKClRvIGNhbGN1bGF0ZSB0aGUgUENBIG1hdHJpeCBpdHNlbGYsIHdlJ2xsIHVzZSBhbiBhcHByb2FjaCBmcm9tIHRoZSBgYmF0Y2hlbG9yYCBwYWNrYWdlLCB3aGljaCBpcyB0aGUgUiBwYWNrYWdlIHRoYXQgY29udGFpbnMgdGhlIGBmYXN0TU5OYCBtZXRob2QuClRoZSBbYGJhdGNoZWxvcjo6bXVsdGlCYXRjaFBDQSgpYF0oaHR0cHM6Ly9yZHJyLmlvL2Jpb2MvYmF0Y2hlbG9yL21hbi9tdWx0aUJhdGNoUENBLmh0bWwpIGZ1bmN0aW9uIGNhbGN1bGF0ZXMgYSBiYXRjaC13ZWlnaHRlZCBQQ0EgbWF0cml4LgpUaGlzIHdlaWdodGluZyBlbnN1cmVzIHRoYXQgYWxsIGJhdGNoZXMsIHdoaWNoIG1heSBoYXZlIHZlcnkgZGlmZmVyZW50IG51bWJlcnMgb2YgY2VsbHMsIGNvbnRyaWJ1dGUgZXF1YWxseSB0byB0aGUgb3ZlcmFsbCBzY2FsaW5nLgoKYGBge3IgbWFrZSBtZXJnZWRfcGNhLCBsaXZlID0gVFJVRX0KIyBVc2UgYmF0Y2hlbG9yIHRvIGNhbGN1bGF0ZSBQQ0EgZm9yIG1lcmdlZF9zY2UsIGNvbnNpZGVyaW5nIG9ubHkKIyAgdGhlIGhpZ2gtdmFyaWFuY2UgZ2VuZXMKIyBXZSdsbCBuZWVkIHRvIGluY2x1ZGUgdGhlIGFyZ3VtZW50IGBwcmVzZXJ2ZS5zaW5nbGUgPSBUUlVFYCB0byBnZXQKIyAgYSBzaW5nbGUgbWF0cml4IHdpdGggYWxsIHNhbXBsZXMgYW5kIG5vdCBzZXBhcmF0ZSBtYXRyaWNlcyBmb3IgZWFjaCBzYW1wbGUKbWVyZ2VkX3BjYSA8LSBiYXRjaGVsb3I6Om11bHRpQmF0Y2hQQ0EobWVyZ2VkX3NjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3Vic2V0LnJvdyA9IGh2X2dlbmVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXRjaCA9IG1lcmdlZF9zY2Ukc2FtcGxlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVzZXJ2ZS5zaW5nbGUgPSBUUlVFKQpgYGAKCkxldCdzIGhhdmUgYSBsb29rIGF0IHRoZSBvdXRwdXQ6CmBgYHtyIHByaW50IG1lcmdlZF9wY2EsIGxpdmUgPSBUUlVFfQojIFRoaXMgb3V0cHV0IGlzIG5vdCB2ZXJ5IGludGVyZXN0aW5nIQptZXJnZWRfcGNhCmBgYAoKV2UgY2FuIHVzZSBpbmRleGluZyBgW1sxXV1gIHRvIHNlZSB0aGUgUENBIG1hdHJpeCBjYWxjdWxhdGVkLCBsb29raW5nIGF0IGEgc21hbGwgc3Vic2V0IGZvciBjb252ZW5pZW5jZToKCmBgYHtyIHByaW50IG1lcmdlZF9wY2EgaW5kZXhlZCwgbGl2ZSA9IFRSVUV9Cm1lcmdlZF9wY2FbWzFdXVsxOjUsMTo1XQpgYGAKCldlIGNhbiBub3cgaW5jbHVkZSB0aGlzIFBDQSBtYXRyaXggaW4gb3VyIGBtZXJnZWRfc2NlYCBvYmplY3Q6CgpgYGB7ciBhZGQgbWVyZ2VkX3BjYSwgbGl2ZSA9IFRSVUV9CiMgYWRkIFBDQSByZXN1bHRzIHRvIG1lcmdlZCBTQ0Ugb2JqZWN0CnJlZHVjZWREaW0obWVyZ2VkX3NjZSwgIlBDQSIpIDwtIG1lcmdlZF9wY2FbWzFdXQpgYGAKCk5vdyB0aGF0IHdlIGhhdmUgdGhlIFBDQSBtYXRyaXgsIHdlIGNhbiBwcm9jZWVkIHRvIGNhbGN1bGF0ZSBVTUFQIHRvIHZpc3VhbGl6ZSB0aGUgdW5jb3JyZWN0ZWQgbWVyZ2VkIGRhdGEuCgpgYGB7ciBjYWxjdWxhdGUgbWVyZ2VkIHVtYXAsIGxpdmUgPSBUUlVFfQptZXJnZWRfc2NlIDwtIHNjYXRlcjo6cnVuVU1BUChtZXJnZWRfc2NlKQpgYGAKCmBgYHtyIHBsb3QgdW5jb3JyZWN0ZWQgbWVyZ2VkIFVNQVB9CiMgVU1BUHMgc2NhbGVkIHRvZ2V0aGVyIHdoZW4gY2FsY3VsYXRlZCBmcm9tIHRoZSBtZXJnZWQgU0NFCnNjYXRlcjo6cGxvdFVNQVAoCiAgbWVyZ2VkX3NjZSwKICBjb2xvcl9ieSA9ICJzYW1wbGUiLAogICMgU29tZSBzdHlsaW5nIHRvIGhlbHAgdXMgc2VlIHRoZSBwb2ludHM6CiAgcG9pbnRfc2l6ZSA9IDAuNSwKICBwb2ludF9hbHBoYSA9IDAuMgopICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIsIG5hbWUgPSAic2FtcGxlIikgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAzLCBhbHBoYSA9IDEpKSkgKwogIGdndGl0bGUoIlVNQVAgY2FsY3VsYXRlZCBvbiBtZXJnZWRfc2NlIikKYGBgCgpXZSBzZWUgKG1vc3RseSkgZm91ciBzZXBhcmF0ZSBjbHVtcHMgcmVwcmVzZW50aW5nIHRoZSBmb3VyIGRpZmZlcmVudCBfbWVyZ2VkIGJ1dCBub3QgeWV0IGludGVncmF0ZWRfIHNhbXBsZXMuCldlIGNhbiB0aGluayBvZiB0aGlzIFVNQVAgYXMgb3VyICJiZWZvcmUiIFVNQVAsIGFuZCB3ZSBjYW4gY29tcGFyZSB0aGlzIHRvIHRoZSAiYWZ0ZXIiIFVNQVAgd2Ugc2VlIHBvc3QtaW50ZWdyYXRpb24uCgpMZXQncyBkaXNjdXNzIGEgbGl0dGxlIGZpcnN0OiBXaGF0IHZpc3VhbCBkaWZmZXJlbmNlcyBkbyB5b3UgdGhpbmsgdGhlIFVNQVAgb24gdGhlIGludGVncmF0ZWQgdmVyc2lvbiBvZiBkYXRhIHdpbGwgaGF2ZT8KV2hhdCBzaW1pbGFyaXRpZXMgZG8geW91IHRoaW5rIHRoZSBpbnRlZ3JhdGVkIFVNQVAgd2lsbCBoYXZlIHRvIHRoaXMgcGxvdD8KCgojIyMgSW50ZWdyYXRlIHdpdGggYGZhc3RNTk5gCgpGaW5hbGx5LCB3ZSdyZSByZWFkeSB0byBpbnRlZ3JhdGUhClRvIHN0YXJ0LCB3ZSdsbCB1c2UgdGhlIGBmYXN0TU5OYCBhcHByb2FjaCBmcm9tIHRoZSBCaW9jb25kdWN0b3IgW2BiYXRjaGVsb3JgIHBhY2thZ2VdKGh0dHA6Ly93d3cuYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy9yZWxlYXNlL2Jpb2MvaHRtbC9iYXRjaGVsb3IuaHRtbCkuCgpgZmFzdE1OTmAgdGFrZXMgYXMgaW5wdXQgdGhlIGBtZXJnZWRfc2NlYCBvYmplY3QgdG8gaW50ZWdyYXRlLCBhbmQgdGhlIGZpcnN0IHN0ZXAgaXQgcGVyZm9ybXMgaXMgYWN0dWFsbHkgdG8gcnVuIGBiYXRjaGVsb3I6Om11bHRpQmF0Y2hQQ0EoKWAgb24gdGhhdCBTQ0UuCkl0IHRoZW4gdXNlcyB0aGF0IGJhdGNoLXdlaWdodGVkIFBDQSBtYXRyaXggdG8gcGVyZm9ybSB0aGUgYWN0dWFsIGJhdGNoIGNvcnJlY3Rpb24uClRoZSBgYmF0Y2hgIGFyZ3VtZW50IGlzIHVzZWQgdG8gc3BlY2lmeSB0aGUgZGlmZmVyZW50IGdyb3VwaW5ncyB3aXRoaW4gdGhlIGBtZXJnZWRfc2NlYCAoaS5lLiB0aGUgb3JpZ2luYWwgc2FtcGxlIHRoYXQgZWFjaCBjZWxsIGJlbG9uZ3MgdG8pLCBhbmQgdGhlIGBzdWJzZXQucm93YCBhcmd1bWVudCBjYW4gb3B0aW9uYWxseSBiZSB1c2VkIHRvIHByb3ZpZGUgYSB2ZWN0b3Igb2YgaGlnaC12YXJpYW5jZSBnZW5lcyB0aGF0IHNob3VsZCBiZSBjb25zaWRlcmVkIGZvciB0aGlzIFBDQSBjYWxjdWxhdGlvbi4KYGZhc3RNTk5gIHdpbGwgcmV0dXJuIGFuIFNDRSBvYmplY3QgdGhhdCBjb250YWlucyBhIGJhdGNoLWNvcnJlY3RlZCBQQ0EuCkxldCdzIHJ1biBpdCBhbmQgc2F2ZSB0aGUgcmVzdWx0IHRvIGEgdmFyaWFibGUgY2FsbGVkIGBpbnRlZ3JhdGVkX3NjZWAuCgoKYGBge3IgcnVuIGZhc3Rtbm4sIGxpdmUgPSBUUlVFfQojIGludGVncmF0ZSB3aXRoIGZhc3RNTk4sIGFnYWluIHNwZWNpZnlpbmcgb25seSBvdXIgaGlnaC12YXJpYW5jZSBnZW5lcwppbnRlZ3JhdGVkX3NjZSA8LSBiYXRjaGVsb3I6OmZhc3RNTk4oCiAgbWVyZ2VkX3NjZSwKICBiYXRjaCA9IG1lcmdlZF9zY2Ukc2FtcGxlLAogIHN1YnNldC5yb3cgPSBodl9nZW5lcwopCmBgYAoKTGV0J3MgaGF2ZSBhIGxvb2sgYXQgdGhlIHJlc3VsdDoKCmBgYHtyIGZhc3Rtbm4gcmVzdWx0LCBsaXZlID0gVFJVRX0KIyBQcmludCB0aGUgaW50ZWdyYXRlZF9zY2Ugb2JqZWN0CmludGVncmF0ZWRfc2NlCmBgYAoKVGhlcmUgYXJlIGNvdXBsZSBwaWVjZXMgb2YgaW5mb3JtYXRpb24gaGVyZSBvZiBpbnRlcmVzdDoKCi0gVGhlIGBjb3JyZWN0ZWRgIHJlZHVjZWQgZGltZW5zaW9uIHJlcHJlc2VudHMgdGhlIGJhdGNoLWNvcnJlY3RlZCBQQ0EgdGhhdCBgZmFzdE1OTmAgY2FsY3VsYXRlZC4KLSBUaGUgYHJlY29uc3RydWN0ZWRgIGFzc2F5IHJlcHJlc2VudHMgdGhlIGJhdGNoLWNvcnJlY3RlZCBub3JtYWxpemVkIGV4cHJlc3Npb24gdmFsdWVzLCB3aGljaCBgZmFzdE1OTmAgImJhY2stY2FsY3VsYXRlZCIgZnJvbSB0aGUgYmF0Y2gtY29ycmVjdGVkIFBDQSAoYGNvcnJlY3RlZGApLgpHZW5lcmFsbHkgc3BlYWtpbmcsIHRoZXNlIGV4cHJlc3Npb24gdmFsdWVzIGFyZSBub3Qgc3RhbmQtYWxvbmUgdmFsdWVzIHRoYXQgeW91IHNob3VsZCB1c2UgZm9yIG90aGVyIGFwcGxpY2F0aW9ucyBsaWtlIGRpZmZlcmVudGlhbCBnZW5lIGV4cHJlc3Npb24sIGFzIGRlc2NyaWJlZCBpbiBbX09yY2hlc3RyYXRpbmcgU2luZ2xlIENlbGwgQW5hbHlzZXNfXShodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy8zLjE5L09TQ0EubXVsdGlzYW1wbGUvdXNpbmctY29ycmVjdGVkLXZhbHVlcy5odG1sKS4KSWYgdGhlIGBzdWJzZXQucm93YCBhcmd1bWVudCBpcyBwcm92aWRlZCAoYXMgaXQgd2FzIGhlcmUpLCBvbmx5IGdlbmVzIHByZXNlbnQgaW4gYHN1YnNldC5yb3dgIHdpbGwgYmUgaW5jbHVkZWQgaW4gdGhlc2UgcmVjb25zdHJ1Y3RlZCBleHByZXNzaW9uIHZhbHVlcywgYnV0IHRoaXMgc2V0dGluZyBjYW4gYmUgb3ZlcnJpZGRlbiBzbyB0aGF0IGFsbCBnZW5lcyBoYXZlIHJlY29uc3RydWN0ZWQgZXhwcmVzc2lvbiB3aXRoIHRoZSBhcmd1bWVudCBgY29ycmVjdC5hbGwgPSBUUlVFYC4KCldlJ3JlIG1vc3RseSBpbnRlcmVzdGVkIGluIHRoZSBQQ0EgdGhhdCBgZmFzdE1OTmAgY2FsY3VsYXRlZCwgc28gbGV0J3Mgc2F2ZSB0aGF0IGluZm9ybWF0aW9uICh3aXRoIGFuIGluZm9ybWF0aXZlIGFuZCB1bmlxdWUgbmFtZSkgaW50byBvdXIgYG1lcmdlZF9zY2VgIG9iamVjdDoKCmBgYHtyIGZhc3Rtbm4gcGNzLCBsaXZlID0gVFJVRX0KIyBNYWtlIGEgbmV3IHJlZHVjZWREaW0gbmFtZWQgZmFzdG1ubl9QQ0EgZnJvbSB0aGUgY29ycmVjdGVkIHJlZHVjZWREaW0gaW4gaW50ZWdyYXRlZF9zY2UKcmVkdWNlZERpbShtZXJnZWRfc2NlLCAiZmFzdG1ubl9QQ0EiKSA8LSByZWR1Y2VkRGltKGludGVncmF0ZWRfc2NlLCAiY29ycmVjdGVkIikKYGBgCgpGaW5hbGx5LCB3ZSdsbCBjYWxjdWxhdGUgVU1BUCBmcm9tIHRoZXNlIGNvcnJlY3RlZCBQQ0EgbWF0cml4IGZvciB2aXN1YWxpemF0aW9uLgpJbiB0aGlzIGNhc2Ugd2UgbmVlZCB0byBzcGVjaWZ5IHR3byBhZGRpdGlvbmFsIGFyZ3VtZW50cyBzaW5jZSB3ZSdyZSB3b3JraW5nIHdpdGggbm9uLXN0YW5kYXJkIHJlZHVjZWQgZGltZW5zaW9uIG5hbWVzOgoKKyBgZGltcmVkID0gImZhc3Rtbm5fUENBImAsIHdoaWNoIHNwZWNpZmllcyB0aGUgZXhpc3RpbmcgcmVkdWNlZCBkaW1lbnNpb24gdG8gdXNlIGZvciB0aGUgY2FsY3VsYXRpb24KKyBgbmFtZSA9ICJmYXN0bW5uX1VNQVAiYCwgd2hpY2ggbmFtZXMgdGhlIGZpbmFsIFVNQVAgdGhhdCB0aGlzIGZ1bmN0aW9uIGNhbGN1bGF0ZXMKCmBgYHtyIGNhbGN1bGF0ZSBmYXN0bW5uIHVtYXAsIGxpdmUgPSBUUlVFfQojIENhbGN1bGF0ZSBVTUFQCm1lcmdlZF9zY2UgPC0gc2NhdGVyOjpydW5VTUFQKAogIG1lcmdlZF9zY2UsCiAgZGltcmVkID0gImZhc3Rtbm5fUENBIiwKICBuYW1lID0gImZhc3Rtbm5fVU1BUCIKKQpgYGAKCkZpcnN0LCBsZXQncyBwbG90IHRoZSBpbnRlZ3JhdGVkIFVNQVAgaGlnaGxpZ2h0aW5nIHRoZSBkaWZmZXJlbnQgYmF0Y2hlcy4KQSB3ZWxsLWludGVncmF0ZWQgZGF0YXNldCB3aWxsIHNob3cgYmF0Y2ggbWl4aW5nLCBidXQgYSBwb29ybHktaW50ZWdyYXRlZCBkYXRhc2V0IHdpbGwgc2hvdyBtb3JlIHNlcGFyYXRpb24gYW1vbmcgYmF0Y2hlcywgc2ltaWxhciB0byB0aGUgdW5jb3JyZWN0ZWQgVU1BUC4KTm90ZSB0aGF0IHRoaXMgaXMgYSBtb3JlIHF1YWxpdGF0aXZlIHdheSB0byBhc3Nlc3MgdGhlIHN1Y2Nlc3Mgb2YgaW50ZWdyYXRpb24sIGJ1dCB0aGVyZSBhcmUgZm9ybWFsIG1ldHJpY3Mgb25lIGNhbiB1c2UgdG8gYXNzZXNzIGJhdGNoIG1peGluZywgd2hpY2ggeW91IGNhbiByZWFkIG1vcmUgYWJvdXQgaW4gW3RoaXMgY2hhcHRlciBvZiBPU0NBXShodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy8zLjE5L09TQ0EubXVsdGlzYW1wbGUvY29ycmVjdGlvbi1kaWFnbm9zdGljcy5odG1sKS4KCmBgYHtyIHBsb3QgZmFzdG1ubiB1bWFwIGJhdGNoZXN9CnNjYXRlcjo6cGxvdFJlZHVjZWREaW0obWVyZ2VkX3NjZSwKICAgICAgICAgICAgICAgICAgICAgICAjIHBsb3QgdGhlIGZhc3RNTk4gY29vcmRpbmF0ZXMKICAgICAgICAgICAgICAgICAgICAgICBkaW1yZWQgPSAiZmFzdG1ubl9VTUFQIiwKICAgICAgICAgICAgICAgICAgICAgICAjIGNvbG9yIGJ5IHNhbXBsZQogICAgICAgICAgICAgICAgICAgICAgIGNvbG9yX2J5ID0gInNhbXBsZSIsCiAgICAgICAgICAgICAgICAgICAgICAgIyBTb21lIHN0eWxpbmcgdG8gaGVscCB1cyBzZWUgdGhlIHBvaW50czoKICAgICAgICAgICAgICAgICAgICAgICBwb2ludF9zaXplID0gMC41LAogICAgICAgICAgICAgICAgICAgICAgIHBvaW50X2FscGhhID0gMC4yKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiRGFyazIiLCBuYW1lID0gInNhbXBsZSIpICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMywgYWxwaGEgPSAxKSkpICsKICBnZ3RpdGxlKCJVTUFQIGFmdGVyIGludGVncmF0aW9uIHdpdGggZmFzdE1OTiIpCmBgYAoKVGhpcyBgZmFzdG1ubl9VTUFQYCBjZXJ0YWlubHkgbG9va3MgZGlmZmVyZW50IGZyb20gdGhlIG9uZSB3ZSBtYWRlIGJlZm9yZSBpbnRlZ3JhdGluZyEKV2hhdCBkaWZmZXJlbnQgdHJlbmRzIGRvIHlvdSBzZWU/CkRvIGFsbCBzYW1wbGVzIGxvb2sgImVxdWFsbHkgd2VsbCIgaW50ZWdyYXRlZCwgZnJvbSBhIGZpcnN0IGxvb2s/CgpJbXBvcnRhbnRseSwgb25lIHJlYXNvbiB0aGF0IGJhdGNoZXMgbWF5IHN0aWxsIGFwcGVhciBzZXBhcmF0ZWQgaW4gdGhlIGNvcnJlY3RlZCBVTUFQIGlzIGlmIHRoZXkgX3Nob3VsZF8gYmUgc2VwYXJhdGVkIC0gZm9yIGV4YW1wbGUsIG1heWJlIHR3byBiYXRjaGVzIGNvbnRhaW4gdmVyeSBkaWZmZXJlbnQgY2VsbCB0eXBlcywgaGF2ZSB2ZXJ5IGRpZmZlcmVudCBkaWFnbm9zZXMsIG9yIG1heSBiZSBmcm9tIGRpZmZlcmVudCBwYXRpZW50cy4KClJlY2FsbCBmcm9tIGVhcmxpZXIgdGhhdCB3ZSBjb252ZW5pZW50bHkgaGF2ZSBjZWxsIHR5cGUgYW5ub3RhdGlvbnMgaW4gb3VyIFNDRXMsIHNvIHdlIGNhbiBleHBsb3JlIHRob3NlIGhlcmUhCkxldCdzIHRha2UgYSBxdWljayBkZXRvdXIgdG8gc2VlIHdoYXQga2luZHMgb2YgY2VsbCB0eXBlcyBhcmUgaW4gdGhpcyBkYXRhIGJ5IG1ha2luZyBhIGJhcnBsb3Qgb2YgdGhlIGNlbGwgdHlwZXMgYWNyb3NzIHNhbXBsZXM6CgpgYGB7ciBleHBsb3JlIGNlbGx0eXBlc30KIyBDZWxsIHR5cGVzIGFyZSBpbiB0aGUgYGNlbGx0eXBlX2Jyb2FkYCBhbmQgYGNlbGx0eXBlX2ZpbmVgIGNvbHVtbnMKbWVyZ2VkX3NjZV9kZiA8LSBhcy5kYXRhLmZyYW1lKGNvbERhdGEobWVyZ2VkX3NjZSkpCgojIFVzZSBnZ3Bsb3QyIHRvIG1ha2UgYSBiYXJwbG90IHRoZSBjZWxsIHR5cGVzIGFjcm9zcyBzYW1wbGVzCmdncGxvdChtZXJnZWRfc2NlX2RmLAogICAgICAgYWVzKHggPSBzYW1wbGUsCiAgICAgICAgICAgZmlsbCA9IGNlbGx0eXBlX2Jyb2FkKSkgKwogICMgQmFycGxvdCBvZiBjZWxsdHlwZSBwcm9wb3J0aW9ucwogIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKSArCiAgIyBVc2UgYSBDVkQtZnJpZW5kbHkgY29sb3Igc2NoZW1lCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIsIG5hLnZhbHVlID0gImdyZXk4MCIpICsKICAjIGN1c3RvbWl6ZSB5LWF4aXMgbGFiZWwKICBsYWJzKHkgPSAiUHJvcG9ydGlvbiIpICsKICAjIG5pY2VyIHRoZW1lCiAgdGhlbWVfYncoKQpgYGAKCldlIHNlZSB0aGF0IFR1bW9yIGNlbGwgdHlwZXMgYXJlIGJ5IGZhciB0aGUgbW9zdCBwcmV2YWxlbnQgYWNyb3NzIGFsbCBzYW1wbGVzLCBhbmQgbm9ybWFsIHRpc3N1ZSBjZWxsIHR5cGVzIGFyZSBub3QgdmVyeSBjb21tb24uCldlIHNlZSBhbHNvIHRoYXQgYFNDUENMMDAwNDgxYCBoYXMgYSBsYXJnZXIgYFR1bW9yX015b2N5dGVgIHBvcHVsYXRpb24sIHdoaWxlIGFsbCBvdGhlciBzYW1wbGVzIGhhdmUgbGFyZ2VyIGBUdW1vcl9NZXNvZGVybWAgcG9wdWxhdGlvbnMuClRoaXMgZGlmZmVyZW5jZSBfbWF5XyBleHBsYWluIHdoeSB3ZSBvYnNlcnZlIHRoYXQgYFNDUENMMDAwNDgxYCBpcyBzb21ld2hhdCBtb3JlIHNlcGFyYXRlZCBmcm9tIHRoZSBvdGhlciBzYW1wbGVzIGluIHRoZSBgZmFzdE1OTmAgVU1BUC4KCkxldCdzIHJlLXBsb3QgdGhpcyBVTUFQIHRvIGhpZ2hsaWdodCBjZWxsIHR5cGVzOgoKCmBgYHtyIHBsb3QgZmFzdG1ubiB1bWFwIGNlbGx0eXBlc30Kc2NhdGVyOjpwbG90UmVkdWNlZERpbShtZXJnZWRfc2NlLAogICAgICAgICAgICAgICAgICAgICAgIGRpbXJlZCA9ICJmYXN0bW5uX1VNQVAiLAogICAgICAgICAgICAgICAgICAgICAgICMgY29sb3IgYnkgYnJvYWQgY2VsbHR5cGVzCiAgICAgICAgICAgICAgICAgICAgICAgY29sb3JfYnkgPSAiY2VsbHR5cGVfYnJvYWQiLAogICAgICAgICAgICAgICAgICAgICAgIHBvaW50X3NpemUgPSAwLjUsCiAgICAgICAgICAgICAgICAgICAgICAgcG9pbnRfYWxwaGEgPSAwLjIpICsKICAjIGluY2x1ZGUgYXJndW1lbnQgdG8gc3BlY2lmeSBjb2xvciBvZiBOQSB2YWx1ZXMKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIsIG5hbWUgPSAiQnJvYWQgY2VsbHR5cGUiLCBuYS52YWx1ZSA9ICJncmV5ODAiKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDMsIGFscGhhID0gMSkpKSArCiAgZ2d0aXRsZSgiVU1BUCBhZnRlciBpbnRlZ3JhdGlvbiB3aXRoIGZhc3RNTk4iKQpgYGAKClRoaXMgVU1BUCBzaG93cyB0aGF0IHRoZSBub3JtYWwgdGlzc3VlIGNlbGwgdHlwZXMgKG1vc3RseSB2YXNjdWxhciBlbmRvdGhlbGl1bSwgbXVzY2xlIGNlbGxzLCBhbmQgbW9ub2N5dGVzKSB0ZW5kIHRvIGNsdXN0ZXIgdG9nZXRoZXIgYW5kIGFyZSBnZW5lcmFsbHkgc2VwYXJhdGVkIGZyb20gdGhlIHR1bW9yIGNlbGwgdHlwZXMsIHdoaWNoIGlzIGFuIGVuY291cmFnaW5nIHBhdHRlcm4hClR1bW9yIGNlbGwgdHlwZXMgZnJvbSBkaWZmZXJlbnQgc2FtcGxlcyBhcmUgYWxsIGFsc28gY2x1c3RlcmluZyB0b2dldGhlciwgd2hpY2ggaXMgZXZlbiBtb3JlIGVuY291cmFnaW5nIHRoYXQgd2UgaGFkIHN1Y2Nlc3NmdWwgaW50ZWdyYXRpb24uCgpIb3dldmVyLCBpdCdzIGEgYml0IGNoYWxsZW5naW5nIHRvIHNlZSBhbGwgdGhlIHBvaW50cyBnaXZlbiB0aGUgYW1vdW50IG9mIG92ZXJsYXAgaW4gdGhlIHBsb3QuCk9uZSB3YXkgd2UgY2FuIHNlZSBhbGwgdGhlIHBvaW50cyBhIGJpdCBiZXR0ZXIgaXMgdG8gZmFjZXQgdGhlIHBsb3QgYnkgc2FtcGxlLCB1c2luZyBgZmFjZXRfd3JhcCgpYCBmcm9tIHRoZSBgZ2dwbG90MmAgcGFja2FnZSAod2hpY2ggd2UgY2FuIGRvIGJlY2F1c2UgYHNjYXRlcjo6cGxvdFJlZHVjZWREaW0oKWAgcmV0dXJucyBhIGBnZ3Bsb3QyYCBvYmplY3QpOgoKYGBge3IgcGxvdCBmYXN0bW5uIHVtYXAgY2VsbHR5cGVzIGZhY2V0ZWR9CnNjYXRlcjo6cGxvdFJlZHVjZWREaW0obWVyZ2VkX3NjZSwKICAgICAgICAgICAgICAgICAgICAgICBkaW1yZWQgPSAiZmFzdG1ubl9VTUFQIiwKICAgICAgICAgICAgICAgICAgICAgICBjb2xvcl9ieSA9ICJjZWxsdHlwZV9icm9hZCIsCiAgICAgICAgICAgICAgICAgICAgICAgcG9pbnRfc2l6ZSA9IDAuNSwKICAgICAgICAgICAgICAgICAgICAgICBwb2ludF9hbHBoYSA9IDAuMiwKICAgICAgICAgICAgICAgICAgICAgICAjIEFsbG93IGZvciBmYWNldGluZyBieSBhIHZhcmlhYmxlIHVzaW5nIGBvdGhlcl9maWVsZHNgOgogICAgICAgICAgICAgICAgICAgICAgIG90aGVyX2ZpZWxkcyA9ICJzYW1wbGUiKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiRGFyazIiLCBuYW1lID0gIkJyb2FkIGNlbGx0eXBlIiwgbmEudmFsdWUgPSAiZ3JleTgwIikgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAzLCBhbHBoYSA9IDEpKSkgKwogIGdndGl0bGUoIlVNQVAgYWZ0ZXIgaW50ZWdyYXRpb24gd2l0aCBmYXN0TU5OIikgKwogICMgRmFjZXQgYnkgc2FtcGxlCiAgZmFjZXRfd3JhcCh2YXJzKHNhbXBsZSkpICsKICAjIFVzZSBhIHRoZW1lIHdpdGggYmFja2dyb3VuZCBncmlkIHRvIG1vcmUgZWFzaWx5IGNvbXBhcmUgcGFuZWwgY29vcmRpbmF0ZXMKICB0aGVtZV9idygpCmBgYAoKV2hhdCB0cmVuZHMgZG8geW91IG9ic2VydmUgYmV0d2VlbiB0dW1vciBhbmQgaGVhbHRoeSB0aXNzdWVzIGFtb25nIHRoZXNlIGludGVncmF0ZWQgc2FtcGxlcz8KCiMjIyBJbnRlZ3JhdGUgd2l0aCBgaGFybW9ueWAKCmBmYXN0TU5OYCBpcyBvbmx5IG9uZSBvZiBtYW55IGFwcHJvYWNoZXMgdG8gcGVyZm9ybSBpbnRlZ3JhdGlvbiwgYW5kIGRpZmZlcmVudCBtZXRob2RzIGhhdmUgZGlmZmVyZW50IGNhcGFiaWxpdGllcyBhbmQgbWF5IGdpdmUgZGlmZmVyZW50IHJlc3VsdHMuCkZvciBleGFtcGxlLCBzb21lIG1ldGhvZHMgY2FuIGFjY29tbW9kYXRlIGFkZGl0aW9uYWwgY292YXJpYXRlcyAoZS5nLiwgdGVjaG5vbG9neSwgcGF0aWVudCwgZGlhZ25vc2lzLCBldGMuKSB0aGF0IGNhbiBpbmZsdWVuY2UgaW50ZWdyYXRpb24uCkluIGZhY3QgdGhlIGRhdGEgd2UgYXJlIHVzaW5nIGhhcyBhIGtub3duIF9wYXRpZW50XyBjb3ZhcmlhdGU7IGBTQ1BDTDAwMDQ3OWAgYW5kIGBTQ1BDTDAwMDQ4MGAgYXJlIGZyb20gUGF0aWVudCBBLCBhbmQgYFNDUENMMDAwNDgxYCBhbmQgYFNDUENMMDAwNDgyYCBhcmUgZnJvbSBQYXRpZW50IEIuCgpTbywgbGV0J3MgcGVyZm9ybSBpbnRlZ3JhdGlvbiB3aXRoIGEgbWV0aG9kIHRoYXQgY2FuIHVzZSB0aGlzIGluZm9ybWF0aW9uIC0gW2BoYXJtb255YF0oaHR0cHM6Ly9wb3J0YWxzLmJyb2FkaW5zdGl0dXRlLm9yZy9oYXJtb255LykhCgpUbyBiZWdpbiBzZXR0aW5nIHVwIGZvciBgaGFybW9ueWAgaW50ZWdyYXRpb24sIHdlIG5lZWQgdG8gYWRkIGV4cGxpY2l0IHBhdGllbnQgaW5mb3JtYXRpb24gaW50byBvdXIgbWVyZ2VkIFNDRS4KV2UnbGwgY3JlYXRlIGEgbmV3IGNvbHVtbiBgcGF0aWVudGAgd2hvc2UgdmFsdWUgaXMgZWl0aGVyICJBIiBvciAiQiIgZGVwZW5kaW5nIG9uIHRoZSBnaXZlbiBzYW1wbGUgbmFtZSwgdXNpbmcgdGhlIFtgZHBseXI6OmNhc2Vfd2hlbigpYF0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9jYXNlX3doZW4uaHRtbCkgZnVuY3Rpb24uCldlIHByb3ZpZGUgdGhpcyBmdW5jdGlvbiB3aXRoIGEgc2V0IG9mIGxvZ2ljYWwgZXhwcmVzc2lvbnMgYW5kIGVhY2ggYXNzaWduZWQgdmFsdWUgaXMgZGVzaWduYXRlZCBieSBgfmAuClRoZSBleHByZXNzaW9ucyBhcmUgZXZhbHVhdGVkIGluIG9yZGVyLCBzdG9wcGluZyBhdCB0aGUgX2ZpcnN0XyBvbmUgdGhhdCBldmFsdWF0ZXMgYXMgYFRSVUVgIGFuZCByZXR1cm5pbmcgdGhlIGFzc29jaWF0ZWQgdmFsdWUuCgpgYGB7ciBhZGQgcGF0aWVudCBpbmZvfQojIENyZWF0ZSBwYXRpZW50IGNvbHVtbiB3aXRoIHZhbHVlcyAiQSIgb3IgIkIiIGZvciB0aGUgdHdvIHBhdGllbnRzCm1lcmdlZF9zY2UkcGF0aWVudCA8LSBkcGx5cjo6Y2FzZV93aGVuKAogIG1lcmdlZF9zY2Ukc2FtcGxlICVpbiUgYygiU0NQQ0wwMDA0NzkiLCAiU0NQQ0wwMDA0ODAiKSB+ICJBIiwKICBtZXJnZWRfc2NlJHNhbXBsZSAlaW4lIGMoIlNDUENMMDAwNDgxIiwgIlNDUENMMDAwNDgyIikgfiAiQiIsCikKYGBgCgoKVW5saWtlIGBmYXN0TU5OYCwgYGhhcm1vbnlgIGRvZXMgbm90IGNhbGN1bGF0ZSBjb3JyZWN0ZWQgZXhwcmVzc2lvbiB2YWx1ZXMgbm9yIGRvZXMgaXQgcmV0dXJuIGFuIFNDRSBvYmplY3QuCkxpa2UgYGZhc3RNTk5gLCBgaGFybW9ueWAgcGVyZm9ybXMgaW50ZWdyYXRpb24gb24gYSBtZXJnZWQgUENBIG1hdHJpeC4KSG93ZXZlciwgdW5saWtlIGBmYXN0TU5OYCwgYGhhcm1vbnlgIGRvZXMgbm90ICJiYWNrLWNhbGN1bGF0ZSIgY29ycmVjdGVkIGV4cHJlc3Npb24gZnJvbSB0aGUgY29ycmVjdGVkIFBDQSBtYXRyaXggYW5kIGl0IG9ubHkgcmV0dXJucyB0aGUgY29ycmVjdGVkIFBDQSBtYXRyaXggaXRzZWxmLgpGb3IgaW5wdXQsIGBoYXJtb255YCBuZWVkcyBhIGNvdXBsZSBwaWVjZXMgb2YgaW5mb3JtYXRpb246CgotIEZpcnN0LCBgaGFybW9ueWAgdGFrZXMgYSBiYXRjaC13ZWlnaHRlZCBQQ0EgbWF0cml4IHRvIHBlcmZvcm0gaW50ZWdyYXRpb24uCldlIGFscmVhZHkgY2FsY3VsYXRlZCBhIGJhdGNoLXdlaWdodGVkIFBDQSBtYXRyaXggc28gd2UnbGwgcHJvdmlkZSB0aGlzIGFzIHRoZSB0aGUgaW5wdXQuCi0gU2Vjb25kLCB3ZSBuZWVkIHRvIHRlbGwgYGhhcm1vbnlgIGFib3V0IHRoZSBjb3ZhcmlhdGVzIHRvIHVzZSAtIGBzYW1wbGVgIGFuZCBgcGF0aWVudGAuClRvIGRvIHRoaXMsIHdlIHByb3ZpZGUgdHdvIGFyZ3VtZW50czoKICAtIGBtZXRhX2RhdGFgLCBhIGRhdGEgZnJhbWUgdGhhdCBjb250YWlucyBjb3ZhcmlhdGVzIGFjcm9zcyBzYW1wbGVzLgogIFdlIGNhbiBzaW1wbHkgc3BlY2lmeSB0aGUgU0NFIGBjb2xEYXRhYCBoZXJlIHNpbmNlIGl0IGNvbnRhaW5zIGBzYW1wbGVgIGFuZCBgcGF0aWVudGAgY29sdW1ucy4KICAtIGB2YXJzX3VzZWAsIGEgdmVjdG9yIG9mIHdoaWNoIGNvbHVtbiBuYW1lcyBpbiBgbWV0YV9kYXRhYCBzaG91bGQgYWN0dWFsbHkgYmUgdXNlZCBhcyBjb3ZhcmlhdGVzLgogIE90aGVyIGNvbHVtbnMgaW4gYG1ldGFfZGF0YWAgd2hpY2ggYXJlIG5vdCBpbiBgdmFyc191c2VgIGFyZSBpZ25vcmVkLgoKTGV0J3MgZ28hCgpgYGB7ciBydW4gaGFybW9ueSwgbGl2ZSA9IFRSVUV9CiMgUnVuIGhhcm1vbnkgaW50ZWdyYXRpb24KaGFybW9ueV9wY2EgPC0gaGFybW9ueTo6UnVuSGFybW9ueSgKICBkYXRhX21hdCA9IHJlZHVjZWREaW0obWVyZ2VkX3NjZSwgIlBDQSIpLAogIG1ldGFfZGF0YSA9IGNvbERhdGEobWVyZ2VkX3NjZSksCiAgdmFyc191c2UgPSBjKCJzYW1wbGUiLCAicGF0aWVudCIpCikKYGBgCgpUaGUgcmVzdWx0IGlzIGEgUENBIG1hdHJpeC4KTGV0J3MgcHJpbnQgYSBzdWJzZXQgb2YgdGhpcyBtYXRyaXggdG8gc2VlIGl0OgoKYGBge3IgcHJpbnQgaGFybW9ueSByZXN1bHQsIGxpdmUgPSBUUlVFfQojIFByaW50IHRoZSBoYXJtb255IHJlc3VsdApoYXJtb255X3BjYVsxOjUsIDE6NV0KYGBgCgpBcyB3ZSBkaWQgd2l0aCBgZmFzdE1OTmAgcmVzdWx0cywgbGV0J3Mgc3RvcmUgdGhpcyBQQ0EgbWF0cml4IGRpcmVjdGx5IGluIG91ciBgbWVyZ2VkX3NjZWAgb2JqZWN0IHdpdGggYW4gaW5mb3JtYXRpdmUgbmFtZSB0aGF0IHdvbid0IG92ZXJ3cml0ZSBhbnkgb2YgdGhlIGV4aXN0aW5nIFBDQSBtYXRyaWNlcy4KV2UnbGwgYWxzbyBjYWxjdWxhdGUgVU1BUCBmcm9tIGl0LgoKYGBge3Igc2F2ZSBoYXJtb255LCBsaXZlID0gVFJVRX0KIyBTdG9yZSBQQ0EgYXMgYGhhcm1vbnlfUENBYApyZWR1Y2VkRGltKG1lcmdlZF9zY2UsICJoYXJtb255X1BDQSIpIDwtIGhhcm1vbnlfcGNhCgojIEFzIGJlZm9yZSwgY2FsY3VsYXRlIFVNQVAgb24gdGhpcyBQQ0EgbWF0cml4IHdpdGggYXBwcm9wcmlhdGUgbmFtZXMKbWVyZ2VkX3NjZSA8LSBzY2F0ZXI6OnJ1blVNQVAobWVyZ2VkX3NjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltcmVkID0gImhhcm1vbnlfUENBIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSAgID0gImhhcm1vbnlfVU1BUCIpCmBgYAoKCkxldCdzIHNlZSBob3cgdGhlIGBoYXJtb255YCBVTUFQLCBjb2xvcmVkIGJ5IHNhbXBsZSwgbG9va3MgY29tcGFyZWQgdG8gdGhlIGBmYXN0TU5OYCBVTUFQOgoKYGBge3IgcGxvdCBoYXJtb255IHVtYXAgYmF0Y2hlc30Kc2NhdGVyOjpwbG90UmVkdWNlZERpbShtZXJnZWRfc2NlLAogICAgICAgICAgICAgICAgICAgICAgIGRpbXJlZCA9ICJoYXJtb255X1VNQVAiLAogICAgICAgICAgICAgICAgICAgICAgIGNvbG9yX2J5ID0gInNhbXBsZSIsCiAgICAgICAgICAgICAgICAgICAgICAgcG9pbnRfc2l6ZSA9IDAuNSwKICAgICAgICAgICAgICAgICAgICAgICBwb2ludF9hbHBoYSA9IDAuMikgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIiwgbmFtZSA9ICJzYW1wbGUiKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDMsIGFscGhhID0gMSkpKSArCiAgZ2d0aXRsZSgiVU1BUCBhZnRlciBpbnRlZ3JhdGlvbiB3aXRoIGhhcm1vbnkiKQpgYGAKCkhvdyBkbyB5b3UgdGhpbmsgdGhpcyBgaGFybW9ueWAgVU1BUCBjb21wYXJlcyB0byB0aGF0IGZyb20gYGZhc3RNTk5gIGludGVncmF0aW9uPwoKTGV0J3Mgc2VlIGhvdyB0aGlzIFVNQVAgbG9va3MgY29sb3JlZCBieSBjZWxsIHR5cGUsIGFuZCBmYWNldGVkIGZvciB2aXNpYmlsaXR5OgoKYGBge3IgcGxvdCBoYXJtb255IHVtYXAgY2VsbHR5cGVzfQpzY2F0ZXI6OnBsb3RSZWR1Y2VkRGltKG1lcmdlZF9zY2UsCiAgICAgICAgICAgICAgICAgICAgICAgZGltcmVkID0gImhhcm1vbnlfVU1BUCIsCiAgICAgICAgICAgICAgICAgICAgICAgY29sb3JfYnkgPSAiY2VsbHR5cGVfYnJvYWQiLAogICAgICAgICAgICAgICAgICAgICAgIHBvaW50X3NpemUgPSAwLjUsCiAgICAgICAgICAgICAgICAgICAgICAgcG9pbnRfYWxwaGEgPSAwLjIsCiAgICAgICAgICAgICAgICAgICAgICAgIyBTcGVjaWZ5IHZhcmlhYmxlIGZvciBmYWNldGluZwogICAgICAgICAgICAgICAgICAgICAgIG90aGVyX2ZpZWxkcyA9ICJzYW1wbGUiKSArCiAgc2NhbGVfY29sb3JfYnJld2VyKHBhbGV0dGUgPSAiRGFyazIiLCBuYW1lID0gIkJyb2FkIGNlbGx0eXBlIiwgbmEudmFsdWUgPSAiZ3JleTgwIikgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAzLCBhbHBoYSA9IDEpKSkgKwogIGdndGl0bGUoIlVNQVAgYWZ0ZXIgaW50ZWdyYXRpb24gd2l0aCBoYXJtb255IikgKwogIGZhY2V0X3dyYXAodmFycyhzYW1wbGUpKQpgYGAKCldoYXQgZG8geW91IG5vdyBub3RpY2UgaW4gdGhpcyBmYWNldGVkIHZpZXcgdGhhdCB3YXNuJ3QgY2xlYXIgcHJldmlvdXNseT8KQXJlIHRoZXJlIG90aGVyIHBhdHRlcm5zIHlvdSBzZWUgdGhhdCBhcmUgc2ltaWxhciBvciBkaWZmZXJlbnQgZnJvbSB0aGUgYGZhc3RNTk5gIFVNQVA/CkhvdyBkbyB5b3UgdGhpbmsgYGZhc3RNTk5gIHZzLiBgaGFybW9ueWAgcGVyZm9ybWVkIGluIGludGVncmF0aW5nIHRoZXNlIHNhbXBsZXM/CgojIyBFeHBvcnQKCkZpbmFsbHksIHdlJ2xsIGV4cG9ydCB0aGUgZmluYWwgU0NFIG9iamVjdCB3aXRoIGJvdGggYGZhc3RNTk5gIGFuZCBgaGFybW9ueWAgaW50ZWdyYXRpb24gdG8gYSBmaWxlLgpTaW5jZSB0aGlzIG9iamVjdCBpcyB2ZXJ5IGxhcmdlIChvdmVyIDEgR0IhKSwgd2UnbGwgZXhwb3J0IGl0IHRvIGEgZmlsZSB3aXRoIHNvbWUgY29tcHJlc3Npb24sIHdoaWNoLCBpbiB0aGlzIGNhc2UsIHdpbGwgcmVkdWNlIHRoZSBmaW5hbCBzaXplIHRvIGEgc21hbGxlciB+MzYwIE1CLgpUaGlzIHdpbGwgdGFrZSBhIGNvdXBsZSBtaW51dGVzIHRvIHNhdmUgd2hpbGUgY29tcHJlc3Npb24gaXMgcGVyZm9ybWVkLgoKYGBge3Igc2F2ZSBpbnRlZ3JhdGlvbiwgbGl2ZSA9IFRSVUV9CiMgRXhwb3J0IHRvIFJEUyBmaWxlIHdpdGggImd6IiBjb21wcmVzc2lvbgpyZWFkcjo6d3JpdGVfcmRzKAogIG1lcmdlZF9zY2UsCiAgaW50ZWdyYXRlZF9zY2VfZmlsZSwKICBjb21wcmVzcyA9ICJneiIKKQpgYGAKCgojIyBQcmludCBzZXNzaW9uIGluZm8KCkFzIGFsd2F5cywgd2UnbGwgcHJpbnQgdGhlIHNlc3Npb24gaW5mbyB0byBiZSB0cmFuc3BhcmVudCBhYm91dCB3aGF0IHBhY2thZ2VzLCBhbmQgd2hpY2ggdmVyc2lvbnMsIHdlcmUgdXNlZCBkdXJpbmcgdGhpcyBSIHNlc3Npb24uCgpgYGB7ciBzZXNzaW9uaW5mb30Kc2Vzc2lvbkluZm8oKQpgYGAK