1 Introduction

1.1 Prediction with High Dimensional Predictors

General setting:

  • Aim: build a prediction model that gives a prediction of an outcome for a given set of predictors.

  • We use XX to refer to the predictors and YY to refer to the outcome.

  • A training data set is available, say (๐—,๐˜)(\mathbf{X},\mathbf{Y}). It contains nn observations on outcomes and on pp predictors.

  • Using the training data, a prediction model is build, say mฬ‚(๐—)\hat{m}(\mathbf{X}). This typically involves model building (feature selection) and parameter estimation.

  • During the model building, potential models need to be evaluated in terms of their prediction quality.

1.2 Example: Toxicogenomics in early drug development

1.2.1 Background

  • Effect of compound on gene expression.

  • Insight in action and toxicity of drug in early phase

  • Determine activity with bio-assay: e.g.ย binding affinity of compound to cell wall receptor (target, IC50).

  • Early phase: 20 to 50 compounds

  • Based on in vitro results one aims to get insight in how to build better compound (higher on-target activity less toxicity.

  • Small variations in molecular structure lead to variations in BA and gene expression.

  • Aim: Build model to predict bio-activity based on gene expression in liver cell line.

1.2.2 Data

  • 30 chemical compounds have been screened for toxicity

  • Bioassay data on toxicity screening

  • Gene expressions in a liver cell line are profiled for each compound (4000 genes)

toxData <- read_csv(
  "https://raw.githubusercontent.com/statOmics/HDA2020/data/toxDataCentered.csv",
  col_types = cols()
)
svdX <- svd(toxData[,-1])

Data is already centered:

toxData %>%
  colMeans %>%
  range
#> [1] -2.405483e-17  5.921189e-17
 toxData %>%
  names %>%
  head
#> [1] "BA" "X1" "X2" "X3" "X4" "X5"
  • First column contains data on Bioassay.
  • The higher the score on Bioassay the more toxic the compound
  • Other columns contain data on gene expression X1, โ€ฆ , X4000

1.2.3 Data exploration

toxData %>%
  ggplot(aes(x="",y=BA)) +
  geom_boxplot(outlier.shape=NA) +
  geom_point(position="jitter")

svdX <- toxData[,-1] %>%
  svd

k <- 2
Vk <- svdX$v[,1:k]
Uk <- svdX$u[,1:k]
Dk <- diag(svdX$d[1:k])
Zk <- Uk%*%Dk
colnames(Zk) <- paste0("Z",1:k)
colnames(Vk) <- paste0("V",1:k)

Zk %>%
  as.data.frame %>%
  mutate(BA = toxData %>% pull(BA)) %>%
  ggplot(aes(x= Z1, y = Z2, color = BA)) +
  geom_point(size = 3) +
  scale_colour_gradient2(low = "blue",mid="white",high="red") +
  geom_point(size = 3, pch = 21, color = "black")

  • Scores on the first two principal components (or MDS plot).
  • Each point corresponds to a compound.
  • Color code refers to the toxicity score (higher score more toxic).
  • Clear separation between compounds according to toxicity.

  • Next logic step in a PCA is to interpret the principal components.
  • We thus have to assess the loadings.
  • We can add a vector for each gene to get a biplot, but this would require plotting 4000 vectors, which would render the plot unreadable.

Alternative graph to look at the many loadings of the first two PCs.

grid.arrange(
  Vk %>%
    as.data.frame %>%
    mutate(geneID = 1:nrow(Vk)) %>%
    ggplot(aes(x = geneID, y = V1)) +
    geom_point(pch=21) +
    geom_hline(yintercept = c(-2,0,2)*sd(Vk[,1]), col = "red") ,
  Vk %>%
    as.data.frame %>%
    mutate(geneID = 1:nrow(Vk)) %>%
    ggplot(aes(x = geneID, y = V2)) +
    geom_point(pch=21) +
    geom_hline(yintercept = c(-2,0,2)*sd(Vk[,2]), col = "red"),
  ncol=2)

  • It is almost impossible to interpret the PCs because there are 4000 genes contributing to each PC.

  • In an attempt to find the most important genes (in the sense that they drive the interpretation of the PCs), the plots show horizontal reference lines: the average of the loadings, and the average ยฑ twice the standard deviation of the loadings. In between the lines we expects about 95% of the loadings (if they were normally distributed).

  • The points outside the band come from the genes that have rather large loadings (in absolute value) and hence are important for the interpretation of the PCs.

  • Note, that particularly for the first PC, only a few genes show a markedly large loadings that are negative. This means that an upregulation of these genes will lead to low scores on PC1.

  • These genes will very likely play an important role in the toxicity mechanism.

  • Indeed, low scores on PC1 are in the direction of more toxicity.

  • In the next chapter we will introduce a method to obtain sparse PCs.

1.2.4 Prediction model

m1 <- lm(BA ~ -1 + ., toxData)

m1 %>%
  coef %>%
  head(40)
#>          X1          X2          X3          X4          X5          X6 
#>  -7.4569940   0.3571348  11.2492315  10.8354021 -13.7433891   5.6833874 
#>          X7          X8          X9         X10         X11         X12 
#>  65.5387777   4.3404555   7.9103924  37.0296057 -54.8368698 -55.5547845 
#>         X13         X14         X15         X16         X17         X18 
#>   5.7924667  23.1428002  -6.9610365 -28.5250571 -22.5509025 -97.9623731 
#>         X19         X20         X21         X22         X23         X24 
#> -30.4171782 -32.6991673 -14.2808834 -16.1431266 -22.7498681  73.1635178 
#>         X25         X26         X27         X28         X29         X30 
#>  -5.7065827  37.4745379 -20.1999102  14.9906821  99.6080955          NA 
#>         X31         X32         X33         X34         X35         X36 
#>          NA          NA          NA          NA          NA          NA 
#>         X37         X38         X39         X40 
#>          NA          NA          NA          NA
m1 %>%
  coef %>%
  is.na %>%
  sum
#> [1] 3971
summary(m1)$r.squared
#> [1] 1

Problem??

1.3 Brain example

  • Courtesy to Solomon Kurz. Statistical rethinking with brms, ggplot2, and the tidyverse version 1.2.0.

https://bookdown.org/content/3890/ https://github.com/ASKurz/Statistical_Rethinking_with_brms_ggplot2_and_the_tidyverse

  • Data with brain size and body size for seven species
brain <-
tibble(species = c("afarensis", "africanus", "habilis", "boisei", "rudolfensis", "ergaster", "sapiens"),
       brain   = c(438, 452, 612, 521, 752, 871, 1350),
       mass    = c(37.0, 35.5, 34.5, 41.5, 55.5, 61.0, 53.5))

1.3.1 Data exploration

brain
p <- brain %>%
  ggplot(aes(x =  mass, y = brain, label = species)) +
  geom_point()

p + geom_text(nudge_y = 40)

1.3.2 Models

Six models range in complexity from the simple univariate model

brainiโˆผNormal(ฮผi,ฯƒ)ฮผi=ฮฒ0+ฮฒ1massi,\begin{align*} \text{brain}_i & \sim \operatorname{Normal} (\mu_i, \sigma) \\ \mu_i & = \beta_0 + \beta_1 \text{mass}_i, \end{align*}

to the dizzying sixth-degree polynomial model

brainiโˆผNormal(ฮผi,ฯƒ)ฮผi=ฮฒ0+ฮฒ1massi+ฮฒ2massi2+ฮฒ3massi3+ฮฒ4massi4+ฮฒ5massi5+ฮฒ6massi6.\begin{align*} \text{brain}_i & \sim \operatorname{Normal} (\mu_i, \sigma) \\ \mu_i & = \beta_0 + \beta_1 \text{mass}_i + \beta_2 \text{mass}_i^2 + \beta_3 \text{mass}_i^3 + \beta_4 \text{mass}_i^4 + \beta_5 \text{mass}_i^5 + \beta_6 \text{mass}_i^6. \end{align*}

formulas <- sapply(1:6, function(i)
  return(
     paste0("I(mass^",1:i,")") %>% paste(collapse=" + ")
    )
)

formulas <- sapply(
  paste0("brain ~ ", formulas),
  as.formula)

models <- lapply(formulas, lm , data = brain)
data.frame(
  formula=formulas %>%
    as.character,
  r2 = sapply(
    models,
    function(mod) summary(mod)$r.squared)
  )  %>%
  ggplot(
    aes(x = r2,
      y = formula,
      label = r2 %>%
        round(2) %>%
        as.character)
  ) +
  geom_text()

We plot the fit for each model individually and them arrange them together in one plot.

plots <- lapply(1:6, function(i)
{
  p +
  geom_smooth(method = "lm", formula = y ~ poly(x,i)) +
  ggtitle(
    paste0(
      "r2 = ",
      round(summary(models[[i]])$r.squared*100,1),
      "%")
    )
})

do.call("grid.arrange",c(plots, ncol = 3))

  • We clearly see that increasing the model complexity always produces a fit with a smaller SSE.

  • The problem of overfitting is very obvious. The more complex polynomial models will not generalise well for prediction!

  • We even have a model that fits the data perfectly, but that will make very absurd preditions!

  • Too few parameters hurts, too. Fit the underfit intercept-only model.

m0 <- lm(brain ~ 1, brain)
summary(m0)
#> 
#> Call:
#> lm(formula = brain ~ 1, data = brain)
#> 
#> Residuals:
#>     Min      1Q  Median      3Q     Max 
#> -275.71 -227.21 -101.71   97.79  636.29 
#> 
#> Coefficients:
#>             Estimate Std. Error t value Pr(>|t|)   
#> (Intercept)    713.7      121.8    5.86  0.00109 **
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 322.2 on 6 degrees of freedom
p +
  stat_smooth(method = "lm", formula = y ~ 1) +
  ggtitle(
    paste0(
      "r2 = ",
      round(summary(m0)$r.squared*100,1),
      "%")
    )

The underfit model did not learn anything about the relation between mass and brain. It would also do a very poor job for predicting new data.

1.4 Overview

We will make a distinction between continuous and discrete outcomes. In this course we focus on

  • Linear regression models for continous outcomes

    • Penalised regression: Lasso and ridge
    • Principal component regression (PCR)
  • Logistic regression models for binary outcomes

    • Penalised regression: Lasso and ridge

For all types of model, we will discuss feature selection methods.

2 Linear Regression for High Dimensional Data

Consider linear regression model (for double centered data) Yi=ฮฒ1Xi1+ฮฒ2Xi2+โ‹ฏ+ฮฒpXip+ฯตi, Y_i = \beta_1X_{i1} + \beta_2 X_{i2} + \cdots + \beta_pX_{ip} + \epsilon_i , with E[ฯตโˆฃ๐—]=0\text{E}\left[\epsilon \mid \mathbf{X}\right]=0 and var[ฯตโˆฃ๐—]=ฯƒ2\text{var}\left[\epsilon \mid \mathbf{X}\right]=\sigma^2.

In matrix notation the model becomes ๐˜=๐—๐›ƒ+๐›œ. \mathbf{Y} = \mathbf{X}\mathbf\beta + \mathbf\epsilon. The least squares estimator of ๐›ƒ\mathbf\beta is given by ๐›ƒฬ‚=(๐—T๐—)โˆ’1๐—T๐˜, \hat{\mathbf\beta} = (\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y} , and the variance of ๐›ƒฬ‚\hat{\mathbf\beta} equals var[๐›ƒฬ‚]=(๐—T๐—)โˆ’1ฯƒ2. \text{var}\left[\hat{\mathbf\beta}\right] = (\mathbf{X}^T\mathbf{X})^{-1}\sigma^2. โ†’\longrightarrow the pร—pp \times p matrix (๐—T๐—)โˆ’1(\mathbf{X}^T\mathbf{X})^{-1} is crucial

Note, that

  • with double centered data it is meant that both the responses are centered (mean of ๐˜\mathbf{Y} is zero) and that all predictors are centered (columns of ๐—\mathbf{X} have zero mean). With double centered data the intercept in a linear regression model is always exactly equal to zero and hence the intercept must not be included in the model.

  • we do not assume that the residuals are normally distributed. For prediction purposes this is often not required (normality is particularly important for statistical inference in small samples).

2.1 Linear Regression for multivariate data vs High Dimensional Data

  • ๐—๐“๐—\mathbf{X^TX} and (๐—๐“๐—)โˆ’1(\mathbf{X^TX})^{-1} are pร—pp \times p matrices

  • ๐—๐“๐—\mathbf{X^TX} can only be inverted if it has rank pp

  • Rank of a matrix of form ๐—๐“๐—\mathbf{X^TX}, with ๐—\mathbf{X} and nร—pn\times p matrix, can never be larger than min(n,p)\min(n,p).

  • in most regression problems n>pn>p and rank of (๐—๐“๐—)(\mathbf{X^TX}) equals pp

  • in high dimensional regression problems p>>>np >>> n and rank of (๐—๐“๐—)(\mathbf{X^TX}) equals n<pn<p

  • in the toxicogenomics example n=30<p=4000n=30<p=4000 and rank(๐—๐“๐—)โ‰คn=30\text{rank}(\mathbf{X^TX})\leq n=30. โ†’\longrightarrow (๐—๐“๐—)โˆ’1(\mathbf{X^TX})^{-1} does not exist, and neither does ๐›ƒฬ‚\hat{\boldsymbol{\beta}}.

2.2 Can SVD help?

  • Since the columns of ๐—\mathbf{X} are centered, ๐—๐“๐—โˆvar[๐—]\mathbf{X^TX} \propto \text{var}\left[\mathbf{X}\right].

  • if rank(๐—๐“๐—)=n=30\text{rank}(\mathbf{X^TX})=n=30, the PCA will give 30 components, each being a linear combination of p=4000p=4000 variables. These 30 PCs contain all information present in the original ๐—\mathbf{X} data.

  • if rank(๐—)=n=30\text{rank}(\mathbf{X})=n=30, the SVD of ๐—\mathbf{X} is given by ๐—=โˆ‘i=1nฮดi๐ฎi๐ฏiT=๐”๐šซ๐•T=๐™๐•T, \mathbf{X} = \sum_{i=1}^n \delta_i \mathbf{u}_i \mathbf{v}_i^T = \mathbf{U} \boldsymbol{\Delta} \mathbf{V}^T = \mathbf{ZV}^T, with ๐™\mathbf{Z} the nร—nn\times n matrix with the scores on the nn PCs.

  • Still problematic because if we use all PCs n=pn=p.

3 Principal Component Regression

A principal component regression (PCR) consists of

  1. transforming p=4000p=4000 dimensional XX-variable to the n=30n=30 dimensional ZZ-variable (PC scores). The nn PCs are mutually uncorrelated.

  2. using the nn PC-variables as regressors in a linear regression model

  3. performing feature selection to select the most important regressors (PC).

Feature selection is key, because we donโ€™t want to have as many regressors as there are observations in the data. This would result in zero residual degrees of freedom. (see later)


To keep the exposition general so that we allow for a feature selection to have taken place, I use the notation ๐”S\mathbf{U}_S to denote a matrix with left-singular column vectors ๐ฎi\mathbf{u}_i, with iโˆˆ๐’ฎi \in {\mathcal{S}} (๐’ฎ{\mathcal{S}} an index set referring to the PCs to be included in the regression model).

For example, suppose that a feature selection method has resulted in the selection of PCs 1, 3 and 12 for inclusion in the prediction model, then ๐’ฎ={1,3,12}{\mathcal{S}}=\{1,3,12\} and ๐”S=(๐ฎ1๐ฎ3๐ฎ12). \mathbf{U}_S = \begin{pmatrix} \mathbf{u}_1 & \mathbf{u}_3 & \mathbf{u}_{12} \end{pmatrix}.


3.0.1 Example model based on first 4 PCs

k <- 30
Uk <- svdX$u[,1:k]
Dk <- diag(svdX$d[1:k])
Zk <- Uk%*%Dk
Y <- toxData %>%
  pull(BA)

m4 <- lm(Y~Zk[,1:4])
summary(m4)
#> 
#> Call:
#> lm(formula = Y ~ Zk[, 1:4])
#> 
#> Residuals:
#>     Min      1Q  Median      3Q     Max 
#> -2.1438 -0.7033 -0.1222  0.7255  2.2997 
#> 
#> Coefficients:
#>               Estimate Std. Error t value Pr(>|t|)    
#> (Intercept)  7.961e-16  2.081e-01   0.000   1.0000    
#> Zk[, 1:4]1  -5.275e-01  7.725e-02  -6.828 3.72e-07 ***
#> Zk[, 1:4]2  -1.231e-02  8.262e-02  -0.149   0.8828    
#> Zk[, 1:4]3  -1.759e-01  8.384e-02  -2.098   0.0461 *  
#> Zk[, 1:4]4  -3.491e-02  8.396e-02  -0.416   0.6811    
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 1.14 on 25 degrees of freedom
#> Multiple R-squared:  0.672,  Adjusted R-squared:  0.6195 
#> F-statistic:  12.8 on 4 and 25 DF,  p-value: 8.352e-06

Note:

  • the intercept is estimated as zero. (Why?) The model could have been fitted as
m4 <- lm(Y~-1+Zk[,1:4])
  • the PC-predictors are uncorrelated (by construction)

  • first PC-predictors are not necessarily the most important predictors

  • pp-values are not very meaningful when prediction is the objective

Methods for feature selection will be discussed later.

4 Ridge Regression

4.1 Penalty

The ridge parameter estimator is defined as the parameter ๐›ƒ\mathbf\beta that minimises the penalised least squares criterion

SSEpen=โ€–๐˜โˆ’๐—๐›ƒโ€–22+ฮปโ€–๐›ƒโ€–22 \text{SSE}_\text{pen}=\Vert\mathbf{Y} - \mathbf{X\beta}\Vert_2^2 + \lambda \Vert \boldsymbol{\beta} \Vert_2^2

  • โ€–๐›ƒโ€–22=โˆ‘j=1pฮฒj2\Vert \boldsymbol{\beta} \Vert_2^2=\sum_{j=1}^p \beta_j^2 is the L2L_2 penalty term

  • ฮป>0\lambda>0 is the penalty parameter (to be chosen by the user).

Note, that that is equivalent to minimizing โ€–๐˜โˆ’๐—๐›ƒโ€–22 subject to โ€–๐›ƒโ€–22โ‰คs \Vert\mathbf{Y} - \mathbf{X\beta}\Vert_2^2 \text{ subject to } \Vert \boldsymbol{\beta}\Vert^2_2\leq s

Note, that ss has a one-to-one correspondence with ฮป\lambda

4.2 Graphical interpretation

4.3 Solution

The solution is given by ๐›ƒฬ‚=(๐—๐“๐—+ฮป๐ˆ)โˆ’1๐—๐“๐˜. \hat{\boldsymbol{\beta}} = (\mathbf{X^TX}+\lambda \mathbf{I})^{-1} \mathbf{X^T Y}. It can be shown that (๐—๐“๐—+ฮป๐ˆ)(\mathbf{X^TX}+\lambda \mathbf{I}) is always of rank pp if ฮป>0\lambda>0.

Hence, (๐—๐“๐—+ฮป๐ˆ)(\mathbf{X^TX}+\lambda \mathbf{I}) is invertible and ๐›ƒฬ‚\hat{\boldsymbol{\beta}} exists even if p>>>np>>>n.

We also find var[๐›ƒฬ‚]=(๐—๐“๐—+ฮป๐ˆ)โˆ’1๐—T๐—(๐—๐“๐—+ฮป๐ˆ)โˆ’1ฯƒ2 \text{var}\left[\hat{\mathbf\beta}\right] = (\mathbf{X^TX}+\lambda \mathbf{I})^{-1} \mathbf{X}^T\mathbf{X} (\mathbf{X^TX}+\lambda \mathbf{I})^{-1}\sigma^2

However, it can be shown that improved intervals that also account for the bias can be constructed by using:

var[๐›ƒฬ‚]=(๐—๐“๐—+ฮป๐ˆ)โˆ’1ฯƒ2. \text{var}\left[\hat{\mathbf\beta}\right] = (\mathbf{X^TX}+\lambda \mathbf{I})^{-1} \sigma^2.

4.3.1 Proof

The criterion to be minimised is SSEpen=โ€–๐˜โˆ’๐—๐›ƒโ€–22+ฮปโ€–๐›ƒโ€–22. \text{SSE}_\text{pen}=\Vert\mathbf{Y} - \mathbf{X\beta}\Vert_2^2 + \lambda \Vert \boldsymbol{\beta} \Vert_2^2. First we re-express SSE in matrix notation: SSEpen=(๐˜โˆ’๐—๐›ƒ)T(๐˜โˆ’๐—๐›ƒ)+ฮป๐›ƒT๐›ƒ. \text{SSE}_\text{pen} = (\mathbf{Y}-\mathbf{X\beta})^T(\mathbf{Y}-\mathbf{X\beta}) + \lambda \boldsymbol{\beta}^T\boldsymbol{\beta}. The partial derivative w.r.t. ๐›ƒ\boldsymbol{\beta} is โˆ‚โˆ‚๐›ƒSSEpen=โˆ’2๐—T(๐˜โˆ’๐—๐›ƒ)+2ฮป๐›ƒ. \frac{\partial}{\partial \boldsymbol{\beta}}\text{SSE}_\text{pen} = -2\mathbf{X}^T(\mathbf{Y}-\mathbf{X\beta})+2\lambda\boldsymbol{\beta}. Solving โˆ‚โˆ‚๐›ƒSSEpen=0\frac{\partial}{\partial \boldsymbol{\beta}}\text{SSE}_\text{pen}=0 gives ๐›ƒฬ‚=(๐—๐“๐—+ฮป๐ˆ)โˆ’1๐—๐“๐˜. \hat{\boldsymbol{\beta}} = (\mathbf{X^TX}+\lambda \mathbf{I})^{-1} \mathbf{X^T Y}. (assumption: (๐—๐“๐—+ฮป๐ˆ)(\mathbf{X^TX}+\lambda \mathbf{I}) is of rank pp. This is always true if ฮป>0\lambda>0)

4.5 Properties

  • The Ridge estimator is biased! The ๐›ƒ\boldsymbol{\beta} are shrunken to zero! E[๐›ƒฬ‚]=(๐—๐“๐—+ฮป๐ˆ)โˆ’1๐—TE[๐˜]=(๐—T๐—+ฮป๐ˆ)โˆ’1๐—T๐—๐›ƒ\begin{eqnarray} \text{E}[\hat{\boldsymbol{\beta}}] &=& (\mathbf{X^TX}+\lambda \mathbf{I})^{-1} \mathbf{X}^T \text{E}[\mathbf{Y}]\\ &=& (\mathbf{X}^T\mathbf{X}+\lambda \mathbf{I})^{-1} \mathbf{X}^T \mathbf{X}\boldsymbol{\beta}\\ \end{eqnarray}

  • Note, that the shrinkage is larger in the direction of the smaller eigenvalues.

E[๐›ƒฬ‚]=๐•(๐šซ2+ฮป๐ˆ)โˆ’1๐•T๐•๐šซ2๐•T๐›ƒ=๐•(๐šซ2+ฮป๐ˆ)โˆ’1๐šซ2๐•T๐›ƒ=๐•[ฮด12ฮด12+ฮปโ€ฆ0โ‹ฎ0โ€ฆฮดr2ฮดr2+ฮป]๐•T๐›ƒ\begin{eqnarray} \text{E}[\hat{\boldsymbol{\beta}}]&=&\mathbf{V} (\boldsymbol{\Delta}^2+\lambda \mathbf{I})^{-1} \mathbf{V}^T \mathbf{V} \boldsymbol{\Delta}^2 \mathbf{V}^T\boldsymbol{\beta}\\ &=&\mathbf{V} (\boldsymbol{\Delta}^2+\lambda \mathbf{I})^{-1} \boldsymbol{\Delta}^2 \mathbf{V}^T\boldsymbol{\beta}\\ &=& \mathbf{V} \left[\begin{array}{ccc} \frac{\delta_1^2}{\delta_1^2+\lambda}&\ldots&0 \\ &\vdots&\\ 0&\ldots&\frac{\delta_r^2}{\delta_r^2+\lambda} \end{array}\right] \mathbf{V}^T\boldsymbol{\beta} \end{eqnarray}

  • the variance of the prediction Yฬ‚(๐ฑ)=๐ฑTฮฒฬ‚\hat{{Y}}(\mathbf{x})=\mathbf{x}^T\hat\beta, var[Yฬ‚(๐ฑ)โˆฃ๐ฑ]=๐ฑT(๐—๐“๐—+ฮป๐ˆ)โˆ’1๐ฑ \text{var}\left[\hat{{Y}}(\mathbf{x})\mid \mathbf{x}\right] = \mathbf{x}^T(\mathbf{X^TX}+\lambda \mathbf{I})^{-1}\mathbf{x} is smaller than with the least-squares estimator.

  • through the bias-variance trade-off it is hoped that better predictions in terms of expected conditional test error can be obtained, for an appropriate choice of ฮป\lambda.

Recall the expression of the expected conditional test error Err(๐ฑ)=E[(Yฬ‚โˆ’Y*)2โˆฃ๐ฑ]=var[Yฬ‚โˆฃ๐ฑ]+bias2(๐ฑ)+var[Y*โˆฃ๐ฑ]\begin{eqnarray} Err(\mathbf{x}) &=& \text{E}\left[(\hat{Y} - Y^*)^2\mid \mathbf{x}\right]\\ &=& \text{var}\left[\hat{Y}\mid \mathbf{x}\right] + \text{bias}^2(\mathbf{x})+ \text{var}\left[Y^*\mid \mathbf{x}\right] \end{eqnarray} where

  • Yฬ‚=Yฬ‚(๐ฑ)=๐ฑT๐›ƒฬ‚\hat{Y}=\hat{Y}(\mathbf{x})=\mathbf{x}^T\hat{\boldsymbol{\beta}} is the prediction at ๐ฑ\mathbf{x}
  • Y*Y^* is an outcome at predictor ๐ฑ\mathbf{x}
  • ฮผ(๐ฑ)=E[Yฬ‚โˆฃ๐ฑ] and ฮผ*(x)=E[Y*โˆฃ๐ฑ]\mu(\mathbf{x}) = \text{E}\left[\hat{Y}\mid \mathbf{x}\right] \text{ and } \mu^*(x)=\text{E}\left[Y^*\mid \mathbf{x}\right]
  • bias(๐ฑ)=ฮผ(๐ฑ)โˆ’ฮผ*(๐ฑ)\text{bias}(\mathbf{x})=\mu(\mathbf{x})-\mu^*(\mathbf{x})
  • var[Y*โˆฃ๐ฑ]\text{var}\left[Y^*\mid \mathbf{x}\right] the irreducible error that does not depend on the model. It simply originates from observations that randomly fluctuate around the true mean ฮผ*(x)\mu^*(x).

4.6 Toxicogenomics example

library(glmnet)
mRidge <- glmnet(
  x = toxData[,-1] %>%
    as.matrix,
  y = toxData %>%
    pull(BA),
  alpha = 0) # ridge: alpha = 0

plot(mRidge, xvar="lambda")

The R function uses to refer to the penalty parameter. In this course we use ฮป\lambda, because ฮป\lambda is often used as eigenvalues.

The graph shows that with increasing penalty parameter, the parameter estimates are shrunken towards zero. The estimates will only reach zero for ฮปโ†’โˆž\lambda \rightarrow \infty. The stronger the shrinkage, the larger the bias (towards zero) and the smaller the variance of the parameter estimators (and hence also smaller variance of the predictions).

Another (informal) viewpoint is the following. By shrinking the estimates towards zero, the estimates loose some of their ``degrees of freedomโ€™โ€™ so that the parameters become estimable with only n<pn<p data points. Even with a very small ฮป>0\lambda>0, the parameters regain their estimability. However, note that the variance of the estimator is given by var[๐›ƒฬ‚]=(๐—๐“๐—+ฮป๐ˆ)โˆ’1ฯƒ2=๐•(๐šซ2+ฮป๐ˆ)โˆ’1๐•Tฯƒ2. \text{var}\left[\hat{\mathbf\beta}\right] = (\mathbf{X^TX}+\lambda \mathbf{I})^{-1} \sigma^2 = \mathbf{V}(\boldsymbol{\Delta}^2+\lambda\mathbf{I})^{-1}\mathbf{V}^T\sigma^2. Hence, a small ฮป\lambda will result in large variances of the parameter estimators. The larger ฮป\lambda, the smaller the variances become. In the limit, as ฮปโ†’โˆž\lambda\rightarrow\infty, the estimates are converged to zero and show no variability any longer.

5 Lasso Regression

  • The Lasso is another example of penalised regression.

  • The lasso estimator of ๐›ƒ\boldsymbol{\beta} is the solution to minimising the penalised SSE SSEpen=โˆ‘i=1n(Yiโˆ’๐ฑiT๐›ƒ)2+ฮปโˆ‘j=1p|ฮฒj|. \text{SSE}_\text{pen} = \sum_{i=1}^n (Y_i - \mathbf{x}_i^T\boldsymbol{\beta})^2 + \lambda \sum_{j=1}^p \vert \beta_j\vert.

or, equivalently, minimising

SSE=โ€–๐˜โˆ’๐—๐›ƒโ€–22 subject to โ€–๐›ƒโ€–1โ‰คc \text{SSE} = \Vert \mathbf{Y} - \mathbf{X\beta}\Vert_2^2 \text{ subject to } \Vert \mathbf\beta\Vert_1 \leq c with

  • โ€–๐›ƒโ€–1=โˆ‘j=1p|ฮฒj|\Vert \mathbf\beta\Vert_1 = \sum\limits_{j=1}^p \vert \beta_j \vert

  • Despite strong similarity between ridge and lasso regression (L2L_2 versus L1L_1 norm in penalty term), there is no analytical solution of the lasso parameter estimator of ๐›ƒ\mathbf\beta.

  • Fortunately, computational efficient algorithms have been implemented in statistical software

  • The Lasso estimator of ๐›ƒ\boldsymbol{\beta} is biased and generally has a smaller variance then the least-squares estimator.

  • Hence, the bias-variance trade-off may here also help in finding better predictions with biased estimators.

  • In contrast to ridge regression, however, the lasso estimator can give at most min(p,n)\min(p,n) non-zero ฮฒ\beta-estimates.

  • Hence, at first sight the lasso is not directly appropriate for high-dimensional settings.

  • An important advantage of the lasso is that choosing an appropriate value for ฮป\lambda is a kind a model building or feature selection procedure (see further).

5.1 Graphical interpretation of Lasso vs ridge

Note that the lasso is a constrained regression problem with

โ€–๐˜โˆ’๐—๐›ƒโ€–22 subject to โ€–๐›ƒโ€–1โ‰คc \Vert \mathbf{Y} - \mathbf{X\beta}\Vert_2^2 \text{ subject to } \Vert \mathbf\beta\Vert_1 \leq c and ridge โ€–๐˜โˆ’๐—๐›ƒโ€–22 subject to โ€–๐›ƒโ€–22โ‰คc \Vert \mathbf{Y} - \mathbf{X\beta}\Vert_2^2 \text{ subject to } \Vert \mathbf\beta\Vert^2_2 \leq c

Note, that

  • parameters for the lasso can never switch sign, they are set at zero! Selection!
  • ridge regression can lead to parameters that switch sign.

5.2 Toxicogenomics example

mLasso <- glmnet(
  x = toxData[,-1] %>%
    as.matrix,
  y = toxData %>%
    pull(BA),
alpha = 1)
plot(mLasso, xvar = "lambda")

  • The graph with the paths of the parameter estimates nicely illustrates the typical behaviour of the lasso estimates as a function of ฮป\lambda: when ฮป\lambda increases the estimates are shrunken towards zero.

  • When an estimate hits zero, it remains exactly equal to zero when ฮณ\gamma further increases. A parameter estimate equal to zero, say ฮฒฬ‚j=0\hat\beta_j=0, implies that the corresponding predictor xjx_j is no longer included in the model (i.e.ย ฮฒjxj=0\beta_jx_j=0).

  • The model fit is known as a sparse model fit (many zeroes). Hence, choosing a appropriate value for ฮณ\gamma is like choosing the important predictors in the model (feature selection).

6 Splines and the connection to ridge regression.

6.1 Lidar dataset

  • LIDAR (light detection and ranging) uses the reflection of laser-emitted light to detect chemical compounds in the atmosphere.

  • The LIDAR technique has proven to be an efficient tool for monitoring the distribution of several atmospheric pollutants of importance; see Sigrist (1994).

  • The range is the distance traveled before the light is reflected back to its source.

  • The logratio is the logarithm of the ratio of received light from two laser sources.

    • One source had a frequency equal to the resonance frequency of the compound of interest, which was mercury in this study.

    • The other source had a frequency off this resonance frequency.

    • The concentration of mercury can be derived from a regression model of the logratio in function of the range for each range x.

library("SemiPar")
data(lidar)
pLidar <- lidar %>%
  ggplot(aes(x = range, y = logratio)) +
  geom_point() +
  xlab("range (m)")

pLidar +
  geom_smooth()

  • The data is non-linear
  • Linear regression will not work!
  • The data shows a smooth relation between the logratio and the range

6.2 Basis expansion

yi=f(xi)+ฯตi,y_i=f(x_i)+\epsilon_i, with f(x)=โˆ‘k=1Kฮธkbk(x)f(x)=\sum\limits_{k=1}^K \theta_k b_k(x)

  • Select set of basis functions bk(x)b_k(x)

  • Select number of basis functions KK

  • Examples

    • Polynomial model: xkx^k
    • Orthogonal series: Fourier, Legendre polynomials, Wavelets
    • Polynomial splines: 1,x,(xโˆ’tm)+1, x, (x-t_m)_+ with m=1,โ€ฆ,Kโˆ’2m=1, \ldots, K-2 knots tmt_m
    • โ€ฆ

6.2.1 Trunctated line basis

yi=f(xi)+ฯตi,y_i=f(x_i)+\epsilon_i,

  • One of the most simple basis expansions
  • f(xi)=ฮฒ0+ฮฒ1xi+โˆ‘m=1Kโˆ’2ฮธm(xiโˆ’tm)+f(x_i)=\beta_0+\beta_1x_i+\sum\limits_{m=1}^{K-2}\theta_m(x_i-t_m)_+ with (.)+(.)_+ the operator that takes the positive part.
  • Note, that better basis expansions exist, which are orthogonal, computational more stable and/or continuous derivative beyond first order
  • We will use this basis for didactical purposes
  • We can use OLS to fit y w.r.t. the basis.
knots <- seq(400,700,12.5)

basis <- sapply(knots,
  function(k,y) (y-k)*(y>k),
  y= lidar %>% pull(range)
  )

basisExp <- cbind(1, range = lidar %>% pull(range), basis)

splineFitLs <- lm(logratio ~ -1 + basisExp, lidar)

pBasis <- basisExp[,-1] %>%
  data.frame %>%
  gather("basis","values",-1) %>%
  ggplot(aes(x = range, y = values, color = basis)) +
  geom_line() +
  theme(legend.position="none") +
  ylab("basis")

grid.arrange(
  pLidar +
    geom_line(aes(x = lidar$range, y = splineFitLs$fitted), lwd = 2),
  pBasis,
  ncol=1)

  • Note, that the model is overfitting!
  • The fit is very wiggly and is tuned too much to the data.
  • The fit has a large variance and low bias.
  • It will therefore not generalise well to predict the logratio of future observations.

6.2.1.1 Solution for overfitting?

  • We could perform model selection on the basis to select the important basis functions to model the signal. But, this will have the undesired property that the fit will no longer be smooth.

  • We can also adopt a ridge penalty!

  • However, we do not want to penalise the intercept and the linear term.

  • Ridge criterion

โ€–๐˜โˆ’๐—๐›ƒโ€–2+ฮป๐›ƒT๐ƒ๐›ƒ\Vert\mathbf{Y}-\mathbf{X\beta}\Vert^2+\lambda\boldsymbol{\beta}^T\mathbf{D}\boldsymbol{\beta}

With ๐ƒ\mathbf{D} with dimensions (K,K): ๐ƒ=[๐ŸŽ2ร—2๐ŸŽ2ร—Kโˆ’2๐ŸŽKโˆ’2ร—2๐ˆKโˆ’2ร—Kโˆ’2]\mathbf{D}=\left[\begin{array}{cc}\mathbf{0}_{2\times2}& \mathbf{0}_{2\times K-2}\\ \mathbf{0}_{K-2\times2}&\mathbf{I}_{K-2\times K-2}\end{array}\right]

  • Here we will set the penalty at 900.
D <- diag(ncol(basisExp))
D[1:2,1:2] <- 0
lambda <- 900
betaRidge <- solve(t(basisExp)%*%basisExp+(lambda*D))%*%t(basisExp)%*%lidar$logratio
grid.arrange(
  pLidar +
    geom_line(aes(x = lidar$range, y = c(basisExp %*% betaRidge)), lwd = 2),
  pBasis,
  ncol=1)

How do we choose ฮป\lambda?


7 Evaluation of Prediction Models

Predictions are calculated with the fitted model Yฬ‚(๐ฑ)=mฬ‚(๐ฑ)=๐ฑTฮฒฬ‚ \hat{Y}(\mathbf{x}) = \hat{m}(\mathbf{x})=\mathbf{x}^T\hat{\beta} when focussing on prediction, we want the prediction error to be as small as possible.

The prediction error for a prediction at covariate pattern ๐ฑ\mathbf{x} is given by Yฬ‚(๐ฑ)โˆ’Y*, \hat{Y}(\mathbf{x}) - Y^*, where

  • Yฬ‚(๐ฑ)=๐ฑT๐›ƒฬ‚\hat{Y}(\mathbf{x})=\mathbf{x}^T\hat{\boldsymbol{\beta}} is the prediction at ๐ฑ\mathbf{x}

  • Y*Y^* is an outcome at covariate pattern ๐ฑ\mathbf{x}

Prediction is typically used to predict an outcome before it is observed.

  • Hence, the outcome Y*Y^* is not observed yet, and
  • the prediction error cannot be computed.

  • Recall that the prediction model Yฬ‚(๐ฑ)\hat{Y}(\mathbf{x}) is estimated by using data in the training data set (๐—,๐˜)(\mathbf{X},\mathbf{Y}), and

  • that the outcome Y*Y^* is an outcome at ๐ฑ\mathbf{x} which is assumed to be independent of the training data.

  • Goal is to use prediction model for predicting a future observation (Y*Y^*), i.e.ย an observation that still has to be realised/observed (otherwise prediction seems rather useless).

  • Hence, Y*Y^* can never be part of the training data set.


Here we provide definitions and we show how the prediction performance of a prediction model can be evaluated from data.

Let ๐’ฏ=(๐˜,๐—){\mathcal{T}}=(\mathbf{Y},\mathbf{X}) denote the training data, from which the prediction model Yฬ‚(โ‹…)\hat{Y}(\cdot) is build. This building process typically involves feature selection and parameter estimation.

We will use a more general notation for the prediction model: mฬ‚(๐ฑ)=Yฬ‚(๐ฑ)\hat{m}(\mathbf{x})=\hat{Y}(\mathbf{x}).


7.1 Test or Generalisation Error

The test or generalisation error for prediction model mฬ‚(โ‹…)\hat{m}(\cdot) is given by Err๐’ฏ=EY*,X*[(mฬ‚(๐—*)โˆ’Y*)2โˆฃ๐’ฏ] \text{Err}_{\mathcal{T}} = \text{E}_{Y^*,X^*}\left[(\hat{m}(\mathbf{X}^*) - Y^*)^2\mid {\mathcal{T}}\right] where (Y*,X*)(Y^*,X^*) is independent of the training data.


  • Note that the test error is conditional on the training data ๐’ฏ{\mathcal{T}}.
  • Hence, the test error evaluates the performance of the single model build from the observed training data.
  • This is the ultimate target of the model assessment, because it is exactly this prediction model that will be used in practice and applied to future predictors ๐—*\mathbf{X}^* to predict Y*Y^*.
  • The test error is defined as an average over all such future observations (Y*,๐—*)(Y^*,\mathbf{X}^*).

7.2 Conditional test error

Sometimes the conditional test error is used:

The conditional test error in ๐ฑ\mathbf{x} for prediction model mฬ‚(๐ฑ)\hat{m}(\mathbf{x}) is given by Err๐’ฏ(๐ฑ)=EY*[(mฬ‚(๐ฑ)โˆ’Y*)2โˆฃ๐’ฏ,๐ฑ] \text{Err}_{\mathcal{T}}(\mathbf{x}) = \text{E}_{Y^*}\left[(\hat{m}(\mathbf{x}) - Y^*)^2\mid {\mathcal{T}}, \mathbf{x}\right] where Y*Y^* is an outcome at predictor ๐ฑ\mathbf{x}, independent of the training data.

Hence, Err๐’ฏ=EX*[Err๐’ฏ(๐—*)]. \text{Err}_{\mathcal{T}} = \text{E}_{X^*}\left[\text{Err}_{\mathcal{T}}(\mathbf{X}^*)\right].

A closely related error is the insample error.


7.3 Insample Error

The insample error for prediction model mฬ‚(๐ฑ)\hat{m}(\mathbf{x}) is given by Errin๐’ฏ=1nโˆ‘i=1nErr๐’ฏ(๐ฑi), \text{Err}_{\text{in} \mathcal{T}} = \frac{1}{n}\sum_{i=1}^n \text{Err}_{\mathcal{T}}(\mathbf{x}_i),

i.e.ย the insample error is the sample average of the conditional test errors evaluated in the nn training dataset predictors ๐ฑi\mathbf{x}_i.

Since Err๐’ฏ\text{Err}_{\mathcal{T}} is an average over all ๐—\mathbf{X}, even over those predictors not observed in the training dataset, it is sometimes referred to as the outsample error.


7.4 Estimation of the insample error

We start with introducing the training error rate, which is closely related to the MSE in linear models.

7.4.1 Training error

The training error is given by errยฏ=1nโˆ‘i=1n(Yiโˆ’mฬ‚(๐ฑi))2, \overline{\text{err}} = \frac{1}{n}\sum_{i=1}^n (Y_i - \hat{m}(\mathbf{x}_i))^2 , where the (Yi,๐ฑi)(Y_i,\mathbf{x}_i) from the training dataset which is also used for the calculation of mฬ‚\hat{m}.

  • The training error is an overly optimistic estimate of the test error Err๐’ฏ\text{Err}_{\mathcal{T}}.

  • The training error will never increases when the model becomes more complex. โ†’\longrightarrow cannot be used directly as a model selection criterion.

Indeed, model parameters are often estimated by minimising the training error (cfr. SSE).

  • Hence the fitted model adapts to the training data, and
  • training error will be an overly optimistic estimate of the test error Err๐’ฏ\text{Err}_{\mathcal{T}}.

It can be shown that the training error is related to the insample test error via

E๐˜[Errin๐’ฏ]=E๐˜[errยฏ]+2nโˆ‘i=1ncov๐˜[mฬ‚(๐ฑi),Yi], \text{E}_\mathbf{Y} \left[\text{Err}_{\text{in}{\mathcal{T}}}\right] = \text{E}_\mathbf{Y}\left[\overline{\text{err}}\right] + \frac{2}{n}\sum_{i=1}^n \text{cov}_\mathbf{Y}\left[\hat{m}(\mathbf{x}_i),Y_i\right],

Note, that for linear models mฬ‚(๐ฑi)=๐—๐›ƒฬ‚=๐—(๐—T๐—)โˆ’1๐—T๐˜=๐‡๐˜ \hat{m}(\mathbf{x}_i) = \mathbf{X}\hat{\boldsymbol{\beta}}= \mathbf{X}(\mathbf{X}^T\mathbf{X})^{-1}\mathbf{X}^T\mathbf{Y} = \mathbf{HY} with

  • ๐‡\mathbf{H} the hat matrix and
  • all YiY_i are assumed to be independently distributed N(๐—๐›ƒ,ฯƒ2)N(\mathbf{X}\boldsymbol{\beta},\sigma^2)

Hence, for linear models with independent observations

cov๐˜[mฬ‚(๐ฑi),Yi)]=cov๐˜[๐‡iT๐˜,Yi)]=cov๐˜[hiiYi,Yi]=hiicov๐˜[Yi,Yi]=hiiฯƒ2\begin{eqnarray} \text{cov}_\mathbf{Y}\left[\hat{m}(\mathbf{x}_i),Y_i)\right] &=& \text{cov}_\mathbf{Y}\left[\mathbf{H}_{i}^T\mathbf{Y},Y_i)\right]\\ &=& \text{cov}_\mathbf{Y}\left[h_{ii} Y_i,Y_i\right]\\ &=& h_{ii} \text{cov}_\mathbf{Y}\left[Y_i,Y_i\right]\\ &=& h_{ii} \sigma^2\\ \end{eqnarray}

And we can thus estimate the insample error by Mallowโ€™s CpC_p

Cp=errยฏ+2ฯƒ2ntr(๐‡)=errยฏ+2ฯƒ2pn\begin{eqnarray} C_p &=& \overline{\text{err}} + \frac{2\sigma^2}{n}\text{tr}(\mathbf{H})\\ &=& \overline{\text{err}} + \frac{2\sigma^2p}{n} \end{eqnarray}

with pp the number of predictors.

  • Mallowโ€™s CpC_p is often used for model selection.
  • Note, that we can also consider it as a kind of penalized least squares:

nร—Cp=โ€–๐˜โˆ’๐—๐›ƒโ€–22+2ฯƒ2โ€–๐›ƒโ€–0 n \times C_p = \Vert \mathbf{Y} - \mathbf{X}\boldsymbol{\beta}\Vert_2^2 + 2\sigma^2 \Vert \boldsymbol{\beta} \Vert_0 with L0L_0 norm โ€–๐›ƒโ€–0=โˆ‘j=1pฮฒp0=p\Vert \boldsymbol{\beta} \Vert_0 = \sum_{j=1}^p \beta_p^0 = p.


7.5 Expected test error

The test or generalisation error was defined conditionally on the training data. By averaging over the distribution of training datasets, the expected test error arises.

E๐’ฏ[Err๐’ฏ]=E๐’ฏ[EY*,X*[(mฬ‚(๐—*)โˆ’Y*)2โˆฃ๐’ฏ]]=EY*,X*,๐’ฏ[(mฬ‚(๐—*)โˆ’Y*)2].\begin{eqnarray*} \text{E}_{\mathcal{T}}\left[\text{Err}_{{\mathcal{T}}}\right] &=& \text{E}_{\mathcal{T}}\left[\text{E}_{Y^*,X^*}\left[(\hat{m}(\mathbf{X}^*) - Y^*)^2\mid {\mathcal{T}}\right]\right] \\ &=& \text{E}_{Y^*,X^*,{\mathcal{T}}}\left[(\hat{m}(\mathbf{X}^*) - Y^*)^2\right]. \end{eqnarray*}

  • The expected test error may not be of direct interest when the goal is to assess the prediction performance of a single prediction model mฬ‚(โ‹…)\hat{m}(\cdot).

  • The expected test error averages the test errors of all models that can be build from all training datasets, and hence this may be less relevant when the interest is in evaluating one particular model that resulted from a single observed training dataset.

  • Also note that building a prediction model involves both parameter estimation and feature selection.

  • Hence the expected test error also evaluates the feature selection procedure (on average).

  • If the expected test error is small, it is an indication that the model building process gives good predictions for future observations (Y*,๐—*)(Y^*,\mathbf{X}^*) on average.

7.5.1 Estimating the Expected test error

The expected test error may be estimated by cross validation (CV).

7.5.1.1 Leave one out cross validation (LOOCV)}

The LOOCV estimator of the expected test error (or expected outsample error) is given by CV=1nโˆ‘i=1n(Yiโˆ’mฬ‚โˆ’i(๐ฑi))2, \text{CV} = \frac{1}{n} \sum_{i=1}^n \left(Y_i - \hat{m}^{-i}(\mathbf{x}_i)\right)^2 , where

  • the (Yi,๐ฑi)(Y_i,\mathbf{x}_i) form the training dataset
  • mฬ‚โˆ’i\hat{m}^{-i} is the fitted model based on all training data, except observation ii
  • mฬ‚โˆ’i(๐ฑi)\hat{m}^{-i}(\mathbf{x}_i) is the prediction at ๐ฑi\mathbf{x}_i, which is the observation left out the training data before building model mm.

Some rationale as to why LOOCV offers a good estimator of the outsample error:

  • the prediction error Y*โˆ’mฬ‚(๐ฑ)Y^*-\hat{m}(\mathbf{x}) is mimicked by not using one of the training outcomes YiY_i for the estimation of the model so that this YiY_i plays the role of Y*Y^*, and, consequently, the fitted model mฬ‚โˆ’i\hat{m}^{-i} is independent of YiY_i

  • the sum in CVCV is over all ๐ฑi\mathbf{x}_i in the training dataset, but each term ๐ฑi\mathbf{x}_i was left out once for the calculation of mฬ‚โˆ’i\hat{m}^{-i}. Hence, mฬ‚โˆ’i(๐ฑi)\hat{m}^{-i}(\mathbf{x}_i) mimics an outsample prediction.

  • the sum in CV is over nn different training datasets (each one with a different observation removed), and hence CV is an estimator of the expected test error.

  • For linear models the LOOCV can be readily obtained from the fitted model: i.e.

CV=1nโˆ‘i=1nei2(1โˆ’hii)2\text{CV} = \frac{1}{n}\sum\limits_{i=1}^n \frac{e_i^2}{(1-h_{ii})^2}

with eie_i the residuals from the model that is fitted based on all training data.


An alternative to LOOCV is the kk-fold cross validation procedure. It also gives an estimate of the expected outsample error.

7.5.1.2 kk-fold cross validation

  • Randomly divide the training dataset into kk approximately equal subsets . Let SjS_j denote the index set of the jjth subset (referred to as a fold). Let njn_j denote the number of observations in fold jj.

  • The kk-fold cross validation estimator of the expected outsample error is given by CVk=1kโˆ‘j=1k1njโˆ‘iโˆˆSj(Yiโˆ’mฬ‚โˆ’Sj(๐ฑi))2 \text{CV}_k = \frac{1}{k}\sum_{j=1}^k \frac{1}{n_j} \sum_{i\in S_j} \left(Y_i - \hat{m}^{-S_j}(\mathbf{x}_i)\right)^2 where mฬ‚โˆ’Sj\hat{m}^{-S_j} is the model fitted using all training data, except observations in fold jj (i.e.ย observations iโˆˆSji \in S_j).


The cross validation estimators of the expected outsample error are nearly unbiased. One argument that helps to understand where the bias comes from is the fact that e.g.ย in de LOOCV estimator the model is fit on only nโˆ’1n-1 observations, whereas we are aiming at estimating the outsample error of a model fit on all nn training observations. Fortunately, the bias is often small and is in practice hardly a concern.

kk-fold CV is computationally more complex.

Since CV and CVk_k are estimators, they also show sampling variability. Standard errors of the CV or CVk_k can be computed. We donโ€™t show the details, but in the example this is illustrated.

7.5.2 Bias Variance trade-off

For the expected conditional test error in ๐ฑ\mathbf{x}, it holds that E๐’ฏ[Err๐’ฏ(๐ฑ)]=EY*,๐’ฏ[(mฬ‚(๐ฑ)โˆ’Y*)2โˆฃ๐ฑ]=var๐˜[Yฬ‚(๐ฑ)โˆฃ๐ฑ]+(ฮผ(๐ฑ)โˆ’ฮผ*(๐ฑ))2+varY*[Y*โˆฃ๐ฑ]\begin{eqnarray*} \text{E}_{\mathcal{T}}\left[\text{Err}_{\mathcal{T}}(\mathbf{x})\right] &=& \text{E}_{Y^*,{\mathcal{T}}}\left[(\hat{m}(\mathbf{x})-Y^*)^2 \mid \mathbf{x}\right] \\ &=& \text{var}_{\mathbf{Y}}\left[\hat{Y}(\mathbf{x})\mid \mathbf{x}\right] +(\mu(\mathbf{x})-\mu^*(\mathbf{x}))^2+\text{var}_{Y^*}\left[Y^*\mid \mathbf{x}\right] \end{eqnarray*} where ฮผ(๐ฑ)=E๐˜[Yฬ‚(๐ฑ)โˆฃ๐ฑ] and ฮผ*(๐ฑ)=EY*[Y*โˆฃ๐ฑ]\mu(\mathbf{x}) = \text{E}_{\mathbf{Y}}\left[\hat{Y}(\mathbf{x})\mid \mathbf{x}\right] \text{ and } \mu^*(\mathbf{x})=\text{E}_{Y^*}\left[Y^*\mid \mathbf{x}\right].

  • bias: bias(๐ฑ)=ฮผ(๐ฑ)โˆ’ฮผ*(๐ฑ)\text{bias}(\mathbf{x})=\mu(\mathbf{x})-\mu^*(\mathbf{x})

  • varY*[Y*โˆฃ๐ฑ]\text{var}_{Y^*}\left[Y^*\mid \mathbf{x}\right] does not depend on the model, and is referred to as the irreducible variance.


The importance of the bias-variance trade-off can be seen from a model selection perspective. When we agree that a good model is a model that has a small expected conditional test error at some point ๐ฑ\mathbf{x}, then the bias-variance trade-off shows us that a model may be biased as long as it has a small variance to compensate for the bias. It often happens that a biased model has a substantial smaller variance. When these two are combined, a small expected test error may occur.

Also note that the model mm which forms the basis of the prediction model mฬ‚(๐ฑ)\hat{m}(\mathbf{x}) does NOT need to satisfy m(๐ฑ)=ฮผ(๐ฑ)m(\mathbf{x})=\mu(\mathbf{x}) or m(๐ฑ)=ฮผ*(๐ฑ)m(\mathbf{x})=\mu^*(\mathbf{x}). The model mm is known by the data-analyst (its the basis of the prediction model), whereas ฮผ(๐ฑ)\mu(\mathbf{x}) and ฮผ*(๐ฑ)\mu^*(\mathbf{x}) are generally unknown to the data-analyst. We only hope that mm serves well as a prediction model.


7.5.3 In practice

We use cross validation to estimate the lambda penalty for penalised regression:

  • Ridge Regression
  • Lasso
  • Build models, e.g.ย select the number of PCs for PCA regression
  • Splines

7.5.4 Toxicogenomics example

7.5.4.1 Lasso

set.seed(15)
library(glmnet)
mCvLasso <- cv.glmnet(
  x = toxData[,-1] %>%
    as.matrix,
  y = toxData %>%
    pull(BA),
  alpha = 1)  # lasso alpha=1

plot(mCvLasso)

Default CV procedure in is k=10k=10-fold CV.

The Graphs shows

  • 10-fold CV estimates of the extra-sample error as a function of the lasso penalty parameter ฮป\lambda.
  • estimate plus and minus once the estimated standard error of the CV estimate (grey bars)
  • On top the number of non-zero regression parameter estimates are shown.

Two vertical reference lines are added to the graph. They correspond to

  • the log(ฮป)\log(\lambda) that gives the smallest CV estimate of the extra-sample error, and
  • the largest log(ฮป)\log(\lambda) that gives a CV estimate of the extra-sample error that is within one standard error from the smallest error estimate.
  • The latter choice of ฮป\lambda has no firm theoretical basis, except that it somehow accounts for the imprecision of the error estimate. One could loosely say that this ฮณ\gamma corresponds to the smallest model (i.e.ย least number of predictors) that gives an error that is within margin of error of the error of the best model.

mLassoOpt <- glmnet(
  x = toxData[,-1] %>%
    as.matrix,
  y = toxData %>%
    pull(BA),
    alpha = 1,
    lambda = mCvLasso$lambda.min)

summary(coef(mLassoOpt))

With the optimal ฮป\lambda (smallest error estimate) the output shows the 9 non-zero estimated regression coefficients (sparse solution).


mLasso1se <- glmnet(
  x = toxData[,-1] %>%
    as.matrix,
    y= toxData %>%
      pull(BA),
    alpha = 1,
    lambda = mCvLasso$lambda.1se)

mLasso1se %>%
  coef %>%
  summary

This shows the solution for the largest ฮป\lambda within one standard error of the optimal model. Now only 3 non-zero estimates result.


7.5.4.2 Ridge

mCvRidge <- cv.glmnet(
  x = toxData[,-1] %>%
    as.matrix,
    y = toxData %>%
      pull(BA),
      alpha = 0)  # ridge alpha=0

plot(mCvRidge)

  • Ridge does not seem to have optimal solution.
  • 10-fold CV is also larger than for lasso.

7.5.4.3 PCA regression

set.seed(1264)
library(DAAG)

tox <- data.frame(
  Y = toxData %>%
    pull(BA),
  PC = Zk)

PC.seq <- 1:25
Err <- numeric(25)

mCvPca <- cv.lm(
  Y~PC.1,
  data = tox,
  m = 5,
  printit = FALSE)

Err[1]<-attr(mCvPca,"ms")

for(i in 2:25) {
  mCvPca <- cv.lm(
    as.formula(
      paste("Y ~ PC.1 + ",
        paste("PC.", 2:i, collapse = "+", sep=""),
        sep=""
      )
    ),
    data = tox,
    m = 5,
    printit = FALSE)
  Err[i]<-attr(mCvPca,"ms")
}
  • Here we illustrate principal component regression.

  • The most important PCs are selected in a forward model selection procedure.

  • Within the model selection procedure the models are evaluated with 5-fold CV estimates of the outsample error.

  • It is important to realise that a forward model selection procedure will not necessarily result in the best prediction model, particularly because the order of the PCs is generally not related to the importance of the PCs for predicting the outcome.

  • A supervised PC would be better.

pPCreg <- data.frame(PC.seq, Err) %>%
  ggplot(aes(x = PC.seq, y = Err)) +
  geom_line() +
  geom_point() +
  geom_hline(
    yintercept = c(
      mCvLasso$cvm[mCvLasso$lambda==mCvLasso$lambda.min],
      mCvLasso$cvm[mCvLasso$lambda==mCvLasso$lambda.1se]),
    col = "red") +
  xlim(1,26)

grid.arrange(
  pPCreg,
  pPCreg + ylim(0,5),
  ncol=2)

  • The graph shows the CV estimate of the outsample error as a function of the number of sparse PCs included in the model.

  • A very small error is obtained with the model with only the first PC. The best model with 3 PCs.

  • The two vertical reference lines correspond to the error estimates obtained with lasso (optimal ฮป\lambda and largest ฮป\lambda within one standard error).

  • Thus although there was a priori no guarantee that the first PCs are the most predictive, it seems to be the case here (we were lucky!).

  • Moreover, the first PC resulted in a small outsample error.

  • Note that the graph does not indicate the variability of the error estimates (no error bars).

  • Also note that the graph clearly illustrates the effect of overfitting: including too many PCs causes a large outsample error.

7.5.5 Lidar Example: splines

  • We use the mgcv package to fit the spline model to the lidar data.
  • A better basis is used than the truncated spline basis
  • Thin plate splines are also linear smoothers, i.e. Yฬ‚=mฬ‚(๐—)=๐’๐˜\hat{Y} = \hat{m}(\mathbf{X}) = \mathbf{SY}
  • So their variance can be easily calculated.
  • The ridge/smoothness penalty is chosen by generalized cross validation.
library(mgcv)
gamfit <- gam(logratio ~ s(range), data = lidar)
gamfit$sp
#>    s(range) 
#> 0.006114634
pLidar +
  geom_line(aes(x = lidar$range, y = gamfit$fitted), lwd = 2)

7.6 More general error definitions

So far we only looked at continuous outcomes YY and errors defined in terms of the squared loss (mฬ‚(๐ฑ)โˆ’Y*)2(\hat{m}(\mathbf{x})-Y^*)^2.

More generally, a loss function measures an discrepancy between the prediction mฬ‚(๐ฑ)\hat{m}(\mathbf{x}) and an independent outcome Y*Y^* that corresponds to ๐ฑ\mathbf{x}.

Some examples for continuous YY: L(Y*,mฬ‚(๐ฑ))=(mฬ‚(๐ฑ)โˆ’Y*)2(squared error)L(Y*,mฬ‚(๐ฑ))=|mฬ‚(๐ฑ)โˆ’Y*|(absolute error)L(Y*,mฬ‚(๐ฑ))=2โˆซ๐’ดfy(y)logfy(y)fmฬ‚(y)dy(deviance).\begin{eqnarray*} L(Y^*,\hat{m}(\mathbf{x})) &=& (\hat{m}(\mathbf{x})-Y^*)^2 \;\;\text{(squared error)} \\ L(Y^*,\hat{m}(\mathbf{x})) &=& \vert\hat{m}(\mathbf{x})-Y^*\vert \;\;\text{(absolute error)} \\ L(Y^*,\hat{m}(\mathbf{x})) &=& 2 \int_{\mathcal{Y}} f_y(y) \log\frac{f_y(y)}{f_{\hat{m}}(y)} dy \;\;\text{(deviance)}. \end{eqnarray*}

In the expression of the deviance

  • fyf_y denotes the density function of a distribution with mean set to yy (cfr. perfect fit), and
  • fmฬ‚f_{\hat{m}} is the density function of the same distribution but with mean set to the predicted outcome mฬ‚(๐ฑ)\hat{m}(\mathbf{x}).

With a given loss function, the errors are defined as follows: - Test or generalisation or outsample error Err๐’ฏ=EY*,X*[L(Y*,mฬ‚(๐—*))] \text{Err}_{\mathcal{T}} = \text{E}_{Y^*,X^*}\left[L(Y^*,\hat{m}(\mathbf{X}^*))\right]

  • Training error errยฏ=1nโˆ‘i=1nL(Yi,mฬ‚(๐ฑi)) \overline{\text{err}} = \frac{1}{n}\sum_{i=1}^n L(Y_i,\hat{m}(\mathbf{x}_i))

  • โ€ฆ\ldots


When an exponential family distribution is assumed for the outcome distribution, and when the deviance loss is used, the insample error can be estimated by means of the AIC and BIC.

7.6.1 Akaikeโ€™s Information Criterion (AIC)

The AIC for a model mm is given by AIC=โˆ’2lnLฬ‚(m)+2p \text{AIC} = -2 \ln \hat{L}(m) +2p where Lฬ‚(m)\hat{L}(m) is the maximised likelihood for model mm.

When assuming normally distributed error terms and homoscedasticity, the AIC becomes AIC=nlnSSE(m)+2p=nln(nerrยฏ(m))+2p \text{AIC} = n\ln \text{SSE}(m) +2p = n\ln(n\overline{\text{err}}(m)) + 2p with SSE(m)\text{SSE}(m) the residual sum of squares of model mm.

In linear models with normal error terms, Mallowโ€™s CpC_p criterion (statistic) is a linearised version of AIC and it is an unbiased estimator of the in-sample error.


7.6.2 Bayesian Information Criterion (BIC)}

The BIC for a model mm is given by BIC=โˆ’2lnLฬ‚(m)+pln(n) \text{BIC} = -2 \ln \hat{L}(m) +p\ln(n) where Lฬ‚(m)\hat{L}(m) is the maximised likelihood for model mm.

When assuming normally distributed error terms and homoscedasticity, the BIC becomes BIC=nlnSSE(m)+pln(n)=nln(nerrยฏ(m))+pln(n) \text{BIC} = n\ln \text{SSE}(m) +p\ln(n) = n\ln(n\overline{\text{err}}(m)) + p\ln(n) with SSE(m)\text{SSE}(m) the residual sum of squares of model mm.

When large datasets are used, the BIC will favour smaller models than the AIC.


7.7 Training and test sets

Sometimes, when a large (training) dataset is available, one may decide the split the dataset randomly in a

  • training dataset: data are used for model fitting and for model building or feature selection (this may require e.g.ย cross validation)

  • test dataset: this data are used to evaluate the final model (result of model building). An unbiased estimate of the outsample error (i.e.ย test or generalisation error) based on this test data is 1mโˆ‘i=1m(mฬ‚(๐ฑi)โˆ’Yi)2, \frac{1}{m} \sum_{i=1}^m \left(\hat{m}(\mathbf{x}_i)-Y_i\right)^2, where

    • (Y1,๐ฑ1),โ€ฆ,(Ym,๐ฑm)(Y_1,\mathbf{x}_1), \ldots, (Y_m,\mathbf{x}_m) denote the mm observations in the test dataset

    • mฬ‚\hat{m} is estimated from using the training data (this may also be the result from model building, using only the training data).


Note that the training dataset is used for model building or feature selection. This also requires the evaluation of models. For these evaluations the methods from the previous slides can be used (e.g.ย cross validation, kk-fold CV, Mallowโ€™s CpC_p). The test dataset is only used for the evaluation of the final model (estimated and build from using only the training data). The estimate of the outsample error based on the test dataset is the best possible estimate in the sense that it is unbiased. The observations used for this estimation are independent of the observations in the training data. However, if the number of data points in the test dataset (mm) is small, the estimate of the outsample error may show large variance and hence is not reliable.

8 Logistic Regression Analysis for High Dimensional Data

8.1 Breast Cancer Example

  • Schmidt et al., 2008, Cancer Research, 68, 5405-5413

  • Gene expression patterns in n=200n=200 breast tumors were investigated (p=22283p=22283 genes)

  • After surgery the tumors were graded by a pathologist (stage 1,2,3)

  • Here the objective is to predict stage 3 from the gene expression data (prediction of binary outcome)

  • If the prediction model works well, it can be used to predict the stage from a biopsy sample.

8.2 Data

#BiocManager::install("genefu")
#BiocManager::install("breastCancerMAINZ")

library(genefu)
library(breastCancerMAINZ)
data(mainz)

X <- t(exprs(mainz)) # gene expressions
n <- nrow(X)
H <- diag(n)-1/n*matrix(1,ncol=n,nrow=n)
X <- H%*%X
Y <- ifelse(pData(mainz)$grade==3,1,0)
table(Y)
#> Y
#>   0   1 
#> 165  35
svdX <- svd(X)
k <- 2
Zk <- svdX$u[,1:k] %*% diag(svdX$d[1:k])
colnames(Zk) <- paste0("Z",1:k)

Zk %>%
  as.data.frame %>%
  mutate(grade = Y %>% as.factor) %>%
  ggplot(aes(x= Z1, y = Z2, color = grade)) +
  geom_point(size = 3)


8.3 Logistic regression models

Binary outcomes are often analysed with logistic regression models.

Let YY denote the binary (1/0, case/control, positive/negative) outcome, and ๐ฑ\mathbf{x} the pp-dimensional predictor.

Logistic regression assumes Yโˆฃ๐ฑโˆผBernoulli(ฯ€(๐ฑ)) Y \mid \mathbf{x} \sim \text{Bernoulli}(\pi(\mathbf{x})) with ฯ€(๐ฑ)=P[Y=1โˆฃ๐ฑ]\pi(\mathbf{x}) = \text{P}\left[Y=1\mid \mathbf{x}\right] and lnฯ€(๐ฑ)1โˆ’ฯ€(๐ฑ)=ฮฒ0+๐›ƒT๐ฑ. \ln \frac{\pi(\mathbf{x})}{1-\pi(\mathbf{x})}=\beta_0 + \boldsymbol{\beta}^T\mathbf{x}.

The parameters are typically estimated by maximising the log-likelihood, which is denoted by l(๐›ƒ)l(\mathbf{ \beta}), i.e. ๐›ƒฬ‚=ArgMaxฮฒl(๐›ƒ). \hat{\boldsymbol{\beta}} = \text{ArgMax}_\beta l(\boldsymbol{\beta}).

  • Maximum likelihood is only applicable when n>pn>p.

  • When p>np>n penalised maximum likelihood methods are applicable.


8.4 Penalized maximum likelihood

Penalised estimation methods (e.g.ย lasso and ridge) can als be applied to maximum likelihood, resulting in the penalised maximum likelihood estimator.

Lasso: ๐›ƒฬ‚=ArgMaxฮฒl(๐›ƒ)โˆ’ฮปโ€–๐›ƒโ€–1. \hat{\boldsymbol{\beta}} = \text{ArgMax}_\beta l(\boldsymbol{\beta}) -\lambda \Vert \boldsymbol{\beta}\Vert_1.

Ridge: ๐›ƒฬ‚=ArgMaxฮฒl(๐›ƒ)โˆ’ฮปโ€–๐›ƒโ€–22. \hat{\boldsymbol{\beta}} = \text{ArgMax}_\beta l(\boldsymbol{\beta}) -\lambda \Vert \boldsymbol{\beta}\Vert_2^2.

Once the parameters are estimated, the model may be used to compute ฯ€ฬ‚(๐ฑ)=Pฬ‚[Y=1โˆฃ๐ฑ]. \hat{\pi}(\mathbf{x}) = \hat{\text{P}}\left[Y=1\mid \mathbf{x}\right]. With these estimated probabilities the prediction rule becomes ฯ€ฬ‚(๐ฑ)โ‰คcpredict Y=0ฯ€ฬ‚(๐ฑ)>cpredict Y=1\begin{eqnarray*} \hat{\pi}(\mathbf{x}) &\leq c& \text{predict } Y=0 \\ \hat{\pi}(\mathbf{x}) &>c & \text{predict } Y=1 \end{eqnarray*} with 0<c<10<c<1 a threshold that either is fixed (e.g.ย c=1/2c=1/2), depends on prior probabilities, or is empirically determined by optimising e.g.ย the Area Under the ROC Curve (AUC) or by finding a good compromise between sensitivity and specificity.

Note that logistic regression directly models the Posterior probability that an observation belongs to class Y=1Y=1, given the predictor ๐ฑ\mathbf{x}.

8.5 Model evaluation

Common model evaluation criteria for binary prediction models are:

  • sensitivity = true positive rate (TPR)

  • specificity = true negative rate (TNR)

  • misclassification error

  • area under the ROC curve (AUC)

These criteria can again be estimated via cross validation or via splitting of the data into training and test/validation data.

8.5.1 Sensitivity of a model ฯ€\pi with threshold cc

Sensitivity is the probability to correctly predict a positive outcome: sens(ฯ€,c)=PX*[ฯ€ฬ‚(๐—*)>cโˆฃY*=1โˆฃ๐’ฏ]. \text{sens}(\pi,c)=\text{P}_{X^*}\left[\hat\pi(\mathbf{X}^*)>c \mid Y^*=1 \mid {\mathcal{T}}\right].

It is also known as the true positive rate (TPR).

8.5.2 Specificity of a model ฯ€\pi with threshold cc

Specificity is the probability to correctly predict a negative outcome: spec(ฯ€,c)=PX*[ฯ€ฬ‚(๐—*)โ‰คcโˆฃY*=0โˆฃ๐’ฏ]. \text{spec}(\pi,c)=\text{P}_{X^*}\left[\hat\pi(\mathbf{X}^*)\leq c \mid Y^*=0 \mid {\mathcal{T}}\right].

It is also known as the true negative rate (TNR).


8.5.3 Misclassification error of a model ฯ€\pi with threshold cc

The misclassification error is the probability to incorrectly predict an outcome: mce(ฯ€,c)=PX*,Y*[ฯ€ฬ‚(๐—)โ‰คc and Y*=1โˆฃ๐’ฏ]+PX*,Y*[ฯ€ฬ‚(๐—)>c and Y*=0โˆฃ๐’ฏ].\begin{eqnarray*} \text{mce}(\pi,c) &=&\text{P}_{X^*,Y^*}\left[\hat\pi(\mathbf{X})\leq c \text{ and } Y^*=1 \mid {\mathcal{T}}\right] \\ & & + \text{P}_{X^*,Y^*}\left[\hat\pi(\mathbf{X})> c \text{ and } Y^*=0 \mid {\mathcal{T}}\right]. \end{eqnarray*}

Note that in the definitions of sensitivity, specificity and the misclassification error, the probabilities refer to the distribution of the (๐—*,Y*)(\mathbf{X}^*,Y^*), which is independent of the training data, conditional on the training data. This is in line with the test or generalisation error. The misclassification error is actually the test error when a 0/1 loss function is used. Just as before, the sensitivity, specificity and the misclassification error can also be averaged over the distribution of the training data set, which is in line with the expected test error which has been discussed earlier.


8.5.4 ROC curve of a model ฯ€\pi

The Receiver Operating Characteristic (ROC) curve for model ฯ€\pi is given by the function

ROC:[0,1]โ†’[0,1]ร—[0,1]:cโ†ฆ(1โˆ’spec(ฯ€,c),sens(ฯ€,c)). \text{ROC}: [0,1] \rightarrow [0,1]\times [0,1]: c \mapsto (1-\text{spec}(\pi,c), \text{sens}(\pi,c)).

For when cc moves from 1 to 0, the ROC function defines a curve in the plane [0,1]ร—[0,1][0,1]\times [0,1], moving from (0,0)(0,0) for c=1c=1 to (1,1)(1,1) for c=0c=0.

The horizontal axis of the ROC curve shows 1-specificity. This is also known as the False Positive Rate (FPR).


8.5.5 Area under the curve (AUC) of a model ฯ€\pi

The area under the curve (AUC) for model ฯ€\pi is area under the ROC curve and is given by โˆซ01ROC(c)dc. \int_0^1 \text{ROC}(c) dc.

Some notes about the AUC:

  • AUC=0.5 results when the ROC curve is the diagonal. This corresponds to flipping a coin, i.e.ย a complete random prediction.

  • AUC=1 results from the perfect ROC curve, which is the ROC curve through the points (0,0)(0,0), (0,1)(0,1) and (1,1)(1,1). This ROC curve includes a threshold cc such that sensitivity and specificity are equal to one.

8.6 Breast cancer example

8.6.1 Data

library(glmnet)

#BiocManager::install("genefu")
#BiocManager::install("breastCancerMAINZ")

library(genefu)
library(breastCancerMAINZ)
data(mainz)

X <- t(exprs(mainz)) # gene expressions
n <- nrow(X)
H <- diag(n)-1/n*matrix(1,ncol=n,nrow=n)
X <- H%*%X
Y <- ifelse(pData(mainz)$grade==3,1,0)
table(Y)
#> Y
#>   0   1 
#> 165  35

From the table of the outcomes in Y we read that

  • 35 tumors were graded as stage 3 and
  • 165 tumors were graded as stage 1 or 2.

In this the stage 3 tumors are referred to as cases or postives and the stage 1 and 2 tumors as controls or negatives.


8.6.2 Training and test dataset

The use of the lasso logistic regression for the prediction of stage 3 breast cancer is illustrated here by

  • randomly splitting the dataset into a training dataset (80%80\% of data = 160 tumors) and a test dataset (40 tumors)

  • using the training data to select a good ฮป\lambda value in the lasso logistic regression model (through 10-fold CV)

  • evaluating the final model by means of the test dataset (ROC Curve, AUC).

## Used to provide same results as in previous R version
RNGkind(sample.kind = "Rounding")
set.seed(6977326)
####

n <- nrow(X)
nTrain <- round(0.8*n)
nTrain
#> [1] 160
indTrain <- sample(1:n,nTrain)
XTrain <- X[indTrain,]
YTrain <- Y[indTrain]
XTest <- X[-indTrain,]
YTest <- Y[-indTrain]
table(YTest)
#> YTest
#>  0  1 
#> 32  8

Note that the randomly selected test data has 20% cases of stage 3 tumors. This is a bit higher than the 17.5% in the complete data.

One could also perform the random splitting among the positives and the negatives separately (stratified splitting).

8.6.3 Model fitting based on training data

mLasso <- glmnet(
  x = XTrain,
  y = YTrain,
  alpha = 1,
  family="binomial")  # lasso: alpha = 1

plot(mLasso, xvar = "lambda", xlim = c(-6,-1.5))


mCvLasso <- cv.glmnet(
  x = XTrain,
  y = YTrain,
  alpha = 1,
  type.measure = "class",
    family = "binomial")  # lasso alpha = 1

plot(mCvLasso)

mCvLasso
#> 
#> Call:  cv.glmnet(x = XTrain, y = YTrain, type.measure = "class", alpha = 1,      family = "binomial") 
#> 
#> Measure: Misclassification Error 
#> 
#>     Lambda Index Measure      SE Nonzero
#> min 0.1044    14  0.1437 0.03366      18
#> 1se 0.1911     1  0.1688 0.03492       0

The total misclassification error is used here to select a good value for ฮป\lambda.

# BiocManager::install("plotROC")
library(plotROC)

dfLassoOpt <- data.frame(
  pi = predict(mCvLasso,
    newx = XTest,
    s = mCvLasso$lambda.min,
    type = "response") %>% c(.),
  known.truth = YTest)

roc <-
  dfLassoOpt  %>%
  ggplot(aes(d = known.truth, m = pi)) +
  geom_roc(n.cuts = 0) +
  xlab("1-specificity (FPR)") +
  ylab("sensitivity (TPR)")

roc

calc_auc(roc)
  • The ROC curve is shown for the model based on ฮป\lambda with the smallest misclassification error. The model has an AUC of 0.83.

  • Based on this ROC curve an appropriate threshold cc can be chosen. For example, from the ROC curve we see that it is possible to attain a specificity and a sensitivity of 75%.

  • The sensitivities and specificities in the ROC curve are unbiased (independent test dataset) for the prediction model build from the training data. The estimates of sensitivity and specificity, however, are based on only 40 observations.


mLambdaOpt <- glmnet(x = XTrain,
  y = YTrain,
  alpha = 1,
  lambda = mCvLasso$lambda.min,
  family="binomial")

qplot(
  summary(coef(mLambdaOpt))[-1,1],
  summary(coef(mLambdaOpt))[-1,3]) +
  xlab("gene ID") +
  ylab("beta-hat") +
  geom_hline(yintercept = 0, color = "red")

  • The model with the optimal ฮป\lambda has only 19 non-zero parameter estimates.
  • Thus only 19 genes are involved in the prediction model.
  • These 19 parameter estimates are plotting in the graph. A listing of the model output would show the names of the genes.

dfLasso1se <- data.frame(
  pi = predict(mCvLasso,
    newx = XTest,
    s = mCvLasso$lambda.1se,
    type = "response") %>% c(.),
  known.truth = YTest)

roc <-
  rbind(
    dfLassoOpt %>%
      mutate(method = "min"),
    dfLasso1se %>%
      mutate(method = "1se")
  ) %>%
  ggplot(aes(d = known.truth, m = pi, color = method)) +
  geom_roc(n.cuts = 0) +
  xlab("1-specificity (FPR)") +
  ylab("sensitivity (TPR)")

roc

calc_auc(roc)
  • When using the ฮป\lambda of the optimal model up to 1 standard deviation, a diagonal ROC curve is obtained and hence AUC is 0.50.5.

  • This prediction model is thus equivalent to flipping a coin for making the prediction.

  • The reason is that with this choice of ฮป\lambda (strong penalisation) almost all predictors are removed from the model.

  • Therefore, do never blindly choose for the ``optimalโ€™โ€™ ฮป\lambda as defined here, but assess the performance of the model first.

mLambda1se <- glmnet(x = XTrain,
  y = YTrain,
  alpha = 1,
  lambda = mCvLasso$lambda.1se,
  family="binomial")

mLambda1se %>%
  coef %>%
  summary

8.7 The Elastic Net

The lasso and ridge regression have positive and negative properties.

  • Lasso

    • positive: sparse solution

    • negative: at most min(n,p)\min(n,p) predictors can be selected

    • negative: tend to select one predictor among a group of highly correlated predictors

  • Ridge

    • negative: no sparse solution
    • positive: more than min(n,p)\min(n,p) predictors can be selected

A compromise between lasso and ridge: the elastic net: ๐›ƒฬ‚=ArgMaxฮฒl(๐›ƒ)โˆ’ฮณ1โ€–๐›ƒโ€–1โˆ’ฮณ2โ€–๐›ƒโ€–22. \hat{\boldsymbol{\beta}} = \text{ArgMax}_\beta l(\boldsymbol{\beta}) -\gamma_1 \Vert \boldsymbol\beta\Vert_1 -\gamma_2 \Vert \boldsymbol\beta\Vert_2^2.

The elastic gives a sparse solution with potentially more than min(n,p)\min(n,p) predictors.


The glmnet R function uses the following parameterisation, ๐›ƒฬ‚=ArgMaxฮฒl(๐›ƒ)โˆ’ฮปฮฑโ€–๐›ƒโ€–1โˆ’ฮป(1โˆ’ฮฑ)โ€–๐›ƒโ€–22. \hat{\boldsymbol{\beta}} = \text{ArgMax}_\beta l(\boldsymbol{\beta}) -\lambda\alpha \Vert \boldsymbol\beta\Vert_1 -\lambda(1-\alpha) \Vert \boldsymbol\beta\Vert_2^2.

  • ฮฑ\alpha parameter gives weight to L1L_1 penalty term (hence ฮฑ=1\alpha=1 gives the lasso, and ฮฑ=0\alpha=0 gives ridge).

  • a ฮป\lambda parameter to give weight to the penalisation

  • Note that the combination of ฮป\lambda and ฮฑ\alpha gives the same flexibility as the combination of the parameters ฮป1\lambda_1 and ฮป2\lambda_2.


8.7.1 Breast cancer example

mElastic <- glmnet(
  x = XTrain,
  y = YTrain,
  alpha = 0.5,
  family="binomial")  # elastic net

plot(mElastic, xvar = "lambda",xlim=c(-5.5,-1))

mCvElastic <- cv.glmnet(x = XTrain,
  y = YTrain,
  alpha = 0.5,
  family = "binomial",
    type.measure = "class")  # elastic net

plot(mCvElastic)

mCvElastic
#> 
#> Call:  cv.glmnet(x = XTrain, y = YTrain, type.measure = "class", alpha = 0.5,      family = "binomial") 
#> 
#> Measure: Misclassification Error 
#> 
#>      Lambda Index Measure      SE Nonzero
#> min 0.01859    66  0.1313 0.02708     148
#> 1se 0.21876    13  0.1562 0.03391      26
dfElast <- data.frame(
  pi = predict(mElastic,
    newx = XTest,
    s = mCvElastic$lambda.min,
    type = "response") %>% c(.),
  known.truth = YTest)

roc <- rbind(
  dfLassoOpt %>% mutate(method = "lasso"),
  dfElast %>% mutate(method = "elast. net")) %>%
  ggplot(aes(d = known.truth, m = pi, color = method)) +
  geom_roc(n.cuts = 0) +
  xlab("1-specificity (FPR)") +
  ylab("sensitivity (TPR)")

roc

calc_auc(roc)
  • More parameters are used than for the lasso, but the performance does not improve.
mElasticOpt <- glmnet(x = XTrain,
  y = YTrain,
  alpha = 0.5,
  lambda = mCvElastic$lambda.min,
  family="binomial")

qplot(
  summary(coef(mElasticOpt))[-1,1],
  summary(coef(mElasticOpt))[-1,3]) +
  xlab("gene ID") +
  ylab("beta-hat") +
  geom_hline(yintercept = 0, color = "red")

Acknowledgement

  • Olivier Thas for sharing his materials of Analysis of High Dimensional Data 2019-2020, which I used as the starting point for this chapter.

Session info

Session info
#> [1] "2025-10-20 15:42:49 CEST"
#> โ”€ Session info โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
#>  setting  value
#>  version  R version 4.4.0 RC (2024-04-16 r86468)
#>  os       macOS 15.6
#>  system   aarch64, darwin20
#>  ui       X11
#>  language (EN)
#>  collate  en_US.UTF-8
#>  ctype    en_US.UTF-8
#>  tz       Europe/Brussels
#>  date     2025-10-20
#>  pandoc   3.4 @ /Applications/RStudio.app/Contents/Resources/app/quarto/bin/tools/aarch64/ (via rmarkdown)
#> 
#> โ”€ Packages โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
#>  package           * version    date (UTC) lib source
#>  AIMS              * 1.36.0     2024-04-30 [1] Bioconductor 3.19 (R 4.4.0)
#>  AnnotationDbi       1.66.0     2024-05-01 [1] Bioconductor 3.19 (R 4.4.0)
#>  Biobase           * 2.64.0     2024-04-30 [1] Bioconductor 3.19 (R 4.4.0)
#>  BiocFileCache       2.12.0     2024-04-30 [1] Bioconductor 3.19 (R 4.4.0)
#>  BiocGenerics      * 0.54.0     2025-04-15 [1] Bioconductor 3.21 (R 4.4.0)
#>  biomaRt           * 2.60.1     2024-06-26 [1] Bioconductor 3.19 (R 4.4.0)
#>  Biostrings          2.72.1     2024-06-02 [1] Bioconductor 3.19 (R 4.4.0)
#>  bit                 4.5.0      2024-09-20 [1] CRAN (R 4.4.1)
#>  bit64               4.5.2      2024-09-22 [1] CRAN (R 4.4.1)
#>  blob                1.2.4      2023-03-17 [1] CRAN (R 4.4.0)
#>  bookdown            0.40       2024-07-02 [1] CRAN (R 4.4.0)
#>  bootstrap           2019.6     2019-06-17 [1] CRAN (R 4.4.0)
#>  breastCancerMAINZ * 1.42.0     2024-05-02 [1] Bioconductor 3.19 (R 4.4.0)
#>  bslib               0.8.0      2024-07-29 [1] CRAN (R 4.4.0)
#>  cachem              1.1.0      2024-05-16 [1] CRAN (R 4.4.0)
#>  class               7.3-22     2023-05-03 [1] CRAN (R 4.4.0)
#>  cli                 3.6.3      2024-06-21 [1] CRAN (R 4.4.0)
#>  cluster             2.1.6      2023-12-01 [1] CRAN (R 4.4.0)
#>  codetools           0.2-20     2024-03-31 [1] CRAN (R 4.4.0)
#>  colorspace          2.1-1      2024-07-26 [1] CRAN (R 4.4.0)
#>  crayon              1.5.3      2024-06-20 [1] CRAN (R 4.4.0)
#>  curl                5.2.3      2024-09-20 [1] CRAN (R 4.4.1)
#>  DAAG              * 1.25.6     2024-05-26 [1] CRAN (R 4.4.0)
#>  data.table          1.17.6     2025-06-17 [1] CRAN (R 4.4.1)
#>  DBI                 1.2.3      2024-06-02 [1] CRAN (R 4.4.0)
#>  dbplyr              2.5.0      2024-03-19 [1] CRAN (R 4.4.0)
#>  deldir              2.0-4      2024-02-28 [1] CRAN (R 4.4.0)
#>  digest              0.6.37     2024-08-19 [1] CRAN (R 4.4.1)
#>  dplyr             * 1.1.4      2023-11-17 [1] CRAN (R 4.4.0)
#>  e1071             * 1.7-16     2024-09-16 [1] CRAN (R 4.4.1)
#>  evaluate            1.0.0      2024-09-17 [1] CRAN (R 4.4.1)
#>  fansi               1.0.6      2023-12-08 [1] CRAN (R 4.4.0)
#>  farver              2.1.2      2024-05-13 [1] CRAN (R 4.4.0)
#>  fastmap             1.2.0      2024-05-15 [1] CRAN (R 4.4.0)
#>  filelock            1.0.3      2023-12-11 [1] CRAN (R 4.4.0)
#>  forcats           * 1.0.0      2023-01-29 [1] CRAN (R 4.4.0)
#>  foreach             1.5.2      2022-02-02 [1] CRAN (R 4.4.0)
#>  future              1.34.0     2024-07-29 [1] CRAN (R 4.4.0)
#>  future.apply        1.11.2     2024-03-28 [1] CRAN (R 4.4.0)
#>  genefu            * 2.36.0     2024-04-30 [1] Bioconductor 3.19 (R 4.4.0)
#>  generics          * 0.1.3      2022-07-05 [1] CRAN (R 4.4.0)
#>  GenomeInfoDb        1.40.1     2024-06-16 [1] Bioconductor 3.19 (R 4.4.0)
#>  GenomeInfoDbData    1.2.12     2024-04-24 [1] Bioconductor
#>  ggforce           * 0.4.2      2024-02-19 [1] CRAN (R 4.4.0)
#>  ggplot2           * 3.5.1      2024-04-23 [1] CRAN (R 4.4.0)
#>  glmnet            * 4.1-8      2023-08-22 [1] CRAN (R 4.4.0)
#>  globals             0.16.3     2024-03-08 [1] CRAN (R 4.4.0)
#>  glue                1.8.0      2024-09-30 [1] CRAN (R 4.4.1)
#>  gridExtra         * 2.3        2017-09-09 [1] CRAN (R 4.4.0)
#>  gtable              0.3.5      2024-04-22 [1] CRAN (R 4.4.0)
#>  highr               0.11       2024-05-26 [1] CRAN (R 4.4.0)
#>  hms                 1.1.3      2023-03-21 [1] CRAN (R 4.4.0)
#>  htmltools           0.5.8.1    2024-04-04 [1] CRAN (R 4.4.0)
#>  httr                1.4.7      2023-08-15 [1] CRAN (R 4.4.0)
#>  httr2               1.0.5      2024-09-26 [1] CRAN (R 4.4.1)
#>  iC10              * 2.0.2      2024-07-19 [1] CRAN (R 4.4.0)
#>  iC10TrainingData    2.0.1      2024-07-16 [1] CRAN (R 4.4.0)
#>  impute              1.78.0     2024-04-30 [1] Bioconductor 3.19 (R 4.4.0)
#>  interp              1.1-6      2024-01-26 [1] CRAN (R 4.4.0)
#>  IRanges             2.38.1     2024-07-03 [1] Bioconductor 3.19 (R 4.4.0)
#>  iterators           1.0.14     2022-02-05 [1] CRAN (R 4.4.0)
#>  jpeg                0.1-10     2022-11-29 [1] CRAN (R 4.4.0)
#>  jquerylib           0.1.4      2021-04-26 [1] CRAN (R 4.4.0)
#>  jsonlite            1.8.9      2024-09-20 [1] CRAN (R 4.4.1)
#>  KEGGREST            1.44.1     2024-06-19 [1] Bioconductor 3.19 (R 4.4.0)
#>  KernSmooth          2.23-24    2024-05-17 [1] CRAN (R 4.4.0)
#>  knitr               1.48       2024-07-07 [1] CRAN (R 4.4.0)
#>  labeling            0.4.3      2023-08-29 [1] CRAN (R 4.4.0)
#>  latex2exp         * 0.9.6      2022-11-28 [1] CRAN (R 4.4.0)
#>  lattice             0.22-6     2024-03-20 [1] CRAN (R 4.4.0)
#>  latticeExtra        0.6-30     2022-07-04 [1] CRAN (R 4.4.0)
#>  lava                1.8.0      2024-03-05 [1] CRAN (R 4.4.0)
#>  lifecycle           1.0.4      2023-11-07 [1] CRAN (R 4.4.0)
#>  limma               3.60.6     2024-10-02 [1] Bioconductor 3.19 (R 4.4.0)
#>  listenv             0.9.1      2024-01-29 [1] CRAN (R 4.4.0)
#>  lubridate         * 1.9.3      2023-09-27 [1] CRAN (R 4.4.0)
#>  magrittr            2.0.3      2022-03-30 [1] CRAN (R 4.4.0)
#>  MASS                7.3-61     2024-06-13 [1] CRAN (R 4.4.0)
#>  Matrix            * 1.7-0      2024-03-22 [1] CRAN (R 4.4.0)
#>  mclust              6.1.1      2024-04-29 [1] CRAN (R 4.4.0)
#>  memoise             2.0.1      2021-11-26 [1] CRAN (R 4.4.0)
#>  mgcv              * 1.9-1      2023-12-21 [1] CRAN (R 4.4.0)
#>  munsell             0.5.1      2024-04-01 [1] CRAN (R 4.4.0)
#>  nlme              * 3.1-166    2024-08-14 [1] CRAN (R 4.4.0)
#>  pamr                1.57       2024-07-01 [1] CRAN (R 4.4.0)
#>  parallelly          1.38.0     2024-07-27 [1] CRAN (R 4.4.0)
#>  pillar              1.9.0      2023-03-22 [1] CRAN (R 4.4.0)
#>  pkgconfig           2.0.3      2019-09-22 [1] CRAN (R 4.4.0)
#>  plotROC           * 2.3.1      2023-10-06 [1] CRAN (R 4.4.0)
#>  plyr                1.8.9      2023-10-02 [1] CRAN (R 4.4.0)
#>  png                 0.1-8      2022-11-29 [1] CRAN (R 4.4.0)
#>  polyclip            1.10-7     2024-07-23 [1] CRAN (R 4.4.0)
#>  prettyunits         1.2.0      2023-09-24 [1] CRAN (R 4.4.0)
#>  prodlim           * 2024.06.25 2024-06-24 [1] CRAN (R 4.4.0)
#>  progress            1.2.3      2023-12-06 [1] CRAN (R 4.4.0)
#>  proxy               0.4-27     2022-06-09 [1] CRAN (R 4.4.0)
#>  purrr             * 1.0.2      2023-08-10 [1] CRAN (R 4.4.0)
#>  R6                  2.5.1      2021-08-19 [1] CRAN (R 4.4.0)
#>  rappdirs            0.3.3      2021-01-31 [1] CRAN (R 4.4.0)
#>  rbibutils           2.2.16     2023-10-25 [1] CRAN (R 4.4.0)
#>  RColorBrewer        1.1-3      2022-04-03 [1] CRAN (R 4.4.0)
#>  Rcpp                1.0.13-1   2024-11-02 [1] CRAN (R 4.4.1)
#>  Rdpack              2.6.1      2024-08-06 [1] CRAN (R 4.4.0)
#>  readr             * 2.1.5      2024-01-10 [1] CRAN (R 4.4.0)
#>  rlang               1.1.4      2024-06-04 [1] CRAN (R 4.4.0)
#>  rmarkdown           2.28       2024-08-17 [1] CRAN (R 4.4.0)
#>  rmeta               3.0        2018-03-20 [1] CRAN (R 4.4.0)
#>  RSQLite             2.3.7      2024-05-27 [1] CRAN (R 4.4.0)
#>  rstudioapi          0.16.0     2024-03-24 [1] CRAN (R 4.4.0)
#>  S4Vectors           0.42.1     2024-07-03 [1] Bioconductor 3.19 (R 4.4.0)
#>  sass                0.4.9      2024-03-15 [1] CRAN (R 4.4.0)
#>  scales              1.3.0      2023-11-28 [1] CRAN (R 4.4.0)
#>  SemiPar           * 1.0-4.2    2018-04-16 [1] CRAN (R 4.4.0)
#>  sessioninfo         1.2.2      2021-12-06 [1] CRAN (R 4.4.0)
#>  shape               1.4.6.1    2024-02-23 [1] CRAN (R 4.4.0)
#>  statmod             1.5.0      2023-01-06 [1] CRAN (R 4.4.0)
#>  stringi             1.8.4      2024-05-06 [1] CRAN (R 4.4.0)
#>  stringr           * 1.5.1      2023-11-14 [1] CRAN (R 4.4.0)
#>  SuppDists           1.1-9.8    2024-09-03 [1] CRAN (R 4.4.1)
#>  survcomp          * 1.54.0     2024-04-30 [1] Bioconductor 3.19 (R 4.4.0)
#>  survival          * 3.7-0      2024-06-05 [1] CRAN (R 4.4.0)
#>  survivalROC         1.0.3.1    2022-12-05 [1] CRAN (R 4.4.0)
#>  tibble            * 3.2.1      2023-03-20 [1] CRAN (R 4.4.0)
#>  tidyr             * 1.3.1      2024-01-24 [1] CRAN (R 4.4.0)
#>  tidyselect          1.2.1      2024-03-11 [1] CRAN (R 4.4.0)
#>  tidyverse         * 2.0.0      2023-02-22 [1] CRAN (R 4.4.0)
#>  timechange          0.3.0      2024-01-18 [1] CRAN (R 4.4.0)
#>  tweenr              2.0.3      2024-02-26 [1] CRAN (R 4.4.0)
#>  tzdb                0.4.0      2023-05-12 [1] CRAN (R 4.4.0)
#>  UCSC.utils          1.0.0      2024-05-06 [1] Bioconductor 3.19 (R 4.4.0)
#>  utf8                1.2.4      2023-10-22 [1] CRAN (R 4.4.0)
#>  vctrs               0.6.5      2023-12-01 [1] CRAN (R 4.4.0)
#>  vroom               1.6.5      2023-12-05 [1] CRAN (R 4.4.0)
#>  withr               3.0.1      2024-07-31 [1] CRAN (R 4.4.0)
#>  xfun                0.47       2024-08-17 [1] CRAN (R 4.4.0)
#>  xml2                1.3.6      2023-12-04 [1] CRAN (R 4.4.0)
#>  XVector             0.44.0     2024-04-30 [1] Bioconductor 3.19 (R 4.4.0)
#>  yaml                2.3.10     2024-07-26 [1] CRAN (R 4.4.0)
#>  zlibbioc            1.50.0     2024-04-30 [1] Bioconductor 3.19 (R 4.4.0)
#> 
#>  [1] /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/library
#> 
#> โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
LS0tCnRpdGxlOiAiMy4gUHJlZGljdGlvbiB3aXRoIEhpZ2ggRGltZW5zaW9uYWwgUHJlZGljdG9ycyIKYXV0aG9yOiAiTGlldmVuIENsZW1lbnQiCmRhdGU6ICJzdGF0T21pY3MsIEdoZW50IFVuaXZlcnNpdHkgKGh0dHBzOi8vc3RhdG9taWNzLmdpdGh1Yi5pbykiCm91dHB1dDoKICBib29rZG93bjo6cGRmX2RvY3VtZW50MjoKICAgIHRvYzogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCiAgICBsYXRleF9lbmdpbmU6IHhlbGF0ZXgKYWx3YXlzX2FsbG93X2h0bWw6IHRydWUKLS0tCgpgYGB7ciwgY2hpbGQ9Il9zZXR1cC5SbWQifQpgYGAKCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9IEZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShncmlkRXh0cmEpCmBgYAoKIyBJbnRyb2R1Y3Rpb24KCiMjIFByZWRpY3Rpb24gd2l0aCBIaWdoIERpbWVuc2lvbmFsIFByZWRpY3RvcnMKCkdlbmVyYWwgc2V0dGluZzoKCi0gICBBaW06IGJ1aWxkIGEgKipwcmVkaWN0aW9uIG1vZGVsKiogdGhhdCBnaXZlcyBhIHByZWRpY3Rpb24gb2YgYW4gb3V0Y29tZSBmb3IgYSBnaXZlbiBzZXQgb2YgcHJlZGljdG9ycy4KCi0gV2UgdXNlICRYJCB0byByZWZlciB0byB0aGUgcHJlZGljdG9ycyBhbmQgJFkkIHRvIHJlZmVyIHRvIHRoZSBvdXRjb21lLgoKCi0gQSAqKnRyYWluaW5nIGRhdGEgc2V0KiogaXMgYXZhaWxhYmxlLCBzYXkgJChcbWF0aGJme1h9LFxtYXRoYmZ7WX0pJC4gSXQgY29udGFpbnMgJG4kIG9ic2VydmF0aW9ucyBvbiBvdXRjb21lcyBhbmQgb24gJHAkIHByZWRpY3RvcnMuCgotIFVzaW5nIHRoZSB0cmFpbmluZyBkYXRhLCBhIHByZWRpY3Rpb24gbW9kZWwgaXMgYnVpbGQsIHNheSAkXGhhdHttfShcbWF0aGJme1h9KSQuIFRoaXMgdHlwaWNhbGx5IGludm9sdmVzICoqbW9kZWwgYnVpbGRpbmcgKGZlYXR1cmUgc2VsZWN0aW9uKSoqIGFuZCBwYXJhbWV0ZXIgZXN0aW1hdGlvbi4KCgotICAgRHVyaW5nIHRoZSBtb2RlbCBidWlsZGluZywgcG90ZW50aWFsICoqbW9kZWxzIG5lZWQgdG8gYmUgZXZhbHVhdGVkKiogaW4gdGVybXMgb2YgdGhlaXIgcHJlZGljdGlvbiBxdWFsaXR5LgoKIyMgRXhhbXBsZTogVG94aWNvZ2Vub21pY3MgaW4gZWFybHkgZHJ1ZyBkZXZlbG9wbWVudAoKIyMjIEJhY2tncm91bmQKCi0gRWZmZWN0IG9mIGNvbXBvdW5kIG9uIGdlbmUgZXhwcmVzc2lvbi4KCi0gSW5zaWdodCBpbiBhY3Rpb24gYW5kIHRveGljaXR5IG9mIGRydWcgaW4gZWFybHkgcGhhc2UKLSBEZXRlcm1pbmUgYWN0aXZpdHkgd2l0aCBiaW8tYXNzYXk6IGUuZy4gYmluZGluZyBhZmZpbml0eSBvZiBjb21wb3VuZCB0byBjZWxsIHdhbGwgcmVjZXB0b3IgKHRhcmdldCwgSUM1MCkuCi0gRWFybHkgcGhhc2U6ICAyMCB0byA1MCBjb21wb3VuZHMKLSBCYXNlZCBvbiBpbiB2aXRybyByZXN1bHRzIG9uZSBhaW1zIHRvIGdldCBpbnNpZ2h0IGluIGhvdyB0byBidWlsZCBiZXR0ZXIgY29tcG91bmQgKGhpZ2hlciBvbi10YXJnZXQgYWN0aXZpdHkgbGVzcyB0b3hpY2l0eS4KLSBTbWFsbCB2YXJpYXRpb25zIGluIG1vbGVjdWxhciBzdHJ1Y3R1cmUgbGVhZCB0byB2YXJpYXRpb25zIGluIEJBIGFuZCBnZW5lIGV4cHJlc3Npb24uCi0gQWltOiBCdWlsZCBtb2RlbCB0byBwcmVkaWN0IGJpby1hY3Rpdml0eSBiYXNlZCBvbiBnZW5lIGV4cHJlc3Npb24gaW4gbGl2ZXIgY2VsbCBsaW5lLgoKIyMjIERhdGEKCi0gMzAgY2hlbWljYWwgY29tcG91bmRzIGhhdmUgYmVlbiBzY3JlZW5lZCBmb3IgdG94aWNpdHkKCi0gQmlvYXNzYXkgZGF0YSBvbiB0b3hpY2l0eSBzY3JlZW5pbmcKCi0gR2VuZSBleHByZXNzaW9ucyBpbiBhIGxpdmVyIGNlbGwgbGluZSBhcmUgcHJvZmlsZWQgZm9yIGVhY2ggY29tcG91bmQgKDQwMDAgZ2VuZXMpCgoKYGBge3J9CnRveERhdGEgPC0gcmVhZF9jc3YoCiAgImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9zdGF0T21pY3MvSERBMjAyMC9kYXRhL3RveERhdGFDZW50ZXJlZC5jc3YiLAogIGNvbF90eXBlcyA9IGNvbHMoKQopCnN2ZFggPC0gc3ZkKHRveERhdGFbLC0xXSkKYGBgCgpEYXRhIGlzIGFscmVhZHkgY2VudGVyZWQ6CgpgYGB7cn0KdG94RGF0YSAlPiUKICBjb2xNZWFucyAlPiUKICByYW5nZQpgYGAKCmBgYHtyfQogdG94RGF0YSAlPiUKICBuYW1lcyAlPiUKICBoZWFkCmBgYAoKLSBGaXJzdCBjb2x1bW4gY29udGFpbnMgZGF0YSBvbiBCaW9hc3NheS4KLSBUaGUgaGlnaGVyIHRoZSBzY29yZSBvbiBCaW9hc3NheSB0aGUgbW9yZSB0b3hpYyB0aGUgY29tcG91bmQKLSBPdGhlciBjb2x1bW5zIGNvbnRhaW4gZGF0YSBvbiBnZW5lIGV4cHJlc3Npb24gWDEsIC4uLiAsIFg0MDAwCgojIyMgRGF0YSBleHBsb3JhdGlvbgoKYGBge3J9CnRveERhdGEgJT4lCiAgZ2dwbG90KGFlcyh4PSIiLHk9QkEpKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGU9TkEpICsKICBnZW9tX3BvaW50KHBvc2l0aW9uPSJqaXR0ZXIiKQpgYGAKCmBgYHtyfQpzdmRYIDwtIHRveERhdGFbLC0xXSAlPiUKICBzdmQKCmsgPC0gMgpWayA8LSBzdmRYJHZbLDE6a10KVWsgPC0gc3ZkWCR1WywxOmtdCkRrIDwtIGRpYWcoc3ZkWCRkWzE6a10pClprIDwtIFVrJSolRGsKY29sbmFtZXMoWmspIDwtIHBhc3RlMCgiWiIsMTprKQpjb2xuYW1lcyhWaykgPC0gcGFzdGUwKCJWIiwxOmspCgpaayAlPiUKICBhcy5kYXRhLmZyYW1lICU+JQogIG11dGF0ZShCQSA9IHRveERhdGEgJT4lIHB1bGwoQkEpKSAlPiUKICBnZ3Bsb3QoYWVzKHg9IFoxLCB5ID0gWjIsIGNvbG9yID0gQkEpKSArCiAgZ2VvbV9wb2ludChzaXplID0gMykgKwogIHNjYWxlX2NvbG91cl9ncmFkaWVudDIobG93ID0gImJsdWUiLG1pZD0id2hpdGUiLGhpZ2g9InJlZCIpICsKICBnZW9tX3BvaW50KHNpemUgPSAzLCBwY2ggPSAyMSwgY29sb3IgPSAiYmxhY2siKQpgYGAKCi0gU2NvcmVzIG9uIHRoZSBmaXJzdCB0d28gcHJpbmNpcGFsIGNvbXBvbmVudHMgKG9yIE1EUyBwbG90KS4KLSBFYWNoIHBvaW50IGNvcnJlc3BvbmRzIHRvIGEgY29tcG91bmQuCi0gQ29sb3IgY29kZSByZWZlcnMgdG8gdGhlIHRveGljaXR5IHNjb3JlIChoaWdoZXIgc2NvcmUgbW9yZSB0b3hpYykuCi0gQ2xlYXIgc2VwYXJhdGlvbiBiZXR3ZWVuIGNvbXBvdW5kcyBhY2NvcmRpbmcgdG8gdG94aWNpdHkuCgotLS0KCi0gTmV4dCBsb2dpYyBzdGVwIGluIGEgUENBIGlzIHRvIGludGVycHJldCB0aGUgcHJpbmNpcGFsIGNvbXBvbmVudHMuCi0gV2UgdGh1cyBoYXZlIHRvIGFzc2VzcyB0aGUgbG9hZGluZ3MuCi0gV2UgY2FuIGFkZCBhIHZlY3RvciBmb3IgZWFjaCBnZW5lIHRvIGdldCBhIGJpcGxvdCwgYnV0IHRoaXMgd291bGQgcmVxdWlyZSBwbG90dGluZyA0MDAwIHZlY3RvcnMsIHdoaWNoIHdvdWxkIHJlbmRlciB0aGUgcGxvdCB1bnJlYWRhYmxlLgoKQWx0ZXJuYXRpdmUgZ3JhcGggdG8gbG9vayBhdCB0aGUgbWFueSBsb2FkaW5ncyBvZiB0aGUgZmlyc3QgdHdvIFBDcy4KCmBgYHtyfQpncmlkLmFycmFuZ2UoCiAgVmsgJT4lCiAgICBhcy5kYXRhLmZyYW1lICU+JQogICAgbXV0YXRlKGdlbmVJRCA9IDE6bnJvdyhWaykpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gZ2VuZUlELCB5ID0gVjEpKSArCiAgICBnZW9tX3BvaW50KHBjaD0yMSkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gYygtMiwwLDIpKnNkKFZrWywxXSksIGNvbCA9ICJyZWQiKSAsCiAgVmsgJT4lCiAgICBhcy5kYXRhLmZyYW1lICU+JQogICAgbXV0YXRlKGdlbmVJRCA9IDE6bnJvdyhWaykpICU+JQogICAgZ2dwbG90KGFlcyh4ID0gZ2VuZUlELCB5ID0gVjIpKSArCiAgICBnZW9tX3BvaW50KHBjaD0yMSkgKwogICAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gYygtMiwwLDIpKnNkKFZrWywyXSksIGNvbCA9ICJyZWQiKSwKICBuY29sPTIpCmBgYAoKLSBJdCBpcyBhbG1vc3QgaW1wb3NzaWJsZSB0byBpbnRlcnByZXQgdGhlIFBDcyBiZWNhdXNlIHRoZXJlIGFyZSA0MDAwIGdlbmVzIGNvbnRyaWJ1dGluZyB0byBlYWNoIFBDLgoKLSBJbiBhbiBhdHRlbXB0IHRvIGZpbmQgdGhlIG1vc3QgaW1wb3J0YW50IGdlbmVzIChpbiB0aGUgc2Vuc2UgdGhhdCB0aGV5IGRyaXZlIHRoZSBpbnRlcnByZXRhdGlvbiBvZiB0aGUgUENzKSwgdGhlIHBsb3RzIHNob3cgaG9yaXpvbnRhbCByZWZlcmVuY2UgbGluZXM6IHRoZSBhdmVyYWdlIG9mIHRoZSBsb2FkaW5ncywgYW5kIHRoZSBhdmVyYWdlIMKxIHR3aWNlIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIGxvYWRpbmdzLiBJbiBiZXR3ZWVuIHRoZSBsaW5lcyB3ZSBleHBlY3RzIGFib3V0IDk1JSBvZiB0aGUgbG9hZGluZ3MgKGlmIHRoZXkgd2VyZSBub3JtYWxseSBkaXN0cmlidXRlZCkuCgotIFRoZSBwb2ludHMgb3V0c2lkZSB0aGUgYmFuZCBjb21lIGZyb20gdGhlIGdlbmVzIHRoYXQgaGF2ZSByYXRoZXIgbGFyZ2UgbG9hZGluZ3MgKGluIGFic29sdXRlIHZhbHVlKSBhbmQgaGVuY2UgYXJlIGltcG9ydGFudCBmb3IgdGhlIGludGVycHJldGF0aW9uIG9mIHRoZSBQQ3MuCgotIE5vdGUsIHRoYXQgcGFydGljdWxhcmx5IGZvciB0aGUgZmlyc3QgUEMsIG9ubHkgYSBmZXcgZ2VuZXMgc2hvdyBhIG1hcmtlZGx5IGxhcmdlIGxvYWRpbmdzIHRoYXQgYXJlIG5lZ2F0aXZlLiBUaGlzIG1lYW5zIHRoYXQgYW4gdXByZWd1bGF0aW9uIG9mIHRoZXNlIGdlbmVzIHdpbGwgbGVhZCB0byBsb3cgc2NvcmVzIG9uIFBDMS4KLSBUaGVzZSBnZW5lcyB3aWxsIHZlcnkgbGlrZWx5IHBsYXkgYW4gaW1wb3J0YW50IHJvbGUgaW4gdGhlIHRveGljaXR5IG1lY2hhbmlzbS4KLSBJbmRlZWQsIGxvdyBzY29yZXMgb24gUEMxIGFyZSBpbiB0aGUgZGlyZWN0aW9uIG9mIG1vcmUgdG94aWNpdHkuCi0gSW4gdGhlIG5leHQgY2hhcHRlciB3ZSB3aWxsIGludHJvZHVjZSBhIG1ldGhvZCB0byBvYnRhaW4gc3BhcnNlIFBDcy4KCiMjIyBQcmVkaWN0aW9uIG1vZGVsCgpgYGB7cn0KbTEgPC0gbG0oQkEgfiAtMSArIC4sIHRveERhdGEpCgptMSAlPiUKICBjb2VmICU+JQogIGhlYWQoNDApCgptMSAlPiUKICBjb2VmICU+JQogIGlzLm5hICU+JQogIHN1bQoKc3VtbWFyeShtMSkkci5zcXVhcmVkCmBgYAoKUHJvYmxlbT8/CgojIyBCcmFpbiBleGFtcGxlCgotIENvdXJ0ZXN5IHRvIFNvbG9tb24gS3Vyei4gU3RhdGlzdGljYWwgcmV0aGlua2luZyB3aXRoIGJybXMsIGdncGxvdDIsIGFuZCB0aGUgdGlkeXZlcnNlIHZlcnNpb24gMS4yLjAuCgpodHRwczovL2Jvb2tkb3duLm9yZy9jb250ZW50LzM4OTAvCmh0dHBzOi8vZ2l0aHViLmNvbS9BU0t1cnovU3RhdGlzdGljYWxfUmV0aGlua2luZ193aXRoX2JybXNfZ2dwbG90Ml9hbmRfdGhlX3RpZHl2ZXJzZQoKLSBEYXRhIHdpdGggYnJhaW4gc2l6ZSBhbmQgYm9keSBzaXplIGZvciBzZXZlbiBzcGVjaWVzCgpgYGB7cn0KYnJhaW4gPC0KdGliYmxlKHNwZWNpZXMgPSBjKCJhZmFyZW5zaXMiLCAiYWZyaWNhbnVzIiwgImhhYmlsaXMiLCAiYm9pc2VpIiwgInJ1ZG9sZmVuc2lzIiwgImVyZ2FzdGVyIiwgInNhcGllbnMiKSwKICAgICAgIGJyYWluICAgPSBjKDQzOCwgNDUyLCA2MTIsIDUyMSwgNzUyLCA4NzEsIDEzNTApLAogICAgICAgbWFzcyAgICA9IGMoMzcuMCwgMzUuNSwgMzQuNSwgNDEuNSwgNTUuNSwgNjEuMCwgNTMuNSkpCmBgYAoKIyMjIERhdGEgZXhwbG9yYXRpb24KCmBgYHtyfQpicmFpbgoKcCA8LSBicmFpbiAlPiUKICBnZ3Bsb3QoYWVzKHggPSAgbWFzcywgeSA9IGJyYWluLCBsYWJlbCA9IHNwZWNpZXMpKSArCiAgZ2VvbV9wb2ludCgpCgpwICsgZ2VvbV90ZXh0KG51ZGdlX3kgPSA0MCkKYGBgCgojIyMgTW9kZWxzCgpTaXggbW9kZWxzIHJhbmdlIGluIGNvbXBsZXhpdHkgZnJvbSB0aGUgc2ltcGxlIHVuaXZhcmlhdGUgbW9kZWwKClxiZWdpbnthbGlnbip9Clx0ZXh0e2JyYWlufV9pICYgXHNpbSBcb3BlcmF0b3JuYW1le05vcm1hbH0gKFxtdV9pLCBcc2lnbWEpIFxcClxtdV9pICYgPSBcYmV0YV8wICsgXGJldGFfMSBcdGV4dHttYXNzfV9pLApcZW5ke2FsaWduKn0KCnRvIHRoZSBkaXp6eWluZyBzaXh0aC1kZWdyZWUgcG9seW5vbWlhbCBtb2RlbAoKXGJlZ2lue2FsaWduKn0KXHRleHR7YnJhaW59X2kgJiBcc2ltIFxvcGVyYXRvcm5hbWV7Tm9ybWFsfSAoXG11X2ksIFxzaWdtYSkgXFwKXG11X2kgJiA9IFxiZXRhXzAgKyBcYmV0YV8xIFx0ZXh0e21hc3N9X2kgKyBcYmV0YV8yIFx0ZXh0e21hc3N9X2leMiArIFxiZXRhXzMgXHRleHR7bWFzc31faV4zICsgXGJldGFfNCBcdGV4dHttYXNzfV9pXjQgKyBcYmV0YV81IFx0ZXh0e21hc3N9X2leNSArIFxiZXRhXzYgXHRleHR7bWFzc31faV42LgpcZW5ke2FsaWduKn0KCmBgYHtyLCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEZ9CmZvcm11bGFzIDwtIHNhcHBseSgxOjYsIGZ1bmN0aW9uKGkpCiAgcmV0dXJuKAogICAgIHBhc3RlMCgiSShtYXNzXiIsMTppLCIpIikgJT4lIHBhc3RlKGNvbGxhcHNlPSIgKyAiKQogICAgKQopCgpmb3JtdWxhcyA8LSBzYXBwbHkoCiAgcGFzdGUwKCJicmFpbiB+ICIsIGZvcm11bGFzKSwKICBhcy5mb3JtdWxhKQoKbW9kZWxzIDwtIGxhcHBseShmb3JtdWxhcywgbG0gLCBkYXRhID0gYnJhaW4pCmBgYAoKYGBge3J9CmRhdGEuZnJhbWUoCiAgZm9ybXVsYT1mb3JtdWxhcyAlPiUKICAgIGFzLmNoYXJhY3RlciwKICByMiA9IHNhcHBseSgKICAgIG1vZGVscywKICAgIGZ1bmN0aW9uKG1vZCkgc3VtbWFyeShtb2QpJHIuc3F1YXJlZCkKICApICAlPiUKICBnZ3Bsb3QoCiAgICBhZXMoeCA9IHIyLAogICAgICB5ID0gZm9ybXVsYSwKICAgICAgbGFiZWwgPSByMiAlPiUKICAgICAgICByb3VuZCgyKSAlPiUKICAgICAgICBhcy5jaGFyYWN0ZXIpCiAgKSArCiAgZ2VvbV90ZXh0KCkKYGBgCgpXZSBwbG90IHRoZSBmaXQgZm9yIGVhY2ggbW9kZWwgaW5kaXZpZHVhbGx5IGFuZCB0aGVtIGFycmFuZ2UgdGhlbSB0b2dldGhlciBpbiBvbmUgcGxvdC4KCmBgYHtyfQpwbG90cyA8LSBsYXBwbHkoMTo2LCBmdW5jdGlvbihpKQp7CiAgcCArCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIiwgZm9ybXVsYSA9IHkgfiBwb2x5KHgsaSkpICsKICBnZ3RpdGxlKAogICAgcGFzdGUwKAogICAgICAicjIgPSAiLAogICAgICByb3VuZChzdW1tYXJ5KG1vZGVsc1tbaV1dKSRyLnNxdWFyZWQqMTAwLDEpLAogICAgICAiJSIpCiAgICApCn0pCgpkby5jYWxsKCJncmlkLmFycmFuZ2UiLGMocGxvdHMsIG5jb2wgPSAzKSkKYGBgCgotIFdlIGNsZWFybHkgc2VlIHRoYXQgaW5jcmVhc2luZyB0aGUgbW9kZWwgY29tcGxleGl0eSBhbHdheXMgcHJvZHVjZXMgYSBmaXQgd2l0aCBhIHNtYWxsZXIgU1NFLgotIFRoZSBwcm9ibGVtIG9mIG92ZXJmaXR0aW5nIGlzIHZlcnkgb2J2aW91cy4gVGhlIG1vcmUgY29tcGxleCBwb2x5bm9taWFsIG1vZGVscyB3aWxsIG5vdCBnZW5lcmFsaXNlIHdlbGwgZm9yIHByZWRpY3Rpb24hCi0gV2UgZXZlbiBoYXZlIGEgbW9kZWwgdGhhdCBmaXRzIHRoZSBkYXRhIHBlcmZlY3RseSwgYnV0IHRoYXQgd2lsbCBtYWtlIHZlcnkgYWJzdXJkIHByZWRpdGlvbnMhCgotIFRvbyBmZXcgcGFyYW1ldGVycyBodXJ0cywgdG9vLiBGaXQgdGhlIHVuZGVyZml0IGludGVyY2VwdC1vbmx5IG1vZGVsLgoKYGBge3J9Cm0wIDwtIGxtKGJyYWluIH4gMSwgYnJhaW4pCnN1bW1hcnkobTApCgpwICsKICBzdGF0X3Ntb290aChtZXRob2QgPSAibG0iLCBmb3JtdWxhID0geSB+IDEpICsKICBnZ3RpdGxlKAogICAgcGFzdGUwKAogICAgICAicjIgPSAiLAogICAgICByb3VuZChzdW1tYXJ5KG0wKSRyLnNxdWFyZWQqMTAwLDEpLAogICAgICAiJSIpCiAgICApCmBgYAoKVGhlIHVuZGVyZml0IG1vZGVsIGRpZCBub3QgbGVhcm4gYW55dGhpbmcgYWJvdXQgdGhlIHJlbGF0aW9uIGJldHdlZW4gbWFzcyBhbmQgYnJhaW4uIEl0IHdvdWxkIGFsc28gZG8gYSB2ZXJ5IHBvb3Igam9iIGZvciBwcmVkaWN0aW5nIG5ldyBkYXRhLgoKIyMgT3ZlcnZpZXcKCldlIHdpbGwgbWFrZSBhIGRpc3RpbmN0aW9uIGJldHdlZW4gY29udGludW91cyBhbmQgZGlzY3JldGUgb3V0Y29tZXMuIEluIHRoaXMgY291cnNlIHdlIGZvY3VzIG9uCgotIExpbmVhciByZWdyZXNzaW9uIG1vZGVscyBmb3IgY29udGlub3VzIG91dGNvbWVzCgogIC0gUGVuYWxpc2VkIHJlZ3Jlc3Npb246IExhc3NvIGFuZCByaWRnZQogIC0gUHJpbmNpcGFsIGNvbXBvbmVudCByZWdyZXNzaW9uIChQQ1IpCgotIExvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWxzIGZvciBiaW5hcnkgb3V0Y29tZXMKCiAgLSBQZW5hbGlzZWQgcmVncmVzc2lvbjogTGFzc28gYW5kIHJpZGdlCgpGb3IgYWxsIHR5cGVzIG9mIG1vZGVsLCB3ZSB3aWxsIGRpc2N1c3MgZmVhdHVyZSBzZWxlY3Rpb24gbWV0aG9kcy4KCiMgTGluZWFyIFJlZ3Jlc3Npb24gZm9yIEhpZ2ggRGltZW5zaW9uYWwgRGF0YQoKQ29uc2lkZXIgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgKGZvciBkb3VibGUgY2VudGVyZWQgZGF0YSkKXFsKICBZX2kgPSBcYmV0YV8xWF97aTF9ICsgXGJldGFfMiBYX3tpMn0gKyBcY2RvdHMgKyBcYmV0YV9wWF97aXB9ICsgXGVwc2lsb25faSAsClxdCndpdGggJFx0ZXh0e0V9XGxlZnRbXGVwc2lsb24gXG1pZCBcbWF0aGJme1h9XHJpZ2h0XT0wJCBhbmQgJFx0ZXh0e3Zhcn1cbGVmdFtcZXBzaWxvbiBcbWlkIFxtYXRoYmZ7WH1ccmlnaHRdPVxzaWdtYV4yJC4KCkluIG1hdHJpeCBub3RhdGlvbiB0aGUgbW9kZWwgYmVjb21lcwpcWwogIFxtYXRoYmZ7WX0gPSBcbWF0aGJme1h9XG1hdGhiZlxiZXRhICsgXG1hdGhiZlxlcHNpbG9uLgpcXQpUaGUgbGVhc3Qgc3F1YXJlcyBlc3RpbWF0b3Igb2YgJFxtYXRoYmZcYmV0YSQgaXMgZ2l2ZW4gYnkKXFsKICBcaGF0e1xtYXRoYmZcYmV0YX0gPSAoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfVxtYXRoYmZ7WH1eVFxtYXRoYmZ7WX0gLApcXQphbmQgdGhlIHZhcmlhbmNlIG9mICRcaGF0e1xtYXRoYmZcYmV0YX0kIGVxdWFscwpcWwogIFx0ZXh0e3Zhcn1cbGVmdFtcaGF0e1xtYXRoYmZcYmV0YX1ccmlnaHRdID0gKFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0pXnstMX1cc2lnbWFeMi4KXF0KJFxsb25ncmlnaHRhcnJvdyQgdGhlICRwIFx0aW1lcyBwJCBtYXRyaXggJChcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9JCBpcyBjcnVjaWFsCgpOb3RlLCB0aGF0CgotIHdpdGggZG91YmxlIGNlbnRlcmVkIGRhdGEgaXQgaXMgbWVhbnQgdGhhdCBib3RoIHRoZSByZXNwb25zZXMgYXJlIGNlbnRlcmVkIChtZWFuIG9mICRcbWF0aGJme1l9JCBpcyB6ZXJvKSBhbmQgdGhhdCBhbGwgcHJlZGljdG9ycyBhcmUgY2VudGVyZWQgKGNvbHVtbnMgb2YgJFxtYXRoYmZ7WH0kIGhhdmUgemVybyBtZWFuKS4gV2l0aCBkb3VibGUgY2VudGVyZWQgZGF0YSB0aGUgaW50ZXJjZXB0IGluIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwgaXMgYWx3YXlzIGV4YWN0bHkgZXF1YWwgdG8gemVybyBhbmQgaGVuY2UgdGhlIGludGVyY2VwdCBtdXN0IG5vdCBiZSBpbmNsdWRlZCBpbiB0aGUgbW9kZWwuCgotIHdlIGRvIG5vdCBhc3N1bWUgdGhhdCB0aGUgcmVzaWR1YWxzIGFyZSBub3JtYWxseSBkaXN0cmlidXRlZC4gRm9yIHByZWRpY3Rpb24gcHVycG9zZXMgdGhpcyBpcyBvZnRlbiBub3QgcmVxdWlyZWQgKG5vcm1hbGl0eSBpcyBwYXJ0aWN1bGFybHkgaW1wb3J0YW50IGZvciBzdGF0aXN0aWNhbCBpbmZlcmVuY2UgaW4gc21hbGwgc2FtcGxlcykuCgojIyBMaW5lYXIgUmVncmVzc2lvbiBmb3IgbXVsdGl2YXJpYXRlIGRhdGEgdnMgSGlnaCBEaW1lbnNpb25hbCBEYXRhCgotICRcbWF0aGJme1heVFh9JCBhbmQgJChcbWF0aGJme1heVFh9KV57LTF9JCBhcmUgJHAgXHRpbWVzIHAkIG1hdHJpY2VzCgotICRcbWF0aGJme1heVFh9JCBjYW4gb25seSBiZSBpbnZlcnRlZCBpZiBpdCBoYXMgcmFuayAkcCQKCi0gUmFuayBvZiBhIG1hdHJpeCBvZiBmb3JtICRcbWF0aGJme1heVFh9JCwgd2l0aCAkXG1hdGhiZntYfSQgYW5kICRuXHRpbWVzIHAkIG1hdHJpeCwgY2FuIG5ldmVyIGJlIGxhcmdlciB0aGFuICRcbWluKG4scCkkLgoKLSBpbiBtb3N0IHJlZ3Jlc3Npb24gcHJvYmxlbXMgJG4+cCQgYW5kIHJhbmsgb2YgJChcbWF0aGJme1heVFh9KSQgZXF1YWxzICRwJAoKLSBpbiBoaWdoIGRpbWVuc2lvbmFsIHJlZ3Jlc3Npb24gcHJvYmxlbXMgJHAgPj4+IG4kIGFuZCByYW5rIG9mICQoXG1hdGhiZntYXlRYfSkkIGVxdWFscyAkbjxwJAoKLSBpbiB0aGUgdG94aWNvZ2Vub21pY3MgZXhhbXBsZSAkbj0zMDxwPTQwMDAkIGFuZCAkXHRleHR7cmFua30oXG1hdGhiZntYXlRYfSlcbGVxIG49MzAkLgogICRcbG9uZ3JpZ2h0YXJyb3ckICQoXG1hdGhiZntYXlRYfSleey0xfSQgZG9lcyBub3QgZXhpc3QsIGFuZCBuZWl0aGVyIGRvZXMgJFxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fSQuCgojIyBDYW4gU1ZEIGhlbHA/CiAgLSBTaW5jZSB0aGUgY29sdW1ucyBvZiAkXG1hdGhiZntYfSQgYXJlIGNlbnRlcmVkLCAkXG1hdGhiZntYXlRYfSBccHJvcHRvIFx0ZXh0e3Zhcn1cbGVmdFtcbWF0aGJme1h9XHJpZ2h0XSQuCgogIC0gaWYgJFx0ZXh0e3Jhbmt9KFxtYXRoYmZ7WF5UWH0pPW49MzAkLCB0aGUgUENBIHdpbGwgZ2l2ZSAzMCBjb21wb25lbnRzLCBlYWNoIGJlaW5nIGEgbGluZWFyIGNvbWJpbmF0aW9uIG9mICRwPTQwMDAkIHZhcmlhYmxlcy4gVGhlc2UgMzAgUENzIGNvbnRhaW4gYWxsIGluZm9ybWF0aW9uIHByZXNlbnQgaW4gdGhlIG9yaWdpbmFsICRcbWF0aGJme1h9JCBkYXRhLgoKICAtIGlmICRcdGV4dHtyYW5rfShcbWF0aGJme1h9KT1uPTMwJCwgdGhlIFNWRCBvZiAkXG1hdGhiZntYfSQgaXMgZ2l2ZW4gYnkKICBcWwogICBcbWF0aGJme1h9ID0gXHN1bV97aT0xfV5uIFxkZWx0YV9pIFxtYXRoYmZ7dX1faSBcbWF0aGJme3Z9X2leVCA9IFxtYXRoYmZ7VX0gXGJvbGRzeW1ib2x7XERlbHRhfSBcbWF0aGJme1Z9XlQgPSBcbWF0aGJme1pWfV5ULAogIFxdCiAgd2l0aCAkXG1hdGhiZntafSQgdGhlICRuXHRpbWVzIG4kIG1hdHJpeCB3aXRoIHRoZSBzY29yZXMgb24gdGhlICRuJCBQQ3MuCgogIC0gU3RpbGwgcHJvYmxlbWF0aWMgYmVjYXVzZSBpZiB3ZSB1c2UgYWxsIFBDcyAkbj1wJC4KCgojIFByaW5jaXBhbCBDb21wb25lbnQgUmVncmVzc2lvbgoKQSBwcmluY2lwYWwgY29tcG9uZW50IHJlZ3Jlc3Npb24gKFBDUikgY29uc2lzdHMgb2YKCjEuIHRyYW5zZm9ybWluZyAkcD00MDAwJCBkaW1lbnNpb25hbCAkWCQtdmFyaWFibGUgdG8gdGhlICRuPTMwJCBkaW1lbnNpb25hbCAkWiQtdmFyaWFibGUgKFBDIHNjb3JlcykuIFRoZSAkbiQgUENzIGFyZSBtdXR1YWxseSB1bmNvcnJlbGF0ZWQuCgoyLiB1c2luZyB0aGUgJG4kIFBDLXZhcmlhYmxlcyBhcyByZWdyZXNzb3JzIGluIGEgbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWwKCjMuIHBlcmZvcm1pbmcgZmVhdHVyZSBzZWxlY3Rpb24gdG8gc2VsZWN0IHRoZSBtb3N0IGltcG9ydGFudCByZWdyZXNzb3JzIChQQykuCgpGZWF0dXJlIHNlbGVjdGlvbiBpcyBrZXksIGJlY2F1c2Ugd2UgZG9uJ3Qgd2FudCB0byBoYXZlIGFzIG1hbnkgcmVncmVzc29ycyBhcyB0aGVyZSBhcmUgb2JzZXJ2YXRpb25zIGluIHRoZSBkYXRhLiBUaGlzIHdvdWxkIHJlc3VsdCBpbiB6ZXJvIHJlc2lkdWFsIGRlZ3JlZXMgb2YgZnJlZWRvbS4gKHNlZSBsYXRlcikKCi0tLQoKVG8ga2VlcCB0aGUgZXhwb3NpdGlvbiBnZW5lcmFsIHNvIHRoYXQgd2UgYWxsb3cgZm9yIGEgZmVhdHVyZSBzZWxlY3Rpb24gdG8gaGF2ZSB0YWtlbiBwbGFjZSwgSSB1c2UgdGhlIG5vdGF0aW9uICRcbWF0aGJme1V9X1MkIHRvIGRlbm90ZSBhIG1hdHJpeCB3aXRoIGxlZnQtc2luZ3VsYXIgY29sdW1uIHZlY3RvcnMgJFxtYXRoYmZ7dX1faSQsIHdpdGggJGkgXGluIHtcbWF0aGNhbHtTfX0kICgke1xtYXRoY2Fse1N9fSQgYW4gaW5kZXggc2V0IHJlZmVycmluZyB0byB0aGUgUENzIHRvIGJlIGluY2x1ZGVkIGluIHRoZSByZWdyZXNzaW9uIG1vZGVsKS4KCkZvciBleGFtcGxlLCBzdXBwb3NlIHRoYXQgYSBmZWF0dXJlIHNlbGVjdGlvbiBtZXRob2QgaGFzIHJlc3VsdGVkIGluIHRoZSBzZWxlY3Rpb24gb2YgUENzIDEsIDMgYW5kIDEyIGZvciBpbmNsdXNpb24gaW4gdGhlIHByZWRpY3Rpb24gbW9kZWwsIHRoZW4gJHtcbWF0aGNhbHtTfX09XHsxLDMsMTJcfSQgYW5kClxbCiBcbWF0aGJme1V9X1MgPSBcYmVnaW57cG1hdHJpeH0KICBcbWF0aGJme3V9XzEgJiBcbWF0aGJme3V9XzMgJiBcbWF0aGJme3V9X3sxMn0KIFxlbmR7cG1hdHJpeH0uClxdCgotLS0KCiMjIyBFeGFtcGxlIG1vZGVsIGJhc2VkIG9uIGZpcnN0IDQgUENzCgpgYGB7cn0KayA8LSAzMApVayA8LSBzdmRYJHVbLDE6a10KRGsgPC0gZGlhZyhzdmRYJGRbMTprXSkKWmsgPC0gVWslKiVEawpZIDwtIHRveERhdGEgJT4lCiAgcHVsbChCQSkKCm00IDwtIGxtKFl+WmtbLDE6NF0pCnN1bW1hcnkobTQpCmBgYAoKTm90ZToKCi0gdGhlIGludGVyY2VwdCBpcyBlc3RpbWF0ZWQgYXMgemVyby4gKFdoeT8pIFRoZSBtb2RlbCBjb3VsZCBoYXZlIGJlZW4gZml0dGVkIGFzCgpgYGAKbTQgPC0gbG0oWX4tMStaa1ssMTo0XSkKYGBgCgotIHRoZSBQQy1wcmVkaWN0b3JzIGFyZSB1bmNvcnJlbGF0ZWQgKGJ5IGNvbnN0cnVjdGlvbikKCi0gZmlyc3QgUEMtcHJlZGljdG9ycyBhcmUgbm90IG5lY2Vzc2FyaWx5IHRoZSBtb3N0IGltcG9ydGFudCBwcmVkaWN0b3JzCgotICRwJC12YWx1ZXMgYXJlIG5vdCB2ZXJ5IG1lYW5pbmdmdWwgd2hlbiBwcmVkaWN0aW9uIGlzIHRoZSBvYmplY3RpdmUKCk1ldGhvZHMgZm9yIGZlYXR1cmUgc2VsZWN0aW9uIHdpbGwgYmUgZGlzY3Vzc2VkIGxhdGVyLgoKIyBSaWRnZSBSZWdyZXNzaW9uCgojIyBQZW5hbHR5CgogVGhlIHJpZGdlIHBhcmFtZXRlciBlc3RpbWF0b3IgaXMgZGVmaW5lZCBhcyB0aGUgcGFyYW1ldGVyICRcbWF0aGJmXGJldGEkIHRoYXQgbWluaW1pc2VzIHRoZSAqKnBlbmFsaXNlZCBsZWFzdCBzcXVhcmVzIGNyaXRlcmlvbioqCgogXFsKIFx0ZXh0e1NTRX1fXHRleHR7cGVufT1cVmVydFxtYXRoYmZ7WX0gLSBcbWF0aGJme1hcYmV0YX1cVmVydF8yXjIgKyBcbGFtYmRhIFxWZXJ0IFxib2xkc3ltYm9se1xiZXRhfSBcVmVydF8yXjIKXF0KCi0gJFxWZXJ0IFxib2xkc3ltYm9se1xiZXRhfSBcVmVydF8yXjI9XHN1bV97aj0xfV5wIFxiZXRhX2peMiQgaXMgdGhlICoqJExfMiQgcGVuYWx0eSB0ZXJtKioKCi0gJFxsYW1iZGE+MCQgaXMgdGhlIHBlbmFsdHkgcGFyYW1ldGVyICh0byBiZSBjaG9zZW4gYnkgdGhlIHVzZXIpLgoKTm90ZSwgdGhhdCB0aGF0IGlzIGVxdWl2YWxlbnQgdG8gbWluaW1pemluZwpcWwpcVmVydFxtYXRoYmZ7WX0gLSBcbWF0aGJme1hcYmV0YX1cVmVydF8yXjIgXHRleHR7IHN1YmplY3QgdG8gfSBcVmVydCBcYm9sZHN5bWJvbHtcYmV0YX1cVmVydF4yXzJcbGVxIHMKXF0KCk5vdGUsIHRoYXQgJHMkIGhhcyBhIG9uZS10by1vbmUgY29ycmVzcG9uZGVuY2Ugd2l0aCAkXGxhbWJkYSQKCiMjIEdyYXBoaWNhbCBpbnRlcnByZXRhdGlvbgoKYGBge3IgZWNobyA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeShnZ2ZvcmNlKQpsaWJyYXJ5KGxhdGV4MmV4cCkKbGlicmFyeShncmlkRXh0cmEpCgpwMSA8LSBnZ3Bsb3QoKSArCiAgZ2VvbV9lbGxpcHNlKGFlcyh4MCA9IDQsIHkwID0gMTEsIGEgPSAxMCwgYiA9IDMsIGFuZ2xlID0gcGkgLyA0KSkgKwogIGdlb21fZWxsaXBzZShhZXMoeDAgPSA0LCB5MCA9IDExLCBhID0gNSwgYiA9IDEuNSwgYW5nbGUgPSBwaSAvIDQpKSArCiAgeGxpbSgtMTIuNSwgMTIuNSkgKwogIHlsaW0oLTUsIDIwKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IDQsIHkgPSAxMSkpICsKICBhbm5vdGF0ZSgidGV4dCIsIGxhYmVsID0gVGVYKCIkKFxcaGF0e1xcYmV0YX1fMV57b2xzfSwgXFxoYXR7XFxiZXRhfV8yXntvbHN9KSQiKSwgeCA9IC01LCB5ID0gMTUsIHNpemUgPSA2LCBwYXJzZSA9IFRSVUUpICsKICB4bGFiKFRlWCgiJFxcYmV0YV8xJCIpKSArCiAgeWxhYihUZVgoIiRcXGJldGFfMiQiKSkgKwogIGdlb21fc2VnbWVudCgKICAgIGFlcyh4ID0gLTUsIHkgPSAxMi41LCB4ZW5kID0gMy43LCB5ZW5kID0gMTEuMyksCiAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMC4yNSwgImNtIikpCiAgICApICsKICBjb29yZF9maXhlZCgpCgpwUmlkZ2UgPC0gcDEgKwogIGdlb21fY2lyY2xlKGFlcyh4MCA9IDAsIHkwID0gMCwgciA9IDMuOSkgLCBjb2xvciA9ICJyZWQiKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IC0xLjEsIHkgPSAzLjc1KSwgY29sb3IgPSAicmVkIikgKwogIGFubm90YXRlKCJ0ZXh0IiwgbGFiZWwgPSBUZVgoIiQoXFxoYXR7XFxiZXRhfV8xXntyaWRnZX0sIFxcaGF0e1xcYmV0YX1fMl57cmlkZ2V9KSQiKSwgeCA9IC04LjEsIHkgPSA0LjQ1LCBzaXplID0gNiwgcGFyc2UgPSBUUlVFLCBjb2xvciA9ICJyZWQiKSArCiAgZ2d0aXRsZSgiUmlkZ2UiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAiZ3JleSIpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJncmV5IikgKwogIHRoZW1lX21pbmltYWwoKQoKcFJpZGdlCmBgYAoKIyMgU29sdXRpb24KClRoZSBzb2x1dGlvbiBpcyBnaXZlbiBieQpcWwogIFxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fSA9IChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfSBcbWF0aGJme1heVCBZfS4KXF0KSXQgY2FuIGJlIHNob3duIHRoYXQgJChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSkkIGlzIGFsd2F5cyBvZiByYW5rICRwJCBpZiAkXGxhbWJkYT4wJC4KCkhlbmNlLCAkKFxtYXRoYmZ7WF5UWH0rXGxhbWJkYSBcbWF0aGJme0l9KSQgaXMgaW52ZXJ0aWJsZSBhbmQgJFxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fSQgZXhpc3RzIGV2ZW4gaWYgJHA+Pj5uJC4KCldlIGFsc28gZmluZApcWwogIFx0ZXh0e3Zhcn1cbGVmdFtcaGF0e1xtYXRoYmZcYmV0YX1ccmlnaHRdID0gKFxtYXRoYmZ7WF5UWH0rXGxhbWJkYSBcbWF0aGJme0l9KV57LTF9IFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0gKFxtYXRoYmZ7WF5UWH0rXGxhbWJkYSBcbWF0aGJme0l9KV57LTF9XHNpZ21hXjIKXF0KCkhvd2V2ZXIsIGl0IGNhbiBiZSBzaG93biB0aGF0IGltcHJvdmVkIGludGVydmFscyB0aGF0IGFsc28gYWNjb3VudCBmb3IgdGhlIGJpYXMgY2FuIGJlIGNvbnN0cnVjdGVkIGJ5IHVzaW5nOgoKXFsKICBcdGV4dHt2YXJ9XGxlZnRbXGhhdHtcbWF0aGJmXGJldGF9XHJpZ2h0XSA9IChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfSAgXHNpZ21hXjIuClxdCgojIyMgUHJvb2YKClRoZSBjcml0ZXJpb24gdG8gYmUgbWluaW1pc2VkIGlzCiAgXFsKICAgXHRleHR7U1NFfV9cdGV4dHtwZW59PVxWZXJ0XG1hdGhiZntZfSAtIFxtYXRoYmZ7WFxiZXRhfVxWZXJ0XzJeMiArIFxsYW1iZGEgXFZlcnQgXGJvbGRzeW1ib2x7XGJldGF9IFxWZXJ0XzJeMi4KIFxdCiBGaXJzdCB3ZSByZS1leHByZXNzIFNTRSBpbiBtYXRyaXggbm90YXRpb246CiBcWwogICBcdGV4dHtTU0V9X1x0ZXh0e3Blbn0gPSAoXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX0pXlQoXG1hdGhiZntZfS1cbWF0aGJme1hcYmV0YX0pICsgXGxhbWJkYSBcYm9sZHN5bWJvbHtcYmV0YX1eVFxib2xkc3ltYm9se1xiZXRhfS4KIFxdCiBUaGUgcGFydGlhbCBkZXJpdmF0aXZlIHcuci50LiAkXGJvbGRzeW1ib2x7XGJldGF9JCBpcwogXFsKICAgXGZyYWN7XHBhcnRpYWx9e1xwYXJ0aWFsIFxib2xkc3ltYm9se1xiZXRhfX1cdGV4dHtTU0V9X1x0ZXh0e3Blbn0gPSAtMlxtYXRoYmZ7WH1eVChcbWF0aGJme1l9LVxtYXRoYmZ7WFxiZXRhfSkrMlxsYW1iZGFcYm9sZHN5bWJvbHtcYmV0YX0uCiBcXQogU29sdmluZyAkXGZyYWN7XHBhcnRpYWx9e1xwYXJ0aWFsIFxib2xkc3ltYm9se1xiZXRhfX1cdGV4dHtTU0V9X1x0ZXh0e3Blbn09MCQgZ2l2ZXMKIFxbCiAgIFxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fSA9IChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfSBcbWF0aGJme1heVCBZfS4KIFxdCiAoYXNzdW1wdGlvbjogJChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSkkIGlzIG9mIHJhbmsgJHAkLiBUaGlzIGlzIGFsd2F5cyB0cnVlIGlmICRcbGFtYmRhPjAkKQoKIyMgTGluayB3aXRoIFNWRAoKIyMjIFNWRCBhbmQgaW52ZXJzZQpXcml0ZSB0aGUgU1ZEIG9mICRcbWF0aGJme1h9JCAoJHA+biQpIGFzClxbCiAgIFxtYXRoYmZ7WH0gPSBcc3VtX3tpPTF9Xm4gXGRlbHRhX2kgXG1hdGhiZnt1fV9pIFxtYXRoYmZ7dn1faV5UID0gXHN1bV97aT0xfV5wIFxkZWx0YV9pIFxtYXRoYmZ7dX1faSBcbWF0aGJme3Z9X2leVCAgPSBcbWF0aGJme1V9XGJvbGRzeW1ib2x7XERlbHRhfSBcbWF0aGJme1Z9XlQgLApcXQp3aXRoCgotICRcZGVsdGFfe24rMX09XGRlbHRhX3tuKzJ9PSBcY2RvdHMgPSBcZGVsdGFfcD0wJAoKLSAkXGJvbGRzeW1ib2x7XERlbHRhfSQgYSAkcFx0aW1lcyBwJCBkaWFnb25hbCBtYXRyaXggb2YgdGhlICRcZGVsdGFfMSxcbGRvdHMsIFxkZWx0YV9wJAoKLSAgJFxtYXRoYmZ7VX0kIGFuICRuXHRpbWVzIHAkIG1hdHJpeCBhbmQgJFxtYXRoYmZ7Vn0kIGEgJHAgXHRpbWVzIHAkIG1hdHJpeC4gTm90ZSB0aGF0IG9ubHkgdGhlIGZpcnN0ICRuJCBjb2x1bW5zIG9mICRcbWF0aGJme1V9JCBhbmQgJFxtYXRoYmZ7Vn0kIGFyZSBpbmZvcm1hdGl2ZS4KCldpdGggdGhlIFNWRCBvZiAkXG1hdGhiZntYfSQgd2Ugd3JpdGUKIFxbCiAgIFxtYXRoYmZ7WH1eVFxtYXRoYmZ7WH0gPSBcbWF0aGJme1Z9XGJvbGRzeW1ib2x7XERlbHRhCiAgICAgfV4yXG1hdGhiZntWfV5ULgogXF0KIFRoZSBpbnZlcnNlIG9mICRcbWF0aGJme1h9XlRcbWF0aGJme1h9JCBpcyB0aGVuIGdpdmVuIGJ5CiBcWwogICAoXG1hdGhiZntYfV5UXG1hdGhiZntYfSleey0xfSA9IFxtYXRoYmZ7Vn1cYm9sZHN5bWJvbHtcRGVsdGF9XnstMn1cbWF0aGJme1Z9XlQuCiBcXQogU2luY2UgJFxib2xkc3ltYm9se1xEZWx0YX0kIGhhcyAkXGRlbHRhX3tuKzF9PVxkZWx0YV97bisyfT0gXGNkb3RzID0gXGRlbHRhX3A9MCQsIGl0IGlzIG5vdCBpbnZlcnRpYmxlLgoKIyMjIFNWRCBvZiBwZW5hbGlzZWQgbWF0cml4ICRcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSQKCkl0IGNhbiBiZSBzaG93biB0aGF0ClxbCiAgXG1hdGhiZntYXlRYfStcbGFtYmRhIFxtYXRoYmZ7SX0gPSBcbWF0aGJme1Z9IChcYm9sZHN5bWJvbHtcRGVsdGF9XjIrXGxhbWJkYSBcbWF0aGJme0l9KSBcbWF0aGJme1Z9XlQgLApcXQppLmUuIGFkZGluZyBhIGNvbnN0YW50IHRvIHRoZSBkaWFnb25hbCBlbGVtZW50cyBkb2VzIG5vdCBhZmZlY3QgdGhlIGVpZ2VudmVjdG9ycywgYW5kIGFsbCBlaWdlbnZhbHVlcyBhcmUgaW5jcmVhc2VkIGJ5IHRoaXMgY29uc3RhbnQuCiRcbG9uZ3JpZ2h0YXJyb3ckIHplcm8gZWlnZW52YWx1ZXMgYmVjb21lICRcbGFtYmRhJC4KCkhlbmNlLApcWwogIChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfSA9IFxtYXRoYmZ7Vn0gKFxib2xkc3ltYm9se1xEZWx0YX1eMitcbGFtYmRhIFxtYXRoYmZ7SX0pXnstMX0gXG1hdGhiZntWfV5UICwKXF0Kd2hpY2ggY2FuIGJlIGNvbXB1dGVkIGV2ZW4gd2hlbiBzb21lIGVpZ2VudmFsdWVzIGluICRcYm9sZHN5bWJvbHtcRGVsdGF9XjIkIGFyZSB6ZXJvLgoKTm90ZSwgdGhhdCBmb3IgaGlnaCBkaW1lbnNpb25hbCBkYXRhICgkcD4+Pm4kKSBtYW55IGVpZ2VudmFsdWVzIGFyZSB6ZXJvIGJlY2F1c2UgJFxtYXRoYmZ7WF5UWH0kIGlzIGEgJHAgXHRpbWVzIHAkIG1hdHJpeCBhbmQgaGFzIHJhbmsgJG4kLgoKVGhlIGlkZW50aXR5ICRcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSA9IFxtYXRoYmZ7Vn0gKFxib2xkc3ltYm9se1xEZWx0YX1eMitcbGFtYmRhIFxtYXRoYmZ7SX0pIFxtYXRoYmZ7Vn1eVCQgaXMgZWFzaWx5IGNoZWNrZWQ6ClxbCiAgXG1hdGhiZntWfSAoXGJvbGRzeW1ib2x7XERlbHRhfV4yK1xsYW1iZGEgXG1hdGhiZntJfSkgXG1hdGhiZntWfV5UID0gXG1hdGhiZntWfVxib2xkc3ltYm9se1xEZWx0YX1eMlxtYXRoYmZ7Vn1eVCArIFxsYW1iZGEgXG1hdGhiZntWVn1eVCAgPSBcbWF0aGJme1Z9XGJvbGRzeW1ib2x7XERlbHRhfV4yXG1hdGhiZntWfV5UICsgXGxhbWJkYSBcbWF0aGJme0l9ID0gXG1hdGhiZntYXlRYfStcbGFtYmRhIFxtYXRoYmZ7SX0uClxdCgoKIyMgUHJvcGVydGllcwoKLSBUaGUgUmlkZ2UgZXN0aW1hdG9yIGlzIGJpYXNlZCEgVGhlICRcYm9sZHN5bWJvbHtcYmV0YX0kIGFyZSBzaHJ1bmtlbiB0byB6ZXJvIQpcYmVnaW57ZXFuYXJyYXl9CiBcdGV4dHtFfVtcaGF0e1xib2xkc3ltYm9se1xiZXRhfX1dICY9JiAoXG1hdGhiZntYXlRYfStcbGFtYmRhIFxtYXRoYmZ7SX0pXnstMX0gXG1hdGhiZntYfV5UIFx0ZXh0e0V9W1xtYXRoYmZ7WX1dXFwKJj0mIChcbWF0aGJme1h9XlRcbWF0aGJme1h9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfSBcbWF0aGJme1h9XlQgXG1hdGhiZntYfVxib2xkc3ltYm9se1xiZXRhfVxcClxlbmR7ZXFuYXJyYXl9CgotIE5vdGUsIHRoYXQgdGhlIHNocmlua2FnZSBpcyBsYXJnZXIgaW4gdGhlIGRpcmVjdGlvbiBvZiB0aGUgc21hbGxlciBlaWdlbnZhbHVlcy4KClxiZWdpbntlcW5hcnJheX0KXHRleHR7RX1bXGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19XSY9JlxtYXRoYmZ7Vn0gKFxib2xkc3ltYm9se1xEZWx0YX1eMitcbGFtYmRhIFxtYXRoYmZ7SX0pXnstMX0gXG1hdGhiZntWfV5UIFxtYXRoYmZ7Vn0gXGJvbGRzeW1ib2x7XERlbHRhfV4yIFxtYXRoYmZ7Vn1eVFxib2xkc3ltYm9se1xiZXRhfVxcCiY9JlxtYXRoYmZ7Vn0gKFxib2xkc3ltYm9se1xEZWx0YX1eMitcbGFtYmRhIFxtYXRoYmZ7SX0pXnstMX0gXGJvbGRzeW1ib2x7XERlbHRhfV4yIFxtYXRoYmZ7Vn1eVFxib2xkc3ltYm9se1xiZXRhfVxcCiY9JiBcbWF0aGJme1Z9ClxsZWZ0W1xiZWdpbnthcnJheX17Y2NjfQpcZnJhY3tcZGVsdGFfMV4yfXtcZGVsdGFfMV4yK1xsYW1iZGF9JlxsZG90cyYwIFxcCiZcdmRvdHMmXFwKMCZcbGRvdHMmXGZyYWN7XGRlbHRhX3JeMn17XGRlbHRhX3JeMitcbGFtYmRhfQpcZW5ke2FycmF5fVxyaWdodF0KXG1hdGhiZntWfV5UXGJvbGRzeW1ib2x7XGJldGF9ClxlbmR7ZXFuYXJyYXl9CgotICB0aGUgdmFyaWFuY2Ugb2YgdGhlIHByZWRpY3Rpb24gJFxoYXR7e1l9fShcbWF0aGJme3h9KT1cbWF0aGJme3h9XlRcaGF0XGJldGEkLAogIFxbCiAgICBcdGV4dHt2YXJ9XGxlZnRbXGhhdHt7WX19KFxtYXRoYmZ7eH0pXG1pZCBcbWF0aGJme3h9XHJpZ2h0XSA9IFxtYXRoYmZ7eH1eVChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfVxtYXRoYmZ7eH0KICBcXQogIGlzIHNtYWxsZXIgdGhhbiB3aXRoIHRoZSBsZWFzdC1zcXVhcmVzIGVzdGltYXRvci4KCi0gIHRocm91Z2ggdGhlIGJpYXMtdmFyaWFuY2UgdHJhZGUtb2ZmIGl0IGlzIGhvcGVkIHRoYXQgYmV0dGVyIHByZWRpY3Rpb25zIGluIHRlcm1zIG9mIGV4cGVjdGVkIGNvbmRpdGlvbmFsIHRlc3QgZXJyb3IgY2FuIGJlIG9idGFpbmVkLCBmb3IgYW4gYXBwcm9wcmlhdGUgY2hvaWNlIG9mICRcbGFtYmRhJC4KCgpSZWNhbGwgdGhlIGV4cHJlc3Npb24gb2YgdGhlIGV4cGVjdGVkIGNvbmRpdGlvbmFsIHRlc3QgZXJyb3IKXGJlZ2lue2VxbmFycmF5fQogIEVycihcbWF0aGJme3h9KSAmPSYgXHRleHR7RX1cbGVmdFsoXGhhdHtZfSAtIFleKileMlxtaWQgXG1hdGhiZnt4fVxyaWdodF1cXAogICY9JgogIFx0ZXh0e3Zhcn1cbGVmdFtcaGF0e1l9XG1pZCBcbWF0aGJme3h9XHJpZ2h0XSArIFx0ZXh0e2JpYXN9XjIoXG1hdGhiZnt4fSkrCiAgXHRleHR7dmFyfVxsZWZ0W1leKlxtaWQgXG1hdGhiZnt4fVxyaWdodF0KXGVuZHtlcW5hcnJheX0Kd2hlcmUKCi0gJFxoYXR7WX09XGhhdHtZfShcbWF0aGJme3h9KT1cbWF0aGJme3h9XlRcaGF0e1xib2xkc3ltYm9se1xiZXRhfX0kIGlzIHRoZSBwcmVkaWN0aW9uIGF0ICRcbWF0aGJme3h9JAotICRZXiokIGlzIGFuIG91dGNvbWUgYXQgcHJlZGljdG9yICRcbWF0aGJme3h9JAotICRcbXUoXG1hdGhiZnt4fSkgPSBcdGV4dHtFfVxsZWZ0W1xoYXR7WX1cbWlkIFxtYXRoYmZ7eH1ccmlnaHRdIFx0ZXh0eyBhbmQgfSBcbXVeKih4KT1cdGV4dHtFfVxsZWZ0W1leKlxtaWQgXG1hdGhiZnt4fVxyaWdodF0kCi0gJFx0ZXh0e2JpYXN9KFxtYXRoYmZ7eH0pPVxtdShcbWF0aGJme3h9KS1cbXVeKihcbWF0aGJme3h9KSQKLSAkXHRleHR7dmFyfVxsZWZ0W1leKlxtaWQgXG1hdGhiZnt4fVxyaWdodF0kIHRoZSBpcnJlZHVjaWJsZSBlcnJvciB0aGF0IGRvZXMgbm90IGRlcGVuZCBvbiB0aGUgbW9kZWwuIEl0IHNpbXBseSBvcmlnaW5hdGVzIGZyb20gb2JzZXJ2YXRpb25zIHRoYXQgcmFuZG9tbHkgZmx1Y3R1YXRlIGFyb3VuZCB0aGUgdHJ1ZSBtZWFuICRcbXVeKih4KSQuCgojIyBUb3hpY29nZW5vbWljcyBleGFtcGxlCgpgYGB7cn0KbGlicmFyeShnbG1uZXQpCm1SaWRnZSA8LSBnbG1uZXQoCiAgeCA9IHRveERhdGFbLC0xXSAlPiUKICAgIGFzLm1hdHJpeCwKICB5ID0gdG94RGF0YSAlPiUKICAgIHB1bGwoQkEpLAogIGFscGhhID0gMCkgIyByaWRnZTogYWxwaGEgPSAwCgpwbG90KG1SaWRnZSwgeHZhcj0ibGFtYmRhIikKYGBgCgoKVGhlIFIgZnVuY3Rpb24gXHRleHRzZntnbG1uZXR9IHVzZXMgXHRleHRzZntsYW1iZGF9IHRvIHJlZmVyIHRvIHRoZSBwZW5hbHR5IHBhcmFtZXRlci4gSW4gdGhpcyBjb3Vyc2Ugd2UgdXNlICRcbGFtYmRhJCwgYmVjYXVzZSAkXGxhbWJkYSQgaXMgb2Z0ZW4gdXNlZCBhcyBlaWdlbnZhbHVlcy4KClRoZSBncmFwaCBzaG93cyB0aGF0IHdpdGggaW5jcmVhc2luZyBwZW5hbHR5IHBhcmFtZXRlciwgdGhlIHBhcmFtZXRlciBlc3RpbWF0ZXMgYXJlIHNocnVua2VuIHRvd2FyZHMgemVyby4gVGhlIGVzdGltYXRlcyB3aWxsIG9ubHkgcmVhY2ggemVybyBmb3IgJFxsYW1iZGEgXHJpZ2h0YXJyb3cgXGluZnR5JC4gVGhlIHN0cm9uZ2VyIHRoZSBzaHJpbmthZ2UsIHRoZSBsYXJnZXIgdGhlIGJpYXMgKHRvd2FyZHMgemVybykgYW5kIHRoZSBzbWFsbGVyIHRoZSB2YXJpYW5jZSBvZiB0aGUgcGFyYW1ldGVyIGVzdGltYXRvcnMgKGFuZCBoZW5jZSBhbHNvIHNtYWxsZXIgdmFyaWFuY2Ugb2YgdGhlIHByZWRpY3Rpb25zKS4KCkFub3RoZXIgKGluZm9ybWFsKSB2aWV3cG9pbnQgaXMgdGhlIGZvbGxvd2luZy4gQnkgc2hyaW5raW5nIHRoZSBlc3RpbWF0ZXMgdG93YXJkcyB6ZXJvLCB0aGUgZXN0aW1hdGVzIGxvb3NlIHNvbWUgb2YgdGhlaXIgYGBkZWdyZWVzIG9mIGZyZWVkb20nJyBzbyB0aGF0IHRoZSBwYXJhbWV0ZXJzIGJlY29tZSBlc3RpbWFibGUgd2l0aCBvbmx5ICRuPHAkIGRhdGEgcG9pbnRzLiBFdmVuIHdpdGggYSB2ZXJ5IHNtYWxsICRcbGFtYmRhPjAkLCB0aGUgcGFyYW1ldGVycyByZWdhaW4gdGhlaXIgZXN0aW1hYmlsaXR5LiBIb3dldmVyLCBub3RlIHRoYXQgdGhlIHZhcmlhbmNlIG9mIHRoZSBlc3RpbWF0b3IgaXMgZ2l2ZW4gYnkKXFsKICBcdGV4dHt2YXJ9XGxlZnRbXGhhdHtcbWF0aGJmXGJldGF9XHJpZ2h0XSA9IChcbWF0aGJme1heVFh9K1xsYW1iZGEgXG1hdGhiZntJfSleey0xfSBcc2lnbWFeMiA9IFxtYXRoYmZ7Vn0oXGJvbGRzeW1ib2x7XERlbHRhfV4yK1xsYW1iZGFcbWF0aGJme0l9KV57LTF9XG1hdGhiZntWfV5UXHNpZ21hXjIuClxdCkhlbmNlLCBhIHNtYWxsICRcbGFtYmRhJCB3aWxsIHJlc3VsdCBpbiBsYXJnZSB2YXJpYW5jZXMgb2YgdGhlIHBhcmFtZXRlciBlc3RpbWF0b3JzLiBUaGUgbGFyZ2VyICRcbGFtYmRhJCwgdGhlIHNtYWxsZXIgdGhlIHZhcmlhbmNlcyBiZWNvbWUuIEluIHRoZSBsaW1pdCwgYXMgJFxsYW1iZGFccmlnaHRhcnJvd1xpbmZ0eSQsIHRoZSBlc3RpbWF0ZXMgYXJlIGNvbnZlcmdlZCB0byB6ZXJvIGFuZCBzaG93IG5vIHZhcmlhYmlsaXR5IGFueSBsb25nZXIuCgojIExhc3NvIFJlZ3Jlc3Npb24KCi0gVGhlIExhc3NvIGlzIGFub3RoZXIgZXhhbXBsZSBvZiBwZW5hbGlzZWQgcmVncmVzc2lvbi4KCi0gVGhlIGxhc3NvIGVzdGltYXRvciBvZiAkXGJvbGRzeW1ib2x7XGJldGF9JCBpcyB0aGUgc29sdXRpb24gdG8gbWluaW1pc2luZyB0aGUgcGVuYWxpc2VkIFNTRQpcWwogXHRleHR7U1NFfV9cdGV4dHtwZW59ID0gXHN1bV97aT0xfV5uIChZX2kgLSBcbWF0aGJme3h9X2leVFxib2xkc3ltYm9se1xiZXRhfSleMiArIFxsYW1iZGEgXHN1bV97aj0xfV5wIFx2ZXJ0IFxiZXRhX2pcdmVydC4KXF0KCgpvciwgZXF1aXZhbGVudGx5LCBtaW5pbWlzaW5nCgpcWwpcdGV4dHtTU0V9ICA9IFxWZXJ0IFxtYXRoYmZ7WX0gLSBcbWF0aGJme1hcYmV0YX1cVmVydF8yXjIgXHRleHR7IHN1YmplY3QgdG8gfSBcVmVydCBcbWF0aGJmXGJldGFcVmVydF8xIFxsZXEgYwpcXQp3aXRoCgotICRcVmVydCBcbWF0aGJmXGJldGFcVmVydF8xID0gXHN1bVxsaW1pdHNfe2o9MX1ecCBcdmVydCBcYmV0YV9qIFx2ZXJ0JAoKLSBEZXNwaXRlIHN0cm9uZyBzaW1pbGFyaXR5IGJldHdlZW4gcmlkZ2UgYW5kIGxhc3NvIHJlZ3Jlc3Npb24gKCRMXzIkIHZlcnN1cyAkTF8xJCBub3JtIGluIHBlbmFsdHkgdGVybSksIHRoZXJlIGlzIG5vIGFuYWx5dGljYWwgc29sdXRpb24gb2YgdGhlIGxhc3NvIHBhcmFtZXRlciBlc3RpbWF0b3Igb2YgJFxtYXRoYmZcYmV0YSQuCgotIEZvcnR1bmF0ZWx5LCBjb21wdXRhdGlvbmFsIGVmZmljaWVudCBhbGdvcml0aG1zIGhhdmUgYmVlbiBpbXBsZW1lbnRlZCBpbiBzdGF0aXN0aWNhbCBzb2Z0d2FyZQoKLSBUaGUgTGFzc28gZXN0aW1hdG9yIG9mICRcYm9sZHN5bWJvbHtcYmV0YX0kIGlzIGJpYXNlZCBhbmQgZ2VuZXJhbGx5IGhhcyBhIHNtYWxsZXIgdmFyaWFuY2UgdGhlbiB0aGUgbGVhc3Qtc3F1YXJlcyBlc3RpbWF0b3IuCgotIEhlbmNlLCB0aGUgYmlhcy12YXJpYW5jZSB0cmFkZS1vZmYgbWF5IGhlcmUgYWxzbyBoZWxwIGluIGZpbmRpbmcgYmV0dGVyIHByZWRpY3Rpb25zIHdpdGggYmlhc2VkIGVzdGltYXRvcnMuCgotIEluIGNvbnRyYXN0IHRvIHJpZGdlIHJlZ3Jlc3Npb24sIGhvd2V2ZXIsIHRoZSBsYXNzbyBlc3RpbWF0b3IgY2FuIGdpdmUgYXQgbW9zdCAkXG1pbihwLG4pJCBub24temVybyAkXGJldGEkLWVzdGltYXRlcy4KCi0gSGVuY2UsIGF0IGZpcnN0IHNpZ2h0IHRoZSBsYXNzbyBpcyBub3QgZGlyZWN0bHkgYXBwcm9wcmlhdGUgZm9yIGhpZ2gtZGltZW5zaW9uYWwgc2V0dGluZ3MuCgotIEFuIGltcG9ydGFudCBhZHZhbnRhZ2Ugb2YgdGhlIGxhc3NvIGlzIHRoYXQgY2hvb3NpbmcgYW4gYXBwcm9wcmlhdGUgdmFsdWUgZm9yICRcbGFtYmRhJCBpcyBhIGtpbmQgYSBtb2RlbCBidWlsZGluZyBvciBmZWF0dXJlIHNlbGVjdGlvbiBwcm9jZWR1cmUgKHNlZSBmdXJ0aGVyKS4KCiMjIEdyYXBoaWNhbCBpbnRlcnByZXRhdGlvbiBvZiBMYXNzbyB2cyByaWRnZQoKTm90ZSB0aGF0IHRoZSBsYXNzbyBpcyBhIGNvbnN0cmFpbmVkIHJlZ3Jlc3Npb24gcHJvYmxlbSB3aXRoCgpcWwpcVmVydCBcbWF0aGJme1l9IC0gXG1hdGhiZntYXGJldGF9XFZlcnRfMl4yIFx0ZXh0eyBzdWJqZWN0IHRvIH0gXFZlcnQgXG1hdGhiZlxiZXRhXFZlcnRfMSBcbGVxIGMKXF0KYW5kIHJpZGdlClxbClxWZXJ0IFxtYXRoYmZ7WX0gLSBcbWF0aGJme1hcYmV0YX1cVmVydF8yXjIgXHRleHR7IHN1YmplY3QgdG8gfSBcVmVydCBcbWF0aGJmXGJldGFcVmVydF4yXzIgXGxlcSBjClxdCgpgYGB7ciBlY2hvID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFfQpwTGFzc28gPC0gcDEgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDAsIHkgPSA0LjIgLCB4ZW5kID0gNC4yLCB5ZW5kID0gMCksIGNvbG9yID0gInJlZCIpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSAwLCB5ID0gNC4yICwgeGVuZCA9IC0gNC4yLCB5ZW5kID0gMCksIGNvbG9yID0gInJlZCIpICsKICBnZW9tX3NlZ21lbnQoYWVzKHggPSA0LjIsIHkgPSAwICwgeGVuZCA9IDAsIHllbmQgPSAtNC4yKSwgY29sb3IgPSAicmVkIikgKwogIGdlb21fc2VnbWVudChhZXMoeCA9IDAsIHkgPSAtIDQuMiAsIHhlbmQgPSAtIDQuMiwgeWVuZCA9IDApLCBjb2xvciA9ICJyZWQiKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IDAsIHkgPSA0LjIpLCBjb2xvciA9ICJyZWQiKSArCiAgYW5ub3RhdGUoInRleHQiLCBsYWJlbCA9IFRlWCgiJChcXGhhdHtcXGJldGF9XzFee2xhc3NvfSwgXFxoYXR7XFxiZXRhfV8yXntsYXNzb30pJCIpLCB4ID0gNywgeSA9IDQuMiwgc2l6ZSA9IDYsIHBhcnNlID0gVFJVRSwgY29sb3IgPSAicmVkIikgKwogIGdndGl0bGUoIkxhc3NvIikgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDAsIGNvbG9yID0gImdyZXkiKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAiZ3JleSIpICsKICB0aGVtZV9taW5pbWFsKCkKCmdyaWQuYXJyYW5nZShwTGFzc28sIHBSaWRnZSwgbmNvbCA9IDIpCmBgYAoKTm90ZSwgdGhhdAoKLSBwYXJhbWV0ZXJzIGZvciB0aGUgbGFzc28gY2FuIG5ldmVyIHN3aXRjaCBzaWduLCB0aGV5IGFyZSBzZXQgYXQgemVybyEgU2VsZWN0aW9uIQotIHJpZGdlIHJlZ3Jlc3Npb24gY2FuIGxlYWQgdG8gcGFyYW1ldGVycyB0aGF0IHN3aXRjaCBzaWduLgoKIyMgVG94aWNvZ2Vub21pY3MgZXhhbXBsZQoKYGBge3J9Cm1MYXNzbyA8LSBnbG1uZXQoCiAgeCA9IHRveERhdGFbLC0xXSAlPiUKICAgIGFzLm1hdHJpeCwKICB5ID0gdG94RGF0YSAlPiUKICAgIHB1bGwoQkEpLAphbHBoYSA9IDEpCnBsb3QobUxhc3NvLCB4dmFyID0gImxhbWJkYSIpCmBgYAoKLSBUaGUgZ3JhcGggd2l0aCB0aGUgcGF0aHMgb2YgdGhlIHBhcmFtZXRlciBlc3RpbWF0ZXMgbmljZWx5IGlsbHVzdHJhdGVzIHRoZSB0eXBpY2FsIGJlaGF2aW91ciBvZiB0aGUgbGFzc28gZXN0aW1hdGVzIGFzIGEgZnVuY3Rpb24gb2YgJFxsYW1iZGEkOiB3aGVuICRcbGFtYmRhJCBpbmNyZWFzZXMgdGhlIGVzdGltYXRlcyBhcmUgc2hydW5rZW4gdG93YXJkcyB6ZXJvLgoKLSBXaGVuIGFuIGVzdGltYXRlIGhpdHMgemVybywgaXQgcmVtYWlucyBleGFjdGx5IGVxdWFsIHRvIHplcm8gd2hlbiAkXGdhbW1hJCBmdXJ0aGVyIGluY3JlYXNlcy4gQSBwYXJhbWV0ZXIgZXN0aW1hdGUgZXF1YWwgdG8gemVybywgc2F5ICRcaGF0XGJldGFfaj0wJCwgaW1wbGllcyB0aGF0IHRoZSBjb3JyZXNwb25kaW5nIHByZWRpY3RvciAkeF9qJCBpcyBubyBsb25nZXIgaW5jbHVkZWQgaW4gdGhlIG1vZGVsIChpLmUuICRcYmV0YV9qeF9qPTAkKS4KCi0gVGhlIG1vZGVsIGZpdCBpcyBrbm93biBhcyBhIHNwYXJzZSBtb2RlbCBmaXQgKG1hbnkgemVyb2VzKS4gSGVuY2UsIGNob29zaW5nIGEgYXBwcm9wcmlhdGUgdmFsdWUgZm9yICRcZ2FtbWEkIGlzIGxpa2UgY2hvb3NpbmcgdGhlIGltcG9ydGFudCBwcmVkaWN0b3JzIGluIHRoZSBtb2RlbCAoZmVhdHVyZSBzZWxlY3Rpb24pLgoKCiMgU3BsaW5lcyBhbmQgdGhlIGNvbm5lY3Rpb24gdG8gcmlkZ2UgcmVncmVzc2lvbi4KCiMjIExpZGFyIGRhdGFzZXQKCi0gTElEQVIgKGxpZ2h0IGRldGVjdGlvbiBhbmQgcmFuZ2luZykgdXNlcyB0aGUgcmVmbGVjdGlvbiBvZiBsYXNlci1lbWl0dGVkIGxpZ2h0IHRvIGRldGVjdCBjaGVtaWNhbCBjb21wb3VuZHMgaW4gdGhlIGF0bW9zcGhlcmUuCi0gVGhlIExJREFSIHRlY2huaXF1ZSBoYXMgcHJvdmVuIHRvIGJlIGFuIGVmZmljaWVudCB0b29sIGZvciBtb25pdG9yaW5nIHRoZSBkaXN0cmlidXRpb24gb2Ygc2V2ZXJhbCBhdG1vc3BoZXJpYyBwb2xsdXRhbnRzIG9mIGltcG9ydGFuY2U7IHNlZSBTaWdyaXN0ICgxOTk0KS4KLSBUaGUgcmFuZ2UgaXMgdGhlIGRpc3RhbmNlIHRyYXZlbGVkIGJlZm9yZSB0aGUgbGlnaHQgaXMgcmVmbGVjdGVkIGJhY2sgdG8gaXRzIHNvdXJjZS4KLSBUaGUgbG9ncmF0aW8gaXMgdGhlIGxvZ2FyaXRobSBvZiB0aGUgcmF0aW8gb2YgcmVjZWl2ZWQgbGlnaHQgZnJvbSB0d28gbGFzZXIgc291cmNlcy4KCiAgLSBPbmUgc291cmNlIGhhZCBhIGZyZXF1ZW5jeSBlcXVhbCB0byB0aGUgcmVzb25hbmNlIGZyZXF1ZW5jeSBvZiB0aGUgY29tcG91bmQgb2YgaW50ZXJlc3QsIHdoaWNoIHdhcyBtZXJjdXJ5IGluIHRoaXMgc3R1ZHkuCiAgLSBUaGUgb3RoZXIgc291cmNlIGhhZCBhIGZyZXF1ZW5jeSBvZmYgdGhpcyByZXNvbmFuY2UgZnJlcXVlbmN5LgoKICAtIFRoZSBjb25jZW50cmF0aW9uIG9mIG1lcmN1cnkgY2FuIGJlIGRlcml2ZWQgZnJvbSBhIHJlZ3Jlc3Npb24gbW9kZWwgb2YgdGhlIGxvZ3JhdGlvIGluIGZ1bmN0aW9uIG9mICB0aGUgcmFuZ2UgZm9yIGVhY2ggcmFuZ2UgeC4KCmBgYHtyfQpsaWJyYXJ5KCJTZW1pUGFyIikKZGF0YShsaWRhcikKcExpZGFyIDwtIGxpZGFyICU+JQogIGdncGxvdChhZXMoeCA9IHJhbmdlLCB5ID0gbG9ncmF0aW8pKSArCiAgZ2VvbV9wb2ludCgpICsKICB4bGFiKCJyYW5nZSAobSkiKQoKcExpZGFyICsKICBnZW9tX3Ntb290aCgpCmBgYAoKLSBUaGUgZGF0YSBpcyBub24tbGluZWFyCi0gTGluZWFyIHJlZ3Jlc3Npb24gd2lsbCBub3Qgd29yayEKLSBUaGUgZGF0YSBzaG93cyBhIHNtb290aCByZWxhdGlvbiBiZXR3ZWVuIHRoZSBsb2dyYXRpbyBhbmQgdGhlIHJhbmdlCgojIyBCYXNpcyBleHBhbnNpb24KClxbeV9pPWYoeF9pKStcZXBzaWxvbl9pLFxdCndpdGgKXFtmKHgpPVxzdW1cbGltaXRzX3trPTF9XksgXHRoZXRhX2sgYl9rKHgpXF0KCi0gIFNlbGVjdCBzZXQgb2YgYmFzaXMgZnVuY3Rpb25zICRiX2soeCkkCi0gIFNlbGVjdCBudW1iZXIgb2YgYmFzaXMgZnVuY3Rpb25zICRLJAotICBFeGFtcGxlcwoKICAgIC0gIFBvbHlub21pYWwgbW9kZWw6ICR4XmskCiAgICAtICBPcnRob2dvbmFsIHNlcmllczogRm91cmllciwgTGVnZW5kcmUgcG9seW5vbWlhbHMsIFdhdmVsZXRzCiAgICAtICBQb2x5bm9taWFsIHNwbGluZXM6ICQxLCB4LCAoeC10X20pXyskIHdpdGggJG09MSwgXGxkb3RzLCBLLTIkIGtub3RzICR0X20kCiAgICAtICAuLi4KCiMjIyBUcnVuY3RhdGVkIGxpbmUgYmFzaXMKClxbeV9pPWYoeF9pKStcZXBzaWxvbl9pLFxdCgotICBPbmUgb2YgdGhlIG1vc3Qgc2ltcGxlIGJhc2lzIGV4cGFuc2lvbnMKLSAgJGYoeF9pKT1cYmV0YV8wK1xiZXRhXzF4X2krXHN1bVxsaW1pdHNfe209MX1ee0stMn1cdGhldGFfbSh4X2ktdF9tKV8rJCB3aXRoICQoLilfKyQgdGhlIG9wZXJhdG9yIHRoYXQgdGFrZXMgdGhlIHBvc2l0aXZlIHBhcnQuCi0gIE5vdGUsIHRoYXQgYmV0dGVyIGJhc2lzIGV4cGFuc2lvbnMgZXhpc3QsIHdoaWNoIGFyZSBvcnRob2dvbmFsLCBjb21wdXRhdGlvbmFsIG1vcmUgc3RhYmxlIGFuZC9vciBjb250aW51b3VzIGRlcml2YXRpdmUgYmV5b25kIGZpcnN0IG9yZGVyCi0gIFdlIHdpbGwgdXNlIHRoaXMgYmFzaXMgZm9yIGRpZGFjdGljYWwgcHVycG9zZXMKLSBXZSBjYW4gdXNlIE9MUyB0byBmaXQgeSB3LnIudC4gdGhlIGJhc2lzLgoKYGBge3J9Cmtub3RzIDwtIHNlcSg0MDAsNzAwLDEyLjUpCgpiYXNpcyA8LSBzYXBwbHkoa25vdHMsCiAgZnVuY3Rpb24oayx5KSAoeS1rKSooeT5rKSwKICB5PSBsaWRhciAlPiUgcHVsbChyYW5nZSkKICApCgpiYXNpc0V4cCA8LSBjYmluZCgxLCByYW5nZSA9IGxpZGFyICU+JSBwdWxsKHJhbmdlKSwgYmFzaXMpCgpzcGxpbmVGaXRMcyA8LSBsbShsb2dyYXRpbyB+IC0xICsgYmFzaXNFeHAsIGxpZGFyKQoKcEJhc2lzIDwtIGJhc2lzRXhwWywtMV0gJT4lCiAgZGF0YS5mcmFtZSAlPiUKICBnYXRoZXIoImJhc2lzIiwidmFsdWVzIiwtMSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gcmFuZ2UsIHkgPSB2YWx1ZXMsIGNvbG9yID0gYmFzaXMpKSArCiAgZ2VvbV9saW5lKCkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsKICB5bGFiKCJiYXNpcyIpCgpncmlkLmFycmFuZ2UoCiAgcExpZGFyICsKICAgIGdlb21fbGluZShhZXMoeCA9IGxpZGFyJHJhbmdlLCB5ID0gc3BsaW5lRml0THMkZml0dGVkKSwgbHdkID0gMiksCiAgcEJhc2lzLAogIG5jb2w9MSkKYGBgCgotIE5vdGUsIHRoYXQgdGhlIG1vZGVsIGlzIG92ZXJmaXR0aW5nIQotIFRoZSBmaXQgaXMgdmVyeSB3aWdnbHkgYW5kIGlzIHR1bmVkIHRvbyBtdWNoIHRvIHRoZSBkYXRhLgotIFRoZSBmaXQgaGFzIGEgbGFyZ2UgdmFyaWFuY2UgYW5kIGxvdyBiaWFzLgotIEl0IHdpbGwgdGhlcmVmb3JlIG5vdCBnZW5lcmFsaXNlIHdlbGwgdG8gcHJlZGljdCB0aGUgbG9ncmF0aW8gb2YgZnV0dXJlIG9ic2VydmF0aW9ucy4KCiMjIyMgU29sdXRpb24gZm9yIG92ZXJmaXR0aW5nPwoKLSBXZSBjb3VsZCBwZXJmb3JtIG1vZGVsIHNlbGVjdGlvbiBvbiB0aGUgYmFzaXMgdG8gc2VsZWN0IHRoZSBpbXBvcnRhbnQgYmFzaXMgZnVuY3Rpb25zIHRvIG1vZGVsIHRoZSBzaWduYWwuIEJ1dCwgdGhpcyB3aWxsIGhhdmUgdGhlIHVuZGVzaXJlZCBwcm9wZXJ0eSB0aGF0IHRoZSBmaXQgd2lsbCBubyBsb25nZXIgYmUgc21vb3RoLgoKLSBXZSBjYW4gYWxzbyBhZG9wdCBhIHJpZGdlIHBlbmFsdHkhCi0gSG93ZXZlciwgd2UgZG8gbm90IHdhbnQgdG8gcGVuYWxpc2UgdGhlIGludGVyY2VwdCBhbmQgdGhlIGxpbmVhciB0ZXJtLgotIFJpZGdlIGNyaXRlcmlvbgoKXFtcVmVydFxtYXRoYmZ7WX0tXG1hdGhiZntYXGJldGF9XFZlcnReMitcbGFtYmRhXGJvbGRzeW1ib2x7XGJldGF9XlRcbWF0aGJme0R9XGJvbGRzeW1ib2x7XGJldGF9ClxdCgpXaXRoICRcbWF0aGJme0R9JCB3aXRoIGRpbWVuc2lvbnMgKEssSyk6ICRcbWF0aGJme0R9PVxsZWZ0W1xiZWdpbnthcnJheX17Y2N9XG1hdGhiZnswfV97Mlx0aW1lczJ9JiBcbWF0aGJmezB9X3syXHRpbWVzIEstMn1cXApcbWF0aGJmezB9X3tLLTJcdGltZXMyfSZcbWF0aGJme0l9X3tLLTJcdGltZXMgSy0yfVxlbmR7YXJyYXl9XHJpZ2h0XSQKCi0gSGVyZSB3ZSB3aWxsIHNldCB0aGUgcGVuYWx0eSBhdCA5MDAuCgpgYGB7cn0KRCA8LSBkaWFnKG5jb2woYmFzaXNFeHApKQpEWzE6MiwxOjJdIDwtIDAKbGFtYmRhIDwtIDkwMApiZXRhUmlkZ2UgPC0gc29sdmUodChiYXNpc0V4cCklKiViYXNpc0V4cCsobGFtYmRhKkQpKSUqJXQoYmFzaXNFeHApJSolbGlkYXIkbG9ncmF0aW8KZ3JpZC5hcnJhbmdlKAogIHBMaWRhciArCiAgICBnZW9tX2xpbmUoYWVzKHggPSBsaWRhciRyYW5nZSwgeSA9IGMoYmFzaXNFeHAgJSolIGJldGFSaWRnZSkpLCBsd2QgPSAyKSwKICBwQmFzaXMsCiAgbmNvbD0xKQpgYGAKCkhvdyBkbyB3ZSBjaG9vc2UgJFxsYW1iZGEkPwoKLS0tCgojIEV2YWx1YXRpb24gb2YgUHJlZGljdGlvbiBNb2RlbHMKCgpQcmVkaWN0aW9ucyBhcmUgY2FsY3VsYXRlZCB3aXRoIHRoZSBmaXR0ZWQgbW9kZWwKIFxbCiAgIFxoYXR7WX0oXG1hdGhiZnt4fSkgPSBcaGF0e219KFxtYXRoYmZ7eH0pPVxtYXRoYmZ7eH1eVFxoYXR7XGJldGF9CiBcXQogd2hlbiBmb2N1c3Npbmcgb24gcHJlZGljdGlvbiwgd2Ugd2FudCB0aGUgcHJlZGljdGlvbiBlcnJvciB0byBiZSBhcyBzbWFsbCBhcyBwb3NzaWJsZS4KClRoZSAqKnByZWRpY3Rpb24gZXJyb3IqKiBmb3IgYSBwcmVkaWN0aW9uIGF0IGNvdmFyaWF0ZSBwYXR0ZXJuICRcbWF0aGJme3h9JCBpcyBnaXZlbiBieQogIFxbCiAgICAgXGhhdHtZfShcbWF0aGJme3h9KSAtIFleKiwKICBcXQp3aGVyZQoKLSAkXGhhdHtZfShcbWF0aGJme3h9KT1cbWF0aGJme3h9XlRcaGF0e1xib2xkc3ltYm9se1xiZXRhfX0kIGlzIHRoZSBwcmVkaWN0aW9uIGF0ICRcbWF0aGJme3h9JAoKLSAgJFleKiQgaXMgYW4gb3V0Y29tZSBhdCBjb3ZhcmlhdGUgcGF0dGVybiAkXG1hdGhiZnt4fSQKClByZWRpY3Rpb24gaXMgdHlwaWNhbGx5IHVzZWQgdG8gcHJlZGljdCBhbiBvdXRjb21lIGJlZm9yZSBpdCBpcyBvYnNlcnZlZC4KCi0gSGVuY2UsIHRoZSBvdXRjb21lICRZXiokIGlzIG5vdCBvYnNlcnZlZCB5ZXQsIGFuZAotIHRoZSBwcmVkaWN0aW9uIGVycm9yIGNhbm5vdCBiZSBjb21wdXRlZC4KCi0tLQoKLSBSZWNhbGwgdGhhdCB0aGUgcHJlZGljdGlvbiBtb2RlbCAkXGhhdHtZfShcbWF0aGJme3h9KSQgaXMgZXN0aW1hdGVkIGJ5IHVzaW5nIGRhdGEgaW4gdGhlIHRyYWluaW5nIGRhdGEgc2V0ICQoXG1hdGhiZntYfSxcbWF0aGJme1l9KSQsIGFuZAotIHRoYXQgdGhlIG91dGNvbWUgJFleKiQgaXMgYW4gb3V0Y29tZSBhdCAkXG1hdGhiZnt4fSQgd2hpY2ggaXMgYXNzdW1lZCB0byBiZSBpbmRlcGVuZGVudCBvZiB0aGUgdHJhaW5pbmcgZGF0YS4KCi0gR29hbCBpcyB0byB1c2UgcHJlZGljdGlvbiBtb2RlbCBmb3IgcHJlZGljdGluZyBhIGZ1dHVyZSBvYnNlcnZhdGlvbiAoJFleKiQpLCBpLmUuIGFuIG9ic2VydmF0aW9uIHRoYXQgc3RpbGwgaGFzIHRvIGJlIHJlYWxpc2VkL29ic2VydmVkIChvdGhlcndpc2UgcHJlZGljdGlvbiBzZWVtcyByYXRoZXIgdXNlbGVzcykuCgotIEhlbmNlLCAkWV4qJCBjYW4gbmV2ZXIgYmUgcGFydCBvZiB0aGUgdHJhaW5pbmcgZGF0YSBzZXQuCgotLS0KCkhlcmUgd2UgcHJvdmlkZSBkZWZpbml0aW9ucyBhbmQgd2Ugc2hvdyBob3cgdGhlIHByZWRpY3Rpb24gcGVyZm9ybWFuY2Ugb2YgYSBwcmVkaWN0aW9uIG1vZGVsIGNhbiBiZSBldmFsdWF0ZWQgZnJvbSBkYXRhLgoKTGV0ICR7XG1hdGhjYWx7VH19PShcbWF0aGJme1l9LFxtYXRoYmZ7WH0pJCBkZW5vdGUgdGhlIHRyYWluaW5nIGRhdGEsIGZyb20gd2hpY2ggdGhlIHByZWRpY3Rpb24gbW9kZWwgJFxoYXR7WX0oXGNkb3QpJCBpcyBidWlsZC4gVGhpcyBidWlsZGluZyBwcm9jZXNzIHR5cGljYWxseSBpbnZvbHZlcyBmZWF0dXJlIHNlbGVjdGlvbiBhbmQgcGFyYW1ldGVyIGVzdGltYXRpb24uCgogV2Ugd2lsbCB1c2UgYSBtb3JlIGdlbmVyYWwgbm90YXRpb24gZm9yIHRoZSBwcmVkaWN0aW9uIG1vZGVsOiAkXGhhdHttfShcbWF0aGJme3h9KT1caGF0e1l9KFxtYXRoYmZ7eH0pJC4KCi0tLQoKIyMgVGVzdCBvciBHZW5lcmFsaXNhdGlvbiBFcnJvcgoKIFRoZSB0ZXN0IG9yIGdlbmVyYWxpc2F0aW9uIGVycm9yIGZvciBwcmVkaWN0aW9uIG1vZGVsICRcaGF0e219KFxjZG90KSQgaXMgZ2l2ZW4gYnkKICBcWwogICAgXHRleHR7RXJyfV97XG1hdGhjYWx7VH19ID0gXHRleHR7RX1fe1leKixYXip9XGxlZnRbKFxoYXR7bX0oXG1hdGhiZntYfV4qKSAtIFleKileMlxtaWQge1xtYXRoY2Fse1R9fVxyaWdodF0KICBcXQogIHdoZXJlICQoWV4qLFheKikkIGlzIGluZGVwZW5kZW50IG9mIHRoZSB0cmFpbmluZyBkYXRhLgoKLS0tCgotIE5vdGUgdGhhdCB0aGUgdGVzdCBlcnJvciBpcyBjb25kaXRpb25hbCBvbiB0aGUgdHJhaW5pbmcgZGF0YSAke1xtYXRoY2Fse1R9fSQuCi0gSGVuY2UsIHRoZSB0ZXN0IGVycm9yIGV2YWx1YXRlcyB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIHNpbmdsZSBtb2RlbCBidWlsZCBmcm9tIHRoZSBvYnNlcnZlZCB0cmFpbmluZyBkYXRhLgotIFRoaXMgaXMgdGhlIHVsdGltYXRlIHRhcmdldCBvZiB0aGUgbW9kZWwgYXNzZXNzbWVudCwgYmVjYXVzZSBpdCBpcyBleGFjdGx5IHRoaXMgcHJlZGljdGlvbiBtb2RlbCB0aGF0IHdpbGwgYmUgdXNlZCBpbiBwcmFjdGljZSBhbmQgYXBwbGllZCB0byBmdXR1cmUgcHJlZGljdG9ycyAkXG1hdGhiZntYfV4qJCB0byBwcmVkaWN0ICRZXiokLgotIFRoZSB0ZXN0IGVycm9yIGlzIGRlZmluZWQgYXMgYW4gYXZlcmFnZSBvdmVyIGFsbCBzdWNoIGZ1dHVyZSBvYnNlcnZhdGlvbnMgJChZXiosXG1hdGhiZntYfV4qKSQuCgotLS0KCiMjIENvbmRpdGlvbmFsIHRlc3QgZXJyb3IKClNvbWV0aW1lcyB0aGUgY29uZGl0aW9uYWwgdGVzdCBlcnJvciBpcyB1c2VkOgoKVGhlIGNvbmRpdGlvbmFsIHRlc3QgZXJyb3IgaW4gJFxtYXRoYmZ7eH0kIGZvciBwcmVkaWN0aW9uIG1vZGVsICRcaGF0e219KFxtYXRoYmZ7eH0pJCBpcyBnaXZlbiBieQogXFsKICAgXHRleHR7RXJyfV97XG1hdGhjYWx7VH19KFxtYXRoYmZ7eH0pID0gXHRleHR7RX1fe1leKn1cbGVmdFsoXGhhdHttfShcbWF0aGJme3h9KSAtIFleKileMlxtaWQge1xtYXRoY2Fse1R9fSwgXG1hdGhiZnt4fVxyaWdodF0KIFxdCiB3aGVyZSAkWV4qJCBpcyBhbiBvdXRjb21lIGF0IHByZWRpY3RvciAkXG1hdGhiZnt4fSQsIGluZGVwZW5kZW50IG9mIHRoZSB0cmFpbmluZyBkYXRhLgoKIEhlbmNlLAogXFsKICAgXHRleHR7RXJyfV97XG1hdGhjYWx7VH19ID0gXHRleHR7RX1fe1heKn1cbGVmdFtcdGV4dHtFcnJ9X3tcbWF0aGNhbHtUfX0oXG1hdGhiZntYfV4qKVxyaWdodF0uCiBcXQoKQSBjbG9zZWx5IHJlbGF0ZWQgZXJyb3IgaXMgdGhlICoqaW5zYW1wbGUgZXJyb3IqKi4KCi0tLQoKIyMgSW5zYW1wbGUgRXJyb3IKClRoZSBpbnNhbXBsZSBlcnJvciBmb3IgcHJlZGljdGlvbiBtb2RlbCAkXGhhdHttfShcbWF0aGJme3h9KSQgaXMgZ2l2ZW4gYnkKIFxbCiAgIFx0ZXh0e0Vycn1fe1x0ZXh0e2lufSBcbWF0aGNhbHtUfX0gPSBcZnJhY3sxfXtufVxzdW1fe2k9MX1ebiBcdGV4dHtFcnJ9X3tcbWF0aGNhbHtUfX0oXG1hdGhiZnt4fV9pKSwKIFxdCgppLmUuIHRoZSBpbnNhbXBsZSBlcnJvciBpcyB0aGUgc2FtcGxlIGF2ZXJhZ2Ugb2YgdGhlIGNvbmRpdGlvbmFsIHRlc3QgZXJyb3JzIGV2YWx1YXRlZCBpbiB0aGUgJG4kIHRyYWluaW5nIGRhdGFzZXQgcHJlZGljdG9ycyAkXG1hdGhiZnt4fV9pJC4KClNpbmNlICRcdGV4dHtFcnJ9X3tcbWF0aGNhbHtUfX0kIGlzIGFuIGF2ZXJhZ2Ugb3ZlciBhbGwgJFxtYXRoYmZ7WH0kLCBldmVuIG92ZXIgdGhvc2UgcHJlZGljdG9ycyBub3Qgb2JzZXJ2ZWQgaW4gdGhlIHRyYWluaW5nIGRhdGFzZXQsIGl0IGlzIHNvbWV0aW1lcyByZWZlcnJlZCB0byBhcyB0aGUgKipvdXRzYW1wbGUgZXJyb3IqKi4KCi0tLQoKIyMgRXN0aW1hdGlvbiBvZiB0aGUgaW5zYW1wbGUgZXJyb3IKCldlIHN0YXJ0IHdpdGggaW50cm9kdWNpbmcgdGhlIHRyYWluaW5nIGVycm9yIHJhdGUsIHdoaWNoIGlzIGNsb3NlbHkgcmVsYXRlZCB0byB0aGUgTVNFIGluIGxpbmVhciBtb2RlbHMuCgojIyMgVHJhaW5pbmcgZXJyb3IKCiBUaGUgdHJhaW5pbmcgZXJyb3IgaXMgZ2l2ZW4gYnkKIFxbCiAgIFxvdmVybGluZXtcdGV4dHtlcnJ9fSA9IFxmcmFjezF9e259XHN1bV97aT0xfV5uIChZX2kgLSBcaGF0e219KFxtYXRoYmZ7eH1faSkpXjIgLAogXF0KIHdoZXJlIHRoZSAkKFlfaSxcbWF0aGJme3h9X2kpJCBmcm9tIHRoZSB0cmFpbmluZyBkYXRhc2V0IHdoaWNoIGlzIGFsc28gdXNlZCBmb3IgdGhlIGNhbGN1bGF0aW9uIG9mICRcaGF0e219JC4KCi0gVGhlIHRyYWluaW5nIGVycm9yIGlzIGFuIG92ZXJseSBvcHRpbWlzdGljIGVzdGltYXRlIG9mIHRoZSB0ZXN0IGVycm9yICRcdGV4dHtFcnJ9X3tcbWF0aGNhbHtUfX0kLgoKLSBUaGUgdHJhaW5pbmcgZXJyb3Igd2lsbCBuZXZlciBpbmNyZWFzZXMgd2hlbiB0aGUgbW9kZWwgYmVjb21lcyBtb3JlIGNvbXBsZXguICRcbG9uZ3JpZ2h0YXJyb3ckIGNhbm5vdCBiZSB1c2VkIGRpcmVjdGx5IGFzIGEgbW9kZWwgc2VsZWN0aW9uIGNyaXRlcmlvbi4KCkluZGVlZCwgbW9kZWwgcGFyYW1ldGVycyBhcmUgb2Z0ZW4gZXN0aW1hdGVkIGJ5IG1pbmltaXNpbmcgdGhlIHRyYWluaW5nIGVycm9yIChjZnIuIFNTRSkuCgotIEhlbmNlIHRoZSBmaXR0ZWQgbW9kZWwgYWRhcHRzIHRvIHRoZSB0cmFpbmluZyBkYXRhLCBhbmQKLSB0cmFpbmluZyBlcnJvciB3aWxsIGJlIGFuIG92ZXJseSBvcHRpbWlzdGljIGVzdGltYXRlIG9mIHRoZSB0ZXN0IGVycm9yICRcdGV4dHtFcnJ9X3tcbWF0aGNhbHtUfX0kLgoKLS0tCgpJdCBjYW4gYmUgc2hvd24gdGhhdCB0aGUgdHJhaW5pbmcgZXJyb3IgaXMgcmVsYXRlZCB0byB0aGUgaW5zYW1wbGUgdGVzdCBlcnJvciB2aWEKClxbClx0ZXh0e0V9X1xtYXRoYmZ7WX0KXGxlZnRbXHRleHR7RXJyfV97XHRleHR7aW59e1xtYXRoY2Fse1R9fX1ccmlnaHRdID0gXHRleHR7RX1fXG1hdGhiZntZfVxsZWZ0W1xvdmVybGluZXtcdGV4dHtlcnJ9fVxyaWdodF0gKyBcZnJhY3syfXtufVxzdW1fe2k9MX1ebiBcdGV4dHtjb3Z9X1xtYXRoYmZ7WX1cbGVmdFtcaGF0e219KFxtYXRoYmZ7eH1faSksWV9pXHJpZ2h0XSwKXF0KCk5vdGUsIHRoYXQgZm9yIGxpbmVhciBtb2RlbHMKXFsgXGhhdHttfShcbWF0aGJme3h9X2kpID0gXG1hdGhiZntYfVxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fT0gXG1hdGhiZntYfShcbWF0aGJme1h9XlRcbWF0aGJme1h9KV57LTF9XG1hdGhiZntYfV5UXG1hdGhiZntZfSA9IFxtYXRoYmZ7SFl9ClxdCndpdGgKCi0gJFxtYXRoYmZ7SH0kIHRoZSBoYXQgbWF0cml4IGFuZAotIGFsbCAkWV9pJCBhcmUgYXNzdW1lZCB0byBiZSBpbmRlcGVuZGVudGx5IGRpc3RyaWJ1dGVkICAkTihcbWF0aGJme1h9XGJvbGRzeW1ib2x7XGJldGF9LFxzaWdtYV4yKSQKCkhlbmNlLCBmb3IgbGluZWFyIG1vZGVscyB3aXRoIGluZGVwZW5kZW50IG9ic2VydmF0aW9ucwoKXGJlZ2lue2VxbmFycmF5fQpcdGV4dHtjb3Z9X1xtYXRoYmZ7WX1cbGVmdFtcaGF0e219KFxtYXRoYmZ7eH1faSksWV9pKVxyaWdodF0gJj0mClx0ZXh0e2Nvdn1fXG1hdGhiZntZfVxsZWZ0W1xtYXRoYmZ7SH1fe2l9XlRcbWF0aGJme1l9LFlfaSlccmlnaHRdXFwKJj0mIFx0ZXh0e2Nvdn1fXG1hdGhiZntZfVxsZWZ0W2hfe2lpfSBZX2ksWV9pXHJpZ2h0XVxcCiY9JiBoX3tpaX0gXHRleHR7Y292fV9cbWF0aGJme1l9XGxlZnRbWV9pLFlfaVxyaWdodF1cXAomPSYgaF97aWl9IFxzaWdtYV4yXFwKXGVuZHtlcW5hcnJheX0KCkFuZCB3ZSBjYW4gdGh1cyBlc3RpbWF0ZSB0aGUgaW5zYW1wbGUgZXJyb3IgYnkgTWFsbG93J3MgJENfcCQKClxiZWdpbntlcW5hcnJheX0KQ19wICY9JiBcb3ZlcmxpbmV7XHRleHR7ZXJyfX0gKyBcZnJhY3syXHNpZ21hXjJ9e259XHRleHR7dHJ9KFxtYXRoYmZ7SH0pXFwKJj0mIFxvdmVybGluZXtcdGV4dHtlcnJ9fSArIFxmcmFjezJcc2lnbWFeMnB9e259ClxlbmR7ZXFuYXJyYXl9Cgp3aXRoICRwJCB0aGUgbnVtYmVyIG9mIHByZWRpY3RvcnMuCgotIE1hbGxvdydzICRDX3AkIGlzIG9mdGVuIHVzZWQgZm9yIG1vZGVsIHNlbGVjdGlvbi4KLSBOb3RlLCB0aGF0IHdlIGNhbiBhbHNvIGNvbnNpZGVyIGl0IGFzIGEga2luZCBvZiBwZW5hbGl6ZWQgbGVhc3Qgc3F1YXJlczoKClxbCm4gXHRpbWVzIENfcCA9IFxWZXJ0IFxtYXRoYmZ7WX0gLSBcbWF0aGJme1h9XGJvbGRzeW1ib2x7XGJldGF9XFZlcnRfMl4yICsgMlxzaWdtYV4yIFxWZXJ0IFxib2xkc3ltYm9se1xiZXRhfSBcVmVydF8wClxdCndpdGggJExfMCQgbm9ybSAkXFZlcnQgXGJvbGRzeW1ib2x7XGJldGF9IFxWZXJ0XzAgPSBcc3VtX3tqPTF9XnAgXGJldGFfcF4wID0gcCQuCgotLS0KCiMjIEV4cGVjdGVkIHRlc3QgZXJyb3IKClRoZSB0ZXN0IG9yIGdlbmVyYWxpc2F0aW9uIGVycm9yIHdhcyBkZWZpbmVkIGNvbmRpdGlvbmFsbHkgb24gdGhlIHRyYWluaW5nIGRhdGEuIEJ5IGF2ZXJhZ2luZyBvdmVyIHRoZSBkaXN0cmlidXRpb24gb2YgdHJhaW5pbmcgZGF0YXNldHMsIHRoZSBleHBlY3RlZCB0ZXN0IGVycm9yIGFyaXNlcy4KClxiZWdpbntlcW5hcnJheSp9CiAgIFx0ZXh0e0V9X3tcbWF0aGNhbHtUfX1cbGVmdFtcdGV4dHtFcnJ9X3t7XG1hdGhjYWx7VH19fVxyaWdodF0KICAgICAmPSYgXHRleHR7RX1fe1xtYXRoY2Fse1R9fVxsZWZ0W1x0ZXh0e0V9X3tZXiosWF4qfVxsZWZ0WyhcaGF0e219KFxtYXRoYmZ7WH1eKikgLSBZXiopXjJcbWlkIHtcbWF0aGNhbHtUfX1ccmlnaHRdXHJpZ2h0XSBcXAogICAgICY9JiBcdGV4dHtFfV97WV4qLFheKix7XG1hdGhjYWx7VH19fVxsZWZ0WyhcaGF0e219KFxtYXRoYmZ7WH1eKikgLSBZXiopXjJccmlnaHRdLgogXGVuZHtlcW5hcnJheSp9CgogLSBUaGUgZXhwZWN0ZWQgdGVzdCBlcnJvciBtYXkgbm90IGJlIG9mIGRpcmVjdCBpbnRlcmVzdCB3aGVuIHRoZSBnb2FsIGlzIHRvIGFzc2VzcyB0aGUgcHJlZGljdGlvbiBwZXJmb3JtYW5jZSBvZiBhIHNpbmdsZSBwcmVkaWN0aW9uIG1vZGVsICRcaGF0e219KFxjZG90KSQuCgogLSBUaGUgZXhwZWN0ZWQgdGVzdCBlcnJvciBhdmVyYWdlcyB0aGUgdGVzdCBlcnJvcnMgb2YgYWxsIG1vZGVscyB0aGF0IGNhbiBiZSBidWlsZCBmcm9tIGFsbCB0cmFpbmluZyBkYXRhc2V0cywgYW5kIGhlbmNlIHRoaXMgbWF5IGJlIGxlc3MgcmVsZXZhbnQgd2hlbiB0aGUgaW50ZXJlc3QgaXMgaW4gZXZhbHVhdGluZyBvbmUgcGFydGljdWxhciBtb2RlbCB0aGF0IHJlc3VsdGVkIGZyb20gYSBzaW5nbGUgb2JzZXJ2ZWQgdHJhaW5pbmcgZGF0YXNldC4KCiAtIEFsc28gbm90ZSB0aGF0IGJ1aWxkaW5nIGEgcHJlZGljdGlvbiBtb2RlbCBpbnZvbHZlcyBib3RoIHBhcmFtZXRlciBlc3RpbWF0aW9uIGFuZCBmZWF0dXJlIHNlbGVjdGlvbi4KCiAtIEhlbmNlIHRoZSBleHBlY3RlZCB0ZXN0IGVycm9yIGFsc28gZXZhbHVhdGVzIHRoZSBmZWF0dXJlIHNlbGVjdGlvbiBwcm9jZWR1cmUgKG9uIGF2ZXJhZ2UpLgoKIC0gSWYgdGhlIGV4cGVjdGVkIHRlc3QgZXJyb3IgaXMgc21hbGwsIGl0IGlzIGFuIGluZGljYXRpb24gdGhhdCB0aGUgbW9kZWwgYnVpbGRpbmcgcHJvY2VzcyBnaXZlcyBnb29kIHByZWRpY3Rpb25zIGZvciBmdXR1cmUgb2JzZXJ2YXRpb25zICQoWV4qLFxtYXRoYmZ7WH1eKikkIG9uIGF2ZXJhZ2UuCgojIyMgRXN0aW1hdGluZyB0aGUgRXhwZWN0ZWQgdGVzdCBlcnJvcgoKVGhlIGV4cGVjdGVkIHRlc3QgZXJyb3IgbWF5IGJlIGVzdGltYXRlZCBieSBjcm9zcyB2YWxpZGF0aW9uIChDVikuCgojIyMjIExlYXZlIG9uZSBvdXQgY3Jvc3MgdmFsaWRhdGlvbiAoTE9PQ1YpfQoKVGhlIExPT0NWIGVzdGltYXRvciBvZiB0aGUgZXhwZWN0ZWQgdGVzdCBlcnJvciAob3IgZXhwZWN0ZWQgb3V0c2FtcGxlIGVycm9yKSBpcyBnaXZlbiBieQogIFxbCiAgICAgXHRleHR7Q1Z9ID0gXGZyYWN7MX17bn0gXHN1bV97aT0xfV5uIFxsZWZ0KFlfaSAtIFxoYXR7bX1eey1pfShcbWF0aGJme3h9X2kpXHJpZ2h0KV4yICwKICBcXQp3aGVyZQoKLSB0aGUgJChZX2ksXG1hdGhiZnt4fV9pKSQgZm9ybSB0aGUgdHJhaW5pbmcgZGF0YXNldAotICAgJFxoYXR7bX1eey1pfSQgaXMgdGhlIGZpdHRlZCBtb2RlbCBiYXNlZCBvbiBhbGwgdHJhaW5pbmcgZGF0YSwgZXhjZXB0IG9ic2VydmF0aW9uICRpJAotICAgJFxoYXR7bX1eey1pfShcbWF0aGJme3h9X2kpJCBpcyB0aGUgcHJlZGljdGlvbiBhdCAkXG1hdGhiZnt4fV9pJCwgd2hpY2ggaXMgdGhlIG9ic2VydmF0aW9uIGxlZnQgb3V0IHRoZSB0cmFpbmluZyBkYXRhIGJlZm9yZSBidWlsZGluZyBtb2RlbCAkbSQuCgpTb21lIHJhdGlvbmFsZSBhcyB0byB3aHkgTE9PQ1Ygb2ZmZXJzIGEgZ29vZCBlc3RpbWF0b3Igb2YgdGhlIG91dHNhbXBsZSBlcnJvcjoKCi0gdGhlIHByZWRpY3Rpb24gZXJyb3IgJFleKi1caGF0e219KFxtYXRoYmZ7eH0pJCBpcyBtaW1pY2tlZCBieSBub3QgdXNpbmcgb25lIG9mIHRoZSB0cmFpbmluZyBvdXRjb21lcyAkWV9pJCBmb3IgdGhlIGVzdGltYXRpb24gb2YgdGhlIG1vZGVsIHNvIHRoYXQgdGhpcyAkWV9pJCBwbGF5cyB0aGUgcm9sZSBvZiAkWV4qJCwgYW5kLCBjb25zZXF1ZW50bHksIHRoZSBmaXR0ZWQgbW9kZWwgJFxoYXR7bX1eey1pfSQgaXMgaW5kZXBlbmRlbnQgb2YgJFlfaSQKCiAtIHRoZSBzdW0gaW4gJENWJCBpcyBvdmVyIGFsbCAkXG1hdGhiZnt4fV9pJCBpbiB0aGUgdHJhaW5pbmcgZGF0YXNldCwgYnV0IGVhY2ggdGVybSAkXG1hdGhiZnt4fV9pJCB3YXMgbGVmdCBvdXQgb25jZSBmb3IgdGhlIGNhbGN1bGF0aW9uIG9mICRcaGF0e219XnstaX0kLiBIZW5jZSwgJFxoYXR7bX1eey1pfShcbWF0aGJme3h9X2kpJCBtaW1pY3MgYW4gb3V0c2FtcGxlIHByZWRpY3Rpb24uCgogLSB0aGUgc3VtIGluIENWIGlzIG92ZXIgJG4kIGRpZmZlcmVudCB0cmFpbmluZyBkYXRhc2V0cyAoZWFjaCBvbmUgd2l0aCBhIGRpZmZlcmVudCBvYnNlcnZhdGlvbiByZW1vdmVkKSwgYW5kIGhlbmNlIENWIGlzIGFuIGVzdGltYXRvciBvZiB0aGUgKmV4cGVjdGVkKiB0ZXN0IGVycm9yLgoKIC0gRm9yIGxpbmVhciBtb2RlbHMgdGhlIExPT0NWIGNhbiBiZSByZWFkaWx5IG9idGFpbmVkIGZyb20gdGhlIGZpdHRlZCBtb2RlbDogaS5lLgoKIFxbXHRleHR7Q1Z9ID0gXGZyYWN7MX17bn1cc3VtXGxpbWl0c197aT0xfV5uIFxmcmFje2VfaV4yfXsoMS1oX3tpaX0pXjJ9XF0KCiB3aXRoICRlX2kkIHRoZSByZXNpZHVhbHMgZnJvbSB0aGUgbW9kZWwgdGhhdCBpcyBmaXR0ZWQgYmFzZWQgb24gYWxsIHRyYWluaW5nIGRhdGEuCgotLS0KCkFuIGFsdGVybmF0aXZlIHRvIExPT0NWIGlzIHRoZSAkayQtZm9sZCBjcm9zcyB2YWxpZGF0aW9uIHByb2NlZHVyZS4gSXQgYWxzbyBnaXZlcyBhbiBlc3RpbWF0ZSBvZiB0aGUgZXhwZWN0ZWQgb3V0c2FtcGxlIGVycm9yLgoKIyMjIyAkayQtZm9sZCBjcm9zcyB2YWxpZGF0aW9uCgotICBSYW5kb21seSBkaXZpZGUgdGhlIHRyYWluaW5nIGRhdGFzZXQgaW50byAkayQgYXBwcm94aW1hdGVseSBlcXVhbCBzdWJzZXRzIC4gTGV0ICRTX2okIGRlbm90ZSB0aGUgaW5kZXggc2V0IG9mIHRoZSAkaiR0aCBzdWJzZXQgKHJlZmVycmVkIHRvIGFzIGEgKipmb2xkKiopLiBMZXQgJG5faiQgZGVub3RlIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGluIGZvbGQgJGokLgoKLSBUaGUgJGskLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiBlc3RpbWF0b3Igb2YgdGhlIGV4cGVjdGVkIG91dHNhbXBsZSBlcnJvciBpcyBnaXZlbiBieQogXFsKICAgICBcdGV4dHtDVn1fayA9IFxmcmFjezF9e2t9XHN1bV97aj0xfV5rIFxmcmFjezF9e25fan0gXHN1bV97aVxpbiBTX2p9IFxsZWZ0KFlfaSAtIFxoYXR7bX1eey1TX2p9KFxtYXRoYmZ7eH1faSlccmlnaHQpXjIKIFxdCiB3aGVyZSAkXGhhdHttfV57LVNfan0kIGlzIHRoZSBtb2RlbCBmaXR0ZWQgdXNpbmcgYWxsIHRyYWluaW5nIGRhdGEsIGV4Y2VwdCBvYnNlcnZhdGlvbnMgaW4gZm9sZCAkaiQgKGkuZS4gb2JzZXJ2YXRpb25zICRpIFxpbiBTX2okKS4KCi0tLQoKVGhlIGNyb3NzIHZhbGlkYXRpb24gZXN0aW1hdG9ycyBvZiB0aGUgZXhwZWN0ZWQgb3V0c2FtcGxlIGVycm9yIGFyZSBuZWFybHkgdW5iaWFzZWQuIE9uZSBhcmd1bWVudCB0aGF0IGhlbHBzIHRvIHVuZGVyc3RhbmQgd2hlcmUgdGhlIGJpYXMgY29tZXMgZnJvbSBpcyB0aGUgZmFjdCB0aGF0IGUuZy4gaW4gZGUgTE9PQ1YgZXN0aW1hdG9yIHRoZSBtb2RlbCBpcyBmaXQgb24gb25seSAkbi0xJCBvYnNlcnZhdGlvbnMsIHdoZXJlYXMgd2UgYXJlIGFpbWluZyBhdCBlc3RpbWF0aW5nIHRoZSBvdXRzYW1wbGUgZXJyb3Igb2YgYSBtb2RlbCBmaXQgb24gYWxsICRuJCB0cmFpbmluZyBvYnNlcnZhdGlvbnMuIEZvcnR1bmF0ZWx5LCB0aGUgYmlhcyBpcyBvZnRlbiBzbWFsbCBhbmQgaXMgaW4gcHJhY3RpY2UgaGFyZGx5IGEgY29uY2Vybi4KCiRrJC1mb2xkIENWIGlzIGNvbXB1dGF0aW9uYWxseSBtb3JlIGNvbXBsZXguCgpTaW5jZSBDViBhbmQgQ1YkX2skIGFyZSBlc3RpbWF0b3JzLCB0aGV5IGFsc28gc2hvdyBzYW1wbGluZyB2YXJpYWJpbGl0eS4gU3RhbmRhcmQgZXJyb3JzIG9mIHRoZSBDViBvciBDViRfayQgY2FuIGJlIGNvbXB1dGVkLiBXZSBkb24ndCBzaG93IHRoZSBkZXRhaWxzLCBidXQgaW4gdGhlIGV4YW1wbGUgdGhpcyBpcyBpbGx1c3RyYXRlZC4KCiMjIyBCaWFzIFZhcmlhbmNlIHRyYWRlLW9mZgoKRm9yIHRoZSBleHBlY3RlZCBjb25kaXRpb25hbCB0ZXN0IGVycm9yIGluICRcbWF0aGJme3h9JCwgaXQgaG9sZHMgdGhhdApcYmVnaW57ZXFuYXJyYXkqfQogIFx0ZXh0e0V9X3tcbWF0aGNhbHtUfX1cbGVmdFtcdGV4dHtFcnJ9X3tcbWF0aGNhbHtUfX0oXG1hdGhiZnt4fSlccmlnaHRdCiAgICAmPSYgXHRleHR7RX1fe1leKix7XG1hdGhjYWx7VH19fVxsZWZ0WyhcaGF0e219KFxtYXRoYmZ7eH0pLVleKileMiBcbWlkIFxtYXRoYmZ7eH1ccmlnaHRdIFxcCiAgICAmPSYgIFx0ZXh0e3Zhcn1fe1xtYXRoYmZ7WX19XGxlZnRbXGhhdHtZfShcbWF0aGJme3h9KVxtaWQgXG1hdGhiZnt4fVxyaWdodF0gKyhcbXUoXG1hdGhiZnt4fSktXG11XiooXG1hdGhiZnt4fSkpXjIrXHRleHR7dmFyfV97WV4qfVxsZWZ0W1leKlxtaWQgXG1hdGhiZnt4fVxyaWdodF0KXGVuZHtlcW5hcnJheSp9CndoZXJlICRcbXUoXG1hdGhiZnt4fSkgPSBcdGV4dHtFfV97XG1hdGhiZntZfX1cbGVmdFtcaGF0e1l9KFxtYXRoYmZ7eH0pXG1pZCBcbWF0aGJme3h9XHJpZ2h0XSBcdGV4dHsgYW5kIH0gXG11XiooXG1hdGhiZnt4fSk9XHRleHR7RX1fe1leKn1cbGVmdFtZXipcbWlkIFxtYXRoYmZ7eH1ccmlnaHRdJC4KCi0gKipiaWFzKio6ICRcdGV4dHtiaWFzfShcbWF0aGJme3h9KT1cbXUoXG1hdGhiZnt4fSktXG11XiooXG1hdGhiZnt4fSkkCgotICRcdGV4dHt2YXJ9X3tZXip9XGxlZnRbWV4qXG1pZCBcbWF0aGJme3h9XHJpZ2h0XSQgZG9lcyBub3QgZGVwZW5kIG9uIHRoZSBtb2RlbCwgYW5kIGlzIHJlZmVycmVkIHRvIGFzIHRoZSAqKmlycmVkdWNpYmxlIHZhcmlhbmNlKiouCgotLS0KClRoZSBpbXBvcnRhbmNlIG9mIHRoZSBiaWFzLXZhcmlhbmNlIHRyYWRlLW9mZiBjYW4gYmUgc2VlbiBmcm9tIGEgbW9kZWwgc2VsZWN0aW9uIHBlcnNwZWN0aXZlLiBXaGVuIHdlIGFncmVlIHRoYXQgYSBnb29kIG1vZGVsIGlzIGEgbW9kZWwgdGhhdCBoYXMgYSBzbWFsbCBleHBlY3RlZCBjb25kaXRpb25hbCB0ZXN0IGVycm9yIGF0IHNvbWUgcG9pbnQgJFxtYXRoYmZ7eH0kLCB0aGVuIHRoZSBiaWFzLXZhcmlhbmNlIHRyYWRlLW9mZiBzaG93cyB1cyB0aGF0IGEgbW9kZWwgbWF5IGJlIGJpYXNlZCBhcyBsb25nIGFzIGl0IGhhcyBhIHNtYWxsIHZhcmlhbmNlIHRvIGNvbXBlbnNhdGUgZm9yIHRoZSBiaWFzLiAgSXQgb2Z0ZW4gaGFwcGVucyB0aGF0IGEgYmlhc2VkIG1vZGVsIGhhcyBhIHN1YnN0YW50aWFsIHNtYWxsZXIgdmFyaWFuY2UuIFdoZW4gdGhlc2UgdHdvIGFyZSBjb21iaW5lZCwgYSBzbWFsbCBleHBlY3RlZCB0ZXN0IGVycm9yIG1heSBvY2N1ci4KCkFsc28gbm90ZSB0aGF0IHRoZSBtb2RlbCAkbSQgd2hpY2ggZm9ybXMgdGhlIGJhc2lzIG9mIHRoZSBwcmVkaWN0aW9uIG1vZGVsICRcaGF0e219KFxtYXRoYmZ7eH0pJCBkb2VzIE5PVCBuZWVkIHRvIHNhdGlzZnkgJG0oXG1hdGhiZnt4fSk9XG11KFxtYXRoYmZ7eH0pJCBvciAkbShcbWF0aGJme3h9KT1cbXVeKihcbWF0aGJme3h9KSQuIFRoZSBtb2RlbCAkbSQgaXMga25vd24gYnkgdGhlIGRhdGEtYW5hbHlzdCAoaXRzIHRoZSBiYXNpcyBvZiB0aGUgcHJlZGljdGlvbiBtb2RlbCksIHdoZXJlYXMgJFxtdShcbWF0aGJme3h9KSQgYW5kICRcbXVeKihcbWF0aGJme3h9KSQgYXJlIGdlbmVyYWxseSB1bmtub3duIHRvIHRoZSBkYXRhLWFuYWx5c3QuIFdlIG9ubHkgaG9wZSB0aGF0ICRtJCBzZXJ2ZXMgd2VsbCBhcyBhIHByZWRpY3Rpb24gbW9kZWwuCgotLS0KCiMjIyBJbiBwcmFjdGljZQoKV2UgdXNlIGNyb3NzIHZhbGlkYXRpb24gdG8gZXN0aW1hdGUgdGhlIGxhbWJkYSBwZW5hbHR5IGZvciBwZW5hbGlzZWQgcmVncmVzc2lvbjoKCi0gUmlkZ2UgUmVncmVzc2lvbgotIExhc3NvCi0gQnVpbGQgbW9kZWxzLCBlLmcuIHNlbGVjdCB0aGUgbnVtYmVyIG9mIFBDcyBmb3IgUENBIHJlZ3Jlc3Npb24KLSBTcGxpbmVzCgojIyMgVG94aWNvZ2Vub21pY3MgZXhhbXBsZQoKIyMjIyBMYXNzbwoKYGBge3J9CnNldC5zZWVkKDE1KQpsaWJyYXJ5KGdsbW5ldCkKbUN2TGFzc28gPC0gY3YuZ2xtbmV0KAogIHggPSB0b3hEYXRhWywtMV0gJT4lCiAgICBhcy5tYXRyaXgsCiAgeSA9IHRveERhdGEgJT4lCiAgICBwdWxsKEJBKSwKICBhbHBoYSA9IDEpICAjIGxhc3NvIGFscGhhPTEKCnBsb3QobUN2TGFzc28pCmBgYAoKRGVmYXVsdCBDViBwcm9jZWR1cmUgaW4gXHRleHRzZntjdi5nbG1uZXR9IGlzICRrPTEwJC1mb2xkIENWLgoKVGhlIEdyYXBocyBzaG93cwoKLSAxMC1mb2xkIENWIGVzdGltYXRlcyBvZiB0aGUgZXh0cmEtc2FtcGxlIGVycm9yIGFzIGEgZnVuY3Rpb24gb2YgdGhlIGxhc3NvIHBlbmFsdHkgcGFyYW1ldGVyICRcbGFtYmRhJC4KLSBlc3RpbWF0ZSBwbHVzIGFuZCBtaW51cyBvbmNlIHRoZSBlc3RpbWF0ZWQgc3RhbmRhcmQgZXJyb3Igb2YgdGhlIENWIGVzdGltYXRlIChncmV5IGJhcnMpCi0gT24gdG9wIHRoZSBudW1iZXIgb2Ygbm9uLXplcm8gcmVncmVzc2lvbiBwYXJhbWV0ZXIgZXN0aW1hdGVzIGFyZSBzaG93bi4KClR3byB2ZXJ0aWNhbCByZWZlcmVuY2UgbGluZXMgYXJlIGFkZGVkIHRvIHRoZSBncmFwaC4gVGhleSBjb3JyZXNwb25kIHRvCgotIHRoZSAkXGxvZyhcbGFtYmRhKSQgdGhhdCBnaXZlcyB0aGUgc21hbGxlc3QgQ1YgZXN0aW1hdGUgb2YgdGhlIGV4dHJhLXNhbXBsZSBlcnJvciwgYW5kCi0gdGhlIGxhcmdlc3QgJFxsb2coXGxhbWJkYSkkIHRoYXQgZ2l2ZXMgYSBDViBlc3RpbWF0ZSBvZiB0aGUgZXh0cmEtc2FtcGxlIGVycm9yIHRoYXQgaXMgd2l0aGluIG9uZSBzdGFuZGFyZCBlcnJvciBmcm9tIHRoZSBzbWFsbGVzdCBlcnJvciBlc3RpbWF0ZS4KLSBUaGUgbGF0dGVyIGNob2ljZSBvZiAkXGxhbWJkYSQgaGFzIG5vIGZpcm0gdGhlb3JldGljYWwgYmFzaXMsIGV4Y2VwdCB0aGF0IGl0IHNvbWVob3cgYWNjb3VudHMgZm9yIHRoZSBpbXByZWNpc2lvbiBvZiB0aGUgZXJyb3IgZXN0aW1hdGUuIE9uZSBjb3VsZCBsb29zZWx5IHNheSB0aGF0IHRoaXMgJFxnYW1tYSQgY29ycmVzcG9uZHMgdG8gdGhlIHNtYWxsZXN0IG1vZGVsIChpLmUuIGxlYXN0IG51bWJlciBvZiBwcmVkaWN0b3JzKSB0aGF0IGdpdmVzIGFuIGVycm9yIHRoYXQgaXMgd2l0aGluIG1hcmdpbiBvZiBlcnJvciBvZiB0aGUgZXJyb3Igb2YgdGhlIGJlc3QgbW9kZWwuCgotLS0KCmBgYHtyfQptTGFzc29PcHQgPC0gZ2xtbmV0KAogIHggPSB0b3hEYXRhWywtMV0gJT4lCiAgICBhcy5tYXRyaXgsCiAgeSA9IHRveERhdGEgJT4lCiAgICBwdWxsKEJBKSwKICAgIGFscGhhID0gMSwKICAgIGxhbWJkYSA9IG1Ddkxhc3NvJGxhbWJkYS5taW4pCgpzdW1tYXJ5KGNvZWYobUxhc3NvT3B0KSkKYGBgCgoKV2l0aCB0aGUgb3B0aW1hbCAkXGxhbWJkYSQgKHNtYWxsZXN0IGVycm9yIGVzdGltYXRlKSB0aGUgb3V0cHV0IHNob3dzIHRoZSBgciAgbnJvdyhzdW1tYXJ5KGNvZWYobUxhc3NvT3B0KSkpYCBub24temVybyBlc3RpbWF0ZWQgcmVncmVzc2lvbiBjb2VmZmljaWVudHMgKHNwYXJzZSBzb2x1dGlvbikuCgotLS0KCmBgYHtyfQptTGFzc28xc2UgPC0gZ2xtbmV0KAogIHggPSB0b3hEYXRhWywtMV0gJT4lCiAgICBhcy5tYXRyaXgsCiAgICB5PSB0b3hEYXRhICU+JQogICAgICBwdWxsKEJBKSwKICAgIGFscGhhID0gMSwKICAgIGxhbWJkYSA9IG1Ddkxhc3NvJGxhbWJkYS4xc2UpCgptTGFzc28xc2UgJT4lCiAgY29lZiAlPiUKICBzdW1tYXJ5CmBgYAoKVGhpcyBzaG93cyB0aGUgc29sdXRpb24gZm9yIHRoZSBsYXJnZXN0ICRcbGFtYmRhJCB3aXRoaW4gb25lIHN0YW5kYXJkIGVycm9yIG9mIHRoZSBvcHRpbWFsIG1vZGVsLiBOb3cgb25seSBgciAgbnJvdyhzdW1tYXJ5KGNvZWYobUxhc3NvMXNlKSkpYCBub24temVybyBlc3RpbWF0ZXMgcmVzdWx0LgoKLS0tCgojIyMjIFJpZGdlCgpgYGB7cn0KbUN2UmlkZ2UgPC0gY3YuZ2xtbmV0KAogIHggPSB0b3hEYXRhWywtMV0gJT4lCiAgICBhcy5tYXRyaXgsCiAgICB5ID0gdG94RGF0YSAlPiUKICAgICAgcHVsbChCQSksCiAgICAgIGFscGhhID0gMCkgICMgcmlkZ2UgYWxwaGE9MAoKcGxvdChtQ3ZSaWRnZSkKYGBgCgotIFJpZGdlIGRvZXMgbm90IHNlZW0gdG8gaGF2ZSBvcHRpbWFsIHNvbHV0aW9uLgotIDEwLWZvbGQgQ1YgaXMgYWxzbyBsYXJnZXIgdGhhbiBmb3IgbGFzc28uCgotLS0KCiMjIyMgUENBIHJlZ3Jlc3Npb24KCmBgYHtyIGZpZy5rZWVwID0gIm5vbmUiLCB3YXJuaW5nID0gRkFMU0V9CnNldC5zZWVkKDEyNjQpCmxpYnJhcnkoREFBRykKCnRveCA8LSBkYXRhLmZyYW1lKAogIFkgPSB0b3hEYXRhICU+JQogICAgcHVsbChCQSksCiAgUEMgPSBaaykKClBDLnNlcSA8LSAxOjI1CkVyciA8LSBudW1lcmljKDI1KQoKbUN2UGNhIDwtIGN2LmxtKAogIFl+UEMuMSwKICBkYXRhID0gdG94LAogIG0gPSA1LAogIHByaW50aXQgPSBGQUxTRSkKCkVyclsxXTwtYXR0cihtQ3ZQY2EsIm1zIikKCmZvcihpIGluIDI6MjUpIHsKICBtQ3ZQY2EgPC0gY3YubG0oCiAgICBhcy5mb3JtdWxhKAogICAgICBwYXN0ZSgiWSB+IFBDLjEgKyAiLAogICAgICAgIHBhc3RlKCJQQy4iLCAyOmksIGNvbGxhcHNlID0gIisiLCBzZXA9IiIpLAogICAgICAgIHNlcD0iIgogICAgICApCiAgICApLAogICAgZGF0YSA9IHRveCwKICAgIG0gPSA1LAogICAgcHJpbnRpdCA9IEZBTFNFKQogIEVycltpXTwtYXR0cihtQ3ZQY2EsIm1zIikKfQpgYGAKCi0gSGVyZSB3ZSBpbGx1c3RyYXRlIHByaW5jaXBhbCBjb21wb25lbnQgcmVncmVzc2lvbi4KCi0gVGhlIG1vc3QgaW1wb3J0YW50IFBDcyBhcmUgc2VsZWN0ZWQgaW4gYSBmb3J3YXJkIG1vZGVsIHNlbGVjdGlvbiBwcm9jZWR1cmUuCgotIFdpdGhpbiB0aGUgbW9kZWwgc2VsZWN0aW9uIHByb2NlZHVyZSB0aGUgbW9kZWxzIGFyZSBldmFsdWF0ZWQgd2l0aCA1LWZvbGQgQ1YgZXN0aW1hdGVzIG9mIHRoZSBvdXRzYW1wbGUgZXJyb3IuCgotIEl0IGlzIGltcG9ydGFudCB0byByZWFsaXNlIHRoYXQgYSBmb3J3YXJkIG1vZGVsIHNlbGVjdGlvbiBwcm9jZWR1cmUgd2lsbCBub3QgbmVjZXNzYXJpbHkgcmVzdWx0IGluIHRoZSBiZXN0IHByZWRpY3Rpb24gbW9kZWwsIHBhcnRpY3VsYXJseSBiZWNhdXNlIHRoZSBvcmRlciBvZiB0aGUgUENzIGlzIGdlbmVyYWxseSBub3QgcmVsYXRlZCB0byB0aGUgaW1wb3J0YW5jZSBvZiB0aGUgUENzIGZvciBwcmVkaWN0aW5nIHRoZSBvdXRjb21lLgoKLSBBIHN1cGVydmlzZWQgUEMgd291bGQgYmUgYmV0dGVyLgoKYGBge3J9CnBQQ3JlZyA8LSBkYXRhLmZyYW1lKFBDLnNlcSwgRXJyKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBQQy5zZXEsIHkgPSBFcnIpKSArCiAgZ2VvbV9saW5lKCkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9obGluZSgKICAgIHlpbnRlcmNlcHQgPSBjKAogICAgICBtQ3ZMYXNzbyRjdm1bbUN2TGFzc28kbGFtYmRhPT1tQ3ZMYXNzbyRsYW1iZGEubWluXSwKICAgICAgbUN2TGFzc28kY3ZtW21Ddkxhc3NvJGxhbWJkYT09bUN2TGFzc28kbGFtYmRhLjFzZV0pLAogICAgY29sID0gInJlZCIpICsKICB4bGltKDEsMjYpCgpncmlkLmFycmFuZ2UoCiAgcFBDcmVnLAogIHBQQ3JlZyArIHlsaW0oMCw1KSwKICBuY29sPTIpCmBgYAoKLSBUaGUgZ3JhcGggc2hvd3MgdGhlIENWIGVzdGltYXRlIG9mIHRoZSBvdXRzYW1wbGUgZXJyb3IgYXMgYSBmdW5jdGlvbiBvZiB0aGUgbnVtYmVyIG9mIHNwYXJzZSBQQ3MgaW5jbHVkZWQgaW4gdGhlIG1vZGVsLgoKLSBBIHZlcnkgc21hbGwgZXJyb3IgaXMgb2J0YWluZWQgd2l0aCB0aGUgbW9kZWwgd2l0aCBvbmx5IHRoZSBmaXJzdCBQQy4gVGhlIGJlc3QgbW9kZWwgd2l0aCAzIFBDcy4KCi0gVGhlIHR3byB2ZXJ0aWNhbCByZWZlcmVuY2UgbGluZXMgY29ycmVzcG9uZCB0byB0aGUgZXJyb3IgZXN0aW1hdGVzIG9idGFpbmVkIHdpdGggbGFzc28gKG9wdGltYWwgJFxsYW1iZGEkIGFuZCBsYXJnZXN0ICRcbGFtYmRhJCB3aXRoaW4gb25lIHN0YW5kYXJkIGVycm9yKS4KCi0gVGh1cyBhbHRob3VnaCB0aGVyZSB3YXMgYSBwcmlvcmkgbm8gZ3VhcmFudGVlIHRoYXQgdGhlIGZpcnN0IFBDcyBhcmUgdGhlIG1vc3QgcHJlZGljdGl2ZSwgaXQgc2VlbXMgdG8gYmUgdGhlIGNhc2UgaGVyZSAod2Ugd2VyZSBsdWNreSEpLgoKLSBNb3Jlb3ZlciwgdGhlIGZpcnN0IFBDIHJlc3VsdGVkIGluIGEgc21hbGwgb3V0c2FtcGxlIGVycm9yLgoKLSBOb3RlIHRoYXQgdGhlIGdyYXBoIGRvZXMgbm90IGluZGljYXRlIHRoZSB2YXJpYWJpbGl0eSBvZiB0aGUgZXJyb3IgZXN0aW1hdGVzIChubyBlcnJvciBiYXJzKS4KCi0gQWxzbyBub3RlIHRoYXQgdGhlIGdyYXBoIGNsZWFybHkgaWxsdXN0cmF0ZXMgdGhlIGVmZmVjdCBvZiBvdmVyZml0dGluZzogaW5jbHVkaW5nIHRvbyBtYW55IFBDcyBjYXVzZXMgYSBsYXJnZSBvdXRzYW1wbGUgZXJyb3IuCgojIyMgTGlkYXIgRXhhbXBsZTogc3BsaW5lcwoKLSBXZSB1c2UgdGhlIG1nY3YgcGFja2FnZSB0byBmaXQgdGhlIHNwbGluZSBtb2RlbCB0byB0aGUgbGlkYXIgZGF0YS4KLSBBIGJldHRlciBiYXNpcyBpcyB1c2VkIHRoYW4gdGhlIHRydW5jYXRlZCBzcGxpbmUgYmFzaXMKLSBUaGluIHBsYXRlIHNwbGluZXMgYXJlIGFsc28gbGluZWFyIHNtb290aGVycywgaS5lLgokXGhhdHtZfSA9IFxoYXR7bX0oXG1hdGhiZntYfSkgPSBcbWF0aGJme1NZfSQKLSBTbyB0aGVpciB2YXJpYW5jZSBjYW4gYmUgZWFzaWx5IGNhbGN1bGF0ZWQuCi0gVGhlIHJpZGdlL3Ntb290aG5lc3MgcGVuYWx0eSBpcyBjaG9zZW4gYnkgZ2VuZXJhbGl6ZWQgY3Jvc3MgdmFsaWRhdGlvbi4KCmBgYHtyfQpsaWJyYXJ5KG1nY3YpCmdhbWZpdCA8LSBnYW0obG9ncmF0aW8gfiBzKHJhbmdlKSwgZGF0YSA9IGxpZGFyKQpnYW1maXQkc3AKCnBMaWRhciArCiAgZ2VvbV9saW5lKGFlcyh4ID0gbGlkYXIkcmFuZ2UsIHkgPSBnYW1maXQkZml0dGVkKSwgbHdkID0gMikKYGBgCgojIyBNb3JlIGdlbmVyYWwgZXJyb3IgZGVmaW5pdGlvbnMKClNvIGZhciB3ZSBvbmx5IGxvb2tlZCBhdCBjb250aW51b3VzIG91dGNvbWVzICRZJCBhbmQgZXJyb3JzIGRlZmluZWQgaW4gdGVybXMgb2YgdGhlIHNxdWFyZWQgbG9zcyAkKFxoYXR7bX0oXG1hdGhiZnt4fSktWV4qKV4yJC4KCk1vcmUgZ2VuZXJhbGx5LCBhICoqbG9zcyBmdW5jdGlvbioqIG1lYXN1cmVzIGFuIGRpc2NyZXBhbmN5IGJldHdlZW4gdGhlIHByZWRpY3Rpb24gJFxoYXR7bX0oXG1hdGhiZnt4fSkkIGFuZCBhbiBpbmRlcGVuZGVudCBvdXRjb21lICRZXiokIHRoYXQgY29ycmVzcG9uZHMgdG8gJFxtYXRoYmZ7eH0kLgoKClNvbWUgZXhhbXBsZXMgZm9yIGNvbnRpbnVvdXMgJFkkOgpcYmVnaW57ZXFuYXJyYXkqfQogIEwoWV4qLFxoYXR7bX0oXG1hdGhiZnt4fSkpCiAgICAmPSYgKFxoYXR7bX0oXG1hdGhiZnt4fSktWV4qKV4yIFw7XDtcdGV4dHsoc3F1YXJlZCBlcnJvcil9IFxcCiAgTChZXiosXGhhdHttfShcbWF0aGJme3h9KSkKICAgICY9JiBcdmVydFxoYXR7bX0oXG1hdGhiZnt4fSktWV4qXHZlcnQgXDtcO1x0ZXh0eyhhYnNvbHV0ZSBlcnJvcil9IFxcCiAgIEwoWV4qLFxoYXR7bX0oXG1hdGhiZnt4fSkpCiAgICAmPSYgMiBcaW50X3tcbWF0aGNhbHtZfX0gZl95KHkpIFxsb2dcZnJhY3tmX3koeSl9e2Zfe1xoYXR7bX19KHkpfSBkeSBcO1w7XHRleHR7KGRldmlhbmNlKX0uClxlbmR7ZXFuYXJyYXkqfQoKCkluIHRoZSBleHByZXNzaW9uIG9mIHRoZSBkZXZpYW5jZQoKLSAkZl95JCBkZW5vdGVzIHRoZSBkZW5zaXR5IGZ1bmN0aW9uIG9mIGEgZGlzdHJpYnV0aW9uIHdpdGggbWVhbiBzZXQgdG8gJHkkIChjZnIuIHBlcmZlY3QgZml0KSwgYW5kCi0gJGZfe1xoYXR7bX19JCBpcyB0aGUgZGVuc2l0eSBmdW5jdGlvbiBvZiB0aGUgc2FtZSBkaXN0cmlidXRpb24gYnV0IHdpdGggbWVhbiBzZXQgdG8gdGhlIHByZWRpY3RlZCBvdXRjb21lICRcaGF0e219KFxtYXRoYmZ7eH0pJC4KCi0tLQoKV2l0aCBhIGdpdmVuIGxvc3MgZnVuY3Rpb24sIHRoZSBlcnJvcnMgYXJlIGRlZmluZWQgYXMgZm9sbG93czoKLSBUZXN0IG9yIGdlbmVyYWxpc2F0aW9uIG9yIG91dHNhbXBsZSBlcnJvcgogICAgXFsKICAgICAgXHRleHR7RXJyfV97XG1hdGhjYWx7VH19ID0gXHRleHR7RX1fe1leKixYXip9XGxlZnRbTChZXiosXGhhdHttfShcbWF0aGJme1h9XiopKVxyaWdodF0KICAgIFxdCgotIFRyYWluaW5nIGVycm9yCiAgXFsKICAgIFxvdmVybGluZXtcdGV4dHtlcnJ9fSA9IFxmcmFjezF9e259XHN1bV97aT0xfV5uIEwoWV9pLFxoYXR7bX0oXG1hdGhiZnt4fV9pKSkKICBcXQoKLSAkXGxkb3RzJAoKLS0tCgpXaGVuIGFuIGV4cG9uZW50aWFsIGZhbWlseSBkaXN0cmlidXRpb24gaXMgYXNzdW1lZCBmb3IgdGhlIG91dGNvbWUgZGlzdHJpYnV0aW9uLCBhbmQgd2hlbiB0aGUgZGV2aWFuY2UgbG9zcyBpcyB1c2VkLCB0aGUgaW5zYW1wbGUgZXJyb3IgY2FuIGJlIGVzdGltYXRlZCBieSBtZWFucyBvZiB0aGUgQUlDIGFuZCBCSUMuCgojIyMgQWthaWtlJ3MgSW5mb3JtYXRpb24gQ3JpdGVyaW9uIChBSUMpCgpUaGUgQUlDIGZvciBhIG1vZGVsICRtJCBpcyBnaXZlbiBieQpcWwpcdGV4dHtBSUN9ID0gLTIgXGxuIFxoYXR7TH0obSkgKzJwClxdCndoZXJlICRcaGF0e0x9KG0pJCBpcyB0aGUgbWF4aW1pc2VkIGxpa2VsaWhvb2QgZm9yIG1vZGVsICRtJC4KCldoZW4gYXNzdW1pbmcgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgZXJyb3IgdGVybXMgYW5kIGhvbW9zY2VkYXN0aWNpdHksIHRoZSBBSUMgYmVjb21lcwpcWwpcdGV4dHtBSUN9ID0gblxsbiBcdGV4dHtTU0V9KG0pICsycCA9IG5cbG4oblxvdmVybGluZXtcdGV4dHtlcnJ9fShtKSkgKyAycApcXQp3aXRoICRcdGV4dHtTU0V9KG0pJCB0aGUgcmVzaWR1YWwgc3VtIG9mIHNxdWFyZXMgb2YgbW9kZWwgJG0kLgoKSW4gbGluZWFyIG1vZGVscyB3aXRoIG5vcm1hbCBlcnJvciB0ZXJtcywgTWFsbG93J3MgJENfcCQgY3JpdGVyaW9uIChzdGF0aXN0aWMpIGlzIGEgbGluZWFyaXNlZCB2ZXJzaW9uIG9mIEFJQyBhbmQgaXQgaXMgYW4gdW5iaWFzZWQgZXN0aW1hdG9yIG9mIHRoZSBpbi1zYW1wbGUgZXJyb3IuCgotLS0KCiMjIyBCYXllc2lhbiBJbmZvcm1hdGlvbiBDcml0ZXJpb24gKEJJQyl9CgpUaGUgQklDIGZvciBhIG1vZGVsICRtJCBpcyBnaXZlbiBieQpcWwpcdGV4dHtCSUN9ID0gLTIgXGxuIFxoYXR7TH0obSkgK3BcbG4obikKXF0Kd2hlcmUgJFxoYXR7TH0obSkkIGlzIHRoZSBtYXhpbWlzZWQgbGlrZWxpaG9vZCBmb3IgbW9kZWwgJG0kLgoKV2hlbiBhc3N1bWluZyBub3JtYWxseSBkaXN0cmlidXRlZCBlcnJvciB0ZXJtcyBhbmQgaG9tb3NjZWRhc3RpY2l0eSwgdGhlIEJJQyBiZWNvbWVzClxbClx0ZXh0e0JJQ30gPSBuXGxuIFx0ZXh0e1NTRX0obSkgK3BcbG4obikgPSBuXGxuKG5cb3ZlcmxpbmV7XHRleHR7ZXJyfX0obSkpICsgcFxsbihuKQpcXQp3aXRoICRcdGV4dHtTU0V9KG0pJCB0aGUgcmVzaWR1YWwgc3VtIG9mIHNxdWFyZXMgb2YgbW9kZWwgJG0kLgoKV2hlbiBsYXJnZSBkYXRhc2V0cyBhcmUgdXNlZCwgdGhlIEJJQyB3aWxsIGZhdm91ciBzbWFsbGVyIG1vZGVscyB0aGFuIHRoZSBBSUMuCgotLS0KCiMjIFRyYWluaW5nIGFuZCB0ZXN0IHNldHMKClNvbWV0aW1lcywgd2hlbiBhIGxhcmdlICh0cmFpbmluZykgZGF0YXNldCBpcyBhdmFpbGFibGUsIG9uZSBtYXkgZGVjaWRlIHRoZSBzcGxpdCB0aGUgZGF0YXNldCByYW5kb21seSBpbiBhCgotICoqdHJhaW5pbmcgZGF0YXNldCoqOgogICBkYXRhIGFyZSB1c2VkIGZvciBtb2RlbCBmaXR0aW5nIGFuZCBmb3IgbW9kZWwgYnVpbGRpbmcgb3IgZmVhdHVyZSBzZWxlY3Rpb24gKHRoaXMgbWF5IHJlcXVpcmUgZS5nLiBjcm9zcyB2YWxpZGF0aW9uKQoKLSAqKnRlc3QgZGF0YXNldCoqOgogICB0aGlzIGRhdGEgYXJlIHVzZWQgdG8gZXZhbHVhdGUgdGhlIGZpbmFsIG1vZGVsIChyZXN1bHQgb2YgbW9kZWwgYnVpbGRpbmcpLiBBbiB1bmJpYXNlZCBlc3RpbWF0ZSBvZiB0aGUgb3V0c2FtcGxlIGVycm9yIChpLmUuIHRlc3Qgb3IgZ2VuZXJhbGlzYXRpb24gZXJyb3IpIGJhc2VkIG9uIHRoaXMgdGVzdCBkYXRhIGlzCiAgXFsKICAgICBcZnJhY3sxfXttfSBcc3VtX3tpPTF9Xm0gXGxlZnQoXGhhdHttfShcbWF0aGJme3h9X2kpLVlfaVxyaWdodCleMiwKICBcXQogIHdoZXJlCiAgICAtICQoWV8xLFxtYXRoYmZ7eH1fMSksIFxsZG90cywgKFlfbSxcbWF0aGJme3h9X20pJCBkZW5vdGUgdGhlICRtJCBvYnNlcnZhdGlvbnMgaW4gdGhlIHRlc3QgZGF0YXNldAoKICAgIC0gJFxoYXR7bX0kIGlzIGVzdGltYXRlZCBmcm9tIHVzaW5nIHRoZSB0cmFpbmluZyBkYXRhICh0aGlzIG1heSBhbHNvIGJlIHRoZSByZXN1bHQgZnJvbSBtb2RlbCBidWlsZGluZywgdXNpbmcgb25seSB0aGUgdHJhaW5pbmcgZGF0YSkuCgotLS0KCk5vdGUgdGhhdCB0aGUgdHJhaW5pbmcgZGF0YXNldCBpcyB1c2VkIGZvciBtb2RlbCBidWlsZGluZyBvciBmZWF0dXJlIHNlbGVjdGlvbi4gVGhpcyBhbHNvIHJlcXVpcmVzIHRoZSBldmFsdWF0aW9uIG9mIG1vZGVscy4gRm9yIHRoZXNlIGV2YWx1YXRpb25zIHRoZSBtZXRob2RzIGZyb20gdGhlIHByZXZpb3VzIHNsaWRlcyBjYW4gYmUgdXNlZCAoZS5nLiBjcm9zcyB2YWxpZGF0aW9uLCAkayQtZm9sZCBDViwgTWFsbG93J3MgJENfcCQpLiBUaGUgdGVzdCBkYXRhc2V0IGlzIG9ubHkgdXNlZCBmb3IgdGhlICBldmFsdWF0aW9uIG9mIHRoZSBmaW5hbCBtb2RlbCAoZXN0aW1hdGVkIGFuZCBidWlsZCBmcm9tIHVzaW5nIG9ubHkgdGhlIHRyYWluaW5nIGRhdGEpLiBUaGUgZXN0aW1hdGUgb2YgdGhlIG91dHNhbXBsZSBlcnJvciBiYXNlZCBvbiB0aGUgdGVzdCBkYXRhc2V0IGlzIHRoZSBiZXN0IHBvc3NpYmxlIGVzdGltYXRlIGluIHRoZSBzZW5zZSB0aGF0IGl0IGlzIHVuYmlhc2VkLiBUaGUgb2JzZXJ2YXRpb25zIHVzZWQgZm9yIHRoaXMgZXN0aW1hdGlvbiBhcmUgaW5kZXBlbmRlbnQgb2YgdGhlIG9ic2VydmF0aW9ucyBpbiB0aGUgdHJhaW5pbmcgZGF0YS4KSG93ZXZlciwgaWYgdGhlIG51bWJlciBvZiBkYXRhIHBvaW50cyBpbiB0aGUgdGVzdCBkYXRhc2V0ICgkbSQpIGlzIHNtYWxsLCB0aGUgZXN0aW1hdGUgb2YgdGhlIG91dHNhbXBsZSBlcnJvciBtYXkgc2hvdyBsYXJnZSB2YXJpYW5jZSBhbmQgaGVuY2UgaXMgbm90IHJlbGlhYmxlLgoKIyBMb2dpc3RpYyBSZWdyZXNzaW9uIEFuYWx5c2lzIGZvciBIaWdoIERpbWVuc2lvbmFsIERhdGEKCiMjIEJyZWFzdCBDYW5jZXIgRXhhbXBsZQoKLSBTY2htaWR0ICpldCBhbC4qLCAyMDA4LCBDYW5jZXIgUmVzZWFyY2gsICoqNjgqKiwgNTQwNS01NDEzCgotIEdlbmUgZXhwcmVzc2lvbiBwYXR0ZXJucyBpbiAkbj0yMDAkIGJyZWFzdCB0dW1vcnMgd2VyZSBpbnZlc3RpZ2F0ZWQgKCRwPTIyMjgzJCBnZW5lcykKCi0gQWZ0ZXIgc3VyZ2VyeSB0aGUgdHVtb3JzIHdlcmUgZ3JhZGVkIGJ5IGEgcGF0aG9sb2dpc3QgKHN0YWdlIDEsMiwzKQoKLSBIZXJlIHRoZSBvYmplY3RpdmUgaXMgdG8gcHJlZGljdCBzdGFnZSAzIGZyb20gdGhlIGdlbmUgZXhwcmVzc2lvbiBkYXRhIChwcmVkaWN0aW9uIG9mIGJpbmFyeSBvdXRjb21lKQoKLSBJZiB0aGUgcHJlZGljdGlvbiBtb2RlbCB3b3JrcyB3ZWxsLCBpdCBjYW4gYmUgdXNlZCB0byBwcmVkaWN0IHRoZSBzdGFnZSBmcm9tIGEgYmlvcHN5IHNhbXBsZS4KCiMjIERhdGEKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojQmlvY01hbmFnZXI6Omluc3RhbGwoImdlbmVmdSIpCiNCaW9jTWFuYWdlcjo6aW5zdGFsbCgiYnJlYXN0Q2FuY2VyTUFJTloiKQoKbGlicmFyeShnZW5lZnUpCmxpYnJhcnkoYnJlYXN0Q2FuY2VyTUFJTlopCmRhdGEobWFpbnopCgpYIDwtIHQoZXhwcnMobWFpbnopKSAjIGdlbmUgZXhwcmVzc2lvbnMKbiA8LSBucm93KFgpCkggPC0gZGlhZyhuKS0xL24qbWF0cml4KDEsbmNvbD1uLG5yb3c9bikKWCA8LSBIJSolWApZIDwtIGlmZWxzZShwRGF0YShtYWlueikkZ3JhZGU9PTMsMSwwKQp0YWJsZShZKQoKc3ZkWCA8LSBzdmQoWCkKayA8LSAyClprIDwtIHN2ZFgkdVssMTprXSAlKiUgZGlhZyhzdmRYJGRbMTprXSkKY29sbmFtZXMoWmspIDwtIHBhc3RlMCgiWiIsMTprKQoKWmsgJT4lCiAgYXMuZGF0YS5mcmFtZSAlPiUKICBtdXRhdGUoZ3JhZGUgPSBZICU+JSBhcy5mYWN0b3IpICU+JQogIGdncGxvdChhZXMoeD0gWjEsIHkgPSBaMiwgY29sb3IgPSBncmFkZSkpICsKICBnZW9tX3BvaW50KHNpemUgPSAzKQpgYGAKCi0tLQoKIyMgTG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbHMKCkJpbmFyeSBvdXRjb21lcyBhcmUgb2Z0ZW4gYW5hbHlzZWQgd2l0aCAqKmxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWxzKiouCgpMZXQgJFkkIGRlbm90ZSB0aGUgYmluYXJ5ICgxLzAsIGNhc2UvY29udHJvbCwgcG9zaXRpdmUvbmVnYXRpdmUpIG91dGNvbWUsIGFuZCAkXG1hdGhiZnt4fSQgdGhlICRwJC1kaW1lbnNpb25hbCBwcmVkaWN0b3IuCgpMb2dpc3RpYyByZWdyZXNzaW9uICBhc3N1bWVzClxbCiAgIFkgXG1pZCBcbWF0aGJme3h9IFxzaW0gXHRleHR7QmVybm91bGxpfShccGkoXG1hdGhiZnt4fSkpClxdCndpdGggJFxwaShcbWF0aGJme3h9KSA9IFx0ZXh0e1B9XGxlZnRbWT0xXG1pZCBcbWF0aGJme3h9XHJpZ2h0XSQgYW5kClxbCiAgIFxsbiBcZnJhY3tccGkoXG1hdGhiZnt4fSl9ezEtXHBpKFxtYXRoYmZ7eH0pfT1cYmV0YV8wICsgXGJvbGRzeW1ib2x7XGJldGF9XlRcbWF0aGJme3h9LgpcXQoKVGhlIHBhcmFtZXRlcnMgYXJlIHR5cGljYWxseSBlc3RpbWF0ZWQgYnkgbWF4aW1pc2luZyB0aGUgbG9nLWxpa2VsaWhvb2QsIHdoaWNoIGlzIGRlbm90ZWQgYnkgJGwoXG1hdGhiZnsKXGJldGF9KSQsIGkuZS4KXFsKICAgXGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19ID0gXHRleHR7QXJnTWF4fV9cYmV0YSBsKFxib2xkc3ltYm9se1xiZXRhfSkuClxdCgotIE1heGltdW0gbGlrZWxpaG9vZCBpcyBvbmx5IGFwcGxpY2FibGUgd2hlbiAkbj5wJC4KCi0gV2hlbiAkcD5uJCBwZW5hbGlzZWQgbWF4aW11bSBsaWtlbGlob29kIG1ldGhvZHMgYXJlIGFwcGxpY2FibGUuCgotLS0KCiMjIFBlbmFsaXplZCBtYXhpbXVtIGxpa2VsaWhvb2QKClBlbmFsaXNlZCBlc3RpbWF0aW9uIG1ldGhvZHMgKGUuZy4gbGFzc28gYW5kIHJpZGdlKSBjYW4gYWxzIGJlIGFwcGxpZWQgdG8gbWF4aW11bSBsaWtlbGlob29kLCByZXN1bHRpbmcgaW4gdGhlICoqcGVuYWxpc2VkIG1heGltdW0gbGlrZWxpaG9vZCBlc3RpbWF0b3IqKi4KCkxhc3NvOgpcWwogIFxoYXR7XGJvbGRzeW1ib2x7XGJldGF9fSA9IFx0ZXh0e0FyZ01heH1fXGJldGEgbChcYm9sZHN5bWJvbHtcYmV0YX0pIC1cbGFtYmRhIFxWZXJ0IFxib2xkc3ltYm9se1xiZXRhfVxWZXJ0XzEuClxdCgpSaWRnZToKXFsKICBcaGF0e1xib2xkc3ltYm9se1xiZXRhfX0gPSBcdGV4dHtBcmdNYXh9X1xiZXRhIGwoXGJvbGRzeW1ib2x7XGJldGF9KSAtXGxhbWJkYSBcVmVydCBcYm9sZHN5bWJvbHtcYmV0YX1cVmVydF8yXjIuClxdCgpPbmNlIHRoZSBwYXJhbWV0ZXJzIGFyZSBlc3RpbWF0ZWQsIHRoZSBtb2RlbCBtYXkgYmUgdXNlZCB0byBjb21wdXRlClxbCiAgXGhhdHtccGl9KFxtYXRoYmZ7eH0pID0gXGhhdHtcdGV4dHtQfX1cbGVmdFtZPTFcbWlkIFxtYXRoYmZ7eH1ccmlnaHRdLgpcXQpXaXRoIHRoZXNlIGVzdGltYXRlZCBwcm9iYWJpbGl0aWVzIHRoZSBwcmVkaWN0aW9uIHJ1bGUgYmVjb21lcwpcYmVnaW57ZXFuYXJyYXkqfQogIFxoYXR7XHBpfShcbWF0aGJme3h9KSAmXGxlcSBjJiBcdGV4dHtwcmVkaWN0IH0gWT0wIFxcCiAgXGhhdHtccGl9KFxtYXRoYmZ7eH0pICY+YyAmIFx0ZXh0e3ByZWRpY3QgfSBZPTEKXGVuZHtlcW5hcnJheSp9CndpdGggJDA8YzwxJCBhIHRocmVzaG9sZCB0aGF0IGVpdGhlciBpcyBmaXhlZCAoZS5nLiAkYz0xLzIkKSwgZGVwZW5kcyBvbiBwcmlvciBwcm9iYWJpbGl0aWVzLCBvciBpcyBlbXBpcmljYWxseSBkZXRlcm1pbmVkIGJ5IG9wdGltaXNpbmcgZS5nLiB0aGUgQXJlYSBVbmRlciB0aGUgUk9DIEN1cnZlIChBVUMpIG9yIGJ5IGZpbmRpbmcgYSBnb29kIGNvbXByb21pc2UgYmV0d2VlbiBzZW5zaXRpdml0eSBhbmQgc3BlY2lmaWNpdHkuCgpOb3RlIHRoYXQgbG9naXN0aWMgcmVncmVzc2lvbiBkaXJlY3RseSBtb2RlbHMgdGhlICoqUG9zdGVyaW9yIHByb2JhYmlsaXR5KiogdGhhdCBhbiBvYnNlcnZhdGlvbiBiZWxvbmdzIHRvIGNsYXNzICRZPTEkLCBnaXZlbiB0aGUgcHJlZGljdG9yICRcbWF0aGJme3h9JC4KCiMjIE1vZGVsIGV2YWx1YXRpb24KCkNvbW1vbiBtb2RlbCBldmFsdWF0aW9uIGNyaXRlcmlhIGZvciBiaW5hcnkgcHJlZGljdGlvbiBtb2RlbHMgYXJlOgoKLSBzZW5zaXRpdml0eSA9IHRydWUgcG9zaXRpdmUgcmF0ZSAoVFBSKQoKLSBzcGVjaWZpY2l0eSA9IHRydWUgbmVnYXRpdmUgcmF0ZSAoVE5SKQoKLSBtaXNjbGFzc2lmaWNhdGlvbiBlcnJvcgoKLSBhcmVhIHVuZGVyIHRoZSBST0MgY3VydmUgKEFVQykKClRoZXNlIGNyaXRlcmlhIGNhbiBhZ2FpbiBiZSBlc3RpbWF0ZWQgdmlhIGNyb3NzIHZhbGlkYXRpb24gb3IgdmlhIHNwbGl0dGluZyBvZiB0aGUgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0L3ZhbGlkYXRpb24gZGF0YS4KCiMjIyBTZW5zaXRpdml0eSBvZiBhIG1vZGVsICRccGkkIHdpdGggdGhyZXNob2xkICRjJAoKU2Vuc2l0aXZpdHkgaXMgdGhlIHByb2JhYmlsaXR5IHRvIGNvcnJlY3RseSBwcmVkaWN0IGEgcG9zaXRpdmUgb3V0Y29tZToKXFsKXHRleHR7c2Vuc30oXHBpLGMpPVx0ZXh0e1B9X3tYXip9XGxlZnRbXGhhdFxwaShcbWF0aGJme1h9XiopPmMgXG1pZCBZXio9MSBcbWlkIHtcbWF0aGNhbHtUfX1ccmlnaHRdLgpcXQoKSXQgaXMgYWxzbyBrbm93biBhcyB0aGUgdHJ1ZSBwb3NpdGl2ZSByYXRlIChUUFIpLgoKIyMjIFNwZWNpZmljaXR5IG9mIGEgbW9kZWwgJFxwaSQgd2l0aCB0aHJlc2hvbGQgJGMkCgpTcGVjaWZpY2l0eSBpcyB0aGUgcHJvYmFiaWxpdHkgdG8gY29ycmVjdGx5IHByZWRpY3QgYSBuZWdhdGl2ZSBvdXRjb21lOgpcWwpcdGV4dHtzcGVjfShccGksYyk9XHRleHR7UH1fe1heKn1cbGVmdFtcaGF0XHBpKFxtYXRoYmZ7WH1eKilcbGVxIGMgXG1pZCBZXio9MCBcbWlkIHtcbWF0aGNhbHtUfX1ccmlnaHRdLgpcXQoKSXQgaXMgYWxzbyBrbm93biBhcyB0aGUgdHJ1ZSBuZWdhdGl2ZSByYXRlIChUTlIpLgoKLS0tCgojIyMgTWlzY2xhc3NpZmljYXRpb24gZXJyb3Igb2YgYSBtb2RlbCAkXHBpJCB3aXRoIHRocmVzaG9sZCAkYyQKClRoZSBtaXNjbGFzc2lmaWNhdGlvbiBlcnJvciBpcyB0aGUgcHJvYmFiaWxpdHkgdG8gaW5jb3JyZWN0bHkgcHJlZGljdCBhbiBvdXRjb21lOgpcYmVnaW57ZXFuYXJyYXkqfQpcdGV4dHttY2V9KFxwaSxjKSAmPSZcdGV4dHtQfV97WF4qLFleKn1cbGVmdFtcaGF0XHBpKFxtYXRoYmZ7WH0pXGxlcSBjIFx0ZXh0eyBhbmQgfSBZXio9MSBcbWlkIHtcbWF0aGNhbHtUfX1ccmlnaHRdIFxcCiYgICYgKyBcdGV4dHtQfV97WF4qLFleKn1cbGVmdFtcaGF0XHBpKFxtYXRoYmZ7WH0pPiBjIFx0ZXh0eyBhbmQgfSBZXio9MCBcbWlkIHtcbWF0aGNhbHtUfX1ccmlnaHRdLgpcZW5ke2VxbmFycmF5Kn0KCk5vdGUgdGhhdCBpbiB0aGUgZGVmaW5pdGlvbnMgb2Ygc2Vuc2l0aXZpdHksIHNwZWNpZmljaXR5IGFuZCB0aGUgbWlzY2xhc3NpZmljYXRpb24gZXJyb3IsIHRoZSBwcm9iYWJpbGl0aWVzIHJlZmVyIHRvIHRoZSBkaXN0cmlidXRpb24gb2YgdGhlICAkKFxtYXRoYmZ7WH1eKixZXiopJCwgd2hpY2ggaXMgaW5kZXBlbmRlbnQgb2YgdGhlIHRyYWluaW5nIGRhdGEsIGNvbmRpdGlvbmFsIG9uIHRoZSB0cmFpbmluZyBkYXRhLiBUaGlzIGlzIGluIGxpbmUgd2l0aCB0aGUgdGVzdCBvciBnZW5lcmFsaXNhdGlvbiBlcnJvci4gVGhlIG1pc2NsYXNzaWZpY2F0aW9uIGVycm9yIGlzIGFjdHVhbGx5IHRoZSB0ZXN0IGVycm9yIHdoZW4gYSAwLzEgbG9zcyBmdW5jdGlvbiBpcyB1c2VkLiBKdXN0IGFzIGJlZm9yZSwgdGhlIHNlbnNpdGl2aXR5LCBzcGVjaWZpY2l0eSBhbmQgdGhlIG1pc2NsYXNzaWZpY2F0aW9uIGVycm9yIGNhbiBhbHNvIGJlIGF2ZXJhZ2VkIG92ZXIgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgdHJhaW5pbmcgZGF0YSBzZXQsIHdoaWNoIGlzIGluIGxpbmUgd2l0aCB0aGUgZXhwZWN0ZWQgdGVzdCBlcnJvciB3aGljaCBoYXMgYmVlbiBkaXNjdXNzZWQgZWFybGllci4KCi0tLQoKIyMjIFJPQyBjdXJ2ZSBvZiBhIG1vZGVsICRccGkkCgpUaGUgUmVjZWl2ZXIgT3BlcmF0aW5nIENoYXJhY3RlcmlzdGljIChST0MpIGN1cnZlIGZvciBtb2RlbCAkXHBpJCBpcyBnaXZlbiBieSB0aGUgZnVuY3Rpb24KClxbClx0ZXh0e1JPQ306IFswLDFdIFxyaWdodGFycm93IFswLDFdXHRpbWVzIFswLDFdOiBjIFxtYXBzdG8gKDEtXHRleHR7c3BlY30oXHBpLGMpLCBcdGV4dHtzZW5zfShccGksYykpLgpcXQoKRm9yIHdoZW4gJGMkIG1vdmVzIGZyb20gMSB0byAwLCB0aGUgUk9DIGZ1bmN0aW9uIGRlZmluZXMgYSBjdXJ2ZSBpbiB0aGUgcGxhbmUgJFswLDFdXHRpbWVzIFswLDFdJCwgbW92aW5nIGZyb20gJCgwLDApJCBmb3IgJGM9MSQgdG8gJCgxLDEpJCBmb3IgJGM9MCQuCgpUaGUgaG9yaXpvbnRhbCBheGlzIG9mIHRoZSBST0MgY3VydmUgc2hvd3MgMS1zcGVjaWZpY2l0eS4gVGhpcyBpcyBhbHNvIGtub3duIGFzIHRoZSBGYWxzZSBQb3NpdGl2ZSBSYXRlIChGUFIpLgoKLS0tCgojIyMgQXJlYSB1bmRlciB0aGUgY3VydmUgKEFVQykgb2YgYSBtb2RlbCAkXHBpJAoKVGhlIGFyZWEgdW5kZXIgdGhlIGN1cnZlIChBVUMpIGZvciBtb2RlbCAkXHBpJCBpcyBhcmVhIHVuZGVyIHRoZSBST0MgY3VydmUgYW5kIGlzIGdpdmVuIGJ5ClxbClxpbnRfMF4xIFx0ZXh0e1JPQ30oYykgZGMuClxdCgpTb21lIG5vdGVzIGFib3V0IHRoZSBBVUM6CgotIEFVQz0wLjUgcmVzdWx0cyB3aGVuIHRoZSBST0MgY3VydmUgaXMgdGhlIGRpYWdvbmFsLiBUaGlzIGNvcnJlc3BvbmRzIHRvIGZsaXBwaW5nIGEgY29pbiwgaS5lLiBhIGNvbXBsZXRlIHJhbmRvbSBwcmVkaWN0aW9uLgoKLSBBVUM9MSByZXN1bHRzIGZyb20gdGhlIHBlcmZlY3QgUk9DIGN1cnZlLCB3aGljaCBpcyB0aGUgUk9DIGN1cnZlIHRocm91Z2ggdGhlIHBvaW50cyAkKDAsMCkkLCAkKDAsMSkkIGFuZCAkKDEsMSkkLiBUaGlzIFJPQyBjdXJ2ZSBpbmNsdWRlcyBhIHRocmVzaG9sZCAkYyQgc3VjaCB0aGF0IHNlbnNpdGl2aXR5IGFuZCBzcGVjaWZpY2l0eSBhcmUgZXF1YWwgdG8gb25lLgoKIyMgQnJlYXN0IGNhbmNlciBleGFtcGxlCgojIyMgRGF0YQoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkoZ2xtbmV0KQoKI0Jpb2NNYW5hZ2VyOjppbnN0YWxsKCJnZW5lZnUiKQojQmlvY01hbmFnZXI6Omluc3RhbGwoImJyZWFzdENhbmNlck1BSU5aIikKCmxpYnJhcnkoZ2VuZWZ1KQpsaWJyYXJ5KGJyZWFzdENhbmNlck1BSU5aKQpkYXRhKG1haW56KQoKWCA8LSB0KGV4cHJzKG1haW56KSkgIyBnZW5lIGV4cHJlc3Npb25zCm4gPC0gbnJvdyhYKQpIIDwtIGRpYWcobiktMS9uKm1hdHJpeCgxLG5jb2w9bixucm93PW4pClggPC0gSCUqJVgKWSA8LSBpZmVsc2UocERhdGEobWFpbnopJGdyYWRlPT0zLDEsMCkKdGFibGUoWSkKYGBgCgotLS0KCkZyb20gdGhlIHRhYmxlIG9mIHRoZSBvdXRjb21lcyBpbiBZIHdlIHJlYWQgdGhhdAoKLSBgciBzdW0oWT09MSlgIHR1bW9ycyB3ZXJlIGdyYWRlZCBhcyBzdGFnZSAzIGFuZAotIGByIHN1bShZPT0wKWAgdHVtb3JzIHdlcmUgZ3JhZGVkIGFzIHN0YWdlIDEgb3IgMi4KCkluIHRoaXMgdGhlIHN0YWdlIDMgdHVtb3JzIGFyZSByZWZlcnJlZCB0byBhcyBjYXNlcyBvciBwb3N0aXZlcyBhbmQgdGhlIHN0YWdlIDEgYW5kIDIgdHVtb3JzIGFzIGNvbnRyb2xzIG9yIG5lZ2F0aXZlcy4KCi0tLQoKIyMjIFRyYWluaW5nIGFuZCB0ZXN0IGRhdGFzZXQKClRoZSB1c2Ugb2YgdGhlIGxhc3NvIGxvZ2lzdGljIHJlZ3Jlc3Npb24gZm9yIHRoZSBwcmVkaWN0aW9uIG9mIHN0YWdlIDMgYnJlYXN0IGNhbmNlciBpcyBpbGx1c3RyYXRlZCBoZXJlIGJ5CgotIHJhbmRvbWx5IHNwbGl0dGluZyB0aGUgZGF0YXNldCBpbnRvIGEgdHJhaW5pbmcgZGF0YXNldCAoJDgwXCUkIG9mIGRhdGEgPSAxNjAgdHVtb3JzKSBhbmQgYSB0ZXN0IGRhdGFzZXQgKDQwIHR1bW9ycykKCi0gdXNpbmcgdGhlIHRyYWluaW5nIGRhdGEgdG8gc2VsZWN0IGEgZ29vZCAkXGxhbWJkYSQgdmFsdWUgaW4gdGhlIGxhc3NvIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgKHRocm91Z2ggMTAtZm9sZCBDVikKCi0gZXZhbHVhdGluZyB0aGUgZmluYWwgbW9kZWwgYnkgbWVhbnMgb2YgdGhlIHRlc3QgZGF0YXNldCAoUk9DIEN1cnZlLCBBVUMpLgoKCmBgYHtyfQoKIyMgVXNlZCB0byBwcm92aWRlIHNhbWUgcmVzdWx0cyBhcyBpbiBwcmV2aW91cyBSIHZlcnNpb24KUk5Ha2luZChzYW1wbGUua2luZCA9ICJSb3VuZGluZyIpCnNldC5zZWVkKDY5NzczMjYpCiMjIyMKCm4gPC0gbnJvdyhYKQpuVHJhaW4gPC0gcm91bmQoMC44Km4pCm5UcmFpbgoKaW5kVHJhaW4gPC0gc2FtcGxlKDE6bixuVHJhaW4pClhUcmFpbiA8LSBYW2luZFRyYWluLF0KWVRyYWluIDwtIFlbaW5kVHJhaW5dClhUZXN0IDwtIFhbLWluZFRyYWluLF0KWVRlc3QgPC0gWVstaW5kVHJhaW5dCnRhYmxlKFlUZXN0KQpgYGAKCk5vdGUgdGhhdCB0aGUgcmFuZG9tbHkgc2VsZWN0ZWQgdGVzdCBkYXRhIGhhcyBgciBtZWFuKFlUZXN0PT0xKSoxMDBgJSBjYXNlcyBvZiBzdGFnZSAzIHR1bW9ycy4KVGhpcyBpcyBhIGJpdCBoaWdoZXIgdGhhbiB0aGUgYHIgbWVhbihZPT0xKSoxMDBgJSAgaW4gdGhlIGNvbXBsZXRlIGRhdGEuCgpPbmUgY291bGQgYWxzbyBwZXJmb3JtIHRoZSByYW5kb20gc3BsaXR0aW5nIGFtb25nIHRoZSBwb3NpdGl2ZXMgYW5kIHRoZSBuZWdhdGl2ZXMgc2VwYXJhdGVseSAoc3RyYXRpZmllZCBzcGxpdHRpbmcpLgoKIyMjIE1vZGVsIGZpdHRpbmcgYmFzZWQgb24gdHJhaW5pbmcgZGF0YQoKYGBge3J9Cm1MYXNzbyA8LSBnbG1uZXQoCiAgeCA9IFhUcmFpbiwKICB5ID0gWVRyYWluLAogIGFscGhhID0gMSwKICBmYW1pbHk9ImJpbm9taWFsIikgICMgbGFzc286IGFscGhhID0gMQoKcGxvdChtTGFzc28sIHh2YXIgPSAibGFtYmRhIiwgeGxpbSA9IGMoLTYsLTEuNSkpCmBgYAoKLS0tCgpgYGB7cn0KbUN2TGFzc28gPC0gY3YuZ2xtbmV0KAogIHggPSBYVHJhaW4sCiAgeSA9IFlUcmFpbiwKICBhbHBoYSA9IDEsCiAgdHlwZS5tZWFzdXJlID0gImNsYXNzIiwKCWZhbWlseSA9ICJiaW5vbWlhbCIpICAjIGxhc3NvIGFscGhhID0gMQoKcGxvdChtQ3ZMYXNzbykKbUN2TGFzc28KYGBgCgpUaGUgdG90YWwgbWlzY2xhc3NpZmljYXRpb24gZXJyb3IgaXMgdXNlZCBoZXJlIHRvIHNlbGVjdCBhIGdvb2QgdmFsdWUgZm9yICRcbGFtYmRhJC4KCmBgYHtyfQojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKCJwbG90Uk9DIikKbGlicmFyeShwbG90Uk9DKQoKZGZMYXNzb09wdCA8LSBkYXRhLmZyYW1lKAogIHBpID0gcHJlZGljdChtQ3ZMYXNzbywKICAgIG5ld3ggPSBYVGVzdCwKICAgIHMgPSBtQ3ZMYXNzbyRsYW1iZGEubWluLAogICAgdHlwZSA9ICJyZXNwb25zZSIpICU+JSBjKC4pLAogIGtub3duLnRydXRoID0gWVRlc3QpCgpyb2MgPC0KICBkZkxhc3NvT3B0ICAlPiUKICBnZ3Bsb3QoYWVzKGQgPSBrbm93bi50cnV0aCwgbSA9IHBpKSkgKwogIGdlb21fcm9jKG4uY3V0cyA9IDApICsKICB4bGFiKCIxLXNwZWNpZmljaXR5IChGUFIpIikgKwogIHlsYWIoInNlbnNpdGl2aXR5IChUUFIpIikKCnJvYwoKY2FsY19hdWMocm9jKQpgYGAKCi0gVGhlIFJPQyBjdXJ2ZSBpcyBzaG93biBmb3IgdGhlIG1vZGVsIGJhc2VkIG9uICRcbGFtYmRhJCB3aXRoIHRoZSBzbWFsbGVzdCBtaXNjbGFzc2lmaWNhdGlvbiBlcnJvci4gVGhlIG1vZGVsIGhhcyBhbiBBVUMgb2YgYHIgY2FsY19hdWMocm9jKSAlPiUgcHVsbChBVUMpICU+JSByb3VuZCgyKWAuCgotIEJhc2VkIG9uIHRoaXMgUk9DIGN1cnZlIGFuIGFwcHJvcHJpYXRlIHRocmVzaG9sZCAkYyQgY2FuIGJlIGNob3Nlbi4gRm9yIGV4YW1wbGUsIGZyb20gdGhlIFJPQyBjdXJ2ZSB3ZSBzZWUgdGhhdCBpdCBpcyBwb3NzaWJsZSB0byBhdHRhaW4gYSBzcGVjaWZpY2l0eSBhbmQgYSBzZW5zaXRpdml0eSBvZiA3NVwlLgoKLSBUaGUgc2Vuc2l0aXZpdGllcyBhbmQgc3BlY2lmaWNpdGllcyBpbiB0aGUgUk9DIGN1cnZlIGFyZSB1bmJpYXNlZCAoaW5kZXBlbmRlbnQgdGVzdCBkYXRhc2V0KSBmb3IgdGhlIHByZWRpY3Rpb24gbW9kZWwgYnVpbGQgZnJvbSB0aGUgdHJhaW5pbmcgZGF0YS4gVGhlIGVzdGltYXRlcyBvZiBzZW5zaXRpdml0eSBhbmQgc3BlY2lmaWNpdHksIGhvd2V2ZXIsIGFyZSBiYXNlZCBvbiBvbmx5IDQwIG9ic2VydmF0aW9ucy4KCi0tLQoKYGBge3J9Cm1MYW1iZGFPcHQgPC0gZ2xtbmV0KHggPSBYVHJhaW4sCiAgeSA9IFlUcmFpbiwKICBhbHBoYSA9IDEsCiAgbGFtYmRhID0gbUN2TGFzc28kbGFtYmRhLm1pbiwKICBmYW1pbHk9ImJpbm9taWFsIikKCnFwbG90KAogIHN1bW1hcnkoY29lZihtTGFtYmRhT3B0KSlbLTEsMV0sCiAgc3VtbWFyeShjb2VmKG1MYW1iZGFPcHQpKVstMSwzXSkgKwogIHhsYWIoImdlbmUgSUQiKSArCiAgeWxhYigiYmV0YS1oYXQiKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikKYGBgCgotIFRoZSBtb2RlbCB3aXRoIHRoZSBvcHRpbWFsICRcbGFtYmRhJCBoYXMgb25seSBgciBtTGFtYmRhT3B0ICU+JSBjb2VmICU+JSBzdW1tYXJ5ICU+JSBucm93YCBub24temVybyBwYXJhbWV0ZXIgZXN0aW1hdGVzLgotIFRodXMgb25seSBgciBtTGFtYmRhT3B0ICU+JSBjb2VmICU+JSBzdW1tYXJ5ICU+JSBucm93YCBnZW5lcyBhcmUgaW52b2x2ZWQgaW4gdGhlIHByZWRpY3Rpb24gbW9kZWwuCi0gVGhlc2UgYHIgbUxhbWJkYU9wdCAlPiUgY29lZiAlPiUgc3VtbWFyeSAlPiUgbnJvd2AgcGFyYW1ldGVyIGVzdGltYXRlcyBhcmUgcGxvdHRpbmcgaW4gdGhlIGdyYXBoLgpBIGxpc3Rpbmcgb2YgdGhlIG1vZGVsIG91dHB1dCB3b3VsZCBzaG93IHRoZSBuYW1lcyBvZiB0aGUgZ2VuZXMuCgotLS0KCmBgYHtyfQoKZGZMYXNzbzFzZSA8LSBkYXRhLmZyYW1lKAogIHBpID0gcHJlZGljdChtQ3ZMYXNzbywKICAgIG5ld3ggPSBYVGVzdCwKICAgIHMgPSBtQ3ZMYXNzbyRsYW1iZGEuMXNlLAogICAgdHlwZSA9ICJyZXNwb25zZSIpICU+JSBjKC4pLAogIGtub3duLnRydXRoID0gWVRlc3QpCgpyb2MgPC0KICByYmluZCgKICAgIGRmTGFzc29PcHQgJT4lCiAgICAgIG11dGF0ZShtZXRob2QgPSAibWluIiksCiAgICBkZkxhc3NvMXNlICU+JQogICAgICBtdXRhdGUobWV0aG9kID0gIjFzZSIpCiAgKSAlPiUKICBnZ3Bsb3QoYWVzKGQgPSBrbm93bi50cnV0aCwgbSA9IHBpLCBjb2xvciA9IG1ldGhvZCkpICsKICBnZW9tX3JvYyhuLmN1dHMgPSAwKSArCiAgeGxhYigiMS1zcGVjaWZpY2l0eSAoRlBSKSIpICsKICB5bGFiKCJzZW5zaXRpdml0eSAoVFBSKSIpCgpyb2MKCmNhbGNfYXVjKHJvYykKYGBgCgotIFdoZW4gdXNpbmcgdGhlICRcbGFtYmRhJCBvZiB0aGUgb3B0aW1hbCBtb2RlbCB1cCB0byAxIHN0YW5kYXJkIGRldmlhdGlvbiwgYSBkaWFnb25hbCBST0MgY3VydmUgaXMgb2J0YWluZWQgYW5kIGhlbmNlIEFVQyBpcyAkMC41JC4KCi0gVGhpcyBwcmVkaWN0aW9uIG1vZGVsIGlzIHRodXMgZXF1aXZhbGVudCB0byBmbGlwcGluZyBhIGNvaW4gZm9yIG1ha2luZyB0aGUgcHJlZGljdGlvbi4KCi0gVGhlIHJlYXNvbiBpcyB0aGF0IHdpdGggdGhpcyBjaG9pY2Ugb2YgJFxsYW1iZGEkIChzdHJvbmcgcGVuYWxpc2F0aW9uKSBhbG1vc3QgYWxsIHByZWRpY3RvcnMgYXJlIHJlbW92ZWQgZnJvbSB0aGUgbW9kZWwuCgotIFRoZXJlZm9yZSwgZG8gbmV2ZXIgYmxpbmRseSBjaG9vc2UgZm9yIHRoZSBgYG9wdGltYWwnJyAkXGxhbWJkYSQgYXMgZGVmaW5lZCBoZXJlLCBidXQgYXNzZXNzIHRoZSBwZXJmb3JtYW5jZSBvZiB0aGUgbW9kZWwgZmlyc3QuCgpgYGB7cn0KbUxhbWJkYTFzZSA8LSBnbG1uZXQoeCA9IFhUcmFpbiwKICB5ID0gWVRyYWluLAogIGFscGhhID0gMSwKICBsYW1iZGEgPSBtQ3ZMYXNzbyRsYW1iZGEuMXNlLAogIGZhbWlseT0iYmlub21pYWwiKQoKbUxhbWJkYTFzZSAlPiUKICBjb2VmICU+JQogIHN1bW1hcnkKYGBgCgotLS0KCiMjIFRoZSBFbGFzdGljIE5ldAoKVGhlIGxhc3NvIGFuZCByaWRnZSByZWdyZXNzaW9uIGhhdmUgcG9zaXRpdmUgYW5kIG5lZ2F0aXZlIHByb3BlcnRpZXMuCgotIExhc3NvCgogICAtIHBvc2l0aXZlOiBzcGFyc2Ugc29sdXRpb24KCiAgIC0gbmVnYXRpdmU6IGF0IG1vc3QgJFxtaW4obixwKSQgcHJlZGljdG9ycyBjYW4gYmUgc2VsZWN0ZWQKCiAgIC0gbmVnYXRpdmU6IHRlbmQgdG8gc2VsZWN0IG9uZSBwcmVkaWN0b3IgYW1vbmcgYSBncm91cCBvZiBoaWdobHkgY29ycmVsYXRlZCBwcmVkaWN0b3JzCgoKLSBSaWRnZQoKICAgIC0gbmVnYXRpdmU6IG5vIHNwYXJzZSBzb2x1dGlvbgogICAgLSBwb3NpdGl2ZTogbW9yZSB0aGFuICRcbWluKG4scCkkIHByZWRpY3RvcnMgY2FuIGJlIHNlbGVjdGVkCgpBIGNvbXByb21pc2UgYmV0d2VlbiBsYXNzbyBhbmQgcmlkZ2U6IHRoZSAqKmVsYXN0aWMgbmV0Kio6ClxbCiAgXGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19ID0gXHRleHR7QXJnTWF4fV9cYmV0YSBsKFxib2xkc3ltYm9se1xiZXRhfSkgLVxnYW1tYV8xIFxWZXJ0IFxib2xkc3ltYm9sXGJldGFcVmVydF8xIC1cZ2FtbWFfMiBcVmVydCBcYm9sZHN5bWJvbFxiZXRhXFZlcnRfMl4yLgpcXQoKVGhlIGVsYXN0aWMgZ2l2ZXMgYSBzcGFyc2Ugc29sdXRpb24gd2l0aCBwb3RlbnRpYWxseSBtb3JlIHRoYW4gJFxtaW4obixwKSQgcHJlZGljdG9ycy4KCi0tLQoKVGhlIGBnbG1uZXRgIFIgZnVuY3Rpb24gdXNlcyB0aGUgZm9sbG93aW5nIHBhcmFtZXRlcmlzYXRpb24sClxbCiAgXGhhdHtcYm9sZHN5bWJvbHtcYmV0YX19ID0gXHRleHR7QXJnTWF4fV9cYmV0YSBsKFxib2xkc3ltYm9se1xiZXRhfSkgLVxsYW1iZGFcYWxwaGEgXFZlcnQgXGJvbGRzeW1ib2xcYmV0YVxWZXJ0XzEgLVxsYW1iZGEoMS1cYWxwaGEpIFxWZXJ0IFxib2xkc3ltYm9sXGJldGFcVmVydF8yXjIuClxdCgotICRcYWxwaGEkIHBhcmFtZXRlciBnaXZlcyB3ZWlnaHQgdG8gJExfMSQgcGVuYWx0eSB0ZXJtIChoZW5jZSAkXGFscGhhPTEkIGdpdmVzIHRoZSBsYXNzbywgYW5kICRcYWxwaGE9MCQgZ2l2ZXMgcmlkZ2UpLgoKLSBhICRcbGFtYmRhJCBwYXJhbWV0ZXIgdG8gZ2l2ZSB3ZWlnaHQgdG8gdGhlIHBlbmFsaXNhdGlvbgoKLSBOb3RlIHRoYXQgdGhlIGNvbWJpbmF0aW9uIG9mICRcbGFtYmRhJCBhbmQgJFxhbHBoYSQgZ2l2ZXMgdGhlIHNhbWUgZmxleGliaWxpdHkgYXMgdGhlIGNvbWJpbmF0aW9uIG9mIHRoZSBwYXJhbWV0ZXJzICRcbGFtYmRhXzEkIGFuZCAkXGxhbWJkYV8yJC4KCi0tLQoKIyMjIEJyZWFzdCBjYW5jZXIgZXhhbXBsZQoKYGBge3J9Cm1FbGFzdGljIDwtIGdsbW5ldCgKICB4ID0gWFRyYWluLAogIHkgPSBZVHJhaW4sCiAgYWxwaGEgPSAwLjUsCiAgZmFtaWx5PSJiaW5vbWlhbCIpICAjIGVsYXN0aWMgbmV0CgpwbG90KG1FbGFzdGljLCB4dmFyID0gImxhbWJkYSIseGxpbT1jKC01LjUsLTEpKQpgYGAKCmBgYHtyfQptQ3ZFbGFzdGljIDwtIGN2LmdsbW5ldCh4ID0gWFRyYWluLAogIHkgPSBZVHJhaW4sCiAgYWxwaGEgPSAwLjUsCiAgZmFtaWx5ID0gImJpbm9taWFsIiwKCXR5cGUubWVhc3VyZSA9ICJjbGFzcyIpICAjIGVsYXN0aWMgbmV0CgpwbG90KG1DdkVsYXN0aWMpCm1DdkVsYXN0aWMKYGBgCgpgYGB7cn0KZGZFbGFzdCA8LSBkYXRhLmZyYW1lKAogIHBpID0gcHJlZGljdChtRWxhc3RpYywKICAgIG5ld3ggPSBYVGVzdCwKICAgIHMgPSBtQ3ZFbGFzdGljJGxhbWJkYS5taW4sCiAgICB0eXBlID0gInJlc3BvbnNlIikgJT4lIGMoLiksCiAga25vd24udHJ1dGggPSBZVGVzdCkKCnJvYyA8LSByYmluZCgKICBkZkxhc3NvT3B0ICU+JSBtdXRhdGUobWV0aG9kID0gImxhc3NvIiksCiAgZGZFbGFzdCAlPiUgbXV0YXRlKG1ldGhvZCA9ICJlbGFzdC4gbmV0IikpICU+JQogIGdncGxvdChhZXMoZCA9IGtub3duLnRydXRoLCBtID0gcGksIGNvbG9yID0gbWV0aG9kKSkgKwogIGdlb21fcm9jKG4uY3V0cyA9IDApICsKICB4bGFiKCIxLXNwZWNpZmljaXR5IChGUFIpIikgKwogIHlsYWIoInNlbnNpdGl2aXR5IChUUFIpIikKCnJvYwoKY2FsY19hdWMocm9jKQpgYGAKCi0gTW9yZSBwYXJhbWV0ZXJzIGFyZSB1c2VkIHRoYW4gZm9yIHRoZSBsYXNzbywgYnV0IHRoZSBwZXJmb3JtYW5jZSBkb2VzIG5vdCBpbXByb3ZlLgoKYGBge3J9Cm1FbGFzdGljT3B0IDwtIGdsbW5ldCh4ID0gWFRyYWluLAogIHkgPSBZVHJhaW4sCiAgYWxwaGEgPSAwLjUsCiAgbGFtYmRhID0gbUN2RWxhc3RpYyRsYW1iZGEubWluLAogIGZhbWlseT0iYmlub21pYWwiKQoKcXBsb3QoCiAgc3VtbWFyeShjb2VmKG1FbGFzdGljT3B0KSlbLTEsMV0sCiAgc3VtbWFyeShjb2VmKG1FbGFzdGljT3B0KSlbLTEsM10pICsKICB4bGFiKCJnZW5lIElEIikgKwogIHlsYWIoImJldGEtaGF0IikgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpCmBgYAoKIyBBY2tub3dsZWRnZW1lbnQgey19CgotIE9saXZpZXIgVGhhcyBmb3Igc2hhcmluZyBoaXMgbWF0ZXJpYWxzIG9mIEFuYWx5c2lzIG9mIEhpZ2ggRGltZW5zaW9uYWwgRGF0YSAyMDE5LTIwMjAsIHdoaWNoIEkgdXNlZCBhcyB0aGUgc3RhcnRpbmcgcG9pbnQgZm9yIHRoaXMgY2hhcHRlci4KCmBgYHtyLCBjaGlsZD0iX3Nlc3Npb24taW5mby5SbWQifQpgYGAK