Introduction

Stan recently got Laplace approximation algorithm (see Stan Reference Manual). Specificlly Stan makes the normal approximation in the unconstrained space, samples from the approximation, transforms the sample to the constrained space, and returns the sample. The method has option jacobian that can be used to select whether the Jacobian adjustment is included or not.

This case study provides visual illustration of Jacobian adjustment for a parameter transformation, why it is needed for the Laplace approximation, and effect of jacobian option in Stan log_prob and log_prob_grad functions. This notebook intentionally doesn’t go in the mathematical details of measure and probability theory.

Load packages

library("rprojroot")
root<-has_file(".casestudies-root")$make_fix_file()
library(tidyr) 
library(dplyr) 
library(cmdstanr) 
options(mc.cores = 1)
library(ggplot2)
library(bayesplot)
theme_set(bayesplot::theme_default(base_family = "sans", base_size=16))
theme_remove_yaxis <- theme(axis.text.y = element_blank(),
                            axis.line.y=element_blank(),
                            axis.ticks.y=element_blank())
set1 <- RColorBrewer::brewer.pal(7, "Set1")
library(latex2exp)
library(posterior)
SEED <- 48927 # set random seed for reproducibility

Example model and posterior

For the illustration Binomial model is used with observed data \(N=10, y=9\).

data_bin <- list(N = 10, y = 9)

As Beta(1,1) prior is used the posterior is Beta(9+1,1+1), but for illustration we also use Stan to find the mode of the posterior, sample from the posterior, and compare different posterior density values that we can ask Stan to compute. We use compile_model_methods=TRUE to be able to access log_prob() method later.

code_binom <- root("Jacobian","binom.stan")
writeLines(readLines(code_binom))
// Binomial model with beta(1,1) prior
data {
  int<lower=0> N;              // number of experiments
  int<lower=0> y;              // number of successes
}
parameters {
  real<lower=0,upper=1> theta; // probability of success in range (0,1)
}
model {
  // model block creates the log density to be sampled
  theta ~ beta(1, 1);          // prior
  y ~ binomial(N, theta);      // observation model / likelihood
  // the notation using ~ is syntactic sugar for
  //  target += beta_lpdf(theta | 1, 1);     // lpdf for continuous theta
  //  target += binomial_lpmf(y | N, theta); // lpmf for discrete y
  // target is the log density to be sampled
}
model_bin <- cmdstan_model(stan_file = code_binom,
                           compile_model_methods=TRUE, force_recompile=TRUE)

Default MCMC sampling (as this is an easy posterior we skip showing the results for the convergence diagnostics).

fit_bin <- model_bin$sample(data = data_bin, seed = SEED, refresh=0)
Running MCMC with 4 sequential chains...

Chain 1 finished in 0.0 seconds.
Chain 2 finished in 0.0 seconds.
Chain 3 finished in 0.0 seconds.
Chain 4 finished in 0.0 seconds.

All 4 chains finished successfully.
Mean chain execution time: 0.0 seconds.
Total execution time: 0.5 seconds.

Default optimization finds the maximum of the posterior.

opt_bin <- model_bin$optimize(data = data_bin, seed = SEED)
Initial log joint probability = -14.4243 
    Iter      log prob        ||dx||      ||grad||       alpha      alpha0  # evals  Notes  
       7      -3.25083   0.000570368   2.99771e-06           1           1       10    
Optimization terminated normally:  
  Convergence detected: relative gradient magnitude is below tolerance 
Finished in  0.1 seconds.

The following plot shows the exact posterior (black) and grey vertical line shows the MAP, that is, posterior mode in the constrained space. Stan optimizing finds correctly the mode.

df_bin <- data.frame(theta=plogis(seq(-4,6,length.out=100))) |>
  mutate(pdfbeta=dbeta(theta,9+1,1+1))
ggplot(data=df_bin,aes(x=theta,y=pdfbeta))+
  geom_line()+
  geom_vline(xintercept=(opt_bin$draws()[1,'theta']),color="gray")+
  labs(x=TeX(r'($\theta$)'),y='')+
  theme_remove_yaxis+
  annotate("text", x=0.81,y=3.3,label=TeX(r'($p(\theta|y)$)'),color=1,size=5,hjust=1)+
  annotate("text", x=0.89,y=4.3,label='mode',color="gray",size=5,hjust=1)

Posterior log_prob

The CmdStanR fit object fit_bin provides also access to log_prob and log_prob_grad functions. The documentation of log_prob says

Using model's log_prob and grad_log_prob take values from the
unconstrained space of model parameters and (by default) return
values in the same space.

And one of the options say

jacobian_adjustment: (logical) Whether to include the log-density
adjustments from un/constraining variables.

The functions accepts also jacobian. We can compute the exact posterior density values in grid using Stan and log_prob with jacobian=FALSE. We create a helper function.

fit_pdf <- function(th, fit) {
  exp(fit$log_prob(fit$unconstrain_variables(list(theta=th)),
                   jacobian=FALSE))
}

df_bin |>
  mutate(pdfstan=sapply(theta, fit_pdf, fit_bin)) |>
  ggplot(aes(x=theta,y=pdfbeta))+
  geom_line()+
  geom_line(aes(y=pdfstan),color=set1[1])+
  geom_vline(xintercept=(opt_bin$draws()[1,'theta']),color="gray")+
  labs(x=TeX(r'($\theta$)'),y='')+
  theme_remove_yaxis+
  annotate("text", x=0.81,y=3.3,label=TeX(r'($p(\theta|y)$)'),color=1,size=5,hjust=1)+
  annotate("text", x=0.89,y=4.3,label='mode',color="gray",size=5,hjust=1)+
  annotate("text", x=0.85,y=.2,label=TeX(r'($q(\theta|y)$=exp(lp__))'),color=set1[1],size=5,hjust=1)

The pdf from Stan is much lower than the true posterior, because it is unnormalized posterior as in general computing the normalization term is non-trivial. In this case the true posterior has analytic solution for the normalizing constant \[ \frac{\Gamma(N+2)}{\Gamma(y+1)\Gamma(N-y+1)}=110 \] and we get exact match by multiplying the density returned by Stan by 110.

df_bin |>
  mutate(pdfstan=sapply(theta, fit_pdf, fit_bin)*110) |>
  ggplot(aes(x=theta,y=pdfbeta))+
  geom_line()+
  geom_line(aes(y=pdfstan),color=set1[1])+
  geom_vline(xintercept=(opt_bin$draws()[1,'theta']),color="gray")+
  labs(x=TeX(r'($\theta$)'),y='')+
  theme_remove_yaxis+
  annotate("text", x=0.81,y=3.3,label=TeX(r'($p(\theta|y)$)'),color=1,size=5,hjust=1)+
  annotate("text", x=0.89,y=4.3,label='mode',color="gray",size=5,hjust=1)+
  annotate("text", x=0.79,y=2.9,label=TeX(r'($q(\theta|y)\cdot 110$)'),color=set1[1],size=5,hjust=1)

Thus if someone cares about the posterior mode in the constrained space they need jacobian=FALSE.

Side note: the normalizing constant is not needed for MCMC and not needed when estimating various expectations using MCMC draws, but is used here for illustration.

Constraint and parameter transformation

In this example, theta is constrained to be between 0 and 1

 real<lower=0,upper=1> theta; // probability of success in range (0,1)

To avoid problems with constraints in optimization and MCMC, Stan switches under the hood to unconstrained parameterization using logit transformation \[ \mbox{logit}(\theta) = \log\left(\frac{\theta}{1-\theta}\right) \].

In the above fit_pdf function we used Stan’s unconstrain_pars function to transform theta to logit(theta). In R we can also define logit function as the inverse of the logistic function.

logit <- qlogis

We now switch looking at the distributions in the unconstrained space.

df_bin |>
  mutate(pdfstan=sapply(theta, fit_pdf, fit_bin)*110) |>
  ggplot(aes(x=logit(theta),y=pdfbeta))+
  geom_line()+
  geom_line(aes(y=pdfstan),color=set1[1])+
  geom_vline(xintercept=logit(opt_bin$draws()[1,'theta']),color="gray")+
  xlim(-2.9,6.5)+
  labs(x=TeX(r'($\logit(\theta)$)'),y='')+
  theme_remove_yaxis+
  annotate("text", x=logit(0.81),y=3.3,label=TeX(r'($p(\theta|y)$)'),color=1,size=5,hjust=1)+
  annotate("text", x=logit(0.89),y=4.3,label='mode',color="gray",size=5,hjust=1)+
  annotate("text", x=logit(0.79),y=2.9,label=TeX(r'($q(\theta|y)\cdot 110$)'),color=set1[1],size=5,hjust=1)

The above plot shows now the function that Stan optimizing optimizes in the unconstrained space, and the MAP is the logit of the MAP in the constrained space. Thus if someone cares about the posterior mode in the constrained, but is doing the optimization in unconstrained space they still need jacobian=FALSE.

Parameter transformation and Jacobian adjustment

That function shown above is not the posterior of logit(theta). As the transformation is non-linear we need to take into account the distortion caused by the transform. The density must be multiplied by a Jacobian adjustment equal to the absolute determinant of the Jacobian of the transform. See more in Stan User’s Guide. The Jacobian for lower and upper bounded scalar is given in Stan Reference Manual, and for (0,1)-bounded it is \[ \theta (1-\theta). \]

Stan can do this transformation for us when we call log_prob with jacobian=TRUE

fit_pdf_jacobian <- function(th, fit) {
  exp(fit$log_prob(fit$unconstrain_variables(list(theta=th)),
                   jacobian=TRUE))
}

We compare the true adjusted posterior density in logit(theta) space to non-adjusted density function. For visualization purposes we scale the functions to have the same maximum, so they are not normalized distributions.

df_bin <- df_bin |>
  mutate(pdfstan_nonadjusted=sapply(theta, fit_pdf, fit_bin),
         pdfstan_jacobian=sapply(theta, fit_pdf_jacobian, fit_bin))
ggplot(data=df_bin,aes(x=logit(theta),y=pdfstan_nonadjusted/max(pdfstan_nonadjusted)))+
  geom_line(color=set1[1])+
  geom_line(aes(y=pdfstan_jacobian/max(pdfstan_jacobian)),color=set1[2])+
  xlim(-2.9,6.5)+
  labs(x=TeX(r'($\logit(\theta)$)'),y='')+
  theme_remove_yaxis+
  annotate("text", x=1.15,y=0.95,label=TeX('jacobian=TRUE'),color=set1[2],size=5,hjust=1)+
  annotate("text", x=1.15,y=0.9,label=TeX(r'($q(\logit(\theta)|y)  = q(\theta|y)\theta(1-\theta)$)'),color=set1[2],size=5,hjust=1)+
  annotate("text", x=2.9,y=0.95,label="jacobian=FALSE",color=set1[1],size=5,hjust=0)+
  annotate("text", x=2.9,y=0.9,label=TeX(r'($q(\theta|y) \neq q(\logit(\theta)|y)$)'),color=set1[1],size=5,hjust=0)

Stan MCMC samples from the blue distribution with jacobian=TRUE. The mode of that distribution is different from the mode of jacobian=FALSE. In general the mode is not invariant to transformations.

Wrong normal approximation

Stan optimizing/optimize finds the mode of jacobian=FALSE (in some intefaces jacobian=FALSE). rstanarm has had an option to do normal approximation at the mode jacobian=FALSE in the unconstrained space by computing the Hessian of jacobian=FALSE and then sampling independent draws from that normal distribution. We can do the same in CmdStanR with $laplace() method and option jacobian=FALSE, but this is the wrong thing to do.

lap_bin <- model_bin$laplace(data = data_bin, jacobian=FALSE,
                             seed = SEED, draws=4000, refresh=0)
Finished in  0.1 seconds.
Finished in  0.1 seconds.
lap_draws = lap_bin$draws(format = "df")

We add the current normal approximation to the plot with dashed line.

lap_draws <- lap_draws |>
  mutate(logp=lp__-max(lp__),
         logg=lp_approx__-max(lp_approx__))
ggplot(data=df_bin,aes(x=logit(theta),y=pdfstan_nonadjusted/max(pdfstan_nonadjusted)))+
  geom_line(color=set1[1])+
  geom_line(aes(y=pdfstan_jacobian/max(pdfstan_jacobian)),color=set1[2])+
  geom_line(data=lap_draws,aes(x=logit(theta),y=exp(logg)),color=set1[1],linetype="dashed")+
  xlim(-2.9,6.5)+
  labs(x=TeX(r'($\logit(\theta)$)'),y='')+
  theme_remove_yaxis+
  annotate("text", x=1.15,y=0.95,label=TeX('jacobian=TRUE'),color=set1[2],size=5,hjust=1)+
  annotate("text", x=1.15,y=0.9,label=TeX(r'($q(\logit(\theta)|y) = q(\theta|y)\theta(1-\theta)$)'),color=set1[2],size=5,hjust=1)+
  annotate("text", x=2.9,y=0.95,label="jacobian=FALSE",color=set1[1],size=5,hjust=0)+
  annotate("text", x=2.9,y=0.9,label=TeX(r'($q(\theta|y) \neq q(\logit(\theta)|y)$)'),color=set1[1],size=5,hjust=0)

The problem is that this normal approximation is quite different from the true posterior (with jacobian=TRUE).

Normal approximation

Recently the normal approximation method was implemented in Stan itself with the name laplace. This approximation uses by default jacobian=TRUE. We can use Laplace approximation with CmdStanR method $laplace(), which by default is using he option jacobian=TRUE, which is the correct thing to do.

lap_bin2 <- model_bin$laplace(data=data_bin, seed=SEED, draws=4000, refresh=0)
Finished in  0.1 seconds.
Finished in  0.1 seconds.
lap_draws2 <- lap_bin2$draws(format = "df")

We add the second normal approximation to the plot dashed line.

lap_draws2 <- lap_draws2 |>
  mutate(logp=lp__-max(lp__),
         logg=lp_approx__-max(lp_approx__))
ggplot(data=df_bin,aes(x=logit(theta),y=pdfstan_nonadjusted/max(pdfstan_nonadjusted)))+
  geom_line(color=set1[1])+
  geom_line(aes(y=pdfstan_jacobian/max(pdfstan_jacobian)),color=set1[2])+
  geom_line(data=lap_draws,aes(x=logit(theta),y=exp(logg)),color=set1[1],linetype="dashed")+
  geom_line(data=lap_draws2,aes(x=logit(theta),y=exp(logg)),color=set1[2],linetype="dashed")+
  xlim(-2.9,6.5)+
  labs(x=TeX(r'($\logit(\theta)$)'),y='')+
  theme_remove_yaxis+
  annotate("text", x=1.15,y=0.95,label=TeX('jacobian=TRUE'),color=set1[2],size=5,hjust=1)+
  annotate("text", x=1.15,y=0.9,label=TeX(r'($q(\logit(\theta)|y) = q(\theta|y)\theta(1-\theta)$)'),color=set1[2],size=5,hjust=1)+
  annotate("text", x=2.9,y=0.95,label="jacobian=FALSE",color=set1[1],size=5,hjust=0)+
  annotate("text", x=2.9,y=0.9,label=TeX(r'($q(\theta|y) \neq q(\logit(\theta)|y)$)'),color=set1[1],size=5,hjust=0)

Transforming draws to the constrained space

The draws from the normal approximation (shown with rug lines in op and bottom) can be easily transformed back to the constrained space, and illustrated with kernel density estimates. Before that we plot the kernel density estimates of the draws in the unconstrained space to show that this etimate is reasonable.

ggplot(data=df_bin,aes(x=logit(theta),y=pdfstan_nonadjusted/max(pdfstan_nonadjusted)))+
  geom_line(color=set1[1])+
  geom_line(aes(y=pdfstan_jacobian/max(pdfstan_jacobian)),color=set1[2])+
  geom_line(data=lap_draws,aes(x=logit(theta),y=exp(logg)),color=set1[1],linetype="dashed")+
  geom_line(data=lap_draws2,aes(x=logit(theta),y=exp(logg)),color=set1[2],linetype="dashed")+
  geom_rug(data=lap_draws[1:400,],aes(x=logit(theta),y=0), color=set1[1], alpha=0.2, sides='t')+
  geom_rug(data=lap_draws2[1:400,],aes(x=logit(theta),y=0), color=set1[2], alpha=0.2, sides='b')+
  geom_density(data=lap_draws,aes(x=logit(theta),after_stat(scaled)),adjust=2,color=set1[1],linetype="dotdash")+
  geom_density(data=lap_draws2,aes(x=logit(theta),after_stat(scaled)),adjust=2,color=set1[2],linetype="dotdash")+
  xlim(-2.9,6.5)+
  labs(x=TeX(r'($\logit(\theta)$)'),y='')+
  theme_remove_yaxis+
  annotate("text", x=1.15,y=0.95,label=TeX('jacobian=TRUE'),color=set1[2],size=5,hjust=1)+
  annotate("text", x=1.15,y=0.9,label=TeX(r'($q(\logit(\theta)|y) = q(\theta|y)\theta(1-\theta)$)'),color=set1[2],size=5,hjust=1)+
  annotate("text", x=2.9,y=0.95,label="jacobian=FALSE",color=set1[1],size=5,hjust=0)+
  annotate("text", x=2.9,y=0.9,label=TeX(r'($q(\theta|y) \neq q(\logit(\theta)|y)$)'),color=set1[1],size=5,hjust=0)

When we plot kernel density estimates of the logit transformed draws (draws shown with rug lines in top and bottom) in the constrained space, it’s clear which draws approximate better the true posterior (black line)

ggplot(data=df_bin,aes(x=(theta),y=pdfbeta/max(pdfbeta)))+
  geom_line()+
  geom_density(data=lap_draws,aes(x=(theta),after_stat(scaled)),adjust=1,color=set1[1],linetype="dotdash")+
  geom_density(data=lap_draws2,aes(x=(theta),after_stat(scaled)),adjust=1,color=set1[2],linetype="dotdash")+
  geom_rug(data=lap_draws[1:400,],aes(x=(theta),y=0), color=set1[1], alpha=0.2, sides='t')+
  geom_rug(data=lap_draws2[1:400,],aes(x=(theta),y=0), color=set1[2], alpha=0.2, sides='b')+
  labs(x=TeX(r'($\theta$)'),y='')+
  theme_remove_yaxis

Importance resampling

$laplace() method returns also unormalized target log density (lp__) and normal approximation log density (lp_approx__) for the draws from the normal approximation. These can be used to compute importance ratios exp(lp__-lp_approax__) which can be used to do importance resampling. We can do the importance resampling using the posterior package.

lap_draws2is <- lap_draws2 |>
  weight_draws(lap_draws2$lp__-lap_draws2$lp_approx__, log=TRUE) |>
  resample_draws()

The kernel density estimate using importance resampled draws is even close to the true distribution.

ggplot(data=df_bin,aes(x=(theta),y=pdfbeta/max(pdfbeta)))+
  geom_line()+
  labs(x=TeX(r'($\theta$)'),y='')+
  theme_remove_yaxis+
  geom_density(data=lap_draws2is,aes(x=(theta),after_stat(scaled)),adjust=1,color=set1[2],linetype="dotdash")+
  geom_rug(data=lap_draws2is[1:400,],aes(x=(theta),y=0), color=set1[2], alpha=0.2, sides='b')

Discussion

The normal approximation and importance resampling did work quite well in this simple one dimensional case, but in general the normal approximation for importance sampling works well only in quite low dimensional settings or when the posterior in the unconstrained space is very close to normal.

LS0tCnRpdGxlOiAiTGFwbGFjZSBtZXRob2QgYW5kIEphY29iaWFuIG9mIHBhcmFtZXRlciB0cmFuc2Zvcm1hdGlvbiIKYXV0aG9yOiAiW0FraSBWZWh0YXJpXShodHRwczovL3VzZXJzLmFhbHRvLmZpL35hdmUvKSIKZGF0ZTogIkZpcnN0IHZlcnNpb24gMjAyMS0wMS0yMS4gTGFzdCBtb2RpZmllZCBgciBmb3JtYXQoU3lzLkRhdGUoKSlgLiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogcmVhZGFibGUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgojIyBJbnRyb2R1Y3Rpb24KClN0YW4gcmVjZW50bHkgZ290IExhcGxhY2UgYXBwcm94aW1hdGlvbiBhbGdvcml0aG0gKHNlZSBbU3RhbgpSZWZlcmVuY2UKTWFudWFsXShodHRwczovL21jLXN0YW4ub3JnL2RvY3MvcmVmZXJlbmNlLW1hbnVhbC9sYXBsYWNlLWFwcHJveGltYXRpb24uaHRtbCkpLgpTcGVjaWZpY2xseSBTdGFuIG1ha2VzIHRoZSBub3JtYWwgYXBwcm94aW1hdGlvbiBpbiB0aGUKdW5jb25zdHJhaW5lZCBzcGFjZSwgc2FtcGxlcyBmcm9tIHRoZSBhcHByb3hpbWF0aW9uLCB0cmFuc2Zvcm1zIHRoZQpzYW1wbGUgdG8gdGhlIGNvbnN0cmFpbmVkIHNwYWNlLCBhbmQgcmV0dXJucyB0aGUgc2FtcGxlLiBUaGUgbWV0aG9kCmhhcyBvcHRpb24gYGphY29iaWFuYCB0aGF0IGNhbiBiZSB1c2VkIHRvIHNlbGVjdCB3aGV0aGVyIHRoZQpKYWNvYmlhbiBhZGp1c3RtZW50IGlzIGluY2x1ZGVkIG9yIG5vdC4KClRoaXMgY2FzZSBzdHVkeSBwcm92aWRlcyB2aXN1YWwgaWxsdXN0cmF0aW9uIG9mIEphY29iaWFuIGFkanVzdG1lbnQKZm9yIGEgcGFyYW1ldGVyIHRyYW5zZm9ybWF0aW9uLCB3aHkgaXQgaXMgbmVlZGVkIGZvciB0aGUgTGFwbGFjZQphcHByb3hpbWF0aW9uLCBhbmQgZWZmZWN0IG9mIGBqYWNvYmlhbmAgb3B0aW9uIGluIFN0YW4gYGxvZ19wcm9iYAphbmQgYGxvZ19wcm9iX2dyYWRgIGZ1bmN0aW9ucy4gVGhpcyBub3RlYm9vayBpbnRlbnRpb25hbGx5IGRvZXNuJ3QKZ28gaW4gdGhlIG1hdGhlbWF0aWNhbCBkZXRhaWxzIG9mIG1lYXN1cmUgYW5kIHByb2JhYmlsaXR5IHRoZW9yeS4KCgpgYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRSwgd2FybmluZz1GQUxTRSwgY29tbWVudD1OQSwgY2FjaGU9RkFMU0UpCmBgYGAKCiMjIyMgTG9hZCBwYWNrYWdlcwoKYGBgYHtyfQpsaWJyYXJ5KCJycHJvanJvb3QiKQpyb290PC1oYXNfZmlsZSgiLmNhc2VzdHVkaWVzLXJvb3QiKSRtYWtlX2ZpeF9maWxlKCkKbGlicmFyeSh0aWR5cikgCmxpYnJhcnkoZHBseXIpIApsaWJyYXJ5KGNtZHN0YW5yKSAKb3B0aW9ucyhtYy5jb3JlcyA9IDEpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShiYXllc3Bsb3QpCnRoZW1lX3NldChiYXllc3Bsb3Q6OnRoZW1lX2RlZmF1bHQoYmFzZV9mYW1pbHkgPSAic2FucyIsIGJhc2Vfc2l6ZT0xNikpCnRoZW1lX3JlbW92ZV95YXhpcyA8LSB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMubGluZS55PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGF4aXMudGlja3MueT1lbGVtZW50X2JsYW5rKCkpCnNldDEgPC0gUkNvbG9yQnJld2VyOjpicmV3ZXIucGFsKDcsICJTZXQxIikKbGlicmFyeShsYXRleDJleHApCmxpYnJhcnkocG9zdGVyaW9yKQpTRUVEIDwtIDQ4OTI3ICMgc2V0IHJhbmRvbSBzZWVkIGZvciByZXByb2R1Y2liaWxpdHkKYGBgYAoKIyMgRXhhbXBsZSBtb2RlbCBhbmQgcG9zdGVyaW9yCgpGb3IgdGhlIGlsbHVzdHJhdGlvbiBCaW5vbWlhbCBtb2RlbCBpcyB1c2VkIHdpdGggb2JzZXJ2ZWQgZGF0YQokTj0xMCwgeT05JC4KCmBgYGB7cn0KZGF0YV9iaW4gPC0gbGlzdChOID0gMTAsIHkgPSA5KQpgYGBgCgpBcyBCZXRhKDEsMSkgcHJpb3IgaXMgdXNlZCB0aGUgcG9zdGVyaW9yIGlzIEJldGEoOSsxLDErMSksIGJ1dCBmb3IKaWxsdXN0cmF0aW9uIHdlIGFsc28gdXNlIFN0YW4gdG8gZmluZCB0aGUgbW9kZSBvZiB0aGUgcG9zdGVyaW9yLApzYW1wbGUgZnJvbSB0aGUgcG9zdGVyaW9yLCBhbmQgY29tcGFyZSBkaWZmZXJlbnQgcG9zdGVyaW9yIGRlbnNpdHkKdmFsdWVzIHRoYXQgd2UgY2FuIGFzayBTdGFuIHRvIGNvbXB1dGUuIFdlIHVzZQpgY29tcGlsZV9tb2RlbF9tZXRob2RzPVRSVUVgIHRvIGJlIGFibGUgdG8gYWNjZXNzIGBsb2dfcHJvYigpYAptZXRob2QgbGF0ZXIuCgpgYGBge3J9CmNvZGVfYmlub20gPC0gcm9vdCgiSmFjb2JpYW4iLCJiaW5vbS5zdGFuIikKd3JpdGVMaW5lcyhyZWFkTGluZXMoY29kZV9iaW5vbSkpCm1vZGVsX2JpbiA8LSBjbWRzdGFuX21vZGVsKHN0YW5fZmlsZSA9IGNvZGVfYmlub20sCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbXBpbGVfbW9kZWxfbWV0aG9kcz1UUlVFLCBmb3JjZV9yZWNvbXBpbGU9VFJVRSkKYGBgYAoKRGVmYXVsdCBNQ01DIHNhbXBsaW5nIChhcyB0aGlzIGlzIGFuIGVhc3kgcG9zdGVyaW9yIHdlIHNraXAgc2hvd2luZwp0aGUgcmVzdWx0cyBmb3IgdGhlIGNvbnZlcmdlbmNlIGRpYWdub3N0aWNzKS4KCmBgYGB7cn0KZml0X2JpbiA8LSBtb2RlbF9iaW4kc2FtcGxlKGRhdGEgPSBkYXRhX2Jpbiwgc2VlZCA9IFNFRUQsIHJlZnJlc2g9MCkKYGBgYAoKRGVmYXVsdCBvcHRpbWl6YXRpb24gZmluZHMgdGhlIG1heGltdW0gb2YgdGhlIHBvc3Rlcmlvci4KCmBgYGB7cn0Kb3B0X2JpbiA8LSBtb2RlbF9iaW4kb3B0aW1pemUoZGF0YSA9IGRhdGFfYmluLCBzZWVkID0gU0VFRCkKYGBgYAoKVGhlIGZvbGxvd2luZyBwbG90IHNob3dzIHRoZSBleGFjdCBwb3N0ZXJpb3IgKGJsYWNrKSBhbmQgZ3JleQp2ZXJ0aWNhbCBsaW5lIHNob3dzIHRoZSBNQVAsIHRoYXQgaXMsIHBvc3RlcmlvciBtb2RlIGluIHRoZQpjb25zdHJhaW5lZCBzcGFjZS4gU3RhbiBvcHRpbWl6aW5nIGZpbmRzIGNvcnJlY3RseSB0aGUgbW9kZS4KCmBgYGB7cn0KZGZfYmluIDwtIGRhdGEuZnJhbWUodGhldGE9cGxvZ2lzKHNlcSgtNCw2LGxlbmd0aC5vdXQ9MTAwKSkpIHw+CiAgbXV0YXRlKHBkZmJldGE9ZGJldGEodGhldGEsOSsxLDErMSkpCmdncGxvdChkYXRhPWRmX2JpbixhZXMoeD10aGV0YSx5PXBkZmJldGEpKSsKICBnZW9tX2xpbmUoKSsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9KG9wdF9iaW4kZHJhd3MoKVsxLCd0aGV0YSddKSxjb2xvcj0iZ3JheSIpKwogIGxhYnMoeD1UZVgocicoJFx0aGV0YSQpJykseT0nJykrCiAgdGhlbWVfcmVtb3ZlX3lheGlzKwogIGFubm90YXRlKCJ0ZXh0IiwgeD0wLjgxLHk9My4zLGxhYmVsPVRlWChyJygkcChcdGhldGF8eSkkKScpLGNvbG9yPTEsc2l6ZT01LGhqdXN0PTEpKwogIGFubm90YXRlKCJ0ZXh0IiwgeD0wLjg5LHk9NC4zLGxhYmVsPSdtb2RlJyxjb2xvcj0iZ3JheSIsc2l6ZT01LGhqdXN0PTEpCmBgYGAKCiMjIFBvc3RlcmlvciBsb2dfcHJvYgoKVGhlIENtZFN0YW5SIGZpdCBvYmplY3QgYGZpdF9iaW5gIHByb3ZpZGVzIGFsc28gYWNjZXNzIHRvIGxvZ19wcm9iCmFuZCBsb2dfcHJvYl9ncmFkIGZ1bmN0aW9ucy4gVGhlIGRvY3VtZW50YXRpb24gb2YgbG9nX3Byb2Igc2F5cwoKICAgIFVzaW5nIG1vZGVsJ3MgbG9nX3Byb2IgYW5kIGdyYWRfbG9nX3Byb2IgdGFrZSB2YWx1ZXMgZnJvbSB0aGUKICAgIHVuY29uc3RyYWluZWQgc3BhY2Ugb2YgbW9kZWwgcGFyYW1ldGVycyBhbmQgKGJ5IGRlZmF1bHQpIHJldHVybgogICAgdmFsdWVzIGluIHRoZSBzYW1lIHNwYWNlLgoKQW5kIG9uZSBvZiB0aGUgb3B0aW9ucyBzYXkKCiAgICBqYWNvYmlhbl9hZGp1c3RtZW50OiAobG9naWNhbCkgV2hldGhlciB0byBpbmNsdWRlIHRoZSBsb2ctZGVuc2l0eQogICAgYWRqdXN0bWVudHMgZnJvbSB1bi9jb25zdHJhaW5pbmcgdmFyaWFibGVzLgoKVGhlIGZ1bmN0aW9ucyBhY2NlcHRzIGFsc28gYGphY29iaWFuYC4gV2UgY2FuIGNvbXB1dGUgdGhlIGV4YWN0CnBvc3RlcmlvciBkZW5zaXR5IHZhbHVlcyBpbiBncmlkIHVzaW5nIFN0YW4gYW5kIGxvZ19wcm9iIHdpdGgKYGphY29iaWFuPUZBTFNFYC4gV2UgY3JlYXRlIGEgaGVscGVyIGZ1bmN0aW9uLgoKYGBgYHtyfQpmaXRfcGRmIDwtIGZ1bmN0aW9uKHRoLCBmaXQpIHsKICBleHAoZml0JGxvZ19wcm9iKGZpdCR1bmNvbnN0cmFpbl92YXJpYWJsZXMobGlzdCh0aGV0YT10aCkpLAogICAgICAgICAgICAgICAgICAgamFjb2JpYW49RkFMU0UpKQp9CgpkZl9iaW4gfD4KICBtdXRhdGUocGRmc3Rhbj1zYXBwbHkodGhldGEsIGZpdF9wZGYsIGZpdF9iaW4pKSB8PgogIGdncGxvdChhZXMoeD10aGV0YSx5PXBkZmJldGEpKSsKICBnZW9tX2xpbmUoKSsKICBnZW9tX2xpbmUoYWVzKHk9cGRmc3RhbiksY29sb3I9c2V0MVsxXSkrCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PShvcHRfYmluJGRyYXdzKClbMSwndGhldGEnXSksY29sb3I9ImdyYXkiKSsKICBsYWJzKHg9VGVYKHInKCRcdGhldGEkKScpLHk9JycpKwogIHRoZW1lX3JlbW92ZV95YXhpcysKICBhbm5vdGF0ZSgidGV4dCIsIHg9MC44MSx5PTMuMyxsYWJlbD1UZVgocicoJHAoXHRoZXRhfHkpJCknKSxjb2xvcj0xLHNpemU9NSxoanVzdD0xKSsKICBhbm5vdGF0ZSgidGV4dCIsIHg9MC44OSx5PTQuMyxsYWJlbD0nbW9kZScsY29sb3I9ImdyYXkiLHNpemU9NSxoanVzdD0xKSsKICBhbm5vdGF0ZSgidGV4dCIsIHg9MC44NSx5PS4yLGxhYmVsPVRlWChyJygkcShcdGhldGF8eSkkPWV4cChscF9fKSknKSxjb2xvcj1zZXQxWzFdLHNpemU9NSxoanVzdD0xKQpgYGBgCgoKVGhlIHBkZiBmcm9tIFN0YW4gaXMgbXVjaCBsb3dlciB0aGFuIHRoZSB0cnVlIHBvc3RlcmlvciwgYmVjYXVzZSBpdAppcyB1bm5vcm1hbGl6ZWQgcG9zdGVyaW9yIGFzIGluIGdlbmVyYWwgY29tcHV0aW5nIHRoZSBub3JtYWxpemF0aW9uCnRlcm0gaXMgbm9uLXRyaXZpYWwuIEluIHRoaXMgY2FzZSB0aGUgdHJ1ZSBwb3N0ZXJpb3IgaGFzIGFuYWx5dGljCnNvbHV0aW9uIGZvciB0aGUgbm9ybWFsaXppbmcgY29uc3RhbnQKJCQKXGZyYWN7XEdhbW1hKE4rMil9e1xHYW1tYSh5KzEpXEdhbW1hKE4teSsxKX09MTEwCiQkCmFuZCB3ZSBnZXQgZXhhY3QgbWF0Y2ggYnkgbXVsdGlwbHlpbmcgdGhlIGRlbnNpdHkgcmV0dXJuZWQgYnkKU3RhbiBieSAxMTAuCgpgYGBge3J9CmRmX2JpbiB8PgogIG11dGF0ZShwZGZzdGFuPXNhcHBseSh0aGV0YSwgZml0X3BkZiwgZml0X2JpbikqMTEwKSB8PgogIGdncGxvdChhZXMoeD10aGV0YSx5PXBkZmJldGEpKSsKICBnZW9tX2xpbmUoKSsKICBnZW9tX2xpbmUoYWVzKHk9cGRmc3RhbiksY29sb3I9c2V0MVsxXSkrCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PShvcHRfYmluJGRyYXdzKClbMSwndGhldGEnXSksY29sb3I9ImdyYXkiKSsKICBsYWJzKHg9VGVYKHInKCRcdGhldGEkKScpLHk9JycpKwogIHRoZW1lX3JlbW92ZV95YXhpcysKICBhbm5vdGF0ZSgidGV4dCIsIHg9MC44MSx5PTMuMyxsYWJlbD1UZVgocicoJHAoXHRoZXRhfHkpJCknKSxjb2xvcj0xLHNpemU9NSxoanVzdD0xKSsKICBhbm5vdGF0ZSgidGV4dCIsIHg9MC44OSx5PTQuMyxsYWJlbD0nbW9kZScsY29sb3I9ImdyYXkiLHNpemU9NSxoanVzdD0xKSsKICBhbm5vdGF0ZSgidGV4dCIsIHg9MC43OSx5PTIuOSxsYWJlbD1UZVgocicoJHEoXHRoZXRhfHkpXGNkb3QgMTEwJCknKSxjb2xvcj1zZXQxWzFdLHNpemU9NSxoanVzdD0xKQpgYGBgCgpUaHVzIGlmIHNvbWVvbmUgY2FyZXMgYWJvdXQgdGhlIHBvc3RlcmlvciBtb2RlIGluIHRoZSBjb25zdHJhaW5lZApzcGFjZSB0aGV5IG5lZWQgYGphY29iaWFuPUZBTFNFYC4KClNpZGUgbm90ZTogdGhlIG5vcm1hbGl6aW5nIGNvbnN0YW50IGlzIG5vdCBuZWVkZWQgZm9yIE1DTUMgYW5kIG5vdApuZWVkZWQgd2hlbiBlc3RpbWF0aW5nIHZhcmlvdXMgZXhwZWN0YXRpb25zIHVzaW5nIE1DTUMgZHJhd3MsIGJ1dAppcyB1c2VkIGhlcmUgZm9yIGlsbHVzdHJhdGlvbi4KCiMjIENvbnN0cmFpbnQgYW5kIHBhcmFtZXRlciB0cmFuc2Zvcm1hdGlvbgoKSW4gdGhpcyBleGFtcGxlLCB0aGV0YSBpcyBjb25zdHJhaW5lZCB0byBiZSBiZXR3ZWVuIDAgYW5kIDEKYGBgCiByZWFsPGxvd2VyPTAsdXBwZXI9MT4gdGhldGE7IC8vIHByb2JhYmlsaXR5IG9mIHN1Y2Nlc3MgaW4gcmFuZ2UgKDAsMSkKYGBgClRvIGF2b2lkIHByb2JsZW1zIHdpdGggY29uc3RyYWludHMgaW4gb3B0aW1pemF0aW9uIGFuZCBNQ01DLCBTdGFuCnN3aXRjaGVzIHVuZGVyIHRoZSBob29kIHRvIHVuY29uc3RyYWluZWQgcGFyYW1ldGVyaXphdGlvbiB1c2luZwpsb2dpdCB0cmFuc2Zvcm1hdGlvbgokJApcbWJveHtsb2dpdH0oXHRoZXRhKSA9IFxsb2dcbGVmdChcZnJhY3tcdGhldGF9ezEtXHRoZXRhfVxyaWdodCkKJCQuCgpJbiB0aGUgYWJvdmUgYGZpdF9wZGZgIGZ1bmN0aW9uIHdlIHVzZWQgU3RhbidzIGB1bmNvbnN0cmFpbl9wYXJzYApmdW5jdGlvbiB0byB0cmFuc2Zvcm0gdGhldGEgdG8gbG9naXQodGhldGEpLiBJbiBSIHdlIGNhbiBhbHNvCmRlZmluZSBsb2dpdCBmdW5jdGlvbiBhcyB0aGUgaW52ZXJzZSBvZiB0aGUgbG9naXN0aWMgZnVuY3Rpb24uCgpgYGBge3J9CmxvZ2l0IDwtIHFsb2dpcwpgYGBgCgpXZSBub3cgc3dpdGNoIGxvb2tpbmcgYXQgdGhlIGRpc3RyaWJ1dGlvbnMgaW4gdGhlIHVuY29uc3RyYWluZWQgc3BhY2UuCgpgYGBge3J9CmRmX2JpbiB8PgogIG11dGF0ZShwZGZzdGFuPXNhcHBseSh0aGV0YSwgZml0X3BkZiwgZml0X2JpbikqMTEwKSB8PgogIGdncGxvdChhZXMoeD1sb2dpdCh0aGV0YSkseT1wZGZiZXRhKSkrCiAgZ2VvbV9saW5lKCkrCiAgZ2VvbV9saW5lKGFlcyh5PXBkZnN0YW4pLGNvbG9yPXNldDFbMV0pKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1sb2dpdChvcHRfYmluJGRyYXdzKClbMSwndGhldGEnXSksY29sb3I9ImdyYXkiKSsKICB4bGltKC0yLjksNi41KSsKICBsYWJzKHg9VGVYKHInKCRcbG9naXQoXHRoZXRhKSQpJykseT0nJykrCiAgdGhlbWVfcmVtb3ZlX3lheGlzKwogIGFubm90YXRlKCJ0ZXh0IiwgeD1sb2dpdCgwLjgxKSx5PTMuMyxsYWJlbD1UZVgocicoJHAoXHRoZXRhfHkpJCknKSxjb2xvcj0xLHNpemU9NSxoanVzdD0xKSsKICBhbm5vdGF0ZSgidGV4dCIsIHg9bG9naXQoMC44OSkseT00LjMsbGFiZWw9J21vZGUnLGNvbG9yPSJncmF5IixzaXplPTUsaGp1c3Q9MSkrCiAgYW5ub3RhdGUoInRleHQiLCB4PWxvZ2l0KDAuNzkpLHk9Mi45LGxhYmVsPVRlWChyJygkcShcdGhldGF8eSlcY2RvdCAxMTAkKScpLGNvbG9yPXNldDFbMV0sc2l6ZT01LGhqdXN0PTEpCmBgYGAKClRoZSBhYm92ZSBwbG90IHNob3dzIG5vdyB0aGUgZnVuY3Rpb24gdGhhdCBTdGFuIG9wdGltaXppbmcKb3B0aW1pemVzIGluIHRoZSB1bmNvbnN0cmFpbmVkIHNwYWNlLCBhbmQgdGhlIE1BUCBpcyB0aGUgbG9naXQgb2YKdGhlIE1BUCBpbiB0aGUgY29uc3RyYWluZWQgc3BhY2UuIFRodXMgaWYgc29tZW9uZSBjYXJlcyBhYm91dCB0aGUKcG9zdGVyaW9yIG1vZGUgaW4gdGhlIGNvbnN0cmFpbmVkLCBidXQgaXMgZG9pbmcgdGhlIG9wdGltaXphdGlvbiBpbgp1bmNvbnN0cmFpbmVkIHNwYWNlIHRoZXkgc3RpbGwgbmVlZCBgamFjb2JpYW49RkFMU0VgLgoKIyMgUGFyYW1ldGVyIHRyYW5zZm9ybWF0aW9uIGFuZCBKYWNvYmlhbiBhZGp1c3RtZW50CgpUaGF0IGZ1bmN0aW9uIHNob3duIGFib3ZlIGlzIG5vdCB0aGUgcG9zdGVyaW9yIG9mIGBsb2dpdCh0aGV0YSlgLiBBcwp0aGUgdHJhbnNmb3JtYXRpb24gaXMgbm9uLWxpbmVhciB3ZSBuZWVkIHRvIHRha2UgaW50byBhY2NvdW50IHRoZQpkaXN0b3J0aW9uIGNhdXNlZCBieSB0aGUgdHJhbnNmb3JtLiBUaGUgZGVuc2l0eSBtdXN0IGJlIG11bHRpcGxpZWQgYnkgYQpKYWNvYmlhbiBhZGp1c3RtZW50IGVxdWFsIHRvIHRoZSBhYnNvbHV0ZSBkZXRlcm1pbmFudCBvZiB0aGUKSmFjb2JpYW4gb2YgdGhlIHRyYW5zZm9ybS4gU2VlIG1vcmUgaW4gW1N0YW4gVXNlcidzCkd1aWRlXShodHRwczovL21jLXN0YW4ub3JnL2RvY3MvMl8yNi9zdGFuLXVzZXJzLWd1aWRlL2NoYW5nZXMtb2YtdmFyaWFibGVzLmh0bWwpLgpUaGUgSmFjb2JpYW4gZm9yIGxvd2VyIGFuZCB1cHBlciBib3VuZGVkIHNjYWxhciBpcyBnaXZlbiBpbiBbU3RhbgpSZWZlcmVuY2UKTWFudWFsXShodHRwczovL21jLXN0YW4ub3JnL2RvY3MvMl8yNS9yZWZlcmVuY2UtbWFudWFsL2xvZ2l0LXRyYW5zZm9ybS1qYWNvYmlhbi1zZWN0aW9uLmh0bWwpLCBhbmQgZm9yICgwLDEpLWJvdW5kZWQgaXQgaXMKJCQKXHRoZXRhICgxLVx0aGV0YSkuCiQkCgpTdGFuIGNhbiBkbyB0aGlzIHRyYW5zZm9ybWF0aW9uIGZvciB1cyB3aGVuIHdlIGNhbGwgbG9nX3Byb2Igd2l0aApgamFjb2JpYW49VFJVRWAKCmBgYGB7cn0KZml0X3BkZl9qYWNvYmlhbiA8LSBmdW5jdGlvbih0aCwgZml0KSB7CiAgZXhwKGZpdCRsb2dfcHJvYihmaXQkdW5jb25zdHJhaW5fdmFyaWFibGVzKGxpc3QodGhldGE9dGgpKSwKICAgICAgICAgICAgICAgICAgIGphY29iaWFuPVRSVUUpKQp9CmBgYGAKCldlIGNvbXBhcmUgdGhlIHRydWUgYWRqdXN0ZWQgcG9zdGVyaW9yIGRlbnNpdHkgaW4gbG9naXQodGhldGEpCnNwYWNlIHRvIG5vbi1hZGp1c3RlZCBkZW5zaXR5IGZ1bmN0aW9uLiBGb3IgdmlzdWFsaXphdGlvbiBwdXJwb3Nlcwp3ZSBzY2FsZSB0aGUgZnVuY3Rpb25zIHRvIGhhdmUgdGhlIHNhbWUgbWF4aW11bSwgc28gdGhleSBhcmUgbm90Cm5vcm1hbGl6ZWQgZGlzdHJpYnV0aW9ucy4KCmBgYGB7cn0KZGZfYmluIDwtIGRmX2JpbiB8PgogIG11dGF0ZShwZGZzdGFuX25vbmFkanVzdGVkPXNhcHBseSh0aGV0YSwgZml0X3BkZiwgZml0X2JpbiksCiAgICAgICAgIHBkZnN0YW5famFjb2JpYW49c2FwcGx5KHRoZXRhLCBmaXRfcGRmX2phY29iaWFuLCBmaXRfYmluKSkKZ2dwbG90KGRhdGE9ZGZfYmluLGFlcyh4PWxvZ2l0KHRoZXRhKSx5PXBkZnN0YW5fbm9uYWRqdXN0ZWQvbWF4KHBkZnN0YW5fbm9uYWRqdXN0ZWQpKSkrCiAgZ2VvbV9saW5lKGNvbG9yPXNldDFbMV0pKwogIGdlb21fbGluZShhZXMoeT1wZGZzdGFuX2phY29iaWFuL21heChwZGZzdGFuX2phY29iaWFuKSksY29sb3I9c2V0MVsyXSkrCiAgeGxpbSgtMi45LDYuNSkrCiAgbGFicyh4PVRlWChyJygkXGxvZ2l0KFx0aGV0YSkkKScpLHk9JycpKwogIHRoZW1lX3JlbW92ZV95YXhpcysKICBhbm5vdGF0ZSgidGV4dCIsIHg9MS4xNSx5PTAuOTUsbGFiZWw9VGVYKCdqYWNvYmlhbj1UUlVFJyksY29sb3I9c2V0MVsyXSxzaXplPTUsaGp1c3Q9MSkrCiAgYW5ub3RhdGUoInRleHQiLCB4PTEuMTUseT0wLjksbGFiZWw9VGVYKHInKCRxKFxsb2dpdChcdGhldGEpfHkpICA9IHEoXHRoZXRhfHkpXHRoZXRhKDEtXHRoZXRhKSQpJyksY29sb3I9c2V0MVsyXSxzaXplPTUsaGp1c3Q9MSkrCiAgYW5ub3RhdGUoInRleHQiLCB4PTIuOSx5PTAuOTUsbGFiZWw9ImphY29iaWFuPUZBTFNFIixjb2xvcj1zZXQxWzFdLHNpemU9NSxoanVzdD0wKSsKICBhbm5vdGF0ZSgidGV4dCIsIHg9Mi45LHk9MC45LGxhYmVsPVRlWChyJygkcShcdGhldGF8eSkgXG5lcSBxKFxsb2dpdChcdGhldGEpfHkpJCknKSxjb2xvcj1zZXQxWzFdLHNpemU9NSxoanVzdD0wKQpgYGBgCgpTdGFuIE1DTUMgc2FtcGxlcyBmcm9tIHRoZSBibHVlIGRpc3RyaWJ1dGlvbiB3aXRoCmBqYWNvYmlhbj1UUlVFYC4gVGhlIG1vZGUgb2YgdGhhdCBkaXN0cmlidXRpb24gaXMgZGlmZmVyZW50IGZyb20KdGhlIG1vZGUgb2YgYGphY29iaWFuPUZBTFNFYC4gSW4gZ2VuZXJhbCB0aGUgbW9kZSBpcyBub3QgaW52YXJpYW50CnRvIHRyYW5zZm9ybWF0aW9ucy4KCiMjIFdyb25nIG5vcm1hbCBhcHByb3hpbWF0aW9uCgpTdGFuIG9wdGltaXppbmcvb3B0aW1pemUgZmluZHMgdGhlIG1vZGUgb2YgYGphY29iaWFuPUZBTFNFYCAoaW4Kc29tZSBpbnRlZmFjZXMgYGphY29iaWFuPUZBTFNFYCkuIHJzdGFuYXJtIGhhcyBoYWQgYW4gb3B0aW9uIHRvIGRvCm5vcm1hbCBhcHByb3hpbWF0aW9uIGF0IHRoZSBtb2RlIGBqYWNvYmlhbj1GQUxTRWAgaW4gdGhlCnVuY29uc3RyYWluZWQgc3BhY2UgYnkgY29tcHV0aW5nIHRoZSBIZXNzaWFuIG9mIGBqYWNvYmlhbj1GQUxTRWAKYW5kIHRoZW4gc2FtcGxpbmcgaW5kZXBlbmRlbnQgZHJhd3MgZnJvbSB0aGF0IG5vcm1hbApkaXN0cmlidXRpb24uIFdlIGNhbiBkbyB0aGUgc2FtZSBpbiBDbWRTdGFuUiB3aXRoIGAkbGFwbGFjZSgpYAptZXRob2QgYW5kIG9wdGlvbiBgamFjb2JpYW49RkFMU0VgLCBidXQgdGhpcyBpcyB0aGUgd3JvbmcgdGhpbmcgdG8KZG8uCgpgYGBge3J9CmxhcF9iaW4gPC0gbW9kZWxfYmluJGxhcGxhY2UoZGF0YSA9IGRhdGFfYmluLCBqYWNvYmlhbj1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZWVkID0gU0VFRCwgZHJhd3M9NDAwMCwgcmVmcmVzaD0wKQpsYXBfZHJhd3MgPSBsYXBfYmluJGRyYXdzKGZvcm1hdCA9ICJkZiIpCmBgYGAKCldlIGFkZCB0aGUgY3VycmVudCBub3JtYWwgYXBwcm94aW1hdGlvbiB0byB0aGUgcGxvdCB3aXRoIGRhc2hlZCBsaW5lLgoKYGBgYHtyfQpsYXBfZHJhd3MgPC0gbGFwX2RyYXdzIHw+CiAgbXV0YXRlKGxvZ3A9bHBfXy1tYXgobHBfXyksCiAgICAgICAgIGxvZ2c9bHBfYXBwcm94X18tbWF4KGxwX2FwcHJveF9fKSkKZ2dwbG90KGRhdGE9ZGZfYmluLGFlcyh4PWxvZ2l0KHRoZXRhKSx5PXBkZnN0YW5fbm9uYWRqdXN0ZWQvbWF4KHBkZnN0YW5fbm9uYWRqdXN0ZWQpKSkrCiAgZ2VvbV9saW5lKGNvbG9yPXNldDFbMV0pKwogIGdlb21fbGluZShhZXMoeT1wZGZzdGFuX2phY29iaWFuL21heChwZGZzdGFuX2phY29iaWFuKSksY29sb3I9c2V0MVsyXSkrCiAgZ2VvbV9saW5lKGRhdGE9bGFwX2RyYXdzLGFlcyh4PWxvZ2l0KHRoZXRhKSx5PWV4cChsb2dnKSksY29sb3I9c2V0MVsxXSxsaW5ldHlwZT0iZGFzaGVkIikrCiAgeGxpbSgtMi45LDYuNSkrCiAgbGFicyh4PVRlWChyJygkXGxvZ2l0KFx0aGV0YSkkKScpLHk9JycpKwogIHRoZW1lX3JlbW92ZV95YXhpcysKICBhbm5vdGF0ZSgidGV4dCIsIHg9MS4xNSx5PTAuOTUsbGFiZWw9VGVYKCdqYWNvYmlhbj1UUlVFJyksY29sb3I9c2V0MVsyXSxzaXplPTUsaGp1c3Q9MSkrCiAgYW5ub3RhdGUoInRleHQiLCB4PTEuMTUseT0wLjksbGFiZWw9VGVYKHInKCRxKFxsb2dpdChcdGhldGEpfHkpID0gcShcdGhldGF8eSlcdGhldGEoMS1cdGhldGEpJCknKSxjb2xvcj1zZXQxWzJdLHNpemU9NSxoanVzdD0xKSsKICBhbm5vdGF0ZSgidGV4dCIsIHg9Mi45LHk9MC45NSxsYWJlbD0iamFjb2JpYW49RkFMU0UiLGNvbG9yPXNldDFbMV0sc2l6ZT01LGhqdXN0PTApKwogIGFubm90YXRlKCJ0ZXh0IiwgeD0yLjkseT0wLjksbGFiZWw9VGVYKHInKCRxKFx0aGV0YXx5KSBcbmVxIHEoXGxvZ2l0KFx0aGV0YSl8eSkkKScpLGNvbG9yPXNldDFbMV0sc2l6ZT01LGhqdXN0PTApCmBgYGAKClRoZSBwcm9ibGVtIGlzIHRoYXQgdGhpcyBub3JtYWwgYXBwcm94aW1hdGlvbiBpcyBxdWl0ZSBkaWZmZXJlbnQKZnJvbSB0aGUgdHJ1ZSBwb3N0ZXJpb3IgKHdpdGggYGphY29iaWFuPVRSVUVgKS4KCiMjIE5vcm1hbCBhcHByb3hpbWF0aW9uCgpSZWNlbnRseSB0aGUgbm9ybWFsIGFwcHJveGltYXRpb24gbWV0aG9kIHdhcyBpbXBsZW1lbnRlZCBpbiBTdGFuCml0c2VsZiB3aXRoIHRoZSBuYW1lIGBsYXBsYWNlYC4gVGhpcyBhcHByb3hpbWF0aW9uIHVzZXMgYnkgZGVmYXVsdApgamFjb2JpYW49VFJVRWAuIFdlIGNhbiB1c2UgTGFwbGFjZSBhcHByb3hpbWF0aW9uIHdpdGggQ21kU3RhblIKbWV0aG9kIGAkbGFwbGFjZSgpYCwgd2hpY2ggYnkgZGVmYXVsdCBpcyB1c2luZyBoZSBvcHRpb24KYGphY29iaWFuPVRSVUVgLCB3aGljaCBpcyB0aGUgY29ycmVjdCB0aGluZyB0byBkby4KCmBgYGB7cn0KbGFwX2JpbjIgPC0gbW9kZWxfYmluJGxhcGxhY2UoZGF0YT1kYXRhX2Jpbiwgc2VlZD1TRUVELCBkcmF3cz00MDAwLCByZWZyZXNoPTApCmxhcF9kcmF3czIgPC0gbGFwX2JpbjIkZHJhd3MoZm9ybWF0ID0gImRmIikKYGBgYAoKV2UgYWRkIHRoZSBzZWNvbmQgbm9ybWFsIGFwcHJveGltYXRpb24gdG8gdGhlIHBsb3QgZGFzaGVkIGxpbmUuCgpgYGBge3J9CmxhcF9kcmF3czIgPC0gbGFwX2RyYXdzMiB8PgogIG11dGF0ZShsb2dwPWxwX18tbWF4KGxwX18pLAogICAgICAgICBsb2dnPWxwX2FwcHJveF9fLW1heChscF9hcHByb3hfXykpCmdncGxvdChkYXRhPWRmX2JpbixhZXMoeD1sb2dpdCh0aGV0YSkseT1wZGZzdGFuX25vbmFkanVzdGVkL21heChwZGZzdGFuX25vbmFkanVzdGVkKSkpKwogIGdlb21fbGluZShjb2xvcj1zZXQxWzFdKSsKICBnZW9tX2xpbmUoYWVzKHk9cGRmc3Rhbl9qYWNvYmlhbi9tYXgocGRmc3Rhbl9qYWNvYmlhbikpLGNvbG9yPXNldDFbMl0pKwogIGdlb21fbGluZShkYXRhPWxhcF9kcmF3cyxhZXMoeD1sb2dpdCh0aGV0YSkseT1leHAobG9nZykpLGNvbG9yPXNldDFbMV0sbGluZXR5cGU9ImRhc2hlZCIpKwogIGdlb21fbGluZShkYXRhPWxhcF9kcmF3czIsYWVzKHg9bG9naXQodGhldGEpLHk9ZXhwKGxvZ2cpKSxjb2xvcj1zZXQxWzJdLGxpbmV0eXBlPSJkYXNoZWQiKSsKICB4bGltKC0yLjksNi41KSsKICBsYWJzKHg9VGVYKHInKCRcbG9naXQoXHRoZXRhKSQpJykseT0nJykrCiAgdGhlbWVfcmVtb3ZlX3lheGlzKwogIGFubm90YXRlKCJ0ZXh0IiwgeD0xLjE1LHk9MC45NSxsYWJlbD1UZVgoJ2phY29iaWFuPVRSVUUnKSxjb2xvcj1zZXQxWzJdLHNpemU9NSxoanVzdD0xKSsKICBhbm5vdGF0ZSgidGV4dCIsIHg9MS4xNSx5PTAuOSxsYWJlbD1UZVgocicoJHEoXGxvZ2l0KFx0aGV0YSl8eSkgPSBxKFx0aGV0YXx5KVx0aGV0YSgxLVx0aGV0YSkkKScpLGNvbG9yPXNldDFbMl0sc2l6ZT01LGhqdXN0PTEpKwogIGFubm90YXRlKCJ0ZXh0IiwgeD0yLjkseT0wLjk1LGxhYmVsPSJqYWNvYmlhbj1GQUxTRSIsY29sb3I9c2V0MVsxXSxzaXplPTUsaGp1c3Q9MCkrCiAgYW5ub3RhdGUoInRleHQiLCB4PTIuOSx5PTAuOSxsYWJlbD1UZVgocicoJHEoXHRoZXRhfHkpIFxuZXEgcShcbG9naXQoXHRoZXRhKXx5KSQpJyksY29sb3I9c2V0MVsxXSxzaXplPTUsaGp1c3Q9MCkKYGBgYAoKIyMgVHJhbnNmb3JtaW5nIGRyYXdzIHRvIHRoZSBjb25zdHJhaW5lZCBzcGFjZQoKVGhlIGRyYXdzIGZyb20gdGhlIG5vcm1hbCBhcHByb3hpbWF0aW9uIChzaG93biB3aXRoIHJ1ZyBsaW5lcyBpbiBvcAphbmQgYm90dG9tKSBjYW4gYmUgZWFzaWx5IHRyYW5zZm9ybWVkIGJhY2sgdG8gdGhlIGNvbnN0cmFpbmVkCnNwYWNlLCBhbmQgaWxsdXN0cmF0ZWQgd2l0aCBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0ZXMuIEJlZm9yZSB0aGF0CndlIHBsb3QgdGhlIGtlcm5lbCBkZW5zaXR5IGVzdGltYXRlcyBvZiB0aGUgZHJhd3MgaW4gdGhlCnVuY29uc3RyYWluZWQgc3BhY2UgdG8gc2hvdyB0aGF0IHRoaXMgZXRpbWF0ZSBpcyByZWFzb25hYmxlLgoKYGBgYHtyfQpnZ3Bsb3QoZGF0YT1kZl9iaW4sYWVzKHg9bG9naXQodGhldGEpLHk9cGRmc3Rhbl9ub25hZGp1c3RlZC9tYXgocGRmc3Rhbl9ub25hZGp1c3RlZCkpKSsKICBnZW9tX2xpbmUoY29sb3I9c2V0MVsxXSkrCiAgZ2VvbV9saW5lKGFlcyh5PXBkZnN0YW5famFjb2JpYW4vbWF4KHBkZnN0YW5famFjb2JpYW4pKSxjb2xvcj1zZXQxWzJdKSsKICBnZW9tX2xpbmUoZGF0YT1sYXBfZHJhd3MsYWVzKHg9bG9naXQodGhldGEpLHk9ZXhwKGxvZ2cpKSxjb2xvcj1zZXQxWzFdLGxpbmV0eXBlPSJkYXNoZWQiKSsKICBnZW9tX2xpbmUoZGF0YT1sYXBfZHJhd3MyLGFlcyh4PWxvZ2l0KHRoZXRhKSx5PWV4cChsb2dnKSksY29sb3I9c2V0MVsyXSxsaW5ldHlwZT0iZGFzaGVkIikrCiAgZ2VvbV9ydWcoZGF0YT1sYXBfZHJhd3NbMTo0MDAsXSxhZXMoeD1sb2dpdCh0aGV0YSkseT0wKSwgY29sb3I9c2V0MVsxXSwgYWxwaGE9MC4yLCBzaWRlcz0ndCcpKwogIGdlb21fcnVnKGRhdGE9bGFwX2RyYXdzMlsxOjQwMCxdLGFlcyh4PWxvZ2l0KHRoZXRhKSx5PTApLCBjb2xvcj1zZXQxWzJdLCBhbHBoYT0wLjIsIHNpZGVzPSdiJykrCiAgZ2VvbV9kZW5zaXR5KGRhdGE9bGFwX2RyYXdzLGFlcyh4PWxvZ2l0KHRoZXRhKSxhZnRlcl9zdGF0KHNjYWxlZCkpLGFkanVzdD0yLGNvbG9yPXNldDFbMV0sbGluZXR5cGU9ImRvdGRhc2giKSsKICBnZW9tX2RlbnNpdHkoZGF0YT1sYXBfZHJhd3MyLGFlcyh4PWxvZ2l0KHRoZXRhKSxhZnRlcl9zdGF0KHNjYWxlZCkpLGFkanVzdD0yLGNvbG9yPXNldDFbMl0sbGluZXR5cGU9ImRvdGRhc2giKSsKICB4bGltKC0yLjksNi41KSsKICBsYWJzKHg9VGVYKHInKCRcbG9naXQoXHRoZXRhKSQpJykseT0nJykrCiAgdGhlbWVfcmVtb3ZlX3lheGlzKwogIGFubm90YXRlKCJ0ZXh0IiwgeD0xLjE1LHk9MC45NSxsYWJlbD1UZVgoJ2phY29iaWFuPVRSVUUnKSxjb2xvcj1zZXQxWzJdLHNpemU9NSxoanVzdD0xKSsKICBhbm5vdGF0ZSgidGV4dCIsIHg9MS4xNSx5PTAuOSxsYWJlbD1UZVgocicoJHEoXGxvZ2l0KFx0aGV0YSl8eSkgPSBxKFx0aGV0YXx5KVx0aGV0YSgxLVx0aGV0YSkkKScpLGNvbG9yPXNldDFbMl0sc2l6ZT01LGhqdXN0PTEpKwogIGFubm90YXRlKCJ0ZXh0IiwgeD0yLjkseT0wLjk1LGxhYmVsPSJqYWNvYmlhbj1GQUxTRSIsY29sb3I9c2V0MVsxXSxzaXplPTUsaGp1c3Q9MCkrCiAgYW5ub3RhdGUoInRleHQiLCB4PTIuOSx5PTAuOSxsYWJlbD1UZVgocicoJHEoXHRoZXRhfHkpIFxuZXEgcShcbG9naXQoXHRoZXRhKXx5KSQpJyksY29sb3I9c2V0MVsxXSxzaXplPTUsaGp1c3Q9MCkKYGBgYAoKV2hlbiB3ZSBwbG90IGtlcm5lbCBkZW5zaXR5IGVzdGltYXRlcyBvZiB0aGUgbG9naXQgdHJhbnNmb3JtZWQKZHJhd3MgKGRyYXdzIHNob3duIHdpdGggcnVnIGxpbmVzIGluIHRvcCBhbmQgYm90dG9tKSBpbiB0aGUKY29uc3RyYWluZWQgc3BhY2UsIGl0J3MgY2xlYXIgd2hpY2ggZHJhd3MgYXBwcm94aW1hdGUgYmV0dGVyIHRoZQp0cnVlIHBvc3RlcmlvciAoYmxhY2sgbGluZSkKCmBgYGB7cn0KZ2dwbG90KGRhdGE9ZGZfYmluLGFlcyh4PSh0aGV0YSkseT1wZGZiZXRhL21heChwZGZiZXRhKSkpKwogIGdlb21fbGluZSgpKwogIGdlb21fZGVuc2l0eShkYXRhPWxhcF9kcmF3cyxhZXMoeD0odGhldGEpLGFmdGVyX3N0YXQoc2NhbGVkKSksYWRqdXN0PTEsY29sb3I9c2V0MVsxXSxsaW5ldHlwZT0iZG90ZGFzaCIpKwogIGdlb21fZGVuc2l0eShkYXRhPWxhcF9kcmF3czIsYWVzKHg9KHRoZXRhKSxhZnRlcl9zdGF0KHNjYWxlZCkpLGFkanVzdD0xLGNvbG9yPXNldDFbMl0sbGluZXR5cGU9ImRvdGRhc2giKSsKICBnZW9tX3J1ZyhkYXRhPWxhcF9kcmF3c1sxOjQwMCxdLGFlcyh4PSh0aGV0YSkseT0wKSwgY29sb3I9c2V0MVsxXSwgYWxwaGE9MC4yLCBzaWRlcz0ndCcpKwogIGdlb21fcnVnKGRhdGE9bGFwX2RyYXdzMlsxOjQwMCxdLGFlcyh4PSh0aGV0YSkseT0wKSwgY29sb3I9c2V0MVsyXSwgYWxwaGE9MC4yLCBzaWRlcz0nYicpKwogIGxhYnMoeD1UZVgocicoJFx0aGV0YSQpJykseT0nJykrCiAgdGhlbWVfcmVtb3ZlX3lheGlzCmBgYGAKCiMjIEltcG9ydGFuY2UgcmVzYW1wbGluZwoKYCRsYXBsYWNlKClgIG1ldGhvZCByZXR1cm5zIGFsc28gdW5vcm1hbGl6ZWQgdGFyZ2V0IGxvZyBkZW5zaXR5CihgbHBfX2ApIGFuZCBub3JtYWwgYXBwcm94aW1hdGlvbiBsb2cgZGVuc2l0eSAoYGxwX2FwcHJveF9fYCkgZm9yCnRoZSBkcmF3cyBmcm9tIHRoZSBub3JtYWwgYXBwcm94aW1hdGlvbi4gVGhlc2UgY2FuIGJlIHVzZWQgdG8KY29tcHV0ZSBpbXBvcnRhbmNlIHJhdGlvcyBgZXhwKGxwX18tbHBfYXBwcm9heF9fKWAgd2hpY2ggY2FuIGJlCnVzZWQgdG8gZG8gaW1wb3J0YW5jZSByZXNhbXBsaW5nLiBXZSBjYW4gZG8gdGhlIGltcG9ydGFuY2UKcmVzYW1wbGluZyB1c2luZyB0aGUgcG9zdGVyaW9yIHBhY2thZ2UuCgpgYGBge3J9CmxhcF9kcmF3czJpcyA8LSBsYXBfZHJhd3MyIHw+CiAgd2VpZ2h0X2RyYXdzKGxhcF9kcmF3czIkbHBfXy1sYXBfZHJhd3MyJGxwX2FwcHJveF9fLCBsb2c9VFJVRSkgfD4KICByZXNhbXBsZV9kcmF3cygpCmBgYGAKClRoZSBrZXJuZWwgZGVuc2l0eSBlc3RpbWF0ZSB1c2luZyBpbXBvcnRhbmNlIHJlc2FtcGxlZCBkcmF3cyBpcwpldmVuIGNsb3NlIHRvIHRoZSB0cnVlIGRpc3RyaWJ1dGlvbi4KCmBgYGB7cn0KZ2dwbG90KGRhdGE9ZGZfYmluLGFlcyh4PSh0aGV0YSkseT1wZGZiZXRhL21heChwZGZiZXRhKSkpKwogIGdlb21fbGluZSgpKwogIGxhYnMoeD1UZVgocicoJFx0aGV0YSQpJykseT0nJykrCiAgdGhlbWVfcmVtb3ZlX3lheGlzKwogIGdlb21fZGVuc2l0eShkYXRhPWxhcF9kcmF3czJpcyxhZXMoeD0odGhldGEpLGFmdGVyX3N0YXQoc2NhbGVkKSksYWRqdXN0PTEsY29sb3I9c2V0MVsyXSxsaW5ldHlwZT0iZG90ZGFzaCIpKwogIGdlb21fcnVnKGRhdGE9bGFwX2RyYXdzMmlzWzE6NDAwLF0sYWVzKHg9KHRoZXRhKSx5PTApLCBjb2xvcj1zZXQxWzJdLCBhbHBoYT0wLjIsIHNpZGVzPSdiJykKYGBgYAoKIyMgRGlzY3Vzc2lvbgoKVGhlIG5vcm1hbCBhcHByb3hpbWF0aW9uIGFuZCBpbXBvcnRhbmNlIHJlc2FtcGxpbmcgZGlkIHdvcmsgcXVpdGUKd2VsbCBpbiB0aGlzIHNpbXBsZSBvbmUgZGltZW5zaW9uYWwgY2FzZSwgYnV0IGluIGdlbmVyYWwgdGhlIG5vcm1hbAphcHByb3hpbWF0aW9uIGZvciBpbXBvcnRhbmNlIHNhbXBsaW5nIHdvcmtzIHdlbGwgb25seSBpbiBxdWl0ZSBsb3cKZGltZW5zaW9uYWwgc2V0dGluZ3Mgb3Igd2hlbiB0aGUgcG9zdGVyaW9yIGluIHRoZSB1bmNvbnN0cmFpbmVkCnNwYWNlIGlzIHZlcnkgY2xvc2UgdG8gbm9ybWFsLgoK