In this notebook, we will try to see how the scale of a substitution rate matrix matters.

library(expm) # to compute a matrix exponential

Useful functions for simulating DNA evolution along a branch

# Function for drawing a DNA state, for instance at the root
# We assume the order is A, C, G, T
drawStateAtRoot <- function (probabilities){
    states <- c("A", "C", "G", "T")
    u <- runif(n=1, min=0,max=1)
    if (u < probabilities[1])
      { return (states[1])}
    else if (u < probabilities[1]+probabilities[2])
      { return (states[2])}
    else if (u < probabilities[1]+probabilities[2]+probabilities[3])
      { return (states[3])}
    else { return (states[4])}
}

# Function to draw a state on a branch
drawNewState <- function (probabilities, states){
    u <- runif(n=1, min=0,max=1)
    if (u < probabilities[1])
      { return (states[1])}
    else if (u < probabilities[1]+probabilities[2])
      { return (states[2])}
    else { return (states[3])}
}

# Function to compute the stationary frequencies associated to a rate matrix
computeStationaryFrequencies <- function(rateMatrix) {
  return(expm(rateMatrix*100)[1,])
}

# Function to simulate along a branch using the Gillespie algorithm,
# under a particular rate matrix, given some branch length, 
# and given a starting state.
simulateAlongBranch <- function(startingState, branchLength, rateMatrix) {
    l <- 0.0
    numberOfSubstitutions <- 0
    current <- startingState
    while (l < branchLength) {
        currentRow=1
        if (current =='C') {
          currentRow=2  
        }
        else if (current =='G') {
          currentRow=3  
        }
        else if (current =='T') {
          currentRow=4  
        }
        # rateOfMovingAway is the sum of the rates of all possible events, 
        # aka minus the diagonal element
        rateOfMovingAway = - rateMatrix[currentRow, currentRow]
        l <- l + rexp(rate=rateOfMovingAway, n=1) 
        if (l < branchLength) {
            numberOfSubstitutions <- numberOfSubstitutions + 1
            possibleArrivingIndices <- setdiff(c(1,2,3,4), currentRow)
            probabilitiesOfArrivingStates <- rateMatrix[currentRow, possibleArrivingIndices]/rateOfMovingAway
            current <- drawNewState(probabilitiesOfArrivingStates, c("A", "C", "G", "T")[possibleArrivingIndices])
        }
    }
    return (c(current, numberOfSubstitutions))
}

# Simulate a site history given starting (root) frequencies, a branch length,
# and a rate matrix.
simulateSiteHistory <- function (startingFrequencies, branchLength, rateMatrix) {
  rootState <- drawStateAtRoot(startingFrequencies)
  return(simulateAlongBranch(startingState=rootState, branchLength=branchLength, rateMatrix=rateMatrix))
}

# Simulate numberOfSites site histories along a branch of length branchLength,
# with a rate matrix rateMatrix.
simulateManySiteHistories <- function (branchLength, rateMatrix, numberOfSites) {
  stationaryFrequencies <- computeStationaryFrequencies(rateMatrix)
  allHistories <- matrix(rep(NA, 2*numberOfSites), nrow=numberOfSites, ncol=2)
  for (i in 1:numberOfSites) {
    allHistories[i,] <- simulateSiteHistory(startingFrequencies=stationaryFrequencies, branchLength=branchLength, rateMatrix=rateMatrix)
  }
  return(allHistories)
}

# Plot the number of substitutions as contained in siteHistories.
plotNumbersOfSubstitutions <- function (siteHistories) {
  numbers <- as.numeric(siteHistories[,2])
  hist(numbers, main=paste0("Number of substitutions; mean = ", mean(numbers)), xlab="Numbers of substitutions", ylab="Number of sites")
}

Let’s create a substitution rate matrix, and look at it

substitutionRateMatrix <- 3*matrix(c(c(-1.916, 0.541, 0.787, 0.588), c(0.148, -1.069, 0.415, 0.506), c(0.286, 0.170, -0.591, 0.135), c(0.525, 0.236, 0.594, -1.355)), nrow=4, ncol=4, byrow=TRUE)
print(substitutionRateMatrix)
       [,1]   [,2]   [,3]   [,4]
[1,] -5.748  1.623  2.361  1.764
[2,]  0.444 -3.207  1.245  1.518
[3,]  0.858  0.510 -1.773  0.405
[4,]  1.575  0.708  1.782 -4.065

Let’s simulate 1000 sites with this substitution matrix

We want to simulate DNA evolution along a branch of length 0.5.

siteHistories <- simulateManySiteHistories(branchLength=0.5, rateMatrix=substitutionRateMatrix, numberOfSites=1000)

While doing our simulation, we counted the number of substitutions per site

Let’s look at them!

plotNumbersOfSubstitutions(siteHistories)

When simulating along a branch of length 0.5 with our substitution rate matrix, on average there are ~1.5 substitution per site. That’s not very convenient.

Scaling the substitution rate matrix

Let’s scale the substitution rate matrix: at stationarity, we want the total rate of substitution per unit time to be 1, i.e.:

\[\sum_{i \in {A,C,G,T}}~\sum_{j \in {A,C,G,T}} \pi_i q_{ij} = 1,~~i \neq j\] or, equivalently: \[-\sum_{i \in {A,C,G,T}} \pi_i q_{ii} = 1\]

What is the scale of our current matrix?

computeScaleOfMatrix <- function(rateMatrix) {
  stationaryFrequencies <- computeStationaryFrequencies(rateMatrix)
  qiis <- c(rateMatrix[1,1], rateMatrix[2,2], rateMatrix[3,3], rateMatrix[4,4])
  scale <- -(stationaryFrequencies[1] * qiis[1] + stationaryFrequencies[2] * qiis[2] + stationaryFrequencies[3] * qiis[3] + stationaryFrequencies[4] * qiis[4])
  return(scale)
}
scale <- computeScaleOfMatrix(substitutionRateMatrix)
print(scale)
[1] 3.00007

Our matrix does not have scale 1.0…

Rescaling our matrix

scaledSubstitutionRateMatrix <- substitutionRateMatrix / scale

Simulating with a scaled substitution matrix:


siteHistories <- simulateManySiteHistories(branchLength=0.5, rateMatrix=scaledSubstitutionRateMatrix, numberOfSites=1000)
plotNumbersOfSubstitutions(siteHistories)

When we simulate sites along a branch of length 0.5, with a scaled substitution rate matrix, we get on average 0.5 substitution per site.

Conclusion

To be able to interpret branch lengths as expected numbers of substitutions per site, we need to use scaled substitution rate matrices.

LS0tCnRpdGxlOiAiVGhlIHNjYWxlIG9mIGEgc3Vic3RpdHV0aW9uIHJhdGUgbWF0cml4IgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgoqSW4gdGhpcyBub3RlYm9vaywgd2Ugd2lsbCB0cnkgdG8gc2VlIGhvdyB0aGUgc2NhbGUgb2YgYSBzdWJzdGl0dXRpb24gcmF0ZSBtYXRyaXggbWF0dGVycy4qCgpgYGB7cn0KbGlicmFyeShleHBtKSAjIHRvIGNvbXB1dGUgYSBtYXRyaXggZXhwb25lbnRpYWwKYGBgCgojIyBVc2VmdWwgZnVuY3Rpb25zIGZvciBzaW11bGF0aW5nIEROQSBldm9sdXRpb24gYWxvbmcgYSBicmFuY2gKYGBge3J9CiMgRnVuY3Rpb24gZm9yIGRyYXdpbmcgYSBETkEgc3RhdGUsIGZvciBpbnN0YW5jZSBhdCB0aGUgcm9vdAojIFdlIGFzc3VtZSB0aGUgb3JkZXIgaXMgQSwgQywgRywgVApkcmF3U3RhdGVBdFJvb3QgPC0gZnVuY3Rpb24gKHByb2JhYmlsaXRpZXMpewogICAgc3RhdGVzIDwtIGMoIkEiLCAiQyIsICJHIiwgIlQiKQogICAgdSA8LSBydW5pZihuPTEsIG1pbj0wLG1heD0xKQogICAgaWYgKHUgPCBwcm9iYWJpbGl0aWVzWzFdKQogICAgICB7IHJldHVybiAoc3RhdGVzWzFdKX0KICAgIGVsc2UgaWYgKHUgPCBwcm9iYWJpbGl0aWVzWzFdK3Byb2JhYmlsaXRpZXNbMl0pCiAgICAgIHsgcmV0dXJuIChzdGF0ZXNbMl0pfQogICAgZWxzZSBpZiAodSA8IHByb2JhYmlsaXRpZXNbMV0rcHJvYmFiaWxpdGllc1syXStwcm9iYWJpbGl0aWVzWzNdKQogICAgICB7IHJldHVybiAoc3RhdGVzWzNdKX0KICAgIGVsc2UgeyByZXR1cm4gKHN0YXRlc1s0XSl9Cn0KCiMgRnVuY3Rpb24gdG8gZHJhdyBhIHN0YXRlIG9uIGEgYnJhbmNoCmRyYXdOZXdTdGF0ZSA8LSBmdW5jdGlvbiAocHJvYmFiaWxpdGllcywgc3RhdGVzKXsKICAgIHUgPC0gcnVuaWYobj0xLCBtaW49MCxtYXg9MSkKICAgIGlmICh1IDwgcHJvYmFiaWxpdGllc1sxXSkKICAgICAgeyByZXR1cm4gKHN0YXRlc1sxXSl9CiAgICBlbHNlIGlmICh1IDwgcHJvYmFiaWxpdGllc1sxXStwcm9iYWJpbGl0aWVzWzJdKQogICAgICB7IHJldHVybiAoc3RhdGVzWzJdKX0KICAgIGVsc2UgeyByZXR1cm4gKHN0YXRlc1szXSl9Cn0KCiMgRnVuY3Rpb24gdG8gY29tcHV0ZSB0aGUgc3RhdGlvbmFyeSBmcmVxdWVuY2llcyBhc3NvY2lhdGVkIHRvIGEgcmF0ZSBtYXRyaXgKY29tcHV0ZVN0YXRpb25hcnlGcmVxdWVuY2llcyA8LSBmdW5jdGlvbihyYXRlTWF0cml4KSB7CiAgcmV0dXJuKGV4cG0ocmF0ZU1hdHJpeCoxMDApWzEsXSkKfQoKIyBGdW5jdGlvbiB0byBzaW11bGF0ZSBhbG9uZyBhIGJyYW5jaCB1c2luZyB0aGUgR2lsbGVzcGllIGFsZ29yaXRobSwKIyB1bmRlciBhIHBhcnRpY3VsYXIgcmF0ZSBtYXRyaXgsIGdpdmVuIHNvbWUgYnJhbmNoIGxlbmd0aCwgCiMgYW5kIGdpdmVuIGEgc3RhcnRpbmcgc3RhdGUuCnNpbXVsYXRlQWxvbmdCcmFuY2ggPC0gZnVuY3Rpb24oc3RhcnRpbmdTdGF0ZSwgYnJhbmNoTGVuZ3RoLCByYXRlTWF0cml4KSB7CiAgICBsIDwtIDAuMAogICAgbnVtYmVyT2ZTdWJzdGl0dXRpb25zIDwtIDAKICAgIGN1cnJlbnQgPC0gc3RhcnRpbmdTdGF0ZQogICAgd2hpbGUgKGwgPCBicmFuY2hMZW5ndGgpIHsKICAgICAgICBjdXJyZW50Um93PTEKICAgICAgICBpZiAoY3VycmVudCA9PSdDJykgewogICAgICAgICAgY3VycmVudFJvdz0yICAKICAgICAgICB9CiAgICAgICAgZWxzZSBpZiAoY3VycmVudCA9PSdHJykgewogICAgICAgICAgY3VycmVudFJvdz0zICAKICAgICAgICB9CiAgICAgICAgZWxzZSBpZiAoY3VycmVudCA9PSdUJykgewogICAgICAgICAgY3VycmVudFJvdz00ICAKICAgICAgICB9CiAgICAgICAgIyByYXRlT2ZNb3ZpbmdBd2F5IGlzIHRoZSBzdW0gb2YgdGhlIHJhdGVzIG9mIGFsbCBwb3NzaWJsZSBldmVudHMsIAogICAgICAgICMgYWthIG1pbnVzIHRoZSBkaWFnb25hbCBlbGVtZW50CiAgICAgICAgcmF0ZU9mTW92aW5nQXdheSA9IC0gcmF0ZU1hdHJpeFtjdXJyZW50Um93LCBjdXJyZW50Um93XQogICAgICAgIGwgPC0gbCArIHJleHAocmF0ZT1yYXRlT2ZNb3ZpbmdBd2F5LCBuPTEpIAogICAgICAgIGlmIChsIDwgYnJhbmNoTGVuZ3RoKSB7CiAgICAgICAgICAgIG51bWJlck9mU3Vic3RpdHV0aW9ucyA8LSBudW1iZXJPZlN1YnN0aXR1dGlvbnMgKyAxCiAgICAgICAgICAgIHBvc3NpYmxlQXJyaXZpbmdJbmRpY2VzIDwtIHNldGRpZmYoYygxLDIsMyw0KSwgY3VycmVudFJvdykKICAgICAgICAgICAgcHJvYmFiaWxpdGllc09mQXJyaXZpbmdTdGF0ZXMgPC0gcmF0ZU1hdHJpeFtjdXJyZW50Um93LCBwb3NzaWJsZUFycml2aW5nSW5kaWNlc10vcmF0ZU9mTW92aW5nQXdheQogICAgICAgICAgICBjdXJyZW50IDwtIGRyYXdOZXdTdGF0ZShwcm9iYWJpbGl0aWVzT2ZBcnJpdmluZ1N0YXRlcywgYygiQSIsICJDIiwgIkciLCAiVCIpW3Bvc3NpYmxlQXJyaXZpbmdJbmRpY2VzXSkKICAgICAgICB9CiAgICB9CiAgICByZXR1cm4gKGMoY3VycmVudCwgbnVtYmVyT2ZTdWJzdGl0dXRpb25zKSkKfQoKIyBTaW11bGF0ZSBhIHNpdGUgaGlzdG9yeSBnaXZlbiBzdGFydGluZyAocm9vdCkgZnJlcXVlbmNpZXMsIGEgYnJhbmNoIGxlbmd0aCwKIyBhbmQgYSByYXRlIG1hdHJpeC4Kc2ltdWxhdGVTaXRlSGlzdG9yeSA8LSBmdW5jdGlvbiAoc3RhcnRpbmdGcmVxdWVuY2llcywgYnJhbmNoTGVuZ3RoLCByYXRlTWF0cml4KSB7CiAgcm9vdFN0YXRlIDwtIGRyYXdTdGF0ZUF0Um9vdChzdGFydGluZ0ZyZXF1ZW5jaWVzKQogIHJldHVybihzaW11bGF0ZUFsb25nQnJhbmNoKHN0YXJ0aW5nU3RhdGU9cm9vdFN0YXRlLCBicmFuY2hMZW5ndGg9YnJhbmNoTGVuZ3RoLCByYXRlTWF0cml4PXJhdGVNYXRyaXgpKQp9CgojIFNpbXVsYXRlIG51bWJlck9mU2l0ZXMgc2l0ZSBoaXN0b3JpZXMgYWxvbmcgYSBicmFuY2ggb2YgbGVuZ3RoIGJyYW5jaExlbmd0aCwKIyB3aXRoIGEgcmF0ZSBtYXRyaXggcmF0ZU1hdHJpeC4Kc2ltdWxhdGVNYW55U2l0ZUhpc3RvcmllcyA8LSBmdW5jdGlvbiAoYnJhbmNoTGVuZ3RoLCByYXRlTWF0cml4LCBudW1iZXJPZlNpdGVzKSB7CiAgc3RhdGlvbmFyeUZyZXF1ZW5jaWVzIDwtIGNvbXB1dGVTdGF0aW9uYXJ5RnJlcXVlbmNpZXMocmF0ZU1hdHJpeCkKICBhbGxIaXN0b3JpZXMgPC0gbWF0cml4KHJlcChOQSwgMipudW1iZXJPZlNpdGVzKSwgbnJvdz1udW1iZXJPZlNpdGVzLCBuY29sPTIpCiAgZm9yIChpIGluIDE6bnVtYmVyT2ZTaXRlcykgewogICAgYWxsSGlzdG9yaWVzW2ksXSA8LSBzaW11bGF0ZVNpdGVIaXN0b3J5KHN0YXJ0aW5nRnJlcXVlbmNpZXM9c3RhdGlvbmFyeUZyZXF1ZW5jaWVzLCBicmFuY2hMZW5ndGg9YnJhbmNoTGVuZ3RoLCByYXRlTWF0cml4PXJhdGVNYXRyaXgpCiAgfQogIHJldHVybihhbGxIaXN0b3JpZXMpCn0KCiMgUGxvdCB0aGUgbnVtYmVyIG9mIHN1YnN0aXR1dGlvbnMgYXMgY29udGFpbmVkIGluIHNpdGVIaXN0b3JpZXMuCnBsb3ROdW1iZXJzT2ZTdWJzdGl0dXRpb25zIDwtIGZ1bmN0aW9uIChzaXRlSGlzdG9yaWVzKSB7CiAgbnVtYmVycyA8LSBhcy5udW1lcmljKHNpdGVIaXN0b3JpZXNbLDJdKQogIGhpc3QobnVtYmVycywgbWFpbj1wYXN0ZTAoIk51bWJlciBvZiBzdWJzdGl0dXRpb25zOyBtZWFuID0gIiwgbWVhbihudW1iZXJzKSksIHhsYWI9Ik51bWJlcnMgb2Ygc3Vic3RpdHV0aW9ucyIsIHlsYWI9Ik51bWJlciBvZiBzaXRlcyIpCn0KCmBgYAoKCiMjIExldCdzIGNyZWF0ZSBhIHN1YnN0aXR1dGlvbiByYXRlIG1hdHJpeCwgYW5kIGxvb2sgYXQgaXQKYGBge3J9CnN1YnN0aXR1dGlvblJhdGVNYXRyaXggPC0gMyptYXRyaXgoYyhjKC0xLjkxNiwgMC41NDEsIDAuNzg3LCAwLjU4OCksIGMoMC4xNDgsIC0xLjA2OSwgMC40MTUsIDAuNTA2KSwgYygwLjI4NiwgMC4xNzAsIC0wLjU5MSwgMC4xMzUpLCBjKDAuNTI1LCAwLjIzNiwgMC41OTQsIC0xLjM1NSkpLCBucm93PTQsIG5jb2w9NCwgYnlyb3c9VFJVRSkKcHJpbnQoc3Vic3RpdHV0aW9uUmF0ZU1hdHJpeCkKYGBgCgojIyBMZXQncyBzaW11bGF0ZSAxMDAwIHNpdGVzIHdpdGggdGhpcyBzdWJzdGl0dXRpb24gbWF0cml4CldlIHdhbnQgdG8gc2ltdWxhdGUgRE5BIGV2b2x1dGlvbiBhbG9uZyBhIGJyYW5jaCBvZiBsZW5ndGggMC41LgpgYGB7cn0Kc2l0ZUhpc3RvcmllcyA8LSBzaW11bGF0ZU1hbnlTaXRlSGlzdG9yaWVzKGJyYW5jaExlbmd0aD0wLjUsIHJhdGVNYXRyaXg9c3Vic3RpdHV0aW9uUmF0ZU1hdHJpeCwgbnVtYmVyT2ZTaXRlcz0xMDAwKQpgYGAKCiMjIyBXaGlsZSBkb2luZyBvdXIgc2ltdWxhdGlvbiwgd2UgY291bnRlZCB0aGUgbnVtYmVyIG9mIHN1YnN0aXR1dGlvbnMgcGVyIHNpdGUKTGV0J3MgbG9vayBhdCB0aGVtIQpgYGB7cn0KcGxvdE51bWJlcnNPZlN1YnN0aXR1dGlvbnMoc2l0ZUhpc3RvcmllcykKYGBgCgoKV2hlbiBzaW11bGF0aW5nIGFsb25nIGEgYnJhbmNoIG9mIGxlbmd0aCAwLjUgd2l0aCBvdXIgc3Vic3RpdHV0aW9uIHJhdGUgbWF0cml4LCBvbiBhdmVyYWdlIHRoZXJlIGFyZSB+MS41IHN1YnN0aXR1dGlvbiBwZXIgc2l0ZS4gVGhhdCdzIG5vdCB2ZXJ5IGNvbnZlbmllbnQuCgoKIyMgU2NhbGluZyB0aGUgc3Vic3RpdHV0aW9uIHJhdGUgbWF0cml4CkxldCdzIHNjYWxlIHRoZSBzdWJzdGl0dXRpb24gcmF0ZSBtYXRyaXg6IGF0IHN0YXRpb25hcml0eSwgd2Ugd2FudCB0aGUgdG90YWwgcmF0ZSBvZiBzdWJzdGl0dXRpb24gcGVyIHVuaXQgdGltZSB0byBiZSAxLCBpLmUuOgoKJCRcc3VtX3tpIFxpbiB7QSxDLEcsVH19flxzdW1fe2ogXGluIHtBLEMsRyxUfX0gXHBpX2kgcV97aWp9ID0gMSx+fmkgXG5lcSBqJCQKb3IsIGVxdWl2YWxlbnRseToKJCQtXHN1bV97aSBcaW4ge0EsQyxHLFR9fSBccGlfaSBxX3tpaX0gPSAxJCQKCldoYXQgaXMgdGhlIHNjYWxlIG9mIG91ciBjdXJyZW50IG1hdHJpeD8KCmBgYHtyfQpjb21wdXRlU2NhbGVPZk1hdHJpeCA8LSBmdW5jdGlvbihyYXRlTWF0cml4KSB7CiAgc3RhdGlvbmFyeUZyZXF1ZW5jaWVzIDwtIGNvbXB1dGVTdGF0aW9uYXJ5RnJlcXVlbmNpZXMocmF0ZU1hdHJpeCkKICBxaWlzIDwtIGMocmF0ZU1hdHJpeFsxLDFdLCByYXRlTWF0cml4WzIsMl0sIHJhdGVNYXRyaXhbMywzXSwgcmF0ZU1hdHJpeFs0LDRdKQogIHNjYWxlIDwtIC0oc3RhdGlvbmFyeUZyZXF1ZW5jaWVzWzFdICogcWlpc1sxXSArIHN0YXRpb25hcnlGcmVxdWVuY2llc1syXSAqIHFpaXNbMl0gKyBzdGF0aW9uYXJ5RnJlcXVlbmNpZXNbM10gKiBxaWlzWzNdICsgc3RhdGlvbmFyeUZyZXF1ZW5jaWVzWzRdICogcWlpc1s0XSkKICByZXR1cm4oc2NhbGUpCn0KYGBgCgpgYGB7cn0Kc2NhbGUgPC0gY29tcHV0ZVNjYWxlT2ZNYXRyaXgoc3Vic3RpdHV0aW9uUmF0ZU1hdHJpeCkKcHJpbnQoc2NhbGUpCmBgYAoKT3VyIG1hdHJpeCBkb2VzIG5vdCBoYXZlIHNjYWxlIDEuMC4uLgoKIyMgUmVzY2FsaW5nIG91ciBtYXRyaXgKCmBgYHtyfQpzY2FsZWRTdWJzdGl0dXRpb25SYXRlTWF0cml4IDwtIHN1YnN0aXR1dGlvblJhdGVNYXRyaXggLyBzY2FsZQpgYGAKCiMjIFNpbXVsYXRpbmcgd2l0aCBhIHNjYWxlZCBzdWJzdGl0dXRpb24gbWF0cml4OgoKYGBge3J9CgpzaXRlSGlzdG9yaWVzIDwtIHNpbXVsYXRlTWFueVNpdGVIaXN0b3JpZXMoYnJhbmNoTGVuZ3RoPTAuNSwgcmF0ZU1hdHJpeD1zY2FsZWRTdWJzdGl0dXRpb25SYXRlTWF0cml4LCBudW1iZXJPZlNpdGVzPTEwMDApCnBsb3ROdW1iZXJzT2ZTdWJzdGl0dXRpb25zKHNpdGVIaXN0b3JpZXMpCgpgYGAKV2hlbiB3ZSBzaW11bGF0ZSBzaXRlcyBhbG9uZyBhIGJyYW5jaCBvZiBsZW5ndGggMC41LCB3aXRoIGEgc2NhbGVkIHN1YnN0aXR1dGlvbiByYXRlIG1hdHJpeCwgd2UgZ2V0IG9uIGF2ZXJhZ2UgMC41IHN1YnN0aXR1dGlvbiBwZXIgc2l0ZS4KCgojIENvbmNsdXNpb24KVG8gYmUgYWJsZSB0byBpbnRlcnByZXQgYnJhbmNoIGxlbmd0aHMgYXMgZXhwZWN0ZWQgbnVtYmVycyBvZiBzdWJzdGl0dXRpb25zIHBlciBzaXRlLCB3ZSBuZWVkIHRvIHVzZSBzY2FsZWQgc3Vic3RpdHV0aW9uIHJhdGUgbWF0cmljZXMuCgoKCg==