1 Setup

doParallel::registerDoParallel(cores = 8)

2 Functions

2.1 Utils

Get feature matrix

feats <- function(X, pattern = "Cells_AreaShape_Zernike", ...) {
  X %>% dplyr::select(matches(pattern))
}

2.2 Statistics

2.2.1 Difference between means

Compute difference between mean of specified group and everything else

mean_diff <-
  function(X, label_i, label_col = "Metadata_pert_name", ...) {
    A <- X %>% filter(.data[[label_col]] == label_i)
    B <- X %>% filter(.data[[label_col]] != label_i)
    
    sqrt(sum((colMeans(feats(
      A, ...
    )) - colMeans(feats(
      B, ...
    ))) ** 2))
  }

2.2.2 Median replicate correlation

Compute median replicate correlation of specified group (indicated by label_i)

mrc <- function(X, label_i, label_col = "Metadata_pert_name", ...) {
  A <- X %>% filter(.data[[label_col]] == label_i)
  
  corvec <- function(m) {
    cm <- cor(m)
    cm[upper.tri(cm)]
  }
  
  median(corvec(t(as.matrix(feats(
    A, ...
  )))))
}

2.2.3 Inv of mean pairwise distance

invmpd <-
  function(X, label_i, label_col = "Metadata_pert_name", ...) {
    
    A <- X %>% filter(.data[[label_col]] == label_i)
    
    1 / mean(dist(as.matrix(feats(A, ...))))
    
  }

2.2.4 Trace of precision matrix

Compute trace of precision matrix of specified group (indicated by label_i)

trace_precision <- function(X, label_i, label_col = "Metadata_pert_name", ...) {
  
  A <- X %>% filter(.data[[label_col]] == label_i)
  
  sv <- svd(as.matrix(feats(A, ...)), nu = 0, nv = 0)$d

  sum(1/(sv**2))
  
}

2.2.5 Inverse of major axis length

pc1 <- function(X, label_i, label_col = "Metadata_pert_name", ...) {
  
  A <- X %>% filter(.data[[label_col]] == label_i)
  
  sv <- svd(as.matrix(feats(A, ...)), nu = 0, nv = 0)$d

  1/sv[1]
  
}

2.2.6 Precision

beta <- function(X, label_i, label_col = "Metadata_pert_name", ...) {
  
  A <- X %>% filter(.data[[label_col]] == label_i)
  
  1 / sd(as.matrix(feats(A, ...)))

}

2.3 Testing

Permute label

permute <- function(X, label_col = "Metadata_pert_name") {
  X[[label_col]] <- sample(X[[label_col]])
  X
}

Compute null distribution i.e. the sampling distribution of the test statistic under the null hypothesis

generate_null <-
  function(X,
           label,
           test_statistic = mean_diff,
           n = 100,
           label_col = "Metadata_pert_name",
           ...) {
    
    test_statistic(permute(X, label_col), label, ...)
    
    times(n) %dopar% test_statistic(permute(X, label_col), label, ...)
    
  }

Run permutation test and plot

run_test <- function(test_statistic, ...) {

  null_distribution <- generate_null(test_statistic = test_statistic, ...)
  
  test_statistic_value <- test_statistic(...)
  
  p_value <- sum(null_distribution > test_statistic_value) / length(null_distribution)
  
  p <- 
    data.frame(x = null_distribution) %>%
    ggplot(aes(x)) + geom_histogram(bins = 30) + 
    geom_vline(xintercept = test_statistic_value, color = "red") + 
    labs(caption = glue("p-value = {round(p_value, 2)}"))
  
  p
}

3 Load

Load cell health data

url <-
  "https://github.com/broadinstitute/grit-benchmark/raw/main/1.calculate-metrics/cell-health/data/cell_health_merged_feature_select.csv.gz"

cellhealth <-
  read_csv(
    url,
    col_types = cols(
      .default = col_double(),
      Metadata_Plate = col_character(),
      Metadata_Well = col_character(),
      Metadata_WellRow = col_character(),
      Metadata_WellCol = col_character(),
      Metadata_cell_line = col_character(),
      Metadata_gene_name = col_character(),
      Metadata_pert_name = col_character()
    )
  )

cellhealth <-
  cellhealth %>%
  filter(Metadata_cell_line == "A549") %>%
  filter(!(Metadata_gene_name %in% c("EMPTY", "Chr2")))
cellhealth %>%
  count(Metadata_pert_name, name = "n_replicates") %>% 
  count(n_replicates)

4 Run

X <- cellhealth
n <- 300
label_col <- "Metadata_pert_name"
pattern <- "Cells_Intensity"

4.1 Difference between means

label <- "LacZ-3"
test_statistic <- mean_diff
test_statistic_name <- "mean_diff"

run_test(
  X = X,
  label = label,
  test_statistic = test_statistic,
  n = n,
  label_col = label_col
) +
  xlab(test_statistic_name) +
  ggtitle(
    glue(
      "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 2)}"
    )
  )

label <- "CDK4-2"
test_statistic <- mean_diff
test_statistic_name <- "mean_diff"

run_test(
  X = X,
  label = label,
  test_statistic = test_statistic,
  n = n,
  label_col = label_col
) +
  xlab(test_statistic_name) +
  ggtitle(
    glue(
      "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 2)}"
    )
  )

4.2 Median replicate correlation

label <- "LacZ-3"
test_statistic <- mrc
test_statistic_name <- "mrc"

run_test(X = X,
         label = label,
         test_statistic = test_statistic,
         n = n,
         label_col = label_col) +
  xlab(test_statistic_name) +
  ggtitle(glue(
    "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 2)}"
    ))

label <- "CDK4-2"
test_statistic <- mrc
test_statistic_name <- "mrc"

run_test(X = X,
         label = label,
         test_statistic = test_statistic,
         n = n,
         label_col = label_col) +
  xlab(test_statistic_name) +
  ggtitle(glue(
    "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 2)}"
    ))

4.3 Inv of mean pairwise distance

label <- "LacZ-3"
test_statistic <- invmpd
test_statistic_name <- "invmpd"

run_test(X = X,
         label = label,
         test_statistic = test_statistic,
         n = n,
         label_col = label_col) +
  xlab(test_statistic_name) +
  ggtitle(glue(
    "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 3)}"
    ))

label <- "CDK4-2"
test_statistic <- invmpd
test_statistic_name <- "invmpd"

run_test(X = X,
         label = label,
         test_statistic = test_statistic,
         n = n,
         label_col = label_col) +
  xlab(test_statistic_name) +
  ggtitle(glue(
    "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 3)}"
    ))

4.4 Trace of precision matrix

label <- "LacZ-3"
test_statistic <- trace_precision
test_statistic_name <- "trace_precision"

run_test(X = X,
         label = label,
         test_statistic = test_statistic,
         n = n,
         label_col = label_col) +
  xlab(test_statistic_name) +
  ggtitle(glue(
    "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 3)}"
    ))

label <- "CDK4-2"
test_statistic <- trace_precision
test_statistic_name <- "trace_precision"

run_test(X = X,
         label = label,
         test_statistic = test_statistic,
         n = n,
         label_col = label_col) +
  xlab(test_statistic_name) +
  ggtitle(glue(
    "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 3)}"
    ))

4.5 Inverse of major axis length

label <- "LacZ-3"
test_statistic <- pc1
test_statistic_name <- "pc1"

run_test(X = X,
         label = label,
         test_statistic = test_statistic,
         n = n,
         label_col = label_col) +
  xlab(test_statistic_name) +
  ggtitle(glue(
    "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 3)}"
    ))

label <- "CDK4-2"
test_statistic <- pc1
test_statistic_name <- "pc1"

run_test(X = X,
         label = label,
         test_statistic = test_statistic,
         n = n,
         label_col = label_col) +
  xlab(test_statistic_name) +
  ggtitle(glue(
    "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 3)}"
    ))

4.6 Precision - single variable

label <- "LacZ-3"
test_statistic <- beta
test_statistic_name <- "beta"
pattern <- "Cells_AreaShape_Zernike_0_0"

run_test(X = X,
         label = label,
         test_statistic = test_statistic,
         n = n,
         label_col = label_col,
         pattern = pattern) +
  xlab(test_statistic_name) +
  ggtitle(glue(
    "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 3)}"
    ))

label <- "CDK4-2"
test_statistic <- beta
test_statistic_name <- "beta"
pattern <- "Cells_AreaShape_Zernike_0_0"

run_test(X = X,
         label = label,
         test_statistic = test_statistic,
         n = n,
         label_col = label_col,
         pattern = pattern) +
  xlab(test_statistic_name) +
  ggtitle(glue(
    "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 3)}"
    ))

4.7 Difference between means - single variable

label <- "LacZ-3"
test_statistic <- mean_diff
test_statistic_name <- "mean_diff"
pattern <- "Cells_AreaShape_Zernike_0_0"

run_test(X = X,
         label = label,
         test_statistic = test_statistic,
         n = n,
         label_col = label_col,
         pattern = pattern) +
  xlab(test_statistic_name) +
  ggtitle(glue(
    "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 3)}"
    ))

label <- "CDK4-2"
test_statistic <- mean_diff
test_statistic_name <- "mean_diff"
pattern <- "Cells_AreaShape_Zernike_0_0"

run_test(X = X,
         label = label,
         test_statistic = test_statistic,
         n = n,
         label_col = label_col,
         pattern = pattern) +
  xlab(test_statistic_name) +
  ggtitle(glue(
    "{label_col} = {label}\n{test_statistic_name} = {round(test_statistic(X, label), 3)}"
    ))

LS0tCnRpdGxlOiAiUGVyY2VudCByZXBsaWNhdGluZyB0aHJvdWdoIHRoZSBsZW5zIG9mIHBlcm11dGF0aW9uIHRlc3RpbmciCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMgU2V0dXAKCmBgYHtyfQpzZXQuc2VlZCg0MikKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZm9yZWFjaCkKbGlicmFyeShnbHVlKQpgYGAKCgpgYGB7cn0KZG9QYXJhbGxlbDo6cmVnaXN0ZXJEb1BhcmFsbGVsKGNvcmVzID0gOCkKYGBgCgojIEZ1bmN0aW9ucwoKIyMgVXRpbHMKCkdldCBmZWF0dXJlIG1hdHJpeAoKYGBge3J9CmZlYXRzIDwtIGZ1bmN0aW9uKFgsIHBhdHRlcm4gPSAiQ2VsbHNfQXJlYVNoYXBlX1plcm5pa2UiLCAuLi4pIHsKICBYICU+JSBkcGx5cjo6c2VsZWN0KG1hdGNoZXMocGF0dGVybikpCn0KYGBgCgojIyBTdGF0aXN0aWNzCgojIyMgRGlmZmVyZW5jZSBiZXR3ZWVuIG1lYW5zCgpDb21wdXRlIGRpZmZlcmVuY2UgYmV0d2VlbiBtZWFuIG9mIHNwZWNpZmllZCBncm91cCBhbmQgZXZlcnl0aGluZyBlbHNlCgpgYGB7cn0KbWVhbl9kaWZmIDwtCiAgZnVuY3Rpb24oWCwgbGFiZWxfaSwgbGFiZWxfY29sID0gIk1ldGFkYXRhX3BlcnRfbmFtZSIsIC4uLikgewogICAgQSA8LSBYICU+JSBmaWx0ZXIoLmRhdGFbW2xhYmVsX2NvbF1dID09IGxhYmVsX2kpCiAgICBCIDwtIFggJT4lIGZpbHRlciguZGF0YVtbbGFiZWxfY29sXV0gIT0gbGFiZWxfaSkKICAgIAogICAgc3FydChzdW0oKGNvbE1lYW5zKGZlYXRzKAogICAgICBBLCAuLi4KICAgICkpIC0gY29sTWVhbnMoZmVhdHMoCiAgICAgIEIsIC4uLgogICAgKSkpICoqIDIpKQogIH0KYGBgCgojIyMgTWVkaWFuIHJlcGxpY2F0ZSBjb3JyZWxhdGlvbgoKQ29tcHV0ZSBtZWRpYW4gcmVwbGljYXRlIGNvcnJlbGF0aW9uIG9mIHNwZWNpZmllZCBncm91cCAoaW5kaWNhdGVkIGJ5IGBsYWJlbF9pYCkKCmBgYHtyfQptcmMgPC0gZnVuY3Rpb24oWCwgbGFiZWxfaSwgbGFiZWxfY29sID0gIk1ldGFkYXRhX3BlcnRfbmFtZSIsIC4uLikgewogIEEgPC0gWCAlPiUgZmlsdGVyKC5kYXRhW1tsYWJlbF9jb2xdXSA9PSBsYWJlbF9pKQogIAogIGNvcnZlYyA8LSBmdW5jdGlvbihtKSB7CiAgICBjbSA8LSBjb3IobSkKICAgIGNtW3VwcGVyLnRyaShjbSldCiAgfQogIAogIG1lZGlhbihjb3J2ZWModChhcy5tYXRyaXgoZmVhdHMoCiAgICBBLCAuLi4KICApKSkpKQp9CmBgYAoKIyMjIEludiBvZiBtZWFuIHBhaXJ3aXNlIGRpc3RhbmNlCgpgYGB7cn0KaW52bXBkIDwtCiAgZnVuY3Rpb24oWCwgbGFiZWxfaSwgbGFiZWxfY29sID0gIk1ldGFkYXRhX3BlcnRfbmFtZSIsIC4uLikgewogICAgCiAgICBBIDwtIFggJT4lIGZpbHRlciguZGF0YVtbbGFiZWxfY29sXV0gPT0gbGFiZWxfaSkKICAgIAogICAgMSAvIG1lYW4oZGlzdChhcy5tYXRyaXgoZmVhdHMoQSwgLi4uKSkpKQogICAgCiAgfQpgYGAKCiMjIyBUcmFjZSBvZiBwcmVjaXNpb24gbWF0cml4CgpDb21wdXRlIHRyYWNlIG9mIHByZWNpc2lvbiBtYXRyaXggb2Ygc3BlY2lmaWVkIGdyb3VwIChpbmRpY2F0ZWQgYnkgYGxhYmVsX2lgKQoKYGBge3J9CnRyYWNlX3ByZWNpc2lvbiA8LSBmdW5jdGlvbihYLCBsYWJlbF9pLCBsYWJlbF9jb2wgPSAiTWV0YWRhdGFfcGVydF9uYW1lIiwgLi4uKSB7CiAgCiAgQSA8LSBYICU+JSBmaWx0ZXIoLmRhdGFbW2xhYmVsX2NvbF1dID09IGxhYmVsX2kpCiAgCiAgc3YgPC0gc3ZkKGFzLm1hdHJpeChmZWF0cyhBLCAuLi4pKSwgbnUgPSAwLCBudiA9IDApJGQKCiAgc3VtKDEvKHN2KioyKSkKICAKfQpgYGAKCiMjIyBJbnZlcnNlIG9mIG1ham9yIGF4aXMgbGVuZ3RoCgpgYGB7cn0KcGMxIDwtIGZ1bmN0aW9uKFgsIGxhYmVsX2ksIGxhYmVsX2NvbCA9ICJNZXRhZGF0YV9wZXJ0X25hbWUiLCAuLi4pIHsKICAKICBBIDwtIFggJT4lIGZpbHRlciguZGF0YVtbbGFiZWxfY29sXV0gPT0gbGFiZWxfaSkKICAKICBzdiA8LSBzdmQoYXMubWF0cml4KGZlYXRzKEEsIC4uLikpLCBudSA9IDAsIG52ID0gMCkkZAoKICAxL3N2WzFdCiAgCn0KYGBgCgojIyMgUHJlY2lzaW9uCgpgYGB7cn0KYmV0YSA8LSBmdW5jdGlvbihYLCBsYWJlbF9pLCBsYWJlbF9jb2wgPSAiTWV0YWRhdGFfcGVydF9uYW1lIiwgLi4uKSB7CiAgCiAgQSA8LSBYICU+JSBmaWx0ZXIoLmRhdGFbW2xhYmVsX2NvbF1dID09IGxhYmVsX2kpCiAgCiAgMSAvIHNkKGFzLm1hdHJpeChmZWF0cyhBLCAuLi4pKSkKCn0KYGBgCgojIyBUZXN0aW5nCgpQZXJtdXRlIGxhYmVsCgpgYGB7cn0KcGVybXV0ZSA8LSBmdW5jdGlvbihYLCBsYWJlbF9jb2wgPSAiTWV0YWRhdGFfcGVydF9uYW1lIikgewogIFhbW2xhYmVsX2NvbF1dIDwtIHNhbXBsZShYW1tsYWJlbF9jb2xdXSkKICBYCn0KYGBgCgpDb21wdXRlIG51bGwgZGlzdHJpYnV0aW9uIGkuZS4gdGhlIHNhbXBsaW5nIGRpc3RyaWJ1dGlvbiBvZiB0aGUgdGVzdCBzdGF0aXN0aWMgdW5kZXIgdGhlIG51bGwgaHlwb3RoZXNpcwoKYGBge3J9CmdlbmVyYXRlX251bGwgPC0KICBmdW5jdGlvbihYLAogICAgICAgICAgIGxhYmVsLAogICAgICAgICAgIHRlc3Rfc3RhdGlzdGljID0gbWVhbl9kaWZmLAogICAgICAgICAgIG4gPSAxMDAsCiAgICAgICAgICAgbGFiZWxfY29sID0gIk1ldGFkYXRhX3BlcnRfbmFtZSIsCiAgICAgICAgICAgLi4uKSB7CiAgICAKICAgIHRlc3Rfc3RhdGlzdGljKHBlcm11dGUoWCwgbGFiZWxfY29sKSwgbGFiZWwsIC4uLikKICAgIAogICAgdGltZXMobikgJWRvcGFyJSB0ZXN0X3N0YXRpc3RpYyhwZXJtdXRlKFgsIGxhYmVsX2NvbCksIGxhYmVsLCAuLi4pCiAgICAKICB9CgpgYGAKClJ1biBwZXJtdXRhdGlvbiB0ZXN0IGFuZCBwbG90CgpgYGB7cn0KcnVuX3Rlc3QgPC0gZnVuY3Rpb24odGVzdF9zdGF0aXN0aWMsIC4uLikgewoKICBudWxsX2Rpc3RyaWJ1dGlvbiA8LSBnZW5lcmF0ZV9udWxsKHRlc3Rfc3RhdGlzdGljID0gdGVzdF9zdGF0aXN0aWMsIC4uLikKICAKICB0ZXN0X3N0YXRpc3RpY192YWx1ZSA8LSB0ZXN0X3N0YXRpc3RpYyguLi4pCiAgCiAgcF92YWx1ZSA8LSBzdW0obnVsbF9kaXN0cmlidXRpb24gPiB0ZXN0X3N0YXRpc3RpY192YWx1ZSkgLyBsZW5ndGgobnVsbF9kaXN0cmlidXRpb24pCiAgCiAgcCA8LSAKICAgIGRhdGEuZnJhbWUoeCA9IG51bGxfZGlzdHJpYnV0aW9uKSAlPiUKICAgIGdncGxvdChhZXMoeCkpICsgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDMwKSArIAogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gdGVzdF9zdGF0aXN0aWNfdmFsdWUsIGNvbG9yID0gInJlZCIpICsgCiAgICBsYWJzKGNhcHRpb24gPSBnbHVlKCJwLXZhbHVlID0ge3JvdW5kKHBfdmFsdWUsIDIpfSIpKQogIAogIHAKfQpgYGAKCgojIExvYWQgCgpMb2FkIGNlbGwgaGVhbHRoIGRhdGEKCmBgYHtyfQp1cmwgPC0KICAiaHR0cHM6Ly9naXRodWIuY29tL2Jyb2FkaW5zdGl0dXRlL2dyaXQtYmVuY2htYXJrL3Jhdy9tYWluLzEuY2FsY3VsYXRlLW1ldHJpY3MvY2VsbC1oZWFsdGgvZGF0YS9jZWxsX2hlYWx0aF9tZXJnZWRfZmVhdHVyZV9zZWxlY3QuY3N2Lmd6IgoKY2VsbGhlYWx0aCA8LQogIHJlYWRfY3N2KAogICAgdXJsLAogICAgY29sX3R5cGVzID0gY29scygKICAgICAgLmRlZmF1bHQgPSBjb2xfZG91YmxlKCksCiAgICAgIE1ldGFkYXRhX1BsYXRlID0gY29sX2NoYXJhY3RlcigpLAogICAgICBNZXRhZGF0YV9XZWxsID0gY29sX2NoYXJhY3RlcigpLAogICAgICBNZXRhZGF0YV9XZWxsUm93ID0gY29sX2NoYXJhY3RlcigpLAogICAgICBNZXRhZGF0YV9XZWxsQ29sID0gY29sX2NoYXJhY3RlcigpLAogICAgICBNZXRhZGF0YV9jZWxsX2xpbmUgPSBjb2xfY2hhcmFjdGVyKCksCiAgICAgIE1ldGFkYXRhX2dlbmVfbmFtZSA9IGNvbF9jaGFyYWN0ZXIoKSwKICAgICAgTWV0YWRhdGFfcGVydF9uYW1lID0gY29sX2NoYXJhY3RlcigpCiAgICApCiAgKQoKY2VsbGhlYWx0aCA8LQogIGNlbGxoZWFsdGggJT4lCiAgZmlsdGVyKE1ldGFkYXRhX2NlbGxfbGluZSA9PSAiQTU0OSIpICU+JQogIGZpbHRlcighKE1ldGFkYXRhX2dlbmVfbmFtZSAlaW4lIGMoIkVNUFRZIiwgIkNocjIiKSkpCmBgYAoKCmBgYHtyfQpjZWxsaGVhbHRoICU+JQogIGNvdW50KE1ldGFkYXRhX3BlcnRfbmFtZSwgbmFtZSA9ICJuX3JlcGxpY2F0ZXMiKSAlPiUgCiAgY291bnQobl9yZXBsaWNhdGVzKQpgYGAKCiMgUnVuCgpgYGB7cn0KWCA8LSBjZWxsaGVhbHRoCm4gPC0gMzAwCmxhYmVsX2NvbCA8LSAiTWV0YWRhdGFfcGVydF9uYW1lIgpwYXR0ZXJuIDwtICJDZWxsc19JbnRlbnNpdHkiCmBgYAoKIyMgRGlmZmVyZW5jZSBiZXR3ZWVuIG1lYW5zCgpgYGB7cn0KbGFiZWwgPC0gIkxhY1otMyIKdGVzdF9zdGF0aXN0aWMgPC0gbWVhbl9kaWZmCnRlc3Rfc3RhdGlzdGljX25hbWUgPC0gIm1lYW5fZGlmZiIKCnJ1bl90ZXN0KAogIFggPSBYLAogIGxhYmVsID0gbGFiZWwsCiAgdGVzdF9zdGF0aXN0aWMgPSB0ZXN0X3N0YXRpc3RpYywKICBuID0gbiwKICBsYWJlbF9jb2wgPSBsYWJlbF9jb2wKKSArCiAgeGxhYih0ZXN0X3N0YXRpc3RpY19uYW1lKSArCiAgZ2d0aXRsZSgKICAgIGdsdWUoCiAgICAgICJ7bGFiZWxfY29sfSA9IHtsYWJlbH1cbnt0ZXN0X3N0YXRpc3RpY19uYW1lfSA9IHtyb3VuZCh0ZXN0X3N0YXRpc3RpYyhYLCBsYWJlbCksIDIpfSIKICAgICkKICApCmBgYAoKCmBgYHtyfQpsYWJlbCA8LSAiQ0RLNC0yIgp0ZXN0X3N0YXRpc3RpYyA8LSBtZWFuX2RpZmYKdGVzdF9zdGF0aXN0aWNfbmFtZSA8LSAibWVhbl9kaWZmIgoKcnVuX3Rlc3QoCiAgWCA9IFgsCiAgbGFiZWwgPSBsYWJlbCwKICB0ZXN0X3N0YXRpc3RpYyA9IHRlc3Rfc3RhdGlzdGljLAogIG4gPSBuLAogIGxhYmVsX2NvbCA9IGxhYmVsX2NvbAopICsKICB4bGFiKHRlc3Rfc3RhdGlzdGljX25hbWUpICsKICBnZ3RpdGxlKAogICAgZ2x1ZSgKICAgICAgIntsYWJlbF9jb2x9ID0ge2xhYmVsfVxue3Rlc3Rfc3RhdGlzdGljX25hbWV9ID0ge3JvdW5kKHRlc3Rfc3RhdGlzdGljKFgsIGxhYmVsKSwgMil9IgogICAgKQogICkKYGBgCgojIyBNZWRpYW4gcmVwbGljYXRlIGNvcnJlbGF0aW9uCgpgYGB7cn0KbGFiZWwgPC0gIkxhY1otMyIKdGVzdF9zdGF0aXN0aWMgPC0gbXJjCnRlc3Rfc3RhdGlzdGljX25hbWUgPC0gIm1yYyIKCnJ1bl90ZXN0KFggPSBYLAogICAgICAgICBsYWJlbCA9IGxhYmVsLAogICAgICAgICB0ZXN0X3N0YXRpc3RpYyA9IHRlc3Rfc3RhdGlzdGljLAogICAgICAgICBuID0gbiwKICAgICAgICAgbGFiZWxfY29sID0gbGFiZWxfY29sKSArCiAgeGxhYih0ZXN0X3N0YXRpc3RpY19uYW1lKSArCiAgZ2d0aXRsZShnbHVlKAogICAgIntsYWJlbF9jb2x9ID0ge2xhYmVsfVxue3Rlc3Rfc3RhdGlzdGljX25hbWV9ID0ge3JvdW5kKHRlc3Rfc3RhdGlzdGljKFgsIGxhYmVsKSwgMil9IgogICAgKSkKYGBgCgoKYGBge3J9CmxhYmVsIDwtICJDREs0LTIiCnRlc3Rfc3RhdGlzdGljIDwtIG1yYwp0ZXN0X3N0YXRpc3RpY19uYW1lIDwtICJtcmMiCgpydW5fdGVzdChYID0gWCwKICAgICAgICAgbGFiZWwgPSBsYWJlbCwKICAgICAgICAgdGVzdF9zdGF0aXN0aWMgPSB0ZXN0X3N0YXRpc3RpYywKICAgICAgICAgbiA9IG4sCiAgICAgICAgIGxhYmVsX2NvbCA9IGxhYmVsX2NvbCkgKwogIHhsYWIodGVzdF9zdGF0aXN0aWNfbmFtZSkgKwogIGdndGl0bGUoZ2x1ZSgKICAgICJ7bGFiZWxfY29sfSA9IHtsYWJlbH1cbnt0ZXN0X3N0YXRpc3RpY19uYW1lfSA9IHtyb3VuZCh0ZXN0X3N0YXRpc3RpYyhYLCBsYWJlbCksIDIpfSIKICAgICkpCmBgYAojIyBJbnYgb2YgbWVhbiBwYWlyd2lzZSBkaXN0YW5jZQoKCmBgYHtyfQpsYWJlbCA8LSAiTGFjWi0zIgp0ZXN0X3N0YXRpc3RpYyA8LSBpbnZtcGQKdGVzdF9zdGF0aXN0aWNfbmFtZSA8LSAiaW52bXBkIgoKcnVuX3Rlc3QoWCA9IFgsCiAgICAgICAgIGxhYmVsID0gbGFiZWwsCiAgICAgICAgIHRlc3Rfc3RhdGlzdGljID0gdGVzdF9zdGF0aXN0aWMsCiAgICAgICAgIG4gPSBuLAogICAgICAgICBsYWJlbF9jb2wgPSBsYWJlbF9jb2wpICsKICB4bGFiKHRlc3Rfc3RhdGlzdGljX25hbWUpICsKICBnZ3RpdGxlKGdsdWUoCiAgICAie2xhYmVsX2NvbH0gPSB7bGFiZWx9XG57dGVzdF9zdGF0aXN0aWNfbmFtZX0gPSB7cm91bmQodGVzdF9zdGF0aXN0aWMoWCwgbGFiZWwpLCAzKX0iCiAgICApKQpgYGAKCgpgYGB7cn0KbGFiZWwgPC0gIkNESzQtMiIKdGVzdF9zdGF0aXN0aWMgPC0gaW52bXBkCnRlc3Rfc3RhdGlzdGljX25hbWUgPC0gImludm1wZCIKCnJ1bl90ZXN0KFggPSBYLAogICAgICAgICBsYWJlbCA9IGxhYmVsLAogICAgICAgICB0ZXN0X3N0YXRpc3RpYyA9IHRlc3Rfc3RhdGlzdGljLAogICAgICAgICBuID0gbiwKICAgICAgICAgbGFiZWxfY29sID0gbGFiZWxfY29sKSArCiAgeGxhYih0ZXN0X3N0YXRpc3RpY19uYW1lKSArCiAgZ2d0aXRsZShnbHVlKAogICAgIntsYWJlbF9jb2x9ID0ge2xhYmVsfVxue3Rlc3Rfc3RhdGlzdGljX25hbWV9ID0ge3JvdW5kKHRlc3Rfc3RhdGlzdGljKFgsIGxhYmVsKSwgMyl9IgogICAgKSkKYGBgCgojIyBUcmFjZSBvZiBwcmVjaXNpb24gbWF0cml4CgpgYGB7cn0KbGFiZWwgPC0gIkxhY1otMyIKdGVzdF9zdGF0aXN0aWMgPC0gdHJhY2VfcHJlY2lzaW9uCnRlc3Rfc3RhdGlzdGljX25hbWUgPC0gInRyYWNlX3ByZWNpc2lvbiIKCnJ1bl90ZXN0KFggPSBYLAogICAgICAgICBsYWJlbCA9IGxhYmVsLAogICAgICAgICB0ZXN0X3N0YXRpc3RpYyA9IHRlc3Rfc3RhdGlzdGljLAogICAgICAgICBuID0gbiwKICAgICAgICAgbGFiZWxfY29sID0gbGFiZWxfY29sKSArCiAgeGxhYih0ZXN0X3N0YXRpc3RpY19uYW1lKSArCiAgZ2d0aXRsZShnbHVlKAogICAgIntsYWJlbF9jb2x9ID0ge2xhYmVsfVxue3Rlc3Rfc3RhdGlzdGljX25hbWV9ID0ge3JvdW5kKHRlc3Rfc3RhdGlzdGljKFgsIGxhYmVsKSwgMyl9IgogICAgKSkKYGBgCgoKYGBge3J9CmxhYmVsIDwtICJDREs0LTIiCnRlc3Rfc3RhdGlzdGljIDwtIHRyYWNlX3ByZWNpc2lvbgp0ZXN0X3N0YXRpc3RpY19uYW1lIDwtICJ0cmFjZV9wcmVjaXNpb24iCgpydW5fdGVzdChYID0gWCwKICAgICAgICAgbGFiZWwgPSBsYWJlbCwKICAgICAgICAgdGVzdF9zdGF0aXN0aWMgPSB0ZXN0X3N0YXRpc3RpYywKICAgICAgICAgbiA9IG4sCiAgICAgICAgIGxhYmVsX2NvbCA9IGxhYmVsX2NvbCkgKwogIHhsYWIodGVzdF9zdGF0aXN0aWNfbmFtZSkgKwogIGdndGl0bGUoZ2x1ZSgKICAgICJ7bGFiZWxfY29sfSA9IHtsYWJlbH1cbnt0ZXN0X3N0YXRpc3RpY19uYW1lfSA9IHtyb3VuZCh0ZXN0X3N0YXRpc3RpYyhYLCBsYWJlbCksIDMpfSIKICAgICkpCmBgYAoKIyMgSW52ZXJzZSBvZiBtYWpvciBheGlzIGxlbmd0aAoKYGBge3J9CmxhYmVsIDwtICJMYWNaLTMiCnRlc3Rfc3RhdGlzdGljIDwtIHBjMQp0ZXN0X3N0YXRpc3RpY19uYW1lIDwtICJwYzEiCgpydW5fdGVzdChYID0gWCwKICAgICAgICAgbGFiZWwgPSBsYWJlbCwKICAgICAgICAgdGVzdF9zdGF0aXN0aWMgPSB0ZXN0X3N0YXRpc3RpYywKICAgICAgICAgbiA9IG4sCiAgICAgICAgIGxhYmVsX2NvbCA9IGxhYmVsX2NvbCkgKwogIHhsYWIodGVzdF9zdGF0aXN0aWNfbmFtZSkgKwogIGdndGl0bGUoZ2x1ZSgKICAgICJ7bGFiZWxfY29sfSA9IHtsYWJlbH1cbnt0ZXN0X3N0YXRpc3RpY19uYW1lfSA9IHtyb3VuZCh0ZXN0X3N0YXRpc3RpYyhYLCBsYWJlbCksIDMpfSIKICAgICkpCmBgYAoKCmBgYHtyfQpsYWJlbCA8LSAiQ0RLNC0yIgp0ZXN0X3N0YXRpc3RpYyA8LSBwYzEKdGVzdF9zdGF0aXN0aWNfbmFtZSA8LSAicGMxIgoKcnVuX3Rlc3QoWCA9IFgsCiAgICAgICAgIGxhYmVsID0gbGFiZWwsCiAgICAgICAgIHRlc3Rfc3RhdGlzdGljID0gdGVzdF9zdGF0aXN0aWMsCiAgICAgICAgIG4gPSBuLAogICAgICAgICBsYWJlbF9jb2wgPSBsYWJlbF9jb2wpICsKICB4bGFiKHRlc3Rfc3RhdGlzdGljX25hbWUpICsKICBnZ3RpdGxlKGdsdWUoCiAgICAie2xhYmVsX2NvbH0gPSB7bGFiZWx9XG57dGVzdF9zdGF0aXN0aWNfbmFtZX0gPSB7cm91bmQodGVzdF9zdGF0aXN0aWMoWCwgbGFiZWwpLCAzKX0iCiAgICApKQpgYGAKIyMgUHJlY2lzaW9uIC0gc2luZ2xlIHZhcmlhYmxlCgpgYGB7cn0KbGFiZWwgPC0gIkxhY1otMyIKdGVzdF9zdGF0aXN0aWMgPC0gYmV0YQp0ZXN0X3N0YXRpc3RpY19uYW1lIDwtICJiZXRhIgpwYXR0ZXJuIDwtICJDZWxsc19BcmVhU2hhcGVfWmVybmlrZV8wXzAiCgpydW5fdGVzdChYID0gWCwKICAgICAgICAgbGFiZWwgPSBsYWJlbCwKICAgICAgICAgdGVzdF9zdGF0aXN0aWMgPSB0ZXN0X3N0YXRpc3RpYywKICAgICAgICAgbiA9IG4sCiAgICAgICAgIGxhYmVsX2NvbCA9IGxhYmVsX2NvbCwKICAgICAgICAgcGF0dGVybiA9IHBhdHRlcm4pICsKICB4bGFiKHRlc3Rfc3RhdGlzdGljX25hbWUpICsKICBnZ3RpdGxlKGdsdWUoCiAgICAie2xhYmVsX2NvbH0gPSB7bGFiZWx9XG57dGVzdF9zdGF0aXN0aWNfbmFtZX0gPSB7cm91bmQodGVzdF9zdGF0aXN0aWMoWCwgbGFiZWwpLCAzKX0iCiAgICApKQpgYGAKCgpgYGB7cn0KbGFiZWwgPC0gIkNESzQtMiIKdGVzdF9zdGF0aXN0aWMgPC0gYmV0YQp0ZXN0X3N0YXRpc3RpY19uYW1lIDwtICJiZXRhIgpwYXR0ZXJuIDwtICJDZWxsc19BcmVhU2hhcGVfWmVybmlrZV8wXzAiCgpydW5fdGVzdChYID0gWCwKICAgICAgICAgbGFiZWwgPSBsYWJlbCwKICAgICAgICAgdGVzdF9zdGF0aXN0aWMgPSB0ZXN0X3N0YXRpc3RpYywKICAgICAgICAgbiA9IG4sCiAgICAgICAgIGxhYmVsX2NvbCA9IGxhYmVsX2NvbCwKICAgICAgICAgcGF0dGVybiA9IHBhdHRlcm4pICsKICB4bGFiKHRlc3Rfc3RhdGlzdGljX25hbWUpICsKICBnZ3RpdGxlKGdsdWUoCiAgICAie2xhYmVsX2NvbH0gPSB7bGFiZWx9XG57dGVzdF9zdGF0aXN0aWNfbmFtZX0gPSB7cm91bmQodGVzdF9zdGF0aXN0aWMoWCwgbGFiZWwpLCAzKX0iCiAgICApKQpgYGAKIyMgRGlmZmVyZW5jZSBiZXR3ZWVuIG1lYW5zIC0gc2luZ2xlIHZhcmlhYmxlCgpgYGB7cn0KbGFiZWwgPC0gIkxhY1otMyIKdGVzdF9zdGF0aXN0aWMgPC0gbWVhbl9kaWZmCnRlc3Rfc3RhdGlzdGljX25hbWUgPC0gIm1lYW5fZGlmZiIKcGF0dGVybiA8LSAiQ2VsbHNfQXJlYVNoYXBlX1plcm5pa2VfMF8wIgoKcnVuX3Rlc3QoWCA9IFgsCiAgICAgICAgIGxhYmVsID0gbGFiZWwsCiAgICAgICAgIHRlc3Rfc3RhdGlzdGljID0gdGVzdF9zdGF0aXN0aWMsCiAgICAgICAgIG4gPSBuLAogICAgICAgICBsYWJlbF9jb2wgPSBsYWJlbF9jb2wsCiAgICAgICAgIHBhdHRlcm4gPSBwYXR0ZXJuKSArCiAgeGxhYih0ZXN0X3N0YXRpc3RpY19uYW1lKSArCiAgZ2d0aXRsZShnbHVlKAogICAgIntsYWJlbF9jb2x9ID0ge2xhYmVsfVxue3Rlc3Rfc3RhdGlzdGljX25hbWV9ID0ge3JvdW5kKHRlc3Rfc3RhdGlzdGljKFgsIGxhYmVsKSwgMyl9IgogICAgKSkKYGBgCgoKYGBge3J9CmxhYmVsIDwtICJDREs0LTIiCnRlc3Rfc3RhdGlzdGljIDwtIG1lYW5fZGlmZgp0ZXN0X3N0YXRpc3RpY19uYW1lIDwtICJtZWFuX2RpZmYiCnBhdHRlcm4gPC0gIkNlbGxzX0FyZWFTaGFwZV9aZXJuaWtlXzBfMCIKCnJ1bl90ZXN0KFggPSBYLAogICAgICAgICBsYWJlbCA9IGxhYmVsLAogICAgICAgICB0ZXN0X3N0YXRpc3RpYyA9IHRlc3Rfc3RhdGlzdGljLAogICAgICAgICBuID0gbiwKICAgICAgICAgbGFiZWxfY29sID0gbGFiZWxfY29sLAogICAgICAgICBwYXR0ZXJuID0gcGF0dGVybikgKwogIHhsYWIodGVzdF9zdGF0aXN0aWNfbmFtZSkgKwogIGdndGl0bGUoZ2x1ZSgKICAgICJ7bGFiZWxfY29sfSA9IHtsYWJlbH1cbnt0ZXN0X3N0YXRpc3RpY19uYW1lfSA9IHtyb3VuZCh0ZXN0X3N0YXRpc3RpYyhYLCBsYWJlbCksIDMpfSIKICAgICkpCmBgYAoK