Bayesian \(R^2\). See Chapter 11 in Regression and Other Stories.

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)

Median Bayesian R^2

round(median(bayesR2<-bayes_R2(fit_bayes)), 2)
[1] 0.74

Figures

The first section of code below creates plots using base R graphics.
Below that there is code to produce the plots using ggplot2.

# take a sample of 20 posterior draws
keep <- sample(nrow(posterior), 20)
samp_20_draws <- posterior[keep, ]

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)

Median Bayesian R^2

round(median(bayesR2<-bayes_R2(fit_logit)), 2)
[1] 0.39

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)

Median Bayesian R2

round(median(bayesR2<-bayes_R2(fit_5)), 2)
[1] 0.87

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)

Median Bayesian R2

round(median(bayesR2<-bayes_R2(fit_1)), 2)
[1] 0.12

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))

LowBwt -- linear regression

Predict birth weight, n=189, from Hosmer et al. (2000). Tjur (2009) used logistic regression for dichotomized birth weight. Below we use the continuos valued birth weight.

Predict birth weight

fit_2 <- stan_glm(bwt ~ age + lwt + factor(race) + smoke, data=lowbwt, refresh=0)

Median Bayesian R2

round(median(bayesR2<-bayes_R2(fit_2)), 2)
[1] 0.16

Plot posterior of Bayesian R2

pxl<-xlim(0, 0.36)
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)

Median Bayesian R2

round(median(bayesR2<-bayes_R2(fit_3)), 2)
[1] 0.21

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

Median Bayesian R2

round(median(bayesR2n<-bayes_R2(fit_3n)), 2)
[1] 0.22

Median Bayesian R2 is higher with additional noise predictors, but the distribution of Bayesian R2 reveals that the increase is not practically relevant.

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)

Median Bayesian R2

round(median(bayesR2<-bayes_R2(fit_1a)), 3)
[1] 0.046

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)

Median Bayesian R2

round(median(bayesR2), 3)
[1] 0.046
round(median(bayesR2p<-bayes_R2(fit_1p)), 3)
[1] 0.046

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)

Median Bayesian R2

round(median(bayesR2<-bayes_R2(fit_1b)), 3)
[1] 0.08

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