Context

Despite scRNA-Seq data provides a snapshot of gene expression at a particular time point, expression levels are captured across individual cells. It captures the heterogeneity of cells within a population. If biological tissue was not a steady state, it may be possible to retrieve trajectories, such as developmental lineage or disease progression. Trajectory inferences and RNA velocity leverage the information within that snapshot to infer such cellular trajectories. Ordering cells is a first step to understand the underlying biological processes driving cell fate decisions and differentiation events.

Abbreviations :

TI : trajectory inference
kNN : k-nearest neighbors

Method principles

In section, we make a overview of two main methods to order cells : trajectory inference and RNA velocity.

Trajectory inference

Trajectory inference is based on the gene expression profile. It looks at distance between cells : cells with similar transcriptomic profile are close, and cells with distinct profile are distant. Based on transcriptomic profiles, it finds paths between cells, leading to trajectories.

drawing

Figure from Wolf et al. (2019). Partition-based graph abstraction generates a topology-preserving map of single cells. High-dimensional gene expression data is represented as a kNN graph by choosing a suitable low-dimensional representation and an associated distance metric for computing neighborhood relations. […] We use PCA-based representations and Euclidean distance. […] By discarding spurious edges with low weights, PAGA graphs reveal the denoised topology of the data at a chosen resolution and reveal its connected and disconnected regions. […] We order cells within each partition according to their distance from a root cell. A PAGA path then averages all single-cell paths that pass through the corresponding groups of cells. This allows to trace gene expression changes along complex trajectories at single-cell resolution

There are more than 50 methods to perform trajectory inference because : - many input types are possible (count matrix, normalized matrix, reduced space) - many ways to compute distance - many ways to find a path

How to choose a trajectory inference method ?

drawing

Figure from Saelens et al. (2019). Practical guidelines for method users. As the performance of a method mostly depends on the topology of the trajectory, the choice of TI method will be primarily influenced by the user’s existing knowledge about the expected topology in the data.

How to test several TI methods ?

drawing

Screenshot from https://dynverse.org/, a collection of R packages to apply trajectory inference using the same code structure.

RNA velocity

RNA velocity requires spliced and unspliced count matrices as input. They are identified as described on the figure below :

drawing

Figure from La Manno et al. (2018). Spliced and unspliced counts are estimated by separately counting reads that incorporate the intronic sequence. Multiple reads associated with a given molecule are grouped (boxes with asterisks) for unique molecular identifier (UMI)-based protocols. Pie charts show typical fractions of unspliced molecules.

RNA velocity predicts the future transcriptional state of individual cells by estimating the ratio of unspliced and spliced RNA from sequencing data, which indicates the rate of RNA synthesis and degradation and can be used to predict the future state of a cell.

drawing

Figure from Bergen et al. (2020). Modeling transcriptional dynamics captures transcriptional induction and repression (‘on’ and ‘off’ phase) of unspliced pre-mRNAs, their conversion into mature, spliced mRNAs and their eventual degradation.

Principle summary

The table below summarizes the input, principle and output for the two method families.

Method Input Principle Output
Trajectory inference summarized* gene expression matrix make a topology and find shortest path between cells lineage tree, pseudotime*
RNA velocity spliced and unspliced count matrices relative difference between spliced and unspliced molecules vector field plots, latent time*

*summarized gene expression matrix : Some TI methods do not take the full gene expression matrix as input, but either a subset of genes, or a reduced space built from the gene expression matrix (PCA, batch-effect corrected space…)

*pseudotime : “A cell’s pseudotime, then, is defined as the length of the lineage from its inception up to the projection of a cell onto that lineage. If a trajectory consists of multiple lineages, cells may have multiple pseudotime values, depending on which lineage the cell is projected upon. […] Pseudotime is based solely on transcriptional information, so it cannot be interpreted as an estimator of the true time since initial differentiation” [Weiler et al. (2023)]

drawing

Figure from Weiler et al. (2023). Introduction to trajectory inference terminology. A trajectory is a collection of (one or more) lineages. A lineage is a single differentiation path. The pseudotime of a cell is the length of the lineage from its inception up to the projection of the respective cell onto that lineage.

*latent time : “Each cell is associated with a time. As such, gene-specific times need to be pooled together into a single, gene-shared latent time for every cell” [Weiler et al. (2023)]. So there may be a gap between two latent time points if no cells were captured with the associated unspliced/spliced profile.

drawing

Figure from Bergen et al. (2020). On simulated splicing kinetics, latent time is able to reconstruct the underlying real time at near-perfect correlation and correct scale, clearly outperforming diffusion pseudo-time. In contrast to pseudo-time methods, our latent time is grounded on transcriptional dynamics and internally accounts for speed and direction of motion.

Note :

  • Traditional clustering methods allow to group cells in discrete groups, sharing similarities in their transcriptomic profile
  • Pseudotime and latent-time can be seen as continuous clustering, making connection between closely related cells

Set environment

We will apply two TI methods written in R, and RNA velocity as implemented in scvelo Python module. To access a Python environment within Rmarkdown, we built a Conda environment containing Python languages and all modules (packages) of interest, using the following commands (chunk is not evaluated because of eval = FALSE) :

conda create --name scvelo_env -c conda-forge \
matplotlib=3.5.0 \
numpy=1.21.1 \
pandas=1.5.3 \
numba scipy
conda activate scvelo_env
pip install -U scvelo
pip install ipywidgets
conda install -c conda-forge scanpy python-igraph leidenalg
conda deactivate scvelo_env

We load the Conda environment :

reticulate::use_condaenv("scvelo_env", required = TRUE)
reticulate::py_config()
## python:         /home/nf1/Tools/miniconda3/envs/scvelo_env/bin/python
## libpython:      /home/nf1/Tools/miniconda3/envs/scvelo_env/lib/libpython3.9.so
## pythonhome:     /home/nf1/Tools/miniconda3/envs/scvelo_env:/home/nf1/Tools/miniconda3/envs/scvelo_env
## version:        3.9.18 | packaged by conda-forge | (main, Aug 30 2023, 03:49:32)  [GCC 12.3.0]
## numpy:          /home/nf1/Tools/miniconda3/envs/scvelo_env/lib/python3.9/site-packages/numpy
## numpy_version:  1.21.1
## 
## NOTE: Python version was forced by use_python() function

We import all the Python modules of interest :

import scvelo as scv    # RNA velocity implementation
import scanpy           # scRNA-Seq data handling
import scipy            # sparse matrix
import numpy            # table
import pandas as pd     # table
import matplotlib.pyplot as plt # visualization

We can call the R libraries of interest :

library(Seurat)    # scRNA-Seq data handling
library(ggplot2)   # visualization
library(patchwork) # plot arrangement
library(slingshot) # trajectory inference
## Loading required package: princurve
library(TInGa)     # trajectory inference
library(dynwrap)   # TI
library(dynplot)   # TI visualization
library(Matrix)    # sparse matrix
library(viridis)   # colors
## Loading required package: viridisLite

Dataset presentation

RNA velocity requires both spliced and unspliced count matrices. Since you already have aligned your reads on a reference transcriptome, you may have access to a BAM file (mapping information for each read) and the GTF file (reference transcriptome annotation). The first RNA velocity method (velocyto from La Manno et al. (2018)) implements the spliced and unspliced matrices construction.

AnnData object

We load a published dataset that already contains such matrices, and is stored in the scvelo module :

adata = scv.datasets.pancreas()
adata
## AnnData object with n_obs × n_vars = 3696 × 27998
##     obs: 'clusters_coarse', 'clusters', 'S_score', 'G2M_score'
##     var: 'highly_variable_genes'
##     uns: 'clusters_coarse_colors', 'clusters_colors', 'day_colors', 'neighbors', 'pca'
##     obsm: 'X_pca', 'X_umap'
##     layers: 'spliced', 'unspliced'
##     obsp: 'distances', 'connectivities'

The dataset contains 3,696 cells. We can see the two layers corresponding to both spliced and unspliced count matrices. A UMAP is also stored in the object and enable 2D visualization of all cells. We visualize the dataset :

fig, axs = plt.subplots(1, 2, gridspec_kw = {'width_ratios': [1, 0.2]})

ax = axs[0]
scv.pl.scatter(adata, basis = "umap", color = "clusters",
legend_loc = 'right', size = 60, ax = ax, show = False)
ax.set_aspect('equal')

fig.delaxes(axs[1])

plt.subplots_adjust(left = 0.05, right = 0.9, top = 0.99, bottom = 0.05)
plt.show()

Export and save AnnData

As, we will need also this object in R so save it. We save it in a directory called pancreas :

mkdir -p ./pancreas

We save the spliced, unspliced, and full (spliced + unspliced) matrices :

scipy.io.mmwrite(a = adata.layers['spliced'],
target = './pancreas/matrix_spliced.mtx')

scipy.io.mmwrite(a = adata.layers['unspliced'],
target = './pancreas/matrix_unspliced.mtx')

scipy.io.mmwrite(a = adata.layers['spliced'] + adata.layers['unspliced'],
target = './pancreas/matrix.mtx')

We save the 2D representation, which a UMAP in this dataset :

numpy.savetxt(X = adata.obsm['X_umap'],
fname = './pancreas/umap.csv',
delimiter=',')

We save also the PCA, which is an input from trajectory inference method :

numpy.savetxt(X = adata.obsm['X_pca'],
fname = './pancreas/pca.csv',
delimiter=',')

We save the annotations, i.e. clusters, cell barcodes and features :

pd.DataFrame(adata.obs['clusters']).to_csv('./pancreas/clusters.csv', index = False)
pd.DataFrame(adata.obs_names).to_csv('./pancreas/barcodes.csv', index = False)
pd.DataFrame(adata.var_names).to_csv('./pancreas/features.csv', index = False)

Seurat object

We make a Seurat object from the saved data. First, we load the matrix :

mat = Matrix::readMM('./pancreas/matrix.mtx')

barcodes = as.character(read.csv('./pancreas/barcodes.csv')[, 1])
features = as.character(read.csv('./pancreas/features.csv')[, 1])

rownames(mat) = barcodes # set matrix row names (cells)
colnames(mat) = features # set matrix column names (gene names)

dim(mat)
## [1]  3696 27998

We load the annotation :

clusters = as.character(read.csv('./pancreas/clusters.csv')[, 1])
head(clusters)
## [1] "Pre-endocrine" "Ductal"        "Alpha"         "Ductal"       
## [5] "Ngn3 high EP"  "Ductal"

We build a Seurat object from the count matrix :

sobj = Seurat::CreateSeuratObject(counts = Matrix::t(mat),
                                  meta.data = data.frame(row.names = barcodes,
                                                         clusters = clusters))
sobj
## An object of class Seurat 
## 27998 features across 3696 samples within 1 assay 
## Active assay: RNA (27998 features, 0 variable features)

We add the PCA :

the_pca = read.csv('./pancreas/pca.csv',
                   header = FALSE)
the_pca = as.matrix(the_pca)
colnames(the_pca) = paste0("PC_", c(1:ncol(the_pca)))
rownames(the_pca) = barcodes

sobj@reductions[["pca"]] = Seurat::CreateDimReducObject(embeddings = the_pca,
                                                        key = "pca_")
## Warning: No assay specified, setting assay as RNA by default.
sobj
## An object of class Seurat 
## 27998 features across 3696 samples within 1 assay 
## Active assay: RNA (27998 features, 0 variable features)
##  1 dimensional reduction calculated: pca

We add the UMAP :

the_umap = read.csv('./pancreas/umap.csv',
                    header = FALSE)
the_umap = as.matrix(the_umap)
colnames(the_umap) = c("UMAP_1", "UMAP_2")
rownames(the_umap) = barcodes

sobj@reductions[["umap"]] = Seurat::CreateDimReducObject(embeddings = the_umap,
                                                         key = "umap_")
## Warning: No assay specified, setting assay as RNA by default.
sobj
## An object of class Seurat 
## 27998 features across 3696 samples within 1 assay 
## Active assay: RNA (27998 features, 0 variable features)
##  2 dimensional reductions calculated: pca, umap

We visualize the dataset :

Seurat::DimPlot(sobj, group.by = "clusters", reduction = "umap") +
  ggplot2::labs(title = "Clusters annotation") +
  Seurat::NoAxes() +
  ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5))

Now, we have both Seurat (R) and AnnData (Python) objects.

Trajectory inference

In this section, we used to TI tools, slingshot and TInGa.

With slingshot

First, we run slingshot, then, we visualize results.

Run method

We infer trajectories :

slingshot_traj = slingshot::slingshot(data = sobj@reductions[["pca"]]@cell.embeddings)
## No cluster labels provided. Continuing with one cluster.
## Using full covariance matrix
slingshot_traj
## class: SlingshotDataSet 
## 
##  Samples Dimensions
##     3696         50
## 
## lineages: 1 
## Lineage1: 1  
## 
## curves: 1 
## Curve1: Length: 76,7 Samples: 3696

Slingshot outputs 1 lineage.

Compute pseudotime

We add the pseudotime associated with this lineage in the Seurat object :

sobj$pseudotime_slingshot = slingshot::slingPseudotime(slingshot_traj)[, 1][colnames(sobj)]

head(sobj@meta.data)
##                     orig.ident nCount_RNA nFeature_RNA      clusters
## AAACCTGAGAGGGATA SeuratProject       6529         3115 Pre-endocrine
## AAACCTGAGCCTTGAT SeuratProject       8049         3128        Ductal
## AAACCTGAGGCAATTA SeuratProject       5165         2552         Alpha
## AAACCTGCATCATCCC SeuratProject      10017         3670        Ductal
## AAACCTGGTAAGTGGC SeuratProject       6040         2686  Ngn3 high EP
## AAACCTGGTATTAGCC SeuratProject       9734         3530        Ductal
##                  pseudotime_slingshot
## AAACCTGAGAGGGATA             23,78922
## AAACCTGAGCCTTGAT             69,63663
## AAACCTGAGGCAATTA             19,90596
## AAACCTGCATCATCCC             76,70007
## AAACCTGGTAAGTGGC             40,29643
## AAACCTGGTATTAGCC             71,15815

Visualize results

We visualize pseudotime :

Seurat::FeaturePlot(sobj, features = "pseudotime_slingshot",
                    reduction = "umap",
                    cols = viridis::viridis(n = 100)) +
  ggplot2::ggtitle("Slingshot pseudotime") +
  ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5)) +
  Seurat::NoAxes()

The root corresponds to the cells with pseudotime 0. We could have set manually a root. We run the method with an alternative setting.

With a root ?

We manually set a root using the following command :

slingshot_traj = slingshot::slingshot(data = sobj@reductions[["pca"]]@cell.embeddings,
                                      clusterLabels = sobj$clusters,
                                      start.clus = "Ductal")
## Using full covariance matrix
slingshot_traj
## class: SlingshotDataSet 
## 
##  Samples Dimensions
##     3696         50
## 
## lineages: 2 
## Lineage1: Ductal  Ngn3 low EP  Ngn3 high EP  Pre-endocrine  Beta  Alpha  Epsilon  
## Lineage2: Ductal  Ngn3 low EP  Ngn3 high EP  Pre-endocrine  Delta  
## 
## curves: 2 
## Curve1: Length: 76,337   Samples: 3451,29
## Curve2: Length: 82,676   Samples: 2534,14

Now, there are two lineages. We add the two associated pseudotimes in the object :

slingshot_pdt = slingshot::slingPseudotime(slingshot_traj)
n_lineages = ncol(slingshot_pdt)

sobj@meta.data[, paste0("pseudotime_slingshot_", c(1:n_lineages))] = slingshot::slingPseudotime(slingshot_traj)[colnames(sobj), ]

head(sobj@meta.data)
##                     orig.ident nCount_RNA nFeature_RNA      clusters
## AAACCTGAGAGGGATA SeuratProject       6529         3115 Pre-endocrine
## AAACCTGAGCCTTGAT SeuratProject       8049         3128        Ductal
## AAACCTGAGGCAATTA SeuratProject       5165         2552         Alpha
## AAACCTGCATCATCCC SeuratProject      10017         3670        Ductal
## AAACCTGGTAAGTGGC SeuratProject       6040         2686  Ngn3 high EP
## AAACCTGGTATTAGCC SeuratProject       9734         3530        Ductal
##                  pseudotime_slingshot pseudotime_slingshot_1
## AAACCTGAGAGGGATA             23,78922             54,2616250
## AAACCTGAGCCTTGAT             69,63663              9,2787305
## AAACCTGAGGCAATTA             19,90596             58,5088384
## AAACCTGCATCATCCC             76,70007              0,9812956
## AAACCTGGTAAGTGGC             40,29643             38,9445443
## AAACCTGGTATTAGCC             71,15815              7,8948470
##                  pseudotime_slingshot_2
## AAACCTGAGAGGGATA             59,2412330
## AAACCTGAGCCTTGAT              9,2804274
## AAACCTGAGGCAATTA                     NA
## AAACCTGCATCATCCC              0,9812002
## AAACCTGGTAAGTGGC             40,4065783
## AAACCTGGTATTAGCC              7,9017158

We visualize pseudotimes on the UMAP :

plot_list = lapply(paste0("pseudotime_slingshot_", c(1:n_lineages)), FUN = function(pdt) {
  p = Seurat::FeaturePlot(sobj, features = pdt,
                    reduction = "umap",
                    cols = viridis::viridis(n = 100)) +
  ggplot2::ggtitle(pdt) +
  ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5)) +
  Seurat::NoAxes()
  
  return(p)
})

patchwork::wrap_plots(plot_list)

By setting a root in the Ductal population, there are two distinct lineages ending in two populations. All cells are not included in both lineages.

With TInGa

Now, we run TInGa, then, we visualize results.

Run method

We normalize the count matrix :

sobj = Seurat::NormalizeData(sobj)
sobj
## An object of class Seurat 
## 27998 features across 3696 samples within 1 assay 
## Active assay: RNA (27998 features, 0 variable features)
##  2 dimensional reductions calculated: pca, umap

We build the input from the Seurat object :

expression = Seurat::GetAssayData(sobj, assay = "RNA", slot = "data")
expression = Matrix::t(expression)
expression = expression[, order(colnames(expression))]

counts = Seurat::GetAssayData(sobj, assay = "RNA", slot = "counts")
counts = counts[colnames(expression), ]
counts = Matrix::t(counts)

dataset = dynwrap::wrap_expression(id = sobj@project.name,
                                   expression = expression,
                                   counts = counts)

We apply the TI method :

set.seed(1234)
tinga_traj = dynwrap::infer_trajectory(dataset = dataset,
                                       method = TInGa::gng_param2(),
                                       seed = 1234,
                                       params = list(max_iter = 10000,
                                                     max_nodes = 10,
                                                     epsilon_b = 0.05,
                                                     epsilon_n = 0.001,
                                                     age_max = 200,
                                                     lambda = 200,
                                                     alpha = 0.5,
                                                     beta = 0.99))

Compute pseudotime

To define a pseudotime, we need to set a root. We choose randomly a cell in the Ductal cluster :

set.seed(1234)
root_id = sample(which(sobj$clusters == "Ductal"), size = 1)
root_barcode = colnames(sobj)[root_id]
root_barcode
## [1] "CATCCACCAATGACCT"

We visualize the root cell on the UMAP :

sobj$is_root = (colnames(sobj) == root_barcode)
sobj$cell_name = colnames(sobj)
root_coord = sobj@reductions[["umap"]]@cell.embeddings[root_barcode, ]

root_plot = Seurat::DimPlot(sobj, reduction = "umap",
                            cells.highlight = root_barcode, sizes.highlight = 3) +
  ggplot2::annotate(geom = "text", label = root_barcode,
                    x = root_coord[1] + 0.15*abs(root_coord[1]),
                    y = root_coord[2] + 0.15*abs(root_coord[2])) +
  ggplot2::ggtitle("Root cell") +
  ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5, face = "bold")) +
  Seurat::NoLegend() + Seurat::NoAxes()

sobj$is_root = NULL
sobj$cell_name = NULL

root_plot

We add the pseudotime, starting from this root cell :

tinga_traj = dynwrap::add_root(trajectory = tinga_traj,
                               root_cell_id = root_barcode)
tinga_traj = dynwrap::add_pseudotime(trajectory = tinga_traj)

We add the pseudotime in the Seurat object :

sobj$pseudotime_tinga = tinga_traj$pseudotime

head(sobj@meta.data)
##                     orig.ident nCount_RNA nFeature_RNA      clusters
## AAACCTGAGAGGGATA SeuratProject       6529         3115 Pre-endocrine
## AAACCTGAGCCTTGAT SeuratProject       8049         3128        Ductal
## AAACCTGAGGCAATTA SeuratProject       5165         2552         Alpha
## AAACCTGCATCATCCC SeuratProject      10017         3670        Ductal
## AAACCTGGTAAGTGGC SeuratProject       6040         2686  Ngn3 high EP
## AAACCTGGTATTAGCC SeuratProject       9734         3530        Ductal
##                  pseudotime_slingshot pseudotime_slingshot_1
## AAACCTGAGAGGGATA             23,78922             54,2616250
## AAACCTGAGCCTTGAT             69,63663              9,2787305
## AAACCTGAGGCAATTA             19,90596             58,5088384
## AAACCTGCATCATCCC             76,70007              0,9812956
## AAACCTGGTAAGTGGC             40,29643             38,9445443
## AAACCTGGTATTAGCC             71,15815              7,8948470
##                  pseudotime_slingshot_2 pseudotime_tinga
## AAACCTGAGAGGGATA             59,2412330      1,695568374
## AAACCTGAGCCTTGAT              9,2804274      0,266301621
## AAACCTGAGGCAATTA                     NA      1,862531244
## AAACCTGCATCATCCC              0,9812002      0,008093603
## AAACCTGGTAAGTGGC             40,4065783      1,211911467
## AAACCTGGTATTAGCC              7,9017158      0,251120779

Visualize results

We visualize pseudotime :

Seurat::FeaturePlot(sobj, features = "pseudotime_tinga",
                    reduction = "umap",
                    cols = viridis::viridis(n = 100)) +
  ggplot2::ggtitle("TInGa pseudotime") +
  ggplot2::theme(plot.title = ggplot2::element_text(hjust = 0.5)) +
  Seurat::NoAxes()

We visualize the trajectory with lineage tree :

dynplot::plot_dimred(trajectory = tinga_traj,
                     label_milestones = FALSE,
                     plot_trajectory = TRUE,
                     dimred = sobj[["umap"]]@cell.embeddings,
                     color_cells = 'pseudotime',
                     color_trajectory = "none")

The trajectory is mainly linear. We could have changed the input settings to make a more ramified trajectory.

RNA velocity

In this section, we run RNA velocity method using scvelo implementation.

Visualization

We visualize the number of count for spliced and unspliced transcripts :

adata.obs['total_spliced'] = adata.layers["spliced"].sum(axis = 1)
adata.obs['total_unspliced'] = adata.layers["unspliced"].sum(axis = 1)

# Plot
fig, axs = plt.subplots(1, 2)

ax = axs[0]
scv.pl.scatter(adata, basis = "umap", color = "total_spliced",
legend_loc = 'right', size = 60, ax = ax, show = False)
ax.set_aspect('equal')

ax = axs[1]
scv.pl.scatter(adata, basis = "umap", color = "total_unspliced",
legend_loc = 'right', size = 60, ax = ax, show = False)
ax.set_aspect('equal')

plt.subplots_adjust(left = 0.05, right = 0.9, top = 0.99, bottom = 0.05)
plt.show()

What are the proportions of unspliced compared to spliced transcripts ?

scv.pl.proportions(adata)

Run RNA velocity

We run each step of RNA velocity :

scv.pp.filter_genes(adata)
scv.pp.moments(adata)
## Normalized count data: X, spliced, unspliced.
## computing neighbors
##     finished (0:00:07) --> added 
##     'distances' and 'connectivities', weighted adjacency matrices (adata.obsp)
## computing moments based on connectivities
##     finished (0:00:03) --> added 
##     'Ms' and 'Mu', moments of un/spliced abundances (adata.layers)
scv.tl.recover_dynamics(adata, n_jobs = 12)
## recovering dynamics (using 12/48 cores)
##   0%|          | 0/4130 [00:00<?, ?gene/s]
##     finished (0:05:33) --> added 
##     'fit_pars', fitted parameters for splicing dynamics (adata.var)
scv.tl.velocity(adata, mode = 'dynamical')
## computing velocities
##     finished (0:00:14) --> added 
##     'velocity', velocity vectors for each individual cell (adata.layers)
scv.tl.velocity_graph(adata, n_jobs = 12)
## computing velocity graph (using 12/48 cores)
##   0%|          | 0/3696 [00:00<?, ?cells/s]
##     finished (0:00:14) --> added 
##     'velocity_graph', sparse matrix with cosine correlations (adata.uns)
adata
## AnnData object with n_obs × n_vars = 3696 × 27998
##     obs: 'clusters_coarse', 'clusters', 'S_score', 'G2M_score', 'total_spliced', 'total_unspliced', 'initial_size_unspliced', 'initial_size_spliced', 'initial_size', 'n_counts', 'velocity_self_transition'
##     var: 'highly_variable_genes', 'gene_count_corr', 'fit_r2', 'fit_alpha', 'fit_beta', 'fit_gamma', 'fit_t_', 'fit_scaling', 'fit_std_u', 'fit_std_s', 'fit_likelihood', 'fit_u0', 'fit_s0', 'fit_pval_steady', 'fit_steady_u', 'fit_steady_s', 'fit_variance', 'fit_alignment_scaling', 'velocity_genes'
##     uns: 'clusters_coarse_colors', 'clusters_colors', 'day_colors', 'neighbors', 'pca', 'recover_dynamics', 'velocity_params', 'velocity_graph', 'velocity_graph_neg'
##     obsm: 'X_pca', 'X_umap'
##     varm: 'loss'
##     layers: 'spliced', 'unspliced', 'Ms', 'Mu', 'fit_t', 'fit_tau', 'fit_tau_', 'velocity', 'velocity_u'
##     obsp: 'distances', 'connectivities'

Visualize results

We visualize the vector fields :

adata.obs['no_color'] = 0

fig, axs = plt.subplots(1, 2)

ax = axs[0]
scv.pl.velocity_embedding_stream(adata, basis = 'umap', color = 'no_color', show = False, ax = ax, legend_loc = 'none')
ax.set_title('Vector fields')
ax.set_aspect('equal')

ax = axs[1]
scv.pl.velocity_embedding_stream(adata, basis = 'umap', color = 'clusters', show = False, ax = ax)
ax.set_title('Vector fields and cluster annotation')
ax.set_aspect('equal')

plt.subplots_adjust(left = 0.05, right = 0.9, top = 0.99, bottom = 0.05)
plt.show()

A vector corresponds to one arrow, an oriented segment. It indicates the most probable next stage of cells. Earliest cells are not pointed by any vector. From this figure, root population corresponds to Ductal cluster. There are three possible ends : alpha, beta and epsilon.

Pseudotime and latent time

We infer a root set and compute a pseudotime, from the directed velocity graph :

scv.tl.velocity_pseudotime(adata)
## computing terminal states
##     identified 2 regions of root cells and 1 region of end points .
##     finished (0:00:00) --> added
##     'root_cells', root cells of Markov diffusion process (adata.obs)
##     'end_points', end points of Markov diffusion process (adata.obs)
scv.pl.scatter(adata, color = 'velocity_pseudotime', cmap = 'gnuplot')

We compute the latent time for each cell, based only on its transcriptional dynamics.

scv.tl.latent_time(adata)
## computing latent time using root_cells as prior
##     finished (0:00:06) --> added 
##     'latent_time', shared time (adata.obs)
scv.pl.scatter(adata, color = 'latent_time', color_map = 'gnuplot')

Latent time is coherent with pseudotime. However, pseudotime is homogeneous spread along cells while latent time more corresponds to a cell’s internal clock :

fig, axs = plt.subplots(1, 1)

plt.scatter(x = [t for t in adata.obs["velocity_pseudotime"]],
y = [t for t in adata.obs["latent_time"]],
s = 10)
axs.plot([0, 1], [0, 1], color = 'r', label = 'y = x') 
plt.xlabel('Pseudotime')
plt.ylabel('Latent time')
plt.title('Relation between velocity pseudotime and latent time')
plt.legend()
plt.show()

Save

We save both objects :

adata.write('./pancreas/adata_velocity.h5ad', compression = 'gzip')

To load it, use : adata = scanpy.read_h5ad('./pancreas/adata_velocity.h5ad').

saveRDS(sobj, file = './pancreas/sobj_traj.rds')

To load it, use : sobj = readRDS('./sobj_traj.rds').

Conclusion

  • Two main methods to infer progression order from scRNA-Seq : trajectory inference (TI) and RNA velocity
  • TI is based on gene expression while RNA velocity takes into account the splicing status of each RNA
  • Both methods output “arrows” :
    • TI : lineage tree
    • RNA velocity : vector field
  • Both methods output “values”, that can be interpretated as a continuous clustering :
    • TI : pseudotime, corresponding to a distance between one cell and the root, but this distance is not necessarily associated with a time
    • RNA velocity : a latent time, corresponding to a cell state, and can be interpreted as a biological time
  • A lot of TI tools : find the method suited to your data, based on the biological knowledge
  • A TI tool will always output a result : it doesn’t mean this is biologically true

And after ?

This can be done after trajectory inference or RNA velocity :

  • pseudotime / latent time discretization to group cells into n clusters, then differential expression between groups of cells in similar state
  • application of dynfeature package to identify important genes along pseudotime / latent time
  • heatmap showing gene expression changes along pseudotime or latent time

Session

R session information :

## R version 3.6.3 (2020-02-29)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 20.04.6 LTS
## 
## Matrix products: default
## BLAS:   /usr/local/src/R-3.6.3/lib/libRblas.so
## LAPACK: /usr/local/src/R-3.6.3/lib/libRlapack.so
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8          LC_NUMERIC=C                 
##  [3] LC_TIME=fr_FR.UTF-8           LC_COLLATE=en_US.UTF-8       
##  [5] LC_MONETARY=fr_FR.UTF-8       LC_MESSAGES=en_US.UTF-8      
##  [7] LC_PAPER=fr_FR.UTF-8          LC_NAME=fr_FR.UTF-8          
##  [9] LC_ADDRESS=fr_FR.UTF-8        LC_TELEPHONE=fr_FR.UTF-8     
## [11] LC_MEASUREMENT=fr_FR.UTF-8    LC_IDENTIFICATION=fr_FR.UTF-8
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] dynutils_1.0.5    purrr_0.3.4       tidyr_1.1.4       dplyr_1.0.7      
##  [5] viridis_0.5.1     viridisLite_0.3.0 Matrix_1.2-18     dynplot_1.1.0    
##  [9] dynwrap_1.2.1     TInGa_0.0.0.9000  slingshot_1.4.0   princurve_2.1.4  
## [13] patchwork_1.1.2   ggplot2_3.3.5     Seurat_3.1.5     
## 
## loaded via a namespace (and not attached):
##   [1] utf8_1.1.4                  reticulate_1.34.0          
##   [3] tidyselect_1.1.0            htmlwidgets_1.5.4          
##   [5] grid_3.6.3                  ranger_0.12.1              
##   [7] BiocParallel_1.20.1         Rtsne_0.15                 
##   [9] munsell_0.5.0               codetools_0.2-16           
##  [11] ica_1.0-2                   future_1.22.1              
##  [13] gng_0.1.0                   withr_2.5.0                
##  [15] colorspace_1.4-1            Biobase_2.46.0             
##  [17] highr_0.8                   knitr_1.41                 
##  [19] rstudioapi_0.13             stats4_3.6.3               
##  [21] SingleCellExperiment_1.8.0  ROCR_1.0-7                 
##  [23] listenv_0.8.0               labeling_0.3               
##  [25] GenomeInfoDbData_1.2.2      polyclip_1.10-0            
##  [27] farver_2.0.3                rprojroot_2.0.2            
##  [29] parallelly_1.28.1           vctrs_0.3.8                
##  [31] generics_0.1.0              xfun_0.40                  
##  [33] R6_2.4.1                    GenomeInfoDb_1.22.1        
##  [35] GA_3.2                      graphlayouts_0.7.0         
##  [37] rsvd_1.0.3                  bitops_1.0-6               
##  [39] DelayedArray_0.12.3         assertthat_0.2.1           
##  [41] scales_1.1.1                ggraph_2.0.3               
##  [43] nnet_7.3-12                 lmds_0.1.0                 
##  [45] gtable_0.3.0                dynfeature_1.0.0           
##  [47] babelwhale_1.0.1            npsurv_0.4-0               
##  [49] globals_0.14.0              processx_3.5.2             
##  [51] tidygraph_1.1.2             rlang_1.0.2                
##  [53] randomForestSRC_2.12.1      splines_3.6.3              
##  [55] lazyeval_0.2.2              acepack_1.4.1              
##  [57] checkmate_2.0.0             yaml_2.2.1                 
##  [59] reshape2_1.4.4              backports_1.4.1            
##  [61] Hmisc_4.4-0                 DiagrammeR_1.0.6.1         
##  [63] tools_3.6.3                 ellipsis_0.3.2             
##  [65] gplots_3.0.3                jquerylib_0.1.4            
##  [67] RColorBrewer_1.1-2          BiocGenerics_0.32.0        
##  [69] ggridges_0.5.2              Rcpp_1.0.9                 
##  [71] plyr_1.8.6                  visNetwork_2.0.9           
##  [73] base64enc_0.1-3             zlibbioc_1.32.0            
##  [75] RCurl_1.98-1.2              ps_1.6.0                   
##  [77] rpart_4.1-15                pbapply_1.4-2              
##  [79] cowplot_1.0.0               S4Vectors_0.24.4           
##  [81] dynparam_1.0.0              zoo_1.8-10                 
##  [83] SummarizedExperiment_1.16.1 ggrepel_0.9.1              
##  [85] cluster_2.1.0               magrittr_2.0.1             
##  [87] data.table_1.14.2           carrier_0.1.0              
##  [89] lmtest_0.9-38               RANN_2.6.1                 
##  [91] fitdistrplus_1.0-14         matrixStats_0.56.0         
##  [93] pkgload_1.2.2               hms_1.1.1                  
##  [95] lsei_1.2-0                  evaluate_0.18              
##  [97] jpeg_0.1-8.1                IRanges_2.20.2             
##  [99] gridExtra_2.3               testthat_3.1.0             
## [101] compiler_3.6.3              tibble_3.1.5               
## [103] KernSmooth_2.23-16          crayon_1.3.4               
## [105] htmltools_0.5.2             proxyC_0.1.5               
## [107] tzdb_0.1.2                  Formula_1.2-3              
## [109] RcppParallel_5.1.4          DBI_1.1.1                  
## [111] tweenr_1.0.1                MASS_7.3-51.5              
## [113] rappdirs_0.3.3              data.tree_1.0.0            
## [115] readr_2.0.2                 cli_3.0.1                  
## [117] gdata_2.18.0                parallel_3.6.3             
## [119] igraph_1.2.5                GenomicRanges_1.38.0       
## [121] pkgconfig_2.0.3             dyneval_0.9.9              
## [123] foreign_0.8-75              plotly_4.9.2.1             
## [125] foreach_1.5.0               bslib_0.3.1                
## [127] XVector_0.26.0              stringr_1.4.0              
## [129] digest_0.6.25               dyndimred_1.0.3            
## [131] sctransform_0.2.1           RcppAnnoy_0.0.16           
## [133] tsne_0.1-3                  rmarkdown_2.18             
## [135] leiden_0.3.3                htmlTable_1.13.3           
## [137] uwot_0.1.8                  gtools_3.8.2               
## [139] lifecycle_1.0.1             nlme_3.1-144               
## [141] jsonlite_1.7.2              desc_1.4.1                 
## [143] fansi_0.4.1                 pillar_1.6.3               
## [145] lattice_0.20-38             fastmap_1.1.0              
## [147] httr_1.4.2                  survival_3.1-8             
## [149] waldo_0.3.1                 glue_1.4.2                 
## [151] remotes_2.4.2               png_0.1-7                  
## [153] iterators_1.0.12            ggforce_0.3.1              
## [155] stringi_1.4.6               sass_0.4.0                 
## [157] blob_1.2.1                  latticeExtra_0.6-29        
## [159] caTools_1.18.0              irlba_2.3.3                
## [161] future.apply_1.5.0          ape_5.3

Python session information :

## -----
## anndata             0.10.1
## matplotlib          3.5.0
## numpy               1.21.1
## pandas              1.5.3
## scanpy              1.9.5
## scipy               1.10.1
## scvelo              0.2.5
## session_info        1.0.0
## -----
## Python 3.9.18 | packaged by conda-forge | (main, Aug 30 2023, 04:04:13) [GCC 12.3.0]
## Linux-5.15.0-87-generic-x86_64-with-glibc2.31
## -----
## Session information updated at 2023-11-04 16:14
LS0tCnRpdGxlOiAiSW5mZXIgY29udGludW91cyByZWxhdGlvbnMgYmV0d2VlbiBjZWxscyIKc3VidGl0bGU6ICJUcmFqZWN0b3J5IGluZmVyZW5jZSBhbmQgUk5BIHZlbG9jaXR5IgphdXRob3I6ICJFQkFJSSAyMDIzIG4xIC0gQXVkcmV5IE9uZnJveSIKZGF0ZTogIjYgdG8gMTAgTm92ZW1iZXIgMjAyMyIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCmhlYWRlci1pbmNsdWRlczoKICAgIC0gXHVzZXBhY2thZ2V7c2VjdHN0eX0KICAgIC0gXGFsbHNlY3Rpb25zZm9udHtcY29sb3J7Y3lhbn19Ci0tLQoKPHN0eWxlPgpib2R5IHsKdGV4dC1hbGlnbjoganVzdGlmeX0KPC9zdHlsZT4KCmBgYHtjc3MsIGVjaG8gPSBGQUxTRX0KaDEsIGgyLCBoMywgaDQgewpjb2xvcjogIzI4NmM3YjsKfQpgYGAKCgojIENvbnRleHQKCkRlc3BpdGUgc2NSTkEtU2VxIGRhdGEgcHJvdmlkZXMgYSBzbmFwc2hvdCBvZiBnZW5lIGV4cHJlc3Npb24gYXQgYSBwYXJ0aWN1bGFyIHRpbWUgcG9pbnQsIGV4cHJlc3Npb24gbGV2ZWxzIGFyZSBjYXB0dXJlZCBhY3Jvc3MgaW5kaXZpZHVhbCBjZWxscy4gSXQgY2FwdHVyZXMgdGhlIGhldGVyb2dlbmVpdHkgb2YgY2VsbHMgd2l0aGluIGEgcG9wdWxhdGlvbi4gSWYgYmlvbG9naWNhbCB0aXNzdWUgd2FzIG5vdCBhIHN0ZWFkeSBzdGF0ZSwgaXQgbWF5IGJlIHBvc3NpYmxlIHRvIHJldHJpZXZlIHRyYWplY3Rvcmllcywgc3VjaCBhcyBkZXZlbG9wbWVudGFsIGxpbmVhZ2Ugb3IgZGlzZWFzZSBwcm9ncmVzc2lvbi4gVHJhamVjdG9yeSBpbmZlcmVuY2VzIGFuZCBSTkEgdmVsb2NpdHkgbGV2ZXJhZ2UgdGhlIGluZm9ybWF0aW9uIHdpdGhpbiB0aGF0IHNuYXBzaG90IHRvIGluZmVyIHN1Y2ggY2VsbHVsYXIgdHJhamVjdG9yaWVzLiBPcmRlcmluZyBjZWxscyBpcyBhIGZpcnN0IHN0ZXAgdG8gdW5kZXJzdGFuZCB0aGUgdW5kZXJseWluZyBiaW9sb2dpY2FsIHByb2Nlc3NlcyBkcml2aW5nIGNlbGwgZmF0ZSBkZWNpc2lvbnMgYW5kIGRpZmZlcmVudGlhdGlvbiBldmVudHMuCgpBYmJyZXZpYXRpb25zIDogPGJyPgoKKipUSSoqIDogdHJhamVjdG9yeSBpbmZlcmVuY2UgPGJyPgoqKmtOTioqIDogay1uZWFyZXN0IG5laWdoYm9ycwoKCiMgTWV0aG9kIHByaW5jaXBsZXMKCkluIHNlY3Rpb24sIHdlIG1ha2UgYSBvdmVydmlldyBvZiB0d28gbWFpbiBtZXRob2RzIHRvIG9yZGVyIGNlbGxzIDogKip0cmFqZWN0b3J5IGluZmVyZW5jZSoqIGFuZCAqKlJOQSB2ZWxvY2l0eSoqLgoKIyMgVHJhamVjdG9yeSBpbmZlcmVuY2UKCioqVHJhamVjdG9yeSBpbmZlcmVuY2UqKiBpcyBiYXNlZCBvbiB0aGUgZ2VuZSBleHByZXNzaW9uIHByb2ZpbGUuIEl0IGxvb2tzIGF0IGRpc3RhbmNlIGJldHdlZW4gY2VsbHMgOiBjZWxscyB3aXRoIHNpbWlsYXIgdHJhbnNjcmlwdG9taWMgcHJvZmlsZSBhcmUgY2xvc2UsIGFuZCBjZWxscyB3aXRoIGRpc3RpbmN0IHByb2ZpbGUgYXJlIGRpc3RhbnQuIEJhc2VkIG9uIHRyYW5zY3JpcHRvbWljIHByb2ZpbGVzLCBpdCBmaW5kcyBwYXRocyBiZXR3ZWVuIGNlbGxzLCBsZWFkaW5nIHRvIHRyYWplY3Rvcmllcy4KCjxjZW50ZXI+PGltZyBzcmM9Ii4vaW1hZ2VfUm1kL3BhZ2EucG5nIiBhbHQ9ImRyYXdpbmciIHdpZHRoPSI3MDAiLz48L2NlbnRlcj4KKipGaWd1cmUqKiBmcm9tIFtXb2xmIGV0IGFsLiAoMjAxOSldKGh0dHBzOi8vZG9pLm9yZy8xMC4xMTg2L3MxMzA1OS0wMTktMTY2My14KS4gKlBhcnRpdGlvbi1iYXNlZCBncmFwaCBhYnN0cmFjdGlvbiBnZW5lcmF0ZXMgYSB0b3BvbG9neS1wcmVzZXJ2aW5nIG1hcCBvZiBzaW5nbGUgY2VsbHMuIEhpZ2gtZGltZW5zaW9uYWwgZ2VuZSBleHByZXNzaW9uIGRhdGEgaXMgcmVwcmVzZW50ZWQgYXMgYSBrTk4gZ3JhcGggYnkgY2hvb3NpbmcgYSBzdWl0YWJsZSBsb3ctZGltZW5zaW9uYWwgcmVwcmVzZW50YXRpb24gYW5kIGFuIGFzc29jaWF0ZWQgZGlzdGFuY2UgbWV0cmljIGZvciBjb21wdXRpbmcgbmVpZ2hib3Job29kIHJlbGF0aW9ucy4gWy4uLl0gV2UgdXNlIFBDQS1iYXNlZCByZXByZXNlbnRhdGlvbnMgYW5kIEV1Y2xpZGVhbiBkaXN0YW5jZS4gWy4uLl0gQnkgZGlzY2FyZGluZyBzcHVyaW91cyBlZGdlcyB3aXRoIGxvdyB3ZWlnaHRzLCBQQUdBIGdyYXBocyByZXZlYWwgdGhlIGRlbm9pc2VkIHRvcG9sb2d5IG9mIHRoZSBkYXRhIGF0IGEgY2hvc2VuIHJlc29sdXRpb24gYW5kIHJldmVhbCBpdHMgY29ubmVjdGVkIGFuZCBkaXNjb25uZWN0ZWQgcmVnaW9ucy4gWy4uLl0gV2Ugb3JkZXIgY2VsbHMgd2l0aGluIGVhY2ggcGFydGl0aW9uIGFjY29yZGluZyB0byB0aGVpciBkaXN0YW5jZSBmcm9tIGEgcm9vdCBjZWxsLiBBIFBBR0EgcGF0aCB0aGVuIGF2ZXJhZ2VzIGFsbCBzaW5nbGUtY2VsbCBwYXRocyB0aGF0IHBhc3MgdGhyb3VnaCB0aGUgY29ycmVzcG9uZGluZyBncm91cHMgb2YgY2VsbHMuIFRoaXMgYWxsb3dzIHRvIHRyYWNlIGdlbmUgZXhwcmVzc2lvbiBjaGFuZ2VzIGFsb25nIGNvbXBsZXggdHJhamVjdG9yaWVzIGF0IHNpbmdsZS1jZWxsIHJlc29sdXRpb24qCgpUaGVyZSBhcmUgbW9yZSB0aGFuICoqNTAgbWV0aG9kcyB0byBwZXJmb3JtIHRyYWplY3RvcnkgaW5mZXJlbmNlKiogYmVjYXVzZSA6Ci0gbWFueSBpbnB1dCB0eXBlcyBhcmUgcG9zc2libGUgKGNvdW50IG1hdHJpeCwgbm9ybWFsaXplZCBtYXRyaXgsIHJlZHVjZWQgc3BhY2UpCi0gbWFueSB3YXlzIHRvIGNvbXB1dGUgZGlzdGFuY2UKLSBtYW55IHdheXMgdG8gZmluZCBhIHBhdGgKCioqSG93IHRvIGNob29zZSBhIHRyYWplY3RvcnkgaW5mZXJlbmNlIG1ldGhvZCA/KioKCjxjZW50ZXI+PGltZyBzcmM9Ii4vaW1hZ2VfUm1kL2Nob29zZV90aV9tZXRob2RzLnBuZyIgYWx0PSJkcmF3aW5nIiB3aWR0aD0iNzAwIi8+PC9jZW50ZXI+CioqRmlndXJlKiogZnJvbSBbU2FlbGVucyBldCBhbC4gKDIwMTkpXShodHRwczovL2RvaS5vcmcvMTAuMTAzOC9zNDE1ODctMDE5LTAwNzEtOSkuICpQcmFjdGljYWwgZ3VpZGVsaW5lcyBmb3IgbWV0aG9kIHVzZXJzLiBBcyB0aGUgcGVyZm9ybWFuY2Ugb2YgYSBtZXRob2QgbW9zdGx5IGRlcGVuZHMgb24gdGhlIHRvcG9sb2d5IG9mIHRoZSB0cmFqZWN0b3J5LCB0aGUgY2hvaWNlIG9mIFRJIG1ldGhvZCB3aWxsIGJlIHByaW1hcmlseSBpbmZsdWVuY2VkIGJ5IHRoZSB1c2Vy4oCZcyBleGlzdGluZyBrbm93bGVkZ2UgYWJvdXQgdGhlIGV4cGVjdGVkIHRvcG9sb2d5IGluIHRoZSBkYXRhLioKCioqSG93IHRvIHRlc3Qgc2V2ZXJhbCBUSSBtZXRob2RzID8qKgoKPGNlbnRlcj48aW1nIHNyYz0iLi9pbWFnZV9SbWQvZHludmVyc2UucG5nIiBhbHQ9ImRyYXdpbmciIHdpZHRoPSI3MDAiLz48L2NlbnRlcj4KKipTY3JlZW5zaG90KiogZnJvbSBbaHR0cHM6Ly9keW52ZXJzZS5vcmcvXShodHRwczovL2R5bnZlcnNlLm9yZy8pLCBhIGNvbGxlY3Rpb24gb2YgUiBwYWNrYWdlcyB0byBhcHBseSB0cmFqZWN0b3J5IGluZmVyZW5jZSB1c2luZyB0aGUgc2FtZSBjb2RlIHN0cnVjdHVyZS4KCgojIyBSTkEgdmVsb2NpdHkKClJOQSB2ZWxvY2l0eSByZXF1aXJlcyBzcGxpY2VkIGFuZCB1bnNwbGljZWQgY291bnQgbWF0cmljZXMgYXMgaW5wdXQuIFRoZXkgYXJlIGlkZW50aWZpZWQgYXMgZGVzY3JpYmVkIG9uIHRoZSBmaWd1cmUgYmVsb3cgOgoKPGNlbnRlcj48aW1nIHNyYz0iLi9pbWFnZV9SbWQvc3BsaWNlZF91bnNwbGljZWQucG5nIiBhbHQ9ImRyYXdpbmciIHdpZHRoPSIzMDAiLz48L2NlbnRlcj4KKipGaWd1cmUqKiBmcm9tIFtMYSBNYW5ubyBldCBhbC4gKDIwMTgpXShodHRwczovL2RvaS5vcmcvMTAuMTAzOC9zNDE1ODYtMDE4LTA0MTQtNikuICpTcGxpY2VkIGFuZCB1bnNwbGljZWQgY291bnRzIGFyZSBlc3RpbWF0ZWQgYnkgc2VwYXJhdGVseSBjb3VudGluZyByZWFkcyB0aGF0IGluY29ycG9yYXRlIHRoZSBpbnRyb25pYyBzZXF1ZW5jZS4gTXVsdGlwbGUgcmVhZHMgYXNzb2NpYXRlZCB3aXRoIGEgZ2l2ZW4gbW9sZWN1bGUgYXJlIGdyb3VwZWQgKGJveGVzIHdpdGggYXN0ZXJpc2tzKSBmb3IgdW5pcXVlIG1vbGVjdWxhciBpZGVudGlmaWVyIChVTUkpLWJhc2VkIHByb3RvY29scy4gUGllIGNoYXJ0cyBzaG93IHR5cGljYWwgZnJhY3Rpb25zIG9mIHVuc3BsaWNlZCBtb2xlY3VsZXMuKgoKKipSTkEgdmVsb2NpdHkqKiBwcmVkaWN0cyB0aGUgZnV0dXJlIHRyYW5zY3JpcHRpb25hbCBzdGF0ZSBvZiBpbmRpdmlkdWFsIGNlbGxzIGJ5IGVzdGltYXRpbmcgdGhlIHJhdGlvIG9mIHVuc3BsaWNlZCBhbmQgc3BsaWNlZCBSTkEgZnJvbSBzZXF1ZW5jaW5nIGRhdGEsIHdoaWNoIGluZGljYXRlcyB0aGUgcmF0ZSBvZiBSTkEgc3ludGhlc2lzIGFuZCBkZWdyYWRhdGlvbiBhbmQgY2FuIGJlIHVzZWQgdG8gcHJlZGljdCB0aGUgZnV0dXJlIHN0YXRlIG9mIGEgY2VsbC4KCgo8Y2VudGVyPjxpbWcgc3JjPSIuL2ltYWdlX1JtZC9zcGxpY2luZy5wbmciIGFsdD0iZHJhd2luZyIgd2lkdGg9IjcwMCIvPjwvY2VudGVyPgoqKkZpZ3VyZSoqIGZyb20gW0JlcmdlbiBldCBhbC4gKDIwMjApXShodHRwczovL2RvaS5vcmcvMTAuMTAzOC9zNDE1ODctMDIwLTA1OTEtMykuICpNb2RlbGluZyB0cmFuc2NyaXB0aW9uYWwgZHluYW1pY3MgY2FwdHVyZXMgdHJhbnNjcmlwdGlvbmFsIGluZHVjdGlvbiBhbmQgcmVwcmVzc2lvbiAo4oCYb27igJkgYW5kIOKAmG9mZuKAmSBwaGFzZSkgb2YgdW5zcGxpY2VkIHByZS1tUk5BcywgdGhlaXIgY29udmVyc2lvbiBpbnRvIG1hdHVyZSwgc3BsaWNlZCBtUk5BcyBhbmQgdGhlaXIgZXZlbnR1YWwgZGVncmFkYXRpb24uKgoKCiMjIFByaW5jaXBsZSBzdW1tYXJ5CgpUaGUgdGFibGUgYmVsb3cgc3VtbWFyaXplcyB0aGUgaW5wdXQsIHByaW5jaXBsZSBhbmQgb3V0cHV0IGZvciB0aGUgdHdvIG1ldGhvZCBmYW1pbGllcy4KCnwgIE1ldGhvZCAgICAgICAgICAgICAgIHwgIElucHV0ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgUHJpbmNpcGxlICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfCBPdXRwdXQgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwKfCA6LS0tLS0tLS0tLS0tLS0tOiAgICAgfCA6LS0tLS0tLS0tLS0tLS0tLTogICAgICAgICAgICAgICAgICAgfCA6LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tOiAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IDotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tOiAgICAgfAp8IFRyYWplY3RvcnkgaW5mZXJlbmNlICB8IHN1bW1hcml6ZWRcKiBnZW5lIGV4cHJlc3Npb24gbWF0cml4ICAgIHwgbWFrZSBhIHRvcG9sb2d5IGFuZCBmaW5kIHNob3J0ZXN0IHBhdGggYmV0d2VlbiBjZWxscyAgICAgIHwgbGluZWFnZSB0cmVlLCBwc2V1ZG90aW1lXCogICAgICAgICB8CnwgUk5BIHZlbG9jaXR5ICAgICAgICAgIHwgc3BsaWNlZCBhbmQgdW5zcGxpY2VkIGNvdW50IG1hdHJpY2VzIHwgcmVsYXRpdmUgZGlmZmVyZW5jZSBiZXR3ZWVuIHNwbGljZWQgYW5kIHVuc3BsaWNlZCBtb2xlY3VsZXMgfCB2ZWN0b3IgZmllbGQgcGxvdHMsIGxhdGVudCB0aW1lXCogIHwKClwqKnN1bW1hcml6ZWQqIGdlbmUgZXhwcmVzc2lvbiBtYXRyaXggOiBTb21lIFRJIG1ldGhvZHMgZG8gbm90IHRha2UgdGhlIGZ1bGwgZ2VuZSBleHByZXNzaW9uIG1hdHJpeCBhcyBpbnB1dCwgYnV0IGVpdGhlciBhIHN1YnNldCBvZiBnZW5lcywgb3IgYSByZWR1Y2VkIHNwYWNlIGJ1aWx0IGZyb20gdGhlIGdlbmUgZXhwcmVzc2lvbiBtYXRyaXggKFBDQSwgYmF0Y2gtZWZmZWN0IGNvcnJlY3RlZCBzcGFjZS4uLikgCgpcKioqcHNldWRvdGltZSoqIDogIkEgY2VsbOKAmXMgcHNldWRvdGltZSwgdGhlbiwgaXMgZGVmaW5lZCBhcyB0aGUgbGVuZ3RoIG9mIHRoZSBsaW5lYWdlIGZyb20gaXRzIGluY2VwdGlvbiB1cCB0byB0aGUgcHJvamVjdGlvbiBvZiBhIGNlbGwgb250byB0aGF0IGxpbmVhZ2UuIElmIGEgdHJhamVjdG9yeSBjb25zaXN0cyBvZiBtdWx0aXBsZSBsaW5lYWdlcywgY2VsbHMgbWF5IGhhdmUgbXVsdGlwbGUgcHNldWRvdGltZSB2YWx1ZXMsIGRlcGVuZGluZyBvbiB3aGljaCBsaW5lYWdlIHRoZSBjZWxsIGlzIHByb2plY3RlZCB1cG9uLiBbLi4uXSBQc2V1ZG90aW1lIGlzIGJhc2VkIHNvbGVseSBvbiB0cmFuc2NyaXB0aW9uYWwgaW5mb3JtYXRpb24sIHNvIGl0IGNhbm5vdCBiZSBpbnRlcnByZXRlZCBhcyBhbiBlc3RpbWF0b3Igb2YgdGhlIHRydWUgdGltZSBzaW5jZSBpbml0aWFsIGRpZmZlcmVudGlhdGlvbiIgW1tXZWlsZXIgZXQgYWwuICgyMDIzKV0oaHR0cHM6Ly9wdWJtZWQubmNiaS5ubG0ubmloLmdvdi8zNjQ5NTQ1Ni8pXQoKPGNlbnRlcj48aW1nIHNyYz0iLi9pbWFnZV9SbWQvcHNldWRvdGltZV90cmFqZWN0b3J5X2xpbmVhZ2UucG5nIiBhbHQ9ImRyYXdpbmciIHdpZHRoPSI1MDAiLz48L2NlbnRlcj4KKipGaWd1cmUqKiBmcm9tIFtXZWlsZXIgZXQgYWwuICgyMDIzKV0oaHR0cHM6Ly9wdWJtZWQubmNiaS5ubG0ubmloLmdvdi8zNjQ5NTQ1Ni8pLiAqSW50cm9kdWN0aW9uIHRvIHRyYWplY3RvcnkgaW5mZXJlbmNlIHRlcm1pbm9sb2d5LiBBIHRyYWplY3RvcnkgaXMgYSBjb2xsZWN0aW9uIG9mIChvbmUgb3IgbW9yZSkgbGluZWFnZXMuIEEgbGluZWFnZSBpcyBhIHNpbmdsZSBkaWZmZXJlbnRpYXRpb24gcGF0aC4gVGhlIHBzZXVkb3RpbWUgb2YgYSBjZWxsIGlzIHRoZSBsZW5ndGggb2YgdGhlIGxpbmVhZ2UgZnJvbSBpdHMgaW5jZXB0aW9uIHVwIHRvIHRoZSBwcm9qZWN0aW9uIG9mIHRoZSByZXNwZWN0aXZlIGNlbGwgb250byB0aGF0IGxpbmVhZ2UuKgoKXCoqKmxhdGVudCB0aW1lKiogOiAiRWFjaCBjZWxsIGlzIGFzc29jaWF0ZWQgd2l0aCBhIHRpbWUuIEFzIHN1Y2gsIGdlbmUtc3BlY2lmaWMgdGltZXMgbmVlZCB0byBiZSBwb29sZWQgdG9nZXRoZXIgaW50byBhIHNpbmdsZSwgZ2VuZS1zaGFyZWQgbGF0ZW50IHRpbWUgZm9yIGV2ZXJ5IGNlbGwiIFtbV2VpbGVyIGV0IGFsLiAoMjAyMyldKGh0dHBzOi8vcHVibWVkLm5jYmkubmxtLm5paC5nb3YvMzY0OTU0NTYvKV0uIFNvIHRoZXJlIG1heSBiZSBhIGdhcCBiZXR3ZWVuIHR3byBsYXRlbnQgdGltZSBwb2ludHMgaWYgbm8gY2VsbHMgd2VyZSBjYXB0dXJlZCB3aXRoIHRoZSBhc3NvY2lhdGVkIHVuc3BsaWNlZC9zcGxpY2VkIHByb2ZpbGUuCgo8Y2VudGVyPjxpbWcgc3JjPSIuL2ltYWdlX1JtZC9sYXRlbnRfdGltZS5wbmciIGFsdD0iZHJhd2luZyIgd2lkdGg9IjMwMCIvPjwvY2VudGVyPgoqKkZpZ3VyZSoqIGZyb20gW0JlcmdlbiBldCBhbC4gKDIwMjApXShodHRwczovL2RvaS5vcmcvMTAuMTAzOC9zNDE1ODctMDIwLTA1OTEtMykuICpPbiBzaW11bGF0ZWQgc3BsaWNpbmcga2luZXRpY3MsIGxhdGVudCB0aW1lIGlzIGFibGUgdG8gcmVjb25zdHJ1Y3QgdGhlIHVuZGVybHlpbmcgcmVhbCB0aW1lIGF0IG5lYXItcGVyZmVjdCBjb3JyZWxhdGlvbiBhbmQgY29ycmVjdCBzY2FsZSwgY2xlYXJseSBvdXRwZXJmb3JtaW5nIGRpZmZ1c2lvbiBwc2V1ZG8tdGltZS4gSW4gY29udHJhc3QgdG8gcHNldWRvLXRpbWUgbWV0aG9kcywgb3VyIGxhdGVudCB0aW1lIGlzIGdyb3VuZGVkIG9uIHRyYW5zY3JpcHRpb25hbCBkeW5hbWljcyBhbmQgaW50ZXJuYWxseSBhY2NvdW50cyBmb3Igc3BlZWQgYW5kIGRpcmVjdGlvbiBvZiBtb3Rpb24uKgoKCk5vdGUgOgoKKiBUcmFkaXRpb25hbCBjbHVzdGVyaW5nIG1ldGhvZHMgYWxsb3cgdG8gZ3JvdXAgY2VsbHMgaW4gKipkaXNjcmV0ZSBncm91cHMqKiwgc2hhcmluZyBzaW1pbGFyaXRpZXMgaW4gdGhlaXIgdHJhbnNjcmlwdG9taWMgcHJvZmlsZQoqIFBzZXVkb3RpbWUgYW5kIGxhdGVudC10aW1lIGNhbiBiZSBzZWVuIGFzICoqY29udGludW91cyBjbHVzdGVyaW5nKiosIG1ha2luZyBjb25uZWN0aW9uIGJldHdlZW4gY2xvc2VseSByZWxhdGVkIGNlbGxzCgoKIyBTZXQgZW52aXJvbm1lbnQKCldlIHdpbGwgYXBwbHkgdHdvIFRJIG1ldGhvZHMgd3JpdHRlbiBpbiBSLCBhbmQgUk5BIHZlbG9jaXR5IGFzIGltcGxlbWVudGVkIGluIHNjdmVsbyBQeXRob24gbW9kdWxlLiBUbyBhY2Nlc3MgYSBQeXRob24gZW52aXJvbm1lbnQgd2l0aGluIFJtYXJrZG93biwgd2UgYnVpbHQgYSBDb25kYSBlbnZpcm9ubWVudCBjb250YWluaW5nIFB5dGhvbiBsYW5ndWFnZXMgYW5kIGFsbCBtb2R1bGVzIChwYWNrYWdlcykgb2YgaW50ZXJlc3QsIHVzaW5nIHRoZSBmb2xsb3dpbmcgY29tbWFuZHMgKGNodW5rIGlzIG5vdCBldmFsdWF0ZWQgYmVjYXVzZSBvZiBgZXZhbCA9IEZBTFNFYCkgOgoKYGBge2Jhc2ggY29uZGFfZW52LCBldmFsID0gRkFMU0V9CmNvbmRhIGNyZWF0ZSAtLW5hbWUgc2N2ZWxvX2VudiAtYyBjb25kYS1mb3JnZSBcCm1hdHBsb3RsaWI9My41LjAgXApudW1weT0xLjIxLjEgXApwYW5kYXM9MS41LjMgXApudW1iYSBzY2lweQpjb25kYSBhY3RpdmF0ZSBzY3ZlbG9fZW52CnBpcCBpbnN0YWxsIC1VIHNjdmVsbwpwaXAgaW5zdGFsbCBpcHl3aWRnZXRzCmNvbmRhIGluc3RhbGwgLWMgY29uZGEtZm9yZ2Ugc2NhbnB5IHB5dGhvbi1pZ3JhcGggbGVpZGVuYWxnCmNvbmRhIGRlYWN0aXZhdGUgc2N2ZWxvX2VudgpgYGAKCldlIGxvYWQgdGhlIENvbmRhIGVudmlyb25tZW50IDoKCmBgYHtyIGxvYWRfY29uZGF9CnJldGljdWxhdGU6OnVzZV9jb25kYWVudigic2N2ZWxvX2VudiIsIHJlcXVpcmVkID0gVFJVRSkKcmV0aWN1bGF0ZTo6cHlfY29uZmlnKCkKYGBgCgpXZSBpbXBvcnQgYWxsIHRoZSBQeXRob24gbW9kdWxlcyBvZiBpbnRlcmVzdCA6CgpgYGB7cHl0aG9uIGxvYWRfbW9kdWxlc30KaW1wb3J0IHNjdmVsbyBhcyBzY3YgICAgIyBSTkEgdmVsb2NpdHkgaW1wbGVtZW50YXRpb24KaW1wb3J0IHNjYW5weSAgICAgICAgICAgIyBzY1JOQS1TZXEgZGF0YSBoYW5kbGluZwppbXBvcnQgc2NpcHkgICAgICAgICAgICAjIHNwYXJzZSBtYXRyaXgKaW1wb3J0IG51bXB5ICAgICAgICAgICAgIyB0YWJsZQppbXBvcnQgcGFuZGFzIGFzIHBkICAgICAjIHRhYmxlCmltcG9ydCBtYXRwbG90bGliLnB5cGxvdCBhcyBwbHQgIyB2aXN1YWxpemF0aW9uCmBgYAoKV2UgKmNhbiogY2FsbCB0aGUgUiBsaWJyYXJpZXMgb2YgaW50ZXJlc3QgOgoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KSAgICAjIHNjUk5BLVNlcSBkYXRhIGhhbmRsaW5nCmxpYnJhcnkoZ2dwbG90MikgICAjIHZpc3VhbGl6YXRpb24KbGlicmFyeShwYXRjaHdvcmspICMgcGxvdCBhcnJhbmdlbWVudApsaWJyYXJ5KHNsaW5nc2hvdCkgIyB0cmFqZWN0b3J5IGluZmVyZW5jZQpsaWJyYXJ5KFRJbkdhKSAgICAgIyB0cmFqZWN0b3J5IGluZmVyZW5jZQpsaWJyYXJ5KGR5bndyYXApICAgIyBUSQpsaWJyYXJ5KGR5bnBsb3QpICAgIyBUSSB2aXN1YWxpemF0aW9uCmxpYnJhcnkoTWF0cml4KSAgICAjIHNwYXJzZSBtYXRyaXgKbGlicmFyeSh2aXJpZGlzKSAgICMgY29sb3JzCmBgYAoKCiMgRGF0YXNldCBwcmVzZW50YXRpb24KClJOQSB2ZWxvY2l0eSByZXF1aXJlcyBib3RoIHNwbGljZWQgYW5kIHVuc3BsaWNlZCBjb3VudCBtYXRyaWNlcy4gU2luY2UgeW91IGFscmVhZHkgaGF2ZSBhbGlnbmVkIHlvdXIgcmVhZHMgb24gYSByZWZlcmVuY2UgdHJhbnNjcmlwdG9tZSwgeW91IG1heSBoYXZlIGFjY2VzcyB0byBhICoqQkFNIGZpbGUqKiAobWFwcGluZyBpbmZvcm1hdGlvbiBmb3IgZWFjaCByZWFkKSBhbmQgdGhlICoqR1RGIGZpbGUqKiAocmVmZXJlbmNlIHRyYW5zY3JpcHRvbWUgYW5ub3RhdGlvbikuIFRoZSBmaXJzdCBSTkEgdmVsb2NpdHkgbWV0aG9kICgqKnZlbG9jeXRvKiogZnJvbSBbTGEgTWFubm8gZXQgYWwuICgyMDE4KV0oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMzgvczQxNTg2LTAxOC0wNDE0LTYpKSBpbXBsZW1lbnRzIHRoZSBzcGxpY2VkIGFuZCB1bnNwbGljZWQgbWF0cmljZXMgY29uc3RydWN0aW9uLgoKIyMgQW5uRGF0YSBvYmplY3QKCldlIGxvYWQgYSBwdWJsaXNoZWQgZGF0YXNldCB0aGF0IGFscmVhZHkgY29udGFpbnMgc3VjaCBtYXRyaWNlcywgYW5kIGlzIHN0b3JlZCBpbiB0aGUgc2N2ZWxvIG1vZHVsZSA6CgpgYGB7cHl0aG9uIGdldF9wYW5jcmVhc30KYWRhdGEgPSBzY3YuZGF0YXNldHMucGFuY3JlYXMoKQphZGF0YQpgYGAKClRoZSBkYXRhc2V0IGNvbnRhaW5zIDMsNjk2IGNlbGxzLiBXZSBjYW4gc2VlIHRoZSB0d28gbGF5ZXJzIGNvcnJlc3BvbmRpbmcgdG8gYm90aCBzcGxpY2VkIGFuZCB1bnNwbGljZWQgY291bnQgbWF0cmljZXMuIEEgVU1BUCBpcyBhbHNvIHN0b3JlZCBpbiB0aGUgb2JqZWN0IGFuZCBlbmFibGUgMkQgdmlzdWFsaXphdGlvbiBvZiBhbGwgY2VsbHMuIFdlIHZpc3VhbGl6ZSB0aGUgZGF0YXNldCA6CgpgYGB7cHl0aG9uIHNlZV9hZGF0YSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA1fQpmaWcsIGF4cyA9IHBsdC5zdWJwbG90cygxLCAyLCBncmlkc3BlY19rdyA9IHsnd2lkdGhfcmF0aW9zJzogWzEsIDAuMl19KQoKYXggPSBheHNbMF0Kc2N2LnBsLnNjYXR0ZXIoYWRhdGEsIGJhc2lzID0gInVtYXAiLCBjb2xvciA9ICJjbHVzdGVycyIsCmxlZ2VuZF9sb2MgPSAncmlnaHQnLCBzaXplID0gNjAsIGF4ID0gYXgsIHNob3cgPSBGYWxzZSkKYXguc2V0X2FzcGVjdCgnZXF1YWwnKQoKZmlnLmRlbGF4ZXMoYXhzWzFdKQoKcGx0LnN1YnBsb3RzX2FkanVzdChsZWZ0ID0gMC4wNSwgcmlnaHQgPSAwLjksIHRvcCA9IDAuOTksIGJvdHRvbSA9IDAuMDUpCnBsdC5zaG93KCkKYGBgCgojIyBFeHBvcnQgYW5kIHNhdmUgQW5uRGF0YQoKQXMsIHdlIHdpbGwgbmVlZCBhbHNvIHRoaXMgb2JqZWN0IGluIFIgc28gc2F2ZSBpdC4gV2Ugc2F2ZSBpdCBpbiBhIGRpcmVjdG9yeSBjYWxsZWQgYHBhbmNyZWFzYCA6CgpgYGB7YmFzaCBta2Rpcl9wYW5jcmVhc30KbWtkaXIgLXAgLi9wYW5jcmVhcwpgYGAKCldlIHNhdmUgdGhlIHNwbGljZWQsIHVuc3BsaWNlZCwgYW5kIGZ1bGwgKHNwbGljZWQgKyB1bnNwbGljZWQpIG1hdHJpY2VzIDoKCmBgYHtweXRob24gc2F2ZV9tYXRyaWNlc30Kc2NpcHkuaW8ubW13cml0ZShhID0gYWRhdGEubGF5ZXJzWydzcGxpY2VkJ10sCnRhcmdldCA9ICcuL3BhbmNyZWFzL21hdHJpeF9zcGxpY2VkLm10eCcpCgpzY2lweS5pby5tbXdyaXRlKGEgPSBhZGF0YS5sYXllcnNbJ3Vuc3BsaWNlZCddLAp0YXJnZXQgPSAnLi9wYW5jcmVhcy9tYXRyaXhfdW5zcGxpY2VkLm10eCcpCgpzY2lweS5pby5tbXdyaXRlKGEgPSBhZGF0YS5sYXllcnNbJ3NwbGljZWQnXSArIGFkYXRhLmxheWVyc1sndW5zcGxpY2VkJ10sCnRhcmdldCA9ICcuL3BhbmNyZWFzL21hdHJpeC5tdHgnKQpgYGAKCldlIHNhdmUgdGhlIDJEIHJlcHJlc2VudGF0aW9uLCB3aGljaCBhIFVNQVAgaW4gdGhpcyBkYXRhc2V0IDoKCmBgYHtweXRob24gc2F2ZV91bWFwfQpudW1weS5zYXZldHh0KFggPSBhZGF0YS5vYnNtWydYX3VtYXAnXSwKZm5hbWUgPSAnLi9wYW5jcmVhcy91bWFwLmNzdicsCmRlbGltaXRlcj0nLCcpCmBgYAoKV2Ugc2F2ZSBhbHNvIHRoZSBQQ0EsIHdoaWNoIGlzIGFuIGlucHV0IGZyb20gdHJhamVjdG9yeSBpbmZlcmVuY2UgbWV0aG9kIDoKCmBgYHtweXRob24gc2F2ZV9wY2F9Cm51bXB5LnNhdmV0eHQoWCA9IGFkYXRhLm9ic21bJ1hfcGNhJ10sCmZuYW1lID0gJy4vcGFuY3JlYXMvcGNhLmNzdicsCmRlbGltaXRlcj0nLCcpCmBgYAoKV2Ugc2F2ZSB0aGUgYW5ub3RhdGlvbnMsIGkuZS4gY2x1c3RlcnMsIGNlbGwgYmFyY29kZXMgYW5kIGZlYXR1cmVzIDoKCmBgYHtweXRob24gc2F2ZV9hbm5vdH0KcGQuRGF0YUZyYW1lKGFkYXRhLm9ic1snY2x1c3RlcnMnXSkudG9fY3N2KCcuL3BhbmNyZWFzL2NsdXN0ZXJzLmNzdicsIGluZGV4ID0gRmFsc2UpCnBkLkRhdGFGcmFtZShhZGF0YS5vYnNfbmFtZXMpLnRvX2NzdignLi9wYW5jcmVhcy9iYXJjb2Rlcy5jc3YnLCBpbmRleCA9IEZhbHNlKQpwZC5EYXRhRnJhbWUoYWRhdGEudmFyX25hbWVzKS50b19jc3YoJy4vcGFuY3JlYXMvZmVhdHVyZXMuY3N2JywgaW5kZXggPSBGYWxzZSkKYGBgCgojIyBTZXVyYXQgb2JqZWN0CgpXZSBtYWtlIGEgU2V1cmF0IG9iamVjdCBmcm9tIHRoZSBzYXZlZCBkYXRhLiBGaXJzdCwgd2UgbG9hZCB0aGUgbWF0cml4IDoKCmBgYHtyIGxvYWRfZGF0YX0KbWF0ID0gTWF0cml4OjpyZWFkTU0oJy4vcGFuY3JlYXMvbWF0cml4Lm10eCcpCgpiYXJjb2RlcyA9IGFzLmNoYXJhY3RlcihyZWFkLmNzdignLi9wYW5jcmVhcy9iYXJjb2Rlcy5jc3YnKVssIDFdKQpmZWF0dXJlcyA9IGFzLmNoYXJhY3RlcihyZWFkLmNzdignLi9wYW5jcmVhcy9mZWF0dXJlcy5jc3YnKVssIDFdKQoKcm93bmFtZXMobWF0KSA9IGJhcmNvZGVzICMgc2V0IG1hdHJpeCByb3cgbmFtZXMgKGNlbGxzKQpjb2xuYW1lcyhtYXQpID0gZmVhdHVyZXMgIyBzZXQgbWF0cml4IGNvbHVtbiBuYW1lcyAoZ2VuZSBuYW1lcykKCmRpbShtYXQpCmBgYAoKV2UgbG9hZCB0aGUgYW5ub3RhdGlvbiA6CgpgYGB7ciBsb2FkX2NsdXN0ZXJzfQpjbHVzdGVycyA9IGFzLmNoYXJhY3RlcihyZWFkLmNzdignLi9wYW5jcmVhcy9jbHVzdGVycy5jc3YnKVssIDFdKQpoZWFkKGNsdXN0ZXJzKQpgYGAKCgpXZSBidWlsZCBhIFNldXJhdCBvYmplY3QgZnJvbSB0aGUgY291bnQgbWF0cml4IDoKCmBgYHtyIG1ha2Vfc29ian0Kc29iaiA9IFNldXJhdDo6Q3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cyA9IE1hdHJpeDo6dChtYXQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0YS5kYXRhID0gZGF0YS5mcmFtZShyb3cubmFtZXMgPSBiYXJjb2RlcywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3RlcnMgPSBjbHVzdGVycykpCnNvYmoKYGBgCgpXZSBhZGQgdGhlIFBDQSA6CgpgYGB7ciBhZGRfcGNhfQp0aGVfcGNhID0gcmVhZC5jc3YoJy4vcGFuY3JlYXMvcGNhLmNzdicsCiAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBGQUxTRSkKdGhlX3BjYSA9IGFzLm1hdHJpeCh0aGVfcGNhKQpjb2xuYW1lcyh0aGVfcGNhKSA9IHBhc3RlMCgiUENfIiwgYygxOm5jb2wodGhlX3BjYSkpKQpyb3duYW1lcyh0aGVfcGNhKSA9IGJhcmNvZGVzCgpzb2JqQHJlZHVjdGlvbnNbWyJwY2EiXV0gPSBTZXVyYXQ6OkNyZWF0ZURpbVJlZHVjT2JqZWN0KGVtYmVkZGluZ3MgPSB0aGVfcGNhLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleSA9ICJwY2FfIikKc29iagpgYGAKCgpXZSBhZGQgdGhlIFVNQVAgOgoKYGBge3IgYWRkX3VtYXB9CnRoZV91bWFwID0gcmVhZC5jc3YoJy4vcGFuY3JlYXMvdW1hcC5jc3YnLAogICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IEZBTFNFKQp0aGVfdW1hcCA9IGFzLm1hdHJpeCh0aGVfdW1hcCkKY29sbmFtZXModGhlX3VtYXApID0gYygiVU1BUF8xIiwgIlVNQVBfMiIpCnJvd25hbWVzKHRoZV91bWFwKSA9IGJhcmNvZGVzCgpzb2JqQHJlZHVjdGlvbnNbWyJ1bWFwIl1dID0gU2V1cmF0OjpDcmVhdGVEaW1SZWR1Y09iamVjdChlbWJlZGRpbmdzID0gdGhlX3VtYXAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleSA9ICJ1bWFwXyIpCnNvYmoKYGBgCgpXZSB2aXN1YWxpemUgdGhlIGRhdGFzZXQgOgoKYGBge3Igc2VlX3NvYmosIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gNX0KU2V1cmF0OjpEaW1QbG90KHNvYmosIGdyb3VwLmJ5ID0gImNsdXN0ZXJzIiwgcmVkdWN0aW9uID0gInVtYXAiKSArCiAgZ2dwbG90Mjo6bGFicyh0aXRsZSA9ICJDbHVzdGVycyBhbm5vdGF0aW9uIikgKwogIFNldXJhdDo6Tm9BeGVzKCkgKwogIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQpgYGAKCgpOb3csIHdlIGhhdmUgYm90aCBTZXVyYXQgKFIpIGFuZCBBbm5EYXRhIChQeXRob24pIG9iamVjdHMuCgojIFRyYWplY3RvcnkgaW5mZXJlbmNlCgpJbiB0aGlzIHNlY3Rpb24sIHdlIHVzZWQgdG8gVEkgdG9vbHMsICoqc2xpbmdzaG90KiogYW5kICoqVEluR2EqKi4KCiMjIFdpdGggc2xpbmdzaG90CgpGaXJzdCwgd2UgcnVuIHNsaW5nc2hvdCwgdGhlbiwgd2UgdmlzdWFsaXplIHJlc3VsdHMuCgojIyMgUnVuIG1ldGhvZAoKV2UgaW5mZXIgdHJhamVjdG9yaWVzIDoKCmBgYHtyIHJ1bl9zbGluZ3Nob3R9CnNsaW5nc2hvdF90cmFqID0gc2xpbmdzaG90OjpzbGluZ3Nob3QoZGF0YSA9IHNvYmpAcmVkdWN0aW9uc1tbInBjYSJdXUBjZWxsLmVtYmVkZGluZ3MpCnNsaW5nc2hvdF90cmFqCmBgYAoKU2xpbmdzaG90IG91dHB1dHMgMSBsaW5lYWdlLgoKIyMjIENvbXB1dGUgcHNldWRvdGltZQoKV2UgYWRkIHRoZSBwc2V1ZG90aW1lIGFzc29jaWF0ZWQgd2l0aCB0aGlzIGxpbmVhZ2UgaW4gdGhlIFNldXJhdCBvYmplY3QgOgoKYGBge3IgYWRkX3NsaW5nX3BkdH0Kc29iaiRwc2V1ZG90aW1lX3NsaW5nc2hvdCA9IHNsaW5nc2hvdDo6c2xpbmdQc2V1ZG90aW1lKHNsaW5nc2hvdF90cmFqKVssIDFdW2NvbG5hbWVzKHNvYmopXQoKaGVhZChzb2JqQG1ldGEuZGF0YSkKYGBgCgoKIyMjIFZpc3VhbGl6ZSByZXN1bHRzCgpXZSB2aXN1YWxpemUgcHNldWRvdGltZSA6CgpgYGB7ciBzZWVfc2xpbmdfcGR0LCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDV9ClNldXJhdDo6RmVhdHVyZVBsb3Qoc29iaiwgZmVhdHVyZXMgPSAicHNldWRvdGltZV9zbGluZ3Nob3QiLAogICAgICAgICAgICAgICAgICAgIHJlZHVjdGlvbiA9ICJ1bWFwIiwKICAgICAgICAgICAgICAgICAgICBjb2xzID0gdmlyaWRpczo6dmlyaWRpcyhuID0gMTAwKSkgKwogIGdncGxvdDI6OmdndGl0bGUoIlNsaW5nc2hvdCBwc2V1ZG90aW1lIikgKwogIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgU2V1cmF0OjpOb0F4ZXMoKQpgYGAKClRoZSByb290IGNvcnJlc3BvbmRzIHRvIHRoZSBjZWxscyB3aXRoIHBzZXVkb3RpbWUgMC4gV2UgY291bGQgaGF2ZSBzZXQgbWFudWFsbHkgYSByb290LiBXZSBydW4gdGhlIG1ldGhvZCB3aXRoIGFuIGFsdGVybmF0aXZlIHNldHRpbmcuCgojIyMgV2l0aCBhIHJvb3QgPwoKV2UgbWFudWFsbHkgc2V0IGEgcm9vdCB1c2luZyB0aGUgZm9sbG93aW5nIGNvbW1hbmQgOgoKYGBge3Igd2l0aF9yb290fQpzbGluZ3Nob3RfdHJhaiA9IHNsaW5nc2hvdDo6c2xpbmdzaG90KGRhdGEgPSBzb2JqQHJlZHVjdGlvbnNbWyJwY2EiXV1AY2VsbC5lbWJlZGRpbmdzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsdXN0ZXJMYWJlbHMgPSBzb2JqJGNsdXN0ZXJzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0LmNsdXMgPSAiRHVjdGFsIikKc2xpbmdzaG90X3RyYWoKYGBgCgpOb3csIHRoZXJlIGFyZSB0d28gbGluZWFnZXMuIFdlIGFkZCB0aGUgdHdvIGFzc29jaWF0ZWQgcHNldWRvdGltZXMgaW4gdGhlIG9iamVjdCA6CgpgYGB7ciBhZGRfcGR0c30Kc2xpbmdzaG90X3BkdCA9IHNsaW5nc2hvdDo6c2xpbmdQc2V1ZG90aW1lKHNsaW5nc2hvdF90cmFqKQpuX2xpbmVhZ2VzID0gbmNvbChzbGluZ3Nob3RfcGR0KQoKc29iakBtZXRhLmRhdGFbLCBwYXN0ZTAoInBzZXVkb3RpbWVfc2xpbmdzaG90XyIsIGMoMTpuX2xpbmVhZ2VzKSldID0gc2xpbmdzaG90OjpzbGluZ1BzZXVkb3RpbWUoc2xpbmdzaG90X3RyYWopW2NvbG5hbWVzKHNvYmopLCBdCgpoZWFkKHNvYmpAbWV0YS5kYXRhKQpgYGAKCgpXZSB2aXN1YWxpemUgcHNldWRvdGltZXMgb24gdGhlIFVNQVAgOgoKYGBge3Igc2VlX3BkdHNfc2xpbmcsIGZpZy53aWR0aCA9IDEyLCBmaWcuaGVpZ2h0ID0gNX0KcGxvdF9saXN0ID0gbGFwcGx5KHBhc3RlMCgicHNldWRvdGltZV9zbGluZ3Nob3RfIiwgYygxOm5fbGluZWFnZXMpKSwgRlVOID0gZnVuY3Rpb24ocGR0KSB7CiAgcCA9IFNldXJhdDo6RmVhdHVyZVBsb3Qoc29iaiwgZmVhdHVyZXMgPSBwZHQsCiAgICAgICAgICAgICAgICAgICAgcmVkdWN0aW9uID0gInVtYXAiLAogICAgICAgICAgICAgICAgICAgIGNvbHMgPSB2aXJpZGlzOjp2aXJpZGlzKG4gPSAxMDApKSArCiAgZ2dwbG90Mjo6Z2d0aXRsZShwZHQpICsKICBnZ3Bsb3QyOjp0aGVtZShwbG90LnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkgKwogIFNldXJhdDo6Tm9BeGVzKCkKICAKICByZXR1cm4ocCkKfSkKCnBhdGNod29yazo6d3JhcF9wbG90cyhwbG90X2xpc3QpCmBgYAoKQnkgc2V0dGluZyBhIHJvb3QgaW4gdGhlIER1Y3RhbCBwb3B1bGF0aW9uLCB0aGVyZSBhcmUgdHdvIGRpc3RpbmN0IGxpbmVhZ2VzIGVuZGluZyBpbiB0d28gcG9wdWxhdGlvbnMuIEFsbCBjZWxscyBhcmUgbm90IGluY2x1ZGVkIGluIGJvdGggbGluZWFnZXMuCgoKIyMgV2l0aCBUSW5HYQoKTm93LCB3ZSBydW4gVEluR2EsIHRoZW4sIHdlIHZpc3VhbGl6ZSByZXN1bHRzLgoKIyMjIFJ1biBtZXRob2QKCldlIG5vcm1hbGl6ZSB0aGUgY291bnQgbWF0cml4IDoKCmBgYHtyIG5vcm1hbGl6ZV9zb2JqfQpzb2JqID0gU2V1cmF0OjpOb3JtYWxpemVEYXRhKHNvYmopCnNvYmoKYGBgCgpXZSBidWlsZCB0aGUgaW5wdXQgZnJvbSB0aGUgU2V1cmF0IG9iamVjdCA6CgpgYGB7ciB0aW5nYV9pbnB1dH0KZXhwcmVzc2lvbiA9IFNldXJhdDo6R2V0QXNzYXlEYXRhKHNvYmosIGFzc2F5ID0gIlJOQSIsIHNsb3QgPSAiZGF0YSIpCmV4cHJlc3Npb24gPSBNYXRyaXg6OnQoZXhwcmVzc2lvbikKZXhwcmVzc2lvbiA9IGV4cHJlc3Npb25bLCBvcmRlcihjb2xuYW1lcyhleHByZXNzaW9uKSldCgpjb3VudHMgPSBTZXVyYXQ6OkdldEFzc2F5RGF0YShzb2JqLCBhc3NheSA9ICJSTkEiLCBzbG90ID0gImNvdW50cyIpCmNvdW50cyA9IGNvdW50c1tjb2xuYW1lcyhleHByZXNzaW9uKSwgXQpjb3VudHMgPSBNYXRyaXg6OnQoY291bnRzKQoKZGF0YXNldCA9IGR5bndyYXA6OndyYXBfZXhwcmVzc2lvbihpZCA9IHNvYmpAcHJvamVjdC5uYW1lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGV4cHJlc3Npb24gPSBleHByZXNzaW9uLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvdW50cyA9IGNvdW50cykKYGBgCgpXZSBhcHBseSB0aGUgVEkgbWV0aG9kIDoKCmBgYHtyIGluZmVyX3RyYWplY3Rvcnl9CnNldC5zZWVkKDEyMzQpCnRpbmdhX3RyYWogPSBkeW53cmFwOjppbmZlcl90cmFqZWN0b3J5KGRhdGFzZXQgPSBkYXRhc2V0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSBUSW5HYTo6Z25nX3BhcmFtMigpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gMTIzNCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyYW1zID0gbGlzdChtYXhfaXRlciA9IDEwMDAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heF9ub2RlcyA9IDEwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVwc2lsb25fYiA9IDAuMDUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZXBzaWxvbl9uID0gMC4wMDEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWdlX21heCA9IDIwMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsYW1iZGEgPSAyMDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxwaGEgPSAwLjUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmV0YSA9IDAuOTkpKQpgYGAKCiMjIyBDb21wdXRlIHBzZXVkb3RpbWUKClRvIGRlZmluZSBhIHBzZXVkb3RpbWUsIHdlIG5lZWQgdG8gc2V0IGEgcm9vdC4gV2UgY2hvb3NlIHJhbmRvbWx5IGEgY2VsbCBpbiB0aGUgRHVjdGFsIGNsdXN0ZXIgOgoKYGBge3IgZGVmaW5lX3Jvb3R9CnNldC5zZWVkKDEyMzQpCnJvb3RfaWQgPSBzYW1wbGUod2hpY2goc29iaiRjbHVzdGVycyA9PSAiRHVjdGFsIiksIHNpemUgPSAxKQpyb290X2JhcmNvZGUgPSBjb2xuYW1lcyhzb2JqKVtyb290X2lkXQpyb290X2JhcmNvZGUKYGBgCgpXZSB2aXN1YWxpemUgdGhlIHJvb3QgY2VsbCBvbiB0aGUgVU1BUCA6CgpgYGB7ciBzZWVfcm9vdCwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA1fQpzb2JqJGlzX3Jvb3QgPSAoY29sbmFtZXMoc29iaikgPT0gcm9vdF9iYXJjb2RlKQpzb2JqJGNlbGxfbmFtZSA9IGNvbG5hbWVzKHNvYmopCnJvb3RfY29vcmQgPSBzb2JqQHJlZHVjdGlvbnNbWyJ1bWFwIl1dQGNlbGwuZW1iZWRkaW5nc1tyb290X2JhcmNvZGUsIF0KCnJvb3RfcGxvdCA9IFNldXJhdDo6RGltUGxvdChzb2JqLCByZWR1Y3Rpb24gPSAidW1hcCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjZWxscy5oaWdobGlnaHQJPSByb290X2JhcmNvZGUsIHNpemVzLmhpZ2hsaWdodCA9IDMpICsKICBnZ3Bsb3QyOjphbm5vdGF0ZShnZW9tID0gInRleHQiLCBsYWJlbCA9IHJvb3RfYmFyY29kZSwKICAgICAgICAgICAgICAgICAgICB4ID0gcm9vdF9jb29yZFsxXSArIDAuMTUqYWJzKHJvb3RfY29vcmRbMV0pLAogICAgICAgICAgICAgICAgICAgIHkgPSByb290X2Nvb3JkWzJdICsgMC4xNSphYnMocm9vdF9jb29yZFsyXSkpICsKICBnZ3Bsb3QyOjpnZ3RpdGxlKCJSb290IGNlbGwiKSArCiAgZ2dwbG90Mjo6dGhlbWUocGxvdC50aXRsZSA9IGdncGxvdDI6OmVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSwgZmFjZSA9ICJib2xkIikpICsKICBTZXVyYXQ6Ok5vTGVnZW5kKCkgKyBTZXVyYXQ6Ok5vQXhlcygpCgpzb2JqJGlzX3Jvb3QgPSBOVUxMCnNvYmokY2VsbF9uYW1lID0gTlVMTAoKcm9vdF9wbG90CmBgYAoKV2UgYWRkIHRoZSBwc2V1ZG90aW1lLCBzdGFydGluZyBmcm9tIHRoaXMgcm9vdCBjZWxsIDoKCmBgYHtyIGFkZF9yb290fQp0aW5nYV90cmFqID0gZHlud3JhcDo6YWRkX3Jvb3QodHJhamVjdG9yeSA9IHRpbmdhX3RyYWosCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByb290X2NlbGxfaWQgPSByb290X2JhcmNvZGUpCnRpbmdhX3RyYWogPSBkeW53cmFwOjphZGRfcHNldWRvdGltZSh0cmFqZWN0b3J5ID0gdGluZ2FfdHJhaikKYGBgCgpXZSBhZGQgdGhlIHBzZXVkb3RpbWUgaW4gdGhlIFNldXJhdCBvYmplY3QgOgoKYGBge3IgYWRkX3BkdF90aW5nYX0Kc29iaiRwc2V1ZG90aW1lX3RpbmdhID0gdGluZ2FfdHJhaiRwc2V1ZG90aW1lCgpoZWFkKHNvYmpAbWV0YS5kYXRhKQpgYGAKCgojIyMgVmlzdWFsaXplIHJlc3VsdHMKCldlIHZpc3VhbGl6ZSBwc2V1ZG90aW1lIDoKCmBgYHtyIHNlZV90aW5nYV9wZHQsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gNX0KU2V1cmF0OjpGZWF0dXJlUGxvdChzb2JqLCBmZWF0dXJlcyA9ICJwc2V1ZG90aW1lX3RpbmdhIiwKICAgICAgICAgICAgICAgICAgICByZWR1Y3Rpb24gPSAidW1hcCIsCiAgICAgICAgICAgICAgICAgICAgY29scyA9IHZpcmlkaXM6OnZpcmlkaXMobiA9IDEwMCkpICsKICBnZ3Bsb3QyOjpnZ3RpdGxlKCJUSW5HYSBwc2V1ZG90aW1lIikgKwogIGdncGxvdDI6OnRoZW1lKHBsb3QudGl0bGUgPSBnZ3Bsb3QyOjplbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgU2V1cmF0OjpOb0F4ZXMoKQpgYGAKCldlIHZpc3VhbGl6ZSB0aGUgdHJhamVjdG9yeSB3aXRoIGxpbmVhZ2UgdHJlZSA6CgpgYGB7ciBzZWVfdHJhal9hcnJvd3MsIGZpZy53aWR0aCA9IDEwLCBmaWcuaGVpZ2h0ID0gNX0KZHlucGxvdDo6cGxvdF9kaW1yZWQodHJhamVjdG9yeSA9IHRpbmdhX3RyYWosCiAgICAgICAgICAgICAgICAgICAgIGxhYmVsX21pbGVzdG9uZXMgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgcGxvdF90cmFqZWN0b3J5ID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgZGltcmVkID0gc29ialtbInVtYXAiXV1AY2VsbC5lbWJlZGRpbmdzLAogICAgICAgICAgICAgICAgICAgICBjb2xvcl9jZWxscyA9ICdwc2V1ZG90aW1lJywKICAgICAgICAgICAgICAgICAgICAgY29sb3JfdHJhamVjdG9yeSA9ICJub25lIikKYGBgCgpUaGUgdHJhamVjdG9yeSBpcyBtYWlubHkgbGluZWFyLiBXZSBjb3VsZCBoYXZlIGNoYW5nZWQgdGhlIGlucHV0IHNldHRpbmdzIHRvIG1ha2UgYSBtb3JlIHJhbWlmaWVkIHRyYWplY3RvcnkuCgojIFJOQSB2ZWxvY2l0eQoKSW4gdGhpcyBzZWN0aW9uLCB3ZSBydW4gUk5BIHZlbG9jaXR5IG1ldGhvZCB1c2luZyBzY3ZlbG8gaW1wbGVtZW50YXRpb24uCgojIyMgVmlzdWFsaXphdGlvbgoKV2UgdmlzdWFsaXplIHRoZSBudW1iZXIgb2YgY291bnQgZm9yIHNwbGljZWQgYW5kIHVuc3BsaWNlZCB0cmFuc2NyaXB0cyA6CgpgYGB7cHl0aG9uIHNlZV9TcnJtMiwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA1fQphZGF0YS5vYnNbJ3RvdGFsX3NwbGljZWQnXSA9IGFkYXRhLmxheWVyc1sic3BsaWNlZCJdLnN1bShheGlzID0gMSkKYWRhdGEub2JzWyd0b3RhbF91bnNwbGljZWQnXSA9IGFkYXRhLmxheWVyc1sidW5zcGxpY2VkIl0uc3VtKGF4aXMgPSAxKQoKIyBQbG90CmZpZywgYXhzID0gcGx0LnN1YnBsb3RzKDEsIDIpCgpheCA9IGF4c1swXQpzY3YucGwuc2NhdHRlcihhZGF0YSwgYmFzaXMgPSAidW1hcCIsIGNvbG9yID0gInRvdGFsX3NwbGljZWQiLApsZWdlbmRfbG9jID0gJ3JpZ2h0Jywgc2l6ZSA9IDYwLCBheCA9IGF4LCBzaG93ID0gRmFsc2UpCmF4LnNldF9hc3BlY3QoJ2VxdWFsJykKCmF4ID0gYXhzWzFdCnNjdi5wbC5zY2F0dGVyKGFkYXRhLCBiYXNpcyA9ICJ1bWFwIiwgY29sb3IgPSAidG90YWxfdW5zcGxpY2VkIiwKbGVnZW5kX2xvYyA9ICdyaWdodCcsIHNpemUgPSA2MCwgYXggPSBheCwgc2hvdyA9IEZhbHNlKQpheC5zZXRfYXNwZWN0KCdlcXVhbCcpCgpwbHQuc3VicGxvdHNfYWRqdXN0KGxlZnQgPSAwLjA1LCByaWdodCA9IDAuOSwgdG9wID0gMC45OSwgYm90dG9tID0gMC4wNSkKcGx0LnNob3coKQpgYGAKCldoYXQgYXJlIHRoZSBwcm9wb3J0aW9ucyBvZiB1bnNwbGljZWQgY29tcGFyZWQgdG8gc3BsaWNlZCB0cmFuc2NyaXB0cyA/CgpgYGB7cHl0aG9uIHByb3BvcnRpb25zLCBmaWcud2lkdGggPSAxMiwgZmlnLmhlaWdodCA9IDR9CnNjdi5wbC5wcm9wb3J0aW9ucyhhZGF0YSkKYGBgCgoKIyMjIFJ1biBSTkEgdmVsb2NpdHkKCldlIHJ1biBlYWNoIHN0ZXAgb2YgUk5BIHZlbG9jaXR5IDoKCmBgYHtweXRob24gdmVsb30Kc2N2LnBwLmZpbHRlcl9nZW5lcyhhZGF0YSkKc2N2LnBwLm1vbWVudHMoYWRhdGEpCnNjdi50bC5yZWNvdmVyX2R5bmFtaWNzKGFkYXRhLCBuX2pvYnMgPSAxMikKc2N2LnRsLnZlbG9jaXR5KGFkYXRhLCBtb2RlID0gJ2R5bmFtaWNhbCcpCnNjdi50bC52ZWxvY2l0eV9ncmFwaChhZGF0YSwgbl9qb2JzID0gMTIpCgphZGF0YQpgYGAKCiMjIyBWaXN1YWxpemUgcmVzdWx0cwoKV2UgdmlzdWFsaXplIHRoZSB2ZWN0b3IgZmllbGRzIDoKCmBgYHtweXRob24gdmVjdG9yX2ZpZWxkcywgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA0fQphZGF0YS5vYnNbJ25vX2NvbG9yJ10gPSAwCgpmaWcsIGF4cyA9IHBsdC5zdWJwbG90cygxLCAyKQoKYXggPSBheHNbMF0Kc2N2LnBsLnZlbG9jaXR5X2VtYmVkZGluZ19zdHJlYW0oYWRhdGEsIGJhc2lzID0gJ3VtYXAnLCBjb2xvciA9ICdub19jb2xvcicsIHNob3cgPSBGYWxzZSwgYXggPSBheCwgbGVnZW5kX2xvYyA9ICdub25lJykKYXguc2V0X3RpdGxlKCdWZWN0b3IgZmllbGRzJykKYXguc2V0X2FzcGVjdCgnZXF1YWwnKQoKYXggPSBheHNbMV0Kc2N2LnBsLnZlbG9jaXR5X2VtYmVkZGluZ19zdHJlYW0oYWRhdGEsIGJhc2lzID0gJ3VtYXAnLCBjb2xvciA9ICdjbHVzdGVycycsIHNob3cgPSBGYWxzZSwgYXggPSBheCkKYXguc2V0X3RpdGxlKCdWZWN0b3IgZmllbGRzIGFuZCBjbHVzdGVyIGFubm90YXRpb24nKQpheC5zZXRfYXNwZWN0KCdlcXVhbCcpCgpwbHQuc3VicGxvdHNfYWRqdXN0KGxlZnQgPSAwLjA1LCByaWdodCA9IDAuOSwgdG9wID0gMC45OSwgYm90dG9tID0gMC4wNSkKcGx0LnNob3coKQpgYGAKCkEgdmVjdG9yIGNvcnJlc3BvbmRzIHRvIG9uZSBhcnJvdywgYW4gb3JpZW50ZWQgc2VnbWVudC4gSXQgaW5kaWNhdGVzIHRoZSBtb3N0IHByb2JhYmxlIG5leHQgc3RhZ2Ugb2YgY2VsbHMuIEVhcmxpZXN0IGNlbGxzIGFyZSBub3QgcG9pbnRlZCBieSBhbnkgdmVjdG9yLiBGcm9tIHRoaXMgZmlndXJlLCByb290IHBvcHVsYXRpb24gY29ycmVzcG9uZHMgdG8gRHVjdGFsIGNsdXN0ZXIuIFRoZXJlIGFyZSB0aHJlZSBwb3NzaWJsZSBlbmRzIDogYWxwaGEsIGJldGEgYW5kIGVwc2lsb24uCgoKIyMjIFBzZXVkb3RpbWUgYW5kIGxhdGVudCB0aW1lCgpXZSBpbmZlciBhIHJvb3Qgc2V0IGFuZCBjb21wdXRlIGEgcHNldWRvdGltZSwgZnJvbSB0aGUgZGlyZWN0ZWQgdmVsb2NpdHkgZ3JhcGggOgoKYGBge3B5dGhvbiB2ZWxvY2l0eV9wc2V1ZG90aW1lLCBmaWcud2lkdGggPSAxMCwgZmlnLmhlaWdodCA9IDV9CnNjdi50bC52ZWxvY2l0eV9wc2V1ZG90aW1lKGFkYXRhKQpzY3YucGwuc2NhdHRlcihhZGF0YSwgY29sb3IgPSAndmVsb2NpdHlfcHNldWRvdGltZScsIGNtYXAgPSAnZ251cGxvdCcpCmBgYAoKV2UgY29tcHV0ZSB0aGUgbGF0ZW50IHRpbWUgZm9yIGVhY2ggY2VsbCwgYmFzZWQgb25seSBvbiBpdHMgdHJhbnNjcmlwdGlvbmFsIGR5bmFtaWNzLgoKYGBge3B5dGhvbiB2ZWxvY2l0eV9sYXRlbnRfdGltZSwgZmlnLndpZHRoID0gMTAsIGZpZy5oZWlnaHQgPSA1fQpzY3YudGwubGF0ZW50X3RpbWUoYWRhdGEpCnNjdi5wbC5zY2F0dGVyKGFkYXRhLCBjb2xvciA9ICdsYXRlbnRfdGltZScsIGNvbG9yX21hcCA9ICdnbnVwbG90JykKYGBgCgpMYXRlbnQgdGltZSBpcyBjb2hlcmVudCB3aXRoIHBzZXVkb3RpbWUuIEhvd2V2ZXIsIHBzZXVkb3RpbWUgaXMgaG9tb2dlbmVvdXMgc3ByZWFkIGFsb25nIGNlbGxzIHdoaWxlIGxhdGVudCB0aW1lIG1vcmUgY29ycmVzcG9uZHMgdG8gYSBjZWxsJ3MgaW50ZXJuYWwgY2xvY2sgOgoKYGBge3B5dGhvbiBsYXRlbnRfcHNldWRvdGltZSwgZmlnLndpZHRoID0gNSwgZmlnLmhlaWdodCA9IDV9CmZpZywgYXhzID0gcGx0LnN1YnBsb3RzKDEsIDEpCgpwbHQuc2NhdHRlcih4ID0gW3QgZm9yIHQgaW4gYWRhdGEub2JzWyJ2ZWxvY2l0eV9wc2V1ZG90aW1lIl1dLAp5ID0gW3QgZm9yIHQgaW4gYWRhdGEub2JzWyJsYXRlbnRfdGltZSJdXSwKcyA9IDEwKQpheHMucGxvdChbMCwgMV0sIFswLCAxXSwgY29sb3IgPSAncicsIGxhYmVsID0gJ3kgPSB4JykgCnBsdC54bGFiZWwoJ1BzZXVkb3RpbWUnKQpwbHQueWxhYmVsKCdMYXRlbnQgdGltZScpCnBsdC50aXRsZSgnUmVsYXRpb24gYmV0d2VlbiB2ZWxvY2l0eSBwc2V1ZG90aW1lIGFuZCBsYXRlbnQgdGltZScpCnBsdC5sZWdlbmQoKQpwbHQuc2hvdygpCmBgYAoKIyBTYXZlCgpXZSBzYXZlIGJvdGggb2JqZWN0cyA6CgpgYGB7cHl0aG9uIHNhdmVfZGF0YX0KYWRhdGEud3JpdGUoJy4vcGFuY3JlYXMvYWRhdGFfdmVsb2NpdHkuaDVhZCcsIGNvbXByZXNzaW9uID0gJ2d6aXAnKQpgYGAKClRvIGxvYWQgaXQsIHVzZSA6IGBhZGF0YSA9IHNjYW5weS5yZWFkX2g1YWQoJy4vcGFuY3JlYXMvYWRhdGFfdmVsb2NpdHkuaDVhZCcpYC4KCmBgYHtyIHNhdmVfcmRzfQpzYXZlUkRTKHNvYmosIGZpbGUgPSAnLi9wYW5jcmVhcy9zb2JqX3RyYWoucmRzJykKYGBgCgpUbyBsb2FkIGl0LCB1c2UgOiBgc29iaiA9IHJlYWRSRFMoJy4vc29ial90cmFqLnJkcycpYC4KCgojIENvbmNsdXNpb24KCiogVHdvIG1haW4gbWV0aG9kcyB0byBpbmZlciBwcm9ncmVzc2lvbiBvcmRlciBmcm9tIHNjUk5BLVNlcSA6ICoqdHJhamVjdG9yeSBpbmZlcmVuY2UqKiAoVEkpIGFuZCAqKlJOQSB2ZWxvY2l0eSoqCiogVEkgaXMgYmFzZWQgb24gZ2VuZSBleHByZXNzaW9uIHdoaWxlIFJOQSB2ZWxvY2l0eSB0YWtlcyBpbnRvIGFjY291bnQgdGhlIHNwbGljaW5nIHN0YXR1cyBvZiBlYWNoIFJOQQo8YnI+CiogQm90aCBtZXRob2RzIG91dHB1dCAiYXJyb3dzIiA6CiAgLSBUSSA6IGxpbmVhZ2UgdHJlZQogIC0gUk5BIHZlbG9jaXR5IDogdmVjdG9yIGZpZWxkCjxicj4KKiBCb3RoIG1ldGhvZHMgb3V0cHV0ICJ2YWx1ZXMiLCB0aGF0IGNhbiBiZSBpbnRlcnByZXRhdGVkIGFzIGEgY29udGludW91cyBjbHVzdGVyaW5nIDoKICAtIFRJIDogKipwc2V1ZG90aW1lKiosIGNvcnJlc3BvbmRpbmcgdG8gYSBkaXN0YW5jZSBiZXR3ZWVuIG9uZSBjZWxsIGFuZCB0aGUgcm9vdCwgYnV0IHRoaXMgZGlzdGFuY2UgaXMgbm90IG5lY2Vzc2FyaWx5IGFzc29jaWF0ZWQgd2l0aCBhIHRpbWUKICAtIFJOQSB2ZWxvY2l0eSA6IGEgKipsYXRlbnQgdGltZSoqLCBjb3JyZXNwb25kaW5nIHRvIGEgY2VsbCBzdGF0ZSwgYW5kIGNhbiBiZSBpbnRlcnByZXRlZCBhcyBhIGJpb2xvZ2ljYWwgdGltZQo8YnI+CiogQSAqKmxvdCBvZiBUSSB0b29scyoqIDogZmluZCB0aGUgbWV0aG9kIHN1aXRlZCB0byB5b3VyIGRhdGEsIGJhc2VkIG9uIHRoZSBiaW9sb2dpY2FsIGtub3dsZWRnZQoqIEEgVEkgdG9vbCB3aWxsIGFsd2F5cyBvdXRwdXQgYSByZXN1bHQgOiBpdCBkb2Vzbid0IG1lYW4gdGhpcyBpcyBiaW9sb2dpY2FsbHkgdHJ1ZQoKIyBSZWZlcmVuY2VzCgpIZXJlIGFyZSB0aGUgcmVmZXJlbmNlcyBmb3IgdGhlIHRvb2xzIHVzZWQgOgoKKiB2ZWxvY3l0byAob3JpZ2luYWwgUk5BIFZlbG9jaXR5IG1vZHVsZSkgOgpbTGEgTWFubm8gZXQgYWwuICgyMDE4KSwgUk5BIHZlbG9jaXR5IG9mIHNpbmdsZSBjZWxsc10oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMzgvczQxNTg2LTAxOC0wNDE0LTYpCiogc2N2ZWxvIChSTkEgdmVsb2NpdHkpIDoKW0JlcmdlbiBldCBhbC4gKDIwMjApLCBHZW5lcmFsaXppbmcgUk5BIHZlbG9jaXR5IHRvIHRyYW5zaWVudCBjZWxsIHN0YXRlcyB0aHJvdWdoIGR5bmFtaWNhbCBtb2RlbGluZ10oaHR0cHM6Ly9kb2kub3JnLzEwLjEwMzgvczQxNTg3LTAyMC0wNTkxLTMpIGFuZCBbdHV0b3JpYWxzXShodHRwczovL3NjdmVsby5yZWFkdGhlZG9jcy5pby9lbi9zdGFibGUvKQoqIHNsaW5nc2hvdCAodHJhamVjdG9yeSBpbmZlcmVuY2UpIDoKW1N0cmVldCBldCBhbC4gKDIwMTgpLCBTbGluZ3Nob3Q6IGNlbGwgbGluZWFnZSBhbmQgcHNldWRvdGltZSBpbmZlcmVuY2UgZm9yIHNpbmdsZS1jZWxsIHRyYW5zY3JpcHRvbWljc10oaHR0cHM6Ly9kb2kub3JnLzEwLjExODYvczEyODY0LTAxOC00NzcyLTApCiogVEluR2EgKHRyYWplY3RvcnkgaW5mZXJlbmNlKSA6CltUb2Rvcm92IGV0IGFsLiAoMjAyMCksIFRpbkdhOiBmYXN0IGFuZCBmbGV4aWJsZSB0cmFqZWN0b3J5IGluZmVyZW5jZSB3aXRoIEdyb3dpbmcgTmV1cmFsIEdhc10oaHR0cHM6Ly9kb2kub3JnLzEwLjEwOTMvYmlvaW5mb3JtYXRpY3MvYnRhYTQ2MykKCgojIEFuZCBhZnRlciA/CgpUaGlzIGNhbiBiZSBkb25lIGFmdGVyIHRyYWplY3RvcnkgaW5mZXJlbmNlIG9yIFJOQSB2ZWxvY2l0eSA6CgoqIHBzZXVkb3RpbWUgLyBsYXRlbnQgdGltZSBkaXNjcmV0aXphdGlvbiB0byBncm91cCBjZWxscyBpbnRvICpuKiBjbHVzdGVycywgdGhlbiBkaWZmZXJlbnRpYWwgZXhwcmVzc2lvbiBiZXR3ZWVuIGdyb3VwcyBvZiBjZWxscyBpbiBzaW1pbGFyIHN0YXRlCiogYXBwbGljYXRpb24gb2YgKipbZHluZmVhdHVyZV0oaHR0cHM6Ly9naXRodWIuY29tL2R5bnZlcnNlL2R5bmZlYXR1cmUpKiogcGFja2FnZSB0byBpZGVudGlmeSBpbXBvcnRhbnQgZ2VuZXMgYWxvbmcgcHNldWRvdGltZSAvIGxhdGVudCB0aW1lCiogaGVhdG1hcCBzaG93aW5nIGdlbmUgZXhwcmVzc2lvbiBjaGFuZ2VzIGFsb25nIHBzZXVkb3RpbWUgb3IgbGF0ZW50IHRpbWUKKiAuLi4KCiMgU2Vzc2lvbgoKUiBzZXNzaW9uIGluZm9ybWF0aW9uIDoKCmBgYHtyIHNlc3Npb25pbmZvX3IsIGVjaG8gPSBGQUxTRSwgZm9sZF9vdXRwdXQgPSBUUlVFfQpzZXNzaW9uSW5mbygpCmBgYAoKUHl0aG9uIHNlc3Npb24gaW5mb3JtYXRpb24gOgoKYGBge3B5dGhvbiBzZXNzaW9uaW5mb19weXRob24sIGVjaG8gPSBGQUxTRSwgZm9sZF9vdXRwdXQgPSBUUlVFfQppbXBvcnQgc2Vzc2lvbl9pbmZvCgpzZXNzaW9uX2luZm8uc2hvdygpCmBgYAo=