Instructions
Consider the file A0001.mat from the PhysioNet Challenge https://physionetchallenges.github.io/2020/. Using R:
- Plot the histogram of all the 12 ECG leads with the respective density curve.
- Using the second lead inverted, apply the expectation-maximization algorithm with 2 and 3 latent classes.
- Plot the densities for each of the latent classes.
- Which point in the ECG belongs to each latent class? Plot the ECG in which each point has a color corresponding to the class to which it belongs.
- Use the difference between the averages of class distribution as the convergence criterion.
- Generate 1000 data points at random according to a single distribution fitted to the original data.
- Generate 1000 data points at random according to a mixture of distributions fitted using one of the previous EM computed in line b.
Submitted code file should include comments to improve readability.
Setup
First, load the libraries:
Import data using readMat from R.matlab package. The data comes in a List container, so we drop it to work directly with the matrix:
# Import the data ----
data <- readMat("A0001.mat")
data <- data[[1]] # get rid of List
data <- t(data) # transpose the matrix, so each column represents one lead.
data <- as.data.frame(data)
colnames(data) <- c("I", "II", "III", "aVR", "aVL", "aVF", "V1", "V2", "V3", "V4", "V5", "V6")
Histograms
- Plot the histogram of all the 12 ECG leads with the respective density curve.

Expectation-Maximization
- Using the second lead inverted, apply the expectation-maximization algorithm with 2 and 3 latent classes.
- Plot the densities for each of the latent classes.
# expectation maximization function ----
expmax <- function(data, k, eps = 1e-5, crisp = FALSE, plot = TRUE) {
set.seed(2020)
sample_mean <- sample(data, k)
sample_sd <- rep(sd(data) / k, k)
max_iterations <- 10000
for (i in seq_len(max_iterations)) {
# expectation ----
dens <- sapply(seq_len(k), function(x) {
dnorm(data, mean = sample_mean[x], sd = sample_sd[x])
})
rel <- t(apply(dens, 1, function(x) {
x / sum(x)
}))
# maximization ----
attrib <- t(apply(rel, 1, function(x) {
replace(rep(0, k), which.max(x)[1], 1)
}))
if (crisp) {
# crisp ----
sample_mean.new <- sapply(seq_len(k), function(x) {
mean(data[attrib[, x] == 1])
})
sample_sd.new <- sapply(seq_len(k), function(x) {
sd(data[attrib[, x] == 1])
})
} else {
# soft ----
sample_mean.new <- sapply(seq_len(k), function(x) {
sum(data * rel[, x]) / sum(rel[, x])
})
sample_sd.new <- sapply(seq_len(k), function(x) {
sqrt(sum(rel[, x] * (data - sample_mean[x])^2) / sum(rel[, x]))
})
}
dif <- sum(abs(sample_mean.new - sample_mean))
if (dif < eps) {
message("Finished in ", i, " iterations.")
if (plot) {
gen_data_dens <- list()
max_y <- -Inf
for (i in seq_len(k)) {
set.seed(2020)
gen_data_dens[[i]] <- density(rnorm(100000, mean = sample_mean[i], sd = sample_sd[i]))
max <- max(gen_data_dens[[i]]$y)
if (max > max_y) {
max_y <- max
}
}
hist(data,
main = paste("Expectation-Maximization with", k, "classes"),
freq = FALSE, xlab = "Value", ylim = c(0, max_y)
)
for (i in seq_len(k)) {
lines(gen_data_dens[[i]], lwd = 1, col = i + 1)
}
}
return(list(mean = sample_mean, sd = sample_sd, attrib = attrib, rel = rel, dens = dens))
}
sample_mean <- sample_mean.new
sample_sd <- sample_sd.new
}
stop("Algorithm did not converge.")
}
# data ----
lead <- data[[2]] * -1
# using 2 classes ----
result1 <- expmax(lead, 2, plot = TRUE)
Finished in 56 iterations.

Finished in 71 iterations.

Sections by class
- Which point in the ECG belongs to each latent class? Plot the ECG in which each point has a color corresponding to the class to which it belongs.
- Use the difference between the averages of class distribution as the convergence criterion.
splits <- sort(result1$mean)
split1 <- mean(splits)
class1 <- ifelse(result1$attrib[, 1] == 1, -lead, NA)
class2 <- ifelse(result1$attrib[, 2] == 1, -lead, NA)
{
plot(class1,
type = "l", col = 2, main = "Sections by class - 2", ylab = "value",
ylim = c(-720, 720)
)
lines(class2, col = 3)
legend("topright",
legend = c("1", "2"),
lty = 1, col = 2:3,
title = "Classes"
)
}

splits <- sort(result2$mean)
split1 <- mean(splits[1:2])
split2 <- mean(splits[2:3])
class1 <- ifelse(result2$attrib[, 1] == 1, -lead, NA)
class2 <- ifelse(result2$attrib[, 2] == 1, -lead, NA)
class3 <- ifelse(result2$attrib[, 3] == 1, -lead, NA)
{
plot(class1,
type = "l", col = 2, main = "Sections by class - 3", ylab = "value",
ylim = c(-720, 720)
)
lines(class2, col = 3)
lines(class3, col = 4)
legend("topright",
legend = c("1", "2", "3"),
lty = 1, col = 2:4,
title = "Classes"
)
}

Generating data
- Generate 1000 data points at random according to a single distribution fitted to the original data.
- Generate 1000 data points at random according to a mixture of distributions fitted using one of the previous EM computed in line b.
n <- 1000
classes <- apply(result1$attrib, 1, which.max)
class_prop <- prop.table(table(classes))
set.seed(1000)
sample_classes <- sample(seq_along(result1$mean), size = n, replace = T, prob = class_prop)
sample_prop <- prop.table(table(sample_classes))
mixture1_gen <- round(unlist(sapply(seq_along(result1$mean), function(x) {
rnorm(n * sample_prop[x], mean = result1$mean[x], sd = result1$sd[x])
})), 0)
classes <- apply(result2$attrib, 1, which.max)
class_prop <- prop.table(table(classes))
set.seed(1000)
sample_classes <- sample(seq_along(result2$mean), size = n, replace = T, prob = class_prop)
sample_prop <- prop.table(table(sample_classes))
mixture2_gen <- round(unlist(sapply(seq_along(result2$mean), function(x) {
rnorm(n * sample_prop[x], mean = result2$mean[x], sd = result2$sd[x])
})), 0)
original_gen <- rnorm(n, mean = mean(lead), sd = sd(lead))
oldpar <- par(mfrow = c(2, 2))
breaks <- 10
{
hist(lead,
breaks = breaks, main = "Original dataset", freq = FALSE,
xlim = c(-800, 800), xlab = "value"
)
hist(original_gen,
breaks = breaks, main = "Generated from original", freq = FALSE,
xlim = c(-800, 800), xlab = "value"
)
hist(mixture1_gen,
breaks = breaks, main = "Generated from 2 classes", freq = FALSE,
xlim = c(-800, 800), xlab = "value"
)
hist(mixture2_gen,
breaks = breaks, main = "Generated from 3 classes", freq = FALSE,
xlim = c(-800, 800), xlab = "value"
)
}
par(oldpar)

LS0tCnRpdGxlOiAiSEVBRFMgLSBISURBIC0gQ29tcFN0YXQ6IEFzc2lnbm1lbnQgMiIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIHRoZW1lOiB1bml0ZWQKICAgIHRvYzogeWVzCmF1dGhvcjogRnJhbmNpc2NvIEJpc2Nob2ZmCi0tLQoKIyMgSW5zdHJ1Y3Rpb25zCgpDb25zaWRlciB0aGUgZmlsZSBBMDAwMS5tYXQgZnJvbSB0aGUgUGh5c2lvTmV0IENoYWxsZW5nZSBodHRwczovL3BoeXNpb25ldGNoYWxsZW5nZXMuZ2l0aHViLmlvLzIwMjAvLiBVc2luZyBSOgoKYSkgUGxvdCB0aGUgaGlzdG9ncmFtIG9mIGFsbCB0aGUgMTIgRUNHIGxlYWRzIHdpdGggdGhlIHJlc3BlY3RpdmUgZGVuc2l0eSBjdXJ2ZS4KYikgVXNpbmcgdGhlIHNlY29uZCBsZWFkIGludmVydGVkLCBhcHBseSB0aGUgZXhwZWN0YXRpb24tbWF4aW1pemF0aW9uIGFsZ29yaXRobSB3aXRoIDIgYW5kIDMgbGF0ZW50IGNsYXNzZXMuCmMpIFBsb3QgdGhlIGRlbnNpdGllcyBmb3IgZWFjaCBvZiB0aGUgbGF0ZW50IGNsYXNzZXMuCmQpIFdoaWNoIHBvaW50IGluIHRoZSBFQ0cgYmVsb25ncyB0byBlYWNoIGxhdGVudCBjbGFzcz8gUGxvdCB0aGUgRUNHIGluIHdoaWNoIGVhY2ggcG9pbnQgaGFzIGEgY29sb3IgY29ycmVzcG9uZGluZyB0byB0aGUgY2xhc3MgdG8gd2hpY2ggaXQgYmVsb25ncy4KZSkgVXNlIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGF2ZXJhZ2VzIG9mIGNsYXNzIGRpc3RyaWJ1dGlvbiBhcyB0aGUgY29udmVyZ2VuY2UgY3JpdGVyaW9uLgpmKSBHZW5lcmF0ZSAxMDAwIGRhdGEgcG9pbnRzIGF0IHJhbmRvbSBhY2NvcmRpbmcgdG8gYSBzaW5nbGUgZGlzdHJpYnV0aW9uIGZpdHRlZCB0byB0aGUgb3JpZ2luYWwgZGF0YS4KZykgR2VuZXJhdGUgMTAwMCBkYXRhIHBvaW50cyBhdCByYW5kb20gYWNjb3JkaW5nIHRvIGEgbWl4dHVyZSBvZiBkaXN0cmlidXRpb25zIGZpdHRlZCB1c2luZyBvbmUgb2YgdGhlIHByZXZpb3VzIEVNIGNvbXB1dGVkIGluIGxpbmUgYi4gCgpTdWJtaXR0ZWQgY29kZSBmaWxlIHNob3VsZCBpbmNsdWRlIGNvbW1lbnRzIHRvIGltcHJvdmUgcmVhZGFiaWxpdHkuCgojIyBTZXR1cAoKRmlyc3QsIGxvYWQgdGhlIGxpYnJhcmllczoKCmBgYHtyIHNldHVwLCBtZXNzYWdlID0gRkFMU0V9CmxpYnJhcnkoUi5tYXRsYWIpCmBgYAoKSW1wb3J0IGRhdGEgdXNpbmcgYHJlYWRNYXRgIGZyb20gYFIubWF0bGFiYCBwYWNrYWdlLiBUaGUgZGF0YSBjb21lcyBpbiBhIGBMaXN0YCBjb250YWluZXIsIHNvIHdlIGRyb3AgaXQgdG8gd29yayBkaXJlY3RseSB3aXRoIHRoZSBgbWF0cml4YDoKCmBgYHtyIGltcG9ydH0KIyBJbXBvcnQgdGhlIGRhdGEgLS0tLQpkYXRhIDwtIHJlYWRNYXQoIkEwMDAxLm1hdCIpCmRhdGEgPC0gZGF0YVtbMV1dICMgZ2V0IHJpZCBvZiBMaXN0CmRhdGEgPC0gdChkYXRhKSAjIHRyYW5zcG9zZSB0aGUgbWF0cml4LCBzbyBlYWNoIGNvbHVtbiByZXByZXNlbnRzIG9uZSBsZWFkLgpkYXRhIDwtIGFzLmRhdGEuZnJhbWUoZGF0YSkKY29sbmFtZXMoZGF0YSkgPC0gYygiSSIsICJJSSIsICJJSUkiLCAiYVZSIiwgImFWTCIsICJhVkYiLCAiVjEiLCAiVjIiLCAiVjMiLCAiVjQiLCAiVjUiLCAiVjYiKQpgYGAKCiMjIEhpc3RvZ3JhbXMKCmEpIFBsb3QgdGhlIGhpc3RvZ3JhbSBvZiBhbGwgdGhlIDEyIEVDRyBsZWFkcyB3aXRoIHRoZSByZXNwZWN0aXZlIGRlbnNpdHkgY3VydmUuCgpgYGB7ciBoaXN0b2dyYW1zLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0Kb2xkcGFyIDwtIHBhcihtZnJvdyA9IGMoMywgNCkpCgpmb3IgKGkgaW4gMToxMikgewogIGhpc3QoZGF0YVtbaV1dLCBtYWluID0gbmFtZXMoZGF0YVtpXSksIGZyZXEgPSBGQUxTRSwgeGxhYiA9ICJWYWx1ZSIsIHlsaW0gPSBjKDAsIDAuMDExKSkKICBsaW5lcyhkZW5zaXR5KGRhdGFbW2ldXSksIGx3ZCA9IDEsIGNvbCA9IDQpCn0KCnBhcihvbGRwYXIpCmBgYAoKIyMgRXhwZWN0YXRpb24tTWF4aW1pemF0aW9uCgpiKSBVc2luZyB0aGUgc2Vjb25kIGxlYWQgaW52ZXJ0ZWQsIGFwcGx5IHRoZSBleHBlY3RhdGlvbi1tYXhpbWl6YXRpb24gYWxnb3JpdGhtIHdpdGggMiBhbmQgMyBsYXRlbnQgY2xhc3Nlcy4KYykgUGxvdCB0aGUgZGVuc2l0aWVzIGZvciBlYWNoIG9mIHRoZSBsYXRlbnQgY2xhc3Nlcy4KCmBgYHtyIGVtLCBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD0xMH0KIyBleHBlY3RhdGlvbiBtYXhpbWl6YXRpb24gZnVuY3Rpb24gLS0tLQpleHBtYXggPC0gZnVuY3Rpb24oZGF0YSwgaywgZXBzID0gMWUtNSwgY3Jpc3AgPSBGQUxTRSwgcGxvdCA9IFRSVUUpIHsKICBzZXQuc2VlZCgyMDIwKQogIHNhbXBsZV9tZWFuIDwtIHNhbXBsZShkYXRhLCBrKQogIHNhbXBsZV9zZCA8LSByZXAoc2QoZGF0YSkgLyBrLCBrKQogIG1heF9pdGVyYXRpb25zIDwtIDEwMDAwCgogIGZvciAoaSBpbiBzZXFfbGVuKG1heF9pdGVyYXRpb25zKSkgewogICAgIyBleHBlY3RhdGlvbiAtLS0tCiAgICBkZW5zIDwtIHNhcHBseShzZXFfbGVuKGspLCBmdW5jdGlvbih4KSB7CiAgICAgIGRub3JtKGRhdGEsIG1lYW4gPSBzYW1wbGVfbWVhblt4XSwgc2QgPSBzYW1wbGVfc2RbeF0pCiAgICB9KQoKICAgIHJlbCA8LSB0KGFwcGx5KGRlbnMsIDEsIGZ1bmN0aW9uKHgpIHsKICAgICAgeCAvIHN1bSh4KQogICAgfSkpCgogICAgIyBtYXhpbWl6YXRpb24gLS0tLQogICAgYXR0cmliIDwtIHQoYXBwbHkocmVsLCAxLCBmdW5jdGlvbih4KSB7CiAgICAgIHJlcGxhY2UocmVwKDAsIGspLCB3aGljaC5tYXgoeClbMV0sIDEpCiAgICB9KSkKCiAgICBpZiAoY3Jpc3ApIHsKICAgICAgIyBjcmlzcCAtLS0tCiAgICAgIHNhbXBsZV9tZWFuLm5ldyA8LSBzYXBwbHkoc2VxX2xlbihrKSwgZnVuY3Rpb24oeCkgewogICAgICAgIG1lYW4oZGF0YVthdHRyaWJbLCB4XSA9PSAxXSkKICAgICAgfSkKCiAgICAgIHNhbXBsZV9zZC5uZXcgPC0gc2FwcGx5KHNlcV9sZW4oayksIGZ1bmN0aW9uKHgpIHsKICAgICAgICBzZChkYXRhW2F0dHJpYlssIHhdID09IDFdKQogICAgICB9KQogICAgfSBlbHNlIHsKICAgICAgIyBzb2Z0IC0tLS0KICAgICAgc2FtcGxlX21lYW4ubmV3IDwtIHNhcHBseShzZXFfbGVuKGspLCBmdW5jdGlvbih4KSB7CiAgICAgICAgc3VtKGRhdGEgKiByZWxbLCB4XSkgLyBzdW0ocmVsWywgeF0pCiAgICAgIH0pCgogICAgICBzYW1wbGVfc2QubmV3IDwtIHNhcHBseShzZXFfbGVuKGspLCBmdW5jdGlvbih4KSB7CiAgICAgICAgc3FydChzdW0ocmVsWywgeF0gKiAoZGF0YSAtIHNhbXBsZV9tZWFuW3hdKV4yKSAvIHN1bShyZWxbLCB4XSkpCiAgICAgIH0pCiAgICB9CgogICAgZGlmIDwtIHN1bShhYnMoc2FtcGxlX21lYW4ubmV3IC0gc2FtcGxlX21lYW4pKQoKICAgIGlmIChkaWYgPCBlcHMpIHsKICAgICAgbWVzc2FnZSgiRmluaXNoZWQgaW4gIiwgaSwgIiBpdGVyYXRpb25zLiIpCiAgICAgIGlmIChwbG90KSB7CiAgICAgICAgZ2VuX2RhdGFfZGVucyA8LSBsaXN0KCkKICAgICAgICBtYXhfeSA8LSAtSW5mCiAgICAgICAgZm9yIChpIGluIHNlcV9sZW4oaykpIHsKICAgICAgICAgIHNldC5zZWVkKDIwMjApCiAgICAgICAgICBnZW5fZGF0YV9kZW5zW1tpXV0gPC0gZGVuc2l0eShybm9ybSgxMDAwMDAsIG1lYW4gPSBzYW1wbGVfbWVhbltpXSwgc2QgPSBzYW1wbGVfc2RbaV0pKQogICAgICAgICAgbWF4IDwtIG1heChnZW5fZGF0YV9kZW5zW1tpXV0keSkKICAgICAgICAgIGlmIChtYXggPiBtYXhfeSkgewogICAgICAgICAgICBtYXhfeSA8LSBtYXgKICAgICAgICAgIH0KICAgICAgICB9CgogICAgICAgIGhpc3QoZGF0YSwKICAgICAgICAgIG1haW4gPSBwYXN0ZSgiRXhwZWN0YXRpb24tTWF4aW1pemF0aW9uIHdpdGgiLCBrLCAiY2xhc3NlcyIpLAogICAgICAgICAgZnJlcSA9IEZBTFNFLCB4bGFiID0gIlZhbHVlIiwgeWxpbSA9IGMoMCwgbWF4X3kpCiAgICAgICAgKQoKICAgICAgICBmb3IgKGkgaW4gc2VxX2xlbihrKSkgewogICAgICAgICAgbGluZXMoZ2VuX2RhdGFfZGVuc1tbaV1dLCBsd2QgPSAxLCBjb2wgPSBpICsgMSkKICAgICAgICB9CiAgICAgIH0KCiAgICAgIHJldHVybihsaXN0KG1lYW4gPSBzYW1wbGVfbWVhbiwgc2QgPSBzYW1wbGVfc2QsIGF0dHJpYiA9IGF0dHJpYiwgcmVsID0gcmVsLCBkZW5zID0gZGVucykpCiAgICB9CgogICAgc2FtcGxlX21lYW4gPC0gc2FtcGxlX21lYW4ubmV3CiAgICBzYW1wbGVfc2QgPC0gc2FtcGxlX3NkLm5ldwogIH0KICBzdG9wKCJBbGdvcml0aG0gZGlkIG5vdCBjb252ZXJnZS4iKQp9CgojIGRhdGEgLS0tLQpsZWFkIDwtIGRhdGFbWzJdXSAqIC0xCgojIHVzaW5nIDIgY2xhc3NlcyAtLS0tCnJlc3VsdDEgPC0gZXhwbWF4KGxlYWQsIDIsIHBsb3QgPSBUUlVFKQoKIyB1c2luZyAzIGNsYXNzZXMgLS0tLQpyZXN1bHQyIDwtIGV4cG1heChsZWFkLCAzLCBwbG90ID0gVFJVRSkKYGBgCgojIyBTZWN0aW9ucyBieSBjbGFzcwoKZCkgV2hpY2ggcG9pbnQgaW4gdGhlIEVDRyBiZWxvbmdzIHRvIGVhY2ggbGF0ZW50IGNsYXNzPyBQbG90IHRoZSBFQ0cgaW4gd2hpY2ggZWFjaCBwb2ludCBoYXMgYSBjb2xvciBjb3JyZXNwb25kaW5nIHRvIHRoZSBjbGFzcyB0byB3aGljaCBpdCBiZWxvbmdzLgplKSBVc2UgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiB0aGUgYXZlcmFnZXMgb2YgY2xhc3MgZGlzdHJpYnV0aW9uIGFzIHRoZSBjb252ZXJnZW5jZSBjcml0ZXJpb24uCgpgYGB7ciBjbGFzc2VzMiwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9MTAsIGNvbGxhcHNlPVRSVUV9CgpzcGxpdHMgPC0gc29ydChyZXN1bHQxJG1lYW4pCnNwbGl0MSA8LSBtZWFuKHNwbGl0cykKCmNsYXNzMSA8LSBpZmVsc2UocmVzdWx0MSRhdHRyaWJbLCAxXSA9PSAxLCAtbGVhZCwgTkEpCmNsYXNzMiA8LSBpZmVsc2UocmVzdWx0MSRhdHRyaWJbLCAyXSA9PSAxLCAtbGVhZCwgTkEpCgp7CiAgcGxvdChjbGFzczEsCiAgICB0eXBlID0gImwiLCBjb2wgPSAyLCBtYWluID0gIlNlY3Rpb25zIGJ5IGNsYXNzIC0gMiIsIHlsYWIgPSAidmFsdWUiLAogICAgeWxpbSA9IGMoLTcyMCwgNzIwKQogICkKICBsaW5lcyhjbGFzczIsIGNvbCA9IDMpCiAgbGVnZW5kKCJ0b3ByaWdodCIsCiAgICBsZWdlbmQgPSBjKCIxIiwgIjIiKSwKICAgIGx0eSA9IDEsIGNvbCA9IDI6MywKICAgIHRpdGxlID0gIkNsYXNzZXMiCiAgKQp9CmBgYAoKYGBge3IgY2xhc3NlczMsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTEwLCBjb2xsYXBzZT1UUlVFfQoKc3BsaXRzIDwtIHNvcnQocmVzdWx0MiRtZWFuKQpzcGxpdDEgPC0gbWVhbihzcGxpdHNbMToyXSkKc3BsaXQyIDwtIG1lYW4oc3BsaXRzWzI6M10pCgpjbGFzczEgPC0gaWZlbHNlKHJlc3VsdDIkYXR0cmliWywgMV0gPT0gMSwgLWxlYWQsIE5BKQpjbGFzczIgPC0gaWZlbHNlKHJlc3VsdDIkYXR0cmliWywgMl0gPT0gMSwgLWxlYWQsIE5BKQpjbGFzczMgPC0gaWZlbHNlKHJlc3VsdDIkYXR0cmliWywgM10gPT0gMSwgLWxlYWQsIE5BKQoKCnsKICBwbG90KGNsYXNzMSwKICAgIHR5cGUgPSAibCIsIGNvbCA9IDIsIG1haW4gPSAiU2VjdGlvbnMgYnkgY2xhc3MgLSAzIiwgeWxhYiA9ICJ2YWx1ZSIsCiAgICB5bGltID0gYygtNzIwLCA3MjApCiAgKQogIGxpbmVzKGNsYXNzMiwgY29sID0gMykKICBsaW5lcyhjbGFzczMsIGNvbCA9IDQpCiAgbGVnZW5kKCJ0b3ByaWdodCIsCiAgICBsZWdlbmQgPSBjKCIxIiwgIjIiLCAiMyIpLAogICAgbHR5ID0gMSwgY29sID0gMjo0LAogICAgdGl0bGUgPSAiQ2xhc3NlcyIKICApCn0KYGBgCgojIyBHZW5lcmF0aW5nIGRhdGEKCmYpIEdlbmVyYXRlIDEwMDAgZGF0YSBwb2ludHMgYXQgcmFuZG9tIGFjY29yZGluZyB0byBhIHNpbmdsZSBkaXN0cmlidXRpb24gZml0dGVkIHRvIHRoZSBvcmlnaW5hbCBkYXRhLgpnKSBHZW5lcmF0ZSAxMDAwIGRhdGEgcG9pbnRzIGF0IHJhbmRvbSBhY2NvcmRpbmcgdG8gYSBtaXh0dXJlIG9mIGRpc3RyaWJ1dGlvbnMgZml0dGVkIHVzaW5nIG9uZSBvZiB0aGUgcHJldmlvdXMgRU0gY29tcHV0ZWQgaW4gbGluZSBiLiAKCmBgYHtyIGdlbmVyYXRlZCwgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9MTB9CgpuIDwtIDEwMDAKY2xhc3NlcyA8LSBhcHBseShyZXN1bHQxJGF0dHJpYiwgMSwgd2hpY2gubWF4KQpjbGFzc19wcm9wIDwtIHByb3AudGFibGUodGFibGUoY2xhc3NlcykpCnNldC5zZWVkKDEwMDApCnNhbXBsZV9jbGFzc2VzIDwtIHNhbXBsZShzZXFfYWxvbmcocmVzdWx0MSRtZWFuKSwgc2l6ZSA9IG4sIHJlcGxhY2UgPSBULCBwcm9iID0gY2xhc3NfcHJvcCkKc2FtcGxlX3Byb3AgPC0gcHJvcC50YWJsZSh0YWJsZShzYW1wbGVfY2xhc3NlcykpCm1peHR1cmUxX2dlbiA8LSByb3VuZCh1bmxpc3Qoc2FwcGx5KHNlcV9hbG9uZyhyZXN1bHQxJG1lYW4pLCBmdW5jdGlvbih4KSB7CiAgcm5vcm0obiAqIHNhbXBsZV9wcm9wW3hdLCBtZWFuID0gcmVzdWx0MSRtZWFuW3hdLCBzZCA9IHJlc3VsdDEkc2RbeF0pCn0pKSwgMCkKCmNsYXNzZXMgPC0gYXBwbHkocmVzdWx0MiRhdHRyaWIsIDEsIHdoaWNoLm1heCkKY2xhc3NfcHJvcCA8LSBwcm9wLnRhYmxlKHRhYmxlKGNsYXNzZXMpKQpzZXQuc2VlZCgxMDAwKQpzYW1wbGVfY2xhc3NlcyA8LSBzYW1wbGUoc2VxX2Fsb25nKHJlc3VsdDIkbWVhbiksIHNpemUgPSBuLCByZXBsYWNlID0gVCwgcHJvYiA9IGNsYXNzX3Byb3ApCnNhbXBsZV9wcm9wIDwtIHByb3AudGFibGUodGFibGUoc2FtcGxlX2NsYXNzZXMpKQptaXh0dXJlMl9nZW4gPC0gcm91bmQodW5saXN0KHNhcHBseShzZXFfYWxvbmcocmVzdWx0MiRtZWFuKSwgZnVuY3Rpb24oeCkgewogIHJub3JtKG4gKiBzYW1wbGVfcHJvcFt4XSwgbWVhbiA9IHJlc3VsdDIkbWVhblt4XSwgc2QgPSByZXN1bHQyJHNkW3hdKQp9KSksIDApCgpvcmlnaW5hbF9nZW4gPC0gcm5vcm0obiwgbWVhbiA9IG1lYW4obGVhZCksIHNkID0gc2QobGVhZCkpCgpvbGRwYXIgPC0gcGFyKG1mcm93ID0gYygyLCAyKSkKYnJlYWtzIDwtIDEwCnsKICBoaXN0KGxlYWQsCiAgICBicmVha3MgPSBicmVha3MsIG1haW4gPSAiT3JpZ2luYWwgZGF0YXNldCIsIGZyZXEgPSBGQUxTRSwKICAgIHhsaW0gPSBjKC04MDAsIDgwMCksIHhsYWIgPSAidmFsdWUiCiAgKQogIGhpc3Qob3JpZ2luYWxfZ2VuLAogICAgYnJlYWtzID0gYnJlYWtzLCBtYWluID0gIkdlbmVyYXRlZCBmcm9tIG9yaWdpbmFsIiwgZnJlcSA9IEZBTFNFLAogICAgeGxpbSA9IGMoLTgwMCwgODAwKSwgeGxhYiA9ICJ2YWx1ZSIKICApCiAgaGlzdChtaXh0dXJlMV9nZW4sCiAgICBicmVha3MgPSBicmVha3MsIG1haW4gPSAiR2VuZXJhdGVkIGZyb20gMiBjbGFzc2VzIiwgZnJlcSA9IEZBTFNFLAogICAgeGxpbSA9IGMoLTgwMCwgODAwKSwgeGxhYiA9ICJ2YWx1ZSIKICApCiAgaGlzdChtaXh0dXJlMl9nZW4sCiAgICBicmVha3MgPSBicmVha3MsIG1haW4gPSAiR2VuZXJhdGVkIGZyb20gMyBjbGFzc2VzIiwgZnJlcSA9IEZBTFNFLAogICAgeGxpbSA9IGMoLTgwMCwgODAwKSwgeGxhYiA9ICJ2YWx1ZSIKICApCn0KcGFyKG9sZHBhcikKYGBgCg==