1 Overview

Sensitivity analysis is the process of determining how sensitive a model is to

  • Changes in parameter values
  • Changes in the structure of the model

By showing how the model respond to these changes, sensitivity analysis is useful in

  • Model building
  • Model evaluation

Sensitivity analysis is part of the iterative development cycle of a system dynamic model.

2 Parameter sensitivity

It is often challenging for a modeller to chose a unique value for their model parameters, since in the real world:

  • Many model parameters are difficult or even impossible to measure with a good degree of certainty.
  • Parameter values may change over time.

Therefore, often the modeller cannot be certain about the value chosen for a parameter and has to use estimates. Sensitivity analysis allows the modeller to determine what the reasonable ranges for those estimates are:

  • Greater precision is needed for parameters that the model is sensitive to
  • Less precision is needed for parameters that the model is insensitive to

By comparing model output to real world observations, sensitivity analysis helps the modeller to choose reasonable values for parameters.

2.1 Sampling

Each input parameter of a model can be defined to have an appropriate probability density function (pdf). Then, the model can be simulated by sampling a single value from each parameter’s distribution. Many samples should be taken (and for stochastic models many simulations should be run per sample), producing variable output values.

Sampling values for one parameter is straightforward, but as the dimension of the parameter space increases, the complexity increases exponentially, which can quite easily exceed the available computing resources you have available.

There are various approaches that could be taken to sample from the parameter distributions. In a 2-dimensional parameter space:

2.1.1 Latin hypercube sampling with the lhs package in R

Let’s sample two parameters following a uniform distribution:

# install.packages("lhs")
library("deSolve")
library("ggplot2") #; library(ggsci)
library("gridExtra")
library("scales") # label_percent()

library("lhs")
set.seed(10)
A <- lhs::randomLHS(5, 2) # 5 samples, 2 parameters
gA <- ggplot(data = data.frame(A), mapping = aes( x = X1, y = X2)) +
  geom_point() + coord_fixed() +
  scale_x_continuous(limits = c(0,1), breaks = seq(0, 1, by = 0.2)) +
  scale_y_continuous(limits = c(0,1), breaks = seq(0, 1, by = 0.2)) +
  labs(title = "A <- lhs::randomLHS(5, 2)")

Expand on existing sample

A2 <- lhs::augmentLHS(A, 5) # 5 more samples on top of A
A2_new <- A2[!(A2[,1] %in% A[,1] ),]
gA_exp <- ggplot(mapping = aes( x = X1, y = X2)) +
  geom_point(data = data.frame(A)) +
  geom_point(data = data.frame(A2_new), color = "#00A087FF") +
  coord_fixed() +
  scale_x_continuous(limits = c(0,1), breaks = seq(0, 1, by = 0.2)) +
  scale_y_continuous(limits = c(0,1), breaks = seq(0, 1, by = 0.2)) +
  theme(legend.position="none") +
  labs(title = "A2 <- lhs::augmentLHS(A, 5)")
grid.arrange(gA, gA_exp, ncol = 2)

What if X1 is normally distributed? Note that x2 is still follows a uniform distribution.

B <- lhs::randomLHS(1000, 2) # 1000 samples of 2 parameters
B[,1] <- qnorm(B[,1], mean = 7, sd = 1) # X1 is normally distributed
ggplot(data = data.frame(B)) +
  geom_point(mapping = aes( x = X1, y = X2)) +
  geom_density(mapping = aes(x = X1), color = "#00A087FF", size = 2) +
  scale_x_continuous(limits = c(2,12), breaks = seq(2, 12, by = 1)) +
  scale_y_continuous(limits = c(0,1), breaks = seq(0, 1, by = 0.2))

2.1.2 Sampling parameter sets with the sensitivity package in R

The sensitivity::parameterSets function provides three methods of sampling:

  1. Method “sobol” generates uniformly distributed random numbers, using the sobol function in the randtoolbox package.
library(randtoolbox)
X.sobol <- sensitivity::parameterSets(
  par.ranges = list(X1 = c(1,1000), X2 = c(1,4)),
  samples = 100,
  method = "sobol"
)
plot(X.sobol)

  1. Method “grid” generates a grid within the parameter ranges, including its extremes, with number of points determined by samples
  2. Method “innergrid” generates a grid within the parameter ranges, with edges of the grid offset from the extremes.
library("sensitivity")

X.grid <- sensitivity::parameterSets(
  par.ranges = list(X1 = c(1,1000), X2 = c(1,4)),
  samples = c(8,10),
  method = "grid"
)
plot(X.grid)

X.innergrid <- sensitivity::parameterSets(
  par.ranges = list(X1 = c(1,1000), X2 = c(1,4)),
  samples = c(8,10),
  method = "innergrid"
)
points(X.innergrid, col="red")

2.2 Example of parameter sampling in an SEIR model

2.2.1 Model definition

rm(list = ls())

library("deSolve")
library("ggplot2") #; library(ggsci)
library("gridExtra")
library("scales") # label_percent()

seir_dt <- function(t, state, parameters){
  with(as.list(c(state, parameters)), {
    theta2 <- theta
    if (lk.on) {
      if (t > lk.t && t < (lk.t + lk.len)) {
        theta2 <- theta * lk.eff
      }
    }

    beta <- theta2 * p
    sigma <- 1/sigmad
    gamma <- 1/gammad

    dSdt = mu - beta*I*S - mu*S
    dEdt = beta*I*S - (sigma+mu)*E
    dIdt = sigma*E - (gamma+mu)*I
    dRdt = gamma*I - mu*R
    return(list(c(dSdt, dEdt, dIdt, dRdt)))
  })  
}

plot_out <- function(out.df) {
  i_max <- out.df[which.max(out.df[["I"]]),]
  ggplot(data = out.df, mapping = aes(x = time))+
    geom_line(mapping = aes(y = S, color = "S")) +
    geom_line(mapping = aes(y = E, color = "E")) +
    geom_line(mapping = aes(y = I, color = "I")) +
    geom_line(mapping = aes(y = R, color = "R")) +
    geom_point(data = i_max, mapping = aes(y = I), color = "#DC0000FF") +
    annotate(
      geom = "text",
      x = i_max[["time"]] + 20, 
      y = i_max[["I"]] + 0.05, 
      label = paste(i_max$time, label_percent()(i_max$I), sep = ", ")
    ) +
    scale_color_manual(name="", values = c("S" = "#3C5488FF", "E"="#F39B7FFF", "I"="#DC0000FF", "R"="#00A087FF")) +
    labs(x = "Time", y = "Population (%)")
}
# Set parameter estimates
# beta <- 520/365; # beta = contact_rate * transmission_risk
# sigma <- 1/60; # 1/latent_days
# gamma <- 1/30; # 1/infectious_days
theta_est <- 3
p_est <- 0.15
sigmad_est <- 7 # 1/latent_days
gammad_est <- 24 # 1/infectious_days
# sigmad_est <- 11 # 1/latent_days
# gammad_est <- 7 # 1/infectious_days
mu_est <- 712680/(66650000*365) # UK birth and population figures 2019

# Lockdown
# lk.on_est <- FALSE
lk.t_est <- 15
lk.len_est <- 90
lk.eff_est <- 0.2

init_state <- c(S = 0.98, E = 0.01, I = 0.01, R = 0.0)
timeline <- seq(0,365)

p0 <- c(theta = theta_est, p = p_est, sigmad = sigmad_est, gammad = gammad_est, mu = mu_est,
  lk.on = FALSE, lk.t = lk.t_est, lk.len = lk.len_est, lk.eff = lk.eff_est)

out <- ode(y = init_state, times = timeline, func = seir_dt, parms = p0)
out.df <- data.frame(out)
plot_out(out.df)

2.2.2 With intervention


p1 <- c(theta = theta_est, p = p_est, sigmad = sigmad_est, gammad = gammad_est, mu = mu_est,
  lk.on = TRUE, lk.t = lk.t_est, lk.len = lk.len_est, lk.eff = lk.eff_est)

out <- ode(y = init_state, times = timeline, func = seir_dt, parms = p1)
out.df <- data.frame(out)
i_peak <- out.df[which.max(out.df[["I"]]),]
plot_out(out.df)

2.2.3 Sampling

seir_fun <- function(parameters){
  with(as.list(c(parameters)), {
    out <- ode(y = init_state, times = timeline, func = seir_dt, parms = parameters)
    out.df <- data.frame(out)
    i_peak <- out.df[which.max(out.df[["I"]]),]
    return(as.numeric(i_peak[["I"]]))
  })
} 

p_set <- sensitivity::parameterSets(
    par.ranges = list(
      theta = c(2, 5),
      p = c(0.1, 0.2),
      sigmad = c(5, 14),
      gammad = c(20, 25),
      mu = c(mu_est, mu_est),
      lk.t = c(30,60),
      lk.len = c(30,120),
      lk.eff = c(0.1, 0.8)
    ),
    samples = c(
      3, 5, 3, 3,
      1, 3, 4, 5
    ), # 8100
    method = "grid"
)

unique(p_set[,"lk.len"])
[1]  30  60  90 120
seir_fun(c(p_set[20,], lk.on = TRUE))
[1] 0.3189042

2.2.4 Run model per sample of parameter set

f_name <- "results.df.rds"
results.df <- data.frame(p_set)
results.df[["imax"]] <- 0.0

if (!file.exists(f_name)) {
  start_time <- Sys.time()  
  for (rr in 1:nrow(p_set) ) {
  # for (rr in 1:1000 ) {
    pp <- c(p_set[rr,], lk.on = TRUE)
    results.df[rr,"imax"] <- seir_fun(pp)
  }
  end_time <- Sys.time()
  end_time - start_time # Time difference of 2.52515 mins
  saveRDS(results.df, file = f_name)
}

Here’s one I made earlier

results.df <- readRDS(f_name)
p_best <- results.df[which.min(results.df[["imax"]]),]
p_best
plot_out(data.frame(
  ode(y = init_state, times = timeline, func = seir_dt, parms = c(p_best, lk.on = TRUE) )))

2.2.5 Sensitivity analysis


fs <- c("theta", "p", "sigmad", "gammad", "mu", "lk.t", "lk.len", "lk.eff")
results.df.ft <- results.df
results.df.ft[fs] <- lapply(results.df[fs], factor)

gTheta <- ggplot(data = results.df.ft, aes(x = theta, y = imax)) + geom_boxplot()
gP <- ggplot(data = results.df.ft, aes(x =p, y = imax)) + geom_boxplot()
gSigmad <- ggplot(data = results.df.ft, aes(x = sigmad, y = imax)) + geom_boxplot()
gGammad <- ggplot(data = results.df.ft, aes(x = gammad, y = imax)) + geom_boxplot()
gLkt <- ggplot(data = results.df.ft, aes(x = lk.t, y = imax)) + geom_boxplot()
gLklen <- ggplot(data = results.df.ft, aes(x = lk.len, y = imax)) + geom_boxplot()
gLkeff <- ggplot(data = results.df.ft, aes(x = lk.eff, y = imax)) + geom_boxplot()

grid.arrange(gTheta, gP, gSigmad, gGammad, gLkt, gLklen, gLkeff, ncol = 4)

library(ggsci)
ggplot(data = results.df.ft, aes(x = lk.len, y = imax, fill = p)) +
  geom_boxplot() +
  facet_grid(lk.t~theta, labeller = label_both) +
  scale_fill_npg()

#ggplot(data = results.df, aes(x = p, y = theta, color = imax)) + geom_point() + scale_color_gradient(low="blue", high="red")

3 Model structural uncertainty

… We argue that if we lack detailed knowledge of the biology of the transmission process, parameter estimation should be accompanied by a structural sensitivity analysis, in addition to the standard statistical uncertainty analysis. …

Lloyd AL. Sensitivity of Model-Based Epidemiological Parameter Estimation to Model Assumptions. Mathematical and Statistical Estimation Approaches in Epidemiology. 2009;123-141. doi:10.1007/978-90-481-2313-1_6

3.1 Inclusion of latency (SIR > SEIR)

Impact of latency on a disease outbreak Lloyd AL. 2009

3.2 Gamma distributed latent period

… An individual’s chance of recovery is not constant over time: typically, the recovery rate increases over time. In terms of a mathematical model, this leads to the complication that the times at which different individuals became infected must be tracked. … A mathematical trick allows the inclusion of non-exponential distributions within the compartmental framework. … Lloyd AL. 2009

We now look at how to track the times at which different individuals became exposed. By subdividing the E compartment into m stages:

Different implementations of the E compartment The progressions between stages occur at constant rate, leading to an exponential waiting time in each stage. The overall latent period is described by the sum of these m independent exponential distributions. i.e. latent periods are now gamma distributed with shape parameter m.

  • The sum of k exponential (λ) random variables is a gamma (k, λ) random variable.
  • λ is rate of events (λ=1/θ, θ being the mean wait time)
  • Mean of exponential distribution of rate λ is 1/λ
  • Mean of gamma distribution, k events of rate λ, is k/λ
  • The exponential distribution predicts the wait time until the very first event.
  • The gamma distribution, on the other hand, predicts the wait time until the k-th event occurs.

Gamma Distribution — Intuition, Derivation, and Examples

  • k in out example is the number of subdivided compartments in E which is m
  • λ in our example is the incubation rate σ

3.2.1 Example SEmIR model

se7ir_dt <- function(t, state, parameters){
  with(as.list(c(state, parameters)), {
    theta2 <- theta
    if (lk.on) {
      if (t > lk.t && t < (lk.t + lk.len)) {
        theta2 <- theta * lk.eff
      }
    }

    beta <- theta2 * p
    sigma <- 1/sigmad
    gamma <- 1/gammad

    m <- 7

    dSdt = mu - beta*I*S - mu*S
    # dEdt = beta*I*S - (sigma+mu)*E
    dE1dt = beta*I*S - (m*sigma+mu)*E1
    dE2dt = m*sigma*E1 - (m*sigma+mu)*E2
    dE3dt = m*sigma*E2 - (m*sigma+mu)*E3
    dE4dt = m*sigma*E3 - (m*sigma+mu)*E4
    dE5dt = m*sigma*E4 - (m*sigma+mu)*E5
    dE6dt = m*sigma*E5 - (m*sigma+mu)*E6
    dE7dt = m*sigma*E6 - (m*sigma+mu)*E7
    dIdt = m*sigma*E7 - (gamma+mu)*I
    dRdt = gamma*I - mu*R
    return(list(c(
      dSdt,
      dE1dt,
      dE2dt,
      dE3dt,
      dE4dt,
      dE5dt,
      dE6dt,
      dE7dt,
      dIdt,
      dRdt
    )))
  })  
}
init_state_e7 <- c(
  S = 0.98,
  E1 = 0.01/7,
  E2 = 0.01/7,
  E3 = 0.01/7,
  E4 = 0.01/7,
  E5 = 0.01/7,
  E6 = 0.01/7,
  E7 = 0.01/7,
  I = 0.01,
  R = 0.0
)
out0 <- ode(y = init_state, times = timeline, func = seir_dt, parms = p0)
out0.df <- data.frame(out0)
out07 <- ode(y = init_state_e7, times = timeline, func = se7ir_dt, parms = p0)
out07.df <- data.frame(out07)

out07.df[["E"]] <-
          out07.df[["E1"]] +
          out07.df[["E2"]] +
          out07.df[["E3"]] +
          out07.df[["E4"]] +
          out07.df[["E5"]] +
          out07.df[["E6"]] +
          out07.df[["E7"]]
#i_peak <- out7.df[which.max(out7.df[["I"]]),]
g0 <- plot_out(data.frame(out0))
g70 <- plot_out(out07.df)

out1 <- ode(y = init_state, times = timeline, func = seir_dt, parms = p1)
out1.df <- data.frame(out1)
out17 <- ode(y = init_state_e7, times = timeline, func = se7ir_dt, parms = p1)
out17.df <- data.frame(out17)
out17.df[["E"]] <-
          out17.df[["E1"]] +
          out17.df[["E2"]] +
          out17.df[["E3"]] +
          out17.df[["E4"]] +
          out17.df[["E5"]] +
          out17.df[["E6"]] +
          out17.df[["E7"]]
g1 <- plot_out(data.frame(out1))
g71 <- plot_out(out17.df)

grid.arrange(g0, g70, g1, g71,  ncol = 2)

A closer look at the comparison between E and I of the two models

plot_compare <- function(out.df, out7.df) {
  
  i_max <- out.df[which.max(out.df[["I"]]),]
  i_max7 <- out7.df[which.max(out7.df[["I"]]),]
  e_max <- out.df[which.max(out.df[["E"]]),]
  e_max7 <- out7.df[which.max(out7.df[["E"]]),]
  ggplot( mapping = aes(x = time))+
    geom_line(data = out.df, mapping = aes(y = E, color = "E")) +
    geom_line(data = out.df, mapping = aes(y = I, color = "I")) +
    geom_line(data = out7.df, mapping = aes(y = E, color = "E"), linetype = "dashed") +
    geom_line(data = out7.df, mapping = aes(y = I, color = "I"), linetype = "dashed") +
    geom_point(data = i_max, mapping = aes(y = I), color = "#DC0000FF") +
    geom_point(data = i_max7, mapping = aes(y = I), color = "#DC0000FF") +
    geom_point(data = e_max, mapping = aes(y = E), color = "#F39B7FFF") +
    geom_point(data = e_max7, mapping = aes(y = E), color = "#F39B7FFF") +
    annotate(
      geom = "text",
      x = i_max[["time"]] - 5, 
      y = i_max[["I"]] + 0.05, 
      label = paste(i_max$time, label_percent()(i_max$I), sep = ", ")
    ) +
    annotate(
      geom = "text",
      x = i_max7[["time"]] + 5, 
      y = i_max7[["I"]] + 0.05, 
      label = paste(i_max7$time, label_percent()(i_max7$I), sep = ", ")
    ) +
    annotate(
      geom = "text",
      x = e_max[["time"]] - 5, 
      y = e_max[["E"]] + 0.05, 
      label = paste(e_max$time, label_percent()(e_max$E), sep = ", ")
    ) +
    annotate(
      geom = "text",
      x = e_max7[["time"]] + 5, 
      y = e_max7[["E"]] + 0.05, 
      label = paste(e_max7$time, label_percent()(e_max7$E), sep = ", ")
    ) +
    geom_curve(aes(
        xend = e_max7[["time"]],
        yend = e_max7[["E"]],
        x = e_max[["time"]],
        y = e_max[["E"]]
      ),
      curvature = 0.0,
      arrow = arrow(length = unit(10, "points"))
    ) +
    geom_curve(aes(
        xend = i_max7[["time"]],
        yend = i_max7[["I"]],
        x = i_max[["time"]],
        y = i_max[["I"]]
      ),
      curvature = 0.0,
      arrow = arrow(length = unit(10, "points"))
    ) +
    scale_color_manual(name="", values = c(
      "S" = "#3C5488FF",
      "E" = "#F39B7FFF",
      "E_se7ir" = "#F39B7FFF",
      "I" = "#DC0000FF",
      "I_se7ir" = "#DC0000FF",
      "R" = "#00A087FF")) +
    labs(x = "Time", y = "Population (%)") +
    coord_cartesian(xlim=c(0,70))
}

p1 <- plot_compare(out0.df, out07.df)
p2 <- plot_compare(out1.df, out17.df)

grid.arrange(p1, p2, ncol = 1, heights =unit(c(10,10), c("cm", "cm")) )

What happens if we subdivide the I compartment too?

3.3 Estimating R0 of different models

Effect of model structure over the estimation of R0 Lloyd AL. 2009

4 References

LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIHNlbnNpdGl2aXR5IGFuYWx5c2lzIgpzdWJ0aXRsZTogIlBhcnQgb2YgQ29NbyBjb25zb3J0aXVtJ3MgTU1JRCBjb3Vyc2UiCmF1dGhvcjogIkJvIEdhbyIKZGF0ZTogIjA4LzA0LzIwMjEiCmFsd2F5c19hbGxvd19odG1sOiBUUlVFCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiBUUlVFCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDogVFJVRQogICAgdG9jX2V4cGFuZDogVFJVRQogICAgbnVtYmVyX3NlY3Rpb25zOiBUUlVFCiAgcGRmX2RvY3VtZW50OgogICAgdG9jOiBUUlVFCgotLS0KCiMgT3ZlcnZpZXcKClNlbnNpdGl2aXR5IGFuYWx5c2lzIGlzIHRoZSBwcm9jZXNzIG9mIGRldGVybWluaW5nIGhvdyAqKnNlbnNpdGl2ZSoqIGEgbW9kZWwgaXMgdG8KCiAtIENoYW5nZXMgaW4gcGFyYW1ldGVyIHZhbHVlcwogLSBDaGFuZ2VzIGluIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIG1vZGVsCgpCeSBzaG93aW5nIGhvdyB0aGUgbW9kZWwgcmVzcG9uZCB0byB0aGVzZSBjaGFuZ2VzLCBzZW5zaXRpdml0eSBhbmFseXNpcyBpcyB1c2VmdWwgaW4KCiAtIE1vZGVsIGJ1aWxkaW5nCiAtIE1vZGVsIGV2YWx1YXRpb24KCj4gKlNlbnNpdGl2aXR5IGFuYWx5c2lzIGlzIHBhcnQgb2YgdGhlIGl0ZXJhdGl2ZSBkZXZlbG9wbWVudCBjeWNsZSBvZiBhIHN5c3RlbSBkeW5hbWljIG1vZGVsLioKCiMgUGFyYW1ldGVyIHNlbnNpdGl2aXR5CgpJdCBpcyBvZnRlbiBjaGFsbGVuZ2luZyBmb3IgYSBtb2RlbGxlciB0byBjaG9zZSBhIHVuaXF1ZSB2YWx1ZSBmb3IgdGhlaXIgbW9kZWwgcGFyYW1ldGVycywgc2luY2UgaW4gdGhlIHJlYWwgd29ybGQ6CgogLSBNYW55IG1vZGVsIHBhcmFtZXRlcnMgYXJlIGRpZmZpY3VsdCBvciBldmVuIGltcG9zc2libGUgdG8gbWVhc3VyZSB3aXRoIGEgZ29vZCBkZWdyZWUgb2YgY2VydGFpbnR5LgogLSBQYXJhbWV0ZXIgdmFsdWVzIG1heSBjaGFuZ2Ugb3ZlciB0aW1lLgoKVGhlcmVmb3JlLCBvZnRlbiB0aGUgbW9kZWxsZXIgY2Fubm90IGJlIGNlcnRhaW4gYWJvdXQgdGhlIHZhbHVlIGNob3NlbiBmb3IgYSBwYXJhbWV0ZXIgYW5kIGhhcyB0byB1c2UgKiplc3RpbWF0ZXMqKi4gU2Vuc2l0aXZpdHkgYW5hbHlzaXMgYWxsb3dzIHRoZSBtb2RlbGxlciB0byBkZXRlcm1pbmUgd2hhdCB0aGUgcmVhc29uYWJsZSByYW5nZXMgZm9yIHRob3NlIGVzdGltYXRlcyBhcmU6CgogLSBHcmVhdGVyIHByZWNpc2lvbiBpcyBuZWVkZWQgZm9yIHBhcmFtZXRlcnMgdGhhdCB0aGUgbW9kZWwgaXMgc2Vuc2l0aXZlIHRvCiAtIExlc3MgcHJlY2lzaW9uIGlzIG5lZWRlZCBmb3IgcGFyYW1ldGVycyB0aGF0IHRoZSBtb2RlbCBpcyBpbnNlbnNpdGl2ZSB0bwoKPiBCeSBjb21wYXJpbmcgbW9kZWwgb3V0cHV0IHRvIHJlYWwgd29ybGQgb2JzZXJ2YXRpb25zLCBzZW5zaXRpdml0eSBhbmFseXNpcyBoZWxwcyB0aGUgbW9kZWxsZXIgdG8gY2hvb3NlIHJlYXNvbmFibGUgdmFsdWVzIGZvciBwYXJhbWV0ZXJzLgoKIyMgU2FtcGxpbmcKCkVhY2ggaW5wdXQgcGFyYW1ldGVyIG9mIGEgbW9kZWwgY2FuIGJlIGRlZmluZWQgdG8gaGF2ZSBhbiBhcHByb3ByaWF0ZSAqKnByb2JhYmlsaXR5IGRlbnNpdHkgZnVuY3Rpb24qKiAocGRmKS4gVGhlbiwgdGhlIG1vZGVsIGNhbiBiZSBzaW11bGF0ZWQgYnkgKipzYW1wbGluZyoqIGEgc2luZ2xlIHZhbHVlIGZyb20gZWFjaCBwYXJhbWV0ZXIncyBkaXN0cmlidXRpb24uIE1hbnkgc2FtcGxlcyBzaG91bGQgYmUgdGFrZW4gKGFuZCBmb3Igc3RvY2hhc3RpYyBtb2RlbHMgbWFueSBzaW11bGF0aW9ucyBzaG91bGQgYmUgcnVuIHBlciBzYW1wbGUpLCBwcm9kdWNpbmcgdmFyaWFibGUgb3V0cHV0IHZhbHVlcy4KClNhbXBsaW5nIHZhbHVlcyBmb3Igb25lIHBhcmFtZXRlciBpcyBzdHJhaWdodGZvcndhcmQsIGJ1dCBhcyB0aGUgZGltZW5zaW9uIG9mIHRoZSBwYXJhbWV0ZXIgc3BhY2UgaW5jcmVhc2VzLCB0aGUgY29tcGxleGl0eSBpbmNyZWFzZXMgZXhwb25lbnRpYWxseSwgd2hpY2ggY2FuIHF1aXRlIGVhc2lseSBleGNlZWQgdGhlIGF2YWlsYWJsZSBjb21wdXRpbmcgcmVzb3VyY2VzIHlvdSBoYXZlIGF2YWlsYWJsZS4KClRoZXJlIGFyZSB2YXJpb3VzIGFwcHJvYWNoZXMgdGhhdCBjb3VsZCBiZSB0YWtlbiB0byBzYW1wbGUgZnJvbSB0aGUgcGFyYW1ldGVyIGRpc3RyaWJ1dGlvbnMuIEluIGEgMi1kaW1lbnNpb25hbCBwYXJhbWV0ZXIgc3BhY2U6CgohW3NvdXJjZTogaHR0cHM6Ly90YmlvbWVkLmJpb21lZGNlbnRyYWwuY29tL2FydGljbGVzLzEwLjExODYvMTc0Mi00NjgyLTUtNCNTZWMxOF0oaW1nL1NhU0FUX3NhbXBsaW5nLnBuZykKCiMjIyBMYXRpbiBoeXBlcmN1YmUgc2FtcGxpbmcgd2l0aCB0aGUgYGxoc2AgcGFja2FnZSBpbiBSCgpMZXQncyBzYW1wbGUgdHdvIHBhcmFtZXRlcnMgZm9sbG93aW5nIGEgdW5pZm9ybSBkaXN0cmlidXRpb246CgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKCJsaHMiKQpsaWJyYXJ5KCJkZVNvbHZlIikKbGlicmFyeSgiZ2dwbG90MiIpICM7IGxpYnJhcnkoZ2dzY2kpCmxpYnJhcnkoImdyaWRFeHRyYSIpCmxpYnJhcnkoInNjYWxlcyIpICMgbGFiZWxfcGVyY2VudCgpCgpsaWJyYXJ5KCJsaHMiKQpzZXQuc2VlZCgxMCkKQSA8LSBsaHM6OnJhbmRvbUxIUyg1LCAyKSAjIDUgc2FtcGxlcywgMiBwYXJhbWV0ZXJzCmdBIDwtIGdncGxvdChkYXRhID0gZGF0YS5mcmFtZShBKSwgbWFwcGluZyA9IGFlcyggeCA9IFgxLCB5ID0gWDIpKSArCiAgZ2VvbV9wb2ludCgpICsgY29vcmRfZml4ZWQoKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwxKSwgYnJlYWtzID0gc2VxKDAsIDEsIGJ5ID0gMC4yKSkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsMSksIGJyZWFrcyA9IHNlcSgwLCAxLCBieSA9IDAuMikpICsKICBsYWJzKHRpdGxlID0gIkEgPC0gbGhzOjpyYW5kb21MSFMoNSwgMikiKQoKYGBgCkV4cGFuZCBvbiBleGlzdGluZyBzYW1wbGUKYGBge3J9CkEyIDwtIGxoczo6YXVnbWVudExIUyhBLCA1KSAjIDUgbW9yZSBzYW1wbGVzIG9uIHRvcCBvZiBBCkEyX25ldyA8LSBBMlshKEEyWywxXSAlaW4lIEFbLDFdICksXQpnQV9leHAgPC0gZ2dwbG90KG1hcHBpbmcgPSBhZXMoIHggPSBYMSwgeSA9IFgyKSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGRhdGEuZnJhbWUoQSkpICsKICBnZW9tX3BvaW50KGRhdGEgPSBkYXRhLmZyYW1lKEEyX25ldyksIGNvbG9yID0gIiMwMEEwODdGRiIpICsKICBjb29yZF9maXhlZCgpICsKICBzY2FsZV94X2NvbnRpbnVvdXMobGltaXRzID0gYygwLDEpLCBicmVha3MgPSBzZXEoMCwgMSwgYnkgPSAwLjIpKSArCiAgc2NhbGVfeV9jb250aW51b3VzKGxpbWl0cyA9IGMoMCwxKSwgYnJlYWtzID0gc2VxKDAsIDEsIGJ5ID0gMC4yKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsKICBsYWJzKHRpdGxlID0gIkEyIDwtIGxoczo6YXVnbWVudExIUyhBLCA1KSIpCmdyaWQuYXJyYW5nZShnQSwgZ0FfZXhwLCBuY29sID0gMikKYGBgCgoKV2hhdCBpZiBYMSBpcyBub3JtYWxseSBkaXN0cmlidXRlZD8gTm90ZSB0aGF0IHgyIGlzIHN0aWxsIGZvbGxvd3MgYSB1bmlmb3JtIGRpc3RyaWJ1dGlvbi4KCmBgYHtyfQpCIDwtIGxoczo6cmFuZG9tTEhTKDEwMDAsIDIpICMgMTAwMCBzYW1wbGVzIG9mIDIgcGFyYW1ldGVycwpCWywxXSA8LSBxbm9ybShCWywxXSwgbWVhbiA9IDcsIHNkID0gMSkgIyBYMSBpcyBub3JtYWxseSBkaXN0cmlidXRlZApnZ3Bsb3QoZGF0YSA9IGRhdGEuZnJhbWUoQikpICsKICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoIHggPSBYMSwgeSA9IFgyKSkgKwogIGdlb21fZGVuc2l0eShtYXBwaW5nID0gYWVzKHggPSBYMSksIGNvbG9yID0gIiMwMEEwODdGRiIsIHNpemUgPSAyKSArCiAgc2NhbGVfeF9jb250aW51b3VzKGxpbWl0cyA9IGMoMiwxMiksIGJyZWFrcyA9IHNlcSgyLCAxMiwgYnkgPSAxKSkgKwogIHNjYWxlX3lfY29udGludW91cyhsaW1pdHMgPSBjKDAsMSksIGJyZWFrcyA9IHNlcSgwLCAxLCBieSA9IDAuMikpCgpgYGAKCiAtIFtodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvbGhzL3ZpZ25ldHRlcy9saHNfYmFzaWNzLmh0bWxdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9saHMvdmlnbmV0dGVzL2xoc19iYXNpY3MuaHRtbCkKIC0gW2h0dHBzOi8vZ2l0aHViLmNvbS9iZXJ0Y2FybmVsbC9saHNdKGh0dHBzOi8vZ2l0aHViLmNvbS9iZXJ0Y2FybmVsbC9saHMpCiAtIFtodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvbGhzL2luZGV4Lmh0bWxdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9saHMvaW5kZXguaHRtbCkKCgoKIyMjIFNhbXBsaW5nIHBhcmFtZXRlciBzZXRzIHdpdGggdGhlIGBzZW5zaXRpdml0eWAgcGFja2FnZSBpbiBSCgpUaGUgYHNlbnNpdGl2aXR5OjpwYXJhbWV0ZXJTZXRzYCBmdW5jdGlvbiBwcm92aWRlcyB0aHJlZSBtZXRob2RzIG9mIHNhbXBsaW5nOgoKIDEuIE1ldGhvZCAic29ib2wiIGdlbmVyYXRlcyB1bmlmb3JtbHkgZGlzdHJpYnV0ZWQgcmFuZG9tIG51bWJlcnMsIHVzaW5nIHRoZSBgc29ib2xgCmZ1bmN0aW9uIGluIHRoZSBgcmFuZHRvb2xib3hgIHBhY2thZ2UuCgpgYGB7cn0KbGlicmFyeShyYW5kdG9vbGJveCkKWC5zb2JvbCA8LSBzZW5zaXRpdml0eTo6cGFyYW1ldGVyU2V0cygKICBwYXIucmFuZ2VzID0gbGlzdChYMSA9IGMoMSwxMDAwKSwgWDIgPSBjKDEsNCkpLAogIHNhbXBsZXMgPSAxMDAsCiAgbWV0aG9kID0gInNvYm9sIgopCnBsb3QoWC5zb2JvbCkKYGBgCiFbc291cmNlOiBodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Mb3ctZGlzY3JlcGFuY3lfc2VxdWVuY2VdKGltZy9xdWFzaV92X3BzZXVkby5wbmcpCgogMi4gTWV0aG9kICJncmlkIiBnZW5lcmF0ZXMgYSBncmlkIHdpdGhpbiB0aGUgcGFyYW1ldGVyIHJhbmdlcywgaW5jbHVkaW5nIGl0cyBleHRyZW1lcywgd2l0aCBudW1iZXIKb2YgcG9pbnRzIGRldGVybWluZWQgYnkgc2FtcGxlcwogMy4gTWV0aG9kICJpbm5lcmdyaWQiIGdlbmVyYXRlcyBhIGdyaWQgd2l0aGluIHRoZSBwYXJhbWV0ZXIgcmFuZ2VzLCB3aXRoIGVkZ2VzIG9mIHRoZSBncmlkIG9mZnNldApmcm9tIHRoZSBleHRyZW1lcy4KCmBgYHtyfQpsaWJyYXJ5KCJzZW5zaXRpdml0eSIpCgpYLmdyaWQgPC0gc2Vuc2l0aXZpdHk6OnBhcmFtZXRlclNldHMoCiAgcGFyLnJhbmdlcyA9IGxpc3QoWDEgPSBjKDEsMTAwMCksIFgyID0gYygxLDQpKSwKICBzYW1wbGVzID0gYyg4LDEwKSwKICBtZXRob2QgPSAiZ3JpZCIKKQpwbG90KFguZ3JpZCkKClguaW5uZXJncmlkIDwtIHNlbnNpdGl2aXR5OjpwYXJhbWV0ZXJTZXRzKAogIHBhci5yYW5nZXMgPSBsaXN0KFgxID0gYygxLDEwMDApLCBYMiA9IGMoMSw0KSksCiAgc2FtcGxlcyA9IGMoOCwxMCksCiAgbWV0aG9kID0gImlubmVyZ3JpZCIKKQpwb2ludHMoWC5pbm5lcmdyaWQsIGNvbD0icmVkIikKYGBgCgojIyBFeGFtcGxlIG9mIHBhcmFtZXRlciBzYW1wbGluZyBpbiBhbiBTRUlSIG1vZGVsCgojIyMgTW9kZWwgZGVmaW5pdGlvbgoKYGBge3J9CnJtKGxpc3QgPSBscygpKQoKbGlicmFyeSgiZGVTb2x2ZSIpCmxpYnJhcnkoImdncGxvdDIiKSAjOyBsaWJyYXJ5KGdnc2NpKQpsaWJyYXJ5KCJncmlkRXh0cmEiKQpsaWJyYXJ5KCJzY2FsZXMiKSAjIGxhYmVsX3BlcmNlbnQoKQoKc2Vpcl9kdCA8LSBmdW5jdGlvbih0LCBzdGF0ZSwgcGFyYW1ldGVycyl7CiAgd2l0aChhcy5saXN0KGMoc3RhdGUsIHBhcmFtZXRlcnMpKSwgewogICAgdGhldGEyIDwtIHRoZXRhCiAgICBpZiAobGsub24pIHsKICAgICAgaWYgKHQgPiBsay50ICYmIHQgPCAobGsudCArIGxrLmxlbikpIHsKICAgICAgICB0aGV0YTIgPC0gdGhldGEgKiBsay5lZmYKICAgICAgfQogICAgfQoKICAgIGJldGEgPC0gdGhldGEyICogcAogICAgc2lnbWEgPC0gMS9zaWdtYWQKICAgIGdhbW1hIDwtIDEvZ2FtbWFkCgogICAgZFNkdCA9IG11IC0gYmV0YSpJKlMgLSBtdSpTCiAgICBkRWR0ID0gYmV0YSpJKlMgLSAoc2lnbWErbXUpKkUKICAgIGRJZHQgPSBzaWdtYSpFIC0gKGdhbW1hK211KSpJCiAgICBkUmR0ID0gZ2FtbWEqSSAtIG11KlIKICAgIHJldHVybihsaXN0KGMoZFNkdCwgZEVkdCwgZElkdCwgZFJkdCkpKQogIH0pICAKfQoKcGxvdF9vdXQgPC0gZnVuY3Rpb24ob3V0LmRmKSB7CiAgaV9tYXggPC0gb3V0LmRmW3doaWNoLm1heChvdXQuZGZbWyJJIl1dKSxdCiAgZ2dwbG90KGRhdGEgPSBvdXQuZGYsIG1hcHBpbmcgPSBhZXMoeCA9IHRpbWUpKSsKICAgIGdlb21fbGluZShtYXBwaW5nID0gYWVzKHkgPSBTLCBjb2xvciA9ICJTIikpICsKICAgIGdlb21fbGluZShtYXBwaW5nID0gYWVzKHkgPSBFLCBjb2xvciA9ICJFIikpICsKICAgIGdlb21fbGluZShtYXBwaW5nID0gYWVzKHkgPSBJLCBjb2xvciA9ICJJIikpICsKICAgIGdlb21fbGluZShtYXBwaW5nID0gYWVzKHkgPSBSLCBjb2xvciA9ICJSIikpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGlfbWF4LCBtYXBwaW5nID0gYWVzKHkgPSBJKSwgY29sb3IgPSAiI0RDMDAwMEZGIikgKwogICAgYW5ub3RhdGUoCiAgICAgIGdlb20gPSAidGV4dCIsCiAgICAgIHggPSBpX21heFtbInRpbWUiXV0gKyAyMCwgCiAgICAgIHkgPSBpX21heFtbIkkiXV0gKyAwLjA1LCAKICAgICAgbGFiZWwgPSBwYXN0ZShpX21heCR0aW1lLCBsYWJlbF9wZXJjZW50KCkoaV9tYXgkSSksIHNlcCA9ICIsICIpCiAgICApICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbChuYW1lPSIiLCB2YWx1ZXMgPSBjKCJTIiA9ICIjM0M1NDg4RkYiLCAiRSI9IiNGMzlCN0ZGRiIsICJJIj0iI0RDMDAwMEZGIiwgIlIiPSIjMDBBMDg3RkYiKSkgKwogICAgbGFicyh4ID0gIlRpbWUiLCB5ID0gIlBvcHVsYXRpb24gKCUpIikKfQoKYGBgCgoKYGBge3J9CiMgU2V0IHBhcmFtZXRlciBlc3RpbWF0ZXMKIyBiZXRhIDwtIDUyMC8zNjU7ICMgYmV0YSA9IGNvbnRhY3RfcmF0ZSAqIHRyYW5zbWlzc2lvbl9yaXNrCiMgc2lnbWEgPC0gMS82MDsgIyAxL2xhdGVudF9kYXlzCiMgZ2FtbWEgPC0gMS8zMDsgIyAxL2luZmVjdGlvdXNfZGF5cwp0aGV0YV9lc3QgPC0gMwpwX2VzdCA8LSAwLjE1CnNpZ21hZF9lc3QgPC0gNyAjIDEvbGF0ZW50X2RheXMKZ2FtbWFkX2VzdCA8LSAyNCAjIDEvaW5mZWN0aW91c19kYXlzCiMgc2lnbWFkX2VzdCA8LSAxMSAjIDEvbGF0ZW50X2RheXMKIyBnYW1tYWRfZXN0IDwtIDcgIyAxL2luZmVjdGlvdXNfZGF5cwptdV9lc3QgPC0gNzEyNjgwLyg2NjY1MDAwMCozNjUpICMgVUsgYmlydGggYW5kIHBvcHVsYXRpb24gZmlndXJlcyAyMDE5CgojIExvY2tkb3duCiMgbGsub25fZXN0IDwtIEZBTFNFCmxrLnRfZXN0IDwtIDE1CmxrLmxlbl9lc3QgPC0gOTAKbGsuZWZmX2VzdCA8LSAwLjIKCmluaXRfc3RhdGUgPC0gYyhTID0gMC45OCwgRSA9IDAuMDEsIEkgPSAwLjAxLCBSID0gMC4wKQp0aW1lbGluZSA8LSBzZXEoMCwzNjUpCgpwMCA8LSBjKHRoZXRhID0gdGhldGFfZXN0LCBwID0gcF9lc3QsIHNpZ21hZCA9IHNpZ21hZF9lc3QsIGdhbW1hZCA9IGdhbW1hZF9lc3QsIG11ID0gbXVfZXN0LAogIGxrLm9uID0gRkFMU0UsIGxrLnQgPSBsay50X2VzdCwgbGsubGVuID0gbGsubGVuX2VzdCwgbGsuZWZmID0gbGsuZWZmX2VzdCkKCm91dCA8LSBvZGUoeSA9IGluaXRfc3RhdGUsIHRpbWVzID0gdGltZWxpbmUsIGZ1bmMgPSBzZWlyX2R0LCBwYXJtcyA9IHAwKQpvdXQuZGYgPC0gZGF0YS5mcmFtZShvdXQpCnBsb3Rfb3V0KG91dC5kZikKCmBgYAoKIyMjIFdpdGggaW50ZXJ2ZW50aW9uCgpgYGB7cn0KCnAxIDwtIGModGhldGEgPSB0aGV0YV9lc3QsIHAgPSBwX2VzdCwgc2lnbWFkID0gc2lnbWFkX2VzdCwgZ2FtbWFkID0gZ2FtbWFkX2VzdCwgbXUgPSBtdV9lc3QsCiAgbGsub24gPSBUUlVFLCBsay50ID0gbGsudF9lc3QsIGxrLmxlbiA9IGxrLmxlbl9lc3QsIGxrLmVmZiA9IGxrLmVmZl9lc3QpCgpvdXQgPC0gb2RlKHkgPSBpbml0X3N0YXRlLCB0aW1lcyA9IHRpbWVsaW5lLCBmdW5jID0gc2Vpcl9kdCwgcGFybXMgPSBwMSkKb3V0LmRmIDwtIGRhdGEuZnJhbWUob3V0KQppX3BlYWsgPC0gb3V0LmRmW3doaWNoLm1heChvdXQuZGZbWyJJIl1dKSxdCnBsb3Rfb3V0KG91dC5kZikKCmBgYAoKIyMjIFNhbXBsaW5nCgpgYGB7cn0Kc2Vpcl9mdW4gPC0gZnVuY3Rpb24ocGFyYW1ldGVycyl7CiAgd2l0aChhcy5saXN0KGMocGFyYW1ldGVycykpLCB7CiAgICBvdXQgPC0gb2RlKHkgPSBpbml0X3N0YXRlLCB0aW1lcyA9IHRpbWVsaW5lLCBmdW5jID0gc2Vpcl9kdCwgcGFybXMgPSBwYXJhbWV0ZXJzKQogICAgb3V0LmRmIDwtIGRhdGEuZnJhbWUob3V0KQogICAgaV9wZWFrIDwtIG91dC5kZlt3aGljaC5tYXgob3V0LmRmW1siSSJdXSksXQogICAgcmV0dXJuKGFzLm51bWVyaWMoaV9wZWFrW1siSSJdXSkpCiAgfSkKfSAKCnBfc2V0IDwtIHNlbnNpdGl2aXR5OjpwYXJhbWV0ZXJTZXRzKAogICAgcGFyLnJhbmdlcyA9IGxpc3QoCiAgICAgIHRoZXRhID0gYygyLCA1KSwKICAgICAgcCA9IGMoMC4xLCAwLjIpLAogICAgICBzaWdtYWQgPSBjKDUsIDE0KSwKICAgICAgZ2FtbWFkID0gYygyMCwgMjUpLAogICAgICBtdSA9IGMobXVfZXN0LCBtdV9lc3QpLAogICAgICBsay50ID0gYygzMCw2MCksCiAgICAgIGxrLmxlbiA9IGMoMzAsMTIwKSwKICAgICAgbGsuZWZmID0gYygwLjEsIDAuOCkKICAgICksCiAgICBzYW1wbGVzID0gYygKICAgICAgMywgNSwgMywgMywKICAgICAgMSwgMywgNCwgNQogICAgKSwgIyA4MTAwCiAgICBtZXRob2QgPSAiZ3JpZCIKKQoKdW5pcXVlKHBfc2V0WywibGsubGVuIl0pCmBgYApgYGB7cn0Kc2Vpcl9mdW4oYyhwX3NldFsyMCxdLCBsay5vbiA9IFRSVUUpKQpgYGAKCiMjIyBSdW4gbW9kZWwgcGVyIHNhbXBsZSBvZiBwYXJhbWV0ZXIgc2V0CgpgYGB7cn0KZl9uYW1lIDwtICJyZXN1bHRzLmRmLnJkcyIKcmVzdWx0cy5kZiA8LSBkYXRhLmZyYW1lKHBfc2V0KQpyZXN1bHRzLmRmW1siaW1heCJdXSA8LSAwLjAKCmlmICghZmlsZS5leGlzdHMoZl9uYW1lKSkgewogIHN0YXJ0X3RpbWUgPC0gU3lzLnRpbWUoKSAgCiAgZm9yIChyciBpbiAxOm5yb3cocF9zZXQpICkgewogICMgZm9yIChyciBpbiAxOjEwMDAgKSB7CiAgICBwcCA8LSBjKHBfc2V0W3JyLF0sIGxrLm9uID0gVFJVRSkKICAgIHJlc3VsdHMuZGZbcnIsImltYXgiXSA8LSBzZWlyX2Z1bihwcCkKICB9CiAgZW5kX3RpbWUgPC0gU3lzLnRpbWUoKQogIGVuZF90aW1lIC0gc3RhcnRfdGltZSAjIFRpbWUgZGlmZmVyZW5jZSBvZiAyLjUyNTE1IG1pbnMKICBzYXZlUkRTKHJlc3VsdHMuZGYsIGZpbGUgPSBmX25hbWUpCn0KCmBgYAoKIVtIZXJlJ3Mgb25lIEkgbWFkZSBlYXJsaWVyXShpbWcvaGVyZXNfb25lX21hZGVfZWFybGllci5wbmcpCgpgYGB7cn0KcmVzdWx0cy5kZiA8LSByZWFkUkRTKGZfbmFtZSkKcF9iZXN0IDwtIHJlc3VsdHMuZGZbd2hpY2gubWluKHJlc3VsdHMuZGZbWyJpbWF4Il1dKSxdCnBfYmVzdApwbG90X291dChkYXRhLmZyYW1lKAogIG9kZSh5ID0gaW5pdF9zdGF0ZSwgdGltZXMgPSB0aW1lbGluZSwgZnVuYyA9IHNlaXJfZHQsIHBhcm1zID0gYyhwX2Jlc3QsIGxrLm9uID0gVFJVRSkgKSkpCgpgYGAKCiMjIyBTZW5zaXRpdml0eSBhbmFseXNpcwoKYGBge3J9CgpmcyA8LSBjKCJ0aGV0YSIsICJwIiwgInNpZ21hZCIsICJnYW1tYWQiLCAibXUiLCAibGsudCIsICJsay5sZW4iLCAibGsuZWZmIikKcmVzdWx0cy5kZi5mdCA8LSByZXN1bHRzLmRmCnJlc3VsdHMuZGYuZnRbZnNdIDwtIGxhcHBseShyZXN1bHRzLmRmW2ZzXSwgZmFjdG9yKQoKZ1RoZXRhIDwtIGdncGxvdChkYXRhID0gcmVzdWx0cy5kZi5mdCwgYWVzKHggPSB0aGV0YSwgeSA9IGltYXgpKSArIGdlb21fYm94cGxvdCgpCmdQIDwtIGdncGxvdChkYXRhID0gcmVzdWx0cy5kZi5mdCwgYWVzKHggPXAsIHkgPSBpbWF4KSkgKyBnZW9tX2JveHBsb3QoKQpnU2lnbWFkIDwtIGdncGxvdChkYXRhID0gcmVzdWx0cy5kZi5mdCwgYWVzKHggPSBzaWdtYWQsIHkgPSBpbWF4KSkgKyBnZW9tX2JveHBsb3QoKQpnR2FtbWFkIDwtIGdncGxvdChkYXRhID0gcmVzdWx0cy5kZi5mdCwgYWVzKHggPSBnYW1tYWQsIHkgPSBpbWF4KSkgKyBnZW9tX2JveHBsb3QoKQpnTGt0IDwtIGdncGxvdChkYXRhID0gcmVzdWx0cy5kZi5mdCwgYWVzKHggPSBsay50LCB5ID0gaW1heCkpICsgZ2VvbV9ib3hwbG90KCkKZ0xrbGVuIDwtIGdncGxvdChkYXRhID0gcmVzdWx0cy5kZi5mdCwgYWVzKHggPSBsay5sZW4sIHkgPSBpbWF4KSkgKyBnZW9tX2JveHBsb3QoKQpnTGtlZmYgPC0gZ2dwbG90KGRhdGEgPSByZXN1bHRzLmRmLmZ0LCBhZXMoeCA9IGxrLmVmZiwgeSA9IGltYXgpKSArIGdlb21fYm94cGxvdCgpCgpncmlkLmFycmFuZ2UoZ1RoZXRhLCBnUCwgZ1NpZ21hZCwgZ0dhbW1hZCwgZ0xrdCwgZ0xrbGVuLCBnTGtlZmYsIG5jb2wgPSA0KQoKYGBgCgoKYGBge3J9CmxpYnJhcnkoZ2dzY2kpCmdncGxvdChkYXRhID0gcmVzdWx0cy5kZi5mdCwgYWVzKHggPSBsay5sZW4sIHkgPSBpbWF4LCBmaWxsID0gcCkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZmFjZXRfZ3JpZChsay50fnRoZXRhLCBsYWJlbGxlciA9IGxhYmVsX2JvdGgpICsKICBzY2FsZV9maWxsX25wZygpCmBgYAoKYGBge3J9CiNnZ3Bsb3QoZGF0YSA9IHJlc3VsdHMuZGYsIGFlcyh4ID0gcCwgeSA9IHRoZXRhLCBjb2xvciA9IGltYXgpKSArIGdlb21fcG9pbnQoKSArIHNjYWxlX2NvbG9yX2dyYWRpZW50KGxvdz0iYmx1ZSIsIGhpZ2g9InJlZCIpCmBgYAoKIyBNb2RlbCBzdHJ1Y3R1cmFsIHVuY2VydGFpbnR5Cgo+IC4uLiBXZSBhcmd1ZSB0aGF0IGlmIHdlIGxhY2sgZGV0YWlsZWQga25vd2xlZGdlIG9mIHRoZSBiaW9sb2d5IG9mIHRoZSB0cmFuc21pc3Npb24gcHJvY2VzcywgcGFyYW1ldGVyIGVzdGltYXRpb24gc2hvdWxkIGJlIGFjY29tcGFuaWVkIGJ5IGEgc3RydWN0dXJhbCBzZW5zaXRpdml0eSBhbmFseXNpcywgaW4gYWRkaXRpb24gdG8gdGhlIHN0YW5kYXJkIHN0YXRpc3RpY2FsIHVuY2VydGFpbnR5IGFuYWx5c2lzLiAuLi4gCgpbTGxveWQgQUwuIFNlbnNpdGl2aXR5IG9mIE1vZGVsLUJhc2VkIEVwaWRlbWlvbG9naWNhbCBQYXJhbWV0ZXIgRXN0aW1hdGlvbiB0byBNb2RlbCBBc3N1bXB0aW9ucy4gTWF0aGVtYXRpY2FsIGFuZCBTdGF0aXN0aWNhbCBFc3RpbWF0aW9uIEFwcHJvYWNoZXMgaW4gRXBpZGVtaW9sb2d5LiAyMDA5OzEyMy0xNDEuIGRvaToxMC4xMDA3Lzk3OC05MC00ODEtMjMxMy0xXzZdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzcxMjE1NjQvKQoKIyMgSW5jbHVzaW9uIG9mIGxhdGVuY3kgKFNJUiA+IFNFSVIpCgohW0ltcGFjdCBvZiBsYXRlbmN5IG9uIGEgZGlzZWFzZSBvdXRicmVhayBbTGxveWQgQUwuIDIwMDldKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzcxMjE1NjQvKV0oaW1nL3Npcl92X3NlaXIucG5nKQoKIyMgR2FtbWEgZGlzdHJpYnV0ZWQgbGF0ZW50IHBlcmlvZAoKPiAuLi4gQW4gaW5kaXZpZHVhbOKAmXMgY2hhbmNlIG9mIHJlY292ZXJ5IGlzIG5vdCBjb25zdGFudCBvdmVyIHRpbWU6IHR5cGljYWxseSwgdGhlIHJlY292ZXJ5IHJhdGUgaW5jcmVhc2VzIG92ZXIgdGltZS4gSW4gdGVybXMgb2YgYSBtYXRoZW1hdGljYWwgbW9kZWwsIHRoaXMgbGVhZHMgdG8gdGhlIGNvbXBsaWNhdGlvbiB0aGF0IHRoZSB0aW1lcyBhdCB3aGljaCBkaWZmZXJlbnQgaW5kaXZpZHVhbHMgYmVjYW1lIGluZmVjdGVkIG11c3QgYmUgdHJhY2tlZC4gLi4uIEEgbWF0aGVtYXRpY2FsIHRyaWNrIGFsbG93cyB0aGUgaW5jbHVzaW9uIG9mIG5vbi1leHBvbmVudGlhbCBkaXN0cmlidXRpb25zIHdpdGhpbiB0aGUgY29tcGFydG1lbnRhbCBmcmFtZXdvcmsuIC4uLiBbTGxveWQgQUwuIDIwMDldKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcG1jL2FydGljbGVzL1BNQzcxMjE1NjQvKQoKCldlIG5vdyBsb29rIGF0IGhvdyB0byB0cmFjayB0aGUgdGltZXMgYXQgd2hpY2ggZGlmZmVyZW50IGluZGl2aWR1YWxzIGJlY2FtZSBleHBvc2VkLiBCeSBzdWJkaXZpZGluZyB0aGUgRSBjb21wYXJ0bWVudCBpbnRvIGBtYCBzdGFnZXM6CgohW0RpZmZlcmVudCBpbXBsZW1lbnRhdGlvbnMgb2YgdGhlIEUgY29tcGFydG1lbnRdKGltZy9leHBfdl9nYW1tYS5wbmcpClRoZSBwcm9ncmVzc2lvbnMgYmV0d2VlbiBzdGFnZXMgb2NjdXIgYXQgY29uc3RhbnQgcmF0ZSwgbGVhZGluZyB0byBhbiBleHBvbmVudGlhbCB3YWl0aW5nIHRpbWUgaW4gZWFjaCBzdGFnZS4gVGhlIG92ZXJhbGwgbGF0ZW50IHBlcmlvZCBpcyBkZXNjcmliZWQgYnkgdGhlIHN1bSBvZiB0aGVzZSBgbWAgaW5kZXBlbmRlbnQgZXhwb25lbnRpYWwgZGlzdHJpYnV0aW9ucy4gaS5lLiBsYXRlbnQgcGVyaW9kcyBhcmUgbm93IGdhbW1hIGRpc3RyaWJ1dGVkIHdpdGggc2hhcGUgcGFyYW1ldGVyIGBtYC4KCiAtIFRoZSBzdW0gb2YgYGtgIGV4cG9uZW50aWFsIChgzrtgKSByYW5kb20gdmFyaWFibGVzIGlzIGEgZ2FtbWEgKGBrYCwgYM67YCkgcmFuZG9tIHZhcmlhYmxlLgogLSBgzrtgIGlzIHJhdGUgb2YgZXZlbnRzIChgzrs9MS/OuGAsIGDOuGAgYmVpbmcgdGhlIG1lYW4gd2FpdCB0aW1lKQogLSBNZWFuIG9mIGV4cG9uZW50aWFsIGRpc3RyaWJ1dGlvbiBvZiByYXRlIGDOu2AgaXMgYDEvzrtgCiAtIE1lYW4gb2YgZ2FtbWEgZGlzdHJpYnV0aW9uLCBga2AgZXZlbnRzIG9mIHJhdGUgYM67YCwgaXMgYGsvzrtgCiAtIFRoZSBleHBvbmVudGlhbCBkaXN0cmlidXRpb24gcHJlZGljdHMgdGhlIHdhaXQgdGltZSB1bnRpbCB0aGUgKip2ZXJ5IGZpcnN0KiogZXZlbnQuCiAtIFRoZSBnYW1tYSBkaXN0cmlidXRpb24sIG9uIHRoZSBvdGhlciBoYW5kLCBwcmVkaWN0cyB0aGUgd2FpdCB0aW1lIHVudGlsIHRoZSAqKmstdGgqKiBldmVudCBvY2N1cnMuCgpbR2FtbWEgRGlzdHJpYnV0aW9uIOKAlCBJbnR1aXRpb24sIERlcml2YXRpb24sIGFuZCBFeGFtcGxlc10oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2dhbW1hLWRpc3RyaWJ1dGlvbi1pbnR1aXRpb24tZGVyaXZhdGlvbi1hbmQtZXhhbXBsZXMtNTVmNDA3NDIzODQwKQoKIC0gYGtgIGluIG91dCBleGFtcGxlIGlzIHRoZSBudW1iZXIgb2Ygc3ViZGl2aWRlZCBjb21wYXJ0bWVudHMgaW4gYEVgIHdoaWNoIGlzIGBtYAogLSBgzrtgIGluIG91ciBleGFtcGxlIGlzIHRoZSBpbmN1YmF0aW9uIHJhdGUgYM+DYAoKIyMjIEV4YW1wbGUgYFNFbUlSYCBtb2RlbCAKCmBgYHtyfQpzZTdpcl9kdCA8LSBmdW5jdGlvbih0LCBzdGF0ZSwgcGFyYW1ldGVycyl7CiAgd2l0aChhcy5saXN0KGMoc3RhdGUsIHBhcmFtZXRlcnMpKSwgewogICAgdGhldGEyIDwtIHRoZXRhCiAgICBpZiAobGsub24pIHsKICAgICAgaWYgKHQgPiBsay50ICYmIHQgPCAobGsudCArIGxrLmxlbikpIHsKICAgICAgICB0aGV0YTIgPC0gdGhldGEgKiBsay5lZmYKICAgICAgfQogICAgfQoKICAgIGJldGEgPC0gdGhldGEyICogcAogICAgc2lnbWEgPC0gMS9zaWdtYWQKICAgIGdhbW1hIDwtIDEvZ2FtbWFkCgogICAgbSA8LSA3CgogICAgZFNkdCA9IG11IC0gYmV0YSpJKlMgLSBtdSpTCiAgICAjIGRFZHQgPSBiZXRhKkkqUyAtIChzaWdtYSttdSkqRQogICAgZEUxZHQgPSBiZXRhKkkqUyAtIChtKnNpZ21hK211KSpFMQogICAgZEUyZHQgPSBtKnNpZ21hKkUxIC0gKG0qc2lnbWErbXUpKkUyCiAgICBkRTNkdCA9IG0qc2lnbWEqRTIgLSAobSpzaWdtYSttdSkqRTMKICAgIGRFNGR0ID0gbSpzaWdtYSpFMyAtIChtKnNpZ21hK211KSpFNAogICAgZEU1ZHQgPSBtKnNpZ21hKkU0IC0gKG0qc2lnbWErbXUpKkU1CiAgICBkRTZkdCA9IG0qc2lnbWEqRTUgLSAobSpzaWdtYSttdSkqRTYKICAgIGRFN2R0ID0gbSpzaWdtYSpFNiAtIChtKnNpZ21hK211KSpFNwogICAgZElkdCA9IG0qc2lnbWEqRTcgLSAoZ2FtbWErbXUpKkkKICAgIGRSZHQgPSBnYW1tYSpJIC0gbXUqUgogICAgcmV0dXJuKGxpc3QoYygKICAgICAgZFNkdCwKICAgICAgZEUxZHQsCiAgICAgIGRFMmR0LAogICAgICBkRTNkdCwKICAgICAgZEU0ZHQsCiAgICAgIGRFNWR0LAogICAgICBkRTZkdCwKICAgICAgZEU3ZHQsCiAgICAgIGRJZHQsCiAgICAgIGRSZHQKICAgICkpKQogIH0pICAKfQppbml0X3N0YXRlX2U3IDwtIGMoCiAgUyA9IDAuOTgsCiAgRTEgPSAwLjAxLzcsCiAgRTIgPSAwLjAxLzcsCiAgRTMgPSAwLjAxLzcsCiAgRTQgPSAwLjAxLzcsCiAgRTUgPSAwLjAxLzcsCiAgRTYgPSAwLjAxLzcsCiAgRTcgPSAwLjAxLzcsCiAgSSA9IDAuMDEsCiAgUiA9IDAuMAopCm91dDAgPC0gb2RlKHkgPSBpbml0X3N0YXRlLCB0aW1lcyA9IHRpbWVsaW5lLCBmdW5jID0gc2Vpcl9kdCwgcGFybXMgPSBwMCkKb3V0MC5kZiA8LSBkYXRhLmZyYW1lKG91dDApCm91dDA3IDwtIG9kZSh5ID0gaW5pdF9zdGF0ZV9lNywgdGltZXMgPSB0aW1lbGluZSwgZnVuYyA9IHNlN2lyX2R0LCBwYXJtcyA9IHAwKQpvdXQwNy5kZiA8LSBkYXRhLmZyYW1lKG91dDA3KQoKb3V0MDcuZGZbWyJFIl1dIDwtCiAgICAgICAgICBvdXQwNy5kZltbIkUxIl1dICsKICAgICAgICAgIG91dDA3LmRmW1siRTIiXV0gKwogICAgICAgICAgb3V0MDcuZGZbWyJFMyJdXSArCiAgICAgICAgICBvdXQwNy5kZltbIkU0Il1dICsKICAgICAgICAgIG91dDA3LmRmW1siRTUiXV0gKwogICAgICAgICAgb3V0MDcuZGZbWyJFNiJdXSArCiAgICAgICAgICBvdXQwNy5kZltbIkU3Il1dCiNpX3BlYWsgPC0gb3V0Ny5kZlt3aGljaC5tYXgob3V0Ny5kZltbIkkiXV0pLF0KZzAgPC0gcGxvdF9vdXQoZGF0YS5mcmFtZShvdXQwKSkKZzcwIDwtIHBsb3Rfb3V0KG91dDA3LmRmKQoKb3V0MSA8LSBvZGUoeSA9IGluaXRfc3RhdGUsIHRpbWVzID0gdGltZWxpbmUsIGZ1bmMgPSBzZWlyX2R0LCBwYXJtcyA9IHAxKQpvdXQxLmRmIDwtIGRhdGEuZnJhbWUob3V0MSkKb3V0MTcgPC0gb2RlKHkgPSBpbml0X3N0YXRlX2U3LCB0aW1lcyA9IHRpbWVsaW5lLCBmdW5jID0gc2U3aXJfZHQsIHBhcm1zID0gcDEpCm91dDE3LmRmIDwtIGRhdGEuZnJhbWUob3V0MTcpCm91dDE3LmRmW1siRSJdXSA8LQogICAgICAgICAgb3V0MTcuZGZbWyJFMSJdXSArCiAgICAgICAgICBvdXQxNy5kZltbIkUyIl1dICsKICAgICAgICAgIG91dDE3LmRmW1siRTMiXV0gKwogICAgICAgICAgb3V0MTcuZGZbWyJFNCJdXSArCiAgICAgICAgICBvdXQxNy5kZltbIkU1Il1dICsKICAgICAgICAgIG91dDE3LmRmW1siRTYiXV0gKwogICAgICAgICAgb3V0MTcuZGZbWyJFNyJdXQpnMSA8LSBwbG90X291dChkYXRhLmZyYW1lKG91dDEpKQpnNzEgPC0gcGxvdF9vdXQob3V0MTcuZGYpCgpncmlkLmFycmFuZ2UoZzAsIGc3MCwgZzEsIGc3MSwgIG5jb2wgPSAyKQpgYGAKQSBjbG9zZXIgbG9vayBhdCB0aGUgY29tcGFyaXNvbiBiZXR3ZWVuIEUgYW5kIEkgb2YgdGhlIHR3byBtb2RlbHMKCmBgYHtyfQpwbG90X2NvbXBhcmUgPC0gZnVuY3Rpb24ob3V0LmRmLCBvdXQ3LmRmKSB7CiAgCiAgaV9tYXggPC0gb3V0LmRmW3doaWNoLm1heChvdXQuZGZbWyJJIl1dKSxdCiAgaV9tYXg3IDwtIG91dDcuZGZbd2hpY2gubWF4KG91dDcuZGZbWyJJIl1dKSxdCiAgZV9tYXggPC0gb3V0LmRmW3doaWNoLm1heChvdXQuZGZbWyJFIl1dKSxdCiAgZV9tYXg3IDwtIG91dDcuZGZbd2hpY2gubWF4KG91dDcuZGZbWyJFIl1dKSxdCiAgZ2dwbG90KCBtYXBwaW5nID0gYWVzKHggPSB0aW1lKSkrCiAgICBnZW9tX2xpbmUoZGF0YSA9IG91dC5kZiwgbWFwcGluZyA9IGFlcyh5ID0gRSwgY29sb3IgPSAiRSIpKSArCiAgICBnZW9tX2xpbmUoZGF0YSA9IG91dC5kZiwgbWFwcGluZyA9IGFlcyh5ID0gSSwgY29sb3IgPSAiSSIpKSArCiAgICBnZW9tX2xpbmUoZGF0YSA9IG91dDcuZGYsIG1hcHBpbmcgPSBhZXMoeSA9IEUsIGNvbG9yID0gIkUiKSwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogICAgZ2VvbV9saW5lKGRhdGEgPSBvdXQ3LmRmLCBtYXBwaW5nID0gYWVzKHkgPSBJLCBjb2xvciA9ICJJIiksIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICAgIGdlb21fcG9pbnQoZGF0YSA9IGlfbWF4LCBtYXBwaW5nID0gYWVzKHkgPSBJKSwgY29sb3IgPSAiI0RDMDAwMEZGIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gaV9tYXg3LCBtYXBwaW5nID0gYWVzKHkgPSBJKSwgY29sb3IgPSAiI0RDMDAwMEZGIikgKwogICAgZ2VvbV9wb2ludChkYXRhID0gZV9tYXgsIG1hcHBpbmcgPSBhZXMoeSA9IEUpLCBjb2xvciA9ICIjRjM5QjdGRkYiKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBlX21heDcsIG1hcHBpbmcgPSBhZXMoeSA9IEUpLCBjb2xvciA9ICIjRjM5QjdGRkYiKSArCiAgICBhbm5vdGF0ZSgKICAgICAgZ2VvbSA9ICJ0ZXh0IiwKICAgICAgeCA9IGlfbWF4W1sidGltZSJdXSAtIDUsIAogICAgICB5ID0gaV9tYXhbWyJJIl1dICsgMC4wNSwgCiAgICAgIGxhYmVsID0gcGFzdGUoaV9tYXgkdGltZSwgbGFiZWxfcGVyY2VudCgpKGlfbWF4JEkpLCBzZXAgPSAiLCAiKQogICAgKSArCiAgICBhbm5vdGF0ZSgKICAgICAgZ2VvbSA9ICJ0ZXh0IiwKICAgICAgeCA9IGlfbWF4N1tbInRpbWUiXV0gKyA1LCAKICAgICAgeSA9IGlfbWF4N1tbIkkiXV0gKyAwLjA1LCAKICAgICAgbGFiZWwgPSBwYXN0ZShpX21heDckdGltZSwgbGFiZWxfcGVyY2VudCgpKGlfbWF4NyRJKSwgc2VwID0gIiwgIikKICAgICkgKwogICAgYW5ub3RhdGUoCiAgICAgIGdlb20gPSAidGV4dCIsCiAgICAgIHggPSBlX21heFtbInRpbWUiXV0gLSA1LCAKICAgICAgeSA9IGVfbWF4W1siRSJdXSArIDAuMDUsIAogICAgICBsYWJlbCA9IHBhc3RlKGVfbWF4JHRpbWUsIGxhYmVsX3BlcmNlbnQoKShlX21heCRFKSwgc2VwID0gIiwgIikKICAgICkgKwogICAgYW5ub3RhdGUoCiAgICAgIGdlb20gPSAidGV4dCIsCiAgICAgIHggPSBlX21heDdbWyJ0aW1lIl1dICsgNSwgCiAgICAgIHkgPSBlX21heDdbWyJFIl1dICsgMC4wNSwgCiAgICAgIGxhYmVsID0gcGFzdGUoZV9tYXg3JHRpbWUsIGxhYmVsX3BlcmNlbnQoKShlX21heDckRSksIHNlcCA9ICIsICIpCiAgICApICsKICAgIGdlb21fY3VydmUoYWVzKAogICAgICAgIHhlbmQgPSBlX21heDdbWyJ0aW1lIl1dLAogICAgICAgIHllbmQgPSBlX21heDdbWyJFIl1dLAogICAgICAgIHggPSBlX21heFtbInRpbWUiXV0sCiAgICAgICAgeSA9IGVfbWF4W1siRSJdXQogICAgICApLAogICAgICBjdXJ2YXR1cmUgPSAwLjAsCiAgICAgIGFycm93ID0gYXJyb3cobGVuZ3RoID0gdW5pdCgxMCwgInBvaW50cyIpKQogICAgKSArCiAgICBnZW9tX2N1cnZlKGFlcygKICAgICAgICB4ZW5kID0gaV9tYXg3W1sidGltZSJdXSwKICAgICAgICB5ZW5kID0gaV9tYXg3W1siSSJdXSwKICAgICAgICB4ID0gaV9tYXhbWyJ0aW1lIl1dLAogICAgICAgIHkgPSBpX21heFtbIkkiXV0KICAgICAgKSwKICAgICAgY3VydmF0dXJlID0gMC4wLAogICAgICBhcnJvdyA9IGFycm93KGxlbmd0aCA9IHVuaXQoMTAsICJwb2ludHMiKSkKICAgICkgKwogICAgc2NhbGVfY29sb3JfbWFudWFsKG5hbWU9IiIsIHZhbHVlcyA9IGMoCiAgICAgICJTIiA9ICIjM0M1NDg4RkYiLAogICAgICAiRSIgPSAiI0YzOUI3RkZGIiwKICAgICAgIkVfc2U3aXIiID0gIiNGMzlCN0ZGRiIsCiAgICAgICJJIiA9ICIjREMwMDAwRkYiLAogICAgICAiSV9zZTdpciIgPSAiI0RDMDAwMEZGIiwKICAgICAgIlIiID0gIiMwMEEwODdGRiIpKSArCiAgICBsYWJzKHggPSAiVGltZSIsIHkgPSAiUG9wdWxhdGlvbiAoJSkiKSArCiAgICBjb29yZF9jYXJ0ZXNpYW4oeGxpbT1jKDAsNzApKQp9CmBgYApgYGB7cixmaWcuaGVpZ2h0ID0gOCwgZmlnLndpZHRoPTh9CgpwMSA8LSBwbG90X2NvbXBhcmUob3V0MC5kZiwgb3V0MDcuZGYpCnAyIDwtIHBsb3RfY29tcGFyZShvdXQxLmRmLCBvdXQxNy5kZikKCmdyaWQuYXJyYW5nZShwMSwgcDIsIG5jb2wgPSAxLCBoZWlnaHRzID11bml0KGMoMTAsMTApLCBjKCJjbSIsICJjbSIpKSApCgpgYGAKCj4gV2hhdCBoYXBwZW5zIGlmIHdlIHN1YmRpdmlkZSB0aGUgYElgIGNvbXBhcnRtZW50IHRvbz8KCiMjIEVzdGltYXRpbmcgUjAgb2YgZGlmZmVyZW50IG1vZGVscwoKIVtFZmZlY3Qgb2YgbW9kZWwgc3RydWN0dXJlIG92ZXIgdGhlIGVzdGltYXRpb24gb2YgUjAgW0xsb3lkIEFMLiAyMDA5XShodHRwczovL3d3dy5uY2JpLm5sbS5uaWguZ292L3BtYy9hcnRpY2xlcy9QTUM3MTIxNTY0LyldKGltZy9yMC5wbmcpCgojIFJlZmVyZW5jZXMKIC0gU2FtcGxpbmcgYW5kIHNlbnNpdGl2aXR5IGFuYWx5c2VzIHRvb2xzIChTYVNBVCkgZm9yIGNvbXB1dGF0aW9uYWwgbW9kZWxsaW5nIGh0dHBzOi8vdGJpb21lZC5iaW9tZWRjZW50cmFsLmNvbS9hcnRpY2xlcy8xMC4xMTg2LzE3NDItNDY4Mi01LTQjU2VjMTgKCiAtIEFuIGludHJvZHVjdGlvbiB0byBzZW5zaXRpdml0eSBhbmFseXNpcyBodHRwczovL29jdy5taXQuZWR1L2NvdXJzZXMvc2xvYW4tc2Nob29sLW9mLW1hbmFnZW1lbnQvMTUtOTg4LXN5c3RlbS1keW5hbWljcy1zZWxmLXN0dWR5LWZhbGwtMTk5OC1zcHJpbmctMTk5OS9yZWFkaW5ncy9zZW5zaXRpdml0eWFuYWx5c2lzLnBkZgoKIC0gSGFuZGJvb2sgb2YgVW5jZXJ0YWludHkgUXVhbnRpZmljYXRpb24gcHAgMTEwMy0xMTIyLCBJbnRyb2R1Y3Rpb24gdG8gU2Vuc2l0aXZpdHkgQW5hbHlzaXMKIGh0dHBzOi8vbGluay5zcHJpbmdlci5jb20vcmVmZXJlbmNld29ya2VudHJ5LzEwLjEwMDclMkY5NzgtMy0zMTktMTIzODUtMV8zMQoKIC0gUiBwYWNrYWdlIGBzZW5zaXRpdml0eWAgaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3NlbnNpdGl2aXR5L3NlbnNpdGl2aXR5LnBkZgoKIC0gT3ZlcnZpZXcgb2YgYWR2YW50YWdlcyBhbmQgZHJhd2JhY2tzIG9mIGRpZmZlcmVudCBtZXRob2RzIGZvciBzZW5zaXRpdml0eSBhbmFseXNpcyBpbiB0aGUgY29udGV4dCBvZiBwZXJmb3JtYW5jZSBhc3Nlc3NtZW50IGh0dHBzOi8vaWdkdHAuZXUvd3AtY29udGVudC91cGxvYWRzLzIwMTcvMDkvV0cxLTVfb3ZlcnZpZXdfYmVja2VyLnBkZgogLSBBcHBseWluZyBhIEdsb2JhbCBTZW5zaXRpdml0eSBBbmFseXNpcyBXb3JrZmxvdyB0byBJbXByb3ZlIHRoZSBDb21wdXRhdGlvbmFsIEVmZmljaWVuY2llcyBpbiBQaHlzaW9sb2dpY2FsbHktQmFzZWQgUGhhcm1hY29raW5ldGljIE1vZGVsaW5nIGh0dHBzOi8vd3d3LmZyb250aWVyc2luLm9yZy9hcnRpY2xlcy8xMC4zMzg5L2ZwaGFyLjIwMTguMDA1ODgvZnVsbAog