SYSTEM SETTING

Import Packages

#| label: setup
#| warning: false
#| message: false
set.seed(seed = 2023)

library(tidyverse)
library(here)
library(tidymodels)
library(torch)
# library(tabnet)
devtools::load_all()
library(data.tree)
# library(tidyverse)
#may take 3 mins
source(here("tools/setup.R"))

NODE_RESERVED_NAMES_CONST

replace reserved_names

check_compliant_node(attrition_tree)
check_compliant_node(starwars_tree)

fit a multi-outcome classifier

library(tabnet)
library(recipes)
starwars_rec <-  recipe(species + homeworld ~ ., data = starwars %>%
                          select(-where(is.list)) |> 
                          mutate(species = coalesce(species, "Unknown_Species"),
           homeworld     = coalesce(homeworld, "Unknown_homeworld"))) %>%
    step_zv(all_predictors()) %>%
    step_impute_mode(all_outcomes()) %>%
    step_string2factor(all_string_predictors(), all_outcomes())

fit_multi <- tabnet_fit(starwars_rec, starwars, cat_emb_dim = 2)

prepare Node objects

#| eval: true
starw_split <- starwars %>% 
  tidyr::unnest_longer(films) %>% 
  tidyr::unnest_longer(vehicles, keep_empty = TRUE) %>% 
  tidyr::unnest_longer(starships, keep_empty = TRUE) %>% 
  initial_split( prop = .8, strata = "species")

starwars_train_tree <- starw_split %>% 
  training() %>% 
  # avoid reserved column names
  rename(`_name` = "name", `_height` = "height") %>% 
  rowid_to_column() %>% 
    mutate(species = coalesce(species, "Unknown_Species"),
           sex     = coalesce(sex, "Unknown_Sex"),
           pathString = paste("StarWars_characters", species, sex, `_name`, sep = "/")) %>%
  # remove outcomes labels from predictors
  select(-species, -sex, -`_name`, -rowid) %>% 
  # turn it as hierarchical Node
  as.Node()

starwars_test_tree <- starw_split %>% 
  testing() %>% 
  rename(`_name` = "name", `_height` = "height") %>% 
  rowid_to_column() %>% 
  mutate(pathString = paste("StarWars_characters", species, sex, rowid, sep = "/")) %>%
  select(-species, -sex, -`_name`, -rowid) %>% 
  as.Node() # [77 entries, 11 attributes]

starwars_test_tree$attributesAll
 [1] "_height"    "birth_year" "eye_color"  "films"      "gender"     "hair_color" "homeworld" 
 [8] "mass"       "skin_color" "starships"  "vehicles"  
#| eval: true

attrition_tree <- attrition %>% 
  rowid_to_column() %>% 
  mutate(pathString = paste("attrition", Department, JobRole, rowid, sep = "/")) %>%
  as.Node()
print(attrition_tree, "Age", "Education","EducationField", "DailyRate",  limit = 12)
                            levelName Age Education EducationField DailyRate
1  attrition                           NA        NA             NA        NA
2   ¦--Sales                           NA        NA             NA        NA
3   ¦   ¦--Sales_Executive             NA        NA             NA        NA
4   ¦   ¦   ¦--1                       41         2              2      1102
5   ¦   ¦   ¦--28                      42         4              3       691
6   ¦   ¦   ¦--40                      33         3              2      1141
7   ¦   ¦   ¦--44                      27         3              2       994
8   ¦   ¦   ¦--47                      34         4              3      1065
9   ¦   ¦   ¦--49                      46         4              3      1211
10  ¦   ¦   ¦--53                      44         5              3      1488
11  ¦   ¦   ¦--55                      26         3              3      1443
12  ¦   ¦   °--... 318 nodes w/ 0 sub  NA        NA             NA        NA
13  ¦   °--... 2 nodes w/ 438 sub      NA        NA             NA        NA
14  °--... 2 nodes w/ 1472 sub         NA        NA             NA        NA

compute torch sparse ancestor matrix for attrition_tree

#|label: "hardhat.R L160-L170"
x <- attrition_tree$clone(deep = FALSE)

# ensure there is no level_* col in the Node object
check_compliant_node(x)
# get tree leaves and extract attributes into data.frames
xy_df <- node_to_df(x)
processed <- hardhat::mold(xy_df$x, xy_df$y)
# Given n classes, ancestor is an (n x n) matrix where ancestor_ij = 1 if class i is descendant of class j
# ancestor_tt is the torch_tensor of ancestor

ancestor_tt <- build_ancestor_matrix_from_outcomes(
  x = x,
  outcomes = processed$outcomes,
  device = "cpu"
)
 # [1 12 12]
levels <- sapply(data.tree::Traverse(x), `[[`, "level")
table(levels)
#|label: "hardhat.R  L369-L370"
# tabnet_bridge(processed, config = config, tabnet_model, from_epoch, task = "supervised") -->
config <- tabnet_config(epochs = 2)
config$ancestor <- ancestor_tt # class "sparse_coo_tensor"
config$outcomes <- processed$outcomes
tabnet_model_lst <- tabnet:::tabnet_initialize(processed$predictors, processed$outcomes, config = config)
tabnet_model <-  tabnet:::new_tabnet_fit(tabnet_model_lst, blueprint = processed$blueprint)

fit_lst <- tabnet:::tabnet_train_supervised(tabnet_model, processed$predictors, processed$outcomes, config = config) # Fails with 
#> ! Erreur : mat1 and mat2 shapes cannot be multiplied (1470x2 and 1470x12)
#> Exception raised from meta at /pytorch/aten/src/ATen/native/LinearAlgebra.cpp:204 (most recent call first):
#|label: "model_training.R  L460"

obj <- tabnet_model
x <- processed$predictors
y <- processed$outcomes
train_ds <-   torch::dataset(
  initialize = function() {},
  .getbatch = function(batch) {resolve_data(x[batch,], y[batch,])},
  .length = function() {nrow(x)}
)()
train_dl <- torch::dataloader(
  train_ds,
  batch_size = config$batch_size,
  drop_last = config$drop_last,
  shuffle = TRUE ,
  num_workers = config$num_workers
)
# ... to be continued ...
#|label: "model_training.R train_batch L285"
epoch <- 1
iter <- dataloader_make_iter(train_dl)
batch <- dataloader_next(iter)

c(output, M_loss) %<-% network(batch$x, batch$x_na_mask)
outcome_nlevels <- as.numeric(batch$output_dim$to(device="cpu")) # c(3, 9)
  
config$loss_fn(output,  class_ids_to_binary(
      y = batch$y,
      outcomes = config$outcomes,
      device = out$device
    ))
)
#|label: "loss.R nnf_mc_loss L140"

target <- batch$y
R <- config$ancestor

compute torch sparse ancestor matrix for starwars_tree

#|label: "hardhat.R L160-L170"
x <- starwars_tree$clone(deep = FALSE)

# ensure there is no level_* col in the Node object
check_compliant_node(x)
# get tree leaves and extract attributes into data.frames
xy_df <- node_to_df(x)
processed <- hardhat::mold(xy_df$x, xy_df$y)
# Given n classes, ancestor is an (n x n) matrix where ancestor_ij = 1 if class i is descendant of class j
# ancestor_tt is the `R` torch_tensor of ancestor

ancestor_tt <- build_ancestor_matrix_from_outcomes(
  x = x,
  outcomes = processed$outcomes,
  device = "cpu"
) # [1 43 43]
levels <- sapply(data.tree::Traverse(x), `[[`, "level")
table(levels)

quand tabnet_fit() marche

fit <- tabnet_fit(attrition_tree, cat_emb_dim = 2)
predict(fit, attrition_tree)
# Erreur dans switch(object$spec$mode, regression = "numeric", classification = "class",  : 
#   EXPR doit être un vecteur de longueur 1

develop tabnet:::tabnet_fit.Node()

x <- starwars_tree
# TODO check there is no level_* col in the tree
  # extract attributes data.frame
  xy_df <- ToDataFrameTypeCol(x, x$attributesAll)
  x_df <- xy_df %>% select(-starts_with("level_"))
  y_df <- xy_df %>% select(starts_with("level_"))
  processed <- hardhat::mold(x_df, y_df)
  # embed the M matrix in Sextra
  ancestor <- ToDataFrameNetwork(datatree) %>%
    mutate_if(is.character, . %>% as.factor %>% as.numeric) 
  processed$extra$M <- Matrix::sparseMatrix(ancestor$from, ancestor$to, x = 1)
  check_type(processed$outcomes)

a yarr sourced file

library(yarr)

# Error   Invalid type specification.
# yeast_train <- foreign::read.arff("~/_Data.science/C-HMCNN-master/HMC_data/others/D0_yeast_GO.trainvalid.arff")
yeast_train_raw <- yarr::read.arff("~/_Data.science/C-HMCNN-master/HMC_data/others/D0_yeast_GO.trainvalid.arff")
yeast_train <- yeast_train_raw %>% mutate_all(as.numeric) %>% mutate_all(as.logical)

another yarr sourced file

library(yarr)
enron_train <- yarr::read.arff("~/_Data.science/C-HMCNN-master/HMC_data/others/Enron_corr_trainvalid.arff")
enron_test <- yarr::read.arff("~/_Data.science/C-HMCNN-master/HMC_data/others/Enron_corr_test.arff")
LS0tCnRpdGxlOiAiaW52ZXN0aWdhdGluZyBpc3N1ZSAxMDQiCm91dHB1dDogaHRtbF9ub3RlYm9vawp0bGRyOiBzZWUgaHR0cHM6Ly9naXRodWIuY29tL21sdmVyc2UvdGFibmV0L2lzc3Vlcy8xMTQKLS0tCgoKIyBTWVNURU0gU0VUVElORwojIyBJbXBvcnQgUGFja2FnZXMKYGBge3J9CiN8IGxhYmVsOiBzZXR1cAojfCB3YXJuaW5nOiBmYWxzZQojfCBtZXNzYWdlOiBmYWxzZQpzZXQuc2VlZChzZWVkID0gMjAyMykKCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGhlcmUpCmxpYnJhcnkodGlkeW1vZGVscykKbGlicmFyeSh0b3JjaCkKIyBsaWJyYXJ5KHRhYm5ldCkKZGV2dG9vbHM6OmxvYWRfYWxsKCkKbGlicmFyeShkYXRhLnRyZWUpCiMgbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKCmBgYHtyIH0KI3wgZXZhbDogZmFsc2UKI21heSB0YWtlIDMgbWlucwpzb3VyY2UoaGVyZSgidG9vbHMvc2V0dXAuUiIpKQpgYGAKTk9ERV9SRVNFUlZFRF9OQU1FU19DT05TVAoKIyMgcmVwbGFjZSByZXNlcnZlZF9uYW1lcwoKYGBge3J9CiN8IGV2YWw6IGZhbHNlCmNoZWNrX2NvbXBsaWFudF9ub2RlKGF0dHJpdGlvbl90cmVlKQpjaGVja19jb21wbGlhbnRfbm9kZShzdGFyd2Fyc190cmVlKQpgYGAKIyMgZml0IGEgbXVsdGktb3V0Y29tZSBjbGFzc2lmaWVyCgpgYGB7cn0KI3wgZXZhbDogZmFsc2UKI3wgbGFiZWw6IFJlcHJleCBmb3IgaHR0cHM6Ly9naXRodWIuY29tL21sdmVyc2UvdGFibmV0L2lzc3Vlcy8xMjUKbGlicmFyeSh0YWJuZXQpCmxpYnJhcnkocmVjaXBlcykKc3RhcndhcnNfcmVjIDwtICByZWNpcGUoc3BlY2llcyArIGhvbWV3b3JsZCB+IC4sIGRhdGEgPSBzdGFyd2FycyAlPiUKICAgICAgICAgICAgICAgICAgICAgICAgICBzZWxlY3QoLXdoZXJlKGlzLmxpc3QpKSB8PiAKICAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUoc3BlY2llcyA9IGNvYWxlc2NlKHNwZWNpZXMsICJVbmtub3duX1NwZWNpZXMiKSwKICAgICAgICAgICBob21ld29ybGQgICAgID0gY29hbGVzY2UoaG9tZXdvcmxkLCAiVW5rbm93bl9ob21ld29ybGQiKSkpICU+JQogICAgc3RlcF96dihhbGxfcHJlZGljdG9ycygpKSAlPiUKICAgIHN0ZXBfaW1wdXRlX21vZGUoYWxsX291dGNvbWVzKCkpICU+JQogICAgc3RlcF9zdHJpbmcyZmFjdG9yKGFsbF9zdHJpbmdfcHJlZGljdG9ycygpLCBhbGxfb3V0Y29tZXMoKSkKCmZpdF9tdWx0aSA8LSB0YWJuZXRfZml0KHN0YXJ3YXJzX3JlYywgc3RhcndhcnMsIGNhdF9lbWJfZGltID0gMikKYGBgCgojIyBwcmVwYXJlIE5vZGUgb2JqZWN0cwpgYGB7cn0KI3wgZXZhbDogdHJ1ZQpzdGFyd19zcGxpdCA8LSBzdGFyd2FycyAlPiUgCiAgdGlkeXI6OnVubmVzdF9sb25nZXIoZmlsbXMpICU+JSAKICB0aWR5cjo6dW5uZXN0X2xvbmdlcih2ZWhpY2xlcywga2VlcF9lbXB0eSA9IFRSVUUpICU+JSAKICB0aWR5cjo6dW5uZXN0X2xvbmdlcihzdGFyc2hpcHMsIGtlZXBfZW1wdHkgPSBUUlVFKSAlPiUgCiAgaW5pdGlhbF9zcGxpdCggcHJvcCA9IC44LCBzdHJhdGEgPSAic3BlY2llcyIpCgpzdGFyd2Fyc190cmFpbl90cmVlIDwtIHN0YXJ3X3NwbGl0ICU+JSAKICB0cmFpbmluZygpICU+JSAKICAjIGF2b2lkIHJlc2VydmVkIGNvbHVtbiBuYW1lcwogIHJlbmFtZShgX25hbWVgID0gIm5hbWUiLCBgX2hlaWdodGAgPSAiaGVpZ2h0IikgJT4lIAogIHJvd2lkX3RvX2NvbHVtbigpICU+JSAKICAgIG11dGF0ZShzcGVjaWVzID0gY29hbGVzY2Uoc3BlY2llcywgIlVua25vd25fU3BlY2llcyIpLAogICAgICAgICAgIHNleCAgICAgPSBjb2FsZXNjZShzZXgsICJVbmtub3duX1NleCIpLAogICAgICAgICAgIHBhdGhTdHJpbmcgPSBwYXN0ZSgiU3RhcldhcnNfY2hhcmFjdGVycyIsIHNwZWNpZXMsIHNleCwgYF9uYW1lYCwgc2VwID0gIi8iKSkgJT4lCiAgIyByZW1vdmUgb3V0Y29tZXMgbGFiZWxzIGZyb20gcHJlZGljdG9ycwogIHNlbGVjdCgtc3BlY2llcywgLXNleCwgLWBfbmFtZWAsIC1yb3dpZCkgJT4lIAogICMgdHVybiBpdCBhcyBoaWVyYXJjaGljYWwgTm9kZQogIGFzLk5vZGUoKQoKc3RhcndhcnNfdGVzdF90cmVlIDwtIHN0YXJ3X3NwbGl0ICU+JSAKICB0ZXN0aW5nKCkgJT4lIAogIHJlbmFtZShgX25hbWVgID0gIm5hbWUiLCBgX2hlaWdodGAgPSAiaGVpZ2h0IikgJT4lIAogIHJvd2lkX3RvX2NvbHVtbigpICU+JSAKICBtdXRhdGUocGF0aFN0cmluZyA9IHBhc3RlKCJTdGFyV2Fyc19jaGFyYWN0ZXJzIiwgc3BlY2llcywgc2V4LCByb3dpZCwgc2VwID0gIi8iKSkgJT4lCiAgc2VsZWN0KC1zcGVjaWVzLCAtc2V4LCAtYF9uYW1lYCwgLXJvd2lkKSAlPiUgCiAgYXMuTm9kZSgpICMgWzc3IGVudHJpZXMsIDExIGF0dHJpYnV0ZXNdCgpzdGFyd2Fyc190ZXN0X3RyZWUkYXR0cmlidXRlc0FsbAoKYGBgCgpgYGB7cn0KI3wgZXZhbDogdHJ1ZQoKYXR0cml0aW9uX3RyZWUgPC0gYXR0cml0aW9uICU+JSAKICByb3dpZF90b19jb2x1bW4oKSAlPiUgCiAgbXV0YXRlKHBhdGhTdHJpbmcgPSBwYXN0ZSgiYXR0cml0aW9uIiwgRGVwYXJ0bWVudCwgSm9iUm9sZSwgcm93aWQsIHNlcCA9ICIvIikpICU+JQogIGFzLk5vZGUoKQpwcmludChhdHRyaXRpb25fdHJlZSwgIkFnZSIsICJFZHVjYXRpb24iLCJFZHVjYXRpb25GaWVsZCIsICJEYWlseVJhdGUiLCAgbGltaXQgPSAxMikKYGBgCiMgIGNvbXB1dGUgdG9yY2ggc3BhcnNlIGFuY2VzdG9yIG1hdHJpeCBmb3IgYXR0cml0aW9uX3RyZWUKYGBge3J9CiN8bGFiZWw6ICJoYXJkaGF0LlIgTDE2MC1MMTcwIgp4IDwtIGF0dHJpdGlvbl90cmVlJGNsb25lKGRlZXAgPSBGQUxTRSkKCiMgZW5zdXJlIHRoZXJlIGlzIG5vIGxldmVsXyogY29sIGluIHRoZSBOb2RlIG9iamVjdApjaGVja19jb21wbGlhbnRfbm9kZSh4KQojIGdldCB0cmVlIGxlYXZlcyBhbmQgZXh0cmFjdCBhdHRyaWJ1dGVzIGludG8gZGF0YS5mcmFtZXMKeHlfZGYgPC0gbm9kZV90b19kZih4KQpwcm9jZXNzZWQgPC0gaGFyZGhhdDo6bW9sZCh4eV9kZiR4LCB4eV9kZiR5KQojIEdpdmVuIG4gY2xhc3NlcywgYW5jZXN0b3IgaXMgYW4gKG4geCBuKSBtYXRyaXggd2hlcmUgYW5jZXN0b3JfaWogPSAxIGlmIGNsYXNzIGkgaXMgZGVzY2VuZGFudCBvZiBjbGFzcyBqCiMgYW5jZXN0b3JfdHQgaXMgdGhlIHRvcmNoX3RlbnNvciBvZiBhbmNlc3RvcgoKYW5jZXN0b3JfdHQgPC0gYnVpbGRfYW5jZXN0b3JfbWF0cml4X2Zyb21fb3V0Y29tZXMoCiAgeCA9IHgsCiAgb3V0Y29tZXMgPSBwcm9jZXNzZWQkb3V0Y29tZXMsCiAgZGV2aWNlID0gImNwdSIKKQogIyBbMSAxMiAxMl0KYGBgCgoKYGBge3J9CiN8IGxhYmVsOiAiY2hlY2sgdHJlZSBsZXZlbHMgYXR0cml0aW9uIgoKbGV2ZWxzIDwtIHNhcHBseShkYXRhLnRyZWU6OlRyYXZlcnNlKHgpLCBgW1tgLCAibGV2ZWwiKQp0YWJsZShsZXZlbHMpCmBgYAoKYGBge3J9CiN8bGFiZWw6ICJoYXJkaGF0LlIgIEwzNjktTDM3MCIKIyB0YWJuZXRfYnJpZGdlKHByb2Nlc3NlZCwgY29uZmlnID0gY29uZmlnLCB0YWJuZXRfbW9kZWwsIGZyb21fZXBvY2gsIHRhc2sgPSAic3VwZXJ2aXNlZCIpIC0tPgpjb25maWcgPC0gdGFibmV0X2NvbmZpZyhlcG9jaHMgPSAyKQpjb25maWckYW5jZXN0b3IgPC0gYW5jZXN0b3JfdHQgIyBjbGFzcyAic3BhcnNlX2Nvb190ZW5zb3IiCmNvbmZpZyRvdXRjb21lcyA8LSBwcm9jZXNzZWQkb3V0Y29tZXMKdGFibmV0X21vZGVsX2xzdCA8LSB0YWJuZXQ6Ojp0YWJuZXRfaW5pdGlhbGl6ZShwcm9jZXNzZWQkcHJlZGljdG9ycywgcHJvY2Vzc2VkJG91dGNvbWVzLCBjb25maWcgPSBjb25maWcpCnRhYm5ldF9tb2RlbCA8LSAgdGFibmV0Ojo6bmV3X3RhYm5ldF9maXQodGFibmV0X21vZGVsX2xzdCwgYmx1ZXByaW50ID0gcHJvY2Vzc2VkJGJsdWVwcmludCkKCmZpdF9sc3QgPC0gdGFibmV0Ojo6dGFibmV0X3RyYWluX3N1cGVydmlzZWQodGFibmV0X21vZGVsLCBwcm9jZXNzZWQkcHJlZGljdG9ycywgcHJvY2Vzc2VkJG91dGNvbWVzLCBjb25maWcgPSBjb25maWcpICMgRmFpbHMgd2l0aCAKIz4gISBFcnJldXIgOiBtYXQxIGFuZCBtYXQyIHNoYXBlcyBjYW5ub3QgYmUgbXVsdGlwbGllZCAoMTQ3MHgyIGFuZCAxNDcweDEyKQojPiBFeGNlcHRpb24gcmFpc2VkIGZyb20gbWV0YSBhdCAvcHl0b3JjaC9hdGVuL3NyYy9BVGVuL25hdGl2ZS9MaW5lYXJBbGdlYnJhLmNwcDoyMDQgKG1vc3QgcmVjZW50IGNhbGwgZmlyc3QpOgoKYGBgCgpgYGB7cn0KI3xsYWJlbDogIm1vZGVsX3RyYWluaW5nLlIgIEw0NjAiCgpvYmogPC0gdGFibmV0X21vZGVsCnggPC0gcHJvY2Vzc2VkJHByZWRpY3RvcnMKeSA8LSBwcm9jZXNzZWQkb3V0Y29tZXMKdHJhaW5fZHMgPC0gICB0b3JjaDo6ZGF0YXNldCgKICBpbml0aWFsaXplID0gZnVuY3Rpb24oKSB7fSwKICAuZ2V0YmF0Y2ggPSBmdW5jdGlvbihiYXRjaCkge3Jlc29sdmVfZGF0YSh4W2JhdGNoLF0sIHlbYmF0Y2gsXSl9LAogIC5sZW5ndGggPSBmdW5jdGlvbigpIHtucm93KHgpfQopKCkKdHJhaW5fZGwgPC0gdG9yY2g6OmRhdGFsb2FkZXIoCiAgdHJhaW5fZHMsCiAgYmF0Y2hfc2l6ZSA9IGNvbmZpZyRiYXRjaF9zaXplLAogIGRyb3BfbGFzdCA9IGNvbmZpZyRkcm9wX2xhc3QsCiAgc2h1ZmZsZSA9IFRSVUUgLAogIG51bV93b3JrZXJzID0gY29uZmlnJG51bV93b3JrZXJzCikKIyAuLi4gdG8gYmUgY29udGludWVkIC4uLgpgYGAKCmBgYHtyfQojfGxhYmVsOiAibW9kZWxfdHJhaW5pbmcuUiB0cmFpbl9iYXRjaCBMMjg1IgplcG9jaCA8LSAxCml0ZXIgPC0gZGF0YWxvYWRlcl9tYWtlX2l0ZXIodHJhaW5fZGwpCmJhdGNoIDwtIGRhdGFsb2FkZXJfbmV4dChpdGVyKQoKYyhvdXRwdXQsIE1fbG9zcykgJTwtJSBuZXR3b3JrKGJhdGNoJHgsIGJhdGNoJHhfbmFfbWFzaykKb3V0Y29tZV9ubGV2ZWxzIDwtIGFzLm51bWVyaWMoYmF0Y2gkb3V0cHV0X2RpbSR0byhkZXZpY2U9ImNwdSIpKSAjIGMoMywgOSkKICAKY29uZmlnJGxvc3NfZm4ob3V0cHV0LCAgY2xhc3NfaWRzX3RvX2JpbmFyeSgKICAgICAgeSA9IGJhdGNoJHksCiAgICAgIG91dGNvbWVzID0gY29uZmlnJG91dGNvbWVzLAogICAgICBkZXZpY2UgPSBvdXQkZGV2aWNlCiAgICApKQopCgpgYGAKCmBgYHtyfQojfGxhYmVsOiAibG9zcy5SIG5uZl9tY19sb3NzIEwxNDAiCgp0YXJnZXQgPC0gYmF0Y2gkeQpSIDwtIGNvbmZpZyRhbmNlc3RvcgoKCmBgYAoKCgojICBjb21wdXRlIHRvcmNoIHNwYXJzZSBhbmNlc3RvciBtYXRyaXggZm9yIHN0YXJ3YXJzX3RyZWUKYGBge3J9CiN8bGFiZWw6ICJoYXJkaGF0LlIgTDE2MC1MMTcwIgp4IDwtIHN0YXJ3YXJzX3RyZWUkY2xvbmUoZGVlcCA9IEZBTFNFKQoKIyBlbnN1cmUgdGhlcmUgaXMgbm8gbGV2ZWxfKiBjb2wgaW4gdGhlIE5vZGUgb2JqZWN0CmNoZWNrX2NvbXBsaWFudF9ub2RlKHgpCiMgZ2V0IHRyZWUgbGVhdmVzIGFuZCBleHRyYWN0IGF0dHJpYnV0ZXMgaW50byBkYXRhLmZyYW1lcwp4eV9kZiA8LSBub2RlX3RvX2RmKHgpCnByb2Nlc3NlZCA8LSBoYXJkaGF0Ojptb2xkKHh5X2RmJHgsIHh5X2RmJHkpCiMgR2l2ZW4gbiBjbGFzc2VzLCBhbmNlc3RvciBpcyBhbiAobiB4IG4pIG1hdHJpeCB3aGVyZSBhbmNlc3Rvcl9paiA9IDEgaWYgY2xhc3MgaSBpcyBkZXNjZW5kYW50IG9mIGNsYXNzIGoKIyBhbmNlc3Rvcl90dCBpcyB0aGUgYFJgIHRvcmNoX3RlbnNvciBvZiBhbmNlc3RvcgoKYW5jZXN0b3JfdHQgPC0gYnVpbGRfYW5jZXN0b3JfbWF0cml4X2Zyb21fb3V0Y29tZXMoCiAgeCA9IHgsCiAgb3V0Y29tZXMgPSBwcm9jZXNzZWQkb3V0Y29tZXMsCiAgZGV2aWNlID0gImNwdSIKKSAjIFsxIDQzIDQzXQpgYGAKCmBgYHtyfQojfCBsYWJlbDogImNoZWNrIHRyZWUgbGV2ZWxzIHN0YXJ3YXJzX3RyZWUiCgpsZXZlbHMgPC0gc2FwcGx5KGRhdGEudHJlZTo6VHJhdmVyc2UoeCksIGBbW2AsICJsZXZlbCIpCnRhYmxlKGxldmVscykKYGBgCgoKLS0tCgoKCgoKCiMgcXVhbmQgdGFibmV0X2ZpdCgpIG1hcmNoZQpgYGB7cn0KZml0IDwtIHRhYm5ldF9maXQoYXR0cml0aW9uX3RyZWUsIGNhdF9lbWJfZGltID0gMikKcHJlZGljdChmaXQsIGF0dHJpdGlvbl90cmVlKQojIEVycmV1ciBkYW5zIHN3aXRjaChvYmplY3Qkc3BlYyRtb2RlLCByZWdyZXNzaW9uID0gIm51bWVyaWMiLCBjbGFzc2lmaWNhdGlvbiA9ICJjbGFzcyIsICA6IAojICAgRVhQUiBkb2l0IMOqdHJlIHVuIHZlY3RldXIgZGUgbG9uZ3VldXIgMQpgYGAKCiMgZGV2ZWxvcCBgdGFibmV0Ojo6dGFibmV0X2ZpdC5Ob2RlKClgCmBgYHtyfQojfCBldmFsOiBmYWxzZQp4IDwtIHN0YXJ3YXJzX3RyZWUKIyBUT0RPIGNoZWNrIHRoZXJlIGlzIG5vIGxldmVsXyogY29sIGluIHRoZSB0cmVlCiAgIyBleHRyYWN0IGF0dHJpYnV0ZXMgZGF0YS5mcmFtZQogIHh5X2RmIDwtIFRvRGF0YUZyYW1lVHlwZUNvbCh4LCB4JGF0dHJpYnV0ZXNBbGwpCiAgeF9kZiA8LSB4eV9kZiAlPiUgc2VsZWN0KC1zdGFydHNfd2l0aCgibGV2ZWxfIikpCiAgeV9kZiA8LSB4eV9kZiAlPiUgc2VsZWN0KHN0YXJ0c193aXRoKCJsZXZlbF8iKSkKICBwcm9jZXNzZWQgPC0gaGFyZGhhdDo6bW9sZCh4X2RmLCB5X2RmKQogICMgZW1iZWQgdGhlIE0gbWF0cml4IGluIFNleHRyYQogIGFuY2VzdG9yIDwtIFRvRGF0YUZyYW1lTmV0d29yayhkYXRhdHJlZSkgJT4lCiAgICBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLCAuICU+JSBhcy5mYWN0b3IgJT4lIGFzLm51bWVyaWMpIAogIHByb2Nlc3NlZCRleHRyYSRNIDwtIE1hdHJpeDo6c3BhcnNlTWF0cml4KGFuY2VzdG9yJGZyb20sIGFuY2VzdG9yJHRvLCB4ID0gMSkKICBjaGVja190eXBlKHByb2Nlc3NlZCRvdXRjb21lcykKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgYSB5YXJyIHNvdXJjZWQgZmlsZQpgYGB7cn0KI3wgZXZhbDogZmFsc2UKCmxpYnJhcnkoeWFycikKCiMgRXJyb3IgICBJbnZhbGlkIHR5cGUgc3BlY2lmaWNhdGlvbi4KIyB5ZWFzdF90cmFpbiA8LSBmb3JlaWduOjpyZWFkLmFyZmYoIn4vX0RhdGEuc2NpZW5jZS9DLUhNQ05OLW1hc3Rlci9ITUNfZGF0YS9vdGhlcnMvRDBfeWVhc3RfR08udHJhaW52YWxpZC5hcmZmIikKeWVhc3RfdHJhaW5fcmF3IDwtIHlhcnI6OnJlYWQuYXJmZigifi9fRGF0YS5zY2llbmNlL0MtSE1DTk4tbWFzdGVyL0hNQ19kYXRhL290aGVycy9EMF95ZWFzdF9HTy50cmFpbnZhbGlkLmFyZmYiKQp5ZWFzdF90cmFpbiA8LSB5ZWFzdF90cmFpbl9yYXcgJT4lIG11dGF0ZV9hbGwoYXMubnVtZXJpYykgJT4lIG11dGF0ZV9hbGwoYXMubG9naWNhbCkKYGBgCgojIGFub3RoZXIgeWFyciBzb3VyY2VkIGZpbGUKYGBge3J9CiN8IGV2YWw6IGZhbHNlCgpsaWJyYXJ5KHlhcnIpCmVucm9uX3RyYWluIDwtIHlhcnI6OnJlYWQuYXJmZigifi9fRGF0YS5zY2llbmNlL0MtSE1DTk4tbWFzdGVyL0hNQ19kYXRhL290aGVycy9FbnJvbl9jb3JyX3RyYWludmFsaWQuYXJmZiIpCmVucm9uX3Rlc3QgPC0geWFycjo6cmVhZC5hcmZmKCJ+L19EYXRhLnNjaWVuY2UvQy1ITUNOTi1tYXN0ZXIvSE1DX2RhdGEvb3RoZXJzL0Vucm9uX2NvcnJfdGVzdC5hcmZmIikKYGBg