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.
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.
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.
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.
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