1 Changelog

  • 20230117: Trying to understand memory/time usage when I add back in variables. E.g. my last iteration limited the model to a very small portion of the most-strain-determining variant positions in the data. In order to predict attributes which are less obvious, I will need to be able to use a larger portion of the data.
  • Same day, later: I manually reclassified some of the tricky samples using IGV and comparisons against my changing panel of ‘canonical’ strains. In this iteration, I reclassified a couple of my references and so I hope that it will clarify future runs.

2 Creating a strain classifier using our data

I have not yet implemented a real ML classifier, so I will use some of the caret documentation in order to write a trainer and classifier of the TMRC2 strains.

3 Splitting the data based on outcome

I am taking this pretty much directly from:

https://topepo.github.io/caret/data-splitting.html#simple-splitting-based-on-the-outcome

In order to perform the split by outcome, I think I need to slightly reformat the structure of our variant data to more closely match what I see in the datasets: etitanic/iris/Sonar.

3.1 Define the outcome factor

Setting this to character here because I will be messing with the factor levels and I would rather not have to relevel multiple times.

Note that in the previous revision of this, I used the column ‘zymodemecategorical’, but now I am using knnv2classification. If things continue to change, I may make a v3 column.

ref_col <- "knnhclusttogethercall"
outcome_factor <- as.character(pData(both_snps)[[ref_col]])

3.2 Figure out why old samples were dropped.

In my previous attempt at this, I noticed that the older samples were getting dropped by na.omit, I want to take a moment here to figure out why.

dim(exprs(both_snps))
## [1] 1611844     134
both_norm <- normalize_expt(both_snps, transform="log2", norm="quant")
## transform_counts: Found 207410954 values equal to 0, adding 1 to the matrix.

3.3 Filter the data using near-zero variance

Stealing now from:

https://github.com/compgenomr/book/blob/master/05-supervisedLearning.Rmd

Multiple sections of the above document provide nice ways to remove redundant predictor variables. For my purposes, I would like to keep all of them because I am hoping to make use of the same model in a dataset which is vastly sparser than this. However, for the purposes of me learning, I will apply all of these methods and use it on the rest of the TMRC2 data.

In hopes of using more of the data, I will remove the SD filter.

texprs <- t(exprs(both_norm))
## Given this large amount of data, this step is slow, taking > 10 minutes.
nzv <- preProcess(texprs, method="nzv", uniqueCut=15)
nzv_texprs <- predict(nzv, texprs)
dim(nzv_texprs)
## [1]   134 97426

3.4 Filtering to the highest standard deviation variables

For the moment, I am excluding the following block in order to see how much time/memory keeping these variables costs. If I recall properly, the model of the top-2k variant positions cost ~ 1-4G of memory. I hope that this scales linearly, but I am thinking it might not.

standard_devs <- apply(nzv_texprs, 2, sd)
top_predictors <- order(standard_devs, decreasing = TRUE)[1:2000]
nzv_texprs <- nzv_texprs[, top_predictors]

3.5 Center the data

I think centering may not be needed for this data, but here is how:

nzv_center <- preProcess(nzv_texprs, method = "center")
nzv_texprs <- predict(nzv_center, nzv_texprs)

3.6 Drop correlated

In the same fashion, I want to leave this off because later applications of this model will include low coverage samples which may not have every variant represented.

nzv_correlated <- preProcess(nzv_texprs, method = "corr", cutoff = 0.9)
nzv_texprs <- predict(nzv_correlated, nzv_texprs)
dim(nzv_texprs)
anyNA(nzv_texprs)

3.7 Reformat the metadata to be used by caret

na_idx <- is.na(outcome_factor)
outcome_factor[na_idx] <- "unknown"
meta_factors <- pData(both_snps)[, c(ref_col, "clinicalcategorical", "sus_category_current")]
meta_factors[[ref_col]] <- outcome_factor

3.8 Add the metadata to the filtered variables

In addition, I want make sure that no unknown samples are used in training.

snp_df <- cbind(meta_factors, nzv_texprs)
dim(snp_df)
## [1]   134 97429
snp_train_idx <- createDataPartition(outcome_factor, p = 0.4,
                                     list = FALSE,
                                     times = 1)
summary(outcome_factor[snp_train_idx])
##    Length     Class      Mode 
##        56 character character
snp_train_start <- rownames(snp_df)[snp_train_idx]
train_known_idx <- meta_factors[snp_train_start, ref_col] != "unknown"
train_rownames <- snp_train_start[train_known_idx]
not_train_idx <- ! rownames(snp_df) %in% train_rownames
not_train_rownames <- rownames(snp_df)[not_train_idx]

train_df <- snp_df[train_rownames, ]
train_df[["clinicalcategorical"]] <- NULL
train_df[["sus_category_current"]] <- NULL
train_df[[1]] <- as.factor(as.character(train_df[[1]]))
test_df <- snp_df[not_train_rownames, ]
test_df[[ref_col]] <- NULL
test_df[["clinicalcategorical"]] <- NULL
test_df[["sus_category_current"]] <- NULL

3.9 KNN

Perform the fit and test the training set against itself.

knn_fit <- knn3(x = train_df[, -1],
                y = train_df[[ref_col]],
                k = 5)
train_predict <- predict(knn_fit, train_df[, -1], type = "class")
confusionMatrix(data = train_df[[1]], reference = train_predict)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction z10 z21 z22 z23 z32
##        z10   2   0   0   0   0
##        z21   0   5   0   0   0
##        z22   0   0  16   0   0
##        z23   0   0   0  17   0
##        z32   0   1   0   0   0
## 
## Overall Statistics
##                                         
##                Accuracy : 0.976         
##                  95% CI : (0.871, 0.999)
##     No Information Rate : 0.415         
##     P-Value [Acc > NIR] : 1.24e-14      
##                                         
##                   Kappa : 0.963         
##                                         
##  Mcnemar's Test P-Value : NA            
## 
## Statistics by Class:
## 
##                      Class: z10 Class: z21 Class: z22 Class: z23 Class: z32
## Sensitivity              1.0000      0.833       1.00      1.000         NA
## Specificity              1.0000      1.000       1.00      1.000     0.9756
## Pos Pred Value           1.0000      1.000       1.00      1.000         NA
## Neg Pred Value           1.0000      0.972       1.00      1.000         NA
## Prevalence               0.0488      0.146       0.39      0.415     0.0000
## Detection Rate           0.0488      0.122       0.39      0.415     0.0000
## Detection Prevalence     0.0488      0.122       0.39      0.415     0.0244
## Balanced Accuracy        1.0000      0.917       1.00      1.000         NA
annotations <- train_df[[1]]
names(annotations) <- rownames(train_df)
names(train_predict) <- rownames(train_df)
summary(annotations == train_predict)
##    Mode   FALSE    TRUE 
## logical       1      40

It looks like my knn model has some disagreements in the already difficult cases of poorly represented strains, so I will call it a win. E.g it appears to fail on a z1.0, b2904, z3.0, z2.0, a 2.1, and 2.4.

Now lets try it out on the rest of the data.

test_predict <- predict(knn_fit, test_df, type = "class")
names(test_predict) <- rownames(test_df)
test_predict
## tmrc20001 tmrc20065 tmrc20005 tmrc20008 tmrc20028 tmrc20066 tmrc20039 tmrc20037 
##       z23       z23       z22       z22       z10       z23       z22       z23 
## tmrc20038 tmrc20067 tmrc20041 tmrc20009 tmrc20010 tmrc20016 tmrc20012 tmrc20014 
##       z23       z23       z21       z22       z23       z23       z22       z22 
## tmrc20020 tmrc20024 tmrc20036 tmrc20033 tmrc20026 tmrc20076 tmrc20055 tmrc20071 
##       z22       z22       z21       z22       z22       z22       z22       z23 
## tmrc20094 tmrc20042 tmrc20072 tmrc20059 tmrc20048 tmrc20088 tmrc20056 tmrc20063 
##       z23       z22       z21       z23       z23       z22       z22       z22 
## tmrc20053 tmrc20052 tmrc20064 tmrc20075 tmrc20051 tmrc20049 tmrc20080 tmrc20083 
##       z22       z23       z23       z23       z23       z22       z23       z22 
## tmrc20054 tmrc20085 tmrc20046 tmrc20047 tmrc20061 tmrc20105 tmrc20108 tmrc20096 
##       z23       z23       z21       z23       z21       z23       z23       z22 
## tmrc20092 tmrc20102 tmrc20099 tmrc20100 tmrc20091 tmrc20084 tmrc20087 tmrc20103 
##       z22       z23       z23       z23       z22       z21       z22       z21 
## tmrc20086 tmrc20081 tmrc20106 tmrc20095  hpgl0242  hpgl0243  hpgl0244  hpgl0245 
##       z22       z22       z22       z23       z22       z22       z23       z21 
##  hpgl0246  hpgl0247  hpgl0248  hpgl0316  hpgl0318  hpgl0320  hpgl0322  hpgl0631 
##       z23       z22       z22       z22       z22       z22       z22       z22 
##  hpgl0632  hpgl0633  hpgl0634  hpgl0635  hpgl0636  hpgl0638  hpgl0639  hpgl0641 
##       z22       z23       z22       z23       z22       z22       z23       z22 
##  hpgl0643  hpgl0651  hpgl0652  hpgl0653  hpgl0654  hpgl0655  hpgl0656  hpgl0658 
##       z22       z22       z22       z23       z22       z23       z22       z10 
##  hpgl0659  hpgl0660  hpgl0661  hpgl0662  hpgl0663 
##       z22       z23       z22       z23       z22 
## Levels: z10 z21 z22 z23 z32
ref_result <- pData(both_snps)[[ref_col]]
names(ref_result) <- rownames(pData(both_snps))

shared <- names(ref_result) %in% names(test_predict)
compare <- ref_result[shared]
compare <- compare[!is.na(compare)]
test_predict[compare != test_predict[names(compare)]]
## tmrc20085 
##       z23 
## Levels: z10 z21 z22 z23 z32

4 Look for genomic susceptibility markers

ref_col <- "sus_category_current"
outcome_factor <- as.character(pData(both_snps)[[ref_col]])
na_idx <- is.na(outcome_factor)
outcome_factor[na_idx] <- "unknown"
meta_factors <- pData(both_snps)[, c(ref_col, "clinicalcategorical", "sus_category_current")]
meta_factors[[ref_col]] <- as.factor(outcome_factor)

snp_df <- cbind(meta_factors, nzv_texprs)
dim(snp_df)
## [1]   134 97429
snp_train_idx <- createDataPartition(outcome_factor, p = 0.4,
                                     list = FALSE,
                                     times = 1)
summary(outcome_factor[snp_train_idx])
##    Length     Class      Mode 
##        55 character character
snp_train_start <- rownames(snp_df)[snp_train_idx]
train_known_idx <- meta_factors[snp_train_start, ref_col] != "unknown"
train_rownames <- snp_train_start[train_known_idx]
not_train_idx <- ! rownames(snp_df) %in% train_rownames
not_train_rownames <- rownames(snp_df)[not_train_idx]

train_df <- snp_df[train_rownames, ]
train_df[["clinicalcategorical"]] <- NULL
train_df[["sus_category_current.1"]] <- NULL
train_df[[1]] <- as.factor(as.character(train_df[[1]]))
test_df <- snp_df[not_train_rownames, ]
test_df[[ref_col]] <- NULL
test_df[["clinicalcategorical"]] <- NULL
test_df[["sus_category_current.1"]] <- NULL
test_df[["sus_category_current"]] <- NULL

knn_fit <- knn3(x = train_df[, -1],
                y = train_df[[ref_col]],
                k = 5)
train_predict <- predict(knn_fit, train_df[, -1], type = "class")
confusionMatrix(data = train_df[[1]], reference = train_predict)
## Confusion Matrix and Statistics
## 
##            Reference
## Prediction  ambiguous resistant sensitive
##   ambiguous         3         0         3
##   resistant         2         1         2
##   sensitive         1         1        21
## 
## Overall Statistics
##                                         
##                Accuracy : 0.735         
##                  95% CI : (0.556, 0.871)
##     No Information Rate : 0.765         
##     P-Value [Acc > NIR] : 0.735         
##                                         
##                   Kappa : 0.402         
##                                         
##  Mcnemar's Test P-Value : 0.343         
## 
## Statistics by Class:
## 
##                      Class: ambiguous Class: resistant Class: sensitive
## Sensitivity                    0.5000           0.5000            0.808
## Specificity                    0.8929           0.8750            0.750
## Pos Pred Value                 0.5000           0.2000            0.913
## Neg Pred Value                 0.8929           0.9655            0.545
## Prevalence                     0.1765           0.0588            0.765
## Detection Rate                 0.0882           0.0294            0.618
## Detection Prevalence           0.1765           0.1471            0.676
## Balanced Accuracy              0.6964           0.6875            0.779
annotations <- train_df[[1]]
names(annotations) <- rownames(train_df)
names(train_predict) <- rownames(train_df)
summary(annotations == train_predict)
##    Mode   FALSE    TRUE 
## logical       9      25
test_predict <- predict(knn_fit, test_df, type = "class")
names(test_predict) <- rownames(test_df)

ref_result <- pData(both_snps)[[ref_col]]
names(ref_result) <- rownames(pData(both_snps))
ref_xref <- names(ref_result) %in% names(test_predict)
ref_comp <- ref_result[ref_xref]
ref_comp_idx <- ref_comp != "unknown"
ref_comp <- ref_comp[ref_comp_idx]
ref_comp_idx <- !is.na(ref_comp)
ref_comp <- ref_comp[ref_comp_idx]

test_comp_idx <- names(test_predict) %in% names(ref_comp)
test_comp <-test_predict[test_comp_idx]

agreement <- as.character(ref_comp) == as.character(test_comp)
agree_num <- sum(agreement)
agree_prop <- agree_num / length(ref_comp)
agree_prop
## [1] 0.7083
LS0tCnRpdGxlOiAiVE1SQzIgTUwgQ2xhc3NpZmljYXRpb24gb2Ygc3RyYWlucyB1c2luZyB2YXJpYW50czogMjAyMzAxIgphdXRob3I6ICJhdGIgYWJlbGV3QGdtYWlsLmNvbSIKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiBodG1sX2RvY3VtZW50OgogIGNvZGVfZG93bmxvYWQ6IHRydWUKICBjb2RlX2ZvbGRpbmc6IHNob3cKICBmaWdfY2FwdGlvbjogdHJ1ZQogIGZpZ19oZWlnaHQ6IDcKICBmaWdfd2lkdGg6IDcKICBoaWdobGlnaHQ6IGRlZmF1bHQKICBrZWVwX21kOiBmYWxzZQogIG1vZGU6IHNlbGZjb250YWluZWQKICBudW1iZXJfc2VjdGlvbnM6IHRydWUKICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQogIHRoZW1lOiByZWFkYWJsZQogIHRvYzogdHJ1ZQogIHRvY19mbG9hdDoKICAgY29sbGFwc2VkOiBmYWxzZQogICBzbW9vdGhfc2Nyb2xsOiBmYWxzZQotLS0KCjxzdHlsZT4KICBib2R5IC5tYWluLWNvbnRhaW5lciB7CiAgICBtYXgtd2lkdGg6IDE2MDBweDsKICB9Cjwvc3R5bGU+CgpgYGB7ciBvcHRpb25zLCBpbmNsdWRlID0gRkFMU0V9CmxpYnJhcnkoaHBnbHRvb2xzKQp0dCA8LSBkZXZ0b29sczo6bG9hZF9hbGwoIn4vaHBnbHRvb2xzIikKa25pdHI6Om9wdHNfa25pdCRzZXQocHJvZ3Jlc3MgPSBUUlVFLAogICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gVFJVRSwKICAgICAgICAgICAgICAgICAgICAgd2lkdGggPSA5MCwKICAgICAgICAgICAgICAgICAgICAgZWNobyA9IFRSVUUpCmtuaXRyOjpvcHRzX2NodW5rJHNldChlcnJvciA9IFRSVUUsCiAgICAgICAgICAgICAgICAgICAgICBmaWcud2lkdGggPSA4LAogICAgICAgICAgICAgICAgICAgICAgZmlnLmhlaWdodCA9IDgsCiAgICAgICAgICAgICAgICAgICAgICBkcGkgPSA5NikKb2xkX29wdGlvbnMgPC0gb3B0aW9ucyhkaWdpdHMgPSA0LAogICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICBrbml0ci5kdXBsaWNhdGUubGFiZWwgPSAiYWxsb3ciKQpnZ3Bsb3QyOjp0aGVtZV9zZXQoZ2dwbG90Mjo6dGhlbWVfYncoYmFzZV9zaXplID0gMTIpKQp2ZXIgPC0gIjIwMjMwMSIKcnVuZGF0ZSA8LSBmb3JtYXQoU3lzLkRhdGUoKSwgZm9ybWF0ID0gIiVZJW0lZCIpCgojIyB0bXAgPC0gdHJ5KHNtKGxvYWRtZShmaWxlbmFtZSA9IGdzdWIocGF0dGVybiA9ICJcXC5SbWQiLCByZXBsYWNlID0gIlxcLnJkYVxcLnh6IiwgeCA9IHByZXZpb3VzX2ZpbGUpKSkpCnJtZF9maWxlIDwtIGdsdWU6OmdsdWUoInRtcmMyX2dlbm9tZV9jbGFzc2lmaWVyX3t2ZXJ9LlJtZCIpCnNhdmVmaWxlIDwtIGdzdWIocGF0dGVybiA9ICJcXC5SbWQiLCByZXBsYWNlID0gIlxcLnJkYVxcLnh6IiwgeCA9IHJtZF9maWxlKQpsb2FkZWQgPC0gbG9hZChmaWxlPWdsdWU6OmdsdWUoInJkYS9ib3RoX3NucHMtdnt2ZXJ9LnJkYSIpKQpsaWJyYXJ5KGNhcmV0KQpgYGAKCiMgQ2hhbmdlbG9nCgoqIDIwMjMwMTE3OiBUcnlpbmcgdG8gdW5kZXJzdGFuZCBtZW1vcnkvdGltZSB1c2FnZSB3aGVuIEkgYWRkIGJhY2sgaW4KICB2YXJpYWJsZXMuICBFLmcuIG15IGxhc3QgaXRlcmF0aW9uIGxpbWl0ZWQgdGhlIG1vZGVsIHRvIGEgdmVyeSBzbWFsbAogIHBvcnRpb24gb2YgdGhlIG1vc3Qtc3RyYWluLWRldGVybWluaW5nIHZhcmlhbnQgcG9zaXRpb25zIGluIHRoZQogIGRhdGEuICBJbiBvcmRlciB0byBwcmVkaWN0IGF0dHJpYnV0ZXMgd2hpY2ggYXJlIGxlc3Mgb2J2aW91cywgSSB3aWxsCiAgbmVlZCB0byBiZSBhYmxlIHRvIHVzZSBhIGxhcmdlciBwb3J0aW9uIG9mIHRoZSBkYXRhLgoqIFNhbWUgZGF5LCBsYXRlcjogSSBtYW51YWxseSByZWNsYXNzaWZpZWQgc29tZSBvZiB0aGUgdHJpY2t5IHNhbXBsZXMKICB1c2luZyBJR1YgYW5kIGNvbXBhcmlzb25zIGFnYWluc3QgbXkgY2hhbmdpbmcgcGFuZWwgb2YgJ2Nhbm9uaWNhbCcKICBzdHJhaW5zLiAgSW4gdGhpcyBpdGVyYXRpb24sIEkgcmVjbGFzc2lmaWVkIGEgY291cGxlIG9mIG15CiAgcmVmZXJlbmNlcyBhbmQgc28gSSBob3BlIHRoYXQgaXQgd2lsbCBjbGFyaWZ5IGZ1dHVyZSBydW5zLgoKIyBDcmVhdGluZyBhIHN0cmFpbiBjbGFzc2lmaWVyIHVzaW5nIG91ciBkYXRhCgpJIGhhdmUgbm90IHlldCBpbXBsZW1lbnRlZCBhIHJlYWwgTUwgY2xhc3NpZmllciwgc28gSSB3aWxsIHVzZSBzb21lIG9mCnRoZSBjYXJldCBkb2N1bWVudGF0aW9uIGluIG9yZGVyIHRvIHdyaXRlIGEgdHJhaW5lciBhbmQgY2xhc3NpZmllciBvZgp0aGUgVE1SQzIgc3RyYWlucy4KCiMgU3BsaXR0aW5nIHRoZSBkYXRhIGJhc2VkIG9uIG91dGNvbWUKCkkgYW0gdGFraW5nIHRoaXMgcHJldHR5IG11Y2ggZGlyZWN0bHkgZnJvbToKCmh0dHBzOi8vdG9wZXBvLmdpdGh1Yi5pby9jYXJldC9kYXRhLXNwbGl0dGluZy5odG1sI3NpbXBsZS1zcGxpdHRpbmctYmFzZWQtb24tdGhlLW91dGNvbWUKCkluIG9yZGVyIHRvIHBlcmZvcm0gdGhlIHNwbGl0IGJ5IG91dGNvbWUsIEkgdGhpbmsgSSBuZWVkIHRvIHNsaWdodGx5CnJlZm9ybWF0IHRoZSBzdHJ1Y3R1cmUgb2Ygb3VyIHZhcmlhbnQgZGF0YSB0byBtb3JlIGNsb3NlbHkgbWF0Y2ggd2hhdApJIHNlZSBpbiB0aGUgZGF0YXNldHM6IGV0aXRhbmljL2lyaXMvU29uYXIuCgojIyBEZWZpbmUgdGhlIG91dGNvbWUgZmFjdG9yCgpTZXR0aW5nIHRoaXMgdG8gY2hhcmFjdGVyIGhlcmUgYmVjYXVzZSBJIHdpbGwgYmUgbWVzc2luZyB3aXRoIHRoZQpmYWN0b3IgbGV2ZWxzIGFuZCBJIHdvdWxkIHJhdGhlciBub3QgaGF2ZSB0byByZWxldmVsIG11bHRpcGxlIHRpbWVzLgoKTm90ZSB0aGF0IGluIHRoZSBwcmV2aW91cyByZXZpc2lvbiBvZiB0aGlzLCBJIHVzZWQgdGhlIGNvbHVtbgonenltb2RlbWVjYXRlZ29yaWNhbCcsIGJ1dCBub3cgSSBhbSB1c2luZyBrbm52MmNsYXNzaWZpY2F0aW9uLiAgSWYKdGhpbmdzIGNvbnRpbnVlIHRvIGNoYW5nZSwgSSBtYXkgbWFrZSBhIHYzIGNvbHVtbi4KCmBgYHtyIGNsYXNzaWZ5X3NucHN9CnJlZl9jb2wgPC0gImtubmhjbHVzdHRvZ2V0aGVyY2FsbCIKb3V0Y29tZV9mYWN0b3IgPC0gYXMuY2hhcmFjdGVyKHBEYXRhKGJvdGhfc25wcylbW3JlZl9jb2xdXSkKYGBgCgojIyBGaWd1cmUgb3V0IHdoeSBvbGQgc2FtcGxlcyB3ZXJlIGRyb3BwZWQuCgpJbiBteSBwcmV2aW91cyBhdHRlbXB0IGF0IHRoaXMsIEkgbm90aWNlZCB0aGF0IHRoZSBvbGRlciBzYW1wbGVzIHdlcmUKZ2V0dGluZyBkcm9wcGVkIGJ5IG5hLm9taXQsIEkgd2FudCB0byB0YWtlIGEgbW9tZW50IGhlcmUgdG8gZmlndXJlIG91dAp3aHkuCgpgYGB7ciBjaGVja19vbGRfc2FtcGxlc30KZGltKGV4cHJzKGJvdGhfc25wcykpCmJvdGhfbm9ybSA8LSBub3JtYWxpemVfZXhwdChib3RoX3NucHMsIHRyYW5zZm9ybT0ibG9nMiIsIG5vcm09InF1YW50IikKYGBgCgojIyBGaWx0ZXIgdGhlIGRhdGEgdXNpbmcgbmVhci16ZXJvIHZhcmlhbmNlCgpTdGVhbGluZyBub3cgZnJvbToKCmh0dHBzOi8vZ2l0aHViLmNvbS9jb21wZ2Vub21yL2Jvb2svYmxvYi9tYXN0ZXIvMDUtc3VwZXJ2aXNlZExlYXJuaW5nLlJtZAoKTXVsdGlwbGUgc2VjdGlvbnMgb2YgdGhlIGFib3ZlIGRvY3VtZW50IHByb3ZpZGUgbmljZSB3YXlzIHRvIHJlbW92ZQpyZWR1bmRhbnQgcHJlZGljdG9yIHZhcmlhYmxlcy4gIEZvciBteSBwdXJwb3NlcywgSSB3b3VsZCBsaWtlIHRvIGtlZXAKYWxsIG9mIHRoZW0gYmVjYXVzZSBJIGFtIGhvcGluZyB0byBtYWtlIHVzZSBvZiB0aGUgc2FtZSBtb2RlbCBpbiBhCmRhdGFzZXQgd2hpY2ggaXMgdmFzdGx5IHNwYXJzZXIgdGhhbiB0aGlzLiAgSG93ZXZlciwgZm9yIHRoZSBwdXJwb3NlcwpvZiBtZSBsZWFybmluZywgSSB3aWxsIGFwcGx5IGFsbCBvZiB0aGVzZSBtZXRob2RzIGFuZCB1c2UgaXQgb24gdGhlCnJlc3Qgb2YgdGhlIFRNUkMyIGRhdGEuCgpJbiBob3BlcyBvZiB1c2luZyBtb3JlIG9mIHRoZSBkYXRhLCBJIHdpbGwgcmVtb3ZlIHRoZSBTRCBmaWx0ZXIuCgpgYGB7ciBuenZ9CnRleHBycyA8LSB0KGV4cHJzKGJvdGhfbm9ybSkpCiMjIEdpdmVuIHRoaXMgbGFyZ2UgYW1vdW50IG9mIGRhdGEsIHRoaXMgc3RlcCBpcyBzbG93LCB0YWtpbmcgPiAxMCBtaW51dGVzLgpuenYgPC0gcHJlUHJvY2Vzcyh0ZXhwcnMsIG1ldGhvZD0ibnp2IiwgdW5pcXVlQ3V0PTE1KQpuenZfdGV4cHJzIDwtIHByZWRpY3Qobnp2LCB0ZXhwcnMpCmRpbShuenZfdGV4cHJzKQpgYGAKCiMjIEZpbHRlcmluZyB0byB0aGUgaGlnaGVzdCBzdGFuZGFyZCBkZXZpYXRpb24gdmFyaWFibGVzCgpGb3IgdGhlIG1vbWVudCwgSSBhbSBleGNsdWRpbmcgdGhlIGZvbGxvd2luZyBibG9jayBpbiBvcmRlciB0byBzZWUgaG93Cm11Y2ggdGltZS9tZW1vcnkga2VlcGluZyB0aGVzZSB2YXJpYWJsZXMgY29zdHMuICBJZiBJIHJlY2FsbCBwcm9wZXJseSwKdGhlIG1vZGVsIG9mIHRoZSB0b3AtMmsgdmFyaWFudCBwb3NpdGlvbnMgY29zdCB+IDEtNEcgb2YgbWVtb3J5LiAgSQpob3BlIHRoYXQgdGhpcyBzY2FsZXMgbGluZWFybHksIGJ1dCBJIGFtIHRoaW5raW5nIGl0IG1pZ2h0IG5vdC4KCmBgYHtyIGV4Y2x1ZGVfc2RzLCBldmFsPUZBTFNFfQpzdGFuZGFyZF9kZXZzIDwtIGFwcGx5KG56dl90ZXhwcnMsIDIsIHNkKQp0b3BfcHJlZGljdG9ycyA8LSBvcmRlcihzdGFuZGFyZF9kZXZzLCBkZWNyZWFzaW5nID0gVFJVRSlbMToyMDAwXQpuenZfdGV4cHJzIDwtIG56dl90ZXhwcnNbLCB0b3BfcHJlZGljdG9yc10KYGBgCgojIyBDZW50ZXIgdGhlIGRhdGEKCkkgdGhpbmsgY2VudGVyaW5nIG1heSBub3QgYmUgbmVlZGVkIGZvciB0aGlzIGRhdGEsIGJ1dCBoZXJlIGlzIGhvdzoKCmBgYHtyIGNlbnRlcn0Kbnp2X2NlbnRlciA8LSBwcmVQcm9jZXNzKG56dl90ZXhwcnMsIG1ldGhvZCA9ICJjZW50ZXIiKQpuenZfdGV4cHJzIDwtIHByZWRpY3Qobnp2X2NlbnRlciwgbnp2X3RleHBycykKYGBgCgojIyBEcm9wIGNvcnJlbGF0ZWQKCkluIHRoZSBzYW1lIGZhc2hpb24sIEkgd2FudCB0byBsZWF2ZSB0aGlzIG9mZiBiZWNhdXNlIGxhdGVyCmFwcGxpY2F0aW9ucyBvZiB0aGlzIG1vZGVsIHdpbGwgaW5jbHVkZSBsb3cgY292ZXJhZ2Ugc2FtcGxlcyB3aGljaCBtYXkKbm90IGhhdmUgZXZlcnkgdmFyaWFudCByZXByZXNlbnRlZC4KCmBgYHtyIGNvcnJlbGF0ZWQsIGV2YWw9RkFMU0V9Cm56dl9jb3JyZWxhdGVkIDwtIHByZVByb2Nlc3Mobnp2X3RleHBycywgbWV0aG9kID0gImNvcnIiLCBjdXRvZmYgPSAwLjkpCm56dl90ZXhwcnMgPC0gcHJlZGljdChuenZfY29ycmVsYXRlZCwgbnp2X3RleHBycykKZGltKG56dl90ZXhwcnMpCmFueU5BKG56dl90ZXhwcnMpCmBgYAoKIyMgUmVmb3JtYXQgdGhlIG1ldGFkYXRhIHRvIGJlIHVzZWQgYnkgY2FyZXQKCmBgYHtyIHJlZm9ybWF0X2RhdGF9Cm5hX2lkeCA8LSBpcy5uYShvdXRjb21lX2ZhY3RvcikKb3V0Y29tZV9mYWN0b3JbbmFfaWR4XSA8LSAidW5rbm93biIKbWV0YV9mYWN0b3JzIDwtIHBEYXRhKGJvdGhfc25wcylbLCBjKHJlZl9jb2wsICJjbGluaWNhbGNhdGVnb3JpY2FsIiwgInN1c19jYXRlZ29yeV9jdXJyZW50IildCm1ldGFfZmFjdG9yc1tbcmVmX2NvbF1dIDwtIG91dGNvbWVfZmFjdG9yCmBgYAoKIyMgQWRkIHRoZSBtZXRhZGF0YSB0byB0aGUgZmlsdGVyZWQgdmFyaWFibGVzCgpJbiBhZGRpdGlvbiwgSSB3YW50IG1ha2Ugc3VyZSB0aGF0IG5vIHVua25vd24gc2FtcGxlcyBhcmUgdXNlZCBpbiB0cmFpbmluZy4KCmBgYHtyIG1lcmdlX3BpZWNlc30Kc25wX2RmIDwtIGNiaW5kKG1ldGFfZmFjdG9ycywgbnp2X3RleHBycykKZGltKHNucF9kZikKc25wX3RyYWluX2lkeCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKG91dGNvbWVfZmFjdG9yLCBwID0gMC40LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXMgPSAxKQpzdW1tYXJ5KG91dGNvbWVfZmFjdG9yW3NucF90cmFpbl9pZHhdKQpzbnBfdHJhaW5fc3RhcnQgPC0gcm93bmFtZXMoc25wX2RmKVtzbnBfdHJhaW5faWR4XQp0cmFpbl9rbm93bl9pZHggPC0gbWV0YV9mYWN0b3JzW3NucF90cmFpbl9zdGFydCwgcmVmX2NvbF0gIT0gInVua25vd24iCnRyYWluX3Jvd25hbWVzIDwtIHNucF90cmFpbl9zdGFydFt0cmFpbl9rbm93bl9pZHhdCm5vdF90cmFpbl9pZHggPC0gISByb3duYW1lcyhzbnBfZGYpICVpbiUgdHJhaW5fcm93bmFtZXMKbm90X3RyYWluX3Jvd25hbWVzIDwtIHJvd25hbWVzKHNucF9kZilbbm90X3RyYWluX2lkeF0KCnRyYWluX2RmIDwtIHNucF9kZlt0cmFpbl9yb3duYW1lcywgXQp0cmFpbl9kZltbImNsaW5pY2FsY2F0ZWdvcmljYWwiXV0gPC0gTlVMTAp0cmFpbl9kZltbInN1c19jYXRlZ29yeV9jdXJyZW50Il1dIDwtIE5VTEwKdHJhaW5fZGZbWzFdXSA8LSBhcy5mYWN0b3IoYXMuY2hhcmFjdGVyKHRyYWluX2RmW1sxXV0pKQp0ZXN0X2RmIDwtIHNucF9kZltub3RfdHJhaW5fcm93bmFtZXMsIF0KdGVzdF9kZltbcmVmX2NvbF1dIDwtIE5VTEwKdGVzdF9kZltbImNsaW5pY2FsY2F0ZWdvcmljYWwiXV0gPC0gTlVMTAp0ZXN0X2RmW1sic3VzX2NhdGVnb3J5X2N1cnJlbnQiXV0gPC0gTlVMTApgYGAKCiMjIEtOTgoKUGVyZm9ybSB0aGUgZml0IGFuZCB0ZXN0IHRoZSB0cmFpbmluZyBzZXQgYWdhaW5zdCBpdHNlbGYuCgpgYGB7ciBrbm59Cmtubl9maXQgPC0ga25uMyh4ID0gdHJhaW5fZGZbLCAtMV0sCiAgICAgICAgICAgICAgICB5ID0gdHJhaW5fZGZbW3JlZl9jb2xdXSwKICAgICAgICAgICAgICAgIGsgPSA1KQp0cmFpbl9wcmVkaWN0IDwtIHByZWRpY3Qoa25uX2ZpdCwgdHJhaW5fZGZbLCAtMV0sIHR5cGUgPSAiY2xhc3MiKQpjb25mdXNpb25NYXRyaXgoZGF0YSA9IHRyYWluX2RmW1sxXV0sIHJlZmVyZW5jZSA9IHRyYWluX3ByZWRpY3QpCgphbm5vdGF0aW9ucyA8LSB0cmFpbl9kZltbMV1dCm5hbWVzKGFubm90YXRpb25zKSA8LSByb3duYW1lcyh0cmFpbl9kZikKbmFtZXModHJhaW5fcHJlZGljdCkgPC0gcm93bmFtZXModHJhaW5fZGYpCnN1bW1hcnkoYW5ub3RhdGlvbnMgPT0gdHJhaW5fcHJlZGljdCkKYGBgCgpJdCBsb29rcyBsaWtlIG15IGtubiBtb2RlbCBoYXMgc29tZSBkaXNhZ3JlZW1lbnRzIGluIHRoZSBhbHJlYWR5CmRpZmZpY3VsdCBjYXNlcyBvZiBwb29ybHkgcmVwcmVzZW50ZWQgc3RyYWlucywgc28gSSB3aWxsIGNhbGwgaXQgYQp3aW4uICBFLmcgaXQgYXBwZWFycyB0byBmYWlsIG9uIGEgejEuMCwgYjI5MDQsIHozLjAsIHoyLjAsIGEgMi4xLCBhbmQKMi40LgoKTm93IGxldHMgdHJ5IGl0IG91dCBvbiB0aGUgcmVzdCBvZiB0aGUgZGF0YS4KCmBgYHtyIGtubl90ZXN0fQp0ZXN0X3ByZWRpY3QgPC0gcHJlZGljdChrbm5fZml0LCB0ZXN0X2RmLCB0eXBlID0gImNsYXNzIikKbmFtZXModGVzdF9wcmVkaWN0KSA8LSByb3duYW1lcyh0ZXN0X2RmKQp0ZXN0X3ByZWRpY3QKCnJlZl9yZXN1bHQgPC0gcERhdGEoYm90aF9zbnBzKVtbcmVmX2NvbF1dCm5hbWVzKHJlZl9yZXN1bHQpIDwtIHJvd25hbWVzKHBEYXRhKGJvdGhfc25wcykpCgpzaGFyZWQgPC0gbmFtZXMocmVmX3Jlc3VsdCkgJWluJSBuYW1lcyh0ZXN0X3ByZWRpY3QpCmNvbXBhcmUgPC0gcmVmX3Jlc3VsdFtzaGFyZWRdCmNvbXBhcmUgPC0gY29tcGFyZVshaXMubmEoY29tcGFyZSldCnRlc3RfcHJlZGljdFtjb21wYXJlICE9IHRlc3RfcHJlZGljdFtuYW1lcyhjb21wYXJlKV1dCmBgYAoKIyBMb29rIGZvciBnZW5vbWljIHN1c2NlcHRpYmlsaXR5IG1hcmtlcnMKCmBgYHtyIHJlZm9ybWF0X2RhdGFfc3VzfQpyZWZfY29sIDwtICJzdXNfY2F0ZWdvcnlfY3VycmVudCIKb3V0Y29tZV9mYWN0b3IgPC0gYXMuY2hhcmFjdGVyKHBEYXRhKGJvdGhfc25wcylbW3JlZl9jb2xdXSkKbmFfaWR4IDwtIGlzLm5hKG91dGNvbWVfZmFjdG9yKQpvdXRjb21lX2ZhY3RvcltuYV9pZHhdIDwtICJ1bmtub3duIgptZXRhX2ZhY3RvcnMgPC0gcERhdGEoYm90aF9zbnBzKVssIGMocmVmX2NvbCwgImNsaW5pY2FsY2F0ZWdvcmljYWwiLCAic3VzX2NhdGVnb3J5X2N1cnJlbnQiKV0KbWV0YV9mYWN0b3JzW1tyZWZfY29sXV0gPC0gYXMuZmFjdG9yKG91dGNvbWVfZmFjdG9yKQoKc25wX2RmIDwtIGNiaW5kKG1ldGFfZmFjdG9ycywgbnp2X3RleHBycykKZGltKHNucF9kZikKc25wX3RyYWluX2lkeCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKG91dGNvbWVfZmFjdG9yLCBwID0gMC40LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGltZXMgPSAxKQpzdW1tYXJ5KG91dGNvbWVfZmFjdG9yW3NucF90cmFpbl9pZHhdKQpzbnBfdHJhaW5fc3RhcnQgPC0gcm93bmFtZXMoc25wX2RmKVtzbnBfdHJhaW5faWR4XQp0cmFpbl9rbm93bl9pZHggPC0gbWV0YV9mYWN0b3JzW3NucF90cmFpbl9zdGFydCwgcmVmX2NvbF0gIT0gInVua25vd24iCnRyYWluX3Jvd25hbWVzIDwtIHNucF90cmFpbl9zdGFydFt0cmFpbl9rbm93bl9pZHhdCm5vdF90cmFpbl9pZHggPC0gISByb3duYW1lcyhzbnBfZGYpICVpbiUgdHJhaW5fcm93bmFtZXMKbm90X3RyYWluX3Jvd25hbWVzIDwtIHJvd25hbWVzKHNucF9kZilbbm90X3RyYWluX2lkeF0KCnRyYWluX2RmIDwtIHNucF9kZlt0cmFpbl9yb3duYW1lcywgXQp0cmFpbl9kZltbImNsaW5pY2FsY2F0ZWdvcmljYWwiXV0gPC0gTlVMTAp0cmFpbl9kZltbInN1c19jYXRlZ29yeV9jdXJyZW50LjEiXV0gPC0gTlVMTAp0cmFpbl9kZltbMV1dIDwtIGFzLmZhY3Rvcihhcy5jaGFyYWN0ZXIodHJhaW5fZGZbWzFdXSkpCnRlc3RfZGYgPC0gc25wX2RmW25vdF90cmFpbl9yb3duYW1lcywgXQp0ZXN0X2RmW1tyZWZfY29sXV0gPC0gTlVMTAp0ZXN0X2RmW1siY2xpbmljYWxjYXRlZ29yaWNhbCJdXSA8LSBOVUxMCnRlc3RfZGZbWyJzdXNfY2F0ZWdvcnlfY3VycmVudC4xIl1dIDwtIE5VTEwKdGVzdF9kZltbInN1c19jYXRlZ29yeV9jdXJyZW50Il1dIDwtIE5VTEwKCmtubl9maXQgPC0ga25uMyh4ID0gdHJhaW5fZGZbLCAtMV0sCiAgICAgICAgICAgICAgICB5ID0gdHJhaW5fZGZbW3JlZl9jb2xdXSwKICAgICAgICAgICAgICAgIGsgPSA1KQp0cmFpbl9wcmVkaWN0IDwtIHByZWRpY3Qoa25uX2ZpdCwgdHJhaW5fZGZbLCAtMV0sIHR5cGUgPSAiY2xhc3MiKQpjb25mdXNpb25NYXRyaXgoZGF0YSA9IHRyYWluX2RmW1sxXV0sIHJlZmVyZW5jZSA9IHRyYWluX3ByZWRpY3QpCgphbm5vdGF0aW9ucyA8LSB0cmFpbl9kZltbMV1dCm5hbWVzKGFubm90YXRpb25zKSA8LSByb3duYW1lcyh0cmFpbl9kZikKbmFtZXModHJhaW5fcHJlZGljdCkgPC0gcm93bmFtZXModHJhaW5fZGYpCnN1bW1hcnkoYW5ub3RhdGlvbnMgPT0gdHJhaW5fcHJlZGljdCkKCnRlc3RfcHJlZGljdCA8LSBwcmVkaWN0KGtubl9maXQsIHRlc3RfZGYsIHR5cGUgPSAiY2xhc3MiKQpuYW1lcyh0ZXN0X3ByZWRpY3QpIDwtIHJvd25hbWVzKHRlc3RfZGYpCgpyZWZfcmVzdWx0IDwtIHBEYXRhKGJvdGhfc25wcylbW3JlZl9jb2xdXQpuYW1lcyhyZWZfcmVzdWx0KSA8LSByb3duYW1lcyhwRGF0YShib3RoX3NucHMpKQpyZWZfeHJlZiA8LSBuYW1lcyhyZWZfcmVzdWx0KSAlaW4lIG5hbWVzKHRlc3RfcHJlZGljdCkKcmVmX2NvbXAgPC0gcmVmX3Jlc3VsdFtyZWZfeHJlZl0KcmVmX2NvbXBfaWR4IDwtIHJlZl9jb21wICE9ICJ1bmtub3duIgpyZWZfY29tcCA8LSByZWZfY29tcFtyZWZfY29tcF9pZHhdCnJlZl9jb21wX2lkeCA8LSAhaXMubmEocmVmX2NvbXApCnJlZl9jb21wIDwtIHJlZl9jb21wW3JlZl9jb21wX2lkeF0KCnRlc3RfY29tcF9pZHggPC0gbmFtZXModGVzdF9wcmVkaWN0KSAlaW4lIG5hbWVzKHJlZl9jb21wKQp0ZXN0X2NvbXAgPC10ZXN0X3ByZWRpY3RbdGVzdF9jb21wX2lkeF0KCmFncmVlbWVudCA8LSBhcy5jaGFyYWN0ZXIocmVmX2NvbXApID09IGFzLmNoYXJhY3Rlcih0ZXN0X2NvbXApCmFncmVlX251bSA8LSBzdW0oYWdyZWVtZW50KQphZ3JlZV9wcm9wIDwtIGFncmVlX251bSAvIGxlbmd0aChyZWZfY29tcCkKYWdyZWVfcHJvcApgYGAK