### Load packages
library(tidyverse) # Collection of all the good stuff like dplyr, ggplot2 ect.
library(magrittr) # For extra-piping operators (eg. %<>%)

Introduction to the dataset

The palmer penguin dataset is excellent for EDA and UML. It contains different measures for 3 species of closely related penguins from several islands in Antarctica.

Let’s have a look:

Penguin datast: https://github.com/allisonhorst/palmerpenguins

Obtaining the Data

# load the dataset from GitHub - original source
penguins <- read_csv("https://github.com/allisonhorst/palmerpenguins/raw/5b5891f01b52ae26ad8cb9755ec93672f49328a8/data/penguins_size.csv")
penguins %>% head()
penguins %>% glimpse()
Rows: 344
Columns: 7
$ species_short     <chr> "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", "Adelie", …
$ island            <chr> "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen", "Torgersen…
$ culmen_length_mm  <dbl> 39.1, 39.5, 40.3, NA, 36.7, 39.3, 38.9, 39.2, 34.1, 42.0, 37.8, 37.8, 41.1, 38.6, 34.6, 36.6, 38.7, 42.5, 34.4, 46.0, 37.8, 37.7, 35.9, 38.2…
$ culmen_depth_mm   <dbl> 18.7, 17.4, 18.0, NA, 19.3, 20.6, 17.8, 19.6, 18.1, 20.2, 17.1, 17.3, 17.6, 21.2, 21.1, 17.8, 19.0, 20.7, 18.4, 21.5, 18.3, 18.7, 19.2, 18.1…
$ flipper_length_mm <dbl> 181, 186, 195, NA, 193, 190, 181, 195, 193, 190, 186, 180, 182, 191, 198, 185, 195, 197, 184, 194, 174, 180, 189, 185, 180, 187, 183, 187, 1…
$ body_mass_g       <dbl> 3750, 3800, 3250, NA, 3450, 3650, 3625, 4675, 3475, 4250, 3300, 3700, 3200, 3800, 4400, 3700, 3450, 4500, 3325, 4200, 3400, 3600, 3800, 3950…
$ sex               <chr> "MALE", "FEMALE", "FEMALE", NA, "FEMALE", "MALE", "FEMALE", "MALE", NA, NA, NA, NA, "FEMALE", "MALE", "MALE", "FEMALE", "FEMALE", "MALE", "F…
# drop all missing observations 
penguins %<>% drop_na()

Brief EDA

penguins %>% count(species_short)
penguins %>% count(species_short, island) %>%
  pivot_wider(names_from = island, values_from = n, values_fill = 0)

library(GGally)
penguins %>% ggpairs(legend = 1,
                     columns = c(3:6),
                     mapping = ggplot2::aes(colour=species_short, alpha = 0.5), 
                     lower = list(continuous = wrap("smooth", alpha = 0.3, size=0.1))) +
  theme(legend.position = "bottom")  

Overall we can see some general tendencies in the data:

  • Being “bio” data, it is rather normally distributed
  • Gentoos are on average heavier
  • Glipper length is correlated with body mass for all species
  • Culmen length and depth is correlated with body mass for gentoos but not so much for the other species (visual analysis…no proper calculation)
  • Overall there is obviousely some correlation between the variables that can be ‘exploited’ for dimensionality reduction.

Before we can do any machine learning, it is a good idea to scale the data. Most algorithms are not agnostic to magnitudes and bringing all variables on the same scale is therefore crucial.

Unsupervised Machine Learning (With Penguins)

Dimensionality reduction

PRincipal Component Analysis

  • We start with the most popular classical dimensionality reduction technique, Principal_component-Analysis (PCA).
  • To execute the PCA, we’ll here use the FactoMineR package to compute PCA, and factoextra for extracting and visualizing the results.
  • FactoMineR is a great and my favorite package for computing principal component methods in R. It’s very easy to use and very well documented.
  • There are other alternatives around, but I since quite some time find it to be the most powerful and convenient one. factoextra is just a convenient ggplot wrapper that easily produces nice and informative diagnistic plots for a variety of DR and clustering techniques.

Lets do that. Notice the scale.unit = TRUE argument, which you should ALWAYS use. Afterwards, we take a look at the resulting list object.

res_pca <- penguins %>% 
  select_if(is_numeric) %>%
  PCA(scale.unit = TRUE, graph = FALSE)

Ok, lets see look at the “screeplot”, a diagnostic visualization that displays the variance explained by every component. We here use the factoextra package, like for all following visualizations with the fviz_ prefix. Notice that the output in every case is an ggplot2 object, which could be complemented with further layers.

res_pca %>% 
  fviz_screeplot(addlabels = TRUE, 
                 ncp = 10, 
                 ggtheme = theme_gray())

As expected, we see that the first component already captures a main share of the variance. Let’s look at the corresponding eigenvalues.

res_pca$eig %>% as_tibble()

For feature selection, our rule-of-thumb is to only include components with an eigenvalue > 1, meaning that we in this case would have reduced our data to 4 dimensions. Lets project them onto 2-dimensional space and take a look at the vector of our features. In this case, you could easily condens all information in one dimension. We will include the second as well for 2-d plotting, but otherwise we have to have no hard feelings to discard the rest.

res_pca %>%
  fviz_pca_var(alpha.var = "cos2",
               col.var = "contrib",
               gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
               repel = TRUE,
               ggtheme = theme_gray()) 

Lets look at the numeric values.

res_pca %>% get_pca_var()
Principal Component Analysis Results for variables
 ===================================================
  Name       Description                                    
1 "$coord"   "Coordinates for the variables"                
2 "$cor"     "Correlations between variables and dimensions"
3 "$cos2"    "Cos2 for the variables"                       
4 "$contrib" "contributions of the variables"               
res_pca$var$coord %>% 
  as_tibble() %>% 
  head()

The results-object also contains the observations loading on the components.

res_pca %>% get_pca_var()
Principal Component Analysis Results for variables
 ===================================================
  Name       Description                                    
1 "$coord"   "Coordinates for the variables"                
2 "$cor"     "Correlations between variables and dimensions"
3 "$cos2"    "Cos2 for the variables"                       
4 "$contrib" "contributions of the variables"               
res_pca$ind$coord %>% 
  as_tibble() %>% 
  head()

Let’s visualize our observations and the variable-loading together in the space of the first 2 components.

res_pca %>%
  fviz_pca_biplot(alpha.ind = "cos2",
                  col.ind = "contrib",
                  gradient.cols = c("#00AFBB", "#E7B800", "#FC4E07"),
                  geom = "point", 
                  ggtheme = theme_gray()) 

We cal also briefly check if our dimensionality reductions is helpful to differentiate between species.

res_pca %>% 
  fviz_pca_biplot(alpha.ind = "cos2",
                  geom = "point",                   
                  habillage = penguins %>% pull(species_short) %>% factor(), 
                  addEllipses = TRUE,
                  ggtheme = theme_gray()) 

Umap

Now let’s try out UMAP, a new dimensionality reduction algorightm that comes with many interesting features: https://umap-learn.readthedocs.io/en/latest/

You want to learn more from the guy behind the algorithm? https://youtu.be/nq6iPZVUxZU check out that excellent talk by Leland McInnes or https://arxiv.org/abs/1802.03426.

# install.packages('uwot') # If necessary install
library(uwot)
res_umap <- penguins %>%
  select_if(is_numeric) %>%
  umap(n_neighbors = 15, 
       metric = "cosine", 
       min_dist = 0.01, 
       scale = TRUE) 
res_umap %>% as_tibble() %>%
  glimpse()
Rows: 334
Columns: 2
$ V1 <dbl> -6.745879, -7.580199, -8.403356, -6.654872, -5.534422, -7.580670, -4.743643, -8.540195, -5.400925, -4.831200, -7.705870, -6.612927, -4.340905, -7.741749, -…
$ V2 <dbl> -0.35196864, -0.11502516, -0.83884513, -1.69948804, -2.05393969, -0.09900867, -1.41245281, 0.25474346, -2.06168162, -1.49675905, -1.02527558, -1.65299618, …
res_umap %>%
  as_tibble() %>%
  bind_cols(penguins %>% select(island, species_short)) %>%
  ggplot(aes(x = V1, y = V2, col = species_short)) + 
  geom_point(shape = 21, alpha = 0.5) 

Umap seems to do a better job at reducing the dimensionality in a way that the resulting embedding fits well with the species destinction.

Clustering

  • Now that we had a look at dimensionality reduction, let’s see what clustering can do at the present case.
  • We will try out K-means and hierarchical clustering

K-Means Clustering

  • We now perform a K-means clustering, a classical robust and well performing fast clustering algorithm.
  • We have to upfront choose our k.
  • there exists some guidance, for example the highest gain in “total within sum of sqares” (fast to calculate), the “siluette”, as well as the “gap statistics” (hard to calculate, takes time).
  • Note: Data with different scales needs to be scaled before clustering, since most cluster algorithms do not have an inbuild scale argument.
# We use the viz_nbclust() function of the factorextra package
penguins %>%
  select_if(is_numeric) %>% 
  scale() %>%
  fviz_nbclust(kmeans, method = "wss")  

  • Ok,we here settle for 3 (executive decision, since we want to identify 3 distinct species).
#Before we start, something weird upfront. The function takes the observation names from the rownames (which nobody uses anymore, and are depreciated by `dplyr`). So, remeber to define them just straight before you cluster, otherwise the next `dplyr` function will delete them again.
penguins_clust <- penguins %>% column_to_rownames('species_short') %>%
  select_if(is_numeric) %>%
  scale()
Error in `.rowNamesDF<-`(x, value = value) : 
  duplicate 'row.names' are not allowed
res_km 
K-means clustering with 3 clusters of sizes 129, 120, 85

Cluster means:
  culmen_length_mm culmen_depth_mm flipper_length_mm body_mass_g
1       -1.0470735       0.4884505        -0.8833910  -0.7644518
2        0.6497893      -1.0965845         1.1566225   1.0953386
3        0.6717383       0.8068238        -0.2922031  -0.3861924

Clustering vector:
  [1] 1 1 1 1 1 1 1 1 1 1 1 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 1 1 1 3 1 1 1 3 1 1 1 1 1 1 1 3 1 1 1 1 1 1 1 3 1 1 1 3 1 3 1 1 1 3 1 3 1 1 1 1 1 1
 [83] 1 1 1 3 1 1 1 3 1 1 1 3 1 3 1 1 1 1 1 1 1 3 1 3 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
[165] 3 3 1 3 1 3 3 3 3 3 3 3 1 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 3 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[247] 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
[329] 2 2 2 2 2 2

Within cluster sum of squares by cluster:
[1] 120.8400 140.1537 109.6498
 (between_SS / total_SS =  72.2 %)

Available components:

[1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss" "betweenss"    "size"         "iter"         "ifault"      

Again, lets visualize it. To have a meaningful way for 2d visualization, we again project the observations on the space of the first 2 components.

res_km %>% 
  fviz_cluster(data = penguins %>% select_if(is_numeric) ,
               ggtheme = theme_gray())  

Ok, we got 3 clusters. Let’s look what’s in them.

penguins%>%
  bind_cols(cluster = res_km$cluster) %>%
  select_if(is_numeric) %>%
  group_by(cluster) %>%
  mutate(n = n()) %>%
  summarise_all(funs(mean))

Lets see if they found the species correctly

table(penguins$species_short, res_km$cluster)
           
              1   2   3
  Adelie    124   0  22
  Chinstrap   5   0  63
  Gentoo      0 120   0

Hirarchical Clustering

  • Let’s get it started and perform a cluster. We here use the hcut function,
  • Notice that hcut has a stand = TRUE argument, meaning we do not need to scale the data beforehand.
res_hc <- penguins %>%
  select_if(is_numeric) %>%
  hcut(hc_func = "hclust", 
       k = 3, 
       stand = TRUE)
  • In hierarchical clustering, you categorize the objects into a hierarchy similar to a tree-like diagram which is called a dendrogram.
  • The distance of split or merge (called height) is shown on the y-axis of the dendrogram below.
res_hc %>%
  fviz_dend(rect = TRUE, cex = 0.5)

Notice how the dendrogram is built and every data point finally merges into a single cluster with the height(distance) shown on the y-axis.

Let’s inspect what’s in the clusters.

penguins %>%
  bind_cols(cluster = res_hc$cluster) %>%
  select_if(is_numeric) %>%
  group_by(cluster) %>%
  mutate(n = n()) %>%
  summarise_all(mean)

And again visualize them:

res_hc %>%
  fviz_cluster(data = penguins %>% select_if(is_numeric),
               ggtheme = theme_gray())  

Lets see again how well we did with seperating species:

table(penguins$species_short, res_hc$cluster)
           
              1   2   3
  Adelie    146   0   0
  Chinstrap  11  57   0
  Gentoo      0   0 120

Bonus: Hirarchical Clustering based in PCA

  • You might already have wondered: “Could one combine a PCA with clustering techniques”? The answer is: “Yes!”.
  • In practice, that actually works very fine, and often delivers more robust clusters.
  • We could do it by hand, but the HCPC function already does that for us, and offers also a nice diagnostic viz.
res_hcpc <- res_pca %>% 
  HCPC(nb.clust = -1, #  self determined: higher relative loss of inertia
       graph = FALSE) 
res_hcpc %>%
  plot(choice = "3D.map")

Endnotes

Packages & Ecosystem

  • factominer: Very well documented package & ecosystem webpage with many examples, tutorials, and further reseources

Suggestions for further study

Session Info

sessionInfo()
R version 4.0.2 (2020-06-22)
Platform: x86_64-apple-darwin17.0 (64-bit)
Running under: macOS Catalina 10.15.6

Matrix products: default
BLAS:   /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.0/Resources/lib/libRlapack.dylib

locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] uwot_0.1.8       Matrix_1.2-18    factoextra_1.0.7 FactoMineR_2.3   GGally_2.0.0     magrittr_1.5     forcats_0.5.0    stringr_1.4.0    dplyr_1.0.2     
[10] purrr_0.3.4      readr_1.3.1      tidyr_1.1.2      tibble_3.0.3     ggplot2_3.3.2    tidyverse_1.3.0  knitr_1.29      

loaded via a namespace (and not attached):
 [1] nlme_3.1-149         fs_1.5.0             lubridate_1.7.9      RcppAnnoy_0.0.16     RColorBrewer_1.1-2   progress_1.2.2       httr_1.4.2          
 [8] tools_4.0.2          backports_1.1.9      utf8_1.1.4           R6_2.4.1             DBI_1.1.0            mgcv_1.8-33          colorspace_1.4-1    
[15] withr_2.2.0          tidyselect_1.1.0     prettyunits_1.1.1    curl_4.3             compiler_4.0.2       cli_2.0.2            rvest_0.3.6         
[22] flashClust_1.01-2    xml2_1.3.2           labeling_0.3         scales_1.1.1         digest_0.6.25        foreign_0.8-80       rmarkdown_2.3       
[29] rio_0.5.16           base64enc_0.1-3      htmltools_0.5.0      pkgconfig_2.0.3      dbplyr_1.4.4         rlang_0.4.7          readxl_1.3.1        
[36] rstudioapi_0.11      farver_2.0.3         generics_0.0.2       jsonlite_1.7.1       zip_2.1.1            car_3.0-9            leaps_3.1           
[43] Rcpp_1.0.5           munsell_0.5.0        fansi_0.4.1          abind_1.4-5          lifecycle_0.2.0      scatterplot3d_0.3-41 stringi_1.5.3       
[50] yaml_2.2.1           carData_3.0-4        MASS_7.3-53          plyr_1.8.6           grid_4.0.2           blob_1.2.1           ggrepel_0.8.2       
[57] crayon_1.3.4         lattice_0.20-41      haven_2.3.1          splines_4.0.2        hms_0.5.3            pillar_1.4.6         ggpubr_0.4.0        
[64] ggsignif_0.6.0       codetools_0.2-16     reprex_0.3.0         glue_1.4.2           evaluate_0.14        data.table_1.13.0    modelr_0.1.8        
[71] vctrs_0.3.4          cellranger_1.1.0     gtable_0.3.0         reshape_0.8.8        assertthat_0.2.1     xfun_0.17            openxlsx_4.1.5      
[78] broom_0.7.0          rstatix_0.6.0        cluster_2.1.0        ellipsis_0.3.1      
LS0tCnRpdGxlOiAnQXBwbGljYXRpb246IFVuc3VwZXJ2aXNlZCBtYWNoaW5lIExlYXJuaW5nIChVTUwpIGluIFInCmF1dGhvcjogIkRhbmllbCBTLiBIYWluIChkc2hAYnVzaW5lc3MuYWF1LmRrKSIKZGF0ZTogIlVwZGF0ZWQgYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZCwgJVknKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogZmFsc2UKICAgIHRoZW1lOiBmbGF0bHkKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyBLbml0ciBvcHRpb25zCiMjIyBHZW5lcmljIHByZWFtYmxlClN5cy5zZXRlbnYoTEFORyA9ICJlbiIpICMgRm9yIGVuZ2xpc2ggbGFuZ3VhZ2UKb3B0aW9ucyhzY2lwZW4gPSA1KSAjIFRvIGRlYWN0aXZhdGUgYW5ub3lpbmcgc2NpZW50aWZpYyBudW1iZXIgbm90YXRpb24KCiMgcm0obGlzdD1scygpKTsgZ3JhcGhpY3Mub2ZmKCkgIyBnZXQgcmlkIG9mIGV2ZXJ5dGhpbmcgaW4gdGhlIHdvcmtzcGFjZQppZiAoIXJlcXVpcmUoImtuaXRyIikpIGluc3RhbGwucGFja2FnZXMoImtuaXRyIik7IGxpYnJhcnkoa25pdHIpICMgRm9yIGRpc3BsYXkgb2YgdGhlIG1hcmtkb3duCgojIyMgS25pdHIgb3B0aW9ucwprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZz1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZT1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgZmlnLmFsaWduPSJjZW50ZXIiCiAgICAgICAgICAgICAgICAgICAgICkKYGBgCgpgYGB7cn0KIyMjIExvYWQgcGFja2FnZXMKbGlicmFyeSh0aWR5dmVyc2UpICMgQ29sbGVjdGlvbiBvZiBhbGwgdGhlIGdvb2Qgc3R1ZmYgbGlrZSBkcGx5ciwgZ2dwbG90MiBlY3QuCmxpYnJhcnkobWFncml0dHIpICMgRm9yIGV4dHJhLXBpcGluZyBvcGVyYXRvcnMgKGVnLiAlPD4lKQpgYGAKCiMgSW50cm9kdWN0aW9uIHRvIHRoZSBkYXRhc2V0ClRoZSBwYWxtZXIgcGVuZ3VpbiBkYXRhc2V0IGlzIGV4Y2VsbGVudCBmb3IgRURBIGFuZCBVTUwuIEl0IGNvbnRhaW5zIGRpZmZlcmVudCBtZWFzdXJlcyBmb3IgMyBzcGVjaWVzIG9mIGNsb3NlbHkgcmVsYXRlZCBwZW5ndWlucyBmcm9tIHNldmVyYWwgaXNsYW5kcyBpbiBBbnRhcmN0aWNhLgoKTGV0J3MgaGF2ZSBhIGxvb2s6CgpQZW5ndWluIGRhdGFzdDogaHR0cHM6Ly9naXRodWIuY29tL2FsbGlzb25ob3JzdC9wYWxtZXJwZW5ndWlucwohW10oaHR0cHM6Ly9naXRodWIuY29tL2FsbGlzb25ob3JzdC9wYWxtZXJwZW5ndWlucy9yYXcvbWFzdGVyL21hbi9maWd1cmVzL2x0ZXJfcGVuZ3VpbnMucG5nKQoKIyMgT2J0YWluaW5nIHRoZSBEYXRhCgpgYGB7cn0KIyBsb2FkIHRoZSBkYXRhc2V0IGZyb20gR2l0SHViIC0gb3JpZ2luYWwgc291cmNlCnBlbmd1aW5zIDwtIHJlYWRfY3N2KCJodHRwczovL2dpdGh1Yi5jb20vYWxsaXNvbmhvcnN0L3BhbG1lcnBlbmd1aW5zL3Jhdy81YjU4OTFmMDFiNTJhZTI2YWQ4Y2I5NzU1ZWM5MzY3MmY0OTMyOGE4L2RhdGEvcGVuZ3VpbnNfc2l6ZS5jc3YiKQpgYGAKCmBgYHtyfQpwZW5ndWlucyAlPiUgaGVhZCgpCmBgYAoKYGBge3J9CnBlbmd1aW5zICU+JSBnbGltcHNlKCkKYGBgCgpgYGB7cn0KIyBkcm9wIGFsbCBtaXNzaW5nIG9ic2VydmF0aW9ucyAKcGVuZ3VpbnMgJTw+JSBkcm9wX25hKCkKYGBgCgojIyBCcmllZiBFREEKCmBgYHtyfQpwZW5ndWlucyAlPiUgY291bnQoc3BlY2llc19zaG9ydCkKYGBgCmBgYHtyfQpwZW5ndWlucyAlPiUgY291bnQoc3BlY2llc19zaG9ydCwgaXNsYW5kKSAlPiUKICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gaXNsYW5kLCB2YWx1ZXNfZnJvbSA9IG4sIHZhbHVlc19maWxsID0gMCkKYGBgCiFbXShodHRwczovL2dpdGh1Yi5jb20vYWxsaXNvbmhvcnN0L3BhbG1lcnBlbmd1aW5zL3Jhdy9tYXN0ZXIvbWFuL2ZpZ3VyZXMvY3VsbWVuX2RlcHRoLnBuZykKCmBgYHtyLCBmaWcuaGVpZ2h0PTEyLCBmaWcud2lkdGg9MTJ9CmxpYnJhcnkoR0dhbGx5KQpwZW5ndWlucyAlPiUgZ2dwYWlycyhsZWdlbmQgPSAxLAogICAgICAgICAgICAgICAgICAgICBjb2x1bW5zID0gYygzOjYpLAogICAgICAgICAgICAgICAgICAgICBtYXBwaW5nID0gZ2dwbG90Mjo6YWVzKGNvbG91cj1zcGVjaWVzX3Nob3J0LCBhbHBoYSA9IDAuNSksIAogICAgICAgICAgICAgICAgICAgICBsb3dlciA9IGxpc3QoY29udGludW91cyA9IHdyYXAoInNtb290aCIsIGFscGhhID0gMC4zLCBzaXplPTAuMSkpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpICAKYGBgCgpPdmVyYWxsIHdlIGNhbiBzZWUgc29tZSBnZW5lcmFsIHRlbmRlbmNpZXMgaW4gdGhlIGRhdGE6CgoqICAgQmVpbmcgImJpbyIgZGF0YSwgaXQgaXMgcmF0aGVyIG5vcm1hbGx5IGRpc3RyaWJ1dGVkCiogICBHZW50b29zIGFyZSBvbiBhdmVyYWdlIGhlYXZpZXIKKiAgIEdsaXBwZXIgbGVuZ3RoIGlzIGNvcnJlbGF0ZWQgd2l0aCBib2R5IG1hc3MgZm9yIGFsbCBzcGVjaWVzCiogICBDdWxtZW4gbGVuZ3RoIGFuZCBkZXB0aCBpcyBjb3JyZWxhdGVkIHdpdGggYm9keSBtYXNzIGZvciBnZW50b29zIGJ1dCBub3Qgc28gbXVjaCBmb3IgdGhlIG90aGVyIHNwZWNpZXMgKHZpc3VhbCBhbmFseXNpcy4uLm5vIHByb3BlciBjYWxjdWxhdGlvbikKKiAgIE92ZXJhbGwgdGhlcmUgaXMgb2J2aW91c2VseSBzb21lIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIHZhcmlhYmxlcyB0aGF0IGNhbiBiZSAnZXhwbG9pdGVkJyBmb3IgZGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uLgoKQmVmb3JlIHdlIGNhbiBkbyBhbnkgbWFjaGluZSBsZWFybmluZywgaXQgaXMgYSBnb29kIGlkZWEgdG8gc2NhbGUgdGhlIGRhdGEuIE1vc3QgYWxnb3JpdGhtcyBhcmUgbm90IGFnbm9zdGljIHRvIG1hZ25pdHVkZXMgYW5kIGJyaW5naW5nIGFsbCB2YXJpYWJsZXMgb24gdGhlIHNhbWUgc2NhbGUgaXMgdGhlcmVmb3JlIGNydWNpYWwuCgojIFVuc3VwZXJ2aXNlZCBNYWNoaW5lIExlYXJuaW5nIChXaXRoIFBlbmd1aW5zKQoKIyMgRGltZW5zaW9uYWxpdHkgcmVkdWN0aW9uCgojIyMgUFJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcwoKKiBXZSBzdGFydCB3aXRoIHRoZSBtb3N0IHBvcHVsYXIgY2xhc3NpY2FsIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiB0ZWNobmlxdWUsIFByaW5jaXBhbF9jb21wb25lbnQtQW5hbHlzaXMgKFBDQSkuIAoqIFRvIGV4ZWN1dGUgdGhlIFBDQSwgd2UnbGwgaGVyZSB1c2UgdGhlIFtgRmFjdG9NaW5lUmBdKGh0dHA6Ly9mYWN0b21pbmVyLmZyZWUuZnIvKSBwYWNrYWdlIHRvIGNvbXB1dGUgUENBLCBhbmQgIFtgZmFjdG9leHRyYWBdKGh0dHBzOi8vZ2l0aHViLmNvbS9rYXNzYW1iYXJhL2ZhY3RvZXh0cmEpIGZvciBleHRyYWN0aW5nIGFuZCB2aXN1YWxpemluZyB0aGUgcmVzdWx0cy4gCiogYEZhY3RvTWluZVJgIGlzIGEgZ3JlYXQgYW5kIG15IGZhdm9yaXRlIHBhY2thZ2UgZm9yIGNvbXB1dGluZyBwcmluY2lwYWwgY29tcG9uZW50IG1ldGhvZHMgaW4gUi4gSXQncyB2ZXJ5IGVhc3kgdG8gdXNlIGFuZCB2ZXJ5IHdlbGwgZG9jdW1lbnRlZC4gCiogVGhlcmUgYXJlIG90aGVyIGFsdGVybmF0aXZlcyBhcm91bmQsIGJ1dCBJIHNpbmNlIHF1aXRlIHNvbWUgdGltZSBmaW5kIGl0IHRvIGJlIHRoZSBtb3N0IHBvd2VyZnVsIGFuZCBjb252ZW5pZW50IG9uZS4gYGZhY3RvZXh0cmFgIGlzIGp1c3QgYSBjb252ZW5pZW50IGBnZ3Bsb3RgIHdyYXBwZXIgdGhhdCBlYXNpbHkgcHJvZHVjZXMgbmljZSBhbmQgaW5mb3JtYXRpdmUgZGlhZ25pc3RpYyBwbG90cyBmb3IgYSB2YXJpZXR5IG9mIERSIGFuZCBjbHVzdGVyaW5nIHRlY2huaXF1ZXMuCgpgYGB7cix3YXJuaW5nPUZBTFNFLGVjaG89RkFMU0V9CiMgIyBJbnN0YWxsIHBhY2thZ2VzIGlmIG5lY2Vzc2FyeSAKIyBpbnN0YWxsLnBhY2thZ2VzKCdGYWN0b01pbmVSJykKIyBpbnN0YWxsLnBhY2thZ2VzKCdmYWN0b2V4dHJhJykKCiMgTG9hZCBwYWNrYWdlcwpsaWJyYXJ5KEZhY3RvTWluZVIpCmxpYnJhcnkoZmFjdG9leHRyYSkKYGBgCgpMZXRzIGRvIHRoYXQuIE5vdGljZSB0aGUgYHNjYWxlLnVuaXQgPSBUUlVFYCBhcmd1bWVudCwgd2hpY2ggeW91IHNob3VsZCBBTFdBWVMgdXNlLiBBZnRlcndhcmRzLCB3ZSB0YWtlIGEgbG9vayBhdCB0aGUgcmVzdWx0aW5nIGxpc3Qgb2JqZWN0LgoKYGBge3J9CnJlc19wY2EgPC0gcGVuZ3VpbnMgJT4lIAogIHNlbGVjdF9pZihpc19udW1lcmljKSAlPiUKICBQQ0Eoc2NhbGUudW5pdCA9IFRSVUUsIGdyYXBoID0gRkFMU0UpCmBgYAoKCk9rLCBsZXRzIHNlZSBsb29rIGF0IHRoZSAic2NyZWVwbG90IiwgYSBkaWFnbm9zdGljIHZpc3VhbGl6YXRpb24gdGhhdCBkaXNwbGF5cyB0aGUgdmFyaWFuY2UgZXhwbGFpbmVkIGJ5IGV2ZXJ5IGNvbXBvbmVudC4gV2UgaGVyZSB1c2UgdGhlIGBmYWN0b2V4dHJhYCBwYWNrYWdlLCBsaWtlIGZvciBhbGwgZm9sbG93aW5nIHZpc3VhbGl6YXRpb25zIHdpdGggdGhlIGBmdml6X2AgcHJlZml4LiBOb3RpY2UgdGhhdCB0aGUgb3V0cHV0IGluIGV2ZXJ5IGNhc2UgaXMgYW4gYGdncGxvdDJgIG9iamVjdCwgd2hpY2ggY291bGQgYmUgY29tcGxlbWVudGVkIHdpdGggZnVydGhlciBsYXllcnMuCgpgYGB7cixmaWcuYWxpZ249J2NlbnRlcid9CnJlc19wY2EgJT4lIAogIGZ2aXpfc2NyZWVwbG90KGFkZGxhYmVscyA9IFRSVUUsIAogICAgICAgICAgICAgICAgIG5jcCA9IDEwLCAKICAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfZ3JheSgpKQpgYGAKCkFzIGV4cGVjdGVkLCB3ZSBzZWUgdGhhdCB0aGUgZmlyc3QgY29tcG9uZW50IGFscmVhZHkgY2FwdHVyZXMgYSBtYWluIHNoYXJlIG9mIHRoZSB2YXJpYW5jZS4gTGV0J3MgbG9vayBhdCB0aGUgY29ycmVzcG9uZGluZyBlaWdlbnZhbHVlcy4KCmBgYHtyfQpyZXNfcGNhJGVpZyAlPiUgYXNfdGliYmxlKCkKYGBgCgpGb3IgZmVhdHVyZSBzZWxlY3Rpb24sIG91ciBydWxlLW9mLXRodW1iIGlzIHRvIG9ubHkgaW5jbHVkZSBjb21wb25lbnRzIHdpdGggYW4gZWlnZW52YWx1ZSA+IDEsIG1lYW5pbmcgdGhhdCB3ZSBpbiB0aGlzIGNhc2Ugd291bGQgaGF2ZSByZWR1Y2VkIG91ciBkYXRhIHRvIDQgZGltZW5zaW9ucy4gTGV0cyBwcm9qZWN0IHRoZW0gb250byAyLWRpbWVuc2lvbmFsIHNwYWNlIGFuZCB0YWtlIGEgbG9vayBhdCB0aGUgdmVjdG9yIG9mIG91ciBmZWF0dXJlcy4gSW4gdGhpcyBjYXNlLCB5b3UgY291bGQgZWFzaWx5IGNvbmRlbnMgYWxsIGluZm9ybWF0aW9uIGluIG9uZSBkaW1lbnNpb24uIFdlIHdpbGwgaW5jbHVkZSB0aGUgc2Vjb25kIGFzIHdlbGwgZm9yIDItZCBwbG90dGluZywgYnV0IG90aGVyd2lzZSB3ZSBoYXZlIHRvIGhhdmUgbm8gaGFyZCBmZWVsaW5ncyB0byBkaXNjYXJkIHRoZSByZXN0LgoKCmBgYHtyLGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTEwLGZpZy5hbGlnbj0nY2VudGVyJ30KcmVzX3BjYSAlPiUKICBmdml6X3BjYV92YXIoYWxwaGEudmFyID0gImNvczIiLAogICAgICAgICAgICAgICBjb2wudmFyID0gImNvbnRyaWIiLAogICAgICAgICAgICAgICBncmFkaWVudC5jb2xzID0gYygiIzAwQUZCQiIsICIjRTdCODAwIiwgIiNGQzRFMDciKSwKICAgICAgICAgICAgICAgcmVwZWwgPSBUUlVFLAogICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfZ3JheSgpKSAKYGBgCgpMZXRzIGxvb2sgYXQgdGhlIG51bWVyaWMgdmFsdWVzLgoKYGBge3J9CnJlc19wY2EgJT4lIGdldF9wY2FfdmFyKCkKYGBgCgpgYGB7cn0KcmVzX3BjYSR2YXIkY29vcmQgJT4lIAogIGFzX3RpYmJsZSgpICU+JSAKICBoZWFkKCkKYGBgCgpUaGUgcmVzdWx0cy1vYmplY3QgYWxzbyBjb250YWlucyB0aGUgb2JzZXJ2YXRpb25zIGxvYWRpbmcgb24gdGhlIGNvbXBvbmVudHMuCgpgYGB7cn0KcmVzX3BjYSAlPiUgZ2V0X3BjYV92YXIoKQpgYGAKCmBgYHtyfQpyZXNfcGNhJGluZCRjb29yZCAlPiUgCiAgYXNfdGliYmxlKCkgJT4lIAogIGhlYWQoKQpgYGAKCkxldCdzIHZpc3VhbGl6ZSBvdXIgb2JzZXJ2YXRpb25zIGFuZCB0aGUgdmFyaWFibGUtbG9hZGluZyB0b2dldGhlciBpbiB0aGUgc3BhY2Ugb2YgdGhlIGZpcnN0IDIgY29tcG9uZW50cy4KCmBgYHtyLCxmaWcud2lkdGg9MTUsZmlnLmhlaWdodD0xMCxmaWcuYWxpZ249J2NlbnRlcid9CnJlc19wY2EgJT4lCiAgZnZpel9wY2FfYmlwbG90KGFscGhhLmluZCA9ICJjb3MyIiwKICAgICAgICAgICAgICAgICAgY29sLmluZCA9ICJjb250cmliIiwKICAgICAgICAgICAgICAgICAgZ3JhZGllbnQuY29scyA9IGMoIiMwMEFGQkIiLCAiI0U3QjgwMCIsICIjRkM0RTA3IiksCiAgICAgICAgICAgICAgICAgIGdlb20gPSAicG9pbnQiLCAKICAgICAgICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX2dyYXkoKSkgCmBgYAoKCldlIGNhbCBhbHNvIGJyaWVmbHkgY2hlY2sgaWYgb3VyIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbnMgaXMgaGVscGZ1bCB0byBkaWZmZXJlbnRpYXRlIGJldHdlZW4gYHNwZWNpZXNgLgoKYGBge3IsLGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTEwLGZpZy5hbGlnbj0nY2VudGVyJ30KcmVzX3BjYSAlPiUgCiAgZnZpel9wY2FfYmlwbG90KGFscGhhLmluZCA9ICJjb3MyIiwKICAgICAgICAgICAgICAgICAgZ2VvbSA9ICJwb2ludCIsICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICBoYWJpbGxhZ2UgPSBwZW5ndWlucyAlPiUgcHVsbChzcGVjaWVzX3Nob3J0KSAlPiUgZmFjdG9yKCksIAogICAgICAgICAgICAgICAgICBhZGRFbGxpcHNlcyA9IFRSVUUsCiAgICAgICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9ncmF5KCkpIApgYGAKCiMjIyBVbWFwCgpOb3cgbGV0J3MgdHJ5IG91dCBVTUFQLCBhIG5ldyBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24gYWxnb3JpZ2h0bSB0aGF0IGNvbWVzIHdpdGggbWFueSBpbnRlcmVzdGluZyBmZWF0dXJlczogaHR0cHM6Ly91bWFwLWxlYXJuLnJlYWR0aGVkb2NzLmlvL2VuL2xhdGVzdC8KCllvdSB3YW50IHRvIGxlYXJuIG1vcmUgZnJvbSB0aGUgZ3V5IGJlaGluZCB0aGUgYWxnb3JpdGhtPwpodHRwczovL3lvdXR1LmJlL25xNmlQWlZVeFpVIGNoZWNrIG91dCB0aGF0IGV4Y2VsbGVudCB0YWxrIGJ5IExlbGFuZCBNY0lubmVzIG9yIGh0dHBzOi8vYXJ4aXYub3JnL2Ficy8xODAyLjAzNDI2LgoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygndXdvdCcpICMgSWYgbmVjZXNzYXJ5IGluc3RhbGwKbGlicmFyeSh1d290KQpgYGAKCmBgYHtyfQpyZXNfdW1hcCA8LSBwZW5ndWlucyAlPiUKICBzZWxlY3RfaWYoaXNfbnVtZXJpYykgJT4lCiAgdW1hcChuX25laWdoYm9ycyA9IDE1LCAKICAgICAgIG1ldHJpYyA9ICJjb3NpbmUiLCAKICAgICAgIG1pbl9kaXN0ID0gMC4wMSwgCiAgICAgICBzY2FsZSA9IFRSVUUpIApgYGAKCmBgYHtyfQpyZXNfdW1hcCAlPiUgYXNfdGliYmxlKCkgJT4lCiAgZ2xpbXBzZSgpCmBgYApgYGB7cn0KcmVzX3VtYXAgJT4lCiAgYXNfdGliYmxlKCkgJT4lCiAgYmluZF9jb2xzKHBlbmd1aW5zICU+JSBzZWxlY3QoaXNsYW5kLCBzcGVjaWVzX3Nob3J0KSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gVjEsIHkgPSBWMiwgY29sID0gc3BlY2llc19zaG9ydCkpICsgCiAgZ2VvbV9wb2ludChzaGFwZSA9IDIxLCBhbHBoYSA9IDAuNSkgCmBgYApVbWFwIHNlZW1zIHRvIGRvIGEgYmV0dGVyIGpvYiBhdCByZWR1Y2luZyB0aGUgZGltZW5zaW9uYWxpdHkgaW4gYSB3YXkgdGhhdCB0aGUgcmVzdWx0aW5nIGVtYmVkZGluZyBmaXRzIHdlbGwgd2l0aCB0aGUgc3BlY2llcyBkZXN0aW5jdGlvbi4KCiMjIENsdXN0ZXJpbmcKCiogTm93IHRoYXQgd2UgaGFkIGEgbG9vayBhdCBkaW1lbnNpb25hbGl0eSByZWR1Y3Rpb24sIGxldCdzIHNlZSB3aGF0IGNsdXN0ZXJpbmcgY2FuIGRvIGF0IHRoZSBwcmVzZW50IGNhc2UuCiogV2Ugd2lsbCB0cnkgb3V0ICoqSy1tZWFucyoqIGFuZCAqKmhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nKioKCiMjIyBLLU1lYW5zIENsdXN0ZXJpbmcKCiogV2Ugbm93IHBlcmZvcm0gYSBLLW1lYW5zIGNsdXN0ZXJpbmcsIGEgY2xhc3NpY2FsIHJvYnVzdCBhbmQgd2VsbCBwZXJmb3JtaW5nIGZhc3QgY2x1c3RlcmluZyBhbGdvcml0aG0uCiogV2UgaGF2ZSB0byB1cGZyb250IGNob29zZSBvdXIgYGtgLiAKKiB0aGVyZSBleGlzdHMgc29tZSBndWlkYW5jZSwgZm9yIGV4YW1wbGUgdGhlIGhpZ2hlc3QgZ2FpbiBpbiAidG90YWwgd2l0aGluIHN1bSBvZiBzcWFyZXMiIChmYXN0IHRvIGNhbGN1bGF0ZSksIHRoZSAic2lsdWV0dGUiLCBhcyB3ZWxsIGFzIHRoZSAiZ2FwIHN0YXRpc3RpY3MiIChoYXJkIHRvIGNhbGN1bGF0ZSwgdGFrZXMgdGltZSkuIAoqICoqTm90ZToqKiBEYXRhIHdpdGggZGlmZmVyZW50IHNjYWxlcyBuZWVkcyB0byBiZSBzY2FsZWQgYmVmb3JlIGNsdXN0ZXJpbmcsIHNpbmNlIG1vc3QgY2x1c3RlciBhbGdvcml0aG1zIGRvIG5vdCBoYXZlIGFuIGluYnVpbGQgYHNjYWxlYCBhcmd1bWVudC4KCmBgYHtyLGZpZy5hbGlnbj0nY2VudGVyJ30KIyBXZSB1c2UgdGhlIHZpel9uYmNsdXN0KCkgZnVuY3Rpb24gb2YgdGhlIGZhY3RvcmV4dHJhIHBhY2thZ2UKcGVuZ3VpbnMgJT4lCiAgc2VsZWN0X2lmKGlzX251bWVyaWMpICU+JSAKICBzY2FsZSgpICU+JQogIGZ2aXpfbmJjbHVzdChrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiKSAgCmBgYAoKKiBPayx3ZSBoZXJlIHNldHRsZSBmb3IgMyAoZXhlY3V0aXZlIGRlY2lzaW9uLCBzaW5jZSB3ZSB3YW50IHRvIGlkZW50aWZ5IDMgZGlzdGluY3Qgc3BlY2llcykuIAoKYGBge3J9CiNCZWZvcmUgd2Ugc3RhcnQsIHNvbWV0aGluZyB3ZWlyZCB1cGZyb250LiBUaGUgZnVuY3Rpb24gdGFrZXMgdGhlIG9ic2VydmF0aW9uIG5hbWVzIGZyb20gdGhlIHJvd25hbWVzICh3aGljaCBub2JvZHkgdXNlcyBhbnltb3JlLCBhbmQgYXJlIGRlcHJlY2lhdGVkIGJ5IGBkcGx5cmApLiBTbywgcmVtZWJlciB0byBkZWZpbmUgdGhlbSBqdXN0IHN0cmFpZ2h0IGJlZm9yZSB5b3UgY2x1c3Rlciwgb3RoZXJ3aXNlIHRoZSBuZXh0IGBkcGx5cmAgZnVuY3Rpb24gd2lsbCBkZWxldGUgdGhlbSBhZ2Fpbi4KcGVuZ3VpbnNfY2x1c3QgPC0gcGVuZ3VpbnMgJT4lIGNvbHVtbl90b19yb3duYW1lcygnc3BlY2llc19zaG9ydCcpICU+JQogIHNlbGVjdF9pZihpc19udW1lcmljKSAlPiUKICBzY2FsZSgpCgpyb3duYW1lcyhkYXRhKSA8LSBkYXRhICU+JSBwdWxsKHBsYWNlKQpgYGAKCgpgYGB7cn0KcmVzX2ttIDwtIHBlbmd1aW5zICU+JSAKICBzZWxlY3RfaWYoaXNfbnVtZXJpYykgJT4lCiAgc2NhbGUoKSAlPiUgCiAga21lYW5zKGNlbnRlcnMgPSAzLCBuc3RhcnQgPSAyMCkgIApgYGAKCmBgYHtyfQpyZXNfa20gCmBgYAoKCkFnYWluLCBsZXRzIHZpc3VhbGl6ZSBpdC4gVG8gaGF2ZSBhIG1lYW5pbmdmdWwgd2F5IGZvciAyZCB2aXN1YWxpemF0aW9uLCB3ZSBhZ2FpbiBwcm9qZWN0IHRoZSBvYnNlcnZhdGlvbnMgb24gdGhlIHNwYWNlIG9mIHRoZSBmaXJzdCAyIGNvbXBvbmVudHMuCgpgYGB7ciwsZmlnLndpZHRoPTE1LGZpZy5oZWlnaHQ9MTAsZmlnLmFsaWduPSdjZW50ZXInfQpyZXNfa20gJT4lIAogIGZ2aXpfY2x1c3RlcihkYXRhID0gcGVuZ3VpbnMgJT4lIHNlbGVjdF9pZihpc19udW1lcmljKSAsCiAgICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9ncmF5KCkpICAKYGBgCgpPaywgd2UgZ290IDMgY2x1c3RlcnMuIExldCdzIGxvb2sgd2hhdCdzIGluIHRoZW0uCgpgYGB7cn0KcGVuZ3VpbnMgJT4lCiAgYmluZF9jb2xzKGNsdXN0ZXIgPSByZXNfa20kY2x1c3RlcikgJT4lCiAgc2VsZWN0X2lmKGlzX251bWVyaWMpICU+JQogIGdyb3VwX2J5KGNsdXN0ZXIpICU+JQogIG11dGF0ZShuID0gbigpKSAlPiUKICBzdW1tYXJpc2VfYWxsKGZ1bnMobWVhbikpCmBgYAoKTGV0cyBzZWUgaWYgdGhleSBmb3VuZCB0aGUgc3BlY2llcyBjb3JyZWN0bHkKCmBgYHtyfQp0YWJsZShwZW5ndWlucyRzcGVjaWVzX3Nob3J0LCByZXNfa20kY2x1c3RlcikKYGBgCgojIyMgSGlyYXJjaGljYWwgQ2x1c3RlcmluZwoKKiBMZXQncyBnZXQgaXQgc3RhcnRlZCBhbmQgcGVyZm9ybSBhIGNsdXN0ZXIuIFdlIGhlcmUgdXNlIHRoZSBgaGN1dGAgZnVuY3Rpb24sIAoqIE5vdGljZSB0aGF0IGBoY3V0YCBoYXMgYSBgc3RhbmQgPSBUUlVFYCBhcmd1bWVudCwgbWVhbmluZyB3ZSBkbyBub3QgbmVlZCB0byBzY2FsZSB0aGUgZGF0YSBiZWZvcmVoYW5kLgoKYGBge3J9CnJlc19oYyA8LSBwZW5ndWlucyAlPiUKICBzZWxlY3RfaWYoaXNfbnVtZXJpYykgJT4lCiAgaGN1dChoY19mdW5jID0gImhjbHVzdCIsIAogICAgICAgayA9IDMsIAogICAgICAgc3RhbmQgPSBUUlVFKQpgYGAKCiogSW4gaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcsIHlvdSBjYXRlZ29yaXplIHRoZSBvYmplY3RzIGludG8gYSBoaWVyYXJjaHkgc2ltaWxhciB0byBhIHRyZWUtbGlrZSBkaWFncmFtIHdoaWNoIGlzIGNhbGxlZCBhIGRlbmRyb2dyYW0uIAoqIFRoZSBkaXN0YW5jZSBvZiBzcGxpdCBvciBtZXJnZSAoY2FsbGVkIGhlaWdodCkgaXMgc2hvd24gb24gdGhlIHktYXhpcyBvZiB0aGUgZGVuZHJvZ3JhbSBiZWxvdy4KCmBgYHtyLCxmaWcud2lkdGg9MTUsZmlnLmhlaWdodD0xMCxmaWcuYWxpZ249J2NlbnRlcid9CnJlc19oYyAlPiUKICBmdml6X2RlbmQocmVjdCA9IFRSVUUsIGNleCA9IDAuNSkKYGBgCgoqKk5vdGljZSoqIGhvdyB0aGUgZGVuZHJvZ3JhbSBpcyBidWlsdCBhbmQgZXZlcnkgZGF0YSBwb2ludCBmaW5hbGx5IG1lcmdlcyBpbnRvIGEgc2luZ2xlIGNsdXN0ZXIgd2l0aCB0aGUgaGVpZ2h0KGRpc3RhbmNlKSBzaG93biBvbiB0aGUgeS1heGlzLgoKTGV0J3MgaW5zcGVjdCB3aGF0J3MgaW4gdGhlIGNsdXN0ZXJzLgoKYGBge3J9CnBlbmd1aW5zICU+JQogIGJpbmRfY29scyhjbHVzdGVyID0gcmVzX2hjJGNsdXN0ZXIpICU+JQogIHNlbGVjdF9pZihpc19udW1lcmljKSAlPiUKICBncm91cF9ieShjbHVzdGVyKSAlPiUKICBtdXRhdGUobiA9IG4oKSkgJT4lCiAgc3VtbWFyaXNlX2FsbChtZWFuKQpgYGAKCkFuZCBhZ2FpbiB2aXN1YWxpemUgdGhlbToKCmBgYHtyLCxmaWcud2lkdGg9MTUsZmlnLmhlaWdodD0xMCxmaWcuYWxpZ249J2NlbnRlcid9CnJlc19oYyAlPiUKICBmdml6X2NsdXN0ZXIoZGF0YSA9IHBlbmd1aW5zICU+JSBzZWxlY3RfaWYoaXNfbnVtZXJpYyksCiAgICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9ncmF5KCkpICAKYGBgCgpMZXRzIHNlZSBhZ2FpbiBob3cgd2VsbCB3ZSBkaWQgd2l0aCBzZXBlcmF0aW5nIHNwZWNpZXM6CgpgYGB7cn0KdGFibGUocGVuZ3VpbnMkc3BlY2llc19zaG9ydCwgcmVzX2hjJGNsdXN0ZXIpCmBgYAoKCiMjIyBCb251czogSGlyYXJjaGljYWwgQ2x1c3RlcmluZyBiYXNlZCBpbiBQQ0EKCiogWW91IG1pZ2h0IGFscmVhZHkgaGF2ZSB3b25kZXJlZDogIkNvdWxkIG9uZSBjb21iaW5lIGEgUENBIHdpdGggY2x1c3RlcmluZyB0ZWNobmlxdWVzIj8gVGhlIGFuc3dlciBpczogIlllcyEiLiAKKiBJbiBwcmFjdGljZSwgdGhhdCBhY3R1YWxseSB3b3JrcyB2ZXJ5IGZpbmUsIGFuZCBvZnRlbiBkZWxpdmVycyBtb3JlIHJvYnVzdCBjbHVzdGVycy4gCiogV2UgY291bGQgZG8gaXQgYnkgaGFuZCwgYnV0IHRoZSBgSENQQ2AgZnVuY3Rpb24gYWxyZWFkeSBkb2VzIHRoYXQgZm9yIHVzLCBhbmQgb2ZmZXJzIGFsc28gYSBuaWNlIGRpYWdub3N0aWMgdml6LgoKCmBgYHtyfQpyZXNfaGNwYyA8LSByZXNfcGNhICU+JSAKICBIQ1BDKG5iLmNsdXN0ID0gLTEsICMgIHNlbGYgZGV0ZXJtaW5lZDogaGlnaGVyIHJlbGF0aXZlIGxvc3Mgb2YgaW5lcnRpYQogICAgICAgZ3JhcGggPSBGQUxTRSkgCmBgYAoKYGBge3IsLGZpZy53aWR0aD0xNSxmaWcuaGVpZ2h0PTEwLGZpZy5hbGlnbj0nY2VudGVyJ30KcmVzX2hjcGMgJT4lCiAgcGxvdChjaG9pY2UgPSAiM0QubWFwIikKYGBgCgojIEVuZG5vdGVzCgoKIyMjIFBhY2thZ2VzICYgRWNvc3lzdGVtCgoqIFtmYWN0b21pbmVyXShodHRwOi8vZmFjdG9taW5lci5mcmVlLmZyLyk6IFZlcnkgd2VsbCBkb2N1bWVudGVkIHBhY2thZ2UgJiBlY29zeXN0ZW0gd2VicGFnZSB3aXRoIG1hbnkgZXhhbXBsZXMsIHR1dG9yaWFscywgYW5kIGZ1cnRoZXIgcmVzZW91cmNlcwoKIyMjIFN1Z2dlc3Rpb25zIGZvciBmdXJ0aGVyIHN0dWR5CgoqIERhdGFDYW1wCiAgKiBbVW5zdXBlcnZpc2VkIE1hY2hpbmUgTGVhcm5pbmcgd2l0aCBSXShodHRwczovL2xlYXJuLmRhdGFjYW1wLmNvbS9za2lsbC10cmFja3MvdW5zdXBlcnZpc2VkLW1hY2hpbmUtbGVhcm5pbmctd2l0aC1yKQoqIE90aGVyIG9ubGluZSBzb3VyY2VzCiAgICogW0V4cGxvcmF0b3J5IE11bHRpdmFyaWF0ZSBEYXRhIEFuYWx5c2lzIChNT09DKV0oaHR0cDovL2ZhY3RvbWluZXIuZnJlZS5mci9jb3Vyc2UvTU9PQy5odG1sKQogICAqIEZ1cnRoZXJtb3JlLCBncmFwaGljYWwgaW5zcGVjdGlvbiBvZnRlbiBoZWxwcyBjb21wYXJpbmcgdGhlIHJlc3VsdHMgb2YgZGlmZmVyZW50IGFsZ29yaXRobXMgYW5kIHBhcmFtZXRlcnMuIFtIZXJlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZGVuZGV4dGVuZC92aWduZXR0ZXMvQ2x1c3Rlcl9BbmFseXNpcy5odG1sKSB5b3UgZmluZCBzb21lIGFkdmFuY2VkIGRpYWdub3N0aWMgdmlzdWFsaXphdGlvbnMgZm9yIGhpcmFyY2hpY2FsIGNsdXN0ZXJpbmcuCiAgCiMjIyBTZXNzaW9uIEluZm8KYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgYAo=