library(tidyverse)
library(GSImulator)
library(ape)
library(ggtree)

Introduction

In class, we spoke about the fact that, under the neutral coalescent with inifinite sites mutation, the expected number of mutations that were inherited by \(i\) haploid chromosomes in our sample is \(\theta/i\), where \(\theta = 4N\mu\).

Let’s write that out using the notation that \(S_n^{i}\) denotes the number of mutations that were inherited by \(i\) haploid chromosomes in the sample. We have: \[ E(S_n^{i}) = \frac{\theta}{i}~~,~~i = 1,\ldots, n-1 \]

So, what does that look like? Let’s make a picture. Imagine we have a sample of 25 diploids, so 50 haploid chromosomes in our samples. The total number of SNPs we might find in that sample depends on \(\theta\), of course, but let’s simple imagine that we found 100K SNPs total. If they were generated from a neutral coalescent we could compute the proportions they are expected to be in using the equation above (basically we know that we expect to have 1/2 as many mutations found in two copies as in one copy, and a third as many in 3 copies as in 1 copy, and so forth). In R code we could say:

NumSNPs <- 100000
NumSam <- 50 # number of haploid chromosomes

# here are the unnormalized fractions we expect to find
fracts <-  1 / 1:(NumSam - 1)

# normalize so they sum to one, the multiply by NumSNPs
Sin <- NumSNPs * fracts / sum(fracts)

# let's plot that:
tibble(
  i = 1:(NumSam - 1),
  E_of_S = Sin) %>%
ggplot(aes(x = i, y = E_of_S)) +
  geom_col() +
  theme_bw()

Question 1: with a sample of size 50 gene copies, what is the allele count for a SNP with a minor allele frequency of 0.1?

Question 2: Look at the figure and roughly estimate how many SNPs are expected to have a minor allele frequency less than 0.1.

Does the answer explain why your data set gets so much smaller when you filter on minor allele frequency?

Uses of the SFS

The 1d (and 2-d and higher) site frequency spectra are often used to make demographic inference about the populations that produced them. Site frequency spectra are quite simple to compute, and represent a huge reduction in data, which means that they can be useful when dealing with huge genomic data sets.

Today, we won’t do any actual inference from site frequency spectra. The programs \(\partial a \partial i\) and moments will do that. For now, I just want to give students the chance to explore how the 1-d SFS changes under different demographic scenarios.

Generating SFS from coalescent simulations

To compute average SFS from coalescent simulations, we can simulate thousands of independent coalescent trees, each with a single mutation on them, and then process those in R to compute the SFS. Here is how we would simulate 10K independent SNPs with a sample of 25 diloids (50 haploids):

system2(command = ms_binary(), args = "50 10000 -s 1", stdout = "out.snps", stderr = FALSE)

That creates a rather large file (1.3 Mb or so). We can, however, read that into R and return a site frequency spectrum (which is really little more than a table of counts) using this nifty function:

#' parse ms output with one snp per tree simulated and return the SFS
#'
#' @param nsam The number of haploid chromosomes simulated
#' @param file The name of the file.  By default = "out.snps"
ms2sfs <- function(nsam, file = "out.snps") {
  x <- readr::read_lines(file)
  mat <- matrix(x[x == "0" | x == "1"], nrow = nsam)
  
  tibble(
    S = as.numeric(colSums(mat == 1))
  ) %>%
    count(S) %>%
    left_join(tibble(i = 1:(nrow(mat) - 1)), ., by = c("i" = "S")) %>%
    rename(S = n) %>%
    mutate(S = ifelse(is.na(S), 0, S))
}

See how that works like this:

S <- ms2sfs(50)
S

And now, it would be nice to be able to plot that SFS, along with a picture of what the neutral, standard coalescnet would look like. Here is a function for that:

#' plot an SFS that comes out of ms2sfs()
#' @param S the SFS tibble that comes out of ms2sfs()
#' @param add_neural logical.  If true, a blue dashed frame is printed
# in the location of the expected SFS under the standard coalescent
# with nothing fancy going on.
plot_sfs <- function(S, add_neutral = TRUE) {
  g <- ggplot(S, aes(x = i, y = S)) +
  geom_col(fill = "gray") +
  theme_bw()
  
  if(add_neutral == TRUE) {
    NumSNPs <- sum(S$S)
    NumSam <- max(S$i) + 1
    fracts <-  1 / 1:(NumSam - 1)
    Sin <- NumSNPs * fracts / sum(fracts)
    
    expy <- tibble(
      i = 1:(NumSam - 1),
      S = Sin)
    
    g <- g +
      geom_col(data = expy, fill = NA, colour = "blue", linetype = "dashed", linewidth = 0.5)
  }
  g
}

Now, we can simulate, read, and plot the SFS in three easy steps:

system2(command = ms_binary(), args = "50 10000 -s 1", stdout = "out.snps", stderr = FALSE)
S <- ms2sfs(50)
plot_sfs(S)

And we see that our observed SFS looks a lot like that expected under the neutral coalescent.

Your Group Tasks

For these thought and simulation problems, assume a single population.

  1. Think about how you might simulate different scenarios that would give you an SFS pushed more to the left (more singletons and low-frequency variants than expected under the neutral coalescent with constant population size—what would the tree look like for that?), and also scenarios that would give you an SFS pushed more to the right (more high-frequency variants). Hint: population size changes can do this.

  2. Hand-sketch a tree that would give you an SFS pushed to the left and then sketch another tree that would give you an SFS pushed to the right.

  3. Use ms to simulate 10,000 SNPs from a single population and produce an SFS that has more rare variants than expected under the neutral coalescent with constant population size.

  4. Super bonus problem: create an excess of high-frequency variants by drawing your present-day sample from a population that is admixed between two populations that existed in the past.

If you get stuck…

Here are a few examples you can run to get some intuition. If you get stuck, try running each of these and looking at the results.

system2(command = ms_binary(), args = "50 10000 -s 1 -G 5", stdout = "out.snps", stderr = FALSE)
ms2sfs(50) %>% plot_sfs()
system2(command = ms_binary(), args = "50 10000 -s 1 -en 0.25 1 25", stdout = "out.snps", stderr = FALSE)
ms2sfs(50) %>% plot_sfs()

And for the super bonus problem:

system2(command = ms_binary(), args = "50 10000 -s 1 -es 0.1 1 0.5 -ej 6.0 1 2", stdout = "out.snps", stderr = FALSE)
ms2sfs(50) %>% plot_sfs()
LS0tCnRpdGxlOiAiT25lLWRpbWVuc2lvbmFsIFNpdGUgRnJlcXVlbmN5IFNwZWN0cmEiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCmF1dGhvcjogIkVyaWMgQy4gQW5kZXJzb24iCmJpYmxpb2dyYXBoeTogcmVmZXJlbmNlcy5iaWIKLS0tCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KEdTSW11bGF0b3IpCmxpYnJhcnkoYXBlKQpsaWJyYXJ5KGdndHJlZSkKYGBgCgoKIyMgSW50cm9kdWN0aW9uCgpJbiBjbGFzcywgd2Ugc3Bva2UgYWJvdXQgdGhlIGZhY3QgdGhhdCwgdW5kZXIgdGhlIG5ldXRyYWwgY29hbGVzY2VudCB3aXRoIAppbmlmaW5pdGUgc2l0ZXMgbXV0YXRpb24sIHRoZSBleHBlY3RlZCBudW1iZXIgb2YgbXV0YXRpb25zIHRoYXQgd2VyZSBpbmhlcml0ZWQKYnkgJGkkIGhhcGxvaWQgY2hyb21vc29tZXMgaW4gb3VyIHNhbXBsZSBpcyAkXHRoZXRhL2kkLCB3aGVyZSAkXHRoZXRhID0gNE5cbXUkLgoKTGV0J3Mgd3JpdGUgdGhhdCBvdXQgdXNpbmcgdGhlIG5vdGF0aW9uIHRoYXQgJFNfbl57aX0kIGRlbm90ZXMgdGhlIG51bWJlcgpvZiBtdXRhdGlvbnMgdGhhdCB3ZXJlIGluaGVyaXRlZCBieSAkaSQgaGFwbG9pZCBjaHJvbW9zb21lcyBpbiB0aGUgc2FtcGxlLiAgV2UKaGF2ZToKJCQKRShTX25ee2l9KSA9IFxmcmFje1x0aGV0YX17aX1+fix+fmkgPSAxLFxsZG90cywgbi0xCiQkCgpTbywgd2hhdCBkb2VzIHRoYXQgbG9vayBsaWtlPyAgTGV0J3MgbWFrZSBhIHBpY3R1cmUuCkltYWdpbmUgd2UgaGF2ZSBhIHNhbXBsZSBvZiAyNSBkaXBsb2lkcywgc28gNTAgaGFwbG9pZCBjaHJvbW9zb21lcwppbiBvdXIgc2FtcGxlcy4gIFRoZSB0b3RhbCBudW1iZXIgb2YgU05QcyB3ZSBtaWdodCBmaW5kIGluIHRoYXQKc2FtcGxlIGRlcGVuZHMgb24gJFx0aGV0YSQsIG9mIGNvdXJzZSwgYnV0IGxldCdzIHNpbXBsZSBpbWFnaW5lIHRoYXQKd2UgZm91bmQgMTAwSyBTTlBzIHRvdGFsLiAgSWYgdGhleSB3ZXJlIGdlbmVyYXRlZCBmcm9tIGEgbmV1dHJhbCBjb2FsZXNjZW50CndlIGNvdWxkIGNvbXB1dGUgdGhlIHByb3BvcnRpb25zIHRoZXkgYXJlIGV4cGVjdGVkIHRvIGJlIGluIHVzaW5nIHRoZQplcXVhdGlvbiBhYm92ZSAoYmFzaWNhbGx5IHdlIGtub3cgdGhhdCB3ZSBleHBlY3QgdG8gaGF2ZSAxLzIgYXMgbWFueSBtdXRhdGlvbnMgZm91bmQKaW4gdHdvIGNvcGllcyBhcyBpbiBvbmUgY29weSwgYW5kIGEgdGhpcmQgYXMgbWFueSBpbiAzIGNvcGllcyBhcyBpbiAxIGNvcHksIGFuZApzbyBmb3J0aCkuIEluIFIgY29kZSB3ZSBjb3VsZCBzYXk6CmBgYHtyfQpOdW1TTlBzIDwtIDEwMDAwMApOdW1TYW0gPC0gNTAgIyBudW1iZXIgb2YgaGFwbG9pZCBjaHJvbW9zb21lcwoKIyBoZXJlIGFyZSB0aGUgdW5ub3JtYWxpemVkIGZyYWN0aW9ucyB3ZSBleHBlY3QgdG8gZmluZApmcmFjdHMgPC0gIDEgLyAxOihOdW1TYW0gLSAxKQoKIyBub3JtYWxpemUgc28gdGhleSBzdW0gdG8gb25lLCB0aGUgbXVsdGlwbHkgYnkgTnVtU05QcwpTaW4gPC0gTnVtU05QcyAqIGZyYWN0cyAvIHN1bShmcmFjdHMpCgojIGxldCdzIHBsb3QgdGhhdDoKdGliYmxlKAogIGkgPSAxOihOdW1TYW0gLSAxKSwKICBFX29mX1MgPSBTaW4pICU+JQpnZ3Bsb3QoYWVzKHggPSBpLCB5ID0gRV9vZl9TKSkgKwogIGdlb21fY29sKCkgKwogIHRoZW1lX2J3KCkKYGBgCgpRdWVzdGlvbiAxOiB3aXRoIGEgc2FtcGxlIG9mIHNpemUgNTAgZ2VuZSBjb3BpZXMsIHdoYXQgaXMgdGhlIGFsbGVsZSBjb3VudCBmb3IKYSBTTlAgd2l0aCBhIG1pbm9yIGFsbGVsZSBmcmVxdWVuY3kgb2YgMC4xPwoKUXVlc3Rpb24gMjogTG9vayBhdCB0aGUgZmlndXJlIGFuZCByb3VnaGx5IGVzdGltYXRlIGhvdyBtYW55IFNOUHMgYXJlIGV4cGVjdGVkIHRvCmhhdmUgYSBtaW5vciBhbGxlbGUgZnJlcXVlbmN5IGxlc3MgdGhhbiAwLjEuICAKCkRvZXMgdGhlIGFuc3dlciBleHBsYWluIHdoeSB5b3VyIGRhdGEgc2V0IGdldHMgc28gbXVjaCBzbWFsbGVyIHdoZW4geW91IGZpbHRlcgpvbiBtaW5vciBhbGxlbGUgZnJlcXVlbmN5PwoKCiMjIFVzZXMgb2YgdGhlIFNGUwoKVGhlIDFkIChhbmQgMi1kIGFuZCBoaWdoZXIpIHNpdGUgZnJlcXVlbmN5IHNwZWN0cmEgYXJlIG9mdGVuIHVzZWQgdG8gbWFrZSBkZW1vZ3JhcGhpYwppbmZlcmVuY2UgYWJvdXQgdGhlIHBvcHVsYXRpb25zIHRoYXQgcHJvZHVjZWQgdGhlbS4gIFNpdGUgZnJlcXVlbmN5IHNwZWN0cmEgYXJlCnF1aXRlIHNpbXBsZSB0byBjb21wdXRlLCBhbmQgcmVwcmVzZW50IGEgaHVnZSByZWR1Y3Rpb24gaW4gZGF0YSwgd2hpY2ggbWVhbnMKdGhhdCB0aGV5IGNhbiBiZSB1c2VmdWwgd2hlbiBkZWFsaW5nIHdpdGggaHVnZSBnZW5vbWljIGRhdGEgc2V0cy4KClRvZGF5LCB3ZSB3b24ndCBkbyBhbnkgYWN0dWFsIGluZmVyZW5jZSBmcm9tIHNpdGUgZnJlcXVlbmN5IHNwZWN0cmEuIFRoZSBwcm9ncmFtcwokXHBhcnRpYWwgYSBccGFydGlhbCBpJCBhbmQgX21vbWVudHNfIHdpbGwgZG8gdGhhdC4KRm9yIG5vdywgSSBqdXN0IHdhbnQgdG8gZ2l2ZSBzdHVkZW50cyB0aGUgY2hhbmNlIHRvIGV4cGxvcmUgaG93IAp0aGUgMS1kIFNGUyBjaGFuZ2VzIHVuZGVyIGRpZmZlcmVudCBkZW1vZ3JhcGhpYyBzY2VuYXJpb3MuCgojIyBHZW5lcmF0aW5nIFNGUyBmcm9tIGNvYWxlc2NlbnQgc2ltdWxhdGlvbnMKClRvIGNvbXB1dGUgYXZlcmFnZSBTRlMgZnJvbSBjb2FsZXNjZW50IHNpbXVsYXRpb25zLCB3ZSBjYW4gc2ltdWxhdGUgdGhvdXNhbmRzIG9mCmluZGVwZW5kZW50IGNvYWxlc2NlbnQgdHJlZXMsIGVhY2ggd2l0aCBhIHNpbmdsZSBtdXRhdGlvbiBvbiB0aGVtLCBhbmQgdGhlbgpwcm9jZXNzIHRob3NlIGluIFIgdG8gY29tcHV0ZSB0aGUgU0ZTLiAgSGVyZSBpcyBob3cgd2Ugd291bGQgc2ltdWxhdGUgMTBLIGluZGVwZW5kZW50ClNOUHMgd2l0aCBhIHNhbXBsZSBvZiAyNSBkaWxvaWRzICg1MCBoYXBsb2lkcyk6CmBgYHtyfQpzeXN0ZW0yKGNvbW1hbmQgPSBtc19iaW5hcnkoKSwgYXJncyA9ICI1MCAxMDAwMCAtcyAxIiwgc3Rkb3V0ID0gIm91dC5zbnBzIiwgc3RkZXJyID0gRkFMU0UpCmBgYApUaGF0IGNyZWF0ZXMgYSByYXRoZXIgbGFyZ2UgZmlsZSAoMS4zIE1iIG9yIHNvKS4gIFdlIGNhbiwgaG93ZXZlciwgcmVhZCB0aGF0CmludG8gUiBhbmQgcmV0dXJuIGEgc2l0ZSBmcmVxdWVuY3kgc3BlY3RydW0gKHdoaWNoIGlzIHJlYWxseSBsaXR0bGUgbW9yZSB0aGFuCmEgdGFibGUgb2YgY291bnRzKSB1c2luZyB0aGlzIG5pZnR5IGZ1bmN0aW9uOgpgYGB7cn0KIycgcGFyc2UgbXMgb3V0cHV0IHdpdGggb25lIHNucCBwZXIgdHJlZSBzaW11bGF0ZWQgYW5kIHJldHVybiB0aGUgU0ZTCiMnCiMnIEBwYXJhbSBuc2FtIFRoZSBudW1iZXIgb2YgaGFwbG9pZCBjaHJvbW9zb21lcyBzaW11bGF0ZWQKIycgQHBhcmFtIGZpbGUgVGhlIG5hbWUgb2YgdGhlIGZpbGUuICBCeSBkZWZhdWx0ID0gIm91dC5zbnBzIgptczJzZnMgPC0gZnVuY3Rpb24obnNhbSwgZmlsZSA9ICJvdXQuc25wcyIpIHsKICB4IDwtIHJlYWRyOjpyZWFkX2xpbmVzKGZpbGUpCiAgbWF0IDwtIG1hdHJpeCh4W3ggPT0gIjAiIHwgeCA9PSAiMSJdLCBucm93ID0gbnNhbSkKICAKICB0aWJibGUoCiAgICBTID0gYXMubnVtZXJpYyhjb2xTdW1zKG1hdCA9PSAxKSkKICApICU+JQogICAgY291bnQoUykgJT4lCiAgICBsZWZ0X2pvaW4odGliYmxlKGkgPSAxOihucm93KG1hdCkgLSAxKSksIC4sIGJ5ID0gYygiaSIgPSAiUyIpKSAlPiUKICAgIHJlbmFtZShTID0gbikgJT4lCiAgICBtdXRhdGUoUyA9IGlmZWxzZShpcy5uYShTKSwgMCwgUykpCn0KYGBgCgpTZWUgaG93IHRoYXQgd29ya3MgbGlrZSB0aGlzOgpgYGB7cn0KUyA8LSBtczJzZnMoNTApClMKYGBgCgpBbmQgbm93LCBpdCB3b3VsZCBiZSBuaWNlIHRvIGJlIGFibGUgdG8gcGxvdCB0aGF0IFNGUywgYWxvbmcgd2l0aAphIHBpY3R1cmUgb2Ygd2hhdCB0aGUgbmV1dHJhbCwgc3RhbmRhcmQgY29hbGVzY25ldCB3b3VsZCBsb29rIGxpa2UuCkhlcmUgaXMgYSBmdW5jdGlvbiBmb3IgdGhhdDoKYGBge3J9CiMnIHBsb3QgYW4gU0ZTIHRoYXQgY29tZXMgb3V0IG9mIG1zMnNmcygpCiMnIEBwYXJhbSBTIHRoZSBTRlMgdGliYmxlIHRoYXQgY29tZXMgb3V0IG9mIG1zMnNmcygpCiMnIEBwYXJhbSBhZGRfbmV1cmFsIGxvZ2ljYWwuICBJZiB0cnVlLCBhIGJsdWUgZGFzaGVkIGZyYW1lIGlzIHByaW50ZWQKIyBpbiB0aGUgbG9jYXRpb24gb2YgdGhlIGV4cGVjdGVkIFNGUyB1bmRlciB0aGUgc3RhbmRhcmQgY29hbGVzY2VudAojIHdpdGggbm90aGluZyBmYW5jeSBnb2luZyBvbi4KcGxvdF9zZnMgPC0gZnVuY3Rpb24oUywgYWRkX25ldXRyYWwgPSBUUlVFKSB7CiAgZyA8LSBnZ3Bsb3QoUywgYWVzKHggPSBpLCB5ID0gUykpICsKICBnZW9tX2NvbChmaWxsID0gImdyYXkiKSArCiAgdGhlbWVfYncoKQogIAogIGlmKGFkZF9uZXV0cmFsID09IFRSVUUpIHsKICAgIE51bVNOUHMgPC0gc3VtKFMkUykKICAgIE51bVNhbSA8LSBtYXgoUyRpKSArIDEKICAgIGZyYWN0cyA8LSAgMSAvIDE6KE51bVNhbSAtIDEpCiAgICBTaW4gPC0gTnVtU05QcyAqIGZyYWN0cyAvIHN1bShmcmFjdHMpCiAgICAKICAgIGV4cHkgPC0gdGliYmxlKAogICAgICBpID0gMTooTnVtU2FtIC0gMSksCiAgICAgIFMgPSBTaW4pCiAgICAKICAgIGcgPC0gZyArCiAgICAgIGdlb21fY29sKGRhdGEgPSBleHB5LCBmaWxsID0gTkEsIGNvbG91ciA9ICJibHVlIiwgbGluZXR5cGUgPSAiZGFzaGVkIiwgbGluZXdpZHRoID0gMC41KQogIH0KICBnCn0KYGBgCgpOb3csIHdlIGNhbiBzaW11bGF0ZSwgcmVhZCwgYW5kIHBsb3QgdGhlIFNGUyBpbiB0aHJlZSBlYXN5IHN0ZXBzOgpgYGB7cn0Kc3lzdGVtMihjb21tYW5kID0gbXNfYmluYXJ5KCksIGFyZ3MgPSAiNTAgMTAwMDAgLXMgMSIsIHN0ZG91dCA9ICJvdXQuc25wcyIsIHN0ZGVyciA9IEZBTFNFKQpTIDwtIG1zMnNmcyg1MCkKcGxvdF9zZnMoUykKCmBgYAoKQW5kIHdlIHNlZSB0aGF0IG91ciBvYnNlcnZlZCBTRlMgbG9va3MgYSBsb3QgbGlrZSB0aGF0IGV4cGVjdGVkIHVuZGVyCnRoZSBuZXV0cmFsIGNvYWxlc2NlbnQuCgojIyBZb3VyIEdyb3VwIFRhc2tzCgpGb3IgdGhlc2UgdGhvdWdodCBhbmQgc2ltdWxhdGlvbiBwcm9ibGVtcywgYXNzdW1lIGEgc2luZ2xlIHBvcHVsYXRpb24uCgoKMS4gVGhpbmsgYWJvdXQgaG93IHlvdSBtaWdodCBzaW11bGF0ZSBkaWZmZXJlbnQgc2NlbmFyaW9zIHRoYXQgd291bGQgZ2l2ZSB5b3UKYW4gU0ZTIHB1c2hlZCBtb3JlIHRvIHRoZSBsZWZ0IChtb3JlIHNpbmdsZXRvbnMgYW5kIGxvdy1mcmVxdWVuY3kgdmFyaWFudHMgdGhhbgpleHBlY3RlZCB1bmRlciB0aGUgbmV1dHJhbCBjb2FsZXNjZW50IHdpdGggY29uc3RhbnQgcG9wdWxhdGlvbiBzaXplLS0td2hhdAp3b3VsZCB0aGUgdHJlZSBsb29rIGxpa2UgZm9yIHRoYXQ/KSwgYW5kIGFsc28gc2NlbmFyaW9zIHRoYXQgd291bGQgZ2l2ZQp5b3UgYW4gU0ZTIHB1c2hlZCBtb3JlIHRvIHRoZSByaWdodCAobW9yZSBoaWdoLWZyZXF1ZW5jeSB2YXJpYW50cykuICBIaW50Ogpwb3B1bGF0aW9uIHNpemUgY2hhbmdlcyBjYW4gZG8gdGhpcy4KCgoKMi4gSGFuZC1za2V0Y2ggYSB0cmVlIHRoYXQgd291bGQgZ2l2ZSB5b3UgYW4gU0ZTIHB1c2hlZCB0byB0aGUgbGVmdCBhbmQgdGhlbgpza2V0Y2ggYW5vdGhlciB0cmVlIHRoYXQgd291bGQgZ2l2ZSB5b3UgYW4gU0ZTIHB1c2hlZCB0byB0aGUgcmlnaHQuCgoKMy4gVXNlIGBtc2AgdG8gc2ltdWxhdGUgMTAsMDAwIFNOUHMgZnJvbSBhIHNpbmdsZSBwb3B1bGF0aW9uIGFuZCBwcm9kdWNlCmFuIFNGUyB0aGF0IGhhcyBtb3JlIHJhcmUgdmFyaWFudHMgdGhhbiBleHBlY3RlZCB1bmRlciB0aGUgbmV1dHJhbCBjb2FsZXNjZW50IHdpdGgKY29uc3RhbnQgcG9wdWxhdGlvbiBzaXplLiAgCgoKNC4gU3VwZXIgYm9udXMgcHJvYmxlbTogY3JlYXRlIGFuIGV4Y2VzcyBvZiBoaWdoLWZyZXF1ZW5jeSB2YXJpYW50cyBieSBkcmF3aW5nCnlvdXIgcHJlc2VudC1kYXkgc2FtcGxlIGZyb20gYSBwb3B1bGF0aW9uCnRoYXQgaXMgYWRtaXhlZCBiZXR3ZWVuIHR3byBwb3B1bGF0aW9ucyB0aGF0IGV4aXN0ZWQgaW4gdGhlIHBhc3QuICAKCgoKIyMgSWYgeW91IGdldCBzdHVjay4uLgoKSGVyZSBhcmUgYSBmZXcgZXhhbXBsZXMgeW91IGNhbiBydW4gdG8gZ2V0IHNvbWUgaW50dWl0aW9uLiAgSWYgeW91IGdldCBzdHVjaywgdHJ5CnJ1bm5pbmcgZWFjaCBvZiB0aGVzZSBhbmQgbG9va2luZyBhdCB0aGUgcmVzdWx0cy4KCmBgYHtyLCBldmFsPUZBTFNFfQpzeXN0ZW0yKGNvbW1hbmQgPSBtc19iaW5hcnkoKSwgYXJncyA9ICI1MCAxMDAwMCAtcyAxIC1HIDUiLCBzdGRvdXQgPSAib3V0LnNucHMiLCBzdGRlcnIgPSBGQUxTRSkKbXMyc2ZzKDUwKSAlPiUgcGxvdF9zZnMoKQpgYGAKCgpgYGB7ciwgZXZhbD1GQUxTRX0Kc3lzdGVtMihjb21tYW5kID0gbXNfYmluYXJ5KCksIGFyZ3MgPSAiNTAgMTAwMDAgLXMgMSAtZW4gMC4yNSAxIDI1Iiwgc3Rkb3V0ID0gIm91dC5zbnBzIiwgc3RkZXJyID0gRkFMU0UpCm1zMnNmcyg1MCkgJT4lIHBsb3Rfc2ZzKCkKYGBgCgpBbmQgZm9yIHRoZSBzdXBlciBib251cyBwcm9ibGVtOgpgYGB7ciwgZXZhbD1GQUxTRX0Kc3lzdGVtMihjb21tYW5kID0gbXNfYmluYXJ5KCksIGFyZ3MgPSAiNTAgMTAwMDAgLXMgMSAtZXMgMC4xIDEgMC41IC1laiA2LjAgMSAyIiwgc3Rkb3V0ID0gIm91dC5zbnBzIiwgc3RkZXJyID0gRkFMU0UpCm1zMnNmcyg1MCkgJT4lIHBsb3Rfc2ZzKCkKYGBg