See also - Andrew Gelman, Ben Goodrich, Jonah Gabry, and Aki Vehtari (2018). R-squared for Bayesian regression models. The American Statistician, 73:307-209 doi:10.1080/00031305.2018.1549100.
Introduction
Gelman, Goodrich, Gabry, and Vehtari (2018) define Bayesian \(R^2\) as \[
R^2 = \frac{\mathrm{Var}_{\mu}}{\mathrm{Var}_{\mu}+\mathrm{Var}_\mathrm{res}},
\] where \(\mathrm{Var}_{\mu}\) is variance of modelled predictive means, and \(\mathrm{Var}_\mathrm{res}\) is the modelled residual variance. Specifically both of these are computed only using posterior quantities from the fitted model. The model based \(R^2\) uses draws from the model and residual variances. For linear regression \(\mu_n=X_n\beta\) we define \[
\mathrm{Var}_\mathrm{\mu}^s = V_{n=1}^N \mu_n^s\\
\mathrm{Var}_\mathrm{res}^s = (\sigma^2)^s,
\] and for logistic regression, following Tjur (2009), we define \(\mu_n=\pi_n\) and \[
\mathrm{Var}_\mathrm{\mu}^s = V_{n=1}^N \mu_n^s\\
\mathrm{Var}_\mathrm{res}^s = \frac{1}{N}\sum_{n=1}^N (\pi_n^s(1-\pi_n^s)),
\] where \(\pi_n^s\) are predicted probabilities.
Load packages
library("rprojroot")
root<-has_file(".ROS-Examples-root")$make_fix_file()
library("rstanarm")
library("ggplot2")
library("bayesplot")
theme_set(bayesplot::theme_default(base_family = "sans"))
library("foreign")
# for reproducability
SEED <- 1800
set.seed(SEED)
Function for Bayesian R-squared for stan_glm models.
Bayes-R2 function using modelled (approximate) residual variance
bayes_R2 <- function(fit) {
mupred <- rstanarm::posterior_linpred(fit, transform = TRUE)
var_mupred <- apply(mupred, 1, var)
if (family(fit)$family == "binomial" && NCOL(y) == 1) {
sigma2 <- apply(mupred*(1-mupred), 1, mean)
} else {
sigma2 <- as.matrix(fit, pars = c("sigma"))^2
}
var_mupred / (var_mupred + sigma2)
}
Toy data with n=5
x <- 1:5 - 3
y <- c(1.7, 2.6, 2.5, 4.4, 3.8) - 3
xy <- data.frame(x,y)
Lsq fit
fit <- lm(y ~ x, data = xy)
ols_coef <- coef(fit)
yhat <- ols_coef[1] + ols_coef[2] * x
r <- y - yhat
rsq_1 <- var(yhat)/(var(y))
rsq_2 <- var(yhat)/(var(yhat) + var(r))
round(c(rsq_1, rsq_2), 3)
[1] 0.766 0.766
Bayes fit
fit_bayes <- stan_glm(y ~ x, data = xy,
prior_intercept = normal(0, 0.2, autoscale = FALSE),
prior = normal(1, 0.2, autoscale = FALSE),
prior_aux = NULL,
seed = SEED, refresh = 0
)
posterior <- as.matrix(fit_bayes, pars = c("(Intercept)", "x"))
post_means <- colMeans(posterior)
Base graphics version
par(mar=c(3,3,1,1), mgp=c(1.7,.5,0), tck=-.01)
plot(
x, y,
ylim = range(x),
xlab = "x",
ylab = "y",
main = "Least squares and Bayes fits",
bty = "l",
pch = 20
)
abline(coef(fit)[1], coef(fit)[2], col = "black")
text(-1.6,-.7, "Least-squares\nfit", cex = .9)
abline(0, 1, col = "blue", lty = 2)
text(-1, -1.8, "(Prior regression line)", col = "blue", cex = .9)
abline(coef(fit_bayes)[1], coef(fit_bayes)[2], col = "blue")
text(1.4, 1.2, "Posterior mean fit", col = "blue", cex = .9)
points(
x,
coef(fit_bayes)[1] + coef(fit_bayes)[2] * x,
pch = 20,
col = "blue"
)
par(mar=c(3,3,1,1), mgp=c(1.7,.5,0), tck=-.01)
plot(
x, y,
ylim = range(x),
xlab = "x",
ylab = "y",
bty = "l",
pch = 20,
main = "Bayes posterior simulations"
)
for (s in 1:nrow(samp_20_draws)) {
abline(samp_20_draws[s, 1], samp_20_draws[s, 2], col = "#9497eb")
}
abline(
coef(fit_bayes)[1],
coef(fit_bayes)[2],
col = "#1c35c4",
lwd = 2
)
points(x, y, pch = 20, col = "black")
ggplot version
theme_update(
plot.title = element_text(face = "bold", hjust = 0.5),
axis.text = element_text(size = rel(1.1))
)
fig_1a <-
ggplot(xy, aes(x, y)) +
geom_point() +
geom_abline( # ols regression line
intercept = ols_coef[1],
slope = ols_coef[2],
size = 0.4
) +
geom_abline( # prior regression line
intercept = 0,
slope = 1,
color = "blue",
linetype = 2,
size = 0.3
) +
geom_abline( # posterior mean regression line
intercept = post_means[1],
slope = post_means[2],
color = "blue",
size = 0.4
) +
geom_point(
aes(y = post_means[1] + post_means[2] * x),
color = "blue"
) +
annotate(
geom = "text",
x = c(-1.6, -1, 1.4),
y = c(-0.7, -1.8, 1.2),
label = c(
"Least-squares\nfit",
"(Prior regression line)",
"Posterior mean fit"
),
color = c("black", "blue", "blue"),
size = 3.8
) +
ylim(range(x)) +
ggtitle("Least squares and Bayes fits")
plot(fig_1a)
fig_1b <-
ggplot(xy, aes(x, y)) +
geom_abline( # 20 posterior draws of the regression line
intercept = samp_20_draws[, 1],
slope = samp_20_draws[, 2],
color = "#9497eb",
size = 0.25
) +
geom_abline( # posterior mean regression line
intercept = post_means[1],
slope = post_means[2],
color = "#1c35c4",
size = 1
) +
geom_point() +
ylim(range(x)) +
ggtitle("Bayes posterior simulations")
plot(fig_1b)
Bayesian R^2 Posterior and median
mcmc_hist(data.frame(bayesR2), binwidth=0.02) + xlim(c(0,1)) +
scale_y_continuous(breaks=NULL) +
xlab('Bayesian R2') +
geom_vline(xintercept=median(bayesR2)) +
ggtitle('Bayesian R squared posterior and median')
Toy logistic regression example, n=20
set.seed(20)
y<-rbinom(n=20,size=1,prob=(1:20-0.5)/20)
data <- data.frame(rvote=y, income=1:20)
fit_logit <- stan_glm(rvote ~ income, family=binomial(link="logit"), data=data,
refresh=0)
Plot posterior of Bayesian R^2
pxl<-xlim(0,1)
mcmc_hist(data.frame(bayesR2), binwidth=0.02) + pxl +
scale_y_continuous(breaks=NULL) +
xlab('Bayesian R2') +
geom_vline(xintercept=median(bayesR2))
Mesquite - linear regression
Predicting the yields of mesquite bushes, n=46
Load data
mesquite <- read.table(root("Mesquite/data","mesquite.dat"), header=TRUE)
mesquite$canopy_volume <- mesquite$diam1 * mesquite$diam2 * mesquite$canopy_height
mesquite$canopy_area <- mesquite$diam1 * mesquite$diam2
mesquite$canopy_shape <- mesquite$diam1 / mesquite$diam2
(n <- nrow(mesquite))
[1] 46
Predict log weight model with log canopy volume, log canopy shape, and group
fit_5 <- stan_glm(log(weight) ~ log(canopy_volume) + log(canopy_shape) +
group, data=mesquite, refresh=0)
Plot posterior of Bayesian R2
pxl<-xlim(0.6, 0.95)
mcmc_hist(data.frame(bayesR2), binwidth=0.01) + pxl +
scale_y_continuous(breaks=NULL) +
xlab('Bayesian R2') +
geom_vline(xintercept=median(bayesR2))
LowBwt -- logistic regression
Predict low birth weight, n=189, from Hosmer et al (2000). This data was also used by Tjur (2009)
Load data
lowbwt <- read.table(root("LowBwt/data","lowbwt.dat"), header=TRUE)
(n <- nrow(lowbwt))
[1] 189
head(lowbwt)
ID low age lwt race smoke ptl ht ui ftv bwt
1 85 0 19 182 2 0 0 0 1 0 2523
2 86 0 33 155 3 0 0 0 0 3 2551
3 87 0 20 105 1 1 0 0 0 1 2557
4 88 0 21 108 1 1 0 0 1 2 2594
5 89 0 18 107 1 1 0 0 1 0 2600
6 91 0 21 124 3 0 0 0 0 0 2622
Predict low birth weight
fit_1 <- stan_glm(low ~ age + lwt + factor(race) + smoke,
family=binomial(link="logit"), data=lowbwt, refresh=0)
Plot posterior of Bayesian R2
pxl<-xlim(0, 0.3)
mcmc_hist(data.frame(bayesR2), binwidth=0.01) + pxl +
scale_y_continuous(breaks=NULL) +
xlab('Bayesian R2') +
geom_vline(xintercept=median(bayesR2))
KidIQ - linear regression
Children's test scores data, n=434
Load children's test scores data
kidiq <- read.csv(root("KidIQ/data","kidiq.csv"))
(n <- nrow(kidiq))
[1] 434
head(kidiq)
kid_score mom_hs mom_iq mom_work mom_age
1 65 1 121.11753 4 27
2 98 1 89.36188 4 25
3 85 1 115.44316 4 27
4 83 1 99.44964 3 25
5 115 1 92.74571 4 27
6 98 0 107.90184 1 18
Predict test score
fit_3 <- stan_glm(kid_score ~ mom_hs + mom_iq, data=kidiq,
seed=SEED, refresh=0)
Plot posterior of Bayesian R2
pxl<-xlim(0.05, 0.35)
mcmc_hist(data.frame(bayesR2), binwidth=0.01) + pxl +
scale_y_continuous(breaks=NULL) +
xlab('Bayesian R2') +
geom_vline(xintercept=median(bayesR2))
Add five pure noise predictors to the data
set.seed(1507)
n <- nrow(kidiq)
kidiqr <- kidiq
kidiqr$noise <- array(rnorm(5*n), c(n,5))
Linear regression with additional noise predictors
fit_3n <- stan_glm(kid_score ~ mom_hs + mom_iq + noise, data=kidiqr,
seed=SEED, refresh=0)
print(fit_3n)
stan_glm
family: gaussian [identity]
formula: kid_score ~ mom_hs + mom_iq + noise
observations: 434
predictors: 8
------
Median MAD_SD
(Intercept) 25.7 5.8
mom_hs 6.0 2.3
mom_iq 0.6 0.1
noise1 -0.1 0.8
noise2 -1.3 0.9
noise3 0.0 1.0
noise4 0.2 0.9
noise5 -0.5 0.9
Auxiliary parameter(s):
Median MAD_SD
sigma 18.2 0.6
------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg
Plot posterior of Bayesian R2
pxl<-xlim(0.05, 0.35)
mcmc_hist(data.frame(bayesR2n), binwidth=0.01) + pxl +
scale_y_continuous(breaks=NULL) +
xlab('Bayesian R2') +
geom_vline(xintercept=median(bayesR2))
Earnings - logistic and linear regression
Predict respondents' yearly earnings using survey data from 1990. logistic regression n=1374, linear regression n=1187
Load data
earnings <- read.csv(root("Earnings/data","earnings.csv"))
(n <- nrow(earnings))
[1] 1816
head(earnings)
height weight male earn earnk ethnicity education mother_education
1 74 210 1 50000 50 White 16 16
2 66 125 0 60000 60 White 16 16
3 64 126 0 30000 30 White 16 16
4 65 200 0 25000 25 White 17 17
5 63 110 0 50000 50 Other 16 16
6 68 165 0 62000 62 Black 18 18
father_education walk exercise smokenow tense angry age
1 16 3 3 2 0 0 45
2 16 6 5 1 0 0 58
3 16 8 1 2 1 1 29
4 NA 8 1 2 0 0 57
5 16 5 6 2 0 0 91
6 18 1 1 2 2 2 54
Bayesian logistic regression on non-zero earnings
Predict using height and sex
fit_1a <- stan_glm((earn>0) ~ height + male,
family = binomial(link = "logit"),
data = earnings, refresh=0)
Plot posterior of Bayesian R2
pxl<-xlim(0.02, 0.11)
mcmc_hist(data.frame(bayesR2), binwidth=0.002) + pxl +
scale_y_continuous(breaks=NULL) +
xlab('Bayesian R2') +
geom_vline(xintercept=median(bayesR2))
Bayesian probit regression on non-zero earnings
fit_1p <- stan_glm((earn>0) ~ height + male,
family = binomial(link = "probit"),
data = earnings, refresh=0)
Compare logistic and probit models using new Bayesian R2
pxl<-xlim(0.02, 0.11)
p1<-mcmc_hist(data.frame(bayesR2), binwidth=0.002) + pxl +
scale_y_continuous(breaks=NULL) +
ggtitle('Earnings data with n=1374') +
xlab('Bayesian R2 for logistic model') +
geom_vline(xintercept=median(bayesR2))
p2<-mcmc_hist(data.frame(bayesR2p), binwidth=0.002) + pxl +
scale_y_continuous(breaks=NULL) +
xlab('Bayesian R2 for probit model') +
geom_vline(xintercept=median(bayesR2p))
bayesplot_grid(p1,p2)
There is no practical difference in predictive performance between logit and probit.
Bayesian model on positive earnings on log scale
fit_1b <- stan_glm(log(earn) ~ height + male, data = earnings, subset=earn>0,
refresh=0)
Plot posterior of Bayesian R2
pxl<-xlim(0.02, 0.15)
mcmc_hist(data.frame(bayesR2), binwidth=0.002) + pxl +
scale_y_continuous(breaks=NULL) +
xlab('Bayesian R2') +
geom_vline(xintercept=median(bayesR2))
LS0tCnRpdGxlOiAiUmVncmVzc2lvbiBhbmQgT3RoZXIgU3RvcmllczogQmF5ZXNpYW4gJFJeMiQiCmF1dGhvcjogIkFuZHJldyBHZWxtYW4sIEplbm5pZmVyIEhpbGwsIEFraSBWZWh0YXJpIgpkYXRlOiAiYHIgZm9ybWF0KFN5cy5EYXRlKCkpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogcmVhZGFibGUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCkJheWVzaWFuICRSXjIkLiBTZWUgQ2hhcHRlciAxMSBpbiBSZWdyZXNzaW9uIGFuZCBPdGhlciBTdG9yaWVzLgoKU2VlIGFsc28KLSBBbmRyZXcgR2VsbWFuLCBCZW4gR29vZHJpY2gsIEpvbmFoIEdhYnJ5LCBhbmQgQWtpIFZlaHRhcmkgKDIwMTgpLgogIFItc3F1YXJlZCBmb3IgQmF5ZXNpYW4gcmVncmVzc2lvbiBtb2RlbHMuIFRoZSBBbWVyaWNhbiBTdGF0aXN0aWNpYW4sIDczOjMwNy0yMDkKICBbZG9pOjEwLjEwODAvMDAwMzEzMDUuMjAxOC4xNTQ5MTAwXShodHRwczovL2RvaS5vcmcvMTAuMTA4MC8wMDAzMTMwNS4yMDE4LjE1NDkxMDApLgoKLS0tLS0tLS0tLS0tLQoKIyMgSW50cm9kdWN0aW9uCgpHZWxtYW4sIEdvb2RyaWNoLCBHYWJyeSwgYW5kIFZlaHRhcmkgKDIwMTgpIGRlZmluZSBCYXllc2lhbiAkUl4yJCBhcwokJApSXjIgPSBcZnJhY3tcbWF0aHJte1Zhcn1fe1xtdX19e1xtYXRocm17VmFyfV97XG11fStcbWF0aHJte1Zhcn1fXG1hdGhybXtyZXN9fSwKJCQKd2hlcmUgJFxtYXRocm17VmFyfV97XG11fSQgaXMgdmFyaWFuY2Ugb2YgbW9kZWxsZWQgcHJlZGljdGl2ZSBtZWFucywKYW5kICRcbWF0aHJte1Zhcn1fXG1hdGhybXtyZXN9JCBpcyB0aGUgbW9kZWxsZWQgcmVzaWR1YWwgdmFyaWFuY2UuClNwZWNpZmljYWxseSBib3RoIG9mIHRoZXNlIGFyZSBjb21wdXRlZCBvbmx5IHVzaW5nIHBvc3RlcmlvcgpxdWFudGl0aWVzIGZyb20gdGhlIGZpdHRlZCBtb2RlbC4KVGhlIG1vZGVsIGJhc2VkICRSXjIkIHVzZXMgZHJhd3MgZnJvbSB0aGUgbW9kZWwgYW5kIHJlc2lkdWFsIHZhcmlhbmNlcy4KRm9yIGxpbmVhciByZWdyZXNzaW9uICRcbXVfbj1YX25cYmV0YSQgd2UgZGVmaW5lCiQkClxtYXRocm17VmFyfV9cbWF0aHJte1xtdX1ecyA9IFZfe249MX1eTiBcbXVfbl5zXFwKXG1hdGhybXtWYXJ9X1xtYXRocm17cmVzfV5zID0gKFxzaWdtYV4yKV5zLAokJAphbmQgZm9yIGxvZ2lzdGljIHJlZ3Jlc3Npb24sIGZvbGxvd2luZyBUanVyICgyMDA5KSwKd2UgZGVmaW5lICRcbXVfbj1ccGlfbiQgYW5kIAokJApcbWF0aHJte1Zhcn1fXG1hdGhybXtcbXV9XnMgPSBWX3tuPTF9Xk4gXG11X25ec1xcClxtYXRocm17VmFyfV9cbWF0aHJte3Jlc31ecyA9IFxmcmFjezF9e059XHN1bV97bj0xfV5OIChccGlfbl5zKDEtXHBpX25ecykpLAokJAp3aGVyZSAkXHBpX25ecyQgYXJlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzLgoKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNvbW1lbnQ9TkEpCiMgc3dpdGNoIHRoaXMgdG8gVFJVRSB0byBzYXZlIGZpZ3VyZXMgaW4gc2VwYXJhdGUgZmlsZXMKc2F2ZWZpZ3MgPC0gRkFMU0UKYGBgCgojIyMjIExvYWQgcGFja2FnZXMKCmBgYHtyIH0KbGlicmFyeSgicnByb2pyb290IikKcm9vdDwtaGFzX2ZpbGUoIi5ST1MtRXhhbXBsZXMtcm9vdCIpJG1ha2VfZml4X2ZpbGUoKQpsaWJyYXJ5KCJyc3RhbmFybSIpCmxpYnJhcnkoImdncGxvdDIiKQpsaWJyYXJ5KCJiYXllc3Bsb3QiKQp0aGVtZV9zZXQoYmF5ZXNwbG90Ojp0aGVtZV9kZWZhdWx0KGJhc2VfZmFtaWx5ID0gInNhbnMiKSkKbGlicmFyeSgiZm9yZWlnbiIpCiMgZm9yIHJlcHJvZHVjYWJpbGl0eQpTRUVEIDwtIDE4MDAKc2V0LnNlZWQoU0VFRCkKYGBgCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CiMgZ3JheXNjYWxlIGZpZ3VyZXMgZm9yIHRoZSBib29rCmlmIChzYXZlZmlncykgY29sb3Jfc2NoZW1lX3NldChzY2hlbWUgPSAiZ3JheSIpCmBgYAoKIyMjIyBGdW5jdGlvbiBmb3IgQmF5ZXNpYW4gUi1zcXVhcmVkIGZvciBzdGFuX2dsbSBtb2RlbHMuIAoKQmF5ZXMtUjIgZnVuY3Rpb24gdXNpbmcgbW9kZWxsZWQgKGFwcHJveGltYXRlKSByZXNpZHVhbCB2YXJpYW5jZQoKYGBge3IgfQpiYXllc19SMiA8LSBmdW5jdGlvbihmaXQpIHsKICBtdXByZWQgPC0gcnN0YW5hcm06OnBvc3Rlcmlvcl9saW5wcmVkKGZpdCwgdHJhbnNmb3JtID0gVFJVRSkKICB2YXJfbXVwcmVkIDwtIGFwcGx5KG11cHJlZCwgMSwgdmFyKQogIGlmIChmYW1pbHkoZml0KSRmYW1pbHkgPT0gImJpbm9taWFsIiAmJiBOQ09MKHkpID09IDEpIHsKICAgICAgc2lnbWEyIDwtIGFwcGx5KG11cHJlZCooMS1tdXByZWQpLCAxLCBtZWFuKQogIH0gZWxzZSB7CiAgICAgIHNpZ21hMiA8LSBhcy5tYXRyaXgoZml0LCBwYXJzID0gYygic2lnbWEiKSleMgogIH0KICB2YXJfbXVwcmVkIC8gKHZhcl9tdXByZWQgKyBzaWdtYTIpCn0KYGBgCgojIyBUb3kgZGF0YSB3aXRoIG49NQoKYGBge3IgfQp4IDwtIDE6NSAtIDMKeSA8LSBjKDEuNywgMi42LCAyLjUsIDQuNCwgMy44KSAtIDMKeHkgPC0gZGF0YS5mcmFtZSh4LHkpCmBgYAoKIyMjIyBMc3EgZml0CgpgYGB7ciB9CmZpdCA8LSBsbSh5IH4geCwgZGF0YSA9IHh5KQpvbHNfY29lZiA8LSBjb2VmKGZpdCkKeWhhdCA8LSBvbHNfY29lZlsxXSArIG9sc19jb2VmWzJdICogeApyIDwtIHkgLSB5aGF0CnJzcV8xIDwtIHZhcih5aGF0KS8odmFyKHkpKQpyc3FfMiA8LSB2YXIoeWhhdCkvKHZhcih5aGF0KSArIHZhcihyKSkKcm91bmQoYyhyc3FfMSwgcnNxXzIpLCAzKQpgYGAKCiMjIyMgQmF5ZXMgZml0CgpgYGB7ciB9CmZpdF9iYXllcyA8LSBzdGFuX2dsbSh5IH4geCwgZGF0YSA9IHh5LAogIHByaW9yX2ludGVyY2VwdCA9IG5vcm1hbCgwLCAwLjIsIGF1dG9zY2FsZSA9IEZBTFNFKSwKICBwcmlvciA9IG5vcm1hbCgxLCAwLjIsIGF1dG9zY2FsZSA9IEZBTFNFKSwKICBwcmlvcl9hdXggPSBOVUxMLAogIHNlZWQgPSBTRUVELCByZWZyZXNoID0gMAopCnBvc3RlcmlvciA8LSBhcy5tYXRyaXgoZml0X2JheWVzLCBwYXJzID0gYygiKEludGVyY2VwdCkiLCAieCIpKQpwb3N0X21lYW5zIDwtIGNvbE1lYW5zKHBvc3RlcmlvcikKYGBgCgojIyMjIE1lZGlhbiBCYXllc2lhbiBSXjIKCmBgYHtyIH0Kcm91bmQobWVkaWFuKGJheWVzUjI8LWJheWVzX1IyKGZpdF9iYXllcykpLCAyKQpgYGAKCiMjIyMgRmlndXJlcwoKVGhlIGZpcnN0IHNlY3Rpb24gb2YgY29kZSBiZWxvdyBjcmVhdGVzIHBsb3RzIHVzaW5nIGJhc2UgUiBncmFwaGljcy4gPC9icj4KQmVsb3cgdGhhdCB0aGVyZSBpcyBjb2RlIHRvIHByb2R1Y2UgdGhlIHBsb3RzIHVzaW5nIGdncGxvdDIuCgpgYGB7ciB9CiMgdGFrZSBhIHNhbXBsZSBvZiAyMCBwb3N0ZXJpb3IgZHJhd3MKa2VlcCA8LSBzYW1wbGUobnJvdyhwb3N0ZXJpb3IpLCAyMCkKc2FtcF8yMF9kcmF3cyA8LSBwb3N0ZXJpb3Jba2VlcCwgXQpgYGAKCiMjIyMgQmFzZSBncmFwaGljcyB2ZXJzaW9uCgpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIHBkZigiZmlnL3JzcXVhcmVkMWEucGRmIiwgaGVpZ2h0PTQsIHdpZHRoPTUpCmBgYApgYGB7ciB9CnBhcihtYXI9YygzLDMsMSwxKSwgbWdwPWMoMS43LC41LDApLCB0Y2s9LS4wMSkKcGxvdCgKICB4LCB5LAogIHlsaW0gPSByYW5nZSh4KSwKICB4bGFiID0gIngiLAogIHlsYWIgPSAieSIsCiAgbWFpbiA9ICJMZWFzdCBzcXVhcmVzIGFuZCBCYXllcyBmaXRzIiwKICBidHkgPSAibCIsCiAgcGNoID0gMjAKKQphYmxpbmUoY29lZihmaXQpWzFdLCBjb2VmKGZpdClbMl0sIGNvbCA9ICJibGFjayIpCnRleHQoLTEuNiwtLjcsICJMZWFzdC1zcXVhcmVzXG5maXQiLCBjZXggPSAuOSkKYWJsaW5lKDAsIDEsIGNvbCA9ICJibHVlIiwgbHR5ID0gMikKdGV4dCgtMSwgLTEuOCwgIihQcmlvciByZWdyZXNzaW9uIGxpbmUpIiwgY29sID0gImJsdWUiLCBjZXggPSAuOSkKYWJsaW5lKGNvZWYoZml0X2JheWVzKVsxXSwgY29lZihmaXRfYmF5ZXMpWzJdLCBjb2wgPSAiYmx1ZSIpCnRleHQoMS40LCAxLjIsICJQb3N0ZXJpb3IgbWVhbiBmaXQiLCBjb2wgPSAiYmx1ZSIsIGNleCA9IC45KQpwb2ludHMoCiAgeCwKICBjb2VmKGZpdF9iYXllcylbMV0gKyBjb2VmKGZpdF9iYXllcylbMl0gKiB4LAogIHBjaCA9IDIwLAogIGNvbCA9ICJibHVlIgopCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQoKYGBgCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgcGRmKCJmaWcvcnNxdWFyZWQxYi5wZGYiLCBoZWlnaHQ9NCwgd2lkdGg9NSkKYGBgCmBgYHtyIH0KcGFyKG1hcj1jKDMsMywxLDEpLCBtZ3A9YygxLjcsLjUsMCksIHRjaz0tLjAxKQpwbG90KAogIHgsIHksCiAgeWxpbSA9IHJhbmdlKHgpLAogIHhsYWIgPSAieCIsCiAgeWxhYiA9ICJ5IiwKICBidHkgPSAibCIsCiAgcGNoID0gMjAsCiAgbWFpbiA9ICJCYXllcyBwb3N0ZXJpb3Igc2ltdWxhdGlvbnMiCikKZm9yIChzIGluIDE6bnJvdyhzYW1wXzIwX2RyYXdzKSkgewogIGFibGluZShzYW1wXzIwX2RyYXdzW3MsIDFdLCBzYW1wXzIwX2RyYXdzW3MsIDJdLCBjb2wgPSAiIzk0OTdlYiIpCn0KYWJsaW5lKAogIGNvZWYoZml0X2JheWVzKVsxXSwKICBjb2VmKGZpdF9iYXllcylbMl0sCiAgY29sID0gIiMxYzM1YzQiLAogIGx3ZCA9IDIKKQpwb2ludHMoeCwgeSwgcGNoID0gMjAsIGNvbCA9ICJibGFjayIpCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQpgYGAKCiMjIyMgZ2dwbG90IHZlcnNpb24KCmBgYHtyIH0KdGhlbWVfdXBkYXRlKAogIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLCAKICBheGlzLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IHJlbCgxLjEpKQopCmZpZ18xYSA8LQogIGdncGxvdCh4eSwgYWVzKHgsIHkpKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2FibGluZSggIyBvbHMgcmVncmVzc2lvbiBsaW5lCiAgICBpbnRlcmNlcHQgPSBvbHNfY29lZlsxXSwKICAgIHNsb3BlID0gb2xzX2NvZWZbMl0sCiAgICBzaXplID0gMC40CiAgKSArCiAgZ2VvbV9hYmxpbmUoICMgcHJpb3IgcmVncmVzc2lvbiBsaW5lCiAgICBpbnRlcmNlcHQgPSAwLAogICAgc2xvcGUgPSAxLAogICAgY29sb3IgPSAiYmx1ZSIsCiAgICBsaW5ldHlwZSA9IDIsCiAgICBzaXplID0gMC4zCiAgKSArCiAgZ2VvbV9hYmxpbmUoICMgcG9zdGVyaW9yIG1lYW4gcmVncmVzc2lvbiBsaW5lCiAgICBpbnRlcmNlcHQgPSBwb3N0X21lYW5zWzFdLAogICAgc2xvcGUgPSBwb3N0X21lYW5zWzJdLAogICAgY29sb3IgPSAiYmx1ZSIsCiAgICBzaXplID0gMC40CiAgKSArCiAgZ2VvbV9wb2ludCgKICAgIGFlcyh5ID0gcG9zdF9tZWFuc1sxXSArIHBvc3RfbWVhbnNbMl0gKiB4KSwKICAgIGNvbG9yID0gImJsdWUiCiAgKSArCiAgYW5ub3RhdGUoCiAgICBnZW9tID0gInRleHQiLAogICAgeCA9IGMoLTEuNiwgLTEsIDEuNCksCiAgICB5ID0gYygtMC43LCAtMS44LCAxLjIpLAogICAgbGFiZWwgPSBjKAogICAgICAiTGVhc3Qtc3F1YXJlc1xuZml0IiwKICAgICAgIihQcmlvciByZWdyZXNzaW9uIGxpbmUpIiwKICAgICAgIlBvc3RlcmlvciBtZWFuIGZpdCIKICAgICksCiAgICBjb2xvciA9IGMoImJsYWNrIiwgImJsdWUiLCAiYmx1ZSIpLAogICAgc2l6ZSA9IDMuOAogICkgKwogIHlsaW0ocmFuZ2UoeCkpICsgCiAgZ2d0aXRsZSgiTGVhc3Qgc3F1YXJlcyBhbmQgQmF5ZXMgZml0cyIpCnBsb3QoZmlnXzFhKQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBnZ3NhdmUoImZpZy9yc3F1YXJlZDFhLWdnLnBkZiIsIHdpZHRoID0gNSwgaGVpZ2h0ID0gNCkKYGBgCmBgYHtyIH0KZmlnXzFiIDwtCiAgZ2dwbG90KHh5LCBhZXMoeCwgeSkpICsKICBnZW9tX2FibGluZSggIyAyMCBwb3N0ZXJpb3IgZHJhd3Mgb2YgdGhlIHJlZ3Jlc3Npb24gbGluZQogICAgaW50ZXJjZXB0ID0gc2FtcF8yMF9kcmF3c1ssIDFdLAogICAgc2xvcGUgPSBzYW1wXzIwX2RyYXdzWywgMl0sCiAgICBjb2xvciA9ICIjOTQ5N2ViIiwKICAgIHNpemUgPSAwLjI1CiAgKSArCiAgZ2VvbV9hYmxpbmUoICMgcG9zdGVyaW9yIG1lYW4gcmVncmVzc2lvbiBsaW5lCiAgICBpbnRlcmNlcHQgPSBwb3N0X21lYW5zWzFdLAogICAgc2xvcGUgPSBwb3N0X21lYW5zWzJdLAogICAgY29sb3IgPSAiIzFjMzVjNCIsCiAgICBzaXplID0gMQogICkgKwogIGdlb21fcG9pbnQoKSArCiAgeWxpbShyYW5nZSh4KSkgKyAKICBnZ3RpdGxlKCJCYXllcyBwb3N0ZXJpb3Igc2ltdWxhdGlvbnMiKQpwbG90KGZpZ18xYikKYGBgCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgZ2dzYXZlKCJmaWcvcnNxdWFyZWQxYi1nZy5wZGYiLCB3aWR0aCA9IDUsIGhlaWdodCA9IDQpCmBgYAoKIyMjIyBCYXllc2lhbiBSXjIgUG9zdGVyaW9yIGFuZCBtZWRpYW4KCmBgYHtyIH0KbWNtY19oaXN0KGRhdGEuZnJhbWUoYmF5ZXNSMiksIGJpbndpZHRoPTAuMDIpICsgeGxpbShjKDAsMSkpICsKICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3M9TlVMTCkgKwogICAgeGxhYignQmF5ZXNpYW4gUjInKSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9bWVkaWFuKGJheWVzUjIpKSArCiAgICBnZ3RpdGxlKCdCYXllc2lhbiBSIHNxdWFyZWQgcG9zdGVyaW9yIGFuZCBtZWRpYW4nKQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBnZ3NhdmUoImZpZy9iYXllc3IycG9zdC5wZGYiLCB3aWR0aCA9IDUsIGhlaWdodCA9IDQpCmBgYAoKIyMgVG95IGxvZ2lzdGljIHJlZ3Jlc3Npb24gZXhhbXBsZSwgbj0yMAoKYGBge3IgfQpzZXQuc2VlZCgyMCkKeTwtcmJpbm9tKG49MjAsc2l6ZT0xLHByb2I9KDE6MjAtMC41KS8yMCkKZGF0YSA8LSBkYXRhLmZyYW1lKHJ2b3RlPXksIGluY29tZT0xOjIwKQpmaXRfbG9naXQgPC0gc3Rhbl9nbG0ocnZvdGUgfiBpbmNvbWUsIGZhbWlseT1iaW5vbWlhbChsaW5rPSJsb2dpdCIpLCBkYXRhPWRhdGEsCiAgICAgICAgICAgICAgICAgICAgICByZWZyZXNoPTApCmBgYAoKIyMjIyBNZWRpYW4gQmF5ZXNpYW4gUl4yCgpgYGB7ciB9CnJvdW5kKG1lZGlhbihiYXllc1IyPC1iYXllc19SMihmaXRfbG9naXQpKSwgMikKYGBgCgojIyMjIFBsb3QgcG9zdGVyaW9yIG9mIEJheWVzaWFuIFJeMgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnB4bDwteGxpbSgwLDEpCm1jbWNfaGlzdChkYXRhLmZyYW1lKGJheWVzUjIpLCBiaW53aWR0aD0wLjAyKSArIHB4bCArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzPU5VTEwpICsKICAgIHhsYWIoJ0JheWVzaWFuIFIyJykgKwogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PW1lZGlhbihiYXllc1IyKSkKYGBgCgojIyBNZXNxdWl0ZSAtIGxpbmVhciByZWdyZXNzaW9uCgpQcmVkaWN0aW5nIHRoZSB5aWVsZHMgb2YgbWVzcXVpdGUgYnVzaGVzLCBuPTQ2CgojIyMjIExvYWQgZGF0YQoKYGBge3IgfQptZXNxdWl0ZSA8LSByZWFkLnRhYmxlKHJvb3QoIk1lc3F1aXRlL2RhdGEiLCJtZXNxdWl0ZS5kYXQiKSwgaGVhZGVyPVRSVUUpCm1lc3F1aXRlJGNhbm9weV92b2x1bWUgPC0gbWVzcXVpdGUkZGlhbTEgKiBtZXNxdWl0ZSRkaWFtMiAqIG1lc3F1aXRlJGNhbm9weV9oZWlnaHQKbWVzcXVpdGUkY2Fub3B5X2FyZWEgPC0gbWVzcXVpdGUkZGlhbTEgKiBtZXNxdWl0ZSRkaWFtMgptZXNxdWl0ZSRjYW5vcHlfc2hhcGUgPC0gbWVzcXVpdGUkZGlhbTEgLyBtZXNxdWl0ZSRkaWFtMgoobiA8LSBucm93KG1lc3F1aXRlKSkKYGBgCgojIyMjIFByZWRpY3QgbG9nIHdlaWdodCBtb2RlbCB3aXRoIGxvZyBjYW5vcHkgdm9sdW1lLCBsb2cgY2Fub3B5IHNoYXBlLCBhbmQgZ3JvdXAKCmBgYHtyIH0KZml0XzUgPC0gc3Rhbl9nbG0obG9nKHdlaWdodCkgfiBsb2coY2Fub3B5X3ZvbHVtZSkgKyBsb2coY2Fub3B5X3NoYXBlKSArCiAgICBncm91cCwgZGF0YT1tZXNxdWl0ZSwgcmVmcmVzaD0wKQpgYGAKCiMjIyMgTWVkaWFuIEJheWVzaWFuIFIyCgpgYGB7ciB9CnJvdW5kKG1lZGlhbihiYXllc1IyPC1iYXllc19SMihmaXRfNSkpLCAyKQpgYGAKCiMjIyMgUGxvdCBwb3N0ZXJpb3Igb2YgQmF5ZXNpYW4gUjIKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIGVycm9yPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpweGw8LXhsaW0oMC42LCAwLjk1KQptY21jX2hpc3QoZGF0YS5mcmFtZShiYXllc1IyKSwgYmlud2lkdGg9MC4wMSkgKyBweGwgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1OVUxMKSArCiAgICB4bGFiKCdCYXllc2lhbiBSMicpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1tZWRpYW4oYmF5ZXNSMikpCmBgYAoKIyMgTG93Qnd0IC0tIGxvZ2lzdGljIHJlZ3Jlc3Npb24KClByZWRpY3QgbG93IGJpcnRoIHdlaWdodCwgbj0xODksIGZyb20gSG9zbWVyIGV0IGFsICgyMDAwKS4KVGhpcyBkYXRhIHdhcyBhbHNvIHVzZWQgYnkgVGp1ciAoMjAwOSkKCiMjIyMgTG9hZCBkYXRhCgpgYGB7ciB9Cmxvd2J3dCA8LSByZWFkLnRhYmxlKHJvb3QoIkxvd0J3dC9kYXRhIiwibG93Ynd0LmRhdCIpLCBoZWFkZXI9VFJVRSkKKG4gPC0gbnJvdyhsb3did3QpKQpoZWFkKGxvd2J3dCkKYGBgCgojIyMjIFByZWRpY3QgbG93IGJpcnRoIHdlaWdodAoKYGBge3IgfQpmaXRfMSA8LSBzdGFuX2dsbShsb3cgfiBhZ2UgKyBsd3QgKyBmYWN0b3IocmFjZSkgKyBzbW9rZSwKICAgICAgICAgICAgICAgIGZhbWlseT1iaW5vbWlhbChsaW5rPSJsb2dpdCIpLCBkYXRhPWxvd2J3dCwgcmVmcmVzaD0wKQpgYGAKCiMjIyMgTWVkaWFuIEJheWVzaWFuIFIyCgpgYGB7ciB9CnJvdW5kKG1lZGlhbihiYXllc1IyPC1iYXllc19SMihmaXRfMSkpLCAyKQpgYGAKCiMjIyMgUGxvdCBwb3N0ZXJpb3Igb2YgQmF5ZXNpYW4gUjIKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIGVycm9yPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpweGw8LXhsaW0oMCwgMC4zKQptY21jX2hpc3QoZGF0YS5mcmFtZShiYXllc1IyKSwgYmlud2lkdGg9MC4wMSkgKyBweGwgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1OVUxMKSArCiAgICB4bGFiKCdCYXllc2lhbiBSMicpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1tZWRpYW4oYmF5ZXNSMikpCmBgYAoKIyMgTG93Qnd0IC0tIGxpbmVhciByZWdyZXNzaW9uCgpQcmVkaWN0IGJpcnRoIHdlaWdodCwgbj0xODksIGZyb20gSG9zbWVyIGV0IGFsLiAoMjAwMCkuClRqdXIgKDIwMDkpIHVzZWQgbG9naXN0aWMgcmVncmVzc2lvbiBmb3IgZGljaG90b21pemVkIGJpcnRoIHdlaWdodC4KQmVsb3cgd2UgdXNlIHRoZSBjb250aW51b3MgdmFsdWVkIGJpcnRoIHdlaWdodC4KCiMjIyMgUHJlZGljdCBiaXJ0aCB3ZWlnaHQKCmBgYHtyIH0KZml0XzIgPC0gc3Rhbl9nbG0oYnd0IH4gYWdlICsgbHd0ICsgZmFjdG9yKHJhY2UpICsgc21va2UsIGRhdGE9bG93Ynd0LCByZWZyZXNoPTApCmBgYAoKIyMjIyBNZWRpYW4gQmF5ZXNpYW4gUjIKCmBgYHtyIH0Kcm91bmQobWVkaWFuKGJheWVzUjI8LWJheWVzX1IyKGZpdF8yKSksIDIpCmBgYAoKIyMjIyBQbG90IHBvc3RlcmlvciBvZiBCYXllc2lhbiBSMgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnB4bDwteGxpbSgwLCAwLjM2KQptY21jX2hpc3QoZGF0YS5mcmFtZShiYXllc1IyKSwgYmlud2lkdGg9MC4wMSkgKyBweGwgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1OVUxMKSArCiAgICB4bGFiKCdCYXllc2lhbiBSMicpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1tZWRpYW4oYmF5ZXNSMikpCmBgYAoKIyMgS2lkSVEgLSBsaW5lYXIgcmVncmVzc2lvbgoKQ2hpbGRyZW4ncyB0ZXN0IHNjb3JlcyBkYXRhLCBuPTQzNAoKIyMjIyBMb2FkIGNoaWxkcmVuJ3MgdGVzdCBzY29yZXMgZGF0YQoKYGBge3IgfQpraWRpcSA8LSByZWFkLmNzdihyb290KCJLaWRJUS9kYXRhIiwia2lkaXEuY3N2IikpCihuIDwtIG5yb3coa2lkaXEpKQpoZWFkKGtpZGlxKQpgYGAKCiMjIyMgUHJlZGljdCB0ZXN0IHNjb3JlCgpgYGB7ciB9CmZpdF8zIDwtIHN0YW5fZ2xtKGtpZF9zY29yZSB+IG1vbV9ocyArIG1vbV9pcSwgZGF0YT1raWRpcSwKICAgICAgICAgICAgICAgICAgc2VlZD1TRUVELCByZWZyZXNoPTApCmBgYAoKIyMjIyBNZWRpYW4gQmF5ZXNpYW4gUjIKCmBgYHtyIH0Kcm91bmQobWVkaWFuKGJheWVzUjI8LWJheWVzX1IyKGZpdF8zKSksIDIpCmBgYAoKIyMjIyBQbG90IHBvc3RlcmlvciBvZiBCYXllc2lhbiBSMgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnB4bDwteGxpbSgwLjA1LCAwLjM1KQptY21jX2hpc3QoZGF0YS5mcmFtZShiYXllc1IyKSwgYmlud2lkdGg9MC4wMSkgKyBweGwgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1OVUxMKSArCiAgICB4bGFiKCdCYXllc2lhbiBSMicpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1tZWRpYW4oYmF5ZXNSMikpCmBgYAoKIyMjIyBBZGQgZml2ZSBwdXJlIG5vaXNlIHByZWRpY3RvcnMgdG8gdGhlIGRhdGEKCmBgYHtyIH0Kc2V0LnNlZWQoMTUwNykKbiA8LSBucm93KGtpZGlxKQpraWRpcXIgPC0ga2lkaXEKa2lkaXFyJG5vaXNlIDwtIGFycmF5KHJub3JtKDUqbiksIGMobiw1KSkKYGBgCgojIyMjIExpbmVhciByZWdyZXNzaW9uIHdpdGggYWRkaXRpb25hbCBub2lzZSBwcmVkaWN0b3JzCgpgYGB7ciB9CmZpdF8zbiA8LSBzdGFuX2dsbShraWRfc2NvcmUgfiBtb21faHMgKyBtb21faXEgKyBub2lzZSwgZGF0YT1raWRpcXIsCiAgICAgICAgICAgICAgICAgICBzZWVkPVNFRUQsIHJlZnJlc2g9MCkKcHJpbnQoZml0XzNuKQpgYGAKCiMjIyMgTWVkaWFuIEJheWVzaWFuIFIyCgpgYGB7ciB9CnJvdW5kKG1lZGlhbihiYXllc1IybjwtYmF5ZXNfUjIoZml0XzNuKSksIDIpCmBgYAoKTWVkaWFuIEJheWVzaWFuIFIyIGlzIGhpZ2hlciB3aXRoIGFkZGl0aW9uYWwgbm9pc2UgcHJlZGljdG9ycywgYnV0CnRoZSBkaXN0cmlidXRpb24gb2YgQmF5ZXNpYW4gUjIgcmV2ZWFscyB0aGF0IHRoZSBpbmNyZWFzZSBpcyBub3QKcHJhY3RpY2FsbHkgcmVsZXZhbnQuCgojIyMjIFBsb3QgcG9zdGVyaW9yIG9mIEJheWVzaWFuIFIyCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHhsPC14bGltKDAuMDUsIDAuMzUpCm1jbWNfaGlzdChkYXRhLmZyYW1lKGJheWVzUjJuKSwgYmlud2lkdGg9MC4wMSkgKyBweGwgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1OVUxMKSArCiAgICB4bGFiKCdCYXllc2lhbiBSMicpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1tZWRpYW4oYmF5ZXNSMikpCmBgYAoKIyMgRWFybmluZ3MgLSBsb2dpc3RpYyBhbmQgbGluZWFyIHJlZ3Jlc3Npb24KClByZWRpY3QgcmVzcG9uZGVudHMnIHllYXJseSBlYXJuaW5ncyB1c2luZyBzdXJ2ZXkgZGF0YSBmcm9tIDE5OTAuPC9icj4KbG9naXN0aWMgcmVncmVzc2lvbiBuPTEzNzQsIGxpbmVhciByZWdyZXNzaW9uIG49MTE4NwoKIyMjIyBMb2FkIGRhdGEKCmBgYHtyIH0KZWFybmluZ3MgPC0gcmVhZC5jc3Yocm9vdCgiRWFybmluZ3MvZGF0YSIsImVhcm5pbmdzLmNzdiIpKQoobiA8LSBucm93KGVhcm5pbmdzKSkKaGVhZChlYXJuaW5ncykKYGBgCgojIyMjIEJheWVzaWFuIGxvZ2lzdGljIHJlZ3Jlc3Npb24gb24gbm9uLXplcm8gZWFybmluZ3M8L2JyPgpQcmVkaWN0IHVzaW5nIGhlaWdodCBhbmQgc2V4CgpgYGB7ciB9CmZpdF8xYSA8LSBzdGFuX2dsbSgoZWFybj4wKSB+IGhlaWdodCArIG1hbGUsCiAgICAgICAgICAgICAgICAgICBmYW1pbHkgPSBiaW5vbWlhbChsaW5rID0gImxvZ2l0IiksCiAgICAgICAgICAgICAgICAgICBkYXRhID0gZWFybmluZ3MsIHJlZnJlc2g9MCkKYGBgCgojIyMjIE1lZGlhbiBCYXllc2lhbiBSMgoKYGBge3IgfQpyb3VuZChtZWRpYW4oYmF5ZXNSMjwtYmF5ZXNfUjIoZml0XzFhKSksIDMpCmBgYAoKIyMjIyBQbG90IHBvc3RlcmlvciBvZiBCYXllc2lhbiBSMgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnB4bDwteGxpbSgwLjAyLCAwLjExKQptY21jX2hpc3QoZGF0YS5mcmFtZShiYXllc1IyKSwgYmlud2lkdGg9MC4wMDIpICsgcHhsICsKICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3M9TlVMTCkgKwogICAgeGxhYignQmF5ZXNpYW4gUjInKSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9bWVkaWFuKGJheWVzUjIpKQpgYGAKCiMjIyMgQmF5ZXNpYW4gcHJvYml0IHJlZ3Jlc3Npb24gb24gbm9uLXplcm8gZWFybmluZ3M8L2JyPgoKYGBge3IgfQpmaXRfMXAgPC0gc3Rhbl9nbG0oKGVhcm4+MCkgfiBoZWlnaHQgKyBtYWxlLAogICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICJwcm9iaXQiKSwKICAgICAgICAgICAgICAgICAgIGRhdGEgPSBlYXJuaW5ncywgcmVmcmVzaD0wKQpgYGAKCiMjIyMgTWVkaWFuIEJheWVzaWFuIFIyCgpgYGB7ciB9CnJvdW5kKG1lZGlhbihiYXllc1IyKSwgMykKcm91bmQobWVkaWFuKGJheWVzUjJwPC1iYXllc19SMihmaXRfMXApKSwgMykKYGBgCgojIyMjIENvbXBhcmUgbG9naXN0aWMgYW5kIHByb2JpdCBtb2RlbHMgdXNpbmcgbmV3IEJheWVzaWFuIFIyCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHhsPC14bGltKDAuMDIsIDAuMTEpCnAxPC1tY21jX2hpc3QoZGF0YS5mcmFtZShiYXllc1IyKSwgYmlud2lkdGg9MC4wMDIpICsgcHhsICsKICAgIHNjYWxlX3lfY29udGludW91cyhicmVha3M9TlVMTCkgKwogICAgZ2d0aXRsZSgnRWFybmluZ3MgZGF0YSB3aXRoIG49MTM3NCcpICsKICAgIHhsYWIoJ0JheWVzaWFuIFIyIGZvciBsb2dpc3RpYyBtb2RlbCcpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1tZWRpYW4oYmF5ZXNSMikpCnAyPC1tY21jX2hpc3QoZGF0YS5mcmFtZShiYXllc1IycCksIGJpbndpZHRoPTAuMDAyKSArIHB4bCArCiAgICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzPU5VTEwpICsKICAgIHhsYWIoJ0JheWVzaWFuIFIyIGZvciBwcm9iaXQgbW9kZWwnKSArCiAgICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9bWVkaWFuKGJheWVzUjJwKSkKYmF5ZXNwbG90X2dyaWQocDEscDIpCmBgYAoKVGhlcmUgaXMgbm8gcHJhY3RpY2FsIGRpZmZlcmVuY2UgaW4gcHJlZGljdGl2ZSBwZXJmb3JtYW5jZSBiZXR3ZWVuCmxvZ2l0IGFuZCBwcm9iaXQuCgojIyMjIEJheWVzaWFuIG1vZGVsIG9uIHBvc2l0aXZlIGVhcm5pbmdzIG9uIGxvZyBzY2FsZQoKYGBge3IgfQpmaXRfMWIgPC0gc3Rhbl9nbG0obG9nKGVhcm4pIH4gaGVpZ2h0ICsgbWFsZSwgZGF0YSA9IGVhcm5pbmdzLCBzdWJzZXQ9ZWFybj4wLAogICAgICAgICAgICAgICAgICAgcmVmcmVzaD0wKQpgYGAKCiMjIyMgTWVkaWFuIEJheWVzaWFuIFIyCgpgYGB7ciB9CnJvdW5kKG1lZGlhbihiYXllc1IyPC1iYXllc19SMihmaXRfMWIpKSwgMykKYGBgCgojIyMjIFBsb3QgcG9zdGVyaW9yIG9mIEJheWVzaWFuIFIyCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRSwgd2FybmluZz1GQUxTRX0KcHhsPC14bGltKDAuMDIsIDAuMTUpCm1jbWNfaGlzdChkYXRhLmZyYW1lKGJheWVzUjIpLCBiaW53aWR0aD0wLjAwMikgKyBweGwgKwogICAgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcz1OVUxMKSArCiAgICB4bGFiKCdCYXllc2lhbiBSMicpICsKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdD1tZWRpYW4oYmF5ZXNSMikpCmBgYAoK