Theory
We define as key quantity
- \(I_t\), the true number of new infections at day \(t\).
The two key observed quantities are
- \(c_t\), the reported number of new cases at day \(t\), and
- \(d_t\), the reported number of new deaths at day \(t\).
We assume a growth model with slow-down of the form
\[
I_t = I_0 \exp((\alpha_0 - \beta t) t)
\] with a starting number of infected people, \(I_0\), and a dynamicaly decreasing growth rate \(\alpha_0-\beta t\).
We now consider the testing process, and the process by which people die of the desease as random processes with fixed probability. The probability that a person with an infection gets tested is \(\gamma\), and the probability that an infected person dies is \(\delta\). We need two more parameters for our sampling processes, which are \(\tau_t\), the average time between infection and test, and \(\tau_\delta\), the average time between infecton and death. We can then have the following binomial sampling probabilities:
\[
\begin{split}
c_t &\sim \text{Bin}(c_t; I_{t-\tau_t}, \gamma)\\
d_t &\sim \text{Bin}(d_t; I_{t-\tau_\delta}, \delta)
\end{split}
\]
In summary, the key parameters are, together with short descriptions and prior information:
| \(I_0\) |
Starting nr of infected people |
\([0, \infty)\) |
| \(\alpha_0\) |
Exp. starting growth rate |
\([0, \infty)\) |
| \(\beta\) |
Slow-down rate |
\([0, \infty)\) |
| \(\gamma\) |
Prob. to get tested |
\([0,1]\) |
In addition, we fix the following parameters: | Parameter | Description | Value | |—————|———————————-|———–| | \(\tau_t\) | Time from infection to test | 7 | | \(\tau_\delta\) | Time from infection to death | 17 | | \(\delta\) | Death rate | 0.01 |
Note that for technical reasons we introduce some upper bounds on the prior probabilities in practice, to constrain the parameter search space, but that has no influence on the estimates (because the posteriors are far away from these upper boundaries).
So we can write down a likelihood of the data given the model simply as the product of all these binomial probabilities across all time points where possible. So:
\[
\mathcal{L} = \prod_t \text{Bin}(c_t; I_{t-\tau_t}, \gamma) \prod_t\text{Bin}(d_t; I_{t-\tau_\delta}, \delta)
\] where the products run over all days for which we have know \(c_t\) or \(d_t\), respectively.
This then allows us to compute posterior distributions of all parameters using Monte Carlo sampling, as for example implemented in the package Stan.
Analysis
We first defined the model in STAN as follows:
functions {
real growth_model(real I0, real alpha0, real beta, real t) {
return I0 * exp((alpha0 - beta * t) * t);
}
}
data {
int N;
real cases[N];
real deaths[N];
int day[N];
real<lower=0> tau_t;
real<lower=0> tau_delta;
real<lower=0> delta;
}
parameters {
real<lower=0,upper=1> gamma;
real<lower=0,upper=1> alpha0;
real<lower=0,upper=1> beta;
real<lower=0,upper=1e6> I0;
}
model {
for(i in 1:N) {
real nc = growth_model(I0, alpha0, beta, day[i] - tau_t);
cases[i] ~ normal(nc * gamma, nc * gamma * (1 - gamma));
real nd = growth_model(I0, alpha0, beta, day[i] - tau_delta);
deaths[i] ~ normal(nd * delta, nd * delta * (1 - delta));
}
}
engine.opts$output.var must be a character string providing a name for the returned `stanmodel` object
and compiled it with CmdStan. To prepare the input parameters, we first need to determine a valid time range for our analysis during which we can assume exponential growth:
library(magrittr)
library(ggplot2)
dat <- covid19germany::get_RKI_timeseries() %>% covid19germany::group_RKI_timeseries()
dat %>%
dplyr::select(Date, NumberNewTestedIll, NumberNewDead) %>%
tidyr::pivot_longer(c(NumberNewTestedIll, NumberNewDead), names_to="type", values_to = "Count") %>%
ggplot() + geom_point(mapping = aes(x = Date, y = Count, col=type)) + scale_y_log10()

This reveals a lag of about 10 days between the onset of exponential growth between test cases and deaths. With around 17 days from infection to death (see this post), this yields an average time of 7 days between infection and reported test case, justifying our choice for \(\tau_t=7\).
We then prepared the input for the model:
selected_dat <- dplyr::filter(dat,
Date >= as.POSIXct("2020-02-23") &
Date <= as.POSIXct("2020-03-31"))
day <- as.numeric(difftime(selected_dat$Date, as.Date("2020-02-23"), units="days"))
cases <- selected_dat$NumberNewTestedIll
deaths <- selected_dat$NumberNewDead
N <- length(cases)
tau_t <- 7
tau_delta <- 17
delta <- 0.01
alpha0 <- 0.1
beta <- 0.1
I0 <- 10
gamma <- 0.5
dump(c("day", "cases", "deaths", "N", "tau_t", "tau_delta", "delta"), file="covid19_model.data.R")
dump(c("alpha0", "beta", "I0", "gamma"), file="covid19_model.init.R")
We then ran the model and sampled from the posterior using
# The stan model was compiled to ./covid19_model
./covid19_model sample data file=covid19_model.data.R init=covid19_model.init.R output file=sampling.csv
method = sample (Default)
sample
num_samples = 1000 (Default)
num_warmup = 1000 (Default)
save_warmup = 0 (Default)
thin = 1 (Default)
adapt
engaged = 1 (Default)
gamma = 0.050000000000000003 (Default)
delta = 0.80000000000000004 (Default)
kappa = 0.75 (Default)
t0 = 10 (Default)
init_buffer = 75 (Default)
term_buffer = 50 (Default)
window = 25 (Default)
algorithm = hmc (Default)
hmc
engine = nuts (Default)
nuts
max_depth = 10 (Default)
metric = diag_e (Default)
metric_file = (Default)
stepsize = 1 (Default)
stepsize_jitter = 0 (Default)
id = 0 (Default)
data
file = covid19_model.data.R
init = covid19_model.init.R
random
seed = -1 (Default)
output
file = sampling.csv
diagnostic_file = (Default)
refresh = 100 (Default)
Gradient evaluation took 3.9e-05 seconds
1000 transitions using 10 leapfrog steps per transition would take 0.39 seconds.
Adjust your expectations accordingly!
Iteration: 1 / 2000 [ 0%] (Warmup)
Iteration: 100 / 2000 [ 5%] (Warmup)
Iteration: 200 / 2000 [ 10%] (Warmup)
Iteration: 300 / 2000 [ 15%] (Warmup)
Iteration: 400 / 2000 [ 20%] (Warmup)
Iteration: 500 / 2000 [ 25%] (Warmup)
Iteration: 600 / 2000 [ 30%] (Warmup)
Iteration: 700 / 2000 [ 35%] (Warmup)
Iteration: 800 / 2000 [ 40%] (Warmup)
Iteration: 900 / 2000 [ 45%] (Warmup)
Iteration: 1000 / 2000 [ 50%] (Warmup)
Iteration: 1001 / 2000 [ 50%] (Sampling)
Iteration: 1100 / 2000 [ 55%] (Sampling)
Iteration: 1200 / 2000 [ 60%] (Sampling)
Iteration: 1300 / 2000 [ 65%] (Sampling)
Iteration: 1400 / 2000 [ 70%] (Sampling)
Iteration: 1500 / 2000 [ 75%] (Sampling)
Iteration: 1600 / 2000 [ 80%] (Sampling)
Iteration: 1700 / 2000 [ 85%] (Sampling)
Iteration: 1800 / 2000 [ 90%] (Sampling)
Iteration: 1900 / 2000 [ 95%] (Sampling)
Iteration: 2000 / 2000 [100%] (Sampling)
Elapsed Time: 0.204412 seconds (Warm-up)
0.19322 seconds (Sampling)
0.397632 seconds (Total)
We load the posterior as a table:
stan_output <- readr::read_csv("sampling.csv", comment = "#")
Let’s first just look at marginal summary statistics for each parameter
stan_output %>%
tidyr::pivot_longer(c('I0', 'alpha0', 'beta', 'gamma'),
names_to = "param",
values_to = "value") %>%
dplyr::group_by(param) %>%
dplyr::summarise(perc5 = quantile(value, 0.05),
perc50_median = median(value),
perc95 = quantile(value, 0.95))
Here are correlations between these posteriors:
stan_output %>%
dplyr::select(I0, alpha0, beta, gamma) %>%
GGally::ggpairs()

This is all reasonable and more or less expected given the model set up. For example, the correlation between \(\alpha_0\) and \(\beta\) is expected (you can afford to have steeper growth in the beginning if you have a stronger slow-down).
Visualising the model
OK, here are the model predictions
So we can use the many samples from our posterior to check some predictions. First, we prepare the model predictions in a new table plot_curves:
growth_func <- function(I0, alpha0, beta, t) {
return(I0 * exp(t * (alpha0 - beta * t)))
}
day_tbl <- tibble::tibble(days = 0:60) %>%
dplyr::mutate(date = as.POSIXct("2020-02-23") + lubridate::days(days))
plot_curves <- stan_output %>%
dplyr::select(alpha0, gamma, beta, I0) %>%
dplyr::mutate(id=1:nrow(stan_output)) %>%
tidyr::expand_grid(day_tbl) %>%
dplyr::mutate(true_cases = growth_func(I0, alpha0, beta, days),
predicted_testcases = gamma * growth_func(I0, alpha0, beta, days - 7),
predicted_deaths = 0.01 * growth_func(I0, alpha0, beta, days - 17)) %>%
dplyr::group_by(id) %>%
dplyr::mutate(cum_true_cases = cumsum(true_cases),
cum_predicted_testcases = cumsum(predicted_testcases),
cum_predicted_deaths = cumsum(predicted_deaths)) %>%
dplyr::ungroup()
We can then plot the posteriors as a function of time together with the data:
plot_curves %>%
dplyr::group_by(date) %>%
dplyr::summarise(
true_cases_5 = quantile(true_cases, 0.05),
true_cases_95 = quantile(true_cases, 0.95),
predicted_testcases_5 = quantile(predicted_testcases, 0.05),
predicted_testcases_95 = quantile(predicted_testcases, 0.95),
predicted_deaths_5 = quantile(predicted_deaths, 0.05),
predicted_deaths_95 = quantile(predicted_deaths, 0.95)
) %>%
ggplot() +
geom_ribbon(mapping = aes(x = date, ymin = true_cases_5, ymax=true_cases_95),
fill = "dark green", alpha = 0.5) +
geom_ribbon(mapping = aes(x = date, ymin = predicted_testcases_5, ymax=predicted_testcases_95),
fill = "blue", alpha = 0.5) +
geom_ribbon(mapping = aes(x = date, ymin = predicted_deaths_5, ymax=predicted_deaths_95),
fill = "red", alpha = 0.5) +
geom_point(dat, mapping = aes(x = Date, y = NumberNewTestedIll),
col = "blue") +
geom_point(dplyr::filter(dat, NumberNewDead > 0), mapping = aes(x = Date, y = NumberNewDead), col = "red") +
scale_y_log10(labels = function(x) format(x, big.mark = ",", scientific = FALSE),
breaks = c(1, 10, 100, 1000, 10000, 100000),
limits = c(1, 1e5)) +
theme_minimal() +
ggtitle("Bayesian model predictions") +
theme(axis.title.x=element_blank(),
axis.title.y=element_blank()) +
annotate("text", x = as.POSIXct("2020-03-5"), y = 5e4, label = "true infections\n(given 1% fatality rate)", col="dark green") +
annotate("text", x = as.POSIXct("2020-03-25"), y = 1e3, label = "tested positive", col="blue") +
annotate("text", x = as.POSIXct("2020-03-25"), y = 10, label = "deaths", col="red")

plot_curves %>%
dplyr::group_by(date) %>%
dplyr::summarise(
cum_true_cases_5 = quantile(cum_true_cases, 0.05),
cum_true_cases_95 = quantile(cum_true_cases, 0.95),
cum_predicted_testcases_5 = quantile(cum_predicted_testcases, 0.05),
cum_predicted_testcases_95 = quantile(cum_predicted_testcases, 0.95),
cum_predicted_deaths_5 = quantile(cum_predicted_deaths, 0.05),
cum_predicted_deaths_95 = quantile(cum_predicted_deaths, 0.95)
) %>%
ggplot() +
geom_ribbon(mapping = aes(x = date, ymin = cum_true_cases_5, ymax=cum_true_cases_95),
fill = "dark green", alpha = 0.5) +
geom_ribbon(mapping = aes(x = date, ymin = cum_predicted_testcases_5, ymax=cum_predicted_testcases_95),
fill = "blue", alpha = 0.5) +
geom_ribbon(mapping = aes(x = date, ymin = cum_predicted_deaths_5, ymax=cum_predicted_deaths_95),
fill = "red", alpha = 0.5) +
geom_point(dat, mapping = aes(x = Date, y = CumNumberTestedIll),
col = "blue") +
geom_point(dplyr::filter(dat, CumNumberDead > 0), mapping = aes(x = Date, y = CumNumberDead), col = "red") +
scale_y_log10(labels = function(x) format(x, big.mark = ",", scientific = FALSE),
breaks = c(1, 10, 100, 1000, 10000, 100000, 1000000),
limits = c(1, 1e7)) +
theme_minimal() +
ggtitle("Bayesian model predictions") +
theme(axis.title.x=element_blank(),
axis.title.y=element_blank()) +
annotate("text", x = as.POSIXct("2020-03-10"), y = 1e6, label = "true infections\n(given 1% fatality rate)", col="dark green") +
annotate("text", x = as.POSIXct("2020-03-20"), y = 2e3, label = "tested positive", col="blue") +
annotate("text", x = as.POSIXct("2020-03-20"), y = 20, label = "deaths", col="red")

ggsave("model_predictions.png", width = 8, height = 5)
We can convert the growth rate into a doubling time and plot that as well:
doubling_time <- function(alpha0, beta, t) {
return (log(2) / (alpha0 - beta * t))
}
day_tbl <- tibble::tibble(days = 0:45) %>%
dplyr::mutate(date = as.POSIXct("2020-02-23") + lubridate::days(days))
dt_curves <- stan_output %>%
dplyr::select(alpha0, beta) %>%
dplyr::mutate(id=1:nrow(stan_output)) %>%
tidyr::expand_grid(day_tbl) %>%
dplyr::mutate(dt = doubling_time(alpha0, beta, days))
dt_curves %>%
dplyr::group_by(date) %>%
dplyr::summarise(dt5 = quantile(dt, 0.05), dt50 = quantile(dt, 0.5), dt95 = quantile(dt, 0.95)) %>%
ggplot() +
geom_ribbon(mapping = aes(x = date, ymin = dt5, ymax=dt95), alpha=0.5) +
geom_line(mapping = aes(x = date, y = dt50)) +
theme_minimal() +
ggtitle("Modelling of doubling time (in days)") +
theme(axis.title.x=element_blank(),
axis.title.y=element_blank()) +
geom_vline(xintercept = as.POSIXct("2020-04-01"))

ggsave("doubling_time.png", width=8, height=5)
LS0tCnRpdGxlOiAiQmF5ZXNpYW4gZXN0aW1hdGlvbiBvZiBDT1ZJRC0xOSBlcGlkZW1pYyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKTGFzdCB1cGRhdGU6IEFwcmlsIDJuZCAyMDIwCgojIFRoZW9yeQoKV2UgZGVmaW5lIGFzIGtleSBxdWFudGl0eQoKKiAkSV90JCwgdGhlIF90cnVlXyBudW1iZXIgb2YgbmV3IGluZmVjdGlvbnMgYXQgZGF5ICR0JC4KClRoZSB0d28ga2V5IG9ic2VydmVkIHF1YW50aXRpZXMgYXJlCgoqICRjX3QkLCB0aGUgX3JlcG9ydGVkXyBudW1iZXIgb2YgbmV3IGNhc2VzIGF0IGRheSAkdCQsIGFuZAoqICRkX3QkLCB0aGUgcmVwb3J0ZWQgbnVtYmVyIG9mIG5ldyBkZWF0aHMgYXQgZGF5ICR0JC4KCldlIGFzc3VtZSBhIGdyb3d0aCBtb2RlbCB3aXRoIHNsb3ctZG93biBvZiB0aGUgZm9ybQoKJCQKICBJX3QgPSBJXzAgXGV4cCgoXGFscGhhXzAgLSBcYmV0YSB0KSB0KQokJAp3aXRoIGEgc3RhcnRpbmcgbnVtYmVyIG9mIGluZmVjdGVkIHBlb3BsZSwgJElfMCQsIGFuZCBhIGR5bmFtaWNhbHkgZGVjcmVhc2luZyBncm93dGggcmF0ZSAkXGFscGhhXzAtXGJldGEgdCQuCgpXZSBub3cgY29uc2lkZXIgdGhlIHRlc3RpbmcgcHJvY2VzcywgYW5kIHRoZSBwcm9jZXNzIGJ5IHdoaWNoIHBlb3BsZSBkaWUgb2YgdGhlIGRlc2Vhc2UgYXMgcmFuZG9tIHByb2Nlc3NlcyB3aXRoIGZpeGVkIHByb2JhYmlsaXR5LiBUaGUgcHJvYmFiaWxpdHkgdGhhdCBhIHBlcnNvbiB3aXRoIGFuIGluZmVjdGlvbiBnZXRzIHRlc3RlZCBpcyAkXGdhbW1hJCwgYW5kIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGFuIGluZmVjdGVkIHBlcnNvbiBkaWVzIGlzICRcZGVsdGEkLiBXZSBuZWVkIHR3byBtb3JlIHBhcmFtZXRlcnMgZm9yIG91ciBzYW1wbGluZyBwcm9jZXNzZXMsIHdoaWNoIGFyZSAkXHRhdV90JCwgdGhlIGF2ZXJhZ2UgdGltZSBiZXR3ZWVuIGluZmVjdGlvbiBhbmQgdGVzdCwgYW5kICRcdGF1X1xkZWx0YSQsIHRoZSBhdmVyYWdlIHRpbWUgYmV0d2VlbiBpbmZlY3RvbiBhbmQgZGVhdGguIFdlIGNhbiB0aGVuIGhhdmUgdGhlIGZvbGxvd2luZyBiaW5vbWlhbCBzYW1wbGluZyBwcm9iYWJpbGl0aWVzOgoKJCQKICBcYmVnaW57c3BsaXR9CiAgY190ICZcc2ltIFx0ZXh0e0Jpbn0oY190OyBJX3t0LVx0YXVfdH0sIFxnYW1tYSlcXAogIGRfdCAmXHNpbSBcdGV4dHtCaW59KGRfdDsgSV97dC1cdGF1X1xkZWx0YX0sIFxkZWx0YSkKICBcZW5ke3NwbGl0fQokJAoKSW4gc3VtbWFyeSwgdGhlIGtleSBwYXJhbWV0ZXJzIGFyZSwgdG9nZXRoZXIgd2l0aCBzaG9ydCBkZXNjcmlwdGlvbnMgYW5kIHByaW9yIGluZm9ybWF0aW9uOgoKfCBQYXJhbWV0ZXIgICAgIHwgRGVzY3JpcHRpb24gICAgICAgICAgICAgICAgICAgICAgfCBQcmlvciAgICAgICAgIHwKfC0tLS0tLS0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLXwKfCAkSV8wJCAgICAgICAgIHwgU3RhcnRpbmcgbnIgb2YgaW5mZWN0ZWQgcGVvcGxlICAgfCAkWzAsIFxpbmZ0eSkkIHwKfCAkXGFscGhhXzAkICAgIHwgRXhwLiBzdGFydGluZyBncm93dGggcmF0ZSAgICAgICAgfCAkWzAsIFxpbmZ0eSkkIHwKfCAkXGJldGEkICAgICAgIHwgU2xvdy1kb3duIHJhdGUgICAgICAgICAgICAgICAgICAgfCAkWzAsIFxpbmZ0eSkkIHwKfCAkXGdhbW1hJCAgICAgIHwgUHJvYi4gdG8gZ2V0IHRlc3RlZCAgICAgICAgICAgICAgfCAkWzAsMV0kICAgICAgIHwKCgpJbiBhZGRpdGlvbiwgd2UgZml4IHRoZSBmb2xsb3dpbmcgcGFyYW1ldGVyczoKfCBQYXJhbWV0ZXIgICAgIHwgRGVzY3JpcHRpb24gICAgICAgICAgICAgICAgICAgICAgfCBWYWx1ZSAgICAgfAp8LS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS18CnwgJFx0YXVfdCQgICAgICB8IFRpbWUgZnJvbSBpbmZlY3Rpb24gdG8gIHRlc3QgICAgIHwgNyAgICAgICAgIHwKfCAkXHRhdV9cZGVsdGEkIHwgVGltZSBmcm9tIGluZmVjdGlvbiB0byBkZWF0aCAgICAgfCAxNyAgICAgICAgfAp8ICRcZGVsdGEkICAgICAgfCBEZWF0aCByYXRlICAgICAgICAgICAgICAgICAgICAgICB8IDAuMDEgICAgICB8CgoKTm90ZSB0aGF0IGZvciB0ZWNobmljYWwgcmVhc29ucyB3ZSBpbnRyb2R1Y2Ugc29tZSB1cHBlciBib3VuZHMgb24gdGhlIHByaW9yIHByb2JhYmlsaXRpZXMgaW4gcHJhY3RpY2UsIHRvIGNvbnN0cmFpbiB0aGUgcGFyYW1ldGVyIHNlYXJjaCBzcGFjZSwgYnV0IHRoYXQgaGFzIG5vIGluZmx1ZW5jZSBvbiB0aGUgZXN0aW1hdGVzIChiZWNhdXNlIHRoZSBwb3N0ZXJpb3JzIGFyZSBmYXIgYXdheSBmcm9tIHRoZXNlIHVwcGVyIGJvdW5kYXJpZXMpLgoKU28gd2UgY2FuIHdyaXRlIGRvd24gYSBsaWtlbGlob29kIG9mIHRoZSBkYXRhIGdpdmVuIHRoZSBtb2RlbCBzaW1wbHkgYXMgdGhlIHByb2R1Y3Qgb2YgYWxsIHRoZXNlIGJpbm9taWFsIHByb2JhYmlsaXRpZXMgYWNyb3NzIGFsbCB0aW1lIHBvaW50cyB3aGVyZSBwb3NzaWJsZS4gU286CgokJAogIFxtYXRoY2Fse0x9ID0gXHByb2RfdCBcdGV4dHtCaW59KGNfdDsgSV97dC1cdGF1X3R9LCBcZ2FtbWEpIFxwcm9kX3RcdGV4dHtCaW59KGRfdDsgSV97dC1cdGF1X1xkZWx0YX0sIFxkZWx0YSkgCiQkCndoZXJlIHRoZSBwcm9kdWN0cyBydW4gb3ZlciBhbGwgZGF5cyBmb3Igd2hpY2ggd2UgaGF2ZSBrbm93ICRjX3QkIG9yICRkX3QkLCByZXNwZWN0aXZlbHkuCgpUaGlzIHRoZW4gYWxsb3dzIHVzIHRvIGNvbXB1dGUgcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbnMgb2YgYWxsIHBhcmFtZXRlcnMgdXNpbmcgTW9udGUgQ2FybG8gc2FtcGxpbmcsIGFzIGZvciBleGFtcGxlIGltcGxlbWVudGVkIGluIHRoZSBwYWNrYWdlIFtTdGFuXShodHRwczovL21jLXN0YW4ub3JnL3JzdGFuLykuCgojIEFuYWx5c2lzCgpXZSBmaXJzdCBkZWZpbmVkIHRoZSBtb2RlbCBpbiBbU1RBTl0oaHR0cHM6Ly9tYy1zdGFuLm9yZykgYXMgZm9sbG93czoKCmBgYHtzdGFuIG1vZGVsfQpmdW5jdGlvbnMgewogIHJlYWwgZ3Jvd3RoX21vZGVsKHJlYWwgSTAsIHJlYWwgYWxwaGEwLCByZWFsIGJldGEsIHJlYWwgdCkgewogICAgcmV0dXJuIEkwICogZXhwKChhbHBoYTAgLSBiZXRhICogdCkgKiB0KTsKICB9Cn0KZGF0YSB7IAogIGludCBOOyAKICByZWFsIGNhc2VzW05dOwogIHJlYWwgZGVhdGhzW05dOwogIGludCBkYXlbTl07CiAgcmVhbDxsb3dlcj0wPiB0YXVfdDsKICByZWFsPGxvd2VyPTA+IHRhdV9kZWx0YTsKICByZWFsPGxvd2VyPTA+IGRlbHRhOwp9IApwYXJhbWV0ZXJzIHsKICByZWFsPGxvd2VyPTAsdXBwZXI9MT4gZ2FtbWE7CiAgcmVhbDxsb3dlcj0wLHVwcGVyPTE+IGFscGhhMDsKICByZWFsPGxvd2VyPTAsdXBwZXI9MT4gYmV0YTsKICByZWFsPGxvd2VyPTAsdXBwZXI9MWU2PiBJMDsKfSAKbW9kZWwgewogICAgZm9yKGkgaW4gMTpOKSB7CiAgICAgIHJlYWwgbmMgPSBncm93dGhfbW9kZWwoSTAsIGFscGhhMCwgYmV0YSwgZGF5W2ldIC0gdGF1X3QpOwogICAgICBjYXNlc1tpXSB+IG5vcm1hbChuYyAqIGdhbW1hLCBuYyAqIGdhbW1hICogKDEgLSBnYW1tYSkpOwogICAgICByZWFsIG5kID0gZ3Jvd3RoX21vZGVsKEkwLCBhbHBoYTAsIGJldGEsIGRheVtpXSAtIHRhdV9kZWx0YSk7IAogICAgICBkZWF0aHNbaV0gfiBub3JtYWwobmQgKiBkZWx0YSwgbmQgKiBkZWx0YSAqICgxIC0gZGVsdGEpKTsKICAgIH0KfQpgYGAKCmFuZCBjb21waWxlZCBpdCB3aXRoIFtDbWRTdGFuXShodHRwczovL21jLXN0YW4ub3JnL3VzZXJzL2ludGVyZmFjZXMvY21kc3RhbikuIFRvIHByZXBhcmUgdGhlIGlucHV0IHBhcmFtZXRlcnMsIHdlIGZpcnN0IG5lZWQgdG8gZGV0ZXJtaW5lIGEgdmFsaWQgdGltZSByYW5nZSBmb3Igb3VyIGFuYWx5c2lzIGR1cmluZyB3aGljaCB3ZSBjYW4gYXNzdW1lIGV4cG9uZW50aWFsIGdyb3d0aDoKCmBgYHtyIHBsb3R0aW5nIHJhdGVzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KG1hZ3JpdHRyKQpsaWJyYXJ5KGdncGxvdDIpCgpkYXQgPC0gY292aWQxOWdlcm1hbnk6OmdldF9SS0lfdGltZXNlcmllcygpICU+JSBjb3ZpZDE5Z2VybWFueTo6Z3JvdXBfUktJX3RpbWVzZXJpZXMoKQpkYXQgJT4lCiAgZHBseXI6OnNlbGVjdChEYXRlLCBOdW1iZXJOZXdUZXN0ZWRJbGwsIE51bWJlck5ld0RlYWQpICU+JQogIHRpZHlyOjpwaXZvdF9sb25nZXIoYyhOdW1iZXJOZXdUZXN0ZWRJbGwsIE51bWJlck5ld0RlYWQpLCBuYW1lc190bz0idHlwZSIsIHZhbHVlc190byA9ICJDb3VudCIpICU+JQogIGdncGxvdCgpICsgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBEYXRlLCB5ID0gQ291bnQsIGNvbD10eXBlKSkgKyBzY2FsZV95X2xvZzEwKCkKYGBgCgpUaGlzIHJldmVhbHMgYSBsYWcgb2YgYWJvdXQgMTAgZGF5cyBiZXR3ZWVuIHRoZSBvbnNldCBvZiBleHBvbmVudGlhbCBncm93dGggYmV0d2VlbiB0ZXN0IGNhc2VzIGFuZCBkZWF0aHMuIFdpdGggYXJvdW5kIDE3IGRheXMgZnJvbSBpbmZlY3Rpb24gdG8gZGVhdGggKHNlZSBbdGhpcyBwb3N0XShodHRwczovL21lZGl1bS5jb20vQHRvbWFzcHVleW8vY29yb25hdmlydXMtYWN0LXRvZGF5LW9yLXBlb3BsZS13aWxsLWRpZS1mNGQzZDljZDk5Y2EpKSwgdGhpcyB5aWVsZHMgYW4gYXZlcmFnZSB0aW1lIG9mIDcgZGF5cyBiZXR3ZWVuIGluZmVjdGlvbiBhbmQgcmVwb3J0ZWQgdGVzdCBjYXNlLCBqdXN0aWZ5aW5nIG91ciBjaG9pY2UgZm9yICRcdGF1X3Q9NyQuCgpXZSB0aGVuIHByZXBhcmVkIHRoZSBpbnB1dCBmb3IgdGhlIG1vZGVsOgoKYGBge3IgaW5wdXQgZGF0YSBhbmQgcGFyYW1zfQpzZWxlY3RlZF9kYXQgPC0gZHBseXI6OmZpbHRlcihkYXQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERhdGUgPj0gYXMuUE9TSVhjdCgiMjAyMC0wMi0yMyIpICYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBEYXRlIDw9IGFzLlBPU0lYY3QoIjIwMjAtMDMtMzEiKSkKCmRheSA8LSBhcy5udW1lcmljKGRpZmZ0aW1lKHNlbGVjdGVkX2RhdCREYXRlLCBhcy5EYXRlKCIyMDIwLTAyLTIzIiksIHVuaXRzPSJkYXlzIikpCmNhc2VzIDwtIHNlbGVjdGVkX2RhdCROdW1iZXJOZXdUZXN0ZWRJbGwKZGVhdGhzIDwtIHNlbGVjdGVkX2RhdCROdW1iZXJOZXdEZWFkCk4gPC0gbGVuZ3RoKGNhc2VzKQp0YXVfdCA8LSA3CnRhdV9kZWx0YSA8LSAxNwpkZWx0YSA8LSAwLjAxCmFscGhhMCA8LSAwLjEKYmV0YSA8LSAwLjEKSTAgPC0gMTAKZ2FtbWEgPC0gMC41CgpkdW1wKGMoImRheSIsICJjYXNlcyIsICJkZWF0aHMiLCAiTiIsICJ0YXVfdCIsICJ0YXVfZGVsdGEiLCAiZGVsdGEiKSwgZmlsZT0iY292aWQxOV9tb2RlbC5kYXRhLlIiKQpkdW1wKGMoImFscGhhMCIsICJiZXRhIiwgIkkwIiwgImdhbW1hIiksIGZpbGU9ImNvdmlkMTlfbW9kZWwuaW5pdC5SIikKYGBgCgpXZSB0aGVuIHJhbiB0aGUgbW9kZWwgYW5kIHNhbXBsZWQgZnJvbSB0aGUgcG9zdGVyaW9yIHVzaW5nIApgYGB7YmFzaH0KIyBUaGUgc3RhbiBtb2RlbCB3YXMgY29tcGlsZWQgdG8gLi9jb3ZpZDE5X21vZGVsCi4vY292aWQxOV9tb2RlbCBzYW1wbGUgZGF0YSBmaWxlPWNvdmlkMTlfbW9kZWwuZGF0YS5SIGluaXQ9Y292aWQxOV9tb2RlbC5pbml0LlIgb3V0cHV0IGZpbGU9c2FtcGxpbmcuY3N2CmBgYAoKV2UgbG9hZCB0aGUgcG9zdGVyaW9yIGFzIGEgdGFibGU6CmBgYHtyIGxvYWRpbmcsIG1lc3NhZ2U9RkFMU0V9CnN0YW5fb3V0cHV0IDwtIHJlYWRyOjpyZWFkX2Nzdigic2FtcGxpbmcuY3N2IiwgY29tbWVudCA9ICIjIikKYGBgCgpMZXQncyBmaXJzdCBqdXN0IGxvb2sgYXQgbWFyZ2luYWwgc3VtbWFyeSBzdGF0aXN0aWNzIGZvciBlYWNoIHBhcmFtZXRlcgpgYGB7ciBzdGFuIHN1bW1hcnl9CnN0YW5fb3V0cHV0ICU+JQogIHRpZHlyOjpwaXZvdF9sb25nZXIoYygnSTAnLCAnYWxwaGEwJywgJ2JldGEnLCAnZ2FtbWEnKSwKICAgICAgICAgICAgICAgICAgICAgIG5hbWVzX3RvID0gInBhcmFtIiwKICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWx1ZSIpICU+JQogIGRwbHlyOjpncm91cF9ieShwYXJhbSkgJT4lCiAgZHBseXI6OnN1bW1hcmlzZShwZXJjNSA9IHF1YW50aWxlKHZhbHVlLCAwLjA1KSwKICAgICAgICAgICAgICAgICAgIHBlcmM1MF9tZWRpYW4gPSBtZWRpYW4odmFsdWUpLAogICAgICAgICAgICAgICAgICAgcGVyYzk1ID0gcXVhbnRpbGUodmFsdWUsIDAuOTUpKQpgYGAKCkhlcmUgYXJlIGNvcnJlbGF0aW9ucyBiZXR3ZWVuIHRoZXNlIHBvc3RlcmlvcnM6CmBgYHtyIG1lc3NhZ2U9RkFMU0V9CnN0YW5fb3V0cHV0ICU+JQogIGRwbHlyOjpzZWxlY3QoSTAsIGFscGhhMCwgYmV0YSwgZ2FtbWEpICU+JQogIEdHYWxseTo6Z2dwYWlycygpCmBgYAoKVGhpcyBpcyBhbGwgcmVhc29uYWJsZSBhbmQgbW9yZSBvciBsZXNzIGV4cGVjdGVkIGdpdmVuIHRoZSBtb2RlbCBzZXQgdXAuIEZvciBleGFtcGxlLCB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiAkXGFscGhhXzAkIGFuZCAkXGJldGEkIGlzIGV4cGVjdGVkICh5b3UgY2FuIGFmZm9yZCB0byBoYXZlIHN0ZWVwZXIgZ3Jvd3RoIGluIHRoZSBiZWdpbm5pbmcgaWYgeW91IGhhdmUgYSBzdHJvbmdlciBzbG93LWRvd24pLiAKCiMjIFZpc3VhbGlzaW5nIHRoZSBtb2RlbAoKT0ssIGhlcmUgYXJlIHRoZSBtb2RlbCBwcmVkaWN0aW9ucwoKU28gd2UgY2FuIHVzZSB0aGUgbWFueSBzYW1wbGVzIGZyb20gb3VyIHBvc3RlcmlvciB0byBjaGVjayBzb21lIHByZWRpY3Rpb25zLiBGaXJzdCwgd2UgcHJlcGFyZSB0aGUgbW9kZWwgcHJlZGljdGlvbnMgaW4gYSBuZXcgdGFibGUgYHBsb3RfY3VydmVzYDoKCmBgYHtyIHBsb3RfY3VydmVzfQpncm93dGhfZnVuYyA8LSBmdW5jdGlvbihJMCwgYWxwaGEwLCBiZXRhLCB0KSB7CiAgcmV0dXJuKEkwICogZXhwKHQgKiAoYWxwaGEwIC0gYmV0YSAqIHQpKSkKfQoKZGF5X3RibCA8LSB0aWJibGU6OnRpYmJsZShkYXlzID0gMDo2MCkgJT4lCiAgZHBseXI6Om11dGF0ZShkYXRlID0gYXMuUE9TSVhjdCgiMjAyMC0wMi0yMyIpICsgbHVicmlkYXRlOjpkYXlzKGRheXMpKQpwbG90X2N1cnZlcyA8LSBzdGFuX291dHB1dCAlPiUKICBkcGx5cjo6c2VsZWN0KGFscGhhMCwgZ2FtbWEsIGJldGEsIEkwKSAlPiUKICBkcGx5cjo6bXV0YXRlKGlkPTE6bnJvdyhzdGFuX291dHB1dCkpICU+JQogIHRpZHlyOjpleHBhbmRfZ3JpZChkYXlfdGJsKSAlPiUKICBkcGx5cjo6bXV0YXRlKHRydWVfY2FzZXMgPSBncm93dGhfZnVuYyhJMCwgYWxwaGEwLCBiZXRhLCBkYXlzKSwKICAgICAgICAgICAgICAgIHByZWRpY3RlZF90ZXN0Y2FzZXMgPSBnYW1tYSAqIGdyb3d0aF9mdW5jKEkwLCBhbHBoYTAsIGJldGEsIGRheXMgLSA3KSwKICAgICAgICAgICAgICAgIHByZWRpY3RlZF9kZWF0aHMgPSAwLjAxICogZ3Jvd3RoX2Z1bmMoSTAsIGFscGhhMCwgYmV0YSwgZGF5cyAtIDE3KSkgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGlkKSAlPiUKICBkcGx5cjo6bXV0YXRlKGN1bV90cnVlX2Nhc2VzID0gY3Vtc3VtKHRydWVfY2FzZXMpLAogICAgICAgICAgICAgICAgY3VtX3ByZWRpY3RlZF90ZXN0Y2FzZXMgPSBjdW1zdW0ocHJlZGljdGVkX3Rlc3RjYXNlcyksCiAgICAgICAgICAgICAgICBjdW1fcHJlZGljdGVkX2RlYXRocyA9IGN1bXN1bShwcmVkaWN0ZWRfZGVhdGhzKSkgJT4lCiAgZHBseXI6OnVuZ3JvdXAoKQpgYGAKCldlIGNhbiB0aGVuIHBsb3QgdGhlIHBvc3RlcmlvcnMgYXMgYSBmdW5jdGlvbiBvZiB0aW1lIHRvZ2V0aGVyIHdpdGggdGhlIGRhdGE6CgpgYGB7ciBtb2RlbCBwb3N0ZXJpb3JzIG92ZXIgdGltZX0KcGxvdF9jdXJ2ZXMgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGRhdGUpICU+JQogIGRwbHlyOjpzdW1tYXJpc2UoCiAgICB0cnVlX2Nhc2VzXzUgPSBxdWFudGlsZSh0cnVlX2Nhc2VzLCAwLjA1KSwKICAgIHRydWVfY2FzZXNfOTUgPSBxdWFudGlsZSh0cnVlX2Nhc2VzLCAwLjk1KSwKICAgIHByZWRpY3RlZF90ZXN0Y2FzZXNfNSA9IHF1YW50aWxlKHByZWRpY3RlZF90ZXN0Y2FzZXMsIDAuMDUpLAogICAgcHJlZGljdGVkX3Rlc3RjYXNlc185NSA9IHF1YW50aWxlKHByZWRpY3RlZF90ZXN0Y2FzZXMsIDAuOTUpLAogICAgcHJlZGljdGVkX2RlYXRoc181ID0gcXVhbnRpbGUocHJlZGljdGVkX2RlYXRocywgMC4wNSksCiAgICBwcmVkaWN0ZWRfZGVhdGhzXzk1ID0gcXVhbnRpbGUocHJlZGljdGVkX2RlYXRocywgMC45NSkKICApICU+JQogIGdncGxvdCgpICsKICAgIGdlb21fcmliYm9uKG1hcHBpbmcgPSBhZXMoeCA9IGRhdGUsIHltaW4gPSB0cnVlX2Nhc2VzXzUsIHltYXg9dHJ1ZV9jYXNlc185NSksCiAgICAgICAgICAgICAgICBmaWxsID0gImRhcmsgZ3JlZW4iLCBhbHBoYSA9IDAuNSkgKwogICAgZ2VvbV9yaWJib24obWFwcGluZyA9IGFlcyh4ID0gZGF0ZSwgeW1pbiA9IHByZWRpY3RlZF90ZXN0Y2FzZXNfNSwgeW1heD1wcmVkaWN0ZWRfdGVzdGNhc2VzXzk1KSwKICAgICAgICAgICAgICAgIGZpbGwgPSAiYmx1ZSIsIGFscGhhID0gMC41KSArCiAgICBnZW9tX3JpYmJvbihtYXBwaW5nID0gYWVzKHggPSBkYXRlLCB5bWluID0gcHJlZGljdGVkX2RlYXRoc181LCB5bWF4PXByZWRpY3RlZF9kZWF0aHNfOTUpLAogICAgICAgICAgICAgICAgZmlsbCA9ICJyZWQiLCBhbHBoYSA9IDAuNSkgKwogICAgZ2VvbV9wb2ludChkYXQsIG1hcHBpbmcgPSBhZXMoeCA9IERhdGUsIHkgPSBOdW1iZXJOZXdUZXN0ZWRJbGwpLAogICAgICAgICAgICAgICBjb2wgPSAiYmx1ZSIpICsKICAgIGdlb21fcG9pbnQoZHBseXI6OmZpbHRlcihkYXQsIE51bWJlck5ld0RlYWQgPiAwKSwgbWFwcGluZyA9IGFlcyh4ID0gRGF0ZSwgeSA9IE51bWJlck5ld0RlYWQpLCBjb2wgPSAicmVkIikgKwogICAgc2NhbGVfeV9sb2cxMChsYWJlbHMgPSBmdW5jdGlvbih4KSBmb3JtYXQoeCwgYmlnLm1hcmsgPSAiLCIsIHNjaWVudGlmaWMgPSBGQUxTRSksCiAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoMSwgMTAsIDEwMCwgMTAwMCwgMTAwMDAsIDEwMDAwMCksCiAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMSwgMWU1KSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIGdndGl0bGUoIkJheWVzaWFuIG1vZGVsIHByZWRpY3Rpb25zIikgKwogICAgdGhlbWUoYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGl0bGUueT1lbGVtZW50X2JsYW5rKCkpICsKICAgIGFubm90YXRlKCJ0ZXh0IiwgeCA9IGFzLlBPU0lYY3QoIjIwMjAtMDMtNSIpLCB5ID0gNWU0LCBsYWJlbCA9ICJ0cnVlIGluZmVjdGlvbnNcbihnaXZlbiAxJSBmYXRhbGl0eSByYXRlKSIsIGNvbD0iZGFyayBncmVlbiIpICsKICAgIGFubm90YXRlKCJ0ZXh0IiwgeCA9IGFzLlBPU0lYY3QoIjIwMjAtMDMtMjUiKSwgeSA9IDFlMywgbGFiZWwgPSAidGVzdGVkIHBvc2l0aXZlIiwgY29sPSJibHVlIikgKwogICAgYW5ub3RhdGUoInRleHQiLCB4ID0gYXMuUE9TSVhjdCgiMjAyMC0wMy0yNSIpLCB5ID0gMTAsIGxhYmVsID0gImRlYXRocyIsIGNvbD0icmVkIikgCmBgYAoKYGBge3IgY3VtdWxhdGl2ZSBtb2RlbCBwb3N0ZXJpb3JzIG92ZXIgdGltZX0KcGxvdF9jdXJ2ZXMgJT4lCiAgZHBseXI6Omdyb3VwX2J5KGRhdGUpICU+JQogIGRwbHlyOjpzdW1tYXJpc2UoCiAgICBjdW1fdHJ1ZV9jYXNlc181ID0gcXVhbnRpbGUoY3VtX3RydWVfY2FzZXMsIDAuMDUpLAogICAgY3VtX3RydWVfY2FzZXNfOTUgPSBxdWFudGlsZShjdW1fdHJ1ZV9jYXNlcywgMC45NSksCiAgICBjdW1fcHJlZGljdGVkX3Rlc3RjYXNlc181ID0gcXVhbnRpbGUoY3VtX3ByZWRpY3RlZF90ZXN0Y2FzZXMsIDAuMDUpLAogICAgY3VtX3ByZWRpY3RlZF90ZXN0Y2FzZXNfOTUgPSBxdWFudGlsZShjdW1fcHJlZGljdGVkX3Rlc3RjYXNlcywgMC45NSksCiAgICBjdW1fcHJlZGljdGVkX2RlYXRoc181ID0gcXVhbnRpbGUoY3VtX3ByZWRpY3RlZF9kZWF0aHMsIDAuMDUpLAogICAgY3VtX3ByZWRpY3RlZF9kZWF0aHNfOTUgPSBxdWFudGlsZShjdW1fcHJlZGljdGVkX2RlYXRocywgMC45NSkKICApICU+JQogIGdncGxvdCgpICsKICAgIGdlb21fcmliYm9uKG1hcHBpbmcgPSBhZXMoeCA9IGRhdGUsIHltaW4gPSBjdW1fdHJ1ZV9jYXNlc181LCB5bWF4PWN1bV90cnVlX2Nhc2VzXzk1KSwKICAgICAgICAgICAgICAgIGZpbGwgPSAiZGFyayBncmVlbiIsIGFscGhhID0gMC41KSArCiAgICBnZW9tX3JpYmJvbihtYXBwaW5nID0gYWVzKHggPSBkYXRlLCB5bWluID0gY3VtX3ByZWRpY3RlZF90ZXN0Y2FzZXNfNSwgeW1heD1jdW1fcHJlZGljdGVkX3Rlc3RjYXNlc185NSksCiAgICAgICAgICAgICAgICBmaWxsID0gImJsdWUiLCBhbHBoYSA9IDAuNSkgKwogICAgZ2VvbV9yaWJib24obWFwcGluZyA9IGFlcyh4ID0gZGF0ZSwgeW1pbiA9IGN1bV9wcmVkaWN0ZWRfZGVhdGhzXzUsIHltYXg9Y3VtX3ByZWRpY3RlZF9kZWF0aHNfOTUpLAogICAgICAgICAgICAgICAgZmlsbCA9ICJyZWQiLCBhbHBoYSA9IDAuNSkgKwogICAgZ2VvbV9wb2ludChkYXQsIG1hcHBpbmcgPSBhZXMoeCA9IERhdGUsIHkgPSBDdW1OdW1iZXJUZXN0ZWRJbGwpLAogICAgICAgICAgICAgICBjb2wgPSAiYmx1ZSIpICsKICAgIGdlb21fcG9pbnQoZHBseXI6OmZpbHRlcihkYXQsIEN1bU51bWJlckRlYWQgPiAwKSwgbWFwcGluZyA9IGFlcyh4ID0gRGF0ZSwgeSA9IEN1bU51bWJlckRlYWQpLCBjb2wgPSAicmVkIikgKwogICAgc2NhbGVfeV9sb2cxMChsYWJlbHMgPSBmdW5jdGlvbih4KSBmb3JtYXQoeCwgYmlnLm1hcmsgPSAiLCIsIHNjaWVudGlmaWMgPSBGQUxTRSksCiAgICAgICAgICAgICAgICAgIGJyZWFrcyA9IGMoMSwgMTAsIDEwMCwgMTAwMCwgMTAwMDAsIDEwMDAwMCwgMTAwMDAwMCksCiAgICAgICAgICAgICAgICAgIGxpbWl0cyA9IGMoMSwgMWU3KSkgKwogICAgdGhlbWVfbWluaW1hbCgpICsKICAgIGdndGl0bGUoIkJheWVzaWFuIG1vZGVsIHByZWRpY3Rpb25zIikgKwogICAgdGhlbWUoYXhpcy50aXRsZS54PWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgIGF4aXMudGl0bGUueT1lbGVtZW50X2JsYW5rKCkpICsKICAgIGFubm90YXRlKCJ0ZXh0IiwgeCA9IGFzLlBPU0lYY3QoIjIwMjAtMDMtMTAiKSwgeSA9IDFlNiwgbGFiZWwgPSAidHJ1ZSBpbmZlY3Rpb25zXG4oZ2l2ZW4gMSUgZmF0YWxpdHkgcmF0ZSkiLCBjb2w9ImRhcmsgZ3JlZW4iKSArCiAgICBhbm5vdGF0ZSgidGV4dCIsIHggPSBhcy5QT1NJWGN0KCIyMDIwLTAzLTIwIiksIHkgPSAyZTMsIGxhYmVsID0gInRlc3RlZCBwb3NpdGl2ZSIsIGNvbD0iYmx1ZSIpICsKICAgIGFubm90YXRlKCJ0ZXh0IiwgeCA9IGFzLlBPU0lYY3QoIjIwMjAtMDMtMjAiKSwgeSA9IDIwLCBsYWJlbCA9ICJkZWF0aHMiLCBjb2w9InJlZCIpIApgYGAKCmBgYHtyIHNhdmUgZmlnMX0KZ2dzYXZlKCJtb2RlbF9wcmVkaWN0aW9ucy5wbmciLCB3aWR0aCA9IDgsIGhlaWdodCA9IDUpCmBgYAoKV2UgY2FuIGNvbnZlcnQgdGhlIGdyb3d0aCByYXRlIGludG8gYSBkb3VibGluZyB0aW1lIGFuZCBwbG90IHRoYXQgYXMgd2VsbDoKCmBgYHtyIGRvdWJsaW5nIHRpbWV9CmRvdWJsaW5nX3RpbWUgPC0gZnVuY3Rpb24oYWxwaGEwLCBiZXRhLCB0KSB7CiAgcmV0dXJuIChsb2coMikgLyAoYWxwaGEwIC0gYmV0YSAqIHQpKQp9CgpkYXlfdGJsIDwtIHRpYmJsZTo6dGliYmxlKGRheXMgPSAwOjQ1KSAlPiUKICBkcGx5cjo6bXV0YXRlKGRhdGUgPSBhcy5QT1NJWGN0KCIyMDIwLTAyLTIzIikgKyBsdWJyaWRhdGU6OmRheXMoZGF5cykpCmR0X2N1cnZlcyA8LSBzdGFuX291dHB1dCAlPiUKICBkcGx5cjo6c2VsZWN0KGFscGhhMCwgYmV0YSkgJT4lCiAgZHBseXI6Om11dGF0ZShpZD0xOm5yb3coc3Rhbl9vdXRwdXQpKSAlPiUKICB0aWR5cjo6ZXhwYW5kX2dyaWQoZGF5X3RibCkgJT4lCiAgZHBseXI6Om11dGF0ZShkdCA9IGRvdWJsaW5nX3RpbWUoYWxwaGEwLCBiZXRhLCBkYXlzKSkKCmR0X2N1cnZlcyAlPiUKICBkcGx5cjo6Z3JvdXBfYnkoZGF0ZSkgJT4lCiAgZHBseXI6OnN1bW1hcmlzZShkdDUgPSBxdWFudGlsZShkdCwgMC4wNSksIGR0NTAgPSBxdWFudGlsZShkdCwgMC41KSwgZHQ5NSA9IHF1YW50aWxlKGR0LCAwLjk1KSkgJT4lCmdncGxvdCgpICsKICBnZW9tX3JpYmJvbihtYXBwaW5nID0gYWVzKHggPSBkYXRlLCB5bWluID0gZHQ1LCB5bWF4PWR0OTUpLCBhbHBoYT0wLjUpICsKICBnZW9tX2xpbmUobWFwcGluZyA9IGFlcyh4ID0gZGF0ZSwgeSA9IGR0NTApKSArIAogIHRoZW1lX21pbmltYWwoKSArCiAgZ2d0aXRsZSgiTW9kZWxsaW5nIG9mIGRvdWJsaW5nIHRpbWUgKGluIGRheXMpIikgKwogIHRoZW1lKGF4aXMudGl0bGUueD1lbGVtZW50X2JsYW5rKCksCiAgICAgICAgYXhpcy50aXRsZS55PWVsZW1lbnRfYmxhbmsoKSkgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGFzLlBPU0lYY3QoIjIwMjAtMDQtMDEiKSkKYGBgCgpgYGB7ciBzYXZlIGZpZzJ9Cmdnc2F2ZSgiZG91YmxpbmdfdGltZS5wbmciLCB3aWR0aD04LCBoZWlnaHQ9NSkKYGBgCgoK