I am going to demonstrate MCMC using the Metropolis-Hastings
algorithm as described on page 359 of Professor Michael Lavine’s book
Introduction to Statistical Thought. You can access this free book under
the ‘Resources’ section of the class syllabus. This book is an excellent
resource for Bayesians statistics and statistics generally. I have
included a screen shot below:
y~normal(mu, sd) mu<-bo + b1*x
bo~normal(0,sd=100) b1~normal(0,sd=100) sd~gamma(1,1/100)
Here is the basic algorithm:
Choose a proposal density g(thetaStar | theta)
For each component of the parameter vector (lets call this
parameter theta), generate a proposed parameter. Lets call this
thetaStar
Calculate numerator <-
p(thetaStar|y)*g(theta|thetaStar)
Calculate denominator <-
p(theta|y)*g(thetaStar|theta)
q<-numerator/denominator
Set r -> min(1, q)
Set theta -> thetaStar with probability r else theta
Repeat this for all parameters in the parameter set.
Repeat all sequence of steps many times.
First, I generate some artificial data. This allows us to try to
recapture the model parameters. If we are successful at recapturing the
model parameters, then this gives us some confidence that our code is
working.
We generate a 1000 data points from a linear relationship with
intercept (bo) = 1.0, slope (b1) = 0.2, and sd = 1.5.
nReps<-10
x<-seq(from=0,to=100,by=1)
x<-rep(x,nReps)
b0<-1.0; b1<-0.2
ymean<-b0+b1*x
y<-rnorm(n=length(ymean),mean=ymean,sd=1.5)
data<-data.frame(x,y)
rm(x,y)
plot(data$x,data$y)
Log Likelihood
li_reg_bb<-function(pars,data)
{
a<-pars[1] #intercept
b<-pars[2] #slope
sd_e<-pars[3] #error (residuals)
if(sd_e<=0){return(NaN)}
pred <- a + b * data[,1]
log_likelihood<-sum( dnorm(data[,2],pred,sd_e, log=TRUE) )
# prior<- prior_reg(pars)
return(log_likelihood)
}
In this model, we have hardcoded the priors in the function below:
The prior on the intercept and slope is normal(0,100) and the prior on
the sd is gamma(1, 1/100)
Log Prior Probability
prior_reg<-function(pars)
{
a<-pars[1] # intercept
b<-pars[2] # slope
sd<-pars[3] # sd
prior_a<-dnorm(a,0,100,log=TRUE) ## non-informative (flat) priors on all
prior_b<-dnorm(b,0,100,log=TRUE) ## parameters.
prior_sd<-dgamma(sd,1,1/100,log=TRUE)
return(prior_a + prior_b + prior_sd)
}
Here is what the prior on sd looks like
sd<-seq(from=0, to=1000,len=1000)
d<-dgamma(sd,1,1/100,log=F)
plot(sd,d,type='l' )
Setting up for sampling
myDebug<-FALSE
parmVect<-c(5.5,2,5)
nIters<-1000000
sdSampler<-0.5
iterMat<-matrix(NA,nrow=nIters+1,ncol=3)
colnames(iterMat)<-c("b0","b1","sd")
iterMat[1,]<-parmVect
Running the algorithm…
Calculate numerator <-
p(thetaStar|y)*g(theta|thetaStar)
Calculate denominator <-
p(theta|y)*g(thetaStar|theta)
for(i in 1:nIters){
for(j in seq_along(parmVect)){
if(myDebug) cat("i= ",i," ; j= ",j,fill=TRUE)
if(myDebug) cat("Begin loop: parmVect= ",parmVect,fill=TRUE)
# log p(theta|y) is logLikCurrent + logPriorCurrent
logLikCurrent<-li_reg_bb(parmVect,data)
logPriorCurrent<-prior_reg(parmVect)
if(myDebug) cat("logLikCurrent= ",logLikCurrent," ; logPriorCurrent= ",logPriorCurrent,fill=TRUE)
if(j<3){ # this sampling for intercept and slope
parmStar<-rnorm(n=1,mean=parmVect[j],sd=sdSampler)
# proposed parameter for beta + slope
# calculating g(thetaStar|theta)
logThetaStar<-dnorm(parmStar,mean=parmVect[j],sd=sdSampler,log=TRUE)
# calculating g(theta|thetaStar)
logTheta<-dnorm(parmVect[j],mean=parmStar,sd=sdSampler,log=TRUE)
if(myDebug) {cat("parmStar for b0,b1 = ",parmStar,"logTheta= ",logTheta," ;
logThetaStar= ",logThetaStar,fill=TRUE)}
} else{
parmStar<-rgamma(n=1,shape=2)
# proposed parameter for beta + slope
# calculating g(thetaStar|theta)
logThetaStar<-dgamma(parmStar,shape=2,log=TRUE)
# calculating g(theta|thetaStar)
logTheta<-dgamma(parmVect[j],shape=2,log=TRUE)
if(myDebug) {cat("parmStar for sd = ",parmStar,"logTheta= ",logTheta," ;
logThetaStar= ",logThetaStar,fill=TRUE)}
}
parmVectStar<-parmVect # Current values of parameters are assigned
parmVectStar[j]<-parmStar
if(myDebug) cat("parmVectStar = ",parmVectStar,fill=TRUE)
logLikStar<-li_reg_bb(parmVectStar,data)
logPriorStar<-prior_reg(parmVectStar)
if(myDebug) cat("logLikStar= ",logLikStar," ; logPriorStar= ",logPriorStar,fill=TRUE)
if(myDebug) cat("logTheta= ",logTheta," ; logThetaStar= ",logThetaStar,fill=TRUE)
numerator<- logLikStar+logPriorStar+logTheta
denominator<- logLikCurrent+logPriorCurrent+logThetaStar
qalt<-exp(numerator-denominator)
q<-exp(logLikStar+logPriorStar+logTheta
-logLikCurrent-logPriorCurrent-logThetaStar)
r=min(1, q)
if(myDebug) cat("r= ",r," ; q= ",q,fill=TRUE)
if(runif(n=1)<r) parmVect<-parmVectStar
# browser()
} # end of j loop
iterMat[i+1,]<-parmVect
}
intercept (bo) = 1.0, slope (b1) = 0.2, and sd = 1.5
Calculate numerator <-
p(thetaStar|y)*g(theta|thetaStar)
Calculate denominator <-
p(theta|y)*g(thetaStar|theta)
q<-numerator/denominator
Examinging the chains…based on this lets discard the first 1000
samples as ‘burn in’.
matplot(iterMat)
Examining the parameters…
apply(iterMat[-(1:1001),],2,mean)
b0 b1 sd
1.1062542 0.1983618 1.5127062
apply(iterMat[-(1:1001),],2,quantile,probs=c(0.05,0.5,0.95))
b0 b1 sd
5% 0.9515863 0.1957366 1.458462
50% 1.1075612 0.1983441 1.512031
95% 1.2593096 0.2010010 1.569983
This recaptures our model parameter fairly well.
Examing the chain…
hist(iterMat[-(1:1001),1],freq=FALSE)
hist(iterMat[-(1:1001),2],freq=FALSE)
hist(iterMat[-(1:1001),3],freq=FALSE)
plot(iterMat[,1])
plot(iterMat[,2])
plot(iterMat[,3])
Scratch code
######################################################################
normalProposal<-function(currentValue,sd=2){
proposedValue<-rnorm(1,mean=currentValue,sd)
return(proposedValue)
}
logNormalProposal<-function(currentValue,sd=0.5){
proposedValue<-rlnorm(1,mean=log(currentValue),sd)
return(proposedValue)
}
nlpRegressionLinear<-function(parmVector,weightDat,heightDat,priorVect,print=F){
intercept<-parmVector[1]
B<-parmVector[2]
mySD<-parmVector[3]
w<-weightDat
h<-heightDat
mu<-intercept + B*h
nllik<- -sum(dnorm(x=w,mean=mu,sd=mySD,log=TRUE))
meanNormalPrior<-priorVect[1]
sdNormalPrior<-priorVect[2]
expPrior<-priorVect[3]
nlPriorIntercept<- -dnorm(intercept, mean = meanNormalPrior, sd = sdNormalPrior, log=TRUE)
nlPriorB<- -dnorm(B, mean = meanNormalPrior, sd = sdNormalPrior, log=TRUE)
nlPriorSD<- -dexp(mySD, rate=expPrior, log=TRUE)
nllPosterior<- nllik + nlPriorIntercept + nlPriorB + nlPriorSD
# browser() # for debugging
if (print) cat("nllik= ",nllik,sep=" ", "nllPosterior= ", nllPosterior,fill=T)
return(nllPosterior)}
nllRegressionLinear<-function(parmVector,weightDat,heightDat,print=F){
intercept<-parmVector[1]
B<-parmVector[2]
mySD<-parmVector[3]
w<-weightDat
h<-heightDat
mu<-intercept + B*h
nllik<- -sum(dnorm(x=w,mean=mu,sd=mySD,log=TRUE))
if (print) cat("nllik= ",nllik,sep=" ",fill=T)
return(nllik)}
prior_reg<-function(pars,priorVector)
{
a<-pars[1] #intercept
b<-pars[2] #slope
sd<-pars[3] #error
prior_a<-dnorm(a,0,100,log=TRUE) ## non-informative (flat) priors on all
prior_b<-dnorm(b,0,100,log=TRUE) ## parameters.
prior_sd<-dgamma(sd,1,1/100,log=TRUE)
return(prior_a + prior_b + prior_sd)
}
Belwo is the code that we use to find the Maximum Likelihood Estimate
(MLE) for the linear model: w ~ normal(mu, sd) mu<- intercept +
B*height We have three parameters to estimate: intercept, B, and sd
parVecLinear<-c(0.1,0.1,10) # Initial parameter values
outRegLinear<-optim(par=parVecLinear,fn=nllRegressionLinear,method="L-BFGS-B",lower=c(-Inf,-Inf, 0.01),upper=c(Inf,Inf, Inf), weightDat=myData$weight, heightDat=myData$height,print=F)
outRegLinear$par
outRegLinear$value
We can verify the accuracy of our fit by comparison to the built in
lm() function. We can see that the estimates are very similar.
lmFit<-lm(weight~height,data=myData)
summary(lmFit)
MXIMUM A POSTERIOR (MAP) PROBABILITY ESIMATE: This is what the quap
function in the Rethinking library uses to estimate the posterior
distribution. In this approach, we just need to modify our likelihood
function to include the prior distribution. Recall that the posterior ~
likelihood * prior. Since we are working on the log scale, the
likelihood and prior are added together rather than multiplied. So the
main change in the function below is that I have added the priors into
the calculation, then combine the likelihood and priors to get the
posterior density.
nlpRegressionLinear<-function(parmVector,weightDat,heightDat,priorVect,print=F){
intercept<-parmVector[1]
B<-parmVector[2]
mySD<-parmVector[3]
w<-weightDat
h<-heightDat
mu<-intercept + B*h
nllik<- -sum(dnorm(x=w,mean=mu,sd=mySD,log=TRUE))
meanNormalPrior<-priorVect[1]
sdNormalPrior<-priorVect[2]
expPrior<-priorVect[3]
nlPriorIntercept<- -dnorm(intercept, mean = meanNormalPrior, sd = sdNormalPrior, log=TRUE)
nlPriorB<- -dnorm(B, mean = meanNormalPrior, sd = sdNormalPrior, log=TRUE)
nlPriorSD<- -dexp(mySD, rate=expPrior, log=TRUE)
nllPosterior<- nllik + nlPriorIntercept + nlPriorB + nlPriorSD
# browser() # for debugging
if (print) cat("nllik= ",nllik,sep=" ", "nllPosterior= ", nllPosterior,fill=T)
return(nllPosterior)}
Now we use optim to find the MAP just as we did above for the
MLE.
parVecLinear<-c(0.1,0.1,10) # Initial parameter values
priorVectLinear<-c(0,10,1) # these are parameter values for the priors
outBayesRegLinear<-optim(par=parVecLinear,fn=nlpRegressionLinear,method="L-BFGS-B",lower=c(-Inf,-Inf, 0.01),upper=c(Inf,Inf, Inf), weightDat=myData$weight, heightDat=myData$height,priorVect=priorVectLinear,print=F)
outBayesRegLinear$par
outBayesRegLinear$value
Finally, let’s compare our fit above to that produced by the quap
function within the Rethinking package.
library(rethinking)
lm.qa <- quap(
alist(
weight ~ dnorm(mu,sd) , # normal likelihood
sd ~ dexp(1), # exponential prior
mu<-intercept + beta*height,
intercept~dnorm(0,10),
beta~dnorm(0,10)
) ,
data=list(weight=myData$weight,height=myData$height) )
precis(lm.qa)
Looking a little closer at the quap fit. quap uses the same optim
function that we used above and you can access details of the optim fit
by examining components of the fit. Note that we are digging deeper here
and these components are not normally visible to users, but we are
access the slots of the returned object. (No need to worry about this as
it is a bit more advanced computational concept.)
The bottom like is that our estimates of the MAP and the posterior
density at the MAP from quap (i.e. ‘value’ below -> 1012) are very
similar.
lm.qa
slot(lm.qa, 'optim')
Scratch Code
#########################################################################################################
parVecLinearMLE<-c(-51.6295921,0.6251449,4.2428686)
nlpRegressionLinear(parVecLinearMLE,weightDat=myData$weight,heightDat=myData$height,priorVectLinear,print=F)
nllRegressionLinear(parVecLinearMLE,weightDat=myData$weight,heightDat=myData$height,print=F)
parVecLinearBayes<-c(-42.79,0.57,4.24)
nlpRegressionLinear(parVecLinearBayes,weightDat=myData$weight,heightDat=myData$height,priorVectLinear,print=F)
nllRegressionLinear(parVecLinearBayes,weightDat=myData$weight,heightDat=myData$height,print=F)
This is an R Markdown
Notebook. When you execute code within the notebook, the results appear
beneath the code.
Try executing this chunk by clicking the Run button within
the chunk or by placing your cursor inside it and pressing
Cmd+Shift+Enter.
plot(cars)
Add a new chunk by clicking the Insert Chunk button on the
toolbar or by pressing Cmd+Option+I.
When you save the notebook, an HTML file containing the code and
output will be saved alongside it (click the Preview button or
press Cmd+Shift+K to preview the HTML file).
The preview shows you a rendered HTML copy of the contents of the
editor. Consequently, unlike Knit, Preview does not
run any R code chunks. Instead, the output of the chunk when it was last
run in the editor is displayed.
LS0tCnRpdGxlOiAiQmF5ZXNpYW4gUG9zdGVyaW9yIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZWRpdG9yX29wdGlvbnM6IAogIG1hcmtkb3duOiAKICAgIHdyYXA6IDcyCi0tLQoKSSBhbSBnb2luZyB0byBkZW1vbnN0cmF0ZSBNQ01DIHVzaW5nIHRoZSBNZXRyb3BvbGlzLUhhc3RpbmdzIGFsZ29yaXRobQphcyBkZXNjcmliZWQgb24gcGFnZSAzNTkgb2YgUHJvZmVzc29yIE1pY2hhZWwgTGF2aW5lJ3MgYm9vayBJbnRyb2R1Y3Rpb24KdG8gU3RhdGlzdGljYWwgVGhvdWdodC4gWW91IGNhbiBhY2Nlc3MgdGhpcyBmcmVlIGJvb2sgdW5kZXIgdGhlCidSZXNvdXJjZXMnIHNlY3Rpb24gb2YgdGhlIGNsYXNzIHN5bGxhYnVzLiBUaGlzIGJvb2sgaXMgYW4gZXhjZWxsZW50CnJlc291cmNlIGZvciBCYXllc2lhbnMgc3RhdGlzdGljcyBhbmQgc3RhdGlzdGljcyBnZW5lcmFsbHkuIEkgaGF2ZQppbmNsdWRlZCBhIHNjcmVlbiBzaG90IGJlbG93OgoKIVtNZXRyb3BvbGlzLUhhc3RpbmdzIEFsZ29yaXRobV0oLi9NZXRyb3BvbGlzSGFzdGluZ3NfTGF2aW5lMi5wbmcpCgoKeX5ub3JtYWwobXUsIHNkKQptdTwtYm8gKyBiMSp4Cgpib35ub3JtYWwoMCxzZD0xMDApCmIxfm5vcm1hbCgwLHNkPTEwMCkKc2R+Z2FtbWEoMSwxLzEwMCkKCkhlcmUgaXMgdGhlIGJhc2ljIGFsZ29yaXRobToKCjEuIENob29zZSBhIHByb3Bvc2FsIGRlbnNpdHkgZyh0aGV0YVN0YXIgfCB0aGV0YSkKCjIuIEZvciBlYWNoIGNvbXBvbmVudCBvZiB0aGUgcGFyYW1ldGVyIHZlY3RvciAobGV0cyBjYWxsIHRoaXMgcGFyYW1ldGVyIHRoZXRhKSwgCiAgICBnZW5lcmF0ZSBhIHByb3Bvc2VkIHBhcmFtZXRlci4gTGV0cyBjYWxsIHRoaXMgdGhldGFTdGFyCgozLiBDYWxjdWxhdGUgbnVtZXJhdG9yIDwtIHAodGhldGFTdGFyfHkpKmcodGhldGF8dGhldGFTdGFyKSAKCjQuIENhbGN1bGF0ZSBkZW5vbWluYXRvciA8LSBwKHRoZXRhfHkpKmcodGhldGFTdGFyfHRoZXRhKSAKCjUuIHE8LW51bWVyYXRvci9kZW5vbWluYXRvcgoKNS4gU2V0IHIgLT4gbWluKDEsIHEpCgo2LiBTZXQgdGhldGEgLT4gdGhldGFTdGFyIHdpdGggcHJvYmFiaWxpdHkgciBlbHNlIHRoZXRhCgo3LiBSZXBlYXQgdGhpcyBmb3IgYWxsIHBhcmFtZXRlcnMgaW4gdGhlIHBhcmFtZXRlciBzZXQuCgo4LiBSZXBlYXQgYWxsIHNlcXVlbmNlIG9mIHN0ZXBzIG1hbnkgdGltZXMuCgoKRmlyc3QsIEkgZ2VuZXJhdGUgc29tZSBhcnRpZmljaWFsIGRhdGEuICBUaGlzIGFsbG93cyB1cyB0byB0cnkgdG8gcmVjYXB0dXJlCnRoZSBtb2RlbCBwYXJhbWV0ZXJzLiAgSWYgd2UgYXJlIHN1Y2Nlc3NmdWwgYXQgcmVjYXB0dXJpbmcgdGhlIG1vZGVsIHBhcmFtZXRlcnMsCnRoZW4gdGhpcyBnaXZlcyB1cyBzb21lIGNvbmZpZGVuY2UgdGhhdCBvdXIgY29kZSBpcyB3b3JraW5nLgoKV2UgZ2VuZXJhdGUgYSAxMDAwIGRhdGEgcG9pbnRzIGZyb20gYSBsaW5lYXIgcmVsYXRpb25zaGlwIHdpdGggaW50ZXJjZXB0IChibykgPSAxLjAsCnNsb3BlIChiMSkgPSAwLjIsIGFuZCBzZCA9IDEuNS4KYGBge3J9Cm5SZXBzPC0xMAp4PC1zZXEoZnJvbT0wLHRvPTEwMCxieT0xKQp4PC1yZXAoeCxuUmVwcykKYjA8LTEuMDsgYjE8LTAuMgp5bWVhbjwtYjArYjEqeAp5PC1ybm9ybShuPWxlbmd0aCh5bWVhbiksbWVhbj15bWVhbixzZD0xLjUpCmRhdGE8LWRhdGEuZnJhbWUoeCx5KQpybSh4LHkpCnBsb3QoZGF0YSR4LGRhdGEkeSkKYGBgCgpMb2cgTGlrZWxpaG9vZCAKYGBge3J9CmxpX3JlZ19iYjwtZnVuY3Rpb24ocGFycyxkYXRhKQp7CiAgICBhPC1wYXJzWzFdICAgICAgI2ludGVyY2VwdAogICAgYjwtcGFyc1syXSAgICAgICNzbG9wZQogICAgc2RfZTwtcGFyc1szXSAgICNlcnJvciAocmVzaWR1YWxzKQogICAgaWYoc2RfZTw9MCl7cmV0dXJuKE5hTil9CiAgICBwcmVkIDwtIGEgKyBiICogZGF0YVssMV0KICAgIGxvZ19saWtlbGlob29kPC1zdW0oIGRub3JtKGRhdGFbLDJdLHByZWQsc2RfZSwgbG9nPVRSVUUpICkKICAgICMgcHJpb3I8LSBwcmlvcl9yZWcocGFycykKICAgIHJldHVybihsb2dfbGlrZWxpaG9vZCkKfQpgYGAKCgpJbiB0aGlzIG1vZGVsLCB3ZSBoYXZlIGhhcmRjb2RlZCB0aGUgcHJpb3JzIGluIHRoZSBmdW5jdGlvbiBiZWxvdzoKVGhlIHByaW9yIG9uIHRoZSBpbnRlcmNlcHQgYW5kIHNsb3BlIGlzIG5vcm1hbCgwLDEwMCkgYW5kCnRoZSBwcmlvciBvbiB0aGUgc2QgaXMgZ2FtbWEoMSwgMS8xMDApCgpMb2cgUHJpb3IgUHJvYmFiaWxpdHkKCmBgYHtyfQpwcmlvcl9yZWc8LWZ1bmN0aW9uKHBhcnMpCnsKICAgIGE8LXBhcnNbMV0gICAgIyBpbnRlcmNlcHQKICAgIGI8LXBhcnNbMl0gICAgIyBzbG9wZSAgCiAgICBzZDwtcGFyc1szXSAgICMgc2QKICAgIAogICAgcHJpb3JfYTwtZG5vcm0oYSwwLDEwMCxsb2c9VFJVRSkgICAgICMjIG5vbi1pbmZvcm1hdGl2ZSAoZmxhdCkgcHJpb3JzIG9uIGFsbCAKICAgIHByaW9yX2I8LWRub3JtKGIsMCwxMDAsbG9nPVRSVUUpICAgICAjIyBwYXJhbWV0ZXJzLiAgCiAgICBwcmlvcl9zZDwtZGdhbW1hKHNkLDEsMS8xMDAsbG9nPVRSVUUpICAgICAgCiAgICAKICAgIHJldHVybihwcmlvcl9hICsgcHJpb3JfYiArIHByaW9yX3NkKQp9CgpgYGAKCkhlcmUgaXMgd2hhdCB0aGUgcHJpb3Igb24gc2QgbG9va3MgbGlrZQpgYGB7cn0Kc2Q8LXNlcShmcm9tPTAsIHRvPTEwMDAsbGVuPTEwMDApCmQ8LWRnYW1tYShzZCwxLDEvMTAwLGxvZz1GKQpwbG90KHNkLGQsdHlwZT0nbCcgKQpgYGAKCgoKClNldHRpbmcgdXAgZm9yIHNhbXBsaW5nCmBgYHtyfQpteURlYnVnPC1GQUxTRQpwYXJtVmVjdDwtYyg1LjUsMiw1KQpuSXRlcnM8LTEwMDAwMDAKc2RTYW1wbGVyPC0wLjUKaXRlck1hdDwtbWF0cml4KE5BLG5yb3c9bkl0ZXJzKzEsbmNvbD0zKQpjb2xuYW1lcyhpdGVyTWF0KTwtYygiYjAiLCJiMSIsInNkIikKaXRlck1hdFsxLF08LXBhcm1WZWN0CmBgYAoKClJ1bm5pbmcgdGhlIGFsZ29yaXRobS4uLgoKMy4gQ2FsY3VsYXRlIG51bWVyYXRvciA8LSBwKHRoZXRhU3Rhcnx5KSpnKHRoZXRhfHRoZXRhU3RhcikgCgo0LiBDYWxjdWxhdGUgZGVub21pbmF0b3IgPC0gcCh0aGV0YXx5KSpnKHRoZXRhU3Rhcnx0aGV0YSkgCgoKYGBge3J9CmZvcihpIGluIDE6bkl0ZXJzKXsKICAKICAgIGZvcihqIGluIHNlcV9hbG9uZyhwYXJtVmVjdCkpewogICAgICAgIGlmKG15RGVidWcpIGNhdCgiaT0gIixpLCIgOyBqPSAiLGosZmlsbD1UUlVFKQogICAgICAgIGlmKG15RGVidWcpIGNhdCgiQmVnaW4gbG9vcDogcGFybVZlY3Q9ICIscGFybVZlY3QsZmlsbD1UUlVFKQogICAgICAgIAogICAgICAjIGxvZyBwKHRoZXRhfHkpIGlzIGxvZ0xpa0N1cnJlbnQgKyBsb2dQcmlvckN1cnJlbnQKICAgICAgICBsb2dMaWtDdXJyZW50PC1saV9yZWdfYmIocGFybVZlY3QsZGF0YSkKICAgICAgICBsb2dQcmlvckN1cnJlbnQ8LXByaW9yX3JlZyhwYXJtVmVjdCkgCiAgICAgICAgCiAgICAgICAgaWYobXlEZWJ1ZykgY2F0KCJsb2dMaWtDdXJyZW50PSAiLGxvZ0xpa0N1cnJlbnQsIiA7IGxvZ1ByaW9yQ3VycmVudD0gIixsb2dQcmlvckN1cnJlbnQsZmlsbD1UUlVFKQogICAgICAgIAogICAgICAgIGlmKGo8Myl7ICMgdGhpcyBzYW1wbGluZyBmb3IgaW50ZXJjZXB0IGFuZCBzbG9wZQogICAgICAgICAgICAKICAgICAgICAgICAgcGFybVN0YXI8LXJub3JtKG49MSxtZWFuPXBhcm1WZWN0W2pdLHNkPXNkU2FtcGxlcikKICAgICAgICAgICAgIyBwcm9wb3NlZCBwYXJhbWV0ZXIgZm9yIGJldGEgKyBzbG9wZQogICAgICAgICAgICAKICAgICAgICAgICAgIyBjYWxjdWxhdGluZyBnKHRoZXRhU3Rhcnx0aGV0YSkKICAgICAgICAgICAgbG9nVGhldGFTdGFyPC1kbm9ybShwYXJtU3RhcixtZWFuPXBhcm1WZWN0W2pdLHNkPXNkU2FtcGxlcixsb2c9VFJVRSkKICAgICAgICAgICAgIyBjYWxjdWxhdGluZyBnKHRoZXRhfHRoZXRhU3RhcikKICAgICAgICAgICAgbG9nVGhldGE8LWRub3JtKHBhcm1WZWN0W2pdLG1lYW49cGFybVN0YXIsc2Q9c2RTYW1wbGVyLGxvZz1UUlVFKQogICAgICAgICAgICAKICAgICAgICAgICAgaWYobXlEZWJ1ZykgIHtjYXQoInBhcm1TdGFyIGZvciBiMCxiMSA9ICIscGFybVN0YXIsImxvZ1RoZXRhPSAiLGxvZ1RoZXRhLCIgOwogICAgICAgICAgICAgICAgbG9nVGhldGFTdGFyPSAiLGxvZ1RoZXRhU3RhcixmaWxsPVRSVUUpfQogICAgICAgIH0gZWxzZXsKICAgICAgICAgICAgcGFybVN0YXI8LXJnYW1tYShuPTEsc2hhcGU9MikKICAgICAgICAgICAgIyBwcm9wb3NlZCBwYXJhbWV0ZXIgZm9yIGJldGEgKyBzbG9wZQogICAgICAgICAgICAKICAgICAgICAgICAgIyBjYWxjdWxhdGluZyBnKHRoZXRhU3Rhcnx0aGV0YSkKICAgICAgICAgICAgbG9nVGhldGFTdGFyPC1kZ2FtbWEocGFybVN0YXIsc2hhcGU9Mixsb2c9VFJVRSkKICAgICAgICAgICAgIyBjYWxjdWxhdGluZyBnKHRoZXRhfHRoZXRhU3RhcikKICAgICAgICAgICAgbG9nVGhldGE8LWRnYW1tYShwYXJtVmVjdFtqXSxzaGFwZT0yLGxvZz1UUlVFKQogICAgICAgICAgICAKICAgICAgICAgICAgaWYobXlEZWJ1Zykge2NhdCgicGFybVN0YXIgZm9yIHNkID0gIixwYXJtU3RhciwibG9nVGhldGE9ICIsbG9nVGhldGEsIiA7CiAgICAgICAgICAgICAgICBsb2dUaGV0YVN0YXI9ICIsbG9nVGhldGFTdGFyLGZpbGw9VFJVRSl9CiAgICAgICAgfQogICAgICAgIAogICAgICAgIHBhcm1WZWN0U3RhcjwtcGFybVZlY3QgIyBDdXJyZW50IHZhbHVlcyBvZiBwYXJhbWV0ZXJzIGFyZSBhc3NpZ25lZAogICAgICAgIHBhcm1WZWN0U3RhcltqXTwtcGFybVN0YXIKICAgICAgICBpZihteURlYnVnKSBjYXQoInBhcm1WZWN0U3RhciA9ICIscGFybVZlY3RTdGFyLGZpbGw9VFJVRSkKICAgICAgICAKICAgICAgICBsb2dMaWtTdGFyPC1saV9yZWdfYmIocGFybVZlY3RTdGFyLGRhdGEpCiAgICAgICAgbG9nUHJpb3JTdGFyPC1wcmlvcl9yZWcocGFybVZlY3RTdGFyKSAKICAgICAgICBpZihteURlYnVnKSBjYXQoImxvZ0xpa1N0YXI9ICIsbG9nTGlrU3RhciwiIDsgbG9nUHJpb3JTdGFyPSAiLGxvZ1ByaW9yU3RhcixmaWxsPVRSVUUpCiAgICAgICAgaWYobXlEZWJ1ZykgY2F0KCJsb2dUaGV0YT0gIixsb2dUaGV0YSwiIDsgbG9nVGhldGFTdGFyPSAiLGxvZ1RoZXRhU3RhcixmaWxsPVRSVUUpCiAgICAgICAgCiAgICAgICAgbnVtZXJhdG9yPC0gbG9nTGlrU3Rhcitsb2dQcmlvclN0YXIrbG9nVGhldGEKICAgICAgICBkZW5vbWluYXRvcjwtIGxvZ0xpa0N1cnJlbnQrbG9nUHJpb3JDdXJyZW50K2xvZ1RoZXRhU3RhcgogICAgICAgIHE8LWV4cChudW1lcmF0b3ItZGVub21pbmF0b3IpCiAgICAgICAgI3E8LWV4cChsb2dMaWtTdGFyK2xvZ1ByaW9yU3Rhcitsb2dUaGV0YQogICAgICAgICAjICAgICAgLWxvZ0xpa0N1cnJlbnQtbG9nUHJpb3JDdXJyZW50LWxvZ1RoZXRhU3RhcikKICAgICAgICByPW1pbigxLCBxKQogICAgICAgIAogICAgICAgIGlmKG15RGVidWcpIGNhdCgicj0gIixyLCIgOyBxPSAiLHEsZmlsbD1UUlVFKQogICAgICAgIAogICAgICAgIGlmKHJ1bmlmKG49MSk8cikgcGFybVZlY3Q8LXBhcm1WZWN0U3RhcgogICAgICAgICMgYnJvd3NlcigpICMgZm9yIGRlYnVnZ2luZwogICAgfSAjIGVuZCBvZiBqIGxvb3AKICAKICAgIGl0ZXJNYXRbaSsxLF08LXBhcm1WZWN0Cn0KYGBgCmludGVyY2VwdCAoYm8pID0gMS4wLCBzbG9wZSAoYjEpID0gMC4yLCBhbmQgc2QgPSAxLjUKCjMuIENhbGN1bGF0ZSBudW1lcmF0b3IgPC0gcCh0aGV0YVN0YXJ8eSkqZyh0aGV0YXx0aGV0YVN0YXIpIAoKNC4gQ2FsY3VsYXRlIGRlbm9taW5hdG9yIDwtIHAodGhldGF8eSkqZyh0aGV0YVN0YXJ8dGhldGEpIAoKNS4gcTwtbnVtZXJhdG9yL2Rlbm9taW5hdG9yCgpFeGFtaW5naW5nIHRoZSBjaGFpbnMuLi5iYXNlZCBvbiB0aGlzIGxldHMgZGlzY2FyZCB0aGUgZmlyc3QgMTAwMCBzYW1wbGVzIGFzICdidXJuIGluJy4KYGBge3J9Cm1hdHBsb3QoaXRlck1hdCkKYGBgCgpFeGFtaW5pbmcgdGhlIHBhcmFtZXRlcnMuLi4KYGBge3J9CmFwcGx5KGl0ZXJNYXRbLSgxOjEwMDEpLF0sMixtZWFuKQphcHBseShpdGVyTWF0Wy0oMToxMDAxKSxdLDIscXVhbnRpbGUscHJvYnM9YygwLjA1LDAuNSwwLjk1KSkKYGBgCgpUaGlzIHJlY2FwdHVyZXMgb3VyIG1vZGVsIHBhcmFtZXRlciBmYWlybHkgd2VsbC4KCgpFeGFtaW5nIHRoZSBjaGFpbi4uLgpgYGB7cn0KCmhpc3QoaXRlck1hdFstKDE6MTAwMSksMV0sZnJlcT1GQUxTRSkKaGlzdChpdGVyTWF0Wy0oMToxMDAxKSwyXSxmcmVxPUZBTFNFKQpoaXN0KGl0ZXJNYXRbLSgxOjEwMDEpLDNdLGZyZXE9RkFMU0UpCgpgYGAKCgpgYGB7cn0KcGxvdChpdGVyTWF0WywxXSkKcGxvdChpdGVyTWF0WywyXSkKcGxvdChpdGVyTWF0WywzXSkKYGBgCgoKCgpTY3JhdGNoIGNvZGUgIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKYGBge3J9Cm5vcm1hbFByb3Bvc2FsPC1mdW5jdGlvbihjdXJyZW50VmFsdWUsc2Q9Mil7CiAgCiAgcHJvcG9zZWRWYWx1ZTwtcm5vcm0oMSxtZWFuPWN1cnJlbnRWYWx1ZSxzZCkKICAKICByZXR1cm4ocHJvcG9zZWRWYWx1ZSkKfQpgYGAKCmBgYHtyfQpsb2dOb3JtYWxQcm9wb3NhbDwtZnVuY3Rpb24oY3VycmVudFZhbHVlLHNkPTAuNSl7CiAgCiAgcHJvcG9zZWRWYWx1ZTwtcmxub3JtKDEsbWVhbj1sb2coY3VycmVudFZhbHVlKSxzZCkKICAKICByZXR1cm4ocHJvcG9zZWRWYWx1ZSkKfQpgYGAKCgpgYGB7cn0KbmxwUmVncmVzc2lvbkxpbmVhcjwtZnVuY3Rpb24ocGFybVZlY3Rvcix3ZWlnaHREYXQsaGVpZ2h0RGF0LHByaW9yVmVjdCxwcmludD1GKXsKCiAgaW50ZXJjZXB0PC1wYXJtVmVjdG9yWzFdCiAgQjwtcGFybVZlY3RvclsyXQogIG15U0Q8LXBhcm1WZWN0b3JbM10KICAKICB3PC13ZWlnaHREYXQKICBoPC1oZWlnaHREYXQKICBtdTwtaW50ZXJjZXB0ICsgQipoCiAgCiAgbmxsaWs8LSAtc3VtKGRub3JtKHg9dyxtZWFuPW11LHNkPW15U0QsbG9nPVRSVUUpKQogIAogIG1lYW5Ob3JtYWxQcmlvcjwtcHJpb3JWZWN0WzFdCiAgc2ROb3JtYWxQcmlvcjwtcHJpb3JWZWN0WzJdCiAgZXhwUHJpb3I8LXByaW9yVmVjdFszXQogIAogIG5sUHJpb3JJbnRlcmNlcHQ8LSAtZG5vcm0oaW50ZXJjZXB0LCBtZWFuID0gIG1lYW5Ob3JtYWxQcmlvciwgc2QgPSBzZE5vcm1hbFByaW9yLCBsb2c9VFJVRSkKICBubFByaW9yQjwtIC1kbm9ybShCLCBtZWFuID0gIG1lYW5Ob3JtYWxQcmlvciwgc2QgPSBzZE5vcm1hbFByaW9yLCBsb2c9VFJVRSkKICBubFByaW9yU0Q8LSAtZGV4cChteVNELCByYXRlPWV4cFByaW9yLCBsb2c9VFJVRSkKICAKICBubGxQb3N0ZXJpb3I8LSBubGxpayArIG5sUHJpb3JJbnRlcmNlcHQgKyBubFByaW9yQiArIG5sUHJpb3JTRAogICAjIGJyb3dzZXIoKSAjIGZvciBkZWJ1Z2dpbmcKICBpZiAocHJpbnQpIGNhdCgibmxsaWs9ICIsbmxsaWssc2VwPSIgIiwgIm5sbFBvc3Rlcmlvcj0gIiwgbmxsUG9zdGVyaW9yLGZpbGw9VCkKICByZXR1cm4obmxsUG9zdGVyaW9yKX0KYGBgCgoKCgoKCgoKCmBgYHtyfQpubGxSZWdyZXNzaW9uTGluZWFyPC1mdW5jdGlvbihwYXJtVmVjdG9yLHdlaWdodERhdCxoZWlnaHREYXQscHJpbnQ9Ril7CgogIGludGVyY2VwdDwtcGFybVZlY3RvclsxXQogIEI8LXBhcm1WZWN0b3JbMl0KICBteVNEPC1wYXJtVmVjdG9yWzNdCiAgCiAgdzwtd2VpZ2h0RGF0CiAgaDwtaGVpZ2h0RGF0CiAgbXU8LWludGVyY2VwdCArIEIqaAogIAogIG5sbGlrPC0gLXN1bShkbm9ybSh4PXcsbWVhbj1tdSxzZD1teVNELGxvZz1UUlVFKSkKCiAgaWYgKHByaW50KSBjYXQoIm5sbGlrPSAiLG5sbGlrLHNlcD0iICIsZmlsbD1UKQogIHJldHVybihubGxpayl9CmBgYAoKCmBgYHtyfQpwcmlvcl9yZWc8LWZ1bmN0aW9uKHBhcnMscHJpb3JWZWN0b3IpCnsKICAgIGE8LXBhcnNbMV0gICAgICAgICAgI2ludGVyY2VwdAogICAgYjwtcGFyc1syXSAgICAgICAgICAjc2xvcGUgIAogICAgc2Q8LXBhcnNbM10gICAgI2Vycm9yCiAgICAKICAgIHByaW9yX2E8LWRub3JtKGEsMCwxMDAsbG9nPVRSVUUpICAgICAjIyBub24taW5mb3JtYXRpdmUgKGZsYXQpIHByaW9ycyBvbiBhbGwgCiAgICBwcmlvcl9iPC1kbm9ybShiLDAsMTAwLGxvZz1UUlVFKSAgICAgIyMgcGFyYW1ldGVycy4gIAogICAgcHJpb3Jfc2Q8LWRnYW1tYShzZCwxLDEvMTAwLGxvZz1UUlVFKSAgICAgIAogICAgCiAgICByZXR1cm4ocHJpb3JfYSArIHByaW9yX2IgKyBwcmlvcl9zZCkKfQpgYGAKCgoKCgoKCgoKCkJlbHdvIGlzIHRoZSBjb2RlIHRoYXQgd2UgdXNlIHRvIGZpbmQgdGhlIE1heGltdW0gTGlrZWxpaG9vZCBFc3RpbWF0ZQooTUxFKSBmb3IgdGhlIGxpbmVhciBtb2RlbDogdyBcfiBub3JtYWwobXUsIHNkKSBtdVw8LSBpbnRlcmNlcHQgKwpCXCpoZWlnaHQgV2UgaGF2ZSB0aHJlZSBwYXJhbWV0ZXJzIHRvIGVzdGltYXRlOiBpbnRlcmNlcHQsIEIsIGFuZCBzZAoKYGBge3J9CnBhclZlY0xpbmVhcjwtYygwLjEsMC4xLDEwKSAjIEluaXRpYWwgcGFyYW1ldGVyIHZhbHVlcyAKb3V0UmVnTGluZWFyPC1vcHRpbShwYXI9cGFyVmVjTGluZWFyLGZuPW5sbFJlZ3Jlc3Npb25MaW5lYXIsbWV0aG9kPSJMLUJGR1MtQiIsbG93ZXI9YygtSW5mLC1JbmYsIDAuMDEpLHVwcGVyPWMoSW5mLEluZiwgSW5mKSwgd2VpZ2h0RGF0PW15RGF0YSR3ZWlnaHQsIGhlaWdodERhdD1teURhdGEkaGVpZ2h0LHByaW50PUYpCm91dFJlZ0xpbmVhciRwYXIgCm91dFJlZ0xpbmVhciR2YWx1ZQpgYGAKCldlIGNhbiB2ZXJpZnkgdGhlIGFjY3VyYWN5IG9mIG91ciBmaXQgYnkgY29tcGFyaXNvbiB0byB0aGUgYnVpbHQgaW4gbG0oKQpmdW5jdGlvbi4gV2UgY2FuIHNlZSB0aGF0IHRoZSBlc3RpbWF0ZXMgYXJlIHZlcnkgc2ltaWxhci4KCmBgYHtyfQpsbUZpdDwtbG0od2VpZ2h0fmhlaWdodCxkYXRhPW15RGF0YSkKc3VtbWFyeShsbUZpdCkKYGBgCgpNWElNVU0gQSBQT1NURVJJT1IgKE1BUCkgUFJPQkFCSUxJVFkgRVNJTUFURTogVGhpcyBpcyB3aGF0IHRoZSBxdWFwCmZ1bmN0aW9uIGluIHRoZSBSZXRoaW5raW5nIGxpYnJhcnkgdXNlcyB0byBlc3RpbWF0ZSB0aGUgcG9zdGVyaW9yCmRpc3RyaWJ1dGlvbi4gSW4gdGhpcyBhcHByb2FjaCwgd2UganVzdCBuZWVkIHRvIG1vZGlmeSBvdXIgbGlrZWxpaG9vZApmdW5jdGlvbiB0byBpbmNsdWRlIHRoZSBwcmlvciBkaXN0cmlidXRpb24uIFJlY2FsbCB0aGF0IHRoZSBwb3N0ZXJpb3IgXH4KbGlrZWxpaG9vZCBcKiBwcmlvci4gU2luY2Ugd2UgYXJlIHdvcmtpbmcgb24gdGhlIGxvZyBzY2FsZSwgdGhlCmxpa2VsaWhvb2QgYW5kIHByaW9yIGFyZSBhZGRlZCB0b2dldGhlciByYXRoZXIgdGhhbiBtdWx0aXBsaWVkLiBTbyB0aGUKbWFpbiBjaGFuZ2UgaW4gdGhlIGZ1bmN0aW9uIGJlbG93IGlzIHRoYXQgSSBoYXZlIGFkZGVkIHRoZSBwcmlvcnMgaW50bwp0aGUgY2FsY3VsYXRpb24sIHRoZW4gY29tYmluZSB0aGUgbGlrZWxpaG9vZCBhbmQgcHJpb3JzIHRvIGdldCB0aGUKcG9zdGVyaW9yIGRlbnNpdHkuCgpgYGB7cn0KbmxwUmVncmVzc2lvbkxpbmVhcjwtZnVuY3Rpb24ocGFybVZlY3Rvcix3ZWlnaHREYXQsaGVpZ2h0RGF0LHByaW9yVmVjdCxwcmludD1GKXsKCiAgaW50ZXJjZXB0PC1wYXJtVmVjdG9yWzFdCiAgQjwtcGFybVZlY3RvclsyXQogIG15U0Q8LXBhcm1WZWN0b3JbM10KICAKICB3PC13ZWlnaHREYXQKICBoPC1oZWlnaHREYXQKICBtdTwtaW50ZXJjZXB0ICsgQipoCiAgCiAgbmxsaWs8LSAtc3VtKGRub3JtKHg9dyxtZWFuPW11LHNkPW15U0QsbG9nPVRSVUUpKQogIAogIG1lYW5Ob3JtYWxQcmlvcjwtcHJpb3JWZWN0WzFdCiAgc2ROb3JtYWxQcmlvcjwtcHJpb3JWZWN0WzJdCiAgZXhwUHJpb3I8LXByaW9yVmVjdFszXQogIAogIG5sUHJpb3JJbnRlcmNlcHQ8LSAtZG5vcm0oaW50ZXJjZXB0LCBtZWFuID0gIG1lYW5Ob3JtYWxQcmlvciwgc2QgPSBzZE5vcm1hbFByaW9yLCBsb2c9VFJVRSkKICBubFByaW9yQjwtIC1kbm9ybShCLCBtZWFuID0gIG1lYW5Ob3JtYWxQcmlvciwgc2QgPSBzZE5vcm1hbFByaW9yLCBsb2c9VFJVRSkKICBubFByaW9yU0Q8LSAtZGV4cChteVNELCByYXRlPWV4cFByaW9yLCBsb2c9VFJVRSkKICAKICBubGxQb3N0ZXJpb3I8LSBubGxpayArIG5sUHJpb3JJbnRlcmNlcHQgKyBubFByaW9yQiArIG5sUHJpb3JTRAogICAjIGJyb3dzZXIoKSAjIGZvciBkZWJ1Z2dpbmcKICBpZiAocHJpbnQpIGNhdCgibmxsaWs9ICIsbmxsaWssc2VwPSIgIiwgIm5sbFBvc3Rlcmlvcj0gIiwgbmxsUG9zdGVyaW9yLGZpbGw9VCkKICByZXR1cm4obmxsUG9zdGVyaW9yKX0KYGBgCgpOb3cgd2UgdXNlIG9wdGltIHRvIGZpbmQgdGhlIE1BUCBqdXN0IGFzIHdlIGRpZCBhYm92ZSBmb3IgdGhlIE1MRS4KCmBgYHtyfQpwYXJWZWNMaW5lYXI8LWMoMC4xLDAuMSwxMCkgIyBJbml0aWFsIHBhcmFtZXRlciB2YWx1ZXMgCnByaW9yVmVjdExpbmVhcjwtYygwLDEwLDEpICMgdGhlc2UgYXJlIHBhcmFtZXRlciB2YWx1ZXMgZm9yIHRoZSBwcmlvcnMKb3V0QmF5ZXNSZWdMaW5lYXI8LW9wdGltKHBhcj1wYXJWZWNMaW5lYXIsZm49bmxwUmVncmVzc2lvbkxpbmVhcixtZXRob2Q9IkwtQkZHUy1CIixsb3dlcj1jKC1JbmYsLUluZiwgMC4wMSksdXBwZXI9YyhJbmYsSW5mLCBJbmYpLCB3ZWlnaHREYXQ9bXlEYXRhJHdlaWdodCwgaGVpZ2h0RGF0PW15RGF0YSRoZWlnaHQscHJpb3JWZWN0PXByaW9yVmVjdExpbmVhcixwcmludD1GKQpvdXRCYXllc1JlZ0xpbmVhciRwYXIgCm91dEJheWVzUmVnTGluZWFyJHZhbHVlCmBgYAoKRmluYWxseSwgbGV0J3MgY29tcGFyZSBvdXIgZml0IGFib3ZlIHRvIHRoYXQgcHJvZHVjZWQgYnkgdGhlIHF1YXAKZnVuY3Rpb24gd2l0aGluIHRoZSBSZXRoaW5raW5nIHBhY2thZ2UuCgpgYGB7cn0KbGlicmFyeShyZXRoaW5raW5nKQpsbS5xYSA8LSBxdWFwKAogICAgYWxpc3QoCiAgICAgICAgd2VpZ2h0IH4gZG5vcm0obXUsc2QpICwgICMgbm9ybWFsIGxpa2VsaWhvb2QKICAgICAgICBzZCB+IGRleHAoMSksICAgICAjIGV4cG9uZW50aWFsIHByaW9yCiAgICAgICAgbXU8LWludGVyY2VwdCArIGJldGEqaGVpZ2h0LAogICAgICAgIGludGVyY2VwdH5kbm9ybSgwLDEwKSwKICAgICAgICBiZXRhfmRub3JtKDAsMTApCiAgICApICwKICAgIGRhdGE9bGlzdCh3ZWlnaHQ9bXlEYXRhJHdlaWdodCxoZWlnaHQ9bXlEYXRhJGhlaWdodCkgKQoKcHJlY2lzKGxtLnFhKQpgYGAKCkxvb2tpbmcgYSBsaXR0bGUgY2xvc2VyIGF0IHRoZSBxdWFwIGZpdC4gcXVhcCB1c2VzIHRoZSBzYW1lIG9wdGltCmZ1bmN0aW9uIHRoYXQgd2UgdXNlZCBhYm92ZSBhbmQgeW91IGNhbiBhY2Nlc3MgZGV0YWlscyBvZiB0aGUgb3B0aW0gZml0CmJ5IGV4YW1pbmluZyBjb21wb25lbnRzIG9mIHRoZSBmaXQuIE5vdGUgdGhhdCB3ZSBhcmUgZGlnZ2luZyBkZWVwZXIgaGVyZQphbmQgdGhlc2UgY29tcG9uZW50cyBhcmUgbm90IG5vcm1hbGx5IHZpc2libGUgdG8gdXNlcnMsIGJ1dCB3ZSBhcmUKYWNjZXNzIHRoZSBzbG90cyBvZiB0aGUgcmV0dXJuZWQgb2JqZWN0LiAoTm8gbmVlZCB0byB3b3JyeSBhYm91dCB0aGlzIGFzCml0IGlzIGEgYml0IG1vcmUgYWR2YW5jZWQgY29tcHV0YXRpb25hbCBjb25jZXB0LikKClRoZSBib3R0b20gbGlrZSBpcyB0aGF0IG91ciBlc3RpbWF0ZXMgb2YgdGhlIE1BUCBhbmQgdGhlIHBvc3RlcmlvcgpkZW5zaXR5IGF0IHRoZSBNQVAgZnJvbSBxdWFwIChpLmUuICd2YWx1ZScgYmVsb3cgLVw+IDEwMTIpIGFyZSB2ZXJ5CnNpbWlsYXIuCgpgYGB7cn0KbG0ucWEKc2xvdChsbS5xYSwgJ29wdGltJykKYGBgCgpTY3JhdGNoIENvZGUKXCMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKYGBge3J9CnBhclZlY0xpbmVhck1MRTwtYygtNTEuNjI5NTkyMSwwLjYyNTE0NDksNC4yNDI4Njg2KQoKbmxwUmVncmVzc2lvbkxpbmVhcihwYXJWZWNMaW5lYXJNTEUsd2VpZ2h0RGF0PW15RGF0YSR3ZWlnaHQsaGVpZ2h0RGF0PW15RGF0YSRoZWlnaHQscHJpb3JWZWN0TGluZWFyLHByaW50PUYpCm5sbFJlZ3Jlc3Npb25MaW5lYXIocGFyVmVjTGluZWFyTUxFLHdlaWdodERhdD1teURhdGEkd2VpZ2h0LGhlaWdodERhdD1teURhdGEkaGVpZ2h0LHByaW50PUYpCmBgYAoKYGBge3J9CgpwYXJWZWNMaW5lYXJCYXllczwtYygtNDIuNzksMC41Nyw0LjI0KQpubHBSZWdyZXNzaW9uTGluZWFyKHBhclZlY0xpbmVhckJheWVzLHdlaWdodERhdD1teURhdGEkd2VpZ2h0LGhlaWdodERhdD1teURhdGEkaGVpZ2h0LHByaW9yVmVjdExpbmVhcixwcmludD1GKQpubGxSZWdyZXNzaW9uTGluZWFyKHBhclZlY0xpbmVhckJheWVzLHdlaWdodERhdD1teURhdGEkd2VpZ2h0LGhlaWdodERhdD1teURhdGEkaGVpZ2h0LHByaW50PUYpCmBgYAoKClRoaXMgaXMgYW4gW1IgTWFya2Rvd25dKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20pIE5vdGVib29rLiBXaGVuIHlvdQpleGVjdXRlIGNvZGUgd2l0aGluIHRoZSBub3RlYm9vaywgdGhlIHJlc3VsdHMgYXBwZWFyIGJlbmVhdGggdGhlIGNvZGUuCgpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rCm9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ21kK1NoaWZ0K0VudGVyKi4KCmBgYHtyfQpwbG90KGNhcnMpCmBgYAoKQWRkIGEgbmV3IGNodW5rIGJ5IGNsaWNraW5nIHRoZSAqSW5zZXJ0IENodW5rKiBidXR0b24gb24gdGhlIHRvb2xiYXIgb3IKYnkgcHJlc3NpbmcgKkNtZCtPcHRpb24rSSouCgpXaGVuIHlvdSBzYXZlIHRoZSBub3RlYm9vaywgYW4gSFRNTCBmaWxlIGNvbnRhaW5pbmcgdGhlIGNvZGUgYW5kIG91dHB1dAp3aWxsIGJlIHNhdmVkIGFsb25nc2lkZSBpdCAoY2xpY2sgdGhlICpQcmV2aWV3KiBidXR0b24gb3IgcHJlc3MKKkNtZCtTaGlmdCtLKiB0byBwcmV2aWV3IHRoZSBIVE1MIGZpbGUpLgoKVGhlIHByZXZpZXcgc2hvd3MgeW91IGEgcmVuZGVyZWQgSFRNTCBjb3B5IG9mIHRoZSBjb250ZW50cyBvZiB0aGUKZWRpdG9yLiBDb25zZXF1ZW50bHksIHVubGlrZSAqS25pdCosICpQcmV2aWV3KiBkb2VzIG5vdCBydW4gYW55IFIgY29kZQpjaHVua3MuIEluc3RlYWQsIHRoZSBvdXRwdXQgb2YgdGhlIGNodW5rIHdoZW4gaXQgd2FzIGxhc3QgcnVuIGluIHRoZQplZGl0b3IgaXMgZGlzcGxheWVkLgo=