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