Setup
suppressPackageStartupMessages(library(tidyverse))
suppressPackageStartupMessages(library(MASS))
future::plan(future::multisession, workers = 8)
Specify parameters
#set.seed(42)
d <- 100 # dimensions
n_null_reps <- 10 # number of times to repeat shuffling to create the null distribution
n_means <- 20 # number of centers (components)
n_repeats <- 9 # number of times to repeat the sampling
n_samples_per_mean_l <- seq(3, 20) # number of samples per component (iterate over each)
null_threshold_pc <- 0.95 # threshold above which a component is considered to be replicating
sds <- seq(2, 4, length.out = n_means) ** 3 # s.d. of each component
#sds <- 1 + abs(rnorm(n_means)) / 10
means <- matrix(rnorm(d * n_means), n_means, d) # generate centers
Functions
Elements of upper triangular matrix
corvec <- function(m) {cm <- cor(m); cm[upper.tri(cm)]}
Compute median replicate correlation (mrc)
f_mrc <- function(data_x, n_means, n_samples_per_mean) {
sapply(1:n_means,
function(i) {
start <- (i - 1) * n_samples_per_mean + 1
end <- i * n_samples_per_mean
data_i <- data_x[start:end,]
mean(corvec(t(data_i)))
})
}
Sample the data and compute signal and null distributions of mrc
f_signal_null <-
function(n_means,
n_samples_per_mean,
d,
n_null_reps) {
data <- lapply(1:n_means,
function(i)
mvrnorm(n = n_samples_per_mean,
means[i, ],
sds[i] * diag(rep(1, d))))
data <- do.call(rbind, data)
signal <- f_mrc(data,
n_means,
n_samples_per_mean)
null <-
as.vector(sapply(1:n_null_reps,
function(i)
f_mrc(data[sample(n_samples_per_mean * n_means),],
n_means,
n_samples_per_mean)))
df <- tibble(
label =
c(rep("signal", length(signal)),
rep("null", length(null))),
class =
c(sds,
rep(NA, length(null))),
sim = c(signal, null)
)
df
}
Generate data
Generate mrc of signal and null, n_repeats times.
df <- furrr::future_map_dfr(1:n_repeats,
function(repeat_id)
map_dfr(n_samples_per_mean_l,
function(n_samples_per_mean_i)
f_signal_null(
n_means = n_means,
n_samples_per_mean = n_samples_per_mean_i,
d = d,
n_null_reps = n_null_reps
) %>%
mutate(n_samples_per_mean =
n_samples_per_mean_i,
repeat_id = repeat_id)),
.options = furrr::furrr_options(seed = TRUE))
Compute percent replicating, for each n_samples_per_mean (i.e. replicate cardinality) and each repeat_id (i.e. each shuffling)
dfn <- df %>%
group_by(n_samples_per_mean, repeat_id) %>%
nest() %>%
mutate(percent_rep = map_dbl(data, function(signal_null_df) {
null_threshold <-
signal_null_df %>%
filter(label == "null") %>%
pull("sim") %>%
quantile(null_threshold_pc, names = FALSE)
replicating <-
signal_null_df %>%
filter(label == "signal") %>%
mutate(is_replicating = sim > null_threshold)
percent_rep_i <-
(replicating %>% filter(is_replicating) %>% nrow()) /
(replicating %>% nrow())
percent_rep_i
})) %>%
dplyr::select(-data) %>%
ungroup() %>%
group_by(n_samples_per_mean)
Plot
Dependence of MRC on replicate cardinality
Combine data across repeats
df %>%
ggplot(aes(sim, fill = label)) +
stat_density(position = "identity", alpha = 0.6) +
facet_wrap(~ n_samples_per_mean)

dfn %>%
ggplot(aes(n_samples_per_mean, percent_rep, group = n_samples_per_mean)) +
geom_boxplot(notch=FALSE, outlier.shape=NA, fill="red", alpha=0.2) +
stat_summary(fun=median, geom="line", aes(group=1)) +
ylab("Percent replicating") +
xlab("Number of replicates") +
cowplot::theme_cowplot()

#df %>% count(n_samples_per_mean, repeat_id, label) %>% count(n_samples_per_mean, label, n)
Visualize data using PCA
n_samples_per_mean <- n_samples_per_mean_l[length(n_samples_per_mean_l)]
n_samples_per_mean <- n_samples_per_mean * 10
data <- lapply(1:n_means,
function(i)
mvrnorm(n = n_samples_per_mean,
means[i,],
sds[i] * diag(rep(1, d))))
data <- do.call(rbind, data)
m <- prcomp(data)
data_pc <- as_tibble(m$x[,1:2])
data_pc$sdv <- rep(sds, each = n_samples_per_mean)
round_it <- function(x) round(as.numeric(x), 1)
data_pc %>%
ggplot(aes(PC1, PC2)) +
geom_hex() +
theme_void() +
theme(legend.position = "none") +
facet_wrap(~sdv, labeller = labeller(sdv = round_it)) +
coord_equal()

Plot spread in PC1 wrt s.d.
data_pc %>%
group_by(sdv) %>%
summarise(across(everything(), list(sd = sd))) %>%
ggplot(aes(sdv, PC1_sd)) +
geom_point() +
geom_smooth(method = lm, formula = y ~ x)

MRC vs cardinality across different dispersion levels
df %>%
filter(label == "signal") %>%
group_by(n_samples_per_mean, class) %>%
summarize(across(c("sim"), list(mean = mean, sd = sd)), .groups = "keep") %>%
ggplot(aes(n_samples_per_mean, sim_mean, color = class, group = class)) +
geom_line(alpha = 0.5) #+

df %>%
filter(label == "signal") %>%
group_by(n_samples_per_mean, class) %>%
summarize(across(c("sim"), list(mean = mean, sd = sd)), .groups = "keep") %>%
ggplot(aes(n_samples_per_mean, sim_sd, color = class, group = class)) +
geom_line(alpha = 0.5)

custom.config = umap::umap.defaults
custom.config$random_state = 123
umap_embedding <-
umap::umap(data,
config = custom.config,
n_neighbors = 20)
umap_embedding <-
as.data.frame(umap_embedding$layout)
umap_embedding$sdv <- rep(sds, each = n_samples_per_mean)
round_it <- function(x) round(as.numeric(x), 1)
umap_embedding %>%
ggplot(aes(V1,V2)) +
geom_hex() +
theme_void() +
theme(legend.position = "none") +
facet_wrap(~sdv, labeller = labeller(sdv = round_it)) +
coord_equal()
umap_embedding %>%
ggplot(aes(V1, V2, color = as.factor(sdv))) +
geom_point() +
theme_void() +
theme(legend.position = "none") +
coord_equal()
LS0tCnRpdGxlOiAiUGVyY2VudCByZXBsaWNhdGluZyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBTZXR1cAoKYGBge3J9CnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KHRpZHl2ZXJzZSkpCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KE1BU1MpKQpgYGAKCgpgYGB7cn0KZnV0dXJlOjpwbGFuKGZ1dHVyZTo6bXVsdGlzZXNzaW9uLCB3b3JrZXJzID0gOCkKYGBgCgojIFNwZWNpZnkgcGFyYW1ldGVycwoKYGBge3J9CiNzZXQuc2VlZCg0MikKZCA8LSAxMDAgIyBkaW1lbnNpb25zCm5fbnVsbF9yZXBzIDwtIDEwICMgbnVtYmVyIG9mIHRpbWVzIHRvIHJlcGVhdCBzaHVmZmxpbmcgdG8gY3JlYXRlIHRoZSBudWxsIGRpc3RyaWJ1dGlvbgpuX21lYW5zIDwtIDIwICMgbnVtYmVyIG9mIGNlbnRlcnMgKGNvbXBvbmVudHMpCm5fcmVwZWF0cyA8LSA5ICMgbnVtYmVyIG9mIHRpbWVzIHRvIHJlcGVhdCB0aGUgc2FtcGxpbmcKbl9zYW1wbGVzX3Blcl9tZWFuX2wgPC0gc2VxKDMsIDIwKSAjIG51bWJlciBvZiBzYW1wbGVzIHBlciBjb21wb25lbnQgKGl0ZXJhdGUgb3ZlciBlYWNoKQpudWxsX3RocmVzaG9sZF9wYyA8LSAwLjk1ICMgdGhyZXNob2xkIGFib3ZlIHdoaWNoIGEgY29tcG9uZW50IGlzIGNvbnNpZGVyZWQgdG8gYmUgcmVwbGljYXRpbmcKc2RzIDwtIHNlcSgyLCA0LCBsZW5ndGgub3V0ID0gbl9tZWFucykgKiogMyAgIyBzLmQuIG9mIGVhY2ggY29tcG9uZW50CiNzZHMgPC0gMSArIGFicyhybm9ybShuX21lYW5zKSkgLyAxMApgYGAKCgpgYGB7cn0KbWVhbnMgPC0gbWF0cml4KHJub3JtKGQgKiBuX21lYW5zKSwgbl9tZWFucywgZCkgIyBnZW5lcmF0ZSBjZW50ZXJzCmBgYAoKIyBGdW5jdGlvbnMKCkVsZW1lbnRzIG9mIHVwcGVyIHRyaWFuZ3VsYXIgbWF0cml4CgpgYGB7cn0KY29ydmVjIDwtIGZ1bmN0aW9uKG0pIHtjbSA8LSBjb3IobSk7IGNtW3VwcGVyLnRyaShjbSldfQpgYGAKCkNvbXB1dGUgbWVkaWFuIHJlcGxpY2F0ZSBjb3JyZWxhdGlvbiAobXJjKQoKYGBge3J9CmZfbXJjIDwtIGZ1bmN0aW9uKGRhdGFfeCwgbl9tZWFucywgbl9zYW1wbGVzX3Blcl9tZWFuKSB7CiAgc2FwcGx5KDE6bl9tZWFucywKICAgICAgICAgZnVuY3Rpb24oaSkgewogICAgICAgICAgIHN0YXJ0IDwtIChpIC0gMSkgKiBuX3NhbXBsZXNfcGVyX21lYW4gKyAxCiAgICAgICAgICAgZW5kIDwtIGkgKiBuX3NhbXBsZXNfcGVyX21lYW4KICAgICAgICAgICBkYXRhX2kgPC0gZGF0YV94W3N0YXJ0OmVuZCxdCiAgICAgICAgICAgbWVhbihjb3J2ZWModChkYXRhX2kpKSkKICAgICAgICAgfSkKfQpgYGAKClNhbXBsZSB0aGUgZGF0YSBhbmQgY29tcHV0ZSBzaWduYWwgYW5kIG51bGwgZGlzdHJpYnV0aW9ucyBvZiBtcmMKCmBgYHtyfQpmX3NpZ25hbF9udWxsIDwtCiAgZnVuY3Rpb24obl9tZWFucywKICAgICAgICAgICBuX3NhbXBsZXNfcGVyX21lYW4sCiAgICAgICAgICAgZCwKICAgICAgICAgICBuX251bGxfcmVwcykgewogICAgZGF0YSA8LSBsYXBwbHkoMTpuX21lYW5zLAogICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oaSkKICAgICAgICAgICAgICAgICAgICAgbXZybm9ybShuID0gbl9zYW1wbGVzX3Blcl9tZWFuLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lYW5zW2ksIF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2RzW2ldICogZGlhZyhyZXAoMSwgZCkpKSkKICAgIAogICAgZGF0YSA8LSBkby5jYWxsKHJiaW5kLCBkYXRhKQogICAgCiAgICBzaWduYWwgPC0gZl9tcmMoZGF0YSwKICAgICAgICAgICAgICAgICAgICBuX21lYW5zLAogICAgICAgICAgICAgICAgICAgIG5fc2FtcGxlc19wZXJfbWVhbikKICAgIAogICAgbnVsbCA8LQogICAgICBhcy52ZWN0b3Ioc2FwcGx5KDE6bl9udWxsX3JlcHMsCiAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24oaSkKICAgICAgICAgICAgICAgICAgICAgICAgIGZfbXJjKGRhdGFbc2FtcGxlKG5fc2FtcGxlc19wZXJfbWVhbiAqIG5fbWVhbnMpLF0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuX21lYW5zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9zYW1wbGVzX3Blcl9tZWFuKSkpCiAgICAKICAgIGRmIDwtIHRpYmJsZSgKICAgICAgbGFiZWwgPQogICAgICAgIGMocmVwKCJzaWduYWwiLCBsZW5ndGgoc2lnbmFsKSksCiAgICAgICAgICByZXAoIm51bGwiLCBsZW5ndGgobnVsbCkpKSwKICAgICAgY2xhc3MgPQogICAgICAgIGMoc2RzLAogICAgICAgICAgcmVwKE5BLCBsZW5ndGgobnVsbCkpKSwKICAgICAgc2ltID0gYyhzaWduYWwsIG51bGwpCiAgICApCiAgICAKICAgIGRmCiAgfQpgYGAKCiMgR2VuZXJhdGUgZGF0YQoKR2VuZXJhdGUgbXJjIG9mIHNpZ25hbCBhbmQgbnVsbCwgYG5fcmVwZWF0c2AgdGltZXMuCgpgYGB7cn0KZGYgPC0gZnVycnI6OmZ1dHVyZV9tYXBfZGZyKDE6bl9yZXBlYXRzLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgZnVuY3Rpb24ocmVwZWF0X2lkKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXBfZGZyKG5fc2FtcGxlc19wZXJfbWVhbl9sLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKG5fc2FtcGxlc19wZXJfbWVhbl9pKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZl9zaWduYWxfbnVsbCgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9tZWFucyA9IG5fbWVhbnMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5fc2FtcGxlc19wZXJfbWVhbiA9IG5fc2FtcGxlc19wZXJfbWVhbl9pLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkID0gZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9udWxsX3JlcHMgPSBuX251bGxfcmVwcwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShuX3NhbXBsZXNfcGVyX21lYW4gPQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbl9zYW1wbGVzX3Blcl9tZWFuX2ksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVwZWF0X2lkID0gcmVwZWF0X2lkKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAub3B0aW9ucyA9IGZ1cnJyOjpmdXJycl9vcHRpb25zKHNlZWQgPSBUUlVFKSkKYGBgCgpDb21wdXRlIHBlcmNlbnQgcmVwbGljYXRpbmcsIGZvciBlYWNoIGBuX3NhbXBsZXNfcGVyX21lYW5gIChpLmUuIHJlcGxpY2F0ZSBjYXJkaW5hbGl0eSkgYW5kIGVhY2ggYHJlcGVhdF9pZGAgKGkuZS4gZWFjaCBzaHVmZmxpbmcpCgpgYGB7cn0KZGZuIDwtIGRmICU+JQogIGdyb3VwX2J5KG5fc2FtcGxlc19wZXJfbWVhbiwgcmVwZWF0X2lkKSAlPiUKICBuZXN0KCkgJT4lCiAgbXV0YXRlKHBlcmNlbnRfcmVwID0gbWFwX2RibChkYXRhLCBmdW5jdGlvbihzaWduYWxfbnVsbF9kZikgewogICAgbnVsbF90aHJlc2hvbGQgPC0KICAgICAgc2lnbmFsX251bGxfZGYgJT4lCiAgICAgIGZpbHRlcihsYWJlbCA9PSAibnVsbCIpICU+JQogICAgICBwdWxsKCJzaW0iKSAlPiUKICAgICAgcXVhbnRpbGUobnVsbF90aHJlc2hvbGRfcGMsIG5hbWVzID0gRkFMU0UpCiAgICAKICAgIHJlcGxpY2F0aW5nIDwtCiAgICAgIHNpZ25hbF9udWxsX2RmICU+JQogICAgICBmaWx0ZXIobGFiZWwgPT0gInNpZ25hbCIpICU+JQogICAgICBtdXRhdGUoaXNfcmVwbGljYXRpbmcgPSBzaW0gPiBudWxsX3RocmVzaG9sZCkKICAgIAogICAgcGVyY2VudF9yZXBfaSA8LQogICAgICAocmVwbGljYXRpbmcgJT4lIGZpbHRlcihpc19yZXBsaWNhdGluZykgJT4lIG5yb3coKSkgLwogICAgICAocmVwbGljYXRpbmcgJT4lIG5yb3coKSkKICAgIAogICAgcGVyY2VudF9yZXBfaQogICAgCiAgfSkpICU+JQogIGRwbHlyOjpzZWxlY3QoLWRhdGEpICU+JQogIHVuZ3JvdXAoKSAlPiUKICBncm91cF9ieShuX3NhbXBsZXNfcGVyX21lYW4pCmBgYAoKCiMgUGxvdAoKIyMgRGVwZW5kZW5jZSBvZiBNUkMgb24gcmVwbGljYXRlIGNhcmRpbmFsaXR5IAoKQ29tYmluZSBkYXRhIGFjcm9zcyByZXBlYXRzCgpgYGB7cn0KZGYgJT4lCiBnZ3Bsb3QoYWVzKHNpbSwgZmlsbCA9IGxhYmVsKSkgKyAKICBzdGF0X2RlbnNpdHkocG9zaXRpb24gPSAiaWRlbnRpdHkiLCBhbHBoYSA9IDAuNikgKwogIGZhY2V0X3dyYXAofiBuX3NhbXBsZXNfcGVyX21lYW4pCmBgYAoKIyAKCmBgYHtyfQpkZm4gJT4lCiAgZ2dwbG90KGFlcyhuX3NhbXBsZXNfcGVyX21lYW4sIHBlcmNlbnRfcmVwLCBncm91cCA9IG5fc2FtcGxlc19wZXJfbWVhbikpICsgCiAgZ2VvbV9ib3hwbG90KG5vdGNoPUZBTFNFLCBvdXRsaWVyLnNoYXBlPU5BLCBmaWxsPSJyZWQiLCBhbHBoYT0wLjIpICsgCiAgc3RhdF9zdW1tYXJ5KGZ1bj1tZWRpYW4sIGdlb209ImxpbmUiLCBhZXMoZ3JvdXA9MSkpICsKICB5bGFiKCJQZXJjZW50IHJlcGxpY2F0aW5nIikgKwogIHhsYWIoIk51bWJlciBvZiByZXBsaWNhdGVzIikgKwogIGNvd3Bsb3Q6OnRoZW1lX2Nvd3Bsb3QoKQpgYGAKCgpgYGB7cn0KI2RmICU+JSBjb3VudChuX3NhbXBsZXNfcGVyX21lYW4sIHJlcGVhdF9pZCwgbGFiZWwpICU+JSBjb3VudChuX3NhbXBsZXNfcGVyX21lYW4sIGxhYmVsLCBuKQpgYGAKCiMjIFZpc3VhbGl6ZSBkYXRhIHVzaW5nIFBDQQoKYGBge3J9Cm5fc2FtcGxlc19wZXJfbWVhbiA8LSBuX3NhbXBsZXNfcGVyX21lYW5fbFtsZW5ndGgobl9zYW1wbGVzX3Blcl9tZWFuX2wpXQoKbl9zYW1wbGVzX3Blcl9tZWFuIDwtIG5fc2FtcGxlc19wZXJfbWVhbiAqIDEwCgpkYXRhIDwtIGxhcHBseSgxOm5fbWVhbnMsCiAgICAgICAgICAgICAgIGZ1bmN0aW9uKGkpCiAgICAgICAgICAgICAgICAgbXZybm9ybShuID0gbl9zYW1wbGVzX3Blcl9tZWFuLAogICAgICAgICAgICAgICAgICAgICAgICAgbWVhbnNbaSxdLAogICAgICAgICAgICAgICAgICAgICAgICAgc2RzW2ldICogZGlhZyhyZXAoMSwgZCkpKSkKCmRhdGEgPC0gZG8uY2FsbChyYmluZCwgZGF0YSkKCm0gPC0gcHJjb21wKGRhdGEpCgpkYXRhX3BjIDwtIGFzX3RpYmJsZShtJHhbLDE6Ml0pCgpkYXRhX3BjJHNkdiA8LSByZXAoc2RzLCBlYWNoID0gbl9zYW1wbGVzX3Blcl9tZWFuKQoKcm91bmRfaXQgPC0gZnVuY3Rpb24oeCkgcm91bmQoYXMubnVtZXJpYyh4KSwgMSkKYGBgCgoKYGBge3J9CmRhdGFfcGMgJT4lCiAgZ2dwbG90KGFlcyhQQzEsIFBDMikpICsgCiAgZ2VvbV9oZXgoKSArCiAgdGhlbWVfdm9pZCgpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICBmYWNldF93cmFwKH5zZHYsIGxhYmVsbGVyID0gbGFiZWxsZXIoc2R2ID0gcm91bmRfaXQpKSArCiAgY29vcmRfZXF1YWwoKQpgYGAKCiMjIFBsb3Qgc3ByZWFkIGluIFBDMSB3cnQgcy5kLgoKYGBge3J9CmRhdGFfcGMgJT4lIAogIGdyb3VwX2J5KHNkdikgJT4lCiAgc3VtbWFyaXNlKGFjcm9zcyhldmVyeXRoaW5nKCksIGxpc3Qoc2QgPSBzZCkpKSAlPiUKICBnZ3Bsb3QoYWVzKHNkdiwgUEMxX3NkKSkgKyAKICBnZW9tX3BvaW50KCkgKyAKICBnZW9tX3Ntb290aChtZXRob2QgPSBsbSwgZm9ybXVsYSA9IHkgfiB4KQpgYGAKCiMjIE1SQyB2cyBjYXJkaW5hbGl0eSBhY3Jvc3MgZGlmZmVyZW50IGRpc3BlcnNpb24gbGV2ZWxzCgpgYGB7cn0KZGYgJT4lIAogIGZpbHRlcihsYWJlbCA9PSAic2lnbmFsIikgJT4lIAogIGdyb3VwX2J5KG5fc2FtcGxlc19wZXJfbWVhbiwgY2xhc3MpICU+JSAKICBzdW1tYXJpemUoYWNyb3NzKGMoInNpbSIpLCBsaXN0KG1lYW4gPSBtZWFuLCBzZCA9IHNkKSksIC5ncm91cHMgPSAia2VlcCIpICU+JQogIGdncGxvdChhZXMobl9zYW1wbGVzX3Blcl9tZWFuLCBzaW1fbWVhbiwgY29sb3IgPSBjbGFzcywgZ3JvdXAgPSBjbGFzcykpICsgCiAgZ2VvbV9saW5lKGFscGhhID0gMC41KSAjKyAKCmRmICU+JSAKICBmaWx0ZXIobGFiZWwgPT0gInNpZ25hbCIpICU+JSAKICBncm91cF9ieShuX3NhbXBsZXNfcGVyX21lYW4sIGNsYXNzKSAlPiUgCiAgc3VtbWFyaXplKGFjcm9zcyhjKCJzaW0iKSwgbGlzdChtZWFuID0gbWVhbiwgc2QgPSBzZCkpLCAuZ3JvdXBzID0gImtlZXAiKSAlPiUKICBnZ3Bsb3QoYWVzKG5fc2FtcGxlc19wZXJfbWVhbiwgc2ltX3NkLCBjb2xvciA9IGNsYXNzLCBncm91cCA9IGNsYXNzKSkgKyAKICBnZW9tX2xpbmUoYWxwaGEgPSAwLjUpCmBgYAoKCmBgYHtyIGV2YWw9RkFMU0V9CmN1c3RvbS5jb25maWcgPSB1bWFwOjp1bWFwLmRlZmF1bHRzCgpjdXN0b20uY29uZmlnJHJhbmRvbV9zdGF0ZSA9IDEyMwoKdW1hcF9lbWJlZGRpbmcgPC0KICB1bWFwOjp1bWFwKGRhdGEsCiAgICAgICAgICAgICBjb25maWcgPSBjdXN0b20uY29uZmlnLAogICAgICAgICAgICAgbl9uZWlnaGJvcnMgPSAyMCkKCnVtYXBfZW1iZWRkaW5nIDwtCiAgYXMuZGF0YS5mcmFtZSh1bWFwX2VtYmVkZGluZyRsYXlvdXQpCgp1bWFwX2VtYmVkZGluZyRzZHYgPC0gcmVwKHNkcywgZWFjaCA9IG5fc2FtcGxlc19wZXJfbWVhbikKCnJvdW5kX2l0IDwtIGZ1bmN0aW9uKHgpIHJvdW5kKGFzLm51bWVyaWMoeCksIDEpCgp1bWFwX2VtYmVkZGluZyAlPiUKICBnZ3Bsb3QoYWVzKFYxLFYyKSkgKyAKICBnZW9tX2hleCgpICsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGZhY2V0X3dyYXAofnNkdiwgbGFiZWxsZXIgPSBsYWJlbGxlcihzZHYgPSByb3VuZF9pdCkpICsKICBjb29yZF9lcXVhbCgpCgp1bWFwX2VtYmVkZGluZyAlPiUKICBnZ3Bsb3QoYWVzKFYxLCBWMiwgY29sb3IgPSBhcy5mYWN0b3Ioc2R2KSkpICsgCiAgZ2VvbV9wb2ludCgpICsKICB0aGVtZV92b2lkKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKwogIGNvb3JkX2VxdWFsKCkKYGBgCgo=