Preamble

In verification, the statisitical significance of scores and / or the difference between scores for different forecasts gives an indication of the robustness of those results. In harp, it is assumed that the statistical distribution of scores is unknown so bootstrappi is used.

Bootstrapping

Bootstrapping is a process by which statistics are calculated from samples of different draws from the same data - we will call these replicates. The statistics are calculated for each replicate and the variance between the replicates tells us something about how sure we can be about the statistic.

For forecast verification, this means calculated the scores for many replicates. We can then estimate the confidence intervals for the scores and / or the confidence of the differences between scores for different forecast models.

An example

As an example, let’s compare basic summary scores between AAEPS and IFSENS

library(harp)
library(tidyverse)

fcst <- read_point_forecast(
  2018030800, 
  2018033100,
  c("ALERTNESS_ref", "IFSENS_Arctic"),
  "EPS",
  "T2m",
  by            = "1d",
  file_path     = "/home/andrewts/data/alertness/FCTABLE",
  file_template = "fctable_eps"
)

obs <- read_point_obs(
  first_validdate(fcst),
  last_validdate(fcst),
  "T2m",
  obs_path = "/home/andrewts/data/alertness/OBSTABLE",
  stations = pull_stations(fcst)
)

fcst <- common_cases(fcst)

fcst <- join_to_fcst(
  scale_point_forecast(fcst, -273.15, "degC"),
  scale_point_obs(obs, T2m, -273.15, "degC")
)

fcst <- check_obs_against_fcst(fcst, T2m)

verif <- ens_verify(fcst, T2m, verify_members = FALSE)
library(patchwork)
line_colours <- tribble(
  ~mname, ~colour,
  "ALERTNESS_ref", "#CC8888",
  "IFSENS_Arctic", "#8888CC"
)

p1 <- plot_point_verif(
  verif, spread_skill, plot_num_cases = FALSE, plot_caption = "",
  colour_table = line_colours
) +
  guides(linetype = guide_legend(NULL, override.aes = list(colour = "#CCCCCC")))

p2 <- plot_point_verif(
  verif, mean_bias, plot_num_cases = FALSE, plot_caption = "",
  colour_table = line_colours
) 

p3 <- plot_point_verif(
  verif, crps, plot_num_cases = FALSE,
  colour_table = line_colours
) 

p1 + p2 / p3 + plot_layout(guides = "collect") &
  theme_harp_black(base_size = 10) &
  theme(legend.position = "bottom")

Bootstrap the verification with bootstrap_verify()

With the bootstrap_verify() function we can do a straightforward bootstrapping with e.g. 100 replicates.

bs_basic <- bootstrap_verify(fcst, ens_verify, T2m, n = 100, parallel = TRUE)

There isn’t a built in function for plotting data from bootstrap_verify() yet, but this is a good opportunity to learn a little bit about ggplot, which powers the plotting functionality in harp.

First let’s take a look at the data:

bs_basic
$ens_summary_scores

attr(,"parameter")
[1] "T2m"
attr(,"start_date")
[1] "2018030800"
attr(,"end_date")
[1] "2018033100"
attr(,"num_stations")
[1] "192"

The columns are briefly explained here:

  • fcst_model - The model whose score we are interested in
  • ref_model - When comparing models, this model is used as the reference
  • leadtime - The forecast lead time
  • score - the verificatino score
  • ref_score_<stat> - these are the bootstrap statistics for ref_model - the mean, the median, and the upper and lower bounds of the confidence interval that are set in bootstrap_verify() (the default is 95%)
  • fcst_score_<stat> - the same as ref_score<stat>_ except for fcst_model
  • difference_<stat> - The mean, medain and upper and lower confidence bounds for the score for fcst_model - ref_model
  • percent_better - The percentage of fcst_model that have a better score than ref_model. This can be thought of as the confidence of fcst_model - ref_model

So, now we know the data, let’s try making some plots

plot_func <- function(x, plot_score, conf = c("ribbon", "errorbar"), alpha = 0.5) {
  conf <- match.arg(conf)
  p <- ggplot(
    filter(x$ens_summary_scores, score %in% plot_score), 
    aes(
      x        = leadtime, 
      y        = fcst_score_mean, 
      ymin     = fcst_score_lower, 
      ymax     = fcst_score_upper, 
      colour   = fcst_model,
      fill     = fcst_model,
      linetype = score
    )
  ) + 
    geom_line() +
    scale_x_continuous(breaks = seq(0, 48, 6)) +
    labs(
      x = "Lead Time [h]", 
      y = paste(harpVis:::totitle(gsub("_", " ", plot_score)), collapse = "; "),
      colour = NULL, 
      linetype = NULL,
      title = paste0(
        paste(harpVis:::totitle(gsub("_", " ", plot_score)), collapse = "; "), 
        " : ",
        harpVis:::date_to_char(attr(x, "start_date")), 
        " - ",
        harpVis:::date_to_char(attr(x, "end_date"))
      ),
      subtitle = paste(attr(x, "num_stations"), "Stations")
    ) +
    scale_colour_manual(values = c(ALERTNESS_ref = "#CC8888", IFSENS_Arctic = "#8888CC"))
  
  if (length(plot_score) < 2) {
    p <- p + 
      guides(linetype = "none") 
  }
  
  if (conf == "errorbar") {
    p <- p + geom_errorbar()
  }
  
  if (conf == "ribbon") {
    p <- p + geom_ribbon(alpha = alpha) + guides(fill = "none")
  }
  
  p + theme(legend.position = "bottom")
}

p1 <- plot_func(bs_basic, c("spread", "rmse"), "errorbar") +
  guides(linetype = guide_legend(NULL, override.aes = list(colour = "#CCCCCC")))

p2 <- plot_func(bs_basic, "mean_bias", "errorbar")

p3 <- plot_func(bs_basic, "crps", "errorbar")

p1 + p2 / p3 + plot_layout(guides = "collect") &
  theme_harp_black(base_size = 9) &
  theme(legend.position = "bottom")

p1 <- plot_func(bs_basic, c("spread", "rmse"), "ribbon") +
  guides(linetype = guide_legend(NULL, override.aes = list(colour = "#CCCCCC")))

p2 <- plot_func(bs_basic, "mean_bias", "ribbon")

p3 <- plot_func(bs_basic, "crps", "ribbon")

p1 + p2 / p3 + plot_layout(guides = "collect") &
  theme_harp_black(base_size = 9) &
  theme(legend.position = "bottom")

Or we can plot the difference between the two showing both the confidence interval of the difference and whether that difference is significant or not at the 95% level.

diff_plot <- function(
  x, plot_score, fcst_model, ref_model, significance = 0.95, 
  conf = c("ribbon", "errorbar"), alpha = 0.5
) {
  
  conf <- match.arg(conf)

  sig_text <- c(
    paste0(
      fcst_model, " better than ", ref_model, 
      " with ", significance * 100, "% confidence"
    ),
    paste0(
      fcst_model, " worse than ", ref_model,
      " with ", significance * 100, "% confidence"
    ),
    paste0(
      "No difference between ", fcst_model, " and ", ref_model, 
      " with ", significance * 100, "% confidence"
    )
  )
  
  shapes <- c(24, 25, 1)
  names(shapes) <- sig_text
  
  colours <- c("#8888CC", "#CC8888", "#ACACAC")
  names(colours) <- sig_text
  
  plot_data <- filter(
    x$ens_summary_scores,
    score %in% plot_score,
    fcst_model == .env$fcst_model,
    ref_model == .env$ref_model
  ) %>% 
    mutate(
      sig = case_when(
        percent_better >= significance       ~ sig_text[1],
        percent_better <= (1 - significance) ~ sig_text[2],
        TRUE                                 ~ sig_text[3]
      )
    )
  
    p <- ggplot(
    plot_data, 
    aes(
      x        = leadtime, 
      y        = difference_mean, 
      ymin     = difference_lower, 
      ymax     = difference_upper, 
      linetype = score
    )
  ) + 
    labs(
      x = "Lead Time [h]", 
      y = paste(harpVis:::totitle(gsub("_", " ", plot_score)), collapse = "; "),
      colour = NULL, 
      linetype = NULL,
      shape = NULL,
      title = paste0(
        paste(harpVis:::totitle(gsub("_", " ", plot_score)), collapse = "; "), 
        " : ",
        harpVis:::date_to_char(attr(x, "start_date")), 
        " - ",
        harpVis:::date_to_char(attr(x, "end_date"))
      ),
      subtitle = paste(
        fcst_model, "-", ref_model, "at",
        attr(x, "num_stations"), "Stations"
      )
    ) 
    
  if (length(plot_score) < 2) {
    p <- p + 
      guides(linetype = "none") 
  }
  
  if (conf == "errorbar") {
    p <- p + geom_errorbar(colour = "#CC8888")
  }
  
  if (conf == "ribbon") {
    p <- p + geom_ribbon(alpha = alpha, fill = "#CC8888")
  }

  p +
    geom_line(colour = "#CC8888") +
    geom_point(aes(shape = sig, colour = sig, fill = sig)) +
    scale_colour_manual(values = colours) +
    scale_fill_manual(values = colours) +
    scale_shape_manual(values = shapes) +
    scale_x_continuous(breaks = seq(0, 48, 6)) +
    guides(
      colour = guide_legend(nrow = 3),
      fill = guide_legend(NULL, nrow = 3),
      shape = guide_legend(nrow = 3)
    ) +
    theme(legend.position = "bottom")
}

fm <- "ALERTNESS_ref"
ref <- "IFSENS_Arctic"

p1 <- diff_plot(bs_basic, c("spread", "rmse"), fm, ref, 0.95, "ribbon") +
  guides(linetype = guide_legend(NULL, override.aes = list(colour = "#CCCCCC"))) 

p2 <- diff_plot(bs_basic, "mean_bias", fm, ref, 0.95, "ribbon") +
  guides(colour = "none", fill = "none", shape = "none")

p3 <- diff_plot(bs_basic, "crps", fm, ref, 0.95, "ribbon") +
  guides(colour = "none", fill = "none", shape = "none")

p1 + p2 / p3 + plot_layout(guides = "collect") &
  theme_harp_black(base_size = 9) &
  theme(legend.position = "bottom")

Serial correlations

In bootstrapping a problem arises if the data are in some way serially correlated - whether that be in time or space. In order to get a representative statistic for each bootstrap replicate we need to ensure that those serial correlations are maintained for each replicate. However, how much we need to do that is something of a judgement call.

Spatial autocorrelations

The computation and interpretation of spatial autocorrelations is quite complicated, so it won’t be covered here (but e.g. see moran.test from the spdep package). However, we can ignore the impact of those spatial autocorrelations altogether by making sure that every station is represented in each bootstrap replicate. This can be done by pooling the data so that the block bootstrap can be used whereby blocks of data are drawn with replacement from the dataset. To ensure that all stations are included in each bootstrap replicate we can pool the data by forecast date such that each block contains all stations. This is done with the pool_by argument in bootstrap_verify() where unique values in the column named in pool_by are used to group the data together.

bs_block <- bootstrap_verify(
  fcst, ens_verify, T2m, 100, pool_by = "fcdate", parallel = TRUE
)
Bootstrapping on 8 cores.

We can then see how this compares with the conclusions that might have been drawn from our previous analysis

p1 <- diff_plot(bs_basic, c("spread", "rmse"), "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(linetype = guide_legend(NULL, override.aes = list(colour = "#CCCCCC")))
p2 <- diff_plot(bs_block, c("spread", "rmse"), "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none", linetype = "none")
p3 <- diff_plot(bs_basic, "mean_bias", "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none")
p4 <- diff_plot(bs_block, "mean_bias", "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none")
p5 <- diff_plot(bs_basic, "crps", "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none") +
  labs(caption = "Basic bootstrap")
p6 <- diff_plot(bs_block, "crps", "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none") +
  labs(caption = "Block bootstrap pooled by fcdate")

(p1 + p2) / (p3 + p4) / (p5 + p6) + plot_layout(guides = "collect") &
  theme_harp_black(base_size = 9) &
  theme(legend.position = "bottom", plot.caption = element_text(hjust = 0, size = 9))

It is clear that with the block bootstrap (right-hand column) that there is actually more variability in the difference between scores and some of the symbols that indicated significance with 95% confidence now do not.

Temporal autocorrelations

Let’s look at how the errors are correlated in time. We can do this by grouping the verification by lead time and forecast date and then calculating the autocorrelations as a function of the forecast date.

verif_time <- ens_verify(
  fcst, T2m, groupings = c("leadtime", "fcdate"), verify_members = FALSE
)

R’s acf() function returns a list, but we just want to get the lag and the autocorrelation values from it so we make a function to extract those variables into a data frame.

acf_to_df <- function(x) {
  ac <- acf(x, plot = FALSE)
  data.frame(lag = as.vector(ac$lag), autocorrelation = as.vector(ac$acf))
}

We can then use dplyr’s summarise() function to compute the autocorrelations for each lead time and forecast model. We need to make sure that the data are arranged in order of fcdate to have the correct lags.

autocorr <- group_by(verif_time$ens_summary_scores, mname, leadtime) %>% 
  arrange(fcdate) %>% 
  summarise(ac = list(acf_to_df(mean_bias))) %>% 
  unnest(ac)

And then we can plot the autocorrelations

ggplot(autocorr, aes(lag, autocorrelation, fill = mname)) +
  geom_col(position = "dodge") +
  facet_wrap(vars(
    paste0("LT = ", fct_inorder(formatC(leadtime, width = 2, flag = "0")), "h")
  )) +
  scale_fill_manual(values = c(ALERTNESS_ref = "#CC8888", IFSENS_Arctic = "#8888CC")) +
  theme_harp_black() +
  labs(x = "Lag (days)", y = "Autocorrelation", fill = NULL) +
  theme(
    strip.text = element_text(colour = "#CCCCCC"), 
    legend.position = "bottom"
  )

So, it looks like serial correlations exist for up to 4 days. We can use the information to create pools of 4 consecutive days for the block bootstrap. This is a two stage process - first we need to define the pools using make_bootstrap_pools(). We can then pass the output of this to the pool_by argument in bootstrap_verify()

bs_pools_4d <- make_bootstrap_pools(fcst, fcdate, "4d")
bs_block_4d <- bootstrap_verify(
  fcst, ens_verify, T2m, 100, pool_by = bs_pools_4d, parallel = TRUE
)
Bootstrapping on 8 cores.

Now we can compare the results with those from pooling the data by fcdate only

p1 <- diff_plot(bs_block, c("spread", "rmse"), "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(linetype = guide_legend(NULL, override.aes = list(colour = "#CCCCCC")))
p2 <- diff_plot(bs_block_4d, c("spread", "rmse"), "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none", linetype = "none")
p3 <- diff_plot(bs_block, "mean_bias", "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none")
p4 <- diff_plot(bs_block_4d, "mean_bias", "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none")
p5 <- diff_plot(bs_block, "crps", "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none") +
  labs(caption = "Block bootstrap pooled by fcdate")
p6 <- diff_plot(bs_block_4d, "crps", "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none") +
  labs(caption = "Block bootstrap pooled by 4-day fcdate pools")

(p1 + p2) / (p3 + p4) / (p5 + p6) + plot_layout(guides = "collect") &
  theme_harp_black(base_size = 9) &
  theme(legend.position = "bottom", plot.caption = element_text(hjust = 0, size = 9))

This does not have such a large impact, but there are still some symbols that change. However, discrete pools were defined. We can also define overlapping pools using overlap = TRUE in make_bootstrap_pools()

bs_pools_4d_ol <- make_bootstrap_pools(fcst, fcdate, "4d", overlap = TRUE)
bs_block_4d_ol <- bootstrap_verify(
  fcst, ens_verify, T2m, 100, pool_by = bs_pools_4d, parallel = TRUE
)
Bootstrapping on 8 cores.

… and compare the results

p1 <- diff_plot(bs_block_4d, c("spread", "rmse"), "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(linetype = guide_legend(NULL, override.aes = list(colour = "#CCCCCC")))
p2 <- diff_plot(bs_block_4d_ol, c("spread", "rmse"), "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none", linetype = "none")
p3 <- diff_plot(bs_block_4d, "mean_bias", "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none")
p4 <- diff_plot(bs_block_4d_ol, "mean_bias", "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none")
p5 <- diff_plot(bs_block_4d, "crps", "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none") +
  labs(caption = "Block bootstrap pooled by 4-day fcdate pools")
p6 <- diff_plot(bs_block_4d_ol, "crps", "ALERTNESS_ref", "IFSENS_Arctic") +
  guides(colour = "none", fill = "none", shape = "none") +
  labs(caption = "Block bootstrap pooled by 4-day overlapping fcdate pools")

(p1 + p2) / (p3 + p4) / (p5 + p6) + plot_layout(guides = "collect") &
  theme_harp_black(base_size = 9) &
  theme(legend.position = "bottom", plot.caption = element_text(hjust = 0, size = 9))

Once again we see a small increase in the uncertainty, and a couple of changes in symbol.

LS0tCnRpdGxlOiAiU2lnbmZpY2FuY2UgdGVzdGluZyBpbiBoYXJwIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIyBQcmVhbWJsZQoKSW4gdmVyaWZpY2F0aW9uLCB0aGUgc3RhdGlzaXRpY2FsIHNpZ25pZmljYW5jZSBvZiBzY29yZXMgYW5kIC8gb3IgdGhlIGRpZmZlcmVuY2UgYmV0d2VlbiBzY29yZXMgZm9yIGRpZmZlcmVudCBmb3JlY2FzdHMgZ2l2ZXMgYW4gaW5kaWNhdGlvbiBvZiB0aGUgcm9idXN0bmVzcyBvZiB0aG9zZSByZXN1bHRzLiBJbiBoYXJwLCBpdCBpcyBhc3N1bWVkIHRoYXQgdGhlIHN0YXRpc3RpY2FsIGRpc3RyaWJ1dGlvbiBvZiBzY29yZXMgaXMgdW5rbm93biBzbyBib290c3RyYXBwaSBpcyB1c2VkLiAKCiMjIEJvb3RzdHJhcHBpbmcKQm9vdHN0cmFwcGluZyBpcyBhIHByb2Nlc3MgYnkgd2hpY2ggc3RhdGlzdGljcyBhcmUgY2FsY3VsYXRlZCBmcm9tIHNhbXBsZXMgb2YgZGlmZmVyZW50IGRyYXdzIGZyb20gdGhlIHNhbWUgZGF0YSAtIHdlIHdpbGwgY2FsbCB0aGVzZSByZXBsaWNhdGVzLiBUaGUgc3RhdGlzdGljcyBhcmUgY2FsY3VsYXRlZCBmb3IgZWFjaCByZXBsaWNhdGUgYW5kIHRoZSB2YXJpYW5jZSBiZXR3ZWVuIHRoZSByZXBsaWNhdGVzIHRlbGxzIHVzIHNvbWV0aGluZyBhYm91dCBob3cgc3VyZSB3ZSBjYW4gYmUgYWJvdXQgdGhlIHN0YXRpc3RpYy4gCgpGb3IgZm9yZWNhc3QgdmVyaWZpY2F0aW9uLCB0aGlzIG1lYW5zIGNhbGN1bGF0ZWQgdGhlIHNjb3JlcyBmb3IgbWFueSByZXBsaWNhdGVzLiBXZSBjYW4gdGhlbiBlc3RpbWF0ZSB0aGUgY29uZmlkZW5jZSBpbnRlcnZhbHMgZm9yIHRoZSBzY29yZXMgYW5kIC8gb3IgdGhlIGNvbmZpZGVuY2Ugb2YgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gc2NvcmVzIGZvciBkaWZmZXJlbnQgZm9yZWNhc3QgbW9kZWxzLgoKIyMgQW4gZXhhbXBsZQpBcyBhbiBleGFtcGxlLCBsZXQncyBjb21wYXJlIGJhc2ljIHN1bW1hcnkgc2NvcmVzIGJldHdlZW4gQUFFUFMgYW5kIElGU0VOUwpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShoYXJwKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCmZjc3QgPC0gcmVhZF9wb2ludF9mb3JlY2FzdCgKICAyMDE4MDMwODAwLCAKICAyMDE4MDMzMTAwLAogIGMoIkFMRVJUTkVTU19yZWYiLCAiSUZTRU5TX0FyY3RpYyIpLAogICJFUFMiLAogICJUMm0iLAogIGJ5ICAgICAgICAgICAgPSAiMWQiLAogIGZpbGVfcGF0aCAgICAgPSAiL2hvbWUvYW5kcmV3dHMvZGF0YS9hbGVydG5lc3MvRkNUQUJMRSIsCiAgZmlsZV90ZW1wbGF0ZSA9ICJmY3RhYmxlX2VwcyIKKQoKb2JzIDwtIHJlYWRfcG9pbnRfb2JzKAogIGZpcnN0X3ZhbGlkZGF0ZShmY3N0KSwKICBsYXN0X3ZhbGlkZGF0ZShmY3N0KSwKICAiVDJtIiwKICBvYnNfcGF0aCA9ICIvaG9tZS9hbmRyZXd0cy9kYXRhL2FsZXJ0bmVzcy9PQlNUQUJMRSIsCiAgc3RhdGlvbnMgPSBwdWxsX3N0YXRpb25zKGZjc3QpCikKCmZjc3QgPC0gY29tbW9uX2Nhc2VzKGZjc3QpCgpmY3N0IDwtIGpvaW5fdG9fZmNzdCgKICBzY2FsZV9wb2ludF9mb3JlY2FzdChmY3N0LCAtMjczLjE1LCAiZGVnQyIpLAogIHNjYWxlX3BvaW50X29icyhvYnMsIFQybSwgLTI3My4xNSwgImRlZ0MiKQopCgpmY3N0IDwtIGNoZWNrX29ic19hZ2FpbnN0X2Zjc3QoZmNzdCwgVDJtKQoKdmVyaWYgPC0gZW5zX3ZlcmlmeShmY3N0LCBUMm0sIHZlcmlmeV9tZW1iZXJzID0gRkFMU0UpCmBgYAoKCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuaGVpZ2h0PTggLGZpZy53aWR0aD05fQpsaWJyYXJ5KHBhdGNod29yaykKbGluZV9jb2xvdXJzIDwtIHRyaWJibGUoCiAgfm1uYW1lLCB+Y29sb3VyLAogICJBTEVSVE5FU1NfcmVmIiwgIiNDQzg4ODgiLAogICJJRlNFTlNfQXJjdGljIiwgIiM4ODg4Q0MiCikKCnAxIDwtIHBsb3RfcG9pbnRfdmVyaWYoCiAgdmVyaWYsIHNwcmVhZF9za2lsbCwgcGxvdF9udW1fY2FzZXMgPSBGQUxTRSwgcGxvdF9jYXB0aW9uID0gIiIsCiAgY29sb3VyX3RhYmxlID0gbGluZV9jb2xvdXJzCikgKwogIGd1aWRlcyhsaW5ldHlwZSA9IGd1aWRlX2xlZ2VuZChOVUxMLCBvdmVycmlkZS5hZXMgPSBsaXN0KGNvbG91ciA9ICIjQ0NDQ0NDIikpKQoKcDIgPC0gcGxvdF9wb2ludF92ZXJpZigKICB2ZXJpZiwgbWVhbl9iaWFzLCBwbG90X251bV9jYXNlcyA9IEZBTFNFLCBwbG90X2NhcHRpb24gPSAiIiwKICBjb2xvdXJfdGFibGUgPSBsaW5lX2NvbG91cnMKKSAKCnAzIDwtIHBsb3RfcG9pbnRfdmVyaWYoCiAgdmVyaWYsIGNycHMsIHBsb3RfbnVtX2Nhc2VzID0gRkFMU0UsCiAgY29sb3VyX3RhYmxlID0gbGluZV9jb2xvdXJzCikgCgpwMSArIHAyIC8gcDMgKyBwbG90X2xheW91dChndWlkZXMgPSAiY29sbGVjdCIpICYKICB0aGVtZV9oYXJwX2JsYWNrKGJhc2Vfc2l6ZSA9IDEwKSAmCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCmBgYAojIyMgQm9vdHN0cmFwIHRoZSB2ZXJpZmljYXRpb24gd2l0aCBib290c3RyYXBfdmVyaWZ5KCkgCldpdGggdGhlIGBib290c3RyYXBfdmVyaWZ5KClgIGZ1bmN0aW9uIHdlIGNhbiBkbyBhIHN0cmFpZ2h0Zm9yd2FyZCBib290c3RyYXBwaW5nIHdpdGggZS5nLiAxMDAgcmVwbGljYXRlcy4gCmBgYHtyfQpic19iYXNpYyA8LSBib290c3RyYXBfdmVyaWZ5KGZjc3QsIGVuc192ZXJpZnksIFQybSwgbiA9IDEwMCwgcGFyYWxsZWwgPSBUUlVFKQpgYGAKClRoZXJlIGlzbid0IGEgYnVpbHQgaW4gZnVuY3Rpb24gZm9yIHBsb3R0aW5nIGRhdGEgZnJvbSBgYm9vdHN0cmFwX3ZlcmlmeSgpYCB5ZXQsIGJ1dCB0aGlzIGlzIGEgZ29vZCBvcHBvcnR1bml0eSB0byBsZWFybiBhIGxpdHRsZSBiaXQgYWJvdXQgX2dncGxvdF8sIHdoaWNoIHBvd2VycyB0aGUgcGxvdHRpbmcgZnVuY3Rpb25hbGl0eSBpbiBoYXJwLiAKCkZpcnN0IGxldCdzIHRha2UgYSBsb29rIGF0IHRoZSBkYXRhOgpgYGB7cn0KYnNfYmFzaWMKYGBgCgpUaGUgY29sdW1ucyBhcmUgYnJpZWZseSBleHBsYWluZWQgaGVyZToKCiAgLSBfX2Zjc3RfbW9kZWxfXyAtIFRoZSBtb2RlbCB3aG9zZSBzY29yZSB3ZSBhcmUgaW50ZXJlc3RlZCBpbgogIC0gX19yZWZfbW9kZWxfXyAtIFdoZW4gY29tcGFyaW5nIG1vZGVscywgdGhpcyBtb2RlbCBpcyB1c2VkIGFzIHRoZSByZWZlcmVuY2UKICAtIF9fbGVhZHRpbWVfXyAtIFRoZSBmb3JlY2FzdCBsZWFkIHRpbWUKICAtIF9fc2NvcmVfXyAtIHRoZSB2ZXJpZmljYXRpbm8gc2NvcmUKICAtIF9fcmVmX3Njb3JlX1w8c3RhdFw+X18gLSB0aGVzZSBhcmUgdGhlIGJvb3RzdHJhcCBzdGF0aXN0aWNzIGZvciBfcmVmX21vZGVsXyAtIHRoZSBtZWFuLCB0aGUgbWVkaWFuLCBhbmQgdGhlIHVwcGVyIGFuZCBsb3dlciBib3VuZHMgb2YgdGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgdGhhdCBhcmUgc2V0IGluIGBib290c3RyYXBfdmVyaWZ5KClgICh0aGUgZGVmYXVsdCBpcyA5NSUpIAogIC0gX19mY3N0X3Njb3JlX1w8c3RhdFw+X18gLSB0aGUgc2FtZSBhcyBfcmVmX3Njb3JlX1w8c3RhdFw+XyBleGNlcHQgZm9yIF9mY3N0X21vZGVsXwogIC0gX19kaWZmZXJlbmNlX1w8c3RhdFw+X18gLSBUaGUgbWVhbiwgbWVkYWluIGFuZCB1cHBlciBhbmQgbG93ZXIgY29uZmlkZW5jZSBib3VuZHMgZm9yIHRoZSBzY29yZSBmb3IgX2Zjc3RfbW9kZWxfIC0gX3JlZl9tb2RlbF8KICAtIF9fcGVyY2VudF9iZXR0ZXJfXyAtIFRoZSBwZXJjZW50YWdlIG9mIF9mY3N0X21vZGVsXyB0aGF0IGhhdmUgYSBiZXR0ZXIgc2NvcmUgdGhhbiBfcmVmX21vZGVsXy4gVGhpcyBjYW4gYmUgdGhvdWdodCBvZiBhcyB0aGUgY29uZmlkZW5jZSBvZiBfZmNzdF9tb2RlbF8gLSBfcmVmX21vZGVsXwoKU28sIG5vdyB3ZSBrbm93IHRoZSBkYXRhLCBsZXQncyB0cnkgbWFraW5nIHNvbWUgcGxvdHMKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UsIGZpZy5oZWlnaHQ9OCAsZmlnLndpZHRoPTl9CnBsb3RfZnVuYyA8LSBmdW5jdGlvbih4LCBwbG90X3Njb3JlLCBjb25mID0gYygicmliYm9uIiwgImVycm9yYmFyIiksIGFscGhhID0gMC41KSB7CiAgY29uZiA8LSBtYXRjaC5hcmcoY29uZikKICBwIDwtIGdncGxvdCgKICAgIGZpbHRlcih4JGVuc19zdW1tYXJ5X3Njb3Jlcywgc2NvcmUgJWluJSBwbG90X3Njb3JlKSwgCiAgICBhZXMoCiAgICAgIHggICAgICAgID0gbGVhZHRpbWUsIAogICAgICB5ICAgICAgICA9IGZjc3Rfc2NvcmVfbWVhbiwgCiAgICAgIHltaW4gICAgID0gZmNzdF9zY29yZV9sb3dlciwgCiAgICAgIHltYXggICAgID0gZmNzdF9zY29yZV91cHBlciwgCiAgICAgIGNvbG91ciAgID0gZmNzdF9tb2RlbCwKICAgICAgZmlsbCAgICAgPSBmY3N0X21vZGVsLAogICAgICBsaW5ldHlwZSA9IHNjb3JlCiAgICApCiAgKSArIAogICAgZ2VvbV9saW5lKCkgKwogICAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgwLCA0OCwgNikpICsKICAgIGxhYnMoCiAgICAgIHggPSAiTGVhZCBUaW1lIFtoXSIsIAogICAgICB5ID0gcGFzdGUoaGFycFZpczo6OnRvdGl0bGUoZ3N1YigiXyIsICIgIiwgcGxvdF9zY29yZSkpLCBjb2xsYXBzZSA9ICI7ICIpLAogICAgICBjb2xvdXIgPSBOVUxMLCAKICAgICAgbGluZXR5cGUgPSBOVUxMLAogICAgICB0aXRsZSA9IHBhc3RlMCgKICAgICAgICBwYXN0ZShoYXJwVmlzOjo6dG90aXRsZShnc3ViKCJfIiwgIiAiLCBwbG90X3Njb3JlKSksIGNvbGxhcHNlID0gIjsgIiksIAogICAgICAgICIgOiAiLAogICAgICAgIGhhcnBWaXM6OjpkYXRlX3RvX2NoYXIoYXR0cih4LCAic3RhcnRfZGF0ZSIpKSwgCiAgICAgICAgIiAtICIsCiAgICAgICAgaGFycFZpczo6OmRhdGVfdG9fY2hhcihhdHRyKHgsICJlbmRfZGF0ZSIpKQogICAgICApLAogICAgICBzdWJ0aXRsZSA9IHBhc3RlKGF0dHIoeCwgIm51bV9zdGF0aW9ucyIpLCAiU3RhdGlvbnMiKQogICAgKSArCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcyA9IGMoQUxFUlRORVNTX3JlZiA9ICIjQ0M4ODg4IiwgSUZTRU5TX0FyY3RpYyA9ICIjODg4OENDIikpCiAgCiAgaWYgKGxlbmd0aChwbG90X3Njb3JlKSA8IDIpIHsKICAgIHAgPC0gcCArIAogICAgICBndWlkZXMobGluZXR5cGUgPSAibm9uZSIpIAogIH0KICAKICBpZiAoY29uZiA9PSAiZXJyb3JiYXIiKSB7CiAgICBwIDwtIHAgKyBnZW9tX2Vycm9yYmFyKCkKICB9CiAgCiAgaWYgKGNvbmYgPT0gInJpYmJvbiIpIHsKICAgIHAgPC0gcCArIGdlb21fcmliYm9uKGFscGhhID0gYWxwaGEpICsgZ3VpZGVzKGZpbGwgPSAibm9uZSIpCiAgfQogIAogIHAgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKfQoKcDEgPC0gcGxvdF9mdW5jKGJzX2Jhc2ljLCBjKCJzcHJlYWQiLCAicm1zZSIpLCAiZXJyb3JiYXIiKSArCiAgZ3VpZGVzKGxpbmV0eXBlID0gZ3VpZGVfbGVnZW5kKE5VTEwsIG92ZXJyaWRlLmFlcyA9IGxpc3QoY29sb3VyID0gIiNDQ0NDQ0MiKSkpCgpwMiA8LSBwbG90X2Z1bmMoYnNfYmFzaWMsICJtZWFuX2JpYXMiLCAiZXJyb3JiYXIiKQoKcDMgPC0gcGxvdF9mdW5jKGJzX2Jhc2ljLCAiY3JwcyIsICJlcnJvcmJhciIpCgpwMSArIHAyIC8gcDMgKyBwbG90X2xheW91dChndWlkZXMgPSAiY29sbGVjdCIpICYKICB0aGVtZV9oYXJwX2JsYWNrKGJhc2Vfc2l6ZSA9IDkpICYKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKYGBgCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD04ICxmaWcud2lkdGg9OX0KcDEgPC0gcGxvdF9mdW5jKGJzX2Jhc2ljLCBjKCJzcHJlYWQiLCAicm1zZSIpLCAicmliYm9uIikgKwogIGd1aWRlcyhsaW5ldHlwZSA9IGd1aWRlX2xlZ2VuZChOVUxMLCBvdmVycmlkZS5hZXMgPSBsaXN0KGNvbG91ciA9ICIjQ0NDQ0NDIikpKQoKcDIgPC0gcGxvdF9mdW5jKGJzX2Jhc2ljLCAibWVhbl9iaWFzIiwgInJpYmJvbiIpCgpwMyA8LSBwbG90X2Z1bmMoYnNfYmFzaWMsICJjcnBzIiwgInJpYmJvbiIpCgpwMSArIHAyIC8gcDMgKyBwbG90X2xheW91dChndWlkZXMgPSAiY29sbGVjdCIpICYKICB0aGVtZV9oYXJwX2JsYWNrKGJhc2Vfc2l6ZSA9IDkpICYKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikKYGBgCgpPciB3ZSBjYW4gcGxvdCB0aGUgZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSB0d28gc2hvd2luZyBib3RoIHRoZSBjb25maWRlbmNlIGludGVydmFsIG9mIHRoZSBkaWZmZXJlbmNlIGFuZCB3aGV0aGVyIHRoYXQgZGlmZmVyZW5jZSBpcyBzaWduaWZpY2FudCBvciBub3QgYXQgdGhlIDk1JSBsZXZlbC4gCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmhlaWdodD04ICxmaWcud2lkdGg9OX0KZGlmZl9wbG90IDwtIGZ1bmN0aW9uKAogIHgsIHBsb3Rfc2NvcmUsIGZjc3RfbW9kZWwsIHJlZl9tb2RlbCwgc2lnbmlmaWNhbmNlID0gMC45NSwgCiAgY29uZiA9IGMoInJpYmJvbiIsICJlcnJvcmJhciIpLCBhbHBoYSA9IDAuNQopIHsKICAKICBjb25mIDwtIG1hdGNoLmFyZyhjb25mKQoKICBzaWdfdGV4dCA8LSBjKAogICAgcGFzdGUwKAogICAgICBmY3N0X21vZGVsLCAiIGJldHRlciB0aGFuICIsIHJlZl9tb2RlbCwgCiAgICAgICIgd2l0aCAiLCBzaWduaWZpY2FuY2UgKiAxMDAsICIlIGNvbmZpZGVuY2UiCiAgICApLAogICAgcGFzdGUwKAogICAgICBmY3N0X21vZGVsLCAiIHdvcnNlIHRoYW4gIiwgcmVmX21vZGVsLAogICAgICAiIHdpdGggIiwgc2lnbmlmaWNhbmNlICogMTAwLCAiJSBjb25maWRlbmNlIgogICAgKSwKICAgIHBhc3RlMCgKICAgICAgIk5vIGRpZmZlcmVuY2UgYmV0d2VlbiAiLCBmY3N0X21vZGVsLCAiIGFuZCAiLCByZWZfbW9kZWwsIAogICAgICAiIHdpdGggIiwgc2lnbmlmaWNhbmNlICogMTAwLCAiJSBjb25maWRlbmNlIgogICAgKQogICkKICAKICBzaGFwZXMgPC0gYygyNCwgMjUsIDEpCiAgbmFtZXMoc2hhcGVzKSA8LSBzaWdfdGV4dAogIAogIGNvbG91cnMgPC0gYygiIzg4ODhDQyIsICIjQ0M4ODg4IiwgIiNBQ0FDQUMiKQogIG5hbWVzKGNvbG91cnMpIDwtIHNpZ190ZXh0CiAgCiAgcGxvdF9kYXRhIDwtIGZpbHRlcigKICAgIHgkZW5zX3N1bW1hcnlfc2NvcmVzLAogICAgc2NvcmUgJWluJSBwbG90X3Njb3JlLAogICAgZmNzdF9tb2RlbCA9PSAuZW52JGZjc3RfbW9kZWwsCiAgICByZWZfbW9kZWwgPT0gLmVudiRyZWZfbW9kZWwKICApICU+JSAKICAgIG11dGF0ZSgKICAgICAgc2lnID0gY2FzZV93aGVuKAogICAgICAgIHBlcmNlbnRfYmV0dGVyID49IHNpZ25pZmljYW5jZSAgICAgICB+IHNpZ190ZXh0WzFdLAogICAgICAgIHBlcmNlbnRfYmV0dGVyIDw9ICgxIC0gc2lnbmlmaWNhbmNlKSB+IHNpZ190ZXh0WzJdLAogICAgICAgIFRSVUUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB+IHNpZ190ZXh0WzNdCiAgICAgICkKICAgICkKICAKICAgIHAgPC0gZ2dwbG90KAogICAgcGxvdF9kYXRhLCAKICAgIGFlcygKICAgICAgeCAgICAgICAgPSBsZWFkdGltZSwgCiAgICAgIHkgICAgICAgID0gZGlmZmVyZW5jZV9tZWFuLCAKICAgICAgeW1pbiAgICAgPSBkaWZmZXJlbmNlX2xvd2VyLCAKICAgICAgeW1heCAgICAgPSBkaWZmZXJlbmNlX3VwcGVyLCAKICAgICAgbGluZXR5cGUgPSBzY29yZQogICAgKQogICkgKyAKICAgIGxhYnMoCiAgICAgIHggPSAiTGVhZCBUaW1lIFtoXSIsIAogICAgICB5ID0gcGFzdGUoaGFycFZpczo6OnRvdGl0bGUoZ3N1YigiXyIsICIgIiwgcGxvdF9zY29yZSkpLCBjb2xsYXBzZSA9ICI7ICIpLAogICAgICBjb2xvdXIgPSBOVUxMLCAKICAgICAgbGluZXR5cGUgPSBOVUxMLAogICAgICBzaGFwZSA9IE5VTEwsCiAgICAgIHRpdGxlID0gcGFzdGUwKAogICAgICAgIHBhc3RlKGhhcnBWaXM6Ojp0b3RpdGxlKGdzdWIoIl8iLCAiICIsIHBsb3Rfc2NvcmUpKSwgY29sbGFwc2UgPSAiOyAiKSwgCiAgICAgICAgIiA6ICIsCiAgICAgICAgaGFycFZpczo6OmRhdGVfdG9fY2hhcihhdHRyKHgsICJzdGFydF9kYXRlIikpLCAKICAgICAgICAiIC0gIiwKICAgICAgICBoYXJwVmlzOjo6ZGF0ZV90b19jaGFyKGF0dHIoeCwgImVuZF9kYXRlIikpCiAgICAgICksCiAgICAgIHN1YnRpdGxlID0gcGFzdGUoCiAgICAgICAgZmNzdF9tb2RlbCwgIi0iLCByZWZfbW9kZWwsICJhdCIsCiAgICAgICAgYXR0cih4LCAibnVtX3N0YXRpb25zIiksICJTdGF0aW9ucyIKICAgICAgKQogICAgKSAKICAgIAogIGlmIChsZW5ndGgocGxvdF9zY29yZSkgPCAyKSB7CiAgICBwIDwtIHAgKyAKICAgICAgZ3VpZGVzKGxpbmV0eXBlID0gIm5vbmUiKSAKICB9CiAgCiAgaWYgKGNvbmYgPT0gImVycm9yYmFyIikgewogICAgcCA8LSBwICsgZ2VvbV9lcnJvcmJhcihjb2xvdXIgPSAiI0NDODg4OCIpCiAgfQogIAogIGlmIChjb25mID09ICJyaWJib24iKSB7CiAgICBwIDwtIHAgKyBnZW9tX3JpYmJvbihhbHBoYSA9IGFscGhhLCBmaWxsID0gIiNDQzg4ODgiKQogIH0KCiAgcCArCiAgICBnZW9tX2xpbmUoY29sb3VyID0gIiNDQzg4ODgiKSArCiAgICBnZW9tX3BvaW50KGFlcyhzaGFwZSA9IHNpZywgY29sb3VyID0gc2lnLCBmaWxsID0gc2lnKSkgKwogICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBjb2xvdXJzKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvdXJzKSArCiAgICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzID0gc2hhcGVzKSArCiAgICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDQ4LCA2KSkgKwogICAgZ3VpZGVzKAogICAgICBjb2xvdXIgPSBndWlkZV9sZWdlbmQobnJvdyA9IDMpLAogICAgICBmaWxsID0gZ3VpZGVfbGVnZW5kKE5VTEwsIG5yb3cgPSAzKSwKICAgICAgc2hhcGUgPSBndWlkZV9sZWdlbmQobnJvdyA9IDMpCiAgICApICsKICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQp9CgpmbSA8LSAiQUxFUlRORVNTX3JlZiIKcmVmIDwtICJJRlNFTlNfQXJjdGljIgoKcDEgPC0gZGlmZl9wbG90KGJzX2Jhc2ljLCBjKCJzcHJlYWQiLCAicm1zZSIpLCBmbSwgcmVmLCAwLjk1LCAicmliYm9uIikgKwogIGd1aWRlcyhsaW5ldHlwZSA9IGd1aWRlX2xlZ2VuZChOVUxMLCBvdmVycmlkZS5hZXMgPSBsaXN0KGNvbG91ciA9ICIjQ0NDQ0NDIikpKSAKCnAyIDwtIGRpZmZfcGxvdChic19iYXNpYywgIm1lYW5fYmlhcyIsIGZtLCByZWYsIDAuOTUsICJyaWJib24iKSArCiAgZ3VpZGVzKGNvbG91ciA9ICJub25lIiwgZmlsbCA9ICJub25lIiwgc2hhcGUgPSAibm9uZSIpCgpwMyA8LSBkaWZmX3Bsb3QoYnNfYmFzaWMsICJjcnBzIiwgZm0sIHJlZiwgMC45NSwgInJpYmJvbiIpICsKICBndWlkZXMoY29sb3VyID0gIm5vbmUiLCBmaWxsID0gIm5vbmUiLCBzaGFwZSA9ICJub25lIikKCnAxICsgcDIgLyBwMyArIHBsb3RfbGF5b3V0KGd1aWRlcyA9ICJjb2xsZWN0IikgJgogIHRoZW1lX2hhcnBfYmxhY2soYmFzZV9zaXplID0gOSkgJgogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iKQpgYGAKCiMjIFNlcmlhbCBjb3JyZWxhdGlvbnMKCkluIGJvb3RzdHJhcHBpbmcgYSBwcm9ibGVtIGFyaXNlcyBpZiB0aGUgZGF0YSBhcmUgaW4gc29tZSB3YXkgc2VyaWFsbHkgY29ycmVsYXRlZCAtIHdoZXRoZXIgdGhhdCBiZSBpbiB0aW1lIG9yIHNwYWNlLiBJbiBvcmRlciB0byBnZXQgYSByZXByZXNlbnRhdGl2ZSBzdGF0aXN0aWMgZm9yIGVhY2ggYm9vdHN0cmFwIHJlcGxpY2F0ZSB3ZSBuZWVkIHRvIGVuc3VyZSB0aGF0IHRob3NlIHNlcmlhbCBjb3JyZWxhdGlvbnMgYXJlIG1haW50YWluZWQgZm9yIGVhY2ggcmVwbGljYXRlLiBIb3dldmVyLCBob3cgbXVjaCB3ZSBuZWVkIHRvIGRvIHRoYXQgaXMgc29tZXRoaW5nIG9mIGEganVkZ2VtZW50IGNhbGwuIAoKIyMjIFNwYXRpYWwgYXV0b2NvcnJlbGF0aW9ucwpUaGUgY29tcHV0YXRpb24gYW5kIGludGVycHJldGF0aW9uIG9mIHNwYXRpYWwgYXV0b2NvcnJlbGF0aW9ucyBpcyBxdWl0ZSBjb21wbGljYXRlZCwgc28gaXQgd29uJ3QgYmUgY292ZXJlZCBoZXJlIChidXQgZS5nLiBzZWUgW21vcmFuLnRlc3QgZnJvbSB0aGUgc3BkZXAgcGFja2FnZV0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL3NwZGVwL3ZlcnNpb25zLzEuMS03L3RvcGljcy9tb3Jhbi50ZXN0KSkuIEhvd2V2ZXIsIHdlIGNhbiBpZ25vcmUgdGhlIGltcGFjdCBvZiB0aG9zZSBzcGF0aWFsIGF1dG9jb3JyZWxhdGlvbnMgYWx0b2dldGhlciBieSBtYWtpbmcgc3VyZSB0aGF0IGV2ZXJ5IHN0YXRpb24gaXMgcmVwcmVzZW50ZWQgaW4gZWFjaCBib290c3RyYXAgcmVwbGljYXRlLiBUaGlzIGNhbiBiZSBkb25lIGJ5IHBvb2xpbmcgdGhlIGRhdGEgc28gdGhhdCB0aGUgYmxvY2sgYm9vdHN0cmFwIGNhbiBiZSB1c2VkIHdoZXJlYnkgYmxvY2tzIG9mIGRhdGEgYXJlIGRyYXduIHdpdGggcmVwbGFjZW1lbnQgZnJvbSB0aGUgZGF0YXNldC4gVG8gZW5zdXJlIHRoYXQgYWxsIHN0YXRpb25zIGFyZSBpbmNsdWRlZCBpbiBlYWNoIGJvb3RzdHJhcCByZXBsaWNhdGUgd2UgY2FuIHBvb2wgdGhlIGRhdGEgYnkgZm9yZWNhc3QgZGF0ZSBzdWNoIHRoYXQgZWFjaCBibG9jayBjb250YWlucyBhbGwgc3RhdGlvbnMuIFRoaXMgaXMgZG9uZSB3aXRoIHRoZSBgcG9vbF9ieWAgYXJndW1lbnQgaW4gYGJvb3RzdHJhcF92ZXJpZnkoKWAgd2hlcmUgdW5pcXVlIHZhbHVlcyBpbiB0aGUgY29sdW1uIG5hbWVkIGluIGBwb29sX2J5YCBhcmUgdXNlZCB0byBncm91cCB0aGUgZGF0YSB0b2dldGhlci4KCmBgYHtyfQpic19ibG9jayA8LSBib290c3RyYXBfdmVyaWZ5KAogIGZjc3QsIGVuc192ZXJpZnksIFQybSwgMTAwLCBwb29sX2J5ID0gImZjZGF0ZSIsIHBhcmFsbGVsID0gVFJVRQopCmBgYAoKV2UgY2FuIHRoZW4gc2VlIGhvdyB0aGlzIGNvbXBhcmVzIHdpdGggdGhlIGNvbmNsdXNpb25zIHRoYXQgbWlnaHQgaGF2ZSBiZWVuIGRyYXduIGZyb20gb3VyIHByZXZpb3VzIGFuYWx5c2lzCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD0xMCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KcDEgPC0gZGlmZl9wbG90KGJzX2Jhc2ljLCBjKCJzcHJlYWQiLCAicm1zZSIpLCAiQUxFUlRORVNTX3JlZiIsICJJRlNFTlNfQXJjdGljIikgKwogIGd1aWRlcyhsaW5ldHlwZSA9IGd1aWRlX2xlZ2VuZChOVUxMLCBvdmVycmlkZS5hZXMgPSBsaXN0KGNvbG91ciA9ICIjQ0NDQ0NDIikpKQpwMiA8LSBkaWZmX3Bsb3QoYnNfYmxvY2ssIGMoInNwcmVhZCIsICJybXNlIiksICJBTEVSVE5FU1NfcmVmIiwgIklGU0VOU19BcmN0aWMiKSArCiAgZ3VpZGVzKGNvbG91ciA9ICJub25lIiwgZmlsbCA9ICJub25lIiwgc2hhcGUgPSAibm9uZSIsIGxpbmV0eXBlID0gIm5vbmUiKQpwMyA8LSBkaWZmX3Bsb3QoYnNfYmFzaWMsICJtZWFuX2JpYXMiLCAiQUxFUlRORVNTX3JlZiIsICJJRlNFTlNfQXJjdGljIikgKwogIGd1aWRlcyhjb2xvdXIgPSAibm9uZSIsIGZpbGwgPSAibm9uZSIsIHNoYXBlID0gIm5vbmUiKQpwNCA8LSBkaWZmX3Bsb3QoYnNfYmxvY2ssICJtZWFuX2JpYXMiLCAiQUxFUlRORVNTX3JlZiIsICJJRlNFTlNfQXJjdGljIikgKwogIGd1aWRlcyhjb2xvdXIgPSAibm9uZSIsIGZpbGwgPSAibm9uZSIsIHNoYXBlID0gIm5vbmUiKQpwNSA8LSBkaWZmX3Bsb3QoYnNfYmFzaWMsICJjcnBzIiwgIkFMRVJUTkVTU19yZWYiLCAiSUZTRU5TX0FyY3RpYyIpICsKICBndWlkZXMoY29sb3VyID0gIm5vbmUiLCBmaWxsID0gIm5vbmUiLCBzaGFwZSA9ICJub25lIikgKwogIGxhYnMoY2FwdGlvbiA9ICJCYXNpYyBib290c3RyYXAiKQpwNiA8LSBkaWZmX3Bsb3QoYnNfYmxvY2ssICJjcnBzIiwgIkFMRVJUTkVTU19yZWYiLCAiSUZTRU5TX0FyY3RpYyIpICsKICBndWlkZXMoY29sb3VyID0gIm5vbmUiLCBmaWxsID0gIm5vbmUiLCBzaGFwZSA9ICJub25lIikgKwogIGxhYnMoY2FwdGlvbiA9ICJCbG9jayBib290c3RyYXAgcG9vbGVkIGJ5IGZjZGF0ZSIpCgoocDEgKyBwMikgLyAocDMgKyBwNCkgLyAocDUgKyBwNikgKyBwbG90X2xheW91dChndWlkZXMgPSAiY29sbGVjdCIpICYKICB0aGVtZV9oYXJwX2JsYWNrKGJhc2Vfc2l6ZSA9IDkpICYKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIiwgcGxvdC5jYXB0aW9uID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMCwgc2l6ZSA9IDkpKQpgYGAKCkl0IGlzIGNsZWFyIHRoYXQgd2l0aCB0aGUgYmxvY2sgYm9vdHN0cmFwIChyaWdodC1oYW5kIGNvbHVtbikgdGhhdCB0aGVyZSBpcyBhY3R1YWxseSBtb3JlIHZhcmlhYmlsaXR5IGluIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gc2NvcmVzIGFuZCBzb21lIG9mIHRoZSBzeW1ib2xzIHRoYXQgaW5kaWNhdGVkIHNpZ25pZmljYW5jZSB3aXRoIDk1JSBjb25maWRlbmNlIG5vdyBkbyBub3QuCgojIyMgVGVtcG9yYWwgYXV0b2NvcnJlbGF0aW9ucwpMZXQncyBsb29rIGF0IGhvdyB0aGUgZXJyb3JzIGFyZSBjb3JyZWxhdGVkIGluIHRpbWUuIFdlIGNhbiBkbyB0aGlzIGJ5IGdyb3VwaW5nIHRoZSB2ZXJpZmljYXRpb24gYnkgbGVhZCB0aW1lIGFuZCBmb3JlY2FzdCBkYXRlIGFuZCB0aGVuIGNhbGN1bGF0aW5nIHRoZSBhdXRvY29ycmVsYXRpb25zIGFzIGEgZnVuY3Rpb24gb2YgdGhlIGZvcmVjYXN0IGRhdGUuIAoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnZlcmlmX3RpbWUgPC0gZW5zX3ZlcmlmeSgKICBmY3N0LCBUMm0sIGdyb3VwaW5ncyA9IGMoImxlYWR0aW1lIiwgImZjZGF0ZSIpLCB2ZXJpZnlfbWVtYmVycyA9IEZBTFNFCikKYGBgCgpSJ3MgYGFjZigpYCBmdW5jdGlvbiByZXR1cm5zIGEgbGlzdCwgYnV0IHdlIGp1c3Qgd2FudCB0byBnZXQgdGhlIGxhZyBhbmQgdGhlIGF1dG9jb3JyZWxhdGlvbiB2YWx1ZXMgZnJvbSBpdCBzbyB3ZSBtYWtlIGEgZnVuY3Rpb24gdG8gZXh0cmFjdCB0aG9zZSB2YXJpYWJsZXMgaW50byBhIGRhdGEgZnJhbWUuCmBgYHtyfQphY2ZfdG9fZGYgPC0gZnVuY3Rpb24oeCkgewogIGFjIDwtIGFjZih4LCBwbG90ID0gRkFMU0UpCiAgZGF0YS5mcmFtZShsYWcgPSBhcy52ZWN0b3IoYWMkbGFnKSwgYXV0b2NvcnJlbGF0aW9uID0gYXMudmVjdG9yKGFjJGFjZikpCn0KYGBgCgpXZSBjYW4gdGhlbiB1c2UgZHBseXIncyBgc3VtbWFyaXNlKClgIGZ1bmN0aW9uIHRvIGNvbXB1dGUgdGhlIGF1dG9jb3JyZWxhdGlvbnMgZm9yIGVhY2ggbGVhZCB0aW1lIGFuZCBmb3JlY2FzdCBtb2RlbC4gV2UgbmVlZCB0byBtYWtlIHN1cmUgdGhhdCB0aGUgZGF0YSBhcmUgYXJyYW5nZWQgaW4gb3JkZXIgb2YgZmNkYXRlIHRvIGhhdmUgdGhlIGNvcnJlY3QgbGFncy4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQphdXRvY29yciA8LSBncm91cF9ieSh2ZXJpZl90aW1lJGVuc19zdW1tYXJ5X3Njb3JlcywgbW5hbWUsIGxlYWR0aW1lKSAlPiUgCiAgYXJyYW5nZShmY2RhdGUpICU+JSAKICBzdW1tYXJpc2UoYWMgPSBsaXN0KGFjZl90b19kZihtZWFuX2JpYXMpKSkgJT4lIAogIHVubmVzdChhYykKYGBgCgpBbmQgdGhlbiB3ZSBjYW4gcGxvdCB0aGUgYXV0b2NvcnJlbGF0aW9ucwpgYGB7ciwgZmlnLmhlaWdodD05LCBmaWcud2lkdGg9OCwgZmlnLmFsaWduPSJjZW50ZXIifQpnZ3Bsb3QoYXV0b2NvcnIsIGFlcyhsYWcsIGF1dG9jb3JyZWxhdGlvbiwgZmlsbCA9IG1uYW1lKSkgKwogIGdlb21fY29sKHBvc2l0aW9uID0gImRvZGdlIikgKwogIGZhY2V0X3dyYXAodmFycygKICAgIHBhc3RlMCgiTFQgPSAiLCBmY3RfaW5vcmRlcihmb3JtYXRDKGxlYWR0aW1lLCB3aWR0aCA9IDIsIGZsYWcgPSAiMCIpKSwgImgiKQogICkpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjKEFMRVJUTkVTU19yZWYgPSAiI0NDODg4OCIsIElGU0VOU19BcmN0aWMgPSAiIzg4ODhDQyIpKSArCiAgdGhlbWVfaGFycF9ibGFjaygpICsKICBsYWJzKHggPSAiTGFnIChkYXlzKSIsIHkgPSAiQXV0b2NvcnJlbGF0aW9uIiwgZmlsbCA9IE5VTEwpICsKICB0aGVtZSgKICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3VyID0gIiNDQ0NDQ0MiKSwgCiAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIgogICkKYGBgCgpTbywgaXQgbG9va3MgbGlrZSBzZXJpYWwgY29ycmVsYXRpb25zIGV4aXN0IGZvciB1cCB0byA0IGRheXMuIFdlIGNhbiB1c2UgdGhlIGluZm9ybWF0aW9uIHRvIGNyZWF0ZSBwb29scyBvZiA0IGNvbnNlY3V0aXZlIGRheXMgZm9yIHRoZSBibG9jayBib290c3RyYXAuIFRoaXMgaXMgYSB0d28gc3RhZ2UgcHJvY2VzcyAtIGZpcnN0IHdlIG5lZWQgdG8gZGVmaW5lIHRoZSBwb29scyB1c2luZyBgbWFrZV9ib290c3RyYXBfcG9vbHMoKWAuIFdlIGNhbiB0aGVuIHBhc3MgdGhlIG91dHB1dCBvZiB0aGlzIHRvIHRoZSBgcG9vbF9ieWAgYXJndW1lbnQgaW4gYGJvb3RzdHJhcF92ZXJpZnkoKWAKCmBgYHtyfQpic19wb29sc180ZCA8LSBtYWtlX2Jvb3RzdHJhcF9wb29scyhmY3N0LCBmY2RhdGUsICI0ZCIpCmJzX2Jsb2NrXzRkIDwtIGJvb3RzdHJhcF92ZXJpZnkoCiAgZmNzdCwgZW5zX3ZlcmlmeSwgVDJtLCAxMDAsIHBvb2xfYnkgPSBic19wb29sc180ZCwgcGFyYWxsZWwgPSBUUlVFCikKYGBgCgpOb3cgd2UgY2FuIGNvbXBhcmUgdGhlIHJlc3VsdHMgd2l0aCB0aG9zZSBmcm9tIHBvb2xpbmcgdGhlIGRhdGEgYnkgZmNkYXRlIG9ubHkKYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTEwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwMSA8LSBkaWZmX3Bsb3QoYnNfYmxvY2ssIGMoInNwcmVhZCIsICJybXNlIiksICJBTEVSVE5FU1NfcmVmIiwgIklGU0VOU19BcmN0aWMiKSArCiAgZ3VpZGVzKGxpbmV0eXBlID0gZ3VpZGVfbGVnZW5kKE5VTEwsIG92ZXJyaWRlLmFlcyA9IGxpc3QoY29sb3VyID0gIiNDQ0NDQ0MiKSkpCnAyIDwtIGRpZmZfcGxvdChic19ibG9ja180ZCwgYygic3ByZWFkIiwgInJtc2UiKSwgIkFMRVJUTkVTU19yZWYiLCAiSUZTRU5TX0FyY3RpYyIpICsKICBndWlkZXMoY29sb3VyID0gIm5vbmUiLCBmaWxsID0gIm5vbmUiLCBzaGFwZSA9ICJub25lIiwgbGluZXR5cGUgPSAibm9uZSIpCnAzIDwtIGRpZmZfcGxvdChic19ibG9jaywgIm1lYW5fYmlhcyIsICJBTEVSVE5FU1NfcmVmIiwgIklGU0VOU19BcmN0aWMiKSArCiAgZ3VpZGVzKGNvbG91ciA9ICJub25lIiwgZmlsbCA9ICJub25lIiwgc2hhcGUgPSAibm9uZSIpCnA0IDwtIGRpZmZfcGxvdChic19ibG9ja180ZCwgIm1lYW5fYmlhcyIsICJBTEVSVE5FU1NfcmVmIiwgIklGU0VOU19BcmN0aWMiKSArCiAgZ3VpZGVzKGNvbG91ciA9ICJub25lIiwgZmlsbCA9ICJub25lIiwgc2hhcGUgPSAibm9uZSIpCnA1IDwtIGRpZmZfcGxvdChic19ibG9jaywgImNycHMiLCAiQUxFUlRORVNTX3JlZiIsICJJRlNFTlNfQXJjdGljIikgKwogIGd1aWRlcyhjb2xvdXIgPSAibm9uZSIsIGZpbGwgPSAibm9uZSIsIHNoYXBlID0gIm5vbmUiKSArCiAgbGFicyhjYXB0aW9uID0gIkJsb2NrIGJvb3RzdHJhcCBwb29sZWQgYnkgZmNkYXRlIikKcDYgPC0gZGlmZl9wbG90KGJzX2Jsb2NrXzRkLCAiY3JwcyIsICJBTEVSVE5FU1NfcmVmIiwgIklGU0VOU19BcmN0aWMiKSArCiAgZ3VpZGVzKGNvbG91ciA9ICJub25lIiwgZmlsbCA9ICJub25lIiwgc2hhcGUgPSAibm9uZSIpICsKICBsYWJzKGNhcHRpb24gPSAiQmxvY2sgYm9vdHN0cmFwIHBvb2xlZCBieSA0LWRheSBmY2RhdGUgcG9vbHMiKQoKKHAxICsgcDIpIC8gKHAzICsgcDQpIC8gKHA1ICsgcDYpICsgcGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiKSAmCiAgdGhlbWVfaGFycF9ibGFjayhiYXNlX3NpemUgPSA5KSAmCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAsIHNpemUgPSA5KSkKCmBgYAoKVGhpcyBkb2VzIG5vdCBoYXZlIHN1Y2ggYSBsYXJnZSBpbXBhY3QsIGJ1dCB0aGVyZSBhcmUgc3RpbGwgc29tZSBzeW1ib2xzIHRoYXQgY2hhbmdlLiBIb3dldmVyLCBkaXNjcmV0ZSBwb29scyB3ZXJlIGRlZmluZWQuIFdlIGNhbiBhbHNvIGRlZmluZSBvdmVybGFwcGluZyBwb29scyB1c2luZyBgb3ZlcmxhcCA9IFRSVUVgIGluIGBtYWtlX2Jvb3RzdHJhcF9wb29scygpYAoKYGBge3J9CmJzX3Bvb2xzXzRkX29sIDwtIG1ha2VfYm9vdHN0cmFwX3Bvb2xzKGZjc3QsIGZjZGF0ZSwgIjRkIiwgb3ZlcmxhcCA9IFRSVUUpCmJzX2Jsb2NrXzRkX29sIDwtIGJvb3RzdHJhcF92ZXJpZnkoCiAgZmNzdCwgZW5zX3ZlcmlmeSwgVDJtLCAxMDAsIHBvb2xfYnkgPSBic19wb29sc180ZCwgcGFyYWxsZWwgPSBUUlVFCikKYGBgCgouLi4gYW5kIGNvbXBhcmUgdGhlIHJlc3VsdHMKYGBge3IsIGZpZy53aWR0aD05LCBmaWcuaGVpZ2h0PTEwLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpwMSA8LSBkaWZmX3Bsb3QoYnNfYmxvY2tfNGQsIGMoInNwcmVhZCIsICJybXNlIiksICJBTEVSVE5FU1NfcmVmIiwgIklGU0VOU19BcmN0aWMiKSArCiAgZ3VpZGVzKGxpbmV0eXBlID0gZ3VpZGVfbGVnZW5kKE5VTEwsIG92ZXJyaWRlLmFlcyA9IGxpc3QoY29sb3VyID0gIiNDQ0NDQ0MiKSkpCnAyIDwtIGRpZmZfcGxvdChic19ibG9ja180ZF9vbCwgYygic3ByZWFkIiwgInJtc2UiKSwgIkFMRVJUTkVTU19yZWYiLCAiSUZTRU5TX0FyY3RpYyIpICsKICBndWlkZXMoY29sb3VyID0gIm5vbmUiLCBmaWxsID0gIm5vbmUiLCBzaGFwZSA9ICJub25lIiwgbGluZXR5cGUgPSAibm9uZSIpCnAzIDwtIGRpZmZfcGxvdChic19ibG9ja180ZCwgIm1lYW5fYmlhcyIsICJBTEVSVE5FU1NfcmVmIiwgIklGU0VOU19BcmN0aWMiKSArCiAgZ3VpZGVzKGNvbG91ciA9ICJub25lIiwgZmlsbCA9ICJub25lIiwgc2hhcGUgPSAibm9uZSIpCnA0IDwtIGRpZmZfcGxvdChic19ibG9ja180ZF9vbCwgIm1lYW5fYmlhcyIsICJBTEVSVE5FU1NfcmVmIiwgIklGU0VOU19BcmN0aWMiKSArCiAgZ3VpZGVzKGNvbG91ciA9ICJub25lIiwgZmlsbCA9ICJub25lIiwgc2hhcGUgPSAibm9uZSIpCnA1IDwtIGRpZmZfcGxvdChic19ibG9ja180ZCwgImNycHMiLCAiQUxFUlRORVNTX3JlZiIsICJJRlNFTlNfQXJjdGljIikgKwogIGd1aWRlcyhjb2xvdXIgPSAibm9uZSIsIGZpbGwgPSAibm9uZSIsIHNoYXBlID0gIm5vbmUiKSArCiAgbGFicyhjYXB0aW9uID0gIkJsb2NrIGJvb3RzdHJhcCBwb29sZWQgYnkgNC1kYXkgZmNkYXRlIHBvb2xzIikKcDYgPC0gZGlmZl9wbG90KGJzX2Jsb2NrXzRkX29sLCAiY3JwcyIsICJBTEVSVE5FU1NfcmVmIiwgIklGU0VOU19BcmN0aWMiKSArCiAgZ3VpZGVzKGNvbG91ciA9ICJub25lIiwgZmlsbCA9ICJub25lIiwgc2hhcGUgPSAibm9uZSIpICsKICBsYWJzKGNhcHRpb24gPSAiQmxvY2sgYm9vdHN0cmFwIHBvb2xlZCBieSA0LWRheSBvdmVybGFwcGluZyBmY2RhdGUgcG9vbHMiKQoKKHAxICsgcDIpIC8gKHAzICsgcDQpIC8gKHA1ICsgcDYpICsgcGxvdF9sYXlvdXQoZ3VpZGVzID0gImNvbGxlY3QiKSAmCiAgdGhlbWVfaGFycF9ibGFjayhiYXNlX3NpemUgPSA5KSAmCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAsIHNpemUgPSA5KSkKCmBgYAoKT25jZSBhZ2FpbiB3ZSBzZWUgYSBzbWFsbCBpbmNyZWFzZSBpbiB0aGUgdW5jZXJ0YWludHksIGFuZCBhIGNvdXBsZSBvZiBjaGFuZ2VzIGluIHN5bWJvbC4gCgo=