diff --git a/.github/workflows/run_hello-clusters.yml b/.github/workflows/run_hello-clusters.yml index 100c4c07b..33289df4f 100644 --- a/.github/workflows/run_hello-clusters.yml +++ b/.github/workflows/run_hello-clusters.yml @@ -33,28 +33,23 @@ jobs: run-module: if: github.repository_owner == 'AlexsLemonade' runs-on: ubuntu-latest + container: public.ecr.aws/openscpca/hello-clusters:latest + defaults: + run: + shell: bash -el {0} steps: - name: Checkout repo uses: actions/checkout@v4 - - name: Set up R - uses: r-lib/actions/setup-r@v2 - with: - r-version: 4.4.0 - use-public-rspm: true - - name: Set up pandoc uses: r-lib/actions/setup-pandoc@v2 - - name: Install additional system dependencies + - name: Install aws-cli run: | - sudo apt-get install -y libcurl4-openssl-dev libglpk40 - - - name: Set up renv - uses: r-lib/actions/setup-renv@v2 - with: - working-directory: ${{ env.MODULE_PATH }} + curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" + unzip awscliv2.zip + ./aws/install - name: Download test data run: ./download-data.py --test-data --format SCE --samples SCPCS000001 diff --git a/analyses/hello-clusters/01_perform-evaluate-clustering.Rmd b/analyses/hello-clusters/01_perform-evaluate-clustering.Rmd index 80268a302..adaa3d0e9 100644 --- a/analyses/hello-clusters/01_perform-evaluate-clustering.Rmd +++ b/analyses/hello-clusters/01_perform-evaluate-clustering.Rmd @@ -50,7 +50,7 @@ theme_set(theme_bw()) ```{r base paths} # The base path for the OpenScPCA repository -repository_base <- rprojroot::find_root(rprojroot::is_git_root) +repository_base <- rprojroot::find_root(rprojroot::has_dir(".github")) # The current data directory, found within the repository base directory data_dir <- file.path(repository_base, "data", "current") diff --git a/analyses/hello-clusters/01_perform-evaluate-clustering.nb.html b/analyses/hello-clusters/01_perform-evaluate-clustering.nb.html index bc5d1fd95..a58a5d64b 100644 --- a/analyses/hello-clusters/01_perform-evaluate-clustering.nb.html +++ b/analyses/hello-clusters/01_perform-evaluate-clustering.nb.html @@ -11,7 +11,7 @@ - + Performing graph-based clustering with rOpenScPCA @@ -2901,7 +2901,7 @@

Performing graph-based clustering with rOpenScPCA

Data Lab

-

2024-12-20

+

2025-01-13

@@ -2967,9 +2967,9 @@

Packages

Paths

- +
# The base path for the OpenScPCA repository
-repository_base <- rprojroot::find_root(rprojroot::is_git_root)
+repository_base <- rprojroot::find_root(rprojroot::has_dir(".github"))
 
 # The current data directory, found within the repository base directory
 data_dir <- file.path(repository_base, "data", "current")
@@ -3061,7 +3061,7 @@ 

Clustering with default parameters

@@ -3174,7 +3174,7 @@

Silhouette width

@@ -3190,7 +3190,7 @@

Silhouette width

labs(x = "Cluster", y = "Silhouette width")
-

+

@@ -3227,7 +3227,7 @@

Neighborhood purity

@@ -3243,7 +3243,7 @@

Neighborhood purity

labs(x = "Cluster", y = "Neighborhood purity") -

+

@@ -3285,7 +3285,7 @@

Cluster stability

@@ -3301,7 +3301,7 @@

Cluster stability

labs(x = "Adjusted rand index across bootstrap replicates") -

+

@@ -3359,7 +3359,7 @@

Working with objects directly

@@ -3404,31 +3404,24 @@

Evaluating Seurat clusters

FindNeighbors() |> FindClusters() - -
Warning in irlba(A = t(x = object), nv = npcs, ...): You're computing too large
-a percentage of total singular values, use a standard svd instead.
- - -
Warning: Number of dimensions changing from 10 to 50
- - +
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck
 
-Number of nodes: 100
-Number of edges: 4142
+Number of nodes: 2623
+Number of edges: 78853
 
 Running Louvain algorithm...
-Maximum modularity in 10 random starts: 0.2147
-Number of communities: 2
+Maximum modularity in 10 random starts: 0.8478
+Number of communities: 13
 Elapsed time: 0 seconds
seurat_obj
- +
An object of class Seurat 
-126242 features across 100 samples within 3 assays 
-Active assay: SCT (5604 features, 3000 variable features)
+145743 features across 2623 samples within 3 assays 
+Active assay: SCT (25105 features, 3000 variable features)
  3 layers present: counts, data, scale.data
  2 other assays present: RNA, spliced
  2 dimensional reductions calculated: pca, umap
@@ -3451,7 +3444,7 @@

Evaluating Seurat clusters

@@ -3543,7 +3536,7 @@

Evaluating ScPCA clusters

@@ -3776,7 +3769,7 @@

Session Info

-
LS0tCnRpdGxlOiAiUGVyZm9ybWluZyBncmFwaC1iYXNlZCBjbHVzdGVyaW5nIHdpdGggck9wZW5TY1BDQSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgphdXRob3I6ICJEYXRhIExhYiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KCiMjIEludHJvZHVjdGlvbgoKVGhpcyBub3RlYm9vayBwcm92aWRlcyBleGFtcGxlcyBvZiBob3cgdG8gdXNlIGZ1bmN0aW9ucyBpbiBgck9wZW5TY1BDQWAgdGhhdDoKCiogUGVyZm9ybSBjbHVzdGVyaW5nCiogQ2FsY3VsYXRlIFFDIG1ldHJpY3Mgb24gY2x1c3RlcnMsIGluY2x1ZGluZzoKICAqIFNpbGhvdWV0dGUgd2lkdGgKICAqIE5laWdoYm9yaG9vZCBwdXJpdHkKICAqIENsdXN0ZXIgc3RhYmlsaXR5LCBhcyBtZWFzdXJlZCB3aXRoIHRoZSBBZGp1c3RlZCBSYW5kIEluZGV4CiogQ2FsY3VsYXRlIFFDIG1ldHJpY3Mgb24gY2x1c3RlcnMgb2J0YWluZWQgd2l0aCBvdGhlciB0b29scywgc3VjaCBhcyBgU2V1cmF0YAoqIFNhdmUgY2x1c3RlcmluZyByZXN1bHRzIHRvIGFuIFNDRSBvciBgU2V1cmF0YAoKV2hpbGUgdGhpcyBub3RlYm9vayBkZW1vbnN0cmF0ZXMgaG93IHRvIHVzZSBpbmRpdmlkdWFsIGZ1bmN0aW9ucyB0aGF0IGNhbGN1bGF0ZSBoZWxwZnVsIG1ldHJpY3MgZm9yIGV2YWx1YXRpbmcgY2x1c3RlcmluZyByZXN1bHRzLCBhIGZ1bGwgZXZhbHVhdGlvbiB3b3VsZCBjb21wYXJlIHRoZXNlIG1ldHJpY3MgYWNyb3NzIGRpZmZlcmVudCBjbHVzdGVyaW5ncyBmcm9tIGRpZmZlcmVudCBwYXJhbWV0ZXJpemF0aW9ucy4KClRoaXMgbm90ZWJvb2sgd2lsbCB1c2UgdGhlIHNhbXBsZSBgU0NQQ1MwMDAwMDFgIGZyb20gcHJvamVjdCBgU0NQQ1AwMDAwMDFgLCB3aGljaCBpcyBhc3N1bWVkIHByZXNlbnQgaW4gdGhlIGBPcGVuU2NQQ0EtYW5hbHlzaXMvZGF0YS9jdXJyZW50L1NDUENQMDAwMDAxYCBkaXJlY3RvcnksIGZvciBhbGwgZXhhbXBsZXMuClBsZWFzZSBbc2VlIHRoaXMgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9vcGVuc2NwY2EucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0L2dldHRpbmctc3RhcnRlZC9hY2Nlc3NpbmctcmVzb3VyY2VzL2dldHRpbmctYWNjZXNzLXRvLWRhdGEvKSBmb3IgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCBvYnRhaW5pbmcgU2NQQ0EgZGF0YS4KCiMjIFNldHVwCgojIyMgUGFja2FnZXMKCgpgYGB7ciBwYWNrYWdlc30KbGlicmFyeShyT3BlblNjUENBKQoKc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsKICBsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQogIGxpYnJhcnkoU2V1cmF0KQogIGxpYnJhcnkoZHBseXIpCiAgbGlicmFyeShnZ3Bsb3QyKQp9KQoKIyBTZXQgZ2dwbG90IHRoZW1lIGZvciBwbG90cwp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgoKIyMjIFBhdGhzCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19naXRfcm9vdCkKCiMgVGhlIGN1cnJlbnQgZGF0YSBkaXJlY3RvcnksIGZvdW5kIHdpdGhpbiB0aGUgcmVwb3NpdG9yeSBiYXNlIGRpcmVjdG9yeQpkYXRhX2RpciA8LSBmaWxlLnBhdGgocmVwb3NpdG9yeV9iYXNlLCAiZGF0YSIsICJjdXJyZW50IikKCiMgVGhlIHBhdGggdG8gdGhpcyBtb2R1bGUKbW9kdWxlX2Jhc2UgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImFuYWx5c2VzIiwgImhlbGxvLWNsdXN0ZXJzIikKYGBgCgpgYGB7ciBpbnB1dCBmaWxlIHBhdGh9CiMgUGF0aCB0byBwcm9jZXNzZWQgU0NFIGZpbGUgZm9yIHNhbXBsZSBTQ1BDUzAwMDAwMQppbnB1dF9zY2VfZmlsZSA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJTQ1BDUDAwMDAwMSIsICJTQ1BDUzAwMDAwMSIsICJTQ1BDTDAwMDAwMV9wcm9jZXNzZWQucmRzIikKYGBgCgoKIyMjIFNldCB0aGUgcmFuZG9tIHNlZWQKCkJlY2F1c2UgY2x1c3RlcmluZyBpbnZvbHZlcyByYW5kb20gc2FtcGxpbmcsIGl0IGlzIGltcG9ydGFudCB0byBzZXQgdGhlIHJhbmRvbSBzZWVkIGF0IHRoZSB0b3Agb2YgeW91ciBhbmFseXNpcyBzY3JpcHQgb3Igbm90ZWJvb2sgdG8gZW5zdXJlIHJlcHJvZHVjaWJpbGl0eS4KCmBgYHtyIHNldCBzZWVkfQpzZXQuc2VlZCgyMDI0KQpgYGAKCiMjIFJlYWQgaW4gYW5kIHByZXBhcmUgZGF0YQoKVG8gYmVnaW4sIHdlJ2xsIHJlYWQgaW4gdGhlIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgKFNDRSkgb2JqZWN0LgoKYGBge3IgcmVhZCBkYXRhfQojIFJlYWQgdGhlIFNDRSBmaWxlCnNjZSA8LSByZWFkUkRTKGlucHV0X3NjZV9maWxlKQpgYGAKCkZvciB0aGUgaW5pdGlhbCBjbHVzdGVyIGNhbGN1bGF0aW9ucyBhbmQgZXZhbHVhdGlvbnMsIHdlIHdpbGwgdXNlIHRoZSBQQ0EgbWF0cml4IGV4dHJhY3RlZCBmcm9tIHRoZSBTQ0Ugb2JqZWN0LgpJdCdzIGFsc28gcG9zc2libGUgdG8gdXNlIGFuIFNDRSBvYmplY3Qgb3IgYSBTZXVyYXQgb2JqZWN0IGRpcmVjdGx5LCB3aGljaCB3ZSB3aWxsIGRlbW9uc3RyYXRlIGxhdGVyLgoKCmBgYHtyIGV4dHJhY3QgcGNhIGRhdGF9CiMgRXh0cmFjdCB0aGUgUENBIG1hdHJpeCBmcm9tIGFuIFNDRSBvYmplY3QKcGNhX21hdHJpeCA8LSByZWR1Y2VkRGltKHNjZSwgIlBDQSIpCmBgYAoKIyMgUGVyZm9ybSBjbHVzdGVyaW5nCgpUaGlzIHNlY3Rpb24gd2lsbCBzaG93IGhvdyB0byBwZXJmb3JtIGNsdXN0ZXJpbmcgd2l0aCB0aGUgZnVuY3Rpb24gYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9jbHVzdGVycygpYC4KClRoaXMgZnVuY3Rpb24gdGFrZXMgYSBQQ0EgbWF0cml4IHdpdGggcm93bmFtZXMgcmVwcmVzZW50aW5nIHVuaXF1ZSBjZWxsIGlkcyAoZS5nLiwgYmFyY29kZXMpIGFzIGl0cyBwcmltYXJ5IGFyZ3VtZW50LgpCeSBkZWZhdWx0IGl0IHdpbGwgY2FsY3VsYXRlIGNsdXN0ZXJzIHVzaW5nIHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVyczoKCiogTG91dmFpbiBhbGdvcml0aG0KKiBKYWNjYXJkIHdlaWdodGluZwoqIDEwIG5lYXJlc3QgbmVpZ2hib3JzCiogQSByZXNvbHV0aW9uIHBhcmFtZXRlciBvZiAxCgpUaGlzIGZ1bmN0aW9uIHdpbGwgcmV0dXJuIGEgdGFibGUgd2l0aCB0aGUgZm9sbG93aW5nIGNvbHVtbnM6CgoqIGBjZWxsX2lkYDogVW5pcXVlIGNlbGwgaWRlbnRpZmllcnMsIG9idGFpbmVkIGZyb20gdGhlIFBDQSBtYXRyaXgncyByb3cgbmFtZXMKKiBgY2x1c3RlcmA6IEEgZmFjdG9yIGNvbHVtbiB3aXRoIHRoZSBjbHVzdGVyIGlkZW50aXRpZXMKKiBUaGVyZSB3aWxsIGJlIG9uZSBjb2x1bW4gZm9yIGVhY2ggY2x1c3RlcmluZyBwYXJhbWV0ZXIgdXNlZAoKCiMjIyBDbHVzdGVyaW5nIHdpdGggZGVmYXVsdCBwYXJhbWV0ZXJzCgpgYGB7ciBjbHVzdGVyIHNjZX0KIyBDYWxjdWxhdGUgY2x1c3RlcnMgd2l0aCBkZWZhdWx0IHBhcmFtZXRlcnMKY2x1c3Rlcl9yZXN1bHRzX2RmIDwtIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9jbHVzdGVycyhwY2FfbWF0cml4KQoKIyBQcmludCB0aGUgZmlyc3Qgcm93cyBvZiB0aGUgcmVzdWx0aW5nIHRhYmxlCmhlYWQoY2x1c3Rlcl9yZXN1bHRzX2RmKQpgYGAKCiMjIyBDbHVzdGVyaW5nIHdpdGggbm9uLWRlZmF1bHQgcGFyYW1ldGVycwoKUGFyYW1ldGVycyB1c2VkIGZvciBjbHVzdGVyaW5nIGNhbiBiZSBjdXN0b21pemVkIHdpdGggdGhlc2UgYXJndW1lbnRzOgoKKiBUaGUgYGFsZ29yaXRobWAgY2FuIGJlIG9uZSBvZjoKICAqIGBsb3V2YWluYCwgYHdhbGt0cmFwYCwgb3IgYGxlaWRlbmAKKiBUaGUgYHdlaWdodGluZ2AgY2FuIGJlIG9uZSBvZjoKICAqIGBqYWNjYXJkYCwgYHJhbmtgLCBvciBgbnVtYmVyYAoqIFRoZSBuZWFyZXN0IG5laWdoYm9ycyBwYXJhbWV0ZXIgY2FuIGJlIGN1c3RvbWl6ZWQgd2l0aCB0aGUgYG5uYCBhcmd1bWVudAoqIFRoZSByZXNvbHV0aW9uIHBhcmFtZXRlciBjYW4gYmUgY3VzdG9taXplZCB3aXRoIHRoZSBgcmVzb2x1dGlvbmAgYXJndW1lbnQKICAqIFRoaXMgcGFyYW1ldGVyIGlzIG9ubHkgdXNlZCBieSBMb3V2YWluIGFuZCBMZWlkZW4gYWxnb3JpdGhtcwoqIElmIHRoZSBMZWlkZW4gYWxnb3JpdGhtIGlzIHVzZWQsIGl0cyBkZWZhdWx0IG9iamVjdGl2ZSBmdW5jdGlvbiBwYXJhbWV0ZXIgd2lsbCBiZSBgQ1BNYCwgYnV0IHlvdSBjYW4gYWxzbyBzZXQgIGBvYmplY3RpdmVfZnVuY3Rpb24gPSAibW9kdWxhcml0eSJgIGluc3RlYWQuCiogWW91IGNhbiBwcm92aWRlIGFkZGl0aW9uYWwgcGFyYW1ldGVycyBhcyBhIGxpc3QgdG8gdGhlIGBjbHVzdGVyX2FyZ3NgIGFyZ3VtZW50LgogICogUGxlYXNlIHJlZmVyIHRvIHRoZSBbYGlncmFwaGAgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9pZ3JhcGgub3JnL3IvaHRtbC9sYXRlc3QpIHRvIGxlYXJuIG1vcmUgYWJvdXQgd2hhdCBhZGRpdGlvbmFsIHBhcmFtZXRlcnMgY2FuIGJlIHByb3ZpZGVkIHRvIGVhY2ggY2x1c3RlcmluZyBhbGdvcml0aG0uCiAgKiBOb3RlIHRoYXQgYGNsdXN0ZXJfYXJnc2Agb25seSBhY2NlcHRzIHNpbmdsZS1sZW5ndGggYXJndW1lbnRzIChubyB2ZWN0b3JzIG9yIGxpc3RzKS4KCkZvciBleGFtcGxlOgoKYGBge3IgY2x1c3RlciBzY2Ugbm9uZGVmYXVsdH0KIyBDYWxjdWxhdGUgY2x1c3RlcnMgd2l0aCBub24tZGVmYXVsdCBwYXJhbWV0ZXJzCmNsdXN0ZXJfcmVzdWx0c19kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfY2x1c3RlcnMoCiAgcGNhX21hdHJpeCwKICBhbGdvcml0aG0gPSAibGVpZGVuIiwKICBubiA9IDE1LAogIG9iamVjdGl2ZV9mdW5jdGlvbiA9ICJtb2R1bGFyaXR5IgopCmBgYAoKCiMjIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGNsdXN0ZXJzCgpUaGlzIHNlY3Rpb24gZGVtb25zdHJhdGVzIGhvdyB0byB1c2Ugc2V2ZXJhbCBmdW5jdGlvbnMgZm9yIGV2YWx1YXRpbmcgY2x1c3RlciBxdWFsaXR5IGFuZCByZWxpYWJpbGl0eS4KSXQncyBpbXBvcnRhbnQgdG8gbm90ZSB0aGF0IGEgZnVsbCBldmFsdWF0aW9uIG9mIGNsdXN0ZXJpbmcgcmVzdWx0cyB3b3VsZCBjb21wYXJlIHRoZXNlIG1ldHJpY3MgYWNyb3NzIGEgc2V0IG9mIGNsdXN0ZXJpbmcgcmVzdWx0cywgd2l0aCB0aGUgYWltIG9mIGlkZW50aWZ5aW5nIGFuIG9wdGltYWwgcGFyYW1ldGVyaXphdGlvbi4KCkFsbCBmdW5jdGlvbnMgcHJlc2VudGVkIGluIHRoaXMgc2VjdGlvbiB0YWtlIHRoZSBmb2xsb3dpbmcgcmVxdWlyZWQgYXJndW1lbnRzOgoKKiBBIFBDQSBtYXRyaXggd2l0aCByb3cgbmFtZXMgcmVwcmVzZW50aW5nIHVuaXF1ZSBjZWxsIGlkcyAoZS5nLiwgYmFyY29kZXMpCiogQSBkYXRhIGZyYW1lIHdpdGgsIGF0IGxlYXN0LCBjb2x1bW5zIHJlcHJlc2VudGluZyB1bmlxdWUgY2VsbCBpZHMgYW5kIGNsdXN0ZXIgYXNzaWdubWVudHMKICAqIEJ5IGRlZmF1bHQsIHRoZXNlIGNvbHVtbnMgc2hvdWxkIGJlIG5hbWVkIGBjZWxsX2lkYCBhbmQgYGNsdXN0ZXJgLCByZXNwZWN0aXZlbHksIG1hdGNoaW5nIHRoZSBvdXRwdXQgb2YgYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9jbHVzdGVycygpYAogICogWW91IGNhbiBvdmVycmlkZSB0aGVzZSBkZWZhdWx0cyB1c2luZyB0aGUgYXJndW1lbnRzIGBjZWxsX2lkX2NvbGAgYW5kIGBjbHVzdGVyX2NvbGAKCiMjIyBTaWxob3VldHRlIHdpZHRoCgpTaWxob3VldHRlIHdpZHRoIGlzIGEgY29tbW9uIG1ldHJpYyB0aGF0IG1lYXN1cmVzIGhvdyB3ZWxsIHNlcGFyYXRlZCBjbHVzdGVycyBhcmUgYnksIGZvciBlYWNoIGNlbGwsIGNvbXBhcmluZyB0aGUgYXZlcmFnZSBkaXN0YW5jZSB0byBhbGwgY2VsbHMgaW4gdGhlIHNhbWUgY2x1c3RlciwgYW5kIGFsbCBjZWxscyBpbiBvdGhlciBjbHVzdGVycy4KVGhpcyB2YWx1ZSByYW5nZXMgZnJvbSAtMSB0byAxLgpDZWxscyBpbiB3ZWxsLXNlcGFyYXRlZCBjbHVzdGVycyBzaG91bGQgaGF2ZSBoaWdoIHNpbGhvdWV0dGUgdmFsdWVzIGNsb3NlciB0byAxLgpZb3UgY2FuIHJlYWQgbW9yZSBhYm91dCBzaWxob3VldHRlIHdpZHRoIHB1cml0eSBmcm9tIHRoZSBbX09yY2hlc3RyYXRpbmcgU2luZ2xlIENlbGwgQW5hbHlzaXMgd2l0aCBCaW9jb25kdWN0b3JfIGJvb2tdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy8zLjE5L09TQ0EuYWR2YW5jZWQvY2x1c3RlcmluZy1yZWR1eC5odG1sI3NpbGhvdWV0dGUtd2lkdGgpLgoKV2UnbGwgdXNlIHRoZSBmdW5jdGlvbiBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3NpbGhvdWV0dGUoKWAgdG8gY2FsY3VsYXRlIHRoZSBzaWxob3VldHRlIHdpZHRoLgoKVGhpcyBmdW5jdGlvbiB3aWxsIHJldHVybiB0aGUgaW5wdXR0ZWQgZGF0YSBmcmFtZSB3aXRoIHR3byBhZGRpdGlvbmFsIGNvbHVtbnM6CgoqIGBzaWxob3VldHRlX3dpZHRoYDogVGhlIGNhbGN1bGF0ZWQgc2lsaG91ZXR0ZSB3aWR0aCBmb3IgdGhlIGNlbGwKKiBgc2lsaG91ZXR0ZV9vdGhlcmA6IFRoZSBjbG9zZXQgY2x1c3RlciB0byB0aGUgY2VsbCBiZXNpZGVzIHRoZSBjbHVzdGVyIHRvIHdoaWNoIGl0IGJlbG9uZ3MsIGFzIHVzZWQgaW4gdGhlIHNpbGhvdWV0dGUgd2lkdGggY2FsY3VsYXRpb24KCgpgYGB7ciBzaWxob3VldHRlfQojIGNhbGN1bGF0ZSB0aGUgc2lsaG91ZXR0ZSB3aWR0aCBmb3IgZWFjaCBjZWxsCnNpbGhvdWV0dGVfcmVzdWx0cyA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfc2lsaG91ZXR0ZSgKICBwY2FfbWF0cml4LAogIGNsdXN0ZXJfcmVzdWx0c19kZgopCgojIFByaW50IHRoZSBmaXJzdCByb3dzIG9mIHRoZSByZXN1bHRpbmcgdGFibGUKaGVhZChzaWxob3VldHRlX3Jlc3VsdHMpCmBgYAoKCldlIGNhbiB2aXN1YWxpemUgdGhlc2UgcmVzdWx0cyBieSBwbG90dGluZyBzaWxob3VldHRlIHdpZHRoIGFjcm9zcyBjbHVzdGVycyBhcyB2aW9saW4gcGxvdHMsIGZvciBleGFtcGxlOgoKYGBge3IgdmlvbGluIHNpbGhvdWV0dGV9CmdncGxvdChzaWxob3VldHRlX3Jlc3VsdHMpICsKICBhZXMoeCA9IGNsdXN0ZXIsIHkgPSBzaWxob3VldHRlX3dpZHRoKSArCiAgZ2VvbV92aW9saW4oZmlsbCA9ICJkYXJrbWFnZW50YSIpICsKICBsYWJzKHggPSAiQ2x1c3RlciIsIHkgPSAiU2lsaG91ZXR0ZSB3aWR0aCIpCmBgYAoKIyMjIE5laWdoYm9yaG9vZCBwdXJpdHkKCk5laWdoYm9yaG9vZCBwdXJpdHkgaXMgZGVmaW5lZCwgZm9yIGVhY2ggY2VsbCwgYXMgdGhlIHByb3BvcnRpb24gb2YgbmVpZ2hib3JpbmcgY2VsbHMgdGhhdCBhcmUgYXNzaWduZWQgdG8gdGhlIHNhbWUgY2x1c3Rlci4KVGhpcyB2YWx1ZSByYW5nZXMgZnJvbSAwIHRvIDEuCkNlbGxzIGluIHdlbGwtc2VwYXJhdGVkIGNsdXN0ZXJzIHNob3VsZCBoYXZlIGhpZ2ggcHVyaXR5IHZhbHVlcyBjbG9zZXIgdG8gMSwgc2luY2UgdGhlcmUgc2hvdWxkIGJlIG1pbmltYWwgb3ZlcmxhcCBiZXR3ZWVuIG1lbWJlciBhbmQgbmVpZ2hib3JpbmcgY2VsbHMuCllvdSBjYW4gcmVhZCBtb3JlIGFib3V0IG5laWdoYm9yaG9vZCBwdXJpdHkgZnJvbSB0aGUgW19PcmNoZXN0cmF0aW5nIFNpbmdsZSBDZWxsIEFuYWx5c2lzIHdpdGggQmlvY29uZHVjdG9yXyBib29rXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvYm9va3MvMy4xOS9PU0NBLmFkdmFuY2VkL2NsdXN0ZXJpbmctcmVkdXguaHRtbCNjbHVzdGVyLXB1cml0eSkuCgpXZSdsbCB1c2UgdGhlIGZ1bmN0aW9uIGByT3BlblNjUENBOjpjYWxjdWxhdGVfcHVyaXR5KClgIHRvIGNhbGN1bGF0ZSB0aGUgbmVpZ2hib3Job29kIHB1cml0eS4KClRoaXMgZnVuY3Rpb24gd2lsbCByZXR1cm4gdGhlIGlucHV0dGVkIGRhdGEgZnJhbWUgd2l0aCB0d28gYWRkaXRpb25hbCBjb2x1bW5zOgoKKiBgcHVyaXR5YDogVGhlIG5laWdoYm9yaG9vZCBwdXJpdHkgZm9yIHRoZSBjZWxsCiogYG1heGltdW1fbmVpZ2hib3JgOiBUaGUgY2x1c3RlciB3aXRoIHRoZSBoaWdoZXN0IHByb3BvcnRpb24gb2Ygb2JzZXJ2YXRpb25zIG5laWdoYm9yaW5nIHRoZSBjZWxsCgoKYGBge3IgcHVyaXR5fQojIGNhbGN1bGF0ZSB0aGUgbmVpZ2hib3Job29kIHB1cml0eSBmb3IgZWFjaCBjZWxsCnB1cml0eV9yZXN1bHRzIDwtIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9wdXJpdHkoCiAgcGNhX21hdHJpeCwKICBjbHVzdGVyX3Jlc3VsdHNfZGYKKQoKIyBQcmludCB0aGUgZmlyc3Qgcm93cyBvZiB0aGUgcmVzdWx0aW5nIHRhYmxlCmhlYWQocHVyaXR5X3Jlc3VsdHMpCmBgYAoKCldlIGNhbiB2aXN1YWxpemUgdGhlc2UgcmVzdWx0cyBieSBwbG90dGluZyBwdXJpdHkgY2x1c3RlcnMgYXMgdmlvbGluIHBsb3RzLCBmb3IgZXhhbXBsZToKCmBgYHtyIHZpb2xpbiBwdXJpdHl9CmdncGxvdChwdXJpdHlfcmVzdWx0cykgKwogIGFlcyh4ID0gY2x1c3RlciwgeSA9IHB1cml0eSkgKwogIGdlb21fdmlvbGluKGZpbGwgPSAiZGFya29saXZlZ3JlZW4zIikgKwogIGxhYnMoeCA9ICJDbHVzdGVyIiwgeSA9ICJOZWlnaGJvcmhvb2QgcHVyaXR5IikKYGBgCgojIyMgQ2x1c3RlciBzdGFiaWxpdHkKCkFub3RoZXIgYXBwcm9hY2ggdG8gZXhwbG9yaW5nIGNsdXN0ZXIgcXVhbGl0eSBpcyBob3cgc3RhYmxlIHRoZSBjbHVzdGVycyB0aGVtc2VsdmVzIGFyZSB1c2luZyBib290c3RyYXBwaW5nLgpHaXZlbiBhIHNldCBvZiBvcmlnaW5hbCBjbHVzdGVycywgd2UgY2FuIGNvbXBhcmUgdGhlIGJvb3RzdHJhcHBlZCBjbHVzdGVyIGlkZW50aXRpZXMgdG8gb3JpZ2luYWwgb25lcyB1c2luZyB0aGUgQWRqdXN0ZWQgUmFuZCBJbmRleCAoQVJJKSwgd2hpY2ggbWVhc3VyZXMgdGhlIHNpbWlsYXJpdHkgb2YgdHdvIGRhdGEgY2x1c3RlcmluZ3MuCkFSSSByYW5nZXMgZnJvbSAtMSB0byAxLCB3aGVyZToKCiogQSB2YWx1ZSBvZiAxIGluZGljYXRlcyB0aGV5IGFyZSBjb21wbGV0ZWx5IG92ZXJsYXBwaW5nCiogQSB2YWx1ZSBvZiAtMSBpbmRpY2F0ZXMgdGhleSBhcmUgY29tcGxldGVseSBkaXN0aW5jdAoqIEEgdmFsdWUgb2YgMCBpbmRpY2F0ZXMgYSByYW5kb20gcmVsYXRpb25zaGlwCgpXZSBleHBlY3QgdGhhdCBoaWdobHkgc3RhYmxlIGNsdXN0ZXJpbmdzIGhhdmUgQVJJIHZhbHVlcyBjbG9zZXIgdG8gMSBhY3Jvc3MgYSBzZXQgb2YgYm9vdHN0cmFwIHJlcGxpY2F0ZXMuCgpZb3UgY2FuIHJlYWQgbW9yZSBhYm91dCB0aGUgQWRqdXN0ZWQgUmFuZCBJbmRleCBmcm9tIHRoZSBbX09yY2hlc3RyYXRpbmcgU2luZ2xlIENlbGwgQW5hbHlzaXMgd2l0aCBCaW9jb25kdWN0b3JfIGJvb2tdKGh0dHBzOi8vYmlvY29uZHVjdG9yLm9yZy9ib29rcy9yZWxlYXNlL09TQ0EuYWR2YW5jZWQvY2x1c3RlcmluZy1yZWR1eC5odG1sI2FkanVzdGVkLXJhbmQtaW5kZXgpLgoKV2UnbGwgdXNlIHRoZSBmdW5jdGlvbiBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgpYCB0byBjYWxjdWxhdGUgdGhlIGNsdXN0ZXIgc3RhYmlsaXR5LgpCeSBkZWZhdWx0LCB0aGlzIGZ1bmN0aW9uIHBlcmZvcm1zIDIwIGJvb3RzdHJhcCByZXBsaWNhdGVzLCBidXQgdGhpcyBjYW4gYmUgY3VzdG9taXplZCB1c2luZyB0aGUgYXJndW1lbnQgYHJlcGxpY2F0ZXNgLgoKVGhpcyBmdW5jdGlvbiB3aWxsIHJldHVybiBhIGRhdGEgZnJhbWUgd2l0aCBjb2x1bW5zIGByZXBsaWNhdGVgLCBgYXJpYCwgYW5kIGFkZGl0aW9uYWwgY29sdW1ucyBmb3IgdGhlIGNsdXN0ZXJpbmcgcGFyYW1ldGVycyB1c2VkIHdoZW4gY2FsY3VsYXRpbmcgYm9vdHN0cmFwIGNsdXN0ZXJzLgoKYGBge3Igc3RhYmlsaXR5LCB3YXJuaW5nPUZBTFNFfQojIGNhbGN1bGF0ZSB0aGUgc3RhYmlsaXR5IG9mIGNsdXN0ZXJzCnN0YWJpbGl0eV9yZXN1bHRzIDwtIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zdGFiaWxpdHkoCiAgcGNhX21hdHJpeCwKICBjbHVzdGVyX3Jlc3VsdHNfZGYKKQoKIyBwcmludCB0aGUgcmVzdWx0CnN0YWJpbGl0eV9yZXN1bHRzCmBgYAoKV2UgY2FuIHZpc3VhbGl6ZSB0aGVzZSByZXN1bHRzIGJ5IHBsb3R0aW5nIHN0YWJpbGl0eSBhcyBhIGRlbnNpdHkgcGxvdCwgZm9yIGV4YW1wbGU6CgpgYGB7ciBhcmkgZGVuc2l0eX0KZ2dwbG90KHN0YWJpbGl0eV9yZXN1bHRzKSArCiAgYWVzKHggPSBhcmkpICsKICBnZW9tX2RlbnNpdHkoY29sb3IgPSAiZ3JleTMwIiwgZmlsbCA9ICJsaWdodHNsYXRlYmx1ZSIpICsKICBsYWJzKHggPSAiQWRqdXN0ZWQgcmFuZCBpbmRleCBhY3Jvc3MgYm9vdHN0cmFwIHJlcGxpY2F0ZXMiKQpgYGAKCgojIyMjIFVzaW5nIG5vbi1kZWZhdWx0IGNsdXN0ZXJpbmcgcGFyYW1ldGVycwoKV2hlbiBjYWxjdWxhdGluZyBib290c3RyYXAgY2x1c3RlcnMsIGByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KClgIHVzZXMgYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9jbHVzdGVycygpYCB3aXRoIGRlZmF1bHQgcGFyYW1ldGVycy4KSWYgeW91ciBvcmlnaW5hbCBjbHVzdGVycyB3ZXJlIG5vdCBjYWxjdWxhdGVkIHdpdGggdGhlc2UgZGVmYXVsdHMsIHlvdSBzaG91bGQgcGFzcyB0aG9zZSBjdXN0b21pemVkIHZhbHVlcyBpbnRvIHRoaXMgZnVuY3Rpb24gYXMgd2VsbCB0byBlbnN1cmUgYSBmYWlyIGNvbXBhcmlzb24gYmV0d2VlbiB5b3VyIG9yaWdpbmFsIGNsdXN0ZXJzIGFuZCB0aGUgYm9vdHN0cmFwIGNsdXN0ZXJzLgoKCmBgYHtyIHN0YWJpbGl0eSBjdXN0b20gcGFyYW1ldGVyc30KIyBDYWxjdWxhdGUgY2x1c3RlcnMgd2l0aCBub24tZGVmYXVsdCBwYXJhbWV0ZXJzCmNsdXN0ZXJfZGZfbGVpZGVuIDwtIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9jbHVzdGVycygKICBwY2FfbWF0cml4LAogIGFsZ29yaXRobSA9ICJsZWlkZW4iLAogIHJlc29sdXRpb24gPSAwLjUsCiAgbm4gPSAxNQopCgojIE5vdywgcGFzcyBpbiB0aGUgc2FtZSBhcmd1bWVudHMgY3VzdG9taXppbmcgcGFyYW1ldGVycyBoZXJlCnN0YWJpbGl0eV9yZXN1bHRzX2xlaWRlbiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KAogIHBjYV9tYXRyaXgsCiAgY2x1c3Rlcl9kZl9sZWlkZW4sCiAgYWxnb3JpdGhtID0gImxlaWRlbiIsCiAgcmVzb2x1dGlvbiA9IDAuNSwKICBubiA9IDE1CikKYGBgCgoKIyMgV29ya2luZyB3aXRoIG9iamVjdHMgZGlyZWN0bHkKCkFzIHByZXNlbnRlZCBhYm92ZSwgYHJPcGVuU2NQQ0FgIGNsdXN0ZXJpbmcgZnVuY3Rpb25zIHRha2UgYSBQQ0EgbWF0cml4IHdpdGggcm93IG5hbWVzIHJlcHJlc2VudGluZyB1bmlxdWUgY2VsbCBpZHMgYXMgdGhlaXIgZmlyc3QgYXJndW1lbnQuCgpJbnN0ZWFkIG9mIGEgbWF0cml4LCB5b3UgY2FuIGFsdGVybmF0aXZlbHkgcGFzcyBpbiBhbiBTQ0Ugb3IgU2V1cmF0IG9iamVjdCB0aGF0IGNvbnRhaW5zIGEgbWF0cml4LgoKV2Ugc2hvdyBhbiBleGFtcGxlIG9mIHRoaXMgYmVsb3cgd2l0aCBhbmQgU0NFIG9iamVjdCBhbmQgYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9jbHVzdGVycygpYCwgYnV0IHRoaXMgd2lsbCBhbHNvIHdvcmsgZm9yIGFueSBvZiB0aGUgZXZhbHVhdGlvbiBmdW5jdGlvbnMgYXMgd2VsbCBhbmQgaGFzIHRoZSBzYW1lIHN5bnRheCBmb3IgU2V1cmF0IG9iamVjdHMuCgpgYGB7ciBydW4gb24gc2NlfQojIENhbGN1bGF0ZSBjbHVzdGVycyBmcm9tIGFuIFNDRSBvYmplY3QgdXNpbmcgZGVmYXVsdCBwYXJhbWV0ZXJzCmNsdXN0ZXJfcmVzdWx0c19kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfY2x1c3RlcnMoc2NlKQpjbHVzdGVyX3Jlc3VsdHNfZGYKYGBgCgoKYHJPcGVuU2NQQ0FgIGFzc3VtZXMgdGhhdCB0aGUgUENBIG1hdHJpeCBpcyBuYW1lZCBgUENBYCBpbiBTQ0Ugb2JqZWN0cywgYW5kIGBwY2FgIGluIFNldXJhdCBvYmplY3RzLgpJZiB0aGUgUENBIG1hdHJpeCB5b3Ugd2FudCB0byB1c2UgaW4gdGhlIG9iamVjdCBoYXMgYSBkaWZmZXJlbnQgbmFtZSwgeW91IGNhbiBwcm92aWRlIHRoZSBhcmd1bWVudCBgcGNfbmFtZWAuCgoKIyMgQ2FsY3VsYXRpbmcgUUMgbWV0cmljcyBvbiBleGlzdGluZyBjbHVzdGVycwoKSWYgeW91IGFscmVhZHkgaGF2ZSBjbHVzdGVyaW5nIHJlc3VsdHMgY2FsY3VsYXRlZCB3aXRoIG90aGVyIHRvb2xzLCB5b3UgY2FuIHN0aWxsIHVzZSB0aGUgYHJPcGVuU2NQQ0FgIGZ1bmN0aW9ucyB0byBldmFsdWF0ZSB5b3VyIGNsdXN0ZXJzLgoKSW4gdGhpcyBzZWN0aW9uLCB3ZSdsbCBwcmVzZW50IGV4YW1wbGVzIG9mIGhvdyB5b3UgY2FuIGNhbGN1bGF0ZSB0aGUgc2lsaG91ZXR0ZSB3aWR0aCwgbmVpZ2hib3Job29kIHB1cml0eSwgYW5kIGNsdXN0ZXIgc3RhYmlsaXR5IGZyb20gZXhpc3RpbmcgY2x1c3RlciBhc3NpZ25tZW50cyB3aXRoaW4gb2JqZWN0cy4KCiMjIyBFdmFsdWF0aW5nIFNldXJhdCBjbHVzdGVycwoKSWYgeW91IGFyZSBhbmFseXppbmcgeW91ciBkYXRhIHdpdGggYSBTZXVyYXQgcGlwZWxpbmUgdGhhdCBpbmNsdWRlcyBjYWxjdWxhdGluZyBjbHVzdGVycywgeW91IGNhbiB1c2UgYHJPcGVuU2NQQ0FgIHRvIGV2YWx1YXRlIHRoZW0uCgpUbyBkZW1vbnN0cmF0ZSB0aGlzLCB3ZSdsbCBjb252ZXJ0IG91ciBTQ0Ugb2JqZWN0IHRvIGEgU2V1cmF0IHVzaW5nIHRoZSBmdW5jdGlvbiBgck9wZW5TY1BDQTo6c2NlX3RvX3NldXJhdCgpYC4KVGhlbiwgd2UnbGwgdXNlIGEgc2ltcGxlIFNldXJhdCBwaXBlbGluZSB0byBvYnRhaW4gY2x1c3RlcnMuCgpgYGB7ciBzY2UgdG8gc2V1cmF0LCBtZXNzYWdlID0gRkFMU0V9CiMgQ29udmVydCB0aGUgU0NFIHRvIGEgU2V1cmF0IG9iamVjdCB1c2luZyByT3BlblNjUENBCnNldXJhdF9vYmogPC0gck9wZW5TY1BDQTo6c2NlX3RvX3NldXJhdChzY2UpCgojIENhbGN1bGF0ZSBjbHVzdGVycyB3aXRoIFNldXJhdCB1c2luZyBhIHN0YW5kYXJkIFNldXJhdCBwaXBlbGluZSwgZm9yIGV4YW1wbGUKc2V1cmF0X29iaiA8LSBzZXVyYXRfb2JqIHw+CiAgU0NUcmFuc2Zvcm0oKSB8PgogIFJ1blBDQSgpIHw+CiAgRmluZE5laWdoYm9ycygpIHw+CiAgRmluZENsdXN0ZXJzKCkKCnNldXJhdF9vYmoKYGBgCgoKVG8gY2FsY3VsYXRlIFFDIG1ldHJpY3Mgb24gdGhlc2UgY2x1c3RlcnMsIHdlJ2xsIG5lZWQgdG8gY3JlYXRlIGEgZGF0YSBmcmFtZSB3aXRoIGNvbHVtbnMgYGNlbGxfaWRgIGFuZCBgY2x1c3RlcmA6CgpgYGB7ciBwcmVwYXJlIHNldXJhdCBpbnB1dH0KIyBDcmVhdGUgYSBkYXRhIGZyYW1lIGZvciBpbnB1dApzZXVyYXRfY2x1c3Rlcl9kZiA8LSBkYXRhLmZyYW1lKAogIGNlbGxfaWQgPSBjb2xuYW1lcyhzZXVyYXRfb2JqKSwKICBjbHVzdGVyID0gc2V1cmF0X29iaiRzZXVyYXRfY2x1c3RlcnMKKQoKaGVhZChzZXVyYXRfY2x1c3Rlcl9kZikKYGBgCgpOb3csIHdlIGNhbiBydW4gYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zaWxob3VldHRlKClgIGFuZCBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3B1cml0eSgpYCB1c2luZyB0aGlzIGRhdGEgZnJhbWUgYW5kIHRoZSBTZXVyYXQgb2JqZWN0OgoKYGBge3Igc2V1cmF0IHNpbGhvdWV0dGV9CnNldXJhdF9zaWxob3VldHRlX2RmIDwtIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zaWxob3VldHRlKAogIHNldXJhdF9vYmosCiAgc2V1cmF0X2NsdXN0ZXJfZGYKKQpgYGAKCmBgYHtyIHNldXJhdCBwdXJpdHl9CnNldXJhdF9wdXJpdHlfZGYgPC0gck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3B1cml0eSgKICBzZXVyYXRfb2JqLAogIHNldXJhdF9jbHVzdGVyX2RmCikKYGBgCgpXZSBkbyBub3QgcmVjb21tZW5kIHVzaW5nIGByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KClgIG9uIFNldXJhdCBjbHVzdGVycyBkdWUgdG8gZGlmZmVyZW5jZXMgaW4gdGhlIHVuZGVybHlpbmcgY2x1c3RlcmluZyBhcHByb2FjaCBiZXR3ZWVuIFNldXJhdCBhbmQgdGhlIGBibHVzdGVyYCBwYWNrYWdlIHdoaWNoIGByT3BlblNjUENBYCB1c2VzLgoKIyMjIEV2YWx1YXRpbmcgU2NQQ0EgY2x1c3RlcnMKClNjUENBIGNlbGwgbWV0YWRhdGEgYWxyZWFkeSBjb250YWlucyBhIGNvbHVtbiBjYWxsZWQgYGNsdXN0ZXJgIHdpdGggcmVzdWx0cyBmcm9tIGFuIGF1dG9tYXRlZCBjbHVzdGVyaW5nLgpUaGVzZSBjbHVzdGVycyB3ZXJlIGNhbGN1bGF0ZWQgdXNpbmcgYGJsdXN0ZXJgLCB0aGUgc2FtZSB0b29sIHRoYXQgYHJPcGVuU2NQQ0FgIHVzZXMuClRoZSBzcGVjaWZpY2F0aW9ucyB1c2VkIGZvciB0aGlzIGNsdXN0ZXJpbmcgYXJlIHN0b3JlZCBpbiB0aGUgU0NFIG9iamVjdCdzIG1ldGFkYXRhLCBhcyBmb2xsb3dzOyBub3RlIHRoYXQgYWxsIG90aGVyIGNsdXN0ZXJpbmcgcGFyYW1ldGVycyB3ZXJlIGxlZnQgYXQgdGhlaXIgZGVmYXVsdCB2YWx1ZXMuCgoqIGBtZXRhZGF0YShzY2UpJGNsdXN0ZXJfYWxnb3JpdGhtYDogVGhlIGNsdXN0ZXJpbmcgYWxnb3JpdGhtIHVzZWQKKiBgbWV0YWRhdGEoc2NlKSRjbHVzdGVyX3dlaWdodGluZ2A6IFRoZSB3ZWlnaHRpbmcgc2NoZW1lIHVzZWQKKiBgbWV0YWRhdGEoc2NlKSRjbHVzdGVyX25uYDogVGhlIG51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyB1c2VkCgpZb3UgY2FuIHNlZSBhbGwgdGhlaXIgdmFsdWVzIGhlcmU6CgoKYGBge3IgZXh0cmFjdCBjbHVzdGVyIHBhcmFtc30KIyBQcmludCBjbHVzdGVyaW5nIHNwZWNpZmljYXRpb25zCm1ldGFkYXRhKHNjZSlbYygiY2x1c3Rlcl9hbGdvcml0aG0iLCAiY2x1c3Rlcl93ZWlnaHRpbmciLCAiY2x1c3Rlcl9ubiIpXQpgYGAKCgpJbiB0aGlzIGV4YW1wbGUsIHdlJ2xsIHNob3cgaG93IHRvIHVzZSB0aGUgY2x1c3RlciBldmFsdWF0aW9uIGZ1bmN0aW9ucyBvbiB0aGVzZSBjbHVzdGVycy4KClRvIGJlZ2luLCB3ZSdsbCBwcmVwYXJlIGEgZGF0YSBmcmFtZSB3aXRoIHR3byBjb2x1bW5zOiBgY2VsbF9pZGAgY29udGFpbmluZyBjZWxsIGJhcmNvZGVzLCBhbmQgYGNsdXN0ZXJgIGNvbnRhaW5pbmcgdGhlIGNsdXN0ZXIgaWRlbnRpdGllcy4KCmBgYHtyIHByZXBhcmUgc2NwY2EgZGF0YSBmcmFtZX0Kc2NwY2FfY2x1c3Rlcl9kZiA8LSBkYXRhLmZyYW1lKAogIGNlbGxfaWQgPSBjb2xuYW1lcyhzY2UpLAogIGNsdXN0ZXIgPSBzY2UkY2x1c3RlcgopCgpoZWFkKHNjcGNhX2NsdXN0ZXJfZGYpCmBgYAoKV2UgY2FuIHJ1biBldmFsdWF0aW9uIGZ1bmN0aW9ucyB1c2luZyB0aGlzIGRhdGEgZnJhbWUgYW5kIHRoZSBTQ0Ugb2JqZWN0LgoKYGBge3Igc2NwY2Egc2lsaG91ZXR0ZX0KIyBDYWxjdWxhdGUgc2lsaG91ZXR0ZSB3aWR0aApzY3BjYV9zaWxob3VldHRlX2RmIDwtIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zaWxob3VldHRlKAogIHNjZSwKICBzY3BjYV9jbHVzdGVyX2RmCikKYGBgCgpgYGB7ciBzY3BjYSBwdXJpdHl9CiMgQ2FsY3VsYXRlIG5laWdoYm9yaG9vZCBwdXJpdHkKc2NwY2FfcHVyaXR5X2RmIDwtIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9wdXJpdHkoCiAgc2NlLAogIHNjcGNhX2NsdXN0ZXJfZGYKKQpgYGAKCldoZW4gcnVubmluZyBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgpYCwgd2UnbGwgc3BlY2lmeSB0aGUgc2FtZSBwYXJhbWV0ZXJzIG9yaWdpbmFsbHkgdXNlZCB0byBidWlsZCB0aGUgY2x1c3RlcnMgYnkgZXh0cmFjdGluZyB0aGVtIGZyb20gdGhlIG1ldGFkYXRhLgpXZSdsbCBuZWVkIHRvIGVuc3VyZSB0aGUgcHJvdmlkZWQgYXJndW1lbnRzIGFyZSBsb3dlcmNhc2UsIGFzIHdlbGwuCgpHZW5lcmFsbHkgc3BlYWtpbmcsIHdlIG9ubHkgcmVjb21tZW5kIGV2YWx1YXRpbmcgY2x1c3RlcnMgd2l0aCBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgpYCBpZiB5b3Uga25vdyB0aGUgb3JpZ2luYWwgcGFyYW1ldGVycyB1c2VkLgoKCmBgYHtyIHNjcGNhIHN0YWJpbGl0eX0Kc2NwY2Ffc3RhYmlsaXR5X2RmIDwtIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zdGFiaWxpdHkoCiAgc2NlLAogIHNjcGNhX2NsdXN0ZXJfZGYsCiAgIyBwcm92aWRlIFNjUENBIGNsdXN0ZXJpbmcgcGFyYW1ldGVycyBieSBleHRyYWN0aW5nIGZyb20gdGhlIFNDRSBtZXRhZGF0YQogIGFsZ29yaXRobSA9IHRvbG93ZXIobWV0YWRhdGEoc2NlKSRjbHVzdGVyX2FsZ29yaXRobSksCiAgd2VpZ2h0aW5nID0gdG9sb3dlcihtZXRhZGF0YShzY2UpJGNsdXN0ZXJfd2VpZ2h0aW5nKSwKICBubiA9IG1ldGFkYXRhKHNjZSkkY2x1c3Rlcl9ubgopCmBgYAoKCiMjIFNhdmluZyBjbHVzdGVyaW5nIHJlc3VsdHMKClJlc3VsdHMgY2FuIGVpdGhlciBiZSBkaXJlY3RseSBleHBvcnRlZCBhcyBhIFRTViBmaWxlIChlLmcuLCB3aXRoIGByZWFkcjo6d3JpdGVfdHN2KClgKSwgb3IgeW91IGNhbiBhZGQgdGhlIHJlc3VsdHMgaW50byB5b3VyIFNDRSBvciBTZXVyYXQgb2JqZWN0LgpUaGUgc3Vic2VxdWVudCBleGFtcGxlcyB3aWxsIGRlbW9uc3RyYXRlIHNhdmluZyB0aGUgY2x1c3RlciBhc3NpZ25tZW50cyBzdG9yZWQgaW4gYGNsdXN0ZXJfcmVzdWx0c19kZiRjbHVzdGVyYCB0byBhbiBTQ0UgYW5kIGEgU2V1cmF0IG9iamVjdC4KCl9BIHdvcmQgb2YgY2F1dGlvbiFfCk9iamVjdHMgZnJvbSB0aGUgU2NQQ0EgUG9ydGFsIGFscmVhZHkgY29udGFpbiBhIGNvbHVtbiBjYWxsZWQgYGNsdXN0ZXJgIHdpdGggcmVzdWx0cyBmcm9tIGFuIGF1dG9tYXRlZCBjbHVzdGVyaW5nLgpUaGVzZSBhdXRvbWF0aWMgY2x1c3RlcnMgd2VyZSBub3QgZXZhbHVhdGVkLCBhbmQgdGhlaXIgcGFyYW1ldGVycyB3ZXJlIG5vdCBvcHRpbWl6ZWQgZm9yIGFueSBnaXZlbiBsaWJyYXJ5LgpUbyBhdm9pZCBhbWJpZ3VpdHkgYmV0d2VlbiB0aGUgZXhpc3RpbmcgYW5kIG5ldyBjbHVzdGVyaW5nIHJlc3VsdHMsIHdlJ2xsIG5hbWUgdGhlIG5ldyBjb2x1bW4gYHJvcGVuc2NwY2FfY2x1c3RlcmAuCgojIyMgU2F2aW5nIHJlc3VsdHMgdG8gYW4gU0NFIG9iamVjdAoKV2UgY2FuIGFkZCBjb2x1bW5zIHRvIGFuIFNDRSBvYmplY3QncyBgY29sRGF0YWAgdGFibGUgYnkgZGlyZWN0bHkgY3JlYXRpbmcgYSBjb2x1bW4gaW4gdGhlIG9iamVjdCB3aXRoIGAkYC4KQmVmb3JlIHdlIGRvIHNvLCB3ZSdsbCBjb25maXJtIHRoYXQgdGhlIGNsdXN0ZXJzIGFyZSBpbiB0aGUgc2FtZSBvcmRlciBhcyB0aGUgU0NFIG9iamVjdCBieSBjb21wYXJpbmcgY2VsbCBpZHM6CgpgYGB7ciBjaGVjayBzY2Ugb3JkZXJ9CmFsbC5lcXVhbCgKICBjb2xuYW1lcyhzY2UpLAogIGNsdXN0ZXJfcmVzdWx0c19kZiRjZWxsX2lkCikKYGBgCgpgYGB7ciBhZGQgdG8gc2NlfQojIEFkZCBjbHVzdGVyIHJlc3VsdHMgdG8gdGhlIGNvbERhdGEKc2NlJHJvcGVuc2NwY2FfY2x1c3RlciA8LSBjbHVzdGVyX3Jlc3VsdHNfZGYkY2x1c3RlcgpgYGAKCiMjIyBTYXZpbmcgcmVzdWx0cyB0byBhIFNldXJhdCBvYmplY3QKCgpXZSBjYW4gYWRkIGNvbHVtbnMgdG8gYW4gU2V1cmF0IG9iamVjdCdzIGNlbGwgbWV0YWRhdGEgdGFibGUgYnkgZGlyZWN0bHkgY3JlYXRpbmcgYSBjb2x1bW4gaW4gdGhlIG9iamVjdCB3aXRoIGAkYCAobm90ZSB0aGF0IHlvdSBjYW4gYWxzbyB1c2UgdGhlIFNldXJhdCBmdW5jdGlvbiBgQWRkTWV0YURhdGEoKWApLgpCZWZvcmUgd2UgZG8gc28sIHdlJ2xsIGNvbmZpcm0gdGhhdCB0aGUgY2x1c3RlcnMgYXJlIGluIHRoZSBzYW1lIG9yZGVyIGFzIHRoZSBTZXVyYXQgb2JqZWN0IGJ5IGNvbXBhcmluZyBjZWxsIGlkczoKCgpgYGB7ciBjaGVjayBzZXVyYXQgb3JkZXJ9CmFsbC5lcXVhbCgKICBjb2xuYW1lcyhzZXVyYXRfb2JqKSwKICBjbHVzdGVyX3Jlc3VsdHNfZGYkY2VsbF9pZAopCmBgYAoKYGBge3IgYWRkIHRvIHNldXJhdH0KIyBBZGQgY2x1c3RlciByZXN1bHRzIHRvIHRoZSBjZWxsIG1ldGFkYXRhCnNldXJhdF9vYmokcm9wZW5zY3BjYV9jbHVzdGVyIDwtIGNsdXN0ZXJfcmVzdWx0c19kZiRjbHVzdGVyCmBgYAoKCiMjIFNlc3Npb24gSW5mbwoKYGBge3Igc2Vzc2lvbiBpbmZvfQojIHJlY29yZCB0aGUgdmVyc2lvbnMgb2YgdGhlIHBhY2thZ2VzIHVzZWQgaW4gdGhpcyBhbmFseXNpcyBhbmQgb3RoZXIgZW52aXJvbm1lbnQgaW5mb3JtYXRpb24Kc2Vzc2lvbkluZm8oKQpgYGAK
+
LS0tCnRpdGxlOiAiUGVyZm9ybWluZyBncmFwaC1iYXNlZCBjbHVzdGVyaW5nIHdpdGggck9wZW5TY1BDQSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgphdXRob3I6ICJEYXRhIExhYiIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KCiMjIEludHJvZHVjdGlvbgoKVGhpcyBub3RlYm9vayBwcm92aWRlcyBleGFtcGxlcyBvZiBob3cgdG8gdXNlIGZ1bmN0aW9ucyBpbiBgck9wZW5TY1BDQWAgdGhhdDoKCiogUGVyZm9ybSBjbHVzdGVyaW5nCiogQ2FsY3VsYXRlIFFDIG1ldHJpY3Mgb24gY2x1c3RlcnMsIGluY2x1ZGluZzoKICAqIFNpbGhvdWV0dGUgd2lkdGgKICAqIE5laWdoYm9yaG9vZCBwdXJpdHkKICAqIENsdXN0ZXIgc3RhYmlsaXR5LCBhcyBtZWFzdXJlZCB3aXRoIHRoZSBBZGp1c3RlZCBSYW5kIEluZGV4CiogQ2FsY3VsYXRlIFFDIG1ldHJpY3Mgb24gY2x1c3RlcnMgb2J0YWluZWQgd2l0aCBvdGhlciB0b29scywgc3VjaCBhcyBgU2V1cmF0YAoqIFNhdmUgY2x1c3RlcmluZyByZXN1bHRzIHRvIGFuIFNDRSBvciBgU2V1cmF0YAoKV2hpbGUgdGhpcyBub3RlYm9vayBkZW1vbnN0cmF0ZXMgaG93IHRvIHVzZSBpbmRpdmlkdWFsIGZ1bmN0aW9ucyB0aGF0IGNhbGN1bGF0ZSBoZWxwZnVsIG1ldHJpY3MgZm9yIGV2YWx1YXRpbmcgY2x1c3RlcmluZyByZXN1bHRzLCBhIGZ1bGwgZXZhbHVhdGlvbiB3b3VsZCBjb21wYXJlIHRoZXNlIG1ldHJpY3MgYWNyb3NzIGRpZmZlcmVudCBjbHVzdGVyaW5ncyBmcm9tIGRpZmZlcmVudCBwYXJhbWV0ZXJpemF0aW9ucy4KClRoaXMgbm90ZWJvb2sgd2lsbCB1c2UgdGhlIHNhbXBsZSBgU0NQQ1MwMDAwMDFgIGZyb20gcHJvamVjdCBgU0NQQ1AwMDAwMDFgLCB3aGljaCBpcyBhc3N1bWVkIHByZXNlbnQgaW4gdGhlIGBPcGVuU2NQQ0EtYW5hbHlzaXMvZGF0YS9jdXJyZW50L1NDUENQMDAwMDAxYCBkaXJlY3RvcnksIGZvciBhbGwgZXhhbXBsZXMuClBsZWFzZSBbc2VlIHRoaXMgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9vcGVuc2NwY2EucmVhZHRoZWRvY3MuaW8vZW4vbGF0ZXN0L2dldHRpbmctc3RhcnRlZC9hY2Nlc3NpbmctcmVzb3VyY2VzL2dldHRpbmctYWNjZXNzLXRvLWRhdGEvKSBmb3IgbW9yZSBpbmZvcm1hdGlvbiBhYm91dCBvYnRhaW5pbmcgU2NQQ0EgZGF0YS4KCiMjIFNldHVwCgojIyMgUGFja2FnZXMKCgpgYGB7ciBwYWNrYWdlc30KbGlicmFyeShyT3BlblNjUENBKQoKc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsKICBsaWJyYXJ5KFNpbmdsZUNlbGxFeHBlcmltZW50KQogIGxpYnJhcnkoU2V1cmF0KQogIGxpYnJhcnkoZHBseXIpCiAgbGlicmFyeShnZ3Bsb3QyKQp9KQoKIyBTZXQgZ2dwbG90IHRoZW1lIGZvciBwbG90cwp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgoKIyMjIFBhdGhzCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290OjpoYXNfZGlyKCIuZ2l0aHViIikpCgojIFRoZSBjdXJyZW50IGRhdGEgZGlyZWN0b3J5LCBmb3VuZCB3aXRoaW4gdGhlIHJlcG9zaXRvcnkgYmFzZSBkaXJlY3RvcnkKZGF0YV9kaXIgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImRhdGEiLCAiY3VycmVudCIpCgojIFRoZSBwYXRoIHRvIHRoaXMgbW9kdWxlCm1vZHVsZV9iYXNlIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJhbmFseXNlcyIsICJoZWxsby1jbHVzdGVycyIpCmBgYAoKYGBge3IgaW5wdXQgZmlsZSBwYXRofQojIFBhdGggdG8gcHJvY2Vzc2VkIFNDRSBmaWxlIGZvciBzYW1wbGUgU0NQQ1MwMDAwMDEKaW5wdXRfc2NlX2ZpbGUgPC0gZmlsZS5wYXRoKGRhdGFfZGlyLCAiU0NQQ1AwMDAwMDEiLCAiU0NQQ1MwMDAwMDEiLCAiU0NQQ0wwMDAwMDFfcHJvY2Vzc2VkLnJkcyIpCmBgYAoKCiMjIyBTZXQgdGhlIHJhbmRvbSBzZWVkCgpCZWNhdXNlIGNsdXN0ZXJpbmcgaW52b2x2ZXMgcmFuZG9tIHNhbXBsaW5nLCBpdCBpcyBpbXBvcnRhbnQgdG8gc2V0IHRoZSByYW5kb20gc2VlZCBhdCB0aGUgdG9wIG9mIHlvdXIgYW5hbHlzaXMgc2NyaXB0IG9yIG5vdGVib29rIHRvIGVuc3VyZSByZXByb2R1Y2liaWxpdHkuCgpgYGB7ciBzZXQgc2VlZH0Kc2V0LnNlZWQoMjAyNCkKYGBgCgojIyBSZWFkIGluIGFuZCBwcmVwYXJlIGRhdGEKClRvIGJlZ2luLCB3ZSdsbCByZWFkIGluIHRoZSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIChTQ0UpIG9iamVjdC4KCmBgYHtyIHJlYWQgZGF0YX0KIyBSZWFkIHRoZSBTQ0UgZmlsZQpzY2UgPC0gcmVhZFJEUyhpbnB1dF9zY2VfZmlsZSkKYGBgCgpGb3IgdGhlIGluaXRpYWwgY2x1c3RlciBjYWxjdWxhdGlvbnMgYW5kIGV2YWx1YXRpb25zLCB3ZSB3aWxsIHVzZSB0aGUgUENBIG1hdHJpeCBleHRyYWN0ZWQgZnJvbSB0aGUgU0NFIG9iamVjdC4KSXQncyBhbHNvIHBvc3NpYmxlIHRvIHVzZSBhbiBTQ0Ugb2JqZWN0IG9yIGEgU2V1cmF0IG9iamVjdCBkaXJlY3RseSwgd2hpY2ggd2Ugd2lsbCBkZW1vbnN0cmF0ZSBsYXRlci4KCgpgYGB7ciBleHRyYWN0IHBjYSBkYXRhfQojIEV4dHJhY3QgdGhlIFBDQSBtYXRyaXggZnJvbSBhbiBTQ0Ugb2JqZWN0CnBjYV9tYXRyaXggPC0gcmVkdWNlZERpbShzY2UsICJQQ0EiKQpgYGAKCiMjIFBlcmZvcm0gY2x1c3RlcmluZwoKVGhpcyBzZWN0aW9uIHdpbGwgc2hvdyBob3cgdG8gcGVyZm9ybSBjbHVzdGVyaW5nIHdpdGggdGhlIGZ1bmN0aW9uIGByT3BlblNjUENBOjpjYWxjdWxhdGVfY2x1c3RlcnMoKWAuCgpUaGlzIGZ1bmN0aW9uIHRha2VzIGEgUENBIG1hdHJpeCB3aXRoIHJvd25hbWVzIHJlcHJlc2VudGluZyB1bmlxdWUgY2VsbCBpZHMgKGUuZy4sIGJhcmNvZGVzKSBhcyBpdHMgcHJpbWFyeSBhcmd1bWVudC4KQnkgZGVmYXVsdCBpdCB3aWxsIGNhbGN1bGF0ZSBjbHVzdGVycyB1c2luZyB0aGUgZm9sbG93aW5nIHBhcmFtZXRlcnM6CgoqIExvdXZhaW4gYWxnb3JpdGhtCiogSmFjY2FyZCB3ZWlnaHRpbmcKKiAxMCBuZWFyZXN0IG5laWdoYm9ycwoqIEEgcmVzb2x1dGlvbiBwYXJhbWV0ZXIgb2YgMQoKVGhpcyBmdW5jdGlvbiB3aWxsIHJldHVybiBhIHRhYmxlIHdpdGggdGhlIGZvbGxvd2luZyBjb2x1bW5zOgoKKiBgY2VsbF9pZGA6IFVuaXF1ZSBjZWxsIGlkZW50aWZpZXJzLCBvYnRhaW5lZCBmcm9tIHRoZSBQQ0EgbWF0cml4J3Mgcm93IG5hbWVzCiogYGNsdXN0ZXJgOiBBIGZhY3RvciBjb2x1bW4gd2l0aCB0aGUgY2x1c3RlciBpZGVudGl0aWVzCiogVGhlcmUgd2lsbCBiZSBvbmUgY29sdW1uIGZvciBlYWNoIGNsdXN0ZXJpbmcgcGFyYW1ldGVyIHVzZWQKCgojIyMgQ2x1c3RlcmluZyB3aXRoIGRlZmF1bHQgcGFyYW1ldGVycwoKYGBge3IgY2x1c3RlciBzY2V9CiMgQ2FsY3VsYXRlIGNsdXN0ZXJzIHdpdGggZGVmYXVsdCBwYXJhbWV0ZXJzCmNsdXN0ZXJfcmVzdWx0c19kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfY2x1c3RlcnMocGNhX21hdHJpeCkKCiMgUHJpbnQgdGhlIGZpcnN0IHJvd3Mgb2YgdGhlIHJlc3VsdGluZyB0YWJsZQpoZWFkKGNsdXN0ZXJfcmVzdWx0c19kZikKYGBgCgojIyMgQ2x1c3RlcmluZyB3aXRoIG5vbi1kZWZhdWx0IHBhcmFtZXRlcnMKClBhcmFtZXRlcnMgdXNlZCBmb3IgY2x1c3RlcmluZyBjYW4gYmUgY3VzdG9taXplZCB3aXRoIHRoZXNlIGFyZ3VtZW50czoKCiogVGhlIGBhbGdvcml0aG1gIGNhbiBiZSBvbmUgb2Y6CiAgKiBgbG91dmFpbmAsIGB3YWxrdHJhcGAsIG9yIGBsZWlkZW5gCiogVGhlIGB3ZWlnaHRpbmdgIGNhbiBiZSBvbmUgb2Y6CiAgKiBgamFjY2FyZGAsIGByYW5rYCwgb3IgYG51bWJlcmAKKiBUaGUgbmVhcmVzdCBuZWlnaGJvcnMgcGFyYW1ldGVyIGNhbiBiZSBjdXN0b21pemVkIHdpdGggdGhlIGBubmAgYXJndW1lbnQKKiBUaGUgcmVzb2x1dGlvbiBwYXJhbWV0ZXIgY2FuIGJlIGN1c3RvbWl6ZWQgd2l0aCB0aGUgYHJlc29sdXRpb25gIGFyZ3VtZW50CiAgKiBUaGlzIHBhcmFtZXRlciBpcyBvbmx5IHVzZWQgYnkgTG91dmFpbiBhbmQgTGVpZGVuIGFsZ29yaXRobXMKKiBJZiB0aGUgTGVpZGVuIGFsZ29yaXRobSBpcyB1c2VkLCBpdHMgZGVmYXVsdCBvYmplY3RpdmUgZnVuY3Rpb24gcGFyYW1ldGVyIHdpbGwgYmUgYENQTWAsIGJ1dCB5b3UgY2FuIGFsc28gc2V0ICBgb2JqZWN0aXZlX2Z1bmN0aW9uID0gIm1vZHVsYXJpdHkiYCBpbnN0ZWFkLgoqIFlvdSBjYW4gcHJvdmlkZSBhZGRpdGlvbmFsIHBhcmFtZXRlcnMgYXMgYSBsaXN0IHRvIHRoZSBgY2x1c3Rlcl9hcmdzYCBhcmd1bWVudC4KICAqIFBsZWFzZSByZWZlciB0byB0aGUgW2BpZ3JhcGhgIGRvY3VtZW50YXRpb25dKGh0dHBzOi8vaWdyYXBoLm9yZy9yL2h0bWwvbGF0ZXN0KSB0byBsZWFybiBtb3JlIGFib3V0IHdoYXQgYWRkaXRpb25hbCBwYXJhbWV0ZXJzIGNhbiBiZSBwcm92aWRlZCB0byBlYWNoIGNsdXN0ZXJpbmcgYWxnb3JpdGhtLgogICogTm90ZSB0aGF0IGBjbHVzdGVyX2FyZ3NgIG9ubHkgYWNjZXB0cyBzaW5nbGUtbGVuZ3RoIGFyZ3VtZW50cyAobm8gdmVjdG9ycyBvciBsaXN0cykuCgpGb3IgZXhhbXBsZToKCmBgYHtyIGNsdXN0ZXIgc2NlIG5vbmRlZmF1bHR9CiMgQ2FsY3VsYXRlIGNsdXN0ZXJzIHdpdGggbm9uLWRlZmF1bHQgcGFyYW1ldGVycwpjbHVzdGVyX3Jlc3VsdHNfZGYgPC0gck9wZW5TY1BDQTo6Y2FsY3VsYXRlX2NsdXN0ZXJzKAogIHBjYV9tYXRyaXgsCiAgYWxnb3JpdGhtID0gImxlaWRlbiIsCiAgbm4gPSAxNSwKICBvYmplY3RpdmVfZnVuY3Rpb24gPSAibW9kdWxhcml0eSIKKQpgYGAKCgojIyBDYWxjdWxhdGUgUUMgbWV0cmljcyBvbiBjbHVzdGVycwoKVGhpcyBzZWN0aW9uIGRlbW9uc3RyYXRlcyBob3cgdG8gdXNlIHNldmVyYWwgZnVuY3Rpb25zIGZvciBldmFsdWF0aW5nIGNsdXN0ZXIgcXVhbGl0eSBhbmQgcmVsaWFiaWxpdHkuCkl0J3MgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCBhIGZ1bGwgZXZhbHVhdGlvbiBvZiBjbHVzdGVyaW5nIHJlc3VsdHMgd291bGQgY29tcGFyZSB0aGVzZSBtZXRyaWNzIGFjcm9zcyBhIHNldCBvZiBjbHVzdGVyaW5nIHJlc3VsdHMsIHdpdGggdGhlIGFpbSBvZiBpZGVudGlmeWluZyBhbiBvcHRpbWFsIHBhcmFtZXRlcml6YXRpb24uCgpBbGwgZnVuY3Rpb25zIHByZXNlbnRlZCBpbiB0aGlzIHNlY3Rpb24gdGFrZSB0aGUgZm9sbG93aW5nIHJlcXVpcmVkIGFyZ3VtZW50czoKCiogQSBQQ0EgbWF0cml4IHdpdGggcm93IG5hbWVzIHJlcHJlc2VudGluZyB1bmlxdWUgY2VsbCBpZHMgKGUuZy4sIGJhcmNvZGVzKQoqIEEgZGF0YSBmcmFtZSB3aXRoLCBhdCBsZWFzdCwgY29sdW1ucyByZXByZXNlbnRpbmcgdW5pcXVlIGNlbGwgaWRzIGFuZCBjbHVzdGVyIGFzc2lnbm1lbnRzCiAgKiBCeSBkZWZhdWx0LCB0aGVzZSBjb2x1bW5zIHNob3VsZCBiZSBuYW1lZCBgY2VsbF9pZGAgYW5kIGBjbHVzdGVyYCwgcmVzcGVjdGl2ZWx5LCBtYXRjaGluZyB0aGUgb3V0cHV0IG9mIGByT3BlblNjUENBOjpjYWxjdWxhdGVfY2x1c3RlcnMoKWAKICAqIFlvdSBjYW4gb3ZlcnJpZGUgdGhlc2UgZGVmYXVsdHMgdXNpbmcgdGhlIGFyZ3VtZW50cyBgY2VsbF9pZF9jb2xgIGFuZCBgY2x1c3Rlcl9jb2xgCgojIyMgU2lsaG91ZXR0ZSB3aWR0aAoKU2lsaG91ZXR0ZSB3aWR0aCBpcyBhIGNvbW1vbiBtZXRyaWMgdGhhdCBtZWFzdXJlcyBob3cgd2VsbCBzZXBhcmF0ZWQgY2x1c3RlcnMgYXJlIGJ5LCBmb3IgZWFjaCBjZWxsLCBjb21wYXJpbmcgdGhlIGF2ZXJhZ2UgZGlzdGFuY2UgdG8gYWxsIGNlbGxzIGluIHRoZSBzYW1lIGNsdXN0ZXIsIGFuZCBhbGwgY2VsbHMgaW4gb3RoZXIgY2x1c3RlcnMuClRoaXMgdmFsdWUgcmFuZ2VzIGZyb20gLTEgdG8gMS4KQ2VsbHMgaW4gd2VsbC1zZXBhcmF0ZWQgY2x1c3RlcnMgc2hvdWxkIGhhdmUgaGlnaCBzaWxob3VldHRlIHZhbHVlcyBjbG9zZXIgdG8gMS4KWW91IGNhbiByZWFkIG1vcmUgYWJvdXQgc2lsaG91ZXR0ZSB3aWR0aCBwdXJpdHkgZnJvbSB0aGUgW19PcmNoZXN0cmF0aW5nIFNpbmdsZSBDZWxsIEFuYWx5c2lzIHdpdGggQmlvY29uZHVjdG9yXyBib29rXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvYm9va3MvMy4xOS9PU0NBLmFkdmFuY2VkL2NsdXN0ZXJpbmctcmVkdXguaHRtbCNzaWxob3VldHRlLXdpZHRoKS4KCldlJ2xsIHVzZSB0aGUgZnVuY3Rpb24gYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zaWxob3VldHRlKClgIHRvIGNhbGN1bGF0ZSB0aGUgc2lsaG91ZXR0ZSB3aWR0aC4KClRoaXMgZnVuY3Rpb24gd2lsbCByZXR1cm4gdGhlIGlucHV0dGVkIGRhdGEgZnJhbWUgd2l0aCB0d28gYWRkaXRpb25hbCBjb2x1bW5zOgoKKiBgc2lsaG91ZXR0ZV93aWR0aGA6IFRoZSBjYWxjdWxhdGVkIHNpbGhvdWV0dGUgd2lkdGggZm9yIHRoZSBjZWxsCiogYHNpbGhvdWV0dGVfb3RoZXJgOiBUaGUgY2xvc2V0IGNsdXN0ZXIgdG8gdGhlIGNlbGwgYmVzaWRlcyB0aGUgY2x1c3RlciB0byB3aGljaCBpdCBiZWxvbmdzLCBhcyB1c2VkIGluIHRoZSBzaWxob3VldHRlIHdpZHRoIGNhbGN1bGF0aW9uCgoKYGBge3Igc2lsaG91ZXR0ZX0KIyBjYWxjdWxhdGUgdGhlIHNpbGhvdWV0dGUgd2lkdGggZm9yIGVhY2ggY2VsbApzaWxob3VldHRlX3Jlc3VsdHMgPC0gck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3NpbGhvdWV0dGUoCiAgcGNhX21hdHJpeCwKICBjbHVzdGVyX3Jlc3VsdHNfZGYKKQoKIyBQcmludCB0aGUgZmlyc3Qgcm93cyBvZiB0aGUgcmVzdWx0aW5nIHRhYmxlCmhlYWQoc2lsaG91ZXR0ZV9yZXN1bHRzKQpgYGAKCgpXZSBjYW4gdmlzdWFsaXplIHRoZXNlIHJlc3VsdHMgYnkgcGxvdHRpbmcgc2lsaG91ZXR0ZSB3aWR0aCBhY3Jvc3MgY2x1c3RlcnMgYXMgdmlvbGluIHBsb3RzLCBmb3IgZXhhbXBsZToKCmBgYHtyIHZpb2xpbiBzaWxob3VldHRlfQpnZ3Bsb3Qoc2lsaG91ZXR0ZV9yZXN1bHRzKSArCiAgYWVzKHggPSBjbHVzdGVyLCB5ID0gc2lsaG91ZXR0ZV93aWR0aCkgKwogIGdlb21fdmlvbGluKGZpbGwgPSAiZGFya21hZ2VudGEiKSArCiAgbGFicyh4ID0gIkNsdXN0ZXIiLCB5ID0gIlNpbGhvdWV0dGUgd2lkdGgiKQpgYGAKCiMjIyBOZWlnaGJvcmhvb2QgcHVyaXR5CgpOZWlnaGJvcmhvb2QgcHVyaXR5IGlzIGRlZmluZWQsIGZvciBlYWNoIGNlbGwsIGFzIHRoZSBwcm9wb3J0aW9uIG9mIG5laWdoYm9yaW5nIGNlbGxzIHRoYXQgYXJlIGFzc2lnbmVkIHRvIHRoZSBzYW1lIGNsdXN0ZXIuClRoaXMgdmFsdWUgcmFuZ2VzIGZyb20gMCB0byAxLgpDZWxscyBpbiB3ZWxsLXNlcGFyYXRlZCBjbHVzdGVycyBzaG91bGQgaGF2ZSBoaWdoIHB1cml0eSB2YWx1ZXMgY2xvc2VyIHRvIDEsIHNpbmNlIHRoZXJlIHNob3VsZCBiZSBtaW5pbWFsIG92ZXJsYXAgYmV0d2VlbiBtZW1iZXIgYW5kIG5laWdoYm9yaW5nIGNlbGxzLgpZb3UgY2FuIHJlYWQgbW9yZSBhYm91dCBuZWlnaGJvcmhvb2QgcHVyaXR5IGZyb20gdGhlIFtfT3JjaGVzdHJhdGluZyBTaW5nbGUgQ2VsbCBBbmFseXNpcyB3aXRoIEJpb2NvbmR1Y3Rvcl8gYm9va10oaHR0cHM6Ly9iaW9jb25kdWN0b3Iub3JnL2Jvb2tzLzMuMTkvT1NDQS5hZHZhbmNlZC9jbHVzdGVyaW5nLXJlZHV4Lmh0bWwjY2x1c3Rlci1wdXJpdHkpLgoKV2UnbGwgdXNlIHRoZSBmdW5jdGlvbiBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3B1cml0eSgpYCB0byBjYWxjdWxhdGUgdGhlIG5laWdoYm9yaG9vZCBwdXJpdHkuCgpUaGlzIGZ1bmN0aW9uIHdpbGwgcmV0dXJuIHRoZSBpbnB1dHRlZCBkYXRhIGZyYW1lIHdpdGggdHdvIGFkZGl0aW9uYWwgY29sdW1uczoKCiogYHB1cml0eWA6IFRoZSBuZWlnaGJvcmhvb2QgcHVyaXR5IGZvciB0aGUgY2VsbAoqIGBtYXhpbXVtX25laWdoYm9yYDogVGhlIGNsdXN0ZXIgd2l0aCB0aGUgaGlnaGVzdCBwcm9wb3J0aW9uIG9mIG9ic2VydmF0aW9ucyBuZWlnaGJvcmluZyB0aGUgY2VsbAoKCmBgYHtyIHB1cml0eX0KIyBjYWxjdWxhdGUgdGhlIG5laWdoYm9yaG9vZCBwdXJpdHkgZm9yIGVhY2ggY2VsbApwdXJpdHlfcmVzdWx0cyA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfcHVyaXR5KAogIHBjYV9tYXRyaXgsCiAgY2x1c3Rlcl9yZXN1bHRzX2RmCikKCiMgUHJpbnQgdGhlIGZpcnN0IHJvd3Mgb2YgdGhlIHJlc3VsdGluZyB0YWJsZQpoZWFkKHB1cml0eV9yZXN1bHRzKQpgYGAKCgpXZSBjYW4gdmlzdWFsaXplIHRoZXNlIHJlc3VsdHMgYnkgcGxvdHRpbmcgcHVyaXR5IGNsdXN0ZXJzIGFzIHZpb2xpbiBwbG90cywgZm9yIGV4YW1wbGU6CgpgYGB7ciB2aW9saW4gcHVyaXR5fQpnZ3Bsb3QocHVyaXR5X3Jlc3VsdHMpICsKICBhZXMoeCA9IGNsdXN0ZXIsIHkgPSBwdXJpdHkpICsKICBnZW9tX3Zpb2xpbihmaWxsID0gImRhcmtvbGl2ZWdyZWVuMyIpICsKICBsYWJzKHggPSAiQ2x1c3RlciIsIHkgPSAiTmVpZ2hib3Job29kIHB1cml0eSIpCmBgYAoKIyMjIENsdXN0ZXIgc3RhYmlsaXR5CgpBbm90aGVyIGFwcHJvYWNoIHRvIGV4cGxvcmluZyBjbHVzdGVyIHF1YWxpdHkgaXMgaG93IHN0YWJsZSB0aGUgY2x1c3RlcnMgdGhlbXNlbHZlcyBhcmUgdXNpbmcgYm9vdHN0cmFwcGluZy4KR2l2ZW4gYSBzZXQgb2Ygb3JpZ2luYWwgY2x1c3RlcnMsIHdlIGNhbiBjb21wYXJlIHRoZSBib290c3RyYXBwZWQgY2x1c3RlciBpZGVudGl0aWVzIHRvIG9yaWdpbmFsIG9uZXMgdXNpbmcgdGhlIEFkanVzdGVkIFJhbmQgSW5kZXggKEFSSSksIHdoaWNoIG1lYXN1cmVzIHRoZSBzaW1pbGFyaXR5IG9mIHR3byBkYXRhIGNsdXN0ZXJpbmdzLgpBUkkgcmFuZ2VzIGZyb20gLTEgdG8gMSwgd2hlcmU6CgoqIEEgdmFsdWUgb2YgMSBpbmRpY2F0ZXMgdGhleSBhcmUgY29tcGxldGVseSBvdmVybGFwcGluZwoqIEEgdmFsdWUgb2YgLTEgaW5kaWNhdGVzIHRoZXkgYXJlIGNvbXBsZXRlbHkgZGlzdGluY3QKKiBBIHZhbHVlIG9mIDAgaW5kaWNhdGVzIGEgcmFuZG9tIHJlbGF0aW9uc2hpcAoKV2UgZXhwZWN0IHRoYXQgaGlnaGx5IHN0YWJsZSBjbHVzdGVyaW5ncyBoYXZlIEFSSSB2YWx1ZXMgY2xvc2VyIHRvIDEgYWNyb3NzIGEgc2V0IG9mIGJvb3RzdHJhcCByZXBsaWNhdGVzLgoKWW91IGNhbiByZWFkIG1vcmUgYWJvdXQgdGhlIEFkanVzdGVkIFJhbmQgSW5kZXggZnJvbSB0aGUgW19PcmNoZXN0cmF0aW5nIFNpbmdsZSBDZWxsIEFuYWx5c2lzIHdpdGggQmlvY29uZHVjdG9yXyBib29rXShodHRwczovL2Jpb2NvbmR1Y3Rvci5vcmcvYm9va3MvcmVsZWFzZS9PU0NBLmFkdmFuY2VkL2NsdXN0ZXJpbmctcmVkdXguaHRtbCNhZGp1c3RlZC1yYW5kLWluZGV4KS4KCldlJ2xsIHVzZSB0aGUgZnVuY3Rpb24gYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zdGFiaWxpdHkoKWAgdG8gY2FsY3VsYXRlIHRoZSBjbHVzdGVyIHN0YWJpbGl0eS4KQnkgZGVmYXVsdCwgdGhpcyBmdW5jdGlvbiBwZXJmb3JtcyAyMCBib290c3RyYXAgcmVwbGljYXRlcywgYnV0IHRoaXMgY2FuIGJlIGN1c3RvbWl6ZWQgdXNpbmcgdGhlIGFyZ3VtZW50IGByZXBsaWNhdGVzYC4KClRoaXMgZnVuY3Rpb24gd2lsbCByZXR1cm4gYSBkYXRhIGZyYW1lIHdpdGggY29sdW1ucyBgcmVwbGljYXRlYCwgYGFyaWAsIGFuZCBhZGRpdGlvbmFsIGNvbHVtbnMgZm9yIHRoZSBjbHVzdGVyaW5nIHBhcmFtZXRlcnMgdXNlZCB3aGVuIGNhbGN1bGF0aW5nIGJvb3RzdHJhcCBjbHVzdGVycy4KCmBgYHtyIHN0YWJpbGl0eSwgd2FybmluZz1GQUxTRX0KIyBjYWxjdWxhdGUgdGhlIHN0YWJpbGl0eSBvZiBjbHVzdGVycwpzdGFiaWxpdHlfcmVzdWx0cyA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KAogIHBjYV9tYXRyaXgsCiAgY2x1c3Rlcl9yZXN1bHRzX2RmCikKCiMgcHJpbnQgdGhlIHJlc3VsdApzdGFiaWxpdHlfcmVzdWx0cwpgYGAKCldlIGNhbiB2aXN1YWxpemUgdGhlc2UgcmVzdWx0cyBieSBwbG90dGluZyBzdGFiaWxpdHkgYXMgYSBkZW5zaXR5IHBsb3QsIGZvciBleGFtcGxlOgoKYGBge3IgYXJpIGRlbnNpdHl9CmdncGxvdChzdGFiaWxpdHlfcmVzdWx0cykgKwogIGFlcyh4ID0gYXJpKSArCiAgZ2VvbV9kZW5zaXR5KGNvbG9yID0gImdyZXkzMCIsIGZpbGwgPSAibGlnaHRzbGF0ZWJsdWUiKSArCiAgbGFicyh4ID0gIkFkanVzdGVkIHJhbmQgaW5kZXggYWNyb3NzIGJvb3RzdHJhcCByZXBsaWNhdGVzIikKYGBgCgoKIyMjIyBVc2luZyBub24tZGVmYXVsdCBjbHVzdGVyaW5nIHBhcmFtZXRlcnMKCldoZW4gY2FsY3VsYXRpbmcgYm9vdHN0cmFwIGNsdXN0ZXJzLCBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgpYCB1c2VzIGByT3BlblNjUENBOjpjYWxjdWxhdGVfY2x1c3RlcnMoKWAgd2l0aCBkZWZhdWx0IHBhcmFtZXRlcnMuCklmIHlvdXIgb3JpZ2luYWwgY2x1c3RlcnMgd2VyZSBub3QgY2FsY3VsYXRlZCB3aXRoIHRoZXNlIGRlZmF1bHRzLCB5b3Ugc2hvdWxkIHBhc3MgdGhvc2UgY3VzdG9taXplZCB2YWx1ZXMgaW50byB0aGlzIGZ1bmN0aW9uIGFzIHdlbGwgdG8gZW5zdXJlIGEgZmFpciBjb21wYXJpc29uIGJldHdlZW4geW91ciBvcmlnaW5hbCBjbHVzdGVycyBhbmQgdGhlIGJvb3RzdHJhcCBjbHVzdGVycy4KCgpgYGB7ciBzdGFiaWxpdHkgY3VzdG9tIHBhcmFtZXRlcnN9CiMgQ2FsY3VsYXRlIGNsdXN0ZXJzIHdpdGggbm9uLWRlZmF1bHQgcGFyYW1ldGVycwpjbHVzdGVyX2RmX2xlaWRlbiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfY2x1c3RlcnMoCiAgcGNhX21hdHJpeCwKICBhbGdvcml0aG0gPSAibGVpZGVuIiwKICByZXNvbHV0aW9uID0gMC41LAogIG5uID0gMTUKKQoKIyBOb3csIHBhc3MgaW4gdGhlIHNhbWUgYXJndW1lbnRzIGN1c3RvbWl6aW5nIHBhcmFtZXRlcnMgaGVyZQpzdGFiaWxpdHlfcmVzdWx0c19sZWlkZW4gPC0gck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgKICBwY2FfbWF0cml4LAogIGNsdXN0ZXJfZGZfbGVpZGVuLAogIGFsZ29yaXRobSA9ICJsZWlkZW4iLAogIHJlc29sdXRpb24gPSAwLjUsCiAgbm4gPSAxNQopCmBgYAoKCiMjIFdvcmtpbmcgd2l0aCBvYmplY3RzIGRpcmVjdGx5CgpBcyBwcmVzZW50ZWQgYWJvdmUsIGByT3BlblNjUENBYCBjbHVzdGVyaW5nIGZ1bmN0aW9ucyB0YWtlIGEgUENBIG1hdHJpeCB3aXRoIHJvdyBuYW1lcyByZXByZXNlbnRpbmcgdW5pcXVlIGNlbGwgaWRzIGFzIHRoZWlyIGZpcnN0IGFyZ3VtZW50LgoKSW5zdGVhZCBvZiBhIG1hdHJpeCwgeW91IGNhbiBhbHRlcm5hdGl2ZWx5IHBhc3MgaW4gYW4gU0NFIG9yIFNldXJhdCBvYmplY3QgdGhhdCBjb250YWlucyBhIG1hdHJpeC4KCldlIHNob3cgYW4gZXhhbXBsZSBvZiB0aGlzIGJlbG93IHdpdGggYW5kIFNDRSBvYmplY3QgYW5kIGByT3BlblNjUENBOjpjYWxjdWxhdGVfY2x1c3RlcnMoKWAsIGJ1dCB0aGlzIHdpbGwgYWxzbyB3b3JrIGZvciBhbnkgb2YgdGhlIGV2YWx1YXRpb24gZnVuY3Rpb25zIGFzIHdlbGwgYW5kIGhhcyB0aGUgc2FtZSBzeW50YXggZm9yIFNldXJhdCBvYmplY3RzLgoKYGBge3IgcnVuIG9uIHNjZX0KIyBDYWxjdWxhdGUgY2x1c3RlcnMgZnJvbSBhbiBTQ0Ugb2JqZWN0IHVzaW5nIGRlZmF1bHQgcGFyYW1ldGVycwpjbHVzdGVyX3Jlc3VsdHNfZGYgPC0gck9wZW5TY1BDQTo6Y2FsY3VsYXRlX2NsdXN0ZXJzKHNjZSkKY2x1c3Rlcl9yZXN1bHRzX2RmCmBgYAoKCmByT3BlblNjUENBYCBhc3N1bWVzIHRoYXQgdGhlIFBDQSBtYXRyaXggaXMgbmFtZWQgYFBDQWAgaW4gU0NFIG9iamVjdHMsIGFuZCBgcGNhYCBpbiBTZXVyYXQgb2JqZWN0cy4KSWYgdGhlIFBDQSBtYXRyaXggeW91IHdhbnQgdG8gdXNlIGluIHRoZSBvYmplY3QgaGFzIGEgZGlmZmVyZW50IG5hbWUsIHlvdSBjYW4gcHJvdmlkZSB0aGUgYXJndW1lbnQgYHBjX25hbWVgLgoKCiMjIENhbGN1bGF0aW5nIFFDIG1ldHJpY3Mgb24gZXhpc3RpbmcgY2x1c3RlcnMKCklmIHlvdSBhbHJlYWR5IGhhdmUgY2x1c3RlcmluZyByZXN1bHRzIGNhbGN1bGF0ZWQgd2l0aCBvdGhlciB0b29scywgeW91IGNhbiBzdGlsbCB1c2UgdGhlIGByT3BlblNjUENBYCBmdW5jdGlvbnMgdG8gZXZhbHVhdGUgeW91ciBjbHVzdGVycy4KCkluIHRoaXMgc2VjdGlvbiwgd2UnbGwgcHJlc2VudCBleGFtcGxlcyBvZiBob3cgeW91IGNhbiBjYWxjdWxhdGUgdGhlIHNpbGhvdWV0dGUgd2lkdGgsIG5laWdoYm9yaG9vZCBwdXJpdHksIGFuZCBjbHVzdGVyIHN0YWJpbGl0eSBmcm9tIGV4aXN0aW5nIGNsdXN0ZXIgYXNzaWdubWVudHMgd2l0aGluIG9iamVjdHMuCgojIyMgRXZhbHVhdGluZyBTZXVyYXQgY2x1c3RlcnMKCklmIHlvdSBhcmUgYW5hbHl6aW5nIHlvdXIgZGF0YSB3aXRoIGEgU2V1cmF0IHBpcGVsaW5lIHRoYXQgaW5jbHVkZXMgY2FsY3VsYXRpbmcgY2x1c3RlcnMsIHlvdSBjYW4gdXNlIGByT3BlblNjUENBYCB0byBldmFsdWF0ZSB0aGVtLgoKVG8gZGVtb25zdHJhdGUgdGhpcywgd2UnbGwgY29udmVydCBvdXIgU0NFIG9iamVjdCB0byBhIFNldXJhdCB1c2luZyB0aGUgZnVuY3Rpb24gYHJPcGVuU2NQQ0E6OnNjZV90b19zZXVyYXQoKWAuClRoZW4sIHdlJ2xsIHVzZSBhIHNpbXBsZSBTZXVyYXQgcGlwZWxpbmUgdG8gb2J0YWluIGNsdXN0ZXJzLgoKYGBge3Igc2NlIHRvIHNldXJhdCwgbWVzc2FnZSA9IEZBTFNFfQojIENvbnZlcnQgdGhlIFNDRSB0byBhIFNldXJhdCBvYmplY3QgdXNpbmcgck9wZW5TY1BDQQpzZXVyYXRfb2JqIDwtIHJPcGVuU2NQQ0E6OnNjZV90b19zZXVyYXQoc2NlKQoKIyBDYWxjdWxhdGUgY2x1c3RlcnMgd2l0aCBTZXVyYXQgdXNpbmcgYSBzdGFuZGFyZCBTZXVyYXQgcGlwZWxpbmUsIGZvciBleGFtcGxlCnNldXJhdF9vYmogPC0gc2V1cmF0X29iaiB8PgogIFNDVHJhbnNmb3JtKCkgfD4KICBSdW5QQ0EoKSB8PgogIEZpbmROZWlnaGJvcnMoKSB8PgogIEZpbmRDbHVzdGVycygpCgpzZXVyYXRfb2JqCmBgYAoKClRvIGNhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIHRoZXNlIGNsdXN0ZXJzLCB3ZSdsbCBuZWVkIHRvIGNyZWF0ZSBhIGRhdGEgZnJhbWUgd2l0aCBjb2x1bW5zIGBjZWxsX2lkYCBhbmQgYGNsdXN0ZXJgOgoKYGBge3IgcHJlcGFyZSBzZXVyYXQgaW5wdXR9CiMgQ3JlYXRlIGEgZGF0YSBmcmFtZSBmb3IgaW5wdXQKc2V1cmF0X2NsdXN0ZXJfZGYgPC0gZGF0YS5mcmFtZSgKICBjZWxsX2lkID0gY29sbmFtZXMoc2V1cmF0X29iaiksCiAgY2x1c3RlciA9IHNldXJhdF9vYmokc2V1cmF0X2NsdXN0ZXJzCikKCmhlYWQoc2V1cmF0X2NsdXN0ZXJfZGYpCmBgYAoKTm93LCB3ZSBjYW4gcnVuIGByT3BlblNjUENBOjpjYWxjdWxhdGVfc2lsaG91ZXR0ZSgpYCBhbmQgYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9wdXJpdHkoKWAgdXNpbmcgdGhpcyBkYXRhIGZyYW1lIGFuZCB0aGUgU2V1cmF0IG9iamVjdDoKCmBgYHtyIHNldXJhdCBzaWxob3VldHRlfQpzZXVyYXRfc2lsaG91ZXR0ZV9kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfc2lsaG91ZXR0ZSgKICBzZXVyYXRfb2JqLAogIHNldXJhdF9jbHVzdGVyX2RmCikKYGBgCgpgYGB7ciBzZXVyYXQgcHVyaXR5fQpzZXVyYXRfcHVyaXR5X2RmIDwtIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9wdXJpdHkoCiAgc2V1cmF0X29iaiwKICBzZXVyYXRfY2x1c3Rlcl9kZgopCmBgYAoKV2UgZG8gbm90IHJlY29tbWVuZCB1c2luZyBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgpYCBvbiBTZXVyYXQgY2x1c3RlcnMgZHVlIHRvIGRpZmZlcmVuY2VzIGluIHRoZSB1bmRlcmx5aW5nIGNsdXN0ZXJpbmcgYXBwcm9hY2ggYmV0d2VlbiBTZXVyYXQgYW5kIHRoZSBgYmx1c3RlcmAgcGFja2FnZSB3aGljaCBgck9wZW5TY1BDQWAgdXNlcy4KCiMjIyBFdmFsdWF0aW5nIFNjUENBIGNsdXN0ZXJzCgpTY1BDQSBjZWxsIG1ldGFkYXRhIGFscmVhZHkgY29udGFpbnMgYSBjb2x1bW4gY2FsbGVkIGBjbHVzdGVyYCB3aXRoIHJlc3VsdHMgZnJvbSBhbiBhdXRvbWF0ZWQgY2x1c3RlcmluZy4KVGhlc2UgY2x1c3RlcnMgd2VyZSBjYWxjdWxhdGVkIHVzaW5nIGBibHVzdGVyYCwgdGhlIHNhbWUgdG9vbCB0aGF0IGByT3BlblNjUENBYCB1c2VzLgpUaGUgc3BlY2lmaWNhdGlvbnMgdXNlZCBmb3IgdGhpcyBjbHVzdGVyaW5nIGFyZSBzdG9yZWQgaW4gdGhlIFNDRSBvYmplY3QncyBtZXRhZGF0YSwgYXMgZm9sbG93czsgbm90ZSB0aGF0IGFsbCBvdGhlciBjbHVzdGVyaW5nIHBhcmFtZXRlcnMgd2VyZSBsZWZ0IGF0IHRoZWlyIGRlZmF1bHQgdmFsdWVzLgoKKiBgbWV0YWRhdGEoc2NlKSRjbHVzdGVyX2FsZ29yaXRobWA6IFRoZSBjbHVzdGVyaW5nIGFsZ29yaXRobSB1c2VkCiogYG1ldGFkYXRhKHNjZSkkY2x1c3Rlcl93ZWlnaHRpbmdgOiBUaGUgd2VpZ2h0aW5nIHNjaGVtZSB1c2VkCiogYG1ldGFkYXRhKHNjZSkkY2x1c3Rlcl9ubmA6IFRoZSBudW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMgdXNlZAoKWW91IGNhbiBzZWUgYWxsIHRoZWlyIHZhbHVlcyBoZXJlOgoKCmBgYHtyIGV4dHJhY3QgY2x1c3RlciBwYXJhbXN9CiMgUHJpbnQgY2x1c3RlcmluZyBzcGVjaWZpY2F0aW9ucwptZXRhZGF0YShzY2UpW2MoImNsdXN0ZXJfYWxnb3JpdGhtIiwgImNsdXN0ZXJfd2VpZ2h0aW5nIiwgImNsdXN0ZXJfbm4iKV0KYGBgCgoKSW4gdGhpcyBleGFtcGxlLCB3ZSdsbCBzaG93IGhvdyB0byB1c2UgdGhlIGNsdXN0ZXIgZXZhbHVhdGlvbiBmdW5jdGlvbnMgb24gdGhlc2UgY2x1c3RlcnMuCgpUbyBiZWdpbiwgd2UnbGwgcHJlcGFyZSBhIGRhdGEgZnJhbWUgd2l0aCB0d28gY29sdW1uczogYGNlbGxfaWRgIGNvbnRhaW5pbmcgY2VsbCBiYXJjb2RlcywgYW5kIGBjbHVzdGVyYCBjb250YWluaW5nIHRoZSBjbHVzdGVyIGlkZW50aXRpZXMuCgpgYGB7ciBwcmVwYXJlIHNjcGNhIGRhdGEgZnJhbWV9CnNjcGNhX2NsdXN0ZXJfZGYgPC0gZGF0YS5mcmFtZSgKICBjZWxsX2lkID0gY29sbmFtZXMoc2NlKSwKICBjbHVzdGVyID0gc2NlJGNsdXN0ZXIKKQoKaGVhZChzY3BjYV9jbHVzdGVyX2RmKQpgYGAKCldlIGNhbiBydW4gZXZhbHVhdGlvbiBmdW5jdGlvbnMgdXNpbmcgdGhpcyBkYXRhIGZyYW1lIGFuZCB0aGUgU0NFIG9iamVjdC4KCmBgYHtyIHNjcGNhIHNpbGhvdWV0dGV9CiMgQ2FsY3VsYXRlIHNpbGhvdWV0dGUgd2lkdGgKc2NwY2Ffc2lsaG91ZXR0ZV9kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfc2lsaG91ZXR0ZSgKICBzY2UsCiAgc2NwY2FfY2x1c3Rlcl9kZgopCmBgYAoKYGBge3Igc2NwY2EgcHVyaXR5fQojIENhbGN1bGF0ZSBuZWlnaGJvcmhvb2QgcHVyaXR5CnNjcGNhX3B1cml0eV9kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfcHVyaXR5KAogIHNjZSwKICBzY3BjYV9jbHVzdGVyX2RmCikKYGBgCgpXaGVuIHJ1bm5pbmcgYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zdGFiaWxpdHkoKWAsIHdlJ2xsIHNwZWNpZnkgdGhlIHNhbWUgcGFyYW1ldGVycyBvcmlnaW5hbGx5IHVzZWQgdG8gYnVpbGQgdGhlIGNsdXN0ZXJzIGJ5IGV4dHJhY3RpbmcgdGhlbSBmcm9tIHRoZSBtZXRhZGF0YS4KV2UnbGwgbmVlZCB0byBlbnN1cmUgdGhlIHByb3ZpZGVkIGFyZ3VtZW50cyBhcmUgbG93ZXJjYXNlLCBhcyB3ZWxsLgoKR2VuZXJhbGx5IHNwZWFraW5nLCB3ZSBvbmx5IHJlY29tbWVuZCBldmFsdWF0aW5nIGNsdXN0ZXJzIHdpdGggYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zdGFiaWxpdHkoKWAgaWYgeW91IGtub3cgdGhlIG9yaWdpbmFsIHBhcmFtZXRlcnMgdXNlZC4KCgpgYGB7ciBzY3BjYSBzdGFiaWxpdHl9CnNjcGNhX3N0YWJpbGl0eV9kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KAogIHNjZSwKICBzY3BjYV9jbHVzdGVyX2RmLAogICMgcHJvdmlkZSBTY1BDQSBjbHVzdGVyaW5nIHBhcmFtZXRlcnMgYnkgZXh0cmFjdGluZyBmcm9tIHRoZSBTQ0UgbWV0YWRhdGEKICBhbGdvcml0aG0gPSB0b2xvd2VyKG1ldGFkYXRhKHNjZSkkY2x1c3Rlcl9hbGdvcml0aG0pLAogIHdlaWdodGluZyA9IHRvbG93ZXIobWV0YWRhdGEoc2NlKSRjbHVzdGVyX3dlaWdodGluZyksCiAgbm4gPSBtZXRhZGF0YShzY2UpJGNsdXN0ZXJfbm4KKQpgYGAKCgojIyBTYXZpbmcgY2x1c3RlcmluZyByZXN1bHRzCgpSZXN1bHRzIGNhbiBlaXRoZXIgYmUgZGlyZWN0bHkgZXhwb3J0ZWQgYXMgYSBUU1YgZmlsZSAoZS5nLiwgd2l0aCBgcmVhZHI6OndyaXRlX3RzdigpYCksIG9yIHlvdSBjYW4gYWRkIHRoZSByZXN1bHRzIGludG8geW91ciBTQ0Ugb3IgU2V1cmF0IG9iamVjdC4KVGhlIHN1YnNlcXVlbnQgZXhhbXBsZXMgd2lsbCBkZW1vbnN0cmF0ZSBzYXZpbmcgdGhlIGNsdXN0ZXIgYXNzaWdubWVudHMgc3RvcmVkIGluIGBjbHVzdGVyX3Jlc3VsdHNfZGYkY2x1c3RlcmAgdG8gYW4gU0NFIGFuZCBhIFNldXJhdCBvYmplY3QuCgpfQSB3b3JkIG9mIGNhdXRpb24hXwpPYmplY3RzIGZyb20gdGhlIFNjUENBIFBvcnRhbCBhbHJlYWR5IGNvbnRhaW4gYSBjb2x1bW4gY2FsbGVkIGBjbHVzdGVyYCB3aXRoIHJlc3VsdHMgZnJvbSBhbiBhdXRvbWF0ZWQgY2x1c3RlcmluZy4KVGhlc2UgYXV0b21hdGljIGNsdXN0ZXJzIHdlcmUgbm90IGV2YWx1YXRlZCwgYW5kIHRoZWlyIHBhcmFtZXRlcnMgd2VyZSBub3Qgb3B0aW1pemVkIGZvciBhbnkgZ2l2ZW4gbGlicmFyeS4KVG8gYXZvaWQgYW1iaWd1aXR5IGJldHdlZW4gdGhlIGV4aXN0aW5nIGFuZCBuZXcgY2x1c3RlcmluZyByZXN1bHRzLCB3ZSdsbCBuYW1lIHRoZSBuZXcgY29sdW1uIGByb3BlbnNjcGNhX2NsdXN0ZXJgLgoKIyMjIFNhdmluZyByZXN1bHRzIHRvIGFuIFNDRSBvYmplY3QKCldlIGNhbiBhZGQgY29sdW1ucyB0byBhbiBTQ0Ugb2JqZWN0J3MgYGNvbERhdGFgIHRhYmxlIGJ5IGRpcmVjdGx5IGNyZWF0aW5nIGEgY29sdW1uIGluIHRoZSBvYmplY3Qgd2l0aCBgJGAuCkJlZm9yZSB3ZSBkbyBzbywgd2UnbGwgY29uZmlybSB0aGF0IHRoZSBjbHVzdGVycyBhcmUgaW4gdGhlIHNhbWUgb3JkZXIgYXMgdGhlIFNDRSBvYmplY3QgYnkgY29tcGFyaW5nIGNlbGwgaWRzOgoKYGBge3IgY2hlY2sgc2NlIG9yZGVyfQphbGwuZXF1YWwoCiAgY29sbmFtZXMoc2NlKSwKICBjbHVzdGVyX3Jlc3VsdHNfZGYkY2VsbF9pZAopCmBgYAoKYGBge3IgYWRkIHRvIHNjZX0KIyBBZGQgY2x1c3RlciByZXN1bHRzIHRvIHRoZSBjb2xEYXRhCnNjZSRyb3BlbnNjcGNhX2NsdXN0ZXIgPC0gY2x1c3Rlcl9yZXN1bHRzX2RmJGNsdXN0ZXIKYGBgCgojIyMgU2F2aW5nIHJlc3VsdHMgdG8gYSBTZXVyYXQgb2JqZWN0CgoKV2UgY2FuIGFkZCBjb2x1bW5zIHRvIGFuIFNldXJhdCBvYmplY3QncyBjZWxsIG1ldGFkYXRhIHRhYmxlIGJ5IGRpcmVjdGx5IGNyZWF0aW5nIGEgY29sdW1uIGluIHRoZSBvYmplY3Qgd2l0aCBgJGAgKG5vdGUgdGhhdCB5b3UgY2FuIGFsc28gdXNlIHRoZSBTZXVyYXQgZnVuY3Rpb24gYEFkZE1ldGFEYXRhKClgKS4KQmVmb3JlIHdlIGRvIHNvLCB3ZSdsbCBjb25maXJtIHRoYXQgdGhlIGNsdXN0ZXJzIGFyZSBpbiB0aGUgc2FtZSBvcmRlciBhcyB0aGUgU2V1cmF0IG9iamVjdCBieSBjb21wYXJpbmcgY2VsbCBpZHM6CgoKYGBge3IgY2hlY2sgc2V1cmF0IG9yZGVyfQphbGwuZXF1YWwoCiAgY29sbmFtZXMoc2V1cmF0X29iaiksCiAgY2x1c3Rlcl9yZXN1bHRzX2RmJGNlbGxfaWQKKQpgYGAKCmBgYHtyIGFkZCB0byBzZXVyYXR9CiMgQWRkIGNsdXN0ZXIgcmVzdWx0cyB0byB0aGUgY2VsbCBtZXRhZGF0YQpzZXVyYXRfb2JqJHJvcGVuc2NwY2FfY2x1c3RlciA8LSBjbHVzdGVyX3Jlc3VsdHNfZGYkY2x1c3RlcgpgYGAKCgojIyBTZXNzaW9uIEluZm8KCmBgYHtyIHNlc3Npb24gaW5mb30KIyByZWNvcmQgdGhlIHZlcnNpb25zIG9mIHRoZSBwYWNrYWdlcyB1c2VkIGluIHRoaXMgYW5hbHlzaXMgYW5kIG90aGVyIGVudmlyb25tZW50IGluZm9ybWF0aW9uCnNlc3Npb25JbmZvKCkKYGBgCg==
diff --git a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd new file mode 100644 index 000000000..6ba7df341 --- /dev/null +++ b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd @@ -0,0 +1,456 @@ +--- +title: "Comparing clustering parameters with rOpenScPCA" +date: "`r Sys.Date()`" +author: "Data Lab" +output: + html_notebook: + toc: yes + toc_float: yes + df_print: paged +--- + +## Introduction + +Clustering algorithms have several parameters which can be varied, leading to different clustering results. +A key question when clustering, therefore, is how to identify a set of parameters that lead to robust and reliable clusters that can be used in downstream analysis. + +This notebook provides examples of how to use the `rOpenScPCA` package to: + +* Calculate several versions of clustering results across several different parameterizations +* Calculate QC metrics on across clustering results + +Please refer to the [`01_perform-evaluate-clustering.Rmd`](01_perform-evaluate-clustering.Rmd) notebook for a tutorial on using `rOpenScPCA` functions to: + +* Calculate clusters from a single parameterization +* Calculate QC metrics on a single set of clusters, as well as explanations of the metrics themselves + +This notebook will use the sample `SCPCS000001` from project `SCPCP000001`, which is assumed present in the `OpenScPCA-analysis/data/current/SCPCP000001` directory, for all examples. +Please [see this documentation](https://openscpca.readthedocs.io/en/latest/getting-started/accessing-resources/getting-access-to-data/) for more information about obtaining ScPCA data. + +## Setup + +### Packages + + +```{r packages} +library(rOpenScPCA) + +suppressPackageStartupMessages({ + library(SingleCellExperiment) + library(ggplot2) + library(patchwork) +}) + +# Set ggplot theme for plots +theme_set(theme_bw()) +``` + + +### Paths + +```{r base paths} +# The base path for the OpenScPCA repository +repository_base <- rprojroot::find_root(rprojroot::has_dir(".github")) + +# The current data directory, found within the repository base directory +data_dir <- file.path(repository_base, "data", "current") + +# The path to this module +module_base <- file.path(repository_base, "analyses", "hello-clusters") +``` + +```{r input file path} +# Path to processed SCE file for sample SCPCS000001 +input_sce_file <- file.path(data_dir, "SCPCP000001", "SCPCS000001", "SCPCL000001_processed.rds") +``` + + +### Set the random seed + +Because clustering involves random sampling, it is important to set the random seed at the top of your analysis script or notebook to ensure reproducibility. + +```{r set seed} +set.seed(2024) +``` + +## Read in and prepare data + +To begin, we'll read in the `SingleCellExperiment` (SCE) object. + +```{r read data} +# Read the SCE file +sce <- readRDS(input_sce_file) +``` + +For the initial cluster calculations and evaluations, we will use the PCA matrix extracted from the SCE object. +As shown in [`01_perform-evaluate-clustering.Rmd`](01_perform-evaluate-clustering.Rmd), it is also possible to use an SCE object or a Seurat object directly. + + +```{r extract pca data} +# Extract the PCA matrix from an SCE object +pca_matrix <- reducedDim(sce, "PCA") +``` + +## Varying a single clustering parameter + +This section will show how to perform clustering across a set of parameters (aka, "sweep" a set of parameters) with `rOpenScPCA::sweep_clusters()`. + +This function takes a PCA matrix with row names representing unique cell ids (e.g., barcodes) as its primary argument, with additional arguments for cluster parameters. +This function wraps the `rOpenScPCA::calculate_clusters()` function but allows you to provide a vector of parameter values to perform clustering across, as listed below. +Clusters will be calculated for all combinations of parameters values (where applicable); default values that the function will use for any unspecified parameter values are shown in parentheses. + +* `algorithm`: Which clustering algorithm to use (Louvain) +* `weighting`: Which weighting scheme to use (Jaccard) +* `nn`: The number of nearest neighbors (10) +* `resolution`: The resolution parameter (1; used only with Louvain and Leiden clustering) +* `objective_function`: The objective function to optimize clusters (CPM; used only with Leiden clustering) + +`rOpenScPCA::sweep_clusters()` does not allow you to specify values for any other parameters. + + +This function will return a list of data frames of clustering results. +Each data frame will have the following columns: + +* `cell_id`: Unique cell identifiers, obtained from the PCA matrix's row names +* `cluster`: A factor column with the cluster identities +* There will be one column for each clustering parameter used + +To demonstrate this function, we'll calculate clusters using the Louvain algorithm while varying the `nn` parameter: + +```{r sweep clusters} +# Define nn parameter values of interest +nn_values <- c(10, 20, 30) + +# Calculate clusters varying nn, but leaving other parameters at their default values +cluster_results_list <- rOpenScPCA::sweep_clusters( + pca_matrix, + nn = nn_values +) +``` + +The resulting list has a length of three, one data frame for each `nn` parameter tested: + +```{r length cluster_results_list} +length(cluster_results_list) +``` + +It can be helpful (although it is not strictly necessary to keep track) to name this list by the varied `nn` parameter. +In this case, we'll use these names to label plots. + +```{r set list names} +names(cluster_results_list) <- glue::glue("nn_{nn_values}") +``` + + +We can look at the first few rows of each data frame using [`purrr::map()`](https://purrr.tidyverse.org/reference/map.html) to iterate over the list: + + +```{r map cluster_results_list} +cluster_results_list |> + purrr::map(head) +``` + +Generally speaking, `purrr::map()` can be used to iterate over this list to visualize or analyze each clustering result on its own; we'll use this approach in the following sections. + +### Visualizing clustering results + +When comparing clustering results, it's important to first visualize the different clusterings to build context for interpreting QC metrics. + +As one example of why this is important, we generally expect that more robust clusters will have higher values for metrics like silhouette width and neighborhood purity. +However, we also expect that having fewer clusters in the first place will also lead to higher metrics, regardless of cluster quality: When there are fewer clusters, it is more likely that clusters overlap less with one another just because there aren't many clusters in the first place. +This means that, when interpreting cluster quality metrics, you should be careful to take more context about the data into consideration and not only rely on the metric values. + +We'll therefore visualize these results as UMAPs by iterating over `cluster_results_list` and combining plots with [`patchwork::wrap_plots()`](https://patchwork.data-imaginist.com/reference/wrap_plots.html). +We'll specifically use [`purrr::imap()`](https://purrr.tidyverse.org/reference/imap.html) to iterate so that we can assign this list's names as plot titles. + +For this, we'll begin by extracting a table of UMAP coordinates from our SCE object. + +```{r create umap_df} +umap_df <- reducedDim(sce, "UMAP") |> + as.data.frame() +``` + +Next, we'll iterate over `cluster_results_list` to plot the UMAPs. + +```{r plot nn umaps, fig.width = 12} +umap_plots <- cluster_results_list |> + purrr::imap( + \(cluster_df, clustering_name) { + # Add a column with cluster assignments to umap_df + umap_df_plot <- umap_df |> + dplyr::mutate(cluster = cluster_df$cluster) + + # Plot the UMAP, colored by the new cluster variable + ggplot(umap_df_plot, aes(x = UMAP1, y = UMAP2, color = cluster)) + + geom_point(alpha = 0.6) + + labs(title = glue::glue("nearest neighbors: {clustering_name}")) + + # We'll add a couple UMAP plot settings here, including equal axes and + # turning off the axis ticks and text since UMAP coordinates are not meaningful + coord_equal() + + theme( + axis.ticks = element_blank(), + axis.text = element_blank() + ) + } + ) + +# Print the plots with patchwork::wrap_plots() +patchwork::wrap_plots(umap_plots, ncol = 3) +``` + +These plots show that the number of clusters decreases as the nearest neighbors parameter increases, with between 9-13 clusters. + + + + +### Evaluating clustering results + +This section will use `purrr::map()` to iterate over each clustering result data frame to calculate silhouette width, neighborhood purity, and stability, and then visualize results. +The goal of this code is to identify whether one clustering parameterization produces more reliable clusters. + + +#### Silhouette width and neighborhood purity + +Both silhouette width and neighborhood purity are cell-level quantities, so we can calculate them together in the same call to `purrr::map()`. +Below, we'll iterate over each data frame in `cluster_results_list` to calculate these quantities. + +```{r calculate cell level metrics} +cell_metric_list <- cluster_results_list |> + purrr::map( + \(cluster_df) { + # calculate silhouette width + silhouette_df <- rOpenScPCA::calculate_silhouette(pca_matrix, cluster_df) + + # calculate neighbhorhood purity, starting from silhouette_df + rOpenScPCA::calculate_purity(pca_matrix, silhouette_df) + } + ) + +# View the first six rows of each clustering result's cell-level QC metrics +purrr::map(cell_metric_list, head) +``` + + +To visualize these results, we can combine all data frames in this list into a single overall data frame, where the existing `nn` column will distinguish among conditions. + +```{r combine cell metrics list} +cell_metrics_df <- purrr::list_rbind(cell_metric_list) +``` + +We can visualize silhouette width and neighborhood purity each with boxplots, for example, and use the [`patchwork`](https://patchwork.data-imaginist.com/) package to print them together: + + +```{r} +# Plot silhouette width +silhouette_plot <- ggplot(cell_metrics_df) + + aes(x = as.factor(nn), y = silhouette_width, fill = as.factor(nn)) + + geom_boxplot() + + scale_fill_brewer(palette = "Pastel2") + + labs( + x = "Number of nearest neighbors", + y = "Silhouette width" + ) + + +# Plot neighborhood purity width +purity_plot <- ggplot(cell_metrics_df) + + aes(x = as.factor(nn), y = purity, fill = as.factor(nn)) + + geom_boxplot() + + scale_fill_brewer(palette = "Pastel2") + + labs( + x = "Number of nearest neighbors", + y = "Neighborhood purity" + ) + + +# Add together using the patchwork library, without a legend +silhouette_plot + purity_plot & theme(legend.position = "none") +``` + +While there does not appear to be a salient difference among silhouette width distributions, it does appear that purity is higher with a higher nearest neighbors parameter. +It's worth noting that this trend in purity values is expected: Higher nearest neighbor parameter values lead to fewer clusters, and neighborhood purity tends to be higher when there are fewer clusters. + + +#### Stability + +Next, we'll calculate stability on the clusters using `rOpenScPCA::calculate_stability()`, specifying the same parameter used for the original cluster calculation at each iteration. + +```{r calculate stability} +stability_list <- cluster_results_list |> + purrr::map( + \(cluster_df) { + nn <- cluster_df$nn[1] # all rows have the same `nn` parameter, so we'll take the first + + # calculate stability, passing in the parameter value used for this iteration + rOpenScPCA::calculate_stability(pca_matrix, cluster_df, nn = nn) + } + ) +``` + +We'll again combine the output of `stability_list` into a single data frame and plot `ari` values across `nn` parameterizations. + + +```{r combine plot stability} +stability_df <- purrr::list_rbind(stability_list) + +ggplot(stability_df) + + aes(x = as.factor(nn), y = ari, fill = as.factor(nn)) + + geom_boxplot() + + scale_fill_brewer(palette = "Pastel2") + + labs( + x = "Number of nearest neighbors", + y = "Adjusted Rand Index" + ) + + theme(legend.position = "none") +``` + +Here, we see that a nearest neighbors value of 20 or 30 leads to more stable clustering results compared to 10. + + +## Varying multiple clustering parameters + +The previous section demonstrated how to calculate clusters and QC metrics when varying one parameter, but it is possible to vary multiple parameters at once with `rOpenScPCA::sweep_clusters()`. +In this section, we'll show an overview of how you might write code to vary two parameters at once (here, nearest neighbors and resolution as examples) and visualize results. + + +```{r sweep two parameters} +# Define vectors of parameters to vary +nn_values <- c(10, 20, 30) +res_values <- c(0.5, 1.0, 1.5) + + +cluster_results_list <- rOpenScPCA::sweep_clusters( + pca_matrix, + nn = nn_values, + resolution = res_values +) +``` + +This resulting list now has 9 different clustering results, for each combination of `nn_values` and `res_values`: + + +```{r length cluster_results_list two parameters} +length(cluster_results_list) +``` + + +### Visualize clusters + +Next, we'll iterate over `cluster_results_list` to plot the UMAPs. +This time, we'll use `purrr::map()` and pull out parameters from each iteration's `cluster_df` to form the UMAP panel title. + +```{r plot nn res umaps, fig.height = 14} +umap_plots <- cluster_results_list |> + purrr::map( + \(cluster_df) { + # Add a column with cluster assignments to umap_df + umap_df_plot <- umap_df |> + dplyr::mutate(cluster = cluster_df$cluster) + + # Create a title for the UMAP with both parameters + umap_title <- glue::glue( + "nn: {cluster_df$nn[1]}; res: {cluster_df$resolution[1]}" + ) + + # Plot the UMAP, colored by the new cluster variable + ggplot(umap_df_plot, aes(x = UMAP1, y = UMAP2, color = cluster)) + + geom_point(alpha = 0.6) + + labs(title = umap_title) + + # We'll add a couple UMAP-specific plot settings + coord_equal() + + theme( + axis.ticks = element_blank(), + axis.text = element_blank(), + # Ensure legends fit in the figure + legend.position = "bottom", + legend.key.size = unit(0.2, "cm") + ) + } + ) + +# Print the plots with patchwork::wrap_plots() +patchwork::wrap_plots(umap_plots, ncol = 3) +``` + + + +### Calculate and visualize QC metrics + +This section presents one coding strategy to calculate and visualize results when varying two clustering parameters. +In particular, we use faceting to help display all information in one plot, by placing nearest neighbor values on the X-axis and faceting by resolution values. +Since silhouette width and neighhorbood purity calculations using generally similar code, we'll just show neighborhood purity here. + +#### Neighborhood purity + +First, we'll calculate neighborhood purity and combine results into a single data frame. + +```{r calculate purity two parameters} +purity_df <- cluster_results_list |> + purrr::map( + \(cluster_df) { + rOpenScPCA::calculate_purity(pca_matrix, cluster_df) + } + ) |> + purrr::list_rbind() +``` + + +```{r visualize purity two parameters} +ggplot(purity_df) + + aes(x = as.factor(nn), y = purity, fill = as.factor(nn)) + + geom_boxplot() + + scale_fill_brewer(palette = "Pastel2") + + # facet by resolution, labeling panels with both the resolution column name and value + facet_wrap(vars(resolution), labeller = label_both) + + labs( + x = "Number of nearest neighbors", + y = "Neighborhood purity" + ) + + theme(legend.position = "none") +``` + +### Stability + +Similar to above, we'll calculate stability, combine results into a single data frame, add a `resolution_label` column to support plot interpretation, and finally make our plot. + +```{r calculate stability two parameters} +stability_df <- cluster_results_list |> + purrr::map( + \(cluster_df) { + # Extract parameters for this clustering result + nn <- unique(cluster_df$nn) + resolution <- unique(cluster_df$resolution) + + rOpenScPCA::calculate_stability( + pca_matrix, + cluster_df, + nn = nn, + resolution = resolution + ) + } + ) |> + purrr::list_rbind() +``` + + +```{r visualize stability two parameters} +ggplot(stability_df) + + aes(x = as.factor(nn), y = ari, fill = as.factor(nn)) + + geom_boxplot() + + scale_fill_brewer(palette = "Pastel2") + + facet_wrap(vars(resolution), labeller = label_both) + + labs( + x = "Number of nearest neighbors", + y = "Adjusted Rand Index" + ) + + theme(legend.position = "none") +``` + + + +## Session Info + +```{r session info} +# record the versions of the packages used in this analysis and other environment information +sessionInfo() +``` diff --git a/analyses/hello-clusters/02_compare-clustering-parameters.nb.html b/analyses/hello-clusters/02_compare-clustering-parameters.nb.html new file mode 100644 index 000000000..76257489e --- /dev/null +++ b/analyses/hello-clusters/02_compare-clustering-parameters.nb.html @@ -0,0 +1,3729 @@ + + + + + + + + + + + + + + + +Comparing clustering parameters with rOpenScPCA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+ +
+ + + + + + + + +
+

Introduction

+

Clustering algorithms have several parameters which can be varied, +leading to different clustering results. A key question when clustering, +therefore, is how to identify a set of parameters that lead to robust +and reliable clusters that can be used in downstream analysis.

+

This notebook provides examples of how to use the +rOpenScPCA package to:

+
    +
  • Calculate several versions of clustering results across several +different parameterizations
  • +
  • Calculate QC metrics on across clustering results
  • +
+

Please refer to the 01_perform-evaluate-clustering.Rmd +notebook for a tutorial on using rOpenScPCA functions +to:

+
    +
  • Calculate clusters from a single parameterization
  • +
  • Calculate QC metrics on a single set of clusters, as well as +explanations of the metrics themselves
  • +
+

This notebook will use the sample SCPCS000001 from +project SCPCP000001, which is assumed present in the +OpenScPCA-analysis/data/current/SCPCP000001 directory, for +all examples. Please see +this documentation for more information about obtaining ScPCA +data.

+
+
+

Setup

+
+

Packages

+ + + +
library(rOpenScPCA)
+
+suppressPackageStartupMessages({
+  library(SingleCellExperiment)
+  library(ggplot2)
+  library(patchwork)
+})
+ + +
Warning: package 'S4Vectors' was built under R version 4.4.1
+ + +
Warning: package 'IRanges' was built under R version 4.4.1
+ + +
# Set ggplot theme for plots
+theme_set(theme_bw())
+ + + +
+
+

Paths

+ + + +
# The base path for the OpenScPCA repository
+repository_base <- rprojroot::find_root(rprojroot::has_dir(".github"))
+
+# The current data directory, found within the repository base directory
+data_dir <- file.path(repository_base, "data", "current")
+
+# The path to this module
+module_base <- file.path(repository_base, "analyses", "hello-clusters")
+ + + + + + +
# Path to processed SCE file for sample SCPCS000001
+input_sce_file <- file.path(data_dir, "SCPCP000001", "SCPCS000001", "SCPCL000001_processed.rds")
+ + + +
+
+

Set the random seed

+

Because clustering involves random sampling, it is important to set +the random seed at the top of your analysis script or notebook to ensure +reproducibility.

+ + + +
set.seed(2024)
+ + + +
+
+
+

Read in and prepare data

+

To begin, we’ll read in the SingleCellExperiment (SCE) +object.

+ + + +
# Read the SCE file
+sce <- readRDS(input_sce_file)
+ + + +

For the initial cluster calculations and evaluations, we will use the +PCA matrix extracted from the SCE object. As shown in 01_perform-evaluate-clustering.Rmd, +it is also possible to use an SCE object or a Seurat object +directly.

+ + + +
# Extract the PCA matrix from an SCE object
+pca_matrix <- reducedDim(sce, "PCA")
+ + + +
+
+

Varying a single clustering parameter

+

This section will show how to perform clustering across a set of +parameters (aka, “sweep” a set of parameters) with +rOpenScPCA::sweep_clusters().

+

This function takes a PCA matrix with row names representing unique +cell ids (e.g., barcodes) as its primary argument, with additional +arguments for cluster parameters. This function wraps the +rOpenScPCA::calculate_clusters() function but allows you to +provide a vector of parameter values to perform clustering across, as +listed below. Clusters will be calculated for all combinations of +parameters values (where applicable); default values that the function +will use for any unspecified parameter values are shown in +parentheses.

+
    +
  • algorithm: Which clustering algorithm to use +(Louvain)
  • +
  • weighting: Which weighting scheme to use (Jaccard)
  • +
  • nn: The number of nearest neighbors (10)
  • +
  • resolution: The resolution parameter (1; used only with +Louvain and Leiden clustering)
  • +
  • objective_function: The objective function to optimize +clusters (CPM; used only with Leiden clustering)
  • +
+

rOpenScPCA::sweep_clusters() does not allow you to +specify values for any other parameters.

+

This function will return a list of data frames of clustering +results. Each data frame will have the following columns:

+
    +
  • cell_id: Unique cell identifiers, obtained from the PCA +matrix’s row names
  • +
  • cluster: A factor column with the cluster +identities
  • +
  • There will be one column for each clustering parameter used
  • +
+

To demonstrate this function, we’ll calculate clusters using the +Louvain algorithm while varying the nn parameter:

+ + + +
# Define nn parameter values of interest
+nn_values <- c(10, 20, 30)
+
+# Calculate clusters varying nn, but leaving other parameters at their default values
+cluster_results_list <- rOpenScPCA::sweep_clusters(
+  pca_matrix,
+  nn = nn_values
+)
+ + + +

The resulting list has a length of three, one data frame for each +nn parameter tested:

+ + + +
length(cluster_results_list)
+ + +
[1] 3
+ + + +

It can be helpful (although it is not strictly necessary to keep +track) to name this list by the varied nn parameter. In +this case, we’ll use these names to label plots.

+ + + +
names(cluster_results_list) <- glue::glue("nn_{nn_values}")
+ + + +

We can look at the first few rows of each data frame using purrr::map() +to iterate over the list:

+ + + +
cluster_results_list |>
+  purrr::map(head)
+ + +
$nn_10
+           cell_id cluster algorithm weighting nn resolution
+1 GGTTAACTCCTCACTG       1   louvain   jaccard 10          1
+2 TGATGGTGTTGGATCT       2   louvain   jaccard 10          1
+3 AGATGCTAGAGCACTG       2   louvain   jaccard 10          1
+4 TGGATGTTCAACTTTC       2   louvain   jaccard 10          1
+5 TTATTGCGTGAGTGAC       2   louvain   jaccard 10          1
+6 AATGACCCAAGGTTGG       1   louvain   jaccard 10          1
+
+$nn_20
+           cell_id cluster algorithm weighting nn resolution
+1 GGTTAACTCCTCACTG       1   louvain   jaccard 20          1
+2 TGATGGTGTTGGATCT       1   louvain   jaccard 20          1
+3 AGATGCTAGAGCACTG       1   louvain   jaccard 20          1
+4 TGGATGTTCAACTTTC       1   louvain   jaccard 20          1
+5 TTATTGCGTGAGTGAC       1   louvain   jaccard 20          1
+6 AATGACCCAAGGTTGG       1   louvain   jaccard 20          1
+
+$nn_30
+           cell_id cluster algorithm weighting nn resolution
+1 GGTTAACTCCTCACTG       1   louvain   jaccard 30          1
+2 TGATGGTGTTGGATCT       1   louvain   jaccard 30          1
+3 AGATGCTAGAGCACTG       1   louvain   jaccard 30          1
+4 TGGATGTTCAACTTTC       1   louvain   jaccard 30          1
+5 TTATTGCGTGAGTGAC       1   louvain   jaccard 30          1
+6 AATGACCCAAGGTTGG       1   louvain   jaccard 30          1
+ + + +

Generally speaking, purrr::map() can be used to iterate +over this list to visualize or analyze each clustering result on its +own; we’ll use this approach in the following sections.

+
+

Visualizing clustering results

+

When comparing clustering results, it’s important to first visualize +the different clusterings to build context for interpreting QC +metrics.

+

As one example of why this is important, we generally expect that +more robust clusters will have higher values for metrics like silhouette +width and neighborhood purity. However, we also expect that having fewer +clusters in the first place will also lead to higher metrics, regardless +of cluster quality: When there are fewer clusters, it is more likely +that clusters overlap less with one another just because there aren’t +many clusters in the first place. This means that, when interpreting +cluster quality metrics, you should be careful to take more context +about the data into consideration and not only rely on the metric +values.

+

We’ll therefore visualize these results as UMAPs by iterating over +cluster_results_list and combining plots with patchwork::wrap_plots(). +We’ll specifically use purrr::imap() +to iterate so that we can assign this list’s names as plot titles.

+

For this, we’ll begin by extracting a table of UMAP coordinates from +our SCE object.

+ + + +
umap_df <- reducedDim(sce, "UMAP") |>
+  as.data.frame()
+ + + +

Next, we’ll iterate over cluster_results_list to plot +the UMAPs.

+ + + +
umap_plots <- cluster_results_list |>
+  purrr::imap(
+    \(cluster_df, clustering_name) {
+      # Add a column with cluster assignments to umap_df
+      umap_df_plot <- umap_df |>
+        dplyr::mutate(cluster = cluster_df$cluster)
+
+      # Plot the UMAP, colored by the new cluster variable
+      ggplot(umap_df_plot, aes(x = UMAP1, y = UMAP2, color = cluster)) +
+        geom_point(alpha = 0.6) +
+        labs(title = glue::glue("nearest neighbors: {clustering_name}")) +
+        # We'll add a couple UMAP plot settings here, including equal axes and
+        # turning off the axis ticks and text since UMAP coordinates are not meaningful
+        coord_equal() +
+        theme(
+          axis.ticks = element_blank(),
+          axis.text = element_blank()
+        )
+    }
+  )
+
+# Print the plots with patchwork::wrap_plots()
+patchwork::wrap_plots(umap_plots, ncol = 3)
+ + +

+ + + +

These plots show that the number of clusters decreases as the nearest +neighbors parameter increases, with between 9-13 clusters.

+
+
+

Evaluating clustering results

+

This section will use purrr::map() to iterate over each +clustering result data frame to calculate silhouette width, neighborhood +purity, and stability, and then visualize results. The goal of this code +is to identify whether one clustering parameterization produces more +reliable clusters.

+
+

Silhouette width and neighborhood purity

+

Both silhouette width and neighborhood purity are cell-level +quantities, so we can calculate them together in the same call to +purrr::map(). Below, we’ll iterate over each data frame in +cluster_results_list to calculate these quantities.

+ + + +
cell_metric_list <- cluster_results_list |>
+  purrr::map(
+    \(cluster_df) {
+      # calculate silhouette width
+      silhouette_df <- rOpenScPCA::calculate_silhouette(pca_matrix, cluster_df)
+
+      # calculate neighbhorhood purity, starting from silhouette_df
+      rOpenScPCA::calculate_purity(pca_matrix, silhouette_df)
+    }
+  )
+
+# View the first six rows of each clustering result's cell-level QC metrics
+purrr::map(cell_metric_list, head)
+ + +
$nn_10
+           cell_id cluster algorithm weighting nn resolution silhouette_other
+1 GGTTAACTCCTCACTG       1   louvain   jaccard 10          1                4
+2 TGATGGTGTTGGATCT       2   louvain   jaccard 10          1                1
+3 AGATGCTAGAGCACTG       2   louvain   jaccard 10          1                1
+4 TGGATGTTCAACTTTC       2   louvain   jaccard 10          1                1
+5 TTATTGCGTGAGTGAC       2   louvain   jaccard 10          1                4
+6 AATGACCCAAGGTTGG       1   louvain   jaccard 10          1                2
+  silhouette_width    purity maximum_neighbor
+1       0.11974670 0.5954806                1
+2       0.16684933 0.8703055                2
+3      -0.01994652 0.4088789                1
+4      -0.00389170 0.3559577                1
+5       0.10148714 0.6340306                2
+6       0.29900703 0.8408082                1
+
+$nn_20
+           cell_id cluster algorithm weighting nn resolution silhouette_other
+1 GGTTAACTCCTCACTG       1   louvain   jaccard 20          1                3
+2 TGATGGTGTTGGATCT       1   louvain   jaccard 20          1                3
+3 AGATGCTAGAGCACTG       1   louvain   jaccard 20          1                3
+4 TGGATGTTCAACTTTC       1   louvain   jaccard 20          1                2
+5 TTATTGCGTGAGTGAC       1   louvain   jaccard 20          1                3
+6 AATGACCCAAGGTTGG       1   louvain   jaccard 20          1                3
+  silhouette_width    purity maximum_neighbor
+1      -0.01856650 0.6014493                1
+2       0.16054653 0.9672087                1
+3       0.17129677 1.0000000                1
+4       0.09659459 0.8061952                1
+5       0.03631923 0.8797615                1
+6       0.08324109 1.0000000                1
+
+$nn_30
+           cell_id cluster algorithm weighting nn resolution silhouette_other
+1 GGTTAACTCCTCACTG       1   louvain   jaccard 30          1                4
+2 TGATGGTGTTGGATCT       1   louvain   jaccard 30          1                4
+3 AGATGCTAGAGCACTG       1   louvain   jaccard 30          1                4
+4 TGGATGTTCAACTTTC       1   louvain   jaccard 30          1                4
+5 TTATTGCGTGAGTGAC       1   louvain   jaccard 30          1                4
+6 AATGACCCAAGGTTGG       1   louvain   jaccard 30          1                4
+  silhouette_width    purity maximum_neighbor
+1      -0.01294996 0.6588463                1
+2       0.15391710 1.0000000                1
+3       0.16327444 1.0000000                1
+4       0.13532673 0.9382301                1
+5       0.03019309 0.9093376                1
+6       0.07406343 1.0000000                1
+ + + +

To visualize these results, we can combine all data frames in this +list into a single overall data frame, where the existing +nn column will distinguish among conditions.

+ + + +
cell_metrics_df <- purrr::list_rbind(cell_metric_list)
+ + + +

We can visualize silhouette width and neighborhood purity each with +boxplots, for example, and use the patchwork +package to print them together:

+ + + +
# Plot silhouette width
+silhouette_plot <- ggplot(cell_metrics_df) +
+  aes(x = as.factor(nn), y = silhouette_width, fill = as.factor(nn)) +
+  geom_boxplot() +
+  scale_fill_brewer(palette = "Pastel2") +
+  labs(
+    x = "Number of nearest neighbors",
+    y = "Silhouette width"
+  )
+
+
+# Plot neighborhood purity width
+purity_plot <- ggplot(cell_metrics_df) +
+  aes(x = as.factor(nn), y = purity, fill = as.factor(nn)) +
+  geom_boxplot() +
+  scale_fill_brewer(palette = "Pastel2") +
+  labs(
+    x = "Number of nearest neighbors",
+    y = "Neighborhood purity"
+  )
+
+
+# Add together using the patchwork library, without a legend
+silhouette_plot + purity_plot & theme(legend.position = "none")
+ + +

+ + + +

While there does not appear to be a salient difference among +silhouette width distributions, it does appear that purity is higher +with a higher nearest neighbors parameter. It’s worth noting that this +trend in purity values is expected: Higher nearest neighbor parameter +values lead to fewer clusters, and neighborhood purity tends to be +higher when there are fewer clusters.

+
+
+

Stability

+

Next, we’ll calculate stability on the clusters using +rOpenScPCA::calculate_stability(), specifying the same +parameter used for the original cluster calculation at each +iteration.

+ + + +
stability_list <- cluster_results_list |>
+  purrr::map(
+    \(cluster_df) {
+      nn <- cluster_df$nn[1] # all rows have the same `nn` parameter, so we'll take the first
+
+      # calculate stability, passing in the parameter value used for this iteration
+      rOpenScPCA::calculate_stability(pca_matrix, cluster_df, nn = nn)
+    }
+  )
+ + + +

We’ll again combine the output of stability_list into a +single data frame and plot ari values across +nn parameterizations.

+ + + +
stability_df <- purrr::list_rbind(stability_list)
+
+ggplot(stability_df) +
+  aes(x = as.factor(nn), y = ari, fill = as.factor(nn)) +
+  geom_boxplot() +
+  scale_fill_brewer(palette = "Pastel2") +
+  labs(
+    x = "Number of nearest neighbors",
+    y = "Adjusted Rand Index"
+  ) +
+  theme(legend.position = "none")
+ + +

+ + + +

Here, we see that a nearest neighbors value of 20 or 30 leads to more +stable clustering results compared to 10.

+
+
+
+
+

Varying multiple clustering parameters

+

The previous section demonstrated how to calculate clusters and QC +metrics when varying one parameter, but it is possible to vary multiple +parameters at once with rOpenScPCA::sweep_clusters(). In +this section, we’ll show an overview of how you might write code to vary +two parameters at once (here, nearest neighbors and resolution as +examples) and visualize results.

+ + + +
# Define vectors of parameters to vary
+nn_values <- c(10, 20, 30)
+res_values <- c(0.5, 1.0, 1.5)
+
+
+cluster_results_list <- rOpenScPCA::sweep_clusters(
+  pca_matrix,
+  nn = nn_values,
+  resolution = res_values
+)
+ + + +

This resulting list now has 9 different clustering results, for each +combination of nn_values and res_values:

+ + + +
length(cluster_results_list)
+ + +
[1] 9
+ + + +
+

Visualize clusters

+

Next, we’ll iterate over cluster_results_list to plot +the UMAPs. This time, we’ll use purrr::map() and pull out +parameters from each iteration’s cluster_df to form the +UMAP panel title.

+ + + +
umap_plots <- cluster_results_list |>
+  purrr::map(
+    \(cluster_df) {
+      # Add a column with cluster assignments to umap_df
+      umap_df_plot <- umap_df |>
+        dplyr::mutate(cluster = cluster_df$cluster)
+
+      # Create a title for the UMAP with both parameters
+      umap_title <- glue::glue(
+        "nn: {cluster_df$nn[1]}; res: {cluster_df$resolution[1]}"
+      )
+
+      # Plot the UMAP, colored by the new cluster variable
+      ggplot(umap_df_plot, aes(x = UMAP1, y = UMAP2, color = cluster)) +
+        geom_point(alpha = 0.6) +
+        labs(title = umap_title) +
+        # We'll add a couple UMAP-specific plot settings
+        coord_equal() +
+        theme(
+          axis.ticks = element_blank(),
+          axis.text = element_blank(),
+          # Ensure legends fit in the figure
+          legend.position = "bottom",
+          legend.key.size = unit(0.2, "cm")
+        )
+    }
+  )
+
+# Print the plots with patchwork::wrap_plots()
+patchwork::wrap_plots(umap_plots, ncol = 3)
+ + +

+ + + +
+
+

Calculate and visualize QC metrics

+

This section presents one coding strategy to calculate and visualize +results when varying two clustering parameters. In particular, we use +faceting to help display all information in one plot, by placing nearest +neighbor values on the X-axis and faceting by resolution values. Since +silhouette width and neighhorbood purity calculations using generally +similar code, we’ll just show neighborhood purity here.

+
+

Neighborhood purity

+

First, we’ll calculate neighborhood purity and combine results into a +single data frame.

+ + + +
purity_df <- cluster_results_list |>
+  purrr::map(
+    \(cluster_df) {
+      rOpenScPCA::calculate_purity(pca_matrix, cluster_df)
+    }
+  ) |>
+  purrr::list_rbind()
+ + + + + + +
ggplot(purity_df) +
+  aes(x = as.factor(nn), y = purity, fill = as.factor(nn)) +
+  geom_boxplot() +
+  scale_fill_brewer(palette = "Pastel2") +
+  # facet by resolution, labeling panels with both the resolution column name and value
+  facet_wrap(vars(resolution), labeller = label_both) +
+  labs(
+    x = "Number of nearest neighbors",
+    y = "Neighborhood purity"
+  ) +
+  theme(legend.position = "none")
+ + +

+ + + +
+
+
+

Stability

+

Similar to above, we’ll calculate stability, combine results into a +single data frame, add a resolution_label column to support +plot interpretation, and finally make our plot.

+ + + +
stability_df <- cluster_results_list |>
+  purrr::map(
+    \(cluster_df) {
+      # Extract parameters for this clustering result
+      nn <- unique(cluster_df$nn)
+      resolution <- unique(cluster_df$resolution)
+
+      rOpenScPCA::calculate_stability(
+        pca_matrix,
+        cluster_df,
+        nn = nn,
+        resolution = resolution
+      )
+    }
+  ) |>
+  purrr::list_rbind()
+ + + + + + +
ggplot(stability_df) +
+  aes(x = as.factor(nn), y = ari, fill = as.factor(nn)) +
+  geom_boxplot() +
+  scale_fill_brewer(palette = "Pastel2") +
+  facet_wrap(vars(resolution), labeller = label_both) +
+  labs(
+    x = "Number of nearest neighbors",
+    y = "Adjusted Rand Index"
+  ) +
+  theme(legend.position = "none")
+ + +

+ + + +
+
+
+

Session Info

+ + + +
# record the versions of the packages used in this analysis and other environment information
+sessionInfo()
+ + +
R version 4.4.0 (2024-04-24)
+Platform: aarch64-apple-darwin20
+Running under: macOS 15.2
+
+Matrix products: default
+BLAS:   /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib 
+LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.0
+
+locale:
+[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
+
+time zone: America/New_York
+tzcode source: internal
+
+attached base packages:
+[1] stats4    stats     graphics  grDevices datasets  utils     methods  
+[8] base     
+
+other attached packages:
+ [1] patchwork_1.3.0             ggplot2_3.5.1              
+ [3] SingleCellExperiment_1.26.0 SummarizedExperiment_1.34.0
+ [5] Biobase_2.64.0              GenomicRanges_1.56.1       
+ [7] GenomeInfoDb_1.40.1         IRanges_2.38.1             
+ [9] S4Vectors_0.42.1            BiocGenerics_0.50.0        
+[11] MatrixGenerics_1.16.0       matrixStats_1.4.1          
+[13] rOpenScPCA_0.1.0           
+
+loaded via a namespace (and not attached):
+ [1] gtable_0.3.5            xfun_0.48               bslib_0.8.0            
+ [4] lattice_0.22-6          pdfCluster_1.0-4        vctrs_0.6.5            
+ [7] tools_4.4.0             generics_0.1.3          parallel_4.4.0         
+[10] tibble_3.2.1            fansi_1.0.6             highr_0.11             
+[13] cluster_2.1.6           BiocNeighbors_1.22.0    pkgconfig_2.0.3        
+[16] Matrix_1.7-0            RColorBrewer_1.1-3      lifecycle_1.0.4        
+[19] GenomeInfoDbData_1.2.12 compiler_4.4.0          farver_2.1.2           
+[22] munsell_0.5.1           bluster_1.14.0          codetools_0.2-20       
+[25] htmltools_0.5.8.1       sass_0.4.9              yaml_2.3.10            
+[28] pillar_1.9.0            crayon_1.5.3            jquerylib_0.1.4        
+[31] tidyr_1.3.1             BiocParallel_1.38.0     DelayedArray_0.30.1    
+[34] cachem_1.1.0            abind_1.4-8             tidyselect_1.2.1       
+[37] digest_0.6.37           dplyr_1.1.4             purrr_1.0.2            
+[40] magic_1.6-1             labeling_0.4.3          rprojroot_2.0.4        
+[43] fastmap_1.2.0           grid_4.4.0              colorspace_2.1-1       
+[46] cli_3.6.3               SparseArray_1.4.8       magrittr_2.0.3         
+[49] S4Arrays_1.4.1          utf8_1.2.4              withr_3.0.1            
+[52] scales_1.3.0            UCSC.utils_1.0.0        rmarkdown_2.28         
+[55] XVector_0.44.0          httr_1.4.7              igraph_2.0.3           
+[58] evaluate_1.0.0          knitr_1.48              geometry_0.5.0         
+[61] rlang_1.1.4             Rcpp_1.0.13             glue_1.8.0             
+[64] BiocManager_1.30.25     renv_1.0.10             jsonlite_1.8.9         
+[67] R6_2.5.1                zlibbioc_1.50.0        
+ + +
+ +
LS0tCnRpdGxlOiAiQ29tcGFyaW5nIGNsdXN0ZXJpbmcgcGFyYW1ldGVycyB3aXRoIHJPcGVuU2NQQ0EiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKYXV0aG9yOiAiRGF0YSBMYWIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojIyBJbnRyb2R1Y3Rpb24KCkNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBoYXZlIHNldmVyYWwgcGFyYW1ldGVycyB3aGljaCBjYW4gYmUgdmFyaWVkLCBsZWFkaW5nIHRvIGRpZmZlcmVudCBjbHVzdGVyaW5nIHJlc3VsdHMuCkEga2V5IHF1ZXN0aW9uIHdoZW4gY2x1c3RlcmluZywgdGhlcmVmb3JlLCBpcyBob3cgdG8gaWRlbnRpZnkgYSBzZXQgb2YgcGFyYW1ldGVycyB0aGF0IGxlYWQgdG8gcm9idXN0IGFuZCByZWxpYWJsZSBjbHVzdGVycyB0aGF0IGNhbiBiZSB1c2VkIGluIGRvd25zdHJlYW0gYW5hbHlzaXMuIAoKVGhpcyBub3RlYm9vayBwcm92aWRlcyBleGFtcGxlcyBvZiBob3cgdG8gdXNlIHRoZSBgck9wZW5TY1BDQWAgcGFja2FnZSB0bzoKCiogQ2FsY3VsYXRlIHNldmVyYWwgdmVyc2lvbnMgb2YgY2x1c3RlcmluZyByZXN1bHRzIGFjcm9zcyBzZXZlcmFsIGRpZmZlcmVudCBwYXJhbWV0ZXJpemF0aW9ucwoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGFjcm9zcyBjbHVzdGVyaW5nIHJlc3VsdHMKClBsZWFzZSByZWZlciB0byB0aGUgW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCkgbm90ZWJvb2sgZm9yIGEgdHV0b3JpYWwgb24gdXNpbmcgYHJPcGVuU2NQQ0FgIGZ1bmN0aW9ucyB0bzoKCiogQ2FsY3VsYXRlIGNsdXN0ZXJzIGZyb20gYSBzaW5nbGUgcGFyYW1ldGVyaXphdGlvbgoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGEgc2luZ2xlIHNldCBvZiBjbHVzdGVycywgYXMgd2VsbCBhcyBleHBsYW5hdGlvbnMgb2YgdGhlIG1ldHJpY3MgdGhlbXNlbHZlcwoKVGhpcyBub3RlYm9vayB3aWxsIHVzZSB0aGUgc2FtcGxlIGBTQ1BDUzAwMDAwMWAgZnJvbSBwcm9qZWN0IGBTQ1BDUDAwMDAwMWAsIHdoaWNoIGlzIGFzc3VtZWQgcHJlc2VudCBpbiB0aGUgYE9wZW5TY1BDQS1hbmFseXNpcy9kYXRhL2N1cnJlbnQvU0NQQ1AwMDAwMDFgIGRpcmVjdG9yeSwgZm9yIGFsbCBleGFtcGxlcy4KUGxlYXNlIFtzZWUgdGhpcyBkb2N1bWVudGF0aW9uXShodHRwczovL29wZW5zY3BjYS5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvZ2V0dGluZy1zdGFydGVkL2FjY2Vzc2luZy1yZXNvdXJjZXMvZ2V0dGluZy1hY2Nlc3MtdG8tZGF0YS8pIGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IG9idGFpbmluZyBTY1BDQSBkYXRhLgoKIyMgU2V0dXAKCiMjIyBQYWNrYWdlcwoKCmBgYHtyIHBhY2thZ2VzfQpsaWJyYXJ5KHJPcGVuU2NQQ0EpCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQp9KQoKIyBTZXQgZ2dwbG90IHRoZW1lIGZvciBwbG90cwp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgoKIyMjIFBhdGhzCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290OjpoYXNfZGlyKCIuZ2l0aHViIikpCgojIFRoZSBjdXJyZW50IGRhdGEgZGlyZWN0b3J5LCBmb3VuZCB3aXRoaW4gdGhlIHJlcG9zaXRvcnkgYmFzZSBkaXJlY3RvcnkKZGF0YV9kaXIgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImRhdGEiLCAiY3VycmVudCIpCgojIFRoZSBwYXRoIHRvIHRoaXMgbW9kdWxlCm1vZHVsZV9iYXNlIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJhbmFseXNlcyIsICJoZWxsby1jbHVzdGVycyIpCmBgYAoKYGBge3IgaW5wdXQgZmlsZSBwYXRofQojIFBhdGggdG8gcHJvY2Vzc2VkIFNDRSBmaWxlIGZvciBzYW1wbGUgU0NQQ1MwMDAwMDEKaW5wdXRfc2NlX2ZpbGUgPC0gZmlsZS5wYXRoKGRhdGFfZGlyLCAiU0NQQ1AwMDAwMDEiLCAiU0NQQ1MwMDAwMDEiLCAiU0NQQ0wwMDAwMDFfcHJvY2Vzc2VkLnJkcyIpCmBgYAoKCiMjIyBTZXQgdGhlIHJhbmRvbSBzZWVkCgpCZWNhdXNlIGNsdXN0ZXJpbmcgaW52b2x2ZXMgcmFuZG9tIHNhbXBsaW5nLCBpdCBpcyBpbXBvcnRhbnQgdG8gc2V0IHRoZSByYW5kb20gc2VlZCBhdCB0aGUgdG9wIG9mIHlvdXIgYW5hbHlzaXMgc2NyaXB0IG9yIG5vdGVib29rIHRvIGVuc3VyZSByZXByb2R1Y2liaWxpdHkuCgpgYGB7ciBzZXQgc2VlZH0Kc2V0LnNlZWQoMjAyNCkKYGBgCgojIyBSZWFkIGluIGFuZCBwcmVwYXJlIGRhdGEKClRvIGJlZ2luLCB3ZSdsbCByZWFkIGluIHRoZSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIChTQ0UpIG9iamVjdC4KCmBgYHtyIHJlYWQgZGF0YX0KIyBSZWFkIHRoZSBTQ0UgZmlsZQpzY2UgPC0gcmVhZFJEUyhpbnB1dF9zY2VfZmlsZSkKYGBgCgpGb3IgdGhlIGluaXRpYWwgY2x1c3RlciBjYWxjdWxhdGlvbnMgYW5kIGV2YWx1YXRpb25zLCB3ZSB3aWxsIHVzZSB0aGUgUENBIG1hdHJpeCBleHRyYWN0ZWQgZnJvbSB0aGUgU0NFIG9iamVjdC4KQXMgc2hvd24gaW4gW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCksIGl0IGlzIGFsc28gcG9zc2libGUgdG8gdXNlIGFuIFNDRSBvYmplY3Qgb3IgYSBTZXVyYXQgb2JqZWN0IGRpcmVjdGx5LgoKCmBgYHtyIGV4dHJhY3QgcGNhIGRhdGF9CiMgRXh0cmFjdCB0aGUgUENBIG1hdHJpeCBmcm9tIGFuIFNDRSBvYmplY3QKcGNhX21hdHJpeCA8LSByZWR1Y2VkRGltKHNjZSwgIlBDQSIpCmBgYAoKIyMgVmFyeWluZyBhIHNpbmdsZSBjbHVzdGVyaW5nIHBhcmFtZXRlcgoKVGhpcyBzZWN0aW9uIHdpbGwgc2hvdyBob3cgdG8gcGVyZm9ybSBjbHVzdGVyaW5nIGFjcm9zcyBhIHNldCBvZiBwYXJhbWV0ZXJzIChha2EsICJzd2VlcCIgYSBzZXQgb2YgcGFyYW1ldGVycykgd2l0aCBgck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoKWAuIAoKVGhpcyBmdW5jdGlvbiB0YWtlcyBhIFBDQSBtYXRyaXggd2l0aCByb3cgbmFtZXMgcmVwcmVzZW50aW5nIHVuaXF1ZSBjZWxsIGlkcyAoZS5nLiwgYmFyY29kZXMpIGFzIGl0cyBwcmltYXJ5IGFyZ3VtZW50LCB3aXRoIGFkZGl0aW9uYWwgYXJndW1lbnRzIGZvciBjbHVzdGVyIHBhcmFtZXRlcnMuIApUaGlzIGZ1bmN0aW9uIHdyYXBzIHRoZSBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX2NsdXN0ZXJzKClgIGZ1bmN0aW9uIGJ1dCBhbGxvd3MgeW91IHRvIHByb3ZpZGUgYSB2ZWN0b3Igb2YgcGFyYW1ldGVyIHZhbHVlcyB0byBwZXJmb3JtIGNsdXN0ZXJpbmcgYWNyb3NzLCBhcyBsaXN0ZWQgYmVsb3cuCkNsdXN0ZXJzIHdpbGwgYmUgY2FsY3VsYXRlZCBmb3IgYWxsIGNvbWJpbmF0aW9ucyBvZiBwYXJhbWV0ZXJzIHZhbHVlcyAod2hlcmUgYXBwbGljYWJsZSk7IGRlZmF1bHQgdmFsdWVzIHRoYXQgdGhlIGZ1bmN0aW9uIHdpbGwgdXNlIGZvciBhbnkgdW5zcGVjaWZpZWQgcGFyYW1ldGVyIHZhbHVlcyBhcmUgc2hvd24gaW4gcGFyZW50aGVzZXMuCgoqIGBhbGdvcml0aG1gOiBXaGljaCBjbHVzdGVyaW5nIGFsZ29yaXRobSB0byB1c2UgKExvdXZhaW4pCiogYHdlaWdodGluZ2A6IFdoaWNoIHdlaWdodGluZyBzY2hlbWUgdG8gdXNlIChKYWNjYXJkKQoqIGBubmA6IFRoZSBudW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMgKDEwKQoqIGByZXNvbHV0aW9uYDogVGhlIHJlc29sdXRpb24gcGFyYW1ldGVyICgxOyB1c2VkIG9ubHkgd2l0aCBMb3V2YWluIGFuZCBMZWlkZW4gY2x1c3RlcmluZykKKiBgb2JqZWN0aXZlX2Z1bmN0aW9uYDogVGhlIG9iamVjdGl2ZSBmdW5jdGlvbiB0byBvcHRpbWl6ZSBjbHVzdGVycyAoQ1BNOyB1c2VkIG9ubHkgd2l0aCBMZWlkZW4gY2x1c3RlcmluZykKCmByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygpYCBkb2VzIG5vdCBhbGxvdyB5b3UgdG8gc3BlY2lmeSB2YWx1ZXMgZm9yIGFueSBvdGhlciBwYXJhbWV0ZXJzLiAKCgpUaGlzIGZ1bmN0aW9uIHdpbGwgcmV0dXJuIGEgbGlzdCBvZiBkYXRhIGZyYW1lcyBvZiBjbHVzdGVyaW5nIHJlc3VsdHMuIApFYWNoIGRhdGEgZnJhbWUgd2lsbCBoYXZlIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiogYGNlbGxfaWRgOiBVbmlxdWUgY2VsbCBpZGVudGlmaWVycywgb2J0YWluZWQgZnJvbSB0aGUgUENBIG1hdHJpeCdzIHJvdyBuYW1lcwoqIGBjbHVzdGVyYDogQSBmYWN0b3IgY29sdW1uIHdpdGggdGhlIGNsdXN0ZXIgaWRlbnRpdGllcwoqIFRoZXJlIHdpbGwgYmUgb25lIGNvbHVtbiBmb3IgZWFjaCBjbHVzdGVyaW5nIHBhcmFtZXRlciB1c2VkCgpUbyBkZW1vbnN0cmF0ZSB0aGlzIGZ1bmN0aW9uLCB3ZSdsbCBjYWxjdWxhdGUgY2x1c3RlcnMgdXNpbmcgdGhlIExvdXZhaW4gYWxnb3JpdGhtIHdoaWxlIHZhcnlpbmcgdGhlIGBubmAgcGFyYW1ldGVyOgoKYGBge3Igc3dlZXAgY2x1c3RlcnN9CiMgRGVmaW5lIG5uIHBhcmFtZXRlciB2YWx1ZXMgb2YgaW50ZXJlc3QKbm5fdmFsdWVzIDwtIGMoMTAsIDIwLCAzMCkKCiMgQ2FsY3VsYXRlIGNsdXN0ZXJzIHZhcnlpbmcgbm4sIGJ1dCBsZWF2aW5nIG90aGVyIHBhcmFtZXRlcnMgYXQgdGhlaXIgZGVmYXVsdCB2YWx1ZXMKY2x1c3Rlcl9yZXN1bHRzX2xpc3QgPC0gck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoCiAgcGNhX21hdHJpeCwKICBubiA9IG5uX3ZhbHVlcwopCmBgYAoKVGhlIHJlc3VsdGluZyBsaXN0IGhhcyBhIGxlbmd0aCBvZiB0aHJlZSwgb25lIGRhdGEgZnJhbWUgZm9yIGVhY2ggYG5uYCBwYXJhbWV0ZXIgdGVzdGVkOgoKYGBge3IgbGVuZ3RoIGNsdXN0ZXJfcmVzdWx0c19saXN0fQpsZW5ndGgoY2x1c3Rlcl9yZXN1bHRzX2xpc3QpCmBgYAoKSXQgY2FuIGJlIGhlbHBmdWwgKGFsdGhvdWdoIGl0IGlzIG5vdCBzdHJpY3RseSBuZWNlc3NhcnkgdG8ga2VlcCB0cmFjaykgdG8gbmFtZSB0aGlzIGxpc3QgYnkgdGhlIHZhcmllZCBgbm5gIHBhcmFtZXRlci4KSW4gdGhpcyBjYXNlLCB3ZSdsbCB1c2UgdGhlc2UgbmFtZXMgdG8gbGFiZWwgcGxvdHMuCgpgYGB7ciBzZXQgbGlzdCBuYW1lc30KbmFtZXMoY2x1c3Rlcl9yZXN1bHRzX2xpc3QpIDwtIGdsdWU6OmdsdWUoIm5uX3tubl92YWx1ZXN9IikKYGBgCgoKV2UgY2FuIGxvb2sgYXQgdGhlIGZpcnN0IGZldyByb3dzIG9mIGVhY2ggZGF0YSBmcmFtZSB1c2luZyBbYHB1cnJyOjptYXAoKWBdKGh0dHBzOi8vcHVycnIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvbWFwLmh0bWwpIHRvIGl0ZXJhdGUgb3ZlciB0aGUgbGlzdDoKCgpgYGB7ciBtYXAgY2x1c3Rlcl9yZXN1bHRzX2xpc3R9CmNsdXN0ZXJfcmVzdWx0c19saXN0IHw+CiAgcHVycnI6Om1hcChoZWFkKQpgYGAKCkdlbmVyYWxseSBzcGVha2luZywgYHB1cnJyOjptYXAoKWAgY2FuIGJlIHVzZWQgdG8gaXRlcmF0ZSBvdmVyIHRoaXMgbGlzdCB0byB2aXN1YWxpemUgb3IgYW5hbHl6ZSBlYWNoIGNsdXN0ZXJpbmcgcmVzdWx0IG9uIGl0cyBvd247IHdlJ2xsIHVzZSB0aGlzIGFwcHJvYWNoIGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnMuIAoKIyMjIFZpc3VhbGl6aW5nIGNsdXN0ZXJpbmcgcmVzdWx0cwoKV2hlbiBjb21wYXJpbmcgY2x1c3RlcmluZyByZXN1bHRzLCBpdCdzIGltcG9ydGFudCB0byBmaXJzdCB2aXN1YWxpemUgdGhlIGRpZmZlcmVudCBjbHVzdGVyaW5ncyB0byBidWlsZCBjb250ZXh0IGZvciBpbnRlcnByZXRpbmcgUUMgbWV0cmljcy4KCkFzIG9uZSBleGFtcGxlIG9mIHdoeSB0aGlzIGlzIGltcG9ydGFudCwgd2UgZ2VuZXJhbGx5IGV4cGVjdCB0aGF0IG1vcmUgcm9idXN0IGNsdXN0ZXJzIHdpbGwgaGF2ZSBoaWdoZXIgdmFsdWVzIGZvciBtZXRyaWNzIGxpa2Ugc2lsaG91ZXR0ZSB3aWR0aCBhbmQgbmVpZ2hib3Job29kIHB1cml0eS4KSG93ZXZlciwgd2UgYWxzbyBleHBlY3QgdGhhdCBoYXZpbmcgZmV3ZXIgY2x1c3RlcnMgaW4gdGhlIGZpcnN0IHBsYWNlIHdpbGwgYWxzbyBsZWFkIHRvIGhpZ2hlciBtZXRyaWNzLCByZWdhcmRsZXNzIG9mIGNsdXN0ZXIgcXVhbGl0eTogV2hlbiB0aGVyZSBhcmUgZmV3ZXIgY2x1c3RlcnMsIGl0IGlzIG1vcmUgbGlrZWx5IHRoYXQgY2x1c3RlcnMgb3ZlcmxhcCBsZXNzIHdpdGggb25lIGFub3RoZXIganVzdCBiZWNhdXNlIHRoZXJlIGFyZW4ndCBtYW55IGNsdXN0ZXJzIGluIHRoZSBmaXJzdCBwbGFjZS4KVGhpcyBtZWFucyB0aGF0LCB3aGVuIGludGVycHJldGluZyBjbHVzdGVyIHF1YWxpdHkgbWV0cmljcywgeW91IHNob3VsZCBiZSBjYXJlZnVsIHRvIHRha2UgbW9yZSBjb250ZXh0IGFib3V0IHRoZSBkYXRhIGludG8gY29uc2lkZXJhdGlvbiBhbmQgbm90IG9ubHkgcmVseSBvbiB0aGUgbWV0cmljIHZhbHVlcy4KCldlJ2xsIHRoZXJlZm9yZSB2aXN1YWxpemUgdGhlc2UgcmVzdWx0cyBhcyBVTUFQcyBieSBpdGVyYXRpbmcgb3ZlciBgY2x1c3Rlcl9yZXN1bHRzX2xpc3RgIGFuZCBjb21iaW5pbmcgcGxvdHMgd2l0aCBbYHBhdGNod29yazo6d3JhcF9wbG90cygpYF0oaHR0cHM6Ly9wYXRjaHdvcmsuZGF0YS1pbWFnaW5pc3QuY29tL3JlZmVyZW5jZS93cmFwX3Bsb3RzLmh0bWwpLgpXZSdsbCBzcGVjaWZpY2FsbHkgdXNlIFtgcHVycnI6OmltYXAoKWBdKGh0dHBzOi8vcHVycnIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvaW1hcC5odG1sKSB0byBpdGVyYXRlIHNvIHRoYXQgd2UgY2FuIGFzc2lnbiB0aGlzIGxpc3QncyBuYW1lcyBhcyBwbG90IHRpdGxlcy4KCkZvciB0aGlzLCB3ZSdsbCBiZWdpbiBieSBleHRyYWN0aW5nIGEgdGFibGUgb2YgVU1BUCBjb29yZGluYXRlcyBmcm9tIG91ciBTQ0Ugb2JqZWN0LgoKYGBge3IgY3JlYXRlIHVtYXBfZGZ9CnVtYXBfZGYgPC0gcmVkdWNlZERpbShzY2UsICJVTUFQIikgfD4KICBhcy5kYXRhLmZyYW1lKCkKYGBgCgpOZXh0LCB3ZSdsbCBpdGVyYXRlIG92ZXIgYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBwbG90IHRoZSBVTUFQcy4KCmBgYHtyIHBsb3Qgbm4gdW1hcHMsIGZpZy53aWR0aCA9IDEyfQp1bWFwX3Bsb3RzIDwtIGNsdXN0ZXJfcmVzdWx0c19saXN0IHw+CiAgcHVycnI6OmltYXAoCiAgICBcKGNsdXN0ZXJfZGYsIGNsdXN0ZXJpbmdfbmFtZSkgewogICAgICAjIEFkZCBhIGNvbHVtbiB3aXRoIGNsdXN0ZXIgYXNzaWdubWVudHMgdG8gdW1hcF9kZgogICAgICB1bWFwX2RmX3Bsb3QgPC0gdW1hcF9kZiB8PgogICAgICAgIGRwbHlyOjptdXRhdGUoY2x1c3RlciA9IGNsdXN0ZXJfZGYkY2x1c3RlcikKCiAgICAgICMgUGxvdCB0aGUgVU1BUCwgY29sb3JlZCBieSB0aGUgbmV3IGNsdXN0ZXIgdmFyaWFibGUKICAgICAgZ2dwbG90KHVtYXBfZGZfcGxvdCwgYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGNsdXN0ZXIpKSArCiAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNikgKwogICAgICAgIGxhYnModGl0bGUgPSBnbHVlOjpnbHVlKCJuZWFyZXN0IG5laWdoYm9yczoge2NsdXN0ZXJpbmdfbmFtZX0iKSkgKwogICAgICAgICMgV2UnbGwgYWRkIGEgY291cGxlIFVNQVAgcGxvdCBzZXR0aW5ncyBoZXJlLCBpbmNsdWRpbmcgZXF1YWwgYXhlcyBhbmQKICAgICAgICAjIHR1cm5pbmcgb2ZmIHRoZSBheGlzIHRpY2tzIGFuZCB0ZXh0IHNpbmNlIFVNQVAgY29vcmRpbmF0ZXMgYXJlIG5vdCBtZWFuaW5nZnVsCiAgICAgICAgY29vcmRfZXF1YWwoKSArCiAgICAgICAgdGhlbWUoCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpCiAgICAgICAgKQogICAgfQogICkKCiMgUHJpbnQgdGhlIHBsb3RzIHdpdGggcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKCkKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHVtYXBfcGxvdHMsIG5jb2wgPSAzKQpgYGAKClRoZXNlIHBsb3RzIHNob3cgdGhhdCB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIGRlY3JlYXNlcyBhcyB0aGUgbmVhcmVzdCBuZWlnaGJvcnMgcGFyYW1ldGVyIGluY3JlYXNlcywgd2l0aCBiZXR3ZWVuIDktMTMgY2x1c3RlcnMuCgoKCgojIyMgRXZhbHVhdGluZyBjbHVzdGVyaW5nIHJlc3VsdHMKClRoaXMgc2VjdGlvbiB3aWxsIHVzZSBgcHVycnI6Om1hcCgpYCB0byBpdGVyYXRlIG92ZXIgZWFjaCBjbHVzdGVyaW5nIHJlc3VsdCBkYXRhIGZyYW1lIHRvIGNhbGN1bGF0ZSBzaWxob3VldHRlIHdpZHRoLCBuZWlnaGJvcmhvb2QgcHVyaXR5LCBhbmQgc3RhYmlsaXR5LCBhbmQgdGhlbiB2aXN1YWxpemUgcmVzdWx0cy4KVGhlIGdvYWwgb2YgdGhpcyBjb2RlIGlzIHRvIGlkZW50aWZ5IHdoZXRoZXIgb25lIGNsdXN0ZXJpbmcgcGFyYW1ldGVyaXphdGlvbiBwcm9kdWNlcyBtb3JlIHJlbGlhYmxlIGNsdXN0ZXJzLiAKCgojIyMjIFNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkKCkJvdGggc2lsaG91ZXR0ZSB3aWR0aCBhbmQgbmVpZ2hib3Job29kIHB1cml0eSBhcmUgY2VsbC1sZXZlbCBxdWFudGl0aWVzLCBzbyB3ZSBjYW4gY2FsY3VsYXRlIHRoZW0gdG9nZXRoZXIgaW4gdGhlIHNhbWUgY2FsbCB0byBgcHVycnI6Om1hcCgpYC4KQmVsb3csIHdlJ2xsIGl0ZXJhdGUgb3ZlciBlYWNoIGRhdGEgZnJhbWUgaW4gYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBjYWxjdWxhdGUgdGhlc2UgcXVhbnRpdGllcy4KCmBgYHtyIGNhbGN1bGF0ZSBjZWxsIGxldmVsIG1ldHJpY3N9CmNlbGxfbWV0cmljX2xpc3QgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgICMgY2FsY3VsYXRlIHNpbGhvdWV0dGUgd2lkdGgKICAgICAgc2lsaG91ZXR0ZV9kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfc2lsaG91ZXR0ZShwY2FfbWF0cml4LCBjbHVzdGVyX2RmKQoKICAgICAgIyBjYWxjdWxhdGUgbmVpZ2hiaG9yaG9vZCBwdXJpdHksIHN0YXJ0aW5nIGZyb20gc2lsaG91ZXR0ZV9kZgogICAgICByT3BlblNjUENBOjpjYWxjdWxhdGVfcHVyaXR5KHBjYV9tYXRyaXgsIHNpbGhvdWV0dGVfZGYpCiAgICB9CiAgKQoKIyBWaWV3IHRoZSBmaXJzdCBzaXggcm93cyBvZiBlYWNoIGNsdXN0ZXJpbmcgcmVzdWx0J3MgY2VsbC1sZXZlbCBRQyBtZXRyaWNzCnB1cnJyOjptYXAoY2VsbF9tZXRyaWNfbGlzdCwgaGVhZCkKYGBgCgoKVG8gdmlzdWFsaXplIHRoZXNlIHJlc3VsdHMsIHdlIGNhbiBjb21iaW5lIGFsbCBkYXRhIGZyYW1lcyBpbiB0aGlzIGxpc3QgaW50byBhIHNpbmdsZSBvdmVyYWxsIGRhdGEgZnJhbWUsIHdoZXJlIHRoZSBleGlzdGluZyBgbm5gIGNvbHVtbiB3aWxsIGRpc3Rpbmd1aXNoIGFtb25nIGNvbmRpdGlvbnMuCgpgYGB7ciBjb21iaW5lIGNlbGwgbWV0cmljcyBsaXN0fQpjZWxsX21ldHJpY3NfZGYgPC0gcHVycnI6Omxpc3RfcmJpbmQoY2VsbF9tZXRyaWNfbGlzdCkKYGBgCgpXZSBjYW4gdmlzdWFsaXplIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkgZWFjaCB3aXRoIGJveHBsb3RzLCBmb3IgZXhhbXBsZSwgYW5kIHVzZSB0aGUgW2BwYXRjaHdvcmtgXShodHRwczovL3BhdGNod29yay5kYXRhLWltYWdpbmlzdC5jb20vKSBwYWNrYWdlIHRvIHByaW50IHRoZW0gdG9nZXRoZXI6CgoKYGBge3J9CiMgUGxvdCBzaWxob3VldHRlIHdpZHRoCnNpbGhvdWV0dGVfcGxvdCA8LSBnZ3Bsb3QoY2VsbF9tZXRyaWNzX2RmKSArCiAgYWVzKHggPSBhcy5mYWN0b3Iobm4pLCB5ID0gc2lsaG91ZXR0ZV93aWR0aCwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIlNpbGhvdWV0dGUgd2lkdGgiCiAgKQoKCiMgUGxvdCBuZWlnaGJvcmhvb2QgcHVyaXR5IHdpZHRoCnB1cml0eV9wbG90IDwtIGdncGxvdChjZWxsX21ldHJpY3NfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBwdXJpdHksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMiLAogICAgeSA9ICJOZWlnaGJvcmhvb2QgcHVyaXR5IgogICkKCgojIEFkZCB0b2dldGhlciB1c2luZyB0aGUgcGF0Y2h3b3JrIGxpYnJhcnksIHdpdGhvdXQgYSBsZWdlbmQKc2lsaG91ZXR0ZV9wbG90ICsgcHVyaXR5X3Bsb3QgJiB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKV2hpbGUgdGhlcmUgZG9lcyBub3QgYXBwZWFyIHRvIGJlIGEgc2FsaWVudCBkaWZmZXJlbmNlIGFtb25nIHNpbGhvdWV0dGUgd2lkdGggZGlzdHJpYnV0aW9ucywgaXQgZG9lcyBhcHBlYXIgdGhhdCBwdXJpdHkgaXMgaGlnaGVyIHdpdGggYSBoaWdoZXIgbmVhcmVzdCBuZWlnaGJvcnMgcGFyYW1ldGVyLgpJdCdzIHdvcnRoIG5vdGluZyB0aGF0IHRoaXMgdHJlbmQgaW4gcHVyaXR5IHZhbHVlcyBpcyBleHBlY3RlZDogSGlnaGVyIG5lYXJlc3QgbmVpZ2hib3IgcGFyYW1ldGVyIHZhbHVlcyBsZWFkIHRvIGZld2VyIGNsdXN0ZXJzLCBhbmQgbmVpZ2hib3Job29kIHB1cml0eSB0ZW5kcyB0byBiZSBoaWdoZXIgd2hlbiB0aGVyZSBhcmUgZmV3ZXIgY2x1c3RlcnMuIAoKCiMjIyMgU3RhYmlsaXR5CgpOZXh0LCB3ZSdsbCBjYWxjdWxhdGUgc3RhYmlsaXR5IG9uIHRoZSBjbHVzdGVycyB1c2luZyBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgpYCwgc3BlY2lmeWluZyB0aGUgc2FtZSBwYXJhbWV0ZXIgdXNlZCBmb3IgdGhlIG9yaWdpbmFsIGNsdXN0ZXIgY2FsY3VsYXRpb24gYXQgZWFjaCBpdGVyYXRpb24uIAoKYGBge3IgY2FsY3VsYXRlIHN0YWJpbGl0eX0Kc3RhYmlsaXR5X2xpc3QgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgIG5uIDwtIGNsdXN0ZXJfZGYkbm5bMV0gIyBhbGwgcm93cyBoYXZlIHRoZSBzYW1lIGBubmAgcGFyYW1ldGVyLCBzbyB3ZSdsbCB0YWtlIHRoZSBmaXJzdAoKICAgICAgIyBjYWxjdWxhdGUgc3RhYmlsaXR5LCBwYXNzaW5nIGluIHRoZSBwYXJhbWV0ZXIgdmFsdWUgdXNlZCBmb3IgdGhpcyBpdGVyYXRpb24KICAgICAgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eShwY2FfbWF0cml4LCBjbHVzdGVyX2RmLCBubiA9IG5uKQogICAgfQogICkKYGBgCgpXZSdsbCBhZ2FpbiBjb21iaW5lIHRoZSBvdXRwdXQgb2YgYHN0YWJpbGl0eV9saXN0YCBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUgYW5kIHBsb3QgYGFyaWAgdmFsdWVzIGFjcm9zcyBgbm5gIHBhcmFtZXRlcml6YXRpb25zLgoKCmBgYHtyIGNvbWJpbmUgcGxvdCBzdGFiaWxpdHl9CnN0YWJpbGl0eV9kZiA8LSBwdXJycjo6bGlzdF9yYmluZChzdGFiaWxpdHlfbGlzdCkKCmdncGxvdChzdGFiaWxpdHlfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBhcmksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMiLAogICAgeSA9ICJBZGp1c3RlZCBSYW5kIEluZGV4IgogICkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpIZXJlLCB3ZSBzZWUgdGhhdCBhIG5lYXJlc3QgbmVpZ2hib3JzIHZhbHVlIG9mIDIwIG9yIDMwIGxlYWRzIHRvIG1vcmUgc3RhYmxlIGNsdXN0ZXJpbmcgcmVzdWx0cyBjb21wYXJlZCB0byAxMC4gCgoKIyMgVmFyeWluZyBtdWx0aXBsZSBjbHVzdGVyaW5nIHBhcmFtZXRlcnMKClRoZSBwcmV2aW91cyBzZWN0aW9uIGRlbW9uc3RyYXRlZCBob3cgdG8gY2FsY3VsYXRlIGNsdXN0ZXJzIGFuZCBRQyBtZXRyaWNzIHdoZW4gdmFyeWluZyBvbmUgcGFyYW1ldGVyLCBidXQgaXQgaXMgcG9zc2libGUgdG8gdmFyeSBtdWx0aXBsZSBwYXJhbWV0ZXJzIGF0IG9uY2Ugd2l0aCBgck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoKWAuCkluIHRoaXMgc2VjdGlvbiwgd2UnbGwgc2hvdyBhbiBvdmVydmlldyBvZiBob3cgeW91IG1pZ2h0IHdyaXRlIGNvZGUgdG8gdmFyeSB0d28gcGFyYW1ldGVycyBhdCBvbmNlIChoZXJlLCBuZWFyZXN0IG5laWdoYm9ycyBhbmQgcmVzb2x1dGlvbiBhcyBleGFtcGxlcykgYW5kIHZpc3VhbGl6ZSByZXN1bHRzLgoKCmBgYHtyIHN3ZWVwIHR3byBwYXJhbWV0ZXJzfQojIERlZmluZSB2ZWN0b3JzIG9mIHBhcmFtZXRlcnMgdG8gdmFyeQpubl92YWx1ZXMgPC0gYygxMCwgMjAsIDMwKQpyZXNfdmFsdWVzIDwtIGMoMC41LCAxLjAsIDEuNSkKCgpjbHVzdGVyX3Jlc3VsdHNfbGlzdCA8LSByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygKICBwY2FfbWF0cml4LAogIG5uID0gbm5fdmFsdWVzLAogIHJlc29sdXRpb24gPSByZXNfdmFsdWVzCikKYGBgCgpUaGlzIHJlc3VsdGluZyBsaXN0IG5vdyBoYXMgOSBkaWZmZXJlbnQgY2x1c3RlcmluZyByZXN1bHRzLCBmb3IgZWFjaCBjb21iaW5hdGlvbiBvZiBgbm5fdmFsdWVzYCBhbmQgYHJlc192YWx1ZXNgOgoKCmBgYHtyIGxlbmd0aCBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB0d28gcGFyYW1ldGVyc30KbGVuZ3RoKGNsdXN0ZXJfcmVzdWx0c19saXN0KQpgYGAKCgojIyMgVmlzdWFsaXplIGNsdXN0ZXJzCgpOZXh0LCB3ZSdsbCBpdGVyYXRlIG92ZXIgYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBwbG90IHRoZSBVTUFQcy4KVGhpcyB0aW1lLCB3ZSdsbCB1c2UgYHB1cnJyOjptYXAoKWAgYW5kIHB1bGwgb3V0IHBhcmFtZXRlcnMgZnJvbSBlYWNoIGl0ZXJhdGlvbidzIGBjbHVzdGVyX2RmYCB0byBmb3JtIHRoZSBVTUFQIHBhbmVsIHRpdGxlLgoKYGBge3IgcGxvdCBubiByZXMgdW1hcHMsIGZpZy5oZWlnaHQgPSAxNH0KdW1hcF9wbG90cyA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgIyBBZGQgYSBjb2x1bW4gd2l0aCBjbHVzdGVyIGFzc2lnbm1lbnRzIHRvIHVtYXBfZGYKICAgICAgdW1hcF9kZl9wbG90IDwtIHVtYXBfZGYgfD4KICAgICAgICBkcGx5cjo6bXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyX2RmJGNsdXN0ZXIpCgogICAgICAjIENyZWF0ZSBhIHRpdGxlIGZvciB0aGUgVU1BUCB3aXRoIGJvdGggcGFyYW1ldGVycwogICAgICB1bWFwX3RpdGxlIDwtIGdsdWU6OmdsdWUoCiAgICAgICAgIm5uOiB7Y2x1c3Rlcl9kZiRublsxXX07IHJlczoge2NsdXN0ZXJfZGYkcmVzb2x1dGlvblsxXX0iCiAgICAgICkKCiAgICAgICMgUGxvdCB0aGUgVU1BUCwgY29sb3JlZCBieSB0aGUgbmV3IGNsdXN0ZXIgdmFyaWFibGUKICAgICAgZ2dwbG90KHVtYXBfZGZfcGxvdCwgYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGNsdXN0ZXIpKSArCiAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNikgKwogICAgICAgIGxhYnModGl0bGUgPSB1bWFwX3RpdGxlKSArCiAgICAgICAgIyBXZSdsbCBhZGQgYSBjb3VwbGUgVU1BUC1zcGVjaWZpYyBwbG90IHNldHRpbmdzCiAgICAgICAgY29vcmRfZXF1YWwoKSArCiAgICAgICAgdGhlbWUoCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIyBFbnN1cmUgbGVnZW5kcyBmaXQgaW4gdGhlIGZpZ3VyZQogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgICBsZWdlbmQua2V5LnNpemUgPSB1bml0KDAuMiwgImNtIikKICAgICAgICApCiAgICB9CiAgKQoKIyBQcmludCB0aGUgcGxvdHMgd2l0aCBwYXRjaHdvcms6OndyYXBfcGxvdHMoKQpwYXRjaHdvcms6OndyYXBfcGxvdHModW1hcF9wbG90cywgbmNvbCA9IDMpCmBgYAoKCgojIyMgQ2FsY3VsYXRlIGFuZCB2aXN1YWxpemUgUUMgbWV0cmljcwoKVGhpcyBzZWN0aW9uIHByZXNlbnRzIG9uZSBjb2Rpbmcgc3RyYXRlZ3kgdG8gY2FsY3VsYXRlIGFuZCB2aXN1YWxpemUgcmVzdWx0cyB3aGVuIHZhcnlpbmcgdHdvIGNsdXN0ZXJpbmcgcGFyYW1ldGVycy4KSW4gcGFydGljdWxhciwgd2UgdXNlIGZhY2V0aW5nIHRvIGhlbHAgZGlzcGxheSBhbGwgaW5mb3JtYXRpb24gaW4gb25lIHBsb3QsIGJ5IHBsYWNpbmcgbmVhcmVzdCBuZWlnaGJvciB2YWx1ZXMgb24gdGhlIFgtYXhpcyBhbmQgZmFjZXRpbmcgYnkgcmVzb2x1dGlvbiB2YWx1ZXMuClNpbmNlIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoaG9yYm9vZCBwdXJpdHkgY2FsY3VsYXRpb25zIHVzaW5nIGdlbmVyYWxseSBzaW1pbGFyIGNvZGUsIHdlJ2xsIGp1c3Qgc2hvdyBuZWlnaGJvcmhvb2QgcHVyaXR5IGhlcmUuCgojIyMjIE5laWdoYm9yaG9vZCBwdXJpdHkKCkZpcnN0LCB3ZSdsbCBjYWxjdWxhdGUgbmVpZ2hib3Job29kIHB1cml0eSBhbmQgY29tYmluZSByZXN1bHRzIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZS4KCmBgYHtyIGNhbGN1bGF0ZSBwdXJpdHkgdHdvIHBhcmFtZXRlcnN9CnB1cml0eV9kZiA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3B1cml0eShwY2FfbWF0cml4LCBjbHVzdGVyX2RmKQogICAgfQogICkgfD4KICBwdXJycjo6bGlzdF9yYmluZCgpCmBgYAoKCmBgYHtyIHZpc3VhbGl6ZSBwdXJpdHkgdHdvIHBhcmFtZXRlcnN9CmdncGxvdChwdXJpdHlfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBwdXJpdHksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICAjIGZhY2V0IGJ5IHJlc29sdXRpb24sIGxhYmVsaW5nIHBhbmVscyB3aXRoIGJvdGggdGhlIHJlc29sdXRpb24gY29sdW1uIG5hbWUgYW5kIHZhbHVlCiAgZmFjZXRfd3JhcCh2YXJzKHJlc29sdXRpb24pLCBsYWJlbGxlciA9IGxhYmVsX2JvdGgpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMiLAogICAgeSA9ICJOZWlnaGJvcmhvb2QgcHVyaXR5IgogICkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgojIyMgU3RhYmlsaXR5CgpTaW1pbGFyIHRvIGFib3ZlLCB3ZSdsbCBjYWxjdWxhdGUgc3RhYmlsaXR5LCBjb21iaW5lIHJlc3VsdHMgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lLCBhZGQgYSBgcmVzb2x1dGlvbl9sYWJlbGAgY29sdW1uIHRvIHN1cHBvcnQgcGxvdCBpbnRlcnByZXRhdGlvbiwgYW5kIGZpbmFsbHkgbWFrZSBvdXIgcGxvdC4KCmBgYHtyIGNhbGN1bGF0ZSBzdGFiaWxpdHkgdHdvIHBhcmFtZXRlcnN9CnN0YWJpbGl0eV9kZiA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgIyBFeHRyYWN0IHBhcmFtZXRlcnMgZm9yIHRoaXMgY2x1c3RlcmluZyByZXN1bHQKICAgICAgbm4gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkbm4pCiAgICAgIHJlc29sdXRpb24gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkcmVzb2x1dGlvbikKCiAgICAgIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zdGFiaWxpdHkoCiAgICAgICAgcGNhX21hdHJpeCwKICAgICAgICBjbHVzdGVyX2RmLAogICAgICAgIG5uID0gbm4sCiAgICAgICAgcmVzb2x1dGlvbiA9IHJlc29sdXRpb24KICAgICAgKQogICAgfQogICkgfD4KICBwdXJycjo6bGlzdF9yYmluZCgpCmBgYAoKCmBgYHtyIHZpc3VhbGl6ZSBzdGFiaWxpdHkgdHdvIHBhcmFtZXRlcnN9CmdncGxvdChzdGFiaWxpdHlfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBhcmksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICBmYWNldF93cmFwKHZhcnMocmVzb2x1dGlvbiksIGxhYmVsbGVyID0gbGFiZWxfYm90aCkgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIkFkanVzdGVkIFJhbmQgSW5kZXgiCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9uIGluZm99CiMgcmVjb3JkIHRoZSB2ZXJzaW9ucyBvZiB0aGUgcGFja2FnZXMgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGFuZCBvdGhlciBlbnZpcm9ubWVudCBpbmZvcm1hdGlvbgpzZXNzaW9uSW5mbygpCmBgYAo=
+ + +
+
+ +
+ + + + + + + + + + + + + + + + + diff --git a/analyses/hello-clusters/README.md b/analyses/hello-clusters/README.md index cc992ab79..b095a73c8 100644 --- a/analyses/hello-clusters/README.md +++ b/analyses/hello-clusters/README.md @@ -9,7 +9,7 @@ The `rOpenScPCA` package provides several functions that leverage the [`bluster` ### Perform clustering -The function `calculate_clusters()` can be used to perform graph-based clustering. +The function `rOpenScPCA::calculate_clusters()` can be used to perform graph-based clustering. By default, this function uses the Louvain algorithm with Jaccard weighting. @@ -17,21 +17,20 @@ By default, this function uses the Louvain algorithm with Jaccard weighting. `rOpenScPCA` contains several functions to calculate quality metrics for a particular clustering result: -- `calculate_silhouette()` +- `rOpenScPCA::calculate_silhouette()` - This function calculates the [silhouette width](https://bioconductor.org/books/3.19/OSCA.advanced/clustering-redux.html#silhouette-width) - Clusters with higher average silhouette widths indicate that clusters are highly-separated from other clusters -- `calculate_purity()` +- `rOpenScPCA::calculate_purity()` - This function calculates [neighborhood purity](https://bioconductor.org/books/3.19/OSCA.advanced/clustering-redux.html#cluster-purity) - Higher purity values indicate that most neighboring cells in a cluster are assigned to the same cluster, therefore indicating good cluster separation -- `calculate_stability()` +- `rOpenScPCA::calculate_stability()` - This function calculates [cluster stability](https://bioconductor.org/books/3.19/OSCA.advanced/clustering-redux.html#cluster-bootstrapping) with Adjusted Rand Index (ARI) and a bootstrapping procedure - Higher stability values indicate that clusters are more robust ### Identify optimal clustering parameters It is often helpful to explore and evaluate results from different clustering algorithms and/or parameters to choose an optimal clustering scheme. -The function `sweep_clusters()` allows you to generate clustering results from a provided set of algorithms and/or parameters, whose quality can then be assessed to select a set of clusters to proceed with. - +The function `rOpenScPCA::sweep_clusters()` allows you to generate clustering results from a provided set of algorithms and/or parameters, whose quality can then be assessed to select a set of clusters to proceed with. ## Installing rOpenScPCA @@ -57,5 +56,10 @@ renv::update("rOpenScPCA") ## Example notebooks 1. The `01_perform-evaluate-clustering.Rmd` notebook shows examples of: - - Performing clustering with `calculate_clusters` - - Evaluating clustering with `calculate_silhouette`, `calculate_purity`, and `calculate_stability` + - Performing clustering with `rOpenScPCA::calculate_clusters()` + - Evaluating clustering with `rOpenScPCA::calculate_silhouette()`, `rOpenScPCA::calculate_purity()`, and `rOpenScPCA::calculate_stability()` +It also contains explanations for how to interpret cluster quality metrics. + +2. The `02_compare-clustering-parameters.Rmd` notebook shows examples of: + - Performing clustering across a set of parameterizations with `rOpenScPCA::sweep_clusters()` + - Comparing and visualizing multiple sets of clustering results diff --git a/analyses/hello-clusters/hello-clusters.Rproj b/analyses/hello-clusters/hello-clusters.Rproj index 98df0873f..3bb706a10 100644 --- a/analyses/hello-clusters/hello-clusters.Rproj +++ b/analyses/hello-clusters/hello-clusters.Rproj @@ -1,4 +1,5 @@ Version: 1.0 +ProjectId: 579b4580-65dc-45bf-ab43-c5fcd3170e05 RestoreWorkspace: No SaveWorkspace: No diff --git a/analyses/hello-clusters/run_hello-clusters.sh b/analyses/hello-clusters/run_hello-clusters.sh index dae74bacb..2a10fc22e 100755 --- a/analyses/hello-clusters/run_hello-clusters.sh +++ b/analyses/hello-clusters/run_hello-clusters.sh @@ -12,3 +12,6 @@ cd ${MODULE_DIR} # Render first notebook Rscript -e "rmarkdown::render('01_perform-evaluate-clustering.Rmd', clean = TRUE)" + +# Render second notebook +Rscript -e "rmarkdown::render('02_compare-clustering-parameters.Rmd', clean = TRUE)"