Questão 1

O banco de dados apresentado, trata de restaurante que são ou não Michelin. O banco contém 6 variáveis, ao todo são apenas 164 observações.

Transformando as variáveis

library(tidymodels)
library(dplyr)
library(visdat)
library(skimr)
library(recipes)
library(pROC)
library(readxl)
library(knitr)
library(stargazer)
library(yardstick)
MichelinNY <- read.csv("C:/Users/elton/Downloads/MichelinNY.txt")
tidymodels::tidymodels_prefer()

Transformando as variáveis

MichelinNY<- MichelinNY |>
  rename(c(endereço=Adress,comida=Food,decoraçao=Decor,                                    serviço=Service,preço=Price))
MichelinNY$endereço<- as.factor(as.character(MichelinNY$endereço))

MichelinNY$comida<- as.numeric(as.integer(MichelinNY$comida))
MichelinNY$decoraçao<- as.numeric(as.integer(MichelinNY$decoraçao))
MichelinNY$serviço<- as.numeric(as.integer(MichelinNY$serviço))
MichelinNY$preço<- as.numeric(as.integer(MichelinNY$preço))
MichelinNY$InMichelin <- factor(MichelinNY$InMichelin, levels = c(0,1),labels = c("naoMichelin", "Michelin"))

Descritiva dos dados

vis_dat(MichelinNY)

skim(MichelinNY)
── Data Summary ────────────────────────
                           Values    
Name                       MichelinNY
Number of rows             164       
Number of columns          6         
_______________________              
Column type frequency:               
  factor                   2         
  numeric                  4         
________________________             
Group variables            None      

Divisão Inicial e Receita

set.seed(0) 
divisao_inicial <- rsample::initial_split(MichelinNY, prop = 0.8, strata ="InMichelin")
treinamento <- rsample::training(divisao_inicial)
teste <- rsample::testing(divisao_inicial) 
receita <- 
  treinamento |> recipe(formula = InMichelin ~ .) |> 
    
  recipes::step_dummy(all_nominal(), -all_outcomes()) |>  
  recipes::step_zv(all_numeric(), -all_outcomes()) |> 
  recipes::step_corr(all_predictors(), threshold = 0.7, method = "spearman")

Configurando o Modelo

log_reg <-
  logistic_reg(penalty = tune(), mixture = tune()) |>  
  set_engine(engine = "glmnet") |> 
  set_mode("classification")

Validação Cruzada


validacao_cruzada <- 
  treinamento |> 
  rsample::vfold_cv(v = 5L, strata = InMichelin)

Workflow

wf_todos <- workflow_set(
  preproc = list(receita),
  models = list(modelo_log = log_reg)
) |> 
  mutate(wflow_id = gsub("(recipe_)", "", wflow_id))
controle_grid <- control_grid(
  save_pred = TRUE,
  save_workflow = TRUE,
  parallel_over = "resamples"
)
metrica <- metric_set(accuracy, roc_auc)

Treinando o Modelo

treino <-
  wf_todos |> 
  workflow_map(
    resamples = validacao_cruzada,
    grid = 20L,
    control = controle_grid
  )

Verificando os melhores Hiperparametro

best = treino |> 
  extract_workflow_set_result("modelo_log") |>  
  select_best(metric = "roc_auc")

best |> 
  knitr::kable()
penalty mixture .config
0.0185454 0.997221 Preprocessor1_Model20

test <-  treino |> 
  extract_workflow("modelo_log") |>  
  finalize_workflow(best) |> 
  last_fit(split = divisao_inicial,
           metrics = metric_set(accuracy, roc_auc))

Testando em uma matriz de confusão

predictions <- test |> 
  collect_predictions()

matriz_confusão <- predictions |> 
  conf_mat(InMichelin, .pred_class)

round(prop.table(matriz_confusão$table),2) |> 
  knitr::kable()
naoMichelin Michelin
naoMichelin 0.48 0.12
Michelin 0.06 0.33
NA
NA
melhores <- 
  treino |> 
  rank_results(select_best = TRUE, rank_metric = c( "roc_auc")) 
stargazer::stargazer(melhores,summary = F,type = "text")

=================================================================================================================
   wflow_id         .config        .metric        mean             std_err       n preprocessor    model     rank
-----------------------------------------------------------------------------------------------------------------
1 modelo_log Preprocessor1_Model20 accuracy 0.777356125356125 0.0322521598740875 5    recipe    logistic_reg  1  
2 modelo_log Preprocessor1_Model20 roc_auc  0.858391053391053 0.0248715845505386 5    recipe    logistic_reg  1  
-----------------------------------------------------------------------------------------------------------------

curva_roc <- roc_curve(predictions,InMichelin, .pred_naoMichelin) 
roc_auc(predictions, InMichelin, .pred_naoMichelin)

autoplot(curva_roc)

O modelo apresentado tem desempenho superior a qualquer modelo feito de forma aleatório, mostrando com isso, a eficiência dos modelos de machine learning.

Salvando o Modelo


#Salvando o modelo em um arquivo serializado

#saveRDS(test, file = "modelo_final_questao1.rds")

# Lendo o arquivo serializado com o modelo final
#load(file = "modelo_final_questao1.rds")

# Fazendo novas previsões

#predict(modelo_final_questao1, new_data = data_novo)

Questão 2

A-Explique resumidamente o que é aprendizagem supervisionada e não-supervisionada. Cite um problema de aprendizagem supervisionada e um outro de aprendizagem não-supervisionada.

Supervisionada utiliza conjuntos de dados rotulados, sem a necessidade de uma pessoa externa para compreender o assunto.

Não supervisionada é quando não temos a resposta correta do conteudo, ou seja, temos a necessidade de pessoas externas para entendimento do assunto.

B- Explique qual o motivo que faz com que o Erro Quadrático Médio - EQM para avaliar o desempenho de um modelo é ruim quando não adotamos nenhuma estratégia de divisão do conjunto de dados em treinamento e teste.

Considere a variável aleatória \(Y_i \sim N (sin (X_i),\sigma^2)\) , com \(X_i\) \(\epsilon\) \(U (0,10)\) , \(\forall_i\) . Utilizando a biblioteca \(rpart\), implemente a função arvore(n=250L,complexidade,sigma =0.1) que devolva o gráfico scatterplot com os pontos \(X_i\) e \(Y_i\) e por cima deles o gráfico de linha com os valores preditos. A função deverá ter três argumentos , n,complexidade e sigma, que são tamanho da amostra, o grau de complexidade do modelo, e o desvio padrão, respectivamente. Aqui não se preocupe com divisão entre treino e teste nem de validação cruzada. A solução desse exercício não tem como objetivo encontrar o melhor hiperparâmetro , \(i.e\), não é necessário tunar o parâmetro de complexidade.

library(rpart)
library(ggplot2)
library(tidymodels)
library(tibble)
library(dplyr)
library(workflows)
tidymodels::tidymodels_prefer()

arvore <- function(n = 250, complexidade, sigma = 0.1) {
X <- runif(n, 0, 10)
  
Y <- sin(X) + rnorm(n, mean = 0, sd = sigma)
  
df <- data.frame(X = X, Y = Y)
  
modelo_arvore <- rpart(Y ~ X, data = df, control = rpart.control(cp = complexidade))
  
plot(X, Y, main = "Gráfico de Dispersão e Árvore de Regressão", xlab = "X", ylab = "Y", pch = 19)
  
curve(predict(modelo_arvore, data.frame(X = x)), from = 0, to = 10, add = TRUE, col = "red")
}
arvore(n = 250, complexidade = 0.01, sigma = 0.1)

NA
NA

Sem utilizar a biblioteca tidymodels, apenas as bibliotecas rsample e rpart, treine um modelo com 10 mil observações geradas. No procedimento \(k-folds\) \(cross-validation\), para \(k=20\) , encontre um bom valor para o grau de complexidade considerando um grid de possíveis valores. Dica: experimente testar um parâmetro dentro do conjunto de treino no procedimento de cross-validation. Por exemplo, experimente criar um grid com valores entre \(0.001\) \(0.001\) e \(0.4\) 0.4. Aprensente o gráfico com a estimativa do melhor modelo. Não esqueça de fixar um valor de semente, para que os resultados possam ser reproduzidos.


estimar_e_plotar_arvores <- function(n = 250, num_arvores = 10, complexidade, sigma = 0.1) {
resultados <- data.frame(X = rep(0, n), Y_pred = rep(0, n))
  
for (i in 1:num_arvores) {
   
X <- runif(n, 0, 10)
    
    
Y <- sin(X) + rnorm(n, mean = 0, sd = sigma)
    
   
df <- data.frame(X = X, Y = Y)
    
    
modelo_arvore <- rpart(Y ~ X, data = df, control = rpart.control(cp = complexidade))
    
    
X_pred <- seq(0, 10, length.out = n)
    
    
Y_pred <- predict(modelo_arvore, data.frame(X = X_pred))
    
   
resultados <- data.frame(X = X_pred, Y_pred = resultados$Y_pred + Y_pred / num_arvores)
  }
  
 
ggplot(resultados, aes(x = X, y = Y_pred)) +
    geom_line(color = "blue") +
    ggtitle("Estimativas de Múltiplas Árvores de Regressão") +
    xlab("X") + ylab("Y Predito")
}


estimar_e_plotar_arvores(n = 250, num_arvores = 10, complexidade = 0.01, sigma = 0.1)

NA
NA
NA

Questão 3

Construa um modelo para previsão do valor do imóvel. Considere apenas casas e apartamentos.

imoveissalvador<- read.csv("C:/Users/elton/Downloads/salvador djl.csv", header=FALSE)
imoveissalvador<- imoveissalvador |> select(c(-V1,-V2,-V11,-V26,-V27,-V10))
imoveissalvador<- 
  imoveissalvador |> rename(c(valor=V3,area=V4,
                    iptu=V5,condominio=V6,quarto=V7,
                    vaga=V8,banheiro=V9,
                                          tipo=V12,area_servico=V13,espaco_gourmet=V14,piscina=V15,elevador=V16,salao_de_festa=V17,academia=V18,playground=V19,quadra_esporte=V20,portaria_24h=V21,varanda_gourmet=V22,sauna=V23,spa=V24,comercio=V25,))
imoveissalvador<- imoveissalvador[-1,]
imoveissalvador <- imoveissalvador |>  
  filter(tipo == "casas" | tipo == "apartamentos") |> select(-comercio)

Descritiva dos dados

dados <- dados_expectativa_renda[,-1]
Error: object 'dados_expectativa_renda' not found
skim(imoveissalvador)
── Data Summary ────────────────────────
                           Values         
Name                       imoveissalvador
Number of rows             16944          
Number of columns          22             
_______________________                   
Column type frequency:                    
  character                22             
________________________                  
Group variables            None           

Divisão Inicial e Receita


set.seed(0) 
divisao_inicial1 <- rsample::initial_split(imoveissalvador, prop = 0.8, strata ="valor")
treinamento1 <- rsample::training(divisao_inicial1) 
teste1 <- rsample::testing(divisao_inicial1)

receita1 <- 
  
  treinamento1 |> 
  recipe(formula = valor ~ . , imoveissalvador) |> 
  step_impute_knn(all_predictors(),
                  impute_with = c("iptu","condominio","vaga","quarto","banheiro"),
                  neighbors = 15L) |> 
  step_bin2factor(all_logical_predictors()) |> 
  step_dummy(all_nominal_predictors()) |> 
  step_YeoJohnson(all_numeric_predictors()) |> 
  step_normalize(all_numeric_predictors()) |> 
  step_zv(all_numeric_predictors()) |> 
  step_corr(all_numeric_predictors())

Configurando os Modelos

modelo_elastic <- 
  parsnip::linear_reg(penalty = tune::tune(), mixture = tune::tune()) |> 
  parsnip::set_mode("regression") |> 
  parsnip::set_engine("glmnet")

modelo_knn <-
  parsnip::nearest_neighbor(
    neighbors = tune::tune(),
    dist_power = tune::tune(), 
    weight_func = "gaussian" 
  ) |> 
  parsnip::set_mode("regression") |> 
  parsnip::set_engine("kknn")

modelo_svm <- 
  parsnip::svm_rbf(
    cost = tune::tune(),
    rbf_sigma = tune::tune(),
    margin = tune::tune()
  ) |> 
  parsnip::set_mode("regression") |> 
  parsnip::set_engine("kernlab")

modelo_arvore <- decision_tree(cost_complexity = tune(), tree_depth = tune(),
                         min_n = tune()) |> 
  set_engine(engine = "rpart") |> 
  set_mode("regression")

modelo_floresta <- 
  rand_forest(mtry = tune(), min_n = tune(), trees = tune()) |>   
  set_engine(engine = "ranger", importance = "impurity") |> 
  set_mode("regression")

modelo_bootstrep <- xgb <- boost_tree(tree_depth = tune(), learn_rate = tune(),
                        loss_reduction = tune(), min_n = tune(),
                        sample_size = tune(), trees = tune()) |> 
   set_engine(engine = "xgboost") |> 
   set_mode("regression")

Validação Cruzada

validacao_cruzada1 <- 
  treinamento1 |> 
  rsample::vfold_cv(v = 5L, strata = valor)

Worflow

wf_todos1 <-
  workflow_set(
    preproc = list(receita1),
    models = list(
      knn_fit = modelo_knn,
      elastic_fit = modelo_elastic,
      svm_fit = modelo_svm,
      arvore_fit<- modelo_arvore,
      floresta_fit<- modelo_floresta,
       boot_fit<- modelo_bootstrep
    ),
    cross = TRUE
  )


controle_grid1 <- control_grid(
  save_pred = TRUE,
  save_workflow = TRUE,
  parallel_over = "resamples"
)

Treinando o Modelo

treino1 <-
  wf_todos1 |> 
  workflow_map(
    resamples = validacao_cruzada1,
    grid = 20L,
    control = controle_grid1
  )

Melhores

melhores3 <- 
  treino3 |> 
  rank_results(select_best = TRUE, rank_metric = "rmse")
autoplot(treino1, select_best = TRUE)
treino3 |> 
  rank_results()
melhor_modelo <- 
  treino1 |> 
  extract_workflow_set_result(id = "recipe_1_rand_forest") |> 
  select_best(metric = "rmse")
wf_final3 <- 
  treino1 |> 
  extract_workflow(id = "recipe_1_rand_forest") |> 
  finalize_workflow(melhor_modelo)

teste2 <- 
  wf_final3 |> 
  last_fit(split = divisao_inicial1)

teste2$.metrics

Modelo final

modelo_final <- 
  wf_final3 |> 
  fit(imoveissalvador)
modelo_final
LS0tDQp0aXRsZTogIkFwcmVuZGl6YWdlbSBkYXMgTcOhcXVpbmFzIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KIyBRdWVzdMOjbyAxDQoNCj4NCg0KTyBiYW5jbyBkZSBkYWRvcyBhcHJlc2VudGFkbywgdHJhdGEgZGUgcmVzdGF1cmFudGUgcXVlIHPDo28gb3UgbsOjbyBNaWNoZWxpbi4gTyBiYW5jbyBjb250w6ltIDYgdmFyacOhdmVpcywgYW8gdG9kbyBzw6NvIGFwZW5hcyAxNjQgb2JzZXJ2YcOnw7Vlcy4NCg0KPg0KDQojIyBUcmFuc2Zvcm1hbmRvIGFzIHZhcmnDoXZlaXMNCg0KPg0KDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHltb2RlbHMpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh2aXNkYXQpDQpsaWJyYXJ5KHNraW1yKQ0KbGlicmFyeShyZWNpcGVzKQ0KbGlicmFyeShwUk9DKQ0KbGlicmFyeShyZWFkeGwpDQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShzdGFyZ2F6ZXIpDQpsaWJyYXJ5KHlhcmRzdGljaykNCmBgYA0KDQoNCj4NCg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KTWljaGVsaW5OWSA8LSByZWFkLmNzdigiQzovVXNlcnMvZWx0b24vRG93bmxvYWRzL01pY2hlbGluTlkudHh0IikNCnRpZHltb2RlbHM6OnRpZHltb2RlbHNfcHJlZmVyKCkNCmBgYA0KDQo+DQoNCiMjIFRyYW5zZm9ybWFuZG8gYXMgdmFyacOhdmVpcw0KDQo+DQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCk1pY2hlbGluTlk8LSBNaWNoZWxpbk5ZIHw+DQogIHJlbmFtZShjKGVuZGVyZcOnbz1BZHJlc3MsY29taWRhPUZvb2QsZGVjb3Jhw6dhbz1EZWNvciwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzZXJ2acOnbz1TZXJ2aWNlLHByZcOnbz1QcmljZSkpDQoNCmBgYA0KDQoNCj4NCg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KTWljaGVsaW5OWSRlbmRlcmXDp288LSBhcy5mYWN0b3IoYXMuY2hhcmFjdGVyKE1pY2hlbGluTlkkZW5kZXJlw6dvKSkNCg0KTWljaGVsaW5OWSRjb21pZGE8LSBhcy5udW1lcmljKGFzLmludGVnZXIoTWljaGVsaW5OWSRjb21pZGEpKQ0KTWljaGVsaW5OWSRkZWNvcmHDp2FvPC0gYXMubnVtZXJpYyhhcy5pbnRlZ2VyKE1pY2hlbGluTlkkZGVjb3Jhw6dhbykpDQpNaWNoZWxpbk5ZJHNlcnZpw6dvPC0gYXMubnVtZXJpYyhhcy5pbnRlZ2VyKE1pY2hlbGluTlkkc2VydmnDp28pKQ0KTWljaGVsaW5OWSRwcmXDp288LSBhcy5udW1lcmljKGFzLmludGVnZXIoTWljaGVsaW5OWSRwcmXDp28pKQ0KTWljaGVsaW5OWSRJbk1pY2hlbGluIDwtIGZhY3RvcihNaWNoZWxpbk5ZJEluTWljaGVsaW4sIGxldmVscyA9IGMoMCwxKSxsYWJlbHMgPSBjKCJuYW9NaWNoZWxpbiIsICJNaWNoZWxpbiIpKQ0KDQpgYGANCg0KPg0KDQojIyBEZXNjcml0aXZhIGRvcyBkYWRvcw0KDQo+DQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCnZpc19kYXQoTWljaGVsaW5OWSkNCnNraW0oTWljaGVsaW5OWSkNCg0KYGBgDQoNCj4NCg0KIyMgRGl2aXPDo28gSW5pY2lhbCBlIFJlY2VpdGENCg0KPg0KDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpzZXQuc2VlZCgwKSANCmRpdmlzYW9faW5pY2lhbCA8LSByc2FtcGxlOjppbml0aWFsX3NwbGl0KE1pY2hlbGluTlksIHByb3AgPSAwLjgsIHN0cmF0YSA9IkluTWljaGVsaW4iKQ0KdHJlaW5hbWVudG8gPC0gcnNhbXBsZTo6dHJhaW5pbmcoZGl2aXNhb19pbmljaWFsKQ0KdGVzdGUgPC0gcnNhbXBsZTo6dGVzdGluZyhkaXZpc2FvX2luaWNpYWwpIA0KDQoNCmBgYA0KDQo+DQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCnJlY2VpdGEgPC0gDQogIHRyZWluYW1lbnRvIHw+IHJlY2lwZShmb3JtdWxhID0gSW5NaWNoZWxpbiB+IC4pIHw+IA0KICAgIA0KICByZWNpcGVzOjpzdGVwX2R1bW15KGFsbF9ub21pbmFsKCksIC1hbGxfb3V0Y29tZXMoKSkgfD4gIA0KICByZWNpcGVzOjpzdGVwX3p2KGFsbF9udW1lcmljKCksIC1hbGxfb3V0Y29tZXMoKSkgfD4gDQogIHJlY2lwZXM6OnN0ZXBfY29ycihhbGxfcHJlZGljdG9ycygpLCB0aHJlc2hvbGQgPSAwLjcsIG1ldGhvZCA9ICJzcGVhcm1hbiIpDQoNCmBgYA0KDQo+DQoNCiMjIENvbmZpZ3VyYW5kbyBvIE1vZGVsbw0KDQo+DQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCmxvZ19yZWcgPC0NCiAgbG9naXN0aWNfcmVnKHBlbmFsdHkgPSB0dW5lKCksIG1peHR1cmUgPSB0dW5lKCkpIHw+ICANCiAgc2V0X2VuZ2luZShlbmdpbmUgPSAiZ2xtbmV0IikgfD4gDQogIHNldF9tb2RlKCJjbGFzc2lmaWNhdGlvbiIpDQoNCmBgYA0KDQo+DQoNCg0KIyMgVmFsaWRhw6fDo28gQ3J1emFkYQ0KDQo+DQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCg0KdmFsaWRhY2FvX2NydXphZGEgPC0gDQogIHRyZWluYW1lbnRvIHw+IA0KICByc2FtcGxlOjp2Zm9sZF9jdih2ID0gNUwsIHN0cmF0YSA9IEluTWljaGVsaW4pDQoNCmBgYA0KDQo+DQoNCiMjIFdvcmtmbG93DQoNCj4NCg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kd2ZfdG9kb3MgPC0gd29ya2Zsb3dfc2V0KA0KICBwcmVwcm9jID0gbGlzdChyZWNlaXRhKSwNCiAgbW9kZWxzID0gbGlzdChtb2RlbG9fbG9nID0gbG9nX3JlZykNCikgfD4gDQogIG11dGF0ZSh3Zmxvd19pZCA9IGdzdWIoIihyZWNpcGVfKSIsICIiLCB3Zmxvd19pZCkpDQpjb250cm9sZV9ncmlkIDwtIGNvbnRyb2xfZ3JpZCgNCiAgc2F2ZV9wcmVkID0gVFJVRSwNCiAgc2F2ZV93b3JrZmxvdyA9IFRSVUUsDQogIHBhcmFsbGVsX292ZXIgPSAicmVzYW1wbGVzIg0KKQ0KbWV0cmljYSA8LSBtZXRyaWNfc2V0KGFjY3VyYWN5LCByb2NfYXVjKQ0KDQoNCmBgYA0KDQo+DQoNCiMjIFRyZWluYW5kbyBvIE1vZGVsbw0KDQo+DQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCnRyZWlubyA8LQ0KICB3Zl90b2RvcyB8PiANCiAgd29ya2Zsb3dfbWFwKA0KICAgIHJlc2FtcGxlcyA9IHZhbGlkYWNhb19jcnV6YWRhLA0KICAgIGdyaWQgPSAyMEwsDQogICAgY29udHJvbCA9IGNvbnRyb2xlX2dyaWQNCiAgKQ0KDQoNCmBgYA0KDQo+DQoNCiMjIFZlcmlmaWNhbmRvIG9zIG1lbGhvcmVzIEhpcGVycGFyYW1ldHJvDQoNCj4NCiANCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCmJlc3QgPSB0cmVpbm8gfD4gDQogIGV4dHJhY3Rfd29ya2Zsb3dfc2V0X3Jlc3VsdCgibW9kZWxvX2xvZyIpIHw+ICANCiAgc2VsZWN0X2Jlc3QobWV0cmljID0gInJvY19hdWMiKQ0KDQpiZXN0IHw+IA0KICBrbml0cjo6a2FibGUoKQ0KDQp0ZXN0IDwtICB0cmVpbm8gfD4gDQogIGV4dHJhY3Rfd29ya2Zsb3coIm1vZGVsb19sb2ciKSB8PiAgDQogIGZpbmFsaXplX3dvcmtmbG93KGJlc3QpIHw+IA0KICBsYXN0X2ZpdChzcGxpdCA9IGRpdmlzYW9faW5pY2lhbCwNCiAgICAgICAgICAgbWV0cmljcyA9IG1ldHJpY19zZXQoYWNjdXJhY3ksIHJvY19hdWMpKQ0KDQpgYGANCg0KPg0KDQojIyBUZXN0YW5kbyBlbSB1bWEgbWF0cml6IGRlIGNvbmZ1c8Ojbw0KDQo+DQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCnByZWRpY3Rpb25zIDwtIHRlc3QgfD4gDQogIGNvbGxlY3RfcHJlZGljdGlvbnMoKQ0KDQptYXRyaXpfY29uZnVzw6NvIDwtIHByZWRpY3Rpb25zIHw+IA0KICBjb25mX21hdChJbk1pY2hlbGluLCAucHJlZF9jbGFzcykNCg0Kcm91bmQocHJvcC50YWJsZShtYXRyaXpfY29uZnVzw6NvJHRhYmxlKSwyKSB8PiANCiAga25pdHI6OmthYmxlKCkNCg0KDQpgYGANCg0KPg0KDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQptZWxob3JlcyA8LSANCiAgdHJlaW5vIHw+IA0KICByYW5rX3Jlc3VsdHMoc2VsZWN0X2Jlc3QgPSBUUlVFLCByYW5rX21ldHJpYyA9IGMoICJyb2NfYXVjIikpIA0Kc3RhcmdhemVyOjpzdGFyZ2F6ZXIobWVsaG9yZXMsc3VtbWFyeSA9IEYsdHlwZSA9ICJ0ZXh0IikNCg0KYGBgDQoNCj4NCg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQpjdXJ2YV9yb2MgPC0gcm9jX2N1cnZlKHByZWRpY3Rpb25zLEluTWljaGVsaW4sIC5wcmVkX25hb01pY2hlbGluKSANCnJvY19hdWMocHJlZGljdGlvbnMsIEluTWljaGVsaW4sIC5wcmVkX25hb01pY2hlbGluKQ0KDQphdXRvcGxvdChjdXJ2YV9yb2MpDQoNCmBgYA0KDQo+DQoNCg0KTyBtb2RlbG8gYXByZXNlbnRhZG8gdGVtIGRlc2VtcGVuaG8gc3VwZXJpb3IgYSBxdWFscXVlciBtb2RlbG8gZmVpdG8gZGUgZm9ybWEgYWxlYXTDs3JpbywgbW9zdHJhbmRvIGNvbSBpc3NvLCBhIGVmaWNpw6puY2lhIGRvcyBtb2RlbG9zIGRlIG1hY2hpbmUgbGVhcm5pbmcuDQoNCg0KPg0KDQoNCiMjIFNhbHZhbmRvIG8gTW9kZWxvDQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCg0KI1NhbHZhbmRvIG8gbW9kZWxvIGVtIHVtIGFycXVpdm8gc2VyaWFsaXphZG8NCg0KI3NhdmVSRFModGVzdCwgZmlsZSA9ICJtb2RlbG9fZmluYWxfcXVlc3RhbzEucmRzIikNCg0KIyBMZW5kbyBvIGFycXVpdm8gc2VyaWFsaXphZG8gY29tIG8gbW9kZWxvIGZpbmFsDQojbG9hZChmaWxlID0gIm1vZGVsb19maW5hbF9xdWVzdGFvMS5yZHMiKQ0KDQojIEZhemVuZG8gbm92YXMgcHJldmlzw7Vlcw0KDQojcHJlZGljdChtb2RlbG9fZmluYWxfcXVlc3RhbzEsIG5ld19kYXRhID0gZGF0YV9ub3ZvKQ0KYGBgDQoNCg0KPg0KDQojIFF1ZXN0w6NvIDINCg0KPg0KDQpBLUV4cGxpcXVlIHJlc3VtaWRhbWVudGUgbyBxdWUgw6kgYXByZW5kaXphZ2VtIHN1cGVydmlzaW9uYWRhIGUgbsOjby1zdXBlcnZpc2lvbmFkYS4gQ2l0ZSB1bSBwcm9ibGVtYSBkZSBhcHJlbmRpemFnZW0gc3VwZXJ2aXNpb25hZGEgZSB1bSBvdXRybyBkZSBhcHJlbmRpemFnZW0gbsOjby1zdXBlcnZpc2lvbmFkYS4NCg0KDQpTdXBlcnZpc2lvbmFkYSB1dGlsaXphIGNvbmp1bnRvcyBkZSBkYWRvcyByb3R1bGFkb3MsIHNlbSBhIG5lY2Vzc2lkYWRlIGRlIHVtYSBwZXNzb2EgZXh0ZXJuYSBwYXJhIGNvbXByZWVuZGVyIG8gYXNzdW50by4NCg0KDQpOw6NvIHN1cGVydmlzaW9uYWRhIMOpIHF1YW5kbyBuw6NvIHRlbW9zIGEgcmVzcG9zdGEgY29ycmV0YSBkbyBjb250ZXVkbywgb3Ugc2VqYSwgdGVtb3MgYSBuZWNlc3NpZGFkZSBkZSBwZXNzb2FzIGV4dGVybmFzIHBhcmEgZW50ZW5kaW1lbnRvIGRvIGFzc3VudG8uIA0KDQoNCj4NCg0KQi0gRXhwbGlxdWUgcXVhbCBvIG1vdGl2byBxdWUgZmF6IGNvbSBxdWUgbyBFcnJvIFF1YWRyw6F0aWNvIE3DqWRpbyAtIEVRTSBwYXJhIGF2YWxpYXIgbyBkZXNlbXBlbmhvIGRlIHVtIG1vZGVsbyDDqSBydWltIHF1YW5kbyBuw6NvIGFkb3RhbW9zIG5lbmh1bWEgZXN0cmF0w6lnaWEgZGUgZGl2aXPDo28gZG8gY29uanVudG8gZGUgZGFkb3MgZW0gdHJlaW5hbWVudG8gZSB0ZXN0ZS4NCg0KPg0KDQpDb25zaWRlcmUgYSB2YXJpw6F2ZWwgYWxlYXTDs3JpYSAkWV9pIFxzaW0gTiAoc2luIChYX2kpLFxzaWdtYV4yKSQgLCBjb20gJFhfaSQgJFxlcHNpbG9uJCAkVSAoMCwxMCkkICwgJFxmb3JhbGxfaSQgLiBVdGlsaXphbmRvIGEgYmlibGlvdGVjYSAkcnBhcnQkLCBpbXBsZW1lbnRlIGEgZnVuw6fDo28gYXJ2b3JlKG49MjUwTCxjb21wbGV4aWRhZGUsc2lnbWEgPTAuMSkgcXVlIGRldm9sdmEgbyBncsOhZmljbyBzY2F0dGVycGxvdCBjb20gb3MgcG9udG9zICRYX2kkIGUgJFlfaSQgZSBwb3IgY2ltYSBkZWxlcyBvIGdyw6FmaWNvIGRlIGxpbmhhIGNvbSBvcyB2YWxvcmVzIHByZWRpdG9zLiBBIGZ1bsOnw6NvIGRldmVyw6EgdGVyIHRyw6pzIGFyZ3VtZW50b3MgLCBuLGNvbXBsZXhpZGFkZSBlIHNpZ21hLCBxdWUgc8OjbyB0YW1hbmhvIGRhIGFtb3N0cmEsIG8gZ3JhdSBkZSBjb21wbGV4aWRhZGUgZG8gbW9kZWxvLCBlIG8gZGVzdmlvIHBhZHLDo28sIHJlc3BlY3RpdmFtZW50ZS4gQXF1aSBuw6NvIHNlIHByZW9jdXBlIGNvbSBkaXZpc8OjbyBlbnRyZSB0cmVpbm8gZSB0ZXN0ZSBuZW0gZGUgdmFsaWRhw6fDo28gY3J1emFkYS4gQSBzb2x1w6fDo28gZGVzc2UgZXhlcmPDrWNpbyBuw6NvIHRlbSBjb21vIG9iamV0aXZvIGVuY29udHJhciBvIG1lbGhvciBoaXBlcnBhcsOibWV0cm8gLCAkaS5lJCwgbsOjbyDDqSBuZWNlc3PDoXJpbyB0dW5hciBvIHBhcsOibWV0cm8gZGUgY29tcGxleGlkYWRlLg0KDQo+DQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCmxpYnJhcnkocnBhcnQpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpZHltb2RlbHMpDQpsaWJyYXJ5KHRpYmJsZSkNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHdvcmtmbG93cykNCnRpZHltb2RlbHM6OnRpZHltb2RlbHNfcHJlZmVyKCkNCmBgYA0KDQo+DQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCg0KYXJ2b3JlIDwtIGZ1bmN0aW9uKG4gPSAyNTAsIGNvbXBsZXhpZGFkZSwgc2lnbWEgPSAwLjEpIHsNClggPC0gcnVuaWYobiwgMCwgMTApDQogIA0KWSA8LSBzaW4oWCkgKyBybm9ybShuLCBtZWFuID0gMCwgc2QgPSBzaWdtYSkNCiAgDQpkZiA8LSBkYXRhLmZyYW1lKFggPSBYLCBZID0gWSkNCiAgDQptb2RlbG9fYXJ2b3JlIDwtIHJwYXJ0KFkgfiBYLCBkYXRhID0gZGYsIGNvbnRyb2wgPSBycGFydC5jb250cm9sKGNwID0gY29tcGxleGlkYWRlKSkNCiAgDQpwbG90KFgsIFksIG1haW4gPSAiR3LDoWZpY28gZGUgRGlzcGVyc8OjbyBlIMOBcnZvcmUgZGUgUmVncmVzc8OjbyIsIHhsYWIgPSAiWCIsIHlsYWIgPSAiWSIsIHBjaCA9IDE5KQ0KICANCmN1cnZlKHByZWRpY3QobW9kZWxvX2Fydm9yZSwgZGF0YS5mcmFtZShYID0geCkpLCBmcm9tID0gMCwgdG8gPSAxMCwgYWRkID0gVFJVRSwgY29sID0gInJlZCIpDQp9DQphcnZvcmUobiA9IDI1MCwgY29tcGxleGlkYWRlID0gMC4wMSwgc2lnbWEgPSAwLjEpDQoNCg0KYGBgDQoNCg0KPg0KDQoNClNlbSB1dGlsaXphciBhIGJpYmxpb3RlY2EgdGlkeW1vZGVscywgYXBlbmFzIGFzIGJpYmxpb3RlY2FzIHJzYW1wbGUgZSBycGFydCwgdHJlaW5lIHVtIG1vZGVsbyBjb20gMTAgbWlsIG9ic2VydmHDp8O1ZXMgZ2VyYWRhcy4gTm8gcHJvY2VkaW1lbnRvICRrLWZvbGRzJCAkY3Jvc3MtdmFsaWRhdGlvbiQsIHBhcmEgDQokaz0yMCQgLCBlbmNvbnRyZSB1bSBib20gdmFsb3IgcGFyYSBvIGdyYXUgZGUgY29tcGxleGlkYWRlIGNvbnNpZGVyYW5kbyB1bSBncmlkIGRlIHBvc3PDrXZlaXMgdmFsb3Jlcy4gRGljYTogZXhwZXJpbWVudGUgdGVzdGFyIHVtIHBhcsOibWV0cm8gZGVudHJvIGRvIGNvbmp1bnRvIGRlIHRyZWlubyBubyBwcm9jZWRpbWVudG8gZGUgY3Jvc3MtdmFsaWRhdGlvbi4gUG9yIGV4ZW1wbG8sIGV4cGVyaW1lbnRlIGNyaWFyIHVtIGdyaWQgY29tIHZhbG9yZXMgZW50cmUgDQokMC4wMDEkICQwLjAwMSQgZSAkMC40JCAwLjQuIEFwcmVuc2VudGUgbyBncsOhZmljbyBjb20gYSBlc3RpbWF0aXZhIGRvIG1lbGhvciBtb2RlbG8uIE7Do28gZXNxdWXDp2EgZGUgZml4YXIgdW0gdmFsb3IgZGUgc2VtZW50ZSwgcGFyYSBxdWUgb3MgcmVzdWx0YWRvcyBwb3NzYW0gc2VyIHJlcHJvZHV6aWRvcy4gDQoNCj4NCg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQplc3RpbWFyX2VfcGxvdGFyX2Fydm9yZXMgPC0gZnVuY3Rpb24obiA9IDI1MCwgbnVtX2Fydm9yZXMgPSAxMCwgY29tcGxleGlkYWRlLCBzaWdtYSA9IDAuMSkgew0KcmVzdWx0YWRvcyA8LSBkYXRhLmZyYW1lKFggPSByZXAoMCwgbiksIFlfcHJlZCA9IHJlcCgwLCBuKSkNCiAgDQpmb3IgKGkgaW4gMTpudW1fYXJ2b3Jlcykgew0KICAgDQpYIDwtIHJ1bmlmKG4sIDAsIDEwKQ0KICAgIA0KICAgIA0KWSA8LSBzaW4oWCkgKyBybm9ybShuLCBtZWFuID0gMCwgc2QgPSBzaWdtYSkNCiAgICANCiAgIA0KZGYgPC0gZGF0YS5mcmFtZShYID0gWCwgWSA9IFkpDQogICAgDQogICAgDQptb2RlbG9fYXJ2b3JlIDwtIHJwYXJ0KFkgfiBYLCBkYXRhID0gZGYsIGNvbnRyb2wgPSBycGFydC5jb250cm9sKGNwID0gY29tcGxleGlkYWRlKSkNCiAgICANCiAgICANClhfcHJlZCA8LSBzZXEoMCwgMTAsIGxlbmd0aC5vdXQgPSBuKQ0KICAgIA0KICAgIA0KWV9wcmVkIDwtIHByZWRpY3QobW9kZWxvX2Fydm9yZSwgZGF0YS5mcmFtZShYID0gWF9wcmVkKSkNCiAgICANCiAgIA0KcmVzdWx0YWRvcyA8LSBkYXRhLmZyYW1lKFggPSBYX3ByZWQsIFlfcHJlZCA9IHJlc3VsdGFkb3MkWV9wcmVkICsgWV9wcmVkIC8gbnVtX2Fydm9yZXMpDQogIH0NCiAgDQogDQpnZ3Bsb3QocmVzdWx0YWRvcywgYWVzKHggPSBYLCB5ID0gWV9wcmVkKSkgKw0KICAgIGdlb21fbGluZShjb2xvciA9ICJibHVlIikgKw0KICAgIGdndGl0bGUoIkVzdGltYXRpdmFzIGRlIE3Dumx0aXBsYXMgw4Fydm9yZXMgZGUgUmVncmVzc8OjbyIpICsNCiAgICB4bGFiKCJYIikgKyB5bGFiKCJZIFByZWRpdG8iKQ0KfQ0KDQoNCmVzdGltYXJfZV9wbG90YXJfYXJ2b3JlcyhuID0gMjUwLCBudW1fYXJ2b3JlcyA9IDEwLCBjb21wbGV4aWRhZGUgPSAwLjAxLCBzaWdtYSA9IDAuMSkNCg0KDQoNCmBgYA0KDQoNCg0KPg0KDQojIFF1ZXN0w6NvIDMNCg0KPg0KDQpDb25zdHJ1YSB1bSBtb2RlbG8gcGFyYSBwcmV2aXPDo28gZG8gdmFsb3IgZG8gaW3Ds3ZlbC4gQ29uc2lkZXJlIGFwZW5hcyBjYXNhcyBlIGFwYXJ0YW1lbnRvcy4NCg0KPg0KDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQppbW92ZWlzc2FsdmFkb3I8LSByZWFkLmNzdigiQzovVXNlcnMvZWx0b24vRG93bmxvYWRzL3NhbHZhZG9yIGRqbC5jc3YiLCBoZWFkZXI9RkFMU0UpDQoNCmBgYA0KDQoNCj4NCg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KaW1vdmVpc3NhbHZhZG9yPC0gaW1vdmVpc3NhbHZhZG9yIHw+IHNlbGVjdChjKC1WMSwtVjIsLVYxMSwtVjI2LC1WMjcsLVYxMCkpDQoNCmBgYA0KDQo+DQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCmltb3ZlaXNzYWx2YWRvcjwtIA0KICBpbW92ZWlzc2FsdmFkb3IgfD4gcmVuYW1lKGModmFsb3I9VjMsYXJlYT1WNCwNCiAgICAgICAgICAgICAgICAgICAgaXB0dT1WNSxjb25kb21pbmlvPVY2LHF1YXJ0bz1WNywNCiAgICAgICAgICAgICAgICAgICAgdmFnYT1WOCxiYW5oZWlybz1WOSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpcG89VjEyLGFyZWFfc2Vydmljbz1WMTMsZXNwYWNvX2dvdXJtZXQ9VjE0LHBpc2NpbmE9VjE1LGVsZXZhZG9yPVYxNixzYWxhb19kZV9mZXN0YT1WMTcsYWNhZGVtaWE9VjE4LHBsYXlncm91bmQ9VjE5LHF1YWRyYV9lc3BvcnRlPVYyMCxwb3J0YXJpYV8yNGg9VjIxLHZhcmFuZGFfZ291cm1ldD1WMjIsc2F1bmE9VjIzLHNwYT1WMjQsY29tZXJjaW89VjI1LCkpDQppbW92ZWlzc2FsdmFkb3I8LSBpbW92ZWlzc2FsdmFkb3JbLTEsXQ0KaW1vdmVpc3NhbHZhZG9yIDwtIGltb3ZlaXNzYWx2YWRvciB8PiAgDQogIGZpbHRlcih0aXBvID09ICJjYXNhcyIgfCB0aXBvID09ICJhcGFydGFtZW50b3MiKSB8PiBzZWxlY3QoLWNvbWVyY2lvKQ0KDQpgYGANCg0KDQo+DQoNCiMjIERlc2NyaXRpdmEgZG9zIGRhZG9zDQoNCj4NCg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KdmlzX2RhdChpbW92ZWlzc2FsdmFkb3IsbGFyZ2VfZGF0YV9zaXplID0gMTAwZSsxMDApDQp2aXNfbWlzcyhpbW92ZWlzc2FsdmFkb3IsIHNvcnRfbWlzcyA9IFRSVUUsbGFyZ2VfZGF0YV9zaXplID0gMTAwZSsxMDApDQoNCmBgYA0KDQo+DQoNCg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kc2tpbShpbW92ZWlzc2FsdmFkb3IpDQoNCmBgYA0KDQo+DQoNCiMjIERpdmlzw6NvIEluaWNpYWwgZSBSZWNlaXRhDQoNCj4NCg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KDQpzZXQuc2VlZCgwKSANCmRpdmlzYW9faW5pY2lhbDEgPC0gcnNhbXBsZTo6aW5pdGlhbF9zcGxpdChpbW92ZWlzc2FsdmFkb3IsIHByb3AgPSAwLjgsIHN0cmF0YSA9InZhbG9yIikNCnRyZWluYW1lbnRvMSA8LSByc2FtcGxlOjp0cmFpbmluZyhkaXZpc2FvX2luaWNpYWwxKSANCnRlc3RlMSA8LSByc2FtcGxlOjp0ZXN0aW5nKGRpdmlzYW9faW5pY2lhbDEpDQoNCnJlY2VpdGExIDwtIA0KICANCiAgdHJlaW5hbWVudG8xIHw+IA0KICByZWNpcGUoZm9ybXVsYSA9IHZhbG9yIH4gLiAsIGltb3ZlaXNzYWx2YWRvcikgfD4gDQogIHN0ZXBfaW1wdXRlX2tubihhbGxfcHJlZGljdG9ycygpLA0KICAgICAgICAgICAgICAgICAgaW1wdXRlX3dpdGggPSBjKCJpcHR1IiwiY29uZG9taW5pbyIsInZhZ2EiLCJxdWFydG8iLCJiYW5oZWlybyIpLA0KICAgICAgICAgICAgICAgICAgbmVpZ2hib3JzID0gMTVMKSB8PiANCiAgc3RlcF9iaW4yZmFjdG9yKGFsbF9sb2dpY2FsX3ByZWRpY3RvcnMoKSkgfD4gDQogIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKSB8PiANCiAgc3RlcF9ZZW9Kb2huc29uKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgfD4gDQogIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgfD4gDQogIHN0ZXBfenYoYWxsX251bWVyaWNfcHJlZGljdG9ycygpKSB8PiANCiAgc3RlcF9jb3JyKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkNCmBgYA0KDQo+DQoNCiMjIENvbmZpZ3VyYW5kbyBvcyBNb2RlbG9zDQoNCj4gIA0KDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQptb2RlbG9fZWxhc3RpYyA8LSANCiAgcGFyc25pcDo6bGluZWFyX3JlZyhwZW5hbHR5ID0gdHVuZTo6dHVuZSgpLCBtaXh0dXJlID0gdHVuZTo6dHVuZSgpKSB8PiANCiAgcGFyc25pcDo6c2V0X21vZGUoInJlZ3Jlc3Npb24iKSB8PiANCiAgcGFyc25pcDo6c2V0X2VuZ2luZSgiZ2xtbmV0IikNCg0KbW9kZWxvX2tubiA8LQ0KICBwYXJzbmlwOjpuZWFyZXN0X25laWdoYm9yKA0KICAgIG5laWdoYm9ycyA9IHR1bmU6OnR1bmUoKSwNCiAgICBkaXN0X3Bvd2VyID0gdHVuZTo6dHVuZSgpLCANCiAgICB3ZWlnaHRfZnVuYyA9ICJnYXVzc2lhbiIgDQogICkgfD4gDQogIHBhcnNuaXA6OnNldF9tb2RlKCJyZWdyZXNzaW9uIikgfD4gDQogIHBhcnNuaXA6OnNldF9lbmdpbmUoImtrbm4iKQ0KDQptb2RlbG9fc3ZtIDwtIA0KICBwYXJzbmlwOjpzdm1fcmJmKA0KICAgIGNvc3QgPSB0dW5lOjp0dW5lKCksDQogICAgcmJmX3NpZ21hID0gdHVuZTo6dHVuZSgpLA0KICAgIG1hcmdpbiA9IHR1bmU6OnR1bmUoKQ0KICApIHw+IA0KICBwYXJzbmlwOjpzZXRfbW9kZSgicmVncmVzc2lvbiIpIHw+IA0KICBwYXJzbmlwOjpzZXRfZW5naW5lKCJrZXJubGFiIikNCg0KbW9kZWxvX2Fydm9yZSA8LSBkZWNpc2lvbl90cmVlKGNvc3RfY29tcGxleGl0eSA9IHR1bmUoKSwgdHJlZV9kZXB0aCA9IHR1bmUoKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBtaW5fbiA9IHR1bmUoKSkgfD4gDQogIHNldF9lbmdpbmUoZW5naW5lID0gInJwYXJ0IikgfD4gDQogIHNldF9tb2RlKCJyZWdyZXNzaW9uIikNCg0KbW9kZWxvX2Zsb3Jlc3RhIDwtIA0KICByYW5kX2ZvcmVzdChtdHJ5ID0gdHVuZSgpLCBtaW5fbiA9IHR1bmUoKSwgdHJlZXMgPSB0dW5lKCkpIHw+ICAgDQogIHNldF9lbmdpbmUoZW5naW5lID0gInJhbmdlciIsIGltcG9ydGFuY2UgPSAiaW1wdXJpdHkiKSB8PiANCiAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKQ0KDQptb2RlbG9fYm9vdHN0cmVwIDwtIHhnYiA8LSBib29zdF90cmVlKHRyZWVfZGVwdGggPSB0dW5lKCksIGxlYXJuX3JhdGUgPSB0dW5lKCksDQogICAgICAgICAgICAgICAgICAgICAgICBsb3NzX3JlZHVjdGlvbiA9IHR1bmUoKSwgbWluX24gPSB0dW5lKCksDQogICAgICAgICAgICAgICAgICAgICAgICBzYW1wbGVfc2l6ZSA9IHR1bmUoKSwgdHJlZXMgPSB0dW5lKCkpIHw+IA0KICAgc2V0X2VuZ2luZShlbmdpbmUgPSAieGdib29zdCIpIHw+IA0KICAgc2V0X21vZGUoInJlZ3Jlc3Npb24iKQ0KYGBgDQoNCj4NCg0KIyMgVmFsaWRhw6fDo28gQ3J1emFkYQ0KDQo+DQoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCnZhbGlkYWNhb19jcnV6YWRhMSA8LSANCiAgdHJlaW5hbWVudG8xIHw+IA0KICByc2FtcGxlOjp2Zm9sZF9jdih2ID0gNUwsIHN0cmF0YSA9IHZhbG9yKQ0KDQpgYGANCg0KPg0KDQojIyBXb3JmbG93DQoNCj4NCg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kd2ZfdG9kb3MxIDwtDQogIHdvcmtmbG93X3NldCgNCiAgICBwcmVwcm9jID0gbGlzdChyZWNlaXRhMSksDQogICAgbW9kZWxzID0gbGlzdCgNCiAgICAgIGtubl9maXQgPSBtb2RlbG9fa25uLA0KICAgICAgZWxhc3RpY19maXQgPSBtb2RlbG9fZWxhc3RpYywNCiAgICAgIHN2bV9maXQgPSBtb2RlbG9fc3ZtLA0KICAgICAgYXJ2b3JlX2ZpdDwtIG1vZGVsb19hcnZvcmUsDQogICAgICBmbG9yZXN0YV9maXQ8LSBtb2RlbG9fZmxvcmVzdGEsDQogICAgICAgYm9vdF9maXQ8LSBtb2RlbG9fYm9vdHN0cmVwDQogICAgKSwNCiAgICBjcm9zcyA9IFRSVUUNCiAgKQ0KDQoNCmNvbnRyb2xlX2dyaWQxIDwtIGNvbnRyb2xfZ3JpZCgNCiAgc2F2ZV9wcmVkID0gVFJVRSwNCiAgc2F2ZV93b3JrZmxvdyA9IFRSVUUsDQogIHBhcmFsbGVsX292ZXIgPSAicmVzYW1wbGVzIg0KKQ0KDQpgYGANCg0KPg0KDQojIyBUcmVpbmFuZG8gbyBNb2RlbG8NCg0KPg0KDQpgYGB7cn0NCnRyZWlubzEgPC0NCiAgd2ZfdG9kb3MxIHw+IA0KICB3b3JrZmxvd19tYXAoDQogICAgcmVzYW1wbGVzID0gdmFsaWRhY2FvX2NydXphZGExLA0KICAgIGdyaWQgPSAyMEwsDQogICAgY29udHJvbCA9IGNvbnRyb2xlX2dyaWQxDQogICkNCg0KYXV0b3Bsb3QodHJlaW5vMSwgbWV0cmljID0gInJtc2UiKSArIA0KICBsYWJzKA0KICAgIHRpdGxlID0gIkF2YWxpYcOnw6NvIGRvcyBtb2RlbG9zIGRlIHJlZ3Jlc3PDo28iLA0KICAgIHN1YnRpdGxlID0gIlV0aWxpemFuZG8gYSBtw6l0cmljYSBkbyBFUU0iDQogICkgKyANCiAgeGxhYigiUmFuayBkb3MgV29ya2Zsb3dzIikgKw0KICB5bGFiKCJFcnJvIFF1YWRyw6F0aWNvIE3DqWRpbyAtIEVRTSIpDQoNCmBgYA0KDQoNCg0KPg0KDQojIyBNZWxob3Jlcw0KDQo+DQoNCmBgYHtyfQ0KbWVsaG9yZXMzIDwtIA0KICB0cmVpbm8zIHw+IA0KICByYW5rX3Jlc3VsdHMoc2VsZWN0X2Jlc3QgPSBUUlVFLCByYW5rX21ldHJpYyA9ICJybXNlIikNCmF1dG9wbG90KHRyZWlubzEsIHNlbGVjdF9iZXN0ID0gVFJVRSkNCnRyZWlubzMgfD4gDQogIHJhbmtfcmVzdWx0cygpDQpgYGANCg0KPg0KDQoNCmBgYHtyfQ0KbWVsaG9yX21vZGVsbyA8LSANCiAgdHJlaW5vMSB8PiANCiAgZXh0cmFjdF93b3JrZmxvd19zZXRfcmVzdWx0KGlkID0gInJlY2lwZV8xX3JhbmRfZm9yZXN0IikgfD4gDQogIHNlbGVjdF9iZXN0KG1ldHJpYyA9ICJybXNlIikNCmBgYA0KDQoNCg0KPg0KDQpgYGB7cn0NCndmX2ZpbmFsMyA8LSANCiAgdHJlaW5vMSB8PiANCiAgZXh0cmFjdF93b3JrZmxvdyhpZCA9ICJyZWNpcGVfMV9yYW5kX2ZvcmVzdCIpIHw+IA0KICBmaW5hbGl6ZV93b3JrZmxvdyhtZWxob3JfbW9kZWxvKQ0KDQp0ZXN0ZTIgPC0gDQogIHdmX2ZpbmFsMyB8PiANCiAgbGFzdF9maXQoc3BsaXQgPSBkaXZpc2FvX2luaWNpYWwxKQ0KDQp0ZXN0ZTIkLm1ldHJpY3MNCmBgYA0KDQoNCj4NCg0KIyMgTW9kZWxvIGZpbmFsDQoNCj4NCg0KYGBge3J9DQptb2RlbG9fZmluYWwgPC0gDQogIHdmX2ZpbmFsMyB8PiANCiAgZml0KGltb3ZlaXNzYWx2YWRvcikNCm1vZGVsb19maW5hbA0KYGBgDQoNCg0KDQoNCg0KDQo=