From 6bb42c5e8e71215cc498a8c946934c3a6c2aee18 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Fri, 20 Dec 2024 15:09:04 -0500 Subject: [PATCH 01/16] UUID --- analyses/hello-clusters/hello-clusters.Rproj | 1 + 1 file changed, 1 insertion(+) 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 From 694aadfb91dec0c721508dad7bf095cfb2b73963 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Fri, 20 Dec 2024 15:09:36 -0500 Subject: [PATCH 02/16] WIP: started sweep notebook --- .../02_compare-clustering-parameters.Rmd | 166 ++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 analyses/hello-clusters/02_compare-clustering-parameters.Rmd 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..34db2b80e --- /dev/null +++ b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd @@ -0,0 +1,166 @@ +--- +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 clusters across several different parameterizations +* Calculate QC metrics on across parameterizations + +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 + +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(Seurat) + library(dplyr) + library(ggplot2) +}) + +# 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::is_git_root) + +# 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") +``` + +## Perform clustering across a set of parameters + +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 rownames 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 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 <- seq(10, 30, 10) + +# Calculate clusters varying nn, but leaving other parameters at their default values +cluster_results_list <- rOpenScPCA::sweep_clusters( + pca_matrix, + nn = nn +) +``` + +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) +``` + +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 analyze or visualize each clustering result on its own. +Complementary to this, we can also combine this list into a single data frame to jointly analyze or visualize all clustering results together: + + +```{r bind cluster_results_list rows} +cluster_results_df <- dplyr::bind_rows(cluster_results_list) +head(cluster_results_df) +``` + +## Evaluating clustering results + +This section will use `purrr::map()` to iterate over each df to calculate silhouette, purity, and stability. +We'll also plot results. + +## Session Info + +```{r session info} +# record the versions of the packages used in this analysis and other environment information +sessionInfo() +``` From 56b609bb9b2d3594e8e44c10226607ba05c10c56 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Mon, 13 Jan 2025 10:33:35 -0500 Subject: [PATCH 03/16] flesh out section to vary one parameter --- .../02_compare-clustering-parameters.Rmd | 181 +- .../02_compare-clustering-parameters.nb.html | 3563 +++++++++++++++++ 2 files changed, 3728 insertions(+), 16 deletions(-) create mode 100644 analyses/hello-clusters/02_compare-clustering-parameters.nb.html diff --git a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd index 34db2b80e..9ceeca0e7 100644 --- a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd +++ b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd @@ -16,13 +16,13 @@ A key question when clustering, therefore, is how to identify a set of parameter This notebook provides examples of how to use the `rOpenScPCA` package to: -* Calculate clusters across several different parameterizations -* Calculate QC metrics on across parameterizations +* 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 +* 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. @@ -37,9 +37,8 @@ library(rOpenScPCA) suppressPackageStartupMessages({ library(SingleCellExperiment) - library(Seurat) - library(dplyr) library(ggplot2) + library(patchwork) }) # Set ggplot theme for plots @@ -96,7 +95,7 @@ pca_matrix <- reducedDim(sce, "PCA") 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 rownames representing unique cell ids (e.g., barcodes) as its primary argument, with additional arguments for cluster parameters. +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 @@ -116,17 +115,16 @@ Each data frame will have the following columns: * `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 <- seq(10, 30, 10) +nn_values <- seq(10, 30, 10) # Calculate clusters varying nn, but leaving other parameters at their default values cluster_results_list <- rOpenScPCA::sweep_clusters( pca_matrix, - nn = nn + nn = nn_values ) ``` @@ -136,6 +134,13 @@ The resulting list has a length of three, one data frame for each `nn` parameter 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: + +```{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: @@ -144,19 +149,163 @@ cluster_results_list |> purrr::map(head) ``` -Generally speaking, `purrr::map()` can be used to iterate over this list to analyze or visualize each clustering result on its own. -Complementary to this, we can also combine this list into a single data frame to jointly analyze or visualize all clustering results together: +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. -```{r bind cluster_results_list rows} -cluster_results_df <- dplyr::bind_rows(cluster_results_list) -head(cluster_results_df) +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 all 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) +``` + +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 df to calculate silhouette, purity, and stability. -We'll also plot 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 + purity_df <- rOpenScPCA::calculate_purity(pca_matrix, cluster_df) + + # Combine into a single data frame + dplyr::left_join(silhouette_df, purity_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 <- dplyr::bind_rows(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. + + +### 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 <- unique(cluster_df$nn) + + # 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 <- dplyr::bind_rows(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. + ## Session Info 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..d6c524fd2 --- /dev/null +++ b/analyses/hello-clusters/02_compare-clustering-parameters.nb.html @@ -0,0 +1,3563 @@ + + + + + + + + + + + + + + + +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::is_git_root)
+
+# 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")
+ + + +
+
+

Perform clustering across a set of parameters

+

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 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 <- seq(10, 30, 10)
+
+# 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:

+ + + +
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)
+ + +

+ + + +

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 
+      purity_df <- rOpenScPCA::calculate_purity(pca_matrix, cluster_df)
+      
+      # Combine into a single data frame
+      dplyr::left_join(silhouette_df, purity_df)
+    }
+  )
+ + +
Joining with `by = join_by(cell_id, cluster, algorithm, weighting, nn,
+resolution)`
+Joining with `by = join_by(cell_id, cluster, algorithm, weighting, nn,
+resolution)`
+Joining with `by = join_by(cell_id, cluster, algorithm, weighting, nn,
+resolution)`
+ + +
# 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 <- dplyr::bind_rows(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.

+
+
+

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 <- unique(cluster_df$nn)
+
+      # 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 <- dplyr::bind_rows(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.

+
+
+
+

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        
+ + +
+ +
LS0tCnRpdGxlOiAiQ29tcGFyaW5nIGNsdXN0ZXJpbmcgcGFyYW1ldGVycyB3aXRoIHJPcGVuU2NQQ0EiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKYXV0aG9yOiAiRGF0YSBMYWIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojIyBJbnRyb2R1Y3Rpb24KCkNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBoYXZlIHNldmVyYWwgcGFyYW1ldGVycyB3aGljaCBjYW4gYmUgdmFyaWVkLCBsZWFkaW5nIHRvIGRpZmZlcmVudCBjbHVzdGVyaW5nIHJlc3VsdHMuCkEga2V5IHF1ZXN0aW9uIHdoZW4gY2x1c3RlcmluZywgdGhlcmVmb3JlLCBpcyBob3cgdG8gaWRlbnRpZnkgYSBzZXQgb2YgcGFyYW1ldGVycyB0aGF0IGxlYWQgdG8gcm9idXN0IGFuZCByZWxpYWJsZSBjbHVzdGVycyB0aGF0IGNhbiBiZSB1c2VkIGluIGRvd25zdHJlYW0gYW5hbHlzaXMuIAoKVGhpcyBub3RlYm9vayBwcm92aWRlcyBleGFtcGxlcyBvZiBob3cgdG8gdXNlIHRoZSBgck9wZW5TY1BDQWAgcGFja2FnZSB0bzoKCiogQ2FsY3VsYXRlIHNldmVyYWwgdmVyc2lvbnMgb2YgY2x1c3RlcmluZyByZXN1bHRzIGFjcm9zcyBzZXZlcmFsIGRpZmZlcmVudCBwYXJhbWV0ZXJpemF0aW9ucwoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGFjcm9zcyBjbHVzdGVyaW5nIHJlc3VsdHMKClBsZWFzZSByZWZlciB0byB0aGUgW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCkgbm90ZWJvb2sgZm9yIGEgdHV0b3JpYWwgb24gdXNpbmcgYHJPcGVuU2NQQ0FgIGZ1bmN0aW9ucyB0bzoKCiogQ2FsY3VsYXRlIGNsdXN0ZXJzIGZyb20gYSBzaW5nbGUgcGFyYW1ldGVyaXphdGlvbgoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGEgc2luZ2xlIHNldCBvZiBjbHVzdGVycywgYXMgd2VsbCBhcyBleHBsYW5hdGlvbnMgb2YgdGhlIG1ldHJpY3MgdGhlbXNlbHZlcwoKVGhpcyBub3RlYm9vayB3aWxsIHVzZSB0aGUgc2FtcGxlIGBTQ1BDUzAwMDAwMWAgZnJvbSBwcm9qZWN0IGBTQ1BDUDAwMDAwMWAsIHdoaWNoIGlzIGFzc3VtZWQgcHJlc2VudCBpbiB0aGUgYE9wZW5TY1BDQS1hbmFseXNpcy9kYXRhL2N1cnJlbnQvU0NQQ1AwMDAwMDFgIGRpcmVjdG9yeSwgZm9yIGFsbCBleGFtcGxlcy4KUGxlYXNlIFtzZWUgdGhpcyBkb2N1bWVudGF0aW9uXShodHRwczovL29wZW5zY3BjYS5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvZ2V0dGluZy1zdGFydGVkL2FjY2Vzc2luZy1yZXNvdXJjZXMvZ2V0dGluZy1hY2Nlc3MtdG8tZGF0YS8pIGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IG9idGFpbmluZyBTY1BDQSBkYXRhLgoKIyMgU2V0dXAKCiMjIyBQYWNrYWdlcwoKCmBgYHtyIHBhY2thZ2VzfQpsaWJyYXJ5KHJPcGVuU2NQQ0EpCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQp9KQoKIyBTZXQgZ2dwbG90IHRoZW1lIGZvciBwbG90cwp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgoKIyMjIFBhdGhzCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19naXRfcm9vdCkKCiMgVGhlIGN1cnJlbnQgZGF0YSBkaXJlY3RvcnksIGZvdW5kIHdpdGhpbiB0aGUgcmVwb3NpdG9yeSBiYXNlIGRpcmVjdG9yeQpkYXRhX2RpciA8LSBmaWxlLnBhdGgocmVwb3NpdG9yeV9iYXNlLCAiZGF0YSIsICJjdXJyZW50IikKCiMgVGhlIHBhdGggdG8gdGhpcyBtb2R1bGUKbW9kdWxlX2Jhc2UgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImFuYWx5c2VzIiwgImhlbGxvLWNsdXN0ZXJzIikKYGBgCgpgYGB7ciBpbnB1dCBmaWxlIHBhdGh9CiMgUGF0aCB0byBwcm9jZXNzZWQgU0NFIGZpbGUgZm9yIHNhbXBsZSBTQ1BDUzAwMDAwMQppbnB1dF9zY2VfZmlsZSA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJTQ1BDUDAwMDAwMSIsICJTQ1BDUzAwMDAwMSIsICJTQ1BDTDAwMDAwMV9wcm9jZXNzZWQucmRzIikKYGBgCgoKIyMjIFNldCB0aGUgcmFuZG9tIHNlZWQKCkJlY2F1c2UgY2x1c3RlcmluZyBpbnZvbHZlcyByYW5kb20gc2FtcGxpbmcsIGl0IGlzIGltcG9ydGFudCB0byBzZXQgdGhlIHJhbmRvbSBzZWVkIGF0IHRoZSB0b3Agb2YgeW91ciBhbmFseXNpcyBzY3JpcHQgb3Igbm90ZWJvb2sgdG8gZW5zdXJlIHJlcHJvZHVjaWJpbGl0eS4KCmBgYHtyIHNldCBzZWVkfQpzZXQuc2VlZCgyMDI0KQpgYGAKCiMjIFJlYWQgaW4gYW5kIHByZXBhcmUgZGF0YQoKVG8gYmVnaW4sIHdlJ2xsIHJlYWQgaW4gdGhlIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgKFNDRSkgb2JqZWN0LgoKYGBge3IgcmVhZCBkYXRhfQojIFJlYWQgdGhlIFNDRSBmaWxlCnNjZSA8LSByZWFkUkRTKGlucHV0X3NjZV9maWxlKQpgYGAKCkZvciB0aGUgaW5pdGlhbCBjbHVzdGVyIGNhbGN1bGF0aW9ucyBhbmQgZXZhbHVhdGlvbnMsIHdlIHdpbGwgdXNlIHRoZSBQQ0EgbWF0cml4IGV4dHJhY3RlZCBmcm9tIHRoZSBTQ0Ugb2JqZWN0LgpBcyBzaG93biBpbiBbYDAxX3BlcmZvcm0tZXZhbHVhdGUtY2x1c3RlcmluZy5SbWRgXSgwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kKSwgaXQgaXMgYWxzbyBwb3NzaWJsZSB0byB1c2UgYW4gU0NFIG9iamVjdCBvciBhIFNldXJhdCBvYmplY3QgZGlyZWN0bHkuCgoKYGBge3IgZXh0cmFjdCBwY2EgZGF0YX0KIyBFeHRyYWN0IHRoZSBQQ0EgbWF0cml4IGZyb20gYW4gU0NFIG9iamVjdApwY2FfbWF0cml4IDwtIHJlZHVjZWREaW0oc2NlLCAiUENBIikKYGBgCgojIyBQZXJmb3JtIGNsdXN0ZXJpbmcgYWNyb3NzIGEgc2V0IG9mIHBhcmFtZXRlcnMKClRoaXMgc2VjdGlvbiB3aWxsIHNob3cgaG93IHRvIHBlcmZvcm0gY2x1c3RlcmluZyBhY3Jvc3MgYSBzZXQgb2YgcGFyYW1ldGVycyAoYWthLCAic3dlZXAiIGEgc2V0IG9mIHBhcmFtZXRlcnMpIHdpdGggYHJPcGVuU2NQQ0E6OnN3ZWVwX2NsdXN0ZXJzKClgLiAKClRoaXMgZnVuY3Rpb24gdGFrZXMgYSBQQ0EgbWF0cml4IHdpdGggcm93IG5hbWVzIHJlcHJlc2VudGluZyB1bmlxdWUgY2VsbCBpZHMgKGUuZy4sIGJhcmNvZGVzKSBhcyBpdHMgcHJpbWFyeSBhcmd1bWVudCwgd2l0aCBhZGRpdGlvbmFsIGFyZ3VtZW50cyBmb3IgY2x1c3RlciBwYXJhbWV0ZXJzLiAKVGhpcyBmdW5jdGlvbiB3cmFwcyB0aGUgYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9jbHVzdGVycygpYCBmdW5jdGlvbiBidXQgYWxsb3dzIHlvdSB0byBwcm92aWRlIGEgdmVjdG9yIG9mIHBhcmFtZXRlciB2YWx1ZXMgdG8gcGVyZm9ybSBjbHVzdGVyaW5nIGFjcm9zcywgYXMgbGlzdGVkIGJlbG93LgpDbHVzdGVycyB3aWxsIGJlIGNhbGN1bGF0ZWQgZm9yIGFsbCBjb21iaW5hdGlvbnMgb2YgcGFyYW1ldGVycyB2YWx1ZXMgKHdoZXJlIGFwcGxpY2FibGUpOyBkZWZhdWx0IHZhbHVlcyB0aGF0IHRoZSBmdW5jdGlvbiB3aWxsIHVzZSBmb3IgYW55IHVuc3BlY2lmaWVkIHBhcmFtZXRlciB2YWx1ZXMgYXJlIHNob3duIGluIHBhcmVudGhlc2VzCgoqIGBhbGdvcml0aG1gOiBXaGljaCBjbHVzdGVyaW5nIGFsZ29yaXRobSB0byB1c2UgKExvdXZhaW4pCiogYHdlaWdodGluZ2A6IFdoaWNoIHdlaWdodGluZyBzY2hlbWUgdG8gdXNlIChKYWNjYXJkKQoqIGBubmA6IFRoZSBudW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMgKDEwKQoqIGByZXNvbHV0aW9uYDogVGhlIHJlc29sdXRpb24gcGFyYW1ldGVyICgxOyB1c2VkIG9ubHkgd2l0aCBMb3V2YWluIGFuZCBMZWlkZW4gY2x1c3RlcmluZykKKiBgb2JqZWN0aXZlX2Z1bmN0aW9uYDogVGhlIG9iamVjdGl2ZSBmdW5jdGlvbiB0byBvcHRpbWl6ZSBjbHVzdGVycyAoQ1BNOyB1c2VkIG9ubHkgd2l0aCBMZWlkZW4gY2x1c3RlcmluZykKCmByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygpYCBkb2VzIGFsbG93IHlvdSB0byBzcGVjaWZ5IHZhbHVlcyBmb3IgYW55IG90aGVyIHBhcmFtZXRlcnMuIAoKClRoaXMgZnVuY3Rpb24gd2lsbCByZXR1cm4gYSBsaXN0IG9mIGRhdGEgZnJhbWVzIG9mIGNsdXN0ZXJpbmcgcmVzdWx0cy4gCkVhY2ggZGF0YSBmcmFtZSB3aWxsIGhhdmUgdGhlIGZvbGxvd2luZyBjb2x1bW5zOgoKKiBgY2VsbF9pZGA6IFVuaXF1ZSBjZWxsIGlkZW50aWZpZXJzLCBvYnRhaW5lZCBmcm9tIHRoZSBQQ0EgbWF0cml4J3Mgcm93IG5hbWVzCiogYGNsdXN0ZXJgOiBBIGZhY3RvciBjb2x1bW4gd2l0aCB0aGUgY2x1c3RlciBpZGVudGl0aWVzCiogVGhlcmUgd2lsbCBiZSBvbmUgY29sdW1uIGZvciBlYWNoIGNsdXN0ZXJpbmcgcGFyYW1ldGVyIHVzZWQKClRvIGRlbW9uc3RyYXRlIHRoaXMgZnVuY3Rpb24sIHdlJ2xsIGNhbGN1bGF0ZSBjbHVzdGVycyB1c2luZyB0aGUgTG91dmFpbiBhbGdvcml0aG0gd2hpbGUgdmFyeWluZyB0aGUgYG5uYCBwYXJhbWV0ZXI6CgpgYGB7ciBzd2VlcCBjbHVzdGVyc30KIyBEZWZpbmUgbm4gcGFyYW1ldGVyIHZhbHVlcyBvZiBpbnRlcmVzdApubl92YWx1ZXMgPC0gc2VxKDEwLCAzMCwgMTApCgojIENhbGN1bGF0ZSBjbHVzdGVycyB2YXJ5aW5nIG5uLCBidXQgbGVhdmluZyBvdGhlciBwYXJhbWV0ZXJzIGF0IHRoZWlyIGRlZmF1bHQgdmFsdWVzCmNsdXN0ZXJfcmVzdWx0c19saXN0IDwtIHJPcGVuU2NQQ0E6OnN3ZWVwX2NsdXN0ZXJzKAogIHBjYV9tYXRyaXgsCiAgbm4gPSBubl92YWx1ZXMKKQpgYGAKClRoZSByZXN1bHRpbmcgbGlzdCBoYXMgYSBsZW5ndGggb2YgdGhyZWUsIG9uZSBkYXRhIGZyYW1lIGZvciBlYWNoIGBubmAgcGFyYW1ldGVyIHRlc3RlZDoKCmBgYHtyIGxlbmd0aCBjbHVzdGVyX3Jlc3VsdHNfbGlzdH0KbGVuZ3RoKGNsdXN0ZXJfcmVzdWx0c19saXN0KQpgYGAKCkl0IGNhbiBiZSBoZWxwZnVsIChhbHRob3VnaCBpdCBpcyBub3Qgc3RyaWN0bHkgbmVjZXNzYXJ5IHRvIGtlZXAgdHJhY2spIHRvIG5hbWUgdGhpcyBsaXN0IGJ5IHRoZSB2YXJpZWQgYG5uYCBwYXJhbWV0ZXI6CgpgYGB7ciBzZXQgbGlzdCBuYW1lc30KbmFtZXMoY2x1c3Rlcl9yZXN1bHRzX2xpc3QpIDwtIGdsdWU6OmdsdWUoIm5uX3tubl92YWx1ZXN9IikKYGBgCgoKV2UgY2FuIGxvb2sgYXQgdGhlIGZpcnN0IGZldyByb3dzIG9mIGVhY2ggZGF0YSBmcmFtZSB1c2luZyBbYHB1cnJyOjptYXAoKWBdKGh0dHBzOi8vcHVycnIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvbWFwLmh0bWwpIHRvIGl0ZXJhdGUgb3ZlciB0aGUgbGlzdDoKCgpgYGB7ciBtYXAgY2x1c3Rlcl9yZXN1bHRzX2xpc3R9CmNsdXN0ZXJfcmVzdWx0c19saXN0IHw+CiAgcHVycnI6Om1hcChoZWFkKQpgYGAKCkdlbmVyYWxseSBzcGVha2luZywgYHB1cnJyOjptYXAoKWAgY2FuIGJlIHVzZWQgdG8gaXRlcmF0ZSBvdmVyIHRoaXMgbGlzdCB0byB2aXN1YWxpemUgb3IgYW5hbHl6ZSBlYWNoIGNsdXN0ZXJpbmcgcmVzdWx0IG9uIGl0cyBvd247IHdlJ2xsIHVzZSB0aGlzIGFwcHJvYWNoIGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnMuIAoKIyMgVmlzdWFsaXppbmcgY2x1c3RlcmluZyByZXN1bHRzCgpXaGVuIGNvbXBhcmluZyBjbHVzdGVyaW5nIHJlc3VsdHMsIGl0J3MgaW1wb3J0YW50IHRvIGZpcnN0IHZpc3VhbGl6ZSB0aGUgZGlmZmVyZW50IGNsdXN0ZXJpbmdzIHRvIGJ1aWxkIGNvbnRleHQgZm9yIGludGVycHJldGluZyBRQyBtZXRyaWNzLgoKQXMgb25lIGV4YW1wbGUgb2Ygd2h5IHRoaXMgaXMgaW1wb3J0YW50LCB3ZSBnZW5lcmFsbHkgZXhwZWN0IHRoYXQgbW9yZSByb2J1c3QgY2x1c3RlcnMgd2lsbCBoYXZlIGhpZ2hlciB2YWx1ZXMgZm9yIG1ldHJpY3MgbGlrZSBzaWxob3VldHRlIHdpZHRoIGFuZCBuZWlnaGJvcmhvb2QgcHVyaXR5LgpIb3dldmVyLCB3ZSBhbHNvIGV4cGVjdCB0aGF0IGhhdmluZyBmZXdlciBjbHVzdGVycyBpbiB0aGUgZmlyc3QgcGxhY2Ugd2lsbCBhbHNvIGxlYWQgdG8gaGlnaGVyIG1ldHJpY3MsIHJlZ2FyZGxlc3Mgb2YgY2x1c3RlciBxdWFsaXR5OiBXaGVuIHRoZXJlIGFyZSBmZXdlciBjbHVzdGVycywgaXQgaXMgbW9yZSBsaWtlbHkgdGhhdCBjbHVzdGVycyBvdmVybGFwIGxlc3Mgd2l0aCBvbmUgYW5vdGhlciBqdXN0IGJlY2F1c2UgdGhlcmUgYXJlbid0IG1hbnkgY2x1c3RlcnMgaW4gdGhlIGZpcnN0IHBsYWNlLgpUaGlzIG1lYW5zIHRoYXQsIHdoZW4gaW50ZXJwcmV0aW5nIGNsdXN0ZXIgcXVhbGl0eSBtZXRyaWNzLCB5b3Ugc2hvdWxkIGJlIGNhcmVmdWwgdG8gdGFrZSBtb3JlIGNvbnRleHQgYWJvdXQgdGhlIGRhdGEgaW50byBjb25zaWRlcmF0aW9uIGFuZCBub3Qgb25seSByZWx5IG9uIHRoZSBtZXRyaWMgdmFsdWVzLgoKV2UnbGwgdGhlcmVmb3JlIHZpc3VhbGl6ZSB0aGVzZSByZXN1bHRzIGFzIFVNQVBzIGJ5IGl0ZXJhdGluZyBvdmVyIGBjbHVzdGVyX3Jlc3VsdHNfbGlzdGAgYW5kIGNvbWJpbmluZyBwbG90cyB3aXRoIFtgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKClgXShodHRwczovL3BhdGNod29yay5kYXRhLWltYWdpbmlzdC5jb20vcmVmZXJlbmNlL3dyYXBfcGxvdHMuaHRtbCkuCldlJ2xsIHNwZWNpZmljYWxseSB1c2UgW2BwdXJycjo6aW1hcCgpYF0oaHR0cHM6Ly9wdXJyci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9pbWFwLmh0bWwpIHRvIGl0ZXJhdGUgc28gdGhhdCB3ZSBjYW4gYXNzaWduIHRoaXMgbGlzdCdzIG5hbWVzIGFzIHBsb3QgdGl0bGVzLgoKRm9yIHRoaXMsIHdlJ2xsIGJlZ2luIGJ5IGV4dHJhY3RpbmcgYSB0YWJsZSBvZiBVTUFQIGNvb3JkaW5hdGVzIGZyb20gb3VyIFNDRSBvYmplY3QuCgpgYGB7ciBjcmVhdGUgdW1hcF9kZn0KdW1hcF9kZiA8LSByZWR1Y2VkRGltKHNjZSwgIlVNQVAiKSB8PgogIGFzLmRhdGEuZnJhbWUoKQpgYGAKCk5leHQsIHdlJ2xsIGl0ZXJhdGUgb3ZlciBgY2x1c3Rlcl9yZXN1bHRzX2xpc3RgIHRvIHBsb3QgdGhlIFVNQVBzLgoKYGBge3IgcGxvdCBhbGwgdW1hcHMsIGZpZy53aWR0aCA9IDEyfQp1bWFwX3Bsb3RzIDwtIGNsdXN0ZXJfcmVzdWx0c19saXN0IHw+CiAgcHVycnI6OmltYXAoCiAgICBcKGNsdXN0ZXJfZGYsIGNsdXN0ZXJpbmdfbmFtZSkgewogICAgICAKICAgICAgIyBBZGQgYSBjb2x1bW4gd2l0aCBjbHVzdGVyIGFzc2lnbm1lbnRzIHRvIHVtYXBfZGYKICAgICAgdW1hcF9kZl9wbG90IDwtIHVtYXBfZGYgfD4KICAgICAgICBkcGx5cjo6bXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyX2RmJGNsdXN0ZXIpCgogICAgICAjIFBsb3QgdGhlIFVNQVAsIGNvbG9yZWQgYnkgdGhlIG5ldyBjbHVzdGVyIHZhcmlhYmxlCiAgICAgIGdncGxvdCh1bWFwX2RmX3Bsb3QsIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjbHVzdGVyKSkgKwogICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsKICAgICAgICBsYWJzKHRpdGxlID0gZ2x1ZTo6Z2x1ZSgibmVhcmVzdCBuZWlnaGJvcnM6IHtjbHVzdGVyaW5nX25hbWV9IikpICsKICAgICAgICAjIFdlJ2xsIGFkZCBhIGNvdXBsZSBVTUFQIHBsb3Qgc2V0dGluZ3MgaGVyZSwgaW5jbHVkaW5nIGVxdWFsIGF4ZXMgYW5kIAogICAgICAgICMgdHVybmluZyBvZmYgdGhlIGF4aXMgdGlja3MgYW5kIHRleHQgc2luY2UgVU1BUCBjb29yZGluYXRlcyBhcmUgbm90IG1lYW5pbmdmdWwKICAgICAgICBjb29yZF9lcXVhbCgpICsgCiAgICAgICAgdGhlbWUoCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpCiAgICAgICAgKQogICAgfQogICkKCiMgUHJpbnQgdGhlIHBsb3RzIHdpdGggcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKCkKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHVtYXBfcGxvdHMpCmBgYAoKVGhlc2UgcGxvdHMgc2hvdyB0aGF0IHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgZGVjcmVhc2VzIGFzIHRoZSBuZWFyZXN0IG5laWdoYm9ycyBwYXJhbWV0ZXIgaW5jcmVhc2VzLCB3aXRoIGJldHdlZW4gOS0xMyBjbHVzdGVycy4KCgoKCiMjIEV2YWx1YXRpbmcgY2x1c3RlcmluZyByZXN1bHRzCgpUaGlzIHNlY3Rpb24gd2lsbCB1c2UgYHB1cnJyOjptYXAoKWAgdG8gaXRlcmF0ZSBvdmVyIGVhY2ggY2x1c3RlcmluZyByZXN1bHQgZGF0YSBmcmFtZSB0byBjYWxjdWxhdGUgc2lsaG91ZXR0ZSB3aWR0aCwgbmVpZ2hib3Job29kIHB1cml0eSwgYW5kIHN0YWJpbGl0eSwgYW5kIHRoZW4gdmlzdWFsaXplIHJlc3VsdHMuClRoZSBnb2FsIG9mIHRoaXMgY29kZSBpcyB0byBpZGVudGlmeSB3aGV0aGVyIG9uZSBjbHVzdGVyaW5nIHBhcmFtZXRlcml6YXRpb24gcHJvZHVjZXMgbW9yZSByZWxpYWJsZSBjbHVzdGVycy4gCgoKIyMjIFNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkKCkJvdGggc2lsaG91ZXR0ZSB3aWR0aCBhbmQgbmVpZ2hib3Job29kIHB1cml0eSBhcmUgY2VsbC1sZXZlbCBxdWFudGl0aWVzLCBzbyB3ZSBjYW4gY2FsY3VsYXRlIHRoZW0gdG9nZXRoZXIgaW4gdGhlIHNhbWUgY2FsbCB0byBgcHVycnI6Om1hcCgpYC4KQmVsb3csIHdlJ2xsIGl0ZXJhdGUgb3ZlciBlYWNoIGRhdGEgZnJhbWUgaW4gYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBjYWxjdWxhdGUgdGhlc2UgcXVhbnRpdGllcy4KCmBgYHtyIGNhbGN1bGF0ZSBjZWxsIGxldmVsIG1ldHJpY3N9CmNlbGxfbWV0cmljX2xpc3QgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgIAogICAgICAjIGNhbGN1bGF0ZSBzaWxob3VldHRlIHdpZHRoCiAgICAgIHNpbGhvdWV0dGVfZGYgPC0gck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3NpbGhvdWV0dGUocGNhX21hdHJpeCwgY2x1c3Rlcl9kZikKICAgICAgCiAgICAgICMgY2FsY3VsYXRlIG5laWdoYmhvcmhvb2QgcHVyaXR5IAogICAgICBwdXJpdHlfZGYgPC0gck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3B1cml0eShwY2FfbWF0cml4LCBjbHVzdGVyX2RmKQogICAgICAKICAgICAgIyBDb21iaW5lIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZQogICAgICBkcGx5cjo6bGVmdF9qb2luKHNpbGhvdWV0dGVfZGYsIHB1cml0eV9kZikKICAgIH0KICApCgojIFZpZXcgdGhlIGZpcnN0IHNpeCByb3dzIG9mIGVhY2ggY2x1c3RlcmluZyByZXN1bHQncyBjZWxsLWxldmVsIFFDIG1ldHJpY3MKcHVycnI6Om1hcChjZWxsX21ldHJpY19saXN0LCBoZWFkKQpgYGAKCgpUbyB2aXN1YWxpemUgdGhlc2UgcmVzdWx0cywgd2UgY2FuIGNvbWJpbmUgYWxsIGRhdGEgZnJhbWVzIGluIHRoaXMgbGlzdCBpbnRvIGEgc2luZ2xlIG92ZXJhbGwgZGF0YSBmcmFtZSwgd2hlcmUgdGhlIGV4aXN0aW5nIGBubmAgY29sdW1uIHdpbGwgZGlzdGluZ3Vpc2ggYW1vbmcgY29uZGl0aW9ucy4KCmBgYHtyIGNvbWJpbmUgY2VsbCBtZXRyaWNzIGxpc3R9CmNlbGxfbWV0cmljc19kZiA8LSBkcGx5cjo6YmluZF9yb3dzKGNlbGxfbWV0cmljX2xpc3QpCmBgYAoKV2UgY2FuIHZpc3VhbGl6ZSBzaWxob3VldHRlIHdpZHRoIGFuZCBuZWlnaGJvcmhvb2QgcHVyaXR5IGVhY2ggd2l0aCBib3hwbG90cywgZm9yIGV4YW1wbGUsIGFuZCB1c2UgdGhlIFtgcGF0Y2h3b3JrYF0oaHR0cHM6Ly9wYXRjaHdvcmsuZGF0YS1pbWFnaW5pc3QuY29tLykgcGFja2FnZSB0byBwcmludCB0aGVtIHRvZ2V0aGVyOgoKCmBgYHtyfQojIFBsb3Qgc2lsaG91ZXR0ZSB3aWR0aApzaWxob3VldHRlX3Bsb3QgPC0gZ2dwbG90KGNlbGxfbWV0cmljc19kZikgKyAKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBzaWxob3VldHRlX3dpZHRoLCBmaWxsID0gYXMuZmFjdG9yKG5uKSkgKyAKICBnZW9tX2JveHBsb3QoKSArIAogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMiLCAKICAgIHkgPSAiU2lsaG91ZXR0ZSB3aWR0aCIKICApCgoKIyBQbG90IG5laWdoYm9yaG9vZCBwdXJpdHkgd2lkdGgKcHVyaXR5X3Bsb3QgPC0gZ2dwbG90KGNlbGxfbWV0cmljc19kZikgKyAKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBwdXJpdHksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsIAogICAgeSA9ICJOZWlnaGJvcmhvb2QgcHVyaXR5IgogICkKCgojIEFkZCB0b2dldGhlciB1c2luZyB0aGUgcGF0Y2h3b3JrIGxpYnJhcnksIHdpdGhvdXQgYSBsZWdlbmQKc2lsaG91ZXR0ZV9wbG90ICsgcHVyaXR5X3Bsb3QgJiB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYApXaGlsZSB0aGVyZSBkb2VzIG5vdCBhcHBlYXIgdG8gYmUgYSBzYWxpZW50IGRpZmZlcmVuY2UgYW1vbmcgc2lsaG91ZXR0ZSB3aWR0aCBkaXN0cmlidXRpb25zLCBpdCBkb2VzIGFwcGVhciB0aGF0IHB1cml0eSBpcyBoaWdoZXIgd2l0aCBhIGhpZ2hlciBuZWFyZXN0IG5laWdoYm9ycyBwYXJhbWV0ZXIuCgoKIyMjIFN0YWJpbGl0eQoKTmV4dCwgd2UnbGwgY2FsY3VsYXRlIHN0YWJpbGl0eSBvbiB0aGUgY2x1c3RlcnMgdXNpbmcgYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zdGFiaWxpdHkoKWAsIHNwZWNpZnlpbmcgdGhlIHNhbWUgcGFyYW1ldGVyIHVzZWQgZm9yIHRoZSBvcmlnaW5hbCBjbHVzdGVyIGNhbGN1bGF0aW9uIGF0IGVhY2ggaXRlcmF0aW9uLiAKCmBgYHtyIGNhbGN1bGF0ZSBzdGFiaWxpdHl9CnN0YWJpbGl0eV9saXN0IDwtIGNsdXN0ZXJfcmVzdWx0c19saXN0IHw+CiAgcHVycnI6Om1hcCgKICAgIFwoY2x1c3Rlcl9kZikgewogICAgICAKICAgICAgbm4gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkbm4pCgogICAgICAjIGNhbGN1bGF0ZSBzdGFiaWxpdHksIHBhc3NpbmcgaW4gdGhlIHBhcmFtZXRlciB2YWx1ZSB1c2VkIGZvciB0aGlzIGl0ZXJhdGlvbgogICAgICByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KHBjYV9tYXRyaXgsIGNsdXN0ZXJfZGYsIG5uID0gbm4pCiAgICB9CiAgKQpgYGAKCldlJ2xsIGFnYWluIGNvbWJpbmUgdGhlIG91dHB1dCBvZiBgc3RhYmlsaXR5X2xpc3RgIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZSBhbmQgcGxvdCBgYXJpYCB2YWx1ZXMgYWNyb3NzIGBubmAgcGFyYW1ldGVyaXphdGlvbnMuCgoKYGBge3IgY29tYmluZSBwbG90IHN0YWJpbGl0eX0Kc3RhYmlsaXR5X2RmIDwtIGRwbHlyOjpiaW5kX3Jvd3Moc3RhYmlsaXR5X2xpc3QpCgpnZ3Bsb3Qoc3RhYmlsaXR5X2RmKSArIAogIGFlcyh4ID0gYXMuZmFjdG9yKG5uKSwgeSA9IGFyaSwgZmlsbCA9IGFzLmZhY3RvcihubikpICsgCiAgZ2VvbV9ib3hwbG90KCkgKyAKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhc3RlbDIiKSArCiAgbGFicygKICAgIHggPSAiTnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzIiwgCiAgICB5ID0gIkFkanVzdGVkIFJhbmQgSW5kZXgiCiAgKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpIZXJlLCB3ZSBzZWUgdGhhdCBhIG5lYXJlc3QgbmVpZ2hib3JzIHZhbHVlIG9mIDIwIG9yIDMwIGxlYWRzIHRvIG1vcmUgc3RhYmxlIGNsdXN0ZXJpbmcgcmVzdWx0cyBjb21wYXJlZCB0byAxMC4gCgoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9uIGluZm99CiMgcmVjb3JkIHRoZSB2ZXJzaW9ucyBvZiB0aGUgcGFja2FnZXMgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGFuZCBvdGhlciBlbnZpcm9ubWVudCBpbmZvcm1hdGlvbgpzZXNzaW9uSW5mbygpCmBgYAo=
+ + +
+
+ +
+ + + + + + + + + + + + + + + + + From 947f41fb254adf2adb3db115126d1398e5acd2c9 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Mon, 13 Jan 2025 10:49:49 -0500 Subject: [PATCH 04/16] add section for varying multiple parameters, and other tweaks --- .../02_compare-clustering-parameters.Rmd | 163 ++++++++++- .../02_compare-clustering-parameters.nb.html | 264 +++++++++++++++--- 2 files changed, 383 insertions(+), 44 deletions(-) diff --git a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd index 9ceeca0e7..1133ce947 100644 --- a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd +++ b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd @@ -91,7 +91,7 @@ As shown in [`01_perform-evaluate-clustering.Rmd`](01_perform-evaluate-clusterin pca_matrix <- reducedDim(sce, "PCA") ``` -## Perform clustering across a set of parameters +## 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()`. @@ -151,7 +151,7 @@ cluster_results_list |> 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 +### Visualizing clustering results When comparing clustering results, it's important to first visualize the different clusterings to build context for interpreting QC metrics. @@ -171,7 +171,7 @@ umap_df <- reducedDim(sce, "UMAP") |> Next, we'll iterate over `cluster_results_list` to plot the UMAPs. -```{r plot all umaps, fig.width = 12} +```{r plot nn umaps, fig.width = 12} umap_plots <- cluster_results_list |> purrr::imap( \(cluster_df, clustering_name) { @@ -202,13 +202,13 @@ These plots show that the number of clusters decreases as the nearest neighbors -## Evaluating clustering results +### 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 +#### 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. @@ -268,10 +268,11 @@ purity_plot <- ggplot(cell_metrics_df) + # 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. -### Stability +#### 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. @@ -307,6 +308,156 @@ ggplot(stability_df) + 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 <- seq(10, 30, 10) +res_values <- seq(5, 15, 5) / 10 + + +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: {unique(cluster_df$nn)}; res: {unique(cluster_df$resolution)}" + ) + + # 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(), + legend.position = "bottom" + ) + } + ) + +# Print the plots with patchwork::wrap_plots() +patchwork::wrap_plots(umap_plots) +``` + + + +### 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) + } + ) |> + dplyr::bind_rows() +``` + + +We'll add a column `resolution_label` which we'll use to have informative panel titles in the faceted ggplot we make next. + +```{r add resolution_label column} +purity_df <- purity_df |> + dplyr::mutate(resolution_label = glue::glue("Resolution: {resolution}")) +``` + +```{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 + facet_wrap(vars(resolution_label)) + + 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 + ) + } + ) |> + dplyr::bind_rows() + +stability_df <- stability_df |> + dplyr::mutate(resolution_label = glue::glue("Resolution: {resolution}")) +``` + + +```{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 by resolution + facet_wrap(vars(resolution_label)) + + labs( + x = "Number of nearest neighbors", + y = "Adjusted Rand Index" + ) + + theme(legend.position = "none") +``` + + + ## Session Info ```{r session info} diff --git a/analyses/hello-clusters/02_compare-clustering-parameters.nb.html b/analyses/hello-clusters/02_compare-clustering-parameters.nb.html index d6c524fd2..b29b98fde 100644 --- a/analyses/hello-clusters/02_compare-clustering-parameters.nb.html +++ b/analyses/hello-clusters/02_compare-clustering-parameters.nb.html @@ -3027,8 +3027,8 @@

Read in and prepare data

-
-

Perform clustering across a set of parameters

+
+

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().

@@ -3140,9 +3140,8 @@

Perform clustering across a set of parameters

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

+
+

Visualizing clustering results

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

@@ -3174,11 +3173,10 @@

Visualizing clustering results

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)
@@ -3187,9 +3185,9 @@ 

Visualizing clustering results

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 + # 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() + + coord_equal() + theme( axis.ticks = element_blank(), axis.text = element_blank() @@ -3208,32 +3206,31 @@

Visualizing clustering results

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

-
-

Evaluating clustering results

+
+

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

+
+

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 
+
+      # calculate neighbhorhood purity
       purity_df <- rOpenScPCA::calculate_purity(pca_matrix, cluster_df)
-      
+
       # Combine into a single data frame
       dplyr::left_join(silhouette_df, purity_df)
     }
@@ -3317,25 +3314,25 @@ 

Silhouette width and neighborhood purity

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() + 
+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", 
+    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() + 
+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", 
+    x = "Number of nearest neighbors",
     y = "Neighborhood purity"
   )
 
@@ -3352,19 +3349,18 @@ 

Silhouette width and neighborhood purity

silhouette width distributions, it does appear that purity is higher with a higher nearest neighbors parameter.

-
-

Stability

+
+

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 <- unique(cluster_df$nn)
 
       # calculate stability, passing in the parameter value used for this iteration
@@ -3379,17 +3375,17 @@ 

Stability

nn parameterizations.

- +
stability_df <- dplyr::bind_rows(stability_list)
 
-ggplot(stability_df) + 
-  aes(x = as.factor(nn), y = ari, fill = as.factor(nn)) + 
-  geom_boxplot() + 
+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", 
+    x = "Number of nearest neighbors",
     y = "Adjusted Rand Index"
-  ) + 
+  ) +
   theme(legend.position = "none")
@@ -3401,6 +3397,198 @@

Stability

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 <- seq(10, 30, 10)
+res_values <- seq(5, 15, 5)/10
+
+
+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: {unique(cluster_df$nn)}; res: {unique(cluster_df$resolution)}"
+      )
+
+      # 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(),
+          legend.position = "bottom"
+        )
+  }
+)
+
+# Print the plots with patchwork::wrap_plots()
+patchwork::wrap_plots(umap_plots)
+ + +

+ + + +
+
+

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)
+    }
+  ) |>
+  dplyr::bind_rows()
+ + + +

We’ll add a column resolution_label which we’ll use to +have informative panel titles in the faceted ggplot we make next.

+ + + +
purity_df <- purity_df |>
+  dplyr::mutate(resolution_label = glue::glue("Resolution: {resolution}"))
+ + + + + + +
ggplot(purity_df) +
+  aes(x = as.factor(nn), y = purity, fill = as.factor(nn)) +
+  geom_boxplot() +
+  scale_fill_brewer(palette = "Pastel2") +
+  # facet by resolution
+  facet_wrap(vars(resolution_label)) +
+  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
+      )
+    }
+  ) |>
+  dplyr::bind_rows()
+
+stability_df <- stability_df |>
+  dplyr::mutate(resolution_label = glue::glue("Resolution: {resolution}"))
+ + + + + + +
ggplot(stability_df) +
+  aes(x = as.factor(nn), y = ari, fill = as.factor(nn)) +
+  geom_boxplot() +
+  scale_fill_brewer(palette = "Pastel2") +
+  # facet by resolution
+  facet_wrap(vars(resolution_label)) +
+  labs(
+    x = "Number of nearest neighbors",
+    y = "Adjusted Rand Index"
+  ) +
+  theme(legend.position = "none")
+ + +

+ + + +
+

Session Info

@@ -3465,7 +3653,7 @@

Session Info

-
LS0tCnRpdGxlOiAiQ29tcGFyaW5nIGNsdXN0ZXJpbmcgcGFyYW1ldGVycyB3aXRoIHJPcGVuU2NQQ0EiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKYXV0aG9yOiAiRGF0YSBMYWIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojIyBJbnRyb2R1Y3Rpb24KCkNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBoYXZlIHNldmVyYWwgcGFyYW1ldGVycyB3aGljaCBjYW4gYmUgdmFyaWVkLCBsZWFkaW5nIHRvIGRpZmZlcmVudCBjbHVzdGVyaW5nIHJlc3VsdHMuCkEga2V5IHF1ZXN0aW9uIHdoZW4gY2x1c3RlcmluZywgdGhlcmVmb3JlLCBpcyBob3cgdG8gaWRlbnRpZnkgYSBzZXQgb2YgcGFyYW1ldGVycyB0aGF0IGxlYWQgdG8gcm9idXN0IGFuZCByZWxpYWJsZSBjbHVzdGVycyB0aGF0IGNhbiBiZSB1c2VkIGluIGRvd25zdHJlYW0gYW5hbHlzaXMuIAoKVGhpcyBub3RlYm9vayBwcm92aWRlcyBleGFtcGxlcyBvZiBob3cgdG8gdXNlIHRoZSBgck9wZW5TY1BDQWAgcGFja2FnZSB0bzoKCiogQ2FsY3VsYXRlIHNldmVyYWwgdmVyc2lvbnMgb2YgY2x1c3RlcmluZyByZXN1bHRzIGFjcm9zcyBzZXZlcmFsIGRpZmZlcmVudCBwYXJhbWV0ZXJpemF0aW9ucwoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGFjcm9zcyBjbHVzdGVyaW5nIHJlc3VsdHMKClBsZWFzZSByZWZlciB0byB0aGUgW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCkgbm90ZWJvb2sgZm9yIGEgdHV0b3JpYWwgb24gdXNpbmcgYHJPcGVuU2NQQ0FgIGZ1bmN0aW9ucyB0bzoKCiogQ2FsY3VsYXRlIGNsdXN0ZXJzIGZyb20gYSBzaW5nbGUgcGFyYW1ldGVyaXphdGlvbgoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGEgc2luZ2xlIHNldCBvZiBjbHVzdGVycywgYXMgd2VsbCBhcyBleHBsYW5hdGlvbnMgb2YgdGhlIG1ldHJpY3MgdGhlbXNlbHZlcwoKVGhpcyBub3RlYm9vayB3aWxsIHVzZSB0aGUgc2FtcGxlIGBTQ1BDUzAwMDAwMWAgZnJvbSBwcm9qZWN0IGBTQ1BDUDAwMDAwMWAsIHdoaWNoIGlzIGFzc3VtZWQgcHJlc2VudCBpbiB0aGUgYE9wZW5TY1BDQS1hbmFseXNpcy9kYXRhL2N1cnJlbnQvU0NQQ1AwMDAwMDFgIGRpcmVjdG9yeSwgZm9yIGFsbCBleGFtcGxlcy4KUGxlYXNlIFtzZWUgdGhpcyBkb2N1bWVudGF0aW9uXShodHRwczovL29wZW5zY3BjYS5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvZ2V0dGluZy1zdGFydGVkL2FjY2Vzc2luZy1yZXNvdXJjZXMvZ2V0dGluZy1hY2Nlc3MtdG8tZGF0YS8pIGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IG9idGFpbmluZyBTY1BDQSBkYXRhLgoKIyMgU2V0dXAKCiMjIyBQYWNrYWdlcwoKCmBgYHtyIHBhY2thZ2VzfQpsaWJyYXJ5KHJPcGVuU2NQQ0EpCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQp9KQoKIyBTZXQgZ2dwbG90IHRoZW1lIGZvciBwbG90cwp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgoKIyMjIFBhdGhzCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19naXRfcm9vdCkKCiMgVGhlIGN1cnJlbnQgZGF0YSBkaXJlY3RvcnksIGZvdW5kIHdpdGhpbiB0aGUgcmVwb3NpdG9yeSBiYXNlIGRpcmVjdG9yeQpkYXRhX2RpciA8LSBmaWxlLnBhdGgocmVwb3NpdG9yeV9iYXNlLCAiZGF0YSIsICJjdXJyZW50IikKCiMgVGhlIHBhdGggdG8gdGhpcyBtb2R1bGUKbW9kdWxlX2Jhc2UgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImFuYWx5c2VzIiwgImhlbGxvLWNsdXN0ZXJzIikKYGBgCgpgYGB7ciBpbnB1dCBmaWxlIHBhdGh9CiMgUGF0aCB0byBwcm9jZXNzZWQgU0NFIGZpbGUgZm9yIHNhbXBsZSBTQ1BDUzAwMDAwMQppbnB1dF9zY2VfZmlsZSA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJTQ1BDUDAwMDAwMSIsICJTQ1BDUzAwMDAwMSIsICJTQ1BDTDAwMDAwMV9wcm9jZXNzZWQucmRzIikKYGBgCgoKIyMjIFNldCB0aGUgcmFuZG9tIHNlZWQKCkJlY2F1c2UgY2x1c3RlcmluZyBpbnZvbHZlcyByYW5kb20gc2FtcGxpbmcsIGl0IGlzIGltcG9ydGFudCB0byBzZXQgdGhlIHJhbmRvbSBzZWVkIGF0IHRoZSB0b3Agb2YgeW91ciBhbmFseXNpcyBzY3JpcHQgb3Igbm90ZWJvb2sgdG8gZW5zdXJlIHJlcHJvZHVjaWJpbGl0eS4KCmBgYHtyIHNldCBzZWVkfQpzZXQuc2VlZCgyMDI0KQpgYGAKCiMjIFJlYWQgaW4gYW5kIHByZXBhcmUgZGF0YQoKVG8gYmVnaW4sIHdlJ2xsIHJlYWQgaW4gdGhlIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgKFNDRSkgb2JqZWN0LgoKYGBge3IgcmVhZCBkYXRhfQojIFJlYWQgdGhlIFNDRSBmaWxlCnNjZSA8LSByZWFkUkRTKGlucHV0X3NjZV9maWxlKQpgYGAKCkZvciB0aGUgaW5pdGlhbCBjbHVzdGVyIGNhbGN1bGF0aW9ucyBhbmQgZXZhbHVhdGlvbnMsIHdlIHdpbGwgdXNlIHRoZSBQQ0EgbWF0cml4IGV4dHJhY3RlZCBmcm9tIHRoZSBTQ0Ugb2JqZWN0LgpBcyBzaG93biBpbiBbYDAxX3BlcmZvcm0tZXZhbHVhdGUtY2x1c3RlcmluZy5SbWRgXSgwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kKSwgaXQgaXMgYWxzbyBwb3NzaWJsZSB0byB1c2UgYW4gU0NFIG9iamVjdCBvciBhIFNldXJhdCBvYmplY3QgZGlyZWN0bHkuCgoKYGBge3IgZXh0cmFjdCBwY2EgZGF0YX0KIyBFeHRyYWN0IHRoZSBQQ0EgbWF0cml4IGZyb20gYW4gU0NFIG9iamVjdApwY2FfbWF0cml4IDwtIHJlZHVjZWREaW0oc2NlLCAiUENBIikKYGBgCgojIyBQZXJmb3JtIGNsdXN0ZXJpbmcgYWNyb3NzIGEgc2V0IG9mIHBhcmFtZXRlcnMKClRoaXMgc2VjdGlvbiB3aWxsIHNob3cgaG93IHRvIHBlcmZvcm0gY2x1c3RlcmluZyBhY3Jvc3MgYSBzZXQgb2YgcGFyYW1ldGVycyAoYWthLCAic3dlZXAiIGEgc2V0IG9mIHBhcmFtZXRlcnMpIHdpdGggYHJPcGVuU2NQQ0E6OnN3ZWVwX2NsdXN0ZXJzKClgLiAKClRoaXMgZnVuY3Rpb24gdGFrZXMgYSBQQ0EgbWF0cml4IHdpdGggcm93IG5hbWVzIHJlcHJlc2VudGluZyB1bmlxdWUgY2VsbCBpZHMgKGUuZy4sIGJhcmNvZGVzKSBhcyBpdHMgcHJpbWFyeSBhcmd1bWVudCwgd2l0aCBhZGRpdGlvbmFsIGFyZ3VtZW50cyBmb3IgY2x1c3RlciBwYXJhbWV0ZXJzLiAKVGhpcyBmdW5jdGlvbiB3cmFwcyB0aGUgYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9jbHVzdGVycygpYCBmdW5jdGlvbiBidXQgYWxsb3dzIHlvdSB0byBwcm92aWRlIGEgdmVjdG9yIG9mIHBhcmFtZXRlciB2YWx1ZXMgdG8gcGVyZm9ybSBjbHVzdGVyaW5nIGFjcm9zcywgYXMgbGlzdGVkIGJlbG93LgpDbHVzdGVycyB3aWxsIGJlIGNhbGN1bGF0ZWQgZm9yIGFsbCBjb21iaW5hdGlvbnMgb2YgcGFyYW1ldGVycyB2YWx1ZXMgKHdoZXJlIGFwcGxpY2FibGUpOyBkZWZhdWx0IHZhbHVlcyB0aGF0IHRoZSBmdW5jdGlvbiB3aWxsIHVzZSBmb3IgYW55IHVuc3BlY2lmaWVkIHBhcmFtZXRlciB2YWx1ZXMgYXJlIHNob3duIGluIHBhcmVudGhlc2VzCgoqIGBhbGdvcml0aG1gOiBXaGljaCBjbHVzdGVyaW5nIGFsZ29yaXRobSB0byB1c2UgKExvdXZhaW4pCiogYHdlaWdodGluZ2A6IFdoaWNoIHdlaWdodGluZyBzY2hlbWUgdG8gdXNlIChKYWNjYXJkKQoqIGBubmA6IFRoZSBudW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMgKDEwKQoqIGByZXNvbHV0aW9uYDogVGhlIHJlc29sdXRpb24gcGFyYW1ldGVyICgxOyB1c2VkIG9ubHkgd2l0aCBMb3V2YWluIGFuZCBMZWlkZW4gY2x1c3RlcmluZykKKiBgb2JqZWN0aXZlX2Z1bmN0aW9uYDogVGhlIG9iamVjdGl2ZSBmdW5jdGlvbiB0byBvcHRpbWl6ZSBjbHVzdGVycyAoQ1BNOyB1c2VkIG9ubHkgd2l0aCBMZWlkZW4gY2x1c3RlcmluZykKCmByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygpYCBkb2VzIGFsbG93IHlvdSB0byBzcGVjaWZ5IHZhbHVlcyBmb3IgYW55IG90aGVyIHBhcmFtZXRlcnMuIAoKClRoaXMgZnVuY3Rpb24gd2lsbCByZXR1cm4gYSBsaXN0IG9mIGRhdGEgZnJhbWVzIG9mIGNsdXN0ZXJpbmcgcmVzdWx0cy4gCkVhY2ggZGF0YSBmcmFtZSB3aWxsIGhhdmUgdGhlIGZvbGxvd2luZyBjb2x1bW5zOgoKKiBgY2VsbF9pZGA6IFVuaXF1ZSBjZWxsIGlkZW50aWZpZXJzLCBvYnRhaW5lZCBmcm9tIHRoZSBQQ0EgbWF0cml4J3Mgcm93IG5hbWVzCiogYGNsdXN0ZXJgOiBBIGZhY3RvciBjb2x1bW4gd2l0aCB0aGUgY2x1c3RlciBpZGVudGl0aWVzCiogVGhlcmUgd2lsbCBiZSBvbmUgY29sdW1uIGZvciBlYWNoIGNsdXN0ZXJpbmcgcGFyYW1ldGVyIHVzZWQKClRvIGRlbW9uc3RyYXRlIHRoaXMgZnVuY3Rpb24sIHdlJ2xsIGNhbGN1bGF0ZSBjbHVzdGVycyB1c2luZyB0aGUgTG91dmFpbiBhbGdvcml0aG0gd2hpbGUgdmFyeWluZyB0aGUgYG5uYCBwYXJhbWV0ZXI6CgpgYGB7ciBzd2VlcCBjbHVzdGVyc30KIyBEZWZpbmUgbm4gcGFyYW1ldGVyIHZhbHVlcyBvZiBpbnRlcmVzdApubl92YWx1ZXMgPC0gc2VxKDEwLCAzMCwgMTApCgojIENhbGN1bGF0ZSBjbHVzdGVycyB2YXJ5aW5nIG5uLCBidXQgbGVhdmluZyBvdGhlciBwYXJhbWV0ZXJzIGF0IHRoZWlyIGRlZmF1bHQgdmFsdWVzCmNsdXN0ZXJfcmVzdWx0c19saXN0IDwtIHJPcGVuU2NQQ0E6OnN3ZWVwX2NsdXN0ZXJzKAogIHBjYV9tYXRyaXgsCiAgbm4gPSBubl92YWx1ZXMKKQpgYGAKClRoZSByZXN1bHRpbmcgbGlzdCBoYXMgYSBsZW5ndGggb2YgdGhyZWUsIG9uZSBkYXRhIGZyYW1lIGZvciBlYWNoIGBubmAgcGFyYW1ldGVyIHRlc3RlZDoKCmBgYHtyIGxlbmd0aCBjbHVzdGVyX3Jlc3VsdHNfbGlzdH0KbGVuZ3RoKGNsdXN0ZXJfcmVzdWx0c19saXN0KQpgYGAKCkl0IGNhbiBiZSBoZWxwZnVsIChhbHRob3VnaCBpdCBpcyBub3Qgc3RyaWN0bHkgbmVjZXNzYXJ5IHRvIGtlZXAgdHJhY2spIHRvIG5hbWUgdGhpcyBsaXN0IGJ5IHRoZSB2YXJpZWQgYG5uYCBwYXJhbWV0ZXI6CgpgYGB7ciBzZXQgbGlzdCBuYW1lc30KbmFtZXMoY2x1c3Rlcl9yZXN1bHRzX2xpc3QpIDwtIGdsdWU6OmdsdWUoIm5uX3tubl92YWx1ZXN9IikKYGBgCgoKV2UgY2FuIGxvb2sgYXQgdGhlIGZpcnN0IGZldyByb3dzIG9mIGVhY2ggZGF0YSBmcmFtZSB1c2luZyBbYHB1cnJyOjptYXAoKWBdKGh0dHBzOi8vcHVycnIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvbWFwLmh0bWwpIHRvIGl0ZXJhdGUgb3ZlciB0aGUgbGlzdDoKCgpgYGB7ciBtYXAgY2x1c3Rlcl9yZXN1bHRzX2xpc3R9CmNsdXN0ZXJfcmVzdWx0c19saXN0IHw+CiAgcHVycnI6Om1hcChoZWFkKQpgYGAKCkdlbmVyYWxseSBzcGVha2luZywgYHB1cnJyOjptYXAoKWAgY2FuIGJlIHVzZWQgdG8gaXRlcmF0ZSBvdmVyIHRoaXMgbGlzdCB0byB2aXN1YWxpemUgb3IgYW5hbHl6ZSBlYWNoIGNsdXN0ZXJpbmcgcmVzdWx0IG9uIGl0cyBvd247IHdlJ2xsIHVzZSB0aGlzIGFwcHJvYWNoIGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnMuIAoKIyMgVmlzdWFsaXppbmcgY2x1c3RlcmluZyByZXN1bHRzCgpXaGVuIGNvbXBhcmluZyBjbHVzdGVyaW5nIHJlc3VsdHMsIGl0J3MgaW1wb3J0YW50IHRvIGZpcnN0IHZpc3VhbGl6ZSB0aGUgZGlmZmVyZW50IGNsdXN0ZXJpbmdzIHRvIGJ1aWxkIGNvbnRleHQgZm9yIGludGVycHJldGluZyBRQyBtZXRyaWNzLgoKQXMgb25lIGV4YW1wbGUgb2Ygd2h5IHRoaXMgaXMgaW1wb3J0YW50LCB3ZSBnZW5lcmFsbHkgZXhwZWN0IHRoYXQgbW9yZSByb2J1c3QgY2x1c3RlcnMgd2lsbCBoYXZlIGhpZ2hlciB2YWx1ZXMgZm9yIG1ldHJpY3MgbGlrZSBzaWxob3VldHRlIHdpZHRoIGFuZCBuZWlnaGJvcmhvb2QgcHVyaXR5LgpIb3dldmVyLCB3ZSBhbHNvIGV4cGVjdCB0aGF0IGhhdmluZyBmZXdlciBjbHVzdGVycyBpbiB0aGUgZmlyc3QgcGxhY2Ugd2lsbCBhbHNvIGxlYWQgdG8gaGlnaGVyIG1ldHJpY3MsIHJlZ2FyZGxlc3Mgb2YgY2x1c3RlciBxdWFsaXR5OiBXaGVuIHRoZXJlIGFyZSBmZXdlciBjbHVzdGVycywgaXQgaXMgbW9yZSBsaWtlbHkgdGhhdCBjbHVzdGVycyBvdmVybGFwIGxlc3Mgd2l0aCBvbmUgYW5vdGhlciBqdXN0IGJlY2F1c2UgdGhlcmUgYXJlbid0IG1hbnkgY2x1c3RlcnMgaW4gdGhlIGZpcnN0IHBsYWNlLgpUaGlzIG1lYW5zIHRoYXQsIHdoZW4gaW50ZXJwcmV0aW5nIGNsdXN0ZXIgcXVhbGl0eSBtZXRyaWNzLCB5b3Ugc2hvdWxkIGJlIGNhcmVmdWwgdG8gdGFrZSBtb3JlIGNvbnRleHQgYWJvdXQgdGhlIGRhdGEgaW50byBjb25zaWRlcmF0aW9uIGFuZCBub3Qgb25seSByZWx5IG9uIHRoZSBtZXRyaWMgdmFsdWVzLgoKV2UnbGwgdGhlcmVmb3JlIHZpc3VhbGl6ZSB0aGVzZSByZXN1bHRzIGFzIFVNQVBzIGJ5IGl0ZXJhdGluZyBvdmVyIGBjbHVzdGVyX3Jlc3VsdHNfbGlzdGAgYW5kIGNvbWJpbmluZyBwbG90cyB3aXRoIFtgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKClgXShodHRwczovL3BhdGNod29yay5kYXRhLWltYWdpbmlzdC5jb20vcmVmZXJlbmNlL3dyYXBfcGxvdHMuaHRtbCkuCldlJ2xsIHNwZWNpZmljYWxseSB1c2UgW2BwdXJycjo6aW1hcCgpYF0oaHR0cHM6Ly9wdXJyci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9pbWFwLmh0bWwpIHRvIGl0ZXJhdGUgc28gdGhhdCB3ZSBjYW4gYXNzaWduIHRoaXMgbGlzdCdzIG5hbWVzIGFzIHBsb3QgdGl0bGVzLgoKRm9yIHRoaXMsIHdlJ2xsIGJlZ2luIGJ5IGV4dHJhY3RpbmcgYSB0YWJsZSBvZiBVTUFQIGNvb3JkaW5hdGVzIGZyb20gb3VyIFNDRSBvYmplY3QuCgpgYGB7ciBjcmVhdGUgdW1hcF9kZn0KdW1hcF9kZiA8LSByZWR1Y2VkRGltKHNjZSwgIlVNQVAiKSB8PgogIGFzLmRhdGEuZnJhbWUoKQpgYGAKCk5leHQsIHdlJ2xsIGl0ZXJhdGUgb3ZlciBgY2x1c3Rlcl9yZXN1bHRzX2xpc3RgIHRvIHBsb3QgdGhlIFVNQVBzLgoKYGBge3IgcGxvdCBhbGwgdW1hcHMsIGZpZy53aWR0aCA9IDEyfQp1bWFwX3Bsb3RzIDwtIGNsdXN0ZXJfcmVzdWx0c19saXN0IHw+CiAgcHVycnI6OmltYXAoCiAgICBcKGNsdXN0ZXJfZGYsIGNsdXN0ZXJpbmdfbmFtZSkgewogICAgICAKICAgICAgIyBBZGQgYSBjb2x1bW4gd2l0aCBjbHVzdGVyIGFzc2lnbm1lbnRzIHRvIHVtYXBfZGYKICAgICAgdW1hcF9kZl9wbG90IDwtIHVtYXBfZGYgfD4KICAgICAgICBkcGx5cjo6bXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyX2RmJGNsdXN0ZXIpCgogICAgICAjIFBsb3QgdGhlIFVNQVAsIGNvbG9yZWQgYnkgdGhlIG5ldyBjbHVzdGVyIHZhcmlhYmxlCiAgICAgIGdncGxvdCh1bWFwX2RmX3Bsb3QsIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjbHVzdGVyKSkgKwogICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsKICAgICAgICBsYWJzKHRpdGxlID0gZ2x1ZTo6Z2x1ZSgibmVhcmVzdCBuZWlnaGJvcnM6IHtjbHVzdGVyaW5nX25hbWV9IikpICsKICAgICAgICAjIFdlJ2xsIGFkZCBhIGNvdXBsZSBVTUFQIHBsb3Qgc2V0dGluZ3MgaGVyZSwgaW5jbHVkaW5nIGVxdWFsIGF4ZXMgYW5kIAogICAgICAgICMgdHVybmluZyBvZmYgdGhlIGF4aXMgdGlja3MgYW5kIHRleHQgc2luY2UgVU1BUCBjb29yZGluYXRlcyBhcmUgbm90IG1lYW5pbmdmdWwKICAgICAgICBjb29yZF9lcXVhbCgpICsgCiAgICAgICAgdGhlbWUoCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpCiAgICAgICAgKQogICAgfQogICkKCiMgUHJpbnQgdGhlIHBsb3RzIHdpdGggcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKCkKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHVtYXBfcGxvdHMpCmBgYAoKVGhlc2UgcGxvdHMgc2hvdyB0aGF0IHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgZGVjcmVhc2VzIGFzIHRoZSBuZWFyZXN0IG5laWdoYm9ycyBwYXJhbWV0ZXIgaW5jcmVhc2VzLCB3aXRoIGJldHdlZW4gOS0xMyBjbHVzdGVycy4KCgoKCiMjIEV2YWx1YXRpbmcgY2x1c3RlcmluZyByZXN1bHRzCgpUaGlzIHNlY3Rpb24gd2lsbCB1c2UgYHB1cnJyOjptYXAoKWAgdG8gaXRlcmF0ZSBvdmVyIGVhY2ggY2x1c3RlcmluZyByZXN1bHQgZGF0YSBmcmFtZSB0byBjYWxjdWxhdGUgc2lsaG91ZXR0ZSB3aWR0aCwgbmVpZ2hib3Job29kIHB1cml0eSwgYW5kIHN0YWJpbGl0eSwgYW5kIHRoZW4gdmlzdWFsaXplIHJlc3VsdHMuClRoZSBnb2FsIG9mIHRoaXMgY29kZSBpcyB0byBpZGVudGlmeSB3aGV0aGVyIG9uZSBjbHVzdGVyaW5nIHBhcmFtZXRlcml6YXRpb24gcHJvZHVjZXMgbW9yZSByZWxpYWJsZSBjbHVzdGVycy4gCgoKIyMjIFNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkKCkJvdGggc2lsaG91ZXR0ZSB3aWR0aCBhbmQgbmVpZ2hib3Job29kIHB1cml0eSBhcmUgY2VsbC1sZXZlbCBxdWFudGl0aWVzLCBzbyB3ZSBjYW4gY2FsY3VsYXRlIHRoZW0gdG9nZXRoZXIgaW4gdGhlIHNhbWUgY2FsbCB0byBgcHVycnI6Om1hcCgpYC4KQmVsb3csIHdlJ2xsIGl0ZXJhdGUgb3ZlciBlYWNoIGRhdGEgZnJhbWUgaW4gYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBjYWxjdWxhdGUgdGhlc2UgcXVhbnRpdGllcy4KCmBgYHtyIGNhbGN1bGF0ZSBjZWxsIGxldmVsIG1ldHJpY3N9CmNlbGxfbWV0cmljX2xpc3QgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgIAogICAgICAjIGNhbGN1bGF0ZSBzaWxob3VldHRlIHdpZHRoCiAgICAgIHNpbGhvdWV0dGVfZGYgPC0gck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3NpbGhvdWV0dGUocGNhX21hdHJpeCwgY2x1c3Rlcl9kZikKICAgICAgCiAgICAgICMgY2FsY3VsYXRlIG5laWdoYmhvcmhvb2QgcHVyaXR5IAogICAgICBwdXJpdHlfZGYgPC0gck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3B1cml0eShwY2FfbWF0cml4LCBjbHVzdGVyX2RmKQogICAgICAKICAgICAgIyBDb21iaW5lIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZQogICAgICBkcGx5cjo6bGVmdF9qb2luKHNpbGhvdWV0dGVfZGYsIHB1cml0eV9kZikKICAgIH0KICApCgojIFZpZXcgdGhlIGZpcnN0IHNpeCByb3dzIG9mIGVhY2ggY2x1c3RlcmluZyByZXN1bHQncyBjZWxsLWxldmVsIFFDIG1ldHJpY3MKcHVycnI6Om1hcChjZWxsX21ldHJpY19saXN0LCBoZWFkKQpgYGAKCgpUbyB2aXN1YWxpemUgdGhlc2UgcmVzdWx0cywgd2UgY2FuIGNvbWJpbmUgYWxsIGRhdGEgZnJhbWVzIGluIHRoaXMgbGlzdCBpbnRvIGEgc2luZ2xlIG92ZXJhbGwgZGF0YSBmcmFtZSwgd2hlcmUgdGhlIGV4aXN0aW5nIGBubmAgY29sdW1uIHdpbGwgZGlzdGluZ3Vpc2ggYW1vbmcgY29uZGl0aW9ucy4KCmBgYHtyIGNvbWJpbmUgY2VsbCBtZXRyaWNzIGxpc3R9CmNlbGxfbWV0cmljc19kZiA8LSBkcGx5cjo6YmluZF9yb3dzKGNlbGxfbWV0cmljX2xpc3QpCmBgYAoKV2UgY2FuIHZpc3VhbGl6ZSBzaWxob3VldHRlIHdpZHRoIGFuZCBuZWlnaGJvcmhvb2QgcHVyaXR5IGVhY2ggd2l0aCBib3hwbG90cywgZm9yIGV4YW1wbGUsIGFuZCB1c2UgdGhlIFtgcGF0Y2h3b3JrYF0oaHR0cHM6Ly9wYXRjaHdvcmsuZGF0YS1pbWFnaW5pc3QuY29tLykgcGFja2FnZSB0byBwcmludCB0aGVtIHRvZ2V0aGVyOgoKCmBgYHtyfQojIFBsb3Qgc2lsaG91ZXR0ZSB3aWR0aApzaWxob3VldHRlX3Bsb3QgPC0gZ2dwbG90KGNlbGxfbWV0cmljc19kZikgKyAKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBzaWxob3VldHRlX3dpZHRoLCBmaWxsID0gYXMuZmFjdG9yKG5uKSkgKyAKICBnZW9tX2JveHBsb3QoKSArIAogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMiLCAKICAgIHkgPSAiU2lsaG91ZXR0ZSB3aWR0aCIKICApCgoKIyBQbG90IG5laWdoYm9yaG9vZCBwdXJpdHkgd2lkdGgKcHVyaXR5X3Bsb3QgPC0gZ2dwbG90KGNlbGxfbWV0cmljc19kZikgKyAKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBwdXJpdHksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArIAogIGdlb21fYm94cGxvdCgpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsIAogICAgeSA9ICJOZWlnaGJvcmhvb2QgcHVyaXR5IgogICkKCgojIEFkZCB0b2dldGhlciB1c2luZyB0aGUgcGF0Y2h3b3JrIGxpYnJhcnksIHdpdGhvdXQgYSBsZWdlbmQKc2lsaG91ZXR0ZV9wbG90ICsgcHVyaXR5X3Bsb3QgJiB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYApXaGlsZSB0aGVyZSBkb2VzIG5vdCBhcHBlYXIgdG8gYmUgYSBzYWxpZW50IGRpZmZlcmVuY2UgYW1vbmcgc2lsaG91ZXR0ZSB3aWR0aCBkaXN0cmlidXRpb25zLCBpdCBkb2VzIGFwcGVhciB0aGF0IHB1cml0eSBpcyBoaWdoZXIgd2l0aCBhIGhpZ2hlciBuZWFyZXN0IG5laWdoYm9ycyBwYXJhbWV0ZXIuCgoKIyMjIFN0YWJpbGl0eQoKTmV4dCwgd2UnbGwgY2FsY3VsYXRlIHN0YWJpbGl0eSBvbiB0aGUgY2x1c3RlcnMgdXNpbmcgYHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zdGFiaWxpdHkoKWAsIHNwZWNpZnlpbmcgdGhlIHNhbWUgcGFyYW1ldGVyIHVzZWQgZm9yIHRoZSBvcmlnaW5hbCBjbHVzdGVyIGNhbGN1bGF0aW9uIGF0IGVhY2ggaXRlcmF0aW9uLiAKCmBgYHtyIGNhbGN1bGF0ZSBzdGFiaWxpdHl9CnN0YWJpbGl0eV9saXN0IDwtIGNsdXN0ZXJfcmVzdWx0c19saXN0IHw+CiAgcHVycnI6Om1hcCgKICAgIFwoY2x1c3Rlcl9kZikgewogICAgICAKICAgICAgbm4gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkbm4pCgogICAgICAjIGNhbGN1bGF0ZSBzdGFiaWxpdHksIHBhc3NpbmcgaW4gdGhlIHBhcmFtZXRlciB2YWx1ZSB1c2VkIGZvciB0aGlzIGl0ZXJhdGlvbgogICAgICByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KHBjYV9tYXRyaXgsIGNsdXN0ZXJfZGYsIG5uID0gbm4pCiAgICB9CiAgKQpgYGAKCldlJ2xsIGFnYWluIGNvbWJpbmUgdGhlIG91dHB1dCBvZiBgc3RhYmlsaXR5X2xpc3RgIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZSBhbmQgcGxvdCBgYXJpYCB2YWx1ZXMgYWNyb3NzIGBubmAgcGFyYW1ldGVyaXphdGlvbnMuCgoKYGBge3IgY29tYmluZSBwbG90IHN0YWJpbGl0eX0Kc3RhYmlsaXR5X2RmIDwtIGRwbHlyOjpiaW5kX3Jvd3Moc3RhYmlsaXR5X2xpc3QpCgpnZ3Bsb3Qoc3RhYmlsaXR5X2RmKSArIAogIGFlcyh4ID0gYXMuZmFjdG9yKG5uKSwgeSA9IGFyaSwgZmlsbCA9IGFzLmZhY3RvcihubikpICsgCiAgZ2VvbV9ib3hwbG90KCkgKyAKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhc3RlbDIiKSArCiAgbGFicygKICAgIHggPSAiTnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzIiwgCiAgICB5ID0gIkFkanVzdGVkIFJhbmQgSW5kZXgiCiAgKSArIAogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpIZXJlLCB3ZSBzZWUgdGhhdCBhIG5lYXJlc3QgbmVpZ2hib3JzIHZhbHVlIG9mIDIwIG9yIDMwIGxlYWRzIHRvIG1vcmUgc3RhYmxlIGNsdXN0ZXJpbmcgcmVzdWx0cyBjb21wYXJlZCB0byAxMC4gCgoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9uIGluZm99CiMgcmVjb3JkIHRoZSB2ZXJzaW9ucyBvZiB0aGUgcGFja2FnZXMgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGFuZCBvdGhlciBlbnZpcm9ubWVudCBpbmZvcm1hdGlvbgpzZXNzaW9uSW5mbygpCmBgYAo=
+
LS0tCnRpdGxlOiAiQ29tcGFyaW5nIGNsdXN0ZXJpbmcgcGFyYW1ldGVycyB3aXRoIHJPcGVuU2NQQ0EiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKYXV0aG9yOiAiRGF0YSBMYWIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojIyBJbnRyb2R1Y3Rpb24KCkNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBoYXZlIHNldmVyYWwgcGFyYW1ldGVycyB3aGljaCBjYW4gYmUgdmFyaWVkLCBsZWFkaW5nIHRvIGRpZmZlcmVudCBjbHVzdGVyaW5nIHJlc3VsdHMuCkEga2V5IHF1ZXN0aW9uIHdoZW4gY2x1c3RlcmluZywgdGhlcmVmb3JlLCBpcyBob3cgdG8gaWRlbnRpZnkgYSBzZXQgb2YgcGFyYW1ldGVycyB0aGF0IGxlYWQgdG8gcm9idXN0IGFuZCByZWxpYWJsZSBjbHVzdGVycyB0aGF0IGNhbiBiZSB1c2VkIGluIGRvd25zdHJlYW0gYW5hbHlzaXMuIAoKVGhpcyBub3RlYm9vayBwcm92aWRlcyBleGFtcGxlcyBvZiBob3cgdG8gdXNlIHRoZSBgck9wZW5TY1BDQWAgcGFja2FnZSB0bzoKCiogQ2FsY3VsYXRlIHNldmVyYWwgdmVyc2lvbnMgb2YgY2x1c3RlcmluZyByZXN1bHRzIGFjcm9zcyBzZXZlcmFsIGRpZmZlcmVudCBwYXJhbWV0ZXJpemF0aW9ucwoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGFjcm9zcyBjbHVzdGVyaW5nIHJlc3VsdHMKClBsZWFzZSByZWZlciB0byB0aGUgW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCkgbm90ZWJvb2sgZm9yIGEgdHV0b3JpYWwgb24gdXNpbmcgYHJPcGVuU2NQQ0FgIGZ1bmN0aW9ucyB0bzoKCiogQ2FsY3VsYXRlIGNsdXN0ZXJzIGZyb20gYSBzaW5nbGUgcGFyYW1ldGVyaXphdGlvbgoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGEgc2luZ2xlIHNldCBvZiBjbHVzdGVycywgYXMgd2VsbCBhcyBleHBsYW5hdGlvbnMgb2YgdGhlIG1ldHJpY3MgdGhlbXNlbHZlcwoKVGhpcyBub3RlYm9vayB3aWxsIHVzZSB0aGUgc2FtcGxlIGBTQ1BDUzAwMDAwMWAgZnJvbSBwcm9qZWN0IGBTQ1BDUDAwMDAwMWAsIHdoaWNoIGlzIGFzc3VtZWQgcHJlc2VudCBpbiB0aGUgYE9wZW5TY1BDQS1hbmFseXNpcy9kYXRhL2N1cnJlbnQvU0NQQ1AwMDAwMDFgIGRpcmVjdG9yeSwgZm9yIGFsbCBleGFtcGxlcy4KUGxlYXNlIFtzZWUgdGhpcyBkb2N1bWVudGF0aW9uXShodHRwczovL29wZW5zY3BjYS5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvZ2V0dGluZy1zdGFydGVkL2FjY2Vzc2luZy1yZXNvdXJjZXMvZ2V0dGluZy1hY2Nlc3MtdG8tZGF0YS8pIGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IG9idGFpbmluZyBTY1BDQSBkYXRhLgoKIyMgU2V0dXAKCiMjIyBQYWNrYWdlcwoKCmBgYHtyIHBhY2thZ2VzfQpsaWJyYXJ5KHJPcGVuU2NQQ0EpCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQp9KQoKIyBTZXQgZ2dwbG90IHRoZW1lIGZvciBwbG90cwp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgoKIyMjIFBhdGhzCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19naXRfcm9vdCkKCiMgVGhlIGN1cnJlbnQgZGF0YSBkaXJlY3RvcnksIGZvdW5kIHdpdGhpbiB0aGUgcmVwb3NpdG9yeSBiYXNlIGRpcmVjdG9yeQpkYXRhX2RpciA8LSBmaWxlLnBhdGgocmVwb3NpdG9yeV9iYXNlLCAiZGF0YSIsICJjdXJyZW50IikKCiMgVGhlIHBhdGggdG8gdGhpcyBtb2R1bGUKbW9kdWxlX2Jhc2UgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImFuYWx5c2VzIiwgImhlbGxvLWNsdXN0ZXJzIikKYGBgCgpgYGB7ciBpbnB1dCBmaWxlIHBhdGh9CiMgUGF0aCB0byBwcm9jZXNzZWQgU0NFIGZpbGUgZm9yIHNhbXBsZSBTQ1BDUzAwMDAwMQppbnB1dF9zY2VfZmlsZSA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJTQ1BDUDAwMDAwMSIsICJTQ1BDUzAwMDAwMSIsICJTQ1BDTDAwMDAwMV9wcm9jZXNzZWQucmRzIikKYGBgCgoKIyMjIFNldCB0aGUgcmFuZG9tIHNlZWQKCkJlY2F1c2UgY2x1c3RlcmluZyBpbnZvbHZlcyByYW5kb20gc2FtcGxpbmcsIGl0IGlzIGltcG9ydGFudCB0byBzZXQgdGhlIHJhbmRvbSBzZWVkIGF0IHRoZSB0b3Agb2YgeW91ciBhbmFseXNpcyBzY3JpcHQgb3Igbm90ZWJvb2sgdG8gZW5zdXJlIHJlcHJvZHVjaWJpbGl0eS4KCmBgYHtyIHNldCBzZWVkfQpzZXQuc2VlZCgyMDI0KQpgYGAKCiMjIFJlYWQgaW4gYW5kIHByZXBhcmUgZGF0YQoKVG8gYmVnaW4sIHdlJ2xsIHJlYWQgaW4gdGhlIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgKFNDRSkgb2JqZWN0LgoKYGBge3IgcmVhZCBkYXRhfQojIFJlYWQgdGhlIFNDRSBmaWxlCnNjZSA8LSByZWFkUkRTKGlucHV0X3NjZV9maWxlKQpgYGAKCkZvciB0aGUgaW5pdGlhbCBjbHVzdGVyIGNhbGN1bGF0aW9ucyBhbmQgZXZhbHVhdGlvbnMsIHdlIHdpbGwgdXNlIHRoZSBQQ0EgbWF0cml4IGV4dHJhY3RlZCBmcm9tIHRoZSBTQ0Ugb2JqZWN0LgpBcyBzaG93biBpbiBbYDAxX3BlcmZvcm0tZXZhbHVhdGUtY2x1c3RlcmluZy5SbWRgXSgwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kKSwgaXQgaXMgYWxzbyBwb3NzaWJsZSB0byB1c2UgYW4gU0NFIG9iamVjdCBvciBhIFNldXJhdCBvYmplY3QgZGlyZWN0bHkuCgoKYGBge3IgZXh0cmFjdCBwY2EgZGF0YX0KIyBFeHRyYWN0IHRoZSBQQ0EgbWF0cml4IGZyb20gYW4gU0NFIG9iamVjdApwY2FfbWF0cml4IDwtIHJlZHVjZWREaW0oc2NlLCAiUENBIikKYGBgCgojIyBWYXJ5aW5nIGEgc2luZ2xlIGNsdXN0ZXJpbmcgcGFyYW1ldGVyCgpUaGlzIHNlY3Rpb24gd2lsbCBzaG93IGhvdyB0byBwZXJmb3JtIGNsdXN0ZXJpbmcgYWNyb3NzIGEgc2V0IG9mIHBhcmFtZXRlcnMgKGFrYSwgInN3ZWVwIiBhIHNldCBvZiBwYXJhbWV0ZXJzKSB3aXRoIGByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygpYC4gCgpUaGlzIGZ1bmN0aW9uIHRha2VzIGEgUENBIG1hdHJpeCB3aXRoIHJvdyBuYW1lcyByZXByZXNlbnRpbmcgdW5pcXVlIGNlbGwgaWRzIChlLmcuLCBiYXJjb2RlcykgYXMgaXRzIHByaW1hcnkgYXJndW1lbnQsIHdpdGggYWRkaXRpb25hbCBhcmd1bWVudHMgZm9yIGNsdXN0ZXIgcGFyYW1ldGVycy4gClRoaXMgZnVuY3Rpb24gd3JhcHMgdGhlIGByT3BlblNjUENBOjpjYWxjdWxhdGVfY2x1c3RlcnMoKWAgZnVuY3Rpb24gYnV0IGFsbG93cyB5b3UgdG8gcHJvdmlkZSBhIHZlY3RvciBvZiBwYXJhbWV0ZXIgdmFsdWVzIHRvIHBlcmZvcm0gY2x1c3RlcmluZyBhY3Jvc3MsIGFzIGxpc3RlZCBiZWxvdy4KQ2x1c3RlcnMgd2lsbCBiZSBjYWxjdWxhdGVkIGZvciBhbGwgY29tYmluYXRpb25zIG9mIHBhcmFtZXRlcnMgdmFsdWVzICh3aGVyZSBhcHBsaWNhYmxlKTsgZGVmYXVsdCB2YWx1ZXMgdGhhdCB0aGUgZnVuY3Rpb24gd2lsbCB1c2UgZm9yIGFueSB1bnNwZWNpZmllZCBwYXJhbWV0ZXIgdmFsdWVzIGFyZSBzaG93biBpbiBwYXJlbnRoZXNlcwoKKiBgYWxnb3JpdGhtYDogV2hpY2ggY2x1c3RlcmluZyBhbGdvcml0aG0gdG8gdXNlIChMb3V2YWluKQoqIGB3ZWlnaHRpbmdgOiBXaGljaCB3ZWlnaHRpbmcgc2NoZW1lIHRvIHVzZSAoSmFjY2FyZCkKKiBgbm5gOiBUaGUgbnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzICgxMCkKKiBgcmVzb2x1dGlvbmA6IFRoZSByZXNvbHV0aW9uIHBhcmFtZXRlciAoMTsgdXNlZCBvbmx5IHdpdGggTG91dmFpbiBhbmQgTGVpZGVuIGNsdXN0ZXJpbmcpCiogYG9iamVjdGl2ZV9mdW5jdGlvbmA6IFRoZSBvYmplY3RpdmUgZnVuY3Rpb24gdG8gb3B0aW1pemUgY2x1c3RlcnMgKENQTTsgdXNlZCBvbmx5IHdpdGggTGVpZGVuIGNsdXN0ZXJpbmcpCgpgck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoKWAgZG9lcyBhbGxvdyB5b3UgdG8gc3BlY2lmeSB2YWx1ZXMgZm9yIGFueSBvdGhlciBwYXJhbWV0ZXJzLiAKCgpUaGlzIGZ1bmN0aW9uIHdpbGwgcmV0dXJuIGEgbGlzdCBvZiBkYXRhIGZyYW1lcyBvZiBjbHVzdGVyaW5nIHJlc3VsdHMuIApFYWNoIGRhdGEgZnJhbWUgd2lsbCBoYXZlIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiogYGNlbGxfaWRgOiBVbmlxdWUgY2VsbCBpZGVudGlmaWVycywgb2J0YWluZWQgZnJvbSB0aGUgUENBIG1hdHJpeCdzIHJvdyBuYW1lcwoqIGBjbHVzdGVyYDogQSBmYWN0b3IgY29sdW1uIHdpdGggdGhlIGNsdXN0ZXIgaWRlbnRpdGllcwoqIFRoZXJlIHdpbGwgYmUgb25lIGNvbHVtbiBmb3IgZWFjaCBjbHVzdGVyaW5nIHBhcmFtZXRlciB1c2VkCgpUbyBkZW1vbnN0cmF0ZSB0aGlzIGZ1bmN0aW9uLCB3ZSdsbCBjYWxjdWxhdGUgY2x1c3RlcnMgdXNpbmcgdGhlIExvdXZhaW4gYWxnb3JpdGhtIHdoaWxlIHZhcnlpbmcgdGhlIGBubmAgcGFyYW1ldGVyOgoKYGBge3Igc3dlZXAgY2x1c3RlcnN9CiMgRGVmaW5lIG5uIHBhcmFtZXRlciB2YWx1ZXMgb2YgaW50ZXJlc3QKbm5fdmFsdWVzIDwtIHNlcSgxMCwgMzAsIDEwKQoKIyBDYWxjdWxhdGUgY2x1c3RlcnMgdmFyeWluZyBubiwgYnV0IGxlYXZpbmcgb3RoZXIgcGFyYW1ldGVycyBhdCB0aGVpciBkZWZhdWx0IHZhbHVlcwpjbHVzdGVyX3Jlc3VsdHNfbGlzdCA8LSByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygKICBwY2FfbWF0cml4LAogIG5uID0gbm5fdmFsdWVzCikKYGBgCgpUaGUgcmVzdWx0aW5nIGxpc3QgaGFzIGEgbGVuZ3RoIG9mIHRocmVlLCBvbmUgZGF0YSBmcmFtZSBmb3IgZWFjaCBgbm5gIHBhcmFtZXRlciB0ZXN0ZWQ6CgpgYGB7ciBsZW5ndGggY2x1c3Rlcl9yZXN1bHRzX2xpc3R9Cmxlbmd0aChjbHVzdGVyX3Jlc3VsdHNfbGlzdCkKYGBgCgpJdCBjYW4gYmUgaGVscGZ1bCAoYWx0aG91Z2ggaXQgaXMgbm90IHN0cmljdGx5IG5lY2Vzc2FyeSB0byBrZWVwIHRyYWNrKSB0byBuYW1lIHRoaXMgbGlzdCBieSB0aGUgdmFyaWVkIGBubmAgcGFyYW1ldGVyOgoKYGBge3Igc2V0IGxpc3QgbmFtZXN9Cm5hbWVzKGNsdXN0ZXJfcmVzdWx0c19saXN0KSA8LSBnbHVlOjpnbHVlKCJubl97bm5fdmFsdWVzfSIpCmBgYAoKCldlIGNhbiBsb29rIGF0IHRoZSBmaXJzdCBmZXcgcm93cyBvZiBlYWNoIGRhdGEgZnJhbWUgdXNpbmcgW2BwdXJycjo6bWFwKClgXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL21hcC5odG1sKSB0byBpdGVyYXRlIG92ZXIgdGhlIGxpc3Q6CgoKYGBge3IgbWFwIGNsdXN0ZXJfcmVzdWx0c19saXN0fQpjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoaGVhZCkKYGBgCgpHZW5lcmFsbHkgc3BlYWtpbmcsIGBwdXJycjo6bWFwKClgIGNhbiBiZSB1c2VkIHRvIGl0ZXJhdGUgb3ZlciB0aGlzIGxpc3QgdG8gdmlzdWFsaXplIG9yIGFuYWx5emUgZWFjaCBjbHVzdGVyaW5nIHJlc3VsdCBvbiBpdHMgb3duOyB3ZSdsbCB1c2UgdGhpcyBhcHByb2FjaCBpbiB0aGUgZm9sbG93aW5nIHNlY3Rpb25zLiAKCiMjIyBWaXN1YWxpemluZyBjbHVzdGVyaW5nIHJlc3VsdHMKCldoZW4gY29tcGFyaW5nIGNsdXN0ZXJpbmcgcmVzdWx0cywgaXQncyBpbXBvcnRhbnQgdG8gZmlyc3QgdmlzdWFsaXplIHRoZSBkaWZmZXJlbnQgY2x1c3RlcmluZ3MgdG8gYnVpbGQgY29udGV4dCBmb3IgaW50ZXJwcmV0aW5nIFFDIG1ldHJpY3MuCgpBcyBvbmUgZXhhbXBsZSBvZiB3aHkgdGhpcyBpcyBpbXBvcnRhbnQsIHdlIGdlbmVyYWxseSBleHBlY3QgdGhhdCBtb3JlIHJvYnVzdCBjbHVzdGVycyB3aWxsIGhhdmUgaGlnaGVyIHZhbHVlcyBmb3IgbWV0cmljcyBsaWtlIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkuCkhvd2V2ZXIsIHdlIGFsc28gZXhwZWN0IHRoYXQgaGF2aW5nIGZld2VyIGNsdXN0ZXJzIGluIHRoZSBmaXJzdCBwbGFjZSB3aWxsIGFsc28gbGVhZCB0byBoaWdoZXIgbWV0cmljcywgcmVnYXJkbGVzcyBvZiBjbHVzdGVyIHF1YWxpdHk6IFdoZW4gdGhlcmUgYXJlIGZld2VyIGNsdXN0ZXJzLCBpdCBpcyBtb3JlIGxpa2VseSB0aGF0IGNsdXN0ZXJzIG92ZXJsYXAgbGVzcyB3aXRoIG9uZSBhbm90aGVyIGp1c3QgYmVjYXVzZSB0aGVyZSBhcmVuJ3QgbWFueSBjbHVzdGVycyBpbiB0aGUgZmlyc3QgcGxhY2UuClRoaXMgbWVhbnMgdGhhdCwgd2hlbiBpbnRlcnByZXRpbmcgY2x1c3RlciBxdWFsaXR5IG1ldHJpY3MsIHlvdSBzaG91bGQgYmUgY2FyZWZ1bCB0byB0YWtlIG1vcmUgY29udGV4dCBhYm91dCB0aGUgZGF0YSBpbnRvIGNvbnNpZGVyYXRpb24gYW5kIG5vdCBvbmx5IHJlbHkgb24gdGhlIG1ldHJpYyB2YWx1ZXMuCgpXZSdsbCB0aGVyZWZvcmUgdmlzdWFsaXplIHRoZXNlIHJlc3VsdHMgYXMgVU1BUHMgYnkgaXRlcmF0aW5nIG92ZXIgYGNsdXN0ZXJfcmVzdWx0c19saXN0YCBhbmQgY29tYmluaW5nIHBsb3RzIHdpdGggW2BwYXRjaHdvcms6OndyYXBfcGxvdHMoKWBdKGh0dHBzOi8vcGF0Y2h3b3JrLmRhdGEtaW1hZ2luaXN0LmNvbS9yZWZlcmVuY2Uvd3JhcF9wbG90cy5odG1sKS4KV2UnbGwgc3BlY2lmaWNhbGx5IHVzZSBbYHB1cnJyOjppbWFwKClgXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2ltYXAuaHRtbCkgdG8gaXRlcmF0ZSBzbyB0aGF0IHdlIGNhbiBhc3NpZ24gdGhpcyBsaXN0J3MgbmFtZXMgYXMgcGxvdCB0aXRsZXMuCgpGb3IgdGhpcywgd2UnbGwgYmVnaW4gYnkgZXh0cmFjdGluZyBhIHRhYmxlIG9mIFVNQVAgY29vcmRpbmF0ZXMgZnJvbSBvdXIgU0NFIG9iamVjdC4KCmBgYHtyIGNyZWF0ZSB1bWFwX2RmfQp1bWFwX2RmIDwtIHJlZHVjZWREaW0oc2NlLCAiVU1BUCIpIHw+CiAgYXMuZGF0YS5mcmFtZSgpCmBgYAoKTmV4dCwgd2UnbGwgaXRlcmF0ZSBvdmVyIGBjbHVzdGVyX3Jlc3VsdHNfbGlzdGAgdG8gcGxvdCB0aGUgVU1BUHMuCgpgYGB7ciBwbG90IG5uIHVtYXBzLCBmaWcud2lkdGggPSAxMn0KdW1hcF9wbG90cyA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjppbWFwKAogICAgXChjbHVzdGVyX2RmLCBjbHVzdGVyaW5nX25hbWUpIHsKICAgICAgIyBBZGQgYSBjb2x1bW4gd2l0aCBjbHVzdGVyIGFzc2lnbm1lbnRzIHRvIHVtYXBfZGYKICAgICAgdW1hcF9kZl9wbG90IDwtIHVtYXBfZGYgfD4KICAgICAgICBkcGx5cjo6bXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyX2RmJGNsdXN0ZXIpCgogICAgICAjIFBsb3QgdGhlIFVNQVAsIGNvbG9yZWQgYnkgdGhlIG5ldyBjbHVzdGVyIHZhcmlhYmxlCiAgICAgIGdncGxvdCh1bWFwX2RmX3Bsb3QsIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjbHVzdGVyKSkgKwogICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsKICAgICAgICBsYWJzKHRpdGxlID0gZ2x1ZTo6Z2x1ZSgibmVhcmVzdCBuZWlnaGJvcnM6IHtjbHVzdGVyaW5nX25hbWV9IikpICsKICAgICAgICAjIFdlJ2xsIGFkZCBhIGNvdXBsZSBVTUFQIHBsb3Qgc2V0dGluZ3MgaGVyZSwgaW5jbHVkaW5nIGVxdWFsIGF4ZXMgYW5kCiAgICAgICAgIyB0dXJuaW5nIG9mZiB0aGUgYXhpcyB0aWNrcyBhbmQgdGV4dCBzaW5jZSBVTUFQIGNvb3JkaW5hdGVzIGFyZSBub3QgbWVhbmluZ2Z1bAogICAgICAgIGNvb3JkX2VxdWFsKCkgKwogICAgICAgIHRoZW1lKAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKQogICAgICAgICkKICAgIH0KICApCgojIFByaW50IHRoZSBwbG90cyB3aXRoIHBhdGNod29yazo6d3JhcF9wbG90cygpCnBhdGNod29yazo6d3JhcF9wbG90cyh1bWFwX3Bsb3RzKQpgYGAKClRoZXNlIHBsb3RzIHNob3cgdGhhdCB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIGRlY3JlYXNlcyBhcyB0aGUgbmVhcmVzdCBuZWlnaGJvcnMgcGFyYW1ldGVyIGluY3JlYXNlcywgd2l0aCBiZXR3ZWVuIDktMTMgY2x1c3RlcnMuCgoKCgojIyMgRXZhbHVhdGluZyBjbHVzdGVyaW5nIHJlc3VsdHMKClRoaXMgc2VjdGlvbiB3aWxsIHVzZSBgcHVycnI6Om1hcCgpYCB0byBpdGVyYXRlIG92ZXIgZWFjaCBjbHVzdGVyaW5nIHJlc3VsdCBkYXRhIGZyYW1lIHRvIGNhbGN1bGF0ZSBzaWxob3VldHRlIHdpZHRoLCBuZWlnaGJvcmhvb2QgcHVyaXR5LCBhbmQgc3RhYmlsaXR5LCBhbmQgdGhlbiB2aXN1YWxpemUgcmVzdWx0cy4KVGhlIGdvYWwgb2YgdGhpcyBjb2RlIGlzIHRvIGlkZW50aWZ5IHdoZXRoZXIgb25lIGNsdXN0ZXJpbmcgcGFyYW1ldGVyaXphdGlvbiBwcm9kdWNlcyBtb3JlIHJlbGlhYmxlIGNsdXN0ZXJzLiAKCgojIyMjIFNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkKCkJvdGggc2lsaG91ZXR0ZSB3aWR0aCBhbmQgbmVpZ2hib3Job29kIHB1cml0eSBhcmUgY2VsbC1sZXZlbCBxdWFudGl0aWVzLCBzbyB3ZSBjYW4gY2FsY3VsYXRlIHRoZW0gdG9nZXRoZXIgaW4gdGhlIHNhbWUgY2FsbCB0byBgcHVycnI6Om1hcCgpYC4KQmVsb3csIHdlJ2xsIGl0ZXJhdGUgb3ZlciBlYWNoIGRhdGEgZnJhbWUgaW4gYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBjYWxjdWxhdGUgdGhlc2UgcXVhbnRpdGllcy4KCmBgYHtyIGNhbGN1bGF0ZSBjZWxsIGxldmVsIG1ldHJpY3N9CmNlbGxfbWV0cmljX2xpc3QgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgICMgY2FsY3VsYXRlIHNpbGhvdWV0dGUgd2lkdGgKICAgICAgc2lsaG91ZXR0ZV9kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfc2lsaG91ZXR0ZShwY2FfbWF0cml4LCBjbHVzdGVyX2RmKQoKICAgICAgIyBjYWxjdWxhdGUgbmVpZ2hiaG9yaG9vZCBwdXJpdHkKICAgICAgcHVyaXR5X2RmIDwtIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9wdXJpdHkocGNhX21hdHJpeCwgY2x1c3Rlcl9kZikKCiAgICAgICMgQ29tYmluZSBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUKICAgICAgZHBseXI6OmxlZnRfam9pbihzaWxob3VldHRlX2RmLCBwdXJpdHlfZGYpCiAgICB9CiAgKQoKIyBWaWV3IHRoZSBmaXJzdCBzaXggcm93cyBvZiBlYWNoIGNsdXN0ZXJpbmcgcmVzdWx0J3MgY2VsbC1sZXZlbCBRQyBtZXRyaWNzCnB1cnJyOjptYXAoY2VsbF9tZXRyaWNfbGlzdCwgaGVhZCkKYGBgCgoKVG8gdmlzdWFsaXplIHRoZXNlIHJlc3VsdHMsIHdlIGNhbiBjb21iaW5lIGFsbCBkYXRhIGZyYW1lcyBpbiB0aGlzIGxpc3QgaW50byBhIHNpbmdsZSBvdmVyYWxsIGRhdGEgZnJhbWUsIHdoZXJlIHRoZSBleGlzdGluZyBgbm5gIGNvbHVtbiB3aWxsIGRpc3Rpbmd1aXNoIGFtb25nIGNvbmRpdGlvbnMuCgpgYGB7ciBjb21iaW5lIGNlbGwgbWV0cmljcyBsaXN0fQpjZWxsX21ldHJpY3NfZGYgPC0gZHBseXI6OmJpbmRfcm93cyhjZWxsX21ldHJpY19saXN0KQpgYGAKCldlIGNhbiB2aXN1YWxpemUgc2lsaG91ZXR0ZSB3aWR0aCBhbmQgbmVpZ2hib3Job29kIHB1cml0eSBlYWNoIHdpdGggYm94cGxvdHMsIGZvciBleGFtcGxlLCBhbmQgdXNlIHRoZSBbYHBhdGNod29ya2BdKGh0dHBzOi8vcGF0Y2h3b3JrLmRhdGEtaW1hZ2luaXN0LmNvbS8pIHBhY2thZ2UgdG8gcHJpbnQgdGhlbSB0b2dldGhlcjoKCgpgYGB7cn0KIyBQbG90IHNpbGhvdWV0dGUgd2lkdGgKc2lsaG91ZXR0ZV9wbG90IDwtIGdncGxvdChjZWxsX21ldHJpY3NfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBzaWxob3VldHRlX3dpZHRoLCBmaWxsID0gYXMuZmFjdG9yKG5uKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhc3RlbDIiKSArCiAgbGFicygKICAgIHggPSAiTnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzIiwKICAgIHkgPSAiU2lsaG91ZXR0ZSB3aWR0aCIKICApCgoKIyBQbG90IG5laWdoYm9yaG9vZCBwdXJpdHkgd2lkdGgKcHVyaXR5X3Bsb3QgPC0gZ2dwbG90KGNlbGxfbWV0cmljc19kZikgKwogIGFlcyh4ID0gYXMuZmFjdG9yKG5uKSwgeSA9IHB1cml0eSwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIk5laWdoYm9yaG9vZCBwdXJpdHkiCiAgKQoKCiMgQWRkIHRvZ2V0aGVyIHVzaW5nIHRoZSBwYXRjaHdvcmsgbGlicmFyeSwgd2l0aG91dCBhIGxlZ2VuZApzaWxob3VldHRlX3Bsb3QgKyBwdXJpdHlfcGxvdCAmIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpXaGlsZSB0aGVyZSBkb2VzIG5vdCBhcHBlYXIgdG8gYmUgYSBzYWxpZW50IGRpZmZlcmVuY2UgYW1vbmcgc2lsaG91ZXR0ZSB3aWR0aCBkaXN0cmlidXRpb25zLCBpdCBkb2VzIGFwcGVhciB0aGF0IHB1cml0eSBpcyBoaWdoZXIgd2l0aCBhIGhpZ2hlciBuZWFyZXN0IG5laWdoYm9ycyBwYXJhbWV0ZXIuCgoKIyMjIyBTdGFiaWxpdHkKCk5leHQsIHdlJ2xsIGNhbGN1bGF0ZSBzdGFiaWxpdHkgb24gdGhlIGNsdXN0ZXJzIHVzaW5nIGByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KClgLCBzcGVjaWZ5aW5nIHRoZSBzYW1lIHBhcmFtZXRlciB1c2VkIGZvciB0aGUgb3JpZ2luYWwgY2x1c3RlciBjYWxjdWxhdGlvbiBhdCBlYWNoIGl0ZXJhdGlvbi4gCgpgYGB7ciBjYWxjdWxhdGUgc3RhYmlsaXR5fQpzdGFiaWxpdHlfbGlzdCA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgbm4gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkbm4pCgogICAgICAjIGNhbGN1bGF0ZSBzdGFiaWxpdHksIHBhc3NpbmcgaW4gdGhlIHBhcmFtZXRlciB2YWx1ZSB1c2VkIGZvciB0aGlzIGl0ZXJhdGlvbgogICAgICByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KHBjYV9tYXRyaXgsIGNsdXN0ZXJfZGYsIG5uID0gbm4pCiAgICB9CiAgKQpgYGAKCldlJ2xsIGFnYWluIGNvbWJpbmUgdGhlIG91dHB1dCBvZiBgc3RhYmlsaXR5X2xpc3RgIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZSBhbmQgcGxvdCBgYXJpYCB2YWx1ZXMgYWNyb3NzIGBubmAgcGFyYW1ldGVyaXphdGlvbnMuCgoKYGBge3IgY29tYmluZSBwbG90IHN0YWJpbGl0eX0Kc3RhYmlsaXR5X2RmIDwtIGRwbHlyOjpiaW5kX3Jvd3Moc3RhYmlsaXR5X2xpc3QpCgpnZ3Bsb3Qoc3RhYmlsaXR5X2RmKSArCiAgYWVzKHggPSBhcy5mYWN0b3Iobm4pLCB5ID0gYXJpLCBmaWxsID0gYXMuZmFjdG9yKG5uKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhc3RlbDIiKSArCiAgbGFicygKICAgIHggPSAiTnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzIiwKICAgIHkgPSAiQWRqdXN0ZWQgUmFuZCBJbmRleCIKICApICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKSGVyZSwgd2Ugc2VlIHRoYXQgYSBuZWFyZXN0IG5laWdoYm9ycyB2YWx1ZSBvZiAyMCBvciAzMCBsZWFkcyB0byBtb3JlIHN0YWJsZSBjbHVzdGVyaW5nIHJlc3VsdHMgY29tcGFyZWQgdG8gMTAuIAoKCiMjIFZhcnlpbmcgbXVsdGlwbGUgY2x1c3RlcmluZyBwYXJhbWV0ZXJzCgpUaGUgcHJldmlvdXMgc2VjdGlvbiBkZW1vbnN0cmF0ZWQgaG93IHRvIGNhbGN1bGF0ZSBjbHVzdGVycyBhbmQgUUMgbWV0cmljcyB3aGVuIHZhcnlpbmcgb25lIHBhcmFtZXRlciwgYnV0IGl0IGlzIHBvc3NpYmxlIHRvIHZhcnkgbXVsdGlwbGUgcGFyYW1ldGVycyBhdCBvbmNlIHdpdGggYHJPcGVuU2NQQ0E6OnN3ZWVwX2NsdXN0ZXJzKClgLgpJbiB0aGlzIHNlY3Rpb24sIHdlJ2xsIHNob3cgYW4gb3ZlcnZpZXcgb2YgaG93IHlvdSBtaWdodCB3cml0ZSBjb2RlIHRvIHZhcnkgdHdvIHBhcmFtZXRlcnMgYXQgb25jZSAoaGVyZSwgbmVhcmVzdCBuZWlnaGJvcnMgYW5kIHJlc29sdXRpb24gYXMgZXhhbXBsZXMpIGFuZCB2aXN1YWxpemUgcmVzdWx0cy4KCgpgYGB7ciBzd2VlcCB0d28gcGFyYW1ldGVyc30KIyBEZWZpbmUgdmVjdG9ycyBvZiBwYXJhbWV0ZXJzIHRvIHZhcnkKbm5fdmFsdWVzIDwtIHNlcSgxMCwgMzAsIDEwKQpyZXNfdmFsdWVzIDwtIHNlcSg1LCAxNSwgNSkvMTAKCgpjbHVzdGVyX3Jlc3VsdHNfbGlzdCA8LSByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygKICBwY2FfbWF0cml4LAogIG5uID0gbm5fdmFsdWVzLAogIHJlc29sdXRpb24gPSByZXNfdmFsdWVzCikKYGBgCgpUaGlzIHJlc3VsdGluZyBsaXN0IG5vdyBoYXMgOSBkaWZmZXJlbnQgY2x1c3RlcmluZyByZXN1bHRzLCBmb3IgZWFjaCBjb21iaW5hdGlvbiBvZiBgbm5fdmFsdWVzYCBhbmQgYHJlc192YWx1ZXNgOgoKCmBgYHtyIGxlbmd0aCBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB0d28gcGFyYW1ldGVyc30KbGVuZ3RoKGNsdXN0ZXJfcmVzdWx0c19saXN0KQpgYGAKCgojIyMgVmlzdWFsaXplIGNsdXN0ZXJzCgpOZXh0LCB3ZSdsbCBpdGVyYXRlIG92ZXIgYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBwbG90IHRoZSBVTUFQcy4KVGhpcyB0aW1lLCB3ZSdsbCB1c2UgYHB1cnJyOjptYXAoKWAgYW5kIHB1bGwgb3V0IHBhcmFtZXRlcnMgZnJvbSBlYWNoIGl0ZXJhdGlvbidzIGBjbHVzdGVyX2RmYCB0byBmb3JtIHRoZSBVTUFQIHBhbmVsIHRpdGxlLgoKYGBge3IgcGxvdCBubiByZXMgdW1hcHMsIGZpZy5oZWlnaHQgPSAxNH0KdW1hcF9wbG90cyA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKCiAgICAgICMgQWRkIGEgY29sdW1uIHdpdGggY2x1c3RlciBhc3NpZ25tZW50cyB0byB1bWFwX2RmCiAgICAgIHVtYXBfZGZfcGxvdCA8LSB1bWFwX2RmIHw+CiAgICAgICAgZHBseXI6Om11dGF0ZShjbHVzdGVyID0gY2x1c3Rlcl9kZiRjbHVzdGVyKQoKICAgICAgIyBDcmVhdGUgYSB0aXRsZSBmb3IgdGhlIFVNQVAgd2l0aCBib3RoIHBhcmFtZXRlcnMKICAgICAgdW1hcF90aXRsZSA8LSBnbHVlOjpnbHVlKAogICAgICAgICJubjoge3VuaXF1ZShjbHVzdGVyX2RmJG5uKX07IHJlczoge3VuaXF1ZShjbHVzdGVyX2RmJHJlc29sdXRpb24pfSIKICAgICAgKQoKICAgICAgIyBQbG90IHRoZSBVTUFQLCBjb2xvcmVkIGJ5IHRoZSBuZXcgY2x1c3RlciB2YXJpYWJsZQogICAgICBnZ3Bsb3QodW1hcF9kZl9wbG90LCBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gY2x1c3RlcikpICsKICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC42KSArCiAgICAgICAgbGFicyh0aXRsZSA9IHVtYXBfdGl0bGUpICsKICAgICAgICAjIFdlJ2xsIGFkZCBhIGNvdXBsZSBVTUFQLXNwZWNpZmljIHBsb3Qgc2V0dGluZ3MgCiAgICAgICAgY29vcmRfZXF1YWwoKSArCiAgICAgICAgdGhlbWUoCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIKICAgICAgICApCiAgfQopCgojIFByaW50IHRoZSBwbG90cyB3aXRoIHBhdGNod29yazo6d3JhcF9wbG90cygpCnBhdGNod29yazo6d3JhcF9wbG90cyh1bWFwX3Bsb3RzKQpgYGAKCgoKIyMjIENhbGN1bGF0ZSBhbmQgdmlzdWFsaXplIFFDIG1ldHJpY3MKClRoaXMgc2VjdGlvbiBwcmVzZW50cyBvbmUgY29kaW5nIHN0cmF0ZWd5IHRvIGNhbGN1bGF0ZSBhbmQgdmlzdWFsaXplIHJlc3VsdHMgd2hlbiB2YXJ5aW5nIHR3byBjbHVzdGVyaW5nIHBhcmFtZXRlcnMuCkluIHBhcnRpY3VsYXIsIHdlIHVzZSBmYWNldGluZyB0byBoZWxwIGRpc3BsYXkgYWxsIGluZm9ybWF0aW9uIGluIG9uZSBwbG90LCBieSBwbGFjaW5nIG5lYXJlc3QgbmVpZ2hib3IgdmFsdWVzIG9uIHRoZSBYLWF4aXMgYW5kIGZhY2V0aW5nIGJ5IHJlc29sdXRpb24gdmFsdWVzLgpTaW5jZSBzaWxob3VldHRlIHdpZHRoIGFuZCBuZWlnaGhvcmJvb2QgcHVyaXR5IGNhbGN1bGF0aW9ucyB1c2luZyBnZW5lcmFsbHkgc2ltaWxhciBjb2RlLCB3ZSdsbCBqdXN0IHNob3cgbmVpZ2hib3Job29kIHB1cml0eSBoZXJlLgoKIyMjIyBOZWlnaGJvcmhvb2QgcHVyaXR5CgpGaXJzdCwgd2UnbGwgY2FsY3VsYXRlIG5laWdoYm9yaG9vZCBwdXJpdHkgYW5kIGNvbWJpbmUgcmVzdWx0cyBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUuCgpgYGB7ciBjYWxjdWxhdGUgcHVyaXR5IHR3byBwYXJhbWV0ZXJzfQpwdXJpdHlfZGYgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9wdXJpdHkocGNhX21hdHJpeCwgY2x1c3Rlcl9kZikKICAgIH0KICApIHw+CiAgZHBseXI6OmJpbmRfcm93cygpCmBgYAoKCldlJ2xsIGFkZCBhIGNvbHVtbiBgcmVzb2x1dGlvbl9sYWJlbGAgd2hpY2ggd2UnbGwgdXNlIHRvIGhhdmUgaW5mb3JtYXRpdmUgcGFuZWwgdGl0bGVzIGluIHRoZSBmYWNldGVkIGdncGxvdCB3ZSBtYWtlIG5leHQuCgpgYGB7ciBhZGQgcmVzb2x1dGlvbl9sYWJlbCBjb2x1bW59CnB1cml0eV9kZiA8LSBwdXJpdHlfZGYgfD4KICBkcGx5cjo6bXV0YXRlKHJlc29sdXRpb25fbGFiZWwgPSBnbHVlOjpnbHVlKCJSZXNvbHV0aW9uOiB7cmVzb2x1dGlvbn0iKSkKYGBgCgpgYGB7ciB2aXN1YWxpemUgcHVyaXR5IHR3byBwYXJhbWV0ZXJzfQpnZ3Bsb3QocHVyaXR5X2RmKSArCiAgYWVzKHggPSBhcy5mYWN0b3Iobm4pLCB5ID0gcHVyaXR5LCBmaWxsID0gYXMuZmFjdG9yKG5uKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhc3RlbDIiKSArCiAgIyBmYWNldCBieSByZXNvbHV0aW9uCiAgZmFjZXRfd3JhcCh2YXJzKHJlc29sdXRpb25fbGFiZWwpKSArCiAgbGFicygKICAgIHggPSAiTnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzIiwKICAgIHkgPSAiTmVpZ2hib3Job29kIHB1cml0eSIKICApICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKIyMjIFN0YWJpbGl0eQoKU2ltaWxhciB0byBhYm92ZSwgd2UnbGwgY2FsY3VsYXRlIHN0YWJpbGl0eSwgY29tYmluZSByZXN1bHRzIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZSwgYWRkIGEgYHJlc29sdXRpb25fbGFiZWxgIGNvbHVtbiB0byBzdXBwb3J0IHBsb3QgaW50ZXJwcmV0YXRpb24sIGFuZCBmaW5hbGx5IG1ha2Ugb3VyIHBsb3QuCgpgYGB7ciBjYWxjdWxhdGUgc3RhYmlsaXR5IHR3byBwYXJhbWV0ZXJzfQpzdGFiaWxpdHlfZGYgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CgogICAgICAjIEV4dHJhY3QgcGFyYW1ldGVycyBmb3IgdGhpcyBjbHVzdGVyaW5nIHJlc3VsdAogICAgICBubiA8LSB1bmlxdWUoY2x1c3Rlcl9kZiRubikKICAgICAgcmVzb2x1dGlvbiA8LSB1bmlxdWUoY2x1c3Rlcl9kZiRyZXNvbHV0aW9uKQoKICAgICAgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgKICAgICAgICBwY2FfbWF0cml4LAogICAgICAgIGNsdXN0ZXJfZGYsCiAgICAgICAgbm4gPSBubiwKICAgICAgICByZXNvbHV0aW9uID0gcmVzb2x1dGlvbgogICAgICApCiAgICB9CiAgKSB8PgogIGRwbHlyOjpiaW5kX3Jvd3MoKQoKc3RhYmlsaXR5X2RmIDwtIHN0YWJpbGl0eV9kZiB8PgogIGRwbHlyOjptdXRhdGUocmVzb2x1dGlvbl9sYWJlbCA9IGdsdWU6OmdsdWUoIlJlc29sdXRpb246IHtyZXNvbHV0aW9ufSIpKQoKYGBgCgoKYGBge3IgdmlzdWFsaXplIHN0YWJpbGl0eSB0d28gcGFyYW1ldGVyc30KZ2dwbG90KHN0YWJpbGl0eV9kZikgKwogIGFlcyh4ID0gYXMuZmFjdG9yKG5uKSwgeSA9IGFyaSwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogICMgZmFjZXQgYnkgcmVzb2x1dGlvbgogIGZhY2V0X3dyYXAodmFycyhyZXNvbHV0aW9uX2xhYmVsKSkgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIkFkanVzdGVkIFJhbmQgSW5kZXgiCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9uIGluZm99CiMgcmVjb3JkIHRoZSB2ZXJzaW9ucyBvZiB0aGUgcGFja2FnZXMgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGFuZCBvdGhlciBlbnZpcm9ubWVudCBpbmZvcm1hdGlvbgpzZXNzaW9uSW5mbygpCmBgYAo=
From 04cf200ebe29252c1c467a4dddea52f4f3e18667 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Mon, 13 Jan 2025 10:52:45 -0500 Subject: [PATCH 05/16] add notebook to readme, and add missing :: and () --- analyses/hello-clusters/README.md | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) 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 From 9885599ff9128c7c3acb84f6be50bec2df7831df Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Mon, 13 Jan 2025 10:53:43 -0500 Subject: [PATCH 06/16] add 2nd notebook to run script --- analyses/hello-clusters/run_hello-clusters.sh | 3 +++ 1 file changed, 3 insertions(+) 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)" From c50d8df3db4885fd7eaf30d836c12039d220fc86 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Mon, 13 Jan 2025 11:04:58 -0500 Subject: [PATCH 07/16] we can use docker in the GHA now --- .github/workflows/run_hello-clusters.yml | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/.github/workflows/run_hello-clusters.yml b/.github/workflows/run_hello-clusters.yml index 100c4c07b..e6b5b19f1 100644 --- a/.github/workflows/run_hello-clusters.yml +++ b/.github/workflows/run_hello-clusters.yml @@ -33,29 +33,15 @@ 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 - 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 }} - - name: Download test data run: ./download-data.py --test-data --format SCE --samples SCPCS000001 From c641a3d70b839c78008b9e79262445872efd1dc7 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Mon, 13 Jan 2025 11:22:05 -0500 Subject: [PATCH 08/16] no conda so need to install aws standalone --- .github/workflows/run_hello-clusters.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/run_hello-clusters.yml b/.github/workflows/run_hello-clusters.yml index e6b5b19f1..cce1fa22b 100644 --- a/.github/workflows/run_hello-clusters.yml +++ b/.github/workflows/run_hello-clusters.yml @@ -42,6 +42,12 @@ jobs: - name: Checkout repo uses: actions/checkout@v4 + - name: Install aws-cli + run: | + 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 From 70a9413c4340d082e1812172e46976056727b243 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Mon, 13 Jan 2025 11:28:35 -0500 Subject: [PATCH 09/16] keep pandoc --- .github/workflows/run_hello-clusters.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/run_hello-clusters.yml b/.github/workflows/run_hello-clusters.yml index cce1fa22b..33289df4f 100644 --- a/.github/workflows/run_hello-clusters.yml +++ b/.github/workflows/run_hello-clusters.yml @@ -42,6 +42,9 @@ jobs: - name: Checkout repo uses: actions/checkout@v4 + - name: Set up pandoc + uses: r-lib/actions/setup-pandoc@v2 + - name: Install aws-cli run: | curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" From da002e4ebdccf55ca9277e5179516706e9f0c4f6 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Mon, 13 Jan 2025 11:38:48 -0500 Subject: [PATCH 10/16] swap in new rprojroot line so this runs in CI, and re-render notebooks --- .../01_perform-evaluate-clustering.Rmd | 2 +- .../01_perform-evaluate-clustering.nb.html | 53 ++++++++----------- .../02_compare-clustering-parameters.Rmd | 2 +- .../02_compare-clustering-parameters.nb.html | 22 ++++---- 4 files changed, 35 insertions(+), 44 deletions(-) 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 index 1133ce947..dab142325 100644 --- a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd +++ b/analyses/hello-clusters/02_compare-clustering-parameters.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/02_compare-clustering-parameters.nb.html b/analyses/hello-clusters/02_compare-clustering-parameters.nb.html index b29b98fde..212e116c5 100644 --- a/analyses/hello-clusters/02_compare-clustering-parameters.nb.html +++ b/analyses/hello-clusters/02_compare-clustering-parameters.nb.html @@ -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")
@@ -3408,10 +3408,10 @@ 

Varying multiple clustering parameters

examples) and visualize results.

- +
# Define vectors of parameters to vary
 nn_values <- seq(10, 30, 10)
-res_values <- seq(5, 15, 5)/10
+res_values <- seq(5, 15, 5) / 10
 
 
 cluster_results_list <- rOpenScPCA::sweep_clusters(
@@ -3442,11 +3442,10 @@ 

Visualize clusters

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)
@@ -3460,15 +3459,15 @@ 

Visualize clusters

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 + # We'll add a couple UMAP-specific plot settings coord_equal() + theme( axis.ticks = element_blank(), axis.text = element_blank(), legend.position = "bottom" ) - } -) + } + ) # Print the plots with patchwork::wrap_plots() patchwork::wrap_plots(umap_plots)
@@ -3543,11 +3542,10 @@

Stability

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)
@@ -3653,7 +3651,7 @@ 

Session Info

-
LS0tCnRpdGxlOiAiQ29tcGFyaW5nIGNsdXN0ZXJpbmcgcGFyYW1ldGVycyB3aXRoIHJPcGVuU2NQQ0EiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKYXV0aG9yOiAiRGF0YSBMYWIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojIyBJbnRyb2R1Y3Rpb24KCkNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBoYXZlIHNldmVyYWwgcGFyYW1ldGVycyB3aGljaCBjYW4gYmUgdmFyaWVkLCBsZWFkaW5nIHRvIGRpZmZlcmVudCBjbHVzdGVyaW5nIHJlc3VsdHMuCkEga2V5IHF1ZXN0aW9uIHdoZW4gY2x1c3RlcmluZywgdGhlcmVmb3JlLCBpcyBob3cgdG8gaWRlbnRpZnkgYSBzZXQgb2YgcGFyYW1ldGVycyB0aGF0IGxlYWQgdG8gcm9idXN0IGFuZCByZWxpYWJsZSBjbHVzdGVycyB0aGF0IGNhbiBiZSB1c2VkIGluIGRvd25zdHJlYW0gYW5hbHlzaXMuIAoKVGhpcyBub3RlYm9vayBwcm92aWRlcyBleGFtcGxlcyBvZiBob3cgdG8gdXNlIHRoZSBgck9wZW5TY1BDQWAgcGFja2FnZSB0bzoKCiogQ2FsY3VsYXRlIHNldmVyYWwgdmVyc2lvbnMgb2YgY2x1c3RlcmluZyByZXN1bHRzIGFjcm9zcyBzZXZlcmFsIGRpZmZlcmVudCBwYXJhbWV0ZXJpemF0aW9ucwoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGFjcm9zcyBjbHVzdGVyaW5nIHJlc3VsdHMKClBsZWFzZSByZWZlciB0byB0aGUgW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCkgbm90ZWJvb2sgZm9yIGEgdHV0b3JpYWwgb24gdXNpbmcgYHJPcGVuU2NQQ0FgIGZ1bmN0aW9ucyB0bzoKCiogQ2FsY3VsYXRlIGNsdXN0ZXJzIGZyb20gYSBzaW5nbGUgcGFyYW1ldGVyaXphdGlvbgoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGEgc2luZ2xlIHNldCBvZiBjbHVzdGVycywgYXMgd2VsbCBhcyBleHBsYW5hdGlvbnMgb2YgdGhlIG1ldHJpY3MgdGhlbXNlbHZlcwoKVGhpcyBub3RlYm9vayB3aWxsIHVzZSB0aGUgc2FtcGxlIGBTQ1BDUzAwMDAwMWAgZnJvbSBwcm9qZWN0IGBTQ1BDUDAwMDAwMWAsIHdoaWNoIGlzIGFzc3VtZWQgcHJlc2VudCBpbiB0aGUgYE9wZW5TY1BDQS1hbmFseXNpcy9kYXRhL2N1cnJlbnQvU0NQQ1AwMDAwMDFgIGRpcmVjdG9yeSwgZm9yIGFsbCBleGFtcGxlcy4KUGxlYXNlIFtzZWUgdGhpcyBkb2N1bWVudGF0aW9uXShodHRwczovL29wZW5zY3BjYS5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvZ2V0dGluZy1zdGFydGVkL2FjY2Vzc2luZy1yZXNvdXJjZXMvZ2V0dGluZy1hY2Nlc3MtdG8tZGF0YS8pIGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IG9idGFpbmluZyBTY1BDQSBkYXRhLgoKIyMgU2V0dXAKCiMjIyBQYWNrYWdlcwoKCmBgYHtyIHBhY2thZ2VzfQpsaWJyYXJ5KHJPcGVuU2NQQ0EpCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQp9KQoKIyBTZXQgZ2dwbG90IHRoZW1lIGZvciBwbG90cwp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgoKIyMjIFBhdGhzCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19naXRfcm9vdCkKCiMgVGhlIGN1cnJlbnQgZGF0YSBkaXJlY3RvcnksIGZvdW5kIHdpdGhpbiB0aGUgcmVwb3NpdG9yeSBiYXNlIGRpcmVjdG9yeQpkYXRhX2RpciA8LSBmaWxlLnBhdGgocmVwb3NpdG9yeV9iYXNlLCAiZGF0YSIsICJjdXJyZW50IikKCiMgVGhlIHBhdGggdG8gdGhpcyBtb2R1bGUKbW9kdWxlX2Jhc2UgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImFuYWx5c2VzIiwgImhlbGxvLWNsdXN0ZXJzIikKYGBgCgpgYGB7ciBpbnB1dCBmaWxlIHBhdGh9CiMgUGF0aCB0byBwcm9jZXNzZWQgU0NFIGZpbGUgZm9yIHNhbXBsZSBTQ1BDUzAwMDAwMQppbnB1dF9zY2VfZmlsZSA8LSBmaWxlLnBhdGgoZGF0YV9kaXIsICJTQ1BDUDAwMDAwMSIsICJTQ1BDUzAwMDAwMSIsICJTQ1BDTDAwMDAwMV9wcm9jZXNzZWQucmRzIikKYGBgCgoKIyMjIFNldCB0aGUgcmFuZG9tIHNlZWQKCkJlY2F1c2UgY2x1c3RlcmluZyBpbnZvbHZlcyByYW5kb20gc2FtcGxpbmcsIGl0IGlzIGltcG9ydGFudCB0byBzZXQgdGhlIHJhbmRvbSBzZWVkIGF0IHRoZSB0b3Agb2YgeW91ciBhbmFseXNpcyBzY3JpcHQgb3Igbm90ZWJvb2sgdG8gZW5zdXJlIHJlcHJvZHVjaWJpbGl0eS4KCmBgYHtyIHNldCBzZWVkfQpzZXQuc2VlZCgyMDI0KQpgYGAKCiMjIFJlYWQgaW4gYW5kIHByZXBhcmUgZGF0YQoKVG8gYmVnaW4sIHdlJ2xsIHJlYWQgaW4gdGhlIGBTaW5nbGVDZWxsRXhwZXJpbWVudGAgKFNDRSkgb2JqZWN0LgoKYGBge3IgcmVhZCBkYXRhfQojIFJlYWQgdGhlIFNDRSBmaWxlCnNjZSA8LSByZWFkUkRTKGlucHV0X3NjZV9maWxlKQpgYGAKCkZvciB0aGUgaW5pdGlhbCBjbHVzdGVyIGNhbGN1bGF0aW9ucyBhbmQgZXZhbHVhdGlvbnMsIHdlIHdpbGwgdXNlIHRoZSBQQ0EgbWF0cml4IGV4dHJhY3RlZCBmcm9tIHRoZSBTQ0Ugb2JqZWN0LgpBcyBzaG93biBpbiBbYDAxX3BlcmZvcm0tZXZhbHVhdGUtY2x1c3RlcmluZy5SbWRgXSgwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kKSwgaXQgaXMgYWxzbyBwb3NzaWJsZSB0byB1c2UgYW4gU0NFIG9iamVjdCBvciBhIFNldXJhdCBvYmplY3QgZGlyZWN0bHkuCgoKYGBge3IgZXh0cmFjdCBwY2EgZGF0YX0KIyBFeHRyYWN0IHRoZSBQQ0EgbWF0cml4IGZyb20gYW4gU0NFIG9iamVjdApwY2FfbWF0cml4IDwtIHJlZHVjZWREaW0oc2NlLCAiUENBIikKYGBgCgojIyBWYXJ5aW5nIGEgc2luZ2xlIGNsdXN0ZXJpbmcgcGFyYW1ldGVyCgpUaGlzIHNlY3Rpb24gd2lsbCBzaG93IGhvdyB0byBwZXJmb3JtIGNsdXN0ZXJpbmcgYWNyb3NzIGEgc2V0IG9mIHBhcmFtZXRlcnMgKGFrYSwgInN3ZWVwIiBhIHNldCBvZiBwYXJhbWV0ZXJzKSB3aXRoIGByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygpYC4gCgpUaGlzIGZ1bmN0aW9uIHRha2VzIGEgUENBIG1hdHJpeCB3aXRoIHJvdyBuYW1lcyByZXByZXNlbnRpbmcgdW5pcXVlIGNlbGwgaWRzIChlLmcuLCBiYXJjb2RlcykgYXMgaXRzIHByaW1hcnkgYXJndW1lbnQsIHdpdGggYWRkaXRpb25hbCBhcmd1bWVudHMgZm9yIGNsdXN0ZXIgcGFyYW1ldGVycy4gClRoaXMgZnVuY3Rpb24gd3JhcHMgdGhlIGByT3BlblNjUENBOjpjYWxjdWxhdGVfY2x1c3RlcnMoKWAgZnVuY3Rpb24gYnV0IGFsbG93cyB5b3UgdG8gcHJvdmlkZSBhIHZlY3RvciBvZiBwYXJhbWV0ZXIgdmFsdWVzIHRvIHBlcmZvcm0gY2x1c3RlcmluZyBhY3Jvc3MsIGFzIGxpc3RlZCBiZWxvdy4KQ2x1c3RlcnMgd2lsbCBiZSBjYWxjdWxhdGVkIGZvciBhbGwgY29tYmluYXRpb25zIG9mIHBhcmFtZXRlcnMgdmFsdWVzICh3aGVyZSBhcHBsaWNhYmxlKTsgZGVmYXVsdCB2YWx1ZXMgdGhhdCB0aGUgZnVuY3Rpb24gd2lsbCB1c2UgZm9yIGFueSB1bnNwZWNpZmllZCBwYXJhbWV0ZXIgdmFsdWVzIGFyZSBzaG93biBpbiBwYXJlbnRoZXNlcwoKKiBgYWxnb3JpdGhtYDogV2hpY2ggY2x1c3RlcmluZyBhbGdvcml0aG0gdG8gdXNlIChMb3V2YWluKQoqIGB3ZWlnaHRpbmdgOiBXaGljaCB3ZWlnaHRpbmcgc2NoZW1lIHRvIHVzZSAoSmFjY2FyZCkKKiBgbm5gOiBUaGUgbnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzICgxMCkKKiBgcmVzb2x1dGlvbmA6IFRoZSByZXNvbHV0aW9uIHBhcmFtZXRlciAoMTsgdXNlZCBvbmx5IHdpdGggTG91dmFpbiBhbmQgTGVpZGVuIGNsdXN0ZXJpbmcpCiogYG9iamVjdGl2ZV9mdW5jdGlvbmA6IFRoZSBvYmplY3RpdmUgZnVuY3Rpb24gdG8gb3B0aW1pemUgY2x1c3RlcnMgKENQTTsgdXNlZCBvbmx5IHdpdGggTGVpZGVuIGNsdXN0ZXJpbmcpCgpgck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoKWAgZG9lcyBhbGxvdyB5b3UgdG8gc3BlY2lmeSB2YWx1ZXMgZm9yIGFueSBvdGhlciBwYXJhbWV0ZXJzLiAKCgpUaGlzIGZ1bmN0aW9uIHdpbGwgcmV0dXJuIGEgbGlzdCBvZiBkYXRhIGZyYW1lcyBvZiBjbHVzdGVyaW5nIHJlc3VsdHMuIApFYWNoIGRhdGEgZnJhbWUgd2lsbCBoYXZlIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiogYGNlbGxfaWRgOiBVbmlxdWUgY2VsbCBpZGVudGlmaWVycywgb2J0YWluZWQgZnJvbSB0aGUgUENBIG1hdHJpeCdzIHJvdyBuYW1lcwoqIGBjbHVzdGVyYDogQSBmYWN0b3IgY29sdW1uIHdpdGggdGhlIGNsdXN0ZXIgaWRlbnRpdGllcwoqIFRoZXJlIHdpbGwgYmUgb25lIGNvbHVtbiBmb3IgZWFjaCBjbHVzdGVyaW5nIHBhcmFtZXRlciB1c2VkCgpUbyBkZW1vbnN0cmF0ZSB0aGlzIGZ1bmN0aW9uLCB3ZSdsbCBjYWxjdWxhdGUgY2x1c3RlcnMgdXNpbmcgdGhlIExvdXZhaW4gYWxnb3JpdGhtIHdoaWxlIHZhcnlpbmcgdGhlIGBubmAgcGFyYW1ldGVyOgoKYGBge3Igc3dlZXAgY2x1c3RlcnN9CiMgRGVmaW5lIG5uIHBhcmFtZXRlciB2YWx1ZXMgb2YgaW50ZXJlc3QKbm5fdmFsdWVzIDwtIHNlcSgxMCwgMzAsIDEwKQoKIyBDYWxjdWxhdGUgY2x1c3RlcnMgdmFyeWluZyBubiwgYnV0IGxlYXZpbmcgb3RoZXIgcGFyYW1ldGVycyBhdCB0aGVpciBkZWZhdWx0IHZhbHVlcwpjbHVzdGVyX3Jlc3VsdHNfbGlzdCA8LSByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygKICBwY2FfbWF0cml4LAogIG5uID0gbm5fdmFsdWVzCikKYGBgCgpUaGUgcmVzdWx0aW5nIGxpc3QgaGFzIGEgbGVuZ3RoIG9mIHRocmVlLCBvbmUgZGF0YSBmcmFtZSBmb3IgZWFjaCBgbm5gIHBhcmFtZXRlciB0ZXN0ZWQ6CgpgYGB7ciBsZW5ndGggY2x1c3Rlcl9yZXN1bHRzX2xpc3R9Cmxlbmd0aChjbHVzdGVyX3Jlc3VsdHNfbGlzdCkKYGBgCgpJdCBjYW4gYmUgaGVscGZ1bCAoYWx0aG91Z2ggaXQgaXMgbm90IHN0cmljdGx5IG5lY2Vzc2FyeSB0byBrZWVwIHRyYWNrKSB0byBuYW1lIHRoaXMgbGlzdCBieSB0aGUgdmFyaWVkIGBubmAgcGFyYW1ldGVyOgoKYGBge3Igc2V0IGxpc3QgbmFtZXN9Cm5hbWVzKGNsdXN0ZXJfcmVzdWx0c19saXN0KSA8LSBnbHVlOjpnbHVlKCJubl97bm5fdmFsdWVzfSIpCmBgYAoKCldlIGNhbiBsb29rIGF0IHRoZSBmaXJzdCBmZXcgcm93cyBvZiBlYWNoIGRhdGEgZnJhbWUgdXNpbmcgW2BwdXJycjo6bWFwKClgXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL21hcC5odG1sKSB0byBpdGVyYXRlIG92ZXIgdGhlIGxpc3Q6CgoKYGBge3IgbWFwIGNsdXN0ZXJfcmVzdWx0c19saXN0fQpjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoaGVhZCkKYGBgCgpHZW5lcmFsbHkgc3BlYWtpbmcsIGBwdXJycjo6bWFwKClgIGNhbiBiZSB1c2VkIHRvIGl0ZXJhdGUgb3ZlciB0aGlzIGxpc3QgdG8gdmlzdWFsaXplIG9yIGFuYWx5emUgZWFjaCBjbHVzdGVyaW5nIHJlc3VsdCBvbiBpdHMgb3duOyB3ZSdsbCB1c2UgdGhpcyBhcHByb2FjaCBpbiB0aGUgZm9sbG93aW5nIHNlY3Rpb25zLiAKCiMjIyBWaXN1YWxpemluZyBjbHVzdGVyaW5nIHJlc3VsdHMKCldoZW4gY29tcGFyaW5nIGNsdXN0ZXJpbmcgcmVzdWx0cywgaXQncyBpbXBvcnRhbnQgdG8gZmlyc3QgdmlzdWFsaXplIHRoZSBkaWZmZXJlbnQgY2x1c3RlcmluZ3MgdG8gYnVpbGQgY29udGV4dCBmb3IgaW50ZXJwcmV0aW5nIFFDIG1ldHJpY3MuCgpBcyBvbmUgZXhhbXBsZSBvZiB3aHkgdGhpcyBpcyBpbXBvcnRhbnQsIHdlIGdlbmVyYWxseSBleHBlY3QgdGhhdCBtb3JlIHJvYnVzdCBjbHVzdGVycyB3aWxsIGhhdmUgaGlnaGVyIHZhbHVlcyBmb3IgbWV0cmljcyBsaWtlIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkuCkhvd2V2ZXIsIHdlIGFsc28gZXhwZWN0IHRoYXQgaGF2aW5nIGZld2VyIGNsdXN0ZXJzIGluIHRoZSBmaXJzdCBwbGFjZSB3aWxsIGFsc28gbGVhZCB0byBoaWdoZXIgbWV0cmljcywgcmVnYXJkbGVzcyBvZiBjbHVzdGVyIHF1YWxpdHk6IFdoZW4gdGhlcmUgYXJlIGZld2VyIGNsdXN0ZXJzLCBpdCBpcyBtb3JlIGxpa2VseSB0aGF0IGNsdXN0ZXJzIG92ZXJsYXAgbGVzcyB3aXRoIG9uZSBhbm90aGVyIGp1c3QgYmVjYXVzZSB0aGVyZSBhcmVuJ3QgbWFueSBjbHVzdGVycyBpbiB0aGUgZmlyc3QgcGxhY2UuClRoaXMgbWVhbnMgdGhhdCwgd2hlbiBpbnRlcnByZXRpbmcgY2x1c3RlciBxdWFsaXR5IG1ldHJpY3MsIHlvdSBzaG91bGQgYmUgY2FyZWZ1bCB0byB0YWtlIG1vcmUgY29udGV4dCBhYm91dCB0aGUgZGF0YSBpbnRvIGNvbnNpZGVyYXRpb24gYW5kIG5vdCBvbmx5IHJlbHkgb24gdGhlIG1ldHJpYyB2YWx1ZXMuCgpXZSdsbCB0aGVyZWZvcmUgdmlzdWFsaXplIHRoZXNlIHJlc3VsdHMgYXMgVU1BUHMgYnkgaXRlcmF0aW5nIG92ZXIgYGNsdXN0ZXJfcmVzdWx0c19saXN0YCBhbmQgY29tYmluaW5nIHBsb3RzIHdpdGggW2BwYXRjaHdvcms6OndyYXBfcGxvdHMoKWBdKGh0dHBzOi8vcGF0Y2h3b3JrLmRhdGEtaW1hZ2luaXN0LmNvbS9yZWZlcmVuY2Uvd3JhcF9wbG90cy5odG1sKS4KV2UnbGwgc3BlY2lmaWNhbGx5IHVzZSBbYHB1cnJyOjppbWFwKClgXShodHRwczovL3B1cnJyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2ltYXAuaHRtbCkgdG8gaXRlcmF0ZSBzbyB0aGF0IHdlIGNhbiBhc3NpZ24gdGhpcyBsaXN0J3MgbmFtZXMgYXMgcGxvdCB0aXRsZXMuCgpGb3IgdGhpcywgd2UnbGwgYmVnaW4gYnkgZXh0cmFjdGluZyBhIHRhYmxlIG9mIFVNQVAgY29vcmRpbmF0ZXMgZnJvbSBvdXIgU0NFIG9iamVjdC4KCmBgYHtyIGNyZWF0ZSB1bWFwX2RmfQp1bWFwX2RmIDwtIHJlZHVjZWREaW0oc2NlLCAiVU1BUCIpIHw+CiAgYXMuZGF0YS5mcmFtZSgpCmBgYAoKTmV4dCwgd2UnbGwgaXRlcmF0ZSBvdmVyIGBjbHVzdGVyX3Jlc3VsdHNfbGlzdGAgdG8gcGxvdCB0aGUgVU1BUHMuCgpgYGB7ciBwbG90IG5uIHVtYXBzLCBmaWcud2lkdGggPSAxMn0KdW1hcF9wbG90cyA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjppbWFwKAogICAgXChjbHVzdGVyX2RmLCBjbHVzdGVyaW5nX25hbWUpIHsKICAgICAgIyBBZGQgYSBjb2x1bW4gd2l0aCBjbHVzdGVyIGFzc2lnbm1lbnRzIHRvIHVtYXBfZGYKICAgICAgdW1hcF9kZl9wbG90IDwtIHVtYXBfZGYgfD4KICAgICAgICBkcGx5cjo6bXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyX2RmJGNsdXN0ZXIpCgogICAgICAjIFBsb3QgdGhlIFVNQVAsIGNvbG9yZWQgYnkgdGhlIG5ldyBjbHVzdGVyIHZhcmlhYmxlCiAgICAgIGdncGxvdCh1bWFwX2RmX3Bsb3QsIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjbHVzdGVyKSkgKwogICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsKICAgICAgICBsYWJzKHRpdGxlID0gZ2x1ZTo6Z2x1ZSgibmVhcmVzdCBuZWlnaGJvcnM6IHtjbHVzdGVyaW5nX25hbWV9IikpICsKICAgICAgICAjIFdlJ2xsIGFkZCBhIGNvdXBsZSBVTUFQIHBsb3Qgc2V0dGluZ3MgaGVyZSwgaW5jbHVkaW5nIGVxdWFsIGF4ZXMgYW5kCiAgICAgICAgIyB0dXJuaW5nIG9mZiB0aGUgYXhpcyB0aWNrcyBhbmQgdGV4dCBzaW5jZSBVTUFQIGNvb3JkaW5hdGVzIGFyZSBub3QgbWVhbmluZ2Z1bAogICAgICAgIGNvb3JkX2VxdWFsKCkgKwogICAgICAgIHRoZW1lKAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKQogICAgICAgICkKICAgIH0KICApCgojIFByaW50IHRoZSBwbG90cyB3aXRoIHBhdGNod29yazo6d3JhcF9wbG90cygpCnBhdGNod29yazo6d3JhcF9wbG90cyh1bWFwX3Bsb3RzKQpgYGAKClRoZXNlIHBsb3RzIHNob3cgdGhhdCB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIGRlY3JlYXNlcyBhcyB0aGUgbmVhcmVzdCBuZWlnaGJvcnMgcGFyYW1ldGVyIGluY3JlYXNlcywgd2l0aCBiZXR3ZWVuIDktMTMgY2x1c3RlcnMuCgoKCgojIyMgRXZhbHVhdGluZyBjbHVzdGVyaW5nIHJlc3VsdHMKClRoaXMgc2VjdGlvbiB3aWxsIHVzZSBgcHVycnI6Om1hcCgpYCB0byBpdGVyYXRlIG92ZXIgZWFjaCBjbHVzdGVyaW5nIHJlc3VsdCBkYXRhIGZyYW1lIHRvIGNhbGN1bGF0ZSBzaWxob3VldHRlIHdpZHRoLCBuZWlnaGJvcmhvb2QgcHVyaXR5LCBhbmQgc3RhYmlsaXR5LCBhbmQgdGhlbiB2aXN1YWxpemUgcmVzdWx0cy4KVGhlIGdvYWwgb2YgdGhpcyBjb2RlIGlzIHRvIGlkZW50aWZ5IHdoZXRoZXIgb25lIGNsdXN0ZXJpbmcgcGFyYW1ldGVyaXphdGlvbiBwcm9kdWNlcyBtb3JlIHJlbGlhYmxlIGNsdXN0ZXJzLiAKCgojIyMjIFNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkKCkJvdGggc2lsaG91ZXR0ZSB3aWR0aCBhbmQgbmVpZ2hib3Job29kIHB1cml0eSBhcmUgY2VsbC1sZXZlbCBxdWFudGl0aWVzLCBzbyB3ZSBjYW4gY2FsY3VsYXRlIHRoZW0gdG9nZXRoZXIgaW4gdGhlIHNhbWUgY2FsbCB0byBgcHVycnI6Om1hcCgpYC4KQmVsb3csIHdlJ2xsIGl0ZXJhdGUgb3ZlciBlYWNoIGRhdGEgZnJhbWUgaW4gYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBjYWxjdWxhdGUgdGhlc2UgcXVhbnRpdGllcy4KCmBgYHtyIGNhbGN1bGF0ZSBjZWxsIGxldmVsIG1ldHJpY3N9CmNlbGxfbWV0cmljX2xpc3QgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgICMgY2FsY3VsYXRlIHNpbGhvdWV0dGUgd2lkdGgKICAgICAgc2lsaG91ZXR0ZV9kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfc2lsaG91ZXR0ZShwY2FfbWF0cml4LCBjbHVzdGVyX2RmKQoKICAgICAgIyBjYWxjdWxhdGUgbmVpZ2hiaG9yaG9vZCBwdXJpdHkKICAgICAgcHVyaXR5X2RmIDwtIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9wdXJpdHkocGNhX21hdHJpeCwgY2x1c3Rlcl9kZikKCiAgICAgICMgQ29tYmluZSBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUKICAgICAgZHBseXI6OmxlZnRfam9pbihzaWxob3VldHRlX2RmLCBwdXJpdHlfZGYpCiAgICB9CiAgKQoKIyBWaWV3IHRoZSBmaXJzdCBzaXggcm93cyBvZiBlYWNoIGNsdXN0ZXJpbmcgcmVzdWx0J3MgY2VsbC1sZXZlbCBRQyBtZXRyaWNzCnB1cnJyOjptYXAoY2VsbF9tZXRyaWNfbGlzdCwgaGVhZCkKYGBgCgoKVG8gdmlzdWFsaXplIHRoZXNlIHJlc3VsdHMsIHdlIGNhbiBjb21iaW5lIGFsbCBkYXRhIGZyYW1lcyBpbiB0aGlzIGxpc3QgaW50byBhIHNpbmdsZSBvdmVyYWxsIGRhdGEgZnJhbWUsIHdoZXJlIHRoZSBleGlzdGluZyBgbm5gIGNvbHVtbiB3aWxsIGRpc3Rpbmd1aXNoIGFtb25nIGNvbmRpdGlvbnMuCgpgYGB7ciBjb21iaW5lIGNlbGwgbWV0cmljcyBsaXN0fQpjZWxsX21ldHJpY3NfZGYgPC0gZHBseXI6OmJpbmRfcm93cyhjZWxsX21ldHJpY19saXN0KQpgYGAKCldlIGNhbiB2aXN1YWxpemUgc2lsaG91ZXR0ZSB3aWR0aCBhbmQgbmVpZ2hib3Job29kIHB1cml0eSBlYWNoIHdpdGggYm94cGxvdHMsIGZvciBleGFtcGxlLCBhbmQgdXNlIHRoZSBbYHBhdGNod29ya2BdKGh0dHBzOi8vcGF0Y2h3b3JrLmRhdGEtaW1hZ2luaXN0LmNvbS8pIHBhY2thZ2UgdG8gcHJpbnQgdGhlbSB0b2dldGhlcjoKCgpgYGB7cn0KIyBQbG90IHNpbGhvdWV0dGUgd2lkdGgKc2lsaG91ZXR0ZV9wbG90IDwtIGdncGxvdChjZWxsX21ldHJpY3NfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBzaWxob3VldHRlX3dpZHRoLCBmaWxsID0gYXMuZmFjdG9yKG5uKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhc3RlbDIiKSArCiAgbGFicygKICAgIHggPSAiTnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzIiwKICAgIHkgPSAiU2lsaG91ZXR0ZSB3aWR0aCIKICApCgoKIyBQbG90IG5laWdoYm9yaG9vZCBwdXJpdHkgd2lkdGgKcHVyaXR5X3Bsb3QgPC0gZ2dwbG90KGNlbGxfbWV0cmljc19kZikgKwogIGFlcyh4ID0gYXMuZmFjdG9yKG5uKSwgeSA9IHB1cml0eSwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIk5laWdoYm9yaG9vZCBwdXJpdHkiCiAgKQoKCiMgQWRkIHRvZ2V0aGVyIHVzaW5nIHRoZSBwYXRjaHdvcmsgbGlicmFyeSwgd2l0aG91dCBhIGxlZ2VuZApzaWxob3VldHRlX3Bsb3QgKyBwdXJpdHlfcGxvdCAmIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpXaGlsZSB0aGVyZSBkb2VzIG5vdCBhcHBlYXIgdG8gYmUgYSBzYWxpZW50IGRpZmZlcmVuY2UgYW1vbmcgc2lsaG91ZXR0ZSB3aWR0aCBkaXN0cmlidXRpb25zLCBpdCBkb2VzIGFwcGVhciB0aGF0IHB1cml0eSBpcyBoaWdoZXIgd2l0aCBhIGhpZ2hlciBuZWFyZXN0IG5laWdoYm9ycyBwYXJhbWV0ZXIuCgoKIyMjIyBTdGFiaWxpdHkKCk5leHQsIHdlJ2xsIGNhbGN1bGF0ZSBzdGFiaWxpdHkgb24gdGhlIGNsdXN0ZXJzIHVzaW5nIGByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KClgLCBzcGVjaWZ5aW5nIHRoZSBzYW1lIHBhcmFtZXRlciB1c2VkIGZvciB0aGUgb3JpZ2luYWwgY2x1c3RlciBjYWxjdWxhdGlvbiBhdCBlYWNoIGl0ZXJhdGlvbi4gCgpgYGB7ciBjYWxjdWxhdGUgc3RhYmlsaXR5fQpzdGFiaWxpdHlfbGlzdCA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgbm4gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkbm4pCgogICAgICAjIGNhbGN1bGF0ZSBzdGFiaWxpdHksIHBhc3NpbmcgaW4gdGhlIHBhcmFtZXRlciB2YWx1ZSB1c2VkIGZvciB0aGlzIGl0ZXJhdGlvbgogICAgICByT3BlblNjUENBOjpjYWxjdWxhdGVfc3RhYmlsaXR5KHBjYV9tYXRyaXgsIGNsdXN0ZXJfZGYsIG5uID0gbm4pCiAgICB9CiAgKQpgYGAKCldlJ2xsIGFnYWluIGNvbWJpbmUgdGhlIG91dHB1dCBvZiBgc3RhYmlsaXR5X2xpc3RgIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZSBhbmQgcGxvdCBgYXJpYCB2YWx1ZXMgYWNyb3NzIGBubmAgcGFyYW1ldGVyaXphdGlvbnMuCgoKYGBge3IgY29tYmluZSBwbG90IHN0YWJpbGl0eX0Kc3RhYmlsaXR5X2RmIDwtIGRwbHlyOjpiaW5kX3Jvd3Moc3RhYmlsaXR5X2xpc3QpCgpnZ3Bsb3Qoc3RhYmlsaXR5X2RmKSArCiAgYWVzKHggPSBhcy5mYWN0b3Iobm4pLCB5ID0gYXJpLCBmaWxsID0gYXMuZmFjdG9yKG5uKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhc3RlbDIiKSArCiAgbGFicygKICAgIHggPSAiTnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzIiwKICAgIHkgPSAiQWRqdXN0ZWQgUmFuZCBJbmRleCIKICApICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKSGVyZSwgd2Ugc2VlIHRoYXQgYSBuZWFyZXN0IG5laWdoYm9ycyB2YWx1ZSBvZiAyMCBvciAzMCBsZWFkcyB0byBtb3JlIHN0YWJsZSBjbHVzdGVyaW5nIHJlc3VsdHMgY29tcGFyZWQgdG8gMTAuIAoKCiMjIFZhcnlpbmcgbXVsdGlwbGUgY2x1c3RlcmluZyBwYXJhbWV0ZXJzCgpUaGUgcHJldmlvdXMgc2VjdGlvbiBkZW1vbnN0cmF0ZWQgaG93IHRvIGNhbGN1bGF0ZSBjbHVzdGVycyBhbmQgUUMgbWV0cmljcyB3aGVuIHZhcnlpbmcgb25lIHBhcmFtZXRlciwgYnV0IGl0IGlzIHBvc3NpYmxlIHRvIHZhcnkgbXVsdGlwbGUgcGFyYW1ldGVycyBhdCBvbmNlIHdpdGggYHJPcGVuU2NQQ0E6OnN3ZWVwX2NsdXN0ZXJzKClgLgpJbiB0aGlzIHNlY3Rpb24sIHdlJ2xsIHNob3cgYW4gb3ZlcnZpZXcgb2YgaG93IHlvdSBtaWdodCB3cml0ZSBjb2RlIHRvIHZhcnkgdHdvIHBhcmFtZXRlcnMgYXQgb25jZSAoaGVyZSwgbmVhcmVzdCBuZWlnaGJvcnMgYW5kIHJlc29sdXRpb24gYXMgZXhhbXBsZXMpIGFuZCB2aXN1YWxpemUgcmVzdWx0cy4KCgpgYGB7ciBzd2VlcCB0d28gcGFyYW1ldGVyc30KIyBEZWZpbmUgdmVjdG9ycyBvZiBwYXJhbWV0ZXJzIHRvIHZhcnkKbm5fdmFsdWVzIDwtIHNlcSgxMCwgMzAsIDEwKQpyZXNfdmFsdWVzIDwtIHNlcSg1LCAxNSwgNSkvMTAKCgpjbHVzdGVyX3Jlc3VsdHNfbGlzdCA8LSByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygKICBwY2FfbWF0cml4LAogIG5uID0gbm5fdmFsdWVzLAogIHJlc29sdXRpb24gPSByZXNfdmFsdWVzCikKYGBgCgpUaGlzIHJlc3VsdGluZyBsaXN0IG5vdyBoYXMgOSBkaWZmZXJlbnQgY2x1c3RlcmluZyByZXN1bHRzLCBmb3IgZWFjaCBjb21iaW5hdGlvbiBvZiBgbm5fdmFsdWVzYCBhbmQgYHJlc192YWx1ZXNgOgoKCmBgYHtyIGxlbmd0aCBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB0d28gcGFyYW1ldGVyc30KbGVuZ3RoKGNsdXN0ZXJfcmVzdWx0c19saXN0KQpgYGAKCgojIyMgVmlzdWFsaXplIGNsdXN0ZXJzCgpOZXh0LCB3ZSdsbCBpdGVyYXRlIG92ZXIgYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBwbG90IHRoZSBVTUFQcy4KVGhpcyB0aW1lLCB3ZSdsbCB1c2UgYHB1cnJyOjptYXAoKWAgYW5kIHB1bGwgb3V0IHBhcmFtZXRlcnMgZnJvbSBlYWNoIGl0ZXJhdGlvbidzIGBjbHVzdGVyX2RmYCB0byBmb3JtIHRoZSBVTUFQIHBhbmVsIHRpdGxlLgoKYGBge3IgcGxvdCBubiByZXMgdW1hcHMsIGZpZy5oZWlnaHQgPSAxNH0KdW1hcF9wbG90cyA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKCiAgICAgICMgQWRkIGEgY29sdW1uIHdpdGggY2x1c3RlciBhc3NpZ25tZW50cyB0byB1bWFwX2RmCiAgICAgIHVtYXBfZGZfcGxvdCA8LSB1bWFwX2RmIHw+CiAgICAgICAgZHBseXI6Om11dGF0ZShjbHVzdGVyID0gY2x1c3Rlcl9kZiRjbHVzdGVyKQoKICAgICAgIyBDcmVhdGUgYSB0aXRsZSBmb3IgdGhlIFVNQVAgd2l0aCBib3RoIHBhcmFtZXRlcnMKICAgICAgdW1hcF90aXRsZSA8LSBnbHVlOjpnbHVlKAogICAgICAgICJubjoge3VuaXF1ZShjbHVzdGVyX2RmJG5uKX07IHJlczoge3VuaXF1ZShjbHVzdGVyX2RmJHJlc29sdXRpb24pfSIKICAgICAgKQoKICAgICAgIyBQbG90IHRoZSBVTUFQLCBjb2xvcmVkIGJ5IHRoZSBuZXcgY2x1c3RlciB2YXJpYWJsZQogICAgICBnZ3Bsb3QodW1hcF9kZl9wbG90LCBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gY2x1c3RlcikpICsKICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC42KSArCiAgICAgICAgbGFicyh0aXRsZSA9IHVtYXBfdGl0bGUpICsKICAgICAgICAjIFdlJ2xsIGFkZCBhIGNvdXBsZSBVTUFQLXNwZWNpZmljIHBsb3Qgc2V0dGluZ3MgCiAgICAgICAgY29vcmRfZXF1YWwoKSArCiAgICAgICAgdGhlbWUoCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIKICAgICAgICApCiAgfQopCgojIFByaW50IHRoZSBwbG90cyB3aXRoIHBhdGNod29yazo6d3JhcF9wbG90cygpCnBhdGNod29yazo6d3JhcF9wbG90cyh1bWFwX3Bsb3RzKQpgYGAKCgoKIyMjIENhbGN1bGF0ZSBhbmQgdmlzdWFsaXplIFFDIG1ldHJpY3MKClRoaXMgc2VjdGlvbiBwcmVzZW50cyBvbmUgY29kaW5nIHN0cmF0ZWd5IHRvIGNhbGN1bGF0ZSBhbmQgdmlzdWFsaXplIHJlc3VsdHMgd2hlbiB2YXJ5aW5nIHR3byBjbHVzdGVyaW5nIHBhcmFtZXRlcnMuCkluIHBhcnRpY3VsYXIsIHdlIHVzZSBmYWNldGluZyB0byBoZWxwIGRpc3BsYXkgYWxsIGluZm9ybWF0aW9uIGluIG9uZSBwbG90LCBieSBwbGFjaW5nIG5lYXJlc3QgbmVpZ2hib3IgdmFsdWVzIG9uIHRoZSBYLWF4aXMgYW5kIGZhY2V0aW5nIGJ5IHJlc29sdXRpb24gdmFsdWVzLgpTaW5jZSBzaWxob3VldHRlIHdpZHRoIGFuZCBuZWlnaGhvcmJvb2QgcHVyaXR5IGNhbGN1bGF0aW9ucyB1c2luZyBnZW5lcmFsbHkgc2ltaWxhciBjb2RlLCB3ZSdsbCBqdXN0IHNob3cgbmVpZ2hib3Job29kIHB1cml0eSBoZXJlLgoKIyMjIyBOZWlnaGJvcmhvb2QgcHVyaXR5CgpGaXJzdCwgd2UnbGwgY2FsY3VsYXRlIG5laWdoYm9yaG9vZCBwdXJpdHkgYW5kIGNvbWJpbmUgcmVzdWx0cyBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUuCgpgYGB7ciBjYWxjdWxhdGUgcHVyaXR5IHR3byBwYXJhbWV0ZXJzfQpwdXJpdHlfZGYgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9wdXJpdHkocGNhX21hdHJpeCwgY2x1c3Rlcl9kZikKICAgIH0KICApIHw+CiAgZHBseXI6OmJpbmRfcm93cygpCmBgYAoKCldlJ2xsIGFkZCBhIGNvbHVtbiBgcmVzb2x1dGlvbl9sYWJlbGAgd2hpY2ggd2UnbGwgdXNlIHRvIGhhdmUgaW5mb3JtYXRpdmUgcGFuZWwgdGl0bGVzIGluIHRoZSBmYWNldGVkIGdncGxvdCB3ZSBtYWtlIG5leHQuCgpgYGB7ciBhZGQgcmVzb2x1dGlvbl9sYWJlbCBjb2x1bW59CnB1cml0eV9kZiA8LSBwdXJpdHlfZGYgfD4KICBkcGx5cjo6bXV0YXRlKHJlc29sdXRpb25fbGFiZWwgPSBnbHVlOjpnbHVlKCJSZXNvbHV0aW9uOiB7cmVzb2x1dGlvbn0iKSkKYGBgCgpgYGB7ciB2aXN1YWxpemUgcHVyaXR5IHR3byBwYXJhbWV0ZXJzfQpnZ3Bsb3QocHVyaXR5X2RmKSArCiAgYWVzKHggPSBhcy5mYWN0b3Iobm4pLCB5ID0gcHVyaXR5LCBmaWxsID0gYXMuZmFjdG9yKG5uKSkgKwogIGdlb21fYm94cGxvdCgpICsKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIlBhc3RlbDIiKSArCiAgIyBmYWNldCBieSByZXNvbHV0aW9uCiAgZmFjZXRfd3JhcCh2YXJzKHJlc29sdXRpb25fbGFiZWwpKSArCiAgbGFicygKICAgIHggPSAiTnVtYmVyIG9mIG5lYXJlc3QgbmVpZ2hib3JzIiwKICAgIHkgPSAiTmVpZ2hib3Job29kIHB1cml0eSIKICApICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKIyMjIFN0YWJpbGl0eQoKU2ltaWxhciB0byBhYm92ZSwgd2UnbGwgY2FsY3VsYXRlIHN0YWJpbGl0eSwgY29tYmluZSByZXN1bHRzIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZSwgYWRkIGEgYHJlc29sdXRpb25fbGFiZWxgIGNvbHVtbiB0byBzdXBwb3J0IHBsb3QgaW50ZXJwcmV0YXRpb24sIGFuZCBmaW5hbGx5IG1ha2Ugb3VyIHBsb3QuCgpgYGB7ciBjYWxjdWxhdGUgc3RhYmlsaXR5IHR3byBwYXJhbWV0ZXJzfQpzdGFiaWxpdHlfZGYgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CgogICAgICAjIEV4dHJhY3QgcGFyYW1ldGVycyBmb3IgdGhpcyBjbHVzdGVyaW5nIHJlc3VsdAogICAgICBubiA8LSB1bmlxdWUoY2x1c3Rlcl9kZiRubikKICAgICAgcmVzb2x1dGlvbiA8LSB1bmlxdWUoY2x1c3Rlcl9kZiRyZXNvbHV0aW9uKQoKICAgICAgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgKICAgICAgICBwY2FfbWF0cml4LAogICAgICAgIGNsdXN0ZXJfZGYsCiAgICAgICAgbm4gPSBubiwKICAgICAgICByZXNvbHV0aW9uID0gcmVzb2x1dGlvbgogICAgICApCiAgICB9CiAgKSB8PgogIGRwbHlyOjpiaW5kX3Jvd3MoKQoKc3RhYmlsaXR5X2RmIDwtIHN0YWJpbGl0eV9kZiB8PgogIGRwbHlyOjptdXRhdGUocmVzb2x1dGlvbl9sYWJlbCA9IGdsdWU6OmdsdWUoIlJlc29sdXRpb246IHtyZXNvbHV0aW9ufSIpKQoKYGBgCgoKYGBge3IgdmlzdWFsaXplIHN0YWJpbGl0eSB0d28gcGFyYW1ldGVyc30KZ2dwbG90KHN0YWJpbGl0eV9kZikgKwogIGFlcyh4ID0gYXMuZmFjdG9yKG5uKSwgeSA9IGFyaSwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogICMgZmFjZXQgYnkgcmVzb2x1dGlvbgogIGZhY2V0X3dyYXAodmFycyhyZXNvbHV0aW9uX2xhYmVsKSkgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIkFkanVzdGVkIFJhbmQgSW5kZXgiCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9uIGluZm99CiMgcmVjb3JkIHRoZSB2ZXJzaW9ucyBvZiB0aGUgcGFja2FnZXMgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGFuZCBvdGhlciBlbnZpcm9ubWVudCBpbmZvcm1hdGlvbgpzZXNzaW9uSW5mbygpCmBgYAo=
+
LS0tCnRpdGxlOiAiQ29tcGFyaW5nIGNsdXN0ZXJpbmcgcGFyYW1ldGVycyB3aXRoIHJPcGVuU2NQQ0EiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKYXV0aG9yOiAiRGF0YSBMYWIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojIyBJbnRyb2R1Y3Rpb24KCkNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBoYXZlIHNldmVyYWwgcGFyYW1ldGVycyB3aGljaCBjYW4gYmUgdmFyaWVkLCBsZWFkaW5nIHRvIGRpZmZlcmVudCBjbHVzdGVyaW5nIHJlc3VsdHMuCkEga2V5IHF1ZXN0aW9uIHdoZW4gY2x1c3RlcmluZywgdGhlcmVmb3JlLCBpcyBob3cgdG8gaWRlbnRpZnkgYSBzZXQgb2YgcGFyYW1ldGVycyB0aGF0IGxlYWQgdG8gcm9idXN0IGFuZCByZWxpYWJsZSBjbHVzdGVycyB0aGF0IGNhbiBiZSB1c2VkIGluIGRvd25zdHJlYW0gYW5hbHlzaXMuIAoKVGhpcyBub3RlYm9vayBwcm92aWRlcyBleGFtcGxlcyBvZiBob3cgdG8gdXNlIHRoZSBgck9wZW5TY1BDQWAgcGFja2FnZSB0bzoKCiogQ2FsY3VsYXRlIHNldmVyYWwgdmVyc2lvbnMgb2YgY2x1c3RlcmluZyByZXN1bHRzIGFjcm9zcyBzZXZlcmFsIGRpZmZlcmVudCBwYXJhbWV0ZXJpemF0aW9ucwoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGFjcm9zcyBjbHVzdGVyaW5nIHJlc3VsdHMKClBsZWFzZSByZWZlciB0byB0aGUgW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCkgbm90ZWJvb2sgZm9yIGEgdHV0b3JpYWwgb24gdXNpbmcgYHJPcGVuU2NQQ0FgIGZ1bmN0aW9ucyB0bzoKCiogQ2FsY3VsYXRlIGNsdXN0ZXJzIGZyb20gYSBzaW5nbGUgcGFyYW1ldGVyaXphdGlvbgoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGEgc2luZ2xlIHNldCBvZiBjbHVzdGVycywgYXMgd2VsbCBhcyBleHBsYW5hdGlvbnMgb2YgdGhlIG1ldHJpY3MgdGhlbXNlbHZlcwoKVGhpcyBub3RlYm9vayB3aWxsIHVzZSB0aGUgc2FtcGxlIGBTQ1BDUzAwMDAwMWAgZnJvbSBwcm9qZWN0IGBTQ1BDUDAwMDAwMWAsIHdoaWNoIGlzIGFzc3VtZWQgcHJlc2VudCBpbiB0aGUgYE9wZW5TY1BDQS1hbmFseXNpcy9kYXRhL2N1cnJlbnQvU0NQQ1AwMDAwMDFgIGRpcmVjdG9yeSwgZm9yIGFsbCBleGFtcGxlcy4KUGxlYXNlIFtzZWUgdGhpcyBkb2N1bWVudGF0aW9uXShodHRwczovL29wZW5zY3BjYS5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvZ2V0dGluZy1zdGFydGVkL2FjY2Vzc2luZy1yZXNvdXJjZXMvZ2V0dGluZy1hY2Nlc3MtdG8tZGF0YS8pIGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IG9idGFpbmluZyBTY1BDQSBkYXRhLgoKIyMgU2V0dXAKCiMjIyBQYWNrYWdlcwoKCmBgYHtyIHBhY2thZ2VzfQpsaWJyYXJ5KHJPcGVuU2NQQ0EpCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQp9KQoKIyBTZXQgZ2dwbG90IHRoZW1lIGZvciBwbG90cwp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgoKIyMjIFBhdGhzCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290OjpoYXNfZGlyKCIuZ2l0aHViIikpCgojIFRoZSBjdXJyZW50IGRhdGEgZGlyZWN0b3J5LCBmb3VuZCB3aXRoaW4gdGhlIHJlcG9zaXRvcnkgYmFzZSBkaXJlY3RvcnkKZGF0YV9kaXIgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImRhdGEiLCAiY3VycmVudCIpCgojIFRoZSBwYXRoIHRvIHRoaXMgbW9kdWxlCm1vZHVsZV9iYXNlIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJhbmFseXNlcyIsICJoZWxsby1jbHVzdGVycyIpCmBgYAoKYGBge3IgaW5wdXQgZmlsZSBwYXRofQojIFBhdGggdG8gcHJvY2Vzc2VkIFNDRSBmaWxlIGZvciBzYW1wbGUgU0NQQ1MwMDAwMDEKaW5wdXRfc2NlX2ZpbGUgPC0gZmlsZS5wYXRoKGRhdGFfZGlyLCAiU0NQQ1AwMDAwMDEiLCAiU0NQQ1MwMDAwMDEiLCAiU0NQQ0wwMDAwMDFfcHJvY2Vzc2VkLnJkcyIpCmBgYAoKCiMjIyBTZXQgdGhlIHJhbmRvbSBzZWVkCgpCZWNhdXNlIGNsdXN0ZXJpbmcgaW52b2x2ZXMgcmFuZG9tIHNhbXBsaW5nLCBpdCBpcyBpbXBvcnRhbnQgdG8gc2V0IHRoZSByYW5kb20gc2VlZCBhdCB0aGUgdG9wIG9mIHlvdXIgYW5hbHlzaXMgc2NyaXB0IG9yIG5vdGVib29rIHRvIGVuc3VyZSByZXByb2R1Y2liaWxpdHkuCgpgYGB7ciBzZXQgc2VlZH0Kc2V0LnNlZWQoMjAyNCkKYGBgCgojIyBSZWFkIGluIGFuZCBwcmVwYXJlIGRhdGEKClRvIGJlZ2luLCB3ZSdsbCByZWFkIGluIHRoZSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIChTQ0UpIG9iamVjdC4KCmBgYHtyIHJlYWQgZGF0YX0KIyBSZWFkIHRoZSBTQ0UgZmlsZQpzY2UgPC0gcmVhZFJEUyhpbnB1dF9zY2VfZmlsZSkKYGBgCgpGb3IgdGhlIGluaXRpYWwgY2x1c3RlciBjYWxjdWxhdGlvbnMgYW5kIGV2YWx1YXRpb25zLCB3ZSB3aWxsIHVzZSB0aGUgUENBIG1hdHJpeCBleHRyYWN0ZWQgZnJvbSB0aGUgU0NFIG9iamVjdC4KQXMgc2hvd24gaW4gW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCksIGl0IGlzIGFsc28gcG9zc2libGUgdG8gdXNlIGFuIFNDRSBvYmplY3Qgb3IgYSBTZXVyYXQgb2JqZWN0IGRpcmVjdGx5LgoKCmBgYHtyIGV4dHJhY3QgcGNhIGRhdGF9CiMgRXh0cmFjdCB0aGUgUENBIG1hdHJpeCBmcm9tIGFuIFNDRSBvYmplY3QKcGNhX21hdHJpeCA8LSByZWR1Y2VkRGltKHNjZSwgIlBDQSIpCmBgYAoKIyMgVmFyeWluZyBhIHNpbmdsZSBjbHVzdGVyaW5nIHBhcmFtZXRlcgoKVGhpcyBzZWN0aW9uIHdpbGwgc2hvdyBob3cgdG8gcGVyZm9ybSBjbHVzdGVyaW5nIGFjcm9zcyBhIHNldCBvZiBwYXJhbWV0ZXJzIChha2EsICJzd2VlcCIgYSBzZXQgb2YgcGFyYW1ldGVycykgd2l0aCBgck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoKWAuIAoKVGhpcyBmdW5jdGlvbiB0YWtlcyBhIFBDQSBtYXRyaXggd2l0aCByb3cgbmFtZXMgcmVwcmVzZW50aW5nIHVuaXF1ZSBjZWxsIGlkcyAoZS5nLiwgYmFyY29kZXMpIGFzIGl0cyBwcmltYXJ5IGFyZ3VtZW50LCB3aXRoIGFkZGl0aW9uYWwgYXJndW1lbnRzIGZvciBjbHVzdGVyIHBhcmFtZXRlcnMuIApUaGlzIGZ1bmN0aW9uIHdyYXBzIHRoZSBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX2NsdXN0ZXJzKClgIGZ1bmN0aW9uIGJ1dCBhbGxvd3MgeW91IHRvIHByb3ZpZGUgYSB2ZWN0b3Igb2YgcGFyYW1ldGVyIHZhbHVlcyB0byBwZXJmb3JtIGNsdXN0ZXJpbmcgYWNyb3NzLCBhcyBsaXN0ZWQgYmVsb3cuCkNsdXN0ZXJzIHdpbGwgYmUgY2FsY3VsYXRlZCBmb3IgYWxsIGNvbWJpbmF0aW9ucyBvZiBwYXJhbWV0ZXJzIHZhbHVlcyAod2hlcmUgYXBwbGljYWJsZSk7IGRlZmF1bHQgdmFsdWVzIHRoYXQgdGhlIGZ1bmN0aW9uIHdpbGwgdXNlIGZvciBhbnkgdW5zcGVjaWZpZWQgcGFyYW1ldGVyIHZhbHVlcyBhcmUgc2hvd24gaW4gcGFyZW50aGVzZXMKCiogYGFsZ29yaXRobWA6IFdoaWNoIGNsdXN0ZXJpbmcgYWxnb3JpdGhtIHRvIHVzZSAoTG91dmFpbikKKiBgd2VpZ2h0aW5nYDogV2hpY2ggd2VpZ2h0aW5nIHNjaGVtZSB0byB1c2UgKEphY2NhcmQpCiogYG5uYDogVGhlIG51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyAoMTApCiogYHJlc29sdXRpb25gOiBUaGUgcmVzb2x1dGlvbiBwYXJhbWV0ZXIgKDE7IHVzZWQgb25seSB3aXRoIExvdXZhaW4gYW5kIExlaWRlbiBjbHVzdGVyaW5nKQoqIGBvYmplY3RpdmVfZnVuY3Rpb25gOiBUaGUgb2JqZWN0aXZlIGZ1bmN0aW9uIHRvIG9wdGltaXplIGNsdXN0ZXJzIChDUE07IHVzZWQgb25seSB3aXRoIExlaWRlbiBjbHVzdGVyaW5nKQoKYHJPcGVuU2NQQ0E6OnN3ZWVwX2NsdXN0ZXJzKClgIGRvZXMgYWxsb3cgeW91IHRvIHNwZWNpZnkgdmFsdWVzIGZvciBhbnkgb3RoZXIgcGFyYW1ldGVycy4gCgoKVGhpcyBmdW5jdGlvbiB3aWxsIHJldHVybiBhIGxpc3Qgb2YgZGF0YSBmcmFtZXMgb2YgY2x1c3RlcmluZyByZXN1bHRzLiAKRWFjaCBkYXRhIGZyYW1lIHdpbGwgaGF2ZSB0aGUgZm9sbG93aW5nIGNvbHVtbnM6CgoqIGBjZWxsX2lkYDogVW5pcXVlIGNlbGwgaWRlbnRpZmllcnMsIG9idGFpbmVkIGZyb20gdGhlIFBDQSBtYXRyaXgncyByb3cgbmFtZXMKKiBgY2x1c3RlcmA6IEEgZmFjdG9yIGNvbHVtbiB3aXRoIHRoZSBjbHVzdGVyIGlkZW50aXRpZXMKKiBUaGVyZSB3aWxsIGJlIG9uZSBjb2x1bW4gZm9yIGVhY2ggY2x1c3RlcmluZyBwYXJhbWV0ZXIgdXNlZAoKVG8gZGVtb25zdHJhdGUgdGhpcyBmdW5jdGlvbiwgd2UnbGwgY2FsY3VsYXRlIGNsdXN0ZXJzIHVzaW5nIHRoZSBMb3V2YWluIGFsZ29yaXRobSB3aGlsZSB2YXJ5aW5nIHRoZSBgbm5gIHBhcmFtZXRlcjoKCmBgYHtyIHN3ZWVwIGNsdXN0ZXJzfQojIERlZmluZSBubiBwYXJhbWV0ZXIgdmFsdWVzIG9mIGludGVyZXN0Cm5uX3ZhbHVlcyA8LSBzZXEoMTAsIDMwLCAxMCkKCiMgQ2FsY3VsYXRlIGNsdXN0ZXJzIHZhcnlpbmcgbm4sIGJ1dCBsZWF2aW5nIG90aGVyIHBhcmFtZXRlcnMgYXQgdGhlaXIgZGVmYXVsdCB2YWx1ZXMKY2x1c3Rlcl9yZXN1bHRzX2xpc3QgPC0gck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoCiAgcGNhX21hdHJpeCwKICBubiA9IG5uX3ZhbHVlcwopCmBgYAoKVGhlIHJlc3VsdGluZyBsaXN0IGhhcyBhIGxlbmd0aCBvZiB0aHJlZSwgb25lIGRhdGEgZnJhbWUgZm9yIGVhY2ggYG5uYCBwYXJhbWV0ZXIgdGVzdGVkOgoKYGBge3IgbGVuZ3RoIGNsdXN0ZXJfcmVzdWx0c19saXN0fQpsZW5ndGgoY2x1c3Rlcl9yZXN1bHRzX2xpc3QpCmBgYAoKSXQgY2FuIGJlIGhlbHBmdWwgKGFsdGhvdWdoIGl0IGlzIG5vdCBzdHJpY3RseSBuZWNlc3NhcnkgdG8ga2VlcCB0cmFjaykgdG8gbmFtZSB0aGlzIGxpc3QgYnkgdGhlIHZhcmllZCBgbm5gIHBhcmFtZXRlcjoKCmBgYHtyIHNldCBsaXN0IG5hbWVzfQpuYW1lcyhjbHVzdGVyX3Jlc3VsdHNfbGlzdCkgPC0gZ2x1ZTo6Z2x1ZSgibm5fe25uX3ZhbHVlc30iKQpgYGAKCgpXZSBjYW4gbG9vayBhdCB0aGUgZmlyc3QgZmV3IHJvd3Mgb2YgZWFjaCBkYXRhIGZyYW1lIHVzaW5nIFtgcHVycnI6Om1hcCgpYF0oaHR0cHM6Ly9wdXJyci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9tYXAuaHRtbCkgdG8gaXRlcmF0ZSBvdmVyIHRoZSBsaXN0OgoKCmBgYHtyIG1hcCBjbHVzdGVyX3Jlc3VsdHNfbGlzdH0KY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKGhlYWQpCmBgYAoKR2VuZXJhbGx5IHNwZWFraW5nLCBgcHVycnI6Om1hcCgpYCBjYW4gYmUgdXNlZCB0byBpdGVyYXRlIG92ZXIgdGhpcyBsaXN0IHRvIHZpc3VhbGl6ZSBvciBhbmFseXplIGVhY2ggY2x1c3RlcmluZyByZXN1bHQgb24gaXRzIG93bjsgd2UnbGwgdXNlIHRoaXMgYXBwcm9hY2ggaW4gdGhlIGZvbGxvd2luZyBzZWN0aW9ucy4gCgojIyMgVmlzdWFsaXppbmcgY2x1c3RlcmluZyByZXN1bHRzCgpXaGVuIGNvbXBhcmluZyBjbHVzdGVyaW5nIHJlc3VsdHMsIGl0J3MgaW1wb3J0YW50IHRvIGZpcnN0IHZpc3VhbGl6ZSB0aGUgZGlmZmVyZW50IGNsdXN0ZXJpbmdzIHRvIGJ1aWxkIGNvbnRleHQgZm9yIGludGVycHJldGluZyBRQyBtZXRyaWNzLgoKQXMgb25lIGV4YW1wbGUgb2Ygd2h5IHRoaXMgaXMgaW1wb3J0YW50LCB3ZSBnZW5lcmFsbHkgZXhwZWN0IHRoYXQgbW9yZSByb2J1c3QgY2x1c3RlcnMgd2lsbCBoYXZlIGhpZ2hlciB2YWx1ZXMgZm9yIG1ldHJpY3MgbGlrZSBzaWxob3VldHRlIHdpZHRoIGFuZCBuZWlnaGJvcmhvb2QgcHVyaXR5LgpIb3dldmVyLCB3ZSBhbHNvIGV4cGVjdCB0aGF0IGhhdmluZyBmZXdlciBjbHVzdGVycyBpbiB0aGUgZmlyc3QgcGxhY2Ugd2lsbCBhbHNvIGxlYWQgdG8gaGlnaGVyIG1ldHJpY3MsIHJlZ2FyZGxlc3Mgb2YgY2x1c3RlciBxdWFsaXR5OiBXaGVuIHRoZXJlIGFyZSBmZXdlciBjbHVzdGVycywgaXQgaXMgbW9yZSBsaWtlbHkgdGhhdCBjbHVzdGVycyBvdmVybGFwIGxlc3Mgd2l0aCBvbmUgYW5vdGhlciBqdXN0IGJlY2F1c2UgdGhlcmUgYXJlbid0IG1hbnkgY2x1c3RlcnMgaW4gdGhlIGZpcnN0IHBsYWNlLgpUaGlzIG1lYW5zIHRoYXQsIHdoZW4gaW50ZXJwcmV0aW5nIGNsdXN0ZXIgcXVhbGl0eSBtZXRyaWNzLCB5b3Ugc2hvdWxkIGJlIGNhcmVmdWwgdG8gdGFrZSBtb3JlIGNvbnRleHQgYWJvdXQgdGhlIGRhdGEgaW50byBjb25zaWRlcmF0aW9uIGFuZCBub3Qgb25seSByZWx5IG9uIHRoZSBtZXRyaWMgdmFsdWVzLgoKV2UnbGwgdGhlcmVmb3JlIHZpc3VhbGl6ZSB0aGVzZSByZXN1bHRzIGFzIFVNQVBzIGJ5IGl0ZXJhdGluZyBvdmVyIGBjbHVzdGVyX3Jlc3VsdHNfbGlzdGAgYW5kIGNvbWJpbmluZyBwbG90cyB3aXRoIFtgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKClgXShodHRwczovL3BhdGNod29yay5kYXRhLWltYWdpbmlzdC5jb20vcmVmZXJlbmNlL3dyYXBfcGxvdHMuaHRtbCkuCldlJ2xsIHNwZWNpZmljYWxseSB1c2UgW2BwdXJycjo6aW1hcCgpYF0oaHR0cHM6Ly9wdXJyci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9pbWFwLmh0bWwpIHRvIGl0ZXJhdGUgc28gdGhhdCB3ZSBjYW4gYXNzaWduIHRoaXMgbGlzdCdzIG5hbWVzIGFzIHBsb3QgdGl0bGVzLgoKRm9yIHRoaXMsIHdlJ2xsIGJlZ2luIGJ5IGV4dHJhY3RpbmcgYSB0YWJsZSBvZiBVTUFQIGNvb3JkaW5hdGVzIGZyb20gb3VyIFNDRSBvYmplY3QuCgpgYGB7ciBjcmVhdGUgdW1hcF9kZn0KdW1hcF9kZiA8LSByZWR1Y2VkRGltKHNjZSwgIlVNQVAiKSB8PgogIGFzLmRhdGEuZnJhbWUoKQpgYGAKCk5leHQsIHdlJ2xsIGl0ZXJhdGUgb3ZlciBgY2x1c3Rlcl9yZXN1bHRzX2xpc3RgIHRvIHBsb3QgdGhlIFVNQVBzLgoKYGBge3IgcGxvdCBubiB1bWFwcywgZmlnLndpZHRoID0gMTJ9CnVtYXBfcGxvdHMgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6aW1hcCgKICAgIFwoY2x1c3Rlcl9kZiwgY2x1c3RlcmluZ19uYW1lKSB7CiAgICAgICMgQWRkIGEgY29sdW1uIHdpdGggY2x1c3RlciBhc3NpZ25tZW50cyB0byB1bWFwX2RmCiAgICAgIHVtYXBfZGZfcGxvdCA8LSB1bWFwX2RmIHw+CiAgICAgICAgZHBseXI6Om11dGF0ZShjbHVzdGVyID0gY2x1c3Rlcl9kZiRjbHVzdGVyKQoKICAgICAgIyBQbG90IHRoZSBVTUFQLCBjb2xvcmVkIGJ5IHRoZSBuZXcgY2x1c3RlciB2YXJpYWJsZQogICAgICBnZ3Bsb3QodW1hcF9kZl9wbG90LCBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gY2x1c3RlcikpICsKICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC42KSArCiAgICAgICAgbGFicyh0aXRsZSA9IGdsdWU6OmdsdWUoIm5lYXJlc3QgbmVpZ2hib3JzOiB7Y2x1c3RlcmluZ19uYW1lfSIpKSArCiAgICAgICAgIyBXZSdsbCBhZGQgYSBjb3VwbGUgVU1BUCBwbG90IHNldHRpbmdzIGhlcmUsIGluY2x1ZGluZyBlcXVhbCBheGVzIGFuZAogICAgICAgICMgdHVybmluZyBvZmYgdGhlIGF4aXMgdGlja3MgYW5kIHRleHQgc2luY2UgVU1BUCBjb29yZGluYXRlcyBhcmUgbm90IG1lYW5pbmdmdWwKICAgICAgICBjb29yZF9lcXVhbCgpICsKICAgICAgICB0aGVtZSgKICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCkKICAgICAgICApCiAgICB9CiAgKQoKIyBQcmludCB0aGUgcGxvdHMgd2l0aCBwYXRjaHdvcms6OndyYXBfcGxvdHMoKQpwYXRjaHdvcms6OndyYXBfcGxvdHModW1hcF9wbG90cykKYGBgCgpUaGVzZSBwbG90cyBzaG93IHRoYXQgdGhlIG51bWJlciBvZiBjbHVzdGVycyBkZWNyZWFzZXMgYXMgdGhlIG5lYXJlc3QgbmVpZ2hib3JzIHBhcmFtZXRlciBpbmNyZWFzZXMsIHdpdGggYmV0d2VlbiA5LTEzIGNsdXN0ZXJzLgoKCgoKIyMjIEV2YWx1YXRpbmcgY2x1c3RlcmluZyByZXN1bHRzCgpUaGlzIHNlY3Rpb24gd2lsbCB1c2UgYHB1cnJyOjptYXAoKWAgdG8gaXRlcmF0ZSBvdmVyIGVhY2ggY2x1c3RlcmluZyByZXN1bHQgZGF0YSBmcmFtZSB0byBjYWxjdWxhdGUgc2lsaG91ZXR0ZSB3aWR0aCwgbmVpZ2hib3Job29kIHB1cml0eSwgYW5kIHN0YWJpbGl0eSwgYW5kIHRoZW4gdmlzdWFsaXplIHJlc3VsdHMuClRoZSBnb2FsIG9mIHRoaXMgY29kZSBpcyB0byBpZGVudGlmeSB3aGV0aGVyIG9uZSBjbHVzdGVyaW5nIHBhcmFtZXRlcml6YXRpb24gcHJvZHVjZXMgbW9yZSByZWxpYWJsZSBjbHVzdGVycy4gCgoKIyMjIyBTaWxob3VldHRlIHdpZHRoIGFuZCBuZWlnaGJvcmhvb2QgcHVyaXR5CgpCb3RoIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkgYXJlIGNlbGwtbGV2ZWwgcXVhbnRpdGllcywgc28gd2UgY2FuIGNhbGN1bGF0ZSB0aGVtIHRvZ2V0aGVyIGluIHRoZSBzYW1lIGNhbGwgdG8gYHB1cnJyOjptYXAoKWAuCkJlbG93LCB3ZSdsbCBpdGVyYXRlIG92ZXIgZWFjaCBkYXRhIGZyYW1lIGluIGBjbHVzdGVyX3Jlc3VsdHNfbGlzdGAgdG8gY2FsY3VsYXRlIHRoZXNlIHF1YW50aXRpZXMuCgpgYGB7ciBjYWxjdWxhdGUgY2VsbCBsZXZlbCBtZXRyaWNzfQpjZWxsX21ldHJpY19saXN0IDwtIGNsdXN0ZXJfcmVzdWx0c19saXN0IHw+CiAgcHVycnI6Om1hcCgKICAgIFwoY2x1c3Rlcl9kZikgewogICAgICAjIGNhbGN1bGF0ZSBzaWxob3VldHRlIHdpZHRoCiAgICAgIHNpbGhvdWV0dGVfZGYgPC0gck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3NpbGhvdWV0dGUocGNhX21hdHJpeCwgY2x1c3Rlcl9kZikKCiAgICAgICMgY2FsY3VsYXRlIG5laWdoYmhvcmhvb2QgcHVyaXR5CiAgICAgIHB1cml0eV9kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfcHVyaXR5KHBjYV9tYXRyaXgsIGNsdXN0ZXJfZGYpCgogICAgICAjIENvbWJpbmUgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lCiAgICAgIGRwbHlyOjpsZWZ0X2pvaW4oc2lsaG91ZXR0ZV9kZiwgcHVyaXR5X2RmKQogICAgfQogICkKCiMgVmlldyB0aGUgZmlyc3Qgc2l4IHJvd3Mgb2YgZWFjaCBjbHVzdGVyaW5nIHJlc3VsdCdzIGNlbGwtbGV2ZWwgUUMgbWV0cmljcwpwdXJycjo6bWFwKGNlbGxfbWV0cmljX2xpc3QsIGhlYWQpCmBgYAoKClRvIHZpc3VhbGl6ZSB0aGVzZSByZXN1bHRzLCB3ZSBjYW4gY29tYmluZSBhbGwgZGF0YSBmcmFtZXMgaW4gdGhpcyBsaXN0IGludG8gYSBzaW5nbGUgb3ZlcmFsbCBkYXRhIGZyYW1lLCB3aGVyZSB0aGUgZXhpc3RpbmcgYG5uYCBjb2x1bW4gd2lsbCBkaXN0aW5ndWlzaCBhbW9uZyBjb25kaXRpb25zLgoKYGBge3IgY29tYmluZSBjZWxsIG1ldHJpY3MgbGlzdH0KY2VsbF9tZXRyaWNzX2RmIDwtIGRwbHlyOjpiaW5kX3Jvd3MoY2VsbF9tZXRyaWNfbGlzdCkKYGBgCgpXZSBjYW4gdmlzdWFsaXplIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkgZWFjaCB3aXRoIGJveHBsb3RzLCBmb3IgZXhhbXBsZSwgYW5kIHVzZSB0aGUgW2BwYXRjaHdvcmtgXShodHRwczovL3BhdGNod29yay5kYXRhLWltYWdpbmlzdC5jb20vKSBwYWNrYWdlIHRvIHByaW50IHRoZW0gdG9nZXRoZXI6CgoKYGBge3J9CiMgUGxvdCBzaWxob3VldHRlIHdpZHRoCnNpbGhvdWV0dGVfcGxvdCA8LSBnZ3Bsb3QoY2VsbF9tZXRyaWNzX2RmKSArCiAgYWVzKHggPSBhcy5mYWN0b3Iobm4pLCB5ID0gc2lsaG91ZXR0ZV93aWR0aCwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIlNpbGhvdWV0dGUgd2lkdGgiCiAgKQoKCiMgUGxvdCBuZWlnaGJvcmhvb2QgcHVyaXR5IHdpZHRoCnB1cml0eV9wbG90IDwtIGdncGxvdChjZWxsX21ldHJpY3NfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBwdXJpdHksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMiLAogICAgeSA9ICJOZWlnaGJvcmhvb2QgcHVyaXR5IgogICkKCgojIEFkZCB0b2dldGhlciB1c2luZyB0aGUgcGF0Y2h3b3JrIGxpYnJhcnksIHdpdGhvdXQgYSBsZWdlbmQKc2lsaG91ZXR0ZV9wbG90ICsgcHVyaXR5X3Bsb3QgJiB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKV2hpbGUgdGhlcmUgZG9lcyBub3QgYXBwZWFyIHRvIGJlIGEgc2FsaWVudCBkaWZmZXJlbmNlIGFtb25nIHNpbGhvdWV0dGUgd2lkdGggZGlzdHJpYnV0aW9ucywgaXQgZG9lcyBhcHBlYXIgdGhhdCBwdXJpdHkgaXMgaGlnaGVyIHdpdGggYSBoaWdoZXIgbmVhcmVzdCBuZWlnaGJvcnMgcGFyYW1ldGVyLgoKCiMjIyMgU3RhYmlsaXR5CgpOZXh0LCB3ZSdsbCBjYWxjdWxhdGUgc3RhYmlsaXR5IG9uIHRoZSBjbHVzdGVycyB1c2luZyBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgpYCwgc3BlY2lmeWluZyB0aGUgc2FtZSBwYXJhbWV0ZXIgdXNlZCBmb3IgdGhlIG9yaWdpbmFsIGNsdXN0ZXIgY2FsY3VsYXRpb24gYXQgZWFjaCBpdGVyYXRpb24uIAoKYGBge3IgY2FsY3VsYXRlIHN0YWJpbGl0eX0Kc3RhYmlsaXR5X2xpc3QgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgIG5uIDwtIHVuaXF1ZShjbHVzdGVyX2RmJG5uKQoKICAgICAgIyBjYWxjdWxhdGUgc3RhYmlsaXR5LCBwYXNzaW5nIGluIHRoZSBwYXJhbWV0ZXIgdmFsdWUgdXNlZCBmb3IgdGhpcyBpdGVyYXRpb24KICAgICAgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eShwY2FfbWF0cml4LCBjbHVzdGVyX2RmLCBubiA9IG5uKQogICAgfQogICkKYGBgCgpXZSdsbCBhZ2FpbiBjb21iaW5lIHRoZSBvdXRwdXQgb2YgYHN0YWJpbGl0eV9saXN0YCBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUgYW5kIHBsb3QgYGFyaWAgdmFsdWVzIGFjcm9zcyBgbm5gIHBhcmFtZXRlcml6YXRpb25zLgoKCmBgYHtyIGNvbWJpbmUgcGxvdCBzdGFiaWxpdHl9CnN0YWJpbGl0eV9kZiA8LSBkcGx5cjo6YmluZF9yb3dzKHN0YWJpbGl0eV9saXN0KQoKZ2dwbG90KHN0YWJpbGl0eV9kZikgKwogIGFlcyh4ID0gYXMuZmFjdG9yKG5uKSwgeSA9IGFyaSwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIkFkanVzdGVkIFJhbmQgSW5kZXgiCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCkhlcmUsIHdlIHNlZSB0aGF0IGEgbmVhcmVzdCBuZWlnaGJvcnMgdmFsdWUgb2YgMjAgb3IgMzAgbGVhZHMgdG8gbW9yZSBzdGFibGUgY2x1c3RlcmluZyByZXN1bHRzIGNvbXBhcmVkIHRvIDEwLiAKCgojIyBWYXJ5aW5nIG11bHRpcGxlIGNsdXN0ZXJpbmcgcGFyYW1ldGVycwoKVGhlIHByZXZpb3VzIHNlY3Rpb24gZGVtb25zdHJhdGVkIGhvdyB0byBjYWxjdWxhdGUgY2x1c3RlcnMgYW5kIFFDIG1ldHJpY3Mgd2hlbiB2YXJ5aW5nIG9uZSBwYXJhbWV0ZXIsIGJ1dCBpdCBpcyBwb3NzaWJsZSB0byB2YXJ5IG11bHRpcGxlIHBhcmFtZXRlcnMgYXQgb25jZSB3aXRoIGByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygpYC4KSW4gdGhpcyBzZWN0aW9uLCB3ZSdsbCBzaG93IGFuIG92ZXJ2aWV3IG9mIGhvdyB5b3UgbWlnaHQgd3JpdGUgY29kZSB0byB2YXJ5IHR3byBwYXJhbWV0ZXJzIGF0IG9uY2UgKGhlcmUsIG5lYXJlc3QgbmVpZ2hib3JzIGFuZCByZXNvbHV0aW9uIGFzIGV4YW1wbGVzKSBhbmQgdmlzdWFsaXplIHJlc3VsdHMuCgoKYGBge3Igc3dlZXAgdHdvIHBhcmFtZXRlcnN9CiMgRGVmaW5lIHZlY3RvcnMgb2YgcGFyYW1ldGVycyB0byB2YXJ5Cm5uX3ZhbHVlcyA8LSBzZXEoMTAsIDMwLCAxMCkKcmVzX3ZhbHVlcyA8LSBzZXEoNSwgMTUsIDUpIC8gMTAKCgpjbHVzdGVyX3Jlc3VsdHNfbGlzdCA8LSByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygKICBwY2FfbWF0cml4LAogIG5uID0gbm5fdmFsdWVzLAogIHJlc29sdXRpb24gPSByZXNfdmFsdWVzCikKYGBgCgpUaGlzIHJlc3VsdGluZyBsaXN0IG5vdyBoYXMgOSBkaWZmZXJlbnQgY2x1c3RlcmluZyByZXN1bHRzLCBmb3IgZWFjaCBjb21iaW5hdGlvbiBvZiBgbm5fdmFsdWVzYCBhbmQgYHJlc192YWx1ZXNgOgoKCmBgYHtyIGxlbmd0aCBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB0d28gcGFyYW1ldGVyc30KbGVuZ3RoKGNsdXN0ZXJfcmVzdWx0c19saXN0KQpgYGAKCgojIyMgVmlzdWFsaXplIGNsdXN0ZXJzCgpOZXh0LCB3ZSdsbCBpdGVyYXRlIG92ZXIgYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBwbG90IHRoZSBVTUFQcy4KVGhpcyB0aW1lLCB3ZSdsbCB1c2UgYHB1cnJyOjptYXAoKWAgYW5kIHB1bGwgb3V0IHBhcmFtZXRlcnMgZnJvbSBlYWNoIGl0ZXJhdGlvbidzIGBjbHVzdGVyX2RmYCB0byBmb3JtIHRoZSBVTUFQIHBhbmVsIHRpdGxlLgoKYGBge3IgcGxvdCBubiByZXMgdW1hcHMsIGZpZy5oZWlnaHQgPSAxNH0KdW1hcF9wbG90cyA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgIyBBZGQgYSBjb2x1bW4gd2l0aCBjbHVzdGVyIGFzc2lnbm1lbnRzIHRvIHVtYXBfZGYKICAgICAgdW1hcF9kZl9wbG90IDwtIHVtYXBfZGYgfD4KICAgICAgICBkcGx5cjo6bXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyX2RmJGNsdXN0ZXIpCgogICAgICAjIENyZWF0ZSBhIHRpdGxlIGZvciB0aGUgVU1BUCB3aXRoIGJvdGggcGFyYW1ldGVycwogICAgICB1bWFwX3RpdGxlIDwtIGdsdWU6OmdsdWUoCiAgICAgICAgIm5uOiB7dW5pcXVlKGNsdXN0ZXJfZGYkbm4pfTsgcmVzOiB7dW5pcXVlKGNsdXN0ZXJfZGYkcmVzb2x1dGlvbil9IgogICAgICApCgogICAgICAjIFBsb3QgdGhlIFVNQVAsIGNvbG9yZWQgYnkgdGhlIG5ldyBjbHVzdGVyIHZhcmlhYmxlCiAgICAgIGdncGxvdCh1bWFwX2RmX3Bsb3QsIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjbHVzdGVyKSkgKwogICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsKICAgICAgICBsYWJzKHRpdGxlID0gdW1hcF90aXRsZSkgKwogICAgICAgICMgV2UnbGwgYWRkIGEgY291cGxlIFVNQVAtc3BlY2lmaWMgcGxvdCBzZXR0aW5ncwogICAgICAgIGNvb3JkX2VxdWFsKCkgKwogICAgICAgIHRoZW1lKAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iCiAgICAgICAgKQogICAgfQogICkKCiMgUHJpbnQgdGhlIHBsb3RzIHdpdGggcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKCkKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHVtYXBfcGxvdHMpCmBgYAoKCgojIyMgQ2FsY3VsYXRlIGFuZCB2aXN1YWxpemUgUUMgbWV0cmljcwoKVGhpcyBzZWN0aW9uIHByZXNlbnRzIG9uZSBjb2Rpbmcgc3RyYXRlZ3kgdG8gY2FsY3VsYXRlIGFuZCB2aXN1YWxpemUgcmVzdWx0cyB3aGVuIHZhcnlpbmcgdHdvIGNsdXN0ZXJpbmcgcGFyYW1ldGVycy4KSW4gcGFydGljdWxhciwgd2UgdXNlIGZhY2V0aW5nIHRvIGhlbHAgZGlzcGxheSBhbGwgaW5mb3JtYXRpb24gaW4gb25lIHBsb3QsIGJ5IHBsYWNpbmcgbmVhcmVzdCBuZWlnaGJvciB2YWx1ZXMgb24gdGhlIFgtYXhpcyBhbmQgZmFjZXRpbmcgYnkgcmVzb2x1dGlvbiB2YWx1ZXMuClNpbmNlIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoaG9yYm9vZCBwdXJpdHkgY2FsY3VsYXRpb25zIHVzaW5nIGdlbmVyYWxseSBzaW1pbGFyIGNvZGUsIHdlJ2xsIGp1c3Qgc2hvdyBuZWlnaGJvcmhvb2QgcHVyaXR5IGhlcmUuCgojIyMjIE5laWdoYm9yaG9vZCBwdXJpdHkKCkZpcnN0LCB3ZSdsbCBjYWxjdWxhdGUgbmVpZ2hib3Job29kIHB1cml0eSBhbmQgY29tYmluZSByZXN1bHRzIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZS4KCmBgYHtyIGNhbGN1bGF0ZSBwdXJpdHkgdHdvIHBhcmFtZXRlcnN9CnB1cml0eV9kZiA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3B1cml0eShwY2FfbWF0cml4LCBjbHVzdGVyX2RmKQogICAgfQogICkgfD4KICBkcGx5cjo6YmluZF9yb3dzKCkKYGBgCgoKV2UnbGwgYWRkIGEgY29sdW1uIGByZXNvbHV0aW9uX2xhYmVsYCB3aGljaCB3ZSdsbCB1c2UgdG8gaGF2ZSBpbmZvcm1hdGl2ZSBwYW5lbCB0aXRsZXMgaW4gdGhlIGZhY2V0ZWQgZ2dwbG90IHdlIG1ha2UgbmV4dC4KCmBgYHtyIGFkZCByZXNvbHV0aW9uX2xhYmVsIGNvbHVtbn0KcHVyaXR5X2RmIDwtIHB1cml0eV9kZiB8PgogIGRwbHlyOjptdXRhdGUocmVzb2x1dGlvbl9sYWJlbCA9IGdsdWU6OmdsdWUoIlJlc29sdXRpb246IHtyZXNvbHV0aW9ufSIpKQpgYGAKCmBgYHtyIHZpc3VhbGl6ZSBwdXJpdHkgdHdvIHBhcmFtZXRlcnN9CmdncGxvdChwdXJpdHlfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBwdXJpdHksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICAjIGZhY2V0IGJ5IHJlc29sdXRpb24KICBmYWNldF93cmFwKHZhcnMocmVzb2x1dGlvbl9sYWJlbCkpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMiLAogICAgeSA9ICJOZWlnaGJvcmhvb2QgcHVyaXR5IgogICkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgojIyMgU3RhYmlsaXR5CgpTaW1pbGFyIHRvIGFib3ZlLCB3ZSdsbCBjYWxjdWxhdGUgc3RhYmlsaXR5LCBjb21iaW5lIHJlc3VsdHMgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lLCBhZGQgYSBgcmVzb2x1dGlvbl9sYWJlbGAgY29sdW1uIHRvIHN1cHBvcnQgcGxvdCBpbnRlcnByZXRhdGlvbiwgYW5kIGZpbmFsbHkgbWFrZSBvdXIgcGxvdC4KCmBgYHtyIGNhbGN1bGF0ZSBzdGFiaWxpdHkgdHdvIHBhcmFtZXRlcnN9CnN0YWJpbGl0eV9kZiA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgIyBFeHRyYWN0IHBhcmFtZXRlcnMgZm9yIHRoaXMgY2x1c3RlcmluZyByZXN1bHQKICAgICAgbm4gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkbm4pCiAgICAgIHJlc29sdXRpb24gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkcmVzb2x1dGlvbikKCiAgICAgIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zdGFiaWxpdHkoCiAgICAgICAgcGNhX21hdHJpeCwKICAgICAgICBjbHVzdGVyX2RmLAogICAgICAgIG5uID0gbm4sCiAgICAgICAgcmVzb2x1dGlvbiA9IHJlc29sdXRpb24KICAgICAgKQogICAgfQogICkgfD4KICBkcGx5cjo6YmluZF9yb3dzKCkKCnN0YWJpbGl0eV9kZiA8LSBzdGFiaWxpdHlfZGYgfD4KICBkcGx5cjo6bXV0YXRlKHJlc29sdXRpb25fbGFiZWwgPSBnbHVlOjpnbHVlKCJSZXNvbHV0aW9uOiB7cmVzb2x1dGlvbn0iKSkKYGBgCgoKYGBge3IgdmlzdWFsaXplIHN0YWJpbGl0eSB0d28gcGFyYW1ldGVyc30KZ2dwbG90KHN0YWJpbGl0eV9kZikgKwogIGFlcyh4ID0gYXMuZmFjdG9yKG5uKSwgeSA9IGFyaSwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogICMgZmFjZXQgYnkgcmVzb2x1dGlvbgogIGZhY2V0X3dyYXAodmFycyhyZXNvbHV0aW9uX2xhYmVsKSkgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIkFkanVzdGVkIFJhbmQgSW5kZXgiCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9uIGluZm99CiMgcmVjb3JkIHRoZSB2ZXJzaW9ucyBvZiB0aGUgcGFja2FnZXMgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGFuZCBvdGhlciBlbnZpcm9ubWVudCBpbmZvcm1hdGlvbgpzZXNzaW9uSW5mbygpCmBgYAo=
From 953b3d10b688104a8e5902d4320b8b125192f945 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Tue, 14 Jan 2025 13:45:37 -0500 Subject: [PATCH 11/16] Apply suggestions from code review Co-authored-by: Joshua Shapiro --- .../02_compare-clustering-parameters.Rmd | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd index dab142325..7b9ba3c4c 100644 --- a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd +++ b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd @@ -97,7 +97,7 @@ This section will show how to perform clustering across a set of parameters (aka 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 +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) @@ -119,7 +119,7 @@ To demonstrate this function, we'll calculate clusters using the Louvain algorit ```{r sweep clusters} # Define nn parameter values of interest -nn_values <- seq(10, 30, 10) +nn_values <- c(10, 20, 30) # Calculate clusters varying nn, but leaving other parameters at their default values cluster_results_list <- rOpenScPCA::sweep_clusters( @@ -221,10 +221,7 @@ cell_metric_list <- cluster_results_list |> silhouette_df <- rOpenScPCA::calculate_silhouette(pca_matrix, cluster_df) # calculate neighbhorhood purity - purity_df <- rOpenScPCA::calculate_purity(pca_matrix, cluster_df) - - # Combine into a single data frame - dplyr::left_join(silhouette_df, purity_df) + purity_df <- rOpenScPCA::calculate_purity(pca_matrix, silhouette_df) } ) @@ -280,7 +277,7 @@ Next, we'll calculate stability on the clusters using `rOpenScPCA::calculate_sta stability_list <- cluster_results_list |> purrr::map( \(cluster_df) { - nn <- unique(cluster_df$nn) + 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) @@ -316,8 +313,8 @@ In this section, we'll show an overview of how you might write code to vary two ```{r sweep two parameters} # Define vectors of parameters to vary -nn_values <- seq(10, 30, 10) -res_values <- seq(5, 15, 5) / 10 +nn_values <- c(10, 20, 30) +res_values <-c(0.5, 1.0, 1.5) cluster_results_list <- rOpenScPCA::sweep_clusters( @@ -368,7 +365,7 @@ umap_plots <- cluster_results_list |> ) # Print the plots with patchwork::wrap_plots() -patchwork::wrap_plots(umap_plots) +patchwork::wrap_plots(umap_plots, ncol = 3) ``` @@ -407,7 +404,7 @@ ggplot(purity_df) + geom_boxplot() + scale_fill_brewer(palette = "Pastel2") + # facet by resolution - facet_wrap(vars(resolution_label)) + + facet_wrap(vars(resolution_label), labeller = label_both) + labs( x = "Number of nearest neighbors", y = "Neighborhood purity" From 9de516aa5fa2b97a8ba0a8272eca9c62303e1975 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Tue, 14 Jan 2025 13:50:57 -0500 Subject: [PATCH 12/16] Update analyses/hello-clusters/02_compare-clustering-parameters.Rmd Co-authored-by: Joshua Shapiro --- analyses/hello-clusters/02_compare-clustering-parameters.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd index 7b9ba3c4c..ec6bddc65 100644 --- a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd +++ b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd @@ -233,7 +233,7 @@ 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 <- dplyr::bind_rows(cell_metric_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: From 626b1747af94b867627967dfe86fcecb86a9fff1 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Tue, 14 Jan 2025 14:04:24 -0500 Subject: [PATCH 13/16] updates in response to review --- .../02_compare-clustering-parameters.Rmd | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd index ec6bddc65..457ca45b7 100644 --- a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd +++ b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd @@ -105,7 +105,7 @@ Clusters will be calculated for all combinations of parameters values (where app * `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 allow you to specify values for any other parameters. +`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. @@ -134,7 +134,8 @@ The resulting list has a length of three, one data frame for each `nn` parameter 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: +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}") @@ -194,7 +195,7 @@ umap_plots <- cluster_results_list |> ) # Print the plots with patchwork::wrap_plots() -patchwork::wrap_plots(umap_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. @@ -267,6 +268,7 @@ 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 @@ -289,7 +291,7 @@ We'll again combine the output of `stability_list` into a single data frame and ```{r combine plot stability} -stability_df <- dplyr::bind_rows(stability_list) +stability_df <- purrr::list_rbind(stability_list) ggplot(stability_df) + aes(x = as.factor(nn), y = ari, fill = as.factor(nn)) + @@ -314,7 +316,7 @@ In this section, we'll show an overview of how you might write code to vary two ```{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) +res_values <- c(0.5, 1.0, 1.5) cluster_results_list <- rOpenScPCA::sweep_clusters( @@ -347,7 +349,7 @@ umap_plots <- cluster_results_list |> # Create a title for the UMAP with both parameters umap_title <- glue::glue( - "nn: {unique(cluster_df$nn)}; res: {unique(cluster_df$resolution)}" + "nn: {cluster_df$nn[1]}; res: {cluster_df$resolution[1]}" ) # Plot the UMAP, colored by the new cluster variable @@ -387,24 +389,17 @@ purity_df <- cluster_results_list |> rOpenScPCA::calculate_purity(pca_matrix, cluster_df) } ) |> - dplyr::bind_rows() + purrr::list_rbind() ``` -We'll add a column `resolution_label` which we'll use to have informative panel titles in the faceted ggplot we make next. - -```{r add resolution_label column} -purity_df <- purity_df |> - dplyr::mutate(resolution_label = glue::glue("Resolution: {resolution}")) -``` - ```{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 - facet_wrap(vars(resolution_label), labeller = label_both) + + # 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" @@ -432,10 +427,7 @@ stability_df <- cluster_results_list |> ) } ) |> - dplyr::bind_rows() - -stability_df <- stability_df |> - dplyr::mutate(resolution_label = glue::glue("Resolution: {resolution}")) + purrr::list_rbind() ``` @@ -444,8 +436,7 @@ ggplot(stability_df) + aes(x = as.factor(nn), y = ari, fill = as.factor(nn)) + geom_boxplot() + scale_fill_brewer(palette = "Pastel2") + - # facet by resolution - facet_wrap(vars(resolution_label)) + + facet_wrap(vars(resolution), labeller = label_both) + labs( x = "Number of nearest neighbors", y = "Adjusted Rand Index" From 013fd7ccbe1f5769fc779042a947ce4245032c09 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Tue, 14 Jan 2025 14:06:32 -0500 Subject: [PATCH 14/16] update a comment --- analyses/hello-clusters/02_compare-clustering-parameters.Rmd | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd index 457ca45b7..3bfc1c0a4 100644 --- a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd +++ b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd @@ -221,8 +221,8 @@ cell_metric_list <- cluster_results_list |> # calculate silhouette width silhouette_df <- rOpenScPCA::calculate_silhouette(pca_matrix, cluster_df) - # calculate neighbhorhood purity - purity_df <- rOpenScPCA::calculate_purity(pca_matrix, silhouette_df) + # calculate neighbhorhood purity, starting from silhouette_df + OpenScPCA::calculate_purity(pca_matrix, silhouette_df) } ) From 9de82b4c7872b13af0660d981440f92941567cf1 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Tue, 14 Jan 2025 14:07:09 -0500 Subject: [PATCH 15/16] addidentally deleted an r --- analyses/hello-clusters/02_compare-clustering-parameters.Rmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd index 3bfc1c0a4..e5ffb6c5c 100644 --- a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd +++ b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd @@ -222,7 +222,7 @@ cell_metric_list <- cluster_results_list |> silhouette_df <- rOpenScPCA::calculate_silhouette(pca_matrix, cluster_df) # calculate neighbhorhood purity, starting from silhouette_df - OpenScPCA::calculate_purity(pca_matrix, silhouette_df) + rOpenScPCA::calculate_purity(pca_matrix, silhouette_df) } ) From 1e59f9c2dde1d32025f53e92a91370dd161c5945 Mon Sep 17 00:00:00 2001 From: Stephanie Spielman Date: Tue, 14 Jan 2025 15:16:06 -0500 Subject: [PATCH 16/16] ensure legend fits, and render the html --- .../02_compare-clustering-parameters.Rmd | 4 +- .../02_compare-clustering-parameters.nb.html | 118 ++++++++---------- 2 files changed, 52 insertions(+), 70 deletions(-) diff --git a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd index e5ffb6c5c..6ba7df341 100644 --- a/analyses/hello-clusters/02_compare-clustering-parameters.Rmd +++ b/analyses/hello-clusters/02_compare-clustering-parameters.Rmd @@ -361,7 +361,9 @@ umap_plots <- cluster_results_list |> theme( axis.ticks = element_blank(), axis.text = element_blank(), - legend.position = "bottom" + # Ensure legends fit in the figure + legend.position = "bottom", + legend.key.size = unit(0.2, "cm") ) } ) diff --git a/analyses/hello-clusters/02_compare-clustering-parameters.nb.html b/analyses/hello-clusters/02_compare-clustering-parameters.nb.html index 212e116c5..76257489e 100644 --- a/analyses/hello-clusters/02_compare-clustering-parameters.nb.html +++ b/analyses/hello-clusters/02_compare-clustering-parameters.nb.html @@ -11,7 +11,7 @@ - + Comparing clustering parameters with rOpenScPCA @@ -2901,7 +2901,7 @@

Comparing clustering parameters with rOpenScPCA

Data Lab

-

2025-01-13

+

2025-01-14

@@ -3040,7 +3040,7 @@

Varying a single clustering parameter

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

+parentheses.

  • algorithm: Which clustering algorithm to use (Louvain)
  • @@ -3051,8 +3051,8 @@

    Varying a single clustering parameter

  • objective_function: The objective function to optimize clusters (CPM; used only with Leiden clustering)
-

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

+

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:

    @@ -3066,9 +3066,9 @@

    Varying a single clustering parameter

    Louvain algorithm while varying the nn parameter:

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

    Varying a single clustering parameter

    It can be helpful (although it is not strictly necessary to keep -track) to name this list by the varied nn parameter:

    +track) to name this list by the varied nn parameter. In +this case, we’ll use these names to label plots.

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

    Visualizing clustering results

    the UMAPs.

    - +
    umap_plots <- cluster_results_list |>
       purrr::imap(
         \(cluster_df, clustering_name) {
    @@ -3196,7 +3197,7 @@ 

    Visualizing clustering results

    ) # Print the plots with patchwork::wrap_plots() -patchwork::wrap_plots(umap_plots)
    +patchwork::wrap_plots(umap_plots, ncol = 3)

    @@ -3221,31 +3222,19 @@

    Silhouette width and neighborhood purity

    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
    -      purity_df <- rOpenScPCA::calculate_purity(pca_matrix, cluster_df)
    -
    -      # Combine into a single data frame
    -      dplyr::left_join(silhouette_df, purity_df)
    +      # calculate neighbhorhood purity, starting from silhouette_df
    +      rOpenScPCA::calculate_purity(pca_matrix, silhouette_df)
         }
    -  )
    - - -
    Joining with `by = join_by(cell_id, cluster, algorithm, weighting, nn,
    -resolution)`
    -Joining with `by = join_by(cell_id, cluster, algorithm, weighting, nn,
    -resolution)`
    -Joining with `by = join_by(cell_id, cluster, algorithm, weighting, nn,
    -resolution)`
    - - -
    # View the first six rows of each clustering result's cell-level QC metrics
    +  )
    +
    +# View the first six rows of each clustering result's cell-level QC metrics
     purrr::map(cell_metric_list, head)
    @@ -3304,8 +3293,8 @@

    Silhouette width and neighborhood purity

    nn column will distinguish among conditions.

    - -
    cell_metrics_df <- dplyr::bind_rows(cell_metric_list)
    + +
    cell_metrics_df <- purrr::list_rbind(cell_metric_list)
    @@ -3347,7 +3336,10 @@

    Silhouette width and neighborhood purity

    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.

    +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

    @@ -3357,11 +3349,11 @@

    Stability

    iteration.

    - +
    stability_list <- cluster_results_list |>
       purrr::map(
         \(cluster_df) {
    -      nn <- unique(cluster_df$nn)
    +      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)
    @@ -3375,8 +3367,8 @@ 

    Stability

    nn parameterizations.

    - -
    stability_df <- dplyr::bind_rows(stability_list)
    +
    +
    stability_df <- purrr::list_rbind(stability_list)
     
     ggplot(stability_df) +
       aes(x = as.factor(nn), y = ari, fill = as.factor(nn)) +
    @@ -3408,10 +3400,10 @@ 

    Varying multiple clustering parameters

    examples) and visualize results.

    - +
    # Define vectors of parameters to vary
    -nn_values <- seq(10, 30, 10)
    -res_values <- seq(5, 15, 5) / 10
    +nn_values <- c(10, 20, 30)
    +res_values <- c(0.5, 1.0, 1.5)
     
     
     cluster_results_list <- rOpenScPCA::sweep_clusters(
    @@ -3442,7 +3434,7 @@ 

    Visualize clusters

    UMAP panel title.

    - +
    umap_plots <- cluster_results_list |>
       purrr::map(
         \(cluster_df) {
    @@ -3452,7 +3444,7 @@ 

    Visualize clusters

    # Create a title for the UMAP with both parameters umap_title <- glue::glue( - "nn: {unique(cluster_df$nn)}; res: {unique(cluster_df$resolution)}" + "nn: {cluster_df$nn[1]}; res: {cluster_df$resolution[1]}" ) # Plot the UMAP, colored by the new cluster variable @@ -3464,16 +3456,18 @@

    Visualize clusters

    theme( axis.ticks = element_blank(), axis.text = element_blank(), - legend.position = "bottom" + # 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)
    +patchwork::wrap_plots(umap_plots, ncol = 3)
    -

    +

    @@ -3492,36 +3486,26 @@

    Neighborhood purity

    single data frame.

    - +
    purity_df <- cluster_results_list |>
       purrr::map(
         \(cluster_df) {
           rOpenScPCA::calculate_purity(pca_matrix, cluster_df)
         }
       ) |>
    -  dplyr::bind_rows()
    + purrr::list_rbind()
    -

    We’ll add a column resolution_label which we’ll use to -have informative panel titles in the faceted ggplot we make next.

    - -
    purity_df <- purity_df |>
    -  dplyr::mutate(resolution_label = glue::glue("Resolution: {resolution}"))
    - - - - - - +
    ggplot(purity_df) +
       aes(x = as.factor(nn), y = purity, fill = as.factor(nn)) +
       geom_boxplot() +
       scale_fill_brewer(palette = "Pastel2") +
    -  # facet by resolution
    -  facet_wrap(vars(resolution_label)) +
    +  # 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"
    @@ -3529,7 +3513,7 @@ 

    Neighborhood purity

    theme(legend.position = "none")
    -

    +

    @@ -3542,7 +3526,7 @@

    Stability

    plot interpretation, and finally make our plot.

    - +
    stability_df <- cluster_results_list |>
       purrr::map(
         \(cluster_df) {
    @@ -3558,22 +3542,18 @@ 

    Stability

    ) } ) |> - dplyr::bind_rows() - -stability_df <- stability_df |> - dplyr::mutate(resolution_label = glue::glue("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 by resolution
    -  facet_wrap(vars(resolution_label)) +
    +  facet_wrap(vars(resolution), labeller = label_both) +
       labs(
         x = "Number of nearest neighbors",
         y = "Adjusted Rand Index"
    @@ -3581,7 +3561,7 @@ 

    Stability

    theme(legend.position = "none")
    -

    +

    @@ -3651,7 +3631,7 @@

    Session Info

    -
    LS0tCnRpdGxlOiAiQ29tcGFyaW5nIGNsdXN0ZXJpbmcgcGFyYW1ldGVycyB3aXRoIHJPcGVuU2NQQ0EiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKYXV0aG9yOiAiRGF0YSBMYWIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojIyBJbnRyb2R1Y3Rpb24KCkNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBoYXZlIHNldmVyYWwgcGFyYW1ldGVycyB3aGljaCBjYW4gYmUgdmFyaWVkLCBsZWFkaW5nIHRvIGRpZmZlcmVudCBjbHVzdGVyaW5nIHJlc3VsdHMuCkEga2V5IHF1ZXN0aW9uIHdoZW4gY2x1c3RlcmluZywgdGhlcmVmb3JlLCBpcyBob3cgdG8gaWRlbnRpZnkgYSBzZXQgb2YgcGFyYW1ldGVycyB0aGF0IGxlYWQgdG8gcm9idXN0IGFuZCByZWxpYWJsZSBjbHVzdGVycyB0aGF0IGNhbiBiZSB1c2VkIGluIGRvd25zdHJlYW0gYW5hbHlzaXMuIAoKVGhpcyBub3RlYm9vayBwcm92aWRlcyBleGFtcGxlcyBvZiBob3cgdG8gdXNlIHRoZSBgck9wZW5TY1BDQWAgcGFja2FnZSB0bzoKCiogQ2FsY3VsYXRlIHNldmVyYWwgdmVyc2lvbnMgb2YgY2x1c3RlcmluZyByZXN1bHRzIGFjcm9zcyBzZXZlcmFsIGRpZmZlcmVudCBwYXJhbWV0ZXJpemF0aW9ucwoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGFjcm9zcyBjbHVzdGVyaW5nIHJlc3VsdHMKClBsZWFzZSByZWZlciB0byB0aGUgW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCkgbm90ZWJvb2sgZm9yIGEgdHV0b3JpYWwgb24gdXNpbmcgYHJPcGVuU2NQQ0FgIGZ1bmN0aW9ucyB0bzoKCiogQ2FsY3VsYXRlIGNsdXN0ZXJzIGZyb20gYSBzaW5nbGUgcGFyYW1ldGVyaXphdGlvbgoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGEgc2luZ2xlIHNldCBvZiBjbHVzdGVycywgYXMgd2VsbCBhcyBleHBsYW5hdGlvbnMgb2YgdGhlIG1ldHJpY3MgdGhlbXNlbHZlcwoKVGhpcyBub3RlYm9vayB3aWxsIHVzZSB0aGUgc2FtcGxlIGBTQ1BDUzAwMDAwMWAgZnJvbSBwcm9qZWN0IGBTQ1BDUDAwMDAwMWAsIHdoaWNoIGlzIGFzc3VtZWQgcHJlc2VudCBpbiB0aGUgYE9wZW5TY1BDQS1hbmFseXNpcy9kYXRhL2N1cnJlbnQvU0NQQ1AwMDAwMDFgIGRpcmVjdG9yeSwgZm9yIGFsbCBleGFtcGxlcy4KUGxlYXNlIFtzZWUgdGhpcyBkb2N1bWVudGF0aW9uXShodHRwczovL29wZW5zY3BjYS5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvZ2V0dGluZy1zdGFydGVkL2FjY2Vzc2luZy1yZXNvdXJjZXMvZ2V0dGluZy1hY2Nlc3MtdG8tZGF0YS8pIGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IG9idGFpbmluZyBTY1BDQSBkYXRhLgoKIyMgU2V0dXAKCiMjIyBQYWNrYWdlcwoKCmBgYHtyIHBhY2thZ2VzfQpsaWJyYXJ5KHJPcGVuU2NQQ0EpCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQp9KQoKIyBTZXQgZ2dwbG90IHRoZW1lIGZvciBwbG90cwp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgoKIyMjIFBhdGhzCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290OjpoYXNfZGlyKCIuZ2l0aHViIikpCgojIFRoZSBjdXJyZW50IGRhdGEgZGlyZWN0b3J5LCBmb3VuZCB3aXRoaW4gdGhlIHJlcG9zaXRvcnkgYmFzZSBkaXJlY3RvcnkKZGF0YV9kaXIgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImRhdGEiLCAiY3VycmVudCIpCgojIFRoZSBwYXRoIHRvIHRoaXMgbW9kdWxlCm1vZHVsZV9iYXNlIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJhbmFseXNlcyIsICJoZWxsby1jbHVzdGVycyIpCmBgYAoKYGBge3IgaW5wdXQgZmlsZSBwYXRofQojIFBhdGggdG8gcHJvY2Vzc2VkIFNDRSBmaWxlIGZvciBzYW1wbGUgU0NQQ1MwMDAwMDEKaW5wdXRfc2NlX2ZpbGUgPC0gZmlsZS5wYXRoKGRhdGFfZGlyLCAiU0NQQ1AwMDAwMDEiLCAiU0NQQ1MwMDAwMDEiLCAiU0NQQ0wwMDAwMDFfcHJvY2Vzc2VkLnJkcyIpCmBgYAoKCiMjIyBTZXQgdGhlIHJhbmRvbSBzZWVkCgpCZWNhdXNlIGNsdXN0ZXJpbmcgaW52b2x2ZXMgcmFuZG9tIHNhbXBsaW5nLCBpdCBpcyBpbXBvcnRhbnQgdG8gc2V0IHRoZSByYW5kb20gc2VlZCBhdCB0aGUgdG9wIG9mIHlvdXIgYW5hbHlzaXMgc2NyaXB0IG9yIG5vdGVib29rIHRvIGVuc3VyZSByZXByb2R1Y2liaWxpdHkuCgpgYGB7ciBzZXQgc2VlZH0Kc2V0LnNlZWQoMjAyNCkKYGBgCgojIyBSZWFkIGluIGFuZCBwcmVwYXJlIGRhdGEKClRvIGJlZ2luLCB3ZSdsbCByZWFkIGluIHRoZSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIChTQ0UpIG9iamVjdC4KCmBgYHtyIHJlYWQgZGF0YX0KIyBSZWFkIHRoZSBTQ0UgZmlsZQpzY2UgPC0gcmVhZFJEUyhpbnB1dF9zY2VfZmlsZSkKYGBgCgpGb3IgdGhlIGluaXRpYWwgY2x1c3RlciBjYWxjdWxhdGlvbnMgYW5kIGV2YWx1YXRpb25zLCB3ZSB3aWxsIHVzZSB0aGUgUENBIG1hdHJpeCBleHRyYWN0ZWQgZnJvbSB0aGUgU0NFIG9iamVjdC4KQXMgc2hvd24gaW4gW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCksIGl0IGlzIGFsc28gcG9zc2libGUgdG8gdXNlIGFuIFNDRSBvYmplY3Qgb3IgYSBTZXVyYXQgb2JqZWN0IGRpcmVjdGx5LgoKCmBgYHtyIGV4dHJhY3QgcGNhIGRhdGF9CiMgRXh0cmFjdCB0aGUgUENBIG1hdHJpeCBmcm9tIGFuIFNDRSBvYmplY3QKcGNhX21hdHJpeCA8LSByZWR1Y2VkRGltKHNjZSwgIlBDQSIpCmBgYAoKIyMgVmFyeWluZyBhIHNpbmdsZSBjbHVzdGVyaW5nIHBhcmFtZXRlcgoKVGhpcyBzZWN0aW9uIHdpbGwgc2hvdyBob3cgdG8gcGVyZm9ybSBjbHVzdGVyaW5nIGFjcm9zcyBhIHNldCBvZiBwYXJhbWV0ZXJzIChha2EsICJzd2VlcCIgYSBzZXQgb2YgcGFyYW1ldGVycykgd2l0aCBgck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoKWAuIAoKVGhpcyBmdW5jdGlvbiB0YWtlcyBhIFBDQSBtYXRyaXggd2l0aCByb3cgbmFtZXMgcmVwcmVzZW50aW5nIHVuaXF1ZSBjZWxsIGlkcyAoZS5nLiwgYmFyY29kZXMpIGFzIGl0cyBwcmltYXJ5IGFyZ3VtZW50LCB3aXRoIGFkZGl0aW9uYWwgYXJndW1lbnRzIGZvciBjbHVzdGVyIHBhcmFtZXRlcnMuIApUaGlzIGZ1bmN0aW9uIHdyYXBzIHRoZSBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX2NsdXN0ZXJzKClgIGZ1bmN0aW9uIGJ1dCBhbGxvd3MgeW91IHRvIHByb3ZpZGUgYSB2ZWN0b3Igb2YgcGFyYW1ldGVyIHZhbHVlcyB0byBwZXJmb3JtIGNsdXN0ZXJpbmcgYWNyb3NzLCBhcyBsaXN0ZWQgYmVsb3cuCkNsdXN0ZXJzIHdpbGwgYmUgY2FsY3VsYXRlZCBmb3IgYWxsIGNvbWJpbmF0aW9ucyBvZiBwYXJhbWV0ZXJzIHZhbHVlcyAod2hlcmUgYXBwbGljYWJsZSk7IGRlZmF1bHQgdmFsdWVzIHRoYXQgdGhlIGZ1bmN0aW9uIHdpbGwgdXNlIGZvciBhbnkgdW5zcGVjaWZpZWQgcGFyYW1ldGVyIHZhbHVlcyBhcmUgc2hvd24gaW4gcGFyZW50aGVzZXMKCiogYGFsZ29yaXRobWA6IFdoaWNoIGNsdXN0ZXJpbmcgYWxnb3JpdGhtIHRvIHVzZSAoTG91dmFpbikKKiBgd2VpZ2h0aW5nYDogV2hpY2ggd2VpZ2h0aW5nIHNjaGVtZSB0byB1c2UgKEphY2NhcmQpCiogYG5uYDogVGhlIG51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyAoMTApCiogYHJlc29sdXRpb25gOiBUaGUgcmVzb2x1dGlvbiBwYXJhbWV0ZXIgKDE7IHVzZWQgb25seSB3aXRoIExvdXZhaW4gYW5kIExlaWRlbiBjbHVzdGVyaW5nKQoqIGBvYmplY3RpdmVfZnVuY3Rpb25gOiBUaGUgb2JqZWN0aXZlIGZ1bmN0aW9uIHRvIG9wdGltaXplIGNsdXN0ZXJzIChDUE07IHVzZWQgb25seSB3aXRoIExlaWRlbiBjbHVzdGVyaW5nKQoKYHJPcGVuU2NQQ0E6OnN3ZWVwX2NsdXN0ZXJzKClgIGRvZXMgYWxsb3cgeW91IHRvIHNwZWNpZnkgdmFsdWVzIGZvciBhbnkgb3RoZXIgcGFyYW1ldGVycy4gCgoKVGhpcyBmdW5jdGlvbiB3aWxsIHJldHVybiBhIGxpc3Qgb2YgZGF0YSBmcmFtZXMgb2YgY2x1c3RlcmluZyByZXN1bHRzLiAKRWFjaCBkYXRhIGZyYW1lIHdpbGwgaGF2ZSB0aGUgZm9sbG93aW5nIGNvbHVtbnM6CgoqIGBjZWxsX2lkYDogVW5pcXVlIGNlbGwgaWRlbnRpZmllcnMsIG9idGFpbmVkIGZyb20gdGhlIFBDQSBtYXRyaXgncyByb3cgbmFtZXMKKiBgY2x1c3RlcmA6IEEgZmFjdG9yIGNvbHVtbiB3aXRoIHRoZSBjbHVzdGVyIGlkZW50aXRpZXMKKiBUaGVyZSB3aWxsIGJlIG9uZSBjb2x1bW4gZm9yIGVhY2ggY2x1c3RlcmluZyBwYXJhbWV0ZXIgdXNlZAoKVG8gZGVtb25zdHJhdGUgdGhpcyBmdW5jdGlvbiwgd2UnbGwgY2FsY3VsYXRlIGNsdXN0ZXJzIHVzaW5nIHRoZSBMb3V2YWluIGFsZ29yaXRobSB3aGlsZSB2YXJ5aW5nIHRoZSBgbm5gIHBhcmFtZXRlcjoKCmBgYHtyIHN3ZWVwIGNsdXN0ZXJzfQojIERlZmluZSBubiBwYXJhbWV0ZXIgdmFsdWVzIG9mIGludGVyZXN0Cm5uX3ZhbHVlcyA8LSBzZXEoMTAsIDMwLCAxMCkKCiMgQ2FsY3VsYXRlIGNsdXN0ZXJzIHZhcnlpbmcgbm4sIGJ1dCBsZWF2aW5nIG90aGVyIHBhcmFtZXRlcnMgYXQgdGhlaXIgZGVmYXVsdCB2YWx1ZXMKY2x1c3Rlcl9yZXN1bHRzX2xpc3QgPC0gck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoCiAgcGNhX21hdHJpeCwKICBubiA9IG5uX3ZhbHVlcwopCmBgYAoKVGhlIHJlc3VsdGluZyBsaXN0IGhhcyBhIGxlbmd0aCBvZiB0aHJlZSwgb25lIGRhdGEgZnJhbWUgZm9yIGVhY2ggYG5uYCBwYXJhbWV0ZXIgdGVzdGVkOgoKYGBge3IgbGVuZ3RoIGNsdXN0ZXJfcmVzdWx0c19saXN0fQpsZW5ndGgoY2x1c3Rlcl9yZXN1bHRzX2xpc3QpCmBgYAoKSXQgY2FuIGJlIGhlbHBmdWwgKGFsdGhvdWdoIGl0IGlzIG5vdCBzdHJpY3RseSBuZWNlc3NhcnkgdG8ga2VlcCB0cmFjaykgdG8gbmFtZSB0aGlzIGxpc3QgYnkgdGhlIHZhcmllZCBgbm5gIHBhcmFtZXRlcjoKCmBgYHtyIHNldCBsaXN0IG5hbWVzfQpuYW1lcyhjbHVzdGVyX3Jlc3VsdHNfbGlzdCkgPC0gZ2x1ZTo6Z2x1ZSgibm5fe25uX3ZhbHVlc30iKQpgYGAKCgpXZSBjYW4gbG9vayBhdCB0aGUgZmlyc3QgZmV3IHJvd3Mgb2YgZWFjaCBkYXRhIGZyYW1lIHVzaW5nIFtgcHVycnI6Om1hcCgpYF0oaHR0cHM6Ly9wdXJyci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9tYXAuaHRtbCkgdG8gaXRlcmF0ZSBvdmVyIHRoZSBsaXN0OgoKCmBgYHtyIG1hcCBjbHVzdGVyX3Jlc3VsdHNfbGlzdH0KY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKGhlYWQpCmBgYAoKR2VuZXJhbGx5IHNwZWFraW5nLCBgcHVycnI6Om1hcCgpYCBjYW4gYmUgdXNlZCB0byBpdGVyYXRlIG92ZXIgdGhpcyBsaXN0IHRvIHZpc3VhbGl6ZSBvciBhbmFseXplIGVhY2ggY2x1c3RlcmluZyByZXN1bHQgb24gaXRzIG93bjsgd2UnbGwgdXNlIHRoaXMgYXBwcm9hY2ggaW4gdGhlIGZvbGxvd2luZyBzZWN0aW9ucy4gCgojIyMgVmlzdWFsaXppbmcgY2x1c3RlcmluZyByZXN1bHRzCgpXaGVuIGNvbXBhcmluZyBjbHVzdGVyaW5nIHJlc3VsdHMsIGl0J3MgaW1wb3J0YW50IHRvIGZpcnN0IHZpc3VhbGl6ZSB0aGUgZGlmZmVyZW50IGNsdXN0ZXJpbmdzIHRvIGJ1aWxkIGNvbnRleHQgZm9yIGludGVycHJldGluZyBRQyBtZXRyaWNzLgoKQXMgb25lIGV4YW1wbGUgb2Ygd2h5IHRoaXMgaXMgaW1wb3J0YW50LCB3ZSBnZW5lcmFsbHkgZXhwZWN0IHRoYXQgbW9yZSByb2J1c3QgY2x1c3RlcnMgd2lsbCBoYXZlIGhpZ2hlciB2YWx1ZXMgZm9yIG1ldHJpY3MgbGlrZSBzaWxob3VldHRlIHdpZHRoIGFuZCBuZWlnaGJvcmhvb2QgcHVyaXR5LgpIb3dldmVyLCB3ZSBhbHNvIGV4cGVjdCB0aGF0IGhhdmluZyBmZXdlciBjbHVzdGVycyBpbiB0aGUgZmlyc3QgcGxhY2Ugd2lsbCBhbHNvIGxlYWQgdG8gaGlnaGVyIG1ldHJpY3MsIHJlZ2FyZGxlc3Mgb2YgY2x1c3RlciBxdWFsaXR5OiBXaGVuIHRoZXJlIGFyZSBmZXdlciBjbHVzdGVycywgaXQgaXMgbW9yZSBsaWtlbHkgdGhhdCBjbHVzdGVycyBvdmVybGFwIGxlc3Mgd2l0aCBvbmUgYW5vdGhlciBqdXN0IGJlY2F1c2UgdGhlcmUgYXJlbid0IG1hbnkgY2x1c3RlcnMgaW4gdGhlIGZpcnN0IHBsYWNlLgpUaGlzIG1lYW5zIHRoYXQsIHdoZW4gaW50ZXJwcmV0aW5nIGNsdXN0ZXIgcXVhbGl0eSBtZXRyaWNzLCB5b3Ugc2hvdWxkIGJlIGNhcmVmdWwgdG8gdGFrZSBtb3JlIGNvbnRleHQgYWJvdXQgdGhlIGRhdGEgaW50byBjb25zaWRlcmF0aW9uIGFuZCBub3Qgb25seSByZWx5IG9uIHRoZSBtZXRyaWMgdmFsdWVzLgoKV2UnbGwgdGhlcmVmb3JlIHZpc3VhbGl6ZSB0aGVzZSByZXN1bHRzIGFzIFVNQVBzIGJ5IGl0ZXJhdGluZyBvdmVyIGBjbHVzdGVyX3Jlc3VsdHNfbGlzdGAgYW5kIGNvbWJpbmluZyBwbG90cyB3aXRoIFtgcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKClgXShodHRwczovL3BhdGNod29yay5kYXRhLWltYWdpbmlzdC5jb20vcmVmZXJlbmNlL3dyYXBfcGxvdHMuaHRtbCkuCldlJ2xsIHNwZWNpZmljYWxseSB1c2UgW2BwdXJycjo6aW1hcCgpYF0oaHR0cHM6Ly9wdXJyci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9pbWFwLmh0bWwpIHRvIGl0ZXJhdGUgc28gdGhhdCB3ZSBjYW4gYXNzaWduIHRoaXMgbGlzdCdzIG5hbWVzIGFzIHBsb3QgdGl0bGVzLgoKRm9yIHRoaXMsIHdlJ2xsIGJlZ2luIGJ5IGV4dHJhY3RpbmcgYSB0YWJsZSBvZiBVTUFQIGNvb3JkaW5hdGVzIGZyb20gb3VyIFNDRSBvYmplY3QuCgpgYGB7ciBjcmVhdGUgdW1hcF9kZn0KdW1hcF9kZiA8LSByZWR1Y2VkRGltKHNjZSwgIlVNQVAiKSB8PgogIGFzLmRhdGEuZnJhbWUoKQpgYGAKCk5leHQsIHdlJ2xsIGl0ZXJhdGUgb3ZlciBgY2x1c3Rlcl9yZXN1bHRzX2xpc3RgIHRvIHBsb3QgdGhlIFVNQVBzLgoKYGBge3IgcGxvdCBubiB1bWFwcywgZmlnLndpZHRoID0gMTJ9CnVtYXBfcGxvdHMgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6aW1hcCgKICAgIFwoY2x1c3Rlcl9kZiwgY2x1c3RlcmluZ19uYW1lKSB7CiAgICAgICMgQWRkIGEgY29sdW1uIHdpdGggY2x1c3RlciBhc3NpZ25tZW50cyB0byB1bWFwX2RmCiAgICAgIHVtYXBfZGZfcGxvdCA8LSB1bWFwX2RmIHw+CiAgICAgICAgZHBseXI6Om11dGF0ZShjbHVzdGVyID0gY2x1c3Rlcl9kZiRjbHVzdGVyKQoKICAgICAgIyBQbG90IHRoZSBVTUFQLCBjb2xvcmVkIGJ5IHRoZSBuZXcgY2x1c3RlciB2YXJpYWJsZQogICAgICBnZ3Bsb3QodW1hcF9kZl9wbG90LCBhZXMoeCA9IFVNQVAxLCB5ID0gVU1BUDIsIGNvbG9yID0gY2x1c3RlcikpICsKICAgICAgICBnZW9tX3BvaW50KGFscGhhID0gMC42KSArCiAgICAgICAgbGFicyh0aXRsZSA9IGdsdWU6OmdsdWUoIm5lYXJlc3QgbmVpZ2hib3JzOiB7Y2x1c3RlcmluZ19uYW1lfSIpKSArCiAgICAgICAgIyBXZSdsbCBhZGQgYSBjb3VwbGUgVU1BUCBwbG90IHNldHRpbmdzIGhlcmUsIGluY2x1ZGluZyBlcXVhbCBheGVzIGFuZAogICAgICAgICMgdHVybmluZyBvZmYgdGhlIGF4aXMgdGlja3MgYW5kIHRleHQgc2luY2UgVU1BUCBjb29yZGluYXRlcyBhcmUgbm90IG1lYW5pbmdmdWwKICAgICAgICBjb29yZF9lcXVhbCgpICsKICAgICAgICB0aGVtZSgKICAgICAgICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICBheGlzLnRleHQgPSBlbGVtZW50X2JsYW5rKCkKICAgICAgICApCiAgICB9CiAgKQoKIyBQcmludCB0aGUgcGxvdHMgd2l0aCBwYXRjaHdvcms6OndyYXBfcGxvdHMoKQpwYXRjaHdvcms6OndyYXBfcGxvdHModW1hcF9wbG90cykKYGBgCgpUaGVzZSBwbG90cyBzaG93IHRoYXQgdGhlIG51bWJlciBvZiBjbHVzdGVycyBkZWNyZWFzZXMgYXMgdGhlIG5lYXJlc3QgbmVpZ2hib3JzIHBhcmFtZXRlciBpbmNyZWFzZXMsIHdpdGggYmV0d2VlbiA5LTEzIGNsdXN0ZXJzLgoKCgoKIyMjIEV2YWx1YXRpbmcgY2x1c3RlcmluZyByZXN1bHRzCgpUaGlzIHNlY3Rpb24gd2lsbCB1c2UgYHB1cnJyOjptYXAoKWAgdG8gaXRlcmF0ZSBvdmVyIGVhY2ggY2x1c3RlcmluZyByZXN1bHQgZGF0YSBmcmFtZSB0byBjYWxjdWxhdGUgc2lsaG91ZXR0ZSB3aWR0aCwgbmVpZ2hib3Job29kIHB1cml0eSwgYW5kIHN0YWJpbGl0eSwgYW5kIHRoZW4gdmlzdWFsaXplIHJlc3VsdHMuClRoZSBnb2FsIG9mIHRoaXMgY29kZSBpcyB0byBpZGVudGlmeSB3aGV0aGVyIG9uZSBjbHVzdGVyaW5nIHBhcmFtZXRlcml6YXRpb24gcHJvZHVjZXMgbW9yZSByZWxpYWJsZSBjbHVzdGVycy4gCgoKIyMjIyBTaWxob3VldHRlIHdpZHRoIGFuZCBuZWlnaGJvcmhvb2QgcHVyaXR5CgpCb3RoIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkgYXJlIGNlbGwtbGV2ZWwgcXVhbnRpdGllcywgc28gd2UgY2FuIGNhbGN1bGF0ZSB0aGVtIHRvZ2V0aGVyIGluIHRoZSBzYW1lIGNhbGwgdG8gYHB1cnJyOjptYXAoKWAuCkJlbG93LCB3ZSdsbCBpdGVyYXRlIG92ZXIgZWFjaCBkYXRhIGZyYW1lIGluIGBjbHVzdGVyX3Jlc3VsdHNfbGlzdGAgdG8gY2FsY3VsYXRlIHRoZXNlIHF1YW50aXRpZXMuCgpgYGB7ciBjYWxjdWxhdGUgY2VsbCBsZXZlbCBtZXRyaWNzfQpjZWxsX21ldHJpY19saXN0IDwtIGNsdXN0ZXJfcmVzdWx0c19saXN0IHw+CiAgcHVycnI6Om1hcCgKICAgIFwoY2x1c3Rlcl9kZikgewogICAgICAjIGNhbGN1bGF0ZSBzaWxob3VldHRlIHdpZHRoCiAgICAgIHNpbGhvdWV0dGVfZGYgPC0gck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3NpbGhvdWV0dGUocGNhX21hdHJpeCwgY2x1c3Rlcl9kZikKCiAgICAgICMgY2FsY3VsYXRlIG5laWdoYmhvcmhvb2QgcHVyaXR5CiAgICAgIHB1cml0eV9kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfcHVyaXR5KHBjYV9tYXRyaXgsIGNsdXN0ZXJfZGYpCgogICAgICAjIENvbWJpbmUgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lCiAgICAgIGRwbHlyOjpsZWZ0X2pvaW4oc2lsaG91ZXR0ZV9kZiwgcHVyaXR5X2RmKQogICAgfQogICkKCiMgVmlldyB0aGUgZmlyc3Qgc2l4IHJvd3Mgb2YgZWFjaCBjbHVzdGVyaW5nIHJlc3VsdCdzIGNlbGwtbGV2ZWwgUUMgbWV0cmljcwpwdXJycjo6bWFwKGNlbGxfbWV0cmljX2xpc3QsIGhlYWQpCmBgYAoKClRvIHZpc3VhbGl6ZSB0aGVzZSByZXN1bHRzLCB3ZSBjYW4gY29tYmluZSBhbGwgZGF0YSBmcmFtZXMgaW4gdGhpcyBsaXN0IGludG8gYSBzaW5nbGUgb3ZlcmFsbCBkYXRhIGZyYW1lLCB3aGVyZSB0aGUgZXhpc3RpbmcgYG5uYCBjb2x1bW4gd2lsbCBkaXN0aW5ndWlzaCBhbW9uZyBjb25kaXRpb25zLgoKYGBge3IgY29tYmluZSBjZWxsIG1ldHJpY3MgbGlzdH0KY2VsbF9tZXRyaWNzX2RmIDwtIGRwbHlyOjpiaW5kX3Jvd3MoY2VsbF9tZXRyaWNfbGlzdCkKYGBgCgpXZSBjYW4gdmlzdWFsaXplIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkgZWFjaCB3aXRoIGJveHBsb3RzLCBmb3IgZXhhbXBsZSwgYW5kIHVzZSB0aGUgW2BwYXRjaHdvcmtgXShodHRwczovL3BhdGNod29yay5kYXRhLWltYWdpbmlzdC5jb20vKSBwYWNrYWdlIHRvIHByaW50IHRoZW0gdG9nZXRoZXI6CgoKYGBge3J9CiMgUGxvdCBzaWxob3VldHRlIHdpZHRoCnNpbGhvdWV0dGVfcGxvdCA8LSBnZ3Bsb3QoY2VsbF9tZXRyaWNzX2RmKSArCiAgYWVzKHggPSBhcy5mYWN0b3Iobm4pLCB5ID0gc2lsaG91ZXR0ZV93aWR0aCwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIlNpbGhvdWV0dGUgd2lkdGgiCiAgKQoKCiMgUGxvdCBuZWlnaGJvcmhvb2QgcHVyaXR5IHdpZHRoCnB1cml0eV9wbG90IDwtIGdncGxvdChjZWxsX21ldHJpY3NfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBwdXJpdHksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMiLAogICAgeSA9ICJOZWlnaGJvcmhvb2QgcHVyaXR5IgogICkKCgojIEFkZCB0b2dldGhlciB1c2luZyB0aGUgcGF0Y2h3b3JrIGxpYnJhcnksIHdpdGhvdXQgYSBsZWdlbmQKc2lsaG91ZXR0ZV9wbG90ICsgcHVyaXR5X3Bsb3QgJiB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKV2hpbGUgdGhlcmUgZG9lcyBub3QgYXBwZWFyIHRvIGJlIGEgc2FsaWVudCBkaWZmZXJlbmNlIGFtb25nIHNpbGhvdWV0dGUgd2lkdGggZGlzdHJpYnV0aW9ucywgaXQgZG9lcyBhcHBlYXIgdGhhdCBwdXJpdHkgaXMgaGlnaGVyIHdpdGggYSBoaWdoZXIgbmVhcmVzdCBuZWlnaGJvcnMgcGFyYW1ldGVyLgoKCiMjIyMgU3RhYmlsaXR5CgpOZXh0LCB3ZSdsbCBjYWxjdWxhdGUgc3RhYmlsaXR5IG9uIHRoZSBjbHVzdGVycyB1c2luZyBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgpYCwgc3BlY2lmeWluZyB0aGUgc2FtZSBwYXJhbWV0ZXIgdXNlZCBmb3IgdGhlIG9yaWdpbmFsIGNsdXN0ZXIgY2FsY3VsYXRpb24gYXQgZWFjaCBpdGVyYXRpb24uIAoKYGBge3IgY2FsY3VsYXRlIHN0YWJpbGl0eX0Kc3RhYmlsaXR5X2xpc3QgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgIG5uIDwtIHVuaXF1ZShjbHVzdGVyX2RmJG5uKQoKICAgICAgIyBjYWxjdWxhdGUgc3RhYmlsaXR5LCBwYXNzaW5nIGluIHRoZSBwYXJhbWV0ZXIgdmFsdWUgdXNlZCBmb3IgdGhpcyBpdGVyYXRpb24KICAgICAgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eShwY2FfbWF0cml4LCBjbHVzdGVyX2RmLCBubiA9IG5uKQogICAgfQogICkKYGBgCgpXZSdsbCBhZ2FpbiBjb21iaW5lIHRoZSBvdXRwdXQgb2YgYHN0YWJpbGl0eV9saXN0YCBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUgYW5kIHBsb3QgYGFyaWAgdmFsdWVzIGFjcm9zcyBgbm5gIHBhcmFtZXRlcml6YXRpb25zLgoKCmBgYHtyIGNvbWJpbmUgcGxvdCBzdGFiaWxpdHl9CnN0YWJpbGl0eV9kZiA8LSBkcGx5cjo6YmluZF9yb3dzKHN0YWJpbGl0eV9saXN0KQoKZ2dwbG90KHN0YWJpbGl0eV9kZikgKwogIGFlcyh4ID0gYXMuZmFjdG9yKG5uKSwgeSA9IGFyaSwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIkFkanVzdGVkIFJhbmQgSW5kZXgiCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCkhlcmUsIHdlIHNlZSB0aGF0IGEgbmVhcmVzdCBuZWlnaGJvcnMgdmFsdWUgb2YgMjAgb3IgMzAgbGVhZHMgdG8gbW9yZSBzdGFibGUgY2x1c3RlcmluZyByZXN1bHRzIGNvbXBhcmVkIHRvIDEwLiAKCgojIyBWYXJ5aW5nIG11bHRpcGxlIGNsdXN0ZXJpbmcgcGFyYW1ldGVycwoKVGhlIHByZXZpb3VzIHNlY3Rpb24gZGVtb25zdHJhdGVkIGhvdyB0byBjYWxjdWxhdGUgY2x1c3RlcnMgYW5kIFFDIG1ldHJpY3Mgd2hlbiB2YXJ5aW5nIG9uZSBwYXJhbWV0ZXIsIGJ1dCBpdCBpcyBwb3NzaWJsZSB0byB2YXJ5IG11bHRpcGxlIHBhcmFtZXRlcnMgYXQgb25jZSB3aXRoIGByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygpYC4KSW4gdGhpcyBzZWN0aW9uLCB3ZSdsbCBzaG93IGFuIG92ZXJ2aWV3IG9mIGhvdyB5b3UgbWlnaHQgd3JpdGUgY29kZSB0byB2YXJ5IHR3byBwYXJhbWV0ZXJzIGF0IG9uY2UgKGhlcmUsIG5lYXJlc3QgbmVpZ2hib3JzIGFuZCByZXNvbHV0aW9uIGFzIGV4YW1wbGVzKSBhbmQgdmlzdWFsaXplIHJlc3VsdHMuCgoKYGBge3Igc3dlZXAgdHdvIHBhcmFtZXRlcnN9CiMgRGVmaW5lIHZlY3RvcnMgb2YgcGFyYW1ldGVycyB0byB2YXJ5Cm5uX3ZhbHVlcyA8LSBzZXEoMTAsIDMwLCAxMCkKcmVzX3ZhbHVlcyA8LSBzZXEoNSwgMTUsIDUpIC8gMTAKCgpjbHVzdGVyX3Jlc3VsdHNfbGlzdCA8LSByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygKICBwY2FfbWF0cml4LAogIG5uID0gbm5fdmFsdWVzLAogIHJlc29sdXRpb24gPSByZXNfdmFsdWVzCikKYGBgCgpUaGlzIHJlc3VsdGluZyBsaXN0IG5vdyBoYXMgOSBkaWZmZXJlbnQgY2x1c3RlcmluZyByZXN1bHRzLCBmb3IgZWFjaCBjb21iaW5hdGlvbiBvZiBgbm5fdmFsdWVzYCBhbmQgYHJlc192YWx1ZXNgOgoKCmBgYHtyIGxlbmd0aCBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB0d28gcGFyYW1ldGVyc30KbGVuZ3RoKGNsdXN0ZXJfcmVzdWx0c19saXN0KQpgYGAKCgojIyMgVmlzdWFsaXplIGNsdXN0ZXJzCgpOZXh0LCB3ZSdsbCBpdGVyYXRlIG92ZXIgYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBwbG90IHRoZSBVTUFQcy4KVGhpcyB0aW1lLCB3ZSdsbCB1c2UgYHB1cnJyOjptYXAoKWAgYW5kIHB1bGwgb3V0IHBhcmFtZXRlcnMgZnJvbSBlYWNoIGl0ZXJhdGlvbidzIGBjbHVzdGVyX2RmYCB0byBmb3JtIHRoZSBVTUFQIHBhbmVsIHRpdGxlLgoKYGBge3IgcGxvdCBubiByZXMgdW1hcHMsIGZpZy5oZWlnaHQgPSAxNH0KdW1hcF9wbG90cyA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgIyBBZGQgYSBjb2x1bW4gd2l0aCBjbHVzdGVyIGFzc2lnbm1lbnRzIHRvIHVtYXBfZGYKICAgICAgdW1hcF9kZl9wbG90IDwtIHVtYXBfZGYgfD4KICAgICAgICBkcGx5cjo6bXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyX2RmJGNsdXN0ZXIpCgogICAgICAjIENyZWF0ZSBhIHRpdGxlIGZvciB0aGUgVU1BUCB3aXRoIGJvdGggcGFyYW1ldGVycwogICAgICB1bWFwX3RpdGxlIDwtIGdsdWU6OmdsdWUoCiAgICAgICAgIm5uOiB7dW5pcXVlKGNsdXN0ZXJfZGYkbm4pfTsgcmVzOiB7dW5pcXVlKGNsdXN0ZXJfZGYkcmVzb2x1dGlvbil9IgogICAgICApCgogICAgICAjIFBsb3QgdGhlIFVNQVAsIGNvbG9yZWQgYnkgdGhlIG5ldyBjbHVzdGVyIHZhcmlhYmxlCiAgICAgIGdncGxvdCh1bWFwX2RmX3Bsb3QsIGFlcyh4ID0gVU1BUDEsIHkgPSBVTUFQMiwgY29sb3IgPSBjbHVzdGVyKSkgKwogICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjYpICsKICAgICAgICBsYWJzKHRpdGxlID0gdW1hcF90aXRsZSkgKwogICAgICAgICMgV2UnbGwgYWRkIGEgY291cGxlIFVNQVAtc3BlY2lmaWMgcGxvdCBzZXR0aW5ncwogICAgICAgIGNvb3JkX2VxdWFsKCkgKwogICAgICAgIHRoZW1lKAogICAgICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGV4dCA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iCiAgICAgICAgKQogICAgfQogICkKCiMgUHJpbnQgdGhlIHBsb3RzIHdpdGggcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKCkKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHVtYXBfcGxvdHMpCmBgYAoKCgojIyMgQ2FsY3VsYXRlIGFuZCB2aXN1YWxpemUgUUMgbWV0cmljcwoKVGhpcyBzZWN0aW9uIHByZXNlbnRzIG9uZSBjb2Rpbmcgc3RyYXRlZ3kgdG8gY2FsY3VsYXRlIGFuZCB2aXN1YWxpemUgcmVzdWx0cyB3aGVuIHZhcnlpbmcgdHdvIGNsdXN0ZXJpbmcgcGFyYW1ldGVycy4KSW4gcGFydGljdWxhciwgd2UgdXNlIGZhY2V0aW5nIHRvIGhlbHAgZGlzcGxheSBhbGwgaW5mb3JtYXRpb24gaW4gb25lIHBsb3QsIGJ5IHBsYWNpbmcgbmVhcmVzdCBuZWlnaGJvciB2YWx1ZXMgb24gdGhlIFgtYXhpcyBhbmQgZmFjZXRpbmcgYnkgcmVzb2x1dGlvbiB2YWx1ZXMuClNpbmNlIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoaG9yYm9vZCBwdXJpdHkgY2FsY3VsYXRpb25zIHVzaW5nIGdlbmVyYWxseSBzaW1pbGFyIGNvZGUsIHdlJ2xsIGp1c3Qgc2hvdyBuZWlnaGJvcmhvb2QgcHVyaXR5IGhlcmUuCgojIyMjIE5laWdoYm9yaG9vZCBwdXJpdHkKCkZpcnN0LCB3ZSdsbCBjYWxjdWxhdGUgbmVpZ2hib3Job29kIHB1cml0eSBhbmQgY29tYmluZSByZXN1bHRzIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZS4KCmBgYHtyIGNhbGN1bGF0ZSBwdXJpdHkgdHdvIHBhcmFtZXRlcnN9CnB1cml0eV9kZiA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3B1cml0eShwY2FfbWF0cml4LCBjbHVzdGVyX2RmKQogICAgfQogICkgfD4KICBkcGx5cjo6YmluZF9yb3dzKCkKYGBgCgoKV2UnbGwgYWRkIGEgY29sdW1uIGByZXNvbHV0aW9uX2xhYmVsYCB3aGljaCB3ZSdsbCB1c2UgdG8gaGF2ZSBpbmZvcm1hdGl2ZSBwYW5lbCB0aXRsZXMgaW4gdGhlIGZhY2V0ZWQgZ2dwbG90IHdlIG1ha2UgbmV4dC4KCmBgYHtyIGFkZCByZXNvbHV0aW9uX2xhYmVsIGNvbHVtbn0KcHVyaXR5X2RmIDwtIHB1cml0eV9kZiB8PgogIGRwbHlyOjptdXRhdGUocmVzb2x1dGlvbl9sYWJlbCA9IGdsdWU6OmdsdWUoIlJlc29sdXRpb246IHtyZXNvbHV0aW9ufSIpKQpgYGAKCmBgYHtyIHZpc3VhbGl6ZSBwdXJpdHkgdHdvIHBhcmFtZXRlcnN9CmdncGxvdChwdXJpdHlfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBwdXJpdHksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICAjIGZhY2V0IGJ5IHJlc29sdXRpb24KICBmYWNldF93cmFwKHZhcnMocmVzb2x1dGlvbl9sYWJlbCkpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMiLAogICAgeSA9ICJOZWlnaGJvcmhvb2QgcHVyaXR5IgogICkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgojIyMgU3RhYmlsaXR5CgpTaW1pbGFyIHRvIGFib3ZlLCB3ZSdsbCBjYWxjdWxhdGUgc3RhYmlsaXR5LCBjb21iaW5lIHJlc3VsdHMgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lLCBhZGQgYSBgcmVzb2x1dGlvbl9sYWJlbGAgY29sdW1uIHRvIHN1cHBvcnQgcGxvdCBpbnRlcnByZXRhdGlvbiwgYW5kIGZpbmFsbHkgbWFrZSBvdXIgcGxvdC4KCmBgYHtyIGNhbGN1bGF0ZSBzdGFiaWxpdHkgdHdvIHBhcmFtZXRlcnN9CnN0YWJpbGl0eV9kZiA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgIyBFeHRyYWN0IHBhcmFtZXRlcnMgZm9yIHRoaXMgY2x1c3RlcmluZyByZXN1bHQKICAgICAgbm4gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkbm4pCiAgICAgIHJlc29sdXRpb24gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkcmVzb2x1dGlvbikKCiAgICAgIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zdGFiaWxpdHkoCiAgICAgICAgcGNhX21hdHJpeCwKICAgICAgICBjbHVzdGVyX2RmLAogICAgICAgIG5uID0gbm4sCiAgICAgICAgcmVzb2x1dGlvbiA9IHJlc29sdXRpb24KICAgICAgKQogICAgfQogICkgfD4KICBkcGx5cjo6YmluZF9yb3dzKCkKCnN0YWJpbGl0eV9kZiA8LSBzdGFiaWxpdHlfZGYgfD4KICBkcGx5cjo6bXV0YXRlKHJlc29sdXRpb25fbGFiZWwgPSBnbHVlOjpnbHVlKCJSZXNvbHV0aW9uOiB7cmVzb2x1dGlvbn0iKSkKYGBgCgoKYGBge3IgdmlzdWFsaXplIHN0YWJpbGl0eSB0d28gcGFyYW1ldGVyc30KZ2dwbG90KHN0YWJpbGl0eV9kZikgKwogIGFlcyh4ID0gYXMuZmFjdG9yKG5uKSwgeSA9IGFyaSwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogICMgZmFjZXQgYnkgcmVzb2x1dGlvbgogIGZhY2V0X3dyYXAodmFycyhyZXNvbHV0aW9uX2xhYmVsKSkgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIkFkanVzdGVkIFJhbmQgSW5kZXgiCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9uIGluZm99CiMgcmVjb3JkIHRoZSB2ZXJzaW9ucyBvZiB0aGUgcGFja2FnZXMgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGFuZCBvdGhlciBlbnZpcm9ubWVudCBpbmZvcm1hdGlvbgpzZXNzaW9uSW5mbygpCmBgYAo=
    +
    LS0tCnRpdGxlOiAiQ29tcGFyaW5nIGNsdXN0ZXJpbmcgcGFyYW1ldGVycyB3aXRoIHJPcGVuU2NQQ0EiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKYXV0aG9yOiAiRGF0YSBMYWIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojIyBJbnRyb2R1Y3Rpb24KCkNsdXN0ZXJpbmcgYWxnb3JpdGhtcyBoYXZlIHNldmVyYWwgcGFyYW1ldGVycyB3aGljaCBjYW4gYmUgdmFyaWVkLCBsZWFkaW5nIHRvIGRpZmZlcmVudCBjbHVzdGVyaW5nIHJlc3VsdHMuCkEga2V5IHF1ZXN0aW9uIHdoZW4gY2x1c3RlcmluZywgdGhlcmVmb3JlLCBpcyBob3cgdG8gaWRlbnRpZnkgYSBzZXQgb2YgcGFyYW1ldGVycyB0aGF0IGxlYWQgdG8gcm9idXN0IGFuZCByZWxpYWJsZSBjbHVzdGVycyB0aGF0IGNhbiBiZSB1c2VkIGluIGRvd25zdHJlYW0gYW5hbHlzaXMuIAoKVGhpcyBub3RlYm9vayBwcm92aWRlcyBleGFtcGxlcyBvZiBob3cgdG8gdXNlIHRoZSBgck9wZW5TY1BDQWAgcGFja2FnZSB0bzoKCiogQ2FsY3VsYXRlIHNldmVyYWwgdmVyc2lvbnMgb2YgY2x1c3RlcmluZyByZXN1bHRzIGFjcm9zcyBzZXZlcmFsIGRpZmZlcmVudCBwYXJhbWV0ZXJpemF0aW9ucwoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGFjcm9zcyBjbHVzdGVyaW5nIHJlc3VsdHMKClBsZWFzZSByZWZlciB0byB0aGUgW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCkgbm90ZWJvb2sgZm9yIGEgdHV0b3JpYWwgb24gdXNpbmcgYHJPcGVuU2NQQ0FgIGZ1bmN0aW9ucyB0bzoKCiogQ2FsY3VsYXRlIGNsdXN0ZXJzIGZyb20gYSBzaW5nbGUgcGFyYW1ldGVyaXphdGlvbgoqIENhbGN1bGF0ZSBRQyBtZXRyaWNzIG9uIGEgc2luZ2xlIHNldCBvZiBjbHVzdGVycywgYXMgd2VsbCBhcyBleHBsYW5hdGlvbnMgb2YgdGhlIG1ldHJpY3MgdGhlbXNlbHZlcwoKVGhpcyBub3RlYm9vayB3aWxsIHVzZSB0aGUgc2FtcGxlIGBTQ1BDUzAwMDAwMWAgZnJvbSBwcm9qZWN0IGBTQ1BDUDAwMDAwMWAsIHdoaWNoIGlzIGFzc3VtZWQgcHJlc2VudCBpbiB0aGUgYE9wZW5TY1BDQS1hbmFseXNpcy9kYXRhL2N1cnJlbnQvU0NQQ1AwMDAwMDFgIGRpcmVjdG9yeSwgZm9yIGFsbCBleGFtcGxlcy4KUGxlYXNlIFtzZWUgdGhpcyBkb2N1bWVudGF0aW9uXShodHRwczovL29wZW5zY3BjYS5yZWFkdGhlZG9jcy5pby9lbi9sYXRlc3QvZ2V0dGluZy1zdGFydGVkL2FjY2Vzc2luZy1yZXNvdXJjZXMvZ2V0dGluZy1hY2Nlc3MtdG8tZGF0YS8pIGZvciBtb3JlIGluZm9ybWF0aW9uIGFib3V0IG9idGFpbmluZyBTY1BDQSBkYXRhLgoKIyMgU2V0dXAKCiMjIyBQYWNrYWdlcwoKCmBgYHtyIHBhY2thZ2VzfQpsaWJyYXJ5KHJPcGVuU2NQQ0EpCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkoU2luZ2xlQ2VsbEV4cGVyaW1lbnQpCiAgbGlicmFyeShnZ3Bsb3QyKQogIGxpYnJhcnkocGF0Y2h3b3JrKQp9KQoKIyBTZXQgZ2dwbG90IHRoZW1lIGZvciBwbG90cwp0aGVtZV9zZXQodGhlbWVfYncoKSkKYGBgCgoKIyMjIFBhdGhzCgpgYGB7ciBiYXNlIHBhdGhzfQojIFRoZSBiYXNlIHBhdGggZm9yIHRoZSBPcGVuU2NQQ0EgcmVwb3NpdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290OjpoYXNfZGlyKCIuZ2l0aHViIikpCgojIFRoZSBjdXJyZW50IGRhdGEgZGlyZWN0b3J5LCBmb3VuZCB3aXRoaW4gdGhlIHJlcG9zaXRvcnkgYmFzZSBkaXJlY3RvcnkKZGF0YV9kaXIgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImRhdGEiLCAiY3VycmVudCIpCgojIFRoZSBwYXRoIHRvIHRoaXMgbW9kdWxlCm1vZHVsZV9iYXNlIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJhbmFseXNlcyIsICJoZWxsby1jbHVzdGVycyIpCmBgYAoKYGBge3IgaW5wdXQgZmlsZSBwYXRofQojIFBhdGggdG8gcHJvY2Vzc2VkIFNDRSBmaWxlIGZvciBzYW1wbGUgU0NQQ1MwMDAwMDEKaW5wdXRfc2NlX2ZpbGUgPC0gZmlsZS5wYXRoKGRhdGFfZGlyLCAiU0NQQ1AwMDAwMDEiLCAiU0NQQ1MwMDAwMDEiLCAiU0NQQ0wwMDAwMDFfcHJvY2Vzc2VkLnJkcyIpCmBgYAoKCiMjIyBTZXQgdGhlIHJhbmRvbSBzZWVkCgpCZWNhdXNlIGNsdXN0ZXJpbmcgaW52b2x2ZXMgcmFuZG9tIHNhbXBsaW5nLCBpdCBpcyBpbXBvcnRhbnQgdG8gc2V0IHRoZSByYW5kb20gc2VlZCBhdCB0aGUgdG9wIG9mIHlvdXIgYW5hbHlzaXMgc2NyaXB0IG9yIG5vdGVib29rIHRvIGVuc3VyZSByZXByb2R1Y2liaWxpdHkuCgpgYGB7ciBzZXQgc2VlZH0Kc2V0LnNlZWQoMjAyNCkKYGBgCgojIyBSZWFkIGluIGFuZCBwcmVwYXJlIGRhdGEKClRvIGJlZ2luLCB3ZSdsbCByZWFkIGluIHRoZSBgU2luZ2xlQ2VsbEV4cGVyaW1lbnRgIChTQ0UpIG9iamVjdC4KCmBgYHtyIHJlYWQgZGF0YX0KIyBSZWFkIHRoZSBTQ0UgZmlsZQpzY2UgPC0gcmVhZFJEUyhpbnB1dF9zY2VfZmlsZSkKYGBgCgpGb3IgdGhlIGluaXRpYWwgY2x1c3RlciBjYWxjdWxhdGlvbnMgYW5kIGV2YWx1YXRpb25zLCB3ZSB3aWxsIHVzZSB0aGUgUENBIG1hdHJpeCBleHRyYWN0ZWQgZnJvbSB0aGUgU0NFIG9iamVjdC4KQXMgc2hvd24gaW4gW2AwMV9wZXJmb3JtLWV2YWx1YXRlLWNsdXN0ZXJpbmcuUm1kYF0oMDFfcGVyZm9ybS1ldmFsdWF0ZS1jbHVzdGVyaW5nLlJtZCksIGl0IGlzIGFsc28gcG9zc2libGUgdG8gdXNlIGFuIFNDRSBvYmplY3Qgb3IgYSBTZXVyYXQgb2JqZWN0IGRpcmVjdGx5LgoKCmBgYHtyIGV4dHJhY3QgcGNhIGRhdGF9CiMgRXh0cmFjdCB0aGUgUENBIG1hdHJpeCBmcm9tIGFuIFNDRSBvYmplY3QKcGNhX21hdHJpeCA8LSByZWR1Y2VkRGltKHNjZSwgIlBDQSIpCmBgYAoKIyMgVmFyeWluZyBhIHNpbmdsZSBjbHVzdGVyaW5nIHBhcmFtZXRlcgoKVGhpcyBzZWN0aW9uIHdpbGwgc2hvdyBob3cgdG8gcGVyZm9ybSBjbHVzdGVyaW5nIGFjcm9zcyBhIHNldCBvZiBwYXJhbWV0ZXJzIChha2EsICJzd2VlcCIgYSBzZXQgb2YgcGFyYW1ldGVycykgd2l0aCBgck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoKWAuIAoKVGhpcyBmdW5jdGlvbiB0YWtlcyBhIFBDQSBtYXRyaXggd2l0aCByb3cgbmFtZXMgcmVwcmVzZW50aW5nIHVuaXF1ZSBjZWxsIGlkcyAoZS5nLiwgYmFyY29kZXMpIGFzIGl0cyBwcmltYXJ5IGFyZ3VtZW50LCB3aXRoIGFkZGl0aW9uYWwgYXJndW1lbnRzIGZvciBjbHVzdGVyIHBhcmFtZXRlcnMuIApUaGlzIGZ1bmN0aW9uIHdyYXBzIHRoZSBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX2NsdXN0ZXJzKClgIGZ1bmN0aW9uIGJ1dCBhbGxvd3MgeW91IHRvIHByb3ZpZGUgYSB2ZWN0b3Igb2YgcGFyYW1ldGVyIHZhbHVlcyB0byBwZXJmb3JtIGNsdXN0ZXJpbmcgYWNyb3NzLCBhcyBsaXN0ZWQgYmVsb3cuCkNsdXN0ZXJzIHdpbGwgYmUgY2FsY3VsYXRlZCBmb3IgYWxsIGNvbWJpbmF0aW9ucyBvZiBwYXJhbWV0ZXJzIHZhbHVlcyAod2hlcmUgYXBwbGljYWJsZSk7IGRlZmF1bHQgdmFsdWVzIHRoYXQgdGhlIGZ1bmN0aW9uIHdpbGwgdXNlIGZvciBhbnkgdW5zcGVjaWZpZWQgcGFyYW1ldGVyIHZhbHVlcyBhcmUgc2hvd24gaW4gcGFyZW50aGVzZXMuCgoqIGBhbGdvcml0aG1gOiBXaGljaCBjbHVzdGVyaW5nIGFsZ29yaXRobSB0byB1c2UgKExvdXZhaW4pCiogYHdlaWdodGluZ2A6IFdoaWNoIHdlaWdodGluZyBzY2hlbWUgdG8gdXNlIChKYWNjYXJkKQoqIGBubmA6IFRoZSBudW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMgKDEwKQoqIGByZXNvbHV0aW9uYDogVGhlIHJlc29sdXRpb24gcGFyYW1ldGVyICgxOyB1c2VkIG9ubHkgd2l0aCBMb3V2YWluIGFuZCBMZWlkZW4gY2x1c3RlcmluZykKKiBgb2JqZWN0aXZlX2Z1bmN0aW9uYDogVGhlIG9iamVjdGl2ZSBmdW5jdGlvbiB0byBvcHRpbWl6ZSBjbHVzdGVycyAoQ1BNOyB1c2VkIG9ubHkgd2l0aCBMZWlkZW4gY2x1c3RlcmluZykKCmByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygpYCBkb2VzIG5vdCBhbGxvdyB5b3UgdG8gc3BlY2lmeSB2YWx1ZXMgZm9yIGFueSBvdGhlciBwYXJhbWV0ZXJzLiAKCgpUaGlzIGZ1bmN0aW9uIHdpbGwgcmV0dXJuIGEgbGlzdCBvZiBkYXRhIGZyYW1lcyBvZiBjbHVzdGVyaW5nIHJlc3VsdHMuIApFYWNoIGRhdGEgZnJhbWUgd2lsbCBoYXZlIHRoZSBmb2xsb3dpbmcgY29sdW1uczoKCiogYGNlbGxfaWRgOiBVbmlxdWUgY2VsbCBpZGVudGlmaWVycywgb2J0YWluZWQgZnJvbSB0aGUgUENBIG1hdHJpeCdzIHJvdyBuYW1lcwoqIGBjbHVzdGVyYDogQSBmYWN0b3IgY29sdW1uIHdpdGggdGhlIGNsdXN0ZXIgaWRlbnRpdGllcwoqIFRoZXJlIHdpbGwgYmUgb25lIGNvbHVtbiBmb3IgZWFjaCBjbHVzdGVyaW5nIHBhcmFtZXRlciB1c2VkCgpUbyBkZW1vbnN0cmF0ZSB0aGlzIGZ1bmN0aW9uLCB3ZSdsbCBjYWxjdWxhdGUgY2x1c3RlcnMgdXNpbmcgdGhlIExvdXZhaW4gYWxnb3JpdGhtIHdoaWxlIHZhcnlpbmcgdGhlIGBubmAgcGFyYW1ldGVyOgoKYGBge3Igc3dlZXAgY2x1c3RlcnN9CiMgRGVmaW5lIG5uIHBhcmFtZXRlciB2YWx1ZXMgb2YgaW50ZXJlc3QKbm5fdmFsdWVzIDwtIGMoMTAsIDIwLCAzMCkKCiMgQ2FsY3VsYXRlIGNsdXN0ZXJzIHZhcnlpbmcgbm4sIGJ1dCBsZWF2aW5nIG90aGVyIHBhcmFtZXRlcnMgYXQgdGhlaXIgZGVmYXVsdCB2YWx1ZXMKY2x1c3Rlcl9yZXN1bHRzX2xpc3QgPC0gck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoCiAgcGNhX21hdHJpeCwKICBubiA9IG5uX3ZhbHVlcwopCmBgYAoKVGhlIHJlc3VsdGluZyBsaXN0IGhhcyBhIGxlbmd0aCBvZiB0aHJlZSwgb25lIGRhdGEgZnJhbWUgZm9yIGVhY2ggYG5uYCBwYXJhbWV0ZXIgdGVzdGVkOgoKYGBge3IgbGVuZ3RoIGNsdXN0ZXJfcmVzdWx0c19saXN0fQpsZW5ndGgoY2x1c3Rlcl9yZXN1bHRzX2xpc3QpCmBgYAoKSXQgY2FuIGJlIGhlbHBmdWwgKGFsdGhvdWdoIGl0IGlzIG5vdCBzdHJpY3RseSBuZWNlc3NhcnkgdG8ga2VlcCB0cmFjaykgdG8gbmFtZSB0aGlzIGxpc3QgYnkgdGhlIHZhcmllZCBgbm5gIHBhcmFtZXRlci4KSW4gdGhpcyBjYXNlLCB3ZSdsbCB1c2UgdGhlc2UgbmFtZXMgdG8gbGFiZWwgcGxvdHMuCgpgYGB7ciBzZXQgbGlzdCBuYW1lc30KbmFtZXMoY2x1c3Rlcl9yZXN1bHRzX2xpc3QpIDwtIGdsdWU6OmdsdWUoIm5uX3tubl92YWx1ZXN9IikKYGBgCgoKV2UgY2FuIGxvb2sgYXQgdGhlIGZpcnN0IGZldyByb3dzIG9mIGVhY2ggZGF0YSBmcmFtZSB1c2luZyBbYHB1cnJyOjptYXAoKWBdKGh0dHBzOi8vcHVycnIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvbWFwLmh0bWwpIHRvIGl0ZXJhdGUgb3ZlciB0aGUgbGlzdDoKCgpgYGB7ciBtYXAgY2x1c3Rlcl9yZXN1bHRzX2xpc3R9CmNsdXN0ZXJfcmVzdWx0c19saXN0IHw+CiAgcHVycnI6Om1hcChoZWFkKQpgYGAKCkdlbmVyYWxseSBzcGVha2luZywgYHB1cnJyOjptYXAoKWAgY2FuIGJlIHVzZWQgdG8gaXRlcmF0ZSBvdmVyIHRoaXMgbGlzdCB0byB2aXN1YWxpemUgb3IgYW5hbHl6ZSBlYWNoIGNsdXN0ZXJpbmcgcmVzdWx0IG9uIGl0cyBvd247IHdlJ2xsIHVzZSB0aGlzIGFwcHJvYWNoIGluIHRoZSBmb2xsb3dpbmcgc2VjdGlvbnMuIAoKIyMjIFZpc3VhbGl6aW5nIGNsdXN0ZXJpbmcgcmVzdWx0cwoKV2hlbiBjb21wYXJpbmcgY2x1c3RlcmluZyByZXN1bHRzLCBpdCdzIGltcG9ydGFudCB0byBmaXJzdCB2aXN1YWxpemUgdGhlIGRpZmZlcmVudCBjbHVzdGVyaW5ncyB0byBidWlsZCBjb250ZXh0IGZvciBpbnRlcnByZXRpbmcgUUMgbWV0cmljcy4KCkFzIG9uZSBleGFtcGxlIG9mIHdoeSB0aGlzIGlzIGltcG9ydGFudCwgd2UgZ2VuZXJhbGx5IGV4cGVjdCB0aGF0IG1vcmUgcm9idXN0IGNsdXN0ZXJzIHdpbGwgaGF2ZSBoaWdoZXIgdmFsdWVzIGZvciBtZXRyaWNzIGxpa2Ugc2lsaG91ZXR0ZSB3aWR0aCBhbmQgbmVpZ2hib3Job29kIHB1cml0eS4KSG93ZXZlciwgd2UgYWxzbyBleHBlY3QgdGhhdCBoYXZpbmcgZmV3ZXIgY2x1c3RlcnMgaW4gdGhlIGZpcnN0IHBsYWNlIHdpbGwgYWxzbyBsZWFkIHRvIGhpZ2hlciBtZXRyaWNzLCByZWdhcmRsZXNzIG9mIGNsdXN0ZXIgcXVhbGl0eTogV2hlbiB0aGVyZSBhcmUgZmV3ZXIgY2x1c3RlcnMsIGl0IGlzIG1vcmUgbGlrZWx5IHRoYXQgY2x1c3RlcnMgb3ZlcmxhcCBsZXNzIHdpdGggb25lIGFub3RoZXIganVzdCBiZWNhdXNlIHRoZXJlIGFyZW4ndCBtYW55IGNsdXN0ZXJzIGluIHRoZSBmaXJzdCBwbGFjZS4KVGhpcyBtZWFucyB0aGF0LCB3aGVuIGludGVycHJldGluZyBjbHVzdGVyIHF1YWxpdHkgbWV0cmljcywgeW91IHNob3VsZCBiZSBjYXJlZnVsIHRvIHRha2UgbW9yZSBjb250ZXh0IGFib3V0IHRoZSBkYXRhIGludG8gY29uc2lkZXJhdGlvbiBhbmQgbm90IG9ubHkgcmVseSBvbiB0aGUgbWV0cmljIHZhbHVlcy4KCldlJ2xsIHRoZXJlZm9yZSB2aXN1YWxpemUgdGhlc2UgcmVzdWx0cyBhcyBVTUFQcyBieSBpdGVyYXRpbmcgb3ZlciBgY2x1c3Rlcl9yZXN1bHRzX2xpc3RgIGFuZCBjb21iaW5pbmcgcGxvdHMgd2l0aCBbYHBhdGNod29yazo6d3JhcF9wbG90cygpYF0oaHR0cHM6Ly9wYXRjaHdvcmsuZGF0YS1pbWFnaW5pc3QuY29tL3JlZmVyZW5jZS93cmFwX3Bsb3RzLmh0bWwpLgpXZSdsbCBzcGVjaWZpY2FsbHkgdXNlIFtgcHVycnI6OmltYXAoKWBdKGh0dHBzOi8vcHVycnIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvaW1hcC5odG1sKSB0byBpdGVyYXRlIHNvIHRoYXQgd2UgY2FuIGFzc2lnbiB0aGlzIGxpc3QncyBuYW1lcyBhcyBwbG90IHRpdGxlcy4KCkZvciB0aGlzLCB3ZSdsbCBiZWdpbiBieSBleHRyYWN0aW5nIGEgdGFibGUgb2YgVU1BUCBjb29yZGluYXRlcyBmcm9tIG91ciBTQ0Ugb2JqZWN0LgoKYGBge3IgY3JlYXRlIHVtYXBfZGZ9CnVtYXBfZGYgPC0gcmVkdWNlZERpbShzY2UsICJVTUFQIikgfD4KICBhcy5kYXRhLmZyYW1lKCkKYGBgCgpOZXh0LCB3ZSdsbCBpdGVyYXRlIG92ZXIgYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBwbG90IHRoZSBVTUFQcy4KCmBgYHtyIHBsb3Qgbm4gdW1hcHMsIGZpZy53aWR0aCA9IDEyfQp1bWFwX3Bsb3RzIDwtIGNsdXN0ZXJfcmVzdWx0c19saXN0IHw+CiAgcHVycnI6OmltYXAoCiAgICBcKGNsdXN0ZXJfZGYsIGNsdXN0ZXJpbmdfbmFtZSkgewogICAgICAjIEFkZCBhIGNvbHVtbiB3aXRoIGNsdXN0ZXIgYXNzaWdubWVudHMgdG8gdW1hcF9kZgogICAgICB1bWFwX2RmX3Bsb3QgPC0gdW1hcF9kZiB8PgogICAgICAgIGRwbHlyOjptdXRhdGUoY2x1c3RlciA9IGNsdXN0ZXJfZGYkY2x1c3RlcikKCiAgICAgICMgUGxvdCB0aGUgVU1BUCwgY29sb3JlZCBieSB0aGUgbmV3IGNsdXN0ZXIgdmFyaWFibGUKICAgICAgZ2dwbG90KHVtYXBfZGZfcGxvdCwgYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGNsdXN0ZXIpKSArCiAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNikgKwogICAgICAgIGxhYnModGl0bGUgPSBnbHVlOjpnbHVlKCJuZWFyZXN0IG5laWdoYm9yczoge2NsdXN0ZXJpbmdfbmFtZX0iKSkgKwogICAgICAgICMgV2UnbGwgYWRkIGEgY291cGxlIFVNQVAgcGxvdCBzZXR0aW5ncyBoZXJlLCBpbmNsdWRpbmcgZXF1YWwgYXhlcyBhbmQKICAgICAgICAjIHR1cm5pbmcgb2ZmIHRoZSBheGlzIHRpY2tzIGFuZCB0ZXh0IHNpbmNlIFVNQVAgY29vcmRpbmF0ZXMgYXJlIG5vdCBtZWFuaW5nZnVsCiAgICAgICAgY29vcmRfZXF1YWwoKSArCiAgICAgICAgdGhlbWUoCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpCiAgICAgICAgKQogICAgfQogICkKCiMgUHJpbnQgdGhlIHBsb3RzIHdpdGggcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKCkKcGF0Y2h3b3JrOjp3cmFwX3Bsb3RzKHVtYXBfcGxvdHMsIG5jb2wgPSAzKQpgYGAKClRoZXNlIHBsb3RzIHNob3cgdGhhdCB0aGUgbnVtYmVyIG9mIGNsdXN0ZXJzIGRlY3JlYXNlcyBhcyB0aGUgbmVhcmVzdCBuZWlnaGJvcnMgcGFyYW1ldGVyIGluY3JlYXNlcywgd2l0aCBiZXR3ZWVuIDktMTMgY2x1c3RlcnMuCgoKCgojIyMgRXZhbHVhdGluZyBjbHVzdGVyaW5nIHJlc3VsdHMKClRoaXMgc2VjdGlvbiB3aWxsIHVzZSBgcHVycnI6Om1hcCgpYCB0byBpdGVyYXRlIG92ZXIgZWFjaCBjbHVzdGVyaW5nIHJlc3VsdCBkYXRhIGZyYW1lIHRvIGNhbGN1bGF0ZSBzaWxob3VldHRlIHdpZHRoLCBuZWlnaGJvcmhvb2QgcHVyaXR5LCBhbmQgc3RhYmlsaXR5LCBhbmQgdGhlbiB2aXN1YWxpemUgcmVzdWx0cy4KVGhlIGdvYWwgb2YgdGhpcyBjb2RlIGlzIHRvIGlkZW50aWZ5IHdoZXRoZXIgb25lIGNsdXN0ZXJpbmcgcGFyYW1ldGVyaXphdGlvbiBwcm9kdWNlcyBtb3JlIHJlbGlhYmxlIGNsdXN0ZXJzLiAKCgojIyMjIFNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkKCkJvdGggc2lsaG91ZXR0ZSB3aWR0aCBhbmQgbmVpZ2hib3Job29kIHB1cml0eSBhcmUgY2VsbC1sZXZlbCBxdWFudGl0aWVzLCBzbyB3ZSBjYW4gY2FsY3VsYXRlIHRoZW0gdG9nZXRoZXIgaW4gdGhlIHNhbWUgY2FsbCB0byBgcHVycnI6Om1hcCgpYC4KQmVsb3csIHdlJ2xsIGl0ZXJhdGUgb3ZlciBlYWNoIGRhdGEgZnJhbWUgaW4gYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBjYWxjdWxhdGUgdGhlc2UgcXVhbnRpdGllcy4KCmBgYHtyIGNhbGN1bGF0ZSBjZWxsIGxldmVsIG1ldHJpY3N9CmNlbGxfbWV0cmljX2xpc3QgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgICMgY2FsY3VsYXRlIHNpbGhvdWV0dGUgd2lkdGgKICAgICAgc2lsaG91ZXR0ZV9kZiA8LSByT3BlblNjUENBOjpjYWxjdWxhdGVfc2lsaG91ZXR0ZShwY2FfbWF0cml4LCBjbHVzdGVyX2RmKQoKICAgICAgIyBjYWxjdWxhdGUgbmVpZ2hiaG9yaG9vZCBwdXJpdHksIHN0YXJ0aW5nIGZyb20gc2lsaG91ZXR0ZV9kZgogICAgICByT3BlblNjUENBOjpjYWxjdWxhdGVfcHVyaXR5KHBjYV9tYXRyaXgsIHNpbGhvdWV0dGVfZGYpCiAgICB9CiAgKQoKIyBWaWV3IHRoZSBmaXJzdCBzaXggcm93cyBvZiBlYWNoIGNsdXN0ZXJpbmcgcmVzdWx0J3MgY2VsbC1sZXZlbCBRQyBtZXRyaWNzCnB1cnJyOjptYXAoY2VsbF9tZXRyaWNfbGlzdCwgaGVhZCkKYGBgCgoKVG8gdmlzdWFsaXplIHRoZXNlIHJlc3VsdHMsIHdlIGNhbiBjb21iaW5lIGFsbCBkYXRhIGZyYW1lcyBpbiB0aGlzIGxpc3QgaW50byBhIHNpbmdsZSBvdmVyYWxsIGRhdGEgZnJhbWUsIHdoZXJlIHRoZSBleGlzdGluZyBgbm5gIGNvbHVtbiB3aWxsIGRpc3Rpbmd1aXNoIGFtb25nIGNvbmRpdGlvbnMuCgpgYGB7ciBjb21iaW5lIGNlbGwgbWV0cmljcyBsaXN0fQpjZWxsX21ldHJpY3NfZGYgPC0gcHVycnI6Omxpc3RfcmJpbmQoY2VsbF9tZXRyaWNfbGlzdCkKYGBgCgpXZSBjYW4gdmlzdWFsaXplIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoYm9yaG9vZCBwdXJpdHkgZWFjaCB3aXRoIGJveHBsb3RzLCBmb3IgZXhhbXBsZSwgYW5kIHVzZSB0aGUgW2BwYXRjaHdvcmtgXShodHRwczovL3BhdGNod29yay5kYXRhLWltYWdpbmlzdC5jb20vKSBwYWNrYWdlIHRvIHByaW50IHRoZW0gdG9nZXRoZXI6CgoKYGBge3J9CiMgUGxvdCBzaWxob3VldHRlIHdpZHRoCnNpbGhvdWV0dGVfcGxvdCA8LSBnZ3Bsb3QoY2VsbF9tZXRyaWNzX2RmKSArCiAgYWVzKHggPSBhcy5mYWN0b3Iobm4pLCB5ID0gc2lsaG91ZXR0ZV93aWR0aCwgZmlsbCA9IGFzLmZhY3RvcihubikpICsKICBnZW9tX2JveHBsb3QoKSArCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICJQYXN0ZWwyIikgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIlNpbGhvdWV0dGUgd2lkdGgiCiAgKQoKCiMgUGxvdCBuZWlnaGJvcmhvb2QgcHVyaXR5IHdpZHRoCnB1cml0eV9wbG90IDwtIGdncGxvdChjZWxsX21ldHJpY3NfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBwdXJpdHksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMiLAogICAgeSA9ICJOZWlnaGJvcmhvb2QgcHVyaXR5IgogICkKCgojIEFkZCB0b2dldGhlciB1c2luZyB0aGUgcGF0Y2h3b3JrIGxpYnJhcnksIHdpdGhvdXQgYSBsZWdlbmQKc2lsaG91ZXR0ZV9wbG90ICsgcHVyaXR5X3Bsb3QgJiB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKV2hpbGUgdGhlcmUgZG9lcyBub3QgYXBwZWFyIHRvIGJlIGEgc2FsaWVudCBkaWZmZXJlbmNlIGFtb25nIHNpbGhvdWV0dGUgd2lkdGggZGlzdHJpYnV0aW9ucywgaXQgZG9lcyBhcHBlYXIgdGhhdCBwdXJpdHkgaXMgaGlnaGVyIHdpdGggYSBoaWdoZXIgbmVhcmVzdCBuZWlnaGJvcnMgcGFyYW1ldGVyLgpJdCdzIHdvcnRoIG5vdGluZyB0aGF0IHRoaXMgdHJlbmQgaW4gcHVyaXR5IHZhbHVlcyBpcyBleHBlY3RlZDogSGlnaGVyIG5lYXJlc3QgbmVpZ2hib3IgcGFyYW1ldGVyIHZhbHVlcyBsZWFkIHRvIGZld2VyIGNsdXN0ZXJzLCBhbmQgbmVpZ2hib3Job29kIHB1cml0eSB0ZW5kcyB0byBiZSBoaWdoZXIgd2hlbiB0aGVyZSBhcmUgZmV3ZXIgY2x1c3RlcnMuIAoKCiMjIyMgU3RhYmlsaXR5CgpOZXh0LCB3ZSdsbCBjYWxjdWxhdGUgc3RhYmlsaXR5IG9uIHRoZSBjbHVzdGVycyB1c2luZyBgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eSgpYCwgc3BlY2lmeWluZyB0aGUgc2FtZSBwYXJhbWV0ZXIgdXNlZCBmb3IgdGhlIG9yaWdpbmFsIGNsdXN0ZXIgY2FsY3VsYXRpb24gYXQgZWFjaCBpdGVyYXRpb24uIAoKYGBge3IgY2FsY3VsYXRlIHN0YWJpbGl0eX0Kc3RhYmlsaXR5X2xpc3QgPC0gY2x1c3Rlcl9yZXN1bHRzX2xpc3QgfD4KICBwdXJycjo6bWFwKAogICAgXChjbHVzdGVyX2RmKSB7CiAgICAgIG5uIDwtIGNsdXN0ZXJfZGYkbm5bMV0gIyBhbGwgcm93cyBoYXZlIHRoZSBzYW1lIGBubmAgcGFyYW1ldGVyLCBzbyB3ZSdsbCB0YWtlIHRoZSBmaXJzdAoKICAgICAgIyBjYWxjdWxhdGUgc3RhYmlsaXR5LCBwYXNzaW5nIGluIHRoZSBwYXJhbWV0ZXIgdmFsdWUgdXNlZCBmb3IgdGhpcyBpdGVyYXRpb24KICAgICAgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3N0YWJpbGl0eShwY2FfbWF0cml4LCBjbHVzdGVyX2RmLCBubiA9IG5uKQogICAgfQogICkKYGBgCgpXZSdsbCBhZ2FpbiBjb21iaW5lIHRoZSBvdXRwdXQgb2YgYHN0YWJpbGl0eV9saXN0YCBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUgYW5kIHBsb3QgYGFyaWAgdmFsdWVzIGFjcm9zcyBgbm5gIHBhcmFtZXRlcml6YXRpb25zLgoKCmBgYHtyIGNvbWJpbmUgcGxvdCBzdGFiaWxpdHl9CnN0YWJpbGl0eV9kZiA8LSBwdXJycjo6bGlzdF9yYmluZChzdGFiaWxpdHlfbGlzdCkKCmdncGxvdChzdGFiaWxpdHlfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBhcmksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMiLAogICAgeSA9ICJBZGp1c3RlZCBSYW5kIEluZGV4IgogICkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgpIZXJlLCB3ZSBzZWUgdGhhdCBhIG5lYXJlc3QgbmVpZ2hib3JzIHZhbHVlIG9mIDIwIG9yIDMwIGxlYWRzIHRvIG1vcmUgc3RhYmxlIGNsdXN0ZXJpbmcgcmVzdWx0cyBjb21wYXJlZCB0byAxMC4gCgoKIyMgVmFyeWluZyBtdWx0aXBsZSBjbHVzdGVyaW5nIHBhcmFtZXRlcnMKClRoZSBwcmV2aW91cyBzZWN0aW9uIGRlbW9uc3RyYXRlZCBob3cgdG8gY2FsY3VsYXRlIGNsdXN0ZXJzIGFuZCBRQyBtZXRyaWNzIHdoZW4gdmFyeWluZyBvbmUgcGFyYW1ldGVyLCBidXQgaXQgaXMgcG9zc2libGUgdG8gdmFyeSBtdWx0aXBsZSBwYXJhbWV0ZXJzIGF0IG9uY2Ugd2l0aCBgck9wZW5TY1BDQTo6c3dlZXBfY2x1c3RlcnMoKWAuCkluIHRoaXMgc2VjdGlvbiwgd2UnbGwgc2hvdyBhbiBvdmVydmlldyBvZiBob3cgeW91IG1pZ2h0IHdyaXRlIGNvZGUgdG8gdmFyeSB0d28gcGFyYW1ldGVycyBhdCBvbmNlIChoZXJlLCBuZWFyZXN0IG5laWdoYm9ycyBhbmQgcmVzb2x1dGlvbiBhcyBleGFtcGxlcykgYW5kIHZpc3VhbGl6ZSByZXN1bHRzLgoKCmBgYHtyIHN3ZWVwIHR3byBwYXJhbWV0ZXJzfQojIERlZmluZSB2ZWN0b3JzIG9mIHBhcmFtZXRlcnMgdG8gdmFyeQpubl92YWx1ZXMgPC0gYygxMCwgMjAsIDMwKQpyZXNfdmFsdWVzIDwtIGMoMC41LCAxLjAsIDEuNSkKCgpjbHVzdGVyX3Jlc3VsdHNfbGlzdCA8LSByT3BlblNjUENBOjpzd2VlcF9jbHVzdGVycygKICBwY2FfbWF0cml4LAogIG5uID0gbm5fdmFsdWVzLAogIHJlc29sdXRpb24gPSByZXNfdmFsdWVzCikKYGBgCgpUaGlzIHJlc3VsdGluZyBsaXN0IG5vdyBoYXMgOSBkaWZmZXJlbnQgY2x1c3RlcmluZyByZXN1bHRzLCBmb3IgZWFjaCBjb21iaW5hdGlvbiBvZiBgbm5fdmFsdWVzYCBhbmQgYHJlc192YWx1ZXNgOgoKCmBgYHtyIGxlbmd0aCBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB0d28gcGFyYW1ldGVyc30KbGVuZ3RoKGNsdXN0ZXJfcmVzdWx0c19saXN0KQpgYGAKCgojIyMgVmlzdWFsaXplIGNsdXN0ZXJzCgpOZXh0LCB3ZSdsbCBpdGVyYXRlIG92ZXIgYGNsdXN0ZXJfcmVzdWx0c19saXN0YCB0byBwbG90IHRoZSBVTUFQcy4KVGhpcyB0aW1lLCB3ZSdsbCB1c2UgYHB1cnJyOjptYXAoKWAgYW5kIHB1bGwgb3V0IHBhcmFtZXRlcnMgZnJvbSBlYWNoIGl0ZXJhdGlvbidzIGBjbHVzdGVyX2RmYCB0byBmb3JtIHRoZSBVTUFQIHBhbmVsIHRpdGxlLgoKYGBge3IgcGxvdCBubiByZXMgdW1hcHMsIGZpZy5oZWlnaHQgPSAxNH0KdW1hcF9wbG90cyA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgIyBBZGQgYSBjb2x1bW4gd2l0aCBjbHVzdGVyIGFzc2lnbm1lbnRzIHRvIHVtYXBfZGYKICAgICAgdW1hcF9kZl9wbG90IDwtIHVtYXBfZGYgfD4KICAgICAgICBkcGx5cjo6bXV0YXRlKGNsdXN0ZXIgPSBjbHVzdGVyX2RmJGNsdXN0ZXIpCgogICAgICAjIENyZWF0ZSBhIHRpdGxlIGZvciB0aGUgVU1BUCB3aXRoIGJvdGggcGFyYW1ldGVycwogICAgICB1bWFwX3RpdGxlIDwtIGdsdWU6OmdsdWUoCiAgICAgICAgIm5uOiB7Y2x1c3Rlcl9kZiRublsxXX07IHJlczoge2NsdXN0ZXJfZGYkcmVzb2x1dGlvblsxXX0iCiAgICAgICkKCiAgICAgICMgUGxvdCB0aGUgVU1BUCwgY29sb3JlZCBieSB0aGUgbmV3IGNsdXN0ZXIgdmFyaWFibGUKICAgICAgZ2dwbG90KHVtYXBfZGZfcGxvdCwgYWVzKHggPSBVTUFQMSwgeSA9IFVNQVAyLCBjb2xvciA9IGNsdXN0ZXIpKSArCiAgICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNikgKwogICAgICAgIGxhYnModGl0bGUgPSB1bWFwX3RpdGxlKSArCiAgICAgICAgIyBXZSdsbCBhZGQgYSBjb3VwbGUgVU1BUC1zcGVjaWZpYyBwbG90IHNldHRpbmdzCiAgICAgICAgY29vcmRfZXF1YWwoKSArCiAgICAgICAgdGhlbWUoCiAgICAgICAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgIyBFbnN1cmUgbGVnZW5kcyBmaXQgaW4gdGhlIGZpZ3VyZQogICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsCiAgICAgICAgICBsZWdlbmQua2V5LnNpemUgPSB1bml0KDAuMiwgImNtIikKICAgICAgICApCiAgICB9CiAgKQoKIyBQcmludCB0aGUgcGxvdHMgd2l0aCBwYXRjaHdvcms6OndyYXBfcGxvdHMoKQpwYXRjaHdvcms6OndyYXBfcGxvdHModW1hcF9wbG90cywgbmNvbCA9IDMpCmBgYAoKCgojIyMgQ2FsY3VsYXRlIGFuZCB2aXN1YWxpemUgUUMgbWV0cmljcwoKVGhpcyBzZWN0aW9uIHByZXNlbnRzIG9uZSBjb2Rpbmcgc3RyYXRlZ3kgdG8gY2FsY3VsYXRlIGFuZCB2aXN1YWxpemUgcmVzdWx0cyB3aGVuIHZhcnlpbmcgdHdvIGNsdXN0ZXJpbmcgcGFyYW1ldGVycy4KSW4gcGFydGljdWxhciwgd2UgdXNlIGZhY2V0aW5nIHRvIGhlbHAgZGlzcGxheSBhbGwgaW5mb3JtYXRpb24gaW4gb25lIHBsb3QsIGJ5IHBsYWNpbmcgbmVhcmVzdCBuZWlnaGJvciB2YWx1ZXMgb24gdGhlIFgtYXhpcyBhbmQgZmFjZXRpbmcgYnkgcmVzb2x1dGlvbiB2YWx1ZXMuClNpbmNlIHNpbGhvdWV0dGUgd2lkdGggYW5kIG5laWdoaG9yYm9vZCBwdXJpdHkgY2FsY3VsYXRpb25zIHVzaW5nIGdlbmVyYWxseSBzaW1pbGFyIGNvZGUsIHdlJ2xsIGp1c3Qgc2hvdyBuZWlnaGJvcmhvb2QgcHVyaXR5IGhlcmUuCgojIyMjIE5laWdoYm9yaG9vZCBwdXJpdHkKCkZpcnN0LCB3ZSdsbCBjYWxjdWxhdGUgbmVpZ2hib3Job29kIHB1cml0eSBhbmQgY29tYmluZSByZXN1bHRzIGludG8gYSBzaW5nbGUgZGF0YSBmcmFtZS4KCmBgYHtyIGNhbGN1bGF0ZSBwdXJpdHkgdHdvIHBhcmFtZXRlcnN9CnB1cml0eV9kZiA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgck9wZW5TY1BDQTo6Y2FsY3VsYXRlX3B1cml0eShwY2FfbWF0cml4LCBjbHVzdGVyX2RmKQogICAgfQogICkgfD4KICBwdXJycjo6bGlzdF9yYmluZCgpCmBgYAoKCmBgYHtyIHZpc3VhbGl6ZSBwdXJpdHkgdHdvIHBhcmFtZXRlcnN9CmdncGxvdChwdXJpdHlfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBwdXJpdHksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICAjIGZhY2V0IGJ5IHJlc29sdXRpb24sIGxhYmVsaW5nIHBhbmVscyB3aXRoIGJvdGggdGhlIHJlc29sdXRpb24gY29sdW1uIG5hbWUgYW5kIHZhbHVlCiAgZmFjZXRfd3JhcCh2YXJzKHJlc29sdXRpb24pLCBsYWJlbGxlciA9IGxhYmVsX2JvdGgpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgbmVhcmVzdCBuZWlnaGJvcnMiLAogICAgeSA9ICJOZWlnaGJvcmhvb2QgcHVyaXR5IgogICkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKYGBgCgojIyMgU3RhYmlsaXR5CgpTaW1pbGFyIHRvIGFib3ZlLCB3ZSdsbCBjYWxjdWxhdGUgc3RhYmlsaXR5LCBjb21iaW5lIHJlc3VsdHMgaW50byBhIHNpbmdsZSBkYXRhIGZyYW1lLCBhZGQgYSBgcmVzb2x1dGlvbl9sYWJlbGAgY29sdW1uIHRvIHN1cHBvcnQgcGxvdCBpbnRlcnByZXRhdGlvbiwgYW5kIGZpbmFsbHkgbWFrZSBvdXIgcGxvdC4KCmBgYHtyIGNhbGN1bGF0ZSBzdGFiaWxpdHkgdHdvIHBhcmFtZXRlcnN9CnN0YWJpbGl0eV9kZiA8LSBjbHVzdGVyX3Jlc3VsdHNfbGlzdCB8PgogIHB1cnJyOjptYXAoCiAgICBcKGNsdXN0ZXJfZGYpIHsKICAgICAgIyBFeHRyYWN0IHBhcmFtZXRlcnMgZm9yIHRoaXMgY2x1c3RlcmluZyByZXN1bHQKICAgICAgbm4gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkbm4pCiAgICAgIHJlc29sdXRpb24gPC0gdW5pcXVlKGNsdXN0ZXJfZGYkcmVzb2x1dGlvbikKCiAgICAgIHJPcGVuU2NQQ0E6OmNhbGN1bGF0ZV9zdGFiaWxpdHkoCiAgICAgICAgcGNhX21hdHJpeCwKICAgICAgICBjbHVzdGVyX2RmLAogICAgICAgIG5uID0gbm4sCiAgICAgICAgcmVzb2x1dGlvbiA9IHJlc29sdXRpb24KICAgICAgKQogICAgfQogICkgfD4KICBwdXJycjo6bGlzdF9yYmluZCgpCmBgYAoKCmBgYHtyIHZpc3VhbGl6ZSBzdGFiaWxpdHkgdHdvIHBhcmFtZXRlcnN9CmdncGxvdChzdGFiaWxpdHlfZGYpICsKICBhZXMoeCA9IGFzLmZhY3RvcihubiksIHkgPSBhcmksIGZpbGwgPSBhcy5mYWN0b3Iobm4pKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAiUGFzdGVsMiIpICsKICBmYWNldF93cmFwKHZhcnMocmVzb2x1dGlvbiksIGxhYmVsbGVyID0gbGFiZWxfYm90aCkgKwogIGxhYnMoCiAgICB4ID0gIk51bWJlciBvZiBuZWFyZXN0IG5laWdoYm9ycyIsCiAgICB5ID0gIkFkanVzdGVkIFJhbmQgSW5kZXgiCiAgKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKQpgYGAKCgoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7ciBzZXNzaW9uIGluZm99CiMgcmVjb3JkIHRoZSB2ZXJzaW9ucyBvZiB0aGUgcGFja2FnZXMgdXNlZCBpbiB0aGlzIGFuYWx5c2lzIGFuZCBvdGhlciBlbnZpcm9ubWVudCBpbmZvcm1hdGlvbgpzZXNzaW9uSW5mbygpCmBgYAo=