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)

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)

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

One other new coding strategy we’ll learn in this notebook is using the glue package to combine strings. This package offers a convenient function glue::glue() that can be used instead of the base R paste() function.

# Define a variable for example:
org_name <- "Data Lab"

# We can use paste to combine strings and variables:
paste("Welcome to the", org_name, "workshop on Advanced scRNA-seq!")
[1] "Welcome to the Data Lab workshop on Advanced scRNA-seq!"
Welcome to the Data Lab workshop on Advanced scRNA-seq!

We can use glue::glue() to accomplish the same goal with some different syntax:

# glue::glue takes a single string argument (only one set of quotes!), and
#  variables can easily be included inside {curly braces}
glue::glue("Welcome to the {org_name} workshop on Advanced scRNA-seq!")
Welcome to the Data Lab workshop on Advanced scRNA-seq!
Welcome to the Data Lab workshop on Advanced scRNA-seq!

(Note that even though the glue::glue() output isn’t in quotes, it still behaves like a string!)

Alright, time for the good stuff! 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

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.

# 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 list of SCE objects:

# Print sce_list
sce_list
[[1]]
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):

[[2]]
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):

[[3]]
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):

[[4]]
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):

We now have a list of length four, where each item is a processed SCE object! However, we’ll need to keep track of which sample each item is, so it’s helpful to add names to this list representing the relevant sample names.

# Assign the sample names as the names for sce_list
names(sce_list) <- sample_names
# Print the list to see it with names
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 in Patel et al. (2022). We will end up leveraging these cell type annotations to explore how successful our integration is; 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 these cell type annotations. If we have annotations, they are a helpful “bonus” for assessing the integration’s success, but they are not part of the integration itself.

Prepare the SCE list for integration

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 take care of these items:

  1. We should be able to trace sample-specific information back to the originating sample, including…
    • Cell-level information: Which sample is each cell from?
    • Library-specific feature statistics, e.g., gene-level statistics for a given library found in rowData. Which sample is a given feature statistic from?
  2. SCE objects should contain the same genes: Each SCE object should have the same row names.
  3. SCE cell metadata columns should match: The colData for each SCE object should have the same column names.

We’ll begin by taking some time to thoroughly explore our SCE objects and figure out what wrangling steps we need to take for these specific data. Don’t skip this exploration! Bear in mind that the exact wrangling shown here will not be the same for other SCE objects you work with, but the same general principles apply.

Preserving sample information at the cell level

How will we be able to tell which sample a given cell came from?

The best way to do this is simply to add a colData column with the sample information, so that we can know which sample each row came from.

In addition, we want to pay some attention to the SCE object’s column names (the cell ids), which must remain unique after merging since duplicate ids will cause an R error. In this case, the SCE column names are barcodes (which is usually but not always the case in SCE objects), which are only guaranteed to be unique within a sample but may be repeated across samples. So, after merging, it’s technically possible that multiple cells will have the same barcode. This would be a problem for two reasons: First, the cell id would not be able to point us back to cell’s originating sample. Second, it would literally cause an error in R, which does not allow duplicate column names.

One way to ensure that cell ids remain unique even after merging is to actually modify them by prepending the relevant sample name. For example, consider these barcodes for the SCPCL000479 sample:

# Look at the column names for the `SCPCL000479` sample, for example
colnames(sce_list$SCPCL000479) |>
  # Only print out the first 6 for convenience
  head()
[1] "GGGACCTCAAGCGGAT" "CACAGATAGTGAGTGC" "TGTGGCGGTGAATTGA" "GCCGATGGTACATACC"
[5] "ATTATCCCAGTTGGTT" "TCCGAAATCACACCGG"
GGGACCTCAAGCGGAT
CACAGATAGTGAGTGC
TGTGGCGGTGAATTGA
GCCGATGGTACATACC
ATTATCCCAGTTGGTT
TCCGAAATCACACCGG

These ids will be updated to SCPCL000479-GGGACCTCAAGCGGAT, SCPCL000479-CACAGATAGTGAGTGC, and so on, thereby ensuring fully unique ids for all cells across all samples.

Preserving sample information at the gene level

The rowData table in SCE objects will often contain both “general” and “library-specific” information, for example:

rowData(sce_list$SCPCL000479) |>
  head()
DataFrame with 6 rows and 3 columns
                gene_symbol       mean  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

Here, the rownames are Ensembl gene ids, and columns are gene_symbol, mean, and detected. The gene_symbol column is general information about all genes, not specific to any library or experiment, but mean and detected are library-specific gene statistics. So, gene_symbol does not need to be traced back to its originating sample, but mean and detected do. To this end, we can take a similar approach to what we’ll do for cell ids: We can change the sample-specific rowData column names by prepending the sample name. For example, rather than being called mean, this column will be named SCPCL000479-mean for the SCPCL000479 sample.

All our SCE objects have the same rowData columns (as we can see in the next chunk), so we’ll perform this renaming across all SCEs.

# Use `purrr::map()` to quickly extract rowData column names for all SCEs
purrr::map(sce_list,
           \(x) colnames(rowData(x)))
$SCPCL000479
[1] "gene_symbol" "mean"        "detected"   

$SCPCL000480
[1] "gene_symbol" "mean"        "detected"   

$SCPCL000481
[1] "gene_symbol" "mean"        "detected"   

$SCPCL000482
[1] "gene_symbol" "mean"        "detected"   
gene_symbol
mean
detected
gene_symbol
mean
detected
gene_symbol
mean
detected
gene_symbol
mean
detected

Ensuring that only shared genes are used

The next step in ensuring SCE compatibility is to make sure they all contain the same genes, which are stored as the SCE object’s row names (these names are also found the rowData slot’s row names). Here, those gene ids are unique Ensembl gene ids.

We can use some purrr magic to quickly find the set of shared genes among our samples:

# 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)
# Use head to look at the vector of shared genes:
head(shared_genes)
[1] "ENSG00000000003" "ENSG00000000005" "ENSG00000000419" "ENSG00000000457"
[5] "ENSG00000000460" "ENSG00000000938"
ENSG00000000003
ENSG00000000005
ENSG00000000419
ENSG00000000457
ENSG00000000460
ENSG00000000938

In this case, we happen to know that all SCE objects we’re working with already contained the same genes. We do a quick-and-dirty check for this by looking at the number of rows across SCE objects, and we’ll see that they are all the same:

# 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

So, for our data, we will not have to subset to shared genes since they are already shared!

Ensuring matching columns in colData

Finally, we’ll need to have the same column names across all SCE colData tables, so let’s look at all those column names. We can use similar syntax here to what we used to look at all the rowData column names.

purrr::map(sce_list,
           \(x) colnames(colData(x)) )
$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

It looks like the column names are all already matching among SCEs, so there’s no specific preparation we’ll need to do there.

Perform SCE merging

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.

format_sce <- function(sce, sample_name) {
  # Input arguments:
  ## sce: An SCE object to format
  ## sample_name: The SCE object's name
  # This function returns a formatted SCE object.

  ###### Ensure that we can identify the originating sample information ######
  # Add a column called `sample` that stores this information
  # This will be stored in `colData`
  sce$sample <- sample_name


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


  ###### Ensure gene-level statistics can be identified in `rowData` ######
  # We want to rename the columns `mean` and `detected` to contain the `sample_name`
  # Recall the names are: "gene_symbol", "mean", "detected"
  colnames(rowData(sce)) <- c("gene_symbol",
                              glue::glue("{sample_name}-mean"),
                              glue::glue("{sample_name}-detected"))

  # 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),
  # Name of the function to run
  format_sce
)

# Print resulting list
sce_list_formatted
$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 SCPCL000479-mean SCPCL000479-detected
colnames(1918): SCPCL000479-GGGACCTCAAGCGGAT
  SCPCL000479-CACAGATAGTGAGTGC ... SCPCL000479-GTTGTCCCACGTACAT
  SCPCL000479-TCCGATCGTCGTGCCA
colData names(13): sum detected ... celltype_broad sample
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 SCPCL000480-mean SCPCL000480-detected
colnames(4428): SCPCL000480-TAGGGTTAGCAAGCCA
  SCPCL000480-AACTTCTTCCCTCAAC ... SCPCL000480-AGGGAGTAGCCTCATA
  SCPCL000480-TCGGATACATTGCAAC
colData names(13): sum detected ... celltype_broad sample
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 SCPCL000481-mean SCPCL000481-detected
colnames(5236): SCPCL000481-TCATGAGAGGCCCACT
  SCPCL000481-GGGTATTTCGTTGTGA ... SCPCL000481-AAAGAACCACTTCAAG
  SCPCL000481-CAGCAGCTCGTGCATA
colData names(13): sum detected ... celltype_broad sample
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 SCPCL000482-mean SCPCL000482-detected
colnames(4372): SCPCL000482-GAGAAATCAGATTAAG
  SCPCL000482-CAACCTCTCCGATCGG ... SCPCL000482-TGATTTCCACAAGTTC
  SCPCL000482-ACCAAACGTTCTCAGA
colData names(13): sum detected ... celltype_broad sample
reducedDimNames(2): PCA UMAP
mainExpName: NULL
altExpNames(0):

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

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): salmon_version reference_index ... filtering_method
  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(2): PCA UMAP
mainExpName: NULL
altExpNames(0):

We now have a single 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:

# What are the unique values in the `sample` column?
unique( colData(merged_sce)$sample )
[1] "SCPCL000479" "SCPCL000480" "SCPCL000481" "SCPCL000482"
SCPCL000479
SCPCL000480
SCPCL000481
SCPCL000482
# 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
# 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

Integration

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.

You’ll notice that the merged SCE object object already contains PCA and UMAP reduced dimensions, which were calculated during our pre-processing:

# Print the reducedDimNames of the merged_sce
reducedDimNames(merged_sce)
[1] "PCA"  "UMAP"
PCA
UMAP

These represent the original dimension reductions that were performed on each individual SCE before merging, but we actually need to calculate PCA (and UMAP for visualization) from the merged object directly.

Why can’t we use the sample-specific PCA and UMAP matrices? Part of these calculations themselves involves scaling the raw data to center the mean. When samples are separately centered but plotting together, you will see samples “overlapping” in space, but this placement is actually just an artifact of the individual centering. In addition, the mathematical relationship between the original expression data and reduced dimension version of that data will differ across samples, meaning we can’t interpret them all together. To see how this looks, let’s look at the UMAP when calculated from individual samples:

# Plot UMAP calculated from individual samples with separate scaling
scater::plotReducedDim(merged_sce,
                       dimred = "UMAP",
                       color_by = "sample",
                       point_size = 0.5,
                       point_alpha = 0.2) +
  scale_color_brewer(palette = "Dark2", name = "sample") + # Use a CVD-friendly color scheme and specify legend name
  guides(color = guide_legend(override.aes = list(size = 3, alpha = 1))) + # Modify the legend key with larger, easier to see points
  ggtitle("UMAP calculated on each sample separately")
Scale for colour is already present.
Adding another scale for colour, which will replace the existing scale.

As we see in this UMAP, all samples are centered at zero and all overlapping. This visual artifact can give the incorrect impression that data is integrated - to be clear, this data is NOT integrated!

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. We’ll also save these new reduced dimensions with different names, merged_PCA and merged_UMAP, to distinguish them from already-present PCA and UMAP.

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.

# 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, "merged_PCA") <- merged_pca[[1]]

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

We’ll calculate UMAP as “usual”, but in this case we’ll specify two additional arguments:

  • dimred = "merged_PCA", which specifies which existing reduced dimension should be used for the calculation. We want to use the batch-weighted PCA, which we named above as "merged_PCA".
  • name = "merged_UMAP", which names the final UMAP that this function calculates. This argument will prevent us from overwriting the existing UMAP which is already named “UMAP” and instead create a separate "merged_UMAP".
# add merged_UMAP from merged_PCA
merged_sce <- scater::runUMAP(merged_sce,
                              dimred = "merged_PCA",
                              name = "merged_UMAP")

Now, let’s see how this new merged_UMAP looks compared to the UMAP calculated from individual samples:

# UMAPs scaled together when calculated from the merged SCE
scater::plotReducedDim(merged_sce,
                       dimred = "merged_UMAP",
                       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.

Samples are now separated, which more reasonably reflects that this data is not yet batch-corrected. 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?

Integration 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.

# 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 from merged_UMAP! 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") +
  # 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?

Integration 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 the first patient, and SCPCL000481 and SCPCL000482 are from the second patient.

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 (our merged_PCA reduced dimension), 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, "merged_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.032044 -7.271764 -3.713022 5.477643  0.6781986
SCPCL000479-CACAGATAGTGAGTGC -8.237485 -7.436515 -5.579578 6.101144 -0.1577978
SCPCL000479-TGTGGCGGTGAATTGA -7.893770 -6.595934 -2.042903 4.706149  0.9176450
SCPCL000479-GCCGATGGTACATACC -6.741593 -7.537079 -5.997807 2.609384 -4.1100267
SCPCL000479-ATTATCCCAGTTGGTT -5.534487 -4.887054 -1.897409 2.588823  1.5458433

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))) +
  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")
LS0tCnRpdGxlOiAiSW50ZWdyYXRpbmcgc2NSTkEtc2VxIGRhdGFzZXRzIgphdXRob3I6IERhdGEgTGFiIGZvciBBTFNGCmRhdGU6IDIwMjMKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKIyMgT2JqZWN0aXZlcwoKVGhpcyBub3RlYm9vayB3aWxsIGRlbW9uc3RyYXRlIGhvdyB0bzoKCi0gUHJlcGFyZSBTQ0Ugb2JqZWN0cyBmb3IgaW50ZWdyYXRpb24KLSBBcHBseSBpbnRlZ3JhdGlvbiBtZXRob2RzIGluY2x1ZGluZyBgZmFzdE1OTmAgYW5kIGBoYXJtb255YAotIFZpc3VhbGx5IGV4cGxvcmUgdGhlIHJlc3VsdHMgb2YgaW50ZWdyYXRpb24KLSBVc2UgYHB1cnJyOjptYXAoKWAgZnVuY3Rpb25zIGZvciBpdGVyYXRpbmcgb3ZlciBsaXN0cwoKLS0tCgpJbiB0aGlzIG5vdGVib29rLCB3ZSdsbCBwZXJmb3JtIGludGVncmF0aW9uIG9uIHNjUk5BLXNlcSBkYXRhc2V0cyBmcm9tIHRoZSBbU2luZ2xlLWNlbGwgUGVkaWF0cmljIENhbmNlciBBdGxhcyAoYFNjUENBYCldKGh0dHBzOi8vc2NwY2EuYWxleHNsZW1vbmFkZS5vcmcvKSwgYSBkYXRhYmFzZSBvZiB1bmlmb3JtbHktcHJvY2Vzc2VkIHBlZGlhdHJpYyBzY1JOQS1zZXEgZGF0YSBidWlsdCBhbmQgbWFpbnRhaW5lZCBieSB0aGUgRGF0YSBMYWIuClRoZSBgU2NQQ0FgIGRhdGFiYXNlIGN1cnJlbnRseSBob3N0cyBzaW5nbGUtY2VsbCBwZWRpYXRyaWMgY2FuY2VyIHRyYW5zY3JpcHRvbWljIGRhdGEgZ2VuZXJhdGVkIGJ5IEFMU0YtZnVuZGVkIGxhYnMsIHdpdGggdGhlIGdvYWwgb2YgbWFraW5nIHRoaXMgZGF0YSBlYXNpbHkgYWNjZXNzaWJsZSB0byBpbnZlc3RpZ2F0b3JzIChsaWtlIHlvdSEpLgpUaGUgZXhwcmVzc2lvbiBkYXRhIGluIGBTY1BDQWAgd2VyZSBtYXBwaW5nIGFuZCBxdWFudGlmaWVkIHdpdGggW2BhbGV2aW4tZnJ5YF0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMzgvczQxNTkyLTAyMi0wMTQwOC0zKSwgZm9sbG93ZWQgYnkgcHJvY2Vzc2luZyB3aXRoIEJpb2NvbmR1Y3RvciB0b29scyB1c2luZyB0aGUgc2FtZSBnZW5lcmFsIHByb2NlZHVyZXMgdGhhdCB3ZSBoYXZlIGNvdmVyZWQgaW4gdGhpcyB3b3Jrc2hvcC4KVGhlIHByb2Nlc3NpbmcgcGlwZWxpbmUgdXNlZCBgZW1wdHlEcm9wc0NlbGxSYW5nZXIoKWAgYW5kIGBtaVFDYCB0byBmaWx0ZXIgdGhlIHJhdyBjb3VudHMgbWF0cml4LCBgc2N1dHRsZWAgdG8gbG9nLW5vcm1hbGl6ZSB0aGUgY291bnRzLCBhbmQgYHNjYXRlcmAgZm9yIGRpbWVuc2lvbiByZWR1Y3Rpb24uClRoZSBwcm9jZXNzZWQgZGF0YSBhcmUgc3RvcmVkIGFzIGAucmRzYCBmaWxlcyBjb250YWluaW5nIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgb2JqZWN0cy4KWW91IGNhbiByZWFkIG1vcmUgYWJvdXQgaG93IGRhdGEgaW4gdGhlIGBTY1BDQWAgaXMgcHJvY2Vzc2VkIGluIFt0aGUgYXNzb2NpYXRlZCBkb2N1bWVudGF0aW9uXShodHRwczovL3NjcGNhLnJlYWR0aGVkb2NzLmlvL2VuL2xhdGVzdC8pLgoKCiFbU2luZ2xlLWNlbGwgcm9hZG1hcDogSW50ZWdyYXRpb24gT3ZlcnZpZXddKGRpYWdyYW1zL3JvYWRtYXBfbXVsdGlfbWVyZ2UtaW50ZWdyYXRlLnBuZykKClRvIGxlYXJuIGFib3V0IGludGVncmF0aW9uLCB3ZSdsbCBoYXZlIGEgbG9vayBhdCBmb3VyIHNhbXBsZXMgZnJvbSB0aGUgW2BTQ1BDUDAwMDAwNWAgcHJvamVjdF0oaHR0cHM6Ly9zY3BjYS5hbGV4c2xlbW9uYWRlLm9yZy9wcm9qZWN0cy9TQ1BDUDAwMDAwNSkgKFtQYXRlbCBfZXQgYWwuXyAyMDIyXShodHRwczovL2RvaS5vcmcvMTAuMTAxNi9qLmRldmNlbC4yMDIyLjA0LjAwMykpLCBhbiBpbnZlc3RpZ2F0aW9uIG9mIHBlZGlhdHJpYyBzb2xpZCB0dW1vcnMgbGVkIGJ5IHRoZSBbRHllcl0oaHR0cHM6Ly93d3cuc3RqdWRlLm9yZy9yZXNlYXJjaC9sYWJzL2R5ZXItbGFiLmh0bWwpIGFuZCBbQ2hlbl0oaHR0cHM6Ly93d3cuc3RqdWRlLm9yZy9yZXNlYXJjaC9sYWJzL2NoZW4tbGFiLXRhb3NoZW5nLmh0bWwpIGxhYnMgYXQgU3QuIEp1ZGUgQ2hpbGRyZW4ncyBSZXNlYXJjaCBIb3NwaXRhbC4KVGhlIHBhcnRpY3VsYXIgbGlicmFyaWVzIHdlJ2xsIGludGVncmF0ZSBjb21lIGZyb20gdHdvIHJoYWJkb215b3NhcmNvbWEgKFJNUykgcGF0aWVudHMsIHdpdGggdHdvIHNhbXBsZXMgZnJvbSBlYWNoIG9mIHR3byBwYXRpZW50cywgYWxsIHNlcXVlbmNlZCB3aXRoIDEweCBDaHJvbWl1bSB2MyB0ZWNobm9sb2d5LgpFYWNoIGxpYnJhcnkgaXMgZnJvbSBhIHNlcGFyYXRlIGJpb2xvZ2ljYWwgc2FtcGxlLgoKV2UnbGwgYmUgaW50ZWdyYXRpbmcgdGhlc2Ugc2FtcGxlcyB3aXRoIHR3byBkaWZmZXJlbnQgdG9vbHMsIFtgZmFzdE1OTmBdKGh0dHA6Ly93d3cuYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy8zLjE2L2Jpb2MvaHRtbC9iYXRjaGVsb3IuaHRtbCkgKFtIYWdodmVyZGkgX2V0IGFsLl8gMjAxOF0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMzgvbmJ0LjQwOTEpKSBhbmQgW2BoYXJtb255YF0oaHR0cHM6Ly9wb3J0YWxzLmJyb2FkaW5zdGl0dXRlLm9yZy9oYXJtb255LykgKFtLb3JzdW5za3kgX2V0IGFsLl8gMjAxOV0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMzgvczQxNTkyLTAxOS0wNjE5LTApKS4KSW50ZWdyYXRpb24gY29ycmVjdHMgZm9yIGJhdGNoIGVmZmVjdHMgdGhhdCBhcmlzZSBmcm9tIGRpZmZlcmVudCBsaWJyYXJ5IHByZXBhcmF0aW9ucywgZ2VuZXRpYyBiYWNrZ3JvdW5kcywgYW5kIG90aGVyIHNhbXBsZS1zcGVjaWZpYyBmYWN0b3JzLCBzbyB0aGF0IGRhdGFzZXRzIGNhbiBiZSBqb2ludGx5IGFuYWx5emVkIGF0IHRoZSBjZWxsIGxldmVsLgpgZmFzdE1OTmAgY29ycmVjdHMgZm9yIGJhdGNoIGVmZmVjdHMgdXNpbmcgYSBmYXN0ZXIgdmFyaWFudCBvZiB0aGUgbXV0dWFsLW5lYXJlc3QgbmVpZ2hib3JzIGFsZ29yaXRobSwgdGhlIHRlY2huaWNhbCBkZXRhaWxzIG9mIHdoaWNoIHlvdSBjYW4gbGVhcm4gbW9yZSBhYm91dCBmcm9tIHRoaXMgW3ZpZ25ldHRlIGJ5IEx1biAoMjAxOSldKGh0dHBzOi8vbWFyaW9uaWxhYi5naXRodWIuaW8vRnVydGhlck1OTjIwMTgvdGhlb3J5L2Rlc2NyaXB0aW9uLmh0bWwpLgpgaGFybW9ueWAsIG9uIHRoZSBvdGhlciBoYW5kLCBjb3JyZWN0cyBmb3IgYmF0Y2ggZWZmZWN0cyB1c2luZyBhbiBpdGVyYXRpdmUgY2x1c3RlcmluZyBhcHByb2FjaCwgYW5kIHVubGlrZSBgZmFzdE1OTmAsIGl0IGlzIGFsc28gYWJsZSB0byBjb25zaWRlciBhZGRpdGlvbmFsIGNvdmFyaWF0ZXMgYmV5b25kIGp1c3QgdGhlIGJhdGNoIGdyb3VwaW5ncy4KClJlZ2FyZGxlc3Mgb2Ygd2hpY2ggaW50ZWdyYXRpb24gdG9vbCBpcyB1c2VkLCB0aGUgYFNpbmdsZUNlbGxFeHBlcmltZW50YCAoU0NFKSBvYmplY3RzIGZpcnN0IG5lZWQgdG8gYmUgcmVmb3JtYXR0ZWQgYW5kIG1lcmdlZCBpbnRvIGEgc2luZ2xlICh1bmNvcnJlY3RlZCEpIFNDRSBvYmplY3QgdGhhdCBjb250YWlucyBhbGwgY2VsbHMgZnJvbSBhbGwgc2FtcGxlcy4KVGhpcyBtZXJnZWQgU0NFIGNhbiB0aGVuIGJlIHVzZWQgZm9yIGludGVncmF0aW9uIHRvIG9idGFpbiBhIGZvcm1hbGx5IGJhdGNoLWNvcnJlY3RlZCBTQ0Ugb2JqZWN0LgoKCiMjIFNldCB1cAoKYGBge3Igc2V0dXB9CiMgTG9hZCBsaWJyYXJpZXMKbGlicmFyeShnZ3Bsb3QyKSAgIyBwbG90dGluZyB0b29scwpsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KSAjIHdvcmsgd2l0aCBTQ0Ugb2JqZWN0cwoKIyBTZXQgdGhlIHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eQpzZXQuc2VlZCgxMjM0NSkKYGBgCgoKIyMjIERpcmVjdG9yaWVzIGFuZCBmaWxlcwoKCldlIGhhdmUgYWxyZWFkeSBwcmVwYXJlZCBjb3VudCBkYXRhIGZvciB0aGUgZm91ciBzYW1wbGVzIHdlJ2xsIGJlIGludGVncmF0aW5nIChpLmUuLCBmaWx0ZXJlZCBjZWxscywgbm9ybWFsaXplZCBjb3VudHMsIGFuZCBjYWxjdWxhdGVkIFBDQSAmIFVNQVApLgpUaGVzZSBTQ0Ugb2JqZWN0cywgc3RvcmVkIGFzIFJEUyBmaWxlcywgYXJlIGF2YWlsYWJsZSBpbiB0aGUgYGRhdGEvcm1zL3Byb2Nlc3NlZC9gIGRpcmVjdG9yeSBhbmQgYXJlIG5hbWVkIGFjY29yZGluZyB0byB0aGVpciBgU2NQQ0FgIGxpYnJhcnkgaWRzIDoKCi0gYFNDUENMMDAwNDc5LnJkc2AgKFBhdGllbnQgQSkKLSBgU0NQQ0wwMDA0ODAucmRzYCAoUGF0aWVudCBBKQotIGBTQ1BDTDAwMDQ4MS5yZHNgIChQYXRpZW50IEIpCi0gYFNDUENMMDAwNDgyLnJkc2AgKFBhdGllbnQgQikKClRvIGJlZ2luLCBsZXQncyBzZXQgdXAgb3VyIGRpcmVjdG9yaWVzIGFuZCBmaWxlczoKCmBgYHtyIGRpcmVjdG9yaWVzLCBsaXZlID0gVFJVRX0KIyBEZWZpbmUgZGlyZWN0b3J5IHdoZXJlIHByb2Nlc3NlZCBTQ0Ugb2JqZWN0cyB0byBiZSBpbnRlZ3JhdGVkIGFyZSBzdG9yZWQKaW5wdXRfZGlyIDwtIGZpbGUucGF0aCgiZGF0YSIsICJybXMiLCAicHJvY2Vzc2VkIikKCiMgRGVmaW5lIGRpcmVjdG9yeSB0byBzYXZlIGludGVncmF0ZWQgU0NFIG9iamVjdCB0bwpvdXRwdXRfZGlyIDwtIGZpbGUucGF0aCgiZGF0YSIsICJybXMiLCAiaW50ZWdyYXRlZCIpCgojIENyZWF0ZSBvdXRwdXQgZGlyZWN0b3J5IGlmIGl0IGRvZXNuJ3QgZXhpc3QKZnM6OmRpcl9jcmVhdGUob3V0cHV0X2RpcikKCiMgRGVmaW5lIG91dHB1dCBmaWxlIG5hbWUgZm9yIHRoZSBpbnRlZ3JhdGVkIG9iamVjdAppbnRlZ3JhdGVkX3NjZV9maWxlIDwtIGZpbGUucGF0aChvdXRwdXRfZGlyLCAicm1zX2ludGVncmF0ZWRfc3Vic2V0LnJkcyIpCmBgYAoKCldlIGNhbiB1c2UgdGhlIGBkaXIoKWAgZnVuY3Rpb24gdG8gbGlzdCBhbGwgY29udGVudHMgb2YgYSBnaXZlbiBkaXJlY3RvcnksIGZvciBleGFtcGxlIHRvIHNlZSBhbGwgdGhlIGZpbGVzIGluIG91ciBgaW5wdXRfZGlyYDoKCmBgYHtyIGlucHV0IGRpciwgbGl2ZSA9IFRSVUV9CmRpcihpbnB1dF9kaXIpCmBgYAoKV2Ugd2FudCB0byByZWFkIGluIGp1c3QgZm91ciBvZiB0aGVzZSBmaWxlcywgYXMgbGlzdGVkIHByZXZpb3VzbHkuClRvIHJlYWQgaW4gdGhlc2UgZmlsZXMsIHdlIGNvdWxkIHVzZSB0aGUgYHJlYWRyOjpyZWFkX3JkcygpYCBmdW5jdGlvbiAob3IgdGhlIGJhc2UgUiBgcmVhZFJEUygpYCkgZm91ciB0aW1lcywgb25jZSBmb3IgZWFjaCBvZiB0aGUgZmlsZXMuCldlIGNvdWxkIGFsc28gdXNlIGEgYGZvcmAgbG9vcCwgd2hpY2ggaXMgdGhlIGFwcHJvYWNoIHRoYXQgbWFueSBwcm9ncmFtbWluZyBsYW5ndWFnZXMgd291bGQgbGVhbiB0b3dhcmQuCkEgZGlmZmVyZW50IGFuZCBtb3JlIG1vZHVsYXIgY29kaW5nIGFwcHJvYWNoIHRvIHJlYWRpbmcgaW4gdGhlc2UgZmlsZXMgKGFuZCBtb3JlISkgaXMgdG8gbGV2ZXJhZ2UgdGhlIFtgcHVycnJgXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvKSBgdGlkeXZlcnNlYCBwYWNrYWdlLCB3aGljaCBwcm92aWRlcyBhIGNvbnZlbmllbnQgc2V0IG9mIGZ1bmN0aW9ucyBmb3Igb3BlcmF0aW5nIG9uIGxpc3RzLgpZb3UgY2FuIHJlYWQgbW9yZSBhYm91dCB0aGUgYHB1cnJyYCBmdW5jdGlvbnMgYW5kIHRoZWlyIHBvd2VyIGFuZCB1dGlsaXR5IGluIFIgaW4gW3RoZSAiRnVuY3Rpb25hbHMiIGNoYXB0ZXIgb2YgdGhlIF9BZHZhbmNlZCBSXyBlLWJvb2tdKGh0dHBzOi8vYWR2LXIuaGFkbGV5Lm56L2Z1bmN0aW9uYWxzLmh0bWwpLgoKT2YgcGFydGljdWxhciBpbnRlcmVzdCBpcyB0aGUgW2BwdXJycjo6bWFwKClgXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL21hcC5odG1sKSBmYW1pbHkgb2YgZnVuY3Rpb25zLCB3aGljaCBjYW4gYmUgdXNlZCB0byBydW4gYSBnaXZlbiBmdW5jdGlvbiBvbiBlYWNoIGVsZW1lbnQgb2YgYSBsaXN0IChvciB2ZWN0b3IpIGluIG9uZSBjYWxsLgpUaGUgZ2VuZXJhbCBzeW50YXggZm9yIGBwdXJycjo6bWFwKClgIGFuZCBmcmllbmRzIGlzOgoKYGBgCiMgU3ludGF4IGZvciB1c2luZyB0aGUgbWFwIGZ1bmN0aW9uOgpwdXJycjo6bWFwKDxpbnB1dCBsaXN0IG9yIHZlY3Rvcj4sCiAgICAgICAgICAgPGZ1bmN0aW9uIHRvIGFwcGx5IHRvIGVhY2ggaXRlbSBpbiB0aGUgaW5wdXQ+LAogICAgICAgICAgIDxhbnkgYWRkaXRpb25hbCBhcmd1bWVudHMgdG8gdGhlIGZ1bmN0aW9uIGNhbiBnbyBoZXJlPiwKICAgICAgICAgICA8YW5kIGFsc28gaGVyZSBpZiB0aGVyZSBhcmUgZXZlbiBtb3JlIGFyZ3VtZW50cywgYW5kIHNvIG9uPikKYGBgCgoKVGhlIG91dHB1dCBmcm9tIHJ1bm5pbmcgYHB1cnJyOjptYXAoKWAgaXMgYWx3YXlzIGEgbGlzdCAoYnV0IG5vdGUgdGhhdCB0aGVyZSBhcmUgb3RoZXIgYHB1cnJyOjptYXAoKWAgcmVsYXRpdmVzIHdoaWNoIHJldHVybiBvdGhlciBvYmplY3QgdHlwZXMsIGFzIHlvdSBjYW4gcmVhZCBhYm91dCBpbiBbdGhlIGBwdXJycjo6bWFwKClgIGRvY3VtZW50YXRpb25dKGh0dHBzOi8vcHVycnIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvaW5kZXguaHRtbCkpLgpJZiB0aGlzIGNvbmNlcHQgc291bmRzIGEgbGl0dGxlIGZhbWlsaWFyIHRvIHlvdSwgdGhhdCdzIGJlY2F1c2UgaXQgcHJvYmFibHkgaXMhCkJhc2UgUidzIGBsYXBwbHkoKWAgZnVuY3Rpb24gY2FuIHByb3ZpZGUgc2ltaWxhciB1dGlsaXR5LCBhbmQgdGhlIGBwdXJycjo6bWFwKClgIGZhbWlseSBvZiBmdW5jdGlvbnMgY2FuIChpbiBwYXJ0KSBiZSB0aG91Z2h0IG9mIGFzIGFuIGFsdGVybmF0aXZlIHRvIHNvbWUgb2YgdGhlIGJhc2UgUiBgYXBwbHlgIGZ1bmN0aW9ucywgd2l0aCBtb3JlIGNvbnNpc3RlbnQgYmVoYXZpb3IuCgpMZXQncyBzZWUgYSB2ZXJ5IHNpbXBsZSBleGFtcGxlIG9mIGBwdXJycjo6bWFwKClgIGluIGFjdGlvbiwgaW5zcGlyZWQgYnkgY2FuY2VyIGdyb3VwcyB0aGUgRGF0YSBMYWIgaGFzIGFuYWx5emVkIHRocm91Z2ggdGhlIFtPcGVuUEJUQV0oaHR0cHM6Ly9naXRodWIuY29tL0FsZXhzTGVtb25hZGUvT3BlblBCVEEtYW5hbHlzaXMvKSBwcm9qZWN0OgoKYGBge3IgbWFwIGV4YW1wbGV9CiMgRGVmaW5lIGEgbGlzdCBvZiBjYW5jZXIgaGlzdG9sb2dpZXMKaGlzdG9sb2dpZXMgPC0gbGlzdCgKICAibG93LWdyYWRlIGdsaW9tYXMiICA9IGMoIlNFR0EiLCAiUEEiLCAiR05HIiwgIlBYQSIpLAogICJoaWdoLWdyYWRlIGdsaW9tYXMiID0gYygiRE1HIiwgIkRJUEciKSwKICAiZW1icnlvbmFsIHR1bW9ycyIgICA9IGMoIk1CIiwgIkFUUlQiLCAiRVRNUiIpCiApCgojIFRoZSBvdmVyYWxsIGxlbmd0aCBvZiB0aGUgbGlzdCBpcyAzCmxlbmd0aChoaXN0b2xvZ2llcykKCiMgSG93IGNhbiB3ZSBydW4gYGxlbmd0aCgpYCBvbiBlYWNoIGl0ZW0gb2YgdGhlIGxpc3Q/CiMgV2UgY2FuIHVzZSBvdXIgbmV3IGZyaWVuZCBwdXJycjo6bWFwKCk6CnB1cnJyOjptYXAoaGlzdG9sb2dpZXMsIGxlbmd0aCkKYGBgCgpPbmUgb3RoZXIgbmV3IGNvZGluZyBzdHJhdGVneSB3ZSdsbCBsZWFybiBpbiB0aGlzIG5vdGVib29rIGlzIHVzaW5nIHRoZSBbYGdsdWVgXShodHRwczovL2dsdWUudGlkeXZlcnNlLm9yZy8pIHBhY2thZ2UgdG8gY29tYmluZSBzdHJpbmdzLgpUaGlzIHBhY2thZ2Ugb2ZmZXJzIGEgY29udmVuaWVudCBmdW5jdGlvbiBgZ2x1ZTo6Z2x1ZSgpYCB0aGF0IGNhbiBiZSB1c2VkIGluc3RlYWQgb2YgdGhlIGJhc2UgUiBgcGFzdGUoKWAgZnVuY3Rpb24uCgpgYGB7ciBwYXN0ZX0KIyBEZWZpbmUgYSB2YXJpYWJsZSBmb3IgZXhhbXBsZToKb3JnX25hbWUgPC0gIkRhdGEgTGFiIgoKIyBXZSBjYW4gdXNlIHBhc3RlIHRvIGNvbWJpbmUgc3RyaW5ncyBhbmQgdmFyaWFibGVzOgpwYXN0ZSgiV2VsY29tZSB0byB0aGUiLCBvcmdfbmFtZSwgIndvcmtzaG9wIG9uIEFkdmFuY2VkIHNjUk5BLXNlcSEiKQpgYGAKCldlIGNhbiB1c2UgYGdsdWU6OmdsdWUoKWAgdG8gYWNjb21wbGlzaCB0aGUgc2FtZSBnb2FsIHdpdGggc29tZSBkaWZmZXJlbnQgc3ludGF4OgoKYGBge3IgZ2x1ZX0KIyBnbHVlOjpnbHVlIHRha2VzIGEgc2luZ2xlIHN0cmluZyBhcmd1bWVudCAob25seSBvbmUgc2V0IG9mIHF1b3RlcyEpLCBhbmQKIyAgdmFyaWFibGVzIGNhbiBlYXNpbHkgYmUgaW5jbHVkZWQgaW5zaWRlIHtjdXJseSBicmFjZXN9CmdsdWU6OmdsdWUoIldlbGNvbWUgdG8gdGhlIHtvcmdfbmFtZX0gd29ya3Nob3Agb24gQWR2YW5jZWQgc2NSTkEtc2VxISIpCmBgYAoKKE5vdGUgdGhhdCBldmVuIHRob3VnaCB0aGUgYGdsdWU6OmdsdWUoKWAgb3V0cHV0IGlzbid0IGluIHF1b3RlcywgaXQgc3RpbGwgYmVoYXZlcyBsaWtlIGEgc3RyaW5nISkKCgpBbHJpZ2h0LCB0aW1lIGZvciB0aGUgZ29vZCBzdHVmZiEKTGV0J3MgdXNlIGBwdXJycjo6bWFwKClgIHRvIHJlYWQgaW4gb3VyIFNDRSBvYmplY3RzIHNvIHRoYXQgdGhleSBhcmUgaW1tZWRpYXRlbHkgc3RvcmVkIHRvZ2V0aGVyIGluIGEgbGlzdC4KCgpXZSdsbCBmaXJzdCBuZWVkIHRvIGRlZmluZSBhIHZlY3RvciBvZiB0aGUgZmlsZSBwYXRocyB0byByZWFkIGluLgpXZSdsbCBzdGFydCBieSBjcmVhdGluZyBhIHZlY3RvciBvZiBzYW1wbGUgbmFtZXMgdGhlbXNlbHZlcyBhbmQgdGhlbiBmb3JtYXR0aW5nIHRoZW0gaW50byB0aGUgY29ycmVjdCBwYXRocy4KVGhpcyB3YXkgKGZvcmVzaGFkb3dpbmchKSB3ZSBhbHNvIGhhdmUgYSBzdGFuZC1hbG9uZSB2ZWN0b3Igb2YganVzdCBzYW1wbGUgbmFtZXMsIHdoaWNoIHdpbGwgY29tZSBpbiBoYW5keSEKCmBgYHtyIHNhbXBsZSBuYW1lc30KIyBWZWN0b3Igb2YgYWxsIHRoZSBzYW1wbGVzIHRvIHJlYWQgaW46CnNhbXBsZV9uYW1lcyA8LSBjKCJTQ1BDTDAwMDQ3OSIsCiAgICAgICAgICAgICAgICAgICJTQ1BDTDAwMDQ4MCIsCiAgICAgICAgICAgICAgICAgICJTQ1BDTDAwMDQ4MSIsCiAgICAgICAgICAgICAgICAgICJTQ1BDTDAwMDQ4MiIpCmBgYAoKCmBgYHtyIGRlZmluZSBzY2VfcGF0aHMsIGxpdmUgPSBUUlVFfQojIE5vdywgY29udmVydCB0aGVzZSB0byBmaWxlIHBhdGhzOiA8aW5wdXRfZGlyPi88c2FtcGxlX25hbWU+LnJkcwpzY2VfcGF0aHMgPC0gZmlsZS5wYXRoKGlucHV0X2RpciwKICAgICAgICAgICAgICAgICAgICAgICBnbHVlOjpnbHVlKCJ7c2FtcGxlX25hbWVzfS5yZHMiKQopCiMgUHJpbnQgdGhlIHNjZV9wYXRocyB2ZWN0b3IKc2NlX3BhdGhzCmBgYAoKV2UgY2FuIG5vdyByZWFkIHRoZXNlIGZpbGVzIGluIGFuZCBjcmVhdGUgYSBsaXN0IG9mIGZvdXIgU0NFIG9iamVjdHMuClNpbmNlIGByZWFkcjo6cmVhZF9yZHMoKWAgY2FuIG9ubHkgb3BlcmF0ZSBvbiBvbmUgaW5wdXQgYXQgYSB0aW1lLCB3ZSdsbCBuZWVkIHRvIHVzZSBgcHVycnI6Om1hcCgpYCB0byBydW4gaXQgb24gYWxsIGlucHV0IGZpbGUgcGF0aHMgaW4gb25lIGNvbW1hbmQuCkFsdGhvdWdoIGBzY2VfcGF0aHNgIGlzIGEgdmVjdG9yIChub3QgYSBsaXN0KSwgaXQgd2lsbCBzdGlsbCB3b3JrIGFzIGlucHV0IHRvIGBwdXJycjptYXAoKWAuClRoZSBvdXRwdXQgZnJvbSB0aGlzIGNvZGUgd2lsbCBzdGlsbCBiZSBhIGxpc3QsIHNpbmNlIHRoYXQncyB3aGF0IGBwdXJycjo6bWFwKClgIGFsd2F5cyByZXR1cm5zLgoKYGBge3IgcmVhZCBzY2UgcGF0aHMsIGxpdmUgPSBUUlVFfQojIFVzZSBwdXJycjo6bWFwKCkgdG8gcmVhZCBhbGwgZmlsZXMgaW50byBhIGxpc3QgYXQgb25jZQpzY2VfbGlzdCA8LSBwdXJycjo6bWFwKAogIHNjZV9wYXRocywKICByZWFkcjo6cmVhZF9yZHMKKQpgYGAKCkxldCdzIGhhdmUgYSBsb29rIGF0IG91ciBsaXN0IG9mIFNDRSBvYmplY3RzOgoKYGBge3IgcHJpbnQgc2NlIGxpc3QsIGxpdmU9VFJVRX0KIyBQcmludCBzY2VfbGlzdApzY2VfbGlzdApgYGAKCldlIG5vdyBoYXZlIGEgbGlzdCBvZiBsZW5ndGggZm91ciwgd2hlcmUgZWFjaCBpdGVtIGlzIGEgcHJvY2Vzc2VkIFNDRSBvYmplY3QhCkhvd2V2ZXIsIHdlJ2xsIG5lZWQgdG8ga2VlcCB0cmFjayBvZiB3aGljaCBzYW1wbGUgZWFjaCBpdGVtIGlzLCBzbyBpdCdzIGhlbHBmdWwgdG8gYWRkIF9uYW1lc18gdG8gdGhpcyBsaXN0IHJlcHJlc2VudGluZyB0aGUgcmVsZXZhbnQgc2FtcGxlIG5hbWVzLgoKYGBge3IgYWRkIGxpc3QgbmFtZXMsIGxpdmUgPSBUUlVFfQojIEFzc2lnbiB0aGUgc2FtcGxlIG5hbWVzIGFzIHRoZSBuYW1lcyBmb3Igc2NlX2xpc3QKbmFtZXMoc2NlX2xpc3QpIDwtIHNhbXBsZV9uYW1lcwpgYGAKCmBgYHtyIHByaW50IG5hbWVkIGxpc3QsIGxpdmU9VFJVRX0KIyBQcmludCB0aGUgbGlzdCB0byBzZWUgaXQgd2l0aCBuYW1lcwpzY2VfbGlzdApgYGAKCklmIHlvdSBsb29rIGNsb3NlbHkgYXQgdGhlIHByaW50ZWQgU0NFIG9iamVjdHMsIHlvdSBtYXkgbm90aWNlIHRoYXQgdGhleSBhbGwgY29udGFpbiBgY29sRGF0YWAgdGFibGUgY29sdW1ucyBgY2VsbHR5cGVfZmluZWAgYW5kIGBjZWxsdHlwZV9icm9hZGAuClRoZXNlIGNvbHVtbnMgKHdoaWNoIHdlIGFkZGVkIHRvIFNDRSBvYmplY3RzIGR1cmluZyBbcHJlLXByb2Nlc3NpbmddKGh0dHBzOi8vZ2l0aHViLmNvbS9BbGV4c0xlbW9uYWRlL3RyYWluaW5nLW1vZHVsZXMvdHJlZS9tYXN0ZXIvc2NSTkEtc2VxLWFkdmFuY2VkL3NldHVwL3JtcykpIGNvbnRhaW4gcHV0YXRpdmUgX2NlbGwgdHlwZSBhbm5vdGF0aW9uc18gYXMgYXNzaWduZWQgaW4gW1BhdGVsIF9ldCBhbC5fICgyMDIyKV0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMTYvai5kZXZjZWwuMjAyMi4wNC4wMDMpLgpXZSB3aWxsIGVuZCB1cCBsZXZlcmFnaW5nIHRoZXNlIGNlbGwgdHlwZSBhbm5vdGF0aW9ucyB0byBleHBsb3JlIGhvdyBzdWNjZXNzZnVsIG91ciBpbnRlZ3JhdGlvbiBpczsgYWZ0ZXIgaW50ZWdyYXRpb24sIHdlIGV4cGVjdCBjZWxsIHR5cGVzIGZyb20gZGlmZmVyZW50IHNhbXBsZXMgdG8gZ3JvdXAgdG9nZXRoZXIsIHJhdGhlciB0aGFuIGJlaW5nIHNlcGFyYXRlZCBieSBiYXRjaGVzLgoKVGhhdCBzYWlkLCB0aGUgaW50ZWdyYXRpb24gbWV0aG9kcyB3ZSB3aWxsIGJlIGFwcGx5aW5nIF9kbyBub3QgYWN0dWFsbHkgdXNlXyB0aGVzZSBjZWxsIHR5cGUgYW5ub3RhdGlvbnMuCklmIHdlIGhhdmUgYW5ub3RhdGlvbnMsIHRoZXkgYXJlIGEgaGVscGZ1bCAiYm9udXMiIGZvciBhc3Nlc3NpbmcgdGhlIGludGVncmF0aW9uJ3Mgc3VjY2VzcywgYnV0IHRoZXkgYXJlIG5vdCBwYXJ0IG9mIHRoZSBpbnRlZ3JhdGlvbiBpdHNlbGYuCgoKIyMgUHJlcGFyZSB0aGUgU0NFIGxpc3QgZm9yIGludGVncmF0aW9uCgohW1NpbmdsZS1jZWxsIHJvYWRtYXA6IE1lcmdlXShkaWFncmFtcy9yb2FkbWFwX211bHRpX21lcmdlLnBuZykKCgpOb3cgdGhhdCB3ZSBoYXZlIGEgbGlzdCBvZiBwcm9jZXNzZWQgU0NFIG9iamVjdHMsIHdlIG5lZWQgdG8gbWVyZ2UgdGhlIG9iamVjdHMgaW50byBvbmUgb3ZlcmFsbCBTQ0Ugb2JqZWN0IGZvciBpbnB1dCB0byBpbnRlZ3JhdGlvbi4KQSB3b3JkIG9mIGNhdXRpb24gYmVmb3JlIHdlIGJlZ2luOiAqKlRoaXMgbWVyZ2VkIFNDRSBvYmplY3QgaXMgTk9UIGFuIGludGVncmF0ZWQgU0NFISoqCk1lcmdpbmcgU0NFcyBkb2VzIG5vdCBwZXJmb3JtIGFueSBiYXRjaCBjb3JyZWN0aW9uLCBidXQganVzdCByZW9yZ2FuaXplcyB0aGUgZGF0YSB0byBhbGxvdyB1cyB0byBwcm9jZWVkIHRvIGludGVncmF0aW9uIG5leHQuCgpUbyBtZXJnZSBTQ0Ugb2JqZWN0cywgd2UgZG8gbmVlZCB0byBkbyBzb21lIHdyYW5nbGluZyBhbmQgYm9va2tlZXBpbmcgdG8gZW5zdXJlIGNvbXBhdGliaWxpdHkgYW5kIHRoYXQgd2UgZG9uJ3QgbG9zZSBpbXBvcnRhbnQgaW5mb3JtYXRpb24uCk92ZXJhbGwgd2UnbGwgd2FudCB0byB0YWtlIGNhcmUgb2YgdGhlc2UgaXRlbXM6CgoxLiBXZSBzaG91bGQgYmUgYWJsZSB0byB0cmFjZSBzYW1wbGUtc3BlY2lmaWMgaW5mb3JtYXRpb24gYmFjayB0byB0aGUgb3JpZ2luYXRpbmcgc2FtcGxlLCBpbmNsdWRpbmcuLi4KICAgIC0gQ2VsbC1sZXZlbCBpbmZvcm1hdGlvbjogV2hpY2ggc2FtcGxlIGlzIGVhY2ggY2VsbCBmcm9tPwogICAgLSBMaWJyYXJ5LXNwZWNpZmljIGZlYXR1cmUgc3RhdGlzdGljcywgZS5nLiwgZ2VuZS1sZXZlbCBzdGF0aXN0aWNzIGZvciBhIGdpdmVuIGxpYnJhcnkgZm91bmQgaW4gYHJvd0RhdGFgLgogICAgV2hpY2ggc2FtcGxlIGlzIGEgZ2l2ZW4gZmVhdHVyZSBzdGF0aXN0aWMgZnJvbT8KMi4gU0NFIG9iamVjdHMgc2hvdWxkIGNvbnRhaW4gdGhlIHNhbWUgZ2VuZXM6IEVhY2ggU0NFIG9iamVjdCBzaG91bGQgaGF2ZSB0aGUgc2FtZSByb3cgbmFtZXMuCjMuIFNDRSBjZWxsIG1ldGFkYXRhIGNvbHVtbnMgc2hvdWxkIG1hdGNoOiBUaGUgYGNvbERhdGFgIGZvciBlYWNoIFNDRSBvYmplY3Qgc2hvdWxkIGhhdmUgdGhlIHNhbWUgY29sdW1uIG5hbWVzLgoKCldlJ2xsIGJlZ2luIGJ5IHRha2luZyBzb21lIHRpbWUgdG8gdGhvcm91Z2hseSBleHBsb3JlIG91ciBTQ0Ugb2JqZWN0cyBhbmQgZmlndXJlIG91dCB3aGF0IHdyYW5nbGluZyBzdGVwcyB3ZSBuZWVkIHRvIHRha2UgZm9yIHRoZXNlIHNwZWNpZmljIGRhdGEuCkRvbid0IHNraXAgdGhpcyBleHBsb3JhdGlvbiEKQmVhciBpbiBtaW5kIHRoYXQgdGhlIGV4YWN0IHdyYW5nbGluZyBzaG93biBoZXJlIHdpbGwgbm90IGJlIHRoZSBzYW1lIGZvciBvdGhlciBTQ0Ugb2JqZWN0cyB5b3Ugd29yayB3aXRoLCBidXQgdGhlIHNhbWUgZ2VuZXJhbCBwcmluY2lwbGVzIGFwcGx5LgoKCiMjIyMgUHJlc2VydmluZyBzYW1wbGUgaW5mb3JtYXRpb24gYXQgdGhlIGNlbGwgbGV2ZWwKCkhvdyB3aWxsIHdlIGJlIGFibGUgdG8gdGVsbCB3aGljaCBzYW1wbGUgYSBnaXZlbiBjZWxsIGNhbWUgZnJvbT8KClRoZSBiZXN0IHdheSB0byBkbyB0aGlzIGlzIHNpbXBseSB0byBhZGQgYSBgY29sRGF0YWAgY29sdW1uIHdpdGggdGhlIHNhbXBsZSBpbmZvcm1hdGlvbiwgc28gdGhhdCB3ZSBjYW4ga25vdyB3aGljaCBzYW1wbGUgZWFjaCByb3cgY2FtZSBmcm9tLgoKSW4gYWRkaXRpb24sIHdlIHdhbnQgdG8gcGF5IHNvbWUgYXR0ZW50aW9uIHRvIHRoZSBTQ0Ugb2JqZWN0J3MgY29sdW1uIG5hbWVzICh0aGUgY2VsbCBpZHMpLCB3aGljaCBtdXN0IHJlbWFpbiB1bmlxdWUgYWZ0ZXIgbWVyZ2luZyBzaW5jZSBkdXBsaWNhdGUgaWRzIHdpbGwgY2F1c2UgYW4gUiBlcnJvci4KSW4gdGhpcyBjYXNlLCB0aGUgU0NFIGNvbHVtbiBuYW1lcyBhcmUgYmFyY29kZXMgKHdoaWNoIGlzIHVzdWFsbHkgYnV0IG5vdCBhbHdheXMgdGhlIGNhc2UgaW4gU0NFIG9iamVjdHMpLCB3aGljaCBhcmUgb25seSBndWFyYW50ZWVkIHRvIGJlIHVuaXF1ZSBfd2l0aGluXyBhIHNhbXBsZSBidXQgbWF5IGJlIHJlcGVhdGVkIGFjcm9zcyBzYW1wbGVzLgpTbywgYWZ0ZXIgbWVyZ2luZywgaXQncyB0ZWNobmljYWxseSBwb3NzaWJsZSB0aGF0IG11bHRpcGxlIGNlbGxzIHdpbGwgaGF2ZSB0aGUgc2FtZSBiYXJjb2RlLgpUaGlzIHdvdWxkIGJlIGEgcHJvYmxlbSBmb3IgdHdvIHJlYXNvbnM6CkZpcnN0LCB0aGUgY2VsbCBpZCB3b3VsZCBub3QgYmUgYWJsZSB0byBwb2ludCB1cyBiYWNrIHRvIGNlbGwncyBvcmlnaW5hdGluZyBzYW1wbGUuClNlY29uZCwgaXQgd291bGQgbGl0ZXJhbGx5IGNhdXNlIGFuIGVycm9yIGluIFIsIHdoaWNoIGRvZXMgbm90IGFsbG93IGR1cGxpY2F0ZSBjb2x1bW4gbmFtZXMuCgoKT25lIHdheSB0byBlbnN1cmUgdGhhdCBjZWxsIGlkcyByZW1haW4gdW5pcXVlIGV2ZW4gYWZ0ZXIgbWVyZ2luZyBpcyB0byBhY3R1YWxseSBtb2RpZnkgdGhlbSBieSBfcHJlcGVuZGluZ18gdGhlIHJlbGV2YW50IHNhbXBsZSBuYW1lLgpGb3IgZXhhbXBsZSwgY29uc2lkZXIgdGhlc2UgYmFyY29kZXMgZm9yIHRoZSBgU0NQQ0wwMDA0NzlgIHNhbXBsZToKCmBgYHtyIGJhcmNvZGVzfQojIExvb2sgYXQgdGhlIGNvbHVtbiBuYW1lcyBmb3IgdGhlIGBTQ1BDTDAwMDQ3OWAgc2FtcGxlLCBmb3IgZXhhbXBsZQpjb2xuYW1lcyhzY2VfbGlzdCRTQ1BDTDAwMDQ3OSkgfD4KICAjIE9ubHkgcHJpbnQgb3V0IHRoZSBmaXJzdCA2IGZvciBjb252ZW5pZW5jZQogIGhlYWQoKQpgYGAKClRoZXNlIGlkcyB3aWxsIGJlIHVwZGF0ZWQgdG8gYFNDUENMMDAwNDc5LUdHR0FDQ1RDQUFHQ0dHQVRgLCBgU0NQQ0wwMDA0NzktQ0FDQUdBVEFHVEdBR1RHQ2AsIGFuZCBzbyBvbiwgdGhlcmVieSBlbnN1cmluZyBmdWxseSB1bmlxdWUgaWRzIGZvciBhbGwgY2VsbHMgYWNyb3NzIGFsbCBzYW1wbGVzLgoKIyMjIyBQcmVzZXJ2aW5nIHNhbXBsZSBpbmZvcm1hdGlvbiBhdCB0aGUgZ2VuZSBsZXZlbAoKVGhlIGByb3dEYXRhYCB0YWJsZSBpbiBTQ0Ugb2JqZWN0cyB3aWxsIG9mdGVuIGNvbnRhaW4gYm90aCAiZ2VuZXJhbCIgYW5kICJsaWJyYXJ5LXNwZWNpZmljIiBpbmZvcm1hdGlvbiwgZm9yIGV4YW1wbGU6CgpgYGB7ciByb3dkYXRhfQpyb3dEYXRhKHNjZV9saXN0JFNDUENMMDAwNDc5KSB8PgogIGhlYWQoKQpgYGAKCkhlcmUsIHRoZSByb3duYW1lcyBhcmUgRW5zZW1ibCBnZW5lIGlkcywgYW5kIGNvbHVtbnMgYXJlIGBnZW5lX3N5bWJvbGAsIGBtZWFuYCwgYW5kIGBkZXRlY3RlZGAuClRoZSBgZ2VuZV9zeW1ib2xgIGNvbHVtbiBpcyBnZW5lcmFsIGluZm9ybWF0aW9uIGFib3V0IGFsbCBnZW5lcywgbm90IHNwZWNpZmljIHRvIGFueSBsaWJyYXJ5IG9yIGV4cGVyaW1lbnQsIGJ1dCBgbWVhbmAgYW5kIGBkZXRlY3RlZGAgYXJlIGxpYnJhcnktc3BlY2lmaWMgZ2VuZSBzdGF0aXN0aWNzLgpTbywgYGdlbmVfc3ltYm9sYCBkb2VzIG5vdCBuZWVkIHRvIGJlIHRyYWNlZCBiYWNrIHRvIGl0cyBvcmlnaW5hdGluZyBzYW1wbGUsIGJ1dCBgbWVhbmAgYW5kIGBkZXRlY3RlZGAgZG8uClRvIHRoaXMgZW5kLCB3ZSBjYW4gdGFrZSBhIHNpbWlsYXIgYXBwcm9hY2ggdG8gd2hhdCB3ZSdsbCBkbyBmb3IgY2VsbCBpZHM6CldlIGNhbiBjaGFuZ2UgdGhlIHNhbXBsZS1zcGVjaWZpYyBgcm93RGF0YWAgY29sdW1uIG5hbWVzIGJ5IHByZXBlbmRpbmcgdGhlIHNhbXBsZSBuYW1lLgpGb3IgZXhhbXBsZSwgcmF0aGVyIHRoYW4gYmVpbmcgY2FsbGVkIGBtZWFuYCwgdGhpcyBjb2x1bW4gd2lsbCBiZSBuYW1lZCBgU0NQQ0wwMDA0NzktbWVhbmAgZm9yIHRoZSBgU0NQQ0wwMDA0NzlgIHNhbXBsZS4KCkFsbCBvdXIgU0NFIG9iamVjdHMgaGF2ZSB0aGUgc2FtZSBgcm93RGF0YWAgY29sdW1ucyAoYXMgd2UgY2FuIHNlZSBpbiB0aGUgbmV4dCBjaHVuayksIHNvIHdlJ2xsIHBlcmZvcm0gdGhpcyByZW5hbWluZyBhY3Jvc3MgYWxsIFNDRXMuCgpgYGB7ciBjb21wYXJlIHJvd2RhdGEsIGxpdmUgPSBUUlVFfQojIFVzZSBgcHVycnI6Om1hcCgpYCB0byBxdWlja2x5IGV4dHJhY3Qgcm93RGF0YSBjb2x1bW4gbmFtZXMgZm9yIGFsbCBTQ0VzCnB1cnJyOjptYXAoc2NlX2xpc3QsCiAgICAgICAgICAgXCh4KSBjb2xuYW1lcyhyb3dEYXRhKHgpKSkKYGBgCgoKIyMjIyBFbnN1cmluZyB0aGF0IG9ubHkgc2hhcmVkIGdlbmVzIGFyZSB1c2VkCgpUaGUgbmV4dCBzdGVwIGluIGVuc3VyaW5nIFNDRSBjb21wYXRpYmlsaXR5IGlzIHRvIG1ha2Ugc3VyZSB0aGV5IGFsbCBjb250YWluIHRoZSBzYW1lIGdlbmVzLCB3aGljaCBhcmUgc3RvcmVkIGFzIHRoZSBTQ0Ugb2JqZWN0J3Mgcm93IG5hbWVzICh0aGVzZSBuYW1lcyBhcmUgYWxzbyBmb3VuZCB0aGUgYHJvd0RhdGFgIHNsb3QncyByb3cgbmFtZXMpLgpIZXJlLCB0aG9zZSBnZW5lIGlkcyBhcmUgdW5pcXVlIEVuc2VtYmwgZ2VuZSBpZHMuCgpXZSBjYW4gdXNlIHNvbWUgYHB1cnJyYCBtYWdpYyB0byBxdWlja2x5IGZpbmQgdGhlIHNldCBvZiBzaGFyZWQgZ2VuZXMgYW1vbmcgb3VyIHNhbXBsZXM6CgpgYGB7ciBzaGFyZWQgZ2VuZXN9CiMgRGVmaW5lIHZlY3RvciBvZiBzaGFyZWQgZ2VuZXMKc2hhcmVkX2dlbmVzIDwtIHNjZV9saXN0IHw+CiAgIyBnZXQgcm93bmFtZXMgKGdlbmVzKSBmb3IgZWFjaCBTQ0UgaW4gc2NlX2xpc3QKICBwdXJycjo6bWFwKHJvd25hbWVzKSB8PgogICMgcmVkdWNlIHRvIHRoZSBfaW50ZXJzZWN0aW9uXyBhbW9uZyBsaXN0cwogIHB1cnJyOjpyZWR1Y2UoaW50ZXJzZWN0KQpgYGAKCmBgYHtyIHByaW50IHNoYXJlZCBnZW5lcywgbGl2ZSA9IFRSVUV9CiMgVXNlIGhlYWQgdG8gbG9vayBhdCB0aGUgdmVjdG9yIG9mIHNoYXJlZCBnZW5lczoKaGVhZChzaGFyZWRfZ2VuZXMpCmBgYAoKSW4gdGhpcyBjYXNlLCB3ZSBoYXBwZW4gdG8ga25vdyB0aGF0IGFsbCBTQ0Ugb2JqZWN0cyB3ZSdyZSB3b3JraW5nIHdpdGggYWxyZWFkeSBjb250YWluZWQgdGhlIHNhbWUgZ2VuZXMuCldlIGRvIGEgcXVpY2stYW5kLWRpcnR5IGNoZWNrIGZvciB0aGlzIGJ5IGxvb2tpbmcgYXQgdGhlIG51bWJlciBvZiByb3dzIGFjcm9zcyBTQ0Ugb2JqZWN0cywgYW5kIHdlJ2xsIHNlZSB0aGF0IHRoZXkgYXJlIGFsbCB0aGUgc2FtZToKCmBgYHtyIGNoZWNrIHNoYXJlZCBnZW5lcywgbGl2ZSA9IFRSVUV9CiMgVGhlIG51bWJlciBvZiBnZW5lcyBpbiBhbiBTQ0UgY29ycmVzcG9uZHMgdG8gaXRzIG51bWJlciBvZiByb3dzOgpzY2VfbGlzdCB8PgogIHB1cnJyOjptYXAobnJvdykKYGBgCgpTbywgZm9yIG91ciBkYXRhLCB3ZSB3aWxsIG5vdCBoYXZlIHRvIHN1YnNldCB0byBzaGFyZWQgZ2VuZXMgc2luY2UgdGhleSBhcmUgYWxyZWFkeSBzaGFyZWQhCgojIyMjIEVuc3VyaW5nIG1hdGNoaW5nIGNvbHVtbnMgaW4gYGNvbERhdGFgCgpGaW5hbGx5LCB3ZSdsbCBuZWVkIHRvIGhhdmUgdGhlIHNhbWUgY29sdW1uIG5hbWVzIGFjcm9zcyBhbGwgU0NFIGBjb2xEYXRhYCB0YWJsZXMsIHNvIGxldCdzIGxvb2sgYXQgYWxsIHRob3NlIGNvbHVtbiBuYW1lcy4KV2UgY2FuIHVzZSBzaW1pbGFyIHN5bnRheCBoZXJlIHRvIHdoYXQgd2UgdXNlZCB0byBsb29rIGF0IGFsbCB0aGUgYHJvd0RhdGFgIGNvbHVtbiBuYW1lcy4KCmBgYHtyIGNvbXBhcmUgY29sZGF0YX0KcHVycnI6Om1hcChzY2VfbGlzdCwKICAgICAgICAgICBcKHgpIGNvbG5hbWVzKGNvbERhdGEoeCkpICkKYGBgCgpJdCBsb29rcyBsaWtlIHRoZSBjb2x1bW4gbmFtZXMgYXJlIGFsbCBhbHJlYWR5IG1hdGNoaW5nIGFtb25nIFNDRXMsIHNvIHRoZXJlJ3Mgbm8gc3BlY2lmaWMgcHJlcGFyYXRpb24gd2UnbGwgbmVlZCB0byBkbyB0aGVyZS4KCiMjIyBQZXJmb3JtIFNDRSBtZXJnaW5nCgpBcyB5b3UgY2FuIHNlZSwgdGhlcmUncyBhIGxvdCBvZiBtb3ZpbmcgcGFydHMgdG8gY29uc2lkZXIhCkFnYWluLCB0aGVzZSBtb3ZpbmcgcGFydHMgbWF5ICh3aWxsISkgZGlmZmVyIGZvciBTQ0VzIHRoYXQgeW91IGFyZSB3b3JraW5nIHdpdGgsIHNvIHlvdSBoYXZlIHRvIGV4cGxvcmUgeW91ciBvd24gU0NFcyBpbiBkZXB0aCB0byBwcmVwYXJlIGZvciBtZXJnaW5nLgoKQmFzZWQgb24gb3VyIGV4cGxvcmF0aW9uLCBoZXJlIGlzIGEgc2NoZW1hdGljIG9mIGhvdyBvbmUgb2YgdGhlIFNDRSBvYmplY3RzIHdpbGwgdWx0aW1hdGVseSBiZSBtb2RpZmllZCBpbnRvIHRoZSBmaW5hbCBtZXJnZWQgU0NFOgoKIVtdKGRpYWdyYW1zL3RlY2huaWNhbF9tZXJnZV9zY2UucG5nKQoKCldlJ2xsIHdyaXRlIGEgX2N1c3RvbSBmdW5jdGlvbl8gKHNlZW4gaW4gdGhlIGNodW5rIGJlbG93KSB0YWlsb3JlZCB0byBvdXIgd3JhbmdsaW5nIHN0ZXBzIHRoYXQgcHJlcGFyZXMgYSBzaW5nbGUgU0NFIG9iamVjdCBmb3IgbWVyZ2luZy4KV2UnbGwgdGhlbiB1c2Ugb3VyIG5ldyBgcHVycnI6Om1hcCgpYCBwcm9ncmFtbWluZyBza2lsbHMgdG8gcnVuIHRoaXMgZnVuY3Rpb24gb3ZlciB0aGUgYHNjZV9saXN0YC4KVGhpcyB3aWxsIGdpdmUgdXMgYSBuZXcgbGlzdCBvZiBmb3JtYXR0ZWQgU0NFcyB0aGF0IHdlIGNhbiBwcm9jZWVkIHRvIG1lcmdlLgpJdCdzIGltcG9ydGFudCB0byByZW1lbWJlciB0aGF0IHRoZSBgZm9ybWF0X3NjZSgpYCBmdW5jdGlvbiB3cml0dGVuIGJlbG93IGlzIG5vdCBhIGZ1bmN0aW9uIGZvciBnZW5lcmFsIHVzZSDigJMgaXQncyBiZWVuIHByZWNpc2VseSB3cml0dGVuIHRvIG1hdGNoIHRoZSBwcm9jZXNzaW5nIHdlIG5lZWQgdG8gZG8gZm9yIF90aGVzZV8gU0NFcywgYW5kIGRpZmZlcmVudCBTQ0VzIHlvdSB3b3JrIHdpdGggd2lsbCByZXF1aXJlIGRpZmZlcmVudCB0eXBlcyBvZiBwcm9jZXNzaW5nLgoKYGBge3IgZm9ybWF0X3NjZSBmdW5jdGlvbn0KZm9ybWF0X3NjZSA8LSBmdW5jdGlvbihzY2UsIHNhbXBsZV9uYW1lKSB7CiAgIyBJbnB1dCBhcmd1bWVudHM6CiAgIyMgc2NlOiBBbiBTQ0Ugb2JqZWN0IHRvIGZvcm1hdAogICMjIHNhbXBsZV9uYW1lOiBUaGUgU0NFIG9iamVjdCdzIG5hbWUKICAjIFRoaXMgZnVuY3Rpb24gcmV0dXJucyBhIGZvcm1hdHRlZCBTQ0Ugb2JqZWN0LgoKICAjIyMjIyMgRW5zdXJlIHRoYXQgd2UgY2FuIGlkZW50aWZ5IHRoZSBvcmlnaW5hdGluZyBzYW1wbGUgaW5mb3JtYXRpb24gIyMjIyMjCiAgIyBBZGQgYSBjb2x1bW4gY2FsbGVkIGBzYW1wbGVgIHRoYXQgc3RvcmVzIHRoaXMgaW5mb3JtYXRpb24KICAjIFRoaXMgd2lsbCBiZSBzdG9yZWQgaW4gYGNvbERhdGFgCiAgc2NlJHNhbXBsZSA8LSBzYW1wbGVfbmFtZQoKCiAgIyMjIyMjIEVuc3VyZSBjZWxsIGlkcyB3aWxsIGJlIHVuaXF1ZSAjIyMjIyMKICAjIFVwZGF0ZSB0aGUgU0NFIG9iamVjdCBjb2x1bW4gbmFtZXMgKGNlbGwgaWRzKSBieSBwcmVwZW5kaW5nIGBzYW1wbGVfbmFtZWAKICBjb2xuYW1lcyhzY2UpIDwtIGdsdWU6OmdsdWUoIntzYW1wbGVfbmFtZX0te2NvbG5hbWVzKHNjZSl9IikKCgogICMjIyMjIyBFbnN1cmUgZ2VuZS1sZXZlbCBzdGF0aXN0aWNzIGNhbiBiZSBpZGVudGlmaWVkIGluIGByb3dEYXRhYCAjIyMjIyMKICAjIFdlIHdhbnQgdG8gcmVuYW1lIHRoZSBjb2x1bW5zIGBtZWFuYCBhbmQgYGRldGVjdGVkYCB0byBjb250YWluIHRoZSBgc2FtcGxlX25hbWVgCiAgIyBSZWNhbGwgdGhlIG5hbWVzIGFyZTogImdlbmVfc3ltYm9sIiwgIm1lYW4iLCAiZGV0ZWN0ZWQiCiAgY29sbmFtZXMocm93RGF0YShzY2UpKSA8LSBjKCJnZW5lX3N5bWJvbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdsdWU6OmdsdWUoIntzYW1wbGVfbmFtZX0tbWVhbiIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnbHVlOjpnbHVlKCJ7c2FtcGxlX25hbWV9LWRldGVjdGVkIikpCgogICMgUmV0dXJuIHRoZSBmb3JtYXR0ZWQgU0NFIG9iamVjdAogIHJldHVybihzY2UpCn0KYGBgCgpUbyBydW4gdGhpcyBmdW5jdGlvbiwgd2UnbGwgdXNlIHRoZSBgcHVycnI6Om1hcDIoKWAgZnVuY3Rpb24sIGEgcmVsYXRpdmUgb2YgYHB1cnJyOjptYXAoKWAgdGhhdCBhbGxvd3MgeW91IHRvIGxvb3Agb3ZlciBfdHdvXyBpbnB1dCBsaXN0cy92ZWN0b3JzLgpJbiBvdXIgY2FzZSwgd2Ugd2FudCB0byBydW4gYGZvcm1hdF9zY2UoKWAgb3ZlciBwYWlyZWQgYHNjZV9saXN0YCBpdGVtcyBhbmQgYHNjZV9saXN0YCBuYW1lcy4KCmBgYHtyIGZvcm1hdCBzY2VzIGZvciBtZXJnZSwgbGl2ZSA9IFRSVUV9CiMgV2UgY2FuIHVzZSBgcHVycnI6Om1hcDIoKWAgdG8gbG9vcCBvdmVyIHR3byBsaXN0L3ZlY3RvciBhcmd1bWVudHMKc2NlX2xpc3RfZm9ybWF0dGVkIDwtIHB1cnJyOjptYXAyKAogICMgRWFjaCAiaXRlcmF0aW9uIiB3aWxsIG1hcmNoIGRvd24gdGhlIGZpcnN0IHR3bwogICMgIGFyZ3VtZW50cyBgc2NlX2xpc3RgIGFuZCBgbmFtZXMoc2NlX2xpc3QpYCBpbiBvcmRlcgogIHNjZV9saXN0LAogIG5hbWVzKHNjZV9saXN0KSwKICAjIE5hbWUgb2YgdGhlIGZ1bmN0aW9uIHRvIHJ1bgogIGZvcm1hdF9zY2UKKQoKIyBQcmludCByZXN1bHRpbmcgbGlzdApzY2VfbGlzdF9mb3JtYXR0ZWQKYGBgCgooUHNzdCwgbGlrZSBgcHVycnJgIGFuZCB3YW50IHRvIGRpdmUgZGVlcGVyPyBDaGVjayBvdXQgW3RoZSBgcHVycnI6OmltYXAoKWAgZnVuY3Rpb25dKGh0dHBzOi8vcHVycnIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvaW1hcC5odG1sKSEpCgoKQXQgbG9uZyBsYXN0LCB3ZSBhcmUgcmVhZHkgdG8gbWVyZ2UgdGhlIFNDRXMsIHdoaWNoIHdlJ2xsIGRvIHVzaW5nIHRoZSBSIGZ1bmN0aW9uIGBjYmluZCgpYC4KVGhlIGBjYmluZCgpYCBmdW5jdGlvbiBpcyBvZnRlbiB1c2VkIHRvIGNvbWJpbmUgZGF0YSBmcmFtZXMgb3IgbWF0cmljZXMgYnkgY29sdW1uLCBpLmUuICJzdGFjayIgdGhlbSBuZXh0IHRvIGVhY2ggb3RoZXIuClRoZSBzYW1lIHByaW5jaXBsZSBhcHBsaWVzIGhlcmUsIGJ1dCB3aGVuIHJ1biBvbiBTQ0Ugb2JqZWN0cywgYGNiaW5kKClgIHdpbGwgY3JlYXRlIGEgbmV3IFNDRSBvYmplY3QgYnkgY29tYmluaW5nIGBjb3VudHNgIGFuZCBgbG9nY291bnRzYCBtYXRyaWNlcyBieSBjb2x1bW4uCkZvbGxvd2luZyB0aGF0IHN0cnVjdHVyZSwgb3RoZXIgU0NFIHNsb3RzIChgY29sRGF0YWAsIGByb3dEYXRhYCwgcmVkdWNlZCBkaW1lbnNpb25zLCBhbmQgb3RoZXIgbWV0YWRhdGEpIGFyZSBjb21iaW5lZCBhcHByb3ByaWF0ZWx5LgoKU2luY2Ugd2UgbmVlZCB0byBhcHBseSBgY2JpbmQoKWAgdG8gYSBfbGlzdF8gb2Ygb2JqZWN0cywgd2UgbmVlZCB0byB1c2Ugc29tZSBzbGlnaHRseS1nbmFybHkgc3ludGF4OiBXZSdsbCB1c2UgdGhlIGZ1bmN0aW9uIGBkby5jYWxsKClgLCB3aGljaCBhbGxvd3MgdGhlIGBjYmluZCgpYCBpbnB1dCB0byBiZSBhIGxpc3Qgb2Ygb2JqZWN0cyB0byBjb21iaW5lLgoKYGBge3IgbWVyZ2VzIHNjZXMsIGxpdmUgPSBUUlVFfQojIE1lcmdlIFNDRSBvYmplY3RzCm1lcmdlZF9zY2UgPC0gZG8uY2FsbChjYmluZCwgc2NlX2xpc3RfZm9ybWF0dGVkKQoKIyBQcmludCB0aGUgbWVyZ2VkX3NjZSBvYmplY3QKbWVyZ2VkX3NjZQpgYGAKCldlIG5vdyBoYXZlIGEgc2luZ2xlIFNDRSBvYmplY3QgdGhhdCBjb250YWlucyBhbGwgY2VsbHMgZnJvbSBhbGwgc2FtcGxlcyB3ZSdkIGxpa2UgdG8gaW50ZWdyYXRlLgoKTGV0J3MgdGFrZSBhIHBlZWsgYXQgc29tZSBvZiB0aGUgaW5uYXJkcyBvZiB0aGlzIG5ldyBTQ0Ugb2JqZWN0OgoKYGBge3IgZXhwbG9yZSBtZXJnZWRfc2NlLCBsaXZlID0gVFJVRX0KIyBXaGF0IGFyZSB0aGUgdW5pcXVlIHZhbHVlcyBpbiB0aGUgYHNhbXBsZWAgY29sdW1uPwp1bmlxdWUoIGNvbERhdGEobWVyZ2VkX3NjZSkkc2FtcGxlICkKCiMgV2hhdCBhcmUgdGhlIG5ldyBjZWxsIGlkcyAoY29sdW1uIG5hbWVzKT8KaGVhZCggY29sbmFtZXMobWVyZ2VkX3NjZSkgKQoKIyBXaGF0IGRvZXMgcm93RGF0YSBsb29rIGxpa2U/CmhlYWQoIHJvd0RhdGEobWVyZ2VkX3NjZSkgKQpgYGAKCgojIyBJbnRlZ3JhdGlvbgoKIVtTaW5nbGUtY2VsbCByb2FkbWFwOiBJbnRlZ3JhdGVdKGRpYWdyYW1zL3JvYWRtYXBfbXVsdGlfaW50ZWdyYXRlLnBuZykKCgpTbyBmYXIsIHdlJ3ZlIGNyZWF0ZWQgYSBgbWVyZ2VkX3NjZWAgb2JqZWN0IHdoaWNoIGlzIChhbG1vc3QhKSByZWFkeSBmb3IgaW50ZWdyYXRpb24uCgpUaGUgaW50ZWdyYXRpb24gbWV0aG9kcyB3ZSdsbCBiZSB1c2luZyBoZXJlIGFjdHVhbGx5IHBlcmZvcm0gYmF0Y2ggY29ycmVjdGlvbiBvbiBhIHJlZHVjZWQgZGltZW5zaW9uIHJlcHJlc2VudGF0aW9uIG9mIHRoZSBub3JtYWxpemVkIGdlbmUgZXhwcmVzc2lvbiB2YWx1ZXMsIHdoaWNoIGlzIG1vcmUgZWZmaWNpZW50LgpgZmFzdE1OTmAgYW5kIGBoYXJtb255YCBzcGVjaWZpY2FsbHkgdXNlIFBDQSBmb3IgdGhpcywgYnV0IGJlIGF3YXJlIHRoYXQgZGlmZmVyZW50IGludGVncmF0aW9uIG1ldGhvZHMgbWF5IHVzZSBvdGhlciBraW5kcyBvZiByZWR1Y2VkIGRpbWVuc2lvbnMuCgpZb3UnbGwgbm90aWNlIHRoYXQgdGhlIG1lcmdlZCBTQ0Ugb2JqZWN0IG9iamVjdCBhbHJlYWR5IGNvbnRhaW5zIFBDQSBhbmQgVU1BUCByZWR1Y2VkIGRpbWVuc2lvbnMsIHdoaWNoIHdlcmUgY2FsY3VsYXRlZCBkdXJpbmcgb3VyIHByZS1wcm9jZXNzaW5nOgoKYGBge3IgbWVyZ2VkX3NjZSByZWRkaW0sIGxpdmUgPSBUUlVFfQojIFByaW50IHRoZSByZWR1Y2VkRGltTmFtZXMgb2YgdGhlIG1lcmdlZF9zY2UKcmVkdWNlZERpbU5hbWVzKG1lcmdlZF9zY2UpCmBgYAoKVGhlc2UgcmVwcmVzZW50IHRoZSBvcmlnaW5hbCBkaW1lbnNpb24gcmVkdWN0aW9ucyB0aGF0IHdlcmUgcGVyZm9ybWVkIG9uIF9lYWNoIGluZGl2aWR1YWwgU0NFXyBiZWZvcmUgbWVyZ2luZywgYnV0IHdlIGFjdHVhbGx5IG5lZWQgdG8gY2FsY3VsYXRlIFBDQSAoYW5kIFVNQVAgZm9yIHZpc3VhbGl6YXRpb24pIGZyb20gdGhlIG1lcmdlZCBvYmplY3QgZGlyZWN0bHkuCgpXaHkgY2FuJ3Qgd2UgdXNlIHRoZSBzYW1wbGUtc3BlY2lmaWMgUENBIGFuZCBVTUFQIG1hdHJpY2VzPwpQYXJ0IG9mIHRoZXNlIGNhbGN1bGF0aW9ucyB0aGVtc2VsdmVzIGludm9sdmVzIHNjYWxpbmcgdGhlIHJhdyBkYXRhIHRvIGNlbnRlciB0aGUgbWVhbi4KV2hlbiBzYW1wbGVzIGFyZSBzZXBhcmF0ZWx5IGNlbnRlcmVkIGJ1dCBwbG90dGluZyB0b2dldGhlciwgeW91IHdpbGwgc2VlIHNhbXBsZXMgIm92ZXJsYXBwaW5nIiBpbiBzcGFjZSwgYnV0IHRoaXMgcGxhY2VtZW50IGlzIGFjdHVhbGx5IGp1c3QgYW4gYXJ0aWZhY3Qgb2YgdGhlIGluZGl2aWR1YWwgY2VudGVyaW5nLgpJbiBhZGRpdGlvbiwgdGhlIG1hdGhlbWF0aWNhbCByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgb3JpZ2luYWwgZXhwcmVzc2lvbiBkYXRhIGFuZCByZWR1Y2VkIGRpbWVuc2lvbiB2ZXJzaW9uIG9mIHRoYXQgZGF0YSB3aWxsIGRpZmZlciBhY3Jvc3Mgc2FtcGxlcywgbWVhbmluZyB3ZSBjYW4ndCBpbnRlcnByZXQgdGhlbSBhbGwgdG9nZXRoZXIuClRvIHNlZSBob3cgdGhpcyBsb29rcywgbGV0J3MgbG9vayBhdCB0aGUgVU1BUCB3aGVuIGNhbGN1bGF0ZWQgZnJvbSBpbmRpdmlkdWFsIHNhbXBsZXM6CgpgYGB7ciBwbG90IGluZGl2aWR1YWwgVU1BUHMsIGxpdmUgPSBUUlVFfQojIFBsb3QgVU1BUCBjYWxjdWxhdGVkIGZyb20gaW5kaXZpZHVhbCBzYW1wbGVzIHdpdGggc2VwYXJhdGUgc2NhbGluZwpzY2F0ZXI6OnBsb3RSZWR1Y2VkRGltKG1lcmdlZF9zY2UsCiAgICAgICAgICAgICAgICAgICAgICAgZGltcmVkID0gIlVNQVAiLAogICAgICAgICAgICAgICAgICAgICAgIGNvbG9yX2J5ID0gInNhbXBsZSIsCiAgICAgICAgICAgICAgICAgICAgICAgcG9pbnRfc2l6ZSA9IDAuNSwKICAgICAgICAgICAgICAgICAgICAgICBwb2ludF9hbHBoYSA9IDAuMikgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIiwgbmFtZSA9ICJzYW1wbGUiKSArICMgVXNlIGEgQ1ZELWZyaWVuZGx5IGNvbG9yIHNjaGVtZSBhbmQgc3BlY2lmeSBsZWdlbmQgbmFtZQogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAzLCBhbHBoYSA9IDEpKSkgKyAjIE1vZGlmeSB0aGUgbGVnZW5kIGtleSB3aXRoIGxhcmdlciwgZWFzaWVyIHRvIHNlZSBwb2ludHMKICBnZ3RpdGxlKCJVTUFQIGNhbGN1bGF0ZWQgb24gZWFjaCBzYW1wbGUgc2VwYXJhdGVseSIpCgpgYGAKCgpBcyB3ZSBzZWUgaW4gdGhpcyBVTUFQLCBhbGwgc2FtcGxlcyBhcmUgY2VudGVyZWQgYXQgemVybyBhbmQgYWxsIG92ZXJsYXBwaW5nLgpUaGlzIHZpc3VhbCBhcnRpZmFjdCBjYW4gZ2l2ZSB0aGUgX2luY29ycmVjdCBpbXByZXNzaW9uXyB0aGF0IGRhdGEgaXMgaW50ZWdyYXRlZCAtIHRvIGJlIGNsZWFyLCB0aGlzIGRhdGEgaXMgTk9UIGludGVncmF0ZWQhCgpGb3IgaW5wdXQgdG8gaW50ZWdyYXRpb24sIHdlJ2xsIHdhbnQgdGhlIHJlZHVjZWQgZGltZW5zaW9uIGNhbGN1bGF0aW9ucyB0byBjb25zaWRlciBub3JtYWxpemVkIGdlbmUgZXhwcmVzc2lvbiB2YWx1ZXMgZnJvbSBhbGwgc2FtcGxlcyBzaW11bHRhbmVvdXNseS4KU28gd2UnbGwgbmVlZCB0byByZWNhbGN1bGF0ZSBQQ0EgKGFuZCBVTUFQIGZvciB2aXN1YWxpemF0aW9uKSBvbiB0aGUgbWVyZ2VkIG9iamVjdC4KV2UnbGwgYWxzbyBzYXZlIHRoZXNlIG5ldyByZWR1Y2VkIGRpbWVuc2lvbnMgd2l0aCBkaWZmZXJlbnQgbmFtZXMsIGBtZXJnZWRfUENBYCBhbmQgYG1lcmdlZF9VTUFQYCwgdG8gZGlzdGluZ3Vpc2ggdGhlbSBmcm9tIGFscmVhZHktcHJlc2VudCBgUENBYCBhbmQgYFVNQVBgLgoKRmlyc3QsIGFzIHVzdWFsLCB3ZSdsbCBkZXRlcm1pbmUgdGhlIGhpZ2gtdmFyaWFuY2UgZ2VuZXMgdG8gdXNlIGZvciBQQ0EgZnJvbSB0aGUgYG1lcmdlZF9zY2VgIG9iamVjdC4KRm9yIHRoaXMsIHdlJ2xsIG5lZWQgdG8gcHJvdmlkZSB0aGUgYXJndW1lbnQgYGJsb2NrID0gbWVyZ2VkX3NjZSRzYW1wbGVgIHdoZW4gbW9kZWxpbmcgZ2VuZSB2YXJpYW5jZSwgd2hpY2ggdGVsbHMgYHNjcmFuOjptb2RlbEdlbmVWYXIoKWAgdG8gZmlyc3QgbW9kZWwgdmFyaWFuY2Ugc2VwYXJhdGVseSBmb3IgZWFjaCBiYXRjaCBhbmQgdGhlbiBjb21iaW5lIHRob3NlIG1vZGVsaW5nIHN0YXRpc3RpY3MuCgpgYGB7ciBjYWxjIG1lcmdlZCBodiBnZW5lc30KIyBTcGVjaWZ5IHRoZSBudW1iZXIgb2YgZ2VuZXMgdG8gaWRlbnRpZnkKbnVtX2dlbmVzIDwtIDIwMDAKCiMgQ2FsY3VsYXRlIHZhcmlhdGlvbiBmb3IgZWFjaCBnZW5lCmdlbmVfdmFyaWFuY2UgPC0gc2NyYW46Om1vZGVsR2VuZVZhcihtZXJnZWRfc2NlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBzcGVjaWZ5IHRoZSBncm91cGluZyBjb2x1bW46CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBibG9jayA9IG1lcmdlZF9zY2Ukc2FtcGxlKQoKIyBHZXQgdGhlIHRvcCBgbnVtX2dlbmVzYCBoaWdoLXZhcmlhbmNlIGdlbmVzIHRvIHVzZSBmb3IgZGltZW5zaW9uIHJlZHVjdGlvbgpodl9nZW5lcyA8LSBzY3Jhbjo6Z2V0VG9wSFZHcyhnZW5lX3ZhcmlhbmNlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gbnVtX2dlbmVzKQpgYGAKClRvIGNhbGN1bGF0ZSB0aGUgUENBIG1hdHJpeCBpdHNlbGYsIHdlJ2xsIHVzZSBhbiBhcHByb2FjaCBmcm9tIHRoZSBgYmF0Y2hlbG9yYCBwYWNrYWdlLCB3aGljaCBpcyB0aGUgUiBwYWNrYWdlIHRoYXQgY29udGFpbnMgdGhlIGBmYXN0TU5OYCBtZXRob2QuClRoZSBbYGJhdGNoZWxvcjo6bXVsdGlCYXRjaFBDQSgpYF0oaHR0cHM6Ly9yZHJyLmlvL2Jpb2MvYmF0Y2hlbG9yL21hbi9tdWx0aUJhdGNoUENBLmh0bWwpIGZ1bmN0aW9uIGNhbGN1bGF0ZXMgYSBiYXRjaC13ZWlnaHRlZCBQQ0EgbWF0cml4LgpUaGlzIHdlaWdodGluZyBlbnN1cmVzIHRoYXQgYWxsIGJhdGNoZXMsIHdoaWNoIG1heSBoYXZlIHZlcnkgZGlmZmVyZW50IG51bWJlcnMgb2YgY2VsbHMsIGNvbnRyaWJ1dGUgZXF1YWxseSB0byB0aGUgb3ZlcmFsbCBzY2FsaW5nLgoKYGBge3IgbWFrZSBtZXJnZWRfcGNhLCBsaXZlID0gVFJVRX0KIyBVc2UgYmF0Y2hlbG9yIHRvIGNhbGN1bGF0ZSBQQ0EgZm9yIG1lcmdlZF9zY2UsIGNvbnNpZGVyaW5nIG9ubHkKIyAgdGhlIGhpZ2gtdmFyaWFuY2UgZ2VuZXMKIyBXZSdsbCBuZWVkIHRvIGluY2x1ZGUgdGhlIGFyZ3VtZW50IGBwcmVzZXJ2ZS5zaW5nbGUgPSBUUlVFYCB0byBnZXQKIyAgYSBzaW5nbGUgbWF0cml4IHdpdGggYWxsIHNhbXBsZXMgYW5kIG5vdCBzZXBhcmF0ZSBtYXRyaWNlcyBmb3IgZWFjaCBzYW1wbGUKbWVyZ2VkX3BjYSA8LSBiYXRjaGVsb3I6Om11bHRpQmF0Y2hQQ0EobWVyZ2VkX3NjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3Vic2V0LnJvdyA9IGh2X2dlbmVzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiYXRjaCA9IG1lcmdlZF9zY2Ukc2FtcGxlLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVzZXJ2ZS5zaW5nbGUgPSBUUlVFKQpgYGAKCkxldCdzIGhhdmUgYSBsb29rIGF0IHRoZSBvdXRwdXQ6CmBgYHtyIHByaW50IG1lcmdlZF9wY2EsIGxpdmUgPSBUUlVFfQojIFRoaXMgb3V0cHV0IGlzIG5vdCB2ZXJ5IGludGVyZXN0aW5nIQptZXJnZWRfcGNhCmBgYAoKV2UgY2FuIHVzZSBpbmRleGluZyBgW1sxXV1gIHRvIHNlZSB0aGUgUENBIG1hdHJpeCBjYWxjdWxhdGVkLCBsb29raW5nIGF0IGEgc21hbGwgc3Vic2V0IGZvciBjb252ZW5pZW5jZToKCmBgYHtyIHByaW50IG1lcmdlZF9wY2EgaW5kZXhlZCwgbGl2ZSA9IFRSVUV9Cm1lcmdlZF9wY2FbWzFdXVsxOjUsMTo1XQpgYGAKCldlIGNhbiBub3cgaW5jbHVkZSB0aGlzIFBDQSBtYXRyaXggaW4gb3VyIGBtZXJnZWRfc2NlYCBvYmplY3Q6CgpgYGB7ciBhZGQgbWVyZ2VkX3BjYSwgbGl2ZSA9IFRSVUV9CiMgYWRkIFBDQSByZXN1bHRzIHRvIG1lcmdlZCBTQ0Ugb2JqZWN0CnJlZHVjZWREaW0obWVyZ2VkX3NjZSwgIm1lcmdlZF9QQ0EiKSA8LSBtZXJnZWRfcGNhW1sxXV0KYGBgCgpOb3cgdGhhdCB3ZSBoYXZlIHRoZSBQQ0EgbWF0cml4LCB3ZSBjYW4gcHJvY2VlZCB0byBjYWxjdWxhdGUgVU1BUCB0byB2aXN1YWxpemUgdGhlIHVuY29ycmVjdGVkIG1lcmdlZCBkYXRhLgoKV2UnbGwgY2FsY3VsYXRlIFVNQVAgYXMgInVzdWFsIiwgYnV0IGluIHRoaXMgY2FzZSB3ZSdsbCBzcGVjaWZ5IHR3byBhZGRpdGlvbmFsIGFyZ3VtZW50czoKCi0gYGRpbXJlZCA9ICJtZXJnZWRfUENBImAsIHdoaWNoIHNwZWNpZmllcyB3aGljaCBleGlzdGluZyByZWR1Y2VkIGRpbWVuc2lvbiBzaG91bGQgYmUgdXNlZCBmb3IgdGhlIGNhbGN1bGF0aW9uLgpXZSB3YW50IHRvIHVzZSB0aGUgYmF0Y2gtd2VpZ2h0ZWQgUENBLCB3aGljaCB3ZSBuYW1lZCBhYm92ZSBhcyBgIm1lcmdlZF9QQ0EiYC4KLSBgbmFtZSA9ICJtZXJnZWRfVU1BUCJgLCB3aGljaCBuYW1lcyB0aGUgZmluYWwgVU1BUCB0aGF0IHRoaXMgZnVuY3Rpb24gY2FsY3VsYXRlcy4KVGhpcyBhcmd1bWVudCB3aWxsIHByZXZlbnQgdXMgZnJvbSBvdmVyd3JpdGluZyB0aGUgZXhpc3RpbmcgVU1BUCB3aGljaCBpcyBhbHJlYWR5IG5hbWVkICJVTUFQIiBhbmQgaW5zdGVhZCBjcmVhdGUgYSBzZXBhcmF0ZSBgIm1lcmdlZF9VTUFQImAuCgpgYGB7ciBjYWxjdWxhdGUgbWVyZ2VkIHVtYXAsIGxpdmUgPSBUUlVFfQojIGFkZCBtZXJnZWRfVU1BUCBmcm9tIG1lcmdlZF9QQ0EKbWVyZ2VkX3NjZSA8LSBzY2F0ZXI6OnJ1blVNQVAobWVyZ2VkX3NjZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGltcmVkID0gIm1lcmdlZF9QQ0EiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lID0gIm1lcmdlZF9VTUFQIikKYGBgCgpOb3csIGxldCdzIHNlZSBob3cgdGhpcyBuZXcgYG1lcmdlZF9VTUFQYCBsb29rcyBjb21wYXJlZCB0byB0aGUgYFVNQVBgIGNhbGN1bGF0ZWQgZnJvbSBpbmRpdmlkdWFsIHNhbXBsZXM6CgpgYGB7ciBwbG90IHVuY29ycmVjdGVkIG1lcmdlZCBVTUFQfQojIFVNQVBzIHNjYWxlZCB0b2dldGhlciB3aGVuIGNhbGN1bGF0ZWQgZnJvbSB0aGUgbWVyZ2VkIFNDRQpzY2F0ZXI6OnBsb3RSZWR1Y2VkRGltKG1lcmdlZF9zY2UsCiAgICAgICAgICAgICAgICAgICAgICAgZGltcmVkID0gIm1lcmdlZF9VTUFQIiwKICAgICAgICAgICAgICAgICAgICAgICBjb2xvcl9ieSA9ICJzYW1wbGUiLAogICAgICAgICAgICAgICAgICAgICAgICMgU29tZSBzdHlsaW5nIHRvIGhlbHAgdXMgc2VlIHRoZSBwb2ludHM6CiAgICAgICAgICAgICAgICAgICAgICAgcG9pbnRfc2l6ZSA9IDAuNSwKICAgICAgICAgICAgICAgICAgICAgICBwb2ludF9hbHBoYSA9IDAuMikgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIiwgbmFtZSA9ICJzYW1wbGUiKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDMsIGFscGhhID0gMSkpKSArCiAgZ2d0aXRsZSgiVU1BUCBjYWxjdWxhdGVkIG9uIG1lcmdlZF9zY2UiKQpgYGAKClNhbXBsZXMgYXJlIG5vdyBzZXBhcmF0ZWQsIHdoaWNoIG1vcmUgcmVhc29uYWJseSByZWZsZWN0cyB0aGF0IHRoaXMgZGF0YSBpcyBfbm90IHlldCBiYXRjaC1jb3JyZWN0ZWRfLgpXZSBjYW4gdGhpbmsgb2YgdGhpcyBVTUFQIGFzIG91ciAiYmVmb3JlIiBVTUFQLCBhbmQgd2UgY2FuIGNvbXBhcmUgdGhpcyB0byB0aGUgImFmdGVyIiBVTUFQIHdlIHNlZSBwb3N0LWludGVncmF0aW9uLgoKTGV0J3MgZGlzY3VzcyBhIGxpdHRsZSBmaXJzdDogV2hhdCB2aXN1YWwgZGlmZmVyZW5jZXMgZG8geW91IHRoaW5rIHRoZSBVTUFQIG9uIHRoZSBpbnRlZ3JhdGVkIHZlcnNpb24gb2YgZGF0YSB3aWxsIGhhdmU/CldoYXQgc2ltaWxhcml0aWVzIGRvIHlvdSB0aGluayB0aGUgaW50ZWdyYXRlZCBVTUFQIHdpbGwgaGF2ZSB0byB0aGlzIHBsb3Q/CgoKIyMjIEludGVncmF0aW9uIHdpdGggYGZhc3RNTk5gCgpGaW5hbGx5LCB3ZSdyZSByZWFkeSB0byBpbnRlZ3JhdGUhClRvIHN0YXJ0LCB3ZSdsbCB1c2UgdGhlIGBmYXN0TU5OYCBhcHByb2FjaCBmcm9tIHRoZSBCaW9jb25kdWN0b3IgW2BiYXRjaGVsb3JgIHBhY2thZ2VdKGh0dHA6Ly93d3cuYmlvY29uZHVjdG9yLm9yZy9wYWNrYWdlcy8zLjE2L2Jpb2MvaHRtbC9iYXRjaGVsb3IuaHRtbCkuCgpgZmFzdE1OTmAgdGFrZXMgYXMgaW5wdXQgdGhlIGBtZXJnZWRfc2NlYCBvYmplY3QgdG8gaW50ZWdyYXRlLCBhbmQgdGhlIGZpcnN0IHN0ZXAgaXQgcGVyZm9ybXMgaXMgYWN0dWFsbHkgdG8gcnVuIGBiYXRjaGVsb3I6Om11bHRpQmF0Y2hQQ0EoKWAgb24gdGhhdCBTQ0UuCkl0IHRoZW4gdXNlcyB0aGF0IGJhdGNoLXdlaWdodGVkIFBDQSBtYXRyaXggdG8gcGVyZm9ybSB0aGUgYWN0dWFsIGJhdGNoIGNvcnJlY3Rpb24uClRoZSBgYmF0Y2hgIGFyZ3VtZW50IGlzIHVzZWQgdG8gc3BlY2lmeSB0aGUgZGlmZmVyZW50IGdyb3VwaW5ncyB3aXRoaW4gdGhlIGBtZXJnZWRfc2NlYCAoaS5lLiB0aGUgb3JpZ2luYWwgc2FtcGxlIHRoYXQgZWFjaCBjZWxsIGJlbG9uZ3MgdG8pLCBhbmQgdGhlIGBzdWJzZXQucm93YCBhcmd1bWVudCBjYW4gb3B0aW9uYWxseSBiZSB1c2VkIHRvIHByb3ZpZGUgYSB2ZWN0b3Igb2YgaGlnaC12YXJpYW5jZSBnZW5lcyB0aGF0IHNob3VsZCBiZSBjb25zaWRlcmVkIGZvciB0aGlzIFBDQSBjYWxjdWxhdGlvbi4KYGZhc3RNTk5gIHdpbGwgcmV0dXJuIGFuIFNDRSBvYmplY3QgdGhhdCBjb250YWlucyBhIGJhdGNoLWNvcnJlY3RlZCBQQ0EuCkxldCdzIHJ1biBpdCBhbmQgc2F2ZSB0aGUgcmVzdWx0IHRvIGEgdmFyaWFibGUgY2FsbGVkIGBpbnRlZ3JhdGVkX3NjZWAuCgoKYGBge3IgcnVuIGZhc3Rtbm4sIGxpdmUgPSBUUlVFfQojIGludGVncmF0ZSB3aXRoIGZhc3RNTk4sIGFnYWluIHNwZWNpZnlpbmcgb25seSBvdXIgaGlnaC12YXJpYW5jZSBnZW5lcwppbnRlZ3JhdGVkX3NjZSA8LSBiYXRjaGVsb3I6OmZhc3RNTk4oCiAgbWVyZ2VkX3NjZSwKICBiYXRjaCA9IG1lcmdlZF9zY2Ukc2FtcGxlLAogIHN1YnNldC5yb3cgPSBodl9nZW5lcwopCmBgYAoKTGV0J3MgaGF2ZSBhIGxvb2sgYXQgdGhlIHJlc3VsdDoKCmBgYHtyIGZhc3Rtbm4gcmVzdWx0LCBsaXZlID0gVFJVRX0KIyBQcmludCB0aGUgaW50ZWdyYXRlZF9zY2Ugb2JqZWN0CmludGVncmF0ZWRfc2NlCmBgYAoKVGhlcmUgYXJlIGNvdXBsZSBwaWVjZXMgb2YgaW5mb3JtYXRpb24gaGVyZSBvZiBpbnRlcmVzdDoKCi0gVGhlIGBjb3JyZWN0ZWRgIHJlZHVjZWQgZGltZW5zaW9uIHJlcHJlc2VudHMgdGhlIGJhdGNoLWNvcnJlY3RlZCBQQ0EgdGhhdCBgZmFzdE1OTmAgY2FsY3VsYXRlZC4KLSBUaGUgYHJlY29uc3RydWN0ZWRgIGFzc2F5IHJlcHJlc2VudHMgdGhlIGJhdGNoLWNvcnJlY3RlZCBub3JtYWxpemVkIGV4cHJlc3Npb24gdmFsdWVzLCB3aGljaCBgZmFzdE1OTmAgImJhY2stY2FsY3VsYXRlZCIgZnJvbSB0aGUgYmF0Y2gtY29ycmVjdGVkIFBDQSAoYGNvcnJlY3RlZGApLgpHZW5lcmFsbHkgc3BlYWtpbmcsIHRoZXNlIGV4cHJlc3Npb24gdmFsdWVzIGFyZSBub3Qgc3RhbmQtYWxvbmUgdmFsdWVzIHRoYXQgeW91IHNob3VsZCB1c2UgZm9yIG90aGVyIGFwcGxpY2F0aW9ucyBsaWtlIGRpZmZlcmVudGlhbCBnZW5lIGV4cHJlc3Npb24sIGFzIGRlc2NyaWJlZCBpbiBbX09yY2hlc3RyYXRpbmcgU2luZ2xlIENlbGwgQW5hbHlzZXNfXShodHRwOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy8zLjE2L09TQ0EubXVsdGlzYW1wbGUvdXNpbmctY29ycmVjdGVkLXZhbHVlcy5odG1sKS4KSWYgdGhlIGBzdWJzZXQucm93YCBhcmd1bWVudCBpcyBwcm92aWRlZCAoYXMgaXQgd2FzIGhlcmUpLCBvbmx5IGdlbmVzIHByZXNlbnQgaW4gYHN1YnNldC5yb3dgIHdpbGwgYmUgaW5jbHVkZWQgaW4gdGhlc2UgcmVjb25zdHJ1Y3RlZCBleHByZXNzaW9uIHZhbHVlcywgYnV0IHRoaXMgc2V0dGluZyBjYW4gYmUgb3ZlcnJpZGRlbiBzbyB0aGF0IGFsbCBnZW5lcyBoYXZlIHJlY29uc3RydWN0ZWQgZXhwcmVzc2lvbiB3aXRoIHRoZSBhcmd1bWVudCBgY29ycmVjdC5hbGwgPSBUUlVFYC4KCldlJ3JlIG1vc3RseSBpbnRlcmVzdGVkIGluIHRoZSBQQ0EgdGhhdCBgZmFzdE1OTmAgY2FsY3VsYXRlZCwgc28gbGV0J3Mgc2F2ZSB0aGF0IGluZm9ybWF0aW9uICh3aXRoIGFuIGluZm9ybWF0aXZlIGFuZCB1bmlxdWUgbmFtZSkgaW50byBvdXIgYG1lcmdlZF9zY2VgIG9iamVjdDoKCmBgYHtyIGZhc3Rtbm4gcGNzLCBsaXZlID0gVFJVRX0KIyBNYWtlIGEgbmV3IHJlZHVjZWREaW0gbmFtZWQgZmFzdG1ubl9QQ0EgZnJvbSB0aGUgY29ycmVjdGVkIHJlZHVjZWREaW0gaW4gaW50ZWdyYXRlZF9zY2UKcmVkdWNlZERpbShtZXJnZWRfc2NlLCAiZmFzdG1ubl9QQ0EiKSA8LSByZWR1Y2VkRGltKGludGVncmF0ZWRfc2NlLCAiY29ycmVjdGVkIikKYGBgCgpGaW5hbGx5LCB3ZSdsbCBjYWxjdWxhdGUgVU1BUCBmcm9tIHRoZXNlIGNvcnJlY3RlZCBQQ0EgbWF0cml4IGZvciB2aXN1YWxpemF0aW9uLgoKYGBge3IgY2FsY3VsYXRlIGZhc3Rtbm4gdW1hcCwgbGl2ZSA9IFRSVUV9CiMgQ2FsY3VsYXRlIFVNQVAKbWVyZ2VkX3NjZSA8LSBzY2F0ZXI6OnJ1blVNQVAoCiAgbWVyZ2VkX3NjZSwKICBkaW1yZWQgPSAiZmFzdG1ubl9QQ0EiLAogIG5hbWUgPSAiZmFzdG1ubl9VTUFQIgopCmBgYAoKRmlyc3QsIGxldCdzIHBsb3QgdGhlIGludGVncmF0ZWQgVU1BUCBoaWdobGlnaHRpbmcgdGhlIGRpZmZlcmVudCBiYXRjaGVzLgpBIHdlbGwtaW50ZWdyYXRlZCBkYXRhc2V0IHdpbGwgc2hvdyBiYXRjaCBtaXhpbmcsIGJ1dCBhIHBvb3JseS1pbnRlZ3JhdGVkIGRhdGFzZXQgd2lsbCBzaG93IG1vcmUgc2VwYXJhdGlvbiBhbW9uZyBiYXRjaGVzLCBzaW1pbGFyIHRvIHRoZSB1bmNvcnJlY3RlZCBVTUFQLgpOb3RlIHRoYXQgdGhpcyBpcyBhIG1vcmUgcXVhbGl0YXRpdmUgd2F5IHRvIGFzc2VzcyB0aGUgc3VjY2VzcyBvZiBpbnRlZ3JhdGlvbiwgYnV0IHRoZXJlIGFyZSBmb3JtYWwgbWV0cmljcyBvbmUgY2FuIHVzZSB0byBhc3Nlc3MgYmF0Y2ggbWl4aW5nLCB3aGljaCB5b3UgY2FuIHJlYWQgbW9yZSBhYm91dCBpbiBbdGhpcyBjaGFwdGVyIG9mIE9TQ0FdKGh0dHA6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzLzMuMTYvT1NDQS5tdWx0aXNhbXBsZS9jb3JyZWN0aW9uLWRpYWdub3N0aWNzLmh0bWwpLgoKYGBge3IgcGxvdCBmYXN0bW5uIHVtYXAgYmF0Y2hlc30Kc2NhdGVyOjpwbG90UmVkdWNlZERpbShtZXJnZWRfc2NlLAogICAgICAgICAgICAgICAgICAgICAgICMgcGxvdCB0aGUgZmFzdE1OTiBjb29yZGluYXRlcwogICAgICAgICAgICAgICAgICAgICAgIGRpbXJlZCA9ICJmYXN0bW5uX1VNQVAiLAogICAgICAgICAgICAgICAgICAgICAgICMgY29sb3IgYnkgc2FtcGxlCiAgICAgICAgICAgICAgICAgICAgICAgY29sb3JfYnkgPSAic2FtcGxlIiwKICAgICAgICAgICAgICAgICAgICAgICAjIFNvbWUgc3R5bGluZyB0byBoZWxwIHVzIHNlZSB0aGUgcG9pbnRzOgogICAgICAgICAgICAgICAgICAgICAgIHBvaW50X3NpemUgPSAwLjUsCiAgICAgICAgICAgICAgICAgICAgICAgcG9pbnRfYWxwaGEgPSAwLjIpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIsIG5hbWUgPSAic2FtcGxlIikgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAzLCBhbHBoYSA9IDEpKSkgKwogIGdndGl0bGUoIlVNQVAgYWZ0ZXIgaW50ZWdyYXRpb24gd2l0aCBmYXN0TU5OIikKYGBgCgpUaGlzIGBmYXN0bW5uX1VNQVBgIGNlcnRhaW5seSBsb29rcyBkaWZmZXJlbnQgZnJvbSB0aGUgb25lIHdlIG1hZGUgZnJvbSBgbWVyZ2VkX1VNQVBgIQpXaGF0IGRpZmZlcmVudCB0cmVuZHMgZG8geW91IHNlZT8KRG8gYWxsIHNhbXBsZXMgbG9vayAiZXF1YWxseSB3ZWxsIiBpbnRlZ3JhdGVkLCBmcm9tIGEgZmlyc3QgbG9vaz8KCkltcG9ydGFudGx5LCBvbmUgcmVhc29uIHRoYXQgYmF0Y2hlcyBtYXkgc3RpbGwgYXBwZWFyIHNlcGFyYXRlZCBpbiB0aGUgY29ycmVjdGVkIFVNQVAgaXMgaWYgdGhleSBfc2hvdWxkXyBiZSBzZXBhcmF0ZWQgLSBmb3IgZXhhbXBsZSwgbWF5YmUgdHdvIGJhdGNoZXMgY29udGFpbiB2ZXJ5IGRpZmZlcmVudCBjZWxsIHR5cGVzLCBoYXZlIHZlcnkgZGlmZmVyZW50IGRpYWdub3Nlcywgb3IgbWF5IGJlIGZyb20gZGlmZmVyZW50IHBhdGllbnRzLgoKUmVjYWxsIGZyb20gZWFybGllciB0aGF0IHdlIGNvbnZlbmllbnRseSBoYXZlIGNlbGwgdHlwZSBhbm5vdGF0aW9ucyBpbiBvdXIgU0NFcywgc28gd2UgY2FuIGV4cGxvcmUgdGhvc2UgaGVyZSEKTGV0J3MgdGFrZSBhIHF1aWNrIGRldG91ciB0byBzZWUgd2hhdCBraW5kcyBvZiBjZWxsIHR5cGVzIGFyZSBpbiB0aGlzIGRhdGEgYnkgbWFraW5nIGEgYmFycGxvdCBvZiB0aGUgY2VsbCB0eXBlcyBhY3Jvc3Mgc2FtcGxlczoKCmBgYHtyIGV4cGxvcmUgY2VsbHR5cGVzfQojIENlbGwgdHlwZXMgYXJlIGluIHRoZSBgY2VsbHR5cGVfYnJvYWRgIGFuZCBgY2VsbHR5cGVfZmluZWAgY29sdW1ucwptZXJnZWRfc2NlX2RmIDwtIGFzLmRhdGEuZnJhbWUoY29sRGF0YShtZXJnZWRfc2NlKSkKCiMgVXNlIGdncGxvdDIgdG8gbWFrZSBhIGJhcnBsb3QgdGhlIGNlbGwgdHlwZXMgYWNyb3NzIHNhbXBsZXMKZ2dwbG90KG1lcmdlZF9zY2VfZGYsCiAgICAgICBhZXMoeCA9IHNhbXBsZSwKICAgICAgICAgICBmaWxsID0gY2VsbHR5cGVfYnJvYWQpKSArCiAgIyBCYXJwbG90IG9mIGNlbGx0eXBlIHByb3BvcnRpb25zCiAgZ2VvbV9iYXIocG9zaXRpb24gPSAiZmlsbCIpICsKICAjIFVzZSBhIENWRC1mcmllbmRseSBjb2xvciBzY2hlbWUKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIiwgbmEudmFsdWUgPSAiZ3JleTgwIikgKwogICMgbmljZXIgdGhlbWUKICB0aGVtZV9idygpCmBgYAoKV2Ugc2VlIHRoYXQgVHVtb3IgY2VsbCB0eXBlcyBhcmUgYnkgZmFyIHRoZSBtb3N0IHByZXZhbGVudCBhY3Jvc3MgYWxsIHNhbXBsZXMsIGFuZCBub3JtYWwgdGlzc3VlIGNlbGwgdHlwZXMgYXJlIG5vdCB2ZXJ5IGNvbW1vbi4KV2Ugc2VlIGFsc28gdGhhdCBgU0NQQ0wwMDA0ODFgIGhhcyBhIGxhcmdlciBgVHVtb3JfTXlvY3l0ZWAgcG9wdWxhdGlvbiwgd2hpbGUgYWxsIG90aGVyIHNhbXBsZXMgaGF2ZSBsYXJnZXIgYFR1bW9yX01lc29kZXJtYCBwb3B1bGF0aW9ucy4KVGhpcyBkaWZmZXJlbmNlIF9tYXlfIGV4cGxhaW4gd2h5IHdlIG9ic2VydmUgdGhhdCBgU0NQQ0wwMDA0ODFgIGlzIHNvbWV3aGF0IG1vcmUgc2VwYXJhdGVkIGZyb20gdGhlIG90aGVyIHNhbXBsZXMgaW4gdGhlIGBmYXN0TU5OYCBVTUFQLgoKTGV0J3MgcmUtcGxvdCB0aGlzIFVNQVAgdG8gaGlnaGxpZ2h0IGNlbGwgdHlwZXM6CgoKYGBge3IgcGxvdCBmYXN0bW5uIHVtYXAgY2VsbHR5cGVzfQpzY2F0ZXI6OnBsb3RSZWR1Y2VkRGltKG1lcmdlZF9zY2UsCiAgICAgICAgICAgICAgICAgICAgICAgZGltcmVkID0gImZhc3Rtbm5fVU1BUCIsCiAgICAgICAgICAgICAgICAgICAgICAgIyBjb2xvciBieSBicm9hZCBjZWxsdHlwZXMKICAgICAgICAgICAgICAgICAgICAgICBjb2xvcl9ieSA9ICJjZWxsdHlwZV9icm9hZCIsCiAgICAgICAgICAgICAgICAgICAgICAgcG9pbnRfc2l6ZSA9IDAuNSwKICAgICAgICAgICAgICAgICAgICAgICBwb2ludF9hbHBoYSA9IDAuMikgKwogICMgaW5jbHVkZSBhcmd1bWVudCB0byBzcGVjaWZ5IGNvbG9yIG9mIE5BIHZhbHVlcwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIiwgbmFtZSA9ICJCcm9hZCBjZWxsdHlwZSIsIG5hLnZhbHVlID0gImdyZXk4MCIpICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMywgYWxwaGEgPSAxKSkpICsKICBnZ3RpdGxlKCJVTUFQIGFmdGVyIGludGVncmF0aW9uIHdpdGggZmFzdE1OTiIpCmBgYAoKVGhpcyBVTUFQIHNob3dzIHRoYXQgdGhlIG5vcm1hbCB0aXNzdWUgY2VsbCB0eXBlcyAobW9zdGx5IHZhc2N1bGFyIGVuZG90aGVsaXVtLCBtdXNjbGUgY2VsbHMsIGFuZCBtb25vY3l0ZXMpIHRlbmQgdG8gY2x1c3RlciB0b2dldGhlciBhbmQgYXJlIGdlbmVyYWxseSBzZXBhcmF0ZWQgZnJvbSB0aGUgdHVtb3IgY2VsbCB0eXBlcywgd2hpY2ggaXMgYW4gZW5jb3VyYWdpbmcgcGF0dGVybiEKVHVtb3IgY2VsbCB0eXBlcyBmcm9tIGRpZmZlcmVudCBzYW1wbGVzIGFyZSBhbGwgYWxzbyBjbHVzdGVyaW5nIHRvZ2V0aGVyLCB3aGljaCBpcyBldmVuIG1vcmUgZW5jb3VyYWdpbmcgdGhhdCB3ZSBoYWQgc3VjY2Vzc2Z1bCBpbnRlZ3JhdGlvbi4KCkhvd2V2ZXIsIGl0J3MgYSBiaXQgY2hhbGxlbmdpbmcgdG8gc2VlIGFsbCB0aGUgcG9pbnRzIGdpdmVuIHRoZSBhbW91bnQgb2Ygb3ZlcmxhcCBpbiB0aGUgcGxvdC4KT25lIHdheSB3ZSBjYW4gc2VlIGFsbCB0aGUgcG9pbnRzIGEgYml0IGJldHRlciBpcyB0byBmYWNldCB0aGUgcGxvdCBieSBzYW1wbGUsIHVzaW5nIGBmYWNldF93cmFwKClgIGZyb20gdGhlIGBnZ3Bsb3QyYCBwYWNrYWdlICh3aGljaCB3ZSBjYW4gZG8gYmVjYXVzZSBgc2NhdGVyOjpwbG90UmVkdWNlZERpbSgpYCByZXR1cm5zIGEgYGdncGxvdDJgIG9iamVjdCk6CgpgYGB7ciBwbG90IGZhc3Rtbm4gdW1hcCBjZWxsdHlwZXMgZmFjZXRlZH0Kc2NhdGVyOjpwbG90UmVkdWNlZERpbShtZXJnZWRfc2NlLAogICAgICAgICAgICAgICAgICAgICAgIGRpbXJlZCA9ICJmYXN0bW5uX1VNQVAiLAogICAgICAgICAgICAgICAgICAgICAgIGNvbG9yX2J5ID0gImNlbGx0eXBlX2Jyb2FkIiwKICAgICAgICAgICAgICAgICAgICAgICBwb2ludF9zaXplID0gMC41LAogICAgICAgICAgICAgICAgICAgICAgIHBvaW50X2FscGhhID0gMC4yLAogICAgICAgICAgICAgICAgICAgICAgICMgQWxsb3cgZm9yIGZhY2V0aW5nIGJ5IGEgdmFyaWFibGUgdXNpbmcgYG90aGVyX2ZpZWxkc2A6CiAgICAgICAgICAgICAgICAgICAgICAgb3RoZXJfZmllbGRzID0gInNhbXBsZSIpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIsIG5hbWUgPSAiQnJvYWQgY2VsbHR5cGUiLCBuYS52YWx1ZSA9ICJncmV5ODAiKSArCiAgZ3VpZGVzKGNvbG9yID0gZ3VpZGVfbGVnZW5kKG92ZXJyaWRlLmFlcyA9IGxpc3Qoc2l6ZSA9IDMsIGFscGhhID0gMSkpKSArCiAgZ2d0aXRsZSgiVU1BUCBhZnRlciBpbnRlZ3JhdGlvbiB3aXRoIGZhc3RNTk4iKSArCiAgIyBGYWNldCBieSBzYW1wbGUKICBmYWNldF93cmFwKHZhcnMoc2FtcGxlKSkgKwogICMgVXNlIGEgdGhlbWUgd2l0aCBiYWNrZ3JvdW5kIGdyaWQgdG8gbW9yZSBlYXNpbHkgY29tcGFyZSBwYW5lbCBjb29yZGluYXRlcwogIHRoZW1lX2J3KCkKYGBgCgpXaGF0IHRyZW5kcyBkbyB5b3Ugb2JzZXJ2ZSBiZXR3ZWVuIHR1bW9yIGFuZCBoZWFsdGh5IHRpc3N1ZXMgYW1vbmcgdGhlc2UgaW50ZWdyYXRlZCBzYW1wbGVzPwoKCiMjIyBJbnRlZ3JhdGlvbiB3aXRoIGBoYXJtb255YAoKYGZhc3RNTk5gIGlzIG9ubHkgb25lIG9mIG1hbnkgYXBwcm9hY2hlcyB0byBwZXJmb3JtIGludGVncmF0aW9uLCBhbmQgZGlmZmVyZW50IG1ldGhvZHMgaGF2ZSBkaWZmZXJlbnQgY2FwYWJpbGl0aWVzIGFuZCBtYXkgZ2l2ZSBkaWZmZXJlbnQgcmVzdWx0cy4KRm9yIGV4YW1wbGUsIHNvbWUgbWV0aG9kcyBjYW4gYWNjb21tb2RhdGUgYWRkaXRpb25hbCBjb3ZhcmlhdGVzIChlLmcuLCB0ZWNobm9sb2d5LCBwYXRpZW50LCBkaWFnbm9zaXMsIGV0Yy4pIHRoYXQgY2FuIGluZmx1ZW5jZSBpbnRlZ3JhdGlvbi4KSW4gZmFjdCB0aGUgZGF0YSB3ZSBhcmUgdXNpbmcgaGFzIGEga25vd24gX3BhdGllbnRfIGNvdmFyaWF0ZTsgYFNDUENMMDAwNDc5YCBhbmQgYFNDUENMMDAwNDgwYCBhcmUgZnJvbSB0aGUgZmlyc3QgcGF0aWVudCwgYW5kIGBTQ1BDTDAwMDQ4MWAgYW5kIGBTQ1BDTDAwMDQ4MmAgYXJlIGZyb20gdGhlIHNlY29uZCBwYXRpZW50LgoKU28sIGxldCdzIHBlcmZvcm0gaW50ZWdyYXRpb24gd2l0aCBhIG1ldGhvZCB0aGF0IGNhbiB1c2UgdGhpcyBpbmZvcm1hdGlvbiAtIFtgaGFybW9ueWBdKGh0dHBzOi8vcG9ydGFscy5icm9hZGluc3RpdHV0ZS5vcmcvaGFybW9ueS8pIQoKVG8gYmVnaW4gc2V0dGluZyB1cCBmb3IgYGhhcm1vbnlgIGludGVncmF0aW9uLCB3ZSBuZWVkIHRvIGFkZCBleHBsaWNpdCBwYXRpZW50IGluZm9ybWF0aW9uIGludG8gb3VyIG1lcmdlZCBTQ0UuCldlJ2xsIGNyZWF0ZSBhIG5ldyBjb2x1bW4gYHBhdGllbnRgIHdob3NlIHZhbHVlIGlzIGVpdGhlciAiQSIgb3IgIkIiIGRlcGVuZGluZyBvbiB0aGUgZ2l2ZW4gc2FtcGxlIG5hbWUsIHVzaW5nIHRoZSBbYGRwbHlyOjpjYXNlX3doZW4oKWBdKGh0dHBzOi8vZHBseXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvY2FzZV93aGVuLmh0bWwpIGZ1bmN0aW9uLgpXZSBwcm92aWRlIHRoaXMgZnVuY3Rpb24gd2l0aCBhIHNldCBvZiBsb2dpY2FsIGV4cHJlc3Npb25zIGFuZCBlYWNoIGFzc2lnbmVkIHZhbHVlIGlzIGRlc2lnbmF0ZWQgYnkgYH5gLgpUaGUgZXhwcmVzc2lvbnMgYXJlIGV2YWx1YXRlZCBpbiBvcmRlciwgc3RvcHBpbmcgYXQgdGhlIF9maXJzdF8gb25lIHRoYXQgZXZhbHVhdGVzIGFzIGBUUlVFYCBhbmQgcmV0dXJuaW5nIHRoZSBhc3NvY2lhdGVkIHZhbHVlLgoKYGBge3IgYWRkIHBhdGllbnQgaW5mb30KIyBDcmVhdGUgcGF0aWVudCBjb2x1bW4gd2l0aCB2YWx1ZXMgIkEiIG9yICJCIiBmb3IgdGhlIHR3byBwYXRpZW50cwptZXJnZWRfc2NlJHBhdGllbnQgPC0gZHBseXI6OmNhc2Vfd2hlbigKICBtZXJnZWRfc2NlJHNhbXBsZSAlaW4lIGMoIlNDUENMMDAwNDc5IiwgIlNDUENMMDAwNDgwIikgfiAiQSIsCiAgbWVyZ2VkX3NjZSRzYW1wbGUgJWluJSBjKCJTQ1BDTDAwMDQ4MSIsICJTQ1BDTDAwMDQ4MiIpIH4gIkIiLAopCmBgYAoKClVubGlrZSBgZmFzdE1OTmAsIGBoYXJtb255YCBkb2VzIG5vdCBjYWxjdWxhdGUgY29ycmVjdGVkIGV4cHJlc3Npb24gdmFsdWVzIG5vciBkb2VzIGl0IHJldHVybiBhbiBTQ0Ugb2JqZWN0LgpMaWtlIGBmYXN0TU5OYCwgYGhhcm1vbnlgIHBlcmZvcm1zIGludGVncmF0aW9uIG9uIGEgbWVyZ2VkIFBDQSBtYXRyaXguCkhvd2V2ZXIsIHVubGlrZSBgZmFzdE1OTmAsIGBoYXJtb255YCBkb2VzIG5vdCAiYmFjay1jYWxjdWxhdGUiIGNvcnJlY3RlZCBleHByZXNzaW9uIGZyb20gdGhlIGNvcnJlY3RlZCBQQ0EgbWF0cml4IGFuZCBpdCBvbmx5IHJldHVybnMgdGhlIGNvcnJlY3RlZCBQQ0EgbWF0cml4IGl0c2VsZi4KRm9yIGlucHV0LCBgaGFybW9ueWAgbmVlZHMgYSBjb3VwbGUgcGllY2VzIG9mIGluZm9ybWF0aW9uOgoKLSBGaXJzdCwgYGhhcm1vbnlgIHRha2VzIGEgYmF0Y2gtd2VpZ2h0ZWQgUENBIG1hdHJpeCB0byBwZXJmb3JtIGludGVncmF0aW9uLgpXZSBhbHJlYWR5IGNhbGN1bGF0ZWQgYSBiYXRjaC13ZWlnaHRlZCBQQ0EgbWF0cml4IChvdXIgYG1lcmdlZF9QQ0FgIHJlZHVjZWQgZGltZW5zaW9uKSwgd2UnbGwgcHJvdmlkZSB0aGlzIGFzIHRoZSB0aGUgaW5wdXQuCi0gU2Vjb25kLCB3ZSBuZWVkIHRvIHRlbGwgYGhhcm1vbnlgIGFib3V0IHRoZSBjb3ZhcmlhdGVzIHRvIHVzZSAtIGBzYW1wbGVgIGFuZCBgcGF0aWVudGAuClRvIGRvIHRoaXMsIHdlIHByb3ZpZGUgdHdvIGFyZ3VtZW50czoKICAtIGBtZXRhX2RhdGFgLCBhIGRhdGEgZnJhbWUgdGhhdCBjb250YWlucyBjb3ZhcmlhdGVzIGFjcm9zcyBzYW1wbGVzLgogIFdlIGNhbiBzaW1wbHkgc3BlY2lmeSB0aGUgU0NFIGBjb2xEYXRhYCBoZXJlIHNpbmNlIGl0IGNvbnRhaW5zIGBzYW1wbGVgIGFuZCBgcGF0aWVudGAgY29sdW1ucy4KICAtIGB2YXJzX3VzZWAsIGEgdmVjdG9yIG9mIHdoaWNoIGNvbHVtbiBuYW1lcyBpbiBgbWV0YV9kYXRhYCBzaG91bGQgYWN0dWFsbHkgYmUgdXNlZCBhcyBjb3ZhcmlhdGVzLgogIE90aGVyIGNvbHVtbnMgaW4gYG1ldGFfZGF0YWAgd2hpY2ggYXJlIG5vdCBpbiBgdmFyc191c2VgIGFyZSBpZ25vcmVkLgoKTGV0J3MgZ28hCgpgYGB7ciBydW4gaGFybW9ueSwgbGl2ZSA9IFRSVUV9CiMgUnVuIGhhcm1vbnkgaW50ZWdyYXRpb24KaGFybW9ueV9wY2EgPC0gaGFybW9ueTo6UnVuSGFybW9ueSgKICBkYXRhX21hdCA9IHJlZHVjZWREaW0obWVyZ2VkX3NjZSwgIm1lcmdlZF9QQ0EiKSwKICBtZXRhX2RhdGEgPSBjb2xEYXRhKG1lcmdlZF9zY2UpLAogIHZhcnNfdXNlID0gYygic2FtcGxlIiwgInBhdGllbnQiKQopCmBgYAoKVGhlIHJlc3VsdCBpcyBhIFBDQSBtYXRyaXguCkxldCdzIHByaW50IGEgc3Vic2V0IG9mIHRoaXMgbWF0cml4IHRvIHNlZSBpdDoKCmBgYHtyIHByaW50IGhhcm1vbnkgcmVzdWx0LCBsaXZlID0gVFJVRX0KIyBQcmludCB0aGUgaGFybW9ueSByZXN1bHQKaGFybW9ueV9wY2FbMTo1LCAxOjVdCmBgYAoKQXMgd2UgZGlkIHdpdGggYGZhc3RNTk5gIHJlc3VsdHMsIGxldCdzIHN0b3JlIHRoaXMgUENBIG1hdHJpeCBkaXJlY3RseSBpbiBvdXIgYG1lcmdlZF9zY2VgIG9iamVjdCB3aXRoIGFuIGluZm9ybWF0aXZlIG5hbWUgdGhhdCB3b24ndCBvdmVyd3JpdGUgYW55IG9mIHRoZSBleGlzdGluZyBQQ0EgbWF0cmljZXMuCldlJ2xsIGFsc28gY2FsY3VsYXRlIFVNQVAgZnJvbSBpdC4KCmBgYHtyIHNhdmUgaGFybW9ueSwgbGl2ZSA9IFRSVUV9CiMgU3RvcmUgUENBIGFzIGBoYXJtb255X1BDQWAKcmVkdWNlZERpbShtZXJnZWRfc2NlLCAiaGFybW9ueV9QQ0EiKSA8LSBoYXJtb255X3BjYQoKIyBBcyBiZWZvcmUsIGNhbGN1bGF0ZSBVTUFQIG9uIHRoaXMgUENBIG1hdHJpeCB3aXRoIGFwcHJvcHJpYXRlIG5hbWVzCm1lcmdlZF9zY2UgPC0gc2NhdGVyOjpydW5VTUFQKG1lcmdlZF9zY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpbXJlZCA9ICJoYXJtb255X1BDQSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgICA9ICJoYXJtb255X1VNQVAiKQpgYGAKCgpMZXQncyBzZWUgaG93IHRoZSBgaGFybW9ueWAgVU1BUCwgY29sb3JlZCBieSBzYW1wbGUsIGxvb2tzIGNvbXBhcmVkIHRvIHRoZSBgZmFzdE1OTmAgVU1BUDoKCmBgYHtyIHBsb3QgaGFybW9ueSB1bWFwIGJhdGNoZXN9CnNjYXRlcjo6cGxvdFJlZHVjZWREaW0obWVyZ2VkX3NjZSwKICAgICAgICAgICAgICAgICAgICAgICBkaW1yZWQgPSAiaGFybW9ueV9VTUFQIiwKICAgICAgICAgICAgICAgICAgICAgICBjb2xvcl9ieSA9ICJzYW1wbGUiLAogICAgICAgICAgICAgICAgICAgICAgIHBvaW50X3NpemUgPSAwLjUsCiAgICAgICAgICAgICAgICAgICAgICAgcG9pbnRfYWxwaGEgPSAwLjIpICsKICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIsIG5hbWUgPSAic2FtcGxlIikgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KHNpemUgPSAzLCBhbHBoYSA9IDEpKSkgKwogIGdndGl0bGUoIlVNQVAgYWZ0ZXIgaW50ZWdyYXRpb24gd2l0aCBoYXJtb255IikKYGBgCgpIb3cgZG8geW91IHRoaW5rIHRoaXMgYGhhcm1vbnlgIFVNQVAgY29tcGFyZXMgdG8gdGhhdCBmcm9tIGBmYXN0TU5OYCBpbnRlZ3JhdGlvbj8KCkxldCdzIHNlZSBob3cgdGhpcyBVTUFQIGxvb2tzIGNvbG9yZWQgYnkgY2VsbCB0eXBlLCBhbmQgZmFjZXRlZCBmb3IgdmlzaWJpbGl0eToKCmBgYHtyIHBsb3QgaGFybW9ueSB1bWFwIGNlbGx0eXBlc30Kc2NhdGVyOjpwbG90UmVkdWNlZERpbShtZXJnZWRfc2NlLAogICAgICAgICAgICAgICAgICAgICAgIGRpbXJlZCA9ICJoYXJtb255X1VNQVAiLAogICAgICAgICAgICAgICAgICAgICAgIGNvbG9yX2J5ID0gImNlbGx0eXBlX2Jyb2FkIiwKICAgICAgICAgICAgICAgICAgICAgICBwb2ludF9zaXplID0gMC41LAogICAgICAgICAgICAgICAgICAgICAgIHBvaW50X2FscGhhID0gMC4yLAogICAgICAgICAgICAgICAgICAgICAgICMgU3BlY2lmeSB2YXJpYWJsZSBmb3IgZmFjZXRpbmcKICAgICAgICAgICAgICAgICAgICAgICBvdGhlcl9maWVsZHMgPSAic2FtcGxlIikgKwogIHNjYWxlX2NvbG9yX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIiwgbmFtZSA9ICJCcm9hZCBjZWxsdHlwZSIsIG5hLnZhbHVlID0gImdyZXk4MCIpICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdChzaXplID0gMykpKSArCiAgZ2d0aXRsZSgiVU1BUCBhZnRlciBpbnRlZ3JhdGlvbiB3aXRoIGhhcm1vbnkiKSArCiAgZmFjZXRfd3JhcCh2YXJzKHNhbXBsZSkpCmBgYAoKV2hhdCBkbyB5b3Ugbm93IG5vdGljZSBpbiB0aGlzIGZhY2V0ZWQgdmlldyB0aGF0IHdhc24ndCBjbGVhciBwcmV2aW91c2x5PwpBcmUgdGhlcmUgb3RoZXIgcGF0dGVybnMgeW91IHNlZSB0aGF0IGFyZSBzaW1pbGFyIG9yIGRpZmZlcmVudCBmcm9tIHRoZSBgZmFzdE1OTmAgVU1BUD8KSG93IGRvIHlvdSB0aGluayBgZmFzdE1OTmAgdnMuIGBoYXJtb255YCBwZXJmb3JtZWQgaW4gaW50ZWdyYXRpbmcgdGhlc2Ugc2FtcGxlcz8KCiMjIyBFeHBvcnQKCkZpbmFsbHksIHdlJ2xsIGV4cG9ydCB0aGUgZmluYWwgU0NFIG9iamVjdCB3aXRoIGJvdGggYGZhc3RNTk5gIGFuZCBgaGFybW9ueWAgaW50ZWdyYXRpb24gdG8gYSBmaWxlLgpTaW5jZSB0aGlzIG9iamVjdCBpcyB2ZXJ5IGxhcmdlIChvdmVyIDEgR0IhKSwgd2UnbGwgZXhwb3J0IGl0IHRvIGEgZmlsZSB3aXRoIHNvbWUgY29tcHJlc3Npb24sIHdoaWNoLCBpbiB0aGlzIGNhc2UsIHdpbGwgcmVkdWNlIHRoZSBmaW5hbCBzaXplIHRvIGEgc21hbGxlciB+MzYwIE1CLgpUaGlzIHdpbGwgdGFrZSBhIGNvdXBsZSBtaW51dGVzIHRvIHNhdmUgd2hpbGUgY29tcHJlc3Npb24gaXMgcGVyZm9ybWVkLgoKYGBge3Igc2F2ZSBpbnRlZ3JhdGlvbiwgbGl2ZSA9IFRSVUV9CiMgRXhwb3J0IHRvIFJEUyBmaWxlIHdpdGggImd6IiBjb21wcmVzc2lvbgpyZWFkcjo6d3JpdGVfcmRzKG1lcmdlZF9zY2UsCiAgICAgICAgICAgICAgICAgaW50ZWdyYXRlZF9zY2VfZmlsZSwKICAgICAgICAgICAgICAgICBjb21wcmVzcyA9ICJneiIpCmBgYAoKCiMjIFByaW50IHNlc3Npb24gaW5mbwoKQXMgYWx3YXlzLCB3ZSdsbCBwcmludCB0aGUgc2Vzc2lvbiBpbmZvIHRvIGJlIHRyYW5zcGFyZW50IGFib3V0IHdoYXQgcGFja2FnZXMsIGFuZCB3aGljaCB2ZXJzaW9ucywgd2VyZSB1c2VkIGR1cmluZyB0aGlzIFIgc2Vzc2lvbi4KCmBgYHtyIHNlc3Npb25pbmZvfQpzZXNzaW9uSW5mbygpCmBgYAo=