1.0 Feature Selection Introduction

Feature selection is an important topic that requires in-depth knowledge of the problem domain. Having the right features can help the model perform better. For example, removing the highly correlated attributes can lead to a better model and improve prediction.

In this post, I will explore finding highly correlated variables, Recursive Feature Elimination, stepwise elimination, and Boruta feature selection.

1.1 Plot Histograms

First, I will plot a histogram of the variables in the Pima Indians Diabetes dataset. Various variable exhibit skew distributions and should be considered for transformations.

library("mlbench")
library("caret")
# load the data
data(PimaIndiansDiabetes)
DataExplorer::plot_histogram(PimaIndiansDiabetes)

1.2 Correlations

Below we will transform the response variable from a factor to numeric. Changing the variable allows us to see any correlations with other variables. I also created a highly correlated variable by combining glucose and mass. As expected glucose and mass are highly correlated with the combination variable HCorrelated.


library("corrplot")
package 㤼㸱corrplot㤼㸲 was built under R version 3.6.1corrplot 0.84 loaded
indians = PimaIndiansDiabetes
indians$diabetes = ifelse(indians$diabetes =="pos",1,0)
indians$HCorrelated =indians$glucose*indians$mass

cor_mx = cor(indians  ,use="pairwise.complete.obs", method = "pearson")
corrplot(cor_mx, method = "color", 
         type = "upper", order = "original", number.cex = .7,
         addCoef.col = "black", # Add coefficient of correlation
         tl.col = "black", tl.srt = 90, # Text label color and rotation
                  # hide correlation coefficient on the principal diagonal
         diag = TRUE)

NA
NA
NA
NA

2.0 Recursive Feature Elimination

2.1 Run the Feature Selection

Recursive Feature Elimination(RFE) builds models with different subsets of a dataset to identify a feature that might not be required. Caret provides a rfe function that facilitates this process.

Below we will load the Pima Indians Diabetes and fit the rfe function. Control was implemented using random forest cross-validated with kfold of 10. The final plot indicates that eight variables have an accuracy of 77.73.

# define the control using a random forest selection function
control = rfeControl(functions=rfFuncs, method="cv", number=10, repeats = 1) # method="cv" , leave out repeats to speed up or method = "repeatedcv", and leave out repeats 
# run the RFE algorithm
set.seed(143)
#
#   NOTE THAT THE PrimaIndians [,9] is a Factor Variable with neg pos levels (factor variable not numeric)- We choose the number of variables we want to see.
#
results = rfe(PimaIndiansDiabetes[,1:8], PimaIndiansDiabetes[,9], sizes=c(1:8), rfeControl=control,
              verbose=FALSE)
# summarize the results
print(results)

Recursive feature selection

Outer resampling method: Cross-Validated (10 fold) 

Resampling performance over subset size:

The top 5 variables (out of 8):
   glucose, mass, age, pregnant, insulin
results

Recursive feature selection

Outer resampling method: Cross-Validated (10 fold) 

Resampling performance over subset size:

The top 5 variables (out of 8):
   glucose, mass, age, pregnant, insulin

2.2 List the predictors in the Order of Choice


# list the chosen features
predictors(results)
[1] "glucose"  "mass"     "age"      "pregnant" "insulin"  "pedigree" "triceps"  "pressure"

2.3 Plot The Results

# plot the results
plot(results, type=c("g", "o"))

Another method relies on fitting a random forest model and identifying variable importance. In this method, variable importance can vary by model.

3.0 Variable Importance

# Another method relies on fitting a random forest model and identifying variable importance. In this method, variable importance can vary by model.

glimpse(PimaIndiansDiabetes)
Rows: 768
Columns: 9
$ pregnant <dbl> 6, 1, 8, 1, 0, 5, 3, 10, 2, 8, 4, 10, 10, 1, 5, 7, 0, 7, 1, 1, 3, 8, 7, 9, ...
$ glucose  <dbl> 148, 85, 183, 89, 137, 116, 78, 115, 197, 125, 110, 168, 139, 189, 166, 100...
$ pressure <dbl> 72, 66, 64, 66, 40, 74, 50, 0, 70, 96, 92, 74, 80, 60, 72, 0, 84, 74, 30, 7...
$ triceps  <dbl> 35, 29, 0, 23, 35, 0, 32, 0, 45, 0, 0, 0, 0, 23, 19, 0, 47, 0, 38, 30, 41, ...
$ insulin  <dbl> 0, 0, 0, 94, 168, 0, 88, 0, 543, 0, 0, 0, 0, 846, 175, 0, 230, 0, 83, 96, 2...
$ mass     <dbl> 33.6, 26.6, 23.3, 28.1, 43.1, 25.6, 31.0, 35.3, 30.5, 0.0, 37.6, 38.0, 27.1...
$ pedigree <dbl> 0.627, 0.351, 0.672, 0.167, 2.288, 0.201, 0.248, 0.134, 0.158, 0.232, 0.191...
$ age      <dbl> 50, 31, 32, 21, 33, 30, 26, 29, 53, 54, 30, 34, 57, 59, 51, 32, 31, 31, 33,...
$ diabetes <fct> pos, neg, pos, neg, pos, neg, pos, neg, pos, pos, neg, pos, neg, pos, pos, ...
Data=PimaIndiansDiabetes

set.seed(143)
rPartMod = train(diabetes ~ ., data=Data, method="rpart")
rpartImp = varImp(rPartMod,10)
plot(rpartImp, top = 8, main='Variable Importance Using Glaucoma Dataset')

NA
NA

4.0 StepWise Selection

Stepwise selection is a method that allows for variables to be added or remove in either direction. The model performance is measured in AIC. Akaike information criterion (AIC) estimates the quality of each model relative to each model with the lowest AIC being the best model.

4.1a Backward Selection

The backward procedure begins with a general model that includes all variables and eliminates one variable at a time.



#glm(diabetes ~ ., data=Data, family="binomial")
step(glm(diabetes ~ ., data=Data, family="binomial"),direction="backward")
Start:  AIC=741.45
diabetes ~ pregnant + glucose + pressure + triceps + insulin + 
    mass + pedigree + age

           Df Deviance    AIC
- triceps   1   723.45 739.45
- insulin   1   725.19 741.19
<none>          723.45 741.45
- age       1   725.97 741.97
- pressure  1   729.99 745.99
- pedigree  1   733.78 749.78
- pregnant  1   738.68 754.68
- mass      1   764.22 780.22
- glucose   1   838.37 854.37

Step:  AIC=739.45
diabetes ~ pregnant + glucose + pressure + insulin + mass + pedigree + 
    age

           Df Deviance    AIC
<none>          723.45 739.45
- insulin   1   725.46 739.46
- age       1   725.97 739.97
- pressure  1   730.13 744.13
- pedigree  1   733.92 747.92
- pregnant  1   738.69 752.69
- mass      1   768.77 782.77
- glucose   1   840.87 854.87

Call:  glm(formula = diabetes ~ pregnant + glucose + pressure + insulin + 
    mass + pedigree + age, family = "binomial", data = Data)

Coefficients:
(Intercept)     pregnant      glucose     pressure      insulin         mass     pedigree  
  -8.405136     0.123172     0.035112    -0.013214    -0.001157     0.090089     0.947595  
        age  
   0.014789  

Degrees of Freedom: 767 Total (i.e. Null);  760 Residual
Null Deviance:      993.5 
Residual Deviance: 723.5    AIC: 739.5

4.1b Forward Selection

The forward method begins with a simple model then adds suitable variable one at a time until the best model is obtained.



#glm(diabetes ~ ., data=Data, family="binomial")
step(glm(diabetes ~ ., data=Data, family="binomial"),direction="forward")
Start:  AIC=741.45
diabetes ~ pregnant + glucose + pressure + triceps + insulin + 
    mass + pedigree + age


Call:  glm(formula = diabetes ~ pregnant + glucose + pressure + triceps + 
    insulin + mass + pedigree + age, family = "binomial", data = Data)

Coefficients:
(Intercept)     pregnant      glucose     pressure      triceps      insulin         mass  
  -8.404696     0.123182     0.035164    -0.013296     0.000619    -0.001192     0.089701  
   pedigree          age  
   0.945180     0.014869  

Degrees of Freedom: 767 Total (i.e. Null);  759 Residual
Null Deviance:      993.5 
Residual Deviance: 723.4    AIC: 741.4

4.1c Both Selection

The both method is the combination of backwrad and forward procedures.



#glm(diabetes ~ ., data=Data, family="binomial")
step(glm(diabetes ~ ., data=Data, family="binomial"),direction="both")
Start:  AIC=741.45
diabetes ~ pregnant + glucose + pressure + triceps + insulin + 
    mass + pedigree + age

           Df Deviance    AIC
- triceps   1   723.45 739.45
- insulin   1   725.19 741.19
<none>          723.45 741.45
- age       1   725.97 741.97
- pressure  1   729.99 745.99
- pedigree  1   733.78 749.78
- pregnant  1   738.68 754.68
- mass      1   764.22 780.22
- glucose   1   838.37 854.37

Step:  AIC=739.45
diabetes ~ pregnant + glucose + pressure + insulin + mass + pedigree + 
    age

           Df Deviance    AIC
<none>          723.45 739.45
- insulin   1   725.46 739.46
- age       1   725.97 739.97
+ triceps   1   723.45 741.45
- pressure  1   730.13 744.13
- pedigree  1   733.92 747.92
- pregnant  1   738.69 752.69
- mass      1   768.77 782.77
- glucose   1   840.87 854.87

Call:  glm(formula = diabetes ~ pregnant + glucose + pressure + insulin + 
    mass + pedigree + age, family = "binomial", data = Data)

Coefficients:
(Intercept)     pregnant      glucose     pressure      insulin         mass     pedigree  
  -8.405136     0.123172     0.035112    -0.013214    -0.001157     0.090089     0.947595  
        age  
   0.014789  

Degrees of Freedom: 767 Total (i.e. Null);  760 Residual
Null Deviance:      993.5 
Residual Deviance: 723.5    AIC: 739.5

Seems that the model without Triceps is Best

5.0 Boruta

Boruta is a feature ranking and selection algorithm based on random forest algorithm. The advantages of using this package are the ease of variables selection and the ability to adjust variable selection.

Below I fitted the Boruta function with the dataset for evaluation.

library('Boruta')
set.seed(143)
boruta_output = Boruta(diabetes ~ ., data=na.omit(Data), doTrace=0) 

5.1 Significant Variables

The significant variables can be extracted from the selection. Tentative variables are variables that can be dropped or kept.


Significant_vars = getSelectedAttributes(boruta_output, withTentative = TRUE)
Significant_vars
[1] "pregnant" "glucose"  "pressure" "triceps"  "insulin"  "mass"     "pedigree" "age"     

Seems all are significant

Boruta has a method for making the selecting tentative variable for the user.

roughFixMod = TentativeRoughFix(boruta_output)
boruta_signif = getSelectedAttributes(roughFixMod)
boruta_signif
[1] "pregnant" "glucose"  "pressure" "triceps"  "insulin"  "mass"     "pedigree" "age"     

The importance of variables can be shown by the below method with being the most important variable.

5.2 Importance of Variables

The importance of variables can be shown by the below method with vari being the most important variable.


# Variable Importance Scores
imps = attStats(roughFixMod)
imps2 = imps[imps$decision != 'Rejected', c('meanImp', 'decision')]
head(imps2[order(-imps2$meanImp), ],10)  # descending sort
NA
NA

5.3 Plotting Importance of Variables


plot(boruta_output, cex.axis=.7, las=2, xlab="", main="Variable Importance")  

6.0 Information Value and Weights of Evidence (Categorical Variables)

The Information Value can be used to judge how important a given categorical variable is in explaining the binary Y variable. It goes well with logistic regression and other classification models that can model binary variables.

Let’s try to find out how important the categorical variables are in predicting if an individual will earn >50k from the ‘adult.csv’ dataset. Just run the code below to import the dataset.

library(InformationValue)
inputData <- read.csv("./Data/adult.csv")
print(head(inputData))

Alright, let’s now find the information value for the categorical variables in the inputData.

Here is what the quantum of Information Value means:

Less than 0.02, then the predictor is not useful for modeling (separating the Goods from the Bads) 0.02 to 0.1, then the predictor has only a weak relationship. 0.1 to 0.3, then the predictor has a medium strength relationship. 0.3 or higher, then the predictor has a strong relationship. That was about IV. Then what is Weight of Evidence?

Weights of evidence can be useful to find out how important a given categorical variable is in explaining the ‘events’ (called ‘Goods’ in below table.)

WOE = ln(%good of all good/%bad of all bad)

Here is what the quantum of Information Value means:

Less than 0.02, then the predictor is not useful for modeling (separating the Goods from the Bads) 0.02 to 0.1, then the predictor has only a weak relationship. 0.1 to 0.3, then the predictor has a medium strength relationship. 0.3 or higher, then the predictor has a strong relationship. That was about IV. Then what is Weight of Evidence?

Weights of evidence can be useful to find out how important a given categorical variable is in explaining the ‘events’ (called ‘Goods’ in below table.)

# The ‘Information Value’ of the categorical variable can then be derived from the respective WOE values.
# 
# IV?=?(perc good of all goods?perc bad of all bads)?*?WOE
# 
# The ‘WOETable’ below given the computation in more detail.

WOETable(X=inputData[, 'workclass'], Y=inputData$income)

The total IV of a variable is the sum of IV�s of its categories.

#—————————————————————————————————————- ## TUTORIAL USING OPTIMAL BINNING () # ——————————————————————————————————————-

7.0 WOE FROM Information Pcakage

7.1 Woe Continuous Variables and Factor Variables

library(Information)
package 㤼㸱Information㤼㸲 was built under R version 3.6.3

7.2 Make an independent Variable a Factor

It is important to note here the number of bins for ‘rank’ variable. Since it is a categorical variable, the number of bins would be according to unique values of the factor variable. The parameter bins=10 does not work for a factor variable.


mydata$rank=factor(mydata$rank)

7.3 Compute The Info Value

7.5 Put The Tables if Dataframes

To get WOE table for variable gre, you need to call Tables list from IV list.

We can do this for any of the variables

gre = data.frame(IV$Tables$gre)

gpa = data.frame(IV$Tables$gpa)

X = data.frame(IV$Tables$X)

rank = data.frame(IV$Tables$rank)

7.6 Plot Woe Scores For 1 Variable

We can plot 1 at a time


plot_infotables(IV, "gre")

7.7 Plot Woe Scores For Many Variable

We can plot many at a time


plot_infotables(IV, IV$Summary$Variable[1:4], same_scale=FALSE)

8.0 WOE Bimmnning

This package generates, visualizes, tabulates and deploys a supervised weight of evidence (WOE) binning of variables.

Details

This package generates, visualizes, tabulates and deploys a supervised weight of evidence (WOE) binning of variables.

The package woeBinning automates the process of binning of numeric variables and factors with respect to a dichotomous target variable. Additionally, it visualizes the realized binning solution, tabulates it and deploys it to (new) data. All functions can be used with single variables or an entire data frame.

8.1 Binning

woe.binning generates a supervised fine and coarse classing of numeric variables and factors.

woe.tree.binning generates a supervised tree-like segmentation of numeric variables and factors.

woe.binning.plot visualizes the binning solution generated and saved via woe.binning or woe.tree.binning.

woe.binning.table tabulates the binning solution generated and saved via woe.binning or woe.tree.binning.

woe.binning.deploy deploys the binning solution generated and saved via woe.binning or woe.tree.binning to (new) data.

References

Siddiqi, N. 2006: Credit Risk Scorecards: Developing and Implementing Intelligent Credit Scoring. Hoboken, New Jersey: John Wiley & Sons.

Anderson, R. 2007: The Credit Scoring Toolkit: Theory and Practice for Retail Credit Risk Management and Decision Automation. Oxford / New York: Oxford University Press.

8.2 Examples


# Load German credit data and create subset
data(germancredit)
data set 㤼㸱germancredit㤼㸲 not found
df <- germancredit[, c('creditability', 'credit.amount', 'duration.in.month',
                  'savings.account.and.bonds', 'purpose')]
Error: object 'germancredit' not found
LS0tDQp0aXRsZTogIkZlYXR1cmUgU2VsZWN0aW9uIEJsb2ciDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIyMgIDEuMCBGZWF0dXJlIFNlbGVjdGlvbiBJbnRyb2R1Y3Rpb24NCg0KRmVhdHVyZSBzZWxlY3Rpb24gaXMgYW4gaW1wb3J0YW50IHRvcGljIHRoYXQgcmVxdWlyZXMgaW4tZGVwdGgga25vd2xlZGdlIG9mIHRoZSBwcm9ibGVtIGRvbWFpbi4gSGF2aW5nIHRoZSByaWdodCBmZWF0dXJlcyBjYW4gaGVscCB0aGUgbW9kZWwgcGVyZm9ybSBiZXR0ZXIuIEZvciBleGFtcGxlLCByZW1vdmluZyB0aGUgaGlnaGx5IGNvcnJlbGF0ZWQgYXR0cmlidXRlcyBjYW4gbGVhZCB0byBhIGJldHRlciBtb2RlbCBhbmQgaW1wcm92ZSBwcmVkaWN0aW9uLg0KDQpJbiB0aGlzIHBvc3QsIEkgd2lsbCBleHBsb3JlIGZpbmRpbmcgaGlnaGx5IGNvcnJlbGF0ZWQgdmFyaWFibGVzLCBSZWN1cnNpdmUgRmVhdHVyZSBFbGltaW5hdGlvbiwgc3RlcHdpc2UgZWxpbWluYXRpb24sIGFuZCBCb3J1dGEgZmVhdHVyZSBzZWxlY3Rpb24uDQoNCiMjIyAxLjEgUGxvdCBIaXN0b2dyYW1zDQoNCkZpcnN0LCBJIHdpbGwgcGxvdCBhIGhpc3RvZ3JhbSBvZiB0aGUgdmFyaWFibGVzIGluIHRoZSBQaW1hIEluZGlhbnMgRGlhYmV0ZXMgZGF0YXNldC4gVmFyaW91cyB2YXJpYWJsZSBleGhpYml0IHNrZXcgZGlzdHJpYnV0aW9ucyBhbmQgc2hvdWxkIGJlIGNvbnNpZGVyZWQgZm9yIHRyYW5zZm9ybWF0aW9ucy4NCg0KDQpgYGB7ciBQbG90IEhpc3RvZ3JhbXN9DQpsaWJyYXJ5KCJtbGJlbmNoIikNCmxpYnJhcnkoImNhcmV0IikNCiMgbG9hZCB0aGUgZGF0YQ0KZGF0YShQaW1hSW5kaWFuc0RpYWJldGVzKQ0KRGF0YUV4cGxvcmVyOjpwbG90X2hpc3RvZ3JhbShQaW1hSW5kaWFuc0RpYWJldGVzKQ0KYGBgDQoNCiMjIyAxLjIgQ29ycmVsYXRpb25zDQoNCkJlbG93IHdlIHdpbGwgdHJhbnNmb3JtIHRoZSByZXNwb25zZSB2YXJpYWJsZSBmcm9tIGEgZmFjdG9yIHRvIG51bWVyaWMuIENoYW5naW5nIHRoZSB2YXJpYWJsZSBhbGxvd3MgdXMgdG8gc2VlIGFueSBjb3JyZWxhdGlvbnMgd2l0aCBvdGhlciB2YXJpYWJsZXMuIEkgYWxzbyBjcmVhdGVkIGEgaGlnaGx5IGNvcnJlbGF0ZWQgdmFyaWFibGUgYnkgY29tYmluaW5nIGdsdWNvc2UgYW5kIG1hc3MuIEFzIGV4cGVjdGVkIGdsdWNvc2UgYW5kIG1hc3MgYXJlIGhpZ2hseSBjb3JyZWxhdGVkIHdpdGggdGhlIGNvbWJpbmF0aW9uIHZhcmlhYmxlIEhDb3JyZWxhdGVkLg0KDQpgYGB7ciAgQ29ycmVsYXRpb25zfQ0KDQpsaWJyYXJ5KCJjb3JycGxvdCIpDQppbmRpYW5zID0gUGltYUluZGlhbnNEaWFiZXRlcw0KaW5kaWFucyRkaWFiZXRlcyA9IGlmZWxzZShpbmRpYW5zJGRpYWJldGVzID09InBvcyIsMSwwKQ0KaW5kaWFucyRIQ29ycmVsYXRlZCA9aW5kaWFucyRnbHVjb3NlKmluZGlhbnMkbWFzcw0KDQpjb3JfbXggPSBjb3IoaW5kaWFucyAgLHVzZT0icGFpcndpc2UuY29tcGxldGUub2JzIiwgbWV0aG9kID0gInBlYXJzb24iKQ0KY29ycnBsb3QoY29yX214LCBtZXRob2QgPSAiY29sb3IiLCANCiAgICAgICAgIHR5cGUgPSAidXBwZXIiLCBvcmRlciA9ICJvcmlnaW5hbCIsIG51bWJlci5jZXggPSAuNywNCiAgICAgICAgIGFkZENvZWYuY29sID0gImJsYWNrIiwgIyBBZGQgY29lZmZpY2llbnQgb2YgY29ycmVsYXRpb24NCiAgICAgICAgIHRsLmNvbCA9ICJibGFjayIsIHRsLnNydCA9IDkwLCAjIFRleHQgbGFiZWwgY29sb3IgYW5kIHJvdGF0aW9uDQogICAgICAgICAgICAgICAgICAjIGhpZGUgY29ycmVsYXRpb24gY29lZmZpY2llbnQgb24gdGhlIHByaW5jaXBhbCBkaWFnb25hbA0KICAgICAgICAgZGlhZyA9IFRSVUUpDQoNCg0KDQoNCmBgYA0KDQojIyMgMi4wIFJlY3Vyc2l2ZSBGZWF0dXJlIEVsaW1pbmF0aW9uDQoNCiMjIyMgMi4xIFJ1biB0aGUgRmVhdHVyZSBTZWxlY3Rpb24NCg0KUmVjdXJzaXZlIEZlYXR1cmUgRWxpbWluYXRpb24oUkZFKSBidWlsZHMgbW9kZWxzIHdpdGggZGlmZmVyZW50IHN1YnNldHMgb2YgYSBkYXRhc2V0IHRvIGlkZW50aWZ5IGEgZmVhdHVyZSB0aGF0IG1pZ2h0IG5vdCBiZSByZXF1aXJlZC4gQ2FyZXQgcHJvdmlkZXMgYSByZmUgZnVuY3Rpb24gdGhhdCBmYWNpbGl0YXRlcyB0aGlzIHByb2Nlc3MuDQoNCkJlbG93IHdlIHdpbGwgbG9hZCB0aGUgUGltYSBJbmRpYW5zIERpYWJldGVzIGFuZCBmaXQgdGhlIHJmZSBmdW5jdGlvbi4gQ29udHJvbCB3YXMgaW1wbGVtZW50ZWQgdXNpbmcgcmFuZG9tIGZvcmVzdCBjcm9zcy12YWxpZGF0ZWQgd2l0aCBrZm9sZCBvZiAxMC4gVGhlIGZpbmFsIHBsb3QgaW5kaWNhdGVzIHRoYXQgZWlnaHQgdmFyaWFibGVzIGhhdmUgYW4gYWNjdXJhY3kgb2YgNzcuNzMuDQoNCmBgYHtyIFJGRX0NCiMgZGVmaW5lIHRoZSBjb250cm9sIHVzaW5nIGEgcmFuZG9tIGZvcmVzdCBzZWxlY3Rpb24gZnVuY3Rpb24NCmNvbnRyb2wgPSByZmVDb250cm9sKGZ1bmN0aW9ucz1yZkZ1bmNzLCBtZXRob2Q9ImN2IiwgbnVtYmVyPTEwLCByZXBlYXRzID0gMSkgIyBtZXRob2Q9ImN2IiAsIGxlYXZlIG91dCByZXBlYXRzIHRvIHNwZWVkIHVwIG9yIG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgYW5kIGxlYXZlIG91dCByZXBlYXRzIA0KIyBydW4gdGhlIFJGRSBhbGdvcml0aG0NCnNldC5zZWVkKDE0MykNCiMNCiMgICBOT1RFIFRIQVQgVEhFIFByaW1hSW5kaWFucyBbLDldIGlzIGEgRmFjdG9yIFZhcmlhYmxlIHdpdGggbmVnIHBvcyBsZXZlbHMgKGZhY3RvciB2YXJpYWJsZSBub3QgbnVtZXJpYyktIFdlIGNob29zZSB0aGUgbnVtYmVyIG9mIHZhcmlhYmxlcyB3ZSB3YW50IHRvIHNlZS4NCiMNCnJlc3VsdHMgPSByZmUoUGltYUluZGlhbnNEaWFiZXRlc1ssMTo4XSwgUGltYUluZGlhbnNEaWFiZXRlc1ssOV0sIHNpemVzPWMoMTo4KSwgcmZlQ29udHJvbD1jb250cm9sLA0KICAgICAgICAgICAgICB2ZXJib3NlPUZBTFNFKQ0KIyBzdW1tYXJpemUgdGhlIHJlc3VsdHMNCnByaW50KHJlc3VsdHMpDQoNCnJlc3VsdHMNCg0KDQpgYGANCg0KDQojIyMjIDIuMiBMaXN0IHRoZSBwcmVkaWN0b3JzIGluIHRoZSBPcmRlciBvZiBDaG9pY2UNCg0KYGBge3IgTGlzdCBQcmVkaWN0b3JzfQ0KDQojIGxpc3QgdGhlIGNob3NlbiBmZWF0dXJlcw0KcHJlZGljdG9ycyhyZXN1bHRzKQ0KDQoNCmBgYA0KDQojIyMjIDIuMyBQbG90IFRoZSBSZXN1bHRzDQoNCmBgYHtyfQ0KIyBwbG90IHRoZSByZXN1bHRzDQpwbG90KHJlc3VsdHMsIHR5cGU9YygiZyIsICJvIikpDQpgYGANCg0KQW5vdGhlciBtZXRob2QgcmVsaWVzIG9uIGZpdHRpbmcgYSByYW5kb20gZm9yZXN0IG1vZGVsIGFuZCBpZGVudGlmeWluZyB2YXJpYWJsZSBpbXBvcnRhbmNlLiBJbiB0aGlzIG1ldGhvZCwgdmFyaWFibGUgaW1wb3J0YW5jZSBjYW4gdmFyeSBieSBtb2RlbC4NCg0KIyMjIDMuMCBWYXJpYWJsZSBJbXBvcnRhbmNlDQoNCmBgYHtyIFRlc3QgV2l0aCBBcnRpY2xlIERhdGF9DQojIEFub3RoZXIgbWV0aG9kIHJlbGllcyBvbiBmaXR0aW5nIGEgcmFuZG9tIGZvcmVzdCBtb2RlbCBhbmQgaWRlbnRpZnlpbmcgdmFyaWFibGUgaW1wb3J0YW5jZS4gSW4gdGhpcyBtZXRob2QsIHZhcmlhYmxlIGltcG9ydGFuY2UgY2FuIHZhcnkgYnkgbW9kZWwuDQoNCmdsaW1wc2UoUGltYUluZGlhbnNEaWFiZXRlcykNCkRhdGE9UGltYUluZGlhbnNEaWFiZXRlcw0KDQpzZXQuc2VlZCgxNDMpDQpyUGFydE1vZCA9IHRyYWluKGRpYWJldGVzIH4gLiwgZGF0YT1EYXRhLCBtZXRob2Q9InJwYXJ0IikNCnJwYXJ0SW1wID0gdmFySW1wKHJQYXJ0TW9kLDEwKQ0KcGxvdChycGFydEltcCwgdG9wID0gOCwgbWFpbj0nVmFyaWFibGUgSW1wb3J0YW5jZSBVc2luZyBQaW1hSW5kaWFucyBEYXRhc2V0JykNCg0KDQpgYGANCg0KDQojIyMgNC4wIFN0ZXBXaXNlIFNlbGVjdGlvbg0KDQpTdGVwd2lzZSBzZWxlY3Rpb24gaXMgYSBtZXRob2QgdGhhdCBhbGxvd3MgZm9yIHZhcmlhYmxlcyB0byBiZSBhZGRlZCBvciByZW1vdmUgaW4gZWl0aGVyIGRpcmVjdGlvbi4gVGhlIG1vZGVsIHBlcmZvcm1hbmNlIGlzIG1lYXN1cmVkIGluIEFJQy4gQWthaWtlIGluZm9ybWF0aW9uIGNyaXRlcmlvbiAoQUlDKSBlc3RpbWF0ZXMgdGhlIHF1YWxpdHkgb2YgZWFjaCBtb2RlbCByZWxhdGl2ZSB0byBlYWNoIG1vZGVsIHdpdGggdGhlIGxvd2VzdCBBSUMgYmVpbmcgdGhlIGJlc3QgbW9kZWwuDQoNCg0KIyMjIyA0LjFhIEJhY2t3YXJkIFNlbGVjdGlvbiANCg0KDQpUaGUgYmFja3dhcmQgcHJvY2VkdXJlIGJlZ2lucyB3aXRoIGEgZ2VuZXJhbCBtb2RlbCB0aGF0IGluY2x1ZGVzIGFsbCB2YXJpYWJsZXMgYW5kIGVsaW1pbmF0ZXMgb25lIHZhcmlhYmxlIGF0IGEgdGltZS4NCg0KYGBge3IgQmFja3dhcmR9DQoNCg0KI2dsbShkaWFiZXRlcyB+IC4sIGRhdGE9RGF0YSwgZmFtaWx5PSJiaW5vbWlhbCIpDQpzdGVwKGdsbShkaWFiZXRlcyB+IC4sIGRhdGE9RGF0YSwgZmFtaWx5PSJiaW5vbWlhbCIpLGRpcmVjdGlvbj0iYmFja3dhcmQiKQ0KDQpgYGANCg0KIyMjIyA0LjFiIEZvcndhcmQgIFNlbGVjdGlvbiANCg0KDQpUaGUgZm9yd2FyZCBtZXRob2QgYmVnaW5zIHdpdGggYSBzaW1wbGUgbW9kZWwgdGhlbiBhZGRzIHN1aXRhYmxlIHZhcmlhYmxlIG9uZSBhdCBhIHRpbWUgdW50aWwgdGhlIGJlc3QgbW9kZWwgaXMgb2J0YWluZWQuDQoNCmBgYHtyIEZvcndhcmR9DQoNCg0KI2dsbShkaWFiZXRlcyB+IC4sIGRhdGE9RGF0YSwgZmFtaWx5PSJiaW5vbWlhbCIpDQpzdGVwKGdsbShkaWFiZXRlcyB+IC4sIGRhdGE9RGF0YSwgZmFtaWx5PSJiaW5vbWlhbCIpLGRpcmVjdGlvbj0iZm9yd2FyZCIpDQoNCmBgYA0KDQojIyMjIDQuMWMgQm90aCBTZWxlY3Rpb24NCg0KDQpUaGUgYm90aCBtZXRob2QgaXMgdGhlIGNvbWJpbmF0aW9uIG9mIGJhY2t3cmFkIGFuZCBmb3J3YXJkIHByb2NlZHVyZXMuDQoNCmBgYHtyIEJvdGggfQ0KDQoNCiNnbG0oZGlhYmV0ZXMgfiAuLCBkYXRhPURhdGEsIGZhbWlseT0iYmlub21pYWwiKQ0Kc3RlcChnbG0oZGlhYmV0ZXMgfiAuLCBkYXRhPURhdGEsIGZhbWlseT0iYmlub21pYWwiKSxkaXJlY3Rpb249ImJvdGgiKQ0KDQpgYGANCg0KU2VlbXMgdGhhdCB0aGUgbW9kZWwgd2l0aG91dCBUcmljZXBzIGlzIEJlc3QgDQoNCiMjIyA1LjAgQm9ydXRhDQoNCkJvcnV0YSBpcyBhIGZlYXR1cmUgcmFua2luZyBhbmQgc2VsZWN0aW9uIGFsZ29yaXRobSBiYXNlZCBvbiByYW5kb20gZm9yZXN0IGFsZ29yaXRobS4gVGhlIGFkdmFudGFnZXMgb2YgdXNpbmcgdGhpcyBwYWNrYWdlIGFyZSB0aGUgZWFzZSBvZiB2YXJpYWJsZXMgc2VsZWN0aW9uIGFuZCB0aGUgYWJpbGl0eSB0byBhZGp1c3QgdmFyaWFibGUgc2VsZWN0aW9uLg0KDQpCZWxvdyBJIGZpdHRlZCB0aGUgQm9ydXRhIGZ1bmN0aW9uIHdpdGggdGhlIGRhdGFzZXQgZm9yIGV2YWx1YXRpb24uDQoNCmBgYHtyfQ0KbGlicmFyeSgnQm9ydXRhJykNCnNldC5zZWVkKDE0MykNCmJvcnV0YV9vdXRwdXQgPSBCb3J1dGEoZGlhYmV0ZXMgfiAuLCBkYXRhPW5hLm9taXQoRGF0YSksIGRvVHJhY2U9MCkgDQpgYGANCg0KIyMjIyA1LjEgU2lnbmlmaWNhbnQgVmFyaWFibGVzDQoNClRoZSBzaWduaWZpY2FudCB2YXJpYWJsZXMgY2FuIGJlIGV4dHJhY3RlZCBmcm9tIHRoZSBzZWxlY3Rpb24uIFRlbnRhdGl2ZSB2YXJpYWJsZXMgYXJlIHZhcmlhYmxlcyB0aGF0IGNhbiBiZSBkcm9wcGVkIG9yIGtlcHQuDQoNCg0KYGBge3IgU2lnaWZpY2FudCBWYXJpYWJsZXN9DQoNClNpZ25pZmljYW50X3ZhcnMgPSBnZXRTZWxlY3RlZEF0dHJpYnV0ZXMoYm9ydXRhX291dHB1dCwgd2l0aFRlbnRhdGl2ZSA9IFRSVUUpDQpTaWduaWZpY2FudF92YXJzDQoNCg0KDQpgYGANCg0KU2VlbXMgYWxsIGFyZSBzaWduaWZpY2FudA0KDQpCb3J1dGEgaGFzIGEgbWV0aG9kIGZvciBtYWtpbmcgdGhlIHNlbGVjdGluZyB0ZW50YXRpdmUgdmFyaWFibGUgZm9yIHRoZSB1c2VyLg0KDQpgYGB7cn0NCnJvdWdoRml4TW9kID0gVGVudGF0aXZlUm91Z2hGaXgoYm9ydXRhX291dHB1dCkNCmJvcnV0YV9zaWduaWYgPSBnZXRTZWxlY3RlZEF0dHJpYnV0ZXMocm91Z2hGaXhNb2QpDQpib3J1dGFfc2lnbmlmDQpgYGANCg0KDQpUaGUgaW1wb3J0YW5jZSBvZiB2YXJpYWJsZXMgY2FuIGJlIHNob3duIGJ5IHRoZSBiZWxvdyBtZXRob2Qgd2l0aCAgYmVpbmcgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlLg0KDQoNCiMjIyMgNS4yIEltcG9ydGFuY2Ugb2YgVmFyaWFibGVzDQoNCg0KVGhlIGltcG9ydGFuY2Ugb2YgdmFyaWFibGVzIGNhbiBiZSBzaG93biBieSB0aGUgYmVsb3cgbWV0aG9kIHdpdGggdmFyaSBiZWluZyB0aGUgbW9zdCBpbXBvcnRhbnQgdmFyaWFibGUuDQoNCmBgYHtyIFZhcmlhYmxlIEltcG9ydGFuY2V9DQoNCiMgVmFyaWFibGUgSW1wb3J0YW5jZSBTY29yZXMNCmltcHMgPSBhdHRTdGF0cyhyb3VnaEZpeE1vZCkNCmltcHMyID0gaW1wc1tpbXBzJGRlY2lzaW9uICE9ICdSZWplY3RlZCcsIGMoJ21lYW5JbXAnLCAnZGVjaXNpb24nKV0NCmhlYWQoaW1wczJbb3JkZXIoLWltcHMyJG1lYW5JbXApLCBdLDEwKSAgIyBkZXNjZW5kaW5nIHNvcnQNCg0KDQpgYGANCg0KDQojIyMjIDUuMyBQbG90dGluZyBJbXBvcnRhbmNlIG9mIFZhcmlhYmxlcw0KDQpgYGB7ciBQbG90dGluZ30NCg0KcGxvdChib3J1dGFfb3V0cHV0LCBjZXguYXhpcz0uNywgbGFzPTIsIHhsYWI9IiIsIG1haW49IlZhcmlhYmxlIEltcG9ydGFuY2UiKSAgDQoNCmBgYA0KDQojIyMgNi4wIEluZm9ybWF0aW9uIFZhbHVlIGFuZCBXZWlnaHRzIG9mIEV2aWRlbmNlIChDYXRlZ29yaWNhbCBWYXJpYWJsZXMpDQoNClRoZSBJbmZvcm1hdGlvbiBWYWx1ZSBjYW4gYmUgdXNlZCB0byBqdWRnZSBob3cgaW1wb3J0YW50IGEgZ2l2ZW4gY2F0ZWdvcmljYWwgdmFyaWFibGUgaXMgaW4gZXhwbGFpbmluZyB0aGUgYmluYXJ5IFkgdmFyaWFibGUuIEl0IGdvZXMgd2VsbCB3aXRoIGxvZ2lzdGljIHJlZ3Jlc3Npb24gYW5kIG90aGVyIGNsYXNzaWZpY2F0aW9uIG1vZGVscyB0aGF0IGNhbiBtb2RlbCBiaW5hcnkgdmFyaWFibGVzLg0KDQpMZXTigJlzIHRyeSB0byBmaW5kIG91dCBob3cgaW1wb3J0YW50IHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYXJlIGluIHByZWRpY3RpbmcgaWYgYW4gaW5kaXZpZHVhbCB3aWxsIGVhcm4gPjUwayBmcm9tIHRoZSDigJhhZHVsdC5jc3bigJkgZGF0YXNldC4gSnVzdCBydW4gdGhlIGNvZGUgYmVsb3cgdG8gaW1wb3J0IHRoZSBkYXRhc2V0Lg0KDQpgYGB7cn0NCmxpYnJhcnkoSW5mb3JtYXRpb25WYWx1ZSkNCmlucHV0RGF0YSA8LSByZWFkLmNzdigiLi9EYXRhL2FkdWx0LmNzdiIpDQpwcmludChoZWFkKGlucHV0RGF0YSkpDQpgYGANCg0KQWxyaWdodCwgbGV04oCZcyBub3cgZmluZCB0aGUgaW5mb3JtYXRpb24gdmFsdWUgZm9yIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgaW4gdGhlIGlucHV0RGF0YS4NCg0KYGBge3IgSW5mb3JtYXRpb24gVmFsdWV9DQppbnB1dERhdGEkaW5jb21lPWlmZWxzZShpbnB1dERhdGEkaW5jb21lPT0iPD01MEsiLDAsMSkNCiMgQ2hvb3NlIENhdGVnb3JpY2FsIFZhcmlhYmxlcyB0byBjb21wdXRlIEluZm8gVmFsdWUuIENoYW5nZSBWYXIgbmFtZXMgDQpjYXRfdmFycyA8LSBjICgid29ya2NsYXNzIiwgImVkdWNhdGlvbiIsICJtYXJpdGFsLnN0YXR1cyIsICJvY2N1cGF0aW9uIiwgInJlbGF0aW9uc2hpcCIsICJyYWNlIiwgImdlbmRlciIsICJuYXRpdmUuY291bnRyeSIpICAjIGdldCBhbGwgY2F0ZWdvcmljYWwgdmFyaWFibGVzDQoNCiMgSW5pdCBPdXRwdXQNCmRmX2l2IDwtIGRhdGEuZnJhbWUoVkFSUz1jYXRfdmFycywgSVY9bnVtZXJpYyhsZW5ndGgoY2F0X3ZhcnMpKSwgU1RSRU5HVEg9Y2hhcmFjdGVyKGxlbmd0aChjYXRfdmFycykpLCBzdHJpbmdzQXNGYWN0b3JzID0gRikgICMgaW5pdCBvdXRwdXQgZGF0YWZyYW1lDQoNCg0KDQoNCiMgR2V0IEluZm9ybWF0aW9uIFZhbHVlIGZvciBlYWNoIHZhcmlhYmxlDQpmb3IgKGZhY3Rvcl92YXIgaW4gY2F0X3ZhcnMpew0KICBkZl9pdltkZl9pdiRWQVJTID09IGZhY3Rvcl92YXIsICJJViJdIDwtIEluZm9ybWF0aW9uVmFsdWU6OklWKFg9aW5wdXREYXRhWywgZmFjdG9yX3Zhcl0sIFk9aW5wdXREYXRhJGluY29tZSkNCiAgZGZfaXZbZGZfaXYkVkFSUyA9PSBmYWN0b3JfdmFyLCAiU1RSRU5HVEgiXSA8LSBhdHRyKEluZm9ybWF0aW9uVmFsdWU6OklWKFg9aW5wdXREYXRhWywgZmFjdG9yX3Zhcl0sIFk9aW5wdXREYXRhJGluY29tZSksICJob3dnb29kIikNCn0NCg0KIyBTb3J0DQpkZl9pdiA8LSBkZl9pdltvcmRlcigtZGZfaXYkSVYpLCBdDQoNCmRmX2l2DQoNCg0KDQoNCmBgYA0KDQpIZXJlIGlzIHdoYXQgdGhlIHF1YW50dW0gb2YgSW5mb3JtYXRpb24gVmFsdWUgbWVhbnM6DQoNCkxlc3MgdGhhbiAwLjAyLCB0aGVuIHRoZSBwcmVkaWN0b3IgaXMgbm90IHVzZWZ1bCBmb3IgbW9kZWxpbmcgKHNlcGFyYXRpbmcgdGhlIEdvb2RzIGZyb20gdGhlIEJhZHMpDQowLjAyIHRvIDAuMSwgdGhlbiB0aGUgcHJlZGljdG9yIGhhcyBvbmx5IGEgd2VhayByZWxhdGlvbnNoaXAuDQowLjEgdG8gMC4zLCB0aGVuIHRoZSBwcmVkaWN0b3IgaGFzIGEgbWVkaXVtIHN0cmVuZ3RoIHJlbGF0aW9uc2hpcC4NCjAuMyBvciBoaWdoZXIsIHRoZW4gdGhlIHByZWRpY3RvciBoYXMgYSBzdHJvbmcgcmVsYXRpb25zaGlwLg0KVGhhdCB3YXMgYWJvdXQgSVYuIFRoZW4gd2hhdCBpcyBXZWlnaHQgb2YgRXZpZGVuY2U/DQoNCldlaWdodHMgb2YgZXZpZGVuY2UgY2FuIGJlIHVzZWZ1bCB0byBmaW5kIG91dCBob3cgaW1wb3J0YW50IGEgZ2l2ZW4gY2F0ZWdvcmljYWwgdmFyaWFibGUgaXMgaW4gZXhwbGFpbmluZyB0aGUg4oCYZXZlbnRz4oCZIChjYWxsZWQg4oCYR29vZHPigJkgaW4gYmVsb3cgdGFibGUuKQ0KDQpXT0UgPSBsbiglZ29vZCBvZiBhbGwgZ29vZC8lYmFkIG9mIGFsbCBiYWQpDQoNCkhlcmUgaXMgd2hhdCB0aGUgcXVhbnR1bSBvZiBJbmZvcm1hdGlvbiBWYWx1ZSBtZWFuczoNCg0KTGVzcyB0aGFuIDAuMDIsIHRoZW4gdGhlIHByZWRpY3RvciBpcyBub3QgdXNlZnVsIGZvciBtb2RlbGluZyAoc2VwYXJhdGluZyB0aGUgR29vZHMgZnJvbSB0aGUgQmFkcykNCjAuMDIgdG8gMC4xLCB0aGVuIHRoZSBwcmVkaWN0b3IgaGFzIG9ubHkgYSB3ZWFrIHJlbGF0aW9uc2hpcC4NCjAuMSB0byAwLjMsIHRoZW4gdGhlIHByZWRpY3RvciBoYXMgYSBtZWRpdW0gc3RyZW5ndGggcmVsYXRpb25zaGlwLg0KMC4zIG9yIGhpZ2hlciwgdGhlbiB0aGUgcHJlZGljdG9yIGhhcyBhIHN0cm9uZyByZWxhdGlvbnNoaXAuDQpUaGF0IHdhcyBhYm91dCBJVi4gVGhlbiB3aGF0IGlzIFdlaWdodCBvZiBFdmlkZW5jZT8NCg0KV2VpZ2h0cyBvZiBldmlkZW5jZSBjYW4gYmUgdXNlZnVsIHRvIGZpbmQgb3V0IGhvdyBpbXBvcnRhbnQgYSBnaXZlbiBjYXRlZ29yaWNhbCB2YXJpYWJsZSBpcyBpbiBleHBsYWluaW5nIHRoZSDigJhldmVudHPigJkgKGNhbGxlZCDigJhHb29kc+KAmSBpbiBiZWxvdyB0YWJsZS4pDQoNCg0KYGBge3J9DQojIFRoZSDigJhJbmZvcm1hdGlvbiBWYWx1ZeKAmSBvZiB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGUgY2FuIHRoZW4gYmUgZGVyaXZlZCBmcm9tIHRoZSByZXNwZWN0aXZlIFdPRSB2YWx1ZXMuDQojIA0KIyBJVj89PyhwZXJjIGdvb2Qgb2YgYWxsIGdvb2RzP3BlcmMgYmFkIG9mIGFsbCBiYWRzKT8qP1dPRQ0KIyANCiMgVGhlIOKAmFdPRVRhYmxl4oCZIGJlbG93IGdpdmVuIHRoZSBjb21wdXRhdGlvbiBpbiBtb3JlIGRldGFpbC4NCg0KV09FVGFibGUoWD1pbnB1dERhdGFbLCAnd29ya2NsYXNzJ10sIFk9aW5wdXREYXRhJGluY29tZSkNCmBgYA0KDQpUaGUgdG90YWwgSVYgb2YgYSB2YXJpYWJsZSBpcyB0aGUgc3VtIG9mIElW77+9cyBvZiBpdHMgY2F0ZWdvcmllcy4NCg0KDQoNCiMtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIyAgICAgICAgICAgICAgICAgICAgICAgICBUVVRPUklBTCBVU0lORyBPUFRJTUFMIEJJTk5JTkcgKCkNCiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojIyMgNy4wIFdPRSBGUk9NIEluZm9ybWF0aW9uIFBjYWthZ2UNCg0KIyMjIyA3LjEgV29lIENvbnRpbnVvdXMgVmFyaWFibGVzIGFuZCBGYWN0b3IgVmFyaWFibGVzDQoNCmBgYHtyfQ0KI2luc3RhbGwucGFja2FnZXMoIkluZm9ybWF0aW9uIikNCmxpYnJhcnkoSW5mb3JtYXRpb24pDQoNCiMgbXlkYXRhIDwtIHJlYWQuY3N2KCJodHRwczovL3N0YXRzLmlkcmUudWNsYS5lZHUvc3RhdC9kYXRhL2JpbmFyeS5jc3YiKQ0KIyANCiMgaGVhZChteWRhdGEpDQoNCiMgU3RvcmUgRGF0YSBMb2NhbGx5DQojDQojd3JpdGUuY3N2KG15ZGF0YSwiLi9EYXRhL215ZGF0YS5jc3YiKQ0KIw0KIyBSZWFkIHRoZSBkYXRhIGluIGxvY2FsbHkNCg0KDQpteWRhdGE9cmVhZC5jc3YoIi4vRGF0YS9teWRhdGEuY3N2IikNCg0Kc3VtbWFyeShteWRhdGEpDQoNCg0KDQpgYGANCg0KDQojIyMjIDcuMiBNYWtlIGFuICBpbmRlcGVuZGVudCBWYXJpYWJsZSBhIEZhY3Rvcg0KDQpJdCBpcyBpbXBvcnRhbnQgdG8gbm90ZSBoZXJlIHRoZSBudW1iZXIgb2YgYmlucyBmb3IgJ3JhbmsnIHZhcmlhYmxlLiBTaW5jZSBpdCBpcyBhIGNhdGVnb3JpY2FsIHZhcmlhYmxlLCB0aGUgbnVtYmVyIG9mIGJpbnMgd291bGQgYmUgYWNjb3JkaW5nIHRvIHVuaXF1ZSB2YWx1ZXMgb2YgdGhlIGZhY3RvciB2YXJpYWJsZS4gVGhlIHBhcmFtZXRlciBiaW5zPTEwIGRvZXMgbm90IHdvcmsgZm9yIGEgZmFjdG9yIHZhcmlhYmxlLg0KDQpgYGB7cn0NCg0KbXlkYXRhJHJhbms9ZmFjdG9yKG15ZGF0YSRyYW5rKQ0KDQpgYGANCg0KIyMjIyA3LjMgQ29tcHV0ZSBUaGUgSW5mbyBWYWx1ZQ0KDQpgYGB7ciBJViB9DQoNCiMgTXVzdCBQdXQgSW4gYW4gZXN0aW1hdGUgb2YgdGhlIEJpbnMgDQoNCklWIDwtIGNyZWF0ZV9pbmZvdGFibGVzKGRhdGE9bXlkYXRhLCB5PSJhZG1pdCIsIGJpbnM9MTAsIHBhcmFsbGVsPUZBTFNFKQ0KDQoNCklWIDwtIGNyZWF0ZV9pbmZvdGFibGVzKGRhdGE9bXlkYXRhLCB5PSJhZG1pdCIsIGJpbnM9MTAsIHBhcmFsbGVsPVRSVUUpDQoNCg0KIyBQcm9kdWNlIGFuIElWIFZhbHVlIEZyYW1lDQoNCklWX1ZhbHVlID0gZGF0YS5mcmFtZShJViRTdW1tYXJ5KQ0KDQpJVl9WYWx1ZQ0KDQoNCg0KYGBgDQoNCiMjIyMgNy40IFByaW50IHRoZSBJViBUYWJsZXMNCg0KVG8gZ2V0IFdPRSB0YWJsZSBmb3IgdmFyaWFibGUgZ3JlLCB5b3UgbmVlZCB0byBjYWxsIFRhYmxlcyBsaXN0IGZyb20gSVYgbGlzdC4NCg0KV2UgY2FuIGRvIHRoaXMgZm9yIGFueSBvZiB0aGUgdmFyaWFibGVzDQoNCmBgYHtyfQ0KcHJpbnQoSVYkVGFibGVzJGdyZSwgcm93Lm5hbWVzPUZBTFNFKQ0KcHJpbnQoSVYkVGFibGVzJGdwYSwgcm93Lm5hbWVzPUZBTFNFKQ0KcHJpbnQoSVYkVGFibGVzJHJhbmssIHJvdy5uYW1lcz1GQUxTRSkNCnByaW50KElWJFRhYmxlcyRYLCByb3cubmFtZXM9RkFMU0UpDQpgYGANCg0KIyMjIyA3LjUgUHV0IFRoZSBUYWJsZXMgaWYgRGF0YWZyYW1lcw0KDQpUbyBnZXQgV09FIHRhYmxlIGZvciB2YXJpYWJsZSBncmUsIHlvdSBuZWVkIHRvIGNhbGwgVGFibGVzIGxpc3QgZnJvbSBJViBsaXN0Lg0KDQpXZSBjYW4gZG8gdGhpcyBmb3IgYW55IG9mIHRoZSB2YXJpYWJsZXMNCg0KYGBge3IgRGF0YUZyYW1lc30NCmdyZSA9IGRhdGEuZnJhbWUoSVYkVGFibGVzJGdyZSkNCg0KZ3BhID0gZGF0YS5mcmFtZShJViRUYWJsZXMkZ3BhKQ0KDQpYID0gZGF0YS5mcmFtZShJViRUYWJsZXMkWCkNCg0KcmFuayA9IGRhdGEuZnJhbWUoSVYkVGFibGVzJHJhbmspDQoNCg0KYGBgDQoNCg0KIyMjIyA3LjYgUGxvdCBXb2UgU2NvcmVzIEZvciAxIFZhcmlhYmxlIA0KDQpXZSBjYW4gcGxvdCAxIGF0IGEgdGltZQ0KDQpgYGB7ciAgUGxvdCAxIH0NCg0KcGxvdF9pbmZvdGFibGVzKElWLCAiZ3JlIikNCg0KYGBgDQoNCg0KIyMjIyA3LjcgUGxvdCBXb2UgU2NvcmVzIEZvciBNYW55IFZhcmlhYmxlIA0KDQpXZSBjYW4gcGxvdCBtYW55IGF0IGEgdGltZQ0KDQpgYGB7ciBQbG90IE1hbnkgfQ0KDQpwbG90X2luZm90YWJsZXMoSVYsIElWJFN1bW1hcnkkVmFyaWFibGVbMTo0XSwgc2FtZV9zY2FsZT1GQUxTRSkNCg0KYGBgDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KIyMgOC4wICBXT0UgQmltbW5uaW5nDQojDQpUaGlzIHBhY2thZ2UgZ2VuZXJhdGVzLCB2aXN1YWxpemVzLCB0YWJ1bGF0ZXMgYW5kIGRlcGxveXMgYSBzdXBlcnZpc2VkIHdlaWdodCBvZiBldmlkZW5jZSAoV09FKSBiaW5uaW5nIG9mIHZhcmlhYmxlcy4NCg0KRGV0YWlscw0KDQpUaGlzIHBhY2thZ2UgZ2VuZXJhdGVzLCB2aXN1YWxpemVzLCB0YWJ1bGF0ZXMgYW5kIGRlcGxveXMgYSBzdXBlcnZpc2VkIHdlaWdodCBvZiBldmlkZW5jZSAoV09FKSBiaW5uaW5nIG9mIHZhcmlhYmxlcy4NCg0KVGhlIHBhY2thZ2Ugd29lQmlubmluZyBhdXRvbWF0ZXMgdGhlIHByb2Nlc3Mgb2YgYmlubmluZyBvZiBudW1lcmljIHZhcmlhYmxlcyBhbmQgZmFjdG9ycyB3aXRoIHJlc3BlY3QgdG8gYSBkaWNob3RvbW91cyB0YXJnZXQgdmFyaWFibGUuIEFkZGl0aW9uYWxseSwgaXQgdmlzdWFsaXplcyB0aGUgcmVhbGl6ZWQgYmlubmluZyBzb2x1dGlvbiwgdGFidWxhdGVzIGl0IGFuZCBkZXBsb3lzIGl0IHRvIChuZXcpIGRhdGEuIEFsbCBmdW5jdGlvbnMgY2FuIGJlIHVzZWQgd2l0aCBzaW5nbGUgdmFyaWFibGVzIG9yIGFuIGVudGlyZSBkYXRhIGZyYW1lLg0KDQoNCiMjIyA4LjEgQmlubmluZw0KDQp3b2UuYmlubmluZyBnZW5lcmF0ZXMgYSBzdXBlcnZpc2VkIGZpbmUgYW5kIGNvYXJzZSBjbGFzc2luZyBvZiBudW1lcmljIHZhcmlhYmxlcyBhbmQgZmFjdG9ycy4NCg0Kd29lLnRyZWUuYmlubmluZyBnZW5lcmF0ZXMgYSBzdXBlcnZpc2VkIHRyZWUtbGlrZSBzZWdtZW50YXRpb24gb2YgbnVtZXJpYyB2YXJpYWJsZXMgYW5kIGZhY3RvcnMuDQoNCndvZS5iaW5uaW5nLnBsb3QgdmlzdWFsaXplcyB0aGUgYmlubmluZyBzb2x1dGlvbiBnZW5lcmF0ZWQgYW5kIHNhdmVkIHZpYSB3b2UuYmlubmluZyBvciB3b2UudHJlZS5iaW5uaW5nLg0KDQp3b2UuYmlubmluZy50YWJsZSB0YWJ1bGF0ZXMgdGhlIGJpbm5pbmcgc29sdXRpb24gZ2VuZXJhdGVkIGFuZCBzYXZlZCB2aWEgd29lLmJpbm5pbmcgb3Igd29lLnRyZWUuYmlubmluZy4NCg0Kd29lLmJpbm5pbmcuZGVwbG95IGRlcGxveXMgdGhlIGJpbm5pbmcgc29sdXRpb24gZ2VuZXJhdGVkIGFuZCBzYXZlZCB2aWEgd29lLmJpbm5pbmcgb3Igd29lLnRyZWUuYmlubmluZyB0byAobmV3KSBkYXRhLg0KDQpSZWZlcmVuY2VzIA0KDQpTaWRkaXFpLCBOLiAyMDA2OiBDcmVkaXQgUmlzayBTY29yZWNhcmRzOiBEZXZlbG9waW5nIGFuZCBJbXBsZW1lbnRpbmcgSW50ZWxsaWdlbnQgQ3JlZGl0IFNjb3JpbmcuIEhvYm9rZW4sIE5ldyBKZXJzZXk6IEpvaG4gV2lsZXkgJiBTb25zLg0KDQpBbmRlcnNvbiwgUi4gMjAwNzogVGhlIENyZWRpdCBTY29yaW5nIFRvb2xraXQ6IFRoZW9yeSBhbmQgUHJhY3RpY2UgZm9yIFJldGFpbCBDcmVkaXQgUmlzayBNYW5hZ2VtZW50IGFuZCBEZWNpc2lvbiBBdXRvbWF0aW9uLiBPeGZvcmQgLyBOZXcgWW9yazogT3hmb3JkIFVuaXZlcnNpdHkgUHJlc3MuDQoNCg0KIyMjIDguMiBFeGFtcGxlcw0KDQpgYGB7cn0NCg0KIyBMb2FkIEdlcm1hbiBjcmVkaXQgZGF0YSBhbmQgY3JlYXRlIHN1YnNldA0KZGF0YShnZXJtYW5jcmVkaXQpDQpkZiA8LSBnZXJtYW5jcmVkaXRbLCBjKCdjcmVkaXRhYmlsaXR5JywgJ2NyZWRpdC5hbW91bnQnLCAnZHVyYXRpb24uaW4ubW9udGgnLA0KICAgICAgICAgICAgICAgICAgJ3NhdmluZ3MuYWNjb3VudC5hbmQuYm9uZHMnLCAncHVycG9zZScpXQ0KDQojIEJpbiBhbGwgdmFyaWFibGVzIG9mIHRoZSBkYXRhIGZyYW1lIChhcGFydCBmcm9tIHRoZSB0YXJnZXQgdmFyaWFibGUpDQojIHdpdGggZGVmYXVsdCBwYXJhbWV0ZXIgc2V0dGluZ3MNCmJpbm5pbmcgPC0gd29lLmJpbm5pbmcoZGYsICdjcmVkaXRhYmlsaXR5JywgZGYpDQoNCiMgUGxvdCB0aGUgYmlubmVkIHZhcmlhYmxlcw0Kd29lLmJpbm5pbmcucGxvdChiaW5uaW5nKQ0KDQojIFRhYnVsYXRlIHRoZSBiaW5uZWQgdmFyaWFibGVzDQp0YWJ1bGF0ZS5iaW5uaW5nIDwtIHdvZS5iaW5uaW5nLnRhYmxlKGJpbm5pbmcpDQp0YWJ1bGF0ZS5iaW5uaW5nDQoJCQkJCQkgIA0KIyBEZXBsb3kgdGhlIGJpbm5pbmcgc29sdXRpb24gdG8gdGhlIGRhdGEgZnJhbWUNCiMgKGkuZS4gYWRkIGJpbm5lZCB2YXJpYWJsZXMgYW5kIGNvcnJlc3BvbmRpbmcgV09FIHZhcmlhYmxlcykNCmRmLndpdGguYmlubmVkLnZhcnMuYWRkZWQgPC0gd29lLmJpbm5pbmcuZGVwbG95KGRmLCBiaW5uaW5nLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZGQud29lLm9yLmR1bS52YXI9J3dvZScpDQoNCg0KDQoNCmBgYA0KDQoNCg0KDQoNCg0KDQo=