## install packages with:
## install.packages(c("glmnet", "pls", "NormalBetaPrime", "boot"))
library(NormalBetaPrime)
library(glmnet)
library(pls)
library(boot)

1 Introduction

In this lab session we will look at the following topics

  • Demonstrate why low dimensional prediction modeling fails in high dimension.
  • Carry out Principal Component Regression (PCR)
  • Use glmnet() to carry out ridge regression, lasso and elastic net
  • Evaluation of these prediction models

1.1 The dataset

In this practical, we will use the dataset eyedata provided by the NormalBetaPrime package. This dataset contains gene expression data of 200 genes for 120 samples. The data originates from microarray experiments of mammalian eye tissue samples.

The dataset consists of two objects:

  • genes: a \(120 \times 200\) matrix with the expression levels of 200 genes (columns) for 120 samples (rows)
  • trim32: a vector with 120 expression levels of the TRIM32 gene.
data(eyedata)
## Look at objects that were just loaded
str(genes)
#>  num [1:120, 1:200] 3.68 3.58 3.85 4.13 3.88 ...
#>  - attr(*, "dimnames")=List of 2
#>   ..$ : chr [1:120] "V2" "V3" "V4" "V5" ...
#>   ..$ : chr [1:200] "1377" "1748" "2487" "2679" ...
str(trim32)
#>  num [1:120] 8.42 8.36 8.41 8.29 8.27 ...

The goal of this exercise is to predict the expression levels of TRIM32 from the expression levels of the 200 genes measured in the microarray experiment. For this, it makes sense to start by constructing centered (and possibly scaled) data. We store this in two matrices X and Y:

X <- scale(genes, center = TRUE, scale = TRUE) 
Y <- scale(trim32, center = TRUE)

Remember that scaling avoids that differences in levels of magnitude will give one variable (gene) more influence in the result. This has been illustrated in the second practical session as well. For the Y vector, this is less of an issue as we’re talking about a single variable. Not scaling will make the predictions interpretable as “deviations from the mean”.

1.2 The curse of singularity

We begin by assuming that the predictors and the outcome have been centered so that the intercept is 0. We are presented with the usual regression model:

\[ Y_i=\beta_i X_{i1}+\dots+\beta_pX_{ip}+\epsilon_i \\ \text{ Or } \mathbf{Y}={\mathbf{X}}{\boldsymbol{\beta}} +{\boldsymbol{\epsilon}} \]

Our goal is to get the least squares estimator of \({\boldsymbol{\beta}}\), given by

\[ \hat{{\boldsymbol{\beta}}}= (\mathbf{X}^T{\mathbf{X}})^{-1}{\mathbf{X}}^T{\mathbf{Y}} \]

in which the \(p \times p\) matrix \(({\mathbf{X}}^T{\mathbf{X}})^{-1}\) is crucial! To be able to calculate the inverse of \({\mathbf{X}}^T \mathbf{X}\), it has to be of full rank \(p\), which would be 200 in this case. Let’s check this:

dim(X) # 120 x 200, so p > n!
#> [1] 120 200
qr(X)$rank
#> [1] 119

XtX <- crossprod(X) # calculates t(X) %*% X more efficiently
qr(XtX)$rank
#> [1] 119

# Try to invert using solve: 
solve(XtX)
#> Error in solve.default(XtX): system is computationally singular: reciprocal condition number = 4.20888e-20

We realize we cannot compute \(({\mathbf{X}}^T{\mathbf{X}})^{-1}\) because the rank of \(({\mathbf{X}}^T{\mathbf{X}})\) is less than \(p\) hence we can’t get \(\hat{{\boldsymbol{\beta}}}\) by means of least squares! This is generally referred to as the singularity problem.

2 Principal component regression

A first way to deal with this singularity, is to bypass it using principal components. Since \(\min(n,p) = n = 120\), PCA will give 120 components, each being a linear combination of the \(p\) = 200 variables. These 120 PCs contain all information present in the original data. We could as well use an approximation of \({\mathbf{X}}\), i.e using just a few (\(k<120\)) PCs. So we use PCA as a method for reducing the dimensions while retaining as much variation between the observations as possible. Once we have these PCs, we can use them as variables in a linear regression model.

2.1 Classic linear regression on PCs

We first compute the PCA on the data with prcomp. We will use an arbitrary cutoff of \(k = 4\) PCs to illustrate the process of performing regression on the PCs.

k <- 4 # Arbitrarily chosen k=4
pca <- prcomp(X)
Vk <- pca$rotation[, 1:k] # the loadings matrix
Zk <- pca$x[, 1:k] # the scores matrix

# Use the scores in classic linear regression
pcr_model1 <- lm(Y ~ Zk)
summary(pcr_model1)
#> 
#> Call:
#> lm(formula = Y ~ Zk)
#> 
#> Residuals:
#>      Min       1Q   Median       3Q      Max 
#> -1.72388 -0.34723  0.02811  0.27817  2.03271 
#> 
#> Coefficients:
#>               Estimate Std. Error t value Pr(>|t|)    
#> (Intercept) -2.636e-15  5.454e-02   0.000   1.0000    
#> ZkPC1       -7.172e-02  4.950e-03 -14.488   <2e-16 ***
#> ZkPC2        1.273e-02  1.342e-02   0.949   0.3447    
#> ZkPC3        3.371e-02  2.326e-02   1.449   0.1500    
#> ZkPC4        5.908e-02  2.535e-02   2.330   0.0215 *  
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 0.5975 on 115 degrees of freedom
#> Multiple R-squared:  0.655,  Adjusted R-squared:  0.643 
#> F-statistic: 54.58 on 4 and 115 DF,  p-value: < 2.2e-16

As \(\mathbf{X}\) and \(\mathbf{Y}\) are centered, the intercept is approximately 0.

The output shows that PC1 and PC4 have a \(\beta\) estimate that differs significantly from 0 (at \(p < 0.05\)), but the results can’t be readily interpreted, since we have no immediate interpretation of the PCs.

2.2 Using the package pls

PCR can also be performed using the pcr() function from the package pls directly on the data (so without having to first perform the PCA manually). When using this function, you have to keep a few things in mind:

  1. the number of components (PCs) to use is passed with the argument ncomp
  2. the function allows you to scale (set scale = TRUE) and center (set center = TRUE) the predictors first (in the example here, \(\mathbf{X}\) has already been centered and scaled).

You can use the function pcr() in much the same way as you would use lm(). The resulting fit can easily be examined using the function summary(), but the output looks quite different from what you would get from lm.

# X is already scaled and centered, so that's not needed.
pcr_model2 <- pcr(Y ~ X, ncomp = 4)
summary(pcr_model2)
#> Data:    X dimension: 120 200 
#>  Y dimension: 120 1
#> Fit method: svdpc
#> Number of components considered: 4
#> TRAINING: % variance explained
#>    1 comps  2 comps  3 comps  4 comps
#> X    61.22    69.55    72.33    74.66
#> Y    62.97    63.24    63.87    65.50

First of all the output shows you the data dimensions and the fitting method used. In this case, that is PC calculation based on SVD. The summary() function also provides the percentage of variance explained in the predictors and in the response using different numbers of components. For example, the first PC only captures 61.22% of all the variance, or information in the predictors and it explains 62.9% of the variance in the outcome. Note that for both methods the choice of the number of principal components was arbitrary chosen to be 4.

At a later stage, we will look at how to choose the number of components that has the smallest prediction error.

3 Ridges, Lassos and Elastic Nets

Ridge regression, lasso regression and elastic nets are all closely related techniques, based on the same idea: add a penalty term to the estimating function so \(({\mathbf{X}}^T{\mathbf{X}})\) becomes full rank again and is invertible. Two different penalty terms or regularization methods can be used:

  1. L1 regularization: this regularization adds a term \({\gamma_1\|\boldsymbol{\beta}\|_{1}}\) to the estimating equation. The term will add a penalty based on the absolute value of the magnitude of the coefficients. This is used by the lasso regression

\[ \hat{\boldsymbol{\beta}}^{\text{lasso}} = \text{argmin}_{\boldsymbol{\beta}}\displaystyle({(\mathbf{Y}-\mathbf{X}\boldsymbol{\beta})^T(\mathbf{Y}-\mathbf{X}\boldsymbol{\beta})+{\gamma_1\|\boldsymbol{\beta}\|_{1}}}\displaystyle) \]

  1. L2 regularization: this regularization adds a term \({\gamma_2\|\boldsymbol{\beta}\|_{2}^{2}}\) to the estimating equation. The penalty term is based on the square of the magnitude of the coefficients. This is used by ridge regression.

\[ \hat{\boldsymbol{\beta}}^{\text{ridge}} = \text{argmin}_{\boldsymbol{\beta}}\displaystyle({(\mathbf{Y}-\mathbf{X}\boldsymbol{\beta})^T(\mathbf{Y}-\mathbf{X}\boldsymbol{\beta})+{\gamma_2\|\boldsymbol{\beta}\|_{2}^{2}}}\displaystyle) \]

Elastic nets combine both types of regularizations. It does so by introducing a \(\alpha\) mixing parameter that essentially combines the L1 and L2 norms in a weighted average.

\[ \hat{\boldsymbol{\beta}}^{\text{el.net}} = \text{argmin}_{\boldsymbol{\beta}}\displaystyle({(\mathbf{Y}-\mathbf{X}\boldsymbol{\beta})^{T}(\mathbf{Y}-\mathbf{X}\boldsymbol{\beta})+{\alpha \gamma_1\|\boldsymbol{\beta}\|_{1}}+ {(1 - \alpha)\gamma_2\|\boldsymbol{\beta}\|_{2}^{2}}}\displaystyle) \]

4 Exercise: Verification of ridge regression

In least square regression the minimization of the estimation function \(|{\mathbf{Y} - \mathbf{X} \boldsymbol{\beta}}\|^{2}_{2}\) leads to the solution \({\boldsymbol{\hat{\beta}}=(\mathbf{X^TX})^{-1}\mathbf{X^TY}}\).

For the penalized least squares criterion used by ridge regression, you minimize \(\|{\mathbf{Y}-\mathbf{X}\boldsymbol{\beta}\|^{2}_{2}}+\gamma{\boldsymbol{\|\beta\|^{2}_{2}}}\) which leads to following solution:

\[ {\boldsymbol{\hat{\beta}}=(\mathbf{X^TX}}+\gamma{\mathbf{I}})^{-1}{\mathbf{X^TY}} \]

where \(\mathbf{I}\) is the \(p \times p\) identity matrix.

The ridge parameter \(\gamma\) shrinks the coefficients towards 0, with \(\gamma = 0\) being equivalent to OLS (no shrinkage) and \(\gamma = +\infty\) being equivalent to setting all \(\hat{\beta}\)’s to 0. The optimal parameter lies somewhere in between and needs to be tuned by the user.

Tasks

Solve the following exercises using R.

1. Verify that \({\mathbf{(X^TX}}+\gamma{\mathbf{I}})\) has rank \(200\), for any \(\gamma>0\) of your choice.

Solution

XtX <- crossprod(X)
p <- ncol(X)
gamma <- 2 # My choice

# Compute penalized matrix
XtX_gammaI <- XtX + (gamma * diag(p))
dim(XtX_gammaI)
#> [1] 200 200
qr(XtX_gammaI)$rank == 200 # indeed
#> [1] TRUE

2. Check that the inverse of \({\mathbf{(X^TX}}+\gamma{\mathbf{I}})\) can be computed.

Solution

# Yes, it can be computed (no error)
XtX_gammaI_inv <- solve(XtX_gammaI)
str(XtX_gammaI_inv)
#>  num [1:200, 1:200] 0.25408 -0.02756 0.00453 -0.02961 0.00722 ...
#>  - attr(*, "dimnames")=List of 2
#>   ..$ : chr [1:200] "1377" "1748" "2487" "2679" ...
#>   ..$ : chr [1:200] "1377" "1748" "2487" "2679" ...

3. Finally, compute \({\boldsymbol{\hat{\beta}}=(\mathbf{X^TX}}+\gamma{\mathbf{I}})^{-1}{\mathbf{X^TY}}\).

Solution

## Calculate ridge beta estimates
## Use `drop` to drop dimensions and create vector
ridge_betas <- drop(XtX_gammaI_inv %*% t(X) %*% Y)
length(ridge_betas) # one for every gene
#> [1] 200
summary(ridge_betas)
#>      Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
#> -0.235089 -0.045535 -0.008577 -0.000279  0.054195  0.215292

We have now manually calculated the ridge regression estimates.

5 Performing ridge and lasso regression with glmnet

The package glmnet provides a function glmnet() that allows you to fit all three types of regressions. Which type is used, can be determined by specifying the alpha argument. For a ridge regression, you set alpha to 0, and for a lasso regression you set alpha to 1. Other alpha values between 0 and 1 will fit a form of elastic net. This function has slightly different syntax from the other model-fitting functions. To be able to use it, you have to pass a x matrix as well as a y vector, and you don’t use the formula syntax.

The gamma value, which controls the “strength” of the penalty, can be passed by the argument lambda (notation isn’t always consistent between text books and software…). The function glmnet() can also carry out a search for finding the best gamma value for a fit. This can be done by passing multiple values to the argument lambda. If not supplied, glmnet will generate a range of values itself, based on the data whereby the number of values can be controlled with the nlambda argument. This is generally the recommended way to use glmnet, see ?glmnet for details.

For a thorough introduction to the glmnet package and elastic net models in general, see the glmnet introduction vignette

Demonstration: Ridge regression

Let’s perform a ridge regression in order to predict expression levels of the TRIM32 gene using the 200 gene probes data. We can start by using a \(\gamma\) value of 2.

gamma <- 2
ridge_model <- glmnet(X, Y, alpha = 0, lambda = gamma)

# have a look at the first 10 coefficients
coef(ridge_model)[1:10]
#>  [1] -2.635697e-15 -5.818717e-03 -9.888023e-03  5.100910e-03 -2.482488e-03
#>  [6] -8.341285e-03 -4.528922e-03 -7.961890e-03 -5.039029e-03  6.325841e-03

The first coefficient is the intercept, and is again essentially 0. But a value of 2 for \(\gamma\) might not be the best choice, so let’s see how the coefficients change with different values for \(\gamma\).

We will create a grid of \(\gamma\) values, i.e. a range of values that will be used as input for the glmnet function. Note that this function can take a vector of values as input for the lambda argument, allowing to fit multiple models with the same input data but different hyperparameters.

grid <- seq(1, 1000, by = 10)  # 1 to 1000 with steps of 10
ridge_mod_grid <- glmnet(X, Y, alpha = 0, lambda = grid)

# Plot the coefficients against the (natural) LOG lambda sequence!
# see ?plot.glmnet
plot(ridge_mod_grid, xvar = "lambda", xlab = "log(gamma)")
# add a vertical line at gamma = 2
text(log(gamma), -0.05, labels = expression(gamma == 2), 
     adj = -0.5, col = "firebrick")
abline(v = log(gamma), col = "firebrick", lwd = 2)

This plot is known as a coefficient profile plot, each colored line represents a coefficient \(\hat{\beta}\) from the regression model and shows how they change with increased values of \(\gamma\) (on the log-scale) 1.

Note that for higher values \(\gamma\), the coefficient estimates become closer to 0, showing the shrinkage effect of the ridge penalty.

Similar to the PC regression example, we chose \(\gamma=2\) and the grid rather arbitrarily. We will see subsequently, how to choose \(\gamma\) that minimizes the prediction error.

6 Exercise: Lasso regression

Lasso regression is also a form of penalized regression, but we do not have an analytic solution of \(\hat{{\boldsymbol{\beta}}}\) as in least squares and ridge regression. In order to fit a lasso model, we once again use the glmnet() function. However, this time we use the argument alpha = 1

Tasks

1. Verify that setting alpha = 1 indeed corresponds to lasso regression using the equations from Section 3.

2. Perform a lasso regression with the glmnet function with Y the response and X the predictors.

You do not have to provide a custom sequence of \(\gamma\) (lambda) values here but can instead rely on glmnet’s default behaviour of choosing the grid of \(\gamma\) values based on the data (see ?glmnet for more details).

Solution

# Note that the glmnet() function can supply gamma automatically
# By default it uses a sequence of 100 lambda values
lasso_model <- glmnet(X, Y, alpha = 1)

3. Make the coefficient profile plot and interpret.

Solution

plot(lasso_model, xvar = "lambda", xlab = "log(gamma)")

Note that the number of non-zero coefficients is indicated at the top of the plot. In the case of lasso-regression the regularization is much less smooth compared to the ridge regression, with some coefficients increasing for higher \(\gamma\) before sharply dropping to zero. In contrast to ridge, lasso eventually shrinks all coefficients to 0.

7 Evaluation of prediction models and tuning hyperparameters

First we will split our original data in a training and test set to validate our model. The training set will be used to train the model and tune the hyperparameters, while the test set will be used to evaluate the out-of-sample performance of our final model. If we would use the same data to both fit and test the model, we would get biased results.

Before we begin, we use the set.seed() function in order to set a seed for R’s random number generator, so that we will all obtain precisely the same results as those shown below. It is generally good practice to set a random seed when performing an analysis such as cross-validation that contains an element of randomness, so that the results obtained can be reproduced at a later time.

We begin by using the sample() function to split the set of samples into two subsets, by selecting a random subset of 80 observations out of the original 120 observations. We refer to these observations as the training set. The rest of the observations will be used as the test set.

set.seed(1)
# Sample 80 random IDs from the rows of X (120 total)
trainID <- sample(nrow(X), 80)

# Training data
trainX <- X[trainID, ]
trainY <- Y[trainID]

# Test data
testX <- X[-trainID, ]
testY <- Y[-trainID]

To make fitting the models a bit easier later, we will also create 2 data.frames combining the response and predictors for the training and test data.

train_data <- data.frame("TRIM32" = trainY, trainX)
test_data <- data.frame("TRIM32" = testY, testX)

## Glancing at the data structure: for the first 10 columns only
str(train_data[, 1:10])
#> 'data.frame':    80 obs. of  10 variables:
#>  $ TRIM32: num  0.564 0.231 0.215 -0.239 -0.226 ...
#>  $ X1377 : num  -0.1498 -0.254 -0.7058 0.1164 -0.0195 ...
#>  $ X1748 : num  -0.3063 -0.4263 -0.4714 -0.5319 -0.0733 ...
#>  $ X2487 : num  -0.3588 0.3651 0.0306 0.6907 -1.0774 ...
#>  $ X2679 : num  0.2111 -0.0638 -0.0614 -0.129 -0.5161 ...
#>  $ X2789 : num  -0.0347 0.8042 -0.0639 0.4437 -0.0162 ...
#>  $ X2875 : num  -0.469 -0.535 -0.656 0.134 0.138 ...
#>  $ X3244 : num  0.603 -0.639 0.813 -1.101 0.122 ...
#>  $ X3375 : num  0.2031 -0.00797 -0.22859 0.45933 -0.11757 ...
#>  $ X3732 : num  0.317 -0.517 -0.226 -1.508 0.132 ...

7.1 Model evaluation

We are interested in the out-of-sample error of our models, i.e. how good our model does on unseen data. This will allow us to compare different classes of models. For continuous outcomes we will use the mean squared error (MSE) (or its square-root version, the RMSE).

The evaluation will allow us to compare the performance of different types of models, e.g. PC regression, ridge regression and lasso regression, on our data. However, we still need to find the optimal model within each of these classes, by selecting the best hyperparameter (number of PCs for PC regression and \(\gamma\) for lasso and ridge). For that we will use \(k\)-fold Cross Validation on our training set.

7.2 Tuning hyperparameters

The test set is only used to evaluate the final model. To achieve this final model, we need to find the optimal hyperparameters, i.e. the hyperparameters that best generalize the model to unseen data. We can estimate this by using k-fold cross validation (\(CV_k\)) on the training data.

The \(CV_k\) estimates can be automatically computed for any generalized linear model (generated with glm() and by extension glmnet()) using the cv.glm() function from the boot package.

8 Example: PC regression evaluation

We start with the PC regression and look for the optimal number of PCs that minimizes the MSE using \(k\)-fold Cross validation. We then use this optimal number of PCs to train the final model and evaluate it on the test data.

8.1 k-fold Cross Validation to tune number of components

Conveniently, the pcr function from the pls package has an implementation for k-fold Cross Validation. We simply need to set validation = CV and segments = 20 to perform 20-fold Cross Validation with PC regression. If we don’t specify ncomp, pcr will select the maximum number of PCs that can be used for the CV.

Note that our training data trainX consists of 80 observations (rows). If we perform 20-fold CV, that means we will split the data in 20 groups, so each group will consist of 4 observations. At each CV cycle, one group will be left out and the model will be trained on the remaining groups. This leaves us with 76 training observations for each CV cycle, so the maximal number of components that can be used in the linear regression is 75.

## Set seed for reproducibility, kCV is a random process!
set.seed(123)

K <- 20

## The 'Y ~ .' notation means: fit Y by every other variable in the data
pcr_cv <- pcr(TRIM32 ~ ., data = train_data, validation = "CV", segments = K)
summary(pcr_cv)
#> Data:    X dimension: 80 200 
#>  Y dimension: 80 1
#> Fit method: svdpc
#> Number of components considered: 75
#> 
#> VALIDATION: RMSEP
#> Cross-validated using 20 random segments.
#>        (Intercept)  1 comps  2 comps  3 comps  4 comps  5 comps  6 comps
#> CV           1.112   0.7013   0.7305   0.7402   0.6939   0.6872   0.6811
#> adjCV        1.112   0.6987   0.7269   0.7375   0.6905   0.6810   0.6759
#>        7 comps  8 comps  9 comps  10 comps  11 comps  12 comps  13 comps
#> CV      0.6552   0.6812   0.6377    0.6418    0.6153    0.6126    0.6045
#> adjCV   0.6526   0.6772   0.6294    0.6347    0.6080    0.6050    0.5976
#>        14 comps  15 comps  16 comps  17 comps  18 comps  19 comps  20 comps
#> CV       0.6112    0.5882    0.5834    0.5784    0.5744    0.5752    0.5763
#> adjCV    0.6068    0.5788    0.5751    0.5702    0.5669    0.5681    0.5693
#>        21 comps  22 comps  23 comps  24 comps  25 comps  26 comps  27 comps
#> CV       0.5696    0.5654    0.5624    0.5711    0.5676    0.5691    0.5598
#> adjCV    0.5643    0.5608    0.5596    0.5735    0.5586    0.5619    0.5527
#>        28 comps  29 comps  30 comps  31 comps  32 comps  33 comps  34 comps
#> CV       0.5547    0.5441     0.542    0.5444    0.5416    0.5464    0.5497
#> adjCV    0.5491    0.5400     0.539    0.5399    0.5374    0.5426    0.5438
#>        35 comps  36 comps  37 comps  38 comps  39 comps  40 comps  41 comps
#> CV       0.5496    0.5582    0.5618    0.5892    0.5849    0.5929    0.6047
#> adjCV    0.5437    0.5533    0.5561    0.5844    0.5816    0.5882    0.5997
#>        42 comps  43 comps  44 comps  45 comps  46 comps  47 comps  48 comps
#> CV       0.6127    0.6098    0.6230    0.6204    0.6197    0.6299    0.6337
#> adjCV    0.6079    0.6086    0.6214    0.6109    0.6119    0.6211    0.6264
#>        49 comps  50 comps  51 comps  52 comps  53 comps  54 comps  55 comps
#> CV       0.6410    0.6326    0.6375    0.6595    0.6586    0.6599    0.6453
#> adjCV    0.6325    0.6250    0.6322    0.6544    0.6559    0.6548    0.6369
#>        56 comps  57 comps  58 comps  59 comps  60 comps  61 comps  62 comps
#> CV       0.6442    0.6380    0.6456    0.6389    0.6733    0.6544    0.6544
#> adjCV    0.6355    0.6264    0.6361    0.6327    0.6712    0.6555    0.6425
#>        63 comps  64 comps  65 comps  66 comps  67 comps  68 comps  69 comps
#> CV       0.6566    0.6638    0.6770    0.6657    0.6615    0.6697    0.6545
#> adjCV    0.6462    0.6549    0.6685    0.6586    0.6535    0.6634    0.6456
#>        70 comps  71 comps  72 comps  73 comps  74 comps  75 comps
#> CV       0.6435    0.6402    0.6229    0.6313     0.632    0.6192
#> adjCV    0.6361    0.6292    0.6134    0.6241     0.624    0.6113
#> 
#> TRAINING: % variance explained
#>         1 comps  2 comps  3 comps  4 comps  5 comps  6 comps  7 comps  8 comps
#> X         64.80    72.80    75.38    77.61    79.16    80.58    81.86    82.97
#> TRIM32    68.18    68.55    68.58    72.27    76.46    77.04    77.85    79.04
#>         9 comps  10 comps  11 comps  12 comps  13 comps  14 comps  15 comps
#> X         83.86     84.66     85.44     86.12     86.77     87.37     87.93
#> TRIM32    83.13     83.27     84.03     84.48     84.78     84.96     86.27
#>         16 comps  17 comps  18 comps  19 comps  20 comps  21 comps  22 comps
#> X          88.45     88.95     89.43     89.90     90.33     90.75     91.14
#> TRIM32     86.27     86.38     86.42     86.43     86.45     86.45     86.56
#>         23 comps  24 comps  25 comps  26 comps  27 comps  28 comps  29 comps
#> X          91.51     91.87     92.22     92.57     92.89     93.20     93.50
#> TRIM32     86.63     86.66     88.26     88.26     88.42     88.45     88.51
#>         30 comps  31 comps  32 comps  33 comps  34 comps  35 comps  36 comps
#> X          93.78     94.05     94.31     94.57     94.82     95.05     95.28
#> TRIM32     88.60     88.89     89.14     89.22     89.49     89.57     89.59
#>         37 comps  38 comps  39 comps  40 comps  41 comps  42 comps  43 comps
#> X          95.50     95.72     95.92     96.13     96.33     96.51     96.69
#> TRIM32     89.83     89.84     89.92     90.20     90.37     90.52     90.60
#>         44 comps  45 comps  46 comps  47 comps  48 comps  49 comps  50 comps
#> X          96.86     97.03     97.19     97.34     97.49     97.63     97.77
#> TRIM32     90.90     91.92     91.93     92.07     92.09     92.29     92.47
#>         51 comps  52 comps  53 comps  54 comps  55 comps  56 comps  57 comps
#> X          97.90     98.03     98.15     98.27     98.38     98.49     98.59
#> TRIM32     92.47     92.53     92.57     93.42     94.03     94.31     94.77
#>         58 comps  59 comps  60 comps  61 comps  62 comps  63 comps  64 comps
#> X          98.70     98.80     98.89     98.98     99.06     99.15     99.23
#> TRIM32     94.82     94.85     94.86     94.89     96.55     96.69     96.72
#>         65 comps  66 comps  67 comps  68 comps  69 comps  70 comps  71 comps
#> X          99.30     99.38     99.45     99.51     99.57     99.63     99.68
#> TRIM32     96.76     96.99     97.40     97.47     97.98     98.00     98.60
#>         72 comps  73 comps  74 comps  75 comps
#> X          99.73     99.78     99.82     99.87
#> TRIM32     98.75     98.77     99.04     99.22

We can plot the root mean squared error of prediction (RMSEP) for each number of components as follows.

plot(pcr_cv, plottype = "validation")

The pls package also has a function selectNcomp to select the optimal number of components. Here we use the “one-sigma” method, which returns the lowest number of components for which the RMSE is within one standard error of the absolute minimum. The function also allows plotting the result by specifying plot = TRUE.

optimal_ncomp <- selectNcomp(pcr_cv, method = "onesigma", plot = TRUE)

This outcome shows us that the optimal number of components for our model is 13.

8.2 Validation on test data

We now use our optimal number of components to train the final PCR model. This model is then validated on by generating predictions for the test data and calculating the MSE.

We define a custom function to calculate the MSE. Note that there is also an MSEP function in the pls package which does the prediction and MSE calculation in one go. But our own function will come in handy later for lasso and ridge regression.

# Mean Squared Error
## obs: observations; pred: predictions
MSE <- function(obs, pred){
  mean((drop(obs) - drop(pred))^2)
}
final_pcr_model <- pcr(TRIM32 ~ ., data = train_data, ncomp = optimal_ncomp)
pcr_preds <- predict(final_pcr_model, newdata = test_data, ncomp = optimal_ncomp)
(pcr_mse <- MSE(testY, pcr_preds))
#> [1] 0.3655052

This value on its own does not tell us very much, but we can use it to compare our PCR model with other types of models later.

Finally, we plot the predicted values for our response variable (the TRIM32 gene expression) against the actual observed values from our test set.

predplot(final_pcr_model, newdata = test_data, line = TRUE)

9 Exercise: evaluate and compare prediction models

1. Perform a lasso regression with 20-fold Cross Validation on the training data (trainX, trainY). Plot the results and select the optimal lambda (\(\gamma\)) parameter. Fit a final model with the selected lambda and validate it on the test data.

Hint: use the cv.glmnet() function, for 20 folds CV, set nfolds = 20 and to use the MSE metric set type.measure = "mse". Go to ?cv.glmnet for details.

Solution

set.seed(123)
lasso_cv <- cv.glmnet(trainX, trainY, alpha = 1, 
                      nfolds = K, type.measure = "mse")
lasso_cv
#> 
#> Call:  cv.glmnet(x = trainX, y = trainY, type.measure = "mse", nfolds = K,      alpha = 1) 
#> 
#> Measure: Mean-Squared Error 
#> 
#>      Lambda Index Measure     SE Nonzero
#> min 0.07559    55  0.3639 0.0750      16
#> 1se 0.16668    38  0.4353 0.1646       9
plot(lasso_cv)

Note that we can extract the fitted lasso regression object from the CV result and make the coefficient profile plot as before.

plot(lasso_cv$glmnet.fit, xvar = "lambda")

We can look for the gamma values that give the best result. Here you have two possibilities :

  1. lambda.min: the value of \(\gamma\) that gives the best result for the crossvalidation.
  2. lambda.1se: the largest value of \(\gamma\) such that the MSE is within 1 standard error of the best result from the cross validation.
lasso_cv$lambda.min
#> [1] 0.07558811
lasso_cv$lambda.1se
#> [1] 0.1666817

We will (rather arbitrarily) use lambda.min here to fit the final model and generate predictions on the test data. Note that we don’t actually have to redo the fitting, we can just use our existing lasso_cv object, which already contains the fitted models for a range of lambda values. We can use the predict function and specify the s argument (which confusingly sets lambda in this case) to make predictions on the test data.

lasso_preds <- predict(lasso_cv, s = lasso_cv$lambda.min, newx = testX)
## Calculate MSE
(lasso_mse <- MSE(testY, lasso_preds))
#> [1] 0.3754368

2. Do the same for ridge regression.

Solution

set.seed(123)
ridge_cv <- cv.glmnet(trainX, trainY, alpha = 0, 
                      nfolds = K, type.measure = "mse")
ridge_cv
#> 
#> Call:  cv.glmnet(x = trainX, y = trainY, type.measure = "mse", nfolds = K,      alpha = 0) 
#> 
#> Measure: Mean-Squared Error 
#> 
#>     Lambda Index Measure     SE Nonzero
#> min   9.32   100  0.4648 0.1182     200
#> 1se  43.25    67  0.5820 0.2131     200
plot(ridge_cv)

Note that we can extract the fitted ridge regression object from the CV result and make the coefficient profile plot as before.

plot(ridge_cv$glmnet.fit, xvar = "lambda")

We can look for the gamma values that give the best result. Here you have two possibilities :

  1. lambda.min: the value of \(\gamma\) that gives the best result for the crossvalidation.
  2. lambda.1se: the largest value of \(\gamma\) such that the MSE is within 1 standard error of the best result from the cross validation.
ridge_cv$lambda.min
#> [1] 9.318855
ridge_cv$lambda.1se
#> [1] 43.25429

We will (rather arbitrarily) use lambda.min here to fit the final model and generate predictions on the test data. Note that we don’t actually have to redo the fitting, we can just use our existing ridge_cv object, which already contains the fitted models for a range of lambda values. We can use the predict function and specify the s argument (which confusingly sets lambda in this case) to make predictions on the test data.

ridge_preds <- predict(ridge_cv, s = ridge_cv$lambda.min, newx = testX)
## Calculate MSE
(ridge_mse <- MSE(testY, ridge_preds))
#> [1] 0.3066121

3. Which of the models considered (PCR, lasso, ridge) performs best?.

Solution

Based on the MSE, the ridge model performs best on the test data.

Model MSE
PCR 0.3655052
Lasso 0.3754368
Ridge 0.3066121

  1. Note: log() in R is the natural logarithm by default (base \(e\)) and we will also use this notation in the text (like the x-axis title on the plot above). This might be different from the notation that you’re used to (\(\ln()\)). To take logarithms with a different base in R you can specify the base = argument of log or use the shorthand functions log10(x) and log2(x) for base 10 and 2, respectively↩︎

LS0tCnRpdGxlOiAiQW5hbHlzaXMgb2YgSGlnaCBEaW1lbnNpb25hbCBEYXRhIC0gTGFiIDMiCnN1YnRpdGxlOiAiUGVuYWxpemVkIHJlZ3Jlc3Npb24gdGVjaG5pcXVlcyBmb3IgaGlnaC1kaW1lbnNpb25hbCBkYXRhIgphdXRob3I6ICJBZGFwdGVkIGJ5IE1pbGFuIE1hbGZhaXQiCmRhdGU6ICIwNSBOb3YgMjAyMCIKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBjb2xsYXBzZSA9IFRSVUUsCiAgY29tbWVudCA9ICIjPiIsCiAgZmlnLmFsaWduID0gImNlbnRlciIsCiAgb3V0LndpZHRoID0gIjEwMCUiCikKb3B0aW9ucygKICB3YXJuUGFydGlhbE1hdGNoRG9sbGFyID0gRkFMU0UsCiAgd2FyblBhcnRpYWxNYXRjaEF0dHIgPSBGQUxTRSwKICB3YXJuUGFydGlhbE1hdGNoQXJncyA9IEZBTFNFCikKYGBgCgoqKioKCmBgYHtyIGxpYnJhcmllcywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KIyMgaW5zdGFsbCBwYWNrYWdlcyB3aXRoOgojIyBpbnN0YWxsLnBhY2thZ2VzKGMoImdsbW5ldCIsICJwbHMiLCAiTm9ybWFsQmV0YVByaW1lIiwgImJvb3QiKSkKbGlicmFyeShOb3JtYWxCZXRhUHJpbWUpCmxpYnJhcnkoZ2xtbmV0KQpsaWJyYXJ5KHBscykKbGlicmFyeShib290KQpgYGAKCgojIEludHJvZHVjdGlvbgoKKipJbiB0aGlzIGxhYiBzZXNzaW9uIHdlIHdpbGwgbG9vayBhdCB0aGUgZm9sbG93aW5nIHRvcGljcyoqCgogIC0gRGVtb25zdHJhdGUgd2h5IGxvdyBkaW1lbnNpb25hbCBwcmVkaWN0aW9uIG1vZGVsaW5nIGZhaWxzIGluIGhpZ2ggZGltZW5zaW9uLgogIC0gQ2Fycnkgb3V0IFByaW5jaXBhbCBDb21wb25lbnQgUmVncmVzc2lvbiAoUENSKQogIC0gVXNlIGBnbG1uZXQoKWAgdG8gY2Fycnkgb3V0IHJpZGdlIHJlZ3Jlc3Npb24sIGxhc3NvIGFuZCBlbGFzdGljIG5ldAogIC0gRXZhbHVhdGlvbiBvZiB0aGVzZSBwcmVkaWN0aW9uIG1vZGVscwogIAoKIyMgVGhlIGRhdGFzZXQKCkluIHRoaXMgcHJhY3RpY2FsLCB3ZSB3aWxsIHVzZSB0aGUgZGF0YXNldCBgZXllZGF0YWAgcHJvdmlkZWQgYnkKdGhlIFtfX05vcm1hbEJldGFQcmltZV9fIHBhY2thZ2VdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9Ob3JtYWxCZXRhUHJpbWUvaW5kZXguaHRtbCkuClRoaXMgZGF0YXNldCBjb250YWlucyBnZW5lIGV4cHJlc3Npb24gZGF0YSBvZiAyMDAKZ2VuZXMgZm9yIDEyMCBzYW1wbGVzLiBUaGUgZGF0YSBvcmlnaW5hdGVzIGZyb20gbWljcm9hcnJheSBleHBlcmltZW50cwpvZiBtYW1tYWxpYW4gZXllIHRpc3N1ZSBzYW1wbGVzLgoKVGhlIGRhdGFzZXQgY29uc2lzdHMgb2YgdHdvIG9iamVjdHM6CgogIC0gYGdlbmVzYDogYSAkMTIwIFx0aW1lcyAyMDAkIG1hdHJpeCB3aXRoIHRoZSBleHByZXNzaW9uIGxldmVscyBvZiAyMDAgZ2VuZXMKICAoY29sdW1ucykgZm9yIDEyMCBzYW1wbGVzIChyb3dzKQogIC0gYHRyaW0zMmA6IGEgdmVjdG9yIHdpdGggMTIwIGV4cHJlc3Npb24gbGV2ZWxzIG9mIHRoZSBUUklNMzIgZ2VuZS4KCgpgYGB7ciBsb2FkLWRhdGF9CmRhdGEoZXllZGF0YSkKIyMgTG9vayBhdCBvYmplY3RzIHRoYXQgd2VyZSBqdXN0IGxvYWRlZApzdHIoZ2VuZXMpCnN0cih0cmltMzIpCmBgYAoKVGhlIGdvYWwgb2YgdGhpcyBleGVyY2lzZSBpcyB0byBwcmVkaWN0IHRoZSBleHByZXNzaW9uIGxldmVscyBvZgpUUklNMzIgZnJvbSB0aGUgZXhwcmVzc2lvbiBsZXZlbHMgb2YgdGhlIDIwMCBnZW5lcyBtZWFzdXJlZCBpbiB0aGUKbWljcm9hcnJheSBleHBlcmltZW50LiBGb3IgdGhpcywgaXQgbWFrZXMgc2Vuc2UgdG8gc3RhcnQgYnkgY29uc3RydWN0aW5nCmNlbnRlcmVkIChhbmQgcG9zc2libHkgc2NhbGVkKSBkYXRhLiBXZSBzdG9yZSB0aGlzIGluIHR3byBtYXRyaWNlcwpgWGAgYW5kIGBZYDoKCmBgYHtyIHByZXBhcmUtZGF0YX0KWCA8LSBzY2FsZShnZW5lcywgY2VudGVyID0gVFJVRSwgc2NhbGUgPSBUUlVFKSAKWSA8LSBzY2FsZSh0cmltMzIsIGNlbnRlciA9IFRSVUUpCmBgYAoKUmVtZW1iZXIgdGhhdCBzY2FsaW5nIGF2b2lkcyB0aGF0IGRpZmZlcmVuY2VzIGluIGxldmVscyBvZiBtYWduaXR1ZGUKd2lsbCBnaXZlIG9uZSB2YXJpYWJsZSAoZ2VuZSkgbW9yZSBpbmZsdWVuY2UgaW4gdGhlIHJlc3VsdC4gVGhpcyBoYXMKYmVlbiBpbGx1c3RyYXRlZCBpbiB0aGUgW3NlY29uZCBwcmFjdGljYWwgc2Vzc2lvbl0oLi9MYWIyLVBDQS5odG1sKSBhcyB3ZWxsLgpGb3IgdGhlIGBZYCB2ZWN0b3IsIHRoaXMgaXMgbGVzcyBvZiBhbiBpc3N1ZSBhcyB3ZSdyZSB0YWxraW5nIGFib3V0IGEgc2luZ2xlIHZhcmlhYmxlLgpOb3Qgc2NhbGluZyB3aWxsIG1ha2UgdGhlIHByZWRpY3Rpb25zIGludGVycHJldGFibGUgYXMgImRldmlhdGlvbnMgZnJvbSB0aGUKbWVhbiIuCgojIyBUaGUgY3Vyc2Ugb2Ygc2luZ3VsYXJpdHkKCldlIGJlZ2luIGJ5IGFzc3VtaW5nIHRoYXQgdGhlIHByZWRpY3RvcnMgYW5kIHRoZSBvdXRjb21lIGhhdmUgYmVlbgpjZW50ZXJlZCBzbyB0aGF0IHRoZSBpbnRlcmNlcHQgaXMgMC4KV2UgYXJlIHByZXNlbnRlZCB3aXRoIHRoZSB1c3VhbCByZWdyZXNzaW9uIG1vZGVsOgoKJCQKWV9pPVxiZXRhX2kgWF97aTF9K1xkb3RzK1xiZXRhX3BYX3tpcH0rXGVwc2lsb25faSBcXCAKXHRleHR7IE9yIH0gXG1hdGhiZntZfT17XG1hdGhiZntYfX17XGJvbGRzeW1ib2x7XGJldGF9fSAre1xib2xkc3ltYm9se1xlcHNpbG9ufX0KJCQKCk91ciBnb2FsIGlzIHRvIGdldCB0aGUgbGVhc3Qgc3F1YXJlcyBlc3RpbWF0b3Igb2YKJHtcYm9sZHN5bWJvbHtcYmV0YX19JCwgZ2l2ZW4gYnkKCiQkClxoYXR7e1xib2xkc3ltYm9se1xiZXRhfX19PSAoXG1hdGhiZntYfV5Ue1xtYXRoYmZ7WH19KV57LTF9e1xtYXRoYmZ7WH19XlR7XG1hdGhiZntZfX0KJCQKCmluIHdoaWNoIHRoZSAkcCBcdGltZXMgcCQgbWF0cml4CiQoe1xtYXRoYmZ7WH19XlR7XG1hdGhiZntYfX0pXnstMX0kIGlzIGNydWNpYWwhClRvIGJlIGFibGUgdG8gY2FsY3VsYXRlIHRoZSBpbnZlcnNlIG9mICR7XG1hdGhiZntYfX1eVCBcbWF0aGJme1h9JCwKaXQgaGFzIHRvIGJlIG9mIGZ1bGwgcmFuayAkcCQsIHdoaWNoIHdvdWxkIGJlIDIwMCBpbiB0aGlzIGNhc2UuCkxldCdzIGNoZWNrIHRoaXM6CgpgYGB7ciBzaW5ndWxhcml0eS1wcm9ibGVtLCBlcnJvcj1UUlVFfQpkaW0oWCkgIyAxMjAgeCAyMDAsIHNvIHAgPiBuIQpxcihYKSRyYW5rCgpYdFggPC0gY3Jvc3Nwcm9kKFgpICMgY2FsY3VsYXRlcyB0KFgpICUqJSBYIG1vcmUgZWZmaWNpZW50bHkKcXIoWHRYKSRyYW5rCgojIFRyeSB0byBpbnZlcnQgdXNpbmcgc29sdmU6IApzb2x2ZShYdFgpCmBgYAoKV2UgcmVhbGl6ZSB3ZSBjYW5ub3QgY29tcHV0ZQokKHtcbWF0aGJme1h9fV5Ue1xtYXRoYmZ7WH19KV57LTF9JCBiZWNhdXNlIHRoZSByYW5rIG9mCiQoe1xtYXRoYmZ7WH19XlR7XG1hdGhiZntYfX0pJCBpcyBsZXNzIHRoYW4gJHAkIGhlbmNlIHdlIGNhbuKAmXQKZ2V0ICRcaGF0e3tcYm9sZHN5bWJvbHtcYmV0YX19fSQgYnkgbWVhbnMgb2YgbGVhc3Qgc3F1YXJlcyEgClRoaXMgaXMgZ2VuZXJhbGx5IHJlZmVycmVkIHRvIGFzIHRoZSBfX1tzaW5ndWxhcml0eV0oaHR0cHM6Ly93d3cuc3RhdGlzdGljcy5jb20vZ2xvc3Nhcnkvc2luZ3VsYXJpdHkvKSBwcm9ibGVtX18uCgoKIyBQcmluY2lwYWwgY29tcG9uZW50IHJlZ3Jlc3Npb24KCkEgZmlyc3Qgd2F5IHRvIGRlYWwgd2l0aCB0aGlzIHNpbmd1bGFyaXR5LCBpcyB0byBieXBhc3MgaXQgdXNpbmcgcHJpbmNpcGFsIGNvbXBvbmVudHMuClNpbmNlICRcbWluKG4scCkgPSBuID0gMTIwJCwgClBDQSB3aWxsIGdpdmUgYHIgbWluKGRpbShYKSlgIGNvbXBvbmVudHMsIGVhY2ggYmVpbmcgYSBsaW5lYXIgY29tYmluYXRpb24gb2YgdGhlIAokcCQgPSBgciBuY29sKFgpYCB2YXJpYWJsZXMuClRoZXNlIGByIG1pbihkaW0oWCkpYCBQQ3MgY29udGFpbiBhbGwgaW5mb3JtYXRpb24gcHJlc2VudCBpbiB0aGUgb3JpZ2luYWwgZGF0YS4KV2UgY291bGQgYXMgd2VsbCB1c2UgYW4gYXBwcm94aW1hdGlvbiBvZiAke1xtYXRoYmZ7WH19JCwgaS5lIHVzaW5nIGp1c3QgYSBmZXcgKCRrPDEyMCQpIFBDcy4KU28gd2UgdXNlIFBDQSBhcyBhIG1ldGhvZCBmb3IgcmVkdWNpbmcgdGhlIGRpbWVuc2lvbnMgd2hpbGUgcmV0YWluaW5nCmFzIG11Y2ggdmFyaWF0aW9uIGJldHdlZW4gdGhlIG9ic2VydmF0aW9ucyBhcyBwb3NzaWJsZS4KT25jZSB3ZSBoYXZlIHRoZXNlIFBDcywgd2UgY2FuIHVzZSB0aGVtIGFzIHZhcmlhYmxlcyBpbiBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsLgoKIyMgQ2xhc3NpYyBsaW5lYXIgcmVncmVzc2lvbiBvbiBQQ3MKCldlIGZpcnN0IGNvbXB1dGUgdGhlIFBDQSBvbiB0aGUgZGF0YSB3aXRoIGBwcmNvbXBgLgpXZSB3aWxsIHVzZSBhbiBhcmJpdHJhcnkgY3V0b2ZmIG9mICRrID0gNCQgUENzIHRvIGlsbHVzdHJhdGUgdGhlIHByb2Nlc3Mgb2YgcGVyZm9ybWluZyByZWdyZXNzaW9uIG9uIHRoZSBQQ3MuCgpgYGB7ciBQQy1yZWdyZXNzaW9ufQprIDwtIDQgIyBBcmJpdHJhcmlseSBjaG9zZW4gaz00CnBjYSA8LSBwcmNvbXAoWCkKVmsgPC0gcGNhJHJvdGF0aW9uWywgMTprXSAjIHRoZSBsb2FkaW5ncyBtYXRyaXgKWmsgPC0gcGNhJHhbLCAxOmtdICMgdGhlIHNjb3JlcyBtYXRyaXgKCiMgVXNlIHRoZSBzY29yZXMgaW4gY2xhc3NpYyBsaW5lYXIgcmVncmVzc2lvbgpwY3JfbW9kZWwxIDwtIGxtKFkgfiBaaykKc3VtbWFyeShwY3JfbW9kZWwxKQpgYGAKCkFzICRcbWF0aGJme1h9JCBhbmQgJFxtYXRoYmZ7WX0kIGFyZSBjZW50ZXJlZCwgdGhlIGludGVyY2VwdCBpcyAKYXBwcm94aW1hdGVseSAwLgoKVGhlIG91dHB1dCBzaG93cyB0aGF0IFBDMSBhbmQgUEM0IGhhdmUgYSAkXGJldGEkIGVzdGltYXRlIHRoYXQgCmRpZmZlcnMgc2lnbmlmaWNhbnRseSBmcm9tIDAgKGF0ICRwIDwgMC4wNSQpLCBidXQgdGhlIHJlc3VsdHMgY2FuJ3QgYmUgcmVhZGlseSAKaW50ZXJwcmV0ZWQsIHNpbmNlIHdlIGhhdmUgbm8gaW1tZWRpYXRlIGludGVycHJldGF0aW9uIG9mIHRoZSBQQ3MuCgoKIyMgVXNpbmcgdGhlIHBhY2thZ2UgYHBsc2AKClBDUiBjYW4gYWxzbyBiZSBwZXJmb3JtZWQgdXNpbmcgdGhlIGBwY3IoKWAgZnVuY3Rpb24gZnJvbSB0aGUKcGFja2FnZSAqW3Bsc10oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1wbHMpKgpfX2RpcmVjdGx5IG9uIHRoZSBkYXRhX18gKHNvIHdpdGhvdXQgaGF2aW5nIHRvIGZpcnN0IHBlcmZvcm0gdGhlIFBDQSBtYW51YWxseSkuCldoZW4gdXNpbmcgdGhpcyBmdW5jdGlvbiwgeW91IGhhdmUgdG8ga2VlcCBhIGZldyB0aGluZ3MgaW4gbWluZDoKCiAgMS4gdGhlIG51bWJlciBvZiBjb21wb25lbnRzIChQQ3MpIHRvIHVzZSBpcyBwYXNzZWQgd2l0aCB0aGUgYXJndW1lbnQgYG5jb21wYAogIDIuIHRoZSBmdW5jdGlvbiBhbGxvd3MgeW91IHRvIHNjYWxlIChzZXQgYHNjYWxlID0gVFJVRWApIGFuZAogIGNlbnRlciAoc2V0IGBjZW50ZXIgPSBUUlVFYCkgdGhlIHByZWRpY3RvcnMgZmlyc3QgKGluIHRoZSBleGFtcGxlIGhlcmUsICRcbWF0aGJme1h9JCBoYXMgYWxyZWFkeSBiZWVuIGNlbnRlcmVkIGFuZCBzY2FsZWQpLgogIApZb3UgY2FuIHVzZSB0aGUgZnVuY3Rpb24gYHBjcigpYCBpbiBtdWNoIHRoZSBzYW1lIHdheSBhcyB5b3Ugd291bGQKdXNlIGBsbSgpYC4gVGhlIHJlc3VsdGluZyBmaXQgY2FuIGVhc2lseSBiZSBleGFtaW5lZCB1c2luZyB0aGUgCmZ1bmN0aW9uIGBzdW1tYXJ5KClgLCBidXQgdGhlIG91dHB1dCBsb29rcyBxdWl0ZSBkaWZmZXJlbnQgZnJvbQp3aGF0IHlvdSB3b3VsZCBnZXQgZnJvbSBgbG1gLgoKYGBge3IgUEMtcmVncmVzc2lvbi1wbHMtcGFja2FnZX0KIyBYIGlzIGFscmVhZHkgc2NhbGVkIGFuZCBjZW50ZXJlZCwgc28gdGhhdCdzIG5vdCBuZWVkZWQuCnBjcl9tb2RlbDIgPC0gcGNyKFkgfiBYLCBuY29tcCA9IDQpCnN1bW1hcnkocGNyX21vZGVsMikKYGBgCgpGaXJzdCBvZiBhbGwgdGhlIG91dHB1dCBzaG93cyB5b3UgdGhlIGRhdGEgZGltZW5zaW9ucyBhbmQgdGhlIGZpdHRpbmcKbWV0aG9kIHVzZWQuIEluIHRoaXMgY2FzZSwgdGhhdCBpcyBQQyBjYWxjdWxhdGlvbiBiYXNlZCBvbiBTVkQuIFRoZQpgc3VtbWFyeSgpYCBmdW5jdGlvbiBhbHNvIHByb3ZpZGVzIHRoZSBwZXJjZW50YWdlIG9mIHZhcmlhbmNlCmV4cGxhaW5lZCBpbiB0aGUgcHJlZGljdG9ycyBhbmQgaW4gdGhlIHJlc3BvbnNlIHVzaW5nIGRpZmZlcmVudCBudW1iZXJzCm9mIGNvbXBvbmVudHMuIEZvciBleGFtcGxlLCB0aGUgZmlyc3QgUEMgb25seSBjYXB0dXJlcyA2MS4yMiUgb2YgYWxsCnRoZSB2YXJpYW5jZSwgb3IgaW5mb3JtYXRpb24gaW4gdGhlIHByZWRpY3RvcnMgYW5kIGl0IGV4cGxhaW5zIDYyLjklCm9mIHRoZSB2YXJpYW5jZSBpbiB0aGUgb3V0Y29tZS4gTm90ZSB0aGF0IGZvciBib3RoIG1ldGhvZHMgdGhlIGNob2ljZSBvZgp0aGUgbnVtYmVyIG9mIHByaW5jaXBhbCBjb21wb25lbnRzIHdhcyBhcmJpdHJhcnkgY2hvc2VuIHRvIGJlIDQuCgpBdCBhIGxhdGVyIHN0YWdlLCB3ZSB3aWxsIGxvb2sgYXQgaG93IHRvIGNob29zZSB0aGUgbnVtYmVyIG9mIGNvbXBvbmVudHMKdGhhdCBoYXMgdGhlIF9fc21hbGxlc3QgcHJlZGljdGlvbiBlcnJvcl9fLgoKCiMgUmlkZ2VzLCBMYXNzb3MgYW5kIEVsYXN0aWMgTmV0cyB7I2VsbmV0LXRoZW9yeX0KClJpZGdlIHJlZ3Jlc3Npb24sIGxhc3NvIHJlZ3Jlc3Npb24gYW5kIGVsYXN0aWMgbmV0cyBhcmUgYWxsIGNsb3NlbHkKcmVsYXRlZCB0ZWNobmlxdWVzLCBiYXNlZCBvbiB0aGUgc2FtZSBpZGVhOiBhZGQgYSBwZW5hbHR5IHRlcm0gdG8gCnRoZSBlc3RpbWF0aW5nIGZ1bmN0aW9uIHNvICQoe1xtYXRoYmZ7WH19XlR7XG1hdGhiZntYfX0pJApiZWNvbWVzIGZ1bGwgcmFuayBhZ2FpbiBhbmQgaXMgaW52ZXJ0aWJsZS4gVHdvIGRpZmZlcmVudCBwZW5hbHR5IAp0ZXJtcyBvciByZWd1bGFyaXphdGlvbiBtZXRob2RzIGNhbiBiZSB1c2VkOgoKMS4gTDEgcmVndWxhcml6YXRpb246IHRoaXMgcmVndWxhcml6YXRpb24gYWRkcyBhIHRlcm0gJHtcZ2FtbWFfMVx8XGJvbGRzeW1ib2x7XGJldGF9XHxfezF9fSQgdG8gdGhlIGVzdGltYXRpbmcgZXF1YXRpb24uClRoZSB0ZXJtIHdpbGwgYWRkIGEgcGVuYWx0eSBiYXNlZCBvbiB0aGUgKmFic29sdXRlIHZhbHVlKiBvZiB0aGUKbWFnbml0dWRlIG9mIHRoZSBjb2VmZmljaWVudHMuIFRoaXMgaXMgdXNlZCBieSB0aGUgX19sYXNzbyByZWdyZXNzaW9uX18KIAokJAogXGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19XntcdGV4dHtsYXNzb319ID0gXHRleHR7YXJnbWlufV97XGJvbGRzeW1ib2x7XGJldGF9fVxkaXNwbGF5c3R5bGUoeyhcbWF0aGJme1l9LVxtYXRoYmZ7WH1cYm9sZHN5bWJvbHtcYmV0YX0pXlQoXG1hdGhiZntZfS1cbWF0aGJme1h9XGJvbGRzeW1ib2x7XGJldGF9KSt7XGdhbW1hXzFcfFxib2xkc3ltYm9se1xiZXRhfVx8X3sxfX19XGRpc3BsYXlzdHlsZSkKJCQKCjIuIEwyIHJlZ3VsYXJpemF0aW9uOiB0aGlzIHJlZ3VsYXJpemF0aW9uIGFkZHMgYSB0ZXJtICR7XGdhbW1hXzJcfFxib2xkc3ltYm9se1xiZXRhfVx8X3syfV57Mn19JCB0byB0aGUgZXN0aW1hdGluZyBlcXVhdGlvbi4KVGhlIHBlbmFsdHkgdGVybSBpcyBiYXNlZCBvbiB0aGUgc3F1YXJlIG9mIHRoZSBtYWduaXR1ZGUgb2YgdGhlIApjb2VmZmljaWVudHMuIFRoaXMgaXMgdXNlZCBieSBfX3JpZGdlIHJlZ3Jlc3Npb25fXy4KCiQkCiBcaGF0e1xib2xkc3ltYm9se1xiZXRhfX1ee1x0ZXh0e3JpZGdlfX0gPSBcdGV4dHthcmdtaW59X3tcYm9sZHN5bWJvbHtcYmV0YX19XGRpc3BsYXlzdHlsZSh7KFxtYXRoYmZ7WX0tXG1hdGhiZntYfVxib2xkc3ltYm9se1xiZXRhfSleVChcbWF0aGJme1l9LVxtYXRoYmZ7WH1cYm9sZHN5bWJvbHtcYmV0YX0pK3tcZ2FtbWFfMlx8XGJvbGRzeW1ib2x7XGJldGF9XHxfezJ9XnsyfX19XGRpc3BsYXlzdHlsZSkKJCQKCkVsYXN0aWMgbmV0cyBjb21iaW5lIGJvdGggdHlwZXMgb2YgcmVndWxhcml6YXRpb25zLiBJdCBkb2VzIHNvIGJ5IAppbnRyb2R1Y2luZyBhICRcYWxwaGEkIG1peGluZyBwYXJhbWV0ZXIgdGhhdCBlc3NlbnRpYWxseSBjb21iaW5lcwp0aGUgTDEgYW5kIEwyIG5vcm1zIGluIGEgd2VpZ2h0ZWQgYXZlcmFnZS4KCiQkCiBcaGF0e1xib2xkc3ltYm9se1xiZXRhfX1ee1x0ZXh0e2VsLm5ldH19ID0gXHRleHR7YXJnbWlufV97XGJvbGRzeW1ib2x7XGJldGF9fVxkaXNwbGF5c3R5bGUoeyhcbWF0aGJme1l9LVxtYXRoYmZ7WH1cYm9sZHN5bWJvbHtcYmV0YX0pXntUfShcbWF0aGJme1l9LVxtYXRoYmZ7WH1cYm9sZHN5bWJvbHtcYmV0YX0pK3tcYWxwaGEgXGdhbW1hXzFcfFxib2xkc3ltYm9se1xiZXRhfVx8X3sxfX0rIHsoMSAtIFxhbHBoYSlcZ2FtbWFfMlx8XGJvbGRzeW1ib2x7XGJldGF9XHxfezJ9XnsyfX19XGRpc3BsYXlzdHlsZSkKJCQKCgoKIyBFeGVyY2lzZTogVmVyaWZpY2F0aW9uIG9mIHJpZGdlIHJlZ3Jlc3Npb24KCkluIGxlYXN0IHNxdWFyZSByZWdyZXNzaW9uIHRoZSBtaW5pbWl6YXRpb24gb2YgdGhlIGVzdGltYXRpb24gZnVuY3Rpb24KJHx7XG1hdGhiZntZfSAtIFxtYXRoYmZ7WH0gXGJvbGRzeW1ib2x7XGJldGF9fVx8XnsyfV97Mn0kIGxlYWRzIHRvIHRoZSBzb2x1dGlvbiAke1xib2xkc3ltYm9se1xoYXR7XGJldGF9fT0oXG1hdGhiZntYXlRYfSleey0xfVxtYXRoYmZ7WF5UWX19JC4gCgpGb3IgdGhlIHBlbmFsaXplZCBsZWFzdCBzcXVhcmVzIGNyaXRlcmlvbiB1c2VkIGJ5IHJpZGdlIHJlZ3Jlc3Npb24sIHlvdSBtaW5pbWl6ZSAKJFx8e1xtYXRoYmZ7WX0tXG1hdGhiZntYfVxib2xkc3ltYm9se1xiZXRhfVx8XnsyfV97Mn19K1xnYW1tYXtcYm9sZHN5bWJvbHtcfFxiZXRhXHxeezJ9X3syfX19JAp3aGljaCBsZWFkcyB0byBmb2xsb3dpbmcgc29sdXRpb246CgokJAp7XGJvbGRzeW1ib2x7XGhhdHtcYmV0YX19PShcbWF0aGJme1heVFh9fStcZ2FtbWF7XG1hdGhiZntJfX0pXnstMX17XG1hdGhiZntYXlRZfX0KJCQKCndoZXJlICRcbWF0aGJme0l9JCBpcyB0aGUgJHAgXHRpbWVzIHAkIGlkZW50aXR5IG1hdHJpeC4KClRoZSByaWRnZSBwYXJhbWV0ZXIgJFxnYW1tYSQgKnNocmlua3MqIHRoZSBjb2VmZmljaWVudHMgdG93YXJkcyAwLCB3aXRoICRcZ2FtbWEgPSAwJCBiZWluZyBlcXVpdmFsZW50IHRvIE9MUyAobm8gc2hyaW5rYWdlKSBhbmQgJFxnYW1tYSA9ICtcaW5mdHkkIGJlaW5nIGVxdWl2YWxlbnQgdG8gc2V0dGluZyBhbGwgJFxoYXR7XGJldGF9JCdzIHRvIDAuClRoZSBvcHRpbWFsIHBhcmFtZXRlciBsaWVzIHNvbWV3aGVyZSBpbiBiZXR3ZWVuIGFuZCBuZWVkcyB0byBiZSB0dW5lZCBieSB0aGUgdXNlci4KCgojIyBUYXNrcyB7LX0KClNvbHZlIHRoZSBmb2xsb3dpbmcgZXhlcmNpc2VzIHVzaW5nIFIuCgojIyMjIDEuIFZlcmlmeSB0aGF0ICR7XG1hdGhiZnsoWF5UWH19K1xnYW1tYXtcbWF0aGJme0l9fSkkIGhhcyByYW5rICQyMDAkLCBmb3IgYW55ICRcZ2FtbWE+MCQgb2YgeW91ciBjaG9pY2UuIHstfQoKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CmBgYHtyfQpYdFggPC0gY3Jvc3Nwcm9kKFgpCnAgPC0gbmNvbChYKQpnYW1tYSA8LSAyICMgTXkgY2hvaWNlCgojIENvbXB1dGUgcGVuYWxpemVkIG1hdHJpeApYdFhfZ2FtbWFJIDwtIFh0WCArIChnYW1tYSAqIGRpYWcocCkpCmRpbShYdFhfZ2FtbWFJKQpxcihYdFhfZ2FtbWFJKSRyYW5rID09IDIwMCAjIGluZGVlZApgYGAKPC9kZXRhaWxzPgoKCiMjIyMgMi4gQ2hlY2sgdGhhdCB0aGUgaW52ZXJzZSBvZiAke1xtYXRoYmZ7KFheVFh9fStcZ2FtbWF7XG1hdGhiZntJfX0pJCBjYW4gYmUgY29tcHV0ZWQuIHstfQoKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CmBgYHtyfQojIFllcywgaXQgY2FuIGJlIGNvbXB1dGVkIChubyBlcnJvcikKWHRYX2dhbW1hSV9pbnYgPC0gc29sdmUoWHRYX2dhbW1hSSkKc3RyKFh0WF9nYW1tYUlfaW52KQpgYGAKPC9kZXRhaWxzPgoKCiMjIyMgMy4gRmluYWxseSwgY29tcHV0ZSAke1xib2xkc3ltYm9se1xoYXR7XGJldGF9fT0oXG1hdGhiZntYXlRYfX0rXGdhbW1he1xtYXRoYmZ7SX19KV57LTF9e1xtYXRoYmZ7WF5UWX19JC4gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KYGBge3IgcmlkZ2UtYmV0YS1lc3RpbWF0ZXN9CiMjIENhbGN1bGF0ZSByaWRnZSBiZXRhIGVzdGltYXRlcwojIyBVc2UgYGRyb3BgIHRvIGRyb3AgZGltZW5zaW9ucyBhbmQgY3JlYXRlIHZlY3RvcgpyaWRnZV9iZXRhcyA8LSBkcm9wKFh0WF9nYW1tYUlfaW52ICUqJSB0KFgpICUqJSBZKQpsZW5ndGgocmlkZ2VfYmV0YXMpICMgb25lIGZvciBldmVyeSBnZW5lCnN1bW1hcnkocmlkZ2VfYmV0YXMpCmBgYAoKV2UgaGF2ZSBub3cgbWFudWFsbHkgY2FsY3VsYXRlZCB0aGUgcmlkZ2UgcmVncmVzc2lvbiBlc3RpbWF0ZXMuCgo8L2RldGFpbHM+CgoKCiMgUGVyZm9ybWluZyByaWRnZSBhbmQgbGFzc28gcmVncmVzc2lvbiB3aXRoIGBnbG1uZXRgCgpUaGUgcGFja2FnZSAqW2dsbW5ldF0oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1nbG1uZXQpKiBwcm92aWRlcyBhCmZ1bmN0aW9uIGBnbG1uZXQoKWAgdGhhdCBhbGxvd3MgeW91IHRvIGZpdCBhbGwgdGhyZWUgdHlwZXMgb2YgcmVncmVzc2lvbnMuIFdoaWNoCnR5cGUgaXMgdXNlZCwgY2FuIGJlIGRldGVybWluZWQgYnkgc3BlY2lmeWluZyB0aGUgYGFscGhhYCBhcmd1bWVudC4gRm9yIGEKX19yaWRnZSByZWdyZXNzaW9uX18sIHlvdSBzZXQgYGFscGhhYCB0byAwLCBhbmQgZm9yIGEgX19sYXNzbyByZWdyZXNzaW9uX18geW91CnNldCBgYWxwaGFgIHRvIDEuIE90aGVyIGBhbHBoYWAgdmFsdWVzIGJldHdlZW4gMCBhbmQgMSB3aWxsIGZpdCBhIGZvcm0gb2YKZWxhc3RpYyBuZXQuIFRoaXMgZnVuY3Rpb24gaGFzIHNsaWdodGx5IGRpZmZlcmVudCBzeW50YXggZnJvbSB0aGUgb3RoZXIKbW9kZWwtZml0dGluZyBmdW5jdGlvbnMuIFRvIGJlIGFibGUgdG8gdXNlIGl0LCB5b3UgaGF2ZSB0byBwYXNzIGEgYHhgIG1hdHJpeCBhcwp3ZWxsIGFzIGEgYHlgIHZlY3RvciwgYW5kIHlvdSBkb24ndCB1c2UgdGhlIGZvcm11bGEgc3ludGF4LgoKVGhlIGdhbW1hIHZhbHVlLCB3aGljaCBjb250cm9scyB0aGUgInN0cmVuZ3RoIiBvZiB0aGUgcGVuYWx0eSwgY2FuIGJlIHBhc3NlZCBieQp0aGUgYXJndW1lbnQgYGxhbWJkYWAgKG5vdGF0aW9uIGlzbid0IGFsd2F5cyBjb25zaXN0ZW50IGJldHdlZW4gdGV4dCBib29rcyBhbmQKc29mdHdhcmUuLi4pLiBUaGUgZnVuY3Rpb24gYGdsbW5ldCgpYCBjYW4gYWxzbyBjYXJyeSBvdXQgYSBzZWFyY2ggZm9yIGZpbmRpbmcKdGhlIGJlc3QgZ2FtbWEgdmFsdWUgZm9yIGEgZml0LiBUaGlzIGNhbiBiZSBkb25lIGJ5IHBhc3NpbmcgbXVsdGlwbGUgdmFsdWVzIHRvCnRoZSBhcmd1bWVudCBgbGFtYmRhYC4gSWYgbm90IHN1cHBsaWVkLCBgZ2xtbmV0YCB3aWxsIGdlbmVyYXRlIGEgcmFuZ2Ugb2YgdmFsdWVzCml0c2VsZiwgYmFzZWQgb24gdGhlIGRhdGEgd2hlcmVieSB0aGUgbnVtYmVyIG9mIHZhbHVlcyBjYW4gYmUgY29udHJvbGxlZCB3aXRoCnRoZSBgbmxhbWJkYWAgYXJndW1lbnQuIFRoaXMgaXMgZ2VuZXJhbGx5IHRoZSByZWNvbW1lbmRlZCB3YXkgdG8gdXNlIGBnbG1uZXRgLApzZWUgYD9nbG1uZXRgIGZvciBkZXRhaWxzLgoKRm9yIGEgdGhvcm91Z2ggaW50cm9kdWN0aW9uIHRvIHRoZSBfX2dsbW5ldF9fIHBhY2thZ2UgYW5kIGVsYXN0aWMgbmV0IG1vZGVscyBpbgpnZW5lcmFsLCBzZWUgdGhlCltnbG1uZXQgaW50cm9kdWN0aW9uIHZpZ25ldHRlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ2xtbmV0L3ZpZ25ldHRlcy9nbG1uZXQucGRmKQoKCiMjIERlbW9uc3RyYXRpb246IFJpZGdlIHJlZ3Jlc3Npb24gey19CgpMZXQncyBwZXJmb3JtIGEgcmlkZ2UgcmVncmVzc2lvbiBpbiBvcmRlciB0byBwcmVkaWN0IGV4cHJlc3Npb24gbGV2ZWxzCm9mIHRoZSBUUklNMzIgZ2VuZSB1c2luZyB0aGUgMjAwIGdlbmUgcHJvYmVzIGRhdGEuIFdlIGNhbiBzdGFydCBieQp1c2luZyBhICRcZ2FtbWEkIHZhbHVlIG9mIDIuCgpgYGB7ciBnbG1uZXQtcmlkZ2UtcmVncmVzc2lvbn0KZ2FtbWEgPC0gMgpyaWRnZV9tb2RlbCA8LSBnbG1uZXQoWCwgWSwgYWxwaGEgPSAwLCBsYW1iZGEgPSBnYW1tYSkKCiMgaGF2ZSBhIGxvb2sgYXQgdGhlIGZpcnN0IDEwIGNvZWZmaWNpZW50cwpjb2VmKHJpZGdlX21vZGVsKVsxOjEwXQpgYGAKClRoZSBmaXJzdCBjb2VmZmljaWVudCBpcyB0aGUgaW50ZXJjZXB0LCBhbmQgaXMgYWdhaW4gZXNzZW50aWFsbHkgMC4gQnV0CmEgdmFsdWUgb2YgMiBmb3IgJFxnYW1tYSQgbWlnaHQgbm90IGJlIHRoZSBiZXN0IGNob2ljZSwgc28gbGV0J3Mgc2VlIGhvdwp0aGUgY29lZmZpY2llbnRzIGNoYW5nZSB3aXRoIGRpZmZlcmVudCB2YWx1ZXMgZm9yICRcZ2FtbWEkLgoKV2Ugd2lsbCBjcmVhdGUgYSAqZ3JpZCogb2YgJFxnYW1tYSQgdmFsdWVzLCBpLmUuIGEgcmFuZ2Ugb2YgdmFsdWVzIHRoYXQgd2lsbCBiZQp1c2VkIGFzIGlucHV0IGZvciB0aGUgYGdsbW5ldGAgZnVuY3Rpb24uIE5vdGUgdGhhdCB0aGlzIGZ1bmN0aW9uIGNhbiB0YWtlIGEKdmVjdG9yIG9mIHZhbHVlcyBhcyBpbnB1dCBmb3IgdGhlIGBsYW1iZGFgIGFyZ3VtZW50LCBhbGxvd2luZyB0byBmaXQgbXVsdGlwbGUKbW9kZWxzIHdpdGggdGhlIHNhbWUgaW5wdXQgZGF0YSBidXQgZGlmZmVyZW50IGh5cGVycGFyYW1ldGVycy4KCmBgYHtyIHJpZGdlLXJlZ3Jlc3Npb24tZ3JpZC1zZWFyY2h9CmdyaWQgPC0gc2VxKDEsIDEwMDAsIGJ5ID0gMTApICAjIDEgdG8gMTAwMCB3aXRoIHN0ZXBzIG9mIDEwCnJpZGdlX21vZF9ncmlkIDwtIGdsbW5ldChYLCBZLCBhbHBoYSA9IDAsIGxhbWJkYSA9IGdyaWQpCgojIFBsb3QgdGhlIGNvZWZmaWNpZW50cyBhZ2FpbnN0IHRoZSAobmF0dXJhbCkgTE9HIGxhbWJkYSBzZXF1ZW5jZSEKIyBzZWUgP3Bsb3QuZ2xtbmV0CnBsb3QocmlkZ2VfbW9kX2dyaWQsIHh2YXIgPSAibGFtYmRhIiwgeGxhYiA9ICJsb2coZ2FtbWEpIikKIyBhZGQgYSB2ZXJ0aWNhbCBsaW5lIGF0IGdhbW1hID0gMgp0ZXh0KGxvZyhnYW1tYSksIC0wLjA1LCBsYWJlbHMgPSBleHByZXNzaW9uKGdhbW1hID09IDIpLCAKICAgICBhZGogPSAtMC41LCBjb2wgPSAiZmlyZWJyaWNrIikKYWJsaW5lKHYgPSBsb2coZ2FtbWEpLCBjb2wgPSAiZmlyZWJyaWNrIiwgbHdkID0gMikKYGBgCgpUaGlzIHBsb3QgaXMga25vd24gYXMgYSBfX2NvZWZmaWNpZW50IHByb2ZpbGUgcGxvdF9fLCBlYWNoIGNvbG9yZWQgbGluZQpyZXByZXNlbnRzIGEgY29lZmZpY2llbnQgJFxoYXR7XGJldGF9JCBmcm9tIHRoZSByZWdyZXNzaW9uIG1vZGVsIGFuZCBzaG93cyBob3cKdGhleSBjaGFuZ2Ugd2l0aCBpbmNyZWFzZWQgdmFsdWVzIG9mICRcZ2FtbWEkIChvbiB0aGUgbG9nLXNjYWxlKQpeW05vdGU6IGBsb2coKWAgaW4gUiBpcyB0aGUgX19uYXR1cmFsIGxvZ2FyaXRobV9fIGJ5IGRlZmF1bHQgKGJhc2UgJGUkKSBhbmQgd2UKd2lsbCBhbHNvIHVzZSB0aGlzIG5vdGF0aW9uIGluIHRoZSB0ZXh0IChsaWtlIHRoZSB4LWF4aXMgdGl0bGUgb24gdGhlIHBsb3QgYWJvdmUpLgpUaGlzIG1pZ2h0IGJlIGRpZmZlcmVudCBmcm9tIHRoZSBub3RhdGlvbiB0aGF0IHlvdSdyZSB1c2VkIHRvICgkXGxuKCkkKS4KVG8gdGFrZSBsb2dhcml0aG1zIHdpdGggYSBkaWZmZXJlbnQgYmFzZSBpbiBSIHlvdSBjYW4gc3BlY2lmeSB0aGUgYGJhc2UgPSBgCmFyZ3VtZW50IG9mIGBsb2dgIG9yIHVzZSB0aGUgc2hvcnRoYW5kIGZ1bmN0aW9ucyBgbG9nMTAoeClgIGFuZCBgbG9nMih4KWAgZm9yCmJhc2UgMTAgYW5kIDIsIHJlc3BlY3RpdmVseV0uCgpOb3RlIHRoYXQgZm9yIGhpZ2hlciB2YWx1ZXMgJFxnYW1tYSQsIHRoZSBjb2VmZmljaWVudCBlc3RpbWF0ZXMgYmVjb21lIGNsb3NlciB0byAwLApzaG93aW5nIHRoZSAqc2hyaW5rYWdlKiBlZmZlY3Qgb2YgdGhlIHJpZGdlIHBlbmFsdHkuCgpTaW1pbGFyIHRvIHRoZSBQQyByZWdyZXNzaW9uIGV4YW1wbGUsIHdlIGNob3NlICRcZ2FtbWE9MiQgYW5kIHRoZSBncmlkIHJhdGhlcgphcmJpdHJhcmlseS4gV2Ugd2lsbCBzZWUgc3Vic2VxdWVudGx5LCBob3cgdG8gY2hvb3NlICRcZ2FtbWEkIHRoYXQgbWluaW1pemVzIHRoZQpwcmVkaWN0aW9uIGVycm9yLgoKCiMgRXhlcmNpc2U6IExhc3NvIHJlZ3Jlc3Npb24KCkxhc3NvIHJlZ3Jlc3Npb24gaXMgYWxzbyBhIGZvcm0gb2YgcGVuYWxpemVkIHJlZ3Jlc3Npb24sIGJ1dCB3ZSBkbyBub3QgaGF2ZSBhbgphbmFseXRpYyBzb2x1dGlvbiBvZiAkXGhhdHt7XGJvbGRzeW1ib2x7XGJldGF9fX0kIGFzIGluIGxlYXN0IHNxdWFyZXMKYW5kIHJpZGdlIHJlZ3Jlc3Npb24uIEluIG9yZGVyIHRvIGZpdCBhIGxhc3NvIG1vZGVsLCB3ZSBvbmNlIGFnYWluIHVzZQp0aGUgYGdsbW5ldCgpYCBmdW5jdGlvbi4gSG93ZXZlciwgdGhpcyB0aW1lIHdlIHVzZSB0aGUgYXJndW1lbnQKYGFscGhhID0gMWAKCgojIyBUYXNrcyB7LX0KCiMjIyMgMS4gVmVyaWZ5IHRoYXQgc2V0dGluZyBgYWxwaGEgPSAxYCBpbmRlZWQgY29ycmVzcG9uZHMgdG8gbGFzc28gcmVncmVzc2lvbiB1c2luZyB0aGUgZXF1YXRpb25zIGZyb20gW1NlY3Rpb24gM10oI2VsbmV0LXRoZW9yeSkuIHstfQoKCiMjIyMgMi4gUGVyZm9ybSBhIGxhc3NvIHJlZ3Jlc3Npb24gd2l0aCB0aGUgYGdsbW5ldGAgZnVuY3Rpb24gd2l0aCBgWWAgdGhlIHJlc3BvbnNlIGFuZCBgWGAgdGhlIHByZWRpY3RvcnMuIHstfQoKWW91IGRvIG5vdCBoYXZlIHRvIHByb3ZpZGUgYSBjdXN0b20gc2VxdWVuY2Ugb2YgJFxnYW1tYSQgKGBsYW1iZGFgKSB2YWx1ZXMgaGVyZQpidXQgY2FuIGluc3RlYWQgcmVseSBvbiBgZ2xtbmV0YCdzIGRlZmF1bHQgYmVoYXZpb3VyIG9mIGNob29zaW5nIHRoZSBncmlkIG9mCiRcZ2FtbWEkIHZhbHVlcyBiYXNlZCBvbiB0aGUgZGF0YSAoc2VlIGA/Z2xtbmV0YCBmb3IgbW9yZSBkZXRhaWxzKS4KCjxkZXRhaWxzPjxzdW1tYXJ5PlNvbHV0aW9uPC9zdW1tYXJ5PgpgYGB7ciBnbG1uZXQtbGFzc28tcmVncmVzc2lvbn0KIyBOb3RlIHRoYXQgdGhlIGdsbW5ldCgpIGZ1bmN0aW9uIGNhbiBzdXBwbHkgZ2FtbWEgYXV0b21hdGljYWxseQojIEJ5IGRlZmF1bHQgaXQgdXNlcyBhIHNlcXVlbmNlIG9mIDEwMCBsYW1iZGEgdmFsdWVzCmxhc3NvX21vZGVsIDwtIGdsbW5ldChYLCBZLCBhbHBoYSA9IDEpCmBgYAo8L2RldGFpbHM+CgoKIyMjIyAzLiBNYWtlIHRoZSBjb2VmZmljaWVudCBwcm9maWxlIHBsb3QgYW5kIGludGVycHJldC4gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KCmBgYHtyfQpwbG90KGxhc3NvX21vZGVsLCB4dmFyID0gImxhbWJkYSIsIHhsYWIgPSAibG9nKGdhbW1hKSIpCmBgYAoKTm90ZSB0aGF0IHRoZSBudW1iZXIgb2Ygbm9uLXplcm8gY29lZmZpY2llbnRzIGlzIGluZGljYXRlZCBhdCB0aGUgdG9wIG9mIHRoZSBwbG90LgpJbiB0aGUgY2FzZSBvZiBsYXNzby1yZWdyZXNzaW9uIHRoZSByZWd1bGFyaXphdGlvbiBpcyBtdWNoIGxlc3Mgc21vb3RoIGNvbXBhcmVkCnRvIHRoZSByaWRnZSByZWdyZXNzaW9uLCB3aXRoIHNvbWUgY29lZmZpY2llbnRzIGluY3JlYXNpbmcgZm9yIGhpZ2hlciAkXGdhbW1hJApiZWZvcmUgc2hhcnBseSBkcm9wcGluZyB0byB6ZXJvLgpJbiBjb250cmFzdCB0byByaWRnZSwgbGFzc28gZXZlbnR1YWxseSBzaHJpbmtzIGFsbCBjb2VmZmljaWVudHMgdG8gMC4KCjwvZGV0YWlscz4KCgojIEV2YWx1YXRpb24gb2YgcHJlZGljdGlvbiBtb2RlbHMgYW5kIHR1bmluZyBoeXBlcnBhcmFtZXRlcnMKCkZpcnN0IHdlIHdpbGwgc3BsaXQgb3VyIG9yaWdpbmFsIGRhdGEgaW4gYSB0cmFpbmluZyBhbmQgdGVzdCBzZXQgdG8gdmFsaWRhdGUgb3VyCm1vZGVsLiBUaGUgdHJhaW5pbmcgc2V0IHdpbGwgYmUgdXNlZCB0byB0cmFpbiB0aGUgbW9kZWwgYW5kIHR1bmUgdGhlCmh5cGVycGFyYW1ldGVycywgd2hpbGUgdGhlIHRlc3Qgc2V0IHdpbGwgYmUgdXNlZCB0byBldmFsdWF0ZSB0aGUKX19vdXQtb2Ytc2FtcGxlX18gcGVyZm9ybWFuY2Ugb2Ygb3VyIGZpbmFsIG1vZGVsLiBJZiB3ZSB3b3VsZCB1c2UgdGhlIHNhbWUgZGF0YQp0byBib3RoIGZpdCBhbmQgdGVzdCB0aGUgbW9kZWwsIHdlIHdvdWxkIGdldCBiaWFzZWQgcmVzdWx0cy4KCkJlZm9yZSB3ZSBiZWdpbiwgd2UgdXNlIHRoZSBgc2V0LnNlZWQoKWAgZnVuY3Rpb24gaW4gb3JkZXIgdG8gc2V0IGEgc2VlZApmb3IgUuKAmXMgcmFuZG9tIG51bWJlciBnZW5lcmF0b3IsIHNvIHRoYXQgd2Ugd2lsbCBhbGwgb2J0YWluIHByZWNpc2VseQp0aGUgc2FtZSByZXN1bHRzIGFzIHRob3NlIHNob3duIGJlbG93LiBJdCBpcyBnZW5lcmFsbHkgZ29vZCBwcmFjdGljZSB0bwpzZXQgYSByYW5kb20gc2VlZCB3aGVuIHBlcmZvcm1pbmcgYW4gYW5hbHlzaXMgc3VjaCBhcyBjcm9zcy12YWxpZGF0aW9uCnRoYXQgY29udGFpbnMgYW4gZWxlbWVudCBvZiByYW5kb21uZXNzLCBzbyB0aGF0IHRoZSByZXN1bHRzIG9idGFpbmVkIGNhbgpiZSByZXByb2R1Y2VkIGF0IGEgbGF0ZXIgdGltZS4KCldlIGJlZ2luIGJ5IHVzaW5nIHRoZSBgc2FtcGxlKClgIGZ1bmN0aW9uIHRvIHNwbGl0IHRoZSBzZXQgb2Ygc2FtcGxlcyBpbnRvIHR3bwpzdWJzZXRzLCBieSBzZWxlY3RpbmcgYSByYW5kb20gc3Vic2V0IG9mIDgwIG9ic2VydmF0aW9ucyBvdXQgb2YgdGhlIG9yaWdpbmFsIDEyMApvYnNlcnZhdGlvbnMuIFdlIHJlZmVyIHRvIHRoZXNlIG9ic2VydmF0aW9ucyBhcyB0aGUgX190cmFpbmluZ19fIHNldC4gVGhlIHJlc3QKb2YgdGhlIG9ic2VydmF0aW9ucyB3aWxsIGJlIHVzZWQgYXMgdGhlIF9fdGVzdF9fIHNldC4KCmBgYHtyIGNyZWF0ZS10cmFpbmluZy1zZXR9CnNldC5zZWVkKDEpCiMgU2FtcGxlIDgwIHJhbmRvbSBJRHMgZnJvbSB0aGUgcm93cyBvZiBYICgxMjAgdG90YWwpCnRyYWluSUQgPC0gc2FtcGxlKG5yb3coWCksIDgwKQoKIyBUcmFpbmluZyBkYXRhCnRyYWluWCA8LSBYW3RyYWluSUQsIF0KdHJhaW5ZIDwtIFlbdHJhaW5JRF0KCiMgVGVzdCBkYXRhCnRlc3RYIDwtIFhbLXRyYWluSUQsIF0KdGVzdFkgPC0gWVstdHJhaW5JRF0KYGBgCgpUbyBtYWtlIGZpdHRpbmcgdGhlIG1vZGVscyBhIGJpdCBlYXNpZXIgbGF0ZXIsIHdlIHdpbGwgYWxzbyBjcmVhdGUgMiBkYXRhLmZyYW1lcwpjb21iaW5pbmcgdGhlIHJlc3BvbnNlIGFuZCBwcmVkaWN0b3JzIGZvciB0aGUgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YS4KCmBgYHtyfQp0cmFpbl9kYXRhIDwtIGRhdGEuZnJhbWUoIlRSSU0zMiIgPSB0cmFpblksIHRyYWluWCkKdGVzdF9kYXRhIDwtIGRhdGEuZnJhbWUoIlRSSU0zMiIgPSB0ZXN0WSwgdGVzdFgpCgojIyBHbGFuY2luZyBhdCB0aGUgZGF0YSBzdHJ1Y3R1cmU6IGZvciB0aGUgZmlyc3QgMTAgY29sdW1ucyBvbmx5CnN0cih0cmFpbl9kYXRhWywgMToxMF0pCmBgYAoKCiMjIE1vZGVsIGV2YWx1YXRpb24KCldlIGFyZSBpbnRlcmVzdGVkIGluIHRoZSBfX291dC1vZi1zYW1wbGVfXyBlcnJvciBvZiBvdXIgbW9kZWxzLAppLmUuIGhvdyBnb29kIG91ciBtb2RlbCBkb2VzIG9uIHVuc2VlbiBkYXRhLgpfX1RoaXMgd2lsbCBhbGxvdyB1cyB0byBjb21wYXJlIGRpZmZlcmVudCAqY2xhc3Nlcyogb2YgbW9kZWxzX18uCkZvciBjb250aW51b3VzIG91dGNvbWVzIHdlIHdpbGwgdXNlIHRoZSBfX21lYW4gc3F1YXJlZCBlcnJvciAoTVNFKV9fCihvciBpdHMgc3F1YXJlLXJvb3QgdmVyc2lvbiwgdGhlIFJNU0UpLgoKVGhlIGV2YWx1YXRpb24gd2lsbCBhbGxvdyB1cyB0byBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiBkaWZmZXJlbnQgdHlwZXMgb2YKbW9kZWxzLCBlLmcuIFBDIHJlZ3Jlc3Npb24sIHJpZGdlIHJlZ3Jlc3Npb24gYW5kIGxhc3NvIHJlZ3Jlc3Npb24sIG9uIG91ciBkYXRhLgpIb3dldmVyLCB3ZSBzdGlsbCBuZWVkIHRvIGZpbmQgdGhlIG9wdGltYWwgbW9kZWwgd2l0aGluIGVhY2ggb2YgdGhlc2UgY2xhc3NlcywKYnkgc2VsZWN0aW5nIHRoZSBiZXN0IGh5cGVycGFyYW1ldGVyIChudW1iZXIgb2YgUENzIGZvciBQQyByZWdyZXNzaW9uIGFuZCAkXGdhbW1hJApmb3IgbGFzc28gYW5kIHJpZGdlKS4KRm9yIHRoYXQgd2Ugd2lsbCB1c2UgClsqJGskLWZvbGQgQ3Jvc3MgVmFsaWRhdGlvbipdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0Nyb3NzLXZhbGlkYXRpb25fKHN0YXRpc3RpY3MpKQpvbiBvdXIgdHJhaW5pbmcgc2V0LgoKCiMjIFR1bmluZyBoeXBlcnBhcmFtZXRlcnMKClRoZSB0ZXN0IHNldCBpcyBvbmx5IHVzZWQgdG8gZXZhbHVhdGUgdGhlICpmaW5hbCogbW9kZWwuClRvIGFjaGlldmUgdGhpcyBmaW5hbCBtb2RlbCwgd2UgbmVlZCB0byBmaW5kIHRoZSBvcHRpbWFsIGh5cGVycGFyYW1ldGVycywKaS5lLiB0aGUgaHlwZXJwYXJhbWV0ZXJzIHRoYXQgYmVzdCBnZW5lcmFsaXplIHRoZSBtb2RlbCB0byB1bnNlZW4gZGF0YS4KV2UgY2FuIGVzdGltYXRlIHRoaXMgYnkgdXNpbmcgKmstZm9sZCBjcm9zcyB2YWxpZGF0aW9uKiAoJENWX2skKSBvbgp0aGUgdHJhaW5pbmcgZGF0YS4KClRoZSAkQ1ZfayQgZXN0aW1hdGVzIGNhbiBiZSBhdXRvbWF0aWNhbGx5IGNvbXB1dGVkIGZvciBhbnkKZ2VuZXJhbGl6ZWQgbGluZWFyIG1vZGVsIChnZW5lcmF0ZWQgd2l0aCBgZ2xtKClgIGFuZCBieSBleHRlbnNpb24gYGdsbW5ldCgpYCkgCnVzaW5nIHRoZSBgY3YuZ2xtKClgIGZ1bmN0aW9uIGZyb20gdGhlCipbYm9vdF0oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1ib290KSogcGFja2FnZS4KCgojIEV4YW1wbGU6IFBDIHJlZ3Jlc3Npb24gZXZhbHVhdGlvbgoKV2Ugc3RhcnQgd2l0aCB0aGUgUEMgcmVncmVzc2lvbiBhbmQgbG9vayBmb3IgdGhlIG9wdGltYWwgbnVtYmVyIG9mIFBDcyB0aGF0IG1pbmltaXplcwp0aGUgTVNFIHVzaW5nICRrJC1mb2xkIENyb3NzIHZhbGlkYXRpb24uCldlIHRoZW4gdXNlIHRoaXMgb3B0aW1hbCBudW1iZXIgb2YgUENzIHRvIHRyYWluIHRoZSBmaW5hbCBtb2RlbCBhbmQgZXZhbHVhdGUgaXQKb24gdGhlIHRlc3QgZGF0YS4KCgojIyBrLWZvbGQgQ3Jvc3MgVmFsaWRhdGlvbiB0byB0dW5lIG51bWJlciBvZiBjb21wb25lbnRzCgpDb252ZW5pZW50bHksIHRoZSBgcGNyYCBmdW5jdGlvbiBmcm9tIHRoZSBgcGxzYCBwYWNrYWdlIGhhcyBhbiBpbXBsZW1lbnRhdGlvbiBmb3IKay1mb2xkIENyb3NzIFZhbGlkYXRpb24uIFdlIHNpbXBseSBuZWVkIHRvIHNldCBgdmFsaWRhdGlvbiA9IENWYCBhbmQgYHNlZ21lbnRzID0gMjBgCnRvIHBlcmZvcm0gMjAtZm9sZCBDcm9zcyBWYWxpZGF0aW9uIHdpdGggUEMgcmVncmVzc2lvbi4KSWYgd2UgZG9uJ3Qgc3BlY2lmeSBgbmNvbXBgLCBgcGNyYCB3aWxsIHNlbGVjdCB0aGUgbWF4aW11bSBudW1iZXIgb2YgUENzIHRoYXQgY2FuCmJlIHVzZWQgZm9yIHRoZSBDVi4KCk5vdGUgdGhhdCBvdXIgdHJhaW5pbmcgZGF0YSBgdHJhaW5YYCBjb25zaXN0cyBvZiA4MCBvYnNlcnZhdGlvbnMgKHJvd3MpLgpJZiB3ZSBwZXJmb3JtIDIwLWZvbGQgQ1YsIHRoYXQgbWVhbnMgd2Ugd2lsbCBzcGxpdCB0aGUgZGF0YSBpbiAyMCBncm91cHMsIHNvCmVhY2ggZ3JvdXAgd2lsbCBjb25zaXN0IG9mIDQgb2JzZXJ2YXRpb25zLiBBdCBlYWNoIENWIGN5Y2xlLCBvbmUgZ3JvdXAgd2lsbCBiZSBsZWZ0Cm91dCBhbmQgdGhlIG1vZGVsIHdpbGwgYmUgdHJhaW5lZCBvbiB0aGUgcmVtYWluaW5nIGdyb3Vwcy4gVGhpcyBsZWF2ZXMgdXMgd2l0aAo3NiB0cmFpbmluZyBvYnNlcnZhdGlvbnMgZm9yIGVhY2ggQ1YgY3ljbGUsIHNvIHRoZSBtYXhpbWFsIG51bWJlciBvZiBjb21wb25lbnRzCnRoYXQgY2FuIGJlIHVzZWQgaW4gdGhlIGxpbmVhciByZWdyZXNzaW9uIGlzIDc1LgoKYGBge3IgcGNyLWtDVn0KIyMgU2V0IHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eSwga0NWIGlzIGEgcmFuZG9tIHByb2Nlc3MhCnNldC5zZWVkKDEyMykKCksgPC0gMjAKCiMjIFRoZSAnWSB+IC4nIG5vdGF0aW9uIG1lYW5zOiBmaXQgWSBieSBldmVyeSBvdGhlciB2YXJpYWJsZSBpbiB0aGUgZGF0YQpwY3JfY3YgPC0gcGNyKFRSSU0zMiB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhLCB2YWxpZGF0aW9uID0gIkNWIiwgc2VnbWVudHMgPSBLKQpzdW1tYXJ5KHBjcl9jdikKYGBgCgpXZSBjYW4gcGxvdCB0aGUgKnJvb3QgbWVhbiBzcXVhcmVkIGVycm9yIG9mIHByZWRpY3Rpb24qIChSTVNFUCkgZm9yIGVhY2ggbnVtYmVyCm9mIGNvbXBvbmVudHMgYXMgZm9sbG93cy4KCmBgYHtyIHBjcl9jdi1wbG90fQpwbG90KHBjcl9jdiwgcGxvdHR5cGUgPSAidmFsaWRhdGlvbiIpCmBgYAoKVGhlIGBwbHNgIHBhY2thZ2UgYWxzbyBoYXMgYSBmdW5jdGlvbiBgc2VsZWN0TmNvbXBgIHRvIHNlbGVjdCB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY29tcG9uZW50cy4KSGVyZSB3ZSB1c2UgdGhlICJvbmUtc2lnbWEiIG1ldGhvZCwgd2hpY2ggcmV0dXJucyB0aGUgbG93ZXN0IG51bWJlciBvZiBjb21wb25lbnRzCmZvciB3aGljaCB0aGUgUk1TRSBpcyB3aXRoaW4gb25lIHN0YW5kYXJkIGVycm9yIG9mIHRoZSBhYnNvbHV0ZSBtaW5pbXVtLgpUaGUgZnVuY3Rpb24gYWxzbyBhbGxvd3MgcGxvdHRpbmcgdGhlIHJlc3VsdCBieSBzcGVjaWZ5aW5nIGBwbG90ID0gVFJVRWAuCgpgYGB7ciBwY3Itb3B0aW1hbC1uY29tcH0Kb3B0aW1hbF9uY29tcCA8LSBzZWxlY3ROY29tcChwY3JfY3YsIG1ldGhvZCA9ICJvbmVzaWdtYSIsIHBsb3QgPSBUUlVFKQpgYGAKClRoaXMgb3V0Y29tZSBzaG93cyB1cyB0aGF0IHRoZSBvcHRpbWFsIG51bWJlciBvZiBjb21wb25lbnRzIGZvciBvdXIgbW9kZWwgaXMKYHIgb3B0aW1hbF9uY29tcGAuCgoKIyMgVmFsaWRhdGlvbiBvbiB0ZXN0IGRhdGEKCldlIG5vdyB1c2Ugb3VyIG9wdGltYWwgbnVtYmVyIG9mIGNvbXBvbmVudHMgdG8gdHJhaW4gdGhlIGZpbmFsIFBDUiBtb2RlbC4KVGhpcyBtb2RlbCBpcyB0aGVuIHZhbGlkYXRlZCBvbiBieSBnZW5lcmF0aW5nIHByZWRpY3Rpb25zIGZvciB0aGUgdGVzdCBkYXRhIGFuZApjYWxjdWxhdGluZyB0aGUgTVNFLgoKV2UgZGVmaW5lIGEgY3VzdG9tIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB0aGUgTVNFLgpOb3RlIHRoYXQgdGhlcmUgaXMgYWxzbyBhbiBgTVNFUGAgZnVuY3Rpb24gaW4gdGhlIGBwbHNgIHBhY2thZ2Ugd2hpY2ggZG9lcyB0aGUKcHJlZGljdGlvbiBhbmQgTVNFIGNhbGN1bGF0aW9uIGluIG9uZSBnby4KQnV0IG91ciBvd24gZnVuY3Rpb24gd2lsbCBjb21lIGluIGhhbmR5IGxhdGVyIGZvciBsYXNzbyBhbmQgcmlkZ2UgcmVncmVzc2lvbi4KCmBgYHtyIE1TRX0KIyBNZWFuIFNxdWFyZWQgRXJyb3IKIyMgb2JzOiBvYnNlcnZhdGlvbnM7IHByZWQ6IHByZWRpY3Rpb25zCk1TRSA8LSBmdW5jdGlvbihvYnMsIHByZWQpewogIG1lYW4oKGRyb3Aob2JzKSAtIGRyb3AocHJlZCkpXjIpCn0KYGBgCgpgYGB7ciBmaW5hbF9wY3JfbW9kZWx9CmZpbmFsX3Bjcl9tb2RlbCA8LSBwY3IoVFJJTTMyIH4gLiwgZGF0YSA9IHRyYWluX2RhdGEsIG5jb21wID0gb3B0aW1hbF9uY29tcCkKcGNyX3ByZWRzIDwtIHByZWRpY3QoZmluYWxfcGNyX21vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhLCBuY29tcCA9IG9wdGltYWxfbmNvbXApCihwY3JfbXNlIDwtIE1TRSh0ZXN0WSwgcGNyX3ByZWRzKSkKYGBgCgpUaGlzIHZhbHVlIG9uIGl0cyBvd24gZG9lcyBub3QgdGVsbCB1cyB2ZXJ5IG11Y2gsIGJ1dCB3ZSBjYW4gdXNlIGl0IHRvIGNvbXBhcmUgb3VyClBDUiBtb2RlbCB3aXRoIG90aGVyIHR5cGVzIG9mIG1vZGVscyBsYXRlci4KCkZpbmFsbHksIHdlIHBsb3QgdGhlIHByZWRpY3RlZCB2YWx1ZXMgZm9yIG91ciByZXNwb25zZSB2YXJpYWJsZSAodGhlIFRSSU0zMiBnZW5lIGV4cHJlc3Npb24pCmFnYWluc3QgdGhlIGFjdHVhbCBvYnNlcnZlZCB2YWx1ZXMgZnJvbSBvdXIgdGVzdCBzZXQuCgpgYGB7ciBwY3ItcHJlZHBsb3R9CnByZWRwbG90KGZpbmFsX3Bjcl9tb2RlbCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSwgbGluZSA9IFRSVUUpCmBgYAoKCgojIEV4ZXJjaXNlOiBldmFsdWF0ZSBhbmQgY29tcGFyZSBwcmVkaWN0aW9uIG1vZGVscwoKIyMjIyAxLiBQZXJmb3JtIGEgbGFzc28gcmVncmVzc2lvbiB3aXRoIDIwLWZvbGQgQ3Jvc3MgVmFsaWRhdGlvbiBvbiB0aGUgdHJhaW5pbmcgZGF0YSAoYHRyYWluWGAsIGB0cmFpbllgKS4gUGxvdCB0aGUgcmVzdWx0cyBhbmQgc2VsZWN0IHRoZSBvcHRpbWFsIGBsYW1iZGFgICgkXGdhbW1hJCkgcGFyYW1ldGVyLiBGaXQgYSBmaW5hbCBtb2RlbCB3aXRoIHRoZSBzZWxlY3RlZCBgbGFtYmRhYCBhbmQgdmFsaWRhdGUgaXQgb24gdGhlIHRlc3QgZGF0YS4gey19CgoqSGludCo6IHVzZSB0aGUgYGN2LmdsbW5ldCgpYCBmdW5jdGlvbiwgZm9yIDIwIGZvbGRzIENWLCBzZXQgYG5mb2xkcyA9IDIwYCBhbmQgCnRvIHVzZSB0aGUgTVNFIG1ldHJpYyBzZXQgYHR5cGUubWVhc3VyZSA9ICJtc2UiYC4KR28gdG8gYD9jdi5nbG1uZXRgIGZvciBkZXRhaWxzLgoKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CgpgYGB7ciBsYXNzby1jdn0Kc2V0LnNlZWQoMTIzKQpsYXNzb19jdiA8LSBjdi5nbG1uZXQodHJhaW5YLCB0cmFpblksIGFscGhhID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICBuZm9sZHMgPSBLLCB0eXBlLm1lYXN1cmUgPSAibXNlIikKbGFzc29fY3YKcGxvdChsYXNzb19jdikKYGBgCgpOb3RlIHRoYXQgd2UgY2FuIGV4dHJhY3QgdGhlIGZpdHRlZCBsYXNzbyByZWdyZXNzaW9uIG9iamVjdCBmcm9tIHRoZSBDViByZXN1bHQKYW5kIG1ha2UgdGhlIGNvZWZmaWNpZW50IHByb2ZpbGUgcGxvdCBhcyBiZWZvcmUuCgpgYGB7ciBsYXNzby1jdi1jb2VmZmljaWVudC1wcm9maWxlfQpwbG90KGxhc3NvX2N2JGdsbW5ldC5maXQsIHh2YXIgPSAibGFtYmRhIikKYGBgCgpXZSBjYW4gbG9vayBmb3IgdGhlIGdhbW1hIHZhbHVlcyB0aGF0IGdpdmUgdGhlIGJlc3QgcmVzdWx0LiAKSGVyZSB5b3UgaGF2ZSB0d28gcG9zc2liaWxpdGllcyA6CgoxLiBgbGFtYmRhLm1pbmA6IHRoZSB2YWx1ZSBvZiAgJFxnYW1tYSQgdGhhdCBnaXZlcyB0aGUgYmVzdCByZXN1bHQgZm9yIHRoZSBjcm9zc3ZhbGlkYXRpb24uCjIuIGBsYW1iZGEuMXNlYDogdGhlIGxhcmdlc3QgdmFsdWUgb2YgJFxnYW1tYSQgc3VjaCB0aGF0IHRoZSBNU0UgaXMgd2l0aGluIDEgc3RhbmRhcmQgZXJyb3IKb2YgdGhlIGJlc3QgcmVzdWx0IGZyb20gdGhlIGNyb3NzIHZhbGlkYXRpb24uCgpgYGB7cn0KbGFzc29fY3YkbGFtYmRhLm1pbgpsYXNzb19jdiRsYW1iZGEuMXNlCmBgYAoKV2Ugd2lsbCAocmF0aGVyIGFyYml0cmFyaWx5KSB1c2UgYGxhbWJkYS5taW5gIGhlcmUgdG8gZml0IHRoZSBmaW5hbCBtb2RlbCBhbmQgZ2VuZXJhdGUgcHJlZGljdGlvbnMgb24gdGhlIHRlc3QgZGF0YS4KTm90ZSB0aGF0IHdlIGRvbid0IGFjdHVhbGx5IGhhdmUgdG8gcmVkbyB0aGUgZml0dGluZywgd2UgY2FuIGp1c3QgdXNlIG91ciBleGlzdGluZwpgbGFzc29fY3ZgIG9iamVjdCwgd2hpY2ggYWxyZWFkeSBjb250YWlucyB0aGUgZml0dGVkIG1vZGVscyBmb3IgYSByYW5nZSBvZiBgbGFtYmRhYCB2YWx1ZXMuCldlIGNhbiB1c2UgdGhlIGBwcmVkaWN0YCBmdW5jdGlvbiBhbmQgc3BlY2lmeSB0aGUgYHNgIGFyZ3VtZW50ICh3aGljaCBjb25mdXNpbmdseSBzZXRzIGBsYW1iZGFgIGluIHRoaXMgY2FzZSkgIHRvIG1ha2UgcHJlZGljdGlvbnMgb24gdGhlIHRlc3QgZGF0YS4KCmBgYHtyfQpsYXNzb19wcmVkcyA8LSBwcmVkaWN0KGxhc3NvX2N2LCBzID0gbGFzc29fY3YkbGFtYmRhLm1pbiwgbmV3eCA9IHRlc3RYKQojIyBDYWxjdWxhdGUgTVNFCihsYXNzb19tc2UgPC0gTVNFKHRlc3RZLCBsYXNzb19wcmVkcykpCmBgYAo8L2RldGFpbHM+CgoKIyMjIyAyLiBEbyB0aGUgc2FtZSBmb3IgcmlkZ2UgcmVncmVzc2lvbi4gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KCmBgYHtyIHJpZGdlLWN2fQpzZXQuc2VlZCgxMjMpCnJpZGdlX2N2IDwtIGN2LmdsbW5ldCh0cmFpblgsIHRyYWluWSwgYWxwaGEgPSAwLCAKICAgICAgICAgICAgICAgICAgICAgIG5mb2xkcyA9IEssIHR5cGUubWVhc3VyZSA9ICJtc2UiKQpyaWRnZV9jdgpwbG90KHJpZGdlX2N2KQpgYGAKCk5vdGUgdGhhdCB3ZSBjYW4gZXh0cmFjdCB0aGUgZml0dGVkIHJpZGdlIHJlZ3Jlc3Npb24gb2JqZWN0IGZyb20gdGhlIENWIHJlc3VsdAphbmQgbWFrZSB0aGUgY29lZmZpY2llbnQgcHJvZmlsZSBwbG90IGFzIGJlZm9yZS4KCmBgYHtyIHJpZGdlLWN2LWNvZWZmaWNpZW50LXByb2ZpbGV9CnBsb3QocmlkZ2VfY3YkZ2xtbmV0LmZpdCwgeHZhciA9ICJsYW1iZGEiKQpgYGAKCldlIGNhbiBsb29rIGZvciB0aGUgZ2FtbWEgdmFsdWVzIHRoYXQgZ2l2ZSB0aGUgYmVzdCByZXN1bHQuIApIZXJlIHlvdSBoYXZlIHR3byBwb3NzaWJpbGl0aWVzIDoKCjEuIGBsYW1iZGEubWluYDogdGhlIHZhbHVlIG9mICAkXGdhbW1hJCB0aGF0IGdpdmVzIHRoZSBiZXN0IHJlc3VsdCBmb3IgdGhlIGNyb3NzdmFsaWRhdGlvbi4KMi4gYGxhbWJkYS4xc2VgOiB0aGUgbGFyZ2VzdCB2YWx1ZSBvZiAkXGdhbW1hJCBzdWNoIHRoYXQgdGhlIE1TRSBpcyB3aXRoaW4gMSBzdGFuZGFyZCBlcnJvcgpvZiB0aGUgYmVzdCByZXN1bHQgZnJvbSB0aGUgY3Jvc3MgdmFsaWRhdGlvbi4KCmBgYHtyfQpyaWRnZV9jdiRsYW1iZGEubWluCnJpZGdlX2N2JGxhbWJkYS4xc2UKYGBgCgpXZSB3aWxsIChyYXRoZXIgYXJiaXRyYXJpbHkpIHVzZSBgbGFtYmRhLm1pbmAgaGVyZSB0byBmaXQgdGhlIGZpbmFsIG1vZGVsIGFuZCBnZW5lcmF0ZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBkYXRhLgpOb3RlIHRoYXQgd2UgZG9uJ3QgYWN0dWFsbHkgaGF2ZSB0byByZWRvIHRoZSBmaXR0aW5nLCB3ZSBjYW4ganVzdCB1c2Ugb3VyIGV4aXN0aW5nCmByaWRnZV9jdmAgb2JqZWN0LCB3aGljaCBhbHJlYWR5IGNvbnRhaW5zIHRoZSBmaXR0ZWQgbW9kZWxzIGZvciBhIHJhbmdlIG9mIGBsYW1iZGFgIHZhbHVlcy4KV2UgY2FuIHVzZSB0aGUgYHByZWRpY3RgIGZ1bmN0aW9uIGFuZCBzcGVjaWZ5IHRoZSBgc2AgYXJndW1lbnQgKHdoaWNoIGNvbmZ1c2luZ2x5IHNldHMgYGxhbWJkYWAgaW4gdGhpcyBjYXNlKSAgdG8gbWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBkYXRhLgoKYGBge3IgcmlkZ2UtcHJlZGljdGlvbnN9CnJpZGdlX3ByZWRzIDwtIHByZWRpY3QocmlkZ2VfY3YsIHMgPSByaWRnZV9jdiRsYW1iZGEubWluLCBuZXd4ID0gdGVzdFgpCiMjIENhbGN1bGF0ZSBNU0UKKHJpZGdlX21zZSA8LSBNU0UodGVzdFksIHJpZGdlX3ByZWRzKSkKYGBgCgo8L2RldGFpbHM+CgoKIyMjIyAzLiBXaGljaCBvZiB0aGUgbW9kZWxzIGNvbnNpZGVyZWQgKFBDUiwgbGFzc28sIHJpZGdlKSBwZXJmb3JtcyBiZXN0Py4gey19CiAgICAKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CgpCYXNlZCBvbiB0aGUgTVNFLCB0aGUgcmlkZ2UgbW9kZWwgcGVyZm9ybXMgYmVzdCBvbiB0aGUgdGVzdCBkYXRhLgoKYGBge3IsIGVjaG89RkFMU0V9CmtuaXRyOjprYWJsZSgKICBkYXRhLmZyYW1lKAogICAgIk1vZGVsIiA9IGMoIlBDUiIsICJMYXNzbyIsICJSaWRnZSIpLAogICAgIk1TRSIgPSBjKHBjcl9tc2UsIGxhc3NvX21zZSwgcmlkZ2VfbXNlKQogICkKKQpgYGAKPC9kZXRhaWxzPgo=