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