[2020-04-26]

We have a total of 8 populations with 4 pairs tested on one of 4 herbicides with different modes of action:

print(data.frame(POPULATION=c("ACC13", "ACC49", "ACC31", "ACC62", "ACC11", "ACC59", "ACC09", "ACC59"), HERBICIDE_TREATMENT=rep(c("Clethodim", "Glyphosate", "Sulfometuron", "Terbuthylazine"), each=2)))

In each population, the individuals were sorted according to increasing quantitative herbicide resistance levels, then divided into 5 pools. Giving us in total, 40 data-points, with 10 data-points per herbicide resistance and 5 data-points per population.

With this serevely magnified n << p problem in our PoolGPAS datasets, can we accurately predict phenotypes?

Using the GWAlpha.jl to build our genomic prediction models. We then ask:

  1. Which genomic prediction model performs best?
  2. How does genomic prediction accuracies vary across herbicide treatments?

Load violinplotter for quick distribution visualization and mean comparisons

library(violinplotter)

Load the concatenated *.rmse csv file

dat = read.csv("GP_PERFORMANCES.csv", header=FALSE)
str(dat)
'data.frame':   296 obs. of  5 variables:
 $ V1: Factor w/ 8 levels "ACC09","ACC11",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ V2: Factor w/ 5 levels "GLMNET_ALPHA0.0",..: 1 1 1 1 1 1 1 1 2 2 ...
 $ V3: Factor w/ 8 levels "ACC09","ACC11",..: 1 2 3 4 5 6 7 8 1 2 ...
 $ V4: num  1 -0.0115 0.6342 0.715 0.4882 ...
 $ V5: num  3.65e-15 1.66e-02 2.72e-01 4.27e-01 9.14e-01 ...

Add herbicide columns

colnames(dat) = c("TRAINING_POP", "TRAINING_MODEL", "VALIDATION_POP", "CORRELATION", "RMSE")
herbi_df_train = data.frame(TRAINING_POP=c("ACC13", "ACC49", "ACC31", "ACC62", "ACC11", "ACC59", "ACC09", "ACC54"), TRAINING_HERBICIDE=rep(c("CLETHODIM", "GLYPHOSATE", "SULFOMETURON", "TERBUTHYLAZINE"), each=2))
dat = merge(dat, herbi_df_train, by="TRAINING_POP")
herbi_df_test = data.frame(VALIDATION_POP=c("ACC13", "ACC49", "ACC31", "ACC62", "ACC11", "ACC59", "ACC09", "ACC54"), VALIDATION_HERBICIDE=rep(c("CLETHODIM", "GLYPHOSATE", "SULFOMETURON", "TERBUTHYLAZINE"), each=2))
dat = merge(dat, herbi_df_test, by="VALIDATION_POP")

Preliminary plots

Plotting the ditributions of the 2 genomic prediction metrics:

par(mfrow=c(1,2))
hist(dat$CORRELATION, main="", xlab="Correlation(Observed, Predicted)", col="#80b1d3", bord="#377eb8")
hist(dat$RMSE, main="", xlab="Root Mean Square Error", col="#fb8072", bord="#e41a1c")

The metrics vary within the bounds we expect for correlation and the root mean square error, between -1 to +1 and between 0 to 1, respectively.

Sanity checking comparing sensical vs non-sensical genomic predictions, i.e. within herbicide treatment and across herbicide treatment predictions:

dat$SENSE = rep("NON-SENSICAL", times=nrow(dat))
dat$SENSE[dat$TRAINING_HERBICIDE==dat$VALIDATION_HERBICIDE] = "SENSICAL"
par(mfrow=c(1,2))
violinplotter(CORRELATION ~ SENSE, data=dat)
======================================================
Violin Plotting: SENSE
======================================================
[[1]]
[[1]]$HSD_out.LEVELS
[1] NON_SENSICAL SENSICAL    
Levels: NON_SENSICAL SENSICAL

[[1]]$HSD_out.GROUPING
[1] b a
Levels: a b

[[1]]$HSD_out.NUMBERS
[1] 1 2

[[1]]$HSD_out.MEANS
[1] 0.01552787 0.43118514
violinplotter(RMSE ~ SENSE, data=dat)
======================================================
Violin Plotting: SENSE
======================================================
[[1]]
[[1]]$HSD_out.LEVELS
[1] NON_SENSICAL SENSICAL    
Levels: NON_SENSICAL SENSICAL

[[1]]$HSD_out.GROUPING
[1] a b
Levels: a b

[[1]]$HSD_out.NUMBERS
[1] 1 2

[[1]]$HSD_out.MEANS
[1] 0.4203112 0.2330011

Genomic prediction within herbicide treatments (“SENSICAL”) performed better than prediction across herbicide treatments (“NON-SENSICAL”), which is what we expected.

We then remove these non-sensical genomic predictions:

dat = droplevels(dat[dat$SENSE=="SENSICAL", ])

Comparing auto-cross-validation (model training and validation on the same population) and the proper validation (validation on another population):

dat$AUTOCROSS = rep("AUTO-CROSS", times=nrow(dat))
dat$AUTOCROSS[as.character(dat$TRAINING_POP) != as.character(dat$VALIDATION_POP)] = "PROPER-CROSS"
par(mfrow=c(1,2))
violinplotter(CORRELATION ~ AUTOCROSS, dat)
======================================================
Violin Plotting: AUTOCROSS
======================================================
[[1]]
[[1]]$HSD_out.LEVELS
[1] AUTO_CROSS   PROPER_CROSS
Levels: AUTO_CROSS PROPER_CROSS

[[1]]$HSD_out.GROUPING
[1] a b
Levels: a b

[[1]]$HSD_out.NUMBERS
[1] 1 2

[[1]]$HSD_out.MEANS
[1]  0.88630107 -0.01054504
violinplotter(RMSE ~ AUTOCROSS, dat)
======================================================
Violin Plotting: AUTOCROSS
======================================================
[[1]]
[[1]]$HSD_out.LEVELS
[1] AUTO_CROSS   PROPER_CROSS
Levels: AUTO_CROSS PROPER_CROSS

[[1]]$HSD_out.GROUPING
[1] b a
Levels: a b

[[1]]$HSD_out.NUMBERS
[1] 1 2

[[1]]$HSD_out.MEANS
[1] 0.06333041 0.40267170

As expected, auto-cross-validation performed better than cross-validation on a different population.

We then remove these auto-cross-validations:

dat = droplevels(dat[dat$AUTOCROSS=="PROPER-CROSS", ])

Now on the the main questions: 1. Which genomic prediction model using Pool-seq data (Pool-GP model) currently implemented in GWAlpha.jl performs best? 2. How does genomic prediction accuracies vary across the herbicide treatments?

aggregate(CORRELATION ~ TRAINING_HERBICIDE, FUN=mean, data=dat)
aggregate(CORRELATION ~ TRAINING_MODEL, FUN=mean, data=dat)
aggregate(RMSE ~ TRAINING_HERBICIDE, FUN=mean, data=dat)
aggregate(RMSE ~ TRAINING_MODEL, FUN=mean, data=dat)

In terms of the correlation between observed and predicted herbicide resistance:

violinplotter(CORRELATION ~ TRAINING_MODEL + TRAINING_HERBICIDE, dat)
======================================================
Violin Plotting: TRAINING_MODEL
======================================================
[[1]]
[[1]]$HSD_out.LEVELS
[1] GLMNET_ALPHA0.0 GLMNET_ALPHA0.5 GLMNET_ALPHA1.0 GWAlpha         MIXEDREML_FST  
Levels: GLMNET_ALPHA0.0 GLMNET_ALPHA0.5 GLMNET_ALPHA1.0 GWAlpha MIXEDREML_FST

[[1]]$HSD_out.GROUPING
[1] a a a a a
Levels: a

[[1]]$HSD_out.NUMBERS
[1] 1 2 3 4 5

[[1]]$HSD_out.MEANS
[1] -0.042642980 -0.131238527  0.101426215  0.037449175 -0.009915455


[[2]]
[[2]]$HSD_out.LEVELS
[1] CLETHODIM      GLYPHOSATE     SULFOMETURON   TERBUTHYLAZINE
Levels: CLETHODIM GLYPHOSATE SULFOMETURON TERBUTHYLAZINE

[[2]]$HSD_out.GROUPING
[1] b a a a
Levels: a b

[[2]]$HSD_out.NUMBERS
[1] 1 2 3 4

[[2]]$HSD_out.MEANS
[1] -0.5798616  0.2018083  0.1711151  0.2055418

No significant difference was observed between models and herbicide resistances.

In terms of the correlation between observed and predicted herbicide resistance:

violinplotter(RMSE ~ TRAINING_MODEL + TRAINING_HERBICIDE, dat)
======================================================
Violin Plotting: TRAINING_MODEL
======================================================
[[1]]
[[1]]$HSD_out.LEVELS
[1] GLMNET_ALPHA0.0 GLMNET_ALPHA0.5 GLMNET_ALPHA1.0 GWAlpha         MIXEDREML_FST  
Levels: GLMNET_ALPHA0.0 GLMNET_ALPHA0.5 GLMNET_ALPHA1.0 GWAlpha MIXEDREML_FST

[[1]]$HSD_out.GROUPING
[1] a a a a a
Levels: a

[[1]]$HSD_out.NUMBERS
[1] 1 2 3 4 5

[[1]]$HSD_out.MEANS
[1] 0.4303946 0.4315579 0.4076578 0.3749691 0.3764784


[[2]]
[[2]]$HSD_out.LEVELS
[1] CLETHODIM      GLYPHOSATE     SULFOMETURON   TERBUTHYLAZINE
Levels: CLETHODIM GLYPHOSATE SULFOMETURON TERBUTHYLAZINE

[[2]]$HSD_out.GROUPING
[1] a c d b
Levels: a b c d

[[2]]$HSD_out.NUMBERS
[1] 1 2 3 4

[[2]]$HSD_out.MEANS
[1] 0.76459161 0.28854151 0.01984712 0.42285919

Similarly, no significant difference was observed between models; however, differences in RMSE were observed among the herbicide treatments, with Sulfometuron resistance showing the lowest RMSE. This may be biased due to the low phenotypic variation observed for the sulfometuron resistance:

pheno = read.csv("pheno.csv")
pheno$HERBI_POP = paste0(pheno$HERBI, "-", pheno$POP)
str(pheno)
'data.frame':   3519 obs. of  8 variables:
 $ LIBRARY   : Factor w/ 8 levels "ACC09","ACC11",..: 3 3 3 3 3 3 3 3 3 3 ...
 $ HERBI     : Factor w/ 4 levels "CLETH","GLYPH",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ POP       : Factor w/ 8 levels "ACC09","ACC11",..: 3 3 3 3 3 3 3 3 3 3 ...
 $ PLANT     : Factor w/ 3519 levels "ACC09_001","ACC09_002",..: 1428 1258 1006 1435 1190 1407 1429 1228 1423 1180 ...
 $ RESISTANCE: num  0.245 0.277 0.298 0.3 0.306 ...
 $ SURVI     : int  0 0 0 0 0 0 0 0 1 0 ...
 $ POOL      : int  1 1 1 1 1 1 1 1 1 1 ...
 $ HERBI_POP : chr  "CLETH-ACC13" "CLETH-ACC13" "CLETH-ACC13" "CLETH-ACC13" ...
par(mfrow=c(2,4))
colors = rep(c("#b3de69", "#fdb462", "#80b1d3", "#fb8072"), each=2)
for (i in 1:length(unique(pheno$HERBI_POP))){
  herbi = unique(pheno$HERBI_POP)[i]
  sub = pheno[pheno$HERBI_POP==herbi, ]
  perc_survi = aggregate(SURVI*100 ~ POOL, data=sub, FUN=mean)
  hist(sub$RESISTANCE, xlab="", main=herbi, col=colors[i], bord=NA)
  legend("topright", legend=c("Resistances:", paste0("Pool", perc_survi[,1], " = ", round(perc_survi[,2]), "%")), bty="n")
}

Consistent with other genomic prediction (genomic selection studies), training and validation populations with similar phenotypic range results in higher prediction accuracies (in our case Sulfometuron resistance: ACC11 and ACC59, this and the narrow range of phenotype variation observed), compared with populations with diverging phenotypic range (in our case Clethodim resistance: ACC13 and ACC49). This dataset (PoolGPAS_08SEAu) together with the Inverleigh-Urana (PoolGPAS_InvUra) and the 60 SE Autralian populations (PoolGPAS_60SEAu) datasets will be merged for a 10-fold cross-validation analyses.

LS0tCnRpdGxlOiAiR2Vub21pYyBQcmVkaWN0aW9uIGFuZCBHZW5vbWUtV2lkZSBBc3NvY2lhdGlvbiB1c2luZyBQb29sIFNlcXVlbmNpbmcgRGF0YSAoUG9vbEdQQVMpIgpzdWJ0aXRsZTogIkdlbm9taWMgcHJlZGljdGlvbiBwZXJmb3JtYW5jZSBvbiA4IFNvdXRoZWFzdCBBdXN0cmFsaWFuIExvbGl1bSBwb3B1bGF0aW9ucyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKWzIwMjAtMDQtMjZdCgpXZSBoYXZlIGEgdG90YWwgb2YgOCBwb3B1bGF0aW9ucyB3aXRoIDQgcGFpcnMgdGVzdGVkIG9uIG9uZSBvZiA0IGhlcmJpY2lkZXMgd2l0aCBkaWZmZXJlbnQgbW9kZXMgb2YgYWN0aW9uOgpgYGB7cn0KcHJpbnQoZGF0YS5mcmFtZShQT1BVTEFUSU9OPWMoIkFDQzEzIiwgIkFDQzQ5IiwgIkFDQzMxIiwgIkFDQzYyIiwgIkFDQzExIiwgIkFDQzU5IiwgIkFDQzA5IiwgIkFDQzU5IiksIEhFUkJJQ0lERV9UUkVBVE1FTlQ9cmVwKGMoIkNsZXRob2RpbSIsICJHbHlwaG9zYXRlIiwgIlN1bGZvbWV0dXJvbiIsICJUZXJidXRoeWxhemluZSIpLCBlYWNoPTIpKSkKYGBgCkluIGVhY2ggcG9wdWxhdGlvbiwgdGhlIGluZGl2aWR1YWxzIHdlcmUgc29ydGVkIGFjY29yZGluZyB0byBpbmNyZWFzaW5nIHF1YW50aXRhdGl2ZSBoZXJiaWNpZGUgcmVzaXN0YW5jZSBsZXZlbHMsIHRoZW4gZGl2aWRlZCBpbnRvIDUgcG9vbHMuIEdpdmluZyB1cyBpbiB0b3RhbCwgNDAgZGF0YS1wb2ludHMsIHdpdGggMTAgZGF0YS1wb2ludHMgcGVyIGhlcmJpY2lkZSByZXNpc3RhbmNlIGFuZCA1IGRhdGEtcG9pbnRzIHBlciBwb3B1bGF0aW9uLgoKV2l0aCB0aGlzIHNlcmV2ZWx5IG1hZ25pZmllZCAqKm4gPDwgcCoqIHByb2JsZW0gaW4gb3VyIFBvb2xHUEFTIGRhdGFzZXRzLCBjYW4gd2UgYWNjdXJhdGVseSBwcmVkaWN0IHBoZW5vdHlwZXM/CgpVc2luZyB0aGUgW0dXQWxwaGEuamxdKGh0dHBzOi8vZ2l0aHViLmNvbS9qZWZmZXJzb25mcGFyaWwvR1dBbHBoYS5qbCkgdG8gYnVpbGQgb3VyIGdlbm9taWMgcHJlZGljdGlvbiBtb2RlbHMuIFdlIHRoZW4gYXNrOgoKICAxLiBXaGljaCBnZW5vbWljIHByZWRpY3Rpb24gbW9kZWwgcGVyZm9ybXMgYmVzdD8KICAyLiBIb3cgZG9lcyBnZW5vbWljIHByZWRpY3Rpb24gYWNjdXJhY2llcyB2YXJ5IGFjcm9zcyBoZXJiaWNpZGUgdHJlYXRtZW50cz8KCkxvYWQgdmlvbGlucGxvdHRlciBmb3IgcXVpY2sgZGlzdHJpYnV0aW9uIHZpc3VhbGl6YXRpb24gYW5kIG1lYW4gY29tcGFyaXNvbnMKYGBge3J9CmxpYnJhcnkodmlvbGlucGxvdHRlcikKYGBgCgpMb2FkIHRoZSBjb25jYXRlbmF0ZWQgKi5ybXNlIGNzdiBmaWxlCmBgYHtyfQpkYXQgPSByZWFkLmNzdigiR1BfUEVSRk9STUFOQ0VTLmNzdiIsIGhlYWRlcj1GQUxTRSkKc3RyKGRhdCkKYGBgCgpBZGQgaGVyYmljaWRlIGNvbHVtbnMKYGBge3J9CmNvbG5hbWVzKGRhdCkgPSBjKCJUUkFJTklOR19QT1AiLCAiVFJBSU5JTkdfTU9ERUwiLCAiVkFMSURBVElPTl9QT1AiLCAiQ09SUkVMQVRJT04iLCAiUk1TRSIpCmhlcmJpX2RmX3RyYWluID0gZGF0YS5mcmFtZShUUkFJTklOR19QT1A9YygiQUNDMTMiLCAiQUNDNDkiLCAiQUNDMzEiLCAiQUNDNjIiLCAiQUNDMTEiLCAiQUNDNTkiLCAiQUNDMDkiLCAiQUNDNTQiKSwgVFJBSU5JTkdfSEVSQklDSURFPXJlcChjKCJDTEVUSE9ESU0iLCAiR0xZUEhPU0FURSIsICJTVUxGT01FVFVST04iLCAiVEVSQlVUSFlMQVpJTkUiKSwgZWFjaD0yKSkKZGF0ID0gbWVyZ2UoZGF0LCBoZXJiaV9kZl90cmFpbiwgYnk9IlRSQUlOSU5HX1BPUCIpCmhlcmJpX2RmX3Rlc3QgPSBkYXRhLmZyYW1lKFZBTElEQVRJT05fUE9QPWMoIkFDQzEzIiwgIkFDQzQ5IiwgIkFDQzMxIiwgIkFDQzYyIiwgIkFDQzExIiwgIkFDQzU5IiwgIkFDQzA5IiwgIkFDQzU0IiksIFZBTElEQVRJT05fSEVSQklDSURFPXJlcChjKCJDTEVUSE9ESU0iLCAiR0xZUEhPU0FURSIsICJTVUxGT01FVFVST04iLCAiVEVSQlVUSFlMQVpJTkUiKSwgZWFjaD0yKSkKZGF0ID0gbWVyZ2UoZGF0LCBoZXJiaV9kZl90ZXN0LCBieT0iVkFMSURBVElPTl9QT1AiKQpgYGAKClByZWxpbWluYXJ5IHBsb3RzCgpQbG90dGluZyB0aGUgZGl0cmlidXRpb25zIG9mIHRoZSAyIGdlbm9taWMgcHJlZGljdGlvbiBtZXRyaWNzOgpgYGB7cn0KcGFyKG1mcm93PWMoMSwyKSkKaGlzdChkYXQkQ09SUkVMQVRJT04sIG1haW49IiIsIHhsYWI9IkNvcnJlbGF0aW9uKE9ic2VydmVkLCBQcmVkaWN0ZWQpIiwgY29sPSIjODBiMWQzIiwgYm9yZD0iIzM3N2ViOCIpCmhpc3QoZGF0JFJNU0UsIG1haW49IiIsIHhsYWI9IlJvb3QgTWVhbiBTcXVhcmUgRXJyb3IiLCBjb2w9IiNmYjgwNzIiLCBib3JkPSIjZTQxYTFjIikKYGBgClRoZSBtZXRyaWNzIHZhcnkgd2l0aGluIHRoZSBib3VuZHMgd2UgZXhwZWN0IGZvciBjb3JyZWxhdGlvbiBhbmQgdGhlIHJvb3QgbWVhbiBzcXVhcmUgZXJyb3IsIGJldHdlZW4gLTEgdG8gKzEgYW5kIGJldHdlZW4gMCB0byAxLCByZXNwZWN0aXZlbHkuCgpTYW5pdHkgY2hlY2tpbmcgY29tcGFyaW5nIHNlbnNpY2FsIHZzIG5vbi1zZW5zaWNhbCBnZW5vbWljIHByZWRpY3Rpb25zLCBpLmUuIHdpdGhpbiBoZXJiaWNpZGUgdHJlYXRtZW50IGFuZCBhY3Jvc3MgaGVyYmljaWRlIHRyZWF0bWVudCBwcmVkaWN0aW9uczoKYGBge3J9CmRhdCRTRU5TRSA9IHJlcCgiTk9OLVNFTlNJQ0FMIiwgdGltZXM9bnJvdyhkYXQpKQpkYXQkU0VOU0VbZGF0JFRSQUlOSU5HX0hFUkJJQ0lERT09ZGF0JFZBTElEQVRJT05fSEVSQklDSURFXSA9ICJTRU5TSUNBTCIKcGFyKG1mcm93PWMoMSwyKSkKdmlvbGlucGxvdHRlcihDT1JSRUxBVElPTiB+IFNFTlNFLCBkYXRhPWRhdCkKdmlvbGlucGxvdHRlcihSTVNFIH4gU0VOU0UsIGRhdGE9ZGF0KQpgYGAKR2Vub21pYyBwcmVkaWN0aW9uIHdpdGhpbiBoZXJiaWNpZGUgdHJlYXRtZW50cyAoIlNFTlNJQ0FMIikgcGVyZm9ybWVkIGJldHRlciB0aGFuIHByZWRpY3Rpb24gYWNyb3NzIGhlcmJpY2lkZSB0cmVhdG1lbnRzICgiTk9OLVNFTlNJQ0FMIiksIHdoaWNoIGlzIHdoYXQgd2UgZXhwZWN0ZWQuCgpXZSB0aGVuIHJlbW92ZSB0aGVzZSBub24tc2Vuc2ljYWwgZ2Vub21pYyBwcmVkaWN0aW9uczoKYGBge3J9CmRhdCA9IGRyb3BsZXZlbHMoZGF0W2RhdCRTRU5TRT09IlNFTlNJQ0FMIiwgXSkKYGBgCgpDb21wYXJpbmcgYXV0by1jcm9zcy12YWxpZGF0aW9uIChtb2RlbCB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBvbiB0aGUgc2FtZSBwb3B1bGF0aW9uKSBhbmQgdGhlIHByb3BlciB2YWxpZGF0aW9uICh2YWxpZGF0aW9uIG9uIGFub3RoZXIgcG9wdWxhdGlvbik6CmBgYHtyfQpkYXQkQVVUT0NST1NTID0gcmVwKCJBVVRPLUNST1NTIiwgdGltZXM9bnJvdyhkYXQpKQpkYXQkQVVUT0NST1NTW2FzLmNoYXJhY3RlcihkYXQkVFJBSU5JTkdfUE9QKSAhPSBhcy5jaGFyYWN0ZXIoZGF0JFZBTElEQVRJT05fUE9QKV0gPSAiUFJPUEVSLUNST1NTIgpwYXIobWZyb3c9YygxLDIpKQp2aW9saW5wbG90dGVyKENPUlJFTEFUSU9OIH4gQVVUT0NST1NTLCBkYXQpCnZpb2xpbnBsb3R0ZXIoUk1TRSB+IEFVVE9DUk9TUywgZGF0KQpgYGAKQXMgZXhwZWN0ZWQsIGF1dG8tY3Jvc3MtdmFsaWRhdGlvbiBwZXJmb3JtZWQgYmV0dGVyIHRoYW4gY3Jvc3MtdmFsaWRhdGlvbiBvbiBhIGRpZmZlcmVudCBwb3B1bGF0aW9uLgoKV2UgdGhlbiByZW1vdmUgdGhlc2UgYXV0by1jcm9zcy12YWxpZGF0aW9uczoKYGBge3J9CmRhdCA9IGRyb3BsZXZlbHMoZGF0W2RhdCRBVVRPQ1JPU1M9PSJQUk9QRVItQ1JPU1MiLCBdKQpgYGAKCk5vdyBvbiB0aGUgdGhlIG1haW4gcXVlc3Rpb25zOgoxLiBXaGljaCBnZW5vbWljIHByZWRpY3Rpb24gbW9kZWwgdXNpbmcgUG9vbC1zZXEgZGF0YSAoUG9vbC1HUCBtb2RlbCkgY3VycmVudGx5IGltcGxlbWVudGVkIGluIFtHV0FscGhhLmpsXShodHRwczovL2dpdGh1Yi5jb20vamVmZmVyc29uZnBhcmlsL0dXQWxwaGEuamwpIHBlcmZvcm1zIGJlc3Q/CjIuIEhvdyBkb2VzIGdlbm9taWMgcHJlZGljdGlvbiBhY2N1cmFjaWVzIHZhcnkgYWNyb3NzIHRoZSBoZXJiaWNpZGUgdHJlYXRtZW50cz8KCmBgYHtyfQphZ2dyZWdhdGUoQ09SUkVMQVRJT04gfiBUUkFJTklOR19IRVJCSUNJREUsIEZVTj1tZWFuLCBkYXRhPWRhdCkKYWdncmVnYXRlKENPUlJFTEFUSU9OIH4gVFJBSU5JTkdfTU9ERUwsIEZVTj1tZWFuLCBkYXRhPWRhdCkKYWdncmVnYXRlKFJNU0UgfiBUUkFJTklOR19IRVJCSUNJREUsIEZVTj1tZWFuLCBkYXRhPWRhdCkKYWdncmVnYXRlKFJNU0UgfiBUUkFJTklOR19NT0RFTCwgRlVOPW1lYW4sIGRhdGE9ZGF0KQpgYGAKCkluIHRlcm1zIG9mIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIG9ic2VydmVkIGFuZCBwcmVkaWN0ZWQgaGVyYmljaWRlIHJlc2lzdGFuY2U6CmBgYHtyfQp2aW9saW5wbG90dGVyKENPUlJFTEFUSU9OIH4gVFJBSU5JTkdfTU9ERUwgKyBUUkFJTklOR19IRVJCSUNJREUsIGRhdCkKYGBgCk5vIHNpZ25pZmljYW50IGRpZmZlcmVuY2Ugd2FzIG9ic2VydmVkIGJldHdlZW4gbW9kZWxzIGFuZCBoZXJiaWNpZGUgcmVzaXN0YW5jZXMuIAoKSW4gdGVybXMgb2YgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gb2JzZXJ2ZWQgYW5kIHByZWRpY3RlZCBoZXJiaWNpZGUgcmVzaXN0YW5jZToKYGBge3J9CnZpb2xpbnBsb3R0ZXIoUk1TRSB+IFRSQUlOSU5HX01PREVMICsgVFJBSU5JTkdfSEVSQklDSURFLCBkYXQpCmBgYApTaW1pbGFybHksIG5vIHNpZ25pZmljYW50IGRpZmZlcmVuY2Ugd2FzIG9ic2VydmVkIGJldHdlZW4gbW9kZWxzOyBob3dldmVyLCBkaWZmZXJlbmNlcyBpbiBSTVNFIHdlcmUgb2JzZXJ2ZWQgYW1vbmcgdGhlIGhlcmJpY2lkZSB0cmVhdG1lbnRzLCB3aXRoIFN1bGZvbWV0dXJvbiByZXNpc3RhbmNlIHNob3dpbmcgdGhlIGxvd2VzdCBSTVNFLiBUaGlzIG1heSBiZSBiaWFzZWQgZHVlIHRvIHRoZSBsb3cgcGhlbm90eXBpYyB2YXJpYXRpb24gb2JzZXJ2ZWQgZm9yIHRoZSBzdWxmb21ldHVyb24gcmVzaXN0YW5jZToKYGBge3J9CnBoZW5vID0gcmVhZC5jc3YoInBoZW5vLmNzdiIpCnBoZW5vJEhFUkJJX1BPUCA9IHBhc3RlMChwaGVubyRIRVJCSSwgIi0iLCBwaGVubyRQT1ApCnN0cihwaGVubykKcGFyKG1mcm93PWMoMiw0KSkKY29sb3JzID0gcmVwKGMoIiNiM2RlNjkiLCAiI2ZkYjQ2MiIsICIjODBiMWQzIiwgIiNmYjgwNzIiKSwgZWFjaD0yKQpmb3IgKGkgaW4gMTpsZW5ndGgodW5pcXVlKHBoZW5vJEhFUkJJX1BPUCkpKXsKICBoZXJiaSA9IHVuaXF1ZShwaGVubyRIRVJCSV9QT1ApW2ldCiAgc3ViID0gcGhlbm9bcGhlbm8kSEVSQklfUE9QPT1oZXJiaSwgXQogIHBlcmNfc3VydmkgPSBhZ2dyZWdhdGUoU1VSVkkqMTAwIH4gUE9PTCwgZGF0YT1zdWIsIEZVTj1tZWFuKQogIGhpc3Qoc3ViJFJFU0lTVEFOQ0UsIHhsYWI9IiIsIG1haW49aGVyYmksIGNvbD1jb2xvcnNbaV0sIGJvcmQ9TkEpCiAgbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZD1jKCJSZXNpc3RhbmNlczoiLCBwYXN0ZTAoIlBvb2wiLCBwZXJjX3N1cnZpWywxXSwgIiA9ICIsIHJvdW5kKHBlcmNfc3VydmlbLDJdKSwgIiUiKSksIGJ0eT0ibiIpCn0KYGBgCkNvbnNpc3RlbnQgd2l0aCBvdGhlciBnZW5vbWljIHByZWRpY3Rpb24gKGdlbm9taWMgc2VsZWN0aW9uIHN0dWRpZXMpLCB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBwb3B1bGF0aW9ucyB3aXRoIHNpbWlsYXIgcGhlbm90eXBpYyByYW5nZSByZXN1bHRzIGluIGhpZ2hlciBwcmVkaWN0aW9uIGFjY3VyYWNpZXMgKGluIG91ciBjYXNlIFN1bGZvbWV0dXJvbiByZXNpc3RhbmNlOiBBQ0MxMSBhbmQgQUNDNTksIHRoaXMgYW5kIHRoZSBuYXJyb3cgcmFuZ2Ugb2YgcGhlbm90eXBlIHZhcmlhdGlvbiBvYnNlcnZlZCksIGNvbXBhcmVkIHdpdGggcG9wdWxhdGlvbnMgd2l0aCBkaXZlcmdpbmcgcGhlbm90eXBpYyByYW5nZSAoaW4gb3VyIGNhc2UgQ2xldGhvZGltIHJlc2lzdGFuY2U6IEFDQzEzIGFuZCBBQ0M0OSkuClRoaXMgZGF0YXNldCAoUG9vbEdQQVNfMDhTRUF1KSB0b2dldGhlciB3aXRoIHRoZSBJbnZlcmxlaWdoLVVyYW5hIChQb29sR1BBU19JbnZVcmEpIGFuZCB0aGUgNjAgU0UgQXV0cmFsaWFuIHBvcHVsYXRpb25zIChQb29sR1BBU182MFNFQXUpIGRhdGFzZXRzIHdpbGwgYmUgbWVyZ2VkIGZvciBhIDEwLWZvbGQgY3Jvc3MtdmFsaWRhdGlvbiBhbmFseXNlcy4KCgoKCgo=