Library

library(AppliedPredictiveModeling)
library(caret)
library(corrplot)
library(Cubist)
library(dplyr)
library(earth)
library(forecast)
library(fpp3)
library(gbm)
library(ggplot2)
library(MASS)
library(mice)
library(mlbench)
library(party)
library(randomForest)
library(RANN)
library(rpart)
library(rpart.plot)
library(tidyr)

Description

Do problems 8.1, 8.2, 8.3, and 8.7 in Kuhn and Johnson. Please submit the Rpubs link along with the .rmd file.

8.1.

Recreate the simulated data from Exercise 7.2:

set.seed(200)
simulated <- mlbench.friedman1(200, sd = 1)
simulated <- cbind(simulated$x, simulated$y)
simulated <- as.data.frame(simulated)
colnames(simulated)[ncol(simulated)] <- "y"

(a)

Fit a random forest model to all of the predictors, then estimate the variable importance scores:

 model1 <- randomForest(y ~ ., data = simulated,
 importance = TRUE,
 ntree = 1000)
 rfImp1 <- varImp(model1, scale = FALSE)
 
rownames(rfImp1) <- paste0("V", 1:10)
rfImp1_sorted <- rfImp1[order(-rfImp1$Overall), , drop = FALSE] # 'drop = FALSE' ensures the result is still a data frame
print(rfImp1_sorted)

Did the random forest model significantly use the uninformative predictors (V6V10)?

No. The values of V6-V10 are very low, even most being a negative value indicating a low importance score.

(b)

Now add an additional predictor that is highly correlated with one of the informative predictors. For example:

simulated$duplicate1 <- simulated$V1 + rnorm(200) * .1
cor(simulated$duplicate1, simulated$V1)
[1] 0.9460206
simulated2<-simulated
simulated2$duplicate1 <- simulated2$V1 + rnorm(200) * .1
cor(simulated2$duplicate1, simulated2$V1)
[1] 0.9447637

Fit another random forest model to these data. Did the importance score for V1 change? What happens when you add another predictor that is also highly correlated with V1?

model2 <- randomForest(y ~ ., data = simulated2, importance = TRUE, 
                       ntree = 1000)
rfImp2 <- varImp(model2, scale = FALSE)
rownames(rfImp2) <- c(paste0("V", 1:10), "duplicate1")
rfImp2_sorted <- rfImp2[order(-rfImp2$Overall), , drop = FALSE] # 'drop = FALSE' ensures the result is still a data frame
print(rfImp2_sorted)

The highly correlated rearranged the importance score for all predictors. Predictor V1 was demoted from most important to second most important. The highly correlated duplicate lands in fourth place.

(c)

Use the cforest function in the party package to fit a random forest model using conditional inference trees. The party package function varimp can calculate predictor importance. The conditional argument of that function toggles between the traditional importance measure and the modified version described in Strobl et al. (2007). Do these importances show the same pattern as the traditional random forest model?

model3 <- cforest(y ~., data = simulated)

order(-varimp(model3, conditional = FALSE)) #default conditional: FALSE
 [1]  4  2 11  1  5  7  9 10  6  3  8
order(-varimp(model3, conditional = TRUE))
 [1]  4  2 11  1  5  3  9  6  7 10  8

Using the party package on varimp

cforest conditional=false is 4 2 11 1 5 3 7 9 6 8 10 cforest conditional=true is 4 2 1 11 5 6 3 7 8 9 10 while randomforest is 1 4 2 5 3 6 7 10 9 8

which differ in pattern, but do support that v6-v10 have relatively low importance levels.

(d)

Repeat this process with different tree models, such as boosted trees and Cubist. Does the same pattern occur?

Boosted


set.seed(624)

model_gbm<- gbm(y ~., data = simulated, distribution = "gaussian")

summary.gbm(model_gbm)

Cubist


simulated_x <- subset(simulated, select = -c(y))

cubist_model <- cubist(x = simulated_x, y = simulated$y, committees = 100)
cube_model_v2<-varImp(cubist_model)
rownames(cube_model_v2) <- c(paste0("V", 1:10), "duplicate1")
cubist_sorted <- cube_model_v2[order(-cube_model_v2$Overall), , drop = FALSE]
print(cubist_sorted)

Cubist model has V3 as the most important predictor, and the Bagged Trees has V4 as the most important predictor. Predictors V6 – V10 remain uninformative.

8.2.

Use a simulation to show tree bias with different granularities.

set.seed(624)
#samples for predictors
low <- sample(0:50, 500, replace = T)
medium <- sample(0:500, 500, replace = T)
high <- sample(0:5000, 500, replace = T)

#response
y <- low + medium + high + rnorm(250)

#check variance of predictors
var(low)
[1] 221.9536
var(medium)
[1] 21752.82
var(high)
[1] 2016913
df_sim <- data.frame(low, medium, high, y)

diff_gran_model <- randomForest(y ~., data = df_sim, importance = TRUE, ntree = 1000)

varImp(diff_gran_model, scale=FALSE)

Sample data made using sample has low variable with the highest granularity. This is confirmed by it’s variance of 0.71. Variables medium and high have medium and high variance, when compared to low. Higher variance indicates lower granularity.

The tree model confirms tree bias where highest variance variables (lowest granularity) get ranked with highest importance.

8.3.

In stochastic gradient boosting the bagging fraction and learning rate will govern the construction of the trees as they are guided by the gradient. Although the optimal values of these parameters should be obtained through the tuning process, it is helpful to understand how the magnitudes of these parameters affect magnitudes of variable importance. Figure 8.24 provides the variable importance plots for boosting using two extreme values for the bagging fraction (0.1 and 0.9) and the learning rate (0.1 and 0.9) for the solubility data. The left-hand plot has both parameters set to 0.1, and the right-hand plot has both set to 0.9:

package gbm

(a)

Why does the model on the right focus its importance on just the first few of predictors, whereas the model on the left spreads importance across more predictors?

Left Model

  • The left model with shrinkage = 0.1 & bag.fraction = 0.1 focuses on importance for just the first few predictors because the training set observations fraction is very small(0.1 or 10%).
  • Small portion data used to create the trees, means there’s a focus to it’s importance only on a small amount of predictors.
  • shrinkage parameter or learning rate is set to the highest recommended value of what “usually works” according to documentation for the gbm package. The documentation states “smaller learning rate typically require(s) more trees” meaning that smaller values lead to more trees and higher values lead to less trees.

Right Model

  • The right model with shrinkage = 0.9, bag.fraction = 0.9 has a greater spread importance across the predictors because it uses .9 or 90% of the training set to create its trees.
  • Larger shrinkage parameter suggests that less trees are created in this model.

(b)

Which model do you think would be more predictive of other samples?

The left model with shrinkage = 0.1, bag.fraction = 0.1 would be more predictive of other samples between the two. Considering the amount of trees it should have in comparison to the model on the right with shrinkage = 0.9, bag.fraction = 0.9.

(c)

How would increasing interaction depth affect the slope of predictor importance for either model in Fig. 8.24?

According to pub_journals link Sec 4.2, pg. 15

  • The relationship between shrinkage (learning rate) and interaction depth (tree complexity) in decision tree models, specifically in boosting.
  • Shrinkage is inversely related to interaction depth, meaning as tree complexity increases, the learning rate should decrease to prevent overfitting and maintain model stability.
  • This slower learning process necessitates using more trees.
  • Larger datasets better support more complex trees, as they can handle the detailed structures these trees create without overfitting quickly. *Interaction depth/tree size or tree complexity, affects the maximum size for each tree.
  • As mentioned there is a inverse relationship with shrinkage to interaction depth.
  • The left (shrinkage = 0.1, bag.fraction = 0.1) is likely to benefit from increasing interaction depth.
  • But the right might not since the shrinkage parameter is already so high.

8.7.

Refer to Exercises 6.3 and 7.5 which describe a chemical manufacturing process. Use the same data imputation, data splitting, and pre-processing steps as before and train several tree-based models:

#load data
data(ChemicalManufacturingProcess)

set.seed(624)
imputed_data <- preProcess(ChemicalManufacturingProcess, method = c("knnImpute","nzv", "corr"))
df_full <- predict(imputed_data, ChemicalManufacturingProcess)

chem_index <- createDataPartition(df_full$Yield , p=.8, list=F)

chem_train <-  df_full[chem_index,] 
chem_test <- df_full[-chem_index,]

train_pred <- chem_train[-c(1)]
test_pred<-  chem_test[-c(1)]

(a)

Which tree-based regression model gives the optimal resampling and test set performance?

Boosted Trees

set.seed(624)
gbm_model <- gbm.fit(train_pred, chem_train$Yield, distribution = "gaussian")
Iter   TrainDeviance   ValidDeviance   StepSize   Improve
     1        0.9239             nan     0.0010    0.0004
     2        0.9231             nan     0.0010    0.0006
     3        0.9224             nan     0.0010    0.0007
     4        0.9217             nan     0.0010    0.0006
     5        0.9211             nan     0.0010    0.0006
     6        0.9203             nan     0.0010    0.0006
     7        0.9196             nan     0.0010    0.0007
     8        0.9190             nan     0.0010    0.0006
     9        0.9183             nan     0.0010    0.0007
    10        0.9175             nan     0.0010    0.0005
    20        0.9111             nan     0.0010    0.0007
    40        0.8980             nan     0.0010    0.0006
    60        0.8865             nan     0.0010    0.0004
    80        0.8743             nan     0.0010    0.0005
   100        0.8628             nan     0.0010    0.0002
gbm_model
A gradient boosted model with gaussian loss function.
100 iterations were performed.
There were 46 predictors of which 7 had non-zero influence.
gbm_pred <- predict(gbm_model, newdata = test_pred)
postResample(pred = gbm_pred, obs = chem_test$Yield)
     RMSE  Rsquared       MAE 
1.1097582 0.5627084 0.8634997 

Cubist

set.seed(624)
cube_model <- cubist(train_pred, chem_train$Yield)
cube_model

Call:
cubist.default(x = train_pred, y = chem_train$Yield)

Number of samples: 144 
Number of predictors: 46 

Number of committees: 1 
Number of rules: 2 
cube_pred <- predict(cube_model, newdata = test_pred)
postResample(pred = cube_pred, obs = chem_test$Yield)
     RMSE  Rsquared       MAE 
0.6724398 0.6903309 0.5159054 

Random Forest

set.seed(624)
#fit the model
rf_model <- randomForest(train_pred, chem_train$Yield, importance = TRUE, ntrees = 1000)
rf_model

Call:
 randomForest(x = train_pred, y = chem_train$Yield, importance = TRUE,      ntrees = 1000) 
               Type of random forest: regression
                     Number of trees: 500
No. of variables tried at each split: 15

          Mean of squared residuals: 0.3711346
                    % Var explained: 59.85
rf_pred <- predict(rf_model, newdata = test_pred)
postResample(pred = rf_pred, obs = chem_test$Yield)
     RMSE  Rsquared       MAE 
0.6715320 0.7323224 0.4828902 

Random Forest has the optimal metrics because of its RMSE of 0.6715320 (lowest) and Rsquared of 0.7323224

(b)

Top 10:
ManufacturingProcess32, BiologicalMaterial06, BiologicalMaterial03, ManufacturingProcess13, ManufacturingProcess36, BiologicalMaterial11, ManufacturingProcess09, BiologicalMaterial08, ManufacturingProcess28, ManufacturingProcess11

rf_model_v2<-varImp(rf_model)
#rownames(rf_model_v2) <- c(paste0("V", 1:10), "duplicate1")
rf_model_sorted <- rf_model_v2[order(-rf_model_v2$Overall), , drop = FALSE] # 'drop = FALSE' ensures the result is still a data frame
head(rf_model_sorted,10)

i

Which predictors are most important in the optimal tree-based regression model? Do either the biological or process variables dominate the list?

ManufacturingProcess32 is the most important.Neither of the predictors dominating the list with 5 ManufacturingProcess and 5 BiologicalProcess in the top ten.

ii

How do the top 10 important predictors compare to the top 10 predictors from the optimal linear and nonlinear models?

This combination is basically consistent with the importance variables of the non-linear model, not so much the linear model.

(c)

Plot the optimal single tree with the distribution of yield in the terminal nodes. Does this view of the data provide additional knowledge about the biological or process predictors and their relationship with yield?

rpart_tree <- rpart(Yield ~., data = chem_train)
rpart.plot(rpart_tree)

LS0tDQp0aXRsZTogJ0RBVEEgNjI0OiBQUkVESUNUSVZFIEFOQUxZVElDUyBIVyA5Jw0KYXV0aG9yOiAiR2FicmllbCBDYW1wb3MiDQpkYXRlOiAiTGFzdCBlZGl0ZWQgYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZCwgJVknKWAiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgZ2VvbWV0cnk6IGxlZnQ9MC41Y20scmlnaHQ9MC41Y20sdG9wPTFjbSxib3R0b209MmNtDQogIGh0bWxfZG9jdW1lbnQ6DQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogIHBkZl9kb2N1bWVudDoNCiAgICBsYXRleF9lbmdpbmU6IHhlbGF0ZXgNCnVybGNvbG9yOiBibHVlDQotLS0NCg0KIyBMaWJyYXJ5DQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShBcHBsaWVkUHJlZGljdGl2ZU1vZGVsaW5nKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoY29ycnBsb3QpDQpsaWJyYXJ5KEN1YmlzdCkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGVhcnRoKQ0KbGlicmFyeShmb3JlY2FzdCkNCmxpYnJhcnkoZnBwMykNCmxpYnJhcnkoZ2JtKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShNQVNTKQ0KbGlicmFyeShtaWNlKQ0KbGlicmFyeShtbGJlbmNoKQ0KbGlicmFyeShwYXJ0eSkNCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KbGlicmFyeShSQU5OKQ0KbGlicmFyeShycGFydCkNCmxpYnJhcnkocnBhcnQucGxvdCkNCmxpYnJhcnkodGlkeXIpDQpgYGANCg0KIyBEZXNjcmlwdGlvbg0KDQpEbyBwcm9ibGVtcyA4LjEsIDguMiwgOC4zLCBhbmQgOC43IGluIEt1aG4gYW5kIEpvaG5zb24uICBQbGVhc2Ugc3VibWl0IHRoZSBScHVicyBsaW5rIGFsb25nIHdpdGggdGhlIC5ybWQgZmlsZS4NCg0KIyA4LjEuDQoNClJlY3JlYXRlIHRoZSBzaW11bGF0ZWQgZGF0YSBmcm9tIEV4ZXJjaXNlIDcuMjoNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpzZXQuc2VlZCgyMDApDQpzaW11bGF0ZWQgPC0gbWxiZW5jaC5mcmllZG1hbjEoMjAwLCBzZCA9IDEpDQpzaW11bGF0ZWQgPC0gY2JpbmQoc2ltdWxhdGVkJHgsIHNpbXVsYXRlZCR5KQ0Kc2ltdWxhdGVkIDwtIGFzLmRhdGEuZnJhbWUoc2ltdWxhdGVkKQ0KY29sbmFtZXMoc2ltdWxhdGVkKVtuY29sKHNpbXVsYXRlZCldIDwtICJ5Ig0KYGBgDQoNCg0KIyMgKGEpIA0KDQpGaXQgYSByYW5kb20gZm9yZXN0IG1vZGVsIHRvIGFsbCBvZiB0aGUgcHJlZGljdG9ycywgdGhlbiBlc3RpbWF0ZSB0aGUgdmFyaWFibGUgaW1wb3J0YW5jZSBzY29yZXM6DQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIG1vZGVsMSA8LSByYW5kb21Gb3Jlc3QoeSB+IC4sIGRhdGEgPSBzaW11bGF0ZWQsDQogaW1wb3J0YW5jZSA9IFRSVUUsDQogbnRyZWUgPSAxMDAwKQ0KIHJmSW1wMSA8LSB2YXJJbXAobW9kZWwxLCBzY2FsZSA9IEZBTFNFKQ0KIA0KYGBgDQoNCmBgYHtyfQ0Kcm93bmFtZXMocmZJbXAxKSA8LSBwYXN0ZTAoIlYiLCAxOjEwKQ0KcmZJbXAxX3NvcnRlZCA8LSByZkltcDFbb3JkZXIoLXJmSW1wMSRPdmVyYWxsKSwgLCBkcm9wID0gRkFMU0VdICMgJ2Ryb3AgPSBGQUxTRScgZW5zdXJlcyB0aGUgcmVzdWx0IGlzIHN0aWxsIGEgZGF0YSBmcmFtZQ0KcHJpbnQocmZJbXAxX3NvcnRlZCkNCmBgYA0KDQoNCkRpZCB0aGUgcmFuZG9tIGZvcmVzdCBtb2RlbCBzaWduaWZpY2FudGx5IHVzZSB0aGUgdW5pbmZvcm1hdGl2ZSBwcmVkaWN0b3JzIChgVjZgIOKAkyBgVjEwYCk/DQoNCk5vLiBUaGUgdmFsdWVzIG9mIGBWNmAtYFYxMGAgYXJlIHZlcnkgbG93LCBldmVuIG1vc3QgYmVpbmcgYSBuZWdhdGl2ZSB2YWx1ZSBpbmRpY2F0aW5nIGEgbG93IGltcG9ydGFuY2Ugc2NvcmUuDQoNCg0KIyMgKGIpDQoNCk5vdyBhZGQgYW4gYWRkaXRpb25hbCBwcmVkaWN0b3IgdGhhdCBpcyBoaWdobHkgY29ycmVsYXRlZCB3aXRoIG9uZSBvZiB0aGUgaW5mb3JtYXRpdmUgcHJlZGljdG9ycy4gRm9yIGV4YW1wbGU6DQoNCmBgYHtyfQ0Kc2ltdWxhdGVkJGR1cGxpY2F0ZTEgPC0gc2ltdWxhdGVkJFYxICsgcm5vcm0oMjAwKSAqIC4xDQpjb3Ioc2ltdWxhdGVkJGR1cGxpY2F0ZTEsIHNpbXVsYXRlZCRWMSkNCmBgYA0KDQpgYGB7cn0NCnNpbXVsYXRlZDI8LXNpbXVsYXRlZA0Kc2ltdWxhdGVkMiRkdXBsaWNhdGUxIDwtIHNpbXVsYXRlZDIkVjEgKyBybm9ybSgyMDApICogLjENCmNvcihzaW11bGF0ZWQyJGR1cGxpY2F0ZTEsIHNpbXVsYXRlZDIkVjEpDQpgYGANCg0KDQpGaXQgYW5vdGhlciByYW5kb20gZm9yZXN0IG1vZGVsIHRvIHRoZXNlIGRhdGEuIERpZCB0aGUgaW1wb3J0YW5jZSBzY29yZSBmb3IgYFYxYCBjaGFuZ2U/IFdoYXQgaGFwcGVucyB3aGVuIHlvdSBhZGQgYW5vdGhlciBwcmVkaWN0b3IgdGhhdCBpcyBhbHNvIGhpZ2hseSBjb3JyZWxhdGVkIHdpdGggYFYxYD8NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm1vZGVsMiA8LSByYW5kb21Gb3Jlc3QoeSB+IC4sIGRhdGEgPSBzaW11bGF0ZWQyLCBpbXBvcnRhbmNlID0gVFJVRSwgDQogICAgICAgICAgICAgICAgICAgICAgIG50cmVlID0gMTAwMCkNCnJmSW1wMiA8LSB2YXJJbXAobW9kZWwyLCBzY2FsZSA9IEZBTFNFKQ0KYGBgDQoNCg0KYGBge3J9DQpyb3duYW1lcyhyZkltcDIpIDwtIGMocGFzdGUwKCJWIiwgMToxMCksICJkdXBsaWNhdGUxIikNCnJmSW1wMl9zb3J0ZWQgPC0gcmZJbXAyW29yZGVyKC1yZkltcDIkT3ZlcmFsbCksICwgZHJvcCA9IEZBTFNFXSAjICdkcm9wID0gRkFMU0UnIGVuc3VyZXMgdGhlIHJlc3VsdCBpcyBzdGlsbCBhIGRhdGEgZnJhbWUNCnByaW50KHJmSW1wMl9zb3J0ZWQpDQpgYGANCg0KDQpUaGUgaGlnaGx5IGNvcnJlbGF0ZWQgcmVhcnJhbmdlZCB0aGUgaW1wb3J0YW5jZSBzY29yZSBmb3IgYWxsIHByZWRpY3RvcnMuIFByZWRpY3RvciBWMSB3YXMgZGVtb3RlZCBmcm9tIG1vc3QgaW1wb3J0YW50IHRvIHNlY29uZCBtb3N0IGltcG9ydGFudC4gVGhlIGhpZ2hseSBjb3JyZWxhdGVkIGR1cGxpY2F0ZSBsYW5kcyBpbiBmb3VydGggcGxhY2UuDQoNCg0KIyMgKGMpDQoNClVzZSB0aGUgYGNmb3Jlc3RgIGZ1bmN0aW9uIGluIHRoZSBwYXJ0eSBwYWNrYWdlIHRvIGZpdCBhIHJhbmRvbSBmb3Jlc3QgbW9kZWwgdXNpbmcgY29uZGl0aW9uYWwgaW5mZXJlbmNlIHRyZWVzLiBUaGUgcGFydHkgcGFja2FnZSBmdW5jdGlvbiBgdmFyaW1wYCBjYW4gY2FsY3VsYXRlIHByZWRpY3RvciBpbXBvcnRhbmNlLiBUaGUgY29uZGl0aW9uYWwgYXJndW1lbnQgb2YgdGhhdCBmdW5jdGlvbiB0b2dnbGVzIGJldHdlZW4gdGhlIHRyYWRpdGlvbmFsIGltcG9ydGFuY2UgbWVhc3VyZSBhbmQgdGhlIG1vZGlmaWVkIHZlcnNpb24gZGVzY3JpYmVkIGluICpTdHJvYmwgZXQgYWwuICgyMDA3KSouIERvIHRoZXNlIGltcG9ydGFuY2VzIHNob3cgdGhlIHNhbWUgcGF0dGVybiBhcyB0aGUgdHJhZGl0aW9uYWwgcmFuZG9tIGZvcmVzdCBtb2RlbD8NCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQptb2RlbDMgPC0gY2ZvcmVzdCh5IH4uLCBkYXRhID0gc2ltdWxhdGVkKQ0KDQpvcmRlcigtdmFyaW1wKG1vZGVsMywgY29uZGl0aW9uYWwgPSBGQUxTRSkpICNkZWZhdWx0IGNvbmRpdGlvbmFsOiBGQUxTRQ0KDQpvcmRlcigtdmFyaW1wKG1vZGVsMywgY29uZGl0aW9uYWwgPSBUUlVFKSkNCmBgYA0KDQpVc2luZyB0aGUgcGFydHkgcGFja2FnZSBvbiB2YXJpbXANCg0KY2ZvcmVzdCBjb25kaXRpb25hbD1mYWxzZSBpcyAgNCAgMiAxMSAgMSAgNSAgMyAgNyAgOSAgNiAgOCAxMA0KY2ZvcmVzdCBjb25kaXRpb25hbD10cnVlICBpcyA0ICAyICAxIDExICA1ICA2ICAzICA3ICA4ICA5IDEwDQp3aGlsZQ0KcmFuZG9tZm9yZXN0IGlzIDEgNCAyIDUgMyA2IDcgMTAgOSA4DQoNCndoaWNoIGRpZmZlciBpbiBwYXR0ZXJuLCBidXQgZG8gc3VwcG9ydCB0aGF0IHY2LXYxMCBoYXZlIHJlbGF0aXZlbHkgbG93IGltcG9ydGFuY2UgbGV2ZWxzLg0KDQojIyAoZCkNCg0KUmVwZWF0IHRoaXMgcHJvY2VzcyB3aXRoIGRpZmZlcmVudCB0cmVlIG1vZGVscywgc3VjaCBhcyBib29zdGVkIHRyZWVzIGFuZCBDdWJpc3QuIERvZXMgdGhlIHNhbWUgcGF0dGVybiBvY2N1cj8NCg0KIVtdKC5cSW1hZ2VzXEltZ184XzEucG5nKQ0KDQojIyMgQm9vc3RlZA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQpzZXQuc2VlZCg2MjQpDQoNCm1vZGVsX2dibTwtIGdibSh5IH4uLCBkYXRhID0gc2ltdWxhdGVkLCBkaXN0cmlidXRpb24gPSAiZ2F1c3NpYW4iKQ0KDQpzdW1tYXJ5LmdibShtb2RlbF9nYm0pDQpgYGANCg0KIyMjIEN1YmlzdA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQpzaW11bGF0ZWRfeCA8LSBzdWJzZXQoc2ltdWxhdGVkLCBzZWxlY3QgPSAtYyh5KSkNCg0KY3ViaXN0X21vZGVsIDwtIGN1YmlzdCh4ID0gc2ltdWxhdGVkX3gsIHkgPSBzaW11bGF0ZWQkeSwgY29tbWl0dGVlcyA9IDEwMCkNCg0KYGBgDQoNCmBgYHtyfQ0KY3ViZV9tb2RlbF92MjwtdmFySW1wKGN1YmlzdF9tb2RlbCkNCnJvd25hbWVzKGN1YmVfbW9kZWxfdjIpIDwtIGMocGFzdGUwKCJWIiwgMToxMCksICJkdXBsaWNhdGUxIikNCmN1YmlzdF9zb3J0ZWQgPC0gY3ViZV9tb2RlbF92MltvcmRlcigtY3ViZV9tb2RlbF92MiRPdmVyYWxsKSwgLCBkcm9wID0gRkFMU0VdDQpwcmludChjdWJpc3Rfc29ydGVkKQ0KYGBgDQoNCkN1YmlzdCBtb2RlbCBoYXMgVjMgYXMgdGhlIG1vc3QgaW1wb3J0YW50IHByZWRpY3RvciwgYW5kIHRoZSBCYWdnZWQgVHJlZXMgaGFzIFY0IGFzIHRoZSBtb3N0IGltcG9ydGFudCBwcmVkaWN0b3IuIFByZWRpY3RvcnMgVjYg4oCTIFYxMCByZW1haW4gdW5pbmZvcm1hdGl2ZS4NCg0KDQojIDguMi4NCg0KVXNlIGEgc2ltdWxhdGlvbiB0byBzaG93IHRyZWUgYmlhcyB3aXRoIGRpZmZlcmVudCBncmFudWxhcml0aWVzLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc2V0LnNlZWQoNjI0KQ0KI3NhbXBsZXMgZm9yIHByZWRpY3RvcnMNCmxvdyA8LSBzYW1wbGUoMDo1MCwgNTAwLCByZXBsYWNlID0gVCkNCm1lZGl1bSA8LSBzYW1wbGUoMDo1MDAsIDUwMCwgcmVwbGFjZSA9IFQpDQpoaWdoIDwtIHNhbXBsZSgwOjUwMDAsIDUwMCwgcmVwbGFjZSA9IFQpDQoNCiNyZXNwb25zZQ0KeSA8LSBsb3cgKyBtZWRpdW0gKyBoaWdoICsgcm5vcm0oMjUwKQ0KDQojY2hlY2sgdmFyaWFuY2Ugb2YgcHJlZGljdG9ycw0KdmFyKGxvdykNCnZhcihtZWRpdW0pDQp2YXIoaGlnaCkNCmBgYA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGZfc2ltIDwtIGRhdGEuZnJhbWUobG93LCBtZWRpdW0sIGhpZ2gsIHkpDQoNCmRpZmZfZ3Jhbl9tb2RlbCA8LSByYW5kb21Gb3Jlc3QoeSB+LiwgZGF0YSA9IGRmX3NpbSwgaW1wb3J0YW5jZSA9IFRSVUUsIG50cmVlID0gMTAwMCkNCg0KdmFySW1wKGRpZmZfZ3Jhbl9tb2RlbCwgc2NhbGU9RkFMU0UpDQpgYGANCg0KU2FtcGxlIGRhdGEgbWFkZSB1c2luZyBgc2FtcGxlYCBoYXMgYGxvd2AgdmFyaWFibGUgd2l0aCB0aGUgaGlnaGVzdCBncmFudWxhcml0eS4gVGhpcyBpcyBjb25maXJtZWQgYnkgaXQncyB2YXJpYW5jZSBvZiAwLjcxLiBWYXJpYWJsZXMgYG1lZGl1bWAgYW5kIGBoaWdoYCBoYXZlIG1lZGl1bSBhbmQgaGlnaCB2YXJpYW5jZSwgd2hlbiBjb21wYXJlZCB0byBgbG93YC4gSGlnaGVyIHZhcmlhbmNlIGluZGljYXRlcyBsb3dlciBncmFudWxhcml0eS4gDQoNClRoZSB0cmVlIG1vZGVsIGNvbmZpcm1zIHRyZWUgYmlhcyB3aGVyZSBoaWdoZXN0IHZhcmlhbmNlIHZhcmlhYmxlcyAobG93ZXN0IGdyYW51bGFyaXR5KSBnZXQgcmFua2VkIHdpdGggaGlnaGVzdCBpbXBvcnRhbmNlLg0KDQojIDguMy4NCg0KSW4gc3RvY2hhc3RpYyBncmFkaWVudCBib29zdGluZyB0aGUgYmFnZ2luZyBmcmFjdGlvbiBhbmQgbGVhcm5pbmcgcmF0ZSB3aWxsIGdvdmVybiB0aGUgY29uc3RydWN0aW9uIG9mIHRoZSB0cmVlcyBhcyB0aGV5IGFyZSBndWlkZWQgYnkgdGhlIGdyYWRpZW50LiBBbHRob3VnaCB0aGUgb3B0aW1hbCB2YWx1ZXMgb2YgdGhlc2UgcGFyYW1ldGVycyBzaG91bGQgYmUgb2J0YWluZWQgdGhyb3VnaCB0aGUgdHVuaW5nIHByb2Nlc3MsIGl0IGlzIGhlbHBmdWwgdG8gdW5kZXJzdGFuZCBob3cgdGhlIG1hZ25pdHVkZXMgb2YgdGhlc2UgcGFyYW1ldGVycyBhZmZlY3QgbWFnbml0dWRlcyBvZiB2YXJpYWJsZSBpbXBvcnRhbmNlLiBGaWd1cmUgOC4yNCBwcm92aWRlcyB0aGUgdmFyaWFibGUgaW1wb3J0YW5jZSBwbG90cyBmb3IgYm9vc3RpbmcgdXNpbmcgdHdvIGV4dHJlbWUgdmFsdWVzIGZvciB0aGUgYmFnZ2luZyBmcmFjdGlvbiAoMC4xIGFuZCAwLjkpIGFuZCB0aGUgbGVhcm5pbmcgcmF0ZSAoMC4xIGFuZCAwLjkpIGZvciB0aGUgc29sdWJpbGl0eSBkYXRhLiBUaGUgbGVmdC1oYW5kIHBsb3QgaGFzIGJvdGggcGFyYW1ldGVycyBzZXQgdG8gMC4xLCBhbmQgdGhlIHJpZ2h0LWhhbmQgcGxvdCBoYXMgYm90aCBzZXQgdG8gMC45Og0KDQohW10oLlxJbWFnZXNcSW1nXzhfMS5wbmcpDQoNCltwYWNrYWdlIGdibV0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL2dibS92ZXJzaW9ucy8yLjEuOC90b3BpY3MvZ2JtKQ0KDQojIyAoYSkNCg0KV2h5IGRvZXMgdGhlIG1vZGVsIG9uIHRoZSByaWdodCBmb2N1cyBpdHMgaW1wb3J0YW5jZSBvbiBqdXN0IHRoZSBmaXJzdCBmZXcgb2YgcHJlZGljdG9ycywgd2hlcmVhcyB0aGUgbW9kZWwgb24gdGhlIGxlZnQgc3ByZWFkcyBpbXBvcnRhbmNlIGFjcm9zcw0KbW9yZSBwcmVkaWN0b3JzPw0KDQoqKkxlZnQgTW9kZWwqKg0KDQoqIFRoZSBsZWZ0IG1vZGVsIHdpdGggYHNocmlua2FnZWAgPSAwLjEgJiBgYmFnLmZyYWN0aW9uYCA9IDAuMSBmb2N1c2VzIG9uIGltcG9ydGFuY2UgZm9yIGp1c3QgdGhlIGZpcnN0IGZldyBwcmVkaWN0b3JzIGJlY2F1c2UgdGhlIHRyYWluaW5nIHNldCBvYnNlcnZhdGlvbnMgZnJhY3Rpb24gaXMgdmVyeSBzbWFsbCgwLjEgb3IgMTAlKS4gDQoqIFNtYWxsIHBvcnRpb24gZGF0YSB1c2VkIHRvIGNyZWF0ZSB0aGUgdHJlZXMsIG1lYW5zIHRoZXJlJ3MgYSBmb2N1cyB0byBpdCdzIGltcG9ydGFuY2Ugb25seSBvbiBhIHNtYWxsIGFtb3VudCBvZiBwcmVkaWN0b3JzLiANCiogYHNocmlua2FnZWAgcGFyYW1ldGVyIG9yIGxlYXJuaW5nIHJhdGUgaXMgc2V0IHRvIHRoZSBoaWdoZXN0IHJlY29tbWVuZGVkIHZhbHVlIG9mIHdoYXQgInVzdWFsbHkgd29ya3MiIGFjY29yZGluZyB0byBkb2N1bWVudGF0aW9uIGZvciB0aGUgYGdibWAgcGFja2FnZS4NCiAgVGhlIGRvY3VtZW50YXRpb24gc3RhdGVzICJzbWFsbGVyIGxlYXJuaW5nIHJhdGUgdHlwaWNhbGx5IHJlcXVpcmUocykgbW9yZSB0cmVlcyIgbWVhbmluZyB0aGF0IHNtYWxsZXIgdmFsdWVzIGxlYWQgdG8gbW9yZSB0cmVlcyBhbmQgaGlnaGVyIHZhbHVlcyBsZWFkIHRvICAgICAgbGVzcyB0cmVlcy4NCg0KKipSaWdodCBNb2RlbCoqDQoNCiogVGhlIHJpZ2h0IG1vZGVsIHdpdGggYHNocmlua2FnZWAgPSAwLjksIGBiYWcuZnJhY3Rpb25gID0gMC45IGhhcyBhIGdyZWF0ZXIgc3ByZWFkIGltcG9ydGFuY2UgYWNyb3NzIHRoZSBwcmVkaWN0b3JzIGJlY2F1c2UgaXQgdXNlcyAuOSBvciA5MCUgb2YgdGhlIHRyYWluaW5nIHNldCB0byBjcmVhdGUgaXRzIHRyZWVzLiANCiogTGFyZ2VyIGBzaHJpbmthZ2VgIHBhcmFtZXRlciBzdWdnZXN0cyB0aGF0IGxlc3MgdHJlZXMgYXJlIGNyZWF0ZWQgaW4gdGhpcyBtb2RlbC4NCg0KDQojIyAoYikNCg0KV2hpY2ggbW9kZWwgZG8geW91IHRoaW5rIHdvdWxkIGJlIG1vcmUgcHJlZGljdGl2ZSBvZiBvdGhlciBzYW1wbGVzPw0KDQpUaGUgbGVmdCBtb2RlbCB3aXRoIGBzaHJpbmthZ2VgID0gMC4xLCBgYmFnLmZyYWN0aW9uYCA9IDAuMSB3b3VsZCBiZSBtb3JlIHByZWRpY3RpdmUgb2Ygb3RoZXIgc2FtcGxlcyBiZXR3ZWVuIHRoZSB0d28uIENvbnNpZGVyaW5nIHRoZSBhbW91bnQgb2YgdHJlZXMgaXQgc2hvdWxkIGhhdmUgaW4gY29tcGFyaXNvbiB0byB0aGUgbW9kZWwgb24gdGhlIHJpZ2h0IHdpdGggYHNocmlua2FnZWAgPSAwLjksIGBiYWcuZnJhY3Rpb25gID0gMC45Lg0KDQoNCiMjIChjKQ0KDQpIb3cgd291bGQgaW5jcmVhc2luZyBpbnRlcmFjdGlvbiBkZXB0aCBhZmZlY3QgdGhlIHNsb3BlIG9mIHByZWRpY3RvciBpbXBvcnRhbmNlIGZvciBlaXRoZXIgbW9kZWwgaW4gRmlnLiA4LjI0Pw0KDQpBY2NvcmRpbmcgdG8gW3B1Yl9qb3VybmFscyBsaW5rXShodHRwczovL3d3dy5mcy5mZWQudXMvcm0vcHVic19qb3VybmFscy8yMDE1L3JtcnNfMjAxNV9mcmVlbWFuX2UwMDEucGRmKSBTZWMgNC4yLCBwZy4gMTUgDQoNCiogVGhlIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIHNocmlua2FnZSAobGVhcm5pbmcgcmF0ZSkgYW5kIGludGVyYWN0aW9uIGRlcHRoICh0cmVlIGNvbXBsZXhpdHkpIGluIGRlY2lzaW9uIHRyZWUgbW9kZWxzLCBzcGVjaWZpY2FsbHkgaW4gYm9vc3RpbmcuIA0KKiBTaHJpbmthZ2UgaXMgaW52ZXJzZWx5IHJlbGF0ZWQgdG8gaW50ZXJhY3Rpb24gZGVwdGgsIG1lYW5pbmcgYXMgdHJlZSBjb21wbGV4aXR5IGluY3JlYXNlcywgdGhlIGxlYXJuaW5nIHJhdGUgc2hvdWxkIGRlY3JlYXNlIHRvIHByZXZlbnQgb3ZlcmZpdHRpbmcgYW5kIG1haW50YWluIG1vZGVsIHN0YWJpbGl0eS4NCiogVGhpcyBzbG93ZXIgbGVhcm5pbmcgcHJvY2VzcyBuZWNlc3NpdGF0ZXMgdXNpbmcgbW9yZSB0cmVlcy4NCiogTGFyZ2VyIGRhdGFzZXRzIGJldHRlciBzdXBwb3J0IG1vcmUgY29tcGxleCB0cmVlcywgYXMgdGhleSBjYW4gaGFuZGxlIHRoZSBkZXRhaWxlZCBzdHJ1Y3R1cmVzIHRoZXNlIHRyZWVzIGNyZWF0ZSB3aXRob3V0IG92ZXJmaXR0aW5nIHF1aWNrbHkuDQoqSW50ZXJhY3Rpb24gZGVwdGgvdHJlZSBzaXplIG9yIHRyZWUgY29tcGxleGl0eSwgYWZmZWN0cyB0aGUgbWF4aW11bSBzaXplIGZvciBlYWNoIHRyZWUuIA0KKiBBcyBtZW50aW9uZWQgdGhlcmUgaXMgYSBpbnZlcnNlIHJlbGF0aW9uc2hpcCB3aXRoIHNocmlua2FnZSB0byBpbnRlcmFjdGlvbiBkZXB0aC4gDQoqIFRoZSBsZWZ0IChgc2hyaW5rYWdlYCA9IDAuMSwgYGJhZy5mcmFjdGlvbmAgPSAwLjEpIGlzIGxpa2VseSB0byBiZW5lZml0IGZyb20gaW5jcmVhc2luZyBpbnRlcmFjdGlvbiBkZXB0aC4gDQoqIEJ1dCB0aGUgcmlnaHQgbWlnaHQgbm90IHNpbmNlIHRoZSBzaHJpbmthZ2UgcGFyYW1ldGVyIGlzIGFscmVhZHkgc28gaGlnaC4NCg0KDQoNCiMgOC43Lg0KDQpSZWZlciB0byBFeGVyY2lzZXMgNi4zIGFuZCA3LjUgd2hpY2ggZGVzY3JpYmUgYSBjaGVtaWNhbCBtYW51ZmFjdHVyaW5nIHByb2Nlc3MuIFVzZSB0aGUgc2FtZSBkYXRhIGltcHV0YXRpb24sIGRhdGEgc3BsaXR0aW5nLCBhbmQgcHJlLXByb2Nlc3Npbmcgc3RlcHMgYXMgYmVmb3JlIGFuZCB0cmFpbiBzZXZlcmFsIHRyZWUtYmFzZWQgbW9kZWxzOg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KI2xvYWQgZGF0YQ0KZGF0YShDaGVtaWNhbE1hbnVmYWN0dXJpbmdQcm9jZXNzKQ0KDQpzZXQuc2VlZCg2MjQpDQppbXB1dGVkX2RhdGEgPC0gcHJlUHJvY2VzcyhDaGVtaWNhbE1hbnVmYWN0dXJpbmdQcm9jZXNzLCBtZXRob2QgPSBjKCJrbm5JbXB1dGUiLCJuenYiLCAiY29yciIpKQ0KZGZfZnVsbCA8LSBwcmVkaWN0KGltcHV0ZWRfZGF0YSwgQ2hlbWljYWxNYW51ZmFjdHVyaW5nUHJvY2VzcykNCg0KY2hlbV9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRmX2Z1bGwkWWllbGQgLCBwPS44LCBsaXN0PUYpDQoNCmNoZW1fdHJhaW4gPC0gIGRmX2Z1bGxbY2hlbV9pbmRleCxdIA0KY2hlbV90ZXN0IDwtIGRmX2Z1bGxbLWNoZW1faW5kZXgsXQ0KDQp0cmFpbl9wcmVkIDwtIGNoZW1fdHJhaW5bLWMoMSldDQp0ZXN0X3ByZWQ8LSAgY2hlbV90ZXN0Wy1jKDEpXQ0KYGBgDQoNCiMjIChhKQ0KDQpXaGljaCB0cmVlLWJhc2VkIHJlZ3Jlc3Npb24gbW9kZWwgZ2l2ZXMgdGhlIG9wdGltYWwgcmVzYW1wbGluZyBhbmQgdGVzdCBzZXQgcGVyZm9ybWFuY2U/DQoNCiMjIyBCb29zdGVkIFRyZWVzDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc2V0LnNlZWQoNjI0KQ0KZ2JtX21vZGVsIDwtIGdibS5maXQodHJhaW5fcHJlZCwgY2hlbV90cmFpbiRZaWVsZCwgZGlzdHJpYnV0aW9uID0gImdhdXNzaWFuIikNCmdibV9tb2RlbA0KZ2JtX3ByZWQgPC0gcHJlZGljdChnYm1fbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X3ByZWQpDQpwb3N0UmVzYW1wbGUocHJlZCA9IGdibV9wcmVkLCBvYnMgPSBjaGVtX3Rlc3QkWWllbGQpDQpgYGANCg0KIyMjIEN1YmlzdA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnNldC5zZWVkKDYyNCkNCmN1YmVfbW9kZWwgPC0gY3ViaXN0KHRyYWluX3ByZWQsIGNoZW1fdHJhaW4kWWllbGQpDQpjdWJlX21vZGVsDQpjdWJlX3ByZWQgPC0gcHJlZGljdChjdWJlX21vZGVsLCBuZXdkYXRhID0gdGVzdF9wcmVkKQ0KcG9zdFJlc2FtcGxlKHByZWQgPSBjdWJlX3ByZWQsIG9icyA9IGNoZW1fdGVzdCRZaWVsZCkNCmBgYA0KDQojIyMgUmFuZG9tIEZvcmVzdA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnNldC5zZWVkKDYyNCkNCiNmaXQgdGhlIG1vZGVsDQpyZl9tb2RlbCA8LSByYW5kb21Gb3Jlc3QodHJhaW5fcHJlZCwgY2hlbV90cmFpbiRZaWVsZCwgaW1wb3J0YW5jZSA9IFRSVUUsIG50cmVlcyA9IDEwMDApDQpyZl9tb2RlbA0KDQpyZl9wcmVkIDwtIHByZWRpY3QocmZfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X3ByZWQpDQpwb3N0UmVzYW1wbGUocHJlZCA9IHJmX3ByZWQsIG9icyA9IGNoZW1fdGVzdCRZaWVsZCkNCmBgYA0KDQpSYW5kb20gRm9yZXN0IGhhcyB0aGUgb3B0aW1hbCBtZXRyaWNzIGJlY2F1c2Ugb2YgaXRzIFJNU0Ugb2YgMC42NzE1MzIwIChsb3dlc3QpIGFuZCBSc3F1YXJlZCBvZiAwLjczMjMyMjQgDQoNCiMjIChiKQ0KDQogDQoNClRvcCAxMDogIA0KTWFudWZhY3R1cmluZ1Byb2Nlc3MzMiwgQmlvbG9naWNhbE1hdGVyaWFsMDYsIEJpb2xvZ2ljYWxNYXRlcmlhbDAzLCBNYW51ZmFjdHVyaW5nUHJvY2VzczEzLCBNYW51ZmFjdHVyaW5nUHJvY2VzczM2LCBCaW9sb2dpY2FsTWF0ZXJpYWwxMSwgTWFudWZhY3R1cmluZ1Byb2Nlc3MwOSwgQmlvbG9naWNhbE1hdGVyaWFsMDgsIE1hbnVmYWN0dXJpbmdQcm9jZXNzMjgsIE1hbnVmYWN0dXJpbmdQcm9jZXNzMTENCg0KYGBge3J9DQpyZl9tb2RlbF92MjwtdmFySW1wKHJmX21vZGVsKQ0KYGBgDQoNCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnJmX21vZGVsX3NvcnRlZCA8LSByZl9tb2RlbF92MltvcmRlcigtcmZfbW9kZWxfdjIkT3ZlcmFsbCksICwgZHJvcCA9IEZBTFNFXSAjICdkcm9wID0gRkFMU0UnIGVuc3VyZXMgdGhlIHJlc3VsdCBpcyBzdGlsbCBhIGRhdGEgZnJhbWUNCmhlYWQocmZfbW9kZWxfc29ydGVkLDEwKQ0KYGBgDQoNCiMjIyBpDQoNCldoaWNoIHByZWRpY3RvcnMgYXJlIG1vc3QgaW1wb3J0YW50IGluIHRoZSBvcHRpbWFsIHRyZWUtYmFzZWQgcmVncmVzc2lvbiBtb2RlbD8gRG8gZWl0aGVyIHRoZSBiaW9sb2dpY2FsIG9yIHByb2Nlc3MgdmFyaWFibGVzIGRvbWluYXRlIHRoZSBsaXN0Pw0KDQpgTWFudWZhY3R1cmluZ1Byb2Nlc3MzMmAgaXMgdGhlIG1vc3QgaW1wb3J0YW50Lk5laXRoZXIgb2YgdGhlIHByZWRpY3RvcnMgZG9taW5hdGluZyB0aGUgbGlzdCB3aXRoIDUgYE1hbnVmYWN0dXJpbmdQcm9jZXNzYCBhbmQgNSBgQmlvbG9naWNhbFByb2Nlc3NgIGluIHRoZSB0b3AgdGVuLiANCg0KIyMjIGlpDQoNCkhvdyBkbyB0aGUgdG9wIDEwIGltcG9ydGFudCBwcmVkaWN0b3JzIGNvbXBhcmUgdG8gdGhlIHRvcCAxMCBwcmVkaWN0b3JzIGZyb20gdGhlIG9wdGltYWwgbGluZWFyIGFuZCBub25saW5lYXIgbW9kZWxzPw0KDQpUaGlzIGNvbWJpbmF0aW9uIGlzIGJhc2ljYWxseSBjb25zaXN0ZW50IHdpdGggdGhlIGltcG9ydGFuY2UgdmFyaWFibGVzIG9mIHRoZSBub24tbGluZWFyIG1vZGVsLCBub3Qgc28gbXVjaCB0aGUgbGluZWFyIG1vZGVsLiANCg0KIyMgKGMpDQoNClBsb3QgdGhlIG9wdGltYWwgc2luZ2xlIHRyZWUgd2l0aCB0aGUgZGlzdHJpYnV0aW9uIG9mIHlpZWxkIGluIHRoZSB0ZXJtaW5hbCBub2Rlcy4gRG9lcyB0aGlzIHZpZXcgb2YgdGhlIGRhdGEgcHJvdmlkZSBhZGRpdGlvbmFsIGtub3dsZWRnZSBhYm91dCB0aGUNCmJpb2xvZ2ljYWwgb3IgcHJvY2VzcyBwcmVkaWN0b3JzIGFuZCB0aGVpciByZWxhdGlvbnNoaXAgd2l0aCB5aWVsZD8NCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnJwYXJ0X3RyZWUgPC0gcnBhcnQoWWllbGQgfi4sIGRhdGEgPSBjaGVtX3RyYWluKQ0KcnBhcnQucGxvdChycGFydF90cmVlKQ0KYGBgDQo=