Intro/Overview

This notebook documents the strategy for segregating linked markers from individuals and forming hybrids.

Here we ensure that we load each of the functions:

rfiles <- dir("R", full.names = TRUE)
dump <- lapply(rfiles, source)

Randomize Haplotypes in Founders

We are going to assume no LD (cuz our selected SNPs are quite far apart) so, to initialize our simulations we are going to need to randomly assign alleles to one haplotype or another in the founder individuals.

This is going to work a little like tablify_nh() in that we will pass in a tidy data frame and a list of variants, and then go from there. So, let’s get some data to work on:

library(tidyverse)
TT <- readRDS("intermediates/02/test_and_train.rds")
MM <- readRDS("intermediates/02/marker_rankings.rds")

So, the function looks like:


#' Randomly assign (no-LD) markers to one chromosome or another in a founder individual
#' 
#' blah blah
#' @param D a tidy data frame with at least the columns: id, variant, gene_copy, allele, chrom, coord, pop
#' @param V a vector of variant names giving which variants to use and the order they are
#' to be in.
#' @return A data frame with all the identifiers as before, but now the alleles are in two 
#' columns: hap1 and hap2, which are the alleles as if they occurred together on different
#' homologous chromosomes.  This gets sorted by id, chrom, and coord.
scramble_founder_haplotypes <- function(D, V) {
  # make one column for each gene copy
  D1 <- D %>%
    ungroup() %>%
    filter(variant %in% V) %>%
    tidyr::spread(data = ., key = gene_copy, value = allele) %>%
    mutate(segind = sample(x = c(T,F), size = n(), replace = TRUE),
           hap1 = ifelse(segind, `1`, `2`),
           hap2 = ifelse(segind, `2`, `1`)) %>%
    select(-`1`, -`2`, -segind) %>%
    arrange(id, chrom, coord)
  
}

Let’s see how it works:

Test <- TT %>%
  filter(test_or_train == "test")
v1000 <- MM %>%
  filter(selectable == TRUE & cumsum(selectable) <= 1000) %>%
  .$variant
SFG <- scramble_founder_haplotypes(Test, v1000)

Now, SFG is ready to have some gametes segregated.

Segregate gametes

In order to maintain maximal sample sizes whilst not incurring the sort of inflation of perceived accuracy that comes from sampling with replacement from the genes in the Test group we are going to segregate two gametes from each individual. These will be the opposites of one another—so, effectively all of the genetic material in an individual is getting segregated into the two gametes. (In effect there is no genetic drift in this type of sampling exercise…). The variance in what gets segregated around might be a little greater because the two gametes are not independent, but that is not going to bias the accuracy simulations.

This function is going to work on a data frame like SFG that has a hap1 and a hap2 column. It will create new columns gam1 and gam2 (gam is short for gamete there). We just give a chance of recombination between each pair of markers.

Here is what the function looks like

And this is what it looks like when we use it:

SG <- segregate_gametes(H = SFG)

Reproduction

Now all we need is a function that will combine the gametes of randomly chosen individuals into new individuals. THis is an interesting problem. We would like to make use of all the genetic material that we have from all the test individuals. We will be constrained by what types of hybrid categories we are making and the sizes of the test samples from the different populations.

arrange_matings

The way I am going to approach this is to create a function arrange_matings() that returns a data frame that describes which gametes get passed on to which individuals.

Let’s first get some data sets that we will be passing into arrange_matings().

tmp <- TT %>%
  filter(test_or_train == "test") %>%
  group_by(id, pop, group) %>%
  tally() %>%
  ungroup() %>%
  select(-n)
A <- tmp %>% filter(group == "wild")
B <- tmp %>% filter(group == "farmed")

Let’s write it:

#' return list of  who mates with whom to create hybrid offspring.  This will create F1's between 
#' the A and the B group.
#' @param A a data frame with id, pop, and group.  Should be exclusively from one of the groups (i.e. wild)
#' @param B a data frame with id and pop, and group.  Should be from the other group (i.e. farmed)
#' @return a data frame with columns id, popA, popB, idA gamA idB gamB that tells us which gametes from
#' which individuals each new individual is to be made of.
arrange_matings <- function(A, B) {
  
  n <-  min(nrow(A), nrow(B)) 
  Atib <- tibble(id = rep(sample(A$id, size = n, replace = FALSE), each = 2))  %>%
    mutate(gam = rep(c(1,2), length.out = n())) %>%
    sample_n(size = nrow(.), replace = FALSE)
  Btib <- tibble(id = rep(sample(B$id, size = n, replace = FALSE), each = 2))  %>%
    mutate(gam = rep(c(1,2), length.out = n())) %>%
    sample_n(size = nrow(.), replace = FALSE)
  
  
  Atib2 <- left_join(Atib, A)
  names(Atib2) <- paste(names(Atib2), "A", sep = "_")
  Btib2 <- left_join(Btib, B)
  names(Btib2) <- paste(names(Btib2), "B", sep = "_")
  
  F1s <- bind_cols(Atib2, Btib2) %>%
    mutate(id = paste("F1", 1:n(), sep = "_")) %>%
    select(id, everything())
  
  return(F1s)
}

Now that we have that function, we can create a mating list for F1s like this:

arrange_matings(A, B)
Joining, by = "id"
Joining, by = "id"

Some data to play with

We need some scrambled founder data for the following functions for testing and demo. So, here it is:

Wf <- SFG %>%
  filter(group == "wild")
Ff <- SFG %>%
  filter(group == "farmed")

make_f1s

And so, we can make a function to make F1’s like this:

#' @param Wf a data frame of founders with scrambled haplotypes in hap1 and hap2
#' @param Ff a data frame of founders (from the other species/population) 
#' with scrambled haplotypes in hap1 and hap2
make_f1s <- function(Wf, Ff) {
  A <- Wf %>%
    count(id) %>%
    select(-n)
  B <- Ff %>%
    count(id) %>%
    select(-n)
  
  ML <- arrange_matings(A, B) # get the list of matings
  
  Wft <- Wf %>%
    select(id, variant, chrom, coord, hap1, hap2) %>%
    rename(id_A = id, `1` = hap1, `2` = hap2) %>%
    tidyr::gather(data = ., key = "gam_A", value = "hap1", `1`, `2`) %>%
    mutate(gam_A = as.numeric(gam_A))
  
  Fft <- Ff %>%
    select(id, variant, chrom, coord, hap1, hap2) %>%
    rename(id_B = id, `1` = hap1, `2` = hap2) %>%
    tidyr::gather(data = ., key = "gam_B", value = "hap2", `1`, `2`) %>%
    mutate(gam_B = as.numeric(gam_B))
  
  left_join(ML, Wft) %>%
    left_join(., Fft)
  
}

Then we can use that to make a bunch of F1’s and immediately segrate some haplotypes from them.

newF1s <- make_f1s(Wf, Ff) %>%
  segregate_gametes()
Joining, by = "id"
Joining, by = "id"
Joining, by = c("id_A", "gam_A")
Joining, by = c("id_B", "gam_B", "variant", "chrom", "coord")
newF1s[1:100,]

That is cool. The harder part is the later generations, but not so bad, it turns out.

make_f2s

This function will take the same input as make_f1s() but then it will split the result into two data frames and make F2s from it.

make_f2s <- function(Wf, Ff) {
  F1 <- make_f1s(Wf, Ff) %>%
    segregate_gametes()
  
  # now, make two new data frames that we will combine using make_F1s into F2s
  ids <- sample(unique(F1$id))  # permute all the ids around
  if (length(ids) %% 2 == 1) {  # if there is an odd number of F1s, then drop the first one
    ids <- ids[-1]
  }
  idA <- ids[1:(length(ids) / 2)]
  idB <- ids[(1 + length(ids) / 2):length(ids)]
  
  # now make new Wf and new Ff and run them back through make_F1s
  F1_clean <- F1 %>%
    select(-ends_with("_A"), -ends_with("_B"))
  
  newWf <- F1_clean %>% filter(id %in% idA)
  newFf <- F1_clean %>% filter(id %in% idB)
  
  F2 <- make_f1s(newWf, newFf) %>%
    mutate(id = stringr::str_replace(id, "^F1", "F2"))
  
}

And we can use that thing like this:

newF2s <- make_f2s(Wf, Ff)
Joining, by = "id"
Joining, by = "id"
Joining, by = c("id_A", "gam_A")
Joining, by = c("id_B", "gam_B", "variant", "chrom", "coord")
Joining, by = "id"
Joining, by = "id"
Joining, by = c("id_A", "gam_A")
Joining, by = c("id_B", "gam_B", "variant", "chrom", "coord")
newF2s[1:100, ]

make backcrosses

This is a single function that will make backcrosses to the first population (the A population). If you want to make backcrosses in the other direction, you will just need to call it with the populations in a different order. This is also going to be a wasteful function: it is not going to work hard to make pures out of any extra individuals, etc. It is going to make as many backcrosses as it can, and no more. It is also not going to be population aware—that all has to be done on the front end if you want to make sure that backcrosses are all within the same population, for example.

Before we launch into this, let’s talk about how many backcrosses you can make. Imagine that you are going to be backcrossing to the \(A\) population which has \(n_A\) test indivs. And the other population is the \(B\) population which has \(n_B\) individuals in it. To figure out how many A-backcross indivs you can make without reusing any of your gene copies, you start by computing \(M = \min(\lfloor n_a/3 \rfloor, n_b)\). Then the way you do this is

  1. Hold out \(2M\) of the \(n_a\) individuals for backcrossing later
  2. Make \(2M\) F1s out of \(M\) \(A\)’s and \(M\) \(B\)’s
  3. Mate the \(2M\) \(A\)’s to the \(2M\) F1’s to get \(4M\) backcrosses.

So, you can get \(4M\) backcrosses out of it.

Let’s write a function that does this:

#' @param idstr string that gets prepended to the number of each individual
make_bxs <- function(Wf, Ff, idstr = "BX") {
  A <- Wf %>%
    count(id) %>%
    select(-n)
  B <- Ff %>%
    count(id) %>%
    select(-n)
  
  M <- min(floor(nrow(A)/3), nrow(B))
  
  Aids <- sample(A$id) # permute these
  Bids <- sample(B$id) # permute these too
  
  Ai1 <- Aids[1:M]  # ids of A's for F1 creation
  Bi1 <- Bids[1:M]  # ids of B's for F1 creation
  
  Ai2 <- Aids[(M + 1):(3 * M)]  # ids of A's for making backcrosses
  
  # make the F1s and segregate em 
  F1 <- make_f1s(Wf %>% filter(id %in% Ai1), 
                 Ff %>% filter(id %in% Bi1)) %>%
    segregate_gametes()
  
  # now, make F1's between the F1s and the Ai2's
  BX <- make_f1s(Wf %>% filter(id %in% Ai2), F1) %>%
    mutate(id = stringr::str_replace(id, "^F1", idstr))
  
}

And we can use that like this:

newBXs <- make_bxs(Wf, Ff)
Joining, by = "id"
Joining, by = "id"
Joining, by = c("id_A", "gam_A")
Joining, by = c("id_B", "gam_B", "variant", "chrom", "coord")
Joining, by = "id"
Joining, by = "id"
Joining, by = c("id_A", "gam_A")
Joining, by = c("id_B", "gam_B", "variant", "chrom", "coord")
newBXs[1:100,]
LS0tCnRpdGxlOiAiU2VncmVnYXRpb24gYW5kIFJlcHJvZHVjdGlvbiIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CiMgc2V0IHRoZSB3b3JraW5nIGRpcmVjdG9yeSBhbHdheXMgdG8gdGhlIHByb2plY3QgZGlyZWN0b3J5IChvbmUgbGV2ZWwgdXApCmtuaXRyOjpvcHRzX2tuaXQkc2V0KHJvb3QuZGlyID0gbm9ybWFsaXplUGF0aChycHJvanJvb3Q6OmZpbmRfcnN0dWRpb19yb290X2ZpbGUoKSkpIApgYGAKCgojIyBJbnRyby9PdmVydmlldwoKVGhpcyBub3RlYm9vayBkb2N1bWVudHMgdGhlIHN0cmF0ZWd5IGZvciBzZWdyZWdhdGluZyBsaW5rZWQgbWFya2VycyBmcm9tCmluZGl2aWR1YWxzIGFuZCBmb3JtaW5nIGh5YnJpZHMuCgpIZXJlIHdlIGVuc3VyZSB0aGF0IHdlIGxvYWQgZWFjaCBvZiB0aGUgZnVuY3Rpb25zOgpgYGB7cn0KcmZpbGVzIDwtIGRpcigiUiIsIGZ1bGwubmFtZXMgPSBUUlVFKQpkdW1wIDwtIGxhcHBseShyZmlsZXMsIHNvdXJjZSkKYGBgCgojIyBSYW5kb21pemUgSGFwbG90eXBlcyBpbiBGb3VuZGVycwoKV2UgYXJlIGdvaW5nIHRvIGFzc3VtZSBubyBMRCAoY3V6IG91ciBzZWxlY3RlZCBTTlBzIGFyZSBxdWl0ZSBmYXIgYXBhcnQpCnNvLCB0byBpbml0aWFsaXplIG91ciBzaW11bGF0aW9ucyB3ZSBhcmUgZ29pbmcgdG8gbmVlZCB0byByYW5kb21seSBhc3NpZ24KYWxsZWxlcyB0byBvbmUgaGFwbG90eXBlIG9yIGFub3RoZXIgaW4gdGhlIGZvdW5kZXIgaW5kaXZpZHVhbHMuIAoKVGhpcyBpcyBnb2luZyB0byB3b3JrIGEgbGl0dGxlIGxpa2UgYHRhYmxpZnlfbmgoKWAgaW4gdGhhdCB3ZSB3aWxsIHBhc3MgaW4KYSB0aWR5IGRhdGEgZnJhbWUgYW5kIGEgbGlzdCBvZiB2YXJpYW50cywgYW5kIHRoZW4gZ28gZnJvbSB0aGVyZS4KU28sIGxldCdzIGdldCBzb21lIGRhdGEgdG8gd29yayBvbjoKYGBge3IgZ2V0LWRhdGF9CmxpYnJhcnkodGlkeXZlcnNlKQpUVCA8LSByZWFkUkRTKCJpbnRlcm1lZGlhdGVzLzAyL3Rlc3RfYW5kX3RyYWluLnJkcyIpCk1NIDwtIHJlYWRSRFMoImludGVybWVkaWF0ZXMvMDIvbWFya2VyX3JhbmtpbmdzLnJkcyIpCmBgYAoKU28sIHRoZSBmdW5jdGlvbiBsb29rcyBsaWtlOgpgYGByCmByIHBhc3RlKHJlYWRMaW5lcygiUi9zY3JhbWJsZV9mb3VuZGVyX2hhcGxvdHlwZXMuUiIpLCBjb2xsYXBzZSA9ICJcbiIpYApgYGAKCkxldCdzIHNlZSBob3cgaXQgd29ya3M6CmBgYHtyIGRlbW8tc2NyYW1ibGV9ClRlc3QgPC0gVFQgJT4lCiAgZmlsdGVyKHRlc3Rfb3JfdHJhaW4gPT0gInRlc3QiKQp2MTAwMCA8LSBNTSAlPiUKICBmaWx0ZXIoc2VsZWN0YWJsZSA9PSBUUlVFICYgY3Vtc3VtKHNlbGVjdGFibGUpIDw9IDEwMDApICU+JQogIC4kdmFyaWFudAoKU0ZHIDwtIHNjcmFtYmxlX2ZvdW5kZXJfaGFwbG90eXBlcyhUZXN0LCB2MTAwMCkKYGBgCk5vdywgYFNGR2AgaXMgcmVhZHkgdG8gaGF2ZSBzb21lIGdhbWV0ZXMgc2VncmVnYXRlZC4KCiMjIFNlZ3JlZ2F0ZSBnYW1ldGVzCgpJbiBvcmRlciB0byBtYWludGFpbiBtYXhpbWFsIHNhbXBsZSBzaXplcyB3aGlsc3Qgbm90IGluY3VycmluZyB0aGUgc29ydCBvZiAKaW5mbGF0aW9uIG9mIHBlcmNlaXZlZCBhY2N1cmFjeSB0aGF0IGNvbWVzIGZyb20gc2FtcGxpbmcgd2l0aCByZXBsYWNlbWVudApmcm9tIHRoZSBnZW5lcyBpbiB0aGUgVGVzdCBncm91cCB3ZSBhcmUgZ29pbmcgdG8gc2VncmVnYXRlIHR3byBnYW1ldGVzIGZyb20gCmVhY2ggaW5kaXZpZHVhbC4gIFRoZXNlIHdpbGwgYmUgdGhlIG9wcG9zaXRlcyBvZiBvbmUgYW5vdGhlci0tLXNvLCBlZmZlY3RpdmVseSBhbGwKb2YgdGhlIGdlbmV0aWMgbWF0ZXJpYWwgaW4gYW4gaW5kaXZpZHVhbCBpcyBnZXR0aW5nIHNlZ3JlZ2F0ZWQgaW50byB0aGUgdHdvIGdhbWV0ZXMuCihJbiBlZmZlY3QgdGhlcmUgaXMgbm8gZ2VuZXRpYyBkcmlmdCBpbiB0aGlzIHR5cGUgb2Ygc2FtcGxpbmcgZXhlcmNpc2UuLi4pLiAgVGhlIHZhcmlhbmNlCmluIHdoYXQgZ2V0cyBzZWdyZWdhdGVkIGFyb3VuZCBtaWdodCBiZSBhIGxpdHRsZSBncmVhdGVyIGJlY2F1c2UgdGhlIHR3byBnYW1ldGVzIGFyZQpub3QgaW5kZXBlbmRlbnQsIGJ1dCB0aGF0IGlzIG5vdCBnb2luZyB0byBiaWFzIHRoZSBhY2N1cmFjeSBzaW11bGF0aW9ucy4gIAoKVGhpcyBmdW5jdGlvbiBpcyBnb2luZyB0byB3b3JrIG9uIGEgZGF0YSBmcmFtZSBsaWtlIFNGRyB0aGF0IGhhcyBhIGBoYXAxYCBhbmQgYSBgaGFwMmAKY29sdW1uLiAgSXQgd2lsbCBjcmVhdGUgbmV3IGNvbHVtbnMgYGdhbTFgIGFuZCBgZ2FtMmAgKGdhbSBpcyBzaG9ydCBmb3IgZ2FtZXRlIHRoZXJlKS4KV2UganVzdCBnaXZlIGEgY2hhbmNlIG9mIHJlY29tYmluYXRpb24gYmV0d2VlbiBlYWNoIHBhaXIgb2YgbWFya2Vycy4KCkhlcmUgaXMgd2hhdCB0aGUgZnVuY3Rpb24gbG9va3MgbGlrZQoKQW5kIHRoaXMgaXMgd2hhdCBpdCBsb29rcyBsaWtlIHdoZW4gd2UgdXNlIGl0OgpgYGB7ciB1c2Utc2VnLWdhbX0KU0cgPC0gc2VncmVnYXRlX2dhbWV0ZXMoSCA9IFNGRykKYGBgCgojIyBSZXByb2R1Y3Rpb24KCk5vdyBhbGwgd2UgbmVlZCBpcyBhIGZ1bmN0aW9uIHRoYXQgd2lsbCBjb21iaW5lIHRoZSBnYW1ldGVzIG9mIHJhbmRvbWx5IGNob3NlbiBpbmRpdmlkdWFscyAKaW50byBuZXcgaW5kaXZpZHVhbHMuIFRIaXMgaXMgYW4gaW50ZXJlc3RpbmcgcHJvYmxlbS4gIFdlIHdvdWxkIGxpa2UgdG8gbWFrZSB1c2Ugb2YKYWxsIHRoZSBnZW5ldGljIG1hdGVyaWFsIHRoYXQgd2UgaGF2ZSBmcm9tIGFsbCB0aGUgdGVzdCBpbmRpdmlkdWFscy4gIFdlIHdpbGwgYmUKY29uc3RyYWluZWQgYnkgd2hhdCB0eXBlcyBvZiBoeWJyaWQgY2F0ZWdvcmllcyB3ZSBhcmUgbWFraW5nIGFuZCB0aGUgc2l6ZXMgb2YgdGhlCnRlc3Qgc2FtcGxlcyBmcm9tIHRoZSBkaWZmZXJlbnQgcG9wdWxhdGlvbnMuICAKCiMjIyBhcnJhbmdlX21hdGluZ3MKClRoZSB3YXkgSSBhbSBnb2luZyB0byBhcHByb2FjaCB0aGlzIGlzIHRvIGNyZWF0ZSBhIGZ1bmN0aW9uIGBhcnJhbmdlX21hdGluZ3MoKWAgdGhhdApyZXR1cm5zIGEgZGF0YSBmcmFtZSB0aGF0IGRlc2NyaWJlcyB3aGljaCBnYW1ldGVzIGdldCBwYXNzZWQgb24gdG8gd2hpY2ggaW5kaXZpZHVhbHMuCgpMZXQncyBmaXJzdCBnZXQgc29tZSBkYXRhIHNldHMgdGhhdCB3ZSB3aWxsIGJlIHBhc3NpbmcgaW50byBgYXJyYW5nZV9tYXRpbmdzKClgLgpgYGB7ciB0cmlhbC1kYXRhfQp0bXAgPC0gVFQgJT4lCiAgZmlsdGVyKHRlc3Rfb3JfdHJhaW4gPT0gInRlc3QiKSAlPiUKICBncm91cF9ieShpZCwgcG9wLCBncm91cCkgJT4lCiAgdGFsbHkoKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgc2VsZWN0KC1uKQoKQSA8LSB0bXAgJT4lIGZpbHRlcihncm91cCA9PSAid2lsZCIpCkIgPC0gdG1wICU+JSBmaWx0ZXIoZ3JvdXAgPT0gImZhcm1lZCIpCmBgYApMZXQncyB3cml0ZSBpdDoKYGBgcgpgciBwYXN0ZShyZWFkTGluZXMoIlIvYXJyYW5nZV9tYXRpbmdzLlIiKSwgY29sbGFwc2UgPSAiXG4iKWAKYGBgCgpOb3cgdGhhdCB3ZSBoYXZlIHRoYXQgZnVuY3Rpb24sIHdlIGNhbiBjcmVhdGUgYSBtYXRpbmcgbGlzdCBmb3IgRjFzIGxpa2UgdGhpczoKYGBge3IgZGVtby1hcnJhbmdlfQphcnJhbmdlX21hdGluZ3MoQSwgQikKYGBgCgojIyMgU29tZSBkYXRhIHRvIHBsYXkgd2l0aAoKV2UgbmVlZCBzb21lIHNjcmFtYmxlZCBmb3VuZGVyIGRhdGEgZm9yIHRoZSBmb2xsb3dpbmcgZnVuY3Rpb25zIGZvciB0ZXN0aW5nIAphbmQgZGVtby4gIFNvLCBoZXJlIGl0IGlzOgpgYGB7ciBtYWtlLWRlbW8tZGF0YX0KV2YgPC0gU0ZHICU+JQogIGZpbHRlcihncm91cCA9PSAid2lsZCIpCkZmIDwtIFNGRyAlPiUKICBmaWx0ZXIoZ3JvdXAgPT0gImZhcm1lZCIpCmBgYAojIyMgbWFrZV9mMXMKQW5kIHNvLCB3ZSBjYW4gbWFrZSBhIGZ1bmN0aW9uIHRvIG1ha2UgRjEncyBsaWtlIHRoaXM6CmBgYHIKYHIgcGFzdGUocmVhZExpbmVzKCJSL21ha2VfZjFzLlIiKSwgY29sbGFwc2UgPSAiXG4iKWAKYGBgCgpUaGVuIHdlIGNhbiB1c2UgdGhhdCB0byBtYWtlIGEgYnVuY2ggb2YgRjEncyBhbmQgaW1tZWRpYXRlbHkgc2VncmF0ZSBzb21lIGhhcGxvdHlwZXMgZnJvbSB0aGVtLgpgYGB7ciBkZW1vX2Yxc30KbmV3RjFzIDwtIG1ha2VfZjFzKFdmLCBGZikgJT4lCiAgc2VncmVnYXRlX2dhbWV0ZXMoKQoKbmV3RjFzWzE6MTAwLF0KYGBgCgpUaGF0IGlzIGNvb2wuICBUaGUgaGFyZGVyIHBhcnQgaXMgdGhlIGxhdGVyIGdlbmVyYXRpb25zLCBidXQgbm90IHNvIGJhZCwKaXQgdHVybnMgb3V0LgoKIyMjIG1ha2VfZjJzCgpUaGlzIGZ1bmN0aW9uIHdpbGwgdGFrZSB0aGUgc2FtZSBpbnB1dCBhcyBgbWFrZV9mMXMoKWAgYnV0IHRoZW4gaXQgd2lsbCAKc3BsaXQgdGhlIHJlc3VsdCBpbnRvIHR3byBkYXRhIGZyYW1lcyBhbmQgbWFrZSBGMnMgZnJvbSBpdC4KYGBgcgpgciBwYXN0ZShyZWFkTGluZXMoIlIvbWFrZV9mMnMuUiIpLCBjb2xsYXBzZSA9ICJcbiIpYApgYGAKCkFuZCB3ZSBjYW4gdXNlIHRoYXQgdGhpbmcgbGlrZSB0aGlzOgpgYGB7ciBkZW1vLWYyfQpuZXdGMnMgPC0gbWFrZV9mMnMoV2YsIEZmKQpuZXdGMnNbMToxMDAsIF0KYGBgCgojIyMgbWFrZSBiYWNrY3Jvc3NlcwoKVGhpcyBpcyBhIHNpbmdsZSBmdW5jdGlvbiB0aGF0IHdpbGwgbWFrZSBiYWNrY3Jvc3NlcyB0byB0aGUgZmlyc3QgcG9wdWxhdGlvbiAodGhlIEEgcG9wdWxhdGlvbikuCklmIHlvdSB3YW50IHRvIG1ha2UgYmFja2Nyb3NzZXMgaW4gdGhlIG90aGVyIGRpcmVjdGlvbiwgeW91IHdpbGwganVzdCBuZWVkIHRvIApjYWxsIGl0IHdpdGggdGhlIHBvcHVsYXRpb25zIGluIGEgZGlmZmVyZW50IG9yZGVyLiAgVGhpcyBpcyBhbHNvIGdvaW5nIHRvIGJlIGEgd2FzdGVmdWwgZnVuY3Rpb246Cml0IGlzIG5vdCBnb2luZyB0byB3b3JrIGhhcmQgdG8gbWFrZSBwdXJlcyBvdXQgb2YgYW55IGV4dHJhIGluZGl2aWR1YWxzLCBldGMuICBJdCBpcyBnb2luZyB0byBtYWtlCmFzIG1hbnkgYmFja2Nyb3NzZXMgYXMgaXQgY2FuLCBhbmQgbm8gbW9yZS4gSXQgaXMgYWxzbyBub3QgZ29pbmcgdG8gYmUgcG9wdWxhdGlvbiBhd2FyZS0tLXRoYXQgYWxsCmhhcyB0byBiZSBkb25lIG9uIHRoZSBmcm9udCBlbmQgaWYgeW91IHdhbnQgdG8gbWFrZSBzdXJlIHRoYXQgYmFja2Nyb3NzZXMgYXJlIGFsbCB3aXRoaW4gdGhlIHNhbWUKcG9wdWxhdGlvbiwgZm9yIGV4YW1wbGUuCgpCZWZvcmUgd2UgbGF1bmNoIGludG8gdGhpcywgbGV0J3MgdGFsayBhYm91dCBob3cgbWFueSBiYWNrY3Jvc3NlcyB5b3UgY2FuIG1ha2UuICBJbWFnaW5lIHRoYXQgeW91IGFyZQpnb2luZyB0byBiZSBiYWNrY3Jvc3NpbmcgdG8gdGhlICRBJCBwb3B1bGF0aW9uIHdoaWNoIGhhcyAkbl9BJCB0ZXN0IGluZGl2cy4gIEFuZCB0aGUgb3RoZXIgcG9wdWxhdGlvbgppcyB0aGUgJEIkIHBvcHVsYXRpb24gd2hpY2ggaGFzICRuX0IkIGluZGl2aWR1YWxzIGluIGl0LiAgVG8gZmlndXJlIG91dCBob3cgbWFueSBBLWJhY2tjcm9zcyAKaW5kaXZzIHlvdSBjYW4gbWFrZSB3aXRob3V0IHJldXNpbmcgYW55IG9mIHlvdXIgZ2VuZSBjb3BpZXMsIHlvdSBzdGFydCBieSBjb21wdXRpbmcKJE0gPSBcbWluKFxsZmxvb3Igbl9hLzMgXHJmbG9vciwgbl9iKSQuICBUaGVuIHRoZSB3YXkgeW91IGRvIHRoaXMgaXMKCjEuIEhvbGQgb3V0ICQyTSQgb2YgdGhlICRuX2EkIGluZGl2aWR1YWxzIGZvciBiYWNrY3Jvc3NpbmcgbGF0ZXIKMi4gTWFrZSAkMk0kIEYxcyBvdXQgb2YgJE0kICRBJCdzIGFuZCAkTSQgJEIkJ3MKMy4gTWF0ZSB0aGUgJDJNJCAkQSQncyB0byB0aGUgJDJNJCBGMSdzIHRvIGdldCAkNE0kIGJhY2tjcm9zc2VzLgoKU28sIHlvdSBjYW4gZ2V0ICQ0TSQgYmFja2Nyb3NzZXMgb3V0IG9mIGl0LgoKTGV0J3Mgd3JpdGUgYSBmdW5jdGlvbiB0aGF0IGRvZXMgdGhpczoKYGBgcgpgciBwYXN0ZShyZWFkTGluZXMoIlIvbWFrZV9ieHMuUiIpLCBjb2xsYXBzZSA9ICJcbiIpYApgYGAKCkFuZCB3ZSBjYW4gdXNlIHRoYXQgbGlrZSB0aGlzOgpgYGB7ciBkZW1vLWJ4fQpuZXdCWHMgPC0gbWFrZV9ieHMoV2YsIEZmKQpuZXdCWHNbMToxMDAsXQpgYGA=