Nonlinear models (loess, spline, GP, and BART) and political attitudes as a function of age. See Chapter 22 in Regression and Other Stories.


Load packages

library("rprojroot")
root<-has_file(".ROS-Examples-root")$make_fix_file()
library("rstanarm")
# Enable parallel computation in stan_gamm4 used for splines and GPs
options(mc.cores = parallel::detectCores())
library("dbarts")

Define common plot functions

gay_plot <- function(fit=NULL, question=NULL, title=NULL, savefigs=FALSE) {
  if (savefigs) pdf(root("Gay/figs",paste("gay", index, ".pdf", sep="")), height=4.5, width=6)
  par(mar=c(3,3,1,1), mgp=c(1.7, .5, 0), tck=-.01)
  plot(gay_sum[[j]]$age, gay_sum[[j]]$y/gay_sum[[j]]$n, ylim=c(0,.65), yaxs="i", xlab="Age", ylab=question, yaxt="n", bty="l", type="n", main=title)
  axis(2, seq(0,1,.2), c("0","20%","40%","60%","80%","100%"))
  symbols(gay_sum[[j]]$age, gay_sum[[j]]$y/gay_sum[[j]]$n, circles=sqrt(gay_sum[[j]]$n), inches=.1, add=TRUE, fg=if (is.null(fit)) "black" else "gray80", bg=if (is.null(fit)) "gray70" else "gray90")
  if (!is.null(fit)) {
    n_sims <- nrow(fit)
    for (i in sample(n_sims, 20)){
      lines(gay_sum[[j]]$age, fit[i,], lwd=.5, col="gray50")
    }
    lines(gay_sum[[j]]$age, colMeans(fit), lwd=2)
  }
  if (savefigs) dev.off()
  index <<- index + 1
}

gay_plot_1 <- function(fit=NULL, question=NULL, title=NULL, k_bottom) {
  age <- gay_sum[[j]]$age
  y <- gay_sum[[j]]$y
  n <- gay_sum[[j]]$n
  plot(age, y/n, ylim=c(0,.65), yaxs="i", xlab= if (k==k_bottom) "Age" else "", ylab="", xaxt= if (k<k_bottom) "n", yaxt="n", bty="l", type="n", cex.lab=1.2, cex.axis=1.2)
  axis(1, seq(20,80,20), rep("",4), cex.axis=1.2)
  axis(2, seq(0,1,.1), rep("",11))
  axis(2, seq(0,1,.2), c("0","20%","40%","60%","80%","100%"), cex.axis=1.2)
  symbols(age, y/n, circles=sqrt(n), inches=.1, add=TRUE, fg="gray80", bg="gray90")
  n_sims <- nrow(fit)
  for (i in sample(n_sims, 20)){
    lines(age, fit[i,], lwd=.5, col="gray50")
  }
  lines(age, colMeans(fit), lwd=2)
}

gay_plot_2 <- function(fit=NULL, question=NULL, title=NULL, m=NULL) {
  ok <- gay_sum_2[[j]]$male == m
  age <- gay_sum_2[[j]]$age[ok]
  y <- gay_sum_2[[j]]$y[ok]
  n <- gay_sum_2[[j]]$n[ok]
  plot(age, y/n, ylim=c(0,.65), yaxs="i", xlab= if (k==4) "Age" else "", ylab="", xaxt= if (k<4) "n", yaxt="n", bty="l", type="n", cex.lab=1.4, cex.axis=1.4, main=if (m==0) "Women" else "Men")
  axis(1, seq(20,80,20), rep("",4), cex.axis=1.4)
  axis(2, seq(0,1,.1), rep("",11))
  axis(2, seq(0,1,.2), c("0","20%","40%","60%","80%","100%"), cex.axis=1.4)
  symbols(age, y/n, circles=sqrt(n), inches=.1, add=TRUE, fg="gray80", bg="gray90")
  n_sims <- nrow(fit)
  for (i in sample(n_sims, 20)){
    lines(age, fit[i,ok], lwd=.5, col="gray50")
  }
  lines(age, colMeans(fit[,ok]), lwd=2)
}

Two different questions

question <- c("Support for same-sex marriage", "Do you know any gay people?")
variable <- c("gayFavorStateMarriage", "gayKnowSomeone")
index <- 0

Loop over two different questions

Prepare variables to store results

gay <- as.list(rep(NA, 2))
gay_sum <- as.list(rep(NA, 2))
gay_sum_2 <- as.list(rep(NA, 2))
gay_loess <- as.list(rep(NA, 2))
gay_spline <- as.list(rep(NA, 2))
gay_GP <- as.list(rep(NA, 2))
gay_bart <- as.list(rep(NA, 2))
gay_spline_2 <- as.list(rep(NA, 2))
for (j in 1:2){
  
  # Prepare the data
  gay[[j]] <- read.csv(root("Gay/data","naes04.csv"))
  gay[[j]] <- gay[[j]][!is.na(gay[[j]][,"age"]) & !is.na(gay[[j]][,variable[j]]),]
  gay[[j]]$age[gay[[j]]$age>90] <- 91
  y <- as.character(gay[[j]][,variable[j]])
  gay[[j]]$y <- ifelse(y=="Yes", 1, ifelse(y=="No", 0, NA))
  gender <- as.character(gay[[j]][,"gender"])
  gay[[j]]$male <- ifelse(gender=="Female", 0, ifelse(gender=="Male", 1, NA))
  
  uniq_age <- sort(unique(gay[[j]]$age))
  n_age <- length(uniq_age)
  tab <- table(gay[[j]]$age, gay[[j]]$y)
  y_sum <- as.vector(tab[,2])
  n_sum <- as.vector(tab[,1] + tab[,2])
  gay_sum[[j]] <- data.frame(n=n_sum, y=y_sum, age=uniq_age)
  gay_sum_2[[j]] <- data.frame(array(NA, c(n_age*2, 4), dimnames=list(NULL, c("n", "y", "age", "male"))))
  for (i1 in 1:n_age){
    for (i2 in 0:1){
      ok <- gay[[j]]$age==uniq_age[i1] & gay[[j]]$male==i2
      n_sum_2 <- sum(ok)
      y_sum_2 <- sum(gay[[j]]$y[ok])
      age_sum_2 <- uniq_age[i1]
      male_sum_2 <- i2
      gay_sum_2[[j]][n_age*i2 + i1,] <- c(n_sum_2, y_sum_2, age_sum_2, male_sum_2)
    }
  }
  
  # Make the plots
  gay_plot(question=question[j], title="Raw data from a national survey", savefigs = savefigs)

  # LOESS
  gay_loess[[j]] <- loess(y ~ age, data=gay[[j]])
  gay_loess_fit <- predict(gay_loess[[j]], data.frame(age=gay_sum[[j]]$age))
  gay_loess_fit <- matrix(gay_loess_fit, nrow=100, ncol=length(gay_loess_fit), byrow=TRUE)
  gay_plot(gay_loess_fit, question=question[j], title="Loess fit", savefigs = savefigs)

  # Splines
  gay_spline[[j]] <- stan_gamm4(I(y/n) ~ s(age), data=gay_sum[[j]], adapt_delta=0.99)
  gay_spline_fit <- posterior_linpred(gay_spline[[j]], data.frame(age=gay_sum[[j]]$age))
  gay_plot(gay_spline_fit, question=question[j], title="Spline fit and uncertainty", savefigs = savefigs)

  # GP represented with splines
  gay_GP[[j]] <- stan_gamm4(I(y/n) ~ age + s(age, bs="gp"), data=gay_sum[[j]], prior_smooth=student_t(df=4, scale=100), adapt_delta=0.99)
  gay_GP_fit <- posterior_linpred(gay_GP[[j]], data.frame(age=gay_sum[[j]]$age))
  gay_plot(gay_GP_fit, question=question[j], title="Gaussian process fit and uncertainty", savefigs = savefigs)
  # BART
  output <- capture.output(
    gay_bart[[j]] <- bart(gay[[j]]$age, gay[[j]]$y, matrix(uniq_age), ntree = 20))
  gay_bart_fit <- pnorm(gay_bart[[j]]$yhat.test)
  gay_plot(gay_bart_fit, question=question[j], title="Bart fit and uncertainty", savefigs = savefigs)

  # Another spline
  gay_spline_2[[j]] <- stan_gamm4(I(y/n) ~ s(age, male), data=gay_sum_2[[j]])
}

New graphs

par(mar=c(3,2,1,1), mgp=c(1.7, .5, 0), tck=-.01)
par(mfrow=c(1,2))
for (j in 1:2){
  plot(gay_sum[[j]]$age, gay_sum[[j]]$y/gay_sum[[j]]$n, ylim=c(0,.69), yaxs="i", xlab="Age", ylab="", yaxt="n", bty="l", type="n", main=question[[j]])
  axis(2, seq(0,1,.1), rep("",11))
  axis(2, seq(0,1,.2), c("0","20%","40%","60%","80%","100%"))
  symbols(gay_sum[[j]]$age, gay_sum[[j]]$y/gay_sum[[j]]$n, circles=sqrt(gay_sum[[j]]$n), inches=.1, add=TRUE, fg="black", bg="gray70")
}

par(mar=c(3,2,1,1), mgp=c(1.7, .5, 0), tck=-.01)
par(mfcol=c(4,2), oma=c(0,0,2.5,0))
for (j in 1:2){
  k=1
  gay_loess_fit <- predict(gay_loess[[j]], data.frame(age=gay_sum[[j]]$age))
  gay_loess_fit <- matrix(gay_loess_fit, nrow=100, ncol=length(gay_loess_fit), byrow=TRUE)
  gay_plot_1(gay_loess_fit, question=question[j], title="Loess fit", k_bottom=4)
  k=2
  gay_spline_fit <- posterior_linpred(gay_spline[[j]], data.frame(age=gay_sum[[j]]$age))
  gay_plot_1(gay_spline_fit, question=question[j], title="Spline fit and uncertainty", k_bottom=4)
  k=3
  gay_GP_fit <- posterior_linpred(gay_GP[[j]], data.frame(age=gay_sum[[j]]$age))
  gay_plot_1(gay_GP_fit, question="Support for same-sex marriage", title="Gaussian process fit and uncertainty", k_bottom=4)
  k=4
  gay_bart_fit <- pnorm(gay_bart[[j]]$yhat.test)
  gay_plot_1(gay_bart_fit, question=question[j], title="Bart fit and uncertainty", k_bottom=4)
}
mtext(paste(question[[1]], question[[2]], sep="                               "), side=3, line=1, outer=TRUE)

par(mar=c(3,2,1,1), mgp=c(1.7, .5, 0), tck=-.01)
par(mfcol=c(2,2))
for (j in 1:2){
  gay_spline_2_fit <- posterior_linpred(gay_spline_2[[j]], data.frame(age=gay_sum_2[[j]]$age, male=gay_sum_2[[j]]$male))
  for (m in 0:1){                    
    gay_plot_2(gay_spline_2_fit, question=question[j], title="2-dimensional spline fit", m=m)
  }
}

par(mar=c(3,2,1,1), mgp=c(1.7, .5, 0), tck=-.01)
par(mfcol=c(2,2), oma=c(0,0,2.5,0))
for (j in 1:2){
  k=1
  gay_loess_fit <- predict(gay_loess[[j]], data.frame(age=gay_sum[[j]]$age))
  gay_loess_fit <- matrix(gay_loess_fit, nrow=100, ncol=length(gay_loess_fit), byrow=TRUE)
  gay_plot_1(gay_loess_fit, question=question[j], title="Loess fit", k_bottom=2)
  k=2
  gay_spline_fit <- posterior_linpred(gay_spline[[j]], data.frame(age=gay_sum[[j]]$age))
  gay_plot_1(gay_spline_fit, question=question[j], title="Spline fit and uncertainty", k_bottom=2)
  k=3
}
mtext(paste(question[[1]], question[[2]], sep="                               "), side=3, line=1, outer=TRUE)

LS0tCnRpdGxlOiAiUmVncmVzc2lvbiBhbmQgT3RoZXIgU3RvcmllczogR2F5IgphdXRob3I6ICJBbmRyZXcgR2VsbWFuLCBBa2kgVmVodGFyaSIKZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdGhlbWU6IHJlYWRhYmxlCiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMgogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQpOb25saW5lYXIgbW9kZWxzIChsb2Vzcywgc3BsaW5lLCBHUCwgYW5kIEJBUlQpIGFuZCBwb2xpdGljYWwKYXR0aXR1ZGVzIGFzIGEgZnVuY3Rpb24gb2YgYWdlLiBTZWUgQ2hhcHRlciAyMiBpbiBSZWdyZXNzaW9uIGFuZApPdGhlciBTdG9yaWVzLgoKLS0tLS0tLS0tLS0tLQoKCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGNvbW1lbnQ9TkEpCiMgc3dpdGNoIHRoaXMgdG8gVFJVRSB0byBzYXZlIGZpZ3VyZXMgaW4gc2VwYXJhdGUgZmlsZXMKc2F2ZWZpZ3MgPC0gRkFMU0UKYGBgCgojIyMjIExvYWQgcGFja2FnZXMKCmBgYHtyIH0KbGlicmFyeSgicnByb2pyb290IikKcm9vdDwtaGFzX2ZpbGUoIi5ST1MtRXhhbXBsZXMtcm9vdCIpJG1ha2VfZml4X2ZpbGUoKQpsaWJyYXJ5KCJyc3RhbmFybSIpCiMgRW5hYmxlIHBhcmFsbGVsIGNvbXB1dGF0aW9uIGluIHN0YW5fZ2FtbTQgdXNlZCBmb3Igc3BsaW5lcyBhbmQgR1BzCm9wdGlvbnMobWMuY29yZXMgPSBwYXJhbGxlbDo6ZGV0ZWN0Q29yZXMoKSkKbGlicmFyeSgiZGJhcnRzIikKYGBgCgojIyMjIERlZmluZSBjb21tb24gcGxvdCBmdW5jdGlvbnMKCmBgYHtyIH0KZ2F5X3Bsb3QgPC0gZnVuY3Rpb24oZml0PU5VTEwsIHF1ZXN0aW9uPU5VTEwsIHRpdGxlPU5VTEwsIHNhdmVmaWdzPUZBTFNFKSB7CiAgaWYgKHNhdmVmaWdzKSBwZGYocm9vdCgiR2F5L2ZpZ3MiLHBhc3RlKCJnYXkiLCBpbmRleCwgIi5wZGYiLCBzZXA9IiIpKSwgaGVpZ2h0PTQuNSwgd2lkdGg9NikKICBwYXIobWFyPWMoMywzLDEsMSksIG1ncD1jKDEuNywgLjUsIDApLCB0Y2s9LS4wMSkKICBwbG90KGdheV9zdW1bW2pdXSRhZ2UsIGdheV9zdW1bW2pdXSR5L2dheV9zdW1bW2pdXSRuLCB5bGltPWMoMCwuNjUpLCB5YXhzPSJpIiwgeGxhYj0iQWdlIiwgeWxhYj1xdWVzdGlvbiwgeWF4dD0ibiIsIGJ0eT0ibCIsIHR5cGU9Im4iLCBtYWluPXRpdGxlKQogIGF4aXMoMiwgc2VxKDAsMSwuMiksIGMoIjAiLCIyMCUiLCI0MCUiLCI2MCUiLCI4MCUiLCIxMDAlIikpCiAgc3ltYm9scyhnYXlfc3VtW1tqXV0kYWdlLCBnYXlfc3VtW1tqXV0keS9nYXlfc3VtW1tqXV0kbiwgY2lyY2xlcz1zcXJ0KGdheV9zdW1bW2pdXSRuKSwgaW5jaGVzPS4xLCBhZGQ9VFJVRSwgZmc9aWYgKGlzLm51bGwoZml0KSkgImJsYWNrIiBlbHNlICJncmF5ODAiLCBiZz1pZiAoaXMubnVsbChmaXQpKSAiZ3JheTcwIiBlbHNlICJncmF5OTAiKQogIGlmICghaXMubnVsbChmaXQpKSB7CiAgICBuX3NpbXMgPC0gbnJvdyhmaXQpCiAgICBmb3IgKGkgaW4gc2FtcGxlKG5fc2ltcywgMjApKXsKICAgICAgbGluZXMoZ2F5X3N1bVtbal1dJGFnZSwgZml0W2ksXSwgbHdkPS41LCBjb2w9ImdyYXk1MCIpCiAgICB9CiAgICBsaW5lcyhnYXlfc3VtW1tqXV0kYWdlLCBjb2xNZWFucyhmaXQpLCBsd2Q9MikKICB9CiAgaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKICBpbmRleCA8PC0gaW5kZXggKyAxCn0KCmdheV9wbG90XzEgPC0gZnVuY3Rpb24oZml0PU5VTEwsIHF1ZXN0aW9uPU5VTEwsIHRpdGxlPU5VTEwsIGtfYm90dG9tKSB7CiAgYWdlIDwtIGdheV9zdW1bW2pdXSRhZ2UKICB5IDwtIGdheV9zdW1bW2pdXSR5CiAgbiA8LSBnYXlfc3VtW1tqXV0kbgogIHBsb3QoYWdlLCB5L24sIHlsaW09YygwLC42NSksIHlheHM9ImkiLCB4bGFiPSBpZiAoaz09a19ib3R0b20pICJBZ2UiIGVsc2UgIiIsIHlsYWI9IiIsIHhheHQ9IGlmIChrPGtfYm90dG9tKSAibiIsIHlheHQ9Im4iLCBidHk9ImwiLCB0eXBlPSJuIiwgY2V4LmxhYj0xLjIsIGNleC5heGlzPTEuMikKICBheGlzKDEsIHNlcSgyMCw4MCwyMCksIHJlcCgiIiw0KSwgY2V4LmF4aXM9MS4yKQogIGF4aXMoMiwgc2VxKDAsMSwuMSksIHJlcCgiIiwxMSkpCiAgYXhpcygyLCBzZXEoMCwxLC4yKSwgYygiMCIsIjIwJSIsIjQwJSIsIjYwJSIsIjgwJSIsIjEwMCUiKSwgY2V4LmF4aXM9MS4yKQogIHN5bWJvbHMoYWdlLCB5L24sIGNpcmNsZXM9c3FydChuKSwgaW5jaGVzPS4xLCBhZGQ9VFJVRSwgZmc9ImdyYXk4MCIsIGJnPSJncmF5OTAiKQogIG5fc2ltcyA8LSBucm93KGZpdCkKICBmb3IgKGkgaW4gc2FtcGxlKG5fc2ltcywgMjApKXsKICAgIGxpbmVzKGFnZSwgZml0W2ksXSwgbHdkPS41LCBjb2w9ImdyYXk1MCIpCiAgfQogIGxpbmVzKGFnZSwgY29sTWVhbnMoZml0KSwgbHdkPTIpCn0KCmdheV9wbG90XzIgPC0gZnVuY3Rpb24oZml0PU5VTEwsIHF1ZXN0aW9uPU5VTEwsIHRpdGxlPU5VTEwsIG09TlVMTCkgewogIG9rIDwtIGdheV9zdW1fMltbal1dJG1hbGUgPT0gbQogIGFnZSA8LSBnYXlfc3VtXzJbW2pdXSRhZ2Vbb2tdCiAgeSA8LSBnYXlfc3VtXzJbW2pdXSR5W29rXQogIG4gPC0gZ2F5X3N1bV8yW1tqXV0kbltva10KICBwbG90KGFnZSwgeS9uLCB5bGltPWMoMCwuNjUpLCB5YXhzPSJpIiwgeGxhYj0gaWYgKGs9PTQpICJBZ2UiIGVsc2UgIiIsIHlsYWI9IiIsIHhheHQ9IGlmIChrPDQpICJuIiwgeWF4dD0ibiIsIGJ0eT0ibCIsIHR5cGU9Im4iLCBjZXgubGFiPTEuNCwgY2V4LmF4aXM9MS40LCBtYWluPWlmIChtPT0wKSAiV29tZW4iIGVsc2UgIk1lbiIpCiAgYXhpcygxLCBzZXEoMjAsODAsMjApLCByZXAoIiIsNCksIGNleC5heGlzPTEuNCkKICBheGlzKDIsIHNlcSgwLDEsLjEpLCByZXAoIiIsMTEpKQogIGF4aXMoMiwgc2VxKDAsMSwuMiksIGMoIjAiLCIyMCUiLCI0MCUiLCI2MCUiLCI4MCUiLCIxMDAlIiksIGNleC5heGlzPTEuNCkKICBzeW1ib2xzKGFnZSwgeS9uLCBjaXJjbGVzPXNxcnQobiksIGluY2hlcz0uMSwgYWRkPVRSVUUsIGZnPSJncmF5ODAiLCBiZz0iZ3JheTkwIikKICBuX3NpbXMgPC0gbnJvdyhmaXQpCiAgZm9yIChpIGluIHNhbXBsZShuX3NpbXMsIDIwKSl7CiAgICBsaW5lcyhhZ2UsIGZpdFtpLG9rXSwgbHdkPS41LCBjb2w9ImdyYXk1MCIpCiAgfQogIGxpbmVzKGFnZSwgY29sTWVhbnMoZml0Wyxva10pLCBsd2Q9MikKfQpgYGAKCiMjIyMgVHdvIGRpZmZlcmVudCBxdWVzdGlvbnMKCmBgYHtyIH0KcXVlc3Rpb24gPC0gYygiU3VwcG9ydCBmb3Igc2FtZS1zZXggbWFycmlhZ2UiLCAiRG8geW91IGtub3cgYW55IGdheSBwZW9wbGU/IikKdmFyaWFibGUgPC0gYygiZ2F5RmF2b3JTdGF0ZU1hcnJpYWdlIiwgImdheUtub3dTb21lb25lIikKaW5kZXggPC0gMApgYGAKCiMjIyMgTG9vcCBvdmVyIHR3byBkaWZmZXJlbnQgcXVlc3Rpb25zClByZXBhcmUgdmFyaWFibGVzIHRvIHN0b3JlIHJlc3VsdHMKCmBgYHtyIH0KZ2F5IDwtIGFzLmxpc3QocmVwKE5BLCAyKSkKZ2F5X3N1bSA8LSBhcy5saXN0KHJlcChOQSwgMikpCmdheV9zdW1fMiA8LSBhcy5saXN0KHJlcChOQSwgMikpCmdheV9sb2VzcyA8LSBhcy5saXN0KHJlcChOQSwgMikpCmdheV9zcGxpbmUgPC0gYXMubGlzdChyZXAoTkEsIDIpKQpnYXlfR1AgPC0gYXMubGlzdChyZXAoTkEsIDIpKQpnYXlfYmFydCA8LSBhcy5saXN0KHJlcChOQSwgMikpCmdheV9zcGxpbmVfMiA8LSBhcy5saXN0KHJlcChOQSwgMikpCmZvciAoaiBpbiAxOjIpewogIAogICMgUHJlcGFyZSB0aGUgZGF0YQogIGdheVtbal1dIDwtIHJlYWQuY3N2KHJvb3QoIkdheS9kYXRhIiwibmFlczA0LmNzdiIpKQogIGdheVtbal1dIDwtIGdheVtbal1dWyFpcy5uYShnYXlbW2pdXVssImFnZSJdKSAmICFpcy5uYShnYXlbW2pdXVssdmFyaWFibGVbal1dKSxdCiAgZ2F5W1tqXV0kYWdlW2dheVtbal1dJGFnZT45MF0gPC0gOTEKICB5IDwtIGFzLmNoYXJhY3RlcihnYXlbW2pdXVssdmFyaWFibGVbal1dKQogIGdheVtbal1dJHkgPC0gaWZlbHNlKHk9PSJZZXMiLCAxLCBpZmVsc2UoeT09Ik5vIiwgMCwgTkEpKQogIGdlbmRlciA8LSBhcy5jaGFyYWN0ZXIoZ2F5W1tqXV1bLCJnZW5kZXIiXSkKICBnYXlbW2pdXSRtYWxlIDwtIGlmZWxzZShnZW5kZXI9PSJGZW1hbGUiLCAwLCBpZmVsc2UoZ2VuZGVyPT0iTWFsZSIsIDEsIE5BKSkKICAKICB1bmlxX2FnZSA8LSBzb3J0KHVuaXF1ZShnYXlbW2pdXSRhZ2UpKQogIG5fYWdlIDwtIGxlbmd0aCh1bmlxX2FnZSkKICB0YWIgPC0gdGFibGUoZ2F5W1tqXV0kYWdlLCBnYXlbW2pdXSR5KQogIHlfc3VtIDwtIGFzLnZlY3Rvcih0YWJbLDJdKQogIG5fc3VtIDwtIGFzLnZlY3Rvcih0YWJbLDFdICsgdGFiWywyXSkKICBnYXlfc3VtW1tqXV0gPC0gZGF0YS5mcmFtZShuPW5fc3VtLCB5PXlfc3VtLCBhZ2U9dW5pcV9hZ2UpCiAgZ2F5X3N1bV8yW1tqXV0gPC0gZGF0YS5mcmFtZShhcnJheShOQSwgYyhuX2FnZSoyLCA0KSwgZGltbmFtZXM9bGlzdChOVUxMLCBjKCJuIiwgInkiLCAiYWdlIiwgIm1hbGUiKSkpKQogIGZvciAoaTEgaW4gMTpuX2FnZSl7CiAgICBmb3IgKGkyIGluIDA6MSl7CiAgICAgIG9rIDwtIGdheVtbal1dJGFnZT09dW5pcV9hZ2VbaTFdICYgZ2F5W1tqXV0kbWFsZT09aTIKICAgICAgbl9zdW1fMiA8LSBzdW0ob2spCiAgICAgIHlfc3VtXzIgPC0gc3VtKGdheVtbal1dJHlbb2tdKQogICAgICBhZ2Vfc3VtXzIgPC0gdW5pcV9hZ2VbaTFdCiAgICAgIG1hbGVfc3VtXzIgPC0gaTIKICAgICAgZ2F5X3N1bV8yW1tqXV1bbl9hZ2UqaTIgKyBpMSxdIDwtIGMobl9zdW1fMiwgeV9zdW1fMiwgYWdlX3N1bV8yLCBtYWxlX3N1bV8yKQogICAgfQogIH0KICAKICAjIE1ha2UgdGhlIHBsb3RzCiAgZ2F5X3Bsb3QocXVlc3Rpb249cXVlc3Rpb25bal0sIHRpdGxlPSJSYXcgZGF0YSBmcm9tIGEgbmF0aW9uYWwgc3VydmV5Iiwgc2F2ZWZpZ3MgPSBzYXZlZmlncykKCiAgIyBMT0VTUwogIGdheV9sb2Vzc1tbal1dIDwtIGxvZXNzKHkgfiBhZ2UsIGRhdGE9Z2F5W1tqXV0pCiAgZ2F5X2xvZXNzX2ZpdCA8LSBwcmVkaWN0KGdheV9sb2Vzc1tbal1dLCBkYXRhLmZyYW1lKGFnZT1nYXlfc3VtW1tqXV0kYWdlKSkKICBnYXlfbG9lc3NfZml0IDwtIG1hdHJpeChnYXlfbG9lc3NfZml0LCBucm93PTEwMCwgbmNvbD1sZW5ndGgoZ2F5X2xvZXNzX2ZpdCksIGJ5cm93PVRSVUUpCiAgZ2F5X3Bsb3QoZ2F5X2xvZXNzX2ZpdCwgcXVlc3Rpb249cXVlc3Rpb25bal0sIHRpdGxlPSJMb2VzcyBmaXQiLCBzYXZlZmlncyA9IHNhdmVmaWdzKQoKICAjIFNwbGluZXMKICBnYXlfc3BsaW5lW1tqXV0gPC0gc3Rhbl9nYW1tNChJKHkvbikgfiBzKGFnZSksIGRhdGE9Z2F5X3N1bVtbal1dLCBhZGFwdF9kZWx0YT0wLjk5KQogIGdheV9zcGxpbmVfZml0IDwtIHBvc3Rlcmlvcl9saW5wcmVkKGdheV9zcGxpbmVbW2pdXSwgZGF0YS5mcmFtZShhZ2U9Z2F5X3N1bVtbal1dJGFnZSkpCiAgZ2F5X3Bsb3QoZ2F5X3NwbGluZV9maXQsIHF1ZXN0aW9uPXF1ZXN0aW9uW2pdLCB0aXRsZT0iU3BsaW5lIGZpdCBhbmQgdW5jZXJ0YWludHkiLCBzYXZlZmlncyA9IHNhdmVmaWdzKQoKICAjIEdQIHJlcHJlc2VudGVkIHdpdGggc3BsaW5lcwogIGdheV9HUFtbal1dIDwtIHN0YW5fZ2FtbTQoSSh5L24pIH4gYWdlICsgcyhhZ2UsIGJzPSJncCIpLCBkYXRhPWdheV9zdW1bW2pdXSwgcHJpb3Jfc21vb3RoPXN0dWRlbnRfdChkZj00LCBzY2FsZT0xMDApLCBhZGFwdF9kZWx0YT0wLjk5KQogIGdheV9HUF9maXQgPC0gcG9zdGVyaW9yX2xpbnByZWQoZ2F5X0dQW1tqXV0sIGRhdGEuZnJhbWUoYWdlPWdheV9zdW1bW2pdXSRhZ2UpKQogIGdheV9wbG90KGdheV9HUF9maXQsIHF1ZXN0aW9uPXF1ZXN0aW9uW2pdLCB0aXRsZT0iR2F1c3NpYW4gcHJvY2VzcyBmaXQgYW5kIHVuY2VydGFpbnR5Iiwgc2F2ZWZpZ3MgPSBzYXZlZmlncykKICAjIEJBUlQKICBvdXRwdXQgPC0gY2FwdHVyZS5vdXRwdXQoCiAgICBnYXlfYmFydFtbal1dIDwtIGJhcnQoZ2F5W1tqXV0kYWdlLCBnYXlbW2pdXSR5LCBtYXRyaXgodW5pcV9hZ2UpLCBudHJlZSA9IDIwKSkKICBnYXlfYmFydF9maXQgPC0gcG5vcm0oZ2F5X2JhcnRbW2pdXSR5aGF0LnRlc3QpCiAgZ2F5X3Bsb3QoZ2F5X2JhcnRfZml0LCBxdWVzdGlvbj1xdWVzdGlvbltqXSwgdGl0bGU9IkJhcnQgZml0IGFuZCB1bmNlcnRhaW50eSIsIHNhdmVmaWdzID0gc2F2ZWZpZ3MpCgogICMgQW5vdGhlciBzcGxpbmUKICBnYXlfc3BsaW5lXzJbW2pdXSA8LSBzdGFuX2dhbW00KEkoeS9uKSB+IHMoYWdlLCBtYWxlKSwgZGF0YT1nYXlfc3VtXzJbW2pdXSkKfQpgYGAKCiMjIyMgTmV3IGdyYXBocwoKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBwZGYocm9vdCgiR2F5L2ZpZ3MiLCJnYXkxMC5wZGYiKSwgaGVpZ2h0PTQsIHdpZHRoPTEwKQpgYGAKYGBge3IgfQpwYXIobWFyPWMoMywyLDEsMSksIG1ncD1jKDEuNywgLjUsIDApLCB0Y2s9LS4wMSkKcGFyKG1mcm93PWMoMSwyKSkKZm9yIChqIGluIDE6Mil7CiAgcGxvdChnYXlfc3VtW1tqXV0kYWdlLCBnYXlfc3VtW1tqXV0keS9nYXlfc3VtW1tqXV0kbiwgeWxpbT1jKDAsLjY5KSwgeWF4cz0iaSIsIHhsYWI9IkFnZSIsIHlsYWI9IiIsIHlheHQ9Im4iLCBidHk9ImwiLCB0eXBlPSJuIiwgbWFpbj1xdWVzdGlvbltbal1dKQogIGF4aXMoMiwgc2VxKDAsMSwuMSksIHJlcCgiIiwxMSkpCiAgYXhpcygyLCBzZXEoMCwxLC4yKSwgYygiMCIsIjIwJSIsIjQwJSIsIjYwJSIsIjgwJSIsIjEwMCUiKSkKICBzeW1ib2xzKGdheV9zdW1bW2pdXSRhZ2UsIGdheV9zdW1bW2pdXSR5L2dheV9zdW1bW2pdXSRuLCBjaXJjbGVzPXNxcnQoZ2F5X3N1bVtbal1dJG4pLCBpbmNoZXM9LjEsIGFkZD1UUlVFLCBmZz0iYmxhY2siLCBiZz0iZ3JheTcwIikKfQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKCmBgYApgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQppZiAoc2F2ZWZpZ3MpIHBkZihyb290KCJHYXkvZmlncyIsImdheTExLnBkZiIpLCBoZWlnaHQ9MTAsIHdpZHRoPTcpCmBgYApgYGB7ciB9CnBhcihtYXI9YygzLDIsMSwxKSwgbWdwPWMoMS43LCAuNSwgMCksIHRjaz0tLjAxKQpwYXIobWZjb2w9Yyg0LDIpLCBvbWE9YygwLDAsMi41LDApKQpmb3IgKGogaW4gMToyKXsKICBrPTEKICBnYXlfbG9lc3NfZml0IDwtIHByZWRpY3QoZ2F5X2xvZXNzW1tqXV0sIGRhdGEuZnJhbWUoYWdlPWdheV9zdW1bW2pdXSRhZ2UpKQogIGdheV9sb2Vzc19maXQgPC0gbWF0cml4KGdheV9sb2Vzc19maXQsIG5yb3c9MTAwLCBuY29sPWxlbmd0aChnYXlfbG9lc3NfZml0KSwgYnlyb3c9VFJVRSkKICBnYXlfcGxvdF8xKGdheV9sb2Vzc19maXQsIHF1ZXN0aW9uPXF1ZXN0aW9uW2pdLCB0aXRsZT0iTG9lc3MgZml0Iiwga19ib3R0b209NCkKICBrPTIKICBnYXlfc3BsaW5lX2ZpdCA8LSBwb3N0ZXJpb3JfbGlucHJlZChnYXlfc3BsaW5lW1tqXV0sIGRhdGEuZnJhbWUoYWdlPWdheV9zdW1bW2pdXSRhZ2UpKQogIGdheV9wbG90XzEoZ2F5X3NwbGluZV9maXQsIHF1ZXN0aW9uPXF1ZXN0aW9uW2pdLCB0aXRsZT0iU3BsaW5lIGZpdCBhbmQgdW5jZXJ0YWludHkiLCBrX2JvdHRvbT00KQogIGs9MwogIGdheV9HUF9maXQgPC0gcG9zdGVyaW9yX2xpbnByZWQoZ2F5X0dQW1tqXV0sIGRhdGEuZnJhbWUoYWdlPWdheV9zdW1bW2pdXSRhZ2UpKQogIGdheV9wbG90XzEoZ2F5X0dQX2ZpdCwgcXVlc3Rpb249IlN1cHBvcnQgZm9yIHNhbWUtc2V4IG1hcnJpYWdlIiwgdGl0bGU9IkdhdXNzaWFuIHByb2Nlc3MgZml0IGFuZCB1bmNlcnRhaW50eSIsIGtfYm90dG9tPTQpCiAgaz00CiAgZ2F5X2JhcnRfZml0IDwtIHBub3JtKGdheV9iYXJ0W1tqXV0keWhhdC50ZXN0KQogIGdheV9wbG90XzEoZ2F5X2JhcnRfZml0LCBxdWVzdGlvbj1xdWVzdGlvbltqXSwgdGl0bGU9IkJhcnQgZml0IGFuZCB1bmNlcnRhaW50eSIsIGtfYm90dG9tPTQpCn0KbXRleHQocGFzdGUocXVlc3Rpb25bWzFdXSwgcXVlc3Rpb25bWzJdXSwgc2VwPSIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiksIHNpZGU9MywgbGluZT0xLCBvdXRlcj1UUlVFKQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKCgpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBwZGYocm9vdCgiR2F5L2ZpZ3MiLCJnYXkxMi5wZGYiKSwgaGVpZ2h0PTgsIHdpZHRoPTEwKQpgYGAKYGBge3IgfQpwYXIobWFyPWMoMywyLDEsMSksIG1ncD1jKDEuNywgLjUsIDApLCB0Y2s9LS4wMSkKcGFyKG1mY29sPWMoMiwyKSkKZm9yIChqIGluIDE6Mil7CiAgZ2F5X3NwbGluZV8yX2ZpdCA8LSBwb3N0ZXJpb3JfbGlucHJlZChnYXlfc3BsaW5lXzJbW2pdXSwgZGF0YS5mcmFtZShhZ2U9Z2F5X3N1bV8yW1tqXV0kYWdlLCBtYWxlPWdheV9zdW1fMltbal1dJG1hbGUpKQogIGZvciAobSBpbiAwOjEpeyAgICAgICAgICAgICAgICAgICAgCiAgICBnYXlfcGxvdF8yKGdheV9zcGxpbmVfMl9maXQsIHF1ZXN0aW9uPXF1ZXN0aW9uW2pdLCB0aXRsZT0iMi1kaW1lbnNpb25hbCBzcGxpbmUgZml0IiwgbT1tKQogIH0KfQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKCgpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBwZGYocm9vdCgiR2F5L2ZpZ3MiLCJnYXkxMy5wZGYiKSwgaGVpZ2h0PTUuNSwgd2lkdGg9NykKYGBgCmBgYHtyIH0KcGFyKG1hcj1jKDMsMiwxLDEpLCBtZ3A9YygxLjcsIC41LCAwKSwgdGNrPS0uMDEpCnBhcihtZmNvbD1jKDIsMiksIG9tYT1jKDAsMCwyLjUsMCkpCmZvciAoaiBpbiAxOjIpewogIGs9MQogIGdheV9sb2Vzc19maXQgPC0gcHJlZGljdChnYXlfbG9lc3NbW2pdXSwgZGF0YS5mcmFtZShhZ2U9Z2F5X3N1bVtbal1dJGFnZSkpCiAgZ2F5X2xvZXNzX2ZpdCA8LSBtYXRyaXgoZ2F5X2xvZXNzX2ZpdCwgbnJvdz0xMDAsIG5jb2w9bGVuZ3RoKGdheV9sb2Vzc19maXQpLCBieXJvdz1UUlVFKQogIGdheV9wbG90XzEoZ2F5X2xvZXNzX2ZpdCwgcXVlc3Rpb249cXVlc3Rpb25bal0sIHRpdGxlPSJMb2VzcyBmaXQiLCBrX2JvdHRvbT0yKQogIGs9MgogIGdheV9zcGxpbmVfZml0IDwtIHBvc3Rlcmlvcl9saW5wcmVkKGdheV9zcGxpbmVbW2pdXSwgZGF0YS5mcmFtZShhZ2U9Z2F5X3N1bVtbal1dJGFnZSkpCiAgZ2F5X3Bsb3RfMShnYXlfc3BsaW5lX2ZpdCwgcXVlc3Rpb249cXVlc3Rpb25bal0sIHRpdGxlPSJTcGxpbmUgZml0IGFuZCB1bmNlcnRhaW50eSIsIGtfYm90dG9tPTIpCiAgaz0zCn0KbXRleHQocGFzdGUocXVlc3Rpb25bWzFdXSwgcXVlc3Rpb25bWzJdXSwgc2VwPSIgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiksIHNpZGU9MywgbGluZT0xLCBvdXRlcj1UUlVFKQpgYGAKYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0KaWYgKHNhdmVmaWdzKSBkZXYub2ZmKCkKYGBgCgo=