# install.packages("FactoMineR", "gglot2", "readxl")
# install.packages("devtools")
# devtools::install_github("kassambara/factoextra")
library("ggplot2")
library("FactoMineR")
library("factoextra")
library("readxl")
library("gplots")
library("corrplot")
library("graphics")
library("foreign")
library("readxl")

Ler os dados e atribuir ordem para perfil e aplicação

# ler os dados
investidor <- read_excel("perfil investidor aplicacao.xlsx")
investidor
# atribuir níveis ao perfil e tipo sw aplicação
investidor$perfil <- factor(investidor$perfil, levels = c("Conservador", "Moderado", "Agressivo"))
investidor$aplicacao <-factor(investidor$aplicacao, levels = c("Poupança", "CDB", "Ações"))
tab <- table(investidor$perfil, investidor$aplicacao)
tab
             
              Poupança CDB Ações
  Conservador        8   4     5
  Moderado           5  16     4
  Agressivo          2  20    36
# 1. convert the data as a table
dt <- as.table(as.matrix(tab))
# 2. Graph
balloonplot(t(dt), main ="Tabela de contingência - Perfil x Aplicação ", xlab ="", ylab="",
            label = T, show.margins = T)

As células com os resíduos padronizados absolutos mais elevados contribuem mais para a pontuação total do Qui-quadrado.

chisq <- chisq.test(tab)
Aproxima攼㸷攼㸳o do qui-quadrado pode estar incorreta
chisq

    Pearson's Chi-squared test

data:  tab
X-squared = 31.764, df = 4, p-value = 2.138e-06
# Observed counts
chisq$observed
             
              Poupança CDB Ações
  Conservador        8   4     5
  Moderado           5  16     4
  Agressivo          2  20    36
# Expected counts 
round(chisq$expected,2)
             
              Poupança  CDB Ações
  Conservador     2.55  6.8  7.65
  Moderado        3.75 10.0 11.25
  Agressivo       8.70 23.2 26.10
# residuals padronizados 
round(chisq$residuals, 2)
             
              Poupança   CDB Ações
  Conservador     3.41 -1.07 -0.96
  Moderado        0.65  1.90 -2.16
  Agressivo      -2.27 -0.66  1.94
corrplot(chisq$residuals, is.cor = FALSE)

A contribuição (em %) de uma dada célula para a pontuação total do Qui-quadrado

### Contibution in percentage (%)
contrib <- 100*chisq$residuals^2/chisq$statistic
round(contrib, 3)
             
              Poupança    CDB  Ações
  Conservador   36.670  3.630  2.890
  Moderado       1.312 11.334 14.709
  Agressivo     16.244  1.390 11.822
### Visualize the contribution
corrplot(contrib, is.cor = FALSE)

A inércia total (\(\phi^2\)) é a quantidade de informação contida na tabela de dados

phi2 <- as.numeric(chisq$statistic/sum(tab))
phi2
[1] 0.3176416

o valor \(\phi\) > 0,2 indica uma dependência significativa entre linhas e colunas

O gráfico mosaico é usado para visualizar uma tabela de contingência para examinar a associação entre as variáveis categóricas.

### Mosaic plot of observed values
mosaicplot(tab,  las=2, col="steelblue",
           main = "Países.Disclosure - observed counts")

### Mosaic plot of expected values
mosaicplot(chisq$expected,  las=2, col = "gray",
           main = "Países.Disclosure - expected counts")

Nessas parcelas, as variáveis de coluna são primeiramente divididas (divisão vertical) e, em seguida, as variáveis de linha são divididas (divisão horizontal).

Para cada célula, a altura das barras é proporcional à frequência relativa observada que contém:

O gráfico azul, é o gráfico de mosaico dos valores observados.

O cinzento é o gráfico em mosaico dos valores esperados sob hipótese nula.

Se as variáveis de linha e coluna fossem completamente independentes, as barras de mosaico para os valores observados (gráfico azul) seriam alinhadas como as barras de mosaico para os valores esperados (gráfico cinza).

Também é possível colorir o mosaico de acordo com o valor dos resíduos padronizados:

mosaicplot(tab, shade = TRUE, las=2,main = "Países.Disclosure")

Análise de correspondência

Análise de correspondência (ANACOR) é necessária para a tabela de contingência grande.

Aplica-se para visualizar pontos da linha e pontos da coluna em um espaço dimensional reduzido.

ANACOR é um método de redução dimensional aplicado a uma tabela de contingência.

A informação retida por cada dimensão é chamada autovalor.

A informação total (ou inércia) contida nos dados é chamada phi (\(\phi^2\)) e pode ser calculado da seguinte forma:

\(\phi^2 = \frac{\chi^2}{grand.total}\)

A análise de correspondência é usada para representar graficamente a tabela de distâncias entre variáveis de linha ou entre variáveis de coluna.

A abordagem ANACOR inclui as seguintes etapas:

1.Calcular os resíduos padronizados

Os resíduos padronizados (S) são:

\(S = \frac{o - e}{\sqrt{e}}\)

De fato, S é apenas a raíz quadrada dos termos que compõem a estatística \(\chi^2\).

2. Calcule a decomposição do valor singular (SVD) dos resíduos padronizados.

\(M = \frac{1}{sqrt(grand.total)} \times S\)

SVD significa que queremos encontrar matrizes ortogonais U e V, em conjunto com uma matriz diagonal \(\Delta\), tal que:

\(M = U \Delta V^T\)

O autovalor de um determinado eixo é:

\(\lambda = \delta^2\)

\(\delta\) É o valor singular

As coordenadas das variáveis de linha em um determinado eixo são:

\(row.coord = \frac{U * \delta }{\sqrt{row.mass}}\)

As coordenadas das colunas são

\(col.coord = \frac{V * \delta }{\sqrt{col.mass}}\)

cálculo SVD

### Grand total
n <- sum(tab)
n
[1] 100
### Standardized residuals
residuals <- chisq$residuals/sqrt(n)
residuals
             
                 Poupança         CDB       Ações
  Conservador  0.34129224 -0.10737510 -0.09581095
  Moderado     0.06454972  0.18973666 -0.21615324
  Agressivo   -0.22715127 -0.06643638  0.19378267
### Number of dimensions
nb.axes <- min(nrow(residuals)-1, ncol(residuals)-1)
nb.axes
[1] 2
### Singular value decomposition
res.svd <- svd(residuals, nu = nb.axes, nv = nb.axes)
res.svd
$d
[1] 4.829233e-01 2.905629e-01 1.166671e-18

$u
           [,1]       [,2]
[1,] -0.6716248  0.6155649
[2,] -0.3980015 -0.7691520
[3,]  0.6249119  0.1717122

$v
            [,1]       [,2]
[1,] -0.82178847  0.4179279
[2,] -0.09300977 -0.7689923
[3,]  0.56215025  0.4837221
### singular value
sv <- res.svd$d[1:nb.axes] 
u <-res.svd$u
v <- res.svd$v
### Eigenvalues
eig <- sv^2
### Variances in percentage
variance <- eig*100/sum(eig)
### Cumulative variances
cumvar <- cumsum(variance)
eig<- data.frame(eig = eig, variance = variance,
                     cumvariance = cumvar)
head(eig)
barplot(eig[, 2], names.arg=1:nrow(eig), 
       main = "Variances",
       xlab = "Dimensions",
       ylab = "Percentage of variances",
       col ="steelblue")
### Add connected line segments to the plot
lines(x = 1:nrow(eig), eig[, 2], 
      type="b", pch=19, col = "red")

Quantas dimensões reter ?

1. O número máximo de eixos na ANACOR é:

\(nb.axes = min(r-1, c-1)\)

r e c são respectivamente o número de linhas e colunas na tabela.

Row coordinates

### row sum
row.sum <- apply(tab, 1, sum)
row.sum
Conservador    Moderado   Agressivo 
         17          25          58 
### row mass
row.mass <- row.sum/n
row.mass
Conservador    Moderado   Agressivo 
       0.17        0.25        0.58 
### row coord = sv * u /sqrt(row.mass)
cc <- t(apply(u, 1, '*', sv)) ### each row X sv
row.coord <- apply(cc, 2, '/', sqrt(row.mass))
rownames(row.coord) <- rownames(tab)
colnames(row.coord) <- paste0("Dim.", 1:nb.axes)
round(row.coord,3)
             Dim.1  Dim.2
Conservador -0.787  0.434
Moderado    -0.384 -0.447
Agressivo    0.396  0.066
### plot
plot(row.coord, pch=19, col = "blue")
text(row.coord, labels =rownames(row.coord), pos = 3, col ="blue")
abline(v=0, h=0, lty = 2)

Column coordinates

### Coordinates of columns
col.sum <- apply(tab, 2, sum)
col.mass <- col.sum/n
### coordinates sv * v /sqrt(col.mass)
cc <- t(apply(v, 1, '*', sv))
col.coord <- apply(cc, 2, '/', sqrt(col.mass))
rownames(col.coord) <- colnames(tab)
colnames(col.coord) <- paste0("Dim", 1:nb.axes)
head(col.coord)
                Dim1       Dim2
Poupança -1.02469008  0.3135421
CDB      -0.07101935 -0.3532906
Ações     0.40469167  0.2095221
### plot
plot(col.coord, pch=17, col = "red")
text(col.coord, labels =rownames(col.coord), pos = 3, col ="red")
abline(v=0, h=0, lty = 2)

Biplot de linhas e colunas para ver a associação

xlim <- range(c(row.coord[,1], col.coord[,1]))*1.1
ylim <- range(c(row.coord[,2], col.coord[,2]))*1.1
### Plot of rows
plot(row.coord, pch=19, col = "blue", xlim = xlim, ylim = ylim)
text(row.coord, labels =rownames(row.coord), pos = 3, col ="blue")
### plot off columns
points(col.coord, pch=17, col = "red")
text(col.coord, labels =rownames(col.coord), pos = 3, col ="red")
abline(v=0, h=0, lty = 2)

Você pode interpretar a distância entre pontos de linhas ou entre pontos de coluna, mas a distância entre pontos de coluna e pontos de linha não são significativos.

Diagnóstico

Lembre-se que, a inércia total contida nos dados é:

\(\phi^2 = \frac{\chi^2}{n} = 0.3176\)

Nosso gráfico bidimensional captura 100% da inércia total da tabela.

Contribuição de linhas e colunas

As contribuições de uma linha / coluna para a definição de um eixo principal são:

\(row.contrib = \frac{row.mass * row.coord^2}{eigenvalue}\)

\(col.contrib = \frac{col.mass * col.coord^2}{eigenvalue}\)

Contribuição de linhas em %

### contrib <- row.mass * row.coord^2/eigenvalue
cc <- apply(row.coord^2, 2, "*", row.mass)
row.contrib <- t(apply(cc, 1, "/", eig[1:nb.axes,1])) *100
round(row.contrib, 2)
            Dim.1 Dim.2
Conservador 45.11 37.89
Moderado    15.84 59.16
Agressivo   39.05  2.95
corrplot(row.contrib, is.cor = FALSE)

Contribuição das colunas em %

### contrib <- col.mass * col.coord^2/eigenvalue
cc <- apply(col.coord^2, 2, "*", col.mass)
col.contrib <- t(apply(cc, 1, "/", eig[1:nb.axes,1])) *100
round(col.contrib, 2)
          Dim1  Dim2
Poupança 67.53 17.47
CDB       0.87 59.13
Ações    31.60 23.40
corrplot(col.contrib, is.cor = FALSE)

Qualidade da representação

A qualidade da representação é chamada COS2.

A qualidade da representação de uma linha em um eixo é:

\(row.cos2 = \frac{row.coord^2}{d^2}\)

Lembre-se de que a distância entre cada perfil de linha e o perfil de linha médio é:

\(d^2(row_i, average.profile) = \sum{\frac{(row.profile_i - average.profile)^2}{average.profile}}\)

row.profile <- tab/row.sum
head(round(row.profile, 3))
             
              Poupança   CDB Ações
  Conservador    0.471 0.235 0.294
  Moderado       0.200 0.640 0.160
  Agressivo      0.034 0.345 0.621
average.profile <- col.sum/n
head(round(average.profile, 3))
Poupança      CDB    Ações 
    0.15     0.40     0.45 

O código R abaixo calcula a distância do perfil médio para todas as variáveis de linha

average.rp <- col.sum/n 
d2.row <- apply(row.profile, 1, 
                function(row.p, av.p){sum(((row.p - av.p)^2)/av.p)}, 
                average.rp)
head(round(d2.row,3))
Conservador    Moderado   Agressivo 
      0.807       0.348       0.161 

O cos2 de linhas no mapa de fatores são:

row.cos2 <- apply(row.coord^2, 2, "/", d2.row)
round(row.cos2, 3)
            Dim.1 Dim.2
Conservador 0.767 0.233
Moderado    0.425 0.575
Agressivo   0.973 0.027
corrplot(row.cos2, is.cor = FALSE)

Cos2 das colunas

\(col.cos2 = \frac{col.coord^2}{d^2}\)

col.profile <- t(tab)/col.sum
col.profile <- t(col.profile)
###head(round(col.profile, 3))
average.profile <- row.sum/n
###head(round(average.profile, 3))

O código R abaixo calcula a distância do perfil médio para todas as variáveis da coluna

d2.col <- apply(col.profile, 2, 
        function(col.p, av.p){sum(((col.p - av.p)^2)/av.p)}, 
        average.profile)
round(d2.col,3)
Poupança      CDB    Ações 
   1.148    0.130    0.208 

O cos2 das colunas no mapa de fatores são:

col.cos2 <- apply(col.coord^2, 2, "/", d2.col)
round(col.cos2, 3)
          Dim1  Dim2
Poupança 0.914 0.086
CDB      0.039 0.961
Ações    0.789 0.211
corrplot(col.cos2, is.cor = FALSE)

Pacotes no R para ANACOR

-FactoMineR -ade4 -ca

library(FactoMineR)
res.ca <- CA(tab, graph = F)
res.ca
**Results of the Correspondence Analysis (CA)**
The row variable has  3  categories; the column variable has 3 categories
The chi square of independence between the two variables is equal to 31.76416 (p-value =  2.137594e-06 ).
*The results are available in the following objects:

   name              description                   
1  "$eig"            "eigenvalues"                 
2  "$col"            "results for the columns"     
3  "$col$coord"      "coord. for the columns"      
4  "$col$cos2"       "cos2 for the columns"        
5  "$col$contrib"    "contributions of the columns"
6  "$row"            "results for the rows"        
7  "$row$coord"      "coord. for the rows"         
8  "$row$cos2"       "cos2 for the rows"           
9  "$row$contrib"    "contributions of the rows"   
10 "$call"           "summary called parameters"   
11 "$call$marge.col" "weights of the columns"      
12 "$call$marge.row" "weights of the rows"         
### eigenvalue
head(res.ca$eig)[, 1:2]
### barplot of percentage of variance
barplot(res.ca$eig[,2], names.arg = rownames(res.ca$eig))

### Plot row points
plot(res.ca, invisible ="col")

### Plot column points
plot(res.ca, invisible ="row")

### Biplot of rows and columns
plot(res.ca)

LS0tDQp0aXRsZTogJ0Fuw6FsaXNlIGRlIENvcnJlc3BvbmTDqm5jaWEgLSBwZXJmaWwgaW52ZXN0aWRvciBhcGxpY2FjYW8gLSBBTkFDT1IgJw0KYXV0aG9yOiAiTGVvbmksIFIuIEMuIFByb2Zlc3NvciBEci4iDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdA0KLS0tDQoqKioNCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IEYpDQpybShsaXN0PWxzKGFsbD1UUlVFKSkNCmBgYA0KDQpgYGB7cn0NCiMgaW5zdGFsbC5wYWNrYWdlcygiRmFjdG9NaW5lUiIsICJnZ2xvdDIiLCAicmVhZHhsIikNCiMgaW5zdGFsbC5wYWNrYWdlcygiZGV2dG9vbHMiKQ0KIyBkZXZ0b29sczo6aW5zdGFsbF9naXRodWIoImthc3NhbWJhcmEvZmFjdG9leHRyYSIpDQpsaWJyYXJ5KCJnZ3Bsb3QyIikNCmxpYnJhcnkoIkZhY3RvTWluZVIiKQ0KbGlicmFyeSgiZmFjdG9leHRyYSIpDQpsaWJyYXJ5KCJyZWFkeGwiKQ0KbGlicmFyeSgiZ3Bsb3RzIikNCmxpYnJhcnkoImNvcnJwbG90IikNCmxpYnJhcnkoImdyYXBoaWNzIikNCmxpYnJhcnkoImZvcmVpZ24iKQ0KbGlicmFyeSgicmVhZHhsIikNCmBgYA0KDQojIyMgTGVyIG9zIGRhZG9zIGUgYXRyaWJ1aXIgb3JkZW0gcGFyYSBwZXJmaWwgZSBhcGxpY2HDp8Ojbw0KYGBge3J9DQojIGxlciBvcyBkYWRvcw0KaW52ZXN0aWRvciA8LSByZWFkX2V4Y2VsKCJwZXJmaWwgaW52ZXN0aWRvciBhcGxpY2FjYW8ueGxzeCIpDQppbnZlc3RpZG9yDQoNCiMgYXRyaWJ1aXIgbsOtdmVpcyBhbyBwZXJmaWwgZSB0aXBvIHN3IGFwbGljYcOnw6NvDQppbnZlc3RpZG9yJHBlcmZpbCA8LSBmYWN0b3IoaW52ZXN0aWRvciRwZXJmaWwsIGxldmVscyA9IGMoIkNvbnNlcnZhZG9yIiwgIk1vZGVyYWRvIiwgIkFncmVzc2l2byIpKQ0KaW52ZXN0aWRvciRhcGxpY2FjYW8gPC1mYWN0b3IoaW52ZXN0aWRvciRhcGxpY2FjYW8sIGxldmVscyA9IGMoIlBvdXBhbsOnYSIsICJDREIiLCAiQcOnw7VlcyIpKQ0KDQp0YWIgPC0gdGFibGUoaW52ZXN0aWRvciRwZXJmaWwsIGludmVzdGlkb3IkYXBsaWNhY2FvKQ0KdGFiDQpgYGANCg0KYGBge3IsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTh9DQoNCiMgMS4gY29udmVydCB0aGUgZGF0YSBhcyBhIHRhYmxlDQpkdCA8LSBhcy50YWJsZShhcy5tYXRyaXgodGFiKSkNCiMgMi4gR3JhcGgNCmJhbGxvb25wbG90KHQoZHQpLCBtYWluID0iVGFiZWxhIGRlIGNvbnRpbmfDqm5jaWEgLSBQZXJmaWwgeCBBcGxpY2HDp8OjbyAiLCB4bGFiID0iIiwgeWxhYj0iIiwNCiAgICAgICAgICAgIGxhYmVsID0gVCwgc2hvdy5tYXJnaW5zID0gVCkNCg0KYGBgDQoNCiMjIyBBcyBjw6lsdWxhcyBjb20gb3MgcmVzw61kdW9zIHBhZHJvbml6YWRvcyBhYnNvbHV0b3MgbWFpcyBlbGV2YWRvcyBjb250cmlidWVtIG1haXMgcGFyYSBhIHBvbnR1YcOnw6NvIHRvdGFsIGRvIFF1aS1xdWFkcmFkby4NCg0KYGBge3J9DQpjaGlzcSA8LSBjaGlzcS50ZXN0KHRhYikNCmNoaXNxDQojIE9ic2VydmVkIGNvdW50cw0KY2hpc3Ekb2JzZXJ2ZWQNCiMgRXhwZWN0ZWQgY291bnRzIA0Kcm91bmQoY2hpc3EkZXhwZWN0ZWQsMikNCiMgcmVzaWR1YWxzIHBhZHJvbml6YWRvcyANCnJvdW5kKGNoaXNxJHJlc2lkdWFscywgMikNCg0KY29ycnBsb3QoY2hpc3EkcmVzaWR1YWxzLCBpcy5jb3IgPSBGQUxTRSkNCg0KYGBgDQoNCg0KIyMjIEEgY29udHJpYnVpw6fDo28gKGVtICUpIGRlIHVtYSBkYWRhIGPDqWx1bGEgcGFyYSBhIHBvbnR1YcOnw6NvIHRvdGFsIGRvIFF1aS1xdWFkcmFkbw0KDQpgYGB7cn0NCiMjIyBDb250aWJ1dGlvbiBpbiBwZXJjZW50YWdlICglKQ0KY29udHJpYiA8LSAxMDAqY2hpc3EkcmVzaWR1YWxzXjIvY2hpc3Ekc3RhdGlzdGljDQpyb3VuZChjb250cmliLCAzKQ0KIyMjIFZpc3VhbGl6ZSB0aGUgY29udHJpYnV0aW9uDQpjb3JycGxvdChjb250cmliLCBpcy5jb3IgPSBGQUxTRSkNCmBgYA0KDQoNCiMjIyBBIGluw6lyY2lhIHRvdGFsICgkXHBoaV4yJCkgw6kgYSBxdWFudGlkYWRlIGRlIGluZm9ybWHDp8OjbyBjb250aWRhIG5hIHRhYmVsYSBkZSBkYWRvcw0KIA0KYGBge3J9DQpwaGkyIDwtIGFzLm51bWVyaWMoY2hpc3Ekc3RhdGlzdGljL3N1bSh0YWIpKQ0KcGhpMg0KYGBgDQoNCg0KIyMjIG8gdmFsb3IgJFxwaGkkID4gMCwyIGluZGljYSB1bWEgZGVwZW5kw6puY2lhIHNpZ25pZmljYXRpdmEgZW50cmUgbGluaGFzIGUgY29sdW5hcyANCiMjIyBPIGdyw6FmaWNvIG1vc2FpY28gw6kgdXNhZG8gcGFyYSB2aXN1YWxpemFyIHVtYSB0YWJlbGEgZGUgY29udGluZ8OqbmNpYSBwYXJhIGV4YW1pbmFyIGEgYXNzb2NpYcOnw6NvIGVudHJlIGFzIHZhcmnDoXZlaXMgY2F0ZWfDs3JpY2FzLg0KDQpgYGB7cn0NCiMjIyBNb3NhaWMgcGxvdCBvZiBvYnNlcnZlZCB2YWx1ZXMNCm1vc2FpY3Bsb3QodGFiLCAgbGFzPTIsIGNvbD0ic3RlZWxibHVlIiwNCiAgICAgICAgICAgbWFpbiA9ICJQYcOtc2VzLkRpc2Nsb3N1cmUgLSBvYnNlcnZlZCBjb3VudHMiKQ0KDQojIyMgTW9zYWljIHBsb3Qgb2YgZXhwZWN0ZWQgdmFsdWVzDQptb3NhaWNwbG90KGNoaXNxJGV4cGVjdGVkLCAgbGFzPTIsIGNvbCA9ICJncmF5IiwNCiAgICAgICAgICAgbWFpbiA9ICJQYcOtc2VzLkRpc2Nsb3N1cmUgLSBleHBlY3RlZCBjb3VudHMiKQ0KYGBgDQoNCiMjIyBOZXNzYXMgcGFyY2VsYXMsIGFzIHZhcmnDoXZlaXMgZGUgY29sdW5hIHPDo28gcHJpbWVpcmFtZW50ZSBkaXZpZGlkYXMgKGRpdmlzw6NvIHZlcnRpY2FsKSBlLCBlbSBzZWd1aWRhLCBhcyB2YXJpw6F2ZWlzIGRlIGxpbmhhIHPDo28gZGl2aWRpZGFzIChkaXZpc8OjbyBob3Jpem9udGFsKS4gDQojIyMgUGFyYSBjYWRhIGPDqWx1bGEsIGEgYWx0dXJhIGRhcyBiYXJyYXMgw6kgcHJvcG9yY2lvbmFsIMOgIGZyZXF1w6puY2lhIHJlbGF0aXZhIG9ic2VydmFkYSBxdWUgY29udMOpbToNCiMjIyBPIGdyw6FmaWNvIGF6dWwsIMOpIG8gZ3LDoWZpY28gZGUgbW9zYWljbyBkb3MgdmFsb3JlcyBvYnNlcnZhZG9zLg0KIyMjIE8gY2luemVudG8gw6kgbyBncsOhZmljbyBlbSBtb3NhaWNvIGRvcyB2YWxvcmVzIGVzcGVyYWRvcyBzb2IgaGlww7N0ZXNlIG51bGEuDQojIyMgU2UgYXMgdmFyacOhdmVpcyBkZSBsaW5oYSBlIGNvbHVuYSBmb3NzZW0gY29tcGxldGFtZW50ZSBpbmRlcGVuZGVudGVzLCBhcyBiYXJyYXMgZGUgbW9zYWljbyBwYXJhIG9zIHZhbG9yZXMgb2JzZXJ2YWRvcyAoZ3LDoWZpY28gYXp1bCkgc2VyaWFtIGFsaW5oYWRhcyBjb21vIGFzIGJhcnJhcyBkZSBtb3NhaWNvIHBhcmEgb3MgdmFsb3JlcyBlc3BlcmFkb3MgKGdyw6FmaWNvIGNpbnphKS4NCiMjIyBUYW1iw6ltIMOpIHBvc3PDrXZlbCBjb2xvcmlyIG8gbW9zYWljbyBkZSBhY29yZG8gY29tIG8gdmFsb3IgZG9zIHJlc8OtZHVvcyBwYWRyb25pemFkb3M6DQoNCmBgYHtyfQ0KbW9zYWljcGxvdCh0YWIsIHNoYWRlID0gVFJVRSwgbGFzPTIsbWFpbiA9ICJQYcOtc2VzLkRpc2Nsb3N1cmUiKQ0KYGBgDQoNCg0KDQojIyMgIEFuw6FsaXNlIGRlIGNvcnJlc3BvbmTDqm5jaWENCiMjIyBBbsOhbGlzZSBkZSBjb3JyZXNwb25kw6puY2lhIChBTkFDT1IpIMOpIG5lY2Vzc8OhcmlhIHBhcmEgYSB0YWJlbGEgZGUgY29udGluZ8OqbmNpYSBncmFuZGUuDQojIyMgQXBsaWNhLXNlIHBhcmEgdmlzdWFsaXphciBwb250b3MgZGEgbGluaGEgZSBwb250b3MgZGEgY29sdW5hIGVtIHVtIGVzcGHDp28gZGltZW5zaW9uYWwgcmVkdXppZG8uDQojIyMgQU5BQ09SIMOpIHVtIG3DqXRvZG8gZGUgcmVkdcOnw6NvIGRpbWVuc2lvbmFsIGFwbGljYWRvIGEgdW1hIHRhYmVsYSBkZSBjb250aW5nw6puY2lhLiANCiMjIyBBIGluZm9ybWHDp8OjbyByZXRpZGEgcG9yIGNhZGEgZGltZW5zw6NvIMOpIGNoYW1hZGEgYXV0b3ZhbG9yLg0KIyMjIEEgaW5mb3JtYcOnw6NvIHRvdGFsIChvdSBpbsOpcmNpYSkgY29udGlkYSBub3MgZGFkb3Mgw6kgY2hhbWFkYSBwaGkgKCRccGhpXjIkKSBlIHBvZGUgc2VyIGNhbGN1bGFkbyBkYSBzZWd1aW50ZSBmb3JtYToNCg0KJFxwaGleMiA9IFxmcmFje1xjaGleMn17Z3JhbmQudG90YWx9JA0KDQojIyMgQSBhbsOhbGlzZSBkZSBjb3JyZXNwb25kw6puY2lhIMOpIHVzYWRhIHBhcmEgcmVwcmVzZW50YXIgZ3JhZmljYW1lbnRlIGEgdGFiZWxhIGRlIGRpc3TDom5jaWFzIGVudHJlIHZhcmnDoXZlaXMgZGUgbGluaGEgb3UgZW50cmUgdmFyacOhdmVpcyBkZSBjb2x1bmEuDQojIyMgQSBhYm9yZGFnZW0gQU5BQ09SIGluY2x1aSBhcyBzZWd1aW50ZXMgZXRhcGFzOg0KDQojIyMgMS5DYWxjdWxhciBvcyByZXPDrWR1b3MgcGFkcm9uaXphZG9zDQoNCiMjIyBPcyByZXPDrWR1b3MgcGFkcm9uaXphZG9zIChTKSBzw6NvOg0KDQokUyA9IFxmcmFje28gLSBlfXtcc3FydHtlfX0kDQoNCiMjIyBEZSBmYXRvLCBTIMOpIGFwZW5hcyBhIHJhw616IHF1YWRyYWRhIGRvcyB0ZXJtb3MgcXVlIGNvbXDDtWVtIGEgZXN0YXTDrXN0aWNhICRcY2hpXjIkLg0KDQojIyMgMi4gQ2FsY3VsZSBhIGRlY29tcG9zacOnw6NvIGRvIHZhbG9yIHNpbmd1bGFyIChTVkQpIGRvcyByZXPDrWR1b3MgcGFkcm9uaXphZG9zLg0KDQokTSA9IFxmcmFjezF9e3NxcnQoZ3JhbmQudG90YWwpfSBcdGltZXMgUyQNCg0KIyMjIFNWRCBzaWduaWZpY2EgcXVlIHF1ZXJlbW9zIGVuY29udHJhciBtYXRyaXplcyBvcnRvZ29uYWlzIFUgZSBWLCBlbSBjb25qdW50byBjb20gdW1hIG1hdHJpeiBkaWFnb25hbCAkXERlbHRhJCwgdGFsIHF1ZToNCg0KJE0gPSBVIFxEZWx0YSBWXlQkDQoNCi0gVSDDiSB1bWEgbWF0cml6IGNvbnRlbmRvIGF1dG92ZXRvcmVzIGRlIGxpbmhhcw0KLSDOlCDDiSBhIG1hdHJpeiBkaWFnb25hbC4gT3MgbsO6bWVyb3MgbmEgZGlhZ29uYWwgZGEgbWF0cml6IHPDo28gY2hhbWFkb3MgZGUgdmFsb3JlcyBzaW5ndWxhcmVzIChTVikuIE9zIGF1dG92YWxvcmVzIHPDo28gbyBTViBxdWFkcmFkby4NCi0gViDDiSB1bWEgbWF0cml6IGNvbnRlbmRvIGF1dG92ZXRvcmVzIGRlIGNvbHVuYQ0KDQojIyMgTyBhdXRvdmFsb3IgZGUgdW0gZGV0ZXJtaW5hZG8gZWl4byDDqToNCg0KJFxsYW1iZGEgPSBcZGVsdGFeMiQNCg0KJFxkZWx0YSQgw4kgbyB2YWxvciBzaW5ndWxhcg0KDQojIyMgQXMgY29vcmRlbmFkYXMgZGFzIHZhcmnDoXZlaXMgZGUgbGluaGEgZW0gdW0gZGV0ZXJtaW5hZG8gZWl4byBzw6NvOg0KDQokcm93LmNvb3JkID0gXGZyYWN7VSAqIFxkZWx0YSB9e1xzcXJ0e3Jvdy5tYXNzfX0kDQoNCiMjIyBBcyBjb29yZGVuYWRhcyBkYXMgY29sdW5hcyBzw6NvDQoNCiRjb2wuY29vcmQgPSBcZnJhY3tWICogXGRlbHRhIH17XHNxcnR7Y29sLm1hc3N9fSQNCg0KIyMjIGPDoWxjdWxvIFNWRA0KDQpgYGB7cn0NCiMjIyBHcmFuZCB0b3RhbA0KbiA8LSBzdW0odGFiKQ0Kbg0KIyMjIFN0YW5kYXJkaXplZCByZXNpZHVhbHMNCnJlc2lkdWFscyA8LSBjaGlzcSRyZXNpZHVhbHMvc3FydChuKQ0KcmVzaWR1YWxzDQojIyMgTnVtYmVyIG9mIGRpbWVuc2lvbnMNCm5iLmF4ZXMgPC0gbWluKG5yb3cocmVzaWR1YWxzKS0xLCBuY29sKHJlc2lkdWFscyktMSkNCm5iLmF4ZXMNCg0KIyMjIFNpbmd1bGFyIHZhbHVlIGRlY29tcG9zaXRpb24NCnJlcy5zdmQgPC0gc3ZkKHJlc2lkdWFscywgbnUgPSBuYi5heGVzLCBudiA9IG5iLmF4ZXMpDQpyZXMuc3ZkDQoNCiMjIyBzaW5ndWxhciB2YWx1ZQ0Kc3YgPC0gcmVzLnN2ZCRkWzE6bmIuYXhlc10gDQp1IDwtcmVzLnN2ZCR1DQp2IDwtIHJlcy5zdmQkdg0KYGBgDQoNCg0KDQpgYGB7cn0NCiMjIyBFaWdlbnZhbHVlcw0KZWlnIDwtIHN2XjINCiMjIyBWYXJpYW5jZXMgaW4gcGVyY2VudGFnZQ0KdmFyaWFuY2UgPC0gZWlnKjEwMC9zdW0oZWlnKQ0KIyMjIEN1bXVsYXRpdmUgdmFyaWFuY2VzDQpjdW12YXIgPC0gY3Vtc3VtKHZhcmlhbmNlKQ0KZWlnPC0gZGF0YS5mcmFtZShlaWcgPSBlaWcsIHZhcmlhbmNlID0gdmFyaWFuY2UsDQogICAgICAgICAgICAgICAgICAgICBjdW12YXJpYW5jZSA9IGN1bXZhcikNCmhlYWQoZWlnKQ0KYGBgDQoNCg0KYGBge3J9DQpiYXJwbG90KGVpZ1ssIDJdLCBuYW1lcy5hcmc9MTpucm93KGVpZyksIA0KICAgICAgIG1haW4gPSAiVmFyaWFuY2VzIiwNCiAgICAgICB4bGFiID0gIkRpbWVuc2lvbnMiLA0KICAgICAgIHlsYWIgPSAiUGVyY2VudGFnZSBvZiB2YXJpYW5jZXMiLA0KICAgICAgIGNvbCA9InN0ZWVsYmx1ZSIpDQojIyMgQWRkIGNvbm5lY3RlZCBsaW5lIHNlZ21lbnRzIHRvIHRoZSBwbG90DQpsaW5lcyh4ID0gMTpucm93KGVpZyksIGVpZ1ssIDJdLCANCiAgICAgIHR5cGU9ImIiLCBwY2g9MTksIGNvbCA9ICJyZWQiKQ0KYGBgDQojIyMgUXVhbnRhcyBkaW1lbnPDtWVzIHJldGVyID8NCg0KIyMjIDEuIE8gbsO6bWVybyBtw6F4aW1vIGRlIGVpeG9zIG5hIEFOQUNPUiDDqToNCg0KJG5iLmF4ZXMgPSBtaW4oci0xLCBjLTEpJA0KDQojIyNyIGUgYyBzw6NvIHJlc3BlY3RpdmFtZW50ZSBvIG7Dum1lcm8gZGUgbGluaGFzIGUgY29sdW5hcyBuYSB0YWJlbGEuDQoNCiMjIyBSb3cgY29vcmRpbmF0ZXMNCmBgYHtyfQ0KIyMjIHJvdyBzdW0NCnJvdy5zdW0gPC0gYXBwbHkodGFiLCAxLCBzdW0pDQpyb3cuc3VtDQojIyMgcm93IG1hc3MNCnJvdy5tYXNzIDwtIHJvdy5zdW0vbg0Kcm93Lm1hc3MNCiMjIyByb3cgY29vcmQgPSBzdiAqIHUgL3NxcnQocm93Lm1hc3MpDQpjYyA8LSB0KGFwcGx5KHUsIDEsICcqJywgc3YpKSAjIyMgZWFjaCByb3cgWCBzdg0Kcm93LmNvb3JkIDwtIGFwcGx5KGNjLCAyLCAnLycsIHNxcnQocm93Lm1hc3MpKQ0Kcm93bmFtZXMocm93LmNvb3JkKSA8LSByb3duYW1lcyh0YWIpDQpjb2xuYW1lcyhyb3cuY29vcmQpIDwtIHBhc3RlMCgiRGltLiIsIDE6bmIuYXhlcykNCnJvdW5kKHJvdy5jb29yZCwzKQ0KDQoNCiMjIyBwbG90DQpwbG90KHJvdy5jb29yZCwgcGNoPTE5LCBjb2wgPSAiYmx1ZSIpDQp0ZXh0KHJvdy5jb29yZCwgbGFiZWxzID1yb3duYW1lcyhyb3cuY29vcmQpLCBwb3MgPSAzLCBjb2wgPSJibHVlIikNCmFibGluZSh2PTAsIGg9MCwgbHR5ID0gMikNCmBgYA0KDQojIyMgIENvbHVtbiBjb29yZGluYXRlcw0KDQpgYGB7cn0NCiMjIyBDb29yZGluYXRlcyBvZiBjb2x1bW5zDQpjb2wuc3VtIDwtIGFwcGx5KHRhYiwgMiwgc3VtKQ0KY29sLm1hc3MgPC0gY29sLnN1bS9uDQojIyMgY29vcmRpbmF0ZXMgc3YgKiB2IC9zcXJ0KGNvbC5tYXNzKQ0KY2MgPC0gdChhcHBseSh2LCAxLCAnKicsIHN2KSkNCmNvbC5jb29yZCA8LSBhcHBseShjYywgMiwgJy8nLCBzcXJ0KGNvbC5tYXNzKSkNCnJvd25hbWVzKGNvbC5jb29yZCkgPC0gY29sbmFtZXModGFiKQ0KY29sbmFtZXMoY29sLmNvb3JkKSA8LSBwYXN0ZTAoIkRpbSIsIDE6bmIuYXhlcykNCmhlYWQoY29sLmNvb3JkKQ0KDQojIyMgcGxvdA0KcGxvdChjb2wuY29vcmQsIHBjaD0xNywgY29sID0gInJlZCIpDQp0ZXh0KGNvbC5jb29yZCwgbGFiZWxzID1yb3duYW1lcyhjb2wuY29vcmQpLCBwb3MgPSAzLCBjb2wgPSJyZWQiKQ0KYWJsaW5lKHY9MCwgaD0wLCBsdHkgPSAyKQ0KYGBgDQoNCiMjIyBCaXBsb3QgZGUgbGluaGFzIGUgY29sdW5hcyBwYXJhIHZlciBhIGFzc29jaWHDp8Ojbw0KDQpgYGB7cn0NCnhsaW0gPC0gcmFuZ2UoYyhyb3cuY29vcmRbLDFdLCBjb2wuY29vcmRbLDFdKSkqMS4xDQp5bGltIDwtIHJhbmdlKGMocm93LmNvb3JkWywyXSwgY29sLmNvb3JkWywyXSkpKjEuMQ0KIyMjIFBsb3Qgb2Ygcm93cw0KcGxvdChyb3cuY29vcmQsIHBjaD0xOSwgY29sID0gImJsdWUiLCB4bGltID0geGxpbSwgeWxpbSA9IHlsaW0pDQp0ZXh0KHJvdy5jb29yZCwgbGFiZWxzID1yb3duYW1lcyhyb3cuY29vcmQpLCBwb3MgPSAzLCBjb2wgPSJibHVlIikNCiMjIyBwbG90IG9mZiBjb2x1bW5zDQpwb2ludHMoY29sLmNvb3JkLCBwY2g9MTcsIGNvbCA9ICJyZWQiKQ0KdGV4dChjb2wuY29vcmQsIGxhYmVscyA9cm93bmFtZXMoY29sLmNvb3JkKSwgcG9zID0gMywgY29sID0icmVkIikNCmFibGluZSh2PTAsIGg9MCwgbHR5ID0gMikNCmBgYA0KDQojIyMgVm9jw6ogcG9kZSBpbnRlcnByZXRhciBhIGRpc3TDom5jaWEgZW50cmUgcG9udG9zIGRlIGxpbmhhcyBvdSBlbnRyZSBwb250b3MgZGUgY29sdW5hLCBtYXMgYSBkaXN0w6JuY2lhIGVudHJlIHBvbnRvcyBkZSBjb2x1bmEgZSBwb250b3MgZGUgbGluaGEgbsOjbyBzw6NvIHNpZ25pZmljYXRpdm9zLg0KDQojIyMgRGlhZ27Ds3N0aWNvDQoNCiMjI0xlbWJyZS1zZSBxdWUsIGEgaW7DqXJjaWEgdG90YWwgY29udGlkYSBub3MgZGFkb3Mgw6k6DQoNCiRccGhpXjIgPSBcZnJhY3tcY2hpXjJ9e259ID0gMC4zMTc2JA0KDQojIyNOb3NzbyBncsOhZmljbyBiaWRpbWVuc2lvbmFsIGNhcHR1cmEgMTAwJSBkYSBpbsOpcmNpYSB0b3RhbCBkYSB0YWJlbGEuDQoNCiMjIyBDb250cmlidWnDp8OjbyBkZSBsaW5oYXMgZSBjb2x1bmFzDQoNCiMjI0FzIGNvbnRyaWJ1acOnw7VlcyBkZSB1bWEgbGluaGEgLyBjb2x1bmEgcGFyYSBhIGRlZmluacOnw6NvIGRlIHVtIGVpeG8gcHJpbmNpcGFsIHPDo286DQoNCiRyb3cuY29udHJpYiA9IFxmcmFje3Jvdy5tYXNzICogcm93LmNvb3JkXjJ9e2VpZ2VudmFsdWV9JA0KDQokY29sLmNvbnRyaWIgPSBcZnJhY3tjb2wubWFzcyAqIGNvbC5jb29yZF4yfXtlaWdlbnZhbHVlfSQNCg0KIyMjQ29udHJpYnVpw6fDo28gZGUgbGluaGFzIGVtICUNCg0KYGBge3J9DQojIyMgY29udHJpYiA8LSByb3cubWFzcyAqIHJvdy5jb29yZF4yL2VpZ2VudmFsdWUNCmNjIDwtIGFwcGx5KHJvdy5jb29yZF4yLCAyLCAiKiIsIHJvdy5tYXNzKQ0Kcm93LmNvbnRyaWIgPC0gdChhcHBseShjYywgMSwgIi8iLCBlaWdbMTpuYi5heGVzLDFdKSkgKjEwMA0Kcm91bmQocm93LmNvbnRyaWIsIDIpDQoNCmNvcnJwbG90KHJvdy5jb250cmliLCBpcy5jb3IgPSBGQUxTRSkNCmBgYA0KDQojIyNDb250cmlidWnDp8OjbyBkYXMgY29sdW5hcyBlbSAlDQoNCmBgYHtyfQ0KIyMjIGNvbnRyaWIgPC0gY29sLm1hc3MgKiBjb2wuY29vcmReMi9laWdlbnZhbHVlDQpjYyA8LSBhcHBseShjb2wuY29vcmReMiwgMiwgIioiLCBjb2wubWFzcykNCmNvbC5jb250cmliIDwtIHQoYXBwbHkoY2MsIDEsICIvIiwgZWlnWzE6bmIuYXhlcywxXSkpICoxMDANCnJvdW5kKGNvbC5jb250cmliLCAyKQ0KDQpjb3JycGxvdChjb2wuY29udHJpYiwgaXMuY29yID0gRkFMU0UpDQpgYGANCg0KIyMjIFF1YWxpZGFkZSBkYSByZXByZXNlbnRhw6fDo28NCg0KIyMjQSBxdWFsaWRhZGUgZGEgcmVwcmVzZW50YcOnw6NvIMOpIGNoYW1hZGEgQ09TMi4NCg0KIyMjQSBxdWFsaWRhZGUgZGEgcmVwcmVzZW50YcOnw6NvIGRlIHVtYSBsaW5oYSBlbSB1bSBlaXhvIMOpOg0KDQokcm93LmNvczIgPSBcZnJhY3tyb3cuY29vcmReMn17ZF4yfSQNCg0KLSBSb3cuY29vcmQgw6kgYSBjb29yZGVuYWRhIGRhIGxpbmhhIG5vIGVpeG8NCi0gJGReMiQgw4kgYSBkaXN0w6JuY2lhIGFvIHF1YWRyYWRvIGRvIHBlcmZpbCBtw6lkaW8NCg0KIyMjTGVtYnJlLXNlIGRlIHF1ZSBhIGRpc3TDom5jaWEgZW50cmUgY2FkYSBwZXJmaWwgZGUgbGluaGEgZSBvIHBlcmZpbCBkZSBsaW5oYSBtw6lkaW8gw6k6DQoNCiRkXjIocm93X2ksIGF2ZXJhZ2UucHJvZmlsZSkgPSBcc3Vte1xmcmFjeyhyb3cucHJvZmlsZV9pIC0gYXZlcmFnZS5wcm9maWxlKV4yfXthdmVyYWdlLnByb2ZpbGV9fSQNCg0KYGBge3J9DQpyb3cucHJvZmlsZSA8LSB0YWIvcm93LnN1bQ0KaGVhZChyb3VuZChyb3cucHJvZmlsZSwgMykpDQoNCmF2ZXJhZ2UucHJvZmlsZSA8LSBjb2wuc3VtL24NCmhlYWQocm91bmQoYXZlcmFnZS5wcm9maWxlLCAzKSkNCmBgYA0KDQojIyNPIGPDs2RpZ28gUiBhYmFpeG8gY2FsY3VsYSBhIGRpc3TDom5jaWEgZG8gcGVyZmlsIG3DqWRpbyBwYXJhIHRvZGFzIGFzIHZhcmnDoXZlaXMgZGUgbGluaGENCg0KYGBge3J9DQphdmVyYWdlLnJwIDwtIGNvbC5zdW0vbiANCmQyLnJvdyA8LSBhcHBseShyb3cucHJvZmlsZSwgMSwgDQogICAgICAgICAgICAgICAgZnVuY3Rpb24ocm93LnAsIGF2LnApe3N1bSgoKHJvdy5wIC0gYXYucCleMikvYXYucCl9LCANCiAgICAgICAgICAgICAgICBhdmVyYWdlLnJwKQ0KaGVhZChyb3VuZChkMi5yb3csMykpDQpgYGANCg0KIyMjTyBjb3MyIGRlIGxpbmhhcyBubyBtYXBhIGRlIGZhdG9yZXMgc8OjbzoNCg0KYGBge3J9DQpyb3cuY29zMiA8LSBhcHBseShyb3cuY29vcmReMiwgMiwgIi8iLCBkMi5yb3cpDQpyb3VuZChyb3cuY29zMiwgMykNCg0KY29ycnBsb3Qocm93LmNvczIsIGlzLmNvciA9IEZBTFNFKQ0KYGBgDQoNCiMjI0NvczIgZGFzIGNvbHVuYXMNCg0KJGNvbC5jb3MyID0gXGZyYWN7Y29sLmNvb3JkXjJ9e2ReMn0kDQoNCmBgYHtyfQ0KY29sLnByb2ZpbGUgPC0gdCh0YWIpL2NvbC5zdW0NCmNvbC5wcm9maWxlIDwtIHQoY29sLnByb2ZpbGUpDQojIyNoZWFkKHJvdW5kKGNvbC5wcm9maWxlLCAzKSkNCmF2ZXJhZ2UucHJvZmlsZSA8LSByb3cuc3VtL24NCiMjI2hlYWQocm91bmQoYXZlcmFnZS5wcm9maWxlLCAzKSkNCmBgYA0KDQojIyNPIGPDs2RpZ28gUiBhYmFpeG8gY2FsY3VsYSBhIGRpc3TDom5jaWEgZG8gcGVyZmlsIG3DqWRpbyBwYXJhIHRvZGFzIGFzIHZhcmnDoXZlaXMgZGEgY29sdW5hDQoNCmBgYHtyfQ0KDQpkMi5jb2wgPC0gYXBwbHkoY29sLnByb2ZpbGUsIDIsIA0KICAgICAgICBmdW5jdGlvbihjb2wucCwgYXYucCl7c3VtKCgoY29sLnAgLSBhdi5wKV4yKS9hdi5wKX0sIA0KICAgICAgICBhdmVyYWdlLnByb2ZpbGUpDQpyb3VuZChkMi5jb2wsMykNCmBgYA0KDQojIyNPIGNvczIgZGFzIGNvbHVuYXMgbm8gbWFwYSBkZSBmYXRvcmVzIHPDo286DQoNCmBgYHtyfQ0KY29sLmNvczIgPC0gYXBwbHkoY29sLmNvb3JkXjIsIDIsICIvIiwgZDIuY29sKQ0Kcm91bmQoY29sLmNvczIsIDMpDQoNCmNvcnJwbG90KGNvbC5jb3MyLCBpcy5jb3IgPSBGQUxTRSkNCmBgYA0KDQoNCiMjIyBQYWNvdGVzIG5vIFIgcGFyYSBBTkFDT1INCg0KLUZhY3RvTWluZVINCi1hZGU0DQotY2ENCg0KYGBge3J9DQpsaWJyYXJ5KEZhY3RvTWluZVIpDQpyZXMuY2EgPC0gQ0EodGFiLCBncmFwaCA9IEYpDQoNCnJlcy5jYQ0KYGBgDQoNCg0KYGBge3J9DQojIyMgZWlnZW52YWx1ZQ0KaGVhZChyZXMuY2EkZWlnKVssIDE6Ml0NCg0KIyMjIGJhcnBsb3Qgb2YgcGVyY2VudGFnZSBvZiB2YXJpYW5jZQ0KYmFycGxvdChyZXMuY2EkZWlnWywyXSwgbmFtZXMuYXJnID0gcm93bmFtZXMocmVzLmNhJGVpZykpDQpgYGANCg0KYGBge3J9DQojIyMgUGxvdCByb3cgcG9pbnRzDQpwbG90KHJlcy5jYSwgaW52aXNpYmxlID0iY29sIikNCmBgYA0KDQpgYGB7cn0NCiMjIyBQbG90IGNvbHVtbiBwb2ludHMNCnBsb3QocmVzLmNhLCBpbnZpc2libGUgPSJyb3ciKQ0KYGBgDQoNCmBgYHtyfQ0KIyMjIEJpcGxvdCBvZiByb3dzIGFuZCBjb2x1bW5zDQpwbG90KHJlcy5jYSkNCmBgYA0KDQo=