Setup

Load packages.

library(SpiecEasi)
library(Matrix)
library(reshape2)
library(igraph)
# library(parallel)
devtools::install_github("berryni/mDINGO")
* installing *source* package ‘mDINGO’ ...
** R
** data
** preparing package for lazy loading
** help
*** installing help indices
** building package indices
** testing if installed package can be loaded
* DONE (mDINGO)
library(mDINGO)
availcores = 1#parallel::detectCores() - 1

Load data from american gut project. This is a very small subset of the dataset to make this run quickly for testing purposes.

data(amgut1.filt)
depths <- rowSums(amgut1.filt)
amgut1.filt.n <- t(apply(amgut1.filt, 1, norm_to_total))
amgut1 <- round(amgut1.filt.n * min(depths))
p = dim(amgut1)[2]
n = dim(amgut1)[1]

The dimensions of this data are (289, 127).

To show it has been filtered, a summary of the row sums

print(summary(rowSums(amgut1)))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  11989   12006   12009   12009   12012   12024 

And the column sums:

print(summary(colSums(amgut1)))
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
    538    2670    7098   27327   16572 1148740 

Now, we’ll generate a dataset using a form of the code from the SpiecEasi demo. Unlike that demo, we will generate two different datasets, one for each level of the covariate, and combine them to form a covariate dependent dataset.

Generate Data

Create underlying graphs

Use function from mDINGO to create underlying graph structure. Then plot those graphs. Looking to use SpiecEasi to recreate Global Graph.

graphList = gen_graphs_sf(p, 1, .6, .05)
l_fr = layout_with_fr(graphList$disease)
par(mfrow = c(2,2), mar=c(.5,.5,1,.5))
plot(graphList$disease, vertex.size= 0, main = "Disease Graph", layout = l_fr, vertex.label = NA)
plot(graphList$change, vertex.size= 0, main = "Change Graph", layout = l_fr, vertex.label = NA)
plot(graphList$control, vertex.size= 0, main = "Control Graph", layout = l_fr, vertex.label = NA)
plot(graphList$global, vertex.size= 0, main = "Global Graph", layout = l_fr, vertex.label = NA)

Generate Data using Graphs

Use built in SpiecEasi functions to create datasets from the underlying graphs using our imported dataset as example. The posThetaLims option sets the strenght (weight) of the connections. We’ll make them easy to detect for testing purposes.

prec_dis = igraph2prec(graphList$disease)
cor_dis = cov2cor(prec2cov(prec_dis))
prec_con = igraph2prec(graphList$control)
cor_con = cov2cor(prec2cov(prec_con))
Y_dis = synth_comm_from_counts(amgut1, mar=2, distr='zinegbin', Sigma=cor_dis, n=n)
Y_con = synth_comm_from_counts(amgut1, mar=2, distr='zinegbin', Sigma=cor_con, n=n)
Y = rbind(Y_dis, Y_con)
colnames(Y) = paste0("OTU", 1:dim(Y)[2])

Assign the levels of covariate to vector X

X = as.factor(c(rep(c(1,-1), each = dim(Y)[1]/2)))

We now have a dataset in which the first 289 subjects are in the disease group and the last 289 subjects are controls.

Global Estimation

We now use SpiecEasi to perform global estimation. Purpose of this is to get the graph that the two groups have in common so we can remove that graph and later estimate the differences between the group graphs. This is the estimation of G from the dingo paper.

seRes = spiec.easi(Y, verbose = TRUE, sel.criterion = 'ebic')
Normalizing/clr transformation of data with pseudocount ...
Inverse Covariance Estimation with glasso ...
Model selection with ebic ...
Done!

We used the extended BIC model selection strategy, rather than the default ``Stars’’ method. This could be changed, but we have a clearer idea of what eBIC is doing, so we use it here for demonstrative purposes.

temp = rbind(round(seRes$lambda, 4), round(seRes$ebic.score, 0), round(seRes$sparsity, 2))
row.names(temp) = c("Lambda", "eBIC", "Sparsity")
knitr::kable(temp,  col.names = as.character(1:dim(temp)[2]))

1 2 3 4 5 6 7 8 9 10
Lambda 0.4184 1.942e-01 0.0901 4.180e-02 0.0194 0.009 0.0042 0.0019 9.000e-04 0.0004
eBIC 73406.0000 7.456e+04 75829.0000 9.539e+04 132431.0000 161923.000 179351.0000 187441.0000 1.904e+05 191106.0000
Sparsity 0.0000 1.000e-02 0.0400 2.100e-01 0.5200 0.760 0.9000 0.9600 9.900e-01 1.0000

huge::huge.roc(seRes$path, as.matrix(graphList$global[]))
Computing F1 scores, false positive rates and true positive rates....done.
True Postive Rate: from 0 to 1 
False Positive Rate: from 0 to 0.9988625 
Area under Curve: 0.9885659 
Maximum F1 Score: 0.9729656 

The chosen lambda is the first column in this case. This gives us a Sparcity of 0, meaning that there are no connections in the global graph. Below we compare the global basis graph (truth) with the estimated global graph.

se_igraph = adj2igraph(as.matrix(seRes$refit))
par(mfrow = c(1,2), mar=c(1, 1, 1, 1) + 0.1)
plot(graphList$global, layout = l_fr, vertex.size= 0, vertex.label=NA, main = "Global Basis Graph")
plot(se_igraph, layout= l_fr, vertex.size= 0, vertex.label=NA, main = "Global Spiec-Easi Inferred Graph")

As stated, there are no connections in the estimated global graph (sparsity is 0).

Lets look at what happens if we take the tuning parameter 1 step smaller than the chosen one (column 2 in the table above).

se_igraph = adj2igraph(as.matrix(seRes$path[[2]]))
par(mfrow = c(1,2), mar=c(1, 1, 1, 1) + 0.1)
plot(graphList$global, layout = l_fr, vertex.size= 0, vertex.label=NA, main = "Global Basis Graph")
plot(se_igraph, layout= l_fr, vertex.size= 0, vertex.label=NA, main = "Global Spiec-Easi Inferred Graph")

There are way too many connections in this inferred graph. We need somewhere in the middle. The current steps in the process are too big.

Preliminarily, lets try to add more points to the grid search of SpiecEasi and see if that does better.

seFine = spiec.easi(Y, nlambda = 50, verbose = TRUE, sel.criterion = 'ebic')
Normalizing/clr transformation of data with pseudocount ...
Inverse Covariance Estimation with glasso ...
Model selection with ebic ...
Done!
temp = rbind(round(seFine$lambda, 4), round(seFine$ebic.score, 0), round(seFine$sparsity, 2))
row.names(temp) = c("Lambda", "eBIC", "Sparsity")
knitr::kable(temp,  col.names = as.character(1:dim(temp)[2]))

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
Lambda 0.4184 0.3634 0.3156 0.2741 0.2381 0.2068 0.1796 0.156 0.1355 0.1176 0.1022 0.0887 0.0771 0.0669 0.0581 0.0505 0.0439 0.0381 0.0331 0.0287 0.025 0.0217 0.0188 0.0163 0.0142 0.0123 0.0107 0.0093 0.0081 0.007 0.0061 0.0053 0.0046 0.004 0.0035 0.003 0.0026 0.0023 0.002 0.0017 0.0015 0.0013 0.0011 0.001 0.0008 0.0007 0.0006 0.0006 0.0005 0.0004
eBIC 73406.0000 73524.0000 73698.0000 73833.0000 74123.0000 74354.0000 74442.0000 74327.000 74234.0000 74552.0000 75136.0000 76052.0000 77633.0000 79772.0000 83548.0000 87646.0000 93083.0000 99841.0000 106332.0000 112908.0000 120375.000 127128.0000 134044.0000 140655.0000 146785.0000 151521.0000 156606.0000 161093.0000 164915.0000 168821.000 172318.0000 174947.0000 177884.0000 180102.000 181991.0000 183414.000 184721.0000 186111.0000 187431.000 187958.0000 188775.0000 189278.0000 189556.0000 190057.000 190293.0000 190805.0000 190826.0000 191124.0000 190937.0000 191106.0000
Sparsity 0.0000 0.0000 0.0000 0.0000 0.0100 0.0100 0.0100 0.020 0.0200 0.0300 0.0300 0.0400 0.0600 0.0800 0.1100 0.1500 0.1900 0.2500 0.3000 0.3600 0.420 0.4700 0.5300 0.5900 0.6400 0.6700 0.7200 0.7500 0.7800 0.810 0.8400 0.8600 0.8900 0.910 0.9200 0.930 0.9400 0.9500 0.960 0.9700 0.9800 0.9800 0.9800 0.990 0.9900 0.9900 1.0000 1.0000 1.0000 1.0000

huge::huge.roc(seFine$path, as.matrix(graphList$global[]))
Computing F1 scores, false positive rates and true positive rates....done.
True Postive Rate: from 0 to 1 
False Positive Rate: from 0 to 0.9988625 
Area under Curve: 0.9913756 
Maximum F1 Score: 0.9812361 

This is again the sparsity 0 model, so the inferred global graph has no edges. Looking at the ROC, we see potentially better models, so lets look at model 6, for examples sake.

seFine_igraph = adj2igraph(as.matrix(seFine$path[[6]]))
par(mfrow = c(1,2), mar=c(1, 1, 1, 1) + 0.1)
plot(graphList$global, layout = l_fr, vertex.size= 0, vertex.label=NA, main = "Global Basis Graph")
plot(seFine_igraph, layout= l_fr, vertex.size= 0, vertex.label=NA, main = "Global Spiec-Easi Inferred Graph")

Stars Model Selection

Do model selection with Stars instead. Stars resamples and refits to get edge importance metrics, and chooses sparsity metric of best network. Should be related to most predictive graph.

seFine2 = spiec.easi(Y, method = 'glasso', lambda.min.ratio=.01, nlambda = 50)
Normalizing/clr transformation of data with pseudocount ...
Inverse Covariance Estimation with glasso ...
Model selection with stars ...
Done!
#temp = rbind(round(seFine2$lambda, 4), round(seFine2$ebic.score), round(seFine2$sparsity, 2))
#row.names(temp) = c("Lambda", "eBIC", "Sparsity")
#knitr::kable(temp,  col.names = as.character(1:dim(temp)[2]))
huge::huge.roc(seFine2$path, as.matrix(graphList$global[]))
Computing F1 scores, false positive rates and true positive rates....done.
True Postive Rate: from 0 to 1 
False Positive Rate: from 0 to 0.8979398 
Area under Curve: 0.8905552 
Maximum F1 Score: 0.9824202 

Stars suggests choosing the 0.1634711 sparsity parameter, which is the 11 model in the ROC path.

Lets look at the graphs created and see if they do a better job at matching the global basis graph.

se_igraph = adj2igraph(as.matrix(seFine2$refit))
par(mfrow = c(1,2), mar=c(1, 1, 1, 1) + 0.1)
plot(graphList$global, layout = l_fr, vertex.size= 0, vertex.label=NA, main = "Global Basis Graph")
plot(se_igraph, layout= l_fr, vertex.size= 0, vertex.label=NA, main = "Global Spiec-Easi Inferred Graph")

INstead of choosing that parameter and worrying, we can use the default model selection tool, called Stars. Stars resamples and refits to get edge importance metrics, and chooses sparsity metric of best network. Should be related to most predictive graph.

LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgU2V0dXAKCkxvYWQgcGFja2FnZXMuCgpgYGB7ciBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KFNwaWVjRWFzaSkKbGlicmFyeShNYXRyaXgpCmxpYnJhcnkocmVzaGFwZTIpCmxpYnJhcnkoaWdyYXBoKQojIGxpYnJhcnkocGFyYWxsZWwpCmRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigiYmVycnluaS9tRElOR08iKQpsaWJyYXJ5KG1ESU5HTykKCmF2YWlsY29yZXMgPSAxI3BhcmFsbGVsOjpkZXRlY3RDb3JlcygpIC0gMQpgYGAKCkxvYWQgZGF0YSBmcm9tIGFtZXJpY2FuIGd1dCBwcm9qZWN0LiBUaGlzIGlzIGEgKnZlcnkqIHNtYWxsIHN1YnNldCBvZiB0aGUgZGF0YXNldCB0byBtYWtlIHRoaXMgcnVuIHF1aWNrbHkgZm9yIHRlc3RpbmcgcHVycG9zZXMuCmBgYHtyfQpkYXRhKGFtZ3V0MS5maWx0KQpkZXB0aHMgPC0gcm93U3VtcyhhbWd1dDEuZmlsdCkKYW1ndXQxLmZpbHQubiA8LSB0KGFwcGx5KGFtZ3V0MS5maWx0LCAxLCBub3JtX3RvX3RvdGFsKSkKYW1ndXQxIDwtIHJvdW5kKGFtZ3V0MS5maWx0Lm4gKiBtaW4oZGVwdGhzKSkKCgpwID0gZGltKGFtZ3V0MSlbMl0KbiA9IGRpbShhbWd1dDEpWzFdCmBgYApUaGUgZGltZW5zaW9ucyBvZiB0aGlzIGRhdGEgYXJlIChgciBkaW0oYW1ndXQxKWApLgoKVG8gc2hvdyBpdCBoYXMgYmVlbiBmaWx0ZXJlZCwgYSBzdW1tYXJ5IG9mIHRoZSByb3cgc3VtcwpgYGB7cn0KcHJpbnQoc3VtbWFyeShyb3dTdW1zKGFtZ3V0MSkpKQpgYGAKCkFuZCB0aGUgY29sdW1uIHN1bXM6CmBgYHtyfQpwcmludChzdW1tYXJ5KGNvbFN1bXMoYW1ndXQxKSkpCmBgYAoKTm93LCB3ZSdsbCBnZW5lcmF0ZSBhIGRhdGFzZXQgdXNpbmcgYSBmb3JtIG9mIHRoZSBjb2RlIGZyb20gdGhlIFNwaWVjRWFzaSBkZW1vLiBVbmxpa2UgdGhhdCBkZW1vLCB3ZSB3aWxsIGdlbmVyYXRlIHR3byBkaWZmZXJlbnQgZGF0YXNldHMsIG9uZSBmb3IgZWFjaCBsZXZlbCBvZiB0aGUgY292YXJpYXRlLCBhbmQgY29tYmluZSB0aGVtIHRvIGZvcm0gYSBjb3ZhcmlhdGUgZGVwZW5kZW50IGRhdGFzZXQuCgojIyBHZW5lcmF0ZSBEYXRhCgojIyMgQ3JlYXRlIHVuZGVybHlpbmcgZ3JhcGhzCgpVc2UgZnVuY3Rpb24gZnJvbSBtRElOR08gdG8gY3JlYXRlIHVuZGVybHlpbmcgZ3JhcGggc3RydWN0dXJlLiBUaGVuIHBsb3QgdGhvc2UgZ3JhcGhzLiBMb29raW5nIHRvIHVzZSBTcGllY0Vhc2kgdG8gcmVjcmVhdGUgR2xvYmFsIEdyYXBoLgoKYGBge3J9CmdyYXBoTGlzdCA9IGdlbl9ncmFwaHNfc2YocCwgMSwgLjYsIC4wNSkKbF9mciA9IGxheW91dF93aXRoX2ZyKGdyYXBoTGlzdCRkaXNlYXNlKQpwYXIobWZyb3cgPSBjKDIsMiksIG1hcj1jKC41LC41LDEsLjUpKQpwbG90KGdyYXBoTGlzdCRkaXNlYXNlLCB2ZXJ0ZXguc2l6ZT0gMCwgbWFpbiA9ICJEaXNlYXNlIEdyYXBoIiwgbGF5b3V0ID0gbF9mciwgdmVydGV4LmxhYmVsID0gTkEpCnBsb3QoZ3JhcGhMaXN0JGNoYW5nZSwgdmVydGV4LnNpemU9IDAsIG1haW4gPSAiQ2hhbmdlIEdyYXBoIiwgbGF5b3V0ID0gbF9mciwgdmVydGV4LmxhYmVsID0gTkEpCnBsb3QoZ3JhcGhMaXN0JGNvbnRyb2wsIHZlcnRleC5zaXplPSAwLCBtYWluID0gIkNvbnRyb2wgR3JhcGgiLCBsYXlvdXQgPSBsX2ZyLCB2ZXJ0ZXgubGFiZWwgPSBOQSkKcGxvdChncmFwaExpc3QkZ2xvYmFsLCB2ZXJ0ZXguc2l6ZT0gMCwgbWFpbiA9ICJHbG9iYWwgR3JhcGgiLCBsYXlvdXQgPSBsX2ZyLCB2ZXJ0ZXgubGFiZWwgPSBOQSkKYGBgCgoKIyMjIEdlbmVyYXRlIERhdGEgdXNpbmcgR3JhcGhzCgpVc2UgYnVpbHQgaW4gU3BpZWNFYXNpIGZ1bmN0aW9ucyB0byBjcmVhdGUgZGF0YXNldHMgZnJvbSB0aGUgdW5kZXJseWluZyBncmFwaHMgdXNpbmcgb3VyIGltcG9ydGVkIGRhdGFzZXQgYXMgZXhhbXBsZS4gVGhlIHBvc1RoZXRhTGltcyBvcHRpb24gc2V0cyB0aGUgc3RyZW5naHQgKHdlaWdodCkgb2YgdGhlIGNvbm5lY3Rpb25zLiBXZSdsbCBtYWtlIHRoZW0gZWFzeSB0byBkZXRlY3QgZm9yIHRlc3RpbmcgcHVycG9zZXMuCgpgYGB7cn0KcHJlY19kaXMgPSBpZ3JhcGgycHJlYyhncmFwaExpc3QkZGlzZWFzZSkKY29yX2RpcyA9IGNvdjJjb3IocHJlYzJjb3YocHJlY19kaXMpKQoKcHJlY19jb24gPSBpZ3JhcGgycHJlYyhncmFwaExpc3QkY29udHJvbCkKY29yX2NvbiA9IGNvdjJjb3IocHJlYzJjb3YocHJlY19jb24pKQoKWV9kaXMgPSBzeW50aF9jb21tX2Zyb21fY291bnRzKGFtZ3V0MSwgbWFyPTIsIGRpc3RyPSd6aW5lZ2JpbicsIFNpZ21hPWNvcl9kaXMsIG49bikKWV9jb24gPSBzeW50aF9jb21tX2Zyb21fY291bnRzKGFtZ3V0MSwgbWFyPTIsIGRpc3RyPSd6aW5lZ2JpbicsIFNpZ21hPWNvcl9jb24sIG49bikKWSA9IHJiaW5kKFlfZGlzLCBZX2NvbikKY29sbmFtZXMoWSkgPSBwYXN0ZTAoIk9UVSIsIDE6ZGltKFkpWzJdKQpgYGAKCkFzc2lnbiB0aGUgbGV2ZWxzIG9mIGNvdmFyaWF0ZSB0byB2ZWN0b3IgYFhgCmBgYHtyfQpYID0gYXMuZmFjdG9yKGMocmVwKGMoMSwtMSksIGVhY2ggPSBkaW0oWSlbMV0vMikpKQpgYGAKCldlIG5vdyBoYXZlIGEgZGF0YXNldCBpbiB3aGljaCB0aGUgZmlyc3QgYHIgbGVuZ3RoKFgpLzJgIHN1YmplY3RzIGFyZSBpbiB0aGUgZGlzZWFzZSBncm91cCBhbmQgdGhlIGxhc3QgYHIgbGVuZ3RoKFgpLzJgIHN1YmplY3RzIGFyZSBjb250cm9scy4KCiMjIEdsb2JhbCBFc3RpbWF0aW9uCgpXZSBub3cgdXNlIFNwaWVjRWFzaSB0byBwZXJmb3JtIGdsb2JhbCBlc3RpbWF0aW9uLiBQdXJwb3NlIG9mIHRoaXMgaXMgdG8gZ2V0IHRoZSBncmFwaCB0aGF0IHRoZSB0d28gZ3JvdXBzIGhhdmUgaW4gY29tbW9uIHNvIHdlIGNhbiByZW1vdmUgdGhhdCBncmFwaCBhbmQgbGF0ZXIgZXN0aW1hdGUgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIGdyb3VwIGdyYXBocy4gVGhpcyBpcyB0aGUgZXN0aW1hdGlvbiBvZiBHIGZyb20gdGhlIGRpbmdvIHBhcGVyLgoKYGBge3J9CnNlUmVzID0gc3BpZWMuZWFzaShZLCB2ZXJib3NlID0gVFJVRSwgc2VsLmNyaXRlcmlvbiA9ICdlYmljJykKYGBgCldlIHVzZWQgdGhlIGV4dGVuZGVkIEJJQyBtb2RlbCBzZWxlY3Rpb24gc3RyYXRlZ3ksIHJhdGhlciB0aGFuIHRoZSBkZWZhdWx0IGBgU3RhcnMnJyBtZXRob2QuIFRoaXMgY291bGQgYmUgY2hhbmdlZCwgYnV0IHdlIGhhdmUgYSBjbGVhcmVyIGlkZWEgb2Ygd2hhdCBlQklDIGlzIGRvaW5nLCBzbyB3ZSB1c2UgaXQgaGVyZSBmb3IgZGVtb25zdHJhdGl2ZSBwdXJwb3Nlcy4KCmBgYHtyfQp0ZW1wID0gcmJpbmQocm91bmQoc2VSZXMkbGFtYmRhLCA0KSwgcm91bmQoc2VSZXMkZWJpYy5zY29yZSwgMCksIHJvdW5kKHNlUmVzJHNwYXJzaXR5LCAyKSkKcm93Lm5hbWVzKHRlbXApID0gYygiTGFtYmRhIiwgImVCSUMiLCAiU3BhcnNpdHkiKQprbml0cjo6a2FibGUodGVtcCwgIGNvbC5uYW1lcyA9IGFzLmNoYXJhY3RlcigxOmRpbSh0ZW1wKVsyXSkpCgpodWdlOjpodWdlLnJvYyhzZVJlcyRwYXRoLCBhcy5tYXRyaXgoZ3JhcGhMaXN0JGdsb2JhbFtdKSkKYGBgCgpUaGUgY2hvc2VuIGxhbWJkYSBpcyB0aGUgZmlyc3QgY29sdW1uIGluIHRoaXMgY2FzZS4gVGhpcyBnaXZlcyB1cyBhIFNwYXJjaXR5IG9mIDAsIG1lYW5pbmcgdGhhdCB0aGVyZSBhcmUgbm8gY29ubmVjdGlvbnMgaW4gdGhlIGdsb2JhbCBncmFwaC4gQmVsb3cgd2UgY29tcGFyZSB0aGUgZ2xvYmFsIGJhc2lzIGdyYXBoICh0cnV0aCkgd2l0aCB0aGUgZXN0aW1hdGVkIGdsb2JhbCBncmFwaC4KCmBgYHtyfQpzZV9pZ3JhcGggPSBhZGoyaWdyYXBoKGFzLm1hdHJpeChzZVJlcyRyZWZpdCkpCgpwYXIobWZyb3cgPSBjKDEsMiksIG1hcj1jKDEsIDEsIDEsIDEpICsgMC4xKQpwbG90KGdyYXBoTGlzdCRnbG9iYWwsIGxheW91dCA9IGxfZnIsIHZlcnRleC5zaXplPSAwLCB2ZXJ0ZXgubGFiZWw9TkEsIG1haW4gPSAiR2xvYmFsIEJhc2lzIEdyYXBoIikKcGxvdChzZV9pZ3JhcGgsIGxheW91dD0gbF9mciwgdmVydGV4LnNpemU9IDAsIHZlcnRleC5sYWJlbD1OQSwgbWFpbiA9ICJHbG9iYWwgU3BpZWMtRWFzaSBJbmZlcnJlZCBHcmFwaCIpCmBgYAoKQXMgc3RhdGVkLCB0aGVyZSBhcmUgbm8gY29ubmVjdGlvbnMgaW4gdGhlIGVzdGltYXRlZCBnbG9iYWwgZ3JhcGggKHNwYXJzaXR5IGlzIDApLgoKTGV0cyBsb29rIGF0IHdoYXQgaGFwcGVucyBpZiB3ZSB0YWtlIHRoZSB0dW5pbmcgcGFyYW1ldGVyIDEgc3RlcCBzbWFsbGVyIHRoYW4gdGhlIGNob3NlbiBvbmUgKGNvbHVtbiAyIGluIHRoZSB0YWJsZSBhYm92ZSkuCmBgYHtyfQpzZV9pZ3JhcGggPSBhZGoyaWdyYXBoKGFzLm1hdHJpeChzZVJlcyRwYXRoW1syXV0pKQoKcGFyKG1mcm93ID0gYygxLDIpLCBtYXI9YygxLCAxLCAxLCAxKSArIDAuMSkKcGxvdChncmFwaExpc3QkZ2xvYmFsLCBsYXlvdXQgPSBsX2ZyLCB2ZXJ0ZXguc2l6ZT0gMCwgdmVydGV4LmxhYmVsPU5BLCBtYWluID0gIkdsb2JhbCBCYXNpcyBHcmFwaCIpCnBsb3Qoc2VfaWdyYXBoLCBsYXlvdXQ9IGxfZnIsIHZlcnRleC5zaXplPSAwLCB2ZXJ0ZXgubGFiZWw9TkEsIG1haW4gPSAiR2xvYmFsIFNwaWVjLUVhc2kgSW5mZXJyZWQgR3JhcGgiKQpgYGAKClRoZXJlIGFyZSB3YXkgdG9vIG1hbnkgY29ubmVjdGlvbnMgaW4gdGhpcyBpbmZlcnJlZCBncmFwaC4gV2UgbmVlZCBzb21ld2hlcmUgaW4gdGhlIG1pZGRsZS4gVGhlIGN1cnJlbnQgc3RlcHMgaW4gdGhlIHByb2Nlc3MgYXJlIHRvbyBiaWcuCgpQcmVsaW1pbmFyaWx5LCBsZXRzIHRyeSB0byBhZGQgbW9yZSBwb2ludHMgdG8gdGhlIGdyaWQgc2VhcmNoIG9mIFNwaWVjRWFzaSBhbmQgc2VlIGlmIHRoYXQgZG9lcyBiZXR0ZXIuCgpgYGB7cn0Kc2VGaW5lID0gc3BpZWMuZWFzaShZLCBubGFtYmRhID0gNTAsIHZlcmJvc2UgPSBUUlVFLCBzZWwuY3JpdGVyaW9uID0gJ2ViaWMnKQpgYGAKYGBge3J9CnRlbXAgPSByYmluZChyb3VuZChzZUZpbmUkbGFtYmRhLCA0KSwgcm91bmQoc2VGaW5lJGViaWMuc2NvcmUsIDApLCByb3VuZChzZUZpbmUkc3BhcnNpdHksIDIpKQpyb3cubmFtZXModGVtcCkgPSBjKCJMYW1iZGEiLCAiZUJJQyIsICJTcGFyc2l0eSIpCmtuaXRyOjprYWJsZSh0ZW1wLCAgY29sLm5hbWVzID0gYXMuY2hhcmFjdGVyKDE6ZGltKHRlbXApWzJdKSkKCmh1Z2U6Omh1Z2Uucm9jKHNlRmluZSRwYXRoLCBhcy5tYXRyaXgoZ3JhcGhMaXN0JGdsb2JhbFtdKSkKYGBgCgpUaGlzIGlzIGFnYWluIHRoZSBzcGFyc2l0eSAwIG1vZGVsLCBzbyB0aGUgaW5mZXJyZWQgZ2xvYmFsIGdyYXBoIGhhcyBubyBlZGdlcy4gTG9va2luZyBhdCB0aGUgUk9DLCB3ZSBzZWUgcG90ZW50aWFsbHkgYmV0dGVyIG1vZGVscywgc28gbGV0cyBsb29rIGF0IG1vZGVsIDYsIGZvciBleGFtcGxlcyBzYWtlLgoKYGBge3J9CnNlRmluZV9pZ3JhcGggPSBhZGoyaWdyYXBoKGFzLm1hdHJpeChzZUZpbmUkcGF0aFtbNl1dKSkKCnBhcihtZnJvdyA9IGMoMSwyKSwgbWFyPWMoMSwgMSwgMSwgMSkgKyAwLjEpCnBsb3QoZ3JhcGhMaXN0JGdsb2JhbCwgbGF5b3V0ID0gbF9mciwgdmVydGV4LnNpemU9IDAsIHZlcnRleC5sYWJlbD1OQSwgbWFpbiA9ICJHbG9iYWwgQmFzaXMgR3JhcGgiKQpwbG90KHNlRmluZV9pZ3JhcGgsIGxheW91dD0gbF9mciwgdmVydGV4LnNpemU9IDAsIHZlcnRleC5sYWJlbD1OQSwgbWFpbiA9ICJHbG9iYWwgU3BpZWMtRWFzaSBJbmZlcnJlZCBHcmFwaCIpCmBgYAoKIyMjIFN0YXJzIE1vZGVsIFNlbGVjdGlvbgoKRG8gbW9kZWwgc2VsZWN0aW9uIHdpdGggU3RhcnMgaW5zdGVhZC4gU3RhcnMgcmVzYW1wbGVzIGFuZCByZWZpdHMgdG8gZ2V0IGVkZ2UgaW1wb3J0YW5jZSBtZXRyaWNzLCBhbmQgY2hvb3NlcyBzcGFyc2l0eSBtZXRyaWMgb2YgYmVzdCBuZXR3b3JrLiBTaG91bGQgYmUgcmVsYXRlZCB0byBtb3N0IHByZWRpY3RpdmUgZ3JhcGguCgpgYGB7cn0Kc2VGaW5lMiA9IHNwaWVjLmVhc2koWSwgbWV0aG9kID0gJ2dsYXNzbycsIGxhbWJkYS5taW4ucmF0aW89LjAxLCBubGFtYmRhID0gNTApCmBgYAoKYGBge3J9CiN0ZW1wID0gcmJpbmQocm91bmQoc2VGaW5lMiRsYW1iZGEsIDQpLCByb3VuZChzZUZpbmUyJGViaWMuc2NvcmUpLCByb3VuZChzZUZpbmUyJHNwYXJzaXR5LCAyKSkKI3Jvdy5uYW1lcyh0ZW1wKSA9IGMoIkxhbWJkYSIsICJlQklDIiwgIlNwYXJzaXR5IikKI2tuaXRyOjprYWJsZSh0ZW1wLCAgY29sLm5hbWVzID0gYXMuY2hhcmFjdGVyKDE6ZGltKHRlbXApWzJdKSkKCmh1Z2U6Omh1Z2Uucm9jKHNlRmluZTIkcGF0aCwgYXMubWF0cml4KGdyYXBoTGlzdCRnbG9iYWxbXSkpCmBgYAoKU3RhcnMgc3VnZ2VzdHMgY2hvb3NpbmcgdGhlIGByIHNlRmluZTIkb3B0LmxhbWJkYWAgc3BhcnNpdHkgcGFyYW1ldGVyLCB3aGljaCBpcyB0aGUgYHIgd2hpY2goc2VGaW5lMiRsYW1iZGEgPT0gc2VGaW5lMiRvcHQubGFtYmRhKWAgbW9kZWwgaW4gdGhlIFJPQyBwYXRoLgoKTGV0cyBsb29rIGF0IHRoZSBncmFwaHMgY3JlYXRlZCBhbmQgc2VlIGlmIHRoZXkgZG8gYSBiZXR0ZXIgam9iIGF0IG1hdGNoaW5nIHRoZSBnbG9iYWwgYmFzaXMgZ3JhcGguIAoKYGBge3J9CnNlX2lncmFwaCA9IGFkajJpZ3JhcGgoYXMubWF0cml4KHNlRmluZTIkcmVmaXQpKQoKcGFyKG1mcm93ID0gYygxLDIpLCBtYXI9YygxLCAxLCAxLCAxKSArIDAuMSkKcGxvdChncmFwaExpc3QkZ2xvYmFsLCBsYXlvdXQgPSBsX2ZyLCB2ZXJ0ZXguc2l6ZT0gMCwgdmVydGV4LmxhYmVsPU5BLCBtYWluID0gIkdsb2JhbCBCYXNpcyBHcmFwaCIpCnBsb3Qoc2VfaWdyYXBoLCBsYXlvdXQ9IGxfZnIsIHZlcnRleC5zaXplPSAwLCB2ZXJ0ZXgubGFiZWw9TkEsIG1haW4gPSAiR2xvYmFsIFNwaWVjLUVhc2kgSW5mZXJyZWQgR3JhcGgiKQpgYGAKCklOc3RlYWQgb2YgY2hvb3NpbmcgdGhhdCBwYXJhbWV0ZXIgYW5kIHdvcnJ5aW5nLCB3ZSBjYW4gdXNlIHRoZSBkZWZhdWx0IG1vZGVsIHNlbGVjdGlvbiB0b29sLCBjYWxsZWQgU3RhcnMuIFN0YXJzIHJlc2FtcGxlcyBhbmQgcmVmaXRzIHRvIGdldCBlZGdlIGltcG9ydGFuY2UgbWV0cmljcywgYW5kIGNob29zZXMgc3BhcnNpdHkgbWV0cmljIG9mIGJlc3QgbmV0d29yay4gU2hvdWxkIGJlIHJlbGF0ZWQgdG8gbW9zdCBwcmVkaWN0aXZlIGdyYXBoLgo=