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:

Metropolis-Hastings Algorithm

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:

  1. Choose a proposal density g(thetaStar | theta)

  2. For each component of the parameter vector (lets call this parameter theta), generate a proposed parameter. Lets call this thetaStar

  3. Calculate numerator <- p(thetaStar|y)*g(theta|thetaStar)

  4. Calculate denominator <- p(theta|y)*g(thetaStar|theta)

  5. q<-numerator/denominator

  6. Set r -> min(1, q)

  7. Set theta -> thetaStar with probability r else theta

  8. Repeat this for all parameters in the parameter set.

  9. 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…

  1. Calculate numerator <- p(thetaStar|y)*g(theta|thetaStar)

  2. 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

  1. Calculate numerator <- p(thetaStar|y)*g(theta|thetaStar)

  2. Calculate denominator <- p(theta|y)*g(thetaStar|theta)

  3. 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=