Introduction
Motivation
The SVD is one of the most well used and general purpose tools from linear algebra for data processing!
Methodologically
- Dimension reduction (e.g. images, gene expression data, movie preferences)
- Used as a first step in many data reduction and machine learning approaches
- Taylor a coordinate system based on the data we have
- Solve system of linear equations for non-square matrices: e.g. linear regression
- Basis for principal component analysis (PCA) and multidimensional scaling (MDS).
- PCA is one of the most widely used methods to study high dimensional data and to understand them in terms of their dominant patterns and correlations
Applications:
- At the heart of search engines: Google
- Basis of many facial recognition methods: e.g. Facebook
- Recommender systems such as Amazon and Netflix
- A standard tool for data exploration and dimension reduction in Genomics
Disclaimer
When you want to run the script you will have to comment out the eval=FALSE statement in some R chunks. Because the SVD takes a while on the faces example we save the svd for later use. So you have to comment the eval=FALSE statement in this chunk when you run the script for the first time.
Data
- Extended Yale Face Database B
- Cropped and aligned images of 38 individuals under 64 lighting conditions.
- Each image is 192 pixels tall and 168 pixels wide.
- Each of the facial images in our library will be reshaped into a large vector with 192 × 168 = 32 256 elements.
- We will use the 64 images of 36 people to build our models
library(pixmap)
library(tidyverse)
library(gridExtra)
library(grid)
library(ggmap)
library(downloader)
library(imager)
## Download and unzip data
if(!dir.exists("raw-data")) dir.create("raw-data")
download(
"https://github.com/statOmics/HDA2020/raw/data/yalefaces_cropped.zip",
destfile = "raw-data/yalefaces_cropped.zip", mode = "wb", quiet = TRUE
)
unzip ("raw-data/yalefaces_cropped.zip", exdir = "./raw-data")
dir <- "./raw-data/CroppedYale"
people <- list.files(dir)
people2 <- sapply(people,
function(x) list.files(
paste0(dir,"/",x),
full.names=TRUE
)
)
facesList <- lapply(people2, function(x) read.pnm(x))
grid.arrange(
grobs=lapply(facesList[1+(0:35)*64],
function(x) getChannels(x) %>%
ggimage(.,coord_equal=TRUE)
),
ncol=6)
Method
Let \(\mathbf{X}\) be an \(n\times p\) matrix e.g.
- gene expression of \(p=40 000\) genes for \(n=30\) subjects
- n = 100 000 000 webpages indexed with p search terms, or
- \(n\) images each stored as a \(p=32 256\) vector with the intensity of each pixel
Note: the emoji characters will not be visible in the PDF output.
\[X=
\left[\begin{array}{ccc}
-&\mathbf{x}_{1}^T &- \\
\vdots&\vdots&\vdots\\
-&\mathbf{x}_{i}^T &- \\
\vdots&\vdots&\vdots\\
-&\mathbf{x}_{n}^T &- \\
\end{array}\right]_{n \times p}
\begin{array}{c}
💇\\\\
👮\\
\\
👸\\
\end{array}
\]
The data matrix \(\mathbf{X}\) can be decomposed with the SVD into 3 matrices:
\[
\mathbf{X}=\mathbf{U}_{n\times n}\boldsymbol{\Delta}_{n\times p}\mathbf{V}^T_{p \times p}
\]
an orthonormal matrix \(\mathbf{U}_{n\times n}\) with left singular vectors: \(\mathbf{u}_j^T \mathbf{u}_k=1\) if \(k=j\) and \(\mathbf{u}_j^T \mathbf{u}_k=0\) if \(j\neq k\), i.e. \[ \mathbf{U}^T\mathbf{U}=\mathbf{I}\]
a matrix \(\boldsymbol{\Delta}_{n\times p}\) with only singular values: the singular values \(\delta_i\) are the only non-zero elements of the matrix and are on the diagonal element \([\boldsymbol{\Delta}]_{ii}\). They are also organised so that \(\delta_1 > \delta_2 > \ldots > \delta_r\).
an orthonormal matrix \(\mathbf{V}_{p\times p}\) with right singular vectors: \(\mathbf{v}_j^T \mathbf{v}_k=1\) if \(k=j\) and \(\mathbf{v}_j^T \mathbf{v}_k=0\) if \(j\neq k\) otherwise, i.e. \[ \mathbf{V}^T\mathbf{V}=\mathbf{I}\]
Note, that there are only \(r\) non-zero singular values, with \(r\) the rank of matrix \(X\): \(r \leq \text{min}(n,p)\). So we have \(k=1 \ldots r\) non-zero singular values. Hence, we can also rewrite the approximation by restricting us to the rank of matrix \(\mathbf{X}\). Indeed, the n times p matrix \(\boldsymbol\Delta\) only contains \(r\) non-zero diagonal elements!
- So \[
\mathbf{X}=\mathbf{U}_{n\times r}\boldsymbol{\Delta}_{r\times r}\mathbf{V}^T_{p \times r}
\]
\[
\left[\begin{array}{ccc}
-&\mathbf{x}_{1}^T &- \\
\vdots&\vdots&\vdots\\
-&\mathbf{x}_{i}^T &- \\
\vdots&\vdots&\vdots\\
-&\mathbf{x}_{n}^T &- \\
\end{array}\right]_{n \times p}
=
\left[\begin{array}{ccc}
\mid&&\mid\\
\mathbf{u}_1&\ldots&\mathbf{u}_r\\
\mid&&\mid
\end{array}\right]_{n \times r}
\left[\begin{array}{ccc}
\delta_1\\
&\ddots&\\
&&\delta_r\\
\end{array}\right]_{r \times r}
\left[\begin{array}{ccc}
\mid&&\mid\\
\mathbf{v}_1&\ldots&\mathbf{v}_r\\
\mid&&\mid\\
\end{array}
\right]^T_{p \times r}
\]
Also note that \[
\mathbf{V}^T=\left[\begin{array}{ccc}
-&\mathbf{v}_{1}^T &- \\
\vdots&\vdots&\vdots\\
-&\mathbf{v}_{r}^T &- \\
\end{array}\right]_{r \times p}
\]
We can also rewrite the decomposition using the properties of matrix multiplication
\[\begin{eqnarray}
\mathbf{X} &=& \delta_1\left[
\begin{array}{c}
\mid\\
\mathbf{u}_1\\
\mid
\end{array}
\right]
\begin{array}{c}
\left[
\begin{array}{ccc}
-&
\mathbf{v}_1^T&
-
\end{array}
\right]\\\quad\\\quad
\end{array}
+ \ldots +
\delta_r\left[
\begin{array}{c}
\mid\\
\mathbf{u}_r\\
\mid
\end{array}
\right]
\begin{array}{c}
\left[
\begin{array}{ccc}
-&
\mathbf{v}_r^T&
-
\end{array}
\right]\\\quad\\\quad
\end{array}\\
\mathbf{X} &=& \sum_{k=1}^r \delta_k\mathbf{u}_k\mathbf{v}_k^T
\end{eqnarray}\]
Because both \(\mathbf{U}\) and \(\mathbf{V}\) are orthonormal all their \(r\) vectors are having unit length and they are thus reshaped by the singular values.
Hence, the singular values determine the importance of the rank one matrices \(\delta_k\mathbf{u}_k\mathbf{v}_k^T\) in the reconstruction of the matrix \(\mathbf{X}\) and they are ordered so that \(\delta_1 > \ldots > \delta_r\).
Note, that for symmetric matrices \(\mathbf{X}\) \(\longrightarrow\) \(\mathbf{U} = \mathbf{V}\).
Interpretation of singular vectors: face example
Convert images to vectors
- Convert images to vectors and store them as a matrix
- We use an
sapply
loop to loop over all faces
- We extract the grey intensities from the pictures
- We convert the matrix in a long skinny vector (
c
)
- We transpose the resulting matrix from sapply
allFacesMx <- sapply(facesList,
function(x)
getChannels(x) %>% c
) %>% t
dim(allFacesMx)
## [1] 2432 32256
Save memory by removing facesList object
rm(facesList)
gc()
Hence we obtain a matrix for n = 2432 images with p = 32256 intensities for each pixel of an image.
Before we do the svd we typically center the data by substracting the average of the columns, i.e. the average face.
We will only work with the first 36 people: \(n = 36 \times 64 = 2304\) pictures.
allFacesCenteredMx <- allFacesMx[1:(36*64),]
meanFace <- colMeans(allFacesCenteredMx)
allFacesMxCentered <- allFacesCenteredMx -
matrix(1, nrow=nrow(allFacesCenteredMx), ncol=1) %*% matrix(meanFace,nrow=1)
Visualisation of mean image
plotFaceVector <- function(faceVector,nrow=192,ncol=168) {
matrix(faceVector,nrow=nrow,ncol=ncol) %>%
ggimage()
}
meanFace %>%
plotFaceVector
SVD
SVD
Dimensions of \(\mathbf{U}\), \(\mathbf{V}\)?
n <- nrow(allFacesCenteredMx)
p <- ncol(allFacesCenteredMx)
dim(faceSvd$u)
## [1] 2304 2304
## [1] 32256 2304
Indeed, for the face example \(n<p\) so \(r=n\)
Check orthogonality?
We do not do it for all vectors because it takes too long. First left eigen vector and second left eigenvector. Happens in \(\mathbf{U}^T\mathbf{U}\)
t(faceSvd$u[,1])%*%faceSvd$u[,1]
## [,1]
## [1,] 1
t(faceSvd$u[,1])%*%faceSvd$u[,2]
## [,1]
## [1,] -3.794708e-19
t(faceSvd$u[,2])%*%faceSvd$u[,2]
## [,1]
## [1,] 1
So we see that the left eigenvectors are orthonormal.
We check if it also holds for the rows i.e. \(\mathbf{U}\mathbf{U}^T\)
t(faceSvd$u[1,])%*%faceSvd$u[1,]
## [,1]
## [1,] 1
t(faceSvd$u[2,])%*%faceSvd$u[1,]
## [,1]
## [1,] -1.691355e-16
t(faceSvd$u[2,])%*%faceSvd$u[2,]
## [,1]
## [1,] 1
We also see that the rows of \(\mathbf{U}\) are orthonormal.
t(faceSvd$v[,1])%*%faceSvd$v[,1]
## [,1]
## [1,] 1
t(faceSvd$v[,1])%*%faceSvd$v[,2]
## [,1]
## [1,] -9.397551e-16
t(faceSvd$v[,2])%*%faceSvd$v[,2]
## [,1]
## [1,] 1
So we see that the right eigenvectors are orthonormal.
t(faceSvd$v[1,])%*%faceSvd$v[1,]
## [,1]
## [1,] 0.6181993
t(faceSvd$v[1,])%*%faceSvd$v[2,]
## [,1]
## [1,] 0.07690404
t(faceSvd$v[2,])%*%faceSvd$v[2,]
## [,1]
## [1,] 0.1017995
This, however does not hold for the rows of \(\mathbf{V}\). This is because the matrix \(\mathbf{V}\) no longer is a square matrix! \(r=n\) and \(r<p\)!
Visualize right eigenvectors \(\mathbf{V}\)
grid.arrange(
grobs=apply(
faceSvd$v[,1:36],
2,
plotFaceVector
)
)
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
## rescaling mat to [0,1]...
Hence, the right singular vectors (in \(\mathbf{V}\) of \(\mathbf{X}=\mathbf{U}\boldsymbol{\Delta}\mathbf{V}\)) are also faces and we can thus reconstruct the original faces by linear combinations of the eigen faces.
The first eigen faces are most important to capture overall patterns in the matrix.
Here it are mainly characteristics and shadows that are important for all faces.
From eigen face 5 onwards we start to see specific features.
In this case: \(n < p\), so \(r = n\).
\[
\mathbf{X}_{n\times p}=\mathbf{U}_{n \times n}\boldsymbol{\Delta}_{n\times n}\mathbf{V}_{p\times n}^T
\]
\[
\begin{array}{ccccc}
\left[\begin{array}{ccc}
-&\mathbf{x}_{1}^T &- \\
\vdots&\vdots&\vdots\\
-&\mathbf{x}_{i}^T &- \\
\vdots&\vdots&\vdots\\
-&\mathbf{x}_{n}^T &- \\
\end{array}\right]_{n \times p}
\begin{array}{c}
💇\\\\
👮\\
\\
👸\\
\end{array}
&=&
\begin{array}{c}
\quad\\
\left[\begin{array}{ccc}
\mid&&\mid\\
\mathbf{u}_1&\ldots&\mathbf{u}_n\\
\mid&&\mid
\end{array}\right]_{n \times n}\\
\quad \\
\end{array}
\begin{array}{c}
\quad\\
\left[\begin{array}{ccc}
\delta_1\\
&\ddots&\\
&&\delta_n\\
\end{array}\right]_{n \times n}\\
\quad\\
\end{array}
\begin{array}{c}
\quad
\left[\begin{array}{ccc}
\mid&&\mid\\
\mathbf{v}_1&\ldots&\mathbf{v}_n\\
\mid&&\mid\\
\end{array}
\right]^T_{p \times n}\\
\begin{array}{ccc}
😨&\quad&😐
\end{array}
\end{array}
\end{array}
\]
- Or upon transposing the matrix \(\mathbf{V}\)
\[
\begin{array}{ccccc}
\left[\begin{array}{ccc}
-&\mathbf{x}_{1}^T &- \\
\vdots&\vdots&\vdots\\
-&\mathbf{x}_{i}^T &- \\
\vdots&\vdots&\vdots\\
-&\mathbf{x}_{n}^T &- \\
\end{array}\right]_{n \times p}
\begin{array}{c}
💇\\\\
👮\\
\\
👸\\
\end{array}
&=&
\left[\begin{array}{ccc}
\mid&&\mid\\
\mathbf{u}_1&\ldots&\mathbf{u}_n\\
\mid&&\mid
\end{array}\right]_{n \times n}
\left[\begin{array}{ccc}
\delta_1\\
&\ddots&\\
&&\delta_r\\
\end{array}\right]_{n \times n}
\left[\begin{array}{ccc}
-&\mathbf{v}_{1}^T &- \\
\vdots&\vdots&\vdots\\
-&\mathbf{v}_{p}^T &- \\
\end{array}\right]_{n \times p}
\begin{array}{c}
😨\\
\\
😐
\end{array}
\end{array}
\]
Reconstruction of faces via linear combination of eigen faces.
In left singular vectors \(u_{ij}\) we quantify the contribution of the \(j^\text{th}\) eigenface in the reconstruction of face \(i\) and we rescale the importance of each eigen face by its corresponding eigen value \(\delta_j\).
\[
\left[\begin{array}{ccc}
-&\mathbf{x}_{1}^T &- \\
\vdots&\vdots&\vdots\\
-&\mathbf{x}_{i}^T &- \\
\vdots&\vdots&\vdots\\
-&\mathbf{x}_{n}^T &- \\
\end{array}\right]_{n \times p}
\begin{array}{c}
💇\\\\
👮\\
\\
👸\\
\end{array} =
\delta_1\left[
\begin{array}{c}
\mid\\
\mathbf{u}_1\\
\mid
\end{array}
\right]
\begin{array}{c}
\left[
\begin{array}{ccc}
-&
\mathbf{v}_1^T&
-
\end{array}
\right]\\\quad\\\quad
\end{array}
\begin{array}{c}
😨
\\\quad\\\quad
\end{array}
+ \ldots +
\delta_r\left[
\begin{array}{c}
\mid\\
\mathbf{u}_r\\
\mid
\end{array}
\right]
\begin{array}{c}
\left[
\begin{array}{ccc}
-&
\mathbf{v}_r^T&
-
\end{array}
\right]\\\quad\\\quad
\end{array}
\begin{array}{c}
😐
\\\quad\\\quad
\end{array}
\]
If we truncate the eigen faces say at \(k<r\) we can approximate faces using a limited number of eigen faces!
approximateFace <- function(meanFace,faceSvd,k){
reconstruct <- (meanFace + faceSvd$u[1,1:k] %*%
diag(faceSvd$d[1:k]) %*%
t(faceSvd$v[,1:k]) %>%
c)
}
approxHlp <- sapply(
c(25,100,500),
approximateFace,
meanFace=meanFace,
faceSvd=faceSvd)
grid.arrange(
grobs=apply(
cbind(
approxHlp,
allFacesMxCentered[1,]+meanFace
),
2,
plotFaceVector
)
)
SVD as a Matrix Approximation Method
We have seen that we can use the truncted SVD to approximate matrix \(\mathbf{X}\) by \(\tilde{\mathbf{X}}\), with \(k<r\) and \[
\tilde{\mathbf{X}}=\mathbf{U}_{n\times k}\boldsymbol{\Delta}_{k\times k}\mathbf{V}_{p \times k}^T
\]
It can be shown that SVD: optimal approximation
Let \(\mathbf{X}\) be an \(n\times p\) matrix of rank \(r\leq \min(n,p)\), and let \(\mathbf{A}\) denote an \(n \times p\) matrix of rank \(k\leq r\), with elements denoted by \(a_{ij}\).
The matrix \(\mathbf{A}\) of rank \(k\leq r\) that minimises the Frobenius norm \[
\vert\vert\mathbf{X}-\mathbf{A}\vert\vert^2_\text{fr}=\sum_{i=1}^n\sum_{j=1}^p (x_{ij}-a_{ij})^2
\] is given by the truncated SVD \[
\mathbf{X}_k = \sum_{j=1}^k \delta_j \mathbf{u}_j\mathbf{v}_j^T.
\]
The truncated SVD has \(k < r\) terms. Hence, generally \(\mathbf{X}_k\) does not coincide with \(\mathbf{X}\). It is considered as an approximation.
Note, that the truncated SVD thus approximates the matrix by minimising a kind of sum of least squared errors between the elements of matrix \(\mathbf{X}\) and \(\mathbf{A}\) and that
the truncated SVD \(\mathbf{X}_k\) is the best rank-k approximation of \(\mathbf{X}\) in terms of this Frobenius norm.
Also, note that upon truncation \[\mathbf{V}^T_{p\times k} \mathbf{V}_{p\times k} = \mathbf{I}_{k\times k}\] \[\mathbf{U}^T_{n\times k} \mathbf{U}_{n\times k} = \mathbf{I}_{k\times k}\]
But, that \[\mathbf{V}_{p\times k} \mathbf{V}_{p\times k}^T \neq \mathbf{I}_{p\times p}!!!\] \[\mathbf{U}_{n\times k} \mathbf{U}_{n\times k}^T \neq \mathbf{I}_{n\times n}!!!\]
Some informal statement about the truncated SVD \[
\mathbf{X}_k = \sum_{j=1}^k \delta_j \mathbf{u}_j\mathbf{v}_j^T.
\]
It can be considered as a weighted sum of matrices \(\mathbf{u}_j\mathbf{v}_j^T\), with weights \(\delta_j\).
The terms are ordered with decreasing weights \(\delta_1\geq \delta_2 \geq \cdots \geq \delta_k >0\).
The matrices \(\mathbf{u}_j\mathbf{v}_j^T\) are of equal “magnitude” (constructed from normalised vectors).
Truncation at \(k\) results in \(k\) \(\delta_j\)’s, \(k\times n\) elements in the \(\mathbf{u}_j\) and \(k \times p\) elements in the \(\mathbf{v}_j\). Hence a total of \(k+kn+kp=k(1+n+p)\) elements (usually much smaller than \(np\)). (Note that restrictions apply to \(\mathbf{u}_j\) and \(\mathbf{v}_j\); hence even less independent elements).
\(\longrightarrow\) data compression
Example 1: Image compression
Painting Mondriaan: Composition_No.III with red, blue, yellow and black (1929).
- Have a look at this painting of Mondriaan (1872 – 1944), here shown in black-and-white.
Load the original painting
- fetch image from the web
- convert into greyscale
- plot
- save as Matrix
mondriaan <- load.image("https://upload.wikimedia.org/wikipedia/commons/thumb/a/ac/Piet_Mondrian_-_Composition_No._III%2C_with_red%2C_blue%2C_yellow_and_black%2C_1929.jpg/1920px-Piet_Mondrian_-_Composition_No._III%2C_with_red%2C_blue%2C_yellow_and_black%2C_1929.jpg")
mondriaan <- grayscale(mondriaan)
plot(mondriaan,axes=FALSE)
X <- matrix(as.data.frame(mondriaan)[,3],nrow=nrow(mondriaan),ncol=ncol(mondriaan))
This picture can be represented as a \(1920 \times 1913`\) matrix \(\mathbf{X}\) with gray scale intensities \(\in [0,1]\). (\(\approx 4\times 10^6\) data entries)
We will here not transform the image in a vector, but will look at the performance of the SVD to compress this image. The SVD can be applied to any matrix!
Singular values
monSvd <- svd(X)
p1 <- data.frame(x=1:length(monSvd$d),y=monSvd$d) %>%
ggplot(aes(x=x,y=y)) +
geom_point() +
xlab("k") +
ylab("singular value")
p2 <- data.frame(x=1:10,y=monSvd$d[1:10]) %>%
ggplot(aes(x=x,y=y)) +
geom_point() +
xlab("k") +
ylab("singular value")
grid.arrange(p1,p2,nrow=1)
- The singular values decay very quickly!
Data compression
- We make the plot for a reconstruction with 1 singular vector. This leads to a data compression of \(1-\frac{(1+1920+1913)}{1920\times 1913}\) = 99.9%. We only use 1 left singular vector (1920), 1 eigen value, 1 right singular vector (1913).
k <- 1
approxMon <- monSvd$u[,1:k] %*%
diag(monSvd$d[1:k],ncol=k) %*%
t(monSvd$v[,1:k])
approxMon[approxMon < 0] <- 0
approxMon[approxMon > 1] <- 1
as.cimg(approxMon) %>%
plot(.,main=paste0("Approximation with ",k," singular vectors"),axes=FALSE)
- We make the plot for a reconstruction with 2 singular vector. This leads to a data compression of \(1-\frac{2\times (1+1920+1913)}{1920\times 1913}\) = 99.8%. We only use 2 left singular vectors (2 \(\times\) 1920), 2 singular values, 2 right singular vectors (2 \(\times\) 1913).
k <- 2
approxMon <- monSvd$u[,1:k] %*%
diag(monSvd$d[1:k],ncol=k) %*%
t(monSvd$v[,1:k])
approxMon[approxMon < 0] <- 0
approxMon[approxMon > 1] <- 1
as.cimg(approxMon) %>%
plot(.,main=paste0("Approximation with ",k," singular vectors"),axes=FALSE)
par (mfrow=c(3,3))
par(mar=c(1,2,1,1))
for (k in c(1:8))
{
approxMon <- monSvd$u[,1:k] %*%
diag(monSvd$d[1:k],ncol=k) %*%
t(monSvd$v[,1:k])
approxMon[approxMon < 0] <- 0
approxMon[approxMon > 1] <- 1
approxMon %>%
as.cimg %>%
plot(.,main=paste0(k," singular vectors"),axes=FALSE)
}
plot(as.cimg(X),main=paste0("Original image"),axes=FALSE)
More complex painting: Composition A, Piet Mondriaan
Load the original painting
mondriaan <- load.image("https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Composition_A_by_Piet_Mondrian_Galleria_Nazionale_d%27Arte_Moderna_e_Contemporanea.jpg/1920px-Composition_A_by_Piet_Mondrian_Galleria_Nazionale_d%27Arte_Moderna_e_Contemporanea.jpg")
mondriaan <- grayscale(mondriaan)
plot(mondriaan,axes=FALSE)
X <- matrix(as.data.frame(mondriaan)[,3],nrow=dim(mondriaan)[1],ncol=dim(mondriaan)[2])
Singular values
monSvd <- svd(X)
p1 <- data.frame(x=1:length(monSvd$d),y=monSvd$d) %>%
ggplot(aes(x=x,y=y)) +
geom_point() +
xlab("k") +
ylab("singular value")
p2 <- data.frame(x=1:10,y=monSvd$d[1:10]) %>%
ggplot(aes(x=x,y=y)) +
geom_point() +
xlab("k") +
ylab("singular value")
grid.arrange(p1,p2,nrow=1)
- The singular values decay a bit slower. The painting is a bit more complex. More lines and colors.
Evaluate data compression
par (mfrow=c(3,3))
par(mar=c(1,2,1,1))
for (k in c(1,seq(3,21,3)))
{
approxMon <- monSvd$u[,1:k] %*%
diag(monSvd$d[1:k],ncol=k) %*%
t(monSvd$v[,1:k])
approxMon[approxMon < 0] <- 0
approxMon[approxMon > 1] <- 1
approxMon %>%
as.cimg %>%
plot(.,main=paste0(k," singular vectors"),axes=FALSE)
}
plot(as.cimg(X),main=paste0("Original image"),axes=FALSE)
Self portret Piet Mondriaan
Load the painting
mondriaan <- load.image("https://upload.wikimedia.org/wikipedia/commons/thumb/6/66/Mondrian_Zelfportret.jpg/1920px-Mondrian_Zelfportret.jpg")
mondriaan <- grayscale(mondriaan)
plot(mondriaan,axes=FALSE)
X <- matrix(as.data.frame(mondriaan)[,3],nrow=dim(mondriaan)[1],ncol=dim(mondriaan)[2])
Singular values
monSvd <- svd(X)
p1 <- data.frame(x=1:length(monSvd$d),y=monSvd$d) %>%
ggplot(aes(x=x,y=y)) +
geom_point() +
xlab("k") +
ylab("singular value")
p2 <- data.frame(x=1:10,y=monSvd$d[1:10]) %>%
ggplot(aes(x=x,y=y)) +
geom_point() +
xlab("k") +
ylab("singular value")
grid.arrange(p1,p2,nrow=1)
- The singular values decay much slower. The painting is more complex.
Evaluate compression
par (mfrow=c(3,3))
par(mar=c(1,2,1,1))
for (k in c(1,5,10,20,30,40,50,100))
{
approxMon <- monSvd$u[,1:k] %*%
diag(monSvd$d[1:k],ncol=k) %*%
t(monSvd$v[,1:k])
approxMon[approxMon < 0] <- 0
approxMon[approxMon > 1] <- 1
approxMon %>%
as.cimg %>%
plot(.,main=paste0(k," singular vectors"),axes=FALSE)
}
plot(as.cimg(X),main=paste0("Original image"),axes=FALSE)
Here we need at least 40 singular vector. This leads to a data compression of \(1-\frac{40\times (1+1920+2494)}{1920 \times 2494}\) = 96.3%. We only use 40 left singular vectors (\(40 \times 1920\)), 40 singular values, 40 right eigens vector (\(40\times 2494\)).
Geometric interpretation
Write the truncated SVD as \[
\mathbf{X}_k = \mathbf{U}_k \boldsymbol{\Delta}_k \mathbf{V}_k^T = \mathbf{Z}_k \mathbf{V}_k^T
\] with \[
\mathbf{Z}_k = \mathbf{U}_k \boldsymbol{\Delta}_k
\] an \(n \times k\) matrix.
Each of the \(n\) rows of \(\mathbf{Z}_k\), say \(\mathbf{z}^T_{k,i}\), represents a point in a \(k\)-dimensional space.
Because of the orthonormality of the singular vectors, we also have \[\begin{eqnarray*}
\mathbf{X}_k\mathbf{V}_k &=& \mathbf{Z}_k \mathbf{V}_k^T\mathbf{V}_k \\
\mathbf{X}_k\mathbf{V}_k &=& \mathbf{Z}_k.
\end{eqnarray*}\]
Thus the matrix \(\mathbf{V}_k\) is a transformation matrix that may be used to transform \(\mathbf{X}_k\) into \(\mathbf{Z}_k\), and \(\mathbf{Z}_k\) into \(\mathbf{X}_k\).
Note that
The matrix \(\mathbf{V}_k\) transforms the \(p\)-dimensional \(\mathbf{X}_k\) into the \(k\)-dimensional \(\mathbf{Z}_k\): \(\mathbf{Z}_k = \mathbf{X}_k\mathbf{V}_k\). Note, however, that the matrix \(\mathbf{X}_k\) must not necessarily be used for this transformation, because the SVD of the original matrix \(\mathbf{X}\) also gives directly \(\mathbf{Z}_k = \mathbf{U}_k \boldsymbol{\Delta}_k\).
The inverse transformation from the \(k\)-dimensional \(\mathbf{Z}_k\) to the \(p\)-dimensional \(\mathbf{X}_k\) is given by the transpose of \(\mathbf{V}_k\): \(\mathbf{Z}_k \mathbf{V}_k^T=\mathbf{X}_k\). Often inverse transformations are given by the inverse of a matrix, but thanks to the orthonormality of the columns of \(\mathbf{V}_k\), we get \(\mathbf{V}_k^T\mathbf{V}_k=\mathbf{I}\), and thus \(\mathbf{V}_k^T\) acts as an inverse.
The transformation from the \(k\)-dimensional \(\mathbf{Z}_k\) to the \(p\)-dimensional \(\mathbf{X}_k\) is transforming points from a low dimensional space (\(k\)) to a high dimensional space (\(p\)). You may not interpret this as if this transformation adds information; the transformed points in \(\mathbf{X}_k\) still live in a \(k\)-dimensional subspace of the larger \(p\)-dimensional space; the matrix \(\mathbf{X}_k\) is only of rank \(k\) and thus contains less information than the original data matrix \(\mathbf{X}\) (if rank(\(\mathbf{X}\))\(=r>k\)).
More importantly, it can be shown that (thanks to orthonormality of \(\mathbf{V}\)) \[
\mathbf{X}\mathbf{V}_k = \mathbf{Z}_k.
\] This follows from (w.l.g. rank(\(\mathbf{X}\))=\(r\)) \[\begin{eqnarray*}
\mathbf{X}\mathbf{V}_k
&=& \mathbf{UDV}^T\mathbf{V}_k = \mathbf{UD}\begin{pmatrix}
\mathbf{v}_1^T \\
\vdots \\
\mathbf{v}_r^T
\end{pmatrix}
\begin{pmatrix}
\mathbf{v}_1 \ldots \mathbf{v}_k
\end{pmatrix} \\
&=& \mathbf{UDV}^T\mathbf{V}_k = \mathbf{UD}\begin{pmatrix}
1 & 0 & \ldots & 0 \\
0 & 1 & \ldots & 0 \\
\vdots & \vdots & \ddots & 0 \\
0 & 0 & \ldots & 1 \\
0 & 0 & \ldots & 0 \\
\vdots & \vdots & \vdots & \vdots \\
0 & 0 & \ldots & 0
\end{pmatrix} \
= \mathbf{U}_k\boldsymbol{\Delta}_k = \mathbf{Z}_k
\end{eqnarray*}\]
The \(p \times k\) matrix \(\mathbf{V}_k\) acts as a transformation matrix: transforming \(n\) points in a \(p\) dimensional space to \(n\) points in a \(k\) dimensional space.
We take a closer look at \[
\mathbf{Z}_k = \mathbf{X}\mathbf{V}_k = \begin{pmatrix}
\mathbf{x}_1^T \\
\vdots \\
\mathbf{x}_n^T
\end{pmatrix} \begin{pmatrix}
\mathbf{v}_1 \ldots \mathbf{v}_k
\end{pmatrix}.
\] The \(i\)th row (observation) in \(\mathbf{Z}_k\) equals \[
\mathbf{z}_{k,i}^T = \mathbf{x}_i^T\mathbf{V}_k = \left(\mathbf{x}_i^T \mathbf{v}_1 , \mathbf{x}_i^T \mathbf{v}_2 , \ldots , \mathbf{x}_i^T \mathbf{v}_k\right).
\]
Hence, \(\mathbf{z}_{k,i}^T = \mathbf{x}_i^T\mathbf{V}_k\) is the orthogonal projection of \(\mathbf{x}_i\) onto the \(k\)-dimensional subspace spanned by the columns of \(\mathbf{V}_k\).
SVD transforms data set to lower dimensional data set: The SVD thus gives a transformation of the \(p\) dimensional data to \(k\leq r\) dimensional data: \[
\mathbf{Z}_k = \mathbf{X}\mathbf{V}_k.
\] This is essentially a dimension reduction.
Note that,
The transformation from \(p\)-dimensional \(\mathbf{X}\) to \(k\)-dimensional \(\mathbf{Z}_k\) is important. It shows that the \(n\) points in the rows of \(\mathbf{Z}_k\) are the result of projecting the \(n\) points in \(\mathbf{X}\) onto the columns of \(\mathbf{V}_k\) (i.e. the first \(k\) singular vectors of \(\mathbf{X}\)). We say that the space of \(\mathbf{Z}_k\) is spanned by the column of \(\mathbf{V}_k\).
The points (rows) in \(\mathbf{X}\) live in a \(p\)-dimensional space (or rank(\(\mathbf{X}\))\(=r\) if \(r<p\)) and they are thus projected onto a lower dimensional space. This is in contrast to the projection \(\mathbf{X}_k\mathbf{V}_k=\mathbf{Z}_k\), because the points in \(\mathbf{X}_k\) live in a \(k\)-dimensional subspace of \(\mathbf{X}\).
Note that with \(k<r\) there is no unique transformation to transform \(\mathbf{Z}_k\) back to \(\mathbf{X}\). On the previous slide we only established the transformation \(\mathbf{Z}_k \mathbf{V}_k^T=\mathbf{X}_k\). Indeed, starting from \(\mathbf{X}\mathbf{V}_k = \mathbf{Z}_k\), and right-multiplying with \(\mathbf{V}_k^T\) does not give the backtransformation, because \(\mathbf{V}_k\mathbf{V}_k^T\) is not the identity matrix.
Interpretation of SVD in terms of correlation matrices
For a matrix \(\mathbf X\) the sample variance covariance matrix estimator is \(p\times p\) matrix
\[\begin{eqnarray}
\mathbf{S}&=&\frac{1}{N-1}(\mathbf{X}-\bar{\mathbf{X}})^T (\mathbf{X}-\bar{\mathbf{X}})\\
&=&\frac{1}{N-1}\left[\mathbf{X}^T\mathbf{X} - \bar{\mathbf{X}}^T\bar{\mathbf{X}}\right]
\end{eqnarray}\]
So \(\mathbf{X}^T\mathbf{X}\) defines up to a constant the variance covariance matrix of \(\mathbf{X}\)! When the matrix is column centered \(\mathbf{S}=\frac{1}{n-1}\mathbf{X}^T\mathbf{X}\).
The same holds for the rows of \(\mathbf{X}\)! The covariance between the subjects can be estimated as \[\mathbf{S}=\frac{1}{p-1}\mathbf{X}\mathbf{X}^T\] upon row centering.
Note, that
\[\begin{eqnarray}
\mathbf{X}^T\mathbf{X} &=& \mathbf{V}\boldsymbol{\Delta}\mathbf{U}^T \mathbf{U} \boldsymbol{\Delta} \mathbf{V}^T \\
&=&\mathbf{V}\boldsymbol{\Delta}^2 \mathbf{V}^T
\end{eqnarray}\]
If we rewrite the expression \[\begin{eqnarray}
\mathbf{X}^T\mathbf{X}\mathbf{V} &=&\mathbf{V}\boldsymbol{\Delta}^2 \mathbf{V}^T\mathbf{V}
\mathbf{X}^T\mathbf{X}\mathbf{V}\\ &=&\mathbf{V}\boldsymbol{\Delta}^2
\end{eqnarray}\]
So, if the data are centered, the SVD can be used to perform a spectral decomposition of the sample covariance matrix where the right singular vectors correspond to the eigen vectors of the covariance matrix and the eigenvalues are the squared singular values!
Similarly the left singular values can be used to estimate the covariance matrix of the rows of \(\mathbf{X}\). So in our notation covariance between subjects.
\[\begin{eqnarray}
\mathbf{X}\mathbf{X}^T &=&\mathbf{U}\boldsymbol{\Delta}^2 \mathbf{U}^T
\end{eqnarray}\]
This link is for instance very useful for recommender systems, i.e. to propose movies based on the subjects with whom you correlate. We will also exploit this when we discuss on PCA.
SVD and inverse of a matrix
A linear system of equations with \(n\) equations and \(n\) unknowns \[
\mathbf{A}_{n\times n} \boldsymbol{\beta} = \mathbf{b}
\] can be solved by \[
\boldsymbol{\beta} = \mathbf{A}_{n\times n}^{-1} \mathbf{b}
\]
A unique solution exists if A is full rank.
Note, that a singular value decomposition of the square matrix \(\mathbf{A}=\mathbf{V}\boldsymbol{\Delta}\mathbf{V}^T\) enables the inverse to be written as
\[
\mathbf{A}^{-1} = \mathbf{V}\boldsymbol{\Delta}^{-1}\mathbf{V}^T
\]
indeed \[
\mathbf{A}^{-1} \mathbf{A} = \mathbf{V}\boldsymbol{\Delta}^{-1}\mathbf{V}^T\mathbf{V}\boldsymbol{\Delta}\mathbf{V}^T = \mathbf{I}
\]
Note, that the SVD generalizes this to systems of under (n<p, fat short matrices) and over determined systems (n>p tall skinny matrices):
Let \[\begin{eqnarray}
\mathbf{A} = \mathbf{U}\boldsymbol{\Delta}\mathbf{V}^T
\end{eqnarray}\]
and we want to solve \[\begin{eqnarray}
\mathbf{A} \boldsymbol{\beta} &=& \mathbf{b}\\
\mathbf{U}\boldsymbol{\Delta}\mathbf{V}^T \boldsymbol{\beta} &=& \mathbf{b}\\
\mathbf{U}^T\mathbf{U}\boldsymbol{\Delta}\mathbf{V}^T \boldsymbol{\beta} &=& \mathbf{U}^T\mathbf{b}\\
\boldsymbol{\Delta}\mathbf{V}^T \boldsymbol{\beta} &=& \mathbf{U}^T\mathbf{b}\\
\boldsymbol{\Delta}^{-1}\boldsymbol{\Delta}\mathbf{V}^T \boldsymbol{\beta} &=& \boldsymbol{\Delta}^{-1}\mathbf{U}^T\mathbf{b}\\
\mathbf{V}\mathbf{V}^T \boldsymbol{\beta} &=& \mathbf{V}\boldsymbol{\Delta}^{-1}\mathbf{U}^T\mathbf{b}\\
\boldsymbol{\beta} &=& \mathbf{V}\boldsymbol{\Delta}^{-1}\mathbf{U}^T\mathbf{b}
\end{eqnarray}\]
Note, that for an overdetermined system \(n>p\) so \(r\leq p\). Generally, \(r=p\) and \(\mathbf{V}\) is thus a square matrix so both \(\mathbf{V}^T\mathbf{V}=\mathbf{I}\) and \(\mathbf{V}\mathbf{V}^T=\mathbf{I}\). However, \(\mathbf{U}^T\mathbf{U}=\mathbf{I}\) but \(\mathbf{U}\mathbf{U}^T\neq\mathbf{I}\) because \(r<n\).
\(\mathbf{A}^\dagger=\mathbf{V}\boldsymbol{\Delta}^{-1}\mathbf{U}^T\) is also referred to as the pseudo inverse and it enables us to solve under and overdetermined systems of equations.
Note, that for
- underdetermined systems there typically does not exist a unique solution
- for overdetermined systems usually there does not exist an exact solution. We will focus on the latter in the next section where we explore the link between linear regression and SVD.
Linear regression and SVD
Suppose we have the linear regression problem with \(n>p\):
\[\mathbf{Y}=\mathbf{X}\boldsymbol{\beta} + \boldsymbol{\epsilon}\]
\(\mathbf{X}\) is a tall skinny matrix with \(n >> p\).
We know that \[
\hat{\boldsymbol{\beta}}=\left(\mathbf{X}^T \mathbf{X}\right)^{-1}\mathbf{X}^T\mathbf{Y}
\]
If we replace \(\mathbf{X}\) by its SVD
\[\begin{eqnarray}
\hat{\boldsymbol{\beta}}&=&\left(\mathbf{V}\boldsymbol{\Delta}^2\mathbf{V}^T\right)^{-1}\mathbf{V}\boldsymbol{\Delta}\mathbf{U}^T\mathbf{Y}\\
\hat{\boldsymbol{\beta}}&=&\mathbf{V}\boldsymbol{\Delta}^{-2}\mathbf{V}^T\mathbf{V}\boldsymbol{\Delta}\mathbf{U}^T\mathbf{Y}\\
\hat{\boldsymbol{\beta}}&=&\mathbf{V}\boldsymbol{\Delta}^{-1}\mathbf{U}^T\mathbf{Y}
\end{eqnarray}\]
So the SVD also solves the linear regression problem by using the pseudoinverse! If we now think about the fit:
\[\begin{eqnarray}
\hat{\mathbf{Y}}&=&\mathbf{X}\hat{\boldsymbol{\beta}}\\
&=&\mathbf{U}\boldsymbol{\Delta}^{-1}\mathbf{V}^T\mathbf{V}\boldsymbol{\Delta}\mathbf{U}^T\mathbf{Y}\\
&=&\mathbf{U}\mathbf{U}^T\mathbf{Y}
\end{eqnarray}\]
For an overdetermined system \(\mathbf{U}\mathbf{U}^T\) is not equal to the unity matrix \(\mathbf{I}\) (for an overdetermined system only \(\mathbf{U}^T\mathbf{U}=\mathbf{I}\) because \(n>r\)).
So \(\hat{\mathbf{Y}}\neq \mathbf{Y}\). Hence, we typically do not have an exact solution.
Note, that \(\mathbf{U}\mathbf{U}^T\) spans the same space as the columns of \(\mathbf{X}\), and will define the same \(p\)-dimensional plane in the \(n\) dimensional space \(\mathcal{R}^n\), e.g. cfr \(\mathbf{X}\left(\mathbf{X}^T\mathbf{X}\right)^{-1}\mathbf{X}^T\). So it projects \(\mathbf{Y}\) in the column space of \(\mathbf{X}\) and the errors will be orthogonal onto this plane.
Example prostate dataset
Fit with lm
prostate <- read_csv(
"https://raw.githubusercontent.com/GTPB/PSLS20/master/data/prostate.csv",
col_types = cols()
)
lm1 <- lm(lpsa ~ lcavol + lweight + svi, prostate)
Fit with SVD
X <- prostate[,c(1:2,5)]
X[,3] <- as.double(X[,3]!="healthy")
X <- cbind(Intercept=1,X)
svdX <- svd(X)
betaSvd <- svdX$v %*% diag(1/svdX$d) %*% t(svdX$u) %*% prostate$lpsa
cbind(lm1$coef,betaSvd)
## [,1] [,2]
## (Intercept) -0.2680724 -0.2680724
## lcavol 0.5516386 0.5516386
## lweight 0.5085359 0.5085359
## sviinvasion 0.6661583 0.6661583
SVD and Multi-Dimensional Scaling (MDS)
Example
In this section we will use a dataset on food consumption in the UK. The data originate from the UKs ‘Department for Environment, Food and Rural Affairs’ (DEFRA), showing the consumption in grams (per person, per week) of 17 different types of foodstuff measured and averaged in the four countries of the United Kingdom in 1997. We would like to explore the data and interpret how the food patterns of the different countries differ.
uk <- read_csv(
"https://raw.githubusercontent.com/statOmics/HDA2020/data/ukFoods.csv",
col_types = cols()
)
## Warning: Missing column names filled in: 'X1' [1]
knitr::kable(uk, caption = "The full UK foods data table")
The full UK foods data table
Cheese |
105 |
103 |
103 |
66 |
Carcass_meat |
245 |
227 |
242 |
267 |
Other_meat |
685 |
803 |
750 |
586 |
Fish |
147 |
160 |
122 |
93 |
Fats_and_oils |
193 |
235 |
184 |
209 |
Sugars |
156 |
175 |
147 |
139 |
Fresh_potatoes |
720 |
874 |
566 |
1033 |
Fresh_Veg |
253 |
265 |
171 |
143 |
Other_Veg |
488 |
570 |
418 |
355 |
Processed_potatoes |
198 |
203 |
220 |
187 |
Processed_Veg |
360 |
365 |
337 |
334 |
Fresh_fruit |
1102 |
1137 |
957 |
674 |
Cereals |
1472 |
1582 |
1462 |
1494 |
Beverages |
57 |
73 |
53 |
47 |
Soft_drinks |
1374 |
1256 |
1572 |
1506 |
Alcoholic_drinks |
375 |
475 |
458 |
135 |
Confectionery |
54 |
64 |
62 |
41 |
Note, that here the matrix is displayed with the p variables in the rows and the n experimental units (countries) in the columns. This is often done for high dimensional data where \(p>>n\) because this makes it easier to look at to the raw data table. Note, that the svd calculates left and right singular vectors so it will also provide the correct solution, we just should look to the other set of singular vectors in order to get to the correct interpretation.
Motivation
The objective of Multidimensional Scaling (MDS) is to find a low-dimensional representation, say \(k\)-dimensional, of \(n\) data points such that the distances between the \(n\) points in the \(k\)-dimensional space is a good approximation of a given squared distance matrix, say \(\mathbf{D}_X\).
The squared distance matrix \(\mathbf{D}_X\) may be given without knowledge of the original observations (not even the dimensionality), or it may be computed from a given set of \(n\) \(p\)-dimensional data points.
Note that the distances between points in a \(k\)-dimensional subspace coincide with the distances between these points in the larger \(p\)-dimensional space.
Use of MDS:
A high dimensional data matrix \(\mathbf{X}\) is given, and one wants to get a visual representation (in 2 or 3 dimensions) of the observations. In this graph each point represents an observation (row of data matrix). From this graph one wants points close to one another to be similar, and observations far away from one another to be dissimilar. Thus the distances between the \(n\) points in the original \(p\)-dimensional space should be well preserved in the 2 or 3 dimensional space.
In some applications the researcher only has knowledge of the similarity (or dissimilarity) between observations. For example, food products can be evaluated by a taste panel and a dissimilarity matrix can be completed. This dissimilarity matrix shows for each pair of food products their dissimilarity (numerical values provided by taste panel, but not objectively quantified). Given this dissimilarity matrix, a 2 or 3 dimensional graph could be helpful if the distances between the points (food products) in this graph are monotonically related to the dissimilarities provided by the taste panel. Food products close to one another in this graph, taste similarly.
Here we discuss the “metric” MDS, which actually requires the Euclidean distances between observations. However, the method works also well if the matrix \(\mathbf{D}_X\) contains dissimilarities rather than squared Euclidean distances.
In this chapter we assume that the data matrix \(\mathbf{X}\) is column-centered (i.e. each column has mean zero).
Centering can be accomplished by multiplying the original data matrix \(\mathbf{X}\) with the \(n \times n\) \(\mathbf{centering\ matrix}\) \[
\mathbf{H} = \mathbf{I} - \frac{1}{n} \mathbf{1}\mathbf{1}^T ,
\] in which \(\mathbf{1}\) is an \(n\)-vector with all entries equal to 1. Hence, \(\mathbf{H}\mathbf{X}\) is the column-centered data matrix.
We will assume that \(\mathbf{X}\) is already column-centered, and therefore \(\mathbf{H}\mathbf{X}=\mathbf{X}\).
(Note, that the matrix \(\mathbf{1}\mathbf{1}^T\) is an \(n\times n\) matrix with all entries set to 1. )
We will need the following interesting relationship between the Gram matrix \(\mathbf{X}\mathbf{X}^T\) and the distance matrix.
- For an \(n\times p\) data matrix \(\mathbf{X}\), the matrix \(\mathbf{D}_X\) with squared distances has elements \[
(\mathbf{x}_{i}-\mathbf{x}_{j})^T(\mathbf{x}_{i}-\mathbf{x}_{j}) = \Vert \mathbf{x}_i\Vert^2 - 2 \mathbf{x}^T_i\mathbf{x}_j + \Vert \mathbf{x}_j\Vert.^2
\]
-The \(n\times n\) squared distance matrix can then be written as \[
\mathbf{D}_X = \mathbf{N} - 2\mathbf{X}\mathbf{X}^T + \mathbf{N}^T ,
\] with \(\mathbf{N}\) the \(n \times n\) matrix with \(i\)th row filled with \(\Vert \mathbf{x}_i\Vert^2\).
Note that the elements of \(\mathbf{N}\) can also be found on the diagonal of \(\mathbf{XX}^T\).
Given the structure of the \(\mathbf{H}\) and \(\mathbf{N}\) matrices, it is easy to verify that \[
-\frac{1}{2}\mathbf{H}\mathbf{D}_X\mathbf{H} = \mathbf{X}\mathbf{X}^T.
\] This gives an important relation between the distance matrix and the Gram matrix. We will use the notation \(\mathbf{G}_X = -\frac{1}{2}\mathbf{H}\mathbf{D}_X\mathbf{H}\).
The \(n \times n\) Gram matrix \(\mathbf{X}\mathbf{X}^T\) equals \[
\begin{pmatrix}
\mathbf{x}_1^T \\
\vdots \\
\mathbf{x}_n^T
\end{pmatrix}
\begin{pmatrix}
\mathbf{x}_1 \ldots \mathbf{x}_n
\end{pmatrix}
\] and has thus on its \((i,j)\)th position the inner product \[
\mathbf{x}_i^T\mathbf{x}_j= \Vert \mathbf{x}_i\Vert\Vert \mathbf{x}_j\Vert \cos<\mathbf{x}_i,\mathbf{x}_j> .
\]
On the diagonal we find \(\mathbf{x}_i^T\mathbf{x}_i=\Vert \mathbf{x}_i\Vert^2\), the squared norm of the \(i\)th observation.
The relation \(-\frac{1}{2}\mathbf{H}\mathbf{D}_X\mathbf{H} = \mathbf{X}\mathbf{X}^T\) tells us that the distance matrix and the Gram matrix contain the same information. The distances, however, are in most situations easier to interpret.
Link with the SVD
Note, that we have shown that we can rewrite \(\mathbf{X}\mathbf{X}^T\) using the SVD:
\[
\mathbf{X}\mathbf{X}^T = \mathbf{U}\boldsymbol{\Delta}^2\mathbf{U}^T
\]
Because the truncated SVD of \(\mathbf{X}\) minimises the Frobenius norm \(\Vert \mathbf{X}-\mathbf{X}_k\Vert_F^2\), it this has an important consequence:
Let \(\mathbf{D}_X\) denote the \(n \times n\) matrix with the squared Euclidian distances between the \(n\) data points \(\mathbf{x}_i\) in the original \(p\)-dimensional space.
Let \(\mathbf{D}_{Zk}\) denote the \(n \times n\) matrix with the squared Euclidian distances between the \(n\) transformed data points \(\mathbf{z}_{k,i}\) in the reduced \(k\)-dimensional space.
Then, it can be shown that the truncated SVD also minimises \[
\Vert \mathbf{D}_X - \mathbf{D}_{Zk} \Vert_F^2.
\]
Or in other words: The n points \(\mathbf{z}_{k,i}\) in the k-dimensional subspace spanned by the columns of \(\mathbf{V}_k\) are the best approximation of the \(n\) original points \(\mathbf{x}_i\) in the original p-dimensional space in the sense that the Euclidean distances between the n original points are best approximated by the Euclidean distances between the n transformed points, among all possible k-dimensional linear subspaces.
If \(\mathbf{X}\) is known, we can readily obtain the k-dimensional projection by the SVD of \(\mathbf{X}\)
If we only know the distance matrix \(\mathbf{D}\)
- We can readily obtain the Gram matrix \[-\frac{1}{2}\mathbf{H}\mathbf{D}_X\mathbf{H} = \mathbf{X}\mathbf{X}^T\]
- The truncated SVD of the squared and symmetric Gram matrix \[\mathbf{U}_k\boldsymbol{\Delta}_k^\prime\mathbf{U}_k^T\]
- from which also can obtain \[\mathbf{Z}_k=\mathbf{U}_k\boldsymbol{\Delta}_k,\] with \(\boldsymbol{\Delta}_k= \mathbf{\Delta}_k^{\prime \frac{1}{2}}\)
Example
X <- as.matrix(t(uk[,-1]))
n <- nrow(X)
H <- diag(n) - matrix(1/n,nrow=n,ncol=n)
X <- H%*%X
svdUk <- svd(X)
k <- 2
Uk <- svdUk$u[,1:k]
Dk <- diag(svdUk$d[1:k])
Zk <- Uk%*%Dk
rownames(Zk) <- colnames(uk)[-1]
colnames(Zk) <- paste0("Z",1:k)
Zk
## Z1 Z2
## England -144.99315 2.532999
## Wales -240.52915 224.646925
## Scotland -91.86934 -286.081786
## N.Ireland 477.39164 58.901862
Zk %>%
as.data.frame %>%
ggplot(aes(x=Z1,y=Z2,label=rownames(Zk))) +
geom_point(size = 3) +
geom_text(nudge_x = 50)
The graph suggests that Wales and England are quite similar in terms of food consumption, and North Ireland seem to have a very different food consumption pattern.
Also note that England is close to the origin, meaning that the food consumption pattern is close to the average pattern in the UK.
A few questions remain:
- How well can the 17-dimensional data be represented in a 2-dimensional subspace?
- How can we interpret the distances (differences) between the data points in terms of the original 17 variables?
The biplot
The biplot is a single 2-dimensional graph which displays the information in both \(\mathbf{Z}_2=\mathbf{U}_2\boldsymbol{\Delta}_2\) and \(\mathbf{V}_2\).
The plot of \(\mathbf{Z}_2\) has been discussed previously (e.g. best approximation of distances).
The name “bi”plot refers to the plotting of two parts of the SVD (\(\mathbf{Z}\) and \(\mathbf{V}\)) in a single graph.
From the geometrical interpretation of the SVD we know that \(\mathbf{Z}_2\) is the orthogonal projection of \(\mathbf{X}\) on \(\mathbf{V}_2\) the basis spanned by the first two singular values. Thus for the \(i^\text{th}\) individual we get \[
\mathbf{z}_{2,i}= \mathbf{x}_i \left[\begin{array}{cc}\mathbf{v}_1&\mathbf{v}_2\end{array}\right]
\]
We also know that we can approximate \(\mathbf{X}\) by \(\mathbf{X}_2\) which equals:
\[\begin{align}
\mathbf{X}_2 &= \mathbf{Z_2}\mathbf{V}_2^T\\
&= \mathbf{Z_2}\left[\begin{array}{c} \mathbf{v}_1^T\\
\mathbf{v}_2^T\end{array}\right]
= \mathbf{Z_2}
\left[
\begin{array}{ccc}\tilde{\mathbf{v}}_{2,1} \ldots \tilde{\mathbf{v}}_{2,p}\end{array}
\right]
\end{align}\]
with \(\mathbf{v}_1\) and \(\mathbf{v}_2\) the first two right singular vector and \(\tilde{\mathbf{v}}_j\) the j\(^\text{th}\) column of the matrix \(\mathbf{V}_2^T\).
- This basically shows us that the orthogonal projection of \(\mathbf{z}_{2,i}\) for subject/experimental unit i on \(\tilde{\mathbf{v}}_{2,j}\) gives us the approximation of the value for j\(^\text{th}\) variable that was observed for the \(i^\text{th}\) experimental unit, i.e. \(x_{2,ij}\). \[x_{2,ij}=\mathbf{z}_{2,i}^T\tilde{\mathbf{v}}_{2,j}\]
UK example
Biplot
Vk<-svdUk$v[,1:k]
rownames(Vk) <- uk %>% pull(X1)
colnames(Vk) <- colnames(Zk)
scaleFactor <- mean(svdUk$d[1:k])
diyBiplot <- ggplot() +
geom_point(
data=Zk %>% as.data.frame,
aes(x=Z1,y=Z2),
size = 3) +
geom_text(
data=Zk %>% as.data.frame,
aes(x=Z1,y=Z2,label=rownames(Zk)),
nudge_x = 50) +
geom_segment(
data=Vk %>% as.data.frame,
aes(x=0, y=0, xend=Z1*scaleFactor, yend=Z2*scaleFactor),
arrow=arrow(length=unit(0.4,"cm")),
alpha=0.25) +
geom_text(
data=Vk %>% as.data.frame,
aes(x=Z1*scaleFactor, y=Z2*scaleFactor, label=rownames(Vk)),
alpha=0.5,
size=3)
diyBiplot
The R code shows two ways of constructing a biplot: the first method starts from the SVD of \(\mathbf{X}\) and plots the p vectors \(\tilde{\mathbf{v}}_{2,j}\) as arrows. Note that we used a scaling factor to give the arrows a convenient length in the graph (not too small, not too large); it does not affect the interpretation. The second way of obtaining the biplot is simply by using the R built-in function biplot.
Illustration of projection
We project \(\mathbf{z}_{2,i}\) for N.Ireland on Fresh_potatoes.
rownames(X) <- rownames(Zk)
colnames(X) <- rownames(Vk)
X[,"Fresh_potatoes"]
## England Wales Scotland N.Ireland
## -78.25 75.75 -232.25 234.75
Zk["N.Ireland",]%*%Vk["Fresh_potatoes",]
## [,1]
## [1,] 233.7418
We observe that N. Ireland has a consumption in Fresh_potatoes that is 234.75 above the average and this is well approximated by the projection 233.74.
Also note that
The origin corresponds to the sample average of the 17-dimensional observations in the data matrix
England is close to the origin and thus one could say that the food consumption in England is as the average in the UK
Projecting the \(\mathbf{Z}_2\) coordinates for North Ireland orthogonally onto the vector \(\tilde{\mathbf{v}}_{2,j}\) of fresh potatoes, we get a large and positive \(x_{2,ij}\). Hence, people in N. Ireland tend to eat more fresh potatoes than on average in the UK.
We can do the same for the other vectors.
Further interpretation of the Biplot
From \(\mathbf{x}_{k,i}^T = \mathbf{z}_{k,i}^T\mathbf{V}_k^T\) we learnt that the original data can be (approximately) reconstructed from the \(\mathbf{z}_{k,i}\).
We now show how the dimensions of \(\mathbf{Z}_k\) (columns or variables) can be interpreted. Remember, \[
\mathbf{z}_{k,i}^T = \mathbf{x}_i^T\mathbf{V}_k = \left(\mathbf{x}_i^T \mathbf{v}_1 , \mathbf{x}_i^T \mathbf{v}_2 , \ldots , \mathbf{x}_i^T \mathbf{v}_k\right).
\]
The \(j\)th column of \(\mathbf{Z}_k\) can be written as \[
\mathbf{z}_{k,j} = \mathbf{X}\mathbf{v}_j,
\]
An individual element of \(\mathbf{Z}_k\) can be written as \[
z_{k,ij} = \mathbf{x}_i^T\mathbf{v}_j,
\]
which is a linear combination of the observations on the \(p\) variables in \(\mathbf{x}_i\), with coefficients given by the \(p\) elements in \(\mathbf{v}_j\). Understanding the elements in \(\mathbf{v}_j\) will give us insight into the interpretation of the \(j\)th dimension of \(\mathbf{Z}_k\).
Illustration Uk food
p1 <- ggplot() +
geom_bar(
aes( x=rownames(Vk), y=Vk[,1]),
stat="identity") +
xlab("V1") +
ylab("contribution") +
coord_flip()
p2 <-
ggplot() +
geom_bar(
aes( x=rownames(Vk), y=Vk[,2]),
stat="identity") +
xlab("V2") +
ylab("contribution") +
coord_flip()
grid.arrange(p1,p2,ncol=2)
In giving an interpretation to the elements in \(\mathbf{v}_1\) and \(\mathbf{v}_2\), we ignore the elements close to zero (this is a subjective decision; later we see more objective methods).
For \(\mathbf{v}_1\) (1st dimension of \(\mathbf{Z}_2\)):
contrast of soft drinks and fresh potatoes versus fresh fruit and alcoholic drinks
a large value of \(z_{i1}\) can result from eating many fresh potatoes and drinking a lot of soft drinks, but eating only few fresh fruit and drinking not much alcoholic drinks
a small value of \(z_{i1}\) can result from eating few fresh potatoes and drinking only few soft drinks, but eating a lot of fresh fruit and drinking much alcoholic drinks
For \(\mathbf{v}_2\) (2nd dimension of \(\mathbf{Z}_2\)):
contrast of soft drinks versus fresh potatoes
a large value of \(z_{i2}\) can result from eating many fresh potatoes, but drinking not much soft drinks
a small value of \(z_{i2}\) can result from eating few fresh potatoes, but drinking much soft drinks .
The elements in v1 and v2 are also shown in the biplot.
grid.arrange(p1, p2, diyBiplot, ncol=2, layout_matrix = rbind(c(1,2),c(3,3)))
From the graph we see e.g. that N. Ireland has a high score for the first dimension. Now that we can give an interpretation to the dimension, we conclude that in Northern Ireland people eat relatively much fresh potatoes and drink many soft drinks, but they do not drink much alcoholic drinks and eat not much fresh fruit. We had already come to these findings by projecting N. Ireland on the vectors of these four food products. This comes to no surprise: both interpretations arise from the same data set and its SVD, and so no contradictory results should arise. Other conclusions can be found in a similar fashion.
We have derived the interpretation of the first dimension from the barplot of the elements of \(\mathbf{v}_1\). However, there is actually no need to make a separate plot to read the elements of \(\mathbf{v}_1\); they can also be read from projecting the vectors in the biplot onto the first dimension (i.e. basis vector of the first dimension). For example, fresh potatoes is in the 7th column of \(\mathbf{X}\) and thus in the biplot its vector is \(\tilde{\mathbf{v}}_{2,7}\); it is a 2-dimensional vector (2-dimensional biplot is shown). In the space of the biplot (i.e. the column space of \(\mathbf{Z}_2\)), the first basis vector is given by \((1,0)\). Projecting \(\tilde{\mathbf{v}}_{2,7}^T\) orthogonally onto \((1,0)\) gives \[
\tilde{\mathbf{v}}_{2,7}^T \begin{pmatrix} 1 \\ 0 \end{pmatrix} = (v_{71} , v_{72}) \begin{pmatrix} 1 \\ 0 \end{pmatrix} = v_{71}
\] which is the seventh element of the first right-singular vector of \(\mathbf{X}\), which is thus the bar of fresh potatoes in the barplot of the first dimension.
SVD and principal component analysis (PCA)
PCA is basically a SVD
PCA adds another layer of interpretation
PCA comes with its own terminology
One of the most widely used algorithms for dimension reduction and data exploration of multivariate and high dimensional data.
It is motivated from the decomposition of the Variance covariance matrix of the data
Variance covariance matrix
- For a given centered data matrix \(\mathbf{X}\) the \(p\times p\) covariance matrix can be estimated by \[
\boldsymbol{\Sigma}_X = \frac{1}{n-1}\mathbf{X}^T\mathbf{X} =\frac{1}{n-1}\sum_{i=1}^n \mathbf{x}_i\mathbf{x}_i^T,
\] i.e. the \((i,j)\)th element is given by (column means are zero) \[
\frac{1}{n-1}\sum_{m=1}^n x_{mi}x_{mj}=\frac{1}{n-1}\sum_{m=1}^n (x_{mi}-\bar{x}_i)(x_{mj}-\bar{x}_j).
\]
Note, that when we forget to write the factor \(1/(n-1)\) all the derivations still hold. It is only a proportionality factor and it does not affect the interpretation.
Conventional derivation of PCA
PCA is usually introduced as follows.
Let \[
y_i=\mathbf{x}_i^T\mathbf{a}
\] with \(\mathbf{a}\) a \(p\)-vector of constants. Hence \(y_i\) is a linear combination (or transformation) of \(\mathbf{x}_i\).
PCA aims at finding \(\mathbf{a}\) such that the sample variance among the \(n\) \(y_i\)’s is maximal, with \[\begin{eqnarray*}
\frac{1}{n-1}\sum_{i=1}^n (y_i - \bar{y})^2 = \frac{1}{n-1}\sum_{i=1}^n y_i ^2 \\
&=& \frac{1}{n-1}\sum_{i=1}^n (\mathbf{x}_i^T\mathbf{a})^2 = \frac{1}{n-1} \mathbf{a}^T \left(\sum_{i=1}^n \mathbf{x}_i\mathbf{x}_i^T\right) \mathbf{a} \\
&=& \frac{1}{n-1} \mathbf{a}^T\mathbf{X}^T\mathbf{X}\mathbf{a} = \mathbf{a}^T\boldsymbol{\Sigma}_X\mathbf{a}
\end{eqnarray*}\] in which \(\boldsymbol{\Sigma}_X= \frac{1}{n-1}\mathbf{X}^T\mathbf{X}\) is the sample covariance matrix of \(\mathbf{X}\).
Problem for optimisation
Finding \(\mathbf{a}\) that maximises \[
\text{var}[y] = \mathbf{a}^T\boldsymbol{\Sigma}_X\mathbf{a}
\] has a trivial solution (set all elements of \(\mathbf{a}\) equal to \(\infty\)). To avoid the trivial solution, the solution must satisfy a restriction, e.g. \[
\Vert \mathbf{a}\Vert^2 = \mathbf{a}^T\mathbf{a}=1.
\]
The solution of the constrained maximisation problem may be formulated as \[
\mathbf{a} = \text{ArgMax}_{b:\Vert b\Vert^2=1} \mathbf{b}^T\boldsymbol{\Sigma}_X\mathbf{b}.
\] By introducing a Lagrange multiplier \(\lambda\), we get an unconstrained maximisation problem, \[
\mathbf{a} = \text{ArgMax}_{b} \left(\mathbf{b}^T\boldsymbol{\Sigma}_X\mathbf{b}-\lambda(\mathbf{b}^T\mathbf{b}-1)\right).
\]
Note that,
If the constraint is linear, then the constrained optimisation problem (here: maximisation) may be replaced by an unconstrained optimisation problem of the same criterion but with an ``penalty term’’ added. This method is due to Lagrange. The penalty term vanishes when the constraint is satisfied.
If the constraint is satisfied, \(\mathbf{b}^T\mathbf{b}=\Vert \mathbf{b}\Vert^2=1\) and thus \(\mathbf{b}^T\mathbf{b}-1=0\) and the ``penalty’’ term in the unconstrained criterion vanishes.
The Lagrange multiplier \(\lambda\) is to be considered as an extra parameter that may have to be estimated from the data.
The solution of \[
\mathbf{a} = \text{ArgMax}_{b} \left(\mathbf{b}^T\boldsymbol{\Sigma}_X\mathbf{b}-\lambda(\mathbf{b}^T\mathbf{b}-1)\right)
\] is obtained by differentiating \(\mathbf{b}^T\boldsymbol{\Sigma}_X\mathbf{b}-\lambda(\mathbf{b}^T\mathbf{b}-1)\) w.r.t. \(\mathbf{b}\), equating it to zero and solving for \(\mathbf{b}\). \[\begin{eqnarray*}
\frac{\partial}{\partial \mathbf{b}} \left(\mathbf{b}^T\boldsymbol{\Sigma}_X\mathbf{b}-\lambda(\mathbf{b}^T\mathbf{b}-1)\right)
&=& 0 \\
2\boldsymbol{\Sigma}_X\mathbf{b} -2\lambda\mathbf{b}
&=& 0.
\end{eqnarray*}\]
Hence, we need the solution of \[
\boldsymbol{\Sigma}_X\mathbf{b} = \lambda\mathbf{b}.
\]
This equation has \(r\) solutions:
The eigenvectors are orthonormal, i.e. \(\mathbf{e}_i^T\mathbf{e}_j=1\) if \(i=j\) and \(\mathbf{e}_i^T\mathbf{e}_j=0\) otherwise.
Consider the following calculations, with \(\mathbf{b}=\mathbf{e}_j\), the \(j\)the eigenvector of \(\boldsymbol{\Sigma}_X\). \[\begin{eqnarray*}
\text{var}[y]
&=& \mathbf{e}_j^T\boldsymbol{\Sigma}_X\mathbf{e}_j \\
&=& \mathbf{e}_j^T(\boldsymbol{\Sigma}_X\mathbf{e}_j) \\
&=& \mathbf{e}_j^T \left(\lambda_j \mathbf{e}_j\right) \\
&=& \lambda_j\mathbf{e}_j^T\mathbf{e}_j \\
&=& \lambda_j.
\end{eqnarray*}\] By convention the eigenvectors/eigenvalues are ordered so that \[
\lambda_1 \geq \lambda_2 \geq \cdots \geq \lambda_r.
\] Hence, the first eigenvector \(\mathbf{e}_1\) gives the largest variance \(\lambda_1\). Hence, this is the solution we were looking for.
We now switch notation and from now onwards we denote \(y\) by \(z\).
The observations on the first principal component (PC) are then given by \[
z_{i1} = \mathbf{x}_i^T\mathbf{e}_1.
\]
In PCA terminology they are referred to as the scores of the first PC. The elements in the eigenvector \(\mathbf{e}_i\) that make up the transformation are known as the loadings of the first PC.
The first PC is thus a new variable (construct) that has the largest variance among all linear transformations of the original variables.
Variability between observations is considered as informative to understand differences between the observations.
The equation \[
\boldsymbol{\Sigma}_X\mathbf{b} = \lambda\mathbf{b}
\] has \(r=\text{rank}(\boldsymbol{\Sigma}_X)\) solutions. The eigenvectors are orthonormal, i.e. \[
\forall i\neq j: \mathbf{e}_i^T \mathbf{e}_j = 0 \text{ and } i=j: \mathbf{e}_i^T \mathbf{e}_j = 1
\] Hence (for \(i\neq j\)) \[
\text{cov}\left[\mathbf{x}^T\mathbf{e}_i,\mathbf{x}^T\mathbf{e}_j\right] = \mathbf{e}_i^T\text{var}[\mathbf{x}]\mathbf{e}_j = \mathbf{e}_i^T\boldsymbol{\Sigma}_X\mathbf{e}_j = \mathbf{e}_i^T\left(\lambda_j\mathbf{e}_j\right) = \lambda_j \mathbf{e}_i^T\mathbf{e}_j = 0
\]
If \(z_{j}=\mathbf{x}_i^T\mathbf{e}_j\) denotes the \(j\)th PC, then \[
\text{cov}\left[z_i,z_j\right]=\text{cov}\left[\mathbf{x}^T\mathbf{e}_i,\mathbf{x}^T\mathbf{e}_j\right] =0 \text{ if } i\neq j
\] \[
\text{var}\left[z_j\right] = \lambda_j .
\] We say that the \(j\)th PC maximises the variance among all linear transformations such that it is uncorrelated with the previous PCs.
Interpretation of PCA
A PCA is a transformation of the original \(p\) variables to \(r\) PCs such that
the first PC has largest variance, equal to first eigenvalue of \(\boldsymbol{\Sigma}_X\)
the next PCs have decreasing variances (decreasing information content)
all PCs are mutually uncorrelated (no information-overlap).
Link with SVD
If we write the eigen value decomposition in matrix form: \[
\boldsymbol{\Sigma}_X\mathbf{
B} = \mathbf{B}\boldsymbol{\Lambda}.
\]
with \(\boldsymbol{\Lambda}\) is a diagonal matrix with diagonal elements \([\lambda_i]_{ii}\).
We recognise an expression that we have seen when discussing the link between the SVD and the sample covariance matrix
\[\begin{eqnarray}
\mathbf{X}^T\mathbf{X}\mathbf{V} &=&\mathbf{V}\boldsymbol{\Delta}^2
\end{eqnarray}\]
- So, the SVD can be used to solve the spectral decomposition (eigen value eigen vector) problem and the eigen vectors coincide with the right singular vectors and the eigen values are the singular values squared!
Conservation of variance
The total variance in a data set \(\mathbf{X}\) is given by the sum of the variances of the variables, \[
\sigma^2_{\text{tot}} = \sum_{j=1}^p \text{var}\left[X_j\right] = \text{trace}(\boldsymbol{\Sigma}_X)
\]
For a symmetric matrix it holds that \[
\text{trace}(\boldsymbol{\Sigma}_X) = \sum_{j=1}^r \lambda_r.
\]
Since \(\lambda_r=\text{var}\left[Z_j\right]\), we find \[
\sigma^2_{\text{tot}} = \sum_{j=1}^p \text{var}\left[X_j\right]= \sum_{j=1}^r \text{var}\left[Z_j\right].
\]
\(\longrightarrow\) Thus no information is lost by the PCA transformation from the original \(p\)-dimensional space to the \(r\)-dimensional PCA space.
Choosing the number of dimensions
For PCA the reasoning is usually based on the relative variance of a PC, \[
\frac{\text{var}\left[Z_j\right]}{\sigma^2_{\text{tot}}} = \frac{\lambda_j}{\sum_{i=1}^r \lambda_i} =\frac{\delta_j^2}{\sum_{i=1}^r \delta_i^2} .
\] If the first \(k\) PCs are selected for further use, then they represent \[
100 \times \frac{\sum_{j=1}^k \lambda_j}{\sum_{i=1}^r \lambda_i} \%
= 100 \times \frac{\sum_{j=1}^k \delta^2_j}{\sum_{i=1}^r \delta_i^2} \%
\] of the total variance (or information) of the original data set \(\mathbf{X}\).
UK example
A scree plot is often used to look at the eigenvalues. Here it is shown for the UK consumption data.
n <- nrow(X)
r <- ncol(svdUk$v)
totVar <- sum(svdUk$d^2)/(n-1)
vars <- data.frame(comp=1:4,var=svdUk$d^2/(n-1)) %>%
mutate(propVar=var/totVar,cumVar=cumsum(var/totVar))
pVar1 <- vars %>%
ggplot(aes(x=comp:r,y=var)) +
geom_point() +
geom_line() +
xlab("Component") +
ylab("Variance")
pVar2 <- vars %>%
ggplot(aes(x=comp:r,y=propVar)) +
geom_point() +
geom_line() +
xlab("Component") +
ylab("Proportion of Total Variance")
pVar3 <- vars %>%
ggplot(aes(x=comp:r,y=cumVar)) +
geom_point() +
geom_line() +
xlab("Component") +
ylab("Cum. prop. of tot. var.")
grid.arrange(pVar1, pVar2, pVar3, nrow=1)
## Warning in comp:r: numerical expression has 4 elements: only the first used
## Warning in comp:r: numerical expression has 4 elements: only the first used
## Warning in comp:r: numerical expression has 4 elements: only the first used
## Warning in comp:r: numerical expression has 4 elements: only the first used
## Warning in comp:r: numerical expression has 4 elements: only the first used
## Warning in comp:r: numerical expression has 4 elements: only the first used
## Warning in comp:r: numerical expression has 4 elements: only the first used
## Warning in comp:r: numerical expression has 4 elements: only the first used
## Warning in comp:r: numerical expression has 4 elements: only the first used
From these graphs we may conclude that with using k = 2 dimensions, more than 95% of the total variance is retained.
Choosing the number of dimensions SVD
More generally, scree plots are also informative for the SVD.
The motivation comes from \[
\text{Min}_{A: \text{rank}(A)=k} \Vert \mathbf{X} - \mathbf{A}\Vert_F^2 = \Vert \mathbf{X} - \mathbf{X}_k\Vert_F^2 = \sum_{j=k+1}^r\delta_{j}^2 = \sum_{j=k+1}^r\lambda_{j}.
\] This is known as the approximation error of the matrix \(\mathbf{X}_k\).
Hence, \[
\frac{\sum_{j=1}^k\delta_j^2}{\sum_{j=1}^r \delta_j^2} =\frac{\sum_{j=1}^k\lambda_{j}}{\sum_{j=1}^r\lambda_{j}}
\] still makes sense as a relative quality measure for the SVD in general (including matrix approximation and MDS).
Rules of thumb to select number of dimensions
Select k such that
at least 80% of the total variance is retained in the PC space, or, equivalently, at most 20% relative approximation error
adding more dimensions does not add more information (in a relative sense); this can often be visually detected as the “knee” or “elbow” in the scree plot
none of the dimensions has a variance smaller than the variance of one of the p original variables (this rule is typically only used when the variables are first standardised - see later).
Covariance vs Correlation Matrix
Direction of largest variability
Now that we know how the PCs are constructed, we can give an additional interpretation to the first eigenvector / right-singular vector of \(\boldsymbol{\Sigma}_X\).
The first eigenvector of \(\boldsymbol{\Sigma}_X\) is the direction in the original \(p\)-dimensional space of the largest variability.
The second eigenvector of \(\boldsymbol{\Sigma}_X\) is the direction in the original \(p\)-dimensional space of the second largest variability, among all directions orthogonal to the first eigenvector
Iris example
The iris dataset in R contains information on leaves of iris flowers from different species. Here, we will focus on the setosa species and on the sepal length and sepal width.
irisSetosa <- iris %>%
filter(Species == "setosa") %>%
dplyr::select("Sepal.Length","Sepal.Width")
nIris <- nrow(irisSetosa)
hIris <- diag(nIris) - matrix(1/nIris, nIris, nIris)
irisX <- irisSetosa %>%
as.matrix
irisX <- hIris %*% irisX
irisSvd <- svd(irisX)
pIris <- irisX %>%
as.data.frame %>%
ggplot(aes(x=Sepal.Length,y=Sepal.Width)) +
geom_point()
pIris <- pIris +
geom_segment(
aes(
x = 0,
y = 0,
xend = -irisSvd$v[1,1]*irisSvd$d[1]/sqrt(nIris-1),
yend = -irisSvd$v[2,1]*irisSvd$d[1]/sqrt(nIris-1)
),
arrow = arrow(length=unit(0.4,"cm"))
) +
geom_segment(
aes(
x = 0,
y = 0,
xend = irisSvd$v[1,2]*irisSvd$d[2]/sqrt(nIris-1),
yend = irisSvd$v[2,2]*irisSvd$d[2]/sqrt(nIris-1)
),
arrow = arrow(length=unit(0.4,"cm"))
) +
geom_text(
aes(
x = -irisSvd$v[1,1]*irisSvd$d[1]/sqrt(nIris-1)*1.2,
y = -irisSvd$v[2,1]*irisSvd$d[1]/sqrt(nIris-1)*1.2,
label="e1"
)
) +
geom_text(
aes(
x = irisSvd$v[1,2]*irisSvd$d[2]/sqrt(nIris-1)*1.2,
y = irisSvd$v[2,2]*irisSvd$d[2]/sqrt(nIris-1)*1.2,
label="e2"
)
) +
xlim(-1, 1) +
ylim(-1,1)
pIris
## Warning: Removed 1 rows containing missing values (geom_point).
Covariance versus correlation matrix
So far we have worked with
In some situations it is better to start from the standardised variables:
- substract from each element in \(\mathbf{X}\) the column-mean
- divide each centered element in \(\mathbf{X}\) by the column-specific standard deviation
Thus each element \(x_{ij}\) is replaced with \[
\frac{x_{ij}-\bar{x}_j}{s_j} ,
\] with \(\bar{x}_j\) and \(s_j\) the column mean and column standard deviation.
- In matrix notation, the standardisation, starting from the centered matrix \(\mathbf{X}\) is computed as \[
\mathbf{X}\mathbf{S}^{\prime-1}
\] where \(\mathbf{S}^\prime\) is a diagonal matrix with the column-specific standard deviations.
Iris Example
## Sepal.Length Sepal.Width
## Sepal.Length 0.12424898 0.09921633
## Sepal.Width 0.09921633 0.14368980
Sprime <- diag(sqrt(diag(var(irisX))))
Xs <- irisX%*%solve(Sprime)
var(Xs)
## [,1] [,2]
## [1,] 1.0000000 0.7425467
## [2,] 0.7425467 1.0000000
Much faster to use the scale function. By default center=TRUE and scale=TRUE.
Xs <- irisSetosa %>% scale
var(Xs)
## Sepal.Length Sepal.Width
## Sepal.Length 1.0000000 0.7425467
## Sepal.Width 0.7425467 1.0000000
Show problem of different units:
- Sepal length in cm
- Sepal width in mm
irisX2 <- irisX
irisX2[,2] <- irisX2[,2]*10
pIris2 <- irisX2 %>%
as.data.frame %>%
ggplot(aes(x=Sepal.Length,y=Sepal.Width)) +
geom_point()
irisSvd2 <- svd(irisX2)
pIris2 <- pIris2 +
geom_segment(
aes(
x = 0,
y = 0,
xend = irisSvd2$v[1,1]*irisSvd2$d[1]/sqrt(nIris-1),
yend = irisSvd2$v[2,1]*irisSvd2$d[1]/sqrt(nIris-1)
),
arrow = arrow(length=unit(0.4,"cm"))
) +
geom_segment(
aes(
x = 0,
y = 0,
xend = irisSvd2$v[1,2]*irisSvd2$d[2]/sqrt(nIris-1),
yend = irisSvd2$v[2,2]*irisSvd2$d[2]/sqrt(nIris-1)
),
arrow = arrow(length=unit(0.4,"cm"))
) +
geom_text(
aes(
x = irisSvd2$v[1,1]*irisSvd2$d[1]/sqrt(nIris-1)*1.2,
y = irisSvd2$v[2,1]*irisSvd2$d[1]/sqrt(nIris-1)*1.2,
label="e1"
)
) +
geom_text(
aes(
x = irisSvd2$v[1,2]*irisSvd2$d[2]/sqrt(nIris-1)*1.2,
y = irisSvd2$v[2,2]*irisSvd2$d[2]/sqrt(nIris-1)*1.2,
label="e2"
)
) +
xlim(-10, 10) +
ylim(-10,10)
grid.arrange(pIris, pIris2)
## Warning: Removed 1 rows containing missing values (geom_point).
## Warning: Removed 1 rows containing missing values (geom_point).
Top: original matrix – Bottom: second column of \(\mathbf{X}\) multiplied by 10 (e.g. moving from cm to mm).
Directions of maximal variability are affected by the units of the variables.
Recommendations
When to use correlation and when covariance?
There may be exceptions.
PCA and the Multivariate Normal Distribution
The density function of a multivariate normal distribution (MVN) is given by \[
f(\mathbf{x}) = (2\pi)^{-p/2}\vert \boldsymbol{\Sigma}\vert^{-1/2} \exp\left( -\frac{1}{2} (\mathbf{x}-\boldsymbol{\mu})^T \boldsymbol{\Sigma}^{-1} (\mathbf{x}-\boldsymbol{\mu})\right) ,
\] where
\(\boldsymbol{\mu}\) is the multivariate mean vector (\(p\)-dimensional). The \(j\)th element is \(\mu_j = \text{E}\left[X_j\right]\)
\(\boldsymbol{\Sigma}\) is the \(p \times p\) covariance matrix. The \((i,j)\)th element is \(\sigma_{ij}=\text{cov}\left[X_i, X_j\right]\).
To get a better understanding of the MVN we focus on the exponential which has the factor \[
(\mathbf{x}-\boldsymbol{\mu})^T \boldsymbol{\Sigma}^{-1} (\mathbf{x}-\boldsymbol{\mu})
\] This factor
is the only factor in the density function that depends on \(\mathbf{x}\)
is a \(\mathbf{quadratic}\) form
is constant in points \(\mathbf{x}\) with constant density \(f(\mathbf{x})\).
Consider \(p=2\) (bivariate normal). Then, all \(\mathbf{x} \in \mathbb{R}^2\) for which \[
(\mathbf{x}-\boldsymbol{\mu})^T \boldsymbol{\Sigma}^{-1} (\mathbf{x}-\boldsymbol{\mu}) = \text{constant } c^2
\] lie on an ellipse with center \(\boldsymbol{\mu}\).
These ellipses are known as constant density ellipses.
iris example
pIris +
stat_ellipse() +
stat_ellipse(level=.68) +
stat_ellipse(level=.1)
## Warning: Removed 1 rows containing non-finite values (stat_ellipse).
## Warning: Removed 1 rows containing non-finite values (stat_ellipse).
## Warning: Removed 1 rows containing non-finite values (stat_ellipse).
## Warning: Removed 1 rows containing missing values (geom_point).
Now plug in the SVD of \(\boldsymbol{\Sigma}=\mathbf{VDV}^T\), \[\begin{eqnarray*}
(\mathbf{x}-\boldsymbol{\mu})^T \boldsymbol{\Sigma}^{-1} (\mathbf{x}-\boldsymbol{\mu}) &=& c^2\\
(\mathbf{x}-\boldsymbol{\mu})^T \mathbf{VD}^{-1}\mathbf{V}^T (\mathbf{x}-\boldsymbol{\mu}) &=& c^2
\end{eqnarray*}\]
Note that \(\mathbf{x}-\boldsymbol{\mu}\) is the centered \(\mathbf{x}\). Without loss of generality, take \(\boldsymbol{\mu}=\mathbf{0}\). Hence, \[\begin{eqnarray*}
\mathbf{x}^T \mathbf{VD}^{-1}\mathbf{V}^T \mathbf{x} &=& c^2 \\
\left(\mathbf{x}^T\mathbf{V}\right) \mathbf{D}^{-1} \left(\mathbf{x}^T\mathbf{V}\right)^T &=& c^2 \\
\sum_{j=1}^p (\mathbf{x}^T\mathbf{v}_j)^2 / \delta_j &=& c^2\\
\sum_{j=1}^p (z_j)^2 / (c^2\delta_j) &=& 1.
\end{eqnarray*}\] The last equation is the equation of an ellipse with axes parallel to the basis of \((z_1,\ldots, z_p)\) and with half axis lengths \(c\sqrt{\lambda_j}=c\delta_j\) with \(\lambda_j\) the \(j\)th eigenvalue of \(\boldsymbol{\Sigma}\).
pIris +
stat_ellipse() +
stat_ellipse(level=.68) +
stat_ellipse(level=.1)
## Warning: Removed 1 rows containing non-finite values (stat_ellipse).
## Warning: Removed 1 rows containing non-finite values (stat_ellipse).
## Warning: Removed 1 rows containing non-finite values (stat_ellipse).
## Warning: Removed 1 rows containing missing values (geom_point).
This graphs shows \(n=50\) data points on \(p=2\) dimensions. The two ellipses are constant density ellipses for three different values of \(c\) (i.e. three different constant densities). The inner ellipse corresponds to the largest constant density and the outer to the smallest constant density. The arrows show the two eigenvectors / singular vectors and they are scaled according to the sqrt of the eigen values and they form the axes of the constant density ellipses. It is clear that the first axis is pointing to the larger variance.
Biplot
- Because of the close connection between PCA and the SVD, the biplot as discussed before is still meaningful, with the first axis pointing into the direction of largest variance.
Ovarian Cancer Example
The ovarian cancer data set consists of proteomics data for 216 patients, 121 of whom have ovarian cancer, and 95 of whom do not. For each subject, the expression of 4000 spectral features is assessed. The first 121 rows consist of data for the cancer patients.
Importing the data
ovarian <- read_csv(
"https://raw.githubusercontent.com/statOmics/HDA2020/data/ovarian.csv",
col_names = FALSE,
col_types = cols()
)
grid.arrange(
qplot(1:4000,
ovarian[1,] %>% unlist,
geom="line",
ylab="centered intensity",
xlab="",main="cancer",
ylim=range(ovarian[c(1,2,200,201),])),
qplot(1:4000,
ovarian[2,] %>% unlist,
geom="line",
ylab="centered intensity",
xlab="",main="cancer",
ylim=range(ovarian[c(1,2,200,201),])),
qplot(1:4000,
ovarian[200,] %>% unlist,
geom="line",
ylab="centered intensity",
xlab="",main="Normal",
ylim=range(ovarian[c(1,2,200,201),])),
qplot(1:4000,
ovarian[200,] %>% unlist,
geom="line",
ylab="centered intensity",
xlab="",main="Normal",
ylim=range(ovarian[c(1,2,200,201),])),
ncol=1
)
Centering
ovarian <- scale(ovarian, scale=FALSE)
grid.arrange(
qplot(1:4000,
ovarian[1,],
geom="line",
ylab="centered intensity",
xlab="",main="cancer",
ylim=range(ovarian[c(1,2,200,201),])),
qplot(1:4000,
ovarian[2,],
geom="line",
ylab="centered intensity",
xlab="",main="cancer",
ylim=range(ovarian[c(1,2,200,201),])),
qplot(1:4000,
ovarian[200,],
geom="line",
ylab="centered intensity",
xlab="",main="Normal",
ylim=range(ovarian[c(1,2,200,201),])),
qplot(1:4000,
ovarian[201,],
geom="line",
ylab="centered intensity",
xlab="",main="Normal",
ylim=range(ovarian[c(1,2,200,201),])),
ncol=1
)
SVD Analysis
svdOvarian <- svd(ovarian)
nOvarian <- nrow(ovarian)
r <- ncol(svdOvarian$v)
totVar <- sum(svdOvarian$d^2)/(nOvarian-1)
vars <- data.frame(comp=1:r,var=svdOvarian$d^2/(nOvarian-1)) %>%
mutate(propVar=var/totVar,cumVar=cumsum(var/totVar))
pVar2 <- vars %>%
ggplot(aes(x=comp:r,y=propVar)) +
geom_point() +
geom_line() +
xlab("Component") +
ylab("Proportion of Total Variance")
pVar3 <- vars %>%
ggplot(aes(x=comp:r,y=cumVar)) +
geom_point() +
geom_line() +
xlab("Component") +
ylab("Cum. prop. of tot. var.")
grid.arrange(pVar2, pVar3, nrow=1)
## Warning in comp:r: numerical expression has 216 elements: only the first used
## Warning in comp:r: numerical expression has 216 elements: only the first used
## Warning in comp:r: numerical expression has 216 elements: only the first used
## Warning in comp:r: numerical expression has 216 elements: only the first used
## Warning in comp:r: numerical expression has 216 elements: only the first used
## Warning in comp:r: numerical expression has 216 elements: only the first used
We see that we can explain a lot of the variability using a few PC’s!
Zk <- svdOvarian$u[,1:6]%*%diag(svdOvarian$d[1:6])
colnames(Zk) <- paste0("Z",1:6)
Vk <- svdOvarian$v[,1:6]
colnames(Vk) <- paste0("V",1:6)
reduced <- data.frame(Zk,cancer=c(rep(1,121),rep(2,95)))
pOv1 <- reduced %>%
ggplot(aes(x=Z1,y=Z2,col=cancer)) +
geom_point() +
theme(legend.position = "none")
pOv2 <- Vk %>%
as.data.frame %>%
ggplot(aes(x=1:4000,y=V1)) +
geom_line() +
xlab("")
pOv3 <- Vk %>%
as.data.frame %>%
ggplot(aes(x=1:4000,y=V2)) +
geom_line() +
xlab("")
grid.arrange(pOv1,pOv2,pOv3,layout_matrix = rbind(c(1,3),c(2,NA)))
Reconstruction of profiles:
i <- 1
pOv4 <- qplot(1:4000,
ovarian[i,],
geom="line",
ylab="centered intensity",
xlab="",main="Cancer",
ylim=range(ovarian[c(1,2,200,201),]))
pOv5 <- qplot(1:4000,
Vk[,1:2] %*%Zk[i,1:2],
geom="line",
ylab="centered intensity",
xlab="",main="Reconstructed",
ylim=range(ovarian[c(1,2,200,201),]))
grid.arrange(
pOv1 +
annotate("point", x = reduced[i,1], y = reduced[i,2], colour = "red",cex=3),
pOv2,
pOv3,
pOv4,
pOv5,
layout_matrix = rbind(c(1,2,3),c(4,5,NA)))
i <- 3
pOv4 <- qplot(1:4000,
ovarian[i,],
geom="line",
ylab="centered intensity",
xlab="",main="Cancer",
ylim=range(ovarian[c(1,2,200,201),]))
pOv5 <- qplot(1:4000,
Vk[,1:2] %*%Zk[i,1:2],
geom="line",
ylab="centered intensity",
xlab="",main="Reconstructed",
ylim=range(ovarian[c(1,2,200,201),]))
grid.arrange(
pOv1 +
annotate("point", x = reduced[i,1], y = reduced[i,2], colour = "red",cex=3),
pOv2,
pOv3,
pOv4,
pOv5,
layout_matrix = rbind(c(1,2,3),c(4,5,NA)))
Acknowledgement
- Olivier Thas for sharing his materials of Analysis of High Dimensional Data 2019-2020, which I used as the starting point for this chapter.
Session info
Session info
## [1] "2021-05-11 14:56:44 UTC"
## ─ Session info ───────────────────────────────────────────────────────────────
## setting value
## version R version 4.0.5 (2021-03-31)
## os macOS Catalina 10.15.7
## system x86_64, darwin17.0
## ui X11
## language (EN)
## collate en_US.UTF-8
## ctype en_US.UTF-8
## tz UTC
## date 2021-05-11
##
## ─ Packages ───────────────────────────────────────────────────────────────────
## package * version date lib
## AIMS * 1.22.0 2020-10-27 [1]
## amap 0.8-18 2019-12-12 [1]
## AnnotationDbi 1.52.0 2020-10-27 [1]
## AnnotationHub * 2.22.1 2021-04-16 [1]
## askpass 1.1 2019-01-13 [1]
## assertthat 0.2.1 2019-03-21 [1]
## backports 1.2.1 2020-12-09 [1]
## beachmat 2.6.4 2020-12-20 [1]
## beeswarm 0.3.1 2021-03-07 [1]
## Biobase * 2.50.0 2020-10-27 [1]
## BiocFileCache * 1.14.0 2020-10-27 [1]
## BiocGenerics * 0.36.1 2021-04-16 [1]
## BiocManager 1.30.15 2021-05-11 [1]
## BiocNeighbors 1.8.2 2020-12-07 [1]
## BiocParallel 1.24.1 2020-11-06 [1]
## BiocSingular 1.6.0 2020-10-27 [1]
## BiocVersion 3.12.0 2020-05-14 [1]
## biomaRt * 2.46.3 2021-02-11 [1]
## bit 4.0.4 2020-08-04 [1]
## bit64 4.0.5 2020-08-30 [1]
## bitops 1.0-7 2021-04-24 [1]
## blob 1.2.1 2020-01-20 [1]
## bmp 0.3 2017-09-11 [1]
## boot * 1.3-28 2021-05-03 [1]
## bootstrap 2019.6 2019-06-17 [1]
## breastCancerMAINZ * 1.28.0 2020-10-29 [1]
## broom 0.7.6 2021-04-05 [1]
## bslib 0.2.4 2021-01-25 [1]
## cachem 1.0.4 2021-02-13 [1]
## callr 3.7.0 2021-04-20 [1]
## CCA * 1.2.1 2021-03-01 [1]
## cellranger 1.1.0 2016-07-27 [1]
## class 7.3-18 2021-01-24 [2]
## cli 2.5.0 2021-04-26 [1]
## cluster * 2.1.2 2021-04-17 [1]
## codetools 0.2-18 2020-11-04 [2]
## colorspace 2.0-1 2021-05-04 [1]
## crayon 1.4.1 2021-02-08 [1]
## curl 4.3.1 2021-04-30 [1]
## DAAG * 1.24 2020-03-10 [1]
## DBI 1.1.1 2021-01-15 [1]
## dbplyr * 2.1.1 2021-04-06 [1]
## DelayedArray 0.16.3 2021-03-24 [1]
## DelayedMatrixStats 1.12.3 2021-02-03 [1]
## desc 1.3.0 2021-03-05 [1]
## devtools 2.4.1 2021-05-05 [1]
## digest 0.6.27 2020-10-24 [1]
## dotCall64 * 1.0-1 2021-02-11 [1]
## downloader * 0.4 2015-07-09 [1]
## dplyr * 1.0.6 2021-05-05 [1]
## e1071 * 1.7-6 2021-03-18 [1]
## elasticnet 1.3 2020-05-15 [1]
## ellipsis 0.3.2 2021-04-29 [1]
## emo 0.0.0.9000 2021-05-11 [1]
## evaluate 0.14 2019-05-28 [1]
## ExperimentHub * 1.16.1 2021-04-16 [1]
## fansi 0.4.2 2021-01-15 [1]
## farver 2.1.0 2021-02-28 [1]
## fastmap 1.1.0 2021-01-25 [1]
## fda * 5.1.9 2020-12-16 [1]
## fds * 1.8 2018-10-31 [1]
## fields * 11.6 2020-10-09 [1]
## forcats * 0.5.1 2021-01-27 [1]
## foreach 1.5.1 2020-10-15 [1]
## fs 1.5.0 2020-07-31 [1]
## genefu * 2.22.1 2021-01-26 [1]
## generics 0.1.0 2020-10-31 [1]
## GenomeInfoDb * 1.26.7 2021-04-08 [1]
## GenomeInfoDbData 1.2.4 2021-05-11 [1]
## GenomicRanges * 1.42.0 2020-10-27 [1]
## gganimate * 1.0.7 2020-10-15 [1]
## ggbeeswarm 0.6.0 2017-08-07 [1]
## ggbiplot * 0.55 2021-05-11 [1]
## ggforce * 0.3.3 2021-03-05 [1]
## ggmap * 3.0.0 2019-02-05 [1]
## ggplot2 * 3.3.3 2020-12-30 [1]
## GIGrvg 0.5 2017-06-10 [1]
## git2r 0.28.0 2021-01-10 [1]
## glmnet * 4.1-1 2021-02-21 [1]
## glue 1.4.2 2020-08-27 [1]
## gridExtra * 2.3 2017-09-09 [1]
## gtable 0.3.0 2019-03-25 [1]
## haven 2.4.1 2021-04-23 [1]
## hdrcde 3.4 2021-01-18 [1]
## highr 0.9 2021-04-16 [1]
## hms 1.0.0 2021-01-13 [1]
## htmltools 0.5.1.1 2021-01-22 [1]
## httpuv 1.6.1 2021-05-07 [1]
## httr 1.4.2 2020-07-20 [1]
## HyperbolicDist 0.6-2 2009-09-23 [1]
## iC10 * 1.5 2019-02-08 [1]
## iC10TrainingData * 1.3.1 2018-08-24 [1]
## igraph 1.2.6 2020-10-06 [1]
## imager * 0.42.8 2021-03-15 [1]
## impute * 1.64.0 2020-10-27 [1]
## interactiveDisplayBase 1.28.0 2020-10-27 [1]
## IRanges * 2.24.1 2020-12-12 [1]
## irlba 2.3.3 2019-02-05 [1]
## iterators 1.0.13 2020-10-15 [1]
## jpeg 0.1-8.1 2019-10-24 [1]
## jquerylib 0.1.4 2021-04-26 [1]
## jsonlite 1.7.2 2020-12-09 [1]
## KernSmooth 2.23-18 2020-10-29 [2]
## knitr 1.33 2021-04-24 [1]
## ks 1.12.0 2021-02-07 [1]
## labeling 0.4.2 2020-10-20 [1]
## lars 1.2 2013-04-24 [1]
## later 1.2.0 2021-04-23 [1]
## latex2exp * 0.5.0 2021-03-18 [1]
## lattice * 0.20-41 2020-04-02 [2]
## latticeExtra 0.6-29 2019-12-19 [1]
## lava 1.6.9 2021-03-11 [1]
## lifecycle 1.0.0 2021-02-15 [1]
## limma * 3.46.0 2020-10-27 [1]
## locfdr * 1.1-8 2015-07-15 [1]
## lubridate 1.7.10 2021-02-26 [1]
## magick 2.7.2 2021-05-02 [1]
## magrittr * 2.0.1 2020-11-17 [1]
## maps 3.3.0 2018-04-03 [1]
## MASS * 7.3-53.1 2021-02-12 [2]
## Matrix * 1.3-2 2021-01-06 [2]
## MatrixGenerics * 1.2.1 2021-01-30 [1]
## matrixStats * 0.58.0 2021-01-29 [1]
## mclust * 5.4.7 2020-11-20 [1]
## mda 0.5-2 2020-06-29 [1]
## memoise 2.0.0 2021-01-26 [1]
## mgcv * 1.8-35 2021-04-18 [1]
## mime 0.10 2021-02-13 [1]
## misc3d 0.9-0 2020-09-06 [1]
## modelr 0.1.8 2020-05-19 [1]
## munsell 0.5.0 2018-06-12 [1]
## muscData * 1.4.0 2020-10-29 [1]
## mvtnorm 1.1-1 2020-06-09 [1]
## nlme * 3.1-152 2021-02-04 [2]
## NormalBetaPrime * 2.2 2019-01-19 [1]
## openssl 1.4.4 2021-04-30 [1]
## pamr * 1.56.1 2019-04-22 [1]
## pcaPP * 1.9-74 2021-04-23 [1]
## pillar 1.6.0 2021-04-13 [1]
## pixmap * 0.4-12 2021-01-29 [1]
## pkgbuild 1.2.0 2020-12-15 [1]
## pkgconfig 2.0.3 2019-09-22 [1]
## pkgload 1.2.1 2021-04-06 [1]
## plot3D * 1.3 2019-12-18 [1]
## plotROC * 2.2.1 2018-06-23 [1]
## pls * 2.7-3 2020-08-07 [1]
## plyr * 1.8.6 2020-03-03 [1]
## PMA * 1.2.1 2020-02-03 [1]
## png 0.1-7 2013-12-03 [1]
## polyclip 1.10-0 2019-03-14 [1]
## pracma 2.3.3 2021-01-23 [1]
## prettyunits 1.1.1 2020-01-24 [1]
## processx 3.5.2 2021-04-30 [1]
## prodlim * 2019.11.13 2019-11-17 [1]
## progress 1.2.2 2019-05-16 [1]
## promises 1.2.0.1 2021-02-11 [1]
## proxy 0.4-25 2021-03-05 [1]
## ps 1.6.0 2021-02-28 [1]
## pscl 1.5.5 2020-03-07 [1]
## purrr * 0.3.4 2020-04-17 [1]
## R6 2.5.0 2020-10-28 [1]
## rainbow * 3.6 2019-01-29 [1]
## rappdirs 0.3.3 2021-01-31 [1]
## RColorBrewer 1.1-2 2014-12-07 [1]
## Rcpp 1.0.6 2021-01-15 [1]
## RCurl * 1.98-1.3 2021-03-16 [1]
## readbitmap 0.1.5 2018-06-27 [1]
## readr * 1.4.0 2020-10-05 [1]
## readxl 1.3.1 2019-03-13 [1]
## remotes 2.3.0 2021-04-01 [1]
## reprex 2.0.0 2021-04-02 [1]
## RgoogleMaps 1.4.5.3 2020-02-12 [1]
## rjson 0.2.20 2018-06-08 [1]
## rlang 0.4.11 2021-04-30 [1]
## rmarkdown 2.8 2021-05-07 [1]
## rmeta 3.0 2018-03-20 [1]
## rprojroot 2.0.2 2020-11-15 [1]
## RSQLite 2.2.7 2021-04-22 [1]
## rstudioapi 0.13 2020-11-12 [1]
## rsvd 1.0.5 2021-04-16 [1]
## rvest 1.0.0 2021-03-09 [1]
## S4Vectors * 0.28.1 2020-12-09 [1]
## sass 0.3.1 2021-01-24 [1]
## scales * 1.1.1 2020-05-11 [1]
## scater * 1.18.6 2021-02-26 [1]
## scuttle 1.0.4 2020-12-17 [1]
## SemiPar * 1.0-4.2 2018-04-16 [1]
## sessioninfo 1.1.1 2018-11-05 [1]
## shape 1.4.5 2020-09-13 [1]
## shiny 1.6.0 2021-01-25 [1]
## SingleCellExperiment * 1.12.0 2020-10-27 [1]
## sp 1.4-5 2021-01-10 [1]
## spam * 2.6-0 2020-12-14 [1]
## sparseLDA * 0.1-9 2016-09-22 [1]
## sparseMatrixStats 1.2.1 2021-02-02 [1]
## stringi 1.6.1 2021-05-10 [1]
## stringr * 1.4.0 2019-02-10 [1]
## SummarizedExperiment * 1.20.0 2020-10-27 [1]
## SuppDists 1.1-9.5 2020-01-18 [1]
## survcomp * 1.40.0 2020-10-27 [1]
## survival * 3.2-10 2021-03-16 [2]
## survivalROC 1.0.3 2013-01-13 [1]
## testthat 3.0.2 2021-02-14 [1]
## tibble * 3.1.1 2021-04-18 [1]
## tidyr * 1.1.3 2021-03-03 [1]
## tidyselect 1.1.1 2021-04-30 [1]
## tidyverse * 1.3.1 2021-04-15 [1]
## tiff 0.1-8 2021-03-31 [1]
## tinytex 0.31 2021-03-30 [1]
## truncnorm 1.0-8 2018-02-27 [1]
## tweenr 1.0.2 2021-03-23 [1]
## usethis 2.0.1 2021-02-10 [1]
## utf8 1.2.1 2021-03-12 [1]
## vctrs 0.3.8 2021-04-29 [1]
## vipor 0.4.5 2017-03-22 [1]
## viridis 0.6.1 2021-05-11 [1]
## viridisLite 0.4.0 2021-04-13 [1]
## withr 2.4.2 2021-04-18 [1]
## xfun 0.22 2021-03-11 [1]
## XML 3.99-0.6 2021-03-16 [1]
## xml2 1.3.2 2020-04-23 [1]
## xtable 1.8-4 2019-04-21 [1]
## XVector 0.30.0 2020-10-28 [1]
## yaml 2.2.1 2020-02-01 [1]
## zlibbioc 1.36.0 2020-10-28 [1]
## source
## Bioconductor
## CRAN (R 4.0.2)
## Bioconductor
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Bioconductor
## CRAN (R 4.0.2)
## Bioconductor
## Bioconductor
## Bioconductor
## CRAN (R 4.0.5)
## Bioconductor
## Bioconductor
## Bioconductor
## Bioconductor
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.5)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.5)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Bioconductor
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Github (hadley/emo@3f03b11)
## CRAN (R 4.0.1)
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Bioconductor
## CRAN (R 4.0.2)
## Bioconductor
## Bioconductor
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Github (vqv/ggbiplot@7325e88)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Bioconductor
## Bioconductor
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.5)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.5)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.5)
## CRAN (R 4.0.5)
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.5)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Bioconductor
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Bioconductor
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Bioconductor
## CRAN (R 4.0.5)
## CRAN (R 4.0.2)
## Bioconductor
## CRAN (R 4.0.2)
## Bioconductor
## CRAN (R 4.0.5)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.5)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## CRAN (R 4.0.2)
## Bioconductor
## CRAN (R 4.0.2)
## Bioconductor
##
## [1] /Users/runner/work/_temp/Library
## [2] /Library/Frameworks/R.framework/Versions/4.0/Resources/library
LS0tCnRpdGxlOiAiMi4gU2luZ3VsYXIgVmFsdWUgRGVjb21wb3NpdGlvbiIKYXV0aG9yOiAiTGlldmVuIENsZW1lbnQiCmRhdGU6ICJzdGF0T21pY3MsIEdoZW50IFVuaXZlcnNpdHkgKGh0dHBzOi8vc3RhdG9taWNzLmdpdGh1Yi5pbykiCm91dHB1dDoKICAgIGh0bWxfZG9jdW1lbnQ6CiAgICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgICAgdGhlbWU6IGNvc21vCiAgICAgIHRvYzogdHJ1ZQogICAgICB0b2NfZmxvYXQ6IHRydWUKICAgICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICAgIHBkZl9kb2N1bWVudDoKICAgICAgdG9jOiB0cnVlCiAgICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgICBsYXRleF9lbmdpbmU6IHhlbGF0ZXgKLS0tCgojIEludHJvZHVjdGlvbgoKIyMgTW90aXZhdGlvbgoKVGhlIFNWRCBpcyBvbmUgb2YgdGhlIG1vc3Qgd2VsbCB1c2VkIGFuZCBnZW5lcmFsIHB1cnBvc2UgdG9vbHMgZnJvbSBsaW5lYXIgYWxnZWJyYSBmb3IgZGF0YSBwcm9jZXNzaW5nIQoKTWV0aG9kb2xvZ2ljYWxseQoKLSBEaW1lbnNpb24gcmVkdWN0aW9uIChlLmcuIGltYWdlcywgZ2VuZSBleHByZXNzaW9uIGRhdGEsIG1vdmllIHByZWZlcmVuY2VzKQotIFVzZWQgYXMgYSBmaXJzdCBzdGVwIGluIG1hbnkgZGF0YSByZWR1Y3Rpb24gYW5kIG1hY2hpbmUgbGVhcm5pbmcgYXBwcm9hY2hlcwotIFRheWxvciBhIGNvb3JkaW5hdGUgc3lzdGVtIGJhc2VkIG9uIHRoZSBkYXRhIHdlIGhhdmUKLSBTb2x2ZSBzeXN0ZW0gb2YgbGluZWFyIGVxdWF0aW9ucyBmb3Igbm9uLXNxdWFyZSBtYXRyaWNlczogZS5nLiBsaW5lYXIgcmVncmVzc2lvbgotIEJhc2lzIGZvciBwcmluY2lwYWwgY29tcG9uZW50IGFuYWx5c2lzIChQQ0EpIGFuZCBtdWx0aWRpbWVuc2lvbmFsIHNjYWxpbmcgKE1EUykuCiAgLSBQQ0EgaXMgb25lIG9mIHRoZSBtb3N0IHdpZGVseSB1c2VkIG1ldGhvZHMgdG8gc3R1ZHkgaGlnaCBkaW1lbnNpb25hbCBkYXRhIGFuZCB0byB1bmRlcnN0YW5kIHRoZW0gaW4gdGVybXMgb2YgdGhlaXIgIGRvbWluYW50IHBhdHRlcm5zIGFuZCBjb3JyZWxhdGlvbnMKCkFwcGxpY2F0aW9uczoKCi0gQXQgdGhlIGhlYXJ0IG9mIHNlYXJjaCBlbmdpbmVzOiBHb29nbGUKLSBCYXNpcyBvZiBtYW55IGZhY2lhbCByZWNvZ25pdGlvbiBtZXRob2RzOiBlLmcuIEZhY2Vib29rCi0gUmVjb21tZW5kZXIgc3lzdGVtcyBzdWNoIGFzIEFtYXpvbiBhbmQgTmV0ZmxpeAotIEEgc3RhbmRhcmQgdG9vbCBmb3IgZGF0YSBleHBsb3JhdGlvbiBhbmQgZGltZW5zaW9uIHJlZHVjdGlvbiBpbiBHZW5vbWljcwoKIyMgRGlzY2xhaW1lcgoKV2hlbiB5b3Ugd2FudCB0byBydW4gdGhlIHNjcmlwdCB5b3Ugd2lsbCBoYXZlIHRvIGNvbW1lbnQgb3V0IHRoZSBldmFsPUZBTFNFIHN0YXRlbWVudCBpbiBzb21lIFIgY2h1bmtzLiBCZWNhdXNlIHRoZSBTVkQgdGFrZXMgYSB3aGlsZSBvbiB0aGUgZmFjZXMgZXhhbXBsZSB3ZSBzYXZlIHRoZSBzdmQgZm9yIGxhdGVyIHVzZS4gU28geW91IGhhdmUgdG8gY29tbWVudCB0aGUgZXZhbD1GQUxTRSBzdGF0ZW1lbnQgaW4gdGhpcyBjaHVuayB3aGVuIHlvdSBydW4gdGhlIHNjcmlwdCBmb3IgdGhlIGZpcnN0IHRpbWUuCgojIyBEYXRhCgotIEV4dGVuZGVkIFlhbGUgRmFjZSBEYXRhYmFzZSBCCi0gQ3JvcHBlZCBhbmQgYWxpZ25lZCBpbWFnZXMgb2YgMzggaW5kaXZpZHVhbHMgdW5kZXIgNjQgbGlnaHRpbmcgY29uZGl0aW9ucy4KLSBFYWNoIGltYWdlIGlzIDE5MiBwaXhlbHMgdGFsbCBhbmQgMTY4IHBpeGVscyB3aWRlLgotIEVhY2ggb2YgdGhlIGZhY2lhbCBpbWFnZXMgaW4gb3VyIGxpYnJhcnkgd2lsbCBiZSByZXNoYXBlZCBpbnRvIGEgbGFyZ2UgdmVjdG9yIHdpdGggMTkyIMOXIDE2OCA9IDMyIDI1NiBlbGVtZW50cy4KLSBXZSB3aWxsIHVzZSB0aGUgNjQgaW1hZ2VzIG9mIDM2IHBlb3BsZSB0byBidWlsZCBvdXIgbW9kZWxzCgpgYGB7ciBsaWJyYXJpZXMsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIHNpbGVudD1UUlVFfQpsaWJyYXJ5KHBpeG1hcCkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ3JpZEV4dHJhKQpsaWJyYXJ5KGdyaWQpCmxpYnJhcnkoZ2dtYXApCmxpYnJhcnkoZG93bmxvYWRlcikKbGlicmFyeShpbWFnZXIpCmBgYAoKYGBge3IgZG93bmxvYWQtZGF0YSwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KIyMgRG93bmxvYWQgYW5kIHVuemlwIGRhdGEKaWYoIWRpci5leGlzdHMoInJhdy1kYXRhIikpIGRpci5jcmVhdGUoInJhdy1kYXRhIikKZG93bmxvYWQoCiAgImh0dHBzOi8vZ2l0aHViLmNvbS9zdGF0T21pY3MvSERBMjAyMC9yYXcvZGF0YS95YWxlZmFjZXNfY3JvcHBlZC56aXAiLAogIGRlc3RmaWxlID0gInJhdy1kYXRhL3lhbGVmYWNlc19jcm9wcGVkLnppcCIsIG1vZGUgPSAid2IiLCBxdWlldCA9IFRSVUUKKQp1bnppcCAoInJhdy1kYXRhL3lhbGVmYWNlc19jcm9wcGVkLnppcCIsIGV4ZGlyID0gIi4vcmF3LWRhdGEiKQoKZGlyIDwtICIuL3Jhdy1kYXRhL0Nyb3BwZWRZYWxlIgpgYGAKCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQpwZW9wbGUgPC0gbGlzdC5maWxlcyhkaXIpCnBlb3BsZTIgPC0gc2FwcGx5KHBlb3BsZSwKICBmdW5jdGlvbih4KSBsaXN0LmZpbGVzKAogICAgcGFzdGUwKGRpciwiLyIseCksCiAgICBmdWxsLm5hbWVzPVRSVUUKICAgICkKICApCgpmYWNlc0xpc3QgPC0gbGFwcGx5KHBlb3BsZTIsIGZ1bmN0aW9uKHgpIHJlYWQucG5tKHgpKQoKIGdyaWQuYXJyYW5nZSgKICBncm9icz1sYXBwbHkoZmFjZXNMaXN0WzErKDA6MzUpKjY0XSwKICAgICBmdW5jdGlvbih4KSBnZXRDaGFubmVscyh4KSAlPiUKICAgICAgICBnZ2ltYWdlKC4sY29vcmRfZXF1YWw9VFJVRSkKICAgICAgICApLAogIG5jb2w9NikKYGBgCgoKIyMgTWV0aG9kCgpMZXQgJFxtYXRoYmZ7WH0kIGJlIGFuICRuXHRpbWVzIHAkIG1hdHJpeCBlLmcuCgotIGdlbmUgZXhwcmVzc2lvbiBvZiAkcD00MCAwMDAkIGdlbmVzIGZvciAkbj0zMCQgc3ViamVjdHMKLSBuID0gMTAwIDAwMCAwMDAgd2VicGFnZXMgaW5kZXhlZCB3aXRoIHAgc2VhcmNoIHRlcm1zLCBvcgotICRuJCBpbWFnZXMgZWFjaCBzdG9yZWQgYXMgYSAkcD0zMiAyNTYkIHZlY3RvciB3aXRoIHRoZSBpbnRlbnNpdHkgb2YgZWFjaCBwaXhlbAoKPCEtLSBOZWVkICJlbW8iIHBhY2thZ2UgZm9yIGVtb2ppcywgaW5zdGFsbCB3aXRoIC0tPgo8IS0tIGBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImhhZGxleS9lbW8iKWAgLS0+CgpfX05vdGU6X18gdGhlIGVtb2ppIGNoYXJhY3RlcnMgd2lsbCBub3QgYmUgdmlzaWJsZSBpbiB0aGUgUERGIG91dHB1dC4KClxbWD0KXGxlZnRbXGJlZ2lue2FycmF5fXtjY2N9Ci0mXG1hdGhiZnt4fV97MX1eVCAmLSBcXApcdmRvdHMmXHZkb3RzJlx2ZG90c1xcCi0mXG1hdGhiZnt4fV97aX1eVCAmLSBcXApcdmRvdHMmXHZkb3RzJlx2ZG90c1xcCi0mXG1hdGhiZnt4fV97bn1eVCAmLSBcXApcZW5ke2FycmF5fVxyaWdodF1fe24gXHRpbWVzIHB9ClxiZWdpbnthcnJheX17Y30KYHIgc2V0LnNlZWQoMSk7ZW1vOjpqaSgiYmVhdXR5IilgXFxcXApgciBzZXQuc2VlZCgxKTtlbW86OmppKCJjb3AiKWBcXApcXApgciBzZXQuc2VlZCgxKTtlbW86OmppKCJibG9uZGUiKWBcXApcZW5ke2FycmF5fQpcXQoKVGhlIGRhdGEgbWF0cml4ICAkXG1hdGhiZntYfSQgY2FuIGJlIGRlY29tcG9zZWQgd2l0aCB0aGUgU1ZEIGludG8gMyBtYXRyaWNlczoKClxbClxtYXRoYmZ7WH09XG1hdGhiZntVfV97blx0aW1lcyBufVxib2xkc3ltYm9se1xEZWx0YX1fe25cdGltZXMgcH1cbWF0aGJme1Z9XlRfe3AgXHRpbWVzIHB9ClxdCgotIGFuIG9ydGhvbm9ybWFsIG1hdHJpeCAkXG1hdGhiZntVfV97blx0aW1lcyBufSQgd2l0aCBsZWZ0IHNpbmd1bGFyIHZlY3RvcnM6ICRcbWF0aGJme3V9X2peVCBcbWF0aGJme3V9X2s9MSQgaWYgJGs9aiQgYW5kICRcbWF0aGJme3V9X2peVCBcbWF0aGJme3V9X2s9MCQgaWYgJGpcbmVxIGskLCBpLmUuClxbIFxtYXRoYmZ7VX1eVFxtYXRoYmZ7VX09XG1hdGhiZntJfVxdCgotIGEgbWF0cml4ICRcYm9sZHN5bWJvbHtcRGVsdGF9X3tuXHRpbWVzIHB9JCB3aXRoIG9ubHkgc2luZ3VsYXIgdmFsdWVzOiB0aGUgc2luZ3VsYXIgdmFsdWVzICRcZGVsdGFfaSQgYXJlIHRoZSBvbmx5IG5vbi16ZXJvIGVsZW1lbnRzIG9mIHRoZSBtYXRyaXggYW5kIGFyZSBvbiB0aGUgZGlhZ29uYWwgZWxlbWVudCAkW1xib2xkc3ltYm9se1xEZWx0YX1dX3tpaX0kLiBUaGV5IGFyZSBhbHNvIG9yZ2FuaXNlZCBzbyB0aGF0ICRcZGVsdGFfMSA+IFxkZWx0YV8yID4gXGxkb3RzID4gXGRlbHRhX3IkLgoKLSBhbiBvcnRob25vcm1hbCBtYXRyaXggJFxtYXRoYmZ7Vn1fe3BcdGltZXMgcH0kIHdpdGggcmlnaHQgc2luZ3VsYXIgdmVjdG9yczogJFxtYXRoYmZ7dn1fal5UIFxtYXRoYmZ7dn1faz0xJCBpZiAkaz1qJCBhbmQgJFxtYXRoYmZ7dn1fal5UIFxtYXRoYmZ7dn1faz0wJCBpZiAkalxuZXEgayQgb3RoZXJ3aXNlLCBpLmUuClxbIFxtYXRoYmZ7Vn1eVFxtYXRoYmZ7Vn09XG1hdGhiZntJfVxdCgoKTm90ZSwgdGhhdCB0aGVyZSBhcmUgb25seSAkciQgbm9uLXplcm8gc2luZ3VsYXIgdmFsdWVzLCB3aXRoICRyJCB0aGUgcmFuayBvZiBtYXRyaXggJFgkOiAkciBcbGVxIFx0ZXh0e21pbn0obixwKSQuIFNvIHdlIGhhdmUgJGs9MSBcbGRvdHMgciQgbm9uLXplcm8gc2luZ3VsYXIgdmFsdWVzLiBIZW5jZSwgd2UgY2FuIGFsc28gcmV3cml0ZSB0aGUgYXBwcm94aW1hdGlvbiBieSByZXN0cmljdGluZyB1cyB0byB0aGUgcmFuayBvZiBtYXRyaXggJFxtYXRoYmZ7WH0kLiBJbmRlZWQsIHRoZSBuIHRpbWVzIHAgbWF0cml4ICRcYm9sZHN5bWJvbFxEZWx0YSQgb25seSBjb250YWlucyAkciQgbm9uLXplcm8gZGlhZ29uYWwgZWxlbWVudHMhCgotIFNvClxbClxtYXRoYmZ7WH09XG1hdGhiZntVfV97blx0aW1lcyByfVxib2xkc3ltYm9se1xEZWx0YX1fe3JcdGltZXMgcn1cbWF0aGJme1Z9XlRfe3AgXHRpbWVzIHJ9ClxdCgoKXFsKXGxlZnRbXGJlZ2lue2FycmF5fXtjY2N9Ci0mXG1hdGhiZnt4fV97MX1eVCAmLSBcXApcdmRvdHMmXHZkb3RzJlx2ZG90c1xcCi0mXG1hdGhiZnt4fV97aX1eVCAmLSBcXApcdmRvdHMmXHZkb3RzJlx2ZG90c1xcCi0mXG1hdGhiZnt4fV97bn1eVCAmLSBcXApcZW5ke2FycmF5fVxyaWdodF1fe24gXHRpbWVzIHB9Cj0KXGxlZnRbXGJlZ2lue2FycmF5fXtjY2N9ClxtaWQmJlxtaWRcXApcbWF0aGJme3V9XzEmXGxkb3RzJlxtYXRoYmZ7dX1fclxcClxtaWQmJlxtaWQKXGVuZHthcnJheX1ccmlnaHRdX3tuIFx0aW1lcyByfQpcbGVmdFtcYmVnaW57YXJyYXl9e2NjY30KXGRlbHRhXzFcXAomXGRkb3RzJlxcCiYmXGRlbHRhX3JcXApcZW5ke2FycmF5fVxyaWdodF1fe3IgXHRpbWVzIHJ9ClxsZWZ0W1xiZWdpbnthcnJheX17Y2NjfQpcbWlkJiZcbWlkXFwKXG1hdGhiZnt2fV8xJlxsZG90cyZcbWF0aGJme3Z9X3JcXApcbWlkJiZcbWlkXFwKXGVuZHthcnJheX0KXHJpZ2h0XV5UX3twIFx0aW1lcyByfQpcXQoKQWxzbyBub3RlIHRoYXQKXFsKXG1hdGhiZntWfV5UPVxsZWZ0W1xiZWdpbnthcnJheX17Y2NjfQotJlxtYXRoYmZ7dn1fezF9XlQgJi0gXFwKXHZkb3RzJlx2ZG90cyZcdmRvdHNcXAotJlxtYXRoYmZ7dn1fe3J9XlQgJi0gXFwKXGVuZHthcnJheX1ccmlnaHRdX3tyIFx0aW1lcyBwfQpcXQoKCi0gRm9yIGhpZ2ggZGltZW5zaW9uYWwgZGF0YSAkcD4+Pm4kICRccmlnaHRhcnJvdyQgJFx0ZXh0e21heH0ocik9biQgYW5kCgotIGVxdWl2YWxlbnRseSBmb3IgbXVsdGl2YXJpYXRlIGRhdGEgd2l0aCAkbj5wJCAkXHJpZ2h0YXJyb3ckICRcdGV4dHttYXh9KHIpPXAkCgoKV2UgY2FuIGFsc28gcmV3cml0ZSB0aGUgZGVjb21wb3NpdGlvbiB1c2luZyB0aGUgcHJvcGVydGllcyBvZiBtYXRyaXggbXVsdGlwbGljYXRpb24KClxiZWdpbntlcW5hcnJheX0KXG1hdGhiZntYfSAmPSYgXGRlbHRhXzFcbGVmdFsKXGJlZ2lue2FycmF5fXtjfQpcbWlkXFwKXG1hdGhiZnt1fV8xXFwKXG1pZApcZW5ke2FycmF5fQpccmlnaHRdClxiZWdpbnthcnJheX17Y30KXGxlZnRbClxiZWdpbnthcnJheX17Y2NjfQotJgpcbWF0aGJme3Z9XzFeVCYKLQpcZW5ke2FycmF5fQpccmlnaHRdXFxccXVhZFxcXHF1YWQKXGVuZHthcnJheX0KKyBcbGRvdHMgKwpcZGVsdGFfclxsZWZ0WwpcYmVnaW57YXJyYXl9e2N9ClxtaWRcXApcbWF0aGJme3V9X3JcXApcbWlkClxlbmR7YXJyYXl9ClxyaWdodF0KXGJlZ2lue2FycmF5fXtjfQpcbGVmdFsKXGJlZ2lue2FycmF5fXtjY2N9Ci0mClxtYXRoYmZ7dn1fcl5UJgotClxlbmR7YXJyYXl9ClxyaWdodF1cXFxxdWFkXFxccXVhZApcZW5ke2FycmF5fVxcClxtYXRoYmZ7WH0gJj0mIFxzdW1fe2s9MX1eciBcZGVsdGFfa1xtYXRoYmZ7dX1fa1xtYXRoYmZ7dn1fa15UClxlbmR7ZXFuYXJyYXl9CgotIEJlY2F1c2UgYm90aCAkXG1hdGhiZntVfSQgYW5kICRcbWF0aGJme1Z9JCBhcmUgb3J0aG9ub3JtYWwgYWxsIHRoZWlyICRyJCB2ZWN0b3JzIGFyZSBoYXZpbmcgdW5pdCBsZW5ndGggYW5kIHRoZXkgYXJlIHRodXMgcmVzaGFwZWQgYnkgdGhlIHNpbmd1bGFyIHZhbHVlcy4KCi0gSGVuY2UsIHRoZSBzaW5ndWxhciB2YWx1ZXMgZGV0ZXJtaW5lIHRoZSBpbXBvcnRhbmNlIG9mIHRoZSByYW5rIG9uZSBtYXRyaWNlcyAkXGRlbHRhX2tcbWF0aGJme3V9X2tcbWF0aGJme3Z9X2teVCQgaW4gdGhlIHJlY29uc3RydWN0aW9uIG9mIHRoZSBtYXRyaXggJFxtYXRoYmZ7WH0kIGFuZCB0aGV5IGFyZSBvcmRlcmVkIHNvIHRoYXQgJFxkZWx0YV8xID4gXGxkb3RzID4gXGRlbHRhX3IkLgoKTm90ZSwgdGhhdCBmb3Igc3ltbWV0cmljIG1hdHJpY2VzICRcbWF0aGJme1h9JCAkXGxvbmdyaWdodGFycm93JCAkXG1hdGhiZntVfSA9IFxtYXRoYmZ7Vn0kLgoKIyMgSW50ZXJwcmV0YXRpb24gb2Ygc2luZ3VsYXIgdmVjdG9yczogZmFjZSBleGFtcGxlCgojIyMgQ29udmVydCBpbWFnZXMgdG8gdmVjdG9ycwoxLiBDb252ZXJ0IGltYWdlcyB0byB2ZWN0b3JzIGFuZCBzdG9yZSB0aGVtIGFzIGEgbWF0cml4CiAgICAtIFdlIHVzZSBhbiBgc2FwcGx5YCBsb29wIHRvIGxvb3Agb3ZlciBhbGwgZmFjZXMKICAgIC0gV2UgZXh0cmFjdCB0aGUgZ3JleSBpbnRlbnNpdGllcyBmcm9tIHRoZSBwaWN0dXJlcwogICAgLSBXZSBjb252ZXJ0IHRoZSBtYXRyaXggaW4gYSBsb25nIHNraW5ueSB2ZWN0b3IgKGBjYCkKICAgIC0gV2UgdHJhbnNwb3NlIHRoZSByZXN1bHRpbmcgbWF0cml4IGZyb20gc2FwcGx5CmBgYHtyfQphbGxGYWNlc014IDwtIHNhcHBseShmYWNlc0xpc3QsCiAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uKHgpCiAgICAgICAgICAgICAgICAgICAgICAgIGdldENoYW5uZWxzKHgpICU+JSBjCiAgICAgICAgICAgICAgICAgICAgICkgJT4lIHQKZGltKGFsbEZhY2VzTXgpCmBgYAoKU2F2ZSBtZW1vcnkgYnkgcmVtb3ZpbmcgZmFjZXNMaXN0IG9iamVjdApgYGAKcm0oZmFjZXNMaXN0KQpnYygpCmBgYAoKSGVuY2Ugd2Ugb2J0YWluIGEgbWF0cml4IGZvciBuID0gYHIgbnJvdyhhbGxGYWNlc014KWAgaW1hZ2VzIHdpdGggcCA9IGByIG5jb2woYWxsRmFjZXNNeClgIGludGVuc2l0aWVzIGZvciBlYWNoIHBpeGVsIG9mIGFuIGltYWdlLgoKQmVmb3JlIHdlIGRvIHRoZSBzdmQgd2UgdHlwaWNhbGx5IGNlbnRlciB0aGUgZGF0YSBieSBzdWJzdHJhY3RpbmcgdGhlIGF2ZXJhZ2Ugb2YgdGhlIGNvbHVtbnMsIGkuZS4gdGhlIGF2ZXJhZ2UgZmFjZS4KCldlIHdpbGwgb25seSB3b3JrIHdpdGggdGhlIGZpcnN0IDM2IHBlb3BsZTogJG4gPSAzNiBcdGltZXMgNjQgPSBgciAzNio2NGAkIHBpY3R1cmVzLgoKYGBge3J9CmFsbEZhY2VzQ2VudGVyZWRNeCA8LSBhbGxGYWNlc014WzE6KDM2KjY0KSxdCm1lYW5GYWNlIDwtIGNvbE1lYW5zKGFsbEZhY2VzQ2VudGVyZWRNeCkKCmFsbEZhY2VzTXhDZW50ZXJlZCA8LSBhbGxGYWNlc0NlbnRlcmVkTXggLQogIG1hdHJpeCgxLCBucm93PW5yb3coYWxsRmFjZXNDZW50ZXJlZE14KSwgbmNvbD0xKSAlKiUgbWF0cml4KG1lYW5GYWNlLG5yb3c9MSkKYGBgCgojIyMgVmlzdWFsaXNhdGlvbiBvZiBtZWFuIGltYWdlCmBgYHtyfQpwbG90RmFjZVZlY3RvciA8LSBmdW5jdGlvbihmYWNlVmVjdG9yLG5yb3c9MTkyLG5jb2w9MTY4KSB7CiAgbWF0cml4KGZhY2VWZWN0b3IsbnJvdz1ucm93LG5jb2w9bmNvbCkgJT4lCiAgZ2dpbWFnZSgpICAKfQoKbWVhbkZhY2UgJT4lCiAgcGxvdEZhY2VWZWN0b3IKYGBgCgojIyMgU1ZECgojIyMjIFBlcmZvcm0gU1ZEIGluIFIKCjEuIFdlIGFkb3B0IHN2ZCBvbiB0aGUgY2VudGVyZWQgbWF0cml4CjIuIFdlIGNhY2hlIHRoZSByZXN1bHQgYmVjYXVzZSB0aGUgY2FsY3VsYXRpb24gdGFrZXMgMTAgbWludXRlcy4KCmBgYHtyIHJ1bi1TVkQsIGNhY2hlPVRSVUV9CmZhY2VTdmQgPC0gc3ZkKGFsbEZhY2VzTXhDZW50ZXJlZCkKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0KIyMgUnVuIHRoaXMgY29kZSBtYW51YWxseSB0byBzdG9yZSB0aGUgU1ZEIGZvciBsYXRlciByZS11c2UKc2F2ZVJEUyhmYWNlU3ZkLCBmaWxlID0gImZhY2VTdmQucmRzIikKYGBgCgpgYGB7ciwgZXZhbD1GQUxTRX0KIyMgUnVuIHRoaXMgY29kZSBtYW51YWxseSB0byByZWxvYWQgdGhlIFNWRCByZXN1bHQKZmFjZVN2ZCA8LSByZWFkUkRTKCJmYWNlU3ZkLnJkcyIpCmBgYAoKCgojIyMjIFNWRAoKRGltZW5zaW9ucyBvZiAkXG1hdGhiZntVfSQsICRcbWF0aGJme1Z9JD8KCmBgYHtyfQpuIDwtIG5yb3coYWxsRmFjZXNDZW50ZXJlZE14KQpwIDwtIG5jb2woYWxsRmFjZXNDZW50ZXJlZE14KQpkaW0oZmFjZVN2ZCR1KQpkaW0oZmFjZVN2ZCR2KQpgYGAKCkluZGVlZCwgZm9yIHRoZSBmYWNlIGV4YW1wbGUgJG48cCQgc28gJHI9biQKCkNoZWNrIG9ydGhvZ29uYWxpdHk/CgpXZSBkbyBub3QgZG8gaXQgZm9yIGFsbCB2ZWN0b3JzIGJlY2F1c2UgaXQgdGFrZXMgdG9vIGxvbmcuCkZpcnN0IGxlZnQgZWlnZW4gdmVjdG9yIGFuZCBzZWNvbmQgbGVmdCBlaWdlbnZlY3Rvci4KSGFwcGVucyBpbiAkXG1hdGhiZntVfV5UXG1hdGhiZntVfSQKYGBge3J9CnQoZmFjZVN2ZCR1WywxXSklKiVmYWNlU3ZkJHVbLDFdCnQoZmFjZVN2ZCR1WywxXSklKiVmYWNlU3ZkJHVbLDJdCnQoZmFjZVN2ZCR1WywyXSklKiVmYWNlU3ZkJHVbLDJdCmBgYApTbyB3ZSBzZWUgdGhhdCB0aGUgbGVmdCBlaWdlbnZlY3RvcnMgYXJlIG9ydGhvbm9ybWFsLgoKV2UgY2hlY2sgaWYgaXQgYWxzbyBob2xkcyBmb3IgdGhlIHJvd3MgaS5lLiAkXG1hdGhiZntVfVxtYXRoYmZ7VX1eVCQKYGBge3J9CnQoZmFjZVN2ZCR1WzEsXSklKiVmYWNlU3ZkJHVbMSxdCnQoZmFjZVN2ZCR1WzIsXSklKiVmYWNlU3ZkJHVbMSxdCnQoZmFjZVN2ZCR1WzIsXSklKiVmYWNlU3ZkJHVbMixdCmBgYApXZSBhbHNvIHNlZSB0aGF0IHRoZSByb3dzIG9mICRcbWF0aGJme1V9JCBhcmUgb3J0aG9ub3JtYWwuCgpgYGB7cn0KdChmYWNlU3ZkJHZbLDFdKSUqJWZhY2VTdmQkdlssMV0KdChmYWNlU3ZkJHZbLDFdKSUqJWZhY2VTdmQkdlssMl0KdChmYWNlU3ZkJHZbLDJdKSUqJWZhY2VTdmQkdlssMl0KYGBgClNvIHdlIHNlZSB0aGF0IHRoZSByaWdodCBlaWdlbnZlY3RvcnMgYXJlIG9ydGhvbm9ybWFsLgoKYGBge3J9CnQoZmFjZVN2ZCR2WzEsXSklKiVmYWNlU3ZkJHZbMSxdCnQoZmFjZVN2ZCR2WzEsXSklKiVmYWNlU3ZkJHZbMixdCnQoZmFjZVN2ZCR2WzIsXSklKiVmYWNlU3ZkJHZbMixdCmBgYAoKVGhpcywgaG93ZXZlciBkb2VzIG5vdCBob2xkIGZvciB0aGUgcm93cyBvZiAkXG1hdGhiZntWfSQuClRoaXMgaXMgYmVjYXVzZSB0aGUgbWF0cml4ICRcbWF0aGJme1Z9JCBubyBsb25nZXIgaXMgYSBzcXVhcmUgbWF0cml4ISAkcj1uJCBhbmQgJHI8cCQhCgojIyMjIFZpc3VhbGl6ZSByaWdodCBlaWdlbnZlY3RvcnMgJFxtYXRoYmZ7Vn0kCmBgYHtyfQpncmlkLmFycmFuZ2UoCiBncm9icz1hcHBseSgKICAgZmFjZVN2ZCR2WywxOjM2XSwKICAgMiwKICAgcGxvdEZhY2VWZWN0b3IKICAgKQogKQpgYGAKCi0gSGVuY2UsIHRoZSByaWdodCBzaW5ndWxhciB2ZWN0b3JzIChpbiAkXG1hdGhiZntWfSQgb2YgJFxtYXRoYmZ7WH09XG1hdGhiZntVfVxib2xkc3ltYm9se1xEZWx0YX1cbWF0aGJme1Z9JCkgYXJlIGFsc28gZmFjZXMgYW5kIHdlIGNhbiB0aHVzIHJlY29uc3RydWN0IHRoZSBvcmlnaW5hbCBmYWNlcyBieSBsaW5lYXIgY29tYmluYXRpb25zIG9mIHRoZSBlaWdlbiBmYWNlcy4KCi0gVGhlIGZpcnN0IGVpZ2VuIGZhY2VzIGFyZSBtb3N0IGltcG9ydGFudCB0byBjYXB0dXJlIG92ZXJhbGwgcGF0dGVybnMgaW4gdGhlIG1hdHJpeC4KCi0gSGVyZSBpdCBhcmUgbWFpbmx5IGNoYXJhY3RlcmlzdGljcyBhbmQgc2hhZG93cyB0aGF0IGFyZSBpbXBvcnRhbnQgZm9yIGFsbCBmYWNlcy4KCi0gRnJvbSBlaWdlbiBmYWNlIDUgb253YXJkcyB3ZSBzdGFydCB0byBzZWUgc3BlY2lmaWMgZmVhdHVyZXMuCgotIEluIHRoaXMgY2FzZTogJG4gPCBwJCwgc28gJHIgPSBuJC4KClxbClxtYXRoYmZ7WH1fe25cdGltZXMgcH09XG1hdGhiZntVfV97biBcdGltZXMgbn1cYm9sZHN5bWJvbHtcRGVsdGF9X3tuXHRpbWVzIG59XG1hdGhiZntWfV97cFx0aW1lcyBufV5UClxdCgpcWwpcYmVnaW57YXJyYXl9e2NjY2NjfQpcbGVmdFtcYmVnaW57YXJyYXl9e2NjY30KLSZcbWF0aGJme3h9X3sxfV5UICYtIFxcClx2ZG90cyZcdmRvdHMmXHZkb3RzXFwKLSZcbWF0aGJme3h9X3tpfV5UICYtIFxcClx2ZG90cyZcdmRvdHMmXHZkb3RzXFwKLSZcbWF0aGJme3h9X3tufV5UICYtIFxcClxlbmR7YXJyYXl9XHJpZ2h0XV97biBcdGltZXMgcH0KXGJlZ2lue2FycmF5fXtjfQpgciBzZXQuc2VlZCgxKTtlbW86OmppKCJiZWF1dHkiKWBcXFxcCmByIHNldC5zZWVkKDEpO2Vtbzo6amkoImNvcCIpYFxcClxcCmByIHNldC5zZWVkKDEpO2Vtbzo6amkoImJsb25kZSIpYFxcClxlbmR7YXJyYXl9CiY9JgpcYmVnaW57YXJyYXl9e2N9ClxxdWFkXFwKXGxlZnRbXGJlZ2lue2FycmF5fXtjY2N9ClxtaWQmJlxtaWRcXApcbWF0aGJme3V9XzEmXGxkb3RzJlxtYXRoYmZ7dX1fblxcClxtaWQmJlxtaWQKXGVuZHthcnJheX1ccmlnaHRdX3tuIFx0aW1lcyBufVxcClxxdWFkIFxcClxlbmR7YXJyYXl9ClxiZWdpbnthcnJheX17Y30KXHF1YWRcXApcbGVmdFtcYmVnaW57YXJyYXl9e2NjY30KXGRlbHRhXzFcXAomXGRkb3RzJlxcCiYmXGRlbHRhX25cXApcZW5ke2FycmF5fVxyaWdodF1fe24gXHRpbWVzIG59XFwKXHF1YWRcXApcZW5ke2FycmF5fQpcYmVnaW57YXJyYXl9e2N9ClxxdWFkClxsZWZ0W1xiZWdpbnthcnJheX17Y2NjfQpcbWlkJiZcbWlkXFwKXG1hdGhiZnt2fV8xJlxsZG90cyZcbWF0aGJme3Z9X25cXApcbWlkJiZcbWlkXFwKXGVuZHthcnJheX0KXHJpZ2h0XV5UX3twIFx0aW1lcyBufVxcClxiZWdpbnthcnJheX17Y2NjfQpgciBzZXQuc2VlZCgxKTtlbW86OmppKCJmZWFyIilgJlxxdWFkJmByIHNldC5zZWVkKDEpO2Vtbzo6amkoIm1laCIpYApcZW5ke2FycmF5fQpcZW5ke2FycmF5fQpcZW5ke2FycmF5fQpcXQoKLSBPciB1cG9uIHRyYW5zcG9zaW5nIHRoZSBtYXRyaXggJFxtYXRoYmZ7Vn0kCgpcWwpcYmVnaW57YXJyYXl9e2NjY2NjfQpcbGVmdFtcYmVnaW57YXJyYXl9e2NjY30KLSZcbWF0aGJme3h9X3sxfV5UICYtIFxcClx2ZG90cyZcdmRvdHMmXHZkb3RzXFwKLSZcbWF0aGJme3h9X3tpfV5UICYtIFxcClx2ZG90cyZcdmRvdHMmXHZkb3RzXFwKLSZcbWF0aGJme3h9X3tufV5UICYtIFxcClxlbmR7YXJyYXl9XHJpZ2h0XV97biBcdGltZXMgcH0KXGJlZ2lue2FycmF5fXtjfQpgciBzZXQuc2VlZCgxKTtlbW86OmppKCJiZWF1dHkiKWBcXFxcCmByIHNldC5zZWVkKDEpO2Vtbzo6amkoImNvcCIpYFxcClxcCmByIHNldC5zZWVkKDEpO2Vtbzo6amkoImJsb25kZSIpYFxcClxlbmR7YXJyYXl9CiY9JgpcbGVmdFtcYmVnaW57YXJyYXl9e2NjY30KXG1pZCYmXG1pZFxcClxtYXRoYmZ7dX1fMSZcbGRvdHMmXG1hdGhiZnt1fV9uXFwKXG1pZCYmXG1pZApcZW5ke2FycmF5fVxyaWdodF1fe24gXHRpbWVzIG59ClxsZWZ0W1xiZWdpbnthcnJheX17Y2NjfQpcZGVsdGFfMVxcCiZcZGRvdHMmXFwKJiZcZGVsdGFfclxcClxlbmR7YXJyYXl9XHJpZ2h0XV97biBcdGltZXMgbn0KXGxlZnRbXGJlZ2lue2FycmF5fXtjY2N9Ci0mXG1hdGhiZnt2fV97MX1eVCAmLSBcXApcdmRvdHMmXHZkb3RzJlx2ZG90c1xcCi0mXG1hdGhiZnt2fV97cH1eVCAmLSBcXApcZW5ke2FycmF5fVxyaWdodF1fe24gXHRpbWVzIHB9ClxiZWdpbnthcnJheX17Y30KYHIgc2V0LnNlZWQoMSk7ZW1vOjpqaSgiZmVhciIpYFxcClxcCmByIHNldC5zZWVkKDEpO2Vtbzo6amkoIm1laCIpYApcZW5ke2FycmF5fQpcZW5ke2FycmF5fQpcXQoKIyMjIyBSZWNvbnN0cnVjdGlvbiBvZiBmYWNlcyB2aWEgbGluZWFyIGNvbWJpbmF0aW9uIG9mIGVpZ2VuIGZhY2VzLgoKSW4gbGVmdCBzaW5ndWxhciB2ZWN0b3JzICR1X3tpan0kIHdlIHF1YW50aWZ5IHRoZSBjb250cmlidXRpb24gb2YgdGhlICRqXlx0ZXh0e3RofSQgZWlnZW5mYWNlIGluIHRoZSByZWNvbnN0cnVjdGlvbiBvZiBmYWNlICRpJCBhbmQgd2UgcmVzY2FsZSB0aGUgaW1wb3J0YW5jZSBvZiBlYWNoIGVpZ2VuIGZhY2UgYnkgaXRzIGNvcnJlc3BvbmRpbmcgZWlnZW4gdmFsdWUgJFxkZWx0YV9qJC4KClxbClxsZWZ0W1xiZWdpbnthcnJheX17Y2NjfQotJlxtYXRoYmZ7eH1fezF9XlQgJi0gXFwKXHZkb3RzJlx2ZG90cyZcdmRvdHNcXAotJlxtYXRoYmZ7eH1fe2l9XlQgJi0gXFwKXHZkb3RzJlx2ZG90cyZcdmRvdHNcXAotJlxtYXRoYmZ7eH1fe259XlQgJi0gXFwKXGVuZHthcnJheX1ccmlnaHRdX3tuIFx0aW1lcyBwfQpcYmVnaW57YXJyYXl9e2N9CmByIHNldC5zZWVkKDEpO2Vtbzo6amkoImJlYXV0eSIpYFxcXFwKYHIgc2V0LnNlZWQoMSk7ZW1vOjpqaSgiY29wIilgXFwKXFwKYHIgc2V0LnNlZWQoMSk7ZW1vOjpqaSgiYmxvbmRlIilgXFwKXGVuZHthcnJheX0gPQpcZGVsdGFfMVxsZWZ0WwpcYmVnaW57YXJyYXl9e2N9ClxtaWRcXApcbWF0aGJme3V9XzFcXApcbWlkClxlbmR7YXJyYXl9ClxyaWdodF0KXGJlZ2lue2FycmF5fXtjfQpcbGVmdFsKXGJlZ2lue2FycmF5fXtjY2N9Ci0mClxtYXRoYmZ7dn1fMV5UJgotClxlbmR7YXJyYXl9ClxyaWdodF1cXFxxdWFkXFxccXVhZApcZW5ke2FycmF5fQpcYmVnaW57YXJyYXl9e2N9CmByIHNldC5zZWVkKDEpO2Vtbzo6amkoImZlYXIiKWAKXFxccXVhZFxcXHF1YWQKXGVuZHthcnJheX0KKyBcbGRvdHMgKwpcZGVsdGFfclxsZWZ0WwpcYmVnaW57YXJyYXl9e2N9ClxtaWRcXApcbWF0aGJme3V9X3JcXApcbWlkClxlbmR7YXJyYXl9ClxyaWdodF0KXGJlZ2lue2FycmF5fXtjfQpcbGVmdFsKXGJlZ2lue2FycmF5fXtjY2N9Ci0mClxtYXRoYmZ7dn1fcl5UJgotClxlbmR7YXJyYXl9ClxyaWdodF1cXFxxdWFkXFxccXVhZApcZW5ke2FycmF5fQpcYmVnaW57YXJyYXl9e2N9CmByIHNldC5zZWVkKDEpO2Vtbzo6amkoIm1laCIpYApcXFxxdWFkXFxccXVhZApcZW5ke2FycmF5fQpcXQoKSWYgd2UgdHJ1bmNhdGUgdGhlIGVpZ2VuIGZhY2VzIHNheSBhdCAkazxyJCB3ZSBjYW4gYXBwcm94aW1hdGUgZmFjZXMgdXNpbmcgYSBsaW1pdGVkIG51bWJlciBvZiBlaWdlbiBmYWNlcyEKCmBgYHtyIGZpZy5jYXA9J2FwcHJveGltYXRpb24gd2l0aCAyNSAodG9wIGxlZnQpLCAgMTAwICh0b3AgcmlnaHQpIGFuZCA1MDAgKGJvdHRvbSBsZWZ0KSBlaWdlbmZhY2VzIGFuZCBvcmlnaW5hbCBmYWNlIChib3R0b20gcmlnaHQsIG9yIHdpdGggYWxsIGVpZ2VuZmFjZXMpJ30KYXBwcm94aW1hdGVGYWNlIDwtIGZ1bmN0aW9uKG1lYW5GYWNlLGZhY2VTdmQsayl7CiAgcmVjb25zdHJ1Y3QgPC0gKG1lYW5GYWNlICsgZmFjZVN2ZCR1WzEsMTprXSAlKiUKICAgIGRpYWcoZmFjZVN2ZCRkWzE6a10pICUqJQogIHQoZmFjZVN2ZCR2WywxOmtdKSAlPiUKICBjKQp9CgphcHByb3hIbHAgPC0gc2FwcGx5KAogICAgICBjKDI1LDEwMCw1MDApLAogICAgICBhcHByb3hpbWF0ZUZhY2UsCiAgICAgIG1lYW5GYWNlPW1lYW5GYWNlLAogICAgICBmYWNlU3ZkPWZhY2VTdmQpCgpncmlkLmFycmFuZ2UoCiBncm9icz1hcHBseSgKICAgY2JpbmQoCiAgICAgYXBwcm94SGxwLAogICAgIGFsbEZhY2VzTXhDZW50ZXJlZFsxLF0rbWVhbkZhY2UKICAgKSwKICAgMiwKICAgcGxvdEZhY2VWZWN0b3IKICAgKQogKQoKYGBgCgojIFNWRCBhcyBhIE1hdHJpeCBBcHByb3hpbWF0aW9uIE1ldGhvZAoKLSBXZSBoYXZlIHNlZW4gdGhhdCB3ZSBjYW4gdXNlIHRoZSB0cnVuY3RlZCBTVkQgdG8gYXBwcm94aW1hdGUgbWF0cml4ICRcbWF0aGJme1h9JCBieSAkXHRpbGRle1xtYXRoYmZ7WH19JCwgd2l0aCAkazxyJCBhbmQKXFsKXHRpbGRle1xtYXRoYmZ7WH19PVxtYXRoYmZ7VX1fe25cdGltZXMga31cYm9sZHN5bWJvbHtcRGVsdGF9X3trXHRpbWVzIGt9XG1hdGhiZntWfV97cCBcdGltZXMga31eVApcXQotIEl0IGNhbiBiZSBzaG93biB0aGF0ICoqU1ZEOiBvcHRpbWFsIGFwcHJveGltYXRpb24qKgoKICAtIExldCAkXG1hdGhiZntYfSQgYmUgYW4gJG5cdGltZXMgcCQgbWF0cml4IG9mIHJhbmsgJHJcbGVxIFxtaW4obixwKSQsIGFuZCBsZXQgJFxtYXRoYmZ7QX0kIGRlbm90ZSBhbiAkbiBcdGltZXMgcCQgbWF0cml4IG9mIHJhbmsgJGtcbGVxIHIkLCB3aXRoIGVsZW1lbnRzIGRlbm90ZWQgYnkgJGFfe2lqfSQuCgogIC0gVGhlIG1hdHJpeCAkXG1hdGhiZntBfSQgb2YgcmFuayAka1xsZXEgciQgdGhhdCBtaW5pbWlzZXMgdGhlIEZyb2Jlbml1cyBub3JtCiAgXFsKICAgIFx2ZXJ0XHZlcnRcbWF0aGJme1h9LVxtYXRoYmZ7QX1cdmVydFx2ZXJ0XjJfXHRleHR7ZnJ9PVxzdW1fe2k9MX1eblxzdW1fe2o9MX1ecCAoeF97aWp9LWFfe2lqfSleMgogIFxdCiAgaXMgZ2l2ZW4gYnkgdGhlIHRydW5jYXRlZCBTVkQKICBcWwogICAgIFxtYXRoYmZ7WH1fayA9IFxzdW1fe2o9MX1eayBcZGVsdGFfaiBcbWF0aGJme3V9X2pcbWF0aGJme3Z9X2peVC4KICBcXQoKICAtIFRoZSB0cnVuY2F0ZWQgU1ZEIGhhcyAkayA8IHIkIHRlcm1zLiBIZW5jZSwgZ2VuZXJhbGx5ICRcbWF0aGJme1h9X2skIGRvZXMgbm90IGNvaW5jaWRlIHdpdGggJFxtYXRoYmZ7WH0kLiBJdCBpcyBjb25zaWRlcmVkIGFzIGFuIGFwcHJveGltYXRpb24uCgogIC0gTm90ZSwgdGhhdCB0aGUgdHJ1bmNhdGVkIFNWRCB0aHVzIGFwcHJveGltYXRlcyB0aGUgbWF0cml4IGJ5IG1pbmltaXNpbmcgYSBraW5kIG9mIHN1bSBvZiBsZWFzdCBzcXVhcmVkIGVycm9ycyBiZXR3ZWVuIHRoZSBlbGVtZW50cyBvZiBtYXRyaXggJFxtYXRoYmZ7WH0kIGFuZCAkXG1hdGhiZntBfSQgYW5kIHRoYXQKCiAgLSB0aGUgdHJ1bmNhdGVkIFNWRCAkXG1hdGhiZntYfV9rJCBpcyB0aGUgYmVzdCByYW5rLWsgYXBwcm94aW1hdGlvbiBvZiAkXG1hdGhiZntYfSQgaW4gdGVybXMgb2YgdGhpcwpGcm9iZW5pdXMgbm9ybS4KCiAgLSBBbHNvLCBub3RlIHRoYXQgdXBvbiB0cnVuY2F0aW9uCiAgXFtcbWF0aGJme1Z9XlRfe3BcdGltZXMga30gXG1hdGhiZntWfV97cFx0aW1lcyBrfSA9IFxtYXRoYmZ7SX1fe2tcdGltZXMga31cXQogIFxbXG1hdGhiZntVfV5UX3tuXHRpbWVzIGt9IFxtYXRoYmZ7VX1fe25cdGltZXMga30gPSBcbWF0aGJme0l9X3trXHRpbWVzIGt9XF0KCiAgLSBCdXQsIHRoYXQKXFtcbWF0aGJme1Z9X3twXHRpbWVzIGt9IFxtYXRoYmZ7Vn1fe3BcdGltZXMga31eVCBcbmVxIFxtYXRoYmZ7SX1fe3BcdGltZXMgcH0hISFcXQpcW1xtYXRoYmZ7VX1fe25cdGltZXMga30gXG1hdGhiZntVfV97blx0aW1lcyBrfV5UIFxuZXEgXG1hdGhiZntJfV97blx0aW1lcyBufSEhIVxdCgotLS0KCgpTb21lICoqaW5mb3JtYWwgc3RhdGVtZW50KiogYWJvdXQgdGhlIHRydW5jYXRlZCBTVkQKICBcWwogICAgIFxtYXRoYmZ7WH1fayA9IFxzdW1fe2o9MX1eayBcZGVsdGFfaiBcbWF0aGJme3V9X2pcbWF0aGJme3Z9X2peVC4KICBcXQoKIC0gSXQgY2FuIGJlIGNvbnNpZGVyZWQgYXMgYSB3ZWlnaHRlZCBzdW0gb2YgbWF0cmljZXMgJFxtYXRoYmZ7dX1falxtYXRoYmZ7dn1fal5UJCwgd2l0aCB3ZWlnaHRzICRcZGVsdGFfaiQuCgoKIC0gVGhlIHRlcm1zIGFyZSBvcmRlcmVkIHdpdGggZGVjcmVhc2luZyB3ZWlnaHRzICRcZGVsdGFfMVxnZXEgXGRlbHRhXzIgXGdlcSBcY2RvdHMgXGdlcSBcZGVsdGFfayA+MCQuCgoKIC0gVGhlIG1hdHJpY2VzICRcbWF0aGJme3V9X2pcbWF0aGJme3Z9X2peVCQgYXJlIG9mIGVxdWFsICJtYWduaXR1ZGUiIChjb25zdHJ1Y3RlZCBmcm9tIG5vcm1hbGlzZWQgdmVjdG9ycykuCgogLSBUcnVuY2F0aW9uIGF0ICRrJCByZXN1bHRzIGluICRrJCAkXGRlbHRhX2okJ3MsICRrXHRpbWVzIG4kIGVsZW1lbnRzIGluIHRoZSAkXG1hdGhiZnt1fV9qJCBhbmQgJGsgXHRpbWVzIHAkIGVsZW1lbnRzIGluIHRoZSAkXG1hdGhiZnt2fV9qJC4gSGVuY2UgYSB0b3RhbCBvZiAkaytrbitrcD1rKDErbitwKSQgZWxlbWVudHMgKHVzdWFsbHkgbXVjaCBzbWFsbGVyIHRoYW4gJG5wJCkuIChOb3RlIHRoYXQgcmVzdHJpY3Rpb25zIGFwcGx5IHRvICRcbWF0aGJme3V9X2okIGFuZCAkXG1hdGhiZnt2fV9qJDsgaGVuY2UgZXZlbiBsZXNzIGluZGVwZW5kZW50IGVsZW1lbnRzKS4gIAoKICRcbG9uZ3JpZ2h0YXJyb3ckICoqZGF0YSBjb21wcmVzc2lvbioqCgojIyBFeGFtcGxlIDE6IEltYWdlIGNvbXByZXNzaW9uCgojIyMgUGFpbnRpbmcgTW9uZHJpYWFuOiBDb21wb3NpdGlvbl9Oby5JSUkgd2l0aCByZWQsIGJsdWUsIHllbGxvdyBhbmQgYmxhY2sgKDE5MjkpLgoKLSBIYXZlIGEgbG9vayBhdCB0aGlzIHBhaW50aW5nIG9mIE1vbmRyaWFhbiAoMTg3MiAtLSAxOTQ0KSwgaGVyZSBzaG93biBpbiBibGFjay1hbmQtd2hpdGUuCgojIyMjIExvYWQgdGhlIG9yaWdpbmFsIHBhaW50aW5nCgoxLiBmZXRjaCBpbWFnZSBmcm9tIHRoZSB3ZWIKMi4gY29udmVydCBpbnRvIGdyZXlzY2FsZQozLiBwbG90CjQuIHNhdmUgYXMgTWF0cml4CgpgYGB7cn0KbW9uZHJpYWFuIDwtIGxvYWQuaW1hZ2UoImh0dHBzOi8vdXBsb2FkLndpa2ltZWRpYS5vcmcvd2lraXBlZGlhL2NvbW1vbnMvdGh1bWIvYS9hYy9QaWV0X01vbmRyaWFuXy1fQ29tcG9zaXRpb25fTm8uX0lJSSUyQ193aXRoX3JlZCUyQ19ibHVlJTJDX3llbGxvd19hbmRfYmxhY2slMkNfMTkyOS5qcGcvMTkyMHB4LVBpZXRfTW9uZHJpYW5fLV9Db21wb3NpdGlvbl9Oby5fSUlJJTJDX3dpdGhfcmVkJTJDX2JsdWUlMkNfeWVsbG93X2FuZF9ibGFjayUyQ18xOTI5LmpwZyIpCm1vbmRyaWFhbiA8LSBncmF5c2NhbGUobW9uZHJpYWFuKQpwbG90KG1vbmRyaWFhbixheGVzPUZBTFNFKQpYIDwtIG1hdHJpeChhcy5kYXRhLmZyYW1lKG1vbmRyaWFhbilbLDNdLG5yb3c9bnJvdyhtb25kcmlhYW4pLG5jb2w9bmNvbChtb25kcmlhYW4pKQpgYGAKCi0gVGhpcyBwaWN0dXJlIGNhbiBiZSByZXByZXNlbnRlZCBhcyBhICRgciBucm93KG1vbmRyaWFhbilgIFx0aW1lcyBgciBuY29sKG1vbmRyaWFhbilgYCQgbWF0cml4ICRcbWF0aGJme1h9JCB3aXRoIGdyYXkgc2NhbGUgaW50ZW5zaXRpZXMgJFxpbiBbMCwxXSQuICgkXGFwcHJveCA0XHRpbWVzIDEwXjYkIGRhdGEgZW50cmllcykKCi0gV2Ugd2lsbCBoZXJlIG5vdCB0cmFuc2Zvcm0gdGhlIGltYWdlIGluIGEgdmVjdG9yLCBidXQgd2lsbCBsb29rIGF0IHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgU1ZEIHRvIGNvbXByZXNzIHRoaXMgaW1hZ2UuIFRoZSBTVkQgY2FuIGJlIGFwcGxpZWQgdG8gYW55IG1hdHJpeCEKCgojIyMjIFNpbmd1bGFyIHZhbHVlcwoKYGBge3J9Cm1vblN2ZCA8LSBzdmQoWCkKCnAxIDwtIGRhdGEuZnJhbWUoeD0xOmxlbmd0aChtb25TdmQkZCkseT1tb25TdmQkZCkgJT4lCiAgZ2dwbG90KGFlcyh4PXgseT15KSkgKwogIGdlb21fcG9pbnQoKSArCiAgeGxhYigiayIpICsKICB5bGFiKCJzaW5ndWxhciB2YWx1ZSIpCgpwMiA8LSBkYXRhLmZyYW1lKHg9MToxMCx5PW1vblN2ZCRkWzE6MTBdKSAlPiUKICBnZ3Bsb3QoYWVzKHg9eCx5PXkpKSArCiAgZ2VvbV9wb2ludCgpICsKICB4bGFiKCJrIikgKwogIHlsYWIoInNpbmd1bGFyIHZhbHVlIikKCmdyaWQuYXJyYW5nZShwMSxwMixucm93PTEpCmBgYAoKLSBUaGUgc2luZ3VsYXIgdmFsdWVzIGRlY2F5IHZlcnkgcXVpY2tseSEKCiMjIyMgRGF0YSBjb21wcmVzc2lvbgoKLSBXZSBtYWtlIHRoZSBwbG90IGZvciBhIHJlY29uc3RydWN0aW9uIHdpdGggMSBzaW5ndWxhciB2ZWN0b3IuIFRoaXMgbGVhZHMgdG8gYSBkYXRhIGNvbXByZXNzaW9uIG9mICQxLVxmcmFjeygxK2ByIG5yb3cobW9uZHJpYWFuKWArYHIgbmNvbChtb25kcmlhYW4pYCl9e2ByIG5yb3cobW9uZHJpYWFuKSBgXHRpbWVzIGByIG5jb2wobW9uZHJpYWFuKWB9JCA9IGByIDEwMCAtIHJvdW5kKCgxK25yb3cobW9uZHJpYWFuKStuY29sKG1vbmRyaWFhbikpLyhucm93KG1vbmRyaWFhbikqbmNvbChtb25kcmlhYW4pKSoxMDAsMSlgJS4gV2Ugb25seSB1c2UgMSBsZWZ0IHNpbmd1bGFyIHZlY3RvciAoYHIgbnJvdyhtb25kcmlhYW4pYCksIDEgZWlnZW4gdmFsdWUsIDEgcmlnaHQgc2luZ3VsYXIgdmVjdG9yIChgciBuY29sKG1vbmRyaWFhbilgKS4KCgpgYGB7cn0KayA8LSAxCmFwcHJveE1vbiA8LSBtb25TdmQkdVssMTprXSAlKiUKICBkaWFnKG1vblN2ZCRkWzE6a10sbmNvbD1rKSAlKiUKICB0KG1vblN2ZCR2WywxOmtdKQoKYXBwcm94TW9uW2FwcHJveE1vbiA8IDBdIDwtIDAKYXBwcm94TW9uW2FwcHJveE1vbiA+IDFdIDwtIDEKCmFzLmNpbWcoYXBwcm94TW9uKSAlPiUKICBwbG90KC4sbWFpbj1wYXN0ZTAoIkFwcHJveGltYXRpb24gd2l0aCAiLGssIiBzaW5ndWxhciB2ZWN0b3JzIiksYXhlcz1GQUxTRSkKYGBgCgotIFdlIG1ha2UgdGhlIHBsb3QgZm9yIGEgcmVjb25zdHJ1Y3Rpb24gd2l0aCAyIHNpbmd1bGFyIHZlY3Rvci4gVGhpcyBsZWFkcyB0byBhIGRhdGEgY29tcHJlc3Npb24gb2YgJDEtXGZyYWN7Mlx0aW1lcyAoMStgciBucm93KG1vbmRyaWFhbilgK2ByIG5jb2wobW9uZHJpYWFuKWApfXtgciBucm93KG1vbmRyaWFhbikgYFx0aW1lcyBgciBuY29sKG1vbmRyaWFhbilgfSQgPSBgciAxMDAgLSByb3VuZCgyKigxK25yb3cobW9uZHJpYWFuKStuY29sKG1vbmRyaWFhbikpLyhucm93KG1vbmRyaWFhbikqbmNvbChtb25kcmlhYW4pKSoxMDAsMSlgJS4gV2Ugb25seSB1c2UgMiBsZWZ0IHNpbmd1bGFyIHZlY3RvcnMKKDIgJFx0aW1lcyQgYHIgbnJvdyhtb25kcmlhYW4pYCksIDIgc2luZ3VsYXIgdmFsdWVzLCAyIHJpZ2h0IHNpbmd1bGFyIHZlY3RvcnMgKDIgJFx0aW1lcyQgYHIgbmNvbChtb25kcmlhYW4pYCkuCgpgYGB7cn0KayA8LSAyCmFwcHJveE1vbiA8LSBtb25TdmQkdVssMTprXSAlKiUKICBkaWFnKG1vblN2ZCRkWzE6a10sbmNvbD1rKSAlKiUKICB0KG1vblN2ZCR2WywxOmtdKQoKYXBwcm94TW9uW2FwcHJveE1vbiA8IDBdIDwtIDAKYXBwcm94TW9uW2FwcHJveE1vbiA+IDFdIDwtIDEKCmFzLmNpbWcoYXBwcm94TW9uKSAlPiUKICBwbG90KC4sbWFpbj1wYXN0ZTAoIkFwcHJveGltYXRpb24gd2l0aCAiLGssIiBzaW5ndWxhciB2ZWN0b3JzIiksYXhlcz1GQUxTRSkKYGBgCgoKYGBge3J9CnBhciAobWZyb3c9YygzLDMpKQpwYXIobWFyPWMoMSwyLDEsMSkpCmZvciAoayBpbiBjKDE6OCkpCnsKYXBwcm94TW9uIDwtIG1vblN2ZCR1WywxOmtdICUqJQogIGRpYWcobW9uU3ZkJGRbMTprXSxuY29sPWspICUqJQogIHQobW9uU3ZkJHZbLDE6a10pCgoKYXBwcm94TW9uW2FwcHJveE1vbiA8IDBdIDwtIDAKYXBwcm94TW9uW2FwcHJveE1vbiA+IDFdIDwtIDEKCgphcHByb3hNb24gJT4lCiAgYXMuY2ltZyAlPiUKICBwbG90KC4sbWFpbj1wYXN0ZTAoaywiIHNpbmd1bGFyIHZlY3RvcnMiKSxheGVzPUZBTFNFKQp9ICAKcGxvdChhcy5jaW1nKFgpLG1haW49cGFzdGUwKCJPcmlnaW5hbCBpbWFnZSIpLGF4ZXM9RkFMU0UpCmBgYAoKIyMjIE1vcmUgY29tcGxleCBwYWludGluZzogQ29tcG9zaXRpb24gQSwgUGlldCBNb25kcmlhYW4KCiMjIyMgTG9hZCB0aGUgb3JpZ2luYWwgcGFpbnRpbmcKCmBgYHtyfQptb25kcmlhYW4gPC0gbG9hZC5pbWFnZSgiaHR0cHM6Ly91cGxvYWQud2lraW1lZGlhLm9yZy93aWtpcGVkaWEvY29tbW9ucy90aHVtYi9jL2MzL0NvbXBvc2l0aW9uX0FfYnlfUGlldF9Nb25kcmlhbl9HYWxsZXJpYV9OYXppb25hbGVfZCUyN0FydGVfTW9kZXJuYV9lX0NvbnRlbXBvcmFuZWEuanBnLzE5MjBweC1Db21wb3NpdGlvbl9BX2J5X1BpZXRfTW9uZHJpYW5fR2FsbGVyaWFfTmF6aW9uYWxlX2QlMjdBcnRlX01vZGVybmFfZV9Db250ZW1wb3JhbmVhLmpwZyIpCm1vbmRyaWFhbiA8LSBncmF5c2NhbGUobW9uZHJpYWFuKQpwbG90KG1vbmRyaWFhbixheGVzPUZBTFNFKQpYIDwtIG1hdHJpeChhcy5kYXRhLmZyYW1lKG1vbmRyaWFhbilbLDNdLG5yb3c9ZGltKG1vbmRyaWFhbilbMV0sbmNvbD1kaW0obW9uZHJpYWFuKVsyXSkKYGBgCgoKIyMjIyBTaW5ndWxhciB2YWx1ZXMKCmBgYHtyLCBjYWNoZT1UUlVFfQptb25TdmQgPC0gc3ZkKFgpCgpwMSA8LSBkYXRhLmZyYW1lKHg9MTpsZW5ndGgobW9uU3ZkJGQpLHk9bW9uU3ZkJGQpICU+JQogIGdncGxvdChhZXMoeD14LHk9eSkpICsKICBnZW9tX3BvaW50KCkgKwogIHhsYWIoImsiKSArCiAgeWxhYigic2luZ3VsYXIgdmFsdWUiKQoKcDIgPC0gZGF0YS5mcmFtZSh4PTE6MTAseT1tb25TdmQkZFsxOjEwXSkgJT4lCiAgZ2dwbG90KGFlcyh4PXgseT15KSkgKwogIGdlb21fcG9pbnQoKSArCiAgeGxhYigiayIpICsKICB5bGFiKCJzaW5ndWxhciB2YWx1ZSIpCgpncmlkLmFycmFuZ2UocDEscDIsbnJvdz0xKQpgYGAKCi0gVGhlIHNpbmd1bGFyIHZhbHVlcyBkZWNheSBhIGJpdCBzbG93ZXIuIFRoZSBwYWludGluZyBpcyBhIGJpdCBtb3JlIGNvbXBsZXguIE1vcmUgbGluZXMgYW5kIGNvbG9ycy4KCiMjIyMgRXZhbHVhdGUgZGF0YSBjb21wcmVzc2lvbgoKYGBge3J9CnBhciAobWZyb3c9YygzLDMpKQpwYXIobWFyPWMoMSwyLDEsMSkpCmZvciAoayBpbiBjKDEsc2VxKDMsMjEsMykpKQp7CmFwcHJveE1vbiA8LSBtb25TdmQkdVssMTprXSAlKiUKICBkaWFnKG1vblN2ZCRkWzE6a10sbmNvbD1rKSAlKiUKICB0KG1vblN2ZCR2WywxOmtdKQoKCmFwcHJveE1vblthcHByb3hNb24gPCAwXSA8LSAwCmFwcHJveE1vblthcHByb3hNb24gPiAxXSA8LSAxCgoKYXBwcm94TW9uICU+JQogIGFzLmNpbWcgJT4lCiAgcGxvdCguLG1haW49cGFzdGUwKGssIiBzaW5ndWxhciB2ZWN0b3JzIiksYXhlcz1GQUxTRSkKfSAgCnBsb3QoYXMuY2ltZyhYKSxtYWluPXBhc3RlMCgiT3JpZ2luYWwgaW1hZ2UiKSxheGVzPUZBTFNFKQpgYGAKCiMjIyBTZWxmIHBvcnRyZXQgUGlldCBNb25kcmlhYW4KCiMjIyMgTG9hZCB0aGUgcGFpbnRpbmcKCmBgYHtyfQptb25kcmlhYW4gPC0gbG9hZC5pbWFnZSgiaHR0cHM6Ly91cGxvYWQud2lraW1lZGlhLm9yZy93aWtpcGVkaWEvY29tbW9ucy90aHVtYi82LzY2L01vbmRyaWFuX1plbGZwb3J0cmV0LmpwZy8xOTIwcHgtTW9uZHJpYW5fWmVsZnBvcnRyZXQuanBnIikKbW9uZHJpYWFuIDwtIGdyYXlzY2FsZShtb25kcmlhYW4pCnBsb3QobW9uZHJpYWFuLGF4ZXM9RkFMU0UpClggPC0gbWF0cml4KGFzLmRhdGEuZnJhbWUobW9uZHJpYWFuKVssM10sbnJvdz1kaW0obW9uZHJpYWFuKVsxXSxuY29sPWRpbShtb25kcmlhYW4pWzJdKQpgYGAKCiMjIyMgU2luZ3VsYXIgdmFsdWVzCgpgYGB7ciwgY2FjaGU9VFJVRX0KbW9uU3ZkIDwtIHN2ZChYKQoKcDEgPC0gZGF0YS5mcmFtZSh4PTE6bGVuZ3RoKG1vblN2ZCRkKSx5PW1vblN2ZCRkKSAlPiUKICBnZ3Bsb3QoYWVzKHg9eCx5PXkpKSArCiAgZ2VvbV9wb2ludCgpICsKICB4bGFiKCJrIikgKwogIHlsYWIoInNpbmd1bGFyIHZhbHVlIikKCnAyIDwtIGRhdGEuZnJhbWUoeD0xOjEwLHk9bW9uU3ZkJGRbMToxMF0pICU+JQogIGdncGxvdChhZXMoeD14LHk9eSkpICsKICBnZW9tX3BvaW50KCkgKwogIHhsYWIoImsiKSArCiAgeWxhYigic2luZ3VsYXIgdmFsdWUiKQoKZ3JpZC5hcnJhbmdlKHAxLHAyLG5yb3c9MSkKYGBgCgotIFRoZSBzaW5ndWxhciB2YWx1ZXMgZGVjYXkgbXVjaCBzbG93ZXIuIFRoZSBwYWludGluZyBpcyBtb3JlIGNvbXBsZXguCgojIyMjIEV2YWx1YXRlIGNvbXByZXNzaW9uCgpgYGB7cn0KcGFyIChtZnJvdz1jKDMsMykpCnBhcihtYXI9YygxLDIsMSwxKSkKZm9yIChrIGluIGMoMSw1LDEwLDIwLDMwLDQwLDUwLDEwMCkpCnsKYXBwcm94TW9uIDwtIG1vblN2ZCR1WywxOmtdICUqJQogIGRpYWcobW9uU3ZkJGRbMTprXSxuY29sPWspICUqJQogIHQobW9uU3ZkJHZbLDE6a10pCgoKYXBwcm94TW9uW2FwcHJveE1vbiA8IDBdIDwtIDAKYXBwcm94TW9uW2FwcHJveE1vbiA+IDFdIDwtIDEKCgphcHByb3hNb24gJT4lCiAgYXMuY2ltZyAlPiUKICBwbG90KC4sbWFpbj1wYXN0ZTAoaywiIHNpbmd1bGFyIHZlY3RvcnMiKSxheGVzPUZBTFNFKQp9ICAKcGxvdChhcy5jaW1nKFgpLG1haW49cGFzdGUwKCJPcmlnaW5hbCBpbWFnZSIpLGF4ZXM9RkFMU0UpCmBgYAoKCgpIZXJlIHdlIG5lZWQgYXQgbGVhc3QgNDAgc2luZ3VsYXIgdmVjdG9yLiBUaGlzIGxlYWRzIHRvIGEgZGF0YSBjb21wcmVzc2lvbiBvZiAkMS1cZnJhY3s0MFx0aW1lcyAoMStgciBucm93KG1vbmRyaWFhbilgK2ByIG5jb2wobW9uZHJpYWFuKWApfXtgciBucm93KG1vbmRyaWFhbilgIFx0aW1lcyBgciBuY29sKG1vbmRyaWFhbilgfSQgPSBgciAxMDAgLSByb3VuZCg0MCooMStucm93KG1vbmRyaWFhbikrbmNvbChtb25kcmlhYW4pKS8obnJvdyhtb25kcmlhYW4pKm5jb2wobW9uZHJpYWFuKSkqMTAwLDEpYCUuIFdlIG9ubHkgdXNlIDQwIGxlZnQgc2luZ3VsYXIgdmVjdG9ycyAoJDQwIFx0aW1lcyBgciBucm93KG1vbmRyaWFhbilgJCksIDQwIHNpbmd1bGFyIHZhbHVlcywgNDAgcmlnaHQgZWlnZW5zIHZlY3RvciAoJDQwXHRpbWVzIGByIG5jb2wobW9uZHJpYWFuKWAkKS4KCiMgR2VvbWV0cmljIGludGVycHJldGF0aW9uCgpXcml0ZSB0aGUgKip0cnVuY2F0ZWQgU1ZEKiogYXMKXFsKICBcbWF0aGJme1h9X2sgPSBcbWF0aGJme1V9X2sgXGJvbGRzeW1ib2x7XERlbHRhfV9rIFxtYXRoYmZ7Vn1fa15UID0gXG1hdGhiZntafV9rIFxtYXRoYmZ7Vn1fa15UClxdCndpdGgKXFsKICBcbWF0aGJme1p9X2sgPSBcbWF0aGJme1V9X2sgXGJvbGRzeW1ib2x7XERlbHRhfV9rClxdCmFuICRuIFx0aW1lcyBrJCBtYXRyaXguCgpFYWNoIG9mIHRoZSAkbiQgcm93cyBvZiAkXG1hdGhiZntafV9rJCwgc2F5ICRcbWF0aGJme3p9XlRfe2ssaX0kLCByZXByZXNlbnRzIGEgcG9pbnQgaW4gYSAkayQtZGltZW5zaW9uYWwgc3BhY2UuCgoKQmVjYXVzZSBvZiB0aGUgb3J0aG9ub3JtYWxpdHkgb2YgdGhlIHNpbmd1bGFyIHZlY3RvcnMsIHdlIGFsc28gaGF2ZQpcYmVnaW57ZXFuYXJyYXkqfQogIFxtYXRoYmZ7WH1fa1xtYXRoYmZ7Vn1fayAmPSYgXG1hdGhiZntafV9rIFxtYXRoYmZ7Vn1fa15UXG1hdGhiZntWfV9rIFxcCiAgXG1hdGhiZntYfV9rXG1hdGhiZntWfV9rICY9JiBcbWF0aGJme1p9X2suClxlbmR7ZXFuYXJyYXkqfQoKVGh1cyB0aGUgbWF0cml4ICRcbWF0aGJme1Z9X2skIGlzIGEgKip0cmFuc2Zvcm1hdGlvbiBtYXRyaXgqKiB0aGF0IG1heSBiZSB1c2VkIHRvIHRyYW5zZm9ybSAkXG1hdGhiZntYfV9rJCBpbnRvICRcbWF0aGJme1p9X2skLCBhbmQgJFxtYXRoYmZ7Wn1fayQgaW50byAkXG1hdGhiZntYfV9rJC4KCi0tLQoKTm90ZSB0aGF0CgotIFRoZSBtYXRyaXggJFxtYXRoYmZ7Vn1fayQgdHJhbnNmb3JtcyB0aGUgJHAkLWRpbWVuc2lvbmFsICRcbWF0aGJme1h9X2skIGludG8gdGhlICRrJC1kaW1lbnNpb25hbCAkXG1hdGhiZntafV9rJDogJFxtYXRoYmZ7Wn1fayA9IFxtYXRoYmZ7WH1fa1xtYXRoYmZ7Vn1fayQuIE5vdGUsIGhvd2V2ZXIsIHRoYXQgdGhlIG1hdHJpeCAkXG1hdGhiZntYfV9rJCBtdXN0IG5vdCBuZWNlc3NhcmlseSBiZSB1c2VkIGZvciB0aGlzIHRyYW5zZm9ybWF0aW9uLCBiZWNhdXNlIHRoZSBTVkQgb2YgdGhlIG9yaWdpbmFsIG1hdHJpeCAkXG1hdGhiZntYfSQgYWxzbyBnaXZlcyBkaXJlY3RseSAkXG1hdGhiZntafV9rID0gXG1hdGhiZntVfV9rIFxib2xkc3ltYm9se1xEZWx0YX1fayQuCgotIFRoZSBpbnZlcnNlIHRyYW5zZm9ybWF0aW9uIGZyb20gdGhlICAkayQtZGltZW5zaW9uYWwgJFxtYXRoYmZ7Wn1fayQgdG8gdGhlICRwJC1kaW1lbnNpb25hbCAkXG1hdGhiZntYfV9rJCBpcyBnaXZlbiBieSB0aGUgdHJhbnNwb3NlIG9mICRcbWF0aGJme1Z9X2skOiAkXG1hdGhiZntafV9rIFxtYXRoYmZ7Vn1fa15UPVxtYXRoYmZ7WH1fayQuIE9mdGVuIGludmVyc2UgdHJhbnNmb3JtYXRpb25zIGFyZSBnaXZlbiBieSB0aGUgaW52ZXJzZSBvZiBhIG1hdHJpeCwgYnV0IHRoYW5rcyB0byB0aGUgb3J0aG9ub3JtYWxpdHkgb2YgdGhlIGNvbHVtbnMgb2YgJFxtYXRoYmZ7Vn1fayQsIHdlIGdldCAkXG1hdGhiZntWfV9rXlRcbWF0aGJme1Z9X2s9XG1hdGhiZntJfSQsIGFuZCB0aHVzICRcbWF0aGJme1Z9X2teVCQgYWN0cyBhcyBhbiBpbnZlcnNlLgoKLSBUaGUgdHJhbnNmb3JtYXRpb24gZnJvbSB0aGUgICRrJC1kaW1lbnNpb25hbCAkXG1hdGhiZntafV9rJCB0byB0aGUgJHAkLWRpbWVuc2lvbmFsICRcbWF0aGJme1h9X2skIGlzIHRyYW5zZm9ybWluZyBwb2ludHMgZnJvbSBhIGxvdyBkaW1lbnNpb25hbCBzcGFjZSAoJGskKSB0byBhIGhpZ2ggZGltZW5zaW9uYWwgc3BhY2UgKCRwJCkuIFlvdSBtYXkgbm90IGludGVycHJldCB0aGlzIGFzIGlmIHRoaXMgdHJhbnNmb3JtYXRpb24gYWRkcyBpbmZvcm1hdGlvbjsgdGhlIHRyYW5zZm9ybWVkIHBvaW50cyBpbiAkXG1hdGhiZntYfV9rJCBzdGlsbCBsaXZlIGluIGEgJGskLWRpbWVuc2lvbmFsIHN1YnNwYWNlIG9mIHRoZSBsYXJnZXIgJHAkLWRpbWVuc2lvbmFsIHNwYWNlOyB0aGUgbWF0cml4ICRcbWF0aGJme1h9X2skIGlzIG9ubHkgb2YgcmFuayAkayQgYW5kIHRodXMgY29udGFpbnMgbGVzcyBpbmZvcm1hdGlvbiB0aGFuIHRoZSBvcmlnaW5hbCBkYXRhIG1hdHJpeCAkXG1hdGhiZntYfSQgKGlmIHJhbmsoJFxtYXRoYmZ7WH0kKSQ9cj5rJCkuCgotLS0KCk1vcmUgaW1wb3J0YW50bHksIGl0IGNhbiBiZSBzaG93biB0aGF0ICh0aGFua3MgdG8gb3J0aG9ub3JtYWxpdHkgb2YgJFxtYXRoYmZ7Vn0kKQogIFxbCiAgICAgXG1hdGhiZntYfVxtYXRoYmZ7Vn1fayA9IFxtYXRoYmZ7Wn1fay4KICBcXQogIFRoaXMgZm9sbG93cyBmcm9tICh3LmwuZy4gcmFuaygkXG1hdGhiZntYfSQpPSRyJCkKICBcYmVnaW57ZXFuYXJyYXkqfQogICAgXG1hdGhiZntYfVxtYXRoYmZ7Vn1fawogICAgICAgJj0mIFxtYXRoYmZ7VURWfV5UXG1hdGhiZntWfV9rID0gXG1hdGhiZntVRH1cYmVnaW57cG1hdHJpeH0KICAgICAgIAkgICAgICAgIFxtYXRoYmZ7dn1fMV5UIFxcCgkgICAgICAgIFx2ZG90cyBcXAoJICAgICAgICBcbWF0aGJme3Z9X3JeVAogICAgICAgICAgICAgICBcZW5ke3BtYXRyaXh9CiAgICAgICAgICAgICAgIFxiZWdpbntwbWF0cml4fQogICAgICAgICAgICAgICAgIFxtYXRoYmZ7dn1fMSBcbGRvdHMgXG1hdGhiZnt2fV9rCiAgICAgICAgICAgICAgIFxlbmR7cG1hdHJpeH0gXFwKICAgICAgICY9JiBcbWF0aGJme1VEVn1eVFxtYXRoYmZ7Vn1fayA9IFxtYXRoYmZ7VUR9XGJlZ2lue3BtYXRyaXh9CiAgICAgICAgICAgICAgICAxICYgMCAmIFxsZG90cyAmIDAgXFwKICAgICAgICAgICAgICAgIDAgJiAxICYgXGxkb3RzICYgMCBcXAogICAgICAgICAgICAgICAgXHZkb3RzICYgXHZkb3RzICYgXGRkb3RzICYgMCBcXAogICAgICAgICAgICAgICAgMCAmIDAgJiBcbGRvdHMgJiAxIFxcCiAgICAgICAgICAgICAgICAwICYgMCAmIFxsZG90cyAmIDAgXFwKICAgICAgICAgICAgICAgIFx2ZG90cyAmIFx2ZG90cyAmIFx2ZG90cyAmIFx2ZG90cyBcXAogICAgICAgICAgICAgICAgMCAmIDAgJiBcbGRvdHMgJiAwCiAgICAgICAgICAgICAgIFxlbmR7cG1hdHJpeH0gXAogICAgICAgICAgICAgICA9IFxtYXRoYmZ7VX1fa1xib2xkc3ltYm9se1xEZWx0YX1fayA9IFxtYXRoYmZ7Wn1fawogIFxlbmR7ZXFuYXJyYXkqfQoKICBUaGUgJHAgXHRpbWVzIGskIG1hdHJpeCAkXG1hdGhiZntWfV9rJCBhY3RzIGFzIGEgdHJhbnNmb3JtYXRpb24gbWF0cml4OiB0cmFuc2Zvcm1pbmcgJG4kIHBvaW50cyBpbiBhICRwJCBkaW1lbnNpb25hbCBzcGFjZSB0byAkbiQgcG9pbnRzIGluIGEgJGskIGRpbWVuc2lvbmFsIHNwYWNlLgoKLS0tCgpXZSB0YWtlIGEgY2xvc2VyIGxvb2sgYXQKXFsKICBcbWF0aGJme1p9X2sgPSBcbWF0aGJme1h9XG1hdGhiZntWfV9rID0gXGJlZ2lue3BtYXRyaXh9CiAgIFxtYXRoYmZ7eH1fMV5UIFxcCiAgIFx2ZG90cyBcXAogICBcbWF0aGJme3h9X25eVAogIFxlbmR7cG1hdHJpeH0gXGJlZ2lue3BtYXRyaXh9CiAgIFxtYXRoYmZ7dn1fMSBcbGRvdHMgXG1hdGhiZnt2fV9rCiAgXGVuZHtwbWF0cml4fS4KXF0KVGhlICRpJHRoIHJvdyAob2JzZXJ2YXRpb24pIGluICRcbWF0aGJme1p9X2skIGVxdWFscwpcWwogXG1hdGhiZnt6fV97ayxpfV5UID0gXG1hdGhiZnt4fV9pXlRcbWF0aGJme1Z9X2sgPSBcbGVmdChcbWF0aGJme3h9X2leVCBcbWF0aGJme3Z9XzEgLCBcbWF0aGJme3h9X2leVCBcbWF0aGJme3Z9XzIgLCBcbGRvdHMgLCBcbWF0aGJme3h9X2leVCBcbWF0aGJme3Z9X2tccmlnaHQpLgpcXQoKSGVuY2UsICRcbWF0aGJme3p9X3trLGl9XlQgPSBcbWF0aGJme3h9X2leVFxtYXRoYmZ7Vn1fayQgaXMgdGhlIG9ydGhvZ29uYWwgcHJvamVjdGlvbiBvZiAkXG1hdGhiZnt4fV9pJCBvbnRvIHRoZSAkayQtZGltZW5zaW9uYWwgc3Vic3BhY2Ugc3Bhbm5lZCBieSB0aGUgY29sdW1ucyBvZiAkXG1hdGhiZntWfV9rJC4KClNWRCB0cmFuc2Zvcm1zIGRhdGEgc2V0IHRvIGxvd2VyIGRpbWVuc2lvbmFsIGRhdGEgc2V0OgpUaGUgU1ZEIHRodXMgZ2l2ZXMgYSB0cmFuc2Zvcm1hdGlvbiBvZiB0aGUgJHAkIGRpbWVuc2lvbmFsIGRhdGEgdG8gJGtcbGVxIHIkIGRpbWVuc2lvbmFsIGRhdGE6ClxbCiAgXG1hdGhiZntafV9rID0gXG1hdGhiZntYfVxtYXRoYmZ7Vn1fay4KXF0KVGhpcyBpcyBlc3NlbnRpYWxseSBhIGRpbWVuc2lvbiByZWR1Y3Rpb24uCgotLS0tCgpOb3RlIHRoYXQsCgotIFRoZSB0cmFuc2Zvcm1hdGlvbiBmcm9tICRwJC1kaW1lbnNpb25hbCAkXG1hdGhiZntYfSQgdG8gJGskLWRpbWVuc2lvbmFsICRcbWF0aGJme1p9X2skIGlzIGltcG9ydGFudC4gSXQgc2hvd3MgdGhhdCB0aGUgJG4kIHBvaW50cyBpbiB0aGUgcm93cyBvZiAkXG1hdGhiZntafV9rJCBhcmUgdGhlIHJlc3VsdCBvZiBwcm9qZWN0aW5nIHRoZSAkbiQgcG9pbnRzIGluICRcbWF0aGJme1h9JCBvbnRvIHRoZSBjb2x1bW5zIG9mICRcbWF0aGJme1Z9X2skIChpLmUuIHRoZSBmaXJzdCAkayQgc2luZ3VsYXIgdmVjdG9ycyBvZiAkXG1hdGhiZntYfSQpLiBXZSBzYXkgdGhhdCB0aGUgc3BhY2Ugb2YgJFxtYXRoYmZ7Wn1fayQgaXMgc3Bhbm5lZCBieSB0aGUgY29sdW1uIG9mICRcbWF0aGJme1Z9X2skLgoKLSBUaGUgcG9pbnRzIChyb3dzKSBpbiAkXG1hdGhiZntYfSQgbGl2ZSBpbiBhICRwJC1kaW1lbnNpb25hbCBzcGFjZSAob3IgcmFuaygkXG1hdGhiZntYfSQpJD1yJCBpZiAkcjxwJCkgYW5kIHRoZXkgYXJlIHRodXMgcHJvamVjdGVkIG9udG8gYSBsb3dlciBkaW1lbnNpb25hbCBzcGFjZS4gVGhpcyBpcyBpbiBjb250cmFzdCB0byB0aGUgcHJvamVjdGlvbiAkXG1hdGhiZntYfV9rXG1hdGhiZntWfV9rPVxtYXRoYmZ7Wn1fayQsIGJlY2F1c2UgdGhlIHBvaW50cyBpbiAkXG1hdGhiZntYfV9rJCBsaXZlIGluIGEgJGskLWRpbWVuc2lvbmFsIHN1YnNwYWNlIG9mICRcbWF0aGJme1h9JC4KCi0gTm90ZSB0aGF0IHdpdGggJGs8ciQgdGhlcmUgaXMgbm8gdW5pcXVlIHRyYW5zZm9ybWF0aW9uIHRvIHRyYW5zZm9ybSAkXG1hdGhiZntafV9rJCBiYWNrIHRvICRcbWF0aGJme1h9JC4gT24gdGhlIHByZXZpb3VzIHNsaWRlIHdlIG9ubHkgZXN0YWJsaXNoZWQgdGhlIHRyYW5zZm9ybWF0aW9uICRcbWF0aGJme1p9X2sgXG1hdGhiZntWfV9rXlQ9XG1hdGhiZntYfV9rJC4gSW5kZWVkLCBzdGFydGluZyBmcm9tICRcbWF0aGJme1h9XG1hdGhiZntWfV9rID0gXG1hdGhiZntafV9rJCwgYW5kIHJpZ2h0LW11bHRpcGx5aW5nIHdpdGggJFxtYXRoYmZ7Vn1fa15UJCBkb2VzIG5vdCBnaXZlIHRoZSBiYWNrdHJhbnNmb3JtYXRpb24sIGJlY2F1c2UgJFxtYXRoYmZ7Vn1fa1xtYXRoYmZ7Vn1fa15UJCBpcyBub3QgdGhlIGlkZW50aXR5IG1hdHJpeC4KCgojIEludGVycHJldGF0aW9uIG9mIFNWRCBpbiB0ZXJtcyBvZiBjb3JyZWxhdGlvbiBtYXRyaWNlcwoKRm9yIGEgbWF0cml4ICRcbWF0aGJmIFgkIHRoZSBzYW1wbGUgdmFyaWFuY2UgY292YXJpYW5jZSBtYXRyaXggZXN0aW1hdG9yIGlzICRwXHRpbWVzIHAkIG1hdHJpeAoKXGJlZ2lue2VxbmFycmF5fQpcbWF0aGJme1N9Jj0mXGZyYWN7MX17Ti0xfShcbWF0aGJme1h9LVxiYXJ7XG1hdGhiZntYfX0pXlQgKFxtYXRoYmZ7WH0tXGJhcntcbWF0aGJme1h9fSlcXAomPSZcZnJhY3sxfXtOLTF9XGxlZnRbXG1hdGhiZntYfV5UXG1hdGhiZntYfSAtIFxiYXJ7XG1hdGhiZntYfX1eVFxiYXJ7XG1hdGhiZntYfX1ccmlnaHRdClxlbmR7ZXFuYXJyYXl9CgpTbyAkXG1hdGhiZntYfV5UXG1hdGhiZntYfSQgZGVmaW5lcyB1cCB0byBhIGNvbnN0YW50IHRoZSB2YXJpYW5jZSBjb3ZhcmlhbmNlIG1hdHJpeCBvZiAkXG1hdGhiZntYfSQhIFdoZW4gdGhlIG1hdHJpeCBpcyBjb2x1bW4gY2VudGVyZWQgJFxtYXRoYmZ7U309XGZyYWN7MX17bi0xfVxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0kLgoKVGhlIHNhbWUgaG9sZHMgZm9yIHRoZSByb3dzIG9mICRcbWF0aGJme1h9JCEgVGhlIGNvdmFyaWFuY2UgYmV0d2VlbiB0aGUgc3ViamVjdHMgY2FuIGJlIGVzdGltYXRlZCBhcyBcW1xtYXRoYmZ7U309XGZyYWN7MX17cC0xfVxtYXRoYmZ7WH1cbWF0aGJme1h9XlRcXSB1cG9uIHJvdyBjZW50ZXJpbmcuCgpOb3RlLCB0aGF0ICAKXGJlZ2lue2VxbmFycmF5fQpcbWF0aGJme1h9XlRcbWF0aGJme1h9ICY9JiBcbWF0aGJme1Z9XGJvbGRzeW1ib2x7XERlbHRhfVxtYXRoYmZ7VX1eVCBcbWF0aGJme1V9IFxib2xkc3ltYm9se1xEZWx0YX0gXG1hdGhiZntWfV5UIFxcCiY9JlxtYXRoYmZ7Vn1cYm9sZHN5bWJvbHtcRGVsdGF9XjIgXG1hdGhiZntWfV5UClxlbmR7ZXFuYXJyYXl9CgpJZiB3ZSByZXdyaXRlIHRoZSBleHByZXNzaW9uClxiZWdpbntlcW5hcnJheX0KXG1hdGhiZntYfV5UXG1hdGhiZntYfVxtYXRoYmZ7Vn0gJj0mXG1hdGhiZntWfVxib2xkc3ltYm9se1xEZWx0YX1eMiBcbWF0aGJme1Z9XlRcbWF0aGJme1Z9ClxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH1cbWF0aGJme1Z9XFwgJj0mXG1hdGhiZntWfVxib2xkc3ltYm9se1xEZWx0YX1eMgpcZW5ke2VxbmFycmF5fQoKU28sIGlmIHRoZSBkYXRhIGFyZSBjZW50ZXJlZCwgdGhlIFNWRCBjYW4gYmUgdXNlZCB0byBwZXJmb3JtIGEgc3BlY3RyYWwgZGVjb21wb3NpdGlvbiBvZiB0aGUgc2FtcGxlIGNvdmFyaWFuY2UgbWF0cml4IHdoZXJlIHRoZSByaWdodCBzaW5ndWxhciB2ZWN0b3JzIGNvcnJlc3BvbmQgdG8gdGhlIGVpZ2VuIHZlY3RvcnMgb2YgdGhlIGNvdmFyaWFuY2UgbWF0cml4IGFuZCB0aGUgZWlnZW52YWx1ZXMgYXJlIHRoZSBzcXVhcmVkIHNpbmd1bGFyIHZhbHVlcyEKClNpbWlsYXJseSB0aGUgbGVmdCBzaW5ndWxhciB2YWx1ZXMgY2FuIGJlIHVzZWQgdG8gZXN0aW1hdGUgdGhlIGNvdmFyaWFuY2UgbWF0cml4IG9mIHRoZSByb3dzIG9mICRcbWF0aGJme1h9JC4gU28gaW4gb3VyIG5vdGF0aW9uIGNvdmFyaWFuY2UgYmV0d2VlbiBzdWJqZWN0cy4KClxiZWdpbntlcW5hcnJheX0KXG1hdGhiZntYfVxtYXRoYmZ7WH1eVCAmPSZcbWF0aGJme1V9XGJvbGRzeW1ib2x7XERlbHRhfV4yIFxtYXRoYmZ7VX1eVApcZW5ke2VxbmFycmF5fQoKVGhpcyBsaW5rIGlzIGZvciBpbnN0YW5jZSB2ZXJ5IHVzZWZ1bCBmb3IgcmVjb21tZW5kZXIgc3lzdGVtcywgaS5lLiB0byBwcm9wb3NlIG1vdmllcyBiYXNlZCBvbiB0aGUgc3ViamVjdHMgd2l0aCB3aG9tIHlvdSBjb3JyZWxhdGUuIFdlIHdpbGwgYWxzbyBleHBsb2l0IHRoaXMgd2hlbiB3ZSBkaXNjdXNzIG9uIFBDQS4KCiMgU1ZEIGFuZCBpbnZlcnNlIG9mIGEgbWF0cml4CgpBIGxpbmVhciBzeXN0ZW0gb2YgZXF1YXRpb25zIHdpdGggJG4kIGVxdWF0aW9ucyBhbmQgJG4kIHVua25vd25zClxbClxtYXRoYmZ7QX1fe25cdGltZXMgbn0gXGJvbGRzeW1ib2x7XGJldGF9ID0gXG1hdGhiZntifQpcXQpjYW4gYmUgc29sdmVkIGJ5ClxbClxib2xkc3ltYm9se1xiZXRhfSA9IFxtYXRoYmZ7QX1fe25cdGltZXMgbn1eey0xfSBcbWF0aGJme2J9ClxdCgpBIHVuaXF1ZSBzb2x1dGlvbiBleGlzdHMgaWYgQSBpcyBmdWxsIHJhbmsuCgpOb3RlLCB0aGF0IGEgc2luZ3VsYXIgdmFsdWUgZGVjb21wb3NpdGlvbiBvZiB0aGUgc3F1YXJlIG1hdHJpeCAkXG1hdGhiZntBfT1cbWF0aGJme1Z9XGJvbGRzeW1ib2x7XERlbHRhfVxtYXRoYmZ7Vn1eVCQgZW5hYmxlcyB0aGUgaW52ZXJzZSB0byBiZSB3cml0dGVuIGFzCgpcWwpcbWF0aGJme0F9XnstMX0gPSBcbWF0aGJme1Z9XGJvbGRzeW1ib2x7XERlbHRhfV57LTF9XG1hdGhiZntWfV5UClxdCgppbmRlZWQKXFsKXG1hdGhiZntBfV57LTF9IFxtYXRoYmZ7QX0gPSBcbWF0aGJme1Z9XGJvbGRzeW1ib2x7XERlbHRhfV57LTF9XG1hdGhiZntWfV5UXG1hdGhiZntWfVxib2xkc3ltYm9se1xEZWx0YX1cbWF0aGJme1Z9XlQgPSBcbWF0aGJme0l9ClxdCgpOb3RlLCB0aGF0IHRoZSBTVkQgZ2VuZXJhbGl6ZXMgdGhpcyB0byBzeXN0ZW1zIG9mIHVuZGVyIChuPHAsIGZhdCBzaG9ydCBtYXRyaWNlcykKIGFuZCBvdmVyIGRldGVybWluZWQgc3lzdGVtcyAobj5wIHRhbGwgc2tpbm55IG1hdHJpY2VzKToKCkxldApcYmVnaW57ZXFuYXJyYXl9ClxtYXRoYmZ7QX0gPSBcbWF0aGJme1V9XGJvbGRzeW1ib2x7XERlbHRhfVxtYXRoYmZ7Vn1eVApcZW5ke2VxbmFycmF5fQoKYW5kIHdlIHdhbnQgdG8gc29sdmUKXGJlZ2lue2VxbmFycmF5fQpcbWF0aGJme0F9IFxib2xkc3ltYm9se1xiZXRhfSAmPSYgXG1hdGhiZntifVxcClxtYXRoYmZ7VX1cYm9sZHN5bWJvbHtcRGVsdGF9XG1hdGhiZntWfV5UIFxib2xkc3ltYm9se1xiZXRhfSAmPSYgXG1hdGhiZntifVxcClxtYXRoYmZ7VX1eVFxtYXRoYmZ7VX1cYm9sZHN5bWJvbHtcRGVsdGF9XG1hdGhiZntWfV5UIFxib2xkc3ltYm9se1xiZXRhfSAmPSYgXG1hdGhiZntVfV5UXG1hdGhiZntifVxcClxib2xkc3ltYm9se1xEZWx0YX1cbWF0aGJme1Z9XlQgXGJvbGRzeW1ib2x7XGJldGF9ICY9JiBcbWF0aGJme1V9XlRcbWF0aGJme2J9XFwKXGJvbGRzeW1ib2x7XERlbHRhfV57LTF9XGJvbGRzeW1ib2x7XERlbHRhfVxtYXRoYmZ7Vn1eVCBcYm9sZHN5bWJvbHtcYmV0YX0gJj0mIFxib2xkc3ltYm9se1xEZWx0YX1eey0xfVxtYXRoYmZ7VX1eVFxtYXRoYmZ7Yn1cXApcbWF0aGJme1Z9XG1hdGhiZntWfV5UIFxib2xkc3ltYm9se1xiZXRhfSAmPSYgXG1hdGhiZntWfVxib2xkc3ltYm9se1xEZWx0YX1eey0xfVxtYXRoYmZ7VX1eVFxtYXRoYmZ7Yn1cXApcYm9sZHN5bWJvbHtcYmV0YX0gJj0mIFxtYXRoYmZ7Vn1cYm9sZHN5bWJvbHtcRGVsdGF9XnstMX1cbWF0aGJme1V9XlRcbWF0aGJme2J9ClxlbmR7ZXFuYXJyYXl9CgpOb3RlLCB0aGF0IGZvciBhbiBvdmVyZGV0ZXJtaW5lZCBzeXN0ZW0gJG4+cCQgc28gJHJcbGVxIHAkLgpHZW5lcmFsbHksICRyPXAkIGFuZCAkXG1hdGhiZntWfSQgaXMgdGh1cyBhIHNxdWFyZSBtYXRyaXggc28gYm90aCAkXG1hdGhiZntWfV5UXG1hdGhiZntWfT1cbWF0aGJme0l9JCBhbmQgICRcbWF0aGJme1Z9XG1hdGhiZntWfV5UPVxtYXRoYmZ7SX0kLiBIb3dldmVyLCAkXG1hdGhiZntVfV5UXG1hdGhiZntVfT1cbWF0aGJme0l9JCBidXQgICRcbWF0aGJme1V9XG1hdGhiZntVfV5UXG5lcVxtYXRoYmZ7SX0kIGJlY2F1c2UgJHI8biQuCgokXG1hdGhiZntBfV5cZGFnZ2VyPVxtYXRoYmZ7Vn1cYm9sZHN5bWJvbHtcRGVsdGF9XnstMX1cbWF0aGJme1V9XlQkIGlzIGFsc28gcmVmZXJyZWQgdG8gYXMgdGhlIHBzZXVkbyBpbnZlcnNlIGFuZCBpdCBlbmFibGVzIHVzIHRvIHNvbHZlIHVuZGVyIGFuZCBvdmVyZGV0ZXJtaW5lZCBzeXN0ZW1zIG9mIGVxdWF0aW9ucy4KCk5vdGUsIHRoYXQgZm9yCgotIHVuZGVyZGV0ZXJtaW5lZCBzeXN0ZW1zIHRoZXJlIHR5cGljYWxseSBkb2VzIG5vdCBleGlzdCBhIHVuaXF1ZSBzb2x1dGlvbgotIGZvciBvdmVyZGV0ZXJtaW5lZCBzeXN0ZW1zIHVzdWFsbHkgdGhlcmUgZG9lcyBub3QgZXhpc3QgYW4gZXhhY3Qgc29sdXRpb24uIFdlIHdpbGwgZm9jdXMgb24gdGhlIGxhdHRlciBpbiB0aGUgbmV4dCBzZWN0aW9uIHdoZXJlIHdlIGV4cGxvcmUgdGhlIGxpbmsgYmV0d2VlbiBsaW5lYXIgcmVncmVzc2lvbiBhbmQgU1ZELgoKIyBMaW5lYXIgcmVncmVzc2lvbiBhbmQgU1ZECgpTdXBwb3NlIHdlIGhhdmUgdGhlIGxpbmVhciByZWdyZXNzaW9uIHByb2JsZW0gd2l0aCAkbj5wJDoKClxbXG1hdGhiZntZfT1cbWF0aGJme1h9XGJvbGRzeW1ib2x7XGJldGF9ICsgXGJvbGRzeW1ib2x7XGVwc2lsb259XF0KCiRcbWF0aGJme1h9JCBpcyBhIHRhbGwgc2tpbm55IG1hdHJpeCB3aXRoICRuID4+IHAkLgoKV2Uga25vdyB0aGF0ClxbClxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fT1cbGVmdChcbWF0aGJme1h9XlQgXG1hdGhiZntYfVxyaWdodCleey0xfVxtYXRoYmZ7WH1eVFxtYXRoYmZ7WX0KXF0KCklmIHdlIHJlcGxhY2UgJFxtYXRoYmZ7WH0kIGJ5IGl0cyBTVkQKClxiZWdpbntlcW5hcnJheX0KXGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19Jj0mXGxlZnQoXG1hdGhiZntWfVxib2xkc3ltYm9se1xEZWx0YX1eMlxtYXRoYmZ7Vn1eVFxyaWdodCleey0xfVxtYXRoYmZ7Vn1cYm9sZHN5bWJvbHtcRGVsdGF9XG1hdGhiZntVfV5UXG1hdGhiZntZfVxcClxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fSY9JlxtYXRoYmZ7Vn1cYm9sZHN5bWJvbHtcRGVsdGF9XnstMn1cbWF0aGJme1Z9XlRcbWF0aGJme1Z9XGJvbGRzeW1ib2x7XERlbHRhfVxtYXRoYmZ7VX1eVFxtYXRoYmZ7WX1cXApcaGF0e1xib2xkc3ltYm9se1xiZXRhfX0mPSZcbWF0aGJme1Z9XGJvbGRzeW1ib2x7XERlbHRhfV57LTF9XG1hdGhiZntVfV5UXG1hdGhiZntZfQpcZW5ke2VxbmFycmF5fQoKU28gdGhlIFNWRCBhbHNvIHNvbHZlcyB0aGUgbGluZWFyIHJlZ3Jlc3Npb24gcHJvYmxlbSBieSB1c2luZyB0aGUgcHNldWRvaW52ZXJzZSEKSWYgd2Ugbm93IHRoaW5rIGFib3V0IHRoZSBmaXQ6CgpcYmVnaW57ZXFuYXJyYXl9ClxoYXR7XG1hdGhiZntZfX0mPSZcbWF0aGJme1h9XGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19XFwKJj0mXG1hdGhiZntVfVxib2xkc3ltYm9se1xEZWx0YX1eey0xfVxtYXRoYmZ7Vn1eVFxtYXRoYmZ7Vn1cYm9sZHN5bWJvbHtcRGVsdGF9XG1hdGhiZntVfV5UXG1hdGhiZntZfVxcCiY9JlxtYXRoYmZ7VX1cbWF0aGJme1V9XlRcbWF0aGJme1l9ClxlbmR7ZXFuYXJyYXl9CgotIEZvciBhbiBvdmVyZGV0ZXJtaW5lZCBzeXN0ZW0gJFxtYXRoYmZ7VX1cbWF0aGJme1V9XlQkIGlzIG5vdCBlcXVhbCB0byB0aGUgdW5pdHkgbWF0cml4ICRcbWF0aGJme0l9JCAoZm9yIGFuIG92ZXJkZXRlcm1pbmVkIHN5c3RlbSBvbmx5ICRcbWF0aGJme1V9XlRcbWF0aGJme1V9PVxtYXRoYmZ7SX0kIGJlY2F1c2UgJG4+ciQpLgoKLSBTbyAkXGhhdHtcbWF0aGJme1l9fVxuZXEgXG1hdGhiZntZfSQuIEhlbmNlLCB3ZSB0eXBpY2FsbHkgZG8gbm90IGhhdmUgYW4gZXhhY3Qgc29sdXRpb24uCgotIE5vdGUsIHRoYXQgJFxtYXRoYmZ7VX1cbWF0aGJme1V9XlQkIHNwYW5zIHRoZSBzYW1lIHNwYWNlIGFzIHRoZSBjb2x1bW5zIG9mICRcbWF0aGJme1h9JCwgYW5kIHdpbGwgZGVmaW5lIHRoZSBzYW1lICRwJC1kaW1lbnNpb25hbCBwbGFuZSBpbiB0aGUgJG4kIGRpbWVuc2lvbmFsIHNwYWNlICRcbWF0aGNhbHtSfV5uJCwgZS5nLiBjZnIgJFxtYXRoYmZ7WH1cbGVmdChcbWF0aGJme1h9XlRcbWF0aGJme1h9XHJpZ2h0KV57LTF9XG1hdGhiZntYfV5UJC4gU28gaXQgcHJvamVjdHMgJFxtYXRoYmZ7WX0kIGluIHRoZSBjb2x1bW4gc3BhY2Ugb2YgJFxtYXRoYmZ7WH0kIGFuZCB0aGUgZXJyb3JzIHdpbGwgYmUgb3J0aG9nb25hbCBvbnRvIHRoaXMgcGxhbmUuCgojIyBFeGFtcGxlIHByb3N0YXRlIGRhdGFzZXQKIyMjIEZpdCB3aXRoIGxtCgpgYGB7cn0KcHJvc3RhdGUgPC0gcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9HVFBCL1BTTFMyMC9tYXN0ZXIvZGF0YS9wcm9zdGF0ZS5jc3YiLAogIGNvbF90eXBlcyA9IGNvbHMoKQopCmxtMSA8LSBsbShscHNhIH4gbGNhdm9sICsgbHdlaWdodCArIHN2aSwgcHJvc3RhdGUpCmBgYAoKIyMjIEZpdCB3aXRoIFNWRAoKYGBge3J9ClggPC0gcHJvc3RhdGVbLGMoMToyLDUpXQpYWywzXSA8LSBhcy5kb3VibGUoWFssM10hPSJoZWFsdGh5IikKWCA8LSBjYmluZChJbnRlcmNlcHQ9MSxYKQoKc3ZkWCA8LSBzdmQoWCkKYmV0YVN2ZCA8LSBzdmRYJHYgJSolIGRpYWcoMS9zdmRYJGQpICUqJSB0KHN2ZFgkdSkgJSolIHByb3N0YXRlJGxwc2EKCmNiaW5kKGxtMSRjb2VmLGJldGFTdmQpCmBgYAoKIyBTVkQgYW5kIE11bHRpLURpbWVuc2lvbmFsIFNjYWxpbmcgKE1EUykKCiMjIEV4YW1wbGUKCkluIHRoaXMgc2VjdGlvbiB3ZSB3aWxsIHVzZSBhIGRhdGFzZXQgb24gZm9vZCBjb25zdW1wdGlvbiBpbiB0aGUgVUsuIFRoZSBkYXRhIG9yaWdpbmF0ZSBmcm9tIHRoZSBVS3Mg4oCYRGVwYXJ0bWVudCBmb3IgRW52aXJvbm1lbnQsIEZvb2QgYW5kIFJ1cmFsIEFmZmFpcnPigJkgKERFRlJBKSwgc2hvd2luZyB0aGUgY29uc3VtcHRpb24gaW4gZ3JhbXMgKHBlciBwZXJzb24sIHBlciB3ZWVrKSBvZiAxNyBkaWZmZXJlbnQgdHlwZXMgb2YgZm9vZHN0dWZmIG1lYXN1cmVkIGFuZCBhdmVyYWdlZCBpbiB0aGUgZm91ciBjb3VudHJpZXMgb2YgdGhlIFVuaXRlZCBLaW5nZG9tIGluIDE5OTcuIFdlIHdvdWxkIGxpa2UgdG8gZXhwbG9yZSB0aGUgZGF0YSBhbmQgaW50ZXJwcmV0IGhvdyB0aGUgZm9vZCBwYXR0ZXJucyBvZiB0aGUgZGlmZmVyZW50IGNvdW50cmllcyBkaWZmZXIuCgpgYGB7cn0KdWsgPC0gcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvSERBMjAyMC9kYXRhL3VrRm9vZHMuY3N2IiwKICBjb2xfdHlwZXMgPSBjb2xzKCkKKQprbml0cjo6a2FibGUodWssIGNhcHRpb24gPSAiVGhlIGZ1bGwgVUsgZm9vZHMgZGF0YSB0YWJsZSIpCmBgYAoKTm90ZSwgdGhhdCBoZXJlIHRoZSBtYXRyaXggaXMgZGlzcGxheWVkIHdpdGggdGhlIHAgdmFyaWFibGVzIGluIHRoZSByb3dzIGFuZCB0aGUgbiBleHBlcmltZW50YWwgdW5pdHMgKGNvdW50cmllcykgaW4gdGhlIGNvbHVtbnMuClRoaXMgaXMgb2Z0ZW4gZG9uZSBmb3IgaGlnaCBkaW1lbnNpb25hbCBkYXRhIHdoZXJlICRwPj5uJCBiZWNhdXNlIHRoaXMgbWFrZXMgaXQgZWFzaWVyIHRvIGxvb2sgYXQgdG8gdGhlIHJhdyBkYXRhIHRhYmxlLgpOb3RlLCB0aGF0IHRoZSBzdmQgY2FsY3VsYXRlcyBsZWZ0IGFuZCByaWdodCBzaW5ndWxhciB2ZWN0b3JzIHNvIGl0IHdpbGwgYWxzbyBwcm92aWRlIHRoZSBjb3JyZWN0IHNvbHV0aW9uLCB3ZSBqdXN0IHNob3VsZCBsb29rIHRvIHRoZSBvdGhlciBzZXQgb2Ygc2luZ3VsYXIgdmVjdG9ycyBpbiBvcmRlciB0byBnZXQgdG8gdGhlIGNvcnJlY3QgaW50ZXJwcmV0YXRpb24uCgojIyBNb3RpdmF0aW9uCgpUaGUgb2JqZWN0aXZlIG9mIE11bHRpZGltZW5zaW9uYWwgU2NhbGluZyAoTURTKSBpcyB0byBmaW5kIGEgbG93LWRpbWVuc2lvbmFsIHJlcHJlc2VudGF0aW9uLCBzYXkgJGskLWRpbWVuc2lvbmFsLCBvZiAkbiQgZGF0YSBwb2ludHMgc3VjaCB0aGF0IHRoZSBkaXN0YW5jZXMgYmV0d2VlbiB0aGUgJG4kIHBvaW50cyBpbiB0aGUgJGskLWRpbWVuc2lvbmFsIHNwYWNlIGlzIGEgZ29vZCBhcHByb3hpbWF0aW9uIG9mIGEgZ2l2ZW4gc3F1YXJlZCBkaXN0YW5jZSBtYXRyaXgsIHNheSAkXG1hdGhiZntEfV9YJC4KCi0gVGhlIHNxdWFyZWQgZGlzdGFuY2UgbWF0cml4ICRcbWF0aGJme0R9X1gkIG1heSBiZSBnaXZlbiB3aXRob3V0IGtub3dsZWRnZSBvZiB0aGUgb3JpZ2luYWwgb2JzZXJ2YXRpb25zIChub3QgZXZlbiB0aGUgZGltZW5zaW9uYWxpdHkpLCBvciBpdCBtYXkgYmUgY29tcHV0ZWQgZnJvbSBhIGdpdmVuIHNldCBvZiAkbiQgJHAkLWRpbWVuc2lvbmFsIGRhdGEgcG9pbnRzLgoKLSBOb3RlIHRoYXQgdGhlIGRpc3RhbmNlcyBiZXR3ZWVuIHBvaW50cyBpbiBhICRrJC1kaW1lbnNpb25hbCBzdWJzcGFjZSBjb2luY2lkZSB3aXRoIHRoZSBkaXN0YW5jZXMgYmV0d2VlbiB0aGVzZSBwb2ludHMgaW4gdGhlIGxhcmdlciAkcCQtZGltZW5zaW9uYWwgc3BhY2UuCgpVc2Ugb2YgTURTOgoKLSBBIGhpZ2ggZGltZW5zaW9uYWwgZGF0YSBtYXRyaXggJFxtYXRoYmZ7WH0kIGlzIGdpdmVuLCBhbmQgb25lIHdhbnRzIHRvIGdldCBhIHZpc3VhbCByZXByZXNlbnRhdGlvbiAoaW4gMiBvciAzIGRpbWVuc2lvbnMpIG9mIHRoZSBvYnNlcnZhdGlvbnMuIEluIHRoaXMgZ3JhcGggZWFjaCBwb2ludCByZXByZXNlbnRzIGFuIG9ic2VydmF0aW9uIChyb3cgb2YgZGF0YSBtYXRyaXgpLiBGcm9tIHRoaXMgZ3JhcGggb25lIHdhbnRzIHBvaW50cyBjbG9zZSB0byBvbmUgYW5vdGhlciB0byBiZSBzaW1pbGFyLCBhbmQgb2JzZXJ2YXRpb25zIGZhciBhd2F5IGZyb20gb25lIGFub3RoZXIgdG8gYmUgZGlzc2ltaWxhci4gVGh1cyB0aGUgZGlzdGFuY2VzIGJldHdlZW4gdGhlICRuJCBwb2ludHMgaW4gdGhlIG9yaWdpbmFsICRwJC1kaW1lbnNpb25hbCBzcGFjZSBzaG91bGQgYmUgd2VsbCBwcmVzZXJ2ZWQgaW4gdGhlIDIgb3IgMyBkaW1lbnNpb25hbCBzcGFjZS4KCi0gSW4gc29tZSBhcHBsaWNhdGlvbnMgdGhlIHJlc2VhcmNoZXIgb25seSBoYXMga25vd2xlZGdlIG9mIHRoZSBzaW1pbGFyaXR5IChvciBkaXNzaW1pbGFyaXR5KSBiZXR3ZWVuIG9ic2VydmF0aW9ucy4gRm9yIGV4YW1wbGUsIGZvb2QgcHJvZHVjdHMgY2FuIGJlIGV2YWx1YXRlZCBieSBhIHRhc3RlIHBhbmVsIGFuZCBhIGRpc3NpbWlsYXJpdHkgbWF0cml4IGNhbiBiZSBjb21wbGV0ZWQuIFRoaXMgZGlzc2ltaWxhcml0eSBtYXRyaXggc2hvd3MgZm9yIGVhY2ggcGFpciBvZiBmb29kIHByb2R1Y3RzIHRoZWlyIGRpc3NpbWlsYXJpdHkgKG51bWVyaWNhbCB2YWx1ZXMgcHJvdmlkZWQgYnkgdGFzdGUgcGFuZWwsIGJ1dCBub3Qgb2JqZWN0aXZlbHkgcXVhbnRpZmllZCkuIEdpdmVuIHRoaXMgZGlzc2ltaWxhcml0eSBtYXRyaXgsIGEgMiBvciAzIGRpbWVuc2lvbmFsIGdyYXBoIGNvdWxkIGJlIGhlbHBmdWwgaWYgdGhlIGRpc3RhbmNlcyBiZXR3ZWVuIHRoZSBwb2ludHMgKGZvb2QgcHJvZHVjdHMpIGluIHRoaXMgZ3JhcGggYXJlIG1vbm90b25pY2FsbHkgcmVsYXRlZCB0byB0aGUgZGlzc2ltaWxhcml0aWVzIHByb3ZpZGVkIGJ5IHRoZSB0YXN0ZSBwYW5lbC4gRm9vZCBwcm9kdWN0cyBjbG9zZSB0byBvbmUgYW5vdGhlciBpbiB0aGlzIGdyYXBoLCB0YXN0ZSBzaW1pbGFybHkuCgotIEhlcmUgd2UgZGlzY3VzcyB0aGUgIm1ldHJpYyIgTURTLCB3aGljaCBhY3R1YWxseSByZXF1aXJlcyB0aGUgRXVjbGlkZWFuIGRpc3RhbmNlcyBiZXR3ZWVuIG9ic2VydmF0aW9ucy4gSG93ZXZlciwgdGhlIG1ldGhvZCB3b3JrcyBhbHNvIHdlbGwgaWYgdGhlIG1hdHJpeCAkXG1hdGhiZntEfV9YJCBjb250YWlucyBkaXNzaW1pbGFyaXRpZXMgcmF0aGVyIHRoYW4gc3F1YXJlZCBFdWNsaWRlYW4gZGlzdGFuY2VzLgoKCi0tLQoKSW4gdGhpcyBjaGFwdGVyIHdlIGFzc3VtZSB0aGF0IHRoZSBkYXRhIG1hdHJpeCAkXG1hdGhiZntYfSQgaXMgY29sdW1uLWNlbnRlcmVkIChpLmUuIGVhY2ggY29sdW1uIGhhcyBtZWFuIHplcm8pLgoKQ2VudGVyaW5nIGNhbiBiZSBhY2NvbXBsaXNoZWQgYnkgbXVsdGlwbHlpbmcgdGhlIG9yaWdpbmFsIGRhdGEgbWF0cml4ICRcbWF0aGJme1h9JCB3aXRoIHRoZSAkbiBcdGltZXMgbiQgJFxtYXRoYmZ7Y2VudGVyaW5nXCBtYXRyaXh9JAogXFsKICAgXG1hdGhiZntIfSA9IFxtYXRoYmZ7SX0gLSBcZnJhY3sxfXtufSBcbWF0aGJmezF9XG1hdGhiZnsxfV5UICwKIFxdCiBpbiB3aGljaCAkXG1hdGhiZnsxfSQgaXMgYW4gJG4kLXZlY3RvciB3aXRoIGFsbCBlbnRyaWVzIGVxdWFsIHRvIDEuCiBIZW5jZSwgJFxtYXRoYmZ7SH1cbWF0aGJme1h9JCBpcyB0aGUgY29sdW1uLWNlbnRlcmVkIGRhdGEgbWF0cml4LgoKV2Ugd2lsbCBhc3N1bWUgdGhhdCAkXG1hdGhiZntYfSQgaXMgYWxyZWFkeSBjb2x1bW4tY2VudGVyZWQsIGFuZCB0aGVyZWZvcmUgJFxtYXRoYmZ7SH1cbWF0aGJme1h9PVxtYXRoYmZ7WH0kLgoKKE5vdGUsIHRoYXQgdGhlIG1hdHJpeCAkXG1hdGhiZnsxfVxtYXRoYmZ7MX1eVCQgaXMgYW4gJG5cdGltZXMgbiQgbWF0cml4IHdpdGggYWxsIGVudHJpZXMgc2V0IHRvIDEuCikKCi0tLQoKV2Ugd2lsbCBuZWVkIHRoZSBmb2xsb3dpbmcgaW50ZXJlc3RpbmcgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlICoqR3JhbSBtYXRyaXgqKiAkXG1hdGhiZntYfVxtYXRoYmZ7WH1eVCQgYW5kIHRoZSBkaXN0YW5jZSBtYXRyaXguCgotIEZvciBhbiAkblx0aW1lcyBwJCBkYXRhIG1hdHJpeCAkXG1hdGhiZntYfSQsIHRoZSBtYXRyaXggJFxtYXRoYmZ7RH1fWCQgd2l0aCBzcXVhcmVkIGRpc3RhbmNlcyBoYXMgZWxlbWVudHMKICAgXFsKICAgICAgKFxtYXRoYmZ7eH1fe2l9LVxtYXRoYmZ7eH1fe2p9KV5UKFxtYXRoYmZ7eH1fe2l9LVxtYXRoYmZ7eH1fe2p9KSA9IFxWZXJ0IFxtYXRoYmZ7eH1faVxWZXJ0XjIgLSAyIFxtYXRoYmZ7eH1eVF9pXG1hdGhiZnt4fV9qICsgXFZlcnQgXG1hdGhiZnt4fV9qXFZlcnQuXjIKICAgXF0KCgotVGhlICRuXHRpbWVzIG4kIHNxdWFyZWQgZGlzdGFuY2UgbWF0cml4IGNhbiB0aGVuIGJlIHdyaXR0ZW4gYXMKICAgXFsKICAgICBcbWF0aGJme0R9X1ggPSBcbWF0aGJme059IC0gMlxtYXRoYmZ7WH1cbWF0aGJme1h9XlQgKyBcbWF0aGJme059XlQgLAogICBcXQogICB3aXRoICRcbWF0aGJme059JCB0aGUgJG4gXHRpbWVzIG4kIG1hdHJpeCB3aXRoICRpJHRoIHJvdyBmaWxsZWQgd2l0aCAkXFZlcnQgXG1hdGhiZnt4fV9pXFZlcnReMiQuCgotIE5vdGUgdGhhdCB0aGUgZWxlbWVudHMgb2YgJFxtYXRoYmZ7Tn0kIGNhbiBhbHNvIGJlIGZvdW5kIG9uIHRoZSBkaWFnb25hbCBvZiAkXG1hdGhiZntYWH1eVCQuCgotIEdpdmVuIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlICRcbWF0aGJme0h9JCBhbmQgJFxtYXRoYmZ7Tn0kIG1hdHJpY2VzLCBpdCBpcyBlYXN5IHRvIHZlcmlmeSB0aGF0CiAgIFxbCiAgICAgIC1cZnJhY3sxfXsyfVxtYXRoYmZ7SH1cbWF0aGJme0R9X1hcbWF0aGJme0h9ID0gXG1hdGhiZntYfVxtYXRoYmZ7WH1eVC4KICAgXF0gVGhpcyBnaXZlcyBhbiBpbXBvcnRhbnQgcmVsYXRpb24gYmV0d2VlbiB0aGUgZGlzdGFuY2UgbWF0cml4IGFuZCB0aGUgR3JhbSBtYXRyaXguIFdlIHdpbGwgdXNlIHRoZSBub3RhdGlvbiAkXG1hdGhiZntHfV9YID0gLVxmcmFjezF9ezJ9XG1hdGhiZntIfVxtYXRoYmZ7RH1fWFxtYXRoYmZ7SH0kLgoKLSAgVGhlICRuIFx0aW1lcyBuJCBHcmFtIG1hdHJpeCAkXG1hdGhiZntYfVxtYXRoYmZ7WH1eVCQgIGVxdWFscwpcWwogIFxiZWdpbntwbWF0cml4fQogICBcbWF0aGJme3h9XzFeVCBcXAogICBcdmRvdHMgIFxcCiAgIFxtYXRoYmZ7eH1fbl5UCiAgXGVuZHtwbWF0cml4fQogIFxiZWdpbntwbWF0cml4fQogICAgXG1hdGhiZnt4fV8xIFxsZG90cyBcbWF0aGJme3h9X24KICBcZW5ke3BtYXRyaXh9ClxdCmFuZApoYXMgdGh1cyBvbiBpdHMgJChpLGopJHRoIHBvc2l0aW9uIHRoZSBpbm5lciBwcm9kdWN0ClxbCiAgIFxtYXRoYmZ7eH1faV5UXG1hdGhiZnt4fV9qPSBcVmVydCBcbWF0aGJme3h9X2lcVmVydFxWZXJ0IFxtYXRoYmZ7eH1falxWZXJ0IFxjb3M8XG1hdGhiZnt4fV9pLFxtYXRoYmZ7eH1faj4gLgpcXQoKLSBPbiB0aGUgZGlhZ29uYWwgd2UgZmluZCAkXG1hdGhiZnt4fV9pXlRcbWF0aGJme3h9X2k9XFZlcnQgXG1hdGhiZnt4fV9pXFZlcnReMiQsIHRoZSBzcXVhcmVkIG5vcm0gb2YgdGhlICRpJHRoIG9ic2VydmF0aW9uLgoKLSBUaGUgcmVsYXRpb24gJC1cZnJhY3sxfXsyfVxtYXRoYmZ7SH1cbWF0aGJme0R9X1hcbWF0aGJme0h9ID0gXG1hdGhiZntYfVxtYXRoYmZ7WH1eVCQgdGVsbHMgdXMgdGhhdCB0aGUgZGlzdGFuY2UgbWF0cml4IGFuZCB0aGUgR3JhbSBtYXRyaXggY29udGFpbiB0aGUgc2FtZSBpbmZvcm1hdGlvbi4gVGhlIGRpc3RhbmNlcywgaG93ZXZlciwgYXJlIGluIG1vc3Qgc2l0dWF0aW9ucyBlYXNpZXIgdG8gaW50ZXJwcmV0LgoKLS0tCgojIyBMaW5rIHdpdGggdGhlIFNWRAoKTm90ZSwgdGhhdCB3ZSBoYXZlIHNob3duIHRoYXQgd2UgY2FuIHJld3JpdGUgJFxtYXRoYmZ7WH1cbWF0aGJme1h9XlQkIHVzaW5nIHRoZSBTVkQ6CgpcWwpcbWF0aGJme1h9XG1hdGhiZntYfV5UID0gXG1hdGhiZntVfVxib2xkc3ltYm9se1xEZWx0YX1eMlxtYXRoYmZ7VX1eVApcXQoKQmVjYXVzZSB0aGUgdHJ1bmNhdGVkIFNWRCBvZiAkXG1hdGhiZntYfSQgbWluaW1pc2VzIHRoZSBGcm9iZW5pdXMgbm9ybSAkXFZlcnQgXG1hdGhiZntYfS1cbWF0aGJme1h9X2tcVmVydF9GXjIkLCBpdCB0aGlzIGhhcyBhbiBpbXBvcnRhbnQgY29uc2VxdWVuY2U6CgotIExldCAkXG1hdGhiZntEfV9YJCBkZW5vdGUgdGhlICRuIFx0aW1lcyBuJCBtYXRyaXggd2l0aCB0aGUgc3F1YXJlZCBFdWNsaWRpYW4gZGlzdGFuY2VzIGJldHdlZW4gdGhlICRuJCBkYXRhIHBvaW50cyAkXG1hdGhiZnt4fV9pJCBpbiB0aGUgb3JpZ2luYWwgJHAkLWRpbWVuc2lvbmFsIHNwYWNlLgoKLSBMZXQgJFxtYXRoYmZ7RH1fe1prfSQgZGVub3RlIHRoZSAkbiBcdGltZXMgbiQgbWF0cml4IHdpdGggdGhlIHNxdWFyZWQgRXVjbGlkaWFuIGRpc3RhbmNlcyBiZXR3ZWVuIHRoZSAkbiQgdHJhbnNmb3JtZWQgZGF0YSBwb2ludHMgJFxtYXRoYmZ7en1fe2ssaX0kIGluIHRoZSByZWR1Y2VkICRrJC1kaW1lbnNpb25hbCBzcGFjZS4KCiBUaGVuLCBpdCBjYW4gYmUgc2hvd24gdGhhdCB0aGUgdHJ1bmNhdGVkIFNWRCBhbHNvIG1pbmltaXNlcwogXFsKICAgXFZlcnQgXG1hdGhiZntEfV9YIC0gXG1hdGhiZntEfV97Wmt9IFxWZXJ0X0ZeMi4KIFxdCgpPciBpbiBvdGhlciB3b3JkczogVGhlIG4gcG9pbnRzICRcbWF0aGJme3p9X3trLGl9JCBpbiB0aGUgay1kaW1lbnNpb25hbCBzdWJzcGFjZSBzcGFubmVkIGJ5IHRoZSBjb2x1bW5zIG9mICRcbWF0aGJme1Z9X2skIGFyZSB0aGUgYmVzdCBhcHByb3hpbWF0aW9uIG9mIHRoZSAkbiQgb3JpZ2luYWwgcG9pbnRzICRcbWF0aGJme3h9X2kkIGluIHRoZSBvcmlnaW5hbCBwLWRpbWVuc2lvbmFsIHNwYWNlIGluIHRoZSBzZW5zZSB0aGF0IHRoZSBFdWNsaWRlYW4gZGlzdGFuY2VzIGJldHdlZW4gdGhlIG4gb3JpZ2luYWwgcG9pbnRzIGFyZSBiZXN0IGFwcHJveGltYXRlZCBieSB0aGUgRXVjbGlkZWFuIGRpc3RhbmNlcyBiZXR3ZWVuIHRoZSBuIHRyYW5zZm9ybWVkIHBvaW50cywgYW1vbmcgYWxsIHBvc3NpYmxlIGstZGltZW5zaW9uYWwgbGluZWFyIHN1YnNwYWNlcy4KCi0tLQoKLSBJZiAkXG1hdGhiZntYfSQgaXMga25vd24sIHdlIGNhbiByZWFkaWx5IG9idGFpbiB0aGUgay1kaW1lbnNpb25hbCBwcm9qZWN0aW9uIGJ5IHRoZSBTVkQgb2YgJFxtYXRoYmZ7WH0kCgotIElmIHdlIG9ubHkga25vdyB0aGUgZGlzdGFuY2UgbWF0cml4ICRcbWF0aGJme0R9JAoKICAgIDEuIFdlIGNhbiByZWFkaWx5IG9idGFpbiB0aGUgR3JhbSBtYXRyaXgKICAgIFxbLVxmcmFjezF9ezJ9XG1hdGhiZntIfVxtYXRoYmZ7RH1fWFxtYXRoYmZ7SH0gPSBcbWF0aGJme1h9XG1hdGhiZntYfV5UXF0KICAgIDIuIFRoZSB0cnVuY2F0ZWQgU1ZEIG9mIHRoZSBzcXVhcmVkIGFuZCBzeW1tZXRyaWMgR3JhbSBtYXRyaXgKICAgIFxbXG1hdGhiZntVfV9rXGJvbGRzeW1ib2x7XERlbHRhfV9rXlxwcmltZVxtYXRoYmZ7VX1fa15UXF0KICAgIDMuIGZyb20gd2hpY2ggYWxzbyBjYW4gb2J0YWluCiAgICBcW1xtYXRoYmZ7Wn1faz1cbWF0aGJme1V9X2tcYm9sZHN5bWJvbHtcRGVsdGF9X2ssXF0gd2l0aCAkXGJvbGRzeW1ib2x7XERlbHRhfV9rPSBcbWF0aGJme1xEZWx0YX1fa157XHByaW1lIFxmcmFjezF9ezJ9fSQKCiMjIEV4YW1wbGUKCmBgYHtyfQpYIDwtIGFzLm1hdHJpeCh0KHVrWywtMV0pKQpuIDwtIG5yb3coWCkKSCA8LSBkaWFnKG4pIC0gbWF0cml4KDEvbixucm93PW4sbmNvbD1uKQpYIDwtIEglKiVYCnN2ZFVrIDwtIHN2ZChYKQoKayA8LSAyClVrIDwtIHN2ZFVrJHVbLDE6a10KRGsgPC0gZGlhZyhzdmRVayRkWzE6a10pClprIDwtIFVrJSolRGsKcm93bmFtZXMoWmspIDwtIGNvbG5hbWVzKHVrKVstMV0KY29sbmFtZXMoWmspIDwtIHBhc3RlMCgiWiIsMTprKQpaawoKWmsgJT4lCiAgYXMuZGF0YS5mcmFtZSAlPiUKICBnZ3Bsb3QoYWVzKHg9WjEseT1aMixsYWJlbD1yb3duYW1lcyhaaykpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMykgKyAgCiAgZ2VvbV90ZXh0KG51ZGdlX3ggPSA1MCkgIApgYGAKCi0gVGhlIGdyYXBoIHN1Z2dlc3RzIHRoYXQgV2FsZXMgYW5kIEVuZ2xhbmQgYXJlIHF1aXRlIHNpbWlsYXIgaW4gdGVybXMgb2YgZm9vZCBjb25zdW1wdGlvbiwgYW5kIE5vcnRoIElyZWxhbmQgc2VlbSB0byBoYXZlIGEgdmVyeSBkaWZmZXJlbnQgZm9vZCBjb25zdW1wdGlvbiBwYXR0ZXJuLgoKLSBBbHNvIG5vdGUgdGhhdCBFbmdsYW5kIGlzIGNsb3NlIHRvIHRoZSBvcmlnaW4sIG1lYW5pbmcgdGhhdCB0aGUgZm9vZCBjb25zdW1wdGlvbiBwYXR0ZXJuIGlzIGNsb3NlIHRvIHRoZSBhdmVyYWdlIHBhdHRlcm4gaW4gdGhlIFVLLgoKLSBBIGZldyBxdWVzdGlvbnMgcmVtYWluOgoKICAgIC0gSG93IHdlbGwgY2FuIHRoZSAxNy1kaW1lbnNpb25hbCBkYXRhIGJlIHJlcHJlc2VudGVkIGluIGEgMi1kaW1lbnNpb25hbCBzdWJzcGFjZT8KICAgIC0gSG93IGNhbiB3ZSBpbnRlcnByZXQgdGhlIGRpc3RhbmNlcyAoZGlmZmVyZW5jZXMpIGJldHdlZW4gdGhlIGRhdGEgcG9pbnRzIGluIHRlcm1zIG9mIHRoZSBvcmlnaW5hbCAxNyB2YXJpYWJsZXM/CgojIyBUaGUgYmlwbG90CgpUaGUgYmlwbG90IGlzIGEgc2luZ2xlIDItZGltZW5zaW9uYWwgZ3JhcGggd2hpY2ggZGlzcGxheXMgdGhlIGluZm9ybWF0aW9uIGluIGJvdGggJFxtYXRoYmZ7Wn1fMj1cbWF0aGJme1V9XzJcYm9sZHN5bWJvbHtcRGVsdGF9XzIkIGFuZCAkXG1hdGhiZntWfV8yJC4KClRoZSBwbG90IG9mICRcbWF0aGJme1p9XzIkIGhhcyBiZWVuIGRpc2N1c3NlZCBwcmV2aW91c2x5IChlLmcuIGJlc3QgYXBwcm94aW1hdGlvbiBvZiBkaXN0YW5jZXMpLgoKVGhlIG5hbWUgImJpInBsb3QgcmVmZXJzIHRvIHRoZSBwbG90dGluZyBvZiB0d28gcGFydHMgb2YgdGhlIFNWRCAoJFxtYXRoYmZ7Wn0kIGFuZCAkXG1hdGhiZntWfSQpIGluIGEgc2luZ2xlIGdyYXBoLgoKRnJvbSB0aGUgZ2VvbWV0cmljYWwgaW50ZXJwcmV0YXRpb24gb2YgdGhlIFNWRCB3ZSBrbm93IHRoYXQKJFxtYXRoYmZ7Wn1fMiQgaXMgdGhlIG9ydGhvZ29uYWwgcHJvamVjdGlvbiBvZiAkXG1hdGhiZntYfSQgb24gJFxtYXRoYmZ7Vn1fMiQgdGhlIGJhc2lzIHNwYW5uZWQgYnkgdGhlIGZpcnN0IHR3byBzaW5ndWxhciB2YWx1ZXMuClRodXMgZm9yIHRoZSAkaV5cdGV4dHt0aH0kIGluZGl2aWR1YWwgd2UgZ2V0ClxbClxtYXRoYmZ7en1fezIsaX09IFxtYXRoYmZ7eH1faSBcbGVmdFtcYmVnaW57YXJyYXl9e2NjfVxtYXRoYmZ7dn1fMSZcbWF0aGJme3Z9XzJcZW5ke2FycmF5fVxyaWdodF0KXF0KCldlIGFsc28ga25vdyB0aGF0IHdlIGNhbiBhcHByb3hpbWF0ZSAkXG1hdGhiZntYfSQgYnkgJFxtYXRoYmZ7WH1fMiQgd2hpY2ggZXF1YWxzOgoKXGJlZ2lue2FsaWdufQogIFxtYXRoYmZ7WH1fMiAmPSBcbWF0aGJme1pfMn1cbWF0aGJme1Z9XzJeVFxcCiAgJj0gXG1hdGhiZntaXzJ9XGxlZnRbXGJlZ2lue2FycmF5fXtjfSBcbWF0aGJme3Z9XzFeVFxcCiAgICAgIFxtYXRoYmZ7dn1fMl5UXGVuZHthcnJheX1ccmlnaHRdCiAgPSBcbWF0aGJme1pfMn0KICAgIFxsZWZ0WwogICAgICBcYmVnaW57YXJyYXl9e2NjY31cdGlsZGV7XG1hdGhiZnt2fX1fezIsMX0gXGxkb3RzIFx0aWxkZXtcbWF0aGJme3Z9fV97MixwfVxlbmR7YXJyYXl9CiAgICBccmlnaHRdClxlbmR7YWxpZ259Cgp3aXRoICRcbWF0aGJme3Z9XzEkIGFuZCAkXG1hdGhiZnt2fV8yJCB0aGUgZmlyc3QgdHdvIHJpZ2h0IHNpbmd1bGFyIHZlY3RvciBhbmQgJFx0aWxkZXtcbWF0aGJme3Z9fV9qJCB0aGUgaiReXHRleHR7dGh9JCBjb2x1bW4gb2YgdGhlIG1hdHJpeCAkXG1hdGhiZntWfV8yXlQkLgoKLSBUaGlzIGJhc2ljYWxseSBzaG93cyB1cyB0aGF0IHRoZSBvcnRob2dvbmFsIHByb2plY3Rpb24gb2YgJFxtYXRoYmZ7en1fezIsaX0kIGZvciBzdWJqZWN0L2V4cGVyaW1lbnRhbCB1bml0IGkgb24gJFx0aWxkZXtcbWF0aGJme3Z9fV97MixqfSQgZ2l2ZXMgdXMgdGhlIGFwcHJveGltYXRpb24gb2YgdGhlIHZhbHVlIGZvciAgaiReXHRleHR7dGh9JCB2YXJpYWJsZSB0aGF0IHdhcyBvYnNlcnZlZCBmb3IgdGhlICRpXlx0ZXh0e3RofSQgZXhwZXJpbWVudGFsIHVuaXQsIGkuZS4gJHhfezIsaWp9JC4KXFt4X3syLGlqfT1cbWF0aGJme3p9X3syLGl9XlRcdGlsZGV7XG1hdGhiZnt2fX1fezIsan1cXQoKIyMjIFVLICBleGFtcGxlCgojIyMjIEJpcGxvdAoKYGBge3J9ClZrPC1zdmRVayR2WywxOmtdCnJvd25hbWVzKFZrKSA8LSB1ayAlPiUgcHVsbChYMSkKY29sbmFtZXMoVmspIDwtIGNvbG5hbWVzKFprKQpzY2FsZUZhY3RvciA8LSBtZWFuKHN2ZFVrJGRbMTprXSkKCmRpeUJpcGxvdCA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludCgKICAgIGRhdGE9WmsgJT4lIGFzLmRhdGEuZnJhbWUsCiAgICBhZXMoeD1aMSx5PVoyKSwKICAgIHNpemUgPSAzKSArICAKICBnZW9tX3RleHQoCiAgICBkYXRhPVprICU+JSBhcy5kYXRhLmZyYW1lLAogICAgYWVzKHg9WjEseT1aMixsYWJlbD1yb3duYW1lcyhaaykpLAogICAgbnVkZ2VfeCA9IDUwKSArCiAgZ2VvbV9zZWdtZW50KAogICAgZGF0YT1WayAlPiUgYXMuZGF0YS5mcmFtZSwKICAgIGFlcyh4PTAsIHk9MCwgeGVuZD1aMSpzY2FsZUZhY3RvciwgeWVuZD1aMipzY2FsZUZhY3RvciksCiAgICBhcnJvdz1hcnJvdyhsZW5ndGg9dW5pdCgwLjQsImNtIikpLAogICAgYWxwaGE9MC4yNSkgKwogIGdlb21fdGV4dCgKICAgIGRhdGE9VmsgJT4lIGFzLmRhdGEuZnJhbWUsCiAgICBhZXMoeD1aMSpzY2FsZUZhY3RvciwgeT1aMipzY2FsZUZhY3RvciwgbGFiZWw9cm93bmFtZXMoVmspKSwKICAgIGFscGhhPTAuNSwKICAgIHNpemU9MykKCmRpeUJpcGxvdAoKYmlwbG90KFprLFZrKQpgYGAKClRoZSBSIGNvZGUgc2hvd3MgdHdvIHdheXMgb2YgY29uc3RydWN0aW5nIGEgYmlwbG90OiB0aGUgZmlyc3QgbWV0aG9kIHN0YXJ0cyBmcm9tIHRoZSBTVkQgb2YgJFxtYXRoYmZ7WH0kIGFuZCBwbG90cyB0aGUgcCB2ZWN0b3JzICRcdGlsZGV7XG1hdGhiZnt2fX1fezIsan0kIGFzIGFycm93cy4gTm90ZSB0aGF0IHdlIHVzZWQgYSBzY2FsaW5nIGZhY3RvciB0byBnaXZlIHRoZSBhcnJvd3MgYSBjb252ZW5pZW50IGxlbmd0aCBpbiB0aGUgZ3JhcGggKG5vdCB0b28gc21hbGwsIG5vdCB0b28gbGFyZ2UpOyBpdCBkb2VzIG5vdCBhZmZlY3QgdGhlIGludGVycHJldGF0aW9uLiBUaGUgc2Vjb25kIHdheSBvZiBvYnRhaW5pbmcgdGhlIGJpcGxvdCBpcyBzaW1wbHkgYnkgdXNpbmcgdGhlIFIgYnVpbHQtaW4gZnVuY3Rpb24gYmlwbG90LgoKIyMjIyBJbGx1c3RyYXRpb24gb2YgcHJvamVjdGlvbgoKV2UgcHJvamVjdCAkXG1hdGhiZnt6fV97MixpfSQgZm9yIE4uSXJlbGFuZCBvbiBGcmVzaF9wb3RhdG9lcy4KCmBgYHtyIGVjaG89RkFMU0V9CmdncGxvdCgpICsKICBnZW9tX3BvaW50KAogICAgZGF0YT1aayAlPiUgYXMuZGF0YS5mcmFtZSwKICAgIGFlcyh4PVoxLHk9WjIpLAogICAgc2l6ZSA9IDMpICsgIAogIGdlb21fdGV4dCgKICAgIGRhdGE9WmsgJT4lIGFzLmRhdGEuZnJhbWUsCiAgICBhZXMoeD1aMSx5PVoyLGxhYmVsPXJvd25hbWVzKFprKSksCiAgICBudWRnZV94ID0gNTApICsKICBnZW9tX3NlZ21lbnQoCiAgICBkYXRhPXJiaW5kKFZrWyJGcmVzaF9wb3RhdG9lcyIsXSpzY2FsZUZhY3Rvcixaa1siTi5JcmVsYW5kIixdKSAlPiUgYXMuZGF0YS5mcmFtZSwKICAgIGFlcyh4PTAsIHk9MCwgeGVuZD1aMSwgeWVuZD1aMiksCiAgICBhcnJvdz1hcnJvdyhsZW5ndGg9dW5pdCgwLjQsImNtIikpLAogICAgYWxwaGE9MC4yNSkgKwogICAgZ2VvbV9zZWdtZW50KAogICAgICBhZXMoeD1aa1siTi5JcmVsYW5kIiwxXSwKICAgICAgICB5PVprWyJOLklyZWxhbmQiLDJdLAogICAgICAgIHhlbmQ9VmtbIkZyZXNoX3BvdGF0b2VzIixdJSolWmtbIk4uSXJlbGFuZCIsXSpWa1siRnJlc2hfcG90YXRvZXMiLDFdLAogICAgICAgIHllbmQ9VmtbIkZyZXNoX3BvdGF0b2VzIixdJSolWmtbIk4uSXJlbGFuZCIsXSpWa1siRnJlc2hfcG90YXRvZXMiLDJdCiAgICAgICAgKSwKICAgICAgICBsdHk9MikgKwogICAgZ2VvbV90ZXh0KAogICAgICBhZXMoeD1Wa1siRnJlc2hfcG90YXRvZXMiLDFdKnNjYWxlRmFjdG9yLCB5PVZrWyJGcmVzaF9wb3RhdG9lcyIsMl0qc2NhbGVGYWN0b3IsIGxhYmVsPSJGcmVzaF9wb3RhdG9lcyIpLAogICAgICBhbHBoYT0wLjUsCiAgICAgIHNpemU9MykKYGBgCgoKYGBge3J9CnJvd25hbWVzKFgpIDwtIHJvd25hbWVzKFprKQpjb2xuYW1lcyhYKSA8LSByb3duYW1lcyhWaykKWFssIkZyZXNoX3BvdGF0b2VzIl0KWmtbIk4uSXJlbGFuZCIsXSUqJVZrWyJGcmVzaF9wb3RhdG9lcyIsXQpgYGAKCldlIG9ic2VydmUgdGhhdCBOLiBJcmVsYW5kIGhhcyBhIGNvbnN1bXB0aW9uIGluIEZyZXNoX3BvdGF0b2VzIHRoYXQgaXMgYHIgWFsiTi5JcmVsYW5kIiwiRnJlc2hfcG90YXRvZXMiXWAgYWJvdmUgdGhlIGF2ZXJhZ2UgYW5kIHRoaXMgaXMgd2VsbCBhcHByb3hpbWF0ZWQgYnkgdGhlIHByb2plY3Rpb24gYHIgcm91bmQoWmtbIk4uSXJlbGFuZCIsXSUqJVZrWyJGcmVzaF9wb3RhdG9lcyIsXSwyKQpgLgoKQWxzbyBub3RlIHRoYXQKCi0gVGhlIG9yaWdpbiBjb3JyZXNwb25kcyB0byB0aGUgc2FtcGxlIGF2ZXJhZ2Ugb2YgdGhlIDE3LWRpbWVuc2lvbmFsIG9ic2VydmF0aW9ucyBpbiB0aGUgZGF0YSBtYXRyaXgKCi0gRW5nbGFuZCBpcyBjbG9zZSB0byB0aGUgb3JpZ2luIGFuZCB0aHVzIG9uZSBjb3VsZCBzYXkgdGhhdCB0aGUgZm9vZCBjb25zdW1wdGlvbiBpbiBFbmdsYW5kIGlzIGFzIHRoZSBhdmVyYWdlIGluIHRoZSBVSwoKLSBQcm9qZWN0aW5nIHRoZSAkXG1hdGhiZntafV8yJCBjb29yZGluYXRlcyBmb3IgTm9ydGggSXJlbGFuZCBvcnRob2dvbmFsbHkgb250byB0aGUgdmVjdG9yICRcdGlsZGV7XG1hdGhiZnt2fX1fezIsan0kIG9mIGZyZXNoIHBvdGF0b2VzLCB3ZSBnZXQgYSBsYXJnZSBhbmQgcG9zaXRpdmUgJHhfezIsaWp9JC4gSGVuY2UsIHBlb3BsZSBpbiBOLiBJcmVsYW5kIHRlbmQgdG8gZWF0IG1vcmUgZnJlc2ggcG90YXRvZXMgdGhhbiBvbiBhdmVyYWdlIGluIHRoZSBVSy4KCi0gV2UgY2FuIGRvIHRoZSBzYW1lIGZvciB0aGUgb3RoZXIgdmVjdG9ycy4KCiMjIyBGdXJ0aGVyIGludGVycHJldGF0aW9uIG9mIHRoZSBCaXBsb3QKCkZyb20gJFxtYXRoYmZ7eH1fe2ssaX1eVCA9ICBcbWF0aGJme3p9X3trLGl9XlRcbWF0aGJme1Z9X2teVCQgd2UgbGVhcm50IHRoYXQgdGhlIG9yaWdpbmFsIGRhdGEgY2FuIGJlIChhcHByb3hpbWF0ZWx5KSByZWNvbnN0cnVjdGVkIGZyb20gdGhlICRcbWF0aGJme3p9X3trLGl9JC4KCiBXZSBub3cgc2hvdyBob3cgdGhlIGRpbWVuc2lvbnMgb2YgJFxtYXRoYmZ7Wn1fayQgKGNvbHVtbnMgb3IgdmFyaWFibGVzKSBjYW4gYmUgaW50ZXJwcmV0ZWQuIFJlbWVtYmVyLAogXFsKICBcbWF0aGJme3p9X3trLGl9XlQgPSBcbWF0aGJme3h9X2leVFxtYXRoYmZ7Vn1fayA9IFxsZWZ0KFxtYXRoYmZ7eH1faV5UIFxtYXRoYmZ7dn1fMSAsIFxtYXRoYmZ7eH1faV5UIFxtYXRoYmZ7dn1fMiAsIFxsZG90cyAsIFxtYXRoYmZ7eH1faV5UIFxtYXRoYmZ7dn1fa1xyaWdodCkuCiBcXQoKIFRoZSAkaiR0aCBjb2x1bW4gb2YgJFxtYXRoYmZ7Wn1fayQgY2FuIGJlIHdyaXR0ZW4gYXMKIFxbCiAgIFxtYXRoYmZ7en1fe2ssan0gPSBcbWF0aGJme1h9XG1hdGhiZnt2fV9qLAogXF0KCiBBbiBpbmRpdmlkdWFsIGVsZW1lbnQgb2YgJFxtYXRoYmZ7Wn1fayQgY2FuIGJlIHdyaXR0ZW4gYXMKIFxbCiAgIHpfe2ssaWp9ID0gXG1hdGhiZnt4fV9pXlRcbWF0aGJme3Z9X2osCiBcXQoKIHdoaWNoIGlzIGEgbGluZWFyIGNvbWJpbmF0aW9uIG9mIHRoZSBvYnNlcnZhdGlvbnMgb24gdGhlICRwJCB2YXJpYWJsZXMgaW4gJFxtYXRoYmZ7eH1faSQsIHdpdGggY29lZmZpY2llbnRzIGdpdmVuIGJ5IHRoZSAkcCQgZWxlbWVudHMgaW4gJFxtYXRoYmZ7dn1faiQuIFVuZGVyc3RhbmRpbmcgdGhlIGVsZW1lbnRzIGluICRcbWF0aGJme3Z9X2okIHdpbGwgZ2l2ZSB1cyBpbnNpZ2h0IGludG8gdGhlIGludGVycHJldGF0aW9uIG9mIHRoZSAkaiR0aCBkaW1lbnNpb24gb2YgJFxtYXRoYmZ7Wn1fayQuCgojIyBJbGx1c3RyYXRpb24gVWsgZm9vZAoKYGBge3J9CnAxIDwtIGdncGxvdCgpICsKICBnZW9tX2JhcigKICAgIGFlcyggeD1yb3duYW1lcyhWayksIHk9VmtbLDFdKSwKICAgIHN0YXQ9ImlkZW50aXR5IikgKwogIHhsYWIoIlYxIikgKwogIHlsYWIoImNvbnRyaWJ1dGlvbiIpICsKICBjb29yZF9mbGlwKCkKCnAyIDwtCmdncGxvdCgpICsKICBnZW9tX2JhcigKICAgIGFlcyggeD1yb3duYW1lcyhWayksIHk9VmtbLDJdKSwKICAgIHN0YXQ9ImlkZW50aXR5IikgKwogIHhsYWIoIlYyIikgKwogIHlsYWIoImNvbnRyaWJ1dGlvbiIpICsKICBjb29yZF9mbGlwKCkKCmdyaWQuYXJyYW5nZShwMSxwMixuY29sPTIpCmBgYAoKSW4gZ2l2aW5nIGFuIGludGVycHJldGF0aW9uIHRvIHRoZSBlbGVtZW50cyBpbiAkXG1hdGhiZnt2fV8xJCBhbmQgJFxtYXRoYmZ7dn1fMiQsIHdlIGlnbm9yZSB0aGUgZWxlbWVudHMgY2xvc2UgdG8gemVybyAodGhpcyBpcyBhIHN1YmplY3RpdmUgZGVjaXNpb247IGxhdGVyIHdlIHNlZSBtb3JlIG9iamVjdGl2ZSBtZXRob2RzKS4KCkZvciAkXG1hdGhiZnt2fV8xJCAoMXN0IGRpbWVuc2lvbiBvZiAkXG1hdGhiZntafV8yJCk6CgotIGNvbnRyYXN0IG9mIHNvZnQgZHJpbmtzIGFuZCBmcmVzaCBwb3RhdG9lcyB2ZXJzdXMgZnJlc2ggZnJ1aXQgYW5kIGFsY29ob2xpYyBkcmlua3MKCi0gYSBsYXJnZSB2YWx1ZSBvZiAkel97aTF9JCBjYW4gcmVzdWx0IGZyb20gZWF0aW5nIG1hbnkgZnJlc2ggcG90YXRvZXMgYW5kIGRyaW5raW5nIGEgbG90IG9mIHNvZnQgZHJpbmtzLCBidXQgZWF0aW5nIG9ubHkgZmV3IGZyZXNoIGZydWl0IGFuZCBkcmlua2luZyBub3QgbXVjaCBhbGNvaG9saWMgZHJpbmtzCgotIGEgc21hbGwgdmFsdWUgb2YgJHpfe2kxfSQgY2FuIHJlc3VsdCBmcm9tIGVhdGluZyBmZXcgZnJlc2ggcG90YXRvZXMgYW5kIGRyaW5raW5nIG9ubHkgZmV3IHNvZnQgZHJpbmtzLCBidXQgZWF0aW5nIGEgbG90IG9mIGZyZXNoIGZydWl0IGFuZCBkcmlua2luZyBtdWNoIGFsY29ob2xpYyBkcmlua3MgIAoKRm9yICRcbWF0aGJme3Z9XzIkICgybmQgZGltZW5zaW9uIG9mICRcbWF0aGJme1p9XzIkKToKCi0gY29udHJhc3Qgb2Ygc29mdCBkcmlua3MgdmVyc3VzIGZyZXNoIHBvdGF0b2VzCgotIGEgbGFyZ2UgdmFsdWUgb2YgJHpfe2kyfSQgY2FuIHJlc3VsdCBmcm9tIGVhdGluZyBtYW55IGZyZXNoIHBvdGF0b2VzLCBidXQgZHJpbmtpbmcgbm90IG11Y2ggc29mdCBkcmlua3MKCi0gYSBzbWFsbCB2YWx1ZSBvZiAkel97aTJ9JCBjYW4gcmVzdWx0IGZyb20gZWF0aW5nIGZldyBmcmVzaCBwb3RhdG9lcywgYnV0IGRyaW5raW5nIG11Y2ggc29mdCBkcmlua3MgLgoKLS0tCgpUaGUgZWxlbWVudHMgaW4gdjEgYW5kIHYyIGFyZSBhbHNvIHNob3duIGluIHRoZSBiaXBsb3QuCgpgYGB7cn0KZ3JpZC5hcnJhbmdlKHAxLCBwMiwgZGl5QmlwbG90LCBuY29sPTIsIGxheW91dF9tYXRyaXggPSByYmluZChjKDEsMiksYygzLDMpKSkKYGBgCgpGcm9tIHRoZSBncmFwaCB3ZSBzZWUgZS5nLiB0aGF0IE4uIElyZWxhbmQgaGFzIGEgaGlnaCBzY29yZSBmb3IgdGhlIGZpcnN0IGRpbWVuc2lvbi4gTm93IHRoYXQgd2UgY2FuIGdpdmUgYW4gaW50ZXJwcmV0YXRpb24gdG8gdGhlIGRpbWVuc2lvbiwgd2UgY29uY2x1ZGUgdGhhdCBpbiBOb3J0aGVybiBJcmVsYW5kIHBlb3BsZSBlYXQgcmVsYXRpdmVseSBtdWNoIGZyZXNoIHBvdGF0b2VzIGFuZCBkcmluayBtYW55IHNvZnQgZHJpbmtzLCBidXQgdGhleSBkbyBub3QgZHJpbmsgbXVjaCBhbGNvaG9saWMgZHJpbmtzIGFuZCBlYXQgbm90IG11Y2ggZnJlc2ggZnJ1aXQuIFdlIGhhZCBhbHJlYWR5IGNvbWUgdG8gdGhlc2UgZmluZGluZ3MgYnkgcHJvamVjdGluZyBOLiBJcmVsYW5kIG9uIHRoZSB2ZWN0b3JzIG9mIHRoZXNlIGZvdXIgZm9vZCBwcm9kdWN0cy4gVGhpcyBjb21lcyB0byBubyBzdXJwcmlzZTogYm90aCBpbnRlcnByZXRhdGlvbnMgYXJpc2UgZnJvbSB0aGUgc2FtZSBkYXRhIHNldCBhbmQgaXRzIFNWRCwgYW5kIHNvIG5vIGNvbnRyYWRpY3RvcnkgcmVzdWx0cyBzaG91bGQgYXJpc2UuIE90aGVyIGNvbmNsdXNpb25zIGNhbiBiZSBmb3VuZCBpbiBhIHNpbWlsYXIgZmFzaGlvbi4KCldlIGhhdmUgZGVyaXZlZCB0aGUgaW50ZXJwcmV0YXRpb24gb2YgdGhlIGZpcnN0IGRpbWVuc2lvbiBmcm9tIHRoZSBiYXJwbG90IG9mIHRoZSBlbGVtZW50cyBvZiAkXG1hdGhiZnt2fV8xJC4gSG93ZXZlciwgdGhlcmUgaXMgYWN0dWFsbHkgbm8gbmVlZCB0byBtYWtlIGEgc2VwYXJhdGUgcGxvdCB0byByZWFkIHRoZSBlbGVtZW50cyBvZiAkXG1hdGhiZnt2fV8xJDsgdGhleSBjYW4gYWxzbyBiZSByZWFkIGZyb20gcHJvamVjdGluZyB0aGUgdmVjdG9ycyBpbiB0aGUgYmlwbG90IG9udG8gdGhlIGZpcnN0IGRpbWVuc2lvbiAoaS5lLiBiYXNpcyB2ZWN0b3Igb2YgdGhlIGZpcnN0IGRpbWVuc2lvbikuIEZvciBleGFtcGxlLCBmcmVzaCBwb3RhdG9lcyBpcyBpbiB0aGUgN3RoIGNvbHVtbiBvZiAkXG1hdGhiZntYfSQgYW5kIHRodXMgaW4gdGhlIGJpcGxvdCBpdHMgdmVjdG9yIGlzICRcdGlsZGV7XG1hdGhiZnt2fX1fezIsN30kOyBpdCBpcyBhIDItZGltZW5zaW9uYWwgdmVjdG9yICgyLWRpbWVuc2lvbmFsIGJpcGxvdCBpcyBzaG93bikuIEluIHRoZSBzcGFjZSBvZiB0aGUgYmlwbG90IChpLmUuIHRoZSBjb2x1bW4gc3BhY2Ugb2YgJFxtYXRoYmZ7Wn1fMiQpLCB0aGUgZmlyc3QgYmFzaXMgdmVjdG9yIGlzIGdpdmVuIGJ5ICQoMSwwKSQuIFByb2plY3RpbmcgICRcdGlsZGV7XG1hdGhiZnt2fX1fezIsN31eVCQgb3J0aG9nb25hbGx5IG9udG8gJCgxLDApJCBnaXZlcwpcWwogIFx0aWxkZXtcbWF0aGJme3Z9fV97Miw3fV5UICBcYmVnaW57cG1hdHJpeH0gMSBcXCAwIFxlbmR7cG1hdHJpeH0gPSAodl97NzF9ICwgdl97NzJ9KSBcYmVnaW57cG1hdHJpeH0gMSBcXCAwIFxlbmR7cG1hdHJpeH0gPSB2X3s3MX0KXF0Kd2hpY2ggaXMgdGhlIHNldmVudGggZWxlbWVudCBvZiB0aGUgZmlyc3QgcmlnaHQtc2luZ3VsYXIgdmVjdG9yIG9mICRcbWF0aGJme1h9JCwgd2hpY2ggaXMgdGh1cyB0aGUgYmFyIG9mIGZyZXNoIHBvdGF0b2VzIGluIHRoZSBiYXJwbG90IG9mIHRoZSBmaXJzdCBkaW1lbnNpb24uCgojIFNWRCBhbmQgcHJpbmNpcGFsIGNvbXBvbmVudCBhbmFseXNpcyAoUENBKQoKLSBQQ0EgaXMgYmFzaWNhbGx5IGEgU1ZECgotIFBDQSBhZGRzIGFub3RoZXIgbGF5ZXIgb2YgaW50ZXJwcmV0YXRpb24KCi0gUENBIGNvbWVzIHdpdGggaXRzIG93biB0ZXJtaW5vbG9neQoKLSBPbmUgb2YgdGhlIG1vc3Qgd2lkZWx5IHVzZWQgYWxnb3JpdGhtcyBmb3IgZGltZW5zaW9uIHJlZHVjdGlvbiBhbmQgZGF0YSBleHBsb3JhdGlvbiBvZiBtdWx0aXZhcmlhdGUgYW5kIGhpZ2ggZGltZW5zaW9uYWwgZGF0YS4KCi0gSXQgaXMgbW90aXZhdGVkIGZyb20gdGhlIGRlY29tcG9zaXRpb24gb2YgdGhlIFZhcmlhbmNlIGNvdmFyaWFuY2UgbWF0cml4IG9mIHRoZSBkYXRhCgojIyBWYXJpYW5jZSBjb3ZhcmlhbmNlIG1hdHJpeAoKLSAgIEZvciBhIGdpdmVuIGNlbnRlcmVkIGRhdGEgbWF0cml4ICRcbWF0aGJme1h9JCB0aGUgJHBcdGltZXMgcCQgY292YXJpYW5jZSBtYXRyaXggY2FuIGJlIGVzdGltYXRlZCBieQogIFxbCiAgICBcYm9sZHN5bWJvbHtcU2lnbWF9X1ggPSBcZnJhY3sxfXtuLTF9XG1hdGhiZntYfV5UXG1hdGhiZntYfSA9XGZyYWN7MX17bi0xfVxzdW1fe2k9MX1ebiBcbWF0aGJme3h9X2lcbWF0aGJme3h9X2leVCwKICBcXQogIGkuZS4gdGhlICQoaSxqKSR0aCBlbGVtZW50IGlzIGdpdmVuIGJ5IChjb2x1bW4gbWVhbnMgYXJlIHplcm8pCiAgXFsKICAgIFxmcmFjezF9e24tMX1cc3VtX3ttPTF9Xm4geF97bWl9eF97bWp9PVxmcmFjezF9e24tMX1cc3VtX3ttPTF9Xm4gKHhfe21pfS1cYmFye3h9X2kpKHhfe21qfS1cYmFye3h9X2opLgogIFxdCgpOb3RlLCB0aGF0IHdoZW4gd2UgZm9yZ2V0IHRvIHdyaXRlIHRoZSBmYWN0b3IgJDEvKG4tMSkkIGFsbCB0aGUgZGVyaXZhdGlvbnMgc3RpbGwgaG9sZC4gSXQgaXMgb25seSBhIHByb3BvcnRpb25hbGl0eSBmYWN0b3IgYW5kIGl0IGRvZXMgbm90IGFmZmVjdCB0aGUgaW50ZXJwcmV0YXRpb24uCgotLS0KCiMjIENvbnZlbnRpb25hbCBkZXJpdmF0aW9uIG9mIFBDQQoKUENBIGlzIHVzdWFsbHkgaW50cm9kdWNlZCBhcyBmb2xsb3dzLgoKTGV0ClxbICAKICB5X2k9XG1hdGhiZnt4fV9pXlRcbWF0aGJme2F9ClxdCndpdGggJFxtYXRoYmZ7YX0kIGEgJHAkLXZlY3RvciBvZiBjb25zdGFudHMuIEhlbmNlICR5X2kkIGlzIGEgbGluZWFyIGNvbWJpbmF0aW9uIChvciB0cmFuc2Zvcm1hdGlvbikgb2YgJFxtYXRoYmZ7eH1faSQuCgpQQ0EgYWltcyBhdCBmaW5kaW5nICRcbWF0aGJme2F9JCBzdWNoIHRoYXQgdGhlIHNhbXBsZSB2YXJpYW5jZSBhbW9uZyB0aGUgJG4kICR5X2kkJ3MgaXMgbWF4aW1hbCwgd2l0aApcYmVnaW57ZXFuYXJyYXkqfQogIFxmcmFjezF9e24tMX1cc3VtX3tpPTF9Xm4gKHlfaSAtIFxiYXJ7eX0pXjIgPSAgXGZyYWN7MX17bi0xfVxzdW1fe2k9MX1ebiB5X2kgXjIgXFwKICAgICY9JiBcZnJhY3sxfXtuLTF9XHN1bV97aT0xfV5uIChcbWF0aGJme3h9X2leVFxtYXRoYmZ7YX0pXjIgPSBcZnJhY3sxfXtuLTF9IFxtYXRoYmZ7YX1eVCBcbGVmdChcc3VtX3tpPTF9Xm4gXG1hdGhiZnt4fV9pXG1hdGhiZnt4fV9pXlRccmlnaHQpIFxtYXRoYmZ7YX0gXFwKICAgICY9JiBcZnJhY3sxfXtuLTF9IFxtYXRoYmZ7YX1eVFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH1cbWF0aGJme2F9ID0gXG1hdGhiZnthfV5UXGJvbGRzeW1ib2x7XFNpZ21hfV9YXG1hdGhiZnthfQpcZW5ke2VxbmFycmF5Kn0KaW4gd2hpY2ggJFxib2xkc3ltYm9se1xTaWdtYX1fWD0gXGZyYWN7MX17bi0xfVxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0kIGlzIHRoZSBzYW1wbGUgY292YXJpYW5jZSBtYXRyaXggb2YgJFxtYXRoYmZ7WH0kLgoKIyMjIFByb2JsZW0gZm9yIG9wdGltaXNhdGlvbgoKRmluZGluZyAkXG1hdGhiZnthfSQgdGhhdCBtYXhpbWlzZXMKXFsKICBcdGV4dHt2YXJ9W3ldICA9IFxtYXRoYmZ7YX1eVFxib2xkc3ltYm9se1xTaWdtYX1fWFxtYXRoYmZ7YX0KXF0KaGFzIGEgdHJpdmlhbCBzb2x1dGlvbiAoc2V0IGFsbCBlbGVtZW50cyBvZiAkXG1hdGhiZnthfSQgZXF1YWwgdG8gJFxpbmZ0eSQpLiBUbyBhdm9pZCB0aGUgdHJpdmlhbCBzb2x1dGlvbiwgdGhlIHNvbHV0aW9uIG11c3Qgc2F0aXNmeSBhIHJlc3RyaWN0aW9uLCBlLmcuClxbCiAgXFZlcnQgXG1hdGhiZnthfVxWZXJ0XjIgPSBcbWF0aGJme2F9XlRcbWF0aGJme2F9PTEuClxdCgpUaGUgc29sdXRpb24gb2YgdGhlIGNvbnN0cmFpbmVkIG1heGltaXNhdGlvbiBwcm9ibGVtIG1heSBiZSBmb3JtdWxhdGVkIGFzClxbCiAgXG1hdGhiZnthfSA9IFx0ZXh0e0FyZ01heH1fe2I6XFZlcnQgYlxWZXJ0XjI9MX0gXG1hdGhiZntifV5UXGJvbGRzeW1ib2x7XFNpZ21hfV9YXG1hdGhiZntifS4KXF0KQnkgaW50cm9kdWNpbmcgYSBMYWdyYW5nZSBtdWx0aXBsaWVyICRcbGFtYmRhJCwgd2UgZ2V0IGFuIHVuY29uc3RyYWluZWQgbWF4aW1pc2F0aW9uIHByb2JsZW0sClxbCiAgXG1hdGhiZnthfSA9IFx0ZXh0e0FyZ01heH1fe2J9IFxsZWZ0KFxtYXRoYmZ7Yn1eVFxib2xkc3ltYm9se1xTaWdtYX1fWFxtYXRoYmZ7Yn0tXGxhbWJkYShcbWF0aGJme2J9XlRcbWF0aGJme2J9LTEpXHJpZ2h0KS4KXF0KCi0tLQoKTm90ZSB0aGF0LAoKLSBJZiB0aGUgY29uc3RyYWludCBpcyBsaW5lYXIsIHRoZW4gdGhlIGNvbnN0cmFpbmVkIG9wdGltaXNhdGlvbiBwcm9ibGVtIChoZXJlOiBtYXhpbWlzYXRpb24pIG1heSBiZSByZXBsYWNlZCBieSBhbiB1bmNvbnN0cmFpbmVkIG9wdGltaXNhdGlvbiBwcm9ibGVtIG9mIHRoZSBzYW1lIGNyaXRlcmlvbiBidXQgd2l0aCBhbiBgYHBlbmFsdHkgdGVybScnIGFkZGVkLiBUaGlzIG1ldGhvZCBpcyBkdWUgdG8gTGFncmFuZ2UuIFRoZSBwZW5hbHR5IHRlcm0gdmFuaXNoZXMgd2hlbiB0aGUgY29uc3RyYWludCBpcyBzYXRpc2ZpZWQuCgotIElmIHRoZSBjb25zdHJhaW50IGlzIHNhdGlzZmllZCwgJFxtYXRoYmZ7Yn1eVFxtYXRoYmZ7Yn09XFZlcnQgXG1hdGhiZntifVxWZXJ0XjI9MSQgYW5kIHRodXMgJFxtYXRoYmZ7Yn1eVFxtYXRoYmZ7Yn0tMT0wJCBhbmQgdGhlIGBgcGVuYWx0eScnIHRlcm0gaW4gdGhlIHVuY29uc3RyYWluZWQgY3JpdGVyaW9uIHZhbmlzaGVzLiAgIAoKLSBUaGUgTGFncmFuZ2UgbXVsdGlwbGllciAkXGxhbWJkYSQgaXMgdG8gYmUgY29uc2lkZXJlZCBhcyBhbiBleHRyYSBwYXJhbWV0ZXIgdGhhdCBtYXkgaGF2ZSB0byBiZSBlc3RpbWF0ZWQgZnJvbSB0aGUgZGF0YS4KCi0tLQoKVGhlIHNvbHV0aW9uIG9mClxbCiAgXG1hdGhiZnthfSA9IFx0ZXh0e0FyZ01heH1fe2J9IFxsZWZ0KFxtYXRoYmZ7Yn1eVFxib2xkc3ltYm9se1xTaWdtYX1fWFxtYXRoYmZ7Yn0tXGxhbWJkYShcbWF0aGJme2J9XlRcbWF0aGJme2J9LTEpXHJpZ2h0KQpcXQppcyBvYnRhaW5lZCBieSBkaWZmZXJlbnRpYXRpbmcgJFxtYXRoYmZ7Yn1eVFxib2xkc3ltYm9se1xTaWdtYX1fWFxtYXRoYmZ7Yn0tXGxhbWJkYShcbWF0aGJme2J9XlRcbWF0aGJme2J9LTEpJCB3LnIudC4gJFxtYXRoYmZ7Yn0kLCBlcXVhdGluZyBpdCB0byB6ZXJvIGFuZCBzb2x2aW5nIGZvciAkXG1hdGhiZntifSQuClxiZWdpbntlcW5hcnJheSp9CiBcZnJhY3tccGFydGlhbH17XHBhcnRpYWwgXG1hdGhiZntifX0gXGxlZnQoXG1hdGhiZntifV5UXGJvbGRzeW1ib2x7XFNpZ21hfV9YXG1hdGhiZntifS1cbGFtYmRhKFxtYXRoYmZ7Yn1eVFxtYXRoYmZ7Yn0tMSlccmlnaHQpCiAgICAmPSYgMCBcXAogICAgMlxib2xkc3ltYm9se1xTaWdtYX1fWFxtYXRoYmZ7Yn0gLTJcbGFtYmRhXG1hdGhiZntifQogICAgJj0mIDAuClxlbmR7ZXFuYXJyYXkqfQoKSGVuY2UsIHdlIG5lZWQgdGhlIHNvbHV0aW9uIG9mClxbCiAgXGJvbGRzeW1ib2x7XFNpZ21hfV9YXG1hdGhiZntifSA9IFxsYW1iZGFcbWF0aGJme2J9LgpcXQoKVGhpcyBlcXVhdGlvbiBoYXMgJHIkIHNvbHV0aW9uczoKCi0gICRcbWF0aGJme2J9PVxtYXRoYmZ7ZX1faiQ6IHRoZSAkaiR0aCBlaWdlbnZlY3RvciBvZiAkXGJvbGRzeW1ib2x7XFNpZ21hfV9YJAoKLSAgJFxsYW1iZGE9XGxhbWJkYV9qJDogdGhlICRqJHRoIGVpZ2VudmFsdWUgb2YgJFxib2xkc3ltYm9se1xTaWdtYX1fWCQuCgpUaGUgZWlnZW52ZWN0b3JzIGFyZSBvcnRob25vcm1hbCwgaS5lLgokXG1hdGhiZntlfV9pXlRcbWF0aGJme2V9X2o9MSQgaWYgJGk9aiQgYW5kICRcbWF0aGJme2V9X2leVFxtYXRoYmZ7ZX1faj0wJCBvdGhlcndpc2UuCgotLS0KCkNvbnNpZGVyIHRoZSBmb2xsb3dpbmcgY2FsY3VsYXRpb25zLCB3aXRoICRcbWF0aGJme2J9PVxtYXRoYmZ7ZX1faiQsIHRoZSAkaiR0aGUgZWlnZW52ZWN0b3Igb2YgJFxib2xkc3ltYm9se1xTaWdtYX1fWCQuClxiZWdpbntlcW5hcnJheSp9CiAgXHRleHR7dmFyfVt5XSAgCiAgICAmPSYgXG1hdGhiZntlfV9qXlRcYm9sZHN5bWJvbHtcU2lnbWF9X1hcbWF0aGJme2V9X2ogXFwKICAgICY9JiBcbWF0aGJme2V9X2peVChcYm9sZHN5bWJvbHtcU2lnbWF9X1hcbWF0aGJme2V9X2opIFxcCiAgICAmPSYgXG1hdGhiZntlfV9qXlQgXGxlZnQoXGxhbWJkYV9qIFxtYXRoYmZ7ZX1falxyaWdodCkgXFwKICAgICY9JiBcbGFtYmRhX2pcbWF0aGJme2V9X2peVFxtYXRoYmZ7ZX1faiBcXAogICAgJj0mIFxsYW1iZGFfai4KXGVuZHtlcW5hcnJheSp9CkJ5IGNvbnZlbnRpb24gdGhlIGVpZ2VudmVjdG9ycy9laWdlbnZhbHVlcyBhcmUgb3JkZXJlZCBzbyB0aGF0ClxbCiAgXGxhbWJkYV8xIFxnZXEgXGxhbWJkYV8yIFxnZXEgXGNkb3RzIFxnZXEgXGxhbWJkYV9yLgpcXQpIZW5jZSwgdGhlIGZpcnN0IGVpZ2VudmVjdG9yICRcbWF0aGJme2V9XzEkIGdpdmVzIHRoZSBsYXJnZXN0IHZhcmlhbmNlICRcbGFtYmRhXzEkLiBIZW5jZSwgdGhpcyBpcyB0aGUgc29sdXRpb24gd2Ugd2VyZSBsb29raW5nIGZvci4KCi0tLQoKV2Ugbm93IHN3aXRjaCBub3RhdGlvbiBhbmQgZnJvbSBub3cgb253YXJkcyB3ZSBkZW5vdGUgJHkkIGJ5ICR6JC4KClRoZSBvYnNlcnZhdGlvbnMgb24gdGhlIGZpcnN0IHByaW5jaXBhbCBjb21wb25lbnQgKFBDKSBhcmUgdGhlbiBnaXZlbiBieQpcWwogICAgel97aTF9ID0gXG1hdGhiZnt4fV9pXlRcbWF0aGJme2V9XzEuClxdCgpJbiBQQ0EgdGVybWlub2xvZ3kgdGhleSBhcmUgcmVmZXJyZWQgdG8gYXMgdGhlICoqc2NvcmVzKiogb2YgdGhlIGZpcnN0IFBDLiBUaGUgZWxlbWVudHMgaW4gdGhlIGVpZ2VudmVjdG9yICRcbWF0aGJme2V9X2kkIHRoYXQgbWFrZSB1cCB0aGUgdHJhbnNmb3JtYXRpb24gYXJlIGtub3duIGFzIHRoZSAqKmxvYWRpbmdzKiogIG9mIHRoZSBmaXJzdCBQQy4KClRoZSBmaXJzdCBQQyBpcyB0aHVzIGEgbmV3IHZhcmlhYmxlIChjb25zdHJ1Y3QpIHRoYXQgaGFzIHRoZSBsYXJnZXN0IHZhcmlhbmNlIGFtb25nIGFsbCBsaW5lYXIgdHJhbnNmb3JtYXRpb25zIG9mIHRoZSBvcmlnaW5hbCB2YXJpYWJsZXMuCgpWYXJpYWJpbGl0eSBiZXR3ZWVuIG9ic2VydmF0aW9ucyBpcyBjb25zaWRlcmVkIGFzIGluZm9ybWF0aXZlIHRvIHVuZGVyc3RhbmQgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgb2JzZXJ2YXRpb25zLgoKLS0tCgpUaGUgZXF1YXRpb24KXFsKICBcYm9sZHN5bWJvbHtcU2lnbWF9X1hcbWF0aGJme2J9ID0gXGxhbWJkYVxtYXRoYmZ7Yn0KXF0KaGFzICRyPVx0ZXh0e3Jhbmt9KFxib2xkc3ltYm9se1xTaWdtYX1fWCkkIHNvbHV0aW9ucy4gVGhlIGVpZ2VudmVjdG9ycyBhcmUgb3J0aG9ub3JtYWwsIGkuZS4KXFsKICBcZm9yYWxsIGlcbmVxIGo6IFxtYXRoYmZ7ZX1faV5UIFxtYXRoYmZ7ZX1faiA9IDAgXHRleHR7IGFuZCB9IGk9ajogXG1hdGhiZntlfV9pXlQgXG1hdGhiZntlfV9qID0gMQpcXQpIZW5jZSAoZm9yICRpXG5lcSBqJCkKXFsKICBcdGV4dHtjb3Z9XGxlZnRbXG1hdGhiZnt4fV5UXG1hdGhiZntlfV9pLFxtYXRoYmZ7eH1eVFxtYXRoYmZ7ZX1falxyaWdodF0gPSBcbWF0aGJme2V9X2leVFx0ZXh0e3Zhcn1bXG1hdGhiZnt4fV1cbWF0aGJme2V9X2ogPSBcbWF0aGJme2V9X2leVFxib2xkc3ltYm9se1xTaWdtYX1fWFxtYXRoYmZ7ZX1faiA9IFxtYXRoYmZ7ZX1faV5UXGxlZnQoXGxhbWJkYV9qXG1hdGhiZntlfV9qXHJpZ2h0KSA9IFxsYW1iZGFfaiBcbWF0aGJme2V9X2leVFxtYXRoYmZ7ZX1faiA9IDAKXF0KCklmICR6X3tqfT1cbWF0aGJme3h9X2leVFxtYXRoYmZ7ZX1faiQgZGVub3RlcyB0aGUgJGokdGggUEMsIHRoZW4KXFsKICBcdGV4dHtjb3Z9XGxlZnRbel9pLHpfalxyaWdodF09XHRleHR7Y292fVxsZWZ0W1xtYXRoYmZ7eH1eVFxtYXRoYmZ7ZX1faSxcbWF0aGJme3h9XlRcbWF0aGJme2V9X2pccmlnaHRdID0wIFx0ZXh0eyBpZiB9IGlcbmVxIGoKXF0KXFsKICBcdGV4dHt2YXJ9XGxlZnRbel9qXHJpZ2h0XSA9IFxsYW1iZGFfaiAuClxdCldlIHNheSB0aGF0IHRoZSAkaiR0aCBQQyBtYXhpbWlzZXMgdGhlIHZhcmlhbmNlIGFtb25nIGFsbCBsaW5lYXIgdHJhbnNmb3JtYXRpb25zIHN1Y2ggdGhhdCBpdCBpcyB1bmNvcnJlbGF0ZWQgd2l0aCB0aGUgcHJldmlvdXMgUENzLgoKLS0tCgojIyMgSW50ZXJwcmV0YXRpb24gb2YgUENBCgpBIFBDQSBpcyBhIHRyYW5zZm9ybWF0aW9uIG9mIHRoZSBvcmlnaW5hbCAkcCQgdmFyaWFibGVzIHRvICRyJCBQQ3Mgc3VjaCB0aGF0CgotIHRoZSBmaXJzdCBQQyBoYXMgbGFyZ2VzdCB2YXJpYW5jZSwgZXF1YWwgdG8gZmlyc3QgZWlnZW52YWx1ZSBvZiAkXGJvbGRzeW1ib2x7XFNpZ21hfV9YJAoKLSAgdGhlIG5leHQgUENzIGhhdmUgZGVjcmVhc2luZyB2YXJpYW5jZXMgKGRlY3JlYXNpbmcgaW5mb3JtYXRpb24gY29udGVudCkKCi0gYWxsIFBDcyBhcmUgbXV0dWFsbHkgdW5jb3JyZWxhdGVkIChubyBpbmZvcm1hdGlvbi1vdmVybGFwKS4KCi0tLQoKIyMgTGluayB3aXRoIFNWRAoKSWYgd2Ugd3JpdGUgdGhlIGVpZ2VuIHZhbHVlIGRlY29tcG9zaXRpb24gaW4gbWF0cml4IGZvcm06ClxbClxib2xkc3ltYm9se1xTaWdtYX1fWFxtYXRoYmZ7CiAgQn0gPSBcbWF0aGJme0J9XGJvbGRzeW1ib2x7XExhbWJkYX0uClxdCgp3aXRoICRcYm9sZHN5bWJvbHtcTGFtYmRhfSQgaXMgYSBkaWFnb25hbCBtYXRyaXggd2l0aCBkaWFnb25hbCBlbGVtZW50cyAkW1xsYW1iZGFfaV1fe2lpfSQuCgpXZSByZWNvZ25pc2UgYW4gZXhwcmVzc2lvbiB0aGF0IHdlIGhhdmUgc2VlbiB3aGVuIGRpc2N1c3NpbmcgdGhlIGxpbmsgYmV0d2VlbiB0aGUgU1ZEIGFuZCB0aGUgc2FtcGxlIGNvdmFyaWFuY2UgbWF0cml4CgpcYmVnaW57ZXFuYXJyYXl9ClxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH1cbWF0aGJme1Z9ICY9JlxtYXRoYmZ7Vn1cYm9sZHN5bWJvbHtcRGVsdGF9XjIKXGVuZHtlcW5hcnJheX0KCi0gU28sIHRoZSBTVkQgY2FuIGJlIHVzZWQgdG8gc29sdmUgdGhlIHNwZWN0cmFsIGRlY29tcG9zaXRpb24gKGVpZ2VuIHZhbHVlIGVpZ2VuIHZlY3RvcikgcHJvYmxlbSBhbmQgdGhlIGVpZ2VuIHZlY3RvcnMgY29pbmNpZGUgd2l0aCB0aGUgcmlnaHQgc2luZ3VsYXIgdmVjdG9ycyBhbmQgdGhlIGVpZ2VuIHZhbHVlcyBhcmUgdGhlIHNpbmd1bGFyIHZhbHVlcyBzcXVhcmVkIQoKIyMgQ29uc2VydmF0aW9uIG9mIHZhcmlhbmNlCgotIFRoZSB0b3RhbCB2YXJpYW5jZSBpbiBhIGRhdGEgc2V0ICRcbWF0aGJme1h9JCBpcyBnaXZlbiBieSB0aGUgc3VtIG9mIHRoZSB2YXJpYW5jZXMgb2YgdGhlIHZhcmlhYmxlcywKICBcWwogICAgXHNpZ21hXjJfe1x0ZXh0e3RvdH19ID0gXHN1bV97aj0xfV5wIFx0ZXh0e3Zhcn1cbGVmdFtYX2pccmlnaHRdID0gXHRleHR7dHJhY2V9KFxib2xkc3ltYm9se1xTaWdtYX1fWCkKICBcXQoKCi0gIEZvciBhIHN5bW1ldHJpYyBtYXRyaXggaXQgaG9sZHMgdGhhdAogIFxbCiAgICBcdGV4dHt0cmFjZX0oXGJvbGRzeW1ib2x7XFNpZ21hfV9YKSA9IFxzdW1fe2o9MX1eciBcbGFtYmRhX3IuCiAgXF0KCi0gIFNpbmNlICRcbGFtYmRhX3I9XHRleHR7dmFyfVxsZWZ0W1pfalxyaWdodF0kLCB3ZSBmaW5kCiAgXFsKICAgIFxzaWdtYV4yX3tcdGV4dHt0b3R9fSA9IFxzdW1fe2o9MX1ecCBcdGV4dHt2YXJ9XGxlZnRbWF9qXHJpZ2h0XT0gXHN1bV97aj0xfV5yIFx0ZXh0e3Zhcn1cbGVmdFtaX2pccmlnaHRdLgogIFxdCgokXGxvbmdyaWdodGFycm93JCAgIFRodXMgbm8gaW5mb3JtYXRpb24gaXMgbG9zdCBieSB0aGUgUENBIHRyYW5zZm9ybWF0aW9uIGZyb20gdGhlIG9yaWdpbmFsICRwJC1kaW1lbnNpb25hbCBzcGFjZSB0byB0aGUgJHIkLWRpbWVuc2lvbmFsIFBDQSBzcGFjZS4KCi0tLQoKIyMgQ2hvb3NpbmcgdGhlIG51bWJlciBvZiBkaW1lbnNpb25zCgpGb3IgUENBIHRoZSByZWFzb25pbmcgaXMgdXN1YWxseSBiYXNlZCBvbiB0aGUgcmVsYXRpdmUgdmFyaWFuY2Ugb2YgYSBQQywKXFsKICAgXGZyYWN7XHRleHR7dmFyfVxsZWZ0W1pfalxyaWdodF19e1xzaWdtYV4yX3tcdGV4dHt0b3R9fX0gPSBcZnJhY3tcbGFtYmRhX2p9e1xzdW1fe2k9MX1eciBcbGFtYmRhX2l9ID1cZnJhY3tcZGVsdGFfal4yfXtcc3VtX3tpPTF9XnIgXGRlbHRhX2leMn0gLgpcXQpJZiB0aGUgZmlyc3QgJGskIFBDcyBhcmUgc2VsZWN0ZWQgZm9yIGZ1cnRoZXIgdXNlLCB0aGVuIHRoZXkgcmVwcmVzZW50ClxbCiAgMTAwIFx0aW1lcyAgXGZyYWN7XHN1bV97aj0xfV5rIFxsYW1iZGFfan17XHN1bV97aT0xfV5yIFxsYW1iZGFfaX0gXCUKICA9IDEwMCBcdGltZXMgIFxmcmFje1xzdW1fe2o9MX1eayBcZGVsdGFeMl9qfXtcc3VtX3tpPTF9XnIgXGRlbHRhX2leMn0gXCUKXF0Kb2YgdGhlIHRvdGFsIHZhcmlhbmNlIChvciBpbmZvcm1hdGlvbikgb2YgdGhlIG9yaWdpbmFsIGRhdGEgc2V0ICRcbWF0aGJme1h9JC4KCiMjIyBVSyBleGFtcGxlCgpBIHNjcmVlIHBsb3QgaXMgb2Z0ZW4gdXNlZCB0byBsb29rIGF0IHRoZSBlaWdlbnZhbHVlcy4gSGVyZSBpdCBpcyBzaG93biBmb3IgdGhlIFVLIGNvbnN1bXB0aW9uIGRhdGEuCgpgYGB7cn0KbiA8LSBucm93KFgpCnIgPC0gbmNvbChzdmRVayR2KQp0b3RWYXIgPC0gc3VtKHN2ZFVrJGReMikvKG4tMSkKdmFycyA8LSBkYXRhLmZyYW1lKGNvbXA9MTo0LHZhcj1zdmRVayRkXjIvKG4tMSkpICU+JQogIG11dGF0ZShwcm9wVmFyPXZhci90b3RWYXIsY3VtVmFyPWN1bXN1bSh2YXIvdG90VmFyKSkKCnBWYXIxIDwtIHZhcnMgJT4lCiAgZ2dwbG90KGFlcyh4PWNvbXA6cix5PXZhcikpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICB4bGFiKCJDb21wb25lbnQiKSArCiAgeWxhYigiVmFyaWFuY2UiKQoKcFZhcjIgPC0gdmFycyAlPiUKICBnZ3Bsb3QoYWVzKHg9Y29tcDpyLHk9cHJvcFZhcikpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICB4bGFiKCJDb21wb25lbnQiKSArCiAgeWxhYigiUHJvcG9ydGlvbiBvZiBUb3RhbCBWYXJpYW5jZSIpCgpwVmFyMyA8LSB2YXJzICU+JQogIGdncGxvdChhZXMoeD1jb21wOnIseT1jdW1WYXIpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUoKSArCiAgeGxhYigiQ29tcG9uZW50IikgKwogIHlsYWIoIkN1bS4gcHJvcC4gb2YgdG90LiB2YXIuIikKCmdyaWQuYXJyYW5nZShwVmFyMSwgcFZhcjIsIHBWYXIzLCBucm93PTEpCmBgYAoKRnJvbSB0aGVzZSBncmFwaHMgd2UgbWF5IGNvbmNsdWRlIHRoYXQgd2l0aCB1c2luZyBrID0gMiBkaW1lbnNpb25zLCBtb3JlIHRoYW4gOTUlIG9mIHRoZSB0b3RhbCB2YXJpYW5jZSBpcyByZXRhaW5lZC4KCi0tLQoKIyMjIENob29zaW5nIHRoZSBudW1iZXIgb2YgZGltZW5zaW9ucyBTVkQKCk1vcmUgZ2VuZXJhbGx5LCBzY3JlZSBwbG90cyBhcmUgYWxzbyBpbmZvcm1hdGl2ZSBmb3IgdGhlIFNWRC4KCgogIFRoZSBtb3RpdmF0aW9uIGNvbWVzIGZyb20KICBcWwogICAgXHRleHR7TWlufV97QTogXHRleHR7cmFua30oQSk9a30gXFZlcnQgXG1hdGhiZntYfSAtIFxtYXRoYmZ7QX1cVmVydF9GXjIgPSBcVmVydCBcbWF0aGJme1h9IC0gXG1hdGhiZntYfV9rXFZlcnRfRl4yID0gXHN1bV97aj1rKzF9XnJcZGVsdGFfe2p9XjIgPSBcc3VtX3tqPWsrMX1eclxsYW1iZGFfe2p9LgogIFxdCiAgVGhpcyBpcyBrbm93biBhcyB0aGUgKiphcHByb3hpbWF0aW9uIGVycm9yKiogb2YgdGhlIG1hdHJpeCAkXG1hdGhiZntYfV9rJC4KCiAgSGVuY2UsCiAgXFsKICAgIFxmcmFje1xzdW1fe2o9MX1ea1xkZWx0YV9qXjJ9e1xzdW1fe2o9MX1eciBcZGVsdGFfal4yfSAgPVxmcmFje1xzdW1fe2o9MX1ea1xsYW1iZGFfe2p9fXtcc3VtX3tqPTF9XnJcbGFtYmRhX3tqfX0KICBcXQogIHN0aWxsIG1ha2VzIHNlbnNlIGFzIGEgcmVsYXRpdmUgcXVhbGl0eSBtZWFzdXJlIGZvciB0aGUgU1ZEIGluIGdlbmVyYWwgKGluY2x1ZGluZyBtYXRyaXggYXBwcm94aW1hdGlvbiBhbmQgTURTKS4KCiMjIyBSdWxlcyBvZiB0aHVtYiB0byBzZWxlY3QgbnVtYmVyIG9mIGRpbWVuc2lvbnMKClNlbGVjdCBrIHN1Y2ggdGhhdAoKLSBhdCBsZWFzdCA4MCUgb2YgdGhlIHRvdGFsIHZhcmlhbmNlIGlzIHJldGFpbmVkIGluIHRoZSBQQyBzcGFjZSwgb3IsIGVxdWl2YWxlbnRseSwgYXQKbW9zdCAyMCUgcmVsYXRpdmUgYXBwcm94aW1hdGlvbiBlcnJvcgoKLSBhZGRpbmcgbW9yZSBkaW1lbnNpb25zIGRvZXMgbm90IGFkZCBtb3JlIGluZm9ybWF0aW9uIChpbiBhIHJlbGF0aXZlIHNlbnNlKTsgdGhpcyBjYW4gb2Z0ZW4gYmUgdmlzdWFsbHkgZGV0ZWN0ZWQgYXMgdGhlIOKAnGtuZWXigJ0gb3Ig4oCcZWxib3figJ0gaW4gdGhlIHNjcmVlIHBsb3QKCi0gbm9uZSBvZiB0aGUgZGltZW5zaW9ucyBoYXMgYSB2YXJpYW5jZSBzbWFsbGVyIHRoYW4gdGhlIHZhcmlhbmNlIG9mIG9uZSBvZiB0aGUgcCBvcmlnaW5hbCB2YXJpYWJsZXMgKHRoaXMgcnVsZSBpcyB0eXBpY2FsbHkgb25seSB1c2VkIHdoZW4gdGhlIHZhcmlhYmxlcyBhcmUgZmlyc3Qgc3RhbmRhcmRpc2VkIC0gc2VlIGxhdGVyKS4KCiMjIENvdmFyaWFuY2UgdnMgQ29ycmVsYXRpb24gTWF0cml4CgojIyMgRGlyZWN0aW9uIG9mIGxhcmdlc3QgdmFyaWFiaWxpdHkKCiBOb3cgdGhhdCB3ZSBrbm93IGhvdyB0aGUgUENzIGFyZSBjb25zdHJ1Y3RlZCwgd2UgY2FuIGdpdmUgYW4gYWRkaXRpb25hbCBpbnRlcnByZXRhdGlvbiB0byB0aGUgZmlyc3QgZWlnZW52ZWN0b3IgLyByaWdodC1zaW5ndWxhciB2ZWN0b3Igb2YgJFxib2xkc3ltYm9se1xTaWdtYX1fWCQuCgotIFRoZSBmaXJzdCBlaWdlbnZlY3RvciBvZiAkXGJvbGRzeW1ib2x7XFNpZ21hfV9YJCBpcyB0aGUgZGlyZWN0aW9uIGluIHRoZSBvcmlnaW5hbCAkcCQtZGltZW5zaW9uYWwgc3BhY2Ugb2YgdGhlIGxhcmdlc3QgdmFyaWFiaWxpdHkuCgotIFRoZSBzZWNvbmQgZWlnZW52ZWN0b3Igb2YgJFxib2xkc3ltYm9se1xTaWdtYX1fWCQgaXMgdGhlIGRpcmVjdGlvbiBpbiB0aGUgb3JpZ2luYWwgJHAkLWRpbWVuc2lvbmFsIHNwYWNlIG9mIHRoZSBzZWNvbmQgbGFyZ2VzdCB2YXJpYWJpbGl0eSwgYW1vbmcgYWxsIGRpcmVjdGlvbnMgb3J0aG9nb25hbCB0byB0aGUgZmlyc3QgZWlnZW52ZWN0b3IKCiMjIyMgSXJpcyBleGFtcGxlCgpUaGUgaXJpcyBkYXRhc2V0IGluIFIgY29udGFpbnMgaW5mb3JtYXRpb24gb24gbGVhdmVzIG9mIGlyaXMgZmxvd2VycyBmcm9tIGRpZmZlcmVudCBzcGVjaWVzLgpIZXJlLCB3ZSB3aWxsIGZvY3VzIG9uIHRoZSBzZXRvc2Egc3BlY2llcyBhbmQgb24gdGhlIHNlcGFsIGxlbmd0aCBhbmQgc2VwYWwgd2lkdGguCgpgYGB7cn0KaXJpc1NldG9zYSA8LSBpcmlzICU+JQogIGZpbHRlcihTcGVjaWVzID09ICJzZXRvc2EiKSAlPiUKICBkcGx5cjo6c2VsZWN0KCJTZXBhbC5MZW5ndGgiLCJTZXBhbC5XaWR0aCIpCgpuSXJpcyA8LSBucm93KGlyaXNTZXRvc2EpCmhJcmlzIDwtIGRpYWcobklyaXMpIC0gbWF0cml4KDEvbklyaXMsIG5JcmlzLCBuSXJpcykKaXJpc1ggPC0gaXJpc1NldG9zYSAlPiUKICBhcy5tYXRyaXgKaXJpc1ggPC0gaElyaXMgJSolIGlyaXNYICAKCmlyaXNTdmQgPC0gc3ZkKGlyaXNYKQoKcElyaXMgPC0gaXJpc1ggJT4lCiAgYXMuZGF0YS5mcmFtZSAlPiUKICBnZ3Bsb3QoYWVzKHg9U2VwYWwuTGVuZ3RoLHk9U2VwYWwuV2lkdGgpKSArCiAgZ2VvbV9wb2ludCgpCgpwSXJpcyA8LSBwSXJpcyArCiAgZ2VvbV9zZWdtZW50KAogICAgYWVzKAogICAgICB4ID0gMCwKICAgICAgeSA9IDAsCiAgICAgIHhlbmQgPSAtaXJpc1N2ZCR2WzEsMV0qaXJpc1N2ZCRkWzFdL3NxcnQobklyaXMtMSksCiAgICAgIHllbmQgPSAtaXJpc1N2ZCR2WzIsMV0qaXJpc1N2ZCRkWzFdL3NxcnQobklyaXMtMSkKICAgICAgKSwKICAgICAgYXJyb3cgPSBhcnJvdyhsZW5ndGg9dW5pdCgwLjQsImNtIikpCiAgICApICsKICBnZW9tX3NlZ21lbnQoCiAgICBhZXMoCiAgICAgIHggPSAwLAogICAgICB5ID0gMCwKICAgICAgeGVuZCA9IGlyaXNTdmQkdlsxLDJdKmlyaXNTdmQkZFsyXS9zcXJ0KG5JcmlzLTEpLAogICAgICB5ZW5kID0gaXJpc1N2ZCR2WzIsMl0qaXJpc1N2ZCRkWzJdL3NxcnQobklyaXMtMSkKICAgICAgKSwKICAgIGFycm93ID0gYXJyb3cobGVuZ3RoPXVuaXQoMC40LCJjbSIpKQogICAgKSArCiAgZ2VvbV90ZXh0KAogICAgYWVzKAogICAgICB4ID0gLWlyaXNTdmQkdlsxLDFdKmlyaXNTdmQkZFsxXS9zcXJ0KG5JcmlzLTEpKjEuMiwKICAgICAgeSA9IC1pcmlzU3ZkJHZbMiwxXSppcmlzU3ZkJGRbMV0vc3FydChuSXJpcy0xKSoxLjIsCiAgICAgIGxhYmVsPSJlMSIKICAgICAgKQogICAgKSArICAKICBnZW9tX3RleHQoCiAgICBhZXMoCiAgICAgIHggPSBpcmlzU3ZkJHZbMSwyXSppcmlzU3ZkJGRbMl0vc3FydChuSXJpcy0xKSoxLjIsCiAgICAgIHkgPSBpcmlzU3ZkJHZbMiwyXSppcmlzU3ZkJGRbMl0vc3FydChuSXJpcy0xKSoxLjIsCiAgICAgIGxhYmVsPSJlMiIKICAgICAgKQogICAgKSArCiAgeGxpbSgtMSwgMSkgKwogIHlsaW0oLTEsMSkKCnBJcmlzCmBgYAoKIyMjIENvdmFyaWFuY2UgdmVyc3VzIGNvcnJlbGF0aW9uIG1hdHJpeApTbyBmYXIgd2UgaGF2ZSB3b3JrZWQgd2l0aAoKLSB0aGUgY29sdW1uLWNlbnRlcmVkIGRhdGEgbWF0cml4ICRcbWF0aGJme1h9JAoKLSB0aGUgU1ZEIG9mICRcbWF0aGJme1h9JCBhbmQgJFxib2xkc3ltYm9se1xTaWdtYX1fWFxwcm9wdG8gXG1hdGhiZntYfV5UXG1hdGhiZntYfSQgKHRoZSBjb3ZhcmlhbmNlIG1hdHJpeCkuCgpJbiBzb21lIHNpdHVhdGlvbnMgaXQgaXMgYmV0dGVyIHRvIHN0YXJ0IGZyb20gdGhlIHN0YW5kYXJkaXNlZCB2YXJpYWJsZXM6CgotIHN1YnN0cmFjdCBmcm9tIGVhY2ggZWxlbWVudCBpbiAkXG1hdGhiZntYfSQgdGhlICBjb2x1bW4tbWVhbgotIGRpdmlkZSBlYWNoIGNlbnRlcmVkIGVsZW1lbnQgaW4gJFxtYXRoYmZ7WH0kIGJ5IHRoZSBjb2x1bW4tc3BlY2lmaWMgc3RhbmRhcmQgZGV2aWF0aW9uCgpUaHVzIGVhY2ggZWxlbWVudCAkeF97aWp9JCBpcyByZXBsYWNlZCB3aXRoCiAgXFsKICAgICBcZnJhY3t4X3tpan0tXGJhcnt4fV9qfXtzX2p9ICwKICBcXQp3aXRoICRcYmFye3h9X2okIGFuZCAkc19qJCB0aGUgY29sdW1uIG1lYW4gYW5kIGNvbHVtbiBzdGFuZGFyZCBkZXZpYXRpb24uCgotICAgSW4gbWF0cml4IG5vdGF0aW9uLCB0aGUgc3RhbmRhcmRpc2F0aW9uLCBzdGFydGluZyBmcm9tIHRoZSBjZW50ZXJlZCBtYXRyaXggJFxtYXRoYmZ7WH0kIGlzIGNvbXB1dGVkIGFzCiAgXFsKICAgICAgXG1hdGhiZntYfVxtYXRoYmZ7U31ee1xwcmltZS0xfQogIFxdCiAgd2hlcmUgJFxtYXRoYmZ7U31eXHByaW1lJCBpcyBhIGRpYWdvbmFsIG1hdHJpeCB3aXRoIHRoZSBjb2x1bW4tc3BlY2lmaWMgc3RhbmRhcmQgZGV2aWF0aW9ucy4KCi0tLQoKIyMjIyBJcmlzIEV4YW1wbGUKYGBge3J9CnZhcihpcmlzWCkKU3ByaW1lIDwtIGRpYWcoc3FydChkaWFnKHZhcihpcmlzWCkpKSkKWHMgPC0gaXJpc1glKiVzb2x2ZShTcHJpbWUpCnZhcihYcykKYGBgCgpNdWNoIGZhc3RlciB0byB1c2UgdGhlIHNjYWxlIGZ1bmN0aW9uLiBCeSBkZWZhdWx0IGNlbnRlcj1UUlVFIGFuZCBzY2FsZT1UUlVFLgoKYGBge3J9ClhzIDwtIGlyaXNTZXRvc2EgJT4lIHNjYWxlCnZhcihYcykKYGBgCgpTaG93IHByb2JsZW0gb2YgZGlmZmVyZW50IHVuaXRzOgoKLSBTZXBhbCBsZW5ndGggaW4gY20KLSBTZXBhbCB3aWR0aCAgaW4gbW0KCmBgYHtyfQppcmlzWDIgPC0gaXJpc1gKaXJpc1gyWywyXSA8LSBpcmlzWDJbLDJdKjEwCgpwSXJpczIgPC0gaXJpc1gyICU+JQogIGFzLmRhdGEuZnJhbWUgJT4lCiAgZ2dwbG90KGFlcyh4PVNlcGFsLkxlbmd0aCx5PVNlcGFsLldpZHRoKSkgKwogIGdlb21fcG9pbnQoKQoKaXJpc1N2ZDIgPC0gc3ZkKGlyaXNYMikKCnBJcmlzMiA8LSBwSXJpczIgKwogIGdlb21fc2VnbWVudCgKICAgIGFlcygKICAgICAgeCA9IDAsCiAgICAgIHkgPSAwLAogICAgICB4ZW5kID0gaXJpc1N2ZDIkdlsxLDFdKmlyaXNTdmQyJGRbMV0vc3FydChuSXJpcy0xKSwKICAgICAgeWVuZCA9IGlyaXNTdmQyJHZbMiwxXSppcmlzU3ZkMiRkWzFdL3NxcnQobklyaXMtMSkKICAgICAgKSwKICAgICAgYXJyb3cgPSBhcnJvdyhsZW5ndGg9dW5pdCgwLjQsImNtIikpCiAgICApICsKICBnZW9tX3NlZ21lbnQoCiAgICBhZXMoCiAgICAgIHggPSAwLAogICAgICB5ID0gMCwKICAgICAgeGVuZCA9IGlyaXNTdmQyJHZbMSwyXSppcmlzU3ZkMiRkWzJdL3NxcnQobklyaXMtMSksCiAgICAgIHllbmQgPSBpcmlzU3ZkMiR2WzIsMl0qaXJpc1N2ZDIkZFsyXS9zcXJ0KG5JcmlzLTEpCiAgICAgICksCiAgICBhcnJvdyA9IGFycm93KGxlbmd0aD11bml0KDAuNCwiY20iKSkKICAgICkgKwogIGdlb21fdGV4dCgKICAgIGFlcygKICAgICAgeCA9IGlyaXNTdmQyJHZbMSwxXSppcmlzU3ZkMiRkWzFdL3NxcnQobklyaXMtMSkqMS4yLAogICAgICB5ID0gaXJpc1N2ZDIkdlsyLDFdKmlyaXNTdmQyJGRbMV0vc3FydChuSXJpcy0xKSoxLjIsCiAgICAgIGxhYmVsPSJlMSIKICAgICAgKQogICAgKSArICAKICBnZW9tX3RleHQoCiAgICBhZXMoCiAgICAgIHggPSBpcmlzU3ZkMiR2WzEsMl0qaXJpc1N2ZDIkZFsyXS9zcXJ0KG5JcmlzLTEpKjEuMiwKICAgICAgeSA9IGlyaXNTdmQyJHZbMiwyXSppcmlzU3ZkMiRkWzJdL3NxcnQobklyaXMtMSkqMS4yLAogICAgICBsYWJlbD0iZTIiCiAgICAgICkKICAgICkgKwogIHhsaW0oLTEwLCAxMCkgKwogIHlsaW0oLTEwLDEwKQoKZ3JpZC5hcnJhbmdlKHBJcmlzLCBwSXJpczIpCmBgYAoKVG9wOiBvcmlnaW5hbCBtYXRyaXggLS0gQm90dG9tOiBzZWNvbmQgY29sdW1uIG9mICRcbWF0aGJme1h9JCBtdWx0aXBsaWVkIGJ5IDEwIChlLmcuIG1vdmluZyBmcm9tIGNtIHRvIG1tKS4KCkRpcmVjdGlvbnMgb2YgbWF4aW1hbCB2YXJpYWJpbGl0eSBhcmUgYWZmZWN0ZWQgYnkgdGhlIHVuaXRzIG9mIHRoZSB2YXJpYWJsZXMuCgotLS0KCiMjIyBSZWNvbW1lbmRhdGlvbnMKCldoZW4gdG8gdXNlIGNvcnJlbGF0aW9uIGFuZCB3aGVuIGNvdmFyaWFuY2U/CgotIHVzZSBjb3JyZWxhdGlvbiB3aGVuIGNvbHVtbnMgb2YgJFxtYXRoYmZ7WH0kIGFyZSBleHByZXNzZWQgaW4gZGlmZmVyZW50IHVuaXRzCgotIHVzZSBjb3ZhcmlhbmNlIHdoZW4gY29sdW1ucyBvZiAkXG1hdGhiZntYfSQgYXJlIGV4cHJlc3NlZCBpbiB0aGUgc2FtZSB1bml0cy4KClRoZXJlIG1heSBiZSBleGNlcHRpb25zLgoKLS0tCgojIyBQQ0EgYW5kIHRoZSBNdWx0aXZhcmlhdGUgTm9ybWFsIERpc3RyaWJ1dGlvbgoKVGhlIGRlbnNpdHkgZnVuY3Rpb24gb2YgYSBtdWx0aXZhcmlhdGUgbm9ybWFsIGRpc3RyaWJ1dGlvbiAoTVZOKSBpcyBnaXZlbiBieQpcWwogIGYoXG1hdGhiZnt4fSkgPSAoMlxwaSleey1wLzJ9XHZlcnQgXGJvbGRzeW1ib2x7XFNpZ21hfVx2ZXJ0XnstMS8yfSBcZXhwXGxlZnQoIC1cZnJhY3sxfXsyfSAoXG1hdGhiZnt4fS1cYm9sZHN5bWJvbHtcbXV9KV5UIFxib2xkc3ltYm9se1xTaWdtYX1eey0xfSAoXG1hdGhiZnt4fS1cYm9sZHN5bWJvbHtcbXV9KVxyaWdodCkgLApcXQp3aGVyZQoKLSAkXGJvbGRzeW1ib2x7XG11fSQgaXMgdGhlIG11bHRpdmFyaWF0ZSBtZWFuIHZlY3RvciAoJHAkLWRpbWVuc2lvbmFsKS4gVGhlICRqJHRoIGVsZW1lbnQgaXMgJFxtdV9qID0gXHRleHR7RX1cbGVmdFtYX2pccmlnaHRdJAoKLSAkXGJvbGRzeW1ib2x7XFNpZ21hfSQgaXMgdGhlICRwIFx0aW1lcyBwJCBjb3ZhcmlhbmNlIG1hdHJpeC4gVGhlICQoaSxqKSR0aCBlbGVtZW50IGlzICRcc2lnbWFfe2lqfT1cdGV4dHtjb3Z9XGxlZnRbWF9pLCBYX2pccmlnaHRdJC4KCi0tLQoKVG8gZ2V0IGEgYmV0dGVyIHVuZGVyc3RhbmRpbmcgb2YgdGhlIE1WTiB3ZSBmb2N1cyBvbiB0aGUgZXhwb25lbnRpYWwgd2hpY2ggaGFzIHRoZSBmYWN0b3IKXFsKICAgIChcbWF0aGJme3h9LVxib2xkc3ltYm9se1xtdX0pXlQgXGJvbGRzeW1ib2x7XFNpZ21hfV57LTF9IChcbWF0aGJme3h9LVxib2xkc3ltYm9se1xtdX0pClxdClRoaXMgZmFjdG9yCgotIGlzIHRoZSBvbmx5IGZhY3RvciBpbiB0aGUgZGVuc2l0eSBmdW5jdGlvbiB0aGF0IGRlcGVuZHMgb24gJFxtYXRoYmZ7eH0kCgotIGlzIGEgJFxtYXRoYmZ7cXVhZHJhdGljfSQgZm9ybQoKLSBpcyBjb25zdGFudCBpbiBwb2ludHMgJFxtYXRoYmZ7eH0kIHdpdGggY29uc3RhbnQgZGVuc2l0eSAkZihcbWF0aGJme3h9KSQuCgotLS0KCkNvbnNpZGVyICRwPTIkIChiaXZhcmlhdGUgbm9ybWFsKS4gVGhlbiwgYWxsICRcbWF0aGJme3h9IFxpbiBcbWF0aGJie1J9XjIkIGZvciB3aGljaApcWwogICAgKFxtYXRoYmZ7eH0tXGJvbGRzeW1ib2x7XG11fSleVCBcYm9sZHN5bWJvbHtcU2lnbWF9XnstMX0gKFxtYXRoYmZ7eH0tXGJvbGRzeW1ib2x7XG11fSkgPSBcdGV4dHtjb25zdGFudCB9IGNeMgpcXQpsaWUgb24gYW4gZWxsaXBzZSB3aXRoIGNlbnRlciAkXGJvbGRzeW1ib2x7XG11fSQuCgpUaGVzZSBlbGxpcHNlcyBhcmUga25vd24gYXMgKipjb25zdGFudCBkZW5zaXR5IGVsbGlwc2VzKiouCgojIyMgaXJpcyBleGFtcGxlCgpgYGB7cn0KcElyaXMgKwogIHN0YXRfZWxsaXBzZSgpICsKICBzdGF0X2VsbGlwc2UobGV2ZWw9LjY4KSArCiAgc3RhdF9lbGxpcHNlKGxldmVsPS4xKQpgYGAKCi0tLQoKTm93IHBsdWcgaW4gdGhlIFNWRCBvZiAkXGJvbGRzeW1ib2x7XFNpZ21hfT1cbWF0aGJme1ZEVn1eVCQsClxiZWdpbntlcW5hcnJheSp9CiAgIChcbWF0aGJme3h9LVxib2xkc3ltYm9se1xtdX0pXlQgXGJvbGRzeW1ib2x7XFNpZ21hfV57LTF9IChcbWF0aGJme3h9LVxib2xkc3ltYm9se1xtdX0pICY9JiBjXjJcXAogICAoXG1hdGhiZnt4fS1cYm9sZHN5bWJvbHtcbXV9KV5UIFxtYXRoYmZ7VkR9XnstMX1cbWF0aGJme1Z9XlQgKFxtYXRoYmZ7eH0tXGJvbGRzeW1ib2x7XG11fSkgJj0mIGNeMgpcZW5ke2VxbmFycmF5Kn0KCk5vdGUgdGhhdCAkXG1hdGhiZnt4fS1cYm9sZHN5bWJvbHtcbXV9JCBpcyB0aGUgY2VudGVyZWQgJFxtYXRoYmZ7eH0kLiBXaXRob3V0IGxvc3Mgb2YgZ2VuZXJhbGl0eSwgdGFrZSAkXGJvbGRzeW1ib2x7XG11fT1cbWF0aGJmezB9JC4KSGVuY2UsClxiZWdpbntlcW5hcnJheSp9CiAgIFxtYXRoYmZ7eH1eVCBcbWF0aGJme1ZEfV57LTF9XG1hdGhiZntWfV5UIFxtYXRoYmZ7eH0gJj0mIGNeMiAgICBcXAogICBcbGVmdChcbWF0aGJme3h9XlRcbWF0aGJme1Z9XHJpZ2h0KSBcbWF0aGJme0R9XnstMX0gXGxlZnQoXG1hdGhiZnt4fV5UXG1hdGhiZntWfVxyaWdodCleVCAmPSYgY14yIFxcCiAgIFxzdW1fe2o9MX1ecCAoXG1hdGhiZnt4fV5UXG1hdGhiZnt2fV9qKV4yICAvIFxkZWx0YV9qICY9JiBjXjJcXAogICBcc3VtX3tqPTF9XnAgKHpfaileMiAgLyAoY14yXGRlbHRhX2opICY9JiAxLgpcZW5ke2VxbmFycmF5Kn0KVGhlIGxhc3QgZXF1YXRpb24gaXMgdGhlIGVxdWF0aW9uIG9mIGFuIGVsbGlwc2Ugd2l0aCBheGVzIHBhcmFsbGVsIHRvIHRoZSBiYXNpcyBvZiAkKHpfMSxcbGRvdHMsIHpfcCkkIGFuZCB3aXRoIGhhbGYgYXhpcyBsZW5ndGhzICRjXHNxcnR7XGxhbWJkYV9qfT1jXGRlbHRhX2okIHdpdGggJFxsYW1iZGFfaiQgdGhlICRqJHRoIGVpZ2VudmFsdWUgb2YgJFxib2xkc3ltYm9se1xTaWdtYX0kLgoKLS0tCgpgYGB7cn0KcElyaXMgKwogIHN0YXRfZWxsaXBzZSgpICsKICBzdGF0X2VsbGlwc2UobGV2ZWw9LjY4KSArCiAgc3RhdF9lbGxpcHNlKGxldmVsPS4xKQpgYGAKCgpUaGlzIGdyYXBocyBzaG93cyAkbj01MCQgZGF0YSBwb2ludHMgb24gJHA9MiQgZGltZW5zaW9ucy4gIFRoZSB0d28gZWxsaXBzZXMgYXJlIGNvbnN0YW50IGRlbnNpdHkgZWxsaXBzZXMgZm9yIHRocmVlIGRpZmZlcmVudCB2YWx1ZXMgb2YgJGMkIChpLmUuIHRocmVlIGRpZmZlcmVudCBjb25zdGFudCBkZW5zaXRpZXMpLiBUaGUgaW5uZXIgZWxsaXBzZSBjb3JyZXNwb25kcyB0byB0aGUgbGFyZ2VzdCBjb25zdGFudCBkZW5zaXR5IGFuZCB0aGUgb3V0ZXIgdG8gdGhlIHNtYWxsZXN0IGNvbnN0YW50IGRlbnNpdHkuIFRoZSBhcnJvd3Mgc2hvdyB0aGUgdHdvIGVpZ2VudmVjdG9ycyAvICBzaW5ndWxhciB2ZWN0b3JzIGFuZCB0aGV5IGFyZSBzY2FsZWQgYWNjb3JkaW5nIHRvIHRoZSBzcXJ0IG9mIHRoZSBlaWdlbiB2YWx1ZXMgYW5kIHRoZXkgZm9ybSB0aGUgYXhlcyBvZiB0aGUgY29uc3RhbnQgZGVuc2l0eSBlbGxpcHNlcy4gSXQgaXMgY2xlYXIgdGhhdCB0aGUgZmlyc3QgYXhpcyBpcyBwb2ludGluZyB0byB0aGUgbGFyZ2VyIHZhcmlhbmNlLgoKIyMgQmlwbG90CgotICBCZWNhdXNlIG9mIHRoZSBjbG9zZSBjb25uZWN0aW9uIGJldHdlZW4gUENBIGFuZCB0aGUgU1ZELCB0aGUgYmlwbG90IGFzIGRpc2N1c3NlZCBiZWZvcmUgaXMgc3RpbGwgbWVhbmluZ2Z1bCwgd2l0aCB0aGUgZmlyc3QgYXhpcyBwb2ludGluZyBpbnRvIHRoZSBkaXJlY3Rpb24gb2YgbGFyZ2VzdCB2YXJpYW5jZS4KCiMjIE92YXJpYW4gQ2FuY2VyIEV4YW1wbGUKClRoZSBvdmFyaWFuIGNhbmNlciBkYXRhIHNldCBjb25zaXN0cyBvZiBwcm90ZW9taWNzIGRhdGEgZm9yIDIxNiBwYXRpZW50cywgMTIxIG9mIHdob20gaGF2ZSBvdmFyaWFuIGNhbmNlciwgYW5kIDk1IG9mIHdob20gZG8gbm90LiBGb3IgZWFjaCBzdWJqZWN0LCB0aGUgZXhwcmVzc2lvbiBvZiA0MDAwIHNwZWN0cmFsIGZlYXR1cmVzIGlzIGFzc2Vzc2VkLgpUaGUgZmlyc3QgMTIxIHJvd3MgY29uc2lzdCBvZiBkYXRhIGZvciB0aGUgY2FuY2VyIHBhdGllbnRzLgoKIyMjIEltcG9ydGluZyB0aGUgZGF0YQoKYGBge3J9Cm92YXJpYW4gPC0gcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvSERBMjAyMC9kYXRhL292YXJpYW4uY3N2IiwKICBjb2xfbmFtZXMgPSBGQUxTRSwKICBjb2xfdHlwZXMgPSBjb2xzKCkKKQpncmlkLmFycmFuZ2UoCiAgcXBsb3QoMTo0MDAwLAogICAgb3ZhcmlhblsxLF0gJT4lIHVubGlzdCwKICAgIGdlb209ImxpbmUiLAogICAgeWxhYj0iY2VudGVyZWQgaW50ZW5zaXR5IiwKICAgIHhsYWI9IiIsbWFpbj0iY2FuY2VyIiwKICAgIHlsaW09cmFuZ2Uob3ZhcmlhbltjKDEsMiwyMDAsMjAxKSxdKSksCiAgcXBsb3QoMTo0MDAwLAogICAgb3ZhcmlhblsyLF0gJT4lIHVubGlzdCwKICAgIGdlb209ImxpbmUiLAogICAgeWxhYj0iY2VudGVyZWQgaW50ZW5zaXR5IiwKICAgIHhsYWI9IiIsbWFpbj0iY2FuY2VyIiwKICAgIHlsaW09cmFuZ2Uob3ZhcmlhbltjKDEsMiwyMDAsMjAxKSxdKSksCiAgcXBsb3QoMTo0MDAwLAogICAgb3ZhcmlhblsyMDAsXSAlPiUgdW5saXN0LAogICAgZ2VvbT0ibGluZSIsCiAgICB5bGFiPSJjZW50ZXJlZCBpbnRlbnNpdHkiLAogICAgeGxhYj0iIixtYWluPSJOb3JtYWwiLAogICAgeWxpbT1yYW5nZShvdmFyaWFuW2MoMSwyLDIwMCwyMDEpLF0pKSwKICBxcGxvdCgxOjQwMDAsCiAgICBvdmFyaWFuWzIwMCxdICU+JSB1bmxpc3QsCiAgICBnZW9tPSJsaW5lIiwKICAgIHlsYWI9ImNlbnRlcmVkIGludGVuc2l0eSIsCiAgICB4bGFiPSIiLG1haW49Ik5vcm1hbCIsCiAgICB5bGltPXJhbmdlKG92YXJpYW5bYygxLDIsMjAwLDIwMSksXSkpLAogIG5jb2w9MQogICkKYGBgCgpDZW50ZXJpbmcKCmBgYHtyfQpvdmFyaWFuIDwtICBzY2FsZShvdmFyaWFuLCBzY2FsZT1GQUxTRSkKZ3JpZC5hcnJhbmdlKAogIHFwbG90KDE6NDAwMCwKICAgIG92YXJpYW5bMSxdLAogICAgZ2VvbT0ibGluZSIsCiAgICB5bGFiPSJjZW50ZXJlZCBpbnRlbnNpdHkiLAogICAgeGxhYj0iIixtYWluPSJjYW5jZXIiLAogICAgeWxpbT1yYW5nZShvdmFyaWFuW2MoMSwyLDIwMCwyMDEpLF0pKSwKICBxcGxvdCgxOjQwMDAsCiAgICBvdmFyaWFuWzIsXSwKICAgIGdlb209ImxpbmUiLAogICAgeWxhYj0iY2VudGVyZWQgaW50ZW5zaXR5IiwKICAgIHhsYWI9IiIsbWFpbj0iY2FuY2VyIiwKICAgIHlsaW09cmFuZ2Uob3ZhcmlhbltjKDEsMiwyMDAsMjAxKSxdKSksCiAgcXBsb3QoMTo0MDAwLAogICAgb3ZhcmlhblsyMDAsXSwKICAgIGdlb209ImxpbmUiLAogICAgeWxhYj0iY2VudGVyZWQgaW50ZW5zaXR5IiwKICAgIHhsYWI9IiIsbWFpbj0iTm9ybWFsIiwKICAgIHlsaW09cmFuZ2Uob3ZhcmlhbltjKDEsMiwyMDAsMjAxKSxdKSksCiAgcXBsb3QoMTo0MDAwLAogICAgb3ZhcmlhblsyMDEsXSwKICAgIGdlb209ImxpbmUiLAogICAgeWxhYj0iY2VudGVyZWQgaW50ZW5zaXR5IiwKICAgIHhsYWI9IiIsbWFpbj0iTm9ybWFsIiwKICAgIHlsaW09cmFuZ2Uob3ZhcmlhbltjKDEsMiwyMDAsMjAxKSxdKSksCiAgbmNvbD0xCiAgKQpgYGAKCgojIyMgU1ZEIEFuYWx5c2lzCgpgYGB7cn0Kc3ZkT3ZhcmlhbiA8LSBzdmQob3ZhcmlhbikKCm5PdmFyaWFuIDwtIG5yb3cob3ZhcmlhbikKciA8LSBuY29sKHN2ZE92YXJpYW4kdikKCnRvdFZhciA8LSBzdW0oc3ZkT3ZhcmlhbiRkXjIpLyhuT3Zhcmlhbi0xKQp2YXJzIDwtIGRhdGEuZnJhbWUoY29tcD0xOnIsdmFyPXN2ZE92YXJpYW4kZF4yLyhuT3Zhcmlhbi0xKSkgJT4lCiAgbXV0YXRlKHByb3BWYXI9dmFyL3RvdFZhcixjdW1WYXI9Y3Vtc3VtKHZhci90b3RWYXIpKQoKcFZhcjIgPC0gdmFycyAlPiUKICBnZ3Bsb3QoYWVzKHg9Y29tcDpyLHk9cHJvcFZhcikpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fbGluZSgpICsKICB4bGFiKCJDb21wb25lbnQiKSArCiAgeWxhYigiUHJvcG9ydGlvbiBvZiBUb3RhbCBWYXJpYW5jZSIpCgpwVmFyMyA8LSB2YXJzICU+JQogIGdncGxvdChhZXMoeD1jb21wOnIseT1jdW1WYXIpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUoKSArCiAgeGxhYigiQ29tcG9uZW50IikgKwogIHlsYWIoIkN1bS4gcHJvcC4gb2YgdG90LiB2YXIuIikKCmdyaWQuYXJyYW5nZShwVmFyMiwgcFZhcjMsIG5yb3c9MSkKYGBgCgpXZSBzZWUgdGhhdCB3ZSBjYW4gZXhwbGFpbiBhIGxvdCBvZiB0aGUgdmFyaWFiaWxpdHkgdXNpbmcgYSBmZXcgUEMncyEKCmBgYHtyfQpaayA8LSBzdmRPdmFyaWFuJHVbLDE6Nl0lKiVkaWFnKHN2ZE92YXJpYW4kZFsxOjZdKQpjb2xuYW1lcyhaaykgPC0gcGFzdGUwKCJaIiwxOjYpClZrIDwtIHN2ZE92YXJpYW4kdlssMTo2XQpjb2xuYW1lcyhWaykgPC0gcGFzdGUwKCJWIiwxOjYpCnJlZHVjZWQgPC0gZGF0YS5mcmFtZShaayxjYW5jZXI9YyhyZXAoMSwxMjEpLHJlcCgyLDk1KSkpCgpwT3YxIDwtIHJlZHVjZWQgJT4lCiAgZ2dwbG90KGFlcyh4PVoxLHk9WjIsY29sPWNhbmNlcikpICsKICBnZW9tX3BvaW50KCkgICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpwT3YyIDwtIFZrICU+JQogIGFzLmRhdGEuZnJhbWUgJT4lCiAgZ2dwbG90KGFlcyh4PTE6NDAwMCx5PVYxKSkgKwogIGdlb21fbGluZSgpICsKICB4bGFiKCIiKQoKcE92MyA8LSBWayAlPiUKICBhcy5kYXRhLmZyYW1lICU+JQogIGdncGxvdChhZXMoeD0xOjQwMDAseT1WMikpICsKICBnZW9tX2xpbmUoKSArCiAgeGxhYigiIikKCmdyaWQuYXJyYW5nZShwT3YxLHBPdjIscE92MyxsYXlvdXRfbWF0cml4ID0gcmJpbmQoYygxLDMpLGMoMixOQSkpKQpgYGAKCgpSZWNvbnN0cnVjdGlvbiBvZiBwcm9maWxlczoKCmBgYHtyfQppIDwtIDEKcE92NCA8LSBxcGxvdCgxOjQwMDAsCiAgb3ZhcmlhbltpLF0sCiAgZ2VvbT0ibGluZSIsCiAgeWxhYj0iY2VudGVyZWQgaW50ZW5zaXR5IiwKICB4bGFiPSIiLG1haW49IkNhbmNlciIsCiAgeWxpbT1yYW5nZShvdmFyaWFuW2MoMSwyLDIwMCwyMDEpLF0pKQoKcE92NSA8LSBxcGxvdCgxOjQwMDAsCiAgVmtbLDE6Ml0gJSolWmtbaSwxOjJdLAogIGdlb209ImxpbmUiLAogIHlsYWI9ImNlbnRlcmVkIGludGVuc2l0eSIsCiAgeGxhYj0iIixtYWluPSJSZWNvbnN0cnVjdGVkIiwKICB5bGltPXJhbmdlKG92YXJpYW5bYygxLDIsMjAwLDIwMSksXSkpCgoKCmdyaWQuYXJyYW5nZSgKICBwT3YxICsKICAgIGFubm90YXRlKCJwb2ludCIsIHggPSByZWR1Y2VkW2ksMV0sIHkgPSByZWR1Y2VkW2ksMl0sIGNvbG91ciA9ICJyZWQiLGNleD0zKSwKICBwT3YyLAogIHBPdjMsCiAgcE92NCwKICBwT3Y1LAogIGxheW91dF9tYXRyaXggPSByYmluZChjKDEsMiwzKSxjKDQsNSxOQSkpKQpgYGAKCgoKYGBge3J9CmkgPC0gMwpwT3Y0IDwtIHFwbG90KDE6NDAwMCwKICBvdmFyaWFuW2ksXSwKICBnZW9tPSJsaW5lIiwKICB5bGFiPSJjZW50ZXJlZCBpbnRlbnNpdHkiLAogIHhsYWI9IiIsbWFpbj0iQ2FuY2VyIiwKICB5bGltPXJhbmdlKG92YXJpYW5bYygxLDIsMjAwLDIwMSksXSkpCgpwT3Y1IDwtIHFwbG90KDE6NDAwMCwKICBWa1ssMToyXSAlKiVaa1tpLDE6Ml0sCiAgZ2VvbT0ibGluZSIsCiAgeWxhYj0iY2VudGVyZWQgaW50ZW5zaXR5IiwKICB4bGFiPSIiLG1haW49IlJlY29uc3RydWN0ZWQiLAogIHlsaW09cmFuZ2Uob3ZhcmlhbltjKDEsMiwyMDAsMjAxKSxdKSkKCgoKZ3JpZC5hcnJhbmdlKAogIHBPdjEgKwogICAgYW5ub3RhdGUoInBvaW50IiwgeCA9IHJlZHVjZWRbaSwxXSwgeSA9IHJlZHVjZWRbaSwyXSwgY29sb3VyID0gInJlZCIsY2V4PTMpLAogIHBPdjIsCiAgcE92MywKICBwT3Y0LAogIHBPdjUsCiAgbGF5b3V0X21hdHJpeCA9IHJiaW5kKGMoMSwyLDMpLGMoNCw1LE5BKSkpCmBgYAoKIyBBY2tub3dsZWRnZW1lbnQgey19CgotIE9saXZpZXIgVGhhcyBmb3Igc2hhcmluZyBoaXMgbWF0ZXJpYWxzIG9mIEFuYWx5c2lzIG9mIEhpZ2ggRGltZW5zaW9uYWwgRGF0YSAyMDE5LTIwMjAsIHdoaWNoIEkgdXNlZCBhcyB0aGUgc3RhcnRpbmcgcG9pbnQgZm9yIHRoaXMgY2hhcHRlci4gIAoKIyBTZXNzaW9uIGluZm8gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5TZXNzaW9uIGluZm88L3N1bW1hcnk+CgpgYGB7ciBzZXNzaW9uX2luZm8sIGVjaG89RkFMU0UsIGNhY2hlPUZBTFNFfQpTeXMudGltZSgpCnNlc3Npb25pbmZvOjpzZXNzaW9uX2luZm8oKQpgYGAKCjwvZGV0YWlscz4KCiMgW0hvbWVdKGh0dHBzOi8vc3RhdG9taWNzLmdpdGh1Yi5pby9IREEyMDIwLykgey19Cg==