Simulated Data

For the lab today, we will use a simulated data set, similar to the CMV data set used for the homework. To begin, we create a genome sequence of length 200,000, with 300 palindrome sites.

N <- 200000
n <- 300
set.seed(318)
gene <- seq(1, N)
site.random <- sample.int(N, size=n)

To visualize our simulated data graphically, we can do the following. Notice that the the way we selected our sites uniformly among 200,000 genome locations in the example. This can be verified with the graph, as the points are scattered uniformly across the number line.

library(lattice)
stripplot(site.random, pch=16, cex=0.25)

Now we will create another example, which selects the locations according to some predetermined distribution. In this case, we sample according to normal distribution. To sample according to a specified distribution, we can provide the prob in the sample function. However, we do need to specify a vector of probability weights.

set.seed(214)
norm.quant <- seq(-3, 3, length.out=N)
site.norm <- sample.int(N, size=n, prob=dnorm(norm.quant))

We expect to see more points to concentrate around the middle of the 200,000 locations, which is the case if we examine the graph. In this scenario, palindromes tend to center among the possible gene locations.

library(lattice)
stripplot(site.norm, pch=16, cex=0.25)

The third example that we are going to look at is we randomly pick 30 locations out of the 200,000. For the 30 locations selected, we will assign them double the likelihood of being selected for the 300 palindrome sites as for the rest 199,970.

set.seed(215)
gene.double <- sample.int(N, size=30)
gene.weight <- rep(0.5, N)
gene.weight[gene.double] <- gene.weight[gene.double] + 0.5
set.seed(215)
site.double <- sample.int(N, size=n, prob=gene.weight)

Let’s see how this turns out to be graphically. As one can see, just by comparing the two graphs, site.double hardly has much difference from site.random. However, as we know the underlying truth, the two are not the same. Let’s see if we can distinguish the two as we move on to more rigorous analysis.

library(lattice)
stripplot(site.double, pch=16, cex=0.25)

Non-overlapping Interval Counts

We will be working with these three simulated data set as our examples today. One has site locations uniformly randomly generated, the second one has site locations generated according to normal distribution, and the third one is close to uniformly randomly selected, but not exactly. The next step is probably to split the region into non-overlapping regions of equal length and examine the counts. The information can be easily obtained using the cut function. Below is a custom function in R. By this point, you probably have used many functions in R already. It is not too hard to write one.

regionsplit <- function(n.region, gene, site){
  count.int <- table(cut(site, breaks = seq(1, length(gene), length.out=n.region+1), include.lowest=TRUE))
  count.vector <- as.vector(count.int)
  count.tab <- table(count.vector)
  return (count.tab)
}

Let’s take a look at the counts using different number of regions. As one can see, the one with normally generated sites is easy to pick out even with smaller number of regions. However, it is still difficult to distinguish between the other two.

n.region <- 50
regionsplit(n.region, gene, site.random)
count.vector
 2  3  4  5  6  7  8  9 10 11 
 3  4  6  9  7  7  8  3  2  1 
regionsplit(n.region, gene, site.norm)
count.vector
 0  1  2  3  4  5  6  8 10 11 12 13 14 15 16 18 
10  7  2  6  1  2  2  2  4  2  3  2  4  1  1  1 
regionsplit(n.region, gene, site.double)
count.vector
 0  1  2  3  4  5  6  7  8  9 10 
 1  1  2  5  6  5 10  3  9  2  6 

\(\chi^2\) Test

We are now ready to perform more rigorous testing procedures to detect which one is a random scatter and which one is not. Let’s expand our function, so that it will be capable of returning the table necessary for \(\chi^2\) test as described in the slides.

chisqtable <- function(n.region, site, N){
  n <- length(site)
  lambda.est <- n/n.region
  count.int <- table(cut(site, breaks = seq(1, length(gene), length.out=n.region+1), include.lowest=TRUE))
  count.vector <- as.vector(count.int)
  count.range <- max(count.vector) - min(count.vector) + 1
  
  table <- matrix(rep(NA, count.range*3), count.range, 3)
  for (i in 1:count.range){
    offset <- min(count.vector) - 1
    table[i, 1] <- i + offset
    table[i, 2] <- sum(count.vector == i + offset)
    if ((i + offset == min(count.vector)) && (min(count.vector) != 0))
      table[i, 3] <- ppois(i+offset, lambda.est)*n.region
    else if (i + offset == max(count.vector))
      table[i, 3] <- 1 - ppois(i + offset - 1, lambda.est)
    else
      table[i, 3] <- (ppois(i+offset, lambda.est) - ppois(i + offset - 1, lambda.est))*n.region
  }
  
  return (table)
}

Let’s take a look at the three output tables.

site.random.tabtemp <- chisqtable(n.region, site.random, N)
site.norm.tabtemp <- chisqtable(n.region, site.norm, N)
site.double.tabtemp <- chisqtable(n.region, site.double, N)

After examining the tables, we notice there are counts that can be grouped together, especially for the normally generated site locations, as many of the observed and expected number of occurrence are small. The following is the \(chi^2\) testing of the uniformly randomly selected gene locations.

site.random.tab <- matrix(rep(NA, 7*2), 7, 2)
site.random.tab[1,] <- colSums(site.random.tabtemp[1:2, 2:3])
site.random.tab[2:6,] <- site.random.tabtemp[3:7, 2:3]
site.random.tab[7,] <- colSums(site.random.tabtemp[8:10, 2:3])
site.random.stats <- sum((site.random.tab[,2] - site.random.tab[,1])^2/site.random.tab[,2])
pchisq(site.random.stats, 7 - 2, lower.tail=FALSE)
[1] 0.85465

For the sites selected according to normally distribution, there are more count categories to be agglomerated.

site.norm.tab <- matrix(rep(NA, 7*2), 7, 2)
site.norm.tab[1,] <- colSums(site.norm.tabtemp[1:4, 2:3])
site.norm.tab[2:6,] <- site.norm.tabtemp[5:9, 2:3]
site.norm.tab[7,] <- colSums(site.norm.tabtemp[10:19, 2:3])
site.norm.stats <- sum((site.norm.tab[,2] - site.norm.tab[,1])^2/site.norm.tab[,2])
pchisq(site.norm.stats, 7 - 2, lower.tail=FALSE)
[1] 3.519719e-15

Lastly, for the ones selected with double the likelihood in 30 locations. We have the following result.

site.double.tab <- matrix(rep(NA, 7*2), 7, 2)
site.double.tab[1,] <- colSums(site.double.tabtemp[1:4, 2:3])
site.double.tab[2:6,] <- site.double.tabtemp[5:9, 2:3]
site.double.tab[7,] <- colSums(site.double.tabtemp[10:11, 2:3])
site.double.stats <- sum((site.double.tab[,2] - site.double.tab[,1])^2/site.double.tab[,2])
pchisq(site.double.stats, 7 - 2, lower.tail=FALSE)
[1] 0.02643059

Keep in mind that the null hypothesis of the testing procedures above assumes the site locations are formed by a random scatter. Thus, the conclusions show that with a significance level of 0.05, we are able to reject the null for the cases of normally distributed site locations and of twice the likelihood in 30 locations.

LS0tCnRpdGxlOiAiTWF0aCAxODkvMjg5IExhYiA2IgphdXRob3I6ICJKaWFxaSBHdW8iCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0Ci0tLQoKIyBTaW11bGF0ZWQgRGF0YQpGb3IgdGhlIGxhYiB0b2RheSwgd2Ugd2lsbCB1c2UgYSBzaW11bGF0ZWQgZGF0YSBzZXQsIHNpbWlsYXIgdG8gdGhlIENNViBkYXRhIHNldCB1c2VkIGZvciB0aGUgaG9tZXdvcmsuIFRvIGJlZ2luLCB3ZSBjcmVhdGUgYSBnZW5vbWUgc2VxdWVuY2Ugb2YgbGVuZ3RoIDIwMCwwMDAsIHdpdGggMzAwIHBhbGluZHJvbWUgc2l0ZXMuCmBgYHtyfQpOIDwtIDIwMDAwMApuIDwtIDMwMApzZXQuc2VlZCgzMTgpCmdlbmUgPC0gc2VxKDEsIE4pCnNpdGUucmFuZG9tIDwtIHNhbXBsZS5pbnQoTiwgc2l6ZT1uKQpgYGAKVG8gdmlzdWFsaXplIG91ciBzaW11bGF0ZWQgZGF0YSBncmFwaGljYWxseSwgd2UgY2FuIGRvIHRoZSBmb2xsb3dpbmcuIE5vdGljZSB0aGF0IHRoZSB0aGUgd2F5IHdlIHNlbGVjdGVkIG91ciBzaXRlcyB1bmlmb3JtbHkgYW1vbmcgMjAwLDAwMCBnZW5vbWUgbG9jYXRpb25zIGluIHRoZSBleGFtcGxlLiBUaGlzIGNhbiBiZSB2ZXJpZmllZCB3aXRoIHRoZSBncmFwaCwgYXMgdGhlIHBvaW50cyBhcmUgc2NhdHRlcmVkIHVuaWZvcm1seSBhY3Jvc3MgdGhlIG51bWJlciBsaW5lLgpgYGB7cn0KbGlicmFyeShsYXR0aWNlKQpzdHJpcHBsb3Qoc2l0ZS5yYW5kb20sIHBjaD0xNiwgY2V4PTAuMjUpCmBgYAoKTm93IHdlIHdpbGwgY3JlYXRlIGFub3RoZXIgZXhhbXBsZSwgd2hpY2ggc2VsZWN0cyB0aGUgbG9jYXRpb25zIGFjY29yZGluZyB0byBzb21lIHByZWRldGVybWluZWQgZGlzdHJpYnV0aW9uLiBJbiB0aGlzIGNhc2UsIHdlIHNhbXBsZSBhY2NvcmRpbmcgdG8gbm9ybWFsIGRpc3RyaWJ1dGlvbi4gClRvIHNhbXBsZSBhY2NvcmRpbmcgdG8gYSBzcGVjaWZpZWQgZGlzdHJpYnV0aW9uLCB3ZSBjYW4gcHJvdmlkZSB0aGUgKnByb2IqIGluIHRoZSAqc2FtcGxlKiBmdW5jdGlvbi4gSG93ZXZlciwgd2UgZG8gbmVlZCB0byBzcGVjaWZ5IGEgdmVjdG9yIG9mIHByb2JhYmlsaXR5IHdlaWdodHMuCmBgYHtyfQpzZXQuc2VlZCgyMTQpCm5vcm0ucXVhbnQgPC0gc2VxKC0zLCAzLCBsZW5ndGgub3V0PU4pCnNpdGUubm9ybSA8LSBzYW1wbGUuaW50KE4sIHNpemU9biwgcHJvYj1kbm9ybShub3JtLnF1YW50KSkKYGBgCldlIGV4cGVjdCB0byBzZWUgbW9yZSBwb2ludHMgdG8gY29uY2VudHJhdGUgYXJvdW5kIHRoZSBtaWRkbGUgb2YgdGhlIDIwMCwwMDAgbG9jYXRpb25zLCB3aGljaCBpcyB0aGUgY2FzZSBpZiB3ZSBleGFtaW5lIHRoZSBncmFwaC4gSW4gdGhpcyBzY2VuYXJpbywgcGFsaW5kcm9tZXMgdGVuZCB0byBjZW50ZXIgYW1vbmcgdGhlIHBvc3NpYmxlIGdlbmUgbG9jYXRpb25zLgpgYGB7cn0KbGlicmFyeShsYXR0aWNlKQpzdHJpcHBsb3Qoc2l0ZS5ub3JtLCBwY2g9MTYsIGNleD0wLjI1KQpgYGAKClRoZSB0aGlyZCBleGFtcGxlIHRoYXQgd2UgYXJlIGdvaW5nIHRvIGxvb2sgYXQgaXMgd2UgcmFuZG9tbHkgcGljayAzMCBsb2NhdGlvbnMgb3V0IG9mIHRoZSAyMDAsMDAwLiBGb3IgdGhlIDMwIGxvY2F0aW9ucyBzZWxlY3RlZCwgd2Ugd2lsbCBhc3NpZ24gdGhlbSBkb3VibGUgdGhlIGxpa2VsaWhvb2Qgb2YgYmVpbmcgc2VsZWN0ZWQgZm9yIHRoZSAzMDAgcGFsaW5kcm9tZSBzaXRlcyBhcyBmb3IgdGhlIHJlc3QgMTk5LDk3MC4KYGBge3J9CnNldC5zZWVkKDIxNSkKZ2VuZS5kb3VibGUgPC0gc2FtcGxlLmludChOLCBzaXplPTMwKQpnZW5lLndlaWdodCA8LSByZXAoMC41LCBOKQpnZW5lLndlaWdodFtnZW5lLmRvdWJsZV0gPC0gZ2VuZS53ZWlnaHRbZ2VuZS5kb3VibGVdICsgMC41CnNldC5zZWVkKDIxNSkKc2l0ZS5kb3VibGUgPC0gc2FtcGxlLmludChOLCBzaXplPW4sIHByb2I9Z2VuZS53ZWlnaHQpCmBgYApMZXQncyBzZWUgaG93IHRoaXMgdHVybnMgb3V0IHRvIGJlIGdyYXBoaWNhbGx5LiBBcyBvbmUgY2FuIHNlZSwganVzdCBieSBjb21wYXJpbmcgdGhlIHR3byBncmFwaHMsICpzaXRlLmRvdWJsZSogaGFyZGx5IGhhcyBtdWNoIGRpZmZlcmVuY2UgZnJvbSAqc2l0ZS5yYW5kb20qLiBIb3dldmVyLCBhcyB3ZSBrbm93IHRoZSB1bmRlcmx5aW5nIHRydXRoLCB0aGUgdHdvIGFyZSBub3QgdGhlIHNhbWUuIExldCdzIHNlZSBpZiB3ZSBjYW4gZGlzdGluZ3Vpc2ggdGhlIHR3byBhcyB3ZSBtb3ZlIG9uIHRvIG1vcmUgcmlnb3JvdXMgYW5hbHlzaXMuCmBgYHtyfQpsaWJyYXJ5KGxhdHRpY2UpCnN0cmlwcGxvdChzaXRlLmRvdWJsZSwgcGNoPTE2LCBjZXg9MC4yNSkKYGBgCgojIE5vbi1vdmVybGFwcGluZyBJbnRlcnZhbCBDb3VudHMKV2Ugd2lsbCBiZSB3b3JraW5nIHdpdGggdGhlc2UgdGhyZWUgc2ltdWxhdGVkIGRhdGEgc2V0IGFzIG91ciBleGFtcGxlcyB0b2RheS4gT25lIGhhcyBzaXRlIGxvY2F0aW9ucyB1bmlmb3JtbHkgcmFuZG9tbHkgZ2VuZXJhdGVkLCB0aGUgc2Vjb25kIG9uZSBoYXMgc2l0ZSBsb2NhdGlvbnMgZ2VuZXJhdGVkIGFjY29yZGluZyB0byBub3JtYWwgZGlzdHJpYnV0aW9uLCBhbmQgdGhlIHRoaXJkIG9uZSBpcyBjbG9zZSB0byB1bmlmb3JtbHkgcmFuZG9tbHkgc2VsZWN0ZWQsIGJ1dCBub3QgZXhhY3RseS4KVGhlIG5leHQgc3RlcCBpcyBwcm9iYWJseSB0byBzcGxpdCB0aGUgcmVnaW9uIGludG8gbm9uLW92ZXJsYXBwaW5nIHJlZ2lvbnMgb2YgZXF1YWwgbGVuZ3RoIGFuZCBleGFtaW5lIHRoZSBjb3VudHMuIFRoZSBpbmZvcm1hdGlvbiBjYW4gYmUgZWFzaWx5IG9idGFpbmVkIHVzaW5nIHRoZSAqY3V0KiBmdW5jdGlvbi4KQmVsb3cgaXMgYSBjdXN0b20gZnVuY3Rpb24gaW4gUi4gQnkgdGhpcyBwb2ludCwgeW91IHByb2JhYmx5IGhhdmUgdXNlZCBtYW55IGZ1bmN0aW9ucyBpbiBSIGFscmVhZHkuIEl0IGlzIG5vdCB0b28gaGFyZCB0byB3cml0ZSBvbmUuCmBgYHtyfQpyZWdpb25zcGxpdCA8LSBmdW5jdGlvbihuLnJlZ2lvbiwgZ2VuZSwgc2l0ZSl7CiAgY291bnQuaW50IDwtIHRhYmxlKGN1dChzaXRlLCBicmVha3MgPSBzZXEoMSwgbGVuZ3RoKGdlbmUpLCBsZW5ndGgub3V0PW4ucmVnaW9uKzEpLCBpbmNsdWRlLmxvd2VzdD1UUlVFKSkKICBjb3VudC52ZWN0b3IgPC0gYXMudmVjdG9yKGNvdW50LmludCkKICBjb3VudC50YWIgPC0gdGFibGUoY291bnQudmVjdG9yKQogIHJldHVybiAoY291bnQudGFiKQp9CmBgYApMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgY291bnRzIHVzaW5nIGRpZmZlcmVudCBudW1iZXIgb2YgcmVnaW9ucy4gQXMgb25lIGNhbiBzZWUsIHRoZSBvbmUgd2l0aCBub3JtYWxseSBnZW5lcmF0ZWQgc2l0ZXMgaXMgZWFzeSB0byBwaWNrIG91dCBldmVuIHdpdGggc21hbGxlciBudW1iZXIgb2YgcmVnaW9ucy4gSG93ZXZlciwgaXQgaXMgc3RpbGwgZGlmZmljdWx0IHRvIGRpc3Rpbmd1aXNoIGJldHdlZW4gdGhlIG90aGVyIHR3by4KYGBge3J9Cm4ucmVnaW9uIDwtIDUwCnJlZ2lvbnNwbGl0KG4ucmVnaW9uLCBnZW5lLCBzaXRlLnJhbmRvbSkKcmVnaW9uc3BsaXQobi5yZWdpb24sIGdlbmUsIHNpdGUubm9ybSkKcmVnaW9uc3BsaXQobi5yZWdpb24sIGdlbmUsIHNpdGUuZG91YmxlKQpgYGAKCiMgJFxjaGleMiQgVGVzdApXZSBhcmUgbm93IHJlYWR5IHRvIHBlcmZvcm0gbW9yZSByaWdvcm91cyB0ZXN0aW5nIHByb2NlZHVyZXMgdG8gZGV0ZWN0IHdoaWNoIG9uZSBpcyBhIHJhbmRvbSBzY2F0dGVyIGFuZCB3aGljaCBvbmUgaXMgbm90LiBMZXQncyBleHBhbmQgb3VyIGZ1bmN0aW9uLCBzbyB0aGF0IGl0IHdpbGwgYmUgY2FwYWJsZSBvZiByZXR1cm5pbmcgdGhlIHRhYmxlIG5lY2Vzc2FyeSBmb3IgJFxjaGleMiQgdGVzdCBhcyBkZXNjcmliZWQgaW4gdGhlIHNsaWRlcy4KYGBge3J9CmNoaXNxdGFibGUgPC0gZnVuY3Rpb24obi5yZWdpb24sIHNpdGUsIE4pewogIG4gPC0gbGVuZ3RoKHNpdGUpCiAgIyBsYW1iZGEgZXN0aW1hdGUKICBsYW1iZGEuZXN0IDwtIG4vbi5yZWdpb24KICAjIGN1dCBpbnRvIG4ucmVnaW9uIG51bWJlciBvZiBub24tb3ZlcmxhcHBpbmcgaW50ZXJ2YWxzCiAgY291bnQuaW50IDwtIHRhYmxlKGN1dChzaXRlLCBicmVha3MgPSBzZXEoMSwgbGVuZ3RoKGdlbmUpLCBsZW5ndGgub3V0PW4ucmVnaW9uKzEpLCBpbmNsdWRlLmxvd2VzdD1UUlVFKSkKICAjIGdldCB0aGUgY291bnQgbGV2ZWxzIHJhbmdlCiAgY291bnQudmVjdG9yIDwtIGFzLnZlY3Rvcihjb3VudC5pbnQpCiAgY291bnQucmFuZ2UgPC0gbWF4KGNvdW50LnZlY3RvcikgLSBtaW4oY291bnQudmVjdG9yKSArIDEKICAKICAjIGNyZWF0ZSBjb250aW5nZW5jeSB0YWJsZQogIHRhYmxlIDwtIG1hdHJpeChyZXAoTkEsIGNvdW50LnJhbmdlKjMpLCBjb3VudC5yYW5nZSwgMykKICBmb3IgKGkgaW4gMTpjb3VudC5yYW5nZSl7CiAgICBvZmZzZXQgPC0gbWluKGNvdW50LnZlY3RvcikgLSAxCiAgICAjIGZpcnN0IGNvbHVtbiA9IGNvdW50IGxldmVsCiAgICB0YWJsZVtpLCAxXSA8LSBpICsgb2Zmc2V0CiAgICAjIHNlY29uZCBjb2x1bW4gPSBvYnNlcnZlZCBjb3VudAogICAgdGFibGVbaSwgMl0gPC0gc3VtKGNvdW50LnZlY3RvciA9PSBpICsgb2Zmc2V0KQogICAgIyB0aGlyZCBjb2x1bW4gPSBleHBlY3RlZCBjb3VudAogICAgaWYgKChpICsgb2Zmc2V0ID09IG1pbihjb3VudC52ZWN0b3IpKSAmJiAobWluKGNvdW50LnZlY3RvcikgIT0gMCkpCiAgICAgIHRhYmxlW2ksIDNdIDwtIHBwb2lzKGkrb2Zmc2V0LCBsYW1iZGEuZXN0KSpuLnJlZ2lvbgogICAgZWxzZSBpZiAoaSArIG9mZnNldCA9PSBtYXgoY291bnQudmVjdG9yKSkKICAgICAgdGFibGVbaSwgM10gPC0gMSAtIHBwb2lzKGkgKyBvZmZzZXQgLSAxLCBsYW1iZGEuZXN0KQogICAgZWxzZQogICAgICB0YWJsZVtpLCAzXSA8LSAocHBvaXMoaStvZmZzZXQsIGxhbWJkYS5lc3QpIC0gcHBvaXMoaSArIG9mZnNldCAtIDEsIGxhbWJkYS5lc3QpKSpuLnJlZ2lvbgogIH0KICAKICByZXR1cm4gKHRhYmxlKQp9CmBgYAoKTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIHRocmVlIG91dHB1dCB0YWJsZXMuCmBgYHtyfQpzaXRlLnJhbmRvbS50YWJ0ZW1wIDwtIGNoaXNxdGFibGUobi5yZWdpb24sIHNpdGUucmFuZG9tLCBOKQpzaXRlLm5vcm0udGFidGVtcCA8LSBjaGlzcXRhYmxlKG4ucmVnaW9uLCBzaXRlLm5vcm0sIE4pCnNpdGUuZG91YmxlLnRhYnRlbXAgPC0gY2hpc3F0YWJsZShuLnJlZ2lvbiwgc2l0ZS5kb3VibGUsIE4pCmBgYApBZnRlciBleGFtaW5pbmcgdGhlIHRhYmxlcywgd2Ugbm90aWNlIHRoZXJlIGFyZSBjb3VudHMgdGhhdCBjYW4gYmUgZ3JvdXBlZCB0b2dldGhlciwgZXNwZWNpYWxseSBmb3IgdGhlIG5vcm1hbGx5IGdlbmVyYXRlZCBzaXRlIGxvY2F0aW9ucywgYXMgbWFueSBvZiB0aGUgb2JzZXJ2ZWQgYW5kIGV4cGVjdGVkIG51bWJlciBvZiBvY2N1cnJlbmNlIGFyZSBzbWFsbC4gVGhlIGZvbGxvd2luZyBpcyB0aGUgJGNoaV4yJCB0ZXN0aW5nIG9mIHRoZSB1bmlmb3JtbHkgcmFuZG9tbHkgc2VsZWN0ZWQgZ2VuZSBsb2NhdGlvbnMuCmBgYHtyfQpzaXRlLnJhbmRvbS50YWIgPC0gbWF0cml4KHJlcChOQSwgNyoyKSwgNywgMikKc2l0ZS5yYW5kb20udGFiWzEsXSA8LSBjb2xTdW1zKHNpdGUucmFuZG9tLnRhYnRlbXBbMToyLCAyOjNdKQpzaXRlLnJhbmRvbS50YWJbMjo2LF0gPC0gc2l0ZS5yYW5kb20udGFidGVtcFszOjcsIDI6M10Kc2l0ZS5yYW5kb20udGFiWzcsXSA8LSBjb2xTdW1zKHNpdGUucmFuZG9tLnRhYnRlbXBbODoxMCwgMjozXSkKc2l0ZS5yYW5kb20uc3RhdHMgPC0gc3VtKChzaXRlLnJhbmRvbS50YWJbLDJdIC0gc2l0ZS5yYW5kb20udGFiWywxXSleMi9zaXRlLnJhbmRvbS50YWJbLDJdKQpwY2hpc3Eoc2l0ZS5yYW5kb20uc3RhdHMsIDcgLSAyLCBsb3dlci50YWlsPUZBTFNFKQpgYGAKRm9yIHRoZSBzaXRlcyBzZWxlY3RlZCBhY2NvcmRpbmcgdG8gbm9ybWFsbHkgZGlzdHJpYnV0aW9uLCB0aGVyZSBhcmUgbW9yZSBjb3VudCBjYXRlZ29yaWVzIHRvIGJlIGFnZ2xvbWVyYXRlZC4KYGBge3J9CnNpdGUubm9ybS50YWIgPC0gbWF0cml4KHJlcChOQSwgNyoyKSwgNywgMikKc2l0ZS5ub3JtLnRhYlsxLF0gPC0gY29sU3VtcyhzaXRlLm5vcm0udGFidGVtcFsxOjQsIDI6M10pCnNpdGUubm9ybS50YWJbMjo2LF0gPC0gc2l0ZS5ub3JtLnRhYnRlbXBbNTo5LCAyOjNdCnNpdGUubm9ybS50YWJbNyxdIDwtIGNvbFN1bXMoc2l0ZS5ub3JtLnRhYnRlbXBbMTA6MTksIDI6M10pCnNpdGUubm9ybS5zdGF0cyA8LSBzdW0oKHNpdGUubm9ybS50YWJbLDJdIC0gc2l0ZS5ub3JtLnRhYlssMV0pXjIvc2l0ZS5ub3JtLnRhYlssMl0pCnBjaGlzcShzaXRlLm5vcm0uc3RhdHMsIDcgLSAyLCBsb3dlci50YWlsPUZBTFNFKQpgYGAKTGFzdGx5LCBmb3IgdGhlIG9uZXMgc2VsZWN0ZWQgd2l0aCBkb3VibGUgdGhlIGxpa2VsaWhvb2QgaW4gMzAgbG9jYXRpb25zLiBXZSBoYXZlIHRoZSBmb2xsb3dpbmcgcmVzdWx0LgpgYGB7cn0Kc2l0ZS5kb3VibGUudGFiIDwtIG1hdHJpeChyZXAoTkEsIDcqMiksIDcsIDIpCnNpdGUuZG91YmxlLnRhYlsxLF0gPC0gY29sU3VtcyhzaXRlLmRvdWJsZS50YWJ0ZW1wWzE6NCwgMjozXSkKc2l0ZS5kb3VibGUudGFiWzI6NixdIDwtIHNpdGUuZG91YmxlLnRhYnRlbXBbNTo5LCAyOjNdCnNpdGUuZG91YmxlLnRhYls3LF0gPC0gY29sU3VtcyhzaXRlLmRvdWJsZS50YWJ0ZW1wWzEwOjExLCAyOjNdKQpzaXRlLmRvdWJsZS5zdGF0cyA8LSBzdW0oKHNpdGUuZG91YmxlLnRhYlssMl0gLSBzaXRlLmRvdWJsZS50YWJbLDFdKV4yL3NpdGUuZG91YmxlLnRhYlssMl0pCnBjaGlzcShzaXRlLmRvdWJsZS5zdGF0cywgNyAtIDIsIGxvd2VyLnRhaWw9RkFMU0UpCmBgYAoKS2VlcCBpbiBtaW5kIHRoYXQgdGhlIG51bGwgaHlwb3RoZXNpcyBvZiB0aGUgdGVzdGluZyBwcm9jZWR1cmVzIGFib3ZlIGFzc3VtZXMgdGhlIHNpdGUgbG9jYXRpb25zIGFyZSBmb3JtZWQgYnkgYSByYW5kb20gc2NhdHRlci4gVGh1cywgdGhlIGNvbmNsdXNpb25zIHNob3cgdGhhdCB3aXRoIGEgc2lnbmlmaWNhbmNlIGxldmVsIG9mIDAuMDUsIHdlIGFyZSBhYmxlIHRvIHJlamVjdCB0aGUgbnVsbCBmb3IgdGhlIGNhc2VzIG9mIG5vcm1hbGx5IGRpc3RyaWJ1dGVkIHNpdGUgbG9jYXRpb25zIGFuZCBvZiB0d2ljZSB0aGUgbGlrZWxpaG9vZCBpbiAzMCBsb2NhdGlvbnMuIA==