1 Local level model set up

The following exercise is inspired in Gamerman, Reis and Salazar (2006) Comparison of sampling schemes for dynamic linear models. International Statistical Review, 74, 203-214.

The purpose of the exercise is to compare two MCMC schemes to sample states and fixed parameters in the first order dynamic linear model, also known as the local level model that we have already discussed in class. More specifically, the local level model assumes that the time series data, \(\mbox{data}=\{y_1,\ldots,y_n\}\), can be described as follows, for \(t=1,\ldots,n\) \[\begin{eqnarray*} y_t &=& \beta_t + \nu_t \qquad \nu_t \sim N(0,V)\\ \beta_t &=& \beta_{t-1} + \omega_t \qquad \omega_t \sim N(0,W), \end{eqnarray*}\] with \(\beta_0 \sim N(m_0,C_0)\) and where \(W/V\) is commonly known as the signal to noise ratio (SNR). Here \(m_0\) and \(C_0\) are known hyperparameters.

The model is completed with prior distributions for the error variances, \(V\) and \(W\). Here, we will assume that \[\begin{eqnarray*} V &\sim& IG(a_\nu,b_\nu)\\ W &\sim& IG(a_\omega,b_\omega), \end{eqnarray*}\] \(a_\nu,b_\nu,a_\omega,b_\omega\) known hyperparameters.

2 Simulating one dataset

For simplication, we created the data simulation function sample.data.

sample.data = function(n,W,V,beta0){
  sdW     = sqrt(W)
  sdV     = sqrt(V)
  beta    = rep(0,n)
  y       = rep(0,n)
  beta[1] = rnorm(1,beta0,sdW)
  y[1]    = rnorm(1,beta[1],sdV)
  for (t in 2:n){
    beta[t] = rnorm(1,beta[t-1],sdW)  
    y[t]    = rnorm(1,beta[t],sdV)
  }
  return(y)
}

We simulated \(n=100\) observations, starting with \(\beta_0=0\) and fixing \(V=1.0\) and \(W=0.25\) so the SNR is 0.25.

set.seed(17389)
n     = 100
V     = 1.0
W     = 0.25
beta0 = 0.0
y     = sample.data(n,W,V,beta0)
ts.plot(y)

3 Block-move

The following routine mcmc.joint samples \((\beta_1,\ldots,\beta_n)\), \(V\) and \(W\) iteratively from their full conditional posterior distributions, ie. \[\begin{eqnarray*} p(\beta_1,\ldots,\beta_n|V,W,\mbox{data})\\ p(V|\beta_1,\ldots,\beta_n,W,\mbox{data})\\ p(W|\beta_1,\ldots,\beta_n,V,\mbox{data}), \end{eqnarray*}\] and returns an \(M \times (n+2)\) matrix with \(M\) draws of the \((n+2)\)-dimensional vector \((\beta_1,\ldots,\beta_n,V,W)\).

The full conditional distributions of \(V\) and \(W\) are both Inverse-Gamma distributions, while the full conditional distribution of \((\beta_1,\ldots,\beta_n)\) is \(n\)-variate normal distribution and its joint sampling is facilitated by the kalman recursions, both filtering and smoothing.

Below, \(a_1=m_0\) and \(R_1=C_0+W\), while \(M_0\) and \(M\) are MCMC draws, with the first \(M_0\) draws discarded (burn-in or warm-up) and the following \(M\) draws stores for posterior inference. Initial values for \(V\) and \(W\) are the two last arguments of the function mcmc.joint.

mcmc.joint = function(y,a1,R1,av,bv,aw,bw,M0,M,V,W){
  n      = length(y)
  V.draw = V
  W.draw = W
  niter  = M0+M
  draws  = matrix(0,niter,n+2)
  for (iter in 1:niter){
    beta.draw = ffbs(y,rep(1,n),0.0,V.draw,1.0,0.0,W.draw,a1,R1,nd=1)
    par2v     = bv+sum((y-beta.draw)^2)/2
    par2w     = bw+sum((beta.draw[2:n]-beta.draw[1:(n-1)])^2)/2
    V.draw    = 1/rgamma(1,av+n/2,par2v)
    W.draw    = 1/rgamma(1,aw+(n-1)/2,par2w)
    draws[iter,] = c(beta.draw,V.draw,W.draw)
  }
  return(draws[(M0+1):niter,])
}

3.1 Sampling from \(p(\beta_1,\ldots,\beta_n|V,W,\mbox{data})\)

The following routine ffbs was actually written to be able to sample the states jointly via the forward filtering backward sampling (FFBS) scheme for a more general class of normal dynamic linear model (NDLM):

\[\begin{eqnarray*} y_t &=& F_t^T \theta_t + \nu_t \qquad \nu_t \sim N(0,V)\\ \theta_t &=& \gamma + G \theta_{t-1} + \omega_t \qquad \omega_t \sim N(0,W), \end{eqnarray*}\] where \(F_t\) is a vector of observed components, with \(\theta_t\) a state vector of the same dimensions. If \(F_t\) are the usual definition of regressors in linear regression models, then \(\theta_t\) are the time-varying regression coefficients. For simple local level model, we have that \(F_t=1, \ \ \forall t\), \(\gamma=0\), \(G=1\) and \(\theta_t=\beta_t\). Another special case, that we have studied in this class, is the AR(1) plus noise model: \[\begin{eqnarray*} y_t &=& \theta_t + \nu_t \qquad\qquad \nu_t \sim N(0,V)\\ \theta_t &=& \gamma + \phi \theta_{t-1} + \omega_t \qquad \omega_t \sim N(0,W). \end{eqnarray*}\]

We use the function ffbs to draw \(\beta_1,\ldots,\beta_n\) in the MCMC function mcmc.joint.

ffbs = function(y,Ft,alpha,V,G,gamma,W,a1,R1,nd=1){
  n = length(y)
  if (length(Ft)==1){Ft=rep(Ft,n)}
  if (length(alpha)==1){alpha=rep(alpha,n)}
  if (length(V)==1){V=rep(V,n)}
  a = rep(0,n)
  R = rep(0,n)
  m = rep(0,n)
  C = rep(0,n)
  B = rep(0,n-1)
  H = rep(0,n-1)
  # time t=1
  a[1] = a1
  R[1] = R1
  f    = alpha[1]+Ft[1]*a[1]
  Q    = R[1]*Ft[1]**2+V[1]
  A    = R[1]*Ft[1]/Q
  m[1] = a[1]+A*(y[1]-f)
  C[1] = R[1]-Q*A**2
  # forward filtering
  for (t in 2:n){
    a[t] = gamma + G*m[t-1]
    R[t] = C[t-1]*G**2 + W
    f    = alpha[t]+Ft[t]*a[t]
    Q    = R[t]*Ft[t]**2+V[t]
    A    = R[t]*Ft[t]/Q
    m[t] = a[t]+A*(y[t]-f)
    C[t] = R[t]-Q*A**2
    B[t-1] = C[t-1]*G/R[t]
    H[t-1] = sqrt(C[t-1]-R[t]*B[t-1]**2)
  }
  # backward sampling
  theta = matrix(0,nd,n)
  theta[,n] = rnorm(nd,m[n],sqrt(C[n]))
  for (t in (n-1):1)
    theta[,t] = rnorm(nd,m[t]+B[t]*(theta[,t+1]-a[t+1]),H[t])
  if (nd==1){
    theta[1,]
  }
  else{
    theta
  }
}

3.2 Posterior inference

# Prior for beta(0) ~ N(m0,C0)
# Prior for beta(1) ~ N(a1,R1), a1=m0,R1=C0+W
a1     = 0
R1     = 10
av     = 2.01
bv     = 1.01
aw     = 2.01
bw     = 1.01*W    
m0     = 0
C0     = 10-W

# MCMC initual values
V0     = 1
W0     = 0.5
M0     = 10000
M      = 10000

set.seed(65432)
draws1 = mcmc.joint(y,a1,R1,av,bv,aw,bw,M0,M,V0,W0)

# states MCMC autocorrelations and posterior quantiles
q1 = t(apply(draws1[,1:n],2,quantile,c(0.05,0.5,0.95)))
lagmax = 30
acfs = array(0,c(2,n,lagmax))
for (i in 1:n)
  acfs[1,i,] = acf(draws1[,i],plot=FALSE,lag.max=lagmax)$acf[2:(lagmax+1)]

3.2.1 Posterior inference for \(V\) and \(W\)

par(mfrow=c(2,3),mar = c(2, 2, 1, 1))
ts.plot(draws1[,n+1],ylab="",xlab="Iteration",main="V")
acf(draws1[,n+1],main="")
hist(draws1[,n+1],prob=TRUE,main="",xlab="")
ts.plot(draws1[,n+2],ylab="",xlab="Iteration",main="W")
acf(draws1[,n+2],main="")
hist(draws1[,n+2],prob=TRUE,main="",xlab="")

3.2.2 MCMC autocorrelation for states

par(mfrow=c(1,1),mar=c(5, 4, 4, 2))
ts.plot(t(acfs[1,,]),xlab="Lag",ylab="ACF",main="",ylim=range(acfs))

3.2.3 Posterior quantiles for states

par(mfrow=c(1,1))
ts.plot(q1,col=c(3,2,3),ylim=range(y,q1),ylab="States")
points(y)
title("5th, 50th and 95th percentiles")

4 Single-move

The routine mcmc.single samples \(\beta_1,\ldots,\beta_n\), \(V\) and \(W\) iteratively from their univariate full conditional posterior distributions, ie. \[\begin{eqnarray*} p(V|\beta_1,\ldots,\beta_n,W,\mbox{data})\\ p(W|\beta_1,\ldots,\beta_n,V,\mbox{data}), \end{eqnarray*}\] and \[ p(\beta_t|\beta_{-t},V,W,\mbox{data}) \qquad t=1,\ldots,n, \] where \(\beta_{-t}\) is the vector \((\beta_1,\ldots,\beta_n)\) excluding the \(\beta_t\) state.

mcmc.single returns an \(M \times (n+2)\) matrix with \(M\) draws of the \((n+2)\)-dimensional vector \((\beta_1,\ldots,\beta_n,V,W)\).

mcmc.single = function(y,a1,R1,av,bv,aw,bw,M0,M,beta,V,W){
  n         = length(y)
  beta.draw = beta
  V.draw    = V
  W.draw    = W
  niter     = M0+M
  draws     = matrix(0,niter,n+2)
  for (iter in 1:niter){
    beta.draw = single.sampling(y,a1,R1,av,bv,aw,bw,M0,M,beta.draw,V.draw,W.draw)
    par2v     = bv+sum((y-beta.draw)^2)/2
    par2w     = bw+sum((beta.draw[2:n]-beta.draw[1:(n-1)])^2)/2
    V.draw = 1/rgamma(1,av+n/2,par2v)
    W.draw = 1/rgamma(1,aw+(n-1)/2,par2w)
    draws[iter,] = c(beta.draw,V.draw,W.draw)
  }
  return(draws[(M0+1):niter,])
}

4.1 Sampling from \(p(\beta_t|\beta_{-t},V,W,\mbox{data})\)

The routine single.sampling samples, for \(t=1,\ldots,n\), from the univariate full conditional distributons \[ p(\beta_t|\beta_{-t},V,W,\mbox{data}). \]

single.sampling = function(y,a1,R1,av,bv,aw,bw,M0,M,beta,V,W){
  V1 = 1/(1/R1+1/W+1/V)
  b1 = V1*(a1/R1+beta[2]/W+y[1]/V)
  beta[1] = rnorm(1,b1,sqrt(V1))
  for (t in 2:(n-1)){
    Vt = 1/(2/W+1/V)         
    bt = Vt*((beta[t-1]+beta[t+1])/W+y[t]/V)
    beta[t] = rnorm(1,bt,sqrt(Vt))
  }  
  Vn = 1/(1/W+1/V)
  bn = Vn*(beta[n-1]/W+y[n]/V)
  beta[n] = rnorm(1,bn,sqrt(Vn))
  return(beta)
}

4.2 Posterior inference

set.seed(65432)
beta0v = y
draws2 = mcmc.single(y,a1,R1,av,bv,aw,bw,M0,M,beta0v,V0,W0)
q2     = t(apply(draws2[,1:n],2,quantile,c(0.05,0.5,0.95)))
for (i in 1:n)
  acfs[2,i,] = acf(draws2[,i],plot=FALSE,lag.max=lagmax)$acf[2:(lagmax+1)]

4.2.1 Posterior inference for \(V\) and \(W\)

par(mfrow=c(2,3),mar = c(2, 2, 1, 1))
ts.plot(draws2[,n+1],ylab="",xlab="Iteration",main="V")
acf(draws2[,n+1],main="")
hist(draws2[,n+1],prob=TRUE,main="",xlab="")
ts.plot(draws2[,n+2],ylab="",xlab="Iteration",main="W")
acf(draws2[,n+2],main="")
hist(draws2[,n+2],prob=TRUE,main="",xlab="")

4.2.2 MCMC autocorrelation for states

par(mfrow=c(1,1),mar=c(5, 4, 4, 2))
ts.plot(t(acfs[2,,]),xlab="Lag",ylab="ACF",main="",ylim=range(acfs))

4.2.3 Posterior quantiles for states

par(mfrow=c(1,1))
ts.plot(q2,col=c(3,2,3),ylim=range(y,q2),ylab="States")
points(y)
title("5th, 50th and 95th percentiles")

5 Comparing MCMC schemes

5.1 Comparing ACFs

par(mfrow=c(1,1),mar=c(5, 4, 4, 2))
ts.plot(t(acfs[2,,]),xlab="Lag",ylab="ACF",main="",ylim=range(acfs))
for (i in 1:n)
  lines(acfs[1,i,],col=2)
legend("topright",legend=c("Single-move","Block-move"),col=1:2,lwd=2,bty="n")

5.2 Comparing state posterior quantiles

par(mfrow=c(1,1),mar=c(5, 4, 4, 2))
ts.plot(q2)
lines(q1[,1],col=2)
lines(q1[,2],col=2)
lines(q1[,3],col=2)
legend("top",legend=c("Single-move","Block-move"),col=1:2,lwd=2,bty="n")

5.3 Comparing marginal posterior for \(V\) and \(W\)

par(mfrow=c(1,2),mar = c(2, 2, 1, 1))
plot(density(draws2[,n+1]),main="V")
lines(density(draws1[,n+1]),col=2)
plot(density(draws2[,n+2]),main="W")
lines(density(draws1[,n+2]),col=2)
legend("topright",legend=c("Single-move","Block-move"),col=1:2,lwd=2,bty="n")

6 Replications

We replicate the above exercise for 20 sets of time series data and run both MCMC schemes. We assume that for \(n=100\) and $W {0.01,0.25,1.0}.

set.seed(65432123)
nrep  = 20
n     = 100
V     = 1.0
M0    = 1000
M     = 1000
beta0 = 0.0
V0    = 1.0
W0    = 0.5
a1    = 0.0
R1    = 10.0
av    = 2.01
bv    = 1.01
aw    = 2.01
m0    = 0.0
ws    = c(0.01,0.25,1.0)
nws   = length(ws)
neffs = array(0,c(nrep,nws,2))
for (rep in 1:nrep){
  for (i in 1:3){
    y      = sample.data(n,ws[i],V,beta0)
    beta0v = y
    C0     = 10-W
    bw     = 1.01*W    
    draws1 = mcmc.joint(y,a1,R1,av,bv,aw,bw,M0,M,V0,W0)
    acf1  = acf(draws1[,n+1]/draws1[,n+2],lag.max=31,plot=FALSE)
    neff1 = M/(1+2*sum(acf1$acf[2:31]))
    draws2 = mcmc.single(y,a1,R1,av,bv,aw,bw,M0,M,beta0v,V0,W0)
    acf2  = acf(draws2[,n+1]/draws2[,n+2],lag.max=31,plot=FALSE)
    neff2 = M/(1+2*sum(acf2$acf[2:31]))
    neffs[rep,i,] = trunc(c(neff1,neff2))
  }
}

6.1 Effective sample size (ESS)

comb = c("(100,0.01)","(100,0.25)","(100,1)")
par(mfrow=c(1,1),mar=c(5, 4, 4, 2))
boxplot(cbind(neffs[,,1],neffs[,,2]),outline=FALSE,names=c(comb,comb),
        xlab=paste("(n,W) for V=",V,sep=""))
abline(v=3.5)
legend("topleft",legend=c("Block-move"),bty="n")
legend("topright",legend=c("Single-move"),bty="n")
title(paste("MCMC posterior draws = ",M,sep=""))

6.2 Relative ESS

par(mfrow=c(1,1),mar=c(5, 4, 4, 2))
boxplot(neffs[,,2]/neffs[,,1],outline=FALSE,names=comb,ylab="Relative ESS",
        xlab=paste("(n,W) for V=",V,sep=""))
abline(h=1,lty=2)
title("Ratio single-move to block-move")

LS0tCnRpdGxlOiAiRmlyc3Qgb3JkZXIgRExNIgpzdWJ0aXRsZTogIlNpbmdsZS1tb3ZlIHZzIGJsb2NrLW1vdmUiCmF1dGhvcjogIkhlZGliZXJ0IEYuIExvcGVzIgpkYXRlOiAiYHIgU3lzLkRhdGUoKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdGhlbWU6IHBhcGVyCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzCiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMwogICAgdG9jX2NvbGxhcHNlZDogdHJ1ZQojICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlCi0tLQogIAoKIyBMb2NhbCBsZXZlbCBtb2RlbCBzZXQgdXAKClRoZSBmb2xsb3dpbmcgZXhlcmNpc2UgaXMgaW5zcGlyZWQgaW4gR2FtZXJtYW4sIFJlaXMgYW5kIFNhbGF6YXIgKDIwMDYpIENvbXBhcmlzb24gb2Ygc2FtcGxpbmcgc2NoZW1lcyBmb3IgZHluYW1pYyBsaW5lYXIgbW9kZWxzLiAqSW50ZXJuYXRpb25hbCBTdGF0aXN0aWNhbCBSZXZpZXcqLCA3NCwgMjAzLTIxNC4KClRoZSBwdXJwb3NlIG9mIHRoZSBleGVyY2lzZSBpcyB0byBjb21wYXJlIHR3byBNQ01DIHNjaGVtZXMgdG8gc2FtcGxlIHN0YXRlcyBhbmQgZml4ZWQgcGFyYW1ldGVycyBpbiB0aGUgZmlyc3Qgb3JkZXIgZHluYW1pYyBsaW5lYXIgbW9kZWwsIGFsc28ga25vd24gYXMgdGhlIGxvY2FsIGxldmVsIG1vZGVsIHRoYXQgd2UgaGF2ZSBhbHJlYWR5IGRpc2N1c3NlZCBpbiBjbGFzcy4gIE1vcmUgc3BlY2lmaWNhbGx5LCB0aGUgbG9jYWwgbGV2ZWwgbW9kZWwgYXNzdW1lcyB0aGF0IHRoZSB0aW1lIHNlcmllcyBkYXRhLCAkXG1ib3h7ZGF0YX09XHt5XzEsXGxkb3RzLHlfblx9JCwgY2FuIGJlIGRlc2NyaWJlZCBhcyBmb2xsb3dzLCBmb3IgJHQ9MSxcbGRvdHMsbiQKXGJlZ2lue2VxbmFycmF5Kn0KeV90ICY9JiBcYmV0YV90ICsgXG51X3QgXHFxdWFkIFxudV90IFxzaW0gTigwLFYpXFwKXGJldGFfdCAmPSYgXGJldGFfe3QtMX0gKyBcb21lZ2FfdCBccXF1YWQgXG9tZWdhX3QgXHNpbSBOKDAsVyksClxlbmR7ZXFuYXJyYXkqfQp3aXRoICRcYmV0YV8wIFxzaW0gTihtXzAsQ18wKSQgYW5kIHdoZXJlICRXL1YkIGlzIGNvbW1vbmx5IGtub3duIGFzIHRoZSBzaWduYWwgdG8gbm9pc2UgcmF0aW8gKFNOUikuICBIZXJlICRtXzAkIGFuZCAkQ18wJCBhcmUga25vd24gaHlwZXJwYXJhbWV0ZXJzLgoKVGhlIG1vZGVsIGlzIGNvbXBsZXRlZCB3aXRoIHByaW9yIGRpc3RyaWJ1dGlvbnMgZm9yIHRoZSBlcnJvciB2YXJpYW5jZXMsICRWJCBhbmQgJFckLiAgSGVyZSwgd2Ugd2lsbCBhc3N1bWUgdGhhdApcYmVnaW57ZXFuYXJyYXkqfQpWICZcc2ltJiBJRyhhX1xudSxiX1xudSlcXApXICZcc2ltJiBJRyhhX1xvbWVnYSxiX1xvbWVnYSksClxlbmR7ZXFuYXJyYXkqfQokYV9cbnUsYl9cbnUsYV9cb21lZ2EsYl9cb21lZ2EkIGtub3duIGh5cGVycGFyYW1ldGVycy4KCgojIFNpbXVsYXRpbmcgb25lIGRhdGFzZXQKCkZvciBzaW1wbGljYXRpb24sIHdlIGNyZWF0ZWQgdGhlIGRhdGEgc2ltdWxhdGlvbiBmdW5jdGlvbiAqKnNhbXBsZS5kYXRhKiouCgpgYGB7cn0Kc2FtcGxlLmRhdGEgPSBmdW5jdGlvbihuLFcsVixiZXRhMCl7CiAgc2RXICAgICA9IHNxcnQoVykKICBzZFYgICAgID0gc3FydChWKQogIGJldGEgICAgPSByZXAoMCxuKQogIHkgICAgICAgPSByZXAoMCxuKQogIGJldGFbMV0gPSBybm9ybSgxLGJldGEwLHNkVykKICB5WzFdICAgID0gcm5vcm0oMSxiZXRhWzFdLHNkVikKICBmb3IgKHQgaW4gMjpuKXsKICAgIGJldGFbdF0gPSBybm9ybSgxLGJldGFbdC0xXSxzZFcpICAKICAgIHlbdF0gICAgPSBybm9ybSgxLGJldGFbdF0sc2RWKQogIH0KICByZXR1cm4oeSkKfQpgYGAKCldlIHNpbXVsYXRlZCAkbj0xMDAkIG9ic2VydmF0aW9ucywgc3RhcnRpbmcgd2l0aCAkXGJldGFfMD0wJCBhbmQgZml4aW5nICRWPTEuMCQgYW5kICRXPTAuMjUkIHNvIHRoZSBTTlIgaXMgMC4yNS4KCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NX0Kc2V0LnNlZWQoMTczODkpCm4gICAgID0gMTAwClYgICAgID0gMS4wClcgICAgID0gMC4yNQpiZXRhMCA9IDAuMAp5ICAgICA9IHNhbXBsZS5kYXRhKG4sVyxWLGJldGEwKQp0cy5wbG90KHkpCmBgYAoKIyBCbG9jay1tb3ZlCgpUaGUgZm9sbG93aW5nIHJvdXRpbmUgKiptY21jLmpvaW50Kiogc2FtcGxlcyAkKFxiZXRhXzEsXGxkb3RzLFxiZXRhX24pJCwgJFYkIGFuZCAkVyQgaXRlcmF0aXZlbHkgZnJvbSB0aGVpciBmdWxsIGNvbmRpdGlvbmFsIHBvc3RlcmlvciBkaXN0cmlidXRpb25zLCBpZS4KXGJlZ2lue2VxbmFycmF5Kn0KcChcYmV0YV8xLFxsZG90cyxcYmV0YV9ufFYsVyxcbWJveHtkYXRhfSlcXApwKFZ8XGJldGFfMSxcbGRvdHMsXGJldGFfbixXLFxtYm94e2RhdGF9KVxcCnAoV3xcYmV0YV8xLFxsZG90cyxcYmV0YV9uLFYsXG1ib3h7ZGF0YX0pLApcZW5ke2VxbmFycmF5Kn0KYW5kIHJldHVybnMgYW4gJE0gXHRpbWVzIChuKzIpJCBtYXRyaXggd2l0aCAkTSQgZHJhd3Mgb2YgdGhlICQobisyKSQtZGltZW5zaW9uYWwgdmVjdG9yICQoXGJldGFfMSxcbGRvdHMsXGJldGFfbixWLFcpJC4KClRoZSBmdWxsIGNvbmRpdGlvbmFsIGRpc3RyaWJ1dGlvbnMgb2YgJFYkIGFuZCAkVyQgYXJlIGJvdGggSW52ZXJzZS1HYW1tYSBkaXN0cmlidXRpb25zLCB3aGlsZSB0aGUgZnVsbCBjb25kaXRpb25hbCBkaXN0cmlidXRpb24gb2YKJChcYmV0YV8xLFxsZG90cyxcYmV0YV9uKSQgaXMgJG4kLXZhcmlhdGUgbm9ybWFsIGRpc3RyaWJ1dGlvbiBhbmQgaXRzIGpvaW50IHNhbXBsaW5nIGlzIGZhY2lsaXRhdGVkIGJ5IHRoZSBrYWxtYW4gcmVjdXJzaW9ucywgYm90aCBmaWx0ZXJpbmcgYW5kIHNtb290aGluZy4gCgpCZWxvdywgJGFfMT1tXzAkIGFuZCAkUl8xPUNfMCtXJCwgd2hpbGUgJE1fMCQgYW5kICRNJCBhcmUgTUNNQyBkcmF3cywgd2l0aCB0aGUgZmlyc3QgJE1fMCQgZHJhd3MgZGlzY2FyZGVkIChidXJuLWluIG9yIHdhcm0tdXApIGFuZCB0aGUgZm9sbG93aW5nICRNJCBkcmF3cyBzdG9yZXMgZm9yIHBvc3RlcmlvciBpbmZlcmVuY2UuICBJbml0aWFsIHZhbHVlcyBmb3IgJFYkIGFuZCAkVyQgYXJlIHRoZSB0d28gbGFzdCBhcmd1bWVudHMgb2YgdGhlIGZ1bmN0aW9uICoqbWNtYy5qb2ludCoqLiAKCmBgYHtyfQptY21jLmpvaW50ID0gZnVuY3Rpb24oeSxhMSxSMSxhdixidixhdyxidyxNMCxNLFYsVyl7CiAgbiAgICAgID0gbGVuZ3RoKHkpCiAgVi5kcmF3ID0gVgogIFcuZHJhdyA9IFcKICBuaXRlciAgPSBNMCtNCiAgZHJhd3MgID0gbWF0cml4KDAsbml0ZXIsbisyKQogIGZvciAoaXRlciBpbiAxOm5pdGVyKXsKICAgIGJldGEuZHJhdyA9IGZmYnMoeSxyZXAoMSxuKSwwLjAsVi5kcmF3LDEuMCwwLjAsVy5kcmF3LGExLFIxLG5kPTEpCiAgICBwYXIydiAgICAgPSBiditzdW0oKHktYmV0YS5kcmF3KV4yKS8yCiAgICBwYXIydyAgICAgPSBidytzdW0oKGJldGEuZHJhd1syOm5dLWJldGEuZHJhd1sxOihuLTEpXSleMikvMgogICAgVi5kcmF3ICAgID0gMS9yZ2FtbWEoMSxhdituLzIscGFyMnYpCiAgICBXLmRyYXcgICAgPSAxL3JnYW1tYSgxLGF3KyhuLTEpLzIscGFyMncpCiAgICBkcmF3c1tpdGVyLF0gPSBjKGJldGEuZHJhdyxWLmRyYXcsVy5kcmF3KQogIH0KICByZXR1cm4oZHJhd3NbKE0wKzEpOm5pdGVyLF0pCn0KYGBgCgojIyBTYW1wbGluZyBmcm9tICRwKFxiZXRhXzEsXGxkb3RzLFxiZXRhX258VixXLFxtYm94e2RhdGF9KSQKClRoZSBmb2xsb3dpbmcgcm91dGluZSAqKmZmYnMqKiB3YXMgYWN0dWFsbHkgd3JpdHRlbiB0byBiZSBhYmxlIHRvIHNhbXBsZSB0aGUgc3RhdGVzIGpvaW50bHkgdmlhIHRoZSBmb3J3YXJkIGZpbHRlcmluZyBiYWNrd2FyZCBzYW1wbGluZyAoRkZCUykgc2NoZW1lIGZvciBhIG1vcmUgZ2VuZXJhbCBjbGFzcyBvZiBub3JtYWwgZHluYW1pYyBsaW5lYXIgbW9kZWwgKE5ETE0pOgoKXGJlZ2lue2VxbmFycmF5Kn0KeV90ICY9JiBGX3ReVCBcdGhldGFfdCArIFxudV90IFxxcXVhZCBcbnVfdCBcc2ltIE4oMCxWKVxcClx0aGV0YV90ICY9JiBcZ2FtbWEgKyBHIFx0aGV0YV97dC0xfSArIFxvbWVnYV90IFxxcXVhZCBcb21lZ2FfdCBcc2ltIE4oMCxXKSwKXGVuZHtlcW5hcnJheSp9CndoZXJlICRGX3QkIGlzIGEgdmVjdG9yIG9mIG9ic2VydmVkIGNvbXBvbmVudHMsIHdpdGggJFx0aGV0YV90JCBhIHN0YXRlIHZlY3RvciBvZiB0aGUgc2FtZSBkaW1lbnNpb25zLiAgSWYgJEZfdCQgYXJlIHRoZSB1c3VhbCBkZWZpbml0aW9uIG9mIHJlZ3Jlc3NvcnMgaW4gbGluZWFyIHJlZ3Jlc3Npb24gbW9kZWxzLCB0aGVuICRcdGhldGFfdCQgYXJlIHRoZSB0aW1lLXZhcnlpbmcgcmVncmVzc2lvbiBjb2VmZmljaWVudHMuICBGb3Igc2ltcGxlIGxvY2FsIGxldmVsIG1vZGVsLCAKd2UgaGF2ZSB0aGF0ICRGX3Q9MSwgXCBcIFxmb3JhbGwgdCQsICRcZ2FtbWE9MCQsICRHPTEkIGFuZCAkXHRoZXRhX3Q9XGJldGFfdCQuICBBbm90aGVyIHNwZWNpYWwgY2FzZSwgdGhhdCB3ZSBoYXZlIHN0dWRpZWQgaW4gdGhpcyBjbGFzcywgaXMgdGhlIEFSKDEpIHBsdXMgbm9pc2UgbW9kZWw6ClxiZWdpbntlcW5hcnJheSp9CnlfdCAmPSYgXHRoZXRhX3QgKyBcbnVfdCBccXF1YWRccXF1YWQgXG51X3QgXHNpbSBOKDAsVilcXApcdGhldGFfdCAmPSYgXGdhbW1hICsgXHBoaSBcdGhldGFfe3QtMX0gKyBcb21lZ2FfdCBccXF1YWQgXG9tZWdhX3QgXHNpbSBOKDAsVykuClxlbmR7ZXFuYXJyYXkqfQoKV2UgdXNlIHRoZSBmdW5jdGlvbiAqKmZmYnMqKiB0byBkcmF3ICRcYmV0YV8xLFxsZG90cyxcYmV0YV9uJCBpbiB0aGUgTUNNQyBmdW5jdGlvbiAqKm1jbWMuam9pbnQqKi4KCmBgYHtyfQpmZmJzID0gZnVuY3Rpb24oeSxGdCxhbHBoYSxWLEcsZ2FtbWEsVyxhMSxSMSxuZD0xKXsKICBuID0gbGVuZ3RoKHkpCiAgaWYgKGxlbmd0aChGdCk9PTEpe0Z0PXJlcChGdCxuKX0KICBpZiAobGVuZ3RoKGFscGhhKT09MSl7YWxwaGE9cmVwKGFscGhhLG4pfQogIGlmIChsZW5ndGgoVik9PTEpe1Y9cmVwKFYsbil9CiAgYSA9IHJlcCgwLG4pCiAgUiA9IHJlcCgwLG4pCiAgbSA9IHJlcCgwLG4pCiAgQyA9IHJlcCgwLG4pCiAgQiA9IHJlcCgwLG4tMSkKICBIID0gcmVwKDAsbi0xKQogICMgdGltZSB0PTEKICBhWzFdID0gYTEKICBSWzFdID0gUjEKICBmICAgID0gYWxwaGFbMV0rRnRbMV0qYVsxXQogIFEgICAgPSBSWzFdKkZ0WzFdKioyK1ZbMV0KICBBICAgID0gUlsxXSpGdFsxXS9RCiAgbVsxXSA9IGFbMV0rQSooeVsxXS1mKQogIENbMV0gPSBSWzFdLVEqQSoqMgogICMgZm9yd2FyZCBmaWx0ZXJpbmcKICBmb3IgKHQgaW4gMjpuKXsKICAgIGFbdF0gPSBnYW1tYSArIEcqbVt0LTFdCiAgICBSW3RdID0gQ1t0LTFdKkcqKjIgKyBXCiAgICBmICAgID0gYWxwaGFbdF0rRnRbdF0qYVt0XQogICAgUSAgICA9IFJbdF0qRnRbdF0qKjIrVlt0XQogICAgQSAgICA9IFJbdF0qRnRbdF0vUQogICAgbVt0XSA9IGFbdF0rQSooeVt0XS1mKQogICAgQ1t0XSA9IFJbdF0tUSpBKioyCiAgICBCW3QtMV0gPSBDW3QtMV0qRy9SW3RdCiAgICBIW3QtMV0gPSBzcXJ0KENbdC0xXS1SW3RdKkJbdC0xXSoqMikKICB9CiAgIyBiYWNrd2FyZCBzYW1wbGluZwogIHRoZXRhID0gbWF0cml4KDAsbmQsbikKICB0aGV0YVssbl0gPSBybm9ybShuZCxtW25dLHNxcnQoQ1tuXSkpCiAgZm9yICh0IGluIChuLTEpOjEpCiAgICB0aGV0YVssdF0gPSBybm9ybShuZCxtW3RdK0JbdF0qKHRoZXRhWyx0KzFdLWFbdCsxXSksSFt0XSkKICBpZiAobmQ9PTEpewogICAgdGhldGFbMSxdCiAgfQogIGVsc2V7CiAgICB0aGV0YQogIH0KfQpgYGAgICAgICAgICAgICAgICAgICAgICAgICAgIAoKIyMgUG9zdGVyaW9yIGluZmVyZW5jZQoKYGBge3J9CiMgUHJpb3IgZm9yIGJldGEoMCkgfiBOKG0wLEMwKQojIFByaW9yIGZvciBiZXRhKDEpIH4gTihhMSxSMSksIGExPW0wLFIxPUMwK1cKYTEgICAgID0gMApSMSAgICAgPSAxMAphdiAgICAgPSAyLjAxCmJ2ICAgICA9IDEuMDEKYXcgICAgID0gMi4wMQpidyAgICAgPSAxLjAxKlcgICAgCm0wICAgICA9IDAKQzAgICAgID0gMTAtVwoKIyBNQ01DIGluaXR1YWwgdmFsdWVzClYwICAgICA9IDEKVzAgICAgID0gMC41Ck0wICAgICA9IDEwMDAwCk0gICAgICA9IDEwMDAwCgpzZXQuc2VlZCg2NTQzMikKZHJhd3MxID0gbWNtYy5qb2ludCh5LGExLFIxLGF2LGJ2LGF3LGJ3LE0wLE0sVjAsVzApCgojIHN0YXRlcyBNQ01DIGF1dG9jb3JyZWxhdGlvbnMgYW5kIHBvc3RlcmlvciBxdWFudGlsZXMKcTEgPSB0KGFwcGx5KGRyYXdzMVssMTpuXSwyLHF1YW50aWxlLGMoMC4wNSwwLjUsMC45NSkpKQpsYWdtYXggPSAzMAphY2ZzID0gYXJyYXkoMCxjKDIsbixsYWdtYXgpKQpmb3IgKGkgaW4gMTpuKQogIGFjZnNbMSxpLF0gPSBhY2YoZHJhd3MxWyxpXSxwbG90PUZBTFNFLGxhZy5tYXg9bGFnbWF4KSRhY2ZbMjoobGFnbWF4KzEpXQpgYGAKCgojIyMgUG9zdGVyaW9yIGluZmVyZW5jZSBmb3IgJFYkIGFuZCAkVyQKYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD01fQpwYXIobWZyb3c9YygyLDMpLG1hciA9IGMoMiwgMiwgMSwgMSkpCnRzLnBsb3QoZHJhd3MxWyxuKzFdLHlsYWI9IiIseGxhYj0iSXRlcmF0aW9uIixtYWluPSJWIikKYWNmKGRyYXdzMVssbisxXSxtYWluPSIiKQpoaXN0KGRyYXdzMVssbisxXSxwcm9iPVRSVUUsbWFpbj0iIix4bGFiPSIiKQp0cy5wbG90KGRyYXdzMVssbisyXSx5bGFiPSIiLHhsYWI9Ikl0ZXJhdGlvbiIsbWFpbj0iVyIpCmFjZihkcmF3czFbLG4rMl0sbWFpbj0iIikKaGlzdChkcmF3czFbLG4rMl0scHJvYj1UUlVFLG1haW49IiIseGxhYj0iIikKYGBgCgojIyMgTUNNQyBhdXRvY29ycmVsYXRpb24gZm9yIHN0YXRlcwpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTV9CnBhcihtZnJvdz1jKDEsMSksbWFyPWMoNSwgNCwgNCwgMikpCnRzLnBsb3QodChhY2ZzWzEsLF0pLHhsYWI9IkxhZyIseWxhYj0iQUNGIixtYWluPSIiLHlsaW09cmFuZ2UoYWNmcykpCmBgYAoKIyMjIFBvc3RlcmlvciBxdWFudGlsZXMgZm9yIHN0YXRlcwpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTV9CnBhcihtZnJvdz1jKDEsMSkpCnRzLnBsb3QocTEsY29sPWMoMywyLDMpLHlsaW09cmFuZ2UoeSxxMSkseWxhYj0iU3RhdGVzIikKcG9pbnRzKHkpCnRpdGxlKCI1dGgsIDUwdGggYW5kIDk1dGggcGVyY2VudGlsZXMiKQpgYGAKCgoKCiMgU2luZ2xlLW1vdmUKClRoZSByb3V0aW5lICoqbWNtYy5zaW5nbGUqKiBzYW1wbGVzICRcYmV0YV8xLFxsZG90cyxcYmV0YV9uJCwgJFYkIGFuZCAkVyQgaXRlcmF0aXZlbHkgZnJvbSB0aGVpciB1bml2YXJpYXRlIGZ1bGwgY29uZGl0aW9uYWwgcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbnMsIGllLgpcYmVnaW57ZXFuYXJyYXkqfQpwKFZ8XGJldGFfMSxcbGRvdHMsXGJldGFfbixXLFxtYm94e2RhdGF9KVxcCnAoV3xcYmV0YV8xLFxsZG90cyxcYmV0YV9uLFYsXG1ib3h7ZGF0YX0pLApcZW5ke2VxbmFycmF5Kn0KYW5kCiQkCnAoXGJldGFfdHxcYmV0YV97LXR9LFYsVyxcbWJveHtkYXRhfSkgXHFxdWFkIHQ9MSxcbGRvdHMsbiwKJCQKd2hlcmUgJFxiZXRhX3stdH0kIGlzIHRoZSB2ZWN0b3IgJChcYmV0YV8xLFxsZG90cyxcYmV0YV9uKSQgZXhjbHVkaW5nIHRoZSAkXGJldGFfdCQgc3RhdGUuCgoqKm1jbWMuc2luZ2xlKiogcmV0dXJucyBhbiAkTSBcdGltZXMgKG4rMikkIG1hdHJpeCB3aXRoICRNJCBkcmF3cyBvZiB0aGUgJChuKzIpJC1kaW1lbnNpb25hbCB2ZWN0b3IgJChcYmV0YV8xLFxsZG90cyxcYmV0YV9uLFYsVykkLgoKYGBge3J9Cm1jbWMuc2luZ2xlID0gZnVuY3Rpb24oeSxhMSxSMSxhdixidixhdyxidyxNMCxNLGJldGEsVixXKXsKICBuICAgICAgICAgPSBsZW5ndGgoeSkKICBiZXRhLmRyYXcgPSBiZXRhCiAgVi5kcmF3ICAgID0gVgogIFcuZHJhdyAgICA9IFcKICBuaXRlciAgICAgPSBNMCtNCiAgZHJhd3MgICAgID0gbWF0cml4KDAsbml0ZXIsbisyKQogIGZvciAoaXRlciBpbiAxOm5pdGVyKXsKICAgIGJldGEuZHJhdyA9IHNpbmdsZS5zYW1wbGluZyh5LGExLFIxLGF2LGJ2LGF3LGJ3LE0wLE0sYmV0YS5kcmF3LFYuZHJhdyxXLmRyYXcpCiAgICBwYXIydiAgICAgPSBiditzdW0oKHktYmV0YS5kcmF3KV4yKS8yCiAgICBwYXIydyAgICAgPSBidytzdW0oKGJldGEuZHJhd1syOm5dLWJldGEuZHJhd1sxOihuLTEpXSleMikvMgogICAgVi5kcmF3ID0gMS9yZ2FtbWEoMSxhdituLzIscGFyMnYpCiAgICBXLmRyYXcgPSAxL3JnYW1tYSgxLGF3KyhuLTEpLzIscGFyMncpCiAgICBkcmF3c1tpdGVyLF0gPSBjKGJldGEuZHJhdyxWLmRyYXcsVy5kcmF3KQogIH0KICByZXR1cm4oZHJhd3NbKE0wKzEpOm5pdGVyLF0pCn0KYGBgCgojIyBTYW1wbGluZyBmcm9tICRwKFxiZXRhX3R8XGJldGFfey10fSxWLFcsXG1ib3h7ZGF0YX0pJAoKVGhlIHJvdXRpbmUgKipzaW5nbGUuc2FtcGxpbmcqKiBzYW1wbGVzLCBmb3IgJHQ9MSxcbGRvdHMsbiQsIGZyb20gdGhlIHVuaXZhcmlhdGUgZnVsbCBjb25kaXRpb25hbCBkaXN0cmlidXRvbnMKJCQKcChcYmV0YV90fFxiZXRhX3stdH0sVixXLFxtYm94e2RhdGF9KS4KJCQKCmBgYHtyfQpzaW5nbGUuc2FtcGxpbmcgPSBmdW5jdGlvbih5LGExLFIxLGF2LGJ2LGF3LGJ3LE0wLE0sYmV0YSxWLFcpewogIFYxID0gMS8oMS9SMSsxL1crMS9WKQogIGIxID0gVjEqKGExL1IxK2JldGFbMl0vVyt5WzFdL1YpCiAgYmV0YVsxXSA9IHJub3JtKDEsYjEsc3FydChWMSkpCiAgZm9yICh0IGluIDI6KG4tMSkpewogICAgVnQgPSAxLygyL1crMS9WKSAgICAgICAgIAogICAgYnQgPSBWdCooKGJldGFbdC0xXStiZXRhW3QrMV0pL1creVt0XS9WKQogICAgYmV0YVt0XSA9IHJub3JtKDEsYnQsc3FydChWdCkpCiAgfSAgCiAgVm4gPSAxLygxL1crMS9WKQogIGJuID0gVm4qKGJldGFbbi0xXS9XK3lbbl0vVikKICBiZXRhW25dID0gcm5vcm0oMSxibixzcXJ0KFZuKSkKICByZXR1cm4oYmV0YSkKfQpgYGAKCiMjIFBvc3RlcmlvciBpbmZlcmVuY2UKYGBge3J9CnNldC5zZWVkKDY1NDMyKQpiZXRhMHYgPSB5CmRyYXdzMiA9IG1jbWMuc2luZ2xlKHksYTEsUjEsYXYsYnYsYXcsYncsTTAsTSxiZXRhMHYsVjAsVzApCnEyICAgICA9IHQoYXBwbHkoZHJhd3MyWywxOm5dLDIscXVhbnRpbGUsYygwLjA1LDAuNSwwLjk1KSkpCmZvciAoaSBpbiAxOm4pCiAgYWNmc1syLGksXSA9IGFjZihkcmF3czJbLGldLHBsb3Q9RkFMU0UsbGFnLm1heD1sYWdtYXgpJGFjZlsyOihsYWdtYXgrMSldCmBgYAoKIyMjIFBvc3RlcmlvciBpbmZlcmVuY2UgZm9yICRWJCBhbmQgJFckCgpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTV9CnBhcihtZnJvdz1jKDIsMyksbWFyID0gYygyLCAyLCAxLCAxKSkKdHMucGxvdChkcmF3czJbLG4rMV0seWxhYj0iIix4bGFiPSJJdGVyYXRpb24iLG1haW49IlYiKQphY2YoZHJhd3MyWyxuKzFdLG1haW49IiIpCmhpc3QoZHJhd3MyWyxuKzFdLHByb2I9VFJVRSxtYWluPSIiLHhsYWI9IiIpCnRzLnBsb3QoZHJhd3MyWyxuKzJdLHlsYWI9IiIseGxhYj0iSXRlcmF0aW9uIixtYWluPSJXIikKYWNmKGRyYXdzMlssbisyXSxtYWluPSIiKQpoaXN0KGRyYXdzMlssbisyXSxwcm9iPVRSVUUsbWFpbj0iIix4bGFiPSIiKQpgYGAKCiMjIyBNQ01DIGF1dG9jb3JyZWxhdGlvbiBmb3Igc3RhdGVzCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NX0KcGFyKG1mcm93PWMoMSwxKSxtYXI9Yyg1LCA0LCA0LCAyKSkKdHMucGxvdCh0KGFjZnNbMiwsXSkseGxhYj0iTGFnIix5bGFiPSJBQ0YiLG1haW49IiIseWxpbT1yYW5nZShhY2ZzKSkKYGBgCgojIyMgUG9zdGVyaW9yIHF1YW50aWxlcyBmb3Igc3RhdGVzCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NX0KcGFyKG1mcm93PWMoMSwxKSkKdHMucGxvdChxMixjb2w9YygzLDIsMykseWxpbT1yYW5nZSh5LHEyKSx5bGFiPSJTdGF0ZXMiKQpwb2ludHMoeSkKdGl0bGUoIjV0aCwgNTB0aCBhbmQgOTV0aCBwZXJjZW50aWxlcyIpCmBgYAoKIyBDb21wYXJpbmcgTUNNQyBzY2hlbWVzCgojIyBDb21wYXJpbmcgQUNGcwoKYGBge3IgZmlnLmFsaWduPSdjZW50ZXInLCBmaWcud2lkdGg9NywgZmlnLmhlaWdodD01fQpwYXIobWZyb3c9YygxLDEpLG1hcj1jKDUsIDQsIDQsIDIpKQp0cy5wbG90KHQoYWNmc1syLCxdKSx4bGFiPSJMYWciLHlsYWI9IkFDRiIsbWFpbj0iIix5bGltPXJhbmdlKGFjZnMpKQpmb3IgKGkgaW4gMTpuKQogIGxpbmVzKGFjZnNbMSxpLF0sY29sPTIpCmxlZ2VuZCgidG9wcmlnaHQiLGxlZ2VuZD1jKCJTaW5nbGUtbW92ZSIsIkJsb2NrLW1vdmUiKSxjb2w9MToyLGx3ZD0yLGJ0eT0ibiIpCmBgYAoKIyMgQ29tcGFyaW5nIHN0YXRlIHBvc3RlcmlvciBxdWFudGlsZXMKCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTYsIGZpZy5oZWlnaHQ9NX0KcGFyKG1mcm93PWMoMSwxKSxtYXI9Yyg1LCA0LCA0LCAyKSkKdHMucGxvdChxMikKbGluZXMocTFbLDFdLGNvbD0yKQpsaW5lcyhxMVssMl0sY29sPTIpCmxpbmVzKHExWywzXSxjb2w9MikKbGVnZW5kKCJ0b3AiLGxlZ2VuZD1jKCJTaW5nbGUtbW92ZSIsIkJsb2NrLW1vdmUiKSxjb2w9MToyLGx3ZD0yLGJ0eT0ibiIpCmBgYAoKIyMgQ29tcGFyaW5nIG1hcmdpbmFsIHBvc3RlcmlvciBmb3IgJFYkIGFuZCAkVyQKCmBgYHtyIGZpZy5hbGlnbj0nY2VudGVyJywgZmlnLndpZHRoPTcsIGZpZy5oZWlnaHQ9NH0KcGFyKG1mcm93PWMoMSwyKSxtYXIgPSBjKDIsIDIsIDEsIDEpKQpwbG90KGRlbnNpdHkoZHJhd3MyWyxuKzFdKSxtYWluPSJWIikKbGluZXMoZGVuc2l0eShkcmF3czFbLG4rMV0pLGNvbD0yKQpwbG90KGRlbnNpdHkoZHJhd3MyWyxuKzJdKSxtYWluPSJXIikKbGluZXMoZGVuc2l0eShkcmF3czFbLG4rMl0pLGNvbD0yKQpsZWdlbmQoInRvcHJpZ2h0IixsZWdlbmQ9YygiU2luZ2xlLW1vdmUiLCJCbG9jay1tb3ZlIiksY29sPTE6Mixsd2Q9MixidHk9Im4iKQpgYGAKCgojIFJlcGxpY2F0aW9ucwoKV2UgcmVwbGljYXRlIHRoZSBhYm92ZSBleGVyY2lzZSBmb3IgMjAgc2V0cyBvZiB0aW1lIHNlcmllcyBkYXRhIGFuZCBydW4gYm90aCBNQ01DIHNjaGVtZXMuICBXZSBhc3N1bWUgdGhhdCBmb3IgJG49MTAwJCBhbmQgJFcgXGluIFx7MC4wMSwwLjI1LDEuMFx9LgoKYGBge3J9CnNldC5zZWVkKDY1NDMyMTIzKQpucmVwICA9IDIwCm4gICAgID0gMTAwClYgICAgID0gMS4wCk0wICAgID0gMTAwMApNICAgICA9IDEwMDAKYmV0YTAgPSAwLjAKVjAgICAgPSAxLjAKVzAgICAgPSAwLjUKYTEgICAgPSAwLjAKUjEgICAgPSAxMC4wCmF2ICAgID0gMi4wMQpidiAgICA9IDEuMDEKYXcgICAgPSAyLjAxCm0wICAgID0gMC4wCndzICAgID0gYygwLjAxLDAuMjUsMS4wKQpud3MgICA9IGxlbmd0aCh3cykKbmVmZnMgPSBhcnJheSgwLGMobnJlcCxud3MsMikpCmZvciAocmVwIGluIDE6bnJlcCl7CiAgZm9yIChpIGluIDE6Myl7CiAgICB5ICAgICAgPSBzYW1wbGUuZGF0YShuLHdzW2ldLFYsYmV0YTApCiAgICBiZXRhMHYgPSB5CiAgICBDMCAgICAgPSAxMC1XCiAgICBidyAgICAgPSAxLjAxKlcgICAgCiAgICBkcmF3czEgPSBtY21jLmpvaW50KHksYTEsUjEsYXYsYnYsYXcsYncsTTAsTSxWMCxXMCkKICAgIGFjZjEgID0gYWNmKGRyYXdzMVssbisxXS9kcmF3czFbLG4rMl0sbGFnLm1heD0zMSxwbG90PUZBTFNFKQogICAgbmVmZjEgPSBNLygxKzIqc3VtKGFjZjEkYWNmWzI6MzFdKSkKICAgIGRyYXdzMiA9IG1jbWMuc2luZ2xlKHksYTEsUjEsYXYsYnYsYXcsYncsTTAsTSxiZXRhMHYsVjAsVzApCiAgICBhY2YyICA9IGFjZihkcmF3czJbLG4rMV0vZHJhd3MyWyxuKzJdLGxhZy5tYXg9MzEscGxvdD1GQUxTRSkKICAgIG5lZmYyID0gTS8oMSsyKnN1bShhY2YyJGFjZlsyOjMxXSkpCiAgICBuZWZmc1tyZXAsaSxdID0gdHJ1bmMoYyhuZWZmMSxuZWZmMikpCiAgfQp9CmBgYAoKIyMgRWZmZWN0aXZlIHNhbXBsZSBzaXplIChFU1MpCgpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTV9CmNvbWIgPSBjKCIoMTAwLDAuMDEpIiwiKDEwMCwwLjI1KSIsIigxMDAsMSkiKQpwYXIobWZyb3c9YygxLDEpLG1hcj1jKDUsIDQsIDQsIDIpKQpib3hwbG90KGNiaW5kKG5lZmZzWywsMV0sbmVmZnNbLCwyXSksb3V0bGluZT1GQUxTRSxuYW1lcz1jKGNvbWIsY29tYiksCiAgICAgICAgeGxhYj1wYXN0ZSgiKG4sVykgZm9yIFY9IixWLHNlcD0iIikpCmFibGluZSh2PTMuNSkKbGVnZW5kKCJ0b3BsZWZ0IixsZWdlbmQ9YygiQmxvY2stbW92ZSIpLGJ0eT0ibiIpCmxlZ2VuZCgidG9wcmlnaHQiLGxlZ2VuZD1jKCJTaW5nbGUtbW92ZSIpLGJ0eT0ibiIpCnRpdGxlKHBhc3RlKCJNQ01DIHBvc3RlcmlvciBkcmF3cyA9ICIsTSxzZXA9IiIpKQpgYGAKCiMjIFJlbGF0aXZlIEVTUwpgYGB7ciBmaWcuYWxpZ249J2NlbnRlcicsIGZpZy53aWR0aD03LCBmaWcuaGVpZ2h0PTV9CnBhcihtZnJvdz1jKDEsMSksbWFyPWMoNSwgNCwgNCwgMikpCmJveHBsb3QobmVmZnNbLCwyXS9uZWZmc1ssLDFdLG91dGxpbmU9RkFMU0UsbmFtZXM9Y29tYix5bGFiPSJSZWxhdGl2ZSBFU1MiLAogICAgICAgIHhsYWI9cGFzdGUoIihuLFcpIGZvciBWPSIsVixzZXA9IiIpKQphYmxpbmUoaD0xLGx0eT0yKQp0aXRsZSgiUmF0aW8gc2luZ2xlLW1vdmUgdG8gYmxvY2stbW92ZSIpCmBgYA==