1 Foreword: About the Machine Learning in Medicine (MLM) project

The MLM project has been initialized in 2016 and aims to:

  1. Encourage using Machine Learning techniques in medical research in Vietnam and

  2. Promote the use of R statistical programming language, an open source and leading tool for practicing data science.

2 Introduction

In the last tutorial X13, we have introduced the feature selection methods for a classification problem. This time, we will do the same thing but for a regression problem and with new tools. Given a dataset with a large matrix of covariates X and we want to build a linear regression model for estimating a response variable Y. This problem rises two questions:

I) Among many potential features in the original dataset, how many and which features should be included in the model ?

II) How important are those features ?

This problem could be resolved either before (using feature selection methods) or during the model training process. When linear regression algorithm is selected for fitting the model, some methods could be considered for both model regularisation and feature selection.

3 Objective

In the present tutorial, we will explore 5 different methods for model selection. These include:

  1. Stepwise linear regression (the conventional method)
  2. Lasso regularisation
  3. Elastic net regularisation
  4. Bayesian Model averaging (based on MCMC sampler)
  5. Wrapped learner with random feature selection

4 Materials and method

Our data experiment implies the “Body Fat” dataset by Roger W. Johnson and Carleton College https://ww2.amstat.org/publications/jse/v4n1/datasets.johnson.html

The author measured percentage of body fat, age, weight, height, and ten body circumference measurements (e.g., abdomen) for 251 men. The Body fat was measured by an underwater weighing technique. Their study aimed to predict body fat in terms of other measurements using multiple regression.

The caret package was used for training a Stepwise GLM algorithm. Lasso and elastic net logistic models are supported by GLMnet package. The MCMC based Bayesian Model averaging was done using BMS package. We used mlr package for wrapping a logistic learner with random feature selection integrated.

The performance of 5 regression models will be compared.

5 Preparation

First, we will prepare the ggplot theme for our experiment

library(tidyverse)

theme_bare <- function(base_size=8,base_family="sans"){theme_bw(base_size = base_size, base_family = base_family)+
    theme(
      axis.line = element_blank(), 
      axis.text.x = element_blank(), 
      axis.text.y = element_blank(),
      axis.ticks = element_blank(), 
      axis.title.x = element_blank(), 
      axis.title.y = element_blank(), 
      legend.position = "bottom", 
      panel.background = element_rect(fill = NA), 
      panel.border = element_blank(), 
      panel.grid.major = element_blank(), 
      panel.grid.minor = element_blank(), 
      plot.margin = unit(c(0,0,0,0), "lines")
    )
}

my_theme <- function(base_size =5, base_family = "sans"){
  theme_bw(base_size = base_size, base_family = base_family) +
    theme(
      panel.grid.major = element_line(color = "gray"),
      panel.grid.minor = element_blank(),
      panel.background = element_rect(fill = NA),
      strip.background = element_rect(fill = "#001d60", color = "#00113a", size =0.5),
      strip.text = element_text(face = "bold", size = 5, color = "white"),
      legend.position = "bottom",
      legend.justification = "center",
      legend.background = element_blank(),
      legend.margin = margin(0.5,0.5,0.5,0.5)
    )
}

theme_set(my_theme())

First, we will load the dataframe to R:

df=read.table("https://ww2.amstat.org/publications/jse/datasets/fat.dat.txt")

df= df[,c(2, 5:7, 10:19)]

names(df)=c("brozek_fat","age","weight","height","neck","chest","abdomen","hip","thigh","knee","ankle","biceps","forearm","wrist")
df= df[-42,]

head(df)%>%knitr::kable()
brozek_fat age weight height neck chest abdomen hip thigh knee ankle biceps forearm wrist
12.6 23 154.25 67.75 36.2 93.1 85.2 94.5 59.0 37.3 21.9 32.0 27.4 17.1
6.9 22 173.25 72.25 38.5 93.6 83.0 98.7 58.7 37.3 23.4 30.5 28.9 18.2
24.6 22 154.00 66.25 34.0 95.8 87.9 99.2 59.6 38.9 24.0 28.8 25.2 16.6
10.9 26 184.75 72.25 37.4 101.8 86.4 101.2 60.1 37.3 22.8 32.4 29.4 18.2
27.8 24 184.25 71.25 34.4 97.3 100.0 101.9 63.2 42.2 24.0 32.2 27.7 17.7
20.6 24 210.25 74.75 39.0 104.5 94.4 107.8 66.0 42.0 25.6 35.7 30.6 18.8

The original dataset contains 1 response variable (brozek_fat) and 13 features that would be included as potential predictors in our model.

Hmisc::describe(df)
## df 
## 
##  14  Variables      251  Observations
## ---------------------------------------------------------------------------
## brozek_fat 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0      174        1    18.89    8.807     6.85     8.80 
##      .25      .50      .75      .90      .95 
##    12.80    19.00    24.55    28.80    31.20 
## 
## lowest :  0.0  1.9  4.1  4.6  4.7, highest: 33.8 34.7 36.5 38.2 45.1
## ---------------------------------------------------------------------------
## age 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0       51    0.999    44.89    14.35     25.0     27.0 
##      .25      .50      .75      .90      .95 
##     35.5     43.0     54.0     63.0     67.0 
## 
## lowest : 22 23 24 25 26, highest: 69 70 72 74 81
## ---------------------------------------------------------------------------
## weight 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0      196        1    178.8    31.83    136.4    146.8 
##      .25      .50      .75      .90      .95 
##    158.8    176.2    196.9    217.0    225.8 
## 
## lowest : 118.50 125.00 125.25 125.75 126.50, highest: 241.75 244.25 247.25 262.75 363.15
## ---------------------------------------------------------------------------
## height 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0       47    0.999    70.31    2.983    66.00    67.00 
##      .25      .50      .75      .90      .95 
##    68.25    70.00    72.25    73.75    74.50 
## 
## lowest : 64.00 64.75 65.00 65.50 65.75, highest: 75.25 75.50 76.00 77.50 77.75
## ---------------------------------------------------------------------------
## neck 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0       90        1       38    2.685    34.25    35.10 
##      .25      .50      .75      .90      .95 
##    36.40    38.00    39.45    40.90    41.85 
## 
## lowest : 31.1 31.5 32.8 33.2 33.4, highest: 42.5 42.8 43.2 43.9 51.2
## ---------------------------------------------------------------------------
## chest 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0      173        1    100.8     9.35     89.0     91.1 
##      .25      .50      .75      .90      .95 
##     94.3     99.6    105.3    112.3    116.4 
## 
## lowest :  79.3  83.4  85.1  86.0  86.7, highest: 119.8 119.9 121.6 128.3 136.2
## ---------------------------------------------------------------------------
## abdomen 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0      185        1    92.51    11.87    76.85    79.50 
##      .25      .50      .75      .90      .95 
##    84.55    90.90    99.20   105.70   110.80 
## 
## lowest :  69.4  70.4  72.8  73.7  73.9, highest: 115.9 118.0 122.1 126.2 148.1
## ---------------------------------------------------------------------------
## hip 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0      151        1    99.84    7.509    89.15    91.80 
##      .25      .50      .75      .90      .95 
##    95.50    99.30   103.35   108.60   111.85 
## 
## lowest :  85.0  85.3  87.2  87.5  87.6, highest: 114.3 114.4 116.1 125.6 147.7
## ---------------------------------------------------------------------------
## thigh 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0      138        1    59.36    5.702    51.15    53.00 
##      .25      .50      .75      .90      .95 
##    56.00    59.00    62.30    65.80    68.45 
## 
## lowest : 47.2 49.3 49.6 50.0 50.1, highest: 71.2 72.5 72.9 74.4 87.3
## ---------------------------------------------------------------------------
## knee 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0       90        1    38.57    2.668    34.80    35.60 
##      .25      .50      .75      .90      .95 
##    36.95    38.50    39.90    41.70    42.65 
## 
## lowest : 33.0 33.4 33.5 33.7 34.2, highest: 44.0 44.2 45.0 46.0 49.1
## ---------------------------------------------------------------------------
## ankle 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0       61    0.999     23.1    1.701    21.00    21.50 
##      .25      .50      .75      .90      .95 
##    22.00    22.80    24.00    24.80    25.45 
## 
## lowest : 19.1 19.7 20.1 20.2 20.4, highest: 26.6 27.0 29.6 33.7 33.9
## ---------------------------------------------------------------------------
## biceps 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0      104        1    32.27    3.406    27.60    28.70 
##      .25      .50      .75      .90      .95 
##    30.20    32.00    34.35    36.20    37.20 
## 
## lowest : 24.8 25.3 25.6 25.8 26.0, highest: 38.2 38.4 38.5 39.1 45.0
## ---------------------------------------------------------------------------
## forearm 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0       77        1    28.66    2.256    25.70    26.20 
##      .25      .50      .75      .90      .95 
##    27.30    28.70    30.00    31.10    31.75 
## 
## lowest : 21.0 22.0 23.1 24.6 24.8, highest: 32.8 33.1 33.7 33.8 34.9
## ---------------------------------------------------------------------------
## wrist 
##        n  missing distinct     Info     Mean      Gmd      .05      .10 
##      251        0       44    0.998    18.23    1.047     16.8     17.0 
##      .25      .50      .75      .90      .95 
##     17.6     18.3     18.8     19.4     19.8 
## 
## lowest : 15.8 16.1 16.3 16.5 16.6, highest: 20.1 20.2 20.4 20.9 21.4
## ---------------------------------------------------------------------------

6 Data visualising

First, we would like to know how the data are distributed among 251 men, as well as the correlation pattern among the variables:

plotfuncLow <- function(data,mapping){
  p <- ggplot(data = data,mapping=mapping)+geom_point(aes(fill=df$brozek_fat,color=df$brozek_fat),shape=21,alpha=0.3,show.legend = F)+geom_smooth(method="lm",se=F,color="red4")+scale_fill_gradient2(low="#ccfc2d",mid="#fccc2d",high="#fc2d2d")+scale_color_gradient2(low="#ccfc2d",mid="#fccc2d",high="#fc2d2d")
  p
}

plotfuncmid <- function(data,mapping){
  p <- ggplot(data = data,mapping=mapping)+geom_histogram(aes(),alpha=0.5,fill="#fc881b",color="#fc411b")+scale_fill_gradient2(low="#ccfc2d",mid="#fccc2d",high="#fc2d2d")
  p
}

library(GGally)

ggpairs(df,columns=1:14,lower=list(continuous=plotfuncLow),upper=NULL,diag=list(continuous=plotfuncmid))

library(corrplot)
library(RColorBrewer)

cor_mat=as.matrix(cor(method="pearson",as.matrix(df)))

cor_mat%>%corrplot(.,order="hclust",type="lower",method="color",tl.col="black", tl.srt=45,tl.cex=0.5,col=rev(brewer.pal(n=10, name="Spectral")))

A simple correlation network analysis can help us to identify 6 variable that have the strongest correlations (absolute value of Pearson’s r coefficient were greater than 0.5) with the Body fat. Those variables included Abdomen, Chest, Weight, Hip, Thigh and Knee.

dfscale<-df%>%as.matrix()%>%scale()%>%as_tibble()

m=as.matrix(cor(as.matrix(dfscale)))

library(igraph)

diag(m)<-0

library(ggraph)

cdf=data.frame(row=rownames(m)[row(m)[upper.tri(m)]], 
               col=colnames(m)[col(m)[upper.tri(m)]], 
               corr=m[upper.tri(m)])

names(cdf)=c("from","to","corr")

cdf=subset(cdf,cdf$from=="brozek_fat" & abs(cdf$corr)>0.5)

graph<-graph_from_data_frame(cdf)

ggraph(graph, layout = 'star',circular=F)+geom_edge_fan(aes(colour = corr,width=corr),alpha=0.4)+geom_node_label(aes(label = name))+coord_fixed()+scale_edge_color_gradient(high="#ff003f",low="#0094ff")+scale_edge_width_continuous()+theme_bare(10)

7 Feature selection for a regression task

First, there are 6 methods that could be used to select features for a regression problem:

http://mlr-org.github.io/mlr-tutorial/devel/html/filter_methods/index.html

vimplot=function(data,target,method){
  train.task=makeRegrTask(data=data, target=target)
  imp_var=generateFilterValuesData(train.task, method=method)
  fsdf=imp_var$data%>%.[,-2]
  names(fsdf)=c("Feature","Value")
  fsdf$Method=method
  plot=fsdf%>%ggplot(aes(x=reorder(fsdf$Feature,fsdf$Value),y=Value,fill=Value,color=Value))+geom_segment(aes(x=reorder(fsdf$Feature,fsdf$Value),xend=Feature,y=0,yend=Value),size=1,show.legend = F)+geom_point(size=2,show.legend = F)+scale_x_discrete("Features")+scale_y_continuous(method)+coord_flip()+scale_fill_gradient(low="blue",high="red")+scale_color_gradient(low="blue",high="red")+ggtitle(method)
  return(plot)
}

p1=vimplot(data=df,target="brozek_fat",method="mrmr")

#mrmr

p2=vimplot(data=df,target="brozek_fat",method="linear.correlation")

#carscore

p3=vimplot(data=df,target="brozek_fat",method="carscore")

#variance

p4=vimplot(data=df,target="brozek_fat",method="variance")

#IG

p5=vimplot(data=df,target="brozek_fat",method="information.gain")

#cforest

p6=vimplot(data=df,target="brozek_fat",method="cforest.importance")


library(gridExtra)

grid.arrange(p1,p2,p3,p4,p5,p6,ncol=3)

8 STEPWISE GLM

First, we will consider the Stepwise GLM, but this time we will extend it a little bit with a Bootstrapping. This could be done using caret package:

library(caret)

crt=trainControl(method="boot",number=100,verboseIter = F)
                
set.seed(123)

glmcar= caret::train(data=df, brozek_fat~ ., 
              method = "glmStepAIC", 
              trControl = crt,
              verbose = FALSE)

Despite that the model training costs so much time, the result is not very good, as we could see below:

The Rsquare of the survived model was only 0.7 and there are 8 features included

glmcar
## Generalized Linear Model with Stepwise Feature Selection 
## 
## 251 samples
##  13 predictor
## 
## No pre-processing
## Resampling: Bootstrapped (100 reps) 
## Summary of sample sizes: 251, 251, 251, 251, 251, 251, ... 
## Resampling results:
## 
##   RMSE      Rsquared
##   4.296321  0.695796
summary(glmcar)
## 
## Call:
## NULL
## 
## Deviance Residuals: 
##      Min        1Q    Median        3Q       Max  
## -10.0169   -2.7372   -0.1662    2.7053    9.5436  
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -19.55135   10.88287  -1.797  0.07366 .  
## age           0.05679    0.02874   1.976  0.04927 *  
## weight       -0.08235    0.03708  -2.221  0.02729 *  
## neck         -0.41978    0.20893  -2.009  0.04563 *  
## abdomen       0.88059    0.06686  13.171  < 2e-16 ***
## hip          -0.20147    0.13016  -1.548  0.12297    
## thigh         0.27889    0.12011   2.322  0.02106 *  
## forearm       0.47843    0.17280   2.769  0.00606 ** 
## wrist        -1.37393    0.47426  -2.897  0.00411 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for gaussian family taken to be 15.75356)
## 
##     Null deviance: 14915.5  on 250  degrees of freedom
## Residual deviance:  3812.4  on 242  degrees of freedom
## AIC: 1415.2
## 
## Number of Fisher Scoring iterations: 2

9 LASSO AND ELASTIC NET REGULARISATIONS

Lasso penalizes the absolute values of some coefficients toward zero, so it will remove the less important features from the model. This method has proven to be useful for feature selection in problems with large number of covariates. Lasso is also better than Ridge regularization if our goal would be optimizing the interpretability of the model. When there are many correlated features, lasso tends to let only one of them survive and eliminate other from the final model by shrinking their coefficient to zero.

The elastic net regularization is a flexible solution between Ridge and Lasso, as it combines both l1 and l2 penalties under a parameter called alpha (when a=0, the regularization is strictly Ridge, while a=1 corresponds to lasso penalty). This method provides the strength of both types of regularization, since the lasso optimizes feature selection and interpretability while Ridge allows grouping effect (the correlated variables will be grouped together then depending on the penalty strength, they could contribute to the prediction all at once or totally dropped out from the model.

Both Lasso and Elastic net regularisations are supported by the glmnet package:

#Lasso model

library(glmnet)

lasso = cv.glmnet(x=as.matrix(df[,-1]),y=df$brozek_fat,alpha=1,nfolds=5)

plot(lasso,xvar="lambda")

coef(lasso)
## 14 x 1 sparse Matrix of class "dgCMatrix"
##                       1
## (Intercept) -4.40419369
## age          0.03491352
## weight       .         
## height      -0.25287929
## neck         .         
## chest        .         
## abdomen      0.60370088
## hip          .         
## thigh        .         
## knee         .         
## ankle        .         
## biceps       .         
## forearm      .         
## wrist       -0.89634390
#Elastic net

enet= cv.glmnet(x=as.matrix(df[,-1]),y=df$brozek_fat,nfolds=5)

plot(enet)

coef(enet)
## 14 x 1 sparse Matrix of class "dgCMatrix"
##                       1
## (Intercept) -6.53342851
## age          0.02997906
## weight       .         
## height      -0.24850948
## neck         .         
## chest        .         
## abdomen      0.58853559
## hip          .         
## thigh        .         
## knee         .         
## ankle        .         
## biceps       .         
## forearm      .         
## wrist       -0.70732370

Note that each method provided a different model structure:

We might believe that Abdomen would be important, as this variable was included in both Lasso and Elastic net model.

10 BAYESIAN MODEL AVERAGING

Given a response variable Y and a matrix of all potential predictors (features) X, y could be estimated from X by a linear model :

\[ y= \beta_{0} + \beta_{\gamma} X_{\gamma}+\varepsilon \]

Such model is defined by a constant beta0, a subset of coefficients beta that correspond to a feature subset and E is the residual error that would follow the normal distribution with zero mean.

The goal of Bayesian Model averaging method is to construct a weighted average over all possible combination of features. The model weights are determined from posterior model probabilities that could be estimated from classic Bayes theorem:

\[p(M_{\gamma }|y,X) = \frac{p(y|M_{\gamma},X)p(M_{\gamma})}{p(y|X))} = \frac{p(y|M_{\gamma},X)p(M_{\gamma})}{\sum_{s=1}^{2^K} p(y|M_s,X)p(M_s)}\]

Where K is the number of potential features

p(y|X) indicates the integrated likelihood, it’s simply a constant term over all models.

The PMP p(M|y,X) could be interpreted as “probability of a specific model giving a matrix X and response variable Y, over all possible models“ or posterior model probability (PMP).

PMP is proportional to the marginal likelihood of the model p(y|M,X) = probability of the data given the model M) times a prior model probability p(M)

The model prior indicates the searcher’s belief about the model before looking at data (for example, the searcher believes strongly that a biomarker Y is always associated to patient’s gender, age and weight, so a prior could be formed, based on which there is a high probability for the model that contains Age, gender and Weight over all possible models of other covariates.

When there is no prior knowledge about the problem, a popular choice is to set a uniform prior probability.

The model weighted posterior distribution for any coefficients (beta) could be estimated by:

\[p(\beta |y,X)=\sum_{\gamma =1}^{2^K}p(\beta |M_\gamma ,y,X)p(M_\gamma|X,y)\]

Though BMA could be performed in a straightforward way, that means to enumerate all possible variable combinations until we got the posterior result, MCMC sampling is a more powerful method for BMA. The MCMC sampler should be considered for large dataset with more than 20 variables. MCMC sampler aims to gather the most important part of the posterior model distribution, thus only approximate it but as closely as possible.

In our experiment, we will apply a MCMC sampler that based on based on Metropolis-Hastings algorithm. Our sampler walks through the model space as follow:

At the step I, the sampler stands at a given model Mi with posterior p(Mi|y,X), on the next step i+1, a new candidate Mj is given and becomes the current model. The sampler switches from Mi to Mj with a probability pi,j:

\[p_{i,j}=min(1,p(M_i | y,X)/p(M_i |y,x))\]

If the model Mj is considered worse than Mi, it will be rejected and the sampler moves to the next step and a new candidate Mk will be proposed against Mi. In the case Mj is accepted, it will replace Mi as the current model and has to survive against further candidate models in the next step. The number of times each model survives will converge to the distribution of posterior model probabilities (PMP) p(Mi|y,X).

Here, we apply the standard MCMC sampler called “birth death”. At each step i, the sampler randomly picks one feature Fi among K potential features. If Fi is already included in the current model Mi , the new candidate model Mj on next step will include all the features from Mi except for Fi that will be dropped out. If the current model Mi does not contain Fi, then the candidate model will include all the feature set from Mi plus the chosen feature Fi.

The accuracy of PMP approximation depends on the number of iteration steps that MCMC sampler runs through. The first part of such procedure must also be omitted from the approximation (burn-ins or warming-up). The argument burn specifies the number of burn-ins and the argument iter the number of subsequent iterations to be retained.

Model-specific g prior

In BMA, g prior measures the level of certainty for a zero coefficient. A small g implies a strong belief of researcher (conservative prior) that the coefficients are zero, a large g indicates that the searcher is highly uncertain that coefficients are zero.

The posterior distribution of coefficients reflect prior uncertainty, for a given g, it follows a t-distribution with expected value E(beta|y,X,g,M) = standard OLS beta * g/(1+g)

The smaller g, the more important is the prior, and the more the expected value of coefficients is shrunk toward the prior mean zero.

In MCMC sampling, the model g-prior is not fixed but should be specified from information from data (y,X). The Empirical Bayes g local (EBL) algorithm sets gi = max(0,F_OLS -1) where F_OLS is the standard OLS F-statistic for model Mi.

Now it’s time for coding; We will use the BMS package: The following command consists of a MCMC with 1000 warmup steps and 10,000 steps to approximate the PMP of models. The sampler implied an EBL model specific g prior, the model prior is uniform. The MCMC algorithm is “Brith and death”

library(BMS)

bmsmod= bms(df, burn = 1000, iter = 10000, g = "EBL",mprior = "uniform",mcmc="bdn", user.int = F)

First, we extract the coefficients from averaged model output

The first column in this matrix shows all 13 variable names. First we look at the Post mean, which is the coefficients averaged over all models, including the models that does not include that variable (the coefficient will be zero). The coefficients posterior standard deviation (Post SD) might indicate the certainty of the positive or negative effect. The coefficient sign can also be inferred from the Cond Pos Sign, indicating the posterior probability of a positive coefficient expected value conditional on inclusion or “sign certainty”

The importance of each variable in predicting outcome is measured by PIP which represents the posterior inclusion probabilities or the sim of PMPs for all models wherein that feature was included. The Post mean column also indicate the positive or negative effect of a feature

For example: we can say that Abdomen would be the mist important variable. It was included in all models, it has a certain positive effect on Body fat with standardised coefficient of 1.26.

The less important features will have lower unconditional coefficients since their coefficients beta were null in most of models.

The final column denotes the original order of features in dataset

coef(bmsmod, std.coefs = T, order.by.pip = T, include.constant = T)
##                PIP     Post Mean    Post SD Cond.Pos.Sign Idx
## abdomen     1.0000  1.2579739893 0.09422997     1.0000000   6
## weight      0.9317 -0.4410399100 0.17323011     0.0000000   2
## wrist       0.8131 -0.1270660003 0.07994976     0.0000000  13
## forearm     0.6211  0.0700082566 0.06598249     1.0000000  12
## neck        0.2790 -0.0344094725 0.06710771     0.0000000   4
## biceps      0.2291  0.0227760452 0.05131205     1.0000000  11
## thigh       0.1885  0.0210560768 0.05624798     0.9994695   8
## hip         0.1834 -0.0303147090 0.08711173     0.0000000   7
## age         0.1579  0.0105590771 0.03152008     0.9898670   1
## height      0.1379 -0.0047555336 0.02478991     0.1254532   3
## ankle       0.1139  0.0038949770 0.01909055     0.9894644  10
## knee        0.0979  0.0038758457 0.02460337     0.9223698   9
## chest       0.0857 -0.0005275042 0.02950314     0.4527421   5
## (Intercept) 1.0000 -3.7175785209         NA            NA   0
summary(bmsmod)
## Mean no. regressors               Draws             Burnins 
##            "4.8392"             "10000"              "1000" 
##                Time  No. models visited      Modelspace 2^K 
##     "1.320708 secs"              "3325"              "8192" 
##           % visited         % Topmodels            Corr PMP 
##                "41"                "98"            "0.9899" 
##            No. Obs.         Model Prior             g-Prior 
##               "251"     "uniform / 6.5"               "EBL" 
##     Shrinkage-Stats 
##         "Av=0.9928"

Corr PMP indicates the correlation between the iteration (MCMC sampler) counts and analytical PMP for best models

At 0.993, this correlation indicates a good degree of convergence

To verify how far the posterior model size distribution matches up to our prior, the convergence between analytical and MCMC PMP could be compared on a graph:

plotModelsize(bmsmod)

The posterior model probability curve is well different from our prior. The graph indicates that models that include 4 features have highest probability.

The importance and potential combination of features could be visualized by this graph:

Here Red color indicates a positive coefficient, Skyblue refers to a negative coefficient, and white corresponds to non-inclusion (with zero beta coefficient).

The horizontal axis shows the best models, based on PMPs. The models are ranked from left to right.

image(bmsmod,col=c("#199ac1","#e30c37"))

Based on this graph, the best model would include 4 features which are Abdomen, Weight, Wrist and Forearm (note that this was different to the model structure as suggested by Lasso regularisation).

We can ask for the 3 best models :

This is a binary representation of models’ structure (1 means included, 0 is excluded). The result also provides PMP (posterior model probability) values estimated by either exact enumeration or by MCMC sampler

topmodels.bma(bmsmod)[,1:3]
##                  0883       0881       0885
## age         0.0000000 0.00000000 0.00000000
## weight      1.0000000 1.00000000 1.00000000
## height      0.0000000 0.00000000 0.00000000
## neck        0.0000000 0.00000000 0.00000000
## chest       0.0000000 0.00000000 0.00000000
## abdomen     1.0000000 1.00000000 1.00000000
## hip         0.0000000 0.00000000 0.00000000
## thigh       0.0000000 0.00000000 0.00000000
## knee        0.0000000 0.00000000 0.00000000
## ankle       0.0000000 0.00000000 0.00000000
## biceps      0.0000000 0.00000000 1.00000000
## forearm     1.0000000 0.00000000 0.00000000
## wrist       1.0000000 1.00000000 1.00000000
## PMP (Exact) 0.1364928 0.05813918 0.03899962
## PMP (MCMC)  0.1447000 0.05170000 0.03200000

11 WRAPPER method

Wrapper methods use the performance of a learning algorithm to assess the usefulness of a feature set. In order to select a feature subset a learner is trained repeatedly on different feature subsets and the subset which leads to the best learner performance is chosen.

The search strategy is defined by functions following the naming convention makeFeatSelControl<search_strategy. The following search strategies are available:

  1. Exhaustive search (makeFeatSelControlExhaustive),
  2. Genetic algorithm (makeFeatSelControlGA),
  3. Random search (makeFeatSelControlRandom),
  4. Deterministic forward or backward search (makeFeatSelControlSequential)

The exhaustive search is a brute force method, it will search for the best feature set among all possible combinations of our features. Such method is only feasible for simple problem (K<5). For larger dataset it’s not recommmended as the computation cost is too high.

In our experiment, we will apply a random search. As the BMS model already suggested high PMP for model of 4 variables, we will set the maximal number of variables to 4 in random search.

A wrapper consists of 3 elements:

  1. A resampling protocole, which could be Cross-validation, K folds CV, Bootstrap…

  2. A feature selection control mode, which is Random search in our example

  3. A basic learner which is linear regression (glm based learner)

We must also create a regression task for training

task=makeRegrTask(data=df,target="brozek_fat")

rdesc= makeResampleDesc("CV",iters=100L)
ctrlRdm=makeFeatSelControlRandom(maxit=100L,max.features=4)
wrap=makeFeatSelWrapper(learner = "regr.glm", resampling = rdesc,control=ctrlRdm)
modwrap= train(wrap,task)

Here is the result of model training:

modwrap$learner.model$next.model$learner.model%>%summary()
## 
## Call:
## stats::glm(formula = f, family = family, data = d, control = ctrl, 
##     model = FALSE)
## 
## Deviance Residuals: 
##      Min        1Q    Median        3Q       Max  
## -11.3968   -2.8413   -0.3158    2.9395    9.4035  
## 
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)    
## (Intercept) -18.27167    9.88286  -1.849  0.06568 .  
## weight       -0.08398    0.03269  -2.569  0.01078 *  
## abdomen       0.91221    0.05353  17.041  < 2e-16 ***
## hip          -0.10900    0.11525  -0.946  0.34522    
## wrist        -1.16974    0.41694  -2.806  0.00542 ** 
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for gaussian family taken to be 16.57369)
## 
##     Null deviance: 14915.5  on 250  degrees of freedom
## Residual deviance:  4077.1  on 246  degrees of freedom
## AIC: 1424
## 
## Number of Fisher Scoring iterations: 2

The model contains indeed 4 variables, but its structure is totally different to other methods.

12 COMPARATIVE STUDY

We have performed so far 5 different methods for feature/model selection. We can see that they are not equivalent, each one suggested a different feature subset. Before closing our experiment, we will perform a small comparative study that aims to evaluate the performance of each one among those 5 models.

The model validation is based upon 4 metrics:

  1. Rsquare
  2. Sum of absolute errors
  3. Root mean square error
  4. mean absolute error
#Combining them together:

dummylrner=makeLearner("regr.glm")
dummymod=train(dummylrner,task)
dummypred=predict(dummymod,task)

BMSpred=predict(bmsmod,df)%>%as.vector()
LASSOpred=predict(lasso,as.matrix(df[,-1]))%>%as.vector()
ENETpred=predict(enet,as.matrix(df[,-1]))%>%as.vector()
WRAPpred=predict(modwrap$learner.model$next.model$learner.model)%>%as.vector()
STEPpred=predict(glmcar,df)%>%as.vector()

pred1=dummypred
pred1$data$response=BMSpred
pred2=dummypred
pred2$data$response=LASSOpred
pred3=dummypred
pred3$data$response=ENETpred
pred4=dummypred
pred4$data$response=WRAPpred
pred5=dummypred
pred5$data$response=STEPpred

measure=list(rsq,rmse,mae,sae)
p1=performance(pred1,measure)
p2=performance(pred2,measure)
p3=performance(pred3,measure)
p4=performance(pred4,measure)
p5=performance(pred5,measure)

pdf=rbind(p1,p2,p3,p4,p5)%>%as_tibble()%>%mutate(.,Method=c("BMS","LASSO","ENET","WRAPPER","STEPWISE"))

pdfl=pdf%>%gather(rsq:sae,key="Metric",value="Value")

ggplot(pdfl)+geom_tile(aes(y=Method,x=Metric,fill=Value),color="black")+scale_fill_viridis(option="A",begin=0.7,end=0.95)+geom_text(aes(y=Method,x=Metric,label=round(Value,3)),color="black")+ggtitle("Comparison")+theme_bw(15)

pdf2=cbind(BMSpred,LASSOpred,ENETpred,WRAPpred,STEPpred)%>%as_tibble()%>%mutate(.,Truth=df$brozek_fat)
pdf2%>%gather(BMSpred:Truth,key="Models",value="Value")%>%ggplot(aes(x=Value,fill=Models))+geom_density(alpha=0.3)+theme_bw()

13 Conclusion

In this case study, we have explored 5 different methods for model (and feature) selection applied to a regression problem. It’s clear that those 5 methods are not equivalent in terms of feature subset and model’s performance.

BMA, Lasso and Elasticnet are more effective than Stepwise and Wrapper methods. BMA is the most interesting method, as this method provides many useful information for both feature selection and model interpretation, including the posterior model probabilities and certainty of coefficient values. The BMA also performed better than Elastic net and Lasso models.

In our example, the stepwise model selection performed surprisingly well, as it has the lowest error and largest R2 value. However, the cost of such performance could be high. The methods that based on brute force such as Exhaustive feature tuning, Forward and Backward Stepwise selection… are not recommended for large datasets.

Though the wrapper method is scalable (it could be applied to any algorithm, not only the GLM learner), such method is not reliable, due to a poor reproducibility and high computation cost. Despite that Wrapper is based on model performance, its final outcome was worse than Bayesian Model averaging method.

The Elasticnet performed better than Lasso. This regularisation was also integrated within the GLM algorithm by h2o.

In conclusion, if your study is interpretive, BMA is the best choice. For predictive purpose, Lasso and Elastic net are very good solutions. The wrapper method is not recommended unless you want to imply unusual algorithms.

Thank for joining us and see you in the next tutorial

END

LS0tDQp0aXRsZTogIk1MTSBDYXNlIHN0dWR5IFgxNCINCnN1YnRpdGxlOiAiTW9kZWwgc2VsZWN0aW9uLiBQYXJ0IDE6IFJlZ3Jlc3Npb24gcHJvYmxlbSINCmF1dGhvcjogIk5oYXQgTmFtIExlIERvbmcgKE1ELFBoRCkiDQpkYXRlOiAiSnVseSAwMSwgMjAxNyINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIHRoZW1lOiBqb3VybmFsDQogICAgdG9jOiBUUlVFDQogICAgdG9jX2Zsb2F0OiBUUlVFDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQoNCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZXJyb3IgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UpDQoNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShncmlkRXh0cmEpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShtbHIpDQpsaWJyYXJ5KGNvcnJwbG90KQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQpsaWJyYXJ5KEdHYWxseSkNCmxpYnJhcnkodmlyaWRpc0xpdGUpDQpsaWJyYXJ5KHZpcmlkaXMpDQpsaWJyYXJ5KGlncmFwaCkNCmxpYnJhcnkoZ2dyYXBoKQ0KbGlicmFyeShCTVMpDQpsaWJyYXJ5KGdsbW5ldCkNCmBgYA0KDQohW10oQk1BLnBuZykNCg0KIyBGb3Jld29yZDogQWJvdXQgdGhlIE1hY2hpbmUgTGVhcm5pbmcgaW4gTWVkaWNpbmUgKE1MTSkgcHJvamVjdA0KDQpUaGUgTUxNIHByb2plY3QgaGFzIGJlZW4gaW5pdGlhbGl6ZWQgaW4gMjAxNiBhbmQgYWltcyB0bzogDQoNCigxKSBFbmNvdXJhZ2UgdXNpbmcgTWFjaGluZSBMZWFybmluZyB0ZWNobmlxdWVzIGluIG1lZGljYWwgcmVzZWFyY2ggaW4gVmlldG5hbSBhbmQNCg0KKDIpIFByb21vdGUgdGhlIHVzZSBvZiBSIHN0YXRpc3RpY2FsIHByb2dyYW1taW5nIGxhbmd1YWdlLCBhbiBvcGVuIHNvdXJjZSBhbmQgbGVhZGluZyB0b29sIGZvciBwcmFjdGljaW5nIGRhdGEgc2NpZW5jZS4NCg0KIyBJbnRyb2R1Y3Rpb24NCg0KSW4gdGhlIGxhc3QgdHV0b3JpYWwgWDEzLCB3ZSBoYXZlIGludHJvZHVjZWQgdGhlIGZlYXR1cmUgc2VsZWN0aW9uIG1ldGhvZHMgZm9yIGEgY2xhc3NpZmljYXRpb24gcHJvYmxlbS4gVGhpcyB0aW1lLCB3ZSB3aWxsIGRvIHRoZSBzYW1lIHRoaW5nIGJ1dCBmb3IgYSByZWdyZXNzaW9uIHByb2JsZW0gYW5kIHdpdGggbmV3IHRvb2xzLiBHaXZlbiBhIGRhdGFzZXQgd2l0aCBhIGxhcmdlIG1hdHJpeCBvZiBjb3ZhcmlhdGVzIFggYW5kIHdlIHdhbnQgdG8gYnVpbGQgYSBsaW5lYXIgcmVncmVzc2lvbiBtb2RlbCBmb3IgZXN0aW1hdGluZyBhIHJlc3BvbnNlIHZhcmlhYmxlIFkuIFRoaXMgcHJvYmxlbSByaXNlcyB0d28gcXVlc3Rpb25zOiANCg0KKkkpIEFtb25nIG1hbnkgcG90ZW50aWFsIGZlYXR1cmVzIGluIHRoZSBvcmlnaW5hbCBkYXRhc2V0LCBob3cgbWFueSBhbmQgd2hpY2ggZmVhdHVyZXMgc2hvdWxkIGJlIGluY2x1ZGVkIGluIHRoZSBtb2RlbCA/Kg0KDQoqSUkpIEhvdyBpbXBvcnRhbnQgYXJlIHRob3NlIGZlYXR1cmVzID8qDQoNClRoaXMgcHJvYmxlbSBjb3VsZCBiZSByZXNvbHZlZCBlaXRoZXIgYmVmb3JlICh1c2luZyBmZWF0dXJlIHNlbGVjdGlvbiBtZXRob2RzKSBvciBkdXJpbmcgdGhlIG1vZGVsIHRyYWluaW5nIHByb2Nlc3MuIFdoZW4gbGluZWFyIHJlZ3Jlc3Npb24gYWxnb3JpdGhtIGlzIHNlbGVjdGVkIGZvciBmaXR0aW5nIHRoZSBtb2RlbCwgc29tZSBtZXRob2RzIGNvdWxkIGJlIGNvbnNpZGVyZWQgZm9yIGJvdGggbW9kZWwgcmVndWxhcmlzYXRpb24gYW5kIGZlYXR1cmUgc2VsZWN0aW9uLiANCg0KIyBPYmplY3RpdmUNCg0KSW4gdGhlIHByZXNlbnQgdHV0b3JpYWwsIHdlIHdpbGwgZXhwbG9yZSA1IGRpZmZlcmVudCBtZXRob2RzIGZvciBtb2RlbCBzZWxlY3Rpb24uIFRoZXNlIGluY2x1ZGU6DQoNCjEpIFN0ZXB3aXNlIGxpbmVhciByZWdyZXNzaW9uICh0aGUgY29udmVudGlvbmFsIG1ldGhvZCkNCjIpIExhc3NvIHJlZ3VsYXJpc2F0aW9uDQozKSBFbGFzdGljIG5ldCByZWd1bGFyaXNhdGlvbg0KNCkgQmF5ZXNpYW4gTW9kZWwgYXZlcmFnaW5nIChiYXNlZCBvbiBNQ01DIHNhbXBsZXIpDQo1KSBXcmFwcGVkIGxlYXJuZXIgd2l0aCByYW5kb20gZmVhdHVyZSBzZWxlY3Rpb24NCg0KIyBNYXRlcmlhbHMgYW5kIG1ldGhvZA0KDQpPdXIgZGF0YSBleHBlcmltZW50IGltcGxpZXMgdGhlICJCb2R5IEZhdCIgZGF0YXNldCBieSBSb2dlciBXLiBKb2huc29uIGFuZCBDYXJsZXRvbiBDb2xsZWdlIDxodHRwczovL3d3Mi5hbXN0YXQub3JnL3B1YmxpY2F0aW9ucy9qc2UvdjRuMS9kYXRhc2V0cy5qb2huc29uLmh0bWw+DQoNClRoZSBhdXRob3IgbWVhc3VyZWQgcGVyY2VudGFnZSBvZiBib2R5IGZhdCwgYWdlLCB3ZWlnaHQsIGhlaWdodCwgYW5kIHRlbiBib2R5IGNpcmN1bWZlcmVuY2UgbWVhc3VyZW1lbnRzIChlLmcuLCBhYmRvbWVuKSBmb3IgMjUxIG1lbi4gVGhlIEJvZHkgZmF0IHdhcyBtZWFzdXJlZCBieSBhbiB1bmRlcndhdGVyIHdlaWdoaW5nIHRlY2huaXF1ZS4gVGhlaXIgc3R1ZHkgYWltZWQgdG8gcHJlZGljdCBib2R5IGZhdCBpbiB0ZXJtcyBvZiBvdGhlciBtZWFzdXJlbWVudHMgdXNpbmcgbXVsdGlwbGUgcmVncmVzc2lvbi4NCg0KVGhlIGNhcmV0IHBhY2thZ2Ugd2FzIHVzZWQgZm9yIHRyYWluaW5nIGEgU3RlcHdpc2UgR0xNIGFsZ29yaXRobS4gTGFzc28gYW5kIGVsYXN0aWMgbmV0IGxvZ2lzdGljIG1vZGVscyBhcmUgc3VwcG9ydGVkIGJ5IEdMTW5ldCBwYWNrYWdlLiBUaGUgTUNNQyBiYXNlZCBCYXllc2lhbiBNb2RlbCBhdmVyYWdpbmcgd2FzIGRvbmUgdXNpbmcgQk1TIHBhY2thZ2UuIFdlIHVzZWQgbWxyIHBhY2thZ2UgZm9yIHdyYXBwaW5nIGEgbG9naXN0aWMgbGVhcm5lciB3aXRoIHJhbmRvbSBmZWF0dXJlIHNlbGVjdGlvbiBpbnRlZ3JhdGVkLg0KDQpUaGUgcGVyZm9ybWFuY2Ugb2YgNSByZWdyZXNzaW9uIG1vZGVscyB3aWxsIGJlIGNvbXBhcmVkLiANCg0KIyBQcmVwYXJhdGlvbiANCg0KRmlyc3QsIHdlIHdpbGwgcHJlcGFyZSB0aGUgZ2dwbG90IHRoZW1lIGZvciBvdXIgZXhwZXJpbWVudA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCnRoZW1lX2JhcmUgPC0gZnVuY3Rpb24oYmFzZV9zaXplPTgsYmFzZV9mYW1pbHk9InNhbnMiKXt0aGVtZV9idyhiYXNlX3NpemUgPSBiYXNlX3NpemUsIGJhc2VfZmFtaWx5ID0gYmFzZV9mYW1pbHkpKw0KICAgIHRoZW1lKA0KICAgICAgYXhpcy5saW5lID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfYmxhbmsoKSwgDQogICAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X2JsYW5rKCksIA0KICAgICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iLCANCiAgICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9IE5BKSwgDQogICAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksIA0KICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgDQogICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLCANCiAgICAgIHBsb3QubWFyZ2luID0gdW5pdChjKDAsMCwwLDApLCAibGluZXMiKQ0KICAgICkNCn0NCg0KbXlfdGhlbWUgPC0gZnVuY3Rpb24oYmFzZV9zaXplID01LCBiYXNlX2ZhbWlseSA9ICJzYW5zIil7DQogIHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IGJhc2Vfc2l6ZSwgYmFzZV9mYW1pbHkgPSBiYXNlX2ZhbWlseSkgKw0KICAgIHRoZW1lKA0KICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJncmF5IiksDQogICAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLA0KICAgICAgcGFuZWwuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gTkEpLA0KICAgICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gIiMwMDFkNjAiLCBjb2xvciA9ICIjMDAxMTNhIiwgc2l6ZSA9MC41KSwNCiAgICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDUsIGNvbG9yID0gIndoaXRlIiksDQogICAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwNCiAgICAgIGxlZ2VuZC5qdXN0aWZpY2F0aW9uID0gImNlbnRlciIsDQogICAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICAgIGxlZ2VuZC5tYXJnaW4gPSBtYXJnaW4oMC41LDAuNSwwLjUsMC41KQ0KICAgICkNCn0NCg0KdGhlbWVfc2V0KG15X3RoZW1lKCkpDQoNCmBgYA0KDQpGaXJzdCwgd2Ugd2lsbCBsb2FkIHRoZSBkYXRhZnJhbWUgdG8gUjoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQpkZj1yZWFkLnRhYmxlKCJodHRwczovL3d3Mi5hbXN0YXQub3JnL3B1YmxpY2F0aW9ucy9qc2UvZGF0YXNldHMvZmF0LmRhdC50eHQiKQ0KDQpkZj0gZGZbLGMoMiwgNTo3LCAxMDoxOSldDQoNCm5hbWVzKGRmKT1jKCJicm96ZWtfZmF0IiwiYWdlIiwid2VpZ2h0IiwiaGVpZ2h0IiwibmVjayIsImNoZXN0IiwiYWJkb21lbiIsImhpcCIsInRoaWdoIiwia25lZSIsImFua2xlIiwiYmljZXBzIiwiZm9yZWFybSIsIndyaXN0IikNCmRmPSBkZlstNDIsXQ0KDQpoZWFkKGRmKSU+JWtuaXRyOjprYWJsZSgpDQoNCmBgYA0KDQpUaGUgb3JpZ2luYWwgZGF0YXNldCBjb250YWlucyAxIHJlc3BvbnNlIHZhcmlhYmxlIChicm96ZWtfZmF0KSBhbmQgMTMgZmVhdHVyZXMgdGhhdCB3b3VsZCBiZSBpbmNsdWRlZCBhcyBwb3RlbnRpYWwgcHJlZGljdG9ycyBpbiBvdXIgbW9kZWwuDQoNCmBgYHtyfQ0KSG1pc2M6OmRlc2NyaWJlKGRmKQ0KYGBgDQoNCiMgRGF0YSB2aXN1YWxpc2luZw0KDQpGaXJzdCwgd2Ugd291bGQgbGlrZSB0byBrbm93IGhvdyB0aGUgZGF0YSBhcmUgZGlzdHJpYnV0ZWQgYW1vbmcgMjUxIG1lbiwgYXMgd2VsbCBhcyB0aGUgY29ycmVsYXRpb24gcGF0dGVybiBhbW9uZyB0aGUgdmFyaWFibGVzOg0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0KcGxvdGZ1bmNMb3cgPC0gZnVuY3Rpb24oZGF0YSxtYXBwaW5nKXsNCiAgcCA8LSBnZ3Bsb3QoZGF0YSA9IGRhdGEsbWFwcGluZz1tYXBwaW5nKStnZW9tX3BvaW50KGFlcyhmaWxsPWRmJGJyb3pla19mYXQsY29sb3I9ZGYkYnJvemVrX2ZhdCksc2hhcGU9MjEsYWxwaGE9MC4zLHNob3cubGVnZW5kID0gRikrZ2VvbV9zbW9vdGgobWV0aG9kPSJsbSIsc2U9Rixjb2xvcj0icmVkNCIpK3NjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdz0iI2NjZmMyZCIsbWlkPSIjZmNjYzJkIixoaWdoPSIjZmMyZDJkIikrc2NhbGVfY29sb3JfZ3JhZGllbnQyKGxvdz0iI2NjZmMyZCIsbWlkPSIjZmNjYzJkIixoaWdoPSIjZmMyZDJkIikNCiAgcA0KfQ0KDQpwbG90ZnVuY21pZCA8LSBmdW5jdGlvbihkYXRhLG1hcHBpbmcpew0KICBwIDwtIGdncGxvdChkYXRhID0gZGF0YSxtYXBwaW5nPW1hcHBpbmcpK2dlb21faGlzdG9ncmFtKGFlcygpLGFscGhhPTAuNSxmaWxsPSIjZmM4ODFiIixjb2xvcj0iI2ZjNDExYiIpK3NjYWxlX2ZpbGxfZ3JhZGllbnQyKGxvdz0iI2NjZmMyZCIsbWlkPSIjZmNjYzJkIixoaWdoPSIjZmMyZDJkIikNCiAgcA0KfQ0KDQpsaWJyYXJ5KEdHYWxseSkNCg0KZ2dwYWlycyhkZixjb2x1bW5zPTE6MTQsbG93ZXI9bGlzdChjb250aW51b3VzPXBsb3RmdW5jTG93KSx1cHBlcj1OVUxMLGRpYWc9bGlzdChjb250aW51b3VzPXBsb3RmdW5jbWlkKSkNCg0KYGBgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQpsaWJyYXJ5KGNvcnJwbG90KQ0KbGlicmFyeShSQ29sb3JCcmV3ZXIpDQoNCmNvcl9tYXQ9YXMubWF0cml4KGNvcihtZXRob2Q9InBlYXJzb24iLGFzLm1hdHJpeChkZikpKQ0KDQpjb3JfbWF0JT4lY29ycnBsb3QoLixvcmRlcj0iaGNsdXN0Iix0eXBlPSJsb3dlciIsbWV0aG9kPSJjb2xvciIsdGwuY29sPSJibGFjayIsIHRsLnNydD00NSx0bC5jZXg9MC41LGNvbD1yZXYoYnJld2VyLnBhbChuPTEwLCBuYW1lPSJTcGVjdHJhbCIpKSkNCg0KDQpgYGANCg0KQSBzaW1wbGUgY29ycmVsYXRpb24gbmV0d29yayBhbmFseXNpcyBjYW4gaGVscCB1cyB0byBpZGVudGlmeSA2IHZhcmlhYmxlIHRoYXQgaGF2ZSB0aGUgc3Ryb25nZXN0IGNvcnJlbGF0aW9ucyAoYWJzb2x1dGUgdmFsdWUgb2YgUGVhcnNvbidzIHIgY29lZmZpY2llbnQgd2VyZSBncmVhdGVyIHRoYW4gMC41KSB3aXRoIHRoZSBCb2R5IGZhdC4gVGhvc2UgdmFyaWFibGVzIGluY2x1ZGVkIEFiZG9tZW4sIENoZXN0LCBXZWlnaHQsIEhpcCwgVGhpZ2ggYW5kIEtuZWUuDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQpkZnNjYWxlPC1kZiU+JWFzLm1hdHJpeCgpJT4lc2NhbGUoKSU+JWFzX3RpYmJsZSgpDQoNCm09YXMubWF0cml4KGNvcihhcy5tYXRyaXgoZGZzY2FsZSkpKQ0KDQpsaWJyYXJ5KGlncmFwaCkNCg0KZGlhZyhtKTwtMA0KDQpsaWJyYXJ5KGdncmFwaCkNCg0KY2RmPWRhdGEuZnJhbWUocm93PXJvd25hbWVzKG0pW3JvdyhtKVt1cHBlci50cmkobSldXSwgDQogICAgICAgICAgICAgICBjb2w9Y29sbmFtZXMobSlbY29sKG0pW3VwcGVyLnRyaShtKV1dLCANCiAgICAgICAgICAgICAgIGNvcnI9bVt1cHBlci50cmkobSldKQ0KDQpuYW1lcyhjZGYpPWMoImZyb20iLCJ0byIsImNvcnIiKQ0KDQpjZGY9c3Vic2V0KGNkZixjZGYkZnJvbT09ImJyb3pla19mYXQiICYgYWJzKGNkZiRjb3JyKT4wLjUpDQoNCmdyYXBoPC1ncmFwaF9mcm9tX2RhdGFfZnJhbWUoY2RmKQ0KDQpnZ3JhcGgoZ3JhcGgsIGxheW91dCA9ICdzdGFyJyxjaXJjdWxhcj1GKStnZW9tX2VkZ2VfZmFuKGFlcyhjb2xvdXIgPSBjb3JyLHdpZHRoPWNvcnIpLGFscGhhPTAuNCkrZ2VvbV9ub2RlX2xhYmVsKGFlcyhsYWJlbCA9IG5hbWUpKStjb29yZF9maXhlZCgpK3NjYWxlX2VkZ2VfY29sb3JfZ3JhZGllbnQoaGlnaD0iI2ZmMDAzZiIsbG93PSIjMDA5NGZmIikrc2NhbGVfZWRnZV93aWR0aF9jb250aW51b3VzKCkrdGhlbWVfYmFyZSgxMCkNCg0KDQpgYGANCg0KIyBGZWF0dXJlIHNlbGVjdGlvbiBmb3IgYSByZWdyZXNzaW9uIHRhc2sNCg0KRmlyc3QsIHRoZXJlIGFyZSA2IG1ldGhvZHMgdGhhdCBjb3VsZCBiZSB1c2VkICB0byBzZWxlY3QgZmVhdHVyZXMgZm9yIGEgcmVncmVzc2lvbiBwcm9ibGVtOg0KDQo8aHR0cDovL21sci1vcmcuZ2l0aHViLmlvL21sci10dXRvcmlhbC9kZXZlbC9odG1sL2ZpbHRlcl9tZXRob2RzL2luZGV4Lmh0bWw+DQoNCmBgYHtyfQ0KdmltcGxvdD1mdW5jdGlvbihkYXRhLHRhcmdldCxtZXRob2Qpew0KICB0cmFpbi50YXNrPW1ha2VSZWdyVGFzayhkYXRhPWRhdGEsIHRhcmdldD10YXJnZXQpDQogIGltcF92YXI9Z2VuZXJhdGVGaWx0ZXJWYWx1ZXNEYXRhKHRyYWluLnRhc2ssIG1ldGhvZD1tZXRob2QpDQogIGZzZGY9aW1wX3ZhciRkYXRhJT4lLlssLTJdDQogIG5hbWVzKGZzZGYpPWMoIkZlYXR1cmUiLCJWYWx1ZSIpDQogIGZzZGYkTWV0aG9kPW1ldGhvZA0KICBwbG90PWZzZGYlPiVnZ3Bsb3QoYWVzKHg9cmVvcmRlcihmc2RmJEZlYXR1cmUsZnNkZiRWYWx1ZSkseT1WYWx1ZSxmaWxsPVZhbHVlLGNvbG9yPVZhbHVlKSkrZ2VvbV9zZWdtZW50KGFlcyh4PXJlb3JkZXIoZnNkZiRGZWF0dXJlLGZzZGYkVmFsdWUpLHhlbmQ9RmVhdHVyZSx5PTAseWVuZD1WYWx1ZSksc2l6ZT0xLHNob3cubGVnZW5kID0gRikrZ2VvbV9wb2ludChzaXplPTIsc2hvdy5sZWdlbmQgPSBGKStzY2FsZV94X2Rpc2NyZXRlKCJGZWF0dXJlcyIpK3NjYWxlX3lfY29udGludW91cyhtZXRob2QpK2Nvb3JkX2ZsaXAoKStzY2FsZV9maWxsX2dyYWRpZW50KGxvdz0iYmx1ZSIsaGlnaD0icmVkIikrc2NhbGVfY29sb3JfZ3JhZGllbnQobG93PSJibHVlIixoaWdoPSJyZWQiKStnZ3RpdGxlKG1ldGhvZCkNCiAgcmV0dXJuKHBsb3QpDQp9DQoNCnAxPXZpbXBsb3QoZGF0YT1kZix0YXJnZXQ9ImJyb3pla19mYXQiLG1ldGhvZD0ibXJtciIpDQoNCiNtcm1yDQoNCnAyPXZpbXBsb3QoZGF0YT1kZix0YXJnZXQ9ImJyb3pla19mYXQiLG1ldGhvZD0ibGluZWFyLmNvcnJlbGF0aW9uIikNCg0KI2NhcnNjb3JlDQoNCnAzPXZpbXBsb3QoZGF0YT1kZix0YXJnZXQ9ImJyb3pla19mYXQiLG1ldGhvZD0iY2Fyc2NvcmUiKQ0KDQojdmFyaWFuY2UNCg0KcDQ9dmltcGxvdChkYXRhPWRmLHRhcmdldD0iYnJvemVrX2ZhdCIsbWV0aG9kPSJ2YXJpYW5jZSIpDQoNCiNJRw0KDQpwNT12aW1wbG90KGRhdGE9ZGYsdGFyZ2V0PSJicm96ZWtfZmF0IixtZXRob2Q9ImluZm9ybWF0aW9uLmdhaW4iKQ0KDQojY2ZvcmVzdA0KDQpwNj12aW1wbG90KGRhdGE9ZGYsdGFyZ2V0PSJicm96ZWtfZmF0IixtZXRob2Q9ImNmb3Jlc3QuaW1wb3J0YW5jZSIpDQoNCg0KbGlicmFyeShncmlkRXh0cmEpDQoNCmdyaWQuYXJyYW5nZShwMSxwMixwMyxwNCxwNSxwNixuY29sPTMpDQoNCg0KYGBgDQoNCiMgU1RFUFdJU0UgR0xNDQoNCkZpcnN0LCB3ZSB3aWxsIGNvbnNpZGVyIHRoZSBTdGVwd2lzZSBHTE0sIGJ1dCB0aGlzIHRpbWUgd2Ugd2lsbCBleHRlbmQgaXQgYSBsaXR0bGUgYml0IHdpdGggYSBCb290c3RyYXBwaW5nLiBUaGlzIGNvdWxkIGJlIGRvbmUgdXNpbmcgY2FyZXQgcGFja2FnZToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0UscmVzdWx0cz0iaGlkZSJ9DQoNCmxpYnJhcnkoY2FyZXQpDQoNCmNydD10cmFpbkNvbnRyb2wobWV0aG9kPSJib290IixudW1iZXI9MTAwLHZlcmJvc2VJdGVyID0gRikNCiAgICAgICAgICAgICAgICANCnNldC5zZWVkKDEyMykNCg0KZ2xtY2FyPSBjYXJldDo6dHJhaW4oZGF0YT1kZiwgYnJvemVrX2ZhdH4gLiwgDQogICAgICAgICAgICAgIG1ldGhvZCA9ICJnbG1TdGVwQUlDIiwgDQogICAgICAgICAgICAgIHRyQ29udHJvbCA9IGNydCwNCiAgICAgICAgICAgICAgdmVyYm9zZSA9IEZBTFNFKQ0KDQpgYGANCg0KRGVzcGl0ZSB0aGF0IHRoZSBtb2RlbCB0cmFpbmluZyBjb3N0cyBzbyBtdWNoIHRpbWUsIHRoZSByZXN1bHQgaXMgbm90IHZlcnkgZ29vZCwgYXMgd2UgY291bGQgc2VlIGJlbG93Og0KDQpUaGUgUnNxdWFyZSBvZiB0aGUgc3Vydml2ZWQgbW9kZWwgd2FzIG9ubHkgMC43IGFuZCB0aGVyZSBhcmUgOCBmZWF0dXJlcyBpbmNsdWRlZA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCmdsbWNhcg0KDQpzdW1tYXJ5KGdsbWNhcikNCmBgYA0KDQojIExBU1NPIEFORCBFTEFTVElDIE5FVCBSRUdVTEFSSVNBVElPTlMNCg0KTGFzc28gcGVuYWxpemVzIHRoZSBhYnNvbHV0ZSB2YWx1ZXMgb2Ygc29tZSBjb2VmZmljaWVudHMgdG93YXJkIHplcm8sIHNvIGl0IHdpbGwgcmVtb3ZlIHRoZSBsZXNzIGltcG9ydGFudCBmZWF0dXJlcyBmcm9tIHRoZSBtb2RlbC4gVGhpcyBtZXRob2QgaGFzIHByb3ZlbiB0byBiZSB1c2VmdWwgZm9yIGZlYXR1cmUgc2VsZWN0aW9uIGluIHByb2JsZW1zIHdpdGggbGFyZ2UgbnVtYmVyIG9mIGNvdmFyaWF0ZXMuIExhc3NvIGlzIGFsc28gYmV0dGVyIHRoYW4gUmlkZ2UgcmVndWxhcml6YXRpb24gaWYgb3VyIGdvYWwgd291bGQgYmUgb3B0aW1pemluZyB0aGUgaW50ZXJwcmV0YWJpbGl0eSBvZiB0aGUgbW9kZWwuIFdoZW4gdGhlcmUgYXJlIG1hbnkgY29ycmVsYXRlZCBmZWF0dXJlcywgbGFzc28gdGVuZHMgdG8gbGV0IG9ubHkgb25lIG9mIHRoZW0gc3Vydml2ZSBhbmQgZWxpbWluYXRlIG90aGVyIGZyb20gdGhlIGZpbmFsIG1vZGVsIGJ5IHNocmlua2luZyB0aGVpciBjb2VmZmljaWVudCB0byB6ZXJvLiANCg0KVGhlIGVsYXN0aWMgbmV0IHJlZ3VsYXJpemF0aW9uIGlzIGEgZmxleGlibGUgc29sdXRpb24gYmV0d2VlbiBSaWRnZSBhbmQgTGFzc28sIGFzIGl0IGNvbWJpbmVzIGJvdGggbDEgYW5kIGwyIHBlbmFsdGllcyB1bmRlciBhIHBhcmFtZXRlciBjYWxsZWQgYWxwaGEgKHdoZW4gYT0wLCB0aGUgcmVndWxhcml6YXRpb24gaXMgc3RyaWN0bHkgUmlkZ2UsIHdoaWxlIGE9MSBjb3JyZXNwb25kcyB0byBsYXNzbyBwZW5hbHR5KS4gVGhpcyBtZXRob2QgcHJvdmlkZXMgdGhlIHN0cmVuZ3RoIG9mIGJvdGggdHlwZXMgb2YgcmVndWxhcml6YXRpb24sIHNpbmNlIHRoZSBsYXNzbyBvcHRpbWl6ZXMgZmVhdHVyZSBzZWxlY3Rpb24gYW5kIGludGVycHJldGFiaWxpdHkgd2hpbGUgUmlkZ2UgYWxsb3dzIGdyb3VwaW5nIGVmZmVjdCAodGhlIGNvcnJlbGF0ZWQgdmFyaWFibGVzIHdpbGwgYmUgZ3JvdXBlZCB0b2dldGhlciB0aGVuIGRlcGVuZGluZyBvbiB0aGUgcGVuYWx0eSBzdHJlbmd0aCwgdGhleSBjb3VsZCBjb250cmlidXRlIHRvIHRoZSBwcmVkaWN0aW9uIGFsbCBhdCBvbmNlIG9yIHRvdGFsbHkgZHJvcHBlZCBvdXQgZnJvbSB0aGUgbW9kZWwuDQoNCkJvdGggTGFzc28gYW5kIEVsYXN0aWMgbmV0IHJlZ3VsYXJpc2F0aW9ucyBhcmUgc3VwcG9ydGVkIGJ5IHRoZSBnbG1uZXQgcGFja2FnZToNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQojTGFzc28gbW9kZWwNCg0KbGlicmFyeShnbG1uZXQpDQoNCmxhc3NvID0gY3YuZ2xtbmV0KHg9YXMubWF0cml4KGRmWywtMV0pLHk9ZGYkYnJvemVrX2ZhdCxhbHBoYT0xLG5mb2xkcz01KQ0KDQpwbG90KGxhc3NvLHh2YXI9ImxhbWJkYSIpDQoNCmNvZWYobGFzc28pDQoNCg0KYGBgDQoNCmBgYHtyLG1lc3NhZ2UgPSBGQUxTRSx3YXJuaW5nPUZBTFNFfQ0KI0VsYXN0aWMgbmV0DQoNCmVuZXQ9IGN2LmdsbW5ldCh4PWFzLm1hdHJpeChkZlssLTFdKSx5PWRmJGJyb3pla19mYXQsbmZvbGRzPTUpDQoNCnBsb3QoZW5ldCkNCg0KY29lZihlbmV0KQ0KDQoNCmBgYA0KDQpOb3RlIHRoYXQgZWFjaCBtZXRob2QgcHJvdmlkZWQgYSBkaWZmZXJlbnQgbW9kZWwgc3RydWN0dXJlOg0KDQpXZSBtaWdodCBiZWxpZXZlIHRoYXQgQWJkb21lbiB3b3VsZCBiZSBpbXBvcnRhbnQsIGFzIHRoaXMgdmFyaWFibGUgd2FzIGluY2x1ZGVkIGluIGJvdGggTGFzc28gYW5kIEVsYXN0aWMgbmV0IG1vZGVsLg0KDQojIEJBWUVTSUFOIE1PREVMIEFWRVJBR0lORw0KDQpHaXZlbiBhIHJlc3BvbnNlIHZhcmlhYmxlIFkgYW5kIGEgbWF0cml4IG9mIGFsbCBwb3RlbnRpYWwgcHJlZGljdG9ycyAoZmVhdHVyZXMpIFgsIHkgY291bGQgYmUgZXN0aW1hdGVkIGZyb20gWCBieSBhIGxpbmVhciBtb2RlbCA6DQoNClxbDQp5PSBcYmV0YV97MH0gKyBcYmV0YV97XGdhbW1hfSBYX3tcZ2FtbWF9K1x2YXJlcHNpbG9uDQpcXQ0KDQpTdWNoIG1vZGVsIGlzIGRlZmluZWQgYnkgYSBjb25zdGFudCBiZXRhMCwgYSBzdWJzZXQgb2YgY29lZmZpY2llbnRzIGJldGEgdGhhdCBjb3JyZXNwb25kIHRvIGEgZmVhdHVyZSBzdWJzZXQgYW5kIEUgaXMgdGhlIHJlc2lkdWFsIGVycm9yIHRoYXQgd291bGQgZm9sbG93IHRoZSBub3JtYWwgZGlzdHJpYnV0aW9uIHdpdGggemVybyBtZWFuLg0KDQpUaGUgZ29hbCBvZiBCYXllc2lhbiBNb2RlbCBhdmVyYWdpbmcgbWV0aG9kIGlzIHRvIGNvbnN0cnVjdCBhIHdlaWdodGVkIGF2ZXJhZ2Ugb3ZlciBhbGwgcG9zc2libGUgY29tYmluYXRpb24gb2YgZmVhdHVyZXMuIFRoZSBtb2RlbCB3ZWlnaHRzIGFyZSBkZXRlcm1pbmVkIGZyb20gcG9zdGVyaW9yIG1vZGVsIHByb2JhYmlsaXRpZXMgdGhhdCBjb3VsZCBiZSBlc3RpbWF0ZWQgZnJvbSBjbGFzc2ljIEJheWVzIHRoZW9yZW06DQoNCiBcW3AoTV97XGdhbW1hIH18eSxYKSA9IFxmcmFje3AoeXxNX3tcZ2FtbWF9LFgpcChNX3tcZ2FtbWF9KX17cCh5fFgpKX0gPSBcZnJhY3twKHl8TV97XGdhbW1hfSxYKXAoTV97XGdhbW1hfSl9e1xzdW1fe3M9MX1eezJeS30gcCh5fE1fcyxYKXAoTV9zKX1cXQ0KIA0KV2hlcmUgSyBpcyB0aGUgbnVtYmVyIG9mIHBvdGVudGlhbCBmZWF0dXJlcw0KDQpwKHl8WCkgaW5kaWNhdGVzIHRoZSBpbnRlZ3JhdGVkIGxpa2VsaWhvb2QsIGl04oCZcyBzaW1wbHkgYSBjb25zdGFudCB0ZXJtIG92ZXIgYWxsIG1vZGVscy4gDQoNClRoZSBQTVAgcChNfHksWCkgY291bGQgYmUgaW50ZXJwcmV0ZWQgYXMg4oCccHJvYmFiaWxpdHkgb2YgYSBzcGVjaWZpYyBtb2RlbCBnaXZpbmcgYSBtYXRyaXggWCBhbmQgcmVzcG9uc2UgdmFyaWFibGUgWSwgb3ZlciBhbGwgcG9zc2libGUgbW9kZWxz4oCcIG9yIHBvc3RlcmlvciBtb2RlbCBwcm9iYWJpbGl0eSAoUE1QKS4gDQoNClBNUCBpcyBwcm9wb3J0aW9uYWwgdG8gdGhlIG1hcmdpbmFsIGxpa2VsaWhvb2Qgb2YgdGhlIG1vZGVsICBwKHl8TSxYKSA9IHByb2JhYmlsaXR5IG9mIHRoZSBkYXRhIGdpdmVuIHRoZSBtb2RlbCBNKSB0aW1lcyBhIHByaW9yIG1vZGVsIHByb2JhYmlsaXR5IHAoTSkNCg0KVGhlIG1vZGVsIHByaW9yIGluZGljYXRlcyB0aGUgc2VhcmNoZXLigJlzIGJlbGllZiBhYm91dCB0aGUgbW9kZWwgYmVmb3JlIGxvb2tpbmcgYXQgZGF0YSAoZm9yIGV4YW1wbGUsIHRoZSBzZWFyY2hlciBiZWxpZXZlcyBzdHJvbmdseSB0aGF0IGEgYmlvbWFya2VyIFkgaXMgYWx3YXlzIGFzc29jaWF0ZWQgdG8gcGF0aWVudOKAmXMgZ2VuZGVyLCBhZ2UgYW5kIHdlaWdodCwgc28gYSBwcmlvciBjb3VsZCBiZSBmb3JtZWQsIGJhc2VkIG9uIHdoaWNoIHRoZXJlIGlzIGEgaGlnaCBwcm9iYWJpbGl0eSBmb3IgdGhlIG1vZGVsIHRoYXQgY29udGFpbnMgQWdlLCBnZW5kZXIgYW5kIFdlaWdodCBvdmVyIGFsbCBwb3NzaWJsZSBtb2RlbHMgb2Ygb3RoZXIgY292YXJpYXRlcy4gDQoNCldoZW4gdGhlcmUgaXMgbm8gcHJpb3Iga25vd2xlZGdlIGFib3V0IHRoZSBwcm9ibGVtLCBhIHBvcHVsYXIgY2hvaWNlIGlzIHRvIHNldCBhIHVuaWZvcm0gcHJpb3IgcHJvYmFiaWxpdHkuDQoNClRoZSBtb2RlbCB3ZWlnaHRlZCBwb3N0ZXJpb3IgZGlzdHJpYnV0aW9uIGZvciBhbnkgY29lZmZpY2llbnRzIChiZXRhKSBjb3VsZCBiZSBlc3RpbWF0ZWQgYnk6DQoNClxbcChcYmV0YSB8eSxYKT1cc3VtX3tcZ2FtbWEgPTF9XnsyXkt9cChcYmV0YSB8TV9cZ2FtbWEgLHksWClwKE1fXGdhbW1hfFgseSlcXQ0KDQpUaG91Z2ggQk1BIGNvdWxkIGJlIHBlcmZvcm1lZCBpbiBhIHN0cmFpZ2h0Zm9yd2FyZCB3YXksIHRoYXQgbWVhbnMgdG8gZW51bWVyYXRlIGFsbCBwb3NzaWJsZSB2YXJpYWJsZSBjb21iaW5hdGlvbnMgdW50aWwgd2UgZ290IHRoZSBwb3N0ZXJpb3IgcmVzdWx0LCBNQ01DIHNhbXBsaW5nIGlzIGEgbW9yZSBwb3dlcmZ1bCBtZXRob2QgZm9yIEJNQS4gVGhlIE1DTUMgc2FtcGxlciBzaG91bGQgYmUgY29uc2lkZXJlZCBmb3IgbGFyZ2UgZGF0YXNldCB3aXRoIG1vcmUgdGhhbiAyMCB2YXJpYWJsZXMuIE1DTUMgc2FtcGxlciBhaW1zIHRvIGdhdGhlciB0aGUgbW9zdCBpbXBvcnRhbnQgcGFydCBvZiB0aGUgcG9zdGVyaW9yIG1vZGVsIGRpc3RyaWJ1dGlvbiwgdGh1cyBvbmx5IGFwcHJveGltYXRlIGl0IGJ1dCBhcyBjbG9zZWx5IGFzIHBvc3NpYmxlLiANCg0KSW4gb3VyIGV4cGVyaW1lbnQsIHdlIHdpbGwgYXBwbHkgYSBNQ01DIHNhbXBsZXIgdGhhdCBiYXNlZCBvbiBiYXNlZCBvbiBNZXRyb3BvbGlzLUhhc3RpbmdzIGFsZ29yaXRobS4gT3VyIHNhbXBsZXIgd2Fsa3MgdGhyb3VnaCB0aGUgbW9kZWwgc3BhY2UgYXMgZm9sbG93Og0KDQpBdCB0aGUgc3RlcCBJLCB0aGUgc2FtcGxlciBzdGFuZHMgYXQgYSBnaXZlbiBtb2RlbCBNaSB3aXRoIHBvc3RlcmlvciBwKE1pfHksWCksIG9uIHRoZSBuZXh0IHN0ZXAgaSsxLCBhIG5ldyBjYW5kaWRhdGUgTWogaXMgZ2l2ZW4gYW5kIGJlY29tZXMgdGhlIGN1cnJlbnQgbW9kZWwuIFRoZSBzYW1wbGVyIHN3aXRjaGVzIGZyb20gTWkgdG8gTWogd2l0aCBhIHByb2JhYmlsaXR5IHBpLGo6DQoNClxbcF97aSxqfT1taW4oMSxwKE1faSB8IHksWCkvcChNX2kgfHkseCkpXF0NCg0KSWYgdGhlIG1vZGVsIE1qIGlzIGNvbnNpZGVyZWQgd29yc2UgdGhhbiBNaSwgaXQgd2lsbCBiZSByZWplY3RlZCBhbmQgdGhlIHNhbXBsZXIgbW92ZXMgdG8gdGhlIG5leHQgc3RlcCBhbmQgYSBuZXcgY2FuZGlkYXRlIE1rIHdpbGwgYmUgcHJvcG9zZWQgYWdhaW5zdCBNaS4gSW4gdGhlIGNhc2UgTWogaXMgYWNjZXB0ZWQsIGl0IHdpbGwgcmVwbGFjZSBNaSBhcyB0aGUgY3VycmVudCBtb2RlbCBhbmQgaGFzIHRvIHN1cnZpdmUgYWdhaW5zdCBmdXJ0aGVyIGNhbmRpZGF0ZSBtb2RlbHMgaW4gdGhlIG5leHQgc3RlcC4gVGhlIG51bWJlciBvZiB0aW1lcyBlYWNoIG1vZGVsIHN1cnZpdmVzIHdpbGwgY29udmVyZ2UgdG8gdGhlIGRpc3RyaWJ1dGlvbiBvZiBwb3N0ZXJpb3IgbW9kZWwgcHJvYmFiaWxpdGllcyAoUE1QKSBwKE1pfHksWCkuDQoNCkhlcmUsIHdlIGFwcGx5IHRoZSBzdGFuZGFyZCBNQ01DIHNhbXBsZXIgY2FsbGVkIOKAnGJpcnRoIGRlYXRo4oCdLiBBdCBlYWNoIHN0ZXAgaSwgdGhlIHNhbXBsZXIgcmFuZG9tbHkgcGlja3Mgb25lIGZlYXR1cmUgRmkgYW1vbmcgSyBwb3RlbnRpYWwgZmVhdHVyZXMuIElmIEZpIGlzIGFscmVhZHkgaW5jbHVkZWQgaW4gdGhlIGN1cnJlbnQgbW9kZWwgTWkgLCB0aGUgbmV3IGNhbmRpZGF0ZSBtb2RlbCBNaiBvbiBuZXh0IHN0ZXAgd2lsbCBpbmNsdWRlIGFsbCB0aGUgZmVhdHVyZXMgZnJvbSBNaSBleGNlcHQgZm9yIEZpIHRoYXQgd2lsbCBiZSBkcm9wcGVkIG91dC4gSWYgdGhlIGN1cnJlbnQgbW9kZWwgTWkgZG9lcyBub3QgY29udGFpbiBGaSwgdGhlbiB0aGUgY2FuZGlkYXRlIG1vZGVsIHdpbGwgaW5jbHVkZSBhbGwgdGhlIGZlYXR1cmUgc2V0IGZyb20gTWkgcGx1cyB0aGUgY2hvc2VuIGZlYXR1cmUgRmkuIA0KDQpUaGUgYWNjdXJhY3kgb2YgUE1QIGFwcHJveGltYXRpb24gZGVwZW5kcyBvbiB0aGUgbnVtYmVyIG9mIGl0ZXJhdGlvbiBzdGVwcyB0aGF0IE1DTUMgc2FtcGxlciBydW5zIHRocm91Z2guIFRoZSBmaXJzdCBwYXJ0IG9mIHN1Y2ggcHJvY2VkdXJlIG11c3QgYWxzbyBiZSBvbWl0dGVkIGZyb20gdGhlIGFwcHJveGltYXRpb24gKGJ1cm4taW5zIG9yIHdhcm1pbmctdXApLiBUaGUgYXJndW1lbnQgYnVybiBzcGVjaWZpZXMgdGhlIG51bWJlciBvZiBidXJuLWlucyBhbmQgdGhlIGFyZ3VtZW50IGl0ZXIgdGhlIG51bWJlciBvZiBzdWJzZXF1ZW50IGl0ZXJhdGlvbnMgdG8gYmUgcmV0YWluZWQuDQoNCipNb2RlbC1zcGVjaWZpYyBnIHByaW9yKg0KDQpJbiBCTUEsIGcgcHJpb3IgbWVhc3VyZXMgdGhlIGxldmVsIG9mIGNlcnRhaW50eSBmb3IgYSB6ZXJvIGNvZWZmaWNpZW50LiBBIHNtYWxsIGcgaW1wbGllcyBhIHN0cm9uZyBiZWxpZWYgb2YgcmVzZWFyY2hlciAoY29uc2VydmF0aXZlIHByaW9yKSB0aGF0IHRoZSBjb2VmZmljaWVudHMgYXJlIHplcm8sIGEgbGFyZ2UgZyBpbmRpY2F0ZXMgdGhhdCB0aGUgc2VhcmNoZXIgaXMgaGlnaGx5IHVuY2VydGFpbiB0aGF0IGNvZWZmaWNpZW50cyBhcmUgemVyby4gDQoNClRoZSBwb3N0ZXJpb3IgZGlzdHJpYnV0aW9uIG9mIGNvZWZmaWNpZW50cyByZWZsZWN0IHByaW9yIHVuY2VydGFpbnR5LCBmb3IgYSBnaXZlbiBnLCBpdCBmb2xsb3dzIGEgdC1kaXN0cmlidXRpb24gd2l0aCBleHBlY3RlZCB2YWx1ZSBFKGJldGF8eSxYLGcsTSkgPSBzdGFuZGFyZCBPTFMgYmV0YSAqIGcvKDErZykNCg0KVGhlIHNtYWxsZXIgZywgdGhlIG1vcmUgaW1wb3J0YW50IGlzIHRoZSBwcmlvciwgYW5kIHRoZSBtb3JlIHRoZSBleHBlY3RlZCB2YWx1ZSBvZiBjb2VmZmljaWVudHMgaXMgc2hydW5rIHRvd2FyZCB0aGUgcHJpb3IgbWVhbiB6ZXJvLg0KDQpJbiBNQ01DIHNhbXBsaW5nLCB0aGUgbW9kZWwgZy1wcmlvciBpcyBub3QgZml4ZWQgYnV0IHNob3VsZCBiZSBzcGVjaWZpZWQgZnJvbSBpbmZvcm1hdGlvbiBmcm9tIGRhdGEgKHksWCkuIFRoZSBFbXBpcmljYWwgQmF5ZXMgZyAgbG9jYWwgKEVCTCkgYWxnb3JpdGhtIHNldHMgZ2kgPSBtYXgoMCxGX09MUyAtMSkgd2hlcmUgRl9PTFMgaXMgdGhlIHN0YW5kYXJkIE9MUyBGLXN0YXRpc3RpYyBmb3IgbW9kZWwgTWkuIA0KDQpOb3cgaXQncyB0aW1lIGZvciBjb2Rpbmc7IFdlIHdpbGwgdXNlIHRoZSBCTVMgcGFja2FnZTogVGhlIGZvbGxvd2luZyBjb21tYW5kIGNvbnNpc3RzIG9mIGEgTUNNQyB3aXRoIDEwMDAgd2FybXVwIHN0ZXBzIGFuZCAxMCwwMDAgc3RlcHMgdG8gYXBwcm94aW1hdGUgdGhlIFBNUCBvZiBtb2RlbHMuIFRoZSBzYW1wbGVyIGltcGxpZWQgYW4gRUJMIG1vZGVsIHNwZWNpZmljIGcgcHJpb3IsIHRoZSBtb2RlbCBwcmlvciBpcyB1bmlmb3JtLiBUaGUgTUNNQyBhbGdvcml0aG0gaXMgIkJyaXRoIGFuZCBkZWF0aCINCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQoNCmxpYnJhcnkoQk1TKQ0KDQpibXNtb2Q9IGJtcyhkZiwgYnVybiA9IDEwMDAsIGl0ZXIgPSAxMDAwMCwgZyA9ICJFQkwiLG1wcmlvciA9ICJ1bmlmb3JtIixtY21jPSJiZG4iLCB1c2VyLmludCA9IEYpDQoNCmBgYA0KDQpGaXJzdCwgd2UgZXh0cmFjdCB0aGUgY29lZmZpY2llbnRzIGZyb20gYXZlcmFnZWQgbW9kZWwgb3V0cHV0DQoNClRoZSBmaXJzdCBjb2x1bW4gaW4gdGhpcyBtYXRyaXggc2hvd3MgYWxsIDEzIHZhcmlhYmxlIG5hbWVzLiBGaXJzdCB3ZSBsb29rIGF0IHRoZSBQb3N0IG1lYW4sIHdoaWNoIGlzIHRoZSBjb2VmZmljaWVudHMgYXZlcmFnZWQgb3ZlciBhbGwgbW9kZWxzLCBpbmNsdWRpbmcgdGhlIG1vZGVscyB0aGF0IGRvZXMgbm90IGluY2x1ZGUgdGhhdCB2YXJpYWJsZSAodGhlIGNvZWZmaWNpZW50IHdpbGwgYmUgemVybykuIFRoZSBjb2VmZmljaWVudHMgcG9zdGVyaW9yIHN0YW5kYXJkIGRldmlhdGlvbiAoUG9zdCBTRCkgbWlnaHQgaW5kaWNhdGUgdGhlIGNlcnRhaW50eSBvZiB0aGUgcG9zaXRpdmUgb3IgbmVnYXRpdmUgZWZmZWN0LiBUaGUgY29lZmZpY2llbnQgc2lnbiBjYW4gYWxzbyBiZSBpbmZlcnJlZCBmcm9tIHRoZSBDb25kIFBvcyBTaWduLCBpbmRpY2F0aW5nIHRoZSBwb3N0ZXJpb3IgcHJvYmFiaWxpdHkgb2YgYSBwb3NpdGl2ZSBjb2VmZmljaWVudCBleHBlY3RlZCB2YWx1ZSBjb25kaXRpb25hbCBvbiBpbmNsdXNpb24gb3Ig4oCcc2lnbiBjZXJ0YWludHnigJ0NCg0KVGhlIGltcG9ydGFuY2Ugb2YgZWFjaCB2YXJpYWJsZSBpbiBwcmVkaWN0aW5nIG91dGNvbWUgaXMgbWVhc3VyZWQgYnkgUElQIHdoaWNoIHJlcHJlc2VudHMgdGhlIHBvc3RlcmlvciBpbmNsdXNpb24gcHJvYmFiaWxpdGllcyBvciB0aGUgc2ltIG9mIFBNUHMgZm9yIGFsbCBtb2RlbHMgd2hlcmVpbiB0aGF0IGZlYXR1cmUgd2FzIGluY2x1ZGVkLiBUaGUgUG9zdCBtZWFuIGNvbHVtbiBhbHNvIGluZGljYXRlIHRoZSBwb3NpdGl2ZSBvciBuZWdhdGl2ZSBlZmZlY3Qgb2YgYSBmZWF0dXJlDQoNCkZvciBleGFtcGxlOiB3ZSBjYW4gc2F5IHRoYXQgQWJkb21lbiB3b3VsZCBiZSB0aGUgbWlzdCBpbXBvcnRhbnQgdmFyaWFibGUuIEl0IHdhcyBpbmNsdWRlZCBpbiBhbGwgbW9kZWxzLCBpdCBoYXMgYSBjZXJ0YWluIHBvc2l0aXZlIGVmZmVjdCBvbiBCb2R5IGZhdCB3aXRoIHN0YW5kYXJkaXNlZCBjb2VmZmljaWVudCBvZiAxLjI2LiANCg0KVGhlIGxlc3MgaW1wb3J0YW50IGZlYXR1cmVzIHdpbGwgaGF2ZSBsb3dlciB1bmNvbmRpdGlvbmFsIGNvZWZmaWNpZW50cyBzaW5jZSB0aGVpciBjb2VmZmljaWVudHMgYmV0YSB3ZXJlIG51bGwgaW4gbW9zdCBvZiBtb2RlbHMuDQoNClRoZSBmaW5hbCBjb2x1bW4gZGVub3RlcyB0aGUgb3JpZ2luYWwgb3JkZXIgb2YgZmVhdHVyZXMgaW4gZGF0YXNldA0KDQpgYGB7cixtZXNzYWdlID0gRkFMU0Usd2FybmluZz1GQUxTRX0NCg0KY29lZihibXNtb2QsIHN0ZC5jb2VmcyA9IFQsIG9yZGVyLmJ5LnBpcCA9IFQsIGluY2x1ZGUuY29uc3RhbnQgPSBUKQ0KDQpzdW1tYXJ5KGJtc21vZCkNCmBgYA0KDQpDb3JyIFBNUCBpbmRpY2F0ZXMgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gdGhlIGl0ZXJhdGlvbiAoTUNNQyBzYW1wbGVyKSBjb3VudHMgYW5kIGFuYWx5dGljYWwgUE1QIGZvciBiZXN0IG1vZGVscyANCg0KQXQgMC45OTMsIHRoaXMgY29ycmVsYXRpb24gaW5kaWNhdGVzIGEgZ29vZCBkZWdyZWUgb2YgY29udmVyZ2VuY2UNCg0KVG8gdmVyaWZ5IGhvdyBmYXIgdGhlIHBvc3RlcmlvciBtb2RlbCBzaXplIGRpc3RyaWJ1dGlvbiBtYXRjaGVzIHVwIHRvIG91ciBwcmlvciwgdGhlIGNvbnZlcmdlbmNlIGJldHdlZW4gYW5hbHl0aWNhbCBhbmQgTUNNQyBQTVAgY291bGQgYmUgY29tcGFyZWQgb24gYSBncmFwaDoNCg0KYGBge3J9DQpwbG90TW9kZWxzaXplKGJtc21vZCkNCg0KYGBgDQoNClRoZSBwb3N0ZXJpb3IgbW9kZWwgcHJvYmFiaWxpdHkgY3VydmUgaXMgd2VsbCBkaWZmZXJlbnQgZnJvbSBvdXIgcHJpb3IuIFRoZSBncmFwaCBpbmRpY2F0ZXMgdGhhdCBtb2RlbHMgdGhhdCBpbmNsdWRlIDQgZmVhdHVyZXMgaGF2ZSBoaWdoZXN0IHByb2JhYmlsaXR5Lg0KDQpUaGUgaW1wb3J0YW5jZSBhbmQgcG90ZW50aWFsIGNvbWJpbmF0aW9uIG9mIGZlYXR1cmVzIGNvdWxkIGJlIHZpc3VhbGl6ZWQgYnkgdGhpcyBncmFwaDoNCg0KSGVyZSBSZWQgY29sb3IgaW5kaWNhdGVzIGEgcG9zaXRpdmUgY29lZmZpY2llbnQsIFNreWJsdWUgcmVmZXJzIHRvIGEgbmVnYXRpdmUgY29lZmZpY2llbnQsIGFuZCB3aGl0ZSBjb3JyZXNwb25kcyB0byBub24taW5jbHVzaW9uICh3aXRoIHplcm8gYmV0YSBjb2VmZmljaWVudCkuDQoNClRoZSBob3Jpem9udGFsIGF4aXMgc2hvd3MgdGhlIGJlc3QgbW9kZWxzLCBiYXNlZCBvbiBQTVBzLiBUaGUgbW9kZWxzIGFyZSByYW5rZWQgZnJvbSBsZWZ0IHRvIHJpZ2h0LiANCg0KYGBge3J9DQppbWFnZShibXNtb2QsY29sPWMoIiMxOTlhYzEiLCIjZTMwYzM3IikpDQoNCmBgYA0KDQpCYXNlZCBvbiB0aGlzIGdyYXBoLCB0aGUgYmVzdCBtb2RlbCB3b3VsZCBpbmNsdWRlIDQgZmVhdHVyZXMgd2hpY2ggYXJlIEFiZG9tZW4sIFdlaWdodCwgV3Jpc3QgYW5kIEZvcmVhcm0gKG5vdGUgdGhhdCB0aGlzIHdhcyBkaWZmZXJlbnQgdG8gdGhlIG1vZGVsIHN0cnVjdHVyZSBhcyBzdWdnZXN0ZWQgYnkgTGFzc28gcmVndWxhcmlzYXRpb24pLg0KDQpXZSBjYW4gYXNrIGZvciB0aGUgMyBiZXN0IG1vZGVscyA6DQoNClRoaXMgaXMgYSBiaW5hcnkgcmVwcmVzZW50YXRpb24gb2YgbW9kZWxz4oCZIHN0cnVjdHVyZSAoMSBtZWFucyBpbmNsdWRlZCwgMCBpcyBleGNsdWRlZCkuIFRoZSByZXN1bHQgYWxzbyBwcm92aWRlcyBQTVAgKHBvc3RlcmlvciBtb2RlbCBwcm9iYWJpbGl0eSkgdmFsdWVzIGVzdGltYXRlZCBieSBlaXRoZXIgZXhhY3QgZW51bWVyYXRpb24gb3IgYnkgTUNNQyBzYW1wbGVyDQoNCmBgYHtyfQ0KdG9wbW9kZWxzLmJtYShibXNtb2QpWywxOjNdDQoNCmBgYA0KDQojIFdSQVBQRVIgbWV0aG9kDQoNCldyYXBwZXIgbWV0aG9kcyB1c2UgdGhlIHBlcmZvcm1hbmNlIG9mIGEgbGVhcm5pbmcgYWxnb3JpdGhtIHRvIGFzc2VzcyB0aGUgdXNlZnVsbmVzcyBvZiBhIGZlYXR1cmUgc2V0LiBJbiBvcmRlciB0byBzZWxlY3QgYSBmZWF0dXJlIHN1YnNldCBhIGxlYXJuZXIgaXMgdHJhaW5lZCByZXBlYXRlZGx5IG9uIGRpZmZlcmVudCBmZWF0dXJlIHN1YnNldHMgYW5kIHRoZSBzdWJzZXQgd2hpY2ggbGVhZHMgdG8gdGhlIGJlc3QgbGVhcm5lciBwZXJmb3JtYW5jZSBpcyBjaG9zZW4uDQoNClRoZSBzZWFyY2ggc3RyYXRlZ3kgaXMgZGVmaW5lZCBieSBmdW5jdGlvbnMgZm9sbG93aW5nIHRoZSBuYW1pbmcgY29udmVudGlvbiBtYWtlRmVhdFNlbENvbnRyb2w8c2VhcmNoX3N0cmF0ZWd5LiBUaGUgZm9sbG93aW5nIHNlYXJjaCBzdHJhdGVnaWVzIGFyZSBhdmFpbGFibGU6DQoNCjEpIEV4aGF1c3RpdmUgc2VhcmNoIChtYWtlRmVhdFNlbENvbnRyb2xFeGhhdXN0aXZlKSwNCjIpIEdlbmV0aWMgYWxnb3JpdGhtIChtYWtlRmVhdFNlbENvbnRyb2xHQSksDQozKSBSYW5kb20gc2VhcmNoIChtYWtlRmVhdFNlbENvbnRyb2xSYW5kb20pLA0KNCkgRGV0ZXJtaW5pc3RpYyBmb3J3YXJkIG9yIGJhY2t3YXJkIHNlYXJjaCAobWFrZUZlYXRTZWxDb250cm9sU2VxdWVudGlhbCkNCg0KVGhlIGV4aGF1c3RpdmUgc2VhcmNoIGlzIGEgYnJ1dGUgZm9yY2UgbWV0aG9kLCBpdCB3aWxsIHNlYXJjaCBmb3IgdGhlIGJlc3QgZmVhdHVyZSBzZXQgYW1vbmcgYWxsIHBvc3NpYmxlIGNvbWJpbmF0aW9ucyBvZiBvdXIgZmVhdHVyZXMuIFN1Y2ggbWV0aG9kIGlzIG9ubHkgZmVhc2libGUgZm9yIHNpbXBsZSBwcm9ibGVtIChLPDUpLiBGb3IgbGFyZ2VyIGRhdGFzZXQgaXQncyBub3QgcmVjb21tbWVuZGVkIGFzIHRoZSBjb21wdXRhdGlvbiBjb3N0IGlzIHRvbyBoaWdoLg0KDQpJbiBvdXIgZXhwZXJpbWVudCwgd2Ugd2lsbCBhcHBseSBhIHJhbmRvbSBzZWFyY2guIEFzIHRoZSBCTVMgbW9kZWwgYWxyZWFkeSBzdWdnZXN0ZWQgaGlnaCBQTVAgZm9yIG1vZGVsIG9mIDQgdmFyaWFibGVzLCB3ZSB3aWxsIHNldCB0aGUgbWF4aW1hbCBudW1iZXIgb2YgdmFyaWFibGVzIHRvIDQgaW4gcmFuZG9tIHNlYXJjaC4gDQoNCkEgd3JhcHBlciBjb25zaXN0cyBvZiAzIGVsZW1lbnRzOg0KDQoxKSBBIHJlc2FtcGxpbmcgcHJvdG9jb2xlLCB3aGljaCBjb3VsZCBiZSBDcm9zcy12YWxpZGF0aW9uLCBLIGZvbGRzIENWLCBCb290c3RyYXAuLi4NCg0KMikgQSBmZWF0dXJlIHNlbGVjdGlvbiBjb250cm9sIG1vZGUsIHdoaWNoIGlzIFJhbmRvbSBzZWFyY2ggaW4gb3VyIGV4YW1wbGUNCg0KMykgQSBiYXNpYyBsZWFybmVyIHdoaWNoIGlzIGxpbmVhciByZWdyZXNzaW9uIChnbG0gYmFzZWQgbGVhcm5lcikgDQoNCldlIG11c3QgYWxzbyBjcmVhdGUgYSByZWdyZXNzaW9uIHRhc2sgZm9yIHRyYWluaW5nDQoNCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0UscmVzdWx0cz0iaGlkZSJ9DQoNCnRhc2s9bWFrZVJlZ3JUYXNrKGRhdGE9ZGYsdGFyZ2V0PSJicm96ZWtfZmF0IikNCg0KcmRlc2M9IG1ha2VSZXNhbXBsZURlc2MoIkNWIixpdGVycz0xMDBMKQ0KY3RybFJkbT1tYWtlRmVhdFNlbENvbnRyb2xSYW5kb20obWF4aXQ9MTAwTCxtYXguZmVhdHVyZXM9NCkNCndyYXA9bWFrZUZlYXRTZWxXcmFwcGVyKGxlYXJuZXIgPSAicmVnci5nbG0iLCByZXNhbXBsaW5nID0gcmRlc2MsY29udHJvbD1jdHJsUmRtKQ0KbW9kd3JhcD0gdHJhaW4od3JhcCx0YXNrKQ0KDQpgYGANCg0KSGVyZSBpcyB0aGUgcmVzdWx0IG9mIG1vZGVsIHRyYWluaW5nOg0KDQpgYGB7cn0NCm1vZHdyYXAkbGVhcm5lci5tb2RlbCRuZXh0Lm1vZGVsJGxlYXJuZXIubW9kZWwlPiVzdW1tYXJ5KCkNCmBgYA0KDQpUaGUgbW9kZWwgY29udGFpbnMgaW5kZWVkIDQgdmFyaWFibGVzLCBidXQgaXRzIHN0cnVjdHVyZSBpcyB0b3RhbGx5IGRpZmZlcmVudCB0byBvdGhlciBtZXRob2RzLg0KDQojIENPTVBBUkFUSVZFIFNUVURZDQoNCldlIGhhdmUgcGVyZm9ybWVkIHNvIGZhciA1IGRpZmZlcmVudCBtZXRob2RzIGZvciBmZWF0dXJlL21vZGVsIHNlbGVjdGlvbi4gV2UgY2FuIHNlZSB0aGF0IHRoZXkgYXJlIG5vdCBlcXVpdmFsZW50LCBlYWNoIG9uZSBzdWdnZXN0ZWQgYSBkaWZmZXJlbnQgZmVhdHVyZSBzdWJzZXQuIEJlZm9yZSBjbG9zaW5nIG91ciBleHBlcmltZW50LCB3ZSB3aWxsIHBlcmZvcm0gYSBzbWFsbCBjb21wYXJhdGl2ZSBzdHVkeSB0aGF0IGFpbXMgdG8gZXZhbHVhdGUgdGhlIHBlcmZvcm1hbmNlIG9mIGVhY2ggb25lIGFtb25nIHRob3NlIDUgbW9kZWxzLiANCg0KVGhlIG1vZGVsIHZhbGlkYXRpb24gaXMgYmFzZWQgdXBvbiA0IG1ldHJpY3M6DQoNCjEpIFJzcXVhcmUNCjIpIFN1bSBvZiBhYnNvbHV0ZSBlcnJvcnMNCjMpIFJvb3QgbWVhbiBzcXVhcmUgZXJyb3INCjQpIG1lYW4gYWJzb2x1dGUgZXJyb3INCg0KYGBge3IsbWVzc2FnZSA9IEZBTFNFLHdhcm5pbmc9RkFMU0V9DQoNCiNDb21iaW5pbmcgdGhlbSB0b2dldGhlcjoNCg0KZHVtbXlscm5lcj1tYWtlTGVhcm5lcigicmVnci5nbG0iKQ0KZHVtbXltb2Q9dHJhaW4oZHVtbXlscm5lcix0YXNrKQ0KZHVtbXlwcmVkPXByZWRpY3QoZHVtbXltb2QsdGFzaykNCg0KQk1TcHJlZD1wcmVkaWN0KGJtc21vZCxkZiklPiVhcy52ZWN0b3IoKQ0KTEFTU09wcmVkPXByZWRpY3QobGFzc28sYXMubWF0cml4KGRmWywtMV0pKSU+JWFzLnZlY3RvcigpDQpFTkVUcHJlZD1wcmVkaWN0KGVuZXQsYXMubWF0cml4KGRmWywtMV0pKSU+JWFzLnZlY3RvcigpDQpXUkFQcHJlZD1wcmVkaWN0KG1vZHdyYXAkbGVhcm5lci5tb2RlbCRuZXh0Lm1vZGVsJGxlYXJuZXIubW9kZWwpJT4lYXMudmVjdG9yKCkNClNURVBwcmVkPXByZWRpY3QoZ2xtY2FyLGRmKSU+JWFzLnZlY3RvcigpDQoNCnByZWQxPWR1bW15cHJlZA0KcHJlZDEkZGF0YSRyZXNwb25zZT1CTVNwcmVkDQpwcmVkMj1kdW1teXByZWQNCnByZWQyJGRhdGEkcmVzcG9uc2U9TEFTU09wcmVkDQpwcmVkMz1kdW1teXByZWQNCnByZWQzJGRhdGEkcmVzcG9uc2U9RU5FVHByZWQNCnByZWQ0PWR1bW15cHJlZA0KcHJlZDQkZGF0YSRyZXNwb25zZT1XUkFQcHJlZA0KcHJlZDU9ZHVtbXlwcmVkDQpwcmVkNSRkYXRhJHJlc3BvbnNlPVNURVBwcmVkDQoNCm1lYXN1cmU9bGlzdChyc3Escm1zZSxtYWUsc2FlKQ0KcDE9cGVyZm9ybWFuY2UocHJlZDEsbWVhc3VyZSkNCnAyPXBlcmZvcm1hbmNlKHByZWQyLG1lYXN1cmUpDQpwMz1wZXJmb3JtYW5jZShwcmVkMyxtZWFzdXJlKQ0KcDQ9cGVyZm9ybWFuY2UocHJlZDQsbWVhc3VyZSkNCnA1PXBlcmZvcm1hbmNlKHByZWQ1LG1lYXN1cmUpDQoNCnBkZj1yYmluZChwMSxwMixwMyxwNCxwNSklPiVhc190aWJibGUoKSU+JW11dGF0ZSguLE1ldGhvZD1jKCJCTVMiLCJMQVNTTyIsIkVORVQiLCJXUkFQUEVSIiwiU1RFUFdJU0UiKSkNCg0KcGRmbD1wZGYlPiVnYXRoZXIocnNxOnNhZSxrZXk9Ik1ldHJpYyIsdmFsdWU9IlZhbHVlIikNCg0KZ2dwbG90KHBkZmwpK2dlb21fdGlsZShhZXMoeT1NZXRob2QseD1NZXRyaWMsZmlsbD1WYWx1ZSksY29sb3I9ImJsYWNrIikrc2NhbGVfZmlsbF92aXJpZGlzKG9wdGlvbj0iQSIsYmVnaW49MC43LGVuZD0wLjk1KStnZW9tX3RleHQoYWVzKHk9TWV0aG9kLHg9TWV0cmljLGxhYmVsPXJvdW5kKFZhbHVlLDMpKSxjb2xvcj0iYmxhY2siKStnZ3RpdGxlKCJDb21wYXJpc29uIikrdGhlbWVfYncoMTUpDQoNCg0KcGRmMj1jYmluZChCTVNwcmVkLExBU1NPcHJlZCxFTkVUcHJlZCxXUkFQcHJlZCxTVEVQcHJlZCklPiVhc190aWJibGUoKSU+JW11dGF0ZSguLFRydXRoPWRmJGJyb3pla19mYXQpDQpwZGYyJT4lZ2F0aGVyKEJNU3ByZWQ6VHJ1dGgsa2V5PSJNb2RlbHMiLHZhbHVlPSJWYWx1ZSIpJT4lZ2dwbG90KGFlcyh4PVZhbHVlLGZpbGw9TW9kZWxzKSkrZ2VvbV9kZW5zaXR5KGFscGhhPTAuMykrdGhlbWVfYncoKQ0KDQoNCmBgYA0KDQojIENvbmNsdXNpb24NCg0KSW4gdGhpcyBjYXNlIHN0dWR5LCB3ZSBoYXZlIGV4cGxvcmVkIDUgZGlmZmVyZW50IG1ldGhvZHMgZm9yIG1vZGVsIChhbmQgZmVhdHVyZSkgc2VsZWN0aW9uIGFwcGxpZWQgdG8gYSByZWdyZXNzaW9uIHByb2JsZW0uIEl0J3MgY2xlYXIgdGhhdCB0aG9zZSA1IG1ldGhvZHMgYXJlIG5vdCBlcXVpdmFsZW50IGluIHRlcm1zIG9mIGZlYXR1cmUgc3Vic2V0IGFuZCBtb2RlbCdzIHBlcmZvcm1hbmNlLg0KDQpCTUEsIExhc3NvIGFuZCBFbGFzdGljbmV0IGFyZSBtb3JlIGVmZmVjdGl2ZSB0aGFuIFN0ZXB3aXNlIGFuZCBXcmFwcGVyIG1ldGhvZHMuIEJNQSBpcyB0aGUgbW9zdCBpbnRlcmVzdGluZyBtZXRob2QsIGFzIHRoaXMgbWV0aG9kIHByb3ZpZGVzIG1hbnkgdXNlZnVsIGluZm9ybWF0aW9uIGZvciBib3RoIGZlYXR1cmUgc2VsZWN0aW9uIGFuZCBtb2RlbCBpbnRlcnByZXRhdGlvbiwgaW5jbHVkaW5nIHRoZSBwb3N0ZXJpb3IgbW9kZWwgcHJvYmFiaWxpdGllcyBhbmQgY2VydGFpbnR5IG9mIGNvZWZmaWNpZW50IHZhbHVlcy4gVGhlIEJNQSBhbHNvIHBlcmZvcm1lZCBiZXR0ZXIgdGhhbiBFbGFzdGljIG5ldCBhbmQgTGFzc28gbW9kZWxzLiANCg0KSW4gb3VyIGV4YW1wbGUsIHRoZSBzdGVwd2lzZSBtb2RlbCBzZWxlY3Rpb24gcGVyZm9ybWVkIHN1cnByaXNpbmdseSB3ZWxsLCBhcyBpdCBoYXMgdGhlIGxvd2VzdCBlcnJvciBhbmQgbGFyZ2VzdCBSMiB2YWx1ZS4gSG93ZXZlciwgdGhlIGNvc3Qgb2Ygc3VjaCBwZXJmb3JtYW5jZSBjb3VsZCBiZSBoaWdoLiBUaGUgbWV0aG9kcyB0aGF0IGJhc2VkIG9uIGJydXRlIGZvcmNlIHN1Y2ggYXMgRXhoYXVzdGl2ZSBmZWF0dXJlIHR1bmluZywgRm9yd2FyZCBhbmQgQmFja3dhcmQgU3RlcHdpc2Ugc2VsZWN0aW9uLi4uIGFyZSBub3QgcmVjb21tZW5kZWQgZm9yIGxhcmdlIGRhdGFzZXRzLiANCg0KVGhvdWdoIHRoZSB3cmFwcGVyIG1ldGhvZCBpcyBzY2FsYWJsZSAoaXQgY291bGQgYmUgYXBwbGllZCB0byBhbnkgYWxnb3JpdGhtLCBub3Qgb25seSB0aGUgR0xNIGxlYXJuZXIpLCBzdWNoIG1ldGhvZCBpcyBub3QgcmVsaWFibGUsIGR1ZSB0byBhIHBvb3IgcmVwcm9kdWNpYmlsaXR5IGFuZCBoaWdoIGNvbXB1dGF0aW9uIGNvc3QuIERlc3BpdGUgdGhhdCBXcmFwcGVyIGlzIGJhc2VkIG9uIG1vZGVsIHBlcmZvcm1hbmNlLCBpdHMgZmluYWwgb3V0Y29tZSB3YXMgd29yc2UgdGhhbiBCYXllc2lhbiBNb2RlbCBhdmVyYWdpbmcgbWV0aG9kLg0KDQpUaGUgRWxhc3RpY25ldCBwZXJmb3JtZWQgYmV0dGVyIHRoYW4gTGFzc28uIFRoaXMgcmVndWxhcmlzYXRpb24gd2FzIGFsc28gaW50ZWdyYXRlZCB3aXRoaW4gdGhlIEdMTSBhbGdvcml0aG0gYnkgaDJvLiANCg0KSW4gY29uY2x1c2lvbiwgaWYgeW91ciBzdHVkeSBpcyBpbnRlcnByZXRpdmUsIEJNQSBpcyB0aGUgYmVzdCBjaG9pY2UuIEZvciBwcmVkaWN0aXZlIHB1cnBvc2UsIExhc3NvIGFuZCBFbGFzdGljIG5ldCBhcmUgdmVyeSBnb29kIHNvbHV0aW9ucy4gVGhlIHdyYXBwZXIgbWV0aG9kIGlzIG5vdCByZWNvbW1lbmRlZCB1bmxlc3MgeW91IHdhbnQgdG8gaW1wbHkgdW51c3VhbCBhbGdvcml0aG1zLiANCg0KKipUaGFuayBmb3Igam9pbmluZyB1cyBhbmQgc2VlIHlvdSBpbiB0aGUgbmV4dCB0dXRvcmlhbCoqDQoNCipFTkQqDQo=