Creating Data

The dataset for this machine learning task is created using below Java Program. The program creates two datasets:

public class mlCreateFeatures {

    private static String filePath="Input/";
    
    public static void main(String[] args) {
        
        String fileName = "MUTAG";//IMDB-BINARY , COLLAB , ENZYMES
        outPutFeatures(fileName);
        outPutFeaturesGraphVector(fileName);
        // Files are availble in Project folder /Output
    }

Description

  • GraphID: It is to identify and match to the input file. During machine learning task, this column will be ignored
  • Class: Our task to predict the value of class Label using training data.
  • Components: Integer value indicating how many connected component exists in the each Graph
  • AvgDegree : Average outgoing degree of all nodes
  • Density : Density of Graph ( see https://en.wikipedia.org/wiki/Dense_graph)
  • t-0…t-38 : corresponds to the array output of Graphlet search. (on ML-.csv datasets)
  • t-0…t-16 : corresponds to the array output of SimpleGraphVector.toArray . (on ML-GV-.csv datasets)

Using Data

We will do followings during our Machine learning process:
0. Prepare Environment
1. Import data
2. Data cleansing
3. Train Model
4. Predict
5. Analyse performance

0) Prepare Envirnoment

To do the analysis , we need to import related packages.

rm(list = ls(all = TRUE)) # Removing any exisiting variables
require(dplyr)
require(tidyr)
require(caret)
require(nnet)
require(pROC)
require(e1071)
require(rpart)
## For reproducible results we need to start with the same seed
set.seed(1333) 
if(.Platform$OS.type =="windows")
{
  setwd("D:/gitRepos/PerfAnalysis/")
  
}else {
  setwd("~/gitRepos/PerfAnalysis/")
  ##library(doMC)
  ##registerDoMC(cores = 4)
}
source('myMultiClassSummary.R')

1) Import data

We first read the csv files: ML-MUTAG.csv and ML-GV-MUTAG.csv . Here GV is short for GraphVector and GS is short for GraphletSearch

df.input.GS<-read.csv2(file="./input/ML-MUTAG.csv",
                       sep=";",
                       header = TRUE,
                       dec = ".")
df.input.GV<-read.csv2(file="./input/ML-GV-MUTAG.csv",
                       sep=";",
                       header = TRUE,
                       dec = ".")

Lets see the dimension of each data frame:

   Rows Columns
GS  188      47
GV  188      21

2) Data cleansing

  1. First we need to correct column data types in order our classification algorithms to work. So the GraphID and Class are of the type nominal (or in R called factor).
  2. Then we find the column with zero variance and remove it from dataset.
df.input.GV$GraphID <-as.factor(df.input.GV$GraphID );
df.input.GV$Class<-as.factor(make.names(df.input.GV$Class)); 
df.input.GS$GraphID <-as.factor(df.input.GS$GraphID );
df.input.GS$Class<-as.factor(make.names(df.input.GS$Class)); 
## Finding and removing columns with zero variance
nzv.GS<-nearZeroVar(df.input.GS)
nzv.GV<-nearZeroVar(df.input.GV)

Following columns/features have zero variance, which can be removed:

   Rows Columns
GS  188      47
GV  188      21

Columns for GS to be removed:  Components; d0; d1; d2; d3; d5; d6; d7; d8; d9; d10; d13; d14; d15; d16; d17; d20; d21; d22; d23; d25; d26; d27; d28; d29; d30; d31; d32; d33; d34; d35; d36; d37; d38; X
Columns for GV to be removed:  Components; t.0; t.11; t.12; t.13; t.16; X

Now let see the shape of our datasets after removing zero variance

   Rows Columns
GS  188      12
GV  188      14

Columns for GS:  GraphID; Class; Verticies; Edges; AvgDegree; Density; d4; d11; d12; d18; d19; d24
Columns for GV:  GraphID; Class; t.1; t.2; t.3; t.4; t.5; t.6; t.7; t.8; t.9; t.10; t.14; t.15

The dimensions corresponds to the following Graph type:

All type 3,4,5 Graphlets.

All type 3,4,5 Graphlets.

  1. Partitioning the datasets into train (70 % ) and test (30 %) for cross validation
##randomly partition data into two datasets ,training and testing
#GraphletSearch
inTrain.GS<-createDataPartition(y=df.input.GS$Class,p = .7,list=FALSE)
train.GS<-df.input.GS[inTrain.GS,]   
test.GS<- df.input.GS[-inTrain.GS,] 
#GraphVector
inTrain.GV<-createDataPartition(y=df.input.GV$Class,p = .7,list=FALSE)
train.GV<-df.input.GV[inTrain.GV,]   
test.GV<- df.input.GV[-inTrain.GV,] 

3) Train Model

Now we train following models:
* SVM
* SVM with Polynomial kernel
* SVM with Radial kernel
* Random forest

##Create a data frame from all combinations of the supplied parameters for Radial and linear Kernels
grid <- expand.grid(sigma = c(.01, .015, 0.2),
                    C = c(0.75, 0.9, 1, 1.1, 1.25))  
##Create a data frame from all combinations of the supplied parameters for Polynomial kernel
grid.poly <- expand.grid(C = c(0.75, 0.9, 1, 1.1, 1.25),
                         scale=c(.0001),
                         degree=1:2) 
#We need to check if it is binary classification 
#or multiclass classifcation, which requires diffrent summary function.
if(length(unique(df.input.GS$Class)) > 2) 
{
  ctrl <-trainControl(method="repeatedcv",
                      repeats=10,
                      classProbs=TRUE,
                      summaryFunction=myMultiClassSummary)
} else
{
  ctrl <- trainControl(method="repeatedcv",
                       repeats=10,
                       classProbs=TRUE,
                       summaryFunction=twoClassSummary)
}
## Parameters for Random Forest algorithm.
RF.control <- trainControl(method="repeatedcv", number=10, repeats=3)

Before training we will center and scale the features for better accuracy.

SVM linear

svmModel.linear.GS <- train(x=train.GS[,-c(1,2)],
                            y= train.GS[,2],
                            method = "svmLinear",
                            preProc = c("center","scale"),
                            trControl=ctrl)  
svmModel.linear.GV <- train(x=train.GV[,-c(1,2)],
                            y= train.GV[,2],
                            method = "svmLinear",
                            preProc = c("center","scale"),
                            trControl=ctrl)  

SVM with polynomial kernel

svmModel.Poly.GS <- train(x=train.GS[,-c(1,2)],
                          y= train.GS[,2], 
                          method = "svmPoly",
                          preProc = c("center","scale"),
                          tuneGrid = grid.poly,trControl=ctrl)  
svmModel.Poly.GV <- train(x=train.GV[,-c(1,2)],
                          y= train.GV[,2],
                          method = "svmPoly",
                          preProc = c("center","scale"),
                          tuneGrid = grid.poly,
                          trControl=ctrl)  

SVM with radial kernel

svmModel.Radial.GS <- train(x=train.GS[,-c(1,2)],
                            y= train.GS[,2],
                            method = "svmRadial",
                            preProc = c("center","scale"),
                            tuneGrid = grid,
                            trControl=ctrl)  
svmModel.Radial.GV <- train(x=train.GV[,-c(1,2)],
                            y= train.GV[,2],
                            method = "svmRadial",
                            preProc = c("center","scale"),
                            tuneGrid = grid,
                            trControl=ctrl)  

Random forest

#Random forest has its own naming preference
#GraphletSearch dataset
RfTrainGS<-cbind(y=train.GS[,2],train.GS[,-c(1,2)]) 
RfModelGS <- train(y~.,data=RfTrainGS,method = "rf",
                   preProcess=c("center","scale"),
                   trControl=RF.control,
                   prox=TRUE, 
                   tuneGrid=expand.grid(mtry = 5),
                   number=10,
                   ntree=500) 
#GraphVector dataset
RfTrainGV<-cbind(y=train.GV[,2],train.GV[,-c(1,2)]) 
RfModelGV <- train(y~.,data=RfTrainGV,method = "rf",
                   preProcess=c("center","scale"),
                   trControl=RF.control,
                   prox=TRUE,
                   tuneGrid=expand.grid(mtry = 5),
                   number=10,
                   ntree=500) 

4) Predict

We will predict the Class value for each Graph on test dataset so we can later compare to the actual Class with the predicted value and finally calculate performance.

## Predicting for different models for GraphletSearch test dataset
## -c(1,2) means removing GraphID,Class
prediction.y.linear.GS<-predict(svmModel.linear.GS,test.GS[,-c(1,2)]) 
prediction.y.Radial.GS<-predict(svmModel.Radial.GS,test.GS[,-c(1,2)])
prediction.y.Poly.GS  <-predict(svmModel.Poly.GS,test.GS[,-c(1,2)])
prediction.y.RF.GS<-predict(RfModelGS,test.GS[,-c(1,2)])
## Predicting for different models for GraphVector test dataset
prediction.y.linear.GV<-predict(svmModel.linear.GV,test.GV[,-c(1,2)])
prediction.y.Radial.GV<-predict(svmModel.Radial.GV,test.GV[,-c(1,2)])
prediction.y.Poly.GV  <-predict(svmModel.Poly.GV,test.GV[,-c(1,2)])
prediction.y.RF.GV<-predict(RfModelGV,test.GV[,-c(1,2)])

5) Analyse performance

First we calculate performance for each model, then we will put them in a table for each dataset:

confMatrix.linear.GS<-confusionMatrix(test.GS[,2],prediction.y.linear.GS)
confMatrix.linear.GV<-confusionMatrix(test.GV[,2],prediction.y.linear.GV)
confMatrix.radial.GS<-confusionMatrix(test.GS[,2],prediction.y.Radial.GS)
confMatrix.radial.GV<-confusionMatrix(test.GV[,2],prediction.y.Radial.GV)
confMatrix.poly.GS<-confusionMatrix(test.GS[,2],prediction.y.Poly.GS)
confMatrix.poly.GV<-confusionMatrix(test.GV[,2],prediction.y.Poly.GV)
confMatrix.rf.GS<-confusionMatrix(test.GS[,2],prediction.y.RF.GS)
confMatrix.rf.GV<-confusionMatrix(test.GV[,2],prediction.y.RF.GV)
acc.GS<-cbind(
        Statistic='Accuracy',
        SVM_Linear_GS=confMatrix.linear.GS$overall['Accuracy'],
        SVM_Radial_GS=confMatrix.radial.GS$overall['Accuracy'],
        SVM_Poly_GS=confMatrix.poly.GS$overall['Accuracy'],
        RandForest_GS=confMatrix.rf.GS$overall['Accuracy'])
kappa.GS<-cbind(
        Statistic='Kappa',
        SVM_Linear_GS=confMatrix.linear.GS$overall['Kappa'],
        SVM_Radial_GS=confMatrix.radial.GS$overall['Kappa'],
        SVM_Poly_GS=confMatrix.poly.GS$overall['Kappa'],
        RandForest_GS=confMatrix.rf.GS$overall['Kappa'])
acc.GV<-cbind(
        Statistic='Accuracy',
        SVM_Linear_GV=confMatrix.linear.GV$overall['Accuracy'],
        SVM_Radial_GV=confMatrix.radial.GV$overall['Accuracy'],
        SVM_Poly_GV=confMatrix.poly.GV$overall['Accuracy'],
        RandForest_GV=confMatrix.rf.GV$overall['Accuracy'])
kappa.GV<-cbind(
        Statistic='Kappa',
        SVM_Linear_GV=confMatrix.linear.GV$overall['Kappa'],
        SVM_Radial_GV=confMatrix.radial.GV$overall['Kappa'],
        SVM_Poly_GV=confMatrix.poly.GV$overall['Kappa'],
        RandForest_GV=confMatrix.rf.GV$overall['Kappa'])
comparison.GS<-as.data.frame(rbind(acc.GS,kappa.GS))
comparison.GV<-as.data.frame(rbind(acc.GV,kappa.GV))

Confusion matrix provides different information about performance of our model. As an Example for GraphletSearch dataset using Random forest:

Note: X1 means Class label “1” and X.1 refers to the Class label “-1” of the graph.

Confusion Matrix and Statistics

          Reference
Prediction X1 X.1
       X1  30   7
       X.1  3  15
                                         
               Accuracy : 0.8182         
                 95% CI : (0.691, 0.9092)
    No Information Rate : 0.6            
    P-Value [Acc > NIR] : 0.000462       
                                         
                  Kappa : 0.6094         
 Mcnemar's Test P-Value : 0.342782       
                                         
            Sensitivity : 0.9091         
            Specificity : 0.6818         
         Pos Pred Value : 0.8108         
         Neg Pred Value : 0.8333         
             Prevalence : 0.6000         
         Detection Rate : 0.5455         
   Detection Prevalence : 0.6727         
      Balanced Accuracy : 0.7955         
                                         
       'Positive' Class : X1             
                                         

Generally we can compare models on each dataset based on their accuracy and kappa value. For the same range of Accuracy, we would say the method with higher kappa value provides more reliability on the result.

Performance of GraphletSearch and GraphVector datasets shown below:

For GraphletSearch:

         SVM_Linear_GS       SVM_Radial_GS       SVM_Poly_GS         RandForest_GS      
Accuracy "0.854545454545454" "0.872727272727273" "0.872727272727273" "0.818181818181818"
Kappa    "0.66966966966967"  "0.715025906735751" "0.715025906735751" "0.609375"         


For GraphVector:

         SVM_Linear_GV       SVM_Radial_GV       SVM_Poly_GV         RandForest_GV      
Accuracy "0.890909090909091" "0.836363636363636" "0.836363636363636" "0.890909090909091"
Kappa    "0.759124087591241" "0.611764705882353" "0.63360473723168"  "0.752252252252252"

LS0tCnRpdGxlOiAnQ2xhc3NpZnlpbmcgR3JhcGggZGF0YXNldHMgdXNpbmcgTWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zIDogU1ZNIGFuZCBSYW5kb20KICBGb3Jlc3QnCmF1dGhvcjogIlJlemEgTmlydW1hbmQiCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0Ci0tLQoKIyBDcmVhdGluZyBEYXRhClRoZSBkYXRhc2V0IGZvciB0aGlzIG1hY2hpbmUgbGVhcm5pbmcgdGFzayBpcyBjcmVhdGVkIHVzaW5nIGJlbG93IEphdmEgUHJvZ3JhbS4gVGhlIHByb2dyYW0gY3JlYXRlcyB0d28gZGF0YXNldHM6ICAKCiogVXNpbmcgYWxsIGstR3JhcGhsZXRzICB3aXRoIGs9Myw0LDUgd2l0aCB0aGUgbmFtZSBjb252ZW50aW9uIE1MLTxmaWxlTmFtZT4uY3N2IC4gRm9yIGV4YW1wbGUgTUwtTVVUQUcuY3N2ICAKICAgQ29sdW1uIG5hbWVzOiBHcmFwaElEOyBDbGFzczsgQ29tcG9uZW50cztBdmdEZWdyZWU7IERlbnNpdHk7IFZlcnRpY2VzOyBFZGdlczsgdC0wOyB0LTEuLi4uLi50LTM3OyB0LTM4OyAgCgoqIFVzaW5nIFNpbXBsZUdyYXBoVmVjdG9yIHdpdGggdGhlIG5hbWUgY29udmVudGlvbiBNTC1HVi08ZmlsZU5hbWU+LmNzdi4gRm9yIEV4YW1wbGUgTUwtR1YtTVVUQUcuY3N2LiAgCiAgIENvbHVtbiBuYW1lczogR3JhcGhJRDsgQ2xhc3M7IENvbXBvbmVudHMgOyB0LTA7IHQtMS4uLi4uIHQtMTUgOyB0LTE2IDsgIAoKCmBgYHt9CnB1YmxpYyBjbGFzcyBtbENyZWF0ZUZlYXR1cmVzIHsKCglwcml2YXRlIHN0YXRpYyBTdHJpbmcgZmlsZVBhdGg9IklucHV0LyI7CgkKCXB1YmxpYyBzdGF0aWMgdm9pZCBtYWluKFN0cmluZ1tdIGFyZ3MpIHsKCQkKCQlTdHJpbmcgZmlsZU5hbWUgPSAiTVVUQUciOy8vSU1EQi1CSU5BUlkgLCBDT0xMQUIgLCBFTlpZTUVTCgkJb3V0UHV0RmVhdHVyZXMoZmlsZU5hbWUpOwoJCW91dFB1dEZlYXR1cmVzR3JhcGhWZWN0b3IoZmlsZU5hbWUpOwoJCS8vIEZpbGVzIGFyZSBhdmFpbGJsZSBpbiBQcm9qZWN0IGZvbGRlciAvT3V0cHV0Cgl9CmBgYAoKIyMgRGVzY3JpcHRpb24KKiBHcmFwaElEOiBJdCBpcyB0byBpZGVudGlmeSBhbmQgbWF0Y2ggdG8gdGhlIGlucHV0IGZpbGUuIER1cmluZyBtYWNoaW5lIGxlYXJuaW5nIHRhc2ssIHRoaXMgY29sdW1uIHdpbGwgYmUgaWdub3JlZAoqIENsYXNzOiBPdXIgdGFzayB0byBwcmVkaWN0IHRoZSB2YWx1ZSBvZiBjbGFzcyBMYWJlbCB1c2luZyB0cmFpbmluZyBkYXRhLgoqIENvbXBvbmVudHM6IEludGVnZXIgdmFsdWUgaW5kaWNhdGluZyBob3cgbWFueSBjb25uZWN0ZWQgY29tcG9uZW50IGV4aXN0cyBpbiB0aGUgZWFjaCBHcmFwaCAKKiBBdmdEZWdyZWUgOiBBdmVyYWdlIG91dGdvaW5nIGRlZ3JlZSBvZiBhbGwgbm9kZXMgIAoqIERlbnNpdHkgOiBEZW5zaXR5IG9mIEdyYXBoICggc2VlIGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0RlbnNlX2dyYXBoKQoqIHQtMC4uLnQtMzggOiBjb3JyZXNwb25kcyB0byB0aGUgYXJyYXkgb3V0cHV0IG9mIEdyYXBobGV0IHNlYXJjaC4gKG9uIE1MLTxmaWxlTmFtZT4uY3N2IGRhdGFzZXRzKQoqIHQtMC4uLnQtMTYgOiBjb3JyZXNwb25kcyB0byB0aGUgYXJyYXkgb3V0cHV0IG9mIFNpbXBsZUdyYXBoVmVjdG9yLnRvQXJyYXkgLiAob24gTUwtR1YtPGZpbGVOYW1lPi5jc3YgZGF0YXNldHMpCgoKIyBVc2luZyBEYXRhCldlIHdpbGwgZG8gZm9sbG93aW5ncyBkdXJpbmcgb3VyIE1hY2hpbmUgbGVhcm5pbmcgcHJvY2VzczogIAowLiBQcmVwYXJlIEVudmlyb25tZW50ICAKMS4gSW1wb3J0IGRhdGEgICAKMi4gRGF0YSBjbGVhbnNpbmcgIAozLiBUcmFpbiBNb2RlbCAgIAo0LiBQcmVkaWN0ICAgCjUuIEFuYWx5c2UgcGVyZm9ybWFuY2UgICAKCiMjIDApIFByZXBhcmUgRW52aXJub21lbnQKVG8gZG8gdGhlIGFuYWx5c2lzICwgd2UgbmVlZCB0byBpbXBvcnQgcmVsYXRlZCBwYWNrYWdlcy4gIApgYGB7ciBlbnYsbWVzc2FnZT1GQUxTRSxlY2hvPVRSVUUscmVzdWx0cz0naGlkZScsZXJyb3I9RkFMU0V9CnJtKGxpc3QgPSBscyhhbGwgPSBUUlVFKSkgIyBSZW1vdmluZyBhbnkgZXhpc2l0aW5nIHZhcmlhYmxlcwoKcmVxdWlyZShkcGx5cikKcmVxdWlyZSh0aWR5cikKcmVxdWlyZShjYXJldCkKcmVxdWlyZShubmV0KQpyZXF1aXJlKHBST0MpCnJlcXVpcmUoZTEwNzEpCnJlcXVpcmUocnBhcnQpCgojIyBGb3IgcmVwcm9kdWNpYmxlIHJlc3VsdHMgd2UgbmVlZCB0byBzdGFydCB3aXRoIHRoZSBzYW1lIHNlZWQKc2V0LnNlZWQoMTMzMykgCmBgYAoKYGBge3IgbWVzc2FnZT1GQUxTRSxlY2hvPVRSVUUscmVzdWx0cz0naGlkZScsZXJyb3I9RkFMU0V9CmlmKC5QbGF0Zm9ybSRPUy50eXBlID09IndpbmRvd3MiKQp7CiAgc2V0d2QoIkQ6L2dpdFJlcG9zL1BlcmZBbmFseXNpcy8iKQogIAp9ZWxzZSB7CiAgc2V0d2QoIn4vZ2l0UmVwb3MvUGVyZkFuYWx5c2lzLyIpCiAgIyNsaWJyYXJ5KGRvTUMpCiAgIyNyZWdpc3RlckRvTUMoY29yZXMgPSA0KQp9CnNvdXJjZSgnbXlNdWx0aUNsYXNzU3VtbWFyeS5SJykKYGBgCiMjIDEpIEltcG9ydCBkYXRhCldlIGZpcnN0IHJlYWQgdGhlIGNzdiBmaWxlczogIE1MLU1VVEFHLmNzdiBhbmQgTUwtR1YtTVVUQUcuY3N2IC4KSGVyZSBHViBpcyBzaG9ydCBmb3IgR3JhcGhWZWN0b3IgYW5kIEdTIGlzIHNob3J0IGZvciBHcmFwaGxldFNlYXJjaAoKYGBge3J9CmRmLmlucHV0LkdTPC1yZWFkLmNzdjIoZmlsZT0iLi9pbnB1dC9NTC1NVVRBRy5jc3YiLAogICAgICAgICAgICAgICAgICAgICAgIHNlcD0iOyIsCiAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgICBkZWMgPSAiLiIpCgpkZi5pbnB1dC5HVjwtcmVhZC5jc3YyKGZpbGU9Ii4vaW5wdXQvTUwtR1YtTVVUQUcuY3N2IiwKICAgICAgICAgICAgICAgICAgICAgICBzZXA9IjsiLAogICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICAgZGVjID0gIi4iKQpgYGAKTGV0cyBzZWUgdGhlIGRpbWVuc2lvbiBvZiBlYWNoIGRhdGEgZnJhbWU6CgpgYGB7ciBlY2hvPUZBTFNFIH0KY29tcGFyZTwtcmJpbmQoR1M9ZGltKGRmLmlucHV0LkdTKSxHVj1kaW0oZGYuaW5wdXQuR1YpKQpjb2xuYW1lcyhjb21wYXJlKTwtYygiUm93cyIsIkNvbHVtbnMiKQoKcHJpbnQoY29tcGFyZSkKYGBgCgojIyAyKSBEYXRhIGNsZWFuc2luZyAgCjEuIEZpcnN0IHdlIG5lZWQgdG8gY29ycmVjdCBjb2x1bW4gZGF0YSB0eXBlcyBpbiBvcmRlciBvdXIgY2xhc3NpZmljYXRpb24gYWxnb3JpdGhtcyB0byB3b3JrLiBTbyB0aGUgR3JhcGhJRCBhbmQgQ2xhc3MgYXJlIG9mIHRoZSB0eXBlIG5vbWluYWwgKG9yIGluIFIgY2FsbGVkIGZhY3RvcikuICAKMi4gVGhlbiB3ZSBmaW5kIHRoZSBjb2x1bW4gd2l0aCB6ZXJvIHZhcmlhbmNlIGFuZCByZW1vdmUgaXQgZnJvbSBkYXRhc2V0LiAgCgpgYGB7ciB0aWR5PUZBTFNFLHdhcm5pbmc9RkFMU0V9CmRmLmlucHV0LkdWJEdyYXBoSUQgPC1hcy5mYWN0b3IoZGYuaW5wdXQuR1YkR3JhcGhJRCApOwpkZi5pbnB1dC5HViRDbGFzczwtYXMuZmFjdG9yKG1ha2UubmFtZXMoZGYuaW5wdXQuR1YkQ2xhc3MpKTsgCgpkZi5pbnB1dC5HUyRHcmFwaElEIDwtYXMuZmFjdG9yKGRmLmlucHV0LkdTJEdyYXBoSUQgKTsKZGYuaW5wdXQuR1MkQ2xhc3M8LWFzLmZhY3RvcihtYWtlLm5hbWVzKGRmLmlucHV0LkdTJENsYXNzKSk7IAoKIyMgRmluZGluZyBhbmQgcmVtb3ZpbmcgY29sdW1ucyB3aXRoIHplcm8gdmFyaWFuY2UKbnp2LkdTPC1uZWFyWmVyb1ZhcihkZi5pbnB1dC5HUykKbnp2LkdWPC1uZWFyWmVyb1ZhcihkZi5pbnB1dC5HVikKCmBgYAoKRm9sbG93aW5nIGNvbHVtbnMvZmVhdHVyZXMgaGF2ZSB6ZXJvIHZhcmlhbmNlLCB3aGljaCBjYW4gYmUgcmVtb3ZlZDogIAoKYGBge3IgZWNobz1GQUxTRSx0aWR5PUZBTFNFLHdhcm5pbmc9RkFMU0V9CnByaW50KGNvbXBhcmUpCmNhdCgiXG5Db2x1bW5zIGZvciBHUyB0byBiZSByZW1vdmVkOiAgIikKY2F0KHBhc3RlKG5hbWVzKGRmLmlucHV0LkdTWyxuenYuR1NdKSxjb2xsYXBzZT0iOyAiKSkKY2F0KCJcbkNvbHVtbnMgZm9yIEdWIHRvIGJlIHJlbW92ZWQ6ICAiKQpjYXQocGFzdGUobmFtZXMoZGYuaW5wdXQuR1ZbLG56di5HVl0pLGNvbGxhcHNlPSI7ICIpKQoKZGYuaW5wdXQuR1M8LWRmLmlucHV0LkdTWywtbnp2LkdTXQpkZi5pbnB1dC5HVjwtZGYuaW5wdXQuR1ZbLC1uenYuR1ZdCgpgYGAKTm93IGxldCBzZWUgdGhlIHNoYXBlIG9mIG91ciBkYXRhc2V0cyBhZnRlciByZW1vdmluZyB6ZXJvIHZhcmlhbmNlCgpgYGB7ciBlY2hvPUZBTFNFLHRpZHk9RkFMU0Usd2FybmluZz1GQUxTRSB9Cgpjb21wYXJlPC1yYmluZChHUz1kaW0oZGYuaW5wdXQuR1MpLEdWPWRpbShkZi5pbnB1dC5HVikpCmNvbG5hbWVzKGNvbXBhcmUpPC1jKCJSb3dzIiwiQ29sdW1ucyIpCgpwcmludChjb21wYXJlKQpjYXQoIlxuQ29sdW1ucyBmb3IgR1M6ICAiKQpjYXQocGFzdGUobmFtZXMoZGYuaW5wdXQuR1MpLGNvbGxhcHNlPSI7ICIpKQpjYXQoIlxuQ29sdW1ucyBmb3IgR1Y6ICAiKQpjYXQocGFzdGUobmFtZXMoZGYuaW5wdXQuR1YpLGNvbGxhcHNlPSI7ICIpKQpgYGAKClRoZSBkaW1lbnNpb25zIGNvcnJlc3BvbmRzIHRvIHRoZSBmb2xsb3dpbmcgR3JhcGggdHlwZTogIAoKIVtBbGwgdHlwZSAzLDQsNSBHcmFwaGxldHMuXShncmFwaGxldHNSZWYuUE5HKQoKMy4gUGFydGl0aW9uaW5nIHRoZSBkYXRhc2V0cyBpbnRvIHRyYWluICg3MCAlICkgYW5kIHRlc3QgKDMwICUpIGZvciBjcm9zcyB2YWxpZGF0aW9uCgpgYGB7ciB9CiMjcmFuZG9tbHkgcGFydGl0aW9uIGRhdGEgaW50byB0d28gZGF0YXNldHMgLHRyYWluaW5nIGFuZCB0ZXN0aW5nCiNHcmFwaGxldFNlYXJjaAppblRyYWluLkdTPC1jcmVhdGVEYXRhUGFydGl0aW9uKHk9ZGYuaW5wdXQuR1MkQ2xhc3MscCA9IC43LGxpc3Q9RkFMU0UpCnRyYWluLkdTPC1kZi5pbnB1dC5HU1tpblRyYWluLkdTLF0gICAKdGVzdC5HUzwtIGRmLmlucHV0LkdTWy1pblRyYWluLkdTLF0gCgojR3JhcGhWZWN0b3IKaW5UcmFpbi5HVjwtY3JlYXRlRGF0YVBhcnRpdGlvbih5PWRmLmlucHV0LkdWJENsYXNzLHAgPSAuNyxsaXN0PUZBTFNFKQp0cmFpbi5HVjwtZGYuaW5wdXQuR1ZbaW5UcmFpbi5HVixdICAgCnRlc3QuR1Y8LSBkZi5pbnB1dC5HVlstaW5UcmFpbi5HVixdIAoKYGBgCgojIyAzKSBUcmFpbiBNb2RlbCAgCk5vdyB3ZSB0cmFpbiBmb2xsb3dpbmcgbW9kZWxzOiAgCiogU1ZNICAgCiogU1ZNIHdpdGggUG9seW5vbWlhbCBrZXJuZWwgIAoqIFNWTSB3aXRoIFJhZGlhbCBrZXJuZWwgIAoqIFJhbmRvbSBmb3Jlc3QgIAoKYGBge3IgcGFyYW1ldGVycyx0aWR5PUZBTFNFfQoKIyNDcmVhdGUgYSBkYXRhIGZyYW1lIGZyb20gYWxsIGNvbWJpbmF0aW9ucyBvZiB0aGUgc3VwcGxpZWQgcGFyYW1ldGVycyBmb3IgUmFkaWFsIGFuZCBsaW5lYXIgS2VybmVscwpncmlkIDwtIGV4cGFuZC5ncmlkKHNpZ21hID0gYyguMDEsIC4wMTUsIDAuMiksCiAgICAgICAgICAgICAgICAgICAgQyA9IGMoMC43NSwgMC45LCAxLCAxLjEsIDEuMjUpKSAgCgojI0NyZWF0ZSBhIGRhdGEgZnJhbWUgZnJvbSBhbGwgY29tYmluYXRpb25zIG9mIHRoZSBzdXBwbGllZCBwYXJhbWV0ZXJzIGZvciBQb2x5bm9taWFsIGtlcm5lbApncmlkLnBvbHkgPC0gZXhwYW5kLmdyaWQoQyA9IGMoMC43NSwgMC45LCAxLCAxLjEsIDEuMjUpLAogICAgICAgICAgICAgICAgICAgICAgICAgc2NhbGU9YyguMDAwMSksCiAgICAgICAgICAgICAgICAgICAgICAgICBkZWdyZWU9MToyKSAKCiNXZSBuZWVkIHRvIGNoZWNrIGlmIGl0IGlzIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiAKI29yIG11bHRpY2xhc3MgY2xhc3NpZmNhdGlvbiwgd2hpY2ggcmVxdWlyZXMgZGlmZnJlbnQgc3VtbWFyeSBmdW5jdGlvbi4KaWYobGVuZ3RoKHVuaXF1ZShkZi5pbnB1dC5HUyRDbGFzcykpID4gMikgCnsKICBjdHJsIDwtdHJhaW5Db250cm9sKG1ldGhvZD0icmVwZWF0ZWRjdiIsCiAgICAgICAgICAgICAgICAgICAgICByZXBlYXRzPTEwLAogICAgICAgICAgICAgICAgICAgICAgY2xhc3NQcm9icz1UUlVFLAogICAgICAgICAgICAgICAgICAgICAgc3VtbWFyeUZ1bmN0aW9uPW15TXVsdGlDbGFzc1N1bW1hcnkpCn0gZWxzZQp7CiAgY3RybCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSJyZXBlYXRlZGN2IiwKICAgICAgICAgICAgICAgICAgICAgICByZXBlYXRzPTEwLAogICAgICAgICAgICAgICAgICAgICAgIGNsYXNzUHJvYnM9VFJVRSwKICAgICAgICAgICAgICAgICAgICAgICBzdW1tYXJ5RnVuY3Rpb249dHdvQ2xhc3NTdW1tYXJ5KQp9CgojIyBQYXJhbWV0ZXJzIGZvciBSYW5kb20gRm9yZXN0IGFsZ29yaXRobS4KUkYuY29udHJvbCA8LSB0cmFpbkNvbnRyb2wobWV0aG9kPSJyZXBlYXRlZGN2IiwgbnVtYmVyPTEwLCByZXBlYXRzPTMpCgpgYGAKCkJlZm9yZSB0cmFpbmluZyB3ZSB3aWxsIGNlbnRlciBhbmQgc2NhbGUgdGhlIGZlYXR1cmVzIGZvciBiZXR0ZXIgYWNjdXJhY3kuCgojIyMgU1ZNIGxpbmVhciAgCmBgYHtyIHN2bUxpbmVhciwgbWVzc2FnZT1GQUxTRSxlcnJvcj1GQUxTRSxyZXN1bHRzPSdoaWRlJyxjYWNoZT1UUlVFLHRpZHk9RkFMU0Usd2FybmluZz1GQUxTRX0Kc3ZtTW9kZWwubGluZWFyLkdTIDwtIHRyYWluKHg9dHJhaW4uR1NbLC1jKDEsMildLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgeT0gdHJhaW4uR1NbLDJdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bUxpbmVhciIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVQcm9jID0gYygiY2VudGVyIiwic2NhbGUiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbD1jdHJsKSAgCgpzdm1Nb2RlbC5saW5lYXIuR1YgPC0gdHJhaW4oeD10cmFpbi5HVlssLWMoMSwyKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB5PSB0cmFpbi5HVlssMl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtTGluZWFyIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZVByb2MgPSBjKCJjZW50ZXIiLCJzY2FsZSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sPWN0cmwpICAKYGBgCgojIyMgU1ZNIHdpdGggcG9seW5vbWlhbCBrZXJuZWwgIApgYGB7ciBzdm1Qb2x5LG1lc3NhZ2U9RkFMU0UsZXJyb3I9RkFMU0UscmVzdWx0cz0naGlkZScsY2FjaGU9VFJVRSx0aWR5PUZBTFNFLHdhcm5pbmc9RkFMU0V9CnN2bU1vZGVsLlBvbHkuR1MgPC0gdHJhaW4oeD10cmFpbi5HU1ssLWMoMSwyKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgeT0gdHJhaW4uR1NbLDJdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUG9seSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgcHJlUHJvYyA9IGMoImNlbnRlciIsInNjYWxlIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBncmlkLnBvbHksdHJDb250cm9sPWN0cmwpICAKCnN2bU1vZGVsLlBvbHkuR1YgPC0gdHJhaW4oeD10cmFpbi5HVlssLWMoMSwyKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgeT0gdHJhaW4uR1ZbLDJdLAogICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJzdm1Qb2x5IiwKICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVQcm9jID0gYygiY2VudGVyIiwic2NhbGUiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGdyaWQucG9seSwKICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2w9Y3RybCkgIApgYGAKIyMjIFNWTSB3aXRoIHJhZGlhbCBrZXJuZWwgIApgYGB7ciBzdm1SYWRpYWwsbWVzc2FnZT1GQUxTRSxlcnJvcj1GQUxTRSxyZXN1bHRzPSdoaWRlJyxjYWNoZT1UUlVFLHRpZHk9RkFMU0Usd2FybmluZz1GQUxTRX0Kc3ZtTW9kZWwuUmFkaWFsLkdTIDwtIHRyYWluKHg9dHJhaW4uR1NbLC1jKDEsMildLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgeT0gdHJhaW4uR1NbLDJdLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bVJhZGlhbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVQcm9jID0gYygiY2VudGVyIiwic2NhbGUiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gZ3JpZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbD1jdHJsKSAgCgpzdm1Nb2RlbC5SYWRpYWwuR1YgPC0gdHJhaW4oeD10cmFpbi5HVlssLWMoMSwyKV0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB5PSB0cmFpbi5HVlssMl0sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUmFkaWFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZVByb2MgPSBjKCJjZW50ZXIiLCJzY2FsZSIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBncmlkLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sPWN0cmwpICAKYGBgCgojIFJhbmRvbSBmb3Jlc3QgIApgYGB7ciBSYW5kb21Gb3Jlc3QsbWVzc2FnZT1GQUxTRSxlcnJvcj1GQUxTRSxyZXN1bHRzPSdoaWRlJyxjYWNoZT1UUlVFLHRpZHk9RkFMU0Usd2FybmluZz1GQUxTRX0KI1JhbmRvbSBmb3Jlc3QgaGFzIGl0cyBvd24gbmFtaW5nIHByZWZlcmVuY2UKI0dyYXBobGV0U2VhcmNoIGRhdGFzZXQKUmZUcmFpbkdTPC1jYmluZCh5PXRyYWluLkdTWywyXSx0cmFpbi5HU1ssLWMoMSwyKV0pIAoKUmZNb2RlbEdTIDwtIHRyYWluKHl+LixkYXRhPVJmVHJhaW5HUyxtZXRob2QgPSAicmYiLAogICAgICAgICAgICAgICAgICAgcHJlUHJvY2Vzcz1jKCJjZW50ZXIiLCJzY2FsZSIpLAogICAgICAgICAgICAgICAgICAgdHJDb250cm9sPVJGLmNvbnRyb2wsCiAgICAgICAgICAgICAgICAgICBwcm94PVRSVUUsIAogICAgICAgICAgICAgICAgICAgdHVuZUdyaWQ9ZXhwYW5kLmdyaWQobXRyeSA9IDUpLAogICAgICAgICAgICAgICAgICAgbnVtYmVyPTEwLAogICAgICAgICAgICAgICAgICAgbnRyZWU9NTAwKSAKCiNHcmFwaFZlY3RvciBkYXRhc2V0ClJmVHJhaW5HVjwtY2JpbmQoeT10cmFpbi5HVlssMl0sdHJhaW4uR1ZbLC1jKDEsMildKSAKUmZNb2RlbEdWIDwtIHRyYWluKHl+LixkYXRhPVJmVHJhaW5HVixtZXRob2QgPSAicmYiLAogICAgICAgICAgICAgICAgICAgcHJlUHJvY2Vzcz1jKCJjZW50ZXIiLCJzY2FsZSIpLAogICAgICAgICAgICAgICAgICAgdHJDb250cm9sPVJGLmNvbnRyb2wsCiAgICAgICAgICAgICAgICAgICBwcm94PVRSVUUsCiAgICAgICAgICAgICAgICAgICB0dW5lR3JpZD1leHBhbmQuZ3JpZChtdHJ5ID0gNSksCiAgICAgICAgICAgICAgICAgICBudW1iZXI9MTAsCiAgICAgICAgICAgICAgICAgICBudHJlZT01MDApIAoKYGBgCgojIyA0KSBQcmVkaWN0ICAKV2Ugd2lsbCBwcmVkaWN0IHRoZSBDbGFzcyB2YWx1ZSBmb3IgZWFjaCBHcmFwaCBvbiB0ZXN0IGRhdGFzZXQgc28gd2UgY2FuIGxhdGVyIGNvbXBhcmUgdG8gdGhlIGFjdHVhbCBDbGFzcyB3aXRoIHRoZSBwcmVkaWN0ZWQgdmFsdWUgYW5kIGZpbmFsbHkgY2FsY3VsYXRlIHBlcmZvcm1hbmNlLgpgYGB7ciB0aWR5PUZBTFNFLG1lc3NhZ2U9RkFMU0UsZXJyb3I9RkFMU0Usd2FybmluZz1GQUxTRX0KIyMgUHJlZGljdGluZyBmb3IgZGlmZmVyZW50IG1vZGVscyBmb3IgR3JhcGhsZXRTZWFyY2ggdGVzdCBkYXRhc2V0CiMjIC1jKDEsMikgbWVhbnMgcmVtb3ZpbmcgR3JhcGhJRCxDbGFzcwpwcmVkaWN0aW9uLnkubGluZWFyLkdTPC1wcmVkaWN0KHN2bU1vZGVsLmxpbmVhci5HUyx0ZXN0LkdTWywtYygxLDIpXSkgCnByZWRpY3Rpb24ueS5SYWRpYWwuR1M8LXByZWRpY3Qoc3ZtTW9kZWwuUmFkaWFsLkdTLHRlc3QuR1NbLC1jKDEsMildKQpwcmVkaWN0aW9uLnkuUG9seS5HUyAgPC1wcmVkaWN0KHN2bU1vZGVsLlBvbHkuR1MsdGVzdC5HU1ssLWMoMSwyKV0pCnByZWRpY3Rpb24ueS5SRi5HUzwtcHJlZGljdChSZk1vZGVsR1MsdGVzdC5HU1ssLWMoMSwyKV0pCgojIyBQcmVkaWN0aW5nIGZvciBkaWZmZXJlbnQgbW9kZWxzIGZvciBHcmFwaFZlY3RvciB0ZXN0IGRhdGFzZXQKcHJlZGljdGlvbi55LmxpbmVhci5HVjwtcHJlZGljdChzdm1Nb2RlbC5saW5lYXIuR1YsdGVzdC5HVlssLWMoMSwyKV0pCnByZWRpY3Rpb24ueS5SYWRpYWwuR1Y8LXByZWRpY3Qoc3ZtTW9kZWwuUmFkaWFsLkdWLHRlc3QuR1ZbLC1jKDEsMildKQpwcmVkaWN0aW9uLnkuUG9seS5HViAgPC1wcmVkaWN0KHN2bU1vZGVsLlBvbHkuR1YsdGVzdC5HVlssLWMoMSwyKV0pCnByZWRpY3Rpb24ueS5SRi5HVjwtcHJlZGljdChSZk1vZGVsR1YsdGVzdC5HVlssLWMoMSwyKV0pCmBgYAoKIyMgNSkgQW5hbHlzZSBwZXJmb3JtYW5jZSAgCkZpcnN0IHdlIGNhbGN1bGF0ZSBwZXJmb3JtYW5jZSBmb3IgZWFjaCBtb2RlbCwgdGhlbiB3ZSB3aWxsIHB1dCB0aGVtIGluIGEgdGFibGUgZm9yIGVhY2ggZGF0YXNldDoKYGBge3J9CmNvbmZNYXRyaXgubGluZWFyLkdTPC1jb25mdXNpb25NYXRyaXgodGVzdC5HU1ssMl0scHJlZGljdGlvbi55LmxpbmVhci5HUykKY29uZk1hdHJpeC5saW5lYXIuR1Y8LWNvbmZ1c2lvbk1hdHJpeCh0ZXN0LkdWWywyXSxwcmVkaWN0aW9uLnkubGluZWFyLkdWKQoKY29uZk1hdHJpeC5yYWRpYWwuR1M8LWNvbmZ1c2lvbk1hdHJpeCh0ZXN0LkdTWywyXSxwcmVkaWN0aW9uLnkuUmFkaWFsLkdTKQpjb25mTWF0cml4LnJhZGlhbC5HVjwtY29uZnVzaW9uTWF0cml4KHRlc3QuR1ZbLDJdLHByZWRpY3Rpb24ueS5SYWRpYWwuR1YpCgpjb25mTWF0cml4LnBvbHkuR1M8LWNvbmZ1c2lvbk1hdHJpeCh0ZXN0LkdTWywyXSxwcmVkaWN0aW9uLnkuUG9seS5HUykKY29uZk1hdHJpeC5wb2x5LkdWPC1jb25mdXNpb25NYXRyaXgodGVzdC5HVlssMl0scHJlZGljdGlvbi55LlBvbHkuR1YpCgpjb25mTWF0cml4LnJmLkdTPC1jb25mdXNpb25NYXRyaXgodGVzdC5HU1ssMl0scHJlZGljdGlvbi55LlJGLkdTKQpjb25mTWF0cml4LnJmLkdWPC1jb25mdXNpb25NYXRyaXgodGVzdC5HVlssMl0scHJlZGljdGlvbi55LlJGLkdWKQoKYWNjLkdTPC1jYmluZCgKICAgICAgICBTdGF0aXN0aWM9J0FjY3VyYWN5JywKICAgICAgICBTVk1fTGluZWFyX0dTPWNvbmZNYXRyaXgubGluZWFyLkdTJG92ZXJhbGxbJ0FjY3VyYWN5J10sCiAgICAgICAgU1ZNX1JhZGlhbF9HUz1jb25mTWF0cml4LnJhZGlhbC5HUyRvdmVyYWxsWydBY2N1cmFjeSddLAogICAgICAgIFNWTV9Qb2x5X0dTPWNvbmZNYXRyaXgucG9seS5HUyRvdmVyYWxsWydBY2N1cmFjeSddLAogICAgICAgIFJhbmRGb3Jlc3RfR1M9Y29uZk1hdHJpeC5yZi5HUyRvdmVyYWxsWydBY2N1cmFjeSddKQoKa2FwcGEuR1M8LWNiaW5kKAogICAgICAgIFN0YXRpc3RpYz0nS2FwcGEnLAogICAgICAgIFNWTV9MaW5lYXJfR1M9Y29uZk1hdHJpeC5saW5lYXIuR1Mkb3ZlcmFsbFsnS2FwcGEnXSwKICAgICAgICBTVk1fUmFkaWFsX0dTPWNvbmZNYXRyaXgucmFkaWFsLkdTJG92ZXJhbGxbJ0thcHBhJ10sCiAgICAgICAgU1ZNX1BvbHlfR1M9Y29uZk1hdHJpeC5wb2x5LkdTJG92ZXJhbGxbJ0thcHBhJ10sCiAgICAgICAgUmFuZEZvcmVzdF9HUz1jb25mTWF0cml4LnJmLkdTJG92ZXJhbGxbJ0thcHBhJ10pCgoKYWNjLkdWPC1jYmluZCgKICAgICAgICBTdGF0aXN0aWM9J0FjY3VyYWN5JywKICAgICAgICBTVk1fTGluZWFyX0dWPWNvbmZNYXRyaXgubGluZWFyLkdWJG92ZXJhbGxbJ0FjY3VyYWN5J10sCiAgICAgICAgU1ZNX1JhZGlhbF9HVj1jb25mTWF0cml4LnJhZGlhbC5HViRvdmVyYWxsWydBY2N1cmFjeSddLAogICAgICAgIFNWTV9Qb2x5X0dWPWNvbmZNYXRyaXgucG9seS5HViRvdmVyYWxsWydBY2N1cmFjeSddLAogICAgICAgIFJhbmRGb3Jlc3RfR1Y9Y29uZk1hdHJpeC5yZi5HViRvdmVyYWxsWydBY2N1cmFjeSddKQoKa2FwcGEuR1Y8LWNiaW5kKAogICAgICAgIFN0YXRpc3RpYz0nS2FwcGEnLAogICAgICAgIFNWTV9MaW5lYXJfR1Y9Y29uZk1hdHJpeC5saW5lYXIuR1Ykb3ZlcmFsbFsnS2FwcGEnXSwKICAgICAgICBTVk1fUmFkaWFsX0dWPWNvbmZNYXRyaXgucmFkaWFsLkdWJG92ZXJhbGxbJ0thcHBhJ10sCiAgICAgICAgU1ZNX1BvbHlfR1Y9Y29uZk1hdHJpeC5wb2x5LkdWJG92ZXJhbGxbJ0thcHBhJ10sCiAgICAgICAgUmFuZEZvcmVzdF9HVj1jb25mTWF0cml4LnJmLkdWJG92ZXJhbGxbJ0thcHBhJ10pCgpjb21wYXJpc29uLkdTPC1hcy5kYXRhLmZyYW1lKHJiaW5kKGFjYy5HUyxrYXBwYS5HUykpCmNvbXBhcmlzb24uR1Y8LWFzLmRhdGEuZnJhbWUocmJpbmQoYWNjLkdWLGthcHBhLkdWKSkKYGBgCgpDb25mdXNpb24gbWF0cml4IHByb3ZpZGVzIGRpZmZlcmVudCBpbmZvcm1hdGlvbiBhYm91dCBwZXJmb3JtYW5jZSBvZiBvdXIgbW9kZWwuIEFzIGFuIEV4YW1wbGUgZm9yIEdyYXBobGV0U2VhcmNoIGRhdGFzZXQgdXNpbmcgUmFuZG9tIGZvcmVzdDoKCk5vdGU6IFgxIG1lYW5zIENsYXNzIGxhYmVsICIxIiBhbmQgWC4xIHJlZmVycyB0byB0aGUgQ2xhc3MgbGFiZWwgIi0xIiBvZiB0aGUgZ3JhcGguCgpgYGB7ciBlY2hvPUZBTFNFfQpjb25mTWF0cml4LnJmLkdTCmBgYAoKR2VuZXJhbGx5IHdlIGNhbiBjb21wYXJlIG1vZGVscyBvbiBlYWNoIGRhdGFzZXQgYmFzZWQgb24gdGhlaXIgYWNjdXJhY3kgYW5kIGthcHBhIHZhbHVlLiBGb3IgdGhlIHNhbWUgcmFuZ2Ugb2YgQWNjdXJhY3ksIHdlIHdvdWxkIHNheSB0aGUgbWV0aG9kIHdpdGggaGlnaGVyIGthcHBhIHZhbHVlIHByb3ZpZGVzIG1vcmUgcmVsaWFiaWxpdHkgb24gdGhlIHJlc3VsdC4KClBlcmZvcm1hbmNlIG9mIEdyYXBobGV0U2VhcmNoIGFuZCBHcmFwaFZlY3RvciBkYXRhc2V0cyBzaG93biBiZWxvdzoKCmBgYHtyIGVycm9yPUZBTFNFLG1lc3NhZ2U9RkFMU0UscmVzdWx0cz0naGlkZScsaW5jbHVkZT1GQUxTRSx0aWR5PUZBTFNFfQojIyBSZXNoYXBpbmcgZGF0YSBmcm9tIHdpZGUgdG8gbG9uZyBmb3JtYXQgZm9yIHBsb3R0aW5nLCBhbmQgY29ycmVjdGluZyBkYXRhIHR5cGUgZm9yIHZhbHVlIGNvbHVtbgojI0dyYXBobGV0U2VhcmNoCmdzTG9uZ0Zvcm1hdDwtZ2F0aGVyKGRhdGEgPSBjb21wYXJpc29uLkdTLGtleSA9ICBNb2RlbE5hbWUsdmFsdWUgPSAndmFsdWUnLFNWTV9MaW5lYXJfR1M6UmFuZEZvcmVzdF9HUyxmYWN0b3Jfa2V5ID1UUlVFICkKZ3NMb25nRm9ybWF0JHZhbHVlPC1hcy5udW1lcmljKGdzTG9uZ0Zvcm1hdCR2YWx1ZSkKCiMjR3JhcGhWZWN0b3IKZ3ZMb25nRm9ybWF0PC1nYXRoZXIoZGF0YSA9IGNvbXBhcmlzb24uR1Ysa2V5ID0gIE1vZGVsTmFtZSx2YWx1ZSA9ICd2YWx1ZScsU1ZNX0xpbmVhcl9HVjpSYW5kRm9yZXN0X0dWLGZhY3Rvcl9rZXkgPVRSVUUgKQpndkxvbmdGb3JtYXQkdmFsdWU8LWFzLm51bWVyaWMoZ3ZMb25nRm9ybWF0JHZhbHVlKQoKCmBgYAoKYGBge3IgZWNobz1GQUxTRSxtZXNzYWdlPUZBTFNFLGVycm9yPUZBTFNFLHRpZHk9RkFMU0V9CgojIyBDcmVhdGluZyB0aGUgY2hhcnRzIGFmdGVyIGNvbnZlcnRpbmcgZGF0YSBmcm9tIHdpZGUgdG8gbG9uZyBmb3JtYXQKZ3M8LWdncGxvdChkYXRhPWdzTG9uZ0Zvcm1hdCxhZXMoeD1Nb2RlbE5hbWUseT12YWx1ZSxmaWxsPVN0YXRpc3RpYykpKwogICAgICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5Iixwb3NpdGlvbj1wb3NpdGlvbl9udWRnZSgpKSsKICAgICAgICBnZ3RpdGxlKCJQZXJmb3JtYW5jZSBjb21wYXJpc29uIGZvciBkYXRhc2V0IEdyYXBobGV0U2VhcmNoW011dGFnXSIpCgoKZ3Y8LWdncGxvdChkYXRhPWd2TG9uZ0Zvcm1hdCxhZXMoeD1Nb2RlbE5hbWUseT12YWx1ZSxmaWxsPVN0YXRpc3RpYykpKwogICAgICAgIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5Iixwb3NpdGlvbj1wb3NpdGlvbl9udWRnZSgpKSsKICAgICAgICBnZ3RpdGxlKCJQZXJmb3JtYW5jZSBjb21wYXJpc29uIGZvciBkYXRhc2V0IEdyYXBoVmVjdG9yW011dGFnXSIpCgpjYXQoIkZvciBHcmFwaGxldFNlYXJjaDpcblxuIikKcHJpbnQoYXMubWF0cml4KGNvbXBhcmlzb24uR1NbLC0xXSkpCmNhdCgiXG5cbkZvciBHcmFwaFZlY3RvcjpcblxuIikKcHJpbnQoYXMubWF0cml4KGNvbXBhcmlzb24uR1ZbLC0xXSkpCgpwcmludChncykKY2F0KCJcblxuXG5cblxuIikKcHJpbnQoZ3YpCgpgYGA=