Intro/Overview

This notebook documents the strategy for

  1. selecting (typically at random) individuals to serve as Training Set individuals to choose markers with large allele freq differences between farmed and wild salmon
  2. then, once the training set is available, doing the actual locus selection in a way that takes the most differentiated SNPs from each linkage block.
  3. Finally creating data objects of the Test Set individuals with haplotypes ready to simulate into offspring.

I had originally planned on doing this by using PLINK to quickly compute Fst’s for each locus, but I ultimately decided to keep it all in a tidy Hadley-esque framework. Accordingly, I will be selecting SNPs on the expected rate of correct assignment of an allele to one group or the other—this seems to be a relevant measure anyway, since what one is doing when inferring hybrids is assigning gene copies to one group or the other.

library(tidyverse)
Loading tidyverse: ggplot2
Loading tidyverse: tibble
Loading tidyverse: tidyr
Loading tidyverse: readr
Loading tidyverse: purrr
Loading tidyverse: dplyr
Use suppressPackageStartupMessages() to eliminate package startup messages.
Conflicts with tidy packages --------------------------------------------------------------------------------
filter(): dplyr, stats
lag():    dplyr, stats
library(stringr)
# and source the files that hold functions we will be using
source("R/assign_train_test.R")
source("R/compute_epp.R")
source("R/marker_selection.R")

Training Set Selection

The AQU and WLN groups are the farmed fish in each data set. So, we can get the data and put them into different groups etc. I am going to develop this with west and wrap it into a function later.

dat <- readRDS("intermediates/01/tidy-west.rds") %>%
  mutate(pop = str_sub(id, 1, 3),
         group = ifelse(pop == "AQU" | pop == "WLN", "farmed", "wild"))

This function assigns a fraction of fish to a training and a test group.

#' assign to training and test
#'
#' Simple version that does a fixed fraction from each pop
#' @param dat data frame with columns id, variant, gene_copy, allele, 
#' chrom, coord, pop, and group.  
#' @param train_fract what fraction of each pop should be training (the rest is test)
#' @return A data frame just like dat, but with a column called "test_or_train" that
#' has entries of "test" or "train" as appropriate for each fish id.
assign_train_test <- function(dat, train_fract = 0.5) {
  d2 <- dat %>%
    distinct(id, pop, group) %>%
    group_by(group, pop) %>%
  mutate(test_or_train = sample(c(rep("train", ceiling(train_fract * n())),
                                  rep("test", n() - ceiling(train_fract * n()))
                                  )))
         
  left_join(dat, d2) 
}

Marker ranking and selection

Computing the ranking criterion

We are going to rank markers on the basis of the expected posterior probability with which an allele would be assigned to the correct group. Let \(p_1\) and \(p_2\) be the frequencies of the \(A\) allele at a SNP in two populations, 1 and 2. The other allele, say \(a\) has frequencies of \(1-p_1\) and \(1-p_2\) in the populations. If an allele is drawn and is type \(A\), then the likelihood it came from population 1 is \(p_1\) and from population 2 is \(p_2\), and if we had an equal prior on the origin of the gene copy the posterior that it came from population 1 would be \(p_1/(p_1 + p_2)\). So the expected posterior probability with which a gene copy from population 1 would be assigned to population 1 is simply: \[ E_1 = \frac{p_1^2}{p_1 + p_2} + \frac{(1-p_1)^2}{2 - (p_1 + p_2)} \] and for a gene copy that was actually from population 2 we would have: \[ E_2 = \frac{p_2^2}{p_1 + p_2} + \frac{(1-p_2)^2}{2 - (p_1 + p_2)}. \] So, let us propose as a statistic for ranking loci for their utility for NewHybrids analysis what we call epp: \[ \mathrm{epp} = \frac{E_1 + E_2}{2} \]

To do this we are going to need to

  1. compute the allele frequencies. And we will add half of an allele to each pile (essentially a unit information prior) when we do it.
  2. compute the epp

Here is a function to do that:

#' @param D a data frame like that returned by assign_train_test()
#' @return a data frame that includes variant, chrom, coord, allele then has
#' two columns of allele counts (farmed and wild) taken only from the "train"
#' individuals and then two columns of allele freqs (with the 1/2 correction
#' in there) and  epp computed from those allele freqs.
compute_epp <- function(D) {
  D %>%
    filter(test_or_train == "train") %>%
    group_by(chrom, coord, variant, group, allele) %>%
    summarise(counts = n()) %>%
    tidyr::spread(data = ., key = group, value = counts, fill = 0) %>%
    rename(farmed_cnts = farmed,
           wild_cnts = wild) %>%
    group_by(chrom, coord, variant) %>%
    mutate(farmed_freq = (farmed_cnts + 1/2) / sum(farmed_cnts + 1/2),
           wild_freq = (wild_cnts + 1/2) / sum(wild_cnts + 1/2),
           epp_f = farmed_freq ^ 2 / (farmed_freq + wild_freq),  # epp_f and epp_w are just intermediate allele-specific
           epp_w = wild_freq ^ 2 / (farmed_freq + wild_freq),    # terms that make the calculation easier, here
           epp = (sum(epp_f) + sum(epp_w)) / 2)   %>%
    select(-epp_f, -epp_w)
}

OK, that’s that. So, let’s do it:

set.seed(1234)  # for reproducibility during development
A <- assign_train_test(dat)
Joining, by = c("id", "pop", "group")
R <- compute_epp(A)

Something to note here is that I think the above calculations would also work for multiallelic markers, like haplotypes.

Doing the marker selection

Once we have the ranks of the markers we need to do the selection of them. In doing this we will want to be cognizant of physical linkage. I am not going to screen things out on the basis of LD between loci. My feeling is that if there is LD within a single population between markers that are not very close physically (and hence will be filtered out anyway because of their physical proximity), then those markers will likely be the result of recent strong selection in the aquaculture strain (with very long blocks of LD). In that case, the NewHybrids model is not quite correct, since it assumes independence of parental population allele frequencies. However, since we are simulating things without any LD, I don’t suspect the difference will be all that great. The variance in the posterior probabilities will be reduced a little because we are not simulating with LD, but I think that filtering on the basis of physical proximity is a better bet anyway.

So, our method for marker selection will straightforward:

  1. First, within each chromosome we rank all the variants by epp and then we include the one with the highest epp in the chosen set. When a variant is placed in the chosen set, we add its base-pair coordinates to a set \(B\).
  2. Then move on to the variant with the next highest epp. If its base-pair coordinates are greater than min_dist from any of the previously chosen variants in \(B\), then it will get chosen too, (and its coordindates included in \(B\)).

The above process continues until we have either selected (or not) all the loci on the chromosome/linkage group.

Once all the linkage groups have been processed, we rank all the variants by their epp and select those that were on the “selected” pile from each linkage group.

The choice of min_dist in our case is determined by the sex-averaged map that we have. After sex-averaging the recombination rates, our map changed in units of 0.05 cM, which corresponds to units of 50 Kb on the physical coordinates that we made. This means that any markers that are within 50 Kb of one another are essentially on the same linkage group from the linkage map estimation project. So, we will set min_dist to be 60 Kb. That will ensure that we don’t take more than one marker from each group on the sex-averaged linkage map.

We will make a few functions for this here:

#' first, an internal function to be applied to each chromosome
#' @param co a vector of base pair coordinates ranked descending on epp
#' @param md the min_dist allowed between a new SNP and any other selected ones
ranked_selector <- function(bp, md) {
  N <- length(bp)
  ret <- rep(NA, N)
  ret[1] <- TRUE  # initialize by always choosing the first SNP
  n <- 1  # the number chosen
  B <- bp[1]
  
  if(N==1) return(ret)
    
  for(i in 2:N) {
    if(all(abs(bp[i] - B) > md)) {
      ret[i] <- TRUE
      n <- n + 1
      B[n] <- bp[i]
      
    } else {
      ret[i] <- FALSE
    }
  }
  ret
}


#' @param R a data frame like that returned by compute_epp()
#' @return returns a data frame with chrom, coord, variant, epp, 
#' and a new column "selectable" which is TRUE if it is one that should
#' be selected, and FALSE if it is too close to another, previously selected
#' marker.  It comes back sorted, descending on epp. 
marker_selection <- function(R, min_dist = 6e04) {
  # first we need to whittle R down to only 1 row per variant, and just keep the epp
  # and we want to arrange on chrom then epp
  R1 <- R %>% 
    group_by(chrom, coord, variant) %>%
    summarise(epp = first(epp)) %>%
    ungroup() %>%
    arrange(chrom, desc(epp))
  
  R1 %>% 
    arrange(chrom, desc(epp)) %>%
    group_by(chrom) %>%
    mutate(selectable = ranked_selector(coord, min_dist)) %>%
    ungroup() %>%
    arrange(desc(epp))
}

And this is how we run it:

M <- marker_selection(R, min_dist = 6e04)

It would be interesting to look at those and see how many of them were on the original linkage map, and how many were just “sprinkled in”. Here are the top 1000 loci

# get the sex-averaged map
sa <- readRDS("intermediates/01/sex_averaged_map.rds")
# then add a column "sprinkled"
M %>%
  mutate(sprinkled = !(variant %in% sa$SNP_ID))

Save some files for the next segment

We are going to save the output of test_and_train and the marker selection:

dir.create("intermediates/02", showWarnings = FALSE)
saveRDS(A, file = "intermediates/02/test_and_train.rds", compress = "xz")
saveRDS(M, file = "intermediates/02/marker_rankings.rds", compress = "xz")
LS0tCnRpdGxlOiAiU2VsZWN0aW5nIEluZGl2aWR1YWxzIGFuZCBMb2NpIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyBzZXQgdGhlIHdvcmtpbmcgZGlyZWN0b3J5IGFsd2F5cyB0byB0aGUgcHJvamVjdCBkaXJlY3RvcnkgKG9uZSBsZXZlbCB1cCkKa25pdHI6Om9wdHNfa25pdCRzZXQocm9vdC5kaXIgPSBub3JtYWxpemVQYXRoKHJwcm9qcm9vdDo6ZmluZF9yc3R1ZGlvX3Jvb3RfZmlsZSgpKSkgCmBgYAoKCiMjIEludHJvL092ZXJ2aWV3CgpUaGlzIG5vdGVib29rIGRvY3VtZW50cyB0aGUgc3RyYXRlZ3kgZm9yIAoKMS4gc2VsZWN0aW5nICh0eXBpY2FsbHkgYXQgcmFuZG9tKSBpbmRpdmlkdWFscyB0byBzZXJ2ZSBhcyBUcmFpbmluZyBTZXQgaW5kaXZpZHVhbHMKdG8gY2hvb3NlIG1hcmtlcnMgd2l0aCBsYXJnZSBhbGxlbGUgZnJlcSBkaWZmZXJlbmNlcyBiZXR3ZWVuIGZhcm1lZCBhbmQgd2lsZCBzYWxtb24KMS4gdGhlbiwgb25jZSB0aGUgdHJhaW5pbmcgc2V0IGlzIGF2YWlsYWJsZSwgZG9pbmcgdGhlIGFjdHVhbCBsb2N1cyBzZWxlY3Rpb24gaW4gYSB3YXkKdGhhdCB0YWtlcyB0aGUgbW9zdCBkaWZmZXJlbnRpYXRlZCBTTlBzIGZyb20gZWFjaCBsaW5rYWdlIGJsb2NrLgoxLiBGaW5hbGx5IGNyZWF0aW5nIGRhdGEgb2JqZWN0cyBvZiB0aGUgVGVzdCBTZXQgaW5kaXZpZHVhbHMgd2l0aCBoYXBsb3R5cGVzCnJlYWR5IHRvIHNpbXVsYXRlIGludG8gb2Zmc3ByaW5nLgoKSSBoYWQgb3JpZ2luYWxseSBwbGFubmVkIG9uIGRvaW5nIHRoaXMgYnkgdXNpbmcgUExJTksgdG8gcXVpY2tseSBjb21wdXRlIEZzdCdzCmZvciBlYWNoIGxvY3VzLCBidXQgSSB1bHRpbWF0ZWx5IGRlY2lkZWQgdG8ga2VlcCBpdCBhbGwgaW4gYSB0aWR5IEhhZGxleS1lc3F1ZQpmcmFtZXdvcmsuICBBY2NvcmRpbmdseSwgSSB3aWxsIGJlIHNlbGVjdGluZyBTTlBzIG9uIHRoZSBleHBlY3RlZCByYXRlIG9mIGNvcnJlY3QKYXNzaWdubWVudCBvZiBhbiBhbGxlbGUgdG8gb25lIGdyb3VwIG9yIHRoZSBvdGhlci0tLXRoaXMgc2VlbXMgdG8gYmUgYSByZWxldmFudCBtZWFzdXJlCmFueXdheSwgc2luY2Ugd2hhdCBvbmUgaXMgZG9pbmcgd2hlbiBpbmZlcnJpbmcgaHlicmlkcyBpcyBhc3NpZ25pbmcgZ2VuZSBjb3BpZXMgdG8gb25lCmdyb3VwIG9yIHRoZSBvdGhlci4KCmBgYHtyIGxvYWQtbGlic30KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc3RyaW5ncikKCiMgYW5kIHNvdXJjZSB0aGUgZmlsZXMgdGhhdCBob2xkIGZ1bmN0aW9ucyB3ZSB3aWxsIGJlIHVzaW5nCnNvdXJjZSgiUi9hc3NpZ25fdHJhaW5fdGVzdC5SIikKc291cmNlKCJSL2NvbXB1dGVfZXBwLlIiKQpzb3VyY2UoIlIvbWFya2VyX3NlbGVjdGlvbi5SIikKYGBgCgojIyBUcmFpbmluZyBTZXQgU2VsZWN0aW9uCgpUaGUgQVFVIGFuZCBXTE4gZ3JvdXBzIGFyZSB0aGUgZmFybWVkIGZpc2ggaW4gZWFjaCBkYXRhIHNldC4gIFNvLCB3ZSBjYW4gZ2V0IHRoZSBkYXRhIGFuZCBwdXQgdGhlbQppbnRvIGRpZmZlcmVudCBncm91cHMgZXRjLiAgSSBhbSBnb2luZyB0byBkZXZlbG9wIHRoaXMgd2l0aCB3ZXN0IGFuZCB3cmFwIGl0IGludG8gYSBmdW5jdGlvbiBsYXRlci4KYGBge3IgZ2V0LWRhdGF9CmRhdCA8LSByZWFkUkRTKCJpbnRlcm1lZGlhdGVzLzAxL3RpZHktd2VzdC5yZHMiKSAlPiUKICBtdXRhdGUocG9wID0gc3RyX3N1YihpZCwgMSwgMyksCiAgICAgICAgIGdyb3VwID0gaWZlbHNlKHBvcCA9PSAiQVFVIiB8IHBvcCA9PSAiV0xOIiwgImZhcm1lZCIsICJ3aWxkIikpCmBgYAoKVGhpcyBmdW5jdGlvbiBhc3NpZ25zIGEgZnJhY3Rpb24gb2YgZmlzaCB0byBhIHRyYWluaW5nIGFuZCBhIHRlc3QgZ3JvdXAuCmBgYHIKYHIgcGFzdGUocmVhZExpbmVzKCJSL2Fzc2lnbl90cmFpbl90ZXN0LlIiKSwgY29sbGFwc2UgPSAiXG4iKWAKYGBgCgoKIyMgTWFya2VyIHJhbmtpbmcgYW5kIHNlbGVjdGlvbgoKIyMjIENvbXB1dGluZyB0aGUgcmFua2luZyBjcml0ZXJpb24KCldlIGFyZSBnb2luZyB0byByYW5rIG1hcmtlcnMgb24gdGhlIGJhc2lzIG9mIHRoZSBleHBlY3RlZCBwb3N0ZXJpb3IgcHJvYmFiaWxpdHkgd2l0aCB3aGljaCAKYW4gYWxsZWxlIHdvdWxkIGJlIGFzc2lnbmVkIHRvIHRoZSBjb3JyZWN0IGdyb3VwLiAgTGV0ICRwXzEkIGFuZCAkcF8yJCBiZSB0aGUgZnJlcXVlbmNpZXMgb2YKdGhlICRBJCBhbGxlbGUgYXQgYSBTTlAgaW4gdHdvIHBvcHVsYXRpb25zLCAxIGFuZCAyLiAgVGhlIG90aGVyIGFsbGVsZSwgc2F5ICRhJCBoYXMgZnJlcXVlbmNpZXMKb2YgJDEtcF8xJCBhbmQgJDEtcF8yJCBpbiB0aGUgcG9wdWxhdGlvbnMuICBJZiBhbiBhbGxlbGUgaXMgZHJhd24gYW5kIGlzIHR5cGUgJEEkLCB0aGVuIAp0aGUgbGlrZWxpaG9vZCBpdCBjYW1lIGZyb20gcG9wdWxhdGlvbiAxIGlzICRwXzEkIGFuZCBmcm9tIHBvcHVsYXRpb24gMiBpcyAkcF8yJCwgYW5kIGlmIHdlCmhhZCBhbiBlcXVhbCBwcmlvciBvbiB0aGUgb3JpZ2luIG9mIHRoZSBnZW5lIGNvcHkgdGhlIHBvc3RlcmlvciB0aGF0IGl0IGNhbWUgZnJvbSBwb3B1bGF0aW9uIDEKd291bGQgYmUgJHBfMS8ocF8xICsgcF8yKSQuICBTbyB0aGUgZXhwZWN0ZWQgcG9zdGVyaW9yIHByb2JhYmlsaXR5IHdpdGggd2hpY2ggYSBnZW5lIGNvcHkgZnJvbSBwb3B1bGF0aW9uIDEKd291bGQgYmUgYXNzaWduZWQgdG8gcG9wdWxhdGlvbiAxIGlzIHNpbXBseToKXFsKRV8xID0gXGZyYWN7cF8xXjJ9e3BfMSArIHBfMn0gKyBcZnJhY3soMS1wXzEpXjJ9ezIgLSAocF8xICsgcF8yKX0KXF0KYW5kIGZvciBhIGdlbmUgY29weSB0aGF0IHdhcyBhY3R1YWxseSBmcm9tIHBvcHVsYXRpb24gMiB3ZSB3b3VsZCBoYXZlOgpcWwpFXzIgPSBcZnJhY3twXzJeMn17cF8xICsgcF8yfSArIFxmcmFjeygxLXBfMileMn17MiAtIChwXzEgKyBwXzIpfS4KXF0KU28sIGxldCB1cyBwcm9wb3NlIGFzIGEgc3RhdGlzdGljIGZvciByYW5raW5nIGxvY2kgZm9yIHRoZWlyIHV0aWxpdHkgZm9yIE5ld0h5YnJpZHMgYW5hbHlzaXMKd2hhdCB3ZSBjYWxsIGBlcHBgOgpcWwpcbWF0aHJte2VwcH0gPSBcZnJhY3tFXzEgKyBFXzJ9ezJ9ClxdCgpUbyBkbyB0aGlzCndlIGFyZSBnb2luZyB0byBuZWVkIHRvIAoKMS4gY29tcHV0ZSB0aGUgYWxsZWxlIGZyZXF1ZW5jaWVzLiAgQW5kIHdlIHdpbGwgYWRkIGhhbGYgb2YgYW4gYWxsZWxlIHRvCmVhY2ggcGlsZSAoZXNzZW50aWFsbHkgYSB1bml0IGluZm9ybWF0aW9uIHByaW9yKSB3aGVuIHdlIGRvIGl0LgoxLiBjb21wdXRlIHRoZSBlcHAKCkhlcmUgaXMgYSBmdW5jdGlvbiB0byBkbyB0aGF0OgpgYGByCmByIHBhc3RlKHJlYWRMaW5lcygiUi9jb21wdXRlX2VwcC5SIiksIGNvbGxhcHNlID0gIlxuIilgCmBgYAoKT0ssIHRoYXQncyB0aGF0LiAgIFNvLCBsZXQncyBkbyBpdDoKYGBge3IgZG8tZXBwfQpzZXQuc2VlZCgxMjM0KSAgIyBmb3IgcmVwcm9kdWNpYmlsaXR5IGR1cmluZyBkZXZlbG9wbWVudApBIDwtIGFzc2lnbl90cmFpbl90ZXN0KGRhdCkKUiA8LSBjb21wdXRlX2VwcChBKQpgYGAKClNvbWV0aGluZyB0byBub3RlIGhlcmUgaXMgdGhhdCBJIHRoaW5rIHRoZSBhYm92ZSBjYWxjdWxhdGlvbnMgd291bGQgYWxzbyB3b3JrIGZvciBtdWx0aWFsbGVsaWMKbWFya2VycywgbGlrZSBoYXBsb3R5cGVzLiAKCiMjIyBEb2luZyB0aGUgbWFya2VyIHNlbGVjdGlvbgoKT25jZSB3ZSBoYXZlIHRoZSByYW5rcyBvZiB0aGUgbWFya2VycyB3ZSBuZWVkIHRvIGRvIHRoZSBzZWxlY3Rpb24gb2YgdGhlbS4KSW4gZG9pbmcgdGhpcyB3ZSB3aWxsIHdhbnQgdG8gYmUgY29nbml6YW50IG9mIHBoeXNpY2FsIGxpbmthZ2UuIEkgYW0gbm90CmdvaW5nIHRvIHNjcmVlbiB0aGluZ3Mgb3V0IG9uIHRoZSBiYXNpcyBvZiBMRCBiZXR3ZWVuIGxvY2kuICBNeSBmZWVsaW5nIGlzIHRoYXQKaWYgdGhlcmUgaXMgTEQgd2l0aGluIGEgc2luZ2xlIHBvcHVsYXRpb24gYmV0d2VlbiBtYXJrZXJzIHRoYXQgYXJlIG5vdCB2ZXJ5IGNsb3NlIApwaHlzaWNhbGx5IChhbmQgaGVuY2Ugd2lsbCBiZSBmaWx0ZXJlZCBvdXQgYW55d2F5IGJlY2F1c2Ugb2YgdGhlaXIgcGh5c2ljYWwgcHJveGltaXR5KSwKdGhlbiB0aG9zZSBtYXJrZXJzIHdpbGwgbGlrZWx5IGJlIHRoZSByZXN1bHQgb2YgcmVjZW50IHN0cm9uZyBzZWxlY3Rpb24gaW4gdGhlIGFxdWFjdWx0dXJlCnN0cmFpbiAod2l0aCB2ZXJ5IGxvbmcgYmxvY2tzIG9mIExEKS4gIEluIHRoYXQgY2FzZSwgdGhlIE5ld0h5YnJpZHMgbW9kZWwgaXMgbm90IHF1aXRlIGNvcnJlY3QsIHNpbmNlIGl0IGFzc3VtZXMKaW5kZXBlbmRlbmNlIG9mIHBhcmVudGFsIHBvcHVsYXRpb24gYWxsZWxlIGZyZXF1ZW5jaWVzLiAgSG93ZXZlciwgc2luY2Ugd2UgYXJlIHNpbXVsYXRpbmcKdGhpbmdzIHdpdGhvdXQgYW55IExELCBJIGRvbid0IHN1c3BlY3QgdGhlIGRpZmZlcmVuY2Ugd2lsbCBiZSBhbGwgdGhhdCBncmVhdC4gIFRoZSB2YXJpYW5jZQppbiB0aGUgcG9zdGVyaW9yIHByb2JhYmlsaXRpZXMgd2lsbCBiZSByZWR1Y2VkIGEgbGl0dGxlIGJlY2F1c2Ugd2UgYXJlIG5vdCBzaW11bGF0aW5nIHdpdGggCkxELCBidXQgSSB0aGluayB0aGF0IGZpbHRlcmluZyBvbiB0aGUgYmFzaXMgb2YgcGh5c2ljYWwgcHJveGltaXR5IGlzIGEgYmV0dGVyIGJldCBhbnl3YXkuICAKClNvLCBvdXIgbWV0aG9kIGZvciBtYXJrZXIgc2VsZWN0aW9uIHdpbGwgc3RyYWlnaHRmb3J3YXJkOgoKMS4gRmlyc3QsIHdpdGhpbiBlYWNoIGNocm9tb3NvbWUgd2UgcmFuayBhbGwgdGhlIHZhcmlhbnRzIGJ5IGBlcHBgIGFuZCB0aGVuIHdlIGluY2x1ZGUKdGhlIG9uZSB3aXRoIHRoZSBoaWdoZXN0IGVwcCBpbiB0aGUgY2hvc2VuIHNldC4gV2hlbiBhIHZhcmlhbnQgaXMgcGxhY2VkIGluIHRoZSBjaG9zZW4Kc2V0LCB3ZSBhZGQgaXRzIGJhc2UtcGFpciBjb29yZGluYXRlcyB0byBhIHNldCAkQiQuCjEuIFRoZW4gbW92ZSBvbiB0byB0aGUgdmFyaWFudCB3aXRoIHRoZSBuZXh0IGhpZ2hlc3QgYGVwcGAuICBJZiBpdHMgYmFzZS1wYWlyIGNvb3JkaW5hdGVzCmFyZSBncmVhdGVyIHRoYW4gYG1pbl9kaXN0YCBmcm9tIGFueSBvZiB0aGUgcHJldmlvdXNseSBjaG9zZW4gdmFyaWFudHMgaW4gJEIkLCB0aGVuIGl0CndpbGwgZ2V0IGNob3NlbiB0b28sIChhbmQgaXRzIGNvb3JkaW5kYXRlcyBpbmNsdWRlZCBpbiAkQiQpLgoKVGhlIGFib3ZlIHByb2Nlc3MgY29udGludWVzIHVudGlsIHdlIGhhdmUgZWl0aGVyIHNlbGVjdGVkIChvciBub3QpIGFsbCB0aGUgbG9jaSBvbiB0aGUKY2hyb21vc29tZS9saW5rYWdlIGdyb3VwLgoKT25jZSBhbGwgdGhlIGxpbmthZ2UgZ3JvdXBzIGhhdmUgYmVlbiBwcm9jZXNzZWQsIHdlIHJhbmsgYWxsIHRoZSB2YXJpYW50cyBieSB0aGVpciAKYGVwcGAgYW5kIHNlbGVjdCB0aG9zZSB0aGF0IHdlcmUgb24gdGhlICJzZWxlY3RlZCIgcGlsZSBmcm9tIGVhY2ggbGlua2FnZSBncm91cC4KClRoZSBjaG9pY2Ugb2YgYG1pbl9kaXN0YCBpbiBvdXIgY2FzZSBpcyBkZXRlcm1pbmVkIGJ5IHRoZSBzZXgtYXZlcmFnZWQgbWFwIHRoYXQgd2UgaGF2ZS4KQWZ0ZXIgc2V4LWF2ZXJhZ2luZyB0aGUgcmVjb21iaW5hdGlvbiByYXRlcywgb3VyIG1hcCBjaGFuZ2VkIGluIHVuaXRzIG9mIDAuMDUgY00sIHdoaWNoIApjb3JyZXNwb25kcyB0byB1bml0cyBvZiA1MCBLYiBvbiB0aGUgcGh5c2ljYWwgY29vcmRpbmF0ZXMgdGhhdCB3ZSBtYWRlLiAgVGhpcyBtZWFucwp0aGF0IGFueSBtYXJrZXJzIHRoYXQgYXJlIHdpdGhpbiA1MCBLYiBvZiBvbmUgYW5vdGhlciBhcmUgZXNzZW50aWFsbHkgb24gdGhlIHNhbWUKbGlua2FnZSBncm91cCBmcm9tIHRoZSBsaW5rYWdlIG1hcCBlc3RpbWF0aW9uIHByb2plY3QuICBTbywgd2Ugd2lsbCBzZXQgbWluX2Rpc3QgdG8gYmUgCjYwIEtiLiAgVGhhdCB3aWxsIGVuc3VyZSB0aGF0IHdlIGRvbid0IHRha2UgbW9yZSB0aGFuIG9uZSBtYXJrZXIgZnJvbSBlYWNoIGdyb3VwIG9uIHRoZQpzZXgtYXZlcmFnZWQgbGlua2FnZSBtYXAuICAKCldlIHdpbGwgbWFrZSBhIGZldyBmdW5jdGlvbnMgZm9yIHRoaXMgaGVyZToKYGBgcgpgciBwYXN0ZShyZWFkTGluZXMoIlIvbWFya2VyX3NlbGVjdGlvbi5SIiksIGNvbGxhcHNlID0gIlxuIilgCmBgYAoKCkFuZCB0aGlzIGlzIGhvdyB3ZSBydW4gaXQ6CmBgYHtyIHJ1bi1tLXNlbGVjdH0KTSA8LSBtYXJrZXJfc2VsZWN0aW9uKFIsIG1pbl9kaXN0ID0gNmUwNCkKYGBgCgpJdCB3b3VsZCBiZSBpbnRlcmVzdGluZyB0byBsb29rIGF0IHRob3NlIGFuZCBzZWUgaG93IG1hbnkgb2YgdGhlbSB3ZXJlIG9uIHRoZSBvcmlnaW5hbApsaW5rYWdlIG1hcCwgYW5kIGhvdyBtYW55IHdlcmUganVzdCAic3ByaW5rbGVkIGluIi4gIEhlcmUgYXJlIHRoZSB0b3AgMTAwMCBsb2NpCmBgYHtyIHNob3ctTX0KIyBnZXQgdGhlIHNleC1hdmVyYWdlZCBtYXAKc2EgPC0gcmVhZFJEUygiaW50ZXJtZWRpYXRlcy8wMS9zZXhfYXZlcmFnZWRfbWFwLnJkcyIpCgojIHRoZW4gYWRkIGEgY29sdW1uICJzcHJpbmtsZWQiCk0gJT4lCiAgbXV0YXRlKHNwcmlua2xlZCA9ICEodmFyaWFudCAlaW4lIHNhJFNOUF9JRCkpCmBgYAoKIyMgU2F2ZSBzb21lIGZpbGVzIGZvciB0aGUgbmV4dCBzZWdtZW50CgpXZSBhcmUgZ29pbmcgdG8gc2F2ZSB0aGUgb3V0cHV0IG9mIHRlc3RfYW5kX3RyYWluIGFuZCB0aGUgbWFya2VyIHNlbGVjdGlvbjoKYGBge3Igc2F2ZS1pbnRlcm1lZGlhdGVzfQpkaXIuY3JlYXRlKCJpbnRlcm1lZGlhdGVzLzAyIiwgc2hvd1dhcm5pbmdzID0gRkFMU0UpCnNhdmVSRFMoQSwgZmlsZSA9ICJpbnRlcm1lZGlhdGVzLzAyL3Rlc3RfYW5kX3RyYWluLnJkcyIsIGNvbXByZXNzID0gInh6IikKc2F2ZVJEUyhNLCBmaWxlID0gImludGVybWVkaWF0ZXMvMDIvbWFya2VyX3JhbmtpbmdzLnJkcyIsIGNvbXByZXNzID0gInh6IikKYGBgCg==