Analysis of Protein Dynamics: xgbAnalysis Tutorial

Introduction

The xgbAnalysis package is a tool to find suitable reaction coordinates of biomolecular systems, e.g. proteins, using the XGBoost algorithm that can be found on github. Given a trajectory from a molecular dynamics simulation and suitable corresponding states, it evaluates, which of the input coordinates describe the system best, resulting in a low dimensional reaction coordinate of directly interpretable original coordinates. To obtain states for a given trajectorie the prodyna package can be used.

Installation

Install the xgbAnalysis package, using the R-devtools package

if ( ! ("devtools" %in% installed.packages())) {
  install.packages("devtools")
}
devtools::install_github("moldyn/xgbAnalysis")
library(xgbAnalysis)

Available test data

To this end internal data of HP-35 with every 100th frame for a quick testrun:

Path: /u3/rc_classification/testrun/

Including test files:

  • trajectory from MD simulation converted to dihedral angles villin_360K_every100.dih

  • states obtained from maxgap shifted density based clustering macrostates_every100

Parameter

You can find an introduction to boosted decision trees here.

While XGBoost has many parameter to define, how the decision trees are build, the following are the most important and are usually used improve the learining process.

  • eta learning rate (default=0.3): defines how fast each training round will improve the missclassification done by the model so far. A low value will need more training rounds to get a good prediction accuracy, while a high value will lead to fast overfitting.

  • max_depth maximum tree depth (default=6): defines the maximum depth of the single decision trees, e.g. the maximum number of decision nodes, until a prediction value is assigned.

  • nrounds number of training rounds: defines how many rounds the model is trained. Usually, more training rounds result in a better prediction accuracy, but too many training rounds will lead to overfitting, when the prediction error on the training set diverges from the prediction error on the test set.

  • nthread number of CPU cores to use (default=0): nthread=0 uses all cores

When training a model to precisely predict states for unseen data, a good parameter choice is crucial. For the feature analysis however, the parameter choice is not that important, as the XGBoost algorithm is strong against overfitting and comes with a good default choice of training parameter. Here, it is most important, that the build model is “complex” enough with enough training rounds (nrounds) of trees that are not too small (max_depth), so that enough features (coordinates) are used in the evaluation process for split candidates in the split nodes. Usually the default parameter suit that requirement.

Analysis

Set Up Working Directory and Import Data

In a first step the trajectory and states will be separated into a training set trainsplit = 0.7 => 70% and a test set.

getwd()
import.data(output_dir = "./data", 
            coords     = "villin_360K_every100.dih", 
            states     = "macrostates_every100",
            labels     = "dihedrals", 
            trainsplit = 0.7)

This will create the ouput directory, here data, containing the following files

  • import.data.parameter with the parameter used in the import function, as some future functions need to reload the original trajectory.

  • train.parameter with the default training parameter for the XGBoost algorithm and the number of classes (states) that are needed for the training process. Parameters can be changed using the set.parameter function.

  • train.index as the training set is choosen randomly from the data, this file contains the indices of the training set to ensure that the same set can be taken again.

  • test.index with the indices of the test set.

  • feature.names with the names (labels) of the coordinates of the trajectory file.

  • train.xgb.Dmatrix the training set in the XGBoost dense matrix format.

  • test.xgb.Dmatrix the test set in the XGBoost dense matrix format.

Train a Model

To simply train a model that predicts the corresponding states of the trajectory with the default parameter and 2 training rounds, use

train.model(data_dir   = "./data",
            output_dir = "./model",
            nrounds    = 2
            )

As the data set is small and the number of training rounds is small, the accuracy will not be good. For better accuracy, try using nrounds = 20 training rounds for which the computation time will be 10x larger.

This will produce the following files in the output directory

  • xgb.model the trained model that now can be used to predict states.

  • xgb.dump.model containing the tree structure, that has been trained.

  • importance with the feature importance from the training process.

Feature Selection

TODO description

feature.selection(output_dir = "./featureSelection",
                  data       = "./data",
                  decreasing = F,
                  eta        = 0.3,
                  max_depth  = 6,
                  nrounds    = 20,
                  nthread    = 0,
                  savemode   = T)

This will produce the following files in the ouput directory

  • feature.selection containing the list of features that are dismissed, the overall accuracy and the accuracy for every state for every iteration round.

  • parameter containing the parameter values used for feature selection.

If savemode = TRUE the following files will be produced every iteration round

  • selectround<n>.importance containing the feature importance of the iteration round.

  • selectround<n>.model the trained model.

  • selectround<n>.prediction with the state prediction made by the model of this iteration.

The index n gives the number of coordinates (features) that are dismissed. In round 0, the model is trained with all coordinates and in round 1 the most/least important coordinate from round 0 has been removed from the data set. Given 66 dihedral angles for HP-35, at selectround 65 the model is trained using just the last remaining coordinate.

The prediction accuracy can be plotted in dependency of the number of dismissed coordinates (features).

plt.feature.selection(dir      = "./featureSelection",
                      saveplot = T)

If saveplot = TRUE, the folder ./featureSelection/plot/, containing the plot of the feature selection will be created. If FALSE the plot will be returned.

Dismissing the coordinates decreasingly, the most important coordinate will be dismissed every round.

feature.selection(output_dir = "./featureSelection_decreasing",
                  data       = "./data",
                  decreasing = T,
                  eta        = 0.3,
                  max_depth  = 6,
                  nrounds    = 20,
                  nthread    = 0,
                  savemode   = T)

Plot the feature selection decreasing

plt.feature.selection(dir      = "./featureSelection_decreasing",
                      saveplot = F)

If you want to continue a previous not finished feature selection or you want to dismiss certain features from the beginning, you can give a vector (fdismissed) with the names of the features (coordinates), that will be dismissed before the first iteration. To define how many features (coordinates) should be dismissed at all, use ndismiss

feature.selection(output_dir = "./featureSelection_decreasing",
                  data       = "./data",
                  decreasing = T,
                  ndismiss   = 8,
                  fdismissed = c("Phi2", "Psi13", "Psi4"),
                  eta        = 0.3,
                  max_depth  = 6,
                  nrounds    = 20,
                  nthread    = 0,
                  savemode   = T)

This will remove the coordinates “Phi2”, “Psi13” and “Psi4” from the data set before starting to remove the most important coordinate for 5 iterations.

Single Class Importance

Given a trained model, the feature importance for every state can be obtained. Here, for every single state, the individual importance of all coordinates for that state is calculated and normalized, so that the importance of coordinates for one state add up to 1. The results are shown in a plot that can either be obtained setting a minimum importance, a coordinate has to contribute for at least one state or the number of coordinates that should be shown. Hence, setting the minimum importance to colmin = 0 results in a plot showing all coordinates and setting the number of coordinates to nfeatures = 6 results in a plot showing the 6 most important coordinates, weighted by their population.

plt.single.class.importance(pre    = "./singleClassImportance/sci",
                            model  = "./model/xgb.model",
                            names  = "./data/feature.names",
                            colmin = 0)

This function will produce the file sci_colmin0.png in the directory singleClassImportance with sci as the prefix.

plt.single.class.importance(pre       = "./singleClassImportance/sci",
                            model     = "./model/xgb.model",
                            names     = "./data/feature.names",
                            nfeatures = 6)

This function will produce the file sci_n6.png in the directory singleClassImportance with sci as the prefix.

Multiple plots can be obtained as well

plt.single.class.importance(pre       = "./singleClassImportance/sci",
                            model     = "./model/xgb.model",
                            names     = "./data/feature.names",
                            colmin    = c(0,0.2,0.5),
                            nfeatures = c(4,5,6))

Parameter Testing

To test the influence of different parameter on the prediction accuracy of the trained model, the parameter test fuction can be used. The defpar parameter defines, which training parameter should be used as base. defpar = 'default' uses the XGBoost default parameter. defpar = NA will take the parameter set in the train.parameter file within the data directory. The training parameter can also be set manually with defpar = list(eta=0.3, gamma=0, max_depth=6, min_child_weight=1, subsample=1, colsample_bytree=1). The parameters to test should be given as data frame in the following format: data.frame(parameter = c(start, stop, stepsize, adjust nrounds(T/F))). As some parameter influence the computation time, more training rounds are neccesary to get comparable results of the prediction accuracy. Therefore, T/F parameter defines wether to adjust the training rounds by nrounds*(max_value/current_value).

parameter.test(data       = "./data",
               output_dir = "./parameterTest",
               nthread    = 0,
               nrounds    = 10,
               defpar     = 'default',
               testpar    = data.frame(eta = c(0.1, 0.5, 0.1, T),
                                       max_depth = c(3, 10, 1, T))
               )

This will produce two plots in the output directory, showing the the prediction accuracy in dependence of the learning rate eta and the maximum tree depth max_depth.

LS0tCnRpdGxlOiAieGdiQW5hbHlzaXMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KICAKIyBBbmFseXNpcyBvZiBQcm90ZWluIER5bmFtaWNzOiB4Z2JBbmFseXNpcyBUdXRvcmlhbAoKIyMgSW50cm9kdWN0aW9uCgpUaGUgeGdiQW5hbHlzaXMgcGFja2FnZSBpcyBhIHRvb2wgdG8gZmluZCBzdWl0YWJsZSByZWFjdGlvbiBjb29yZGluYXRlcyBvZiBiaW9tb2xlY3VsYXIgc3lzdGVtcywgZS5nLiBwcm90ZWlucywgdXNpbmcgdGhlIFtYR0Jvb3N0XShodHRwczovL2FyeGl2Lm9yZy9wZGYvMTYwMy4wMjc1NC5wZGYpIGFsZ29yaXRobSB0aGF0IGNhbiBiZSBmb3VuZCBvbiBbZ2l0aHViXShodHRwczovL2dpdGh1Yi5jb20vZG1sYy94Z2Jvb3N0KS4gR2l2ZW4gYSB0cmFqZWN0b3J5IGZyb20gYSBtb2xlY3VsYXIgZHluYW1pY3Mgc2ltdWxhdGlvbiBhbmQgc3VpdGFibGUgY29ycmVzcG9uZGluZyBzdGF0ZXMsIGl0IGV2YWx1YXRlcywgd2hpY2ggb2YgdGhlIGlucHV0IGNvb3JkaW5hdGVzIGRlc2NyaWJlIHRoZSBzeXN0ZW0gYmVzdCwgcmVzdWx0aW5nIGluIGEgbG93IGRpbWVuc2lvbmFsIHJlYWN0aW9uIGNvb3JkaW5hdGUgb2YgZGlyZWN0bHkgaW50ZXJwcmV0YWJsZSBvcmlnaW5hbCBjb29yZGluYXRlcy4gVG8gb2J0YWluIHN0YXRlcyBmb3IgYSBnaXZlbiB0cmFqZWN0b3JpZSB0aGUgW3Byb2R5bmFdKGh0dHBzOi8vZ2l0aHViLmNvbS9sZXR0aXMvcHJvZHluYS9ibG9iL21hc3Rlci92aWduZXR0ZXMvcHJvZHluYVR1dG9yaWFsLlJtZCkgcGFja2FnZSBjYW4gYmUgdXNlZC4KCiMjIEluc3RhbGxhdGlvbgoKSW5zdGFsbCB0aGUgW3hnYkFuYWx5c2lzXShodHRwczovL2dpdGh1Yi5jb20vc2JicmFuZHQveGdiQW5hbHlzaXMpIHBhY2thZ2UsIHVzaW5nIHRoZSBSLWRldnRvb2xzIHBhY2thZ2UKICAKYGBge3J9CmlmICggISAoImRldnRvb2xzIiAlaW4lIGluc3RhbGxlZC5wYWNrYWdlcygpKSkgewogIGluc3RhbGwucGFja2FnZXMoImRldnRvb2xzIikKfQpkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoInNiYnJhbmR0L3hnYkFuYWx5c2lzIikKbGlicmFyeSh4Z2JBbmFseXNpcykKYGBgCgojIyBBdmFpbGFibGUgdGVzdCBkYXRhCgpUbyB0aGlzIGVuZCBpbnRlcm5hbCBkYXRhIG9mIEhQLTM1IHdpdGggZXZlcnkgMTAwdGggZnJhbWUgZm9yIGEgcXVpY2sgdGVzdHJ1bjoKClBhdGg6IGAvdTMvcmNfY2xhc3NpZmljYXRpb24vdGVzdHJ1bi9gCgpJbmNsdWRpbmcgdGVzdCBmaWxlczoKCiAgKiB0cmFqZWN0b3J5IGZyb20gTUQgc2ltdWxhdGlvbiBjb252ZXJ0ZWQgdG8gZGloZWRyYWwgYW5nbGVzIGB2aWxsaW5fMzYwS19ldmVyeTEwMC5kaWhgCiAgCiAgKiBzdGF0ZXMgb2J0YWluZWQgZnJvbSBtYXhnYXAgc2hpZnRlZCBkZW5zaXR5IGJhc2VkIGNsdXN0ZXJpbmcgYG1hY3Jvc3RhdGVzX2V2ZXJ5MTAwYAoKIyMgUGFyYW1ldGVyCgpZb3UgY2FuIGZpbmQgYW4gaW50cm9kdWN0aW9uIHRvIGJvb3N0ZWQgZGVjaXNpb24gdHJlZXMgW2hlcmVdKGh0dHA6Ly94Z2Jvb3N0LnJlYWR0aGVkb2NzLmlvL2VuL2xhdGVzdC9tb2RlbC5odG1sKS4KCldoaWxlIFhHQm9vc3QgaGFzIG1hbnkgcGFyYW1ldGVyIHRvIGRlZmluZSwgaG93IHRoZSBkZWNpc2lvbiB0cmVlcyBhcmUgYnVpbGQsIHRoZSBmb2xsb3dpbmcgYXJlIHRoZSBtb3N0IGltcG9ydGFudCBhbmQgYXJlIHVzdWFsbHkgdXNlZCBpbXByb3ZlIHRoZSBsZWFyaW5pbmcgcHJvY2Vzcy4KCiAgKiBgZXRhYCBsZWFybmluZyByYXRlIChgZGVmYXVsdD0wLjNgKTogZGVmaW5lcyBob3cgZmFzdCBlYWNoIHRyYWluaW5nIHJvdW5kIHdpbGwgaW1wcm92ZSB0aGUgbWlzc2NsYXNzaWZpY2F0aW9uIGRvbmUgYnkgdGhlIG1vZGVsIHNvIGZhci4gQSBsb3cgdmFsdWUgd2lsbCBuZWVkIG1vcmUgdHJhaW5pbmcgcm91bmRzIHRvIGdldCBhIGdvb2QgcHJlZGljdGlvbiBhY2N1cmFjeSwgd2hpbGUgYSBoaWdoIHZhbHVlIHdpbGwgbGVhZCB0byBmYXN0IG92ZXJmaXR0aW5nLgogIAogICogYG1heF9kZXB0aGAgbWF4aW11bSB0cmVlIGRlcHRoIChgZGVmYXVsdD02YCk6IGRlZmluZXMgdGhlIG1heGltdW0gZGVwdGggb2YgdGhlIHNpbmdsZSBkZWNpc2lvbiB0cmVlcywgZS5nLiB0aGUgbWF4aW11bSBudW1iZXIgb2YgZGVjaXNpb24gbm9kZXMsIHVudGlsIGEgcHJlZGljdGlvbiB2YWx1ZSBpcyBhc3NpZ25lZC4KICAKICAqIGBucm91bmRzYCBudW1iZXIgb2YgdHJhaW5pbmcgcm91bmRzOiBkZWZpbmVzIGhvdyBtYW55IHJvdW5kcyB0aGUgbW9kZWwgaXMgdHJhaW5lZC4gVXN1YWxseSwgbW9yZSB0cmFpbmluZyByb3VuZHMgcmVzdWx0IGluIGEgYmV0dGVyIHByZWRpY3Rpb24gYWNjdXJhY3ksIGJ1dCB0b28gbWFueSB0cmFpbmluZyByb3VuZHMgd2lsbCBsZWFkIHRvIG92ZXJmaXR0aW5nLCB3aGVuIHRoZSBwcmVkaWN0aW9uIGVycm9yIG9uIHRoZSB0cmFpbmluZyBzZXQgZGl2ZXJnZXMgZnJvbSB0aGUgcHJlZGljdGlvbiBlcnJvciBvbiB0aGUgdGVzdCBzZXQuCiAgCiAgKiBgbnRocmVhZGAgbnVtYmVyIG9mIENQVSBjb3JlcyB0byB1c2UgKGBkZWZhdWx0PTBgKTogYG50aHJlYWQ9MGAgdXNlcyBhbGwgY29yZXMKICAKV2hlbiB0cmFpbmluZyBhIG1vZGVsIHRvIHByZWNpc2VseSBwcmVkaWN0IHN0YXRlcyBmb3IgdW5zZWVuIGRhdGEsIGEgZ29vZCBwYXJhbWV0ZXIgY2hvaWNlIGlzIGNydWNpYWwuIEZvciB0aGUgZmVhdHVyZSBhbmFseXNpcyBob3dldmVyLCB0aGUgcGFyYW1ldGVyIGNob2ljZSBpcyBub3QgdGhhdCBpbXBvcnRhbnQsIGFzIHRoZSBYR0Jvb3N0IGFsZ29yaXRobSBpcyBzdHJvbmcgYWdhaW5zdCBvdmVyZml0dGluZyBhbmQgY29tZXMgd2l0aCBhIGdvb2QgZGVmYXVsdCBjaG9pY2Ugb2YgdHJhaW5pbmcgcGFyYW1ldGVyLiBIZXJlLCBpdCBpcyBtb3N0IGltcG9ydGFudCwgdGhhdCB0aGUgYnVpbGQgbW9kZWwgaXMgImNvbXBsZXgiIGVub3VnaCB3aXRoIGVub3VnaCB0cmFpbmluZyByb3VuZHMgKGBucm91bmRzYCkgb2YgdHJlZXMgdGhhdCBhcmUgbm90IHRvbyBzbWFsbCAoYG1heF9kZXB0aGApLCBzbyB0aGF0IGVub3VnaCBmZWF0dXJlcyAoY29vcmRpbmF0ZXMpIGFyZSB1c2VkIGluIHRoZSBldmFsdWF0aW9uIHByb2Nlc3MgZm9yIHNwbGl0IGNhbmRpZGF0ZXMgaW4gdGhlIHNwbGl0IG5vZGVzLiBVc3VhbGx5IHRoZSBkZWZhdWx0IHBhcmFtZXRlciBzdWl0IHRoYXQgcmVxdWlyZW1lbnQuCgojIyBBbmFseXNpcwojIyMgU2V0IFVwIFdvcmtpbmcgRGlyZWN0b3J5IGFuZCBJbXBvcnQgRGF0YQoKSW4gYSBmaXJzdCBzdGVwIHRoZSB0cmFqZWN0b3J5IGFuZCBzdGF0ZXMgd2lsbCBiZSBzZXBhcmF0ZWQgaW50byBhIHRyYWluaW5nIHNldCBgdHJhaW5zcGxpdCA9IDAuNyA9PiA3MCVgIGFuZCBhIHRlc3Qgc2V0LgoKYGBge3J9CmdldHdkKCkKaW1wb3J0LmRhdGEob3V0cHV0X2RpciA9ICIuL2RhdGEiLCAKICAgICAgICAgICAgY29vcmRzICAgICA9ICJ2aWxsaW5fMzYwS19ldmVyeTEwMC5kaWgiLCAKICAgICAgICAgICAgc3RhdGVzICAgICA9ICJtYWNyb3N0YXRlc19ldmVyeTEwMCIsCiAgICAgICAgICAgIGxhYmVscyAgICAgPSAiZGloZWRyYWxzIiwgCiAgICAgICAgICAgIHRyYWluc3BsaXQgPSAwLjcpCmBgYAoKVGhpcyB3aWxsIGNyZWF0ZSB0aGUgb3VwdXQgZGlyZWN0b3J5LCBoZXJlIGBkYXRhYCwgY29udGFpbmluZyB0aGUgZm9sbG93aW5nIGZpbGVzCgogICogYGltcG9ydC5kYXRhLnBhcmFtZXRlcmAgd2l0aCB0aGUgcGFyYW1ldGVyIHVzZWQgaW4gdGhlIGltcG9ydCBmdW5jdGlvbiwgYXMgc29tZSBmdXR1cmUgZnVuY3Rpb25zIG5lZWQgdG8gcmVsb2FkIHRoZSBvcmlnaW5hbCB0cmFqZWN0b3J5LgogIAogICogYHRyYWluLnBhcmFtZXRlcmAgd2l0aCB0aGUgZGVmYXVsdCB0cmFpbmluZyBwYXJhbWV0ZXIgZm9yIHRoZSBYR0Jvb3N0IGFsZ29yaXRobSBhbmQgdGhlIG51bWJlciBvZiBjbGFzc2VzIChzdGF0ZXMpIHRoYXQgYXJlIG5lZWRlZCBmb3IgdGhlIHRyYWluaW5nIHByb2Nlc3MuIFBhcmFtZXRlcnMgY2FuIGJlIGNoYW5nZWQgdXNpbmcgdGhlIGBzZXQucGFyYW1ldGVyYCBmdW5jdGlvbi4KICAKICAqIGB0cmFpbi5pbmRleGAgYXMgdGhlIHRyYWluaW5nIHNldCBpcyBjaG9vc2VuIHJhbmRvbWx5IGZyb20gdGhlIGRhdGEsIHRoaXMgZmlsZSBjb250YWlucyB0aGUgaW5kaWNlcyBvZiB0aGUgdHJhaW5pbmcgc2V0IHRvIGVuc3VyZSB0aGF0IHRoZSBzYW1lIHNldCBjYW4gYmUgdGFrZW4gYWdhaW4uCiAgCiAgKiBgdGVzdC5pbmRleGAgd2l0aCB0aGUgaW5kaWNlcyBvZiB0aGUgdGVzdCBzZXQuCiAgCiAgKiBgZmVhdHVyZS5uYW1lc2Agd2l0aCB0aGUgbmFtZXMgKGBsYWJlbHNgKSBvZiB0aGUgY29vcmRpbmF0ZXMgb2YgdGhlIHRyYWplY3RvcnkgZmlsZS4KICAKICA8IS0tICogYGFsbC54Z2IuRG1hdHJpeGAgdGhlIHdob2xlIGRhdGEgc2V0IGluIHRoZSBYR0Jvb3N0IGRlbnNlIG1hdHJpeCBmb3JtYXQgaW5jbHVkaW5nIHRoZSB0cmFqZWN0b3J5IGFuZCB0aGUgc3RhdGVzLiAtLT4KICAKICAqIGB0cmFpbi54Z2IuRG1hdHJpeGAgdGhlIHRyYWluaW5nIHNldCBpbiB0aGUgWEdCb29zdCBkZW5zZSBtYXRyaXggZm9ybWF0LgogIAogICogYHRlc3QueGdiLkRtYXRyaXhgIHRoZSB0ZXN0IHNldCBpbiB0aGUgWEdCb29zdCBkZW5zZSBtYXRyaXggZm9ybWF0LgoKIyMjIFRyYWluIGEgTW9kZWwKClRvIHNpbXBseSB0cmFpbiBhIG1vZGVsIHRoYXQgcHJlZGljdHMgdGhlIGNvcnJlc3BvbmRpbmcgc3RhdGVzIG9mIHRoZSB0cmFqZWN0b3J5IHdpdGggdGhlIGRlZmF1bHQgcGFyYW1ldGVyIGFuZCAyIHRyYWluaW5nIHJvdW5kcywgdXNlCgpgYGB7cn0KdHJhaW4ubW9kZWwoZGF0YV9kaXIgICA9ICIuL2RhdGEiLAogICAgICAgICAgICBvdXRwdXRfZGlyID0gIi4vbW9kZWwiLAogICAgICAgICAgICBucm91bmRzICAgID0gMgogICAgICAgICAgICApCmBgYAoKQXMgdGhlIGRhdGEgc2V0IGlzIHNtYWxsIGFuZCB0aGUgbnVtYmVyIG9mIHRyYWluaW5nIHJvdW5kcyBpcyBzbWFsbCwgdGhlIGFjY3VyYWN5IHdpbGwgbm90IGJlIGdvb2QuIEZvciBiZXR0ZXIgYWNjdXJhY3ksIHRyeSB1c2luZyBgbnJvdW5kcyA9IDIwYCB0cmFpbmluZyByb3VuZHMgZm9yIHdoaWNoIHRoZSBjb21wdXRhdGlvbiB0aW1lIHdpbGwgYmUgMTB4IGxhcmdlci4KClRoaXMgd2lsbCBwcm9kdWNlIHRoZSBmb2xsb3dpbmcgZmlsZXMgaW4gdGhlIG91dHB1dCBkaXJlY3RvcnkKCiAgKiBgeGdiLm1vZGVsYCB0aGUgdHJhaW5lZCBtb2RlbCB0aGF0IG5vdyBjYW4gYmUgdXNlZCB0byBwcmVkaWN0IHN0YXRlcy4KICAKICAqIGB4Z2IuZHVtcC5tb2RlbGAgY29udGFpbmluZyB0aGUgdHJlZSBzdHJ1Y3R1cmUsIHRoYXQgaGFzIGJlZW4gdHJhaW5lZC4KICAKICAqIGBpbXBvcnRhbmNlYCB3aXRoIHRoZSBmZWF0dXJlIGltcG9ydGFuY2UgZnJvbSB0aGUgdHJhaW5pbmcgcHJvY2Vzcy4KCiMjIyBGZWF0dXJlIFNlbGVjdGlvbgoKVE9ETyBkZXNjcmlwdGlvbgoKYGBge3J9CmZlYXR1cmUuc2VsZWN0aW9uKG91dHB1dF9kaXIgPSAiLi9mZWF0dXJlU2VsZWN0aW9uIiwKICAgICAgICAgICAgICAgICAgZGF0YSAgICAgICA9ICIuL2RhdGEiLAogICAgICAgICAgICAgICAgICBkZWNyZWFzaW5nID0gRiwKICAgICAgICAgICAgICAgICAgZXRhICAgICAgICA9IDAuMywKICAgICAgICAgICAgICAgICAgbWF4X2RlcHRoICA9IDYsCiAgICAgICAgICAgICAgICAgIG5yb3VuZHMgICAgPSAyMCwKICAgICAgICAgICAgICAgICAgbnRocmVhZCAgICA9IDAsCiAgICAgICAgICAgICAgICAgIHNhdmVtb2RlICAgPSBUKQpgYGAKClRoaXMgd2lsbCBwcm9kdWNlIHRoZSBmb2xsb3dpbmcgZmlsZXMgaW4gdGhlIG91cHV0IGRpcmVjdG9yeQoKICAqIGBmZWF0dXJlLnNlbGVjdGlvbmAgY29udGFpbmluZyB0aGUgbGlzdCBvZiBmZWF0dXJlcyB0aGF0IGFyZSBkaXNtaXNzZWQsIHRoZSBvdmVyYWxsIGFjY3VyYWN5IGFuZCB0aGUgYWNjdXJhY3kgZm9yIGV2ZXJ5IHN0YXRlIGZvciBldmVyeSBpdGVyYXRpb24gcm91bmQuCiAgCiAgKiBgcGFyYW1ldGVyYCBjb250YWluaW5nIHRoZSBwYXJhbWV0ZXIgdmFsdWVzIHVzZWQgZm9yIGZlYXR1cmUgc2VsZWN0aW9uLgoKSWYgYHNhdmVtb2RlID0gVFJVRWAgdGhlIGZvbGxvd2luZyBmaWxlcyB3aWxsIGJlIHByb2R1Y2VkIGV2ZXJ5IGl0ZXJhdGlvbiByb3VuZAogIAogICogYHNlbGVjdHJvdW5kPG4+LmltcG9ydGFuY2VgIGNvbnRhaW5pbmcgdGhlIGZlYXR1cmUgaW1wb3J0YW5jZSBvZiB0aGUgaXRlcmF0aW9uIHJvdW5kLgogIAogICogYHNlbGVjdHJvdW5kPG4+Lm1vZGVsYCB0aGUgdHJhaW5lZCBtb2RlbC4KICAKICAqIGBzZWxlY3Ryb3VuZDxuPi5wcmVkaWN0aW9uYCB3aXRoIHRoZSBzdGF0ZSBwcmVkaWN0aW9uIG1hZGUgYnkgdGhlIG1vZGVsIG9mIHRoaXMgaXRlcmF0aW9uLgogIApUaGUgaW5kZXggYG5gIGdpdmVzIHRoZSBudW1iZXIgb2YgY29vcmRpbmF0ZXMgKGZlYXR1cmVzKSB0aGF0IGFyZSBkaXNtaXNzZWQuIEluIHJvdW5kIDAsIHRoZSBtb2RlbCBpcyB0cmFpbmVkIHdpdGggYWxsIGNvb3JkaW5hdGVzIGFuZCBpbiByb3VuZCAxIHRoZSBtb3N0L2xlYXN0IGltcG9ydGFudCBjb29yZGluYXRlIGZyb20gcm91bmQgMCBoYXMgYmVlbiByZW1vdmVkIGZyb20gdGhlIGRhdGEgc2V0LiBHaXZlbiA2NiBkaWhlZHJhbCBhbmdsZXMgZm9yIEhQLTM1LCBhdCBzZWxlY3Ryb3VuZCA2NSB0aGUgbW9kZWwgaXMgdHJhaW5lZCB1c2luZyBqdXN0IHRoZSBsYXN0IHJlbWFpbmluZyBjb29yZGluYXRlLgoKVGhlIHByZWRpY3Rpb24gYWNjdXJhY3kgY2FuIGJlIHBsb3R0ZWQgaW4gZGVwZW5kZW5jeSBvZiB0aGUgbnVtYmVyIG9mIGRpc21pc3NlZCBjb29yZGluYXRlcyAoZmVhdHVyZXMpLgoKYGBge3J9CnBsdC5mZWF0dXJlLnNlbGVjdGlvbihkaXIgICAgICA9ICIuL2ZlYXR1cmVTZWxlY3Rpb24iLAogICAgICAgICAgICAgICAgICAgICAgc2F2ZXBsb3QgPSBUKQpgYGAKCklmIGBzYXZlcGxvdCA9IFRSVUVgLCAgdGhlIGZvbGRlciBgLi9mZWF0dXJlU2VsZWN0aW9uL3Bsb3QvYCwgY29udGFpbmluZyB0aGUgcGxvdCBvZiB0aGUgZmVhdHVyZSBzZWxlY3Rpb24gd2lsbCBiZSBjcmVhdGVkLiBJZiBgRkFMU0VgIHRoZSBwbG90IHdpbGwgYmUgcmV0dXJuZWQuCgpEaXNtaXNzaW5nIHRoZSBjb29yZGluYXRlcyBkZWNyZWFzaW5nbHksIHRoZSBtb3N0IGltcG9ydGFudCBjb29yZGluYXRlIHdpbGwgYmUgZGlzbWlzc2VkIGV2ZXJ5IHJvdW5kLgoKYGBge3J9CmZlYXR1cmUuc2VsZWN0aW9uKG91dHB1dF9kaXIgPSAiLi9mZWF0dXJlU2VsZWN0aW9uX2RlY3JlYXNpbmciLAogICAgICAgICAgICAgICAgICBkYXRhICAgICAgID0gIi4vZGF0YSIsCiAgICAgICAgICAgICAgICAgIGRlY3JlYXNpbmcgPSBULAogICAgICAgICAgICAgICAgICBldGEgICAgICAgID0gMC4zLAogICAgICAgICAgICAgICAgICBtYXhfZGVwdGggID0gNiwKICAgICAgICAgICAgICAgICAgbnJvdW5kcyAgICA9IDIwLAogICAgICAgICAgICAgICAgICBudGhyZWFkICAgID0gMCwKICAgICAgICAgICAgICAgICAgc2F2ZW1vZGUgICA9IFQpCmBgYAoKUGxvdCB0aGUgZmVhdHVyZSBzZWxlY3Rpb24gZGVjcmVhc2luZwoKYGBge3J9CnBsdC5mZWF0dXJlLnNlbGVjdGlvbihkaXIgICAgICA9ICIuL2ZlYXR1cmVTZWxlY3Rpb25fZGVjcmVhc2luZyIsCiAgICAgICAgICAgICAgICAgICAgICBzYXZlcGxvdCA9IEYpCmBgYAoKSWYgeW91IHdhbnQgdG8gY29udGludWUgYSBwcmV2aW91cyBub3QgZmluaXNoZWQgZmVhdHVyZSBzZWxlY3Rpb24gb3IgeW91IHdhbnQgdG8gZGlzbWlzcyBjZXJ0YWluIGZlYXR1cmVzIGZyb20gdGhlIGJlZ2lubmluZywgeW91IGNhbiBnaXZlIGEgdmVjdG9yIChgZmRpc21pc3NlZGApIHdpdGggdGhlIG5hbWVzIG9mIHRoZSBmZWF0dXJlcyAoY29vcmRpbmF0ZXMpLCB0aGF0IHdpbGwgYmUgZGlzbWlzc2VkIGJlZm9yZSB0aGUgZmlyc3QgaXRlcmF0aW9uLiBUbyBkZWZpbmUgaG93IG1hbnkgZmVhdHVyZXMgKGNvb3JkaW5hdGVzKSBzaG91bGQgYmUgZGlzbWlzc2VkIGF0IGFsbCwgdXNlIGBuZGlzbWlzc2AKCmBgYHtyfQpmZWF0dXJlLnNlbGVjdGlvbihvdXRwdXRfZGlyID0gIi4vZmVhdHVyZVNlbGVjdGlvbl9kZWNyZWFzaW5nIiwKICAgICAgICAgICAgICAgICAgZGF0YSAgICAgICA9ICIuL2RhdGEiLAogICAgICAgICAgICAgICAgICBkZWNyZWFzaW5nID0gVCwKICAgICAgICAgICAgICAgICAgbmRpc21pc3MgICA9IDgsCiAgICAgICAgICAgICAgICAgIGZkaXNtaXNzZWQgPSBjKCJQaGkyIiwgIlBzaTEzIiwgIlBzaTQiKSwKICAgICAgICAgICAgICAgICAgZXRhICAgICAgICA9IDAuMywKICAgICAgICAgICAgICAgICAgbWF4X2RlcHRoICA9IDYsCiAgICAgICAgICAgICAgICAgIG5yb3VuZHMgICAgPSAyMCwKICAgICAgICAgICAgICAgICAgbnRocmVhZCAgICA9IDAsCiAgICAgICAgICAgICAgICAgIHNhdmVtb2RlICAgPSBUKQpgYGAKClRoaXMgd2lsbCByZW1vdmUgdGhlIGNvb3JkaW5hdGVzICJQaGkyIiwgIlBzaTEzIiBhbmQgIlBzaTQiIGZyb20gdGhlIGRhdGEgc2V0IGJlZm9yZSBzdGFydGluZyB0byByZW1vdmUgdGhlIG1vc3QgaW1wb3J0YW50IGNvb3JkaW5hdGUgZm9yIDUgaXRlcmF0aW9ucy4KCiMjIyBTaW5nbGUgQ2xhc3MgSW1wb3J0YW5jZQoKR2l2ZW4gYSB0cmFpbmVkIG1vZGVsLCB0aGUgZmVhdHVyZSBpbXBvcnRhbmNlIGZvciBldmVyeSBzdGF0ZSBjYW4gYmUgb2J0YWluZWQuIEhlcmUsIGZvciBldmVyeSBzaW5nbGUgc3RhdGUsIHRoZSBpbmRpdmlkdWFsIGltcG9ydGFuY2Ugb2YgYWxsIGNvb3JkaW5hdGVzIGZvciB0aGF0IHN0YXRlIGlzIGNhbGN1bGF0ZWQgYW5kIG5vcm1hbGl6ZWQsIHNvIHRoYXQgdGhlIGltcG9ydGFuY2Ugb2YgY29vcmRpbmF0ZXMgZm9yIG9uZSBzdGF0ZSBhZGQgdXAgdG8gMS4gVGhlIHJlc3VsdHMgYXJlIHNob3duIGluIGEgcGxvdCB0aGF0IGNhbiBlaXRoZXIgYmUgb2J0YWluZWQgc2V0dGluZyBhIG1pbmltdW0gaW1wb3J0YW5jZSwgYSBjb29yZGluYXRlIGhhcyB0byBjb250cmlidXRlIGZvciBhdCBsZWFzdCBvbmUgc3RhdGUgb3IgdGhlIG51bWJlciBvZiBjb29yZGluYXRlcyB0aGF0IHNob3VsZCBiZSBzaG93bi4gSGVuY2UsIHNldHRpbmcgdGhlIG1pbmltdW0gaW1wb3J0YW5jZSB0byBgY29sbWluID0gMGAgcmVzdWx0cyBpbiBhIHBsb3Qgc2hvd2luZyBhbGwgY29vcmRpbmF0ZXMgYW5kIHNldHRpbmcgdGhlIG51bWJlciBvZiBjb29yZGluYXRlcyB0byBgbmZlYXR1cmVzID0gNmAgcmVzdWx0cyBpbiBhIHBsb3Qgc2hvd2luZyB0aGUgNiBtb3N0IGltcG9ydGFudCBjb29yZGluYXRlcywgd2VpZ2h0ZWQgYnkgdGhlaXIgcG9wdWxhdGlvbi4KCmBgYHtyfQpwbHQuc2luZ2xlLmNsYXNzLmltcG9ydGFuY2UocHJlICAgID0gIi4vc2luZ2xlQ2xhc3NJbXBvcnRhbmNlL3NjaSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlbCAgPSAiLi9tb2RlbC94Z2IubW9kZWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZXMgID0gIi4vZGF0YS9mZWF0dXJlLm5hbWVzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG1pbiA9IDApCmBgYAoKVGhpcyBmdW5jdGlvbiB3aWxsIHByb2R1Y2UgdGhlIGZpbGUgYHNjaV9jb2xtaW4wLnBuZ2AgaW4gdGhlIGRpcmVjdG9yeSBgc2luZ2xlQ2xhc3NJbXBvcnRhbmNlYCB3aXRoIGBzY2lgIGFzIHRoZSBwcmVmaXguCgpgYGB7cn0KcGx0LnNpbmdsZS5jbGFzcy5pbXBvcnRhbmNlKHByZSAgICAgICA9ICIuL3NpbmdsZUNsYXNzSW1wb3J0YW5jZS9zY2kiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwgICAgID0gIi4vbW9kZWwveGdiLm1vZGVsIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5hbWVzICAgICA9ICIuL2RhdGEvZmVhdHVyZS5uYW1lcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZmVhdHVyZXMgPSA2KQpgYGAKClRoaXMgZnVuY3Rpb24gd2lsbCBwcm9kdWNlIHRoZSBmaWxlIGBzY2lfbjYucG5nYCBpbiB0aGUgZGlyZWN0b3J5IGBzaW5nbGVDbGFzc0ltcG9ydGFuY2VgIHdpdGggYHNjaWAgYXMgdGhlIHByZWZpeC4KCgpNdWx0aXBsZSBwbG90cyBjYW4gYmUgb2J0YWluZWQgYXMgd2VsbAoKYGBge3J9CnBsdC5zaW5nbGUuY2xhc3MuaW1wb3J0YW5jZShwcmUgICAgICAgPSAiLi9zaW5nbGVDbGFzc0ltcG9ydGFuY2Uvc2NpIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsICAgICA9ICIuL21vZGVsL3hnYi5tb2RlbCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lcyAgICAgPSAiLi9kYXRhL2ZlYXR1cmUubmFtZXMiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sbWluICAgID0gYygwLDAuMiwwLjUpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbmZlYXR1cmVzID0gYyg0LDUsNikpCmBgYAoKIyMjUGFyYW1ldGVyIFRlc3RpbmcKClRvIHRlc3QgdGhlIGluZmx1ZW5jZSBvZiBkaWZmZXJlbnQgcGFyYW1ldGVyIG9uIHRoZSBwcmVkaWN0aW9uIGFjY3VyYWN5IG9mIHRoZSB0cmFpbmVkIG1vZGVsLCB0aGUgcGFyYW1ldGVyIHRlc3QgZnVjdGlvbiBjYW4gYmUgdXNlZC4gVGhlIGBkZWZwYXJgIHBhcmFtZXRlciBkZWZpbmVzLCB3aGljaCB0cmFpbmluZyBwYXJhbWV0ZXIgc2hvdWxkIGJlIHVzZWQgYXMgYmFzZS4gYGRlZnBhciA9ICdkZWZhdWx0J2AgdXNlcyB0aGUgWEdCb29zdCBkZWZhdWx0IHBhcmFtZXRlci4gYGRlZnBhciA9IE5BYCB3aWxsIHRha2UgdGhlIHBhcmFtZXRlciBzZXQgaW4gdGhlIGB0cmFpbi5wYXJhbWV0ZXJgIGZpbGUgd2l0aGluIHRoZSBkYXRhIGRpcmVjdG9yeS4gVGhlIHRyYWluaW5nIHBhcmFtZXRlciBjYW4gYWxzbyBiZSBzZXQgbWFudWFsbHkgd2l0aCBgZGVmcGFyID0gbGlzdChldGE9MC4zLCBnYW1tYT0wLCBtYXhfZGVwdGg9NiwgbWluX2NoaWxkX3dlaWdodD0xLCBzdWJzYW1wbGU9MSwgY29sc2FtcGxlX2J5dHJlZT0xKWAuClRoZSBwYXJhbWV0ZXJzIHRvIHRlc3Qgc2hvdWxkIGJlIGdpdmVuIGFzIGRhdGEgZnJhbWUgaW4gdGhlIGZvbGxvd2luZyBmb3JtYXQ6CmBkYXRhLmZyYW1lKHBhcmFtZXRlciA9IGMoc3RhcnQsIHN0b3AsIHN0ZXBzaXplLCBhZGp1c3QgbnJvdW5kcyhUL0YpKSlgLiBBcyBzb21lIHBhcmFtZXRlciBpbmZsdWVuY2UgdGhlIGNvbXB1dGF0aW9uIHRpbWUsIG1vcmUgdHJhaW5pbmcgcm91bmRzIGFyZSBuZWNjZXNhcnkgdG8gZ2V0IGNvbXBhcmFibGUgcmVzdWx0cyBvZiB0aGUgcHJlZGljdGlvbiBhY2N1cmFjeS4gVGhlcmVmb3JlLCBgVC9GYCBwYXJhbWV0ZXIgZGVmaW5lcyB3ZXRoZXIgdG8gYWRqdXN0IHRoZSB0cmFpbmluZyByb3VuZHMgYnkgYG5yb3VuZHMqKG1heF92YWx1ZS9jdXJyZW50X3ZhbHVlKWAuIAoKYGBge3J9CnBhcmFtZXRlci50ZXN0KGRhdGEgICAgICAgPSAiLi9kYXRhIiwKICAgICAgICAgICAgICAgb3V0cHV0X2RpciA9ICIuL3BhcmFtZXRlclRlc3QiLAogICAgICAgICAgICAgICBudGhyZWFkICAgID0gMCwKICAgICAgICAgICAgICAgbnJvdW5kcyAgICA9IDEwLAogICAgICAgICAgICAgICBkZWZwYXIgICAgID0gJ2RlZmF1bHQnLAogICAgICAgICAgICAgICB0ZXN0cGFyICAgID0gZGF0YS5mcmFtZShldGEgPSBjKDAuMSwgMC41LCAwLjEsIFQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXhfZGVwdGggPSBjKDMsIDEwLCAxLCBUKSkKICAgICAgICAgICAgICAgKQpgYGAKClRoaXMgd2lsbCBwcm9kdWNlIHR3byBwbG90cyBpbiB0aGUgb3V0cHV0IGRpcmVjdG9yeSwgc2hvd2luZyB0aGUgdGhlIHByZWRpY3Rpb24gYWNjdXJhY3kgaW4gZGVwZW5kZW5jZSBvZiB0aGUgbGVhcm5pbmcgcmF0ZSBgZXRhYCBhbmQgdGhlIG1heGltdW0gdHJlZSBkZXB0aCBgbWF4X2RlcHRoYC4K