Note: Some results may differ from the hard copy book due to the changing of sampling procedures introduced in R 3.6.0. See http://bit.ly/35D1SW7 for more details. Access and run the source code for this notebook here.
Hidden chapter requirements used in the book to set the plotting theme and load packages used in hidden code chunks:
knitr::opts_chunk$set(
message = FALSE,
warning = FALSE,
cache = FALSE
)
# Set the graphical theme
ggplot2::theme_set(ggplot2::theme_light())
# packages used behind the scenes
library(knitr)
library(kableExtra)
Prerequisites
For this chapter we’ll use the following packages:
# Helper packages
library(dplyr) # for data manipulation
library(ggplot2) # for data visualization
# Modeling packages
library(mclust) # for fitting clustering algorithms
To illustrate the main concepts of model-based clustering we’ll use the geyser
data provided by the MASS package along with the my_basket
data:
data(geyser, package = 'MASS')
url <- "https://koalaverse.github.io/homlr/data/my_basket.csv"
my_basket <- readr::read_csv(url)
Measuring probability and uncertainty
Figure 22.1:
ggplot(geyser, aes(waiting, duration)) +
geom_point(size = 0.75, alpha = 0.5) +
geom_density2d(aes(alpha = ..level..), show.legend = FALSE) +
scale_x_continuous("X") +
scale_y_continuous("Y") +
theme(
axis.ticks = element_blank(),
axis.text = element_blank()
)
# Apply GMM model with 3 components
geyser_mc <- Mclust(geyser, G = 3, verbose = FALSE)
# Plot results
plot(geyser_mc, what = "density")
plot(geyser_mc, what = "uncertainty")
# Observations with high uncertainty
sort(geyser_mc$uncertainty, decreasing = TRUE) %>% head()
187 211 85 285 28 206
0.4689087 0.4542588 0.4355496 0.4355496 0.4312406 0.4168440
Covariance types
Table 22.1:
readr::read_csv('data/GMM-covariances.csv') %>%
knitr::kable(caption = 'Parameterizations of the covariance matrix',
align = c('c', 'c', 'c', 'c', 'c', 'c')) %>%
kable_styling(bootstrap_options = "striped", full_width = FALSE)
Parameterizations of the covariance matrix
Model |
Family |
Volume |
Shape |
Orientation |
Identifier |
1 |
Spherical |
Equal |
Equal |
NA |
EII |
2 |
Spherical |
Variable |
Equal |
NA |
VII |
3 |
Diagonal |
Equal |
Equal |
Axes |
EEI |
4 |
Diagonal |
Variable |
Equal |
Axes |
VEI |
5 |
Diagonal |
Equal |
Variable |
Axes |
EVI |
6 |
Diagonal |
Variable |
Variable |
Axes |
VVI |
7 |
General |
Equal |
Equal |
Equal |
EEE |
8 |
General |
Equal |
Variable |
Equal |
EVE |
9 |
General |
Variable |
Equal |
Equal |
VEE |
10 |
General |
Variable |
Variable |
Equal |
VVE |
11 |
General |
Equal |
Equal |
Variable |
EEV |
12 |
General |
Variable |
Equal |
Variable |
VEV |
13 |
General |
Equal |
Variable |
Variable |
EVV |
14 |
General |
Variable |
Variable |
Variable |
VVV |
Figure 22.3:
par(
mfrow = c(2, 5), mar = c(0.5, 0.5, 2.5, 0.5),
cex = .4,
pch = 19,
col = alpha('black', 0.25),
xaxt = 'n', yaxt = 'n', ann = FALSE, cex.main = 3
)
# EII
set.seed(111)
obj <- mlbench::mlbench.hypercube(n = 400)
df <- data.frame(
x = obj$x[, 1],
y = obj$x[, 2]
)
m <- Mclust(df, verbose = FALSE)
plot(m, what = "density", type = "hdr")
points(df)
title(main = m$modelName)
# VII
df <- data_frame(
x1 = c(rnorm(100, sd = .75), rnorm(100, sd = .25) + 5),
x2 = c(rnorm(100, sd = .75), rnorm(100, sd = .25))
)
m <- Mclust(df, G = 2, verbose = FALSE)
plot(m, what = "density", type = "hdr")
points(df)
title(main = m$modelName)
# EEI
df <- data_frame(
x1 = c(rnorm(100, sd = .75), rnorm(100, sd = .75)),
x2 = c(rnorm(100, sd = .75), rnorm(100, sd = .75) - 5)
)
m <- Mclust(df, G = 2, modelNames = "EEI", verbose = FALSE)
plot(m, what = "density", type = "hdr")
points(df)
title(main = m$modelName)
# VVI
set.seed(111)
obj <- mlbench::mlbench.cuboids(300)
df <- data.frame(
x = obj$x[, 1],
y = obj$x[, 2]
)
m <- Mclust(df, verbose = FALSE)
plot(m, what = "density", type = "hdr")
points(df)
title(main = m$modelName)
# VVE
m <- Mclust(faithful, G = 2, verbose = FALSE)
plot(m, what = "density", type = "hdr")
points(faithful)
title(main = m$modelName)
# EEE
set.seed(111)
obj <- mlbench::mlbench.cassini(200)
df <- data.frame(
x = obj$x[, 1],
y = obj$x[, 2]
)
m <- Mclust(df, G = 20, verbose = FALSE)
plot(m, what = "density", type = "hdr")
points(df)
title(main = m$modelName)
# EEV
set.seed(111)
obj <- mlbench::mlbench.spirals(200, 1, 0.025)
df <- data.frame(
x = obj$x[, 1],
y = obj$x[, 2]
)
m <- Mclust(df, G = 20, verbose = FALSE)
plot(m, what = "density", type = "hdr")
points(df)
title(main = m$modelName)
# VEV
m <- Mclust(mtcars[, c('mpg', 'wt')], G = 2, verbose = FALSE)
plot(m, what = "density", type = "hdr")
points(mtcars[, c('mpg', 'wt')])
title(main = m$modelName)
# EEV
set.seed(111)
obj <- mlbench::mlbench.smiley()
df <- data.frame(
x = obj$x[, 1],
y = obj$x[, 2]
)
m <- Mclust(df, G = 20, verbose = FALSE)
plot(m, what = "density", type = "hdr")
points(df)
title(main = m$modelName)
# EVE
set.seed(111)
obj <- mlbench::mlbench.1spiral(300, sd = .15)
df <- data.frame(
x = obj[, 1],
y = obj[, 2]
)
m <- Mclust(df, verbose = FALSE)
plot(m, what = "density", type = "hdr")
points(df)
title(main = m$modelName)
Model selection
summary(geyser_mc)
----------------------------------------------------
Gaussian finite mixture model fitted by EM algorithm
----------------------------------------------------
Mclust EEI (diagonal, equal volume and shape) model with 3 components:
Clustering table:
1 2 3
91 107 101
geyser_optimal_mc <- Mclust(geyser, verbose = FALSE)
summary(geyser_optimal_mc)
----------------------------------------------------
Gaussian finite mixture model fitted by EM algorithm
----------------------------------------------------
Mclust VVI (diagonal, varying volume and shape) model with 4 components:
Clustering table:
1 2 3 4
90 17 98 94
legend_args <- list(x = "bottomright", ncol = 5)
plot(geyser_optimal_mc, what = 'BIC', legendArgs = legend_args)
plot(geyser_optimal_mc, what = 'classification')
plot(geyser_optimal_mc, what = 'uncertainty')
My basket example
my_basket_mc <- Mclust(my_basket, 1:20, verbose = FALSE)
summary(my_basket_mc)
----------------------------------------------------
Gaussian finite mixture model fitted by EM algorithm
----------------------------------------------------
Mclust EEV (ellipsoidal, equal volume and shape) model with 6 components:
Clustering table:
1 2 3 4 5 6
391 403 75 315 365 451
plot(my_basket_mc, what = 'BIC',
legendArgs = list(x = "bottomright", ncol = 5))
probabilities <- my_basket_mc$z
colnames(probabilities) <- paste0('C', 1:6)
probabilities <- probabilities %>%
as.data.frame() %>%
mutate(id = row_number()) %>%
tidyr::gather(cluster, probability, -id)
ggplot(probabilities, aes(probability)) +
geom_histogram() +
facet_wrap(~ cluster, nrow = 2)
uncertainty <- data.frame(
id = 1:nrow(my_basket),
cluster = my_basket_mc$classification,
uncertainty = my_basket_mc$uncertainty
)
uncertainty %>%
group_by(cluster) %>%
filter(uncertainty > 0.25) %>%
ggplot(aes(uncertainty, reorder(id, uncertainty))) +
geom_point() +
facet_wrap(~ cluster, scales = 'free_y', nrow = 1)
cluster2 <- my_basket %>%
scale() %>%
as.data.frame() %>%
mutate(cluster = my_basket_mc$classification) %>%
filter(cluster == 2) %>%
select(-cluster)
cluster2 %>%
tidyr::gather(product, std_count) %>%
group_by(product) %>%
summarize(avg = mean(std_count)) %>%
ggplot(aes(avg, reorder(product, avg))) +
geom_point() +
labs(x = "Average standardized consumption", y = NULL)
LS0tCnRpdGxlOiAiQ2hhcHRlciAyMTogTW9kZWwtYmFzZWQgQ2x1c3RlcmluZyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKX19Ob3RlX186IFNvbWUgcmVzdWx0cyBtYXkgZGlmZmVyIGZyb20gdGhlIGhhcmQgY29weSBib29rIGR1ZSB0byB0aGUgY2hhbmdpbmcgb2YKc2FtcGxpbmcgcHJvY2VkdXJlcyBpbnRyb2R1Y2VkIGluIFIgMy42LjAuIFNlZSBodHRwOi8vYml0Lmx5LzM1RDFTVzcgZm9yIG1vcmUKZGV0YWlscy4gQWNjZXNzIGFuZCBydW4gdGhlIHNvdXJjZSBjb2RlIGZvciB0aGlzIG5vdGVib29rIFtoZXJlXShodHRwczovL3JzdHVkaW8uY2xvdWQvcHJvamVjdC84MDExODUpLgoKSGlkZGVuIGNoYXB0ZXIgcmVxdWlyZW1lbnRzIHVzZWQgaW4gdGhlIGJvb2sgdG8gc2V0IHRoZSBwbG90dGluZyB0aGVtZSBhbmQgbG9hZApwYWNrYWdlcyB1c2VkIGluIGhpZGRlbiBjb2RlIGNodW5rczoKCmBgYHtyIHNldHVwfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgbWVzc2FnZSA9IEZBTFNFLCAKICB3YXJuaW5nID0gRkFMU0UsIAogIGNhY2hlID0gRkFMU0UKKQoKIyBTZXQgdGhlIGdyYXBoaWNhbCB0aGVtZQpnZ3Bsb3QyOjp0aGVtZV9zZXQoZ2dwbG90Mjo6dGhlbWVfbGlnaHQoKSkKCiMgcGFja2FnZXMgdXNlZCBiZWhpbmQgdGhlIHNjZW5lcwpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmBgYAoKIyMgUHJlcmVxdWlzaXRlcwoKRm9yIHRoaXMgY2hhcHRlciB3ZSdsbCB1c2UgdGhlIGZvbGxvd2luZyBwYWNrYWdlczoKCmBgYHtyIG1vZGVsLWNsdXN0ZXJpbmctcGtnc30KIyBIZWxwZXIgcGFja2FnZXMKbGlicmFyeShkcGx5cikgICAgIyBmb3IgZGF0YSBtYW5pcHVsYXRpb24KbGlicmFyeShnZ3Bsb3QyKSAgIyBmb3IgZGF0YSB2aXN1YWxpemF0aW9uCgojIE1vZGVsaW5nIHBhY2thZ2VzCmxpYnJhcnkobWNsdXN0KSAgICMgZm9yIGZpdHRpbmcgY2x1c3RlcmluZyBhbGdvcml0aG1zCmBgYAoKVG8gaWxsdXN0cmF0ZSB0aGUgbWFpbiBjb25jZXB0cyBvZiBtb2RlbC1iYXNlZCBjbHVzdGVyaW5nIHdlJ2xsIHVzZSB0aGUgYGdleXNlcmAgZGF0YSBwcm92aWRlZCBieSB0aGUgX19NQVNTX18gcGFja2FnZSBhbG9uZyB3aXRoIHRoZSBgbXlfYmFza2V0YCBkYXRhOgoKYGBge3IgbW9kZWwtY2x1c3RlcmluZy1kYXRhfQpkYXRhKGdleXNlciwgcGFja2FnZSA9ICdNQVNTJykKCnVybCA8LSAiaHR0cHM6Ly9rb2FsYXZlcnNlLmdpdGh1Yi5pby9ob21sci9kYXRhL215X2Jhc2tldC5jc3YiCm15X2Jhc2tldCA8LSByZWFkcjo6cmVhZF9jc3YodXJsKQpgYGAKCgojIyBNZWFzdXJpbmcgcHJvYmFiaWxpdHkgYW5kIHVuY2VydGFpbnR5CgpGaWd1cmUgMjIuMToKCmBgYHtyIG11bHRpdmFyaWF0ZS1kZW5zaXR5LXBsb3QsIGZpZy5jYXA9J0RhdGEgcG9pbnRzIGFjcm9zcyB0d28gZmVhdHVyZXMgKFggYW5kIFkpIGFwcGVhciB0byBjb21lIGZyb20gdGhyZWUgbXVsdGl2YXJpYXRlIG5vcm1hbCBkaXN0cmlidXRpb25zLicsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTMuNX0KZ2dwbG90KGdleXNlciwgYWVzKHdhaXRpbmcsIGR1cmF0aW9uKSkgKwogIGdlb21fcG9pbnQoc2l6ZSA9IDAuNzUsIGFscGhhID0gMC41KSArCiAgZ2VvbV9kZW5zaXR5MmQoYWVzKGFscGhhID0gLi5sZXZlbC4uKSwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHNjYWxlX3hfY29udGludW91cygiWCIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoIlkiKSArCiAgdGhlbWUoCiAgICBheGlzLnRpY2tzID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50ZXh0ID0gZWxlbWVudF9ibGFuaygpCiAgKQpgYGAKCmBgYHtyIGdleXNlci1tYzEtcGxvdCwgb3V0LndpZHRoPSI0OCUiLCBmaWcuYXNwPTEsIGZpZy5zaG93PSdob2xkJywgZmlnLmNhcD0nTXVsdGl2YXJpYXRlIGRlbnNpdHkgcGxvdCAobGVmdCkgaGlnaGxpZ2h0aW5nIHRocmVlIGNsdXN0ZXJzIGluIHRoZSBgZ2V5c2VyYCBkYXRhIGFuZCBhbiB1bmNlcnRhaW50eSBwbG90IChyaWdodCkgaGlnaGxpZ2h0aW5nIG9ic2VydmF0aW9ucyB3aXRoIGhpZ2ggdW5jZXJ0YWludHkgb2Ygd2hpY2ggY2x1c3RlciB0aGV5IGFyZSBhIG1lbWJlciBvZi4nfQojIEFwcGx5IEdNTSBtb2RlbCB3aXRoIDMgY29tcG9uZW50cwpnZXlzZXJfbWMgPC0gTWNsdXN0KGdleXNlciwgRyA9IDMsIHZlcmJvc2UgPSBGQUxTRSkKCiMgUGxvdCByZXN1bHRzCnBsb3QoZ2V5c2VyX21jLCB3aGF0ID0gImRlbnNpdHkiKQpwbG90KGdleXNlcl9tYywgd2hhdCA9ICJ1bmNlcnRhaW50eSIpCmBgYAoKYGBge3IgZ2V5c2VyLW1jLWhpZ2gtdW5jZXJ0YWludHktb2JzfQojIE9ic2VydmF0aW9ucyB3aXRoIGhpZ2ggdW5jZXJ0YWludHkKc29ydChnZXlzZXJfbWMkdW5jZXJ0YWludHksIGRlY3JlYXNpbmcgPSBUUlVFKSAlPiUgaGVhZCgpCmBgYAoKIyMgQ292YXJpYW5jZSB0eXBlcwoKVGFibGUgMjIuMToKCmBgYHtyIGNvdmFyaWFuY2UtcGFyYW1ldGVyaXphdGlvbiwgZmlnLmNhcD0nUGFyYW1ldGVyaXphdGlvbnMgb2YgdGhlIGNvdmFyaWFuY2UgbWF0cml4Lid9CnJlYWRyOjpyZWFkX2NzdignZGF0YS9HTU0tY292YXJpYW5jZXMuY3N2JykgJT4lCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSAnUGFyYW1ldGVyaXphdGlvbnMgb2YgdGhlIGNvdmFyaWFuY2UgbWF0cml4JywKICAgICAgICAgICAgICAgIGFsaWduID0gYygnYycsICdjJywgJ2MnLCAnYycsICdjJywgJ2MnKSkgJT4lCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9ICJzdHJpcGVkIiwgZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCkZpZ3VyZSAyMi4zOgoKYGBge3IgdmlzdWFsaXplLWRpZmZlcmVudC1jb3ZhcmlhbmNlLW1vZGVscywgZmlnLmNhcD0nR3JhcGhpY2FsIHJlcHJlc2VudGF0aW9uIG9mIGhvdyBkaWZmZXJlbnQgY292YXJpYW5jZSBtb2RlbHMgYWxsb3cgR01NcyB0byBjYXB0dXJlIGRpZmZlcmVudCBjbHVzdGVyIHN0cnVjdHVyZXMuJywgZmlnLmhlaWdodD00LjUsIGZpZy53aWR0aD0xMH0KcGFyKAogIG1mcm93ID0gYygyLCA1KSwgbWFyID0gYygwLjUsIDAuNSwgMi41LCAwLjUpLAogIGNleCA9IC40LAogIHBjaCA9IDE5LAogIGNvbCA9IGFscGhhKCdibGFjaycsIDAuMjUpLAogIHhheHQgPSAnbicsIHlheHQgPSAnbicsIGFubiA9IEZBTFNFLCBjZXgubWFpbiA9IDMKICApCgojIEVJSQpzZXQuc2VlZCgxMTEpCm9iaiA8LSBtbGJlbmNoOjptbGJlbmNoLmh5cGVyY3ViZShuID0gNDAwKQpkZiA8LSBkYXRhLmZyYW1lKAogICAgeCA9IG9iaiR4WywgMV0sCiAgICB5ID0gb2JqJHhbLCAyXQopCm0gPC0gTWNsdXN0KGRmLCB2ZXJib3NlID0gRkFMU0UpCnBsb3QobSwgd2hhdCA9ICJkZW5zaXR5IiwgdHlwZSA9ICJoZHIiKQpwb2ludHMoZGYpCnRpdGxlKG1haW4gPSBtJG1vZGVsTmFtZSkKCiMgVklJCmRmIDwtIGRhdGFfZnJhbWUoCiAgICB4MSA9IGMocm5vcm0oMTAwLCBzZCA9IC43NSksIHJub3JtKDEwMCwgc2QgPSAuMjUpICsgNSksCiAgICB4MiA9IGMocm5vcm0oMTAwLCBzZCA9IC43NSksIHJub3JtKDEwMCwgc2QgPSAuMjUpKQopCm0gPC0gTWNsdXN0KGRmLCBHID0gMiwgdmVyYm9zZSA9IEZBTFNFKQpwbG90KG0sIHdoYXQgPSAiZGVuc2l0eSIsIHR5cGUgPSAiaGRyIikKcG9pbnRzKGRmKQp0aXRsZShtYWluID0gbSRtb2RlbE5hbWUpCgojIEVFSQpkZiA8LSBkYXRhX2ZyYW1lKAogICAgeDEgPSBjKHJub3JtKDEwMCwgc2QgPSAuNzUpLCBybm9ybSgxMDAsIHNkID0gLjc1KSksCiAgICB4MiA9IGMocm5vcm0oMTAwLCBzZCA9IC43NSksIHJub3JtKDEwMCwgc2QgPSAuNzUpIC0gNSkKKQptIDwtIE1jbHVzdChkZiwgRyA9IDIsIG1vZGVsTmFtZXMgPSAiRUVJIiwgdmVyYm9zZSA9IEZBTFNFKQpwbG90KG0sIHdoYXQgPSAiZGVuc2l0eSIsIHR5cGUgPSAiaGRyIikKcG9pbnRzKGRmKQp0aXRsZShtYWluID0gbSRtb2RlbE5hbWUpCgojIFZWSQpzZXQuc2VlZCgxMTEpCm9iaiA8LSBtbGJlbmNoOjptbGJlbmNoLmN1Ym9pZHMoMzAwKQpkZiA8LSBkYXRhLmZyYW1lKAogICAgeCA9IG9iaiR4WywgMV0sCiAgICB5ID0gb2JqJHhbLCAyXQopCm0gPC0gTWNsdXN0KGRmLCB2ZXJib3NlID0gRkFMU0UpCnBsb3QobSwgd2hhdCA9ICJkZW5zaXR5IiwgdHlwZSA9ICJoZHIiKQpwb2ludHMoZGYpCnRpdGxlKG1haW4gPSBtJG1vZGVsTmFtZSkKCiMgVlZFCm0gPC0gTWNsdXN0KGZhaXRoZnVsLCBHID0gMiwgdmVyYm9zZSA9IEZBTFNFKQpwbG90KG0sIHdoYXQgPSAiZGVuc2l0eSIsIHR5cGUgPSAiaGRyIikKcG9pbnRzKGZhaXRoZnVsKQp0aXRsZShtYWluID0gbSRtb2RlbE5hbWUpCgojIEVFRQpzZXQuc2VlZCgxMTEpCm9iaiA8LSBtbGJlbmNoOjptbGJlbmNoLmNhc3NpbmkoMjAwKQpkZiA8LSBkYXRhLmZyYW1lKAogICAgeCA9IG9iaiR4WywgMV0sCiAgICB5ID0gb2JqJHhbLCAyXQopCm0gPC0gTWNsdXN0KGRmLCBHID0gMjAsIHZlcmJvc2UgPSBGQUxTRSkKcGxvdChtLCB3aGF0ID0gImRlbnNpdHkiLCB0eXBlID0gImhkciIpCnBvaW50cyhkZikKdGl0bGUobWFpbiA9IG0kbW9kZWxOYW1lKQoKIyBFRVYKc2V0LnNlZWQoMTExKQpvYmogPC0gbWxiZW5jaDo6bWxiZW5jaC5zcGlyYWxzKDIwMCwgMSwgMC4wMjUpCmRmIDwtIGRhdGEuZnJhbWUoCiAgICB4ID0gb2JqJHhbLCAxXSwKICAgIHkgPSBvYmokeFssIDJdCikKbSA8LSBNY2x1c3QoZGYsIEcgPSAyMCwgdmVyYm9zZSA9IEZBTFNFKQpwbG90KG0sIHdoYXQgPSAiZGVuc2l0eSIsIHR5cGUgPSAiaGRyIikKcG9pbnRzKGRmKQp0aXRsZShtYWluID0gbSRtb2RlbE5hbWUpCgojIFZFVgptIDwtIE1jbHVzdChtdGNhcnNbLCBjKCdtcGcnLCAnd3QnKV0sIEcgPSAyLCB2ZXJib3NlID0gRkFMU0UpCnBsb3QobSwgd2hhdCA9ICJkZW5zaXR5IiwgdHlwZSA9ICJoZHIiKQpwb2ludHMobXRjYXJzWywgYygnbXBnJywgJ3d0JyldKQp0aXRsZShtYWluID0gbSRtb2RlbE5hbWUpCgojIEVFVgpzZXQuc2VlZCgxMTEpCm9iaiA8LSBtbGJlbmNoOjptbGJlbmNoLnNtaWxleSgpCmRmIDwtIGRhdGEuZnJhbWUoCiAgICB4ID0gb2JqJHhbLCAxXSwKICAgIHkgPSBvYmokeFssIDJdCikKbSA8LSBNY2x1c3QoZGYsIEcgPSAyMCwgdmVyYm9zZSA9IEZBTFNFKQpwbG90KG0sIHdoYXQgPSAiZGVuc2l0eSIsIHR5cGUgPSAiaGRyIikKcG9pbnRzKGRmKQp0aXRsZShtYWluID0gbSRtb2RlbE5hbWUpCgojIEVWRQpzZXQuc2VlZCgxMTEpCm9iaiA8LSBtbGJlbmNoOjptbGJlbmNoLjFzcGlyYWwoMzAwLCBzZCA9IC4xNSkKZGYgPC0gZGF0YS5mcmFtZSgKICAgIHggPSBvYmpbLCAxXSwKICAgIHkgPSBvYmpbLCAyXQopCm0gPC0gTWNsdXN0KGRmLCB2ZXJib3NlID0gRkFMU0UpCnBsb3QobSwgd2hhdCA9ICJkZW5zaXR5IiwgdHlwZSA9ICJoZHIiKQpwb2ludHMoZGYpCnRpdGxlKG1haW4gPSBtJG1vZGVsTmFtZSkKYGBgCgojIyBNb2RlbCBzZWxlY3Rpb24KCmBgYHtyIGdleXNlci1tb2RlbC1mYW1pbHksIGxpbmV3aWR0aCA9IDcwfQpzdW1tYXJ5KGdleXNlcl9tYykKYGBgCgpgYGB7ciBnZXlzZXItbWMyLXBsb3QsIG91dC53aWR0aD0iMzIlIiwgZmlnLmFzcD0xLCBmaWcuc2hvdz0naG9sZCcsIGZpZy5jYXA9J0lkZW50aWZ5aW5nIHRoZSBvcHRpbWFsIEdNTSBtb2RlbCBhbmQgbnVtYmVyIG9mIGNsdXN0ZXJzIGZvciB0aGUgYGdleXNlcmAgZGF0YSAobGVmdCkuIFRoZSBjbGFzc2lmaWNhdGlvbiAoY2VudGVyKSBhbmQgdW5jZXJ0YWludHkgKHJpZ2h0KSBwbG90cyBpbGx1c3RyYXRlIHdoaWNoIG9ic2VydmF0aW9ucyBhcmUgYXNzaWduZWQgdG8gZWFjaCBjbHVzdGVyIGFuZCB0aGVpciBsZXZlbCBvZiBhc3NpZ25tZW50IHVuY2VydGFpbnR5LicsIGxpbmV3aWR0aCA9IDcwfQpnZXlzZXJfb3B0aW1hbF9tYyA8LSBNY2x1c3QoZ2V5c2VyLCB2ZXJib3NlID0gRkFMU0UpCgpzdW1tYXJ5KGdleXNlcl9vcHRpbWFsX21jKQoKbGVnZW5kX2FyZ3MgPC0gbGlzdCh4ID0gImJvdHRvbXJpZ2h0IiwgbmNvbCA9IDUpCnBsb3QoZ2V5c2VyX29wdGltYWxfbWMsIHdoYXQgPSAnQklDJywgbGVnZW5kQXJncyA9IGxlZ2VuZF9hcmdzKQpwbG90KGdleXNlcl9vcHRpbWFsX21jLCB3aGF0ID0gJ2NsYXNzaWZpY2F0aW9uJykKcGxvdChnZXlzZXJfb3B0aW1hbF9tYywgd2hhdCA9ICd1bmNlcnRhaW50eScpCmBgYAoKCiMjIE15IGJhc2tldCBleGFtcGxlCgpgYGB7ciBteS1iYXNrZXQtQklDLCBmaWcuY2FwPSdCSUMgc2NvcmVzIGZvciBjbHVzdGVycyAoY29tcG9uZW50cykgcmFuZ2luZyBmcm9tIDEtMjAnLCBmaWcuaGVpZ2h0PTQuNSwgbGluZXdpZHRoID0gNzB9Cm15X2Jhc2tldF9tYyA8LSBNY2x1c3QobXlfYmFza2V0LCAxOjIwLCB2ZXJib3NlID0gRkFMU0UpCgpzdW1tYXJ5KG15X2Jhc2tldF9tYykKCnBsb3QobXlfYmFza2V0X21jLCB3aGF0ID0gJ0JJQycsIAogICAgIGxlZ2VuZEFyZ3MgPSBsaXN0KHggPSAiYm90dG9tcmlnaHQiLCBuY29sID0gNSkpCmBgYAoKYGBge3IgbXktYmFza2V0LXByb2JhYmlsaXRpZXMsIGZpZy5jYXA9J0Rpc3RyaWJ1dGlvbiBvZiBwcm9iYWJpbGl0aWVzIGZvciBhbGwgb2JzZXJ2YXRpb25zIGFsaWduaW5nIHRvIGVhY2ggb2YgdGhlIHNpeCBjbHVzdGVycy4nLCBmaWcuaGVpZ2h0PTR9CnByb2JhYmlsaXRpZXMgPC0gbXlfYmFza2V0X21jJHogCmNvbG5hbWVzKHByb2JhYmlsaXRpZXMpIDwtIHBhc3RlMCgnQycsIDE6NikKCnByb2JhYmlsaXRpZXMgPC0gcHJvYmFiaWxpdGllcyAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgbXV0YXRlKGlkID0gcm93X251bWJlcigpKSAlPiUKICB0aWR5cjo6Z2F0aGVyKGNsdXN0ZXIsIHByb2JhYmlsaXR5LCAtaWQpCgpnZ3Bsb3QocHJvYmFiaWxpdGllcywgYWVzKHByb2JhYmlsaXR5KSkgKwogIGdlb21faGlzdG9ncmFtKCkgKwogIGZhY2V0X3dyYXAofiBjbHVzdGVyLCBucm93ID0gMikKYGBgCgpgYGB7ciBjbHVzdGVyLXVuY2VydGFpbnR5LCBmaWcud2lkdGg9MTAsIGZpZy5jYXA9J09ic2VydmF0aW9ucyB0aGF0IGFyZSBhbGlnbmVkIHRvIGVhY2ggY2x1c3RlciBidXQgdGhlaXIgdW5jZXJ0YWludHkgb2YgbWVtYmVyc2hpcCBpcyBncmVhdGVyIHRoYW4gMC4yNS4nfQp1bmNlcnRhaW50eSA8LSBkYXRhLmZyYW1lKAogIGlkID0gMTpucm93KG15X2Jhc2tldCksCiAgY2x1c3RlciA9IG15X2Jhc2tldF9tYyRjbGFzc2lmaWNhdGlvbiwKICB1bmNlcnRhaW50eSA9IG15X2Jhc2tldF9tYyR1bmNlcnRhaW50eQopCgp1bmNlcnRhaW50eSAlPiUKICBncm91cF9ieShjbHVzdGVyKSAlPiUKICBmaWx0ZXIodW5jZXJ0YWludHkgPiAwLjI1KSAlPiUKICBnZ3Bsb3QoYWVzKHVuY2VydGFpbnR5LCByZW9yZGVyKGlkLCB1bmNlcnRhaW50eSkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBmYWNldF93cmFwKH4gY2x1c3Rlciwgc2NhbGVzID0gJ2ZyZWVfeScsIG5yb3cgPSAxKQpgYGAKCmBgYHtyIGNsdXN0ZXIyLWNvbnN1bXB0aW9uLCBmaWcuaGVpZ2h0PTUsIGZpZy5jYXA9J0F2ZXJhZ2Ugc3RhbmRhcmRpemVkIGNvbnN1bXB0aW9uIGZvciBjbHVzdGVyIDIgb2JzZXJ2YXRpb25zIGNvbXBhcmVkIHRvIGFsbCBvYnNlcnZhdGlvbnMuJ30KY2x1c3RlcjIgPC0gbXlfYmFza2V0ICU+JQogIHNjYWxlKCkgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIG11dGF0ZShjbHVzdGVyID0gbXlfYmFza2V0X21jJGNsYXNzaWZpY2F0aW9uKSAlPiUKICBmaWx0ZXIoY2x1c3RlciA9PSAyKSAlPiUKICBzZWxlY3QoLWNsdXN0ZXIpCgpjbHVzdGVyMiAlPiUKICB0aWR5cjo6Z2F0aGVyKHByb2R1Y3QsIHN0ZF9jb3VudCkgJT4lCiAgZ3JvdXBfYnkocHJvZHVjdCkgJT4lCiAgc3VtbWFyaXplKGF2ZyA9IG1lYW4oc3RkX2NvdW50KSkgJT4lCiAgZ2dwbG90KGFlcyhhdmcsIHJlb3JkZXIocHJvZHVjdCwgYXZnKSkpICsKICBnZW9tX3BvaW50KCkgKwogIGxhYnMoeCA9ICJBdmVyYWdlIHN0YW5kYXJkaXplZCBjb25zdW1wdGlvbiIsIHkgPSBOVUxMKQpgYGA=