Analysis of "Electric company" data. See Chapters 1, 16, 19 and 20 in Regression and Other Stories.


Load packages

library("rprojroot")
root<-has_file(".ROS-Examples-root")$make_fix_file()
library("rstanarm")
invlogit <- plogis

Load data

electric_wide <- read.table(root("ElectricCompany/data","electric_wide.txt"), header=TRUE)
head(electric_wide)
    city grade treated_pretest treated_posttest control_pretest
1 Fresno     1            13.8             48.9            12.3
2 Fresno     1            16.5             70.5            14.4
3 Fresno     1            18.5             89.7            17.7
4 Fresno     1             8.8             44.2            11.5
5 Fresno     1            15.3             77.5            16.4
6 Fresno     1            15.0             84.7            16.8
  control_posttest supplement
1             52.3 Supplement
2             55.0    Replace
3             80.4 Supplement
4             47.0    Replace
5             69.7 Supplement
6             74.1    Replace

Plot of raw data

onlytext <- function (string){
  plot(0:1, 0:1, bty='n', type='n', xaxt='n', yaxt='n', xlab='', ylab='')
  text(0.5, 0.5, string, cex=1.2, font=2)
}
nf<- layout(matrix(c(0,1:14), 5, 3, byrow=TRUE), c(5, 10, 10), c(1, 5, 5, 5, 5), TRUE)
par(mar=c(.2, .2, .2, .2))
onlytext('Test scores in control classes')
onlytext('Test scores in treated classes')
par(mar=c(1, 1, 1, 1), lwd=0.7)
attach(electric_wide)
for (j in 1:4){
  onlytext(paste('Grade', j))
  hist(control_posttest[grade==j], breaks=seq(0,125,5), xaxt='n', yaxt='n', main=NULL, col="gray", ylim=c(0,10))
  axis(side=1, seq(0,125,50), line=-.25, cex.axis=1, mgp=c(1,.2,0), tck=0)
  text(2, 6.5, paste("mean =", round(mean(control_posttest[grade==j]))), adj=0)
  text(2, 5, paste("  sd =", round(sd(control_posttest[grade==j]))), adj=0)
  hist(treated_posttest[grade==j], breaks=seq(0,125,5), xaxt='n', yaxt='n', main=NULL, col="gray", ylim=c(0,10))
  axis(side=1, seq(0,125,50), line=-.25, cex.axis=1, mgp=c(1,.2,0), tck=0)
  text(2, 6.5, paste("mean =", round(mean(treated_posttest[grade==j]))), adj=0)
  text(2, 5, paste("  sd =", round(sd(treated_posttest[grade==j]))), adj=0)
}

Plot the data the other way

onlytext<-function(string){
  plot(0:1, 0:1, bty='n', type='n', xaxt='n', yaxt='n', xlab='', ylab='')
  text(0.5, 0.5, string, cex=1.2, font=2)
}
nf<-layout(matrix(c(0,1:14), 3, 5, byrow=FALSE), c(5, 10, 10, 10, 10), c(1, 5, 5), TRUE)
par(mar=c(.2, .2, .2, .2))
onlytext('Control\nclasses')
onlytext('Treated\nclasses')
par(mar=c(.2,.4,.2,.4), lwd=.5)
for (j in 1:4){
  onlytext(paste('Grade', j))
  hist(control_posttest[grade==j], breaks=seq(40,125,5), xaxt='n', yaxt='n', main=NULL, col="gray", ylim=c(0,14))
  axis(side=1, seq(50,100,25), line=-.25, cex.axis=1, mgp=c(1,.2,0), tck=0, lty="blank")
  lines(rep(mean(control_posttest[grade==j]),2), c(0,11), lwd=2)
  hist(treated_posttest[grade==j], breaks=seq(40,125,5), xaxt='n', yaxt='n', main=NULL, col="gray", ylim=c(0,14))
  axis(side=1, seq(50,100,25), line=-.25, cex.axis=1, mgp=c(1,.2,0), tck=0, lty="blank")
  lines(rep(mean(treated_posttest[grade==j]),2), c(0,11), lwd=2)
}

Another plot

par(mfrow=c(1,4), pty="s")
x.range <- cbind(c(5,40,40,40), c(25,125,125,125))
for (j in 1:4){
  ok <- grade==j
  x <- c(treated_pretest[ok], control_pretest[ok])
  y <- c(treated_posttest[ok], control_posttest[ok])
  t <- rep(c(1,0), rep(sum(ok),2))
  plot(c(0,125), c(0,125), type="n", main=paste("Grade",j), xaxs="i", yaxs="i",
        xlab=expression(paste("pre-test, ",x[i])),
        ylab=expression(paste("post-test, ",y[i])),
        cex.axis=1.5, cex.lab=1.5, cex.main=1.8, mgp=c(2.5,.7,0))
  fit_1 <- stan_glm(y ~ x + t, data = electric_wide, refresh = 0, 
                    save_warmup = FALSE, open_progress = FALSE, cores = 1)
  abline(coef(fit_1)[1], coef(fit_1)[2], lwd=.5, lty=2)
  abline(coef(fit_1)[1]+coef(fit_1)[3], coef(fit_1)[2], lwd=.5)
  points(control_pretest[ok], control_posttest[ok], pch=20, cex=1.2)
  points(treated_pretest[ok], treated_posttest[ok], pch=21, cex=1.2)
}

Yet another plot

par(mfrow=c(1,4), pty="s")
for (j in 1:4){
  ok <- grade==j
  x <- c(treated_pretest[ok], control_pretest[ok])
  y <- c(treated_posttest[ok], control_posttest[ok])
  t <- rep(c(1,0), rep(sum(ok),2))
    plot(c(0,125),c(0,125), type="n", main=paste("Grade",j),
        xlab=expression(paste("pre-test, ",x[i])),
        ylab=expression(paste("post-test, ",y[i])),
        cex.axis=1.5, cex.lab=1.5, cex.main=1.8, mgp=c(2.5,.7,0))
  fit_1 <- stan_glm(y ~ x + t, data = electric_wide, refresh = 0, 
                    save_warmup = FALSE, open_progress = FALSE, cores = 1)
  abline(coef(fit_1)[1], coef(fit_1)[2], lwd=.5, lty=2)
  abline(coef(fit_1)[1]+coef(fit_1)[3], coef(fit_1)[2], lwd=.5)
  points(control_pretest[ok], control_posttest[ok], pch=20, cex=1.2)
  points(treated_pretest[ok], treated_posttest[ok], pch=21, cex=1.2)
}

Plot more

par(mfrow=c(1,4), pty="s")
for (j in 1:4){
  ok <- grade==j
  x <- c(treated_pretest[ok], control_pretest[ok])
  y <- c(treated_posttest[ok], control_posttest[ok])
  t <- rep(c(1,0), rep(sum(ok),2))
  plot(c(0,125),c(0,125), type="n", main=paste("Grade",j), xaxs="i", yaxs="i",
        xlab=expression(paste("pre-test, ",x[i])),
        ylab=expression(paste("post-test, ",y[i])),
        cex.axis=1.5, cex.lab=1.5, cex.main=1.8, mgp=c(2.5,.7,0))
  fit_1 <- stan_glm(y ~ x + t + x:t, data = electric_wide, refresh = 0, 
                    save_warmup = FALSE, open_progress = FALSE, cores = 1)
  abline(coef(fit_1)[1], coef(fit_1)[2], lwd=.5, lty=2)
  abline(coef(fit_1)[1]+coef(fit_1)[3], coef(fit_1)[2]+coef(fit_1)[4], lwd=.5)
  ## lm.1 <- lm(y ~ x + t + x*t)
  points(control_pretest[ok], control_posttest[ok], pch=20, cex=1.2)
  points(treated_pretest[ok], treated_posttest[ok], pch=21, cex=1.2)
}

Linear model

post_test <- c(treated_posttest, control_posttest)
pre_test <- c(treated_pretest, control_pretest)
grade <- rep(electric_wide$grade, 2)
treatment <- rep(c(1,0), rep(length(treated_posttest),2))
supp <- rep(NA, length(treatment))
n_pairs <- nrow(electric_wide)
pair_id <- rep(1:n_pairs, 2)
supp[treatment==1] <- ifelse(supplement=="Supplement", 1, 0)
n <- length(post_test)
electric <- data.frame(post_test, pre_test, grade, treatment, supp, pair_id)
#write.csv(electric, root("ElectricCompany/data","electric.csv"))
#electric <- read.csv(root("ElectricCompany/data","electric.csv"))

fit_3 <- stan_glm(post_test ~ treatment + pre_test + treatment:pre_test, subset=(grade==4), data=electric, refresh = 0)
print(fit_3)
stan_glm
 family:       gaussian [identity]
 formula:      post_test ~ treatment + pre_test + treatment:pre_test
 observations: 42
 predictors:   4
------
                   Median MAD_SD
(Intercept)        38.8    4.7  
treatment          14.6    8.3  
pre_test            0.7    0.0  
treatment:pre_test -0.1    0.1  

Auxiliary parameter(s):
      Median MAD_SD
sigma 2.2    0.2   

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg

Another linear model

fit_4 <- stan_glm(post_test ~ treatment + pre_test + treatment * pre_test,
                  subset = (grade==4), data=electric, refresh = 0)
sim_4 <- as.matrix(fit_4)

Plot linear model

plot(0, 0, xlim=range(pre_test[grade==4]), ylim=c(-5,10),
       xlab="pre-test", ylab="treatment effect", main="treatment effect in grade 4")
abline(0, 0, lwd=.5, lty=2)
for (i in 1:20)
  curve(sim_4[i,2] + sim_4[i,4]*x, lwd=.5, col="gray", add=TRUE)
curve(coef(fit_4)[2] + coef(fit_4)[4]*x, lwd=.5, add=TRUE)

Mean effect

n_sims <- 1000
effect <- array(NA, c(n_sims, sum(grade==4)))
for (i in 1:n_sims)
  effect[i,] <- sim_4[i,2] + sim_4[i,4]*pre_test[grade==4]
mean_effect <- rowMeans(effect)

Plot repeated regression results

est1 <- rep(NA,4)
est2 <- rep(NA,4)
se1 <- rep(NA,4)
se2 <- rep(NA,4)
for (k in 1:4) {
    fit_1 <- stan_glm(post_test ~ treatment, subset=(grade==k), data = electric,
                      refresh = 0, save_warmup = FALSE, 
                      open_progress = FALSE, cores = 1)
    fit_2 <- stan_glm(post_test ~ treatment + pre_test, subset=(grade==k),
                      data = electric, refresh = 0, save_warmup = FALSE, 
                      open_progress = FALSE, cores = 1)
    est1[k] <- coef(fit_1)[2]
    est2[k] <- coef(fit_2)[2]
    se1[k] <- se(fit_1)[2]
    se2[k] <- se(fit_2)[2]
}
regression.2tables <- function (name, est1, est2, se1, se2, label1, label2, file, bottom=FALSE){
  J <- length(name)
  name.range <- .6
  x.range <- range (est1+2*se1, est1-2*se1, est2+2*se2, est1-2*se2)
  A <- -x.range[1]/(x.range[2]-x.range[1])
  B <- 1/(x.range[2]-x.range[1])
  height <- .6*J
  width <- 8*(name.range+1)
  gap <- .4

  if (!is.na(file)) postscript(file, horizontal=F, height=height, width=width)
  par (mar=c(0,0,0,0))
  plot (c(-name.range,2+gap), c(3,-J-2), bty="n", xlab="", ylab="",
        xaxt="n", yaxt="n", xaxs="i", yaxs="i", type="n")
  text (-name.range, 2, "Subpopulation", adj=0, cex=1)
  text (.5, 2, label1, adj=.5, cex=1)
  text (1+gap+.5, 2, label2, adj=.5, cex=1)
  lines (c(0,1), c(0,0))
  lines (1+gap+c(0,1), c(0,0))
  lines (c(A,A), c(0,-J-1), lty=2, lwd=.5)
  lines (1+gap+c(A,A), c(0,-J-1), lty=2, lwd=.5)
  ax <- pretty (x.range)
  ax <- ax[(A+B*ax)>0 & (A+B*ax)<1]
  segments (A + B*ax, -.1, A + B*ax, .1, lwd=.5)
  segments (1+gap+A + B*ax, -.1, 1+gap+A + B*ax, .1, lwd=.5)
  text (A + B*ax, .7, ax, cex=1)
  text (1+gap+A + B*ax, .7, ax, cex=1)
  text (-name.range, -(1:J), name, adj=0, cex=1)
  points (A + B*est1, -(1:J), pch=20, cex=1)
  points (1+gap+A + B*est2, -(1:J), pch=20, cex=1)
  segments (A + B*(est1-se1), -(1:J), A + B*(est1+se1), -(1:J), lwd=3)
  segments (1+gap+A + B*(est2-se2), -(1:J), 1+gap+A + B*(est2+se2), -(1:J), lwd=3)
  segments (A + B*(est1-2*se1), -(1:J), A + B*(est1+2*se1), -(1:J), lwd=.5)
  segments (1+gap+A + B*(est2-2*se2), -(1:J), 1+gap+A + B*(est2+2*se2), -(1:J), lwd=.5)
  if (bottom){
    lines (c(0,1), c(-J-1,-J-1))
    lines (1+gap+c(0,1), c(-J-1,-J-1))
    segments (A + B*ax, -J-1-.1, A + B*ax, -J-1+.1, lwd=.5)
    segments (1+gap+A + B*ax, -J-1-.1, 1+gap+A + B*ax, -J-1+.1, lwd=.5)
    text (A + B*ax, -J-1-.7, ax, cex=1)
    text (1+gap+A + B*ax, -J-1-.7, ax, cex=1)
  } 
  if (!is.na(file)) graphics.off()
}
regression.2tables(paste("Grade", 1:4), est1, est2, se1, se2, "Regression on treatment indicator", "Regression on treatment indicator,\ncontrolling for pre-test", NA)

Plot replace/supplement

jitter.binary <- function (a, jitt=.05){
  a + (1-2*a)*runif(length(a), 0, jitt)
}
par(mfrow=c(1,4))
for (k in 1:4){
  cat(paste("Grade",k,":\n"))
  ok <- (grade==k)&(!is.na(supp))
  glm_supp <- stan_glm(supp ~ pre_test, family=binomial(link="logit"),
                       subset=ok, data=electric, refresh = 0, 
                       save_warmup = FALSE, open_progress = FALSE, cores = 1)
  print(glm_supp)
  sims.glm.supp <- as.matrix(glm_supp)
  plot(range(pre_test[ok]), c(0,1), type="n", xlab="Pre-test score",
        ylab="", main=paste("Grade", k),
        cex.axis=1.5, cex.lab=1.5, cex.main=1.8, mgp=c(2.5,.7,0), yaxt="n")
  axis(2, c(0,1), c("     Replace","Supp    "), cex.axis=1.5, mgp=c(2.5,.7,0))
  for (l in 1:20)
      curve(invlogit(sims.glm.supp[l,1] + sims.glm.supp[l,2]*x), lwd=.5, col="gray", add=TRUE)
  points(pre_test[supp==1&ok], jitter.binary(supp[supp==1&ok]), pch=21) #  supp:  open circle
  points(pre_test[supp==0&ok], jitter.binary(supp[supp==0&ok]), pch=20)# replace:  dot
  curve(invlogit(coef(glm_supp)[1] + coef(glm_supp)[2]*x), lwd=.5, add=TRUE)
  glm_supp <- stan_glm(post_test ~ supp + pre_test,
                       subset=((grade==k)&!is.na(supp)), data=electric, refresh = 0, 
                       save_warmup = FALSE, open_progress = FALSE, cores = 1)
  print(glm_supp)
  est1[k] <- coef(glm_supp)[2]
  se1[k] <- se(glm_supp)[2]
}
Grade 1 :
stan_glm
 family:       binomial [logit]
 formula:      supp ~ pre_test
 observations: 21
 predictors:   2
------
            Median MAD_SD
(Intercept) -1.9    2.9  
pre_test     0.2    0.2  

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg
stan_glm
 family:       gaussian [identity]
 formula:      post_test ~ supp + pre_test
 observations: 21
 predictors:   3
------
            Median MAD_SD
(Intercept) -0.1   12.6  
supp         6.0    4.5  
pre_test     4.7    0.8  

Auxiliary parameter(s):
      Median MAD_SD
sigma 9.8    1.6   

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg
Grade 2 :
stan_glm
 family:       binomial [logit]
 formula:      supp ~ pre_test
 observations: 34
 predictors:   2
------
            Median MAD_SD
(Intercept) -0.3    2.7  
pre_test     0.0    0.0  

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg
stan_glm
 family:       gaussian [identity]
 formula:      post_test ~ supp + pre_test
 observations: 34
 predictors:   3
------
            Median MAD_SD
(Intercept) 39.4    6.7  
supp         4.7    1.9  
pre_test     0.8    0.1  

Auxiliary parameter(s):
      Median MAD_SD
sigma 5.4    0.7   

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg
Grade 3 :
stan_glm
 family:       binomial [logit]
 formula:      supp ~ pre_test
 observations: 20
 predictors:   2
------
            Median MAD_SD
(Intercept)  8.9    6.8  
pre_test    -0.1    0.1  

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg
stan_glm
 family:       gaussian [identity]
 formula:      post_test ~ supp + pre_test
 observations: 20
 predictors:   3
------
            Median MAD_SD
(Intercept) 43.7    6.3  
supp        -1.3    1.6  
pre_test     0.7    0.1  

Auxiliary parameter(s):
      Median MAD_SD
sigma 3.0    0.5   

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg
Grade 4 :
stan_glm
 family:       binomial [logit]
 formula:      supp ~ pre_test
 observations: 21
 predictors:   2
------
            Median MAD_SD
(Intercept) -9.4    8.9  
pre_test     0.1    0.1  

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg

stan_glm
 family:       gaussian [identity]
 formula:      post_test ~ supp + pre_test
 observations: 21
 predictors:   3
------
            Median MAD_SD
(Intercept) 54.9   10.6  
supp        -0.4    1.2  
pre_test     0.6    0.1  

Auxiliary parameter(s):
      Median MAD_SD
sigma 2.7    0.4   

------
* For help interpreting the printed output see ?print.stanreg
* For info on the priors used see ?prior_summary.stanreg
regression.2tablesA <- function (name, est1, se1, label1, file, bottom=FALSE){
  J <- length(name)
  name.range <- .6
  x.range <- range (est1+2*se1, est1-2*se1)
  A <- -x.range[1]/(x.range[2]-x.range[1])
  B <- 1/(x.range[2]-x.range[1])
  height <- .6*J
  width <- 8*(name.range+1)
  gap <- .4
  
  if (!is.na(file)) postscript(file, horizontal=F, height=height, width=width)
  par (mar=c(0,0,0,0))
  plot (c(-name.range,2+gap), c(3,-J-2), bty="n", xlab="", ylab="",
        xaxt="n", yaxt="n", xaxs="i", yaxs="i", type="n")
  text (-name.range, 2, "Subpopulation", adj=0, cex=1)
  text (.5, 2, label1, adj=.5, cex=1)
  lines (c(0,1), c(0,0))
  lines (c(A,A), c(0,-J-1), lty=2, lwd=.5)
  ax <- pretty (x.range)
  ax <- ax[(A+B*ax)>0 & (A+B*ax)<1]
  segments (A + B*ax, -.1, A + B*ax, .1, lwd=.5)
  text (A + B*ax, .7, ax, cex=1)
  text (-name.range, -(1:J), name, adj=0, cex=1)
  points (A + B*est1, -(1:J), pch=20, cex=1)
  segments (A + B*(est1-se1), -(1:J), A + B*(est1+se1), -(1:J), lwd=3)
  segments (A + B*(est1-2*se1), -(1:J), A + B*(est1+2*se1), -(1:J), lwd=.5)
  if (bottom){
    lines (c(0,1), c(-J-1,-J-1))
    segments (A + B*ax, -J-1-.1, A + B*ax, -J-1+.1, lwd=.5)
    text (A + B*ax, -J-1-.7, ax, cex=1)
  } 
  if (!is.na(file)) graphics.off()
}
regression.2tablesA(paste("Grade", 1:4), est1, se1, "Estimated effect of supplement,\ncompared to replacement", NA)

LS0tCnRpdGxlOiAiUmVncmVzc2lvbiBhbmQgT3RoZXIgU3RvcmllczogRWxlY3RyaWMgQ29tcGFueSIKYXV0aG9yOiAiQW5kcmV3IEdlbG1hbiwgSmVubmlmZXIgSGlsbCwgQWtpIFZlaHRhcmkiCmRhdGU6ICJgciBmb3JtYXQoU3lzLkRhdGUoKSlgIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRoZW1lOiByZWFkYWJsZQogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDIKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQotLS0KQW5hbHlzaXMgb2YgIkVsZWN0cmljIGNvbXBhbnkiIGRhdGEuIFNlZSBDaGFwdGVycyAxLCAxNiwgMTkgYW5kIDIwCmluIFJlZ3Jlc3Npb24gYW5kIE90aGVyIFN0b3JpZXMuCgotLS0tLS0tLS0tLS0tCgoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChtZXNzYWdlPUZBTFNFLCBlcnJvcj1GQUxTRSwgd2FybmluZz1GQUxTRSwgY29tbWVudD1OQSkKIyBzd2l0Y2ggdGhpcyB0byBUUlVFIHRvIHNhdmUgZmlndXJlcyBpbiBzZXBhcmF0ZSBmaWxlcwpzYXZlZmlncyA8LSBGQUxTRQpgYGAKCiMjIyMgTG9hZCBwYWNrYWdlcwoKYGBge3IgfQpsaWJyYXJ5KCJycHJvanJvb3QiKQpyb290PC1oYXNfZmlsZSgiLlJPUy1FeGFtcGxlcy1yb290IikkbWFrZV9maXhfZmlsZSgpCmxpYnJhcnkoInJzdGFuYXJtIikKaW52bG9naXQgPC0gcGxvZ2lzCmBgYAoKIyMjIyBMb2FkIGRhdGEKCmBgYHtyIH0KZWxlY3RyaWNfd2lkZSA8LSByZWFkLnRhYmxlKHJvb3QoIkVsZWN0cmljQ29tcGFueS9kYXRhIiwiZWxlY3RyaWNfd2lkZS50eHQiKSwgaGVhZGVyPVRSVUUpCmhlYWQoZWxlY3RyaWNfd2lkZSkKYGBgCgojIyMjIFBsb3Qgb2YgcmF3IGRhdGEKCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgcG9zdHNjcmlwdChyb290KCJFbGVjdHJpY0NvbXBhbnkvZmlncyIsImVsZWN0cmljZGF0YS5wcyIpLCBob3Jpem9udGFsPUZBTFNFLCBoZWlnaHQ9Nywgd2lkdGg9NikKYGBgCmBgYHtyIH0Kb25seXRleHQgPC0gZnVuY3Rpb24gKHN0cmluZyl7CiAgcGxvdCgwOjEsIDA6MSwgYnR5PSduJywgdHlwZT0nbicsIHhheHQ9J24nLCB5YXh0PSduJywgeGxhYj0nJywgeWxhYj0nJykKICB0ZXh0KDAuNSwgMC41LCBzdHJpbmcsIGNleD0xLjIsIGZvbnQ9MikKfQpuZjwtIGxheW91dChtYXRyaXgoYygwLDE6MTQpLCA1LCAzLCBieXJvdz1UUlVFKSwgYyg1LCAxMCwgMTApLCBjKDEsIDUsIDUsIDUsIDUpLCBUUlVFKQpwYXIobWFyPWMoLjIsIC4yLCAuMiwgLjIpKQpvbmx5dGV4dCgnVGVzdCBzY29yZXMgaW4gY29udHJvbCBjbGFzc2VzJykKb25seXRleHQoJ1Rlc3Qgc2NvcmVzIGluIHRyZWF0ZWQgY2xhc3NlcycpCnBhcihtYXI9YygxLCAxLCAxLCAxKSwgbHdkPTAuNykKYXR0YWNoKGVsZWN0cmljX3dpZGUpCmZvciAoaiBpbiAxOjQpewogIG9ubHl0ZXh0KHBhc3RlKCdHcmFkZScsIGopKQogIGhpc3QoY29udHJvbF9wb3N0dGVzdFtncmFkZT09al0sIGJyZWFrcz1zZXEoMCwxMjUsNSksIHhheHQ9J24nLCB5YXh0PSduJywgbWFpbj1OVUxMLCBjb2w9ImdyYXkiLCB5bGltPWMoMCwxMCkpCiAgYXhpcyhzaWRlPTEsIHNlcSgwLDEyNSw1MCksIGxpbmU9LS4yNSwgY2V4LmF4aXM9MSwgbWdwPWMoMSwuMiwwKSwgdGNrPTApCiAgdGV4dCgyLCA2LjUsIHBhc3RlKCJtZWFuID0iLCByb3VuZChtZWFuKGNvbnRyb2xfcG9zdHRlc3RbZ3JhZGU9PWpdKSkpLCBhZGo9MCkKICB0ZXh0KDIsIDUsIHBhc3RlKCIgIHNkID0iLCByb3VuZChzZChjb250cm9sX3Bvc3R0ZXN0W2dyYWRlPT1qXSkpKSwgYWRqPTApCiAgaGlzdCh0cmVhdGVkX3Bvc3R0ZXN0W2dyYWRlPT1qXSwgYnJlYWtzPXNlcSgwLDEyNSw1KSwgeGF4dD0nbicsIHlheHQ9J24nLCBtYWluPU5VTEwsIGNvbD0iZ3JheSIsIHlsaW09YygwLDEwKSkKICBheGlzKHNpZGU9MSwgc2VxKDAsMTI1LDUwKSwgbGluZT0tLjI1LCBjZXguYXhpcz0xLCBtZ3A9YygxLC4yLDApLCB0Y2s9MCkKICB0ZXh0KDIsIDYuNSwgcGFzdGUoIm1lYW4gPSIsIHJvdW5kKG1lYW4odHJlYXRlZF9wb3N0dGVzdFtncmFkZT09al0pKSksIGFkaj0wKQogIHRleHQoMiwgNSwgcGFzdGUoIiAgc2QgPSIsIHJvdW5kKHNkKHRyZWF0ZWRfcG9zdHRlc3RbZ3JhZGU9PWpdKSkpLCBhZGo9MCkKfQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKYGBgCgojIyMjIFBsb3QgdGhlIGRhdGEgdGhlIG90aGVyIHdheQoKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBwb3N0c2NyaXB0KHJvb3QoIkVsZWN0cmljQ29tcGFueS9maWdzIiwiZWxlY3RyaWNkYXRhLmhvcml6b250YWwucHMiKSwgaG9yaXpvbnRhbD1GLCBoZWlnaHQ9Niwgd2lkdGg9NykKYGBgCmBgYHtyIH0Kb25seXRleHQ8LWZ1bmN0aW9uKHN0cmluZyl7CiAgcGxvdCgwOjEsIDA6MSwgYnR5PSduJywgdHlwZT0nbicsIHhheHQ9J24nLCB5YXh0PSduJywgeGxhYj0nJywgeWxhYj0nJykKICB0ZXh0KDAuNSwgMC41LCBzdHJpbmcsIGNleD0xLjIsIGZvbnQ9MikKfQpuZjwtbGF5b3V0KG1hdHJpeChjKDAsMToxNCksIDMsIDUsIGJ5cm93PUZBTFNFKSwgYyg1LCAxMCwgMTAsIDEwLCAxMCksIGMoMSwgNSwgNSksIFRSVUUpCnBhcihtYXI9YyguMiwgLjIsIC4yLCAuMikpCm9ubHl0ZXh0KCdDb250cm9sXG5jbGFzc2VzJykKb25seXRleHQoJ1RyZWF0ZWRcbmNsYXNzZXMnKQpwYXIobWFyPWMoLjIsLjQsLjIsLjQpLCBsd2Q9LjUpCmZvciAoaiBpbiAxOjQpewogIG9ubHl0ZXh0KHBhc3RlKCdHcmFkZScsIGopKQogIGhpc3QoY29udHJvbF9wb3N0dGVzdFtncmFkZT09al0sIGJyZWFrcz1zZXEoNDAsMTI1LDUpLCB4YXh0PSduJywgeWF4dD0nbicsIG1haW49TlVMTCwgY29sPSJncmF5IiwgeWxpbT1jKDAsMTQpKQogIGF4aXMoc2lkZT0xLCBzZXEoNTAsMTAwLDI1KSwgbGluZT0tLjI1LCBjZXguYXhpcz0xLCBtZ3A9YygxLC4yLDApLCB0Y2s9MCwgbHR5PSJibGFuayIpCiAgbGluZXMocmVwKG1lYW4oY29udHJvbF9wb3N0dGVzdFtncmFkZT09al0pLDIpLCBjKDAsMTEpLCBsd2Q9MikKICBoaXN0KHRyZWF0ZWRfcG9zdHRlc3RbZ3JhZGU9PWpdLCBicmVha3M9c2VxKDQwLDEyNSw1KSwgeGF4dD0nbicsIHlheHQ9J24nLCBtYWluPU5VTEwsIGNvbD0iZ3JheSIsIHlsaW09YygwLDE0KSkKICBheGlzKHNpZGU9MSwgc2VxKDUwLDEwMCwyNSksIGxpbmU9LS4yNSwgY2V4LmF4aXM9MSwgbWdwPWMoMSwuMiwwKSwgdGNrPTAsIGx0eT0iYmxhbmsiKQogIGxpbmVzKHJlcChtZWFuKHRyZWF0ZWRfcG9zdHRlc3RbZ3JhZGU9PWpdKSwyKSwgYygwLDExKSwgbHdkPTIpCn0KYGBgCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgZGV2Lm9mZigpCmBgYAoKIyMjIyBBbm90aGVyIHBsb3QKCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgcG9zdHNjcmlwdChyb290KCJFbGVjdHJpY0NvbXBhbnkvZmlncyIsImVsZWN0cmljc2NhdHRlcjFhLnBzIiksIGhvcml6b250YWw9VFJVRSwgaGVpZ2h0PTQpCmBgYApgYGB7ciB9CnBhcihtZnJvdz1jKDEsNCksIHB0eT0icyIpCngucmFuZ2UgPC0gY2JpbmQoYyg1LDQwLDQwLDQwKSwgYygyNSwxMjUsMTI1LDEyNSkpCmZvciAoaiBpbiAxOjQpewogIG9rIDwtIGdyYWRlPT1qCiAgeCA8LSBjKHRyZWF0ZWRfcHJldGVzdFtva10sIGNvbnRyb2xfcHJldGVzdFtva10pCiAgeSA8LSBjKHRyZWF0ZWRfcG9zdHRlc3Rbb2tdLCBjb250cm9sX3Bvc3R0ZXN0W29rXSkKICB0IDwtIHJlcChjKDEsMCksIHJlcChzdW0ob2spLDIpKQogIHBsb3QoYygwLDEyNSksIGMoMCwxMjUpLCB0eXBlPSJuIiwgbWFpbj1wYXN0ZSgiR3JhZGUiLGopLCB4YXhzPSJpIiwgeWF4cz0iaSIsCiAgICAgICAgeGxhYj1leHByZXNzaW9uKHBhc3RlKCJwcmUtdGVzdCwgIix4W2ldKSksCiAgICAgICAgeWxhYj1leHByZXNzaW9uKHBhc3RlKCJwb3N0LXRlc3QsICIseVtpXSkpLAogICAgICAgIGNleC5heGlzPTEuNSwgY2V4LmxhYj0xLjUsIGNleC5tYWluPTEuOCwgbWdwPWMoMi41LC43LDApKQogIGZpdF8xIDwtIHN0YW5fZ2xtKHkgfiB4ICsgdCwgZGF0YSA9IGVsZWN0cmljX3dpZGUsIHJlZnJlc2ggPSAwLCAKICAgICAgICAgICAgICAgICAgICBzYXZlX3dhcm11cCA9IEZBTFNFLCBvcGVuX3Byb2dyZXNzID0gRkFMU0UsIGNvcmVzID0gMSkKICBhYmxpbmUoY29lZihmaXRfMSlbMV0sIGNvZWYoZml0XzEpWzJdLCBsd2Q9LjUsIGx0eT0yKQogIGFibGluZShjb2VmKGZpdF8xKVsxXStjb2VmKGZpdF8xKVszXSwgY29lZihmaXRfMSlbMl0sIGx3ZD0uNSkKICBwb2ludHMoY29udHJvbF9wcmV0ZXN0W29rXSwgY29udHJvbF9wb3N0dGVzdFtva10sIHBjaD0yMCwgY2V4PTEuMikKICBwb2ludHModHJlYXRlZF9wcmV0ZXN0W29rXSwgdHJlYXRlZF9wb3N0dGVzdFtva10sIHBjaD0yMSwgY2V4PTEuMikKfQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKYGBgCgojIyMjIFlldCBhbm90aGVyIHBsb3QKCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgcG9zdHNjcmlwdChyb290KCJFbGVjdHJpY0NvbXBhbnkvZmlncyIsImVsZWN0cmljc2NhdHRlcjFiLnBzIiksIGhvcml6b250YWw9VFJVRSwgaGVpZ2h0PTQpCmBgYApgYGB7ciB9CnBhcihtZnJvdz1jKDEsNCksIHB0eT0icyIpCmZvciAoaiBpbiAxOjQpewogIG9rIDwtIGdyYWRlPT1qCiAgeCA8LSBjKHRyZWF0ZWRfcHJldGVzdFtva10sIGNvbnRyb2xfcHJldGVzdFtva10pCiAgeSA8LSBjKHRyZWF0ZWRfcG9zdHRlc3Rbb2tdLCBjb250cm9sX3Bvc3R0ZXN0W29rXSkKICB0IDwtIHJlcChjKDEsMCksIHJlcChzdW0ob2spLDIpKQogICAgcGxvdChjKDAsMTI1KSxjKDAsMTI1KSwgdHlwZT0ibiIsIG1haW49cGFzdGUoIkdyYWRlIixqKSwKICAgICAgICB4bGFiPWV4cHJlc3Npb24ocGFzdGUoInByZS10ZXN0LCAiLHhbaV0pKSwKICAgICAgICB5bGFiPWV4cHJlc3Npb24ocGFzdGUoInBvc3QtdGVzdCwgIix5W2ldKSksCiAgICAgICAgY2V4LmF4aXM9MS41LCBjZXgubGFiPTEuNSwgY2V4Lm1haW49MS44LCBtZ3A9YygyLjUsLjcsMCkpCiAgZml0XzEgPC0gc3Rhbl9nbG0oeSB+IHggKyB0LCBkYXRhID0gZWxlY3RyaWNfd2lkZSwgcmVmcmVzaCA9IDAsIAogICAgICAgICAgICAgICAgICAgIHNhdmVfd2FybXVwID0gRkFMU0UsIG9wZW5fcHJvZ3Jlc3MgPSBGQUxTRSwgY29yZXMgPSAxKQogIGFibGluZShjb2VmKGZpdF8xKVsxXSwgY29lZihmaXRfMSlbMl0sIGx3ZD0uNSwgbHR5PTIpCiAgYWJsaW5lKGNvZWYoZml0XzEpWzFdK2NvZWYoZml0XzEpWzNdLCBjb2VmKGZpdF8xKVsyXSwgbHdkPS41KQogIHBvaW50cyhjb250cm9sX3ByZXRlc3Rbb2tdLCBjb250cm9sX3Bvc3R0ZXN0W29rXSwgcGNoPTIwLCBjZXg9MS4yKQogIHBvaW50cyh0cmVhdGVkX3ByZXRlc3Rbb2tdLCB0cmVhdGVkX3Bvc3R0ZXN0W29rXSwgcGNoPTIxLCBjZXg9MS4yKQp9CmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQpgYGAKCiMjIyMgUGxvdCBtb3JlCgpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIHBvc3RzY3JpcHQocm9vdCgiRWxlY3RyaWNDb21wYW55L2ZpZ3MiLCJlbGVjdHJpY3NjYXR0ZXIyLnBzIiksIGhvcml6b250YWw9VFJVRSwgaGVpZ2h0PTQpCmBgYApgYGB7ciB9CnBhcihtZnJvdz1jKDEsNCksIHB0eT0icyIpCmZvciAoaiBpbiAxOjQpewogIG9rIDwtIGdyYWRlPT1qCiAgeCA8LSBjKHRyZWF0ZWRfcHJldGVzdFtva10sIGNvbnRyb2xfcHJldGVzdFtva10pCiAgeSA8LSBjKHRyZWF0ZWRfcG9zdHRlc3Rbb2tdLCBjb250cm9sX3Bvc3R0ZXN0W29rXSkKICB0IDwtIHJlcChjKDEsMCksIHJlcChzdW0ob2spLDIpKQogIHBsb3QoYygwLDEyNSksYygwLDEyNSksIHR5cGU9Im4iLCBtYWluPXBhc3RlKCJHcmFkZSIsaiksIHhheHM9ImkiLCB5YXhzPSJpIiwKICAgICAgICB4bGFiPWV4cHJlc3Npb24ocGFzdGUoInByZS10ZXN0LCAiLHhbaV0pKSwKICAgICAgICB5bGFiPWV4cHJlc3Npb24ocGFzdGUoInBvc3QtdGVzdCwgIix5W2ldKSksCiAgICAgICAgY2V4LmF4aXM9MS41LCBjZXgubGFiPTEuNSwgY2V4Lm1haW49MS44LCBtZ3A9YygyLjUsLjcsMCkpCiAgZml0XzEgPC0gc3Rhbl9nbG0oeSB+IHggKyB0ICsgeDp0LCBkYXRhID0gZWxlY3RyaWNfd2lkZSwgcmVmcmVzaCA9IDAsIAogICAgICAgICAgICAgICAgICAgIHNhdmVfd2FybXVwID0gRkFMU0UsIG9wZW5fcHJvZ3Jlc3MgPSBGQUxTRSwgY29yZXMgPSAxKQogIGFibGluZShjb2VmKGZpdF8xKVsxXSwgY29lZihmaXRfMSlbMl0sIGx3ZD0uNSwgbHR5PTIpCiAgYWJsaW5lKGNvZWYoZml0XzEpWzFdK2NvZWYoZml0XzEpWzNdLCBjb2VmKGZpdF8xKVsyXStjb2VmKGZpdF8xKVs0XSwgbHdkPS41KQogICMjIGxtLjEgPC0gbG0oeSB+IHggKyB0ICsgeCp0KQogIHBvaW50cyhjb250cm9sX3ByZXRlc3Rbb2tdLCBjb250cm9sX3Bvc3R0ZXN0W29rXSwgcGNoPTIwLCBjZXg9MS4yKQogIHBvaW50cyh0cmVhdGVkX3ByZXRlc3Rbb2tdLCB0cmVhdGVkX3Bvc3R0ZXN0W29rXSwgcGNoPTIxLCBjZXg9MS4yKQp9CmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQpgYGAKCiMjIyMgTGluZWFyIG1vZGVsCgpgYGB7ciB9CnBvc3RfdGVzdCA8LSBjKHRyZWF0ZWRfcG9zdHRlc3QsIGNvbnRyb2xfcG9zdHRlc3QpCnByZV90ZXN0IDwtIGModHJlYXRlZF9wcmV0ZXN0LCBjb250cm9sX3ByZXRlc3QpCmdyYWRlIDwtIHJlcChlbGVjdHJpY193aWRlJGdyYWRlLCAyKQp0cmVhdG1lbnQgPC0gcmVwKGMoMSwwKSwgcmVwKGxlbmd0aCh0cmVhdGVkX3Bvc3R0ZXN0KSwyKSkKc3VwcCA8LSByZXAoTkEsIGxlbmd0aCh0cmVhdG1lbnQpKQpuX3BhaXJzIDwtIG5yb3coZWxlY3RyaWNfd2lkZSkKcGFpcl9pZCA8LSByZXAoMTpuX3BhaXJzLCAyKQpzdXBwW3RyZWF0bWVudD09MV0gPC0gaWZlbHNlKHN1cHBsZW1lbnQ9PSJTdXBwbGVtZW50IiwgMSwgMCkKbiA8LSBsZW5ndGgocG9zdF90ZXN0KQplbGVjdHJpYyA8LSBkYXRhLmZyYW1lKHBvc3RfdGVzdCwgcHJlX3Rlc3QsIGdyYWRlLCB0cmVhdG1lbnQsIHN1cHAsIHBhaXJfaWQpCiN3cml0ZS5jc3YoZWxlY3RyaWMsIHJvb3QoIkVsZWN0cmljQ29tcGFueS9kYXRhIiwiZWxlY3RyaWMuY3N2IikpCiNlbGVjdHJpYyA8LSByZWFkLmNzdihyb290KCJFbGVjdHJpY0NvbXBhbnkvZGF0YSIsImVsZWN0cmljLmNzdiIpKQoKZml0XzMgPC0gc3Rhbl9nbG0ocG9zdF90ZXN0IH4gdHJlYXRtZW50ICsgcHJlX3Rlc3QgKyB0cmVhdG1lbnQ6cHJlX3Rlc3QsIHN1YnNldD0oZ3JhZGU9PTQpLCBkYXRhPWVsZWN0cmljLCByZWZyZXNoID0gMCkKcHJpbnQoZml0XzMpCmBgYAoKIyMjIyBBbm90aGVyIGxpbmVhciBtb2RlbAoKYGBge3IgfQpmaXRfNCA8LSBzdGFuX2dsbShwb3N0X3Rlc3QgfiB0cmVhdG1lbnQgKyBwcmVfdGVzdCArIHRyZWF0bWVudCAqIHByZV90ZXN0LAogICAgICAgICAgICAgICAgICBzdWJzZXQgPSAoZ3JhZGU9PTQpLCBkYXRhPWVsZWN0cmljLCByZWZyZXNoID0gMCkKc2ltXzQgPC0gYXMubWF0cml4KGZpdF80KQpgYGAKCiMjIyMgUGxvdCBsaW5lYXIgbW9kZWwKCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgcG9zdHNjcmlwdChyb290KCJFbGVjdHJpY0NvbXBhbnkvZmlncyIsImdyYWRlNC5pbnRlcmFjdGlvbnMucHMiKSwgaG9yaXpvbnRhbD1UUlVFLCBoZWlnaHQ9My44LCB3aWR0aD01KQpgYGAKYGBge3IgfQpwbG90KDAsIDAsIHhsaW09cmFuZ2UocHJlX3Rlc3RbZ3JhZGU9PTRdKSwgeWxpbT1jKC01LDEwKSwKICAgICAgIHhsYWI9InByZS10ZXN0IiwgeWxhYj0idHJlYXRtZW50IGVmZmVjdCIsIG1haW49InRyZWF0bWVudCBlZmZlY3QgaW4gZ3JhZGUgNCIpCmFibGluZSgwLCAwLCBsd2Q9LjUsIGx0eT0yKQpmb3IgKGkgaW4gMToyMCkKICBjdXJ2ZShzaW1fNFtpLDJdICsgc2ltXzRbaSw0XSp4LCBsd2Q9LjUsIGNvbD0iZ3JheSIsIGFkZD1UUlVFKQpjdXJ2ZShjb2VmKGZpdF80KVsyXSArIGNvZWYoZml0XzQpWzRdKngsIGx3ZD0uNSwgYWRkPVRSVUUpCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIGRldi5vZmYoKQpgYGAKCiMjIyMgTWVhbiBlZmZlY3QKCmBgYHtyIH0Kbl9zaW1zIDwtIDEwMDAKZWZmZWN0IDwtIGFycmF5KE5BLCBjKG5fc2ltcywgc3VtKGdyYWRlPT00KSkpCmZvciAoaSBpbiAxOm5fc2ltcykKICBlZmZlY3RbaSxdIDwtIHNpbV80W2ksMl0gKyBzaW1fNFtpLDRdKnByZV90ZXN0W2dyYWRlPT00XQptZWFuX2VmZmVjdCA8LSByb3dNZWFucyhlZmZlY3QpCmBgYAoKIyMjIyBQbG90IHJlcGVhdGVkIHJlZ3Jlc3Npb24gcmVzdWx0cwoKYGBge3IgfQplc3QxIDwtIHJlcChOQSw0KQplc3QyIDwtIHJlcChOQSw0KQpzZTEgPC0gcmVwKE5BLDQpCnNlMiA8LSByZXAoTkEsNCkKZm9yIChrIGluIDE6NCkgewogICAgZml0XzEgPC0gc3Rhbl9nbG0ocG9zdF90ZXN0IH4gdHJlYXRtZW50LCBzdWJzZXQ9KGdyYWRlPT1rKSwgZGF0YSA9IGVsZWN0cmljLAogICAgICAgICAgICAgICAgICAgICAgcmVmcmVzaCA9IDAsIHNhdmVfd2FybXVwID0gRkFMU0UsIAogICAgICAgICAgICAgICAgICAgICAgb3Blbl9wcm9ncmVzcyA9IEZBTFNFLCBjb3JlcyA9IDEpCiAgICBmaXRfMiA8LSBzdGFuX2dsbShwb3N0X3Rlc3QgfiB0cmVhdG1lbnQgKyBwcmVfdGVzdCwgc3Vic2V0PShncmFkZT09ayksCiAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZWxlY3RyaWMsIHJlZnJlc2ggPSAwLCBzYXZlX3dhcm11cCA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgIG9wZW5fcHJvZ3Jlc3MgPSBGQUxTRSwgY29yZXMgPSAxKQogICAgZXN0MVtrXSA8LSBjb2VmKGZpdF8xKVsyXQogICAgZXN0MltrXSA8LSBjb2VmKGZpdF8yKVsyXQogICAgc2UxW2tdIDwtIHNlKGZpdF8xKVsyXQogICAgc2UyW2tdIDwtIHNlKGZpdF8yKVsyXQp9CnJlZ3Jlc3Npb24uMnRhYmxlcyA8LSBmdW5jdGlvbiAobmFtZSwgZXN0MSwgZXN0Miwgc2UxLCBzZTIsIGxhYmVsMSwgbGFiZWwyLCBmaWxlLCBib3R0b209RkFMU0UpewogIEogPC0gbGVuZ3RoKG5hbWUpCiAgbmFtZS5yYW5nZSA8LSAuNgogIHgucmFuZ2UgPC0gcmFuZ2UgKGVzdDErMipzZTEsIGVzdDEtMipzZTEsIGVzdDIrMipzZTIsIGVzdDEtMipzZTIpCiAgQSA8LSAteC5yYW5nZVsxXS8oeC5yYW5nZVsyXS14LnJhbmdlWzFdKQogIEIgPC0gMS8oeC5yYW5nZVsyXS14LnJhbmdlWzFdKQogIGhlaWdodCA8LSAuNipKCiAgd2lkdGggPC0gOCoobmFtZS5yYW5nZSsxKQogIGdhcCA8LSAuNAoKICBpZiAoIWlzLm5hKGZpbGUpKSBwb3N0c2NyaXB0KGZpbGUsIGhvcml6b250YWw9RiwgaGVpZ2h0PWhlaWdodCwgd2lkdGg9d2lkdGgpCiAgcGFyIChtYXI9YygwLDAsMCwwKSkKICBwbG90IChjKC1uYW1lLnJhbmdlLDIrZ2FwKSwgYygzLC1KLTIpLCBidHk9Im4iLCB4bGFiPSIiLCB5bGFiPSIiLAogICAgICAgIHhheHQ9Im4iLCB5YXh0PSJuIiwgeGF4cz0iaSIsIHlheHM9ImkiLCB0eXBlPSJuIikKICB0ZXh0ICgtbmFtZS5yYW5nZSwgMiwgIlN1YnBvcHVsYXRpb24iLCBhZGo9MCwgY2V4PTEpCiAgdGV4dCAoLjUsIDIsIGxhYmVsMSwgYWRqPS41LCBjZXg9MSkKICB0ZXh0ICgxK2dhcCsuNSwgMiwgbGFiZWwyLCBhZGo9LjUsIGNleD0xKQogIGxpbmVzIChjKDAsMSksIGMoMCwwKSkKICBsaW5lcyAoMStnYXArYygwLDEpLCBjKDAsMCkpCiAgbGluZXMgKGMoQSxBKSwgYygwLC1KLTEpLCBsdHk9MiwgbHdkPS41KQogIGxpbmVzICgxK2dhcCtjKEEsQSksIGMoMCwtSi0xKSwgbHR5PTIsIGx3ZD0uNSkKICBheCA8LSBwcmV0dHkgKHgucmFuZ2UpCiAgYXggPC0gYXhbKEErQipheCk+MCAmIChBK0IqYXgpPDFdCiAgc2VnbWVudHMgKEEgKyBCKmF4LCAtLjEsIEEgKyBCKmF4LCAuMSwgbHdkPS41KQogIHNlZ21lbnRzICgxK2dhcCtBICsgQipheCwgLS4xLCAxK2dhcCtBICsgQipheCwgLjEsIGx3ZD0uNSkKICB0ZXh0IChBICsgQipheCwgLjcsIGF4LCBjZXg9MSkKICB0ZXh0ICgxK2dhcCtBICsgQipheCwgLjcsIGF4LCBjZXg9MSkKICB0ZXh0ICgtbmFtZS5yYW5nZSwgLSgxOkopLCBuYW1lLCBhZGo9MCwgY2V4PTEpCiAgcG9pbnRzIChBICsgQiplc3QxLCAtKDE6SiksIHBjaD0yMCwgY2V4PTEpCiAgcG9pbnRzICgxK2dhcCtBICsgQiplc3QyLCAtKDE6SiksIHBjaD0yMCwgY2V4PTEpCiAgc2VnbWVudHMgKEEgKyBCKihlc3QxLXNlMSksIC0oMTpKKSwgQSArIEIqKGVzdDErc2UxKSwgLSgxOkopLCBsd2Q9MykKICBzZWdtZW50cyAoMStnYXArQSArIEIqKGVzdDItc2UyKSwgLSgxOkopLCAxK2dhcCtBICsgQiooZXN0MitzZTIpLCAtKDE6SiksIGx3ZD0zKQogIHNlZ21lbnRzIChBICsgQiooZXN0MS0yKnNlMSksIC0oMTpKKSwgQSArIEIqKGVzdDErMipzZTEpLCAtKDE6SiksIGx3ZD0uNSkKICBzZWdtZW50cyAoMStnYXArQSArIEIqKGVzdDItMipzZTIpLCAtKDE6SiksIDErZ2FwK0EgKyBCKihlc3QyKzIqc2UyKSwgLSgxOkopLCBsd2Q9LjUpCiAgaWYgKGJvdHRvbSl7CiAgICBsaW5lcyAoYygwLDEpLCBjKC1KLTEsLUotMSkpCiAgICBsaW5lcyAoMStnYXArYygwLDEpLCBjKC1KLTEsLUotMSkpCiAgICBzZWdtZW50cyAoQSArIEIqYXgsIC1KLTEtLjEsIEEgKyBCKmF4LCAtSi0xKy4xLCBsd2Q9LjUpCiAgICBzZWdtZW50cyAoMStnYXArQSArIEIqYXgsIC1KLTEtLjEsIDErZ2FwK0EgKyBCKmF4LCAtSi0xKy4xLCBsd2Q9LjUpCiAgICB0ZXh0IChBICsgQipheCwgLUotMS0uNywgYXgsIGNleD0xKQogICAgdGV4dCAoMStnYXArQSArIEIqYXgsIC1KLTEtLjcsIGF4LCBjZXg9MSkKICB9IAogIGlmICghaXMubmEoZmlsZSkpIGdyYXBoaWNzLm9mZigpCn0KYGBgCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CiMgcGxvdCB0byBmaWxlCnJlZ3Jlc3Npb24uMnRhYmxlcyhwYXN0ZSgiR3JhZGUiLCAxOjQpLCBlc3QxLCBlc3QyLCBzZTEsIHNlMiwgIlJlZ3Jlc3Npb24gb24gdHJlYXRtZW50IGluZGljYXRvciIsICJSZWdyZXNzaW9uIG9uIHRyZWF0bWVudCBpbmRpY2F0b3IsXG5jb250cm9sbGluZyBmb3IgcHJlLXRlc3QiLCByb290KCJFbGVjdHJpY0NvbXBhbnkvZmlncyIsImVsZWN0cmljLmVzdHMucHMiKSkKYGBgCmBgYHtyIH0KcmVncmVzc2lvbi4ydGFibGVzKHBhc3RlKCJHcmFkZSIsIDE6NCksIGVzdDEsIGVzdDIsIHNlMSwgc2UyLCAiUmVncmVzc2lvbiBvbiB0cmVhdG1lbnQgaW5kaWNhdG9yIiwgIlJlZ3Jlc3Npb24gb24gdHJlYXRtZW50IGluZGljYXRvcixcbmNvbnRyb2xsaW5nIGZvciBwcmUtdGVzdCIsIE5BKQpgYGAKCiMjIyMgUGxvdCByZXBsYWNlL3N1cHBsZW1lbnQKCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgcG9zdHNjcmlwdChyb290KCJFbGVjdHJpY0NvbXBhbnkvZmlncyIsImVsZWN0cmljc3VwcDEucHMiKSwgaG9yaXpvbnRhbD1UUlVFLCBoZWlnaHQ9Mi42KQpgYGAKYGBge3IgfQpqaXR0ZXIuYmluYXJ5IDwtIGZ1bmN0aW9uIChhLCBqaXR0PS4wNSl7CiAgYSArICgxLTIqYSkqcnVuaWYobGVuZ3RoKGEpLCAwLCBqaXR0KQp9CnBhcihtZnJvdz1jKDEsNCkpCmZvciAoayBpbiAxOjQpewogIGNhdChwYXN0ZSgiR3JhZGUiLGssIjpcbiIpKQogIG9rIDwtIChncmFkZT09aykmKCFpcy5uYShzdXBwKSkKICBnbG1fc3VwcCA8LSBzdGFuX2dsbShzdXBwIH4gcHJlX3Rlc3QsIGZhbWlseT1iaW5vbWlhbChsaW5rPSJsb2dpdCIpLAogICAgICAgICAgICAgICAgICAgICAgIHN1YnNldD1vaywgZGF0YT1lbGVjdHJpYywgcmVmcmVzaCA9IDAsIAogICAgICAgICAgICAgICAgICAgICAgIHNhdmVfd2FybXVwID0gRkFMU0UsIG9wZW5fcHJvZ3Jlc3MgPSBGQUxTRSwgY29yZXMgPSAxKQogIHByaW50KGdsbV9zdXBwKQogIHNpbXMuZ2xtLnN1cHAgPC0gYXMubWF0cml4KGdsbV9zdXBwKQogIHBsb3QocmFuZ2UocHJlX3Rlc3Rbb2tdKSwgYygwLDEpLCB0eXBlPSJuIiwgeGxhYj0iUHJlLXRlc3Qgc2NvcmUiLAogICAgICAgIHlsYWI9IiIsIG1haW49cGFzdGUoIkdyYWRlIiwgayksCiAgICAgICAgY2V4LmF4aXM9MS41LCBjZXgubGFiPTEuNSwgY2V4Lm1haW49MS44LCBtZ3A9YygyLjUsLjcsMCksIHlheHQ9Im4iKQogIGF4aXMoMiwgYygwLDEpLCBjKCIgICAgIFJlcGxhY2UiLCJTdXBwICAgICIpLCBjZXguYXhpcz0xLjUsIG1ncD1jKDIuNSwuNywwKSkKICBmb3IgKGwgaW4gMToyMCkKICAgICAgY3VydmUoaW52bG9naXQoc2ltcy5nbG0uc3VwcFtsLDFdICsgc2ltcy5nbG0uc3VwcFtsLDJdKngpLCBsd2Q9LjUsIGNvbD0iZ3JheSIsIGFkZD1UUlVFKQogIHBvaW50cyhwcmVfdGVzdFtzdXBwPT0xJm9rXSwgaml0dGVyLmJpbmFyeShzdXBwW3N1cHA9PTEmb2tdKSwgcGNoPTIxKSAjICBzdXBwOiAgb3BlbiBjaXJjbGUKICBwb2ludHMocHJlX3Rlc3Rbc3VwcD09MCZva10sIGppdHRlci5iaW5hcnkoc3VwcFtzdXBwPT0wJm9rXSksIHBjaD0yMCkjIHJlcGxhY2U6ICBkb3QKICBjdXJ2ZShpbnZsb2dpdChjb2VmKGdsbV9zdXBwKVsxXSArIGNvZWYoZ2xtX3N1cHApWzJdKngpLCBsd2Q9LjUsIGFkZD1UUlVFKQogIGdsbV9zdXBwIDwtIHN0YW5fZ2xtKHBvc3RfdGVzdCB+IHN1cHAgKyBwcmVfdGVzdCwKICAgICAgICAgICAgICAgICAgICAgICBzdWJzZXQ9KChncmFkZT09aykmIWlzLm5hKHN1cHApKSwgZGF0YT1lbGVjdHJpYywgcmVmcmVzaCA9IDAsIAogICAgICAgICAgICAgICAgICAgICAgIHNhdmVfd2FybXVwID0gRkFMU0UsIG9wZW5fcHJvZ3Jlc3MgPSBGQUxTRSwgY29yZXMgPSAxKQogIHByaW50KGdsbV9zdXBwKQogIGVzdDFba10gPC0gY29lZihnbG1fc3VwcClbMl0KICBzZTFba10gPC0gc2UoZ2xtX3N1cHApWzJdCn0KYGBgCmBgYHtyIGV2YWw9RkFMU0UsIGluY2x1ZGU9RkFMU0V9CmlmIChzYXZlZmlncykgZGV2Lm9mZigpCmBgYApgYGB7ciB9CnJlZ3Jlc3Npb24uMnRhYmxlc0EgPC0gZnVuY3Rpb24gKG5hbWUsIGVzdDEsIHNlMSwgbGFiZWwxLCBmaWxlLCBib3R0b209RkFMU0UpewogIEogPC0gbGVuZ3RoKG5hbWUpCiAgbmFtZS5yYW5nZSA8LSAuNgogIHgucmFuZ2UgPC0gcmFuZ2UgKGVzdDErMipzZTEsIGVzdDEtMipzZTEpCiAgQSA8LSAteC5yYW5nZVsxXS8oeC5yYW5nZVsyXS14LnJhbmdlWzFdKQogIEIgPC0gMS8oeC5yYW5nZVsyXS14LnJhbmdlWzFdKQogIGhlaWdodCA8LSAuNipKCiAgd2lkdGggPC0gOCoobmFtZS5yYW5nZSsxKQogIGdhcCA8LSAuNAogIAogIGlmICghaXMubmEoZmlsZSkpIHBvc3RzY3JpcHQoZmlsZSwgaG9yaXpvbnRhbD1GLCBoZWlnaHQ9aGVpZ2h0LCB3aWR0aD13aWR0aCkKICBwYXIgKG1hcj1jKDAsMCwwLDApKQogIHBsb3QgKGMoLW5hbWUucmFuZ2UsMitnYXApLCBjKDMsLUotMiksIGJ0eT0ibiIsIHhsYWI9IiIsIHlsYWI9IiIsCiAgICAgICAgeGF4dD0ibiIsIHlheHQ9Im4iLCB4YXhzPSJpIiwgeWF4cz0iaSIsIHR5cGU9Im4iKQogIHRleHQgKC1uYW1lLnJhbmdlLCAyLCAiU3VicG9wdWxhdGlvbiIsIGFkaj0wLCBjZXg9MSkKICB0ZXh0ICguNSwgMiwgbGFiZWwxLCBhZGo9LjUsIGNleD0xKQogIGxpbmVzIChjKDAsMSksIGMoMCwwKSkKICBsaW5lcyAoYyhBLEEpLCBjKDAsLUotMSksIGx0eT0yLCBsd2Q9LjUpCiAgYXggPC0gcHJldHR5ICh4LnJhbmdlKQogIGF4IDwtIGF4WyhBK0IqYXgpPjAgJiAoQStCKmF4KTwxXQogIHNlZ21lbnRzIChBICsgQipheCwgLS4xLCBBICsgQipheCwgLjEsIGx3ZD0uNSkKICB0ZXh0IChBICsgQipheCwgLjcsIGF4LCBjZXg9MSkKICB0ZXh0ICgtbmFtZS5yYW5nZSwgLSgxOkopLCBuYW1lLCBhZGo9MCwgY2V4PTEpCiAgcG9pbnRzIChBICsgQiplc3QxLCAtKDE6SiksIHBjaD0yMCwgY2V4PTEpCiAgc2VnbWVudHMgKEEgKyBCKihlc3QxLXNlMSksIC0oMTpKKSwgQSArIEIqKGVzdDErc2UxKSwgLSgxOkopLCBsd2Q9MykKICBzZWdtZW50cyAoQSArIEIqKGVzdDEtMipzZTEpLCAtKDE6SiksIEEgKyBCKihlc3QxKzIqc2UxKSwgLSgxOkopLCBsd2Q9LjUpCiAgaWYgKGJvdHRvbSl7CiAgICBsaW5lcyAoYygwLDEpLCBjKC1KLTEsLUotMSkpCiAgICBzZWdtZW50cyAoQSArIEIqYXgsIC1KLTEtLjEsIEEgKyBCKmF4LCAtSi0xKy4xLCBsd2Q9LjUpCiAgICB0ZXh0IChBICsgQipheCwgLUotMS0uNywgYXgsIGNleD0xKQogIH0gCiAgaWYgKCFpcy5uYShmaWxlKSkgZ3JhcGhpY3Mub2ZmKCkKfQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KIyBwbG90IHRvIGZpbGUKcmVncmVzc2lvbi4ydGFibGVzQShwYXN0ZSgiR3JhZGUiLCAxOjQpLCBlc3QxLCBzZTEsICJFc3RpbWF0ZWQgZWZmZWN0IG9mIHN1cHBsZW1lbnQsXG5jb21wYXJlZCB0byByZXBsYWNlbWVudCIsIHJvb3QoIkVsZWN0cmljQ29tcGFueS9maWdzIiwiZWxlY3RyaWNzdXBwMi5wcyIpKQpgYGAKYGBge3IgfQpyZWdyZXNzaW9uLjJ0YWJsZXNBKHBhc3RlKCJHcmFkZSIsIDE6NCksIGVzdDEsIHNlMSwgIkVzdGltYXRlZCBlZmZlY3Qgb2Ygc3VwcGxlbWVudCxcbmNvbXBhcmVkIHRvIHJlcGxhY2VtZW50IiwgTkEpCmBgYAoK