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
head(mRNA[,1:20])
     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)
NA

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")


roc.Basal$auc
Area under the curve: 0.9893
roc.Her2$auc
Area under the curve: 0.6019
roc.LumA$auc
Area under the curve: 0.932
LS0tDQp0aXRsZTogIlByZWRpY3QgY2FuY2VyIHN1YnR5cGVzIGZyb20gcHJvdGVpbiBsZXZlbCBzaWduYXR1cmVzIHdpdGggcmFuZG9tIGZvcmVzdCBtb2RlbCAiDQphdXRob3I6ICJNYWxpbmEgRG95bm92YSINCmRhdGU6ICI1LzAyLzIwMjQiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOg0KICAgICB0aGVtZTogc2ltcGxleCAjIENoYW5nZSB0aGUgdGhlbWUgdG8gJ2RhcmtseScNCiAgICAgaGlnaGxpZ2h0OiB0YW5nbyAjIENoYW5nZSB0aGUgaGlnaGxpZ2h0IHN0eWxlDQogICAgIHRvYzogeWVzICMgSW5jbHVkZSB0YWJsZSBvZiBjb250ZW50DQogICAgIHRvY19mbG9hdDogZmFsc2UgIyBJZiB0byBiZSBmbG9hdGluZyBvciBub3QgDQogICAgDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KIyAxLiAgSW5zdGFsbCBhbmQvb3IgbG9hZCB0aGUgcmVxdWlyZWQgcGFja2FnZXMNCg0KYGBge3J9DQoNCiMgaW5zdGFsbC5wYWNrYWdlcygiQmlvY01hbmFnZXIiKQ0KIyBCaW9jTWFuYWdlcjo6aW5zdGFsbChjKCJwaHlsb3NlcSIsICJ2ZWdhbiIsICJtaWNyb2Jpb21lIiwgIk1hYXNsaW4yIikpDQojIEJpb2NNYW5hZ2VyOjppbnN0YWxsKGMoIm1pY3JvYmlvbWVNYXJrZXIiKSkNCiMgQmlvY01hbmFnZXI6Omluc3RhbGwoYygiZ2dwbG90MiIsICJ0aWR5dmVyc2UiLCAiZHBseXIiLCAiZGF0YS50YWJsZSIpKQ0KIyBpbnN0YWxsLnBhY2thZ2VzKCJodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9zcmMvY29udHJpYi9BcmNoaXZlL3JhbmRvbUZvcmVzdC9yYW5kb21Gb3Jlc3RfNC42LTE0LnRhci5neiIsIHJlcG9zID0gTlVMTCwgdHlwZSA9ICJzb3VyY2UiKQ0KDQpsaWJyYXJ5KG1peE9taWNzKQ0KbGlicmFyeShpZ3JhcGgpDQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkNCmxpYnJhcnkoY2FyZXQpDQpsaWJyYXJ5KHBST0MpDQpsaWJyYXJ5KHBoZWF0bWFwKQ0KDQpgYGANCg0KIyAyLiBTZXQgdGhlIHdvcmtpbmcgZGlyZWN0b3J5DQoNCmBgYHtyfQ0Kc2V0d2QoIkM6Ly9Vc2Vycy8vTWFsaW5hLy9EZXNrdG9wLy9SRl9CQ19zdWJ0eXBlc19wcmVkaWN0LyIpDQpgYGANCg0KIyAzLiBMb2FkIHRoZSBleGFtcGxlIGRhdGFzZXQNCg0KVGhlIGV4YW1wbGUgZGF0YXNldCBpcyBmcm9tIHRoZSBwYWNrYWdlIG1peE9taWNzLiBSb3duYW1lcyBhcmUgcGF0aWVudHMgYW5kIGNvbHVtbiBhcmUgdGhlIHByb3RlaW5zIChzb21ldGltZXMgd2l0aCBwb3N0LXRyYW5zbGF0aW9uYWwgbW9kaWZpY2F0aW9uIGxpa2UgIjRFLUJQMV9wVDM3IikuIFRoZSBkYXRhIGlzIG5vcm1hbGl6ZWQuIA0KV2Ugd2lsbCBjYWxsIHRoZSB2YXJpYWJsZSBtUk5BLCBidXQgd2lsbCBrbm93IHRoZSBjdXJyZW50IGFuYWx5c2VzIGFyZSBkb25lIGZvciBub3JtYWxpemVkIHByb3RlaW4gY291bnRzLg0KV2UgYWxzbyBrbm93IHdoYXQgdHlwZSBvZiBjYW5jZXIgdGhlIHBhdGllbnRzIHdlcmUgZGlhZ25vc2VkIHdpdGgsIHdoaWNoIGlzIGNvbnRhaW5lZCBpbiB0aGUgWSB2YXJpYWJsZSBhbmQgd2lsbCBhZGQgaXQgdG8gdGhlIGRhdGFmcmFtZSBhcyBhbiBhZGRpdGlvbmFsIGNvbHVtbiwgc28gd2Ugd2lsbCBoYXZlIG91ciBvdXRjb21lLiAgDQoNCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KZGF0YShicmVhc3QuVENHQSkNCg0KIyMgRXhwbG9yZSB0aGUgZGF0YXNldA0KbVJOQSA9IGJyZWFzdC5UQ0dBJGRhdGEudHJhaW4kcHJvdGVpbg0KDQpkaW0obVJOQSkNCg0KaGVhZChtUk5BWywxOjIwXSkNCg0KIyMgR2V0IEJyZWFzdCBDYW5jZXIgc3VidHlwZSBjb2x1bW4NCg0KWSA8LSBicmVhc3QuVENHQSRkYXRhLnRyYWluJHN1YnR5cGUNCnN1bW1hcnkoWSkNCg0KIyMgTWVyZ2UgdGhlIHByb3RlaW4gbGV2ZWwgZGF0YSB3aXRoIHRoZSBvdXRjb21lIGNvbHVtbg0KDQpnZW5lX2V4cHJfZGF0YT0gZGF0YS5mcmFtZShjYmluZCgobVJOQSksZGF0YS5mcmFtZShZKSkpDQoNCmhlYWQoZ2VuZV9leHByX2RhdGFbLDE6MjBdKQ0KZ2VuZV9leHByX2RhdGEkWSA9IGFzLmZhY3RvcihnZW5lX2V4cHJfZGF0YSRZKQ0KZGltKGdlbmVfZXhwcl9kYXRhKQ0KDQojIyBBc3NpbmcgdGhlIGRhdGFmcmFtZSB3aXRoIG9ubHkgNDAgZmVhdHVyZXMgdG8gZ2VuZV9leHByX2RhdGENCiMgZ2VuZV9leHByX2RhdGEgPSBtUk5BX2ltcA0KIyANCiMgZGltKGdlbmVfZXhwcl9kYXRhKQ0KIyANCiMgaGVhZChnZW5lX2V4cHJfZGF0YVssMToyMF0pDQojIA0KIyBsZXZlbHMoZ2VuZV9leHByX2RhdGEkWSkNCg0KYGBgDQoNCg0KIyA0LiBTcGxpdCBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3Rpbmcgc2V0cw0KDQpgYGB7ciwgZWNobyA9IFRSVUV9DQoNCnNldC5zZWVkKDEyMykgIyBmb3IgcmVwcm9kdWNpYmlsaXR5DQp0cmFpbl9pbmRleCA8LSBzYW1wbGUoMTpucm93KGdlbmVfZXhwcl9kYXRhKSwgMC43ICogbnJvdyhnZW5lX2V4cHJfZGF0YSkpICMgNzAlIGZvciB0cmFpbmluZw0KdHJhaW5fZGF0YSA8LSBnZW5lX2V4cHJfZGF0YVt0cmFpbl9pbmRleCwgXQ0KDQp0ZXN0X2RhdGEgPC0gZ2VuZV9leHByX2RhdGFbLXRyYWluX2luZGV4LCBdDQoNCmBgYA0KDQojIDUuIFRyYWluIFJhbmRvbSBGb3Jlc3QgbW9kZWwNCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KIyMgaW5jbHVkZSAxMHggY3Jvc3MtdmFsaWRhdGlvbg0KY3RybCA8LSBjYXJldDo6dHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQ0KDQpyZl9tb2RlbCA8LSByYW5kb21Gb3Jlc3QoWSB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhLCB0ckNvbnRyb2wgPSBjdHJsLCBudHJlZSA9IDUwMCkNCmBgYA0KDQojIDYuIEZlYXR1cmUgU2VsZWN0aW9uDQpgYGB7ciwgZWNobyA9IFRSVUV9DQppbXBvcnRhbmNlIDwtIGltcG9ydGFuY2UocmZfbW9kZWwpDQppbXBvcnRhbmNlID0gZGF0YS5mcmFtZShpbXBvcnRhbmNlKQ0KaGVhZChpbXBvcnRhbmNlKQ0KaW1wb3J0YW5jZSRnZW5lcyA9IHJvd25hbWVzKGltcG9ydGFuY2UpDQp0b3BfZmVhdHVyZXMgPC0gaW1wb3J0YW5jZSU+JWRwbHlyOjphcnJhbmdlKGRlc2MoTWVhbkRlY3JlYXNlR2luaSkpJT4laGVhZCg0MCkgIyBTZWxlY3QgdG9wIG51bWJlciBmZWF0dXJlcw0KaGVhZCh0b3BfZmVhdHVyZXMsIG49NDApDQoNCmBgYA0KDQojIDcuIEV2YWx1YXRlIE1vZGVsIFBlcmZvcm1hbmNlIGFuZCBQcmludCByZXN1bHRzDQoNCmBgYHtyLCBlY2hvID0gVFJVRX0NCg0KIyBNb2RlbCBldmFsdWF0aW9uDQoNCnByZWRpY3Rpb25zMSA8LSBwcmVkaWN0KHJmX21vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhKQ0KDQoNCiMgQ29tcHV0ZSBjb25mdXNpb24gbWF0cml4DQpjb25mX21hdHJpeCA8LSB0YWJsZSh0ZXN0X2RhdGEkWSwgcHJlZGljdGlvbnMxKQ0KDQojIENhbGN1bGF0ZSBwcmVjaXNpb24NCnByZWNpc2lvbiA8LSBjb25mX21hdHJpeFsyLCAyXSAvIHN1bShjb25mX21hdHJpeFssIDJdKQ0KDQojIENhbGN1bGF0ZSByZWNhbGwgKHNlbnNpdGl2aXR5KQ0KcmVjYWxsIDwtIGNvbmZfbWF0cml4WzIsIDJdIC8gc3VtKGNvbmZfbWF0cml4WzIsIF0pDQoNCiMgQ2FsY3VsYXRlIHNwZWNpZmljaXR5DQpzcGVjaWZpY2l0eSA8LSBjb25mX21hdHJpeFsxLCAxXSAvIHN1bShjb25mX21hdHJpeFsxLCBdKQ0KDQojIENhbGN1bGF0ZSBGMSBzY29yZQ0KZjFfc2NvcmUgPC0gMiAqIChwcmVjaXNpb24gKiByZWNhbGwpIC8gKHByZWNpc2lvbiArIHJlY2FsbCkNCg0KIyBDYWxjdWxhdGUgYWNjdXJhY3kNCg0KYWNjdXJhY3kgPC0gc3VtKGRpYWcoY29uZl9tYXRyaXgpKSAvIHN1bShjb25mX21hdHJpeCkNCg0KIyBQcmludCB0aGUgbWV0cmljcw0KDQpwcmludChjb25mX21hdHJpeCkNCnByaW50KHBhc3RlKCJBY2N1cmFjeToiLCBhY2N1cmFjeSkpDQpwcmludChwYXN0ZSgiUHJlY2lzaW9uOiIsIHByZWNpc2lvbikpDQpwcmludChwYXN0ZSgiUmVjYWxsIChTZW5zaXRpdml0eSk6IiwgcmVjYWxsKSkNCnByaW50KHBhc3RlKCJTcGVjaWZpY2l0eToiLCBzcGVjaWZpY2l0eSkpDQpwcmludChwYXN0ZSgiRjEgU2NvcmU6IiwgZjFfc2NvcmUpKQ0KYGBgDQpQcmVjaXNpb246DQpQcmVjaXNpb24gbWVhc3VyZXMgdGhlIGFjY3VyYWN5IG9mIHBvc2l0aXZlIHByZWRpY3Rpb25zIG1hZGUgYnkgdGhlIG1vZGVsLiBJdCBpcyBjYWxjdWxhdGVkIGFzIHRoZSBudW1iZXIgb2YgdHJ1ZSBwb3NpdGl2ZSBwcmVkaWN0aW9ucyBkaXZpZGVkIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2YgcG9zaXRpdmUgcHJlZGljdGlvbnMgbWFkZSBieSB0aGUgbW9kZWwuIEEgaGlnaCBwcmVjaXNpb24gaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGlzIGdvb2QgYXQgY29ycmVjdGx5IGlkZW50aWZ5aW5nIHBvc2l0aXZlIGNhc2VzIHdpdGhvdXQgaW5jb3JyZWN0bHkgY2xhc3NpZnlpbmcgbmVnYXRpdmUgY2FzZXMgYXMgcG9zaXRpdmUuDQoNClJlY2FsbCAoU2Vuc2l0aXZpdHkpOg0KUmVjYWxsLCBhbHNvIGtub3duIGFzIHNlbnNpdGl2aXR5IG9yIHRydWUgcG9zaXRpdmUgcmF0ZSwgbWVhc3VyZXMgdGhlIGFiaWxpdHkgb2YgdGhlIG1vZGVsIHRvIGNvcnJlY3RseSBpZGVudGlmeSBhbGwgcG9zaXRpdmUgaW5zdGFuY2VzLiBJdCBpcyBjYWxjdWxhdGVkIGFzIHRoZSBudW1iZXIgb2YgdHJ1ZSBwb3NpdGl2ZSBwcmVkaWN0aW9ucyBkaXZpZGVkIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2YgYWN0dWFsIHBvc2l0aXZlIGluc3RhbmNlcyBpbiB0aGUgZGF0YS4gSGlnaCByZWNhbGwgaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGlzIGdvb2QgYXQgY2FwdHVyaW5nIHBvc2l0aXZlIGluc3RhbmNlcyB3aXRob3V0IG1pc3NpbmcgbWFueSBvZiB0aGVtLg0KDQpTcGVjaWZpY2l0eToNClNwZWNpZmljaXR5IG1lYXN1cmVzIHRoZSBhYmlsaXR5IG9mIHRoZSBtb2RlbCB0byBjb3JyZWN0bHkgaWRlbnRpZnkgYWxsIG5lZ2F0aXZlIGluc3RhbmNlcy4gSXQgaXMgY2FsY3VsYXRlZCBhcyB0aGUgbnVtYmVyIG9mIHRydWUgbmVnYXRpdmUgcHJlZGljdGlvbnMgZGl2aWRlZCBieSB0aGUgdG90YWwgbnVtYmVyIG9mIGFjdHVhbCBuZWdhdGl2ZSBpbnN0YW5jZXMgaW4gdGhlIGRhdGEuIEhpZ2ggc3BlY2lmaWNpdHkgaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGlzIGdvb2QgYXQgY29ycmVjdGx5IGNsYXNzaWZ5aW5nIG5lZ2F0aXZlIGluc3RhbmNlcyB3aXRob3V0IG1pc2NsYXNzaWZ5aW5nIHBvc2l0aXZlIGluc3RhbmNlcyBhcyBuZWdhdGl2ZS4NCg0KRjEgU2NvcmU6DQpUaGUgRjEgc2NvcmUgaXMgdGhlIGhhcm1vbmljIG1lYW4gb2YgcHJlY2lzaW9uIGFuZCByZWNhbGwuIEl0IHByb3ZpZGVzIGEgc2luZ2xlIG1ldHJpYyB0aGF0IGJhbGFuY2VzIGJvdGggcHJlY2lzaW9uIGFuZCByZWNhbGwuIEl0IGlzIGNhbGN1bGF0ZWQgYXMgMiB0aW1lcyB0aGUgcHJvZHVjdCBvZiBwcmVjaXNpb24gYW5kIHJlY2FsbCBkaXZpZGVkIGJ5IHRoZSBzdW0gb2YgcHJlY2lzaW9uIGFuZCByZWNhbGwuIFRoZSBGMSBzY29yZSByYW5nZXMgZnJvbSAwIHRvIDEsIHdoZXJlIGEgaGlnaGVyIHZhbHVlIGluZGljYXRlcyBiZXR0ZXIgb3ZlcmFsbCBwZXJmb3JtYW5jZSBpbiB0ZXJtcyBvZiBib3RoIHByZWNpc2lvbiBhbmQgcmVjYWxsLg0KDQpBY2N1cmFjeToNCkFjY3VyYWN5IG1lYXN1cmVzIHRoZSBvdmVyYWxsIGNvcnJlY3RuZXNzIG9mIHRoZSBtb2RlbCdzIHByZWRpY3Rpb25zIGFjcm9zcyBhbGwgY2xhc3Nlcy4gSXQgaXMgY2FsY3VsYXRlZCBhcyB0aGUgbnVtYmVyIG9mIGNvcnJlY3QgcHJlZGljdGlvbnMgKGJvdGggdHJ1ZSBwb3NpdGl2ZXMgYW5kIHRydWUgbmVnYXRpdmVzKSBkaXZpZGVkIGJ5IHRoZSB0b3RhbCBudW1iZXIgb2YgcHJlZGljdGlvbnMgbWFkZSBieSB0aGUgbW9kZWwuIEhpZ2ggYWNjdXJhY3kgaW5kaWNhdGVzIHRoYXQgdGhlIG1vZGVsIGlzIG1ha2luZyBjb3JyZWN0IHByZWRpY3Rpb25zIGZvciB0aGUgbWFqb3JpdHkgb2YgaW5zdGFuY2VzLg0KDQojIDguIFBsb3QgdG9wIGltcG9ydGFudCBmZWF0dXJlcyBhdmVyYWdlZCBhY3Jvc3MgdGhlIHN1YnR5cGVzIG9mIGNhbmNlciANCg0KYGBge3IsIGVjaG8gPSBUUlVFfQ0KY2x1c3Rlcl9nZW5lcyA9IHRvcF9mZWF0dXJlcyRnZW5lcw0KbGVuZ3RoKGNsdXN0ZXJfZ2VuZXMpIA0KDQojIG9ubHkgc2VsZWN0IHRoZSBwcm90ZWlucyB0aGF0IGFyZSBtb3N0IGltcG9ydGFudCBmZWF0dXJlcyBmb3IgdGhlIG1vZGVsIA0KbVJOQV9pbXAgPSBnZW5lX2V4cHJfZGF0YVssYyhjbHVzdGVyX2dlbmVzLCJZIildDQoNCiMgQ2FsY3VsYXRlIGF2ZXJhZ2UgZXhwcmVzc2lvbiBvZiBlYWNoIGdlbmUgZm9yIGVhY2ggY2FuY2VyIHR5cGUNCg0KYXZlcmFnZV9leHByIDwtIGFnZ3JlZ2F0ZSguIH4gWSwgZGF0YSA9IG1STkFfaW1wLCBGVU4gPSBtZWFuKQ0KDQojIFRyYW5zcG9zZSB0aGUgZGF0YWZyYW1lIGZvciBiZXR0ZXIgdmlzdWFsaXphdGlvbg0KYXZlcmFnZV9leHByIDwtIGRhdGEuZnJhbWUodChhdmVyYWdlX2V4cHIpKQ0KY29sbmFtZXMoYXZlcmFnZV9leHByKSA9IGMoIkJhc2FsIiwgIkhlcjIiLCAiTHVtQSIpDQojIFJlbW92ZSB0aGUgJ1knIHJvdyAoYXMgaXQncyBub3QgYSBnZW5lKQ0KYXZlcmFnZV9leHByIDwtIGF2ZXJhZ2VfZXhwclshcm93bmFtZXMoYXZlcmFnZV9leHByKSAlaW4lICJZIiwgXQ0KDQphdmVyYWdlX2V4cHIgPSBkYXRhLmZyYW1lKGF2ZXJhZ2VfZXhwcikNCg0KIyBBcHBseSBhcy5udW1lcmljIHRvIGVhY2ggY29sdW1uIG9mIHRoZSBkYXRhZnJhbWUNCmF2ZXJhZ2VfZXhwcltdIDwtIGxhcHBseShhdmVyYWdlX2V4cHIsIGFzLm51bWVyaWMpDQoNCmhlYWQoYXZlcmFnZV9leHByKQ0KDQpwaGVhdG1hcCh0KGF2ZXJhZ2VfZXhwciksIGZvbnRzaXplX2NvbCA9IDUsZm9udHNpemVfcm93ID0gMTAsDQogICAgICAgICBoZWlnaHQgPSAxOCwgd2lkdGggPSAxNiwgY2x1c3Rlcl9jb2xzID0gVFJVRSwgc2NhbGUgPSAiY29sdW1uIikNCmBgYA0KDQpGcm9tIHRoZSBwaGVhdG1hcCBpdCBpcyBvYnZpb3VzIHRoZXJlIGlzIGdyZWF0IHNlcGFyYXRpb24gYmV0d2VlbiB0aGUgdGhyZWUgY2FuY2VyIHN1YnR5cGVzLiBIZXIyIHBvcyBjYW5jZXIgaGFzIGhpZ2ggbGV2ZWxzIG9mIEhlcjIgcHJvdGVpbnMgZXhwcmVzc2VkLiBMdW1BIGhhcyBoaWdoIHByb3RlaW4gbGV2ZWxzIG9mIGhvcm1vbmUgcmVjZXB0b3JzIHN1Y2ggYXMgQVIsIEVSLCBQUiBhbmQgYWxzbyBHQVRBMyBwcm90ZWluIGxldmVscy4gVGhlIEJhc2FsIHN1YnR5cGUgaXMgcmVwcmVzZW50ZWQgcmF0aGVyIGJ5IHByb3RlaW5zIGludm9sdmVkIGluIHRoZSBwcm9ncmVzc2lvbiBvZiBjZWxsIGN5Y2xlIGFuZCB0aG9zZSBmYXIgaXMgdGhlIG1vc3QgZGlmZmljdWx0IHRvIHRyZWF0LiANCg0KIyA5LiBQbG90IFJPQyBhbmQgY2FsY3VsYXRlIEFVQyBmb3IgZWFjaCBjYW5jZXIgc3VidHlwZSAgDQpgYGB7ciwgZWNobyA9IFRSVUV9DQoNCiMgcHJlZGljdCBjbGFzcyBhbmQgdGhlbiBhdHRhY2ggdGVzdCBjbGFzcw0KcHJlZGljdGlvbnMgPC0gYXMuZGF0YS5mcmFtZShwcmVkaWN0KHJmX21vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhLCB0eXBlID0gInByb2IiKSkNCg0KcHJlZGljdGlvbnMkcHJlZGljdCA8LSBuYW1lcyhwcmVkaWN0aW9ucylbMTozXVthcHBseShwcmVkaWN0aW9uc1ssMTozXSwgMSwgd2hpY2gubWF4KV0NCnByZWRpY3Rpb25zJG9ic2VydmVkIDwtIHRlc3RfZGF0YSRZDQpoZWFkKHByZWRpY3Rpb25zKQ0KDQojICBST0MgY3VydmUsIEJhc2FsIHZzIG5vbiBCYXNhbA0Kcm9jLkJhc2FsIDwtIHJvYyhpZmVsc2UocHJlZGljdGlvbnMkb2JzZXJ2ZWQ9PSJCYXNhbCIsICJCYXNhbCIsICJub24tQmFzYWwiKSwgYXMubnVtZXJpYyhwcmVkaWN0aW9ucyRCYXNhbCkpDQpwbG90KHJvYy5CYXNhbCwgY29sID0gImdyYXk2MCIpDQoNCiMgb3RoZXJzDQpyb2MuSGVyMiA8LSByb2MoaWZlbHNlKHByZWRpY3Rpb25zJG9ic2VydmVkPT0iSGVyMiIsICJIZXIyIiwgIm5vbi1IZXIyIiksIGFzLm51bWVyaWMocHJlZGljdGlvbnMkQmFzYWwpKQ0Kcm9jLkx1bUEgPC0gcm9jKGlmZWxzZShwcmVkaWN0aW9ucyRvYnNlcnZlZD09Ikx1bUEiLCAiTHVtQSIsICJub24tTHVtQSIpLCBhcy5udW1lcmljKHByZWRpY3Rpb25zJEJhc2FsKSkNCmxpbmVzKHJvYy5IZXIyLCBjb2wgPSAiYmx1ZSIpDQpsaW5lcyhyb2MuTHVtQSwgY29sID0gInJlZCIpDQoNCnJvYy5CYXNhbCRhdWMNCnJvYy5IZXIyJGF1Yw0Kcm9jLkx1bUEkYXVjDQoNCmBgYA==