This is an R Markdown Notebook. When you execute code within the notebook, the results appear beneath the code.
Try executing this chunk by clicking the Run button within the chunk or by placing your cursor inside it and pressing Cmd+Shift+Enter.
library(nlme)
fm1 <- nlme(height ~ SSasymp(age, Asym, R0, lrc),
data = Loblolly,
fixed = Asym + R0 + lrc ~ 1,
random = Asym ~ 1,
start = c(Asym = 103, R0 = -8.5, lrc = -3.3))
summary(fm1)
fm2 <- update(fm1, random = pdDiag(Asym + lrc ~ 1))
summary(fm2)
library(brms)
Loading required package: Rcpp
Loading 'brms' package (version 2.9.0). Useful instructions
can be found by typing help('brms'). A more detailed introduction
to the package is available through vignette('brms_overview').
library(brms)
library(tidyverse)
[30m── [1mAttaching packages[22m ──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.2.1 ──[39m
[30m[32m✔[30m [34mggplot2[30m 3.1.0 [32m✔[30m [34mpurrr [30m 0.2.5
[32m✔[30m [34mtibble [30m 2.1.3 [32m✔[30m [34mdplyr [30m 0.8.1
[32m✔[30m [34mtidyr [30m 0.8.2 [32m✔[30m [34mstringr[30m 1.3.1
[32m✔[30m [34mreadr [30m 1.3.1 [32m✔[30m [34mforcats[30m 0.3.0[39m
[30m── [1mConflicts[22m ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
[31m✖[30m [34mdplyr[30m::[32mfilter()[30m masks [34mstats[30m::filter()
[31m✖[30m [34mdplyr[30m::[32mlag()[30m masks [34mstats[30m::lag()[39m
model_1 <- brm(y ~ x + 1 + (1|id), data = df, warmup = 1000, iter = 3000, chains = 2, inits= "random", cores=2)
summary(model_1)
marginal_effects(model_1)
me_loss_1 <- marginal_effects(
model_1, conditions = data.frame(id = c('a','b','c')),
re_formula = NULL, method = "predict"
)
plot(me_loss_1, plot = F, points = T)[[1]] + ggtitle("My custom ggplot here!")
model_2 <- brm(y ~ x + 1 + (x+1|id), data = df, warmup = 1000, iter = 3000, chains = 2, inits= "random", cores=2)
summary(model_2)
marginal_effects(model_2)
me_loss_2 <- marginal_effects(
model_2, conditions = data.frame(id = c('a','b','c')),
re_formula = NULL, method = "predict"
)
plot(me_loss_2, plot = F, points = T)[[1]] + ggtitle("My custom ggplot here!")
model_3 <- brm(y ~ x + 1 + (x|id), data = df, warmup = 1000, iter = 3000, chains = 2, inits= "random", cores=2)
summary(model_3)
marginal_effects(model_3)
me_loss_3 <- marginal_effects(
model_3, conditions = data.frame(id = c('a','b','c')),
re_formula = NULL, method = "predict"
)
plot(me_loss_3, plot = F, points = T)[[1]] + ggtitle("My custom ggplot here!")
df <- read_csv("06_13_19_PYO_FRAP_glycerol_full_norm.csv")
Parsed with column specification:
cols(
.default = col_double(),
filename = [31mcol_character()[39m,
condition = [31mcol_character()[39m,
agarose = [31mcol_character()[39m,
PYO = [31mcol_character()[39m,
objective = [31mcol_character()[39m,
frames = [31mcol_character()[39m,
excitation = [31mcol_character()[39m,
bleach = [31mcol_character()[39m,
fast = [31mcol_character()[39m
)
See spec(...) for full column specifications.
df_fast_1 <- df %>% filter(fast == 'repFAST' & id==1)
df_fast <- df %>% filter(fast == 'repFAST')
ggplot(df_fast_1, aes(x = norm_time, y = full_norm_int, color = condition)) +
geom_point(shape = 21)+
scale_color_viridis_d() +
facet_wrap(~condition)
prior1 <- prior(normal(0.8, 0.2), nlpar = "I0") +
prior(normal(0.6, 0.2), nlpar = "a")+
prior(normal(0.2, 0.1), nlpar = "B")
frap_model_1 <- brm(bf(full_norm_int~I0 - a*exp(-B * norm_time), I0 + a + B~1+(1|condition), nl = T),
data = df_fast_1,
prior = prior1,
chains = 2,
cores = 2,
iter = 500
)
summary(frap_model_1)
marginal_effects(frap_model_1)
me_frap_model_1 <- marginal_effects(
frap_model_1, conditions = data.frame(condition = c('0pctGlyc','10pctGlyc','20pctGlyc','50pctGlyc')),
re_formula = NULL, method = "predict"
)
plot(me_frap_model_1, plot = F, points = T)[[1]] + ggtitle("My custom ggplot here!")
frap_model_2 <- brm(bf(full_norm_int~I0 - a*exp(-B * norm_time), I0 + a + B~1+(1|condition), nl = T),
data = df_fast_1,
prior = prior1,
chains = 4,
cores = 2
)
summary(frap_model_2)
marginal_effects(frap_model_2)
me_frap_model_2 <- marginal_effects(
frap_model_2, conditions = data.frame(condition = c('0pctGlyc','10pctGlyc','20pctGlyc','50pctGlyc')),
re_formula = NULL, method = "predict"
)
plot(me_frap_model_2, plot = F, points = T)[[1]] + ggtitle("My custom ggplot here!")
plot(frap_model_2)
Can we set a group level parameter that doesn’t have a hyperparameter by doing:
#leave out intercept in parameter specification. Only depends on group level intercept?...
frap_model_3 <- brm(bf(full_norm_int~I0 - a*exp(-B * norm_time), I0 + a + B~(1|condition), nl = T),
data = df_fast_1,
prior = prior1,
chains = 4,
cores = 2
)
summary(frap_model_3)
marginal_effects(frap_model_3)
me_frap_model_3 <- marginal_effects(
frap_model_3, conditions = data.frame(condition = c('0pctGlyc','10pctGlyc','20pctGlyc','50pctGlyc')),
re_formula = NULL, method = "predict"
)
plot(me_frap_model_3, plot = F, points = T)[[1]] + ggtitle("My custom ggplot here!")
plot(frap_model_3)
Prior predictive check:
prior1 <- prior(normal(0.8, 0.2), nlpar = "I0") +
prior(normal(0.6, 0.2), nlpar = "a")+
prior(normal(0.2, 0.1), nlpar = "B")
frap_prior_pc <- brm(bf(full_norm_int~I0 - a*exp(-B * norm_time), I0 + a + B~(1|condition), nl = T),
data = df_fast_1,
prior = prior1,
chains = 4,
cores = 2,
iter = 500,
sample_prior = 'only'
)
plot(frap_prior_pc, N = 3)
Really what we want is to have each condition in its own hierarchical model, then we compare the hyperparameter estimates from each hierarchy to compare the conditions. This is because we don’t think there’s a true hyperparameter that controls the FRAPS in the different conditions…but there should be ones among the replicates
frap_model_0pct <- brm(bf(full_norm_int~I0 - a*exp(-B * norm_time), I0 + a + B~1+(1|id), nl = T),
data = df_fast %>% filter(condition== '0pctGlyc'),
prior = prior1,
chains = 4,
cores = 2,
control = list(adapt_delta = 0.95, max_treedepth = 15)
)
marginal_effects(frap_model_0pct)
me_frap_model_0pct <- marginal_effects(
frap_model_0pct, conditions = data.frame(id = c(1,2,3)),
re_formula = NULL, method = "predict"
)
plot(me_frap_model_0pct, plot = F, points = T)[[1]] + ggtitle("My custom ggplot here!") + ylim(-2,2)
plot(frap_model_0pct, N = 3)
frap_model_10pct <- brm(bf(full_norm_int~I0 - a*exp(-B * norm_time), I0 + a + B~1+(1|id), nl = T),
data = df_fast %>% filter(condition== '10pctGlyc'),
prior = prior1,
chains = 4,
cores = 2,
control = list(adapt_delta = 0.9, max_treedepth = 12)
)
summary(frap_model_0pct)
summary(frap_model_10pct)
me_frap_model_10pct <- marginal_effects(
frap_model_10pct, conditions = data.frame(id = c(1,2,3)),
re_formula = NULL, method = "predict"
)
plot(me_frap_model_10pct, plot = F, points = T)[[1]] + ggtitle("My custom ggplot here!") + ylim(-2,2)
plot(frap_model_10pct, N = 3)
For some reason I started with 20 percent glycerol. After a lot of troubleshooting, going back and fitting a single curve changing priors etc, I think the main issue was that I needed to set the lower bound (lb) for the priors to zero to avoid getting negative parameter values…now the model seems to be working pretty well and giving reasonable parameter estimates:
Let’s walk through what worked here.
First, the models were running very slowly…because this is essentially controlling stan that could be for a lot of reasons. The internet suggested two specific commands to speed up performance in rstan:
library(rstan)
rstan_options(auto_write = TRUE)
options(mc.cores = parallel::detectCores())
Next, I played around with the priors a good bit, since I had the estimates from the nls fits. We could make these more weakly informative, but comparing to the posterior shows that I think the data informed the model pretty well:
prior20 <- prior(normal(0.75, 0.2), nlpar = "I0", lb = 0) +
prior(normal(0.6, 0.1), nlpar = "a", lb = 0)+
prior(normal(0.15, 0.05), nlpar = "B", lb = 0)
The nice thing is that this syntax is actually really straightforward and similar to how Justin Bois taught us to write out priors and models…Note that there are some parameters / priors that are sort of implied and not explicit (e.g. sigma for gaussian process?), but the core of it is that my model has three nonlinear parameters and we specify a simple prior for each one.
Finally, we can actually call the brm
function which writes the stan file / model compiles (in C++) and runs the model we write. Note that this command will take a while to run (but significantly faster than before) - I think it was less than 10 min. It is definitely worth starting on a smaller dataset with fewer chains and iterations when trying to get the model up and running well. Also, note that there is a file option to save the model / run in an external file for later use (either with ‘file’ parameter or with save
command):
frap_model_20pct <- brm(bf(full_norm_int~I0 - a*exp(-B * norm_time), I0 + a + B~1+(1|id), nl = T),
data = df_fast %>% filter(condition== '20pctGlyc'),
prior = prior20,
chains = 4,
cores = 2,
iter = 2000,
inits = '0',
control = list(adapt_delta = 0.99, max_treedepth = 20)
)
save(frap_model_20pct, file = "frap_20pct_hier_fit_1.rda")
This should produce some helpful status updates about how the chains are running and how long it takes…and probably some warnings about divergences or more serious problems (e.g. uninformative chains).
Next, I think the first thing to do is look at the summary of the run:
summary(frap_model_20pct)
There were 20 divergent transitions after warmup. Increasing adapt_delta above 0.99 may help.
See http://mc-stan.org/misc/warnings.html#divergent-transitions-after-warmup
Family: gaussian
Links: mu = identity; sigma = identity
Formula: full_norm_int ~ I0 - a * exp(-B * norm_time)
I0 ~ 1 + (1 | id)
a ~ 1 + (1 | id)
B ~ 1 + (1 | id)
Data: df_fast %>% filter(condition == "20pctGlyc") (Number of observations: 1440)
Samples: 4 chains, each with iter = 2000; warmup = 1000; thin = 1;
total post-warmup samples = 4000
Group-Level Effects:
~id (Number of levels: 3)
Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
sd(I0_Intercept) 0.05 0.07 0.01 0.25 815 1.01
sd(a_Intercept) 0.10 0.12 0.01 0.42 1794 1.00
sd(B_Intercept) 0.02 0.05 0.00 0.12 734 1.00
Population-Level Effects:
Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
I0_Intercept 0.73 0.03 0.66 0.80 714 1.00
a_Intercept 0.58 0.05 0.48 0.69 1750 1.00
B_Intercept 0.13 0.02 0.10 0.17 742 1.00
Family Specific Parameters:
Estimate Est.Error l-95% CI u-95% CI Eff.Sample Rhat
sigma 0.09 0.00 0.08 0.09 3684 1.00
Samples were drawn using sampling(NUTS). For each parameter, Eff.Sample
is a crude measure of effective sample size, and Rhat is the potential
scale reduction factor on split chains (at convergence, Rhat = 1).
Here you can see some really nice information. First, there are notable warnings about the run - here we had a few divergences, which is not that serious, but these warnings can be very helpful for more serious problems. Next brms tells us the full formula of the model we specified - Note that there is some shorthand to how the parameters / grouping can be specified, so this let’s you make sure you’re on the same page with the brms formula parsing.
Next there are three groups of effects: group-level effects, population level effects and family specific effects.
Let’s start with the ‘Population-Level’ - these are the hyperparameters, brms just uses a slightly different terminology to refer to them. For each parameter you can see the estimate, confidence interval, effective samples (may be much lower than you think!) and Rhat (diagnostic of convergence). These parameter estimates look great! They have lots of effective samples, nice convergence and reasonable confidence intervals / estimates.
Then we have the ‘Group-Level’ - these are parameters in the hierarchy that connect the hyperparameters to the group specific subset of that parameter. Therefore these are all the standard deviations of the group parameters from the population parameters. It does seem kinda weird to me that the actual parameters (not just sd) for each group are not reported, but the model definitely has that information as we will see below (although I need to figure out how to extract this in table form).
Lastly we have the ‘family specific’ parameter sigma. The family of this model is gaussian (by default). I interpret that to mean that a gaussian process / model describes the actual datapoints connecting them to the specified mathematical model. From JB’s class, we would explicity write this out and give it priors etc, but here I think it’s implicit and it essentially uses a single sigma to describe the noise in all the data (no hierarchy).
Now that we sort of understand the ideal output from the model, let’s look at the diagnostics that further help us decide whether this run was totally nonsensical or actually useful. Below are the traceplots for each chain as well as the histogram of parameter values from the samples along those chains.
So these traceplots look beautiful and is what we want to see. The 4 chains are very well mixed - they have converged on similar values. All of the chains are moving - they are not stuck, and so the samples coming from the chains are relatively independent (important for getting “effective samples”). You can see that the estimates for sd at the group level have kinda wide distributions, but the “samples” for these parameters are only the three replicate frap curves, this also probably effects the confidence of the hyperparameters we care about. On the other hand you can see the sigma estimates are extremely tight, which likely comes from the high number of datapoints that this parameter is estimated on (~400 datapoints per replicate X 3 reps).
You can also compare these parameter distributions (for a, B and I0) to the priors we set. You can see that they have all shifted at least some, with a and B shifting significantly. That’s good, because it should mean that the data really did inform the posterior distribution and we’re not just getting back our prior. There should be a way to overlay the priors and posteriors, but we’ll save that for another time.
Also note, that we can do a prior predictive check with brm
Finally, we can actually visualize the model fits for each group within our hierarchy. I believe the best fit line is just the median sampled value for each parameter and the shaded area is the 95% confidence interval for the data points?
First here’s the actual parameter estimates for each replicate (group)!
frap_model_20pct_table
$id
, , I0_Intercept
Estimate Est.Error Q2.5 Q97.5
1 0.7453687 0.004986916 0.7359148 0.7550766
2 0.7372406 0.004737759 0.7281110 0.7463463
3 0.7222025 0.004987149 0.7123059 0.7325901
, , a_Intercept
Estimate Est.Error Q2.5 Q97.5
1 0.6123333 0.02460473 0.5677048 0.6593948
2 0.5721523 0.02244596 0.5284341 0.6189857
3 0.5418665 0.02405789 0.4946067 0.5869938
, , B_Intercept
Estimate Est.Error Q2.5 Q97.5
1 0.1289537 0.007316051 0.1146116 0.1451590
2 0.1297877 0.007829621 0.1148376 0.1488467
3 0.1271142 0.008011565 0.1095685 0.1439614
Now we can visualize those estimates with our data:
frap_model_20pct_table
$id
, , I0_Intercept
Estimate Est.Error Q2.5 Q97.5
1 0.7453687 0.004986916 0.7359148 0.7550766
2 0.7372406 0.004737759 0.7281110 0.7463463
3 0.7222025 0.004987149 0.7123059 0.7325901
, , a_Intercept
Estimate Est.Error Q2.5 Q97.5
1 0.6123333 0.02460473 0.5677048 0.6593948
2 0.5721523 0.02244596 0.5284341 0.6189857
3 0.5418665 0.02405789 0.4946067 0.5869938
, , B_Intercept
Estimate Est.Error Q2.5 Q97.5
1 0.1289537 0.007316051 0.1146116 0.1451590
2 0.1297877 0.007829621 0.1148376 0.1488467
3 0.1271142 0.008011565 0.1095685 0.1439614
These fits look great! You can see that there are differences between the three replicates - especially obvious in I0 (the recovery baseline value). We can also see the individual estimates for within the groups. For some reason brms calls all of this “marginal effects” which I find somewhat confusing…but it seems to work in a reasonable manner.
We can also do more stuff like from JBs class like look at the pairs plots for the different parameters:
Ok! Now let’s move forward and try to run models for our four glycerol FRAP conditions and compare the parameter estimates. For now I’ll only work with the ‘FAST’ dataset, because from the nls fits it seemed more informative…maybe we’ll go back and try to fit the fast and slow acquisitions as part of the same hierarchy and see how it looks.
0 % Glycerol
frap_model_0pct <- brm(bf(full_norm_int~I0 - a*exp(-B * norm_time), I0 + a + B~1+(1|id), nl = T),
data = df_fast %>% filter(condition== '0pctGlyc'),
prior = prior20,
chains = 4,
cores = 2,
iter = 2000,
inits = '0',
control = list(adapt_delta = 0.99, max_treedepth = 20)
)
save(frap_model_0pct, file = "frap_0pct_hier_fit_1.rda")
load("frap_0pct_hier_fit_1.rda")
summary(frap_model_0pct)
plot(frap_model_0pct, N = 3, ask = F)
me_frap_model_0pct <- marginal_effects(
frap_model_0pct,
conditions = data.frame(id = c(1,2,3)),
re_formula = NULL, method = "predict"
)
plot(me_frap_model_0pct, points = T )
Beautiful!
10% Glycerol
frap_model_10pct <- brm(bf(full_norm_int~I0 - a*exp(-B * norm_time), I0 + a + B~1+(1|id), nl = T),
data = df_fast %>% filter(condition== '10pctGlyc'),
prior = prior20,
chains = 4,
cores = 2,
iter = 2000,
inits = '0',
control = list(adapt_delta = 0.99, max_treedepth = 20)
)
save(frap_model_10pct, file = "frap_10pct_hier_fit_1.rda")
load("frap_10pct_hier_fit_1.rda")
summary(frap_model_10pct)
plot(frap_model_10pct, N = 3, ask = F)
me_frap_model_10pct <- marginal_effects(
frap_model_10pct,
conditions = data.frame(id = c(1,2,3)),
re_formula = NULL, method = "predict"
)
plot(me_frap_model_10pct, points = T )
Also looks like a very good fit.
50% Glycerol
frap_model_50pct <- brm(bf(full_norm_int~I0 - a*exp(-B * norm_time), I0 + a + B~1+(1|id), nl = T),
data = df_fast %>% filter(condition== '50pctGlyc'),
prior = prior20,
chains = 4,
cores = 2,
iter = 2000,
inits = '0',
control = list(adapt_delta = 0.99, max_treedepth = 20),
file = 'frap_50pct_hier_fit_1.rds'
)
save(frap_model_50pct, file = "frap_50pct_hier_fit_1.rda")
load("frap_50pct_hier_fit_1.rda")
summary(frap_model_50pct)
plot(frap_model_50pct, N = 3, ask = F)
me_frap_model_50pct <- marginal_effects(
frap_model_50pct,
conditions = data.frame(id = c(1,2,3)),
re_formula = NULL, method = "predict"
)
plot(me_frap_model_50pct, points = T )
Comparing 4 conditions
Brms calls the hyperparameters ‘fixed effects’ and the lower hierarchical parameters ‘random effects’…therefore to get the table of hyperparameters you call fixef()
and for the only the group effects you call ranef()
, which gives you the difference from the population level (fixed effects). Then coef()
gives you the actual estimate for the groups taking into account both levels of effects…I’m pretty sure I have this right :)
library(tidyverse)
frap_model_ests <- bind_rows(
as_tibble(fixef(frap_model_0pct),rownames = 'Term') %>% mutate(Condition = 0),
as_tibble(fixef(frap_model_10pct),rownames = 'Term') %>% mutate(Condition = 10),
as_tibble(fixef(frap_model_20pct),rownames = 'Term') %>% mutate(Condition = 20),
as_tibble(fixef(frap_model_50pct),rownames = 'Term') %>% mutate(Condition = 50)
)
ggplot(frap_model_ests, aes(x = factor(Condition), y = Estimate)) + geom_pointrange(aes(ymin = Q2.5, ymax = Q97.5)) + facet_wrap(~Term, scales = 'free')
Let’s try to actually look at the posterior distributions here using tidybayes package.
library(tidybayes)
library(tidyverse)
library(brms)
library(ggridges)
get_variables(frap_model_0pct)
frap_model_dists <- bind_rows(
frap_model_0pct %>% spread_draws(b_B_Intercept) %>% mutate(Condition = 0),
frap_model_10pct %>% spread_draws(b_B_Intercept) %>% mutate(Condition = 10),
frap_model_20pct %>% spread_draws(b_B_Intercept) %>% mutate(Condition = 20),
frap_model_50pct %>% spread_draws(b_B_Intercept) %>% mutate(Condition = 50)
)
ggplot(frap_model_dists, aes(x = b_B_Intercept, y = factor(Condition))) + geom_halfeyeh()
ggplot(frap_model_dists, aes(x = b_B_Intercept, y = factor(Condition))) + geom_density_ridges(quantile_lines = T,quantiles = c(0.025,0.1,0.5,0.9, 0.975), jittered_points = T, position = 'raincloud', point_alpha = 0.01)
asld;kfj
LS0tCnRpdGxlOiAiUiBOb3RlYm9vayIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBpcyBhbiBbUiBNYXJrZG93bl0oaHR0cDovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkgTm90ZWJvb2suIFdoZW4geW91IGV4ZWN1dGUgY29kZSB3aXRoaW4gdGhlIG5vdGVib29rLCB0aGUgcmVzdWx0cyBhcHBlYXIgYmVuZWF0aCB0aGUgY29kZS4gCgpUcnkgZXhlY3V0aW5nIHRoaXMgY2h1bmsgYnkgY2xpY2tpbmcgdGhlICpSdW4qIGJ1dHRvbiB3aXRoaW4gdGhlIGNodW5rIG9yIGJ5IHBsYWNpbmcgeW91ciBjdXJzb3IgaW5zaWRlIGl0IGFuZCBwcmVzc2luZyAqQ21kK1NoaWZ0K0VudGVyKi4gCgpgYGB7ciBldmFsID0gRn0KbGlicmFyeShubG1lKQoKZm0xIDwtIG5sbWUoaGVpZ2h0IH4gU1Nhc3ltcChhZ2UsIEFzeW0sIFIwLCBscmMpLAogICAgICAgICAgICBkYXRhID0gTG9ibG9sbHksCiAgICAgICAgICAgIGZpeGVkID0gQXN5bSArIFIwICsgbHJjIH4gMSwKICAgICAgICAgICAgcmFuZG9tID0gQXN5bSB+IDEsCiAgICAgICAgICAgIHN0YXJ0ID0gYyhBc3ltID0gMTAzLCBSMCA9IC04LjUsIGxyYyA9IC0zLjMpKQpzdW1tYXJ5KGZtMSkKZm0yIDwtIHVwZGF0ZShmbTEsIHJhbmRvbSA9IHBkRGlhZyhBc3ltICsgbHJjIH4gMSkpCnN1bW1hcnkoZm0yKQpgYGAKCgpgYGB7cn0KbGlicmFyeShicm1zKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCmRmIDwtIHRpYmJsZSh4ID0gYyhybm9ybSgxMDAsbWVhbiA9IHNlcSgxLDEwMCwxKSksIHJub3JtKDEwMCxtZWFuID0gc2VxKDEsMTAwLDEpKSwgcm5vcm0oMTAwLG1lYW4gPSBzZXEoMSwxMDAsMSkpKSwgeSA9IGMocm5vcm0oMTAwLG1lYW4gPSBzZXEoMSwxMDAsMSkpLCBybm9ybSgxMDAsbWVhbiA9IHNlcSgxLDIwMCwyKSksIHJub3JtKDEwMCxtZWFuID0gc2VxKDEsMzAwLDMpKSksIGlkID0gYyhyZXAoJ2EnLDEwMCkscmVwKCdiJywxMDApLHJlcCgnYycsMTAwKSkpCgpnZ3Bsb3QoZGYsIGFlcyh4LHksY29sb3IgPSBpZCkpICsgZ2VvbV9wb2ludCgpKyBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nKSArIGZhY2V0X2dyaWQofmlkKQpgYGAKCmBgYHtyIGV2YWwgPSBGfQptb2RlbF8xIDwtIGJybSh5IH4geCArIDEgKyAoMXxpZCksIGRhdGEgPSBkZiwgd2FybXVwID0gMTAwMCwgaXRlciA9IDMwMDAsIGNoYWlucyA9IDIsIGluaXRzPSAicmFuZG9tIiwgY29yZXM9MikKCnN1bW1hcnkobW9kZWxfMSkKCm1hcmdpbmFsX2VmZmVjdHMobW9kZWxfMSkKCm1lX2xvc3NfMSA8LSBtYXJnaW5hbF9lZmZlY3RzKAogIG1vZGVsXzEsIGNvbmRpdGlvbnMgPSBkYXRhLmZyYW1lKGlkID0gYygnYScsJ2InLCdjJykpLCAKICByZV9mb3JtdWxhID0gTlVMTCwgbWV0aG9kID0gInByZWRpY3QiCikKCnBsb3QobWVfbG9zc18xLCBwbG90ID0gRiwgcG9pbnRzID0gVClbWzFdXSArIGdndGl0bGUoIk15IGN1c3RvbSBnZ3Bsb3QgaGVyZSEiKQpgYGAKCmBgYHtyIGV2YWwgPSBGfQptb2RlbF8yIDwtIGJybSh5IH4geCArIDEgKyAoeCsxfGlkKSwgZGF0YSA9IGRmLCB3YXJtdXAgPSAxMDAwLCBpdGVyID0gMzAwMCwgY2hhaW5zID0gMiwgaW5pdHM9ICJyYW5kb20iLCBjb3Jlcz0yKQoKc3VtbWFyeShtb2RlbF8yKQoKbWFyZ2luYWxfZWZmZWN0cyhtb2RlbF8yKQoKbWVfbG9zc18yIDwtIG1hcmdpbmFsX2VmZmVjdHMoCiAgbW9kZWxfMiwgY29uZGl0aW9ucyA9IGRhdGEuZnJhbWUoaWQgPSBjKCdhJywnYicsJ2MnKSksIAogIHJlX2Zvcm11bGEgPSBOVUxMLCBtZXRob2QgPSAicHJlZGljdCIKKQoKcGxvdChtZV9sb3NzXzIsIHBsb3QgPSBGLCBwb2ludHMgPSBUKVtbMV1dICsgZ2d0aXRsZSgiTXkgY3VzdG9tIGdncGxvdCBoZXJlISIpCmBgYAoKYGBge3IgZXZhbD0gRn0KCm1vZGVsXzMgPC0gYnJtKHkgfiB4ICsgMSArICh4fGlkKSwgZGF0YSA9IGRmLCB3YXJtdXAgPSAxMDAwLCBpdGVyID0gMzAwMCwgY2hhaW5zID0gMiwgaW5pdHM9ICJyYW5kb20iLCBjb3Jlcz0yKQoKc3VtbWFyeShtb2RlbF8zKQoKbWFyZ2luYWxfZWZmZWN0cyhtb2RlbF8zKQoKbWVfbG9zc18zIDwtIG1hcmdpbmFsX2VmZmVjdHMoCiAgbW9kZWxfMywgY29uZGl0aW9ucyA9IGRhdGEuZnJhbWUoaWQgPSBjKCdhJywnYicsJ2MnKSksIAogIHJlX2Zvcm11bGEgPSBOVUxMLCBtZXRob2QgPSAicHJlZGljdCIKKQoKcGxvdChtZV9sb3NzXzMsIHBsb3QgPSBGLCBwb2ludHMgPSBUKVtbMV1dICsgZ2d0aXRsZSgiTXkgY3VzdG9tIGdncGxvdCBoZXJlISIpCmBgYAoKYGBge3J9CmRmIDwtIHJlYWRfY3N2KCIwNl8xM18xOV9QWU9fRlJBUF9nbHljZXJvbF9mdWxsX25vcm0uY3N2IikgCgpkZl9mYXN0XzEgPC0gZGYgJT4lIGZpbHRlcihmYXN0ID09ICdyZXBGQVNUJyAmIGlkPT0xKQoKZGZfZmFzdCA8LSBkZiAlPiUgZmlsdGVyKGZhc3QgPT0gJ3JlcEZBU1QnKQoKZ2dwbG90KGRmX2Zhc3RfMSwgYWVzKHggPSBub3JtX3RpbWUsIHkgPSBmdWxsX25vcm1faW50LCBjb2xvciA9IGNvbmRpdGlvbikpICsgCiAgZ2VvbV9wb2ludChzaGFwZSA9IDIxKSsKICBzY2FsZV9jb2xvcl92aXJpZGlzX2QoKSArIAogIGZhY2V0X3dyYXAofmNvbmRpdGlvbikKCmBgYAoKYGBge3IgZXZhbCA9IEZ9CnByaW9yMSA8LSBwcmlvcihub3JtYWwoMC44LCAwLjIpLCBubHBhciA9ICJJMCIpICsKICBwcmlvcihub3JtYWwoMC42LCAwLjIpLCBubHBhciA9ICJhIikrCiAgcHJpb3Iobm9ybWFsKDAuMiwgMC4xKSwgbmxwYXIgPSAiQiIpCgpmcmFwX21vZGVsXzEgPC0gYnJtKGJmKGZ1bGxfbm9ybV9pbnR+STAgLSBhKmV4cCgtQiAqIG5vcm1fdGltZSksIEkwICsgYSArIEJ+MSsoMXxjb25kaXRpb24pLCBubCA9IFQpLAogICAgIGRhdGEgPSBkZl9mYXN0XzEsCiAgICAgcHJpb3IgPSBwcmlvcjEsCiAgICBjaGFpbnMgPSAyLAogICAgY29yZXMgPSAyLAogICAgaXRlciA9IDUwMAopCiAgICAgCiAgICAgCnN1bW1hcnkoZnJhcF9tb2RlbF8xKQoKbWFyZ2luYWxfZWZmZWN0cyhmcmFwX21vZGVsXzEpCgptZV9mcmFwX21vZGVsXzEgPC0gbWFyZ2luYWxfZWZmZWN0cygKICBmcmFwX21vZGVsXzEsIGNvbmRpdGlvbnMgPSBkYXRhLmZyYW1lKGNvbmRpdGlvbiA9IGMoJzBwY3RHbHljJywnMTBwY3RHbHljJywnMjBwY3RHbHljJywnNTBwY3RHbHljJykpLCAKICByZV9mb3JtdWxhID0gTlVMTCwgbWV0aG9kID0gInByZWRpY3QiCikKCnBsb3QobWVfZnJhcF9tb2RlbF8xLCBwbG90ID0gRiwgcG9pbnRzID0gVClbWzFdXSArIGdndGl0bGUoIk15IGN1c3RvbSBnZ3Bsb3QgaGVyZSEiKQoKYGBgCgpgYGB7ciBldmFsID0gRn0KZnJhcF9tb2RlbF8yIDwtIGJybShiZihmdWxsX25vcm1faW50fkkwIC0gYSpleHAoLUIgKiBub3JtX3RpbWUpLCBJMCArIGEgKyBCfjErKDF8Y29uZGl0aW9uKSwgbmwgPSBUKSwKICAgICBkYXRhID0gZGZfZmFzdF8xLAogICAgIHByaW9yID0gcHJpb3IxLAogICAgY2hhaW5zID0gNCwKICAgIGNvcmVzID0gMgopCiAgICAgCiAgICAgCnN1bW1hcnkoZnJhcF9tb2RlbF8yKQoKbWFyZ2luYWxfZWZmZWN0cyhmcmFwX21vZGVsXzIpCgptZV9mcmFwX21vZGVsXzIgPC0gbWFyZ2luYWxfZWZmZWN0cygKICBmcmFwX21vZGVsXzIsIGNvbmRpdGlvbnMgPSBkYXRhLmZyYW1lKGNvbmRpdGlvbiA9IGMoJzBwY3RHbHljJywnMTBwY3RHbHljJywnMjBwY3RHbHljJywnNTBwY3RHbHljJykpLCAKICByZV9mb3JtdWxhID0gTlVMTCwgbWV0aG9kID0gInByZWRpY3QiCikKCnBsb3QobWVfZnJhcF9tb2RlbF8yLCBwbG90ID0gRiwgcG9pbnRzID0gVClbWzFdXSArIGdndGl0bGUoIk15IGN1c3RvbSBnZ3Bsb3QgaGVyZSEiKQoKcGxvdChmcmFwX21vZGVsXzIpCmBgYAoKCgpDYW4gd2Ugc2V0IGEgZ3JvdXAgbGV2ZWwgcGFyYW1ldGVyIHRoYXQgZG9lc24ndCBoYXZlIGEgaHlwZXJwYXJhbWV0ZXIgYnkgZG9pbmc6CmBgYHtyIGV2YWwgPSBGfQojbGVhdmUgb3V0IGludGVyY2VwdCBpbiBwYXJhbWV0ZXIgc3BlY2lmaWNhdGlvbi4gT25seSBkZXBlbmRzIG9uIGdyb3VwIGxldmVsIGludGVyY2VwdD8uLi4KCmZyYXBfbW9kZWxfMyA8LSBicm0oYmYoZnVsbF9ub3JtX2ludH5JMCAtIGEqZXhwKC1CICogbm9ybV90aW1lKSwgSTAgKyBhICsgQn4oMXxjb25kaXRpb24pLCBubCA9IFQpLAogICAgIGRhdGEgPSBkZl9mYXN0XzEsCiAgICAgcHJpb3IgPSBwcmlvcjEsCiAgICBjaGFpbnMgPSA0LAogICAgY29yZXMgPSAyCikKICAgICAKICAgICAKc3VtbWFyeShmcmFwX21vZGVsXzMpCgptYXJnaW5hbF9lZmZlY3RzKGZyYXBfbW9kZWxfMykKCm1lX2ZyYXBfbW9kZWxfMyA8LSBtYXJnaW5hbF9lZmZlY3RzKAogIGZyYXBfbW9kZWxfMywgY29uZGl0aW9ucyA9IGRhdGEuZnJhbWUoY29uZGl0aW9uID0gYygnMHBjdEdseWMnLCcxMHBjdEdseWMnLCcyMHBjdEdseWMnLCc1MHBjdEdseWMnKSksIAogIHJlX2Zvcm11bGEgPSBOVUxMLCBtZXRob2QgPSAicHJlZGljdCIKKQoKcGxvdChtZV9mcmFwX21vZGVsXzMsIHBsb3QgPSBGLCBwb2ludHMgPSBUKVtbMV1dICsgZ2d0aXRsZSgiTXkgY3VzdG9tIGdncGxvdCBoZXJlISIpCgpwbG90KGZyYXBfbW9kZWxfMykKYGBgCgoKUHJpb3IgcHJlZGljdGl2ZSBjaGVjazoKCmBgYHtyIGV2YWwgPSBGfQoKcHJpb3IxIDwtIHByaW9yKG5vcm1hbCgwLjgsIDAuMiksIG5scGFyID0gIkkwIikgKwogIHByaW9yKG5vcm1hbCgwLjYsIDAuMiksIG5scGFyID0gImEiKSsKICBwcmlvcihub3JtYWwoMC4yLCAwLjEpLCBubHBhciA9ICJCIikKCmZyYXBfcHJpb3JfcGMgPC0gYnJtKGJmKGZ1bGxfbm9ybV9pbnR+STAgLSBhKmV4cCgtQiAqIG5vcm1fdGltZSksIEkwICsgYSArIEJ+KDF8Y29uZGl0aW9uKSwgbmwgPSBUKSwKICAgICBkYXRhID0gZGZfZmFzdF8xLAogICAgIHByaW9yID0gcHJpb3IxLAogICAgY2hhaW5zID0gNCwKICAgIGNvcmVzID0gMiwKICAgIGl0ZXIgPSA1MDAsCiAgICBzYW1wbGVfcHJpb3IgPSAnb25seScKKQoKcGxvdChmcmFwX3ByaW9yX3BjLCBOID0gMykKYGBgCgpSZWFsbHkgd2hhdCB3ZSB3YW50IGlzIHRvIGhhdmUgZWFjaCBjb25kaXRpb24gaW4gaXRzIG93biBoaWVyYXJjaGljYWwgbW9kZWwsIHRoZW4gd2UgY29tcGFyZSB0aGUgaHlwZXJwYXJhbWV0ZXIgZXN0aW1hdGVzIGZyb20gZWFjaCBoaWVyYXJjaHkgdG8gY29tcGFyZSB0aGUgY29uZGl0aW9ucy4gVGhpcyBpcyBiZWNhdXNlIHdlIGRvbid0IHRoaW5rIHRoZXJlJ3MgYSB0cnVlIGh5cGVycGFyYW1ldGVyIHRoYXQgY29udHJvbHMgdGhlIEZSQVBTIGluIHRoZSBkaWZmZXJlbnQgY29uZGl0aW9ucy4uLmJ1dCB0aGVyZSBzaG91bGQgYmUgb25lcyBhbW9uZyB0aGUgcmVwbGljYXRlcwoKYGBge3IgZXZhbCA9IEZ9CmZyYXBfbW9kZWxfMHBjdCA8LSBicm0oYmYoZnVsbF9ub3JtX2ludH5JMCAtIGEqZXhwKC1CICogbm9ybV90aW1lKSwgSTAgKyBhICsgQn4xKygxfGlkKSwgbmwgPSBUKSwKICAgICBkYXRhID0gZGZfZmFzdCAlPiUgZmlsdGVyKGNvbmRpdGlvbj09ICcwcGN0R2x5YycpLAogICAgIHByaW9yID0gcHJpb3IxLAogICAgY2hhaW5zID0gNCwKICAgIGNvcmVzID0gMiwKICAgIGNvbnRyb2wgPSBsaXN0KGFkYXB0X2RlbHRhID0gMC45NSwgbWF4X3RyZWVkZXB0aCA9IDE1KQopCgoKCm1hcmdpbmFsX2VmZmVjdHMoZnJhcF9tb2RlbF8wcGN0KQoKbWVfZnJhcF9tb2RlbF8wcGN0IDwtIG1hcmdpbmFsX2VmZmVjdHMoCiAgZnJhcF9tb2RlbF8wcGN0LCBjb25kaXRpb25zID0gZGF0YS5mcmFtZShpZCA9IGMoMSwyLDMpKSwgCiAgcmVfZm9ybXVsYSA9IE5VTEwsIG1ldGhvZCA9ICJwcmVkaWN0IgopCgpwbG90KG1lX2ZyYXBfbW9kZWxfMHBjdCwgcGxvdCA9IEYsIHBvaW50cyA9IFQpW1sxXV0gKyBnZ3RpdGxlKCJNeSBjdXN0b20gZ2dwbG90IGhlcmUhIikgKyB5bGltKC0yLDIpCgpwbG90KGZyYXBfbW9kZWxfMHBjdCwgTiA9IDMpCgpgYGAKCmBgYHtyIGV2YWwgPSBGfQpmcmFwX21vZGVsXzEwcGN0IDwtIGJybShiZihmdWxsX25vcm1faW50fkkwIC0gYSpleHAoLUIgKiBub3JtX3RpbWUpLCBJMCArIGEgKyBCfjErKDF8aWQpLCBubCA9IFQpLAogICAgIGRhdGEgPSBkZl9mYXN0ICU+JSBmaWx0ZXIoY29uZGl0aW9uPT0gJzEwcGN0R2x5YycpLAogICAgIHByaW9yID0gcHJpb3IxLAogICAgY2hhaW5zID0gNCwKICAgIGNvcmVzID0gMiwKICAgIGNvbnRyb2wgPSBsaXN0KGFkYXB0X2RlbHRhID0gMC45LCBtYXhfdHJlZWRlcHRoID0gMTIpCikKCgogIHN1bW1hcnkoZnJhcF9tb2RlbF8wcGN0KQogIApzdW1tYXJ5KGZyYXBfbW9kZWxfMTBwY3QpCgptZV9mcmFwX21vZGVsXzEwcGN0IDwtIG1hcmdpbmFsX2VmZmVjdHMoCiAgZnJhcF9tb2RlbF8xMHBjdCwgY29uZGl0aW9ucyA9IGRhdGEuZnJhbWUoaWQgPSBjKDEsMiwzKSksIAogIHJlX2Zvcm11bGEgPSBOVUxMLCBtZXRob2QgPSAicHJlZGljdCIKKQoKcGxvdChtZV9mcmFwX21vZGVsXzEwcGN0LCBwbG90ID0gRiwgcG9pbnRzID0gVClbWzFdXSArIGdndGl0bGUoIk15IGN1c3RvbSBnZ3Bsb3QgaGVyZSEiKSArIHlsaW0oLTIsMikKCnBsb3QoZnJhcF9tb2RlbF8xMHBjdCwgTiA9IDMpCgpgYGAKCkZvciBzb21lIHJlYXNvbiBJIHN0YXJ0ZWQgd2l0aCAyMCBwZXJjZW50IGdseWNlcm9sLiBBZnRlciBhIGxvdCBvZiB0cm91Ymxlc2hvb3RpbmcsIGdvaW5nIGJhY2sgYW5kIGZpdHRpbmcgYSBzaW5nbGUgY3VydmUgY2hhbmdpbmcgcHJpb3JzIGV0YywgSSB0aGluayB0aGUgbWFpbiBpc3N1ZSB3YXMgdGhhdCBJIG5lZWRlZCB0byBzZXQgdGhlIGxvd2VyIGJvdW5kIChsYikgZm9yIHRoZSBwcmlvcnMgdG8gemVybyB0byBhdm9pZCBnZXR0aW5nIG5lZ2F0aXZlIHBhcmFtZXRlciB2YWx1ZXMuLi5ub3cgdGhlIG1vZGVsIHNlZW1zIHRvIGJlIHdvcmtpbmcgcHJldHR5IHdlbGwgYW5kIGdpdmluZyByZWFzb25hYmxlIHBhcmFtZXRlciBlc3RpbWF0ZXM6IAoKTGV0J3Mgd2FsayB0aHJvdWdoIHdoYXQgd29ya2VkIGhlcmUuIAoKRmlyc3QsIHRoZSBtb2RlbHMgd2VyZSBydW5uaW5nIHZlcnkgc2xvd2x5Li4uYmVjYXVzZSB0aGlzIGlzIGVzc2VudGlhbGx5IGNvbnRyb2xsaW5nIHN0YW4gdGhhdCBjb3VsZCBiZSBmb3IgYSBsb3Qgb2YgcmVhc29ucy4gVGhlIGludGVybmV0IHN1Z2dlc3RlZCB0d28gc3BlY2lmaWMgY29tbWFuZHMgdG8gc3BlZWQgdXAgcGVyZm9ybWFuY2UgaW4gcnN0YW46CgpgYGB7cn0KbGlicmFyeShyc3RhbikKcnN0YW5fb3B0aW9ucyhhdXRvX3dyaXRlID0gVFJVRSkKb3B0aW9ucyhtYy5jb3JlcyA9IHBhcmFsbGVsOjpkZXRlY3RDb3JlcygpKQoKYGBgCgpOZXh0LCBJIHBsYXllZCBhcm91bmQgd2l0aCB0aGUgcHJpb3JzIGEgZ29vZCBiaXQsIHNpbmNlIEkgaGFkIHRoZSBlc3RpbWF0ZXMgZnJvbSB0aGUgbmxzIGZpdHMuIFdlIGNvdWxkIG1ha2UgdGhlc2UgbW9yZSB3ZWFrbHkgaW5mb3JtYXRpdmUsIGJ1dCBjb21wYXJpbmcgdG8gdGhlIHBvc3RlcmlvciBzaG93cyB0aGF0IEkgdGhpbmsgdGhlIGRhdGEgaW5mb3JtZWQgdGhlIG1vZGVsIHByZXR0eSB3ZWxsOgoKYGBge3J9CnByaW9yMjAgPC0gcHJpb3Iobm9ybWFsKDAuNzUsIDAuMiksIG5scGFyID0gIkkwIiwgbGIgPSAwKSArCiAgcHJpb3Iobm9ybWFsKDAuNiwgMC4xKSwgbmxwYXIgPSAiYSIsIGxiID0gMCkrCiAgcHJpb3Iobm9ybWFsKDAuMTUsIDAuMDUpLCBubHBhciA9ICJCIiwgbGIgPSAwKQpgYGAKClRoZSBuaWNlIHRoaW5nIGlzIHRoYXQgdGhpcyBzeW50YXggaXMgYWN0dWFsbHkgcmVhbGx5IHN0cmFpZ2h0Zm9yd2FyZCBhbmQgc2ltaWxhciB0byBob3cgSnVzdGluIEJvaXMgdGF1Z2h0IHVzIHRvIHdyaXRlIG91dCBwcmlvcnMgYW5kIG1vZGVscy4uLk5vdGUgdGhhdCB0aGVyZSBhcmUgc29tZSBwYXJhbWV0ZXJzIC8gcHJpb3JzIHRoYXQgYXJlIHNvcnQgb2YgaW1wbGllZCBhbmQgbm90IGV4cGxpY2l0IChlLmcuIHNpZ21hIGZvciBnYXVzc2lhbiBwcm9jZXNzPyksIGJ1dCB0aGUgY29yZSBvZiBpdCBpcyB0aGF0IG15IG1vZGVsIGhhcyB0aHJlZSBub25saW5lYXIgcGFyYW1ldGVycyBhbmQgd2Ugc3BlY2lmeSBhIHNpbXBsZSBwcmlvciBmb3IgZWFjaCBvbmUuCgpGaW5hbGx5LCB3ZSBjYW4gYWN0dWFsbHkgY2FsbCB0aGUgYGJybWAgZnVuY3Rpb24gd2hpY2ggd3JpdGVzIHRoZSBzdGFuIGZpbGUgLyBtb2RlbCBjb21waWxlcyAoaW4gQysrKSBhbmQgcnVucyB0aGUgbW9kZWwgd2Ugd3JpdGUuIE5vdGUgdGhhdCB0aGlzIGNvbW1hbmQgd2lsbCB0YWtlIGEgd2hpbGUgdG8gcnVuIChidXQgc2lnbmlmaWNhbnRseSBmYXN0ZXIgdGhhbiBiZWZvcmUpIC0gSSB0aGluayBpdCB3YXMgbGVzcyB0aGFuIDEwIG1pbi4gSXQgaXMgZGVmaW5pdGVseSB3b3J0aCBzdGFydGluZyBvbiBhIHNtYWxsZXIgZGF0YXNldCB3aXRoIGZld2VyIGNoYWlucyBhbmQgaXRlcmF0aW9ucyB3aGVuIHRyeWluZyB0byBnZXQgdGhlIG1vZGVsIHVwIGFuZCBydW5uaW5nIHdlbGwuIEFsc28sIG5vdGUgdGhhdCB0aGVyZSBpcyBhIGZpbGUgb3B0aW9uIHRvIHNhdmUgdGhlIG1vZGVsIC8gcnVuIGluIGFuIGV4dGVybmFsIGZpbGUgZm9yIGxhdGVyIHVzZSAoZWl0aGVyIHdpdGggJ2ZpbGUnIHBhcmFtZXRlciBvciB3aXRoIGBzYXZlYCBjb21tYW5kKToKYGBge3IgZXZhbCA9IEZ9CmZyYXBfbW9kZWxfMjBwY3QgPC0gYnJtKGJmKGZ1bGxfbm9ybV9pbnR+STAgLSBhKmV4cCgtQiAqIG5vcm1fdGltZSksIEkwICsgYSArIEJ+MSsoMXxpZCksIG5sID0gVCksCiAgICAgZGF0YSA9IGRmX2Zhc3QgJT4lIGZpbHRlcihjb25kaXRpb249PSAnMjBwY3RHbHljJyksCiAgICAgcHJpb3IgPSBwcmlvcjIwLAogICAgY2hhaW5zID0gNCwKICAgIGNvcmVzID0gMiwKICAgIGl0ZXIgPSAyMDAwLAogICAgaW5pdHMgPSAnMCcsCiAgICBjb250cm9sID0gbGlzdChhZGFwdF9kZWx0YSA9IDAuOTksIG1heF90cmVlZGVwdGggPSAyMCkKKQoKc2F2ZShmcmFwX21vZGVsXzIwcGN0LCBmaWxlID0gImZyYXBfMjBwY3RfaGllcl9maXRfMS5yZGEiKQpgYGAKClRoaXMgc2hvdWxkIHByb2R1Y2Ugc29tZSBoZWxwZnVsIHN0YXR1cyB1cGRhdGVzIGFib3V0IGhvdyB0aGUgY2hhaW5zIGFyZSBydW5uaW5nIGFuZCBob3cgbG9uZyBpdCB0YWtlcy4uLmFuZCBwcm9iYWJseSBzb21lIHdhcm5pbmdzIGFib3V0IGRpdmVyZ2VuY2VzIG9yIG1vcmUgc2VyaW91cyBwcm9ibGVtcyAoZS5nLiB1bmluZm9ybWF0aXZlIGNoYWlucykuIAoKTmV4dCwgSSB0aGluayB0aGUgZmlyc3QgdGhpbmcgdG8gZG8gaXMgbG9vayBhdCB0aGUgc3VtbWFyeSBvZiB0aGUgcnVuOgpgYGB7cn0KbG9hZCgiZnJhcF8yMHBjdF9oaWVyX2ZpdF8xLnJkYSIpCgpzdW1tYXJ5KGZyYXBfbW9kZWxfMjBwY3QpCmBgYAoKSGVyZSB5b3UgY2FuIHNlZSBzb21lIHJlYWxseSBuaWNlIGluZm9ybWF0aW9uLiBGaXJzdCwgdGhlcmUgYXJlIG5vdGFibGUgd2FybmluZ3MgYWJvdXQgIHRoZSBydW4gLSBoZXJlIHdlIGhhZCBhIGZldyBkaXZlcmdlbmNlcywgd2hpY2ggaXMgbm90IHRoYXQgc2VyaW91cywgYnV0IHRoZXNlIHdhcm5pbmdzIGNhbiBiZSB2ZXJ5IGhlbHBmdWwgZm9yIG1vcmUgc2VyaW91cyBwcm9ibGVtcy4gTmV4dCBicm1zIHRlbGxzIHVzIHRoZSBmdWxsIGZvcm11bGEgb2YgdGhlIG1vZGVsIHdlIHNwZWNpZmllZCAtIE5vdGUgdGhhdCB0aGVyZSBpcyBzb21lIHNob3J0aGFuZCB0byBob3cgdGhlIHBhcmFtZXRlcnMgLyBncm91cGluZyBjYW4gYmUgc3BlY2lmaWVkLCBzbyB0aGlzIGxldCdzIHlvdSBtYWtlIHN1cmUgeW91J3JlIG9uIHRoZSBzYW1lIHBhZ2Ugd2l0aCB0aGUgYnJtcyBmb3JtdWxhIHBhcnNpbmcuIAoKTmV4dCB0aGVyZSBhcmUgdGhyZWUgZ3JvdXBzIG9mIGVmZmVjdHM6ICpncm91cC1sZXZlbCogZWZmZWN0cywgKnBvcHVsYXRpb24gbGV2ZWwqIGVmZmVjdHMgYW5kICpmYW1pbHkgc3BlY2lmaWMqIGVmZmVjdHMuIAoKTGV0J3Mgc3RhcnQgd2l0aCB0aGUgJ1BvcHVsYXRpb24tTGV2ZWwnIC0gdGhlc2UgYXJlIHRoZSAqKmh5cGVycGFyYW1ldGVycyoqLCBicm1zIGp1c3QgdXNlcyBhIHNsaWdodGx5IGRpZmZlcmVudCB0ZXJtaW5vbG9neSB0byByZWZlciB0byB0aGVtLiBGb3IgZWFjaCBwYXJhbWV0ZXIgeW91IGNhbiBzZWUgdGhlIGVzdGltYXRlLCBjb25maWRlbmNlIGludGVydmFsLCBlZmZlY3RpdmUgc2FtcGxlcyAobWF5IGJlIG11Y2ggbG93ZXIgdGhhbiB5b3UgdGhpbmshKSBhbmQgUmhhdCAoZGlhZ25vc3RpYyBvZiBjb252ZXJnZW5jZSkuIFRoZXNlIHBhcmFtZXRlciBlc3RpbWF0ZXMgbG9vayBncmVhdCEgVGhleSBoYXZlIGxvdHMgb2YgZWZmZWN0aXZlIHNhbXBsZXMsIG5pY2UgY29udmVyZ2VuY2UgYW5kIHJlYXNvbmFibGUgY29uZmlkZW5jZSBpbnRlcnZhbHMgLyBlc3RpbWF0ZXMuCgpUaGVuIHdlIGhhdmUgdGhlICdHcm91cC1MZXZlbCcgLSB0aGVzZSBhcmUgcGFyYW1ldGVycyBpbiB0aGUgaGllcmFyY2h5IHRoYXQgY29ubmVjdCB0aGUgaHlwZXJwYXJhbWV0ZXJzIHRvIHRoZSBncm91cCBzcGVjaWZpYyBzdWJzZXQgb2YgdGhhdCBwYXJhbWV0ZXIuIFRoZXJlZm9yZSB0aGVzZSBhcmUgYWxsIHRoZSBzdGFuZGFyZCBkZXZpYXRpb25zIG9mIHRoZSBncm91cCBwYXJhbWV0ZXJzIGZyb20gdGhlIHBvcHVsYXRpb24gcGFyYW1ldGVycy4gSXQgZG9lcyBzZWVtIGtpbmRhIHdlaXJkIHRvIG1lIHRoYXQgdGhlIGFjdHVhbCBwYXJhbWV0ZXJzIChub3QganVzdCBzZCkgZm9yIGVhY2ggZ3JvdXAgYXJlIG5vdCByZXBvcnRlZCwgYnV0IHRoZSBtb2RlbCBkZWZpbml0ZWx5IGhhcyB0aGF0IGluZm9ybWF0aW9uIGFzIHdlIHdpbGwgc2VlIGJlbG93IChhbHRob3VnaCBJIG5lZWQgdG8gZmlndXJlIG91dCBob3cgdG8gZXh0cmFjdCB0aGlzIGluIHRhYmxlIGZvcm0pLgoKTGFzdGx5IHdlIGhhdmUgdGhlICdmYW1pbHkgc3BlY2lmaWMnIHBhcmFtZXRlciAqc2lnbWEqLiBUaGUgZmFtaWx5IG9mIHRoaXMgbW9kZWwgaXMgZ2F1c3NpYW4gKGJ5IGRlZmF1bHQpLiBJIGludGVycHJldCB0aGF0IHRvIG1lYW4gdGhhdCBhIGdhdXNzaWFuIHByb2Nlc3MgLyBtb2RlbCBkZXNjcmliZXMgdGhlIGFjdHVhbCBkYXRhcG9pbnRzIGNvbm5lY3RpbmcgdGhlbSB0byB0aGUgc3BlY2lmaWVkIG1hdGhlbWF0aWNhbCBtb2RlbC4gRnJvbSBKQidzIGNsYXNzLCB3ZSB3b3VsZCBleHBsaWNpdHkgd3JpdGUgdGhpcyBvdXQgYW5kIGdpdmUgaXQgcHJpb3JzIGV0YywgYnV0IGhlcmUgSSB0aGluayBpdCdzIGltcGxpY2l0IGFuZCBpdCBlc3NlbnRpYWxseSB1c2VzIGEgc2luZ2xlIHNpZ21hIHRvIGRlc2NyaWJlIHRoZSBub2lzZSBpbiBhbGwgdGhlIGRhdGEgKG5vIGhpZXJhcmNoeSkuCgpOb3cgdGhhdCB3ZSBzb3J0IG9mIHVuZGVyc3RhbmQgdGhlIGlkZWFsIG91dHB1dCBmcm9tIHRoZSBtb2RlbCwgbGV0J3MgbG9vayBhdCB0aGUgZGlhZ25vc3RpY3MgdGhhdCBmdXJ0aGVyIGhlbHAgdXMgZGVjaWRlIHdoZXRoZXIgdGhpcyBydW4gd2FzIHRvdGFsbHkgbm9uc2Vuc2ljYWwgb3IgYWN0dWFsbHkgdXNlZnVsLiBCZWxvdyBhcmUgdGhlIHRyYWNlcGxvdHMgZm9yIGVhY2ggY2hhaW4gYXMgd2VsbCBhcyB0aGUgaGlzdG9ncmFtIG9mIHBhcmFtZXRlciB2YWx1ZXMgZnJvbSB0aGUgc2FtcGxlcyBhbG9uZyB0aG9zZSBjaGFpbnMuIAoKYGBge3J9CnBsb3QoZnJhcF9tb2RlbF8yMHBjdCwgTiA9IDMsIGFzayA9IEYpCmBgYAoKU28gdGhlc2UgdHJhY2VwbG90cyBsb29rIGJlYXV0aWZ1bCBhbmQgaXMgd2hhdCB3ZSB3YW50IHRvIHNlZS4gVGhlIDQgY2hhaW5zIGFyZSB2ZXJ5IHdlbGwgbWl4ZWQgLSB0aGV5IGhhdmUgY29udmVyZ2VkIG9uIHNpbWlsYXIgdmFsdWVzLiBBbGwgb2YgdGhlIGNoYWlucyBhcmUgbW92aW5nIC0gdGhleSBhcmUgbm90IHN0dWNrLCBhbmQgc28gdGhlIHNhbXBsZXMgY29taW5nIGZyb20gdGhlIGNoYWlucyBhcmUgcmVsYXRpdmVseSBpbmRlcGVuZGVudCAoaW1wb3J0YW50IGZvciBnZXR0aW5nICJlZmZlY3RpdmUgc2FtcGxlcyIpLiBZb3UgY2FuIHNlZSB0aGF0IHRoZSBlc3RpbWF0ZXMgZm9yIHNkIGF0IHRoZSBncm91cCBsZXZlbCBoYXZlIGtpbmRhIHdpZGUgZGlzdHJpYnV0aW9ucywgYnV0IHRoZSAic2FtcGxlcyIgZm9yIHRoZXNlIHBhcmFtZXRlcnMgYXJlIG9ubHkgdGhlIHRocmVlIHJlcGxpY2F0ZSBmcmFwIGN1cnZlcywgdGhpcyBhbHNvIHByb2JhYmx5IGVmZmVjdHMgdGhlIGNvbmZpZGVuY2Ugb2YgdGhlIGh5cGVycGFyYW1ldGVycyB3ZSBjYXJlIGFib3V0LiBPbiB0aGUgb3RoZXIgaGFuZCB5b3UgY2FuIHNlZSB0aGUgc2lnbWEgZXN0aW1hdGVzIGFyZSBleHRyZW1lbHkgdGlnaHQsIHdoaWNoIGxpa2VseSBjb21lcyBmcm9tIHRoZSBoaWdoIG51bWJlciBvZiBkYXRhcG9pbnRzIHRoYXQgdGhpcyBwYXJhbWV0ZXIgaXMgZXN0aW1hdGVkIG9uICh+NDAwIGRhdGFwb2ludHMgcGVyIHJlcGxpY2F0ZSBYIDMgcmVwcykuCgpZb3UgY2FuIGFsc28gY29tcGFyZSB0aGVzZSBwYXJhbWV0ZXIgZGlzdHJpYnV0aW9ucyAoZm9yIGEsIEIgYW5kIEkwKSB0byB0aGUgcHJpb3JzIHdlIHNldC4gWW91IGNhbiBzZWUgdGhhdCB0aGV5IGhhdmUgYWxsIHNoaWZ0ZWQgYXQgbGVhc3Qgc29tZSwgd2l0aCBhIGFuZCBCIHNoaWZ0aW5nIHNpZ25pZmljYW50bHkuIFRoYXQncyBnb29kLCBiZWNhdXNlIGl0IHNob3VsZCBtZWFuIHRoYXQgdGhlIGRhdGEgcmVhbGx5IGRpZCBpbmZvcm0gdGhlIHBvc3RlcmlvciBkaXN0cmlidXRpb24gYW5kIHdlJ3JlIG5vdCBqdXN0IGdldHRpbmcgYmFjayBvdXIgcHJpb3IuIFRoZXJlIHNob3VsZCBiZSBhIHdheSB0byBvdmVybGF5IHRoZSBwcmlvcnMgYW5kIHBvc3RlcmlvcnMsIGJ1dCB3ZSdsbCBzYXZlIHRoYXQgZm9yIGFub3RoZXIgdGltZS4gCgpBbHNvIG5vdGUsIHRoYXQgd2UgY2FuIGRvIGEgcHJpb3IgcHJlZGljdGl2ZSBjaGVjayB3aXRoIGBicm1gCgpGaW5hbGx5LCB3ZSBjYW4gYWN0dWFsbHkgdmlzdWFsaXplIHRoZSBtb2RlbCBmaXRzIGZvciBlYWNoIGdyb3VwIHdpdGhpbiBvdXIgaGllcmFyY2h5LiBJIGJlbGlldmUgdGhlIGJlc3QgZml0IGxpbmUgaXMganVzdCB0aGUgbWVkaWFuIHNhbXBsZWQgdmFsdWUgZm9yIGVhY2ggcGFyYW1ldGVyIGFuZCB0aGUgc2hhZGVkIGFyZWEgaXMgdGhlIDk1JSBjb25maWRlbmNlIGludGVydmFsIGZvciB0aGUgZGF0YSBwb2ludHM/CgpGaXJzdCBoZXJlJ3MgdGhlIGFjdHVhbCBwYXJhbWV0ZXIgZXN0aW1hdGVzIGZvciBlYWNoIHJlcGxpY2F0ZSAoZ3JvdXApIQpgYGB7cn0KCmZyYXBfbW9kZWxfMjBwY3RfdGFibGUgPC0gY29lZihmcmFwX21vZGVsXzIwcGN0LCBzdW1tYXJ5ID0gVCwgcm9idXN0ID0gVCkKCmZyYXBfbW9kZWxfMjBwY3RfdGFibGUKYGBgCgpOb3cgd2UgY2FuIHZpc3VhbGl6ZSB0aG9zZSBlc3RpbWF0ZXMgd2l0aCBvdXIgZGF0YToKYGBge3J9Cm1lX2ZyYXBfbW9kZWxfMjBwY3QgPC0gbWFyZ2luYWxfZWZmZWN0cygKICBmcmFwX21vZGVsXzIwcGN0LCAKICBjb25kaXRpb25zID0gZGF0YS5mcmFtZShpZCA9IGMoMSwyLDMpKSwKICByZV9mb3JtdWxhID0gTlVMTCwgbWV0aG9kID0gInByZWRpY3QiCikKCgpwbG90KG1lX2ZyYXBfbW9kZWxfMjBwY3QsIHBvaW50cyA9IFQgKQoKYGBgCgpUaGVzZSBmaXRzIGxvb2sgZ3JlYXQhIFlvdSBjYW4gc2VlIHRoYXQgdGhlcmUgYXJlIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIHRocmVlIHJlcGxpY2F0ZXMgLSBlc3BlY2lhbGx5IG9idmlvdXMgaW4gSTAgKHRoZSByZWNvdmVyeSBiYXNlbGluZSB2YWx1ZSkuIFdlIGNhbiBhbHNvIHNlZSB0aGUgaW5kaXZpZHVhbCBlc3RpbWF0ZXMgZm9yIHdpdGhpbiB0aGUgZ3JvdXBzLiBGb3Igc29tZSByZWFzb24gYnJtcyBjYWxscyBhbGwgb2YgdGhpcyAibWFyZ2luYWwgZWZmZWN0cyIgd2hpY2ggSSBmaW5kIHNvbWV3aGF0IGNvbmZ1c2luZy4uLmJ1dCBpdCBzZWVtcyB0byB3b3JrIGluIGEgcmVhc29uYWJsZSBtYW5uZXIuCgpXZSBjYW4gYWxzbyBkbyBtb3JlIHN0dWZmIGxpa2UgZnJvbSBKQnMgY2xhc3MgbGlrZSBsb29rIGF0IHRoZSBwYWlycyBwbG90cyBmb3IgdGhlIGRpZmZlcmVudCBwYXJhbWV0ZXJzOgpgYGB7cn0KcGFpcnMoZnJhcF9tb2RlbF8yMHBjdCxwYXJzID0gYygnYl9JMF9JbnRlcmNlcHQnLCdiX2FfSW50ZXJjZXB0JywnYl9CX0ludGVyY2VwdCcpKQpgYGAKCk9rISBOb3cgbGV0J3MgbW92ZSBmb3J3YXJkIGFuZCB0cnkgdG8gcnVuIG1vZGVscyBmb3Igb3VyIGZvdXIgZ2x5Y2Vyb2wgRlJBUCBjb25kaXRpb25zIGFuZCBjb21wYXJlIHRoZSBwYXJhbWV0ZXIgZXN0aW1hdGVzLiBGb3Igbm93IEknbGwgb25seSB3b3JrIHdpdGggdGhlICdGQVNUJyBkYXRhc2V0LCBiZWNhdXNlIGZyb20gdGhlIG5scyBmaXRzIGl0IHNlZW1lZCBtb3JlIGluZm9ybWF0aXZlLi4ubWF5YmUgd2UnbGwgZ28gYmFjayBhbmQgdHJ5IHRvIGZpdCB0aGUgZmFzdCBhbmQgc2xvdyBhY3F1aXNpdGlvbnMgYXMgcGFydCBvZiB0aGUgc2FtZSBoaWVyYXJjaHkgYW5kIHNlZSBob3cgaXQgbG9va3MuIAoKIyMgMCAlIEdseWNlcm9sCgpgYGB7ciBldmFsID0gRn0KZnJhcF9tb2RlbF8wcGN0IDwtIGJybShiZihmdWxsX25vcm1faW50fkkwIC0gYSpleHAoLUIgKiBub3JtX3RpbWUpLCBJMCArIGEgKyBCfjErKDF8aWQpLCBubCA9IFQpLAogICAgIGRhdGEgPSBkZl9mYXN0ICU+JSBmaWx0ZXIoY29uZGl0aW9uPT0gJzBwY3RHbHljJyksCiAgICAgcHJpb3IgPSBwcmlvcjIwLAogICAgY2hhaW5zID0gNCwKICAgIGNvcmVzID0gMiwKICAgIGl0ZXIgPSAyMDAwLAogICAgaW5pdHMgPSAnMCcsCiAgICBjb250cm9sID0gbGlzdChhZGFwdF9kZWx0YSA9IDAuOTksIG1heF90cmVlZGVwdGggPSAyMCkKKQoKc2F2ZShmcmFwX21vZGVsXzBwY3QsIGZpbGUgPSAiZnJhcF8wcGN0X2hpZXJfZml0XzEucmRhIikKYGBgCgpgYGB7cn0KbG9hZCgiZnJhcF8wcGN0X2hpZXJfZml0XzEucmRhIikKCnN1bW1hcnkoZnJhcF9tb2RlbF8wcGN0KQpgYGAKCmBgYHtyfQpwbG90KGZyYXBfbW9kZWxfMHBjdCwgTiA9IDMsIGFzayA9IEYpCmBgYAoKCmBgYHtyfQptZV9mcmFwX21vZGVsXzBwY3QgPC0gbWFyZ2luYWxfZWZmZWN0cygKICBmcmFwX21vZGVsXzBwY3QsIAogIGNvbmRpdGlvbnMgPSBkYXRhLmZyYW1lKGlkID0gYygxLDIsMykpLAogIHJlX2Zvcm11bGEgPSBOVUxMLCBtZXRob2QgPSAicHJlZGljdCIKKQoKCnBsb3QobWVfZnJhcF9tb2RlbF8wcGN0LCBwb2ludHMgPSBUICkKCmBgYAoKQmVhdXRpZnVsIQoKIyMgMTAlIEdseWNlcm9sCgpgYGB7ciBldmFsID0gRn0KZnJhcF9tb2RlbF8xMHBjdCA8LSBicm0oYmYoZnVsbF9ub3JtX2ludH5JMCAtIGEqZXhwKC1CICogbm9ybV90aW1lKSwgSTAgKyBhICsgQn4xKygxfGlkKSwgbmwgPSBUKSwKICAgICBkYXRhID0gZGZfZmFzdCAlPiUgZmlsdGVyKGNvbmRpdGlvbj09ICcxMHBjdEdseWMnKSwKICAgICBwcmlvciA9IHByaW9yMjAsCiAgICBjaGFpbnMgPSA0LAogICAgY29yZXMgPSAyLAogICAgaXRlciA9IDIwMDAsCiAgICBpbml0cyA9ICcwJywKICAgIGNvbnRyb2wgPSBsaXN0KGFkYXB0X2RlbHRhID0gMC45OSwgbWF4X3RyZWVkZXB0aCA9IDIwKQopCgpzYXZlKGZyYXBfbW9kZWxfMTBwY3QsIGZpbGUgPSAiZnJhcF8xMHBjdF9oaWVyX2ZpdF8xLnJkYSIpCmBgYAoKYGBge3J9CmxvYWQoImZyYXBfMTBwY3RfaGllcl9maXRfMS5yZGEiKQoKc3VtbWFyeShmcmFwX21vZGVsXzEwcGN0KQpgYGAKCmBgYHtyfQpwbG90KGZyYXBfbW9kZWxfMTBwY3QsIE4gPSAzLCBhc2sgPSBGKQpgYGAKCmBgYHtyfQptZV9mcmFwX21vZGVsXzEwcGN0IDwtIG1hcmdpbmFsX2VmZmVjdHMoCiAgZnJhcF9tb2RlbF8xMHBjdCwgCiAgY29uZGl0aW9ucyA9IGRhdGEuZnJhbWUoaWQgPSBjKDEsMiwzKSksCiAgcmVfZm9ybXVsYSA9IE5VTEwsIG1ldGhvZCA9ICJwcmVkaWN0IgopCgoKcGxvdChtZV9mcmFwX21vZGVsXzEwcGN0LCBwb2ludHMgPSBUICkKCmBgYAoKQWxzbyBsb29rcyBsaWtlIGEgdmVyeSBnb29kIGZpdC4KCiMjIDUwJSBHbHljZXJvbAoKYGBge3IgZXZhbCA9IEZ9CmZyYXBfbW9kZWxfNTBwY3QgPC0gYnJtKGJmKGZ1bGxfbm9ybV9pbnR+STAgLSBhKmV4cCgtQiAqIG5vcm1fdGltZSksIEkwICsgYSArIEJ+MSsoMXxpZCksIG5sID0gVCksCiAgICAgZGF0YSA9IGRmX2Zhc3QgJT4lIGZpbHRlcihjb25kaXRpb249PSAnNTBwY3RHbHljJyksCiAgICAgcHJpb3IgPSBwcmlvcjIwLAogICAgY2hhaW5zID0gNCwKICAgIGNvcmVzID0gMiwKICAgIGl0ZXIgPSAyMDAwLAogICAgaW5pdHMgPSAnMCcsCiAgICBjb250cm9sID0gbGlzdChhZGFwdF9kZWx0YSA9IDAuOTksIG1heF90cmVlZGVwdGggPSAyMCksCiAgICBmaWxlID0gJ2ZyYXBfNTBwY3RfaGllcl9maXRfMS5yZHMnCikKCnNhdmUoZnJhcF9tb2RlbF81MHBjdCwgZmlsZSA9ICJmcmFwXzUwcGN0X2hpZXJfZml0XzEucmRhIikKYGBgCgpgYGB7cn0KbG9hZCgiZnJhcF81MHBjdF9oaWVyX2ZpdF8xLnJkYSIpCgpzdW1tYXJ5KGZyYXBfbW9kZWxfNTBwY3QpCmBgYAoKCmBgYHtyfQpwbG90KGZyYXBfbW9kZWxfNTBwY3QsIE4gPSAzLCBhc2sgPSBGKQpgYGAKCmBgYHtyfQptZV9mcmFwX21vZGVsXzUwcGN0IDwtIG1hcmdpbmFsX2VmZmVjdHMoCiAgZnJhcF9tb2RlbF81MHBjdCwgCiAgY29uZGl0aW9ucyA9IGRhdGEuZnJhbWUoaWQgPSBjKDEsMiwzKSksCiAgcmVfZm9ybXVsYSA9IE5VTEwsIG1ldGhvZCA9ICJwcmVkaWN0IgopCgoKcGxvdChtZV9mcmFwX21vZGVsXzUwcGN0LCBwb2ludHMgPSBUICkKCmBgYAoKIyBDb21wYXJpbmcgNCBjb25kaXRpb25zCgpCcm1zIGNhbGxzIHRoZSBoeXBlcnBhcmFtZXRlcnMgJ2ZpeGVkIGVmZmVjdHMnIGFuZCB0aGUgbG93ZXIgaGllcmFyY2hpY2FsIHBhcmFtZXRlcnMgJ3JhbmRvbSBlZmZlY3RzJy4uLnRoZXJlZm9yZSB0byBnZXQgdGhlIHRhYmxlIG9mIGh5cGVycGFyYW1ldGVycyB5b3UgY2FsbCBgZml4ZWYoKWAgYW5kIGZvciB0aGUgb25seSB0aGUgZ3JvdXAgZWZmZWN0cyB5b3UgY2FsbCBgcmFuZWYoKWAsIHdoaWNoIGdpdmVzIHlvdSB0aGUgZGlmZmVyZW5jZSBmcm9tIHRoZSBwb3B1bGF0aW9uIGxldmVsIChmaXhlZCBlZmZlY3RzKS4gVGhlbiBgY29lZigpYCBnaXZlcyB5b3UgdGhlIGFjdHVhbCBlc3RpbWF0ZSBmb3IgdGhlIGdyb3VwcyB0YWtpbmcgaW50byBhY2NvdW50IGJvdGggbGV2ZWxzIG9mIGVmZmVjdHMuLi5JJ20gcHJldHR5IHN1cmUgSSBoYXZlIHRoaXMgcmlnaHQgOikKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCmZyYXBfbW9kZWxfZXN0cyA8LSBiaW5kX3Jvd3MoCiAgYXNfdGliYmxlKGZpeGVmKGZyYXBfbW9kZWxfMHBjdCkscm93bmFtZXMgPSAnVGVybScpICU+JSBtdXRhdGUoQ29uZGl0aW9uID0gMCksCiAgYXNfdGliYmxlKGZpeGVmKGZyYXBfbW9kZWxfMTBwY3QpLHJvd25hbWVzID0gJ1Rlcm0nKSAlPiUgbXV0YXRlKENvbmRpdGlvbiA9IDEwKSwKICBhc190aWJibGUoZml4ZWYoZnJhcF9tb2RlbF8yMHBjdCkscm93bmFtZXMgPSAnVGVybScpICU+JSBtdXRhdGUoQ29uZGl0aW9uID0gMjApLAogIGFzX3RpYmJsZShmaXhlZihmcmFwX21vZGVsXzUwcGN0KSxyb3duYW1lcyA9ICdUZXJtJykgJT4lIG11dGF0ZShDb25kaXRpb24gPSA1MCkKKQoKZ2dwbG90KGZyYXBfbW9kZWxfZXN0cywgYWVzKHggPSBmYWN0b3IoQ29uZGl0aW9uKSwgeSA9IEVzdGltYXRlKSkgKyBnZW9tX3BvaW50cmFuZ2UoYWVzKHltaW4gPSBRMi41LCB5bWF4ID0gUTk3LjUpKSArIGZhY2V0X3dyYXAoflRlcm0sIHNjYWxlcyA9ICdmcmVlJykKYGBgCgpMZXQncyB0cnkgdG8gYWN0dWFsbHkgbG9vayBhdCB0aGUgcG9zdGVyaW9yIGRpc3RyaWJ1dGlvbnMgaGVyZSB1c2luZyB0aWR5YmF5ZXMgcGFja2FnZS4KCmBgYHtyfQpsaWJyYXJ5KHRpZHliYXllcykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoYnJtcykKbGlicmFyeShnZ3JpZGdlcykKCmdldF92YXJpYWJsZXMoZnJhcF9tb2RlbF8wcGN0KQoKZnJhcF9tb2RlbF9kaXN0cyA8LSBiaW5kX3Jvd3MoCiAgZnJhcF9tb2RlbF8wcGN0ICU+JSBzcHJlYWRfZHJhd3MoYl9CX0ludGVyY2VwdCkgJT4lIG11dGF0ZShDb25kaXRpb24gPSAwKSwKICBmcmFwX21vZGVsXzEwcGN0ICU+JSBzcHJlYWRfZHJhd3MoYl9CX0ludGVyY2VwdCkgJT4lIG11dGF0ZShDb25kaXRpb24gPSAxMCksCiAgZnJhcF9tb2RlbF8yMHBjdCAlPiUgc3ByZWFkX2RyYXdzKGJfQl9JbnRlcmNlcHQpICU+JSBtdXRhdGUoQ29uZGl0aW9uID0gMjApLAogIGZyYXBfbW9kZWxfNTBwY3QgJT4lIHNwcmVhZF9kcmF3cyhiX0JfSW50ZXJjZXB0KSAlPiUgbXV0YXRlKENvbmRpdGlvbiA9IDUwKQopCgpnZ3Bsb3QoZnJhcF9tb2RlbF9kaXN0cywgYWVzKHggPSBiX0JfSW50ZXJjZXB0LCB5ID0gZmFjdG9yKENvbmRpdGlvbikpKSArIGdlb21faGFsZmV5ZWgoKQoKZ2dwbG90KGZyYXBfbW9kZWxfZGlzdHMsIGFlcyh4ID0gYl9CX0ludGVyY2VwdCwgeSA9IGZhY3RvcihDb25kaXRpb24pKSkgKyBnZW9tX2RlbnNpdHlfcmlkZ2VzKHF1YW50aWxlX2xpbmVzID0gVCxxdWFudGlsZXMgPSBjKDAuMDI1LDAuMSwwLjUsMC45LCAwLjk3NSksIGppdHRlcmVkX3BvaW50cyA9IFQsIHBvc2l0aW9uID0gJ3JhaW5jbG91ZCcsIHBvaW50X2FscGhhID0gMC4wMSkKYGBgCgoKCgoKCgphc2xkO2tmago=