1 Introduction

1.1 Objective

Objective: grouping of observations into clusters, so that

  • similar observations appear in the same cluster
  • dissimilar observations appear in distinct clusters

\(\longrightarrow\) need for a measure for similarity and dissimilarity?

1.2 Example 1

Single cell transcriptomics: \(n \times p\) Matrix for which

  • every column contains the expression levels of one of \(p\) genes for \(n\) cells

  • every row contains the expression levels of \(p\) genes for one cell (sample)

  • Research question: look for groups of cells that have similar gene expression patterns

  • Or, look for groups of genes that have similar expression levels across the different cells. This can help us in understanding the regulation and functionality of the genes.

\(\longrightarrow\) both observations (rows) and variables (columns) can be clustered

1.3 Example 2

Abundance studies: the abundances of \(n\) plant species are counted on \(p\) plots (habitats)

  • look for groups that contain species that live in the same habitats, or, look for groups of habitats that have similar species communities

\(\longrightarrow\) both observations (rows) and variables (columns) can be clustered

2 Partition Based Cluster Analysis

  • Partition based cluster methods require the number of clusters (k) to be specified prior to the start of the algorithm.

2.1 K-means Methods

  • To use the k-means clustering algorithm we have to pre-define \(k\), the number of clusters we want to define.

  • The k-means algorithm is iterative.

  • The algorithm starts by defining k cluster centers (centroids).

  • Then the algorithm proceeds as follows

    1. First each observation is assigned to the cluster with the closest center to that observation.
    2. Then the k centers are redefined using the observations in each cluster, i.e. the multivariate means (column means) of all observations in a cluster are used to define each new cluster center.
    3. We repeat these two steps until the centers converge.

2.2 Example

library(tidyverse)
data(diabetes,package = "mclust")
class <- diabetes$class
table(class)
#> class
#> Chemical   Normal    Overt 
#>       36       76       33
head(diabetes)
mclust::clPairs(diabetes[,-1], diabetes$class)

diabetesKmeans <- kmeans(diabetes[,-1], centers = 3)
diabetesKmeans
#> K-means clustering with 3 clusters of sizes 26, 25, 94
#> 
#> Cluster means:
#>     glucose   insulin      sspg
#> 1 241.65385 1152.8846  75.69231
#> 2 105.04000  525.6000 375.96000
#> 3  93.39362  375.5213 166.17021
#> 
#> Clustering vector:
#>   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18  19  20 
#>   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3 
#>  21  22  23  24  25  26  27  28  29  30  31  32  33  34  35  36  37  38  39  40 
#>   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3 
#>  41  42  43  44  45  46  47  48  49  50  51  52  53  54  55  56  57  58  59  60 
#>   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3   3 
#>  61  62  63  64  65  66  67  68  69  70  71  72  73  74  75  76  77  78  79  80 
#>   3   3   3   3   3   3   3   3   2   3   2   3   3   3   3   3   3   3   3   3 
#>  81  82  83  84  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99 100 
#>   3   2   3   3   3   2   2   3   2   2   2   2   2   2   2   3   2   2   2   2 
#> 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 
#>   2   2   3   3   3   2   3   2   3   3   2   3   1   1   2   1   1   1   1   1 
#> 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 
#>   1   1   1   3   1   1   1   1   1   1   2   1   1   3   3   2   2   1   1   1 
#> 141 142 143 144 145 
#>   1   1   1   1   1 
#> 
#> Within cluster sum of squares by cluster:
#> [1] 1738796.1  687281.9  947827.2
#>  (between_SS / total_SS =  80.6 %)
#> 
#> Available components:
#> 
#> [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
#> [6] "betweenss"    "size"         "iter"         "ifault"
mclust::clPairs(diabetes[,-1], diabetes$class, main = "Real"); mclust::clPairs(diabetes[,-1], diabetesKmeans$cluster,colors = c(2,3,4),symbols = c(0,17,16), main="K-means")

3 Hierarchical Cluster Analysis

  • Distinction between agglomerative and divisive methods
  • Agglomerative start from the situation where each individual observations forms its own cluster (so it starts with n clusters). In the next steps clusters are sequentially merged, until finally there is only one cluster with n observations.
  • Divisive methods work just the other way around.
  • The solution of an hierarchical clustering is thus a sequence of n nested cluster solutions.

3.1 General Algorithm of Agglomerative Hierarchical Clustering

  • In step 0 each observations is considered as a cluster (i.e. \(n\) clusters).

  • Every next step consists of:

    1. merge the two clusters with the smallest intercluster dissimilarity
    2. recalculate the intercluster dissimilarities

In step 0 the intercluster dissimilarity coincides with the dissimilarity between the corresponding observations

\(\rightarrow\) intercluster dissimilarity?

3.2 Intercluster Dissimilarities

  • Represent clusters (e.g. \(C_1\) and \(C_2\)) as sets of points \(\mathbf{x}_i\) which belong to that cluster

  • \(d(C_1,C_2)\): intercluster dissimilarity between

We consider three intercluster dissimilarities.

3.2.1 Single Linkage = Nearest Neighbour

\[ d(C_1,C_2) = \min_{\mathbf{x}_1 \in C_1; \mathbf{x}_2 \in C_2} d(\mathbf{x}_1,\mathbf{x}_2) , \]

i.e. the dissimilarity between \(C_1\) and \(C_2\) is determined by the smallest dissimilarity between a point of \(C_1\) and a point of \(C_2\).

3.2.2 Complete Linkage = Furthest Neighbour

\[ d(C_1,C_2) = \max_{\mathbf{x}_1 \in C_1; \mathbf{x}_2 \in C_2} d(\mathbf{x}_1,\mathbf{x}_2) , \] i.e. the dissimilarity between \(C_1\) and \(C_2\) is determined by the largest dissimilarity between a point of \(C_1\) and a point of \(C_2\).

3.2.3 Average Linkage = Group Average

\[ d(C_1,C_2) = \frac{1}{\vert C_1 \vert \vert C_2 \vert} \sum_{\mathbf{x}_1 \in C_1; \mathbf{x}_2 \in C_2} d(\mathbf{x}_1,\mathbf{x}_2) , \] i.e. the dissimilarity between \(C_1\) and \(C_2\) is determined by the average dissimilarity between all points of \(C_1\) and all points of \(C_2\).

3.3 Cluster Tree

Hierarchical nature of the algorithm:

  • Nested sequence of clusters \(\longrightarrow\) visualisation via a tree

  • Height of branches indicate the intercluster dissimilarity at which clusters are merged.

  • Can used as instrument for deciding the number of clusters in the data

4 Toy example

X1 X2 label
1.50 2.40 1
2.00 2.50 2
2.50 2.25 3
2.00 3.00 4
2.25 3.20 5
toy %>%
  ggplot(aes(X1, X2, label = label)) +
  geom_point() +
  geom_text(nudge_x = .05)

toy[,1:2] %>% dist
#>           1         2         3         4
#> 2 0.5099020                              
#> 3 1.0111874 0.5590170                    
#> 4 0.7810250 0.5000000 0.9013878          
#> 5 1.0965856 0.7433034 0.9823441 0.3201562

4.1 Single linkage

toyDist <- toy[,1:2] %>% dist
toySingle <- hclust(toyDist, method = "single")
par(mfrow=c(1,2),pty="s")
plot(X2 ~ X1, toy, xlim = c(1.25,2.75),ylim = c(2,3.5))
text(toy$X1*1.05,toy$X2,label=toy$label)
plot(toySingle, main = "Single")

toyDist
#>           1         2         3         4
#> 2 0.5099020                              
#> 3 1.0111874 0.5590170                    
#> 4 0.7810250 0.5000000 0.9013878          
#> 5 1.0965856 0.7433034 0.9823441 0.3201562

4.2 Complete linkage

toyComplete <- hclust(toyDist, method = "complete")
par(mfrow=c(1,2),pty="s")
plot(X2 ~ X1, toy, xlim = c(1.25,2.75),ylim = c(2,3.5))
text(toy$X1*1.05,toy$X2,label=toy$label)
plot(toyComplete,  main = "Complete")

toyDist
#>           1         2         3         4
#> 2 0.5099020                              
#> 3 1.0111874 0.5590170                    
#> 4 0.7810250 0.5000000 0.9013878          
#> 5 1.0965856 0.7433034 0.9823441 0.3201562

4.3 Average linkage

toyAvg <- hclust(toyDist, method = "average")
par(mfrow=c(1,2),pty="s")
plot(X2 ~ X1, toy, xlim = c(1.25,2.75),ylim = c(2,3.5))
text(toy$X1*1.05,toy$X2,label=toy$label)
plot(toyAvg, main = "Average")

toyDist
#>           1         2         3         4
#> 2 0.5099020                              
#> 3 1.0111874 0.5590170                    
#> 4 0.7810250 0.5000000 0.9013878          
#> 5 1.0965856 0.7433034 0.9823441 0.3201562

4.4 Example

diabetesDist <- dist(diabetes[,-1])
diabetesSingle <- hclust(diabetesDist, method = "single")
plot(diabetesSingle, labels = as.double(diabetes$class), main="single")

diabetesComplete <- hclust(diabetesDist, method = "complete")
plot(diabetesComplete, labels = as.double(diabetes$class), main="complete")

diabetesAverage <- hclust(diabetesDist, method = "average")
plot(diabetesAverage, labels = as.double(diabetes$class), main = "average",cex=0.5)

Session info

Session info
#> [1] "2024-10-07 12:41:04 CEST"
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value
#>  version  R version 4.4.0 RC (2024-04-16 r86468)
#>  os       macOS Big Sur 11.6
#>  system   aarch64, darwin20
#>  ui       X11
#>  language (EN)
#>  collate  en_US.UTF-8
#>  ctype    en_US.UTF-8
#>  tz       Europe/Brussels
#>  date     2024-10-07
#>  pandoc   3.1.1 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/ (via rmarkdown)
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package     * version date (UTC) lib source
#>  bookdown      0.40    2024-07-02 [1] CRAN (R 4.4.0)
#>  bslib         0.8.0   2024-07-29 [1] CRAN (R 4.4.0)
#>  cachem        1.1.0   2024-05-16 [1] CRAN (R 4.4.0)
#>  cli           3.6.3   2024-06-21 [1] CRAN (R 4.4.0)
#>  colorspace    2.1-1   2024-07-26 [1] CRAN (R 4.4.0)
#>  digest        0.6.37  2024-08-19 [1] CRAN (R 4.4.1)
#>  dplyr       * 1.1.4   2023-11-17 [1] CRAN (R 4.4.0)
#>  evaluate      1.0.0   2024-09-17 [1] CRAN (R 4.4.1)
#>  fansi         1.0.6   2023-12-08 [1] CRAN (R 4.4.0)
#>  farver        2.1.2   2024-05-13 [1] CRAN (R 4.4.0)
#>  fastmap       1.2.0   2024-05-15 [1] CRAN (R 4.4.0)
#>  forcats     * 1.0.0   2023-01-29 [1] CRAN (R 4.4.0)
#>  generics      0.1.3   2022-07-05 [1] CRAN (R 4.4.0)
#>  ggplot2     * 3.5.1   2024-04-23 [1] CRAN (R 4.4.0)
#>  glue          1.8.0   2024-09-30 [1] CRAN (R 4.4.1)
#>  gtable        0.3.5   2024-04-22 [1] CRAN (R 4.4.0)
#>  highr         0.11    2024-05-26 [1] CRAN (R 4.4.0)
#>  hms           1.1.3   2023-03-21 [1] CRAN (R 4.4.0)
#>  htmltools     0.5.8.1 2024-04-04 [1] CRAN (R 4.4.0)
#>  jquerylib     0.1.4   2021-04-26 [1] CRAN (R 4.4.0)
#>  jsonlite      1.8.9   2024-09-20 [1] CRAN (R 4.4.1)
#>  knitr         1.48    2024-07-07 [1] CRAN (R 4.4.0)
#>  labeling      0.4.3   2023-08-29 [1] CRAN (R 4.4.0)
#>  lifecycle     1.0.4   2023-11-07 [1] CRAN (R 4.4.0)
#>  lubridate   * 1.9.3   2023-09-27 [1] CRAN (R 4.4.0)
#>  magrittr      2.0.3   2022-03-30 [1] CRAN (R 4.4.0)
#>  mclust        6.1.1   2024-04-29 [1] CRAN (R 4.4.0)
#>  munsell       0.5.1   2024-04-01 [1] CRAN (R 4.4.0)
#>  pillar        1.9.0   2023-03-22 [1] CRAN (R 4.4.0)
#>  pkgconfig     2.0.3   2019-09-22 [1] CRAN (R 4.4.0)
#>  purrr       * 1.0.2   2023-08-10 [1] CRAN (R 4.4.0)
#>  R6            2.5.1   2021-08-19 [1] CRAN (R 4.4.0)
#>  readr       * 2.1.5   2024-01-10 [1] CRAN (R 4.4.0)
#>  rlang         1.1.4   2024-06-04 [1] CRAN (R 4.4.0)
#>  rmarkdown     2.28    2024-08-17 [1] CRAN (R 4.4.0)
#>  rstudioapi    0.16.0  2024-03-24 [1] CRAN (R 4.4.0)
#>  sass          0.4.9   2024-03-15 [1] CRAN (R 4.4.0)
#>  scales        1.3.0   2023-11-28 [1] CRAN (R 4.4.0)
#>  sessioninfo   1.2.2   2021-12-06 [1] CRAN (R 4.4.0)
#>  stringi       1.8.4   2024-05-06 [1] CRAN (R 4.4.0)
#>  stringr     * 1.5.1   2023-11-14 [1] CRAN (R 4.4.0)
#>  tibble      * 3.2.1   2023-03-20 [1] CRAN (R 4.4.0)
#>  tidyr       * 1.3.1   2024-01-24 [1] CRAN (R 4.4.0)
#>  tidyselect    1.2.1   2024-03-11 [1] CRAN (R 4.4.0)
#>  tidyverse   * 2.0.0   2023-02-22 [1] CRAN (R 4.4.0)
#>  timechange    0.3.0   2024-01-18 [1] CRAN (R 4.4.0)
#>  tzdb          0.4.0   2023-05-12 [1] CRAN (R 4.4.0)
#>  utf8          1.2.4   2023-10-22 [1] CRAN (R 4.4.0)
#>  vctrs         0.6.5   2023-12-01 [1] CRAN (R 4.4.0)
#>  withr         3.0.1   2024-07-31 [1] CRAN (R 4.4.0)
#>  xfun          0.47    2024-08-17 [1] CRAN (R 4.4.0)
#>  yaml          2.3.10  2024-07-26 [1] CRAN (R 4.4.0)
#> 
#>  [1] /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library
#> 
#> ──────────────────────────────────────────────────────────────────────────────
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIENsdXN0ZXJpbmciCmF1dGhvcjogIkxpZXZlbiBDbGVtZW50IgpvdXRwdXQ6CiAgYm9va2Rvd246Omh0bWxfZG9jdW1lbnQyOgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgYm9va2Rvd246OnBkZl9kb2N1bWVudDI6CiAgICB0b2M6IHRydWUKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgbGF0ZXhfZW5naW5lOiB4ZWxhdGV4Ci0tLQoKYGBge3IsIGNoaWxkPSJfc2V0dXAuUm1kIn0KYGBgCgojIEludHJvZHVjdGlvbgojIyBPYmplY3RpdmUKCk9iamVjdGl2ZTogZ3JvdXBpbmcgb2Ygb2JzZXJ2YXRpb25zIGludG8gKipjbHVzdGVycyoqLCBzbyB0aGF0CgotIHNpbWlsYXIgb2JzZXJ2YXRpb25zIGFwcGVhciBpbiB0aGUgc2FtZSBjbHVzdGVyCi0gZGlzc2ltaWxhciBvYnNlcnZhdGlvbnMgYXBwZWFyIGluIGRpc3RpbmN0IGNsdXN0ZXJzCgokXGxvbmdyaWdodGFycm93JCBuZWVkIGZvciBhIG1lYXN1cmUgZm9yICoqc2ltaWxhcml0eSoqIGFuZCAqKmRpc3NpbWlsYXJpdHkqKj8KCgojIyBFeGFtcGxlIDEKClNpbmdsZSBjZWxsIHRyYW5zY3JpcHRvbWljczogICRuIFx0aW1lcyBwJCBNYXRyaXggZm9yIHdoaWNoCgogIC0gZXZlcnkgY29sdW1uIGNvbnRhaW5zIHRoZSBleHByZXNzaW9uIGxldmVscyBvZiBvbmUgb2YgJHAkIGdlbmVzIGZvciAkbiQgY2VsbHMKCiAgLSBldmVyeSByb3cgY29udGFpbnMgdGhlIGV4cHJlc3Npb24gbGV2ZWxzIG9mICRwJCBnZW5lcyBmb3Igb25lIGNlbGwgKCoqc2FtcGxlKiopCgogIC0gUmVzZWFyY2ggcXVlc3Rpb246IGxvb2sgZm9yIGdyb3VwcyBvZiBjZWxscyB0aGF0IGhhdmUgc2ltaWxhciBnZW5lIGV4cHJlc3Npb24gcGF0dGVybnMKCiAtIE9yLCBsb29rIGZvciBncm91cHMgb2YgZ2VuZXMgdGhhdCBoYXZlIHNpbWlsYXIgZXhwcmVzc2lvbiBsZXZlbHMgYWNyb3NzIHRoZSBkaWZmZXJlbnQgY2VsbHMuIFRoaXMgY2FuCiBoZWxwIHVzIGluIHVuZGVyc3RhbmRpbmcgdGhlIHJlZ3VsYXRpb24gYW5kIGZ1bmN0aW9uYWxpdHkgb2YgdGhlIGdlbmVzLgoKICRcbG9uZ3JpZ2h0YXJyb3ckIGJvdGggKipvYnNlcnZhdGlvbnMqKiAocm93cykgYW5kICoqdmFyaWFibGVzKiogKGNvbHVtbnMpIGNhbiBiZSBjbHVzdGVyZWQKCgojIyBFeGFtcGxlIDIKCkFidW5kYW5jZSBzdHVkaWVzOiB0aGUgYWJ1bmRhbmNlcyBvZiAkbiQgcGxhbnQgc3BlY2llcyBhcmUgY291bnRlZCBvbiAkcCQgcGxvdHMgKGhhYml0YXRzKQoKICAtIGxvb2sgZm9yIGdyb3VwcyB0aGF0IGNvbnRhaW4gc3BlY2llcyB0aGF0IGxpdmUgaW4gdGhlIHNhbWUgaGFiaXRhdHMsIG9yLCBsb29rIGZvciBncm91cHMgb2YKIGhhYml0YXRzIHRoYXQgaGF2ZSBzaW1pbGFyIHNwZWNpZXMgY29tbXVuaXRpZXMKCiRcbG9uZ3JpZ2h0YXJyb3ckIGJvdGggKipvYnNlcnZhdGlvbnMqKiAocm93cykgYW5kICoqdmFyaWFibGVzKiogKGNvbHVtbnMpIGNhbiBiZSBjbHVzdGVyZWQKCiMgUGFydGl0aW9uIEJhc2VkIENsdXN0ZXIgQW5hbHlzaXMKCi0gUGFydGl0aW9uIGJhc2VkIGNsdXN0ZXIgbWV0aG9kcyByZXF1aXJlIHRoZSBudW1iZXIgb2YgY2x1c3RlcnMgKGspIHRvIGJlIHNwZWNpZmllZCBwcmlvciB0byB0aGUgc3RhcnQgb2YgdGhlIGFsZ29yaXRobS4KCiMjIEstbWVhbnMgTWV0aG9kcwoKLSBUbyB1c2UgdGhlIGstbWVhbnMgY2x1c3RlcmluZyBhbGdvcml0aG0gd2UgaGF2ZSB0byBwcmUtZGVmaW5lICRrJCwgdGhlIG51bWJlciBvZiBjbHVzdGVycyB3ZSB3YW50IHRvIGRlZmluZS4KLSBUaGUgay1tZWFucyBhbGdvcml0aG0gaXMgaXRlcmF0aXZlLgotIFRoZSBhbGdvcml0aG0gc3RhcnRzIGJ5IGRlZmluaW5nIGsgY2x1c3RlciBjZW50ZXJzIChjZW50cm9pZHMpLgotIFRoZW4gdGhlIGFsZ29yaXRobSBwcm9jZWVkcyBhcyBmb2xsb3dzCgogICAgMS4gRmlyc3QgZWFjaCBvYnNlcnZhdGlvbiBpcyBhc3NpZ25lZCB0byB0aGUgY2x1c3RlciB3aXRoIHRoZSBjbG9zZXN0IGNlbnRlciB0byB0aGF0IG9ic2VydmF0aW9uLgogICAgMi4gVGhlbiB0aGUgayBjZW50ZXJzIGFyZSByZWRlZmluZWQgdXNpbmcgdGhlIG9ic2VydmF0aW9ucyBpbiBlYWNoIGNsdXN0ZXIsIGkuZS4gdGhlIG11bHRpdmFyaWF0ZSBtZWFucyAoY29sdW1uIG1lYW5zKSBvZiBhbGwgb2JzZXJ2YXRpb25zIGluIGEgY2x1c3RlciBhcmUgdXNlZCB0byBkZWZpbmUgZWFjaCBuZXcgY2x1c3RlciBjZW50ZXIuCiAgICAzLiBXZSByZXBlYXQgdGhlc2UgdHdvIHN0ZXBzIHVudGlsIHRoZSBjZW50ZXJzIGNvbnZlcmdlLgoKIyMgRXhhbXBsZQoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpkYXRhKGRpYWJldGVzLHBhY2thZ2UgPSAibWNsdXN0IikKY2xhc3MgPC0gZGlhYmV0ZXMkY2xhc3MKdGFibGUoY2xhc3MpCmhlYWQoZGlhYmV0ZXMpCmBgYApgYGB7cn0KbWNsdXN0OjpjbFBhaXJzKGRpYWJldGVzWywtMV0sIGRpYWJldGVzJGNsYXNzKQpgYGAKCmBgYHtyfQpkaWFiZXRlc0ttZWFucyA8LSBrbWVhbnMoZGlhYmV0ZXNbLC0xXSwgY2VudGVycyA9IDMpCmRpYWJldGVzS21lYW5zCmBgYAoKYGBge3Igb3V0LndpZHRoPSI0OSUifQptY2x1c3Q6OmNsUGFpcnMoZGlhYmV0ZXNbLC0xXSwgZGlhYmV0ZXMkY2xhc3MsIG1haW4gPSAiUmVhbCIpOyBtY2x1c3Q6OmNsUGFpcnMoZGlhYmV0ZXNbLC0xXSwgZGlhYmV0ZXNLbWVhbnMkY2x1c3Rlcixjb2xvcnMgPSBjKDIsMyw0KSxzeW1ib2xzID0gYygwLDE3LDE2KSwgbWFpbj0iSy1tZWFucyIpCmBgYAoKIyBIaWVyYXJjaGljYWwgQ2x1c3RlciBBbmFseXNpcwoKLSBEaXN0aW5jdGlvbiBiZXR3ZWVuIGFnZ2xvbWVyYXRpdmUgYW5kIGRpdmlzaXZlIG1ldGhvZHMKLSBBZ2dsb21lcmF0aXZlIHN0YXJ0IGZyb20gdGhlIHNpdHVhdGlvbiB3aGVyZSBlYWNoIGluZGl2aWR1YWwgb2JzZXJ2YXRpb25zIGZvcm1zIGl0cyBvd24gY2x1c3RlciAoc28gaXQgc3RhcnRzIHdpdGggbiBjbHVzdGVycykuIEluIHRoZSBuZXh0IHN0ZXBzIGNsdXN0ZXJzIGFyZSBzZXF1ZW50aWFsbHkgbWVyZ2VkLCB1bnRpbCBmaW5hbGx5IHRoZXJlIGlzIG9ubHkgb25lIGNsdXN0ZXIgd2l0aCBuIG9ic2VydmF0aW9ucy4KLSBEaXZpc2l2ZSBtZXRob2RzIHdvcmsganVzdCB0aGUgb3RoZXIgd2F5IGFyb3VuZC4KLSBUaGUgc29sdXRpb24gb2YgYW4gaGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcgaXMgdGh1cyBhIHNlcXVlbmNlIG9mIG4gbmVzdGVkIGNsdXN0ZXIgc29sdXRpb25zLgoKIyMgR2VuZXJhbCBBbGdvcml0aG0gb2YgQWdnbG9tZXJhdGl2ZSBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZwoKLSBJbiBzdGVwIDAgZWFjaCBvYnNlcnZhdGlvbnMgaXMgY29uc2lkZXJlZCBhcyBhIGNsdXN0ZXIgKGkuZS4gJG4kIGNsdXN0ZXJzKS4KCi0gRXZlcnkgbmV4dCBzdGVwIGNvbnNpc3RzIG9mOgoKICAgMS4gbWVyZ2UgdGhlIHR3byBjbHVzdGVycyB3aXRoIHRoZSBzbWFsbGVzdCBpbnRlcmNsdXN0ZXIgZGlzc2ltaWxhcml0eQogICAyLiByZWNhbGN1bGF0ZSB0aGUgaW50ZXJjbHVzdGVyIGRpc3NpbWlsYXJpdGllcwoKSW4gc3RlcCAwIHRoZSBpbnRlcmNsdXN0ZXIgZGlzc2ltaWxhcml0eSBjb2luY2lkZXMgd2l0aCB0aGUgZGlzc2ltaWxhcml0eSBiZXR3ZWVuIHRoZSBjb3JyZXNwb25kaW5nIG9ic2VydmF0aW9ucwoKJFxyaWdodGFycm93JCBpbnRlcmNsdXN0ZXIgZGlzc2ltaWxhcml0eT8KCiMjIEludGVyY2x1c3RlciBEaXNzaW1pbGFyaXRpZXMKCi0gUmVwcmVzZW50IGNsdXN0ZXJzIChlLmcuICRDXzEkIGFuZCAkQ18yJCkKICAgYXMgc2V0cyBvZiBwb2ludHMgJFxtYXRoYmZ7eH1faSQgd2hpY2ggYmVsb25nIHRvIHRoYXQgY2x1c3RlcgoKLSAkZChDXzEsQ18yKSQ6IGludGVyY2x1c3RlciBkaXNzaW1pbGFyaXR5IGJldHdlZW4KCldlIGNvbnNpZGVyIHRocmVlIGludGVyY2x1c3RlciBkaXNzaW1pbGFyaXRpZXMuCgojIyMgU2luZ2xlIExpbmthZ2UgPSBOZWFyZXN0IE5laWdoYm91cgoKXFsKICBkKENfMSxDXzIpID0gXG1pbl97XG1hdGhiZnt4fV8xIFxpbiBDXzE7IFxtYXRoYmZ7eH1fMiBcaW4gQ18yfQogIGQoXG1hdGhiZnt4fV8xLFxtYXRoYmZ7eH1fMikgLApcXQoKaS5lLiB0aGUgZGlzc2ltaWxhcml0eSBiZXR3ZWVuICRDXzEkIGFuZCAkQ18yJCBpcyBkZXRlcm1pbmVkIGJ5IHRoZSBzbWFsbGVzdCBkaXNzaW1pbGFyaXR5IGJldHdlZW4gYSBwb2ludCBvZiAkQ18xJCBhbmQgYSBwb2ludCBvZiAkQ18yJC4KCmBgYHtyLCBlY2hvPUZBTFNFLCBvdXQud2lkdGg9JzcwJSd9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCIuL2ZpZ3VyZXMvaGNsdXN0TmVhcmVzdC5wbmciKQpgYGAKCiMjIyBDb21wbGV0ZSBMaW5rYWdlID0gRnVydGhlc3QgTmVpZ2hib3VyCiAgIFxbCiAgICBkKENfMSxDXzIpID0gXG1heF97XG1hdGhiZnt4fV8xIFxpbiBDXzE7IFxtYXRoYmZ7eH1fMiBcaW4gQ18yfQogICAgZChcbWF0aGJme3h9XzEsXG1hdGhiZnt4fV8yKSAsCiAgIFxdCiAgIGkuZS4gdGhlIGRpc3NpbWlsYXJpdHkgYmV0d2VlbiAkQ18xJCBhbmQgJENfMiQgaXMgZGV0ZXJtaW5lZCBieSB0aGUgbGFyZ2VzdCBkaXNzaW1pbGFyaXR5IGJldHdlZW4gYSBwb2ludCBvZiAkQ18xJCBhbmQgYQogICBwb2ludCBvZiAkQ18yJC4KCgpgYGB7ciwgZWNobz1GQUxTRSwgb3V0LndpZHRoPSc3MCUnfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiLi9maWd1cmVzL2hjbHVzdEZ1cnRoZXN0LnBuZyIpCmBgYAoKIyMjIEF2ZXJhZ2UgTGlua2FnZSA9IEdyb3VwIEF2ZXJhZ2UKCiAgIFxbCiAgICBkKENfMSxDXzIpID0gXGZyYWN7MX17XHZlcnQgQ18xIFx2ZXJ0IFx2ZXJ0IENfMiBcdmVydH0KICAgIFxzdW1fe1xtYXRoYmZ7eH1fMSBcaW4gQ18xOyBcbWF0aGJme3h9XzIgXGluIENfMn0KICAgIGQoXG1hdGhiZnt4fV8xLFxtYXRoYmZ7eH1fMikgLAogICBcXQogICBpLmUuIHRoZSBkaXNzaW1pbGFyaXR5IGJldHdlZW4gJENfMSQgYW5kICRDXzIkIGlzIGRldGVybWluZWQgYnkgdGhlIGF2ZXJhZ2UgZGlzc2ltaWxhcml0eSBiZXR3ZWVuIGFsbCBwb2ludHMgb2YgJENfMSQgYW5kIGFsbAogICBwb2ludHMgb2YgJENfMiQuCgpgYGB7ciwgZWNobz1GQUxTRSwgb3V0LndpZHRoPSc3MCUnfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiLi9maWd1cmVzL2hjbHVzdEF2ZXJhZ2UucG5nIikKYGBgCgoKIyMgQ2x1c3RlciBUcmVlCgpIaWVyYXJjaGljYWwgbmF0dXJlIG9mIHRoZSBhbGdvcml0aG06CgotIE5lc3RlZCBzZXF1ZW5jZSBvZiBjbHVzdGVycyAkXGxvbmdyaWdodGFycm93JCB2aXN1YWxpc2F0aW9uIHZpYSBhIHRyZWUKCgotIEhlaWdodCBvZiBicmFuY2hlcyBpbmRpY2F0ZSB0aGUgaW50ZXJjbHVzdGVyIGRpc3NpbWlsYXJpdHkgYXQgd2hpY2ggY2x1c3RlcnMgYXJlIG1lcmdlZC4KCi0gQ2FuIHVzZWQgYXMgaW5zdHJ1bWVudCBmb3IgZGVjaWRpbmcgdGhlIG51bWJlciBvZiBjbHVzdGVycyBpbiB0aGUgZGF0YQoKCiMgVG95IGV4YW1wbGUKCgpgYGB7ciBlY2hvID0gRkFMU0V9CnRveSA8LSBkYXRhLmZyYW1lKAogIFgxID0gYygxLjUwLAogICAgICAgICAyLjAwLAogICAgICAgICAyLjUwLAogICAgICAgICAyLjAwLAogICAgICAgICAyLjI1KSwKICBYMiA9IGMoMi40MCwKICAgICAgICAgMi41MCwKICAgICAgICAgMi4yNSwKICAgICAgICAgMy4wMCwKICAgICAgICAgMy4yMCksCiAgbGFiZWwgPSAxOjUKICApCgprbml0cjo6a2FibGUodG95KQpgYGAKCmBgYHtyfQp0b3kgJT4lCiAgZ2dwbG90KGFlcyhYMSwgWDIsIGxhYmVsID0gbGFiZWwpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX3RleHQobnVkZ2VfeCA9IC4wNSkKCnRveVssMToyXSAlPiUgZGlzdApgYGAKCiMjIFNpbmdsZSBsaW5rYWdlCgpgYGB7cn0KdG95RGlzdCA8LSB0b3lbLDE6Ml0gJT4lIGRpc3QKdG95U2luZ2xlIDwtIGhjbHVzdCh0b3lEaXN0LCBtZXRob2QgPSAic2luZ2xlIikKcGFyKG1mcm93PWMoMSwyKSxwdHk9InMiKQpwbG90KFgyIH4gWDEsIHRveSwgeGxpbSA9IGMoMS4yNSwyLjc1KSx5bGltID0gYygyLDMuNSkpCnRleHQodG95JFgxKjEuMDUsdG95JFgyLGxhYmVsPXRveSRsYWJlbCkKcGxvdCh0b3lTaW5nbGUsIG1haW4gPSAiU2luZ2xlIikKdG95RGlzdApgYGAKCiMjIENvbXBsZXRlIGxpbmthZ2UKCmBgYHtyfQp0b3lDb21wbGV0ZSA8LSBoY2x1c3QodG95RGlzdCwgbWV0aG9kID0gImNvbXBsZXRlIikKcGFyKG1mcm93PWMoMSwyKSxwdHk9InMiKQpwbG90KFgyIH4gWDEsIHRveSwgeGxpbSA9IGMoMS4yNSwyLjc1KSx5bGltID0gYygyLDMuNSkpCnRleHQodG95JFgxKjEuMDUsdG95JFgyLGxhYmVsPXRveSRsYWJlbCkKcGxvdCh0b3lDb21wbGV0ZSwgIG1haW4gPSAiQ29tcGxldGUiKQp0b3lEaXN0CmBgYAoKIyMgQXZlcmFnZSBsaW5rYWdlCgpgYGB7cn0KdG95QXZnIDwtIGhjbHVzdCh0b3lEaXN0LCBtZXRob2QgPSAiYXZlcmFnZSIpCnBhcihtZnJvdz1jKDEsMikscHR5PSJzIikKcGxvdChYMiB+IFgxLCB0b3ksIHhsaW0gPSBjKDEuMjUsMi43NSkseWxpbSA9IGMoMiwzLjUpKQp0ZXh0KHRveSRYMSoxLjA1LHRveSRYMixsYWJlbD10b3kkbGFiZWwpCnBsb3QodG95QXZnLCBtYWluID0gIkF2ZXJhZ2UiKQp0b3lEaXN0CmBgYAoKIyMgRXhhbXBsZQoKYGBge3J9CmRpYWJldGVzRGlzdCA8LSBkaXN0KGRpYWJldGVzWywtMV0pCmRpYWJldGVzU2luZ2xlIDwtIGhjbHVzdChkaWFiZXRlc0Rpc3QsIG1ldGhvZCA9ICJzaW5nbGUiKQpwbG90KGRpYWJldGVzU2luZ2xlLCBsYWJlbHMgPSBhcy5kb3VibGUoZGlhYmV0ZXMkY2xhc3MpLCBtYWluPSJzaW5nbGUiKQpkaWFiZXRlc0NvbXBsZXRlIDwtIGhjbHVzdChkaWFiZXRlc0Rpc3QsIG1ldGhvZCA9ICJjb21wbGV0ZSIpCnBsb3QoZGlhYmV0ZXNDb21wbGV0ZSwgbGFiZWxzID0gYXMuZG91YmxlKGRpYWJldGVzJGNsYXNzKSwgbWFpbj0iY29tcGxldGUiKQpkaWFiZXRlc0F2ZXJhZ2UgPC0gaGNsdXN0KGRpYWJldGVzRGlzdCwgbWV0aG9kID0gImF2ZXJhZ2UiKQpwbG90KGRpYWJldGVzQXZlcmFnZSwgbGFiZWxzID0gYXMuZG91YmxlKGRpYWJldGVzJGNsYXNzKSwgbWFpbiA9ICJhdmVyYWdlIixjZXg9MC41KQpgYGAKCiMgTW9kZWwtYmFzZWQgY2x1c3RlcmluZwoKLSBbUGFwZXI6IEZyYWxleSBhbmQgUmFmdGVyeSAoMTk5OCkuIEhvdyBNYW55IENsdXN0ZXJzPyBXaGljaCBDbHVzdGVyaW5nIE1ldGhvZD8gQW5zd2VycyBWaWEgTW9kZWwtQmFzZWQgQ2x1c3RlciBBbmFseXNpcy4gVGhlIENvbXB1dGVyIEpvdXJuYWwsICg0MSk4OjU3OC01ODguXShodHRwczovL3NpdGVzLnN0YXQud2FzaGluZ3Rvbi5lZHUvcGVvcGxlL3JhZnRlcnkvUmVzZWFyY2gvUERGL2ZyYWxleTE5OTgucGRmKQoKLSBbRU0gYWxnb3JpdGhtXSguL2VtLmh0bWwpIFtbUERGXSguL2VtLnBkZildCgotIEV4YW1wbGU6IHNlZSB0dXRvcmlhbCBzZXNzaW9uCgpgYGB7ciwgY2hpbGQ9Il9zZXNzaW9uLWluZm8uUm1kIn0KYGBgCg==