This is a collection of my thoughts on Katherine Bailey’s post Gaussian Processes for Dummies. I’m just learning about Gaussian processes myself, and what follows surely reflects some of my confusion about all this. In the future I’ll revisit these notes with (hopefully) a more informed perspective.

It seems like Bailey’s post outlines a method for inferring the values of a function \(f\) after learning a handful of its points. I’ll implement the algorithm described in their post (which is originally in Python) in R, then experiment a bit to try to understand what to expect from it.

library(rethinking)    # github.com/rmcelreath/rethinking
library(corrplot)
library(matrixcalc)

We’ll take a set of points on a sine curve, \(f(x) = \sin x\), and use this as our training data. We store the x values in xtrain, and their corresponding sine values in ytrain.

xtrain <- c(-4, -3, -2, -1, 1, 2, 5)
ytrain <- sin(xtrain)
plot(xtrain, ytrain)

This training data will be used for about the first half of these notes.

1 Direct Gaussian process

Starting with a set of points (xtrain, for example), we can impose a set of correlations on those points based on their distances from each other. For our purposes, we want nearer points to be more correlated, and farther points to be less correlated. This is what we’d expect from a continuous function: nearby x-values should have nearby y values.

We’ll let the covariance between points \(x_i\) and \(x_j\) fall off like a Gaussian curve as the distance between the points increases, as in

\[ \operatorname{Cov}(x_i,x_j) = \exp\!\left(-\eta^2 (x_i - x_j)^2\right). \]

We can tweak the parameter \(\eta^2\) to increase or decrease the distance over which points covary.

Here’s an R function to compute this covariance matrix for two input lists, with a small adjustment along the diagonal to ensure positive definiteness:

covK <- function(a, b, rhosq) {
    K <- outer(a, b, function(x,y) exp(-rhosq*(x - y)^2))
    if (length(a) == length(b))
        diag(K) <- 1 + 0.01
    return(K)
}

If we compute the the Cholesky decomposition of a covariance matrix \(\mathbf K\),

\[ \mathbf K = \mathbf L \mathbf L^T, \]

we end up with a matrix \(\mathbf L\) which takes uncorrelated samples \(\mathbf x\) to correlated samples \(\mathbf L \mathbf x\) (this is matrix multiplication of \(\mathbf L\) and \(\mathbf x\)).

Now, our goal is to estimate the function \(f\), and our prior will be that its values are normally distributed with mean 0 and variance 1 and correlated according to \(\mathbf K\). To get a few sample functions from this prior, we generate standard normal noise on the interval we’re interested in, then give it the desired correlation by multiplying it by the appropriate Cholesky factor \(\mathbf L\).

Our “test” points, which is just a grid of \(x\)-values where we want to estimate \(f(x)\), will be be 100 equally spaced points ranging from -5 to 5. We compute the covariance matrix corresponding to distances between these points using covK() and its Cholesky factor using chol(), generate 3 sets of standard normal noise at these points, multiply them each by the Cholesky factor, and plot the results.

n <- 100
xtest <- seq(from = -5, to = 5, length.out = n)
rhosq <- 5    # Bailey's parameter choice
# compute correlation matrix and Cholesky factor
K_ss <- covK(xtest, xtest, rhosq)
L_ss <- chol(K_ss)
# take three samples of Gaussian noise from the posterior
#   and correlate each one by multiplying it against L_ss
num_post_samples <- 3
uncorrelated <- matrix(rnorm(num_post_samples*n), nrow = n)
correlated <- L_ss %*% uncorrelated
# plot the three samples
plot(0, xlim = c(-5,5), ylim = c(-3,3), type = "n", xlab = "x", ylab = "y")
col = c("black", "red", "blue")
for (i in 1:num_post_samples)
    lines(xtest, correlated[,i], col = col[i])
mtext("Three samples from the prior")

Some of the variable names here have been chosen to match the ones in Bailey’s code.

In the above plot, notice that the resulting curves are no longer just pure noise. Their values now covary according to a decaying exponential of their squared distances; if one x-value has a high y-value, the nearby x-values will also have high y-values, but x-values farther away don’t feel the same pull upward. Any x-values farther than 1 unit apart essentially don’t correlate at all.

Determination of the posterior mean at each xtest (in other words, our guess for the value of \(f\) at each xtest) and the posterior correlator matrix can be done using matrix algebra. I haven’t looked at the reference given by Bailey (Murphy’s Machine Learning: A Probablistic Perspective) for this calculation yet, so I can’t say I understand it yet. But I have done my best to accurately implement it in R, based on the Python code given by Bailey.

I wrote a function gpfit to calculate the posterior mean, variance, and posterior correlator matrix given a set of training x-values xtrain and their corresponding y-values ytrain, a set of test x-values xtest, and a fit parameter rhosq. The function also returns the smallest eigenvalue of a matrix from an intermediate step of the calculation—this will be used later to differentiate between different possible values of the parameter \(\rho^2\).

# this function requires covK() defined above
gpfit <- function(xtrain, ytrain, xtest, rhosq) {
    # compute posterior mean
    K <- covK(xtrain, xtrain, rhosq)
    L <- chol(K)
    K_s <- covK(xtrain, xtest, rhosq)
    Lk <- solve(L, K_s)
    X <- solve(L, ytrain)
    mu <- t(Lk) %*% X
    
    # compute posterior variance
    K_ss <- covK(xtest, xtest, rhosq)
    variance <- diag(K_ss) - apply(Lk^2, 2, sum)
    
    # compute posterior correlator matrix if it's well-defined
    K_ss <- covK(xtest, xtest, rhosq)
    A <- K_ss - t(Lk) %*% Lk
    mineigen = min(eigen(A, symmetric = TRUE, only.values = TRUE)$values)
    if (mineigen > 0)  # is A positive definite?
        L_post <- chol(A)
    else
        L_post <- NULL
    
    return(list(mean = mu, variance = variance, L_post = L_post, mineigen = mineigen))
}

In the following code we use gpfit() to compute the Gaussian process fit, then plot the posterior mean fit curve and shade the 95% posterior density interval for the mean (as determined by the variance returned by gpfit()).

fit <- gpfit(xtrain, ytrain, xtest, rhosq)
PI <- matrix(nrow = 2, ncol = n)
PI[1,] <- fit$mean - 2*sqrt(fit$variance)
PI[2,] <- fit$mean + 2*sqrt(fit$variance)
plot(0, xlim = c(-5, 5), ylim = c(-3, 3), type = "n", xlab = "x", ylab = "y")
shade(PI, xtest)
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)

As gpfit() also returns the posterior correlator matrix \(\mathbf L\), we can sample a fit curve from the posterior distribution of curves by generating standard Gaussian noise \(\mathbf x\) then transforming it via \(\mathbf x_{\text{post}} = \mathbf \mu + \mathbf L \mathbf x\). In the following code we sample three curves from the posterior and include them in the previous plot.

num_post_samples <- 3
uncorrelated <- matrix(rnorm(num_post_samples*n), nrow = n)
correlated <- fit$L_post %*% uncorrelated
plot(0, xlim = c(-5, 5), ylim = c(-3, 3), type = "n", xlab = "x", ylab = "y")
shade(PI, xtest)
col = c("gray2", "red", rangi2)
for (i in 1:num_post_samples)
    lines(xtest, fit$mean + correlated[,i], col = col[i])    # mu + Lx
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)
mtext("Three samples from the posterior")

Let’s try to get a better picture of the posterior through sampling. We’ll sample 100,000 curves from the posterior, then plot the first 150 of them behind the posterior mean curve.

num_post_samples <- 1e5
uncorrelated <- matrix(rnorm(num_post_samples*n), nrow = n)
correlated <- fit$L_post %*% uncorrelated
plot(0, xlim = c(-5, 5), ylim = c(-3, 3), type = "n", xlab = "x", ylab = "y")
for (i in 1:150)
    lines(xtest, fit$mean + correlated[,i], col = col.alpha("black", 0.15))    # mu + Lx
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)
mtext(paste("150 samples from the posterior"))

These seem to follow a different distribution than the regions that were shaded according to the variance above. Using all 100,000 sample curves, we’ll compute and plot the empirical 95% intervals implied by the sample.

posterior <- apply(correlated, 2, function(x) fit$mean + x)    # mu + Lx
mu.HPDI <- apply(posterior, 1, HPDI, prob = 0.95)
plot(0, xlim = c(-5, 5), ylim = c(-3, 3), type = "n", xlab = "x", ylab = "y")
shade(mu.HPDI, xtest)
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)
mtext(paste("95% posterior density interval (", num_post_samples, " samples)", sep = ""))

While there is some similarity to the region defined by the variance, this region isn’t nearly as regular the earlier one. And this isn’t due to random sampling—we’ve taken enough samples to get a good approximation. You can try running the code above several times (or taking many more samples if your compute can handle it) to be sure.

1.1 The parameter \(\rho^2\)

In the above calculations we took \(\rho^2 = 5\) to match the Python code written by Bailey. This parameter measures how “smooth” the interpolating curve is, with a smaller \(\rho^2\) yielding a smoother curve. Try decreasing it to 4 and running the code, and then to 3 and running it again. But decrease it too low (try \(\rho^2 = 2\)) and you’ll get an error when you try to sample curves from the posterior. However, the posterior mean curve is computed just fine. That’s odd.

The above code doesn’t really illustrate it well, but if you decrease it even further, say to \(\rho^2 = 1.5\), then the posterior “variance” computed by gpfit becomes negative and the posterior mean fails to interpolate the training points (but maybe that’s a good thing—we’ll explore this in section 2). Strange behavior indeed.

I suspect the reasons for this are buried in assumptions behind the specific steps in the matrix algebra. It’s possible that the steps could be altered to still give results in these cases, but I haven’t looked at it yet.

1.2 Determining the minimal values of \(\rho^2\)

Using the variance and mineigen values returned by gpfit() we can determine the minimum values of the parameter \(\rho^2\) which yield good behaviors in sampling from the posterior and computing the posterior mean curve.

1.2.1 The smallest \(\rho^2\) to sample from the posterior

The function minrhosq() below uses a root-finding algorithm to determine the smallest admissible value of \(\rho^2\) for a given training set and test set. We pass it these training and test sets, along with an interval which we know contains the desired value of \(\rho^2\).

By default, minrhosq() finds the smallest value of rhosq with which we can still sample from the posterior using \(\mathbf L\). If we look closely at how the matrix \(\mathbf L\) is computed, we see that it won’t exist when the value of mineigen is negative (because then the matrix which is used to construct \(\mathbf L\) isn’t positive definite), and it will exist if mineigen is positive. We find the desired value of rhosq by finding where this mineigen transitions from positive to negative.

minrhosq <- function(xtrain, ytrain, xtest, interval, forinterp = FALSE) {
    if (!forinterp) {
        # find the smallest value of rhosq which gives positive mineigen
        root <- uniroot(
            function(x) gpfit(xtrain, ytrain, xtest, x)$mineigen,
            interval
        )
    } else {
        # find the smallest value of rhosq which gives positive variance
        root <- uniroot(
            function(x) min(gpfit(xtrain, ytrain, xtest, x)$variance),
            interval
        )
    }
    # $f.root is the value of the function at $root
    # -- If $f.root is negative, return $root + (root precision) as rhosq instead
    if (root$f.root > 0)
        return(root$root)
    else
        return(root$root + root$estim.prec)
}

We know that the minimal \(\rho^2\) for which \(\mathbf L\) exists is somewhere between 2 and 3. Note that mineigen is negative when rhosq is 2 and positive when it’s 3:

gpfit(xtrain, ytrain, xtest, 2)$mineigen
[1] -0.02939852
gpfit(xtrain, ytrain, xtest, 3)$mineigen
[1] 0.01

In the following code we use minrhosq() to find this value of rhosq between 2 and 3, then plug it into gpfit() and plot the posterior mean curve and the 95% posterior density interval.

rhosq <- minrhosq(xtrain, ytrain, xtest, c(2,3))
fit <- gpfit(xtrain, ytrain, xtest, rhosq)
num_post_samples <- 1e5
uncorrelated <- matrix(rnorm(num_post_samples*n), nrow = n)
correlated <- fit$L_post %*% uncorrelated
posterior <- apply(correlated, 2, function(x) fit$mean + x)
mu.HPDI <- apply(posterior, 1, HPDI, prob = 0.95)
plot(0, xlim = c(-5, 5), ylim = c(-3, 3), type = "n", xlab = "x", ylab = "y")
shade(mu.HPDI, xtest)
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)
mtext(paste("95% posterior density interval (", num_post_samples, " samples)", sep = ""))

We’re getting some interesting artifacts from the posterior samples. I think these are occurring because we’re right on the border of the posterior correlator \(\mathbf L\) breaking down. If we increase rhosq slightly, these should go away:

fit <- gpfit(xtrain, ytrain, xtest, rhosq + 0.1)    # slightly increase rhosq
correlated <- fit$L_post %*% uncorrelated
posterior <- apply(correlated, 2, function(x) fit$mean + x)
mu.HPDI <- apply(posterior, 1, HPDI, prob = 0.95)
plot(0, xlim = c(-5, 5), ylim = c(-3, 3), type = "n", xlab = "x", ylab = "y")
shade(mu.HPDI, xtest)
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)
mtext(paste("95% posterior density interval (", num_post_samples, " samples)", sep = ""))

1.2.2 The smallest \(\rho^2\) to interpolate the training points

If we decrease rhosq slightly below the value obtained in the last section, we can no longer sample from the posterior. However, we can still compute the posterior mean curve and estimate the posterior density interval using the variance returned by gpfit().

But there is another minimum now, lower than the previous one, beyond which the posterior mean curve no longer interpolates the training points. We can estimate this minimum by seeing which values of rhosq produce a negative variance:

min(gpfit(xtrain, ytrain, xtest, 1)$variance)
[1] -0.1584486
min(gpfit(xtrain, ytrain, xtest, 2)$variance)
[1] 0.00519122

So, we know that the minimal rhosq in this case is between 1 and 2. Now we call minrhosq(), passing it the interval (1,2) and the switch forinterp = TRUE.

Unfortunately now we can’t sample from the posterior because the posterior correlator matrix (at least as it is defined here, using the Cholesky decomposition) doesn’t exist. At least we can still use the variance returned by gpfit() to estimate the posterior density intervals.

rhosq <- minrhosq(xtrain, ytrain, xtest, c(1,2), forinterp = TRUE)
fit <- gpfit(xtrain, ytrain, xtest, rhosq)
PI <- matrix(nrow = 2, ncol = n)
PI[1,] <- fit$mean - 2*sqrt(fit$variance)
PI[2,] <- fit$mean + 2*sqrt(fit$variance)
plot(0, xlim = c(-5, 5), ylim = c(-3, 3), type = "n")
shade(PI, xtest)
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)

This is the smoothest curve so far!

If we decrease rhosq any farther, the posterior mean curve will no longer interpolate the training points. But is this really so bad? We’ll explore this next.

2 Learning the Gaussian process parameter from the data

If we wanted to, we could entertain the idea that our training points have some measurement error in them. In that case interpolating the points would be blatant overfitting of the model.

In this section we’ll explore how to determine the parameter rhosq from the training data itself. We’ll do this by creating a model which learns the distances between the points of xtrain which have correlated y-values in ytrain, according to the Gaussian kernel

\[ \operatorname{Cov}(x_i,x_j) = \exp\!\left(-\eta^2 (x_i - x_j)^2\right) \]

that we used above.

First we compute the matrix of distances between the x-values of our training data and put the training data into a data frame for convenience.

d <- data.frame(x = as.factor(xtrain), y = ytrain)
distances <- as.matrix(dist(xtrain))
colnames(distances) <- d$x
rownames(distances) <- d$x
distances
   -4 -3 -2 -1 1 2 5
-4  0  1  2  3 5 6 9
-3  1  0  1  2 4 5 8
-2  2  1  0  1 3 4 7
-1  3  2  1  0 2 3 6
1   5  4  3  2 0 1 4
2   6  5  4  3 1 0 3
5   9  8  7  6 4 3 0

In our model, we’ll suppose that the measurement error on our training points is normally distributed with mean 0 and standard deviation 0.1. We’ll fit the model using map2stan from the rethinking package. A good overview of how this works can be found in McElreath’s book Statistical Rethinking: A Bayesian Course, chapter 13 section 4.

m1 <- map2stan(
    alist(
        y ~ dnorm(mu, 0.1),
        mu <- g[x],
        g[x] ~ GPL2(Dmat, 1, rhosq, 0.01),
        rhosq ~ dgamma(1.5, 1)
    ),
    data = list(
        x = d$x,
        y = d$y,
        Dmat = distances
    ),
    warmup = 2e3,
    iter = 1e4,
    control = list(adapt_delta = 0.95)
)

Take a look at the mean value of rhosq estimated by the model. The precis() function also returns some information about the posterior density of rhosq, including its standard deviation and 89% highest posterior density interval.

precis(m1)

To get an idea of the model’s uncertainty about rhosq, let’s take 200 samples from its posterior and plot their corresponding distance correlation functions. The mean correlation function is shown as a thick solid line.

post <- extract.samples(m1)
rhosq <- mean(post$rhosq)
# plot the posterior mean covariance function
curve(
    exp(-mean(post$rhosq)*x^2),
    from = 0, to = 10,
    xlab = "dstance", ylab = "covariance",
    ylim = c(0, 1),
    lwd = 3
)
# plot 200 functions sampled from posterior
for ( i in 1:200 )
    curve(
        exp(-post$rhosq[i]*x^2),
        col = col.alpha("black", 0.17),
        add = TRUE
    )

The model is definitely uncertain about the value of rhosq, but let’s see what kind of result the mean estimate gives us.

Here’s a visualization of the correlation matrix between the values of xtrain. Note that each x is moderately correlated with the other values distance 1 away, and there is a miniscule correlation with values distance 2 away. Beyond that there is no discernable correlation.

# compute posterior mean covariance among points
K <- covK(xtrain, xtrain, rhosq)
# convert to correlation matrix
Rho <- round(cov2cor(K), 2)
# add row and column names for convenience
colnames(Rho) <- d$x
rownames(Rho) <- colnames(Rho)
corrplot(Rho, method = "circle")

Perhaps a better way to see this is by drawing lines between the training points corresponding to the strength of their correlation.

plot(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)
# overlay lines shaded by Rho
for (i in 1:length(xtrain))
    for (j in 1:length(xtrain))
        if (i < j)
            lines(
                c(xtrain[i], xtrain[j]),
                c(ytrain[i], ytrain[j]),
                lwd = 2, col = col.alpha("black", Rho[i,j]^2)
            )

So there is some expected correlation between the four points on the left, and between the two points in the middle, just based on their horizontal proximity.

Finally we’ll plot the inferred posterior mean curve using the mean value of rhosq from the model. The “true” sine curve is shown in black.

fit <- gpfit(xtrain, ytrain, xtest, rhosq)
plot(0, xlim = c(-5, 5), ylim = c(-3, 3), type = "n")
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)
curve(sin(x), lty = "dashed", col = col.alpha("black", 0.5), add = TRUE)

2.1 Limitations

To be honest, I’m nut sure if the behavior shown above is intended by the computations that go into creating the posterior mean fit curve. It actually works pretty well in that example, but let’s explore an example where it doesn’t do so well.

Instead of the nice, widely-spaced training set we’ve been using up to this point, we’ll use a training set consisting of a random sample of 10 x-values between -5 and 5.

set.seed(5)
xtrain <- runif(7, -5, 5)
ytrain <- sin(xtrain)
plot(xtrain, ytrain)

d <- data.frame(x = as.factor(round(xtrain,2)), y = ytrain)
distances <- as.matrix(dist(xtrain))
colnames(distances) <- d$x
rownames(distances) <- d$x

Now what parameter does the model learn from the data?

m2 <- map2stan(
    m1,
    data = list(
        x = d$x,
        y = d$y,
        Dmat = distances
    ),
    warmup = 2e3,
    iter = 1e4,
    control = list(adapt_delta = 0.95)
)
precis(m2)

The mean rhosq is much larger, so the model only expect points which are very close together to correlate. As before, let’s plot a picture of the uncertainty around the model’s estimate of rhosq.

post <- extract.samples(m2)
rhosq <- mean(post$rhosq)
# plot the posterior mean covariance function
curve(
    exp(-rhosq*x^2),
    from = 0, to = 10,
    xlab = "dstance", ylab = "covariance",
    ylim = c(0, 1),
    lwd = 3
)
# plot 200 functions sampled from posterior
for ( i in 1:200 )
    curve(
        exp(-post$rhosq[i]*x^2),
        col = col.alpha("black", 0.08),
        add = TRUE
    )

The model is actually very certain about this value of rhosq. This is because it now has a much wider variety of distances to learn from. Here’s what the correlations between the new training data looks like:

# compute posterior mean covariance among points
K <- covK(xtrain, xtrain, rhosq)
# convert to correlation matrix
Rho <- round(cov2cor(K), 2)
# add row and column names for convenience
colnames(Rho) <- d$x
rownames(Rho) <- colnames(Rho)
# plot the training data
plot(xtrain, ytrain, pch = 16, col = rangi2)
# overlay lines shaded by Rho
for (i in 1:length(xtrain))
    for (j in 1:length(xtrain))
        if (i < j)
            lines(
                c(xtrain[i], xtrain[j]),
                c(ytrain[i], ytrain[j]),
                lwd = 2, col = col.alpha("black", Rho[i,j]^2)
            )

Pretty much the only points that are correlated are the two near x=2.

But now something strange happens when we use gpfit() to compute the posterior mean fit curve to this new data.

fit <- gpfit(xtrain, ytrain, xtest, rhosq)
plot(0, xlim = c(-5, 5), ylim = c(-3, 3), type = "n")
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)
curve(sin(x), lty = "dashed", col = col.alpha("black", 0.5), add = TRUE)

All of the points except the two near x=2 were interpolated, and near those points the posterior mean curve blew up.

I really suspect that this kind of thing could be accounted for in the matrix algebra in gpfit(). I’d really like to revisit this example once I understand that calculation a bit more.

3 Evenly spaced test points

Of course, not all is lost. In this section we’ll show that our model behaves fairly well for evenly spaced training data, provided the x-values are far enough apart.

3.1 Large distances

Here our training data will consist of 9 evenly spaced points from -6 to 7.

xtrain <- seq(from = -6, to = 7, length.out = 9)
ytrain <- sin(xtrain)
d <- data.frame(x = as.factor(round(xtrain,2)), y = ytrain)
distances <- as.matrix(dist(xtrain))
colnames(distances) <- d$x
rownames(distances) <- d$x
plot(xtrain, ytrain)

We attempt to learn the Gaussian process parameter from the data:

m3 <- map2stan(
    m1,
    data = list(
        x = d$x,
        y = d$y,
        Dmat = distances
    ),
    warmup = 2e3,
    iter = 1e4,
    control = list(adapt_delta = 0.99)
)

And plot the uncertainty about it, this time choosing the median from the posterior to be our value of rhosq (just because):

post <- extract.samples(m3)
( rhosq <- median(post$rhosq) )
[1] 1.031402
# plot the posterior median covariance function
curve(
    exp(-rhosq*x^2),
    from = 0, to = 10,
    xlab = "dstance", ylab = "covariance",
    ylim = c(0, 1),
    lwd = 3
)
# plot 200 functions sampled from posterior
for ( i in 1:200 )
    curve(
        exp(-post$rhosq[i]*x^2),
        col = col.alpha("black", 0.15),
        add = TRUE
    )

Here are the estimated correlations between the values of xtrain:

# compute posterior median covariance among points
K <- covK(xtrain, xtrain, rhosq)
# convert to correlation matrix
Rho <- round(cov2cor(K), 2)
# add row and column names for convenience
colnames(Rho) <- d$x
rownames(Rho) <- d$x
corrplot(Rho, method = "circle")

The model believes that the training data is largely uncorrelated. (To be fair, this is plausible. If we didn’t know the data came from a sine curve, the above plot of xtrain versus ytrain might just seem like noise.)

Because the value of rhosq obtained leads to so little correlation, we actually just end up interpolating the training data. The parameter is also large enough that we can sample from the posterior, so let’s do that. Here’s the result:

n <- 100
xtest <- seq(from = -7, to = 8, length.out = n)
fit <- gpfit(xtrain, ytrain, xtest, rhosq)
num_post_samples <- 1e5
uncorrelated <- matrix(rnorm(num_post_samples*n), nrow = n)
correlated <- fit$L_post %*% uncorrelated
posterior <- apply(correlated, 2, function(x) fit$mean + x)
mu.HPDI <- apply(posterior, 1, HPDI, prob = 0.95)
plot(0, xlim = c(-7, 8), ylim = c(-3, 3), type = "n", xlab = "x", ylab = "y")
shade(mu.HPDI, xtest)
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)
curve(sin(x), lty = "dashed", col = col.alpha("black", 0.5), add = TRUE)
mtext(paste("95% posterior density interval (", num_post_samples, " samples)", sep = ""))

3.1.1 Minimal \(\rho^2\)

Because the value of rhosq we estimated above lead to interpolating the data, we can decrease it further for a smoother interpolation if we’d like.

The minimal rhosq which allows sampling from the posterior is between 0.5 and 1:

gpfit(xtrain, ytrain, xtest, 0.5)$mineigen
[1] -0.5255456
gpfit(xtrain, ytrain, xtest, 1)$mineigen
[1] 0.01

Here’s the fit and posterior it produces:

( rhosq <- minrhosq(xtrain, ytrain, xtest, c(0.5, 1)) )
[1] 0.8313541
fit <- gpfit(xtrain, ytrain, xtest, rhosq + 0.1)    # slightly increase rhosq
correlated <- fit$L_post %*% uncorrelated
posterior <- apply(correlated, 2, function(x) fit$mean + x)
mu.HPDI <- apply(posterior, 1, HPDI, prob = 0.95)
plot(0, xlim = c(-7, 8), ylim = c(-3, 3), type = "n", xlab = "x", ylab = "y")
shade(mu.HPDI, xtest)
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)
curve(sin(x), lty = "dashed", col = col.alpha("black", 0.5), add = TRUE)
mtext(paste("95% posterior density interval (", num_post_samples, " samples)", sep = ""))

The minimal value of rhosq which still interpolates the training data is also between 0.5 and 1:

min(gpfit(xtrain, ytrain, xtest, 0.5)$variance)
[1] -0.05631391
min(gpfit(xtrain, ytrain, xtest, 1)$variance)
[1] 0.02000743

And here’s its fit, using the variance provided by gpfit() to estimate the posterior:

( rhosq <- minrhosq(xtrain, ytrain, xtest, c(0.5, 1), forinterp = TRUE) )
[1] 0.7044019
fit <- gpfit(xtrain, ytrain, xtest, rhosq)
PI <- matrix(nrow = 2, ncol = n)
PI[1,] <- fit$mean - 2*sqrt(fit$variance)
PI[2,] <- fit$mean + 2*sqrt(fit$variance)
plot(0, xlim = c(-7, 8), ylim = c(-3, 3), type = "n")
shade(PI, xtest)
curve(sin(x), lty = "dashed", col = col.alpha("black", 0.5), add = TRUE)
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)

3.2 Smaller distances

Now we’ll use 20 evenly spaced points between -6 and 7 to see how the model behaves.

xtrain <- seq(from = -6, to = 7, length.out = 20)
ytrain <- sin(xtrain)
d <- data.frame(x = as.factor(round(xtrain,2)), y = ytrain)
distances <- as.matrix(dist(xtrain))
colnames(distances) <- d$x
rownames(distances) <- d$x
plot(xtrain, ytrain)

We attempt to learn the Gaussian process parameter from the data:

m4 <- map2stan(
    m1,
    data = list(
        x = d$x,
        y = d$y,
        Dmat = distances
    ),
    warmup = 2e3,
    iter = 1e4,
    control = list(adapt_delta = 0.95)
)

And plot the uncertainty about it:

post <- extract.samples(m4)
( rhosq <- mean(post$rhosq) )
[1] 0.2483101
# plot the posterior mean covariance function
curve(
    exp(-rhosq*x^2),
    from = 0, to = 10,
    xlab = "dstance", ylab = "covariance",
    ylim = c(0, 1),
    lwd = 3
)
# plot 200 functions sampled from posterior
for ( i in 1:200 )
    curve(
        exp(-post$rhosq[i]*x^2),
        col = col.alpha("black", 0.15),
        add = TRUE
    )

Here are the estimated correlations between the values of xtrain:

# compute posterior median covariance among points
K <- covK(xtrain, xtrain, rhosq)
# convert to correlation matrix
Rho <- round(cov2cor(K), 2)
# add row and column names for convenience
colnames(Rho) <- d$x
rownames(Rho) <- d$x
corrplot(Rho, method = "circle")

(Neat!)

The model believes that the training data is highly correlated over short distances. As before, we can draw lines between the training points to visualize the strength of the estimated correlations between them, with darker lines corresponding to higher correlation and lighter lines to less correlation.

# plot the training data
plot(xtrain, ytrain, pch = 16, col = rangi2)
# overlay lines shaded by Rho
for (i in 1:length(xtrain))
    for (j in 1:length(xtrain))
        if (i < j)
            lines(
                c(xtrain[i], xtrain[j]),
                c(ytrain[i], ytrain[j]),
                lwd = 2, col = col.alpha("black", Rho[i,j]^2)
            )

And here’s the computed posterior mean curve:

fit <- gpfit(xtrain, ytrain, xtest, rhosq)
plot(0, xlim = c(-7, 8), ylim = c(-3, 3), type = "n", xlab = "x", ylab = "y")
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)
curve(sin(x), lty = "dashed", col = col.alpha("black", 0.5), add = TRUE)
mtext(paste("95% posterior density interval (", num_post_samples, " samples)", sep = ""))

Well it still kinda blows up over on the right side there. Interesting. I really do think there is another way to compute this curve which doesn’t have this blowup behavior.

3.2.1 Minimal \(\rho^2\)

Just out of curiosity, what’s the minimum value of rhosq which still allows us to interpolate this dense data? It’s between 2 and 4:

min(gpfit(xtrain, ytrain, xtest, 2)$variance)
[1] -0.1787963
min(gpfit(xtrain, ytrain, xtest, 4)$variance)
[1] 0.02179861

And here’s the corresponding posterior mean curve:

( rhosq <- minrhosq(xtrain, ytrain, xtest, c(2, 4), forinterp = TRUE) )
[1] 3.441064
fit <- gpfit(xtrain, ytrain, xtest, rhosq)
plot(0, xlim = c(-7, 8), ylim = c(-3, 3), type = "n")
curve(sin(x), lty = "dashed", col = col.alpha("black", 0.5), add = TRUE)
lines(xtest, fit$mean, col = "red", lwd = 3, lty = "dashed")
points(xtrain, ytrain, pch = 16, col = rangi2, cex = 1.5)


Antonio R. Vargas

21 Sep. 2018

Updated 22 Sep 2018

LS0tDQp0aXRsZTogIkZ1bmN0aW9uIGluZmVyZW5jZSB1c2luZyBHYXVzc2lhbiBwcm9jZXNzZXMiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiBUUlVFDQotLS0NCg0KLS0tLQ0KDQpUaGlzIGlzIGEgY29sbGVjdGlvbiBvZiBteSB0aG91Z2h0cyBvbiBLYXRoZXJpbmUgQmFpbGV5J3MgcG9zdCBbR2F1c3NpYW4gUHJvY2Vzc2VzIGZvciBEdW1taWVzXShodHRwOi8va2F0YmFpbGV5LmdpdGh1Yi5pby9wb3N0L2dhdXNzaWFuLXByb2Nlc3Nlcy1mb3ItZHVtbWllcy8pLiBJJ20ganVzdCBsZWFybmluZyBhYm91dCBHYXVzc2lhbiBwcm9jZXNzZXMgbXlzZWxmLCBhbmQgd2hhdCBmb2xsb3dzIHN1cmVseSByZWZsZWN0cyBzb21lIG9mIG15IGNvbmZ1c2lvbiBhYm91dCBhbGwgdGhpcy4gSW4gdGhlIGZ1dHVyZSBJJ2xsIHJldmlzaXQgdGhlc2Ugbm90ZXMgd2l0aCAoaG9wZWZ1bGx5KSBhIG1vcmUgaW5mb3JtZWQgcGVyc3BlY3RpdmUuDQoNCkl0IHNlZW1zIGxpa2UgQmFpbGV5J3MgcG9zdCBvdXRsaW5lcyBhIG1ldGhvZCBmb3IgaW5mZXJyaW5nIHRoZSB2YWx1ZXMgb2YgYSBmdW5jdGlvbiAkZiQgYWZ0ZXIgbGVhcm5pbmcgYSBoYW5kZnVsIG9mIGl0cyBwb2ludHMuIEknbGwgaW1wbGVtZW50IHRoZSBhbGdvcml0aG0gZGVzY3JpYmVkIGluIHRoZWlyIHBvc3QgKHdoaWNoIGlzIG9yaWdpbmFsbHkgaW4gUHl0aG9uKSBpbiBSLCB0aGVuIGV4cGVyaW1lbnQgYSBiaXQgdG8gdHJ5IHRvIHVuZGVyc3RhbmQgd2hhdCB0byBleHBlY3QgZnJvbSBpdC4NCg0KYGBge3J9DQpsaWJyYXJ5KHJldGhpbmtpbmcpICAgICMgZ2l0aHViLmNvbS9ybWNlbHJlYXRoL3JldGhpbmtpbmcNCmxpYnJhcnkoY29ycnBsb3QpDQpsaWJyYXJ5KG1hdHJpeGNhbGMpDQpgYGANCg0KV2UnbGwgdGFrZSBhIHNldCBvZiBwb2ludHMgb24gYSBzaW5lIGN1cnZlLCAkZih4KSA9IFxzaW4geCQsIGFuZCB1c2UgdGhpcyBhcyBvdXIgKip0cmFpbmluZyBkYXRhKiouIFdlIHN0b3JlIHRoZSB4IHZhbHVlcyBpbiBgeHRyYWluYCwgYW5kIHRoZWlyIGNvcnJlc3BvbmRpbmcgc2luZSB2YWx1ZXMgaW4gYHl0cmFpbmAuDQoNCmBgYHtyfQ0KeHRyYWluIDwtIGMoLTQsIC0zLCAtMiwgLTEsIDEsIDIsIDUpDQp5dHJhaW4gPC0gc2luKHh0cmFpbikNCnBsb3QoeHRyYWluLCB5dHJhaW4pDQpgYGANCg0KVGhpcyB0cmFpbmluZyBkYXRhIHdpbGwgYmUgdXNlZCBmb3IgYWJvdXQgdGhlIGZpcnN0IGhhbGYgb2YgdGhlc2Ugbm90ZXMuDQoNCiMgMSBEaXJlY3QgR2F1c3NpYW4gcHJvY2Vzcw0KDQpTdGFydGluZyB3aXRoIGEgc2V0IG9mIHBvaW50cyAoYHh0cmFpbmAsIGZvciBleGFtcGxlKSwgd2UgY2FuIGltcG9zZSBhIHNldCBvZiBjb3JyZWxhdGlvbnMgb24gdGhvc2UgcG9pbnRzIGJhc2VkIG9uIHRoZWlyIGRpc3RhbmNlcyBmcm9tIGVhY2ggb3RoZXIuIEZvciBvdXIgcHVycG9zZXMsIHdlIHdhbnQgbmVhcmVyIHBvaW50cyB0byBiZSBtb3JlIGNvcnJlbGF0ZWQsIGFuZCBmYXJ0aGVyIHBvaW50cyB0byBiZSBsZXNzIGNvcnJlbGF0ZWQuIFRoaXMgaXMgd2hhdCB3ZSdkIGV4cGVjdCBmcm9tIGEgY29udGludW91cyBmdW5jdGlvbjogbmVhcmJ5IHgtdmFsdWVzIHNob3VsZCBoYXZlIG5lYXJieSB5IHZhbHVlcy4NCg0KV2UnbGwgbGV0IHRoZSBjb3ZhcmlhbmNlIGJldHdlZW4gcG9pbnRzICR4X2kkIGFuZCAkeF9qJCBmYWxsIG9mZiBsaWtlIGEgR2F1c3NpYW4gY3VydmUgYXMgdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlIHBvaW50cyBpbmNyZWFzZXMsIGFzIGluDQoNCiQkDQpcb3BlcmF0b3JuYW1le0Nvdn0oeF9pLHhfaikgPSAgXGV4cFwhXGxlZnQoLVxldGFeMiAoeF9pIC0geF9qKV4yXHJpZ2h0KS4NCiQkDQoNCldlIGNhbiB0d2VhayB0aGUgcGFyYW1ldGVyICRcZXRhXjIkIHRvIGluY3JlYXNlIG9yIGRlY3JlYXNlIHRoZSBkaXN0YW5jZSBvdmVyIHdoaWNoIHBvaW50cyBjb3ZhcnkuDQoNCkhlcmUncyBhbiBSIGZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhpcyBjb3ZhcmlhbmNlIG1hdHJpeCBmb3IgdHdvIGlucHV0IGxpc3RzLCB3aXRoIGEgc21hbGwgYWRqdXN0bWVudCBhbG9uZyB0aGUgZGlhZ29uYWwgdG8gZW5zdXJlIHBvc2l0aXZlIGRlZmluaXRlbmVzczoNCg0KYGBge3J9DQpjb3ZLIDwtIGZ1bmN0aW9uKGEsIGIsIHJob3NxKSB7DQogICAgSyA8LSBvdXRlcihhLCBiLCBmdW5jdGlvbih4LHkpIGV4cCgtcmhvc3EqKHggLSB5KV4yKSkNCiAgICBpZiAobGVuZ3RoKGEpID09IGxlbmd0aChiKSkNCiAgICAgICAgZGlhZyhLKSA8LSAxICsgMC4wMQ0KICAgIHJldHVybihLKQ0KfQ0KYGBgDQoNCklmIHdlIGNvbXB1dGUgdGhlIHRoZSAqQ2hvbGVza3kgZGVjb21wb3NpdGlvbiogb2YgYSBjb3ZhcmlhbmNlIG1hdHJpeCAkXG1hdGhiZiBLJCwNCg0KJCQNClxtYXRoYmYgSyA9IFxtYXRoYmYgTCBcbWF0aGJmIExeVCwNCiQkDQoNCndlIGVuZCB1cCB3aXRoIGEgbWF0cml4ICRcbWF0aGJmIEwkIHdoaWNoIHRha2VzIHVuY29ycmVsYXRlZCBzYW1wbGVzICRcbWF0aGJmIHgkIHRvIGNvcnJlbGF0ZWQgc2FtcGxlcyAkXG1hdGhiZiBMIFxtYXRoYmYgeCQgKHRoaXMgaXMgbWF0cml4IG11bHRpcGxpY2F0aW9uIG9mICRcbWF0aGJmIEwkIGFuZCAkXG1hdGhiZiB4JCkuDQoNCk5vdywgb3VyIGdvYWwgaXMgdG8gZXN0aW1hdGUgdGhlIGZ1bmN0aW9uICRmJCwgYW5kIG91ciBwcmlvciB3aWxsIGJlIHRoYXQgaXRzIHZhbHVlcyBhcmUgbm9ybWFsbHkgZGlzdHJpYnV0ZWQgd2l0aCBtZWFuIDAgYW5kIHZhcmlhbmNlIDEgYW5kIGNvcnJlbGF0ZWQgYWNjb3JkaW5nIHRvICRcbWF0aGJmIEskLiBUbyBnZXQgYSBmZXcgc2FtcGxlIGZ1bmN0aW9ucyBmcm9tIHRoaXMgcHJpb3IsIHdlIGdlbmVyYXRlIHN0YW5kYXJkIG5vcm1hbCBub2lzZSBvbiB0aGUgaW50ZXJ2YWwgd2UncmUgaW50ZXJlc3RlZCBpbiwgdGhlbiBnaXZlIGl0IHRoZSBkZXNpcmVkIGNvcnJlbGF0aW9uIGJ5IG11bHRpcGx5aW5nIGl0IGJ5IHRoZSBhcHByb3ByaWF0ZSBDaG9sZXNreSBmYWN0b3IgJFxtYXRoYmYgTCQuDQoNCk91ciAidGVzdCIgcG9pbnRzLCB3aGljaCBpcyBqdXN0IGEgZ3JpZCBvZiAkeCQtdmFsdWVzIHdoZXJlIHdlIHdhbnQgdG8gZXN0aW1hdGUgJGYoeCkkLCB3aWxsIGJlIGJlIDEwMCBlcXVhbGx5IHNwYWNlZCBwb2ludHMgcmFuZ2luZyBmcm9tIC01IHRvIDUuIFdlIGNvbXB1dGUgdGhlIGNvdmFyaWFuY2UgbWF0cml4IGNvcnJlc3BvbmRpbmcgdG8gZGlzdGFuY2VzIGJldHdlZW4gdGhlc2UgcG9pbnRzIHVzaW5nIGBjb3ZLKClgIGFuZCBpdHMgQ2hvbGVza3kgZmFjdG9yIHVzaW5nIGBjaG9sKClgLCBnZW5lcmF0ZSAzIHNldHMgb2Ygc3RhbmRhcmQgbm9ybWFsIG5vaXNlIGF0IHRoZXNlIHBvaW50cywgbXVsdGlwbHkgdGhlbSBlYWNoIGJ5IHRoZSBDaG9sZXNreSBmYWN0b3IsIGFuZCBwbG90IHRoZSByZXN1bHRzLg0KDQpgYGB7cn0NCm4gPC0gMTAwDQp4dGVzdCA8LSBzZXEoZnJvbSA9IC01LCB0byA9IDUsIGxlbmd0aC5vdXQgPSBuKQ0Kcmhvc3EgPC0gNSAgICAjIEJhaWxleSdzIHBhcmFtZXRlciBjaG9pY2UNCg0KIyBjb21wdXRlIGNvcnJlbGF0aW9uIG1hdHJpeCBhbmQgQ2hvbGVza3kgZmFjdG9yDQpLX3NzIDwtIGNvdksoeHRlc3QsIHh0ZXN0LCByaG9zcSkNCkxfc3MgPC0gY2hvbChLX3NzKQ0KDQojIHRha2UgdGhyZWUgc2FtcGxlcyBvZiBHYXVzc2lhbiBub2lzZSBmcm9tIHRoZSBwb3N0ZXJpb3INCiMgICBhbmQgY29ycmVsYXRlIGVhY2ggb25lIGJ5IG11bHRpcGx5aW5nIGl0IGFnYWluc3QgTF9zcw0KbnVtX3Bvc3Rfc2FtcGxlcyA8LSAzDQp1bmNvcnJlbGF0ZWQgPC0gbWF0cml4KHJub3JtKG51bV9wb3N0X3NhbXBsZXMqbiksIG5yb3cgPSBuKQ0KY29ycmVsYXRlZCA8LSBMX3NzICUqJSB1bmNvcnJlbGF0ZWQNCg0KIyBwbG90IHRoZSB0aHJlZSBzYW1wbGVzDQpwbG90KDAsIHhsaW0gPSBjKC01LDUpLCB5bGltID0gYygtMywzKSwgdHlwZSA9ICJuIiwgeGxhYiA9ICJ4IiwgeWxhYiA9ICJ5IikNCmNvbCA9IGMoImJsYWNrIiwgInJlZCIsICJibHVlIikNCmZvciAoaSBpbiAxOm51bV9wb3N0X3NhbXBsZXMpDQogICAgbGluZXMoeHRlc3QsIGNvcnJlbGF0ZWRbLGldLCBjb2wgPSBjb2xbaV0pDQptdGV4dCgiVGhyZWUgc2FtcGxlcyBmcm9tIHRoZSBwcmlvciIpDQpgYGANCg0KU29tZSBvZiB0aGUgdmFyaWFibGUgbmFtZXMgaGVyZSBoYXZlIGJlZW4gY2hvc2VuIHRvIG1hdGNoIHRoZSBvbmVzIGluIEJhaWxleSdzIGNvZGUuDQoNCkluIHRoZSBhYm92ZSBwbG90LCBub3RpY2UgdGhhdCB0aGUgcmVzdWx0aW5nIGN1cnZlcyBhcmUgbm8gbG9uZ2VyIGp1c3QgcHVyZSBub2lzZS4gVGhlaXIgdmFsdWVzIG5vdyBjb3ZhcnkgYWNjb3JkaW5nIHRvIGEgZGVjYXlpbmcgZXhwb25lbnRpYWwgb2YgdGhlaXIgc3F1YXJlZCBkaXN0YW5jZXM7IGlmIG9uZSB4LXZhbHVlIGhhcyBhIGhpZ2ggeS12YWx1ZSwgdGhlIG5lYXJieSB4LXZhbHVlcyB3aWxsIGFsc28gaGF2ZSBoaWdoIHktdmFsdWVzLCBidXQgeC12YWx1ZXMgZmFydGhlciBhd2F5IGRvbid0IGZlZWwgdGhlIHNhbWUgcHVsbCB1cHdhcmQuIEFueSB4LXZhbHVlcyBmYXJ0aGVyIHRoYW4gMSB1bml0IGFwYXJ0IGVzc2VudGlhbGx5IGRvbid0IGNvcnJlbGF0ZSBhdCBhbGwuDQoNCkRldGVybWluYXRpb24gb2YgdGhlIHBvc3RlcmlvciBtZWFuIGF0IGVhY2ggYHh0ZXN0YCAoaW4gb3RoZXIgd29yZHMsIG91ciBndWVzcyBmb3IgdGhlIHZhbHVlIG9mICRmJCBhdCBlYWNoIGB4dGVzdGApIGFuZCB0aGUgcG9zdGVyaW9yIGNvcnJlbGF0b3IgbWF0cml4IGNhbiBiZSBkb25lIHVzaW5nIG1hdHJpeCBhbGdlYnJhLiBJIGhhdmVuJ3QgbG9va2VkIGF0IHRoZSByZWZlcmVuY2UgZ2l2ZW4gYnkgQmFpbGV5IChNdXJwaHkncyAqTWFjaGluZSBMZWFybmluZzogQSBQcm9iYWJsaXN0aWMgUGVyc3BlY3RpdmUqKSBmb3IgdGhpcyBjYWxjdWxhdGlvbiB5ZXQsIHNvIEkgY2FuJ3Qgc2F5IEkgdW5kZXJzdGFuZCBpdCB5ZXQuIEJ1dCBJIGhhdmUgZG9uZSBteSBiZXN0IHRvIGFjY3VyYXRlbHkgaW1wbGVtZW50IGl0IGluIFIsIGJhc2VkIG9uIHRoZSBQeXRob24gY29kZSBnaXZlbiBieSBCYWlsZXkuDQoNCkkgd3JvdGUgYSBmdW5jdGlvbiBgZ3BmaXRgIHRvIGNhbGN1bGF0ZSB0aGUgcG9zdGVyaW9yIG1lYW4sIHZhcmlhbmNlLCBhbmQgcG9zdGVyaW9yIGNvcnJlbGF0b3IgbWF0cml4IGdpdmVuIGEgc2V0IG9mIHRyYWluaW5nIHgtdmFsdWVzIGB4dHJhaW5gIGFuZCB0aGVpciBjb3JyZXNwb25kaW5nIHktdmFsdWVzIGB5dHJhaW5gLCBhIHNldCBvZiB0ZXN0IHgtdmFsdWVzIGB4dGVzdGAsIGFuZCBhIGZpdCBwYXJhbWV0ZXIgYHJob3NxYC4gVGhlIGZ1bmN0aW9uIGFsc28gcmV0dXJucyB0aGUgc21hbGxlc3QgZWlnZW52YWx1ZSBvZiBhIG1hdHJpeCBmcm9tIGFuIGludGVybWVkaWF0ZSBzdGVwIG9mIHRoZSBjYWxjdWxhdGlvbi0tLXRoaXMgd2lsbCBiZSB1c2VkIFtsYXRlcl0oI21pbnJob3NxKSB0byBkaWZmZXJlbnRpYXRlIGJldHdlZW4gZGlmZmVyZW50IHBvc3NpYmxlIHZhbHVlcyBvZiB0aGUgcGFyYW1ldGVyICRccmhvXjIkLg0KDQpgYGB7cn0NCiMgdGhpcyBmdW5jdGlvbiByZXF1aXJlcyBjb3ZLKCkgZGVmaW5lZCBhYm92ZQ0KZ3BmaXQgPC0gZnVuY3Rpb24oeHRyYWluLCB5dHJhaW4sIHh0ZXN0LCByaG9zcSkgew0KICAgICMgY29tcHV0ZSBwb3N0ZXJpb3IgbWVhbg0KICAgIEsgPC0gY292Syh4dHJhaW4sIHh0cmFpbiwgcmhvc3EpDQogICAgTCA8LSBjaG9sKEspDQogICAgS19zIDwtIGNvdksoeHRyYWluLCB4dGVzdCwgcmhvc3EpDQogICAgTGsgPC0gc29sdmUoTCwgS19zKQ0KICAgIFggPC0gc29sdmUoTCwgeXRyYWluKQ0KICAgIG11IDwtIHQoTGspICUqJSBYDQogICAgDQogICAgIyBjb21wdXRlIHBvc3RlcmlvciB2YXJpYW5jZQ0KICAgIEtfc3MgPC0gY292Syh4dGVzdCwgeHRlc3QsIHJob3NxKQ0KICAgIHZhcmlhbmNlIDwtIGRpYWcoS19zcykgLSBhcHBseShMa14yLCAyLCBzdW0pDQogICAgDQogICAgIyBjb21wdXRlIHBvc3RlcmlvciBjb3JyZWxhdG9yIG1hdHJpeCBpZiBpdCdzIHdlbGwtZGVmaW5lZA0KICAgIEtfc3MgPC0gY292Syh4dGVzdCwgeHRlc3QsIHJob3NxKQ0KICAgIEEgPC0gS19zcyAtIHQoTGspICUqJSBMaw0KICAgIG1pbmVpZ2VuID0gbWluKGVpZ2VuKEEsIHN5bW1ldHJpYyA9IFRSVUUsIG9ubHkudmFsdWVzID0gVFJVRSkkdmFsdWVzKQ0KICAgIGlmIChtaW5laWdlbiA+IDApICAjIGlzIEEgcG9zaXRpdmUgZGVmaW5pdGU/DQogICAgICAgIExfcG9zdCA8LSBjaG9sKEEpDQogICAgZWxzZQ0KICAgICAgICBMX3Bvc3QgPC0gTlVMTA0KICAgIA0KICAgIHJldHVybihsaXN0KG1lYW4gPSBtdSwgdmFyaWFuY2UgPSB2YXJpYW5jZSwgTF9wb3N0ID0gTF9wb3N0LCBtaW5laWdlbiA9IG1pbmVpZ2VuKSkNCn0NCmBgYA0KDQpJbiB0aGUgZm9sbG93aW5nIGNvZGUgd2UgdXNlIGBncGZpdCgpYCB0byBjb21wdXRlIHRoZSBHYXVzc2lhbiBwcm9jZXNzIGZpdCwgdGhlbiBwbG90IHRoZSBwb3N0ZXJpb3IgbWVhbiBmaXQgY3VydmUgYW5kIHNoYWRlIHRoZSA5NSUgcG9zdGVyaW9yIGRlbnNpdHkgaW50ZXJ2YWwgZm9yIHRoZSBtZWFuIChhcyBkZXRlcm1pbmVkIGJ5IHRoZSB2YXJpYW5jZSByZXR1cm5lZCBieSBgZ3BmaXQoKWApLg0KDQpgYGB7cn0NCmZpdCA8LSBncGZpdCh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIHJob3NxKQ0KUEkgPC0gbWF0cml4KG5yb3cgPSAyLCBuY29sID0gbikNClBJWzEsXSA8LSBmaXQkbWVhbiAtIDIqc3FydChmaXQkdmFyaWFuY2UpDQpQSVsyLF0gPC0gZml0JG1lYW4gKyAyKnNxcnQoZml0JHZhcmlhbmNlKQ0KcGxvdCgwLCB4bGltID0gYygtNSwgNSksIHlsaW0gPSBjKC0zLCAzKSwgdHlwZSA9ICJuIiwgeGxhYiA9ICJ4IiwgeWxhYiA9ICJ5IikNCnNoYWRlKFBJLCB4dGVzdCkNCmxpbmVzKHh0ZXN0LCBmaXQkbWVhbiwgY29sID0gInJlZCIsIGx3ZCA9IDMsIGx0eSA9ICJkYXNoZWQiKQ0KcG9pbnRzKHh0cmFpbiwgeXRyYWluLCBwY2ggPSAxNiwgY29sID0gcmFuZ2kyLCBjZXggPSAxLjUpDQpgYGANCg0KQXMgYGdwZml0KClgIGFsc28gcmV0dXJucyB0aGUgcG9zdGVyaW9yIGNvcnJlbGF0b3IgbWF0cml4ICRcbWF0aGJmIEwkLCB3ZSBjYW4gc2FtcGxlIGEgZml0IGN1cnZlIGZyb20gdGhlIHBvc3RlcmlvciBkaXN0cmlidXRpb24gb2YgY3VydmVzIGJ5IGdlbmVyYXRpbmcgc3RhbmRhcmQgR2F1c3NpYW4gbm9pc2UgJFxtYXRoYmYgeCQgdGhlbiB0cmFuc2Zvcm1pbmcgaXQgdmlhICRcbWF0aGJmIHhfe1x0ZXh0e3Bvc3R9fSA9IFxtYXRoYmYgXG11ICsgXG1hdGhiZiBMIFxtYXRoYmYgeCQuIEluIHRoZSBmb2xsb3dpbmcgY29kZSB3ZSBzYW1wbGUgdGhyZWUgY3VydmVzIGZyb20gdGhlIHBvc3RlcmlvciBhbmQgaW5jbHVkZSB0aGVtIGluIHRoZSBwcmV2aW91cyBwbG90Lg0KDQpgYGB7cn0NCm51bV9wb3N0X3NhbXBsZXMgPC0gMw0KdW5jb3JyZWxhdGVkIDwtIG1hdHJpeChybm9ybShudW1fcG9zdF9zYW1wbGVzKm4pLCBucm93ID0gbikNCmNvcnJlbGF0ZWQgPC0gZml0JExfcG9zdCAlKiUgdW5jb3JyZWxhdGVkDQpwbG90KDAsIHhsaW0gPSBjKC01LCA1KSwgeWxpbSA9IGMoLTMsIDMpLCB0eXBlID0gIm4iLCB4bGFiID0gIngiLCB5bGFiID0gInkiKQ0Kc2hhZGUoUEksIHh0ZXN0KQ0KY29sID0gYygiZ3JheTIiLCAicmVkIiwgcmFuZ2kyKQ0KZm9yIChpIGluIDE6bnVtX3Bvc3Rfc2FtcGxlcykNCiAgICBsaW5lcyh4dGVzdCwgZml0JG1lYW4gKyBjb3JyZWxhdGVkWyxpXSwgY29sID0gY29sW2ldKSAgICAjIG11ICsgTHgNCmxpbmVzKHh0ZXN0LCBmaXQkbWVhbiwgY29sID0gInJlZCIsIGx3ZCA9IDMsIGx0eSA9ICJkYXNoZWQiKQ0KcG9pbnRzKHh0cmFpbiwgeXRyYWluLCBwY2ggPSAxNiwgY29sID0gcmFuZ2kyLCBjZXggPSAxLjUpDQptdGV4dCgiVGhyZWUgc2FtcGxlcyBmcm9tIHRoZSBwb3N0ZXJpb3IiKQ0KYGBgDQoNCkxldCdzIHRyeSB0byBnZXQgYSBiZXR0ZXIgcGljdHVyZSBvZiB0aGUgcG9zdGVyaW9yIHRocm91Z2ggc2FtcGxpbmcuIFdlJ2xsIHNhbXBsZSAxMDAsMDAwIGN1cnZlcyBmcm9tIHRoZSBwb3N0ZXJpb3IsIHRoZW4gcGxvdCB0aGUgZmlyc3QgMTUwIG9mIHRoZW0gYmVoaW5kIHRoZSBwb3N0ZXJpb3IgbWVhbiBjdXJ2ZS4NCg0KYGBge3J9DQpudW1fcG9zdF9zYW1wbGVzIDwtIDFlNQ0KdW5jb3JyZWxhdGVkIDwtIG1hdHJpeChybm9ybShudW1fcG9zdF9zYW1wbGVzKm4pLCBucm93ID0gbikNCmNvcnJlbGF0ZWQgPC0gZml0JExfcG9zdCAlKiUgdW5jb3JyZWxhdGVkDQpwbG90KDAsIHhsaW0gPSBjKC01LCA1KSwgeWxpbSA9IGMoLTMsIDMpLCB0eXBlID0gIm4iLCB4bGFiID0gIngiLCB5bGFiID0gInkiKQ0KZm9yIChpIGluIDE6MTUwKQ0KICAgIGxpbmVzKHh0ZXN0LCBmaXQkbWVhbiArIGNvcnJlbGF0ZWRbLGldLCBjb2wgPSBjb2wuYWxwaGEoImJsYWNrIiwgMC4xNSkpICAgICMgbXUgKyBMeA0KbGluZXMoeHRlc3QsIGZpdCRtZWFuLCBjb2wgPSAicmVkIiwgbHdkID0gMywgbHR5ID0gImRhc2hlZCIpDQpwb2ludHMoeHRyYWluLCB5dHJhaW4sIHBjaCA9IDE2LCBjb2wgPSByYW5naTIsIGNleCA9IDEuNSkNCm10ZXh0KHBhc3RlKCIxNTAgc2FtcGxlcyBmcm9tIHRoZSBwb3N0ZXJpb3IiKSkNCmBgYA0KDQpUaGVzZSBzZWVtIHRvIGZvbGxvdyBhIGRpZmZlcmVudCBkaXN0cmlidXRpb24gdGhhbiB0aGUgcmVnaW9ucyB0aGF0IHdlcmUgc2hhZGVkIGFjY29yZGluZyB0byB0aGUgdmFyaWFuY2UgYWJvdmUuIFVzaW5nIGFsbCAxMDAsMDAwIHNhbXBsZSBjdXJ2ZXMsIHdlJ2xsIGNvbXB1dGUgYW5kIHBsb3QgdGhlIGVtcGlyaWNhbCA5NSUgaW50ZXJ2YWxzIGltcGxpZWQgYnkgdGhlIHNhbXBsZS4NCg0KYGBge3J9DQpwb3N0ZXJpb3IgPC0gYXBwbHkoY29ycmVsYXRlZCwgMiwgZnVuY3Rpb24oeCkgZml0JG1lYW4gKyB4KSAgICAjIG11ICsgTHgNCm11LkhQREkgPC0gYXBwbHkocG9zdGVyaW9yLCAxLCBIUERJLCBwcm9iID0gMC45NSkNCnBsb3QoMCwgeGxpbSA9IGMoLTUsIDUpLCB5bGltID0gYygtMywgMyksIHR5cGUgPSAibiIsIHhsYWIgPSAieCIsIHlsYWIgPSAieSIpDQpzaGFkZShtdS5IUERJLCB4dGVzdCkNCmxpbmVzKHh0ZXN0LCBmaXQkbWVhbiwgY29sID0gInJlZCIsIGx3ZCA9IDMsIGx0eSA9ICJkYXNoZWQiKQ0KcG9pbnRzKHh0cmFpbiwgeXRyYWluLCBwY2ggPSAxNiwgY29sID0gcmFuZ2kyLCBjZXggPSAxLjUpDQptdGV4dChwYXN0ZSgiOTUlIHBvc3RlcmlvciBkZW5zaXR5IGludGVydmFsICgiLCBudW1fcG9zdF9zYW1wbGVzLCAiIHNhbXBsZXMpIiwgc2VwID0gIiIpKQ0KYGBgDQoNCldoaWxlIHRoZXJlIGlzIHNvbWUgc2ltaWxhcml0eSB0byB0aGUgcmVnaW9uIGRlZmluZWQgYnkgdGhlIHZhcmlhbmNlLCB0aGlzIHJlZ2lvbiBpc24ndCBuZWFybHkgYXMgcmVndWxhciB0aGUgZWFybGllciBvbmUuIEFuZCB0aGlzIGlzbid0IGR1ZSB0byByYW5kb20gc2FtcGxpbmctLS13ZSd2ZSB0YWtlbiBlbm91Z2ggc2FtcGxlcyB0byBnZXQgYSBnb29kIGFwcHJveGltYXRpb24uIFlvdSBjYW4gdHJ5IHJ1bm5pbmcgdGhlIGNvZGUgYWJvdmUgc2V2ZXJhbCB0aW1lcyAob3IgdGFraW5nIG1hbnkgbW9yZSBzYW1wbGVzIGlmIHlvdXIgY29tcHV0ZSBjYW4gaGFuZGxlIGl0KSB0byBiZSBzdXJlLg0KDQojIyAxLjEgVGhlIHBhcmFtZXRlciAkXHJob14yJA0KDQpJbiB0aGUgYWJvdmUgY2FsY3VsYXRpb25zIHdlIHRvb2sgJFxyaG9eMiA9IDUkIHRvIG1hdGNoIHRoZSBQeXRob24gY29kZSB3cml0dGVuIGJ5IEJhaWxleS4gVGhpcyBwYXJhbWV0ZXIgbWVhc3VyZXMgaG93ICJzbW9vdGgiIHRoZSBpbnRlcnBvbGF0aW5nIGN1cnZlIGlzLCB3aXRoIGEgc21hbGxlciAkXHJob14yJCB5aWVsZGluZyBhIHNtb290aGVyIGN1cnZlLiBUcnkgZGVjcmVhc2luZyBpdCB0byA0IGFuZCBydW5uaW5nIHRoZSBjb2RlLCBhbmQgdGhlbiB0byAzIGFuZCBydW5uaW5nIGl0IGFnYWluLiBCdXQgZGVjcmVhc2UgaXQgdG9vIGxvdyAodHJ5ICRccmhvXjIgPSAyJCkgYW5kIHlvdSdsbCBnZXQgYW4gZXJyb3Igd2hlbiB5b3UgdHJ5IHRvIHNhbXBsZSBjdXJ2ZXMgZnJvbSB0aGUgcG9zdGVyaW9yLiBIb3dldmVyLCB0aGUgcG9zdGVyaW9yIG1lYW4gY3VydmUgaXMgY29tcHV0ZWQganVzdCBmaW5lLiBUaGF0J3Mgb2RkLg0KDQpUaGUgYWJvdmUgY29kZSBkb2Vzbid0IHJlYWxseSBpbGx1c3RyYXRlIGl0IHdlbGwsIGJ1dCBpZiB5b3UgZGVjcmVhc2UgaXQgKmV2ZW4gZnVydGhlciosIHNheSB0byAkXHJob14yID0gMS41JCwgdGhlbiB0aGUgcG9zdGVyaW9yICJ2YXJpYW5jZSIgY29tcHV0ZWQgYnkgYGdwZml0YCBiZWNvbWVzICoqbmVnYXRpdmUqKiBhbmQgdGhlIHBvc3RlcmlvciBtZWFuIGZhaWxzIHRvIGludGVycG9sYXRlIHRoZSB0cmFpbmluZyBwb2ludHMgKGJ1dCBtYXliZSB0aGF0J3MgYSBnb29kIHRoaW5nLS0td2UnbGwgZXhwbG9yZSB0aGlzIGluIFtzZWN0aW9uIDJdKCNsZWFybmZyb21kYXRhKSkuIFN0cmFuZ2UgYmVoYXZpb3IgaW5kZWVkLg0KDQpJIHN1c3BlY3QgdGhlIHJlYXNvbnMgZm9yIHRoaXMgYXJlIGJ1cmllZCBpbiBhc3N1bXB0aW9ucyBiZWhpbmQgdGhlIHNwZWNpZmljIHN0ZXBzIGluIHRoZSBtYXRyaXggYWxnZWJyYS4gSXQncyBwb3NzaWJsZSB0aGF0IHRoZSBzdGVwcyBjb3VsZCBiZSBhbHRlcmVkIHRvIHN0aWxsIGdpdmUgcmVzdWx0cyBpbiB0aGVzZSBjYXNlcywgYnV0IEkgaGF2ZW4ndCBsb29rZWQgYXQgaXQgeWV0Lg0KDQojIyAxLjIgRGV0ZXJtaW5pbmcgdGhlIG1pbmltYWwgdmFsdWVzIG9mICRccmhvXjIkIHsjbWlucmhvc3F9DQoNClVzaW5nIHRoZSBgdmFyaWFuY2VgIGFuZCBgbWluZWlnZW5gIHZhbHVlcyByZXR1cm5lZCBieSBgZ3BmaXQoKWAgd2UgY2FuIGRldGVybWluZSB0aGUgbWluaW11bSB2YWx1ZXMgb2YgdGhlIHBhcmFtZXRlciAkXHJob14yJCB3aGljaCB5aWVsZCBnb29kIGJlaGF2aW9ycyBpbiBzYW1wbGluZyBmcm9tIHRoZSBwb3N0ZXJpb3IgYW5kIGNvbXB1dGluZyB0aGUgcG9zdGVyaW9yIG1lYW4gY3VydmUuDQoNCiMjIyAxLjIuMSBUaGUgc21hbGxlc3QgJFxyaG9eMiQgdG8gc2FtcGxlIGZyb20gdGhlIHBvc3Rlcmlvcg0KDQpUaGUgZnVuY3Rpb24gYG1pbnJob3NxKClgIGJlbG93IHVzZXMgYSByb290LWZpbmRpbmcgYWxnb3JpdGhtIHRvIGRldGVybWluZSB0aGUgc21hbGxlc3QgYWRtaXNzaWJsZSB2YWx1ZSBvZiAkXHJob14yJCBmb3IgYSBnaXZlbiB0cmFpbmluZyBzZXQgYW5kIHRlc3Qgc2V0LiBXZSBwYXNzIGl0IHRoZXNlIHRyYWluaW5nIGFuZCB0ZXN0IHNldHMsIGFsb25nIHdpdGggYW4gaW50ZXJ2YWwgd2hpY2ggd2Uga25vdyBjb250YWlucyB0aGUgZGVzaXJlZCB2YWx1ZSBvZiAkXHJob14yJC4NCg0KQnkgZGVmYXVsdCwgYG1pbnJob3NxKClgIGZpbmRzIHRoZSBzbWFsbGVzdCB2YWx1ZSBvZiBgcmhvc3FgIHdpdGggd2hpY2ggd2UgY2FuIHN0aWxsIHNhbXBsZSBmcm9tIHRoZSBwb3N0ZXJpb3IgdXNpbmcgJFxtYXRoYmYgTCQuIElmIHdlIGxvb2sgY2xvc2VseSBhdCBob3cgdGhlIG1hdHJpeCAkXG1hdGhiZiBMJCBpcyBjb21wdXRlZCwgd2Ugc2VlIHRoYXQgaXQgd29uJ3QgZXhpc3Qgd2hlbiB0aGUgdmFsdWUgb2YgYG1pbmVpZ2VuYCBpcyBuZWdhdGl2ZSAoYmVjYXVzZSB0aGVuIHRoZSBtYXRyaXggd2hpY2ggaXMgdXNlZCB0byBjb25zdHJ1Y3QgJFxtYXRoYmYgTCQgaXNuJ3QgcG9zaXRpdmUgZGVmaW5pdGUpLCBhbmQgaXQgd2lsbCBleGlzdCBpZiBgbWluZWlnZW5gIGlzIHBvc2l0aXZlLiBXZSBmaW5kIHRoZSBkZXNpcmVkIHZhbHVlIG9mIGByaG9zcWAgYnkgZmluZGluZyB3aGVyZSB0aGlzIGBtaW5laWdlbmAgdHJhbnNpdGlvbnMgZnJvbSBwb3NpdGl2ZSB0byBuZWdhdGl2ZS4NCg0KYGBge3J9DQptaW5yaG9zcSA8LSBmdW5jdGlvbih4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIGludGVydmFsLCBmb3JpbnRlcnAgPSBGQUxTRSkgew0KICAgIGlmICghZm9yaW50ZXJwKSB7DQogICAgICAgICMgZmluZCB0aGUgc21hbGxlc3QgdmFsdWUgb2Ygcmhvc3Egd2hpY2ggZ2l2ZXMgcG9zaXRpdmUgbWluZWlnZW4NCiAgICAgICAgcm9vdCA8LSB1bmlyb290KA0KICAgICAgICAgICAgZnVuY3Rpb24oeCkgZ3BmaXQoeHRyYWluLCB5dHJhaW4sIHh0ZXN0LCB4KSRtaW5laWdlbiwNCiAgICAgICAgICAgIGludGVydmFsDQogICAgICAgICkNCiAgICB9IGVsc2Ugew0KICAgICAgICAjIGZpbmQgdGhlIHNtYWxsZXN0IHZhbHVlIG9mIHJob3NxIHdoaWNoIGdpdmVzIHBvc2l0aXZlIHZhcmlhbmNlDQogICAgICAgIHJvb3QgPC0gdW5pcm9vdCgNCiAgICAgICAgICAgIGZ1bmN0aW9uKHgpIG1pbihncGZpdCh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIHgpJHZhcmlhbmNlKSwNCiAgICAgICAgICAgIGludGVydmFsDQogICAgICAgICkNCiAgICB9DQogICAgIyAkZi5yb290IGlzIHRoZSB2YWx1ZSBvZiB0aGUgZnVuY3Rpb24gYXQgJHJvb3QNCiAgICAjIC0tIElmICRmLnJvb3QgaXMgbmVnYXRpdmUsIHJldHVybiAkcm9vdCArIChyb290IHByZWNpc2lvbikgYXMgcmhvc3EgaW5zdGVhZA0KICAgIGlmIChyb290JGYucm9vdCA+IDApDQogICAgICAgIHJldHVybihyb290JHJvb3QpDQogICAgZWxzZQ0KICAgICAgICByZXR1cm4ocm9vdCRyb290ICsgcm9vdCRlc3RpbS5wcmVjKQ0KfQ0KYGBgDQoNCldlIGtub3cgdGhhdCB0aGUgbWluaW1hbCAkXHJob14yJCBmb3Igd2hpY2ggJFxtYXRoYmYgTCQgZXhpc3RzIGlzIHNvbWV3aGVyZSBiZXR3ZWVuIDIgYW5kIDMuIE5vdGUgdGhhdCBgbWluZWlnZW5gIGlzIG5lZ2F0aXZlIHdoZW4gYHJob3NxYCBpcyAyIGFuZCBwb3NpdGl2ZSB3aGVuIGl0J3MgMzoNCg0KYGBge3J9DQpncGZpdCh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIDIpJG1pbmVpZ2VuDQpncGZpdCh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIDMpJG1pbmVpZ2VuDQpgYGANCg0KSW4gdGhlIGZvbGxvd2luZyBjb2RlIHdlIHVzZSBgbWlucmhvc3EoKWAgdG8gZmluZCB0aGlzIHZhbHVlIG9mIGByaG9zcWAgYmV0d2VlbiAyIGFuZCAzLCB0aGVuIHBsdWcgaXQgaW50byBgZ3BmaXQoKWAgYW5kIHBsb3QgdGhlIHBvc3RlcmlvciBtZWFuIGN1cnZlIGFuZCB0aGUgOTUlIHBvc3RlcmlvciBkZW5zaXR5IGludGVydmFsLg0KDQpgYGB7cn0NCnJob3NxIDwtIG1pbnJob3NxKHh0cmFpbiwgeXRyYWluLCB4dGVzdCwgYygyLDMpKQ0KZml0IDwtIGdwZml0KHh0cmFpbiwgeXRyYWluLCB4dGVzdCwgcmhvc3EpDQpudW1fcG9zdF9zYW1wbGVzIDwtIDFlNQ0KdW5jb3JyZWxhdGVkIDwtIG1hdHJpeChybm9ybShudW1fcG9zdF9zYW1wbGVzKm4pLCBucm93ID0gbikNCmNvcnJlbGF0ZWQgPC0gZml0JExfcG9zdCAlKiUgdW5jb3JyZWxhdGVkDQpwb3N0ZXJpb3IgPC0gYXBwbHkoY29ycmVsYXRlZCwgMiwgZnVuY3Rpb24oeCkgZml0JG1lYW4gKyB4KQ0KbXUuSFBESSA8LSBhcHBseShwb3N0ZXJpb3IsIDEsIEhQREksIHByb2IgPSAwLjk1KQ0KcGxvdCgwLCB4bGltID0gYygtNSwgNSksIHlsaW0gPSBjKC0zLCAzKSwgdHlwZSA9ICJuIiwgeGxhYiA9ICJ4IiwgeWxhYiA9ICJ5IikNCnNoYWRlKG11LkhQREksIHh0ZXN0KQ0KbGluZXMoeHRlc3QsIGZpdCRtZWFuLCBjb2wgPSAicmVkIiwgbHdkID0gMywgbHR5ID0gImRhc2hlZCIpDQpwb2ludHMoeHRyYWluLCB5dHJhaW4sIHBjaCA9IDE2LCBjb2wgPSByYW5naTIsIGNleCA9IDEuNSkNCm10ZXh0KHBhc3RlKCI5NSUgcG9zdGVyaW9yIGRlbnNpdHkgaW50ZXJ2YWwgKCIsIG51bV9wb3N0X3NhbXBsZXMsICIgc2FtcGxlcykiLCBzZXAgPSAiIikpDQpgYGANCg0KV2UncmUgZ2V0dGluZyBzb21lIGludGVyZXN0aW5nIGFydGlmYWN0cyBmcm9tIHRoZSBwb3N0ZXJpb3Igc2FtcGxlcy4gSSB0aGluayB0aGVzZSBhcmUgb2NjdXJyaW5nIGJlY2F1c2Ugd2UncmUgcmlnaHQgb24gdGhlIGJvcmRlciBvZiB0aGUgcG9zdGVyaW9yIGNvcnJlbGF0b3IgJFxtYXRoYmYgTCQgYnJlYWtpbmcgZG93bi4gSWYgd2UgaW5jcmVhc2UgYHJob3NxYCBzbGlnaHRseSwgdGhlc2Ugc2hvdWxkIGdvIGF3YXk6DQoNCmBgYHtyfQ0KZml0IDwtIGdwZml0KHh0cmFpbiwgeXRyYWluLCB4dGVzdCwgcmhvc3EgKyAwLjEpICAgICMgc2xpZ2h0bHkgaW5jcmVhc2Ugcmhvc3ENCmNvcnJlbGF0ZWQgPC0gZml0JExfcG9zdCAlKiUgdW5jb3JyZWxhdGVkDQpwb3N0ZXJpb3IgPC0gYXBwbHkoY29ycmVsYXRlZCwgMiwgZnVuY3Rpb24oeCkgZml0JG1lYW4gKyB4KQ0KbXUuSFBESSA8LSBhcHBseShwb3N0ZXJpb3IsIDEsIEhQREksIHByb2IgPSAwLjk1KQ0KcGxvdCgwLCB4bGltID0gYygtNSwgNSksIHlsaW0gPSBjKC0zLCAzKSwgdHlwZSA9ICJuIiwgeGxhYiA9ICJ4IiwgeWxhYiA9ICJ5IikNCnNoYWRlKG11LkhQREksIHh0ZXN0KQ0KbGluZXMoeHRlc3QsIGZpdCRtZWFuLCBjb2wgPSAicmVkIiwgbHdkID0gMywgbHR5ID0gImRhc2hlZCIpDQpwb2ludHMoeHRyYWluLCB5dHJhaW4sIHBjaCA9IDE2LCBjb2wgPSByYW5naTIsIGNleCA9IDEuNSkNCm10ZXh0KHBhc3RlKCI5NSUgcG9zdGVyaW9yIGRlbnNpdHkgaW50ZXJ2YWwgKCIsIG51bV9wb3N0X3NhbXBsZXMsICIgc2FtcGxlcykiLCBzZXAgPSAiIikpDQpgYGANCg0KDQojIyMgMS4yLjIgVGhlIHNtYWxsZXN0ICRccmhvXjIkIHRvIGludGVycG9sYXRlIHRoZSB0cmFpbmluZyBwb2ludHMNCg0KSWYgd2UgZGVjcmVhc2UgYHJob3NxYCBzbGlnaHRseSBiZWxvdyB0aGUgdmFsdWUgb2J0YWluZWQgaW4gdGhlIGxhc3Qgc2VjdGlvbiwgd2UgY2FuIG5vIGxvbmdlciBzYW1wbGUgZnJvbSB0aGUgcG9zdGVyaW9yLiBIb3dldmVyLCB3ZSBjYW4gc3RpbGwgY29tcHV0ZSB0aGUgcG9zdGVyaW9yIG1lYW4gY3VydmUgYW5kIGVzdGltYXRlIHRoZSBwb3N0ZXJpb3IgZGVuc2l0eSBpbnRlcnZhbCB1c2luZyB0aGUgdmFyaWFuY2UgcmV0dXJuZWQgYnkgYGdwZml0KClgLg0KDQpCdXQgdGhlcmUgaXMgYW5vdGhlciBtaW5pbXVtIG5vdywgbG93ZXIgdGhhbiB0aGUgcHJldmlvdXMgb25lLCBiZXlvbmQgd2hpY2ggdGhlIHBvc3RlcmlvciBtZWFuIGN1cnZlIG5vIGxvbmdlciBpbnRlcnBvbGF0ZXMgdGhlIHRyYWluaW5nIHBvaW50cy4gV2UgY2FuIGVzdGltYXRlIHRoaXMgbWluaW11bSBieSBzZWVpbmcgd2hpY2ggdmFsdWVzIG9mIGByaG9zcWAgcHJvZHVjZSBhIG5lZ2F0aXZlIHZhcmlhbmNlOg0KDQpgYGB7cn0NCm1pbihncGZpdCh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIDEpJHZhcmlhbmNlKQ0KbWluKGdwZml0KHh0cmFpbiwgeXRyYWluLCB4dGVzdCwgMikkdmFyaWFuY2UpDQpgYGANCg0KU28sIHdlIGtub3cgdGhhdCB0aGUgbWluaW1hbCBgcmhvc3FgIGluIHRoaXMgY2FzZSBpcyBiZXR3ZWVuIDEgYW5kIDIuIE5vdyB3ZSBjYWxsIGBtaW5yaG9zcSgpYCwgcGFzc2luZyBpdCB0aGUgaW50ZXJ2YWwgKDEsMikgYW5kIHRoZSBzd2l0Y2ggYGZvcmludGVycCA9IFRSVUVgLg0KDQpVbmZvcnR1bmF0ZWx5IG5vdyB3ZSBjYW4ndCBzYW1wbGUgZnJvbSB0aGUgcG9zdGVyaW9yIGJlY2F1c2UgdGhlIHBvc3RlcmlvciBjb3JyZWxhdG9yIG1hdHJpeCAoYXQgbGVhc3QgYXMgaXQgaXMgZGVmaW5lZCBoZXJlLCB1c2luZyB0aGUgQ2hvbGVza3kgZGVjb21wb3NpdGlvbikgZG9lc24ndCBleGlzdC4gQXQgbGVhc3Qgd2UgY2FuIHN0aWxsIHVzZSB0aGUgdmFyaWFuY2UgcmV0dXJuZWQgYnkgYGdwZml0KClgIHRvIGVzdGltYXRlIHRoZSBwb3N0ZXJpb3IgZGVuc2l0eSBpbnRlcnZhbHMuDQoNCmBgYHtyfQ0Kcmhvc3EgPC0gbWlucmhvc3EoeHRyYWluLCB5dHJhaW4sIHh0ZXN0LCBjKDEsMiksIGZvcmludGVycCA9IFRSVUUpDQpmaXQgPC0gZ3BmaXQoeHRyYWluLCB5dHJhaW4sIHh0ZXN0LCByaG9zcSkNClBJIDwtIG1hdHJpeChucm93ID0gMiwgbmNvbCA9IG4pDQpQSVsxLF0gPC0gZml0JG1lYW4gLSAyKnNxcnQoZml0JHZhcmlhbmNlKQ0KUElbMixdIDwtIGZpdCRtZWFuICsgMipzcXJ0KGZpdCR2YXJpYW5jZSkNCnBsb3QoMCwgeGxpbSA9IGMoLTUsIDUpLCB5bGltID0gYygtMywgMyksIHR5cGUgPSAibiIpDQpzaGFkZShQSSwgeHRlc3QpDQpsaW5lcyh4dGVzdCwgZml0JG1lYW4sIGNvbCA9ICJyZWQiLCBsd2QgPSAzLCBsdHkgPSAiZGFzaGVkIikNCnBvaW50cyh4dHJhaW4sIHl0cmFpbiwgcGNoID0gMTYsIGNvbCA9IHJhbmdpMiwgY2V4ID0gMS41KQ0KYGBgDQoNClRoaXMgaXMgdGhlIHNtb290aGVzdCBjdXJ2ZSBzbyBmYXIhDQoNCklmIHdlIGRlY3JlYXNlIGByaG9zcWAgYW55IGZhcnRoZXIsIHRoZSBwb3N0ZXJpb3IgbWVhbiBjdXJ2ZSB3aWxsIG5vIGxvbmdlciBpbnRlcnBvbGF0ZSB0aGUgdHJhaW5pbmcgcG9pbnRzLiAqQnV0IGlzIHRoaXMgcmVhbGx5IHNvIGJhZD8qIFdlJ2xsIGV4cGxvcmUgdGhpcyBuZXh0Lg0KDQojIDIgTGVhcm5pbmcgdGhlIEdhdXNzaWFuIHByb2Nlc3MgcGFyYW1ldGVyIGZyb20gdGhlIGRhdGEgeyNsZWFybmZyb21kYXRhfQ0KDQpJZiB3ZSB3YW50ZWQgdG8sIHdlIGNvdWxkIGVudGVydGFpbiB0aGUgaWRlYSB0aGF0IG91ciB0cmFpbmluZyBwb2ludHMgaGF2ZSBzb21lIG1lYXN1cmVtZW50IGVycm9yIGluIHRoZW0uIEluIHRoYXQgY2FzZSBpbnRlcnBvbGF0aW5nIHRoZSBwb2ludHMgd291bGQgYmUgYmxhdGFudCBvdmVyZml0dGluZyBvZiB0aGUgbW9kZWwuDQoNCkluIHRoaXMgc2VjdGlvbiB3ZSdsbCBleHBsb3JlIGhvdyB0byBkZXRlcm1pbmUgdGhlIHBhcmFtZXRlciBgcmhvc3FgIGZyb20gdGhlIHRyYWluaW5nIGRhdGEgaXRzZWxmLiBXZSdsbCBkbyB0aGlzIGJ5IGNyZWF0aW5nIGEgbW9kZWwgd2hpY2ggbGVhcm5zIHRoZSBkaXN0YW5jZXMgYmV0d2VlbiB0aGUgcG9pbnRzIG9mIGB4dHJhaW5gIHdoaWNoIGhhdmUgY29ycmVsYXRlZCB5LXZhbHVlcyBpbiBgeXRyYWluYCwgYWNjb3JkaW5nIHRvIHRoZSBHYXVzc2lhbiBrZXJuZWwNCg0KJCQNClxvcGVyYXRvcm5hbWV7Q292fSh4X2kseF9qKSA9ICBcZXhwXCFcbGVmdCgtXGV0YV4yICh4X2kgLSB4X2opXjJccmlnaHQpDQokJA0KDQp0aGF0IHdlIHVzZWQgYWJvdmUuDQoNCkZpcnN0IHdlIGNvbXB1dGUgdGhlIG1hdHJpeCBvZiBkaXN0YW5jZXMgYmV0d2VlbiB0aGUgeC12YWx1ZXMgb2Ygb3VyIHRyYWluaW5nIGRhdGEgYW5kIHB1dCB0aGUgdHJhaW5pbmcgZGF0YSBpbnRvIGEgZGF0YSBmcmFtZSBmb3IgY29udmVuaWVuY2UuDQoNCmBgYHtyfQ0KZCA8LSBkYXRhLmZyYW1lKHggPSBhcy5mYWN0b3IoeHRyYWluKSwgeSA9IHl0cmFpbikNCmRpc3RhbmNlcyA8LSBhcy5tYXRyaXgoZGlzdCh4dHJhaW4pKQ0KY29sbmFtZXMoZGlzdGFuY2VzKSA8LSBkJHgNCnJvd25hbWVzKGRpc3RhbmNlcykgPC0gZCR4DQpkaXN0YW5jZXMNCmBgYA0KDQpJbiBvdXIgbW9kZWwsIHdlJ2xsIHN1cHBvc2UgdGhhdCB0aGUgbWVhc3VyZW1lbnQgZXJyb3Igb24gb3VyIHRyYWluaW5nIHBvaW50cyBpcyBub3JtYWxseSBkaXN0cmlidXRlZCB3aXRoIG1lYW4gMCBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIDAuMS4gV2UnbGwgZml0IHRoZSBtb2RlbCB1c2luZyBgbWFwMnN0YW5gIGZyb20gdGhlIGByZXRoaW5raW5nYCBwYWNrYWdlLiBBIGdvb2Qgb3ZlcnZpZXcgb2YgaG93IHRoaXMgd29ya3MgY2FuIGJlIGZvdW5kIGluIE1jRWxyZWF0aCdzIGJvb2sgKlN0YXRpc3RpY2FsIFJldGhpbmtpbmc6IEEgQmF5ZXNpYW4gQ291cnNlKiwgY2hhcHRlciAxMyBzZWN0aW9uIDQuDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KbTEgPC0gbWFwMnN0YW4oDQogICAgYWxpc3QoDQogICAgICAgIHkgfiBkbm9ybShtdSwgMC4xKSwNCiAgICAgICAgbXUgPC0gZ1t4XSwNCiAgICAgICAgZ1t4XSB+IEdQTDIoRG1hdCwgMSwgcmhvc3EsIDAuMDEpLA0KICAgICAgICByaG9zcSB+IGRnYW1tYSgxLjUsIDEpDQogICAgKSwNCiAgICBkYXRhID0gbGlzdCgNCiAgICAgICAgeCA9IGQkeCwNCiAgICAgICAgeSA9IGQkeSwNCiAgICAgICAgRG1hdCA9IGRpc3RhbmNlcw0KICAgICksDQogICAgd2FybXVwID0gMmUzLA0KICAgIGl0ZXIgPSAxZTQsDQogICAgY29udHJvbCA9IGxpc3QoYWRhcHRfZGVsdGEgPSAwLjk1KQ0KKQ0KYGBgDQoNClRha2UgYSBsb29rIGF0IHRoZSBtZWFuIHZhbHVlIG9mIGByaG9zcWAgZXN0aW1hdGVkIGJ5IHRoZSBtb2RlbC4gVGhlIGBwcmVjaXMoKWAgZnVuY3Rpb24gYWxzbyByZXR1cm5zIHNvbWUgaW5mb3JtYXRpb24gYWJvdXQgdGhlIHBvc3RlcmlvciBkZW5zaXR5IG9mIGByaG9zcWAsIGluY2x1ZGluZyBpdHMgc3RhbmRhcmQgZGV2aWF0aW9uIGFuZCA4OSUgaGlnaGVzdCBwb3N0ZXJpb3IgZGVuc2l0eSBpbnRlcnZhbC4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpwcmVjaXMobTEpDQpgYGANCg0KVG8gZ2V0IGFuIGlkZWEgb2YgdGhlIG1vZGVsJ3MgdW5jZXJ0YWludHkgYWJvdXQgYHJob3NxYCwgbGV0J3MgdGFrZSAyMDAgc2FtcGxlcyBmcm9tIGl0cyBwb3N0ZXJpb3IgYW5kIHBsb3QgdGhlaXIgY29ycmVzcG9uZGluZyBkaXN0YW5jZSBjb3JyZWxhdGlvbiBmdW5jdGlvbnMuIFRoZSBtZWFuIGNvcnJlbGF0aW9uIGZ1bmN0aW9uIGlzIHNob3duIGFzIGEgdGhpY2sgc29saWQgbGluZS4NCg0KYGBge3J9DQpwb3N0IDwtIGV4dHJhY3Quc2FtcGxlcyhtMSkNCnJob3NxIDwtIG1lYW4ocG9zdCRyaG9zcSkNCg0KIyBwbG90IHRoZSBwb3N0ZXJpb3IgbWVhbiBjb3ZhcmlhbmNlIGZ1bmN0aW9uDQpjdXJ2ZSgNCiAgICBleHAoLW1lYW4ocG9zdCRyaG9zcSkqeF4yKSwNCiAgICBmcm9tID0gMCwgdG8gPSAxMCwNCiAgICB4bGFiID0gImRzdGFuY2UiLCB5bGFiID0gImNvdmFyaWFuY2UiLA0KICAgIHlsaW0gPSBjKDAsIDEpLA0KICAgIGx3ZCA9IDMNCikNCg0KIyBwbG90IDIwMCBmdW5jdGlvbnMgc2FtcGxlZCBmcm9tIHBvc3Rlcmlvcg0KZm9yICggaSBpbiAxOjIwMCApDQogICAgY3VydmUoDQogICAgICAgIGV4cCgtcG9zdCRyaG9zcVtpXSp4XjIpLA0KICAgICAgICBjb2wgPSBjb2wuYWxwaGEoImJsYWNrIiwgMC4xNyksDQogICAgICAgIGFkZCA9IFRSVUUNCiAgICApDQpgYGANCg0KVGhlIG1vZGVsIGlzIGRlZmluaXRlbHkgdW5jZXJ0YWluIGFib3V0IHRoZSB2YWx1ZSBvZiBgcmhvc3FgLCBidXQgbGV0J3Mgc2VlIHdoYXQga2luZCBvZiByZXN1bHQgdGhlIG1lYW4gZXN0aW1hdGUgZ2l2ZXMgdXMuDQoNCkhlcmUncyBhIHZpc3VhbGl6YXRpb24gb2YgdGhlIGNvcnJlbGF0aW9uIG1hdHJpeCBiZXR3ZWVuIHRoZSB2YWx1ZXMgb2YgYHh0cmFpbmAuIE5vdGUgdGhhdCBlYWNoIHggaXMgbW9kZXJhdGVseSBjb3JyZWxhdGVkIHdpdGggdGhlIG90aGVyIHZhbHVlcyBkaXN0YW5jZSAxIGF3YXksIGFuZCB0aGVyZSBpcyBhIG1pbmlzY3VsZSBjb3JyZWxhdGlvbiB3aXRoIHZhbHVlcyBkaXN0YW5jZSAyIGF3YXkuIEJleW9uZCB0aGF0IHRoZXJlIGlzIG5vIGRpc2Nlcm5hYmxlIGNvcnJlbGF0aW9uLg0KDQpgYGB7cn0NCiMgY29tcHV0ZSBwb3N0ZXJpb3IgbWVhbiBjb3ZhcmlhbmNlIGFtb25nIHBvaW50cw0KSyA8LSBjb3ZLKHh0cmFpbiwgeHRyYWluLCByaG9zcSkNCiMgY29udmVydCB0byBjb3JyZWxhdGlvbiBtYXRyaXgNClJobyA8LSByb3VuZChjb3YyY29yKEspLCAyKQ0KIyBhZGQgcm93IGFuZCBjb2x1bW4gbmFtZXMgZm9yIGNvbnZlbmllbmNlDQpjb2xuYW1lcyhSaG8pIDwtIGQkeA0Kcm93bmFtZXMoUmhvKSA8LSBjb2xuYW1lcyhSaG8pDQpjb3JycGxvdChSaG8sIG1ldGhvZCA9ICJjaXJjbGUiKQ0KYGBgDQoNClBlcmhhcHMgYSBiZXR0ZXIgd2F5IHRvIHNlZSB0aGlzIGlzIGJ5IGRyYXdpbmcgbGluZXMgYmV0d2VlbiB0aGUgdHJhaW5pbmcgcG9pbnRzIGNvcnJlc3BvbmRpbmcgdG8gdGhlIHN0cmVuZ3RoIG9mIHRoZWlyIGNvcnJlbGF0aW9uLg0KDQpgYGB7cn0NCnBsb3QoeHRyYWluLCB5dHJhaW4sIHBjaCA9IDE2LCBjb2wgPSByYW5naTIsIGNleCA9IDEuNSkNCiMgb3ZlcmxheSBsaW5lcyBzaGFkZWQgYnkgUmhvDQpmb3IgKGkgaW4gMTpsZW5ndGgoeHRyYWluKSkNCiAgICBmb3IgKGogaW4gMTpsZW5ndGgoeHRyYWluKSkNCiAgICAgICAgaWYgKGkgPCBqKQ0KICAgICAgICAgICAgbGluZXMoDQogICAgICAgICAgICAgICAgYyh4dHJhaW5baV0sIHh0cmFpbltqXSksDQogICAgICAgICAgICAgICAgYyh5dHJhaW5baV0sIHl0cmFpbltqXSksDQogICAgICAgICAgICAgICAgbHdkID0gMiwgY29sID0gY29sLmFscGhhKCJibGFjayIsIFJob1tpLGpdXjIpDQogICAgICAgICAgICApDQpgYGANCg0KU28gdGhlcmUgaXMgc29tZSBleHBlY3RlZCBjb3JyZWxhdGlvbiBiZXR3ZWVuIHRoZSBmb3VyIHBvaW50cyBvbiB0aGUgbGVmdCwgYW5kIGJldHdlZW4gdGhlIHR3byBwb2ludHMgaW4gdGhlIG1pZGRsZSwganVzdCBiYXNlZCBvbiB0aGVpciBob3Jpem9udGFsIHByb3hpbWl0eS4NCg0KRmluYWxseSB3ZSdsbCBwbG90IHRoZSBpbmZlcnJlZCBwb3N0ZXJpb3IgbWVhbiBjdXJ2ZSB1c2luZyB0aGUgbWVhbiB2YWx1ZSBvZiBgcmhvc3FgIGZyb20gdGhlIG1vZGVsLiBUaGUgInRydWUiIHNpbmUgY3VydmUgaXMgc2hvd24gaW4gYmxhY2suDQoNCmBgYHtyfQ0KZml0IDwtIGdwZml0KHh0cmFpbiwgeXRyYWluLCB4dGVzdCwgcmhvc3EpDQpwbG90KDAsIHhsaW0gPSBjKC01LCA1KSwgeWxpbSA9IGMoLTMsIDMpLCB0eXBlID0gIm4iKQ0KbGluZXMoeHRlc3QsIGZpdCRtZWFuLCBjb2wgPSAicmVkIiwgbHdkID0gMywgbHR5ID0gImRhc2hlZCIpDQpwb2ludHMoeHRyYWluLCB5dHJhaW4sIHBjaCA9IDE2LCBjb2wgPSByYW5naTIsIGNleCA9IDEuNSkNCmN1cnZlKHNpbih4KSwgbHR5ID0gImRhc2hlZCIsIGNvbCA9IGNvbC5hbHBoYSgiYmxhY2siLCAwLjUpLCBhZGQgPSBUUlVFKQ0KYGBgDQoNCg0KIyMgMi4xIExpbWl0YXRpb25zDQoNClRvIGJlIGhvbmVzdCwgSSdtIG51dCBzdXJlIGlmIHRoZSBiZWhhdmlvciBzaG93biBhYm92ZSBpcyBpbnRlbmRlZCBieSB0aGUgY29tcHV0YXRpb25zIHRoYXQgZ28gaW50byBjcmVhdGluZyB0aGUgcG9zdGVyaW9yIG1lYW4gZml0IGN1cnZlLiBJdCBhY3R1YWxseSB3b3JrcyBwcmV0dHkgd2VsbCBpbiB0aGF0IGV4YW1wbGUsIGJ1dCBsZXQncyBleHBsb3JlIGFuIGV4YW1wbGUgd2hlcmUgaXQgZG9lc24ndCBkbyBzbyB3ZWxsLg0KDQpJbnN0ZWFkIG9mIHRoZSBuaWNlLCB3aWRlbHktc3BhY2VkIHRyYWluaW5nIHNldCB3ZSd2ZSBiZWVuIHVzaW5nIHVwIHRvIHRoaXMgcG9pbnQsIHdlJ2xsIHVzZSBhIHRyYWluaW5nIHNldCBjb25zaXN0aW5nIG9mIGEgcmFuZG9tIHNhbXBsZSBvZiAxMCB4LXZhbHVlcyBiZXR3ZWVuIC01IGFuZCA1Lg0KDQpgYGB7cn0NCnNldC5zZWVkKDUpDQp4dHJhaW4gPC0gcnVuaWYoNywgLTUsIDUpDQp5dHJhaW4gPC0gc2luKHh0cmFpbikNCnBsb3QoeHRyYWluLCB5dHJhaW4pDQpkIDwtIGRhdGEuZnJhbWUoeCA9IGFzLmZhY3Rvcihyb3VuZCh4dHJhaW4sMikpLCB5ID0geXRyYWluKQ0KZGlzdGFuY2VzIDwtIGFzLm1hdHJpeChkaXN0KHh0cmFpbikpDQpjb2xuYW1lcyhkaXN0YW5jZXMpIDwtIGQkeA0Kcm93bmFtZXMoZGlzdGFuY2VzKSA8LSBkJHgNCmBgYA0KDQpOb3cgd2hhdCBwYXJhbWV0ZXIgZG9lcyB0aGUgbW9kZWwgbGVhcm4gZnJvbSB0aGUgZGF0YT8NCg0KYGBge3IgbWVzc2FnZSA9IEZBTFNFfQ0KbTIgPC0gbWFwMnN0YW4oDQogICAgbTEsDQogICAgZGF0YSA9IGxpc3QoDQogICAgICAgIHggPSBkJHgsDQogICAgICAgIHkgPSBkJHksDQogICAgICAgIERtYXQgPSBkaXN0YW5jZXMNCiAgICApLA0KICAgIHdhcm11cCA9IDJlMywNCiAgICBpdGVyID0gMWU0LA0KICAgIGNvbnRyb2wgPSBsaXN0KGFkYXB0X2RlbHRhID0gMC45NSkNCikNCmBgYA0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRX0NCnByZWNpcyhtMikNCmBgYA0KDQpUaGUgbWVhbiBgcmhvc3FgIGlzIG11Y2ggbGFyZ2VyLCBzbyB0aGUgbW9kZWwgb25seSBleHBlY3QgcG9pbnRzIHdoaWNoIGFyZSB2ZXJ5IGNsb3NlIHRvZ2V0aGVyIHRvIGNvcnJlbGF0ZS4gQXMgYmVmb3JlLCBsZXQncyBwbG90IGEgcGljdHVyZSBvZiB0aGUgdW5jZXJ0YWludHkgYXJvdW5kIHRoZSBtb2RlbCdzIGVzdGltYXRlIG9mIGByaG9zcWAuDQoNCmBgYHtyfQ0KcG9zdCA8LSBleHRyYWN0LnNhbXBsZXMobTIpDQpyaG9zcSA8LSBtZWFuKHBvc3Qkcmhvc3EpDQoNCiMgcGxvdCB0aGUgcG9zdGVyaW9yIG1lYW4gY292YXJpYW5jZSBmdW5jdGlvbg0KY3VydmUoDQogICAgZXhwKC1yaG9zcSp4XjIpLA0KICAgIGZyb20gPSAwLCB0byA9IDEwLA0KICAgIHhsYWIgPSAiZHN0YW5jZSIsIHlsYWIgPSAiY292YXJpYW5jZSIsDQogICAgeWxpbSA9IGMoMCwgMSksDQogICAgbHdkID0gMw0KKQ0KDQojIHBsb3QgMjAwIGZ1bmN0aW9ucyBzYW1wbGVkIGZyb20gcG9zdGVyaW9yDQpmb3IgKCBpIGluIDE6MjAwICkNCiAgICBjdXJ2ZSgNCiAgICAgICAgZXhwKC1wb3N0JHJob3NxW2ldKnheMiksDQogICAgICAgIGNvbCA9IGNvbC5hbHBoYSgiYmxhY2siLCAwLjA4KSwNCiAgICAgICAgYWRkID0gVFJVRQ0KICAgICkNCmBgYA0KDQpUaGUgbW9kZWwgaXMgYWN0dWFsbHkgKnZlcnkgY2VydGFpbiogYWJvdXQgdGhpcyB2YWx1ZSBvZiBgcmhvc3FgLiBUaGlzIGlzIGJlY2F1c2UgaXQgbm93IGhhcyBhIG11Y2ggd2lkZXIgdmFyaWV0eSBvZiBkaXN0YW5jZXMgdG8gbGVhcm4gZnJvbS4gSGVyZSdzIHdoYXQgdGhlIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHRoZSBuZXcgdHJhaW5pbmcgZGF0YSBsb29rcyBsaWtlOg0KDQpgYGB7cn0NCiMgY29tcHV0ZSBwb3N0ZXJpb3IgbWVhbiBjb3ZhcmlhbmNlIGFtb25nIHBvaW50cw0KSyA8LSBjb3ZLKHh0cmFpbiwgeHRyYWluLCByaG9zcSkNCiMgY29udmVydCB0byBjb3JyZWxhdGlvbiBtYXRyaXgNClJobyA8LSByb3VuZChjb3YyY29yKEspLCAyKQ0KIyBhZGQgcm93IGFuZCBjb2x1bW4gbmFtZXMgZm9yIGNvbnZlbmllbmNlDQpjb2xuYW1lcyhSaG8pIDwtIGQkeA0Kcm93bmFtZXMoUmhvKSA8LSBjb2xuYW1lcyhSaG8pDQojIHBsb3QgdGhlIHRyYWluaW5nIGRhdGENCnBsb3QoeHRyYWluLCB5dHJhaW4sIHBjaCA9IDE2LCBjb2wgPSByYW5naTIpDQojIG92ZXJsYXkgbGluZXMgc2hhZGVkIGJ5IFJobw0KZm9yIChpIGluIDE6bGVuZ3RoKHh0cmFpbikpDQogICAgZm9yIChqIGluIDE6bGVuZ3RoKHh0cmFpbikpDQogICAgICAgIGlmIChpIDwgaikNCiAgICAgICAgICAgIGxpbmVzKA0KICAgICAgICAgICAgICAgIGMoeHRyYWluW2ldLCB4dHJhaW5bal0pLA0KICAgICAgICAgICAgICAgIGMoeXRyYWluW2ldLCB5dHJhaW5bal0pLA0KICAgICAgICAgICAgICAgIGx3ZCA9IDIsIGNvbCA9IGNvbC5hbHBoYSgiYmxhY2siLCBSaG9baSxqXV4yKQ0KICAgICAgICAgICAgKQ0KYGBgDQoNClByZXR0eSBtdWNoIHRoZSBvbmx5IHBvaW50cyB0aGF0IGFyZSBjb3JyZWxhdGVkIGFyZSB0aGUgdHdvIG5lYXIgeD0yLg0KDQpCdXQgbm93IHNvbWV0aGluZyBzdHJhbmdlIGhhcHBlbnMgd2hlbiB3ZSB1c2UgYGdwZml0KClgIHRvIGNvbXB1dGUgdGhlIHBvc3RlcmlvciBtZWFuIGZpdCBjdXJ2ZSB0byB0aGlzIG5ldyBkYXRhLg0KDQpgYGB7cn0NCmZpdCA8LSBncGZpdCh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIHJob3NxKQ0KcGxvdCgwLCB4bGltID0gYygtNSwgNSksIHlsaW0gPSBjKC0zLCAzKSwgdHlwZSA9ICJuIikNCmxpbmVzKHh0ZXN0LCBmaXQkbWVhbiwgY29sID0gInJlZCIsIGx3ZCA9IDMsIGx0eSA9ICJkYXNoZWQiKQ0KcG9pbnRzKHh0cmFpbiwgeXRyYWluLCBwY2ggPSAxNiwgY29sID0gcmFuZ2kyLCBjZXggPSAxLjUpDQpjdXJ2ZShzaW4oeCksIGx0eSA9ICJkYXNoZWQiLCBjb2wgPSBjb2wuYWxwaGEoImJsYWNrIiwgMC41KSwgYWRkID0gVFJVRSkNCmBgYA0KDQpBbGwgb2YgdGhlIHBvaW50cyAqKmV4Y2VwdCoqIHRoZSB0d28gbmVhciB4PTIgd2VyZSBpbnRlcnBvbGF0ZWQsIGFuZCBuZWFyIHRob3NlIHBvaW50cyB0aGUgcG9zdGVyaW9yIG1lYW4gY3VydmUgYmxldyB1cC4NCg0KSSByZWFsbHkgc3VzcGVjdCB0aGF0IHRoaXMga2luZCBvZiB0aGluZyBjb3VsZCBiZSBhY2NvdW50ZWQgZm9yIGluIHRoZSBtYXRyaXggYWxnZWJyYSBpbiBgZ3BmaXQoKWAuIEknZCByZWFsbHkgbGlrZSB0byByZXZpc2l0IHRoaXMgZXhhbXBsZSBvbmNlIEkgdW5kZXJzdGFuZCB0aGF0IGNhbGN1bGF0aW9uIGEgYml0IG1vcmUuDQoNCiMgMyBFdmVubHkgc3BhY2VkIHRlc3QgcG9pbnRzDQoNCk9mIGNvdXJzZSwgbm90IGFsbCBpcyBsb3N0LiBJbiB0aGlzIHNlY3Rpb24gd2UnbGwgc2hvdyB0aGF0IG91ciBtb2RlbCBiZWhhdmVzIGZhaXJseSB3ZWxsIGZvciBldmVubHkgc3BhY2VkIHRyYWluaW5nIGRhdGEsIHByb3ZpZGVkIHRoZSB4LXZhbHVlcyBhcmUgZmFyIGVub3VnaCBhcGFydC4NCg0KIyMgMy4xIExhcmdlIGRpc3RhbmNlcw0KDQpIZXJlIG91ciB0cmFpbmluZyBkYXRhIHdpbGwgY29uc2lzdCBvZiA5IGV2ZW5seSBzcGFjZWQgcG9pbnRzIGZyb20gLTYgdG8gNy4NCg0KYGBge3J9DQp4dHJhaW4gPC0gc2VxKGZyb20gPSAtNiwgdG8gPSA3LCBsZW5ndGgub3V0ID0gOSkNCnl0cmFpbiA8LSBzaW4oeHRyYWluKQ0KZCA8LSBkYXRhLmZyYW1lKHggPSBhcy5mYWN0b3Iocm91bmQoeHRyYWluLDIpKSwgeSA9IHl0cmFpbikNCmRpc3RhbmNlcyA8LSBhcy5tYXRyaXgoZGlzdCh4dHJhaW4pKQ0KY29sbmFtZXMoZGlzdGFuY2VzKSA8LSBkJHgNCnJvd25hbWVzKGRpc3RhbmNlcykgPC0gZCR4DQpwbG90KHh0cmFpbiwgeXRyYWluKQ0KYGBgDQoNCldlIGF0dGVtcHQgdG8gbGVhcm4gdGhlIEdhdXNzaWFuIHByb2Nlc3MgcGFyYW1ldGVyIGZyb20gdGhlIGRhdGE6DQoNCmBgYHtyIG1lc3NhZ2UgPSBGQUxTRX0NCm0zIDwtIG1hcDJzdGFuKA0KICAgIG0xLA0KICAgIGRhdGEgPSBsaXN0KA0KICAgICAgICB4ID0gZCR4LA0KICAgICAgICB5ID0gZCR5LA0KICAgICAgICBEbWF0ID0gZGlzdGFuY2VzDQogICAgKSwNCiAgICB3YXJtdXAgPSAyZTMsDQogICAgaXRlciA9IDFlNCwNCiAgICBjb250cm9sID0gbGlzdChhZGFwdF9kZWx0YSA9IDAuOTkpDQopDQpgYGANCg0KQW5kIHBsb3QgdGhlIHVuY2VydGFpbnR5IGFib3V0IGl0LCB0aGlzIHRpbWUgY2hvb3NpbmcgdGhlICoqbWVkaWFuKiogZnJvbSB0aGUgcG9zdGVyaW9yIHRvIGJlIG91ciB2YWx1ZSBvZiBgcmhvc3FgIChqdXN0IGJlY2F1c2UpOg0KDQpgYGB7cn0NCnBvc3QgPC0gZXh0cmFjdC5zYW1wbGVzKG0zKQ0KKCByaG9zcSA8LSBtZWRpYW4ocG9zdCRyaG9zcSkgKQ0KDQojIHBsb3QgdGhlIHBvc3RlcmlvciBtZWRpYW4gY292YXJpYW5jZSBmdW5jdGlvbg0KY3VydmUoDQogICAgZXhwKC1yaG9zcSp4XjIpLA0KICAgIGZyb20gPSAwLCB0byA9IDEwLA0KICAgIHhsYWIgPSAiZHN0YW5jZSIsIHlsYWIgPSAiY292YXJpYW5jZSIsDQogICAgeWxpbSA9IGMoMCwgMSksDQogICAgbHdkID0gMw0KKQ0KDQojIHBsb3QgMjAwIGZ1bmN0aW9ucyBzYW1wbGVkIGZyb20gcG9zdGVyaW9yDQpmb3IgKCBpIGluIDE6MjAwICkNCiAgICBjdXJ2ZSgNCiAgICAgICAgZXhwKC1wb3N0JHJob3NxW2ldKnheMiksDQogICAgICAgIGNvbCA9IGNvbC5hbHBoYSgiYmxhY2siLCAwLjE1KSwNCiAgICAgICAgYWRkID0gVFJVRQ0KICAgICkNCmBgYA0KDQpIZXJlIGFyZSB0aGUgZXN0aW1hdGVkIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHRoZSB2YWx1ZXMgb2YgYHh0cmFpbmA6DQoNCmBgYHtyfQ0KIyBjb21wdXRlIHBvc3RlcmlvciBtZWRpYW4gY292YXJpYW5jZSBhbW9uZyBwb2ludHMNCksgPC0gY292Syh4dHJhaW4sIHh0cmFpbiwgcmhvc3EpDQojIGNvbnZlcnQgdG8gY29ycmVsYXRpb24gbWF0cml4DQpSaG8gPC0gcm91bmQoY292MmNvcihLKSwgMikNCiMgYWRkIHJvdyBhbmQgY29sdW1uIG5hbWVzIGZvciBjb252ZW5pZW5jZQ0KY29sbmFtZXMoUmhvKSA8LSBkJHgNCnJvd25hbWVzKFJobykgPC0gZCR4DQpjb3JycGxvdChSaG8sIG1ldGhvZCA9ICJjaXJjbGUiKQ0KYGBgDQoNClRoZSBtb2RlbCBiZWxpZXZlcyB0aGF0IHRoZSB0cmFpbmluZyBkYXRhIGlzIGxhcmdlbHkgdW5jb3JyZWxhdGVkLiAoVG8gYmUgZmFpciwgdGhpcyBpcyBwbGF1c2libGUuIElmIHdlIGRpZG4ndCBrbm93IHRoZSBkYXRhIGNhbWUgZnJvbSBhIHNpbmUgY3VydmUsIHRoZSBhYm92ZSBwbG90IG9mIGB4dHJhaW5gIHZlcnN1cyBgeXRyYWluYCBtaWdodCBqdXN0IHNlZW0gbGlrZSBub2lzZS4pDQoNCkJlY2F1c2UgdGhlIHZhbHVlIG9mIGByaG9zcWAgb2J0YWluZWQgbGVhZHMgdG8gc28gbGl0dGxlIGNvcnJlbGF0aW9uLCB3ZSBhY3R1YWxseSBqdXN0IGVuZCB1cCBpbnRlcnBvbGF0aW5nIHRoZSB0cmFpbmluZyBkYXRhLiBUaGUgcGFyYW1ldGVyIGlzIGFsc28gbGFyZ2UgZW5vdWdoIHRoYXQgd2UgY2FuIHNhbXBsZSBmcm9tIHRoZSBwb3N0ZXJpb3IsIHNvIGxldCdzIGRvIHRoYXQuIEhlcmUncyB0aGUgcmVzdWx0Og0KDQpgYGB7cn0NCm4gPC0gMTAwDQp4dGVzdCA8LSBzZXEoZnJvbSA9IC03LCB0byA9IDgsIGxlbmd0aC5vdXQgPSBuKQ0KZml0IDwtIGdwZml0KHh0cmFpbiwgeXRyYWluLCB4dGVzdCwgcmhvc3EpDQpudW1fcG9zdF9zYW1wbGVzIDwtIDFlNQ0KdW5jb3JyZWxhdGVkIDwtIG1hdHJpeChybm9ybShudW1fcG9zdF9zYW1wbGVzKm4pLCBucm93ID0gbikNCmNvcnJlbGF0ZWQgPC0gZml0JExfcG9zdCAlKiUgdW5jb3JyZWxhdGVkDQpwb3N0ZXJpb3IgPC0gYXBwbHkoY29ycmVsYXRlZCwgMiwgZnVuY3Rpb24oeCkgZml0JG1lYW4gKyB4KQ0KbXUuSFBESSA8LSBhcHBseShwb3N0ZXJpb3IsIDEsIEhQREksIHByb2IgPSAwLjk1KQ0KcGxvdCgwLCB4bGltID0gYygtNywgOCksIHlsaW0gPSBjKC0zLCAzKSwgdHlwZSA9ICJuIiwgeGxhYiA9ICJ4IiwgeWxhYiA9ICJ5IikNCnNoYWRlKG11LkhQREksIHh0ZXN0KQ0KbGluZXMoeHRlc3QsIGZpdCRtZWFuLCBjb2wgPSAicmVkIiwgbHdkID0gMywgbHR5ID0gImRhc2hlZCIpDQpwb2ludHMoeHRyYWluLCB5dHJhaW4sIHBjaCA9IDE2LCBjb2wgPSByYW5naTIsIGNleCA9IDEuNSkNCmN1cnZlKHNpbih4KSwgbHR5ID0gImRhc2hlZCIsIGNvbCA9IGNvbC5hbHBoYSgiYmxhY2siLCAwLjUpLCBhZGQgPSBUUlVFKQ0KbXRleHQocGFzdGUoIjk1JSBwb3N0ZXJpb3IgZGVuc2l0eSBpbnRlcnZhbCAoIiwgbnVtX3Bvc3Rfc2FtcGxlcywgIiBzYW1wbGVzKSIsIHNlcCA9ICIiKSkNCmBgYA0KDQojIyMgMy4xLjEgTWluaW1hbCAkXHJob14yJA0KDQpCZWNhdXNlIHRoZSB2YWx1ZSBvZiBgcmhvc3FgIHdlIGVzdGltYXRlZCBhYm92ZSBsZWFkIHRvIGludGVycG9sYXRpbmcgdGhlIGRhdGEsIHdlIGNhbiBkZWNyZWFzZSBpdCBmdXJ0aGVyIGZvciBhIHNtb290aGVyIGludGVycG9sYXRpb24gaWYgd2UnZCBsaWtlLg0KDQpUaGUgbWluaW1hbCBgcmhvc3FgIHdoaWNoIGFsbG93cyBzYW1wbGluZyBmcm9tIHRoZSBwb3N0ZXJpb3IgaXMgYmV0d2VlbiAwLjUgYW5kIDE6DQoNCmBgYHtyfQ0KZ3BmaXQoeHRyYWluLCB5dHJhaW4sIHh0ZXN0LCAwLjUpJG1pbmVpZ2VuDQpncGZpdCh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIDEpJG1pbmVpZ2VuDQpgYGANCg0KSGVyZSdzIHRoZSBmaXQgYW5kIHBvc3RlcmlvciBpdCBwcm9kdWNlczoNCg0KYGBge3J9DQooIHJob3NxIDwtIG1pbnJob3NxKHh0cmFpbiwgeXRyYWluLCB4dGVzdCwgYygwLjUsIDEpKSApDQpmaXQgPC0gZ3BmaXQoeHRyYWluLCB5dHJhaW4sIHh0ZXN0LCByaG9zcSArIDAuMSkgICAgIyBzbGlnaHRseSBpbmNyZWFzZSByaG9zcQ0KY29ycmVsYXRlZCA8LSBmaXQkTF9wb3N0ICUqJSB1bmNvcnJlbGF0ZWQNCnBvc3RlcmlvciA8LSBhcHBseShjb3JyZWxhdGVkLCAyLCBmdW5jdGlvbih4KSBmaXQkbWVhbiArIHgpDQptdS5IUERJIDwtIGFwcGx5KHBvc3RlcmlvciwgMSwgSFBESSwgcHJvYiA9IDAuOTUpDQpwbG90KDAsIHhsaW0gPSBjKC03LCA4KSwgeWxpbSA9IGMoLTMsIDMpLCB0eXBlID0gIm4iLCB4bGFiID0gIngiLCB5bGFiID0gInkiKQ0Kc2hhZGUobXUuSFBESSwgeHRlc3QpDQpsaW5lcyh4dGVzdCwgZml0JG1lYW4sIGNvbCA9ICJyZWQiLCBsd2QgPSAzLCBsdHkgPSAiZGFzaGVkIikNCnBvaW50cyh4dHJhaW4sIHl0cmFpbiwgcGNoID0gMTYsIGNvbCA9IHJhbmdpMiwgY2V4ID0gMS41KQ0KY3VydmUoc2luKHgpLCBsdHkgPSAiZGFzaGVkIiwgY29sID0gY29sLmFscGhhKCJibGFjayIsIDAuNSksIGFkZCA9IFRSVUUpDQptdGV4dChwYXN0ZSgiOTUlIHBvc3RlcmlvciBkZW5zaXR5IGludGVydmFsICgiLCBudW1fcG9zdF9zYW1wbGVzLCAiIHNhbXBsZXMpIiwgc2VwID0gIiIpKQ0KYGBgDQoNClRoZSBtaW5pbWFsIHZhbHVlIG9mIGByaG9zcWAgd2hpY2ggc3RpbGwgaW50ZXJwb2xhdGVzIHRoZSB0cmFpbmluZyBkYXRhIGlzIGFsc28gYmV0d2VlbiAwLjUgYW5kIDE6DQoNCmBgYHtyfQ0KbWluKGdwZml0KHh0cmFpbiwgeXRyYWluLCB4dGVzdCwgMC41KSR2YXJpYW5jZSkNCm1pbihncGZpdCh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIDEpJHZhcmlhbmNlKQ0KYGBgDQoNCkFuZCBoZXJlJ3MgaXRzIGZpdCwgdXNpbmcgdGhlIHZhcmlhbmNlIHByb3ZpZGVkIGJ5IGBncGZpdCgpYCB0byBlc3RpbWF0ZSB0aGUgcG9zdGVyaW9yOg0KDQpgYGB7cn0NCiggcmhvc3EgPC0gbWlucmhvc3EoeHRyYWluLCB5dHJhaW4sIHh0ZXN0LCBjKDAuNSwgMSksIGZvcmludGVycCA9IFRSVUUpICkNCmZpdCA8LSBncGZpdCh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIHJob3NxKQ0KUEkgPC0gbWF0cml4KG5yb3cgPSAyLCBuY29sID0gbikNClBJWzEsXSA8LSBmaXQkbWVhbiAtIDIqc3FydChmaXQkdmFyaWFuY2UpDQpQSVsyLF0gPC0gZml0JG1lYW4gKyAyKnNxcnQoZml0JHZhcmlhbmNlKQ0KcGxvdCgwLCB4bGltID0gYygtNywgOCksIHlsaW0gPSBjKC0zLCAzKSwgdHlwZSA9ICJuIikNCnNoYWRlKFBJLCB4dGVzdCkNCmN1cnZlKHNpbih4KSwgbHR5ID0gImRhc2hlZCIsIGNvbCA9IGNvbC5hbHBoYSgiYmxhY2siLCAwLjUpLCBhZGQgPSBUUlVFKQ0KbGluZXMoeHRlc3QsIGZpdCRtZWFuLCBjb2wgPSAicmVkIiwgbHdkID0gMywgbHR5ID0gImRhc2hlZCIpDQpwb2ludHMoeHRyYWluLCB5dHJhaW4sIHBjaCA9IDE2LCBjb2wgPSByYW5naTIsIGNleCA9IDEuNSkNCmBgYA0KDQojIyAzLjIgU21hbGxlciBkaXN0YW5jZXMNCg0KTm93IHdlJ2xsIHVzZSAyMCBldmVubHkgc3BhY2VkIHBvaW50cyBiZXR3ZWVuIC02IGFuZCA3IHRvIHNlZSBob3cgdGhlIG1vZGVsIGJlaGF2ZXMuDQoNCmBgYHtyfQ0KeHRyYWluIDwtIHNlcShmcm9tID0gLTYsIHRvID0gNywgbGVuZ3RoLm91dCA9IDIwKQ0KeXRyYWluIDwtIHNpbih4dHJhaW4pDQpkIDwtIGRhdGEuZnJhbWUoeCA9IGFzLmZhY3Rvcihyb3VuZCh4dHJhaW4sMikpLCB5ID0geXRyYWluKQ0KZGlzdGFuY2VzIDwtIGFzLm1hdHJpeChkaXN0KHh0cmFpbikpDQpjb2xuYW1lcyhkaXN0YW5jZXMpIDwtIGQkeA0Kcm93bmFtZXMoZGlzdGFuY2VzKSA8LSBkJHgNCnBsb3QoeHRyYWluLCB5dHJhaW4pDQpgYGANCg0KV2UgYXR0ZW1wdCB0byBsZWFybiB0aGUgR2F1c3NpYW4gcHJvY2VzcyBwYXJhbWV0ZXIgZnJvbSB0aGUgZGF0YToNCg0KYGBge3IgbWVzc2FnZSA9IEZBTFNFfQ0KbTQgPC0gbWFwMnN0YW4oDQogICAgbTEsDQogICAgZGF0YSA9IGxpc3QoDQogICAgICAgIHggPSBkJHgsDQogICAgICAgIHkgPSBkJHksDQogICAgICAgIERtYXQgPSBkaXN0YW5jZXMNCiAgICApLA0KICAgIHdhcm11cCA9IDJlMywNCiAgICBpdGVyID0gMWU0LA0KICAgIGNvbnRyb2wgPSBsaXN0KGFkYXB0X2RlbHRhID0gMC45NSkNCikNCmBgYA0KDQpBbmQgcGxvdCB0aGUgdW5jZXJ0YWludHkgYWJvdXQgaXQ6DQoNCmBgYHtyfQ0KcG9zdCA8LSBleHRyYWN0LnNhbXBsZXMobTQpDQooIHJob3NxIDwtIG1lYW4ocG9zdCRyaG9zcSkgKQ0KDQojIHBsb3QgdGhlIHBvc3RlcmlvciBtZWFuIGNvdmFyaWFuY2UgZnVuY3Rpb24NCmN1cnZlKA0KICAgIGV4cCgtcmhvc3EqeF4yKSwNCiAgICBmcm9tID0gMCwgdG8gPSAxMCwNCiAgICB4bGFiID0gImRzdGFuY2UiLCB5bGFiID0gImNvdmFyaWFuY2UiLA0KICAgIHlsaW0gPSBjKDAsIDEpLA0KICAgIGx3ZCA9IDMNCikNCg0KIyBwbG90IDIwMCBmdW5jdGlvbnMgc2FtcGxlZCBmcm9tIHBvc3Rlcmlvcg0KZm9yICggaSBpbiAxOjIwMCApDQogICAgY3VydmUoDQogICAgICAgIGV4cCgtcG9zdCRyaG9zcVtpXSp4XjIpLA0KICAgICAgICBjb2wgPSBjb2wuYWxwaGEoImJsYWNrIiwgMC4xNSksDQogICAgICAgIGFkZCA9IFRSVUUNCiAgICApDQpgYGANCg0KSGVyZSBhcmUgdGhlIGVzdGltYXRlZCBjb3JyZWxhdGlvbnMgYmV0d2VlbiB0aGUgdmFsdWVzIG9mIGB4dHJhaW5gOg0KDQpgYGB7cn0NCiMgY29tcHV0ZSBwb3N0ZXJpb3IgbWVkaWFuIGNvdmFyaWFuY2UgYW1vbmcgcG9pbnRzDQpLIDwtIGNvdksoeHRyYWluLCB4dHJhaW4sIHJob3NxKQ0KIyBjb252ZXJ0IHRvIGNvcnJlbGF0aW9uIG1hdHJpeA0KUmhvIDwtIHJvdW5kKGNvdjJjb3IoSyksIDIpDQojIGFkZCByb3cgYW5kIGNvbHVtbiBuYW1lcyBmb3IgY29udmVuaWVuY2UNCmNvbG5hbWVzKFJobykgPC0gZCR4DQpyb3duYW1lcyhSaG8pIDwtIGQkeA0KY29ycnBsb3QoUmhvLCBtZXRob2QgPSAiY2lyY2xlIikNCmBgYA0KDQooTmVhdCEpDQoNClRoZSBtb2RlbCBiZWxpZXZlcyB0aGF0IHRoZSB0cmFpbmluZyBkYXRhIGlzIGhpZ2hseSBjb3JyZWxhdGVkIG92ZXIgc2hvcnQgZGlzdGFuY2VzLiBBcyBiZWZvcmUsIHdlIGNhbiBkcmF3IGxpbmVzIGJldHdlZW4gdGhlIHRyYWluaW5nIHBvaW50cyB0byB2aXN1YWxpemUgdGhlIHN0cmVuZ3RoIG9mIHRoZSBlc3RpbWF0ZWQgY29ycmVsYXRpb25zIGJldHdlZW4gdGhlbSwgd2l0aCBkYXJrZXIgbGluZXMgY29ycmVzcG9uZGluZyB0byBoaWdoZXIgY29ycmVsYXRpb24gYW5kIGxpZ2h0ZXIgbGluZXMgdG8gbGVzcyBjb3JyZWxhdGlvbi4NCg0KYGBge3J9DQojIHBsb3QgdGhlIHRyYWluaW5nIGRhdGENCnBsb3QoeHRyYWluLCB5dHJhaW4sIHBjaCA9IDE2LCBjb2wgPSByYW5naTIpDQojIG92ZXJsYXkgbGluZXMgc2hhZGVkIGJ5IFJobw0KZm9yIChpIGluIDE6bGVuZ3RoKHh0cmFpbikpDQogICAgZm9yIChqIGluIDE6bGVuZ3RoKHh0cmFpbikpDQogICAgICAgIGlmIChpIDwgaikNCiAgICAgICAgICAgIGxpbmVzKA0KICAgICAgICAgICAgICAgIGMoeHRyYWluW2ldLCB4dHJhaW5bal0pLA0KICAgICAgICAgICAgICAgIGMoeXRyYWluW2ldLCB5dHJhaW5bal0pLA0KICAgICAgICAgICAgICAgIGx3ZCA9IDIsIGNvbCA9IGNvbC5hbHBoYSgiYmxhY2siLCBSaG9baSxqXV4yKQ0KICAgICAgICAgICAgKQ0KYGBgDQoNCkFuZCBoZXJlJ3MgdGhlIGNvbXB1dGVkIHBvc3RlcmlvciBtZWFuIGN1cnZlOg0KDQpgYGB7cn0NCmZpdCA8LSBncGZpdCh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIHJob3NxKQ0KcGxvdCgwLCB4bGltID0gYygtNywgOCksIHlsaW0gPSBjKC0zLCAzKSwgdHlwZSA9ICJuIiwgeGxhYiA9ICJ4IiwgeWxhYiA9ICJ5IikNCmxpbmVzKHh0ZXN0LCBmaXQkbWVhbiwgY29sID0gInJlZCIsIGx3ZCA9IDMsIGx0eSA9ICJkYXNoZWQiKQ0KcG9pbnRzKHh0cmFpbiwgeXRyYWluLCBwY2ggPSAxNiwgY29sID0gcmFuZ2kyLCBjZXggPSAxLjUpDQpjdXJ2ZShzaW4oeCksIGx0eSA9ICJkYXNoZWQiLCBjb2wgPSBjb2wuYWxwaGEoImJsYWNrIiwgMC41KSwgYWRkID0gVFJVRSkNCm10ZXh0KHBhc3RlKCI5NSUgcG9zdGVyaW9yIGRlbnNpdHkgaW50ZXJ2YWwgKCIsIG51bV9wb3N0X3NhbXBsZXMsICIgc2FtcGxlcykiLCBzZXAgPSAiIikpDQpgYGANCg0KV2VsbCBpdCBzdGlsbCBraW5kYSBibG93cyB1cCBvdmVyIG9uIHRoZSByaWdodCBzaWRlIHRoZXJlLiBJbnRlcmVzdGluZy4gSSByZWFsbHkgZG8gdGhpbmsgdGhlcmUgaXMgYW5vdGhlciB3YXkgdG8gY29tcHV0ZSB0aGlzIGN1cnZlIHdoaWNoIGRvZXNuJ3QgaGF2ZSB0aGlzIGJsb3d1cCBiZWhhdmlvci4NCg0KIyMjIDMuMi4xIE1pbmltYWwgJFxyaG9eMiQNCg0KSnVzdCBvdXQgb2YgY3VyaW9zaXR5LCB3aGF0J3MgdGhlIG1pbmltdW0gdmFsdWUgb2YgYHJob3NxYCB3aGljaCBzdGlsbCBhbGxvd3MgdXMgdG8gaW50ZXJwb2xhdGUgdGhpcyBkZW5zZSBkYXRhPyBJdCdzIGJldHdlZW4gMiBhbmQgNDoNCg0KYGBge3J9DQptaW4oZ3BmaXQoeHRyYWluLCB5dHJhaW4sIHh0ZXN0LCAyKSR2YXJpYW5jZSkNCm1pbihncGZpdCh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIDQpJHZhcmlhbmNlKQ0KYGBgDQoNCkFuZCBoZXJlJ3MgdGhlIGNvcnJlc3BvbmRpbmcgcG9zdGVyaW9yIG1lYW4gY3VydmU6DQoNCmBgYHtyfQ0KKCByaG9zcSA8LSBtaW5yaG9zcSh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIGMoMiwgNCksIGZvcmludGVycCA9IFRSVUUpICkNCmZpdCA8LSBncGZpdCh4dHJhaW4sIHl0cmFpbiwgeHRlc3QsIHJob3NxKQ0KcGxvdCgwLCB4bGltID0gYygtNywgOCksIHlsaW0gPSBjKC0zLCAzKSwgdHlwZSA9ICJuIikNCmN1cnZlKHNpbih4KSwgbHR5ID0gImRhc2hlZCIsIGNvbCA9IGNvbC5hbHBoYSgiYmxhY2siLCAwLjUpLCBhZGQgPSBUUlVFKQ0KbGluZXMoeHRlc3QsIGZpdCRtZWFuLCBjb2wgPSAicmVkIiwgbHdkID0gMywgbHR5ID0gImRhc2hlZCIpDQpwb2ludHMoeHRyYWluLCB5dHJhaW4sIHBjaCA9IDE2LCBjb2wgPSByYW5naTIsIGNleCA9IDEuNSkNCmBgYA0KDQotLS0tDQoNCltBbnRvbmlvIFIuIFZhcmdhc10oaHR0cHM6Ly9tYXRoc3RhdC5kYWwuY2EvfmFudG9uaW92LykNCg0KMjEgU2VwLiAyMDE4DQoNClVwZGF0ZWQgMjIgU2VwIDIwMTgNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg==