Required packages

The “caret” and “ellipse” packages are required to perform the steps in this document. The “if” statements below will only install the required packages if they are not already installed, to avoid losing time to re-loading packages.

if("caret" %in% rownames(installed.packages()) == FALSE) {install.packages("caret")}
if("ellipse" %in% rownames(installed.packages()) == FALSE) {install.packages("ellipse")}
library(caret)
library(ellipse)

Executive Summary

Machine learning is just one application of artificial intelligence (AI) whereby systems automatically learn and improve from experience without being explicitly programmed, (Expertsystem.com, 2020). The machine learning performed in this document was based on the “Your First Machine Learning Project in R Step-By-Step”, by Jason Brownlee from Machine Learning Mastery and aims to produce an algorithm that can predict flower species using the length and width of the flower’s petals and sepal (bud), (Brownlee, 2016). The steps involved in the process are as follows:

Data

The Iris dataset will be used to produce the machine learning algorithm in this document. The Iris dataset is readily available in the R envioronment but can also be accessed from the UCI Machine Learning Repository (https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data). The Iris dataset will be assigned to “dataset” so that the rest of the steps can be used in the future with new datasets simply by assigning the new dataset to “dataset”.

#The Iris dataset is pre-loaded into the R enviornment
data(iris)
# rename the dataset so the remaining steps can be performed with any dataset assigned to "dataset"
dataset <- iris

Understand

The Iris dataset contains 150 observations of 5 variables for flowers from three different species, “setosa”, “versicolor” and “virginica”. All columns are numeric except for the “Species” column, which is a factor as it contains categorical data about flower species. The variables in the dataset are as follow:

dim(dataset)
[1] 150   5
sapply(dataset, class) #list the data types in the dataset
Sepal.Length  Sepal.Width Petal.Length  Petal.Width      Species 
   "numeric"    "numeric"    "numeric"    "numeric"     "factor" 
head(dataset)
levels(dataset$Species)
[1] "setosa"     "versicolor" "virginica" 

Create Training and Validation datasets

The dataset was then separated into two subsets of data, one to be used for training the machine learning algorithms and another to be used to validate the final model and check that data leakage had not occurred.

# Create a subset of 80% of the rows in the original dataset. We will use these to train the model
validation_index <- createDataPartition(dataset$Species, p=0.80, list=FALSE)
# Select 20% of the data for validation
validation <- dataset[-validation_index,]
# Assign the training data to "dataset"
dataset <- dataset[validation_index,]

Explore

The training data was explored to observe the number of instances of each flower species and summary statistics (mean, median, min, max) of the dataset.

# Look at the number of instances (rows) that belong to each class
percentage <- prop.table( table(dataset$Species) ) * 100
cbind(freq = table(dataset$Species), percentage = percentage)
           freq percentage
setosa       40   33.33333
versicolor   40   33.33333
virginica    40   33.33333
# There are the same number of observations (40) in each class (Species)
summary(dataset)
  Sepal.Length   Sepal.Width     Petal.Length    Petal.Width          Species  
 Min.   :4.30   Min.   :2.000   Min.   :1.000   Min.   :0.100   setosa    :40  
 1st Qu.:5.10   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300   versicolor:40  
 Median :5.80   Median :3.000   Median :4.400   Median :1.300   virginica :40  
 Mean   :5.86   Mean   :3.051   Mean   :3.765   Mean   :1.201                  
 3rd Qu.:6.40   3rd Qu.:3.325   3rd Qu.:5.100   3rd Qu.:1.800                  
 Max.   :7.90   Max.   :4.400   Max.   :6.900   Max.   :2.500                  

Visualise the data

The flower species was separated from the numeric variables in the training data in order to inspect the numeric variables.

Univariate plots of the numeric training data were produced and outliers were observed in the “sepal.width”" variable. “sepal.Length” exhibited slightly positive skew while “petal.length” and “petal.width” displayed slightly negative skew. A barplot of the frequency of each flower species was also produced, which confirmed that each flower species was represented the same number of times in the training data.

A multivaraite plot was produced to visualise the relationship between each numeric variable and each of the other numeric variables. The points in these scatter plots were grouped by flower species. The points appeared to cluster according to flower species and thus they were encircled with an ellipse to highlight the clustering. Boxplots of each variable, grouped according to flower species, were also produced tto observe whether particular variables displayed different distributions according to flower species. The boxplots revealed that the distribution of all variables appeared to differ according to flower species. Finally, density plots were produced to highlight the differences in distributions of the numeric variables according to flower species.

#Univariate plots
#Split the data into species, and the numeric variables.
x <- dataset[ ,1:4]
y <- dataset[ ,5]
#Because data in x are numeric, you can use a boxplot to visualise the variables
par(mfrow = c(1,4))
for (i in 1:4) {
  boxplot(x[ ,i], main = names(iris[i]))
}
#outliers in sepal.width
#slight positive skew in sepal.length, and slight negative skew in petal.length and petal.width
#barplot of y - not interseting b/c all categories have the same freq as seen earlier
par(mfrow = c(1,1))

plot(y)

#Multivariate plots
#scatterplot matrix of every pair of variables.
featurePlot(x = x, y = y)

#The points from each of the 3 Species appear to be clustered, so circle them to highlight this ("ellipse").
featurePlot(x = x, y = y, plot = "ellipse")

#boxplots by Species (class)
featurePlot(x = x, y = y, plot = "box")

#When split by Species, the distribution of each variable (x) is different. 
#>>>There appears to be a relationship between Species and the distribution of each of the variables
#density plots for each attribute by class value
scales <- list(x = list(relation = "free"), y = list(relation = "free") )
featurePlot(x = x, y = y, plot = "density", scales = scales)

# shows that the distribution of each attribute (x) is different based on the species

Evaluate Some Algorithms

Five algorithms were evaluated using the training dataset, the algorithms evaulated were:

The algorithms were used to produce predictive models by training them using 10-fold cross validation. This involved splitting the training data into 10 parts; 9 of which were used to train the model and 1 to test it, for all combinations of train-test splits. This was repeated 3 times for each algorithm, using different splits of the data in each repetition.

The models were then evaluated according to how accurate they were when making predictions. In this case, the predictions of each model were used to produce a ratio, being the number of correctly predicted instances (flower species) divided by the total number of instances (predictions made) in the dataset.

The LDA model was identified as the most accurate model, having the highest accuracy and Kappa of all models produced. In the test example, the model accuracy was 97.5%, with a standard deviation of +/- 5.6% (Margin of Error).

# Run algorithms using 10-fold cross validation
control <- trainControl(method = "cv", number = 10)
metric <- "Accuracy"
#We will be using the "metric" variable created above when we build and evaluate each model (next).
#Build the 5 models:
#Make sure you reset the random number seed before each run to ensure that the evaluation of each algorithm is performed using exactly the same data splits. This ensures the results are directly comparavble.
# 1 - LDA
set.seed(7)
fit.lda <- train(Species~., data=dataset, method="lda", metric=metric, trControl=control)
# 2 - CART
set.seed(7)
fit.cart <- train(Species~., data=dataset, method="rpart", metric=metric, trControl=control)
# 3 - kNN
set.seed(7)
fit.knn <- train(Species~., data=dataset, method="knn", metric=metric, trControl=control)
# 4 - SVM
set.seed(7)
fit.svm <- train(Species~., data=dataset, method="svmRadial", metric=metric, trControl=control)
# 5 - Random Forest
set.seed(7)
fit.rf <- train(Species~., data=dataset, method="rf", metric=metric, trControl=control)
#summarize the accuracy of the models and compare them to identify the best one
results <- resamples(list(lda=fit.lda, cart=fit.cart, knn=fit.knn, svm=fit.svm, rf=fit.rf))
summary(results) # Kappa gives us the accuracy of each model

Call:
summary.resamples(object = results)

Models: lda, cart, knn, svm, rf 
Number of resamples: 10 

Accuracy 
          Min.   1st Qu.    Median      Mean   3rd Qu. Max. NA's
lda  0.8333333 1.0000000 1.0000000 0.9750000 1.0000000    1    0
cart 0.8333333 0.9166667 0.9166667 0.9333333 0.9791667    1    0
knn  0.9166667 0.9166667 1.0000000 0.9666667 1.0000000    1    0
svm  0.8333333 0.9166667 0.9583333 0.9416667 1.0000000    1    0
rf   0.9166667 0.9166667 0.9583333 0.9583333 1.0000000    1    0

Kappa 
      Min. 1st Qu. Median   Mean 3rd Qu. Max. NA's
lda  0.750   1.000 1.0000 0.9625 1.00000    1    0
cart 0.750   0.875 0.8750 0.9000 0.96875    1    0
knn  0.875   0.875 1.0000 0.9500 1.00000    1    0
svm  0.750   0.875 0.9375 0.9125 1.00000    1    0
rf   0.875   0.875 0.9375 0.9375 1.00000    1    0
#Because each model was evaluated 10 times (10-fold cross validation) we have data that we can plot (e.g. the mean of each model)
#Create a plot of the results from the model evaluation and compare the spread of each model's mean accuracy.
dotplot(results)

#The best model is the LDA model as it has the highest mean accuracy and Kappa of all those tested
print(fit.lda) #View a summary of the LDA model
Linear Discriminant Analysis 

120 samples
  4 predictor
  3 classes: 'setosa', 'versicolor', 'virginica' 

No pre-processing
Resampling: Cross-Validated (10 fold) 
Summary of sample sizes: 108, 108, 108, 108, 108, 108, ... 
Resampling results:

  Accuracy  Kappa 
  0.975     0.9625
fit.lda$results #accuracy in example was 98.3%. SD is +/-3.5%, this is the Margin of Error (MoE).

Run the model on the validation dataset

The LDA model was then used to predict flower species using the validation dataset, to observe its performance on a dataset it had not yet seen and confirm that the accuracy of the model was within the margin of error observed using the training dataset. The results were presented in a confusion matrix, a convenient way of displaying the outcomes of a prediction model. This also provided an opportunity to test the model for errors such as overfitting and data leakage, which would result in an artificially positive result.

In the example run, the model’s accuracy (100%) was within the Margin of Error (MoE) of the accuracy observed when producing the model (97.5 +/- 5.6 = [100, 91.9]). Therefore, it was likely that the model was accurate and reliable, and unlikely that errors such as overfitting or data leakage had occurred.

#Run the LDA model on the validation dataset and summarise the results in a "confusion matrix"
predictions <- predict(fit.lda, validation)
confusionMatrix(predictions, validation$Species) #Check that the accuracy is within the expected MoE
Confusion Matrix and Statistics

            Reference
Prediction   setosa versicolor virginica
  setosa         10          0         0
  versicolor      0         10         0
  virginica       0          0        10

Overall Statistics
                                     
               Accuracy : 1          
                 95% CI : (0.8843, 1)
    No Information Rate : 0.3333     
    P-Value [Acc > NIR] : 4.857e-15  
                                     
                  Kappa : 1          
                                     
 Mcnemar's Test P-Value : NA         

Statistics by Class:

                     Class: setosa Class: versicolor Class: virginica
Sensitivity                 1.0000            1.0000           1.0000
Specificity                 1.0000            1.0000           1.0000
Pos Pred Value              1.0000            1.0000           1.0000
Neg Pred Value              1.0000            1.0000           1.0000
Prevalence                  0.3333            0.3333           0.3333
Detection Rate              0.3333            0.3333           0.3333
Detection Prevalence        0.3333            0.3333           0.3333
Balanced Accuracy           1.0000            1.0000           1.0000
#For the model to be considered reliable, its prediction accuracy should be within the MoE around the test accuracy: 98.3 +/- 3.5 = [100, 94.8]
example_data <- iris[40,1:4]
predict(fit.lda, example_data) #predicts the Species of the data you select as "example_data"
[1] setosa
Levels: setosa versicolor virginica

Use the model to predict flower species.

The model was then used to provide an example of what it could do. In this case, the LDA model was run on a selected set of petal and sepal lengths and widths from the Iris dataset and the prediction was then compared with what was in the Iris dataset to observe whether the model correctly predicted the flower species. The example was taken from row 40 of the Iris dataset, where the flower species was “setosa”.

example_data <- iris[40,1:4]
predict(fit.lda, example_data) #predicts the Species of the data you select as "example_data"
[1] setosa
Levels: setosa versicolor virginica

References

Expertsystem.com, 2020. What is Machine Learning? A definition, viewed 08 June 2020, https://expertsystem.com/machine-learning-definition/#:~:text=Machine%20learning%20is%20an%20application,use%20it%20learn%20for%20themselves.

Brownlee, J., 2016. Machine Learning Mastery, Your First Machine Learning Project in R Step-By-Step, viewed 16 May 2020, https://machinelearningmastery.com/machine-learning-in-r-step-by-step/



LS0tDQp0aXRsZTogIk1hY2hpbmUgTGVhcm5pbmcgZm9yIEJlZ2lubmVycyB1c2luZyBSIg0KYXV0aG9yOiAiSmFtZXMgQW5ndXMiDQpzdWJ0aXRsZTogIlVzZSBNYWNoaW5lIGxlYXJuaW5nIGluIFIgdG8gcHJvZHVjZSBhbiBhbGdvcml0aG0gdGhhdCBwcmVkaWN0cyBmbG93ZXIgc3BlY2llcyINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KLS0tDQoNCiMjIFJlcXVpcmVkIHBhY2thZ2VzDQoNClRoZSAiY2FyZXQiIGFuZCAiZWxsaXBzZSIgcGFja2FnZXMgYXJlIHJlcXVpcmVkIHRvIHBlcmZvcm0gdGhlIHN0ZXBzIGluIHRoaXMgZG9jdW1lbnQuIFRoZSAiaWYiIHN0YXRlbWVudHMgYmVsb3cgd2lsbCBvbmx5IGluc3RhbGwgdGhlIHJlcXVpcmVkIHBhY2thZ2VzIGlmIHRoZXkgYXJlIG5vdCBhbHJlYWR5IGluc3RhbGxlZCwgdG8gYXZvaWQgbG9zaW5nIHRpbWUgdG8gcmUtbG9hZGluZyBwYWNrYWdlcy4NCg0KYGBge3J9DQppZigiY2FyZXQiICVpbiUgcm93bmFtZXMoaW5zdGFsbGVkLnBhY2thZ2VzKCkpID09IEZBTFNFKSB7aW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKX0NCmlmKCJlbGxpcHNlIiAlaW4lIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSA9PSBGQUxTRSkge2luc3RhbGwucGFja2FnZXMoImVsbGlwc2UiKX0NCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KGVsbGlwc2UpDQpgYGANCg0KDQojIyBFeGVjdXRpdmUgU3VtbWFyeSANCg0KTWFjaGluZSBsZWFybmluZyBpcyBqdXN0IG9uZSBhcHBsaWNhdGlvbiBvZiBhcnRpZmljaWFsIGludGVsbGlnZW5jZSAoQUkpIHdoZXJlYnkgc3lzdGVtcyBhdXRvbWF0aWNhbGx5IGxlYXJuIGFuZCBpbXByb3ZlIGZyb20gZXhwZXJpZW5jZSB3aXRob3V0IGJlaW5nIGV4cGxpY2l0bHkgcHJvZ3JhbW1lZCwgKEV4cGVydHN5c3RlbS5jb20sIDIwMjApLiBUaGUgbWFjaGluZSBsZWFybmluZyBwZXJmb3JtZWQgaW4gdGhpcyBkb2N1bWVudCB3YXMgYmFzZWQgb24gdGhlICJZb3VyIEZpcnN0IE1hY2hpbmUgTGVhcm5pbmcgUHJvamVjdCBpbiBSIFN0ZXAtQnktU3RlcCIsIGJ5IEphc29uIEJyb3dubGVlIGZyb20gTWFjaGluZSBMZWFybmluZyBNYXN0ZXJ5IGFuZCBhaW1zIHRvIHByb2R1Y2UgYW4gYWxnb3JpdGhtIHRoYXQgY2FuIHByZWRpY3QgZmxvd2VyIHNwZWNpZXMgdXNpbmcgdGhlIGxlbmd0aCBhbmQgd2lkdGggb2YgdGhlIGZsb3dlcidzIHBldGFscyBhbmQgc2VwYWwgKGJ1ZCksIChCcm93bmxlZSwgMjAxNikuIFRoZSBzdGVwcyBpbnZvbHZlZCBpbiB0aGUgcHJvY2VzcyBhcmUgYXMgZm9sbG93czoNCg0KKiBJbXBvcnQgdGhlIGRhdGENCiogVW5kZXJzdGFuZCB0aGUgcHJvcGVydGllcyBvZiB0aGUgZGF0YQ0KKiBTZXBhcmF0ZSB0aGUgZGF0YSBpbnRvIHR3byBkYXRhc2V0cywgb25lIHRvIGJlIHVzZWQgZm9yIHRyYWluaW5nIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyBhbmQgYW5vdGhlciBmb3IgdmFsaWRhdGluZyBhbmQgYXNzZXNzaW5nIGFjY3VyYWN5IG9mIHRoZSBmaW5hbCBtb2RlbC4NCiogRXhwbG9yZSB0aGUgdHJhaW5pbmcgZGF0YQ0KKiBWaXN1YWxpc2UgdGhlIGRhdGEgdG8gb2JzZXJ2ZSB3aGljaCBmYWN0b3JzIGFwcGVhciBpbmZsdWVudGlhbA0KKiBFdmFsdWF0ZSBhIHNlbGVjdGlvbiBvZiBhbGdvcml0aG1zIHVzaW5nIHRoZSB0cmFpbmluZyBkYXRhDQoqIEFwcGx5IHRoZSBtb2RlbCB0byBwcmVkaWN0IGZsb3dlciBzcGVjaWVzDQoNCiMjIERhdGENCg0KVGhlIElyaXMgZGF0YXNldCB3aWxsIGJlIHVzZWQgdG8gcHJvZHVjZSB0aGUgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG0gaW4gdGhpcyBkb2N1bWVudC4gVGhlIElyaXMgZGF0YXNldCBpcyByZWFkaWx5IGF2YWlsYWJsZSBpbiB0aGUgUiBlbnZpb3Jvbm1lbnQgYnV0IGNhbiBhbHNvIGJlIGFjY2Vzc2VkIGZyb20gdGhlIFVDSSBNYWNoaW5lIExlYXJuaW5nIFJlcG9zaXRvcnkgKGh0dHBzOi8vYXJjaGl2ZS5pY3MudWNpLmVkdS9tbC9tYWNoaW5lLWxlYXJuaW5nLWRhdGFiYXNlcy9pcmlzL2lyaXMuZGF0YSkuDQpUaGUgSXJpcyBkYXRhc2V0IHdpbGwgYmUgYXNzaWduZWQgdG8gImRhdGFzZXQiIHNvIHRoYXQgdGhlIHJlc3Qgb2YgdGhlIHN0ZXBzIGNhbiBiZSB1c2VkIGluIHRoZSBmdXR1cmUgd2l0aCBuZXcgZGF0YXNldHMgc2ltcGx5IGJ5IGFzc2lnbmluZyB0aGUgbmV3IGRhdGFzZXQgdG8gImRhdGFzZXQiLg0KDQpgYGB7cn0NCiNUaGUgSXJpcyBkYXRhc2V0IGlzIHByZS1sb2FkZWQgaW50byB0aGUgUiBlbnZpb3JubWVudA0KZGF0YShpcmlzKQ0KIyByZW5hbWUgdGhlIGRhdGFzZXQgc28gdGhlIHJlbWFpbmluZyBzdGVwcyBjYW4gYmUgcGVyZm9ybWVkIHdpdGggYW55IGRhdGFzZXQgYXNzaWduZWQgdG8gImRhdGFzZXQiDQpkYXRhc2V0IDwtIGlyaXMNCmBgYA0KDQojIyBVbmRlcnN0YW5kIA0KDQpUaGUgSXJpcyBkYXRhc2V0IGNvbnRhaW5zIDE1MCBvYnNlcnZhdGlvbnMgb2YgNSB2YXJpYWJsZXMgZm9yIGZsb3dlcnMgZnJvbSB0aHJlZSBkaWZmZXJlbnQgc3BlY2llcywgInNldG9zYSIsICJ2ZXJzaWNvbG9yIiBhbmQgInZpcmdpbmljYSIuIEFsbCBjb2x1bW5zIGFyZSBudW1lcmljIGV4Y2VwdCBmb3IgdGhlICJTcGVjaWVzIiBjb2x1bW4sIHdoaWNoIGlzIGEgZmFjdG9yIGFzIGl0IGNvbnRhaW5zIGNhdGVnb3JpY2FsIGRhdGEgYWJvdXQgZmxvd2VyIHNwZWNpZXMuIFRoZSB2YXJpYWJsZXMgaW4gdGhlIGRhdGFzZXQgYXJlIGFzIGZvbGxvdzoNCg0KKiBTZXBhbC5MZW5ndGggIC0gVGhlIGxlbmd0aCBvZiBhIGZsb3dlcidzIHNlcGFsIChidWQpLCBtZWFzdXJlZCBpbiBjZW50aW1ldGVycyAoY20pDQoqIFNlcGFsLldpZHRoICAgLSBUaGUgd2lkdGggb2YgYSBmbG93ZXIncyBzZXBhbCAoYnVkKSwgbWVhc3VyZWQgaW4gY2VudGltZXRlcnMgKGNtKQ0KKiBQZXRhbC5MZW5ndGggIC0gVGhlIGxlbmd0aCBvZiBhIGZsb3dlcidzIHBldGFsLCBtZWFzdXJlZCBpbiBjZW50aW1ldGVycyAoY20pDQoqIFBldGFsLldpZHRoICAgLSBUaGUgd2lkdGggb2YgYSBmbG93ZXIncyBwZXRhbCwgbWVhc3VyZWQgaW4gY2VudGltZXRlcnMgKGNtKQ0KKiBTcGVjaWVzICAgICAgIC0gVGhlIHNwZWNpZXMgb2YgdGhlIGZsb3dlcg0KDQpgYGB7cn0NCmRpbShkYXRhc2V0KQ0Kc2FwcGx5KGRhdGFzZXQsIGNsYXNzKSAjbGlzdCB0aGUgZGF0YSB0eXBlcyBpbiB0aGUgZGF0YXNldA0KaGVhZChkYXRhc2V0KQ0KbGV2ZWxzKGRhdGFzZXQkU3BlY2llcykNCmBgYA0KDQoNCiMjCUNyZWF0ZSBUcmFpbmluZyBhbmQgVmFsaWRhdGlvbiBkYXRhc2V0cw0KDQpUaGUgZGF0YXNldCB3YXMgdGhlbiBzZXBhcmF0ZWQgaW50byB0d28gc3Vic2V0cyBvZiBkYXRhLCBvbmUgdG8gYmUgdXNlZCBmb3IgdHJhaW5pbmcgdGhlIG1hY2hpbmUgbGVhcm5pbmcgYWxnb3JpdGhtcyBhbmQgYW5vdGhlciB0byBiZSB1c2VkIHRvIHZhbGlkYXRlIHRoZSBmaW5hbCBtb2RlbCBhbmQgY2hlY2sgdGhhdCBkYXRhIGxlYWthZ2UgaGFkIG5vdCBvY2N1cnJlZC4NCg0KYGBge3J9DQojIENyZWF0ZSBhIHN1YnNldCBvZiA4MCUgb2YgdGhlIHJvd3MgaW4gdGhlIG9yaWdpbmFsIGRhdGFzZXQuIFdlIHdpbGwgdXNlIHRoZXNlIHRvIHRyYWluIHRoZSBtb2RlbA0KdmFsaWRhdGlvbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdGFzZXQkU3BlY2llcywgcD0wLjgwLCBsaXN0PUZBTFNFKQ0KIyBTZWxlY3QgMjAlIG9mIHRoZSBkYXRhIGZvciB2YWxpZGF0aW9uDQp2YWxpZGF0aW9uIDwtIGRhdGFzZXRbLXZhbGlkYXRpb25faW5kZXgsXQ0KIyBBc3NpZ24gdGhlIHRyYWluaW5nIGRhdGEgdG8gImRhdGFzZXQiDQpkYXRhc2V0IDwtIGRhdGFzZXRbdmFsaWRhdGlvbl9pbmRleCxdDQpgYGANCg0KIyMJRXhwbG9yZQ0KDQpUaGUgdHJhaW5pbmcgZGF0YSB3YXMgZXhwbG9yZWQgdG8gb2JzZXJ2ZSB0aGUgbnVtYmVyIG9mIGluc3RhbmNlcyBvZiBlYWNoIGZsb3dlciBzcGVjaWVzIGFuZCBzdW1tYXJ5IHN0YXRpc3RpY3MgKG1lYW4sIG1lZGlhbiwgbWluLCBtYXgpIG9mIHRoZSBkYXRhc2V0Lg0KDQpgYGB7cn0NCiMgTG9vayBhdCB0aGUgbnVtYmVyIG9mIGluc3RhbmNlcyAocm93cykgdGhhdCBiZWxvbmcgdG8gZWFjaCBjbGFzcw0KcGVyY2VudGFnZSA8LSBwcm9wLnRhYmxlKCB0YWJsZShkYXRhc2V0JFNwZWNpZXMpICkgKiAxMDANCmNiaW5kKGZyZXEgPSB0YWJsZShkYXRhc2V0JFNwZWNpZXMpLCBwZXJjZW50YWdlID0gcGVyY2VudGFnZSkNCiMgVGhlcmUgYXJlIHRoZSBzYW1lIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgKDQwKSBpbiBlYWNoIGNsYXNzIChTcGVjaWVzKQ0Kc3VtbWFyeShkYXRhc2V0KQ0KYGBgDQoNCg0KIyMgVmlzdWFsaXNlIHRoZSBkYXRhDQoNClRoZSBmbG93ZXIgc3BlY2llcyB3YXMgc2VwYXJhdGVkIGZyb20gdGhlIG51bWVyaWMgdmFyaWFibGVzIGluIHRoZSB0cmFpbmluZyBkYXRhIGluIG9yZGVyIHRvIGluc3BlY3QgdGhlIG51bWVyaWMgdmFyaWFibGVzLiANCg0KVW5pdmFyaWF0ZSBwbG90cyBvZiB0aGUgbnVtZXJpYyB0cmFpbmluZyBkYXRhIHdlcmUgcHJvZHVjZWQgYW5kIG91dGxpZXJzIHdlcmUgb2JzZXJ2ZWQgaW4gdGhlICJzZXBhbC53aWR0aCIiIHZhcmlhYmxlLiAic2VwYWwuTGVuZ3RoIiBleGhpYml0ZWQgc2xpZ2h0bHkgcG9zaXRpdmUgc2tldyB3aGlsZSAicGV0YWwubGVuZ3RoIiBhbmQgInBldGFsLndpZHRoIiBkaXNwbGF5ZWQgc2xpZ2h0bHkgbmVnYXRpdmUgc2tldy4gQSBiYXJwbG90IG9mIHRoZSBmcmVxdWVuY3kgb2YgZWFjaCBmbG93ZXIgc3BlY2llcyB3YXMgYWxzbyBwcm9kdWNlZCwgd2hpY2ggY29uZmlybWVkIHRoYXQgZWFjaCBmbG93ZXIgc3BlY2llcyB3YXMgcmVwcmVzZW50ZWQgdGhlIHNhbWUgbnVtYmVyIG9mIHRpbWVzIGluIHRoZSB0cmFpbmluZyBkYXRhLg0KDQpBIG11bHRpdmFyYWl0ZSBwbG90IHdhcyBwcm9kdWNlZCB0byB2aXN1YWxpc2UgdGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIGVhY2ggbnVtZXJpYyB2YXJpYWJsZSBhbmQgZWFjaCBvZiB0aGUgb3RoZXIgbnVtZXJpYyB2YXJpYWJsZXMuIFRoZSBwb2ludHMgaW4gdGhlc2Ugc2NhdHRlciBwbG90cyB3ZXJlIGdyb3VwZWQgYnkgZmxvd2VyIHNwZWNpZXMuIFRoZSBwb2ludHMgYXBwZWFyZWQgdG8gY2x1c3RlciBhY2NvcmRpbmcgdG8gZmxvd2VyIHNwZWNpZXMgYW5kIHRodXMgdGhleSB3ZXJlIGVuY2lyY2xlZCB3aXRoIGFuIGVsbGlwc2UgdG8gaGlnaGxpZ2h0IHRoZSBjbHVzdGVyaW5nLg0KQm94cGxvdHMgb2YgZWFjaCB2YXJpYWJsZSwgZ3JvdXBlZCBhY2NvcmRpbmcgdG8gZmxvd2VyIHNwZWNpZXMsIHdlcmUgYWxzbyBwcm9kdWNlZCB0dG8gb2JzZXJ2ZSB3aGV0aGVyIHBhcnRpY3VsYXIgdmFyaWFibGVzIGRpc3BsYXllZCBkaWZmZXJlbnQgZGlzdHJpYnV0aW9ucyBhY2NvcmRpbmcgdG8gZmxvd2VyIHNwZWNpZXMuIFRoZSBib3hwbG90cyByZXZlYWxlZCB0aGF0IHRoZSBkaXN0cmlidXRpb24gb2YgYWxsIHZhcmlhYmxlcyBhcHBlYXJlZCB0byBkaWZmZXIgYWNjb3JkaW5nIHRvIGZsb3dlciBzcGVjaWVzLg0KRmluYWxseSwgZGVuc2l0eSBwbG90cyB3ZXJlIHByb2R1Y2VkIHRvIGhpZ2hsaWdodCB0aGUgZGlmZmVyZW5jZXMgaW4gZGlzdHJpYnV0aW9ucyBvZiB0aGUgbnVtZXJpYyB2YXJpYWJsZXMgYWNjb3JkaW5nIHRvIGZsb3dlciBzcGVjaWVzLg0KDQpgYGB7cn0NCiNVbml2YXJpYXRlIHBsb3RzDQojU3BsaXQgdGhlIGRhdGEgaW50byBzcGVjaWVzLCBhbmQgdGhlIG51bWVyaWMgdmFyaWFibGVzLg0KeCA8LSBkYXRhc2V0WyAsMTo0XQ0KeSA8LSBkYXRhc2V0WyAsNV0NCiNCZWNhdXNlIGRhdGEgaW4geCBhcmUgbnVtZXJpYywgeW91IGNhbiB1c2UgYSBib3hwbG90IHRvIHZpc3VhbGlzZSB0aGUgdmFyaWFibGVzDQpwYXIobWZyb3cgPSBjKDEsNCkpDQpmb3IgKGkgaW4gMTo0KSB7DQogIGJveHBsb3QoeFsgLGldLCBtYWluID0gbmFtZXMoaXJpc1tpXSkpDQp9DQojb3V0bGllcnMgaW4gc2VwYWwud2lkdGgNCiNzbGlnaHQgcG9zaXRpdmUgc2tldyBpbiBzZXBhbC5sZW5ndGgsIGFuZCBzbGlnaHQgbmVnYXRpdmUgc2tldyBpbiBwZXRhbC5sZW5ndGggYW5kIHBldGFsLndpZHRoDQojYmFycGxvdCBvZiB5IC0gbm90IGludGVyc2V0aW5nIGIvYyBhbGwgY2F0ZWdvcmllcyBoYXZlIHRoZSBzYW1lIGZyZXEgYXMgc2VlbiBlYXJsaWVyDQpwYXIobWZyb3cgPSBjKDEsMSkpDQpwbG90KHkpDQojTXVsdGl2YXJpYXRlIHBsb3RzDQojc2NhdHRlcnBsb3QgbWF0cml4IG9mIGV2ZXJ5IHBhaXIgb2YgdmFyaWFibGVzLg0KZmVhdHVyZVBsb3QoeCA9IHgsIHkgPSB5KQ0KI1RoZSBwb2ludHMgZnJvbSBlYWNoIG9mIHRoZSAzIFNwZWNpZXMgYXBwZWFyIHRvIGJlIGNsdXN0ZXJlZCwgc28gY2lyY2xlIHRoZW0gdG8gaGlnaGxpZ2h0IHRoaXMgKCJlbGxpcHNlIikuDQpmZWF0dXJlUGxvdCh4ID0geCwgeSA9IHksIHBsb3QgPSAiZWxsaXBzZSIpDQojYm94cGxvdHMgYnkgU3BlY2llcyAoY2xhc3MpDQpmZWF0dXJlUGxvdCh4ID0geCwgeSA9IHksIHBsb3QgPSAiYm94IikNCiNXaGVuIHNwbGl0IGJ5IFNwZWNpZXMsIHRoZSBkaXN0cmlidXRpb24gb2YgZWFjaCB2YXJpYWJsZSAoeCkgaXMgZGlmZmVyZW50LiANCiM+Pj5UaGVyZSBhcHBlYXJzIHRvIGJlIGEgcmVsYXRpb25zaGlwIGJldHdlZW4gU3BlY2llcyBhbmQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBlYWNoIG9mIHRoZSB2YXJpYWJsZXMNCg0KI2RlbnNpdHkgcGxvdHMgZm9yIGVhY2ggYXR0cmlidXRlIGJ5IGNsYXNzIHZhbHVlDQpzY2FsZXMgPC0gbGlzdCh4ID0gbGlzdChyZWxhdGlvbiA9ICJmcmVlIiksIHkgPSBsaXN0KHJlbGF0aW9uID0gImZyZWUiKSApDQpmZWF0dXJlUGxvdCh4ID0geCwgeSA9IHksIHBsb3QgPSAiZGVuc2l0eSIsIHNjYWxlcyA9IHNjYWxlcykNCiMgc2hvd3MgdGhhdCB0aGUgZGlzdHJpYnV0aW9uIG9mIGVhY2ggYXR0cmlidXRlICh4KSBpcyBkaWZmZXJlbnQgYmFzZWQgb24gdGhlIHNwZWNpZXMNCmBgYA0KDQoNCiMjIEV2YWx1YXRlIFNvbWUgQWxnb3JpdGhtcw0KDQpGaXZlIGFsZ29yaXRobXMgd2VyZSBldmFsdWF0ZWQgdXNpbmcgdGhlIHRyYWluaW5nIGRhdGFzZXQsIHRoZSBhbGdvcml0aG1zIGV2YXVsYXRlZCB3ZXJlOg0KDQoqIExpbmVhciBEaXNjcmltaW5hbnQgQW5hbHlzaXMgICAgICAgICAgKExEQSkgICAgICAgICAgICAgICAgICAgICAgIChzaW1wbGUgbGluZWFyKQ0KKiBDbGFzc2lmaWNhdGlvbiBhbmQgUmVncmVzc2lvbiBUcmVlcyAgIChDQVJUKSAgICAgICAgICAgICAgICAgICAgICAobm9uLWxpbmVhcikNCiogay1OZWFyZXN0IE5laWdoYm9ycyAgICAgICAgICAgICAgICAgICAoa05OKSAgICAgICAgICAgICAgICAgICAgICAgKG5vbi1saW5lYXIpDQoqIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzICAgICAgICAgICAgICAgKFNWTSkgd2l0aCBhIGxpbmVhciBrZXJuZWwgIChjb21wbGV4IG5vbi1saW5lYXIpDQoqIFJhbmRvbSBGb3Jlc3QgICAgICAgICAgICAgICAgICAgICAgICAgKFJGKSAgICAgICAgICAgICAgICAgICAgICAgIChjb21wbGV4IG5vbi1saW5lYXIpDQoNClRoZSBhbGdvcml0aG1zIHdlcmUgdXNlZCB0byBwcm9kdWNlIHByZWRpY3RpdmUgbW9kZWxzIGJ5IHRyYWluaW5nIHRoZW0gdXNpbmcgMTAtZm9sZCBjcm9zcyB2YWxpZGF0aW9uLiBUaGlzIGludm9sdmVkIHNwbGl0dGluZyB0aGUgdHJhaW5pbmcgZGF0YSBpbnRvIDEwIHBhcnRzOyA5IG9mIHdoaWNoIHdlcmUgdXNlZCB0byB0cmFpbiB0aGUgbW9kZWwgYW5kIDEgdG8gdGVzdCBpdCwgZm9yIGFsbCBjb21iaW5hdGlvbnMgb2YgdHJhaW4tdGVzdCBzcGxpdHMuIFRoaXMgd2FzIHJlcGVhdGVkIDMgdGltZXMgZm9yIGVhY2ggYWxnb3JpdGhtLCB1c2luZyBkaWZmZXJlbnQgc3BsaXRzIG9mIHRoZSBkYXRhIGluIGVhY2ggcmVwZXRpdGlvbi4NCg0KVGhlIG1vZGVscyB3ZXJlIHRoZW4gZXZhbHVhdGVkIGFjY29yZGluZyB0byBob3cgYWNjdXJhdGUgdGhleSB3ZXJlIHdoZW4gbWFraW5nIHByZWRpY3Rpb25zLiBJbiB0aGlzIGNhc2UsIHRoZSBwcmVkaWN0aW9ucyBvZiBlYWNoIG1vZGVsIHdlcmUgdXNlZCB0byBwcm9kdWNlIGEgcmF0aW8sIGJlaW5nIHRoZSBudW1iZXIgb2YgY29ycmVjdGx5IHByZWRpY3RlZCBpbnN0YW5jZXMgKGZsb3dlciBzcGVjaWVzKSBkaXZpZGVkIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2YgaW5zdGFuY2VzIChwcmVkaWN0aW9ucyBtYWRlKSBpbiB0aGUgZGF0YXNldC4NCg0KVGhlIExEQSBtb2RlbCB3YXMgaWRlbnRpZmllZCBhcyB0aGUgbW9zdCBhY2N1cmF0ZSBtb2RlbCwgaGF2aW5nIHRoZSBoaWdoZXN0IGFjY3VyYWN5IGFuZCBLYXBwYSBvZiBhbGwgbW9kZWxzIHByb2R1Y2VkLiBJbiB0aGUgdGVzdCBleGFtcGxlLCB0aGUgbW9kZWwgYWNjdXJhY3kgd2FzIDk3LjUlLCB3aXRoIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mICsvLSA1LjYlIChNYXJnaW4gb2YgRXJyb3IpLg0KDQpgYGB7cn0NCiMgUnVuIGFsZ29yaXRobXMgdXNpbmcgMTAtZm9sZCBjcm9zcyB2YWxpZGF0aW9uDQpjb250cm9sIDwtIHRyYWluQ29udHJvbChtZXRob2QgPSAiY3YiLCBudW1iZXIgPSAxMCkNCm1ldHJpYyA8LSAiQWNjdXJhY3kiDQojV2Ugd2lsbCBiZSB1c2luZyB0aGUgIm1ldHJpYyIgdmFyaWFibGUgY3JlYXRlZCBhYm92ZSB3aGVuIHdlIGJ1aWxkIGFuZCBldmFsdWF0ZSBlYWNoIG1vZGVsIChuZXh0KS4NCg0KI0J1aWxkIHRoZSA1IG1vZGVsczoNCiNNYWtlIHN1cmUgeW91IHJlc2V0IHRoZSByYW5kb20gbnVtYmVyIHNlZWQgYmVmb3JlIGVhY2ggcnVuIHRvIGVuc3VyZSB0aGF0IHRoZSBldmFsdWF0aW9uIG9mIGVhY2ggYWxnb3JpdGhtIGlzIHBlcmZvcm1lZCB1c2luZyBleGFjdGx5IHRoZSBzYW1lIGRhdGEgc3BsaXRzLiBUaGlzIGVuc3VyZXMgdGhlIHJlc3VsdHMgYXJlIGRpcmVjdGx5IGNvbXBhcmF2YmxlLg0KIyAxIC0gTERBDQpzZXQuc2VlZCg3KQ0KZml0LmxkYSA8LSB0cmFpbihTcGVjaWVzfi4sIGRhdGE9ZGF0YXNldCwgbWV0aG9kPSJsZGEiLCBtZXRyaWM9bWV0cmljLCB0ckNvbnRyb2w9Y29udHJvbCkNCiMgMiAtIENBUlQNCnNldC5zZWVkKDcpDQpmaXQuY2FydCA8LSB0cmFpbihTcGVjaWVzfi4sIGRhdGE9ZGF0YXNldCwgbWV0aG9kPSJycGFydCIsIG1ldHJpYz1tZXRyaWMsIHRyQ29udHJvbD1jb250cm9sKQ0KIyAzIC0ga05ODQpzZXQuc2VlZCg3KQ0KZml0LmtubiA8LSB0cmFpbihTcGVjaWVzfi4sIGRhdGE9ZGF0YXNldCwgbWV0aG9kPSJrbm4iLCBtZXRyaWM9bWV0cmljLCB0ckNvbnRyb2w9Y29udHJvbCkNCiMgNCAtIFNWTQ0Kc2V0LnNlZWQoNykNCmZpdC5zdm0gPC0gdHJhaW4oU3BlY2llc34uLCBkYXRhPWRhdGFzZXQsIG1ldGhvZD0ic3ZtUmFkaWFsIiwgbWV0cmljPW1ldHJpYywgdHJDb250cm9sPWNvbnRyb2wpDQojIDUgLSBSYW5kb20gRm9yZXN0DQpzZXQuc2VlZCg3KQ0KZml0LnJmIDwtIHRyYWluKFNwZWNpZXN+LiwgZGF0YT1kYXRhc2V0LCBtZXRob2Q9InJmIiwgbWV0cmljPW1ldHJpYywgdHJDb250cm9sPWNvbnRyb2wpDQojc3VtbWFyaXplIHRoZSBhY2N1cmFjeSBvZiB0aGUgbW9kZWxzIGFuZCBjb21wYXJlIHRoZW0gdG8gaWRlbnRpZnkgdGhlIGJlc3Qgb25lDQpyZXN1bHRzIDwtIHJlc2FtcGxlcyhsaXN0KGxkYT1maXQubGRhLCBjYXJ0PWZpdC5jYXJ0LCBrbm49Zml0Lmtubiwgc3ZtPWZpdC5zdm0sIHJmPWZpdC5yZikpDQpzdW1tYXJ5KHJlc3VsdHMpICMgS2FwcGEgZ2l2ZXMgdXMgdGhlIGFjY3VyYWN5IG9mIGVhY2ggbW9kZWwNCiNCZWNhdXNlIGVhY2ggbW9kZWwgd2FzIGV2YWx1YXRlZCAxMCB0aW1lcyAoMTAtZm9sZCBjcm9zcyB2YWxpZGF0aW9uKSB3ZSBoYXZlIGRhdGEgdGhhdCB3ZSBjYW4gcGxvdCAoZS5nLiB0aGUgbWVhbiBvZiBlYWNoIG1vZGVsKQ0KI0NyZWF0ZSBhIHBsb3Qgb2YgdGhlIHJlc3VsdHMgZnJvbSB0aGUgbW9kZWwgZXZhbHVhdGlvbiBhbmQgY29tcGFyZSB0aGUgc3ByZWFkIG9mIGVhY2ggbW9kZWwncyBtZWFuIGFjY3VyYWN5Lg0KZG90cGxvdChyZXN1bHRzKQ0KI1RoZSBiZXN0IG1vZGVsIGlzIHRoZSBMREEgbW9kZWwgYXMgaXQgaGFzIHRoZSBoaWdoZXN0IG1lYW4gYWNjdXJhY3kgYW5kIEthcHBhIG9mIGFsbCB0aG9zZSB0ZXN0ZWQNCnByaW50KGZpdC5sZGEpICNWaWV3IGEgc3VtbWFyeSBvZiB0aGUgTERBIG1vZGVsDQpmaXQubGRhJHJlc3VsdHMgI2FjY3VyYWN5IGluIGV4YW1wbGUgd2FzIDk3LjUlLiBTRCBpcyArLy01LjYlLCB0aGlzIGlzIHRoZSBNYXJnaW4gb2YgRXJyb3IgKE1vRSkuDQpgYGANCg0KDQojIyBSdW4gdGhlIG1vZGVsIG9uIHRoZSB2YWxpZGF0aW9uIGRhdGFzZXQNCg0KVGhlIExEQSBtb2RlbCB3YXMgdGhlbiB1c2VkIHRvIHByZWRpY3QgZmxvd2VyIHNwZWNpZXMgdXNpbmcgdGhlIHZhbGlkYXRpb24gZGF0YXNldCwgdG8gb2JzZXJ2ZSBpdHMgcGVyZm9ybWFuY2Ugb24gYSBkYXRhc2V0IGl0IGhhZCBub3QgeWV0IHNlZW4gYW5kIGNvbmZpcm0gdGhhdCB0aGUgYWNjdXJhY3kgb2YgdGhlIG1vZGVsIHdhcyB3aXRoaW4gdGhlIG1hcmdpbiBvZiBlcnJvciBvYnNlcnZlZCB1c2luZyB0aGUgdHJhaW5pbmcgZGF0YXNldC4gVGhlIHJlc3VsdHMgd2VyZSBwcmVzZW50ZWQgaW4gYSBjb25mdXNpb24gbWF0cml4LCBhIGNvbnZlbmllbnQgd2F5IG9mIGRpc3BsYXlpbmcgdGhlIG91dGNvbWVzIG9mIGEgcHJlZGljdGlvbiBtb2RlbC4gVGhpcyBhbHNvIHByb3ZpZGVkIGFuIG9wcG9ydHVuaXR5IHRvIHRlc3QgdGhlIG1vZGVsIGZvciBlcnJvcnMgc3VjaCBhcyBvdmVyZml0dGluZyBhbmQgZGF0YSBsZWFrYWdlLCB3aGljaCB3b3VsZCByZXN1bHQgaW4gYW4gYXJ0aWZpY2lhbGx5IHBvc2l0aXZlIHJlc3VsdC4gDQoNCkluIHRoZSBleGFtcGxlIHJ1biwgdGhlIG1vZGVsJ3MgYWNjdXJhY3kgKDEwMCUpIHdhcyB3aXRoaW4gdGhlIE1hcmdpbiBvZiBFcnJvciAoTW9FKSBvZiB0aGUgYWNjdXJhY3kgb2JzZXJ2ZWQgd2hlbiBwcm9kdWNpbmcgdGhlIG1vZGVsICg5Ny41ICsvLSA1LjYgPSBbMTAwLCA5MS45XSkuIFRoZXJlZm9yZSwgaXQgd2FzIGxpa2VseSB0aGF0IHRoZSBtb2RlbCB3YXMgYWNjdXJhdGUgYW5kIHJlbGlhYmxlLCBhbmQgdW5saWtlbHkgdGhhdCBlcnJvcnMgc3VjaCBhcyBvdmVyZml0dGluZyBvciBkYXRhIGxlYWthZ2UgaGFkIG9jY3VycmVkLg0KDQpgYGB7cn0NCiNSdW4gdGhlIExEQSBtb2RlbCBvbiB0aGUgdmFsaWRhdGlvbiBkYXRhc2V0IGFuZCBzdW1tYXJpc2UgdGhlIHJlc3VsdHMgaW4gYSAiY29uZnVzaW9uIG1hdHJpeCINCnByZWRpY3Rpb25zIDwtIHByZWRpY3QoZml0LmxkYSwgdmFsaWRhdGlvbikNCmNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9ucywgdmFsaWRhdGlvbiRTcGVjaWVzKSAjQ2hlY2sgdGhhdCB0aGUgYWNjdXJhY3kgaXMgd2l0aGluIHRoZSBleHBlY3RlZCBNb0UNCiNGb3IgdGhlIG1vZGVsIHRvIGJlIGNvbnNpZGVyZWQgcmVsaWFibGUsIGl0cyBwcmVkaWN0aW9uIGFjY3VyYWN5IHNob3VsZCBiZSB3aXRoaW4gdGhlIE1vRSBhcm91bmQgdGhlIHRlc3QgYWNjdXJhY3k6IDk4LjMgKy8tIDMuNSA9IFsxMDAsIDk0LjhdDQpleGFtcGxlX2RhdGEgPC0gaXJpc1s0MCwxOjRdDQpwcmVkaWN0KGZpdC5sZGEsIGV4YW1wbGVfZGF0YSkgI3ByZWRpY3RzIHRoZSBTcGVjaWVzIG9mIHRoZSBkYXRhIHlvdSBzZWxlY3QgYXMgImV4YW1wbGVfZGF0YSINCmBgYA0KDQojIyBVc2UgdGhlIG1vZGVsIHRvIHByZWRpY3QgZmxvd2VyIHNwZWNpZXMuDQoNClRoZSBtb2RlbCB3YXMgdGhlbiB1c2VkIHRvIHByb3ZpZGUgYW4gZXhhbXBsZSBvZiB3aGF0IGl0IGNvdWxkIGRvLiBJbiB0aGlzIGNhc2UsIHRoZSBMREEgbW9kZWwgd2FzIHJ1biBvbiBhIHNlbGVjdGVkIHNldCBvZiBwZXRhbCBhbmQgc2VwYWwgbGVuZ3RocyBhbmQgd2lkdGhzIGZyb20gdGhlIElyaXMgZGF0YXNldCBhbmQgdGhlIHByZWRpY3Rpb24gd2FzIHRoZW4gY29tcGFyZWQgd2l0aCB3aGF0IHdhcyBpbiB0aGUgSXJpcyBkYXRhc2V0IHRvIG9ic2VydmUgd2hldGhlciB0aGUgbW9kZWwgY29ycmVjdGx5IHByZWRpY3RlZCB0aGUgZmxvd2VyIHNwZWNpZXMuIFRoZSBleGFtcGxlIHdhcyB0YWtlbiBmcm9tIHJvdyA0MCBvZiB0aGUgSXJpcyBkYXRhc2V0LCB3aGVyZSB0aGUgZmxvd2VyIHNwZWNpZXMgd2FzICJzZXRvc2EiLg0KDQpgYGB7cn0NCmV4YW1wbGVfZGF0YSA8LSBpcmlzWzQwLDE6NF0NCnByZWRpY3QoZml0LmxkYSwgZXhhbXBsZV9kYXRhKSAjcHJlZGljdHMgdGhlIFNwZWNpZXMgb2YgdGhlIGRhdGEgeW91IHNlbGVjdCBhcyAiZXhhbXBsZV9kYXRhIg0KYGBgDQoNCiMjIFJlZmVyZW5jZXMNCg0KRXhwZXJ0c3lzdGVtLmNvbSwgMjAyMC4gV2hhdCBpcyBNYWNoaW5lIExlYXJuaW5nPyBBIGRlZmluaXRpb24sIHZpZXdlZCAwOCBKdW5lIDIwMjAsIDxodHRwczovL2V4cGVydHN5c3RlbS5jb20vbWFjaGluZS1sZWFybmluZy1kZWZpbml0aW9uLyM6fjp0ZXh0PU1hY2hpbmUlMjBsZWFybmluZyUyMGlzJTIwYW4lMjBhcHBsaWNhdGlvbix1c2UlMjBpdCUyMGxlYXJuJTIwZm9yJTIwdGhlbXNlbHZlcy4+DQoNCkJyb3dubGVlLCBKLiwgMjAxNi4gTWFjaGluZSBMZWFybmluZyBNYXN0ZXJ5LCBZb3VyIEZpcnN0IE1hY2hpbmUgTGVhcm5pbmcgUHJvamVjdCBpbiBSIFN0ZXAtQnktU3RlcCwgdmlld2VkIDE2IE1heSAyMDIwLCA8aHR0cHM6Ly9tYWNoaW5lbGVhcm5pbmdtYXN0ZXJ5LmNvbS9tYWNoaW5lLWxlYXJuaW5nLWluLXItc3RlcC1ieS1zdGVwLz4NCg0KPGJyPg0KPGJyPg0K