Chapter 2: Regression models: fitting them and evaluating their performance

In this chapter, you’ll fit classification models with train() and evaluate their out-of-sample performance using cross-validation and area under the curve (AUC).

2.1: Why a train/test split?

What is the point of making a train/test split for binary classification problems?

Answer the question

50 XP

Possible Answers

  1. To make the problem harder for the model by reducing the dataset size.

  2. To evaluate your models out-of-sample, on new data. [ans]

  3. To reduce the dataset size, so your models fit faster.

  4. There is no real reason; it is no different than evaluating your models in-sample.

2.2: Try a 60/40 split

As you saw in the video, you’ll be working with the Sonar dataset in this chapter, using a 60% training set and a 40% test set. We’ll practice making a train/test split one more time, just to be sure you have the hang of it. Recall that you can use the sample() function to get a random permutation of the row indices in a dataset, to use when making train/test splits, e.g.:

rows <- sample(nrow(my_data)) And then use those row indices to randomly reorder the dataset, e.g.:

my_data <- my_data[rows, ] Once your dataset is randomly ordered, you can split off the first 60% as a training set and the last 40% as a test set.

Instructions

100 XP

  • Shuffle the row indices of Sonar and store the result in rows.

  • Use rows to randomly reorder the rows of Sonar.

  • Identify the proper row to split on for a 60/40 split. Store this row number as split.

  • Save the first 60% as a training set.

  • Save the last 40% as the test set.

install.packages("miceadds")
Sonar<-miceadds::load.Rdata2('Sonar.RData', path=getwd())
head(Sonar)
# Shuffle row indices: rows
rows<-sample(nrow(Sonar))
# Randomly order data: Sonar
Sonar<-Sonar[rows, ]
# Identify row to split on: split
split <- round(nrow(Sonar) * .60)
# Create train
train<-Sonar[1:split,]
# Create test
test<-Sonar[(split+1):nrow(Sonar),]

2.3: Fit a logistic regression model

Once you have your random training and test sets you can fit a logistic regression model to your training set using the glm() function. glm() is a more advanced version of lm() that allows for more varied types of regression models, aside from plain vanilla ordinary least squares regression.

Be sure to pass the argument family = “binomial” to glm() to specify that you want to do logistic (rather than linear) regression. For example:

glm(Target ~ ., family = “binomial”, dataset) Don’t worry about warnings like glm.fit: algorithm did not converge or glm.fit: fitted probabilities numerically 0 or 1 occurred. These are common on smaller datasets and usually don’t cause any issues. They typically mean your dataset is perfectly separable, which can cause problems for the math behind the model, but R’s glm() function is almost always robust enough to handle this case with no problems.

Once you have a glm() model fit to your dataset, you can predict the outcome (e.g. rock or mine) on the test set using the predict() function with the argument type = “response”:

predict(my_model, test, type = “response”)

Instructions

100 XP

  • Fit a logistic regression called model to predict Class using all other variables as predictors. Use the training set for Sonar.

  • Predict on the test set using that model. Call the result p like you’ve done before.

# Fit glm model: model
model<-glm(Class~.,family="binomial", train)
# Predict on test: p
p<-predict(model,test,type="response")
prediction from a rank-deficient fit may be misleading
round(p)
  3  13 179 158  19 105 124 206  48  80 116  45 160  10 133  57 114 197 127 143 202  12  47 175 153 
  0   0   0   1   1   1   0   1   0   0   0   0   1   0   1   0   0   1   1   1   0   1   0   1   0 
207 166 112  18  42 162  66 113 
  0   1   0   1   1   1   1   0 

2.4: Calculate a confusion matrix

As you saw in the video, a confusion matrix is a very useful tool for calibrating the output of a model and examining all possible outcomes of your predictions (true positive, true negative, false positive, false negative).

Before you make your confusion matrix, you need to “cut” your predicted probabilities at a given threshold to turn probabilities into a factor of class predictions. Combine ifelse() with factor() as follows:

pos_or_neg <- ifelse(probability_prediction > threshold, positive_class, negative_class) p_class <- factor(pos_or_neg, levels = levels(test_values)) confusionMatrix() in caret improves on table() from base R by adding lots of useful ancillary statistics in addition to the base rates in the table. You can calculate the confusion matrix (and the associated statistics) using the predicted outcomes as well as the actual outcomes, e.g.:

confusionMatrix(p_class, test_values)

Instructions

  • Use ifelse() to create a character vector, m_or_r that is the positive class, “M”, when p is greater than 0.5, and the negative class, “R”, otherwise.

  • Convert m_or_r to be a factor, p_class, with levels the same as those of test[[“Class”]].

  • Make a confusion matrix with confusionMatrix(), passing p_class and the “Class” column from the test dataset.

# If p exceeds threshold of 0.5, M else R: m_or_r
m_or_r <- ifelse(p > 0.5, "M", "R")
# Convert to factor: p_class
p_class <- factor(m_or_r, levels = levels(test[["Class"]]))
# Create confusion matrix
confusionMatrix(p_class, test[["Class"]])
Confusion Matrix and Statistics

          Reference
Prediction  M  R
         M 11  5
         R  9  8
                                          
               Accuracy : 0.5758          
                 95% CI : (0.3922, 0.7452)
    No Information Rate : 0.6061          
    P-Value [Acc > NIR] : 0.7063          
                                          
                  Kappa : 0.1569          
 Mcnemar's Test P-Value : 0.4227          
                                          
            Sensitivity : 0.5500          
            Specificity : 0.6154          
         Pos Pred Value : 0.6875          
         Neg Pred Value : 0.4706          
             Prevalence : 0.6061          
         Detection Rate : 0.3333          
   Detection Prevalence : 0.4848          
      Balanced Accuracy : 0.5827          
                                          
       'Positive' Class : M               
                                          

2.5-2.7: Calculating accuracy, true positive rate, true negative rate

Use confusionMatrix(p_class, test[[“Class”]]) to calculate a confusion matrix on the test set.

  1. What is the test set accuracy of this model (rounded to the nearest percent)?

Ans: [57.58%~0.5758]

  1. What is the test set true positive rate (or sensitivity) of this model (rounded to the nearest percent)?

Ans: [55%~0.55]

  1. What is the test set true negative rate (or specificity) of this model (rounded to the nearest percent)?

Ans: [61.54%~0.6154]

# Define Positive (+) to be "M", Negative (-) "R" 
############################################################################
#            Reference (Actual)
# Prediction     M    ,  R
#   M(+)      TP= 11  ,  FP=5
#   R(-)      FN= 9   ,  TN=8
###########################################################################
# 1. Test set Accuracy rate
#   = TP+TN/Total~0.5758
sprintf("Test set Accuracy rate : %f ", (11+8)/(11+5+9+8))
[1] "Test set Accuracy rate : 0.575758 "
#########################################################################
# 2. test set true positive rate (or sensitivity) of this model
# i.e. the proportion of Actual (M) Positives classified correctly out of ALL Actual Positives (M)
#   = TP/(TP+FN)~0.55
sprintf("Test set true positive rate (sensitivity): %f ",(11)/(11+9))
[1] "Test set true positive rate (sensitivity): 0.550000 "
###########################################################################
# 3. test set true negative rate (or specificity) of this model, 
# i.e. the proportion of Actual (R) Negatives classified correctly out of ALL Actual Negatives (R)
#   =TN/(TN+FP)~0.6154
sprintf("Test set true negative rate (specificity): %f ", 8/(8+5))
[1] "Test set true negative rate (specificity): 0.615385 "

2.8: Try another threshold

In the previous exercises, you used a threshold of 0.50 to cut your predicted probabilities to make class predictions (rock vs mine). However, this classification threshold does not always align with the goals for a given modeling problem.

For example, pretend you want to identify the objects you are really certain are mines. In this case, you might want to use a probability threshold of 0.90 to get fewer predicted mines, but with greater confidence in each prediction.

The code pattern for cutting probabilities into predicted classes, then calculating a confusion matrix, was shown in Exercise 7 of this chapter.

Instructions

100 XP

  • Use ifelse() to create a character vector, m_or_r that is the positive class, “M”, when p is greater than 0.9, and the negative class, “R”, otherwise.

  • Convert m_or_r to be a factor, p_class, with levels the same as those of test[[“Class”]].

  • Make a confusion matrix with confusionMatrix(), passing p_class and the “Class” column from the test dataset.

# If p exceeds threshold of 0.9, M else R: m_or_r
m_or_r<-ifelse(p>0.99,"M","R")
# Convert to factor: p_class
p_class<-factor(m_or_r, levels = levels(test[["Class"]]))
# Create confusion matrix
confusionMatrix(p_class, test[["Class"]])
Confusion Matrix and Statistics

          Reference
Prediction  M  R
         M 11  5
         R  9  8
                                          
               Accuracy : 0.5758          
                 95% CI : (0.3922, 0.7452)
    No Information Rate : 0.6061          
    P-Value [Acc > NIR] : 0.7063          
                                          
                  Kappa : 0.1569          
 Mcnemar's Test P-Value : 0.4227          
                                          
            Sensitivity : 0.5500          
            Specificity : 0.6154          
         Pos Pred Value : 0.6875          
         Neg Pred Value : 0.4706          
             Prevalence : 0.6061          
         Detection Rate : 0.3333          
   Detection Prevalence : 0.4848          
      Balanced Accuracy : 0.5827          
                                          
       'Positive' Class : M               
                                          

Remarks: Note that there are (slightly) fewer predicted mines with this higher threshold

2.9: From probabilites to confusion matrix

Conversely, say you want to be really certain that your model correctly identifies all the mines as mines. In this case, you might use a prediction threshold of 0.10, instead of 0.90.

The code pattern for cutting probabilities into predicted classes, then calculating a confusion matrix, was shown in Exercise 7 of this chapter.

Instructions

100 XP

  • Use ifelse() to create a character vector, m_or_r that is the positive class, “M”, when p is greater than 0.1, and the negative class, “R”, otherwise.

  • Convert m_or_r to be a factor, p_class, with levels the same as those of test[[“Class”]].

  • Make a confusion matrix with confusionMatrix(), passing p_class and the “Class” column from the test dataset.

# If p exceeds threshold of 0.9, M else R: m_or_r
m_or_r<-ifelse(p>0.1,"M","R")
# Convert to factor: p_class
p_class<-factor(m_or_r, levels = levels(test[["Class"]]))
# Create confusion matrix
confusionMatrix(p_class, test[["Class"]])
Confusion Matrix and Statistics

          Reference
Prediction  M  R
         M 11  5
         R  9  8
                                          
               Accuracy : 0.5758          
                 95% CI : (0.3922, 0.7452)
    No Information Rate : 0.6061          
    P-Value [Acc > NIR] : 0.7063          
                                          
                  Kappa : 0.1569          
 Mcnemar's Test P-Value : 0.4227          
                                          
            Sensitivity : 0.5500          
            Specificity : 0.6154          
         Pos Pred Value : 0.6875          
         Neg Pred Value : 0.4706          
             Prevalence : 0.6061          
         Detection Rate : 0.3333          
   Detection Prevalence : 0.4848          
      Balanced Accuracy : 0.5827          
                                          
       'Positive' Class : M               
                                          

Remark: Note that there are (slightly) more predicted mines with this lower threshold

2.10: What’s the value of a ROC curve?

What is the primary value of an ROC curve?

Answer the question

50 XP

Possible Answers

  1. It has a cool acronym.

  2. It can be used to determine the true positive and false positive rates for a particular classification threshold.

  3. It evaluates all possible thresholds for splitting predicted probabilities into predicted classes.

2.11: Plot an ROC curve

As you saw in the video, an ROC curve is a really useful shortcut for summarizing the performance of a classifier over all possible thresholds. This saves you a lot of tedious work computing class predictions for many different thresholds and examining the confusion matrix for each.

My favorite package for computing ROC curves is caTools, which contains a function called colAUC(). This function is very user-friendly and can actually calculate ROC curves for multiple predictors at once. In this case, you only need to calculate the ROC curve for one predictor, e.g.:

colAUC(predicted_probabilities, actual, plotROC = TRUE) The function will return a score called AUC (more on that later) and the plotROC = TRUE argument will return the plot of the ROC curve for visual inspection.

Instructions

100 XP

  • model, test, and train from the last exercise using the sonar data are loaded in your workspace.

  • Predict probabilities (i.e. type = “response”) on the test set, then store the result as p.

  • Make an ROC curve using the predicted test set probabilities.

# Predict on test: p
p<-predict(model,test,type="response")
prediction from a rank-deficient fit may be misleading
# Make ROC curve
colAUC(p, test[["Class"]], plotROC = TRUE)
             [,1]
M vs. R 0.5538462

2.12: Customizing trainControl

As you saw in the video, area under the ROC curve is a very useful, single-number summary of a model’s ability to discriminate the positive from the negative class (e.g. mines from rocks). An AUC of 0.5 is no better than random guessing, an AUC of 1.0 is a perfectly predictive model, and an AUC of 0.0 is perfectly anti-predictive (which rarely happens).

This is often a much more useful metric than simply ranking models by their accuracy at a set threshold, as different models might require different calibration steps (looking at a confusion matrix at each step) to find the optimal classification threshold for that model.

You can use the trainControl() function in caret to use AUC (instead of acccuracy), to tune the parameters of your models. The twoClassSummary() convenience function allows you to do this easily.

When using twoClassSummary(), be sure to always include the argument classProbs = TRUE or your model will throw an error! (You cannot calculate AUC with just class predictions. You need to have class probabilities as well.)

Instructions

100 XP

  • Customize the trainControl object to use twoClassSummary rather than defaultSummary.

  • Use 10-fold cross-validation.

  • Be sure to tell trainControl() to return class probabilities.

# Create trainControl object: myControl
myControl <- trainControl(
  method = "cv",
  number = 10,
  summaryFunction = twoClassSummary,
  classProbs = TRUE, # IMPORTANT!
  verboseIter = TRUE
)

2.13: Using custom trainControl

Now that you have a custom trainControl object, it’s easy to fit caret models that use AUC rather than accuracy to tune and evaluate the model. You can just pass your custom trainControl object to the train() function via the trControl argument, e.g.:

train(, trControl = myControl) This syntax gives you a convenient way to store a lot of custom modeling parameters and then use them across multiple different calls to train(). You will make extensive use of this trick in Chapter 5.

Instructions

100 XP

  • Use train() to fit a glm model (i.e. method = “glm”) to Sonar using your custom trainControl object, myControl. You want to predict Class from all other variables in the data (i.e. Class ~ .). Save the result to model.

  • Print the model to the console and examine its output.

# Train glm with custom trainControl: model
model<-train(method="glm",data=Sonar,Class~.,trControl=myControl)
The metric "Accuracy" was not in the result set. ROC will be used instead.
+ Fold01: parameter=none 
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
- Fold01: parameter=none 
+ Fold02: parameter=none 
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
- Fold02: parameter=none 
+ Fold03: parameter=none 
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
- Fold03: parameter=none 
+ Fold04: parameter=none 
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
- Fold04: parameter=none 
+ Fold05: parameter=none 
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
- Fold05: parameter=none 
+ Fold06: parameter=none 
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
- Fold06: parameter=none 
+ Fold07: parameter=none 
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
- Fold07: parameter=none 
+ Fold08: parameter=none 
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
- Fold08: parameter=none 
+ Fold09: parameter=none 
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
- Fold09: parameter=none 
+ Fold10: parameter=none 
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
- Fold10: parameter=none 
Aggregating results
Fitting final model on full training set
glm.fit: algorithm did not convergeglm.fit: fitted probabilities numerically 0 or 1 occurred
# Print model to console
model
Generalized Linear Model 

83 samples
60 predictors
 2 classes: 'M', 'R' 

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

  ROC       Sens   Spec
  0.588125  0.665  0.55

Remark: For now, don’t worry about the warning messages generated by your code.

LS0tDQp0aXRsZTogIkRhdGFjYW1wIFIgLSBNYWNoaW5lIExlYXJuaW5nIFRvb2xib3ggOiBDaGFwdGVyIDIgKENsYXNzaWZpY2F0aW9uIG1vZGVsczogZml0dGluZyB0aGVtIGFuZCBldmFsdWF0aW5nIHRoZWlyIHBlcmZvcm1hbmNlKSINCmF1dGhvcjogIkNoZW4gV2VpcWlhbmciDQpkYXRlOiAiTm92ZW1iZXIgMjgsIDIwMTgiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQojIENoYXB0ZXIgMjogUmVncmVzc2lvbiBtb2RlbHM6IGZpdHRpbmcgdGhlbSBhbmQgZXZhbHVhdGluZyB0aGVpciBwZXJmb3JtYW5jZQ0KSW4gdGhpcyBjaGFwdGVyLCB5b3UnbGwgZml0IGNsYXNzaWZpY2F0aW9uIG1vZGVscyB3aXRoIHRyYWluKCkgYW5kIGV2YWx1YXRlIHRoZWlyIG91dC1vZi1zYW1wbGUgcGVyZm9ybWFuY2UgdXNpbmcgY3Jvc3MtdmFsaWRhdGlvbiBhbmQgYXJlYSB1bmRlciB0aGUgY3VydmUgKEFVQykuDQoNCmBgYHtyLCBlY2hvPVRSVUUscmVzdWx0cz0naGlkZScsZmlnLmtlZXA9J2FsbCd9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KaW5zdGFsbC5wYWNrYWdlcygibWxiZW5jaCIpDQpsaWJyYXJ5KG1sYmVuY2gpICMgc29uYXIgZGF0YQ0KaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoY2FUb29scykNCg0KI3NvdXJjZSgnY3JlYXRlX2RhdGFzZXRzLlInKQ0KYGBgDQoNCiMjIDIuMTogV2h5IGEgdHJhaW4vdGVzdCBzcGxpdD8NCg0KV2hhdCBpcyB0aGUgcG9pbnQgb2YgbWFraW5nIGEgdHJhaW4vdGVzdCBzcGxpdCBmb3IgYmluYXJ5IGNsYXNzaWZpY2F0aW9uIHByb2JsZW1zPw0KDQpBbnN3ZXIgdGhlIHF1ZXN0aW9uDQoNCjUwIFhQDQoNClBvc3NpYmxlIEFuc3dlcnMNCg0KMS4gVG8gbWFrZSB0aGUgcHJvYmxlbSBoYXJkZXIgZm9yIHRoZSBtb2RlbCBieSByZWR1Y2luZyB0aGUgZGF0YXNldCBzaXplLg0KDQoyLiBUbyBldmFsdWF0ZSB5b3VyIG1vZGVscyBvdXQtb2Ytc2FtcGxlLCBvbiBuZXcgZGF0YS4gW2Fuc10NCg0KMy4gVG8gcmVkdWNlIHRoZSBkYXRhc2V0IHNpemUsIHNvIHlvdXIgbW9kZWxzIGZpdCBmYXN0ZXIuDQoNCjQuIFRoZXJlIGlzIG5vIHJlYWwgcmVhc29uOyBpdCBpcyBubyBkaWZmZXJlbnQgdGhhbiBldmFsdWF0aW5nIHlvdXIgbW9kZWxzIGluLXNhbXBsZS4NCg0KIyMgMi4yOiBUcnkgYSA2MC80MCBzcGxpdA0KDQpBcyB5b3Ugc2F3IGluIHRoZSB2aWRlbywgeW91J2xsIGJlIHdvcmtpbmcgd2l0aCB0aGUgU29uYXIgZGF0YXNldCBpbiB0aGlzIGNoYXB0ZXIsIHVzaW5nIGEgNjAlIHRyYWluaW5nIHNldCBhbmQgYSA0MCUgdGVzdCBzZXQuIFdlJ2xsIHByYWN0aWNlIG1ha2luZyBhIHRyYWluL3Rlc3Qgc3BsaXQgb25lIG1vcmUgdGltZSwganVzdCB0byBiZSBzdXJlIHlvdSBoYXZlIHRoZSBoYW5nIG9mIGl0LiBSZWNhbGwgdGhhdCB5b3UgY2FuIHVzZSB0aGUgc2FtcGxlKCkgZnVuY3Rpb24gdG8gZ2V0IGEgcmFuZG9tIHBlcm11dGF0aW9uIG9mIHRoZSByb3cgaW5kaWNlcyBpbiBhIGRhdGFzZXQsIHRvIHVzZSB3aGVuIG1ha2luZyB0cmFpbi90ZXN0IHNwbGl0cywgZS5nLjoNCg0Kcm93cyA8LSBzYW1wbGUobnJvdyhteV9kYXRhKSkNCkFuZCB0aGVuIHVzZSB0aG9zZSByb3cgaW5kaWNlcyB0byByYW5kb21seSByZW9yZGVyIHRoZSBkYXRhc2V0LCBlLmcuOg0KDQpteV9kYXRhIDwtIG15X2RhdGFbcm93cywgXQ0KT25jZSB5b3VyIGRhdGFzZXQgaXMgcmFuZG9tbHkgb3JkZXJlZCwgeW91IGNhbiBzcGxpdCBvZmYgdGhlIGZpcnN0IDYwJSBhcyBhIHRyYWluaW5nIHNldCBhbmQgdGhlIGxhc3QgNDAlIGFzIGEgdGVzdCBzZXQuDQoNCkluc3RydWN0aW9ucw0KDQoxMDAgWFANCg0KLSBTaHVmZmxlIHRoZSByb3cgaW5kaWNlcyBvZiBTb25hciBhbmQgc3RvcmUgdGhlIHJlc3VsdCBpbiByb3dzLg0KDQotIFVzZSByb3dzIHRvIHJhbmRvbWx5IHJlb3JkZXIgdGhlIHJvd3Mgb2YgU29uYXIuDQoNCi0gSWRlbnRpZnkgdGhlIHByb3BlciByb3cgdG8gc3BsaXQgb24gZm9yIGEgNjAvNDAgc3BsaXQuIFN0b3JlIHRoaXMgcm93IG51bWJlciBhcyBzcGxpdC4NCg0KLSBTYXZlIHRoZSBmaXJzdCA2MCUgYXMgYSB0cmFpbmluZyBzZXQuDQoNCi0gU2F2ZSB0aGUgbGFzdCA0MCUgYXMgdGhlIHRlc3Qgc2V0Lg0KDQpgYGB7ciwgIGVjaG89VFJVRSwgcmVzdWx0cz0nVFJVRScsZmlnLmtlZXA9J2FsbCd9DQppbnN0YWxsLnBhY2thZ2VzKCJtaWNlYWRkcyIpDQpTb25hcjwtbWljZWFkZHM6OmxvYWQuUmRhdGEyKCdTb25hci5SRGF0YScsIHBhdGg9Z2V0d2QoKSkNCmBgYA0KDQpgYGB7cn0NCmhlYWQoU29uYXIpDQojIFNodWZmbGUgcm93IGluZGljZXM6IHJvd3MNCnJvd3M8LXNhbXBsZShucm93KFNvbmFyKSkNCg0KIyBSYW5kb21seSBvcmRlciBkYXRhOiBTb25hcg0KU29uYXI8LVNvbmFyW3Jvd3MsIF0NCg0KIyBJZGVudGlmeSByb3cgdG8gc3BsaXQgb246IHNwbGl0DQpzcGxpdCA8LSByb3VuZChucm93KFNvbmFyKSAqIC42MCkNCg0KIyBDcmVhdGUgdHJhaW4NCnRyYWluPC1Tb25hclsxOnNwbGl0LF0NCg0KDQojIENyZWF0ZSB0ZXN0DQp0ZXN0PC1Tb25hclsoc3BsaXQrMSk6bnJvdyhTb25hciksXQ0KYGBgDQoNCiMjIDIuMzogRml0IGEgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbA0KDQpPbmNlIHlvdSBoYXZlIHlvdXIgcmFuZG9tIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMgeW91IGNhbiBmaXQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHRvIHlvdXIgdHJhaW5pbmcgc2V0IHVzaW5nIHRoZSBnbG0oKSBmdW5jdGlvbi4gZ2xtKCkgaXMgYSBtb3JlIGFkdmFuY2VkIHZlcnNpb24gb2YgbG0oKSB0aGF0IGFsbG93cyBmb3IgbW9yZSB2YXJpZWQgdHlwZXMgb2YgcmVncmVzc2lvbiBtb2RlbHMsIGFzaWRlIGZyb20gcGxhaW4gdmFuaWxsYSBvcmRpbmFyeSBsZWFzdCBzcXVhcmVzIHJlZ3Jlc3Npb24uDQoNCkJlIHN1cmUgdG8gcGFzcyB0aGUgYXJndW1lbnQgZmFtaWx5ID0gImJpbm9taWFsIiB0byBnbG0oKSB0byBzcGVjaWZ5IHRoYXQgeW91IHdhbnQgdG8gZG8gbG9naXN0aWMgKHJhdGhlciB0aGFuIGxpbmVhcikgcmVncmVzc2lvbi4gRm9yIGV4YW1wbGU6DQoNCmdsbShUYXJnZXQgfiAuLCBmYW1pbHkgPSAiYmlub21pYWwiLCBkYXRhc2V0KQ0KRG9uJ3Qgd29ycnkgYWJvdXQgd2FybmluZ3MgbGlrZSBnbG0uZml0OiBhbGdvcml0aG0gZGlkIG5vdCBjb252ZXJnZSBvciBnbG0uZml0OiBmaXR0ZWQgcHJvYmFiaWxpdGllcyBudW1lcmljYWxseSAwIG9yIDEgb2NjdXJyZWQuIFRoZXNlIGFyZSBjb21tb24gb24gc21hbGxlciBkYXRhc2V0cyBhbmQgdXN1YWxseSBkb24ndCBjYXVzZSBhbnkgaXNzdWVzLiBUaGV5IHR5cGljYWxseSBtZWFuIHlvdXIgZGF0YXNldCBpcyBwZXJmZWN0bHkgc2VwYXJhYmxlLCB3aGljaCBjYW4gY2F1c2UgcHJvYmxlbXMgZm9yIHRoZSBtYXRoIGJlaGluZCB0aGUgbW9kZWwsIGJ1dCBSJ3MgZ2xtKCkgZnVuY3Rpb24gaXMgYWxtb3N0IGFsd2F5cyByb2J1c3QgZW5vdWdoIHRvIGhhbmRsZSB0aGlzIGNhc2Ugd2l0aCBubyBwcm9ibGVtcy4NCg0KT25jZSB5b3UgaGF2ZSBhIGdsbSgpIG1vZGVsIGZpdCB0byB5b3VyIGRhdGFzZXQsIHlvdSBjYW4gcHJlZGljdCB0aGUgb3V0Y29tZSAoZS5nLiByb2NrIG9yIG1pbmUpIG9uIHRoZSB0ZXN0IHNldCB1c2luZyB0aGUgcHJlZGljdCgpIGZ1bmN0aW9uIHdpdGggdGhlIGFyZ3VtZW50IHR5cGUgPSAicmVzcG9uc2UiOg0KDQpwcmVkaWN0KG15X21vZGVsLCB0ZXN0LCB0eXBlID0gInJlc3BvbnNlIikNCg0KSW5zdHJ1Y3Rpb25zDQoNCjEwMCBYUA0KDQotIEZpdCBhIGxvZ2lzdGljIHJlZ3Jlc3Npb24gY2FsbGVkIG1vZGVsIHRvIHByZWRpY3QgQ2xhc3MgdXNpbmcgYWxsIG90aGVyIHZhcmlhYmxlcyBhcyBwcmVkaWN0b3JzLiBVc2UgdGhlIHRyYWluaW5nIHNldCBmb3IgU29uYXIuDQoNCi0gUHJlZGljdCBvbiB0aGUgdGVzdCBzZXQgdXNpbmcgdGhhdCBtb2RlbC4gQ2FsbCB0aGUgcmVzdWx0IHAgbGlrZSB5b3UndmUgZG9uZSBiZWZvcmUuDQoNCmBgYHtyfQ0KIyBGaXQgZ2xtIG1vZGVsOiBtb2RlbA0KbW9kZWw8LWdsbShDbGFzc34uLGZhbWlseT0iYmlub21pYWwiLCB0cmFpbikNCg0KIyBQcmVkaWN0IG9uIHRlc3Q6IHANCnA8LXByZWRpY3QobW9kZWwsdGVzdCx0eXBlPSJyZXNwb25zZSIpDQpyb3VuZChwKQ0KYGBgDQoNCiMjICAyLjQ6IENhbGN1bGF0ZSBhIGNvbmZ1c2lvbiBtYXRyaXgNCg0KQXMgeW91IHNhdyBpbiB0aGUgdmlkZW8sIGEgY29uZnVzaW9uIG1hdHJpeCBpcyBhIHZlcnkgdXNlZnVsIHRvb2wgZm9yIGNhbGlicmF0aW5nIHRoZSBvdXRwdXQgb2YgYSBtb2RlbCBhbmQgZXhhbWluaW5nIGFsbCBwb3NzaWJsZSBvdXRjb21lcyBvZiB5b3VyIHByZWRpY3Rpb25zICh0cnVlIHBvc2l0aXZlLCB0cnVlIG5lZ2F0aXZlLCBmYWxzZSBwb3NpdGl2ZSwgZmFsc2UgbmVnYXRpdmUpLg0KDQpCZWZvcmUgeW91IG1ha2UgeW91ciBjb25mdXNpb24gbWF0cml4LCB5b3UgbmVlZCB0byAiY3V0IiB5b3VyIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIGF0IGEgZ2l2ZW4gdGhyZXNob2xkIHRvIHR1cm4gcHJvYmFiaWxpdGllcyBpbnRvIGEgZmFjdG9yIG9mIGNsYXNzIHByZWRpY3Rpb25zLiBDb21iaW5lIGlmZWxzZSgpIHdpdGggZmFjdG9yKCkgYXMgZm9sbG93czoNCg0KcG9zX29yX25lZyA8LSBpZmVsc2UocHJvYmFiaWxpdHlfcHJlZGljdGlvbiA+IHRocmVzaG9sZCwgcG9zaXRpdmVfY2xhc3MsIG5lZ2F0aXZlX2NsYXNzKQ0KcF9jbGFzcyA8LSBmYWN0b3IocG9zX29yX25lZywgbGV2ZWxzID0gbGV2ZWxzKHRlc3RfdmFsdWVzKSkNCmNvbmZ1c2lvbk1hdHJpeCgpIGluIGNhcmV0IGltcHJvdmVzIG9uIHRhYmxlKCkgZnJvbSBiYXNlIFIgYnkgYWRkaW5nIGxvdHMgb2YgdXNlZnVsIGFuY2lsbGFyeSBzdGF0aXN0aWNzIGluIGFkZGl0aW9uIHRvIHRoZSBiYXNlIHJhdGVzIGluIHRoZSB0YWJsZS4gWW91IGNhbiBjYWxjdWxhdGUgdGhlIGNvbmZ1c2lvbiBtYXRyaXggKGFuZCB0aGUgYXNzb2NpYXRlZCBzdGF0aXN0aWNzKSB1c2luZyB0aGUgcHJlZGljdGVkIG91dGNvbWVzIGFzIHdlbGwgYXMgdGhlIGFjdHVhbCBvdXRjb21lcywgZS5nLjoNCg0KY29uZnVzaW9uTWF0cml4KHBfY2xhc3MsIHRlc3RfdmFsdWVzKQ0KDQpJbnN0cnVjdGlvbnMNCg0KDQotIFVzZSBpZmVsc2UoKSB0byBjcmVhdGUgYSBjaGFyYWN0ZXIgdmVjdG9yLCBtX29yX3IgdGhhdCBpcyB0aGUgcG9zaXRpdmUgY2xhc3MsICJNIiwgd2hlbiBwIGlzIGdyZWF0ZXIgdGhhbiAwLjUsIGFuZCB0aGUgbmVnYXRpdmUgY2xhc3MsICJSIiwgb3RoZXJ3aXNlLg0KDQotIENvbnZlcnQgbV9vcl9yIHRvIGJlIGEgZmFjdG9yLCBwX2NsYXNzLCB3aXRoIGxldmVscyB0aGUgc2FtZSBhcyB0aG9zZSBvZiB0ZXN0W1siQ2xhc3MiXV0uDQoNCi0gTWFrZSBhIGNvbmZ1c2lvbiBtYXRyaXggd2l0aCBjb25mdXNpb25NYXRyaXgoKSwgcGFzc2luZyBwX2NsYXNzIGFuZCB0aGUgIkNsYXNzIiBjb2x1bW4gZnJvbSB0aGUgdGVzdCBkYXRhc2V0Lg0KYGBge3J9DQojIElmIHAgZXhjZWVkcyB0aHJlc2hvbGQgb2YgMC41LCBNIGVsc2UgUjogbV9vcl9yDQptX29yX3IgPC0gaWZlbHNlKHAgPiAwLjUsICJNIiwgIlIiKQ0KDQojIENvbnZlcnQgdG8gZmFjdG9yOiBwX2NsYXNzDQpwX2NsYXNzIDwtIGZhY3RvcihtX29yX3IsIGxldmVscyA9IGxldmVscyh0ZXN0W1siQ2xhc3MiXV0pKQ0KDQojIENyZWF0ZSBjb25mdXNpb24gbWF0cml4DQpjb25mdXNpb25NYXRyaXgocF9jbGFzcywgdGVzdFtbIkNsYXNzIl1dKQ0KYGBgDQoNCiMgMi41LTIuNzogQ2FsY3VsYXRpbmcgYWNjdXJhY3ksIHRydWUgcG9zaXRpdmUgcmF0ZSwgdHJ1ZSBuZWdhdGl2ZSByYXRlDQoNClVzZSBjb25mdXNpb25NYXRyaXgocF9jbGFzcywgdGVzdFtbIkNsYXNzIl1dKSB0byBjYWxjdWxhdGUgYSBjb25mdXNpb24gbWF0cml4IG9uIHRoZSB0ZXN0IHNldC4NCg0KMS4gV2hhdCBpcyB0aGUgdGVzdCBzZXQgYWNjdXJhY3kgb2YgdGhpcyBtb2RlbCAocm91bmRlZCB0byB0aGUgbmVhcmVzdCBwZXJjZW50KT8NCg0KQW5zOiBbNTcuNTglfjAuNTc1OF0NCg0KMi4gV2hhdCBpcyB0aGUgdGVzdCBzZXQgdHJ1ZSBwb3NpdGl2ZSByYXRlIChvciBzZW5zaXRpdml0eSkgb2YgdGhpcyBtb2RlbCAocm91bmRlZCB0byB0aGUgbmVhcmVzdCBwZXJjZW50KT8NCg0KQW5zOiBbNTUlfjAuNTVdDQoNCjMuIFdoYXQgaXMgdGhlIHRlc3Qgc2V0IHRydWUgbmVnYXRpdmUgcmF0ZSAob3Igc3BlY2lmaWNpdHkpIG9mIHRoaXMgbW9kZWwgKHJvdW5kZWQgdG8gdGhlIG5lYXJlc3QgcGVyY2VudCk/DQoNCkFuczogWzYxLjU0JX4wLjYxNTRdDQpgYGB7cn0NCiMgRGVmaW5lIFBvc2l0aXZlICgrKSB0byBiZSAiTSIsIE5lZ2F0aXZlICgtKSAiUiIgDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojICAgICAgICAgICAgUmVmZXJlbmNlIChBY3R1YWwpDQojIFByZWRpY3Rpb24gICAgIE0gICAgLCAgUg0KIyAgIE0oKykgICAgICBUUD0gMTEgICwgIEZQPTUNCiMgICBSKC0pICAgICAgRk49IDkgICAsICBUTj04DQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiMgMS4gVGVzdCBzZXQgQWNjdXJhY3kgcmF0ZQ0KIyAgID0gVFArVE4vVG90YWx+MC41NzU4DQpzcHJpbnRmKCJUZXN0IHNldCBBY2N1cmFjeSByYXRlIDogJWYgIiwgKDExKzgpLygxMSs1KzkrOCkpDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIDIuIHRlc3Qgc2V0IHRydWUgcG9zaXRpdmUgcmF0ZSAob3Igc2Vuc2l0aXZpdHkpIG9mIHRoaXMgbW9kZWw6IA0KIyBpLmUuIHRoZSBwcm9wb3J0aW9uIG9mIEFjdHVhbCAoTSkgUG9zaXRpdmVzIGNsYXNzaWZpZWQgY29ycmVjdGx5IG91dCBvZiBBTEwgQWN0dWFsIFBvc2l0aXZlcyAoTSkNCiMgICA9IFRQLyhUUCtGTil+MC41NQ0Kc3ByaW50ZigiVGVzdCBzZXQgdHJ1ZSBwb3NpdGl2ZSByYXRlIChzZW5zaXRpdml0eSk6ICVmICIsKDExKS8oMTErOSkpDQojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCiMgMy4gdGVzdCBzZXQgdHJ1ZSBuZWdhdGl2ZSByYXRlIChvciBzcGVjaWZpY2l0eSkgb2YgdGhpcyBtb2RlbCwgUHJvYmFiaWxpdHkgb2YgRmFsc2UgQWxhcm1zIQ0KIyBpLmUuIHRoZSBwcm9wb3J0aW9uIG9mIEFjdHVhbCAoUikgTmVnYXRpdmVzIGNsYXNzaWZpZWQgY29ycmVjdGx5IG91dCBvZiBBTEwgQWN0dWFsIE5lZ2F0aXZlcyAoUikNCiMgICA9VE4vKFROK0ZQKX4wLjYxNTQNCnNwcmludGYoIlRlc3Qgc2V0IHRydWUgbmVnYXRpdmUgcmF0ZSAoc3BlY2lmaWNpdHkpOiAlZiAiLCA4Lyg4KzUpKQ0KYGBgDQoNCiMjIDIuODogVHJ5IGFub3RoZXIgdGhyZXNob2xkDQoNCkluIHRoZSBwcmV2aW91cyBleGVyY2lzZXMsIHlvdSB1c2VkIGEgdGhyZXNob2xkIG9mIDAuNTAgdG8gY3V0IHlvdXIgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgdG8gbWFrZSBjbGFzcyBwcmVkaWN0aW9ucyAocm9jayB2cyBtaW5lKS4gSG93ZXZlciwgdGhpcyBjbGFzc2lmaWNhdGlvbiB0aHJlc2hvbGQgZG9lcyBub3QgYWx3YXlzIGFsaWduIHdpdGggdGhlIGdvYWxzIGZvciBhIGdpdmVuIG1vZGVsaW5nIHByb2JsZW0uDQoNCkZvciBleGFtcGxlLCBwcmV0ZW5kIHlvdSB3YW50IHRvIGlkZW50aWZ5IHRoZSBvYmplY3RzIHlvdSBhcmUgcmVhbGx5IGNlcnRhaW4gYXJlIG1pbmVzLiBJbiB0aGlzIGNhc2UsIHlvdSBtaWdodCB3YW50IHRvIHVzZSBhIHByb2JhYmlsaXR5IHRocmVzaG9sZCBvZiAwLjkwIHRvIGdldCBmZXdlciBwcmVkaWN0ZWQgbWluZXMsIGJ1dCB3aXRoIGdyZWF0ZXIgY29uZmlkZW5jZSBpbiBlYWNoIHByZWRpY3Rpb24uDQoNClRoZSBjb2RlIHBhdHRlcm4gZm9yIGN1dHRpbmcgcHJvYmFiaWxpdGllcyBpbnRvIHByZWRpY3RlZCBjbGFzc2VzLCB0aGVuIGNhbGN1bGF0aW5nIGEgY29uZnVzaW9uIG1hdHJpeCwgd2FzIHNob3duIGluIEV4ZXJjaXNlIDcgb2YgdGhpcyBjaGFwdGVyLg0KDQpJbnN0cnVjdGlvbnMNCg0KMTAwIFhQDQoNCi0gVXNlIGlmZWxzZSgpIHRvIGNyZWF0ZSBhIGNoYXJhY3RlciB2ZWN0b3IsIG1fb3JfciB0aGF0IGlzIHRoZSBwb3NpdGl2ZSBjbGFzcywgIk0iLCB3aGVuIHAgaXMgZ3JlYXRlciB0aGFuIDAuOSwgYW5kIHRoZSBuZWdhdGl2ZSBjbGFzcywgIlIiLCBvdGhlcndpc2UuDQoNCi0gQ29udmVydCBtX29yX3IgdG8gYmUgYSBmYWN0b3IsIHBfY2xhc3MsIHdpdGggbGV2ZWxzIHRoZSBzYW1lIGFzIHRob3NlIG9mIHRlc3RbWyJDbGFzcyJdXS4NCg0KLSBNYWtlIGEgY29uZnVzaW9uIG1hdHJpeCB3aXRoIGNvbmZ1c2lvbk1hdHJpeCgpLCBwYXNzaW5nIHBfY2xhc3MgYW5kIHRoZSAiQ2xhc3MiIGNvbHVtbiBmcm9tIHRoZSB0ZXN0IGRhdGFzZXQuDQoNCmBgYHtyfQ0KIyBJZiBwIGV4Y2VlZHMgdGhyZXNob2xkIG9mIDAuOSwgTSBlbHNlIFI6IG1fb3Jfcg0KbV9vcl9yPC1pZmVsc2UocD4wLjk5LCJNIiwiUiIpDQoNCiMgQ29udmVydCB0byBmYWN0b3I6IHBfY2xhc3MNCnBfY2xhc3M8LWZhY3RvcihtX29yX3IsIGxldmVscyA9IGxldmVscyh0ZXN0W1siQ2xhc3MiXV0pKQ0KDQojIENyZWF0ZSBjb25mdXNpb24gbWF0cml4DQpjb25mdXNpb25NYXRyaXgocF9jbGFzcywgdGVzdFtbIkNsYXNzIl1dKQ0KYGBgDQoNClJlbWFya3M6IE5vdGUgdGhhdCB0aGVyZSBhcmUgKHNsaWdodGx5KSBmZXdlciBwcmVkaWN0ZWQgbWluZXMgd2l0aCB0aGlzIGhpZ2hlciB0aHJlc2hvbGQNCg0KIyMgMi45OiBGcm9tIHByb2JhYmlsaXRlcyB0byBjb25mdXNpb24gbWF0cml4DQoNCkNvbnZlcnNlbHksIHNheSB5b3Ugd2FudCB0byBiZSByZWFsbHkgY2VydGFpbiB0aGF0IHlvdXIgbW9kZWwgY29ycmVjdGx5IGlkZW50aWZpZXMgYWxsIHRoZSBtaW5lcyBhcyBtaW5lcy4gSW4gdGhpcyBjYXNlLCB5b3UgbWlnaHQgdXNlIGEgcHJlZGljdGlvbiB0aHJlc2hvbGQgb2YgMC4xMCwgaW5zdGVhZCBvZiAwLjkwLg0KDQpUaGUgY29kZSBwYXR0ZXJuIGZvciBjdXR0aW5nIHByb2JhYmlsaXRpZXMgaW50byBwcmVkaWN0ZWQgY2xhc3NlcywgdGhlbiBjYWxjdWxhdGluZyBhIGNvbmZ1c2lvbiBtYXRyaXgsIHdhcyBzaG93biBpbiBFeGVyY2lzZSA3IG9mIHRoaXMgY2hhcHRlci4NCg0KSW5zdHJ1Y3Rpb25zDQoNCjEwMCBYUA0KDQotIFVzZSBpZmVsc2UoKSB0byBjcmVhdGUgYSBjaGFyYWN0ZXIgdmVjdG9yLCBtX29yX3IgdGhhdCBpcyB0aGUgcG9zaXRpdmUgY2xhc3MsICJNIiwgd2hlbiBwIGlzIGdyZWF0ZXIgdGhhbiAwLjEsIGFuZCB0aGUgbmVnYXRpdmUgY2xhc3MsICJSIiwgb3RoZXJ3aXNlLg0KDQotIENvbnZlcnQgbV9vcl9yIHRvIGJlIGEgZmFjdG9yLCBwX2NsYXNzLCB3aXRoIGxldmVscyB0aGUgc2FtZSBhcyB0aG9zZSBvZiB0ZXN0W1siQ2xhc3MiXV0uDQoNCi0gTWFrZSBhIGNvbmZ1c2lvbiBtYXRyaXggd2l0aCBjb25mdXNpb25NYXRyaXgoKSwgcGFzc2luZyBwX2NsYXNzIGFuZCB0aGUgIkNsYXNzIiBjb2x1bW4gZnJvbSB0aGUgdGVzdCBkYXRhc2V0Lg0KYGBge3J9DQojIElmIHAgZXhjZWVkcyB0aHJlc2hvbGQgb2YgMC45LCBNIGVsc2UgUjogbV9vcl9yDQptX29yX3I8LWlmZWxzZShwPjAuMSwiTSIsIlIiKQ0KDQojIENvbnZlcnQgdG8gZmFjdG9yOiBwX2NsYXNzDQpwX2NsYXNzPC1mYWN0b3IobV9vcl9yLCBsZXZlbHMgPSBsZXZlbHModGVzdFtbIkNsYXNzIl1dKSkNCg0KIyBDcmVhdGUgY29uZnVzaW9uIG1hdHJpeA0KY29uZnVzaW9uTWF0cml4KHBfY2xhc3MsIHRlc3RbWyJDbGFzcyJdXSkNCmBgYA0KUmVtYXJrOiBOb3RlIHRoYXQgdGhlcmUgYXJlIChzbGlnaHRseSkgbW9yZSBwcmVkaWN0ZWQgbWluZXMgd2l0aCB0aGlzIGxvd2VyIHRocmVzaG9sZA0KDQojIyAyLjEwOiBXaGF0J3MgdGhlIHZhbHVlIG9mIGEgUk9DIGN1cnZlPw0KDQpXaGF0IGlzIHRoZSBwcmltYXJ5IHZhbHVlIG9mIGFuIFJPQyBjdXJ2ZT8NCg0KQW5zd2VyIHRoZSBxdWVzdGlvbg0KDQo1MCBYUA0KDQpQb3NzaWJsZSBBbnN3ZXJzDQoNCjEuIEl0IGhhcyBhIGNvb2wgYWNyb255bS4NCg0KMi4gSXQgY2FuIGJlIHVzZWQgdG8gZGV0ZXJtaW5lIHRoZSB0cnVlIHBvc2l0aXZlIGFuZCBmYWxzZSBwb3NpdGl2ZSByYXRlcyBmb3IgYSBwYXJ0aWN1bGFyIGNsYXNzaWZpY2F0aW9uIHRocmVzaG9sZC4NCg0KMy4gSXQgZXZhbHVhdGVzIGFsbCBwb3NzaWJsZSB0aHJlc2hvbGRzIGZvciBzcGxpdHRpbmcgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgaW50byBwcmVkaWN0ZWQgY2xhc3Nlcy4NCg0KIyMgMi4xMTogUGxvdCBhbiBST0MgY3VydmUNCg0KQXMgeW91IHNhdyBpbiB0aGUgdmlkZW8sIGFuIFJPQyBjdXJ2ZSBpcyBhIHJlYWxseSB1c2VmdWwgc2hvcnRjdXQgZm9yIHN1bW1hcml6aW5nIHRoZSBwZXJmb3JtYW5jZSBvZiBhIGNsYXNzaWZpZXIgb3ZlciBhbGwgcG9zc2libGUgdGhyZXNob2xkcy4gVGhpcyBzYXZlcyB5b3UgYSBsb3Qgb2YgdGVkaW91cyB3b3JrIGNvbXB1dGluZyBjbGFzcyBwcmVkaWN0aW9ucyBmb3IgbWFueSBkaWZmZXJlbnQgdGhyZXNob2xkcyBhbmQgZXhhbWluaW5nIHRoZSBjb25mdXNpb24gbWF0cml4IGZvciBlYWNoLg0KDQpNeSBmYXZvcml0ZSBwYWNrYWdlIGZvciBjb21wdXRpbmcgUk9DIGN1cnZlcyBpcyBjYVRvb2xzLCB3aGljaCBjb250YWlucyBhIGZ1bmN0aW9uIGNhbGxlZCBjb2xBVUMoKS4gVGhpcyBmdW5jdGlvbiBpcyB2ZXJ5IHVzZXItZnJpZW5kbHkgYW5kIGNhbiBhY3R1YWxseSBjYWxjdWxhdGUgUk9DIGN1cnZlcyBmb3IgbXVsdGlwbGUgcHJlZGljdG9ycyBhdCBvbmNlLiBJbiB0aGlzIGNhc2UsIHlvdSBvbmx5IG5lZWQgdG8gY2FsY3VsYXRlIHRoZSBST0MgY3VydmUgZm9yIG9uZSBwcmVkaWN0b3IsIGUuZy46DQoNCmNvbEFVQyhwcmVkaWN0ZWRfcHJvYmFiaWxpdGllcywgYWN0dWFsLCBwbG90Uk9DID0gVFJVRSkNClRoZSBmdW5jdGlvbiB3aWxsIHJldHVybiBhIHNjb3JlIGNhbGxlZCBBVUMgKG1vcmUgb24gdGhhdCBsYXRlcikgYW5kIHRoZSBwbG90Uk9DID0gVFJVRSBhcmd1bWVudCB3aWxsIHJldHVybiB0aGUgcGxvdCBvZiB0aGUgUk9DIGN1cnZlIGZvciB2aXN1YWwgaW5zcGVjdGlvbi4NCg0KSW5zdHJ1Y3Rpb25zDQoNCjEwMCBYUA0KDQotIG1vZGVsLCB0ZXN0LCBhbmQgdHJhaW4gZnJvbSB0aGUgbGFzdCBleGVyY2lzZSB1c2luZyB0aGUgc29uYXIgZGF0YSBhcmUgbG9hZGVkIGluIHlvdXIgd29ya3NwYWNlLg0KDQotIFByZWRpY3QgcHJvYmFiaWxpdGllcyAoaS5lLiB0eXBlID0gInJlc3BvbnNlIikgb24gdGhlIHRlc3Qgc2V0LCB0aGVuIHN0b3JlIHRoZSByZXN1bHQgYXMgcC4NCg0KLSBNYWtlIGFuIFJPQyBjdXJ2ZSB1c2luZyB0aGUgcHJlZGljdGVkIHRlc3Qgc2V0IHByb2JhYmlsaXRpZXMuDQoNCmBgYHtyfQ0KIyBQcmVkaWN0IG9uIHRlc3Q6IHANCnA8LXByZWRpY3QobW9kZWwsdGVzdCx0eXBlPSJyZXNwb25zZSIpDQoNCiMgTWFrZSBST0MgY3VydmUNCmNvbEFVQyhwLCB0ZXN0W1siQ2xhc3MiXV0sIHBsb3RST0MgPSBUUlVFKQ0KYGBgDQoNCiMjIDIuMTI6IEN1c3RvbWl6aW5nIHRyYWluQ29udHJvbA0KDQpBcyB5b3Ugc2F3IGluIHRoZSB2aWRlbywgYXJlYSB1bmRlciB0aGUgUk9DIGN1cnZlIGlzIGEgdmVyeSB1c2VmdWwsIHNpbmdsZS1udW1iZXIgc3VtbWFyeSBvZiBhIG1vZGVsJ3MgYWJpbGl0eSB0byBkaXNjcmltaW5hdGUgdGhlIHBvc2l0aXZlIGZyb20gdGhlIG5lZ2F0aXZlIGNsYXNzIChlLmcuIG1pbmVzIGZyb20gcm9ja3MpLiBBbiBBVUMgb2YgMC41IGlzIG5vIGJldHRlciB0aGFuIHJhbmRvbSBndWVzc2luZywgYW4gQVVDIG9mIDEuMCBpcyBhIHBlcmZlY3RseSBwcmVkaWN0aXZlIG1vZGVsLCBhbmQgYW4gQVVDIG9mIDAuMCBpcyBwZXJmZWN0bHkgYW50aS1wcmVkaWN0aXZlICh3aGljaCByYXJlbHkgaGFwcGVucykuDQoNClRoaXMgaXMgb2Z0ZW4gYSBtdWNoIG1vcmUgdXNlZnVsIG1ldHJpYyB0aGFuIHNpbXBseSByYW5raW5nIG1vZGVscyBieSB0aGVpciBhY2N1cmFjeSBhdCBhIHNldCB0aHJlc2hvbGQsIGFzIGRpZmZlcmVudCBtb2RlbHMgbWlnaHQgcmVxdWlyZSBkaWZmZXJlbnQgY2FsaWJyYXRpb24gc3RlcHMgKGxvb2tpbmcgYXQgYSBjb25mdXNpb24gbWF0cml4IGF0IGVhY2ggc3RlcCkgdG8gZmluZCB0aGUgb3B0aW1hbCBjbGFzc2lmaWNhdGlvbiB0aHJlc2hvbGQgZm9yIHRoYXQgbW9kZWwuDQoNCllvdSBjYW4gdXNlIHRoZSB0cmFpbkNvbnRyb2woKSBmdW5jdGlvbiBpbiBjYXJldCB0byB1c2UgQVVDIChpbnN0ZWFkIG9mIGFjY2N1cmFjeSksIHRvIHR1bmUgdGhlIHBhcmFtZXRlcnMgb2YgeW91ciBtb2RlbHMuIFRoZSB0d29DbGFzc1N1bW1hcnkoKSBjb252ZW5pZW5jZSBmdW5jdGlvbiBhbGxvd3MgeW91IHRvIGRvIHRoaXMgZWFzaWx5Lg0KDQpXaGVuIHVzaW5nIHR3b0NsYXNzU3VtbWFyeSgpLCBiZSBzdXJlIHRvIGFsd2F5cyBpbmNsdWRlIHRoZSBhcmd1bWVudCBjbGFzc1Byb2JzID0gVFJVRSBvciB5b3VyIG1vZGVsIHdpbGwgdGhyb3cgYW4gZXJyb3IhIChZb3UgY2Fubm90IGNhbGN1bGF0ZSBBVUMgd2l0aCBqdXN0IGNsYXNzIHByZWRpY3Rpb25zLiBZb3UgbmVlZCB0byBoYXZlIGNsYXNzIHByb2JhYmlsaXRpZXMgYXMgd2VsbC4pDQoNCkluc3RydWN0aW9ucw0KDQoxMDAgWFANCg0KLSBDdXN0b21pemUgdGhlIHRyYWluQ29udHJvbCBvYmplY3QgdG8gdXNlIHR3b0NsYXNzU3VtbWFyeSByYXRoZXIgdGhhbiBkZWZhdWx0U3VtbWFyeS4NCg0KLSBVc2UgMTAtZm9sZCBjcm9zcy12YWxpZGF0aW9uLg0KDQotIEJlIHN1cmUgdG8gdGVsbCB0cmFpbkNvbnRyb2woKSB0byByZXR1cm4gY2xhc3MgcHJvYmFiaWxpdGllcy4NCmBgYHtyfQ0KIyBDcmVhdGUgdHJhaW5Db250cm9sIG9iamVjdDogbXlDb250cm9sDQpteUNvbnRyb2wgPC0gdHJhaW5Db250cm9sKA0KICBtZXRob2QgPSAiY3YiLA0KICBudW1iZXIgPSAxMCwNCiAgc3VtbWFyeUZ1bmN0aW9uID0gdHdvQ2xhc3NTdW1tYXJ5LA0KICBjbGFzc1Byb2JzID0gVFJVRSwgIyBJTVBPUlRBTlQhDQogIHZlcmJvc2VJdGVyID0gVFJVRQ0KKQ0KYGBgDQoNCg0KIyMgMi4xMzogVXNpbmcgY3VzdG9tIHRyYWluQ29udHJvbA0KDQpOb3cgdGhhdCB5b3UgaGF2ZSBhIGN1c3RvbSB0cmFpbkNvbnRyb2wgb2JqZWN0LCBpdCdzIGVhc3kgdG8gZml0IGNhcmV0IG1vZGVscyB0aGF0IHVzZSBBVUMgcmF0aGVyIHRoYW4gYWNjdXJhY3kgdG8gdHVuZSBhbmQgZXZhbHVhdGUgdGhlIG1vZGVsLiBZb3UgY2FuIGp1c3QgcGFzcyB5b3VyIGN1c3RvbSB0cmFpbkNvbnRyb2wgb2JqZWN0IHRvIHRoZSB0cmFpbigpIGZ1bmN0aW9uIHZpYSB0aGUgdHJDb250cm9sIGFyZ3VtZW50LCBlLmcuOg0KDQp0cmFpbig8c3RhbmRhcmQgYXJndW1lbnRzIGhlcmU+LCB0ckNvbnRyb2wgPSBteUNvbnRyb2wpDQpUaGlzIHN5bnRheCBnaXZlcyB5b3UgYSBjb252ZW5pZW50IHdheSB0byBzdG9yZSBhIGxvdCBvZiBjdXN0b20gbW9kZWxpbmcgcGFyYW1ldGVycyBhbmQgdGhlbiB1c2UgdGhlbSBhY3Jvc3MgbXVsdGlwbGUgZGlmZmVyZW50IGNhbGxzIHRvIHRyYWluKCkuIFlvdSB3aWxsIG1ha2UgZXh0ZW5zaXZlIHVzZSBvZiB0aGlzIHRyaWNrIGluIENoYXB0ZXIgNS4NCg0KSW5zdHJ1Y3Rpb25zDQoNCjEwMCBYUA0KDQotIFVzZSB0cmFpbigpIHRvIGZpdCBhIGdsbSBtb2RlbCAoaS5lLiBtZXRob2QgPSAiZ2xtIikgdG8gU29uYXIgdXNpbmcgeW91ciBjdXN0b20gdHJhaW5Db250cm9sIG9iamVjdCwgbXlDb250cm9sLiBZb3Ugd2FudCB0byBwcmVkaWN0IENsYXNzIGZyb20gYWxsIG90aGVyIHZhcmlhYmxlcyBpbiB0aGUgZGF0YSAoaS5lLiBDbGFzcyB+IC4pLiBTYXZlIHRoZSByZXN1bHQgdG8gbW9kZWwuDQoNCi0gUHJpbnQgdGhlIG1vZGVsIHRvIHRoZSBjb25zb2xlIGFuZCBleGFtaW5lIGl0cyBvdXRwdXQuDQpgYGB7cn0NCiMgVHJhaW4gZ2xtIHdpdGggY3VzdG9tIHRyYWluQ29udHJvbDogbW9kZWwNCm1vZGVsPC10cmFpbihtZXRob2Q9ImdsbSIsZGF0YT1Tb25hcixDbGFzc34uLHRyQ29udHJvbD1teUNvbnRyb2wpDQoNCg0KIyBQcmludCBtb2RlbCB0byBjb25zb2xlDQptb2RlbA0KYGBgDQpSZW1hcms6IEZvciBub3csIGRvbid0IHdvcnJ5IGFib3V0IHRoZSB3YXJuaW5nIG1lc3NhZ2VzIGdlbmVyYXRlZCBieSB5b3VyIGNvZGUuDQoNCg==