Change log


## Install necessary packages with:

# install.packages(c("mclust", "gclus", "tidyverse", "gridExtra"))

# if (!requireNamespace("remotes", quietly = TRUE)) {
#     install.packages("remotes")
# }
# remotes::install_github("vqv/ggbiplot")

library(mclust)
library(gclus)  # contains the 'wine' data
library(ggbiplot)
library(tidyverse)
library(gridExtra)

theme_set(theme_minimal())

1 The wine data

In this lab session, we will explore the wine data, following the example analysis from Scrucca et al. (2016).

This dataset provides 13 measurements obtained from a chemical analysis of 178 wines grown in the same region in Italy but derived from three different cultivars (Barolo, Grignolino, Barbera). The original cultivar labels are provided in the dataset.

We will apply different clustering algorithms and validate them by comparing how well the clusters capture the original classes.

data("wine", package = "gclus")
class <- factor(wine$Class, levels = 1:3, labels = c("Barolo", "Grignolino", "Barbera"))
table(class)
#> class
#>     Barolo Grignolino    Barbera 
#>         59         71         48

X <- as.matrix(wine[, -1])
summary(X)
#>     Alcohol          Malic            Ash          Alcalinity   
#>  Min.   :11.03   Min.   :0.740   Min.   :1.360   Min.   :10.60  
#>  1st Qu.:12.36   1st Qu.:1.603   1st Qu.:2.210   1st Qu.:17.20  
#>  Median :13.05   Median :1.865   Median :2.360   Median :19.50  
#>  Mean   :13.00   Mean   :2.336   Mean   :2.367   Mean   :19.49  
#>  3rd Qu.:13.68   3rd Qu.:3.083   3rd Qu.:2.558   3rd Qu.:21.50  
#>  Max.   :14.83   Max.   :5.800   Max.   :3.230   Max.   :30.00  
#>    Magnesium         Phenols        Flavanoids     Nonflavanoid   
#>  Min.   : 70.00   Min.   :0.980   Min.   :0.340   Min.   :0.1300  
#>  1st Qu.: 88.00   1st Qu.:1.742   1st Qu.:1.205   1st Qu.:0.2700  
#>  Median : 98.00   Median :2.355   Median :2.135   Median :0.3400  
#>  Mean   : 99.74   Mean   :2.295   Mean   :2.029   Mean   :0.3619  
#>  3rd Qu.:107.00   3rd Qu.:2.800   3rd Qu.:2.875   3rd Qu.:0.4375  
#>  Max.   :162.00   Max.   :3.880   Max.   :5.080   Max.   :0.6600  
#>  Proanthocyanins   Intensity           Hue             OD280      
#>  Min.   :0.410   Min.   : 1.280   Min.   :0.4800   Min.   :1.270  
#>  1st Qu.:1.250   1st Qu.: 3.220   1st Qu.:0.7825   1st Qu.:1.938  
#>  Median :1.555   Median : 4.690   Median :0.9650   Median :2.780  
#>  Mean   :1.591   Mean   : 5.058   Mean   :0.9575   Mean   :2.612  
#>  3rd Qu.:1.950   3rd Qu.: 6.200   3rd Qu.:1.1200   3rd Qu.:3.170  
#>  Max.   :3.580   Max.   :13.000   Max.   :1.7100   Max.   :4.000  
#>     Proline      
#>  Min.   : 278.0  
#>  1st Qu.: 500.5  
#>  Median : 673.5  
#>  Mean   : 746.9  
#>  3rd Qu.: 985.0  
#>  Max.   :1680.0

We will use a PCA to visualize the data, along with the class labels, in a low-dimensional space. This will allow us to interpret the results of the clustering algorithms more easily.

Tasks

1. Perform a PCA of the scaled wine data.

Solution
wine_pca <- prcomp(X, scale. = TRUE)

2. Create a biplot of the first two PCs. Color each observation by its class label. Interpret the PCs. How distinguishable are the classes with respect to the PCs?

Solution

Using ggbiplot:

cols <- c("Barolo" = "#28a028", "Grignolino" = "#da6852", "Barbera" = "#0078cd")

ggbiplot(wine_pca, groups = class) +
  scale_color_manual(values = cols) +
  labs(color = "True labels") +
  theme(aspect.ratio = 0.8, legend.position = "top")

Alternatively, using base R plotting:

Zk <- wine_pca$x[, 1:2]
Vk <- wine_pca$rotation[, 1:2]

plot(Zk, pch = 20,
  col = cols[class],
  xlab = "PC1", ylab = "PC2"
)
legend("topleft", levels(class),
  col = cols,
  pch = 19
)
alpha <- 5 # rescaling
for (i in 1:13) {
  arrows(0, 0, alpha * Vk[i, 1], alpha * Vk[i, 2], length = 0.2, col = "#8f4040")
  text(alpha * Vk[i, 1], alpha * Vk[i, 2], rownames(Vk)[i], col = "#8f4040")
}

Interpretation: Low values on PC1 correspond to heavier, aged, more structured wines, while high values indicate fresher, less heavy, younger wines. High values on PC2 correspond to lighter-colored wines with lower alcohol content. Importantly, the three classes are quite clearly visually separated with little overlap between them. We can thus rely on the biplot to visually investigate how well a clustering algorithm recovers the true classes.

2 Hierarchical clustering

Tasks

1. Perform hierarchical clustering of the wine data, using a Euclidean distance matrix and the complete-linkage algorithm (see ?hclust).

Solution
# Calculate distance matrix and perform hierarchical clustering
wine_dist <- dist(X, method = "euclidean")
hc <- hclust(wine_dist, method = "complete")

2. Plot the clustering dendrogram.

Solution
plot(hc, labels = FALSE)

3. Use cutree to select three clusters from the hierarchical clustering. Recreate the PCA biplot from Section 1, but color each observation according to its cluster. Compare the resulting biplot to the earlier one from Section 1.

Solution
# Select three clusters
hc_clusters <- cutree(hc, k = 3)
# Tabulate clusters against true classes
table(class, hc_clusters)
#>             hc_clusters
#> class         1  2  3
#>   Barolo     43 16  0
#>   Grignolino  0 15 56
#>   Barbera     0 21 27

Biplot with ggbiplot:

plot1 <- ggbiplot(wine_pca, groups = class) +
  scale_color_manual(values = cols) +
  labs(color = "True labels") +
  theme(aspect.ratio = 0.8, legend.position = "top")

plot2 <- ggbiplot(wine_pca, groups = factor(hc_clusters)) +
  scale_color_manual(values = c("#9d05f4", "#01d9d9", "#f4dc00")) +
  labs(color = "HC clusters") +
  theme(aspect.ratio = 0.8, legend.position = "top")

grid.arrange(plot1, nullGrob(), plot2, ncol = 3, widths = c(1, 0.1, 1))

Alternatively, using base R plotting:

par(mfrow = c(1, 2))

# True class labels
plot(Zk, pch = 20,
  col = cols[class],
  xlab = "PC1", ylab = "PC2"
)
legend("topleft", levels(class),
  col = cols,
  pch = 19
)
alpha <- 5 # rescaling
for (i in 1:13) {
  arrows(0, 0, alpha * Vk[i, 1], alpha * Vk[i, 2], length = 0.2, col = "#8f4040")
  text(alpha * Vk[i, 1], alpha * Vk[i, 2], rownames(Vk)[i], col = "#8f4040")
}

cluster_cols <- c("1" = "#9d05f4", "2" = "#01d9d9", "3" = "#f4dc00")
# Clusters
plot(Zk, pch = 20,
  col = cluster_cols[factor(hc_clusters)],
  xlab = "PC1", ylab = "PC2"
)
legend("topleft", levels(factor(hc_clusters)),
  col = cluster_cols,
  pch = 19
)
alpha <- 5 # rescaling
for (i in 1:13) {
  arrows(0, 0, alpha * Vk[i, 1], alpha * Vk[i, 2], length = 0.2, col = "#8f4040")
  text(alpha * Vk[i, 1], alpha * Vk[i, 2], rownames(Vk)[i], col = "#8f4040")
}

Interpretation: The first cluster corresponds to most of the Barolo class, but the second cluster overlaps with all three classes, and the third cluster captures wines from both the Grignolino and the Barbera class. Overall, the three clusters do not recover the true class labels.

Bonus: can you improve the results by using different distance metrics or linkages?

3 Model-based clustering

Tasks

1. Use mclust::Mclust() to perform model-based clustering on the wine data.

Solution
mc <- Mclust(X)
summary(mc)
#> ---------------------------------------------------- 
#> Gaussian finite mixture model fitted by EM algorithm 
#> ---------------------------------------------------- 
#> 
#> Mclust VVE (ellipsoidal, equal orientation) model with 3 components: 
#> 
#>  log-likelihood   n  df       BIC       ICL
#>       -3015.335 178 158 -6849.391 -6850.734
#> 
#> Clustering table:
#>  1  2  3 
#> 59 69 50
summary(mc$BIC)
#> Best BIC values:
#>              VVE,3       EVE,4       VVE,4
#> BIC      -6849.391 -6873.61648 -6885.47222
#> BIC diff     0.000   -24.22499   -36.08073

2. Visualize the clustering via pairwise plots. Plot the BIC (Bayesian information criterion) values and interpret the results.

Hint: Check ?plot.Mclust.

Solution
# Pairwise clustering plots
plot(mc, what = "classification")

# BIC plot; exclude two models with very low BIC
plot(mc, what = "BIC", ylim = range(mc$BIC[, -(1:2)], na.rm = TRUE),
  legendArgs = list(x = "bottomleft")
)

Interpretation: A three-component mixture with covariances having different shapes and volumes but the same orientation (VVE) results in the highest BIC.

3. Recreate the PCA biplot from Section 1, but color each observation according to its cluster. Compare the resulting biplot to the earlier ones.

Solution
# Tabulate clusters against true classes
table(class, mc$classification)
#>             
#> class         1  2  3
#>   Barolo     59  0  0
#>   Grignolino  0 69  2
#>   Barbera     0  0 48

Biplot with ggbiplot:

plot1 <- ggbiplot(wine_pca, groups = class) +
  scale_color_manual(values = cols) +
  labs(color = "True labels") +
  theme(aspect.ratio = 0.8, legend.position = "top")

plot2 <- ggbiplot(wine_pca, groups = factor(mc$classification)) +
  scale_color_manual(values = c("#9d05f4", "#01d9d9", "#f4dc00")) +
  labs(color = "Mclust clusters") +
  theme(aspect.ratio = 0.8, legend.position = "top")

grid.arrange(plot1, nullGrob(), plot2, ncol = 3, widths = c(1, 0.1, 1))

Alternatively, using base R plotting:

par(mfrow = c(1, 2))

# True class labels
plot(Zk, pch = 20,
  col = cols[class],
  xlab = "PC1", ylab = "PC2"
)
legend("topleft", levels(class),
  col = cols,
  pch = 19
)
alpha <- 5 # rescaling
for (i in 1:13) {
  arrows(0, 0, alpha * Vk[i, 1], alpha * Vk[i, 2], length = 0.2, col = "#8f4040")
  text(alpha * Vk[i, 1], alpha * Vk[i, 2], rownames(Vk)[i], col = "#8f4040")
}

cluster_cols <- c("1" = "#9d05f4", "2" = "#01d9d9", "3" = "#f4dc00")
# Clusters
plot(Zk, pch = 20,
  col = cluster_cols[factor(mc$classification)],
  xlab = "PC1", ylab = "PC2"
)
legend("topleft", levels(factor(mc$classification)),
  col = cluster_cols,
  pch = 19
)
alpha <- 5 # rescaling
for (i in 1:13) {
  arrows(0, 0, alpha * Vk[i, 1], alpha * Vk[i, 2], length = 0.2, col = "#8f4040")
  text(alpha * Vk[i, 1], alpha * Vk[i, 2], rownames(Vk)[i], col = "#8f4040")
}

Interpretation: The first cluster corresponds to the Barolo class, the second cluster to the Grignolino class and the third cluster to Barbera. Overall, the three clusters recover the true class labels very well, with only two Grignolino wines falsely assigned to the third cluster (Barbera).

4. Select an appropriate number of PCs from the PCA. Repeat the model-based clustering on the PCs. How do the results compare to those on the full data?

Solution
# Proportion of variance explained by each PC
wine_prop_var <- data.frame(
  PC = 1:ncol(wine_pca$x),
  var = wine_pca$sdev^2 / sum(wine_pca$sdev^2)
)

ggplot(wine_prop_var, aes(PC, var)) +
  geom_point() +
  geom_line() +
  geom_vline(xintercept = 7.5, col = "firebrick") +
  scale_x_continuous(breaks = 1:ncol(wine_pca$x)) +
  labs(y = "Proportion of variance")

The first 7 PCs explain about 92% of the variance in the wine data.

# Select the first k PCs
k <- 7
pca_X <- wine_pca$x[, 1:k]

# Refit the model-based clustering
mc2 <- Mclust(pca_X)
summary(mc2)
#> ---------------------------------------------------- 
#> Gaussian finite mixture model fitted by EM algorithm 
#> ---------------------------------------------------- 
#> 
#> Mclust VEI (diagonal, equal shape) model with 5 components: 
#> 
#>  log-likelihood   n df       BIC       ICL
#>       -1719.764 178 50 -3698.617 -3725.317
#> 
#> Clustering table:
#>  1  2  3  4  5 
#> 53 25 19 34 47
summary(mc2$BIC)
#> Best BIC values:
#>              VEI,5        VEI,4        VEI,6
#> BIC      -3698.617 -3700.939851 -3704.927637
#> BIC diff     0.000    -2.322571    -6.310357

A five-component mixture with diagonal covariances having equal shape, but varying volume (VEI), results in the highest BIC.

# Tabulate clusters against true classes
table(class, mc2$classification)
#>             
#> class         1  2  3  4  5
#>   Barolo     53  6  0  0  0
#>   Grignolino  0 16 18 34  3
#>   Barbera     0  3  1  0 44

Biplot with ggbiplot:

plot1 <- ggbiplot(wine_pca, groups = class) +
  scale_color_manual(values = cols) +
  labs(color = "True labels") +
  theme(aspect.ratio = 0.8, legend.position = "top")

plot2 <- ggbiplot(wine_pca, groups = factor(mc2$classification)) +
  scale_color_manual(values = c("#9d05f4", "#01d9d9", "#01d9d9", "#01d9d9", "#f4dc00")) +
  labs(color = "Mclust clusters") +
  theme(aspect.ratio = 0.8, legend.position = "top")

grid.arrange(plot1, nullGrob(), plot2, ncol = 3, widths = c(1, 0.1, 1))

Alternatively, using base R plotting:

par(mfrow = c(1, 2))

# True class labels
plot(Zk, pch = 20,
  col = cols[class],
  xlab = "PC1", ylab = "PC2"
)
legend("topleft", levels(class),
  col = cols,
  pch = 19
)
alpha <- 5 # rescaling
for (i in 1:13) {
  arrows(0, 0, alpha * Vk[i, 1], alpha * Vk[i, 2], length = 0.2, col = "#8f4040")
  text(alpha * Vk[i, 1], alpha * Vk[i, 2], rownames(Vk)[i], col = "#8f4040")
}

cluster_cols <- c("1" = "#9d05f4", "2" = "#01d9d9", "3" = "#01d9d9", "4" = "#01d9d9", "5" = "#f4dc00")
# Clusters
plot(Zk, pch = 20,
  col = cluster_cols[factor(mc2$classification)],
  xlab = "PC1", ylab = "PC2"
)
legend("topleft", levels(factor(mc2$classification)),
  col = cluster_cols,
  pch = 19
)
alpha <- 5 # rescaling
for (i in 1:13) {
  arrows(0, 0, alpha * Vk[i, 1], alpha * Vk[i, 2], length = 0.2, col = "#8f4040")
  text(alpha * Vk[i, 1], alpha * Vk[i, 2], rownames(Vk)[i], col = "#8f4040")
}

Interpretation: The first cluster corresponds to most of the Barolo class. The fifth color mainly corresponds to the Barbera class, while containing a few Grignolino wines. Combining the second, third and fourth cluster recovers most of the Grignolino class, with some overlap with the remaining classes. Overall, after combining the three smaller clusters, the clustering recovers the true class labels fairly well, though not quite as closely as the clustering on the original data.

Additional resources

  • Section 14.3 of Hastie, Tibshirani, and Friedman (2009)

Session info

Session info
#> [1] "2025-11-09 16:06:01 CET"
#> ─ Session info ───────────────────────────────────────────────────────────────
#>  setting  value
#>  version  R version 4.5.2 (2025-10-31)
#>  os       macOS Sequoia 15.6
#>  system   aarch64, darwin20
#>  ui       X11
#>  language (EN)
#>  collate  en_US.UTF-8
#>  ctype    en_US.UTF-8
#>  tz       Europe/Brussels
#>  date     2025-11-09
#>  pandoc   3.6.3 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/aarch64/ (via rmarkdown)
#>  quarto   1.7.32 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/quarto
#> 
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package      * version date (UTC) lib source
#>  bookdown       0.45    2025-10-03 [1] CRAN (R 4.5.0)
#>  bslib          0.9.0   2025-01-30 [1] CRAN (R 4.5.0)
#>  cachem         1.1.0   2024-05-16 [1] CRAN (R 4.5.0)
#>  cli            3.6.5   2025-04-23 [1] CRAN (R 4.5.0)
#>  cluster      * 2.1.8.1 2025-03-12 [1] CRAN (R 4.5.2)
#>  digest         0.6.37  2024-08-19 [1] CRAN (R 4.5.0)
#>  dplyr        * 1.1.4   2023-11-17 [1] CRAN (R 4.5.0)
#>  evaluate       1.0.5   2025-08-27 [1] CRAN (R 4.5.0)
#>  farver         2.1.2   2024-05-13 [1] CRAN (R 4.5.0)
#>  fastmap        1.2.0   2024-05-15 [1] CRAN (R 4.5.0)
#>  forcats      * 1.0.1   2025-09-25 [1] CRAN (R 4.5.0)
#>  gclus        * 1.3.3   2025-03-28 [1] CRAN (R 4.5.0)
#>  generics       0.1.4   2025-05-09 [1] CRAN (R 4.5.0)
#>  ggbiplot     * 0.55    2025-11-05 [1] Github (vqv/ggbiplot@f7ea76d)
#>  ggplot2      * 4.0.0   2025-09-11 [1] CRAN (R 4.5.0)
#>  glue           1.8.0   2024-09-30 [1] CRAN (R 4.5.0)
#>  gridExtra    * 2.3     2017-09-09 [1] CRAN (R 4.5.0)
#>  gtable         0.3.6   2024-10-25 [1] CRAN (R 4.5.0)
#>  hms            1.1.4   2025-10-17 [1] CRAN (R 4.5.0)
#>  htmltools      0.5.8.1 2024-04-04 [1] CRAN (R 4.5.0)
#>  jquerylib      0.1.4   2021-04-26 [1] CRAN (R 4.5.0)
#>  jsonlite       2.0.0   2025-03-27 [1] CRAN (R 4.5.0)
#>  knitr          1.50    2025-03-16 [1] CRAN (R 4.5.0)
#>  labeling       0.4.3   2023-08-29 [1] CRAN (R 4.5.0)
#>  lifecycle      1.0.4   2023-11-07 [1] CRAN (R 4.5.0)
#>  lubridate    * 1.9.4   2024-12-08 [1] CRAN (R 4.5.0)
#>  magrittr       2.0.4   2025-09-12 [1] CRAN (R 4.5.0)
#>  mclust       * 6.1.2   2025-10-31 [1] CRAN (R 4.5.0)
#>  pillar         1.11.1  2025-09-17 [1] CRAN (R 4.5.0)
#>  pkgconfig      2.0.3   2019-09-22 [1] CRAN (R 4.5.0)
#>  plyr         * 1.8.9   2023-10-02 [1] CRAN (R 4.5.0)
#>  purrr        * 1.2.0   2025-11-04 [1] CRAN (R 4.5.0)
#>  R6             2.6.1   2025-02-15 [1] CRAN (R 4.5.0)
#>  RColorBrewer   1.1-3   2022-04-03 [1] CRAN (R 4.5.0)
#>  Rcpp           1.1.0   2025-07-02 [1] CRAN (R 4.5.0)
#>  readr        * 2.1.5   2024-01-10 [1] CRAN (R 4.5.0)
#>  rlang          1.1.6   2025-04-11 [1] CRAN (R 4.5.0)
#>  rmarkdown      2.30    2025-09-28 [1] CRAN (R 4.5.0)
#>  rstudioapi     0.17.1  2024-10-22 [1] CRAN (R 4.5.0)
#>  S7             0.2.0   2024-11-07 [1] CRAN (R 4.5.0)
#>  sass           0.4.10  2025-04-11 [1] CRAN (R 4.5.0)
#>  scales       * 1.4.0   2025-04-24 [1] CRAN (R 4.5.0)
#>  sessioninfo    1.2.3   2025-02-05 [1] CRAN (R 4.5.0)
#>  stringi        1.8.7   2025-03-27 [1] CRAN (R 4.5.0)
#>  stringr      * 1.6.0   2025-11-04 [1] CRAN (R 4.5.0)
#>  tibble       * 3.3.0   2025-06-08 [1] CRAN (R 4.5.0)
#>  tidyr        * 1.3.1   2024-01-24 [1] CRAN (R 4.5.0)
#>  tidyselect     1.2.1   2024-03-11 [1] CRAN (R 4.5.0)
#>  tidyverse    * 2.0.0   2023-02-22 [1] CRAN (R 4.5.0)
#>  timechange     0.3.0   2024-01-18 [1] CRAN (R 4.5.0)
#>  tzdb           0.5.0   2025-03-15 [1] CRAN (R 4.5.0)
#>  vctrs          0.6.5   2023-12-01 [1] CRAN (R 4.5.0)
#>  withr          3.0.2   2024-10-28 [1] CRAN (R 4.5.0)
#>  xfun           0.54    2025-10-30 [1] CRAN (R 4.5.0)
#>  yaml           2.3.10  2024-07-26 [1] CRAN (R 4.5.0)
#> 
#>  [1] /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/library
#>  * ── Packages attached to the search path.
#> 
#> ──────────────────────────────────────────────────────────────────────────────

References

Hastie, Trevor, Robert Tibshirani, and Jerome Friedman. 2009. The Elements of Statistical Learning. 2nd ed. Springer.
LS0tCnRpdGxlOiAiTGFiIDU6IENsdXN0ZXJpbmciCnN1YnRpdGxlOiAiSGlnaCBEaW1lbnNpb25hbCBEYXRhIEFuYWx5c2lzIHByYWN0aWNhbHMiCmF1dGhvcjogIk1pbGFuIE1hbGZhaXQgYW5kIExlbyBGdWhyaG9wIgpkYXRlOiAiMjQgRmViIDIwMjIgPGJyLz4gKExhc3QgdXBkYXRlZDogMjAyNS0xMS0wOSkiCnJlZmVyZW5jZXM6Ci0gaWQ6IGVzbC1ib29rCiAgdHlwZTogYm9vawogIGF1dGhvcjoKICAtIGZhbWlseTogSGFzdGllCiAgICBnaXZlbjogVHJldm9yCiAgLSBmYW1pbHk6IFRpYnNoaXJhbmkKICAgIGdpdmVuOiBSb2JlcnQKICAtIGZhbWlseTogRnJpZWRtYW4KICAgIGdpdmVuOiBKZXJvbWUKICBpc3N1ZWQ6CiAgLSB5ZWFyOiAyMDA5CiAgdGl0bGU6IFRoZSBFbGVtZW50cyBvZiBTdGF0aXN0aWNhbCBMZWFybmluZwogIHB1Ymxpc2hlcjogU3ByaW5nZXIKICBlZGl0aW9uOiAybmQKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBjb2xsYXBzZSA9IFRSVUUsCiAgY29tbWVudCA9ICIjPiIsCiAgZmlnLnNob3cgPSAiaG9sZCIKKQoKb3B0aW9ucyh3aWR0aCA9IDgwKQpgYGAKCiMjIyBbQ2hhbmdlIGxvZ10oaHR0cHM6Ly9naXRodWIuY29tL3N0YXRPbWljcy9IRERBL2NvbW1pdHMvbWFzdGVyL0xhYjYtQ2x1c3RlcmluZy5SbWQpIHstfQoKKioqCgpgYGB7ciBsaWJyYXJpZXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMjIEluc3RhbGwgbmVjZXNzYXJ5IHBhY2thZ2VzIHdpdGg6CgojIGluc3RhbGwucGFja2FnZXMoYygibWNsdXN0IiwgImdjbHVzIiwgInRpZHl2ZXJzZSIsICJncmlkRXh0cmEiKSkKCiMgaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJyZW1vdGVzIiwgcXVpZXRseSA9IFRSVUUpKSB7CiMgICAgIGluc3RhbGwucGFja2FnZXMoInJlbW90ZXMiKQojIH0KIyByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigidnF2L2dnYmlwbG90IikKCmxpYnJhcnkobWNsdXN0KQpsaWJyYXJ5KGdjbHVzKSAgIyBjb250YWlucyB0aGUgJ3dpbmUnIGRhdGEKbGlicmFyeShnZ2JpcGxvdCkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ3JpZEV4dHJhKQoKdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkKYGBgCgoKIyBUaGUgd2luZSBkYXRhCgpJbiB0aGlzIGxhYiBzZXNzaW9uLCB3ZSB3aWxsIGV4cGxvcmUgdGhlIFtgd2luZWBdW3dpbmVdIGRhdGEsIGZvbGxvd2luZyB0aGUKZXhhbXBsZSBhbmFseXNpcyBmcm9tIFtTY3J1Y2NhICpldCBhbC4qICgyMDE2KV1bc2NydWNjYTIwMTZdLgoKVGhpcyBkYXRhc2V0IHByb3ZpZGVzIDEzIG1lYXN1cmVtZW50cyBvYnRhaW5lZCBmcm9tIGEgY2hlbWljYWwgYW5hbHlzaXMgb2YgMTc4CndpbmVzIGdyb3duIGluIHRoZSBzYW1lIHJlZ2lvbiBpbiBJdGFseSBidXQgZGVyaXZlZCBmcm9tIHRocmVlIGRpZmZlcmVudApjdWx0aXZhcnMgKEJhcm9sbywgR3JpZ25vbGlubywgQmFyYmVyYSkuIFRoZSBvcmlnaW5hbCBjdWx0aXZhciBsYWJlbHMgYXJlCnByb3ZpZGVkIGluIHRoZSBkYXRhc2V0LgoKV2Ugd2lsbCBhcHBseSBkaWZmZXJlbnQgY2x1c3RlcmluZyBhbGdvcml0aG1zIGFuZCB2YWxpZGF0ZSB0aGVtIGJ5IGNvbXBhcmluZyBob3cKd2VsbCB0aGUgY2x1c3RlcnMgY2FwdHVyZSB0aGUgb3JpZ2luYWwgY2xhc3Nlcy4KCmBgYHtyfQpkYXRhKCJ3aW5lIiwgcGFja2FnZSA9ICJnY2x1cyIpCmNsYXNzIDwtIGZhY3Rvcih3aW5lJENsYXNzLCBsZXZlbHMgPSAxOjMsIGxhYmVscyA9IGMoIkJhcm9sbyIsICJHcmlnbm9saW5vIiwgIkJhcmJlcmEiKSkKdGFibGUoY2xhc3MpCgpYIDwtIGFzLm1hdHJpeCh3aW5lWywgLTFdKQpzdW1tYXJ5KFgpCmBgYAoKV2Ugd2lsbCB1c2UgYSBQQ0EgdG8gdmlzdWFsaXplIHRoZSBkYXRhLCBhbG9uZyB3aXRoIHRoZSBjbGFzcyBsYWJlbHMsIGluIGEgbG93LWRpbWVuc2lvbmFsIHNwYWNlLgpUaGlzIHdpbGwgYWxsb3cgdXMgdG8gaW50ZXJwcmV0IHRoZSByZXN1bHRzIG9mIHRoZSBjbHVzdGVyaW5nIGFsZ29yaXRobXMgbW9yZSBlYXNpbHkuCgojIyMgVGFza3Mgey19CgojIyMjIDEuIFBlcmZvcm0gYSBQQ0Egb2YgdGhlIHNjYWxlZCBgd2luZWAgZGF0YS4gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KCmBgYHtyfQp3aW5lX3BjYSA8LSBwcmNvbXAoWCwgc2NhbGUuID0gVFJVRSkKYGBgCgo8L2RldGFpbHM+CgojIyMjIDIuIENyZWF0ZSBhIGJpcGxvdCBvZiB0aGUgZmlyc3QgdHdvIFBDcy4gQ29sb3IgZWFjaCBvYnNlcnZhdGlvbiBieSBpdHMgY2xhc3MgbGFiZWwuIEludGVycHJldCB0aGUgUENzLiBIb3cgZGlzdGluZ3Vpc2hhYmxlIGFyZSB0aGUgY2xhc3NlcyB3aXRoIHJlc3BlY3QgdG8gdGhlIFBDcz8gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KClVzaW5nIGBnZ2JpcGxvdGA6CgpgYGB7cn0KY29scyA8LSBjKCJCYXJvbG8iID0gIiMyOGEwMjgiLCAiR3JpZ25vbGlubyIgPSAiI2RhNjg1MiIsICJCYXJiZXJhIiA9ICIjMDA3OGNkIikKCmdnYmlwbG90KHdpbmVfcGNhLCBncm91cHMgPSBjbGFzcykgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjb2xzKSArCiAgbGFicyhjb2xvciA9ICJUcnVlIGxhYmVscyIpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAwLjgsIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQpgYGAKCkFsdGVybmF0aXZlbHksIHVzaW5nIGJhc2UgYFJgIHBsb3R0aW5nOgoKYGBge3J9ClprIDwtIHdpbmVfcGNhJHhbLCAxOjJdClZrIDwtIHdpbmVfcGNhJHJvdGF0aW9uWywgMToyXQoKcGxvdChaaywgcGNoID0gMjAsCiAgY29sID0gY29sc1tjbGFzc10sCiAgeGxhYiA9ICJQQzEiLCB5bGFiID0gIlBDMiIKKQpsZWdlbmQoInRvcGxlZnQiLCBsZXZlbHMoY2xhc3MpLAogIGNvbCA9IGNvbHMsCiAgcGNoID0gMTkKKQphbHBoYSA8LSA1ICMgcmVzY2FsaW5nCmZvciAoaSBpbiAxOjEzKSB7CiAgYXJyb3dzKDAsIDAsIGFscGhhICogVmtbaSwgMV0sIGFscGhhICogVmtbaSwgMl0sIGxlbmd0aCA9IDAuMiwgY29sID0gIiM4ZjQwNDAiKQogIHRleHQoYWxwaGEgKiBWa1tpLCAxXSwgYWxwaGEgKiBWa1tpLCAyXSwgcm93bmFtZXMoVmspW2ldLCBjb2wgPSAiIzhmNDA0MCIpCn0KYGBgCgpfX0ludGVycHJldGF0aW9uX186IExvdyB2YWx1ZXMgb24gUEMxIGNvcnJlc3BvbmQgdG8gaGVhdmllciwgYWdlZCwgbW9yZSBzdHJ1Y3R1cmVkIHdpbmVzLCB3aGlsZSBoaWdoIHZhbHVlcyBpbmRpY2F0ZSBmcmVzaGVyLCBsZXNzIGhlYXZ5LCB5b3VuZ2VyIHdpbmVzLiAKSGlnaCB2YWx1ZXMgb24gUEMyIGNvcnJlc3BvbmQgdG8gbGlnaHRlci1jb2xvcmVkIHdpbmVzIHdpdGggbG93ZXIgYWxjb2hvbCBjb250ZW50LgpJbXBvcnRhbnRseSwgdGhlIHRocmVlIGNsYXNzZXMgYXJlIHF1aXRlIGNsZWFybHkgdmlzdWFsbHkgc2VwYXJhdGVkIHdpdGggbGl0dGxlIG92ZXJsYXAgYmV0d2VlbiB0aGVtLiBXZSBjYW4gdGh1cyByZWx5IG9uIHRoZSBiaXBsb3QgdG8gdmlzdWFsbHkgaW52ZXN0aWdhdGUgaG93IHdlbGwgYSBjbHVzdGVyaW5nIGFsZ29yaXRobSByZWNvdmVycyB0aGUgdHJ1ZSBjbGFzc2VzLiAKCjwvZGV0YWlscz4KCiMgSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcKCiMjIyBUYXNrcyB7LX0KCiMjIyMgMS4gUGVyZm9ybSBoaWVyYXJjaGljYWwgY2x1c3RlcmluZyBvZiB0aGUgd2luZSBkYXRhLCB1c2luZyBhIEV1Y2xpZGVhbiBkaXN0YW5jZSBtYXRyaXggYW5kIHRoZSBjb21wbGV0ZS1saW5rYWdlIGFsZ29yaXRobSAoc2VlIGA/aGNsdXN0YCkuIHstfQoKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CgpgYGB7cn0KIyBDYWxjdWxhdGUgZGlzdGFuY2UgbWF0cml4IGFuZCBwZXJmb3JtIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nCndpbmVfZGlzdCA8LSBkaXN0KFgsIG1ldGhvZCA9ICJldWNsaWRlYW4iKQpoYyA8LSBoY2x1c3Qod2luZV9kaXN0LCBtZXRob2QgPSAiY29tcGxldGUiKQpgYGAKCjwvZGV0YWlscz4KCiMjIyMgMi4gUGxvdCB0aGUgY2x1c3RlcmluZyAqZGVuZHJvZ3JhbSouIHstfQoKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CgpgYGB7cn0KcGxvdChoYywgbGFiZWxzID0gRkFMU0UpCmBgYAoKPC9kZXRhaWxzPgoKIyMjIyAzLiBVc2UgYGN1dHJlZWAgdG8gc2VsZWN0IHRocmVlIGNsdXN0ZXJzIGZyb20gdGhlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nLiBSZWNyZWF0ZSB0aGUgUENBIGJpcGxvdCBmcm9tIFNlY3Rpb24gMSwgYnV0IGNvbG9yIGVhY2ggb2JzZXJ2YXRpb24gYWNjb3JkaW5nIHRvIGl0cyBjbHVzdGVyLiBDb21wYXJlIHRoZSByZXN1bHRpbmcgYmlwbG90IHRvIHRoZSBlYXJsaWVyIG9uZSBmcm9tIFNlY3Rpb24gMS4gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KCmBgYHtyfQojIFNlbGVjdCB0aHJlZSBjbHVzdGVycwpoY19jbHVzdGVycyA8LSBjdXRyZWUoaGMsIGsgPSAzKQojIFRhYnVsYXRlIGNsdXN0ZXJzIGFnYWluc3QgdHJ1ZSBjbGFzc2VzCnRhYmxlKGNsYXNzLCBoY19jbHVzdGVycykKYGBgCgpCaXBsb3Qgd2l0aCBgZ2diaXBsb3RgOgoKYGBge3J9CnBsb3QxIDwtIGdnYmlwbG90KHdpbmVfcGNhLCBncm91cHMgPSBjbGFzcykgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjb2xzKSArCiAgbGFicyhjb2xvciA9ICJUcnVlIGxhYmVscyIpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAwLjgsIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQoKcGxvdDIgPC0gZ2diaXBsb3Qod2luZV9wY2EsIGdyb3VwcyA9IGZhY3RvcihoY19jbHVzdGVycykpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiIzlkMDVmNCIsICIjMDFkOWQ5IiwgIiNmNGRjMDAiKSkgKwogIGxhYnMoY29sb3IgPSAiSEMgY2x1c3RlcnMiKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMC44LCBsZWdlbmQucG9zaXRpb24gPSAidG9wIikKCmdyaWQuYXJyYW5nZShwbG90MSwgbnVsbEdyb2IoKSwgcGxvdDIsIG5jb2wgPSAzLCB3aWR0aHMgPSBjKDEsIDAuMSwgMSkpCmBgYAoKQWx0ZXJuYXRpdmVseSwgdXNpbmcgYmFzZSBgUmAgcGxvdHRpbmc6CgpgYGB7cn0KcGFyKG1mcm93ID0gYygxLCAyKSkKCiMgVHJ1ZSBjbGFzcyBsYWJlbHMKcGxvdChaaywgcGNoID0gMjAsCiAgY29sID0gY29sc1tjbGFzc10sCiAgeGxhYiA9ICJQQzEiLCB5bGFiID0gIlBDMiIKKQpsZWdlbmQoInRvcGxlZnQiLCBsZXZlbHMoY2xhc3MpLAogIGNvbCA9IGNvbHMsCiAgcGNoID0gMTkKKQphbHBoYSA8LSA1ICMgcmVzY2FsaW5nCmZvciAoaSBpbiAxOjEzKSB7CiAgYXJyb3dzKDAsIDAsIGFscGhhICogVmtbaSwgMV0sIGFscGhhICogVmtbaSwgMl0sIGxlbmd0aCA9IDAuMiwgY29sID0gIiM4ZjQwNDAiKQogIHRleHQoYWxwaGEgKiBWa1tpLCAxXSwgYWxwaGEgKiBWa1tpLCAyXSwgcm93bmFtZXMoVmspW2ldLCBjb2wgPSAiIzhmNDA0MCIpCn0KCmNsdXN0ZXJfY29scyA8LSBjKCIxIiA9ICIjOWQwNWY0IiwgIjIiID0gIiMwMWQ5ZDkiLCAiMyIgPSAiI2Y0ZGMwMCIpCiMgQ2x1c3RlcnMKcGxvdChaaywgcGNoID0gMjAsCiAgY29sID0gY2x1c3Rlcl9jb2xzW2ZhY3RvcihoY19jbHVzdGVycyldLAogIHhsYWIgPSAiUEMxIiwgeWxhYiA9ICJQQzIiCikKbGVnZW5kKCJ0b3BsZWZ0IiwgbGV2ZWxzKGZhY3RvcihoY19jbHVzdGVycykpLAogIGNvbCA9IGNsdXN0ZXJfY29scywKICBwY2ggPSAxOQopCmFscGhhIDwtIDUgIyByZXNjYWxpbmcKZm9yIChpIGluIDE6MTMpIHsKICBhcnJvd3MoMCwgMCwgYWxwaGEgKiBWa1tpLCAxXSwgYWxwaGEgKiBWa1tpLCAyXSwgbGVuZ3RoID0gMC4yLCBjb2wgPSAiIzhmNDA0MCIpCiAgdGV4dChhbHBoYSAqIFZrW2ksIDFdLCBhbHBoYSAqIFZrW2ksIDJdLCByb3duYW1lcyhWaylbaV0sIGNvbCA9ICIjOGY0MDQwIikKfQpgYGAKCl9fSW50ZXJwcmV0YXRpb25fXzogVGhlIGZpcnN0IGNsdXN0ZXIgY29ycmVzcG9uZHMgdG8gbW9zdCBvZiB0aGUgQmFyb2xvIGNsYXNzLCBidXQgdGhlIHNlY29uZCBjbHVzdGVyIG92ZXJsYXBzIHdpdGggYWxsIHRocmVlIGNsYXNzZXMsIGFuZCB0aGUgdGhpcmQgY2x1c3RlciBjYXB0dXJlcyB3aW5lcyBmcm9tIGJvdGggdGhlIEdyaWdub2xpbm8gYW5kIHRoZSBCYXJiZXJhIGNsYXNzLgpPdmVyYWxsLCB0aGUgdGhyZWUgY2x1c3RlcnMgZG8gbm90IHJlY292ZXIgdGhlIHRydWUgY2xhc3MgbGFiZWxzLgoKPC9kZXRhaWxzPgoKIyMjIyBCb251czogY2FuIHlvdSBpbXByb3ZlIHRoZSByZXN1bHRzIGJ5IHVzaW5nIGRpZmZlcmVudCBkaXN0YW5jZSBtZXRyaWNzIG9yIGxpbmthZ2VzPyB7LX0KCgojIE1vZGVsLWJhc2VkIGNsdXN0ZXJpbmcKCiMjIyBUYXNrcyB7LX0KCiMjIyMgMS4gVXNlIGBtY2x1c3Q6Ok1jbHVzdCgpYCB0byBwZXJmb3JtIG1vZGVsLWJhc2VkIGNsdXN0ZXJpbmcgb24gdGhlIGB3aW5lYCBkYXRhLiB7LX0KCjxkZXRhaWxzPjxzdW1tYXJ5PlNvbHV0aW9uPC9zdW1tYXJ5PgoKYGBge3J9Cm1jIDwtIE1jbHVzdChYKQpzdW1tYXJ5KG1jKQpzdW1tYXJ5KG1jJEJJQykKYGBgCgo8L2RldGFpbHM+CgojIyMjIDIuIFZpc3VhbGl6ZSB0aGUgY2x1c3RlcmluZyB2aWEgcGFpcndpc2UgcGxvdHMuIFBsb3QgdGhlIEJJQyAoQmF5ZXNpYW4gaW5mb3JtYXRpb24gY3JpdGVyaW9uKSB2YWx1ZXMgYW5kIGludGVycHJldCB0aGUgcmVzdWx0cy4gey19CgpIaW50OiBDaGVjayBgP3Bsb3QuTWNsdXN0YC4KCjxkZXRhaWxzPjxzdW1tYXJ5PlNvbHV0aW9uPC9zdW1tYXJ5PgoKYGBge3J9CiMgUGFpcndpc2UgY2x1c3RlcmluZyBwbG90cwpwbG90KG1jLCB3aGF0ID0gImNsYXNzaWZpY2F0aW9uIikKCiMgQklDIHBsb3Q7IGV4Y2x1ZGUgdHdvIG1vZGVscyB3aXRoIHZlcnkgbG93IEJJQwpwbG90KG1jLCB3aGF0ID0gIkJJQyIsIHlsaW0gPSByYW5nZShtYyRCSUNbLCAtKDE6MildLCBuYS5ybSA9IFRSVUUpLAogIGxlZ2VuZEFyZ3MgPSBsaXN0KHggPSAiYm90dG9tbGVmdCIpCikKYGBgCgpfX0ludGVycHJldGF0aW9uX186IEEgdGhyZWUtY29tcG9uZW50IG1peHR1cmUgd2l0aCBjb3ZhcmlhbmNlcyBoYXZpbmcKZGlmZmVyZW50IHNoYXBlcyBhbmQgdm9sdW1lcyBidXQgdGhlIHNhbWUgb3JpZW50YXRpb24gKFZWRSkgcmVzdWx0cyBpbiB0aGUKaGlnaGVzdCBCSUMuIAoKPC9kZXRhaWxzPgoKIyMjIyAzLiBSZWNyZWF0ZSB0aGUgUENBIGJpcGxvdCBmcm9tIFNlY3Rpb24gMSwgYnV0IGNvbG9yIGVhY2ggb2JzZXJ2YXRpb24gYWNjb3JkaW5nIHRvIGl0cyBjbHVzdGVyLiBDb21wYXJlIHRoZSByZXN1bHRpbmcgYmlwbG90IHRvIHRoZSBlYXJsaWVyIG9uZXMuIHstfQoKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CgpgYGB7cn0KIyBUYWJ1bGF0ZSBjbHVzdGVycyBhZ2FpbnN0IHRydWUgY2xhc3Nlcwp0YWJsZShjbGFzcywgbWMkY2xhc3NpZmljYXRpb24pCmBgYAoKQmlwbG90IHdpdGggYGdnYmlwbG90YDoKCmBgYHtyfQpwbG90MSA8LSBnZ2JpcGxvdCh3aW5lX3BjYSwgZ3JvdXBzID0gY2xhc3MpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gY29scykgKwogIGxhYnMoY29sb3IgPSAiVHJ1ZSBsYWJlbHMiKSArCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMC44LCBsZWdlbmQucG9zaXRpb24gPSAidG9wIikKCnBsb3QyIDwtIGdnYmlwbG90KHdpbmVfcGNhLCBncm91cHMgPSBmYWN0b3IobWMkY2xhc3NpZmljYXRpb24pKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoIiM5ZDA1ZjQiLCAiIzAxZDlkOSIsICIjZjRkYzAwIikpICsKICBsYWJzKGNvbG9yID0gIk1jbHVzdCBjbHVzdGVycyIpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAwLjgsIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQoKZ3JpZC5hcnJhbmdlKHBsb3QxLCBudWxsR3JvYigpLCBwbG90MiwgbmNvbCA9IDMsIHdpZHRocyA9IGMoMSwgMC4xLCAxKSkKYGBgCgpBbHRlcm5hdGl2ZWx5LCB1c2luZyBiYXNlIGBSYCBwbG90dGluZzoKCmBgYHtyfQpwYXIobWZyb3cgPSBjKDEsIDIpKQoKIyBUcnVlIGNsYXNzIGxhYmVscwpwbG90KFprLCBwY2ggPSAyMCwKICBjb2wgPSBjb2xzW2NsYXNzXSwKICB4bGFiID0gIlBDMSIsIHlsYWIgPSAiUEMyIgopCmxlZ2VuZCgidG9wbGVmdCIsIGxldmVscyhjbGFzcyksCiAgY29sID0gY29scywKICBwY2ggPSAxOQopCmFscGhhIDwtIDUgIyByZXNjYWxpbmcKZm9yIChpIGluIDE6MTMpIHsKICBhcnJvd3MoMCwgMCwgYWxwaGEgKiBWa1tpLCAxXSwgYWxwaGEgKiBWa1tpLCAyXSwgbGVuZ3RoID0gMC4yLCBjb2wgPSAiIzhmNDA0MCIpCiAgdGV4dChhbHBoYSAqIFZrW2ksIDFdLCBhbHBoYSAqIFZrW2ksIDJdLCByb3duYW1lcyhWaylbaV0sIGNvbCA9ICIjOGY0MDQwIikKfQoKY2x1c3Rlcl9jb2xzIDwtIGMoIjEiID0gIiM5ZDA1ZjQiLCAiMiIgPSAiIzAxZDlkOSIsICIzIiA9ICIjZjRkYzAwIikKIyBDbHVzdGVycwpwbG90KFprLCBwY2ggPSAyMCwKICBjb2wgPSBjbHVzdGVyX2NvbHNbZmFjdG9yKG1jJGNsYXNzaWZpY2F0aW9uKV0sCiAgeGxhYiA9ICJQQzEiLCB5bGFiID0gIlBDMiIKKQpsZWdlbmQoInRvcGxlZnQiLCBsZXZlbHMoZmFjdG9yKG1jJGNsYXNzaWZpY2F0aW9uKSksCiAgY29sID0gY2x1c3Rlcl9jb2xzLAogIHBjaCA9IDE5CikKYWxwaGEgPC0gNSAjIHJlc2NhbGluZwpmb3IgKGkgaW4gMToxMykgewogIGFycm93cygwLCAwLCBhbHBoYSAqIFZrW2ksIDFdLCBhbHBoYSAqIFZrW2ksIDJdLCBsZW5ndGggPSAwLjIsIGNvbCA9ICIjOGY0MDQwIikKICB0ZXh0KGFscGhhICogVmtbaSwgMV0sIGFscGhhICogVmtbaSwgMl0sIHJvd25hbWVzKFZrKVtpXSwgY29sID0gIiM4ZjQwNDAiKQp9CmBgYAoKX19JbnRlcnByZXRhdGlvbl9fOiBUaGUgZmlyc3QgY2x1c3RlciBjb3JyZXNwb25kcyB0byB0aGUgQmFyb2xvIGNsYXNzLCB0aGUgc2Vjb25kIGNsdXN0ZXIgdG8gdGhlIEdyaWdub2xpbm8gY2xhc3MgYW5kIHRoZSB0aGlyZCBjbHVzdGVyIHRvIEJhcmJlcmEuIApPdmVyYWxsLCB0aGUgdGhyZWUgY2x1c3RlcnMgcmVjb3ZlciB0aGUgdHJ1ZSBjbGFzcyBsYWJlbHMgdmVyeSB3ZWxsLCB3aXRoIG9ubHkgdHdvIEdyaWdub2xpbm8gd2luZXMgZmFsc2VseSBhc3NpZ25lZCB0byB0aGUgdGhpcmQgY2x1c3RlciAoQmFyYmVyYSkuCgo8L2RldGFpbHM+CgoKIyMjIyA0LiBTZWxlY3QgYW4gYXBwcm9wcmlhdGUgbnVtYmVyIG9mIFBDcyBmcm9tIHRoZSBQQ0EuIFJlcGVhdCB0aGUgbW9kZWwtYmFzZWQgY2x1c3RlcmluZyBvbiB0aGUgUENzLiBIb3cgZG8gdGhlIHJlc3VsdHMgY29tcGFyZSB0byB0aG9zZSBvbiB0aGUgZnVsbCBkYXRhPyB7LX0KCjxkZXRhaWxzPjxzdW1tYXJ5PlNvbHV0aW9uPC9zdW1tYXJ5PgpgYGB7cn0KIyBQcm9wb3J0aW9uIG9mIHZhcmlhbmNlIGV4cGxhaW5lZCBieSBlYWNoIFBDCndpbmVfcHJvcF92YXIgPC0gZGF0YS5mcmFtZSgKICBQQyA9IDE6bmNvbCh3aW5lX3BjYSR4KSwKICB2YXIgPSB3aW5lX3BjYSRzZGV2XjIgLyBzdW0od2luZV9wY2Ekc2Rldl4yKQopCgpnZ3Bsb3Qod2luZV9wcm9wX3ZhciwgYWVzKFBDLCB2YXIpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUoKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNy41LCBjb2wgPSAiZmlyZWJyaWNrIikgKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSAxOm5jb2wod2luZV9wY2EkeCkpICsKICBsYWJzKHkgPSAiUHJvcG9ydGlvbiBvZiB2YXJpYW5jZSIpCmBgYAoKVGhlIGZpcnN0IDcgUENzIGV4cGxhaW4gYWJvdXQgOTIlIG9mIHRoZSB2YXJpYW5jZSBpbiB0aGUgYHdpbmVgIGRhdGEuCgpgYGB7cn0KIyBTZWxlY3QgdGhlIGZpcnN0IGsgUENzCmsgPC0gNwpwY2FfWCA8LSB3aW5lX3BjYSR4WywgMTprXQoKIyBSZWZpdCB0aGUgbW9kZWwtYmFzZWQgY2x1c3RlcmluZwptYzIgPC0gTWNsdXN0KHBjYV9YKQpzdW1tYXJ5KG1jMikKc3VtbWFyeShtYzIkQklDKQpgYGAKCkEgZml2ZS1jb21wb25lbnQgbWl4dHVyZSB3aXRoIGRpYWdvbmFsIGNvdmFyaWFuY2VzIGhhdmluZyBlcXVhbCBzaGFwZSwgYnV0CnZhcnlpbmcgdm9sdW1lIChWRUkpLCByZXN1bHRzIGluIHRoZSBoaWdoZXN0IEJJQy4gCgpgYGB7cn0KIyBUYWJ1bGF0ZSBjbHVzdGVycyBhZ2FpbnN0IHRydWUgY2xhc3Nlcwp0YWJsZShjbGFzcywgbWMyJGNsYXNzaWZpY2F0aW9uKQpgYGAKCkJpcGxvdCB3aXRoIGBnZ2JpcGxvdGA6CgpgYGB7cn0KcGxvdDEgPC0gZ2diaXBsb3Qod2luZV9wY2EsIGdyb3VwcyA9IGNsYXNzKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGNvbHMpICsKICBsYWJzKGNvbG9yID0gIlRydWUgbGFiZWxzIikgKwogIHRoZW1lKGFzcGVjdC5yYXRpbyA9IDAuOCwgbGVnZW5kLnBvc2l0aW9uID0gInRvcCIpCgpwbG90MiA8LSBnZ2JpcGxvdCh3aW5lX3BjYSwgZ3JvdXBzID0gZmFjdG9yKG1jMiRjbGFzc2lmaWNhdGlvbikpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiIzlkMDVmNCIsICIjMDFkOWQ5IiwgIiMwMWQ5ZDkiLCAiIzAxZDlkOSIsICIjZjRkYzAwIikpICsKICBsYWJzKGNvbG9yID0gIk1jbHVzdCBjbHVzdGVycyIpICsKICB0aGVtZShhc3BlY3QucmF0aW8gPSAwLjgsIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiKQoKZ3JpZC5hcnJhbmdlKHBsb3QxLCBudWxsR3JvYigpLCBwbG90MiwgbmNvbCA9IDMsIHdpZHRocyA9IGMoMSwgMC4xLCAxKSkKYGBgCgpBbHRlcm5hdGl2ZWx5LCB1c2luZyBiYXNlIGBSYCBwbG90dGluZzoKCmBgYHtyfQpwYXIobWZyb3cgPSBjKDEsIDIpKQoKIyBUcnVlIGNsYXNzIGxhYmVscwpwbG90KFprLCBwY2ggPSAyMCwKICBjb2wgPSBjb2xzW2NsYXNzXSwKICB4bGFiID0gIlBDMSIsIHlsYWIgPSAiUEMyIgopCmxlZ2VuZCgidG9wbGVmdCIsIGxldmVscyhjbGFzcyksCiAgY29sID0gY29scywKICBwY2ggPSAxOQopCmFscGhhIDwtIDUgIyByZXNjYWxpbmcKZm9yIChpIGluIDE6MTMpIHsKICBhcnJvd3MoMCwgMCwgYWxwaGEgKiBWa1tpLCAxXSwgYWxwaGEgKiBWa1tpLCAyXSwgbGVuZ3RoID0gMC4yLCBjb2wgPSAiIzhmNDA0MCIpCiAgdGV4dChhbHBoYSAqIFZrW2ksIDFdLCBhbHBoYSAqIFZrW2ksIDJdLCByb3duYW1lcyhWaylbaV0sIGNvbCA9ICIjOGY0MDQwIikKfQoKY2x1c3Rlcl9jb2xzIDwtIGMoIjEiID0gIiM5ZDA1ZjQiLCAiMiIgPSAiIzAxZDlkOSIsICIzIiA9ICIjMDFkOWQ5IiwgIjQiID0gIiMwMWQ5ZDkiLCAiNSIgPSAiI2Y0ZGMwMCIpCiMgQ2x1c3RlcnMKcGxvdChaaywgcGNoID0gMjAsCiAgY29sID0gY2x1c3Rlcl9jb2xzW2ZhY3RvcihtYzIkY2xhc3NpZmljYXRpb24pXSwKICB4bGFiID0gIlBDMSIsIHlsYWIgPSAiUEMyIgopCmxlZ2VuZCgidG9wbGVmdCIsIGxldmVscyhmYWN0b3IobWMyJGNsYXNzaWZpY2F0aW9uKSksCiAgY29sID0gY2x1c3Rlcl9jb2xzLAogIHBjaCA9IDE5CikKYWxwaGEgPC0gNSAjIHJlc2NhbGluZwpmb3IgKGkgaW4gMToxMykgewogIGFycm93cygwLCAwLCBhbHBoYSAqIFZrW2ksIDFdLCBhbHBoYSAqIFZrW2ksIDJdLCBsZW5ndGggPSAwLjIsIGNvbCA9ICIjOGY0MDQwIikKICB0ZXh0KGFscGhhICogVmtbaSwgMV0sIGFscGhhICogVmtbaSwgMl0sIHJvd25hbWVzKFZrKVtpXSwgY29sID0gIiM4ZjQwNDAiKQp9CmBgYAoKX19JbnRlcnByZXRhdGlvbl9fOiBUaGUgZmlyc3QgY2x1c3RlciBjb3JyZXNwb25kcyB0byBtb3N0IG9mIHRoZSBCYXJvbG8gY2xhc3MuIFRoZSBmaWZ0aCBjb2xvciBtYWlubHkgY29ycmVzcG9uZHMgdG8gdGhlIEJhcmJlcmEgY2xhc3MsIHdoaWxlIGNvbnRhaW5pbmcgYSBmZXcgR3JpZ25vbGlubyB3aW5lcy4KQ29tYmluaW5nIHRoZSBzZWNvbmQsIHRoaXJkIGFuZCBmb3VydGggY2x1c3RlciByZWNvdmVycyBtb3N0IG9mIHRoZSBHcmlnbm9saW5vIGNsYXNzLCB3aXRoIHNvbWUgb3ZlcmxhcCB3aXRoIHRoZSByZW1haW5pbmcgY2xhc3Nlcy4KT3ZlcmFsbCwgYWZ0ZXIgY29tYmluaW5nIHRoZSB0aHJlZSBzbWFsbGVyIGNsdXN0ZXJzLCB0aGUgY2x1c3RlcmluZyByZWNvdmVycyB0aGUgdHJ1ZSBjbGFzcyBsYWJlbHMgZmFpcmx5IHdlbGwsIHRob3VnaCBub3QgcXVpdGUgYXMgY2xvc2VseSBhcyB0aGUgY2x1c3RlcmluZyBvbiB0aGUgb3JpZ2luYWwgZGF0YS4KCjwvZGV0YWlscz4KCiMgQWRkaXRpb25hbCByZXNvdXJjZXMgey19Ci0gU2VjdGlvbiAxNC4zIG9mIEBlc2wtYm9vawoKCmBgYHtyLCBjaGlsZD0iX3Nlc3Npb24taW5mby5SbWQifQpgYGAKClt3aW5lXTogaHR0cHM6Ly9yZHJyLmlvL2NyYW4vZ2NsdXMvbWFuL3dpbmUuaHRtbApbc2NydWNjYTIwMTZdOiBodHRwczovL3N2bi5yLXByb2plY3Qub3JnL1Jqb3VybmFsL2h0bWwvYXJjaGl2ZS8yMDE2L1JKLTIwMTYtMDIxL1JKLTIwMTYtMDIxLnBkZgoKIyBSZWZlcmVuY2VzIHstfQ==