## install packages with:
## install.packages(c("glmnet", "pls", "NormalBetaPrime", "boot"))
library(NormalBetaPrime)
library(glmnet)
library(pls)
library(boot)
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
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”.
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.
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.
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.
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:
- the number of components (PCs) to use is passed with the argument
ncomp
- 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.
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:
- 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)
\]
- 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)
\]
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.
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.
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.
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 ...
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.
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.
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.
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.
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)
Exercise: evaluate and compare prediction models
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 :
lambda.min
: the value of \(\gamma\) that gives the best result for the crossvalidation.
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
LS0tCnRpdGxlOiAiQW5hbHlzaXMgb2YgSGlnaCBEaW1lbnNpb25hbCBEYXRhIC0gTGFiIDMiCnN1YnRpdGxlOiAiUGVuYWxpemVkIHJlZ3Jlc3Npb24gdGVjaG5pcXVlcyBmb3IgaGlnaC1kaW1lbnNpb25hbCBkYXRhIgphdXRob3I6ICJBZGFwdGVkIGJ5IE1pbGFuIE1hbGZhaXQiCmRhdGU6ICIwNSBOb3YgMjAyMCIKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRSwgY2FjaGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldCgKICBjb2xsYXBzZSA9IFRSVUUsCiAgY29tbWVudCA9ICIjPiIsCiAgZmlnLmFsaWduID0gImNlbnRlciIsCiAgb3V0LndpZHRoID0gIjEwMCUiCikKb3B0aW9ucygKICB3YXJuUGFydGlhbE1hdGNoRG9sbGFyID0gRkFMU0UsCiAgd2FyblBhcnRpYWxNYXRjaEF0dHIgPSBGQUxTRSwKICB3YXJuUGFydGlhbE1hdGNoQXJncyA9IEZBTFNFCikKYGBgCgoqKioKCmBgYHtyIGxpYnJhcmllcywgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KIyMgaW5zdGFsbCBwYWNrYWdlcyB3aXRoOgojIyBpbnN0YWxsLnBhY2thZ2VzKGMoImdsbW5ldCIsICJwbHMiLCAiTm9ybWFsQmV0YVByaW1lIiwgImJvb3QiKSkKbGlicmFyeShOb3JtYWxCZXRhUHJpbWUpCmxpYnJhcnkoZ2xtbmV0KQpsaWJyYXJ5KHBscykKbGlicmFyeShib290KQpgYGAKCgojIEludHJvZHVjdGlvbgoKKipJbiB0aGlzIGxhYiBzZXNzaW9uIHdlIHdpbGwgbG9vayBhdCB0aGUgZm9sbG93aW5nIHRvcGljcyoqCgogIC0gRGVtb25zdHJhdGUgd2h5IGxvdyBkaW1lbnNpb25hbCBwcmVkaWN0aW9uIG1vZGVsaW5nIGZhaWxzIGluIGhpZ2ggZGltZW5zaW9uLgogIC0gQ2Fycnkgb3V0IFByaW5jaXBhbCBDb21wb25lbnQgUmVncmVzc2lvbiAoUENSKQogIC0gVXNlIGBnbG1uZXQoKWAgdG8gY2Fycnkgb3V0IHJpZGdlIHJlZ3Jlc3Npb24sIGxhc3NvIGFuZCBlbGFzdGljIG5ldAogIC0gRXZhbHVhdGlvbiBvZiB0aGVzZSBwcmVkaWN0aW9uIG1vZGVscwogIAoKIyMgVGhlIGRhdGFzZXQKCkluIHRoaXMgcHJhY3RpY2FsLCB3ZSB3aWxsIHVzZSB0aGUgZGF0YXNldCBgZXllZGF0YWAgcHJvdmlkZWQgYnkKdGhlIFtfX05vcm1hbEJldGFQcmltZV9fIHBhY2thZ2VdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9Ob3JtYWxCZXRhUHJpbWUvaW5kZXguaHRtbCkuClRoaXMgZGF0YXNldCBjb250YWlucyBnZW5lIGV4cHJlc3Npb24gZGF0YSBvZiAyMDAKZ2VuZXMgZm9yIDEyMCBzYW1wbGVzLiBUaGUgZGF0YSBvcmlnaW5hdGVzIGZyb20gbWljcm9hcnJheSBleHBlcmltZW50cwpvZiBtYW1tYWxpYW4gZXllIHRpc3N1ZSBzYW1wbGVzLgoKVGhlIGRhdGFzZXQgY29uc2lzdHMgb2YgdHdvIG9iamVjdHM6CgogIC0gYGdlbmVzYDogYSAkMTIwIFx0aW1lcyAyMDAkIG1hdHJpeCB3aXRoIHRoZSBleHByZXNzaW9uIGxldmVscyBvZiAyMDAgZ2VuZXMKICAoY29sdW1ucykgZm9yIDEyMCBzYW1wbGVzIChyb3dzKQogIC0gYHRyaW0zMmA6IGEgdmVjdG9yIHdpdGggMTIwIGV4cHJlc3Npb24gbGV2ZWxzIG9mIHRoZSBUUklNMzIgZ2VuZS4KCgpgYGB7ciBsb2FkLWRhdGF9CmRhdGEoZXllZGF0YSkKIyMgTG9vayBhdCBvYmplY3RzIHRoYXQgd2VyZSBqdXN0IGxvYWRlZApzdHIoZ2VuZXMpCnN0cih0cmltMzIpCmBgYAoKVGhlIGdvYWwgb2YgdGhpcyBleGVyY2lzZSBpcyB0byBwcmVkaWN0IHRoZSBleHByZXNzaW9uIGxldmVscyBvZgpUUklNMzIgZnJvbSB0aGUgZXhwcmVzc2lvbiBsZXZlbHMgb2YgdGhlIDIwMCBnZW5lcyBtZWFzdXJlZCBpbiB0aGUKbWljcm9hcnJheSBleHBlcmltZW50LiBGb3IgdGhpcywgaXQgbWFrZXMgc2Vuc2UgdG8gc3RhcnQgYnkgY29uc3RydWN0aW5nCmNlbnRlcmVkIChhbmQgcG9zc2libHkgc2NhbGVkKSBkYXRhLiBXZSBzdG9yZSB0aGlzIGluIHR3byBtYXRyaWNlcwpgWGAgYW5kIGBZYDoKCmBgYHtyIHByZXBhcmUtZGF0YX0KWCA8LSBzY2FsZShnZW5lcywgY2VudGVyID0gVFJVRSwgc2NhbGUgPSBUUlVFKSAKWSA8LSBzY2FsZSh0cmltMzIsIGNlbnRlciA9IFRSVUUpCmBgYAoKUmVtZW1iZXIgdGhhdCBzY2FsaW5nIGF2b2lkcyB0aGF0IGRpZmZlcmVuY2VzIGluIGxldmVscyBvZiBtYWduaXR1ZGUKd2lsbCBnaXZlIG9uZSB2YXJpYWJsZSAoZ2VuZSkgbW9yZSBpbmZsdWVuY2UgaW4gdGhlIHJlc3VsdC4gVGhpcyBoYXMKYmVlbiBpbGx1c3RyYXRlZCBpbiB0aGUgW3NlY29uZCBwcmFjdGljYWwgc2Vzc2lvbl0oLi9MYWIyLVBDQS5odG1sKSBhcyB3ZWxsLgpGb3IgdGhlIGBZYCB2ZWN0b3IsIHRoaXMgaXMgbGVzcyBvZiBhbiBpc3N1ZSBhcyB3ZSdyZSB0YWxraW5nIGFib3V0IGEgc2luZ2xlIHZhcmlhYmxlLgpOb3Qgc2NhbGluZyB3aWxsIG1ha2UgdGhlIHByZWRpY3Rpb25zIGludGVycHJldGFibGUgYXMgImRldmlhdGlvbnMgZnJvbSB0aGUKbWVhbiIuCgojIyBUaGUgY3Vyc2Ugb2Ygc2luZ3VsYXJpdHkKCldlIGJlZ2luIGJ5IGFzc3VtaW5nIHRoYXQgdGhlIHByZWRpY3RvcnMgYW5kIHRoZSBvdXRjb21lIGhhdmUgYmVlbgpjZW50ZXJlZCBzbyB0aGF0IHRoZSBpbnRlcmNlcHQgaXMgMC4KV2UgYXJlIHByZXNlbnRlZCB3aXRoIHRoZSB1c3VhbCByZWdyZXNzaW9uIG1vZGVsOgoKJCQKWV9pPVxiZXRhX2kgWF97aTF9K1xkb3RzK1xiZXRhX3BYX3tpcH0rXGVwc2lsb25faSBcXCAKXHRleHR7IE9yIH0gXG1hdGhiZntZfT17XG1hdGhiZntYfX17XGJvbGRzeW1ib2x7XGJldGF9fSAre1xib2xkc3ltYm9se1xlcHNpbG9ufX0KJCQKCk91ciBnb2FsIGlzIHRvIGdldCB0aGUgbGVhc3Qgc3F1YXJlcyBlc3RpbWF0b3Igb2YKJHtcYm9sZHN5bWJvbHtcYmV0YX19JCwgZ2l2ZW4gYnkKCiQkClxoYXR7e1xib2xkc3ltYm9se1xiZXRhfX19PSAoXG1hdGhiZntYfV5Ue1xtYXRoYmZ7WH19KV57LTF9e1xtYXRoYmZ7WH19XlR7XG1hdGhiZntZfX0KJCQKCmluIHdoaWNoIHRoZSAkcCBcdGltZXMgcCQgbWF0cml4CiQoe1xtYXRoYmZ7WH19XlR7XG1hdGhiZntYfX0pXnstMX0kIGlzIGNydWNpYWwhClRvIGJlIGFibGUgdG8gY2FsY3VsYXRlIHRoZSBpbnZlcnNlIG9mICR7XG1hdGhiZntYfX1eVCBcbWF0aGJme1h9JCwKaXQgaGFzIHRvIGJlIG9mIGZ1bGwgcmFuayAkcCQsIHdoaWNoIHdvdWxkIGJlIDIwMCBpbiB0aGlzIGNhc2UuCkxldCdzIGNoZWNrIHRoaXM6CgpgYGB7ciBzaW5ndWxhcml0eS1wcm9ibGVtLCBlcnJvcj1UUlVFfQpkaW0oWCkgIyAxMjAgeCAyMDAsIHNvIHAgPiBuIQpxcihYKSRyYW5rCgpYdFggPC0gY3Jvc3Nwcm9kKFgpICMgY2FsY3VsYXRlcyB0KFgpICUqJSBYIG1vcmUgZWZmaWNpZW50bHkKcXIoWHRYKSRyYW5rCgojIFRyeSB0byBpbnZlcnQgdXNpbmcgc29sdmU6IApzb2x2ZShYdFgpCmBgYAoKV2UgcmVhbGl6ZSB3ZSBjYW5ub3QgY29tcHV0ZQokKHtcbWF0aGJme1h9fV5Ue1xtYXRoYmZ7WH19KV57LTF9JCBiZWNhdXNlIHRoZSByYW5rIG9mCiQoe1xtYXRoYmZ7WH19XlR7XG1hdGhiZntYfX0pJCBpcyBsZXNzIHRoYW4gJHAkIGhlbmNlIHdlIGNhbuKAmXQKZ2V0ICRcaGF0e3tcYm9sZHN5bWJvbHtcYmV0YX19fSQgYnkgbWVhbnMgb2YgbGVhc3Qgc3F1YXJlcyEgClRoaXMgaXMgZ2VuZXJhbGx5IHJlZmVycmVkIHRvIGFzIHRoZSBfX1tzaW5ndWxhcml0eV0oaHR0cHM6Ly93d3cuc3RhdGlzdGljcy5jb20vZ2xvc3Nhcnkvc2luZ3VsYXJpdHkvKSBwcm9ibGVtX18uCgoKIyBQcmluY2lwYWwgY29tcG9uZW50IHJlZ3Jlc3Npb24KCkEgZmlyc3Qgd2F5IHRvIGRlYWwgd2l0aCB0aGlzIHNpbmd1bGFyaXR5LCBpcyB0byBieXBhc3MgaXQgdXNpbmcgcHJpbmNpcGFsIGNvbXBvbmVudHMuClNpbmNlICRcbWluKG4scCkgPSBuID0gMTIwJCwgClBDQSB3aWxsIGdpdmUgYHIgbWluKGRpbShYKSlgIGNvbXBvbmVudHMsIGVhY2ggYmVpbmcgYSBsaW5lYXIgY29tYmluYXRpb24gb2YgdGhlIAokcCQgPSBgciBuY29sKFgpYCB2YXJpYWJsZXMuClRoZXNlIGByIG1pbihkaW0oWCkpYCBQQ3MgY29udGFpbiBhbGwgaW5mb3JtYXRpb24gcHJlc2VudCBpbiB0aGUgb3JpZ2luYWwgZGF0YS4KV2UgY291bGQgYXMgd2VsbCB1c2UgYW4gYXBwcm94aW1hdGlvbiBvZiAke1xtYXRoYmZ7WH19JCwgaS5lIHVzaW5nIGp1c3QgYSBmZXcgKCRrPDEyMCQpIFBDcy4KU28gd2UgdXNlIFBDQSBhcyBhIG1ldGhvZCBmb3IgcmVkdWNpbmcgdGhlIGRpbWVuc2lvbnMgd2hpbGUgcmV0YWluaW5nCmFzIG11Y2ggdmFyaWF0aW9uIGJldHdlZW4gdGhlIG9ic2VydmF0aW9ucyBhcyBwb3NzaWJsZS4KT25jZSB3ZSBoYXZlIHRoZXNlIFBDcywgd2UgY2FuIHVzZSB0aGVtIGFzIHZhcmlhYmxlcyBpbiBhIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsLgoKIyMgQ2xhc3NpYyBsaW5lYXIgcmVncmVzc2lvbiBvbiBQQ3MKCldlIGZpcnN0IGNvbXB1dGUgdGhlIFBDQSBvbiB0aGUgZGF0YSB3aXRoIGBwcmNvbXBgLgpXZSB3aWxsIHVzZSBhbiBhcmJpdHJhcnkgY3V0b2ZmIG9mICRrID0gNCQgUENzIHRvIGlsbHVzdHJhdGUgdGhlIHByb2Nlc3Mgb2YgcGVyZm9ybWluZyByZWdyZXNzaW9uIG9uIHRoZSBQQ3MuCgpgYGB7ciBQQy1yZWdyZXNzaW9ufQprIDwtIDQgIyBBcmJpdHJhcmlseSBjaG9zZW4gaz00CnBjYSA8LSBwcmNvbXAoWCkKVmsgPC0gcGNhJHJvdGF0aW9uWywgMTprXSAjIHRoZSBsb2FkaW5ncyBtYXRyaXgKWmsgPC0gcGNhJHhbLCAxOmtdICMgdGhlIHNjb3JlcyBtYXRyaXgKCiMgVXNlIHRoZSBzY29yZXMgaW4gY2xhc3NpYyBsaW5lYXIgcmVncmVzc2lvbgpwY3JfbW9kZWwxIDwtIGxtKFkgfiBaaykKc3VtbWFyeShwY3JfbW9kZWwxKQpgYGAKCkFzICRcbWF0aGJme1h9JCBhbmQgJFxtYXRoYmZ7WX0kIGFyZSBjZW50ZXJlZCwgdGhlIGludGVyY2VwdCBpcyAKYXBwcm94aW1hdGVseSAwLgoKVGhlIG91dHB1dCBzaG93cyB0aGF0IFBDMSBhbmQgUEM0IGhhdmUgYSAkXGJldGEkIGVzdGltYXRlIHRoYXQgCmRpZmZlcnMgc2lnbmlmaWNhbnRseSBmcm9tIDAgKGF0ICRwIDwgMC4wNSQpLCBidXQgdGhlIHJlc3VsdHMgY2FuJ3QgYmUgcmVhZGlseSAKaW50ZXJwcmV0ZWQsIHNpbmNlIHdlIGhhdmUgbm8gaW1tZWRpYXRlIGludGVycHJldGF0aW9uIG9mIHRoZSBQQ3MuCgoKIyMgVXNpbmcgdGhlIHBhY2thZ2UgYHBsc2AKClBDUiBjYW4gYWxzbyBiZSBwZXJmb3JtZWQgdXNpbmcgdGhlIGBwY3IoKWAgZnVuY3Rpb24gZnJvbSB0aGUKcGFja2FnZSAqW3Bsc10oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1wbHMpKgpfX2RpcmVjdGx5IG9uIHRoZSBkYXRhX18gKHNvIHdpdGhvdXQgaGF2aW5nIHRvIGZpcnN0IHBlcmZvcm0gdGhlIFBDQSBtYW51YWxseSkuCldoZW4gdXNpbmcgdGhpcyBmdW5jdGlvbiwgeW91IGhhdmUgdG8ga2VlcCBhIGZldyB0aGluZ3MgaW4gbWluZDoKCiAgMS4gdGhlIG51bWJlciBvZiBjb21wb25lbnRzIChQQ3MpIHRvIHVzZSBpcyBwYXNzZWQgd2l0aCB0aGUgYXJndW1lbnQgYG5jb21wYAogIDIuIHRoZSBmdW5jdGlvbiBhbGxvd3MgeW91IHRvIHNjYWxlIChzZXQgYHNjYWxlID0gVFJVRWApIGFuZAogIGNlbnRlciAoc2V0IGBjZW50ZXIgPSBUUlVFYCkgdGhlIHByZWRpY3RvcnMgZmlyc3QgKGluIHRoZSBleGFtcGxlIGhlcmUsICRcbWF0aGJme1h9JCBoYXMgYWxyZWFkeSBiZWVuIGNlbnRlcmVkIGFuZCBzY2FsZWQpLgogIApZb3UgY2FuIHVzZSB0aGUgZnVuY3Rpb24gYHBjcigpYCBpbiBtdWNoIHRoZSBzYW1lIHdheSBhcyB5b3Ugd291bGQKdXNlIGBsbSgpYC4gVGhlIHJlc3VsdGluZyBmaXQgY2FuIGVhc2lseSBiZSBleGFtaW5lZCB1c2luZyB0aGUgCmZ1bmN0aW9uIGBzdW1tYXJ5KClgLCBidXQgdGhlIG91dHB1dCBsb29rcyBxdWl0ZSBkaWZmZXJlbnQgZnJvbQp3aGF0IHlvdSB3b3VsZCBnZXQgZnJvbSBgbG1gLgoKYGBge3IgUEMtcmVncmVzc2lvbi1wbHMtcGFja2FnZX0KIyBYIGlzIGFscmVhZHkgc2NhbGVkIGFuZCBjZW50ZXJlZCwgc28gdGhhdCdzIG5vdCBuZWVkZWQuCnBjcl9tb2RlbDIgPC0gcGNyKFkgfiBYLCBuY29tcCA9IDQpCnN1bW1hcnkocGNyX21vZGVsMikKYGBgCgpGaXJzdCBvZiBhbGwgdGhlIG91dHB1dCBzaG93cyB5b3UgdGhlIGRhdGEgZGltZW5zaW9ucyBhbmQgdGhlIGZpdHRpbmcKbWV0aG9kIHVzZWQuIEluIHRoaXMgY2FzZSwgdGhhdCBpcyBQQyBjYWxjdWxhdGlvbiBiYXNlZCBvbiBTVkQuIFRoZQpgc3VtbWFyeSgpYCBmdW5jdGlvbiBhbHNvIHByb3ZpZGVzIHRoZSBwZXJjZW50YWdlIG9mIHZhcmlhbmNlCmV4cGxhaW5lZCBpbiB0aGUgcHJlZGljdG9ycyBhbmQgaW4gdGhlIHJlc3BvbnNlIHVzaW5nIGRpZmZlcmVudCBudW1iZXJzCm9mIGNvbXBvbmVudHMuIEZvciBleGFtcGxlLCB0aGUgZmlyc3QgUEMgb25seSBjYXB0dXJlcyA2MS4yMiUgb2YgYWxsCnRoZSB2YXJpYW5jZSwgb3IgaW5mb3JtYXRpb24gaW4gdGhlIHByZWRpY3RvcnMgYW5kIGl0IGV4cGxhaW5zIDYyLjklCm9mIHRoZSB2YXJpYW5jZSBpbiB0aGUgb3V0Y29tZS4gTm90ZSB0aGF0IGZvciBib3RoIG1ldGhvZHMgdGhlIGNob2ljZSBvZgp0aGUgbnVtYmVyIG9mIHByaW5jaXBhbCBjb21wb25lbnRzIHdhcyBhcmJpdHJhcnkgY2hvc2VuIHRvIGJlIDQuCgpBdCBhIGxhdGVyIHN0YWdlLCB3ZSB3aWxsIGxvb2sgYXQgaG93IHRvIGNob29zZSB0aGUgbnVtYmVyIG9mIGNvbXBvbmVudHMKdGhhdCBoYXMgdGhlIF9fc21hbGxlc3QgcHJlZGljdGlvbiBlcnJvcl9fLgoKCiMgUmlkZ2VzLCBMYXNzb3MgYW5kIEVsYXN0aWMgTmV0cyB7I2VsbmV0LXRoZW9yeX0KClJpZGdlIHJlZ3Jlc3Npb24sIGxhc3NvIHJlZ3Jlc3Npb24gYW5kIGVsYXN0aWMgbmV0cyBhcmUgYWxsIGNsb3NlbHkKcmVsYXRlZCB0ZWNobmlxdWVzLCBiYXNlZCBvbiB0aGUgc2FtZSBpZGVhOiBhZGQgYSBwZW5hbHR5IHRlcm0gdG8gCnRoZSBlc3RpbWF0aW5nIGZ1bmN0aW9uIHNvICQoe1xtYXRoYmZ7WH19XlR7XG1hdGhiZntYfX0pJApiZWNvbWVzIGZ1bGwgcmFuayBhZ2FpbiBhbmQgaXMgaW52ZXJ0aWJsZS4gVHdvIGRpZmZlcmVudCBwZW5hbHR5IAp0ZXJtcyBvciByZWd1bGFyaXphdGlvbiBtZXRob2RzIGNhbiBiZSB1c2VkOgoKMS4gTDEgcmVndWxhcml6YXRpb246IHRoaXMgcmVndWxhcml6YXRpb24gYWRkcyBhIHRlcm0gJHtcZ2FtbWFfMVx8XGJvbGRzeW1ib2x7XGJldGF9XHxfezF9fSQgdG8gdGhlIGVzdGltYXRpbmcgZXF1YXRpb24uClRoZSB0ZXJtIHdpbGwgYWRkIGEgcGVuYWx0eSBiYXNlZCBvbiB0aGUgKmFic29sdXRlIHZhbHVlKiBvZiB0aGUKbWFnbml0dWRlIG9mIHRoZSBjb2VmZmljaWVudHMuIFRoaXMgaXMgdXNlZCBieSB0aGUgX19sYXNzbyByZWdyZXNzaW9uX18KIAokJAogXGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19XntcdGV4dHtsYXNzb319ID0gXHRleHR7YXJnbWlufV97XGJvbGRzeW1ib2x7XGJldGF9fVxkaXNwbGF5c3R5bGUoeyhcbWF0aGJme1l9LVxtYXRoYmZ7WH1cYm9sZHN5bWJvbHtcYmV0YX0pXlQoXG1hdGhiZntZfS1cbWF0aGJme1h9XGJvbGRzeW1ib2x7XGJldGF9KSt7XGdhbW1hXzFcfFxib2xkc3ltYm9se1xiZXRhfVx8X3sxfX19XGRpc3BsYXlzdHlsZSkKJCQKCjIuIEwyIHJlZ3VsYXJpemF0aW9uOiB0aGlzIHJlZ3VsYXJpemF0aW9uIGFkZHMgYSB0ZXJtICR7XGdhbW1hXzJcfFxib2xkc3ltYm9se1xiZXRhfVx8X3syfV57Mn19JCB0byB0aGUgZXN0aW1hdGluZyBlcXVhdGlvbi4KVGhlIHBlbmFsdHkgdGVybSBpcyBiYXNlZCBvbiB0aGUgc3F1YXJlIG9mIHRoZSBtYWduaXR1ZGUgb2YgdGhlIApjb2VmZmljaWVudHMuIFRoaXMgaXMgdXNlZCBieSBfX3JpZGdlIHJlZ3Jlc3Npb25fXy4KCiQkCiBcaGF0e1xib2xkc3ltYm9se1xiZXRhfX1ee1x0ZXh0e3JpZGdlfX0gPSBcdGV4dHthcmdtaW59X3tcYm9sZHN5bWJvbHtcYmV0YX19XGRpc3BsYXlzdHlsZSh7KFxtYXRoYmZ7WX0tXG1hdGhiZntYfVxib2xkc3ltYm9se1xiZXRhfSleVChcbWF0aGJme1l9LVxtYXRoYmZ7WH1cYm9sZHN5bWJvbHtcYmV0YX0pK3tcZ2FtbWFfMlx8XGJvbGRzeW1ib2x7XGJldGF9XHxfezJ9XnsyfX19XGRpc3BsYXlzdHlsZSkKJCQKCkVsYXN0aWMgbmV0cyBjb21iaW5lIGJvdGggdHlwZXMgb2YgcmVndWxhcml6YXRpb25zLiBJdCBkb2VzIHNvIGJ5IAppbnRyb2R1Y2luZyBhICRcYWxwaGEkIG1peGluZyBwYXJhbWV0ZXIgdGhhdCBlc3NlbnRpYWxseSBjb21iaW5lcwp0aGUgTDEgYW5kIEwyIG5vcm1zIGluIGEgd2VpZ2h0ZWQgYXZlcmFnZS4KCiQkCiBcaGF0e1xib2xkc3ltYm9se1xiZXRhfX1ee1x0ZXh0e2VsLm5ldH19ID0gXHRleHR7YXJnbWlufV97XGJvbGRzeW1ib2x7XGJldGF9fVxkaXNwbGF5c3R5bGUoeyhcbWF0aGJme1l9LVxtYXRoYmZ7WH1cYm9sZHN5bWJvbHtcYmV0YX0pXntUfShcbWF0aGJme1l9LVxtYXRoYmZ7WH1cYm9sZHN5bWJvbHtcYmV0YX0pK3tcYWxwaGEgXGdhbW1hXzFcfFxib2xkc3ltYm9se1xiZXRhfVx8X3sxfX0rIHsoMSAtIFxhbHBoYSlcZ2FtbWFfMlx8XGJvbGRzeW1ib2x7XGJldGF9XHxfezJ9XnsyfX19XGRpc3BsYXlzdHlsZSkKJCQKCgoKIyBFeGVyY2lzZTogVmVyaWZpY2F0aW9uIG9mIHJpZGdlIHJlZ3Jlc3Npb24KCkluIGxlYXN0IHNxdWFyZSByZWdyZXNzaW9uIHRoZSBtaW5pbWl6YXRpb24gb2YgdGhlIGVzdGltYXRpb24gZnVuY3Rpb24KJHx7XG1hdGhiZntZfSAtIFxtYXRoYmZ7WH0gXGJvbGRzeW1ib2x7XGJldGF9fVx8XnsyfV97Mn0kIGxlYWRzIHRvIHRoZSBzb2x1dGlvbiAke1xib2xkc3ltYm9se1xoYXR7XGJldGF9fT0oXG1hdGhiZntYXlRYfSleey0xfVxtYXRoYmZ7WF5UWX19JC4gCgpGb3IgdGhlIHBlbmFsaXplZCBsZWFzdCBzcXVhcmVzIGNyaXRlcmlvbiB1c2VkIGJ5IHJpZGdlIHJlZ3Jlc3Npb24sIHlvdSBtaW5pbWl6ZSAKJFx8e1xtYXRoYmZ7WX0tXG1hdGhiZntYfVxib2xkc3ltYm9se1xiZXRhfVx8XnsyfV97Mn19K1xnYW1tYXtcYm9sZHN5bWJvbHtcfFxiZXRhXHxeezJ9X3syfX19JAp3aGljaCBsZWFkcyB0byBmb2xsb3dpbmcgc29sdXRpb246CgokJAp7XGJvbGRzeW1ib2x7XGhhdHtcYmV0YX19PShcbWF0aGJme1heVFh9fStcZ2FtbWF7XG1hdGhiZntJfX0pXnstMX17XG1hdGhiZntYXlRZfX0KJCQKCndoZXJlICRcbWF0aGJme0l9JCBpcyB0aGUgJHAgXHRpbWVzIHAkIGlkZW50aXR5IG1hdHJpeC4KClRoZSByaWRnZSBwYXJhbWV0ZXIgJFxnYW1tYSQgKnNocmlua3MqIHRoZSBjb2VmZmljaWVudHMgdG93YXJkcyAwLCB3aXRoICRcZ2FtbWEgPSAwJCBiZWluZyBlcXVpdmFsZW50IHRvIE9MUyAobm8gc2hyaW5rYWdlKSBhbmQgJFxnYW1tYSA9ICtcaW5mdHkkIGJlaW5nIGVxdWl2YWxlbnQgdG8gc2V0dGluZyBhbGwgJFxoYXR7XGJldGF9JCdzIHRvIDAuClRoZSBvcHRpbWFsIHBhcmFtZXRlciBsaWVzIHNvbWV3aGVyZSBpbiBiZXR3ZWVuIGFuZCBuZWVkcyB0byBiZSB0dW5lZCBieSB0aGUgdXNlci4KCgojIyBUYXNrcyB7LX0KClNvbHZlIHRoZSBmb2xsb3dpbmcgZXhlcmNpc2VzIHVzaW5nIFIuCgojIyMjIDEuIFZlcmlmeSB0aGF0ICR7XG1hdGhiZnsoWF5UWH19K1xnYW1tYXtcbWF0aGJme0l9fSkkIGhhcyByYW5rICQyMDAkLCBmb3IgYW55ICRcZ2FtbWE+MCQgb2YgeW91ciBjaG9pY2UuIHstfQoKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CmBgYHtyfQpYdFggPC0gY3Jvc3Nwcm9kKFgpCnAgPC0gbmNvbChYKQpnYW1tYSA8LSAyICMgTXkgY2hvaWNlCgojIENvbXB1dGUgcGVuYWxpemVkIG1hdHJpeApYdFhfZ2FtbWFJIDwtIFh0WCArIChnYW1tYSAqIGRpYWcocCkpCmRpbShYdFhfZ2FtbWFJKQpxcihYdFhfZ2FtbWFJKSRyYW5rID09IDIwMCAjIGluZGVlZApgYGAKPC9kZXRhaWxzPgoKCiMjIyMgMi4gQ2hlY2sgdGhhdCB0aGUgaW52ZXJzZSBvZiAke1xtYXRoYmZ7KFheVFh9fStcZ2FtbWF7XG1hdGhiZntJfX0pJCBjYW4gYmUgY29tcHV0ZWQuIHstfQoKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CmBgYHtyfQojIFllcywgaXQgY2FuIGJlIGNvbXB1dGVkIChubyBlcnJvcikKWHRYX2dhbW1hSV9pbnYgPC0gc29sdmUoWHRYX2dhbW1hSSkKc3RyKFh0WF9nYW1tYUlfaW52KQpgYGAKPC9kZXRhaWxzPgoKCiMjIyMgMy4gRmluYWxseSwgY29tcHV0ZSAke1xib2xkc3ltYm9se1xoYXR7XGJldGF9fT0oXG1hdGhiZntYXlRYfX0rXGdhbW1he1xtYXRoYmZ7SX19KV57LTF9e1xtYXRoYmZ7WF5UWX19JC4gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KYGBge3IgcmlkZ2UtYmV0YS1lc3RpbWF0ZXN9CiMjIENhbGN1bGF0ZSByaWRnZSBiZXRhIGVzdGltYXRlcwojIyBVc2UgYGRyb3BgIHRvIGRyb3AgZGltZW5zaW9ucyBhbmQgY3JlYXRlIHZlY3RvcgpyaWRnZV9iZXRhcyA8LSBkcm9wKFh0WF9nYW1tYUlfaW52ICUqJSB0KFgpICUqJSBZKQpsZW5ndGgocmlkZ2VfYmV0YXMpICMgb25lIGZvciBldmVyeSBnZW5lCnN1bW1hcnkocmlkZ2VfYmV0YXMpCmBgYAoKV2UgaGF2ZSBub3cgbWFudWFsbHkgY2FsY3VsYXRlZCB0aGUgcmlkZ2UgcmVncmVzc2lvbiBlc3RpbWF0ZXMuCgo8L2RldGFpbHM+CgoKCiMgUGVyZm9ybWluZyByaWRnZSBhbmQgbGFzc28gcmVncmVzc2lvbiB3aXRoIGBnbG1uZXRgCgpUaGUgcGFja2FnZSAqW2dsbW5ldF0oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1nbG1uZXQpKiBwcm92aWRlcyBhCmZ1bmN0aW9uIGBnbG1uZXQoKWAgdGhhdCBhbGxvd3MgeW91IHRvIGZpdCBhbGwgdGhyZWUgdHlwZXMgb2YgcmVncmVzc2lvbnMuIFdoaWNoCnR5cGUgaXMgdXNlZCwgY2FuIGJlIGRldGVybWluZWQgYnkgc3BlY2lmeWluZyB0aGUgYGFscGhhYCBhcmd1bWVudC4gRm9yIGEKX19yaWRnZSByZWdyZXNzaW9uX18sIHlvdSBzZXQgYGFscGhhYCB0byAwLCBhbmQgZm9yIGEgX19sYXNzbyByZWdyZXNzaW9uX18geW91CnNldCBgYWxwaGFgIHRvIDEuIE90aGVyIGBhbHBoYWAgdmFsdWVzIGJldHdlZW4gMCBhbmQgMSB3aWxsIGZpdCBhIGZvcm0gb2YKZWxhc3RpYyBuZXQuIFRoaXMgZnVuY3Rpb24gaGFzIHNsaWdodGx5IGRpZmZlcmVudCBzeW50YXggZnJvbSB0aGUgb3RoZXIKbW9kZWwtZml0dGluZyBmdW5jdGlvbnMuIFRvIGJlIGFibGUgdG8gdXNlIGl0LCB5b3UgaGF2ZSB0byBwYXNzIGEgYHhgIG1hdHJpeCBhcwp3ZWxsIGFzIGEgYHlgIHZlY3RvciwgYW5kIHlvdSBkb24ndCB1c2UgdGhlIGZvcm11bGEgc3ludGF4LgoKVGhlIGdhbW1hIHZhbHVlLCB3aGljaCBjb250cm9scyB0aGUgInN0cmVuZ3RoIiBvZiB0aGUgcGVuYWx0eSwgY2FuIGJlIHBhc3NlZCBieQp0aGUgYXJndW1lbnQgYGxhbWJkYWAgKG5vdGF0aW9uIGlzbid0IGFsd2F5cyBjb25zaXN0ZW50IGJldHdlZW4gdGV4dCBib29rcyBhbmQKc29mdHdhcmUuLi4pLiBUaGUgZnVuY3Rpb24gYGdsbW5ldCgpYCBjYW4gYWxzbyBjYXJyeSBvdXQgYSBzZWFyY2ggZm9yIGZpbmRpbmcKdGhlIGJlc3QgZ2FtbWEgdmFsdWUgZm9yIGEgZml0LiBUaGlzIGNhbiBiZSBkb25lIGJ5IHBhc3NpbmcgbXVsdGlwbGUgdmFsdWVzIHRvCnRoZSBhcmd1bWVudCBgbGFtYmRhYC4gSWYgbm90IHN1cHBsaWVkLCBgZ2xtbmV0YCB3aWxsIGdlbmVyYXRlIGEgcmFuZ2Ugb2YgdmFsdWVzCml0c2VsZiwgYmFzZWQgb24gdGhlIGRhdGEgd2hlcmVieSB0aGUgbnVtYmVyIG9mIHZhbHVlcyBjYW4gYmUgY29udHJvbGxlZCB3aXRoCnRoZSBgbmxhbWJkYWAgYXJndW1lbnQuIFRoaXMgaXMgZ2VuZXJhbGx5IHRoZSByZWNvbW1lbmRlZCB3YXkgdG8gdXNlIGBnbG1uZXRgLApzZWUgYD9nbG1uZXRgIGZvciBkZXRhaWxzLgoKRm9yIGEgdGhvcm91Z2ggaW50cm9kdWN0aW9uIHRvIHRoZSBfX2dsbW5ldF9fIHBhY2thZ2UgYW5kIGVsYXN0aWMgbmV0IG1vZGVscyBpbgpnZW5lcmFsLCBzZWUgdGhlCltnbG1uZXQgaW50cm9kdWN0aW9uIHZpZ25ldHRlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZ2xtbmV0L3ZpZ25ldHRlcy9nbG1uZXQucGRmKQoKCiMjIERlbW9uc3RyYXRpb246IFJpZGdlIHJlZ3Jlc3Npb24gey19CgpMZXQncyBwZXJmb3JtIGEgcmlkZ2UgcmVncmVzc2lvbiBpbiBvcmRlciB0byBwcmVkaWN0IGV4cHJlc3Npb24gbGV2ZWxzCm9mIHRoZSBUUklNMzIgZ2VuZSB1c2luZyB0aGUgMjAwIGdlbmUgcHJvYmVzIGRhdGEuIFdlIGNhbiBzdGFydCBieQp1c2luZyBhICRcZ2FtbWEkIHZhbHVlIG9mIDIuCgpgYGB7ciBnbG1uZXQtcmlkZ2UtcmVncmVzc2lvbn0KZ2FtbWEgPC0gMgpyaWRnZV9tb2RlbCA8LSBnbG1uZXQoWCwgWSwgYWxwaGEgPSAwLCBsYW1iZGEgPSBnYW1tYSkKCiMgaGF2ZSBhIGxvb2sgYXQgdGhlIGZpcnN0IDEwIGNvZWZmaWNpZW50cwpjb2VmKHJpZGdlX21vZGVsKVsxOjEwXQpgYGAKClRoZSBmaXJzdCBjb2VmZmljaWVudCBpcyB0aGUgaW50ZXJjZXB0LCBhbmQgaXMgYWdhaW4gZXNzZW50aWFsbHkgMC4gQnV0CmEgdmFsdWUgb2YgMiBmb3IgJFxnYW1tYSQgbWlnaHQgbm90IGJlIHRoZSBiZXN0IGNob2ljZSwgc28gbGV0J3Mgc2VlIGhvdwp0aGUgY29lZmZpY2llbnRzIGNoYW5nZSB3aXRoIGRpZmZlcmVudCB2YWx1ZXMgZm9yICRcZ2FtbWEkLgoKV2Ugd2lsbCBjcmVhdGUgYSAqZ3JpZCogb2YgJFxnYW1tYSQgdmFsdWVzLCBpLmUuIGEgcmFuZ2Ugb2YgdmFsdWVzIHRoYXQgd2lsbCBiZQp1c2VkIGFzIGlucHV0IGZvciB0aGUgYGdsbW5ldGAgZnVuY3Rpb24uIE5vdGUgdGhhdCB0aGlzIGZ1bmN0aW9uIGNhbiB0YWtlIGEKdmVjdG9yIG9mIHZhbHVlcyBhcyBpbnB1dCBmb3IgdGhlIGBsYW1iZGFgIGFyZ3VtZW50LCBhbGxvd2luZyB0byBmaXQgbXVsdGlwbGUKbW9kZWxzIHdpdGggdGhlIHNhbWUgaW5wdXQgZGF0YSBidXQgZGlmZmVyZW50IGh5cGVycGFyYW1ldGVycy4KCmBgYHtyIHJpZGdlLXJlZ3Jlc3Npb24tZ3JpZC1zZWFyY2h9CmdyaWQgPC0gc2VxKDEsIDEwMDAsIGJ5ID0gMTApICAjIDEgdG8gMTAwMCB3aXRoIHN0ZXBzIG9mIDEwCnJpZGdlX21vZF9ncmlkIDwtIGdsbW5ldChYLCBZLCBhbHBoYSA9IDAsIGxhbWJkYSA9IGdyaWQpCgojIFBsb3QgdGhlIGNvZWZmaWNpZW50cyBhZ2FpbnN0IHRoZSAobmF0dXJhbCkgTE9HIGxhbWJkYSBzZXF1ZW5jZSEKIyBzZWUgP3Bsb3QuZ2xtbmV0CnBsb3QocmlkZ2VfbW9kX2dyaWQsIHh2YXIgPSAibGFtYmRhIiwgeGxhYiA9ICJsb2coZ2FtbWEpIikKIyBhZGQgYSB2ZXJ0aWNhbCBsaW5lIGF0IGdhbW1hID0gMgp0ZXh0KGxvZyhnYW1tYSksIC0wLjA1LCBsYWJlbHMgPSBleHByZXNzaW9uKGdhbW1hID09IDIpLCAKICAgICBhZGogPSAtMC41LCBjb2wgPSAiZmlyZWJyaWNrIikKYWJsaW5lKHYgPSBsb2coZ2FtbWEpLCBjb2wgPSAiZmlyZWJyaWNrIiwgbHdkID0gMikKYGBgCgpUaGlzIHBsb3QgaXMga25vd24gYXMgYSBfX2NvZWZmaWNpZW50IHByb2ZpbGUgcGxvdF9fLCBlYWNoIGNvbG9yZWQgbGluZQpyZXByZXNlbnRzIGEgY29lZmZpY2llbnQgJFxoYXR7XGJldGF9JCBmcm9tIHRoZSByZWdyZXNzaW9uIG1vZGVsIGFuZCBzaG93cyBob3cKdGhleSBjaGFuZ2Ugd2l0aCBpbmNyZWFzZWQgdmFsdWVzIG9mICRcZ2FtbWEkIChvbiB0aGUgbG9nLXNjYWxlKQpeW05vdGU6IGBsb2coKWAgaW4gUiBpcyB0aGUgX19uYXR1cmFsIGxvZ2FyaXRobV9fIGJ5IGRlZmF1bHQgKGJhc2UgJGUkKSBhbmQgd2UKd2lsbCBhbHNvIHVzZSB0aGlzIG5vdGF0aW9uIGluIHRoZSB0ZXh0IChsaWtlIHRoZSB4LWF4aXMgdGl0bGUgb24gdGhlIHBsb3QgYWJvdmUpLgpUaGlzIG1pZ2h0IGJlIGRpZmZlcmVudCBmcm9tIHRoZSBub3RhdGlvbiB0aGF0IHlvdSdyZSB1c2VkIHRvICgkXGxuKCkkKS4KVG8gdGFrZSBsb2dhcml0aG1zIHdpdGggYSBkaWZmZXJlbnQgYmFzZSBpbiBSIHlvdSBjYW4gc3BlY2lmeSB0aGUgYGJhc2UgPSBgCmFyZ3VtZW50IG9mIGBsb2dgIG9yIHVzZSB0aGUgc2hvcnRoYW5kIGZ1bmN0aW9ucyBgbG9nMTAoeClgIGFuZCBgbG9nMih4KWAgZm9yCmJhc2UgMTAgYW5kIDIsIHJlc3BlY3RpdmVseV0uCgpOb3RlIHRoYXQgZm9yIGhpZ2hlciB2YWx1ZXMgJFxnYW1tYSQsIHRoZSBjb2VmZmljaWVudCBlc3RpbWF0ZXMgYmVjb21lIGNsb3NlciB0byAwLApzaG93aW5nIHRoZSAqc2hyaW5rYWdlKiBlZmZlY3Qgb2YgdGhlIHJpZGdlIHBlbmFsdHkuCgpTaW1pbGFyIHRvIHRoZSBQQyByZWdyZXNzaW9uIGV4YW1wbGUsIHdlIGNob3NlICRcZ2FtbWE9MiQgYW5kIHRoZSBncmlkIHJhdGhlcgphcmJpdHJhcmlseS4gV2Ugd2lsbCBzZWUgc3Vic2VxdWVudGx5LCBob3cgdG8gY2hvb3NlICRcZ2FtbWEkIHRoYXQgbWluaW1pemVzIHRoZQpwcmVkaWN0aW9uIGVycm9yLgoKCiMgRXhlcmNpc2U6IExhc3NvIHJlZ3Jlc3Npb24KCkxhc3NvIHJlZ3Jlc3Npb24gaXMgYWxzbyBhIGZvcm0gb2YgcGVuYWxpemVkIHJlZ3Jlc3Npb24sIGJ1dCB3ZSBkbyBub3QgaGF2ZSBhbgphbmFseXRpYyBzb2x1dGlvbiBvZiAkXGhhdHt7XGJvbGRzeW1ib2x7XGJldGF9fX0kIGFzIGluIGxlYXN0IHNxdWFyZXMKYW5kIHJpZGdlIHJlZ3Jlc3Npb24uIEluIG9yZGVyIHRvIGZpdCBhIGxhc3NvIG1vZGVsLCB3ZSBvbmNlIGFnYWluIHVzZQp0aGUgYGdsbW5ldCgpYCBmdW5jdGlvbi4gSG93ZXZlciwgdGhpcyB0aW1lIHdlIHVzZSB0aGUgYXJndW1lbnQKYGFscGhhID0gMWAKCgojIyBUYXNrcyB7LX0KCiMjIyMgMS4gVmVyaWZ5IHRoYXQgc2V0dGluZyBgYWxwaGEgPSAxYCBpbmRlZWQgY29ycmVzcG9uZHMgdG8gbGFzc28gcmVncmVzc2lvbiB1c2luZyB0aGUgZXF1YXRpb25zIGZyb20gW1NlY3Rpb24gM10oI2VsbmV0LXRoZW9yeSkuIHstfQoKCiMjIyMgMi4gUGVyZm9ybSBhIGxhc3NvIHJlZ3Jlc3Npb24gd2l0aCB0aGUgYGdsbW5ldGAgZnVuY3Rpb24gd2l0aCBgWWAgdGhlIHJlc3BvbnNlIGFuZCBgWGAgdGhlIHByZWRpY3RvcnMuIHstfQoKWW91IGRvIG5vdCBoYXZlIHRvIHByb3ZpZGUgYSBjdXN0b20gc2VxdWVuY2Ugb2YgJFxnYW1tYSQgKGBsYW1iZGFgKSB2YWx1ZXMgaGVyZQpidXQgY2FuIGluc3RlYWQgcmVseSBvbiBgZ2xtbmV0YCdzIGRlZmF1bHQgYmVoYXZpb3VyIG9mIGNob29zaW5nIHRoZSBncmlkIG9mCiRcZ2FtbWEkIHZhbHVlcyBiYXNlZCBvbiB0aGUgZGF0YSAoc2VlIGA/Z2xtbmV0YCBmb3IgbW9yZSBkZXRhaWxzKS4KCjxkZXRhaWxzPjxzdW1tYXJ5PlNvbHV0aW9uPC9zdW1tYXJ5PgpgYGB7ciBnbG1uZXQtbGFzc28tcmVncmVzc2lvbn0KIyBOb3RlIHRoYXQgdGhlIGdsbW5ldCgpIGZ1bmN0aW9uIGNhbiBzdXBwbHkgZ2FtbWEgYXV0b21hdGljYWxseQojIEJ5IGRlZmF1bHQgaXQgdXNlcyBhIHNlcXVlbmNlIG9mIDEwMCBsYW1iZGEgdmFsdWVzCmxhc3NvX21vZGVsIDwtIGdsbW5ldChYLCBZLCBhbHBoYSA9IDEpCmBgYAo8L2RldGFpbHM+CgoKIyMjIyAzLiBNYWtlIHRoZSBjb2VmZmljaWVudCBwcm9maWxlIHBsb3QgYW5kIGludGVycHJldC4gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KCmBgYHtyfQpwbG90KGxhc3NvX21vZGVsLCB4dmFyID0gImxhbWJkYSIsIHhsYWIgPSAibG9nKGdhbW1hKSIpCmBgYAoKTm90ZSB0aGF0IHRoZSBudW1iZXIgb2Ygbm9uLXplcm8gY29lZmZpY2llbnRzIGlzIGluZGljYXRlZCBhdCB0aGUgdG9wIG9mIHRoZSBwbG90LgpJbiB0aGUgY2FzZSBvZiBsYXNzby1yZWdyZXNzaW9uIHRoZSByZWd1bGFyaXphdGlvbiBpcyBtdWNoIGxlc3Mgc21vb3RoIGNvbXBhcmVkCnRvIHRoZSByaWRnZSByZWdyZXNzaW9uLCB3aXRoIHNvbWUgY29lZmZpY2llbnRzIGluY3JlYXNpbmcgZm9yIGhpZ2hlciAkXGdhbW1hJApiZWZvcmUgc2hhcnBseSBkcm9wcGluZyB0byB6ZXJvLgpJbiBjb250cmFzdCB0byByaWRnZSwgbGFzc28gZXZlbnR1YWxseSBzaHJpbmtzIGFsbCBjb2VmZmljaWVudHMgdG8gMC4KCjwvZGV0YWlscz4KCgojIEV2YWx1YXRpb24gb2YgcHJlZGljdGlvbiBtb2RlbHMgYW5kIHR1bmluZyBoeXBlcnBhcmFtZXRlcnMKCkZpcnN0IHdlIHdpbGwgc3BsaXQgb3VyIG9yaWdpbmFsIGRhdGEgaW4gYSB0cmFpbmluZyBhbmQgdGVzdCBzZXQgdG8gdmFsaWRhdGUgb3VyCm1vZGVsLiBUaGUgdHJhaW5pbmcgc2V0IHdpbGwgYmUgdXNlZCB0byB0cmFpbiB0aGUgbW9kZWwgYW5kIHR1bmUgdGhlCmh5cGVycGFyYW1ldGVycywgd2hpbGUgdGhlIHRlc3Qgc2V0IHdpbGwgYmUgdXNlZCB0byBldmFsdWF0ZSB0aGUKX19vdXQtb2Ytc2FtcGxlX18gcGVyZm9ybWFuY2Ugb2Ygb3VyIGZpbmFsIG1vZGVsLiBJZiB3ZSB3b3VsZCB1c2UgdGhlIHNhbWUgZGF0YQp0byBib3RoIGZpdCBhbmQgdGVzdCB0aGUgbW9kZWwsIHdlIHdvdWxkIGdldCBiaWFzZWQgcmVzdWx0cy4KCkJlZm9yZSB3ZSBiZWdpbiwgd2UgdXNlIHRoZSBgc2V0LnNlZWQoKWAgZnVuY3Rpb24gaW4gb3JkZXIgdG8gc2V0IGEgc2VlZApmb3IgUuKAmXMgcmFuZG9tIG51bWJlciBnZW5lcmF0b3IsIHNvIHRoYXQgd2Ugd2lsbCBhbGwgb2J0YWluIHByZWNpc2VseQp0aGUgc2FtZSByZXN1bHRzIGFzIHRob3NlIHNob3duIGJlbG93LiBJdCBpcyBnZW5lcmFsbHkgZ29vZCBwcmFjdGljZSB0bwpzZXQgYSByYW5kb20gc2VlZCB3aGVuIHBlcmZvcm1pbmcgYW4gYW5hbHlzaXMgc3VjaCBhcyBjcm9zcy12YWxpZGF0aW9uCnRoYXQgY29udGFpbnMgYW4gZWxlbWVudCBvZiByYW5kb21uZXNzLCBzbyB0aGF0IHRoZSByZXN1bHRzIG9idGFpbmVkIGNhbgpiZSByZXByb2R1Y2VkIGF0IGEgbGF0ZXIgdGltZS4KCldlIGJlZ2luIGJ5IHVzaW5nIHRoZSBgc2FtcGxlKClgIGZ1bmN0aW9uIHRvIHNwbGl0IHRoZSBzZXQgb2Ygc2FtcGxlcyBpbnRvIHR3bwpzdWJzZXRzLCBieSBzZWxlY3RpbmcgYSByYW5kb20gc3Vic2V0IG9mIDgwIG9ic2VydmF0aW9ucyBvdXQgb2YgdGhlIG9yaWdpbmFsIDEyMApvYnNlcnZhdGlvbnMuIFdlIHJlZmVyIHRvIHRoZXNlIG9ic2VydmF0aW9ucyBhcyB0aGUgX190cmFpbmluZ19fIHNldC4gVGhlIHJlc3QKb2YgdGhlIG9ic2VydmF0aW9ucyB3aWxsIGJlIHVzZWQgYXMgdGhlIF9fdGVzdF9fIHNldC4KCmBgYHtyIGNyZWF0ZS10cmFpbmluZy1zZXR9CnNldC5zZWVkKDEpCiMgU2FtcGxlIDgwIHJhbmRvbSBJRHMgZnJvbSB0aGUgcm93cyBvZiBYICgxMjAgdG90YWwpCnRyYWluSUQgPC0gc2FtcGxlKG5yb3coWCksIDgwKQoKIyBUcmFpbmluZyBkYXRhCnRyYWluWCA8LSBYW3RyYWluSUQsIF0KdHJhaW5ZIDwtIFlbdHJhaW5JRF0KCiMgVGVzdCBkYXRhCnRlc3RYIDwtIFhbLXRyYWluSUQsIF0KdGVzdFkgPC0gWVstdHJhaW5JRF0KYGBgCgpUbyBtYWtlIGZpdHRpbmcgdGhlIG1vZGVscyBhIGJpdCBlYXNpZXIgbGF0ZXIsIHdlIHdpbGwgYWxzbyBjcmVhdGUgMiBkYXRhLmZyYW1lcwpjb21iaW5pbmcgdGhlIHJlc3BvbnNlIGFuZCBwcmVkaWN0b3JzIGZvciB0aGUgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YS4KCmBgYHtyfQp0cmFpbl9kYXRhIDwtIGRhdGEuZnJhbWUoIlRSSU0zMiIgPSB0cmFpblksIHRyYWluWCkKdGVzdF9kYXRhIDwtIGRhdGEuZnJhbWUoIlRSSU0zMiIgPSB0ZXN0WSwgdGVzdFgpCgojIyBHbGFuY2luZyBhdCB0aGUgZGF0YSBzdHJ1Y3R1cmU6IGZvciB0aGUgZmlyc3QgMTAgY29sdW1ucyBvbmx5CnN0cih0cmFpbl9kYXRhWywgMToxMF0pCmBgYAoKCiMjIE1vZGVsIGV2YWx1YXRpb24KCldlIGFyZSBpbnRlcmVzdGVkIGluIHRoZSBfX291dC1vZi1zYW1wbGVfXyBlcnJvciBvZiBvdXIgbW9kZWxzLAppLmUuIGhvdyBnb29kIG91ciBtb2RlbCBkb2VzIG9uIHVuc2VlbiBkYXRhLgpfX1RoaXMgd2lsbCBhbGxvdyB1cyB0byBjb21wYXJlIGRpZmZlcmVudCAqY2xhc3Nlcyogb2YgbW9kZWxzX18uCkZvciBjb250aW51b3VzIG91dGNvbWVzIHdlIHdpbGwgdXNlIHRoZSBfX21lYW4gc3F1YXJlZCBlcnJvciAoTVNFKV9fCihvciBpdHMgc3F1YXJlLXJvb3QgdmVyc2lvbiwgdGhlIFJNU0UpLgoKVGhlIGV2YWx1YXRpb24gd2lsbCBhbGxvdyB1cyB0byBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiBkaWZmZXJlbnQgdHlwZXMgb2YKbW9kZWxzLCBlLmcuIFBDIHJlZ3Jlc3Npb24sIHJpZGdlIHJlZ3Jlc3Npb24gYW5kIGxhc3NvIHJlZ3Jlc3Npb24sIG9uIG91ciBkYXRhLgpIb3dldmVyLCB3ZSBzdGlsbCBuZWVkIHRvIGZpbmQgdGhlIG9wdGltYWwgbW9kZWwgd2l0aGluIGVhY2ggb2YgdGhlc2UgY2xhc3NlcywKYnkgc2VsZWN0aW5nIHRoZSBiZXN0IGh5cGVycGFyYW1ldGVyIChudW1iZXIgb2YgUENzIGZvciBQQyByZWdyZXNzaW9uIGFuZCAkXGdhbW1hJApmb3IgbGFzc28gYW5kIHJpZGdlKS4KRm9yIHRoYXQgd2Ugd2lsbCB1c2UgClsqJGskLWZvbGQgQ3Jvc3MgVmFsaWRhdGlvbipdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0Nyb3NzLXZhbGlkYXRpb25fKHN0YXRpc3RpY3MpKQpvbiBvdXIgdHJhaW5pbmcgc2V0LgoKCiMjIFR1bmluZyBoeXBlcnBhcmFtZXRlcnMKClRoZSB0ZXN0IHNldCBpcyBvbmx5IHVzZWQgdG8gZXZhbHVhdGUgdGhlICpmaW5hbCogbW9kZWwuClRvIGFjaGlldmUgdGhpcyBmaW5hbCBtb2RlbCwgd2UgbmVlZCB0byBmaW5kIHRoZSBvcHRpbWFsIGh5cGVycGFyYW1ldGVycywKaS5lLiB0aGUgaHlwZXJwYXJhbWV0ZXJzIHRoYXQgYmVzdCBnZW5lcmFsaXplIHRoZSBtb2RlbCB0byB1bnNlZW4gZGF0YS4KV2UgY2FuIGVzdGltYXRlIHRoaXMgYnkgdXNpbmcgKmstZm9sZCBjcm9zcyB2YWxpZGF0aW9uKiAoJENWX2skKSBvbgp0aGUgdHJhaW5pbmcgZGF0YS4KClRoZSAkQ1ZfayQgZXN0aW1hdGVzIGNhbiBiZSBhdXRvbWF0aWNhbGx5IGNvbXB1dGVkIGZvciBhbnkKZ2VuZXJhbGl6ZWQgbGluZWFyIG1vZGVsIChnZW5lcmF0ZWQgd2l0aCBgZ2xtKClgIGFuZCBieSBleHRlbnNpb24gYGdsbW5ldCgpYCkgCnVzaW5nIHRoZSBgY3YuZ2xtKClgIGZ1bmN0aW9uIGZyb20gdGhlCipbYm9vdF0oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1ib290KSogcGFja2FnZS4KCgojIEV4YW1wbGU6IFBDIHJlZ3Jlc3Npb24gZXZhbHVhdGlvbgoKV2Ugc3RhcnQgd2l0aCB0aGUgUEMgcmVncmVzc2lvbiBhbmQgbG9vayBmb3IgdGhlIG9wdGltYWwgbnVtYmVyIG9mIFBDcyB0aGF0IG1pbmltaXplcwp0aGUgTVNFIHVzaW5nICRrJC1mb2xkIENyb3NzIHZhbGlkYXRpb24uCldlIHRoZW4gdXNlIHRoaXMgb3B0aW1hbCBudW1iZXIgb2YgUENzIHRvIHRyYWluIHRoZSBmaW5hbCBtb2RlbCBhbmQgZXZhbHVhdGUgaXQKb24gdGhlIHRlc3QgZGF0YS4KCgojIyBrLWZvbGQgQ3Jvc3MgVmFsaWRhdGlvbiB0byB0dW5lIG51bWJlciBvZiBjb21wb25lbnRzCgpDb252ZW5pZW50bHksIHRoZSBgcGNyYCBmdW5jdGlvbiBmcm9tIHRoZSBgcGxzYCBwYWNrYWdlIGhhcyBhbiBpbXBsZW1lbnRhdGlvbiBmb3IKay1mb2xkIENyb3NzIFZhbGlkYXRpb24uIFdlIHNpbXBseSBuZWVkIHRvIHNldCBgdmFsaWRhdGlvbiA9IENWYCBhbmQgYHNlZ21lbnRzID0gMjBgCnRvIHBlcmZvcm0gMjAtZm9sZCBDcm9zcyBWYWxpZGF0aW9uIHdpdGggUEMgcmVncmVzc2lvbi4KSWYgd2UgZG9uJ3Qgc3BlY2lmeSBgbmNvbXBgLCBgcGNyYCB3aWxsIHNlbGVjdCB0aGUgbWF4aW11bSBudW1iZXIgb2YgUENzIHRoYXQgY2FuCmJlIHVzZWQgZm9yIHRoZSBDVi4KCk5vdGUgdGhhdCBvdXIgdHJhaW5pbmcgZGF0YSBgdHJhaW5YYCBjb25zaXN0cyBvZiA4MCBvYnNlcnZhdGlvbnMgKHJvd3MpLgpJZiB3ZSBwZXJmb3JtIDIwLWZvbGQgQ1YsIHRoYXQgbWVhbnMgd2Ugd2lsbCBzcGxpdCB0aGUgZGF0YSBpbiAyMCBncm91cHMsIHNvCmVhY2ggZ3JvdXAgd2lsbCBjb25zaXN0IG9mIDQgb2JzZXJ2YXRpb25zLiBBdCBlYWNoIENWIGN5Y2xlLCBvbmUgZ3JvdXAgd2lsbCBiZSBsZWZ0Cm91dCBhbmQgdGhlIG1vZGVsIHdpbGwgYmUgdHJhaW5lZCBvbiB0aGUgcmVtYWluaW5nIGdyb3Vwcy4gVGhpcyBsZWF2ZXMgdXMgd2l0aAo3NiB0cmFpbmluZyBvYnNlcnZhdGlvbnMgZm9yIGVhY2ggQ1YgY3ljbGUsIHNvIHRoZSBtYXhpbWFsIG51bWJlciBvZiBjb21wb25lbnRzCnRoYXQgY2FuIGJlIHVzZWQgaW4gdGhlIGxpbmVhciByZWdyZXNzaW9uIGlzIDc1LgoKYGBge3IgcGNyLWtDVn0KIyMgU2V0IHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eSwga0NWIGlzIGEgcmFuZG9tIHByb2Nlc3MhCnNldC5zZWVkKDEyMykKCksgPC0gMjAKCiMjIFRoZSAnWSB+IC4nIG5vdGF0aW9uIG1lYW5zOiBmaXQgWSBieSBldmVyeSBvdGhlciB2YXJpYWJsZSBpbiB0aGUgZGF0YQpwY3JfY3YgPC0gcGNyKFRSSU0zMiB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhLCB2YWxpZGF0aW9uID0gIkNWIiwgc2VnbWVudHMgPSBLKQpzdW1tYXJ5KHBjcl9jdikKYGBgCgpXZSBjYW4gcGxvdCB0aGUgKnJvb3QgbWVhbiBzcXVhcmVkIGVycm9yIG9mIHByZWRpY3Rpb24qIChSTVNFUCkgZm9yIGVhY2ggbnVtYmVyCm9mIGNvbXBvbmVudHMgYXMgZm9sbG93cy4KCmBgYHtyIHBjcl9jdi1wbG90fQpwbG90KHBjcl9jdiwgcGxvdHR5cGUgPSAidmFsaWRhdGlvbiIpCmBgYAoKVGhlIGBwbHNgIHBhY2thZ2UgYWxzbyBoYXMgYSBmdW5jdGlvbiBgc2VsZWN0TmNvbXBgIHRvIHNlbGVjdCB0aGUgb3B0aW1hbCBudW1iZXIgb2YgY29tcG9uZW50cy4KSGVyZSB3ZSB1c2UgdGhlICJvbmUtc2lnbWEiIG1ldGhvZCwgd2hpY2ggcmV0dXJucyB0aGUgbG93ZXN0IG51bWJlciBvZiBjb21wb25lbnRzCmZvciB3aGljaCB0aGUgUk1TRSBpcyB3aXRoaW4gb25lIHN0YW5kYXJkIGVycm9yIG9mIHRoZSBhYnNvbHV0ZSBtaW5pbXVtLgpUaGUgZnVuY3Rpb24gYWxzbyBhbGxvd3MgcGxvdHRpbmcgdGhlIHJlc3VsdCBieSBzcGVjaWZ5aW5nIGBwbG90ID0gVFJVRWAuCgpgYGB7ciBwY3Itb3B0aW1hbC1uY29tcH0Kb3B0aW1hbF9uY29tcCA8LSBzZWxlY3ROY29tcChwY3JfY3YsIG1ldGhvZCA9ICJvbmVzaWdtYSIsIHBsb3QgPSBUUlVFKQpgYGAKClRoaXMgb3V0Y29tZSBzaG93cyB1cyB0aGF0IHRoZSBvcHRpbWFsIG51bWJlciBvZiBjb21wb25lbnRzIGZvciBvdXIgbW9kZWwgaXMKYHIgb3B0aW1hbF9uY29tcGAuCgoKIyMgVmFsaWRhdGlvbiBvbiB0ZXN0IGRhdGEKCldlIG5vdyB1c2Ugb3VyIG9wdGltYWwgbnVtYmVyIG9mIGNvbXBvbmVudHMgdG8gdHJhaW4gdGhlIGZpbmFsIFBDUiBtb2RlbC4KVGhpcyBtb2RlbCBpcyB0aGVuIHZhbGlkYXRlZCBvbiBieSBnZW5lcmF0aW5nIHByZWRpY3Rpb25zIGZvciB0aGUgdGVzdCBkYXRhIGFuZApjYWxjdWxhdGluZyB0aGUgTVNFLgoKV2UgZGVmaW5lIGEgY3VzdG9tIGZ1bmN0aW9uIHRvIGNhbGN1bGF0ZSB0aGUgTVNFLgpOb3RlIHRoYXQgdGhlcmUgaXMgYWxzbyBhbiBgTVNFUGAgZnVuY3Rpb24gaW4gdGhlIGBwbHNgIHBhY2thZ2Ugd2hpY2ggZG9lcyB0aGUKcHJlZGljdGlvbiBhbmQgTVNFIGNhbGN1bGF0aW9uIGluIG9uZSBnby4KQnV0IG91ciBvd24gZnVuY3Rpb24gd2lsbCBjb21lIGluIGhhbmR5IGxhdGVyIGZvciBsYXNzbyBhbmQgcmlkZ2UgcmVncmVzc2lvbi4KCmBgYHtyIE1TRX0KIyBNZWFuIFNxdWFyZWQgRXJyb3IKIyMgb2JzOiBvYnNlcnZhdGlvbnM7IHByZWQ6IHByZWRpY3Rpb25zCk1TRSA8LSBmdW5jdGlvbihvYnMsIHByZWQpewogIG1lYW4oKGRyb3Aob2JzKSAtIGRyb3AocHJlZCkpXjIpCn0KYGBgCgpgYGB7ciBmaW5hbF9wY3JfbW9kZWx9CmZpbmFsX3Bjcl9tb2RlbCA8LSBwY3IoVFJJTTMyIH4gLiwgZGF0YSA9IHRyYWluX2RhdGEsIG5jb21wID0gb3B0aW1hbF9uY29tcCkKcGNyX3ByZWRzIDwtIHByZWRpY3QoZmluYWxfcGNyX21vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhLCBuY29tcCA9IG9wdGltYWxfbmNvbXApCihwY3JfbXNlIDwtIE1TRSh0ZXN0WSwgcGNyX3ByZWRzKSkKYGBgCgpUaGlzIHZhbHVlIG9uIGl0cyBvd24gZG9lcyBub3QgdGVsbCB1cyB2ZXJ5IG11Y2gsIGJ1dCB3ZSBjYW4gdXNlIGl0IHRvIGNvbXBhcmUgb3VyClBDUiBtb2RlbCB3aXRoIG90aGVyIHR5cGVzIG9mIG1vZGVscyBsYXRlci4KCkZpbmFsbHksIHdlIHBsb3QgdGhlIHByZWRpY3RlZCB2YWx1ZXMgZm9yIG91ciByZXNwb25zZSB2YXJpYWJsZSAodGhlIFRSSU0zMiBnZW5lIGV4cHJlc3Npb24pCmFnYWluc3QgdGhlIGFjdHVhbCBvYnNlcnZlZCB2YWx1ZXMgZnJvbSBvdXIgdGVzdCBzZXQuCgpgYGB7ciBwY3ItcHJlZHBsb3R9CnByZWRwbG90KGZpbmFsX3Bjcl9tb2RlbCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSwgbGluZSA9IFRSVUUpCmBgYAoKCgojIEV4ZXJjaXNlOiBldmFsdWF0ZSBhbmQgY29tcGFyZSBwcmVkaWN0aW9uIG1vZGVscwoKIyMjIyAxLiBQZXJmb3JtIGEgbGFzc28gcmVncmVzc2lvbiB3aXRoIDIwLWZvbGQgQ3Jvc3MgVmFsaWRhdGlvbiBvbiB0aGUgdHJhaW5pbmcgZGF0YSAoYHRyYWluWGAsIGB0cmFpbllgKS4gUGxvdCB0aGUgcmVzdWx0cyBhbmQgc2VsZWN0IHRoZSBvcHRpbWFsIGBsYW1iZGFgICgkXGdhbW1hJCkgcGFyYW1ldGVyLiBGaXQgYSBmaW5hbCBtb2RlbCB3aXRoIHRoZSBzZWxlY3RlZCBgbGFtYmRhYCBhbmQgdmFsaWRhdGUgaXQgb24gdGhlIHRlc3QgZGF0YS4gey19CgoqSGludCo6IHVzZSB0aGUgYGN2LmdsbW5ldCgpYCBmdW5jdGlvbiwgZm9yIDIwIGZvbGRzIENWLCBzZXQgYG5mb2xkcyA9IDIwYCBhbmQgCnRvIHVzZSB0aGUgTVNFIG1ldHJpYyBzZXQgYHR5cGUubWVhc3VyZSA9ICJtc2UiYC4KR28gdG8gYD9jdi5nbG1uZXRgIGZvciBkZXRhaWxzLgoKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CgpgYGB7ciBsYXNzby1jdn0Kc2V0LnNlZWQoMTIzKQpsYXNzb19jdiA8LSBjdi5nbG1uZXQodHJhaW5YLCB0cmFpblksIGFscGhhID0gMSwgCiAgICAgICAgICAgICAgICAgICAgICBuZm9sZHMgPSBLLCB0eXBlLm1lYXN1cmUgPSAibXNlIikKbGFzc29fY3YKcGxvdChsYXNzb19jdikKYGBgCgpOb3RlIHRoYXQgd2UgY2FuIGV4dHJhY3QgdGhlIGZpdHRlZCBsYXNzbyByZWdyZXNzaW9uIG9iamVjdCBmcm9tIHRoZSBDViByZXN1bHQKYW5kIG1ha2UgdGhlIGNvZWZmaWNpZW50IHByb2ZpbGUgcGxvdCBhcyBiZWZvcmUuCgpgYGB7ciBsYXNzby1jdi1jb2VmZmljaWVudC1wcm9maWxlfQpwbG90KGxhc3NvX2N2JGdsbW5ldC5maXQsIHh2YXIgPSAibGFtYmRhIikKYGBgCgpXZSBjYW4gbG9vayBmb3IgdGhlIGdhbW1hIHZhbHVlcyB0aGF0IGdpdmUgdGhlIGJlc3QgcmVzdWx0LiAKSGVyZSB5b3UgaGF2ZSB0d28gcG9zc2liaWxpdGllcyA6CgoxLiBgbGFtYmRhLm1pbmA6IHRoZSB2YWx1ZSBvZiAgJFxnYW1tYSQgdGhhdCBnaXZlcyB0aGUgYmVzdCByZXN1bHQgZm9yIHRoZSBjcm9zc3ZhbGlkYXRpb24uCjIuIGBsYW1iZGEuMXNlYDogdGhlIGxhcmdlc3QgdmFsdWUgb2YgJFxnYW1tYSQgc3VjaCB0aGF0IHRoZSBNU0UgaXMgd2l0aGluIDEgc3RhbmRhcmQgZXJyb3IKb2YgdGhlIGJlc3QgcmVzdWx0IGZyb20gdGhlIGNyb3NzIHZhbGlkYXRpb24uCgpgYGB7cn0KbGFzc29fY3YkbGFtYmRhLm1pbgpsYXNzb19jdiRsYW1iZGEuMXNlCmBgYAoKV2Ugd2lsbCAocmF0aGVyIGFyYml0cmFyaWx5KSB1c2UgYGxhbWJkYS5taW5gIGhlcmUgdG8gZml0IHRoZSBmaW5hbCBtb2RlbCBhbmQgZ2VuZXJhdGUgcHJlZGljdGlvbnMgb24gdGhlIHRlc3QgZGF0YS4KTm90ZSB0aGF0IHdlIGRvbid0IGFjdHVhbGx5IGhhdmUgdG8gcmVkbyB0aGUgZml0dGluZywgd2UgY2FuIGp1c3QgdXNlIG91ciBleGlzdGluZwpgbGFzc29fY3ZgIG9iamVjdCwgd2hpY2ggYWxyZWFkeSBjb250YWlucyB0aGUgZml0dGVkIG1vZGVscyBmb3IgYSByYW5nZSBvZiBgbGFtYmRhYCB2YWx1ZXMuCldlIGNhbiB1c2UgdGhlIGBwcmVkaWN0YCBmdW5jdGlvbiBhbmQgc3BlY2lmeSB0aGUgYHNgIGFyZ3VtZW50ICh3aGljaCBjb25mdXNpbmdseSBzZXRzIGBsYW1iZGFgIGluIHRoaXMgY2FzZSkgIHRvIG1ha2UgcHJlZGljdGlvbnMgb24gdGhlIHRlc3QgZGF0YS4KCmBgYHtyfQpsYXNzb19wcmVkcyA8LSBwcmVkaWN0KGxhc3NvX2N2LCBzID0gbGFzc29fY3YkbGFtYmRhLm1pbiwgbmV3eCA9IHRlc3RYKQojIyBDYWxjdWxhdGUgTVNFCihsYXNzb19tc2UgPC0gTVNFKHRlc3RZLCBsYXNzb19wcmVkcykpCmBgYAo8L2RldGFpbHM+CgoKIyMjIyAyLiBEbyB0aGUgc2FtZSBmb3IgcmlkZ2UgcmVncmVzc2lvbi4gey19Cgo8ZGV0YWlscz48c3VtbWFyeT5Tb2x1dGlvbjwvc3VtbWFyeT4KCmBgYHtyIHJpZGdlLWN2fQpzZXQuc2VlZCgxMjMpCnJpZGdlX2N2IDwtIGN2LmdsbW5ldCh0cmFpblgsIHRyYWluWSwgYWxwaGEgPSAwLCAKICAgICAgICAgICAgICAgICAgICAgIG5mb2xkcyA9IEssIHR5cGUubWVhc3VyZSA9ICJtc2UiKQpyaWRnZV9jdgpwbG90KHJpZGdlX2N2KQpgYGAKCk5vdGUgdGhhdCB3ZSBjYW4gZXh0cmFjdCB0aGUgZml0dGVkIHJpZGdlIHJlZ3Jlc3Npb24gb2JqZWN0IGZyb20gdGhlIENWIHJlc3VsdAphbmQgbWFrZSB0aGUgY29lZmZpY2llbnQgcHJvZmlsZSBwbG90IGFzIGJlZm9yZS4KCmBgYHtyIHJpZGdlLWN2LWNvZWZmaWNpZW50LXByb2ZpbGV9CnBsb3QocmlkZ2VfY3YkZ2xtbmV0LmZpdCwgeHZhciA9ICJsYW1iZGEiKQpgYGAKCldlIGNhbiBsb29rIGZvciB0aGUgZ2FtbWEgdmFsdWVzIHRoYXQgZ2l2ZSB0aGUgYmVzdCByZXN1bHQuIApIZXJlIHlvdSBoYXZlIHR3byBwb3NzaWJpbGl0aWVzIDoKCjEuIGBsYW1iZGEubWluYDogdGhlIHZhbHVlIG9mICAkXGdhbW1hJCB0aGF0IGdpdmVzIHRoZSBiZXN0IHJlc3VsdCBmb3IgdGhlIGNyb3NzdmFsaWRhdGlvbi4KMi4gYGxhbWJkYS4xc2VgOiB0aGUgbGFyZ2VzdCB2YWx1ZSBvZiAkXGdhbW1hJCBzdWNoIHRoYXQgdGhlIE1TRSBpcyB3aXRoaW4gMSBzdGFuZGFyZCBlcnJvcgpvZiB0aGUgYmVzdCByZXN1bHQgZnJvbSB0aGUgY3Jvc3MgdmFsaWRhdGlvbi4KCmBgYHtyfQpyaWRnZV9jdiRsYW1iZGEubWluCnJpZGdlX2N2JGxhbWJkYS4xc2UKYGBgCgpXZSB3aWxsIChyYXRoZXIgYXJiaXRyYXJpbHkpIHVzZSBgbGFtYmRhLm1pbmAgaGVyZSB0byBmaXQgdGhlIGZpbmFsIG1vZGVsIGFuZCBnZW5lcmF0ZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBkYXRhLgpOb3RlIHRoYXQgd2UgZG9uJ3QgYWN0dWFsbHkgaGF2ZSB0byByZWRvIHRoZSBmaXR0aW5nLCB3ZSBjYW4ganVzdCB1c2Ugb3VyIGV4aXN0aW5nCmByaWRnZV9jdmAgb2JqZWN0LCB3aGljaCBhbHJlYWR5IGNvbnRhaW5zIHRoZSBmaXR0ZWQgbW9kZWxzIGZvciBhIHJhbmdlIG9mIGBsYW1iZGFgIHZhbHVlcy4KV2UgY2FuIHVzZSB0aGUgYHByZWRpY3RgIGZ1bmN0aW9uIGFuZCBzcGVjaWZ5IHRoZSBgc2AgYXJndW1lbnQgKHdoaWNoIGNvbmZ1c2luZ2x5IHNldHMgYGxhbWJkYWAgaW4gdGhpcyBjYXNlKSAgdG8gbWFrZSBwcmVkaWN0aW9ucyBvbiB0aGUgdGVzdCBkYXRhLgoKYGBge3IgcmlkZ2UtcHJlZGljdGlvbnN9CnJpZGdlX3ByZWRzIDwtIHByZWRpY3QocmlkZ2VfY3YsIHMgPSByaWRnZV9jdiRsYW1iZGEubWluLCBuZXd4ID0gdGVzdFgpCiMjIENhbGN1bGF0ZSBNU0UKKHJpZGdlX21zZSA8LSBNU0UodGVzdFksIHJpZGdlX3ByZWRzKSkKYGBgCgo8L2RldGFpbHM+CgoKIyMjIyAzLiBXaGljaCBvZiB0aGUgbW9kZWxzIGNvbnNpZGVyZWQgKFBDUiwgbGFzc28sIHJpZGdlKSBwZXJmb3JtcyBiZXN0Py4gey19CiAgICAKPGRldGFpbHM+PHN1bW1hcnk+U29sdXRpb248L3N1bW1hcnk+CgpCYXNlZCBvbiB0aGUgTVNFLCB0aGUgcmlkZ2UgbW9kZWwgcGVyZm9ybXMgYmVzdCBvbiB0aGUgdGVzdCBkYXRhLgoKYGBge3IsIGVjaG89RkFMU0V9CmtuaXRyOjprYWJsZSgKICBkYXRhLmZyYW1lKAogICAgIk1vZGVsIiA9IGMoIlBDUiIsICJMYXNzbyIsICJSaWRnZSIpLAogICAgIk1TRSIgPSBjKHBjcl9tc2UsIGxhc3NvX21zZSwgcmlkZ2VfbXNlKQogICkKKQpgYGAKPC9kZXRhaWxzPgo=