O Problema

Esse notebook introduz conceitos do algoritmo de Árvore de Decisão (AD), e para ilustrações do fato, faço uso do dataset Íris.

# estabelecendo o ambiente
suppressMessages({
        library(tidyverse)
        library(magrittr)
        library(knitr)
        library(GGally)
        library(rpart)
        library(rattle)
        library(rpart.utils)
        })  

setwd("~/Dropbox/kaggle/iris-species/")  
opts_chunk$set(cache=TRUE)  

data(iris)
iris %<>%  as_tibble()

O dataset Íris contém medidas de comprimento e largura da pétala e da sépala de três espécies de íris:

Versicolor
 

Setosa
 

Virgínica
 

Estrutura da flor:


O dataset contém 150 amostras, 50 de cada espécie.

iris %>%  head() %>% print()

A missão

Conhecer o algoritmo de Árvore de Decisão e criar um modelo possível de prever a espécie de íris a partir das medidas da pétala e sépala.

Antes da modelagem e análise em geral, o primeiro passo é dividir o conjunto em dados de treinamento e de teste. Para não criarmos um bias que influencie nas predições feitas sobre o conjunto de teste.

set.seed(654)
train_idx <- sample(nrow(iris), .75*nrow(iris))
train <- iris %>% slice(train_idx)
test <- iris %>% slice(-train_idx)

No dataset constam 4 preditores, é interessante checar o nível de correlação deles, e ou separação no plano que delineie as classes que queremos classificar.

train %>%
        ggpairs(aes(colour=Species), columns=1:4, lower="blank", diag="blank", 
                  upper=list(continuous="points")) + theme_bw() 

Algumas variáveis apresentam uma correlação bem forte, como Petal.Length e Petal.Width, e as regiões de tamanho das pétalas tem uma apresentam uma diferenciação bem clara das classes.

 


Aprendizado de Máquina

Dentro do espaço Preditor \(\Large{X}\) encontrar a melhor separação de sub-espaços que designem cada classe.

\[ \Large{\mathcal{F}} : X \rightarrow Y \]


Árvore de Decisão


Definições

Olhando um exemplo genérico de AD para o caso íris na figura abaixo, nesse exemplo só constam 2 preditores.

  • Abordagem Top-Down -> imagina a árvore invertida, o cume do grafo é a Raiz.
  • Os nós internos são Ramais.
  • Os nós terminais são Folhas, cada qual designa uma classe e representam micros-regiões oriundas da divisão do espaço de Preditores.
tree.fit <- rpart("Species ~ Petal.Length + Petal.Width", train, control=rpart.control(cp=0, minbucket=1))
fancyRpartPlot(tree.fit, sub="")

Regiões de decisão

limiares <- rpart.subrules.table(tree.fit) %>% 
                as_tibble() %>%
                select(Variable, Less) %>% 
                filter(!is.na(Less)) %>% 
                mutate(Less=as.numeric(as.character(Less)))
                
limiares_pl <- limiares %>% filter(Variable=="Petal.Length") %>%  .$Less           
limiares_pw <- limiares %>% filter(Variable=="Petal.Width") %>%  .$Less           

x_axis <- train$Petal.Length %>% range()
y_axis <- train$Petal.Width %>% range()
n_points <- train %>% nrow()
            
train %>% ggplot(aes(x=Petal.Length, y=Petal.Width, colour=Species)) +
            geom_point() + theme_bw() + scale_x_continuous(breaks=seq(10,1)) +
            geom_line(aes(x=rep(limiares_pl[1], n_points), y=seq(y_axis[1], y_axis[2], length.out=n_points)), colour="black") + 
            geom_line(aes(x=seq(limiares_pl[2], x_axis[2], length.out=n_points), y=rep(limiares_pw[1], n_points)), colour="black") +
            geom_line(aes(x=rep(limiares_pl[2], n_points), y=seq(y_axis[1], y_axis[2], length.out=n_points)), colour="black") +
            geom_line(aes(x=seq(limiares_pl[2], x_axis[2], length.out=n_points), y=rep(limiares_pw[1], n_points)), colour="black") +
            geom_line(aes(x=rep(limiares_pl[3], n_points), y=seq(y_axis[1], limiares_pw[1], length.out=n_points)), colour="black") +
            geom_line(aes(x=seq(limiares_pl[3], x_axis[2], length.out=n_points), y=rep(limiares_pw[2], n_points)), colour="black")

Cada micro-região representa uma folha. O espaço preditor visto em 2D está segmentado.

Agora, pondo para ilustração da estratificação, representamos o espaço preditor 2D de um modelo aleatório como uma figura 3D, cada nível descreve a profundidade do ramal.

Vista superior da figura 3D:

Outro detalhe importante sobre a divisão do espaço, trabalhando-se com AD, é o que não acontece, como abaixo:


Como construir a árvore?

– Ideia Básica:

  1. Dentro do espaço preditor do conjunto de treinamento \(S\), escolha o melhor preditor \(A\) e o seu melhor valor de limiar como decisão para a raiz da árvore.
  2. Divida o espaço \(S\) em subsets \({S_1, S_2, ..., S_k}\), em que cada subset \(S_i\) contém amostras de mesmo valor resultante da decisão escolhida em 1.
  3. Recursivamente, aplique os passos 1 e 2 para cada novo subset \(S_i\) até que todos os nós contenham somente elementos da mesma classe.

Como escolher o melhor preditor feature no nó da árvore?

  1. Taxa do erro? -> Não converge bem, não é sensitiva ao crescimento da árvore.

  2. Índice Gini -> Medida de impureza do nó.

\[G({s_i}) = \sum_{k=1}^{K}{\hat{p}_{{s_i}k}(1-\hat{p}_{{s_i}k})}\]

  1. Entropia -> Quantidade de desordem, e pelo conceito da teoria de informação, a quantidade de bits necessário para guardar a variabilidade da informação.

\[E({s_i}) = -\sum_{k=1}^K{\hat{p}_{{s_i}k}\log{{\hat{p}}_{{s_i}k}}}\]


O Comportamento das funções em relação a distribuição das classes dentro do nó:

f_gini <- function(p){ p*(1-p) + (1-p)*(1-(1-p)) }
f_entr <- function(p){ ifelse(p%in%c(0,1), 0, 
                              - (p*log(p, base=2) + (1-p)*log((1-p), base=2)))}

ps <- seq(0, 1, length.out=100)
y_gini <- sapply(ps, f_gini)
y_entr <- sapply(ps, f_entr)

ggplot(tibble(probs=ps)) +
        geom_line(aes(x=probs, y=y_gini, colour="Gini")) +
        geom_line(aes(x=probs, y=y_entr, colour="Entropia")) +
        theme_bw()

 

Exemplo da escolha do critério de decisão usando Entropia.

Para simplificação do caso, continuamos somente no espaço 2-D dos preditores Petal Length e Petal Width.

1 - Medição de entropia no Nó Raiz.

O espaço Preditor original é mostrado abaixo, no caso \(S_1\), a raiz da árvore.

S1 <- train %>%  select(Petal.Length, Petal.Width, Species)

ggplot(S1, aes(x=Petal.Length, Petal.Width)) + 
        geom_point(aes(colour=Species)) +
        theme_bw()

Para calcular a entropia, precisamos somente da probabilidade de cada classe.

\[E({s_i}) = -\sum_{k=1}^K{\hat{p}_{{s_i}k}\log{{\hat{p}}_{{s_i}k}}}\]

S1 %>% group_by(Species) %>% 
        summarise(quantidade=n()) %>%
        mutate(prob=quantidade/sum(quantidade)) %>% 
        mutate(prob=round(prob, 3))

Computando a somatória do nó:

E_S1 <- - (0.339*log(0.339, base=2) + 0.339*log(0.339, base=2) + 0.321*log(0.321, base=2)) 
print(E_S1)

 

2 - Critério para divisão:

Para decidir em quais dos pontos de decisão ocorrerá a divisão, usa-se o conceito de Ganho de Informação, que é a diferença de entropia entre os nós-filhos e o nó-pai.

\[\Delta{E} = p(S_{1})E(S_{1}) - \Big[p(S_{11})E(S_{11}) + p(S_{12})E(S_{12})\Big]\]

No nosso caso temos duas variáveis contínuas. Para cada atributo, todos os valores dentro do domínio daquela variável são testados como ponto de decisão, o que obter o maior ganho de informação é o selecionado.

fs_entropy <- function(S){
        if( nrow(S)==0){
                return( 0)
        }
        
        list_p <- S %>% 
                group_by(Species) %>% 
                summarise(quantidade=n()) %>%                   
                mutate(prob=quantidade/sum(quantidade)) %>% 
                .$prob
        
        list_p <- list_p[list_p > 0 | list_p < 1]
        entropia <- list_p %>% 
                        sapply(X=., FUN=function(p){ -p*log(p, base=2) }) %>%
                        sum()
        
        return(entropia)
}


delta_E <- tibble(attr=character(), thrs=numeric(), gain=numeric())
E_S1 <- fs_entropy(S1)
for( A in colnames(S1)[1:2]){
        range <- S1 %>% 
                select(eval(parse(text=A))) %>% 
                range()
        range_seq <- seq(range[1], range[2], 0.01)
        
        for( i in range_seq){
                S11 <- S1 %>% filter(get(A, pos=.) >= i)
                S12 <- S1 %>% filter(!get(A, pos=.) >= i)
                
                E_S11 <- fs_entropy(S11)
                E_S12 <- fs_entropy(S12)
                
                dE <- E_S1 - (1/nrow(S1))*(nrow(S11)*E_S11 + nrow(S12)*E_S12)        
                
                delta_E <- rbind.data.frame(delta_E, tibble(A, i, dE))
        
        }
        
        
}

ggplot(delta_E, aes(x=i, y=dE)) + 
        geom_line(aes(colour=A)) +
        facet_wrap(~A, nrow=1, scales="free_x") +
        theme_bw()

Aqui há um empate entre os dois atributos que antigem o mesmo o máximo valor de ganho. Em petal.length, o valor máximo se repete em uma faixa do domínio.

delta_E %>% filter(dE==max(dE)) %>%  filter(A=="Petal.Length") %>%  select(i) %>%  range()

O valor médio dessa faixa é exatamente o valor de corte escolhido na nossa primeira árvore. (O número aparente no diagrama árvore é arrendondado e por isso não bate exatamente.)

(1.91+3)/2

 

3 - Repetir o passo 1 e 2.

Replicar o processo de divisão com todos os novos nós-filhos gerados.

 


Podagem da Árvore e Overfitting

\[\sum_{m=1}^{|T|}\sum_{x_i\in{S_m}}(y_i - \hat{y}_{R_m})^2 + \alpha|T|\]

Após a árvore completamente modelada, existe uma tendência do algoritmo super ajustar os limites de decisão ao conjunto de treinamento, o algoritmo acaba aprendendo as informações particulares do conjunto, que são ruídos da informação, e não necessariamente se repetirão para o conjunto de teste.

Para a podagem, adiciona-se um regulador \(\alpha\) que penaliza cada vez que o comprimento da árvore \(|T|\) aumenta. A função convergirá no momento que o ganho da informação ao dividir o não não compensar o fator de punição.

tree.fit.pr <- prune(tree.fit, cp=0.1)
fancyRpartPlot(tree.fit.pr, sub="")

 


Comparando predição entre a árvore inteira e a podada.

Os dois primeiros números são a acertividade sobre o conjunto de treinamento, o primeiro valor é o da árvore inteira e o segundo a da podada, como esperado a da árvore inteira se sobressai em performance nesse conjunto, pois possui mais profundidade e engloba todas nuances do conjunto.

train.pred.1 <- predict(tree.fit, train, type="class")
## acertividade no trainset
mean(train.pred.1==train$Species) %>% round(3) %>% `*`(., 100)
train.pred.2 <- predict(tree.fit.pr, train, type="class")
## acertividade no trainset
mean(train.pred.2==train$Species) %>% round(3) %>% `*`(., 100)

 

Agora no conjunto de teste, a performance de ambos são equivalentes. A árvore podada, de estrutura menos complexa, consegue atender igualmente as predições para as amostras novas.

test.pred.1 <- predict(tree.fit, test, type="class")
## acertividade no testset
mean(test.pred.1==test$Species) %>% round(3) %>% `*`(., 100)
test.pred.2 <- predict(tree.fit.pr, test, type="class")
## acertividade no testset
mean(test.pred.2==test$Species) %>% round(3) %>% `*`(., 100)

 


Elencamentos

Construções de modelos bem mais poderosos usando árvores.

Bagging

Boosting

Random Forest

 


O Lado A e B dos modelos de Árvore de Decisão

Prós

  • Boa interpretabilidade.
  • Funciona bem para todos os tipos de dados (caractere, fator, númerico, booleano).
  • Insensível a outliers.
  • Fácil de implementar.

Contras

  • Não performa bem para limites lineares ou suaves.
  • Tendência a overfitting (segue demasiado o ruído)
  • Não competitvo aos melhores algortimos de aprendizado supervisionado. Contudo com os métodos de elencamento, é extremamente poderoso, mas perde a interpretabilidade.

 

LS0tCnRpdGxlOiAiw4Fydm9yZXMgZGUgRGVjaXPDo28iCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0CiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdAptYXRoamF4OiBsb2NhbAotLS0KCioqKgojIyBPIFByb2JsZW1hCgpFc3NlIG5vdGVib29rIGludHJvZHV6IGNvbmNlaXRvcyBkbyBhbGdvcml0bW8gZGUgKirDgXJ2b3JlIGRlIERlY2lzw6NvKiogKEFEKSwgZSBwYXJhIGlsdXN0cmHDp8O1ZXMgZG8gZmF0bywgZmHDp28gdXNvIGRvIGRhdGFzZXQgKsONcmlzKi4KCmBgYHtyLCBtZXNzYWdlPUYsIGVjaG89VCwgcmVzdWx0cz1GfQojIGVzdGFiZWxlY2VuZG8gbyBhbWJpZW50ZQpzdXBwcmVzc01lc3NhZ2VzKHsKICAgICAgICBsaWJyYXJ5KHRpZHl2ZXJzZSkKICAgICAgICBsaWJyYXJ5KG1hZ3JpdHRyKQogICAgICAgIGxpYnJhcnkoa25pdHIpCiAgICAgICAgbGlicmFyeShHR2FsbHkpCiAgICAgICAgbGlicmFyeShycGFydCkKICAgICAgICBsaWJyYXJ5KHJhdHRsZSkKICAgICAgICBsaWJyYXJ5KHJwYXJ0LnV0aWxzKQogICAgICAgIH0pICAKCnNldHdkKCJ+L0Ryb3Bib3gva2FnZ2xlL2lyaXMtc3BlY2llcy8iKSAgCm9wdHNfY2h1bmskc2V0KGNhY2hlPVRSVUUpICAKCmRhdGEoaXJpcykKaXJpcyAlPD4lICBhc190aWJibGUoKQpgYGAKCk8gZGF0YXNldCDDjXJpcyBjb250w6ltIG1lZGlkYXMgZGUgY29tcHJpbWVudG8gZSBsYXJndXJhIGRhIHDDqXRhbGEgZSBkYSBzw6lwYWxhIGRlIHRyw6pzIGVzcMOpY2llcyBkZSDDrXJpczogICAKCioqVmVyc2ljb2xvcioqICAKPGltZyBzcmM9Ii4vcGljcy9pcmlzLXZlcnNpY29sb3IuanBnIiB3aWR0aD0iMzAwIiBoZWlnaHQ9IjMwMCIgLz4KJm5ic3A7CgoqKlNldG9zYSoqICAKPGltZyBzcmM9Ii4vcGljcy9pcmlzLXNldG9zYS5qcGciIHdpZHRoPSIzMDAiIGhlaWdodD0iMjAwIiAvPgombmJzcDsKCioqVmlyZ8OtbmljYSoqICAKPGltZyBzcmM9Ii4vcGljcy9pcmlzLXZpcmdpbmljYS5qcGciIHdpZHRoPSIzMDAiIGhlaWdodD0iMjAwIiAvPgombmJzcDsKCioqRXN0cnV0dXJhIGRhIGZsb3I6KiogIAo8aW1nIHNyYz0iLi9waWNzL21vcmZvbG9naWEtZGEtZmxvci5qcGciIHdpZHRoPSIzMDAiIGhlaWdodD0iMjAwIiAvPgoKKioqCgpPIGRhdGFzZXQgY29udMOpbSAxNTAgYW1vc3RyYXMsIDUwIGRlIGNhZGEgZXNww6ljaWUuIAoKYGBge3J9CmlyaXMgJT4lICBoZWFkKCkgJT4lIHByaW50KCkKYGBgCgoqKioKIyMgQSBtaXNzw6NvCgpDb25oZWNlciBvIGFsZ29yaXRtbyBkZSDDgXJ2b3JlIGRlIERlY2lzw6NvIGUgY3JpYXIgdW0gbW9kZWxvIHBvc3PDrXZlbCBkZSBwcmV2ZXIgYSBlc3DDqWNpZSBkZSDDrXJpcyBhIHBhcnRpciBkYXMgbWVkaWRhcyBkYSBww6l0YWxhIGUgc8OpcGFsYS4KCkFudGVzIGRhIG1vZGVsYWdlbSBlIGFuw6FsaXNlIGVtIGdlcmFsLCBvIHByaW1laXJvIHBhc3NvIMOpIGRpdmlkaXIgbyBjb25qdW50byBlbSBkYWRvcyBkZSB0cmVpbmFtZW50byBlIGRlIHRlc3RlLiBQYXJhIG7Do28gY3JpYXJtb3MgIHVtICpiaWFzKiBxdWUgaW5mbHVlbmNpZSBuYXMgcHJlZGnDp8O1ZXMgZmVpdGFzIHNvYnJlIG8gY29uanVudG8gZGUgdGVzdGUuCgpgYGB7cn0Kc2V0LnNlZWQoNjU0KQp0cmFpbl9pZHggPC0gc2FtcGxlKG5yb3coaXJpcyksIC43NSpucm93KGlyaXMpKQp0cmFpbiA8LSBpcmlzICU+JSBzbGljZSh0cmFpbl9pZHgpCnRlc3QgPC0gaXJpcyAlPiUgc2xpY2UoLXRyYWluX2lkeCkKYGBgCgpObyBkYXRhc2V0IGNvbnN0YW0gNCBwcmVkaXRvcmVzLCDDqSBpbnRlcmVzc2FudGUgY2hlY2FyIG8gbsOtdmVsIGRlIGNvcnJlbGHDp8OjbyBkZWxlcywgZSBvdSBzZXBhcmHDp8OjbyBubyBwbGFubyBxdWUgZGVsaW5laWUgYXMgY2xhc3NlcyBxdWUgcXVlcmVtb3MgY2xhc3NpZmljYXIuCgpgYGB7ciwgbWVzc2FnZT1GLCB3YXJuaW5nPUYsIHJlc3VsdHM9Rn0KdHJhaW4gJT4lCiAgICAgICAgZ2dwYWlycyhhZXMoY29sb3VyPVNwZWNpZXMpLCBjb2x1bW5zPTE6NCwgbG93ZXI9ImJsYW5rIiwgZGlhZz0iYmxhbmsiLCAKICAgICAgICAgICAgICAgICAgdXBwZXI9bGlzdChjb250aW51b3VzPSJwb2ludHMiKSkgKyB0aGVtZV9idygpIApgYGAKCgpBbGd1bWFzIHZhcmnDoXZlaXMgYXByZXNlbnRhbSB1bWEgY29ycmVsYcOnw6NvIGJlbSBmb3J0ZSwgY29tbyBgUGV0YWwuTGVuZ3RoYCBlIGBQZXRhbC5XaWR0aGAsIGUgYXMgcmVnacO1ZXMgZGUgdGFtYW5obyBkYXMgcMOpdGFsYXMgdGVtIHVtYSBhcHJlc2VudGFtIHVtYSBkaWZlcmVuY2lhw6fDo28gYmVtIGNsYXJhIGRhcyBjbGFzc2VzLgoKJm5ic3A7CgoKCioqKgojIyBBcHJlbmRpemFkbyBkZSBNw6FxdWluYQoKRGVudHJvIGRvIGVzcGHDp28gUHJlZGl0b3IgJFxMYXJnZXtYfSQgZW5jb250cmFyIGEgbWVsaG9yIHNlcGFyYcOnw6NvIGRlIHN1Yi1lc3Bhw6dvcyBxdWUgZGVzaWduZW0gY2FkYSBjbGFzc2UuCgoKJCQgXExhcmdle1xtYXRoY2Fse0Z9fSA6IFggXHJpZ2h0YXJyb3cgWSAkJAoKKioqCiMjIMOBcnZvcmUgZGUgRGVjaXPDo28KCi0gTcOpdG9kbyBkZSBjbGFzc2lmaWNhw6fDo28gZSByZWdyZXNzw6NvIG7Do28tcGFyYW3DqXRyaWNvIGUgbsOjby1saW5lYXIuCi0gRW52b2x2ZSBlc3RyYXRpZmljYcOnw6NvIGUgc2VnbWVudGHDp8OjbyBkbyBlc3Bhw6dvIGRlIHByZWRpdG9yZXMgZW0gdW0gbsO6bWVybyBkZSBwZXF1ZW5hcyByZWdpw7Vlcy4KCioqKgoKIyMjIERlZmluacOnw7VlcwogCk9saGFuZG8gdW0gZXhlbXBsbyBnZW7DqXJpY28gZGUgIEFEIHBhcmEgbyBjYXNvIMOtcmlzIG5hIGZpZ3VyYSBhYmFpeG8sIG5lc3NlIGV4ZW1wbG8gc8OzIGNvbnN0YW0gMiBwcmVkaXRvcmVzLgogCi0gQWJvcmRhZ2VtIFRvcC1Eb3duIC0+IGltYWdpbmEgYSDDoXJ2b3JlIGludmVydGlkYSwgbyBjdW1lIGRvIGdyYWZvIMOpIGEgKipSYWl6KiouCi0gT3MgbsOzcyBpbnRlcm5vcyBzw6NvICoqUmFtYWlzKiouCi0gT3MgbsOzcyB0ZXJtaW5haXMgc8OjbyAqKkZvbGhhcyoqLCBjYWRhIHF1YWwgZGVzaWduYSB1bWEgY2xhc3NlIGUgcmVwcmVzZW50YW0gbWljcm9zLXJlZ2nDtWVzIG9yaXVuZGFzIGRhIGRpdmlzw6NvIGRvIGVzcGHDp28gZGUgUHJlZGl0b3Jlcy4KCgpgYGB7cn0KdHJlZS5maXQgPC0gcnBhcnQoIlNwZWNpZXMgfiBQZXRhbC5MZW5ndGggKyBQZXRhbC5XaWR0aCIsIHRyYWluLCBjb250cm9sPXJwYXJ0LmNvbnRyb2woY3A9MCwgbWluYnVja2V0PTEpKQpmYW5jeVJwYXJ0UGxvdCh0cmVlLmZpdCwgc3ViPSIiKQpgYGAKCioqKgojIyMgUmVnacO1ZXMgZGUgZGVjaXPDo28KCmBgYHtyfQpsaW1pYXJlcyA8LSBycGFydC5zdWJydWxlcy50YWJsZSh0cmVlLmZpdCkgJT4lIAogICAgICAgICAgICAgICAgYXNfdGliYmxlKCkgJT4lCiAgICAgICAgICAgICAgICBzZWxlY3QoVmFyaWFibGUsIExlc3MpICU+JSAKICAgICAgICAgICAgICAgIGZpbHRlcighaXMubmEoTGVzcykpICU+JSAKICAgICAgICAgICAgICAgIG11dGF0ZShMZXNzPWFzLm51bWVyaWMoYXMuY2hhcmFjdGVyKExlc3MpKSkKICAgICAgICAgICAgICAgIApsaW1pYXJlc19wbCA8LSBsaW1pYXJlcyAlPiUgZmlsdGVyKFZhcmlhYmxlPT0iUGV0YWwuTGVuZ3RoIikgJT4lICAuJExlc3MgICAgICAgICAgIApsaW1pYXJlc19wdyA8LSBsaW1pYXJlcyAlPiUgZmlsdGVyKFZhcmlhYmxlPT0iUGV0YWwuV2lkdGgiKSAlPiUgIC4kTGVzcyAgICAgICAgICAgCgp4X2F4aXMgPC0gdHJhaW4kUGV0YWwuTGVuZ3RoICU+JSByYW5nZSgpCnlfYXhpcyA8LSB0cmFpbiRQZXRhbC5XaWR0aCAlPiUgcmFuZ2UoKQpuX3BvaW50cyA8LSB0cmFpbiAlPiUgbnJvdygpCiAgICAgICAgICAgIAp0cmFpbiAlPiUgZ2dwbG90KGFlcyh4PVBldGFsLkxlbmd0aCwgeT1QZXRhbC5XaWR0aCwgY29sb3VyPVNwZWNpZXMpKSArCiAgICAgICAgICAgIGdlb21fcG9pbnQoKSArIHRoZW1lX2J3KCkgKyBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzPXNlcSgxMCwxKSkgKwogICAgICAgICAgICBnZW9tX2xpbmUoYWVzKHg9cmVwKGxpbWlhcmVzX3BsWzFdLCBuX3BvaW50cyksIHk9c2VxKHlfYXhpc1sxXSwgeV9heGlzWzJdLCBsZW5ndGgub3V0PW5fcG9pbnRzKSksIGNvbG91cj0iYmxhY2siKSArIAogICAgICAgICAgICBnZW9tX2xpbmUoYWVzKHg9c2VxKGxpbWlhcmVzX3BsWzJdLCB4X2F4aXNbMl0sIGxlbmd0aC5vdXQ9bl9wb2ludHMpLCB5PXJlcChsaW1pYXJlc19wd1sxXSwgbl9wb2ludHMpKSwgY29sb3VyPSJibGFjayIpICsKICAgICAgICAgICAgZ2VvbV9saW5lKGFlcyh4PXJlcChsaW1pYXJlc19wbFsyXSwgbl9wb2ludHMpLCB5PXNlcSh5X2F4aXNbMV0sIHlfYXhpc1syXSwgbGVuZ3RoLm91dD1uX3BvaW50cykpLCBjb2xvdXI9ImJsYWNrIikgKwogICAgICAgICAgICBnZW9tX2xpbmUoYWVzKHg9c2VxKGxpbWlhcmVzX3BsWzJdLCB4X2F4aXNbMl0sIGxlbmd0aC5vdXQ9bl9wb2ludHMpLCB5PXJlcChsaW1pYXJlc19wd1sxXSwgbl9wb2ludHMpKSwgY29sb3VyPSJibGFjayIpICsKICAgICAgICAgICAgZ2VvbV9saW5lKGFlcyh4PXJlcChsaW1pYXJlc19wbFszXSwgbl9wb2ludHMpLCB5PXNlcSh5X2F4aXNbMV0sIGxpbWlhcmVzX3B3WzFdLCBsZW5ndGgub3V0PW5fcG9pbnRzKSksIGNvbG91cj0iYmxhY2siKSArCiAgICAgICAgICAgIGdlb21fbGluZShhZXMoeD1zZXEobGltaWFyZXNfcGxbM10sIHhfYXhpc1syXSwgbGVuZ3RoLm91dD1uX3BvaW50cyksIHk9cmVwKGxpbWlhcmVzX3B3WzJdLCBuX3BvaW50cykpLCBjb2xvdXI9ImJsYWNrIikKCmBgYAoKQ2FkYSBtaWNyby1yZWdpw6NvIHJlcHJlc2VudGEgdW1hIGZvbGhhLiBPIGVzcGHDp28gcHJlZGl0b3IgdmlzdG8gZW0gMkQgZXN0w6Egc2VnbWVudGFkby4gCgpBZ29yYSwgcG9uZG8gcGFyYSBpbHVzdHJhw6fDo28gZGEgZXN0cmF0aWZpY2HDp8OjbywgcmVwcmVzZW50YW1vcyBvIGVzcGHDp28gcHJlZGl0b3IgMkQgZGUgdW0gbW9kZWxvIGFsZWF0w7NyaW8gY29tbyB1bWEgZmlndXJhIDNELCBjYWRhIG7DrXZlbCBkZXNjcmV2ZSBhIHByb2Z1bmRpZGFkZSBkbyByYW1hbC4gIAoKPGRpdiBzdHlsZT0id2lkdGg6MzAwcHg7IGhlaWdodD0yMDBweCI+CiFbXSguL3BpY3Mvc3RyYXRpZmljYXRpb24tM0QucG5nKQo8L2Rpdj4KClZpc3RhIHN1cGVyaW9yIGRhIGZpZ3VyYSAzRDoKPGRpdiBzdHlsZT0id2lkdGg6MzAwcHg7IGhlaWdodD0yMDBweCI+CiFbXSguL3BpY3Mvby1xdWUtYS1hcnZvcmUtZmF6LnBuZykKPC9kaXY+CgoKKk91dHJvIGRldGFsaGUgaW1wb3J0YW50ZSBzb2JyZSBhIGRpdmlzw6NvIGRvIGVzcGHDp28sIHRyYWJhbGhhbmRvLXNlIGNvbSBBRCwgw6kgbyBxdWUgbsOjbyBhY29udGVjZSwgY29tbyBhYmFpeG86KgoKPGRpdiBzdHlsZT0id2lkdGg6MzAwcHg7IGhlaWdodD0yMDBweCI+CiFbXSguL3BpY3Mvby1xdWUtYS1hcnZvcmUtTkFPLWZhei5wbmcpCjwvZGl2PgoKCgoqKioKIyMjIENvbW8gY29uc3RydWlyIGEgw6Fydm9yZT8KCioqLS0gSWRlaWEgQsOhc2ljYToqKgoKMS4gRGVudHJvIGRvIGVzcGHDp28gcHJlZGl0b3IgZG8gY29uanVudG8gZGUgdHJlaW5hbWVudG8gJFMkLCBlc2NvbGhhIG8gbWVsaG9yIHByZWRpdG9yICRBJCBlIG8gc2V1IG1lbGhvciB2YWxvciBkZSBsaW1pYXIgY29tbyBkZWNpc8OjbyBwYXJhIGEgcmFpeiBkYSDDoXJ2b3JlLgoyLiBEaXZpZGEgbyBlc3Bhw6dvICRTJCBlbSBzdWJzZXRzICR7U18xLCBTXzIsIC4uLiwgU19rfSQsIGVtIHF1ZSBjYWRhIHN1YnNldCAkU19pJCBjb250w6ltIGFtb3N0cmFzIGRlIG1lc21vIHZhbG9yIHJlc3VsdGFudGUgZGEgZGVjaXPDo28gZXNjb2xoaWRhIGVtICoqMSoqLgozLiBSZWN1cnNpdmFtZW50ZSwgYXBsaXF1ZSBvcyBwYXNzb3MgKioxKiogZSAqKjIqKiBwYXJhIGNhZGEgbm92byBzdWJzZXQgJFNfaSQgYXTDqSBxdWUgdG9kb3Mgb3MgKm7Ds3MqIGNvbnRlbmhhbSBzb21lbnRlIGVsZW1lbnRvcyBkYSBtZXNtYSBjbGFzc2UuCgoqKkNvbW8gZXNjb2xoZXIgbyBtZWxob3IgcHJlZGl0b3IgKmZlYXR1cmUqIG5vIG7DsyBkYSDDoXJ2b3JlPyoqCgoxLiBUYXhhIGRvIGVycm8/IC0+IE7Do28gY29udmVyZ2UgYmVtLCBuw6NvIMOpIHNlbnNpdGl2YSBhbyBjcmVzY2ltZW50byBkYSDDoXJ2b3JlLgoKMi4gw41uZGljZSBHaW5pIC0+IE1lZGlkYSBkZSBpbXB1cmV6YSBkbyBuw7MuCgokJEcoe3NfaX0pID0gXHN1bV97az0xfV57S317XGhhdHtwfV97e3NfaX1rfSgxLVxoYXR7cH1fe3tzX2l9a30pfSQkCgozLiBFbnRyb3BpYSAtPiBRdWFudGlkYWRlIGRlIGRlc29yZGVtLCBlIHBlbG8gY29uY2VpdG8gZGEgKnRlb3JpYSBkZSBpbmZvcm1hw6fDo28qLCBhIHF1YW50aWRhZGUgZGUgYml0cyBuZWNlc3PDoXJpbyBwYXJhIGd1YXJkYXIgYSB2YXJpYWJpbGlkYWRlIGRhIGluZm9ybWHDp8Ojby4KCiQkRSh7c19pfSkgPSAtXHN1bV97az0xfV5Le1xoYXR7cH1fe3tzX2l9a31cbG9ne3tcaGF0e3B9fV97e3NfaX1rfX19JCQKCioqKgpPIENvbXBvcnRhbWVudG8gZGFzIGZ1bsOnw7VlcyBlbSByZWxhw6fDo28gYSBkaXN0cmlidWnDp8OjbyBkYXMgY2xhc3NlcyBkZW50cm8gZG8gbsOzOgpgYGB7cn0KZl9naW5pIDwtIGZ1bmN0aW9uKHApeyBwKigxLXApICsgKDEtcCkqKDEtKDEtcCkpIH0KZl9lbnRyIDwtIGZ1bmN0aW9uKHApeyBpZmVsc2UocCVpbiVjKDAsMSksIDAsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIChwKmxvZyhwLCBiYXNlPTIpICsgKDEtcCkqbG9nKCgxLXApLCBiYXNlPTIpKSl9CgpwcyA8LSBzZXEoMCwgMSwgbGVuZ3RoLm91dD0xMDApCnlfZ2luaSA8LSBzYXBwbHkocHMsIGZfZ2luaSkKeV9lbnRyIDwtIHNhcHBseShwcywgZl9lbnRyKQoKZ2dwbG90KHRpYmJsZShwcm9icz1wcykpICsKICAgICAgICBnZW9tX2xpbmUoYWVzKHg9cHJvYnMsIHk9eV9naW5pLCBjb2xvdXI9IkdpbmkiKSkgKwogICAgICAgIGdlb21fbGluZShhZXMoeD1wcm9icywgeT15X2VudHIsIGNvbG91cj0iRW50cm9waWEiKSkgKwogICAgICAgIHRoZW1lX2J3KCkKCmBgYAoKJm5ic3A7CgojIyMjICoqRXhlbXBsbyBkYSBlc2NvbGhhIGRvIGNyaXTDqXJpbyBkZSBkZWNpc8OjbyB1c2FuZG8gRW50cm9waWEuKioKClBhcmEgc2ltcGxpZmljYcOnw6NvIGRvIGNhc28sIGNvbnRpbnVhbW9zIHNvbWVudGUgbm8gZXNwYcOnbyAyLUQgZG9zIHByZWRpdG9yZXMgYFBldGFsIExlbmd0aGAgZSBgUGV0YWwgV2lkdGhgLgoKCioqMSAtIE1lZGnDp8OjbyBkZSBlbnRyb3BpYSBubyBOw7MgUmFpei4qKgoKTyBlc3Bhw6dvIFByZWRpdG9yIG9yaWdpbmFsIMOpIG1vc3RyYWRvIGFiYWl4bywgbm8gY2FzbyAkU18xJCwgYSByYWl6IGRhIMOhcnZvcmUuCgpgYGB7cn0KUzEgPC0gdHJhaW4gJT4lICBzZWxlY3QoUGV0YWwuTGVuZ3RoLCBQZXRhbC5XaWR0aCwgU3BlY2llcykKCmdncGxvdChTMSwgYWVzKHg9UGV0YWwuTGVuZ3RoLCBQZXRhbC5XaWR0aCkpICsgCiAgICAgICAgZ2VvbV9wb2ludChhZXMoY29sb3VyPVNwZWNpZXMpKSArCiAgICAgICAgdGhlbWVfYncoKQpgYGAKCgpQYXJhIGNhbGN1bGFyIGEgZW50cm9waWEsIHByZWNpc2Ftb3Mgc29tZW50ZSBkYSBwcm9iYWJpbGlkYWRlIGRlIGNhZGEgY2xhc3NlLgoKJCRFKHtzX2l9KSA9IC1cc3VtX3trPTF9Xkt7XGhhdHtwfV97e3NfaX1rfVxsb2d7e1xoYXR7cH19X3t7c19pfWt9fX0kJAoKYGBge3J9ClMxICU+JSBncm91cF9ieShTcGVjaWVzKSAlPiUgCiAgICAgICAgc3VtbWFyaXNlKHF1YW50aWRhZGU9bigpKSAlPiUKICAgICAgICBtdXRhdGUocHJvYj1xdWFudGlkYWRlL3N1bShxdWFudGlkYWRlKSkgJT4lIAogICAgICAgIG11dGF0ZShwcm9iPXJvdW5kKHByb2IsIDMpKQpgYGAKCkNvbXB1dGFuZG8gYSBzb21hdMOzcmlhIGRvIG7DszoKYGBge3J9CkVfUzEgPC0gLSAoMC4zMzkqbG9nKDAuMzM5LCBiYXNlPTIpICsgMC4zMzkqbG9nKDAuMzM5LCBiYXNlPTIpICsgMC4zMjEqbG9nKDAuMzIxLCBiYXNlPTIpKSAKcHJpbnQoRV9TMSkKYGBgCgombmJzcDsKCgoqKjIgLSBDcml0w6lyaW8gcGFyYSBkaXZpc8OjbzoqKiAKClBhcmEgZGVjaWRpciBlbSBxdWFpcyBkb3MgcG9udG9zIGRlIGRlY2lzw6NvIG9jb3JyZXLDoSBhIGRpdmlzw6NvLCB1c2Etc2UgbyBjb25jZWl0byBkZSAqKkdhbmhvIGRlIEluZm9ybWHDp8OjbyoqLCBxdWUgw6kgYSBkaWZlcmVuw6dhIGRlIGVudHJvcGlhIGVudHJlIG9zIG7Ds3MtZmlsaG9zIGUgbyBuw7MtcGFpLgoKJCRcRGVsdGF7RX0gPSBwKFNfezF9KUUoU197MX0pIC0gXEJpZ1twKFNfezExfSlFKFNfezExfSkgKyBwKFNfezEyfSlFKFNfezEyfSlcQmlnXSQkCgpObyBub3NzbyBjYXNvIHRlbW9zIGR1YXMgdmFyacOhdmVpcyBjb250w61udWFzLiBQYXJhIGNhZGEgYXRyaWJ1dG8sIHRvZG9zIG9zIHZhbG9yZXMgZGVudHJvIGRvIGRvbcOtbmlvIGRhcXVlbGEgdmFyacOhdmVsIHPDo28gdGVzdGFkb3MgY29tbyBwb250byBkZSBkZWNpc8OjbywgbyBxdWUgb2J0ZXIgbyBtYWlvciAqZ2FuaG8gZGUgaW5mb3JtYcOnw6NvKiDDqSBvIHNlbGVjaW9uYWRvLgoKYGBge3J9CmZzX2VudHJvcHkgPC0gZnVuY3Rpb24oUyl7CiAgICAgICAgaWYoIG5yb3coUyk9PTApewogICAgICAgICAgICAgICAgcmV0dXJuKCAwKQogICAgICAgIH0KICAgICAgICAKICAgICAgICBsaXN0X3AgPC0gUyAlPiUgCiAgICAgICAgICAgICAgICBncm91cF9ieShTcGVjaWVzKSAlPiUgCiAgICAgICAgICAgICAgICBzdW1tYXJpc2UocXVhbnRpZGFkZT1uKCkpICU+JSAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIG11dGF0ZShwcm9iPXF1YW50aWRhZGUvc3VtKHF1YW50aWRhZGUpKSAlPiUgCiAgICAgICAgICAgICAgICAuJHByb2IKICAgICAgICAKICAgICAgICBsaXN0X3AgPC0gbGlzdF9wW2xpc3RfcCA+IDAgfCBsaXN0X3AgPCAxXQogICAgICAgIGVudHJvcGlhIDwtIGxpc3RfcCAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgIHNhcHBseShYPS4sIEZVTj1mdW5jdGlvbihwKXsgLXAqbG9nKHAsIGJhc2U9MikgfSkgJT4lCiAgICAgICAgICAgICAgICAgICAgICAgIHN1bSgpCiAgICAgICAgCiAgICAgICAgcmV0dXJuKGVudHJvcGlhKQp9CgoKZGVsdGFfRSA8LSB0aWJibGUoYXR0cj1jaGFyYWN0ZXIoKSwgdGhycz1udW1lcmljKCksIGdhaW49bnVtZXJpYygpKQpFX1MxIDwtIGZzX2VudHJvcHkoUzEpCmZvciggQSBpbiBjb2xuYW1lcyhTMSlbMToyXSl7CiAgICAgICAgcmFuZ2UgPC0gUzEgJT4lIAogICAgICAgICAgICAgICAgc2VsZWN0KGV2YWwocGFyc2UodGV4dD1BKSkpICU+JSAKICAgICAgICAgICAgICAgIHJhbmdlKCkKICAgICAgICByYW5nZV9zZXEgPC0gc2VxKHJhbmdlWzFdLCByYW5nZVsyXSwgMC4wMSkKICAgICAgICAKICAgICAgICBmb3IoIGkgaW4gcmFuZ2Vfc2VxKXsKICAgICAgICAgICAgICAgIFMxMSA8LSBTMSAlPiUgZmlsdGVyKGdldChBLCBwb3M9LikgPj0gaSkKICAgICAgICAgICAgICAgIFMxMiA8LSBTMSAlPiUgZmlsdGVyKCFnZXQoQSwgcG9zPS4pID49IGkpCiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIEVfUzExIDwtIGZzX2VudHJvcHkoUzExKQogICAgICAgICAgICAgICAgRV9TMTIgPC0gZnNfZW50cm9weShTMTIpCiAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgIGRFIDwtIEVfUzEgLSAoMS9ucm93KFMxKSkqKG5yb3coUzExKSpFX1MxMSArIG5yb3coUzEyKSpFX1MxMikgICAgICAgIAogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICBkZWx0YV9FIDwtIHJiaW5kLmRhdGEuZnJhbWUoZGVsdGFfRSwgdGliYmxlKEEsIGksIGRFKSkKICAgICAgICAKICAgICAgICB9CiAgICAgICAgCiAgICAgICAgCn0KCmdncGxvdChkZWx0YV9FLCBhZXMoeD1pLCB5PWRFKSkgKyAKICAgICAgICBnZW9tX2xpbmUoYWVzKGNvbG91cj1BKSkgKwogICAgICAgIGZhY2V0X3dyYXAofkEsIG5yb3c9MSwgc2NhbGVzPSJmcmVlX3giKSArCiAgICAgICAgdGhlbWVfYncoKQoKYGBgCgoKQXF1aSBow6EgdW0gZW1wYXRlIGVudHJlIG9zIGRvaXMgYXRyaWJ1dG9zIHF1ZSBhbnRpZ2VtIG8gbWVzbW8gbyBtw6F4aW1vIHZhbG9yIGRlIGdhbmhvLiAgRW0gYHBldGFsLmxlbmd0aGAsIG8gdmFsb3IgbcOheGltbyBzZSByZXBldGUgZW0gdW1hIGZhaXhhIGRvIGRvbcOtbmlvLgoKYGBge3J9CmRlbHRhX0UgJT4lIGZpbHRlcihkRT09bWF4KGRFKSkgJT4lICBmaWx0ZXIoQT09IlBldGFsLkxlbmd0aCIpICU+JSAgc2VsZWN0KGkpICU+JSAgcmFuZ2UoKQpgYGAKCk8gdmFsb3IgbcOpZGlvIGRlc3NhIGZhaXhhIMOpIGV4YXRhbWVudGUgbyB2YWxvciBkZSBjb3J0ZSBlc2NvbGhpZG8gbmEgbm9zc2EgcHJpbWVpcmEgw6Fydm9yZS4gKCpPIG7Dum1lcm8gYXBhcmVudGUgbm8gZGlhZ3JhbWEgw6Fydm9yZSDDqSBhcnJlbmRvbmRhZG8gZSBwb3IgaXNzbyBuw6NvIGJhdGUgZXhhdGFtZW50ZS4qKQoKYGBge3J9CigxLjkxKzMpLzIKYGBgCgombmJzcDsKCioqMyAtIFJlcGV0aXIgbyBwYXNzbyAxIGUgMi4qKgoKUmVwbGljYXIgbyBwcm9jZXNzbyBkZSBkaXZpc8OjbyBjb20gdG9kb3Mgb3Mgbm92b3MgbsOzcy1maWxob3MgZ2VyYWRvcy4KCiZuYnNwOwoKKioqCiMjIyBQb2RhZ2VtIGRhIMOBcnZvcmUgZSBPdmVyZml0dGluZwoKPGltZyBzcmM9Ii4vcGljcy90cmVlLXBydW5pbmctZ3VpZGUuanBnIiB3aWR0aD0iMzAwIiBoZWlnaHQ9IjIwMCIgLz4KCgokJFxzdW1fe209MX1ee3xUfH1cc3VtX3t4X2lcaW57U19tfX0oeV9pIC0gXGhhdHt5fV97Ul9tfSleMiArIFxhbHBoYXxUfCQkCgpBcMOzcyBhIMOhcnZvcmUgY29tcGxldGFtZW50ZSBtb2RlbGFkYSwgZXhpc3RlIHVtYSB0ZW5kw6puY2lhIGRvIGFsZ29yaXRtbyBzdXBlciBhanVzdGFyIG9zIGxpbWl0ZXMgZGUgZGVjaXPDo28gYW8gY29uanVudG8gZGUgdHJlaW5hbWVudG8sIG8gYWxnb3JpdG1vIGFjYWJhIGFwcmVuZGVuZG8gYXMgaW5mb3JtYcOnw7VlcyBwYXJ0aWN1bGFyZXMgZG8gY29uanVudG8sIHF1ZSBzw6NvIHJ1w61kb3MgZGEgaW5mb3JtYcOnw6NvLCBlIG7Do28gbmVjZXNzYXJpYW1lbnRlIHNlIHJlcGV0aXLDo28gcGFyYSBvIGNvbmp1bnRvIGRlIHRlc3RlLgoKUGFyYSBhIHBvZGFnZW0sIGFkaWNpb25hLXNlIHVtIHJlZ3VsYWRvciAkXGFscGhhJCBxdWUgcGVuYWxpemEgY2FkYSB2ZXogcXVlIG8gY29tcHJpbWVudG8gZGEgw6Fydm9yZSAkfFR8JCBhdW1lbnRhLiBBIGZ1bsOnw6NvIGNvbnZlcmdpcsOhIG5vIG1vbWVudG8gcXVlIG8gZ2FuaG8gZGEgaW5mb3JtYcOnw6NvIGFvIGRpdmlkaXIgbyBuw6NvIG7Do28gY29tcGVuc2FyIG8gZmF0b3IgZGUgcHVuacOnw6NvLiAKCmBgYHtyfQp0cmVlLmZpdC5wciA8LSBwcnVuZSh0cmVlLmZpdCwgY3A9MC4xKQpmYW5jeVJwYXJ0UGxvdCh0cmVlLmZpdC5wciwgc3ViPSIiKQpgYGAKCiZuYnNwOwoKKioqCiMjIyBDb21wYXJhbmRvIHByZWRpw6fDo28gZW50cmUgYSDDoXJ2b3JlIGludGVpcmEgZSBhIHBvZGFkYS4KCk9zIGRvaXMgcHJpbWVpcm9zIG7Dum1lcm9zIHPDo28gYSBhY2VydGl2aWRhZGUgc29icmUgbyBjb25qdW50byBkZSB0cmVpbmFtZW50bywgbyBwcmltZWlybyB2YWxvciDDqSBvIGRhIMOhcnZvcmUgaW50ZWlyYSBlIG8gc2VndW5kbyBhIGRhIHBvZGFkYSwgY29tbyBlc3BlcmFkbyBhIGRhIMOhcnZvcmUgaW50ZWlyYSBzZSBzb2JyZXNzYWkgZW0gcGVyZm9ybWFuY2UgbmVzc2UgY29uanVudG8sIHBvaXMgcG9zc3VpIG1haXMgcHJvZnVuZGlkYWRlIGUgZW5nbG9iYSB0b2RhcyBudWFuY2VzIGRvIGNvbmp1bnRvLiAKCmBgYHtyfQp0cmFpbi5wcmVkLjEgPC0gcHJlZGljdCh0cmVlLmZpdCwgdHJhaW4sIHR5cGU9ImNsYXNzIikKIyMgYWNlcnRpdmlkYWRlIG5vIHRyYWluc2V0Cm1lYW4odHJhaW4ucHJlZC4xPT10cmFpbiRTcGVjaWVzKSAlPiUgcm91bmQoMykgJT4lIGAqYCguLCAxMDApCmBgYAoKYGBge3J9CnRyYWluLnByZWQuMiA8LSBwcmVkaWN0KHRyZWUuZml0LnByLCB0cmFpbiwgdHlwZT0iY2xhc3MiKQojIyBhY2VydGl2aWRhZGUgbm8gdHJhaW5zZXQKbWVhbih0cmFpbi5wcmVkLjI9PXRyYWluJFNwZWNpZXMpICU+JSByb3VuZCgzKSAlPiUgYCpgKC4sIDEwMCkKYGBgCgombmJzcDsKCkFnb3JhIG5vIGNvbmp1bnRvIGRlIHRlc3RlLCBhIHBlcmZvcm1hbmNlIGRlIGFtYm9zIHPDo28gZXF1aXZhbGVudGVzLiBBIMOhcnZvcmUgcG9kYWRhLCBkZSBlc3RydXR1cmEgbWVub3MgY29tcGxleGEsIGNvbnNlZ3VlIGF0ZW5kZXIgaWd1YWxtZW50ZSBhcyBwcmVkacOnw7VlcyBwYXJhIGFzIGFtb3N0cmFzIG5vdmFzLgoKCmBgYHtyfQp0ZXN0LnByZWQuMSA8LSBwcmVkaWN0KHRyZWUuZml0LCB0ZXN0LCB0eXBlPSJjbGFzcyIpCiMjIGFjZXJ0aXZpZGFkZSBubyB0ZXN0c2V0Cm1lYW4odGVzdC5wcmVkLjE9PXRlc3QkU3BlY2llcykgJT4lIHJvdW5kKDMpICU+JSBgKmAoLiwgMTAwKQpgYGAKCgpgYGB7cn0KdGVzdC5wcmVkLjIgPC0gcHJlZGljdCh0cmVlLmZpdC5wciwgdGVzdCwgdHlwZT0iY2xhc3MiKQojIyBhY2VydGl2aWRhZGUgbm8gdGVzdHNldAptZWFuKHRlc3QucHJlZC4yPT10ZXN0JFNwZWNpZXMpICU+JSByb3VuZCgzKSAlPiUgYCpgKC4sIDEwMCkKCmBgYAoKJm5ic3A7CgoqKioKIyMgRWxlbmNhbWVudG9zCgo+IENvbnN0cnXDp8O1ZXMgZGUgbW9kZWxvcyBiZW0gbWFpcyBwb2Rlcm9zb3MgdXNhbmRvIMOhcnZvcmVzLgoKKipCYWdnaW5nKioKCi0gQm9vdHN0cmFwIGUgY29tYmluYcOnw6NvIHBhcmFsZWxhLgoKKipCb29zdGluZyoqCgotIENvbWJpbmHDp8OjbyBzZXJpYWwgZGFzIMOhcnZvcmVzLgoKKipSYW5kb20gRm9yZXN0KioKCi0gQ29tYmluYcOnw6NvIHBhcmFsZWxhIGRhcyDDoXJ2b3JlcyAoICpub3JtYWxtZW50ZSBjb20gY2VudGVuYXMqICkgZSB0cnVxdWVzIGRlIGRlY29ycmVsYcOnw6NvIGVudHJlIGFzIMOhcnZvcmVzLgoKCiZuYnNwOwoKKioqCiMjIE8gTGFkbyBBIGUgQiBkb3MgbW9kZWxvcyBkZSDDgXJ2b3JlIGRlIERlY2lzw6NvCgojIyMgUHLDs3MKCi0gQm9hIGludGVycHJldGFiaWxpZGFkZS4KLSBGdW5jaW9uYSBiZW0gcGFyYSB0b2RvcyBvcyB0aXBvcyBkZSBkYWRvcyAoY2FyYWN0ZXJlLCBmYXRvciwgbsO6bWVyaWNvLCBib29sZWFubykuCi0gSW5zZW5zw612ZWwgYSBvdXRsaWVycy4KLSBGw6FjaWwgZGUgaW1wbGVtZW50YXIuCgojIyMgQ29udHJhcwoKLSBOw6NvIHBlcmZvcm1hIGJlbSBwYXJhIGxpbWl0ZXMgbGluZWFyZXMgb3Ugc3VhdmVzLgotIFRlbmTDqm5jaWEgYSBvdmVyZml0dGluZyAoc2VndWUgZGVtYXNpYWRvIG8gKnJ1w61kbyopCi0gTsOjbyBjb21wZXRpdHZvIGFvcyBtZWxob3JlcyBhbGdvcnRpbW9zIGRlIGFwcmVuZGl6YWRvIHN1cGVydmlzaW9uYWRvLiBDb250dWRvIGNvbSBvcyBtw6l0b2RvcyBkZSBlbGVuY2FtZW50bywgw6kgZXh0cmVtYW1lbnRlIHBvZGVyb3NvLCBtYXMgcGVyZGUgYSBpbnRlcnByZXRhYmlsaWRhZGUuCgombmJzcDsK