\[ \DeclareMathOperator{\Normal}{Normal} \DeclareMathOperator{\Uniform}{Uniform} \DeclareMathOperator{\Binomial}{Binomial} \]

The goal of this note is to give some intuition for how a normal approximation for a Bayesian (joint) posterior could be computed numerically. We’ll use the normal approximation generated by the map function from the rethinking package as a baseline to compare against.

library(rethinking)    # https://github.com/rmcelreath/rethinking

1 Estimating a binomial probability

We’ll start by considering the simple problem of estimating a binomial probability \(p\).

Suppose that out of 9 trials we observed 6 successes, and that our prior for \(p\) is

\[ p \sim \Normal(0.25, 0.5). \]

1.1 Analytical solution

The binomial likelihood of seeing 6 successes in 9 trials is \(\Binomial(6 \mid 9,p)\), so by Bayes’ theorem our posterior for \(p\) is

\[ f(p) \propto \Binomial(6 \mid 9,p) \Normal(p \mid 0.25,0.5), \]

and numerical integration shows that the constant of proportionality is about 1/0.0581627. Here’s a plot of this posterior:

curve(
    dbinom(6, 9, x)*dnorm(x, 0.25, 0.5)/0.0581627,
    xlim = c(0,1), xlab = "p", ylab = "posterior"
)

1.2 Posterior estimate from map

Let’s use rethinking::map to get a normal approximation for the posterior of the binomial parameter.

m1 <- map(
    alist(
        k ~ dbinom(n, p),
        p ~ dnorm(0.25, 0.5)
    ),
    data = list(n = 9, k = 6),
    start = list(p = 0.5)
)
precis(m1)

Here’s a plot of the normal approximation from map, shown as a dashed curve.

# plot the exact posterior
curve(
    dbinom(6, 9, x)*dnorm(x, 0.25, 0.5)/0.0581627,
    xlim = c(0,1), xlab = "p", ylab = "posterior"
)
# plot the normal approximation for the posterior from map()
curve(dnorm(x, coef(m1)["p"], precis(m1)@output$StdDev), lty = 2, add = TRUE)

1.3 Manually obtaining the normal approximation

There are two parts to finding the normal approximation. First we find the maximum of the posterior (which becomes the mean of the normal), then we estimate the curvature at that point (which tells us about the standard deviation of the normal).

There are many ways to tackle the first step. I think map uses some kind of adaptive hill-climbing, where the algorithm takes steps toward a peak of the posterior \(f(p)\) along paths of steepest ascent, adjusting its step size depending on the steepness at its current position. But we’re not going to get that fancy. Instead we’ll just compute the posterior on a grid, then perform a brute force search for the highest grid point.

(You might say to yourself: “If you’re computing the posterior on a grid, do you really need the normal approximation?” No, we wouldn’t need the normal approximation. Computing the posterior on a grid gives us much more information since it’s a more accurate picture of the posterior than the normal approximation. However, our goal is to illustrate the basic concepts of the normal approximation, and using a grid is the simplest approach.)

The second step involves some light calculus.

Suppose we’ve already found the location \(p_0\) of the maximum of the posterior. We’ll use this \(p_0\) as the mean of our normal approximation. Then we just need to find the standard deviation \(\sigma\) which makes

\[ f(p) \approx \frac{1}{\sigma\sqrt{2\pi}} \exp\!\left[-\frac{(p-p_0)^2}{2\sigma^2}\right] \]

when \(p\) is near \(p_0\). Taking the logarithm of both sides, we have

\[ \log f(p) \approx -\frac{(p-p_0)^2}{2\sigma^2} + \text{const}. \tag{$*$} \]

This is what’s meant by “quadratic approximation”: our goal is to approximate \(\log f(p)\) near its maximum by a quadratic polynomial of the form \(-(p-\mu)^2/(2\sigma^2) + \text{const}\).

Well since \(f(p)\) (and hence \(\log f(p)\)) has a maximum at \(p = p_0\), the linear term drops out of the Taylor series for \(\log f(p)\) centered at \(p = p_0\), and the most significant terms are the constant term and the quadratic term. In other words, if we define \(g(p) = \log f(p)\), then

\[ \log f(p) \approx \frac{g''(p_0)}{2} (p - p_0)^2 + \text{const} \]

when \(p\) is near \(p_0\). Plugging this into \((*)\), ignoring the constant terms, and solving for \(\sigma\), we see that we should choose

\[ \sigma = \sqrt{- \frac{1}{g''(p_0)}}. \]

That is to say, for our normal approximation we should choose the standard deviation to be the square root of the negative reciprocal of the second derivative of \(\log f(p)\) at its maximum.

We will also compute this second derivative from the grid of points we use to estimate \(p_0\) in the first step.

1.3.1 First step: finding the maximum

Recall that the posterior is proportional to \(\Binomial(6 \mid 9,p) \Normal(p \mid 0.25,0.5)\). We’ll evaluate the logarithm of this,

\[ g(p) = \log f(p) = \log \Binomial(6 \mid 9,p) + \log \Normal(p \mid 0.25,0.5) + \text{const}, \]

at a grid of points, then find which point on the grid corresponds to the maximum.

# the grid
gridsize <- 1000
p.grid <- seq(from = 0, to = 1, length.out = gridsize)
# evaluate the posterior at the grid points
g.grid <- sapply(p.grid, function(p) dbinom(6, 9, p, log = TRUE) + dnorm(p, 0.25, 0.5, log = TRUE))
# find the point on the grid which maximizes the posterior
index_of_max <- which.max(g.grid)
( p0 <- p.grid[index_of_max] )
[1] 0.6276276

1.3.2 Second step: computing the second derivative

The variable g.grid now contains a sequence of values of the log-posterior: \(g_1, g_2, \ldots, g_{1000}\). Using these values, we will use the discrete second derivative to approximate the true value of \(g''(p_0)\).

The discrete second derivative is defined by

\[ g''(p_i) \approx \frac{g_{i-1} - 2g_i + g_{i+1}}{\Delta p^2}, \]

where \(\Delta p\) is the distance between the grid points \(p_i\). In code:

( gpp <- (g.grid[index_of_max - 1] - 2*g.grid[index_of_max] + g.grid[index_of_max + 1])*(gridsize-1)^2 )
[1] -40.86723

(Note that, since the points of p.grid range from 0 to 1, the distance between them is 1/(gridsize-1). So we’ve replaced “dividing by \(\Delta p^2\)” in the above formula with “multiplying by (gridsize-1)^2”.)

As a sanity check, it makes sense that \(g''(p_0)\) is negative—we’re at a local max, after all. Finally we use this value for \(\sigma\):

( sigma <- sqrt(-1/gpp) )
[1] 0.1564273

What if we didn’t have the full grid to work with?

If we hadn’t computed the posterior on a grid beforehand, we could always choose an appropriately small step size and compute just the three grid points we need in the formula for the discrete second derivative.

1.3.3 Results

Here’s a plot of the normal approximation we’ve computed:

# plot the exact posterior
curve(
    dbinom(6, 9, x)*dnorm(x, 0.25, 0.5)/0.0581627,
    xlim = c(0,1), xlab = "p", ylab = "posterior"
)
# plot our normal approximation for the posterior
curve(dnorm(x, p0, sigma), lty = 2, col = rangi2, add = TRUE)

And here’s a table comparing the estimates of the mean and standard deviation from map with our estimates:

rbind(
    as.data.frame(precis(m1)@output[1:2], row.names = "map's"),
    data.frame(Mean = p0, StdDev = sigma, row.names = "ours")
)

The only things we needed to calculate this mean and this standard deviation were the likelihood and the prior. We didn’t even need to know the constant of proportionality we’d need to multiply them by to get the exact posterior.

2 Estimating the mean and standard deviation for a normal likelihood

Now we consider a problem where we’ll estimate two parameters instead of one. This means that we’ll be finding a normal approximation for their joint posterior.

Suppose that we have data containing 20 observations which we know to be normally distributed with some mean and variance. Our prior for the mean \(\mu\) is

\[ \mu \sim \Normal(0,5) \]

and our prior for the standard deviation \(\sigma\) is

\[ \sigma \sim \Uniform(0,2). \]

Here’s our data:

true_mu <- 2
true_sd <- 1
set.seed(1)
d <- rnorm(20, true_mu, true_sd)

2.1 Analytical solution

The joint posterior for \(\mu\) and \(\sigma\) is

\[ f(\mu,\sigma) \propto \Normal(\mu \mid 0,5) \Uniform(\sigma \mid 0,2) \prod_{i=1}^{20} \Normal(d_i \mid \mu,\sigma), \]

where \(d_1, d_2, \ldots, d_{20}\) are our 20 data points.

We’ll skip finding the constant of proportionality and plotting the posterior and move straight to the estimated means, standard deviations, and correlation for the parameters \(\mu\) and \(\sigma\).

2.2 Posterior estimates from map

We’ll use map to find normal approximations to the marginal posteriors for \(\mu\) and \(\sigma\).

m2 <- map(
    alist(
        x ~ dnorm(mu, sigma),
        mu ~ dnorm(0, 5),
        sigma ~ dunif(0,2)
    ),
    data = list(x = d)
)
precis(m2, corr = TRUE)

Note that the model estimates mu and sigma to have a very small negative correlation of -0.005502.

2.3 Manually obtaining the normal approximation

In this case we’re finding the normal approximation to the joint posterior for \(\mu\) and \(\sigma\). Finding the maximum of the posterior will basically be just as easy as before, but to estimate the curvature near this maximum we’ll need to use some multivariable calculus.

Suppose we’ve already found the location \((\mu_0, \sigma_0)\) of the maximum of the posterior. We need to find the covariance matrix \(\mathbf \Sigma\) which makes

\[ f(\mathbf m) \approx \frac{1}{2\pi \sqrt{\lvert \Sigma \rvert}} \exp\!\left[ -\frac{1}{2} (\mathbf m - \mathbf m_0)^{\text{T}} \mathbf\Sigma^{-1} (\mathbf m - \mathbf m_0) \right] \]

when \(\mathbf m\) is near \(\mathbf m_0\), where

\[ \mathbf m = \begin{pmatrix} \mu \\ \sigma \end{pmatrix} \qquad \text{and} \qquad \mathbf m_0 = \begin{pmatrix} \mu_0 \\ \sigma_0 \end{pmatrix}. \]

Taking logarithms, we want

\[ \log f(\mathbf m) \approx -\frac{1}{2} (\mathbf m - \mathbf m_0)^{\text{T}} \mathbf\Sigma^{-1} (\mathbf m - \mathbf m_0) + \text{const}. \]

Just as before, since \(f(\mathbf m)\) (and hence \(\log f(\mathbf m)\)) has a maximum at \(\mathbf m = \mathbf m_0\), the linear terms drop out of the Taylor series for \(\log f(\mathbf m)\), and we’re left with the approximation

\[ \log f(\mathbf m) \approx \frac{1}{2} (\mathbf m - \mathbf m_0)^{\text{T}} \mathbf H(\mathbf m_0) (\mathbf m - \mathbf m_0) + \text{const}, \]

where \(\mathbf H(\mathbf m_0)\) is the Hessian matrix for \(g(\mathbf m) = \log f(\mathbf m)\) evaluated at \(\mathbf m = \mathbf m_0\). (This Hessian matrix is just a matrix of the second derivatives of \(\log f(\mathbf m)\).) Comparing this formula to the one above, we see that we should take

\[ \mathbf \Sigma = -\mathbf H(\mathbf m_0)^{-1}. \]

Compare this with our choice for \(\sigma^2\) in section 1.3.

2.3.1 Finding the maximum

There’s probably a cleaner way to do this, but we’ll use two applications of which.max and max to find the indices of the maximum of the joint posterior.

# the grid
mu.gridsize <- 5000
mu.max <- 5
sigma.gridsize <- 1000
mu.grid <- seq(from = -mu.max, to = mu.max, length.out = mu.gridsize)
sigma.grid <- seq(from = 0, to = 2, length.out = sigma.gridsize)
g.grid <- outer(
    mu.grid, sigma.grid,
    function(m,s) {
        log_post <- dnorm(m, 0, 5, log = TRUE) + dunif(s, 0, 2, log = TRUE)
        for (i in 1:length(d))
            log_post <- log_post + dnorm(d[i], m, s, log = TRUE)
        return(log_post)
    }
)
# find the entries with the maximum of the posterior
rom <- which.max(apply(g.grid, 1, max))    # rom = Row Of Max
com <- which.max(apply(g.grid, 2, max))    # com = Col Of Max
# find corresponding mu and sigma
( mu0 <- mu.grid[rom] )
[1] 2.187437
( sigma0 <- sigma.grid[com] )
[1] 0.8908909

There’s an awkward for loop in the above code that you might have noticed. Originally I wrote the call to outer like

log_posterior <- function(mu, sigma)
    dnorm(mu, 0, 5, log = TRUE) + dunif(sigma, 0, 2, log = TRUE) + sum(dnorm(d, mu, sigma, log = TRUE))
v_log_posterior <- Vectorize(log_posterior, c("mu", "sigma"))
g.grid <- outer(mu.grid, sigma.grid, v_log_posterior)

…but this ended up being extremely slow. The code above with the for loop ended up being much faster.

2.3.2 Computing the Hessian

The variable g.grid now contains a 2d array of values of the log posterior.

Whereas in the one-dimensional case there was only a single second derivative to worry about, now there are four: \(g_{xx}(\mathbf m)\), \(g_{xy}(\mathbf m)\), \(g_{yx}(\mathbf m)\), and \(g_{yy}(\mathbf m)\). But thankfully \(g_{xy} = g_{yx}\) if \(g\) is nice enough (it is), so really there are only three to worry about.

We still compute them using discrete differences:

\[ g_{xx}(\mu_i, \sigma_j) \approx \frac{g_{i-1,j} - 2 g_{i,j} + g_{i+1,j}}{\Delta\mu^2}, \\ g_{yy}(\mu_i, \sigma_j) \approx \frac{g_{i,j-1} - 2 g_{i,j} + g_{i,j+1}}{\Delta\sigma^2}, \\ g_{xy}(\mu_i, \sigma_j) \approx \frac{g_{i-1,j-1} - g_{i-1,j+1} - g_{i+1,j-1} + g_{i+1,j+1}}{4\Delta \sigma \Delta \mu}. \]

Then our Hessian is

\[ \mathbf H(\mathbf m_0) = \begin{pmatrix} g_{xx}(\mathbf m_0) & g_{xy}(\mathbf m_0) \\ g_{yx}(\mathbf m_0) & g_{yy}(\mathbf m_0) \end{pmatrix}. \]

Here’s the code:

# compute the Hessian
g_xx <- (g.grid[rom-1, com] - 2*g.grid[rom, com] + g.grid[rom+1, com])*((mu.gridsize-1)/(2*mu.max))^2
g_yy <- (g.grid[rom, com-1] - 2*g.grid[rom, com] + g.grid[rom, com+1])*((sigma.gridsize-1)/2)^2
g_xy <- (g.grid[rom-1, com-1] - g.grid[rom-1, com+1] - g.grid[rom+1, com-1] +
             g.grid[rom+1, com+1])*((mu.gridsize-1)*(sigma.gridsize-1)/(16*mu.max))
( hess <- matrix(c(g_xx, g_xy, g_xy, g_yy), ncol = 2) )
            [,1]        [,2]
[1,] -25.2388638  -0.1745989
[2,]  -0.1745989 -50.2700662
# compute covariance matrix
( big_sigma <- -solve(hess) )
              [,1]          [,2]
[1,]  0.0396223870 -0.0001376172
[2,] -0.0001376172  0.0198930319

2.3.3 Results

We can read off the marginal standard deviations from the covariance matrix:

# standard deviation for mu posterior
( mu_sd <- sqrt(big_sigma[1,1]) )
[1] 0.1990537
# standard deviation for sigma posterior
( sigma_sd <- sqrt(big_sigma[2,2]) )
[1] 0.1410427

And note that we picked up the same slight correlation between \(\mu\) and \(\sigma\) that map did:

# estimated correlation between mu and sigma
big_sigma[1,2]/(mu_sd*sigma_sd)
[1] -0.004901757

Here’s a summary comparing all estimates from map with our estimates:

data.frame(
    mu_Mean = c(coef(m2)["mu"], mu0),
    mu_StdDev = c(precis(m2)@output[1,2], mu_sd),
    sigma_Mean = c(coef(m2)["sigma"], sigma0),
    sigma_StdDev = c(precis(m2)@output[2,2], sigma_sd),
    corr = c(precis(m2, corr=TRUE)@output[1,6], big_sigma[1,2]/(mu_sd*sigma_sd)),
    row.names = c("map's", "ours")
)

3 More parameters

This brute force approach to finding the maximum of the joint posterior can only take us so far. As the dimensionality of g.grid increases with more parameters, just constructing g.grid would take ages. A more intelligent approach, like picking a starting point and only computing the values of \(g\) necessary to forge a path of steepest ascent, would be much desired. Based on what McElreath hinted at in the book, I think this is something like what map does.

I wanted to keep things simple in this note, and the brute force approach is as simple as it gets.


Antonio R. Vargas

1 Oct. 2018

Updated 6 Oct. 2018.

LS0tDQp0aXRsZTogIkV4cGxvcmluZyBudW1lcmljYWwgbm9ybWFsIHBvc3RlcmlvciBhcHByb3hpbWF0aW9uIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogVFJVRQ0KLS0tDQoNCiQkDQpcRGVjbGFyZU1hdGhPcGVyYXRvcntcTm9ybWFsfXtOb3JtYWx9DQpcRGVjbGFyZU1hdGhPcGVyYXRvcntcVW5pZm9ybX17VW5pZm9ybX0NClxEZWNsYXJlTWF0aE9wZXJhdG9ye1xCaW5vbWlhbH17Qmlub21pYWx9DQokJA0KDQpUaGUgZ29hbCBvZiB0aGlzIG5vdGUgaXMgdG8gZ2l2ZSBzb21lIGludHVpdGlvbiBmb3IgaG93IGEgbm9ybWFsIGFwcHJveGltYXRpb24gZm9yIGEgQmF5ZXNpYW4gKGpvaW50KSBwb3N0ZXJpb3IgY291bGQgYmUgY29tcHV0ZWQgbnVtZXJpY2FsbHkuIFdlJ2xsIHVzZSB0aGUgbm9ybWFsIGFwcHJveGltYXRpb24gZ2VuZXJhdGVkIGJ5IHRoZSBgbWFwYCBmdW5jdGlvbiBmcm9tIHRoZSBgcmV0aGlua2luZ2AgcGFja2FnZSBhcyBhIGJhc2VsaW5lIHRvIGNvbXBhcmUgYWdhaW5zdC4NCg0KYGBge3J9DQpsaWJyYXJ5KHJldGhpbmtpbmcpICAgICMgaHR0cHM6Ly9naXRodWIuY29tL3JtY2VscmVhdGgvcmV0aGlua2luZw0KYGBgDQoNCiMgMSBFc3RpbWF0aW5nIGEgYmlub21pYWwgcHJvYmFiaWxpdHkNCg0KV2UnbGwgc3RhcnQgYnkgY29uc2lkZXJpbmcgdGhlIHNpbXBsZSBwcm9ibGVtIG9mIGVzdGltYXRpbmcgYSBiaW5vbWlhbCBwcm9iYWJpbGl0eSAkcCQuDQoNClN1cHBvc2UgdGhhdCBvdXQgb2YgOSB0cmlhbHMgd2Ugb2JzZXJ2ZWQgNiBzdWNjZXNzZXMsIGFuZCB0aGF0IG91ciBwcmlvciBmb3IgJHAkIGlzDQoNCiQkDQpwIFxzaW0gXE5vcm1hbCgwLjI1LCAwLjUpLg0KJCQNCg0KIyMgMS4xIEFuYWx5dGljYWwgc29sdXRpb24NCg0KVGhlIGJpbm9taWFsIGxpa2VsaWhvb2Qgb2Ygc2VlaW5nIDYgc3VjY2Vzc2VzIGluIDkgdHJpYWxzIGlzICRcQmlub21pYWwoNiBcbWlkIDkscCkkLCBzbyBieSBCYXllcycgdGhlb3JlbSBvdXIgcG9zdGVyaW9yIGZvciAkcCQgaXMNCg0KJCQNCmYocCkgXHByb3B0byBcQmlub21pYWwoNiBcbWlkIDkscCkgXE5vcm1hbChwIFxtaWQgMC4yNSwwLjUpLA0KJCQNCg0KYW5kIG51bWVyaWNhbCBpbnRlZ3JhdGlvbiBzaG93cyB0aGF0IHRoZSBjb25zdGFudCBvZiBwcm9wb3J0aW9uYWxpdHkgaXMgYWJvdXQgMS8wLjA1ODE2MjcuIEhlcmUncyBhIHBsb3Qgb2YgdGhpcyBwb3N0ZXJpb3I6DQoNCmBgYHtyfQ0KY3VydmUoDQogICAgZGJpbm9tKDYsIDksIHgpKmRub3JtKHgsIDAuMjUsIDAuNSkvMC4wNTgxNjI3LA0KICAgIHhsaW0gPSBjKDAsMSksIHhsYWIgPSAicCIsIHlsYWIgPSAicG9zdGVyaW9yIg0KKQ0KYGBgDQoNCiMjIDEuMiBQb3N0ZXJpb3IgZXN0aW1hdGUgZnJvbSBgbWFwYA0KDQpMZXQncyB1c2UgYHJldGhpbmtpbmc6Om1hcGAgdG8gZ2V0IGEgbm9ybWFsIGFwcHJveGltYXRpb24gZm9yIHRoZSBwb3N0ZXJpb3Igb2YgdGhlIGJpbm9taWFsIHBhcmFtZXRlci4NCg0KYGBge3J9DQptMSA8LSBtYXAoDQogICAgYWxpc3QoDQogICAgICAgIGsgfiBkYmlub20obiwgcCksDQogICAgICAgIHAgfiBkbm9ybSgwLjI1LCAwLjUpDQogICAgKSwNCiAgICBkYXRhID0gbGlzdChuID0gOSwgayA9IDYpLA0KICAgIHN0YXJ0ID0gbGlzdChwID0gMC41KQ0KKQ0KcHJlY2lzKG0xKQ0KYGBgDQoNCkhlcmUncyBhIHBsb3Qgb2YgdGhlIG5vcm1hbCBhcHByb3hpbWF0aW9uIGZyb20gYG1hcGAsIHNob3duIGFzIGEgZGFzaGVkIGN1cnZlLg0KDQpgYGB7cn0NCiMgcGxvdCB0aGUgZXhhY3QgcG9zdGVyaW9yDQpjdXJ2ZSgNCiAgICBkYmlub20oNiwgOSwgeCkqZG5vcm0oeCwgMC4yNSwgMC41KS8wLjA1ODE2MjcsDQogICAgeGxpbSA9IGMoMCwxKSwgeGxhYiA9ICJwIiwgeWxhYiA9ICJwb3N0ZXJpb3IiDQopDQojIHBsb3QgdGhlIG5vcm1hbCBhcHByb3hpbWF0aW9uIGZvciB0aGUgcG9zdGVyaW9yIGZyb20gbWFwKCkNCmN1cnZlKGRub3JtKHgsIGNvZWYobTEpWyJwIl0sIHByZWNpcyhtMSlAb3V0cHV0JFN0ZERldiksIGx0eSA9IDIsIGFkZCA9IFRSVUUpDQpgYGANCg0KIyMgMS4zIE1hbnVhbGx5IG9idGFpbmluZyB0aGUgbm9ybWFsIGFwcHJveGltYXRpb24geyNzMTN9DQoNClRoZXJlIGFyZSB0d28gcGFydHMgdG8gZmluZGluZyB0aGUgbm9ybWFsIGFwcHJveGltYXRpb24uIEZpcnN0IHdlIGZpbmQgdGhlIG1heGltdW0gb2YgdGhlIHBvc3RlcmlvciAod2hpY2ggYmVjb21lcyB0aGUgbWVhbiBvZiB0aGUgbm9ybWFsKSwgdGhlbiB3ZSBlc3RpbWF0ZSB0aGUgY3VydmF0dXJlIGF0IHRoYXQgcG9pbnQgKHdoaWNoIHRlbGxzIHVzIGFib3V0IHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gb2YgdGhlIG5vcm1hbCkuDQoNClRoZXJlIGFyZSBtYW55IHdheXMgdG8gdGFja2xlIHRoZSBmaXJzdCBzdGVwLiBJIHRoaW5rIGBtYXBgIHVzZXMgc29tZSBraW5kIG9mIGFkYXB0aXZlIGhpbGwtY2xpbWJpbmcsIHdoZXJlIHRoZSBhbGdvcml0aG0gdGFrZXMgc3RlcHMgdG93YXJkIGEgcGVhayBvZiB0aGUgcG9zdGVyaW9yICRmKHApJCBhbG9uZyBwYXRocyBvZiBzdGVlcGVzdCBhc2NlbnQsIGFkanVzdGluZyBpdHMgc3RlcCBzaXplIGRlcGVuZGluZyBvbiB0aGUgc3RlZXBuZXNzIGF0IGl0cyBjdXJyZW50IHBvc2l0aW9uLiBCdXQgd2UncmUgbm90IGdvaW5nIHRvIGdldCB0aGF0IGZhbmN5LiBJbnN0ZWFkIHdlJ2xsIGp1c3QgY29tcHV0ZSB0aGUgcG9zdGVyaW9yIG9uIGEgZ3JpZCwgdGhlbiBwZXJmb3JtIGEgYnJ1dGUgZm9yY2Ugc2VhcmNoIGZvciB0aGUgaGlnaGVzdCBncmlkIHBvaW50Lg0KDQoqKigqKllvdSBtaWdodCBzYXkgdG8geW91cnNlbGY6ICJJZiB5b3UncmUgY29tcHV0aW5nIHRoZSBwb3N0ZXJpb3Igb24gYSBncmlkLCBkbyB5b3UgcmVhbGx5IG5lZWQgdGhlIG5vcm1hbCBhcHByb3hpbWF0aW9uPyIgTm8sIHdlIHdvdWxkbid0IG5lZWQgdGhlIG5vcm1hbCBhcHByb3hpbWF0aW9uLiBDb21wdXRpbmcgdGhlIHBvc3RlcmlvciBvbiBhIGdyaWQgZ2l2ZXMgdXMgbXVjaCBtb3JlIGluZm9ybWF0aW9uIHNpbmNlIGl0J3MgYSBtb3JlIGFjY3VyYXRlIHBpY3R1cmUgb2YgdGhlIHBvc3RlcmlvciB0aGFuIHRoZSBub3JtYWwgYXBwcm94aW1hdGlvbi4gSG93ZXZlciwgb3VyIGdvYWwgaXMgdG8gaWxsdXN0cmF0ZSB0aGUgYmFzaWMgY29uY2VwdHMgb2YgdGhlIG5vcm1hbCBhcHByb3hpbWF0aW9uLCBhbmQgdXNpbmcgYSBncmlkIGlzIHRoZSBzaW1wbGVzdCBhcHByb2FjaC4qKikqKg0KDQpUaGUgc2Vjb25kIHN0ZXAgaW52b2x2ZXMgc29tZSBsaWdodCBjYWxjdWx1cy4NCg0KU3VwcG9zZSB3ZSd2ZSBhbHJlYWR5IGZvdW5kIHRoZSBsb2NhdGlvbiAkcF8wJCBvZiB0aGUgbWF4aW11bSBvZiB0aGUgcG9zdGVyaW9yLiBXZSdsbCB1c2UgdGhpcyAkcF8wJCBhcyB0aGUgbWVhbiBvZiBvdXIgbm9ybWFsIGFwcHJveGltYXRpb24uIFRoZW4gd2UganVzdCBuZWVkIHRvIGZpbmQgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiAkXHNpZ21hJCB3aGljaCBtYWtlcw0KDQokJA0KZihwKSBcYXBwcm94IFxmcmFjezF9e1xzaWdtYVxzcXJ0ezJccGl9fSBcZXhwXCFcbGVmdFstXGZyYWN7KHAtcF8wKV4yfXsyXHNpZ21hXjJ9XHJpZ2h0XQ0KJCQNCg0Kd2hlbiAkcCQgaXMgbmVhciAkcF8wJC4gVGFraW5nIHRoZSBsb2dhcml0aG0gb2YgYm90aCBzaWRlcywgd2UgaGF2ZQ0KDQokJA0KXGxvZyBmKHApIFxhcHByb3ggLVxmcmFjeyhwLXBfMCleMn17MlxzaWdtYV4yfSArIFx0ZXh0e2NvbnN0fS4gXHRhZ3skKiR9DQokJA0KDQpUaGlzIGlzIHdoYXQncyBtZWFudCBieSAicXVhZHJhdGljIGFwcHJveGltYXRpb24iOiBvdXIgZ29hbCBpcyB0byBhcHByb3hpbWF0ZSAkXGxvZyBmKHApJCBuZWFyIGl0cyBtYXhpbXVtIGJ5IGEgcXVhZHJhdGljIHBvbHlub21pYWwgb2YgdGhlIGZvcm0gJC0ocC1cbXUpXjIvKDJcc2lnbWFeMikgKyBcdGV4dHtjb25zdH0kLg0KDQpXZWxsIHNpbmNlICRmKHApJCAoYW5kIGhlbmNlICRcbG9nIGYocCkkKSBoYXMgYSBtYXhpbXVtIGF0ICRwID0gcF8wJCwgdGhlIGxpbmVhciB0ZXJtIGRyb3BzIG91dCBvZiB0aGUgVGF5bG9yIHNlcmllcyBmb3IgJFxsb2cgZihwKSQgY2VudGVyZWQgYXQgJHAgPSBwXzAkLCBhbmQgdGhlIG1vc3Qgc2lnbmlmaWNhbnQgdGVybXMgYXJlIHRoZSBjb25zdGFudCB0ZXJtIGFuZCB0aGUgcXVhZHJhdGljIHRlcm0uIEluIG90aGVyIHdvcmRzLCBpZiB3ZSBkZWZpbmUgJGcocCkgPSBcbG9nIGYocCkkLCB0aGVuDQoNCiQkDQpcbG9nIGYocCkgXGFwcHJveCBcZnJhY3tnJycocF8wKX17Mn0gKHAgLSBwXzApXjIgKyBcdGV4dHtjb25zdH0NCiQkDQoNCndoZW4gJHAkIGlzIG5lYXIgJHBfMCQuIFBsdWdnaW5nIHRoaXMgaW50byAkKCopJCwgaWdub3JpbmcgdGhlIGNvbnN0YW50IHRlcm1zLCBhbmQgc29sdmluZyBmb3IgJFxzaWdtYSQsIHdlIHNlZSB0aGF0IHdlIHNob3VsZCBjaG9vc2UNCg0KJCQNClxzaWdtYSA9IFxzcXJ0ey0gXGZyYWN7MX17ZycnKHBfMCl9fS4NCiQkDQoNClRoYXQgaXMgdG8gc2F5LCBmb3Igb3VyIG5vcm1hbCBhcHByb3hpbWF0aW9uIHdlIHNob3VsZCBjaG9vc2UgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiB0byBiZSB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIG5lZ2F0aXZlIHJlY2lwcm9jYWwgb2YgdGhlIHNlY29uZCBkZXJpdmF0aXZlIG9mICRcbG9nIGYocCkkIGF0IGl0cyBtYXhpbXVtLg0KDQpXZSB3aWxsIGFsc28gY29tcHV0ZSB0aGlzIHNlY29uZCBkZXJpdmF0aXZlIGZyb20gdGhlIGdyaWQgb2YgcG9pbnRzIHdlIHVzZSB0byBlc3RpbWF0ZSAkcF8wJCBpbiB0aGUgZmlyc3Qgc3RlcC4NCg0KIyMjIDEuMy4xIEZpcnN0IHN0ZXA6IGZpbmRpbmcgdGhlIG1heGltdW0NCg0KUmVjYWxsIHRoYXQgdGhlIHBvc3RlcmlvciBpcyBwcm9wb3J0aW9uYWwgdG8gJFxCaW5vbWlhbCg2IFxtaWQgOSxwKSBcTm9ybWFsKHAgXG1pZCAwLjI1LDAuNSkkLiBXZSdsbCBldmFsdWF0ZSB0aGUgKmxvZ2FyaXRobSogb2YgdGhpcywNCg0KJCQNCmcocCkgPSBcbG9nIGYocCkgPSBcbG9nIFxCaW5vbWlhbCg2IFxtaWQgOSxwKSArIFxsb2cgXE5vcm1hbChwIFxtaWQgMC4yNSwwLjUpICsgXHRleHR7Y29uc3R9LA0KJCQNCg0KYXQgYSBncmlkIG9mIHBvaW50cywgdGhlbiBmaW5kIHdoaWNoIHBvaW50IG9uIHRoZSBncmlkIGNvcnJlc3BvbmRzIHRvIHRoZSBtYXhpbXVtLg0KDQpgYGB7cn0NCiMgdGhlIGdyaWQNCmdyaWRzaXplIDwtIDEwMDANCnAuZ3JpZCA8LSBzZXEoZnJvbSA9IDAsIHRvID0gMSwgbGVuZ3RoLm91dCA9IGdyaWRzaXplKQ0KIyBldmFsdWF0ZSB0aGUgcG9zdGVyaW9yIGF0IHRoZSBncmlkIHBvaW50cw0KZy5ncmlkIDwtIHNhcHBseShwLmdyaWQsIGZ1bmN0aW9uKHApIGRiaW5vbSg2LCA5LCBwLCBsb2cgPSBUUlVFKSArIGRub3JtKHAsIDAuMjUsIDAuNSwgbG9nID0gVFJVRSkpDQojIGZpbmQgdGhlIHBvaW50IG9uIHRoZSBncmlkIHdoaWNoIG1heGltaXplcyB0aGUgcG9zdGVyaW9yDQppbmRleF9vZl9tYXggPC0gd2hpY2gubWF4KGcuZ3JpZCkNCiggcDAgPC0gcC5ncmlkW2luZGV4X29mX21heF0gKQ0KYGBgDQoNCiMjIyAxLjMuMiBTZWNvbmQgc3RlcDogY29tcHV0aW5nIHRoZSBzZWNvbmQgZGVyaXZhdGl2ZQ0KDQpUaGUgdmFyaWFibGUgYGcuZ3JpZGAgbm93IGNvbnRhaW5zIGEgc2VxdWVuY2Ugb2YgdmFsdWVzIG9mIHRoZSBsb2ctcG9zdGVyaW9yOiAkZ18xLCBnXzIsIFxsZG90cywgZ197MTAwMH0kLiBVc2luZyB0aGVzZSB2YWx1ZXMsIHdlIHdpbGwgdXNlIHRoZSAqZGlzY3JldGUgc2Vjb25kIGRlcml2YXRpdmUqIHRvIGFwcHJveGltYXRlIHRoZSB0cnVlIHZhbHVlIG9mICRnJycocF8wKSQuDQoNClRoZSBkaXNjcmV0ZSBzZWNvbmQgZGVyaXZhdGl2ZSBpcyBkZWZpbmVkIGJ5DQoNCiQkDQpnJycocF9pKSBcYXBwcm94IFxmcmFje2dfe2ktMX0gLSAyZ19pICsgZ197aSsxfX17XERlbHRhIHBeMn0sDQokJA0KDQp3aGVyZSAkXERlbHRhIHAkIGlzIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZSBncmlkIHBvaW50cyAkcF9pJC4gSW4gY29kZToNCg0KYGBge3J9DQooIGdwcCA8LSAoZy5ncmlkW2luZGV4X29mX21heCAtIDFdIC0gMipnLmdyaWRbaW5kZXhfb2ZfbWF4XSArIGcuZ3JpZFtpbmRleF9vZl9tYXggKyAxXSkqKGdyaWRzaXplLTEpXjIgKQ0KYGBgDQoNCihOb3RlIHRoYXQsIHNpbmNlIHRoZSBwb2ludHMgb2YgYHAuZ3JpZGAgcmFuZ2UgZnJvbSAwIHRvIDEsIHRoZSBkaXN0YW5jZSBiZXR3ZWVuIHRoZW0gaXMgYDEvKGdyaWRzaXplLTEpYC4gU28gd2UndmUgcmVwbGFjZWQgImRpdmlkaW5nIGJ5ICRcRGVsdGEgcF4yJCIgaW4gdGhlIGFib3ZlIGZvcm11bGEgd2l0aCAibXVsdGlwbHlpbmcgYnkgYChncmlkc2l6ZS0xKV4yYCIuKQ0KDQpBcyBhIHNhbml0eSBjaGVjaywgaXQgbWFrZXMgc2Vuc2UgdGhhdCAkZycnKHBfMCkkIGlzIG5lZ2F0aXZlLS0td2UncmUgYXQgYSBsb2NhbCBtYXgsIGFmdGVyIGFsbC4gRmluYWxseSB3ZSB1c2UgdGhpcyB2YWx1ZSBmb3IgJFxzaWdtYSQ6DQoNCmBgYHtyfQ0KKCBzaWdtYSA8LSBzcXJ0KC0xL2dwcCkgKQ0KYGBgDQoNCiMjIyMgV2hhdCBpZiB3ZSBkaWRuJ3QgaGF2ZSB0aGUgZnVsbCBncmlkIHRvIHdvcmsgd2l0aD8NCg0KSWYgd2UgaGFkbid0IGNvbXB1dGVkIHRoZSBwb3N0ZXJpb3Igb24gYSBncmlkIGJlZm9yZWhhbmQsIHdlIGNvdWxkIGFsd2F5cyBjaG9vc2UgYW4gYXBwcm9wcmlhdGVseSBzbWFsbCBzdGVwIHNpemUgYW5kIGNvbXB1dGUganVzdCB0aGUgdGhyZWUgZ3JpZCBwb2ludHMgd2UgbmVlZCBpbiB0aGUgZm9ybXVsYSBmb3IgdGhlIGRpc2NyZXRlIHNlY29uZCBkZXJpdmF0aXZlLg0KDQojIyMgMS4zLjMgUmVzdWx0cw0KDQpIZXJlJ3MgYSBwbG90IG9mIHRoZSBub3JtYWwgYXBwcm94aW1hdGlvbiB3ZSd2ZSBjb21wdXRlZDoNCg0KYGBge3J9DQojIHBsb3QgdGhlIGV4YWN0IHBvc3Rlcmlvcg0KY3VydmUoDQogICAgZGJpbm9tKDYsIDksIHgpKmRub3JtKHgsIDAuMjUsIDAuNSkvMC4wNTgxNjI3LA0KICAgIHhsaW0gPSBjKDAsMSksIHhsYWIgPSAicCIsIHlsYWIgPSAicG9zdGVyaW9yIg0KKQ0KIyBwbG90IG91ciBub3JtYWwgYXBwcm94aW1hdGlvbiBmb3IgdGhlIHBvc3Rlcmlvcg0KY3VydmUoZG5vcm0oeCwgcDAsIHNpZ21hKSwgbHR5ID0gMiwgY29sID0gcmFuZ2kyLCBhZGQgPSBUUlVFKQ0KYGBgDQoNCkFuZCBoZXJlJ3MgYSB0YWJsZSBjb21wYXJpbmcgdGhlIGVzdGltYXRlcyBvZiB0aGUgbWVhbiBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIGZyb20gYG1hcGAgd2l0aCBvdXIgZXN0aW1hdGVzOg0KDQpgYGB7cn0NCnJiaW5kKA0KICAgIGFzLmRhdGEuZnJhbWUocHJlY2lzKG0xKUBvdXRwdXRbMToyXSwgcm93Lm5hbWVzID0gIm1hcCdzIiksDQogICAgZGF0YS5mcmFtZShNZWFuID0gcDAsIFN0ZERldiA9IHNpZ21hLCByb3cubmFtZXMgPSAib3VycyIpDQopDQpgYGANCg0KVGhlIG9ubHkgdGhpbmdzIHdlIG5lZWRlZCB0byBjYWxjdWxhdGUgdGhpcyBtZWFuIGFuZCB0aGlzIHN0YW5kYXJkIGRldmlhdGlvbiB3ZXJlIHRoZSBsaWtlbGlob29kIGFuZCB0aGUgcHJpb3IuIFdlIGRpZG4ndCBldmVuIG5lZWQgdG8ga25vdyB0aGUgY29uc3RhbnQgb2YgcHJvcG9ydGlvbmFsaXR5IHdlJ2QgbmVlZCB0byBtdWx0aXBseSB0aGVtIGJ5IHRvIGdldCB0aGUgZXhhY3QgcG9zdGVyaW9yLg0KDQojIDIgRXN0aW1hdGluZyB0aGUgbWVhbiBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIGZvciBhIG5vcm1hbCBsaWtlbGlob29kDQoNCk5vdyB3ZSBjb25zaWRlciBhIHByb2JsZW0gd2hlcmUgd2UnbGwgZXN0aW1hdGUgdHdvIHBhcmFtZXRlcnMgaW5zdGVhZCBvZiBvbmUuIFRoaXMgbWVhbnMgdGhhdCB3ZSdsbCBiZSBmaW5kaW5nIGEgbm9ybWFsIGFwcHJveGltYXRpb24gZm9yIHRoZWlyICpqb2ludCogcG9zdGVyaW9yLg0KDQpTdXBwb3NlIHRoYXQgd2UgaGF2ZSBkYXRhIGNvbnRhaW5pbmcgMjAgb2JzZXJ2YXRpb25zIHdoaWNoIHdlIGtub3cgdG8gYmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgd2l0aCBzb21lIG1lYW4gYW5kIHZhcmlhbmNlLiBPdXIgcHJpb3IgZm9yIHRoZSBtZWFuICRcbXUkIGlzDQoNCiQkDQpcbXUgXHNpbSBcTm9ybWFsKDAsNSkNCiQkDQoNCmFuZCBvdXIgcHJpb3IgZm9yIHRoZSBzdGFuZGFyZCBkZXZpYXRpb24gJFxzaWdtYSQgaXMNCg0KJCQNClxzaWdtYSBcc2ltIFxVbmlmb3JtKDAsMikuDQokJA0KDQpIZXJlJ3Mgb3VyIGRhdGE6DQoNCmBgYHtyfQ0KdHJ1ZV9tdSA8LSAyDQp0cnVlX3NkIDwtIDENCnNldC5zZWVkKDEpDQpkIDwtIHJub3JtKDIwLCB0cnVlX211LCB0cnVlX3NkKQ0KYGBgDQoNCiMjIDIuMSBBbmFseXRpY2FsIHNvbHV0aW9uDQoNClRoZSBqb2ludCBwb3N0ZXJpb3IgZm9yICRcbXUkIGFuZCAkXHNpZ21hJCBpcw0KDQokJA0KZihcbXUsXHNpZ21hKSBccHJvcHRvIFxOb3JtYWwoXG11IFxtaWQgMCw1KSBcVW5pZm9ybShcc2lnbWEgXG1pZCAwLDIpIFxwcm9kX3tpPTF9XnsyMH0gXE5vcm1hbChkX2kgXG1pZCBcbXUsXHNpZ21hKSwNCiQkDQoNCndoZXJlICRkXzEsIGRfMiwgXGxkb3RzLCBkX3syMH0kIGFyZSBvdXIgMjAgZGF0YSBwb2ludHMuDQoNCldlJ2xsIHNraXAgZmluZGluZyB0aGUgY29uc3RhbnQgb2YgcHJvcG9ydGlvbmFsaXR5IGFuZCBwbG90dGluZyB0aGUgcG9zdGVyaW9yIGFuZCBtb3ZlIHN0cmFpZ2h0IHRvIHRoZSBlc3RpbWF0ZWQgbWVhbnMsIHN0YW5kYXJkIGRldmlhdGlvbnMsIGFuZCBjb3JyZWxhdGlvbiBmb3IgdGhlIHBhcmFtZXRlcnMgJFxtdSQgYW5kICRcc2lnbWEkLg0KDQojIyAyLjIgUG9zdGVyaW9yIGVzdGltYXRlcyBmcm9tIGBtYXBgDQoNCldlJ2xsIHVzZSBgbWFwYCB0byBmaW5kIG5vcm1hbCBhcHByb3hpbWF0aW9ucyB0byB0aGUgbWFyZ2luYWwgcG9zdGVyaW9ycyBmb3IgJFxtdSQgYW5kICRcc2lnbWEkLg0KDQpgYGB7cn0NCm0yIDwtIG1hcCgNCiAgICBhbGlzdCgNCiAgICAgICAgeCB+IGRub3JtKG11LCBzaWdtYSksDQogICAgICAgIG11IH4gZG5vcm0oMCwgNSksDQogICAgICAgIHNpZ21hIH4gZHVuaWYoMCwyKQ0KICAgICksDQogICAgZGF0YSA9IGxpc3QoeCA9IGQpDQopDQpwcmVjaXMobTIsIGNvcnIgPSBUUlVFKQ0KYGBgDQoNCk5vdGUgdGhhdCB0aGUgbW9kZWwgZXN0aW1hdGVzIGBtdWAgYW5kIGBzaWdtYWAgdG8gaGF2ZSBhIHZlcnkgc21hbGwgbmVnYXRpdmUgY29ycmVsYXRpb24gb2YgYHIgcHJlY2lzKG0yLCBjb3JyPVRSVUUpQG91dHB1dFsxLDZdYC4NCg0KIyMgMi4zIE1hbnVhbGx5IG9idGFpbmluZyB0aGUgbm9ybWFsIGFwcHJveGltYXRpb24NCg0KSW4gdGhpcyBjYXNlIHdlJ3JlIGZpbmRpbmcgdGhlIG5vcm1hbCBhcHByb3hpbWF0aW9uIHRvIHRoZSAqam9pbnQqIHBvc3RlcmlvciBmb3IgJFxtdSQgYW5kICRcc2lnbWEkLiBGaW5kaW5nIHRoZSBtYXhpbXVtIG9mIHRoZSBwb3N0ZXJpb3Igd2lsbCBiYXNpY2FsbHkgYmUganVzdCBhcyBlYXN5IGFzIGJlZm9yZSwgYnV0IHRvIGVzdGltYXRlIHRoZSBjdXJ2YXR1cmUgbmVhciB0aGlzIG1heGltdW0gd2UnbGwgbmVlZCB0byB1c2Ugc29tZSBtdWx0aXZhcmlhYmxlIGNhbGN1bHVzLg0KDQpTdXBwb3NlIHdlJ3ZlIGFscmVhZHkgZm91bmQgdGhlIGxvY2F0aW9uICQoXG11XzAsIFxzaWdtYV8wKSQgb2YgdGhlIG1heGltdW0gb2YgdGhlIHBvc3Rlcmlvci4gV2UgbmVlZCB0byBmaW5kIHRoZSBjb3ZhcmlhbmNlIG1hdHJpeCAkXG1hdGhiZiBcU2lnbWEkIHdoaWNoIG1ha2VzDQoNCiQkDQpmKFxtYXRoYmYgbSkgXGFwcHJveCBcZnJhY3sxfXsyXHBpIFxzcXJ0e1xsdmVydCBcU2lnbWEgXHJ2ZXJ0fX0gXGV4cFwhXGxlZnRbIC1cZnJhY3sxfXsyfSAoXG1hdGhiZiBtIC0gXG1hdGhiZiBtXzApXntcdGV4dHtUfX0gXG1hdGhiZlxTaWdtYV57LTF9IChcbWF0aGJmIG0gLSBcbWF0aGJmIG1fMCkgXHJpZ2h0XQ0KJCQNCg0Kd2hlbiAkXG1hdGhiZiBtJCBpcyBuZWFyICRcbWF0aGJmIG1fMCQsIHdoZXJlDQoNCiQkDQpcbWF0aGJmIG0gPSBcYmVnaW57cG1hdHJpeH0gXG11IFxcIFxzaWdtYSBcZW5ke3BtYXRyaXh9IFxxcXVhZCBcdGV4dHthbmR9IFxxcXVhZCBcbWF0aGJmIG1fMCA9IFxiZWdpbntwbWF0cml4fSBcbXVfMCBcXCBcc2lnbWFfMCBcZW5ke3BtYXRyaXh9Lg0KJCQNCg0KVGFraW5nIGxvZ2FyaXRobXMsIHdlIHdhbnQNCg0KJCQNClxsb2cgZihcbWF0aGJmIG0pIFxhcHByb3ggLVxmcmFjezF9ezJ9IChcbWF0aGJmIG0gLSBcbWF0aGJmIG1fMClee1x0ZXh0e1R9fSBcbWF0aGJmXFNpZ21hXnstMX0gKFxtYXRoYmYgbSAtIFxtYXRoYmYgbV8wKSArIFx0ZXh0e2NvbnN0fS4NCiQkDQoNCkp1c3QgYXMgYmVmb3JlLCBzaW5jZSAkZihcbWF0aGJmIG0pJCAoYW5kIGhlbmNlICRcbG9nIGYoXG1hdGhiZiBtKSQpIGhhcyBhIG1heGltdW0gYXQgJFxtYXRoYmYgbSA9IFxtYXRoYmYgbV8wJCwgdGhlIGxpbmVhciB0ZXJtcyBkcm9wIG91dCBvZiB0aGUgVGF5bG9yIHNlcmllcyBmb3IgJFxsb2cgZihcbWF0aGJmIG0pJCwgYW5kIHdlJ3JlIGxlZnQgd2l0aCB0aGUgYXBwcm94aW1hdGlvbg0KDQokJA0KXGxvZyBmKFxtYXRoYmYgbSkgXGFwcHJveCBcZnJhY3sxfXsyfSAoXG1hdGhiZiBtIC0gXG1hdGhiZiBtXzApXntcdGV4dHtUfX0gXG1hdGhiZiBIKFxtYXRoYmYgbV8wKSAoXG1hdGhiZiBtIC0gXG1hdGhiZiBtXzApICsgXHRleHR7Y29uc3R9LA0KJCQNCg0Kd2hlcmUgJFxtYXRoYmYgSChcbWF0aGJmIG1fMCkkIGlzIHRoZSBIZXNzaWFuIG1hdHJpeCBmb3IgJGcoXG1hdGhiZiBtKSA9IFxsb2cgZihcbWF0aGJmIG0pJCBldmFsdWF0ZWQgYXQgJFxtYXRoYmYgbSA9IFxtYXRoYmYgbV8wJC4gKFRoaXMgKkhlc3NpYW4gbWF0cml4KiBpcyBqdXN0IGEgbWF0cml4IG9mIHRoZSBzZWNvbmQgZGVyaXZhdGl2ZXMgb2YgJFxsb2cgZihcbWF0aGJmIG0pJC4pIENvbXBhcmluZyB0aGlzIGZvcm11bGEgdG8gdGhlIG9uZSBhYm92ZSwgd2Ugc2VlIHRoYXQgd2Ugc2hvdWxkIHRha2UNCg0KJCQNClxtYXRoYmYgXFNpZ21hID0gLVxtYXRoYmYgSChcbWF0aGJmIG1fMCleey0xfS4NCiQkDQoNCkNvbXBhcmUgdGhpcyB3aXRoIG91ciBjaG9pY2UgZm9yICRcc2lnbWFeMiQgaW4gW3NlY3Rpb24gMS4zXSgjczEzKS4NCg0KIyMjIDIuMy4xIEZpbmRpbmcgdGhlIG1heGltdW0NCg0KVGhlcmUncyBwcm9iYWJseSBhIGNsZWFuZXIgd2F5IHRvIGRvIHRoaXMsIGJ1dCB3ZSdsbCB1c2UgdHdvIGFwcGxpY2F0aW9ucyBvZiBgd2hpY2gubWF4YCBhbmQgYG1heGAgdG8gZmluZCB0aGUgaW5kaWNlcyBvZiB0aGUgbWF4aW11bSBvZiB0aGUgam9pbnQgcG9zdGVyaW9yLg0KDQpgYGB7cn0NCiMgdGhlIGdyaWQNCm11LmdyaWRzaXplIDwtIDUwMDANCm11Lm1heCA8LSA1DQpzaWdtYS5ncmlkc2l6ZSA8LSAxMDAwDQptdS5ncmlkIDwtIHNlcShmcm9tID0gLW11Lm1heCwgdG8gPSBtdS5tYXgsIGxlbmd0aC5vdXQgPSBtdS5ncmlkc2l6ZSkNCnNpZ21hLmdyaWQgPC0gc2VxKGZyb20gPSAwLCB0byA9IDIsIGxlbmd0aC5vdXQgPSBzaWdtYS5ncmlkc2l6ZSkNCmcuZ3JpZCA8LSBvdXRlcigNCiAgICBtdS5ncmlkLCBzaWdtYS5ncmlkLA0KICAgIGZ1bmN0aW9uKG0scykgew0KICAgICAgICBsb2dfcG9zdCA8LSBkbm9ybShtLCAwLCA1LCBsb2cgPSBUUlVFKSArIGR1bmlmKHMsIDAsIDIsIGxvZyA9IFRSVUUpDQogICAgICAgIGZvciAoaSBpbiAxOmxlbmd0aChkKSkNCiAgICAgICAgICAgIGxvZ19wb3N0IDwtIGxvZ19wb3N0ICsgZG5vcm0oZFtpXSwgbSwgcywgbG9nID0gVFJVRSkNCiAgICAgICAgcmV0dXJuKGxvZ19wb3N0KQ0KICAgIH0NCikNCiMgZmluZCB0aGUgZW50cmllcyB3aXRoIHRoZSBtYXhpbXVtIG9mIHRoZSBwb3N0ZXJpb3INCnJvbSA8LSB3aGljaC5tYXgoYXBwbHkoZy5ncmlkLCAxLCBtYXgpKSAgICAjIHJvbSA9IFJvdyBPZiBNYXgNCmNvbSA8LSB3aGljaC5tYXgoYXBwbHkoZy5ncmlkLCAyLCBtYXgpKSAgICAjIGNvbSA9IENvbCBPZiBNYXgNCiMgZmluZCBjb3JyZXNwb25kaW5nIG11IGFuZCBzaWdtYQ0KKCBtdTAgPC0gbXUuZ3JpZFtyb21dICkNCiggc2lnbWEwIDwtIHNpZ21hLmdyaWRbY29tXSApDQpgYGANCg0KVGhlcmUncyBhbiBhd2t3YXJkIGBmb3JgIGxvb3AgaW4gdGhlIGFib3ZlIGNvZGUgdGhhdCB5b3UgbWlnaHQgaGF2ZSBub3RpY2VkLiBPcmlnaW5hbGx5IEkgd3JvdGUgdGhlIGNhbGwgdG8gYG91dGVyYCBsaWtlDQoNCmBgYHtyIGV2YWwgPSBGQUxTRX0NCmxvZ19wb3N0ZXJpb3IgPC0gZnVuY3Rpb24obXUsIHNpZ21hKQ0KICAgIGRub3JtKG11LCAwLCA1LCBsb2cgPSBUUlVFKSArIGR1bmlmKHNpZ21hLCAwLCAyLCBsb2cgPSBUUlVFKSArIHN1bShkbm9ybShkLCBtdSwgc2lnbWEsIGxvZyA9IFRSVUUpKQ0Kdl9sb2dfcG9zdGVyaW9yIDwtIFZlY3Rvcml6ZShsb2dfcG9zdGVyaW9yLCBjKCJtdSIsICJzaWdtYSIpKQ0KZy5ncmlkIDwtIG91dGVyKG11LmdyaWQsIHNpZ21hLmdyaWQsIHZfbG9nX3Bvc3RlcmlvcikNCmBgYA0KDQouLi5idXQgdGhpcyBlbmRlZCB1cCBiZWluZyBleHRyZW1lbHkgc2xvdy4gVGhlIGNvZGUgYWJvdmUgd2l0aCB0aGUgYGZvcmAgbG9vcCBlbmRlZCB1cCBiZWluZyBtdWNoIGZhc3Rlci4NCg0KDQojIyMgMi4zLjIgQ29tcHV0aW5nIHRoZSBIZXNzaWFuDQoNClRoZSB2YXJpYWJsZSBgZy5ncmlkYCBub3cgY29udGFpbnMgYSAyZCBhcnJheSBvZiB2YWx1ZXMgb2YgdGhlIGxvZyBwb3N0ZXJpb3IuDQoNCldoZXJlYXMgaW4gdGhlIG9uZS1kaW1lbnNpb25hbCBjYXNlIHRoZXJlIHdhcyBvbmx5IGEgc2luZ2xlIHNlY29uZCBkZXJpdmF0aXZlIHRvIHdvcnJ5IGFib3V0LCBub3cgdGhlcmUgYXJlIGZvdXI6ICRnX3t4eH0oXG1hdGhiZiBtKSQsICRnX3t4eX0oXG1hdGhiZiBtKSQsICRnX3t5eH0oXG1hdGhiZiBtKSQsIGFuZCAkZ197eXl9KFxtYXRoYmYgbSkkLiBCdXQgdGhhbmtmdWxseSAkZ197eHl9ID0gZ197eXh9JCBpZiAkZyQgaXMgbmljZSBlbm91Z2ggKGl0IGlzKSwgc28gcmVhbGx5IHRoZXJlIGFyZSBvbmx5IHRocmVlIHRvIHdvcnJ5IGFib3V0Lg0KDQpXZSBzdGlsbCBjb21wdXRlIHRoZW0gdXNpbmcgZGlzY3JldGUgZGlmZmVyZW5jZXM6DQoNCiQkDQpnX3t4eH0oXG11X2ksIFxzaWdtYV9qKSBcYXBwcm94IFxmcmFje2dfe2ktMSxqfSAtIDIgZ197aSxqfSArIGdfe2krMSxqfX17XERlbHRhXG11XjJ9LCBcXA0KZ197eXl9KFxtdV9pLCBcc2lnbWFfaikgXGFwcHJveCBcZnJhY3tnX3tpLGotMX0gLSAyIGdfe2ksan0gKyBnX3tpLGorMX19e1xEZWx0YVxzaWdtYV4yfSwgXFwNCmdfe3h5fShcbXVfaSwgXHNpZ21hX2opIFxhcHByb3ggXGZyYWN7Z197aS0xLGotMX0gLSBnX3tpLTEsaisxfSAtIGdfe2krMSxqLTF9ICsgZ197aSsxLGorMX19ezRcRGVsdGEgXHNpZ21hIFxEZWx0YSBcbXV9Lg0KJCQNCg0KVGhlbiBvdXIgSGVzc2lhbiBpcw0KDQokJA0KXG1hdGhiZiBIKFxtYXRoYmYgbV8wKSA9IFxiZWdpbntwbWF0cml4fSBnX3t4eH0oXG1hdGhiZiBtXzApICYgZ197eHl9KFxtYXRoYmYgbV8wKSBcXCBnX3t5eH0oXG1hdGhiZiBtXzApICYgZ197eXl9KFxtYXRoYmYgbV8wKSBcZW5ke3BtYXRyaXh9Lg0KJCQNCg0KSGVyZSdzIHRoZSBjb2RlOg0KDQpgYGB7cn0NCiMgY29tcHV0ZSB0aGUgSGVzc2lhbg0KZ194eCA8LSAoZy5ncmlkW3JvbS0xLCBjb21dIC0gMipnLmdyaWRbcm9tLCBjb21dICsgZy5ncmlkW3JvbSsxLCBjb21dKSooKG11LmdyaWRzaXplLTEpLygyKm11Lm1heCkpXjINCmdfeXkgPC0gKGcuZ3JpZFtyb20sIGNvbS0xXSAtIDIqZy5ncmlkW3JvbSwgY29tXSArIGcuZ3JpZFtyb20sIGNvbSsxXSkqKChzaWdtYS5ncmlkc2l6ZS0xKS8yKV4yDQpnX3h5IDwtIChnLmdyaWRbcm9tLTEsIGNvbS0xXSAtIGcuZ3JpZFtyb20tMSwgY29tKzFdIC0gZy5ncmlkW3JvbSsxLCBjb20tMV0gKw0KICAgICAgICAgICAgIGcuZ3JpZFtyb20rMSwgY29tKzFdKSooKG11LmdyaWRzaXplLTEpKihzaWdtYS5ncmlkc2l6ZS0xKS8oMTYqbXUubWF4KSkNCiggaGVzcyA8LSBtYXRyaXgoYyhnX3h4LCBnX3h5LCBnX3h5LCBnX3l5KSwgbmNvbCA9IDIpICkNCiMgY29tcHV0ZSBjb3ZhcmlhbmNlIG1hdHJpeA0KKCBiaWdfc2lnbWEgPC0gLXNvbHZlKGhlc3MpICkNCmBgYA0KDQojIyMgMi4zLjMgUmVzdWx0cw0KDQpXZSBjYW4gcmVhZCBvZmYgdGhlIG1hcmdpbmFsIHN0YW5kYXJkIGRldmlhdGlvbnMgZnJvbSB0aGUgY292YXJpYW5jZSBtYXRyaXg6DQoNCmBgYHtyfQ0KIyBzdGFuZGFyZCBkZXZpYXRpb24gZm9yIG11IHBvc3Rlcmlvcg0KKCBtdV9zZCA8LSBzcXJ0KGJpZ19zaWdtYVsxLDFdKSApDQojIHN0YW5kYXJkIGRldmlhdGlvbiBmb3Igc2lnbWEgcG9zdGVyaW9yDQooIHNpZ21hX3NkIDwtIHNxcnQoYmlnX3NpZ21hWzIsMl0pICkNCmBgYA0KDQpBbmQgbm90ZSB0aGF0IHdlIHBpY2tlZCB1cCB0aGUgc2FtZSBzbGlnaHQgY29ycmVsYXRpb24gYmV0d2VlbiAkXG11JCBhbmQgJFxzaWdtYSQgdGhhdCBgbWFwYCBkaWQ6DQoNCmBgYHtyfQ0KIyBlc3RpbWF0ZWQgY29ycmVsYXRpb24gYmV0d2VlbiBtdSBhbmQgc2lnbWENCmJpZ19zaWdtYVsxLDJdLyhtdV9zZCpzaWdtYV9zZCkNCmBgYA0KDQpIZXJlJ3MgYSBzdW1tYXJ5IGNvbXBhcmluZyBhbGwgZXN0aW1hdGVzIGZyb20gYG1hcGAgd2l0aCBvdXIgZXN0aW1hdGVzOg0KDQpgYGB7cn0NCmRhdGEuZnJhbWUoDQogICAgbXVfTWVhbiA9IGMoY29lZihtMilbIm11Il0sIG11MCksDQogICAgbXVfU3RkRGV2ID0gYyhwcmVjaXMobTIpQG91dHB1dFsxLDJdLCBtdV9zZCksDQogICAgc2lnbWFfTWVhbiA9IGMoY29lZihtMilbInNpZ21hIl0sIHNpZ21hMCksDQogICAgc2lnbWFfU3RkRGV2ID0gYyhwcmVjaXMobTIpQG91dHB1dFsyLDJdLCBzaWdtYV9zZCksDQogICAgY29yciA9IGMocHJlY2lzKG0yLCBjb3JyPVRSVUUpQG91dHB1dFsxLDZdLCBiaWdfc2lnbWFbMSwyXS8obXVfc2Qqc2lnbWFfc2QpKSwNCiAgICByb3cubmFtZXMgPSBjKCJtYXAncyIsICJvdXJzIikNCikNCmBgYA0KDQoNCiMgMyBNb3JlIHBhcmFtZXRlcnMNCg0KVGhpcyBicnV0ZSBmb3JjZSBhcHByb2FjaCB0byBmaW5kaW5nIHRoZSBtYXhpbXVtIG9mIHRoZSBqb2ludCBwb3N0ZXJpb3IgY2FuIG9ubHkgdGFrZSB1cyBzbyBmYXIuIEFzIHRoZSBkaW1lbnNpb25hbGl0eSBvZiBgZy5ncmlkYCBpbmNyZWFzZXMgd2l0aCBtb3JlIHBhcmFtZXRlcnMsIGp1c3QgY29uc3RydWN0aW5nIGBnLmdyaWRgIHdvdWxkIHRha2UgYWdlcy4gQSBtb3JlIGludGVsbGlnZW50IGFwcHJvYWNoLCBsaWtlIHBpY2tpbmcgYSBzdGFydGluZyBwb2ludCBhbmQgb25seSBjb21wdXRpbmcgdGhlIHZhbHVlcyBvZiAkZyQgbmVjZXNzYXJ5IHRvIGZvcmdlIGEgcGF0aCBvZiBzdGVlcGVzdCBhc2NlbnQsIHdvdWxkIGJlIG11Y2ggZGVzaXJlZC4gQmFzZWQgb24gd2hhdCBNY0VscmVhdGggaGludGVkIGF0IGluIHRoZSBib29rLCBJIHRoaW5rIHRoaXMgaXMgc29tZXRoaW5nIGxpa2Ugd2hhdCBgbWFwYCBkb2VzLg0KDQpJIHdhbnRlZCB0byBrZWVwIHRoaW5ncyBzaW1wbGUgaW4gdGhpcyBub3RlLCBhbmQgdGhlIGJydXRlIGZvcmNlIGFwcHJvYWNoIGlzIGFzIHNpbXBsZSBhcyBpdCBnZXRzLg0KDQotLS0tDQoNCltBbnRvbmlvIFIuIFZhcmdhc10oaHR0cHM6Ly9tYXRoc3RhdC5kYWwuY2EvfmFudG9uaW92LykNCg0KMSBPY3QuIDIwMTgNCg0KVXBkYXRlZCA2IE9jdC4gMjAxOC4NCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=