Descrição do desafio

Construir um algoritmo em linguagem R, utilizando NRBF (Network Radial Base Funcion) para modelar o comportamento de um fenômeno descrito na base de dados no arquivo Anexo. Especificamente para a aplicação do NRBF, use:
1. Função radial Gaussiana como função de ativação
2. Técnica da matriz pseudo-inversa, para resolução do sistema de coeficientes das funções
3. Método de K-means para seleção dos polos
4. Número de polos igual ou maior à quantidade de variáveis (6)

Como resultado, esperamos receber um código em R contendo:
1. O algoritmo de modelagem
2. Rotinas para a visualização da aplicação do algoritmo
3. Análise de assertividade e erro
4. Aplicação do código para os dados do arquivo Anexo

Introdução

Regressão paramétrica é um tipo de modelagem cuja característica distintiva é a ausência (completa ou quase) de conhecimento a priori sobre a forma da função que está sendo estimada. Assim, o conjunto de formas que a função pode assumir é abrangente. Como consequência, haverá um número elevado de parâmetros estimados que não possuem uma interpretação física isolada.

As redes neurais para aprendizado supervisionado do tipo RBF (Radial Basis Function Neural Network) são um exemplo de modelo não-paramétrico baseado na teoria de aproximação de funções, que trata o problema genérico de aproximar uma função \(y(x)\) por uma função de aproximação \(f(w, x)\) dado um número fixo de parâmetros \(w\).

Uma função de ativação de base radial é caracterizada por apresentar uma resposta que decresce (ou cresce) monotomicamente com a distância em relação a um ponto central. O centro e a taxa de decrescimento em cada diração são alguns dos parâmetros a serem definidos. Uma função de base radial típica é a função gaussiana, dada na forma:

\(h_j(x) = \exp(\dfrac{-(x-c_j)^2}{{r_j^2}})\)

As funções de base radial podem ser utilizadas como funções-base em qualquer tipo de modelo de regressão não-linear e como função de ativação de qualquer tipo de rede multicamada. As redes RBF sempre apresentam uma única camada intermediária, os neurônios de saída são sempre lineares e os neurônios da camada intermediária têm uma função de base radial como função de ativação.

Um exemplo visual da rede RBF é mostrada na figura abaixo.

Uma das abordagens para o treinamento das redes neurais RBF é a adaptação supervisionada dos pesos da camada de saída, empregando técnicas como pseudo-inversão.

No caso de algoritmos que se ocupam apenas com o ajuste dos pesos da camada de saída de uma rede RBF, é necessário estabelecer algum critério para a fixação dos centros. Uma alternativa é auto-organizar os centros, de acordo com a distribuição ds dados de entrada, por meio do algoritmo k-means.

Em suma, existem três tipos de parâmetros que precisam ser aprendidos por uma rede neural RBF:
* Os centros da função de ativação;
* As dispersões (taxa de decrescimento) da função de ativação;
* Os pesos da camada intermediária para a camada de output.

Código

Para este exercício, são usados os seguintes pacotes:

library(readxl)
library(GGally)
library(tidyverse)

A leitura do arquivo e a mudança da ordem das colunas é feita abaixo:

arquivo <- "Base Projeto Daemon - Quant 2018.xlsx"
df <- readxl::read_excel(arquivo, sheet = "Sheet1",
                         col_names = TRUE, range = "B5:H128")
df <- as.data.frame(df)
# mudar ordem das colunas
df <- df[, rev(1:ncol(df))]
knitr::kable(head(df))
nuVHF nuHF nuLF a1 RMSSD SDNN fc
0.0120016 0.2751218 0.2343089 0.2645995 0.0335986 0.0515247 1.393146
0.0347053 0.2907528 0.2066793 0.2863589 0.0263905 0.0358156 1.444002
0.0360863 0.2920406 0.1471003 0.2780337 0.0234379 0.0301764 1.457103
0.0184709 0.1712113 0.1472847 0.1994207 0.0245995 0.0459377 1.462573
0.0212941 0.1456406 0.1891565 0.1937349 0.0232357 0.0437853 1.467897
0.0168037 0.2222374 0.1856247 0.2385001 0.0278113 0.0452344 1.434446

O objetivo deste trabalho é modelar a variável de frequência cardíaca em função de todas as outras variáveis do conjunto de dados.

Modelagem

No código abaixo, é criada uma função que realiza as seguintes tarefas:
* Extrai uma quantidade k de centros da matriz de input X. Por padrão, k corresponde à quantidade de colunas na matriz de input X;
* Aplica uma função de ativação gaussiana para obter a matriz \(\phi\), onde cada elemento da matriz N x k corresponde a \(\gamma \times h(\|x_i - t_i\|)\), onde \(\gamma\) corresponde à dispersão da função de ativação;
* Obtem pela técnica da matriz pseudo inversa o vetor de pesos, a partir da matriz \(\phi\) e do vetor de output (ou target) y;
* Retorna valores ajustados para o vetor de output y a partir de uma matriz de input X_test.

nrbf <- function(X, Y, X_test = X, k = ncol(X), gamma = 1.0, seed = 123, plot = TRUE){
  library(corpcor)
  library(neuralnet)
  set.seed(seed)
  #### Definição dos argumentos:
  # X: Matriz de input 
  # Y: Matriz de output
  # k: número de centros (polos)
  # gamma: parametro de aprendizado
  
  N <- nrow(X) # numero de observacoes
  # criar funcao de ativacao gaussiana
  ativ_gaussiana <- function(gamma, x, y){
    # x e y sao vetores numericos
    
    v <- as.matrix(x - y)
    modulo <- norm(v, "F")
    exp(-gamma * modulo^2)
  }
  
    
  # criar clusteres em matriz de input normalizada
  cl <- kmeans(scale(X), centers = k)
  cl_centros <- cl$centers # (Matriz de dimensões k x ncol(X))
  # criar matriz vazia Phi para preencher  posteriormente
  matriz_phi <- matrix(NA_real_, nrow = N, ncol = k + 1)
  
  #browser()
  # iterar em cada linha
  for (l in 1:N){
    # Preencher a primeira coluna com 1 (coluna de bias)
    matriz_phi[l, 1] <- 1
    # iterar em cada coluna 
    for (c in 1:k){
      # calcular modulo do vetor das diferencas
      # preencher celula (com excecao da primeira coluna, que eh a de bias)
      matriz_phi[l, c + 1] <- ativ_gaussiana(gamma = gamma, x = X[l, ], y = cl_centros[c, ])
    }
  }
  
  w <- corpcor::pseudoinverse(t(matriz_phi) %*% matriz_phi) %*% t(matriz_phi) %*% Y 
  
  # obter previsoes para Y
  # inicializar vetor de previsoes a partir do bias do vetor de pesos
  prev <- rep(w[1], N)
  for (n in 1:N){
    for (j in 1:nrow(cl_centros)){
      prev[n] <- prev[n] + w[j + 1] * ativ_gaussiana(gamma = gamma, x = X_test[n, ], y = cl_centros[j, ])
    }
  }
  
  # plotar rede neural
  if (plot){
    # combinar input e target em um dataframe
    df_nnet <- data.frame(input, target = target)
    model_formula <- paste0("target ~ ", paste0(colnames(input), collapse = " + "))
    nn <- neuralnet(model_formula, data = df_nnet, hidden = k)
    plot(nn, information = FALSE, show.weights = FALSE)
  }
  
  list(weights=w, centers=cl_centros, gamma=gamma, prev = prev)  
}

Após criar a função, o código abaixo mostra como é simples ajustar o modelo à matriz de input deste exercício:

input <- as.matrix(df[, -7]) # a ultima coluna corresponde ao vetor y que se deseja prever
target <- as.matrix(df[, 7])
modelo <- nrbf(input, target)

# visualizar resultados do modelo
modelo$weights
                 [,1]
[1,]     0.4299523943
[2,] -7643.2332381372
[3,]     4.9617618704
[4,]    -1.8867322271
[5,]    69.6572399773
[6,]    12.6253134136
[7,]     0.1175875511
modelo$centers
          nuVHF          nuHF           nuLF            a1         RMSSD          SDNN
1  0.5837561752  0.6350442465 -0.66467743516  1.4381165768  2.2253072683  2.1569218102
2  0.4543580595  1.4390765402 -0.72753512269  0.4754167243  0.4489823963  0.2195427664
3 -0.6797732920 -0.8771208817 -0.36038859061 -1.0627150483 -0.6210457527 -0.2637736570
4  1.9941167775 -0.2407254829  0.87993133176 -0.1132062724 -0.3152785222 -0.5166736536
5 -0.5319770927 -0.8128502961  1.74751201173 -0.9378453344 -0.5700864149 -0.3475751648
6 -0.5577582063 -0.2105481031 -0.05084140639  0.2103505167 -0.4882146668 -0.5692230101

Análise dos resultados do modelo

O código abaixo mostra a qualidade do ajuste do modelo:

prev <- modelo$prev
plot(target)
lines(prev, col = "blue", lty = 2)

O gráfico acima mostra que o ajuste obtido é relativamente bom, mas falhou em ajustar os pontos mais à esquerda no gráfico. A função abaixo calcula o MAPE (Mean Absolute Percentage Error) do modelo de função de base radial.

# obter acuracia
mape <- function(real, previsto){
  erro <- real - previsto
  mean(abs(erro)/real)
}
mape(target, prev)
[1] 0.07508241996

Portanto, o modelo apresenta um erro de 7,5%.

Comparação com o modelo de regressão linear múltipla (MRLM)

O modelo de regressão linear múltipla abaixo é criado para comparar sua acurácia com o modelo de função de base radial:

# regressao nultipla linear
modelo_lin <- lm(fc ~ ., data = df)
prev_lin <- fitted(modelo_lin)
mape_lin <- mape(target, prev_lin)
mape_lin
[1] 0.05113152375

O erro do modelo de regressão múltipla é de 5%, o que significa que sua qualidade de ajuste é superior ao modelo de função de base radial. O gráfico abaixo mostra essa comparação visualmente:

plot(target)
lines(prev, col = "blue", lty = 2)
lines(fitted(modelo_lin), col = "red", lty = 2)
legend("topright", lty = 1, col = c("blue", "red"),
       legend = c("NRBF", "MRLM"))

Otimização do número de polos (centros)

É possível com certa facilidade obter a qualidade de ajuste do modelo NRBF para diferentes valores do número de polos (k) usados no modelo para descobrir qual número maximiza a acurácia do modelo.

O código abaixo obtem o MAPE do modelo NRBF para valores de K de 1 a 60. Os resultados são mostrados no gráfico abaixo, no qual a linha vertical corresponde ao MAPE do modelo de regressão linear múltipla.

# simular valores de k para observar se o rmse cai
vetor_k <- 1:60
# criar dataframe para armazenar resultados do rmse
df_mape <- data.frame(k = vetor_k, mape = rep(NA, length(vetor_k)))
for (i in 1:length(vetor_k)){
  prev_loop <- nrbf(input, target, k = vetor_k[i], plot = FALSE)$prev
  df_mape[i, 2] <- mape(target, prev_loop)
}
# analisar resultados visualmente
ggplot(df_mape, aes(x = k, y = 100 * mape)) +
  geom_point() +
  scale_x_continuous(breaks = seq(0, 60, 5)) +
  scale_y_continuous(breaks = scales::pretty_breaks(n = 10)) +
  geom_hline(yintercept = 100 * mape_lin, linetype = "dashed") + 
  labs(x = "Quantidade de polos", y = "MAPE (%)",
       title = "Relação entre quantidade de polos e o erro do modelo \n de função de base radial")

Uma informação muito útil extraída do gráfico é que a partir de \(k = 20\) o modelo NRBF se torna melhor que o de regressão linear múltipla. O erro do modelo é reduzido sensivelmente de \(k = 30\) para \(k = 31\) e se estabiliza desde então, o que nos leva a acreditar que este seja o número ideal de polos para o modelo.

O gráfico abaixo mostra a qualidade do ajuste do modelo NRBF com 31 polos no conjunto de dados do exercício.

# obter modelo com 31 clusteres
melhor_modelo <- nrbf(input, target, k = 31, plot = TRUE)

plot(target)
lines(melhor_modelo$prev, col = "blue", lty = 2)

LS0tCnRpdGxlOiAiQXBsaWNhw6fDo28gZGUgTmV0d29yayBSYWRpYWwgQmFzZSBGdW5jdGlvbiBwYXJhIG1vZGVsYWdlbSBkZSBmcmVxdcOqbmNpYSBjYXJkw61hY2EiCmF1dGhvcjogIlNpbGxhcyBUZWl4ZWlyYSBHb256YWdhIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UpCmBgYAoKIyBEZXNjcmnDp8OjbyBkbyBkZXNhZmlvCgpDb25zdHJ1aXIgdW0gYWxnb3JpdG1vIGVtIGxpbmd1YWdlbSBSLCB1dGlsaXphbmRvIE5SQkYgKE5ldHdvcmsgUmFkaWFsIEJhc2UgRnVuY2lvbikgcGFyYSBtb2RlbGFyIG8gY29tcG9ydGFtZW50byBkZSB1bSBmZW7DtG1lbm8gZGVzY3JpdG8gbmEgYmFzZSBkZSBkYWRvcyBubyBhcnF1aXZvIEFuZXhvLiAKRXNwZWNpZmljYW1lbnRlIHBhcmEgYSBhcGxpY2HDp8OjbyBkbyBOUkJGLCB1c2U6ICAKMS4gRnVuw6fDo28gcmFkaWFsIEdhdXNzaWFuYSBjb21vIGZ1bsOnw6NvIGRlIGF0aXZhw6fDo28gIAoyLiBUw6ljbmljYSBkYSBtYXRyaXogcHNldWRvLWludmVyc2EsIHBhcmEgcmVzb2x1w6fDo28gZG8gc2lzdGVtYSBkZSBjb2VmaWNpZW50ZXMgZGFzIGZ1bsOnw7VlcyAgCjMuIE3DqXRvZG8gZGUgSy1tZWFucyBwYXJhIHNlbGXDp8OjbyBkb3MgcG9sb3MgIAo0LiBOw7ptZXJvIGRlIHBvbG9zIGlndWFsIG91IG1haW9yIMOgIHF1YW50aWRhZGUgZGUgdmFyacOhdmVpcyAoNikgIAoKQ29tbyByZXN1bHRhZG8sIGVzcGVyYW1vcyByZWNlYmVyIHVtIGPDs2RpZ28gZW0gUiBjb250ZW5kbzogIAoxLiBPIGFsZ29yaXRtbyBkZSBtb2RlbGFnZW0gIAoyLiBSb3RpbmFzIHBhcmEgYSB2aXN1YWxpemHDp8OjbyBkYSBhcGxpY2HDp8OjbyBkbyBhbGdvcml0bW8gIAozLiBBbsOhbGlzZSBkZSBhc3NlcnRpdmlkYWRlIGUgZXJybyAgCjQuIEFwbGljYcOnw6NvIGRvIGPDs2RpZ28gcGFyYSBvcyBkYWRvcyBkbyBhcnF1aXZvIEFuZXhvICAgCgojIEludHJvZHXDp8OjbwoKUmVncmVzc8OjbyBwYXJhbcOpdHJpY2Egw6kgdW0gdGlwbyBkZSBtb2RlbGFnZW0gY3VqYSBjYXJhY3RlcsOtc3RpY2EgZGlzdGludGl2YSDDqSBhIGF1c8OqbmNpYSAoY29tcGxldGEgb3UgcXVhc2UpIGRlIGNvbmhlY2ltZW50byBhIHByaW9yaSBzb2JyZSBhIGZvcm1hIGRhIGZ1bsOnw6NvIHF1ZSBlc3TDoSBzZW5kbyBlc3RpbWFkYS4gQXNzaW0sIG8gY29uanVudG8gZGUgZm9ybWFzIHF1ZSBhIGZ1bsOnw6NvIHBvZGUgYXNzdW1pciDDqSBhYnJhbmdlbnRlLiBDb21vIGNvbnNlcXXDqm5jaWEsIGhhdmVyw6EgdW0gbsO6bWVybyBlbGV2YWRvIGRlIHBhcsOibWV0cm9zIGVzdGltYWRvcyBxdWUgbsOjbyBwb3NzdWVtIHVtYSBpbnRlcnByZXRhw6fDo28gZsOtc2ljYSBpc29sYWRhLgoKQXMgcmVkZXMgbmV1cmFpcyBwYXJhIGFwcmVuZGl6YWRvIHN1cGVydmlzaW9uYWRvIGRvIHRpcG8gKipSQkYqKiAoKlJhZGlhbCBCYXNpcyBGdW5jdGlvbiBOZXVyYWwgTmV0d29yayopIHPDo28gdW0gZXhlbXBsbyBkZSBtb2RlbG8gbsOjby1wYXJhbcOpdHJpY28gYmFzZWFkbyBuYSB0ZW9yaWEgZGUgYXByb3hpbWHDp8OjbyBkZSBmdW7Dp8O1ZXMsIHF1ZSB0cmF0YSBvIHByb2JsZW1hIGdlbsOpcmljbyBkZSBhcHJveGltYXIgdW1hIGZ1bsOnw6NvICR5KHgpJCBwb3IgdW1hIGZ1bsOnw6NvIGRlIGFwcm94aW1hw6fDo28gJGYodywgeCkkIGRhZG8gdW0gbsO6bWVybyBmaXhvIGRlIHBhcsOibWV0cm9zICR3JC4KClVtYSBmdW7Dp8OjbyBkZSBhdGl2YcOnw6NvIGRlIGJhc2UgcmFkaWFsIMOpIGNhcmFjdGVyaXphZGEgcG9yIGFwcmVzZW50YXIgdW1hIHJlc3Bvc3RhIHF1ZSBkZWNyZXNjZSAob3UgY3Jlc2NlKSBtb25vdG9taWNhbWVudGUgY29tIGEgZGlzdMOibmNpYSBlbSByZWxhw6fDo28gYSB1bSBwb250byBjZW50cmFsLiBPIGNlbnRybyBlIGEgdGF4YSBkZSBkZWNyZXNjaW1lbnRvIGVtIGNhZGEgZGlyYcOnw6NvIHPDo28gYWxndW5zIGRvcyBwYXLDom1ldHJvcyBhIHNlcmVtIGRlZmluaWRvcy4gVW1hIGZ1bsOnw6NvIGRlIGJhc2UgcmFkaWFsIHTDrXBpY2Egw6kgYSBmdW7Dp8OjbyBnYXVzc2lhbmEsIGRhZGEgbmEgZm9ybWE6CgokaF9qKHgpID0gXGV4cChcZGZyYWN7LSh4LWNfaileMn17e3Jfal4yfX0pJCAgCgpBcyBmdW7Dp8O1ZXMgZGUgYmFzZSByYWRpYWwgcG9kZW0gc2VyIHV0aWxpemFkYXMgY29tbyBmdW7Dp8O1ZXMtYmFzZSBlbSBxdWFscXVlciB0aXBvIGRlIG1vZGVsbyBkZSByZWdyZXNzw6NvIG7Do28tbGluZWFyIGUgY29tbyBmdW7Dp8OjbyBkZSBhdGl2YcOnw6NvIGRlIHF1YWxxdWVyIHRpcG8gZGUgcmVkZSBtdWx0aWNhbWFkYS4gQXMgcmVkZXMgUkJGIHNlbXByZSBhcHJlc2VudGFtIHVtYSDDum5pY2EgY2FtYWRhIGludGVybWVkacOhcmlhLCBvcyBuZXVyw7RuaW9zIGRlIHNhw61kYSBzw6NvIHNlbXByZSBsaW5lYXJlcyBlIG9zIG5ldXLDtG5pb3MgZGEgY2FtYWRhIGludGVybWVkacOhcmlhIHTDqm0gdW1hIGZ1bsOnw6NvIGRlIGJhc2UgcmFkaWFsIGNvbW8gZnVuw6fDo28gZGUgYXRpdmHDp8Ojby4gIAoKVW0gZXhlbXBsbyB2aXN1YWwgZGEgcmVkZSBSQkYgw6kgbW9zdHJhZGEgbmEgZmlndXJhIGFiYWl4by4KCiFbXShodHRwczovL2kuaW1ndXIuY29tLzI3UFhiWlouanBnKQoKVW1hIGRhcyBhYm9yZGFnZW5zIHBhcmEgbyB0cmVpbmFtZW50byBkYXMgcmVkZXMgbmV1cmFpcyBSQkYgw6kgYSBhZGFwdGHDp8OjbyBzdXBlcnZpc2lvbmFkYSBkb3MgcGVzb3MgZGEgY2FtYWRhIGRlIHNhw61kYSwgZW1wcmVnYW5kbyB0w6ljbmljYXMgY29tbyBwc2V1ZG8taW52ZXJzw6NvLgoKTm8gY2FzbyBkZSBhbGdvcml0bW9zIHF1ZSBzZSBvY3VwYW0gYXBlbmFzIGNvbSBvIGFqdXN0ZSBkb3MgcGVzb3MgZGEgY2FtYWRhIGRlIHNhw61kYSBkZSB1bWEgcmVkZSBSQkYsIMOpIG5lY2Vzc8OhcmlvIGVzdGFiZWxlY2VyIGFsZ3VtIGNyaXTDqXJpbyBwYXJhIGEgZml4YcOnw6NvIGRvcyBjZW50cm9zLiBVbWEgYWx0ZXJuYXRpdmEgw6kgYXV0by1vcmdhbml6YXIgb3MgY2VudHJvcywgZGUgYWNvcmRvIGNvbSBhIGRpc3RyaWJ1acOnw6NvIGRzIGRhZG9zIGRlIGVudHJhZGEsIHBvciBtZWlvIGRvIGFsZ29yaXRtbyAqay1tZWFucyouCgpFbSBzdW1hLCBleGlzdGVtIHRyw6pzIHRpcG9zIGRlIHBhcsOibWV0cm9zIHF1ZSBwcmVjaXNhbSBzZXIgYXByZW5kaWRvcyBwb3IgdW1hIHJlZGUgbmV1cmFsIFJCRjogIAoqIE9zIGNlbnRyb3MgZGEgZnVuw6fDo28gZGUgYXRpdmHDp8OjbzsgIAoqIEFzIGRpc3BlcnPDtWVzICh0YXhhIGRlIGRlY3Jlc2NpbWVudG8pIGRhIGZ1bsOnw6NvIGRlIGF0aXZhw6fDo287ICAKKiBPcyBwZXNvcyBkYSBjYW1hZGEgaW50ZXJtZWRpw6FyaWEgcGFyYSBhIGNhbWFkYSBkZSBvdXRwdXQuICAKCiMgQ8OzZGlnbwoKUGFyYSBlc3RlIGV4ZXJjw61jaW8sIHPDo28gdXNhZG9zIG9zIHNlZ3VpbnRlcyBwYWNvdGVzOgoKYGBge3J9CmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KEdHYWxseSkKbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKQSBsZWl0dXJhIGRvIGFycXVpdm8gZSBhIG11ZGFuw6dhIGRhIG9yZGVtIGRhcyBjb2x1bmFzIMOpIGZlaXRhIGFiYWl4bzoKCmBgYHtyfQphcnF1aXZvIDwtICJCYXNlIFByb2pldG8gRGFlbW9uIC0gUXVhbnQgMjAxOC54bHN4IgpkZiA8LSByZWFkeGw6OnJlYWRfZXhjZWwoYXJxdWl2bywgc2hlZXQgPSAiU2hlZXQxIiwKICAgICAgICAgICAgICAgICAgICAgICAgIGNvbF9uYW1lcyA9IFRSVUUsIHJhbmdlID0gIkI1OkgxMjgiKQpkZiA8LSBhcy5kYXRhLmZyYW1lKGRmKQojIG11ZGFyIG9yZGVtIGRhcyBjb2x1bmFzCmRmIDwtIGRmWywgcmV2KDE6bmNvbChkZikpXQprbml0cjo6a2FibGUoaGVhZChkZikpCmBgYAoKTyBvYmpldGl2byBkZXN0ZSB0cmFiYWxobyDDqSBtb2RlbGFyIGEgdmFyacOhdmVsIGRlIGZyZXF1w6puY2lhIGNhcmTDrWFjYSBlbSBmdW7Dp8OjbyBkZSB0b2RhcyBhcyBvdXRyYXMgdmFyacOhdmVpcyBkbyBjb25qdW50byBkZSBkYWRvcy4gCgojIyBNb2RlbGFnZW0KCk5vIGPDs2RpZ28gYWJhaXhvLCDDqSBjcmlhZGEgdW1hIGZ1bsOnw6NvIHF1ZSByZWFsaXphIGFzIHNlZ3VpbnRlcyB0YXJlZmFzOiAgCiogRXh0cmFpIHVtYSBxdWFudGlkYWRlIGBrYCBkZSBjZW50cm9zIGRhIG1hdHJpeiBkZSBpbnB1dCBgWGAuIFBvciBwYWRyw6NvLCBga2AgY29ycmVzcG9uZGUgw6AgcXVhbnRpZGFkZSBkZSBjb2x1bmFzIG5hIG1hdHJpeiBkZSBpbnB1dCBgWGA7ICAKKiBBcGxpY2EgdW1hIGZ1bsOnw6NvIGRlIGF0aXZhw6fDo28gZ2F1c3NpYW5hIHBhcmEgb2J0ZXIgYSBtYXRyaXogJFxwaGkkLCBvbmRlIGNhZGEgZWxlbWVudG8gZGEgbWF0cml6IE4geCBrIGNvcnJlc3BvbmRlIGEgJFxnYW1tYSBcdGltZXMgaChcfHhfaSAtIHRfaVx8KSQsIG9uZGUgJFxnYW1tYSQgY29ycmVzcG9uZGUgw6AgZGlzcGVyc8OjbyBkYSBmdW7Dp8OjbyBkZSBhdGl2YcOnw6NvOyAgCiogT2J0ZW0gcGVsYSB0w6ljbmljYSBkYSBtYXRyaXogcHNldWRvIGludmVyc2EgbyB2ZXRvciBkZSBwZXNvcywgYSBwYXJ0aXIgZGEgbWF0cml6ICRccGhpJCBlIGRvIHZldG9yIGRlIG91dHB1dCAob3UgdGFyZ2V0KSBgeWA7ICAKKiBSZXRvcm5hIHZhbG9yZXMgYWp1c3RhZG9zIHBhcmEgbyB2ZXRvciBkZSBvdXRwdXQgYHlgIGEgcGFydGlyIGRlIHVtYSBtYXRyaXogZGUgaW5wdXQgYFhfdGVzdGAuICAKCgpgYGB7ciBkZWZpbmljYW8gZGEgZnVuY2FvfQpucmJmIDwtIGZ1bmN0aW9uKFgsIFksIFhfdGVzdCA9IFgsIGsgPSBuY29sKFgpLCBnYW1tYSA9IDEuMCwgc2VlZCA9IDEyMywgcGxvdCA9IFRSVUUpewogIGxpYnJhcnkoY29ycGNvcikKICBsaWJyYXJ5KG5ldXJhbG5ldCkKICBzZXQuc2VlZChzZWVkKQogICMjIyMgRGVmaW5pw6fDo28gZG9zIGFyZ3VtZW50b3M6CiAgIyBYOiBNYXRyaXogZGUgaW5wdXQgCiAgIyBZOiBNYXRyaXogZGUgb3V0cHV0CiAgIyBrOiBuw7ptZXJvIGRlIGNlbnRyb3MgKHBvbG9zKQogICMgZ2FtbWE6IHBhcmFtZXRybyBkZSBhcHJlbmRpemFkbwogIAogIE4gPC0gbnJvdyhYKSAjIG51bWVybyBkZSBvYnNlcnZhY29lcwoKICAjIGNyaWFyIGZ1bmNhbyBkZSBhdGl2YWNhbyBnYXVzc2lhbmEKICBhdGl2X2dhdXNzaWFuYSA8LSBmdW5jdGlvbihnYW1tYSwgeCwgeSl7CiAgICAjIHggZSB5IHNhbyB2ZXRvcmVzIG51bWVyaWNvcwogICAgCiAgICB2IDwtIGFzLm1hdHJpeCh4IC0geSkKICAgIG1vZHVsbyA8LSBub3JtKHYsICJGIikKICAgIGV4cCgtZ2FtbWEgKiBtb2R1bG9eMikKICB9CiAgCiAgICAKICAjIGNyaWFyIGNsdXN0ZXJlcyBlbSBtYXRyaXogZGUgaW5wdXQgbm9ybWFsaXphZGEKICBjbCA8LSBrbWVhbnMoc2NhbGUoWCksIGNlbnRlcnMgPSBrKQogIGNsX2NlbnRyb3MgPC0gY2wkY2VudGVycyAjIChNYXRyaXogZGUgZGltZW5zw7VlcyBrIHggbmNvbChYKSkKICAjIGNyaWFyIG1hdHJpeiB2YXppYSBQaGkgcGFyYSBwcmVlbmNoZXIgIHBvc3Rlcmlvcm1lbnRlCiAgbWF0cml6X3BoaSA8LSBtYXRyaXgoTkFfcmVhbF8sIG5yb3cgPSBOLCBuY29sID0gayArIDEpCiAgCiAgI2Jyb3dzZXIoKQogICMgaXRlcmFyIGVtIGNhZGEgbGluaGEKICBmb3IgKGwgaW4gMTpOKXsKICAgICMgUHJlZW5jaGVyIGEgcHJpbWVpcmEgY29sdW5hIGNvbSAxIChjb2x1bmEgZGUgYmlhcykKICAgIG1hdHJpel9waGlbbCwgMV0gPC0gMQogICAgIyBpdGVyYXIgZW0gY2FkYSBjb2x1bmEgCiAgICBmb3IgKGMgaW4gMTprKXsKICAgICAgIyBjYWxjdWxhciBtb2R1bG8gZG8gdmV0b3IgZGFzIGRpZmVyZW5jYXMKICAgICAgIyBwcmVlbmNoZXIgY2VsdWxhIChjb20gZXhjZWNhbyBkYSBwcmltZWlyYSBjb2x1bmEsIHF1ZSBlaCBhIGRlIGJpYXMpCiAgICAgIG1hdHJpel9waGlbbCwgYyArIDFdIDwtIGF0aXZfZ2F1c3NpYW5hKGdhbW1hID0gZ2FtbWEsIHggPSBYW2wsIF0sIHkgPSBjbF9jZW50cm9zW2MsIF0pCiAgICB9CiAgfQogIAogIHcgPC0gY29ycGNvcjo6cHNldWRvaW52ZXJzZSh0KG1hdHJpel9waGkpICUqJSBtYXRyaXpfcGhpKSAlKiUgdChtYXRyaXpfcGhpKSAlKiUgWSAKICAKICAjIG9idGVyIHByZXZpc29lcyBwYXJhIFkKICAjIGluaWNpYWxpemFyIHZldG9yIGRlIHByZXZpc29lcyBhIHBhcnRpciBkbyBiaWFzIGRvIHZldG9yIGRlIHBlc29zCiAgcHJldiA8LSByZXAod1sxXSwgTikKICBmb3IgKG4gaW4gMTpOKXsKICAgIGZvciAoaiBpbiAxOm5yb3coY2xfY2VudHJvcykpewogICAgICBwcmV2W25dIDwtIHByZXZbbl0gKyB3W2ogKyAxXSAqIGF0aXZfZ2F1c3NpYW5hKGdhbW1hID0gZ2FtbWEsIHggPSBYX3Rlc3RbbiwgXSwgeSA9IGNsX2NlbnRyb3NbaiwgXSkKICAgIH0KICB9CiAgCiAgIyBwbG90YXIgcmVkZSBuZXVyYWwKICBpZiAocGxvdCl7CiAgICAjIGNvbWJpbmFyIGlucHV0IGUgdGFyZ2V0IGVtIHVtIGRhdGFmcmFtZQogICAgZGZfbm5ldCA8LSBkYXRhLmZyYW1lKGlucHV0LCB0YXJnZXQgPSB0YXJnZXQpCiAgICBtb2RlbF9mb3JtdWxhIDwtIHBhc3RlMCgidGFyZ2V0IH4gIiwgcGFzdGUwKGNvbG5hbWVzKGlucHV0KSwgY29sbGFwc2UgPSAiICsgIikpCiAgICBubiA8LSBuZXVyYWxuZXQobW9kZWxfZm9ybXVsYSwgZGF0YSA9IGRmX25uZXQsIGhpZGRlbiA9IGspCiAgICBwbG90KG5uLCBpbmZvcm1hdGlvbiA9IEZBTFNFLCBzaG93LndlaWdodHMgPSBGQUxTRSkKICB9CiAgCiAgbGlzdCh3ZWlnaHRzPXcsIGNlbnRlcnM9Y2xfY2VudHJvcywgZ2FtbWE9Z2FtbWEsIHByZXYgPSBwcmV2KSAgCn0KCgpgYGAKCkFww7NzIGNyaWFyIGEgZnVuw6fDo28sIG8gY8OzZGlnbyBhYmFpeG8gbW9zdHJhIGNvbW8gw6kgc2ltcGxlcyBhanVzdGFyIG8gbW9kZWxvIMOgIG1hdHJpeiBkZSBpbnB1dCBkZXN0ZSBleGVyY8OtY2lvOgoKYGBge3Igb2J0ZXIgbW9kZWxvfQppbnB1dCA8LSBhcy5tYXRyaXgoZGZbLCAtN10pICMgYSB1bHRpbWEgY29sdW5hIGNvcnJlc3BvbmRlIGFvIHZldG9yIHkgcXVlIHNlIGRlc2VqYSBwcmV2ZXIKdGFyZ2V0IDwtIGFzLm1hdHJpeChkZlssIDddKQoKbW9kZWxvIDwtIG5yYmYoaW5wdXQsIHRhcmdldCkKCiMgdmlzdWFsaXphciByZXN1bHRhZG9zIGRvIG1vZGVsbwptb2RlbG8kd2VpZ2h0cwptb2RlbG8kY2VudGVycwpgYGAKCgojIyBBbsOhbGlzZSBkb3MgcmVzdWx0YWRvcyBkbyBtb2RlbG8KCk8gY8OzZGlnbyBhYmFpeG8gbW9zdHJhIGEgcXVhbGlkYWRlIGRvIGFqdXN0ZSBkbyBtb2RlbG86ICAKCmBgYHtyfQpwcmV2IDwtIG1vZGVsbyRwcmV2CgpwbG90KHRhcmdldCkKbGluZXMocHJldiwgY29sID0gImJsdWUiLCBsdHkgPSAyKQoKYGBgCgpPIGdyw6FmaWNvIGFjaW1hIG1vc3RyYSBxdWUgbyBhanVzdGUgb2J0aWRvIMOpIHJlbGF0aXZhbWVudGUgYm9tLCBtYXMgZmFsaG91IGVtIGFqdXN0YXIgb3MgcG9udG9zIG1haXMgw6AgZXNxdWVyZGEgbm8gZ3LDoWZpY28uIEEgZnVuw6fDo28gYWJhaXhvIGNhbGN1bGEgbyBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpIGRvIG1vZGVsbyBkZSBmdW7Dp8OjbyBkZSBiYXNlIHJhZGlhbC4KCmBgYHtyfQojIG9idGVyIGFjdXJhY2lhCm1hcGUgPC0gZnVuY3Rpb24ocmVhbCwgcHJldmlzdG8pewogIGVycm8gPC0gcmVhbCAtIHByZXZpc3RvCiAgbWVhbihhYnMoZXJybykvcmVhbCkKfQoKbWFwZSh0YXJnZXQsIHByZXYpCgpgYGAKClBvcnRhbnRvLCBvIG1vZGVsbyBhcHJlc2VudGEgdW0gZXJybyBkZSA3LDUlLgoKIyMgQ29tcGFyYcOnw6NvIGNvbSBvIG1vZGVsbyBkZSByZWdyZXNzw6NvIGxpbmVhciBtw7psdGlwbGEgKE1STE0pCgpPIG1vZGVsbyBkZSByZWdyZXNzw6NvIGxpbmVhciBtw7psdGlwbGEgYWJhaXhvIMOpIGNyaWFkbyBwYXJhIGNvbXBhcmFyIHN1YSBhY3Vyw6FjaWEgY29tIG8gbW9kZWxvIGRlIGZ1bsOnw6NvIGRlIGJhc2UgcmFkaWFsOgoKYGBge3J9CiMgcmVncmVzc2FvIG51bHRpcGxhIGxpbmVhcgptb2RlbG9fbGluIDwtIGxtKGZjIH4gLiwgZGF0YSA9IGRmKQpwcmV2X2xpbiA8LSBmaXR0ZWQobW9kZWxvX2xpbikKbWFwZV9saW4gPC0gbWFwZSh0YXJnZXQsIHByZXZfbGluKQptYXBlX2xpbgpgYGAKCk8gZXJybyBkbyBtb2RlbG8gZGUgcmVncmVzc8OjbyBtw7psdGlwbGEgw6kgZGUgNSUsIG8gcXVlIHNpZ25pZmljYSBxdWUgc3VhIHF1YWxpZGFkZSBkZSBhanVzdGUgw6kgc3VwZXJpb3IgYW8gbW9kZWxvIGRlIGZ1bsOnw6NvIGRlIGJhc2UgcmFkaWFsLiBPIGdyw6FmaWNvIGFiYWl4byBtb3N0cmEgZXNzYSBjb21wYXJhw6fDo28gdmlzdWFsbWVudGU6CgpgYGB7cn0KCnBsb3QodGFyZ2V0KQpsaW5lcyhwcmV2LCBjb2wgPSAiYmx1ZSIsIGx0eSA9IDIpCmxpbmVzKGZpdHRlZChtb2RlbG9fbGluKSwgY29sID0gInJlZCIsIGx0eSA9IDIpCmxlZ2VuZCgidG9wcmlnaHQiLCBsdHkgPSAxLCBjb2wgPSBjKCJibHVlIiwgInJlZCIpLAogICAgICAgbGVnZW5kID0gYygiTlJCRiIsICJNUkxNIikpCmBgYAoKIyMgT3RpbWl6YcOnw6NvIGRvIG7Dum1lcm8gZGUgcG9sb3MgKGNlbnRyb3MpCgrDiSBwb3Nzw612ZWwgY29tIGNlcnRhIGZhY2lsaWRhZGUgb2J0ZXIgYSBxdWFsaWRhZGUgZGUgYWp1c3RlIGRvIG1vZGVsbyBOUkJGIHBhcmEgZGlmZXJlbnRlcyB2YWxvcmVzIGRvIG7Dum1lcm8gZGUgcG9sb3MgKGBrYCkgdXNhZG9zIG5vIG1vZGVsbyBwYXJhIGRlc2NvYnJpciBxdWFsIG7Dum1lcm8gbWF4aW1pemEgYSBhY3Vyw6FjaWEgZG8gbW9kZWxvLgoKTyBjw7NkaWdvIGFiYWl4byBvYnRlbSBvIE1BUEUgZG8gbW9kZWxvIE5SQkYgcGFyYSB2YWxvcmVzIGRlIEsgZGUgMSBhIDYwLiBPcyByZXN1bHRhZG9zIHPDo28gbW9zdHJhZG9zIG5vIGdyw6FmaWNvIGFiYWl4bywgbm8gcXVhbCBhIGxpbmhhIHZlcnRpY2FsIGNvcnJlc3BvbmRlIGFvIE1BUEUgZG8gbW9kZWxvIGRlIHJlZ3Jlc3PDo28gbGluZWFyIG3Dumx0aXBsYS4KCmBgYHtyIHNpbXVsYXIga30KIyBzaW11bGFyIHZhbG9yZXMgZGUgayBwYXJhIG9ic2VydmFyIHNlIG8gcm1zZSBjYWkKdmV0b3JfayA8LSAxOjYwCgojIGNyaWFyIGRhdGFmcmFtZSBwYXJhIGFybWF6ZW5hciByZXN1bHRhZG9zIGRvIHJtc2UKZGZfbWFwZSA8LSBkYXRhLmZyYW1lKGsgPSB2ZXRvcl9rLCBtYXBlID0gcmVwKE5BLCBsZW5ndGgodmV0b3JfaykpKQoKZm9yIChpIGluIDE6bGVuZ3RoKHZldG9yX2spKXsKICBwcmV2X2xvb3AgPC0gbnJiZihpbnB1dCwgdGFyZ2V0LCBrID0gdmV0b3Jfa1tpXSwgcGxvdCA9IEZBTFNFKSRwcmV2CiAgZGZfbWFwZVtpLCAyXSA8LSBtYXBlKHRhcmdldCwgcHJldl9sb29wKQp9CgojIGFuYWxpc2FyIHJlc3VsdGFkb3MgdmlzdWFsbWVudGUKZ2dwbG90KGRmX21hcGUsIGFlcyh4ID0gaywgeSA9IDEwMCAqIG1hcGUpKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV94X2NvbnRpbnVvdXMoYnJlYWtzID0gc2VxKDAsIDYwLCA1KSkgKwogIHNjYWxlX3lfY29udGludW91cyhicmVha3MgPSBzY2FsZXM6OnByZXR0eV9icmVha3MobiA9IDEwKSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEwMCAqIG1hcGVfbGluLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArIAogIGxhYnMoeCA9ICJRdWFudGlkYWRlIGRlIHBvbG9zIiwgeSA9ICJNQVBFICglKSIsCiAgICAgICB0aXRsZSA9ICJSZWxhw6fDo28gZW50cmUgcXVhbnRpZGFkZSBkZSBwb2xvcyBlIG8gZXJybyBkbyBtb2RlbG8gXG4gZGUgZnVuw6fDo28gZGUgYmFzZSByYWRpYWwiKQoKCgoKYGBgCgpVbWEgaW5mb3JtYcOnw6NvIG11aXRvIMO6dGlsIGV4dHJhw61kYSBkbyBncsOhZmljbyDDqSBxdWUgYSBwYXJ0aXIgZGUgJGsgPSAyMCQgbyBtb2RlbG8gTlJCRiBzZSB0b3JuYSBtZWxob3IgcXVlIG8gZGUgcmVncmVzc8OjbyBsaW5lYXIgbcO6bHRpcGxhLiBPIGVycm8gZG8gbW9kZWxvIMOpIHJlZHV6aWRvIHNlbnNpdmVsbWVudGUgZGUgJGsgPSAzMCQgcGFyYSAkayA9IDMxJCBlIHNlIGVzdGFiaWxpemEgZGVzZGUgZW50w6NvLCBvIHF1ZSBub3MgbGV2YSBhIGFjcmVkaXRhciBxdWUgZXN0ZSBzZWphIG8gbsO6bWVybyBpZGVhbCBkZSBwb2xvcyBwYXJhIG8gbW9kZWxvLgoKTyBncsOhZmljbyBhYmFpeG8gbW9zdHJhIGEgcXVhbGlkYWRlIGRvIGFqdXN0ZSBkbyBtb2RlbG8gTlJCRiBjb20gMzEgcG9sb3Mgbm8gY29uanVudG8gZGUgZGFkb3MgZG8gZXhlcmPDrWNpby4KCmBgYHtyIHBsb3QgbWVsaG9yIG1vZGVsb30KIyBvYnRlciBtb2RlbG8gY29tIDMxIGNsdXN0ZXJlcwptZWxob3JfbW9kZWxvIDwtIG5yYmYoaW5wdXQsIHRhcmdldCwgayA9IDMxLCBwbG90ID0gVFJVRSkKcGxvdCh0YXJnZXQpCmxpbmVzKG1lbGhvcl9tb2RlbG8kcHJldiwgY29sID0gImJsdWUiLCBsdHkgPSAyKQoKYGBgCgoKIyBSZWZlcsOqbmNpYXMKCltOb3RhcyBkZSBhdWxhIGRvcyBwcm9mZXNzb3JlcyBGZXJuYW5kbyBKLiBWb24gWnViZW4gJiBMZXZ5IEJvY2NhdG9dKGZ0cDovL2Z0cC5kY2EuZmVlLnVuaWNhbXAuYnIvcHViL2RvY3Mvdm9uenViZW4vaWEzNTNfMDUvdG9waWNvOF8wNS5wZGYpICAKCgpbRXhwbGljYcOnw6NvIHRlw7NyaWNhIGRlIE5SQkYgY29tIGFwbGljYcOnw6NvIGRlIGPDs2RpZ28gUl0oaHR0cDovL3d3dy5kaS5mYy51bC5wdC9+anBuL3IvcmJmL3JiZi5odG1sKQoK