kNN Project: Predicting which MiLB hitters from A+ in 2013 made it to MLB
After going through the entire chapter on k-nearest neighbors (kNN), I decided to use kNN to build a model to predict which minor league players from high-A (A+) in 2013 ended up making a major league debut. I used a 100 plate appearance (PA) cutoff to be included in the data.
I settled on 4 predictors, one being the seasonal age of the player (Age) and the others being three rate statistics in strikeout percentage (K%), walk percentage (BB%) and isolated slugging percentage (ISO). The performance stats are typically decent indicators of player performance, while age plays a big factor in projecting talent. Additionally, the three performance metrics shouldn’t have much covariance between them.
I pulled the data from fangraphs and the leaderboard can be accessed here.
library("tidyverse")
library("mlr")
MiLB2013 <- read_csv("data/MiLB_HighA_2013.csv")
# Remove the player names from the data
MiLB2013knn <- select(MiLB2013,-Name)
summary(MiLB2013knn)
Age BB K ISO MLB
Min. :19.00 Min. :0.009901 Min. :0.05991 Min. :0.009615 Length:461
1st Qu.:22.00 1st Qu.:0.065089 1st Qu.:0.15353 1st Qu.:0.085308 Class :character
Median :23.00 Median :0.085389 Median :0.19312 Median :0.125767 Mode :character
Mean :22.88 Mean :0.087832 Mean :0.19784 Mean :0.130792
3rd Qu.:24.00 3rd Qu.:0.107784 3rd Qu.:0.23709 3rd Qu.:0.174545
Max. :29.00 Max. :0.204409 Max. :0.40415 Max. :0.325243
MiLB2013knn %>%
group_by(MLB) %>%
summarise(n = n()) %>%
mutate(freq = n / sum(n))
To figure out what k value is optimal for the kNN model, I did hyperparameter tuning for k. I expected k to be a bit lower initially and started across the set of k = 1:10. When k=10 was the best performing, I expanded it out to k=25. Again, k=25 came back as the best performing and then opened it up to 60 as it shows below.
# Task
MiLBTask <- makeClassifTask(data = MiLB2013knn, target = "MLB")
Provided data is not a pure data.frame but from class tbl_df, hence it will be converted.
# hyperarameter tuning
knnParamSpace <- makeParamSet(makeDiscreteParam("k", values = 1:60))
gridSearch <- makeTuneControlGrid()
cvForTuning <- makeResampleDesc("RepCV", folds = 10, reps = 20)
tunedMiLB <- tuneParams("classif.knn", task = MiLBTask,
resampling = cvForTuning,
par.set = knnParamSpace, control = gridSearch)
tunedMiLB
Tune result:
Op. pars: k=35
mmce.test.mean=0.1906707
tunedMiLB$x
$k
[1] 35
knnTuningData <- generateHyperParsEffectData(tunedMiLB)
plotHyperParsEffect(knnTuningData, x = "k", y = "mmce.test.mean",
plot.type = "line") +
theme_bw()

The value of k that minimizes the mean mmce is 33. The graph above displays the mmce mean for all values of k between 1 and 60. While 33 is the minimum value on the graph, a potential range of k between 28 to 35 could work and in another iteration, k=35 had the lowest value.
I expected a lower optimum value for k because the data only has 461 observations.
tunedKnnMiLB <- setHyperPars(makeLearner("classif.knn"),
par.vals = tunedMiLB$x)
tunedKnnModelMiLB <- train(tunedKnnMiLB, MiLBTask)
Model Performance
To evaluate performance I used K-fold cross validation with 5 folds and 50 repetitions and started with the tuned k=33.
knn <- makeLearner("classif.knn", par.vals = list("k"=33))
knnModel <- train(knn, MiLBTask)
kFold <- makeResampleDesc(method = "RepCV", folds = 5, reps = 50,
stratify = TRUE)
kFoldCV <- resample(learner = knn, task = MiLBTask,
resampling = kFold, measures = list(mmce, acc), show.info = FALSE)
kFoldCV$aggr
mmce.test.mean acc.test.mean
0.1923622 0.8076378
calculateConfusionMatrix(kFoldCV$pred, relative = TRUE)
Relative confusion matrix (normalized by row/column):
predicted
true No Yes -err.-
No 0.96/0.82 0.04/0.28 0.04
Yes 0.66/0.18 0.34/0.72 0.66
-err.- 0.18 0.28 0.19
Absolute confusion matrix:
predicted
true No Yes -err.-
No 16720 730 730
Yes 3704 1896 3704
-err.- 3704 730 4434
The confusion matrix above shows that “No” MLB predictions were correct 82% of the time and “Yes” MLB predictions were correct 72% of the time.
However, of the players that did make it to MLB, it was wrong on their classification 66% of the time.
So the model seems to be conservative when predicting that a player will make it to MLB. The data had 24% of players make it to MLB and in the cross validation, it only predicted “Yes” 11% of the time.
Ultimately the model does a pretty good job of identifying which players won’t make MLB but a horrible job identifying who ultimately does make MLB.
Because the “Yes” error was so high, I wondered if a lower k value, might trade off some of the overall model error for a bit better “Yes” MLB result. Looking back at the hyperparameters plot, there was a local minimum under 20, so I tested out the model with k=17.
knn2 <- makeLearner("classif.knn", par.vals = list("k"=17))
knnModel2 <- train(knn2, MiLBTask)
# Use same CV as above
# kFold <- makeResampleDesc(method = "RepCV", folds = 5, reps = 50,
# stratify = TRUE)
kFoldCV2 <- resample(learner = knn2, task = MiLBTask,
resampling = kFold, measures = list(mmce, acc), show.info = FALSE)
kFoldCV2$aggr
mmce.test.mean acc.test.mean
0.1995227 0.8004773
calculateConfusionMatrix(kFoldCV2$pred, relative = TRUE)
Relative confusion matrix (normalized by row/column):
predicted
true No Yes -err.-
No 0.93/0.83 0.07/0.35 0.07
Yes 0.61/0.17 0.39/0.65 0.61
-err.- 0.17 0.35 0.20
Absolute confusion matrix:
predicted
true No Yes -err.-
No 16242 1208 1208
Yes 3391 2209 3391
-err.- 3391 1208 4599
The result here is mixed. When the model predicted that a player made MLB, it was only correct 64% of the time, down from 72% from the first model. However, looking at the players that did make it to MLB, the model correctly classified 40%, up from 34% in the first model.
The number of total players that this model predicted would make MLB is 15%, up from 11% from the first model. This is still well short of the 24% mark that did make MLB.
Which model is best depends on your goals. I’m leaning toward the second model because it’s making a closer attempt at predicting how many players actually made it, even if it’s wrong more often. Model 1 predicted correctly that 38 players would make MLB on average, while model 2 correctly predicted 44 players on average. Again, considering 112 players from 2013 made it up, that’s not a great model!
Predictions
Even though neither model did a great job in cross validation and the models use all 2013 data, I wanted to explore predicting some players from 2014 using our models.
I selected players that were ranked 26-30 in wRC+ in 2014. These players were Carlos Correa, Derrick Chung, Tyler White, Kyle Waldrop and Daniel Carbonell. We have a major league regular that debuted at a young age in Correa, a quad-A hitter in Tyler White, a cup of coffee MLB player in Waldrop and two other players that never made it up to the bigs.
# data for Carlos Correa, Derrick Chung, Tyler White, Kyle Waldrop, Daniel Carbonell
newMiLBdata <- tibble(Age = c(19, 26, 23, 22, 23),
BB = c(.123, .095, .151, .076, .06),
K = c(.154, .1, .145, .194, .19),
ISO = c(.185, .118, .260, .156, .194))
# Using k=33
newMiLBPred1 <- predict(knnModel, newdata = newMiLBdata)
Provided data for prediction is not a pure data.frame but from class tbl_df, hence it will be converted.
getPredictionResponse(newMiLBPred1)
[1] Yes No No No No
Levels: No Yes
# Using k=17
newMiLBPred2 <- predict(knnModel2, newdata = newMiLBdata)
Provided data for prediction is not a pure data.frame but from class tbl_df, hence it will be converted.
getPredictionResponse(newMiLBPred2)
[1] Yes No Yes Yes No
Levels: No Yes
The first kNN model with k=33 only predicted Carlos Correa to make MLB out of the 5 players. In past results it also correctly selected Kyle Waldrop to make it to MLB. The conservative nature of handing out “yes” MLB predictions is present in our small sample set. In both iterations, the model was wrong on Tyler White. Given that his stats were all superior to Waldrop (who was predicted correctly in a past iteration) - it points to age potentially having a large impact in the kNN model.
The second kNN model with k=17 correctly predicted the playing futures of all 5 players. Given that the model on average was only correctly predicting MLB players 62% of the time, the results here are either (a) lucky or (b) biased based on my selection of high performing 2014 players (ranked 26-30 in wRC+).
Closing Thoughts
While kNN was easy to implement and fun to test out on this question, it may not be the best algorithm for predicting which players would make MLB. It’s easy to identify who won’t make it, but it’s a lot harder to identify who does! It would also be nice to have information about the variables used in the model - like which one is most closely related to making it to the big leagues, which is something kNN doesn’t offer.
I also wanted to note that a decent amount of error may have been introduced given the sample of players needing only 100 plate appearances to be included in the data. It’s well known that any player can get “hot” or “cold” for 100+ PA - see our 2014 example of Derrick Chung! Ideally we’d be looking at more data than just A+ performance to gauge their full abilities.
I’ll be looking to use this data in future algorithms - including in the next chapter on logistic regression!
LS0tCnRpdGxlOiAiUG9zdCAxOiBrLU5lYXJlc3QgTmVpZ2hib3JzIEFsZ29yaXRobSIKZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJWQgJUIgJVknKWAiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KPGJyLz48YnIvPgoqX19NYWNoaW5lIExlYXJuaW5nIFNlcmllcyBJbnRyb19fKgoKKkFsbCBwb3N0cyBmcm9tIHRoaXMgc2VyaWVzIGNhbiBiZSBmb3VuZCBhdCBbaGVyZV0oaHR0cHM6Ly9zbWFkYXBsYXlzZmFudGFzeS5jb20vbWxyLykuKgoKKkR1cmluZyB0aGlzIHRpbWUgYXQgaG9tZSB0aGF0IG1vc3Qgb2YgdXMgaGF2ZSBiZWVuIGZvcmNlZCBpbnRvIEkndmUgZGVjaWRlZCBzdGFydCByZXRvb2xpbmcgbXkgYXJzZW5hbCBhcyBhIHN0YXRpc3RpY2lhbi4gQWZ0ZXIgZ2V0dGluZyBteSBNYXN0ZXIncyBpbiBBcHBsaWVkIFN0YXRpc3RpY3MsIEkndmUgc3BlbnQgdGhlIHBhc3QgNyB5ZWFycyB3b3JraW5nIGluIG1hcmtldGluZyBhbmFseXRpY3MuIEkndmUga2VwdCBzb21lIGFzcGVjdHMgb2YgdGhlc2Ugc2tpbGxzIHNoYXJwLCBpbiBwYXJ0aWN1bGFyIGxpbmVhciByZWdyZXNzaW9uIGFuZCBjb2RpbmcgaW4gUiAoYnVpbGRpbmcgc2hpbnkgYXBwcyBhbmQgbGVhcm5pbmcvZGV2ZWxvcGluZyB0aWR5dmVyc2UgdG9vbGJveCkuIEJ1dCBpdCdzIHRpbWUgdG8gYm90aCByZWZyZXNoIGFuZCBsZWFybiBzb21lIG5ldyBzdGF0aXN0aWNhbCB0ZWNobmlxdWVzLiogCgoqSSBkZWNpZGVkIHRvIGRpdmUgaW50byBtYWNoaW5lIGxlYXJuaW5nIGFuZCBkbyB0aGF0IGluIFIuIEl0J3MgYSB0b3BpYyB3aGVyZSBJIGhhdmUgbGl0dGxlIGV4cG9zdXJlLCBidXQgaGF2ZSBhIGxvdCB0byBsZWFybi4gQWZ0ZXIgY29taW5nIGFjcm9zcyB0aGUgYm9vayBbTWFjaGluZSBMZWFybmluZyB3aXRoIFIsIHRoZSB0aWR5dmVyc2UsIGFuZCBtbHJdKGh0dHBzOi8vd3d3Lm1hbm5pbmcuY29tL2Jvb2tzL21hY2hpbmUtbGVhcm5pbmctd2l0aC1yLXRoZS10aWR5dmVyc2UtYW5kLW1scikgYnkgW0hlZmluIFJoeXNdKGh0dHBzOi8vdHdpdHRlci5jb20vSFJKMjEpIEkgZGVjaWRlZCB0byBidXkgaXQgYW5kIG1ha2UgdGhlIGxlYXAuIEknbSBub3QgZmFyIGludG8gdGhlIGJvb2ssIGJ1dCBzbyBmYXIgaXQncyBiZWVuIHJlYWxseSBlYXN5IHRvIGZvbGxvdyBhbG9uZyBhbmQgZG9lcyBhIGdyZWF0IGpvYiBnb2luZyBvdmVyIHRoZSB0aWR5dmVyc2UuKgoKKk15IGdvYWwgaXMgdG8gZ28gdGhyb3VnaCB0aGUgZW50aXJlIGJvb2sgY2hhcHRlciBieSBjaGFwdGVyLCBhbmQgYXMgSSdtIGEgYmlnIGJhc2ViYWxsIG5lcmQsIGNyZWF0ZSBzbWFsbCBwcm9qZWN0cyB3aXRoIGJhc2ViYWxsIGRhdGEgZm9yIGVhY2ggYWxnb3JpdGhtLioKCjxici8+CgojIyMga05OIFByb2plY3Q6IFByZWRpY3Rpbmcgd2hpY2ggTWlMQiBoaXR0ZXJzIGZyb20gQSsgaW4gMjAxMyBtYWRlIGl0IHRvIE1MQgo8YnIvPgpBZnRlciBnb2luZyB0aHJvdWdoIHRoZSBlbnRpcmUgY2hhcHRlciBvbiBrLW5lYXJlc3QgbmVpZ2hib3JzIChrTk4pLCBJIGRlY2lkZWQgdG8gdXNlIGtOTiB0byBidWlsZCBhIG1vZGVsIHRvIHByZWRpY3Qgd2hpY2ggbWlub3IgbGVhZ3VlIHBsYXllcnMgZnJvbSBoaWdoLUEgKEErKSBpbiAyMDEzIGVuZGVkIHVwIG1ha2luZyBhIG1ham9yIGxlYWd1ZSBkZWJ1dC4gSSB1c2VkIGEgMTAwIHBsYXRlIGFwcGVhcmFuY2UgKFBBKSBjdXRvZmYgdG8gYmUgaW5jbHVkZWQgaW4gdGhlIGRhdGEuCgpJIHNldHRsZWQgb24gNCBwcmVkaWN0b3JzLCBvbmUgYmVpbmcgdGhlIHNlYXNvbmFsIGFnZSBvZiB0aGUgcGxheWVyIChBZ2UpIGFuZCB0aGUgb3RoZXJzIGJlaW5nIHRocmVlIHJhdGUgc3RhdGlzdGljcyBpbiBzdHJpa2VvdXQgcGVyY2VudGFnZSAoSyUpLCB3YWxrIHBlcmNlbnRhZ2UgKEJCJSkgYW5kIGlzb2xhdGVkIHNsdWdnaW5nIHBlcmNlbnRhZ2UgKElTTykuIFRoZSBwZXJmb3JtYW5jZSBzdGF0cyBhcmUgdHlwaWNhbGx5IGRlY2VudCBpbmRpY2F0b3JzIG9mIHBsYXllciBwZXJmb3JtYW5jZSwgd2hpbGUgYWdlIHBsYXlzIGEgYmlnIGZhY3RvciBpbiBwcm9qZWN0aW5nIHRhbGVudC4gQWRkaXRpb25hbGx5LCB0aGUgdGhyZWUgcGVyZm9ybWFuY2UgbWV0cmljcyBzaG91bGRuJ3QgaGF2ZSBtdWNoIGNvdmFyaWFuY2UgYmV0d2VlbiB0aGVtLgoKSSBwdWxsZWQgdGhlIGRhdGEgZnJvbSBmYW5ncmFwaHMgYW5kIHRoZSBsZWFkZXJib2FyZCBjYW4gYmUgYWNjZXNzZWQgW2hlcmVdKGh0dHBzOi8vd3d3LmZhbmdyYXBocy5jb20vbGVhZGVycy9taW5vci1sZWFndWU/cG9zPWFsbCZsZz04LDksMTAmc3RhdHM9YmF0JnF1YWw9MTAwJnR5cGU9MSZ0ZWFtPSZzZWFzb249MjAxMyZzZWFzb25FbmQ9MjAxMyZvcmc9JmluZD0wJnNwbGl0VGVhbT1mYWxzZSZwbGF5ZXJzPSZzb3J0PTE4LDEpLgoKYGBge3IgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSgidGlkeXZlcnNlIikKbGlicmFyeSgibWxyIikKCk1pTEIyMDEzIDwtIHJlYWRfY3N2KCJkYXRhL01pTEJfSGlnaEFfMjAxMy5jc3YiKQoKIyBSZW1vdmUgdGhlIHBsYXllciBuYW1lcyBmcm9tIHRoZSBkYXRhCk1pTEIyMDEza25uIDwtIHNlbGVjdChNaUxCMjAxMywtTmFtZSkKCnN1bW1hcnkoTWlMQjIwMTNrbm4pCgpNaUxCMjAxM2tubiAlPiUgCiAgZ3JvdXBfYnkoTUxCKSAlPiUKICBzdW1tYXJpc2UobiA9IG4oKSkgJT4lCiAgbXV0YXRlKGZyZXEgPSBuIC8gc3VtKG4pKQpgYGAKClRvIGZpZ3VyZSBvdXQgd2hhdCBrIHZhbHVlIGlzIG9wdGltYWwgZm9yIHRoZSBrTk4gbW9kZWwsIEkgZGlkIGh5cGVycGFyYW1ldGVyIHR1bmluZyBmb3Igay4gSSBleHBlY3RlZCBrIHRvIGJlIGEgYml0IGxvd2VyIGluaXRpYWxseSBhbmQgc3RhcnRlZCBhY3Jvc3MgdGhlIHNldCBvZiBrID0gMToxMC4gV2hlbiBrPTEwIHdhcyB0aGUgYmVzdCBwZXJmb3JtaW5nLCBJIGV4cGFuZGVkIGl0IG91dCB0byBrPTI1LiBBZ2Fpbiwgaz0yNSBjYW1lIGJhY2sgYXMgdGhlIGJlc3QgcGVyZm9ybWluZyBhbmQgdGhlbiBvcGVuZWQgaXQgdXAgdG8gNjAgYXMgaXQgc2hvd3MgYmVsb3cuCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQojIFRhc2sKTWlMQlRhc2sgPC0gbWFrZUNsYXNzaWZUYXNrKGRhdGEgPSBNaUxCMjAxM2tubiwgdGFyZ2V0ID0gIk1MQiIpCgojIGh5cGVyYXJhbWV0ZXIgdHVuaW5nCmtublBhcmFtU3BhY2UgPC0gbWFrZVBhcmFtU2V0KG1ha2VEaXNjcmV0ZVBhcmFtKCJrIiwgdmFsdWVzID0gMTo2MCkpCmdyaWRTZWFyY2ggPC0gbWFrZVR1bmVDb250cm9sR3JpZCgpCmN2Rm9yVHVuaW5nIDwtIG1ha2VSZXNhbXBsZURlc2MoIlJlcENWIiwgZm9sZHMgPSAxMCwgcmVwcyA9IDIwKQp0dW5lZE1pTEIgPC0gdHVuZVBhcmFtcygiY2xhc3NpZi5rbm4iLCB0YXNrID0gTWlMQlRhc2ssCiAgICAgICAgICAgICAgICAgICAgIHJlc2FtcGxpbmcgPSBjdkZvclR1bmluZywKICAgICAgICAgICAgICAgICAgICAgcGFyLnNldCA9IGtublBhcmFtU3BhY2UsIGNvbnRyb2wgPSBncmlkU2VhcmNoKQp0dW5lZE1pTEIKdHVuZWRNaUxCJHgKCmtublR1bmluZ0RhdGEgPC0gZ2VuZXJhdGVIeXBlclBhcnNFZmZlY3REYXRhKHR1bmVkTWlMQikKCnBsb3RIeXBlclBhcnNFZmZlY3Qoa25uVHVuaW5nRGF0YSwgeCA9ICJrIiwgeSA9ICJtbWNlLnRlc3QubWVhbiIsCiAgICAgICAgICAgICAgICAgICAgcGxvdC50eXBlID0gImxpbmUiKSArCiAgdGhlbWVfYncoKQoKYGBgCgpUaGUgdmFsdWUgb2YgayB0aGF0IG1pbmltaXplcyB0aGUgbWVhbiBtbWNlIGlzIDMzLiBUaGUgZ3JhcGggYWJvdmUgZGlzcGxheXMgdGhlIG1tY2UgbWVhbiBmb3IgYWxsIHZhbHVlcyBvZiBrIGJldHdlZW4gMSBhbmQgNjAuIFdoaWxlIDMzIGlzIHRoZSBtaW5pbXVtIHZhbHVlIG9uIHRoZSBncmFwaCwgYSBwb3RlbnRpYWwgcmFuZ2Ugb2YgayBiZXR3ZWVuIDI4IHRvIDM1IGNvdWxkIHdvcmsgYW5kIGluIGFub3RoZXIgaXRlcmF0aW9uLCBrPTM1IGhhZCB0aGUgbG93ZXN0IHZhbHVlLgoKSSBleHBlY3RlZCBhIGxvd2VyIG9wdGltdW0gdmFsdWUgZm9yIGsgYmVjYXVzZSB0aGUgZGF0YSBvbmx5IGhhcyA0NjEgb2JzZXJ2YXRpb25zLgoKYGBge3IgbWVzc2FnZT1GQUxTRX0KdHVuZWRLbm5NaUxCIDwtIHNldEh5cGVyUGFycyhtYWtlTGVhcm5lcigiY2xhc3NpZi5rbm4iKSwKICAgICAgICAgICAgICAgICAgICAgICAgIHBhci52YWxzID0gdHVuZWRNaUxCJHgpCgp0dW5lZEtubk1vZGVsTWlMQiA8LSB0cmFpbih0dW5lZEtubk1pTEIsIE1pTEJUYXNrKQpgYGAKCjxici8+PGJyLz4KCl9fTW9kZWwgUGVyZm9ybWFuY2VfXyAKClRvIGV2YWx1YXRlIHBlcmZvcm1hbmNlIEkgdXNlZCBLLWZvbGQgY3Jvc3MgdmFsaWRhdGlvbiB3aXRoIDUgZm9sZHMgYW5kIDUwIHJlcGV0aXRpb25zIGFuZCBzdGFydGVkIHdpdGggdGhlIHR1bmVkIGs9MzMuCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQoKa25uIDwtIG1ha2VMZWFybmVyKCJjbGFzc2lmLmtubiIsIHBhci52YWxzID0gbGlzdCgiayI9MzMpKQprbm5Nb2RlbCA8LSB0cmFpbihrbm4sIE1pTEJUYXNrKQoKa0ZvbGQgPC0gbWFrZVJlc2FtcGxlRGVzYyhtZXRob2QgPSAiUmVwQ1YiLCBmb2xkcyA9IDUsIHJlcHMgPSA1MCwKICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJhdGlmeSA9IFRSVUUpCgprRm9sZENWIDwtIHJlc2FtcGxlKGxlYXJuZXIgPSBrbm4sIHRhc2sgPSBNaUxCVGFzaywKICAgICAgICAgICAgICAgICAgICByZXNhbXBsaW5nID0ga0ZvbGQsIG1lYXN1cmVzID0gbGlzdChtbWNlLCBhY2MpLCBzaG93LmluZm8gPSBGQUxTRSkKCmtGb2xkQ1YkYWdncgoKY2FsY3VsYXRlQ29uZnVzaW9uTWF0cml4KGtGb2xkQ1YkcHJlZCwgcmVsYXRpdmUgPSBUUlVFKQoKYGBgCgpUaGUgY29uZnVzaW9uIG1hdHJpeCBhYm92ZSBzaG93cyB0aGF0ICJObyIgTUxCIHByZWRpY3Rpb25zIHdlcmUgY29ycmVjdCA4MiUgb2YgdGhlIHRpbWUgYW5kICJZZXMiIE1MQiBwcmVkaWN0aW9ucyB3ZXJlIGNvcnJlY3QgNzIlIG9mIHRoZSB0aW1lLgoKSG93ZXZlciwgb2YgdGhlIHBsYXllcnMgdGhhdCBkaWQgbWFrZSBpdCB0byBNTEIsIGl0IHdhcyB3cm9uZyBvbiB0aGVpciBjbGFzc2lmaWNhdGlvbiA2NiUgb2YgdGhlIHRpbWUuCgpTbyB0aGUgbW9kZWwgc2VlbXMgdG8gYmUgY29uc2VydmF0aXZlIHdoZW4gcHJlZGljdGluZyB0aGF0IGEgcGxheWVyIHdpbGwgbWFrZSBpdCB0byBNTEIuIFRoZSBkYXRhIGhhZCAyNCUgb2YgcGxheWVycyBtYWtlIGl0IHRvIE1MQiBhbmQgaW4gdGhlIGNyb3NzIHZhbGlkYXRpb24sIGl0IG9ubHkgcHJlZGljdGVkICJZZXMiIDExJSBvZiB0aGUgdGltZS4KClVsdGltYXRlbHkgdGhlIG1vZGVsIGRvZXMgYSBwcmV0dHkgZ29vZCBqb2Igb2YgaWRlbnRpZnlpbmcgd2hpY2ggcGxheWVycyB3b24ndCBtYWtlIE1MQiBidXQgYSBob3JyaWJsZSBqb2IgaWRlbnRpZnlpbmcgd2hvIHVsdGltYXRlbHkgZG9lcyBtYWtlIE1MQi4KCkJlY2F1c2UgdGhlICJZZXMiIGVycm9yIHdhcyBzbyBoaWdoLCBJIHdvbmRlcmVkIGlmIGEgbG93ZXIgayB2YWx1ZSwgbWlnaHQgdHJhZGUgb2ZmIHNvbWUgb2YgdGhlIG92ZXJhbGwgbW9kZWwgZXJyb3IgZm9yIGEgYml0IGJldHRlciAiWWVzIiBNTEIgcmVzdWx0LiBMb29raW5nIGJhY2sgYXQgdGhlIGh5cGVycGFyYW1ldGVycyBwbG90LCB0aGVyZSB3YXMgYSBsb2NhbCBtaW5pbXVtIHVuZGVyIDIwLCBzbyBJIHRlc3RlZCBvdXQgdGhlIG1vZGVsIHdpdGggaz0xNy4KCmBgYHtyfQprbm4yIDwtIG1ha2VMZWFybmVyKCJjbGFzc2lmLmtubiIsIHBhci52YWxzID0gbGlzdCgiayI9MTcpKQprbm5Nb2RlbDIgPC0gdHJhaW4oa25uMiwgTWlMQlRhc2spCgojIFVzZSBzYW1lIENWIGFzIGFib3ZlCiMga0ZvbGQgPC0gbWFrZVJlc2FtcGxlRGVzYyhtZXRob2QgPSAiUmVwQ1YiLCBmb2xkcyA9IDUsIHJlcHMgPSA1MCwKIyAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyYXRpZnkgPSBUUlVFKQoKa0ZvbGRDVjIgPC0gcmVzYW1wbGUobGVhcm5lciA9IGtubjIsIHRhc2sgPSBNaUxCVGFzaywKICAgICAgICAgICAgICAgICAgICByZXNhbXBsaW5nID0ga0ZvbGQsIG1lYXN1cmVzID0gbGlzdChtbWNlLCBhY2MpLCBzaG93LmluZm8gPSBGQUxTRSkKCmtGb2xkQ1YyJGFnZ3IKCmNhbGN1bGF0ZUNvbmZ1c2lvbk1hdHJpeChrRm9sZENWMiRwcmVkLCByZWxhdGl2ZSA9IFRSVUUpCmBgYAoKVGhlIHJlc3VsdCBoZXJlIGlzIG1peGVkLiBXaGVuIHRoZSBtb2RlbCBwcmVkaWN0ZWQgdGhhdCBhIHBsYXllciBtYWRlIE1MQiwgaXQgd2FzIG9ubHkgY29ycmVjdCA2NCUgb2YgdGhlIHRpbWUsIGRvd24gZnJvbSA3MiUgZnJvbSB0aGUgZmlyc3QgbW9kZWwuIEhvd2V2ZXIsIGxvb2tpbmcgYXQgdGhlIHBsYXllcnMgdGhhdCBkaWQgbWFrZSBpdCB0byBNTEIsIHRoZSBtb2RlbCBjb3JyZWN0bHkgY2xhc3NpZmllZCA0MCUsIHVwIGZyb20gMzQlIGluIHRoZSBmaXJzdCBtb2RlbC4KClRoZSBudW1iZXIgb2YgdG90YWwgcGxheWVycyB0aGF0IHRoaXMgbW9kZWwgcHJlZGljdGVkIHdvdWxkIG1ha2UgTUxCIGlzIDE1JSwgdXAgZnJvbSAxMSUgZnJvbSB0aGUgZmlyc3QgbW9kZWwuIFRoaXMgaXMgc3RpbGwgd2VsbCBzaG9ydCBvZiB0aGUgMjQlIG1hcmsgdGhhdCBkaWQgbWFrZSBNTEIuCgpXaGljaCBtb2RlbCBpcyBiZXN0IGRlcGVuZHMgb24geW91ciBnb2Fscy4gSSdtIGxlYW5pbmcgdG93YXJkIHRoZSBzZWNvbmQgbW9kZWwgYmVjYXVzZSBpdCdzIG1ha2luZyBhIGNsb3NlciBhdHRlbXB0IGF0IHByZWRpY3RpbmcgaG93IG1hbnkgcGxheWVycyBhY3R1YWxseSBtYWRlIGl0LCBldmVuIGlmIGl0J3Mgd3JvbmcgbW9yZSBvZnRlbi4gTW9kZWwgMSBwcmVkaWN0ZWQgY29ycmVjdGx5IHRoYXQgMzggcGxheWVycyB3b3VsZCBtYWtlIE1MQiBvbiBhdmVyYWdlLCB3aGlsZSBtb2RlbCAyIGNvcnJlY3RseSBwcmVkaWN0ZWQgNDQgcGxheWVycyBvbiBhdmVyYWdlLiBBZ2FpbiwgY29uc2lkZXJpbmcgMTEyIHBsYXllcnMgZnJvbSAyMDEzIG1hZGUgaXQgdXAsIHRoYXQncyBub3QgYSBncmVhdCBtb2RlbCEKCjxici8+PGJyLz4KX19QcmVkaWN0aW9uc19fCgpFdmVuIHRob3VnaCBuZWl0aGVyIG1vZGVsIGRpZCBhIGdyZWF0IGpvYiBpbiBjcm9zcyB2YWxpZGF0aW9uIGFuZCB0aGUgbW9kZWxzIHVzZSBhbGwgMjAxMyBkYXRhLCBJIHdhbnRlZCB0byBleHBsb3JlIHByZWRpY3Rpbmcgc29tZSBwbGF5ZXJzIGZyb20gMjAxNCB1c2luZyBvdXIgbW9kZWxzLiAKCkkgc2VsZWN0ZWQgcGxheWVycyB0aGF0IHdlcmUgcmFua2VkIDI2LTMwIGluIHdSQysgaW4gMjAxNC4gVGhlc2UgcGxheWVycyB3ZXJlIENhcmxvcyBDb3JyZWEsIERlcnJpY2sgQ2h1bmcsIFR5bGVyIFdoaXRlLCBLeWxlIFdhbGRyb3AgYW5kIERhbmllbCBDYXJib25lbGwuIFdlIGhhdmUgYSBtYWpvciBsZWFndWUgcmVndWxhciB0aGF0IGRlYnV0ZWQgYXQgYSB5b3VuZyBhZ2UgaW4gQ29ycmVhLCBhIHF1YWQtQSBoaXR0ZXIgaW4gVHlsZXIgV2hpdGUsIGEgY3VwIG9mIGNvZmZlZSBNTEIgcGxheWVyIGluIFdhbGRyb3AgYW5kIHR3byBvdGhlciBwbGF5ZXJzIHRoYXQgbmV2ZXIgbWFkZSBpdCB1cCB0byB0aGUgYmlncy4KCmBgYHtyfQojIGRhdGEgZm9yIENhcmxvcyBDb3JyZWEsIERlcnJpY2sgQ2h1bmcsIFR5bGVyIFdoaXRlLCBLeWxlIFdhbGRyb3AsIERhbmllbCBDYXJib25lbGwKbmV3TWlMQmRhdGEgPC0gdGliYmxlKEFnZSA9IGMoMTksIDI2LCAyMywgMjIsIDIzKSwKICAgICAgICAgICAgICAgICAgICAgIEJCID0gYyguMTIzLCAuMDk1LCAuMTUxLCAuMDc2LCAuMDYpLAogICAgICAgICAgICAgICAgICAgICAgSyA9IGMoLjE1NCwgLjEsIC4xNDUsIC4xOTQsIC4xOSksCiAgICAgICAgICAgICAgICAgICAgICBJU08gPSBjKC4xODUsIC4xMTgsIC4yNjAsIC4xNTYsIC4xOTQpKQoKIyBVc2luZyBrPTMzCm5ld01pTEJQcmVkMSA8LSBwcmVkaWN0KGtubk1vZGVsLCBuZXdkYXRhID0gbmV3TWlMQmRhdGEpCgpnZXRQcmVkaWN0aW9uUmVzcG9uc2UobmV3TWlMQlByZWQxKQoKIyBVc2luZyBrPTE3Cm5ld01pTEJQcmVkMiA8LSBwcmVkaWN0KGtubk1vZGVsMiwgbmV3ZGF0YSA9IG5ld01pTEJkYXRhKQoKZ2V0UHJlZGljdGlvblJlc3BvbnNlKG5ld01pTEJQcmVkMikKYGBgCgpUaGUgZmlyc3Qga05OIG1vZGVsIHdpdGggaz0zMyBvbmx5IHByZWRpY3RlZCBDYXJsb3MgQ29ycmVhIHRvIG1ha2UgTUxCIG91dCBvZiB0aGUgNSBwbGF5ZXJzLiBJbiBwYXN0IHJlc3VsdHMgaXQgYWxzbyBjb3JyZWN0bHkgc2VsZWN0ZWQgS3lsZSBXYWxkcm9wIHRvIG1ha2UgaXQgdG8gTUxCLiBUaGUgY29uc2VydmF0aXZlIG5hdHVyZSBvZiBoYW5kaW5nIG91dCAieWVzIiBNTEIgcHJlZGljdGlvbnMgaXMgcHJlc2VudCBpbiBvdXIgc21hbGwgc2FtcGxlIHNldC4gSW4gYm90aCBpdGVyYXRpb25zLCB0aGUgbW9kZWwgd2FzIHdyb25nIG9uIFR5bGVyIFdoaXRlLiBHaXZlbiB0aGF0IGhpcyBzdGF0cyB3ZXJlIGFsbCBzdXBlcmlvciB0byBXYWxkcm9wICh3aG8gd2FzIHByZWRpY3RlZCBjb3JyZWN0bHkgaW4gYSBwYXN0IGl0ZXJhdGlvbikgLSBpdCBwb2ludHMgdG8gYWdlIHBvdGVudGlhbGx5IGhhdmluZyBhIGxhcmdlIGltcGFjdCBpbiB0aGUga05OIG1vZGVsLiAKClRoZSBzZWNvbmQga05OIG1vZGVsIHdpdGggaz0xNyBjb3JyZWN0bHkgcHJlZGljdGVkIHRoZSBwbGF5aW5nIGZ1dHVyZXMgb2YgYWxsIDUgcGxheWVycy4gR2l2ZW4gdGhhdCB0aGUgbW9kZWwgb24gYXZlcmFnZSB3YXMgb25seSBjb3JyZWN0bHkgcHJlZGljdGluZyBNTEIgcGxheWVycyA2MiUgb2YgdGhlIHRpbWUsIHRoZSByZXN1bHRzIGhlcmUgYXJlIGVpdGhlciAoYSkgbHVja3kgb3IgKGIpIGJpYXNlZCBiYXNlZCBvbiBteSBzZWxlY3Rpb24gb2YgaGlnaCBwZXJmb3JtaW5nIDIwMTQgcGxheWVycyAocmFua2VkIDI2LTMwIGluIHdSQyspLiAKCjxici8+PGJyLz4KX19DbG9zaW5nIFRob3VnaHRzX18KCldoaWxlIGtOTiB3YXMgZWFzeSB0byBpbXBsZW1lbnQgYW5kIGZ1biB0byB0ZXN0IG91dCBvbiB0aGlzIHF1ZXN0aW9uLCBpdCBtYXkgbm90IGJlIHRoZSBiZXN0IGFsZ29yaXRobSBmb3IgcHJlZGljdGluZyB3aGljaCBwbGF5ZXJzIHdvdWxkIG1ha2UgTUxCLiBJdCdzIGVhc3kgdG8gaWRlbnRpZnkgd2hvIHdvbid0IG1ha2UgaXQsIGJ1dCBpdCdzIGEgbG90IGhhcmRlciB0byBpZGVudGlmeSB3aG8gZG9lcyEgSXQgd291bGQgYWxzbyBiZSBuaWNlIHRvIGhhdmUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHZhcmlhYmxlcyB1c2VkIGluIHRoZSBtb2RlbCAtIGxpa2Ugd2hpY2ggb25lIGlzIG1vc3QgY2xvc2VseSByZWxhdGVkIHRvIG1ha2luZyBpdCB0byB0aGUgYmlnIGxlYWd1ZXMsIHdoaWNoIGlzIHNvbWV0aGluZyBrTk4gZG9lc24ndCBvZmZlci4gCgpJIGFsc28gd2FudGVkIHRvIG5vdGUgdGhhdCBhIGRlY2VudCBhbW91bnQgb2YgZXJyb3IgbWF5IGhhdmUgYmVlbiBpbnRyb2R1Y2VkIGdpdmVuIHRoZSBzYW1wbGUgb2YgcGxheWVycyBuZWVkaW5nIG9ubHkgMTAwIHBsYXRlIGFwcGVhcmFuY2VzIHRvIGJlIGluY2x1ZGVkIGluIHRoZSBkYXRhLiBJdCdzIHdlbGwga25vd24gdGhhdCBhbnkgcGxheWVyIGNhbiBnZXQgImhvdCIgb3IgImNvbGQiIGZvciAxMDArIFBBIC0gc2VlIG91ciAyMDE0IGV4YW1wbGUgb2YgRGVycmljayBDaHVuZyEgSWRlYWxseSB3ZSdkIGJlIGxvb2tpbmcgYXQgbW9yZSBkYXRhIHRoYW4ganVzdCBBKyBwZXJmb3JtYW5jZSB0byBnYXVnZSB0aGVpciBmdWxsIGFiaWxpdGllcy4KCkknbGwgYmUgbG9va2luZyB0byB1c2UgdGhpcyBkYXRhIGluIGZ1dHVyZSBhbGdvcml0aG1zIC0gaW5jbHVkaW5nIGluIHRoZSBuZXh0IGNoYXB0ZXIgb24gbG9naXN0aWMgcmVncmVzc2lvbiEKCjxici8+PGJyLz4K