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=