• Shantel A Martinez
  • Input dataset:
  • rrBLUP
  • RKHS
  • Running LASSO
  • Plots

Author: Shantel A. Martinez
Purpose: Analyze the PHS data under mutliple GS models and determine which model bests fits out data.
Last updated: 2019.02.19

The majority of the tutorials I learned basics of genomic selection came from the wheat cimmyt tutorial and validation examples and for rrBLUP I started with this tutorial and the rrBLUP CRAN page.

Input dataset:

For example purposes, we will use the example dataset using Campos et al., 2010 rather than my project dataset.

# install.packages("BGLR") #Install package if not already installed
library(BGLR)
data(wheat)
y <- wheat.Y #Phenotypic data
X <- wheat.X #Genotypic data
n<-nrow(X); p<-ncol(X) #defining n as the row number of matrix `X` and p as the col number of matrix `X`
y[1:4,] #Each column is a different phenotype; lines/varieties (rows)
##               1           2          4          5
## 775   1.6716295 -1.72746986 -1.8902848  0.0509159
## 2166 -0.2527028  0.40952243  0.3093855 -1.7387588
## 2167  0.3418151 -0.64862633 -0.7995592 -1.0535691
## 2465  0.7854395  0.09394919  0.5704677  0.5517574
X[1:5,1:5] #lines/varieties(rows) and markers (col)
##      wPt.0538 wPt.8463 wPt.6348 wPt.9992 wPt.2838
## [1,]        0        1        1        1        1
## [2,]        1        1        1        1        1
## [3,]        1        1        1        1        1
## [4,]        0        1        1        1        1
## [5,]        0        1        1        1        1

Load all packages for the entire script and set the working directory
Remember to install all packages once before loading them below

library(BGLR)
library(rrBLUP)
library(foreach)
library(doMC)  # Used for running the loops on multiple cores (example here, 5 cores)
registerDoMC(cores=5)

library(ggplot2)
font<-element_text(face = "bold",  size = 16)  #ggplot graphical preferences
font2<-element_text(size = 16)

# setwd("D:/PHS Genomic Selection Project/Data Analysis/GS/20190219")

rrBLUP

#Parameters defined  
folds=5              
K=A.mat(X)
y = y[,2]

#Function
test_CV_parallel<-function(x,folds){   
  set.seed(x)
  results=c()
  for(i in 1:folds){   
    tst <-  sample(rep(1:folds,length.out=n))
    yNA<-y
    yNA[which(i==tst)]<-NA 
    ans <- mixed.solve(yNA,K=K)
    yHat_1=ans$u[-tst]
    results <- append(results,cor(yHat_1,y[-tst]))
  }
  return(results)
}

#Analysis
cv_results6=foreach(cores=1:5, .combine='c') %dopar% test_CV_parallel(x=cores, folds=5) #5 (multi) core computer; dopar will do the command in parallel
# cv_results6=foreach(cores=1:5, .combine='c') %do% test_CV_parallel(x=cores, folds=5) #one core computer

#Data Organization
accuracy5 = as.data.frame(matrix(cv_results6)) 
accuracy5$model<-"rrBLUP"
names(accuracy5) <- c("PA",  "model")      
accuracyrr<-accuracy5

The script step by step:

Lets start from the basics within all the loops and work outwards.
Running genomic prediction for a phenotype y while using a kinship matrix K consists of the following commands:
K=A.mat(X) The A.mat function in rrBLUP will calculate the addititve relationship matrix
y = y[,2] define which phenotype you want to predict (this will leave y as just a vector of n x 1)
pred <- mixed.solve(y,K=K) mixed.solve salculates maximum-likelihood (ML/REML) solutions for mixed models, but since you are using K in your model, it will also calculate the GEBVs
pred$u[] the output pred will contain a dataframe u that containts the GEBVs

However, this is using all 100% of your data, to calculate the GEBVs, so since you arent training on a subset of the data to predict another subset, the prediction accuracy of the observed variable y to the predicted variable u is not useful.

5-fold cross validation
To cross validate, you can divide you dataset into 5 subset, train your model on 4/5 of the subsets, and test for accuracy on the remaining 1/5 of the subset.
NOTE: There are many ways to subset your data, depending on the question you are asking for your project and also depending on how you wish to write your script. Below is how I initially learned to subset for 5-folds based on the CIMMYT tutorials, but it is not the only way.

Lets start with a for loop, for i from 1 to folds (and we can define as folds=5 for tutorial purposes):
for(i in 1:folds){
}

Within that loop, we want to subset the dataset:
tst <- sample(rep(1:folds,length.out=n)) create a vector tst that has the values 1:folds (in our case 1:5) and a length of n which is the number of lines in X which should be the same number of lines in y

yNA<-y define a new vector yNA with the same values as y
yNA[which(i==tst)]<-NA make 1/5 of the pheno data NA, or omitted, so that later on when we calculate the mixed.solve model, we are only using 4/5 of the dataset. More specifically, this command

Now, yNA[tst] is a vector of the training dataset while yNA[-tst] is a vector of the testing dataset
which means you can specifically pull out the GEBVs u from the test set -tsts to use in your prediction accuracy (PA) calculation. Here yHat_1=ans$u[-tst] we call the test sets GEBVs yHat_1

Predicion Accuracy
The PA of the observed variable y to the predicted variable u. However, since we are doing CV, we want the observed values of the test group y[-tst] to the predicted values/GEBVs, which we just defined above as yHat_1.
results <- append(results,cor(yHat_1,y[-tst])) Add/append this new PA calculated to the results vector

  for(i in 1:folds){   
    tst <-  sample(rep(1:folds,length.out=n))
    yNA<-y
    yNA[which(i==tst)]<-NA 
    ans <- mixed.solve(yNA,K=K)
    yHat_1=ans$u[-tst]
    results <- append(results,cor(yHat_1,y[-tst]))
  }

Replicate CV and Randomization
Creating a function for the rrBLUP analysis helps with replication, but also keeps your code more simple downstream. Also, I use the %dopar% command to distribute the computation over 5 cores at a time.

Define a function called test_CV_parallel with variable x and folds that need to be defined.

test_CV_parallel<-function(x,folds){    
}  

set.seed(x) setting the randomization based on x which will change through the function. As shown below, x is based on the number of cores used to run the function x=cores and we will define cores=1:5. So as the function is using 1, 2, 3, 4, 5 cores, the random seed will change to seed(1)seed(5). This is very useful when you have a heavy computational load.
results=c() create an empty vector called results

return(results)

foreach command
cv_results6 = foreach(cores=1:5, .combine='c') %dopar% test_CV_parallel(x=cores, folds=5) 5 (multi) core computer; dopar will do the command in parallel whereas %do% can be used for a one core computer

And this completes the whole Genomic Prediction Analysis:

#Function
test_CV_parallel<-function(x,folds){   
  set.seed(x)
  results=c()
  for(i in 1:folds){   
    tst <-  sample(rep(1:folds,length.out=n))
    yNA<-y
    yNA[which(i==tst)]<-NA 
    ans <- mixed.solve(yNA,K=K)
    yHat_1=ans$u[-tst]
    results <- append(results,cor(yHat_1,y[-tst]))
  }
  return(results)
}
#Analysis
cv_results6=foreach(cores=1:5, .combine='c') %dopar% test_CV_parallel(x=cores, folds=5) #5 (multi) core computer; dopar will do the command in parallel
# cv_results6=foreach(cores=1:5, .combine='c') %do% test_CV_parallel(x=cores, folds=5) #one core computer

Data Organization
The last portion is organizing the output into a workable dataframe the I can use to plot of combined with other results downstream.

accuracy5 = as.data.frame(matrix(cv_results6)) the matrix cv_results6 is made into a dataframe called aaccuracy5
accuracy5$model<-"rrBLUP" add in a new column model into the df and fill the column with the characters rrBLUP
names(accuracy5) <- c("PA", "model") rename the column names to PA and model
accuracyrr<-accuracy5 rename the df to accuracyrr

Now if you’ve already defined your function upstream, all you need to write for each run is:

#Analysis
cv_results6=foreach(cores=1:5, .combine='c') %dopar% test_CV_parallel(x=cores, folds=5) #5 (multi) core computer; dopar will do the command in parallel

#Data Organization
accuracy5 = as.data.frame(matrix(cv_results6)) 
accuracy5$model<-"rrBLUP"; names(accuracy5) <- c("PA",  "model")      
accuracyrr<-accuracy5

Note: folds=5 does not need to be defined anymore, since it is integrated into the function as x and folds.


RKHS

The 5-fold cross validation subsetting and data organization is all the same as explained in the rrBLUP section, however the model within the CV is now changed. An explanation of the RKHS model is below.

#Parameters defined
nIter = 12000
burnIn=2000
h <- 0.5
D<-(as.matrix(dist(X,method='euclidean'))^2)/p
U<-exp(-h*D) 
# y = y[,2]

#Function
test_CV_parallel<-function(x,folds){   
  set.seed(x)
  results=c()
  for(i in 1:folds){    
    tst <-  sample(rep(1:folds,length.out=n))
    yNA<-y
    yNA[which(i==tst)]<-NA 
    ETA<-list(list(K=U,model='RKHS'))
    fm<-BGLR(y=yNA,ETA=ETA,nIter=nIter, burnIn=burnIn)
    yHat_1=fm$yHat[-tst]
    results <- append(results,cor(yHat_1,y[-tst]))
  }
  return(results)
}

#Analysis
cv_results5=foreach(cores=1:5, .combine='c') %dopar% test_CV_parallel(x=cores, folds=5)

#Data Organization
accuracy5 = as.data.frame(matrix(cv_results5)) 
accuracy5$model<-"RKHS"
names(accuracy5) <- c("PA", "model")        
accuracyrk<-accuracy5 

As stated in the Campos Lab document:

h is a bandwidth parameter controlling how fast the kernel decay as the two points, (xi , xi’ ), get further apart

I have attempted to determine h with my dataset founf in this R Notebook

h <- 0.5 just for tutorial purposes, we will set h to 0.5. However I suggest reading the Campos Lab document section 4 and Campos et al., 2010 to better understand h

Distance function and reproducing kernel
D<-(as.matrix(dist(X,method='euclidean'))^2)/p
U<-exp(-h*D)

Fitting the RKHS Model
ETA<-list(list(K=U,model='RKHS'))
fm<-BGLR(y=yNA,ETA=ETA,nIter=nIter, burnIn=burnIn)
fm$yHat[-tst]


Running LASSO

The 5-fold cross validation subsetting and data organization is all the same as explained in the rrBLUP section, however the model within the CV is now changed. An explanation of the LASSO model is below.

#Parameters defined
nIter = 12000
burnIn=2000
# y = y[,2]

#Function
test_CV_parallel<-function(x,folds){   
  set.seed(x)
  results=c()
  for(i in 1:folds){   
    tst <-  sample(rep(1:folds,length.out=n))
    yNA<-y
    yNA[which(i==tst)]<-NA 
    ETA<-list(list(X=X,model='BL'))
    fm<-BGLR(y=yNA, response_type = "gaussian", ETA = ETA, nIter=nIter, burnIn=burnIn) 
    yHat_1=fm$yHat[-tst]
    results <- append(results,cor(yHat_1,y[-tst]))
  }
  return(results)
}

#Analysis
cv_results7=foreach(cores=1:5, .combine='c') %dopar% test_CV_parallel(x=cores, folds=5)

#Data Organization
accuracy5 = as.data.frame(matrix(cv_results7)) 
accuracy5$model<-"LASSO"
names(accuracy5) <- c("PA", "model")      
accuracyBL<-accuracy5

Fitting the RKHS Model
ETA<-list(list(X=X,model='BL'))
fm<-BGLR(y=yNA, response_type = "gaussian", ETA = ETA, nIter=nIter, burnIn=burnIn)


Plots

A simple ggplot boxplot of Prediction Accuracies (PA) across the three models

accuracy <- rbind( accuracyrk, accuracyrr, accuracyBL) #headers: PA model 
# write.csv(accuracy, file="Accuracy_PHSBLUP_20181221.csv", row.names = FALSE)
# accuracy <- read.csv("Accuracy_PHSBLUP_20181221.csv")

ggplot(accuracy, aes(accuracy$model, accuracy$PA, fill = factor(model))) +
  geom_boxplot()+ theme_bw() +
  theme(axis.text = font2,  axis.title = font, strip.text.x = font, plot.title = font)+
  ylab("PA") + xlab("")+ ylim(0.6, 0.9) +
  ggtitle("Wheat | Model Comparison")

Remember to fix you ylim range to (0,1) at first, then minimize it if its hard to see the differences

LS0tDQp0aXRsZTogIkdlbm9taWMgUHJlZGljdGlvbiBNb2RlbHMiDQphdXRob3I6ICJTaGFudGVsIEEgTWFydGluZXoiDQpvdXRwdXQ6DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2RlcHRoOiA1DQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgdGhlbWU6IGZsYXRseQ0KLS0tDQoNCioqQXV0aG9yOioqIFNoYW50ZWwgQS4gTWFydGluZXogIA0KKipQdXJwb3NlOioqIEFuYWx5emUgdGhlIFBIUyBkYXRhIHVuZGVyIG11dGxpcGxlIEdTIG1vZGVscyBhbmQgZGV0ZXJtaW5lIHdoaWNoIG1vZGVsIGJlc3RzIGZpdHMgb3V0IGRhdGEuICAgDQoqKkxhc3QgdXBkYXRlZDoqKiAyMDE5LjAyLjE5ICAgDQoNClRoZSBtYWpvcml0eSBvZiB0aGUgdHV0b3JpYWxzIEkgbGVhcm5lZCBiYXNpY3Mgb2YgZ2Vub21pYyBzZWxlY3Rpb24gY2FtZSBmcm9tIHRoZSB3aGVhdCBbY2ltbXl0IHR1dG9yaWFsXShodHRwOi8vYmdsci5yLWZvcmdlLnItcHJvamVjdC5vcmcvQkdMUi10dXRvcmlhbC5wZGYpIGFuZCBbdmFsaWRhdGlvbiBleGFtcGxlc10oaHR0cHM6Ly9naXRodWIuY29tL2dkbGMvQkdMUi1SL2Jsb2IvbWFzdGVyL2luc3QvbWQvVmFsaWRhdGlvbi5tZCkgYW5kIGZvciByckJMVVAgSSBzdGFydGVkIHdpdGggdGhpcyBbdHV0b3JpYWxdKGh0dHA6Ly9wYmd3b3Jrcy5vcmcvc2l0ZXMvcGJnd29ya3Mub3JnL2ZpbGVzL0ludHJvZHVjdGlvbiUyMHRvJTIwR2Vub21pYyUyMFNlbGVjdGlvbiUyMGluJTIwUi5wZGYpIGFuZCB0aGUgW3JyQkxVUCBDUkFOIHBhZ2VdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9yckJMVVAvcnJCTFVQLnBkZikuICAgDQoNCiMjIyMgSW5wdXQgZGF0YXNldDoNCkZvciBleGFtcGxlIHB1cnBvc2VzLCB3ZSB3aWxsIHVzZSB0aGUgZXhhbXBsZSBkYXRhc2V0IHVzaW5nIENhbXBvcyBldCBhbC4sIDIwMTAgcmF0aGVyIHRoYW4gbXkgcHJvamVjdCBkYXRhc2V0LiAgIA0KYGBge3J9DQojIGluc3RhbGwucGFja2FnZXMoIkJHTFIiKSAjSW5zdGFsbCBwYWNrYWdlIGlmIG5vdCBhbHJlYWR5IGluc3RhbGxlZA0KbGlicmFyeShCR0xSKQ0KZGF0YSh3aGVhdCkNCnkgPC0gd2hlYXQuWSAjUGhlbm90eXBpYyBkYXRhDQpYIDwtIHdoZWF0LlggI0dlbm90eXBpYyBkYXRhDQpuPC1ucm93KFgpOyBwPC1uY29sKFgpICNkZWZpbmluZyBuIGFzIHRoZSByb3cgbnVtYmVyIG9mIG1hdHJpeCBgWGAgYW5kIHAgYXMgdGhlIGNvbCBudW1iZXIgb2YgbWF0cml4IGBYYA0KeVsxOjQsXSAjRWFjaCBjb2x1bW4gaXMgYSBkaWZmZXJlbnQgcGhlbm90eXBlOyBsaW5lcy92YXJpZXRpZXMgKHJvd3MpDQpYWzE6NSwxOjVdICNsaW5lcy92YXJpZXRpZXMocm93cykgYW5kIG1hcmtlcnMgKGNvbCkNCg0KYGBgDQoNCkxvYWQgYWxsIHBhY2thZ2VzIGZvciB0aGUgZW50aXJlIHNjcmlwdCBhbmQgc2V0IHRoZSB3b3JraW5nIGRpcmVjdG9yeSAgIA0KKlJlbWVtYmVyIHRvIGluc3RhbGwgYWxsIHBhY2thZ2VzIG9uY2UgYmVmb3JlIGxvYWRpbmcgdGhlbSBiZWxvdyogIA0KYGBge3IgZXZhbD1GQUxTRX0NCmxpYnJhcnkoQkdMUikNCmxpYnJhcnkocnJCTFVQKQ0KbGlicmFyeShmb3JlYWNoKQ0KbGlicmFyeShkb01DKSAgIyBVc2VkIGZvciBydW5uaW5nIHRoZSBsb29wcyBvbiBtdWx0aXBsZSBjb3JlcyAoZXhhbXBsZSBoZXJlLCA1IGNvcmVzKQ0KcmVnaXN0ZXJEb01DKGNvcmVzPTUpDQoNCmxpYnJhcnkoZ2dwbG90MikNCmZvbnQ8LWVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCAgc2l6ZSA9IDE2KSAgI2dncGxvdCBncmFwaGljYWwgcHJlZmVyZW5jZXMNCmZvbnQyPC1lbGVtZW50X3RleHQoc2l6ZSA9IDE2KQ0KDQojIHNldHdkKCJEOi9QSFMgR2Vub21pYyBTZWxlY3Rpb24gUHJvamVjdC9EYXRhIEFuYWx5c2lzL0dTLzIwMTkwMjE5IikNCg0KYGBgDQoNCi0tLS0tLS0tLS0tLS0NCg0KIyMjIyByckJMVVAgIA0KDQpgYGB7ciBldmFsPUZBTFNFfQ0KI1BhcmFtZXRlcnMgZGVmaW5lZCAgDQpmb2xkcz01ICAgICAgICAgICAgICANCks9QS5tYXQoWCkNCnkgPSB5WywyXQ0KDQojRnVuY3Rpb24NCnRlc3RfQ1ZfcGFyYWxsZWw8LWZ1bmN0aW9uKHgsZm9sZHMpeyAgIA0KICBzZXQuc2VlZCh4KQ0KICByZXN1bHRzPWMoKQ0KICBmb3IoaSBpbiAxOmZvbGRzKXsgICANCiAgICB0c3QgPC0gIHNhbXBsZShyZXAoMTpmb2xkcyxsZW5ndGgub3V0PW4pKQ0KICAgIHlOQTwteQ0KICAgIHlOQVt3aGljaChpPT10c3QpXTwtTkEgDQogICAgYW5zIDwtIG1peGVkLnNvbHZlKHlOQSxLPUspDQogICAgeUhhdF8xPWFucyR1Wy10c3RdDQogICAgcmVzdWx0cyA8LSBhcHBlbmQocmVzdWx0cyxjb3IoeUhhdF8xLHlbLXRzdF0pKQ0KICB9DQogIHJldHVybihyZXN1bHRzKQ0KfQ0KDQojQW5hbHlzaXMNCmN2X3Jlc3VsdHM2PWZvcmVhY2goY29yZXM9MTo1LCAuY29tYmluZT0nYycpICVkb3BhciUgdGVzdF9DVl9wYXJhbGxlbCh4PWNvcmVzLCBmb2xkcz01KSAjNSAobXVsdGkpIGNvcmUgY29tcHV0ZXI7IGRvcGFyIHdpbGwgZG8gdGhlIGNvbW1hbmQgaW4gcGFyYWxsZWwNCiMgY3ZfcmVzdWx0czY9Zm9yZWFjaChjb3Jlcz0xOjUsIC5jb21iaW5lPSdjJykgJWRvJSB0ZXN0X0NWX3BhcmFsbGVsKHg9Y29yZXMsIGZvbGRzPTUpICNvbmUgY29yZSBjb21wdXRlcg0KDQojRGF0YSBPcmdhbml6YXRpb24NCmFjY3VyYWN5NSA9IGFzLmRhdGEuZnJhbWUobWF0cml4KGN2X3Jlc3VsdHM2KSkgDQphY2N1cmFjeTUkbW9kZWw8LSJyckJMVVAiDQpuYW1lcyhhY2N1cmFjeTUpIDwtIGMoIlBBIiwgICJtb2RlbCIpICAgICAgDQphY2N1cmFjeXJyPC1hY2N1cmFjeTUNCmBgYCANCg0KKipUaGUgc2NyaXB0IHN0ZXAgYnkgc3RlcDoqKiAgICANCg0KKipMZXRzIHN0YXJ0IGZyb20gdGhlIGJhc2ljcyB3aXRoaW4gYWxsIHRoZSBsb29wcyBhbmQgd29yayBvdXR3YXJkcy4qKiAgDQpSdW5uaW5nIGdlbm9taWMgcHJlZGljdGlvbiBmb3IgYSBwaGVub3R5cGUgYHlgIHdoaWxlIHVzaW5nIGEga2luc2hpcCBtYXRyaXggYEtgIGNvbnNpc3RzIG9mIHRoZSBmb2xsb3dpbmcgY29tbWFuZHM6ICANCmBLPUEubWF0KFgpYCBUaGUgQS5tYXQgZnVuY3Rpb24gaW4gcnJCTFVQIHdpbGwgY2FsY3VsYXRlIHRoZSBhZGRpdGl0dmUgcmVsYXRpb25zaGlwIG1hdHJpeCAgDQpgeSA9IHlbLDJdYCBkZWZpbmUgd2hpY2ggcGhlbm90eXBlIHlvdSB3YW50IHRvIHByZWRpY3QgKHRoaXMgd2lsbCBsZWF2ZSB5IGFzIGp1c3QgYSB2ZWN0b3Igb2YgbiB4IDEpICANCmBwcmVkIDwtIG1peGVkLnNvbHZlKHksSz1LKWAgbWl4ZWQuc29sdmUgc2FsY3VsYXRlcyBtYXhpbXVtLWxpa2VsaWhvb2QgKE1ML1JFTUwpIHNvbHV0aW9ucyBmb3IgbWl4ZWQgbW9kZWxzLCBidXQgc2luY2UgeW91IGFyZSB1c2luZyBgS2AgaW4geW91ciBtb2RlbCwgaXQgd2lsbCBhbHNvIGNhbGN1bGF0ZSB0aGUgR0VCVnMgIA0KYHByZWQkdVtdYCB0aGUgb3V0cHV0IGBwcmVkYCB3aWxsIGNvbnRhaW4gYSBkYXRhZnJhbWUgYHVgIHRoYXQgY29udGFpbnRzIHRoZSBHRUJWcw0KICANCkhvd2V2ZXIsIHRoaXMgaXMgdXNpbmcgYWxsIDEwMCUgb2YgeW91ciBkYXRhLCB0byBjYWxjdWxhdGUgdGhlIEdFQlZzLCBzbyBzaW5jZSB5b3UgYXJlbnQgdHJhaW5pbmcgb24gYSBzdWJzZXQgb2YgdGhlIGRhdGEgdG8gcHJlZGljdCBhbm90aGVyIHN1YnNldCwgdGhlIHByZWRpY3Rpb24gYWNjdXJhY3kgb2YgdGhlIG9ic2VydmVkIHZhcmlhYmxlIGB5YCB0byB0aGUgcHJlZGljdGVkIHZhcmlhYmxlIGB1YCBpcyBub3QgdXNlZnVsLiAgDQoNCioqNS1mb2xkIGNyb3NzIHZhbGlkYXRpb24gKiogIA0KVG8gY3Jvc3MgdmFsaWRhdGUsIHlvdSBjYW4gZGl2aWRlIHlvdSBkYXRhc2V0IGludG8gNSBzdWJzZXQsIHRyYWluIHlvdXIgbW9kZWwgb24gNC81IG9mIHRoZSBzdWJzZXRzLCBhbmQgdGVzdCBmb3IgYWNjdXJhY3kgb24gdGhlIHJlbWFpbmluZyAxLzUgb2YgdGhlIHN1YnNldC4gICANCioqTk9URSoqOiBUaGVyZSBhcmUgbWFueSB3YXlzIHRvIHN1YnNldCB5b3VyIGRhdGEsIGRlcGVuZGluZyBvbiB0aGUgcXVlc3Rpb24geW91IGFyZSBhc2tpbmcgZm9yIHlvdXIgcHJvamVjdCBhbmQgYWxzbyBkZXBlbmRpbmcgb24gaG93IHlvdSB3aXNoIHRvIHdyaXRlIHlvdXIgc2NyaXB0LiBCZWxvdyBpcyBob3cgSSBpbml0aWFsbHkgbGVhcm5lZCB0byBzdWJzZXQgZm9yIDUtZm9sZHMgYmFzZWQgb24gdGhlIENJTU1ZVCB0dXRvcmlhbHMsIGJ1dCBpdCBpcyBub3QgdGhlIG9ubHkgd2F5LiANCg0KTGV0cyBzdGFydCB3aXRoIGEgZm9yIGxvb3AsIGZvciBgaWAgZnJvbSAxIHRvIGZvbGRzIChhbmQgd2UgY2FuIGRlZmluZSBhcyBgZm9sZHM9NWAgZm9yIHR1dG9yaWFsIHB1cnBvc2VzKTogIA0KYCAgZm9yKGkgaW4gMTpmb2xkcyl7YCAgICAgDQpgICB9YCAgIA0KICANCldpdGhpbiB0aGF0IGxvb3AsIHdlIHdhbnQgdG8gc3Vic2V0IHRoZSBkYXRhc2V0OiAgDQpgdHN0IDwtICBzYW1wbGUocmVwKDE6Zm9sZHMsbGVuZ3RoLm91dD1uKSlgIGNyZWF0ZSBhIHZlY3RvciBgdHN0YCB0aGF0IGhhcyB0aGUgdmFsdWVzIGAxOmZvbGRzYCAoaW4gb3VyIGNhc2UgMTo1KSBhbmQgYSBgbGVuZ3RoYCBvZiBgbmAgd2hpY2ggaXMgdGhlIG51bWJlciBvZiBsaW5lcyBpbiBgWGAgd2hpY2ggc2hvdWxkIGJlIHRoZSBzYW1lIG51bWJlciBvZiBsaW5lcyBpbiBgeWAgICAgIA0KDQpgeU5BPC15YCBkZWZpbmUgYSBuZXcgdmVjdG9yIGB5TkFgIHdpdGggdGhlIHNhbWUgdmFsdWVzIGFzIGB5YCAgICANCmB5TkFbd2hpY2goaT09dHN0KV08LU5BYCBtYWtlIDEvNSBvZiB0aGUgcGhlbm8gZGF0YSBOQSwgb3Igb21pdHRlZCwgc28gdGhhdCBsYXRlciBvbiB3aGVuIHdlIGNhbGN1bGF0ZSB0aGUgbWl4ZWQuc29sdmUgbW9kZWwsIHdlIGFyZSBvbmx5IHVzaW5nIDQvNSBvZiB0aGUgZGF0YXNldC4gTW9yZSBzcGVjaWZpY2FsbHksIHRoaXMgY29tbWFuZCAgICANCg0KKipOb3csIGB5TkFbdHN0XWAgaXMgYSB2ZWN0b3Igb2YgdGhlIHRyYWluaW5nIGRhdGFzZXQgd2hpbGUgYHlOQVstdHN0XWAgaXMgYSB2ZWN0b3Igb2YgdGhlIHRlc3RpbmcgZGF0YXNldCoqICAgDQp3aGljaCBtZWFucyB5b3UgY2FuIHNwZWNpZmljYWxseSBwdWxsIG91dCB0aGUgR0VCVnMgYHVgIGZyb20gdGhlIHRlc3Qgc2V0IGAtdHN0c2AgdG8gdXNlIGluIHlvdXIgcHJlZGljdGlvbiBhY2N1cmFjeSAoUEEpIGNhbGN1bGF0aW9uLiBIZXJlIGB5SGF0XzE9YW5zJHVbLXRzdF1gIHdlIGNhbGwgdGhlIHRlc3Qgc2V0cyBHRUJWcyBgeUhhdF8xYCAgIA0KDQoqKlByZWRpY2lvbiBBY2N1cmFjeSoqICANClRoZSBQQSBvZiB0aGUgb2JzZXJ2ZWQgdmFyaWFibGUgYHlgIHRvIHRoZSBwcmVkaWN0ZWQgdmFyaWFibGUgYHVgLiBIb3dldmVyLCBzaW5jZSB3ZSBhcmUgZG9pbmcgQ1YsIHdlIHdhbnQgdGhlIG9ic2VydmVkIHZhbHVlcyBvZiB0aGUgdGVzdCBncm91cCBgeVstdHN0XWAgdG8gdGhlIHByZWRpY3RlZCB2YWx1ZXMvR0VCVnMsIHdoaWNoIHdlIGp1c3QgZGVmaW5lZCBhYm92ZSBhcyBgeUhhdF8xYC4gICAgDQpgcmVzdWx0cyA8LSBhcHBlbmQocmVzdWx0cyxjb3IoeUhhdF8xLHlbLXRzdF0pKWAgQWRkL2FwcGVuZCB0aGlzIG5ldyBQQSBjYWxjdWxhdGVkIHRvIHRoZSByZXN1bHRzIHZlY3RvciANCg0KYGBge3IgZXZhbD1GQUxTRX0NCiAgZm9yKGkgaW4gMTpmb2xkcyl7ICAgDQogICAgdHN0IDwtICBzYW1wbGUocmVwKDE6Zm9sZHMsbGVuZ3RoLm91dD1uKSkNCiAgICB5TkE8LXkNCiAgICB5TkFbd2hpY2goaT09dHN0KV08LU5BIA0KICAgIGFucyA8LSBtaXhlZC5zb2x2ZSh5TkEsSz1LKQ0KICAgIHlIYXRfMT1hbnMkdVstdHN0XQ0KICAgIHJlc3VsdHMgPC0gYXBwZW5kKHJlc3VsdHMsY29yKHlIYXRfMSx5Wy10c3RdKSkNCiAgfQ0KYGBgDQoNCioqUmVwbGljYXRlIENWIGFuZCBSYW5kb21pemF0aW9uKiogIA0KQ3JlYXRpbmcgYSBmdW5jdGlvbiBmb3IgdGhlIHJyQkxVUCBhbmFseXNpcyBoZWxwcyB3aXRoIHJlcGxpY2F0aW9uLCBidXQgYWxzbyBrZWVwcyB5b3VyIGNvZGUgbW9yZSBzaW1wbGUgZG93bnN0cmVhbS4gQWxzbywgIEkgdXNlIHRoZSBgJWRvcGFyJWAgY29tbWFuZCB0byBkaXN0cmlidXRlIHRoZSBjb21wdXRhdGlvbiBvdmVyIDUgY29yZXMgYXQgYSB0aW1lLiAgIA0KDQpEZWZpbmUgYSBmdW5jdGlvbiBjYWxsZWQgYHRlc3RfQ1ZfcGFyYWxsZWxgIHdpdGggdmFyaWFibGUgYHhgIGFuZCBgZm9sZHNgIHRoYXQgbmVlZCB0byBiZSBkZWZpbmVkLiAgDQpgYGB7ciBldmFsPUZBTFNFfQ0KdGVzdF9DVl9wYXJhbGxlbDwtZnVuY3Rpb24oeCxmb2xkcyl7ICAgIA0KfSAgDQpgYGANCg0KDQpgc2V0LnNlZWQoeClgIHNldHRpbmcgdGhlIHJhbmRvbWl6YXRpb24gYmFzZWQgb24gYHhgIHdoaWNoIHdpbGwgY2hhbmdlIHRocm91Z2ggdGhlIGZ1bmN0aW9uLiBBcyBzaG93biBiZWxvdywgeCBpcyBiYXNlZCBvbiB0aGUgbnVtYmVyIG9mIGNvcmVzIHVzZWQgdG8gcnVuIHRoZSBmdW5jdGlvbiBgeD1jb3Jlc2AgYW5kIHdlIHdpbGwgZGVmaW5lIGBjb3Jlcz0xOjVgLiBTbyBhcyB0aGUgZnVuY3Rpb24gaXMgdXNpbmcgMSwgMiwgMywgNCwgNSBjb3JlcywgdGhlIHJhbmRvbSBzZWVkIHdpbGwgY2hhbmdlIHRvIGBzZWVkKDEpYCAuLi4gYHNlZWQoNSlgLiBUaGlzIGlzIHZlcnkgdXNlZnVsIHdoZW4geW91IGhhdmUgYSBoZWF2eSBjb21wdXRhdGlvbmFsIGxvYWQuICANCmByZXN1bHRzPWMoKWAgIGNyZWF0ZSBhbiBlbXB0eSB2ZWN0b3IgY2FsbGVkIGByZXN1bHRzYA0KDQpgcmV0dXJuKHJlc3VsdHMpYCAgDQoNCioqZm9yZWFjaCBjb21tYW5kKiogIA0KYGN2X3Jlc3VsdHM2ID0gZm9yZWFjaChjb3Jlcz0xOjUsIC5jb21iaW5lPSdjJykgJWRvcGFyJSB0ZXN0X0NWX3BhcmFsbGVsKHg9Y29yZXMsIGZvbGRzPTUpYCA1IChtdWx0aSkgY29yZSBjb21wdXRlcjsgZG9wYXIgd2lsbCBkbyB0aGUgY29tbWFuZCBpbiBwYXJhbGxlbCB3aGVyZWFzICVkbyUgY2FuIGJlIHVzZWQgZm9yIGEgb25lIGNvcmUgY29tcHV0ZXINCg0KDQpBbmQgdGhpcyBjb21wbGV0ZXMgdGhlIHdob2xlICoqR2Vub21pYyBQcmVkaWN0aW9uIEFuYWx5c2lzKio6ICANCmBgYHtyIGV2YWw9RkFMU0V9DQojRnVuY3Rpb24NCnRlc3RfQ1ZfcGFyYWxsZWw8LWZ1bmN0aW9uKHgsZm9sZHMpeyAgIA0KICBzZXQuc2VlZCh4KQ0KICByZXN1bHRzPWMoKQ0KICBmb3IoaSBpbiAxOmZvbGRzKXsgICANCiAgICB0c3QgPC0gIHNhbXBsZShyZXAoMTpmb2xkcyxsZW5ndGgub3V0PW4pKQ0KICAgIHlOQTwteQ0KICAgIHlOQVt3aGljaChpPT10c3QpXTwtTkEgDQogICAgYW5zIDwtIG1peGVkLnNvbHZlKHlOQSxLPUspDQogICAgeUhhdF8xPWFucyR1Wy10c3RdDQogICAgcmVzdWx0cyA8LSBhcHBlbmQocmVzdWx0cyxjb3IoeUhhdF8xLHlbLXRzdF0pKQ0KICB9DQogIHJldHVybihyZXN1bHRzKQ0KfQ0KI0FuYWx5c2lzDQpjdl9yZXN1bHRzNj1mb3JlYWNoKGNvcmVzPTE6NSwgLmNvbWJpbmU9J2MnKSAlZG9wYXIlIHRlc3RfQ1ZfcGFyYWxsZWwoeD1jb3JlcywgZm9sZHM9NSkgIzUgKG11bHRpKSBjb3JlIGNvbXB1dGVyOyBkb3BhciB3aWxsIGRvIHRoZSBjb21tYW5kIGluIHBhcmFsbGVsDQojIGN2X3Jlc3VsdHM2PWZvcmVhY2goY29yZXM9MTo1LCAuY29tYmluZT0nYycpICVkbyUgdGVzdF9DVl9wYXJhbGxlbCh4PWNvcmVzLCBmb2xkcz01KSAjb25lIGNvcmUgY29tcHV0ZXINCmBgYA0KDQoqKkRhdGEgT3JnYW5pemF0aW9uKiogIA0KVGhlIGxhc3QgcG9ydGlvbiBpcyBvcmdhbml6aW5nIHRoZSBvdXRwdXQgaW50byBhIHdvcmthYmxlIGRhdGFmcmFtZSB0aGUgSSBjYW4gdXNlIHRvIHBsb3Qgb2YgY29tYmluZWQgd2l0aCBvdGhlciByZXN1bHRzIGRvd25zdHJlYW0uICANCg0KYGFjY3VyYWN5NSA9IGFzLmRhdGEuZnJhbWUobWF0cml4KGN2X3Jlc3VsdHM2KSlgIHRoZSBtYXRyaXggYGN2X3Jlc3VsdHM2YCBpcyBtYWRlIGludG8gYSBkYXRhZnJhbWUgY2FsbGVkIGBhYWNjdXJhY3k1YCAgICANCmBhY2N1cmFjeTUkbW9kZWw8LSJyckJMVVAiYCBhZGQgaW4gYSBuZXcgY29sdW1uIGBtb2RlbGAgaW50byB0aGUgZGYgYW5kIGZpbGwgdGhlIGNvbHVtbiB3aXRoIHRoZSBjaGFyYWN0ZXJzIGByckJMVVBgICANCmBuYW1lcyhhY2N1cmFjeTUpIDwtIGMoIlBBIiwgICJtb2RlbCIpYCByZW5hbWUgdGhlIGNvbHVtbiBuYW1lcyB0byBgUEFgIGFuZCBgbW9kZWxgICAgICAgICAgDQpgYWNjdXJhY3lycjwtYWNjdXJhY3k1YCByZW5hbWUgdGhlIGRmIHRvIGBhY2N1cmFjeXJyYCAgICANCg0KDQpOb3cgaWYgeW91J3ZlIGFscmVhZHkgZGVmaW5lZCB5b3VyIGZ1bmN0aW9uIHVwc3RyZWFtLCBhbGwgeW91IG5lZWQgdG8gd3JpdGUgZm9yIGVhY2ggcnVuIGlzOiAgDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQojQW5hbHlzaXMNCmN2X3Jlc3VsdHM2PWZvcmVhY2goY29yZXM9MTo1LCAuY29tYmluZT0nYycpICVkb3BhciUgdGVzdF9DVl9wYXJhbGxlbCh4PWNvcmVzLCBmb2xkcz01KSAjNSAobXVsdGkpIGNvcmUgY29tcHV0ZXI7IGRvcGFyIHdpbGwgZG8gdGhlIGNvbW1hbmQgaW4gcGFyYWxsZWwNCg0KI0RhdGEgT3JnYW5pemF0aW9uDQphY2N1cmFjeTUgPSBhcy5kYXRhLmZyYW1lKG1hdHJpeChjdl9yZXN1bHRzNikpIA0KYWNjdXJhY3k1JG1vZGVsPC0icnJCTFVQIjsgbmFtZXMoYWNjdXJhY3k1KSA8LSBjKCJQQSIsICAibW9kZWwiKSAgICAgIA0KYWNjdXJhY3lycjwtYWNjdXJhY3k1DQoNCmBgYA0KDQpOb3RlOiBgZm9sZHM9NWAgZG9lcyBub3QgbmVlZCB0byBiZSBkZWZpbmVkIGFueW1vcmUsIHNpbmNlIGl0IGlzIGludGVncmF0ZWQgaW50byB0aGUgZnVuY3Rpb24gYXMgYHhgIGFuZCBgZm9sZHNgLiANCg0KLS0tLS0tLS0tLQ0KDQojIyMjIFJLSFMgIA0KDQpUaGUgNS1mb2xkIGNyb3NzIHZhbGlkYXRpb24gc3Vic2V0dGluZyBhbmQgZGF0YSBvcmdhbml6YXRpb24gaXMgYWxsIHRoZSBzYW1lIGFzIGV4cGxhaW5lZCBpbiB0aGUgcnJCTFVQIHNlY3Rpb24sIGhvd2V2ZXIgdGhlIG1vZGVsIHdpdGhpbiB0aGUgQ1YgaXMgbm93IGNoYW5nZWQuIEFuIGV4cGxhbmF0aW9uIG9mIHRoZSBSS0hTIG1vZGVsIGlzIGJlbG93LiANCg0KYGBge3IgZXZhbD1GQUxTRX0gDQojUGFyYW1ldGVycyBkZWZpbmVkDQpuSXRlciA9IDEyMDAwDQpidXJuSW49MjAwMA0KaCA8LSAwLjUNCkQ8LShhcy5tYXRyaXgoZGlzdChYLG1ldGhvZD0nZXVjbGlkZWFuJykpXjIpL3ANClU8LWV4cCgtaCpEKSANCiMgeSA9IHlbLDJdDQoNCiNGdW5jdGlvbg0KdGVzdF9DVl9wYXJhbGxlbDwtZnVuY3Rpb24oeCxmb2xkcyl7ICAgDQogIHNldC5zZWVkKHgpDQogIHJlc3VsdHM9YygpDQogIGZvcihpIGluIDE6Zm9sZHMpeyAgICANCiAgICB0c3QgPC0gIHNhbXBsZShyZXAoMTpmb2xkcyxsZW5ndGgub3V0PW4pKQ0KICAgIHlOQTwteQ0KICAgIHlOQVt3aGljaChpPT10c3QpXTwtTkEgDQogICAgRVRBPC1saXN0KGxpc3QoSz1VLG1vZGVsPSdSS0hTJykpDQogICAgZm08LUJHTFIoeT15TkEsRVRBPUVUQSxuSXRlcj1uSXRlciwgYnVybkluPWJ1cm5JbikNCiAgICB5SGF0XzE9Zm0keUhhdFstdHN0XQ0KICAgIHJlc3VsdHMgPC0gYXBwZW5kKHJlc3VsdHMsY29yKHlIYXRfMSx5Wy10c3RdKSkNCiAgfQ0KICByZXR1cm4ocmVzdWx0cykNCn0NCg0KI0FuYWx5c2lzDQpjdl9yZXN1bHRzNT1mb3JlYWNoKGNvcmVzPTE6NSwgLmNvbWJpbmU9J2MnKSAlZG9wYXIlIHRlc3RfQ1ZfcGFyYWxsZWwoeD1jb3JlcywgZm9sZHM9NSkNCg0KI0RhdGEgT3JnYW5pemF0aW9uDQphY2N1cmFjeTUgPSBhcy5kYXRhLmZyYW1lKG1hdHJpeChjdl9yZXN1bHRzNSkpIA0KYWNjdXJhY3k1JG1vZGVsPC0iUktIUyINCm5hbWVzKGFjY3VyYWN5NSkgPC0gYygiUEEiLCAibW9kZWwiKSAgICAgICAgDQphY2N1cmFjeXJrPC1hY2N1cmFjeTUgDQoNCmBgYA0KDQoNCkFzIHN0YXRlZCBpbiB0aGUgW0NhbXBvcyBMYWIgZG9jdW1lbnRdKGh0dHBzOi8vanZhbmRlcncudW5lLmVkdS5hdS9HZGxDSGFuZG91dHMucGRmKTogDQoNCj4gaCBpcyBhIGJhbmR3aWR0aCBwYXJhbWV0ZXIgY29udHJvbGxpbmcgaG93IGZhc3QgdGhlIGtlcm5lbCBkZWNheSBhcyB0aGUgdHdvIHBvaW50cywgKHhpICwgeGknICksIGdldCBmdXJ0aGVyIGFwYXJ0DQoNCkkgaGF2ZSBhdHRlbXB0ZWQgdG8gZGV0ZXJtaW5lIGBoYCB3aXRoIG15IGRhdGFzZXQgZm91bmYgaW4gdGhpcyBbUiBOb3RlYm9va10oaHR0cHM6Ly9uYnZpZXdlci5qdXB5dGVyLm9yZy9naXRodWIvc2hhbnRlbC1tYXJ0aW5lei9zaGFudGVsLW1hcnRpbmV6LmdpdGh1Yi5pby9ibG9iL21hc3Rlci9SbWQlMjBQcm90b2NvbHMvQmFuZHdpZHRoUGFyYW1ldGVyX1JLSFMubmIuaHRtbD9mbHVzaF9jYWNoZT10cnVlKSAgDQoNCmBoIDwtIDAuNWAganVzdCBmb3IgdHV0b3JpYWwgcHVycG9zZXMsIHdlIHdpbGwgc2V0IGggdG8gMC41LiBIb3dldmVyIEkgc3VnZ2VzdCByZWFkaW5nIHRoZSBbQ2FtcG9zIExhYiBkb2N1bWVudF0oaHR0cHM6Ly9qdmFuZGVydy51bmUuZWR1LmF1L0dkbENIYW5kb3V0cy5wZGYpIHNlY3Rpb24gNCBhbmQgW0NhbXBvcyBldCBhbC4sIDIwMTBdKGh0dHBzOi8vd3d3LmNhbWJyaWRnZS5vcmcvY29yZS9qb3VybmFscy9nZW5ldGljcy1yZXNlYXJjaC9hcnRpY2xlL3NlbWlwYXJhbWV0cmljLWdlbm9taWNlbmFibGVkLXByZWRpY3Rpb24tb2YtZ2VuZXRpYy12YWx1ZXMtdXNpbmctcmVwcm9kdWNpbmcta2VybmVsLWhpbGJlcnQtc3BhY2VzLW1ldGhvZHMvMkI4MjM5MTZDRjRENzZGQUU2QkM4MTQ1NUZENzNBQkIvY29yZS1yZWFkZXIpIHRvIGJldHRlciB1bmRlcnN0YW5kIGBoYCAgIA0KDQoqKkRpc3RhbmNlIGZ1bmN0aW9uIGFuZCByZXByb2R1Y2luZyBrZXJuZWwqKiAgDQpgRDwtKGFzLm1hdHJpeChkaXN0KFgsbWV0aG9kPSdldWNsaWRlYW4nKSleMikvcGAgIA0KYFU8LWV4cCgtaCpEKWAgICANCg0KKipGaXR0aW5nIHRoZSBSS0hTIE1vZGVsKiogIA0KYEVUQTwtbGlzdChsaXN0KEs9VSxtb2RlbD0nUktIUycpKWAgIA0KYGZtPC1CR0xSKHk9eU5BLEVUQT1FVEEsbkl0ZXI9bkl0ZXIsIGJ1cm5Jbj1idXJuSW4pYCAgDQpgZm0keUhhdFstdHN0XWAgIA0KIA0KLS0tLS0tLS0tLQ0KDQojIyMjIFJ1bm5pbmcgTEFTU08gIA0KDQpUaGUgNS1mb2xkIGNyb3NzIHZhbGlkYXRpb24gc3Vic2V0dGluZyBhbmQgZGF0YSBvcmdhbml6YXRpb24gaXMgYWxsIHRoZSBzYW1lIGFzIGV4cGxhaW5lZCBpbiB0aGUgcnJCTFVQIHNlY3Rpb24sIGhvd2V2ZXIgdGhlIG1vZGVsIHdpdGhpbiB0aGUgQ1YgaXMgbm93IGNoYW5nZWQuIEFuIGV4cGxhbmF0aW9uIG9mIHRoZSBMQVNTTyBtb2RlbCBpcyBiZWxvdy4gDQoNCmBgYHtyIGV2YWw9RkFMU0V9DQojUGFyYW1ldGVycyBkZWZpbmVkDQpuSXRlciA9IDEyMDAwDQpidXJuSW49MjAwMA0KIyB5ID0geVssMl0NCg0KI0Z1bmN0aW9uDQp0ZXN0X0NWX3BhcmFsbGVsPC1mdW5jdGlvbih4LGZvbGRzKXsgICANCiAgc2V0LnNlZWQoeCkNCiAgcmVzdWx0cz1jKCkNCiAgZm9yKGkgaW4gMTpmb2xkcyl7ICAgDQogICAgdHN0IDwtICBzYW1wbGUocmVwKDE6Zm9sZHMsbGVuZ3RoLm91dD1uKSkNCiAgICB5TkE8LXkNCiAgICB5TkFbd2hpY2goaT09dHN0KV08LU5BIA0KICAgIEVUQTwtbGlzdChsaXN0KFg9WCxtb2RlbD0nQkwnKSkNCiAgICBmbTwtQkdMUih5PXlOQSwgcmVzcG9uc2VfdHlwZSA9ICJnYXVzc2lhbiIsIEVUQSA9IEVUQSwgbkl0ZXI9bkl0ZXIsIGJ1cm5Jbj1idXJuSW4pIA0KICAgIHlIYXRfMT1mbSR5SGF0Wy10c3RdDQogICAgcmVzdWx0cyA8LSBhcHBlbmQocmVzdWx0cyxjb3IoeUhhdF8xLHlbLXRzdF0pKQ0KICB9DQogIHJldHVybihyZXN1bHRzKQ0KfQ0KDQojQW5hbHlzaXMNCmN2X3Jlc3VsdHM3PWZvcmVhY2goY29yZXM9MTo1LCAuY29tYmluZT0nYycpICVkb3BhciUgdGVzdF9DVl9wYXJhbGxlbCh4PWNvcmVzLCBmb2xkcz01KQ0KDQojRGF0YSBPcmdhbml6YXRpb24NCmFjY3VyYWN5NSA9IGFzLmRhdGEuZnJhbWUobWF0cml4KGN2X3Jlc3VsdHM3KSkgDQphY2N1cmFjeTUkbW9kZWw8LSJMQVNTTyINCm5hbWVzKGFjY3VyYWN5NSkgPC0gYygiUEEiLCAibW9kZWwiKSAgICAgIA0KYWNjdXJhY3lCTDwtYWNjdXJhY3k1DQoNCmBgYCANCg0KKipGaXR0aW5nIHRoZSBSS0hTIE1vZGVsKiogIA0KYEVUQTwtbGlzdChsaXN0KFg9WCxtb2RlbD0nQkwnKSlgICAgIA0KYGZtPC1CR0xSKHk9eU5BLCByZXNwb25zZV90eXBlID0gImdhdXNzaWFuIiwgRVRBID0gRVRBLCBuSXRlcj1uSXRlciwgYnVybkluPWJ1cm5JbikgYCAgICANCg0KLS0tLS0tLS0tLQ0KIyMjIyBQbG90cyAgDQpBIHNpbXBsZSBnZ3Bsb3QgYm94cGxvdCAgb2YgUHJlZGljdGlvbiBBY2N1cmFjaWVzIChQQSkgYWNyb3NzIHRoZSB0aHJlZSBtb2RlbHMgIA0KYGBge3IgZXZhbD1GQUxTRX0NCmFjY3VyYWN5IDwtIHJiaW5kKCBhY2N1cmFjeXJrLCBhY2N1cmFjeXJyLCBhY2N1cmFjeUJMKSAjaGVhZGVyczogUEEgbW9kZWwgDQojIHdyaXRlLmNzdihhY2N1cmFjeSwgZmlsZT0iQWNjdXJhY3lfUEhTQkxVUF8yMDE4MTIyMS5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkNCiMgYWNjdXJhY3kgPC0gcmVhZC5jc3YoIkFjY3VyYWN5X1BIU0JMVVBfMjAxODEyMjEuY3N2IikNCg0KZ2dwbG90KGFjY3VyYWN5LCBhZXMoYWNjdXJhY3kkbW9kZWwsIGFjY3VyYWN5JFBBLCBmaWxsID0gZmFjdG9yKG1vZGVsKSkpICsNCiAgZ2VvbV9ib3hwbG90KCkrIHRoZW1lX2J3KCkgKw0KICB0aGVtZShheGlzLnRleHQgPSBmb250MiwgIGF4aXMudGl0bGUgPSBmb250LCBzdHJpcC50ZXh0LnggPSBmb250LCBwbG90LnRpdGxlID0gZm9udCkrDQogIHlsYWIoIlBBIikgKyB4bGFiKCIiKSsgeWxpbSgwLjYsIDAuOSkgKw0KICBnZ3RpdGxlKCJXaGVhdCB8IE1vZGVsIENvbXBhcmlzb24iKQ0KYGBgDQoNClJlbWVtYmVyIHRvIGZpeCB5b3UgYHlsaW1gIHJhbmdlIHRvIGAoMCwxKWAgYXQgZmlyc3QsIHRoZW4gbWluaW1pemUgaXQgaWYgaXRzIGhhcmQgdG8gc2VlIHRoZSBkaWZmZXJlbmNlcyAgDQo=