In this notebook we will visualize the effect of the choice of elastic tree hyperparameters for the elastic and embedded tree. In the second part we will be working with the dataset by Paul et al., detailing mouse hematopoiesis. We will be needing MERLoT, Monocle 2, as well as some customized versions of the MERLoT functions.

library(merlot)
library(monocle)
source("~/Documents/repos/merlot-scripts/scripts/niko_tree_funcs.R")

We will read the dataset and the annotation.

CellTypes= paste(find.package("merlot"), "/example/PaulCellsMapping.csv", sep="")
Types=read.table(file =CellTypes, header=F, stringsAsFactors = F, sep=",")
paul_colorcells=c()
selected_colors=c("cyan1", "cyan4", "darkcyan",  "blue", "darkblue", "blue4", "navy", "darkgoldenrod", "darkgoldenrod1", "darkgoldenrod1", "gold", "bisque", "palegreen", "darkolivegreen2", "darkolivegreen3", "darkolivegreen4", "darkolivegreen", "chartreuse4", "darkgreen")
for(i in 1:19)
{
  paul_colorcells[which(Types[,2]==i)]=selected_colors[i]
}

ExpressionData=paste(find.package("merlot"), "/example/Paul2015.txt", sep="")
raw <- read.table(ExpressionData, sep="\t", header=T, row.names=1, stringsAsFactors = T)
exprs <- t(as.matrix(raw))
data <- newCellDataSet(exprs, expressionFamily = negbinomial.size(), lowerDetectionLimit = 1)
Dataset=ReadDataset(ExpressionData)

We will compute the dimensionality reduction using Monocle 2 and plot the result.

data <- estimateSizeFactors(data)
data <- estimateDispersions(data) # fails

data <- detectGenes(data, min_expr=0.1)

disp_table <- dispersionTable(data)
ordering_genes <- subset(disp_table, mean_expression >= 0.5 & dispersion_empirical >= 2*dispersion_fit)$gene_id

data <- setOrderingFilter(data, ordering_genes)
data <- reduceDimension(data)
data <- orderCells(data, reverse=FALSE)

CellCoordinates <- t(reducedDimS(data))
plot(CellCoordinates, pch=16, col=paul_colorcells)

We see that Monocle captures the trajectory that leads from the early progenitors (yellow) towards the myeloblasts (blue) and the erythroid cells (green), just as the authors suggested in their analysis. We calculate the scaffold tree with and without local averaging, and plot the results.

ScaffoldTree=CalculateScaffoldTree(CellCoordinates = CellCoordinates, NEndpoints = 3,
                                   python_location = "~/miniconda3/envs/py36/bin/python")
reduced <- CalculateScaffoldTree(CellCoordinates = CellCoordinates, NEndpoints = 3,
                                 python_location = "~/miniconda3/envs/py36/bin/python", reduced=210)

par(mfrow=c(1,2))
plot_scaffold_tree(ScaffoldTree)
plot_scaffold_tree(reduced)

Note the massive difference in runtime: calculating the scaffold tree on the full dataset takes approximately 10 minutes, while it completes in only fractions of a second in reduced mode. The quality of the produced scaffold is very similar.

We will now calculate the elastic tree with different elasticity hyperparameters: the MERLoT defaults and the values that ElPiGraph.R proposes for the elastic_tree algorithm.

  1. MERLoT full (B) MERLoT reduced (C) ElPi full (D) ElPi reduced.

We see that when using the MERLoT hyperparameters the elastic tree follows the topology detected by the scaffold tree. ElPi hyperparameters when using all cells for the elastic tree also capture the overall topology, even though the progenitor branch (red) is truncated. When using the forgiving ElPi hyperparameters with reduced mode the branching location is removed to a high density region, and the progenitor branch is replaced by a bend in the tree.

Before we continue with the successful elastic trees we need to inflate the reduced ones with the full coordinates again:

elastic_reduced <- inflate_elastic_tree(elastic_reduced, CellCoordinates)

Now calculate the different embedded trees:

# Embedd the principal elastic tree on the gene expression space from which it was calculated.
elastic_full_merlot = GenesSpaceEmbedding(ExpressionMatrix = Dataset$ExpressionMatrix, ElasticTree = elastic_full)
elastic_reduced_merlot = GenesSpaceEmbedding(ExpressionMatrix = Dataset$ExpressionMatrix, ElasticTree = elastic_reduced)
elastic_full_elpi <- nikoEmbed(ExpressionMatrix = Dataset$ExpressionMatrix, ElasticTree = elastic_full)
elastic_reduced_elpi <- nikoEmbed(ExpressionMatrix = Dataset$ExpressionMatrix, ElasticTree = elastic_reduced)
elpi_full_elpi <- nikoEmbed(ExpressionMatrix = Dataset$ExpressionMatrix, ElasticTree = elpi_full)
elpi_full_merlot <- GenesSpaceEmbedding(ExpressionMatrix = Dataset$ExpressionMatrix, ElasticTree = elpi_full)

We calculate the corresponding pseudotimes:

elastic_full_merlot_pt <- CalculatePseudotimes(elastic_full_merlot, T0=3)
elastic_reduced_merlot_pt <- CalculatePseudotimes(elastic_reduced_merlot, T0=3)
elastic_full_elpi_pt <- CalculatePseudotimes(elastic_full_elpi, T0=3)
elastic_reduced_elpi_pt <- CalculatePseudotimes(elastic_reduced_elpi, T0=3)
elpi_full_elpi_pt <- CalculatePseudotimes(elpi_full_elpi, T0=3)
elpi_full_merlot_pt <- CalculatePseudotimes(elpi_full_merlot, T0=3)

With the pseudotimes available, we can now plot the expression of genes through pseudotime, allowing a comparison between the different approaches.

hpms. elastic reduced mode hpms. embedded
(A) MERLoT FALSE MERLoT
(B) MERLoT TRUE MERLoT
(C) MERLoT FALSE ElPiGraph.R
(D) MERLoT TRUE ElPiGraph.R
(E) ElPiGraph.R FALSE ElPiGraph.R
(F) ElPiGraph.R FALSE MERLoT

It is evident in a glance that the big difference in the quality of gene expression profiles in pseudotime lies in the choice of hyperparameters for the embedded tree; panels A, B, and F combine different scaffold and elastic trees, but all return very similar expression profiles. In contrast to that, panels C, D, and E all have wiggles in the expression profiles. This overfitting is particularly obvious for the Klf1 and Actb profiles.

LS0tCnRpdGxlOiAiVGhlIGh5cGVycGFyYW1ldGVyIGNob2ljZSAtIHB0LiAyIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpJbiB0aGlzIG5vdGVib29rIHdlIHdpbGwgdmlzdWFsaXplIHRoZSBlZmZlY3Qgb2YgdGhlIGNob2ljZSBvZiBlbGFzdGljIHRyZWUgaHlwZXJwYXJhbWV0ZXJzIGZvciB0aGUgZWxhc3RpYyBhbmQgZW1iZWRkZWQgdHJlZS4gSW4gdGhlIHNlY29uZCBwYXJ0IHdlIHdpbGwgYmUgd29ya2luZyB3aXRoIHRoZSBkYXRhc2V0IGJ5IFBhdWwgX2V0IGFsLl8sIGRldGFpbGluZyBtb3VzZSBoZW1hdG9wb2llc2lzLiBXZSB3aWxsIGJlIG5lZWRpbmcgTUVSTG9ULCBNb25vY2xlIDIsIGFzIHdlbGwgYXMgc29tZSBjdXN0b21pemVkIHZlcnNpb25zIG9mIHRoZSBNRVJMb1QgZnVuY3Rpb25zLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkobWVybG90KQpsaWJyYXJ5KG1vbm9jbGUpCnNvdXJjZSgifi9Eb2N1bWVudHMvcmVwb3MvbWVybG90LXNjcmlwdHMvc2NyaXB0cy9uaWtvX3RyZWVfZnVuY3MuUiIpCmBgYAoKV2Ugd2lsbCByZWFkIHRoZSBkYXRhc2V0IGFuZCB0aGUgYW5ub3RhdGlvbi4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpDZWxsVHlwZXM9IHBhc3RlKGZpbmQucGFja2FnZSgibWVybG90IiksICIvZXhhbXBsZS9QYXVsQ2VsbHNNYXBwaW5nLmNzdiIsIHNlcD0iIikKVHlwZXM9cmVhZC50YWJsZShmaWxlID1DZWxsVHlwZXMsIGhlYWRlcj1GLCBzdHJpbmdzQXNGYWN0b3JzID0gRiwgc2VwPSIsIikKcGF1bF9jb2xvcmNlbGxzPWMoKQpzZWxlY3RlZF9jb2xvcnM9YygiY3lhbjEiLCAiY3lhbjQiLCAiZGFya2N5YW4iLCAgImJsdWUiLCAiZGFya2JsdWUiLCAiYmx1ZTQiLCAibmF2eSIsICJkYXJrZ29sZGVucm9kIiwgImRhcmtnb2xkZW5yb2QxIiwgImRhcmtnb2xkZW5yb2QxIiwgImdvbGQiLCAiYmlzcXVlIiwgInBhbGVncmVlbiIsICJkYXJrb2xpdmVncmVlbjIiLCAiZGFya29saXZlZ3JlZW4zIiwgImRhcmtvbGl2ZWdyZWVuNCIsICJkYXJrb2xpdmVncmVlbiIsICJjaGFydHJldXNlNCIsICJkYXJrZ3JlZW4iKQpmb3IoaSBpbiAxOjE5KQp7CiAgcGF1bF9jb2xvcmNlbGxzW3doaWNoKFR5cGVzWywyXT09aSldPXNlbGVjdGVkX2NvbG9yc1tpXQp9CgpFeHByZXNzaW9uRGF0YT1wYXN0ZShmaW5kLnBhY2thZ2UoIm1lcmxvdCIpLCAiL2V4YW1wbGUvUGF1bDIwMTUudHh0Iiwgc2VwPSIiKQpyYXcgPC0gcmVhZC50YWJsZShFeHByZXNzaW9uRGF0YSwgc2VwPSJcdCIsIGhlYWRlcj1ULCByb3cubmFtZXM9MSwgc3RyaW5nc0FzRmFjdG9ycyA9IFQpCmV4cHJzIDwtIHQoYXMubWF0cml4KHJhdykpCmRhdGEgPC0gbmV3Q2VsbERhdGFTZXQoZXhwcnMsIGV4cHJlc3Npb25GYW1pbHkgPSBuZWdiaW5vbWlhbC5zaXplKCksIGxvd2VyRGV0ZWN0aW9uTGltaXQgPSAxKQpEYXRhc2V0PVJlYWREYXRhc2V0KEV4cHJlc3Npb25EYXRhKQpgYGAKV2Ugd2lsbCBjb21wdXRlIHRoZSBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gdXNpbmcgTW9ub2NsZSAyIGFuZCBwbG90IHRoZSByZXN1bHQuCgpgYGB7cn0KZGF0YSA8LSBlc3RpbWF0ZVNpemVGYWN0b3JzKGRhdGEpCmRhdGEgPC0gZXN0aW1hdGVEaXNwZXJzaW9ucyhkYXRhKSAjIGZhaWxzCgpkYXRhIDwtIGRldGVjdEdlbmVzKGRhdGEsIG1pbl9leHByPTAuMSkKCmRpc3BfdGFibGUgPC0gZGlzcGVyc2lvblRhYmxlKGRhdGEpCm9yZGVyaW5nX2dlbmVzIDwtIHN1YnNldChkaXNwX3RhYmxlLCBtZWFuX2V4cHJlc3Npb24gPj0gMC41ICYgZGlzcGVyc2lvbl9lbXBpcmljYWwgPj0gMipkaXNwZXJzaW9uX2ZpdCkkZ2VuZV9pZAoKZGF0YSA8LSBzZXRPcmRlcmluZ0ZpbHRlcihkYXRhLCBvcmRlcmluZ19nZW5lcykKZGF0YSA8LSByZWR1Y2VEaW1lbnNpb24oZGF0YSkKZGF0YSA8LSBvcmRlckNlbGxzKGRhdGEsIHJldmVyc2U9RkFMU0UpCgpDZWxsQ29vcmRpbmF0ZXMgPC0gdChyZWR1Y2VkRGltUyhkYXRhKSkKcGxvdChDZWxsQ29vcmRpbmF0ZXMsIHBjaD0xNiwgY29sPXBhdWxfY29sb3JjZWxscykKYGBgCgpXZSBzZWUgdGhhdCBNb25vY2xlIGNhcHR1cmVzIHRoZSB0cmFqZWN0b3J5IHRoYXQgbGVhZHMgZnJvbSB0aGUgZWFybHkgcHJvZ2VuaXRvcnMgKHllbGxvdykgdG93YXJkcyB0aGUgbXllbG9ibGFzdHMgKGJsdWUpIGFuZCB0aGUgZXJ5dGhyb2lkIGNlbGxzIChncmVlbiksIGp1c3QgYXMgdGhlIGF1dGhvcnMgc3VnZ2VzdGVkIGluIHRoZWlyIGFuYWx5c2lzLiBXZSBjYWxjdWxhdGUgdGhlIHNjYWZmb2xkIHRyZWUgd2l0aCBhbmQgd2l0aG91dCBsb2NhbCBhdmVyYWdpbmcsIGFuZCBwbG90IHRoZSByZXN1bHRzLgoKYGBge3IsIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD00fQpTY2FmZm9sZFRyZWU9Q2FsY3VsYXRlU2NhZmZvbGRUcmVlKENlbGxDb29yZGluYXRlcyA9IENlbGxDb29yZGluYXRlcywgTkVuZHBvaW50cyA9IDMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcHl0aG9uX2xvY2F0aW9uID0gIn4vbWluaWNvbmRhMy9lbnZzL3B5MzYvYmluL3B5dGhvbiIpCnJlZHVjZWQgPC0gQ2FsY3VsYXRlU2NhZmZvbGRUcmVlKENlbGxDb29yZGluYXRlcyA9IENlbGxDb29yZGluYXRlcywgTkVuZHBvaW50cyA9IDMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHB5dGhvbl9sb2NhdGlvbiA9ICJ+L21pbmljb25kYTMvZW52cy9weTM2L2Jpbi9weXRob24iLCByZWR1Y2VkPTIxMCkKCnBhcihtZnJvdz1jKDEsMikpCnBsb3Rfc2NhZmZvbGRfdHJlZShTY2FmZm9sZFRyZWUpCnBsb3Rfc2NhZmZvbGRfdHJlZShyZWR1Y2VkKQpgYGAKCk5vdGUgdGhlIG1hc3NpdmUgZGlmZmVyZW5jZSBpbiBydW50aW1lOiBjYWxjdWxhdGluZyB0aGUgc2NhZmZvbGQgdHJlZSBvbiB0aGUgZnVsbCBkYXRhc2V0IHRha2VzIGFwcHJveGltYXRlbHkgMTAgbWludXRlcywgd2hpbGUgaXQgY29tcGxldGVzIGluIG9ubHkgZnJhY3Rpb25zIG9mIGEgc2Vjb25kIGluIHJlZHVjZWQgbW9kZS4gVGhlIHF1YWxpdHkgb2YgdGhlIHByb2R1Y2VkIHNjYWZmb2xkIGlzIHZlcnkgc2ltaWxhci4KCldlIHdpbGwgbm93IGNhbGN1bGF0ZSB0aGUgZWxhc3RpYyB0cmVlIHdpdGggZGlmZmVyZW50IGVsYXN0aWNpdHkgaHlwZXJwYXJhbWV0ZXJzOiB0aGUgTUVSTG9UIGRlZmF1bHRzIGFuZCB0aGUgdmFsdWVzIHRoYXQgRWxQaUdyYXBoLlIgcHJvcG9zZXMgZm9yIHRoZSBgZWxhc3RpY190cmVlYCBhbGdvcml0aG0uCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9OH0KTnVtYmVyT2ZOb2Rlcz0xMDAKCmVsYXN0aWNfZnVsbCA8LSBDYWxjdWxhdGVFbGFzdGljVHJlZShTY2FmZm9sZFRyZWUgPSBTY2FmZm9sZFRyZWUsIE5feWsgPSBOdW1iZXJPZk5vZGVzKQplbGFzdGljX3JlZHVjZWQgPC0gQ2FsY3VsYXRlRWxhc3RpY1RyZWUoU2NhZmZvbGRUcmVlID0gcmVkdWNlZCwgTl95ayA9IE51bWJlck9mTm9kZXMpCmVscGlfZnVsbCA8LSBuaWtvRWxhc3RpYyhTY2FmZm9sZFRyZWUgPSBTY2FmZm9sZFRyZWUsIE5feWsgPSBOdW1iZXJPZk5vZGVzKQplbHBpX3JlZHVjZWQgPC0gbmlrb0VsYXN0aWMoU2NhZmZvbGRUcmVlID0gcmVkdWNlZCwgTl95ayA9IE51bWJlck9mTm9kZXMpCnBhcihtZnJvdz1jKDIsMikpCnBsb3RfZWxhc3RpY190cmVlKGVsYXN0aWNfZnVsbCkKdGV4dCgtNCwgLTIsIGxhYmVscyA9ICJBIiwgY2V4ID0gMykKcGxvdF9lbGFzdGljX3RyZWUoZWxhc3RpY19yZWR1Y2VkKQp0ZXh0KDQsIC0yLCBsYWJlbHMgPSAiQiIsIGNleCA9IDMpCnBsb3RfZWxhc3RpY190cmVlKGVscGlfZnVsbCkKdGV4dCgtNCwgLTIsIGxhYmVscyA9ICJDIiwgY2V4ID0gMykKcGxvdF9lbGFzdGljX3RyZWUoZWxwaV9yZWR1Y2VkKQp0ZXh0KDQsIC0yLCBsYWJlbHMgPSAiRCIsIGNleCA9IDMpCmBgYAoKKEEpIE1FUkxvVCBmdWxsIChCKSBNRVJMb1QgcmVkdWNlZCAoQykgRWxQaSBmdWxsIChEKSBFbFBpIHJlZHVjZWQuCgpXZSBzZWUgdGhhdCB3aGVuIHVzaW5nIHRoZSBNRVJMb1QgaHlwZXJwYXJhbWV0ZXJzIHRoZSBlbGFzdGljIHRyZWUgZm9sbG93cyB0aGUgdG9wb2xvZ3kgZGV0ZWN0ZWQgYnkgdGhlIHNjYWZmb2xkIHRyZWUuIEVsUGkgaHlwZXJwYXJhbWV0ZXJzIHdoZW4gdXNpbmcgYWxsIGNlbGxzIGZvciB0aGUgZWxhc3RpYyB0cmVlIGFsc28gY2FwdHVyZSB0aGUgb3ZlcmFsbCB0b3BvbG9neSwgZXZlbiB0aG91Z2ggdGhlIHByb2dlbml0b3IgYnJhbmNoIChyZWQpIGlzIHRydW5jYXRlZC4gV2hlbiB1c2luZyB0aGUgZm9yZ2l2aW5nIEVsUGkgaHlwZXJwYXJhbWV0ZXJzIHdpdGggcmVkdWNlZCBtb2RlIHRoZSBicmFuY2hpbmcgbG9jYXRpb24gaXMgcmVtb3ZlZCB0byBhIGhpZ2ggZGVuc2l0eSByZWdpb24sIGFuZCB0aGUgcHJvZ2VuaXRvciBicmFuY2ggaXMgcmVwbGFjZWQgYnkgYSBiZW5kIGluIHRoZSB0cmVlLgoKQmVmb3JlIHdlIGNvbnRpbnVlIHdpdGggdGhlIHN1Y2Nlc3NmdWwgZWxhc3RpYyB0cmVlcyB3ZSBuZWVkIHRvIGluZmxhdGUgdGhlIHJlZHVjZWQgb25lcyB3aXRoIHRoZSBmdWxsIGNvb3JkaW5hdGVzIGFnYWluOgoKYGBge3J9CmVsYXN0aWNfcmVkdWNlZCA8LSBpbmZsYXRlX2VsYXN0aWNfdHJlZShlbGFzdGljX3JlZHVjZWQsIENlbGxDb29yZGluYXRlcykKYGBgCgpOb3cgY2FsY3VsYXRlIHRoZSBkaWZmZXJlbnQgZW1iZWRkZWQgdHJlZXM6CgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KIyBFbWJlZGQgdGhlIHByaW5jaXBhbCBlbGFzdGljIHRyZWUgb24gdGhlIGdlbmUgZXhwcmVzc2lvbiBzcGFjZSBmcm9tIHdoaWNoIGl0IHdhcyBjYWxjdWxhdGVkLgplbGFzdGljX2Z1bGxfbWVybG90ID0gR2VuZXNTcGFjZUVtYmVkZGluZyhFeHByZXNzaW9uTWF0cml4ID0gRGF0YXNldCRFeHByZXNzaW9uTWF0cml4LCBFbGFzdGljVHJlZSA9IGVsYXN0aWNfZnVsbCkKZWxhc3RpY19yZWR1Y2VkX21lcmxvdCA9IEdlbmVzU3BhY2VFbWJlZGRpbmcoRXhwcmVzc2lvbk1hdHJpeCA9IERhdGFzZXQkRXhwcmVzc2lvbk1hdHJpeCwgRWxhc3RpY1RyZWUgPSBlbGFzdGljX3JlZHVjZWQpCmVsYXN0aWNfZnVsbF9lbHBpIDwtIG5pa29FbWJlZChFeHByZXNzaW9uTWF0cml4ID0gRGF0YXNldCRFeHByZXNzaW9uTWF0cml4LCBFbGFzdGljVHJlZSA9IGVsYXN0aWNfZnVsbCkKZWxhc3RpY19yZWR1Y2VkX2VscGkgPC0gbmlrb0VtYmVkKEV4cHJlc3Npb25NYXRyaXggPSBEYXRhc2V0JEV4cHJlc3Npb25NYXRyaXgsIEVsYXN0aWNUcmVlID0gZWxhc3RpY19yZWR1Y2VkKQplbHBpX2Z1bGxfZWxwaSA8LSBuaWtvRW1iZWQoRXhwcmVzc2lvbk1hdHJpeCA9IERhdGFzZXQkRXhwcmVzc2lvbk1hdHJpeCwgRWxhc3RpY1RyZWUgPSBlbHBpX2Z1bGwpCmVscGlfZnVsbF9tZXJsb3QgPC0gR2VuZXNTcGFjZUVtYmVkZGluZyhFeHByZXNzaW9uTWF0cml4ID0gRGF0YXNldCRFeHByZXNzaW9uTWF0cml4LCBFbGFzdGljVHJlZSA9IGVscGlfZnVsbCkKYGBgCgo8IS0tIFRoZXJlIGlzIG5vIGltbWVkaWF0ZSB3YXkgdG8gdmlzdWFsaXplIHRoZSBlbWJlZGRlZCB0cmVlLCBidXQgd2UgY2FuIHBsb3QgdGhlIHRyZWVzIHRoYXQgcmVzdWx0IGZyb20gdGhlIGVtYmVkZGluZyBpbiBhIHNjaGVtYXRpYyB3YXksIHVzaW5nIHRoZSBgcGxvdF9mbGF0dGVuZWRfdHJlZWAgZnVuY3Rpb24uIC0tPgoKPCEtLSBgYGB7ciwgZmlnLndpZHRoPTEwLCBmaWcuaGVpZ2h0PTR9IC0tPgo8IS0tIHBhcihtZnJvdz1jKDIsMykpIC0tPgo8IS0tIHBsb3RfZmxhdHRlbmVkX3RyZWUoZWxhc3RpY19mdWxsX21lcmxvdCwgY2VsbF9hbm5vdCA9IFR5cGVzJFYyLCBub2RlX3NpemUgPSAiY2VsbHMiKSAtLT4KPCEtLSBwbG90X2ZsYXR0ZW5lZF90cmVlKGVsYXN0aWNfcmVkdWNlZF9tZXJsb3QsIGNlbGxfYW5ub3QgPSBUeXBlcyRWMiwgbm9kZV9zaXplID0gImNlbGxzIikgLS0+CjwhLS0gcGxvdF9mbGF0dGVuZWRfdHJlZShlbGFzdGljX2Z1bGxfZWxwaSwgY2VsbF9hbm5vdCA9IFR5cGVzJFYyLCBub2RlX3NpemUgPSAiY2VsbHMiKSAtLT4KPCEtLSBwbG90X2ZsYXR0ZW5lZF90cmVlKGVsYXN0aWNfcmVkdWNlZF9lbHBpLCBjZWxsX2Fubm90ID0gVHlwZXMkVjIsIG5vZGVfc2l6ZSA9ICJjZWxscyIpIC0tPgo8IS0tIHBsb3RfZmxhdHRlbmVkX3RyZWUoZWxwaV9mdWxsX2VscGksIGNlbGxfYW5ub3QgPSBUeXBlcyRWMiwgbm9kZV9zaXplID0gImNlbGxzIikgLS0+CjwhLS0gcGxvdF9mbGF0dGVuZWRfdHJlZShlbHBpX2Z1bGxfbWVybG90LCBjZWxsX2Fubm90ID0gVHlwZXMkVjIsIG5vZGVfc2l6ZSA9ICJjZWxscyIpIC0tPgo8IS0tIHBhcihtZnJvdz1jKDEsMSkpIC0tPgo8IS0tIGBgYCAtLT4KCldlIGNhbGN1bGF0ZSB0aGUgY29ycmVzcG9uZGluZyBwc2V1ZG90aW1lczoKCmBgYHtyfQplbGFzdGljX2Z1bGxfbWVybG90X3B0IDwtIENhbGN1bGF0ZVBzZXVkb3RpbWVzKGVsYXN0aWNfZnVsbF9tZXJsb3QsIFQwPTMpCmVsYXN0aWNfcmVkdWNlZF9tZXJsb3RfcHQgPC0gQ2FsY3VsYXRlUHNldWRvdGltZXMoZWxhc3RpY19yZWR1Y2VkX21lcmxvdCwgVDA9MykKZWxhc3RpY19mdWxsX2VscGlfcHQgPC0gQ2FsY3VsYXRlUHNldWRvdGltZXMoZWxhc3RpY19mdWxsX2VscGksIFQwPTMpCmVsYXN0aWNfcmVkdWNlZF9lbHBpX3B0IDwtIENhbGN1bGF0ZVBzZXVkb3RpbWVzKGVsYXN0aWNfcmVkdWNlZF9lbHBpLCBUMD0zKQplbHBpX2Z1bGxfZWxwaV9wdCA8LSBDYWxjdWxhdGVQc2V1ZG90aW1lcyhlbHBpX2Z1bGxfZWxwaSwgVDA9MykKZWxwaV9mdWxsX21lcmxvdF9wdCA8LSBDYWxjdWxhdGVQc2V1ZG90aW1lcyhlbHBpX2Z1bGxfbWVybG90LCBUMD0zKQpgYGAKCldpdGggdGhlIHBzZXVkb3RpbWVzIGF2YWlsYWJsZSwgd2UgY2FuIG5vdyBwbG90IHRoZSBleHByZXNzaW9uIG9mIGdlbmVzIHRocm91Z2ggcHNldWRvdGltZSwgYWxsb3dpbmcgYSBjb21wYXJpc29uIGJldHdlZW4gdGhlIGRpZmZlcmVudCBhcHByb2FjaGVzLgoKYGBge3IsIGVjaG89RkFMU0UsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTE1fQpwYXIobWZjb2w9Yyg2LCAzKSkKZ2VuZSA8LSAiS2xmMSIKcGxvdF9wc2V1ZG90aW1lX2V4cHJlc3Npb25fZ2VuZShnZW5lLCBlbGFzdGljX2Z1bGxfbWVybG90LCBlbGFzdGljX2Z1bGxfbWVybG90X3B0LCBhZGRsZWdlbmQgPSBGLCBzZWxlY3RlZGNvbG9ycyA9IHBsb3RfY29scykKdGV4dCg0LCA4LjUsIGxhYmVscyA9ICJBIiwgY2V4ID0gMywgYWRqPWMoMC41LCAwLjUpKQpwbG90X3BzZXVkb3RpbWVfZXhwcmVzc2lvbl9nZW5lKGdlbmUsIGVsYXN0aWNfcmVkdWNlZF9tZXJsb3QsIGVsYXN0aWNfcmVkdWNlZF9tZXJsb3RfcHQsIGFkZGxlZ2VuZCA9IEYsIHNlbGVjdGVkY29sb3JzID0gcGxvdF9jb2xzKQp0ZXh0KDQsIDguNSwgbGFiZWxzID0gIkIiLCBjZXggPSAzLCBhZGo9YygwLjUsIDAuNSkpCnBsb3RfcHNldWRvdGltZV9leHByZXNzaW9uX2dlbmUoZ2VuZSwgZWxhc3RpY19mdWxsX2VscGksIGVsYXN0aWNfZnVsbF9lbHBpX3B0LCBhZGRsZWdlbmQgPSBGLCBzZWxlY3RlZGNvbG9ycyA9IHBsb3RfY29scykKdGV4dCg0LCA5LCBsYWJlbHMgPSAiQyIsIGNleCA9IDMsIGFkaj1jKDAuNSwgMC41KSkKcGxvdF9wc2V1ZG90aW1lX2V4cHJlc3Npb25fZ2VuZShnZW5lLCBlbGFzdGljX3JlZHVjZWRfZWxwaSwgZWxhc3RpY19yZWR1Y2VkX2VscGlfcHQsIGFkZGxlZ2VuZCA9IEYsIHNlbGVjdGVkY29sb3JzID0gcGxvdF9jb2xzKQp0ZXh0KDQsIDEwLCBsYWJlbHMgPSAiRCIsIGNleCA9IDMsIGFkaj1jKDAuNSwgMC41KSkKcGxvdF9wc2V1ZG90aW1lX2V4cHJlc3Npb25fZ2VuZShnZW5lLCBlbHBpX2Z1bGxfZWxwaSwgZWxwaV9mdWxsX2VscGlfcHQsIGFkZGxlZ2VuZCA9IEYsIHNlbGVjdGVkY29sb3JzID0gcGxvdF9jb2xzKQp0ZXh0KDQsIDksIGxhYmVscyA9ICJFIiwgY2V4ID0gMywgYWRqPWMoMC41LCAwLjUpKQpwbG90X3BzZXVkb3RpbWVfZXhwcmVzc2lvbl9nZW5lKGdlbmUsIGVscGlfZnVsbF9tZXJsb3QsIGVscGlfZnVsbF9tZXJsb3RfcHQsIGFkZGxlZ2VuZCA9IEYsIHNlbGVjdGVkY29sb3JzID0gcGxvdF9jb2xzKQp0ZXh0KDQsIDguNSwgbGFiZWxzID0gIkYiLCBjZXggPSAzLCBhZGo9YygwLjUsIDAuNSkpCmdlbmUgPC0gIk1wbyIKcGxvdF9wc2V1ZG90aW1lX2V4cHJlc3Npb25fZ2VuZShnZW5lLCBlbGFzdGljX2Z1bGxfbWVybG90LCBlbGFzdGljX2Z1bGxfbWVybG90X3B0LCBhZGRsZWdlbmQgPSBGLCBzZWxlY3RlZGNvbG9ycyA9IHBsb3RfY29scykKcGxvdF9wc2V1ZG90aW1lX2V4cHJlc3Npb25fZ2VuZShnZW5lLCBlbGFzdGljX3JlZHVjZWRfbWVybG90LCBlbGFzdGljX3JlZHVjZWRfbWVybG90X3B0LCBhZGRsZWdlbmQgPSBGLCBzZWxlY3RlZGNvbG9ycyA9IHBsb3RfY29scykKcGxvdF9wc2V1ZG90aW1lX2V4cHJlc3Npb25fZ2VuZShnZW5lLCBlbGFzdGljX2Z1bGxfZWxwaSwgZWxhc3RpY19mdWxsX2VscGlfcHQsIGFkZGxlZ2VuZCA9IEYsIHNlbGVjdGVkY29sb3JzID0gcGxvdF9jb2xzKQpwbG90X3BzZXVkb3RpbWVfZXhwcmVzc2lvbl9nZW5lKGdlbmUsIGVsYXN0aWNfcmVkdWNlZF9lbHBpLCBlbGFzdGljX3JlZHVjZWRfZWxwaV9wdCwgYWRkbGVnZW5kID0gRiwgc2VsZWN0ZWRjb2xvcnMgPSBwbG90X2NvbHMpCnBsb3RfcHNldWRvdGltZV9leHByZXNzaW9uX2dlbmUoZ2VuZSwgZWxwaV9mdWxsX2VscGksIGVscGlfZnVsbF9lbHBpX3B0LCBhZGRsZWdlbmQgPSBGLCBzZWxlY3RlZGNvbG9ycyA9IHBsb3RfY29scykKcGxvdF9wc2V1ZG90aW1lX2V4cHJlc3Npb25fZ2VuZShnZW5lLCBlbHBpX2Z1bGxfbWVybG90LCBlbHBpX2Z1bGxfbWVybG90X3B0LCBhZGRsZWdlbmQgPSBGLCBzZWxlY3RlZGNvbG9ycyA9IHBsb3RfY29scykKZ2VuZSA8LSAiQWN0YiIKcGxvdF9wc2V1ZG90aW1lX2V4cHJlc3Npb25fZ2VuZShnZW5lLCBlbGFzdGljX2Z1bGxfbWVybG90LCBlbGFzdGljX2Z1bGxfbWVybG90X3B0LCBhZGRsZWdlbmQgPSBGLCBzZWxlY3RlZGNvbG9ycyA9IHBsb3RfY29scykKcGxvdF9wc2V1ZG90aW1lX2V4cHJlc3Npb25fZ2VuZShnZW5lLCBlbGFzdGljX3JlZHVjZWRfbWVybG90LCBlbGFzdGljX3JlZHVjZWRfbWVybG90X3B0LCBhZGRsZWdlbmQgPSBGLCBzZWxlY3RlZGNvbG9ycyA9IHBsb3RfY29scykKcGxvdF9wc2V1ZG90aW1lX2V4cHJlc3Npb25fZ2VuZShnZW5lLCBlbGFzdGljX2Z1bGxfZWxwaSwgZWxhc3RpY19mdWxsX2VscGlfcHQsIGFkZGxlZ2VuZCA9IEYsIHNlbGVjdGVkY29sb3JzID0gcGxvdF9jb2xzKQpwbG90X3BzZXVkb3RpbWVfZXhwcmVzc2lvbl9nZW5lKGdlbmUsIGVsYXN0aWNfcmVkdWNlZF9lbHBpLCBlbGFzdGljX3JlZHVjZWRfZWxwaV9wdCwgYWRkbGVnZW5kID0gRiwgc2VsZWN0ZWRjb2xvcnMgPSBwbG90X2NvbHMpCnBsb3RfcHNldWRvdGltZV9leHByZXNzaW9uX2dlbmUoZ2VuZSwgZWxwaV9mdWxsX2VscGksIGVscGlfZnVsbF9lbHBpX3B0LCBhZGRsZWdlbmQgPSBGLCBzZWxlY3RlZGNvbG9ycyA9IHBsb3RfY29scykKcGxvdF9wc2V1ZG90aW1lX2V4cHJlc3Npb25fZ2VuZShnZW5lLCBlbHBpX2Z1bGxfbWVybG90LCBlbHBpX2Z1bGxfbWVybG90X3B0LCBhZGRsZWdlbmQgPSBGLCBzZWxlY3RlZGNvbG9ycyA9IHBsb3RfY29scykKcGFyKG1mcm93PWMoMSwxKSkKYGBgCgpgYGB7ciwgZWNobz1GQUxTRSwgcmVzdWx0cz0nYXNpcyd9CmxpYnJhcnkoa25pdHIpCmEgPC0gYygiTUVSTG9UIiwgRkFMU0UsICJNRVJMb1QiKQpiIDwtIGMoIk1FUkxvVCIsIFRSVUUsICJNRVJMb1QiKQpjIDwtIGMoIk1FUkxvVCIsIEZBTFNFLCAiRWxQaUdyYXBoLlIiKQpkIDwtIGMoIk1FUkxvVCIsIFRSVUUsICJFbFBpR3JhcGguUiIpCmUgPC0gYygiRWxQaUdyYXBoLlIiLCBGQUxTRSwgIkVsUGlHcmFwaC5SIikKZiA8LSBjKCJFbFBpR3JhcGguUiIsIEZBTFNFLCAiTUVSTG9UIikKdGVzdCA8LSBkYXRhLmZyYW1lKHJiaW5kKGEsIGIsIGMsIGQsIGUsIGYpKQpyb3duYW1lcyh0ZXN0KSA8LSBjKCIoQSkiLCAiKEIpIiwgIihDKSIsICIoRCkiLCAiKEUpIiwgIihGKSIpCmNvbG5hbWVzKHRlc3QpIDwtIGMoImhwbXMuIGVsYXN0aWMiLCAicmVkdWNlZCBtb2RlIiwgImhwbXMuIGVtYmVkZGVkIikKa25pdHI6OmthYmxlKHRlc3QpCmBgYAoKSXQgaXMgZXZpZGVudCBpbiBhIGdsYW5jZSB0aGF0IHRoZSBiaWcgZGlmZmVyZW5jZSBpbiB0aGUgcXVhbGl0eSBvZiBnZW5lIGV4cHJlc3Npb24gcHJvZmlsZXMgaW4gcHNldWRvdGltZSBsaWVzIGluIHRoZSBjaG9pY2Ugb2YgaHlwZXJwYXJhbWV0ZXJzIGZvciB0aGUgZW1iZWRkZWQgdHJlZTsgcGFuZWxzIEEsIEIsIGFuZCBGIGNvbWJpbmUgZGlmZmVyZW50IHNjYWZmb2xkIGFuZCBlbGFzdGljIHRyZWVzLCBidXQgYWxsIHJldHVybiB2ZXJ5IHNpbWlsYXIgZXhwcmVzc2lvbiBwcm9maWxlcy4gSW4gY29udHJhc3QgdG8gdGhhdCwgcGFuZWxzIEMsIEQsIGFuZCBFIGFsbCBoYXZlIHdpZ2dsZXMgaW4gdGhlIGV4cHJlc3Npb24gcHJvZmlsZXMuIFRoaXMgb3ZlcmZpdHRpbmcgaXMgcGFydGljdWxhcmx5IG9idmlvdXMgZm9yIHRoZSBgS2xmMWAgYW5kIGBBY3RiYCBwcm9maWxlcy4KCgoKCgoKCgoKCgoKCgoKCgoKCgoK