1 Introduction

1.1 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

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

1.3 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)

1.4 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} \]

  • For high dimensional data \(p>>>n\) \(\rightarrow\) \(\text{max}(r)=n\) and

  • equivalently for multivariate data with \(n>p\) \(\rightarrow\) \(\text{max}(r)=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}\).

1.5 Interpretation of singular vectors: face example

1.5.1 Convert images to vectors

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

1.5.2 Visualisation of mean image

plotFaceVector <- function(faceVector,nrow=192,ncol=168) {
  matrix(faceVector,nrow=nrow,ncol=ncol) %>%
  ggimage()  
}

meanFace %>%
  plotFaceVector

1.5.3 SVD

1.5.3.1 Perform SVD in R

  1. We adopt svd on the centered matrix
  2. We cache the result because the calculation takes 10 minutes.
faceSvd <- svd(allFacesMxCentered)
## Run this code manually to store the SVD for later re-use
saveRDS(faceSvd, file = "faceSvd.rds")
## Run this code manually to reload the SVD result
faceSvd <- readRDS("faceSvd.rds")

1.5.3.2 SVD

Dimensions of \(\mathbf{U}\), \(\mathbf{V}\)?

n <- nrow(allFacesCenteredMx)
p <- ncol(allFacesCenteredMx)
dim(faceSvd$u)
## [1] 2304 2304
dim(faceSvd$v)
## [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\)!

1.5.3.3 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} \]

1.5.3.4 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
   )
 )
approximation with 25 (top left),  100 (top right) and 500 (bottom left) eigenfaces and original face (bottom right, or with all eigenfaces)

approximation with 25 (top left), 100 (top right) and 500 (bottom left) eigenfaces and original face (bottom right, or with all eigenfaces)

2 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

2.1 Example 1: Image compression

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

2.1.1.1 Load the original painting

  1. fetch image from the web
  2. convert into greyscale
  3. plot
  4. 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!

2.1.1.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 very quickly!

2.1.1.3 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)

2.1.2 More complex painting: Composition A, Piet Mondriaan

2.1.2.1 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])

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

2.1.2.3 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)

2.1.3 Self portret Piet Mondriaan

2.1.3.1 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])

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

2.1.3.3 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\)).

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

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

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

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

6.1 Example prostate dataset

6.1.1 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)

6.1.2 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

7 SVD and Multi-Dimensional Scaling (MDS)

7.1 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
X1 England Wales Scotland N.Ireland
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.

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


7.4 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?

7.5 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}\]

7.5.1 UK example

7.5.1.1 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

biplot(Zk,Vk)

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.

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

7.5.2 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\).

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

8 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

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


8.2 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}\).

8.2.1 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:

  • \(\mathbf{b}=\mathbf{e}_j\): the \(j\)th eigenvector of \(\boldsymbol{\Sigma}_X\)

  • \(\lambda=\lambda_j\): the \(j\)th eigenvalue of \(\boldsymbol{\Sigma}_X\).

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.


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


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


8.5 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}\).

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


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

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

8.6 Covariance vs Correlation Matrix

8.6.1 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

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

8.6.2 Covariance versus correlation matrix

So far we have worked with

  • the column-centered data matrix \(\mathbf{X}\)

  • the SVD of \(\mathbf{X}\) and \(\boldsymbol{\Sigma}_X\propto \mathbf{X}^T\mathbf{X}\) (the covariance matrix).

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.

8.6.2.1 Iris Example

var(irisX)
##              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.


8.6.3 Recommendations

When to use correlation and when covariance?

  • use correlation when columns of \(\mathbf{X}\) are expressed in different units

  • use covariance when columns of \(\mathbf{X}\) are expressed in the same units.

There may be exceptions.


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

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

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

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

8.9.1 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
  )

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