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