Bandits
One step
To illustrate bandits, we consider a very simple DGP:
\(Y(0) \sim N(5,2^2)\)
\(Y(1) \sim
N(5.5,2^2)\)
This means that the optimal policy rule assigns everybody to
treatment one. Let’s see how UCB and Thompson sampling figure this out
on-the-fly. For a stream of 1000 observations.
if (!require("tidyverse")) install.packages("tidyverse", dependencies = TRUE); library(tidyverse)
if (!require("ggridges")) install.packages("ggridges", dependencies = TRUE); library(ggridges)
set.seed(1234)
# Define parameters
gamma0 = 5
gamma1 = 5.5
sd0 = 2
sd1 = 2
n = 1000
# Draw the potential outcomes of all individuals
Y0 = rnorm(n,gamma0,sd0)
Y1 = rnorm(n,gamma1,sd1)
hist(Y0)
hist(Y1)
As a kick-start, we assign the first four individuals “manually” to
control/treatment/control/treatment:
# Assign individual 1 to treatment zero
W = 0
# and observe the potential outcome under non-treatment
Y = Y0[1]
# Assign individual 2 to treatment one
W = c(W,1)
# and observe the potential outcome under treatment
Y = c(Y,Y1[2])
# Assign individual 3 to treatment zero
W = c(W,0)
# and observe the potential outcome under treatment
Y = c(Y,Y0[3])
# Assign individual 4 to treatment one
W = c(W,1)
# and observe the potential outcome under treatment
Y = c(Y,Y1[4])
# Print the realizes values
W
[1] 0 1 0 1
Y
[1] 2.585869 6.102933 7.168882 6.770741
Now calculate the means in each treatment arm:
gamma = c(mean(Y[W==0]), mean(Y[W==1]))
gamma
[1] 4.877375 6.436837
And the standard errors of the means:
se = c(sd(Y[W==0]) / sqrt(sum(W==0)),
sd(Y[W==1]) / sqrt(sum(W==1)))
se
[1] 2.291507 0.333904
Upper Confidence Bound (UCB)
For \(i=5\), we implement now
explicitly UCB \[W_{i+1} = argmax_w
(\hat{\gamma}_{i,w} + \alpha \hat{\sigma}_{i,w})\] with
alpha = 2
:
# Calculate upper confidence bound
alpha = 2
upper_ci = gamma + alpha * se
upper_ci
[1] 9.460389 7.104645
# Choose the treatment with the highest bound
which.max(upper_ci)-1
[1] 0
Let’s visualize the decision.
plot_ucb = function(gamma, se, alpha, unit = FALSE) {
# Calculate UCB and find the arm with the highest UCB
ucb <- gamma + alpha * se
highest_ucb_arm <- which.max(ucb)
# Data frame for plotting
data <- data.frame(
Arm = factor(1:(length(gamma))),
Estimate = gamma,
UCB = ucb,
Highlight = ifelse(1:length(gamma) == highest_ucb_arm, "forestgreen", "orange")
)
# Plot
g = ggplot(data, aes(x = Arm)) +
geom_errorbar(aes(ymin = Estimate, ymax = UCB, color = Highlight), width = 0.01, linewidth = 1) +
geom_segment(aes(x = as.numeric(Arm)-0.05, y = UCB, xend = as.numeric(Arm)+0.05, yend = UCB, color = Highlight), linewidth = 1) +
geom_point(aes(y = Estimate), size = 4, color = "black") +
scale_color_identity() + geom_hline(yintercept = 0) +
labs(y = "Estimate / Upper Confidence Bound", x = "Arm",
title = "UCB Sampling for Multi-Armed Bandits") +
theme_minimal() + ylim(0,10) + theme(plot.title = element_text(hjust = 0.5))
if (is.numeric(unit)) g = g + labs(title = paste("UCB Sampling of treatment for unit",unit+1))
print(g)
}
plot_ucb(gamma,se,2)
Thompson sampling
Thompson sampling draws a value for each arm from an arm specific
normal distribution \(\tilde{\gamma}_{i,w}
\sim N(\hat{\gamma}_{i,w},\alpha^2\hat{\sigma}^2_{i,w})\) and
sets \[W_{i+1} = argmax_w
\tilde{\gamma}_{i,w} \]
# Thompson
draws = c(rnorm(1,gamma[1],alpha*se[1]),
rnorm(1,gamma[2],alpha*se[2]))
draws
[1] 0.4143512 6.3703030
which.max(draws)-1
[1] 1
Let’s visualize the decision.
plot_ts = function(gamma, se, alpha, gamma_tilde=draws, unit = FALSE) {
# This is probably the most complicated and unelegant plot I ever created, but it makes the point
# Let me know if you have an alternative to plot it more elegantly
highest_arm <- which.max(gamma_tilde)
# Data frame for plotting
data <- data.frame(
Arm = 0:1,
Estimate = gamma,
Draw = gamma_tilde,
se = se * alpha
)
plot_data <- data %>%
group_by(Arm) %>%
slice(rep(1:n(), each = 10000)) %>%
mutate(
y = rnorm(n(), mean = Estimate, sd = se),
) %>%
mutate(
dens = dnorm(y,mean = Estimate, sd = se),
) %>%
ungroup()
# Plot
g = ggplot(plot_data, aes(y=Arm)) +
geom_point(data=data, aes(y=Arm, x = Draw,color=Draw), size = 5, shape = 4, stroke=1) +
scale_color_gradient(low = "orange", high = "forestgreen") +
geom_point(data=data, aes(x=Estimate), size = 3, color = "black") +
geom_density_ridges(data = plot_data[plot_data$Arm=="0",], aes(y=Arm,x = y, height = dens),
alpha=0.7, position = position_nudge(y = 0), stat = "identity") +
geom_density_ridges(data = plot_data[plot_data$Arm=="1",], aes(y=Arm,x = y, height = dens),
alpha=0.7, position = position_nudge(y = 0), stat = "identity") +
labs(y = "Arm / Normal distribution", x = "Estimate",
title = "Thompson Sampling") +
theme_minimal() + scale_y_continuous(breaks = c(0, 1), labels = c("0", "1")) +
# # xlim(-10,20) +
geom_vline(xintercept = 0) +
theme(legend.position="none", plot.title = element_text(hjust = 0.5)) +
coord_flip(ylim = c(0, 3),xlim = c(-10, 20))
if (is.numeric(unit)) g = g + labs(title = paste("Thompson Sampling for unit",unit+1))
print(g)
}
plot_ts(gamma,se,2,draws)
Note that with this very seed, UCB and Thompson sampling do not take
the same choice \(\Rightarrow\) their
treatment sequences differ. Note that the decision of UCB is
deterministic, while it is random for Thompson sampling.
Dynamic treatment assignment (UCB)
First, proceed with UCB and define a function that takes
and returns a treatment assignment for the next individual:
ucb = function(Y, W, alpha, plot = FALSE){
gamma = c(mean(Y[W==0]), mean(Y[W==1]))
se = c(sd(Y[W==0]) / sqrt(sum(W==0)), sd(Y[W==1]) / sqrt(sum(W==1)))
assign = which.max(gamma + alpha * se)
if (isTRUE(plot)) plot_ucb(gamma,se,2, unit = length(Y))
return(assign-1)
}
Now we run UCB until \(i=1000\) and
plot the evolution:
# Store to use it for Thompson below
Wstart = W
Ystart = Y
plot_grid = c(5:10,20,50,100,200,500,1000)
for (i in 5:1000) {
# Assign unit to treatment (and sometimes plot)
if (i %in% plot_grid) Wi = ucb(Y,W,alpha,plot = TRUE)
else Wi = ucb(Y,W,alpha,plot = FALSE)
W = c(W,Wi)
# and observe the potential outcome under treatment
Y = c(Y,(1-Wi) * Y0[i] + Wi * Y1[i])
}
additionally plot the share being assigned to the optimal policy
share = cumsum(W) / 1:n
plot(1:n,share)
and the regret
regret = cumsum(gamma1 - (W * gamma1 + (1-W) * gamma0) ) / 1:n
plot(1:n,regret)
We observe that as observations flow in, UCB figures out that
treatment one is dominant and starts to solely assign it. Such that at
the end of the 1000 observations regret is negligible. Note that the
regret of a 50:50 experiment would have been 0.25.
Dynamic treatment assignment (Thompson)
Write a function similar to ucb
above:
thompson = function(Y, W, alpha, plot = FALSE){
gamma = c(mean(Y[W==0]), mean(Y[W==1]))
se = c(sd(Y[W==0]) / sqrt(sum(W==0)), sd(Y[W==1]) / sqrt(sum(W==1)))
draws = c(rnorm(1, gamma[1], alpha * se[1]),
rnorm(1, gamma[2], alpha * se[2]))
assign = which.max(draws)
if (isTRUE(plot)) plot_ts(gamma,se,2,draws,unit = length(Y))
return(assign-1)
}
Now we run Thompson until \(i=1000\)
and plot the evolution:
# Restore starting points
W = Wstart
Y = Ystart
plot_grid = c(5:10,20,50,100,200,500,1000)
for (i in 5:1000) {
# Assign unit to treatment (and sometimes plot)
if (i %in% plot_grid) Wi = thompson(Y,W,alpha,plot = TRUE)
else Wi = thompson(Y,W,alpha,plot = FALSE)
W = c(W,Wi)
# and observe the potential outcome under treatment
Y = c(Y,(1-Wi) * Y0[i] + Wi * Y1[i])
}
additionally plot the share being assigned to the optimal policy
share = cumsum(W) / 1:n
plot(1:n,share)
and the regret
regret = cumsum(gamma1 - (W * gamma1 + (1-W) * gamma0) ) / 1:n
plot(1:n,regret)
Again we observe that as observations flow in, Thompson sampling
figures out that treatment one is dominant and starts to solely assign
it. Such that at the end of the 1000 observations regret is negligible.
Note that the regret of a 50:50 experiment would have been 0.25.
Simulation study UCB
We now run this 100 times each for a sequence of values of the tuning
parameters alpha = c(0,0.5,1,1.5,2,2.5,3,4,5,10,1000)
. We
track the following quantities:
Average regret at \(i=10\),
\(i=100\) and \(i=1000\)
Probability to assign the optimal treatment at \(i=10\), \(i=100\) and \(i=1000\)
alpha = c(0,0.5,1,1.5,2,2.5,3,4,5,10,1000)
reps = 1000 # Reduce to 100 if you have no time
temp = matrix(NA,reps,6)
results = matrix(NA,length(alpha),6)
colnames(results) = c("regret10","regret100","regret1000","optimal10","optimal100","optimal1000")
for (a in 1:length(alpha)) {
for (j in 1:reps) {
# Draw sample
Y0 = rnorm(n,gamma0,sd0)
Y1 = rnorm(n,gamma1,sd1)
# Get kick-start
W = c(0,1,0,1)
Y = (1-W) * Y0[1:4] + W * Y1[1:4]
# Run UCB
for (i in 5:n) {
# Assign individual 4 to treatment one
Wi = ucb(Y,W,alpha[a])
W = c(W,Wi)
# and observe the potential outcome under treatment
Y = c(Y,(1-Wi) * Y0[i] + Wi * Y1[i])
}
regret = cumsum(gamma1 - (W * gamma1 + (1-W) * gamma0) ) / 1:n
temp[j,1] = regret[10]
temp[j,2] = regret[100]
temp[j,3] = regret[1000]
temp[j,4] = W[10]
temp[j,5] = W[100]
temp[j,6] = W[1000]
}
results[a,] = colMeans(temp)
}
as_tibble(cbind(alpha, results[,1:3])) %>% pivot_longer(!alpha,names_to = "individual", values_to = "regret") %>%
ggplot(aes(factor(alpha), regret, color=as.factor(individual))) +
geom_line(aes(group=factor(individual))) +
labs(x = "alpha", color = "Position") + theme_bw()
as_tibble(cbind(alpha, results[,4:6])) %>% pivot_longer(!alpha,names_to = "individual", values_to = "optimal") %>%
ggplot(aes(factor(alpha), optimal, color=as.factor(individual))) +
geom_line(aes(group=factor(individual))) +
labs(x = "alpha", y = "Prob of optimal choice", color = "Position") + theme_bw()
We observe that a value of \(\alpha =
3\) minimizes regret in the long run and maximizes the
probability to assign the best treatment to individual 1000. Very low
values of \(\alpha\) result in too
little exploration. This means the bandit might commit early on to the
wrong treatment. On the other hand, very high values of \(\alpha\) explore too much. \(\alpha = 1000\) is a very extreme case,
which is nearly equivalent to running a 50:50 experiment. The bandit
ignores basically any knowledge about the better treatment.
Simulation study Thompson sampling
Use the Thompson function in the same loop as before:
alpha = c(0,0.5,1,1.5,2,2.5,3,4,5,10,1000)
reps = 1000 # Reduce to 100 if you have no time
temp = matrix(NA,reps,6)
results = matrix(NA,length(alpha),6)
colnames(results) = c("regret10","regret100","regret1000","optimal10","optimal100","optimal1000")
for (a in 1:length(alpha)) {
for (j in 1:reps) {
# Draw sample
Y0 = rnorm(n,gamma0,sd0)
Y1 = rnorm(n,gamma1,sd1)
# Get kick-start
W = c(0,1,0,1)
Y = (1-W) * Y0[1:4] + W * Y1[1:4]
# Run UCB
for (i in 5:n) {
# Assign individual 4 to treatment one
Wi = thompson(Y,W,alpha[a])
W = c(W,Wi)
# and observe the potential outcome under treatment
Y = c(Y,(1-Wi) * Y0[i] + Wi * Y1[i])
}
regret = cumsum(gamma1 - (W * gamma1 + (1-W) * gamma0) ) / 1:n
temp[j,1] = regret[10]
temp[j,2] = regret[100]
temp[j,3] = regret[1000]
temp[j,4] = W[10]
temp[j,5] = W[100]
temp[j,6] = W[1000]
}
results[a,] = colMeans(temp)
}
as_tibble(cbind(alpha, results[,1:3])) %>% pivot_longer(!alpha,names_to = "individual", values_to = "regret") %>%
ggplot(aes(factor(alpha), regret, color=as.factor(individual))) +
geom_line(aes(group=factor(individual))) +
labs(x = "alpha", color = "Position") + theme_bw()
as_tibble(cbind(alpha, results[,4:6])) %>% pivot_longer(!alpha,names_to = "individual", values_to = "optimal") %>%
ggplot(aes(factor(alpha), optimal, color=as.factor(individual))) +
geom_line(aes(group=factor(individual))) +
labs(x = "alpha", y = "Prob of optimal choice", color = "Position") + theme_bw()
We see the same exploration vs. exploitation trade-off as for UCB.
However, the optimal \(\alpha = 1\) and
Thompson sampling manages to realize a smaller average regret of around
0.05, while UCB had only 0.07, at least with this very coarse grid.
Potential extensions
- Change the mean and/or variance of the treatment and control group.
First think about what you expect and then test your intuition by
running the modified notebook.
LS0tDQp0aXRsZTogIkNhdXNhbCBNTDogTXVsdGktYXJtZWQgYmFuZGl0Ig0Kc3VidGl0bGU6ICJTaW11bGF0aW9uIG5vdGVib29rIg0KYXV0aG9yOiAiTWljaGFlbCBLbmF1cyINCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVtLyV5JylgIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KLS0tDQoNCjxicj4NCg0KR29hbHM6DQoNCi0gICBIYW5kY29kZSBVQ0IgYW5kIFRob21wc29uIHNhbXBsaW5nIGluIHN0eWxpemVkIERHUA0KDQo8YnI+DQoNCiMgQmFuZGl0cw0KDQojIyBPbmUgc3RlcA0KDQpUbyBpbGx1c3RyYXRlIGJhbmRpdHMsIHdlIGNvbnNpZGVyIGEgdmVyeSBzaW1wbGUgREdQOg0KDQotICAgJFkoMCkgXHNpbSBOKDUsMl4yKSQNCg0KLSAgICRZKDEpIFxzaW0gTig1LjUsMl4yKSQNCg0KVGhpcyBtZWFucyB0aGF0IHRoZSBvcHRpbWFsIHBvbGljeSBydWxlIGFzc2lnbnMgZXZlcnlib2R5IHRvIHRyZWF0bWVudCBvbmUuIExldCdzIHNlZSBob3cgVUNCIGFuZCBUaG9tcHNvbiBzYW1wbGluZyBmaWd1cmUgdGhpcyBvdXQgb24tdGhlLWZseS4gRm9yIGEgc3RyZWFtIG9mIDEwMDAgb2JzZXJ2YXRpb25zLg0KDQpgYGB7ciwgd2FybmluZz1GLG1lc3NhZ2U9Rn0NCmlmICghcmVxdWlyZSgidGlkeXZlcnNlIikpIGluc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIsIGRlcGVuZGVuY2llcyA9IFRSVUUpOyBsaWJyYXJ5KHRpZHl2ZXJzZSkNCmlmICghcmVxdWlyZSgiZ2dyaWRnZXMiKSkgaW5zdGFsbC5wYWNrYWdlcygiZ2dyaWRnZXMiLCBkZXBlbmRlbmNpZXMgPSBUUlVFKTsgbGlicmFyeShnZ3JpZGdlcykNCg0Kc2V0LnNlZWQoMTIzNCkNCg0KIyBEZWZpbmUgcGFyYW1ldGVycw0KZ2FtbWEwID0gNQ0KZ2FtbWExID0gNS41DQpzZDAgPSAyDQpzZDEgPSAyDQpuID0gMTAwMA0KDQojIERyYXcgdGhlIHBvdGVudGlhbCBvdXRjb21lcyBvZiBhbGwgaW5kaXZpZHVhbHMNClkwID0gcm5vcm0obixnYW1tYTAsc2QwKQ0KWTEgPSBybm9ybShuLGdhbW1hMSxzZDEpDQoNCmhpc3QoWTApDQpoaXN0KFkxKQ0KYGBgDQoNCkFzIGEga2ljay1zdGFydCwgd2UgYXNzaWduIHRoZSBmaXJzdCBmb3VyIGluZGl2aWR1YWxzICJtYW51YWxseSIgdG8gY29udHJvbC90cmVhdG1lbnQvY29udHJvbC90cmVhdG1lbnQ6DQoNCmBgYHtyfQ0KIyBBc3NpZ24gaW5kaXZpZHVhbCAxIHRvIHRyZWF0bWVudCB6ZXJvDQpXID0gMA0KIyBhbmQgb2JzZXJ2ZSB0aGUgcG90ZW50aWFsIG91dGNvbWUgdW5kZXIgbm9uLXRyZWF0bWVudA0KWSA9IFkwWzFdDQojIEFzc2lnbiBpbmRpdmlkdWFsIDIgdG8gdHJlYXRtZW50IG9uZQ0KVyA9IGMoVywxKQ0KIyBhbmQgb2JzZXJ2ZSB0aGUgcG90ZW50aWFsIG91dGNvbWUgdW5kZXIgdHJlYXRtZW50DQpZID0gYyhZLFkxWzJdKQ0KIyBBc3NpZ24gaW5kaXZpZHVhbCAzIHRvIHRyZWF0bWVudCB6ZXJvDQpXID0gYyhXLDApDQojIGFuZCBvYnNlcnZlIHRoZSBwb3RlbnRpYWwgb3V0Y29tZSB1bmRlciB0cmVhdG1lbnQNClkgPSBjKFksWTBbM10pDQojIEFzc2lnbiBpbmRpdmlkdWFsIDQgdG8gdHJlYXRtZW50IG9uZQ0KVyA9IGMoVywxKQ0KIyBhbmQgb2JzZXJ2ZSB0aGUgcG90ZW50aWFsIG91dGNvbWUgdW5kZXIgdHJlYXRtZW50DQpZID0gYyhZLFkxWzRdKQ0KDQojIFByaW50IHRoZSByZWFsaXplcyB2YWx1ZXMNClcNClkNCmBgYA0KDQpOb3cgY2FsY3VsYXRlIHRoZSBtZWFucyBpbiBlYWNoIHRyZWF0bWVudCBhcm06DQoNCmBgYHtyfQ0KZ2FtbWEgPSBjKG1lYW4oWVtXPT0wXSksIG1lYW4oWVtXPT0xXSkpDQpnYW1tYQ0KYGBgDQoNCkFuZCB0aGUgc3RhbmRhcmQgZXJyb3JzIG9mIHRoZSBtZWFuczoNCg0KYGBge3J9DQpzZSA9IGMoc2QoWVtXPT0wXSkgLyBzcXJ0KHN1bShXPT0wKSksDQogICAgICAgc2QoWVtXPT0xXSkgLyBzcXJ0KHN1bShXPT0xKSkpDQpzZQ0KYGBgDQoNCiMjIyBVcHBlciBDb25maWRlbmNlIEJvdW5kIChVQ0IpDQoNCkZvciAkaT01JCwgd2UgaW1wbGVtZW50IG5vdyBleHBsaWNpdGx5IFVDQiAkJFdfe2krMX0gPSBhcmdtYXhfdyAoXGhhdHtcZ2FtbWF9X3tpLHd9ICsgXGFscGhhIFxoYXR7XHNpZ21hfV97aSx3fSkkJCB3aXRoIGBhbHBoYSA9IDJgOg0KDQpgYGB7cn0NCiMgQ2FsY3VsYXRlIHVwcGVyIGNvbmZpZGVuY2UgYm91bmQNCmFscGhhID0gMg0KdXBwZXJfY2kgPSBnYW1tYSArIGFscGhhICogc2UNCnVwcGVyX2NpDQojIENob29zZSB0aGUgdHJlYXRtZW50IHdpdGggdGhlIGhpZ2hlc3QgYm91bmQNCndoaWNoLm1heCh1cHBlcl9jaSktMQ0KYGBgDQoNCkxldCdzIHZpc3VhbGl6ZSB0aGUgZGVjaXNpb24uDQoNCmBgYHtyfQ0KcGxvdF91Y2IgPSBmdW5jdGlvbihnYW1tYSwgc2UsIGFscGhhLCB1bml0ID0gRkFMU0UpIHsNCiAgIyBDYWxjdWxhdGUgVUNCIGFuZCBmaW5kIHRoZSBhcm0gd2l0aCB0aGUgaGlnaGVzdCBVQ0INCiAgdWNiIDwtIGdhbW1hICsgYWxwaGEgKiBzZQ0KICBoaWdoZXN0X3VjYl9hcm0gPC0gd2hpY2gubWF4KHVjYikNCg0KICAjIERhdGEgZnJhbWUgZm9yIHBsb3R0aW5nDQogIGRhdGEgPC0gZGF0YS5mcmFtZSgNCiAgICBBcm0gPSBmYWN0b3IoMToobGVuZ3RoKGdhbW1hKSkpLA0KICAgIEVzdGltYXRlID0gZ2FtbWEsDQogICAgVUNCID0gdWNiLA0KICAgIEhpZ2hsaWdodCA9IGlmZWxzZSgxOmxlbmd0aChnYW1tYSkgPT0gaGlnaGVzdF91Y2JfYXJtLCAiZm9yZXN0Z3JlZW4iLCAib3JhbmdlIikNCiAgKQ0KDQogICMgUGxvdA0KICBnID0gZ2dwbG90KGRhdGEsIGFlcyh4ID0gQXJtKSkgKw0KICAgIGdlb21fZXJyb3JiYXIoYWVzKHltaW4gPSBFc3RpbWF0ZSwgeW1heCA9IFVDQiwgY29sb3IgPSBIaWdobGlnaHQpLCB3aWR0aCA9IDAuMDEsIGxpbmV3aWR0aCA9IDEpICsNCiAgICBnZW9tX3NlZ21lbnQoYWVzKHggPSBhcy5udW1lcmljKEFybSktMC4wNSwgeSA9IFVDQiwgeGVuZCA9IGFzLm51bWVyaWMoQXJtKSswLjA1LCB5ZW5kID0gVUNCLCBjb2xvciA9IEhpZ2hsaWdodCksIGxpbmV3aWR0aCA9IDEpICsNCiAgICBnZW9tX3BvaW50KGFlcyh5ID0gRXN0aW1hdGUpLCBzaXplID0gNCwgY29sb3IgPSAiYmxhY2siKSArDQogICAgc2NhbGVfY29sb3JfaWRlbnRpdHkoKSArIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDApICsNCiAgICBsYWJzKHkgPSAiRXN0aW1hdGUgLyBVcHBlciBDb25maWRlbmNlIEJvdW5kIiwgeCA9ICJBcm0iLA0KICAgICAgdGl0bGUgPSAiVUNCIFNhbXBsaW5nIGZvciBNdWx0aS1Bcm1lZCBCYW5kaXRzIikgKw0KICAgIHRoZW1lX21pbmltYWwoKSArIHlsaW0oMCwxMCkgKyB0aGVtZShwbG90LnRpdGxlID0gIGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpDQogIGlmIChpcy5udW1lcmljKHVuaXQpKSBnID0gZyArIGxhYnModGl0bGUgPSBwYXN0ZSgiVUNCIFNhbXBsaW5nIG9mIHRyZWF0bWVudCBmb3IgdW5pdCIsdW5pdCsxKSkNCiAgcHJpbnQoZykNCn0NCnBsb3RfdWNiKGdhbW1hLHNlLDIpDQpgYGANCg0KIyMjIFRob21wc29uIHNhbXBsaW5nDQoNClRob21wc29uIHNhbXBsaW5nIGRyYXdzIGEgdmFsdWUgZm9yIGVhY2ggYXJtIGZyb20gYW4gYXJtIHNwZWNpZmljIG5vcm1hbCBkaXN0cmlidXRpb24gJFx0aWxkZXtcZ2FtbWF9X3tpLHd9IFxzaW0gTihcaGF0e1xnYW1tYX1fe2ksd30sXGFscGhhXjJcaGF0e1xzaWdtYX1eMl97aSx3fSkkIGFuZCBzZXRzICQkV197aSsxfSA9IGFyZ21heF93IFx0aWxkZXtcZ2FtbWF9X3tpLHd9ICQkDQoNCmBgYHtyfQ0KIyBUaG9tcHNvbg0KZHJhd3MgPSBjKHJub3JtKDEsZ2FtbWFbMV0sYWxwaGEqc2VbMV0pLA0KICAgICAgICAgIHJub3JtKDEsZ2FtbWFbMl0sYWxwaGEqc2VbMl0pKQ0KZHJhd3MNCndoaWNoLm1heChkcmF3cyktMQ0KYGBgDQoNCkxldCdzIHZpc3VhbGl6ZSB0aGUgZGVjaXNpb24uDQoNCmBgYHtyfQ0KcGxvdF90cyA9IGZ1bmN0aW9uKGdhbW1hLCBzZSwgYWxwaGEsIGdhbW1hX3RpbGRlPWRyYXdzLCB1bml0ID0gRkFMU0UpIHsNCiAgIyBUaGlzIGlzIHByb2JhYmx5IHRoZSBtb3N0IGNvbXBsaWNhdGVkIGFuZCB1bmVsZWdhbnQgcGxvdCBJIGV2ZXIgY3JlYXRlZCwgYnV0IGl0IG1ha2VzIHRoZSBwb2ludA0KICAjIExldCBtZSBrbm93IGlmIHlvdSBoYXZlIGFuIGFsdGVybmF0aXZlIHRvIHBsb3QgaXQgbW9yZSBlbGVnYW50bHkNCg0KICBoaWdoZXN0X2FybSA8LSB3aGljaC5tYXgoZ2FtbWFfdGlsZGUpDQoNCiAgIyBEYXRhIGZyYW1lIGZvciBwbG90dGluZw0KICBkYXRhIDwtIGRhdGEuZnJhbWUoDQogICAgQXJtID0gMDoxLA0KICAgIEVzdGltYXRlID0gZ2FtbWEsDQogICAgRHJhdyA9IGdhbW1hX3RpbGRlLA0KICAgIHNlID0gc2UgKiBhbHBoYQ0KICApDQoNCiAgcGxvdF9kYXRhIDwtIGRhdGEgJT4lDQogICAgZ3JvdXBfYnkoQXJtKSAlPiUNCiAgICBzbGljZShyZXAoMTpuKCksIGVhY2ggPSAxMDAwMCkpICU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIHkgPSBybm9ybShuKCksIG1lYW4gPSBFc3RpbWF0ZSwgc2QgPSBzZSksDQogICAgKSAlPiUNCiAgICBtdXRhdGUoDQogICAgICBkZW5zID0gZG5vcm0oeSxtZWFuID0gRXN0aW1hdGUsIHNkID0gc2UpLA0KICAgICkgJT4lDQogICAgdW5ncm91cCgpDQoNCiAgIyBQbG90DQogIGcgPSBnZ3Bsb3QocGxvdF9kYXRhLCBhZXMoeT1Bcm0pKSArIA0KICAgIGdlb21fcG9pbnQoZGF0YT1kYXRhLCBhZXMoeT1Bcm0sIHggPSBEcmF3LGNvbG9yPURyYXcpLCBzaXplID0gNSwgc2hhcGUgPSA0LCBzdHJva2U9MSkgKw0KICAgIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdyA9ICJvcmFuZ2UiLCBoaWdoID0gImZvcmVzdGdyZWVuIikgKw0KICAgIGdlb21fcG9pbnQoZGF0YT1kYXRhLCBhZXMoeD1Fc3RpbWF0ZSksIHNpemUgPSAzLCBjb2xvciA9ICJibGFjayIpICsNCiAgICBnZW9tX2RlbnNpdHlfcmlkZ2VzKGRhdGEgPSBwbG90X2RhdGFbcGxvdF9kYXRhJEFybT09IjAiLF0sIGFlcyh5PUFybSx4ID0geSwgaGVpZ2h0ID0gZGVucyksDQogICAgICAgICAgICAgICAgICAgICAgICBhbHBoYT0wLjcsIHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeSA9IDApLCBzdGF0ID0gImlkZW50aXR5IikgKw0KICAgIGdlb21fZGVuc2l0eV9yaWRnZXMoZGF0YSA9IHBsb3RfZGF0YVtwbG90X2RhdGEkQXJtPT0iMSIsXSwgYWVzKHk9QXJtLHggPSB5LCBoZWlnaHQgPSBkZW5zKSwNCiAgICAgICAgICAgICAgICAgICAgICBhbHBoYT0wLjcsIHBvc2l0aW9uID0gcG9zaXRpb25fbnVkZ2UoeSA9IDApLCAgc3RhdCA9ICJpZGVudGl0eSIpICsNCiAgICBsYWJzKHkgPSAiQXJtIC8gTm9ybWFsIGRpc3RyaWJ1dGlvbiIsIHggPSAiRXN0aW1hdGUiLA0KICAgICAgICAgdGl0bGUgPSAiVGhvbXBzb24gU2FtcGxpbmciKSArDQogICAgdGhlbWVfbWluaW1hbCgpICsgc2NhbGVfeV9jb250aW51b3VzKGJyZWFrcyA9IGMoMCwgMSksIGxhYmVscyA9IGMoIjAiLCAiMSIpKSArDQogICAgIyAjIHhsaW0oLTEwLDIwKSArDQogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCkgKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIsIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArDQogICAgY29vcmRfZmxpcCh5bGltID0gYygwLCAzKSx4bGltID0gYygtMTAsIDIwKSkNCg0KICAgIGlmIChpcy5udW1lcmljKHVuaXQpKSBnID0gZyArIGxhYnModGl0bGUgPSBwYXN0ZSgiVGhvbXBzb24gU2FtcGxpbmcgZm9yIHVuaXQiLHVuaXQrMSkpDQogIHByaW50KGcpDQp9DQpwbG90X3RzKGdhbW1hLHNlLDIsZHJhd3MpDQpgYGANCg0KDQoNCk5vdGUgdGhhdCB3aXRoIHRoaXMgdmVyeSBzZWVkLCBVQ0IgYW5kIFRob21wc29uIHNhbXBsaW5nIGRvIG5vdCB0YWtlIHRoZSBzYW1lIGNob2ljZSAkXFJpZ2h0YXJyb3ckIHRoZWlyIHRyZWF0bWVudCBzZXF1ZW5jZXMgZGlmZmVyLiBOb3RlIHRoYXQgdGhlIGRlY2lzaW9uIG9mIFVDQiBpcyBkZXRlcm1pbmlzdGljLCB3aGlsZSBpdCBpcyByYW5kb20gZm9yIFRob21wc29uIHNhbXBsaW5nLg0KDQo8YnI+DQoNCiMjIER5bmFtaWMgdHJlYXRtZW50IGFzc2lnbm1lbnQgKFVDQikNCg0KRmlyc3QsIHByb2NlZWQgd2l0aCBVQ0IgYW5kIGRlZmluZSBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMNCg0KLSAgIG9ic2VydmVkIG91dGNvbWVzDQoNCi0gICBhc3NpZ25lZCB0cmVhdG1lbnRzDQoNCi0gICBhbmQgdGhlIHR1bmluZyBwYXJhbWV0ZXIgJFxhbHBoYSQgYXMgYXJndW1lbnRzDQoNCmFuZCByZXR1cm5zIGEgdHJlYXRtZW50IGFzc2lnbm1lbnQgZm9yIHRoZSBuZXh0IGluZGl2aWR1YWw6DQoNCmBgYHtyfQ0KdWNiID0gZnVuY3Rpb24oWSwgVywgYWxwaGEsIHBsb3QgPSBGQUxTRSl7DQogIGdhbW1hID0gYyhtZWFuKFlbVz09MF0pLCBtZWFuKFlbVz09MV0pKQ0KICBzZSA9IGMoc2QoWVtXPT0wXSkgLyBzcXJ0KHN1bShXPT0wKSksIHNkKFlbVz09MV0pIC8gc3FydChzdW0oVz09MSkpKQ0KICBhc3NpZ24gPSB3aGljaC5tYXgoZ2FtbWEgKyBhbHBoYSAqIHNlKQ0KICBpZiAoaXNUUlVFKHBsb3QpKSBwbG90X3VjYihnYW1tYSxzZSwyLCB1bml0ID0gbGVuZ3RoKFkpKQ0KICByZXR1cm4oYXNzaWduLTEpDQp9DQpgYGANCg0KTm93IHdlIHJ1biBVQ0IgdW50aWwgJGk9MTAwMCQgYW5kIHBsb3QgdGhlIGV2b2x1dGlvbjoNCg0KYGBge3J9DQojIFN0b3JlIHRvIHVzZSBpdCBmb3IgVGhvbXBzb24gYmVsb3cNCldzdGFydCA9IFcNCllzdGFydCA9IFkNCg0KcGxvdF9ncmlkID0gYyg1OjEwLDIwLDUwLDEwMCwyMDAsNTAwLDEwMDApDQpmb3IgKGkgaW4gNToxMDAwKSB7DQogICMgQXNzaWduIHVuaXQgdG8gdHJlYXRtZW50IChhbmQgc29tZXRpbWVzIHBsb3QpDQogIGlmIChpICVpbiUgcGxvdF9ncmlkKSBXaSA9IHVjYihZLFcsYWxwaGEscGxvdCA9IFRSVUUpDQogIGVsc2UgV2kgPSB1Y2IoWSxXLGFscGhhLHBsb3QgPSBGQUxTRSkNCiAgVyA9IGMoVyxXaSkNCiAgIyBhbmQgb2JzZXJ2ZSB0aGUgcG90ZW50aWFsIG91dGNvbWUgdW5kZXIgdHJlYXRtZW50DQogIFkgPSBjKFksKDEtV2kpICogWTBbaV0gKyBXaSAqIFkxW2ldKQ0KfQ0KYGBgDQoNCmFkZGl0aW9uYWxseSBwbG90IHRoZSBzaGFyZSBiZWluZyBhc3NpZ25lZCB0byB0aGUgb3B0aW1hbCBwb2xpY3kNCg0KYGBge3J9DQpzaGFyZSA9IGN1bXN1bShXKSAvIDE6bg0KcGxvdCgxOm4sc2hhcmUpDQpgYGANCg0KYW5kIHRoZSByZWdyZXQNCg0KYGBge3J9DQpyZWdyZXQgPSBjdW1zdW0oZ2FtbWExIC0gKFcgKiBnYW1tYTEgKyAoMS1XKSAqIGdhbW1hMCkgKSAgLyAxOm4NCnBsb3QoMTpuLHJlZ3JldCkNCmBgYA0KDQpXZSBvYnNlcnZlIHRoYXQgYXMgb2JzZXJ2YXRpb25zIGZsb3cgaW4sIFVDQiBmaWd1cmVzIG91dCB0aGF0IHRyZWF0bWVudCBvbmUgaXMgZG9taW5hbnQgYW5kIHN0YXJ0cyB0byBzb2xlbHkgYXNzaWduIGl0LiBTdWNoIHRoYXQgYXQgdGhlIGVuZCBvZiB0aGUgMTAwMCBvYnNlcnZhdGlvbnMgcmVncmV0IGlzIG5lZ2xpZ2libGUuIE5vdGUgdGhhdCB0aGUgcmVncmV0IG9mIGEgNTA6NTAgZXhwZXJpbWVudCB3b3VsZCBoYXZlIGJlZW4gMC4yNS4NCg0KPGJyPg0KDQojIyBEeW5hbWljIHRyZWF0bWVudCBhc3NpZ25tZW50IChUaG9tcHNvbikNCg0KV3JpdGUgYSBmdW5jdGlvbiBzaW1pbGFyIHRvIGB1Y2JgIGFib3ZlOg0KDQpgYGB7cn0NCnRob21wc29uID0gZnVuY3Rpb24oWSwgVywgYWxwaGEsIHBsb3QgPSBGQUxTRSl7DQogIGdhbW1hID0gYyhtZWFuKFlbVz09MF0pLCBtZWFuKFlbVz09MV0pKQ0KICBzZSA9IGMoc2QoWVtXPT0wXSkgLyBzcXJ0KHN1bShXPT0wKSksIHNkKFlbVz09MV0pIC8gc3FydChzdW0oVz09MSkpKQ0KICBkcmF3cyA9IGMocm5vcm0oMSwgZ2FtbWFbMV0sIGFscGhhICogc2VbMV0pLA0KICAgICAgICAgIHJub3JtKDEsIGdhbW1hWzJdLCBhbHBoYSAqIHNlWzJdKSkNCiAgYXNzaWduID0gd2hpY2gubWF4KGRyYXdzKQ0KICBpZiAoaXNUUlVFKHBsb3QpKSBwbG90X3RzKGdhbW1hLHNlLDIsZHJhd3MsdW5pdCA9IGxlbmd0aChZKSkNCiAgcmV0dXJuKGFzc2lnbi0xKQ0KfQ0KYGBgDQoNCk5vdyB3ZSBydW4gVGhvbXBzb24gdW50aWwgJGk9MTAwMCQgYW5kIHBsb3QgdGhlIGV2b2x1dGlvbjoNCg0KYGBge3J9DQojIFJlc3RvcmUgc3RhcnRpbmcgcG9pbnRzDQpXID0gV3N0YXJ0DQpZID0gWXN0YXJ0DQoNCnBsb3RfZ3JpZCA9IGMoNToxMCwyMCw1MCwxMDAsMjAwLDUwMCwxMDAwKQ0KZm9yIChpIGluIDU6MTAwMCkgew0KICAjIEFzc2lnbiB1bml0IHRvIHRyZWF0bWVudCAoYW5kIHNvbWV0aW1lcyBwbG90KQ0KICBpZiAoaSAlaW4lIHBsb3RfZ3JpZCkgV2kgPSB0aG9tcHNvbihZLFcsYWxwaGEscGxvdCA9IFRSVUUpDQogIGVsc2UgV2kgPSB0aG9tcHNvbihZLFcsYWxwaGEscGxvdCA9IEZBTFNFKQ0KICBXID0gYyhXLFdpKQ0KICAjIGFuZCBvYnNlcnZlIHRoZSBwb3RlbnRpYWwgb3V0Y29tZSB1bmRlciB0cmVhdG1lbnQNCiAgWSA9IGMoWSwoMS1XaSkgKiBZMFtpXSArIFdpICogWTFbaV0pDQp9DQpgYGANCg0KYWRkaXRpb25hbGx5IHBsb3QgdGhlIHNoYXJlIGJlaW5nIGFzc2lnbmVkIHRvIHRoZSBvcHRpbWFsIHBvbGljeQ0KDQpgYGB7cn0NCnNoYXJlID0gY3Vtc3VtKFcpIC8gMTpuDQpwbG90KDE6bixzaGFyZSkNCmBgYA0KDQphbmQgdGhlIHJlZ3JldA0KDQpgYGB7cn0NCnJlZ3JldCA9IGN1bXN1bShnYW1tYTEgLSAoVyAqIGdhbW1hMSArICgxLVcpICogZ2FtbWEwKSApICAvIDE6bg0KcGxvdCgxOm4scmVncmV0KQ0KYGBgDQoNCkFnYWluIHdlIG9ic2VydmUgdGhhdCBhcyBvYnNlcnZhdGlvbnMgZmxvdyBpbiwgVGhvbXBzb24gc2FtcGxpbmcgZmlndXJlcyBvdXQgdGhhdCB0cmVhdG1lbnQgb25lIGlzIGRvbWluYW50IGFuZCBzdGFydHMgdG8gc29sZWx5IGFzc2lnbiBpdC4gU3VjaCB0aGF0IGF0IHRoZSBlbmQgb2YgdGhlIDEwMDAgb2JzZXJ2YXRpb25zIHJlZ3JldCBpcyBuZWdsaWdpYmxlLiBOb3RlIHRoYXQgdGhlIHJlZ3JldCBvZiBhIDUwOjUwIGV4cGVyaW1lbnQgd291bGQgaGF2ZSBiZWVuIDAuMjUuDQoNCjxicj4NCg0KIyMgU2ltdWxhdGlvbiBzdHVkeSBVQ0INCg0KV2Ugbm93IHJ1biB0aGlzIDEwMCB0aW1lcyBlYWNoIGZvciBhIHNlcXVlbmNlIG9mIHZhbHVlcyBvZiB0aGUgdHVuaW5nIHBhcmFtZXRlcnMgYGFscGhhID0gYygwLDAuNSwxLDEuNSwyLDIuNSwzLDQsNSwxMCwxMDAwKWAuIFdlIHRyYWNrIHRoZSBmb2xsb3dpbmcgcXVhbnRpdGllczoNCg0KLSAgIEF2ZXJhZ2UgcmVncmV0IGF0ICRpPTEwJCwgJGk9MTAwJCBhbmQgJGk9MTAwMCQNCg0KLSAgIFByb2JhYmlsaXR5IHRvIGFzc2lnbiB0aGUgb3B0aW1hbCB0cmVhdG1lbnQgYXQgJGk9MTAkLCAkaT0xMDAkIGFuZCAkaT0xMDAwJA0KDQpgYGB7cn0NCmFscGhhID0gYygwLDAuNSwxLDEuNSwyLDIuNSwzLDQsNSwxMCwxMDAwKQ0KDQpyZXBzID0gMTAwMCAjIFJlZHVjZSB0byAxMDAgaWYgeW91IGhhdmUgbm8gdGltZQ0KDQp0ZW1wID0gbWF0cml4KE5BLHJlcHMsNikNCnJlc3VsdHMgPSBtYXRyaXgoTkEsbGVuZ3RoKGFscGhhKSw2KQ0KY29sbmFtZXMocmVzdWx0cykgPSBjKCJyZWdyZXQxMCIsInJlZ3JldDEwMCIsInJlZ3JldDEwMDAiLCJvcHRpbWFsMTAiLCJvcHRpbWFsMTAwIiwib3B0aW1hbDEwMDAiKQ0KDQpmb3IgKGEgaW4gMTpsZW5ndGgoYWxwaGEpKSB7DQogIGZvciAoaiBpbiAxOnJlcHMpIHsNCiAgICAjIERyYXcgc2FtcGxlDQogICAgWTAgPSBybm9ybShuLGdhbW1hMCxzZDApDQogICAgWTEgPSBybm9ybShuLGdhbW1hMSxzZDEpDQoNCiAgICAjIEdldCBraWNrLXN0YXJ0DQogICAgVyA9IGMoMCwxLDAsMSkNCiAgICBZID0gKDEtVykgKiBZMFsxOjRdICsgVyAqIFkxWzE6NF0NCg0KICAgICMgUnVuIFVDQg0KICAgIGZvciAoaSBpbiA1Om4pIHsNCiAgICAgICMgQXNzaWduIGluZGl2aWR1YWwgNCB0byB0cmVhdG1lbnQgb25lDQogICAgICBXaSA9IHVjYihZLFcsYWxwaGFbYV0pDQogICAgICBXID0gYyhXLFdpKQ0KICAgICAgIyBhbmQgb2JzZXJ2ZSB0aGUgcG90ZW50aWFsIG91dGNvbWUgdW5kZXIgdHJlYXRtZW50DQogICAgICBZID0gYyhZLCgxLVdpKSAqIFkwW2ldICsgV2kgKiBZMVtpXSkNCiAgICB9DQogICAgcmVncmV0ID0gY3Vtc3VtKGdhbW1hMSAtIChXICogZ2FtbWExICsgKDEtVykgKiBnYW1tYTApICkgIC8gMTpuDQogICAgdGVtcFtqLDFdID0gcmVncmV0WzEwXQ0KICAgIHRlbXBbaiwyXSA9IHJlZ3JldFsxMDBdDQogICAgdGVtcFtqLDNdID0gcmVncmV0WzEwMDBdDQogICAgdGVtcFtqLDRdID0gV1sxMF0NCiAgICB0ZW1wW2osNV0gPSBXWzEwMF0NCiAgICB0ZW1wW2osNl0gPSBXWzEwMDBdDQogIH0NCiByZXN1bHRzW2EsXSA9IGNvbE1lYW5zKHRlbXApDQp9DQoNCmFzX3RpYmJsZShjYmluZChhbHBoYSwgcmVzdWx0c1ssMTozXSkpICU+JSBwaXZvdF9sb25nZXIoIWFscGhhLG5hbWVzX3RvID0gImluZGl2aWR1YWwiLCB2YWx1ZXNfdG8gPSAicmVncmV0IikgJT4lDQogIGdncGxvdChhZXMoZmFjdG9yKGFscGhhKSwgcmVncmV0LCBjb2xvcj1hcy5mYWN0b3IoaW5kaXZpZHVhbCkpKSArDQogIGdlb21fbGluZShhZXMoZ3JvdXA9ZmFjdG9yKGluZGl2aWR1YWwpKSkgKw0KICBsYWJzKHggPSAiYWxwaGEiLCBjb2xvciA9ICJQb3NpdGlvbiIpICsgdGhlbWVfYncoKQ0KDQphc190aWJibGUoY2JpbmQoYWxwaGEsIHJlc3VsdHNbLDQ6Nl0pKSAlPiUgcGl2b3RfbG9uZ2VyKCFhbHBoYSxuYW1lc190byA9ICJpbmRpdmlkdWFsIiwgdmFsdWVzX3RvID0gIm9wdGltYWwiKSAlPiUNCiAgZ2dwbG90KGFlcyhmYWN0b3IoYWxwaGEpLCBvcHRpbWFsLCBjb2xvcj1hcy5mYWN0b3IoaW5kaXZpZHVhbCkpKSArDQogIGdlb21fbGluZShhZXMoZ3JvdXA9ZmFjdG9yKGluZGl2aWR1YWwpKSkgKw0KICBsYWJzKHggPSAiYWxwaGEiLCB5ID0gIlByb2Igb2Ygb3B0aW1hbCBjaG9pY2UiLCBjb2xvciA9ICJQb3NpdGlvbiIpICsgdGhlbWVfYncoKQ0KYGBgDQoNCldlIG9ic2VydmUgdGhhdCBhIHZhbHVlIG9mICRcYWxwaGEgPSAzJCBtaW5pbWl6ZXMgcmVncmV0IGluIHRoZSBsb25nIHJ1biBhbmQgbWF4aW1pemVzIHRoZSBwcm9iYWJpbGl0eSB0byBhc3NpZ24gdGhlIGJlc3QgdHJlYXRtZW50IHRvIGluZGl2aWR1YWwgMTAwMC4gVmVyeSBsb3cgdmFsdWVzIG9mICRcYWxwaGEkIHJlc3VsdCBpbiB0b28gbGl0dGxlIGV4cGxvcmF0aW9uLiBUaGlzIG1lYW5zIHRoZSBiYW5kaXQgbWlnaHQgY29tbWl0IGVhcmx5IG9uIHRvIHRoZSB3cm9uZyB0cmVhdG1lbnQuIE9uIHRoZSBvdGhlciBoYW5kLCB2ZXJ5IGhpZ2ggdmFsdWVzIG9mICRcYWxwaGEkIGV4cGxvcmUgdG9vIG11Y2guICRcYWxwaGEgPSAxMDAwJCBpcyBhIHZlcnkgZXh0cmVtZSBjYXNlLCB3aGljaCBpcyBuZWFybHkgZXF1aXZhbGVudCB0byBydW5uaW5nIGEgNTA6NTAgZXhwZXJpbWVudC4gVGhlIGJhbmRpdCBpZ25vcmVzIGJhc2ljYWxseSBhbnkga25vd2xlZGdlIGFib3V0IHRoZSBiZXR0ZXIgdHJlYXRtZW50Lg0KDQo8YnI+DQoNCiMjIFNpbXVsYXRpb24gc3R1ZHkgVGhvbXBzb24gc2FtcGxpbmcNCg0KVXNlIHRoZSBUaG9tcHNvbiBmdW5jdGlvbiBpbiB0aGUgc2FtZSBsb29wIGFzIGJlZm9yZToNCg0KYGBge3J9DQphbHBoYSA9IGMoMCwwLjUsMSwxLjUsMiwyLjUsMyw0LDUsMTAsMTAwMCkNCg0KcmVwcyA9IDEwMDAgIyBSZWR1Y2UgdG8gMTAwIGlmIHlvdSBoYXZlIG5vIHRpbWUNCg0KdGVtcCA9IG1hdHJpeChOQSxyZXBzLDYpDQpyZXN1bHRzID0gbWF0cml4KE5BLGxlbmd0aChhbHBoYSksNikNCmNvbG5hbWVzKHJlc3VsdHMpID0gYygicmVncmV0MTAiLCJyZWdyZXQxMDAiLCJyZWdyZXQxMDAwIiwib3B0aW1hbDEwIiwib3B0aW1hbDEwMCIsIm9wdGltYWwxMDAwIikNCg0KZm9yIChhIGluIDE6bGVuZ3RoKGFscGhhKSkgew0KICBmb3IgKGogaW4gMTpyZXBzKSB7DQogICAgIyBEcmF3IHNhbXBsZQ0KICAgIFkwID0gcm5vcm0obixnYW1tYTAsc2QwKQ0KICAgIFkxID0gcm5vcm0obixnYW1tYTEsc2QxKQ0KDQogICAgIyBHZXQga2ljay1zdGFydA0KICAgIFcgPSBjKDAsMSwwLDEpDQogICAgWSA9ICgxLVcpICogWTBbMTo0XSArIFcgKiBZMVsxOjRdDQoNCiAgICAjIFJ1biBVQ0INCiAgICBmb3IgKGkgaW4gNTpuKSB7DQogICAgICAjIEFzc2lnbiBpbmRpdmlkdWFsIDQgdG8gdHJlYXRtZW50IG9uZQ0KICAgICAgV2kgPSB0aG9tcHNvbihZLFcsYWxwaGFbYV0pDQogICAgICBXID0gYyhXLFdpKQ0KICAgICAgIyBhbmQgb2JzZXJ2ZSB0aGUgcG90ZW50aWFsIG91dGNvbWUgdW5kZXIgdHJlYXRtZW50DQogICAgICBZID0gYyhZLCgxLVdpKSAqIFkwW2ldICsgV2kgKiBZMVtpXSkNCiAgICB9DQogICAgcmVncmV0ID0gY3Vtc3VtKGdhbW1hMSAtIChXICogZ2FtbWExICsgKDEtVykgKiBnYW1tYTApICkgIC8gMTpuDQogICAgdGVtcFtqLDFdID0gcmVncmV0WzEwXQ0KICAgIHRlbXBbaiwyXSA9IHJlZ3JldFsxMDBdDQogICAgdGVtcFtqLDNdID0gcmVncmV0WzEwMDBdDQogICAgdGVtcFtqLDRdID0gV1sxMF0NCiAgICB0ZW1wW2osNV0gPSBXWzEwMF0NCiAgICB0ZW1wW2osNl0gPSBXWzEwMDBdDQogIH0NCiByZXN1bHRzW2EsXSA9IGNvbE1lYW5zKHRlbXApDQp9DQoNCmFzX3RpYmJsZShjYmluZChhbHBoYSwgcmVzdWx0c1ssMTozXSkpICU+JSBwaXZvdF9sb25nZXIoIWFscGhhLG5hbWVzX3RvID0gImluZGl2aWR1YWwiLCB2YWx1ZXNfdG8gPSAicmVncmV0IikgJT4lDQogIGdncGxvdChhZXMoZmFjdG9yKGFscGhhKSwgcmVncmV0LCBjb2xvcj1hcy5mYWN0b3IoaW5kaXZpZHVhbCkpKSArDQogIGdlb21fbGluZShhZXMoZ3JvdXA9ZmFjdG9yKGluZGl2aWR1YWwpKSkgKw0KICBsYWJzKHggPSAiYWxwaGEiLCBjb2xvciA9ICJQb3NpdGlvbiIpICsgdGhlbWVfYncoKQ0KDQphc190aWJibGUoY2JpbmQoYWxwaGEsIHJlc3VsdHNbLDQ6Nl0pKSAlPiUgcGl2b3RfbG9uZ2VyKCFhbHBoYSxuYW1lc190byA9ICJpbmRpdmlkdWFsIiwgdmFsdWVzX3RvID0gIm9wdGltYWwiKSAlPiUNCiAgZ2dwbG90KGFlcyhmYWN0b3IoYWxwaGEpLCBvcHRpbWFsLCBjb2xvcj1hcy5mYWN0b3IoaW5kaXZpZHVhbCkpKSArDQogIGdlb21fbGluZShhZXMoZ3JvdXA9ZmFjdG9yKGluZGl2aWR1YWwpKSkgKw0KICBsYWJzKHggPSAiYWxwaGEiLCB5ID0gIlByb2Igb2Ygb3B0aW1hbCBjaG9pY2UiLCBjb2xvciA9ICJQb3NpdGlvbiIpICsgdGhlbWVfYncoKQ0KYGBgDQoNCldlIHNlZSB0aGUgc2FtZSBleHBsb3JhdGlvbiB2cy4gZXhwbG9pdGF0aW9uIHRyYWRlLW9mZiBhcyBmb3IgVUNCLiBIb3dldmVyLCB0aGUgb3B0aW1hbCAkXGFscGhhID0gMSQgYW5kIFRob21wc29uIHNhbXBsaW5nIG1hbmFnZXMgdG8gcmVhbGl6ZSBhIHNtYWxsZXIgYXZlcmFnZSByZWdyZXQgb2YgYXJvdW5kIDAuMDUsIHdoaWxlIFVDQiBoYWQgb25seSAwLjA3LCBhdCBsZWFzdCB3aXRoIHRoaXMgdmVyeSBjb2Fyc2UgZ3JpZC4NCg0KPGJyPg0KDQojIyBQb3RlbnRpYWwgZXh0ZW5zaW9ucw0KDQotICAgQ2hhbmdlIHRoZSBtZWFuIGFuZC9vciB2YXJpYW5jZSBvZiB0aGUgdHJlYXRtZW50IGFuZCBjb250cm9sIGdyb3VwLiBGaXJzdCB0aGluayBhYm91dCB3aGF0IHlvdSBleHBlY3QgYW5kIHRoZW4gdGVzdCB5b3VyIGludHVpdGlvbiBieSBydW5uaW5nIHRoZSBtb2RpZmllZCBub3RlYm9vay4NCg==