Last updated: 2022-10-15

Checks: 7 0

Knit directory: myTidyTuesday/

This reproducible R Markdown analysis was created with workflowr (version 1.7.0). The Checks tab describes the reproducibility checks that were applied when the results were created. The Past versions tab lists the development history.


Great! Since the R Markdown file has been committed to the Git repository, you know the exact version of the code that produced these results.

Great job! The global environment was empty. Objects defined in the global environment can affect the analysis in your R Markdown file in unknown ways. For reproduciblity it’s best to always run the code in an empty environment.

The command set.seed(20210907) was run prior to running the code in the R Markdown file. Setting a seed ensures that any results that rely on randomness, e.g. subsampling or permutations, are reproducible.

Great job! Recording the operating system, R version, and package versions is critical for reproducibility.

Nice! There were no cached chunks for this analysis, so you can be confident that you successfully produced the results during this run.

Great job! Using relative paths to the files within your workflowr project makes it easier to run your code on other machines.

Great! You are using Git for version control. Tracking code development and connecting the code version to the results is critical for reproducibility.

The results in this page were generated with repository version fad0136. See the Past versions tab to see a history of the changes made to the R Markdown and HTML files.

Note that you need to be careful to ensure that all relevant files for the analysis have been committed to Git prior to generating the results (you can use wflow_publish or wflow_git_commit). workflowr only checks the R Markdown file, but you know if there are other scripts or data files that it depends on. Below is the status of the Git repository when the results were generated:


Ignored files:
    Ignored:    .Rhistory
    Ignored:    .Rproj.user/
    Ignored:    data/.Rhistory
    Ignored:    data/CNHI_Excel_Chart.xlsx
    Ignored:    data/Chicago.rds
    Ignored:    data/CommunityTreemap.jpeg
    Ignored:    data/Community_Roles.jpeg
    Ignored:    data/SeriesReport-20220414171148_6c3b18.xlsx
    Ignored:    data/Weekly_Chicago_IL_Regular_Reformulated_Retail_Gasoline_Prices.csv
    Ignored:    data/YammerDigitalDataScienceMembership.xlsx
    Ignored:    data/YammerMemberPage.rds
    Ignored:    data/YammerMembers.rds
    Ignored:    data/df.rds
    Ignored:    data/grainstocks.rds
    Ignored:    data/hike_data.rds
    Ignored:    data/lm_res.rds
    Ignored:    data/netflixTitles.rds
    Ignored:    data/netflixTitles2.rds
    Ignored:    data/raw_weather.RData
    Ignored:    data/sample_submission.csv
    Ignored:    data/submission.csv
    Ignored:    data/test.csv
    Ignored:    data/train.csv
    Ignored:    data/us_states.rds
    Ignored:    data/us_states_hexgrid.geojson
    Ignored:    data/weatherstats_toronto_daily.csv
    Ignored:    data/xgb_res.rds

Untracked files:
    Untracked:  analysis/2022_09_01_kaggle_tabular_playground.qmd
    Untracked:  code/YammerReach.R
    Untracked:  code/autokeras.R
    Untracked:  code/chicago.R
    Untracked:  code/glmnet_test.R
    Untracked:  code/googleCompute.R
    Untracked:  code/work list batch targets.R
    Untracked:  environment.yml
    Untracked:  figure/
    Untracked:  report.html

Unstaged changes:
    Modified:   analysis/2021_01_19_tidy_tuesday.Rmd
    Modified:   analysis/2021_03_24_tidy_tuesday.Rmd
    Deleted:    analysis/2021_04_20.Rmd
    Deleted:    analysis/2022_02_11_tabular_playground.Rmd
    Deleted:    analysis/2022_04_18.qmd
    Modified:   analysis/Survival.Rmd
    Modified:   analysis/_site.yml
    Modified:   code/_common.R

Note that any generated files, e.g. HTML, png, CSS, etc., are not included in this status report because it is ok for generated content to have uncommitted changes.


These are the previous versions of the repository in which changes were made to the R Markdown (analysis/EnglishLanguageLearning.Rmd) and HTML (docs/EnglishLanguageLearning.html) files. If you’ve configured a remote Git repository (see ?wflow_git_remote), click on the hyperlinks in the table below to view the files as they were in that past version.

File Version Author Date Message
html fad0136 opus1993 2022-10-15 Build site.
Rmd 73c07de opus1993 2022-10-15 Kaggle English Language Learning
html 66b10e0 opus1993 2022-10-15 Build site.
Rmd bb8b757 opus1993 2022-10-15 initial commit of Kaggle English Language Learning

The Kaggle Challenge presented here works with a dataset that comprises argumentative essays (the ELLIPSE corpus) written by 8th-12th grade English Language Learners (ELLs). The essays have been scored according to six measures: cohesion, syntax, vocabulary, phraseology, grammar, and conventions.

Each measure represents a component of writing proficiency, ranging from 1.0 to 5.0 in increments of 0.5. Our task is to predict the score of each measure by essay.

Preprocessing

Natural Language Processing techniques offer a wide variety of tools to approach this problem. The Kaggle host is requiring that the model run as a standalone, without internet assistance. They also ask for a parsimonous, explainable model.

We will start with exploring the predictive potential of the text count features, like numbers of words, distinct words, and spaces.

Unsupervised topic grouping categories may be useful for measures like conventions or grammar. We will start with LDA.

Individual words may have some predictive power, but they could be so sparse as to be difficult to separate from the background noise.

A sentiment dictionary may add predictive power to some measures, along with helping to count miss-spellings.

Word embeddings like Glove or Huggingface could also better characterize meaning.

Modeling

Most are tempted to jump into (CNN / LSTM) deep learning predictive models, but the number of essays is really pretty small for a deep learning run.

I spent a few evenings with the torch/brulee approach on tidymodels, but discovered that modeling time consumed would be signifiant and the results were not better than random forests on strong engineered features with case weights based on inverse proportions of the metric values.

I ultimately settled on the xgboost approach here. No doubt it massively overfits on specific words and text counts, like the number of unique words.

One last point. I believe that the Essay Scoring is done by humans in a way where the metrics are judged together, and not entirely independently. In other words, low grammar and low cohesion are likely related. I will go as far as I can assuming independence, but at some point a chaining or calibration run to pull all metrics together may be appropriate.

suppressPackageStartupMessages({
library(tidyverse)
library(tidymodels)

library(stm)
library(text2vec)

library(tidytext)
library(textrecipes)

})

tidymodels::tidymodels_prefer()

theme_set(theme_minimal())
train_essays_raw <- read_csv(here::here("data","train.csv"),
                         show_col_types = FALSE) 

submit_essays_raw <- read_csv(here::here("data","test.csv"),
                          show_col_types = FALSE) 

outcomes = names(train_essays_raw)[3:8]

This is a function to look at nrc sentiments and a way to break out mis-spelled words by subtracting dictionary words from total unique tokens.

sentiment_preprocessor <- function(data = submit_essays_raw){

data %>%
  tidytext::unnest_tokens(word, full_text) |> 
  inner_join(get_sentiments("nrc"), by = "word") %>% # pull out only sentiment words
  count(sentiment, text_id) %>% # count the # of positive & negative words
  spread(sentiment, n, fill = 0) %>% # made data wide rather than narrow
 mutate(sentiment = positive - negative,
       dictionary_words = positive + negative) %>% # # of positive words - # of negative words
    select(anger:dictionary_words)
  
}

sentiments <- paste(names(sentiment_preprocessor()),
                             collapse =  " + ")

sentiment_preprocessor(data = train_essays_raw) |> 
  pivot_longer(cols = everything(),
               names_to = "metric",
               values_to = "Sentiment word counts") |> 
  ggplot(aes(`Sentiment word counts`, fill = metric)) +
  geom_histogram(bins = 35, show.legend = FALSE) +
  facet_wrap(vars(metric)) +
  labs(y = "Number of Essays",
       title = "Most essays contain few words of anger and disgust")

train_essays_sentiment <- train_essays_raw |> 
         bind_cols(sentiment_preprocessor(train_essays_raw))

submit_essays_sentiment <- submit_essays_raw |> 
    bind_cols(sentiment_preprocessor(submit_essays_raw))

Essays with more words, or more sentences, do not necessarily score better.

te_long <- train_essays_raw |>
  pivot_longer(cols = cohesion:conventions,
               names_to = "metric",
               values_to = "value") |>
  mutate(metric = as.factor(metric),
         value = as.factor(value))

te_long |> 
  group_by(n_words = ggplot2::cut_interval(
    tokenizers::count_words(full_text), 
    length = 200),
    metric, value) |> 
  summarise(`Number of essays` = n(),
            .groups = "drop") |> 
  ggplot(aes(n_words, `Number of essays`, fill = as.factor(value))) +
  geom_col() +
  scale_x_discrete(guide = guide_axis(n.dodge = 2)) +
  facet_wrap(vars(metric)) +
  labs(x = "Number of words per essay",
       y = "Number of essays",
       fill = "Score")

te_long |> 
  group_by(n_words = ggplot2::cut_interval(
    tokenizers::count_sentences(full_text), length = 20),
    metric, value) |> 
  summarise(`Number of essays` = n(),
            .groups = "drop") |> 
  ggplot(aes(n_words, `Number of essays`, fill = as.factor(value))) +
  geom_col() +
  scale_x_discrete(guide = guide_axis(n.dodge = 2)) +
  facet_wrap(vars(metric)) +
  labs(x = "Number of sentences per essay",
       y = "Number of essays",
       fill = "Score")

A look at predictor and outcome pairwise correlations.

train_essays_sentiment |> 
 #   select(!!outcomes) %>%
  corrr::correlate(
    quiet = TRUE
  ) %>%
  corrr::rearrange() %>%
  corrr::shave() %>%
  corrr::rplot(print_cor = TRUE) +
  scale_x_discrete(guide = guide_axis(n.dodge = 2))

train_essays_sentiment |> 
 #   select(!!outcomes) %>%
  corrr::correlate(
    quiet = TRUE
  ) %>%
  corrr::network_plot()

Let’s set some initial hyperparameters.

# train dataset has 21953 unique one n_gram tokens. 
topics <- 90L   # LDA topic models

Latent Dirichlet allocation (LDA) is an unsupervised generative statistical model that explains a set of observations through unobserved groups, and the content of each group may explain why some parts of the data are similar.

I’d like to explore the use of inverse probability weights because there are so few essays with scores at the highest and lowest levels. When survey respondents have different probabilities of selection, (inverse) probability weights help reduce bias in the results.

I am making us of metaprogramming techniques to pass text vector column names into the formula and case weights functions to re-use them for each metric.

case_weight_builder <- function(data, outcome) {
  data %>%
    inner_join(
      data %>%
        count(.data[[outcome]],
              name = "case_wts"),
      by = glue::glue("{ outcome }")
    ) %>%
    mutate(case_wts = importance_weights(max(case_wts) / case_wts))
}


recipe_builder <- function(outcome = "cohesion",
                           predictor = "full_text",
                           sentiments = "dictionary_words"){

rec <- recipe(formula(glue::glue("{ outcome } ~ { predictor } + { sentiments } + case_wts")),
              data = train_essays_sentiment %>%
                   case_weight_builder(outcome) 
              ) %>%
  step_textfeature(full_text,
                   keep_original_cols = TRUE) %>%
  step_rename_at(
    starts_with("textfeature_"),
    fn = ~ gsub("textfeature_full_text_", "", .)
  ) %>%
  step_mutate(nonwords = n_uq_words - dictionary_words ) %>%
#### cluster the essays by topic, generally
   step_tokenize(full_text) %>% 
   step_lda(full_text, 
            num_topics = topics, 
            keep_original_cols = TRUE) %>%
  step_tfidf(full_text) %>%
  step_clean_names(all_predictors()) %>%
#### remove columns that are super-sparse and unbalanced
  step_nzv(all_predictors(), unique_cut = 9) %>%
  step_normalize(all_numeric_predictors())

return(rec)

}

As mentioned above, the model specification is xgboost for regression to predict a continuous outcome.

# finalize(mtry(), 
#          recipe_builder() |> 
#            prep() |> 
#            bake(new_data = NULL))

spec <-
  boost_tree(
    mtry = 70,  # 75L
    trees = 500L,
    tree_depth = 9, # 6L
    learn_rate = 0.01,  # originally 0.1
    min_n = 20,  # 20L
    loss_reduction = 0
  ) %>%
  set_engine('xgboost') %>%
  set_mode('regression')

# all_cores <- parallelly::availableCores(omit = 1)
# all_cores
# 
# future::plan("multisession", workers = all_cores) # on Windows

We fit for cohesion first and use case weights to adjust for the frequency of occurrence of cohesion. After looking at variable importance (roughly the number of times a variable appears in the trees) and residuals, we make our prediction on the submission set essays and adding that column to the dataframe.

outcome <- outcomes[1]

train_df <- train_essays_sentiment %>%
                   case_weight_builder(outcome)

wf <- workflow(recipe_builder(outcome = outcome, sentiments = sentiments), spec) |> 
       add_case_weights(case_wts)

#
#folds <- vfold_cv(train_df, strata = !!outcome)

#grid <- expand.grid(learn_rate = c(0.006, 0.01, 0.03))

# rs <- tune_grid(
#   wf,
#   folds,
#   grid = grid,
#   metrics = metric_set(rmse),
#   control = control_grid()
# )
#   
# autoplot(rs)
# collect_metrics(rs)

fit <- parsnip::fit(wf, train_df)
as(<dgTMatrix>, "dgCMatrix") is deprecated since Matrix 1.5-0; do as(., "CsparseMatrix") instead
extract_fit_engine(fit) |> 
  vip::vip(num_features = 20)

train_preds <- predict(fit, new_data = train_essays_sentiment) |> 
   bind_cols(train_essays_raw |> select(!!outcome)) |> 
   rename(truth = !!outcome)

train_preds |> 
   ggplot(aes(x = factor(truth), y = .pred - truth)) +
   geom_boxplot() +
   labs(title = glue::glue("{ outcome } residuals")) 

train_preds %>%
   yardstick::rmse(truth, .pred)
submit_essays_sentiment <- predict(fit, submit_essays_sentiment) |> 
  rename({{outcome}} := .pred) |> 
  bind_cols(submit_essays_sentiment)
Warning in get_dtm(corp): dtm has 0 rows. Empty iterator?

We fit for syntax second and use case weights to adjust for the frequency of occurrence of syntax. I am choosing to use the predicted values of cohesion above as an additional predictor.

outcome <- outcomes[2]
predictor <- glue::glue("full_text + { outcomes[1] }")

train_df <- train_essays_sentiment %>%
                   case_weight_builder(outcome)

wf <- workflow(recipe_builder(outcome, predictor, sentiments), spec) %>%
          add_case_weights(case_wts)

fit <- parsnip::fit(wf, train_df)

extract_fit_engine(fit) |> 
  vip::vip(num_features = 20)

train_preds <- predict(fit, new_data = train_essays_sentiment) |> 
   bind_cols(train_essays_raw |> select(!!outcome)) |> 
   rename(truth = !!outcome)

train_preds |> 
   ggplot(aes(x = factor(truth), y = .pred - truth)) +
   geom_boxplot() +
   labs(title = glue::glue("{ outcome } residuals")) 

train_preds %>%
   yardstick::rmse(truth, .pred)
submit_essays_sentiment <- predict(fit, submit_essays_sentiment) |> 
  rename({{outcome}} := .pred) |> 
  bind_cols(submit_essays_sentiment)
Warning in get_dtm(corp): dtm has 0 rows. Empty iterator?

We fit for vocabulary next and use case weights to adjust for the frequency of occurrence of vocabulary. I am choosing to use the predicted values of cohesion and syntax above as additional predictors.

outcome <- outcomes[3]
predictor <- glue::glue("full_text + { outcomes[1] } + { outcomes[2] }")

train_df <- train_essays_sentiment %>%
                   case_weight_builder(outcome)

wf <- workflow(recipe_builder(outcome, predictor, sentiments), spec) %>%
          add_case_weights(case_wts)

fit <- parsnip::fit(wf, train_df)

extract_fit_engine(fit) |> 
  vip::vip(num_features = 20)

train_preds <- predict(fit, new_data = train_essays_sentiment) |> 
   bind_cols(train_essays_raw |> select(!!outcome)) |> 
   rename(truth = !!outcome)

train_preds |> 
   ggplot(aes(x = factor(truth), y = .pred - truth)) +
   geom_boxplot() +
   labs(title = glue::glue("{ outcome } residuals")) 

train_preds %>%
   yardstick::rmse(truth, .pred)
submit_essays_sentiment <- predict(fit, submit_essays_sentiment) |> 
  rename({{outcome}} := .pred) |> 
  bind_cols(submit_essays_sentiment)
Warning in get_dtm(corp): dtm has 0 rows. Empty iterator?

We fit for phraseology next and use case weights to adjust for the frequency of occurrence of phraseology. I am choosing to use the predicted values of cohesion, syntax, and vocabulary above as additional predictors.

outcome <- outcomes[4]
predictor <- glue::glue("full_text + { outcomes[1] } + { outcomes[2] } + { outcomes[3] }")

train_df <- train_essays_sentiment %>%
                   case_weight_builder(outcome)

wf <- workflow(recipe_builder(outcome, predictor, sentiments), spec) %>%
          add_case_weights(case_wts)

fit <- parsnip::fit(wf, train_df)

extract_fit_engine(fit) |> 
  vip::vip(num_features = 20)

train_preds <- predict(fit, new_data = train_essays_sentiment) |> 
   bind_cols(train_essays_raw |> select(!!outcome)) |> 
   rename(truth = !!outcome)

train_preds |> 
   ggplot(aes(x = factor(truth), y = .pred - truth)) +
   geom_boxplot() +
   labs(title = glue::glue("{ outcome } residuals")) 

train_preds %>%
   yardstick::rmse(truth, .pred)
submit_essays_sentiment <- predict(fit, submit_essays_sentiment) |> 
  rename({{outcome}} := .pred) |> 
  bind_cols(submit_essays_sentiment)
Warning in get_dtm(corp): dtm has 0 rows. Empty iterator?

We fit for grammar next and use case weights to adjust for the frequency of occurrence of grammar. I am choosing to use the predicted values of cohesion, syntax, vocabulary, and phraseology above as additional predictors.

outcome <- outcomes[5]
predictor <- glue::glue("full_text + { outcomes[1] } + { outcomes[2]} + {outcomes[3]} + {outcomes[4]}")

train_df <- train_essays_sentiment %>%
                   case_weight_builder(outcome)

wf <- workflow(recipe_builder(outcome, predictor, sentiments), spec) %>%
          add_case_weights(case_wts)

fit <- parsnip::fit(wf, train_df)

extract_fit_engine(fit) |> 
  vip::vip(num_features = 20)

train_preds <- predict(fit, new_data = train_essays_sentiment) |> 
   bind_cols(train_essays_raw |> select(!!outcome)) |> 
   rename(truth = !!outcome)

train_preds |> 
   ggplot(aes(x = factor(truth), y = .pred - truth)) +
   geom_boxplot() +
   labs(title = glue::glue("{ outcome } residuals")) 

train_preds %>%
   yardstick::rmse(truth, .pred)
submit_essays_sentiment <- predict(fit, submit_essays_sentiment) |> 
  rename({{outcome}} := .pred) |> 
  bind_cols(submit_essays_sentiment)
Warning in get_dtm(corp): dtm has 0 rows. Empty iterator?

We fit for conventions next and use case weights to adjust for the frequency of occurrence of conventions. I am choosing to use the predicted values of cohesion, syntax, vocabulary, phraseology and grammar above as additional predictors.

outcome <- outcomes[6]
predictor <- glue::glue("full_text + { outcomes[1] } +{ outcomes[2] }+{ outcomes[3] }+{ outcomes[4] }+{ outcomes[5] }")

train_df <- train_essays_sentiment %>%
                   case_weight_builder(outcome)

wf <- workflow(recipe_builder(outcome, predictor, sentiments), spec) %>%
          add_case_weights(case_wts)

fit <- parsnip::fit(wf, train_df)

extract_fit_engine(fit) |> 
  vip::vip(num_features = 20)

train_preds <- predict(fit, new_data = train_essays_sentiment) |> 
   bind_cols(train_essays_raw |> select(!!outcome)) |> 
   rename(truth = !!outcome)

train_preds |> 
   ggplot(aes(x = factor(truth), y = .pred - truth)) +
   geom_boxplot() +
   labs(title = glue::glue("{ outcome } residuals")) 

train_preds %>%
   yardstick::rmse(truth, .pred)
submit_essays_sentiment <- predict(fit, submit_essays_sentiment) |> 
  rename({{outcome}} := .pred) |> 
  bind_cols(submit_essays_sentiment)
Warning in get_dtm(corp): dtm has 0 rows. Empty iterator?

The Submission

submission <-
  submit_essays_sentiment %>%
  select(text_id, !!outcomes)
submission
write_csv(submission, here::here("data", "submission.csv"))

sessionInfo()
R version 4.2.1 (2022-06-23 ucrt)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 22621)

Matrix products: default

locale:
[1] LC_COLLATE=English_United States.utf8 
[2] LC_CTYPE=English_United States.utf8   
[3] LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.utf8    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] textrecipes_1.0.1  tidytext_0.3.4     text2vec_0.6.2     stm_1.3.6         
 [5] yardstick_1.1.0    workflowsets_1.0.0 workflows_1.1.0    tune_1.0.1        
 [9] rsample_1.1.0      recipes_1.0.1      parsnip_1.0.2      modeldata_1.0.1   
[13] infer_1.0.3        dials_1.0.0        scales_1.2.1       broom_1.0.1       
[17] tidymodels_1.0.0   forcats_0.5.2      stringr_1.4.1      dplyr_1.0.10      
[21] purrr_0.3.5        readr_2.1.3        tidyr_1.2.1        tibble_3.1.8      
[25] ggplot2_3.3.6      tidyverse_1.3.2    workflowr_1.7.0   

loaded via a namespace (and not attached):
  [1] readxl_1.4.1           backports_1.4.1        splines_4.2.1         
  [4] listenv_0.8.0          SnowballC_0.7.0        digest_0.6.29         
  [7] foreach_1.5.2          htmltools_0.5.3        float_0.3-0           
 [10] fansi_1.0.3            magrittr_2.0.3         memoise_2.0.1         
 [13] googlesheets4_1.0.1    tzdb_0.3.0             globals_0.16.1        
 [16] modelr_0.1.9           gower_1.0.0            vroom_1.6.0           
 [19] hardhat_1.2.0          colorspace_2.0-3       vip_0.3.2             
 [22] ggrepel_0.9.1          rappdirs_0.3.3         rvest_1.0.3           
 [25] haven_2.5.1            xfun_0.33              callr_3.7.2           
 [28] crayon_1.5.2           jsonlite_1.8.2         survival_3.3-1        
 [31] iterators_1.0.14       glue_1.6.2             registry_0.5-1        
 [34] gtable_0.3.1           gargle_1.2.1           ipred_0.9-13          
 [37] future.apply_1.9.1     mlapi_0.1.1            DBI_1.1.3             
 [40] Rcpp_1.0.9             GPfit_1.0-8            bit_4.0.4             
 [43] lava_1.6.10            prodlim_2019.11.13     httr_1.4.4            
 [46] ellipsis_0.3.2         farver_2.1.1           pkgconfig_2.0.3       
 [49] nnet_7.3-17            sass_0.4.2             dbplyr_2.2.1          
 [52] janitor_2.1.0          here_1.0.1             utf8_1.2.2            
 [55] labeling_0.4.2         tidyselect_1.2.0       rlang_1.0.6           
 [58] DiceDesign_1.9         later_1.3.0            munsell_0.5.0         
 [61] cellranger_1.1.0       tools_4.2.1            cachem_1.0.6          
 [64] xgboost_1.6.0.1        cli_3.4.0              corrr_0.4.4           
 [67] generics_0.1.3         rsparse_0.5.1          evaluate_0.17         
 [70] fastmap_1.1.0          yaml_2.3.5             textdata_0.4.4        
 [73] processx_3.7.0         RhpcBLASctl_0.21-247.1 knitr_1.40            
 [76] bit64_4.0.5            fs_1.5.2               lgr_0.4.4             
 [79] future_1.28.0          whisker_0.4            textfeatures_0.3.3    
 [82] xml2_1.3.3             tokenizers_0.2.3       compiler_4.2.1        
 [85] rstudioapi_0.14        reprex_2.0.2           lhs_1.1.5             
 [88] bslib_0.4.0            stringi_1.7.8          highr_0.9             
 [91] ps_1.7.1               lattice_0.20-45        Matrix_1.5-1          
 [94] conflicted_1.1.0       vctrs_0.4.2            pillar_1.8.1          
 [97] lifecycle_1.0.3        furrr_0.3.1            jquerylib_0.1.4       
[100] data.table_1.14.2      seriation_1.3.6        httpuv_1.6.6          
[103] R6_2.5.1               TSP_1.2-1              promises_1.2.0.1      
[106] gridExtra_2.3          janeaustenr_1.0.0      parallelly_1.32.1     
[109] codetools_0.2-18       MASS_7.3-57            assertthat_0.2.1      
[112] rprojroot_2.0.3        withr_2.5.0            parallel_4.2.1        
[115] hms_1.1.2              grid_4.2.1             rpart_4.1.16          
[118] timeDate_4021.106      class_7.3-20           snakecase_0.11.0      
[121] rmarkdown_2.17         googledrive_2.0.0      git2r_0.30.1          
[124] getPass_0.2-2          lubridate_1.8.0       
LS0tDQp0aXRsZTogIkthZ2dsZSBGZWVkYmFjayBQcml6ZSAtIEVuZ2xpc2ggTGFuZ3VhZ2UgTGVhcm5pbmciDQphdXRob3I6ICJKaW0gR3J1bWFuIg0KZGF0ZTogIk9jdG9iZXIgMTUsIDIwMjIiDQpvdXRwdXQ6DQogIHdvcmtmbG93cjo6d2Zsb3dfaHRtbDoNCiAgICB0b2M6IG5vDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KZWRpdG9yX29wdGlvbnM6DQogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlDQotLS0NCg0KVGhlIEthZ2dsZSBDaGFsbGVuZ2UgcHJlc2VudGVkIGhlcmUgd29ya3Mgd2l0aCBhIGRhdGFzZXQgdGhhdCBjb21wcmlzZXMgYXJndW1lbnRhdGl2ZSBlc3NheXMgKHRoZSBFTExJUFNFIGNvcnB1cykgd3JpdHRlbiBieSA4dGgtMTJ0aCBncmFkZSBFbmdsaXNoIExhbmd1YWdlIExlYXJuZXJzIChFTExzKS4gVGhlIGVzc2F5cyBoYXZlIGJlZW4gc2NvcmVkIGFjY29yZGluZyB0byBzaXggbWVhc3VyZXM6ICoqY29oZXNpb24sIHN5bnRheCwgdm9jYWJ1bGFyeSwgcGhyYXNlb2xvZ3ksIGdyYW1tYXIsKiogYW5kICoqY29udmVudGlvbnMqKi4NCg0KRWFjaCBtZWFzdXJlIHJlcHJlc2VudHMgYSBjb21wb25lbnQgb2Ygd3JpdGluZyBwcm9maWNpZW5jeSwgcmFuZ2luZyBmcm9tIDEuMCB0byA1LjAgaW4gaW5jcmVtZW50cyBvZiAwLjUuIE91ciB0YXNrIGlzIHRvIHByZWRpY3QgdGhlIHNjb3JlIG9mIGVhY2ggbWVhc3VyZSBieSBlc3NheS4NCg0KIVtdKGh0dHBzOi8vc3RvcmFnZS5nb29nbGVhcGlzLmNvbS9rYWdnbGUtY29tcGV0aXRpb25zL2thZ2dsZS8zODMyMS9sb2dvcy9oZWFkZXIucG5nKQ0KDQoNCiMjIFByZXByb2Nlc3NpbmcgDQoNCk5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZyB0ZWNobmlxdWVzIG9mZmVyIGEgd2lkZSB2YXJpZXR5IG9mIHRvb2xzIHRvIGFwcHJvYWNoIHRoaXMgcHJvYmxlbS4gVGhlIEthZ2dsZSBob3N0IGlzIHJlcXVpcmluZyB0aGF0IHRoZSBtb2RlbCBydW4gYXMgYSBzdGFuZGFsb25lLCB3aXRob3V0IGludGVybmV0IGFzc2lzdGFuY2UuIFRoZXkgYWxzbyBhc2sgZm9yIGEgcGFyc2ltb25vdXMsIGV4cGxhaW5hYmxlIG1vZGVsLg0KDQpXZSB3aWxsIHN0YXJ0IHdpdGggZXhwbG9yaW5nIHRoZSBwcmVkaWN0aXZlIHBvdGVudGlhbCBvZiB0aGUgdGV4dCBjb3VudCBmZWF0dXJlcywgbGlrZSBudW1iZXJzIG9mIHdvcmRzLCBkaXN0aW5jdCB3b3JkcywgYW5kIHNwYWNlcy4NCg0KVW5zdXBlcnZpc2VkIHRvcGljIGdyb3VwaW5nIGNhdGVnb3JpZXMgbWF5IGJlIHVzZWZ1bCBmb3IgbWVhc3VyZXMgbGlrZSBjb252ZW50aW9ucyBvciBncmFtbWFyLiAgV2Ugd2lsbCBzdGFydCB3aXRoIExEQS4NCg0KSW5kaXZpZHVhbCB3b3JkcyBtYXkgaGF2ZSBzb21lIHByZWRpY3RpdmUgcG93ZXIsIGJ1dCB0aGV5IGNvdWxkIGJlIHNvIHNwYXJzZSBhcyB0byBiZSBkaWZmaWN1bHQgdG8gc2VwYXJhdGUgZnJvbSB0aGUgYmFja2dyb3VuZCBub2lzZS4gDQoNCkEgc2VudGltZW50IGRpY3Rpb25hcnkgbWF5IGFkZCBwcmVkaWN0aXZlIHBvd2VyIHRvIHNvbWUgbWVhc3VyZXMsIGFsb25nIHdpdGggaGVscGluZyB0byBjb3VudCBtaXNzLXNwZWxsaW5ncy4NCg0KV29yZCBlbWJlZGRpbmdzIGxpa2UgR2xvdmUgb3IgSHVnZ2luZ2ZhY2UgY291bGQgYWxzbyBiZXR0ZXIgY2hhcmFjdGVyaXplIG1lYW5pbmcuICANCg0KIyMgTW9kZWxpbmcgDQoNCk1vc3QgYXJlIHRlbXB0ZWQgdG8ganVtcCBpbnRvIChDTk4gLyBMU1RNKSBkZWVwIGxlYXJuaW5nIHByZWRpY3RpdmUgbW9kZWxzLCBidXQgdGhlIG51bWJlciBvZiBlc3NheXMgaXMgcmVhbGx5IHByZXR0eSBzbWFsbCBmb3IgYSBkZWVwIGxlYXJuaW5nIHJ1bi4NCg0KSSBzcGVudCBhIGZldyBldmVuaW5ncyB3aXRoIHRoZSB0b3JjaC9gYnJ1bGVlYCBhcHByb2FjaCBvbiBgdGlkeW1vZGVsc2AsIGJ1dCBkaXNjb3ZlcmVkIHRoYXQgbW9kZWxpbmcgdGltZSBjb25zdW1lZCB3b3VsZCBiZSBzaWduaWZpYW50IGFuZCB0aGUgcmVzdWx0cyB3ZXJlIG5vdCBiZXR0ZXIgdGhhbiByYW5kb20gZm9yZXN0cyBvbiBzdHJvbmcgZW5naW5lZXJlZCBmZWF0dXJlcyB3aXRoIGNhc2Ugd2VpZ2h0cyBiYXNlZCBvbiBpbnZlcnNlIHByb3BvcnRpb25zIG9mIHRoZSBtZXRyaWMgdmFsdWVzLg0KDQpJIHVsdGltYXRlbHkgc2V0dGxlZCBvbiB0aGUgYHhnYm9vc3RgIGFwcHJvYWNoIGhlcmUuIE5vIGRvdWJ0IGl0IG1hc3NpdmVseSBvdmVyZml0cyBvbiBzcGVjaWZpYyB3b3JkcyBhbmQgdGV4dCBjb3VudHMsIGxpa2UgdGhlIG51bWJlciBvZiB1bmlxdWUgd29yZHMuIA0KDQpPbmUgbGFzdCBwb2ludC4gSSBiZWxpZXZlIHRoYXQgdGhlIEVzc2F5IFNjb3JpbmcgaXMgZG9uZSBieSBodW1hbnMgaW4gYSB3YXkgd2hlcmUgdGhlIG1ldHJpY3MgYXJlIGp1ZGdlZCB0b2dldGhlciwgYW5kIG5vdCBlbnRpcmVseSBpbmRlcGVuZGVudGx5LiBJbiBvdGhlciB3b3JkcywgbG93IGBncmFtbWFyYCBhbmQgbG93IGBjb2hlc2lvbmAgYXJlIGxpa2VseSByZWxhdGVkLiBJIHdpbGwgZ28gYXMgZmFyIGFzIEkgY2FuIGFzc3VtaW5nIGluZGVwZW5kZW5jZSwgYnV0IGF0IHNvbWUgcG9pbnQgYSBjaGFpbmluZyBvciBjYWxpYnJhdGlvbiBydW4gdG8gcHVsbCBhbGwgbWV0cmljcyB0b2dldGhlciBtYXkgYmUgYXBwcm9wcmlhdGUuDQoNCmBgYHtyfQ0KI3wgbGFiZWw6IHB1bGwgcGFja2FnZXMgaW50byBtZW1vcnkNCg0KDQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoew0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KHRpZHltb2RlbHMpDQoNCmxpYnJhcnkoc3RtKQ0KbGlicmFyeSh0ZXh0MnZlYykNCg0KbGlicmFyeSh0aWR5dGV4dCkNCmxpYnJhcnkodGV4dHJlY2lwZXMpDQoNCn0pDQoNCnRpZHltb2RlbHM6OnRpZHltb2RlbHNfcHJlZmVyKCkNCg0KdGhlbWVfc2V0KHRoZW1lX21pbmltYWwoKSkNCg0KYGBgDQoNCg0KYGBge3J9DQojfCBsYWJlbDogcmVhZCBkYXRhIGZpbGVzLCBhZGQgcHJlDQoNCnRyYWluX2Vzc2F5c19yYXcgPC0gcmVhZF9jc3YoaGVyZTo6aGVyZSgiZGF0YSIsInRyYWluLmNzdiIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfY29sX3R5cGVzID0gRkFMU0UpIA0KDQpzdWJtaXRfZXNzYXlzX3JhdyA8LSByZWFkX2NzdihoZXJlOjpoZXJlKCJkYXRhIiwidGVzdC5jc3YiKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgc2hvd19jb2xfdHlwZXMgPSBGQUxTRSkgDQoNCm91dGNvbWVzID0gbmFtZXModHJhaW5fZXNzYXlzX3JhdylbMzo4XQ0KDQpgYGANCg0KVGhpcyBpcyBhIGZ1bmN0aW9uIHRvIGxvb2sgYXQgYG5yY2Agc2VudGltZW50cyBhbmQgYSB3YXkgdG8gYnJlYWsgb3V0IG1pcy1zcGVsbGVkIHdvcmRzIGJ5IHN1YnRyYWN0aW5nIGRpY3Rpb25hcnkgd29yZHMgZnJvbSB0b3RhbCB1bmlxdWUgdG9rZW5zLg0KDQpgYGB7cn0NCnNlbnRpbWVudF9wcmVwcm9jZXNzb3IgPC0gZnVuY3Rpb24oZGF0YSA9IHN1Ym1pdF9lc3NheXNfcmF3KXsNCg0KZGF0YSAlPiUNCiAgdGlkeXRleHQ6OnVubmVzdF90b2tlbnMod29yZCwgZnVsbF90ZXh0KSB8PiANCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygibnJjIiksIGJ5ID0gIndvcmQiKSAlPiUgIyBwdWxsIG91dCBvbmx5IHNlbnRpbWVudCB3b3Jkcw0KICBjb3VudChzZW50aW1lbnQsIHRleHRfaWQpICU+JSAjIGNvdW50IHRoZSAjIG9mIHBvc2l0aXZlICYgbmVnYXRpdmUgd29yZHMNCiAgc3ByZWFkKHNlbnRpbWVudCwgbiwgZmlsbCA9IDApICU+JSAjIG1hZGUgZGF0YSB3aWRlIHJhdGhlciB0aGFuIG5hcnJvdw0KIG11dGF0ZShzZW50aW1lbnQgPSBwb3NpdGl2ZSAtIG5lZ2F0aXZlLA0KICAgICAgIGRpY3Rpb25hcnlfd29yZHMgPSBwb3NpdGl2ZSArIG5lZ2F0aXZlKSAlPiUgIyAjIG9mIHBvc2l0aXZlIHdvcmRzIC0gIyBvZiBuZWdhdGl2ZSB3b3Jkcw0KICAgIHNlbGVjdChhbmdlcjpkaWN0aW9uYXJ5X3dvcmRzKQ0KICANCn0NCg0Kc2VudGltZW50cyA8LSBwYXN0ZShuYW1lcyhzZW50aW1lbnRfcHJlcHJvY2Vzc29yKCkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xsYXBzZSA9ICAiICsgIikNCg0Kc2VudGltZW50X3ByZXByb2Nlc3NvcihkYXRhID0gdHJhaW5fZXNzYXlzX3JhdykgfD4gDQogIHBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLA0KICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAibWV0cmljIiwNCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJTZW50aW1lbnQgd29yZCBjb3VudHMiKSB8PiANCiAgZ2dwbG90KGFlcyhgU2VudGltZW50IHdvcmQgY291bnRzYCwgZmlsbCA9IG1ldHJpYykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDM1LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGZhY2V0X3dyYXAodmFycyhtZXRyaWMpKSArDQogIGxhYnMoeSA9ICJOdW1iZXIgb2YgRXNzYXlzIiwNCiAgICAgICB0aXRsZSA9ICJNb3N0IGVzc2F5cyBjb250YWluIGZldyB3b3JkcyBvZiBhbmdlciBhbmQgZGlzZ3VzdCIpDQoNCnRyYWluX2Vzc2F5c19zZW50aW1lbnQgPC0gdHJhaW5fZXNzYXlzX3JhdyB8PiANCiAgICAgICAgIGJpbmRfY29scyhzZW50aW1lbnRfcHJlcHJvY2Vzc29yKHRyYWluX2Vzc2F5c19yYXcpKQ0KDQpzdWJtaXRfZXNzYXlzX3NlbnRpbWVudCA8LSBzdWJtaXRfZXNzYXlzX3JhdyB8PiANCiAgICBiaW5kX2NvbHMoc2VudGltZW50X3ByZXByb2Nlc3NvcihzdWJtaXRfZXNzYXlzX3JhdykpDQpgYGANCg0KRXNzYXlzIHdpdGggbW9yZSB3b3Jkcywgb3IgbW9yZSBzZW50ZW5jZXMsIGRvIG5vdCBuZWNlc3NhcmlseSBzY29yZSBiZXR0ZXIuIA0KDQpgYGB7cn0NCiN8IGxhYmVsOiBvdXRjb21lIHZhcmlhYmxlIGRpc3RyaWJ1dGlvbnMNCg0KdGVfbG9uZyA8LSB0cmFpbl9lc3NheXNfcmF3IHw+DQogIHBpdm90X2xvbmdlcihjb2xzID0gY29oZXNpb246Y29udmVudGlvbnMsDQogICAgICAgICAgICAgICBuYW1lc190byA9ICJtZXRyaWMiLA0KICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgfD4NCiAgbXV0YXRlKG1ldHJpYyA9IGFzLmZhY3RvcihtZXRyaWMpLA0KICAgICAgICAgdmFsdWUgPSBhcy5mYWN0b3IodmFsdWUpKQ0KDQp0ZV9sb25nIHw+IA0KICBncm91cF9ieShuX3dvcmRzID0gZ2dwbG90Mjo6Y3V0X2ludGVydmFsKA0KICAgIHRva2VuaXplcnM6OmNvdW50X3dvcmRzKGZ1bGxfdGV4dCksIA0KICAgIGxlbmd0aCA9IDIwMCksDQogICAgbWV0cmljLCB2YWx1ZSkgfD4gDQogIHN1bW1hcmlzZShgTnVtYmVyIG9mIGVzc2F5c2AgPSBuKCksDQogICAgICAgICAgICAuZ3JvdXBzID0gImRyb3AiKSB8PiANCiAgZ2dwbG90KGFlcyhuX3dvcmRzLCBgTnVtYmVyIG9mIGVzc2F5c2AsIGZpbGwgPSBhcy5mYWN0b3IodmFsdWUpKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShndWlkZSA9IGd1aWRlX2F4aXMobi5kb2RnZSA9IDIpKSArDQogIGZhY2V0X3dyYXAodmFycyhtZXRyaWMpKSArDQogIGxhYnMoeCA9ICJOdW1iZXIgb2Ygd29yZHMgcGVyIGVzc2F5IiwNCiAgICAgICB5ID0gIk51bWJlciBvZiBlc3NheXMiLA0KICAgICAgIGZpbGwgPSAiU2NvcmUiKQ0KDQp0ZV9sb25nIHw+IA0KICBncm91cF9ieShuX3dvcmRzID0gZ2dwbG90Mjo6Y3V0X2ludGVydmFsKA0KICAgIHRva2VuaXplcnM6OmNvdW50X3NlbnRlbmNlcyhmdWxsX3RleHQpLCBsZW5ndGggPSAyMCksDQogICAgbWV0cmljLCB2YWx1ZSkgfD4gDQogIHN1bW1hcmlzZShgTnVtYmVyIG9mIGVzc2F5c2AgPSBuKCksDQogICAgICAgICAgICAuZ3JvdXBzID0gImRyb3AiKSB8PiANCiAgZ2dwbG90KGFlcyhuX3dvcmRzLCBgTnVtYmVyIG9mIGVzc2F5c2AsIGZpbGwgPSBhcy5mYWN0b3IodmFsdWUpKSkgKw0KICBnZW9tX2NvbCgpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShndWlkZSA9IGd1aWRlX2F4aXMobi5kb2RnZSA9IDIpKSArDQogIGZhY2V0X3dyYXAodmFycyhtZXRyaWMpKSArDQogIGxhYnMoeCA9ICJOdW1iZXIgb2Ygc2VudGVuY2VzIHBlciBlc3NheSIsDQogICAgICAgeSA9ICJOdW1iZXIgb2YgZXNzYXlzIiwNCiAgICAgICBmaWxsID0gIlNjb3JlIikNCg0KYGBgDQoNCkEgbG9vayBhdCBwcmVkaWN0b3IgYW5kIG91dGNvbWUgcGFpcndpc2UgY29ycmVsYXRpb25zLg0KDQpgYGB7cn0NCg0KdHJhaW5fZXNzYXlzX3NlbnRpbWVudCB8PiANCiAjICAgc2VsZWN0KCEhb3V0Y29tZXMpICU+JQ0KICBjb3Jycjo6Y29ycmVsYXRlKA0KICAgIHF1aWV0ID0gVFJVRQ0KICApICU+JQ0KICBjb3Jycjo6cmVhcnJhbmdlKCkgJT4lDQogIGNvcnJyOjpzaGF2ZSgpICU+JQ0KICBjb3Jycjo6cnBsb3QocHJpbnRfY29yID0gVFJVRSkgKw0KICBzY2FsZV94X2Rpc2NyZXRlKGd1aWRlID0gZ3VpZGVfYXhpcyhuLmRvZGdlID0gMikpDQoNCnRyYWluX2Vzc2F5c19zZW50aW1lbnQgfD4gDQogIyAgIHNlbGVjdCghIW91dGNvbWVzKSAlPiUNCiAgY29ycnI6OmNvcnJlbGF0ZSgNCiAgICBxdWlldCA9IFRSVUUNCiAgKSAlPiUNCiAgY29ycnI6Om5ldHdvcmtfcGxvdCgpDQoNCmBgYA0KDQoqICAgVm9jYWJ1bGFyeSBhbmQgUGhyYXNlb2xvZ3kgKDAuNzQpIHRyYWNrIHRvZ2V0aGVyLg0KKiAgIFBocmFzZW9sb2d5IGFuZCBTeW50YXggKDAuNzMpIHRyYWNrIHRvZ2V0aGVyLg0KKiAgIFByYXNlb2xvZ3kgYW5kIEdyYW1tYXIgKDAuNzIpIHRyYWNrIHRvZ2V0aGVyLg0KDQpMZXQncyBzZXQgc29tZSBpbml0aWFsIGh5cGVycGFyYW1ldGVycy4NCg0KYGBge3J9DQojfCBsYWJlbDogcGFyYW1ldGVycw0KDQojIHRyYWluIGRhdGFzZXQgaGFzIDIxOTUzIHVuaXF1ZSBvbmUgbl9ncmFtIHRva2Vucy4gDQp0b3BpY3MgPC0gOTBMICAgIyBMREEgdG9waWMgbW9kZWxzDQoNCmBgYA0KDQpMYXRlbnQgRGlyaWNobGV0IGFsbG9jYXRpb24gKExEQSkgaXMgYW4gdW5zdXBlcnZpc2VkIGdlbmVyYXRpdmUgc3RhdGlzdGljYWwgbW9kZWwgdGhhdCBleHBsYWlucyBhIHNldCBvZiBvYnNlcnZhdGlvbnMgdGhyb3VnaCB1bm9ic2VydmVkIGdyb3VwcywgYW5kIHRoZSBjb250ZW50IG9mIGVhY2ggZ3JvdXAgbWF5IGV4cGxhaW4gd2h5IHNvbWUgcGFydHMgb2YgdGhlIGRhdGEgYXJlIHNpbWlsYXIuDQoNCkknZCBsaWtlIHRvIGV4cGxvcmUgdGhlIHVzZSBvZiBgaW52ZXJzZSBwcm9iYWJpbGl0eSB3ZWlnaHRzYCBiZWNhdXNlIHRoZXJlIGFyZSBzbyBmZXcgZXNzYXlzIHdpdGggc2NvcmVzIGF0IHRoZSBoaWdoZXN0IGFuZCBsb3dlc3QgbGV2ZWxzLiBXaGVuIHN1cnZleSByZXNwb25kZW50cyBoYXZlIGRpZmZlcmVudCBwcm9iYWJpbGl0aWVzIG9mIHNlbGVjdGlvbiwgKGludmVyc2UpIHByb2JhYmlsaXR5IHdlaWdodHMgaGVscCByZWR1Y2UgYmlhcyBpbiB0aGUgcmVzdWx0cy4NCg0KSSBhbSBtYWtpbmcgdXMgb2YgbWV0YXByb2dyYW1taW5nIHRlY2huaXF1ZXMgdG8gcGFzcyB0ZXh0IHZlY3RvciBjb2x1bW4gbmFtZXMgaW50byB0aGUgZm9ybXVsYSBhbmQgY2FzZSB3ZWlnaHRzIGZ1bmN0aW9ucyB0byByZS11c2UgdGhlbSBmb3IgZWFjaCBtZXRyaWMuDQoNCmBgYHtyfQ0KI3wgbGFiZWw6IHByZXByb2Nlc3NvcnMNCg0KY2FzZV93ZWlnaHRfYnVpbGRlciA8LSBmdW5jdGlvbihkYXRhLCBvdXRjb21lKSB7DQogIGRhdGEgJT4lDQogICAgaW5uZXJfam9pbigNCiAgICAgIGRhdGEgJT4lDQogICAgICAgIGNvdW50KC5kYXRhW1tvdXRjb21lXV0sDQogICAgICAgICAgICAgIG5hbWUgPSAiY2FzZV93dHMiKSwNCiAgICAgIGJ5ID0gZ2x1ZTo6Z2x1ZSgieyBvdXRjb21lIH0iKQ0KICAgICkgJT4lDQogICAgbXV0YXRlKGNhc2Vfd3RzID0gaW1wb3J0YW5jZV93ZWlnaHRzKG1heChjYXNlX3d0cykgLyBjYXNlX3d0cykpDQp9DQoNCg0KcmVjaXBlX2J1aWxkZXIgPC0gZnVuY3Rpb24ob3V0Y29tZSA9ICJjb2hlc2lvbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICBwcmVkaWN0b3IgPSAiZnVsbF90ZXh0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbnRpbWVudHMgPSAiZGljdGlvbmFyeV93b3JkcyIpew0KDQpyZWMgPC0gcmVjaXBlKGZvcm11bGEoZ2x1ZTo6Z2x1ZSgieyBvdXRjb21lIH0gfiB7IHByZWRpY3RvciB9ICsgeyBzZW50aW1lbnRzIH0gKyBjYXNlX3d0cyIpKSwNCiAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2Vzc2F5c19zZW50aW1lbnQgJT4lDQogICAgICAgICAgICAgICAgICAgY2FzZV93ZWlnaHRfYnVpbGRlcihvdXRjb21lKSANCiAgICAgICAgICAgICAgKSAlPiUNCiAgc3RlcF90ZXh0ZmVhdHVyZShmdWxsX3RleHQsDQogICAgICAgICAgICAgICAgICAga2VlcF9vcmlnaW5hbF9jb2xzID0gVFJVRSkgJT4lDQogIHN0ZXBfcmVuYW1lX2F0KA0KICAgIHN0YXJ0c193aXRoKCJ0ZXh0ZmVhdHVyZV8iKSwNCiAgICBmbiA9IH4gZ3N1YigidGV4dGZlYXR1cmVfZnVsbF90ZXh0XyIsICIiLCAuKQ0KICApICU+JQ0KICBzdGVwX211dGF0ZShub253b3JkcyA9IG5fdXFfd29yZHMgLSBkaWN0aW9uYXJ5X3dvcmRzICkgJT4lDQojIyMjIGNsdXN0ZXIgdGhlIGVzc2F5cyBieSB0b3BpYywgZ2VuZXJhbGx5DQogICBzdGVwX3Rva2VuaXplKGZ1bGxfdGV4dCkgJT4lIA0KICAgc3RlcF9sZGEoZnVsbF90ZXh0LCANCiAgICAgICAgICAgIG51bV90b3BpY3MgPSB0b3BpY3MsIA0KICAgICAgICAgICAga2VlcF9vcmlnaW5hbF9jb2xzID0gVFJVRSkgJT4lDQogIHN0ZXBfdGZpZGYoZnVsbF90ZXh0KSAlPiUNCiAgc3RlcF9jbGVhbl9uYW1lcyhhbGxfcHJlZGljdG9ycygpKSAlPiUNCiMjIyMgcmVtb3ZlIGNvbHVtbnMgdGhhdCBhcmUgc3VwZXItc3BhcnNlIGFuZCB1bmJhbGFuY2VkDQogIHN0ZXBfbnp2KGFsbF9wcmVkaWN0b3JzKCksIHVuaXF1ZV9jdXQgPSA5KSAlPiUNCiAgc3RlcF9ub3JtYWxpemUoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKQ0KDQpyZXR1cm4ocmVjKQ0KDQp9DQpgYGANCg0KQXMgbWVudGlvbmVkIGFib3ZlLCB0aGUgbW9kZWwgc3BlY2lmaWNhdGlvbiBpcyBgeGdib29zdGAgZm9yIHJlZ3Jlc3Npb24gdG8gcHJlZGljdCBhIGNvbnRpbnVvdXMgb3V0Y29tZS4NCg0KYGBge3J9DQojfCBsYWJlbDogbW9kZWwgc3BlY2lmaWNhdGlvbg0KDQojIGZpbmFsaXplKG10cnkoKSwgDQojICAgICAgICAgIHJlY2lwZV9idWlsZGVyKCkgfD4gDQojICAgICAgICAgICAgcHJlcCgpIHw+IA0KIyAgICAgICAgICAgIGJha2UobmV3X2RhdGEgPSBOVUxMKSkNCg0Kc3BlYyA8LQ0KICBib29zdF90cmVlKA0KICAgIG10cnkgPSA3MCwgICMgNzVMDQogICAgdHJlZXMgPSA1MDBMLA0KICAgIHRyZWVfZGVwdGggPSA5LCAjIDZMDQogICAgbGVhcm5fcmF0ZSA9IDAuMDEsICAjIG9yaWdpbmFsbHkgMC4xDQogICAgbWluX24gPSAyMCwgICMgMjBMDQogICAgbG9zc19yZWR1Y3Rpb24gPSAwDQogICkgJT4lDQogIHNldF9lbmdpbmUoJ3hnYm9vc3QnKSAlPiUNCiAgc2V0X21vZGUoJ3JlZ3Jlc3Npb24nKQ0KDQojIGFsbF9jb3JlcyA8LSBwYXJhbGxlbGx5OjphdmFpbGFibGVDb3JlcyhvbWl0ID0gMSkNCiMgYWxsX2NvcmVzDQojIA0KIyBmdXR1cmU6OnBsYW4oIm11bHRpc2Vzc2lvbiIsIHdvcmtlcnMgPSBhbGxfY29yZXMpICMgb24gV2luZG93cw0KDQpgYGANCg0KV2UgZml0IGZvciBgY29oZXNpb25gIGZpcnN0IGFuZCB1c2UgY2FzZSB3ZWlnaHRzIHRvIGFkanVzdCBmb3IgdGhlIGZyZXF1ZW5jeSBvZiBvY2N1cnJlbmNlIG9mIGBjb2hlc2lvbmAuICBBZnRlciBsb29raW5nIGF0IHZhcmlhYmxlIGltcG9ydGFuY2UgKHJvdWdobHkgdGhlIG51bWJlciBvZiB0aW1lcyBhIHZhcmlhYmxlIGFwcGVhcnMgaW4gdGhlIHRyZWVzKSBhbmQgcmVzaWR1YWxzLCB3ZSBtYWtlIG91ciBwcmVkaWN0aW9uIG9uIHRoZSBzdWJtaXNzaW9uIHNldCBlc3NheXMgYW5kIGFkZGluZyB0aGF0IGNvbHVtbiB0byB0aGUgZGF0YWZyYW1lLg0KDQpgYGB7cn0NCiN8IGxhYmVsOiBmaXQgY29oZXNpb24NCg0Kb3V0Y29tZSA8LSBvdXRjb21lc1sxXQ0KDQp0cmFpbl9kZiA8LSB0cmFpbl9lc3NheXNfc2VudGltZW50ICU+JQ0KICAgICAgICAgICAgICAgICAgIGNhc2Vfd2VpZ2h0X2J1aWxkZXIob3V0Y29tZSkNCg0Kd2YgPC0gd29ya2Zsb3cocmVjaXBlX2J1aWxkZXIob3V0Y29tZSA9IG91dGNvbWUsIHNlbnRpbWVudHMgPSBzZW50aW1lbnRzKSwgc3BlYykgfD4gDQogICAgICAgYWRkX2Nhc2Vfd2VpZ2h0cyhjYXNlX3d0cykNCg0KIw0KI2ZvbGRzIDwtIHZmb2xkX2N2KHRyYWluX2RmLCBzdHJhdGEgPSAhIW91dGNvbWUpDQoNCiNncmlkIDwtIGV4cGFuZC5ncmlkKGxlYXJuX3JhdGUgPSBjKDAuMDA2LCAwLjAxLCAwLjAzKSkNCg0KIyBycyA8LSB0dW5lX2dyaWQoDQojICAgd2YsDQojICAgZm9sZHMsDQojICAgZ3JpZCA9IGdyaWQsDQojICAgbWV0cmljcyA9IG1ldHJpY19zZXQocm1zZSksDQojICAgY29udHJvbCA9IGNvbnRyb2xfZ3JpZCgpDQojICkNCiMgICANCiMgYXV0b3Bsb3QocnMpDQojIGNvbGxlY3RfbWV0cmljcyhycykNCg0KZml0IDwtIHBhcnNuaXA6OmZpdCh3ZiwgdHJhaW5fZGYpDQoNCmV4dHJhY3RfZml0X2VuZ2luZShmaXQpIHw+IA0KICB2aXA6OnZpcChudW1fZmVhdHVyZXMgPSAyMCkNCg0KdHJhaW5fcHJlZHMgPC0gcHJlZGljdChmaXQsIG5ld19kYXRhID0gdHJhaW5fZXNzYXlzX3NlbnRpbWVudCkgfD4gDQogICBiaW5kX2NvbHModHJhaW5fZXNzYXlzX3JhdyB8PiBzZWxlY3QoISFvdXRjb21lKSkgfD4gDQogICByZW5hbWUodHJ1dGggPSAhIW91dGNvbWUpDQoNCnRyYWluX3ByZWRzIHw+IA0KICAgZ2dwbG90KGFlcyh4ID0gZmFjdG9yKHRydXRoKSwgeSA9IC5wcmVkIC0gdHJ1dGgpKSArDQogICBnZW9tX2JveHBsb3QoKSArDQogICBsYWJzKHRpdGxlID0gZ2x1ZTo6Z2x1ZSgieyBvdXRjb21lIH0gcmVzaWR1YWxzIikpIA0KDQp0cmFpbl9wcmVkcyAlPiUNCiAgIHlhcmRzdGljazo6cm1zZSh0cnV0aCwgLnByZWQpDQoNCnN1Ym1pdF9lc3NheXNfc2VudGltZW50IDwtIHByZWRpY3QoZml0LCBzdWJtaXRfZXNzYXlzX3NlbnRpbWVudCkgfD4gDQogIHJlbmFtZSh7e291dGNvbWV9fSA6PSAucHJlZCkgfD4gDQogIGJpbmRfY29scyhzdWJtaXRfZXNzYXlzX3NlbnRpbWVudCkNCg0KYGBgDQoNCldlIGZpdCBmb3IgYHN5bnRheGAgc2Vjb25kIGFuZCB1c2UgY2FzZSB3ZWlnaHRzIHRvIGFkanVzdCBmb3IgdGhlIGZyZXF1ZW5jeSBvZiBvY2N1cnJlbmNlIG9mIGBzeW50YXhgLiBJIGFtIGNob29zaW5nIHRvIHVzZSB0aGUgcHJlZGljdGVkIHZhbHVlcyBvZiBgY29oZXNpb25gIGFib3ZlIGFzIGFuIGFkZGl0aW9uYWwgcHJlZGljdG9yLg0KDQpgYGB7cn0NCiN8IGxhYmVsOiBmaXQgc3ludGF4DQoNCm91dGNvbWUgPC0gb3V0Y29tZXNbMl0NCnByZWRpY3RvciA8LSBnbHVlOjpnbHVlKCJmdWxsX3RleHQgKyB7IG91dGNvbWVzWzFdIH0iKQ0KDQp0cmFpbl9kZiA8LSB0cmFpbl9lc3NheXNfc2VudGltZW50ICU+JQ0KICAgICAgICAgICAgICAgICAgIGNhc2Vfd2VpZ2h0X2J1aWxkZXIob3V0Y29tZSkNCg0Kd2YgPC0gd29ya2Zsb3cocmVjaXBlX2J1aWxkZXIob3V0Y29tZSwgcHJlZGljdG9yLCBzZW50aW1lbnRzKSwgc3BlYykgJT4lDQogICAgICAgICAgYWRkX2Nhc2Vfd2VpZ2h0cyhjYXNlX3d0cykNCg0KZml0IDwtIHBhcnNuaXA6OmZpdCh3ZiwgdHJhaW5fZGYpDQoNCmV4dHJhY3RfZml0X2VuZ2luZShmaXQpIHw+IA0KICB2aXA6OnZpcChudW1fZmVhdHVyZXMgPSAyMCkNCg0KdHJhaW5fcHJlZHMgPC0gcHJlZGljdChmaXQsIG5ld19kYXRhID0gdHJhaW5fZXNzYXlzX3NlbnRpbWVudCkgfD4gDQogICBiaW5kX2NvbHModHJhaW5fZXNzYXlzX3JhdyB8PiBzZWxlY3QoISFvdXRjb21lKSkgfD4gDQogICByZW5hbWUodHJ1dGggPSAhIW91dGNvbWUpDQoNCnRyYWluX3ByZWRzIHw+IA0KICAgZ2dwbG90KGFlcyh4ID0gZmFjdG9yKHRydXRoKSwgeSA9IC5wcmVkIC0gdHJ1dGgpKSArDQogICBnZW9tX2JveHBsb3QoKSArDQogICBsYWJzKHRpdGxlID0gZ2x1ZTo6Z2x1ZSgieyBvdXRjb21lIH0gcmVzaWR1YWxzIikpIA0KDQp0cmFpbl9wcmVkcyAlPiUNCiAgIHlhcmRzdGljazo6cm1zZSh0cnV0aCwgLnByZWQpDQoNCnN1Ym1pdF9lc3NheXNfc2VudGltZW50IDwtIHByZWRpY3QoZml0LCBzdWJtaXRfZXNzYXlzX3NlbnRpbWVudCkgfD4gDQogIHJlbmFtZSh7e291dGNvbWV9fSA6PSAucHJlZCkgfD4gDQogIGJpbmRfY29scyhzdWJtaXRfZXNzYXlzX3NlbnRpbWVudCkNCg0KYGBgDQoNCldlIGZpdCBmb3IgYHZvY2FidWxhcnlgIG5leHQgYW5kIHVzZSBjYXNlIHdlaWdodHMgdG8gYWRqdXN0IGZvciB0aGUgZnJlcXVlbmN5IG9mIG9jY3VycmVuY2Ugb2YgYHZvY2FidWxhcnlgLiBJIGFtIGNob29zaW5nIHRvIHVzZSB0aGUgcHJlZGljdGVkIHZhbHVlcyBvZiBgY29oZXNpb25gIGFuZCBgc3ludGF4YCBhYm92ZSBhcyBhZGRpdGlvbmFsIHByZWRpY3RvcnMuDQoNCmBgYHtyfQ0KI3wgbGFiZWw6IGZpdCB2b2NhYnVsYXJ5DQoNCm91dGNvbWUgPC0gb3V0Y29tZXNbM10NCnByZWRpY3RvciA8LSBnbHVlOjpnbHVlKCJmdWxsX3RleHQgKyB7IG91dGNvbWVzWzFdIH0gKyB7IG91dGNvbWVzWzJdIH0iKQ0KDQp0cmFpbl9kZiA8LSB0cmFpbl9lc3NheXNfc2VudGltZW50ICU+JQ0KICAgICAgICAgICAgICAgICAgIGNhc2Vfd2VpZ2h0X2J1aWxkZXIob3V0Y29tZSkNCg0Kd2YgPC0gd29ya2Zsb3cocmVjaXBlX2J1aWxkZXIob3V0Y29tZSwgcHJlZGljdG9yLCBzZW50aW1lbnRzKSwgc3BlYykgJT4lDQogICAgICAgICAgYWRkX2Nhc2Vfd2VpZ2h0cyhjYXNlX3d0cykNCg0KZml0IDwtIHBhcnNuaXA6OmZpdCh3ZiwgdHJhaW5fZGYpDQoNCmV4dHJhY3RfZml0X2VuZ2luZShmaXQpIHw+IA0KICB2aXA6OnZpcChudW1fZmVhdHVyZXMgPSAyMCkNCg0KdHJhaW5fcHJlZHMgPC0gcHJlZGljdChmaXQsIG5ld19kYXRhID0gdHJhaW5fZXNzYXlzX3NlbnRpbWVudCkgfD4gDQogICBiaW5kX2NvbHModHJhaW5fZXNzYXlzX3JhdyB8PiBzZWxlY3QoISFvdXRjb21lKSkgfD4gDQogICByZW5hbWUodHJ1dGggPSAhIW91dGNvbWUpDQoNCnRyYWluX3ByZWRzIHw+IA0KICAgZ2dwbG90KGFlcyh4ID0gZmFjdG9yKHRydXRoKSwgeSA9IC5wcmVkIC0gdHJ1dGgpKSArDQogICBnZW9tX2JveHBsb3QoKSArDQogICBsYWJzKHRpdGxlID0gZ2x1ZTo6Z2x1ZSgieyBvdXRjb21lIH0gcmVzaWR1YWxzIikpIA0KDQp0cmFpbl9wcmVkcyAlPiUNCiAgIHlhcmRzdGljazo6cm1zZSh0cnV0aCwgLnByZWQpDQoNCnN1Ym1pdF9lc3NheXNfc2VudGltZW50IDwtIHByZWRpY3QoZml0LCBzdWJtaXRfZXNzYXlzX3NlbnRpbWVudCkgfD4gDQogIHJlbmFtZSh7e291dGNvbWV9fSA6PSAucHJlZCkgfD4gDQogIGJpbmRfY29scyhzdWJtaXRfZXNzYXlzX3NlbnRpbWVudCkNCg0KYGBgDQoNCldlIGZpdCBmb3IgYHBocmFzZW9sb2d5YCBuZXh0IGFuZCB1c2UgY2FzZSB3ZWlnaHRzIHRvIGFkanVzdCBmb3IgdGhlIGZyZXF1ZW5jeSBvZiBvY2N1cnJlbmNlIG9mIGBwaHJhc2VvbG9neWAuIEkgYW0gY2hvb3NpbmcgdG8gdXNlIHRoZSBwcmVkaWN0ZWQgdmFsdWVzIG9mIGBjb2hlc2lvbmAsICBgc3ludGF4YCwgYW5kIGB2b2NhYnVsYXJ5YCBhYm92ZSBhcyBhZGRpdGlvbmFsIHByZWRpY3RvcnMuDQoNCmBgYHtyfQ0KI3wgbGFiZWw6IGZpdCBwaHJhc2VvbG9neQ0KDQpvdXRjb21lIDwtIG91dGNvbWVzWzRdDQpwcmVkaWN0b3IgPC0gZ2x1ZTo6Z2x1ZSgiZnVsbF90ZXh0ICsgeyBvdXRjb21lc1sxXSB9ICsgeyBvdXRjb21lc1syXSB9ICsgeyBvdXRjb21lc1szXSB9IikNCg0KdHJhaW5fZGYgPC0gdHJhaW5fZXNzYXlzX3NlbnRpbWVudCAlPiUNCiAgICAgICAgICAgICAgICAgICBjYXNlX3dlaWdodF9idWlsZGVyKG91dGNvbWUpDQoNCndmIDwtIHdvcmtmbG93KHJlY2lwZV9idWlsZGVyKG91dGNvbWUsIHByZWRpY3Rvciwgc2VudGltZW50cyksIHNwZWMpICU+JQ0KICAgICAgICAgIGFkZF9jYXNlX3dlaWdodHMoY2FzZV93dHMpDQoNCmZpdCA8LSBwYXJzbmlwOjpmaXQod2YsIHRyYWluX2RmKQ0KDQpleHRyYWN0X2ZpdF9lbmdpbmUoZml0KSB8PiANCiAgdmlwOjp2aXAobnVtX2ZlYXR1cmVzID0gMjApDQoNCnRyYWluX3ByZWRzIDwtIHByZWRpY3QoZml0LCBuZXdfZGF0YSA9IHRyYWluX2Vzc2F5c19zZW50aW1lbnQpIHw+IA0KICAgYmluZF9jb2xzKHRyYWluX2Vzc2F5c19yYXcgfD4gc2VsZWN0KCEhb3V0Y29tZSkpIHw+IA0KICAgcmVuYW1lKHRydXRoID0gISFvdXRjb21lKQ0KDQp0cmFpbl9wcmVkcyB8PiANCiAgIGdncGxvdChhZXMoeCA9IGZhY3Rvcih0cnV0aCksIHkgPSAucHJlZCAtIHRydXRoKSkgKw0KICAgZ2VvbV9ib3hwbG90KCkgKw0KICAgbGFicyh0aXRsZSA9IGdsdWU6OmdsdWUoInsgb3V0Y29tZSB9IHJlc2lkdWFscyIpKSANCg0KdHJhaW5fcHJlZHMgJT4lDQogICB5YXJkc3RpY2s6OnJtc2UodHJ1dGgsIC5wcmVkKQ0KDQpzdWJtaXRfZXNzYXlzX3NlbnRpbWVudCA8LSBwcmVkaWN0KGZpdCwgc3VibWl0X2Vzc2F5c19zZW50aW1lbnQpIHw+IA0KICByZW5hbWUoe3tvdXRjb21lfX0gOj0gLnByZWQpIHw+IA0KICBiaW5kX2NvbHMoc3VibWl0X2Vzc2F5c19zZW50aW1lbnQpDQpgYGANCg0KV2UgZml0IGZvciBgZ3JhbW1hcmAgbmV4dCBhbmQgdXNlIGNhc2Ugd2VpZ2h0cyB0byBhZGp1c3QgZm9yIHRoZSBmcmVxdWVuY3kgb2Ygb2NjdXJyZW5jZSBvZiBgZ3JhbW1hcmAuIEkgYW0gY2hvb3NpbmcgdG8gdXNlIHRoZSBwcmVkaWN0ZWQgdmFsdWVzIG9mIGBjb2hlc2lvbmAsICBgc3ludGF4YCwgYHZvY2FidWxhcnlgLCBhbmQgYHBocmFzZW9sb2d5YCBhYm92ZSBhcyBhZGRpdGlvbmFsIHByZWRpY3RvcnMuDQoNCmBgYHtyfQ0KI3wgbGFiZWw6IGZpdCBncmFtbWFyDQoNCm91dGNvbWUgPC0gb3V0Y29tZXNbNV0NCnByZWRpY3RvciA8LSBnbHVlOjpnbHVlKCJmdWxsX3RleHQgKyB7IG91dGNvbWVzWzFdIH0gKyB7IG91dGNvbWVzWzJdfSArIHtvdXRjb21lc1szXX0gKyB7b3V0Y29tZXNbNF19IikNCg0KdHJhaW5fZGYgPC0gdHJhaW5fZXNzYXlzX3NlbnRpbWVudCAlPiUNCiAgICAgICAgICAgICAgICAgICBjYXNlX3dlaWdodF9idWlsZGVyKG91dGNvbWUpDQoNCndmIDwtIHdvcmtmbG93KHJlY2lwZV9idWlsZGVyKG91dGNvbWUsIHByZWRpY3Rvciwgc2VudGltZW50cyksIHNwZWMpICU+JQ0KICAgICAgICAgIGFkZF9jYXNlX3dlaWdodHMoY2FzZV93dHMpDQoNCmZpdCA8LSBwYXJzbmlwOjpmaXQod2YsIHRyYWluX2RmKQ0KDQpleHRyYWN0X2ZpdF9lbmdpbmUoZml0KSB8PiANCiAgdmlwOjp2aXAobnVtX2ZlYXR1cmVzID0gMjApDQoNCnRyYWluX3ByZWRzIDwtIHByZWRpY3QoZml0LCBuZXdfZGF0YSA9IHRyYWluX2Vzc2F5c19zZW50aW1lbnQpIHw+IA0KICAgYmluZF9jb2xzKHRyYWluX2Vzc2F5c19yYXcgfD4gc2VsZWN0KCEhb3V0Y29tZSkpIHw+IA0KICAgcmVuYW1lKHRydXRoID0gISFvdXRjb21lKQ0KDQp0cmFpbl9wcmVkcyB8PiANCiAgIGdncGxvdChhZXMoeCA9IGZhY3Rvcih0cnV0aCksIHkgPSAucHJlZCAtIHRydXRoKSkgKw0KICAgZ2VvbV9ib3hwbG90KCkgKw0KICAgbGFicyh0aXRsZSA9IGdsdWU6OmdsdWUoInsgb3V0Y29tZSB9IHJlc2lkdWFscyIpKSANCg0KdHJhaW5fcHJlZHMgJT4lDQogICB5YXJkc3RpY2s6OnJtc2UodHJ1dGgsIC5wcmVkKQ0KDQpzdWJtaXRfZXNzYXlzX3NlbnRpbWVudCA8LSBwcmVkaWN0KGZpdCwgc3VibWl0X2Vzc2F5c19zZW50aW1lbnQpIHw+IA0KICByZW5hbWUoe3tvdXRjb21lfX0gOj0gLnByZWQpIHw+IA0KICBiaW5kX2NvbHMoc3VibWl0X2Vzc2F5c19zZW50aW1lbnQpDQoNCmBgYA0KDQpXZSBmaXQgZm9yIGBjb252ZW50aW9uc2AgbmV4dCBhbmQgdXNlIGNhc2Ugd2VpZ2h0cyB0byBhZGp1c3QgZm9yIHRoZSBmcmVxdWVuY3kgb2Ygb2NjdXJyZW5jZSBvZiBgY29udmVudGlvbnNgLiBJIGFtIGNob29zaW5nIHRvIHVzZSB0aGUgcHJlZGljdGVkIHZhbHVlcyBvZiBgY29oZXNpb25gLCAgYHN5bnRheGAsIGB2b2NhYnVsYXJ5YCwgYHBocmFzZW9sb2d5YCBhbmQgYGdyYW1tYXJgIGFib3ZlIGFzIGFkZGl0aW9uYWwgcHJlZGljdG9ycy4NCg0KYGBge3J9DQojfCBsYWJlbDogZml0IGNvbnZlbnRpb25zDQoNCm91dGNvbWUgPC0gb3V0Y29tZXNbNl0NCnByZWRpY3RvciA8LSBnbHVlOjpnbHVlKCJmdWxsX3RleHQgKyB7IG91dGNvbWVzWzFdIH0gK3sgb3V0Y29tZXNbMl0gfSt7IG91dGNvbWVzWzNdIH0reyBvdXRjb21lc1s0XSB9K3sgb3V0Y29tZXNbNV0gfSIpDQoNCnRyYWluX2RmIDwtIHRyYWluX2Vzc2F5c19zZW50aW1lbnQgJT4lDQogICAgICAgICAgICAgICAgICAgY2FzZV93ZWlnaHRfYnVpbGRlcihvdXRjb21lKQ0KDQp3ZiA8LSB3b3JrZmxvdyhyZWNpcGVfYnVpbGRlcihvdXRjb21lLCBwcmVkaWN0b3IsIHNlbnRpbWVudHMpLCBzcGVjKSAlPiUNCiAgICAgICAgICBhZGRfY2FzZV93ZWlnaHRzKGNhc2Vfd3RzKQ0KDQpmaXQgPC0gcGFyc25pcDo6Zml0KHdmLCB0cmFpbl9kZikNCg0KZXh0cmFjdF9maXRfZW5naW5lKGZpdCkgfD4gDQogIHZpcDo6dmlwKG51bV9mZWF0dXJlcyA9IDIwKQ0KDQp0cmFpbl9wcmVkcyA8LSBwcmVkaWN0KGZpdCwgbmV3X2RhdGEgPSB0cmFpbl9lc3NheXNfc2VudGltZW50KSB8PiANCiAgIGJpbmRfY29scyh0cmFpbl9lc3NheXNfcmF3IHw+IHNlbGVjdCghIW91dGNvbWUpKSB8PiANCiAgIHJlbmFtZSh0cnV0aCA9ICEhb3V0Y29tZSkNCg0KdHJhaW5fcHJlZHMgfD4gDQogICBnZ3Bsb3QoYWVzKHggPSBmYWN0b3IodHJ1dGgpLCB5ID0gLnByZWQgLSB0cnV0aCkpICsNCiAgIGdlb21fYm94cGxvdCgpICsNCiAgIGxhYnModGl0bGUgPSBnbHVlOjpnbHVlKCJ7IG91dGNvbWUgfSByZXNpZHVhbHMiKSkgDQoNCnRyYWluX3ByZWRzICU+JQ0KICAgeWFyZHN0aWNrOjpybXNlKHRydXRoLCAucHJlZCkNCg0Kc3VibWl0X2Vzc2F5c19zZW50aW1lbnQgPC0gcHJlZGljdChmaXQsIHN1Ym1pdF9lc3NheXNfc2VudGltZW50KSB8PiANCiAgcmVuYW1lKHt7b3V0Y29tZX19IDo9IC5wcmVkKSB8PiANCiAgYmluZF9jb2xzKHN1Ym1pdF9lc3NheXNfc2VudGltZW50KQ0KDQpgYGANCg0KIyMgVGhlIFN1Ym1pc3Npb24NCg0KYGBge3J9DQojfCBsYWJlbDogYnVpbGQgc3VibWlzc2lvbnMgZGF0YWZyYW1lDQoNCnN1Ym1pc3Npb24gPC0NCiAgc3VibWl0X2Vzc2F5c19zZW50aW1lbnQgJT4lDQogIHNlbGVjdCh0ZXh0X2lkLCAhIW91dGNvbWVzKQ0KDQpgYGANCg0KYGBge3J9DQojfCBsYWJlbDogd3JpdGUgc3VibWlzc2lvbiBvdXQgYXMgYSBjc3YNCnN1Ym1pc3Npb24NCg0Kd3JpdGVfY3N2KHN1Ym1pc3Npb24sIGhlcmU6OmhlcmUoImRhdGEiLCAic3VibWlzc2lvbi5jc3YiKSkNCmBgYA0KDQoNCg0KDQoNCg0KDQoNCg0K