Goals:

  • Handcode UCB and Thompson sampling in stylized DGP


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

  • observed outcomes

  • assigned treatments

  • and the tuning parameter \(\alpha\) as arguments

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==