Loading Libraries

library(ggplot2)
library(caTools)
library(ROCR)
library(rpart)
library(rpart.plot)
library(corrplot)
library(caret)
library(reshape2)
library(pROC)

Importing Dataset

#loading dataset
att_data=read.csv("D:/capstone/attrition_data.csv")

Head Data

#HEAD
head(att_data)
colnames(att_data)[1]="Age"

Checking for null values

#checking null values
data.frame(colSums(is.na(att_data)))

Hence it is clear that our data has no missing value and so we can move further for Descriptive Analysis of the data.

Exploratory Data Analysis

#Checking structure of the given attributes in data
str(att_data)
'data.frame':   1470 obs. of  35 variables:
 $ Age                     : int  41 49 37 33 27 32 59 30 38 36 ...
 $ Attrition               : Factor w/ 2 levels "No","Yes": 2 1 2 1 1 1 1 1 1 1 ...
 $ BusinessTravel          : Factor w/ 3 levels "Non-Travel","Travel_Frequently",..: 3 2 3 2 3 2 3 3 2 3 ...
 $ DailyRate               : int  1102 279 1373 1392 591 1005 1324 1358 216 1299 ...
 $ Department              : Factor w/ 3 levels "Human Resources",..: 3 2 2 2 2 2 2 2 2 2 ...
 $ DistanceFromHome        : int  1 8 2 3 2 2 3 24 23 27 ...
 $ Education               : int  2 1 2 4 1 2 3 1 3 3 ...
 $ EducationField          : Factor w/ 6 levels "Human Resources",..: 2 2 5 2 4 2 4 2 2 4 ...
 $ EmployeeCount           : int  1 1 1 1 1 1 1 1 1 1 ...
 $ EmployeeNumber          : int  1 2 4 5 7 8 10 11 12 13 ...
 $ EnvironmentSatisfaction : int  2 3 4 4 1 4 3 4 4 3 ...
 $ Gender                  : Factor w/ 2 levels "Female","Male": 1 2 2 1 2 2 1 2 2 2 ...
 $ HourlyRate              : int  94 61 92 56 40 79 81 67 44 94 ...
 $ JobInvolvement          : int  3 2 2 3 3 3 4 3 2 3 ...
 $ JobLevel                : int  2 2 1 1 1 1 1 1 3 2 ...
 $ JobRole                 : Factor w/ 9 levels "Healthcare Representative",..: 8 7 3 7 3 3 3 3 5 1 ...
 $ JobSatisfaction         : int  4 2 3 3 2 4 1 3 3 3 ...
 $ MaritalStatus           : Factor w/ 3 levels "Divorced","Married",..: 3 2 3 2 2 3 2 1 3 2 ...
 $ MonthlyIncome           : int  5993 5130 2090 2909 3468 3068 2670 2693 9526 5237 ...
 $ MonthlyRate             : int  19479 24907 2396 23159 16632 11864 9964 13335 8787 16577 ...
 $ NumCompaniesWorked      : int  8 1 6 1 9 0 4 1 0 6 ...
 $ Over18                  : Factor w/ 1 level "Y": 1 1 1 1 1 1 1 1 1 1 ...
 $ OverTime                : Factor w/ 2 levels "No","Yes": 2 1 2 2 1 1 2 1 1 1 ...
 $ PercentSalaryHike       : int  11 23 15 11 12 13 20 22 21 13 ...
 $ PerformanceRating       : int  3 4 3 3 3 3 4 4 4 3 ...
 $ RelationshipSatisfaction: int  1 4 2 3 4 3 1 2 2 2 ...
 $ StandardHours           : int  80 80 80 80 80 80 80 80 80 80 ...
 $ StockOptionLevel        : int  0 1 0 0 1 0 3 1 0 2 ...
 $ TotalWorkingYears       : int  8 10 7 8 6 8 12 1 10 17 ...
 $ TrainingTimesLastYear   : int  0 3 3 3 3 2 3 2 2 3 ...
 $ WorkLifeBalance         : int  1 3 3 3 3 2 2 3 3 2 ...
 $ YearsAtCompany          : int  6 10 0 8 2 7 1 1 9 7 ...
 $ YearsInCurrentRole      : int  4 7 0 7 2 7 0 0 7 7 ...
 $ YearsSinceLastPromotion : int  0 1 0 3 2 3 0 0 1 7 ...
 $ YearsWithCurrManager    : int  5 7 0 0 2 6 0 0 8 7 ...

We can see that our data has 1470 observations and 35 variables and also the class of each variable.

Some of the variables (like Education,JobInvolvement, EnvironmentSatisfaction…)are ordinal but fall under integer class in our data so we need to change those as a factor.

ord_col=c("Education","EnvironmentSatisfaction","JobInvolvement","JobLevel",
          "JobSatisfaction","PerformanceRating","RelationshipSatisfaction",
          "StandardHours","StockOptionLevel","WorkLifeBalance")
att_data[,ord_col]=lapply(att_data[,ord_col],as.factor)
str(att_data)
'data.frame':   1470 obs. of  35 variables:
 $ Age                     : int  41 49 37 33 27 32 59 30 38 36 ...
 $ Attrition               : Factor w/ 2 levels "No","Yes": 2 1 2 1 1 1 1 1 1 1 ...
 $ BusinessTravel          : Factor w/ 3 levels "Non-Travel","Travel_Frequently",..: 3 2 3 2 3 2 3 3 2 3 ...
 $ DailyRate               : int  1102 279 1373 1392 591 1005 1324 1358 216 1299 ...
 $ Department              : Factor w/ 3 levels "Human Resources",..: 3 2 2 2 2 2 2 2 2 2 ...
 $ DistanceFromHome        : int  1 8 2 3 2 2 3 24 23 27 ...
 $ Education               : Factor w/ 5 levels "1","2","3","4",..: 2 1 2 4 1 2 3 1 3 3 ...
 $ EducationField          : Factor w/ 6 levels "Human Resources",..: 2 2 5 2 4 2 4 2 2 4 ...
 $ EmployeeCount           : int  1 1 1 1 1 1 1 1 1 1 ...
 $ EmployeeNumber          : int  1 2 4 5 7 8 10 11 12 13 ...
 $ EnvironmentSatisfaction : Factor w/ 4 levels "1","2","3","4": 2 3 4 4 1 4 3 4 4 3 ...
 $ Gender                  : Factor w/ 2 levels "Female","Male": 1 2 2 1 2 2 1 2 2 2 ...
 $ HourlyRate              : int  94 61 92 56 40 79 81 67 44 94 ...
 $ JobInvolvement          : Factor w/ 4 levels "1","2","3","4": 3 2 2 3 3 3 4 3 2 3 ...
 $ JobLevel                : Factor w/ 5 levels "1","2","3","4",..: 2 2 1 1 1 1 1 1 3 2 ...
 $ JobRole                 : Factor w/ 9 levels "Healthcare Representative",..: 8 7 3 7 3 3 3 3 5 1 ...
 $ JobSatisfaction         : Factor w/ 4 levels "1","2","3","4": 4 2 3 3 2 4 1 3 3 3 ...
 $ MaritalStatus           : Factor w/ 3 levels "Divorced","Married",..: 3 2 3 2 2 3 2 1 3 2 ...
 $ MonthlyIncome           : int  5993 5130 2090 2909 3468 3068 2670 2693 9526 5237 ...
 $ MonthlyRate             : int  19479 24907 2396 23159 16632 11864 9964 13335 8787 16577 ...
 $ NumCompaniesWorked      : int  8 1 6 1 9 0 4 1 0 6 ...
 $ Over18                  : Factor w/ 1 level "Y": 1 1 1 1 1 1 1 1 1 1 ...
 $ OverTime                : Factor w/ 2 levels "No","Yes": 2 1 2 2 1 1 2 1 1 1 ...
 $ PercentSalaryHike       : int  11 23 15 11 12 13 20 22 21 13 ...
 $ PerformanceRating       : Factor w/ 2 levels "3","4": 1 2 1 1 1 1 2 2 2 1 ...
 $ RelationshipSatisfaction: Factor w/ 4 levels "1","2","3","4": 1 4 2 3 4 3 1 2 2 2 ...
 $ StandardHours           : Factor w/ 1 level "80": 1 1 1 1 1 1 1 1 1 1 ...
 $ StockOptionLevel        : Factor w/ 4 levels "0","1","2","3": 1 2 1 1 2 1 4 2 1 3 ...
 $ TotalWorkingYears       : int  8 10 7 8 6 8 12 1 10 17 ...
 $ TrainingTimesLastYear   : int  0 3 3 3 3 2 3 2 2 3 ...
 $ WorkLifeBalance         : Factor w/ 4 levels "1","2","3","4": 1 3 3 3 3 2 2 3 3 2 ...
 $ YearsAtCompany          : int  6 10 0 8 2 7 1 1 9 7 ...
 $ YearsInCurrentRole      : int  4 7 0 7 2 7 0 0 7 7 ...
 $ YearsSinceLastPromotion : int  0 1 0 3 2 3 0 0 1 7 ...
 $ YearsWithCurrManager    : int  5 7 0 0 2 6 0 0 8 7 ...

Now we have the data with ordinal columns as factor and other variables in integer format.

Data Description

#summarising data
summary(att_data)
      Age        Attrition            BusinessTravel   DailyRate     
 Min.   :18.00   No :1233   Non-Travel       : 150   Min.   : 102.0  
 1st Qu.:30.00   Yes: 237   Travel_Frequently: 277   1st Qu.: 465.0  
 Median :36.00              Travel_Rarely    :1043   Median : 802.0  
 Mean   :36.92                                       Mean   : 802.5  
 3rd Qu.:43.00                                       3rd Qu.:1157.0  
 Max.   :60.00                                       Max.   :1499.0  
                                                                     
                  Department  DistanceFromHome Education
 Human Resources       : 63   Min.   : 1.000   1:170    
 Research & Development:961   1st Qu.: 2.000   2:282    
 Sales                 :446   Median : 7.000   3:572    
                              Mean   : 9.193   4:398    
                              3rd Qu.:14.000   5: 48    
                              Max.   :29.000            
                                                        
          EducationField EmployeeCount EmployeeNumber  
 Human Resources : 27    Min.   :1     Min.   :   1.0  
 Life Sciences   :606    1st Qu.:1     1st Qu.: 491.2  
 Marketing       :159    Median :1     Median :1020.5  
 Medical         :464    Mean   :1     Mean   :1024.9  
 Other           : 82    3rd Qu.:1     3rd Qu.:1555.8  
 Technical Degree:132    Max.   :1     Max.   :2068.0  
                                                       
 EnvironmentSatisfaction    Gender      HourlyRate     JobInvolvement
 1:284                   Female:588   Min.   : 30.00   1: 83         
 2:287                   Male  :882   1st Qu.: 48.00   2:375         
 3:453                                Median : 66.00   3:868         
 4:446                                Mean   : 65.89   4:144         
                                      3rd Qu.: 83.75                 
                                      Max.   :100.00                 
                                                                     
 JobLevel                      JobRole    JobSatisfaction  MaritalStatus
 1:543    Sales Executive          :326   1:289           Divorced:327  
 2:534    Research Scientist       :292   2:280           Married :673  
 3:218    Laboratory Technician    :259   3:442           Single  :470  
 4:106    Manufacturing Director   :145   4:459                         
 5: 69    Healthcare Representative:131                                 
          Manager                  :102                                 
          (Other)                  :215                                 
 MonthlyIncome    MonthlyRate    NumCompaniesWorked Over18   OverTime  
 Min.   : 1009   Min.   : 2094   Min.   :0.000      Y:1470   No :1054  
 1st Qu.: 2911   1st Qu.: 8047   1st Qu.:1.000               Yes: 416  
 Median : 4919   Median :14236   Median :2.000                         
 Mean   : 6503   Mean   :14313   Mean   :2.693                         
 3rd Qu.: 8379   3rd Qu.:20462   3rd Qu.:4.000                         
 Max.   :19999   Max.   :26999   Max.   :9.000                         
                                                                       
 PercentSalaryHike PerformanceRating RelationshipSatisfaction
 Min.   :11.00     3:1244            1:276                   
 1st Qu.:12.00     4: 226            2:303                   
 Median :14.00                       3:459                   
 Mean   :15.21                       4:432                   
 3rd Qu.:18.00                                               
 Max.   :25.00                                               
                                                             
 StandardHours StockOptionLevel TotalWorkingYears TrainingTimesLastYear
 80:1470       0:631            Min.   : 0.00     Min.   :0.000        
               1:596            1st Qu.: 6.00     1st Qu.:2.000        
               2:158            Median :10.00     Median :3.000        
               3: 85            Mean   :11.28     Mean   :2.799        
                                3rd Qu.:15.00     3rd Qu.:3.000        
                                Max.   :40.00     Max.   :6.000        
                                                                       
 WorkLifeBalance YearsAtCompany   YearsInCurrentRole
 1: 80           Min.   : 0.000   Min.   : 0.000    
 2:344           1st Qu.: 3.000   1st Qu.: 2.000    
 3:893           Median : 5.000   Median : 3.000    
 4:153           Mean   : 7.008   Mean   : 4.229    
                 3rd Qu.: 9.000   3rd Qu.: 7.000    
                 Max.   :40.000   Max.   :18.000    
                                                    
 YearsSinceLastPromotion YearsWithCurrManager
 Min.   : 0.000          Min.   : 0.000      
 1st Qu.: 0.000          1st Qu.: 2.000      
 Median : 1.000          Median : 3.000      
 Mean   : 2.188          Mean   : 4.123      
 3rd Qu.: 3.000          3rd Qu.: 7.000      
 Max.   :15.000          Max.   :17.000      
                                             

Above information describes the distribution of the data (central tendencies of integer type data variables and frequency distribution of category type columns).

Data Visualization

#Distribution of the data
int_vars=colnames(att_data[which(sapply(att_data,class)=="integer")])
melt_attrition_dat = melt(att_data[,c("Attrition", int_vars)], id.var = "Attrition")
head(melt_attrition_dat)
NA

p <- ggplot(data = melt_attrition_dat , aes(x=variable, y=value,fill=Attrition)) + geom_boxplot()+scale_fill_viridis_d()
p <- p + facet_wrap( ~ variable, scales="free")
p

Here we can see the distribution of all the variable of integer class.

cat_cols=colnames(att_data[which(sapply(att_data,class)=="factor")])
freq_tbl=apply(att_data[,cat_cols],2, function(x) table(att_data$Attrition,x))
freq_tbl= lapply(freq_tbl,function(x) as.data.frame.matrix(x))
freq_tbl
$Attrition

$BusinessTravel

$Department

$Education

$EducationField

$EnvironmentSatisfaction

$Gender

$JobInvolvement

$JobLevel

$JobRole

$JobSatisfaction

$MaritalStatus

$Over18

$OverTime

$PerformanceRating

$RelationshipSatisfaction

$StandardHours

$StockOptionLevel

$WorkLifeBalance
a=names(freq_tbl)[2]
freq_tbl[a][[1]]
i =0
for(name in names(freq_tbl)[-1]){
    i <- i +1
    var_data <- data.frame(apply(freq_tbl[name][[1]],2, function(x) x[2]/sum(x)))
    colnames(var_data) <- name
   my_plot <- ggplot(data=var_data, aes(x=row.names(var_data), y=var_data[,name])) +  geom_bar(stat="identity",fill='red') +
        ylim(0.0,1.0) + ylab("%attrition") + xlab(name) + theme(axis.text.x = element_text(angle = 90, hjust = 1))
    plot(my_plot)
    remove(my_plot)
}

From the above graphs we can understand the attrition rate of employees with respect to different attribute values.

library(gridExtra)
g1=ggplot(data = att_data,aes(MonthlyIncome,EmployeeNumber,size=Age,col=Attrition))+geom_point(alpha=0.7)+ggtitle("Attrition vs Monthlyincome (1)")
g2=ggplot(data = att_data[which(att_data$Attrition=='Yes'),],aes(MonthlyIncome,EmployeeNumber,size=Age))+geom_point(col='skyblue',alpha=0.7)+ggtitle("Attrition vs Monthlyincome (2)")
grid.arrange(g1,g2)

from the first plot shows the attrition according to Monthlyincome and Age of employee.

From the second graph we can conclude that employees with Monthlyincome less than 15000 and Age between 20-40 have more chances of leaving the company.

Correlation

#plotting corrplot
corrplot(cor(att_data[setdiff(int_vars,"EmployeeCount")]))

Above graph shows correlation between the two variables and we can clearly see that some of the vairables are having a good postive correlation (like Age and totalworkingyears).

Feature Reduction

Entropy and variance based
#1)Feature Reduction using entropy 
library(entropy)
library(dplyr)
#1.1)Calculating entropy of the categorical variable 
entropy_cat = unlist(lapply(att_data[,cat_cols],function(x) entropy(table(x))))/unlist(lapply(att_data[,cat_cols],function(x) log2(length(x))))
#1.2)Picking variable with 0 entropy
zero_entropy_variable= names(entropy_cat[entropy_cat==0])
#2)Feature Reduction using variance 
#2.1)Normalising the data
norm_att_data=as.data.frame.matrix(apply(att_data[,int_vars],2,function(x) (x-min(x))/(max(x)-min(x)) ))
#2.2)Calculating variance
norm_att_data=apply(norm_att_data,2,var)
#2.3 Picking low variance variable
low_var=names(norm_att_data[is.na(norm_att_data)==TRUE])
#Removing variable with 0 entropy and low variance
att_data1=select(att_data,-c(zero_entropy_variable,low_var,"EmployeeNumber"))
#dimension 
dim(att_data1)
[1] 1470   31

Here we can see that 4 features are removed from the data on the basis of 0 entropy,null variance and EmployeeNumber is also removed as its just the id nominal type .

Splitting the Data

Splitting the data randomly into 75:25 ratio for training and testing the model.

#Spliting the data into 75:25 proportion
library(caTools)
set.seed(11)
splt=sample.split(att_data1$Attrition,SplitRatio = 0.75)
train_dat=subset(att_data1,splt==TRUE)
test_dat=subset(att_data1,splt==FALSE)
print(paste("Train data has ",nrow(train_dat),"observations"))
[1] "Train data has  1103 observations"
print(paste("Test data has ",nrow(test_dat),"observations"))
[1] "Test data has  367 observations"

Model Building

Logistics Regression
#Building logistics model
set.seed(111)
model_dat=glm(Attrition~.,data = train_dat,family = "binomial")
#Summary of model
summary(model_dat)

Call:
glm(formula = Attrition ~ ., family = "binomial", data = train_dat)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-1.91844  -0.41889  -0.18242  -0.04541   3.08249  

Coefficients:
                                   Estimate Std. Error z value Pr(>|z|)
(Intercept)                      -1.159e+01  7.064e+02  -0.016 0.986915
Age                              -4.859e-02  1.720e-02  -2.824 0.004737
BusinessTravelTravel_Frequently   2.618e+00  6.033e-01   4.340 1.43e-05
BusinessTravelTravel_Rarely       1.463e+00  5.633e-01   2.596 0.009418
DailyRate                        -5.154e-04  2.852e-04  -1.808 0.070666
DepartmentResearch & Development  1.501e+01  7.064e+02   0.021 0.983047
DepartmentSales                   1.344e+01  7.064e+02   0.019 0.984827
DistanceFromHome                  5.329e-02  1.396e-02   3.817 0.000135
Education2                        5.362e-03  4.240e-01   0.013 0.989909
Education3                        1.359e-01  3.685e-01   0.369 0.712178
Education4                        2.821e-01  3.988e-01   0.708 0.479230
Education5                        1.245e-01  7.755e-01   0.161 0.872434
EducationFieldLife Sciences      -6.579e-01  1.157e+00  -0.569 0.569447
EducationFieldMarketing          -3.030e-01  1.203e+00  -0.252 0.801096
EducationFieldMedical            -8.482e-01  1.153e+00  -0.736 0.461935
EducationFieldOther              -3.357e-01  1.236e+00  -0.271 0.786010
EducationFieldTechnical Degree    6.766e-01  1.174e+00   0.576 0.564325
EnvironmentSatisfaction2         -7.970e-01  3.417e-01  -2.333 0.019667
EnvironmentSatisfaction3         -1.302e+00  3.294e-01  -3.952 7.76e-05
EnvironmentSatisfaction4         -1.451e+00  3.290e-01  -4.409 1.04e-05
GenderMale                        4.375e-01  2.353e-01   1.859 0.063050
HourlyRate                        1.887e-04  5.786e-03   0.033 0.973992
JobInvolvement2                  -1.347e+00  4.583e-01  -2.940 0.003280
JobInvolvement3                  -1.496e+00  4.341e-01  -3.446 0.000569
JobInvolvement4                  -2.174e+00  6.039e-01  -3.599 0.000319
JobLevel2                        -1.607e+00  5.277e-01  -3.046 0.002315
JobLevel3                        -2.992e-01  8.404e-01  -0.356 0.721842
JobLevel4                        -1.963e+00  1.521e+00  -1.290 0.196919
JobLevel5                         1.029e+00  1.891e+00   0.544 0.586243
JobRoleHuman Resources            1.625e+01  7.064e+02   0.023 0.981648
JobRoleLaboratory Technician      1.584e+00  7.824e-01   2.024 0.042953
JobRoleManager                    6.024e-01  1.281e+00   0.470 0.638218
JobRoleManufacturing Director     1.476e+00  7.665e-01   1.925 0.054182
JobRoleResearch Director         -1.731e+00  1.326e+00  -1.305 0.191959
JobRoleResearch Scientist         6.122e-01  8.014e-01   0.764 0.444886
JobRoleSales Executive            3.765e+00  1.682e+00   2.239 0.025170
JobRoleSales Representative       3.571e+00  1.761e+00   2.028 0.042558
JobSatisfaction2                 -1.078e+00  3.498e-01  -3.080 0.002067
JobSatisfaction3                 -8.495e-01  3.100e-01  -2.741 0.006132
JobSatisfaction4                 -1.569e+00  3.283e-01  -4.780 1.75e-06
MaritalStatusMarried              9.250e-02  3.504e-01   0.264 0.791772
MaritalStatusSingle               3.223e-01  4.899e-01   0.658 0.510655
MonthlyIncome                    -6.515e-05  1.107e-04  -0.588 0.556204
MonthlyRate                       1.350e-05  1.576e-05   0.856 0.391862
NumCompaniesWorked                2.090e-01  4.835e-02   4.323 1.54e-05
OverTimeYes                       2.319e+00  2.580e-01   8.990  < 2e-16
PercentSalaryHike                -4.636e-02  4.898e-02  -0.946 0.343904
PerformanceRating4                2.202e-01  5.075e-01   0.434 0.664319
RelationshipSatisfaction2        -4.632e-01  3.573e-01  -1.296 0.194854
RelationshipSatisfaction3        -8.227e-01  3.147e-01  -2.614 0.008939
RelationshipSatisfaction4        -8.541e-01  3.195e-01  -2.674 0.007504
StockOptionLevel1                -1.417e+00  3.881e-01  -3.650 0.000263
StockOptionLevel2                -1.433e+00  5.665e-01  -2.530 0.011416
StockOptionLevel3                -6.412e-01  5.986e-01  -1.071 0.284056
TotalWorkingYears                -2.329e-02  3.626e-02  -0.642 0.520676
TrainingTimesLastYear            -1.681e-01  8.844e-02  -1.901 0.057295
WorkLifeBalance2                 -9.458e-01  4.704e-01  -2.011 0.044373
WorkLifeBalance3                 -1.495e+00  4.416e-01  -3.385 0.000713
WorkLifeBalance4                 -1.121e+00  5.287e-01  -2.121 0.033938
YearsAtCompany                    1.595e-01  5.074e-02   3.143 0.001671
YearsInCurrentRole               -2.031e-01  6.302e-02  -3.224 0.001265
YearsSinceLastPromotion           1.613e-01  5.549e-02   2.907 0.003654
YearsWithCurrManager             -2.340e-01  6.167e-02  -3.794 0.000148
                                    
(Intercept)                         
Age                              ** 
BusinessTravelTravel_Frequently  ***
BusinessTravelTravel_Rarely      ** 
DailyRate                        .  
DepartmentResearch & Development    
DepartmentSales                     
DistanceFromHome                 ***
Education2                          
Education3                          
Education4                          
Education5                          
EducationFieldLife Sciences         
EducationFieldMarketing             
EducationFieldMedical               
EducationFieldOther                 
EducationFieldTechnical Degree      
EnvironmentSatisfaction2         *  
EnvironmentSatisfaction3         ***
EnvironmentSatisfaction4         ***
GenderMale                       .  
HourlyRate                          
JobInvolvement2                  ** 
JobInvolvement3                  ***
JobInvolvement4                  ***
JobLevel2                        ** 
JobLevel3                           
JobLevel4                           
JobLevel5                           
JobRoleHuman Resources              
JobRoleLaboratory Technician     *  
JobRoleManager                      
JobRoleManufacturing Director    .  
JobRoleResearch Director            
JobRoleResearch Scientist           
JobRoleSales Executive           *  
JobRoleSales Representative      *  
JobSatisfaction2                 ** 
JobSatisfaction3                 ** 
JobSatisfaction4                 ***
MaritalStatusMarried                
MaritalStatusSingle                 
MonthlyIncome                       
MonthlyRate                         
NumCompaniesWorked               ***
OverTimeYes                      ***
PercentSalaryHike                   
PerformanceRating4                  
RelationshipSatisfaction2           
RelationshipSatisfaction3        ** 
RelationshipSatisfaction4        ** 
StockOptionLevel1                ***
StockOptionLevel2                *  
StockOptionLevel3                   
TotalWorkingYears                   
TrainingTimesLastYear            .  
WorkLifeBalance2                 *  
WorkLifeBalance3                 ***
WorkLifeBalance4                 *  
YearsAtCompany                   ** 
YearsInCurrentRole               ** 
YearsSinceLastPromotion          ** 
YearsWithCurrManager             ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 974.94  on 1102  degrees of freedom
Residual deviance: 564.13  on 1040  degrees of freedom
AIC: 690.13

Number of Fisher Scoring iterations: 15

From the above summary we can see the residual deviance and AIC of the model and the important variables having p-value < 0.05.

Mkaing Prediction Now initially, we will make the prediction using model inclusive of all the variables and check the performance of our model.

#Making prediciton
predict_dat=predict(model_dat,newdata=test_dat,type="response")

Now the outcome of logistics regression model is in probablity.In order to make actual predictions we have to make a confusion matrix by setting threshold value “t” which filters the probablity above threshold value as “Yes” an below as “No”.

Initially we are settign threshold as 0.5 as its the general value and also mid value which keep senstivity and specificity in balance as increasing threshold will lead to increase in specificity but decrease senstivity which means more error in prediction employees who will churn.Similarily lower threshold value decrease specificity and increase senstivity which means more error in predicting employees who will not churn.

#Making confusion matrix with threshold 0.5
confusion_dat=table(test_dat$Attrition,predict_dat>0.5)
print("Confusion matrix for threshold 0.5")
[1] "Confusion matrix for threshold 0.5"
confusion_dat
     
      FALSE TRUE
  No    291   17
  Yes    24   35
#Calcluating senstivity/tpr
tp=confusion_dat[4]
total_p=tp+confusion_dat[2]
senstivity_dat=tp/total_p
print(paste("senstivity:",senstivity_dat))
[1] "senstivity: 0.593220338983051"
#Calculating specificity/fpr
tn=confusion_dat[1]
total_n=tn+confusion_dat[3]
specificity_dat=tn/total_n
print(paste("specificity:",specificity_dat))
[1] "specificity: 0.944805194805195"

Now here we can see that specificity is way too high as compared to senstivity so in order to resolve this problem this we can find an optimal value of threshold.

ROCR Curve

In order to find the optimal value of threshold we will plot a ROCR curve (true postive rate vs false postive rate) which will indicate the value with minimum error.

#Reciever Operator Characteristics Curve
rocr_curve=function(predi){
#Prediction
rocr_pred=prediction(predi,test_dat$Attrition)
#Performance
rocr_perf=performance(rocr_pred,"tpr","fpr")
#plotting ROCR  
plot(rocr_perf,colorize=TRUE,print.cutoffs.at=seq(0,1,0.1),text.adj=c(-0.2,1.7))
}
rocr_curve(predict_dat)

From the ROCR plot we can understand that 0.25 is the optimum value of threshold.

So now we will make the actual prediction using threshold=0.3 and check its accuracy ,senstivity,specificity and kappa value.

#performance model function

#confusion matrix    
confus_dat1=table(test_dat$Attrition,predict_dat>0.25)
print("Confusion matrix with optimum threshold")
[1] "Confusion matrix with optimum threshold"
print(confus_dat1)
     
      FALSE TRUE
  No    263   45
  Yes    15   44
model_perform=function(confu){
print('Model Performance:')
#caluclating senstivity
tp1=confu[4]
totl_p=tp1+confu[2]
sens=tp/totl_p
print(paste("senstivity:",sens))
#calculating specificity
tn1=confu[1]
totl_n=tn1+confu[3]
spec=tn1/totl_n
print(paste("specificity:",spec))
#calculating accuracy
acc_dat1=sum(diag(confu))/sum(confu)
print(paste("Accuracy:",round(acc_dat1*100),"%"))
}
model_perform(confus_dat1)
[1] "Model Performance:"
[1] "senstivity: 0.593220338983051"
[1] "specificity: 0.853896103896104"
[1] "Accuracy: 84 %"

Here we got the model with good accuracy and as we are more concerned about getting more true predictions of employees who will churn, it is optimum threshold for our concern.

Now we will try to reduce the complexity of the model by selecting important features for the model based on p-value. In summary of the model all variables which have p-value less than 0.05 are considered important.

summ_dat=as.data.frame.matrix(summary(model_dat)$coef)
sign_var=summ_dat[summ_dat$`Pr(>|z|)`<0.05,]
sign_var

Above shown are the significant variables and now we will build a model using these variables only and check the tradeoff between complexity and accuracy.

Model Building with significant variables

#Selecting significant variable names 
name_signi=c("Age","BusinessTravel","DistanceFromHome","EnvironmentSatisfaction","Gender","JobInvolvement","JobLevel",
                             "JobSatisfaction","NumCompaniesWorked","OverTime","RelationshipSatisfaction",
                             "StockOptionLevel","WorkLifeBalance","YearsSinceLastPromotion", "YearsWithCurrManager")
#Building model
model_signi=glm(Attrition~.,data = train_dat[,c("Attrition",name_signi)],family = "binomial")
summary(model_signi)

Call:
glm(formula = Attrition ~ ., family = "binomial", data = train_dat[, 
    c("Attrition", name_signi)])

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.1400  -0.4827  -0.2572  -0.1034   3.8460  

Coefficients:
                                Estimate Std. Error z value Pr(>|z|)    
(Intercept)                      2.60837    0.89237   2.923 0.003467 ** 
Age                             -0.04958    0.01375  -3.606 0.000311 ***
BusinessTravelTravel_Frequently  2.23263    0.54159   4.122 3.75e-05 ***
BusinessTravelTravel_Rarely      1.29233    0.51517   2.509 0.012122 *  
DistanceFromHome                 0.04507    0.01269   3.552 0.000382 ***
EnvironmentSatisfaction2        -0.63252    0.30604  -2.067 0.038755 *  
EnvironmentSatisfaction3        -0.94279    0.28913  -3.261 0.001111 ** 
EnvironmentSatisfaction4        -1.11141    0.29305  -3.793 0.000149 ***
GenderMale                       0.44480    0.21216   2.097 0.036037 *  
JobInvolvement2                 -1.23489    0.41718  -2.960 0.003076 ** 
JobInvolvement3                 -1.43074    0.39265  -3.644 0.000269 ***
JobInvolvement4                 -2.15916    0.56174  -3.844 0.000121 ***
JobLevel2                       -1.23814    0.25765  -4.805 1.54e-06 ***
JobLevel3                       -0.78077    0.34101  -2.290 0.022044 *  
JobLevel4                       -2.03736    0.70595  -2.886 0.003902 ** 
JobLevel5                       -0.99226    0.67429  -1.472 0.141142    
JobSatisfaction2                -0.76296    0.31963  -2.387 0.016987 *  
JobSatisfaction3                -0.59131    0.27465  -2.153 0.031319 *  
JobSatisfaction4                -1.31119    0.29711  -4.413 1.02e-05 ***
NumCompaniesWorked               0.16109    0.04138   3.892 9.92e-05 ***
OverTimeYes                      1.96367    0.22484   8.734  < 2e-16 ***
RelationshipSatisfaction2       -0.46350    0.31659  -1.464 0.143183    
RelationshipSatisfaction3       -0.71781    0.28640  -2.506 0.012198 *  
RelationshipSatisfaction4       -0.77716    0.29414  -2.642 0.008239 ** 
StockOptionLevel1               -1.32750    0.23591  -5.627 1.83e-08 ***
StockOptionLevel2               -1.53873    0.44518  -3.456 0.000547 ***
StockOptionLevel3               -0.71447    0.47612  -1.501 0.133455    
WorkLifeBalance2                -0.94262    0.40221  -2.344 0.019100 *  
WorkLifeBalance3                -1.26483    0.37405  -3.381 0.000721 ***
WorkLifeBalance4                -1.04510    0.46404  -2.252 0.024312 *  
YearsSinceLastPromotion          0.16447    0.04373   3.761 0.000169 ***
YearsWithCurrManager            -0.17747    0.04329  -4.100 4.13e-05 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 974.94  on 1102  degrees of freedom
Residual deviance: 649.40  on 1071  degrees of freedom
AIC: 713.4

Number of Fisher Scoring iterations: 6

Now we can make prediction using this model and with optimum threshold value we will check the model performance.

#Making predictions for model with significance variables
pred_dat1=predict(model_signi,test_dat[,c("Attrition",name_signi)],type = "response")
#Plotting rocr cure for optimum threshold
rocr_curve(pred_dat1)

From te above plot we can observe that the optimum threshold value lies between 0.2-0.3 .So now we will evaluate model performance using this threshold value.

Model Performance:

Evaluating model performance with significant variables only.

#making confusion matrix
print("Confusion Matrix of model with significant variables")
[1] "Confusion Matrix of model with significant variables"
confus_dat2=table(test_dat$Attrition,pred_dat1>0.28)
confus_dat2
     
      FALSE TRUE
  No    265   43
  Yes    19   40
model_perform(confu = confus_dat2)
[1] "Model Performance:"
[1] "senstivity: 0.593220338983051"
[1] "specificity: 0.86038961038961"
[1] "Accuracy: 83 %"

Classification and Regression Trees (CART):

#Making decision tres model
set.seed(1111)
dt_model=rpart(Attrition~.,method = "class",data = train_dat)

Now we can predict the test data using this model and evaluates its performance as we did in logistics regression.

#Making predictions
pred_dat2=as.data.frame.matrix(predict(dt_model,test_dat,type = "prob"))
pred_dat2=pred_dat2$Yes
#Plotting rocr curve 
rocr_curve(predi = pred_dat2)

From the above we can observe that optimum threshold value between 0.2-0.3 .So now we will evaluate the model performance using this threshold value.

Decision Tree Model Performance:

#Making confusion matrix
confu_dat3=table(test_dat$Attrition,pred_dat2>0.2)
print("Confusion Matrix for DT model")
[1] "Confusion Matrix for DT model"
confu_dat3
     
      FALSE TRUE
  No    291   17
  Yes    33   26
model_perform(confu_dat3)
[1] "Model Performance:"
[1] "senstivity: 0.593220338983051"
[1] "specificity: 0.944805194805195"
[1] "Accuracy: 86 %"

As we can see here the accuracy has been improved a little bit but also the specificity has increased remarkably which means less chances of error in predicting the employees who will not churn and more chances of error in predicting employees who will churn.

Now like we picked the significant variables in logistics regression we will do the same with DT model and evaluate the model performance.

Decision Tree Model Building:

Model Building including only important variables.

#Picking important variables
imp_varr=names(sort(dt_model$variable.importance,decreasing = TRUE))
#Model Building
dt_model2=rpart(Attrition~.,method = "class",data = train_dat[,c("Attrition",imp_varr)])

Decision Tree Model Performance: Evualating performance of decision tree model with important variables only.

Prediciton
#Making prediction
pred_dat3=as.data.frame.matrix(predict(dt_model2,test_dat[,c("Attrition",imp_varr)],type = "prob"))
pred_dat3=pred_dat3$Yes
#rocr curve
rocr_curve(pred_dat3)

Here we can observe that optimum value of threshold 0.3. Now we will evaluate the model performance using optimum threshold.

#making cinfusion matrix 
confu_dat4=table(test_dat$Attrition,pred_dat3>0.2)
print("Confusion matrix for important variables")
[1] "Confusion matrix for important variables"
confu_dat4
     
      FALSE TRUE
  No    291   17
  Yes    33   26
model_perform(confu_dat4)
[1] "Model Performance:"
[1] "senstivity: 0.593220338983051"
[1] "specificity: 0.944805194805195"
[1] "Accuracy: 86 %"
prp(dt_model2,roundint = FALSE)

Above shown is the decision tree diagram for important variables.

Feature Engineering:

Now we will try to introduce new features and build a model to evaluate its performance.

#Copying test train data
train_new_dat=train_dat
test_new_dat=test_dat
#Making new feature for first company
firstcompany=att_data$NumCompaniesWorked==1
#Making another feature for loyality
loyality=att_data$YearsAtCompany/att_data$TotalWorkingYears
loyality[is.na(loyality)]=0
# Making  volatility as a new feature 
volatility = att_data$TotalWorkingYears/att_data$NumCompaniesWorked
volatility[is.infinite(volatility)]=att_data$TotalWorkingYears[is.infinite(volatility)]
#Adding new features in train test data
train_new_dat$firstcompany=firstcompany[splt==TRUE]
train_new_dat$loyality=loyality[splt==TRUE]
train_new_dat$volatility=volatility[splt==TRUE]
test_new_dat$firstcompany=firstcompany[splt==FALSE]
test_new_dat$loyality=loyality[splt==FALSE]
test_new_dat$volatility=volatility[splt==FALSE]
#Picking new features
new_feat=c("firstcompany","loyality","volatility")

Now as we have created some new features we can build a new model and check its performance

Model Building:

Model building with new feature and the significant features

#Model Building
model_new_feat=glm(Attrition~.,data = train_new_dat[,c("Attrition",name_signi,new_feat)],family = "binomial")
summary(model_new_feat)

Call:
glm(formula = Attrition ~ ., family = "binomial", data = train_new_dat[, 
    c("Attrition", name_signi, new_feat)])

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.1095  -0.4768  -0.2588  -0.1002   3.9119  

Coefficients:
                                 Estimate Std. Error z value Pr(>|z|)
(Intercept)                      2.211467   0.975990   2.266 0.023459
Age                             -0.044252   0.014639  -3.023 0.002504
BusinessTravelTravel_Frequently  2.271981   0.544798   4.170 3.04e-05
BusinessTravelTravel_Rarely      1.320968   0.518432   2.548 0.010834
DistanceFromHome                 0.045034   0.012712   3.543 0.000396
EnvironmentSatisfaction2        -0.642659   0.306483  -2.097 0.036004
EnvironmentSatisfaction3        -0.952836   0.289995  -3.286 0.001017
EnvironmentSatisfaction4        -1.131019   0.294474  -3.841 0.000123
GenderMale                       0.467585   0.213825   2.187 0.028760
JobInvolvement2                 -1.266370   0.420309  -3.013 0.002587
JobInvolvement3                 -1.447307   0.396168  -3.653 0.000259
JobInvolvement4                 -2.154320   0.564135  -3.819 0.000134
JobLevel2                       -1.198499   0.267044  -4.488 7.19e-06
JobLevel3                       -0.752171   0.355111  -2.118 0.034164
JobLevel4                       -1.988190   0.739288  -2.689 0.007160
JobLevel5                       -0.974479   0.701801  -1.389 0.164972
JobSatisfaction2                -0.766564   0.320723  -2.390 0.016843
JobSatisfaction3                -0.592820   0.275563  -2.151 0.031452
JobSatisfaction4                -1.313512   0.298732  -4.397 1.10e-05
NumCompaniesWorked               0.189683   0.057605   3.293 0.000992
OverTimeYes                      1.975797   0.225943   8.745  < 2e-16
RelationshipSatisfaction2       -0.473502   0.316516  -1.496 0.134658
RelationshipSatisfaction3       -0.721628   0.286964  -2.515 0.011914
RelationshipSatisfaction4       -0.795014   0.295916  -2.687 0.007218
StockOptionLevel1               -1.312684   0.236788  -5.544 2.96e-08
StockOptionLevel2               -1.548887   0.446240  -3.471 0.000519
StockOptionLevel3               -0.718871   0.480022  -1.498 0.134242
WorkLifeBalance2                -0.963807   0.401893  -2.398 0.016477
WorkLifeBalance3                -1.291353   0.374172  -3.451 0.000558
WorkLifeBalance4                -1.046844   0.463310  -2.259 0.023853
YearsSinceLastPromotion          0.163445   0.045236   3.613 0.000303
YearsWithCurrManager            -0.184868   0.052021  -3.554 0.000380
firstcompanyTRUE                 0.363822   0.316608   1.149 0.250505
loyality                         0.033154   0.493545   0.067 0.946442
volatility                      -0.002868   0.038053  -0.075 0.939914
                                   
(Intercept)                     *  
Age                             ** 
BusinessTravelTravel_Frequently ***
BusinessTravelTravel_Rarely     *  
DistanceFromHome                ***
EnvironmentSatisfaction2        *  
EnvironmentSatisfaction3        ** 
EnvironmentSatisfaction4        ***
GenderMale                      *  
JobInvolvement2                 ** 
JobInvolvement3                 ***
JobInvolvement4                 ***
JobLevel2                       ***
JobLevel3                       *  
JobLevel4                       ** 
JobLevel5                          
JobSatisfaction2                *  
JobSatisfaction3                *  
JobSatisfaction4                ***
NumCompaniesWorked              ***
OverTimeYes                     ***
RelationshipSatisfaction2          
RelationshipSatisfaction3       *  
RelationshipSatisfaction4       ** 
StockOptionLevel1               ***
StockOptionLevel2               ***
StockOptionLevel3                  
WorkLifeBalance2                *  
WorkLifeBalance3                ***
WorkLifeBalance4                *  
YearsSinceLastPromotion         ***
YearsWithCurrManager            ***
firstcompanyTRUE                   
loyality                           
volatility                         
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 974.94  on 1102  degrees of freedom
Residual deviance: 647.48  on 1068  degrees of freedom
AIC: 717.48

Number of Fisher Scoring iterations: 6

Now we can check the model performance and compare it with the others.

Model performance:

Evaluating model performance for new features and significant features.

#Making prediction
pred_dat4=predict(model_new_feat,test_new_dat[,c("Attrition",name_signi,new_feat)],type = "response")
#plotting Rocr curve
rocr_curve(predi = pred_dat4)

Here we can clearly see that the optimum threshold value is between 0.3 and 0.4 .So now we will make the confusion matrix with 0.35 as threshold value and evaluate the performance of the model

#Confusion matrix
confu_dat_new=table(test_new_dat$Attrition,pred_dat4>0.35)
print("Confusion matrix ")
[1] "Confusion matrix "
confu_dat_new
     
      FALSE TRUE
  No    279   29
  Yes    22   37
#model performace
model_perform(confu_dat_new)
[1] "Model Performance:"
[1] "senstivity: 0.593220338983051"
[1] "specificity: 0.905844155844156"
[1] "Accuracy: 86 %"

Model Comparison:

We will compare the performance of each model to sewlect the best model.

#Making functions
sensti=function(x){
    tp1=x[4]
totl_p=tp1+x[2]
sens=tp/totl_p
sens
}
specif=function(y){
    tn1=y[1]
totl_n=tn1+y[3]
spec=tn1/totl_n
spec
}

acc=function(z){
    acc_z=sum(diag(z))/sum(z)
    accur=round(acc_z*100)
    accur
}
#senstivity
sens_log=sensti(confus_dat1)
sens_log_sign=sensti(confus_dat2)
sens_DT=S=sensti(confu_dat3)
sens_DT_imp=sensti(confu_dat4)
sens_log_new=sensti(confu_dat_new)
#specificity
speci_log=specif(confus_dat1)
speci_log_sign=specif(confus_dat2)
speci_DT=S=specif(confu_dat3)
speci_DT_imp=specif(confu_dat4)
speci_log_new=specif(confu_dat_new)
#Accuracy
acc_log=acc(confus_dat1)
acc_log_sign=acc(confus_dat2)
acc_DT=S=acc(confu_dat3)
acc_DT_imp=acc(confu_dat4)
acc_log_new=acc(confu_dat_new)
#Performance comparison table
data.frame(list("model_name" = c("cart all variables","cart important variables","logistic all variables","logistic significant variables","Logistic with feature engineering"),
                "Sensitivity" = c(sens_DT,sens_DT_imp,sens_log,sens_log_sign,sens_log_new),
                "Specificity" = c(speci_DT,speci_DT_imp,speci_log,speci_log_sign,speci_log_new),
               "Accuracy" = c(acc_DT,acc_DT_imp,acc_log,acc_log_sign,acc_log_new)))
NA
NA

From the above statistics we conclude that for this use case:

Model Selection:

Here we are considering two main factors for model selection:

1)According to Occam learning we should choose the model with less complexity (i.e model having only sufficient variables to understand the pattern) even though if it is having little less accuracy than other models.

2)Second factor depends upon buisness intrest if they are concern about senstivity ,specificity or total accuracy.As in this case company is more concerned about finding out the employees who will churn,So we should concern for senstivity to have a higher value and less specificity value.

Now after considering the above 2 points we can conclude that “logistics model with significant variables” is satisfying both the above mentioned statements without a remarkable decrease in the accuracy hence is the best model of all the models.

#plot for Decision Tree
plot(roc(test_dat$Attrition, pred_dat2), print.auc=TRUE,col="black")
Setting levels: control = No, case = Yes
Setting direction: controls < cases
#plot for Decision Tree with important variables
plot(roc(test_dat$Attrition, pred_dat3), print.auc = TRUE,col = "green", print.auc.y = .1, add = TRUE)
Setting levels: control = No, case = Yes
Setting direction: controls < cases
#plot for logistics regression with significant variables
plot(roc(test_dat$Attrition, pred_dat1), print.auc = TRUE,col = "blue", print.auc.y = .2, add = TRUE)
Setting levels: control = No, case = Yes
Setting direction: controls < cases
#plot for logistics regression with all variables
plot(roc(test_dat$Attrition, predict_dat), print.auc = TRUE,col = "red", print.auc.y = .3, add = TRUE)
Setting levels: control = No, case = Yes
Setting direction: controls < cases
#plot for logistics regression with significant and important variables
plot(roc(test_dat$Attrition, pred_dat4), print.auc = TRUE,col = "pink", print.auc.y = .4, add = TRUE)
Setting levels: control = No, case = Yes
Setting direction: controls < cases

LS0tDQp0aXRsZTogIkVNUExPWUVFIENIVVJOIEFOQUxZU0lTIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCjxoMz5Mb2FkaW5nIExpYnJhcmllczxoMz4NCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGNhVG9vbHMpDQpsaWJyYXJ5KFJPQ1IpDQpsaWJyYXJ5KHJwYXJ0KQ0KbGlicmFyeShycGFydC5wbG90KQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KbGlicmFyeShwUk9DKQ0KYGBgDQoNCjxoMz5JbXBvcnRpbmcgRGF0YXNldDxoMz4NCmBgYHtyfQ0KI2xvYWRpbmcgZGF0YXNldA0KYXR0X2RhdGE9cmVhZC5jc3YoIkQ6L2NhcHN0b25lL2F0dHJpdGlvbl9kYXRhLmNzdiIpDQpgYGANCg0KDQo8aDM+SGVhZCBEYXRhPGgzPg0KYGBge3J9DQojSEVBRA0KaGVhZChhdHRfZGF0YSkNCmNvbG5hbWVzKGF0dF9kYXRhKVsxXT0iQWdlIg0KYGBgDQo8aDM+Q2hlY2tpbmcgZm9yIG51bGwgdmFsdWVzPGgzPg0KYGBge3J9DQojY2hlY2tpbmcgbnVsbCB2YWx1ZXMNCmRhdGEuZnJhbWUoY29sU3Vtcyhpcy5uYShhdHRfZGF0YSkpKQ0KYGBgDQpIZW5jZSBpdCBpcyBjbGVhciB0aGF0IG91ciBkYXRhIGhhcyBubyBtaXNzaW5nIHZhbHVlIGFuZCBzbyAgd2UgY2FuIG1vdmUgZnVydGhlciBmb3IgIERlc2NyaXB0aXZlIEFuYWx5c2lzIG9mIHRoZSBkYXRhLg0KDQo8aDM+RXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpczxoMz4NCmBgYHtyfQ0KI0NoZWNraW5nIHN0cnVjdHVyZSBvZiB0aGUgZ2l2ZW4gYXR0cmlidXRlcyBpbiBkYXRhDQpzdHIoYXR0X2RhdGEpDQpgYGANCldlIGNhbiBzZWUgdGhhdCBvdXIgZGF0YSBoYXMgMTQ3MCBvYnNlcnZhdGlvbnMgYW5kIDM1IHZhcmlhYmxlcyBhbmQgYWxzbyB0aGUgY2xhc3Mgb2YgZWFjaCB2YXJpYWJsZS4NCg0KU29tZSBvZiB0aGUgdmFyaWFibGVzIChsaWtlIEVkdWNhdGlvbixKb2JJbnZvbHZlbWVudCwgRW52aXJvbm1lbnRTYXRpc2ZhY3Rpb24uLi4pYXJlIG9yZGluYWwgYnV0IGZhbGwgdW5kZXIgaW50ZWdlciBjbGFzcyBpbiBvdXIgZGF0YSBzbyB3ZSBuZWVkIHRvIGNoYW5nZSAgdGhvc2UgYXMgYSBmYWN0b3IuIA0KYGBge3J9DQpvcmRfY29sPWMoIkVkdWNhdGlvbiIsIkVudmlyb25tZW50U2F0aXNmYWN0aW9uIiwiSm9iSW52b2x2ZW1lbnQiLCJKb2JMZXZlbCIsDQogICAgICAgICAgIkpvYlNhdGlzZmFjdGlvbiIsIlBlcmZvcm1hbmNlUmF0aW5nIiwiUmVsYXRpb25zaGlwU2F0aXNmYWN0aW9uIiwNCiAgICAgICAgICAiU3RhbmRhcmRIb3VycyIsIlN0b2NrT3B0aW9uTGV2ZWwiLCJXb3JrTGlmZUJhbGFuY2UiKQ0KYXR0X2RhdGFbLG9yZF9jb2xdPWxhcHBseShhdHRfZGF0YVssb3JkX2NvbF0sYXMuZmFjdG9yKQ0Kc3RyKGF0dF9kYXRhKQ0KDQpgYGANCk5vdyB3ZSBoYXZlIHRoZSBkYXRhIHdpdGggb3JkaW5hbCBjb2x1bW5zIGFzIGZhY3RvciBhbmQgb3RoZXIgdmFyaWFibGVzIGluIGludGVnZXIgZm9ybWF0Lg0KDQo8aDM+RGF0YSBEZXNjcmlwdGlvbjxoMz4NCmBgYHtyfQ0KI3N1bW1hcmlzaW5nIGRhdGENCnN1bW1hcnkoYXR0X2RhdGEpDQpgYGANCkFib3ZlIGluZm9ybWF0aW9uIGRlc2NyaWJlcyB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBkYXRhIChjZW50cmFsIHRlbmRlbmNpZXMgb2YgaW50ZWdlciB0eXBlIGRhdGEgdmFyaWFibGVzIGFuZCBmcmVxdWVuY3kgIGRpc3RyaWJ1dGlvbiBvZiBjYXRlZ29yeSB0eXBlIGNvbHVtbnMpLg0KDQo8aDM+IERhdGEgVmlzdWFsaXphdGlvbjxoMz4NCmBgYHtyfQ0KI0Rpc3RyaWJ1dGlvbiBvZiB0aGUgZGF0YQ0KaW50X3ZhcnM9Y29sbmFtZXMoYXR0X2RhdGFbd2hpY2goc2FwcGx5KGF0dF9kYXRhLGNsYXNzKT09ImludGVnZXIiKV0pDQptZWx0X2F0dHJpdGlvbl9kYXQgPSBtZWx0KGF0dF9kYXRhWyxjKCJBdHRyaXRpb24iLCBpbnRfdmFycyldLCBpZC52YXIgPSAiQXR0cml0aW9uIikNCmhlYWQobWVsdF9hdHRyaXRpb25fZGF0KQ0KDQpgYGANCg0KYGBge3IsZmlnLmhlaWdodD03LGZpZy53aWR0aD03fQ0KDQpwIDwtIGdncGxvdChkYXRhID0gbWVsdF9hdHRyaXRpb25fZGF0ICwgYWVzKHg9dmFyaWFibGUsIHk9dmFsdWUsZmlsbD1BdHRyaXRpb24pKSArIGdlb21fYm94cGxvdCgpK3NjYWxlX2ZpbGxfdmlyaWRpc19kKCkNCnAgPC0gcCArIGZhY2V0X3dyYXAoIH4gdmFyaWFibGUsIHNjYWxlcz0iZnJlZSIpDQpwDQoNCmBgYA0KSGVyZSB3ZSBjYW4gc2VlIHRoZSBkaXN0cmlidXRpb24gb2YgYWxsIHRoZSB2YXJpYWJsZSBvZiBpbnRlZ2VyIGNsYXNzLg0KDQpgYGB7cn0NCmNhdF9jb2xzPWNvbG5hbWVzKGF0dF9kYXRhW3doaWNoKHNhcHBseShhdHRfZGF0YSxjbGFzcyk9PSJmYWN0b3IiKV0pDQpmcmVxX3RibD1hcHBseShhdHRfZGF0YVssY2F0X2NvbHNdLDIsIGZ1bmN0aW9uKHgpIHRhYmxlKGF0dF9kYXRhJEF0dHJpdGlvbix4KSkNCmZyZXFfdGJsPSBsYXBwbHkoZnJlcV90YmwsZnVuY3Rpb24oeCkgYXMuZGF0YS5mcmFtZS5tYXRyaXgoeCkpDQpmcmVxX3RibA0KYT1uYW1lcyhmcmVxX3RibClbMl0NCmZyZXFfdGJsW2FdW1sxXV0NCmBgYA0KDQpgYGB7cn0NCmkgPTANCmZvcihuYW1lIGluIG5hbWVzKGZyZXFfdGJsKVstMV0pew0KICAgIGkgPC0gaSArMQ0KICAgIHZhcl9kYXRhIDwtIGRhdGEuZnJhbWUoYXBwbHkoZnJlcV90YmxbbmFtZV1bWzFdXSwyLCBmdW5jdGlvbih4KSB4WzJdL3N1bSh4KSkpDQogICAgY29sbmFtZXModmFyX2RhdGEpIDwtIG5hbWUNCiAgIG15X3Bsb3QgPC0gZ2dwbG90KGRhdGE9dmFyX2RhdGEsIGFlcyh4PXJvdy5uYW1lcyh2YXJfZGF0YSksIHk9dmFyX2RhdGFbLG5hbWVdKSkgKyAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLGZpbGw9J3JlZCcpICsNCiAgICAgICAgeWxpbSgwLjAsMS4wKSArIHlsYWIoIiVhdHRyaXRpb24iKSArIHhsYWIobmFtZSkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKQ0KICAgIHBsb3QobXlfcGxvdCkNCiAgICByZW1vdmUobXlfcGxvdCkNCn0NCmBgYA0KRnJvbSB0aGUgYWJvdmUgZ3JhcGhzIHdlIGNhbiB1bmRlcnN0YW5kIHRoZSBhdHRyaXRpb24gcmF0ZSBvZiBlbXBsb3llZXMgd2l0aCByZXNwZWN0IHRvIGRpZmZlcmVudCBhdHRyaWJ1dGUgdmFsdWVzLg0KYGBge3IsZmlnLmhlaWdodD0xMCxmaWcud2lkdGg9MTB9DQpsaWJyYXJ5KGdyaWRFeHRyYSkNCmcxPWdncGxvdChkYXRhID0gYXR0X2RhdGEsYWVzKE1vbnRobHlJbmNvbWUsRW1wbG95ZWVOdW1iZXIsc2l6ZT1BZ2UsY29sPUF0dHJpdGlvbikpK2dlb21fcG9pbnQoYWxwaGE9MC43KStnZ3RpdGxlKCJBdHRyaXRpb24gdnMgTW9udGhseWluY29tZSAoMSkiKQ0KZzI9Z2dwbG90KGRhdGEgPSBhdHRfZGF0YVt3aGljaChhdHRfZGF0YSRBdHRyaXRpb249PSdZZXMnKSxdLGFlcyhNb250aGx5SW5jb21lLEVtcGxveWVlTnVtYmVyLHNpemU9QWdlKSkrZ2VvbV9wb2ludChjb2w9J3NreWJsdWUnLGFscGhhPTAuNykrZ2d0aXRsZSgiQXR0cml0aW9uIHZzIE1vbnRobHlpbmNvbWUgKDIpIikNCmdyaWQuYXJyYW5nZShnMSxnMikNCg0KYGBgDQpmcm9tIHRoZSBmaXJzdCBwbG90IHNob3dzIHRoZSBhdHRyaXRpb24gYWNjb3JkaW5nIHRvIE1vbnRobHlpbmNvbWUgYW5kIEFnZSBvZiBlbXBsb3llZS4NCg0KRnJvbSB0aGUgc2Vjb25kIGdyYXBoIHdlIGNhbiBjb25jbHVkZSB0aGF0IGVtcGxveWVlcyB3aXRoIE1vbnRobHlpbmNvbWUgbGVzcyB0aGFuIDE1MDAwIGFuZCBBZ2UgYmV0d2VlbiAyMC00MCBoYXZlIG1vcmUgY2hhbmNlcyBvZiBsZWF2aW5nIHRoZSBjb21wYW55Lg0KDQo8aDM+Q29ycmVsYXRpb248aDM+DQpgYGB7cixmaWcuaGVpZ2h0PTEwLGZpZy53aWR0aD0xMH0NCiNwbG90dGluZyBjb3JycGxvdA0KY29ycnBsb3QoY29yKGF0dF9kYXRhW3NldGRpZmYoaW50X3ZhcnMsIkVtcGxveWVlQ291bnQiKV0pKQ0KYGBgDQpBYm92ZSBncmFwaCBzaG93cyBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSB0d28gdmFyaWFibGVzIGFuZCB3ZSBjYW4gY2xlYXJseSBzZWUgdGhhdCBzb21lIG9mIHRoZSB2YWlyYWJsZXMgYXJlIGhhdmluZyBhIGdvb2QgcG9zdGl2ZSBjb3JyZWxhdGlvbiAobGlrZSBBZ2UgYW5kIHRvdGFsd29ya2luZ3llYXJzKS4NCg0KPGgzPkZlYXR1cmUgUmVkdWN0aW9uPGgzPg0KDQo8aDY+RW50cm9weSBhbmQgdmFyaWFuY2UgYmFzZWQ8aDY+DQpgYGB7cn0NCiMxKUZlYXR1cmUgUmVkdWN0aW9uIHVzaW5nIGVudHJvcHkgDQpsaWJyYXJ5KGVudHJvcHkpDQpsaWJyYXJ5KGRwbHlyKQ0KIzEuMSlDYWxjdWxhdGluZyBlbnRyb3B5IG9mIHRoZSBjYXRlZ29yaWNhbCB2YXJpYWJsZSANCmVudHJvcHlfY2F0ID0gdW5saXN0KGxhcHBseShhdHRfZGF0YVssY2F0X2NvbHNdLGZ1bmN0aW9uKHgpIGVudHJvcHkodGFibGUoeCkpKSkvdW5saXN0KGxhcHBseShhdHRfZGF0YVssY2F0X2NvbHNdLGZ1bmN0aW9uKHgpIGxvZzIobGVuZ3RoKHgpKSkpDQojMS4yKVBpY2tpbmcgdmFyaWFibGUgd2l0aCAwIGVudHJvcHkNCnplcm9fZW50cm9weV92YXJpYWJsZT0gbmFtZXMoZW50cm9weV9jYXRbZW50cm9weV9jYXQ9PTBdKQ0KIzIpRmVhdHVyZSBSZWR1Y3Rpb24gdXNpbmcgdmFyaWFuY2UgDQojMi4xKU5vcm1hbGlzaW5nIHRoZSBkYXRhDQpub3JtX2F0dF9kYXRhPWFzLmRhdGEuZnJhbWUubWF0cml4KGFwcGx5KGF0dF9kYXRhWyxpbnRfdmFyc10sMixmdW5jdGlvbih4KSAoeC1taW4oeCkpLyhtYXgoeCktbWluKHgpKSApKQ0KIzIuMilDYWxjdWxhdGluZyB2YXJpYW5jZQ0Kbm9ybV9hdHRfZGF0YT1hcHBseShub3JtX2F0dF9kYXRhLDIsdmFyKQ0KIzIuMyBQaWNraW5nIGxvdyB2YXJpYW5jZSB2YXJpYWJsZQ0KbG93X3Zhcj1uYW1lcyhub3JtX2F0dF9kYXRhW2lzLm5hKG5vcm1fYXR0X2RhdGEpPT1UUlVFXSkNCiNSZW1vdmluZyB2YXJpYWJsZSB3aXRoIDAgZW50cm9weSBhbmQgbG93IHZhcmlhbmNlDQphdHRfZGF0YTE9c2VsZWN0KGF0dF9kYXRhLC1jKHplcm9fZW50cm9weV92YXJpYWJsZSxsb3dfdmFyLCJFbXBsb3llZU51bWJlciIpKQ0KI2RpbWVuc2lvbiANCmRpbShhdHRfZGF0YTEpDQpgYGANCkhlcmUgd2UgY2FuIHNlZSB0aGF0IDQgZmVhdHVyZXMgYXJlIHJlbW92ZWQgZnJvbSB0aGUgZGF0YSBvbiB0aGUgYmFzaXMgb2YgMCBlbnRyb3B5LG51bGwgdmFyaWFuY2UgYW5kIEVtcGxveWVlTnVtYmVyIGlzIGFsc28gcmVtb3ZlZCBhcyBpdHMganVzdCB0aGUgaWQgbm9taW5hbCB0eXBlIC4NCg0KPGgzPlNwbGl0dGluZyB0aGUgRGF0YTxoMz4NCg0KU3BsaXR0aW5nIHRoZSBkYXRhIHJhbmRvbWx5ICBpbnRvIDc1OjI1IHJhdGlvIGZvciB0cmFpbmluZyBhbmQgdGVzdGluZyB0aGUgbW9kZWwuDQpgYGB7cn0NCiNTcGxpdGluZyB0aGUgZGF0YSBpbnRvIDc1OjI1IHByb3BvcnRpb24NCmxpYnJhcnkoY2FUb29scykNCnNldC5zZWVkKDExKQ0Kc3BsdD1zYW1wbGUuc3BsaXQoYXR0X2RhdGExJEF0dHJpdGlvbixTcGxpdFJhdGlvID0gMC43NSkNCnRyYWluX2RhdD1zdWJzZXQoYXR0X2RhdGExLHNwbHQ9PVRSVUUpDQp0ZXN0X2RhdD1zdWJzZXQoYXR0X2RhdGExLHNwbHQ9PUZBTFNFKQ0KcHJpbnQocGFzdGUoIlRyYWluIGRhdGEgaGFzICIsbnJvdyh0cmFpbl9kYXQpLCJvYnNlcnZhdGlvbnMiKSkNCnByaW50KHBhc3RlKCJUZXN0IGRhdGEgaGFzICIsbnJvdyh0ZXN0X2RhdCksIm9ic2VydmF0aW9ucyIpKQ0KYGBgDQo8aDM+TW9kZWwgQnVpbGRpbmc8aDM+DQoNCjxoNT5Mb2dpc3RpY3MgUmVncmVzc2lvbjxoNT4NCg0KYGBge3J9DQojQnVpbGRpbmcgbG9naXN0aWNzIG1vZGVsDQpzZXQuc2VlZCgxMTEpDQptb2RlbF9kYXQ9Z2xtKEF0dHJpdGlvbn4uLGRhdGEgPSB0cmFpbl9kYXQsZmFtaWx5ID0gImJpbm9taWFsIikNCiNTdW1tYXJ5IG9mIG1vZGVsDQpzdW1tYXJ5KG1vZGVsX2RhdCkNCmBgYA0KRnJvbSB0aGUgYWJvdmUgc3VtbWFyeSAgd2UgY2FuIHNlZSB0aGUgcmVzaWR1YWwgZGV2aWFuY2UgYW5kIEFJQyBvZiB0aGUgbW9kZWwgYW5kIHRoZSBpbXBvcnRhbnQgdmFyaWFibGVzIGhhdmluZyBwLXZhbHVlIDwgMC4wNS4NCg0KPGgzPk1rYWluZyBQcmVkaWN0aW9uDQpOb3cgaW5pdGlhbGx5LCB3ZSB3aWxsIG1ha2UgdGhlIHByZWRpY3Rpb24gdXNpbmcgbW9kZWwgaW5jbHVzaXZlIG9mIGFsbCB0aGUgdmFyaWFibGVzIGFuZCBjaGVjayB0aGUgcGVyZm9ybWFuY2Ugb2Ygb3VyIG1vZGVsLg0KYGBge3J9DQojTWFraW5nIHByZWRpY2l0b24NCnByZWRpY3RfZGF0PXByZWRpY3QobW9kZWxfZGF0LG5ld2RhdGE9dGVzdF9kYXQsdHlwZT0icmVzcG9uc2UiKQ0KDQpgYGANCk5vdyB0aGUgb3V0Y29tZSBvZiBsb2dpc3RpY3MgcmVncmVzc2lvbiBtb2RlbCBpcyBpbiBwcm9iYWJsaXR5LkluIG9yZGVyIHRvIG1ha2UgYWN0dWFsIHByZWRpY3Rpb25zIHdlIGhhdmUgdG8gbWFrZSBhIGNvbmZ1c2lvbiBtYXRyaXggYnkgc2V0dGluZyB0aHJlc2hvbGQgdmFsdWUgInQiIHdoaWNoIGZpbHRlcnMgdGhlIHByb2JhYmxpdHkgYWJvdmUgdGhyZXNob2xkIHZhbHVlIGFzICJZZXMiIGFuIGJlbG93IGFzICJObyIuDQoNCkluaXRpYWxseSB3ZSBhcmUgc2V0dGlnbiB0aHJlc2hvbGQgYXMgMC41IGFzIGl0cyB0aGUgZ2VuZXJhbCB2YWx1ZSBhbmQgYWxzbyBtaWQgdmFsdWUgd2hpY2gga2VlcCBzZW5zdGl2aXR5IGFuZCBzcGVjaWZpY2l0eSBpbiBiYWxhbmNlIGFzIGluY3JlYXNpbmcgdGhyZXNob2xkIHdpbGwgbGVhZCB0byBpbmNyZWFzZSBpbiBzcGVjaWZpY2l0eSBidXQgZGVjcmVhc2Ugc2Vuc3Rpdml0eSB3aGljaCBtZWFucyBtb3JlIGVycm9yIGluIHByZWRpY3Rpb24gZW1wbG95ZWVzIHdobyB3aWxsIGNodXJuLlNpbWlsYXJpbHkgbG93ZXIgdGhyZXNob2xkIHZhbHVlIGRlY3JlYXNlIHNwZWNpZmljaXR5IGFuZCBpbmNyZWFzZSBzZW5zdGl2aXR5IHdoaWNoIG1lYW5zIG1vcmUgZXJyb3IgaW4gcHJlZGljdGluZyBlbXBsb3llZXMgd2hvIHdpbGwgbm90IGNodXJuLg0KYGBge3J9DQojTWFraW5nIGNvbmZ1c2lvbiBtYXRyaXggd2l0aCB0aHJlc2hvbGQgMC41DQpjb25mdXNpb25fZGF0PXRhYmxlKHRlc3RfZGF0JEF0dHJpdGlvbixwcmVkaWN0X2RhdD4wLjUpDQpwcmludCgiQ29uZnVzaW9uIG1hdHJpeCBmb3IgdGhyZXNob2xkIDAuNSIpDQpjb25mdXNpb25fZGF0DQojQ2FsY2x1YXRpbmcgc2Vuc3Rpdml0eS90cHINCnRwPWNvbmZ1c2lvbl9kYXRbNF0NCnRvdGFsX3A9dHArY29uZnVzaW9uX2RhdFsyXQ0Kc2Vuc3Rpdml0eV9kYXQ9dHAvdG90YWxfcA0KcHJpbnQocGFzdGUoInNlbnN0aXZpdHk6IixzZW5zdGl2aXR5X2RhdCkpDQojQ2FsY3VsYXRpbmcgc3BlY2lmaWNpdHkvZnByDQp0bj1jb25mdXNpb25fZGF0WzFdDQp0b3RhbF9uPXRuK2NvbmZ1c2lvbl9kYXRbM10NCnNwZWNpZmljaXR5X2RhdD10bi90b3RhbF9uDQpwcmludChwYXN0ZSgic3BlY2lmaWNpdHk6IixzcGVjaWZpY2l0eV9kYXQpKQ0KDQpgYGANCk5vdyAgaGVyZSB3ZSBjYW4gc2VlIHRoYXQgc3BlY2lmaWNpdHkgaXMgd2F5IHRvbyBoaWdoIGFzIGNvbXBhcmVkIHRvIHNlbnN0aXZpdHkgc28gaW4gb3JkZXIgdG8gcmVzb2x2ZSB0aGlzIHByb2JsZW0gdGhpcyB3ZSBjYW4gZmluZCBhbiBvcHRpbWFsIHZhbHVlIG9mIHRocmVzaG9sZC4NCg0KPGgzPlJPQ1IgQ3VydmU8aDM+DQoNCkluIG9yZGVyIHRvIGZpbmQgdGhlIG9wdGltYWwgdmFsdWUgb2YgdGhyZXNob2xkIHdlIHdpbGwgcGxvdCBhIFJPQ1IgY3VydmUgKHRydWUgcG9zdGl2ZSByYXRlIHZzIGZhbHNlIHBvc3RpdmUgcmF0ZSkgd2hpY2ggd2lsbCBpbmRpY2F0ZSB0aGUgdmFsdWUgd2l0aCBtaW5pbXVtIGVycm9yLg0KDQpgYGB7cixmaWcuaGVpZ2h0PTYsZmlnLndpZHRoPTEwfQ0KI1JlY2lldmVyIE9wZXJhdG9yIENoYXJhY3RlcmlzdGljcyBDdXJ2ZQ0Kcm9jcl9jdXJ2ZT1mdW5jdGlvbihwcmVkaSl7DQojUHJlZGljdGlvbg0Kcm9jcl9wcmVkPXByZWRpY3Rpb24ocHJlZGksdGVzdF9kYXQkQXR0cml0aW9uKQ0KI1BlcmZvcm1hbmNlDQpyb2NyX3BlcmY9cGVyZm9ybWFuY2Uocm9jcl9wcmVkLCJ0cHIiLCJmcHIiKQ0KI3Bsb3R0aW5nIFJPQ1IgIA0KcGxvdChyb2NyX3BlcmYsY29sb3JpemU9VFJVRSxwcmludC5jdXRvZmZzLmF0PXNlcSgwLDEsMC4xKSx0ZXh0LmFkaj1jKC0wLjIsMS43KSkNCn0NCnJvY3JfY3VydmUocHJlZGljdF9kYXQpDQpgYGANCkZyb20gdGhlIFJPQ1IgcGxvdCB3ZSBjYW4gdW5kZXJzdGFuZCB0aGF0IDAuMjUgaXMgdGhlIG9wdGltdW0gdmFsdWUgb2YgdGhyZXNob2xkLg0KDQpTbyBub3cgd2Ugd2lsbCBtYWtlIHRoZSBhY3R1YWwgcHJlZGljdGlvbiB1c2luZyB0aHJlc2hvbGQ9MC4zIGFuZCBjaGVjayBpdHMgYWNjdXJhY3kgLHNlbnN0aXZpdHksc3BlY2lmaWNpdHkgYW5kIGthcHBhIHZhbHVlLg0KDQpgYGB7cn0NCiNwZXJmb3JtYW5jZSBtb2RlbCBmdW5jdGlvbg0KDQojY29uZnVzaW9uIG1hdHJpeCAgICANCmNvbmZ1c19kYXQxPXRhYmxlKHRlc3RfZGF0JEF0dHJpdGlvbixwcmVkaWN0X2RhdD4wLjI1KQ0KcHJpbnQoIkNvbmZ1c2lvbiBtYXRyaXggd2l0aCBvcHRpbXVtIHRocmVzaG9sZCIpDQpwcmludChjb25mdXNfZGF0MSkNCm1vZGVsX3BlcmZvcm09ZnVuY3Rpb24oY29uZnUpew0KcHJpbnQoJ01vZGVsIFBlcmZvcm1hbmNlOicpDQojY2FsdWNsYXRpbmcgc2Vuc3Rpdml0eQ0KdHAxPWNvbmZ1WzRdDQp0b3RsX3A9dHAxK2NvbmZ1WzJdDQpzZW5zPXRwL3RvdGxfcA0KcHJpbnQocGFzdGUoInNlbnN0aXZpdHk6IixzZW5zKSkNCiNjYWxjdWxhdGluZyBzcGVjaWZpY2l0eQ0KdG4xPWNvbmZ1WzFdDQp0b3RsX249dG4xK2NvbmZ1WzNdDQpzcGVjPXRuMS90b3RsX24NCnByaW50KHBhc3RlKCJzcGVjaWZpY2l0eToiLHNwZWMpKQ0KI2NhbGN1bGF0aW5nIGFjY3VyYWN5DQphY2NfZGF0MT1zdW0oZGlhZyhjb25mdSkpL3N1bShjb25mdSkNCnByaW50KHBhc3RlKCJBY2N1cmFjeToiLHJvdW5kKGFjY19kYXQxKjEwMCksIiUiKSkNCn0NCm1vZGVsX3BlcmZvcm0oY29uZnVzX2RhdDEpDQpgYGANCkhlcmUgd2UgZ290IHRoZSBtb2RlbCB3aXRoIGdvb2QgYWNjdXJhY3kgYW5kIGFzIHdlIGFyZSBtb3JlIGNvbmNlcm5lZCBhYm91dCBnZXR0aW5nIG1vcmUgdHJ1ZSBwcmVkaWN0aW9ucyBvZiBlbXBsb3llZXMgd2hvIHdpbGwgY2h1cm4sIGl0IGlzIG9wdGltdW0gdGhyZXNob2xkIGZvciBvdXIgY29uY2Vybi4NCg0KTm93IHdlIHdpbGwgdHJ5IHRvIHJlZHVjZSB0aGUgY29tcGxleGl0eSBvZiB0aGUgbW9kZWwgYnkgc2VsZWN0aW5nIGltcG9ydGFudCBmZWF0dXJlcyBmb3IgdGhlIG1vZGVsIGJhc2VkIG9uIHAtdmFsdWUuIEluIHN1bW1hcnkgb2YgdGhlIG1vZGVsIGFsbCB2YXJpYWJsZXMgd2hpY2ggaGF2ZSBwLXZhbHVlIGxlc3MgdGhhbiAwLjA1IGFyZSBjb25zaWRlcmVkIGltcG9ydGFudC4NCmBgYHtyfQ0Kc3VtbV9kYXQ9YXMuZGF0YS5mcmFtZS5tYXRyaXgoc3VtbWFyeShtb2RlbF9kYXQpJGNvZWYpDQpzaWduX3Zhcj1zdW1tX2RhdFtzdW1tX2RhdCRgUHIoPnx6fClgPDAuMDUsXQ0Kc2lnbl92YXINCmBgYA0KQWJvdmUgc2hvd24gYXJlIHRoZSBzaWduaWZpY2FudCB2YXJpYWJsZXMgYW5kIG5vdyB3ZSB3aWxsIGJ1aWxkIGEgbW9kZWwgdXNpbmcgdGhlc2UgdmFyaWFibGVzIG9ubHkgYW5kIGNoZWNrIHRoZSB0cmFkZW9mZiBiZXR3ZWVuIGNvbXBsZXhpdHkgYW5kIGFjY3VyYWN5Lg0KDQoNCjxoMz5Nb2RlbCBCdWlsZGluZyB3aXRoIHNpZ25pZmljYW50IHZhcmlhYmxlczxoMz4NCg0KYGBge3J9DQojU2VsZWN0aW5nIHNpZ25pZmljYW50IHZhcmlhYmxlIG5hbWVzIA0KbmFtZV9zaWduaT1jKCJBZ2UiLCJCdXNpbmVzc1RyYXZlbCIsIkRpc3RhbmNlRnJvbUhvbWUiLCJFbnZpcm9ubWVudFNhdGlzZmFjdGlvbiIsIkdlbmRlciIsIkpvYkludm9sdmVtZW50IiwiSm9iTGV2ZWwiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSm9iU2F0aXNmYWN0aW9uIiwiTnVtQ29tcGFuaWVzV29ya2VkIiwiT3ZlclRpbWUiLCJSZWxhdGlvbnNoaXBTYXRpc2ZhY3Rpb24iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiU3RvY2tPcHRpb25MZXZlbCIsIldvcmtMaWZlQmFsYW5jZSIsIlllYXJzU2luY2VMYXN0UHJvbW90aW9uIiwgIlllYXJzV2l0aEN1cnJNYW5hZ2VyIikNCiNCdWlsZGluZyBtb2RlbA0KbW9kZWxfc2lnbmk9Z2xtKEF0dHJpdGlvbn4uLGRhdGEgPSB0cmFpbl9kYXRbLGMoIkF0dHJpdGlvbiIsbmFtZV9zaWduaSldLGZhbWlseSA9ICJiaW5vbWlhbCIpDQpzdW1tYXJ5KG1vZGVsX3NpZ25pKQ0KYGBgDQpOb3cgd2UgY2FuIG1ha2UgcHJlZGljdGlvbiB1c2luZyB0aGlzIG1vZGVsIGFuZCB3aXRoIG9wdGltdW0gdGhyZXNob2xkIHZhbHVlIHdlIHdpbGwgY2hlY2sgdGhlIG1vZGVsIHBlcmZvcm1hbmNlLg0KYGBge3IsZmlnLmhlaWdodD02LGZpZy53aWR0aD04fQ0KI01ha2luZyBwcmVkaWN0aW9ucyBmb3IgbW9kZWwgd2l0aCBzaWduaWZpY2FuY2UgdmFyaWFibGVzDQpwcmVkX2RhdDE9cHJlZGljdChtb2RlbF9zaWduaSx0ZXN0X2RhdFssYygiQXR0cml0aW9uIixuYW1lX3NpZ25pKV0sdHlwZSA9ICJyZXNwb25zZSIpDQojUGxvdHRpbmcgcm9jciBjdXJlIGZvciBvcHRpbXVtIHRocmVzaG9sZA0Kcm9jcl9jdXJ2ZShwcmVkX2RhdDEpDQpgYGANCkZyb20gdGUgYWJvdmUgcGxvdCB3ZSBjYW4gb2JzZXJ2ZSB0aGF0IHRoZSBvcHRpbXVtIHRocmVzaG9sZCB2YWx1ZSBsaWVzIGJldHdlZW4gMC4yLTAuMyAuU28gbm93IHdlIHdpbGwgZXZhbHVhdGUgbW9kZWwgcGVyZm9ybWFuY2UgdXNpbmcgdGhpcyB0aHJlc2hvbGQgdmFsdWUuDQoNCjxoMz5Nb2RlbCBQZXJmb3JtYW5jZTo8aDM+DQpFdmFsdWF0aW5nIG1vZGVsIHBlcmZvcm1hbmNlIHdpdGggc2lnbmlmaWNhbnQgdmFyaWFibGVzIG9ubHkuDQpgYGB7cn0NCiNtYWtpbmcgY29uZnVzaW9uIG1hdHJpeA0KcHJpbnQoIkNvbmZ1c2lvbiBNYXRyaXggb2YgbW9kZWwgd2l0aCBzaWduaWZpY2FudCB2YXJpYWJsZXMiKQ0KY29uZnVzX2RhdDI9dGFibGUodGVzdF9kYXQkQXR0cml0aW9uLHByZWRfZGF0MT4wLjI4KQ0KY29uZnVzX2RhdDINCm1vZGVsX3BlcmZvcm0oY29uZnUgPSBjb25mdXNfZGF0MikNCmBgYA0KPGgzPkNsYXNzaWZpY2F0aW9uIGFuZCBSZWdyZXNzaW9uIFRyZWVzIChDQVJUKTo8aDM+DQpgYGB7cn0NCiNNYWtpbmcgZGVjaXNpb24gdHJlcyBtb2RlbA0Kc2V0LnNlZWQoMTExMSkNCmR0X21vZGVsPXJwYXJ0KEF0dHJpdGlvbn4uLG1ldGhvZCA9ICJjbGFzcyIsZGF0YSA9IHRyYWluX2RhdCkNCmBgYA0KTm93IHdlIGNhbiBwcmVkaWN0IHRoZSB0ZXN0IGRhdGEgdXNpbmcgdGhpcyBtb2RlbCBhbmQgZXZhbHVhdGVzIGl0cyBwZXJmb3JtYW5jZSBhcyB3ZSBkaWQgaW4gbG9naXN0aWNzIHJlZ3Jlc3Npb24uDQpgYGB7cixmaWcuaGVpZ2h0PTgsZmlnLndpZHRoPTh9DQojTWFraW5nIHByZWRpY3Rpb25zDQpwcmVkX2RhdDI9YXMuZGF0YS5mcmFtZS5tYXRyaXgocHJlZGljdChkdF9tb2RlbCx0ZXN0X2RhdCx0eXBlID0gInByb2IiKSkNCnByZWRfZGF0Mj1wcmVkX2RhdDIkWWVzDQojUGxvdHRpbmcgcm9jciBjdXJ2ZSANCnJvY3JfY3VydmUocHJlZGkgPSBwcmVkX2RhdDIpDQpgYGANCkZyb20gdGhlIGFib3ZlIHdlIGNhbiBvYnNlcnZlIHRoYXQgb3B0aW11bSB0aHJlc2hvbGQgdmFsdWUgYmV0d2VlbiAwLjItMC4zIC5TbyBub3cgd2Ugd2lsbCBldmFsdWF0ZSB0aGUgbW9kZWwgcGVyZm9ybWFuY2UgdXNpbmcgdGhpcyB0aHJlc2hvbGQgdmFsdWUuDQoNCjxoMz5EZWNpc2lvbiBUcmVlIE1vZGVsIFBlcmZvcm1hbmNlOjxoMz4NCmBgYHtyfQ0KI01ha2luZyBjb25mdXNpb24gbWF0cml4DQpjb25mdV9kYXQzPXRhYmxlKHRlc3RfZGF0JEF0dHJpdGlvbixwcmVkX2RhdDI+MC4yKQ0KcHJpbnQoIkNvbmZ1c2lvbiBNYXRyaXggZm9yIERUIG1vZGVsIikNCmNvbmZ1X2RhdDMNCm1vZGVsX3BlcmZvcm0oY29uZnVfZGF0MykNCmBgYA0KQXMgd2UgY2FuIHNlZSBoZXJlIHRoZSBhY2N1cmFjeSBoYXMgYmVlbiBpbXByb3ZlZCBhIGxpdHRsZSBiaXQgYnV0IGFsc28gdGhlIHNwZWNpZmljaXR5IGhhcyBpbmNyZWFzZWQgcmVtYXJrYWJseSB3aGljaCBtZWFucyBsZXNzIGNoYW5jZXMgb2YgZXJyb3IgaW4gcHJlZGljdGluZyB0aGUgZW1wbG95ZWVzIHdobyB3aWxsICBub3QgY2h1cm4gYW5kIG1vcmUgY2hhbmNlcyBvZiBlcnJvciBpbiBwcmVkaWN0aW5nIGVtcGxveWVlcyB3aG8gd2lsbCBjaHVybi4NCg0KTm93IGxpa2Ugd2UgcGlja2VkIHRoZSBzaWduaWZpY2FudCB2YXJpYWJsZXMgaW4gbG9naXN0aWNzIHJlZ3Jlc3Npb24gd2Ugd2lsbCBkbyB0aGUgc2FtZSB3aXRoIERUIG1vZGVsIGFuZCBldmFsdWF0ZSB0aGUgbW9kZWwgcGVyZm9ybWFuY2UuDQo8aDM+RGVjaXNpb24gVHJlZSBNb2RlbCBCdWlsZGluZzo8aDM+DQpNb2RlbCBCdWlsZGluZyBpbmNsdWRpbmcgb25seSBpbXBvcnRhbnQgdmFyaWFibGVzLg0KYGBge3J9DQojUGlja2luZyBpbXBvcnRhbnQgdmFyaWFibGVzDQppbXBfdmFycj1uYW1lcyhzb3J0KGR0X21vZGVsJHZhcmlhYmxlLmltcG9ydGFuY2UsZGVjcmVhc2luZyA9IFRSVUUpKQ0KI01vZGVsIEJ1aWxkaW5nDQpkdF9tb2RlbDI9cnBhcnQoQXR0cml0aW9ufi4sbWV0aG9kID0gImNsYXNzIixkYXRhID0gdHJhaW5fZGF0WyxjKCJBdHRyaXRpb24iLGltcF92YXJyKV0pDQpgYGANCjxoMz5EZWNpc2lvbiBUcmVlIE1vZGVsIFBlcmZvcm1hbmNlOg0KRXZ1YWxhdGluZyBwZXJmb3JtYW5jZSBvZiBkZWNpc2lvbiB0cmVlIG1vZGVsIHdpdGggaW1wb3J0YW50IHZhcmlhYmxlcyBvbmx5Lg0KDQo8aDU+UHJlZGljaXRvbjxoNT4NCmBgYHtyLGZpZy5oZWlnaHQ9OCxmaWcud2lkdGg9OH0NCiNNYWtpbmcgcHJlZGljdGlvbg0KcHJlZF9kYXQzPWFzLmRhdGEuZnJhbWUubWF0cml4KHByZWRpY3QoZHRfbW9kZWwyLHRlc3RfZGF0WyxjKCJBdHRyaXRpb24iLGltcF92YXJyKV0sdHlwZSA9ICJwcm9iIikpDQpwcmVkX2RhdDM9cHJlZF9kYXQzJFllcw0KI3JvY3IgY3VydmUNCnJvY3JfY3VydmUocHJlZF9kYXQzKQ0KYGBgDQpIZXJlIHdlIGNhbiBvYnNlcnZlIHRoYXQgb3B0aW11bSB2YWx1ZSBvZiB0aHJlc2hvbGQgMC4zLiBOb3cgd2Ugd2lsbCBldmFsdWF0ZSB0aGUgbW9kZWwgcGVyZm9ybWFuY2UgdXNpbmcgb3B0aW11bSB0aHJlc2hvbGQuDQoNCmBgYHtyfQ0KI21ha2luZyBjaW5mdXNpb24gbWF0cml4IA0KY29uZnVfZGF0ND10YWJsZSh0ZXN0X2RhdCRBdHRyaXRpb24scHJlZF9kYXQzPjAuMikNCnByaW50KCJDb25mdXNpb24gbWF0cml4IGZvciBpbXBvcnRhbnQgdmFyaWFibGVzIikNCmNvbmZ1X2RhdDQNCm1vZGVsX3BlcmZvcm0oY29uZnVfZGF0NCkNCmBgYA0KYGBge3J9DQpwcnAoZHRfbW9kZWwyLHJvdW5kaW50ID0gRkFMU0UpDQpgYGANCkFib3ZlIHNob3duIGlzIHRoZSBkZWNpc2lvbiB0cmVlIGRpYWdyYW0gZm9yIGltcG9ydGFudCB2YXJpYWJsZXMuDQoNCjxoMz5GZWF0dXJlIEVuZ2luZWVyaW5nOjxoMz4NCk5vdyB3ZSB3aWxsIHRyeSB0byBpbnRyb2R1Y2UgbmV3IGZlYXR1cmVzIGFuZCBidWlsZCBhIG1vZGVsIHRvIGV2YWx1YXRlIGl0cyBwZXJmb3JtYW5jZS4NCmBgYHtyfQ0KI0NvcHlpbmcgdGVzdCB0cmFpbiBkYXRhDQp0cmFpbl9uZXdfZGF0PXRyYWluX2RhdA0KdGVzdF9uZXdfZGF0PXRlc3RfZGF0DQojTWFraW5nIG5ldyBmZWF0dXJlIGZvciBmaXJzdCBjb21wYW55DQpmaXJzdGNvbXBhbnk9YXR0X2RhdGEkTnVtQ29tcGFuaWVzV29ya2VkPT0xDQojTWFraW5nIGFub3RoZXIgZmVhdHVyZSBmb3IgbG95YWxpdHkNCmxveWFsaXR5PWF0dF9kYXRhJFllYXJzQXRDb21wYW55L2F0dF9kYXRhJFRvdGFsV29ya2luZ1llYXJzDQpsb3lhbGl0eVtpcy5uYShsb3lhbGl0eSldPTANCiMgTWFraW5nICB2b2xhdGlsaXR5IGFzIGEgbmV3IGZlYXR1cmUgDQp2b2xhdGlsaXR5ID0gYXR0X2RhdGEkVG90YWxXb3JraW5nWWVhcnMvYXR0X2RhdGEkTnVtQ29tcGFuaWVzV29ya2VkDQp2b2xhdGlsaXR5W2lzLmluZmluaXRlKHZvbGF0aWxpdHkpXT1hdHRfZGF0YSRUb3RhbFdvcmtpbmdZZWFyc1tpcy5pbmZpbml0ZSh2b2xhdGlsaXR5KV0NCiNBZGRpbmcgbmV3IGZlYXR1cmVzIGluIHRyYWluIHRlc3QgZGF0YQ0KdHJhaW5fbmV3X2RhdCRmaXJzdGNvbXBhbnk9Zmlyc3Rjb21wYW55W3NwbHQ9PVRSVUVdDQp0cmFpbl9uZXdfZGF0JGxveWFsaXR5PWxveWFsaXR5W3NwbHQ9PVRSVUVdDQp0cmFpbl9uZXdfZGF0JHZvbGF0aWxpdHk9dm9sYXRpbGl0eVtzcGx0PT1UUlVFXQ0KdGVzdF9uZXdfZGF0JGZpcnN0Y29tcGFueT1maXJzdGNvbXBhbnlbc3BsdD09RkFMU0VdDQp0ZXN0X25ld19kYXQkbG95YWxpdHk9bG95YWxpdHlbc3BsdD09RkFMU0VdDQp0ZXN0X25ld19kYXQkdm9sYXRpbGl0eT12b2xhdGlsaXR5W3NwbHQ9PUZBTFNFXQ0KI1BpY2tpbmcgbmV3IGZlYXR1cmVzDQpuZXdfZmVhdD1jKCJmaXJzdGNvbXBhbnkiLCJsb3lhbGl0eSIsInZvbGF0aWxpdHkiKQ0KYGBgDQpOb3cgYXMgd2UgaGF2ZSBjcmVhdGVkIHNvbWUgbmV3IGZlYXR1cmVzIHdlIGNhbiBidWlsZCBhIG5ldyBtb2RlbCBhbmQgY2hlY2sgaXRzIHBlcmZvcm1hbmNlDQoNCjxoMz5Nb2RlbCBCdWlsZGluZzo8aDM+DQpNb2RlbCBidWlsZGluZyB3aXRoIG5ldyBmZWF0dXJlIGFuZCB0aGUgc2lnbmlmaWNhbnQgZmVhdHVyZXMNCmBgYHtyfQ0KI01vZGVsIEJ1aWxkaW5nDQptb2RlbF9uZXdfZmVhdD1nbG0oQXR0cml0aW9ufi4sZGF0YSA9IHRyYWluX25ld19kYXRbLGMoIkF0dHJpdGlvbiIsbmFtZV9zaWduaSxuZXdfZmVhdCldLGZhbWlseSA9ICJiaW5vbWlhbCIpDQpzdW1tYXJ5KG1vZGVsX25ld19mZWF0KQ0KDQpgYGANCk5vdyB3ZSBjYW4gY2hlY2sgdGhlIG1vZGVsIHBlcmZvcm1hbmNlIGFuZCBjb21wYXJlIGl0IHdpdGggdGhlIG90aGVycy4NCg0KPGgzPk1vZGVsIHBlcmZvcm1hbmNlOjxoMz4NCkV2YWx1YXRpbmcgbW9kZWwgcGVyZm9ybWFuY2UgZm9yIG5ldyBmZWF0dXJlcyBhbmQgc2lnbmlmaWNhbnQgZmVhdHVyZXMuDQpgYGB7cixmaWcuaGVpZ2h0PTgsZmlnLndpZHRoPTh9DQojTWFraW5nIHByZWRpY3Rpb24NCnByZWRfZGF0ND1wcmVkaWN0KG1vZGVsX25ld19mZWF0LHRlc3RfbmV3X2RhdFssYygiQXR0cml0aW9uIixuYW1lX3NpZ25pLG5ld19mZWF0KV0sdHlwZSA9ICJyZXNwb25zZSIpDQojcGxvdHRpbmcgUm9jciBjdXJ2ZQ0Kcm9jcl9jdXJ2ZShwcmVkaSA9IHByZWRfZGF0NCkNCmBgYA0KSGVyZSB3ZSBjYW4gY2xlYXJseSBzZWUgdGhhdCB0aGUgb3B0aW11bSB0aHJlc2hvbGQgdmFsdWUgaXMgYmV0d2VlbiAwLjMgYW5kIDAuNCAuU28gbm93IHdlIHdpbGwgbWFrZSB0aGUgY29uZnVzaW9uIG1hdHJpeCB3aXRoIDAuMzUgYXMgdGhyZXNob2xkIHZhbHVlIGFuZCBldmFsdWF0ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVsDQpgYGB7cn0NCiNDb25mdXNpb24gbWF0cml4DQpjb25mdV9kYXRfbmV3PXRhYmxlKHRlc3RfbmV3X2RhdCRBdHRyaXRpb24scHJlZF9kYXQ0PjAuMzUpDQpwcmludCgiQ29uZnVzaW9uIG1hdHJpeCAiKQ0KY29uZnVfZGF0X25ldw0KI21vZGVsIHBlcmZvcm1hY2UNCm1vZGVsX3BlcmZvcm0oY29uZnVfZGF0X25ldykNCmBgYA0KPGgzPk1vZGVsIENvbXBhcmlzb246PGgzPg0KV2Ugd2lsbCBjb21wYXJlIHRoZSBwZXJmb3JtYW5jZSBvZiBlYWNoIG1vZGVsIHRvIHNld2xlY3QgdGhlIGJlc3QgbW9kZWwuDQoNCmBgYHtyfQ0KI01ha2luZyBmdW5jdGlvbnMNCnNlbnN0aT1mdW5jdGlvbih4KXsNCiAgICB0cDE9eFs0XQ0KdG90bF9wPXRwMSt4WzJdDQpzZW5zPXRwL3RvdGxfcA0Kc2Vucw0KfQ0Kc3BlY2lmPWZ1bmN0aW9uKHkpew0KICAgIHRuMT15WzFdDQp0b3RsX249dG4xK3lbM10NCnNwZWM9dG4xL3RvdGxfbg0Kc3BlYw0KfQ0KDQphY2M9ZnVuY3Rpb24oeil7DQogICAgYWNjX3o9c3VtKGRpYWcoeikpL3N1bSh6KQ0KICAgIGFjY3VyPXJvdW5kKGFjY196KjEwMCkNCiAgICBhY2N1cg0KfQ0KI3NlbnN0aXZpdHkNCnNlbnNfbG9nPXNlbnN0aShjb25mdXNfZGF0MSkNCnNlbnNfbG9nX3NpZ249c2Vuc3RpKGNvbmZ1c19kYXQyKQ0Kc2Vuc19EVD1TPXNlbnN0aShjb25mdV9kYXQzKQ0Kc2Vuc19EVF9pbXA9c2Vuc3RpKGNvbmZ1X2RhdDQpDQpzZW5zX2xvZ19uZXc9c2Vuc3RpKGNvbmZ1X2RhdF9uZXcpDQojc3BlY2lmaWNpdHkNCnNwZWNpX2xvZz1zcGVjaWYoY29uZnVzX2RhdDEpDQpzcGVjaV9sb2dfc2lnbj1zcGVjaWYoY29uZnVzX2RhdDIpDQpzcGVjaV9EVD1TPXNwZWNpZihjb25mdV9kYXQzKQ0Kc3BlY2lfRFRfaW1wPXNwZWNpZihjb25mdV9kYXQ0KQ0Kc3BlY2lfbG9nX25ldz1zcGVjaWYoY29uZnVfZGF0X25ldykNCiNBY2N1cmFjeQ0KYWNjX2xvZz1hY2MoY29uZnVzX2RhdDEpDQphY2NfbG9nX3NpZ249YWNjKGNvbmZ1c19kYXQyKQ0KYWNjX0RUPVM9YWNjKGNvbmZ1X2RhdDMpDQphY2NfRFRfaW1wPWFjYyhjb25mdV9kYXQ0KQ0KYWNjX2xvZ19uZXc9YWNjKGNvbmZ1X2RhdF9uZXcpDQojUGVyZm9ybWFuY2UgY29tcGFyaXNvbiB0YWJsZQ0KZGF0YS5mcmFtZShsaXN0KCJtb2RlbF9uYW1lIiA9IGMoImNhcnQgYWxsIHZhcmlhYmxlcyIsImNhcnQgaW1wb3J0YW50IHZhcmlhYmxlcyIsImxvZ2lzdGljIGFsbCB2YXJpYWJsZXMiLCJsb2dpc3RpYyBzaWduaWZpY2FudCB2YXJpYWJsZXMiLCJMb2dpc3RpYyB3aXRoIGZlYXR1cmUgZW5naW5lZXJpbmciKSwNCiAgICAgICAgICAgICAgICAiU2Vuc2l0aXZpdHkiID0gYyhzZW5zX0RULHNlbnNfRFRfaW1wLHNlbnNfbG9nLHNlbnNfbG9nX3NpZ24sc2Vuc19sb2dfbmV3KSwNCiAgICAgICAgICAgICAgICAiU3BlY2lmaWNpdHkiID0gYyhzcGVjaV9EVCxzcGVjaV9EVF9pbXAsc3BlY2lfbG9nLHNwZWNpX2xvZ19zaWduLHNwZWNpX2xvZ19uZXcpLA0KICAgICAgICAgICAgICAgIkFjY3VyYWN5IiA9IGMoYWNjX0RULGFjY19EVF9pbXAsYWNjX2xvZyxhY2NfbG9nX3NpZ24sYWNjX2xvZ19uZXcpKSkNCiAgICAgICAgICAgICAgIA0KDQpgYGANCkZyb20gdGhlIGFib3ZlIHN0YXRpc3RpY3Mgd2UgY29uY2x1ZGUgdGhhdCBmb3IgdGhpcyB1c2UgY2FzZToNCg0KLSBDQVJUIHdpdGggaW5jbHVzaW9uIG9mIG9ubHkgaW1wb3J0YW50IHZhcmlhYmxlcyBvdXRwZXJmb3JtcyBvdGhlciBtb2RlbHMgaW4gdGVybXMgb2Ygb3ZlcmFsbCBhY2N1cmFjeS4NCi0gSW5jbHVzaW9uIG9mIG5ldyBmZWF0dXJlcyBhZnRlciBmZWF0dXJlIGVuZ2luZWVyaW5nIGluY3JlYXNlZCB0aGUgYWNjdXJhY3kgb2YgTG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbC4NCg0KPGgzPk1vZGVsIFNlbGVjdGlvbjo8aDM+DQoNCkhlcmUgd2UgYXJlIGNvbnNpZGVyaW5nIHR3byBtYWluIGZhY3RvcnMgZm9yIG1vZGVsIHNlbGVjdGlvbjoNCg0KMSlBY2NvcmRpbmcgdG8gT2NjYW0gbGVhcm5pbmcgd2Ugc2hvdWxkIGNob29zZSB0aGUgbW9kZWwgd2l0aCBsZXNzIGNvbXBsZXhpdHkgKGkuZSBtb2RlbCBoYXZpbmcgb25seSBzdWZmaWNpZW50IHZhcmlhYmxlcyAgdG8gdW5kZXJzdGFuZCB0aGUgcGF0dGVybikgZXZlbiB0aG91Z2ggaWYgaXQgaXMgaGF2aW5nIGxpdHRsZSBsZXNzIGFjY3VyYWN5IHRoYW4gb3RoZXIgbW9kZWxzLg0KDQoyKVNlY29uZCBmYWN0b3IgZGVwZW5kcyB1cG9uIGJ1aXNuZXNzIGludHJlc3QgaWYgdGhleSBhcmUgY29uY2VybiBhYm91dCBzZW5zdGl2aXR5ICxzcGVjaWZpY2l0eSBvciB0b3RhbCBhY2N1cmFjeS5BcyBpbiB0aGlzIGNhc2UgY29tcGFueSBpcyBtb3JlIGNvbmNlcm5lZCBhYm91dCBmaW5kaW5nIG91dCB0aGUgZW1wbG95ZWVzIHdobyB3aWxsIGNodXJuLFNvIHdlIHNob3VsZCBjb25jZXJuIGZvciBzZW5zdGl2aXR5IHRvIGhhdmUgYSBoaWdoZXIgdmFsdWUgYW5kIGxlc3Mgc3BlY2lmaWNpdHkgdmFsdWUuDQoNCk5vdyBhZnRlciBjb25zaWRlcmluZyB0aGUgYWJvdmUgMiBwb2ludHMgd2UgY2FuIGNvbmNsdWRlIHRoYXQgImxvZ2lzdGljcyBtb2RlbCB3aXRoIHNpZ25pZmljYW50IHZhcmlhYmxlcyIgaXMgc2F0aXNmeWluZyBib3RoIHRoZSBhYm92ZSBtZW50aW9uZWQgc3RhdGVtZW50cyB3aXRob3V0IGEgcmVtYXJrYWJsZSBkZWNyZWFzZSBpbiB0aGUgYWNjdXJhY3kgaGVuY2UgaXMgdGhlIGJlc3QgbW9kZWwgb2YgYWxsIHRoZSBtb2RlbHMuDQoNCmBgYHtyLGZpZy5oZWlnaHQ9OCxmaWcud2lkdGg9OH0NCiNwbG90IGZvciBEZWNpc2lvbiBUcmVlDQpwbG90KHJvYyh0ZXN0X2RhdCRBdHRyaXRpb24sIHByZWRfZGF0MiksIHByaW50LmF1Yz1UUlVFLGNvbD0iYmxhY2siKQ0KI3Bsb3QgZm9yIERlY2lzaW9uIFRyZWUgd2l0aCBpbXBvcnRhbnQgdmFyaWFibGVzDQpwbG90KHJvYyh0ZXN0X2RhdCRBdHRyaXRpb24sIHByZWRfZGF0MyksIHByaW50LmF1YyA9IFRSVUUsY29sID0gImdyZWVuIiwgcHJpbnQuYXVjLnkgPSAuMSwgYWRkID0gVFJVRSkNCiNwbG90IGZvciBsb2dpc3RpY3MgcmVncmVzc2lvbiB3aXRoIHNpZ25pZmljYW50IHZhcmlhYmxlcw0KcGxvdChyb2ModGVzdF9kYXQkQXR0cml0aW9uLCBwcmVkX2RhdDEpLCBwcmludC5hdWMgPSBUUlVFLGNvbCA9ICJibHVlIiwgcHJpbnQuYXVjLnkgPSAuMiwgYWRkID0gVFJVRSkNCiNwbG90IGZvciBsb2dpc3RpY3MgcmVncmVzc2lvbiB3aXRoIGFsbCB2YXJpYWJsZXMNCnBsb3Qocm9jKHRlc3RfZGF0JEF0dHJpdGlvbiwgcHJlZGljdF9kYXQpLCBwcmludC5hdWMgPSBUUlVFLGNvbCA9ICJyZWQiLCBwcmludC5hdWMueSA9IC4zLCBhZGQgPSBUUlVFKQ0KI3Bsb3QgZm9yIGxvZ2lzdGljcyByZWdyZXNzaW9uIHdpdGggc2lnbmlmaWNhbnQgYW5kIGltcG9ydGFudCB2YXJpYWJsZXMNCnBsb3Qocm9jKHRlc3RfZGF0JEF0dHJpdGlvbiwgcHJlZF9kYXQ0KSwgcHJpbnQuYXVjID0gVFJVRSxjb2wgPSAicGluayIsIHByaW50LmF1Yy55ID0gLjQsIGFkZCA9IFRSVUUpDQpgYGANCg0K