Gibbs sampling

ggplot2 is used for plotting, tidyr for manipulating data frames

library(ggplot2)
theme_set(theme_minimal())
library(tidyr)
library(gganimate)
library(MASS)
library(posterior)
library(rprojroot)
root<-has_file(".BDA_R_demos_root")$make_fix_file()

Parameters of a normal distribution used as a toy target distribution

y1 <- 0
y2 <- 0
r <- 0.8
Sigma <- diag(2)
Sigma[1, 2] <- r
Sigma[2, 1] <- r

Sample from the toy distribution to visualize 90% HPD interval with ggplot’s stat_ellipse()

dft <- data.frame(mvrnorm(100000, c(0, 0), Sigma))

see BDA3 p. 85 for how to compute HPD for multivariate normal in 2d-case contour for 90% HPD is an ellipse, whose semimajor axes can be computed from the eigenvalues of the covariance matrix scaled by a value selected to get ellipse match the density at the edge of 90% HPD. Angle of the ellipse could be computed from the eigenvectors, but since the marginals are same we know that angle is pi/4 Starting value of the chain

t1 <- -2.5
t2 <- 2.5

Number of iterations.

M <- 2*2500

N.B. In this implementation one iteration updates only one parameter and one complete iteration updating both parameters takes two basic iterations. This implementation was used to make plotting of Gibbs sampler’s zig-zagging. In plots You can implement this also by saving only the final state of complete iteration updating all parameters. Insert your own Gibbs sampling here

# Allocate memory for the sample
tt <- matrix(rep(0, 2*M), ncol = 2)
tt[1,] <- c(t1, t2)    # Save starting point
# For demonstration load pre-computed values
# Replace this with your algorithm!
# tt is a M x 2 array, with M draws of both theta_1 and theta_2
load(root("demos_ch11","demo11_1.RData"))

The rest is for illustration Take the first 50 draws to illustrate how the sampler works

df100 <- data.frame(id=rep(1,100),
                    iter=1:100, 
                    th1 = tt[1:100, 1],
                    th2 = tt[1:100, 2],
                    th1l = c(tt[1, 1], tt[1:(100-1), 1]),
                    th2l = c(tt[1, 2], tt[1:(100-1), 2]))

Take the first 1000 observations

S <- 1000
dfs <- data.frame(th1 = tt[1:S, 1], th2 = tt[1:S, 2])

Remove warm-up period of 50 first draws later

warm <- 50

# labels and frame indices for the plot
labs1 <- c('Draws', 'Steps of the sampler', '90% HPD')
ind1 <- (1:50)*2-1
df100s <- df100
df100s[ind1+1,3:4]=df100s[ind1,3:4]
p1 <- ggplot() +
  geom_point(data = df100s,
             aes(th1, th2, group=id, color ='1')) +
  geom_segment(data = df100, aes(x = th1, xend = th1l, color = '2',
                                 y = th2, yend = th2l)) +
  stat_ellipse(data = dft, aes(x = X1, y = X2, color = '3'), level = 0.9) +
  coord_cartesian(xlim = c(-4, 4), ylim = c(-4, 4)) +
  labs(x = 'theta1', y = 'theta2') +
  scale_color_manual(values = c('red', 'forestgreen','blue'), labels = labs1) +
  guides(color = guide_legend(override.aes = list(
    shape = c(16, NA, NA), linetype = c(0, 1, 1)))) +
  theme(legend.position = 'bottom', legend.title = element_blank())

The following generates a gif animation of the steps of the sampler (might take 10 seconds).

anim <- animate(p1 +
          transition_reveal(along=iter) + 
          shadow_trail(0.01))

Show the animation

anim

Show only the end result as a static figure

p1

Highlight warm-up period of the 30 first draws with purple

p1 + geom_point(data = df100[ind1[1:30],],
                aes(th1, th2), color = 'green')

show 950 draws after a warm-up period of 50 draws is removed

labs2 <- c('Draws', '90% HPD')
ggplot() +
  geom_point(data = dfs[-(1:warm),],
             aes(th1, th2, color = '1'), alpha = 0.5) +
  stat_ellipse(data = dft, aes(x = X1, y = X2, color = '2'), level = 0.9) +
  coord_cartesian(xlim = c(-4, 4), ylim = c(-4, 4)) +
  labs(x = 'theta1', y = 'theta2') +
  scale_color_manual(values = c('steelblue', 'blue'), labels = labs2) +
  guides(color = guide_legend(override.aes = list(
    shape = c(16, NA), linetype = c(0, 1), alpha = c(1, 1)))) +
  theme(legend.position = 'bottom', legend.title = element_blank())

Convergence diagnostics

summarise_draws(dfs, Rhat=rhat_basic, ESS=ess_basic)
## # A tibble: 2 × 3
##   variable  Rhat   ESS
##   <chr>    <num> <num>
## 1 th1      0.999  136.
## 2 th2      1.00   139.
neff <- apply(dfs, 2, ess_basic)
# both theta have own neff, but for plotting these are so close to each
# other, so that single relative efficiency value is used
reff <- mean(neff/S)

Visual convergence diagnostics

Collapse the data frame with row numbers augmented into key-value pairs for visualizing the chains

dfb <- dfs[-(1:warm),]
Sb <- S-warm
dfch <- within(dfb, iter <- 1:Sb) %>% 
  pivot_longer(cols = !iter, names_to = "grp", values_to = "value")

Another data frame for visualizing the estimate of the autocorrelation function

nlags <- 20
dfa <- sapply(dfb, function(x) acf(x, lag.max = nlags, plot = F)$acf) %>%
  data.frame(iter = 0:(nlags)) %>% 
  pivot_longer(cols = !iter, names_to = "grp", values_to = "value")

A third data frame to visualize the cumulative averages and the 95% intervals

dfca <- (cumsum(dfb) / (1:Sb)) %>%
  within({iter <- 1:Sb
          uppi <-  1.96/sqrt(1:Sb)
          upp <- 1.96/(sqrt(1:Sb*reff))}) %>%
  pivot_longer(cols = !iter, names_to = "grp", values_to = "value")

Visualize the chains

ggplot(data = dfch) +
  geom_line(aes(iter, value, color = grp)) +
  labs(title = 'Trends') +
  scale_color_discrete(labels = c('theta1','theta2')) +
  theme(legend.position = 'bottom', legend.title = element_blank())

Visualize the estimate of the autocorrelation function

ggplot(data = dfa) +
  geom_line(aes(iter, value, color = grp)) +
  geom_hline(aes(yintercept = 0)) +
  labs(title = 'Autocorrelation function') +
  scale_color_discrete(labels = c('theta1', 'theta2')) +
  theme(legend.position = 'bottom', legend.title = element_blank())

Visualize the estimate of the Monte Carlo error estimates

# labels
labs3 <- c('theta1', 'theta2',
           '95% interval for MCMC error',
           '95% interval for independent MC')
ggplot() +
  geom_line(data = dfca, aes(iter, value, color = grp, linetype = grp)) +
  geom_line(aes(1:Sb, -1.96/sqrt(1:Sb*reff)), linetype = 2) +
  geom_line(aes(1:Sb, -1.96/sqrt(1:Sb)), linetype = 3) +
  geom_hline(aes(yintercept = 0)) +
  coord_cartesian(ylim = c(-1.5, 1.5)) +
  labs(title = 'Cumulative averages') +
  scale_color_manual(values = c('red','blue',rep('black', 2)), labels = labs3) +
  scale_linetype_manual(values = c(1, 1, 2, 3), labels = labs3) +
  theme(legend.position = 'bottom', legend.title = element_blank())

Same again with r=0.99 Parameters of a normal distribution used as a toy target distribution

y1 <- 0
y2 <- 0
r <- 0.99
Sigma <- diag(2)
Sigma[1, 2] <- r
Sigma[2, 1] <- r

Sample from the toy distribution to visualize 90% HPD interval with ggplot’s stat_ellipse()

dft <- data.frame(mvrnorm(100000, c(0, 0), Sigma))

see BDA3 p. 85 for how to compute HPD for multivariate normal in 2d-case contour for 90% HPD is an ellipse, whose semimajor axes can be computed from the eigenvalues of the covariance matrix scaled by a value selected to get ellipse match the density at the edge of 90% HPD. Angle of the ellipse could be computed from the eigenvectors, but since the marginals are same we know that angle is pi/4 Starting value of the chain

t1 <- -2.5
t2 <- 2.5

Number of iterations.

M <- 2*2500

N.B. In this implementation one iteration updates only one parameter and one complete iteration updating both parameters takes two basic iterations. This implementation was used to make plotting of Gibbs sampler’s zig-zagging. In plots You can implement this also by saving only the final state of complete iteration updating all parameters. Insert your own Gibbs sampling here

#Allocate memory for the sample
tt <- matrix(rep(0, 2*M), ncol = 2)
tt[1,] <- c(t1, t2)    # Save starting point
# For demonstration load pre-computed values
# Replace this with your algorithm!
# tt is a M x 2 array, with M draws of both theta_1 and theta_2
load(root("demos_ch11","demo11_1b.RData"))

The rest is for illustration Take the first 50 draws to illustrate how the sampler works

df100 <- data.frame(id=rep(1,100),
                    iter=1:100, 
                    th1 = tt[1:100, 1],
                    th2 = tt[1:100, 2],
                    th1l = c(tt[1, 1], tt[1:(100-1), 1]),
                    th2l = c(tt[1, 2], tt[1:(100-1), 2]))

Take the first 1000 observations

S <- 1000
dfs <- data.frame(th1 = tt[1:S, 1], th2 = tt[1:S, 2])

Remove warm-up period of 50 first draws later

warm <- 50

# labels and frame indices for the plot
labs1 <- c('Draws', 'Steps of the sampler', '90% HPD')
ind1 <- (1:50)*2-1
df100s <- df100
df100s[ind1+1,3:4]=df100s[ind1,3:4]
p1 <- ggplot() +
  geom_point(data = df100s,
             aes(th1, th2, group=id, color ='1')) +
  geom_segment(data = df100, aes(x = th1, xend = th1l, color = '2',
                                 y = th2, yend = th2l)) +
  stat_ellipse(data = dft, aes(x = X1, y = X2, color = '3'), level = 0.9) +
  coord_cartesian(xlim = c(-4, 4), ylim = c(-4, 4)) +
  labs(x = 'theta1', y = 'theta2') +
  scale_color_manual(values = c('red', 'forestgreen','blue'), labels = labs1) +
  guides(color = guide_legend(override.aes = list(
    shape = c(16, NA, NA), linetype = c(0, 1, 1)))) +
  theme(legend.position = 'bottom', legend.title = element_blank())

The following generates a gif animation of the steps of the sampler (might take 10 seconds).

anim <- animate(p1 +   
                  transition_reveal(along=iter) + 
                  shadow_trail(0.01))

Show the animation

anim

Show only the end result as a static figure

p1

Highlight warm-up period of the 30 first draws with purple

p1 + geom_point(data = df100[ind1[1:30],],
                aes(th1, th2), color = 'green')

show 950 draws after a warm-up period of 50 draws is removed

labs2 <- c('Draws', '90% HPD')
ggplot() +
  geom_point(data = dfs[-(1:warm),],
             aes(th1, th2, color = '1'), alpha = 0.5) +
  stat_ellipse(data = dft, aes(x = X1, y = X2, color = '2'), level = 0.9) +
  coord_cartesian(xlim = c(-4, 4), ylim = c(-4, 4)) +
  labs(x = 'theta1', y = 'theta2') +
  scale_color_manual(values = c('steelblue', 'blue'), labels = labs2) +
  guides(color = guide_legend(override.aes = list(
    shape = c(16, NA), linetype = c(0, 1), alpha = c(1, 1)))) +
  theme(legend.position = 'bottom', legend.title = element_blank())

Convergence diagnostics

summarise_draws(dfs, Rhat=rhat_basic, ESS=ess_basic)
## # A tibble: 2 × 3
##   variable  Rhat   ESS
##   <chr>    <num> <num>
## 1 th1       1.09  9.72
## 2 th2       1.09  9.68
neff <- apply(dfs, 2, ess_basic)
# both theta have own neff, but for plotting these are so close to each
# other, so that single relative efficiency value is used
reff <- mean(neff/S)

Visual convergence diagnostics

Collapse the data frame with row numbers augmented into key-value pairs for visualizing the chains

dfb <- dfs[-(1:warm),]
Sb <- S-warm
dfch <- within(dfb, iter <- 1:Sb) %>% 
  pivot_longer(cols = !iter, names_to = "grp", values_to = "value")

Another data frame for visualizing the estimate of the autocorrelation function

nlags <- 75
dfa <- sapply(dfb, function(x) acf(x, lag.max = nlags, plot = F)$acf) %>%
  data.frame(iter = 0:(nlags)) %>% 
  pivot_longer(cols = !iter, names_to = "grp", values_to = "value")

A third data frame to visualize the cumulative averages and the 95% intervals

dfca <- (cumsum(dfb) / (1:Sb)) %>%
  within({iter <- 1:Sb
          uppi <-  1.96/sqrt(1:Sb)
          upp <- 1.96/(sqrt(1:Sb*reff))}) %>%
  pivot_longer(cols = !iter, names_to = "grp", values_to = "value")

Visualize the chains

ggplot(data = dfch) +
  geom_line(aes(iter, value, color = grp)) +
  labs(title = 'Trends') +
  scale_color_discrete(labels = c('theta1','theta2')) +
  theme(legend.position = 'bottom', legend.title = element_blank())

Visualize the estimate of the autocorrelation function

ggplot(data = dfa) +
  geom_line(aes(iter, value, color = grp)) +
  geom_hline(aes(yintercept = 0)) +
  labs(title = 'Autocorrelation function') +
  scale_color_discrete(labels = c('theta1', 'theta2')) +
  theme(legend.position = 'bottom', legend.title = element_blank())

Visualize the estimate of the Monte Carlo error estimates

# labels
labs3 <- c('theta1', 'theta2',
           '95% interval for MCMC error',
           '95% interval for independent MC')
ggplot() +
  geom_line(data = dfca, aes(iter, value, color = grp, linetype = grp)) +
  geom_line(aes(1:Sb, -1.96/sqrt(1:Sb*reff)), linetype = 2) +
  geom_line(aes(1:Sb, -1.96/sqrt(1:Sb)), linetype = 3) +
  geom_hline(aes(yintercept = 0)) +
  coord_cartesian(ylim = c(-1.5, 1.5)) +
  labs(title = 'Cumulative averages') +
  scale_color_manual(values = c('red','blue',rep('black', 2)), labels = labs3) +
  scale_linetype_manual(values = c(1, 1, 2, 3), labels = labs3) +
  theme(legend.position = 'bottom', legend.title = element_blank())

LS0tCnRpdGxlOiAiQmF5ZXNpYW4gZGF0YSBhbmFseXNpcyBkZW1vIDExLjEiCmF1dGhvcjogIkFraSBWZWh0YXJpLCBNYXJrdXMgUGFhc2luaWVtaSIKZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdGhlbWU6IHJlYWRhYmxlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQojIyBHaWJicyBzYW1wbGluZwoKZ2dwbG90MiBpcyB1c2VkIGZvciBwbG90dGluZywgdGlkeXIgZm9yIG1hbmlwdWxhdGluZyBkYXRhIGZyYW1lcwoKYGBge3Igc2V0dXAsIG1lc3NhZ2U9RkFMU0UsIGVycm9yPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KGdncGxvdDIpCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZ2dhbmltYXRlKQpsaWJyYXJ5KE1BU1MpCmxpYnJhcnkocG9zdGVyaW9yKQpsaWJyYXJ5KHJwcm9qcm9vdCkKcm9vdDwtaGFzX2ZpbGUoIi5CREFfUl9kZW1vc19yb290IikkbWFrZV9maXhfZmlsZSgpCmBgYAoKUGFyYW1ldGVycyBvZiBhIG5vcm1hbCBkaXN0cmlidXRpb24gdXNlZCBhcyBhIHRveSB0YXJnZXQgZGlzdHJpYnV0aW9uCgpgYGB7ciB9CnkxIDwtIDAKeTIgPC0gMApyIDwtIDAuOApTaWdtYSA8LSBkaWFnKDIpClNpZ21hWzEsIDJdIDwtIHIKU2lnbWFbMiwgMV0gPC0gcgpgYGAKClNhbXBsZSBmcm9tIHRoZSB0b3kgZGlzdHJpYnV0aW9uIHRvIHZpc3VhbGl6ZSA5MCUgSFBECmludGVydmFsIHdpdGggZ2dwbG90J3Mgc3RhdF9lbGxpcHNlKCkKCmBgYHtyIH0KZGZ0IDwtIGRhdGEuZnJhbWUobXZybm9ybSgxMDAwMDAsIGMoMCwgMCksIFNpZ21hKSkKYGBgCgpzZWUgQkRBMyBwLiA4NSBmb3IgaG93IHRvIGNvbXB1dGUgSFBEIGZvciBtdWx0aXZhcmlhdGUgbm9ybWFsCmluIDJkLWNhc2UgY29udG91ciBmb3IgOTAlIEhQRCBpcyBhbiBlbGxpcHNlLCB3aG9zZSBzZW1pbWFqb3IKYXhlcyBjYW4gYmUgY29tcHV0ZWQgZnJvbSB0aGUgZWlnZW52YWx1ZXMgb2YgdGhlIGNvdmFyaWFuY2UKbWF0cml4IHNjYWxlZCBieSBhIHZhbHVlIHNlbGVjdGVkIHRvIGdldCBlbGxpcHNlIG1hdGNoIHRoZQpkZW5zaXR5IGF0IHRoZSBlZGdlIG9mIDkwJSBIUEQuIEFuZ2xlIG9mIHRoZSBlbGxpcHNlIGNvdWxkIGJlCmNvbXB1dGVkIGZyb20gdGhlIGVpZ2VudmVjdG9ycywgYnV0IHNpbmNlIHRoZSBtYXJnaW5hbHMgYXJlIHNhbWUKd2Uga25vdyB0aGF0IGFuZ2xlIGlzIHBpLzQKU3RhcnRpbmcgdmFsdWUgb2YgdGhlIGNoYWluCgpgYGB7ciB9CnQxIDwtIC0yLjUKdDIgPC0gMi41CmBgYAoKTnVtYmVyIG9mIGl0ZXJhdGlvbnMuCgpgYGB7ciB9Ck0gPC0gMioyNTAwCmBgYAoKTi5CLiBJbiB0aGlzIGltcGxlbWVudGF0aW9uIG9uZSBpdGVyYXRpb24gdXBkYXRlcyBvbmx5IG9uZSBwYXJhbWV0ZXIgYW5kIG9uZQpjb21wbGV0ZSBpdGVyYXRpb24gdXBkYXRpbmcgYm90aCBwYXJhbWV0ZXJzIHRha2VzIHR3byBiYXNpYyBpdGVyYXRpb25zLiBUaGlzCmltcGxlbWVudGF0aW9uIHdhcyB1c2VkIHRvIG1ha2UgcGxvdHRpbmcgb2YgR2liYnMgc2FtcGxlcidzIHppZy16YWdnaW5nLiBJbgpwbG90cyBZb3UgY2FuIGltcGxlbWVudCB0aGlzIGFsc28gYnkgc2F2aW5nIG9ubHkgdGhlIGZpbmFsIHN0YXRlIG9mIGNvbXBsZXRlCml0ZXJhdGlvbiB1cGRhdGluZyBhbGwgcGFyYW1ldGVycy4KSW5zZXJ0IHlvdXIgb3duIEdpYmJzIHNhbXBsaW5nIGhlcmUKCmBgYHtyIH0KIyBBbGxvY2F0ZSBtZW1vcnkgZm9yIHRoZSBzYW1wbGUKdHQgPC0gbWF0cml4KHJlcCgwLCAyKk0pLCBuY29sID0gMikKdHRbMSxdIDwtIGModDEsIHQyKSAgICAjIFNhdmUgc3RhcnRpbmcgcG9pbnQKIyBGb3IgZGVtb25zdHJhdGlvbiBsb2FkIHByZS1jb21wdXRlZCB2YWx1ZXMKIyBSZXBsYWNlIHRoaXMgd2l0aCB5b3VyIGFsZ29yaXRobSEKIyB0dCBpcyBhIE0geCAyIGFycmF5LCB3aXRoIE0gZHJhd3Mgb2YgYm90aCB0aGV0YV8xIGFuZCB0aGV0YV8yCmxvYWQocm9vdCgiZGVtb3NfY2gxMSIsImRlbW8xMV8xLlJEYXRhIikpCmBgYAoKVGhlIHJlc3QgaXMgZm9yIGlsbHVzdHJhdGlvbgpUYWtlIHRoZSBmaXJzdCA1MCBkcmF3cwp0byBpbGx1c3RyYXRlIGhvdyB0aGUgc2FtcGxlciB3b3JrcwoKYGBge3IgfQpkZjEwMCA8LSBkYXRhLmZyYW1lKGlkPXJlcCgxLDEwMCksCiAgICAgICAgICAgICAgICAgICAgaXRlcj0xOjEwMCwgCiAgICAgICAgICAgICAgICAgICAgdGgxID0gdHRbMToxMDAsIDFdLAogICAgICAgICAgICAgICAgICAgIHRoMiA9IHR0WzE6MTAwLCAyXSwKICAgICAgICAgICAgICAgICAgICB0aDFsID0gYyh0dFsxLCAxXSwgdHRbMTooMTAwLTEpLCAxXSksCiAgICAgICAgICAgICAgICAgICAgdGgybCA9IGModHRbMSwgMl0sIHR0WzE6KDEwMC0xKSwgMl0pKQpgYGAKClRha2UgdGhlIGZpcnN0IDEwMDAgb2JzZXJ2YXRpb25zCgpgYGB7ciB9ClMgPC0gMTAwMApkZnMgPC0gZGF0YS5mcmFtZSh0aDEgPSB0dFsxOlMsIDFdLCB0aDIgPSB0dFsxOlMsIDJdKQpgYGAKClJlbW92ZSB3YXJtLXVwIHBlcmlvZCBvZiA1MCBmaXJzdCBkcmF3cyBsYXRlcgoKYGBge3IgfQp3YXJtIDwtIDUwCgojIGxhYmVscyBhbmQgZnJhbWUgaW5kaWNlcyBmb3IgdGhlIHBsb3QKbGFiczEgPC0gYygnRHJhd3MnLCAnU3RlcHMgb2YgdGhlIHNhbXBsZXInLCAnOTAlIEhQRCcpCmluZDEgPC0gKDE6NTApKjItMQpkZjEwMHMgPC0gZGYxMDAKZGYxMDBzW2luZDErMSwzOjRdPWRmMTAwc1tpbmQxLDM6NF0KcDEgPC0gZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGRmMTAwcywKICAgICAgICAgICAgIGFlcyh0aDEsIHRoMiwgZ3JvdXA9aWQsIGNvbG9yID0nMScpKSArCiAgZ2VvbV9zZWdtZW50KGRhdGEgPSBkZjEwMCwgYWVzKHggPSB0aDEsIHhlbmQgPSB0aDFsLCBjb2xvciA9ICcyJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IHRoMiwgeWVuZCA9IHRoMmwpKSArCiAgc3RhdF9lbGxpcHNlKGRhdGEgPSBkZnQsIGFlcyh4ID0gWDEsIHkgPSBYMiwgY29sb3IgPSAnMycpLCBsZXZlbCA9IDAuOSkgKwogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygtNCwgNCksIHlsaW0gPSBjKC00LCA0KSkgKwogIGxhYnMoeCA9ICd0aGV0YTEnLCB5ID0gJ3RoZXRhMicpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygncmVkJywgJ2ZvcmVzdGdyZWVuJywnYmx1ZScpLCBsYWJlbHMgPSBsYWJzMSkgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KAogICAgc2hhcGUgPSBjKDE2LCBOQSwgTkEpLCBsaW5ldHlwZSA9IGMoMCwgMSwgMSkpKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nLCBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKVGhlIGZvbGxvd2luZyBnZW5lcmF0ZXMgYSBnaWYgYW5pbWF0aW9uCm9mIHRoZSBzdGVwcyBvZiB0aGUgc2FtcGxlciAobWlnaHQgdGFrZSAxMCBzZWNvbmRzKS4KCmBgYHtyIEdpYmJzICgxKSwgcmVzdWx0cz0naGlkZScsIG1lc3NhZ2U9RkFMU0V9CmFuaW0gPC0gYW5pbWF0ZShwMSArCiAgICAgICAgICB0cmFuc2l0aW9uX3JldmVhbChhbG9uZz1pdGVyKSArIAogICAgICAgICAgc2hhZG93X3RyYWlsKDAuMDEpKQpgYGAKClNob3cgdGhlIGFuaW1hdGlvbgoKYGBge3IgfQphbmltCmBgYAoKU2hvdyBvbmx5IHRoZSBlbmQgcmVzdWx0IGFzIGEgc3RhdGljIGZpZ3VyZQoKYGBge3IgfQpwMQpgYGAKCkhpZ2hsaWdodCB3YXJtLXVwIHBlcmlvZCBvZiB0aGUgMzAgZmlyc3QgZHJhd3Mgd2l0aCBwdXJwbGUKCmBgYHtyIH0KcDEgKyBnZW9tX3BvaW50KGRhdGEgPSBkZjEwMFtpbmQxWzE6MzBdLF0sCiAgICAgICAgICAgICAgICBhZXModGgxLCB0aDIpLCBjb2xvciA9ICdncmVlbicpCmBgYAoKc2hvdyA5NTAgZHJhd3MgYWZ0ZXIgYSB3YXJtLXVwIHBlcmlvZCBvZgo1MCBkcmF3cyBpcyByZW1vdmVkCgpgYGB7ciB9CmxhYnMyIDwtIGMoJ0RyYXdzJywgJzkwJSBIUEQnKQpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChkYXRhID0gZGZzWy0oMTp3YXJtKSxdLAogICAgICAgICAgICAgYWVzKHRoMSwgdGgyLCBjb2xvciA9ICcxJyksIGFscGhhID0gMC41KSArCiAgc3RhdF9lbGxpcHNlKGRhdGEgPSBkZnQsIGFlcyh4ID0gWDEsIHkgPSBYMiwgY29sb3IgPSAnMicpLCBsZXZlbCA9IDAuOSkgKwogIGNvb3JkX2NhcnRlc2lhbih4bGltID0gYygtNCwgNCksIHlsaW0gPSBjKC00LCA0KSkgKwogIGxhYnMoeCA9ICd0aGV0YTEnLCB5ID0gJ3RoZXRhMicpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygnc3RlZWxibHVlJywgJ2JsdWUnKSwgbGFiZWxzID0gbGFiczIpICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdCgKICAgIHNoYXBlID0gYygxNiwgTkEpLCBsaW5ldHlwZSA9IGMoMCwgMSksIGFscGhhID0gYygxLCAxKSkpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScsIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgojIyMgQ29udmVyZ2VuY2UgZGlhZ25vc3RpY3MKCmBgYHtyIH0Kc3VtbWFyaXNlX2RyYXdzKGRmcywgUmhhdD1yaGF0X2Jhc2ljLCBFU1M9ZXNzX2Jhc2ljKQpuZWZmIDwtIGFwcGx5KGRmcywgMiwgZXNzX2Jhc2ljKQojIGJvdGggdGhldGEgaGF2ZSBvd24gbmVmZiwgYnV0IGZvciBwbG90dGluZyB0aGVzZSBhcmUgc28gY2xvc2UgdG8gZWFjaAojIG90aGVyLCBzbyB0aGF0IHNpbmdsZSByZWxhdGl2ZSBlZmZpY2llbmN5IHZhbHVlIGlzIHVzZWQKcmVmZiA8LSBtZWFuKG5lZmYvUykKYGBgCgojIyMgVmlzdWFsIGNvbnZlcmdlbmNlIGRpYWdub3N0aWNzCkNvbGxhcHNlIHRoZSBkYXRhIGZyYW1lIHdpdGggcm93IG51bWJlcnMgYXVnbWVudGVkCmludG8ga2V5LXZhbHVlIHBhaXJzIGZvciB2aXN1YWxpemluZyB0aGUgY2hhaW5zCgpgYGB7ciB9CmRmYiA8LSBkZnNbLSgxOndhcm0pLF0KU2IgPC0gUy13YXJtCmRmY2ggPC0gd2l0aGluKGRmYiwgaXRlciA8LSAxOlNiKSAlPiUgCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAhaXRlciwgbmFtZXNfdG8gPSAiZ3JwIiwgdmFsdWVzX3RvID0gInZhbHVlIikKYGBgCgpBbm90aGVyIGRhdGEgZnJhbWUgZm9yIHZpc3VhbGl6aW5nIHRoZSBlc3RpbWF0ZSBvZgp0aGUgYXV0b2NvcnJlbGF0aW9uIGZ1bmN0aW9uCgpgYGB7ciB9Cm5sYWdzIDwtIDIwCmRmYSA8LSBzYXBwbHkoZGZiLCBmdW5jdGlvbih4KSBhY2YoeCwgbGFnLm1heCA9IG5sYWdzLCBwbG90ID0gRikkYWNmKSAlPiUKICBkYXRhLmZyYW1lKGl0ZXIgPSAwOihubGFncykpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9ICFpdGVyLCBuYW1lc190byA9ICJncnAiLCB2YWx1ZXNfdG8gPSAidmFsdWUiKQpgYGAKCkEgdGhpcmQgZGF0YSBmcmFtZSB0byB2aXN1YWxpemUgdGhlIGN1bXVsYXRpdmUgYXZlcmFnZXMKYW5kIHRoZSA5NSUgaW50ZXJ2YWxzCgpgYGB7ciB9CmRmY2EgPC0gKGN1bXN1bShkZmIpIC8gKDE6U2IpKSAlPiUKICB3aXRoaW4oe2l0ZXIgPC0gMTpTYgogICAgICAgICAgdXBwaSA8LSAgMS45Ni9zcXJ0KDE6U2IpCiAgICAgICAgICB1cHAgPC0gMS45Ni8oc3FydCgxOlNiKnJlZmYpKX0pICU+JQogIHBpdm90X2xvbmdlcihjb2xzID0gIWl0ZXIsIG5hbWVzX3RvID0gImdycCIsIHZhbHVlc190byA9ICJ2YWx1ZSIpCmBgYAoKVmlzdWFsaXplIHRoZSBjaGFpbnMKCmBgYHtyIH0KZ2dwbG90KGRhdGEgPSBkZmNoKSArCiAgZ2VvbV9saW5lKGFlcyhpdGVyLCB2YWx1ZSwgY29sb3IgPSBncnApKSArCiAgbGFicyh0aXRsZSA9ICdUcmVuZHMnKSArCiAgc2NhbGVfY29sb3JfZGlzY3JldGUobGFiZWxzID0gYygndGhldGExJywndGhldGEyJykpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnYm90dG9tJywgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpgYGAKClZpc3VhbGl6ZSB0aGUgZXN0aW1hdGUgb2YgdGhlIGF1dG9jb3JyZWxhdGlvbiBmdW5jdGlvbgoKYGBge3IgfQpnZ3Bsb3QoZGF0YSA9IGRmYSkgKwogIGdlb21fbGluZShhZXMoaXRlciwgdmFsdWUsIGNvbG9yID0gZ3JwKSkgKwogIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQgPSAwKSkgKwogIGxhYnModGl0bGUgPSAnQXV0b2NvcnJlbGF0aW9uIGZ1bmN0aW9uJykgKwogIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKGxhYmVscyA9IGMoJ3RoZXRhMScsICd0aGV0YTInKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nLCBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKVmlzdWFsaXplIHRoZSBlc3RpbWF0ZSBvZiB0aGUgTW9udGUgQ2FybG8gZXJyb3IgZXN0aW1hdGVzCgpgYGB7ciB9CiMgbGFiZWxzCmxhYnMzIDwtIGMoJ3RoZXRhMScsICd0aGV0YTInLAogICAgICAgICAgICc5NSUgaW50ZXJ2YWwgZm9yIE1DTUMgZXJyb3InLAogICAgICAgICAgICc5NSUgaW50ZXJ2YWwgZm9yIGluZGVwZW5kZW50IE1DJykKZ2dwbG90KCkgKwogIGdlb21fbGluZShkYXRhID0gZGZjYSwgYWVzKGl0ZXIsIHZhbHVlLCBjb2xvciA9IGdycCwgbGluZXR5cGUgPSBncnApKSArCiAgZ2VvbV9saW5lKGFlcygxOlNiLCAtMS45Ni9zcXJ0KDE6U2IqcmVmZikpLCBsaW5ldHlwZSA9IDIpICsKICBnZW9tX2xpbmUoYWVzKDE6U2IsIC0xLjk2L3NxcnQoMTpTYikpLCBsaW5ldHlwZSA9IDMpICsKICBnZW9tX2hsaW5lKGFlcyh5aW50ZXJjZXB0ID0gMCkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoLTEuNSwgMS41KSkgKwogIGxhYnModGl0bGUgPSAnQ3VtdWxhdGl2ZSBhdmVyYWdlcycpICsKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygncmVkJywnYmx1ZScscmVwKCdibGFjaycsIDIpKSwgbGFiZWxzID0gbGFiczMpICsKICBzY2FsZV9saW5ldHlwZV9tYW51YWwodmFsdWVzID0gYygxLCAxLCAyLCAzKSwgbGFiZWxzID0gbGFiczMpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnYm90dG9tJywgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpgYGAKClNhbWUgYWdhaW4gd2l0aCByPTAuOTkKUGFyYW1ldGVycyBvZiBhIG5vcm1hbCBkaXN0cmlidXRpb24gdXNlZCBhcyBhIHRveSB0YXJnZXQgZGlzdHJpYnV0aW9uCgpgYGB7ciB9CnkxIDwtIDAKeTIgPC0gMApyIDwtIDAuOTkKU2lnbWEgPC0gZGlhZygyKQpTaWdtYVsxLCAyXSA8LSByClNpZ21hWzIsIDFdIDwtIHIKYGBgCgpTYW1wbGUgZnJvbSB0aGUgdG95IGRpc3RyaWJ1dGlvbiB0byB2aXN1YWxpemUgOTAlIEhQRAppbnRlcnZhbCB3aXRoIGdncGxvdCdzIHN0YXRfZWxsaXBzZSgpCgpgYGB7ciB9CmRmdCA8LSBkYXRhLmZyYW1lKG12cm5vcm0oMTAwMDAwLCBjKDAsIDApLCBTaWdtYSkpCmBgYAoKc2VlIEJEQTMgcC4gODUgZm9yIGhvdyB0byBjb21wdXRlIEhQRCBmb3IgbXVsdGl2YXJpYXRlIG5vcm1hbAppbiAyZC1jYXNlIGNvbnRvdXIgZm9yIDkwJSBIUEQgaXMgYW4gZWxsaXBzZSwgd2hvc2Ugc2VtaW1ham9yCmF4ZXMgY2FuIGJlIGNvbXB1dGVkIGZyb20gdGhlIGVpZ2VudmFsdWVzIG9mIHRoZSBjb3ZhcmlhbmNlCm1hdHJpeCBzY2FsZWQgYnkgYSB2YWx1ZSBzZWxlY3RlZCB0byBnZXQgZWxsaXBzZSBtYXRjaCB0aGUKZGVuc2l0eSBhdCB0aGUgZWRnZSBvZiA5MCUgSFBELiBBbmdsZSBvZiB0aGUgZWxsaXBzZSBjb3VsZCBiZQpjb21wdXRlZCBmcm9tIHRoZSBlaWdlbnZlY3RvcnMsIGJ1dCBzaW5jZSB0aGUgbWFyZ2luYWxzIGFyZSBzYW1lCndlIGtub3cgdGhhdCBhbmdsZSBpcyBwaS80ClN0YXJ0aW5nIHZhbHVlIG9mIHRoZSBjaGFpbgoKYGBge3IgfQp0MSA8LSAtMi41CnQyIDwtIDIuNQpgYGAKCk51bWJlciBvZiBpdGVyYXRpb25zLgoKYGBge3IgfQpNIDwtIDIqMjUwMApgYGAKCk4uQi4gSW4gdGhpcyBpbXBsZW1lbnRhdGlvbiBvbmUgaXRlcmF0aW9uIHVwZGF0ZXMgb25seSBvbmUgcGFyYW1ldGVyIGFuZCBvbmUKY29tcGxldGUgaXRlcmF0aW9uIHVwZGF0aW5nIGJvdGggcGFyYW1ldGVycyB0YWtlcyB0d28gYmFzaWMgaXRlcmF0aW9ucy4gVGhpcwppbXBsZW1lbnRhdGlvbiB3YXMgdXNlZCB0byBtYWtlIHBsb3R0aW5nIG9mIEdpYmJzIHNhbXBsZXIncyB6aWctemFnZ2luZy4gSW4KcGxvdHMgWW91IGNhbiBpbXBsZW1lbnQgdGhpcyBhbHNvIGJ5IHNhdmluZyBvbmx5IHRoZSBmaW5hbCBzdGF0ZSBvZiBjb21wbGV0ZQppdGVyYXRpb24gdXBkYXRpbmcgYWxsIHBhcmFtZXRlcnMuCkluc2VydCB5b3VyIG93biBHaWJicyBzYW1wbGluZyBoZXJlCgpgYGB7ciB9CiNBbGxvY2F0ZSBtZW1vcnkgZm9yIHRoZSBzYW1wbGUKdHQgPC0gbWF0cml4KHJlcCgwLCAyKk0pLCBuY29sID0gMikKdHRbMSxdIDwtIGModDEsIHQyKSAgICAjIFNhdmUgc3RhcnRpbmcgcG9pbnQKIyBGb3IgZGVtb25zdHJhdGlvbiBsb2FkIHByZS1jb21wdXRlZCB2YWx1ZXMKIyBSZXBsYWNlIHRoaXMgd2l0aCB5b3VyIGFsZ29yaXRobSEKIyB0dCBpcyBhIE0geCAyIGFycmF5LCB3aXRoIE0gZHJhd3Mgb2YgYm90aCB0aGV0YV8xIGFuZCB0aGV0YV8yCmxvYWQocm9vdCgiZGVtb3NfY2gxMSIsImRlbW8xMV8xYi5SRGF0YSIpKQpgYGAKClRoZSByZXN0IGlzIGZvciBpbGx1c3RyYXRpb24KVGFrZSB0aGUgZmlyc3QgNTAgZHJhd3MKdG8gaWxsdXN0cmF0ZSBob3cgdGhlIHNhbXBsZXIgd29ya3MKCmBgYHtyIH0KZGYxMDAgPC0gZGF0YS5mcmFtZShpZD1yZXAoMSwxMDApLAogICAgICAgICAgICAgICAgICAgIGl0ZXI9MToxMDAsIAogICAgICAgICAgICAgICAgICAgIHRoMSA9IHR0WzE6MTAwLCAxXSwKICAgICAgICAgICAgICAgICAgICB0aDIgPSB0dFsxOjEwMCwgMl0sCiAgICAgICAgICAgICAgICAgICAgdGgxbCA9IGModHRbMSwgMV0sIHR0WzE6KDEwMC0xKSwgMV0pLAogICAgICAgICAgICAgICAgICAgIHRoMmwgPSBjKHR0WzEsIDJdLCB0dFsxOigxMDAtMSksIDJdKSkKYGBgCgpUYWtlIHRoZSBmaXJzdCAxMDAwIG9ic2VydmF0aW9ucwoKYGBge3IgfQpTIDwtIDEwMDAKZGZzIDwtIGRhdGEuZnJhbWUodGgxID0gdHRbMTpTLCAxXSwgdGgyID0gdHRbMTpTLCAyXSkKYGBgCgpSZW1vdmUgd2FybS11cCBwZXJpb2Qgb2YgNTAgZmlyc3QgZHJhd3MgbGF0ZXIKCmBgYHtyIH0Kd2FybSA8LSA1MAoKIyBsYWJlbHMgYW5kIGZyYW1lIGluZGljZXMgZm9yIHRoZSBwbG90CmxhYnMxIDwtIGMoJ0RyYXdzJywgJ1N0ZXBzIG9mIHRoZSBzYW1wbGVyJywgJzkwJSBIUEQnKQppbmQxIDwtICgxOjUwKSoyLTEKZGYxMDBzIDwtIGRmMTAwCmRmMTAwc1tpbmQxKzEsMzo0XT1kZjEwMHNbaW5kMSwzOjRdCnAxIDwtIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBkZjEwMHMsCiAgICAgICAgICAgICBhZXModGgxLCB0aDIsIGdyb3VwPWlkLCBjb2xvciA9JzEnKSkgKwogIGdlb21fc2VnbWVudChkYXRhID0gZGYxMDAsIGFlcyh4ID0gdGgxLCB4ZW5kID0gdGgxbCwgY29sb3IgPSAnMicsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSB0aDIsIHllbmQgPSB0aDJsKSkgKwogIHN0YXRfZWxsaXBzZShkYXRhID0gZGZ0LCBhZXMoeCA9IFgxLCB5ID0gWDIsIGNvbG9yID0gJzMnKSwgbGV2ZWwgPSAwLjkpICsKICBjb29yZF9jYXJ0ZXNpYW4oeGxpbSA9IGMoLTQsIDQpLCB5bGltID0gYygtNCwgNCkpICsKICBsYWJzKHggPSAndGhldGExJywgeSA9ICd0aGV0YTInKSArCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoJ3JlZCcsICdmb3Jlc3RncmVlbicsJ2JsdWUnKSwgbGFiZWxzID0gbGFiczEpICsKICBndWlkZXMoY29sb3IgPSBndWlkZV9sZWdlbmQob3ZlcnJpZGUuYWVzID0gbGlzdCgKICAgIHNoYXBlID0gYygxNiwgTkEsIE5BKSwgbGluZXR5cGUgPSBjKDAsIDEsIDEpKSkpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnYm90dG9tJywgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpgYGAKClRoZSBmb2xsb3dpbmcgZ2VuZXJhdGVzIGEgZ2lmIGFuaW1hdGlvbgpvZiB0aGUgc3RlcHMgb2YgdGhlIHNhbXBsZXIgKG1pZ2h0IHRha2UgMTAgc2Vjb25kcykuCgpgYGB7ciBHaWJicyAoMiksIHJlc3VsdHM9J2hpZGUnLCBtZXNzYWdlPUZBTFNFfQphbmltIDwtIGFuaW1hdGUocDEgKyAgIAogICAgICAgICAgICAgICAgICB0cmFuc2l0aW9uX3JldmVhbChhbG9uZz1pdGVyKSArIAogICAgICAgICAgICAgICAgICBzaGFkb3dfdHJhaWwoMC4wMSkpCmBgYAoKU2hvdyB0aGUgYW5pbWF0aW9uCgpgYGB7ciB9CmFuaW0KYGBgCgpTaG93IG9ubHkgdGhlIGVuZCByZXN1bHQgYXMgYSBzdGF0aWMgZmlndXJlCgpgYGB7ciB9CnAxCmBgYAoKSGlnaGxpZ2h0IHdhcm0tdXAgcGVyaW9kIG9mIHRoZSAzMCBmaXJzdCBkcmF3cyB3aXRoIHB1cnBsZQoKYGBge3IgfQpwMSArIGdlb21fcG9pbnQoZGF0YSA9IGRmMTAwW2luZDFbMTozMF0sXSwKICAgICAgICAgICAgICAgIGFlcyh0aDEsIHRoMiksIGNvbG9yID0gJ2dyZWVuJykKYGBgCgpzaG93IDk1MCBkcmF3cyBhZnRlciBhIHdhcm0tdXAgcGVyaW9kIG9mCjUwIGRyYXdzIGlzIHJlbW92ZWQKCmBgYHtyIH0KbGFiczIgPC0gYygnRHJhd3MnLCAnOTAlIEhQRCcpCmdncGxvdCgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBkZnNbLSgxOndhcm0pLF0sCiAgICAgICAgICAgICBhZXModGgxLCB0aDIsIGNvbG9yID0gJzEnKSwgYWxwaGEgPSAwLjUpICsKICBzdGF0X2VsbGlwc2UoZGF0YSA9IGRmdCwgYWVzKHggPSBYMSwgeSA9IFgyLCBjb2xvciA9ICcyJyksIGxldmVsID0gMC45KSArCiAgY29vcmRfY2FydGVzaWFuKHhsaW0gPSBjKC00LCA0KSwgeWxpbSA9IGMoLTQsIDQpKSArCiAgbGFicyh4ID0gJ3RoZXRhMScsIHkgPSAndGhldGEyJykgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCdzdGVlbGJsdWUnLCAnYmx1ZScpLCBsYWJlbHMgPSBsYWJzMikgKwogIGd1aWRlcyhjb2xvciA9IGd1aWRlX2xlZ2VuZChvdmVycmlkZS5hZXMgPSBsaXN0KAogICAgc2hhcGUgPSBjKDE2LCBOQSksIGxpbmV0eXBlID0gYygwLCAxKSwgYWxwaGEgPSBjKDEsIDEpKSkpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnYm90dG9tJywgbGVnZW5kLnRpdGxlID0gZWxlbWVudF9ibGFuaygpKQpgYGAKCiMjIyBDb252ZXJnZW5jZSBkaWFnbm9zdGljcwoKYGBge3IgfQpzdW1tYXJpc2VfZHJhd3MoZGZzLCBSaGF0PXJoYXRfYmFzaWMsIEVTUz1lc3NfYmFzaWMpCm5lZmYgPC0gYXBwbHkoZGZzLCAyLCBlc3NfYmFzaWMpCiMgYm90aCB0aGV0YSBoYXZlIG93biBuZWZmLCBidXQgZm9yIHBsb3R0aW5nIHRoZXNlIGFyZSBzbyBjbG9zZSB0byBlYWNoCiMgb3RoZXIsIHNvIHRoYXQgc2luZ2xlIHJlbGF0aXZlIGVmZmljaWVuY3kgdmFsdWUgaXMgdXNlZApyZWZmIDwtIG1lYW4obmVmZi9TKQpgYGAKCiMjIyBWaXN1YWwgY29udmVyZ2VuY2UgZGlhZ25vc3RpY3MKQ29sbGFwc2UgdGhlIGRhdGEgZnJhbWUgd2l0aCByb3cgbnVtYmVycyBhdWdtZW50ZWQKaW50byBrZXktdmFsdWUgcGFpcnMgZm9yIHZpc3VhbGl6aW5nIHRoZSBjaGFpbnMKCmBgYHtyIH0KZGZiIDwtIGRmc1stKDE6d2FybSksXQpTYiA8LSBTLXdhcm0KZGZjaCA8LSB3aXRoaW4oZGZiLCBpdGVyIDwtIDE6U2IpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9ICFpdGVyLCBuYW1lc190byA9ICJncnAiLCB2YWx1ZXNfdG8gPSAidmFsdWUiKQpgYGAKCkFub3RoZXIgZGF0YSBmcmFtZSBmb3IgdmlzdWFsaXppbmcgdGhlIGVzdGltYXRlIG9mCnRoZSBhdXRvY29ycmVsYXRpb24gZnVuY3Rpb24KCmBgYHtyIH0KbmxhZ3MgPC0gNzUKZGZhIDwtIHNhcHBseShkZmIsIGZ1bmN0aW9uKHgpIGFjZih4LCBsYWcubWF4ID0gbmxhZ3MsIHBsb3QgPSBGKSRhY2YpICU+JQogIGRhdGEuZnJhbWUoaXRlciA9IDA6KG5sYWdzKSkgJT4lIAogIHBpdm90X2xvbmdlcihjb2xzID0gIWl0ZXIsIG5hbWVzX3RvID0gImdycCIsIHZhbHVlc190byA9ICJ2YWx1ZSIpCmBgYAoKQSB0aGlyZCBkYXRhIGZyYW1lIHRvIHZpc3VhbGl6ZSB0aGUgY3VtdWxhdGl2ZSBhdmVyYWdlcwphbmQgdGhlIDk1JSBpbnRlcnZhbHMKCmBgYHtyIH0KZGZjYSA8LSAoY3Vtc3VtKGRmYikgLyAoMTpTYikpICU+JQogIHdpdGhpbih7aXRlciA8LSAxOlNiCiAgICAgICAgICB1cHBpIDwtICAxLjk2L3NxcnQoMTpTYikKICAgICAgICAgIHVwcCA8LSAxLjk2LyhzcXJ0KDE6U2IqcmVmZikpfSkgJT4lCiAgcGl2b3RfbG9uZ2VyKGNvbHMgPSAhaXRlciwgbmFtZXNfdG8gPSAiZ3JwIiwgdmFsdWVzX3RvID0gInZhbHVlIikKYGBgCgpWaXN1YWxpemUgdGhlIGNoYWlucwoKYGBge3IgfQpnZ3Bsb3QoZGF0YSA9IGRmY2gpICsKICBnZW9tX2xpbmUoYWVzKGl0ZXIsIHZhbHVlLCBjb2xvciA9IGdycCkpICsKICBsYWJzKHRpdGxlID0gJ1RyZW5kcycpICsKICBzY2FsZV9jb2xvcl9kaXNjcmV0ZShsYWJlbHMgPSBjKCd0aGV0YTEnLCd0aGV0YTInKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nLCBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoKVmlzdWFsaXplIHRoZSBlc3RpbWF0ZSBvZiB0aGUgYXV0b2NvcnJlbGF0aW9uIGZ1bmN0aW9uCgpgYGB7ciB9CmdncGxvdChkYXRhID0gZGZhKSArCiAgZ2VvbV9saW5lKGFlcyhpdGVyLCB2YWx1ZSwgY29sb3IgPSBncnApKSArCiAgZ2VvbV9obGluZShhZXMoeWludGVyY2VwdCA9IDApKSArCiAgbGFicyh0aXRsZSA9ICdBdXRvY29ycmVsYXRpb24gZnVuY3Rpb24nKSArCiAgc2NhbGVfY29sb3JfZGlzY3JldGUobGFiZWxzID0gYygndGhldGExJywgJ3RoZXRhMicpKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ2JvdHRvbScsIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSkKYGBgCgpWaXN1YWxpemUgdGhlIGVzdGltYXRlIG9mIHRoZSBNb250ZSBDYXJsbyBlcnJvciBlc3RpbWF0ZXMKCmBgYHtyIH0KIyBsYWJlbHMKbGFiczMgPC0gYygndGhldGExJywgJ3RoZXRhMicsCiAgICAgICAgICAgJzk1JSBpbnRlcnZhbCBmb3IgTUNNQyBlcnJvcicsCiAgICAgICAgICAgJzk1JSBpbnRlcnZhbCBmb3IgaW5kZXBlbmRlbnQgTUMnKQpnZ3Bsb3QoKSArCiAgZ2VvbV9saW5lKGRhdGEgPSBkZmNhLCBhZXMoaXRlciwgdmFsdWUsIGNvbG9yID0gZ3JwLCBsaW5ldHlwZSA9IGdycCkpICsKICBnZW9tX2xpbmUoYWVzKDE6U2IsIC0xLjk2L3NxcnQoMTpTYipyZWZmKSksIGxpbmV0eXBlID0gMikgKwogIGdlb21fbGluZShhZXMoMTpTYiwgLTEuOTYvc3FydCgxOlNiKSksIGxpbmV0eXBlID0gMykgKwogIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQgPSAwKSkgKwogIGNvb3JkX2NhcnRlc2lhbih5bGltID0gYygtMS41LCAxLjUpKSArCiAgbGFicyh0aXRsZSA9ICdDdW11bGF0aXZlIGF2ZXJhZ2VzJykgKwogIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKCdyZWQnLCdibHVlJyxyZXAoJ2JsYWNrJywgMikpLCBsYWJlbHMgPSBsYWJzMykgKwogIHNjYWxlX2xpbmV0eXBlX21hbnVhbCh2YWx1ZXMgPSBjKDEsIDEsIDIsIDMpLCBsYWJlbHMgPSBsYWJzMykgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICdib3R0b20nLCBsZWdlbmQudGl0bGUgPSBlbGVtZW50X2JsYW5rKCkpCmBgYAoK