1. Install and/or load the required packages
# install.packages("BiocManager")
# BiocManager::install(c("phyloseq", "vegan", "microbiome", "Maaslin2"))
# BiocManager::install(c("microbiomeMarker"))
# BiocManager::install(c("ggplot2", "tidyverse", "dplyr", "data.table"))
# install.packages("https://cran.r-project.org/src/contrib/Archive/randomForest/randomForest_4.6-14.tar.gz", repos = NULL, type = "source")
library(mixOmics)
library(igraph)
library(randomForest)
library(caret)
library(pROC)
library(pheatmap)
2. Set the working directory
setwd("C://Users//Malina//Desktop//RF_BC_subtypes_predict/")
3. Load the example dataset
The example dataset is from the package mixOmics. Rownames are
patients and column are the proteins (sometimes with post-translational
modification like “4E-BP1_pT37”). The data is normalized. We will call
the variable mRNA, but will know the current analyses are done for
normalized protein counts. We also know what type of cancer the patients
were diagnosed with, which is contained in the Y variable and will add
it to the dataframe as an additional column, so we will have our
outcome.
data(breast.TCGA)
## Explore the dataset
mRNA = breast.TCGA$data.train$protein
dim(mRNA)
[1] 150 142
14-3-3_epsilon 4E-BP1 4E-BP1_pS65 4E-BP1_pT37 4E-BP1_pT70 53BP1
A0FJ 0.04913078 0.4474862 -0.07432175 -0.3811629 0.02606943 0.91783419
A13E -0.07998211 0.6052184 0.28891184 1.1602454 -0.01096717 0.05910121
A0G0 -0.03284989 0.8946097 0.89127784 1.4001889 0.26588012 0.51704453
A0SX -0.20532949 -0.1413229 -0.01841096 -0.4442243 -0.05126772 -0.31372867
A143 0.06019021 0.1317690 0.66532539 0.3517633 0.06980286 0.33091238
A0DA 0.03076171 0.0329968 0.05236064 0.7892360 0.01704555 -0.22027100
A-Raf_pS299 ACC1 ACC_pS79 AMPK_alpha AMPK_pT172 ANLN
A0FJ 0.02274147 -0.08626782 -0.4166244 0.28527039 0.07659533 0.17231110
A13E -0.45985298 -0.59269183 -0.0622684 -0.27523360 -0.14897676 0.22210598
A0G0 -0.19182192 0.41117190 0.8258286 0.06774184 0.71122916 0.12199399
A0SX -0.07482347 -0.85148060 -0.6634104 0.02956373 0.42335804 1.05494810
A143 -0.02435747 0.76975143 0.8734787 -0.21653182 -0.47414555 0.01378422
A0DA 0.41861665 -0.71430870 -0.2175268 -0.06306506 -0.04603971 0.06025690
AR ARID1A ASNS ATM Akt Akt_pS473 Akt_pT308
A0FJ -1.3076057 0.5050945 0.8114629 -0.49594473 -0.001377255 -0.5215168 0.38153872
A13E -1.6204760 0.3395816 1.1810158 -0.27553339 -0.755547887 0.3024077 0.07748138
A0G0 -1.0778944 0.2271807 1.9509224 0.77085780 -0.067397666 1.6739827 0.83743199
A0SX -1.2670547 0.3552977 0.6074238 0.78132869 0.056726701 -0.3884839 0.31431844
A143 -0.6013274 0.5441251 0.5387629 0.01385431 0.238114357 1.6713476 2.36407695
A0DA -1.2080385 -0.1109448 0.3119495 0.07174832 0.193712038 0.5128177 0.19289383
Annexin_I
A0FJ -0.09290929
A13E 0.19474984
A0G0 1.25299238
A0SX 0.57527418
A143 -1.55700359
A0DA 0.49101519
## Get Breast Cancer subtype column
Y <- breast.TCGA$data.train$subtype
summary(Y)
Basal Her2 LumA
45 30 75
## Merge the protein level data with the outcome column
gene_expr_data= data.frame(cbind((mRNA),data.frame(Y)))
head(gene_expr_data[,1:20])
gene_expr_data$Y = as.factor(gene_expr_data$Y)
dim(gene_expr_data)
[1] 150 143
## Assing the dataframe with only 40 features to gene_expr_data
# gene_expr_data = mRNA_imp
#
# dim(gene_expr_data)
#
# head(gene_expr_data[,1:20])
#
# levels(gene_expr_data$Y)
4. Split data into training and testing sets
set.seed(123) # for reproducibility
train_index <- sample(1:nrow(gene_expr_data), 0.7 * nrow(gene_expr_data)) # 70% for training
train_data <- gene_expr_data[train_index, ]
test_data <- gene_expr_data[-train_index, ]
5. Train Random Forest model
## include 10x cross-validation
ctrl <- caret::trainControl(method = "cv", number = 10)
rf_model <- randomForest(Y ~ ., data = train_data, trControl = ctrl, ntree = 500)
6. Feature Selection
importance <- importance(rf_model)
importance = data.frame(importance)
head(importance)
importance$genes = rownames(importance)
top_features <- importance%>%dplyr::arrange(desc(MeanDecreaseGini))%>%head(40) # Select top number features
head(top_features, n=40)
7. Evaluate Model Performance and Print results
# Model evaluation
predictions1 <- predict(rf_model, newdata = test_data)
# Compute confusion matrix
conf_matrix <- table(test_data$Y, predictions1)
# Calculate precision
precision <- conf_matrix[2, 2] / sum(conf_matrix[, 2])
# Calculate recall (sensitivity)
recall <- conf_matrix[2, 2] / sum(conf_matrix[2, ])
# Calculate specificity
specificity <- conf_matrix[1, 1] / sum(conf_matrix[1, ])
# Calculate F1 score
f1_score <- 2 * (precision * recall) / (precision + recall)
# Calculate accuracy
accuracy <- sum(diag(conf_matrix)) / sum(conf_matrix)
# Print the metrics
print(conf_matrix)
predictions1
Basal Her2 LumA
Basal 11 0 0
Her2 1 7 1
LumA 0 1 24
print(paste("Accuracy:", accuracy))
[1] "Accuracy: 0.933333333333333"
print(paste("Precision:", precision))
[1] "Precision: 0.875"
print(paste("Recall (Sensitivity):", recall))
[1] "Recall (Sensitivity): 0.777777777777778"
print(paste("Specificity:", specificity))
[1] "Specificity: 1"
print(paste("F1 Score:", f1_score))
[1] "F1 Score: 0.823529411764706"
Precision: Precision measures the accuracy of positive predictions
made by the model. It is calculated as the number of true positive
predictions divided by the total number of positive predictions made by
the model. A high precision indicates that the model is good at
correctly identifying positive cases without incorrectly classifying
negative cases as positive.
Recall (Sensitivity): Recall, also known as sensitivity or true
positive rate, measures the ability of the model to correctly identify
all positive instances. It is calculated as the number of true positive
predictions divided by the total number of actual positive instances in
the data. High recall indicates that the model is good at capturing
positive instances without missing many of them.
Specificity: Specificity measures the ability of the model to
correctly identify all negative instances. It is calculated as the
number of true negative predictions divided by the total number of
actual negative instances in the data. High specificity indicates that
the model is good at correctly classifying negative instances without
misclassifying positive instances as negative.
F1 Score: The F1 score is the harmonic mean of precision and recall.
It provides a single metric that balances both precision and recall. It
is calculated as 2 times the product of precision and recall divided by
the sum of precision and recall. The F1 score ranges from 0 to 1, where
a higher value indicates better overall performance in terms of both
precision and recall.
Accuracy: Accuracy measures the overall correctness of the model’s
predictions across all classes. It is calculated as the number of
correct predictions (both true positives and true negatives) divided by
the total number of predictions made by the model. High accuracy
indicates that the model is making correct predictions for the majority
of instances.
8. Plot top important features averaged across the subtypes of
cancer
cluster_genes = top_features$genes
length(cluster_genes)
[1] 40
# only select the proteins that are most important features for the model
mRNA_imp = gene_expr_data[,c(cluster_genes,"Y")]
# Calculate average expression of each gene for each cancer type
average_expr <- aggregate(. ~ Y, data = mRNA_imp, FUN = mean)
# Transpose the dataframe for better visualization
average_expr <- data.frame(t(average_expr))
colnames(average_expr) = c("Basal", "Her2", "LumA")
# Remove the 'Y' row (as it's not a gene)
average_expr <- average_expr[!rownames(average_expr) %in% "Y", ]
average_expr = data.frame(average_expr)
# Apply as.numeric to each column of the dataframe
average_expr[] <- lapply(average_expr, as.numeric)
head(average_expr)
pheatmap(t(average_expr), fontsize_col = 5,fontsize_row = 10,
height = 18, width = 16, cluster_cols = TRUE, scale = "column")

From the pheatmap it is obvious there is great separation between the
three cancer subtypes. Her2 pos cancer has high levels of Her2 proteins
expressed. LumA has high protein levels of hormone receptors such as AR,
ER, PR and also GATA3 protein levels. The Basal subtype is represented
rather by proteins involved in the progression of cell cycle and those
far is the most difficult to treat.
9. Plot ROC and calculate AUC for each cancer subtype
# predict class and then attach test class
predictions <- as.data.frame(predict(rf_model, newdata = test_data, type = "prob"))
predictions$predict <- names(predictions)[1:3][apply(predictions[,1:3], 1, which.max)]
predictions$observed <- test_data$Y
head(predictions)
# ROC curve, Basal vs non Basal
roc.Basal <- roc(ifelse(predictions$observed=="Basal", "Basal", "non-Basal"), as.numeric(predictions$Basal))
Setting levels: control = Basal, case = non-Basal
Setting direction: controls > cases
plot(roc.Basal, col = "gray60")
# others
roc.Her2 <- roc(ifelse(predictions$observed=="Her2", "Her2", "non-Her2"), as.numeric(predictions$Basal))
Setting levels: control = Her2, case = non-Her2
Setting direction: controls > cases
roc.LumA <- roc(ifelse(predictions$observed=="LumA", "LumA", "non-LumA"), as.numeric(predictions$Basal))
Setting levels: control = LumA, case = non-LumA
Setting direction: controls < cases
lines(roc.Her2, col = "blue")
lines(roc.LumA, col = "red")

Area under the curve: 0.9893
Area under the curve: 0.6019
Area under the curve: 0.932
LS0tDQp0aXRsZTogIlByZWRpY3QgY2FuY2VyIHN1YnR5cGVzIGZyb20gcHJvdGVpbiBsZXZlbCBzaWduYXR1cmVzIHdpdGggcmFuZG9tIGZvcmVzdCBtb2RlbCAiDQphdXRob3I6ICJNYWxpbmEgRG95bm92YSINCmRhdGU6ICI1LzAyLzIwMjQiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgICB0aGVtZTogc2ltcGxleCAjIENoYW5nZSB0aGUgdGhlbWUgdG8gJ2RhcmtseScNCiAgICAgaGlnaGxpZ2h0OiB0YW5nbyAjIENoYW5nZSB0aGUgaGlnaGxpZ2h0IHN0eWxlDQogICAgIHRvYzogeWVzICMgSW5jbHVkZSB0YWJsZSBvZiBjb250ZW50DQogICAgIHRvY19mbG9hdDogZmFsc2UgIyBJZiB0byBiZSBmbG9hdGluZyBvciBub3QgDQogICAgDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KIyAxLiAgSW5zdGFsbCBhbmQvb3IgbG9hZCB0aGUgcmVxdWlyZWQgcGFja2FnZXMNCg0KYGBge3J9DQoNCiMgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQ0KIyBCaW9jTWFuYWdlcjo6aW5zdGFsbChjKCJwaHlsb3NlcSIsICJ2ZWdhbiIsICJtaWNyb2Jpb21lIiwgIk1hYXNsaW4yIikpDQojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKGMoIm1pY3JvYmlvbWVNYXJrZXIiKSkNCiMgQmlvY01hbmFnZXI6Omluc3RhbGwoYygiZ2dwbG90MiIsICJ0aWR5dmVyc2UiLCAiZHBseXIiLCAiZGF0YS50YWJsZSIpKQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9zcmMvY29udHJpYi9BcmNoaXZlL3JhbmRvbUZvcmVzdC9yYW5kb21Gb3Jlc3RfNC42LTE0LnRhci5neiIsIHJlcG9zID0gTlVMTCwgdHlwZSA9ICJzb3VyY2UiKQ0KDQpsaWJyYXJ5KG1peE9taWNzKQ0KbGlicmFyeShpZ3JhcGgpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHBST0MpDQpsaWJyYXJ5KHBoZWF0bWFwKQ0KDQpgYGANCg0KIyAyLiBTZXQgdGhlIHdvcmtpbmcgZGlyZWN0b3J5DQoNCmBgYHtyfQ0Kc2V0d2QoIkM6Ly9Vc2Vycy8vTWFsaW5hLy9EZXNrdG9wLy9SRl9CQ19zdWJ0eXBlc19wcmVkaWN0LyIpDQpgYGANCg0KIyAzLiBMb2FkIHRoZSBleGFtcGxlIGRhdGFzZXQNCg0KVGhlIGV4YW1wbGUgZGF0YXNldCBpcyBmcm9tIHRoZSBwYWNrYWdlIG1peE9taWNzLiBSb3duYW1lcyBhcmUgcGF0aWVudHMgYW5kIGNvbHVtbiBhcmUgdGhlIHByb3RlaW5zIChzb21ldGltZXMgd2l0aCBwb3N0LXRyYW5zbGF0aW9uYWwgbW9kaWZpY2F0aW9uIGxpa2UgIjRFLUJQMV9wVDM3IikuIFRoZSBkYXRhIGlzIG5vcm1hbGl6ZWQuIA0KV2Ugd2lsbCBjYWxsIHRoZSB2YXJpYWJsZSBtUk5BLCBidXQgd2lsbCBrbm93IHRoZSBjdXJyZW50IGFuYWx5c2VzIGFyZSBkb25lIGZvciBub3JtYWxpemVkIHByb3RlaW4gY291bnRzLg0KV2UgYWxzbyBrbm93IHdoYXQgdHlwZSBvZiBjYW5jZXIgdGhlIHBhdGllbnRzIHdlcmUgZGlhZ25vc2VkIHdpdGgsIHdoaWNoIGlzIGNvbnRhaW5lZCBpbiB0aGUgWSB2YXJpYWJsZSBhbmQgd2lsbCBhZGQgaXQgdG8gdGhlIGRhdGFmcmFtZSBhcyBhbiBhZGRpdGlvbmFsIGNvbHVtbiwgc28gd2Ugd2lsbCBoYXZlIG91ciBvdXRjb21lLiAgDQoNCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KZGF0YShicmVhc3QuVENHQSkNCg0KIyMgRXhwbG9yZSB0aGUgZGF0YXNldA0KbVJOQSA9IGJyZWFzdC5UQ0dBJGRhdGEudHJhaW4kcHJvdGVpbg0KDQpkaW0obVJOQSkNCg0KaGVhZChtUk5BWywxOjIwXSkNCg0KIyMgR2V0IEJyZWFzdCBDYW5jZXIgc3VidHlwZSBjb2x1bW4NCg0KWSA8LSBicmVhc3QuVENHQSRkYXRhLnRyYWluJHN1YnR5cGUNCnN1bW1hcnkoWSkNCg0KIyMgTWVyZ2UgdGhlIHByb3RlaW4gbGV2ZWwgZGF0YSB3aXRoIHRoZSBvdXRjb21lIGNvbHVtbg0KDQpnZW5lX2V4cHJfZGF0YT0gZGF0YS5mcmFtZShjYmluZCgobVJOQSksZGF0YS5mcmFtZShZKSkpDQoNCmhlYWQoZ2VuZV9leHByX2RhdGFbLDE6MjBdKQ0KZ2VuZV9leHByX2RhdGEkWSA9IGFzLmZhY3RvcihnZW5lX2V4cHJfZGF0YSRZKQ0KZGltKGdlbmVfZXhwcl9kYXRhKQ0KDQojIyBBc3NpbmcgdGhlIGRhdGFmcmFtZSB3aXRoIG9ubHkgNDAgZmVhdHVyZXMgdG8gZ2VuZV9leHByX2RhdGENCiMgZ2VuZV9leHByX2RhdGEgPSBtUk5BX2ltcA0KIyANCiMgZGltKGdlbmVfZXhwcl9kYXRhKQ0KIyANCiMgaGVhZChnZW5lX2V4cHJfZGF0YVssMToyMF0pDQojIA0KIyBsZXZlbHMoZ2VuZV9leHByX2RhdGEkWSkNCg0KYGBgDQoNCg0KIyA0LiBTcGxpdCBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cw0KDQpgYGB7ciwgZWNobyA9IFRSVUV9DQoNCnNldC5zZWVkKDEyMykgIyBmb3IgcmVwcm9kdWNpYmlsaXR5DQp0cmFpbl9pbmRleCA8LSBzYW1wbGUoMTpucm93KGdlbmVfZXhwcl9kYXRhKSwgMC43ICogbnJvdyhnZW5lX2V4cHJfZGF0YSkpICMgNzAlIGZvciB0cmFpbmluZw0KdHJhaW5fZGF0YSA8LSBnZW5lX2V4cHJfZGF0YVt0cmFpbl9pbmRleCwgXQ0KDQp0ZXN0X2RhdGEgPC0gZ2VuZV9leHByX2RhdGFbLXRyYWluX2luZGV4LCBdDQoNCmBgYA0KDQojIDUuIFRyYWluIFJhbmRvbSBGb3Jlc3QgbW9kZWwNCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KIyMgaW5jbHVkZSAxMHggY3Jvc3MtdmFsaWRhdGlvbg0KY3RybCA8LSBjYXJldDo6dHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQ0KDQpyZl9tb2RlbCA8LSByYW5kb21Gb3Jlc3QoWSB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhLCB0ckNvbnRyb2wgPSBjdHJsLCBudHJlZSA9IDUwMCkNCmBgYA0KDQojIDYuIEZlYXR1cmUgU2VsZWN0aW9uDQpgYGB7ciwgZWNobyA9IFRSVUV9DQppbXBvcnRhbmNlIDwtIGltcG9ydGFuY2UocmZfbW9kZWwpDQppbXBvcnRhbmNlID0gZGF0YS5mcmFtZShpbXBvcnRhbmNlKQ0KaGVhZChpbXBvcnRhbmNlKQ0KaW1wb3J0YW5jZSRnZW5lcyA9IHJvd25hbWVzKGltcG9ydGFuY2UpDQp0b3BfZmVhdHVyZXMgPC0gaW1wb3J0YW5jZSU+JWRwbHlyOjphcnJhbmdlKGRlc2MoTWVhbkRlY3JlYXNlR2luaSkpJT4laGVhZCg0MCkgIyBTZWxlY3QgdG9wIG51bWJlciBmZWF0dXJlcw0KaGVhZCh0b3BfZmVhdHVyZXMsIG49NDApDQoNCmBgYA0KDQojIDcuIEV2YWx1YXRlIE1vZGVsIFBlcmZvcm1hbmNlIGFuZCBQcmludCByZXN1bHRzDQoNCmBgYHtyLCBlY2hvID0gVFJVRX0NCg0KIyBNb2RlbCBldmFsdWF0aW9uDQoNCnByZWRpY3Rpb25zMSA8LSBwcmVkaWN0KHJmX21vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhKQ0KDQoNCiMgQ29tcHV0ZSBjb25mdXNpb24gbWF0cml4DQpjb25mX21hdHJpeCA8LSB0YWJsZSh0ZXN0X2RhdGEkWSwgcHJlZGljdGlvbnMxKQ0KDQojIENhbGN1bGF0ZSBwcmVjaXNpb24NCnByZWNpc2lvbiA8LSBjb25mX21hdHJpeFsyLCAyXSAvIHN1bShjb25mX21hdHJpeFssIDJdKQ0KDQojIENhbGN1bGF0ZSByZWNhbGwgKHNlbnNpdGl2aXR5KQ0KcmVjYWxsIDwtIGNvbmZfbWF0cml4WzIsIDJdIC8gc3VtKGNvbmZfbWF0cml4WzIsIF0pDQoNCiMgQ2FsY3VsYXRlIHNwZWNpZmljaXR5DQpzcGVjaWZpY2l0eSA8LSBjb25mX21hdHJpeFsxLCAxXSAvIHN1bShjb25mX21hdHJpeFsxLCBdKQ0KDQojIENhbGN1bGF0ZSBGMSBzY29yZQ0KZjFfc2NvcmUgPC0gMiAqIChwcmVjaXNpb24gKiByZWNhbGwpIC8gKHByZWNpc2lvbiArIHJlY2FsbCkNCg0KIyBDYWxjdWxhdGUgYWNjdXJhY3kNCg0KYWNjdXJhY3kgPC0gc3VtKGRpYWcoY29uZl9tYXRyaXgpKSAvIHN1bShjb25mX21hdHJpeCkNCg0KIyBQcmludCB0aGUgbWV0cmljcw0KDQpwcmludChjb25mX21hdHJpeCkNCnByaW50KHBhc3RlKCJBY2N1cmFjeToiLCBhY2N1cmFjeSkpDQpwcmludChwYXN0ZSgiUHJlY2lzaW9uOiIsIHByZWNpc2lvbikpDQpwcmludChwYXN0ZSgiUmVjYWxsIChTZW5zaXRpdml0eSk6IiwgcmVjYWxsKSkNCnByaW50KHBhc3RlKCJTcGVjaWZpY2l0eToiLCBzcGVjaWZpY2l0eSkpDQpwcmludChwYXN0ZSgiRjEgU2NvcmU6IiwgZjFfc2NvcmUpKQ0KYGBgDQpQcmVjaXNpb246DQpQcmVjaXNpb24gbWVhc3VyZXMgdGhlIGFjY3VyYWN5IG9mIHBvc2l0aXZlIHByZWRpY3Rpb25zIG1hZGUgYnkgdGhlIG1vZGVsLiBJdCBpcyBjYWxjdWxhdGVkIGFzIHRoZSBudW1iZXIgb2YgdHJ1ZSBwb3NpdGl2ZSBwcmVkaWN0aW9ucyBkaXZpZGVkIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2YgcG9zaXRpdmUgcHJlZGljdGlvbnMgbWFkZSBieSB0aGUgbW9kZWwuIEEgaGlnaCBwcmVjaXNpb24gaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGlzIGdvb2QgYXQgY29ycmVjdGx5IGlkZW50aWZ5aW5nIHBvc2l0aXZlIGNhc2VzIHdpdGhvdXQgaW5jb3JyZWN0bHkgY2xhc3NpZnlpbmcgbmVnYXRpdmUgY2FzZXMgYXMgcG9zaXRpdmUuDQoNClJlY2FsbCAoU2Vuc2l0aXZpdHkpOg0KUmVjYWxsLCBhbHNvIGtub3duIGFzIHNlbnNpdGl2aXR5IG9yIHRydWUgcG9zaXRpdmUgcmF0ZSwgbWVhc3VyZXMgdGhlIGFiaWxpdHkgb2YgdGhlIG1vZGVsIHRvIGNvcnJlY3RseSBpZGVudGlmeSBhbGwgcG9zaXRpdmUgaW5zdGFuY2VzLiBJdCBpcyBjYWxjdWxhdGVkIGFzIHRoZSBudW1iZXIgb2YgdHJ1ZSBwb3NpdGl2ZSBwcmVkaWN0aW9ucyBkaXZpZGVkIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2YgYWN0dWFsIHBvc2l0aXZlIGluc3RhbmNlcyBpbiB0aGUgZGF0YS4gSGlnaCByZWNhbGwgaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGlzIGdvb2QgYXQgY2FwdHVyaW5nIHBvc2l0aXZlIGluc3RhbmNlcyB3aXRob3V0IG1pc3NpbmcgbWFueSBvZiB0aGVtLg0KDQpTcGVjaWZpY2l0eToNClNwZWNpZmljaXR5IG1lYXN1cmVzIHRoZSBhYmlsaXR5IG9mIHRoZSBtb2RlbCB0byBjb3JyZWN0bHkgaWRlbnRpZnkgYWxsIG5lZ2F0aXZlIGluc3RhbmNlcy4gSXQgaXMgY2FsY3VsYXRlZCBhcyB0aGUgbnVtYmVyIG9mIHRydWUgbmVnYXRpdmUgcHJlZGljdGlvbnMgZGl2aWRlZCBieSB0aGUgdG90YWwgbnVtYmVyIG9mIGFjdHVhbCBuZWdhdGl2ZSBpbnN0YW5jZXMgaW4gdGhlIGRhdGEuIEhpZ2ggc3BlY2lmaWNpdHkgaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGlzIGdvb2QgYXQgY29ycmVjdGx5IGNsYXNzaWZ5aW5nIG5lZ2F0aXZlIGluc3RhbmNlcyB3aXRob3V0IG1pc2NsYXNzaWZ5aW5nIHBvc2l0aXZlIGluc3RhbmNlcyBhcyBuZWdhdGl2ZS4NCg0KRjEgU2NvcmU6DQpUaGUgRjEgc2NvcmUgaXMgdGhlIGhhcm1vbmljIG1lYW4gb2YgcHJlY2lzaW9uIGFuZCByZWNhbGwuIEl0IHByb3ZpZGVzIGEgc2luZ2xlIG1ldHJpYyB0aGF0IGJhbGFuY2VzIGJvdGggcHJlY2lzaW9uIGFuZCByZWNhbGwuIEl0IGlzIGNhbGN1bGF0ZWQgYXMgMiB0aW1lcyB0aGUgcHJvZHVjdCBvZiBwcmVjaXNpb24gYW5kIHJlY2FsbCBkaXZpZGVkIGJ5IHRoZSBzdW0gb2YgcHJlY2lzaW9uIGFuZCByZWNhbGwuIFRoZSBGMSBzY29yZSByYW5nZXMgZnJvbSAwIHRvIDEsIHdoZXJlIGEgaGlnaGVyIHZhbHVlIGluZGljYXRlcyBiZXR0ZXIgb3ZlcmFsbCBwZXJmb3JtYW5jZSBpbiB0ZXJtcyBvZiBib3RoIHByZWNpc2lvbiBhbmQgcmVjYWxsLg0KDQpBY2N1cmFjeToNCkFjY3VyYWN5IG1lYXN1cmVzIHRoZSBvdmVyYWxsIGNvcnJlY3RuZXNzIG9mIHRoZSBtb2RlbCdzIHByZWRpY3Rpb25zIGFjcm9zcyBhbGwgY2xhc3Nlcy4gSXQgaXMgY2FsY3VsYXRlZCBhcyB0aGUgbnVtYmVyIG9mIGNvcnJlY3QgcHJlZGljdGlvbnMgKGJvdGggdHJ1ZSBwb3NpdGl2ZXMgYW5kIHRydWUgbmVnYXRpdmVzKSBkaXZpZGVkIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2YgcHJlZGljdGlvbnMgbWFkZSBieSB0aGUgbW9kZWwuIEhpZ2ggYWNjdXJhY3kgaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGlzIG1ha2luZyBjb3JyZWN0IHByZWRpY3Rpb25zIGZvciB0aGUgbWFqb3JpdHkgb2YgaW5zdGFuY2VzLg0KDQojIDguIFBsb3QgdG9wIGltcG9ydGFudCBmZWF0dXJlcyBhdmVyYWdlZCBhY3Jvc3MgdGhlIHN1YnR5cGVzIG9mIGNhbmNlciANCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KY2x1c3Rlcl9nZW5lcyA9IHRvcF9mZWF0dXJlcyRnZW5lcw0KbGVuZ3RoKGNsdXN0ZXJfZ2VuZXMpIA0KDQojIG9ubHkgc2VsZWN0IHRoZSBwcm90ZWlucyB0aGF0IGFyZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlcyBmb3IgdGhlIG1vZGVsIA0KbVJOQV9pbXAgPSBnZW5lX2V4cHJfZGF0YVssYyhjbHVzdGVyX2dlbmVzLCJZIildDQoNCiMgQ2FsY3VsYXRlIGF2ZXJhZ2UgZXhwcmVzc2lvbiBvZiBlYWNoIGdlbmUgZm9yIGVhY2ggY2FuY2VyIHR5cGUNCg0KYXZlcmFnZV9leHByIDwtIGFnZ3JlZ2F0ZSguIH4gWSwgZGF0YSA9IG1STkFfaW1wLCBGVU4gPSBtZWFuKQ0KDQojIFRyYW5zcG9zZSB0aGUgZGF0YWZyYW1lIGZvciBiZXR0ZXIgdmlzdWFsaXphdGlvbg0KYXZlcmFnZV9leHByIDwtIGRhdGEuZnJhbWUodChhdmVyYWdlX2V4cHIpKQ0KY29sbmFtZXMoYXZlcmFnZV9leHByKSA9IGMoIkJhc2FsIiwgIkhlcjIiLCAiTHVtQSIpDQojIFJlbW92ZSB0aGUgJ1knIHJvdyAoYXMgaXQncyBub3QgYSBnZW5lKQ0KYXZlcmFnZV9leHByIDwtIGF2ZXJhZ2VfZXhwclshcm93bmFtZXMoYXZlcmFnZV9leHByKSAlaW4lICJZIiwgXQ0KDQphdmVyYWdlX2V4cHIgPSBkYXRhLmZyYW1lKGF2ZXJhZ2VfZXhwcikNCg0KIyBBcHBseSBhcy5udW1lcmljIHRvIGVhY2ggY29sdW1uIG9mIHRoZSBkYXRhZnJhbWUNCmF2ZXJhZ2VfZXhwcltdIDwtIGxhcHBseShhdmVyYWdlX2V4cHIsIGFzLm51bWVyaWMpDQoNCmhlYWQoYXZlcmFnZV9leHByKQ0KDQpwaGVhdG1hcCh0KGF2ZXJhZ2VfZXhwciksIGZvbnRzaXplX2NvbCA9IDUsZm9udHNpemVfcm93ID0gMTAsDQogICAgICAgICBoZWlnaHQgPSAxOCwgd2lkdGggPSAxNiwgY2x1c3Rlcl9jb2xzID0gVFJVRSwgc2NhbGUgPSAiY29sdW1uIikNCmBgYA0KDQpGcm9tIHRoZSBwaGVhdG1hcCBpdCBpcyBvYnZpb3VzIHRoZXJlIGlzIGdyZWF0IHNlcGFyYXRpb24gYmV0d2VlbiB0aGUgdGhyZWUgY2FuY2VyIHN1YnR5cGVzLiBIZXIyIHBvcyBjYW5jZXIgaGFzIGhpZ2ggbGV2ZWxzIG9mIEhlcjIgcHJvdGVpbnMgZXhwcmVzc2VkLiBMdW1BIGhhcyBoaWdoIHByb3RlaW4gbGV2ZWxzIG9mIGhvcm1vbmUgcmVjZXB0b3JzIHN1Y2ggYXMgQVIsIEVSLCBQUiBhbmQgYWxzbyBHQVRBMyBwcm90ZWluIGxldmVscy4gVGhlIEJhc2FsIHN1YnR5cGUgaXMgcmVwcmVzZW50ZWQgcmF0aGVyIGJ5IHByb3RlaW5zIGludm9sdmVkIGluIHRoZSBwcm9ncmVzc2lvbiBvZiBjZWxsIGN5Y2xlIGFuZCB0aG9zZSBmYXIgaXMgdGhlIG1vc3QgZGlmZmljdWx0IHRvIHRyZWF0LiANCg0KIyA5LiBQbG90IFJPQyBhbmQgY2FsY3VsYXRlIEFVQyBmb3IgZWFjaCBjYW5jZXIgc3VidHlwZSAgDQpgYGB7ciwgZWNobyA9IFRSVUV9DQoNCiMgcHJlZGljdCBjbGFzcyBhbmQgdGhlbiBhdHRhY2ggdGVzdCBjbGFzcw0KcHJlZGljdGlvbnMgPC0gYXMuZGF0YS5mcmFtZShwcmVkaWN0KHJmX21vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhLCB0eXBlID0gInByb2IiKSkNCg0KcHJlZGljdGlvbnMkcHJlZGljdCA8LSBuYW1lcyhwcmVkaWN0aW9ucylbMTozXVthcHBseShwcmVkaWN0aW9uc1ssMTozXSwgMSwgd2hpY2gubWF4KV0NCnByZWRpY3Rpb25zJG9ic2VydmVkIDwtIHRlc3RfZGF0YSRZDQpoZWFkKHByZWRpY3Rpb25zKQ0KDQojICBST0MgY3VydmUsIEJhc2FsIHZzIG5vbiBCYXNhbA0Kcm9jLkJhc2FsIDwtIHJvYyhpZmVsc2UocHJlZGljdGlvbnMkb2JzZXJ2ZWQ9PSJCYXNhbCIsICJCYXNhbCIsICJub24tQmFzYWwiKSwgYXMubnVtZXJpYyhwcmVkaWN0aW9ucyRCYXNhbCkpDQpwbG90KHJvYy5CYXNhbCwgY29sID0gImdyYXk2MCIpDQoNCiMgb3RoZXJzDQpyb2MuSGVyMiA8LSByb2MoaWZlbHNlKHByZWRpY3Rpb25zJG9ic2VydmVkPT0iSGVyMiIsICJIZXIyIiwgIm5vbi1IZXIyIiksIGFzLm51bWVyaWMocHJlZGljdGlvbnMkQmFzYWwpKQ0Kcm9jLkx1bUEgPC0gcm9jKGlmZWxzZShwcmVkaWN0aW9ucyRvYnNlcnZlZD09Ikx1bUEiLCAiTHVtQSIsICJub24tTHVtQSIpLCBhcy5udW1lcmljKHByZWRpY3Rpb25zJEJhc2FsKSkNCmxpbmVzKHJvYy5IZXIyLCBjb2wgPSAiYmx1ZSIpDQpsaW5lcyhyb2MuTHVtQSwgY29sID0gInJlZCIpDQoNCnJvYy5CYXNhbCRhdWMNCnJvYy5IZXIyJGF1Yw0Kcm9jLkx1bUEkYXVjDQoNCmBgYA==