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 (V6 – V10)?
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=