5. Exploring the data
vars <- c("CD34", "TIGIT", "NKG7", "GNLY", "IFNG", "PRF1", "GZMB", "IL4", "IL13", "IL2RA", "LAG3")
pl <- list()
pl <- list(DimPlot(obj, group.by = "seurat_clusters", label = T) + theme_void() + NoLegend())
for (i in vars) {
pl[[i]] <- FeaturePlot(obj, features = i, order = T) + theme_void() + NoLegend()
}
wrap_plots(pl)

NA
NA
6. compute the lineages on these dataset
# Define lineage ends
ENDS <- c("2", "6", "8")
set.seed(1)
lineages <- as.SlingshotDataSet(getLineages(
data = obj@reductions$umap@cell.embeddings,
clusterLabels = obj$seurat_clusters,
dist.method = "mnn", # It can be: "simple", "scaled.full", "scaled.diag", "slingshot" or "mnn"
end.clus = ENDS, # You can also define the ENDS!
start.clus = "3"
)) # define where to START the trajectories
# IF NEEDED, ONE CAN ALSO MANULALLY EDIT THE LINEAGES, FOR EXAMPLE:
# sel <- sapply( lineages@lineages, function(x){rev(x)[1]} ) %in% ENDS
# lineages@lineages <- lineages@lineages[ sel ]
# names(lineages@lineages) <- paste0("Lineage",1:length(lineages@lineages))
# lineages
# Change the reduction to our "fixed" UMAP2d (FOR VISUALISATION ONLY)
lineages@reducedDim <- obj@reductions$umap@cell.embeddings
{
plot(obj@reductions$umap@cell.embeddings, col = pal[obj$seurat_clusters], cex = .5, pch = 16)
lines(lineages, lwd = 1, col = "black", cex = 2)
text(centroids2d, labels = rownames(centroids2d), cex = 0.8, font = 2, col = "white")
}

Defining Principal Curves

compute the differentiation pseudotime
pseudotime <- slingPseudotime(curves, na = FALSE)
cellWeights <- slingCurveWeights(curves)
x <- rowMeans(pseudotime)
x <- x / max(x)
o <- order(x)
{
plot(obj@reductions$umap@cell.embeddings[o, ],
main = paste0("pseudotime"), pch = 16, cex = 0.4, axes = F, xlab = "", ylab = "",
col = colorRampPalette(c("grey70", "orange3", "firebrick", "purple4"))(99)[x[o] * 98 + 1]
)
points(centroids2d, cex = 2.5, pch = 16, col = "#FFFFFF99")
text(centroids2d, labels = levels(obj$seurat_clusters), cex = 1, font = 2)
}

7. Finding differentially expressed genes

plotGeneCount(curves, clusters = obj$seurat_clusters, models = sceGAM)
lineages
class: SlingshotDataSet
lineages: 4
Lineage1: 3 10 1 9 4 0 12 13 6
Lineage2: 3 10 1 9 4 0 7 11 8
Lineage3: 3 10 1 9 2
Lineage4: 3 5
curves: 0
lc <- sapply(lineages@lineages, function(x) {
rev(x)[1]
})
names(lc) <- gsub("Lineage", "L", names(lc))
lc.idx = match(lc, levels(obj$seurat_clusters))
{
plot(obj@reductions$umap@cell.embeddings, col = pal[obj$seurat_clusters], pch = 16)
lines(curves, lwd = 2, col = "black")
points(centroids2d[lc.idx, ], col = "black", pch = 16, cex = 4)
text(centroids2d[lc.idx, ], labels = names(lc), cex = 1, font = 2, col = "white")
}

NA
NA
Genes that change with pseudotime
set.seed(8)
res <- na.omit(associationTest(sceGAM, contrastType = "consecutive"))
res <- res[res$pvalue < 1e-3, ]
res <- res[res$waldStat > mean(res$waldStat), ]
res <- res[order(res$waldStat, decreasing = T), ]
res[1:10, ]
NA
We can plot their expression
par(mfrow = c(4, 4), mar = c(.1, .1, 2, 1))
{
plot(obj@reductions$umap@cell.embeddings, col = pal[obj$seurat_clusters], cex = .5, pch = 16, axes = F, xlab = "", ylab = "")
lines(curves, lwd = 2, col = "black")
points(centroids2d[lc.idx, ], col = "black", pch = 15, cex = 3, xpd = T)
text(centroids2d[lc.idx, ], labels = names(lc), cex = 1, font = 2, col = "white", xpd = T)
}
vars <- rownames(res[1:15, ])
vars <- na.omit(vars[vars != "NA"])
for (i in vars) {
x <- drop0(obj@assays$SCT@data)[i, ]
x <- (x - min(x)) / (max(x) - min(x))
o <- order(x)
plot(obj@reductions$umap@cell.embeddings[o, ],
main = paste0(i), pch = 16, cex = 0.5, axes = F, xlab = "", ylab = "",
col = colorRampPalette(c("lightgray", "grey60", "navy"))(99)[x[o] * 98 + 1]
)
}

Genes that change between two pseudotime points
res <- na.omit(startVsEndTest(sceGAM, pseudotimeValues = c(0, 1)))
res <- res[res$pvalue < 1e-3, ]
res <- res[res$waldStat > mean(res$waldStat), ]
res <- res[order(res$waldStat, decreasing = T), ]
res[1:10, 1:6]
identify which genes go up or down. Let’s check lineage 1:
# Get the top UP and Down regulated in lineage 1
res_lin1 <- sort(setNames(res$logFClineage1, rownames(res)))
vars <- names(c(rev(res_lin1)[1:7], res_lin1[1:8]))
vars <- na.omit(vars[vars != "NA"])
par(mfrow = c(4, 4), mar = c(.1, .1, 2, 1))
{
plot(obj@reductions$umap@cell.embeddings, col = pal[obj$seurat_clusters], cex = .5, pch = 16, axes = F, xlab = "", ylab = "")
lines(curves, lwd = 2, col = "black")
points(centroids2d[lc.idx, ], col = "black", pch = 15, cex = 3, xpd = T)
text(centroids2d[lc.idx, ], labels = names(lc), cex = 1, font = 2, col = "white", xpd = T)
}
for (i in vars) {
x <- drop0(obj@assays$SCT@data)[i, ]
x <- (x - min(x)) / (max(x) - min(x))
o <- order(x)
plot(obj@reductions$umap@cell.embeddings[o, ],
main = paste0(i), pch = 16, cex = 0.5, axes = F, xlab = "", ylab = "",
col = colorRampPalette(c("lightgray", "grey60", "navy"))(99)[x[o] * 98 + 1]
)
}

Genes that are different between lineages
res <- na.omit(diffEndTest(sceGAM))
res <- res[res$pvalue < 1e-3, ]
res <- res[res$waldStat > mean(res$waldStat), ]
res <- res[order(res$waldStat, decreasing = T), ]
res[1:10, ]
NA
pairwise comparison between each lineage. Let’s check lineage 1 vs
lineage 2:
# Get the top UP and Down regulated in lineage 1 vs 2
res_lin1_2 <- sort(setNames(res$logFC1_2, rownames(res)))
vars <- names(c(rev(res_lin1_2)[1:7], res_lin1_2[1:8]))
vars <- na.omit(vars[vars != "NA"])
par(mfrow = c(4, 4), mar = c(.1, .1, 2, 1))
{
plot(obj@reductions$umap@cell.embeddings, col = pal[obj$seurat_clusters], cex = .5, pch = 16, axes = F, xlab = "", ylab = "")
lines(curves, lwd = 2, col = "black")
points(centroids2d[lc.idx, ], col = "black", pch = 15, cex = 3, xpd = T)
text(centroids2d[lc.idx, ], labels = names(lc), cex = 1, font = 2, col = "white", xpd = T)
}
for (i in vars) {
x <- drop0(obj@assays$SCT@data)[i, ]
x <- (x - min(x)) / (max(x) - min(x))
o <- order(x)
plot(obj@reductions$umap@cell.embeddings[o, ],
main = paste0(i), pch = 16, cex = 0.5, axes = F, xlab = "", ylab = "",
col = colorRampPalette(c("lightgray", "grey60", "navy"))(99)[x[o] * 98 + 1]
)
}

LS0tCnRpdGxlOiAiVHJhamVjdG9yeSBpbmZlcmVuY2UgdXNpbmcgU2xpbmdzaG90IgphdXRob3I6IE5hc2lyIE1haG1vb2QgQWJiYXNpCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogICNybWRmb3JtYXRzOjpyZWFkdGhlZG93bgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdG9jX2NvbGxhcHNlZDogdHJ1ZQotLS0KCiMgMS4gbG9hZCBsaWJyYXJpZXMKYGBge3IsIGluY2x1ZGU9RkFMU0V9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyh7CiAgbGlicmFyeShTZXVyYXQpCiAgbGlicmFyeShwbG90bHkpCiAgb3B0aW9ucyhyZ2wucHJpbnRSZ2x3aWRnZXQgPSBUUlVFKQogIGxpYnJhcnkoTWF0cml4KQogIGxpYnJhcnkoc3BhcnNlTWF0cml4U3RhdHMpCiAgbGlicmFyeShzbGluZ3Nob3QpCiAgbGlicmFyeSh0cmFkZVNlcSkKICBsaWJyYXJ5KHBhdGNod29yaykKfSkKCgpgYGAKCiMgMi4gTmljZSBmdW5jdGlvbiB0byBlYXNpbHkgZHJhdyBhIGdyYXBoOgpgYGB7ciwgaW5jbHVkZT1GQUxTRX0KIyBBZGQgZ3JhcGggdG8gdGhlIGJhc2UgUiBncmFwaGljcyBwbG90CmRyYXdfZ3JhcGggPC0gZnVuY3Rpb24obGF5b3V0LCBncmFwaCwgbHdkID0gMC4yLCBjb2wgPSAiZ3JleSIpIHsKICByZXMgPC0gcmVwKHggPSAxOihsZW5ndGgoZ3JhcGhAcCkgLSAxKSwgdGltZXMgPSAoZ3JhcGhAcFstMV0gLSBncmFwaEBwWy1sZW5ndGgoZ3JhcGhAcCldKSkKICBzZWdtZW50cygKICAgIHgwID0gbGF5b3V0W2dyYXBoQGkgKyAxLCAxXSwgeDEgPSBsYXlvdXRbcmVzLCAxXSwKICAgIHkwID0gbGF5b3V0W2dyYXBoQGkgKyAxLCAyXSwgeTEgPSBsYXlvdXRbcmVzLCAyXSwgbHdkID0gbHdkLCBjb2wgPSBjb2wKICApCn0KYGBgCgojIDMuIFJlYWRpbmcgZGF0YQpgYGB7cn0KbG9hZCgiLi4vMjAyNV9OZXdIYXJtb255X0ludGVncmF0ZWRfRmlsZXMvMC1pbXBfUm9iai8wLXJvYmovNS1IYXJtb255X0ludGVncmF0ZWRfQWxsX3NhbXBsZXNfTWVyZ2VkX0NENFRjZWxsc19maW5hbF9SZXNvbHV0aW9uX1NlbGVjdGVkXzAuOF9BRFRfTm9ybWFsaXplZF9jbGVhbmVkX210LnJvYmoiKQoKIyBEZWZpbmUgc29tZSBjb2xvciBwYWxldHRlCnBhbCA8LSBjKHNjYWxlczo6aHVlX3BhbCgpKDgpLCBSQ29sb3JCcmV3ZXI6OmJyZXdlci5wYWwoOSwgIlNldDEiKSwgUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDgsICJTZXQyIikpCnNldC5zZWVkKDEpCnBhbCA8LSByZXAoc2FtcGxlKHBhbCwgbGVuZ3RoKHBhbCkpLCAyMDApCgpgYGAKCgojIDQuIFRyYWplY3RvcnkgaW5mZXJlbmNlIHVzaW5nIFNsaW5nc2hvdApgYGB7ciwgZWNobz1GQUxTRX0KIyBvYmogPC1BbGxfc2FtcGxlc19NZXJnZWQKIyAKIyBybShBbGxfc2FtcGxlc19NZXJnZWQpCiMgCiMgZ2MoKQoKIyBDYWxjdWxhdGUgY2x1c3RlciBjZW50cm9pZHMgKGZvciBwbG90dGluZyB0aGUgbGFiZWxzIGxhdGVyKQptbSA8LSBzcGFyc2UubW9kZWwubWF0cml4KH4gMCArIGZhY3RvcihvYmokc2V1cmF0X2NsdXN0ZXJzKSkKY29sbmFtZXMobW0pIDwtIGxldmVscyhmYWN0b3Iob2JqJHNldXJhdF9jbHVzdGVycykpCmNlbnRyb2lkczJkIDwtIGFzLm1hdHJpeCh0KHQob2JqQHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3MpICUqJSBtbSkgLyBNYXRyaXg6OmNvbFN1bXMobW0pKQoKYGBgCgoKIyMgTGV04oCZcyB2aXN1YWxpemUgd2hpY2ggY2x1c3RlcnMgd2UgaGF2ZSBpbiBvdXIgZGF0YXNldDoKYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlnLndpZHRoPTEwfQoKdmFycyA8LSBjKCJQYXRpZW50X29yaWdpbiIsICJjZWxsX2xpbmUiLCAic2V1cmF0X2NsdXN0ZXJzIiwgIlBoYXNlIikKcGwgPC0gbGlzdCgpCgpmb3IgKGkgaW4gdmFycykgewogIHBsW1tpXV0gPC0gRGltUGxvdChvYmosIGdyb3VwLmJ5ID0gaSwgbGFiZWwgPSBUKSArIHRoZW1lX3ZvaWQoKSArIE5vTGVnZW5kKCkKfQp3cmFwX3Bsb3RzKHBsKQoKdGFibGUob2JqJHNldXJhdF9jbHVzdGVycykKYGBgCgojIDUuIEV4cGxvcmluZyB0aGUgZGF0YQpgYGB7cn0KCnZhcnMgPC0gYygiQ0QzNCIsICJUSUdJVCIsICJOS0c3IiwgIkdOTFkiLCAiSUZORyIsICJQUkYxIiwgIkdaTUIiLCAiSUw0IiwgIklMMTMiLCAiSUwyUkEiLCAiTEFHMyIpCnBsIDwtIGxpc3QoKQoKcGwgPC0gbGlzdChEaW1QbG90KG9iaiwgZ3JvdXAuYnkgPSAic2V1cmF0X2NsdXN0ZXJzIiwgbGFiZWwgPSBUKSArIHRoZW1lX3ZvaWQoKSArIE5vTGVnZW5kKCkpCmZvciAoaSBpbiB2YXJzKSB7CiAgcGxbW2ldXSA8LSBGZWF0dXJlUGxvdChvYmosIGZlYXR1cmVzID0gaSwgb3JkZXIgPSBUKSArIHRoZW1lX3ZvaWQoKSArIE5vTGVnZW5kKCkKfQp3cmFwX3Bsb3RzKHBsKQoKCmBgYAojIDYuIGNvbXB1dGUgdGhlIGxpbmVhZ2VzIG9uIHRoZXNlIGRhdGFzZXQKYGBge3J9CiMgRGVmaW5lIGxpbmVhZ2UgZW5kcwpFTkRTIDwtIGMoIjIiLCAiNiIsICI4IikKCnNldC5zZWVkKDEpCmxpbmVhZ2VzIDwtIGFzLlNsaW5nc2hvdERhdGFTZXQoZ2V0TGluZWFnZXMoCiAgZGF0YSAgICAgICAgICAgPSBvYmpAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5ncywKICBjbHVzdGVyTGFiZWxzICA9IG9iaiRzZXVyYXRfY2x1c3RlcnMsCiAgZGlzdC5tZXRob2QgICAgPSAibW5uIiwgIyBJdCBjYW4gYmU6ICJzaW1wbGUiLCAic2NhbGVkLmZ1bGwiLCAic2NhbGVkLmRpYWciLCAic2xpbmdzaG90IiBvciAibW5uIgogIGVuZC5jbHVzICAgICAgID0gRU5EUywgIyBZb3UgY2FuIGFsc28gZGVmaW5lIHRoZSBFTkRTIQogIHN0YXJ0LmNsdXMgICAgID0gIjMiCikpICMgZGVmaW5lIHdoZXJlIHRvIFNUQVJUIHRoZSB0cmFqZWN0b3JpZXMKCgojIElGIE5FRURFRCwgT05FIENBTiBBTFNPIE1BTlVMQUxMWSBFRElUIFRIRSBMSU5FQUdFUywgRk9SIEVYQU1QTEU6CiMgc2VsIDwtIHNhcHBseSggbGluZWFnZXNAbGluZWFnZXMsIGZ1bmN0aW9uKHgpe3Jldih4KVsxXX0gKSAlaW4lIEVORFMKIyBsaW5lYWdlc0BsaW5lYWdlcyA8LSBsaW5lYWdlc0BsaW5lYWdlc1sgc2VsIF0KIyBuYW1lcyhsaW5lYWdlc0BsaW5lYWdlcykgPC0gcGFzdGUwKCJMaW5lYWdlIiwxOmxlbmd0aChsaW5lYWdlc0BsaW5lYWdlcykpCiMgbGluZWFnZXMKCgojIENoYW5nZSB0aGUgcmVkdWN0aW9uIHRvIG91ciAiZml4ZWQiIFVNQVAyZCAoRk9SIFZJU1VBTElTQVRJT04gT05MWSkKbGluZWFnZXNAcmVkdWNlZERpbSA8LSBvYmpAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5ncwoKewogIHBsb3Qob2JqQHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3MsIGNvbCA9IHBhbFtvYmokc2V1cmF0X2NsdXN0ZXJzXSwgY2V4ID0gLjUsIHBjaCA9IDE2KQogIGxpbmVzKGxpbmVhZ2VzLCBsd2QgPSAxLCBjb2wgPSAiYmxhY2siLCBjZXggPSAyKQogIHRleHQoY2VudHJvaWRzMmQsIGxhYmVscyA9IHJvd25hbWVzKGNlbnRyb2lkczJkKSwgY2V4ID0gMC44LCBmb250ID0gMiwgY29sID0gIndoaXRlIikKfQoKYGBgCgojIyAgRGVmaW5pbmcgUHJpbmNpcGFsIEN1cnZlcwpgYGB7cn0KCiMgRGVmaW5lIGN1cnZlcwpjdXJ2ZXMgPC0gYXMuU2xpbmdzaG90RGF0YVNldChnZXRDdXJ2ZXMoCiAgZGF0YSAgICAgICAgICA9IGxpbmVhZ2VzLAogIHRocmVzaCAgICAgICAgPSAxZS0xLAogIHN0cmV0Y2ggICAgICAgPSAxZS0xLAogIGFsbG93LmJyZWFrcyAgPSBGLAogIGFwcHJveF9wb2ludHMgPSAxMDAKKSkKCgojIFBsb3RzCnsKICBwbG90KG9iakByZWR1Y3Rpb25zJHVtYXBAY2VsbC5lbWJlZGRpbmdzLCBjb2wgPSBwYWxbb2JqJHNldXJhdF9jbHVzdGVyc10sIHBjaCA9IDE2KQogIGxpbmVzKGN1cnZlcywgbHdkID0gMiwgY29sID0gImJsYWNrIikKICB0ZXh0KGNlbnRyb2lkczJkLCBsYWJlbHMgPSBsZXZlbHMob2JqJHNldXJhdF9jbHVzdGVycyksIGNleCA9IDEsIGZvbnQgPSAyKQp9CmBgYAoKIyMgIGNvbXB1dGUgdGhlIGRpZmZlcmVudGlhdGlvbiBwc2V1ZG90aW1lCmBgYHtyfQoKcHNldWRvdGltZSA8LSBzbGluZ1BzZXVkb3RpbWUoY3VydmVzLCBuYSA9IEZBTFNFKQpjZWxsV2VpZ2h0cyA8LSBzbGluZ0N1cnZlV2VpZ2h0cyhjdXJ2ZXMpCgp4IDwtIHJvd01lYW5zKHBzZXVkb3RpbWUpCnggPC0geCAvIG1heCh4KQpvIDwtIG9yZGVyKHgpCgp7CiAgcGxvdChvYmpAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5nc1tvLCBdLAogICAgbWFpbiA9IHBhc3RlMCgicHNldWRvdGltZSIpLCBwY2ggPSAxNiwgY2V4ID0gMC40LCBheGVzID0gRiwgeGxhYiA9ICIiLCB5bGFiID0gIiIsCiAgICBjb2wgPSBjb2xvclJhbXBQYWxldHRlKGMoImdyZXk3MCIsICJvcmFuZ2UzIiwgImZpcmVicmljayIsICJwdXJwbGU0IikpKDk5KVt4W29dICogOTggKyAxXQogICkKICBwb2ludHMoY2VudHJvaWRzMmQsIGNleCA9IDIuNSwgcGNoID0gMTYsIGNvbCA9ICIjRkZGRkZGOTkiKQogIHRleHQoY2VudHJvaWRzMmQsIGxhYmVscyA9IGxldmVscyhvYmokc2V1cmF0X2NsdXN0ZXJzKSwgY2V4ID0gMSwgZm9udCA9IDIpCn0KYGBgCiMgNy4gRmluZGluZyBkaWZmZXJlbnRpYWxseSBleHByZXNzZWQgZ2VuZXMKYGBge3J9CgpzZWxfY2VsbHMgPC0gc3BsaXQoY29sbmFtZXMob2JqQGFzc2F5cyRTQ1RAZGF0YSksIG9iaiRzZXVyYXRfY2x1c3RlcnMpCnNlbF9jZWxscyA8LSB1bmxpc3QobGFwcGx5KHNlbF9jZWxscywgZnVuY3Rpb24oeCkgewogIHNldC5zZWVkKDEpCiAgcmV0dXJuKHNhbXBsZSh4LCAyMCkpCn0pKQoKZ3YgPC0gYXMuZGF0YS5mcmFtZShuYS5vbWl0KHNjcmFuOjptb2RlbEdlbmVWYXIob2JqQGFzc2F5cyRTQ1RAZGF0YVssIHNlbF9jZWxsc10pKSkKZ3YgPC0gZ3Zbb3JkZXIoZ3YkYmlvLCBkZWNyZWFzaW5nID0gVCksIF0Kc2VsX2dlbmVzIDwtIHNvcnQocm93bmFtZXMoZ3YpWzE6NTAwXSkKCnBhdGhfZmlsZSA8LSAiL2hvbWUvYmlvaW5mby9kYXRhL3RyYWplY3Rvcnkvc2V1cmF0X3NjZWdhbS5yZHMiCgojIGZldGNoX2RhdGEgaXMgZGVmaW5lZCBhdCB0aGUgdG9wIG9mIHRoaXMgZG9jdW1lbnQKc2NlR0FNIDwtIGZpdEdBTSgKICBjb3VudHMgPSBkcm9wMChvYmpAYXNzYXlzJFNDVEBkYXRhW3NlbF9nZW5lcywgc2VsX2NlbGxzXSksCiAgcHNldWRvdGltZSA9IHBzZXVkb3RpbWVbc2VsX2NlbGxzLCBdLAogIGNlbGxXZWlnaHRzID0gY2VsbFdlaWdodHNbc2VsX2NlbGxzLCBdLAogIG5rbm90cyA9IDUsIHZlcmJvc2UgPSBUUlVFLCBwYXJhbGxlbCA9IFRSVUUsIHNjZSA9IFRSVUUsCiAgQlBQQVJBTSA9IEJpb2NQYXJhbGxlbDo6TXVsdGljb3JlUGFyYW0oKQopCgoKCnBsb3RHZW5lQ291bnQoY3VydmVzLCBjbHVzdGVycyA9IG9iaiRzZXVyYXRfY2x1c3RlcnMsIG1vZGVscyA9IHNjZUdBTSkKCgpsaW5lYWdlcwoKbGMgPC0gc2FwcGx5KGxpbmVhZ2VzQGxpbmVhZ2VzLCBmdW5jdGlvbih4KSB7CiAgcmV2KHgpWzFdCn0pCm5hbWVzKGxjKSA8LSBnc3ViKCJMaW5lYWdlIiwgIkwiLCBuYW1lcyhsYykpCmxjLmlkeCA9IG1hdGNoKGxjLCBsZXZlbHMob2JqJHNldXJhdF9jbHVzdGVycykpCgp7CiAgcGxvdChvYmpAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5ncywgY29sID0gcGFsW29iaiRzZXVyYXRfY2x1c3RlcnNdLCBwY2ggPSAxNikKICBsaW5lcyhjdXJ2ZXMsIGx3ZCA9IDIsIGNvbCA9ICJibGFjayIpCiAgcG9pbnRzKGNlbnRyb2lkczJkW2xjLmlkeCwgXSwgY29sID0gImJsYWNrIiwgcGNoID0gMTYsIGNleCA9IDQpCiAgdGV4dChjZW50cm9pZHMyZFtsYy5pZHgsIF0sIGxhYmVscyA9IG5hbWVzKGxjKSwgY2V4ID0gMSwgZm9udCA9IDIsIGNvbCA9ICJ3aGl0ZSIpCn0KCgoKCmBgYAoKCgoKCgoKCiMjIEdlbmVzIHRoYXQgY2hhbmdlIHdpdGggcHNldWRvdGltZQpgYGB7cn0KCnNldC5zZWVkKDgpCnJlcyA8LSBuYS5vbWl0KGFzc29jaWF0aW9uVGVzdChzY2VHQU0sIGNvbnRyYXN0VHlwZSA9ICJjb25zZWN1dGl2ZSIpKQpyZXMgPC0gcmVzW3JlcyRwdmFsdWUgPCAxZS0zLCBdCnJlcyA8LSByZXNbcmVzJHdhbGRTdGF0ID4gbWVhbihyZXMkd2FsZFN0YXQpLCBdCnJlcyA8LSByZXNbb3JkZXIocmVzJHdhbGRTdGF0LCBkZWNyZWFzaW5nID0gVCksIF0KcmVzWzE6MTAsIF0KCmBgYAoKCiMjIFdlIGNhbiBwbG90IHRoZWlyIGV4cHJlc3Npb24KYGBge3J9CgpwYXIobWZyb3cgPSBjKDQsIDQpLCBtYXIgPSBjKC4xLCAuMSwgMiwgMSkpCnsKICBwbG90KG9iakByZWR1Y3Rpb25zJHVtYXBAY2VsbC5lbWJlZGRpbmdzLCBjb2wgPSBwYWxbb2JqJHNldXJhdF9jbHVzdGVyc10sIGNleCA9IC41LCBwY2ggPSAxNiwgYXhlcyA9IEYsIHhsYWIgPSAiIiwgeWxhYiA9ICIiKQogIGxpbmVzKGN1cnZlcywgbHdkID0gMiwgY29sID0gImJsYWNrIikKICBwb2ludHMoY2VudHJvaWRzMmRbbGMuaWR4LCBdLCBjb2wgPSAiYmxhY2siLCBwY2ggPSAxNSwgY2V4ID0gMywgeHBkID0gVCkKICB0ZXh0KGNlbnRyb2lkczJkW2xjLmlkeCwgXSwgbGFiZWxzID0gbmFtZXMobGMpLCBjZXggPSAxLCBmb250ID0gMiwgY29sID0gIndoaXRlIiwgeHBkID0gVCkKfQoKdmFycyA8LSByb3duYW1lcyhyZXNbMToxNSwgXSkKdmFycyA8LSBuYS5vbWl0KHZhcnNbdmFycyAhPSAiTkEiXSkKCmZvciAoaSBpbiB2YXJzKSB7CiAgeCA8LSBkcm9wMChvYmpAYXNzYXlzJFNDVEBkYXRhKVtpLCBdCiAgeCA8LSAoeCAtIG1pbih4KSkgLyAobWF4KHgpIC0gbWluKHgpKQogIG8gPC0gb3JkZXIoeCkKICBwbG90KG9iakByZWR1Y3Rpb25zJHVtYXBAY2VsbC5lbWJlZGRpbmdzW28sIF0sCiAgICBtYWluID0gcGFzdGUwKGkpLCBwY2ggPSAxNiwgY2V4ID0gMC41LCBheGVzID0gRiwgeGxhYiA9ICIiLCB5bGFiID0gIiIsCiAgICBjb2wgPSBjb2xvclJhbXBQYWxldHRlKGMoImxpZ2h0Z3JheSIsICJncmV5NjAiLCAibmF2eSIpKSg5OSlbeFtvXSAqIDk4ICsgMV0KICApCn0KCmBgYAoKCgojIyBHZW5lcyB0aGF0IGNoYW5nZSBiZXR3ZWVuIHR3byBwc2V1ZG90aW1lIHBvaW50cwpgYGB7cn0KcmVzIDwtIG5hLm9taXQoc3RhcnRWc0VuZFRlc3Qoc2NlR0FNLCBwc2V1ZG90aW1lVmFsdWVzID0gYygwLCAxKSkpCnJlcyA8LSByZXNbcmVzJHB2YWx1ZSA8IDFlLTMsIF0KcmVzIDwtIHJlc1tyZXMkd2FsZFN0YXQgPiBtZWFuKHJlcyR3YWxkU3RhdCksIF0KcmVzIDwtIHJlc1tvcmRlcihyZXMkd2FsZFN0YXQsIGRlY3JlYXNpbmcgPSBUKSwgXQpyZXNbMToxMCwgMTo2XQpgYGAKIyMgaWRlbnRpZnkgd2hpY2ggZ2VuZXMgZ28gdXAgb3IgZG93bi4gTGV04oCZcyBjaGVjayBsaW5lYWdlIDE6CmBgYHtyfQojIEdldCB0aGUgdG9wIFVQIGFuZCBEb3duIHJlZ3VsYXRlZCBpbiBsaW5lYWdlIDEKcmVzX2xpbjEgPC0gc29ydChzZXROYW1lcyhyZXMkbG9nRkNsaW5lYWdlMSwgcm93bmFtZXMocmVzKSkpCnZhcnMgPC0gbmFtZXMoYyhyZXYocmVzX2xpbjEpWzE6N10sIHJlc19saW4xWzE6OF0pKQp2YXJzIDwtIG5hLm9taXQodmFyc1t2YXJzICE9ICJOQSJdKQoKcGFyKG1mcm93ID0gYyg0LCA0KSwgbWFyID0gYyguMSwgLjEsIDIsIDEpKQoKewogIHBsb3Qob2JqQHJlZHVjdGlvbnMkdW1hcEBjZWxsLmVtYmVkZGluZ3MsIGNvbCA9IHBhbFtvYmokc2V1cmF0X2NsdXN0ZXJzXSwgY2V4ID0gLjUsIHBjaCA9IDE2LCBheGVzID0gRiwgeGxhYiA9ICIiLCB5bGFiID0gIiIpCiAgbGluZXMoY3VydmVzLCBsd2QgPSAyLCBjb2wgPSAiYmxhY2siKQogIHBvaW50cyhjZW50cm9pZHMyZFtsYy5pZHgsIF0sIGNvbCA9ICJibGFjayIsIHBjaCA9IDE1LCBjZXggPSAzLCB4cGQgPSBUKQogIHRleHQoY2VudHJvaWRzMmRbbGMuaWR4LCBdLCBsYWJlbHMgPSBuYW1lcyhsYyksIGNleCA9IDEsIGZvbnQgPSAyLCBjb2wgPSAid2hpdGUiLCB4cGQgPSBUKQp9Cgpmb3IgKGkgaW4gdmFycykgewogIHggPC0gZHJvcDAob2JqQGFzc2F5cyRTQ1RAZGF0YSlbaSwgXQogIHggPC0gKHggLSBtaW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSkKICBvIDwtIG9yZGVyKHgpCiAgcGxvdChvYmpAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5nc1tvLCBdLAogICAgbWFpbiA9IHBhc3RlMChpKSwgcGNoID0gMTYsIGNleCA9IDAuNSwgYXhlcyA9IEYsIHhsYWIgPSAiIiwgeWxhYiA9ICIiLAogICAgY29sID0gY29sb3JSYW1wUGFsZXR0ZShjKCJsaWdodGdyYXkiLCAiZ3JleTYwIiwgIm5hdnkiKSkoOTkpW3hbb10gKiA5OCArIDFdCiAgKQp9CmBgYAoKIyMgR2VuZXMgdGhhdCBhcmUgZGlmZmVyZW50IGJldHdlZW4gbGluZWFnZXMKYGBge3J9CnJlcyA8LSBuYS5vbWl0KGRpZmZFbmRUZXN0KHNjZUdBTSkpCnJlcyA8LSByZXNbcmVzJHB2YWx1ZSA8IDFlLTMsIF0KcmVzIDwtIHJlc1tyZXMkd2FsZFN0YXQgPiBtZWFuKHJlcyR3YWxkU3RhdCksIF0KcmVzIDwtIHJlc1tvcmRlcihyZXMkd2FsZFN0YXQsIGRlY3JlYXNpbmcgPSBUKSwgXQpyZXNbMToxMCwgXQoKYGBgCgojIyBwYWlyd2lzZSBjb21wYXJpc29uIGJldHdlZW4gZWFjaCBsaW5lYWdlLiBMZXTigJlzIGNoZWNrIGxpbmVhZ2UgMSB2cyBsaW5lYWdlIDI6CmBgYHtyfQojIEdldCB0aGUgdG9wIFVQIGFuZCBEb3duIHJlZ3VsYXRlZCBpbiBsaW5lYWdlIDEgdnMgMgpyZXNfbGluMV8yIDwtIHNvcnQoc2V0TmFtZXMocmVzJGxvZ0ZDMV8yLCByb3duYW1lcyhyZXMpKSkKdmFycyA8LSBuYW1lcyhjKHJldihyZXNfbGluMV8yKVsxOjddLCByZXNfbGluMV8yWzE6OF0pKQp2YXJzIDwtIG5hLm9taXQodmFyc1t2YXJzICE9ICJOQSJdKQoKcGFyKG1mcm93ID0gYyg0LCA0KSwgbWFyID0gYyguMSwgLjEsIDIsIDEpKQp7CiAgcGxvdChvYmpAcmVkdWN0aW9ucyR1bWFwQGNlbGwuZW1iZWRkaW5ncywgY29sID0gcGFsW29iaiRzZXVyYXRfY2x1c3RlcnNdLCBjZXggPSAuNSwgcGNoID0gMTYsIGF4ZXMgPSBGLCB4bGFiID0gIiIsIHlsYWIgPSAiIikKICBsaW5lcyhjdXJ2ZXMsIGx3ZCA9IDIsIGNvbCA9ICJibGFjayIpCiAgcG9pbnRzKGNlbnRyb2lkczJkW2xjLmlkeCwgXSwgY29sID0gImJsYWNrIiwgcGNoID0gMTUsIGNleCA9IDMsIHhwZCA9IFQpCiAgdGV4dChjZW50cm9pZHMyZFtsYy5pZHgsIF0sIGxhYmVscyA9IG5hbWVzKGxjKSwgY2V4ID0gMSwgZm9udCA9IDIsIGNvbCA9ICJ3aGl0ZSIsIHhwZCA9IFQpCn0KCmZvciAoaSBpbiB2YXJzKSB7CiAgeCA8LSBkcm9wMChvYmpAYXNzYXlzJFNDVEBkYXRhKVtpLCBdCiAgeCA8LSAoeCAtIG1pbih4KSkgLyAobWF4KHgpIC0gbWluKHgpKQogIG8gPC0gb3JkZXIoeCkKICBwbG90KG9iakByZWR1Y3Rpb25zJHVtYXBAY2VsbC5lbWJlZGRpbmdzW28sIF0sCiAgICBtYWluID0gcGFzdGUwKGkpLCBwY2ggPSAxNiwgY2V4ID0gMC41LCBheGVzID0gRiwgeGxhYiA9ICIiLCB5bGFiID0gIiIsCiAgICBjb2wgPSBjb2xvclJhbXBQYWxldHRlKGMoImxpZ2h0Z3JheSIsICJncmV5NjAiLCAibmF2eSIpKSg5OSlbeFtvXSAqIDk4ICsgMV0KICApCn0KYGBgCgoKCgo=