Introduction

This includes the code used to create the models and plots for student retention data used as an example in several BDA course lectures in 2023.

Load packages

library(brms)
#options(brms.backend = "cmdstanr")
# Using RStan backend for moment matching in LOO
options(brms.backend = "rstan")
library(posterior)
library(tidybayes)
library(dplyr)
library(tidyr)
library(ggplot2)
library(ggdist)
library(latex2exp)
library(khroma)
library(RColorBrewer)
theme_set(bayesplot::theme_default(base_family = "sans", base_size=16))

Data

Since 2018, there have been 9 assignments in BDA course. Data are the number of students who submitted each assignment. As the course is not compulsory for most, and it is common that some students register for more courses than they need and during the course may decide to drop-out. Also as the students can submit also in spring, some students may stop submitting in autumn and get back in spring. Although there are also external reasons why students drop out from the course, we are interested in following the retention and hope that the changes in the course would improve the retention. As a starting point we want to analyse whether we can see differences in years.

# Number of students who returned the first assignment on 2018-2022
nstudents1<-rep(c(176,242,332,301,245),each=9)
# Number of students for each round
nstudents<-c(c(176, 174, 158, 135, 138, 129, 126, 123, 121),
             c(242, 212, 184, 177, 174, 172, 163, 156, 153),
             c(332, 310, 278, 258, 243, 242, 226, 224, 218),
             c(301, 269, 231, 232, 217, 208, 193, 191, 190),
             c(245, 240, 228, 217, 206, 199, 191, 182, 175))
# Proportion of students
propstudents<-c(c(176, 174, 158, 135, 138, 129, 126, 123, 121)/176,
                c(242, 212, 184, 177, 174, 172, 163, 156, 153)/242,
                c(332, 310, 278, 258, 243, 242, 226, 224, 218)/332,
                c(301, 269, 231, 232, 217, 208, 193, 191, 190)/301,
                c(245, 240, 228, 217, 206, 199, 191, 182, 175)/245)
# Year as integers and factors
year <- rep(2018:2022,each=9)
fyear <- factor(year)
# Assignment numbers
assignment <- rep(1:9, 5)
# Tibble
tb <- tibble(assignment, nstudents, nstudents1, propstudents, year, fyear)

# Another tibble for including 2023 first submission numbers
# Number of students who returned the first assignment
nstudents1<-rep(c(176,242,332,301,245,264),each=9)
# Number of students for each round
nstudents<-c(c(176, 174, 158, 135, 138, 129, 126, 123, 121),
                c(242, 212, 184, 177, 174, 172, 163, 156, 153),
                c(332, 310, 278, 258, 243, 242, 226, 224, 218),
                c(301, 269, 231, 232, 217, 208, 193, 191, 190),
                c(245, 240, 228, 217, 206, 199, 191, 182, 175),
                c(264,  NA,  NA,  NA,  NA,  NA,  NA,  NA,  NA))

propstudents<-c(c(176, 174, 158, 135, 138, 129, 126, 123, 121)/176,
                c(242, 212, 184, 177, 174, 172, 163, 156, 153)/242,
                c(332, 310, 278, 258, 243, 242, 226, 224, 218)/332,
                c(301, 269, 231, 232, 217, 208, 193, 191, 190)/301,
                c(245, 240, 228, 217, 206, 199, 191, 182, 175)/245,
                c(264,  NA,  NA,  NA,  NA,  NA,  NA,  NA,  NA)/264)
# Year as integers and factors
year <- rep(2018:2023,each=9)
fyear <- factor(year)
# Assignment numbers
assignment <- rep(1:9, 6)
# Tibble
tb2 <- tibble(assignment, nstudents, nstudents1, propstudents, year, fyear)

# Another tibble for including 2023 all submission rounds
# Number of students for each round
nstudents<-c(c(176, 174, 158, 135, 138, 129, 126, 123, 121),
                c(242, 212, 184, 177, 174, 172, 163, 156, 153),
                c(332, 310, 278, 258, 243, 242, 226, 224, 218),
                c(301, 269, 231, 232, 217, 208, 193, 191, 190),
                c(245, 240, 228, 217, 206, 199, 191, 182, 175),
                c(264, 249, 215, 221, 215, 206, 192,  186, 179))

propstudents<-c(c(176, 174, 158, 135, 138, 129, 126, 123, 121)/176,
                c(242, 212, 184, 177, 174, 172, 163, 156, 153)/242,
                c(332, 310, 278, 258, 243, 242, 226, 224, 218)/332,
                c(301, 269, 231, 232, 217, 208, 193, 191, 190)/301,
                c(245, 240, 228, 217, 206, 199, 191, 182, 175)/245,
                c(264, 249, 215, 221, 215, 206, 192, 186, 179)/264)
# Year as integers and factors
year <- rep(2018:2023,each=9)
fyear <- factor(year)
# Assignment numbers
assignment <- rep(1:9, 6)
# Tibble
tb3 <- tibble(assignment, nstudents, nstudents1, propstudents, year, fyear)

Plot of submission numbers

ggplot(tb, aes(x=assignment, y=nstudents, group=year, color=as.factor(year-2017))) +
  geom_line(linewidth=1) + geom_point(size=3) +
  scale_x_continuous(breaks=1:9, lim=c(1,10.2)) +
  labs(x="Assignment", y="Number of students submitted", title="Student retention") +
  theme(legend.position="none") +
  scale_color_bright()+
  annotate(geom="text", x=rep(9.9, 6), y=c(121,153,218,192,169,180), label=c(2018:2023), size=5, color = color("bright")(6))

Plot of raw proportions *100 for different assignments and years

ggplot(tb, aes(x=assignment, y=propstudents*100, group=year, color=as.factor(year-2017))) +
  geom_line(linewidth=1) + geom_point(size=3) +
  scale_x_continuous(breaks=1:9, lim=c(1,10.2)) +
  ylim(c(55,100)) +
  labs(x="Assignment", y="Proportion submitted %", title="Student retention") +
  theme(legend.position="none") +
  scale_color_bright()+
  annotate(geom="text", x=rep(9.9, 5), y=c(69, 61, 66, 63.5, 72), label=c(2018:2022), size=5, color = color("bright")(5))

Models

Latent hierarchical linear model + binomial observation model

# save_pars is used for later moment matching
fit4 <- brm(nstudents | trials(nstudents1) ~ assignment + (assignment | year), family=binomial(), data=filter(tb, assignment>1), control = list(adapt_delta = 0.95), save_pars=save_pars(all=TRUE), seed=7253)
## Warning: There were 2 divergent transitions after warmup. See
## https://mc-stan.org/misc/warnings.html#divergent-transitions-after-warmup
## to find out why this is a problem and how to eliminate them.
## Warning: Examine the pairs() plot to diagnose sampling problems

First with plain PSIS-LOO, and we see some warnings

fit4 <- add_criterion(fit4, 'loo', save_psis=TRUE, moment_match=FALSE, overwrite=TRUE)
## Warning: Found 5 observations with a pareto_k > 0.7 in model 'fit4'. It is
## recommended to set 'moment_match = TRUE' in order to perform moment matching
## for problematic observations.
loo(fit4)
## 
## Computed from 4000 by 40 log-likelihood matrix
## 
##          Estimate   SE
## elpd_loo   -184.1 17.1
## p_loo        26.2  6.3
## looic       368.2 34.3
## ------
## Monte Carlo SE of elpd_loo is NA.
## 
## Pareto k diagnostic values:
##                          Count Pct.    Min. n_eff
## (-Inf, 0.5]   (good)     28    70.0%   399       
##  (0.5, 0.7]   (ok)        7    17.5%   77        
##    (0.7, 1]   (bad)       4    10.0%   46        
##    (1, Inf)   (very bad)  1     2.5%   49        
## See help('pareto-k-diagnostic') for details.

PSIS-LOO + moment matching. There is still one khat>0.7, which we could fix with reloo=TRUE, but skip that now.

# overwrite is needed to force LOO recomputation
fit4 <- add_criterion(fit4, 'loo', save_psis=TRUE, moment_match=TRUE, overwrite=TRUE)
## Warning: Some Pareto k diagnostic values are too high. See help('pareto-k-diagnostic') for details.
## Warning: Found 1 observations with a pareto_k > 0.7 in model 'fit4'. It is
## recommended to set 'reloo = TRUE' in order to calculate the ELPD without the
## assumption that these observations are negligible. This will refit the model 1
## times to compute the ELPDs for the problematic observations directly.
loo(fit4)
## 
## Computed from 4000 by 40 log-likelihood matrix
## 
##          Estimate   SE
## elpd_loo   -184.3 17.3
## p_loo        23.8  5.7
## looic       368.7 34.5
## ------
## Monte Carlo SE of elpd_loo is NA.
## 
## Pareto k diagnostic values:
##                          Count Pct.    Min. n_eff
## (-Inf, 0.5]   (good)     29    72.5%   399       
##  (0.5, 0.7]   (ok)       10    25.0%   77        
##    (0.7, 1]   (bad)       1     2.5%   165       
##    (1, Inf)   (very bad)  0     0.0%   <NA>      
## See help('pareto-k-diagnostic') for details.

Latent spline + hierarchical linear model with binomial observation model

fit6 <- brm(nstudents | trials(nstudents1) ~ s(assignment, k=4) + (assignment | year), family=binomial(), data=filter(tb, assignment>1), control = list(adapt_delta = 0.95), save_pars=save_pars(all=TRUE), seed=7253)
## Warning: There were 2 divergent transitions after warmup. See
## https://mc-stan.org/misc/warnings.html#divergent-transitions-after-warmup
## to find out why this is a problem and how to eliminate them.
## Warning: Examine the pairs() plot to diagnose sampling problems

First with plain PSIS-LOO, and we see some warnings

fit6 <- add_criterion(fit6, 'loo', save_psis=TRUE, moment_match=FALSE, overwrite=TRUE)
## Warning: Found 1 observations with a pareto_k > 0.7 in model 'fit6'. It is
## recommended to set 'moment_match = TRUE' in order to perform moment matching
## for problematic observations.
loo(fit6)
## 
## Computed from 4000 by 40 log-likelihood matrix
## 
##          Estimate   SE
## elpd_loo   -141.7  7.2
## p_loo        11.5  2.9
## looic       283.4 14.4
## ------
## Monte Carlo SE of elpd_loo is NA.
## 
## Pareto k diagnostic values:
##                          Count Pct.    Min. n_eff
## (-Inf, 0.5]   (good)     34    85.0%   558       
##  (0.5, 0.7]   (ok)        5    12.5%   226       
##    (0.7, 1]   (bad)       1     2.5%   215       
##    (1, Inf)   (very bad)  0     0.0%   <NA>      
## See help('pareto-k-diagnostic') for details.

PSIS-LOO + moment matching

# overwrite is needed to force LOO recomputation
fit6 <- add_criterion(fit6, 'loo', save_psis=TRUE, moment_match=TRUE, overwrite=TRUE)
## Warning: Some Pareto k diagnostic values are slightly high. See help('pareto-k-diagnostic') for details.
loo(fit6)
## 
## Computed from 4000 by 40 log-likelihood matrix
## 
##          Estimate   SE
## elpd_loo   -141.7  7.2
## p_loo        10.9  2.5
## looic       283.4 14.4
## ------
## Monte Carlo SE of elpd_loo is 0.1.
## 
## Pareto k diagnostic values:
##                          Count Pct.    Min. n_eff
## (-Inf, 0.5]   (good)     34    85.0%   558       
##  (0.5, 0.7]   (ok)        6    15.0%   226       
##    (0.7, 1]   (bad)       0     0.0%   <NA>      
##    (1, Inf)   (very bad)  0     0.0%   <NA>      
## 
## All Pareto k estimates are ok (k < 0.7).
## See help('pareto-k-diagnostic') for details.

Compare models

loo_compare(loo(fit4), loo(fit6))
##      elpd_diff se_diff
## fit6   0.0       0.0  
## fit4 -42.6      14.3

Model predictions

Plot intervals for assignment 9 proportion estimates

assign9linpred<-rvar(posterior_linpred(fit6, newdata=filter(tb,assignment==9), trandform=TRUE))
data.frame(year=c("2018","2019","2020","2021","2022"),propstudents=mean(assign9linpred),q05=quantile(assign9linpred,0.05)[1:5],q95=quantile(assign9linpred,0.95)[1:5])|>
ggplot(aes(x=year, y=propstudents*100, ymin=q05*100, ymax=q95*100)) +
  geom_pointrange(color=4) +
  labs(x="Year", y="Proportion submitted %", title="Proportion submitting 9th assign. (90% interval)")

Plot distribution of the difference in linear predictor

assign9linpred<-rvar(posterior_linpred(fit6, newdata=filter(tb,assignment==9), transform=TRUE))
(assign9linpred[5]-assign9linpred[1:4]) |>
  as_draws_df() |>
  rename_with(~ c("2018","2019","2020","2021"), starts_with("x")) |>
  pivot_longer(cols=starts_with("2"), names_to="year", values_to="value") |>
  ggplot(aes(y=year, x=value)) +
  stat_halfeye() +
  geom_vline(xintercept=0) +
  ylab("Year")+
  xlab(TeX("2022 retention was better $\\rightarrow$"))
## Warning: Dropping 'draws_df' class as required metadata was removed.

Plot model prediction of proportions for 2018-2022

tb |>
  filter(assignment>1)|>
  add_linpred_draws(fit6, transform=TRUE) |>
  ggplot(aes(x = assignment, y = propstudents, group=fyear)) +
  stat_lineribbon(aes(y = .linpred), .width = c(.95), alpha = 1/2, color=brewer.pal(5, "Blues")[[5]])+
  geom_point(data=tb, color=1)+
  scale_fill_brewer() +
  facet_grid(. ~ fyear)+
  theme(legend.position="none")+
  labs(x="Assignment", y = "Proportion of submissions")+
  scale_x_continuous(breaks=1:9, lim=c(1,10.2))

Plot model prediction of proportions for 2018-2023 after we have observed only the first assignment submission numbers for 2023

tb2 |>
  filter(assignment>1)|>
  group_by(fyear) |>
  add_linpred_draws(fit6, transform=TRUE, allow_new_levels=TRUE) |>
  ggplot(aes(x = assignment, y = propstudents, group=fyear)) +
  stat_lineribbon(aes(y = .linpred), .width = c(.95), alpha = 1/2, color=brewer.pal(5, "Blues")[[5]])+
  geom_point(data=tb, color=1)+
  scale_fill_brewer() +
  facet_grid(. ~ fyear)+
  theme(legend.position="none")+
  labs(x="Assignment", y = "Proportion of submissions")+
  scale_x_continuous(breaks=1:9, lim=c(1,10.2))

Update the posterior with 2023 data

fit6b <- update(fit6, newdata=filter(tb3, assignment>1))

Plot model prediction of proportions for 2018-2022 after observing all submission numbers for 2023

tb3 |>
  filter(assignment>1)|>
  add_linpred_draws(fit6b, transform=TRUE) |>
  ggplot(aes(x = assignment, y = propstudents, group=fyear)) +
  stat_lineribbon(aes(y = .linpred), .width = c(.95), alpha = 1/2, color=brewer.pal(5, "Blues")[[5]])+
  geom_point(data=tb3, color=1)+
  scale_fill_brewer() +
  facet_grid(. ~ fyear)+
  theme(legend.position="none")+
  labs(x="Assignment", y = "Proportion of submissions")+
  scale_x_continuous(breaks=1:9, lim=c(1,10.2))

The posterior predictive distribution for year 2023

posterior_linpred(fit6, allow_new_levels=TRUE,
                  newdata=filter(tb3,year==2023&assignment==9),
                  transform = TRUE) |>
  quantile(c(0.05,0.95))
##        5%       95% 
## 0.6055084 0.7182974

PPC

PPC density overlays

pp_check(fit4, ndraws=20)+
  xlim(c(50,370))

pp_check(fit6, ndraws=20)+
  xlim(c(50,370))

PPC intervals grouped

pp_check(fit4, type = "intervals_grouped", group="year",
         facet_args=list(nrow=1))

pp_check(fit6, type = "intervals_grouped", group="year",
         facet_args=list(nrow=1))

PPC ribbon grouped

pp_check(fit4, type = "ribbon_grouped", group="year",
         facet_args=list(nrow=1))

pp_check(fit6, type = "ribbon_grouped", group="year",
         facet_args=list(nrow=1))

PPC intervals

pp_check(fit4, type = "intervals")

pp_check(fit6, type = "intervals")

PPC LOO intervals. We get LOO warnings, but in this case they don’t matter.

pp_check(fit4, type = "loo_intervals")
## Warning: Some Pareto k diagnostic values are too high. See help('pareto-k-diagnostic') for details.

pp_check(fit6, type = "loo_intervals")
## Warning: Some Pareto k diagnostic values are too high. See help('pareto-k-diagnostic') for details.

PPC LOO-PIT-QQ-uniform

pp_check(fit4, type = "loo_pit_qq", ndraws=4000)
## Warning: Some Pareto k diagnostic values are too high. See help('pareto-k-diagnostic') for details.

pp_check(fit6, type = "loo_pit_qq", ndraws=4000)
## Warning: Some Pareto k diagnostic values are too high. See help('pareto-k-diagnostic') for details.

PPC LOO-PIT-QQ-normal

pp_check(fit4, type = "loo_pit_qq", ndraws=4000, compare="normal")
## Warning: Some Pareto k diagnostic values are too high. See help('pareto-k-diagnostic') for details.
## Warning in stats::qnorm(pit): NaNs produced
## Warning: Removed 2 rows containing non-finite values (`stat_qq()`).
## Warning: Removed 2 rows containing non-finite values (`stat_qq_line()`).

pp_check(fit6, type = "loo_pit_qq", ndraws=4000, compare="normal")
## Warning: Some Pareto k diagnostic values are too high. See help('pareto-k-diagnostic') for details.

LS0tCnRpdGxlOiAiQmF5ZXNpYW4gZGF0YSBhbmFseXNpcyAtIFN0dWRlbnQgcmV0ZW50aW9uIgphdXRob3I6ICJBa2kgVmVodGFyaSIKZGF0ZTogIkZpcnN0IHZlcnNpb24gMjAyMy0xMS0yMi4gTGFzdCBtb2RpZmllZCBgciBmb3JtYXQoU3lzLkRhdGUoKSlgLiIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBmaWdfY2FwdGlvbjogeWVzCiAgICB0b2M6IFRSVUUKICAgIHRvY19kZXB0aDogMgogICAgbnVtYmVyX3NlY3Rpb25zOiBGQUxTRQogICAgdG9jX2Zsb2F0OgogICAgICBzbW9vdGhfc2Nyb2xsOiBGQUxTRQogICAgdGhlbWU6IHJlYWRhYmxlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChjYWNoZT1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgZXJyb3I9RkFMU0UsIHdhcm5pbmc9VFJVRSwgb3V0LndpZHRoPSc5NSUnKQpgYGAKCiMgSW50cm9kdWN0aW9uCgpUaGlzIGluY2x1ZGVzIHRoZSBjb2RlIHVzZWQgdG8gY3JlYXRlIHRoZSBtb2RlbHMgYW5kIHBsb3RzIGZvcgpzdHVkZW50IHJldGVudGlvbiBkYXRhIHVzZWQgYXMgYW4gZXhhbXBsZSBpbiBzZXZlcmFsIEJEQSBjb3Vyc2UKbGVjdHVyZXMgaW4gMjAyMy4KCiMjIyMgTG9hZCBwYWNrYWdlcwoKYGBge3J9CmxpYnJhcnkoYnJtcykKI29wdGlvbnMoYnJtcy5iYWNrZW5kID0gImNtZHN0YW5yIikKIyBVc2luZyBSU3RhbiBiYWNrZW5kIGZvciBtb21lbnQgbWF0Y2hpbmcgaW4gTE9PCm9wdGlvbnMoYnJtcy5iYWNrZW5kID0gInJzdGFuIikKbGlicmFyeShwb3N0ZXJpb3IpCmxpYnJhcnkodGlkeWJheWVzKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZ2dkaXN0KQpsaWJyYXJ5KGxhdGV4MmV4cCkKbGlicmFyeShraHJvbWEpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQp0aGVtZV9zZXQoYmF5ZXNwbG90Ojp0aGVtZV9kZWZhdWx0KGJhc2VfZmFtaWx5ID0gInNhbnMiLCBiYXNlX3NpemU9MTYpKQpgYGAKCiMgRGF0YQoKU2luY2UgMjAxOCwgdGhlcmUgaGF2ZSBiZWVuIDkgYXNzaWdubWVudHMgaW4gQkRBIGNvdXJzZS4gIERhdGEgYXJlCnRoZSBudW1iZXIgb2Ygc3R1ZGVudHMgd2hvIHN1Ym1pdHRlZCBlYWNoIGFzc2lnbm1lbnQuIEFzIHRoZSBjb3Vyc2UKaXMgbm90IGNvbXB1bHNvcnkgZm9yIG1vc3QsIGFuZCBpdCBpcyBjb21tb24gdGhhdCBzb21lIHN0dWRlbnRzCnJlZ2lzdGVyIGZvciBtb3JlIGNvdXJzZXMgdGhhbiB0aGV5IG5lZWQgYW5kIGR1cmluZyB0aGUgY291cnNlIG1heQpkZWNpZGUgdG8gZHJvcC1vdXQuIEFsc28gYXMgdGhlIHN0dWRlbnRzIGNhbiBzdWJtaXQgYWxzbyBpbiBzcHJpbmcsCnNvbWUgc3R1ZGVudHMgbWF5IHN0b3Agc3VibWl0dGluZyBpbiBhdXR1bW4gYW5kIGdldCBiYWNrIGluIHNwcmluZy4KQWx0aG91Z2ggdGhlcmUgYXJlIGFsc28gZXh0ZXJuYWwgcmVhc29ucyB3aHkgc3R1ZGVudHMgZHJvcCBvdXQgZnJvbQp0aGUgY291cnNlLCB3ZSBhcmUgaW50ZXJlc3RlZCBpbiBmb2xsb3dpbmcgdGhlIHJldGVudGlvbiBhbmQgaG9wZQp0aGF0IHRoZSBjaGFuZ2VzIGluIHRoZSBjb3Vyc2Ugd291bGQgaW1wcm92ZSB0aGUgcmV0ZW50aW9uLiBBcyBhCnN0YXJ0aW5nIHBvaW50IHdlIHdhbnQgdG8gYW5hbHlzZSB3aGV0aGVyIHdlIGNhbiBzZWUgZGlmZmVyZW5jZXMgaW4KeWVhcnMuCgoKYGBge3J9CiMgTnVtYmVyIG9mIHN0dWRlbnRzIHdobyByZXR1cm5lZCB0aGUgZmlyc3QgYXNzaWdubWVudCBvbiAyMDE4LTIwMjIKbnN0dWRlbnRzMTwtcmVwKGMoMTc2LDI0MiwzMzIsMzAxLDI0NSksZWFjaD05KQojIE51bWJlciBvZiBzdHVkZW50cyBmb3IgZWFjaCByb3VuZApuc3R1ZGVudHM8LWMoYygxNzYsIDE3NCwgMTU4LCAxMzUsIDEzOCwgMTI5LCAxMjYsIDEyMywgMTIxKSwKICAgICAgICAgICAgIGMoMjQyLCAyMTIsIDE4NCwgMTc3LCAxNzQsIDE3MiwgMTYzLCAxNTYsIDE1MyksCiAgICAgICAgICAgICBjKDMzMiwgMzEwLCAyNzgsIDI1OCwgMjQzLCAyNDIsIDIyNiwgMjI0LCAyMTgpLAogICAgICAgICAgICAgYygzMDEsIDI2OSwgMjMxLCAyMzIsIDIxNywgMjA4LCAxOTMsIDE5MSwgMTkwKSwKICAgICAgICAgICAgIGMoMjQ1LCAyNDAsIDIyOCwgMjE3LCAyMDYsIDE5OSwgMTkxLCAxODIsIDE3NSkpCiMgUHJvcG9ydGlvbiBvZiBzdHVkZW50cwpwcm9wc3R1ZGVudHM8LWMoYygxNzYsIDE3NCwgMTU4LCAxMzUsIDEzOCwgMTI5LCAxMjYsIDEyMywgMTIxKS8xNzYsCiAgICAgICAgICAgICAgICBjKDI0MiwgMjEyLCAxODQsIDE3NywgMTc0LCAxNzIsIDE2MywgMTU2LCAxNTMpLzI0MiwKICAgICAgICAgICAgICAgIGMoMzMyLCAzMTAsIDI3OCwgMjU4LCAyNDMsIDI0MiwgMjI2LCAyMjQsIDIxOCkvMzMyLAogICAgICAgICAgICAgICAgYygzMDEsIDI2OSwgMjMxLCAyMzIsIDIxNywgMjA4LCAxOTMsIDE5MSwgMTkwKS8zMDEsCiAgICAgICAgICAgICAgICBjKDI0NSwgMjQwLCAyMjgsIDIxNywgMjA2LCAxOTksIDE5MSwgMTgyLCAxNzUpLzI0NSkKIyBZZWFyIGFzIGludGVnZXJzIGFuZCBmYWN0b3JzCnllYXIgPC0gcmVwKDIwMTg6MjAyMixlYWNoPTkpCmZ5ZWFyIDwtIGZhY3Rvcih5ZWFyKQojIEFzc2lnbm1lbnQgbnVtYmVycwphc3NpZ25tZW50IDwtIHJlcCgxOjksIDUpCiMgVGliYmxlCnRiIDwtIHRpYmJsZShhc3NpZ25tZW50LCBuc3R1ZGVudHMsIG5zdHVkZW50czEsIHByb3BzdHVkZW50cywgeWVhciwgZnllYXIpCgojIEFub3RoZXIgdGliYmxlIGZvciBpbmNsdWRpbmcgMjAyMyBmaXJzdCBzdWJtaXNzaW9uIG51bWJlcnMKIyBOdW1iZXIgb2Ygc3R1ZGVudHMgd2hvIHJldHVybmVkIHRoZSBmaXJzdCBhc3NpZ25tZW50Cm5zdHVkZW50czE8LXJlcChjKDE3NiwyNDIsMzMyLDMwMSwyNDUsMjY0KSxlYWNoPTkpCiMgTnVtYmVyIG9mIHN0dWRlbnRzIGZvciBlYWNoIHJvdW5kCm5zdHVkZW50czwtYyhjKDE3NiwgMTc0LCAxNTgsIDEzNSwgMTM4LCAxMjksIDEyNiwgMTIzLCAxMjEpLAogICAgICAgICAgICAgICAgYygyNDIsIDIxMiwgMTg0LCAxNzcsIDE3NCwgMTcyLCAxNjMsIDE1NiwgMTUzKSwKICAgICAgICAgICAgICAgIGMoMzMyLCAzMTAsIDI3OCwgMjU4LCAyNDMsIDI0MiwgMjI2LCAyMjQsIDIxOCksCiAgICAgICAgICAgICAgICBjKDMwMSwgMjY5LCAyMzEsIDIzMiwgMjE3LCAyMDgsIDE5MywgMTkxLCAxOTApLAogICAgICAgICAgICAgICAgYygyNDUsIDI0MCwgMjI4LCAyMTcsIDIwNiwgMTk5LCAxOTEsIDE4MiwgMTc1KSwKICAgICAgICAgICAgICAgIGMoMjY0LCAgTkEsICBOQSwgIE5BLCAgTkEsICBOQSwgIE5BLCAgTkEsICBOQSkpCgpwcm9wc3R1ZGVudHM8LWMoYygxNzYsIDE3NCwgMTU4LCAxMzUsIDEzOCwgMTI5LCAxMjYsIDEyMywgMTIxKS8xNzYsCiAgICAgICAgICAgICAgICBjKDI0MiwgMjEyLCAxODQsIDE3NywgMTc0LCAxNzIsIDE2MywgMTU2LCAxNTMpLzI0MiwKICAgICAgICAgICAgICAgIGMoMzMyLCAzMTAsIDI3OCwgMjU4LCAyNDMsIDI0MiwgMjI2LCAyMjQsIDIxOCkvMzMyLAogICAgICAgICAgICAgICAgYygzMDEsIDI2OSwgMjMxLCAyMzIsIDIxNywgMjA4LCAxOTMsIDE5MSwgMTkwKS8zMDEsCiAgICAgICAgICAgICAgICBjKDI0NSwgMjQwLCAyMjgsIDIxNywgMjA2LCAxOTksIDE5MSwgMTgyLCAxNzUpLzI0NSwKICAgICAgICAgICAgICAgIGMoMjY0LCAgTkEsICBOQSwgIE5BLCAgTkEsICBOQSwgIE5BLCAgTkEsICBOQSkvMjY0KQojIFllYXIgYXMgaW50ZWdlcnMgYW5kIGZhY3RvcnMKeWVhciA8LSByZXAoMjAxODoyMDIzLGVhY2g9OSkKZnllYXIgPC0gZmFjdG9yKHllYXIpCiMgQXNzaWdubWVudCBudW1iZXJzCmFzc2lnbm1lbnQgPC0gcmVwKDE6OSwgNikKIyBUaWJibGUKdGIyIDwtIHRpYmJsZShhc3NpZ25tZW50LCBuc3R1ZGVudHMsIG5zdHVkZW50czEsIHByb3BzdHVkZW50cywgeWVhciwgZnllYXIpCgojIEFub3RoZXIgdGliYmxlIGZvciBpbmNsdWRpbmcgMjAyMyBhbGwgc3VibWlzc2lvbiByb3VuZHMKIyBOdW1iZXIgb2Ygc3R1ZGVudHMgZm9yIGVhY2ggcm91bmQKbnN0dWRlbnRzPC1jKGMoMTc2LCAxNzQsIDE1OCwgMTM1LCAxMzgsIDEyOSwgMTI2LCAxMjMsIDEyMSksCiAgICAgICAgICAgICAgICBjKDI0MiwgMjEyLCAxODQsIDE3NywgMTc0LCAxNzIsIDE2MywgMTU2LCAxNTMpLAogICAgICAgICAgICAgICAgYygzMzIsIDMxMCwgMjc4LCAyNTgsIDI0MywgMjQyLCAyMjYsIDIyNCwgMjE4KSwKICAgICAgICAgICAgICAgIGMoMzAxLCAyNjksIDIzMSwgMjMyLCAyMTcsIDIwOCwgMTkzLCAxOTEsIDE5MCksCiAgICAgICAgICAgICAgICBjKDI0NSwgMjQwLCAyMjgsIDIxNywgMjA2LCAxOTksIDE5MSwgMTgyLCAxNzUpLAogICAgICAgICAgICAgICAgYygyNjQsIDI0OSwgMjE1LCAyMjEsIDIxNSwgMjA2LCAxOTIsICAxODYsIDE3OSkpCgpwcm9wc3R1ZGVudHM8LWMoYygxNzYsIDE3NCwgMTU4LCAxMzUsIDEzOCwgMTI5LCAxMjYsIDEyMywgMTIxKS8xNzYsCiAgICAgICAgICAgICAgICBjKDI0MiwgMjEyLCAxODQsIDE3NywgMTc0LCAxNzIsIDE2MywgMTU2LCAxNTMpLzI0MiwKICAgICAgICAgICAgICAgIGMoMzMyLCAzMTAsIDI3OCwgMjU4LCAyNDMsIDI0MiwgMjI2LCAyMjQsIDIxOCkvMzMyLAogICAgICAgICAgICAgICAgYygzMDEsIDI2OSwgMjMxLCAyMzIsIDIxNywgMjA4LCAxOTMsIDE5MSwgMTkwKS8zMDEsCiAgICAgICAgICAgICAgICBjKDI0NSwgMjQwLCAyMjgsIDIxNywgMjA2LCAxOTksIDE5MSwgMTgyLCAxNzUpLzI0NSwKICAgICAgICAgICAgICAgIGMoMjY0LCAyNDksIDIxNSwgMjIxLCAyMTUsIDIwNiwgMTkyLCAxODYsIDE3OSkvMjY0KQojIFllYXIgYXMgaW50ZWdlcnMgYW5kIGZhY3RvcnMKeWVhciA8LSByZXAoMjAxODoyMDIzLGVhY2g9OSkKZnllYXIgPC0gZmFjdG9yKHllYXIpCiMgQXNzaWdubWVudCBudW1iZXJzCmFzc2lnbm1lbnQgPC0gcmVwKDE6OSwgNikKIyBUaWJibGUKdGIzIDwtIHRpYmJsZShhc3NpZ25tZW50LCBuc3R1ZGVudHMsIG5zdHVkZW50czEsIHByb3BzdHVkZW50cywgeWVhciwgZnllYXIpCmBgYAoKUGxvdCBvZiBzdWJtaXNzaW9uIG51bWJlcnMKCmBgYHtyfQpnZ3Bsb3QodGIsIGFlcyh4PWFzc2lnbm1lbnQsIHk9bnN0dWRlbnRzLCBncm91cD15ZWFyLCBjb2xvcj1hcy5mYWN0b3IoeWVhci0yMDE3KSkpICsKICBnZW9tX2xpbmUobGluZXdpZHRoPTEpICsgZ2VvbV9wb2ludChzaXplPTMpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPTE6OSwgbGltPWMoMSwxMC4yKSkgKwogIGxhYnMoeD0iQXNzaWdubWVudCIsIHk9Ik51bWJlciBvZiBzdHVkZW50cyBzdWJtaXR0ZWQiLCB0aXRsZT0iU3R1ZGVudCByZXRlbnRpb24iKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKwogIHNjYWxlX2NvbG9yX2JyaWdodCgpKwogIGFubm90YXRlKGdlb209InRleHQiLCB4PXJlcCg5LjksIDYpLCB5PWMoMTIxLDE1MywyMTgsMTkyLDE2OSwxODApLCBsYWJlbD1jKDIwMTg6MjAyMyksIHNpemU9NSwgY29sb3IgPSBjb2xvcigiYnJpZ2h0IikoNikpCmBgYAoKUGxvdCBvZiByYXcgcHJvcG9ydGlvbnMgKjEwMCBmb3IgZGlmZmVyZW50IGFzc2lnbm1lbnRzIGFuZCB5ZWFycwoKYGBge3J9CmdncGxvdCh0YiwgYWVzKHg9YXNzaWdubWVudCwgeT1wcm9wc3R1ZGVudHMqMTAwLCBncm91cD15ZWFyLCBjb2xvcj1hcy5mYWN0b3IoeWVhci0yMDE3KSkpICsKICBnZW9tX2xpbmUobGluZXdpZHRoPTEpICsgZ2VvbV9wb2ludChzaXplPTMpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPTE6OSwgbGltPWMoMSwxMC4yKSkgKwogIHlsaW0oYyg1NSwxMDApKSArCiAgbGFicyh4PSJBc3NpZ25tZW50IiwgeT0iUHJvcG9ydGlvbiBzdWJtaXR0ZWQgJSIsIHRpdGxlPSJTdHVkZW50IHJldGVudGlvbiIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249Im5vbmUiKSArCiAgc2NhbGVfY29sb3JfYnJpZ2h0KCkrCiAgYW5ub3RhdGUoZ2VvbT0idGV4dCIsIHg9cmVwKDkuOSwgNSksIHk9Yyg2OSwgNjEsIDY2LCA2My41LCA3MiksIGxhYmVsPWMoMjAxODoyMDIyKSwgc2l6ZT01LCBjb2xvciA9IGNvbG9yKCJicmlnaHQiKSg1KSkKYGBgCgojIE1vZGVscwoKTGF0ZW50IGhpZXJhcmNoaWNhbCBsaW5lYXIgbW9kZWwgKyBiaW5vbWlhbCBvYnNlcnZhdGlvbiBtb2RlbAoKYGBge3J9CiMgc2F2ZV9wYXJzIGlzIHVzZWQgZm9yIGxhdGVyIG1vbWVudCBtYXRjaGluZwpgYGAKYGBge3IgcmVzdWx0cz0naGlkZSd9CmZpdDQgPC0gYnJtKG5zdHVkZW50cyB8IHRyaWFscyhuc3R1ZGVudHMxKSB+IGFzc2lnbm1lbnQgKyAoYXNzaWdubWVudCB8IHllYXIpLCBmYW1pbHk9Ymlub21pYWwoKSwgZGF0YT1maWx0ZXIodGIsIGFzc2lnbm1lbnQ+MSksIGNvbnRyb2wgPSBsaXN0KGFkYXB0X2RlbHRhID0gMC45NSksIHNhdmVfcGFycz1zYXZlX3BhcnMoYWxsPVRSVUUpLCBzZWVkPTcyNTMpCmBgYAoKRmlyc3Qgd2l0aCBwbGFpbiBQU0lTLUxPTywgYW5kIHdlIHNlZSBzb21lIHdhcm5pbmdzCgpgYGB7cn0KZml0NCA8LSBhZGRfY3JpdGVyaW9uKGZpdDQsICdsb28nLCBzYXZlX3BzaXM9VFJVRSwgbW9tZW50X21hdGNoPUZBTFNFLCBvdmVyd3JpdGU9VFJVRSkKYGBgCmBgYHtyfQpsb28oZml0NCkKYGBgCgpQU0lTLUxPTyArIG1vbWVudCBtYXRjaGluZy4gVGhlcmUgaXMgc3RpbGwgb25lIGtoYXQ+MC43LCB3aGljaCB3ZQpjb3VsZCBmaXggd2l0aCByZWxvbz1UUlVFLCBidXQgc2tpcCB0aGF0IG5vdy4KCmBgYHtyfQojIG92ZXJ3cml0ZSBpcyBuZWVkZWQgdG8gZm9yY2UgTE9PIHJlY29tcHV0YXRpb24KZml0NCA8LSBhZGRfY3JpdGVyaW9uKGZpdDQsICdsb28nLCBzYXZlX3BzaXM9VFJVRSwgbW9tZW50X21hdGNoPVRSVUUsIG92ZXJ3cml0ZT1UUlVFKQpsb28oZml0NCkKYGBgCgpMYXRlbnQgc3BsaW5lICsgaGllcmFyY2hpY2FsIGxpbmVhciBtb2RlbCB3aXRoIGJpbm9taWFsIG9ic2VydmF0aW9uIG1vZGVsCgpgYGB7ciByZXN1bHRzPSdoaWRlJ30KZml0NiA8LSBicm0obnN0dWRlbnRzIHwgdHJpYWxzKG5zdHVkZW50czEpIH4gcyhhc3NpZ25tZW50LCBrPTQpICsgKGFzc2lnbm1lbnQgfCB5ZWFyKSwgZmFtaWx5PWJpbm9taWFsKCksIGRhdGE9ZmlsdGVyKHRiLCBhc3NpZ25tZW50PjEpLCBjb250cm9sID0gbGlzdChhZGFwdF9kZWx0YSA9IDAuOTUpLCBzYXZlX3BhcnM9c2F2ZV9wYXJzKGFsbD1UUlVFKSwgc2VlZD03MjUzKQpgYGAKCkZpcnN0IHdpdGggcGxhaW4gUFNJUy1MT08sIGFuZCB3ZSBzZWUgc29tZSB3YXJuaW5ncwoKYGBge3J9CmZpdDYgPC0gYWRkX2NyaXRlcmlvbihmaXQ2LCAnbG9vJywgc2F2ZV9wc2lzPVRSVUUsIG1vbWVudF9tYXRjaD1GQUxTRSwgb3ZlcndyaXRlPVRSVUUpCmBgYApgYGB7cn0KbG9vKGZpdDYpCmBgYAoKUFNJUy1MT08gKyBtb21lbnQgbWF0Y2hpbmcKCmBgYHtyfQojIG92ZXJ3cml0ZSBpcyBuZWVkZWQgdG8gZm9yY2UgTE9PIHJlY29tcHV0YXRpb24KZml0NiA8LSBhZGRfY3JpdGVyaW9uKGZpdDYsICdsb28nLCBzYXZlX3BzaXM9VFJVRSwgbW9tZW50X21hdGNoPVRSVUUsIG92ZXJ3cml0ZT1UUlVFKQpsb28oZml0NikKYGBgCgpDb21wYXJlIG1vZGVscwoKYGBge3J9Cmxvb19jb21wYXJlKGxvbyhmaXQ0KSwgbG9vKGZpdDYpKQpgYGAKCiMgTW9kZWwgcHJlZGljdGlvbnMKClBsb3QgaW50ZXJ2YWxzIGZvciBhc3NpZ25tZW50IDkgcHJvcG9ydGlvbiBlc3RpbWF0ZXMKCmBgYHtyfQphc3NpZ245bGlucHJlZDwtcnZhcihwb3N0ZXJpb3JfbGlucHJlZChmaXQ2LCBuZXdkYXRhPWZpbHRlcih0Yixhc3NpZ25tZW50PT05KSwgdHJhbmRmb3JtPVRSVUUpKQpkYXRhLmZyYW1lKHllYXI9YygiMjAxOCIsIjIwMTkiLCIyMDIwIiwiMjAyMSIsIjIwMjIiKSxwcm9wc3R1ZGVudHM9bWVhbihhc3NpZ245bGlucHJlZCkscTA1PXF1YW50aWxlKGFzc2lnbjlsaW5wcmVkLDAuMDUpWzE6NV0scTk1PXF1YW50aWxlKGFzc2lnbjlsaW5wcmVkLDAuOTUpWzE6NV0pfD4KZ2dwbG90KGFlcyh4PXllYXIsIHk9cHJvcHN0dWRlbnRzKjEwMCwgeW1pbj1xMDUqMTAwLCB5bWF4PXE5NSoxMDApKSArCiAgZ2VvbV9wb2ludHJhbmdlKGNvbG9yPTQpICsKICBsYWJzKHg9IlllYXIiLCB5PSJQcm9wb3J0aW9uIHN1Ym1pdHRlZCAlIiwgdGl0bGU9IlByb3BvcnRpb24gc3VibWl0dGluZyA5dGggYXNzaWduLiAoOTAlIGludGVydmFsKSIpCmBgYAoKUGxvdCBkaXN0cmlidXRpb24gb2YgdGhlIGRpZmZlcmVuY2UgaW4gbGluZWFyIHByZWRpY3RvcgoKYGBge3J9CmFzc2lnbjlsaW5wcmVkPC1ydmFyKHBvc3Rlcmlvcl9saW5wcmVkKGZpdDYsIG5ld2RhdGE9ZmlsdGVyKHRiLGFzc2lnbm1lbnQ9PTkpLCB0cmFuc2Zvcm09VFJVRSkpCihhc3NpZ245bGlucHJlZFs1XS1hc3NpZ245bGlucHJlZFsxOjRdKSB8PgogIGFzX2RyYXdzX2RmKCkgfD4KICByZW5hbWVfd2l0aCh+IGMoIjIwMTgiLCIyMDE5IiwiMjAyMCIsIjIwMjEiKSwgc3RhcnRzX3dpdGgoIngiKSkgfD4KICBwaXZvdF9sb25nZXIoY29scz1zdGFydHNfd2l0aCgiMiIpLCBuYW1lc190bz0ieWVhciIsIHZhbHVlc190bz0idmFsdWUiKSB8PgogIGdncGxvdChhZXMoeT15ZWFyLCB4PXZhbHVlKSkgKwogIHN0YXRfaGFsZmV5ZSgpICsKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MCkgKwogIHlsYWIoIlllYXIiKSsKICB4bGFiKFRlWCgiMjAyMiByZXRlbnRpb24gd2FzIGJldHRlciAkXFxyaWdodGFycm93JCIpKQpgYGAKClBsb3QgbW9kZWwgcHJlZGljdGlvbiBvZiBwcm9wb3J0aW9ucyBmb3IgMjAxOC0yMDIyCgpgYGB7cn0KdGIgfD4KICBmaWx0ZXIoYXNzaWdubWVudD4xKXw+CiAgYWRkX2xpbnByZWRfZHJhd3MoZml0NiwgdHJhbnNmb3JtPVRSVUUpIHw+CiAgZ2dwbG90KGFlcyh4ID0gYXNzaWdubWVudCwgeSA9IHByb3BzdHVkZW50cywgZ3JvdXA9ZnllYXIpKSArCiAgc3RhdF9saW5lcmliYm9uKGFlcyh5ID0gLmxpbnByZWQpLCAud2lkdGggPSBjKC45NSksIGFscGhhID0gMS8yLCBjb2xvcj1icmV3ZXIucGFsKDUsICJCbHVlcyIpW1s1XV0pKwogIGdlb21fcG9pbnQoZGF0YT10YiwgY29sb3I9MSkrCiAgc2NhbGVfZmlsbF9icmV3ZXIoKSArCiAgZmFjZXRfZ3JpZCguIH4gZnllYXIpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpKwogIGxhYnMoeD0iQXNzaWdubWVudCIsIHkgPSAiUHJvcG9ydGlvbiBvZiBzdWJtaXNzaW9ucyIpKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9MTo5LCBsaW09YygxLDEwLjIpKQpgYGAKClBsb3QgbW9kZWwgcHJlZGljdGlvbiBvZiBwcm9wb3J0aW9ucyBmb3IgMjAxOC0yMDIzIGFmdGVyIHdlIGhhdmUKb2JzZXJ2ZWQgb25seSB0aGUgZmlyc3QgYXNzaWdubWVudCBzdWJtaXNzaW9uIG51bWJlcnMgZm9yIDIwMjMKCmBgYHtyfQp0YjIgfD4KICBmaWx0ZXIoYXNzaWdubWVudD4xKXw+CiAgZ3JvdXBfYnkoZnllYXIpIHw+CiAgYWRkX2xpbnByZWRfZHJhd3MoZml0NiwgdHJhbnNmb3JtPVRSVUUsIGFsbG93X25ld19sZXZlbHM9VFJVRSkgfD4KICBnZ3Bsb3QoYWVzKHggPSBhc3NpZ25tZW50LCB5ID0gcHJvcHN0dWRlbnRzLCBncm91cD1meWVhcikpICsKICBzdGF0X2xpbmVyaWJib24oYWVzKHkgPSAubGlucHJlZCksIC53aWR0aCA9IGMoLjk1KSwgYWxwaGEgPSAxLzIsIGNvbG9yPWJyZXdlci5wYWwoNSwgIkJsdWVzIilbWzVdXSkrCiAgZ2VvbV9wb2ludChkYXRhPXRiLCBjb2xvcj0xKSsKICBzY2FsZV9maWxsX2JyZXdlcigpICsKICBmYWNldF9ncmlkKC4gfiBmeWVhcikrCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikrCiAgbGFicyh4PSJBc3NpZ25tZW50IiwgeSA9ICJQcm9wb3J0aW9uIG9mIHN1Ym1pc3Npb25zIikrCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcz0xOjksIGxpbT1jKDEsMTAuMikpCmBgYAoKVXBkYXRlIHRoZSBwb3N0ZXJpb3Igd2l0aCAyMDIzIGRhdGEKCmBgYHtyIHJlc3VsdHM9J2hpZGUnfQpmaXQ2YiA8LSB1cGRhdGUoZml0NiwgbmV3ZGF0YT1maWx0ZXIodGIzLCBhc3NpZ25tZW50PjEpKQpgYGAKClBsb3QgbW9kZWwgcHJlZGljdGlvbiBvZiBwcm9wb3J0aW9ucyBmb3IgMjAxOC0yMDIyIGFmdGVyIG9ic2VydmluZwphbGwgc3VibWlzc2lvbiBudW1iZXJzIGZvciAyMDIzCgpgYGB7cn0KdGIzIHw+CiAgZmlsdGVyKGFzc2lnbm1lbnQ+MSl8PgogIGFkZF9saW5wcmVkX2RyYXdzKGZpdDZiLCB0cmFuc2Zvcm09VFJVRSkgfD4KICBnZ3Bsb3QoYWVzKHggPSBhc3NpZ25tZW50LCB5ID0gcHJvcHN0dWRlbnRzLCBncm91cD1meWVhcikpICsKICBzdGF0X2xpbmVyaWJib24oYWVzKHkgPSAubGlucHJlZCksIC53aWR0aCA9IGMoLjk1KSwgYWxwaGEgPSAxLzIsIGNvbG9yPWJyZXdlci5wYWwoNSwgIkJsdWVzIilbWzVdXSkrCiAgZ2VvbV9wb2ludChkYXRhPXRiMywgY29sb3I9MSkrCiAgc2NhbGVfZmlsbF9icmV3ZXIoKSArCiAgZmFjZXRfZ3JpZCguIH4gZnllYXIpKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpKwogIGxhYnMoeD0iQXNzaWdubWVudCIsIHkgPSAiUHJvcG9ydGlvbiBvZiBzdWJtaXNzaW9ucyIpKwogIHNjYWxlX3hfY29udGludW91cyhicmVha3M9MTo5LCBsaW09YygxLDEwLjIpKQpgYGAKClRoZSBwb3N0ZXJpb3IgcHJlZGljdGl2ZSBkaXN0cmlidXRpb24gZm9yIHllYXIgMjAyMwoKYGBge3J9CnBvc3Rlcmlvcl9saW5wcmVkKGZpdDYsIGFsbG93X25ld19sZXZlbHM9VFJVRSwKICAgICAgICAgICAgICAgICAgbmV3ZGF0YT1maWx0ZXIodGIzLHllYXI9PTIwMjMmYXNzaWdubWVudD09OSksCiAgICAgICAgICAgICAgICAgIHRyYW5zZm9ybSA9IFRSVUUpIHw+CiAgcXVhbnRpbGUoYygwLjA1LDAuOTUpKQpgYGAKCiMgUFBDCgpQUEMgZGVuc2l0eSBvdmVybGF5cwoKYGBge3J9CnBwX2NoZWNrKGZpdDQsIG5kcmF3cz0yMCkrCiAgeGxpbShjKDUwLDM3MCkpCnBwX2NoZWNrKGZpdDYsIG5kcmF3cz0yMCkrCiAgeGxpbShjKDUwLDM3MCkpCmBgYAoKUFBDIGludGVydmFscyBncm91cGVkCgpgYGB7cn0KcHBfY2hlY2soZml0NCwgdHlwZSA9ICJpbnRlcnZhbHNfZ3JvdXBlZCIsIGdyb3VwPSJ5ZWFyIiwKICAgICAgICAgZmFjZXRfYXJncz1saXN0KG5yb3c9MSkpCnBwX2NoZWNrKGZpdDYsIHR5cGUgPSAiaW50ZXJ2YWxzX2dyb3VwZWQiLCBncm91cD0ieWVhciIsCiAgICAgICAgIGZhY2V0X2FyZ3M9bGlzdChucm93PTEpKQpgYGAKClBQQyByaWJib24gZ3JvdXBlZAoKYGBge3J9CnBwX2NoZWNrKGZpdDQsIHR5cGUgPSAicmliYm9uX2dyb3VwZWQiLCBncm91cD0ieWVhciIsCiAgICAgICAgIGZhY2V0X2FyZ3M9bGlzdChucm93PTEpKQpwcF9jaGVjayhmaXQ2LCB0eXBlID0gInJpYmJvbl9ncm91cGVkIiwgZ3JvdXA9InllYXIiLAogICAgICAgICBmYWNldF9hcmdzPWxpc3QobnJvdz0xKSkKYGBgCgpQUEMgaW50ZXJ2YWxzCgpgYGB7cn0KcHBfY2hlY2soZml0NCwgdHlwZSA9ICJpbnRlcnZhbHMiKQpwcF9jaGVjayhmaXQ2LCB0eXBlID0gImludGVydmFscyIpCmBgYAoKUFBDIExPTyBpbnRlcnZhbHMuIFdlIGdldCBMT08gd2FybmluZ3MsIGJ1dCBpbiB0aGlzIGNhc2UgdGhleSBkb24ndCBtYXR0ZXIuCgpgYGB7cn0KcHBfY2hlY2soZml0NCwgdHlwZSA9ICJsb29faW50ZXJ2YWxzIikKcHBfY2hlY2soZml0NiwgdHlwZSA9ICJsb29faW50ZXJ2YWxzIikKYGBgCgpQUEMgTE9PLVBJVC1RUS11bmlmb3JtCgpgYGB7cn0KcHBfY2hlY2soZml0NCwgdHlwZSA9ICJsb29fcGl0X3FxIiwgbmRyYXdzPTQwMDApCnBwX2NoZWNrKGZpdDYsIHR5cGUgPSAibG9vX3BpdF9xcSIsIG5kcmF3cz00MDAwKQpgYGAKClBQQyBMT08tUElULVFRLW5vcm1hbAoKYGBge3J9CnBwX2NoZWNrKGZpdDQsIHR5cGUgPSAibG9vX3BpdF9xcSIsIG5kcmF3cz00MDAwLCBjb21wYXJlPSJub3JtYWwiKQpwcF9jaGVjayhmaXQ2LCB0eXBlID0gImxvb19waXRfcXEiLCBuZHJhd3M9NDAwMCwgY29tcGFyZT0ibm9ybWFsIikKYGBgCgo=