Introduction
This vignette describes how you can use the Observational Health Data
Sciences and Informatics (OHDSI) PatientLevelPrediction
package to create learning curves. This vignette assumes you have read
and are comfortable with building patient level prediction models as
described in the vignette('BuildingPredictiveModels')
.
Prediction models will show overly-optimistic performance when
predicting on the same data as used for training. Therefore,
best-practice is to partition our data into a training set and testing
set. We then train our prediction model on the training set portion and
asses its ability to generalize to unseen data by measuring its
performance on the testing set.
Learning curves assess the effect of training set size on model
performance by training a sequence of prediction models on successively
larger subsets of the training set. A learning curve plot can also help
in diagnosing a bias or variance problem as explained below.
Learning curve example.
Figure 1, shows an example of learning curve plot in which the
vertical axis represents the model performance and the horizontal axis
the training set size. If training set size is small, the performance on
the training set is high, because a model can often be fitted well to a
limited number of training examples. At the same time, the performance
on the testing set will be poor, because the model trained on such a
limited number of training examples will not generalize well to unseen
data in the testing set. As the training set size increases, the
performance of the model on the training set will decrease. It becomes
more difficult for the model to find a good fit through all the training
examples. Also, the model will be trained on a more representative
portion of training examples, making it generalize better to unseen
data. This can be observed by the increasin testing set performance.
The learning curve can help us in diagnosing bias and variance
problems with our classifier which will provide guidance on how to
further improve our model. We can observe high variance (overfitting) in
a prediction model if it performs well on the training set, but poorly
on the testing set (Figure 2). Adding additional data is a common
approach to counteract high variance. From the learning curve it becomes
apparent, that adding additional data may improve performance on the
testing set a little further, as the learning curve has not yet
plateaued and, thus, the model is not saturated yet. Therefore, adding
more data will decrease the gap between training set and testing set,
which is the main indicator for a high variance problem.
Prediction model suffering from high
variance.
Furthermore, we can observe high bias (underfitting) if a prediction
model performs poorly on the training set as well as on the testing set
(Figure 3). The learning curves of training set and testing set have
flattened on a low performance with only a small gap in between them.
Adding additional data will in this case have little to no impact on the
model performance. Choosing another prediction algorithm that can find
more complex (for example non-linear) relationships in the data may be
an alternative approach to consider in this high bias situation.
Prediction model suffering from high bias.
Creating the learning curve
Use the PatientLevelPrediction
package to create a plpData
object . Alternatively, you can
make use of the data simulator. The following code snippet creates data
for 12000 patients.
set.seed(1234)
data(plpDataSimulationProfile)
sampleSize <- 12000
plpData <- simulatePlpData(
plpDataSimulationProfile,
n = sampleSize
)
Specify the population settings (this does additional exclusions such
as requiring minimum prior observation or no prior outcome as well as
specifying the time-at-risk period to enable labels to be created):
populationSettings <- createStudyPopulationSettings(
binary = TRUE,
firstExposureOnly = FALSE,
washoutPeriod = 0,
removeSubjectsWithPriorOutcome = FALSE,
priorOutcomeLookback = 99999,
requireTimeAtRisk = FALSE,
minTimeAtRisk = 0,
riskWindowStart = 0,
riskWindowEnd = 365,
verbosity = "INFO"
)
Specify the prediction algorithm to be used.
# Use LASSO logistic regression
modelSettings <- setLassoLogisticRegression()
Specify the split settings and a sequence of training set fractions
(these over ride the splitSetting trainFraction). Alternatively, instead
of trainFractions
, you can provide a sequence of training
events (trainEvents
) instead of the training set fractions.
This is recommended, because our research has shown that number of
events is the important determinant of model performance. Make sure that
your training set contains the number of events specified.
splitSettings <- createDefaultSplitSetting(
testFraction = 0.2,
type = "stratified",
splitSeed = 1000
)
trainFractions <- seq(0.1, 0.8, 0.1) # Create eight training set fractions
# alternatively use a sequence of training events by uncommenting the line below.
# trainEvents <- seq(100, 5000, 100)
Create the learning curve object.
learningCurve <- createLearningCurve(
plpData = plpData,
outcomeId = 2,
parallel = TRUE,
cores = 4,
modelSettings = modelSettings,
saveDirectory = getwd(),
analysisId = "learningCurve",
populationSettings = populationSettings,
splitSettings = splitSettings,
trainFractions = trainFractions,
trainEvents = NULL,
preprocessSettings = createPreprocessSettings(
minFraction = 0.001,
normalize = TRUE
),
executeSettings = createExecuteSettings(
runSplitData = TRUE,
runSampleData = FALSE,
runfeatureEngineering = FALSE,
runPreprocessData = TRUE,
runModelDevelopment = TRUE,
runCovariateSummary = FALSE
)
)
Plot the learning curve object (Figure 4). Specify one of the
available metrics: AUROC
, AUPRC
,
sBrier
. Moreover, you can specify what metric to put on the
abscissa, number of observations
or number of
events
. We recommend the latter, because
events
are determinant of model performance and allow you
to better compare learning curves across different prediction problems
and databases.
plotLearningCurve(
learningCurve,
metric = "AUROC",
abscissa = "events",
plotTitle = "Learning Curve",
plotSubtitle = "AUROC performance"
)
Learning curve plot.
Parallel processing
The learning curve object can be created in parallel, which can
reduce computation time significantly. Whether to run the code in
parallel or not is specified using the parallel
input.
Currently this functionality is only available for LASSO logistic
regression and gradient boosting machines. Depending on the number of
parallel workers it may require a significant amount of memory. We
advise to use the parallelized learning curve function for parameter
search and exploratory data analysis.
When running in parrallel, R will find the number of available
processing cores automatically and register the required parallel
backend. Alternatively, you can provide the number of cores you wish to
use via the cores
input.
Demo
We have added a demo of the learningcurve:
# Show all demos in our package:
demo(package = "PatientLevelPrediction")
# Run the learning curve
demo("LearningCurveDemo", package = "PatientLevelPrediction")
Do note that running this demo can take a considerable amount of time
(15 min on Quad core running in parallel)!
Publication
A publication titled ‘How little data do we need for patient-level
prediction?’ uses the learning curve functionality in this package and
can be accessed as preprint in the arXiv archives at https://arxiv.org/abs/2008.07361.
LS0tCnRpdGxlOiAiQ3JlYXRpbmcgTGVhcm5pbmcgQ3VydmVzIgphdXRob3I6ICJMdWlzIEguIEpvaG4sIEplbm5hIE0uIFJlcHMsIFBldGVyIFIuIFJpam5iZWVrIgpkYXRlOiAnYHIgU3lzLkRhdGUoKWAnCmhlYWRlci1pbmNsdWRlczoKICAgIC0gXHVzZXBhY2thZ2V7ZmFuY3loZHJ9CiAgICAtIFxwYWdlc3R5bGV7ZmFuY3l9CiAgICAtIFxmYW5jeWhlYWR7fQogICAgLSBcZmFuY3loZWFkW0Nde0dlbmVyYXRpbmcgTGVhcm5pbmcgQ3VydmVzfQogICAgLSBcZmFuY3lmb290W0Nde1x0aGVwYWdlfQogICAgLSBccmVuZXdjb21tYW5ke1xoZWFkcnVsZXdpZHRofXswLjRwdH0KICAgIC0gXHJlbmV3Y29tbWFuZHtcZm9vdHJ1bGV3aWR0aH17MC40cHR9CiAgICAtIFxmYW5jeWZvb3RbQ117UGF0aWVudExldmVsUHJlZGljdGlvbiBQYWNrYWdlIFZlcnNpb24gYHIgICAgdXRpbHM6OnBhY2thZ2VWZXJzaW9uKCJQYXRpZW50TGV2ZWxQcmVkaWN0aW9uIilgfQpvdXRwdXQ6ICAgCiAgcGRmX2RvY3VtZW50OgogICAgaW5jbHVkZXM6CiAgICAgIGluX2hlYWRlcjogcHJlYW1ibGUudGV4CiAgICBudW1iZXJfc2VjdGlvbnM6IHllcwogICAgdG9jOiB5ZXMKICBodG1sX2RvY3VtZW50OgogICAgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRvYzogeWVzCiAgaHRtbF9ub3RlYm9vazogCiAgICB0b2M6IHllcwotLS0KCmBgYHs9aHRtbH0KPCEtLQolXFZpZ25ldHRlRW5naW5le2tuaXRyOjpybWFya2Rvd259CiVcVmlnbmV0dGVJbmRleEVudHJ5e0NyZWF0aW5nIExlYXJuaW5nIEN1cnZlc30KLS0+CmBgYApgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCmBgYHtyLCBldmFsPUZBTFNFLCBlY2hvID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFfQpsaWJyYXJ5KFBhdGllbnRMZXZlbFByZWRpY3Rpb24pCnZpZ25ldHRlRGF0YUZvbGRlciA8LSAiczovdGVtcC9wbHBWaWduZXR0ZSIKIyBMb2FkIGFsbCBuZWVkZWQgZGF0YSBpZiBpdCBleGlzdHMgb24gdGhpcyBjb21wdXRlcjoKaWYgKGZpbGUuZXhpc3RzKHZpZ25ldHRlRGF0YUZvbGRlcikpIHsKICBwbHBNb2RlbCA8LSBsb2FkUGxwTW9kZWwodmlnbmV0dGVEYXRhRm9sZGVyLCAibW9kZWwiKQogIGxyUmVzdWx0cyA8LSBsb2FkUGxwTW9kZWwoZmlsZS5wYXRoKHZpZ25ldHRlRGF0YUZvbGRlciwgInJlc3VsdHMiKSkKfQpgYGAKCiMgSW50cm9kdWN0aW9uCgpUaGlzIHZpZ25ldHRlIGRlc2NyaWJlcyBob3cgeW91IGNhbiB1c2UgdGhlIE9ic2VydmF0aW9uYWwgSGVhbHRoIERhdGEgU2NpZW5jZXMgYW5kIEluZm9ybWF0aWNzIChPSERTSSkgW2BQYXRpZW50TGV2ZWxQcmVkaWN0aW9uYF0oaHR0cDovL2dpdGh1Yi5jb20vT0hEU0kvUGF0aWVudExldmVsUHJlZGljdGlvbikgcGFja2FnZSB0byBjcmVhdGUgbGVhcm5pbmcgY3VydmVzLiBUaGlzIHZpZ25ldHRlIGFzc3VtZXMgeW91IGhhdmUgcmVhZCBhbmQgYXJlIGNvbWZvcnRhYmxlIHdpdGggYnVpbGRpbmcgcGF0aWVudCBsZXZlbCBwcmVkaWN0aW9uIG1vZGVscyBhcyBkZXNjcmliZWQgaW4gdGhlIGB2aWduZXR0ZSgnQnVpbGRpbmdQcmVkaWN0aXZlTW9kZWxzJylgLgoKUHJlZGljdGlvbiBtb2RlbHMgd2lsbCBzaG93IG92ZXJseS1vcHRpbWlzdGljIHBlcmZvcm1hbmNlIHdoZW4gcHJlZGljdGluZyBvbiB0aGUgc2FtZSBkYXRhIGFzIHVzZWQgZm9yIHRyYWluaW5nLiBUaGVyZWZvcmUsIGJlc3QtcHJhY3RpY2UgaXMgdG8gcGFydGl0aW9uIG91ciBkYXRhIGludG8gYSB0cmFpbmluZyBzZXQgYW5kIHRlc3Rpbmcgc2V0LiBXZSB0aGVuIHRyYWluIG91ciBwcmVkaWN0aW9uIG1vZGVsIG9uIHRoZSB0cmFpbmluZyBzZXQgcG9ydGlvbiBhbmQgYXNzZXMgaXRzIGFiaWxpdHkgdG8gZ2VuZXJhbGl6ZSB0byB1bnNlZW4gZGF0YSBieSBtZWFzdXJpbmcgaXRzIHBlcmZvcm1hbmNlIG9uIHRoZSB0ZXN0aW5nIHNldC4KCkxlYXJuaW5nIGN1cnZlcyBhc3Nlc3MgdGhlIGVmZmVjdCBvZiB0cmFpbmluZyBzZXQgc2l6ZSBvbiBtb2RlbCBwZXJmb3JtYW5jZSBieSB0cmFpbmluZyBhIHNlcXVlbmNlIG9mIHByZWRpY3Rpb24gbW9kZWxzIG9uIHN1Y2Nlc3NpdmVseSBsYXJnZXIgc3Vic2V0cyBvZiB0aGUgdHJhaW5pbmcgc2V0LiBBIGxlYXJuaW5nIGN1cnZlIHBsb3QgY2FuIGFsc28gaGVscCBpbiBkaWFnbm9zaW5nIGEgYmlhcyBvciB2YXJpYW5jZSBwcm9ibGVtIGFzIGV4cGxhaW5lZCBiZWxvdy4KCiFbTGVhcm5pbmcgY3VydmUgZXhhbXBsZS5dKGxlYXJuaW5nQ3VydmUucG5nKQoKRmlndXJlIDEsIHNob3dzIGFuIGV4YW1wbGUgb2YgbGVhcm5pbmcgY3VydmUgcGxvdCBpbiB3aGljaCB0aGUgdmVydGljYWwgYXhpcyByZXByZXNlbnRzIHRoZSBtb2RlbCBwZXJmb3JtYW5jZSBhbmQgdGhlIGhvcml6b250YWwgYXhpcyB0aGUgdHJhaW5pbmcgc2V0IHNpemUuIElmIHRyYWluaW5nIHNldCBzaXplIGlzIHNtYWxsLCB0aGUgcGVyZm9ybWFuY2Ugb24gdGhlIHRyYWluaW5nIHNldCBpcyBoaWdoLCBiZWNhdXNlIGEgbW9kZWwgY2FuIG9mdGVuIGJlIGZpdHRlZCB3ZWxsIHRvIGEgbGltaXRlZCBudW1iZXIgb2YgdHJhaW5pbmcgZXhhbXBsZXMuIEF0IHRoZSBzYW1lIHRpbWUsIHRoZSBwZXJmb3JtYW5jZSBvbiB0aGUgdGVzdGluZyBzZXQgd2lsbCBiZSBwb29yLCBiZWNhdXNlIHRoZSBtb2RlbCB0cmFpbmVkIG9uIHN1Y2ggYSBsaW1pdGVkIG51bWJlciBvZiB0cmFpbmluZyBleGFtcGxlcyB3aWxsIG5vdCBnZW5lcmFsaXplIHdlbGwgdG8gdW5zZWVuIGRhdGEgaW4gdGhlIHRlc3Rpbmcgc2V0LiBBcyB0aGUgdHJhaW5pbmcgc2V0IHNpemUgaW5jcmVhc2VzLCB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVsIG9uIHRoZSB0cmFpbmluZyBzZXQgd2lsbCBkZWNyZWFzZS4gSXQgYmVjb21lcyBtb3JlIGRpZmZpY3VsdCBmb3IgdGhlIG1vZGVsIHRvIGZpbmQgYSBnb29kIGZpdCB0aHJvdWdoIGFsbCB0aGUgdHJhaW5pbmcgZXhhbXBsZXMuIEFsc28sIHRoZSBtb2RlbCB3aWxsIGJlIHRyYWluZWQgb24gYSBtb3JlIHJlcHJlc2VudGF0aXZlIHBvcnRpb24gb2YgdHJhaW5pbmcgZXhhbXBsZXMsIG1ha2luZyBpdCBnZW5lcmFsaXplIGJldHRlciB0byB1bnNlZW4gZGF0YS4gVGhpcyBjYW4gYmUgb2JzZXJ2ZWQgYnkgdGhlIGluY3JlYXNpbiB0ZXN0aW5nIHNldCBwZXJmb3JtYW5jZS4KClRoZSBsZWFybmluZyBjdXJ2ZSBjYW4gaGVscCB1cyBpbiBkaWFnbm9zaW5nIGJpYXMgYW5kIHZhcmlhbmNlIHByb2JsZW1zIHdpdGggb3VyIGNsYXNzaWZpZXIgd2hpY2ggd2lsbCBwcm92aWRlIGd1aWRhbmNlIG9uIGhvdyB0byBmdXJ0aGVyIGltcHJvdmUgb3VyIG1vZGVsLiBXZSBjYW4gb2JzZXJ2ZSBoaWdoIHZhcmlhbmNlIChvdmVyZml0dGluZykgaW4gYSBwcmVkaWN0aW9uIG1vZGVsIGlmIGl0IHBlcmZvcm1zIHdlbGwgb24gdGhlIHRyYWluaW5nIHNldCwgYnV0IHBvb3JseSBvbiB0aGUgdGVzdGluZyBzZXQgKEZpZ3VyZSAyKS4gQWRkaW5nIGFkZGl0aW9uYWwgZGF0YSBpcyBhIGNvbW1vbiBhcHByb2FjaCB0byBjb3VudGVyYWN0IGhpZ2ggdmFyaWFuY2UuIEZyb20gdGhlIGxlYXJuaW5nIGN1cnZlIGl0IGJlY29tZXMgYXBwYXJlbnQsIHRoYXQgYWRkaW5nIGFkZGl0aW9uYWwgZGF0YSBtYXkgaW1wcm92ZSBwZXJmb3JtYW5jZSBvbiB0aGUgdGVzdGluZyBzZXQgYSBsaXR0bGUgZnVydGhlciwgYXMgdGhlIGxlYXJuaW5nIGN1cnZlIGhhcyBub3QgeWV0IHBsYXRlYXVlZCBhbmQsIHRodXMsIHRoZSBtb2RlbCBpcyBub3Qgc2F0dXJhdGVkIHlldC4gVGhlcmVmb3JlLCBhZGRpbmcgbW9yZSBkYXRhIHdpbGwgZGVjcmVhc2UgdGhlIGdhcCBiZXR3ZWVuIHRyYWluaW5nIHNldCBhbmQgdGVzdGluZyBzZXQsIHdoaWNoIGlzIHRoZSBtYWluIGluZGljYXRvciBmb3IgYSBoaWdoIHZhcmlhbmNlIHByb2JsZW0uCgohW1ByZWRpY3Rpb24gbW9kZWwgc3VmZmVyaW5nIGZyb20gaGlnaCB2YXJpYW5jZS5dKGxlYXJuaW5nQ3VydmVWYXJpYW5jZS5wbmcpCgpGdXJ0aGVybW9yZSwgd2UgY2FuIG9ic2VydmUgaGlnaCBiaWFzICh1bmRlcmZpdHRpbmcpIGlmIGEgcHJlZGljdGlvbiBtb2RlbCBwZXJmb3JtcyBwb29ybHkgb24gdGhlIHRyYWluaW5nIHNldCBhcyB3ZWxsIGFzIG9uIHRoZSB0ZXN0aW5nIHNldCAoRmlndXJlIDMpLiBUaGUgbGVhcm5pbmcgY3VydmVzIG9mIHRyYWluaW5nIHNldCBhbmQgdGVzdGluZyBzZXQgaGF2ZSBmbGF0dGVuZWQgb24gYSBsb3cgcGVyZm9ybWFuY2Ugd2l0aCBvbmx5IGEgc21hbGwgZ2FwIGluIGJldHdlZW4gdGhlbS4gQWRkaW5nIGFkZGl0aW9uYWwgZGF0YSB3aWxsIGluIHRoaXMgY2FzZSBoYXZlIGxpdHRsZSB0byBubyBpbXBhY3Qgb24gdGhlIG1vZGVsIHBlcmZvcm1hbmNlLiBDaG9vc2luZyBhbm90aGVyIHByZWRpY3Rpb24gYWxnb3JpdGhtIHRoYXQgY2FuIGZpbmQgbW9yZSBjb21wbGV4IChmb3IgZXhhbXBsZSBub24tbGluZWFyKSByZWxhdGlvbnNoaXBzIGluIHRoZSBkYXRhIG1heSBiZSBhbiBhbHRlcm5hdGl2ZSBhcHByb2FjaCB0byBjb25zaWRlciBpbiB0aGlzIGhpZ2ggYmlhcyBzaXR1YXRpb24uCgohW1ByZWRpY3Rpb24gbW9kZWwgc3VmZmVyaW5nIGZyb20gaGlnaCBiaWFzLl0obGVhcm5pbmdDdXJ2ZUJpYXMucG5nKQoKIyBDcmVhdGluZyB0aGUgbGVhcm5pbmcgY3VydmUKClVzZSB0aGUgW2BQYXRpZW50TGV2ZWxQcmVkaWN0aW9uYF0oaHR0cDovL2dpdGh1Yi5jb20vT0hEU0kvUGF0aWVudExldmVsUHJlZGljdGlvbikgcGFja2FnZSB0byBjcmVhdGUgYSBgcGxwRGF0YWAgb2JqZWN0IC4gQWx0ZXJuYXRpdmVseSwgeW91IGNhbiBtYWtlIHVzZSBvZiB0aGUgZGF0YSBzaW11bGF0b3IuIFRoZSBmb2xsb3dpbmcgY29kZSBzbmlwcGV0IGNyZWF0ZXMgZGF0YSBmb3IgMTIwMDAgcGF0aWVudHMuCgpgYGB7ciBldmFsPUZBTFNFfQpzZXQuc2VlZCgxMjM0KQpkYXRhKHBscERhdGFTaW11bGF0aW9uUHJvZmlsZSkKc2FtcGxlU2l6ZSA8LSAxMjAwMApwbHBEYXRhIDwtIHNpbXVsYXRlUGxwRGF0YSgKICBwbHBEYXRhU2ltdWxhdGlvblByb2ZpbGUsCiAgbiA9IHNhbXBsZVNpemUKKQpgYGAKClNwZWNpZnkgdGhlIHBvcHVsYXRpb24gc2V0dGluZ3MgKHRoaXMgZG9lcyBhZGRpdGlvbmFsIGV4Y2x1c2lvbnMgc3VjaCBhcyByZXF1aXJpbmcgbWluaW11bSBwcmlvciBvYnNlcnZhdGlvbiBvciBubyBwcmlvciBvdXRjb21lIGFzIHdlbGwgYXMgc3BlY2lmeWluZyB0aGUgdGltZS1hdC1yaXNrIHBlcmlvZCB0byBlbmFibGUgbGFiZWxzIHRvIGJlIGNyZWF0ZWQpOgoKYGBge3IgZXZhbD1GQUxTRX0KcG9wdWxhdGlvblNldHRpbmdzIDwtIGNyZWF0ZVN0dWR5UG9wdWxhdGlvblNldHRpbmdzKAogIGJpbmFyeSA9IFRSVUUsCiAgZmlyc3RFeHBvc3VyZU9ubHkgPSBGQUxTRSwKICB3YXNob3V0UGVyaW9kID0gMCwKICByZW1vdmVTdWJqZWN0c1dpdGhQcmlvck91dGNvbWUgPSBGQUxTRSwKICBwcmlvck91dGNvbWVMb29rYmFjayA9IDk5OTk5LAogIHJlcXVpcmVUaW1lQXRSaXNrID0gRkFMU0UsCiAgbWluVGltZUF0UmlzayA9IDAsCiAgcmlza1dpbmRvd1N0YXJ0ID0gMCwKICByaXNrV2luZG93RW5kID0gMzY1LAogIHZlcmJvc2l0eSA9ICJJTkZPIgopCmBgYAoKU3BlY2lmeSB0aGUgcHJlZGljdGlvbiBhbGdvcml0aG0gdG8gYmUgdXNlZC4KCmBgYHtyIGV2YWw9RkFMU0V9CiMgVXNlIExBU1NPIGxvZ2lzdGljIHJlZ3Jlc3Npb24KbW9kZWxTZXR0aW5ncyA8LSBzZXRMYXNzb0xvZ2lzdGljUmVncmVzc2lvbigpCmBgYAoKU3BlY2lmeSB0aGUgc3BsaXQgc2V0dGluZ3MgYW5kIGEgc2VxdWVuY2Ugb2YgdHJhaW5pbmcgc2V0IGZyYWN0aW9ucyAodGhlc2Ugb3ZlciByaWRlIHRoZSBzcGxpdFNldHRpbmcgdHJhaW5GcmFjdGlvbikuIEFsdGVybmF0aXZlbHksIGluc3RlYWQgb2YgYHRyYWluRnJhY3Rpb25zYCwgeW91IGNhbiBwcm92aWRlIGEgc2VxdWVuY2Ugb2YgdHJhaW5pbmcgZXZlbnRzIChgdHJhaW5FdmVudHNgKSBpbnN0ZWFkIG9mIHRoZSB0cmFpbmluZyBzZXQgZnJhY3Rpb25zLiBUaGlzIGlzIHJlY29tbWVuZGVkLCBiZWNhdXNlIG91ciByZXNlYXJjaCBoYXMgc2hvd24gdGhhdCBudW1iZXIgb2YgZXZlbnRzIGlzIHRoZSBpbXBvcnRhbnQgZGV0ZXJtaW5hbnQgb2YgbW9kZWwgcGVyZm9ybWFuY2UuIE1ha2Ugc3VyZSB0aGF0IHlvdXIgdHJhaW5pbmcgc2V0IGNvbnRhaW5zIHRoZSBudW1iZXIgb2YgZXZlbnRzIHNwZWNpZmllZC4KCmBgYHtyIGV2YWwgPSBGQUxTRX0Kc3BsaXRTZXR0aW5ncyA8LSBjcmVhdGVEZWZhdWx0U3BsaXRTZXR0aW5nKAogIHRlc3RGcmFjdGlvbiA9IDAuMiwKICB0eXBlID0gInN0cmF0aWZpZWQiLAogIHNwbGl0U2VlZCA9IDEwMDAKKQoKdHJhaW5GcmFjdGlvbnMgPC0gc2VxKDAuMSwgMC44LCAwLjEpICMgQ3JlYXRlIGVpZ2h0IHRyYWluaW5nIHNldCBmcmFjdGlvbnMKCiMgYWx0ZXJuYXRpdmVseSB1c2UgYSBzZXF1ZW5jZSBvZiB0cmFpbmluZyBldmVudHMgYnkgdW5jb21tZW50aW5nIHRoZSBsaW5lIGJlbG93LgojIHRyYWluRXZlbnRzIDwtIHNlcSgxMDAsIDUwMDAsIDEwMCkKYGBgCgpDcmVhdGUgdGhlIGxlYXJuaW5nIGN1cnZlIG9iamVjdC4KCmBgYHtyIGV2YWw9RkFMU0V9CmxlYXJuaW5nQ3VydmUgPC0gY3JlYXRlTGVhcm5pbmdDdXJ2ZSgKICBwbHBEYXRhID0gcGxwRGF0YSwKICBvdXRjb21lSWQgPSAyLAogIHBhcmFsbGVsID0gVFJVRSwKICBjb3JlcyA9IDQsCiAgbW9kZWxTZXR0aW5ncyA9IG1vZGVsU2V0dGluZ3MsCiAgc2F2ZURpcmVjdG9yeSA9IGdldHdkKCksCiAgYW5hbHlzaXNJZCA9ICJsZWFybmluZ0N1cnZlIiwKICBwb3B1bGF0aW9uU2V0dGluZ3MgPSBwb3B1bGF0aW9uU2V0dGluZ3MsCiAgc3BsaXRTZXR0aW5ncyA9IHNwbGl0U2V0dGluZ3MsCiAgdHJhaW5GcmFjdGlvbnMgPSB0cmFpbkZyYWN0aW9ucywKICB0cmFpbkV2ZW50cyA9IE5VTEwsCiAgcHJlcHJvY2Vzc1NldHRpbmdzID0gY3JlYXRlUHJlcHJvY2Vzc1NldHRpbmdzKAogICAgbWluRnJhY3Rpb24gPSAwLjAwMSwKICAgIG5vcm1hbGl6ZSA9IFRSVUUKICApLAogIGV4ZWN1dGVTZXR0aW5ncyA9IGNyZWF0ZUV4ZWN1dGVTZXR0aW5ncygKICAgIHJ1blNwbGl0RGF0YSA9IFRSVUUsCiAgICBydW5TYW1wbGVEYXRhID0gRkFMU0UsCiAgICBydW5mZWF0dXJlRW5naW5lZXJpbmcgPSBGQUxTRSwKICAgIHJ1blByZXByb2Nlc3NEYXRhID0gVFJVRSwKICAgIHJ1bk1vZGVsRGV2ZWxvcG1lbnQgPSBUUlVFLAogICAgcnVuQ292YXJpYXRlU3VtbWFyeSA9IEZBTFNFCiAgKQopCmBgYAoKUGxvdCB0aGUgbGVhcm5pbmcgY3VydmUgb2JqZWN0IChGaWd1cmUgNCkuIFNwZWNpZnkgb25lIG9mIHRoZSBhdmFpbGFibGUgbWV0cmljczogYEFVUk9DYCwgYEFVUFJDYCwgYHNCcmllcmAuIE1vcmVvdmVyLCB5b3UgY2FuIHNwZWNpZnkgd2hhdCBtZXRyaWMgdG8gcHV0IG9uIHRoZSBhYnNjaXNzYSwgbnVtYmVyIG9mIGBvYnNlcnZhdGlvbnNgIG9yIG51bWJlciBvZiBgZXZlbnRzYC4gV2UgcmVjb21tZW5kIHRoZSBsYXR0ZXIsIGJlY2F1c2UgYGV2ZW50c2AgYXJlIGRldGVybWluYW50IG9mIG1vZGVsIHBlcmZvcm1hbmNlIGFuZCBhbGxvdyB5b3UgdG8gYmV0dGVyIGNvbXBhcmUgbGVhcm5pbmcgY3VydmVzIGFjcm9zcyBkaWZmZXJlbnQgcHJlZGljdGlvbiBwcm9ibGVtcyBhbmQgZGF0YWJhc2VzLgoKYGBge3IgZXZhbD1GQUxTRX0KcGxvdExlYXJuaW5nQ3VydmUoCiAgbGVhcm5pbmdDdXJ2ZSwKICBtZXRyaWMgPSAiQVVST0MiLAogIGFic2Npc3NhID0gImV2ZW50cyIsCiAgcGxvdFRpdGxlID0gIkxlYXJuaW5nIEN1cnZlIiwKICBwbG90U3VidGl0bGUgPSAiQVVST0MgcGVyZm9ybWFuY2UiCikKYGBgCgohW0xlYXJuaW5nIGN1cnZlIHBsb3QuXShsZWFybmluZ0N1cnZlUGxvdC5wbmcpCgojIFBhcmFsbGVsIHByb2Nlc3NpbmcKClRoZSBsZWFybmluZyBjdXJ2ZSBvYmplY3QgY2FuIGJlIGNyZWF0ZWQgaW4gcGFyYWxsZWwsIHdoaWNoIGNhbiByZWR1Y2UgY29tcHV0YXRpb24gdGltZSBzaWduaWZpY2FudGx5LiBXaGV0aGVyIHRvIHJ1biB0aGUgY29kZSBpbiBwYXJhbGxlbCBvciBub3QgaXMgc3BlY2lmaWVkIHVzaW5nIHRoZSBgcGFyYWxsZWxgIGlucHV0LiBDdXJyZW50bHkgdGhpcyBmdW5jdGlvbmFsaXR5IGlzIG9ubHkgYXZhaWxhYmxlIGZvciBMQVNTTyBsb2dpc3RpYyByZWdyZXNzaW9uIGFuZCBncmFkaWVudCBib29zdGluZyBtYWNoaW5lcy4gRGVwZW5kaW5nIG9uIHRoZSBudW1iZXIgb2YgcGFyYWxsZWwgd29ya2VycyBpdCBtYXkgcmVxdWlyZSBhIHNpZ25pZmljYW50IGFtb3VudCBvZiBtZW1vcnkuIFdlIGFkdmlzZSB0byB1c2UgdGhlIHBhcmFsbGVsaXplZCBsZWFybmluZyBjdXJ2ZSBmdW5jdGlvbiBmb3IgcGFyYW1ldGVyIHNlYXJjaCBhbmQgZXhwbG9yYXRvcnkgZGF0YSBhbmFseXNpcy4KCldoZW4gcnVubmluZyBpbiBwYXJyYWxsZWwsIFIgd2lsbCBmaW5kIHRoZSBudW1iZXIgb2YgYXZhaWxhYmxlIHByb2Nlc3NpbmcgY29yZXMgYXV0b21hdGljYWxseSBhbmQgcmVnaXN0ZXIgdGhlIHJlcXVpcmVkIHBhcmFsbGVsIGJhY2tlbmQuIEFsdGVybmF0aXZlbHksIHlvdSBjYW4gcHJvdmlkZSB0aGUgbnVtYmVyIG9mIGNvcmVzIHlvdSB3aXNoIHRvIHVzZSB2aWEgdGhlIGBjb3Jlc2AgaW5wdXQuCgojIERlbW8KCldlIGhhdmUgYWRkZWQgYSBkZW1vIG9mIHRoZSBsZWFybmluZ2N1cnZlOgoKYGBge3IgZXZhbD1GQUxTRX0KIyBTaG93IGFsbCBkZW1vcyBpbiBvdXIgcGFja2FnZToKZGVtbyhwYWNrYWdlID0gIlBhdGllbnRMZXZlbFByZWRpY3Rpb24iKQoKIyBSdW4gdGhlIGxlYXJuaW5nIGN1cnZlCmRlbW8oIkxlYXJuaW5nQ3VydmVEZW1vIiwgcGFja2FnZSA9ICJQYXRpZW50TGV2ZWxQcmVkaWN0aW9uIikKYGBgCgpEbyBub3RlIHRoYXQgcnVubmluZyB0aGlzIGRlbW8gY2FuIHRha2UgYSBjb25zaWRlcmFibGUgYW1vdW50IG9mIHRpbWUgKDE1IG1pbiBvbiBRdWFkIGNvcmUgcnVubmluZyBpbiBwYXJhbGxlbCkhCgojIFB1YmxpY2F0aW9uCgpBIHB1YmxpY2F0aW9uIHRpdGxlZCAnSG93IGxpdHRsZSBkYXRhIGRvIHdlIG5lZWQgZm9yIHBhdGllbnQtbGV2ZWwgcHJlZGljdGlvbj8nIHVzZXMgdGhlIGxlYXJuaW5nIGN1cnZlIGZ1bmN0aW9uYWxpdHkgaW4gdGhpcyBwYWNrYWdlIGFuZCBjYW4gYmUgYWNjZXNzZWQgYXMgcHJlcHJpbnQgaW4gdGhlIGFyWGl2IGFyY2hpdmVzIGF0IDxodHRwczovL2FyeGl2Lm9yZy9hYnMvMjAwOC4wNzM2MT4uCgojIEFja25vd2xlZGdtZW50cwoKQ29uc2lkZXJhYmxlIHdvcmsgaGFzIGJlZW4gZGVkaWNhdGVkIHRvIHByb3ZpZGUgdGhlIGBQYXRpZW50TGV2ZWxQcmVkaWN0aW9uYCBwYWNrYWdlLgoKYGBge3IgdGlkeT1UUlVFLGV2YWw9VFJVRX0KY2l0YXRpb24oIlBhdGllbnRMZXZlbFByZWRpY3Rpb24iKQpgYGAKCioqUGxlYXNlIHJlZmVyZW5jZSB0aGlzIHBhcGVyIGlmIHlvdSB1c2UgdGhlIFBMUCBQYWNrYWdlIGluIHlvdXIgd29yazoqKgoKW1JlcHMgSk0sIFNjaHVlbWllIE1KLCBTdWNoYXJkIE1BLCBSeWFuIFBCLCBSaWpuYmVlayBQUi4gRGVzaWduIGFuZCBpbXBsZW1lbnRhdGlvbiBvZiBhIHN0YW5kYXJkaXplZCBmcmFtZXdvcmsgdG8gZ2VuZXJhdGUgYW5kIGV2YWx1YXRlIHBhdGllbnQtbGV2ZWwgcHJlZGljdGlvbiBtb2RlbHMgdXNpbmcgb2JzZXJ2YXRpb25hbCBoZWFsdGhjYXJlIGRhdGEuIEogQW0gTWVkIEluZm9ybSBBc3NvYy4gMjAxODsyNSg4KTo5NjktOTc1Ll0oaHR0cDovL2R4LmRvaS5vcmcvMTAuMTA5My9qYW1pYS9vY3kwMzIpCg==