Last updated: 2021-10-07

Checks: 7 0

Knit directory: myTidyTuesday/

This reproducible R Markdown analysis was created with workflowr (version 1.6.2). 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 d4b2e17. 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:    catboost_info/
    Ignored:    data/2021-09-08/
    Ignored:    data/CNHI_Excel_Chart.xlsx
    Ignored:    data/CommunityTreemap.jpeg
    Ignored:    data/Community_Roles.jpeg
    Ignored:    data/YammerDigitalDataScienceMembership.xlsx
    Ignored:    data/acs_poverty.rds
    Ignored:    data/fmhpi.rds
    Ignored:    data/grainstocks.rds
    Ignored:    data/hike_data.rds
    Ignored:    data/nber_rs.rmd
    Ignored:    data/netflixTitles.rmd
    Ignored:    data/netflixTitles2.rds
    Ignored:    data/us_states.rds
    Ignored:    data/us_states_hexgrid.geojson
    Ignored:    data/weatherstats_toronto_daily.csv

Untracked files:
    Untracked:  analysis/CHN_1_sp.rds
    Untracked:  analysis/sample data for r test.xlsx
    Untracked:  code/YammerReach.R
    Untracked:  code/work list batch targets.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/2021_04_20.Rmd) and HTML (docs/2021_04_20.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
Rmd d4b2e17 opus1993 2021-10-07 attempt to put logo in figure folder
html a2fbea1 opus1993 2021-10-07 Build site.
Rmd 65449f7 opus1993 2021-10-07 add Netflix titles NLP

knitr::include_graphics("figure/2021_04_20.Rmd/Netflix_Logo_RGB.png", error = FALSE)

The #TidyTuesday data this week is Netflix’s streaming catalog titles with descriptions, genre classifications, actors, directors, dates, and other metadata.

Introduction

First, I want to acknowledge that I benefited from the guidance and work of the following:

  1. David Robinson’s Youtube screencast and code at the repo here.

  2. Julia Silge’s Which #TidyTuesday Netflix titles are movies and which are TV shows?

  3. Mark H White, PhD’s blog post EXPLORING THE STAR WARS “PREQUEL RENAISSANCE” USING TIDYMODELS AND WORKFLOWSETS

  4. Max Kuhn and Julia Silge’s in-progres book Tidy Modeling With R Chapter 15: Screening Many Models

  5. Max Kuhn’s LA R User Group talk on 18May2021

My goal is to explore and classify using the tidymodels framework and workflowsets to predict the genre of the titles on the streaming services. Dr. Silge hinted in her broadcast that a multi-class prediction would be possible with a neural net. Let’s explore that path.

First, let’s load libraries and set a ggplot theme:

suppressPackageStartupMessages({
library(tidyverse)

library(lubridate)
library(hrbrthemes)

library(tidytext)

library(tidymodels)
library(themis)
library(textrecipes)

library(discrim) 

library(finetune)  #  fast hyperparameter selection
library(workflowsets)
library(tidyposterior)
  
library(stacks)

 })

source(here::here("code","_common.R"),
       verbose = FALSE,
       local = knitr::knit_global())

ggplot2::theme_set(theme_jim(base_size = 12))

Let’s load the data and clean up the duration movie field and date_added fields.

netflix_titles <-
  tidytuesdayR::tt_load("2021-04-20")$netflix_titles %>%
  separate(duration,
    c("duration", "duration_units"),
    sep = " ",
    convert = TRUE
  ) %>%
  mutate(
    date_added = mdy(date_added),
    year_added = year(date_added)
  )

    Downloading file 1 of 1: `netflix_titles.csv`

Exploration

Skimming

The dataset includes 7,787 titles, both as movies and TV shows. Other summary details of the dataset, as provided by skimr

skimr::skim(netflix_titles)
Data summary
Name netflix_titles
Number of rows 7787
Number of columns 14
_______________________
Column type frequency:
character 10
Date 1
numeric 3
________________________
Group variables None

Variable type: character

skim_variable n_missing complete_rate min max empty n_unique whitespace
show_id 0 1.00 2 5 0 7787 0
type 0 1.00 5 7 0 2 0
title 0 1.00 1 104 0 7787 0
director 2389 0.69 2 208 0 4049 0
cast 718 0.91 3 771 0 6831 0
country 507 0.93 4 123 0 681 0
rating 7 1.00 1 8 0 14 0
duration_units 0 1.00 3 7 0 3 0
listed_in 0 1.00 6 79 0 492 0
description 0 1.00 61 248 0 7769 0

Variable type: Date

skim_variable n_missing complete_rate min max median n_unique
date_added 10 1 2008-01-01 2021-01-16 2019-03-08 1512

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
release_year 0 1 2013.93 8.76 1925 2013 2017 2018 2021 ▁▁▁▁▇
duration 0 1 69.12 50.95 1 2 88 106 312 ▆▇▂▁▁
year_added 10 1 2018.49 1.39 2008 2018 2019 2020 2021 ▁▁▁▆▇

Genres

The listed_in character field associates individual titles with one or more genres. When separated out, the count of titles in each genre includes:

netflix_titles %>%
  separate_rows(listed_in, sep = ", ") %>%
  count(listed_in, sort = TRUE)

Film Duration

Let’s take a quick peek at the median time duration of only the movies for each genre. Note again that the same title can often be counted in more than one genre grouping.

netflix_titles %>%
  separate_rows(listed_in, sep = ", ") %>%
  filter(type == "Movie") %>%
  group_by(type, genre = listed_in) %>%
  mutate(
    n = n()
  ) %>%
  ungroup() %>%
  mutate(
    genre = glue::glue("{ genre } ({ n })"),
    genre = fct_reorder(genre, duration)
  ) %>%
  filter(genre != "Movies") %>%
  ungroup() %>%
  ggplot(aes(duration, genre)) +
  ggdist::stat_dots(
    aes(color = genre),
    side = "top",
    justification = -0.1,
    binwidth = 0.5,
    show.legend = FALSE
  ) +
  geom_boxplot(aes(
    color = genre,
    fill = stage(genre,
      after_scale = ggplot2::alpha(fill, 0.1)
    )
  ),
  width = 0.1,
  outlier.shape = NA,
  show.legend = FALSE
  ) +
  scale_color_manual(values = viridis::viridis_pal(option = "H")(21)[c(1, 8, 15, 2, 9, 16, 3, 10, 17, 4, 11, 18, 5, 12, 19, 6, 13, 20, 7, 14, 21)]) +
  scale_fill_manual(values = viridis::viridis_pal(option = "H")(21)[c(1, 8, 15, 2, 9, 16, 3, 10, 17, 4, 11, 18, 5, 12, 19, 6, 13, 20, 7, 14, 21)]) +
  labs(
    x = "Media Streaming Netflix Movie Duration (Minutes)",
    y = NULL,
    title = "NETFLIX movie playlength by genre",
    subtitle = "(Count of titles) within the genre"
  )

A dotplot and boxplotshowing movie playlength distributions across genres

The median of Netflix titles labeled as Documentaries and Stand-Up Comedy movies are shorter in duration than Dramas and Classic Movies.

Feature Engineering

Given the description, cast members, director, and release_year, can we build a useful model to predict the genre?

Unbalanced multi-class problems are especially challenging. And in the Netflix dataset, most movies belong to two or three genres at the same time. To make this problem just a little less complex, we will bundle some genre’s into logical groupings. I am also only using the first listed_in genre, as the primary. The result is 20 genres.

netflix_df <- netflix_titles %>%
  select(
    genre = listed_in,
    show_id,
    description,
    cast,
    director,
    release_year
  ) %>%
  separate_rows(genre, sep = ", ") %>%
  mutate(genre = case_when(
    str_detect(genre, "International") ~ "International",
    str_detect(genre, "Romantic") ~ "Romantic",
    str_detect(genre, "Drama") ~ "Drama",
    str_detect(genre, "Action") ~ "Action",
    str_detect(genre, "Comed") ~ "Comedy",
    str_detect(genre, "Cult") ~ "Cult",
    str_detect(genre, "Thrill") ~ "Thriller",
    str_detect(genre, "Teen|Children|Kids") ~ "Children",
    str_detect(genre, "Science|Docu") ~ "Documentary",
    str_detect(genre, "Anime") ~ "Anime",
    str_detect(genre, "Horror") ~ "Horror",
    str_detect(genre, "Independent") ~ "Independent",
    str_detect(genre, "Crime") ~ "Crime",
    str_detect(genre, "Music") ~ "Music",
    str_detect(genre, "British") ~ "British",
    str_detect(genre, "Reality") ~ "Reality",
    str_detect(genre, "Classic") ~ "Classic",
    str_detect(genre, "Myster") ~ "Mysteries",
    str_detect(genre, "Faith") ~ "Faith",
    TRUE ~ "Other"
  )) %>%
  group_by(show_id) %>%
  mutate(position = row_number()) %>%
  ungroup() %>%
  pivot_wider(
    names_from = position,
    values_from = genre,
    names_prefix = "genre"
  ) %>%
  mutate(genre1 = as.factor(genre1))
netflix_df %>%
  count(genre1) %>%
  mutate(pct = n / sum(n)) %>%
  ggplot(aes(pct, fct_reorder(genre1, pct))) +
  geom_col(fill = "lightblue") +
  scale_x_continuous(labels = scales::percent) +
  geom_label(aes(label = scales::percent(pct, accuracy = .1))) +
  labs(
    title = "Severe Imbalance in Media Genre Classification",
    y = NULL, x = NULL
  )

Let’s split the data using the default 3:1 ratio of training-to-test and resample the training set using 5-fold cross-validation. V-fold cross-validation randomly splits the data into V groups (5) of roughly equal size (called “folds”). A resample of the analysis data consists of V-1 (4) of the folds while the assessment set contains the final fold. In basic V-fold cross-validation (i.e. no repeats), the number of resamples is equal to V.

Cross Validation

The strata argument causes the random sampling to be conducted within the stratification variable, in this case genre1.

set.seed(1501)
netflix_split <- initial_split(netflix_df, strata = genre1)
netflix_train <- training(netflix_split)
netflix_test <- testing(netflix_split)

netflix_folds <- vfold_cv(netflix_train,
  v = 5, strata = genre1
)
ggplot(
  netflix_folds %>% tidy(),
  aes(Fold, Row, fill = Data)
) +
  geom_tile() +
  labs(caption = "Resampling strategy")

Our dataset will be pre-processed through several different recipes that engineer text features by tokenizing the description, cast, and the director fields.

The first recipe applies filters to use the words, cast members, and director with the 30 highest term frequencies after removing stop words and stemming.

The second filter the description words to remove stopwords and then builds 25 word embeddings with Glove Twitter 27B. Learn more about word embeddings and pre-trained model transfer learning here. The recipe goes on to hash the cast member and director features into numeric variables with the hashing trick. All models are centered and scaled. There are no missing values.

With a big multi-class challenge, we need to take action to balance the classes. smote upsamples members of the minority classes with nearest neighbors imputation (in the training set only). upsampling replicates rows of the minority classes to match the count of the members of the largest class member.

Pre-processing

(token_rec <- recipe(genre1 ~ description + cast + director,
  data = netflix_train
) %>%
  # upsample training set so each class has the same number of rows
  step_upsample(genre1) %>%
  # work with description character field
  step_tokenize(description) %>%
  step_stopwords(description) %>%
  step_stem(description) %>%
  step_word_embeddings(description,
    embeddings = textdata::embedding_glove27b()
  ) %>%
  # work with the cast members character field
  step_tokenize(cast,
    token = "regex",
    options = list(pattern = ", ")
  ) %>%
  step_tokenfilter(cast, max_tokens = 200) %>%
  step_texthash(cast, num_terms = 6) %>%
  # work with the director member character field
  step_tokenize(director,
    token = "regex",
    options = list(pattern = ", ")
  ) %>%
  step_tokenfilter(director, max_tokens = 200) %>%
  step_texthash(director, num_terms = 10) %>%
  # normalize for any engine with regularization
  step_normalize(all_numeric())
)
Recipe

Inputs:

      role #variables
   outcome          1
 predictor          3

Operations:

Up-sampling based on genre1
Tokenization for description
Stop word removal for description
Stemming for description
Word embeddings aggregated from description
Tokenization for cast
Text filtering for cast
Feature hashing with cast
Tokenization for director
Text filtering for director
Feature hashing with director
Centering and scaling for all_numeric()

Text Embeddings Recipe

And a look at the features of the upsampled “embeddings” recipe:

token_rec %>%
  prep() %>%
  bake(new_data = NULL) %>%
  glimpse()
Rows: 21,006
Columns: 42
$ director_hash01 <dbl> -0.02893736, -0.02893736, -0.02893736, -0.02893736, -0~
$ director_hash02 <dbl> 0.01096156, 0.01096156, 0.01096156, 0.01096156, 0.0109~
$ director_hash03 <dbl> 0.05915029, 0.05915029, 0.05915029, 0.05915029, 0.0591~
$ director_hash04 <dbl> -0.7685871, -0.7685871, -0.7685871, -0.7685871, -0.768~
$ director_hash05 <dbl> -0.03928938, -0.03928938, -0.03928938, -0.03928938, -0~
$ director_hash06 <dbl> 0.01435543, 0.01435543, 0.01435543, 0.01435543, 0.0143~
$ director_hash07 <dbl> 0.04672439, 0.04672439, 0.04672439, 0.04672439, 0.0467~
$ director_hash08 <dbl> -0.0485395, -0.0485395, -0.0485395, -0.0485395, -0.048~
$ director_hash09 <dbl> -0.03925078, -0.03925078, -0.03925078, -0.03925078, -0~
$ director_hash10 <dbl> -0.009853192, -0.009853192, -0.009853192, -0.009853192~
$ cast_hash1      <dbl> -0.08471693, -0.08471693, -3.38020567, -0.08471693, -0~
$ cast_hash2      <dbl> 0.09249959, 0.09249959, 0.09249959, 0.09249959, 0.0924~
$ cast_hash3      <dbl> -0.05483196, -0.05483196, -0.05483196, -0.05483196, -0~
$ cast_hash4      <dbl> -0.08204554, -0.08204554, -0.08204554, -0.08204554, -0~
$ cast_hash5      <dbl> -0.00775839, -0.00775839, 2.25575196, -0.00775839, -0.~
$ cast_hash6      <dbl> -0.04166601, -0.04166601, -0.04166601, -0.04166601, -0~
$ genre1          <fct> Action, Action, Action, Action, Action, Action, Action~
$ w_embed_sum_d1  <dbl> -0.88769404, -0.42409688, 1.13895629, -1.66692534, 0.1~
$ w_embed_sum_d2  <dbl> 0.8027839, 1.9160799, 0.8872131, 0.3691772, -1.6421994~
$ w_embed_sum_d3  <dbl> -0.45137723, -0.07343944, -0.78374714, -0.98154366, -1~
$ w_embed_sum_d4  <dbl> 0.2481995, 1.0630392, 0.1830963, 1.6405290, 0.7708223,~
$ w_embed_sum_d5  <dbl> 0.100271613, -1.313108020, -0.450255723, 0.053833303, ~
$ w_embed_sum_d6  <dbl> 0.98962476, -0.54912573, 0.38250539, 0.42568428, 0.122~
$ w_embed_sum_d7  <dbl> -0.41984969, -0.26710037, 0.03264045, -0.40406612, -1.~
$ w_embed_sum_d8  <dbl> -1.0401391, -0.2923903, 0.3710757, -0.9874991, -2.0057~
$ w_embed_sum_d9  <dbl> 1.1823848, 0.6338659, -0.2088298, 1.0279676, 0.4180787~
$ w_embed_sum_d10 <dbl> 1.560526621, 0.408680891, 1.597693635, -0.194099924, 0~
$ w_embed_sum_d11 <dbl> 0.06575798, 1.52348731, 1.84824395, -0.69609988, 0.091~
$ w_embed_sum_d12 <dbl> 0.03596779, 0.19906447, 0.70283170, 0.90545887, -0.254~
$ w_embed_sum_d13 <dbl> 0.7670217, 0.2198714, 0.7434887, 0.1240857, 0.4057891,~
$ w_embed_sum_d14 <dbl> 0.23182713, -1.16364635, -0.93447456, 1.27965719, 0.28~
$ w_embed_sum_d15 <dbl> 0.152729431, 1.426433309, -1.104981440, 0.002337636, 0~
$ w_embed_sum_d16 <dbl> 1.42214118, -0.39406305, -1.02111869, -0.41868217, -0.~
$ w_embed_sum_d17 <dbl> -0.16916311, -0.48377838, -0.46560217, 0.60142401, -0.~
$ w_embed_sum_d18 <dbl> -0.09388500, -0.37158055, -0.99407908, -0.35556000, 1.~
$ w_embed_sum_d19 <dbl> 0.32575707, -0.49348854, 0.16692971, 1.07728364, 1.075~
$ w_embed_sum_d20 <dbl> -0.79798799, 0.24579381, -1.62909190, -1.24971495, -0.~
$ w_embed_sum_d21 <dbl> 0.41841392, -0.00691488, -0.46134090, 0.90286682, -0.5~
$ w_embed_sum_d22 <dbl> 1.6369944284, 1.8566314246, -0.4315628413, 1.469026277~
$ w_embed_sum_d23 <dbl> 0.54119547, 2.21096826, -0.29194279, 0.34880442, 0.150~
$ w_embed_sum_d24 <dbl> -0.662200013, 0.862445045, 0.977315898, -1.452283239, ~
$ w_embed_sum_d25 <dbl> 0.59674274, 0.53158162, 0.09021243, 0.31440704, -0.137~

Machine Learning

For the models themselves, we use the the parsnip package to create sets of model specifications. All are setup for multi-class classification.

logistic_reg_glmnet_spec <-
  multinom_reg(
    penalty = tune(),
    mixture = 0.01
  ) %>%
  set_engine("glmnet") %>%
  set_mode("classification")

nb_spec <- naive_Bayes() %>%
  set_mode("classification") %>%
  set_engine("naivebayes")

svm_spec <-
  svm_linear() %>%
  set_mode("classification") %>%
  set_engine("LiblineaR")

Workflowsets

This is where the cool part comes in: We now define a “workflow set.” We apply cross = TRUE to look at all combinations of recipes and models.

(wfs <-
  workflow_set(
    preproc = list(embeddings = token_rec),
    models = list(
      nb_spec,
      logistic_reg_glmnet_spec,
      svm_spec
    )
  )
)

And we see we have our six different workflows set up.

Tuning and Cross Validation

The engines chosen here require no tuning. We will load register a parallel backend to speed the computations across the cross validation folds.

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

ctrl <-
  control_grid(
    save_pred = FALSE,
    parallel_over = "everything",
    save_workflow = FALSE
  )

system.time(
  cv_res <- wfs %>%
    workflow_map("tune_grid",
      seed = 2021,
      resamples = netflix_folds,
      control = ctrl,
      grid = 20,
      metrics = metric_set(
        accuracy,
        sensitivity,
        specificity,
        j_index
      ),
      verbose = TRUE
    )
)

future::plan(strategy = "sequential")
i   No tuning parameters. `fit_resamples()` will be attempted
i 1 of 3 resampling: embeddings_naive_Bayes
v 1 of 3 resampling: embeddings_naive_Bayes (30.8s)
i 2 of 3 tuning:     embeddings_multinom_reg
v 2 of 3 tuning:     embeddings_multinom_reg (2m 29.2s)
i   No tuning parameters. `fit_resamples()` will be attempted
i 3 of 3 resampling: embeddings_svm_linear
v 3 of 3 resampling: embeddings_svm_linear (4m 27.1s)
   user  system elapsed 
 448.23    0.31  449.03 

Note the run times.

Compare the models

We can then look at the results. What are the top workflows?

autoplot(cv_res)

cv_res %>%
  rank_results(select_best = TRUE) %>%
  pivot_wider(
    id_cols = wflow_id,
    names_from = .metric,
    values_from = mean
  ) %>%
  select(wflow_id, accuracy, j_index, sens, spec)

So the GLMnet runs fast and delivers the best results of the three engines used. Let’s see what we can do to improve it, still using the same resample folds.

logistic_reg_glmnet_spec <-
  multinom_reg(
    penalty = tune(),
    mixture = tune()
  ) %>%
  set_engine("glmnet") %>%
  set_mode("classification")

glm_wf <- workflow(token_rec, logistic_reg_glmnet_spec)

ctrl <-
  control_grid(
    save_pred = TRUE,
    parallel_over = "everything",
    save_workflow = FALSE
  )

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


cv_glmnet <- glm_wf %>%
  tune_grid(
    resamples = netflix_folds,
    control = ctrl,
    grid = 20,
    metrics = metric_set(
      roc_auc,
      accuracy,
      sensitivity,
      specificity,
      mn_log_loss
    )
  )

future::plan(strategy = "sequential")

autoplot(cv_glmnet)

Not bad. Let’s look more closely at performance across cross validation folds and prediction classes next.

Performance

We can look at ROC curves by genre class for the set of 5 cross-validation folds of the best model:

cv_glmnet %>%
  collect_predictions() %>%
  group_by(id) %>%
  roc_curve(truth = genre1, .pred_Action:.pred_Thriller) %>%
  autoplot() +
  labs(
    color = NULL,
    title = glue::glue("ROC curve for Netflix Movie GLMnet Predictor"),
    subtitle = "Each resample fold is shown in a different color"
  )

This model predicts Documentaries and International films very well. On the other extreme, Romantic Movies and “Other” are not predicted well in every cross validation fold.

We can also create a confusion matrix from the resamples using conf_mat_resampled(), which computes a separate confusion matrix for each resample and then averages the cell counts.

show_best(cv_glmnet, metric = "mn_log_loss") %>%
  select(-.estimator)
show_best(cv_glmnet, metric = "roc_auc") %>%
  select(-.estimator)
cv_glmnet %>%
  conf_mat_resampled(
    tidy = FALSE,
    parameters = select_best(cv_glmnet,
      metric = "mn_log_loss"
    )
  ) %>%
  autoplot(type = "heatmap") +
  labs(title = "Confusion Matrix, all resamples, best mn_log_loss results")

Generally, it is a good idea to evaluate the models over multiple metrics so that different aspects of the model fit are taken into account. Also, it often makes sense to choose a slightly suboptimal parameter combination that is associated with a simpler model. For this model, simplicity corresponds to larger penalty values.

best_results <- cv_glmnet %>%
  select_best(metric = "mn_log_loss")

test_results <- glm_wf %>%
  finalize_workflow(best_results) %>%
  last_fit(split = netflix_split)

The test results show:

collect_metrics(test_results)

This is a good test result, as the model run on unseen data delivers performance comparable to the training figures, so does not over-fit. Even so, I’d like to explore other machine learning engines, including deep learning, to improve classificaiton accuracy.


sessionInfo()
R version 4.1.1 (2021-08-10)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 22000)

Matrix products: default

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

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

other attached packages:
 [1] glmnet_4.1-2        Matrix_1.3-4        vctrs_0.3.8        
 [4] rlang_0.4.11        text2vec_0.6        SnowballC_0.7.0    
 [7] stopwords_2.2       stacks_0.2.1        tidyposterior_0.1.0
[10] finetune_0.1.0      discrim_0.1.3       textrecipes_0.4.1  
[13] themis_0.1.4        yardstick_0.0.8     workflowsets_0.1.0 
[16] workflows_0.2.3     tune_0.1.6          rsample_0.1.0      
[19] recipes_0.1.17      parsnip_0.1.7.900   modeldata_0.1.1    
[22] infer_1.0.0         dials_0.0.10        scales_1.1.1       
[25] broom_0.7.9         tidymodels_0.1.4    tidytext_0.3.2     
[28] hrbrthemes_0.8.0    lubridate_1.7.10    forcats_0.5.1      
[31] stringr_1.4.0       dplyr_1.0.7         purrr_0.3.4        
[34] readr_2.0.2         tidyr_1.1.4         tibble_3.1.4       
[37] ggplot2_3.3.5       tidyverse_1.3.1     workflowr_1.6.2    

loaded via a namespace (and not attached):
  [1] rappdirs_0.3.3       R.methodsS3_1.8.1    ragg_1.1.3          
  [4] bit64_4.0.5          knitr_1.36           dygraphs_1.1.1.6    
  [7] R.utils_2.11.0       styler_1.6.2         data.table_1.14.2   
 [10] rpart_4.1-15         inline_0.3.19        hardhat_0.1.6       
 [13] doParallel_1.0.16    generics_0.1.0       GPfit_1.0-8         
 [16] RhpcBLASctl_0.21-247 callr_3.7.0          usethis_2.0.1       
 [19] RANN_2.6.1           future_1.22.1        conflicted_1.0.4    
 [22] bit_4.0.4            tzdb_0.1.2           tokenizers_0.2.1    
 [25] xml2_1.3.2           httpuv_1.6.3         StanHeaders_2.21.0-7
 [28] assertthat_0.2.1     viridis_0.6.1        gower_0.2.2         
 [31] xfun_0.26            ggdist_3.0.0         hms_1.1.1           
 [34] jquerylib_0.1.4      bayesplot_1.8.1      evaluate_0.14       
 [37] promises_1.2.0.1     fansi_0.5.0          dbplyr_2.1.1        
 [40] readxl_1.3.1         igraph_1.2.6         DBI_1.1.1           
 [43] htmlwidgets_1.5.4    stats4_4.1.1         rsparse_0.4.0       
 [46] ellipsis_0.3.2       crosstalk_1.1.1      selectr_0.4-2       
 [49] backports_1.2.1      V8_3.4.2             markdown_1.1        
 [52] prismatic_1.0.0      textdata_0.4.1       RcppParallel_5.1.4  
 [55] here_1.0.1           cachem_1.0.6         withr_2.4.2         
 [58] vroom_1.5.5          checkmate_2.0.0      xts_0.12.1          
 [61] prettyunits_1.1.1    crayon_1.4.1         labeling_0.4.2      
 [64] pkgconfig_2.0.3      nlme_3.1-152         nnet_7.3-16         
 [67] globals_0.14.0       lifecycle_1.0.1      miniUI_0.1.1.1      
 [70] skimr_2.1.3          colourpicker_1.1.1   extrafontdb_1.0     
 [73] unbalanced_2.0       modelr_0.1.8         distributional_0.2.2
 [76] cellranger_1.1.0     rprojroot_2.0.2      matrixStats_0.61.0  
 [79] loo_2.4.1            boot_1.3-28          zoo_1.8-9           
 [82] reprex_2.0.1         base64enc_0.1-3      whisker_0.4         
 [85] ggridges_0.5.3       processx_3.5.2       viridisLite_0.4.0   
 [88] float_0.2-6          ROSE_0.0-4           R.oo_1.24.0         
 [91] pROC_1.18.0          mlr_2.19.0           shape_1.4.6         
 [94] parallelly_1.28.1    R.cache_0.15.0       shinystan_2.5.0     
 [97] magrittr_2.0.1       ParamHelpers_1.14    plyr_1.8.6          
[100] threejs_0.3.3        compiler_4.1.1       rstantools_2.1.1    
[103] lme4_1.1-27.1        cli_3.0.1            DiceDesign_1.9      
[106] listenv_0.8.0        janeaustenr_0.1.5    ps_1.6.0            
[109] MASS_7.3-54          tidyselect_1.1.1     stringi_1.7.5       
[112] textshaping_0.3.5    butcher_0.1.5        highr_0.9           
[115] yaml_2.2.1           grid_4.1.1           sass_0.4.0          
[118] fastmatch_1.1-3      tools_4.1.1          future.apply_1.8.1  
[121] parallel_4.1.1       rstudioapi_0.13      foreach_1.5.1       
[124] git2r_0.28.0         gridExtra_2.3        prodlim_2019.11.13  
[127] farver_2.1.0         digest_0.6.28        FNN_1.1.3           
[130] shiny_1.7.1          lava_1.6.10          Rcpp_1.0.7          
[133] later_1.3.0          httr_1.4.2           gdtools_0.2.3       
[136] rsconnect_0.8.24     colorspace_2.0-2     rvest_1.0.1         
[139] fs_1.5.0             lgr_0.4.3            splines_4.1.1       
[142] rematch2_2.1.2       shinythemes_1.2.0    systemfonts_1.0.2   
[145] xtable_1.8-4         rstanarm_2.21.1      jsonlite_1.7.2      
[148] nloptr_1.2.2.2       BBmisc_1.11          timeDate_3043.102   
[151] rstan_2.21.2         ipred_0.9-12         R6_2.5.1            
[154] mlapi_0.1.0          lhs_1.1.3            pillar_1.6.3        
[157] htmltools_0.5.2      mime_0.12            glue_1.4.2          
[160] fastmap_1.1.0        minqa_1.2.4          DT_0.19             
[163] class_7.3-19         codetools_0.2-18     pkgbuild_1.2.0      
[166] furrr_0.2.3          utf8_1.2.2           lattice_0.20-44     
[169] bslib_0.3.0          curl_4.3.2           tidytuesdayR_1.0.1  
[172] gtools_3.9.2         shinyjs_2.0.0        Rttf2pt1_1.3.8      
[175] survival_3.2-11      parallelMap_1.5.1    repr_1.1.3          
[178] rmarkdown_2.11       munsell_0.5.0        iterators_1.0.13    
[181] haven_2.4.3          reshape2_1.4.4       gtable_0.3.0        
[184] extrafont_0.17      
LS0tDQp0aXRsZTogIk5ldGZsaXggVGl0bGVzIE5MUCINCmF1dGhvcjogIkppbSBHcnVtYW4iDQpkYXRlOiAiQXByaWwgMjAsIDIwMjEiDQpvdXRwdXQ6DQogIHdvcmtmbG93cjo6d2Zsb3dfaHRtbDoNCiAgICB0b2M6IG5vDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KZWRpdG9yX29wdGlvbnM6DQogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlDQotLS0NCg0KYGBge3IgbmV0ZmxpeF9sb2dvfQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImZpZ3VyZS8yMDIxXzA0XzIwLlJtZC9OZXRmbGl4X0xvZ29fUkdCLnBuZyIsIGVycm9yID0gRkFMU0UpDQpgYGANCg0KVGhlICNUaWR5VHVlc2RheSBkYXRhIHRoaXMgd2VlayBpcyBOZXRmbGl4J3Mgc3RyZWFtaW5nIGNhdGFsb2cgdGl0bGVzIHdpdGggZGVzY3JpcHRpb25zLCBnZW5yZSBjbGFzc2lmaWNhdGlvbnMsIGFjdG9ycywgZGlyZWN0b3JzLCBkYXRlcywgYW5kIG90aGVyIG1ldGFkYXRhLg0KDQojIEludHJvZHVjdGlvbg0KDQpGaXJzdCwgSSB3YW50IHRvIGFja25vd2xlZGdlIHRoYXQgSSBiZW5lZml0ZWQgZnJvbSB0aGUgZ3VpZGFuY2UgYW5kIHdvcmsgb2YgdGhlIGZvbGxvd2luZzoNCg0KMS4gRGF2aWQgUm9iaW5zb24ncyBZb3V0dWJlIHNjcmVlbmNhc3QgYW5kIGNvZGUgYXQgdGhlIFtyZXBvIGhlcmVdKGh0dHBzOi8vZ2l0aHViLmNvbS9kZ3J0d28vZGF0YS1zY3JlZW5jYXN0cy9ibG9iL21hc3Rlci8yMDIxXzA0XzIwX25ldGZsaXhfdGl0bGVzLlJtZCkuDQoNCjEuIEp1bGlhIFNpbGdlJ3MgW1doaWNoICNUaWR5VHVlc2RheSBOZXRmbGl4IHRpdGxlcyBhcmUgbW92aWVzIGFuZCB3aGljaCBhcmUgVFYgc2hvd3M/XShodHRwczovL2p1bGlhc2lsZ2UuY29tL2Jsb2cvbmV0ZmxpeC10aXRsZXMvKQ0KDQoxLiBNYXJrIEggV2hpdGUsIFBoRCdzIGJsb2cgcG9zdCBbRVhQTE9SSU5HIFRIRSBTVEFSIFdBUlMgIlBSRVFVRUwgUkVOQUlTU0FOQ0UiIFVTSU5HIFRJRFlNT0RFTFMgQU5EIFdPUktGTE9XU0VUU10oaHR0cHM6Ly93d3cubWFya2h3LmNvbS9ibG9nL3ByZXF1ZWwtcmVuYWlzc2FuY2UpDQoNCjEuIE1heCBLdWhuIGFuZCBKdWxpYSBTaWxnZSdzIGluLXByb2dyZXMgYm9vayBUaWR5IE1vZGVsaW5nIFdpdGggUiBbQ2hhcHRlciAxNTogU2NyZWVuaW5nIE1hbnkgTW9kZWxzXShodHRwczovL3d3dy50bXdyLm9yZy93b3JrZmxvdy1zZXRzLmh0bWwpDQoNCjEuIE1heCBLdWhuJ3MgTEEgUiBVc2VyIEdyb3VwIHRhbGsgb24gMThNYXkyMDIxDQoNCk15IGdvYWwgaXMgdG8gZXhwbG9yZSBhbmQgY2xhc3NpZnkgdXNpbmcgdGhlIGB0aWR5bW9kZWxzYCBmcmFtZXdvcmsgYW5kIGB3b3JrZmxvd3NldHNgIHRvIHByZWRpY3QgdGhlIGdlbnJlIG9mIHRoZSB0aXRsZXMgb24gdGhlIHN0cmVhbWluZyBzZXJ2aWNlcy4gRHIuIFNpbGdlIGhpbnRlZCBpbiBoZXIgYnJvYWRjYXN0IHRoYXQgYSBtdWx0aS1jbGFzcyBwcmVkaWN0aW9uIHdvdWxkIGJlIHBvc3NpYmxlIHdpdGggYSBuZXVyYWwgbmV0LiBMZXQncyBleHBsb3JlIHRoYXQgcGF0aC4NCg0KRmlyc3QsIGxldCdzIGxvYWQgbGlicmFyaWVzIGFuZCBzZXQgYSBnZ3Bsb3QgdGhlbWU6DQoNCmBgYHtyIHNldHVwLCB3YXJuaW5nPUZBTFNFfQ0KDQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoew0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShocmJydGhlbWVzKQ0KDQpsaWJyYXJ5KHRpZHl0ZXh0KQ0KDQpsaWJyYXJ5KHRpZHltb2RlbHMpDQpsaWJyYXJ5KHRoZW1pcykNCmxpYnJhcnkodGV4dHJlY2lwZXMpDQoNCmxpYnJhcnkoZGlzY3JpbSkgDQoNCmxpYnJhcnkoZmluZXR1bmUpICAjICBmYXN0IGh5cGVycGFyYW1ldGVyIHNlbGVjdGlvbg0KbGlicmFyeSh3b3JrZmxvd3NldHMpDQpsaWJyYXJ5KHRpZHlwb3N0ZXJpb3IpDQogIA0KbGlicmFyeShzdGFja3MpDQoNCiB9KQ0KDQpzb3VyY2UoaGVyZTo6aGVyZSgiY29kZSIsIl9jb21tb24uUiIpLA0KICAgICAgIHZlcmJvc2UgPSBGQUxTRSwNCiAgICAgICBsb2NhbCA9IGtuaXRyOjprbml0X2dsb2JhbCgpKQ0KDQpnZ3Bsb3QyOjp0aGVtZV9zZXQodGhlbWVfamltKGJhc2Vfc2l6ZSA9IDEyKSkNCg0KYGBgDQoNCkxldCdzIGxvYWQgdGhlIGRhdGEgYW5kIGNsZWFuIHVwIHRoZSBkdXJhdGlvbiBtb3ZpZSBmaWVsZCBhbmQgZGF0ZV9hZGRlZCBmaWVsZHMuDQoNCmBgYHtyIGxvYWQsIG1lc3NhZ2U9RkFMU0V9DQoNCm5ldGZsaXhfdGl0bGVzIDwtDQogIHRpZHl0dWVzZGF5Ujo6dHRfbG9hZCgiMjAyMS0wNC0yMCIpJG5ldGZsaXhfdGl0bGVzICU+JQ0KICBzZXBhcmF0ZShkdXJhdGlvbiwNCiAgICAgICAgICAgYygiZHVyYXRpb24iLCAiZHVyYXRpb25fdW5pdHMiKSwNCiAgICAgICAgICAgc2VwID0gIiAiLA0KICAgICAgICAgICBjb252ZXJ0ID0gVFJVRSkgJT4lDQogIG11dGF0ZShkYXRlX2FkZGVkID0gbWR5KGRhdGVfYWRkZWQpLA0KICAgICAgICAgeWVhcl9hZGRlZCA9IHllYXIoZGF0ZV9hZGRlZCkpIA0KDQpgYGANCg0KIyBFeHBsb3JhdGlvbiB7LnRhYnNldCAudGFic2V0LXBpbGxzfQ0KDQojIyBTa2ltbWluZw0KDQpUaGUgZGF0YXNldCBpbmNsdWRlcyBgciBzY2FsZXM6OmNvbW1hKG5yb3cobmV0ZmxpeF90aXRsZXMpKWAgdGl0bGVzLCBib3RoIGFzIG1vdmllcyBhbmQgVFYgc2hvd3MuIE90aGVyIHN1bW1hcnkgZGV0YWlscyBvZiB0aGUgZGF0YXNldCwgYXMgcHJvdmlkZWQgYnkgYHNraW1yYA0KDQpgYGB7ciBza2ltfQ0Kc2tpbXI6OnNraW0obmV0ZmxpeF90aXRsZXMpIA0KYGBgDQoNCiMjIEdlbnJlcw0KDQpUaGUgYGxpc3RlZF9pbmAgY2hhcmFjdGVyIGZpZWxkIGFzc29jaWF0ZXMgaW5kaXZpZHVhbCB0aXRsZXMgd2l0aCBvbmUgb3IgbW9yZSBnZW5yZXMuIFdoZW4gc2VwYXJhdGVkIG91dCwgdGhlIGNvdW50IG9mIHRpdGxlcyBpbiBlYWNoIGdlbnJlIGluY2x1ZGVzOg0KDQpgYGB7ciBnZW5yZXN9DQpuZXRmbGl4X3RpdGxlcyAlPiUNCiAgc2VwYXJhdGVfcm93cyhsaXN0ZWRfaW4sIHNlcCA9ICIsICIpICU+JSANCiAgY291bnQobGlzdGVkX2luLCBzb3J0ID0gVFJVRSkNCmBgYA0KDQojIyBGaWxtIER1cmF0aW9uIHsuYWN0aXZlfQ0KDQpMZXQncyB0YWtlIGEgcXVpY2sgcGVlayBhdCB0aGUgbWVkaWFuIHRpbWUgZHVyYXRpb24gb2Ygb25seSB0aGUgbW92aWVzIGZvciBlYWNoIGdlbnJlLiBOb3RlIGFnYWluIHRoYXQgdGhlIHNhbWUgdGl0bGUgY2FuIG9mdGVuIGJlIGNvdW50ZWQgaW4gbW9yZSB0aGFuIG9uZSBnZW5yZSBncm91cGluZy4NCg0KYGBge3IgbmV0ZmxpeF90aXRsZXN9DQojfCBmaWcud2lkdGg6IDkNCiN8IGZpZy5hc3A6IDENCiN8IGZpZy5hbGlnbjogImNlbnRlciINCiN8IG91dC53aWR0aDogIjMwMCUiDQojfCBmaWcuYWx0OiA+DQojfCAgQSBkb3RwbG90IGFuZCBib3hwbG90c2hvd2luZyBtb3ZpZSBwbGF5bGVuZ3RoDQojfCAgZGlzdHJpYnV0aW9ucyBhY3Jvc3MgZ2VucmVzDQoNCm5ldGZsaXhfdGl0bGVzICU+JQ0KICBzZXBhcmF0ZV9yb3dzKGxpc3RlZF9pbiwgc2VwID0gIiwgIikgJT4lDQogIGZpbHRlcih0eXBlID09ICJNb3ZpZSIpICU+JQ0KICBncm91cF9ieSh0eXBlLCBnZW5yZSA9IGxpc3RlZF9pbikgJT4lDQogIG11dGF0ZSgNCiAgICBuID0gbigpDQogICkgJT4lIA0KICB1bmdyb3VwKCkgJT4lIA0KICBtdXRhdGUoDQogICAgZ2VucmUgPSBnbHVlOjpnbHVlKCJ7IGdlbnJlIH0gKHsgbiB9KSIpLA0KICAgIGdlbnJlID0gZmN0X3Jlb3JkZXIoZ2VucmUsIGR1cmF0aW9uKQ0KICApICU+JQ0KICBmaWx0ZXIoZ2VucmUgIT0gIk1vdmllcyIpICU+JSAgDQogIHVuZ3JvdXAoKSAlPiUgDQogIGdncGxvdChhZXMoZHVyYXRpb24sIGdlbnJlKSkgKw0KICBnZ2Rpc3Q6OnN0YXRfZG90cygNCiAgICBhZXMoY29sb3IgPSBnZW5yZSksDQogICAgc2lkZSA9ICJ0b3AiLA0KICAgIGp1c3RpZmljYXRpb24gPSAtMC4xLA0KICAgIGJpbndpZHRoID0gMC41LA0KICAgIHNob3cubGVnZW5kID0gRkFMU0UNCiAgKSArDQogIGdlb21fYm94cGxvdChhZXMoY29sb3IgPSBnZW5yZSwNCiAgICAgICAgICAgICAgICAgICBmaWxsID0gc3RhZ2UoZ2VucmUsIA0KICAgICAgICAgICAgICAgICAgICAgICAgYWZ0ZXJfc2NhbGUgPSBnZ3Bsb3QyOjphbHBoYShmaWxsLCAwLjEpKSksDQogICAgd2lkdGggPSAwLjEsDQogICAgb3V0bGllci5zaGFwZSA9IE5BLA0KICAgIHNob3cubGVnZW5kID0gRkFMU0UNCiAgKSArDQogICAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IHZpcmlkaXM6OnZpcmlkaXNfcGFsKG9wdGlvbiA9ICJIIikoMjEpW2MoMSwgOCwgMTUsIDIsIDksIDE2LCAzLCAxMCwgMTcsIDQsIDExLCAxOCwgNSwgMTIsIDE5LCA2LCAxMywgMjAsIDcsIDE0LCAyMSldKSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IHZpcmlkaXM6OnZpcmlkaXNfcGFsKG9wdGlvbiA9ICJIIikoMjEpW2MoMSwgOCwgMTUsIDIsIDksIDE2LCAzLCAxMCwgMTcsIDQsIDExLCAxOCwgNSwgMTIsIDE5LCA2LCAxMywgMjAsIDcsIDE0LCAyMSldKSArDQogIGxhYnMoeCA9ICJNZWRpYSBTdHJlYW1pbmcgTmV0ZmxpeCBNb3ZpZSBEdXJhdGlvbiAoTWludXRlcykiLA0KICAgICAgICAgICB5ID0gTlVMTCwNCiAgICAgICB0aXRsZSA9ICJORVRGTElYIG1vdmllIHBsYXlsZW5ndGggYnkgZ2VucmUiLA0KICAgICAgIHN1YnRpdGxlID0gIihDb3VudCBvZiB0aXRsZXMpIHdpdGhpbiB0aGUgZ2VucmUiDQogICAgICApDQpgYGANCg0KVGhlIG1lZGlhbiBvZiBOZXRmbGl4IHRpdGxlcyBsYWJlbGVkIGFzIERvY3VtZW50YXJpZXMgYW5kIFN0YW5kLVVwIENvbWVkeSBtb3ZpZXMgYXJlIHNob3J0ZXIgaW4gZHVyYXRpb24gdGhhbiBEcmFtYXMgYW5kIENsYXNzaWMgTW92aWVzLiAgDQoNCiMgey19DQoNCiMgRmVhdHVyZSBFbmdpbmVlcmluZyB7LnRhYnNldCAudGFic2V0LXBpbGxzfQ0KDQpHaXZlbiB0aGUgZGVzY3JpcHRpb24sIGNhc3QgbWVtYmVycywgZGlyZWN0b3IsIGFuZCByZWxlYXNlX3llYXIsIGNhbiB3ZSBidWlsZCBhIHVzZWZ1bCBtb2RlbCB0byBwcmVkaWN0IHRoZSBnZW5yZT8NCg0KVW5iYWxhbmNlZCBtdWx0aS1jbGFzcyBwcm9ibGVtcyBhcmUgZXNwZWNpYWxseSBjaGFsbGVuZ2luZy4gQW5kIGluIHRoZSBOZXRmbGl4IGRhdGFzZXQsIG1vc3QgbW92aWVzIGJlbG9uZyB0byB0d28gb3IgdGhyZWUgZ2VucmVzIGF0IHRoZSBzYW1lIHRpbWUuIFRvIG1ha2UgdGhpcyBwcm9ibGVtIGp1c3QgYSBsaXR0bGUgbGVzcyBjb21wbGV4LCB3ZSB3aWxsIGJ1bmRsZSBzb21lIGdlbnJlJ3MgaW50byBsb2dpY2FsIGdyb3VwaW5ncy4gSSBhbSBhbHNvIG9ubHkgdXNpbmcgdGhlIGZpcnN0IGBsaXN0ZWRfaW5gIGdlbnJlLCBhcyB0aGUgcHJpbWFyeS4gVGhlIHJlc3VsdCBpcyAyMCBnZW5yZXMuDQoNCmBgYHtyIGRhdGFmcmFtZX0NCm5ldGZsaXhfZGYgPC0gbmV0ZmxpeF90aXRsZXMgJT4lIA0KICBzZWxlY3QoZ2VucmUgPSBsaXN0ZWRfaW4sIA0KICAgICAgICAgc2hvd19pZCwgDQogICAgICAgICBkZXNjcmlwdGlvbiwgDQogICAgICAgICBjYXN0LCANCiAgICAgICAgIGRpcmVjdG9yLCANCiAgICAgICAgIHJlbGVhc2VfeWVhcikgJT4lIA0KICBzZXBhcmF0ZV9yb3dzKGdlbnJlLCBzZXAgPSAiLCAiKSAlPiUgDQogIG11dGF0ZShnZW5yZSA9IGNhc2Vfd2hlbigNCiAgICAgICAgc3RyX2RldGVjdChnZW5yZSwgIkludGVybmF0aW9uYWwiKSB+ICJJbnRlcm5hdGlvbmFsIiwNCiAgICAgICAgc3RyX2RldGVjdChnZW5yZSwgIlJvbWFudGljIikgfiAiUm9tYW50aWMiLA0KICAgICAgICBzdHJfZGV0ZWN0KGdlbnJlLCAiRHJhbWEiKSB+ICJEcmFtYSIsDQogICAgICAgIHN0cl9kZXRlY3QoZ2VucmUsICJBY3Rpb24iKSB+ICJBY3Rpb24iLA0KICAgICAgICBzdHJfZGV0ZWN0KGdlbnJlLCAiQ29tZWQiKSB+ICJDb21lZHkiLA0KICAgICAgICBzdHJfZGV0ZWN0KGdlbnJlLCAiQ3VsdCIpIH4gIkN1bHQiLA0KICAgICAgICBzdHJfZGV0ZWN0KGdlbnJlLCAiVGhyaWxsIikgfiAiVGhyaWxsZXIiLA0KICAgICAgICBzdHJfZGV0ZWN0KGdlbnJlLCAiVGVlbnxDaGlsZHJlbnxLaWRzIikgfiAiQ2hpbGRyZW4iLA0KICAgICAgICBzdHJfZGV0ZWN0KGdlbnJlLCAiU2NpZW5jZXxEb2N1IikgfiAiRG9jdW1lbnRhcnkiLA0KICAgICAgICBzdHJfZGV0ZWN0KGdlbnJlLCAiQW5pbWUiKSB+ICJBbmltZSIsDQogICAgICAgIHN0cl9kZXRlY3QoZ2VucmUsICJIb3Jyb3IiKSB+ICJIb3Jyb3IiLA0KICAgICAgICBzdHJfZGV0ZWN0KGdlbnJlLCAiSW5kZXBlbmRlbnQiKSB+ICJJbmRlcGVuZGVudCIsDQogICAgICAgIHN0cl9kZXRlY3QoZ2VucmUsICJDcmltZSIpIH4gIkNyaW1lIiwNCiAgICAgICAgc3RyX2RldGVjdChnZW5yZSwgIk11c2ljIikgfiAiTXVzaWMiLA0KICAgICAgICBzdHJfZGV0ZWN0KGdlbnJlLCAiQnJpdGlzaCIpIH4gIkJyaXRpc2giLA0KICAgICAgICBzdHJfZGV0ZWN0KGdlbnJlLCAiUmVhbGl0eSIpIH4gIlJlYWxpdHkiLA0KICAgICAgICBzdHJfZGV0ZWN0KGdlbnJlLCAiQ2xhc3NpYyIpIH4gIkNsYXNzaWMiLA0KICAgICAgICBzdHJfZGV0ZWN0KGdlbnJlLCAiTXlzdGVyIikgfiAiTXlzdGVyaWVzIiwNCiAgICAgICAgc3RyX2RldGVjdChnZW5yZSwgIkZhaXRoIikgfiAiRmFpdGgiLA0KICAgICAgICBUUlVFIH4gIk90aGVyIg0KICAgICAgKSkgJT4lIA0KICAgIGdyb3VwX2J5KHNob3dfaWQpICU+JSANCiAgICBtdXRhdGUocG9zaXRpb24gPSByb3dfbnVtYmVyKCkpICU+JSANCiAgICB1bmdyb3VwKCkgJT4lIA0KICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBwb3NpdGlvbiwNCiAgICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IGdlbnJlLA0KICAgICAgICAgICAgICAgIG5hbWVzX3ByZWZpeCA9ICJnZW5yZSIpICU+JSANCiAgICBtdXRhdGUoZ2VucmUxID0gYXMuZmFjdG9yKGdlbnJlMSkpDQoNCmBgYA0KDQpgYGB7ciBjb3VudF9nZW5yZXN9DQpuZXRmbGl4X2RmICU+JQ0KICBjb3VudChnZW5yZTEpICU+JSANCiAgbXV0YXRlKHBjdCA9IG4gLyBzdW0obikpICU+JSANCiAgZ2dwbG90KGFlcyhwY3QsIGZjdF9yZW9yZGVyKGdlbnJlMSwgcGN0KSkpICsNCiAgZ2VvbV9jb2woZmlsbCA9ICJsaWdodGJsdWUiKSArDQogIHNjYWxlX3hfY29udGludW91cyhsYWJlbHMgPSBzY2FsZXM6OnBlcmNlbnQpICsNCiAgZ2VvbV9sYWJlbChhZXMobGFiZWwgPSBzY2FsZXM6OnBlcmNlbnQocGN0LCBhY2N1cmFjeSA9IC4xKSkpICsNCiAgbGFicyh0aXRsZSA9ICJTZXZlcmUgSW1iYWxhbmNlIGluIE1lZGlhIEdlbnJlIENsYXNzaWZpY2F0aW9uIiwNCiAgICAgICB5ID0gTlVMTCwgeCA9IE5VTEwpDQpgYGANCg0KTGV04oCZcyBzcGxpdCB0aGUgZGF0YSB1c2luZyB0aGUgZGVmYXVsdCAzOjEgcmF0aW8gb2YgdHJhaW5pbmctdG8tdGVzdCBhbmQgcmVzYW1wbGUgdGhlIHRyYWluaW5nIHNldCB1c2luZyA1LWZvbGQgY3Jvc3MtdmFsaWRhdGlvbi4gVi1mb2xkIGNyb3NzLXZhbGlkYXRpb24gcmFuZG9tbHkgc3BsaXRzIHRoZSBkYXRhIGludG8gViBncm91cHMgKDUpIG9mIHJvdWdobHkgZXF1YWwgc2l6ZSAoY2FsbGVkICJmb2xkcyIpLiBBIHJlc2FtcGxlIG9mIHRoZSBhbmFseXNpcyBkYXRhIGNvbnNpc3RzIG9mIFYtMSAoNCkgb2YgdGhlIGZvbGRzIHdoaWxlIHRoZSBhc3Nlc3NtZW50IHNldCBjb250YWlucyB0aGUgZmluYWwgZm9sZC4gSW4gYmFzaWMgVi1mb2xkIGNyb3NzLXZhbGlkYXRpb24gKGkuZS4gbm8gcmVwZWF0cyksIHRoZSBudW1iZXIgb2YgcmVzYW1wbGVzIGlzIGVxdWFsIHRvIFYuDQoNCiMjIENyb3NzIFZhbGlkYXRpb24gey5hY3RpdmV9DQoNClRoZSBzdHJhdGEgYXJndW1lbnQgY2F1c2VzIHRoZSByYW5kb20gc2FtcGxpbmcgdG8gYmUgY29uZHVjdGVkIHdpdGhpbiB0aGUgc3RyYXRpZmljYXRpb24gdmFyaWFibGUsIGluIHRoaXMgY2FzZSBnZW5yZTEuIA0KDQpgYGB7ciBjcm9zc192YWxpZGF0aW9ufQ0Kc2V0LnNlZWQoMTUwMSkNCm5ldGZsaXhfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChuZXRmbGl4X2RmLCBzdHJhdGEgPSBnZW5yZTEpDQpuZXRmbGl4X3RyYWluIDwtIHRyYWluaW5nKG5ldGZsaXhfc3BsaXQpDQpuZXRmbGl4X3Rlc3QgIDwtIHRlc3RpbmcobmV0ZmxpeF9zcGxpdCkNCg0KbmV0ZmxpeF9mb2xkcyA8LSB2Zm9sZF9jdihuZXRmbGl4X3RyYWluLA0KICAgICAgICAgICAgICAgICAgICAgICAgICB2ID0gNSwgc3RyYXRhID0gZ2VucmUxKQ0KYGBgDQoNCmBgYHtyIHJlc2FtcGxpbmdfc3RyYXRlZ3l9DQpnZ3Bsb3QobmV0ZmxpeF9mb2xkcyAlPiUgIHRpZHkoKSwgDQogICAgICAgYWVzKEZvbGQsIFJvdywgZmlsbCA9IERhdGEpKSArDQogIGdlb21fdGlsZSgpICsNCiAgbGFicyhjYXB0aW9uID0gIlJlc2FtcGxpbmcgc3RyYXRlZ3kiKQ0KDQpgYGANCg0KT3VyIGRhdGFzZXQgd2lsbCBiZSBwcmUtcHJvY2Vzc2VkIHRocm91Z2ggc2V2ZXJhbCBkaWZmZXJlbnQgcmVjaXBlcyB0aGF0IGVuZ2luZWVyIHRleHQgZmVhdHVyZXMgYnkgdG9rZW5pemluZyB0aGUgZGVzY3JpcHRpb24sIGNhc3QsIGFuZCB0aGUgZGlyZWN0b3IgZmllbGRzLg0KDQpUaGUgZmlyc3QgcmVjaXBlIGFwcGxpZXMgZmlsdGVycyB0byB1c2UgdGhlIHdvcmRzLCBjYXN0IG1lbWJlcnMsIGFuZCBkaXJlY3RvciB3aXRoIHRoZSAzMCBoaWdoZXN0IHRlcm0gZnJlcXVlbmNpZXMgYWZ0ZXIgcmVtb3Zpbmcgc3RvcCB3b3JkcyBhbmQgc3RlbW1pbmcuDQoNClRoZSBzZWNvbmQgZmlsdGVyIHRoZSBkZXNjcmlwdGlvbiB3b3JkcyB0byByZW1vdmUgc3RvcHdvcmRzIGFuZCB0aGVuIGJ1aWxkcyAyNSB3b3JkIGVtYmVkZGluZ3Mgd2l0aCBbR2xvdmUgVHdpdHRlciAyN0JdKGh0dHBzOi8vd3d3LmFjbHdlYi5vcmcvYW50aG9sb2d5L0QxNC0xMTYyLykuIExlYXJuIG1vcmUgYWJvdXQgd29yZCBlbWJlZGRpbmdzIGFuZCBwcmUtdHJhaW5lZCBtb2RlbCB0cmFuc2ZlciBsZWFybmluZyBbaGVyZV0oaHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1vVXB1QUJLb0VsdykuIFRoZSByZWNpcGUgZ29lcyBvbiB0byBoYXNoIHRoZSBjYXN0IG1lbWJlciBhbmQgZGlyZWN0b3IgZmVhdHVyZXMgaW50byBudW1lcmljIHZhcmlhYmxlcyB3aXRoIHRoZSBoYXNoaW5nIHRyaWNrLiBBbGwgbW9kZWxzIGFyZSBjZW50ZXJlZCBhbmQgc2NhbGVkLiBUaGVyZSBhcmUgbm8gbWlzc2luZyB2YWx1ZXMuIA0KDQpXaXRoIGEgYmlnIG11bHRpLWNsYXNzIGNoYWxsZW5nZSwgd2UgbmVlZCB0byB0YWtlIGFjdGlvbiB0byBiYWxhbmNlIHRoZSBjbGFzc2VzLiBgc21vdGVgIHVwc2FtcGxlcyBtZW1iZXJzIG9mIHRoZSBtaW5vcml0eSBjbGFzc2VzIHdpdGggbmVhcmVzdCBuZWlnaGJvcnMgaW1wdXRhdGlvbiAoaW4gdGhlIHRyYWluaW5nIHNldCBvbmx5KS4gYHVwc2FtcGxpbmdgIHJlcGxpY2F0ZXMgcm93cyBvZiB0aGUgbWlub3JpdHkgY2xhc3NlcyB0byBtYXRjaCB0aGUgY291bnQgb2YgdGhlIG1lbWJlcnMgb2YgdGhlIGxhcmdlc3QgY2xhc3MgbWVtYmVyLiANCg0KIyMgUHJlLXByb2Nlc3NpbmcNCg0KYGBge3IgcmVjaXBlc30NCih0b2tlbl9yZWMgPC0gcmVjaXBlKGdlbnJlMSB+IGRlc2NyaXB0aW9uICsgY2FzdCArIGRpcmVjdG9yLCANCiAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBuZXRmbGl4X3RyYWluKSAlPiUNCiAgIyB1cHNhbXBsZSB0cmFpbmluZyBzZXQgc28gZWFjaCBjbGFzcyBoYXMgdGhlIHNhbWUgbnVtYmVyIG9mIHJvd3MNCiAgc3RlcF91cHNhbXBsZShnZW5yZTEpICU+JSANCiAgIyB3b3JrIHdpdGggZGVzY3JpcHRpb24gY2hhcmFjdGVyIGZpZWxkDQogIHN0ZXBfdG9rZW5pemUoZGVzY3JpcHRpb24pICU+JQ0KICBzdGVwX3N0b3B3b3JkcyhkZXNjcmlwdGlvbikgJT4lDQogIHN0ZXBfc3RlbShkZXNjcmlwdGlvbikgJT4lIA0KICBzdGVwX3dvcmRfZW1iZWRkaW5ncyhkZXNjcmlwdGlvbiwNCiAgICAgICAgICAgICAgICAgICAgICAgZW1iZWRkaW5ncyA9IHRleHRkYXRhOjplbWJlZGRpbmdfZ2xvdmUyN2IoKSkgJT4lIA0KICAjIHdvcmsgd2l0aCB0aGUgY2FzdCBtZW1iZXJzIGNoYXJhY3RlciBmaWVsZA0KICBzdGVwX3Rva2VuaXplKGNhc3QsDQogICAgICAgICAgICAgICAgdG9rZW4gPSAicmVnZXgiLA0KICAgICAgICAgICAgICAgIG9wdGlvbnMgPSBsaXN0KHBhdHRlcm4gPSAiLCAiKSkgJT4lDQogIHN0ZXBfdG9rZW5maWx0ZXIoY2FzdCwgbWF4X3Rva2VucyA9IDIwMCkgJT4lIA0KICBzdGVwX3RleHRoYXNoKGNhc3QsIG51bV90ZXJtcyA9IDYpICU+JSANCiAgIyB3b3JrIHdpdGggdGhlIGRpcmVjdG9yIG1lbWJlciBjaGFyYWN0ZXIgZmllbGQNCiAgc3RlcF90b2tlbml6ZShkaXJlY3RvciwNCiAgICAgICAgICAgICAgICB0b2tlbiA9ICJyZWdleCIsDQogICAgICAgICAgICAgICAgb3B0aW9ucyA9IGxpc3QocGF0dGVybiA9ICIsICIpKSAlPiUNCiAgc3RlcF90b2tlbmZpbHRlcihkaXJlY3RvciwgbWF4X3Rva2VucyA9IDIwMCkgJT4lIA0KICBzdGVwX3RleHRoYXNoKGRpcmVjdG9yLCBudW1fdGVybXMgPSAxMCkgJT4lIA0KICAjIG5vcm1hbGl6ZSBmb3IgYW55IGVuZ2luZSB3aXRoIHJlZ3VsYXJpemF0aW9uDQogIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljKCkpDQopDQoNCmBgYA0KDQojIyBUZXh0IEVtYmVkZGluZ3MgUmVjaXBlDQoNCkFuZCBhIGxvb2sgYXQgdGhlIGZlYXR1cmVzIG9mIHRoZSB1cHNhbXBsZWQgImVtYmVkZGluZ3MiIHJlY2lwZToNCg0KYGBge3IgZW1iZWRkaW5nc19yZWN9DQp0b2tlbl9yZWMgJT4lIA0KICBwcmVwKCkgJT4lIA0KICBiYWtlKG5ld19kYXRhID0gTlVMTCkgJT4lIA0KICBnbGltcHNlKCkNCmBgYA0KDQojIHstfQ0KDQojIE1hY2hpbmUgTGVhcm5pbmcgey50YWJzZXQgLnRhYnNldC1waWxsc30NCg0KRm9yIHRoZSBtb2RlbHMgdGhlbXNlbHZlcywgd2UgdXNlIHRoZSB0aGUgYHBhcnNuaXBgIHBhY2thZ2UgdG8gY3JlYXRlIHNldHMgb2YgbW9kZWwgc3BlY2lmaWNhdGlvbnMuICBBbGwgYXJlIHNldHVwIGZvciBtdWx0aS1jbGFzcyBjbGFzc2lmaWNhdGlvbi4gDQoNCmBgYHtyIG1hY2hpbmVfbGVhcm5pbmdfc3BlY3N9DQoNCmxvZ2lzdGljX3JlZ19nbG1uZXRfc3BlYyA8LQ0KICBtdWx0aW5vbV9yZWcocGVuYWx0eSA9IHR1bmUoKSwgDQogICAgICAgICAgICAgICBtaXh0dXJlID0gMC4wMSkgJT4lDQogIHNldF9lbmdpbmUoJ2dsbW5ldCcpICU+JSANCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikNCg0KbmJfc3BlYyA8LSBuYWl2ZV9CYXllcygpICU+JQ0KICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKSAlPiUNCiAgc2V0X2VuZ2luZSgibmFpdmViYXllcyIpDQoNCnN2bV9zcGVjIDwtDQogIHN2bV9saW5lYXIoKSAlPiUNCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikgJT4lDQogIHNldF9lbmdpbmUoIkxpYmxpbmVhUiIpDQoNCmBgYA0KDQojIyBXb3JrZmxvd3NldHMgDQoNClRoaXMgaXMgd2hlcmUgdGhlIGNvb2wgcGFydCBjb21lcyBpbjogV2Ugbm93IGRlZmluZSBhIOKAnHdvcmtmbG93IHNldC7igJ0gV2UgYXBwbHkgYGNyb3NzID0gVFJVRWAgdG8gbG9vayBhdCBhbGwgY29tYmluYXRpb25zIG9mIHJlY2lwZXMgYW5kIG1vZGVscy4gDQoNCmBgYHtyIHdvcmtmbG93c2V0c30NCih3ZnMgPC0NCiAgICAgd29ya2Zsb3dfc2V0KA0KICAgICAgIHByZXByb2MgPSBsaXN0KGVtYmVkZGluZ3MgPSB0b2tlbl9yZWMpLA0KICAgICAgIG1vZGVscyA9IGxpc3QoDQogICAgICAgICBuYl9zcGVjLCANCiAgICAgICAgIGxvZ2lzdGljX3JlZ19nbG1uZXRfc3BlYywNCiAgICAgICAgIHN2bV9zcGVjKQ0KICAgICkNCikNCg0KYGBgDQoNCkFuZCB3ZSBzZWUgd2UgaGF2ZSBvdXIgc2l4IGRpZmZlcmVudCB3b3JrZmxvd3Mgc2V0IHVwLiANCg0KIyMgVHVuaW5nIGFuZCBDcm9zcyBWYWxpZGF0aW9uDQoNClRoZSBlbmdpbmVzIGNob3NlbiBoZXJlIHJlcXVpcmUgbm8gdHVuaW5nLiBXZSB3aWxsIGxvYWQgcmVnaXN0ZXIgYSBwYXJhbGxlbCBiYWNrZW5kIHRvIHNwZWVkIHRoZSBjb21wdXRhdGlvbnMgYWNyb3NzIHRoZSBjcm9zcyB2YWxpZGF0aW9uIGZvbGRzLg0KDQpgYGB7ciB0dW5lX2dyaWQsIGV2YWwgPSBGQUxTRX0NCg0KYWxsX2NvcmVzIDwtIHBhcmFsbGVsbHk6OmF2YWlsYWJsZUNvcmVzKG9taXQgPSAxKQ0KZnV0dXJlOjpwbGFuKCJtdWx0aXNlc3Npb24iLCB3b3JrZXJzID0gYWxsX2NvcmVzKSAjIG9uIFdpbmRvd3MNCg0KY3RybCA8LQ0KICAgY29udHJvbF9ncmlkKA0KICAgICAgc2F2ZV9wcmVkID0gRkFMU0UsDQogICAgICBwYXJhbGxlbF9vdmVyID0gImV2ZXJ5dGhpbmciLA0KICAgICAgc2F2ZV93b3JrZmxvdyA9IEZBTFNFDQogICApDQoNCnN5c3RlbS50aW1lKA0KDQogICAgICBjdl9yZXMgPC0gd2ZzICU+JQ0KICAgICAgICB3b3JrZmxvd19tYXAoInR1bmVfZ3JpZCIsDQogICAgICAgICAgICAgICAgICAgICBzZWVkID0gMjAyMSwNCiAgICAgICAgICAgICAgICAgICAgIHJlc2FtcGxlcyA9IG5ldGZsaXhfZm9sZHMsDQogICAgICAgICAgICAgICAgICAgICBjb250cm9sID0gY3RybCwNCiAgICAgICAgICAgICAgICAgICAgIGdyaWQgPSAyMCwNCiAgICAgICAgICAgICAgICAgICAgIG1ldHJpY3MgPSBtZXRyaWNfc2V0KGFjY3VyYWN5LCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNlbnNpdGl2aXR5LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3BlY2lmaWNpdHksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBqX2luZGV4DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApLCANCiAgICAgICAgICAgICAgICAgICAgIHZlcmJvc2UgPSBUUlVFKQ0KKQ0KDQpmdXR1cmU6OnBsYW4oc3RyYXRlZ3kgPSAic2VxdWVudGlhbCIpDQoNCmBgYA0KDQpgYGB7ciB0dW5lX2dyaWQyX2V2YWwsIGluY2x1ZGUgPSBGQUxTRX0NCmlmIChmaWxlLmV4aXN0cyhoZXJlOjpoZXJlKCJkYXRhIiwibmV0ZmxpeFRpdGxlcy5ybWQiKSkpIHsNCmN2X3JlcyA8LSByZWFkX3JkcyhoZXJlOjpoZXJlKCJkYXRhIiwibmV0ZmxpeFRpdGxlcy5ybWQiKSkNCn0gZWxzZSB7DQoNCmFsbF9jb3JlcyA8LSBwYXJhbGxlbGx5OjphdmFpbGFibGVDb3JlcyhvbWl0ID0gMSkNCmZ1dHVyZTo6cGxhbigibXVsdGlzZXNzaW9uIiwgd29ya2VycyA9IGFsbF9jb3JlcykgIyBvbiBXaW5kb3dzDQoNCmN0cmwgPC0NCiAgIGNvbnRyb2xfZ3JpZCgNCiAgICAgIHNhdmVfcHJlZCA9IEZBTFNFLA0KICAgICAgcGFyYWxsZWxfb3ZlciA9ICJldmVyeXRoaW5nIiwNCiAgICAgIHNhdmVfd29ya2Zsb3cgPSBGQUxTRQ0KICAgKQ0KDQpzeXN0ZW0udGltZSgNCg0KICAgICAgY3ZfcmVzIDwtIHdmcyAlPiUNCiAgICAgICAgd29ya2Zsb3dfbWFwKCJ0dW5lX2dyaWQiLA0KICAgICAgICAgICAgICAgICAgICAgc2VlZCA9IDIwMjEsDQogICAgICAgICAgICAgICAgICAgICByZXNhbXBsZXMgPSBuZXRmbGl4X2ZvbGRzLA0KICAgICAgICAgICAgICAgICAgICAgY29udHJvbCA9IGN0cmwsDQogICAgICAgICAgICAgICAgICAgICBncmlkID0gMjAsDQogICAgICAgICAgICAgICAgICAgICBtZXRyaWNzID0gbWV0cmljX3NldChhY2N1cmFjeSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZW5zaXRpdml0eSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNwZWNpZmljaXR5LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgal9pbmRleA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKSwgDQogICAgICAgICAgICAgICAgICAgICB2ZXJib3NlID0gVFJVRSkNCikNCg0KZnV0dXJlOjpwbGFuKHN0cmF0ZWd5ID0gInNlcXVlbnRpYWwiKQ0Kd3JpdGVfcmRzKGN2X3JlcywgaGVyZTo6aGVyZSgiZGF0YSIsIm5ldGZsaXhUaXRsZXMucm1kIikpDQp9DQpgYGANCg0KYGBgDQppCU5vIHR1bmluZyBwYXJhbWV0ZXJzLiBgZml0X3Jlc2FtcGxlcygpYCB3aWxsIGJlIGF0dGVtcHRlZA0KaSAxIG9mIDMgcmVzYW1wbGluZzogZW1iZWRkaW5nc19uYWl2ZV9CYXllcw0KdiAxIG9mIDMgcmVzYW1wbGluZzogZW1iZWRkaW5nc19uYWl2ZV9CYXllcyAoMzAuOHMpDQppIDIgb2YgMyB0dW5pbmc6ICAgICBlbWJlZGRpbmdzX211bHRpbm9tX3JlZw0KdiAyIG9mIDMgdHVuaW5nOiAgICAgZW1iZWRkaW5nc19tdWx0aW5vbV9yZWcgKDJtIDI5LjJzKQ0KaQlObyB0dW5pbmcgcGFyYW1ldGVycy4gYGZpdF9yZXNhbXBsZXMoKWAgd2lsbCBiZSBhdHRlbXB0ZWQNCmkgMyBvZiAzIHJlc2FtcGxpbmc6IGVtYmVkZGluZ3Nfc3ZtX2xpbmVhcg0KdiAzIG9mIDMgcmVzYW1wbGluZzogZW1iZWRkaW5nc19zdm1fbGluZWFyICg0bSAyNy4xcykNCiAgIHVzZXIgIHN5c3RlbSBlbGFwc2VkIA0KIDQ0OC4yMyAgICAwLjMxICA0NDkuMDMgDQpgYGANCg0KTm90ZSB0aGUgcnVuIHRpbWVzLg0KDQojIyBDb21wYXJlIHRoZSBtb2RlbHMgDQoNCldlIGNhbiB0aGVuIGxvb2sgYXQgdGhlIHJlc3VsdHMuIFdoYXQgYXJlIHRoZSB0b3Agd29ya2Zsb3dzPw0KDQpgYGB7cn0NCmF1dG9wbG90KGN2X3JlcykNCg0KY3ZfcmVzICU+JSANCiAgcmFua19yZXN1bHRzKHNlbGVjdF9iZXN0ID0gVFJVRSkgJT4lIA0KICBwaXZvdF93aWRlcihpZF9jb2xzID0gd2Zsb3dfaWQsDQogICAgICAgICAgICAgIG5hbWVzX2Zyb20gPSAubWV0cmljLA0KICAgICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IG1lYW4pICU+JSANCiAgc2VsZWN0KHdmbG93X2lkLCBhY2N1cmFjeSwgal9pbmRleCwgc2Vucywgc3BlYykNCg0KYGBgDQoNClNvIHRoZSBHTE1uZXQgcnVucyBmYXN0IGFuZCBkZWxpdmVycyB0aGUgYmVzdCByZXN1bHRzIG9mIHRoZSB0aHJlZSBlbmdpbmVzIHVzZWQuICBMZXQncyBzZWUgd2hhdCB3ZSBjYW4gZG8gdG8gaW1wcm92ZSBpdCwgc3RpbGwgdXNpbmcgdGhlIHNhbWUgcmVzYW1wbGUgZm9sZHMuDQoNCmBgYHtyIEdMTW5ldF9ub2V2YWwsIGV2YWw9RkFMU0V9DQpsb2dpc3RpY19yZWdfZ2xtbmV0X3NwZWMgPC0NCiAgbXVsdGlub21fcmVnKHBlbmFsdHkgPSB0dW5lKCksIA0KICAgICAgICAgICAgICAgbWl4dHVyZSA9IHR1bmUoKSkgJT4lDQogIHNldF9lbmdpbmUoJ2dsbW5ldCcpICU+JSANCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikNCg0KZ2xtX3dmIDwtIHdvcmtmbG93KHRva2VuX3JlYywgbG9naXN0aWNfcmVnX2dsbW5ldF9zcGVjKQ0KDQpjdHJsIDwtDQogICBjb250cm9sX2dyaWQoDQogICAgICBzYXZlX3ByZWQgPSBUUlVFLA0KICAgICAgcGFyYWxsZWxfb3ZlciA9ICJldmVyeXRoaW5nIiwNCiAgICAgIHNhdmVfd29ya2Zsb3cgPSBGQUxTRQ0KICAgKQ0KDQphbGxfY29yZXMgPC0gcGFyYWxsZWxseTo6YXZhaWxhYmxlQ29yZXMob21pdCA9IDEpDQpmdXR1cmU6OnBsYW4oIm11bHRpc2Vzc2lvbiIsIHdvcmtlcnMgPSBhbGxfY29yZXMpICMgb24gV2luZG93cw0KDQoNCmN2X2dsbW5ldCA8LSBnbG1fd2YgJT4lIA0KICAgICAgICB0dW5lX2dyaWQocmVzYW1wbGVzID0gbmV0ZmxpeF9mb2xkcywNCiAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBjdHJsLA0KICAgICAgICAgICAgICAgICAgZ3JpZCA9IDIwLA0KICAgICAgICAgICAgICAgICAgbWV0cmljcyA9IG1ldHJpY19zZXQocm9jX2F1YywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjY3VyYWN5LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2Vuc2l0aXZpdHksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGVjaWZpY2l0eSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1uX2xvZ19sb3NzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApDQopDQoNCmZ1dHVyZTo6cGxhbihzdHJhdGVneSA9ICJzZXF1ZW50aWFsIikNCg0KYXV0b3Bsb3QoY3ZfZ2xtbmV0KQ0KDQpgYGANCg0KYGBge3IgR0xNbmV0X2V2YWwsIGluY2x1ZGU9RkFMU0V9DQpsb2dpc3RpY19yZWdfZ2xtbmV0X3NwZWMgPC0NCiAgbXVsdGlub21fcmVnKHBlbmFsdHkgPSB0dW5lKCksIA0KICAgICAgICAgICAgICAgbWl4dHVyZSA9IHR1bmUoKSkgJT4lDQogIHNldF9lbmdpbmUoJ2dsbW5ldCcpICU+JSANCiAgc2V0X21vZGUoImNsYXNzaWZpY2F0aW9uIikNCg0KZ2xtX3dmIDwtIHdvcmtmbG93KHRva2VuX3JlYywgbG9naXN0aWNfcmVnX2dsbW5ldF9zcGVjKQ0KDQpjdHJsIDwtDQogICBjb250cm9sX2dyaWQoDQogICAgICBzYXZlX3ByZWQgPSBUUlVFLA0KICAgICAgcGFyYWxsZWxfb3ZlciA9ICJldmVyeXRoaW5nIiwNCiAgICAgIHNhdmVfd29ya2Zsb3cgPSBGQUxTRQ0KICAgKQ0KDQppZiAoZmlsZS5leGlzdHMoaGVyZTo6aGVyZSgiZGF0YSIsIm5ldGZsaXhUaXRsZXMyLnJkcyIpKSkgew0KY3ZfZ2xtbmV0IDwtIHJlYWRfcmRzKGhlcmU6OmhlcmUoImRhdGEiLCJuZXRmbGl4VGl0bGVzMi5yZHMiKSkgDQp9IGVsc2Ugew0KDQphbGxfY29yZXMgPC0gcGFyYWxsZWxseTo6YXZhaWxhYmxlQ29yZXMob21pdCA9IDEpDQpmdXR1cmU6OnBsYW4oIm11bHRpc2Vzc2lvbiIsIHdvcmtlcnMgPSBhbGxfY29yZXMpICMgb24gV2luZG93cw0KDQoNCmN2X2dsbW5ldCA8LSBnbG1fd2YgJT4lIA0KICAgICAgICB0dW5lX2dyaWQocmVzYW1wbGVzID0gbmV0ZmxpeF9mb2xkcywNCiAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBjdHJsLA0KICAgICAgICAgICAgICAgICAgZ3JpZCA9IDIwLA0KICAgICAgICAgICAgICAgICAgbWV0cmljcyA9IG1ldHJpY19zZXQocm9jX2F1YywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFjY3VyYWN5LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2Vuc2l0aXZpdHksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzcGVjaWZpY2l0eSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1uX2xvZ19sb3NzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApDQopDQoNCmZ1dHVyZTo6cGxhbihzdHJhdGVneSA9ICJzZXF1ZW50aWFsIikNCg0Kd3JpdGVfcmRzKGN2X2dsbW5ldCwgaGVyZTo6aGVyZSgiZGF0YSIsIm5ldGZsaXhUaXRsZXMyLnJkcyIpKQ0KfQ0KDQphdXRvcGxvdChjdl9nbG1uZXQpDQoNCmBgYA0KDQpOb3QgYmFkLiBMZXQncyBsb29rIG1vcmUgY2xvc2VseSBhdCBwZXJmb3JtYW5jZSBhY3Jvc3MgY3Jvc3MgdmFsaWRhdGlvbiBmb2xkcyBhbmQgcHJlZGljdGlvbiBjbGFzc2VzIG5leHQuDQoNCiMjIFBlcmZvcm1hbmNlIHsuYWN0aXZlfQ0KDQpXZSBjYW4gbG9vayBhdCBST0MgY3VydmVzIGJ5IGdlbnJlIGNsYXNzIGZvciB0aGUgc2V0IG9mIDUgY3Jvc3MtdmFsaWRhdGlvbiBmb2xkcyBvZiB0aGUgYmVzdCBtb2RlbDoNCg0KYGBge3IgUk9DX2N1cnZlcywgZmlnLmFzcD0xfQ0KY3ZfZ2xtbmV0ICU+JSANCiAgY29sbGVjdF9wcmVkaWN0aW9ucygpICU+JSANCiAgZ3JvdXBfYnkoaWQpICU+JQ0KICByb2NfY3VydmUodHJ1dGggPSBnZW5yZTEsIC5wcmVkX0FjdGlvbjoucHJlZF9UaHJpbGxlcikgJT4lDQogIGF1dG9wbG90KCkgKw0KICBsYWJzKA0KICAgIGNvbG9yID0gTlVMTCwNCiAgICB0aXRsZSA9IGdsdWU6OmdsdWUoIlJPQyBjdXJ2ZSBmb3IgTmV0ZmxpeCBNb3ZpZSBHTE1uZXQgUHJlZGljdG9yIiksDQogICAgc3VidGl0bGUgPSAiRWFjaCByZXNhbXBsZSBmb2xkIGlzIHNob3duIGluIGEgZGlmZmVyZW50IGNvbG9yIg0KICApDQpgYGANCg0KVGhpcyBtb2RlbCBwcmVkaWN0cyBEb2N1bWVudGFyaWVzIGFuZCBJbnRlcm5hdGlvbmFsIGZpbG1zIHZlcnkgd2VsbC4gT24gdGhlIG90aGVyIGV4dHJlbWUsIFJvbWFudGljIE1vdmllcyBhbmQgIk90aGVyIiBhcmUgbm90IHByZWRpY3RlZCB3ZWxsIGluIGV2ZXJ5IGNyb3NzIHZhbGlkYXRpb24gZm9sZC4NCg0KV2UgY2FuIGFsc28gY3JlYXRlIGEgY29uZnVzaW9uIG1hdHJpeCBmcm9tIHRoZSByZXNhbXBsZXMgdXNpbmcgYGNvbmZfbWF0X3Jlc2FtcGxlZCgpYCwgd2hpY2ggY29tcHV0ZXMgYSBzZXBhcmF0ZSBjb25mdXNpb24gbWF0cml4IGZvciBlYWNoIHJlc2FtcGxlIGFuZCB0aGVuIGF2ZXJhZ2VzIHRoZSBjZWxsIGNvdW50cy4NCg0KYGBge3IgY29uZnVzaW9uX21hdHJpeCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLmFzcD0xfQ0Kc2hvd19iZXN0KGN2X2dsbW5ldCwgbWV0cmljID0gIm1uX2xvZ19sb3NzIikgJT4lIA0KICBzZWxlY3QoLS5lc3RpbWF0b3IpDQoNCnNob3dfYmVzdChjdl9nbG1uZXQsIG1ldHJpYyA9ICJyb2NfYXVjIikgJT4lIA0KICBzZWxlY3QoLS5lc3RpbWF0b3IpDQoNCmN2X2dsbW5ldCAlPiUgDQogIGNvbmZfbWF0X3Jlc2FtcGxlZCh0aWR5ID0gRkFMU0UsDQogICAgICAgICAgICAgICAgICAgICBwYXJhbWV0ZXJzID0gc2VsZWN0X2Jlc3QoY3ZfZ2xtbmV0LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldHJpYyA9ICJtbl9sb2dfbG9zcyIpKSAlPiUgDQogIGF1dG9wbG90KHR5cGUgPSAiaGVhdG1hcCIpICsgDQogIGxhYnModGl0bGUgPSAiQ29uZnVzaW9uIE1hdHJpeCwgYWxsIHJlc2FtcGxlcywgYmVzdCBtbl9sb2dfbG9zcyByZXN1bHRzIikNCiAgDQpgYGANCg0KR2VuZXJhbGx5LCBpdCBpcyBhIGdvb2QgaWRlYSB0byBldmFsdWF0ZSB0aGUgbW9kZWxzIG92ZXIgbXVsdGlwbGUgbWV0cmljcyBzbyB0aGF0IGRpZmZlcmVudCBhc3BlY3RzIG9mIHRoZSBtb2RlbCBmaXQgYXJlIHRha2VuIGludG8gYWNjb3VudC4gQWxzbywgaXQgb2Z0ZW4gbWFrZXMgc2Vuc2UgdG8gY2hvb3NlIGEgc2xpZ2h0bHkgc3Vib3B0aW1hbCBwYXJhbWV0ZXIgY29tYmluYXRpb24gdGhhdCBpcyBhc3NvY2lhdGVkIHdpdGggYSBzaW1wbGVyIG1vZGVsLiBGb3IgdGhpcyBtb2RlbCwgc2ltcGxpY2l0eSBjb3JyZXNwb25kcyB0byBsYXJnZXIgcGVuYWx0eSB2YWx1ZXMuDQoNCmBgYHtyfQ0KYmVzdF9yZXN1bHRzIDwtIGN2X2dsbW5ldCAlPiUgDQogIHNlbGVjdF9iZXN0KG1ldHJpYyA9ICJtbl9sb2dfbG9zcyIpDQoNCnRlc3RfcmVzdWx0cyA8LSBnbG1fd2YgJT4lIA0KICBmaW5hbGl6ZV93b3JrZmxvdyhiZXN0X3Jlc3VsdHMpICU+JSANCiAgbGFzdF9maXQoc3BsaXQgPSBuZXRmbGl4X3NwbGl0KQ0KDQpgYGANCg0KVGhlIHRlc3QgcmVzdWx0cyBzaG93Og0KDQpgYGB7cn0NCmNvbGxlY3RfbWV0cmljcyh0ZXN0X3Jlc3VsdHMpDQoNCmBgYA0KDQpUaGlzIGlzIGEgZ29vZCB0ZXN0IHJlc3VsdCwgYXMgdGhlIG1vZGVsIHJ1biBvbiB1bnNlZW4gZGF0YSBkZWxpdmVycyBwZXJmb3JtYW5jZSBjb21wYXJhYmxlIHRvIHRoZSB0cmFpbmluZyBmaWd1cmVzLCBzbyBkb2VzIG5vdCBvdmVyLWZpdC4gRXZlbiBzbywgSSdkIGxpa2UgdG8gZXhwbG9yZSBvdGhlciBtYWNoaW5lIGxlYXJuaW5nIGVuZ2luZXMsIGluY2x1ZGluZyBkZWVwIGxlYXJuaW5nLCB0byBpbXByb3ZlIGNsYXNzaWZpY2FpdG9uIGFjY3VyYWN5Lg0KDQoNCg0KDQo=