Introducción

  1. ¿Qué es Supervised Machine Learning y cuáles son algunas de sus aplicaciones en análisis de clasificación?
    El aprendizaje supervisado (Supervised Machine Learning) es un tipo de enfoque de aprendizaje donde se entrena un modelo utilizando un conjunto de datos que incluye entradas y las respuestas deseadas asociadas con esas entradas. El objetivo es aprender una función que mapee las entradas a las salidas correctas basadas en ejemplos de entrenamiento etiquetados. Algunas de las aplicaciones comunes del aprendizaje supervisado en análisis de clasificación son:
  • Clasificación de correos electrónicos: Identificar si un correo electrónico es spam o no spam.
  • Detección de fraudes: Reconocer transacciones financieras como legítimas o fraudulentas.
  • Diagnóstico médico: Clasificar imágenes médicas o datos de pacientes para diagnosticar enfermedades.
  • Análisis de sentimientos: Clasificar comentarios de redes sociales, reseñas de productos u opiniones de usuarios en positivas, neutrales o negativas.
  • Reconocimiento de voz: Clasificar grabaciones de voz en diferentes categorías, como comandos de voz o transcripción de texto.
  1. ¿Cuáles son los principales algoritmos de Supervised Machine Learning - Classification?
    Los principales algoritmos de aprendizaje supervisado de Clasificación son:
  • Regresión Logística: Realiza una clasificación binaria acorde a la regresión ya que, estima las probabilidades de que una instancia pertenezca a una de las dos clases.
  • K-Nearest Neighbors (KNN): Clasifica una instancia basándose en las clases de sus vecinos más cercanos en el espacio de características, es decir, predice la clasificación de un nuevo punto de muestra en función de las clases de sus vecinos más cercanos. No recomendable para bd con alto número de entradas.
  • Support Vector Machines (SVM): Busca el hiperplano que mejor separa las clases en el espacio de características con la ayuda de vectores de soporte; efectivo en espacios de características no lineales a través del uso de kernels.
  • Bayes:
  • Árboles de Decisión: Divide repetidamente el espacio de características en subconjuntos cada vez más pequeños basados en la característica que proporciona una mejor estimación de clasificación.
  • Random Forest: Algoritmo de conjunto que combina más de un algortimo para clasificar objetos, por ende, combina las predicciones de múltiples árboles creados en subconjuntos aleatorios para tomar el promedio de las predicciones y mejorar tanto la precisión como reducir el sobreajuste.
  1. Respecto a la selección de los resultados de los modelos de clasificación: ¿Qué es la matriz de confusión? ¿Qué es el estadístico Kappa? ¿Cuál es la relación entre AUC y ROC Curve?
    La selección de los resultados de los modelos de clasificación implica evaluar su desempeño utilizando diversas métricas y herramientas. Las principales son:
  • Matriz de confusión: Tabla que describe el rendimiento de un modelo de clasificación en términos de los valores reales y predichos de las clases. Se compone de cuatro celdas: verdaderos positivos (TP), falsos positivos (FP), verdaderos negativos (TN) y falsos negativos (FN). Acorde a esto se calculan varias métricas de evaluación como precisión, sensibilidad, especificidad, etc.
  • Estadístico Kappa: Medida de la concordancia entre las clasificaciones observadas y las clasificaciones esperadas por azar; permite evaluar la confiabilidad de un modelo de clasificación ajustado, teniendo en cuenta la posibilidad de que las clasificaciones ocurran al azar. Dicho valor varía entre -1 y 1, donde 1 indica una concordancia perfecta, 0 indica concordancia aleatoria y valores negativos indican concordancia inferior a la aleatoria.
  • AUC y ROC Curve: La curva ROC (Receiver Operating Characteristic) es una representación gráfica de la relación entre la tasa de verdaderos positivos (sensibilidad) frente a la tasa de falsos positivos (1 - especificidad) en varios umbrales de clasificación. El Área bajo la Curva (AUC) es una métrica de la capacidad discriminativa del modelo que se basa en el área bajo la curva ROC. Por lo tanto: Una curva ROC ideal estaría ubicada en el rincón superior izquierdo del gráfico, lo que indica una alta tasa de verdaderos positivos y una baja tasa de falsos positivos. En este caso, el AUC sería cercano a 1, indicando un modelo muy preciso y efectivo. No obstante, si el clasificador es similar a una estimación aleatoria, la curva ROC sería una línea diagonal desde el punto (0,0) hasta el punto (1,1). En este caso, el AUC sería cercano a 0.5, lo que indica un modelo que no es mejor que el azar en la clasificación.

Acorde a esto, se determina que cuanto mayor sea el AUC, mejor será el rendimiento del modelo en términos de separación entre clases. En consecuencia, la relación entre AUC y la curva ROC es que el AUC es una medida resumida del rendimiento global del modelo, mientras que la curva ROC proporciona información detallada sobre su rendimiento en diferentes umbrales de clasificación.

Base de datos

La base de datos “Bank Application” proporciona información sobre los clientes y las características de los préstamos, lo que permite realizar análisis y modelos de clasificación para determinar la probabilidad de que un cliente tenga dificultades o no para realizar los pagos de un préstamo. A continuación, se muestra una descripción de las variables:
- TARGET (Variable objetivo): 1 - cliente con dificultades de pago: tuvo retrasos en el pago de más de X días en al menos uno de los primeros Y plazos del préstamo en nuestra muestra, 0 - todos los demás casos.
- NAME_CONTRACT_TYPE: Identificación de si el préstamo es al contado o rotatorio.
- FLAG_OWN_CAR: Marca si el cliente es propietario de un coche.
- FLAG_OWN_REALTY: Marca si el cliente es propietario de una casa o un piso.
- CNT_CHILDREN: Número de hijos del cliente.
- AMT_INCOME_TOTAL: Ingresos del cliente.
- AMT_CREDIT: Importe del crédito del préstamo.
- AMT_ANNUITY: Anualidad del préstamo.
- AMT_GOODS_PRICE: Para préstamos al consumo es el precio de los bienes para los que se concede el préstamo.
- NAME_INCOME_TYPE: Tipo de ingresos del cliente (empresario, trabajador, baja por maternidad,…)
- NAME_EDUCATION_TYPE: Nivel de estudios más alto alcanzado por el cliente.
- NAME_FAMILY_STATUS: Situación familiar del cliente.
- NAME_HOUSING_TYPE: Cuál es la situación de vivienda del cliente (alquiler, vive con los padres, …)
- REGION_RATING_CLIENT_W_CITY: Nuestra valoración de la región en la que vive el cliente teniendo en cuenta la ciudad (1,2,3)

Análisis Exploratorio de los Datos (EDA)

Instalación y llamado de ibrerías

library(foreign)        # Read Data Stored by 'Minitab', 'S', 'SAS', 'SPSS', 'Stata', 'Systat', 'Weka', 'dBase'
library(ggplot2)        # It is a system for creating graphics
library(dplyr)          # A fast, consistent tool for working with data frame like objects
library(mapview)        # Quickly and conveniently create interactive visualizations of spatial data with or without background maps
library(naniar)         # Provides data structures and functions that facilitate the plotting of missing values and examination of imputations.
library(tmaptools)       # A collection of functions to create spatial weights matrix objects from polygon 'contiguities', for summarizing these objects, and for permitting their use in spatial data analysis
library(tmap)           # For drawing thematic maps
library(RColorBrewer)   # It offers several color palettes 
library(dlookr)         # A collection of tools that support data diagnosis, exploration, and transformation
library(foreign)
library(modelr)
library(dplyr)
library(tidyverse) 
library(ggplot2)
library(broom)
library(ISLR)          # great textbook to learn, explore, and put in practice data science skills.  
library(readr)
library(caret)
library(e1071)
library(class)
library(ROCR)
library(pROC)
library(lmtest)
library(caTools)
library(rpart)
library(rpart.plot)
library(psych)  
library(ggpubr)
library(reshape)
library(Metrics)
library(mlbench)
library(rsample)
library(cluster)     # clustering algorithms
library(factoextra)  # clustering algorithms & visualization
library(gridExtra)   
library(DataExplorer)

Carga de base de datos “bank_application.csv”

df <- read.csv("C:\\Users\\AVRIL\\Documents\\bank_application2.csv")

Se eliminan duplicados

df <- unique(df)
dim(df)
## [1] 179020     15

Medidas descriptivas

Se determina que no hay missing rows y acorde a los estadísticos descriptivos se determina que: * Las personas con un tipo de contrato “Work” tienen el mayor ingreso total promedio.
* Las personas con un nivel educativo “Higher education” tienen el mayor ingreso total promedio. * Las personas con un estado civil “Married” tienen el mayor ingreso total promedio. * Las mayoría de las personas que vive en una región con calificación de “2”.

A continuación, se genera resumen de cada una de las variables posterior a la identificación y reemplazo de NaN con 0

# Se identifica si hay valores nulos
df[is.na(df)] <- 0

dfn <- is.na(df)
df <- na.omit(df)

plot_missing(df)

Se reemplazan outliers de variables numéricas seleccionadas con mediana

df$NAME_CONTRACT_TYPE <- as.factor(df$NAME_CONTRACT_TYPE)
df$GENDER <- as.factor(df$GENDER)
df$FLAG_OWN_REALTY <- as.factor(df$FLAG_OWN_REALTY)
df$FLAG_OWN_CAR <- as.factor(df$FLAG_OWN_CAR)
df$NAME_INCOME_TYPE <- as.factor(df$NAME_INCOME_TYPE)
df$NAME_EDUCATION_TYPE <- as.factor(df$NAME_EDUCATION_TYPE)
df$NAME_FAMILY_STATUS <- as.factor(df$NAME_FAMILY_STATUS)
df$NAME_HOUSING_TYPE <- as.factor(df$NAME_HOUSING_TYPE)

# Convertir los factores a numéricos
df$GENDER <- as.numeric(df$GENDER)
df$FLAG_OWN_CAR <- as.numeric(df$FLAG_OWN_CAR)
df$FLAG_OWN_REALTY <- as.numeric(df$FLAG_OWN_REALTY)
df$NAME_CONTRACT_TYPE <- as.numeric(df$NAME_CONTRACT_TYPE)
df$NAME_INCOME_TYPE <- as.numeric(df$NAME_INCOME_TYPE)
df$NAME_EDUCATION_TYPE <- as.numeric(df$NAME_EDUCATION_TYPE)
df$NAME_FAMILY_STATUS <- as.numeric(df$NAME_FAMILY_STATUS)
df$NAME_HOUSING_TYPE <- as.numeric(df$NAME_HOUSING_TYPE)
df$NAME_FAMILY_STATUS <- as.numeric(df$NAME_FAMILY_STATUS)
df$NAME_FAMILY_STATUS <- as.numeric(df$NAME_FAMILY_STATUS)


reemplazar_outliers <- function(df, cols) {
  for (col in cols) {
    qnt <- quantile(df[[col]], c(0.25, 0.75))
    iqr <- qnt[2] - qnt[1]
    lower <- qnt[1] - 1.5 * iqr
    upper <- qnt[2] + 1.5 * iqr
    df[[col]][df[[col]] < lower | df[[col]] > upper] <- median(df[[col]], na.rm = TRUE)
  }
  return(df)
}
# Lista de columnas a reemplazar outliers
columnas_a_reemplazar <- c("CNT_CHILDREN", "AMT_INCOME_TOTAL", "DAYS_EMPLOYED", "OWN_CAR_AGE")

# Función para reemplazar outliers
df <- reemplazar_outliers(df, columnas_a_reemplazar)
# Visualización de medidas descriptivas 

summary(df)
##      TARGET        NAME_CONTRACT_TYPE     GENDER       FLAG_OWN_CAR  
##  Min.   :0.00000   Min.   :1.000      Min.   :1.000   Min.   :1.000  
##  1st Qu.:0.00000   1st Qu.:1.000      1st Qu.:1.000   1st Qu.:1.000  
##  Median :0.00000   Median :1.000      Median :1.000   Median :1.000  
##  Mean   :0.08383   Mean   :1.081      Mean   :1.345   Mean   :1.343  
##  3rd Qu.:0.00000   3rd Qu.:1.000      3rd Qu.:2.000   3rd Qu.:2.000  
##  Max.   :1.00000   Max.   :2.000      Max.   :2.000   Max.   :2.000  
##  FLAG_OWN_REALTY  CNT_CHILDREN    AMT_INCOME_TOTAL   AMT_CREDIT     
##  Min.   :1.000   Min.   :0.0000   Min.   : 25650   Min.   :  45000  
##  1st Qu.:1.000   1st Qu.:0.0000   1st Qu.:112500   1st Qu.: 273636  
##  Median :2.000   Median :0.0000   Median :153000   Median : 518562  
##  Mean   :1.687   Mean   :0.3757   Mean   :154921   Mean   : 603622  
##  3rd Qu.:2.000   3rd Qu.:1.0000   3rd Qu.:190350   3rd Qu.: 810000  
##  Max.   :2.000   Max.   :2.0000   Max.   :337500   Max.   :4050000  
##   AMT_ANNUITY     AMT_GOODS_PRICE   NAME_INCOME_TYPE NAME_EDUCATION_TYPE
##  Min.   :     0   Min.   :      0   Min.   :1.000    Min.   :1.000      
##  1st Qu.: 16799   1st Qu.: 238500   1st Qu.:4.000    1st Qu.:3.000      
##  Median : 25128   Median : 450000   Median :8.000    Median :5.000      
##  Mean   : 27358   Mean   : 541663   Mean   :5.652    Mean   :4.175      
##  3rd Qu.: 34911   3rd Qu.: 679500   3rd Qu.:8.000    3rd Qu.:5.000      
##  Max.   :258026   Max.   :4050000   Max.   :8.000    Max.   :5.000      
##  NAME_FAMILY_STATUS NAME_HOUSING_TYPE REGION_RATING_CLIENT_W_CITY
##  Min.   :1.000      Min.   :1.000     Min.   :1.000              
##  1st Qu.:2.000      1st Qu.:2.000     1st Qu.:2.000              
##  Median :2.000      Median :2.000     Median :2.000              
##  Mean   :2.472      Mean   :2.296     Mean   :2.032              
##  3rd Qu.:3.000      3rd Qu.:2.000     3rd Qu.:2.000              
##  Max.   :6.000      Max.   :6.000     Max.   :3.000

Medidas de dispersión

Acorde a las gráficas siguientes se determina que:
- Las variables AMT_ANNUITY, AMT_CREDIT, AMT_GOODS_PRICE y AMT_INCOME_TOTAL tienen un rango similar (40,000).
- La variable AMT_INCOME_TOTAL tiene la mayor desviación estándar y varianza, lo que indica que hay una mayor dispersión en los valores de esta variable.
- La variable CNT_CHILDREN tiene la menor desviación estándar y varianza, lo que indica que hay una menor dispersión en los valores de esta variable.

En este chunk se genera boxplot de cada una de las variabes numéricas del dataframe para identificar outliers

columnas_numericas <- names(df)[sapply(df, is.numeric)]

for (col in columnas_numericas) {
  boxplot(df[[col]], main=col)
}

Identificación de patrones y/o tendencias en los datos mediante el uso de gráficos

Los gráficos posteriores muestran las siguientes tendencias:
* La mayoría de las correlaciones son positivas, lo que indica que las variables tienden a aumentar o disminuir juntas. * Las variables relacionadas con el ingreso (CNT_CHILDREN, AMT_INCOME_TOTAL) y el crédito (AMT_CREDIT, AMT_ANNUITY) tienen las correlaciones más fuertes. * Las variables relacionadas con la ubicación (REGION_RATING_CLIENT) también tienen correlaciones moderadas.

Gráficos de para visualizar distribución de variables

# Descripción introductoria o resumen de df
introduce(df)
##     rows columns discrete_columns continuous_columns all_missing_columns
## 1 179020      15                0                 15                   0
##   total_missing_values complete_rows total_observations memory_usage
## 1                    0        179020            2685300     20054424
plot_intro(df)

# Histograma para cada variable numérica del dataframe df
plot_histogram(df)

# Gráfico de barras para cada variable categórica 
plot_bar(df)

Visualización de distribución normal

plot_normality(df)

Correlación entre variables
Se visualiza que hay una asociación positiva entre expenses y smoker

dev.new(width = 10, height = 8)
correlate(df) %>%  plot()
plot_correlation(df)

Transformación de variables de interés

Acorde a los gráficos de distribución normal, se identificaron las variables que tendrían mejor normalidad al convertirlas a logaritmo para generar el dataframe.

df$log_AMT_CREDIT <- log(df$AMT_CREDIT)
df$log_AMT_ANNUITY <- log(df$AMT_ANNUITY+0.01)
df$log_AMT_GOODS_PRICE <- log(df$AMT_GOODS_PRICE+0.01)

# Genera el nuevo dataframe df1 con las transformaciones
df1 <- data.frame(
  TARGET = df$TARGET,
  log_AMT_CREDIT = df$log_AMT_CREDIT,
  log_AMT_ANNUITY = df$log_AMT_ANNUITY,
  log_AMT_GOODS_PRICE = df$log_AMT_GOODS_PRICE,
  NAME_CONTRACT_TYPE = df$NAME_CONTRACT_TYPE,
  GENDER = df$GENDER,
  FLAG_OWN_CAR = df$FLAG_OWN_CAR,
  FLAG_OWN_REALTY = df$FLAG_OWN_REALTY,
  CNT_CHILDREN = df$CNT_CHILDREN,
  AMT_INCOME_TOTAL = df$AMT_INCOME_TOTAL,
  NAME_INCOME_TYPE = df$NAME_INCOME_TYPE,
  NAME_EDUCATION_TYPE = df$NAME_EDUCATION_TYPE,
  NAME_FAMILY_STATUS = df$NAME_FAMILY_STATUS,
  NAME_HOUSING_TYPE = df$NAME_HOUSING_TYPE,
  REGION_RATING_CLIENT_W_CITY = df$REGION_RATING_CLIENT_W_CITY
)

summary(df1)
##      TARGET        log_AMT_CREDIT  log_AMT_ANNUITY  log_AMT_GOODS_PRICE
##  Min.   :0.00000   Min.   :10.71   Min.   :-4.605   Min.   :-4.605     
##  1st Qu.:0.00000   1st Qu.:12.52   1st Qu.: 9.729   1st Qu.:12.382     
##  Median :0.00000   Median :13.16   Median :10.132   Median :13.017     
##  Mean   :0.08383   Mean   :13.08   Mean   :10.077   Mean   :12.953     
##  3rd Qu.:0.00000   3rd Qu.:13.60   3rd Qu.:10.461   3rd Qu.:13.429     
##  Max.   :1.00000   Max.   :15.21   Max.   :12.461   Max.   :15.214     
##  NAME_CONTRACT_TYPE     GENDER       FLAG_OWN_CAR   FLAG_OWN_REALTY
##  Min.   :1.000      Min.   :1.000   Min.   :1.000   Min.   :1.000  
##  1st Qu.:1.000      1st Qu.:1.000   1st Qu.:1.000   1st Qu.:1.000  
##  Median :1.000      Median :1.000   Median :1.000   Median :2.000  
##  Mean   :1.081      Mean   :1.345   Mean   :1.343   Mean   :1.687  
##  3rd Qu.:1.000      3rd Qu.:2.000   3rd Qu.:2.000   3rd Qu.:2.000  
##  Max.   :2.000      Max.   :2.000   Max.   :2.000   Max.   :2.000  
##   CNT_CHILDREN    AMT_INCOME_TOTAL NAME_INCOME_TYPE NAME_EDUCATION_TYPE
##  Min.   :0.0000   Min.   : 25650   Min.   :1.000    Min.   :1.000      
##  1st Qu.:0.0000   1st Qu.:112500   1st Qu.:4.000    1st Qu.:3.000      
##  Median :0.0000   Median :153000   Median :8.000    Median :5.000      
##  Mean   :0.3757   Mean   :154921   Mean   :5.652    Mean   :4.175      
##  3rd Qu.:1.0000   3rd Qu.:190350   3rd Qu.:8.000    3rd Qu.:5.000      
##  Max.   :2.0000   Max.   :337500   Max.   :8.000    Max.   :5.000      
##  NAME_FAMILY_STATUS NAME_HOUSING_TYPE REGION_RATING_CLIENT_W_CITY
##  Min.   :1.000      Min.   :1.000     Min.   :1.000              
##  1st Qu.:2.000      1st Qu.:2.000     1st Qu.:2.000              
##  Median :2.000      Median :2.000     Median :2.000              
##  Mean   :2.472      Mean   :2.296     Mean   :2.032              
##  3rd Qu.:3.000      3rd Qu.:2.000     3rd Qu.:2.000              
##  Max.   :6.000      Max.   :6.000     Max.   :3.000

Especificación del modelo de regresión lineal a estimar

Se infiere que el impacto de cada una de las variables explicativas sobre la principal variable de estudio, en este caso “TARGET”, sería de esta manera:
- AMT ANNUITY: Se espera que un aumento en la cantidad de la anualidad tenga un impacto positivo en la probabilidad de que un cliente sea bueno.
- AMT CREDIT: Se espera que un aumento en la cantidad del crédito tenga un impacto negativo en la probabilidad de que un cliente sea bueno.
- AMT GOODS PRICE: Se espera que un aumento en la cantidad del precio de los bienes tenga un impacto negativo en la probabilidad de que un cliente sea bueno.
- AMT INCOME TOTAL: Se espera que un aumento en la cantidad del ingreso total tenga un impacto positivo en la probabilidad de que un cliente sea bueno.
- CNT CHILDREN: Se espera que un aumento en el número de hijos tenga un impacto negativo en la probabilidad de que un cliente sea bueno.
- REGION_RATING_CLIENT_W_CITY: Se espera que un aumento en la calificación del cliente con ciudad tenga un impacto positivo en la probabilidad de que un cliente sea bueno.

par(mfrow = c(3, 2))  # Divide el área de la trama en una cuadrícula de 3 filas y 2 columnas

# AMT_CREDIT
boxplot(TARGET ~ AMT_CREDIT, data = df, main = "TARGET vs. AMT_CREDIT", xlab = "AMT_CREDIT", ylab = "TARGET")

# AMT_ANNUITY
boxplot(TARGET ~ AMT_ANNUITY, data = df, main = "TARGET vs. AMT_ANNUITY", xlab = "AMT_ANNUITY", ylab = "TARGET")

# AMT_GOODS_PRICE
boxplot(TARGET ~ AMT_GOODS_PRICE, data = df, main = "TARGET vs. AMT_GOODS_PRICE", xlab = "AMT_GOODS_PRICE", ylab = "TARGET")

Estimación de modelos de Supervised Machine Learning (SML)

Partición de bd

set.seed(123)  
partition <- createDataPartition(y = df1$TARGET, p=0.7, list=F)
train = df1[partition, ]
test  = df1[-partition, ]

Simple Logistic Model

simple_logistic <- glm(TARGET ~ log_AMT_CREDIT, family = "binomial", data = train)
summary(simple_logistic) 
## 
## Call:
## glm(formula = TARGET ~ log_AMT_CREDIT, family = "binomial", data = train)
## 
## Coefficients:
##                Estimate Std. Error z value Pr(>|z|)    
## (Intercept)    -1.55828    0.18480  -8.432  < 2e-16 ***
## log_AMT_CREDIT -0.06341    0.01414  -4.485  7.3e-06 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 72407  on 125313  degrees of freedom
## Residual deviance: 72387  on 125312  degrees of freedom
## AIC: 72391
## 
## Number of Fisher Scoring iterations: 5
exp(coef(simple_logistic))
##    (Intercept) log_AMT_CREDIT 
##      0.2104977      0.9385604
confint(simple_logistic)   ### the estimated coefficients based on the confidence interval 
##                      2.5 %      97.5 %
## (Intercept)    -1.92096520 -1.19655969
## log_AMT_CREDIT -0.09109548 -0.03567187
varImp(simple_logistic)
##                Overall
## log_AMT_CREDIT  4.4847
test_simple_logistic <- predict(simple_logistic, newdata = test, type = "response")
predicted_classes <- ifelse(test_simple_logistic > 0.5, 1, 0)
confusion_matrix <- confusionMatrix(factor(predicted_classes), factor(test$TARGET))
confusion_matrix
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction     0     1
##          0 49250  4456
##          1     0     0
##                                           
##                Accuracy : 0.917           
##                  95% CI : (0.9147, 0.9193)
##     No Information Rate : 0.917           
##     P-Value [Acc > NIR] : 0.504           
##                                           
##                   Kappa : 0               
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 1.000           
##             Specificity : 0.000           
##          Pos Pred Value : 0.917           
##          Neg Pred Value :   NaN           
##              Prevalence : 0.917           
##          Detection Rate : 0.917           
##    Detection Prevalence : 1.000           
##       Balanced Accuracy : 0.500           
##                                           
##        'Positive' Class : 0               
## 
par(mfrow=c(1, 2))

prediction(test_simple_logistic, test$TARGET) %>%
  performance(measure = "tpr", x.measure = "fpr") %>%
  plot()

# AUC - Simple Logistic Regression Model 
auc_simple <- prediction(test_simple_logistic, test$TARGET) %>% performance(measure = "auc") %>% .@y.values
#KAPP
kappa_simple <- confusion_matrix$overall["Kappa"]
kappa_simple
## Kappa 
##     0

Simple Logistic Model Ajusted

Se realiza un Simple Logistic Model Ajustado con el fin de evitar que el desbalanceo de clases afecta las métricas de clasificación del modelo.

class_weights <- ifelse(train$TARGET == 0, mean(train$TARGET == 1), mean(train$TARGET == 0))
simple_logistic_wss <- glm(TARGET ~ log_AMT_CREDIT, family = "binomial", data = train, weights = class_weights)

predictions_simple_wss <- predict(simple_logistic_wss, newdata = test, type = "response")

predicted_simple_wss <- ifelse(predictions_simple_wss > 0.5, 1, 0)

confusion_simple_wss <- confusionMatrix(factor(predicted_simple_wss), factor(test$TARGET))
confusion_simple_wss
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction     0     1
##          0 27314  2326
##          1 21936  2130
##                                          
##                Accuracy : 0.5482         
##                  95% CI : (0.544, 0.5525)
##     No Information Rate : 0.917          
##     P-Value [Acc > NIR] : 1              
##                                          
##                   Kappa : 0.0109         
##                                          
##  Mcnemar's Test P-Value : <2e-16         
##                                          
##             Sensitivity : 0.55460        
##             Specificity : 0.47801        
##          Pos Pred Value : 0.92152        
##          Neg Pred Value : 0.08851        
##              Prevalence : 0.91703        
##          Detection Rate : 0.50858        
##    Detection Prevalence : 0.55189        
##       Balanced Accuracy : 0.51630        
##                                          
##        'Positive' Class : 0              
## 
# AUC - Simple Logistic Regression Model 
auc_simple_wss <- prediction(predictions_simple_wss, test$TARGET) %>% performance(measure = "auc") %>% .@y.values
#KAPP
kappa_simple_wss <- confusion_simple_wss$overall["Kappa"]

Multiple Logistic Model

multiple_logistic <- glm(TARGET ~ ., family = "binomial", data = train)
summary(multiple_logistic)
## 
## Call:
## glm(formula = TARGET ~ ., family = "binomial", data = train)
## 
## Coefficients:
##                               Estimate Std. Error z value Pr(>|z|)    
## (Intercept)                 -4.893e+00  2.608e-01 -18.764  < 2e-16 ***
## log_AMT_CREDIT              -6.556e-02  3.059e-02  -2.143 0.032099 *  
## log_AMT_ANNUITY              2.164e-01  3.691e-02   5.864 4.53e-09 ***
## log_AMT_GOODS_PRICE         -5.975e-02  1.590e-02  -3.759 0.000171 ***
## NAME_CONTRACT_TYPE          -1.900e-01  4.417e-02  -4.301 1.70e-05 ***
## GENDER                       4.651e-01  2.268e-02  20.504  < 2e-16 ***
## FLAG_OWN_CAR                -3.364e-01  2.409e-02 -13.965  < 2e-16 ***
## FLAG_OWN_REALTY              4.153e-02  2.270e-02   1.830 0.067304 .  
## CNT_CHILDREN                 8.972e-02  1.581e-02   5.676 1.38e-08 ***
## AMT_INCOME_TOTAL            -3.430e-07  1.921e-07  -1.785 0.074207 .  
## NAME_INCOME_TYPE             5.123e-02  4.235e-03  12.096  < 2e-16 ***
## NAME_EDUCATION_TYPE          1.485e-01  9.046e-03  16.411  < 2e-16 ***
## NAME_FAMILY_STATUS          -2.299e-03  9.205e-03  -0.250 0.802812    
## NAME_HOUSING_TYPE            8.379e-02  9.760e-03   8.584  < 2e-16 ***
## REGION_RATING_CLIENT_W_CITY  3.756e-01  2.063e-02  18.203  < 2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 72407  on 125313  degrees of freedom
## Residual deviance: 70649  on 125299  degrees of freedom
## AIC: 70679
## 
## Number of Fisher Scoring iterations: 5
varImp(multiple_logistic)
##                                Overall
## log_AMT_CREDIT               2.1431741
## log_AMT_ANNUITY              5.8637464
## log_AMT_GOODS_PRICE          3.7587415
## NAME_CONTRACT_TYPE           4.3010208
## GENDER                      20.5040567
## FLAG_OWN_CAR                13.9651679
## FLAG_OWN_REALTY              1.8296379
## CNT_CHILDREN                 5.6758051
## AMT_INCOME_TOTAL             1.7853344
## NAME_INCOME_TYPE            12.0963472
## NAME_EDUCATION_TYPE         16.4113921
## NAME_FAMILY_STATUS           0.2497099
## NAME_HOUSING_TYPE            8.5844808
## REGION_RATING_CLIENT_W_CITY 18.2033900
plot(multiple_logistic, which = 4, id.n = 5)

multiple_logistic_data <- augment(multiple_logistic) %>%  mutate(index = 1:n())
multiple_logistic_data %>% top_n(5, .cooksd)
## # A tibble: 5 × 23
##   .rownames TARGET log_AMT_CREDIT log_AMT_ANNUITY log_AMT_GOODS_PRICE
##   <chr>      <int>          <dbl>           <dbl>               <dbl>
## 1 49926          1           12.5            9.51               -4.61
## 2 55280          1           13.7           10.7                -4.61
## 3 68430          1           12.1            9.10               -4.61
## 4 121951         1           11.8            8.82               -4.61
## 5 149739         1           11.8            8.82               -4.61
## # ℹ 18 more variables: NAME_CONTRACT_TYPE <dbl>, GENDER <dbl>,
## #   FLAG_OWN_CAR <dbl>, FLAG_OWN_REALTY <dbl>, CNT_CHILDREN <dbl>,
## #   AMT_INCOME_TOTAL <dbl>, NAME_INCOME_TYPE <dbl>, NAME_EDUCATION_TYPE <dbl>,
## #   NAME_FAMILY_STATUS <dbl>, NAME_HOUSING_TYPE <dbl>,
## #   REGION_RATING_CLIENT_W_CITY <int>, .fitted <dbl>, .resid <dbl>, .hat <dbl>,
## #   .sigma <dbl>, .cooksd <dbl>, .std.resid <dbl>, index <int>
library(caret)

# Predice las clases en el conjunto de prueba
predictions_multiple <- predict(multiple_logistic, newdata = test, type = "response")

predicted_classes <- ifelse(predictions_multiple > 0.5, 1, 0)

confusion_multiple <- confusionMatrix(factor(predicted_classes), factor(test$TARGET))
confusion_multiple
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction     0     1
##          0 49250  4456
##          1     0     0
##                                           
##                Accuracy : 0.917           
##                  95% CI : (0.9147, 0.9193)
##     No Information Rate : 0.917           
##     P-Value [Acc > NIR] : 0.504           
##                                           
##                   Kappa : 0               
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 1.000           
##             Specificity : 0.000           
##          Pos Pred Value : 0.917           
##          Neg Pred Value :   NaN           
##              Prevalence : 0.917           
##          Detection Rate : 0.917           
##    Detection Prevalence : 1.000           
##       Balanced Accuracy : 0.500           
##                                           
##        'Positive' Class : 0               
## 
par(mfrow=c(1, 2))

prediction(predictions_multiple, test$TARGET) %>%
  performance(measure = "tpr", x.measure = "fpr") %>%
  plot()

# AUC  
auc_multiple <- prediction(predictions_multiple, test$TARGET) %>% performance(measure = "auc") %>% .@y.values
kappa_multiple <- confusion_multiple$overall["Kappa"]
kappa_multiple
## Kappa 
##     0

Multiple Logistic Model Ajusted

Se realiza un Multiple Logistic Model Ajustado con el fin de evitar que el desbalanceo de clases afecta las métricas de clasificación del modelo.

class_weights_multiple <- ifelse(train$TARGET == 0, mean(train$TARGET == 1), mean(train$TARGET == 0))

# Entrenar el modelo de regresión logística con ajuste de pesos de clase
multiple_logistic_weighted <- glm(TARGET ~ ., family = "binomial", data = train, weights = class_weights_multiple)

predictions_wss <- predict(multiple_logistic_weighted, newdata = test, type = "response")

predicted_wss <- ifelse(predictions_wss > 0.5, 1, 0)

confusion_multiple_wss <- confusionMatrix(factor(predicted_wss), factor(test$TARGET))
confusion_multiple_wss
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction     0     1
##          0 28663  1803
##          1 20587  2653
##                                           
##                Accuracy : 0.5831          
##                  95% CI : (0.5789, 0.5873)
##     No Information Rate : 0.917           
##     P-Value [Acc > NIR] : 1               
##                                           
##                   Kappa : 0.0608          
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 0.5820          
##             Specificity : 0.5954          
##          Pos Pred Value : 0.9408          
##          Neg Pred Value : 0.1142          
##              Prevalence : 0.9170          
##          Detection Rate : 0.5337          
##    Detection Prevalence : 0.5673          
##       Balanced Accuracy : 0.5887          
##                                           
##        'Positive' Class : 0               
## 
auc_multiple_wss <- prediction(predictions_wss, test$TARGET) %>% performance(measure = "auc") %>% .@y.values
kapp_multiple_wss <- confusion_multiple_wss$overall["Kappa"]

Decision Trees

A causa del desbalanceo de clases, el decision tree implica cierto sesgo de clasificación lo que se denota en la matriz de confusión, ya que el modelo no está prediciendo la clase positiva en absoluto y, en cambio, está prediciendo la mayoría de las instancias como negativas.

set.seed(123)
dt.rpart <- rpart((factor(TARGET)) ~ .,data = train, method = "class", control = rpart.control(cp=0.005))
dt.rpart
## n= 125314 
## 
## node), split, n, loss, yval, (yprob)
##       * denotes terminal node
## 
## 1) root 125314 10551 0 (0.9158035 0.0841965) *
prp(dt.rpart, extra = 2) # extra argument includes the proportion of correct predictions (*)

### TESTING PERFORMANCE 
test_ppredict_tree <- predict(object = dt.rpart, newdata = test, type = "class")

# test confusion matrix
confusion_tree <- confusionMatrix(factor(test_ppredict_tree), factor(test$TARGET))
confusion_tree
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction     0     1
##          0 49250  4456
##          1     0     0
##                                           
##                Accuracy : 0.917           
##                  95% CI : (0.9147, 0.9193)
##     No Information Rate : 0.917           
##     P-Value [Acc > NIR] : 0.504           
##                                           
##                   Kappa : 0               
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 1.000           
##             Specificity : 0.000           
##          Pos Pred Value : 0.917           
##          Neg Pred Value :   NaN           
##              Prevalence : 0.917           
##          Detection Rate : 0.917           
##    Detection Prevalence : 1.000           
##       Balanced Accuracy : 0.500           
##                                           
##        'Positive' Class : 0               
## 
kappa_tree <- confusion_tree$overall["Kappa"]
kappa_tree
## Kappa 
##     0
test_pprob_tree <- predict(object = dt.rpart, newdata = test, type = "prob")

roc_obj_tree <- roc(test$TARGET, test_pprob_tree[, 1])  # Assuming first column is positive class probability

# Plotting ROC curve
plot.roc(roc_obj_tree, main = "Curva ROC", col = "blue")

# AUC - Simple Logistic Regression Model 
auc_tree <- roc(test$TARGET, test_pprob_tree[, 1])$auc
auc_tree
## Area under the curve: 0.5

Random Forest

library(randomForest)

train$TARGET <- as.factor(train$TARGET)

set.seed(123)
random_forest <- randomForest(TARGET ~ log_AMT_CREDIT + log_AMT_ANNUITY + log_AMT_GOODS_PRICE + NAME_CONTRACT_TYPE + GENDER + FLAG_OWN_CAR + FLAG_OWN_REALTY + CNT_CHILDREN + AMT_INCOME_TOTAL + NAME_INCOME_TYPE + NAME_EDUCATION_TYPE + NAME_FAMILY_STATUS + NAME_HOUSING_TYPE + REGION_RATING_CLIENT_W_CITY, data=train, ntree=500, mtry=4)
print(random_forest) 
## 
## Call:
##  randomForest(formula = TARGET ~ log_AMT_CREDIT + log_AMT_ANNUITY +      log_AMT_GOODS_PRICE + NAME_CONTRACT_TYPE + GENDER + FLAG_OWN_CAR +      FLAG_OWN_REALTY + CNT_CHILDREN + AMT_INCOME_TOTAL + NAME_INCOME_TYPE +      NAME_EDUCATION_TYPE + NAME_FAMILY_STATUS + NAME_HOUSING_TYPE +      REGION_RATING_CLIENT_W_CITY, data = train, ntree = 500, mtry = 4) 
##                Type of random forest: classification
##                      Number of trees: 500
## No. of variables tried at each split: 4
## 
##         OOB estimate of  error rate: 8.5%
## Confusion matrix:
##        0   1 class.error
## 0 114639 124 0.001080488
## 1  10532  19 0.998199223
varImpPlot(random_forest, n.var = 10, main = "Top 10 - Variable")

importance(random_forest)  
##                             MeanDecreaseGini
## log_AMT_CREDIT                    2912.13155
## log_AMT_ANNUITY                   3939.97160
## log_AMT_GOODS_PRICE               2048.80362
## NAME_CONTRACT_TYPE                  97.77419
## GENDER                             176.42915
## FLAG_OWN_CAR                       335.00925
## FLAG_OWN_REALTY                    526.55972
## CNT_CHILDREN                       648.33952
## AMT_INCOME_TOTAL                  2844.76651
## NAME_INCOME_TYPE                   407.90761
## NAME_EDUCATION_TYPE                348.15858
## NAME_FAMILY_STATUS                 934.95265
## NAME_HOUSING_TYPE                  489.00328
## REGION_RATING_CLIENT_W_CITY        384.45193
predictions_rf <- predict(random_forest, newdata = test)

predictions_rf <- as.factor(predictions_rf)
test$TARGET <- as.factor(test$TARGET)

# Crear la matriz de confusión
confusion_rf <- confusionMatrix(predictions_rf, test$TARGET)
print(confusion_rf)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction     0     1
##          0 49201  4451
##          1    49     5
##                                           
##                Accuracy : 0.9162          
##                  95% CI : (0.9138, 0.9185)
##     No Information Rate : 0.917           
##     P-Value [Acc > NIR] : 0.7572          
##                                           
##                   Kappa : 2e-04           
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 0.999005        
##             Specificity : 0.001122        
##          Pos Pred Value : 0.917039        
##          Neg Pred Value : 0.092593        
##              Prevalence : 0.917030        
##          Detection Rate : 0.916117        
##    Detection Prevalence : 0.998995        
##       Balanced Accuracy : 0.500064        
##                                           
##        'Positive' Class : 0               
## 
prediccion_prob_rf <- predict(random_forest, test, type = "prob")
prediccion_prob_true_rf <- prediccion_prob_rf[, "1"]

## Generar la curva ROC
roc_obj_rf <- roc(test$TARGET, prediccion_prob_true_rf)

## Dibujar la curva ROC
plot.roc(roc_obj_rf, main="Curva ROC", col="blue")

## Calcular el AUC
library(pROC)
# AUC - Simple Logistic Regression Model 
auc_rf <- roc(test$TARGET, prediccion_prob_rf[, 1])$auc
auc_rf
## Area under the curve: 0.6121

SVM

Se selecciona una sola variable explicativa a causa de la dimnesionalidad de la bd y permite captar una parte significativa del poder predictivo del conjunto de datos a causa de la influencia/importancia de la variable, lo que la convertiría en una buena candidata para un modelo de una sola variable.

svm_model <- svm(TARGET ~ log_AMT_CREDIT, data = train,type = "C-classification", kernel = "linear",scale = FALSE)
svm_model
## 
## Call:
## svm(formula = TARGET ~ log_AMT_CREDIT, data = train, type = "C-classification", 
##     kernel = "linear", scale = FALSE)
## 
## 
## Parameters:
##    SVM-Type:  C-classification 
##  SVM-Kernel:  linear 
##        cost:  1 
## 
## Number of Support Vectors:  21108
predict_svm <- predict(svm_model, newdata = test)
confusion_svm <- confusionMatrix(predict_svm, test$TARGET) 
kappa_svm <- confusion_svm$overall["Kappa"]
kappa_svm
## Kappa 
##     0
# validate our model
predict_svm <- predict(svm_model, newdata = test)
confusion_svm <- confusionMatrix(predict_svm, test$TARGET) 
confusion_svm
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction     0     1
##          0 49250  4456
##          1     0     0
##                                           
##                Accuracy : 0.917           
##                  95% CI : (0.9147, 0.9193)
##     No Information Rate : 0.917           
##     P-Value [Acc > NIR] : 0.504           
##                                           
##                   Kappa : 0               
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 1.000           
##             Specificity : 0.000           
##          Pos Pred Value : 0.917           
##          Neg Pred Value :   NaN           
##              Prevalence : 0.917           
##          Detection Rate : 0.917           
##    Detection Prevalence : 1.000           
##       Balanced Accuracy : 0.500           
##                                           
##        'Positive' Class : 0               
## 

K-MEANS

Cabe destacar que, no se obtuvieron las métricas de AUC, ROC y Matriz de confusión debido a la naturaleza del model. No obstante, se genera la gráfica del codo para determinar el número de clusters adecuados.

set.seed(123)

# Define a function to compute within-cluster sum of squares (WCSS)
wss <- function(k) {
  kmeans(df1, k, nstart = 10)$tot.withinss
}

# Initialize variables for storing WCSS values
wcss_values <- vector()

# Compute WCSS for different values of k (number of clusters)
max_k <- 10  # Reducido el rango a 10
for (k in 1:max_k) {
  wcss_values[k] <- wss(k)
}

# Plot the elbow method to find the optimal number of clusters
plot(1:max_k, wcss_values, type = "b", pch = 19, frame = FALSE, 
     xlab = "Clusters (K)",
     ylab = "WCSS",
     main = "Método del Codo")

# Set seed for reproducibility
set.seed(123)
# Acorde al Método de Codo, se selecciona el número de clusters que en este caso sería 2
k2 <- kmeans(df1, centers = 2, nstart = 25)
str(k2)
## List of 9
##  $ cluster     : int [1:179020] 1 1 2 2 2 2 2 2 1 2 ...
##  $ centers     : num [1:2, 1:15] 0.0784 0.0867 13.3398 12.9426 10.2996 ...
##   ..- attr(*, "dimnames")=List of 2
##   .. ..$ : chr [1:2] "1" "2"
##   .. ..$ : chr [1:15] "TARGET" "log_AMT_CREDIT" "log_AMT_ANNUITY" "log_AMT_GOODS_PRICE" ...
##  $ totss       : num 7.06e+14
##  $ withinss    : num [1:2] 1.13e+14 1.22e+14
##  $ tot.withinss: num 2.35e+14
##  $ betweenss   : num 4.7e+14
##  $ size        : int [1:2] 61232 117788
##  $ iter        : int 1
##  $ ifault      : int 0
##  - attr(*, "class")= chr "kmeans"
fviz_cluster(k2, data = df1)

KNN

Cada uno de los modelos siguientes tienen una alta precisión, con valores superiores al 95%. A pesar de esto, se determina que conforme el valor de k disminuye, la precisión del modelo aumenta ligeramente. Esto puede indicar que al considerar menos vecinos cercanos, el modelo tiende a capturar mejor las características locales de los datos. Por lo tanto, el modelo knnn a seleccionar sería knn_model2 con k=1.

library(class)
knn_model <- knn(train = train[, -ncol(train)], test = test[, -ncol(test)], cl = train$TARGET, k = 5)

confusion_knn <- table(Actual = test$TARGET, Predicted = knn_model)
print(confusion_knn)
##       Predicted
## Actual     0     1
##      0 49239    11
##      1  2166  2290
accuracy <- sum(diag(confusion_knn)) / sum(confusion_knn)
print(paste("Accuracy:", accuracy))
## [1] "Accuracy: 0.959464491863107"
knn_model1 <- knn(train = train[, -ncol(train)], test = test[, -ncol(test)], cl = train$TARGET, k = 3)

confusion_knn1 <- table(Actual = test$TARGET, Predicted = knn_model1)
print(confusion_knn1)
##       Predicted
## Actual     0     1
##      0 49216    34
##      1  1833  2623
accuracy1 <- sum(diag(confusion_knn1)) / sum(confusion_knn1)
print(paste("Accuracy:", accuracy1))
## [1] "Accuracy: 0.965236658846311"
knn_model2 <- knn(train = train[, -ncol(train)], test = test[, -ncol(test)], cl = train$TARGET, k = 1)

# Evaluate the model
confusion_knn2 <- table(Actual = test$TARGET, Predicted = knn_model2)
print(confusion_knn2)
##       Predicted
## Actual     0     1
##      0 48992   258
##      1  1394  3062
# Calculate accuracy
accuracyknn <- sum(diag(confusion_knn2)) / sum(confusion_knn2)
print(paste("Accuracy:", accuracyknn))
## [1] "Accuracy: 0.969239935947566"

Naïve Bayes

naive_bayes_model_a   <- naiveBayes(TARGET ~ ., data = train)

### Evaluar resultados de clasificacion 
predicted<-predict(naive_bayes_model_a, as.data.frame(test))
confusionBayes <- confusionMatrix(test$TARGET, predicted)
confusionBayes
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction     0     1
##          0 48037  1213
##          1  4235   221
##                                          
##                Accuracy : 0.8986         
##                  95% CI : (0.896, 0.9011)
##     No Information Rate : 0.9733         
##     P-Value [Acc > NIR] : 1              
##                                          
##                   Kappa : 0.0361         
##                                          
##  Mcnemar's Test P-Value : <2e-16         
##                                          
##             Sensitivity : 0.9190         
##             Specificity : 0.1541         
##          Pos Pred Value : 0.9754         
##          Neg Pred Value : 0.0496         
##              Prevalence : 0.9733         
##          Detection Rate : 0.8944         
##    Detection Prevalence : 0.9170         
##       Balanced Accuracy : 0.5365         
##                                          
##        'Positive' Class : 0              
## 
kappa_Bayes <- confusionBayes$overall["Kappa"]
kappa_Bayes 
##      Kappa 
## 0.03610047
train %>% filter(TARGET == "1") %>% select_if(is.numeric) %>% cor() %>% corrplot::corrplot(type = "upper")

#Se seleccionan variables de importancia acorde a modelos anteriores
train %>% dplyr::select(AMT_INCOME_TOTAL, log_AMT_CREDIT, log_AMT_ANNUITY, log_AMT_GOODS_PRICE, GENDER, NAME_INCOME_TYPE) %>% gather(metric, value) %>% ggplot(aes(value, fill = metric)) + 
  geom_density(show.legend = FALSE) + 
  facet_wrap(~ metric, scales = "free")

predicted_probs <- predict(naive_bayes_model_a, newdata = test, type = "raw")

# Extraer las probabilidades predichas de la clase positiva
positive_probs <- predicted_probs[, "1"]
predictions <- prediction(positive_probs, test$TARGET)

# AUC
auc_bayes <- performance(predictions, "auc")@y.values[[1]]
print(paste("AUC:", auc_bayes))
## [1] "AUC: 0.618200605582856"
# Curva ROC
roc_bayes <- performance(predictions, "tpr", "fpr")
plot(roc_bayes, main = "Curva ROC", col = "blue")
abline(a = 0, b = 1, lty = 2, col = "red")  # Lí

Evaluación y Selección de Modelo de Regresión

Se observan los siguientes hallazgos en relación a la comparativa de los modelos:
1. Los modelos Simple_model y Multiple_model tienen la misma precisión que el KNN, pero tienen un valor Kappa de 0.000000000, lo que indica que no son mejores que el azar. 2. El modelo Random_forest tiene una precisión de 0.9162, pero tiene un valor Kappa de 0.000230883, lo que indica que es un poco mejor que el azar. 3. Los modelos SimpleLogistic, MultipleLogistic, SVM y DecisionTree presentan cieto sesgo en relación a la clasificación de positivos verdaderos debido al desbalance de clases. 4. El modelo K-Means no tiene un valor de precisión, Kappa o AUC, lo que significa que no se puede comparar con los otros modelos. 5. El modelo KNN no tiene un valor de Kappa o AUC, pero tiene un Accuracy de 0.969 lo que significa que es posible que tenga cierto sesgo 6. El modelo Bayes es un buen modelo de clasificación, pero no es el mejor en ninguna de las tres métricas.

Con base en las observaciones comentadas anteriormente, se determina que el modelo Random_forest tiene una alta precisión lo que implica la fracción de predicciones que el modelo realizó correctamente. No obstante, el Multiple_model_wss podría ser preferible debido a su mayor AUC lo que implica que tiene mayor capacidad de discriminación.

accuracysimple <- confusion_matrix$overall["Accuracy"]

accuracysimplewss <- confusion_simple_wss$overall["Accuracy"]

accuracymultiple <- confusion_multiple$overall["Accuracy"]

accuracymultiplewss <- confusion_multiple_wss$overall["Accuracy"]

accuracytree <- confusion_tree$overall["Accuracy"]

accuracyrf <- confusion_rf$overall["Accuracy"]

accuracysvm <- confusion_svm$overall["Accuracy"]

accuracybayes <- confusionBayes$overall["Accuracy"]

resultados <- data.frame(
  "Model" = c("Simple_model", "Simple_model_wss", "Multiple_model", "Multiple_model_wss", 
              "Decision_tree", "Random_forest", "SVM", "Bayes", "KNN", "K-Means"),
  "Accuracy" = c(accuracysimple, accuracysimplewss, accuracymultiple, accuracymultiplewss,
                  accuracytree, accuracyrf, accuracysvm, accuracybayes, accuracyknn, NA),
  "Kappa" = c(kappa_simple, kappa_simple_wss, kappa_multiple, kapp_multiple_wss, 
              kappa_tree, confusion_rf$overall["Kappa"], kappa_svm, kappa_Bayes, NA, NA),  
  "AUC" = c(0.526, 0.526, 0.624, 0.625, auc_tree, auc_rf, NA, auc_bayes, NA, NA)
)

# Ordenar el dataframe por Accuracy
resultados
##                 Model  Accuracy       Kappa       AUC
## 1        Simple_model 0.9170298 0.000000000 0.5260000
## 2    Simple_model_wss 0.5482441 0.010863950 0.5260000
## 3      Multiple_model 0.9170298 0.000000000 0.6240000
## 4  Multiple_model_wss 0.5831006 0.060804144 0.6250000
## 5       Decision_tree 0.9170298 0.000000000 0.5000000
## 6       Random_forest 0.9162105 0.000230883 0.6120832
## 7                 SVM 0.9170298 0.000000000        NA
## 8               Bayes 0.8985588 0.036100468 0.6182006
## 9                 KNN 0.9692399          NA        NA
## 10            K-Means        NA          NA        NA

Hallazgos

EDA

El análisis exploratorio da a conocer que la variable dependiente (TARGET) tiene una fuerte relación lineal segun los coeficientes de correlación con log_AMT_CREDIT, log_AMT_ANNUITY y log_AMT_GOODS_PRICE. Asimismo, se determina que tienen un efecto positivo y significativo sobre la variable objetivo lo que significa que a medida que aumenta el valor de estas variables, también aumenta el valor de y. Según esto, se espera que: * Los clientes con un mayor log_AMT_CREDIT tienen un mayor riesgo de dificultades de pago.
* Los clientes con un log_AMT_ANNUITY elevado tienen más probabilidades de sufrir dificultades de pago, ya que tienen que abonar una cantidad mayor cada año.
* Los clientes que pidieron un préstamo para un alto log_AMT_GOODS_PRICE tienen más probabilidades de tener dificultades de pago debido a que están asumiendo una deuda mayor de la que pueden permitirse

Modelo seleccionado

Con base en lo anterior, se determina que el modelo de Random Forest sería el indicado para predecir a causa de la robustez y eficiencia por el entrenamiento de los árboles de decisión en paralelo. Mientras tanto, el modelo Multiple_model_wss sería ideal para clasificar puesto que, indica una mejor concordancia entre las predicciones del modelo y los valores reales, por ende, mejor discriminación de clases. No obstante, al ser un modelo ajustado es probable que tienda a sobreaprender de los datos o exista algún sesgo.

LS0tDQp0aXRsZTogIkFjdGl2aWRhZDJfTW9kZWxvcyBkZSBDbGFzaWZpY2FjacOzbl9BMDA4MzMxMTMiDQphdXRob3I6ICJBdnJpbCBMb2JhdG8gLSBBMDA4MzMxMTMiDQpkYXRlOiAiMjAyNC0wMy0xMCINCm91dHB1dDogDQogIGh0bWxfZG9jdW1lbnQ6IA0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCi0tLQ0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjojNDU4QjAwIj4qKkludHJvZHVjY2nDs24qKjwvc3Bhbj4NCjEuICoqwr9RdcOpIGVzIFN1cGVydmlzZWQgTWFjaGluZSBMZWFybmluZyB5IGN1w6FsZXMgc29uIGFsZ3VuYXMgZGUgc3VzIGFwbGljYWNpb25lcyBlbiBhbsOhbGlzaXMgZGUgY2xhc2lmaWNhY2nDs24/KiogIA0KRWwgYXByZW5kaXphamUgc3VwZXJ2aXNhZG8gKCpTdXBlcnZpc2VkIE1hY2hpbmUgTGVhcm5pbmcqKSBlcyB1biB0aXBvIGRlIGVuZm9xdWUgZGUgYXByZW5kaXphamUgZG9uZGUgc2UgZW50cmVuYSB1biBtb2RlbG8gdXRpbGl6YW5kbyB1biBjb25qdW50byBkZSBkYXRvcyBxdWUgaW5jbHV5ZSBlbnRyYWRhcyB5IGxhcyByZXNwdWVzdGFzIGRlc2VhZGFzIGFzb2NpYWRhcyBjb24gZXNhcyBlbnRyYWRhcy4gRWwgb2JqZXRpdm8gZXMgYXByZW5kZXIgdW5hIGZ1bmNpw7NuIHF1ZSBtYXBlZSBsYXMgZW50cmFkYXMgYSBsYXMgc2FsaWRhcyBjb3JyZWN0YXMgYmFzYWRhcyBlbiBlamVtcGxvcyBkZSBlbnRyZW5hbWllbnRvIGV0aXF1ZXRhZG9zLiBBbGd1bmFzIGRlIGxhcyBhcGxpY2FjaW9uZXMgY29tdW5lcyBkZWwgYXByZW5kaXphamUgc3VwZXJ2aXNhZG8gZW4gYW7DoWxpc2lzIGRlIGNsYXNpZmljYWNpw7NuIHNvbjoNCi0gQ2xhc2lmaWNhY2nDs24gZGUgY29ycmVvcyBlbGVjdHLDs25pY29zOiBJZGVudGlmaWNhciBzaSB1biBjb3JyZW8gZWxlY3Ryw7NuaWNvIGVzIHNwYW0gbyBubyBzcGFtLiAgDQotIERldGVjY2nDs24gZGUgZnJhdWRlczogUmVjb25vY2VyIHRyYW5zYWNjaW9uZXMgZmluYW5jaWVyYXMgY29tbyBsZWfDrXRpbWFzIG8gZnJhdWR1bGVudGFzLiAgDQotIERpYWduw7NzdGljbyBtw6lkaWNvOiBDbGFzaWZpY2FyIGltw6FnZW5lcyBtw6lkaWNhcyBvIGRhdG9zIGRlIHBhY2llbnRlcyBwYXJhIGRpYWdub3N0aWNhciBlbmZlcm1lZGFkZXMuICANCi0gQW7DoWxpc2lzIGRlIHNlbnRpbWllbnRvczogQ2xhc2lmaWNhciBjb21lbnRhcmlvcyBkZSByZWRlcyBzb2NpYWxlcywgcmVzZcOxYXMgZGUgcHJvZHVjdG9zIHUgb3BpbmlvbmVzIGRlIHVzdWFyaW9zIGVuIHBvc2l0aXZhcywgbmV1dHJhbGVzIG8gbmVnYXRpdmFzLiAgDQotIFJlY29ub2NpbWllbnRvIGRlIHZvejogQ2xhc2lmaWNhciBncmFiYWNpb25lcyBkZSB2b3ogZW4gZGlmZXJlbnRlcyBjYXRlZ29yw61hcywgY29tbyBjb21hbmRvcyBkZSB2b3ogbyB0cmFuc2NyaXBjacOzbiBkZSB0ZXh0by4gIA0KDQoyLiAqKsK/Q3XDoWxlcyBzb24gbG9zIHByaW5jaXBhbGVzIGFsZ29yaXRtb3MgZGUgU3VwZXJ2aXNlZCBNYWNoaW5lIExlYXJuaW5nIC0gQ2xhc3NpZmljYXRpb24/KiogIA0KTG9zIHByaW5jaXBhbGVzIGFsZ29yaXRtb3MgZGUgYXByZW5kaXphamUgc3VwZXJ2aXNhZG8gZGUgQ2xhc2lmaWNhY2nDs24gc29uOg0KKiBSZWdyZXNpw7NuIExvZ8Otc3RpY2E6IFJlYWxpemEgdW5hIGNsYXNpZmljYWNpw7NuIGJpbmFyaWEgYWNvcmRlIGEgbGEgcmVncmVzacOzbiB5YSBxdWUsIGVzdGltYSBsYXMgcHJvYmFiaWxpZGFkZXMgZGUgcXVlIHVuYSBpbnN0YW5jaWEgcGVydGVuZXpjYSBhIHVuYSBkZSBsYXMgZG9zIGNsYXNlcy4gIA0KKiBLLU5lYXJlc3QgTmVpZ2hib3JzIChLTk4pOiBDbGFzaWZpY2EgdW5hIGluc3RhbmNpYSBiYXPDoW5kb3NlIGVuIGxhcyBjbGFzZXMgZGUgc3VzIHZlY2lub3MgbcOhcyBjZXJjYW5vcyBlbiBlbCBlc3BhY2lvIGRlIGNhcmFjdGVyw61zdGljYXMsIGVzIGRlY2lyLCBwcmVkaWNlIGxhIGNsYXNpZmljYWNpw7NuIGRlIHVuIG51ZXZvIHB1bnRvIGRlIG11ZXN0cmEgZW4gZnVuY2nDs24gZGUgbGFzIGNsYXNlcyBkZSBzdXMgdmVjaW5vcyBtw6FzIGNlcmNhbm9zLiBObyByZWNvbWVuZGFibGUgcGFyYSBiZCBjb24gYWx0byBuw7ptZXJvIGRlIGVudHJhZGFzLiAgDQoqIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIChTVk0pOiBCdXNjYSBlbCBoaXBlcnBsYW5vIHF1ZSBtZWpvciBzZXBhcmEgbGFzIGNsYXNlcyBlbiBlbCBlc3BhY2lvIGRlIGNhcmFjdGVyw61zdGljYXMgY29uIGxhIGF5dWRhIGRlIHZlY3RvcmVzIGRlIHNvcG9ydGU7IGVmZWN0aXZvIGVuIGVzcGFjaW9zIGRlIGNhcmFjdGVyw61zdGljYXMgbm8gbGluZWFsZXMgYSB0cmF2w6lzIGRlbCB1c28gZGUga2VybmVscy4gIA0KKiBCYXllczoNCiogw4FyYm9sZXMgZGUgRGVjaXNpw7NuOiBEaXZpZGUgcmVwZXRpZGFtZW50ZSBlbCBlc3BhY2lvIGRlIGNhcmFjdGVyw61zdGljYXMgZW4gc3ViY29uanVudG9zIGNhZGEgdmV6IG3DoXMgcGVxdWXDsW9zIGJhc2Fkb3MgZW4gbGEgY2FyYWN0ZXLDrXN0aWNhIHF1ZSBwcm9wb3JjaW9uYSB1bmEgbWVqb3IgZXN0aW1hY2nDs24gZGUgY2xhc2lmaWNhY2nDs24uICANCiogUmFuZG9tIEZvcmVzdDogQWxnb3JpdG1vIGRlIGNvbmp1bnRvIHF1ZSBjb21iaW5hIG3DoXMgZGUgdW4gYWxnb3J0aW1vIHBhcmEgY2xhc2lmaWNhciBvYmpldG9zLCBwb3IgZW5kZSwgY29tYmluYSBsYXMgcHJlZGljY2lvbmVzIGRlIG3Dumx0aXBsZXMgw6FyYm9sZXMgY3JlYWRvcyBlbiBzdWJjb25qdW50b3MgYWxlYXRvcmlvcyBwYXJhIHRvbWFyIGVsIHByb21lZGlvIGRlIGxhcyBwcmVkaWNjaW9uZXMgeSBtZWpvcmFyIHRhbnRvIGxhIHByZWNpc2nDs24gY29tbyByZWR1Y2lyIGVsIHNvYnJlYWp1c3RlLiAgDQoNCg0KMy4gKipSZXNwZWN0byBhIGxhIHNlbGVjY2nDs24gZGUgbG9zIHJlc3VsdGFkb3MgZGUgbG9zIG1vZGVsb3MgZGUgY2xhc2lmaWNhY2nDs246IMK/UXXDqSBlcyBsYSBtYXRyaXogZGUgY29uZnVzacOzbj8gwr9RdcOpIGVzIGVsIGVzdGFkw61zdGljbyBLYXBwYT8gwr9DdcOhbCBlcyBsYSByZWxhY2nDs24gZW50cmUgQVVDIHkgUk9DIEN1cnZlPyAqKiAgDQpMYSBzZWxlY2Npw7NuIGRlIGxvcyByZXN1bHRhZG9zIGRlIGxvcyBtb2RlbG9zIGRlIGNsYXNpZmljYWNpw7NuIGltcGxpY2EgZXZhbHVhciBzdSBkZXNlbXBlw7FvIHV0aWxpemFuZG8gZGl2ZXJzYXMgbcOpdHJpY2FzIHkgaGVycmFtaWVudGFzLiBMYXMgcHJpbmNpcGFsZXMgc29uOiAgDQoqIE1hdHJpeiBkZSBjb25mdXNpw7NuOiBUYWJsYSBxdWUgZGVzY3JpYmUgZWwgcmVuZGltaWVudG8gZGUgdW4gbW9kZWxvIGRlIGNsYXNpZmljYWNpw7NuIGVuIHTDqXJtaW5vcyBkZSBsb3MgdmFsb3JlcyByZWFsZXMgeSBwcmVkaWNob3MgZGUgbGFzIGNsYXNlcy4gU2UgY29tcG9uZSBkZSBjdWF0cm8gY2VsZGFzOiB2ZXJkYWRlcm9zIHBvc2l0aXZvcyAoVFApLCBmYWxzb3MgcG9zaXRpdm9zIChGUCksIHZlcmRhZGVyb3MgbmVnYXRpdm9zIChUTikgeSBmYWxzb3MgbmVnYXRpdm9zIChGTikuIEFjb3JkZSBhIGVzdG8gc2UgY2FsY3VsYW4gdmFyaWFzIG3DqXRyaWNhcyBkZSBldmFsdWFjacOzbiBjb21vIHByZWNpc2nDs24sIHNlbnNpYmlsaWRhZCwgZXNwZWNpZmljaWRhZCwgZXRjLg0KKiBFc3RhZMOtc3RpY28gS2FwcGE6IE1lZGlkYSBkZSBsYSBjb25jb3JkYW5jaWEgZW50cmUgbGFzIGNsYXNpZmljYWNpb25lcyBvYnNlcnZhZGFzIHkgbGFzIGNsYXNpZmljYWNpb25lcyBlc3BlcmFkYXMgcG9yIGF6YXI7IHBlcm1pdGUgZXZhbHVhciBsYSBjb25maWFiaWxpZGFkIGRlIHVuIG1vZGVsbyBkZSBjbGFzaWZpY2FjacOzbiBhanVzdGFkbywgdGVuaWVuZG8gZW4gY3VlbnRhIGxhIHBvc2liaWxpZGFkIGRlIHF1ZSBsYXMgY2xhc2lmaWNhY2lvbmVzIG9jdXJyYW4gYWwgYXphci4gRGljaG8gdmFsb3IgdmFyw61hIGVudHJlIC0xIHkgMSwgZG9uZGUgMSBpbmRpY2EgdW5hIGNvbmNvcmRhbmNpYSBwZXJmZWN0YSwgMCBpbmRpY2EgY29uY29yZGFuY2lhIGFsZWF0b3JpYSB5IHZhbG9yZXMgbmVnYXRpdm9zIGluZGljYW4gY29uY29yZGFuY2lhIGluZmVyaW9yIGEgbGEgYWxlYXRvcmlhLg0KKiBBVUMgeSBST0MgQ3VydmU6IExhIGN1cnZhIFJPQyAoKlJlY2VpdmVyIE9wZXJhdGluZyBDaGFyYWN0ZXJpc3RpYyopIGVzIHVuYSByZXByZXNlbnRhY2nDs24gZ3LDoWZpY2EgZGUgbGEgcmVsYWNpw7NuIGVudHJlIGxhIHRhc2EgZGUgdmVyZGFkZXJvcyBwb3NpdGl2b3MgIChzZW5zaWJpbGlkYWQpIGZyZW50ZSBhIGxhIHRhc2EgZGUgZmFsc29zIHBvc2l0aXZvcyAoMSAtIGVzcGVjaWZpY2lkYWQpIGVuIHZhcmlvcyB1bWJyYWxlcyBkZSBjbGFzaWZpY2FjacOzbi4gRWwgw4FyZWEgYmFqbyBsYSBDdXJ2YSAoQVVDKSBlcyB1bmEgbcOpdHJpY2EgZGUgbGEgY2FwYWNpZGFkIGRpc2NyaW1pbmF0aXZhIGRlbCBtb2RlbG8gcXVlIHNlIGJhc2EgZW4gZWwgw6FyZWEgYmFqbyBsYSBjdXJ2YSBST0MuIFBvciBsbyB0YW50bzogVW5hIGN1cnZhIFJPQyBpZGVhbCBlc3RhcsOtYSB1YmljYWRhIGVuIGVsIHJpbmPDs24gc3VwZXJpb3IgaXpxdWllcmRvIGRlbCBncsOhZmljbywgbG8gcXVlIGluZGljYSB1bmEgYWx0YSB0YXNhIGRlIHZlcmRhZGVyb3MgcG9zaXRpdm9zIHkgdW5hIGJhamEgdGFzYSBkZSBmYWxzb3MgcG9zaXRpdm9zLiBFbiBlc3RlIGNhc28sIGVsIEFVQyBzZXLDrWEgY2VyY2FubyBhIDEsIGluZGljYW5kbyB1biBtb2RlbG8gbXV5IHByZWNpc28geSBlZmVjdGl2by4gTm8gb2JzdGFudGUsIHNpIGVsIGNsYXNpZmljYWRvciBlcyBzaW1pbGFyIGEgdW5hIGVzdGltYWNpw7NuIGFsZWF0b3JpYSwgbGEgY3VydmEgUk9DIHNlcsOtYSB1bmEgbMOtbmVhIGRpYWdvbmFsIGRlc2RlIGVsIHB1bnRvICgwLDApIGhhc3RhIGVsIHB1bnRvICgxLDEpLiBFbiBlc3RlIGNhc28sIGVsIEFVQyBzZXLDrWEgY2VyY2FubyBhIDAuNSwgbG8gcXVlIGluZGljYSB1biBtb2RlbG8gcXVlIG5vIGVzIG1lam9yIHF1ZSBlbCBhemFyIGVuIGxhIGNsYXNpZmljYWNpw7NuLg0KDQpBY29yZGUgYSBlc3RvLCBzZSBkZXRlcm1pbmEgcXVlIGN1YW50byBtYXlvciBzZWEgZWwgQVVDLCBtZWpvciBzZXLDoSBlbCByZW5kaW1pZW50byBkZWwgbW9kZWxvIGVuIHTDqXJtaW5vcyBkZSBzZXBhcmFjacOzbiBlbnRyZSBjbGFzZXMuIEVuIGNvbnNlY3VlbmNpYSwgbGEgcmVsYWNpw7NuIGVudHJlIEFVQyB5IGxhIGN1cnZhIFJPQyBlcyBxdWUgZWwgQVVDIGVzIHVuYSBtZWRpZGEgcmVzdW1pZGEgZGVsIHJlbmRpbWllbnRvIGdsb2JhbCBkZWwgbW9kZWxvLCBtaWVudHJhcyBxdWUgbGEgY3VydmEgUk9DIHByb3BvcmNpb25hIGluZm9ybWFjacOzbiBkZXRhbGxhZGEgc29icmUgc3UgcmVuZGltaWVudG8gZW4gZGlmZXJlbnRlcyB1bWJyYWxlcyBkZSBjbGFzaWZpY2FjacOzbi4NCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6IzQ1OEIwMCI+KipCYXNlIGRlIGRhdG9zKio8L3NwYW4+ICANCkxhIGJhc2UgZGUgZGF0b3MgIkJhbmsgQXBwbGljYXRpb24iIHByb3BvcmNpb25hIGluZm9ybWFjacOzbiBzb2JyZSBsb3MgY2xpZW50ZXMgeSBsYXMgY2FyYWN0ZXLDrXN0aWNhcyBkZSBsb3MgcHLDqXN0YW1vcywgbG8gcXVlIHBlcm1pdGUgcmVhbGl6YXIgYW7DoWxpc2lzIHkgbW9kZWxvcyBkZSBjbGFzaWZpY2FjacOzbiBwYXJhIGRldGVybWluYXIgbGEgcHJvYmFiaWxpZGFkIGRlIHF1ZSB1biBjbGllbnRlIHRlbmdhIGRpZmljdWx0YWRlcyBvIG5vIHBhcmEgcmVhbGl6YXIgbG9zIHBhZ29zIGRlIHVuIHByw6lzdGFtby4gQSBjb250aW51YWNpw7NuLCBzZSBtdWVzdHJhIHVuYSBkZXNjcmlwY2nDs24gZGUgbGFzIHZhcmlhYmxlczogIA0KLSBUQVJHRVQgKFZhcmlhYmxlIG9iamV0aXZvKTogMSAtIGNsaWVudGUgY29uIGRpZmljdWx0YWRlcyBkZSBwYWdvOiB0dXZvIHJldHJhc29zIGVuIGVsIHBhZ28gZGUgbcOhcyBkZSBYIGTDrWFzIGVuIGFsIG1lbm9zIHVubyBkZSBsb3MgcHJpbWVyb3MgWSBwbGF6b3MgZGVsIHByw6lzdGFtbyBlbiBudWVzdHJhIG11ZXN0cmEsIDAgLSB0b2RvcyBsb3MgZGVtw6FzIGNhc29zLiAgDQotIE5BTUVfQ09OVFJBQ1RfVFlQRTogSWRlbnRpZmljYWNpw7NuIGRlIHNpIGVsIHByw6lzdGFtbyBlcyBhbCBjb250YWRvIG8gcm90YXRvcmlvLiAgDQotIEZMQUdfT1dOX0NBUjogTWFyY2Egc2kgZWwgY2xpZW50ZSBlcyBwcm9waWV0YXJpbyBkZSB1biBjb2NoZS4gIA0KLSBGTEFHX09XTl9SRUFMVFk6IE1hcmNhIHNpIGVsIGNsaWVudGUgZXMgcHJvcGlldGFyaW8gZGUgdW5hIGNhc2EgbyB1biBwaXNvLiAgDQotIENOVF9DSElMRFJFTjogTsO6bWVybyBkZSBoaWpvcyBkZWwgY2xpZW50ZS4gIA0KLSBBTVRfSU5DT01FX1RPVEFMOiBJbmdyZXNvcyBkZWwgY2xpZW50ZS4gIA0KLSBBTVRfQ1JFRElUOiBJbXBvcnRlIGRlbCBjcsOpZGl0byBkZWwgcHLDqXN0YW1vLiAgDQotIEFNVF9BTk5VSVRZOiBBbnVhbGlkYWQgZGVsIHByw6lzdGFtby4gIA0KLSBBTVRfR09PRFNfUFJJQ0U6IFBhcmEgcHLDqXN0YW1vcyBhbCBjb25zdW1vIGVzIGVsIHByZWNpbyBkZSBsb3MgYmllbmVzIHBhcmEgbG9zIHF1ZSBzZSBjb25jZWRlIGVsIHByw6lzdGFtby4gIA0KLSBOQU1FX0lOQ09NRV9UWVBFOiBUaXBvIGRlIGluZ3Jlc29zIGRlbCBjbGllbnRlIChlbXByZXNhcmlvLCB0cmFiYWphZG9yLCBiYWphIHBvciBtYXRlcm5pZGFkLC4uLikgIA0KLSBOQU1FX0VEVUNBVElPTl9UWVBFOiBOaXZlbCBkZSBlc3R1ZGlvcyBtw6FzIGFsdG8gYWxjYW56YWRvIHBvciBlbCBjbGllbnRlLiAgDQotIE5BTUVfRkFNSUxZX1NUQVRVUzogU2l0dWFjacOzbiBmYW1pbGlhciBkZWwgY2xpZW50ZS4gIA0KLSBOQU1FX0hPVVNJTkdfVFlQRTogQ3XDoWwgZXMgbGEgc2l0dWFjacOzbiBkZSB2aXZpZW5kYSBkZWwgY2xpZW50ZSAoYWxxdWlsZXIsIHZpdmUgY29uIGxvcyBwYWRyZXMsIC4uLikgIA0KLSBSRUdJT05fUkFUSU5HX0NMSUVOVF9XX0NJVFk6IE51ZXN0cmEgdmFsb3JhY2nDs24gZGUgbGEgcmVnacOzbiBlbiBsYSBxdWUgdml2ZSBlbCBjbGllbnRlIHRlbmllbmRvIGVuIGN1ZW50YSBsYSBjaXVkYWQgKDEsMiwzKSAgDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiM0NThCMDAiPioqQW7DoWxpc2lzIEV4cGxvcmF0b3JpbyBkZSBsb3MgRGF0b3MgKEVEQSkqKjwvc3Bhbj4NCg0KSW5zdGFsYWNpw7NuIHkgbGxhbWFkbyBkZSBpYnJlcsOtYXMNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGZvcmVpZ24pICAgICAgICAjIFJlYWQgRGF0YSBTdG9yZWQgYnkgJ01pbml0YWInLCAnUycsICdTQVMnLCAnU1BTUycsICdTdGF0YScsICdTeXN0YXQnLCAnV2VrYScsICdkQmFzZScNCmxpYnJhcnkoZ2dwbG90MikgICAgICAgICMgSXQgaXMgYSBzeXN0ZW0gZm9yIGNyZWF0aW5nIGdyYXBoaWNzDQpsaWJyYXJ5KGRwbHlyKSAgICAgICAgICAjIEEgZmFzdCwgY29uc2lzdGVudCB0b29sIGZvciB3b3JraW5nIHdpdGggZGF0YSBmcmFtZSBsaWtlIG9iamVjdHMNCmxpYnJhcnkobWFwdmlldykgICAgICAgICMgUXVpY2tseSBhbmQgY29udmVuaWVudGx5IGNyZWF0ZSBpbnRlcmFjdGl2ZSB2aXN1YWxpemF0aW9ucyBvZiBzcGF0aWFsIGRhdGEgd2l0aCBvciB3aXRob3V0IGJhY2tncm91bmQgbWFwcw0KbGlicmFyeShuYW5pYXIpICAgICAgICAgIyBQcm92aWRlcyBkYXRhIHN0cnVjdHVyZXMgYW5kIGZ1bmN0aW9ucyB0aGF0IGZhY2lsaXRhdGUgdGhlIHBsb3R0aW5nIG9mIG1pc3NpbmcgdmFsdWVzIGFuZCBleGFtaW5hdGlvbiBvZiBpbXB1dGF0aW9ucy4NCmxpYnJhcnkodG1hcHRvb2xzKSAgICAgICAjIEEgY29sbGVjdGlvbiBvZiBmdW5jdGlvbnMgdG8gY3JlYXRlIHNwYXRpYWwgd2VpZ2h0cyBtYXRyaXggb2JqZWN0cyBmcm9tIHBvbHlnb24gJ2NvbnRpZ3VpdGllcycsIGZvciBzdW1tYXJpemluZyB0aGVzZSBvYmplY3RzLCBhbmQgZm9yIHBlcm1pdHRpbmcgdGhlaXIgdXNlIGluIHNwYXRpYWwgZGF0YSBhbmFseXNpcw0KbGlicmFyeSh0bWFwKSAgICAgICAgICAgIyBGb3IgZHJhd2luZyB0aGVtYXRpYyBtYXBzDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikgICAjIEl0IG9mZmVycyBzZXZlcmFsIGNvbG9yIHBhbGV0dGVzIA0KbGlicmFyeShkbG9va3IpICAgICAgICAgIyBBIGNvbGxlY3Rpb24gb2YgdG9vbHMgdGhhdCBzdXBwb3J0IGRhdGEgZGlhZ25vc2lzLCBleHBsb3JhdGlvbiwgYW5kIHRyYW5zZm9ybWF0aW9uDQpsaWJyYXJ5KGZvcmVpZ24pDQpsaWJyYXJ5KG1vZGVscikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkgDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGJyb29tKQ0KbGlicmFyeShJU0xSKSAgICAgICAgICAjIGdyZWF0IHRleHRib29rIHRvIGxlYXJuLCBleHBsb3JlLCBhbmQgcHV0IGluIHByYWN0aWNlIGRhdGEgc2NpZW5jZSBza2lsbHMuICANCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShlMTA3MSkNCmxpYnJhcnkoY2xhc3MpDQpsaWJyYXJ5KFJPQ1IpDQpsaWJyYXJ5KHBST0MpDQpsaWJyYXJ5KGxtdGVzdCkNCmxpYnJhcnkoY2FUb29scykNCmxpYnJhcnkocnBhcnQpDQpsaWJyYXJ5KHJwYXJ0LnBsb3QpDQpsaWJyYXJ5KHBzeWNoKSAgDQpsaWJyYXJ5KGdncHVicikNCmxpYnJhcnkocmVzaGFwZSkNCmxpYnJhcnkoTWV0cmljcykNCmxpYnJhcnkobWxiZW5jaCkNCmxpYnJhcnkocnNhbXBsZSkNCmxpYnJhcnkoY2x1c3RlcikgICAgICMgY2x1c3RlcmluZyBhbGdvcml0aG1zDQpsaWJyYXJ5KGZhY3RvZXh0cmEpICAjIGNsdXN0ZXJpbmcgYWxnb3JpdGhtcyAmIHZpc3VhbGl6YXRpb24NCmxpYnJhcnkoZ3JpZEV4dHJhKSAgIA0KbGlicmFyeShEYXRhRXhwbG9yZXIpDQpgYGANCg0KQ2FyZ2EgZGUgYmFzZSBkZSBkYXRvcyAiYmFua19hcHBsaWNhdGlvbi5jc3YiDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGYgPC0gcmVhZC5jc3YoIkM6XFxVc2Vyc1xcQVZSSUxcXERvY3VtZW50c1xcYmFua19hcHBsaWNhdGlvbjIuY3N2IikNCmBgYA0KDQpTZSBlbGltaW5hbiBkdXBsaWNhZG9zDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGYgPC0gdW5pcXVlKGRmKQ0KZGltKGRmKQ0KYGBgDQoNCiMjICoqTWVkaWRhcyBkZXNjcmlwdGl2YXMqKg0KU2UgZGV0ZXJtaW5hIHF1ZSBubyBoYXkgbWlzc2luZyByb3dzIHkgYWNvcmRlIGEgbG9zIGVzdGFkw61zdGljb3MgZGVzY3JpcHRpdm9zIHNlIGRldGVybWluYSBxdWU6DQoqIExhcyBwZXJzb25hcyBjb24gdW4gdGlwbyBkZSBjb250cmF0byAiV29yayIgdGllbmVuIGVsIG1heW9yIGluZ3Jlc28gdG90YWwgcHJvbWVkaW8uICANCiogTGFzIHBlcnNvbmFzIGNvbiB1biBuaXZlbCBlZHVjYXRpdm8gIkhpZ2hlciBlZHVjYXRpb24iIHRpZW5lbiBlbCBtYXlvciBpbmdyZXNvIHRvdGFsIHByb21lZGlvLg0KKiBMYXMgcGVyc29uYXMgY29uIHVuIGVzdGFkbyBjaXZpbCAiTWFycmllZCIgdGllbmVuIGVsIG1heW9yIGluZ3Jlc28gdG90YWwgcHJvbWVkaW8uDQoqIExhcyBtYXlvcsOtYSBkZSBsYXMgcGVyc29uYXMgcXVlIHZpdmUgZW4gdW5hIHJlZ2nDs24gY29uIGNhbGlmaWNhY2nDs24gZGUgIjIiLg0KDQpBIGNvbnRpbnVhY2nDs24sIHNlIGdlbmVyYSByZXN1bWVuIGRlIGNhZGEgdW5hIGRlIGxhcyB2YXJpYWJsZXMgcG9zdGVyaW9yIGEgbGEgaWRlbnRpZmljYWNpw7NuIHkgcmVlbXBsYXpvIGRlIE5hTiBjb24gMA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCiMgU2UgaWRlbnRpZmljYSBzaSBoYXkgdmFsb3JlcyBudWxvcw0KZGZbaXMubmEoZGYpXSA8LSAwDQoNCmRmbiA8LSBpcy5uYShkZikNCmRmIDwtIG5hLm9taXQoZGYpDQoNCnBsb3RfbWlzc2luZyhkZikNCmBgYA0KDQpTZSByZWVtcGxhemFuIG91dGxpZXJzIGRlIHZhcmlhYmxlcyBudW3DqXJpY2FzIHNlbGVjY2lvbmFkYXMgY29uIG1lZGlhbmENCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpkZiROQU1FX0NPTlRSQUNUX1RZUEUgPC0gYXMuZmFjdG9yKGRmJE5BTUVfQ09OVFJBQ1RfVFlQRSkNCmRmJEdFTkRFUiA8LSBhcy5mYWN0b3IoZGYkR0VOREVSKQ0KZGYkRkxBR19PV05fUkVBTFRZIDwtIGFzLmZhY3RvcihkZiRGTEFHX09XTl9SRUFMVFkpDQpkZiRGTEFHX09XTl9DQVIgPC0gYXMuZmFjdG9yKGRmJEZMQUdfT1dOX0NBUikNCmRmJE5BTUVfSU5DT01FX1RZUEUgPC0gYXMuZmFjdG9yKGRmJE5BTUVfSU5DT01FX1RZUEUpDQpkZiROQU1FX0VEVUNBVElPTl9UWVBFIDwtIGFzLmZhY3RvcihkZiROQU1FX0VEVUNBVElPTl9UWVBFKQ0KZGYkTkFNRV9GQU1JTFlfU1RBVFVTIDwtIGFzLmZhY3RvcihkZiROQU1FX0ZBTUlMWV9TVEFUVVMpDQpkZiROQU1FX0hPVVNJTkdfVFlQRSA8LSBhcy5mYWN0b3IoZGYkTkFNRV9IT1VTSU5HX1RZUEUpDQoNCiMgQ29udmVydGlyIGxvcyBmYWN0b3JlcyBhIG51bcOpcmljb3MNCmRmJEdFTkRFUiA8LSBhcy5udW1lcmljKGRmJEdFTkRFUikNCmRmJEZMQUdfT1dOX0NBUiA8LSBhcy5udW1lcmljKGRmJEZMQUdfT1dOX0NBUikNCmRmJEZMQUdfT1dOX1JFQUxUWSA8LSBhcy5udW1lcmljKGRmJEZMQUdfT1dOX1JFQUxUWSkNCmRmJE5BTUVfQ09OVFJBQ1RfVFlQRSA8LSBhcy5udW1lcmljKGRmJE5BTUVfQ09OVFJBQ1RfVFlQRSkNCmRmJE5BTUVfSU5DT01FX1RZUEUgPC0gYXMubnVtZXJpYyhkZiROQU1FX0lOQ09NRV9UWVBFKQ0KZGYkTkFNRV9FRFVDQVRJT05fVFlQRSA8LSBhcy5udW1lcmljKGRmJE5BTUVfRURVQ0FUSU9OX1RZUEUpDQpkZiROQU1FX0ZBTUlMWV9TVEFUVVMgPC0gYXMubnVtZXJpYyhkZiROQU1FX0ZBTUlMWV9TVEFUVVMpDQpkZiROQU1FX0hPVVNJTkdfVFlQRSA8LSBhcy5udW1lcmljKGRmJE5BTUVfSE9VU0lOR19UWVBFKQ0KZGYkTkFNRV9GQU1JTFlfU1RBVFVTIDwtIGFzLm51bWVyaWMoZGYkTkFNRV9GQU1JTFlfU1RBVFVTKQ0KZGYkTkFNRV9GQU1JTFlfU1RBVFVTIDwtIGFzLm51bWVyaWMoZGYkTkFNRV9GQU1JTFlfU1RBVFVTKQ0KDQoNCnJlZW1wbGF6YXJfb3V0bGllcnMgPC0gZnVuY3Rpb24oZGYsIGNvbHMpIHsNCiAgZm9yIChjb2wgaW4gY29scykgew0KICAgIHFudCA8LSBxdWFudGlsZShkZltbY29sXV0sIGMoMC4yNSwgMC43NSkpDQogICAgaXFyIDwtIHFudFsyXSAtIHFudFsxXQ0KICAgIGxvd2VyIDwtIHFudFsxXSAtIDEuNSAqIGlxcg0KICAgIHVwcGVyIDwtIHFudFsyXSArIDEuNSAqIGlxcg0KICAgIGRmW1tjb2xdXVtkZltbY29sXV0gPCBsb3dlciB8IGRmW1tjb2xdXSA+IHVwcGVyXSA8LSBtZWRpYW4oZGZbW2NvbF1dLCBuYS5ybSA9IFRSVUUpDQogIH0NCiAgcmV0dXJuKGRmKQ0KfQ0KIyBMaXN0YSBkZSBjb2x1bW5hcyBhIHJlZW1wbGF6YXIgb3V0bGllcnMNCmNvbHVtbmFzX2FfcmVlbXBsYXphciA8LSBjKCJDTlRfQ0hJTERSRU4iLCAiQU1UX0lOQ09NRV9UT1RBTCIsICJEQVlTX0VNUExPWUVEIiwgIk9XTl9DQVJfQUdFIikNCg0KIyBGdW5jacOzbiBwYXJhIHJlZW1wbGF6YXIgb3V0bGllcnMNCmRmIDwtIHJlZW1wbGF6YXJfb3V0bGllcnMoZGYsIGNvbHVtbmFzX2FfcmVlbXBsYXphcikNCiMgVmlzdWFsaXphY2nDs24gZGUgbWVkaWRhcyBkZXNjcmlwdGl2YXMgDQoNCnN1bW1hcnkoZGYpDQpgYGANCg0KIyMgKipNZWRpZGFzIGRlIGRpc3BlcnNpw7NuKioNCkFjb3JkZSBhIGxhcyBncsOhZmljYXMgc2lndWllbnRlcyBzZSBkZXRlcm1pbmEgcXVlOiAgDQotIExhcyB2YXJpYWJsZXMgQU1UX0FOTlVJVFksIEFNVF9DUkVESVQsIEFNVF9HT09EU19QUklDRSB5IEFNVF9JTkNPTUVfVE9UQUwgdGllbmVuIHVuIHJhbmdvIHNpbWlsYXIgKDQwLDAwMCkuICANCi0gTGEgdmFyaWFibGUgQU1UX0lOQ09NRV9UT1RBTCB0aWVuZSBsYSBtYXlvciBkZXN2aWFjacOzbiBlc3TDoW5kYXIgeSB2YXJpYW56YSwgbG8gcXVlIGluZGljYSBxdWUgaGF5IHVuYSBtYXlvciBkaXNwZXJzacOzbiBlbiBsb3MgdmFsb3JlcyBkZSBlc3RhIHZhcmlhYmxlLiAgDQotIExhIHZhcmlhYmxlIENOVF9DSElMRFJFTiB0aWVuZSBsYSBtZW5vciBkZXN2aWFjacOzbiBlc3TDoW5kYXIgeSB2YXJpYW56YSwgbG8gcXVlIGluZGljYSBxdWUgaGF5IHVuYSBtZW5vciBkaXNwZXJzacOzbiBlbiBsb3MgdmFsb3JlcyBkZSBlc3RhIHZhcmlhYmxlLiAgDQoNCkVuIGVzdGUgY2h1bmsgc2UgZ2VuZXJhIGJveHBsb3QgZGUgY2FkYSB1bmEgZGUgbGFzIHZhcmlhYmVzIG51bcOpcmljYXMgZGVsIGRhdGFmcmFtZSBwYXJhIGlkZW50aWZpY2FyIG91dGxpZXJzDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KY29sdW1uYXNfbnVtZXJpY2FzIDwtIG5hbWVzKGRmKVtzYXBwbHkoZGYsIGlzLm51bWVyaWMpXQ0KDQpmb3IgKGNvbCBpbiBjb2x1bW5hc19udW1lcmljYXMpIHsNCiAgYm94cGxvdChkZltbY29sXV0sIG1haW49Y29sKQ0KfQ0KYGBgDQoNCiMjICoqSWRlbnRpZmljYWNpw7NuIGRlIHBhdHJvbmVzIHkvbyB0ZW5kZW5jaWFzIGVuIGxvcyBkYXRvcyBtZWRpYW50ZSBlbCB1c28gZGUgZ3LDoWZpY29zKiogIA0KTG9zIGdyw6FmaWNvcyBwb3N0ZXJpb3JlcyBtdWVzdHJhbiBsYXMgc2lndWllbnRlcyB0ZW5kZW5jaWFzOiAgDQoqIExhIG1heW9yw61hIGRlIGxhcyBjb3JyZWxhY2lvbmVzIHNvbiBwb3NpdGl2YXMsIGxvIHF1ZSBpbmRpY2EgcXVlIGxhcyB2YXJpYWJsZXMgdGllbmRlbiBhIGF1bWVudGFyIG8gZGlzbWludWlyIGp1bnRhcy4NCiogTGFzIHZhcmlhYmxlcyByZWxhY2lvbmFkYXMgY29uIGVsIGluZ3Jlc28gKENOVF9DSElMRFJFTiwgQU1UX0lOQ09NRV9UT1RBTCkgeSBlbCBjcsOpZGl0byAoQU1UX0NSRURJVCwgQU1UX0FOTlVJVFkpIHRpZW5lbiBsYXMgY29ycmVsYWNpb25lcyBtw6FzIGZ1ZXJ0ZXMuDQoqIExhcyB2YXJpYWJsZXMgcmVsYWNpb25hZGFzIGNvbiBsYSB1YmljYWNpw7NuIChSRUdJT05fUkFUSU5HX0NMSUVOVCkgdGFtYmnDqW4gdGllbmVuIGNvcnJlbGFjaW9uZXMgbW9kZXJhZGFzLg0KDQpHcsOhZmljb3MgZGUgcGFyYSB2aXN1YWxpemFyIGRpc3RyaWJ1Y2nDs24gZGUgdmFyaWFibGVzDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KIyBEZXNjcmlwY2nDs24gaW50cm9kdWN0b3JpYSBvIHJlc3VtZW4gZGUgZGYNCmludHJvZHVjZShkZikNCnBsb3RfaW50cm8oZGYpDQoNCiMgSGlzdG9ncmFtYSBwYXJhIGNhZGEgdmFyaWFibGUgbnVtw6lyaWNhIGRlbCBkYXRhZnJhbWUgZGYNCnBsb3RfaGlzdG9ncmFtKGRmKQ0KDQojIEdyw6FmaWNvIGRlIGJhcnJhcyBwYXJhIGNhZGEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgDQpwbG90X2JhcihkZikNCmBgYA0KDQpWaXN1YWxpemFjacOzbiBkZSBkaXN0cmlidWNpw7NuIG5vcm1hbCAgDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcGxvdF9ub3JtYWxpdHkoZGYpDQpgYGANCg0KKipDb3JyZWxhY2nDs24gZW50cmUgdmFyaWFibGVzKiogIA0KU2UgdmlzdWFsaXphIHF1ZSBoYXkgdW5hIGFzb2NpYWNpw7NuIHBvc2l0aXZhIGVudHJlIGV4cGVuc2VzIHkgc21va2VyDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGV2Lm5ldyh3aWR0aCA9IDEwLCBoZWlnaHQgPSA4KQ0KY29ycmVsYXRlKGRmKSAlPiUgIHBsb3QoKQ0KcGxvdF9jb3JyZWxhdGlvbihkZikNCmBgYA0KDQojIyAqKlRyYW5zZm9ybWFjacOzbiBkZSB2YXJpYWJsZXMgZGUgaW50ZXLDqXMqKiAgDQpBY29yZGUgYSBsb3MgZ3LDoWZpY29zIGRlIGRpc3RyaWJ1Y2nDs24gbm9ybWFsLCBzZSBpZGVudGlmaWNhcm9uIGxhcyB2YXJpYWJsZXMgcXVlIHRlbmRyw61hbiBtZWpvciBub3JtYWxpZGFkIGFsIGNvbnZlcnRpcmxhcyBhIGxvZ2FyaXRtbyBwYXJhIGdlbmVyYXIgZWwgZGF0YWZyYW1lLiANCmBgYHtyfQ0KZGYkbG9nX0FNVF9DUkVESVQgPC0gbG9nKGRmJEFNVF9DUkVESVQpDQpkZiRsb2dfQU1UX0FOTlVJVFkgPC0gbG9nKGRmJEFNVF9BTk5VSVRZKzAuMDEpDQpkZiRsb2dfQU1UX0dPT0RTX1BSSUNFIDwtIGxvZyhkZiRBTVRfR09PRFNfUFJJQ0UrMC4wMSkNCg0KIyBHZW5lcmEgZWwgbnVldm8gZGF0YWZyYW1lIGRmMSBjb24gbGFzIHRyYW5zZm9ybWFjaW9uZXMNCmRmMSA8LSBkYXRhLmZyYW1lKA0KICBUQVJHRVQgPSBkZiRUQVJHRVQsDQogIGxvZ19BTVRfQ1JFRElUID0gZGYkbG9nX0FNVF9DUkVESVQsDQogIGxvZ19BTVRfQU5OVUlUWSA9IGRmJGxvZ19BTVRfQU5OVUlUWSwNCiAgbG9nX0FNVF9HT09EU19QUklDRSA9IGRmJGxvZ19BTVRfR09PRFNfUFJJQ0UsDQogIE5BTUVfQ09OVFJBQ1RfVFlQRSA9IGRmJE5BTUVfQ09OVFJBQ1RfVFlQRSwNCiAgR0VOREVSID0gZGYkR0VOREVSLA0KICBGTEFHX09XTl9DQVIgPSBkZiRGTEFHX09XTl9DQVIsDQogIEZMQUdfT1dOX1JFQUxUWSA9IGRmJEZMQUdfT1dOX1JFQUxUWSwNCiAgQ05UX0NISUxEUkVOID0gZGYkQ05UX0NISUxEUkVOLA0KICBBTVRfSU5DT01FX1RPVEFMID0gZGYkQU1UX0lOQ09NRV9UT1RBTCwNCiAgTkFNRV9JTkNPTUVfVFlQRSA9IGRmJE5BTUVfSU5DT01FX1RZUEUsDQogIE5BTUVfRURVQ0FUSU9OX1RZUEUgPSBkZiROQU1FX0VEVUNBVElPTl9UWVBFLA0KICBOQU1FX0ZBTUlMWV9TVEFUVVMgPSBkZiROQU1FX0ZBTUlMWV9TVEFUVVMsDQogIE5BTUVfSE9VU0lOR19UWVBFID0gZGYkTkFNRV9IT1VTSU5HX1RZUEUsDQogIFJFR0lPTl9SQVRJTkdfQ0xJRU5UX1dfQ0lUWSA9IGRmJFJFR0lPTl9SQVRJTkdfQ0xJRU5UX1dfQ0lUWQ0KKQ0KDQpzdW1tYXJ5KGRmMSkNCmBgYA0KDQojIDxzcGFuIHN0eWxlPSJjb2xvcjojNDU4QjAwIj4qKkVzcGVjaWZpY2FjacOzbiBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIGEgZXN0aW1hcioqPC9zcGFuPg0KDQpTZSBpbmZpZXJlIHF1ZSBlbCBpbXBhY3RvIGRlIGNhZGEgdW5hIGRlIGxhcyB2YXJpYWJsZXMgZXhwbGljYXRpdmFzIHNvYnJlIGxhIHByaW5jaXBhbCB2YXJpYWJsZSBkZSBlc3R1ZGlvLCBlbiBlc3RlIGNhc28gIlRBUkdFVCIsIHNlcsOtYSBkZSBlc3RhIG1hbmVyYTogIA0KLSBBTVQgQU5OVUlUWTogU2UgZXNwZXJhIHF1ZSB1biBhdW1lbnRvIGVuIGxhIGNhbnRpZGFkIGRlIGxhIGFudWFsaWRhZCB0ZW5nYSB1biBpbXBhY3RvIHBvc2l0aXZvIGVuIGxhIHByb2JhYmlsaWRhZCBkZSBxdWUgdW4gY2xpZW50ZSBzZWEgYnVlbm8uICANCi0gQU1UIENSRURJVDogU2UgZXNwZXJhIHF1ZSB1biBhdW1lbnRvIGVuIGxhIGNhbnRpZGFkIGRlbCBjcsOpZGl0byB0ZW5nYSB1biBpbXBhY3RvIG5lZ2F0aXZvIGVuIGxhIHByb2JhYmlsaWRhZCBkZSBxdWUgdW4gY2xpZW50ZSBzZWEgYnVlbm8uICANCi0gQU1UIEdPT0RTIFBSSUNFOiBTZSBlc3BlcmEgcXVlIHVuIGF1bWVudG8gZW4gbGEgY2FudGlkYWQgZGVsIHByZWNpbyBkZSBsb3MgYmllbmVzIHRlbmdhIHVuIGltcGFjdG8gbmVnYXRpdm8gZW4gbGEgcHJvYmFiaWxpZGFkIGRlIHF1ZSB1biBjbGllbnRlIHNlYSBidWVuby4gIA0KLSBBTVQgSU5DT01FIFRPVEFMOiBTZSBlc3BlcmEgcXVlIHVuIGF1bWVudG8gZW4gbGEgY2FudGlkYWQgZGVsIGluZ3Jlc28gdG90YWwgdGVuZ2EgdW4gaW1wYWN0byBwb3NpdGl2byBlbiBsYSBwcm9iYWJpbGlkYWQgZGUgcXVlIHVuIGNsaWVudGUgc2VhIGJ1ZW5vLiAgDQotIENOVCBDSElMRFJFTjogU2UgZXNwZXJhIHF1ZSB1biBhdW1lbnRvIGVuIGVsIG7Dum1lcm8gZGUgaGlqb3MgdGVuZ2EgdW4gaW1wYWN0byBuZWdhdGl2byBlbiBsYSBwcm9iYWJpbGlkYWQgZGUgcXVlIHVuIGNsaWVudGUgc2VhIGJ1ZW5vLiAgDQotIFJFR0lPTl9SQVRJTkdfQ0xJRU5UX1dfQ0lUWTogU2UgZXNwZXJhIHF1ZSB1biBhdW1lbnRvIGVuIGxhIGNhbGlmaWNhY2nDs24gZGVsIGNsaWVudGUgY29uIGNpdWRhZCB0ZW5nYSB1biBpbXBhY3RvIHBvc2l0aXZvIGVuIGxhIHByb2JhYmlsaWRhZCBkZSBxdWUgdW4gY2xpZW50ZSBzZWEgYnVlbm8uICANCg0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDMsIDIpKSAgIyBEaXZpZGUgZWwgw6FyZWEgZGUgbGEgdHJhbWEgZW4gdW5hIGN1YWRyw61jdWxhIGRlIDMgZmlsYXMgeSAyIGNvbHVtbmFzDQoNCiMgQU1UX0NSRURJVA0KYm94cGxvdChUQVJHRVQgfiBBTVRfQ1JFRElULCBkYXRhID0gZGYsIG1haW4gPSAiVEFSR0VUIHZzLiBBTVRfQ1JFRElUIiwgeGxhYiA9ICJBTVRfQ1JFRElUIiwgeWxhYiA9ICJUQVJHRVQiKQ0KDQojIEFNVF9BTk5VSVRZDQpib3hwbG90KFRBUkdFVCB+IEFNVF9BTk5VSVRZLCBkYXRhID0gZGYsIG1haW4gPSAiVEFSR0VUIHZzLiBBTVRfQU5OVUlUWSIsIHhsYWIgPSAiQU1UX0FOTlVJVFkiLCB5bGFiID0gIlRBUkdFVCIpDQoNCiMgQU1UX0dPT0RTX1BSSUNFDQpib3hwbG90KFRBUkdFVCB+IEFNVF9HT09EU19QUklDRSwgZGF0YSA9IGRmLCBtYWluID0gIlRBUkdFVCB2cy4gQU1UX0dPT0RTX1BSSUNFIiwgeGxhYiA9ICJBTVRfR09PRFNfUFJJQ0UiLCB5bGFiID0gIlRBUkdFVCIpDQpgYGANCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6IzQ1OEIwMCI+KipFc3RpbWFjacOzbiBkZSBtb2RlbG9zIGRlIFN1cGVydmlzZWQgTWFjaGluZSBMZWFybmluZyAoU01MKSoqPC9zcGFuPg0KDQojIyAqKlBhcnRpY2nDs24gZGUgYmQqKg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpICANCnBhcnRpdGlvbiA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKHkgPSBkZjEkVEFSR0VULCBwPTAuNywgbGlzdD1GKQ0KdHJhaW4gPSBkZjFbcGFydGl0aW9uLCBdDQp0ZXN0ICA9IGRmMVstcGFydGl0aW9uLCBdDQpgYGANCg0KIyMgKipTaW1wbGUgTG9naXN0aWMgTW9kZWwqKg0KYGBge3J9DQpzaW1wbGVfbG9naXN0aWMgPC0gZ2xtKFRBUkdFVCB+IGxvZ19BTVRfQ1JFRElULCBmYW1pbHkgPSAiYmlub21pYWwiLCBkYXRhID0gdHJhaW4pDQpzdW1tYXJ5KHNpbXBsZV9sb2dpc3RpYykgDQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmV4cChjb2VmKHNpbXBsZV9sb2dpc3RpYykpDQpjb25maW50KHNpbXBsZV9sb2dpc3RpYykgICAjIyMgdGhlIGVzdGltYXRlZCBjb2VmZmljaWVudHMgYmFzZWQgb24gdGhlIGNvbmZpZGVuY2UgaW50ZXJ2YWwgDQp2YXJJbXAoc2ltcGxlX2xvZ2lzdGljKQ0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0ZXN0X3NpbXBsZV9sb2dpc3RpYyA8LSBwcmVkaWN0KHNpbXBsZV9sb2dpc3RpYywgbmV3ZGF0YSA9IHRlc3QsIHR5cGUgPSAicmVzcG9uc2UiKQ0KcHJlZGljdGVkX2NsYXNzZXMgPC0gaWZlbHNlKHRlc3Rfc2ltcGxlX2xvZ2lzdGljID4gMC41LCAxLCAwKQ0KY29uZnVzaW9uX21hdHJpeCA8LSBjb25mdXNpb25NYXRyaXgoZmFjdG9yKHByZWRpY3RlZF9jbGFzc2VzKSwgZmFjdG9yKHRlc3QkVEFSR0VUKSkNCmNvbmZ1c2lvbl9tYXRyaXgNCg0KcGFyKG1mcm93PWMoMSwgMikpDQoNCnByZWRpY3Rpb24odGVzdF9zaW1wbGVfbG9naXN0aWMsIHRlc3QkVEFSR0VUKSAlPiUNCiAgcGVyZm9ybWFuY2UobWVhc3VyZSA9ICJ0cHIiLCB4Lm1lYXN1cmUgPSAiZnByIikgJT4lDQogIHBsb3QoKQ0KDQojIEFVQyAtIFNpbXBsZSBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIA0KYXVjX3NpbXBsZSA8LSBwcmVkaWN0aW9uKHRlc3Rfc2ltcGxlX2xvZ2lzdGljLCB0ZXN0JFRBUkdFVCkgJT4lIHBlcmZvcm1hbmNlKG1lYXN1cmUgPSAiYXVjIikgJT4lIC5AeS52YWx1ZXMNCiNLQVBQDQprYXBwYV9zaW1wbGUgPC0gY29uZnVzaW9uX21hdHJpeCRvdmVyYWxsWyJLYXBwYSJdDQprYXBwYV9zaW1wbGUNCmBgYA0KDQojIyMgKipTaW1wbGUgTG9naXN0aWMgTW9kZWwgQWp1c3RlZCoqICANClNlIHJlYWxpemEgdW4gU2ltcGxlIExvZ2lzdGljIE1vZGVsIEFqdXN0YWRvIGNvbiBlbCBmaW4gZGUgZXZpdGFyIHF1ZSBlbCBkZXNiYWxhbmNlbyBkZSBjbGFzZXMgYWZlY3RhIGxhcyBtw6l0cmljYXMgZGUgY2xhc2lmaWNhY2nDs24gZGVsIG1vZGVsby4gIA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KY2xhc3Nfd2VpZ2h0cyA8LSBpZmVsc2UodHJhaW4kVEFSR0VUID09IDAsIG1lYW4odHJhaW4kVEFSR0VUID09IDEpLCBtZWFuKHRyYWluJFRBUkdFVCA9PSAwKSkNCnNpbXBsZV9sb2dpc3RpY193c3MgPC0gZ2xtKFRBUkdFVCB+IGxvZ19BTVRfQ1JFRElULCBmYW1pbHkgPSAiYmlub21pYWwiLCBkYXRhID0gdHJhaW4sIHdlaWdodHMgPSBjbGFzc193ZWlnaHRzKQ0KDQpwcmVkaWN0aW9uc19zaW1wbGVfd3NzIDwtIHByZWRpY3Qoc2ltcGxlX2xvZ2lzdGljX3dzcywgbmV3ZGF0YSA9IHRlc3QsIHR5cGUgPSAicmVzcG9uc2UiKQ0KDQpwcmVkaWN0ZWRfc2ltcGxlX3dzcyA8LSBpZmVsc2UocHJlZGljdGlvbnNfc2ltcGxlX3dzcyA+IDAuNSwgMSwgMCkNCg0KY29uZnVzaW9uX3NpbXBsZV93c3MgPC0gY29uZnVzaW9uTWF0cml4KGZhY3RvcihwcmVkaWN0ZWRfc2ltcGxlX3dzcyksIGZhY3Rvcih0ZXN0JFRBUkdFVCkpDQpjb25mdXNpb25fc2ltcGxlX3dzcw0KDQojIEFVQyAtIFNpbXBsZSBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIA0KYXVjX3NpbXBsZV93c3MgPC0gcHJlZGljdGlvbihwcmVkaWN0aW9uc19zaW1wbGVfd3NzLCB0ZXN0JFRBUkdFVCkgJT4lIHBlcmZvcm1hbmNlKG1lYXN1cmUgPSAiYXVjIikgJT4lIC5AeS52YWx1ZXMNCiNLQVBQDQprYXBwYV9zaW1wbGVfd3NzIDwtIGNvbmZ1c2lvbl9zaW1wbGVfd3NzJG92ZXJhbGxbIkthcHBhIl0NCmBgYA0KDQoNCiMjICoqTXVsdGlwbGUgTG9naXN0aWMgTW9kZWwqKg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCm11bHRpcGxlX2xvZ2lzdGljIDwtIGdsbShUQVJHRVQgfiAuLCBmYW1pbHkgPSAiYmlub21pYWwiLCBkYXRhID0gdHJhaW4pDQpzdW1tYXJ5KG11bHRpcGxlX2xvZ2lzdGljKQ0KdmFySW1wKG11bHRpcGxlX2xvZ2lzdGljKQ0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpwbG90KG11bHRpcGxlX2xvZ2lzdGljLCB3aGljaCA9IDQsIGlkLm4gPSA1KQ0KbXVsdGlwbGVfbG9naXN0aWNfZGF0YSA8LSBhdWdtZW50KG11bHRpcGxlX2xvZ2lzdGljKSAlPiUgIG11dGF0ZShpbmRleCA9IDE6bigpKQ0KbXVsdGlwbGVfbG9naXN0aWNfZGF0YSAlPiUgdG9wX24oNSwgLmNvb2tzZCkNCg0KbGlicmFyeShjYXJldCkNCg0KIyBQcmVkaWNlIGxhcyBjbGFzZXMgZW4gZWwgY29uanVudG8gZGUgcHJ1ZWJhDQpwcmVkaWN0aW9uc19tdWx0aXBsZSA8LSBwcmVkaWN0KG11bHRpcGxlX2xvZ2lzdGljLCBuZXdkYXRhID0gdGVzdCwgdHlwZSA9ICJyZXNwb25zZSIpDQoNCnByZWRpY3RlZF9jbGFzc2VzIDwtIGlmZWxzZShwcmVkaWN0aW9uc19tdWx0aXBsZSA+IDAuNSwgMSwgMCkNCg0KY29uZnVzaW9uX211bHRpcGxlIDwtIGNvbmZ1c2lvbk1hdHJpeChmYWN0b3IocHJlZGljdGVkX2NsYXNzZXMpLCBmYWN0b3IodGVzdCRUQVJHRVQpKQ0KY29uZnVzaW9uX211bHRpcGxlDQoNCnBhcihtZnJvdz1jKDEsIDIpKQ0KDQpwcmVkaWN0aW9uKHByZWRpY3Rpb25zX211bHRpcGxlLCB0ZXN0JFRBUkdFVCkgJT4lDQogIHBlcmZvcm1hbmNlKG1lYXN1cmUgPSAidHByIiwgeC5tZWFzdXJlID0gImZwciIpICU+JQ0KICBwbG90KCkNCg0KIyBBVUMgIA0KYXVjX211bHRpcGxlIDwtIHByZWRpY3Rpb24ocHJlZGljdGlvbnNfbXVsdGlwbGUsIHRlc3QkVEFSR0VUKSAlPiUgcGVyZm9ybWFuY2UobWVhc3VyZSA9ICJhdWMiKSAlPiUgLkB5LnZhbHVlcw0Ka2FwcGFfbXVsdGlwbGUgPC0gY29uZnVzaW9uX211bHRpcGxlJG92ZXJhbGxbIkthcHBhIl0NCmthcHBhX211bHRpcGxlDQpgYGANCg0KIyMjICoqTXVsdGlwbGUgTG9naXN0aWMgTW9kZWwgQWp1c3RlZCoqDQpTZSByZWFsaXphIHVuIE11bHRpcGxlIExvZ2lzdGljIE1vZGVsIEFqdXN0YWRvIGNvbiBlbCBmaW4gZGUgZXZpdGFyIHF1ZSBlbCBkZXNiYWxhbmNlbyBkZSBjbGFzZXMgYWZlY3RhIGxhcyBtw6l0cmljYXMgZGUgY2xhc2lmaWNhY2nDs24gZGVsIG1vZGVsby4gIA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmNsYXNzX3dlaWdodHNfbXVsdGlwbGUgPC0gaWZlbHNlKHRyYWluJFRBUkdFVCA9PSAwLCBtZWFuKHRyYWluJFRBUkdFVCA9PSAxKSwgbWVhbih0cmFpbiRUQVJHRVQgPT0gMCkpDQoNCiMgRW50cmVuYXIgZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbG9nw61zdGljYSBjb24gYWp1c3RlIGRlIHBlc29zIGRlIGNsYXNlDQptdWx0aXBsZV9sb2dpc3RpY193ZWlnaHRlZCA8LSBnbG0oVEFSR0VUIH4gLiwgZmFtaWx5ID0gImJpbm9taWFsIiwgZGF0YSA9IHRyYWluLCB3ZWlnaHRzID0gY2xhc3Nfd2VpZ2h0c19tdWx0aXBsZSkNCg0KcHJlZGljdGlvbnNfd3NzIDwtIHByZWRpY3QobXVsdGlwbGVfbG9naXN0aWNfd2VpZ2h0ZWQsIG5ld2RhdGEgPSB0ZXN0LCB0eXBlID0gInJlc3BvbnNlIikNCg0KcHJlZGljdGVkX3dzcyA8LSBpZmVsc2UocHJlZGljdGlvbnNfd3NzID4gMC41LCAxLCAwKQ0KDQpjb25mdXNpb25fbXVsdGlwbGVfd3NzIDwtIGNvbmZ1c2lvbk1hdHJpeChmYWN0b3IocHJlZGljdGVkX3dzcyksIGZhY3Rvcih0ZXN0JFRBUkdFVCkpDQpjb25mdXNpb25fbXVsdGlwbGVfd3NzDQoNCmF1Y19tdWx0aXBsZV93c3MgPC0gcHJlZGljdGlvbihwcmVkaWN0aW9uc193c3MsIHRlc3QkVEFSR0VUKSAlPiUgcGVyZm9ybWFuY2UobWVhc3VyZSA9ICJhdWMiKSAlPiUgLkB5LnZhbHVlcw0Ka2FwcF9tdWx0aXBsZV93c3MgPC0gY29uZnVzaW9uX211bHRpcGxlX3dzcyRvdmVyYWxsWyJLYXBwYSJdDQpgYGANCg0KIyMgKipEZWNpc2lvbiBUcmVlcyoqDQpBIGNhdXNhIGRlbCBkZXNiYWxhbmNlbyBkZSBjbGFzZXMsIGVsIGRlY2lzaW9uIHRyZWUgaW1wbGljYSBjaWVydG8gc2VzZ28gZGUgY2xhc2lmaWNhY2nDs24gbG8gcXVlIHNlIGRlbm90YSBlbiBsYSBtYXRyaXogZGUgY29uZnVzacOzbiwgeWEgcXVlIGVsIG1vZGVsbyBubyBlc3TDoSBwcmVkaWNpZW5kbyBsYSBjbGFzZSBwb3NpdGl2YSBlbiBhYnNvbHV0byB5LCBlbiBjYW1iaW8sIGVzdMOhIHByZWRpY2llbmRvIGxhIG1heW9yw61hIGRlIGxhcyBpbnN0YW5jaWFzIGNvbW8gbmVnYXRpdmFzLg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc2V0LnNlZWQoMTIzKQ0KZHQucnBhcnQgPC0gcnBhcnQoKGZhY3RvcihUQVJHRVQpKSB+IC4sZGF0YSA9IHRyYWluLCBtZXRob2QgPSAiY2xhc3MiLCBjb250cm9sID0gcnBhcnQuY29udHJvbChjcD0wLjAwNSkpDQpkdC5ycGFydA0KcHJwKGR0LnJwYXJ0LCBleHRyYSA9IDIpICMgZXh0cmEgYXJndW1lbnQgaW5jbHVkZXMgdGhlIHByb3BvcnRpb24gb2YgY29ycmVjdCBwcmVkaWN0aW9ucyAoKikNCg0KIyMjIFRFU1RJTkcgUEVSRk9STUFOQ0UgDQp0ZXN0X3BwcmVkaWN0X3RyZWUgPC0gcHJlZGljdChvYmplY3QgPSBkdC5ycGFydCwgbmV3ZGF0YSA9IHRlc3QsIHR5cGUgPSAiY2xhc3MiKQ0KDQojIHRlc3QgY29uZnVzaW9uIG1hdHJpeA0KY29uZnVzaW9uX3RyZWUgPC0gY29uZnVzaW9uTWF0cml4KGZhY3Rvcih0ZXN0X3BwcmVkaWN0X3RyZWUpLCBmYWN0b3IodGVzdCRUQVJHRVQpKQ0KY29uZnVzaW9uX3RyZWUNCmthcHBhX3RyZWUgPC0gY29uZnVzaW9uX3RyZWUkb3ZlcmFsbFsiS2FwcGEiXQ0Ka2FwcGFfdHJlZQ0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0ZXN0X3Bwcm9iX3RyZWUgPC0gcHJlZGljdChvYmplY3QgPSBkdC5ycGFydCwgbmV3ZGF0YSA9IHRlc3QsIHR5cGUgPSAicHJvYiIpDQoNCnJvY19vYmpfdHJlZSA8LSByb2ModGVzdCRUQVJHRVQsIHRlc3RfcHByb2JfdHJlZVssIDFdKSAgIyBBc3N1bWluZyBmaXJzdCBjb2x1bW4gaXMgcG9zaXRpdmUgY2xhc3MgcHJvYmFiaWxpdHkNCg0KIyBQbG90dGluZyBST0MgY3VydmUNCnBsb3Qucm9jKHJvY19vYmpfdHJlZSwgbWFpbiA9ICJDdXJ2YSBST0MiLCBjb2wgPSAiYmx1ZSIpDQoNCiMgQVVDIC0gU2ltcGxlIExvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwgDQphdWNfdHJlZSA8LSByb2ModGVzdCRUQVJHRVQsIHRlc3RfcHByb2JfdHJlZVssIDFdKSRhdWMNCmF1Y190cmVlDQpgYGANCg0KDQojIyAqKlJhbmRvbSBGb3Jlc3QqKg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KDQp0cmFpbiRUQVJHRVQgPC0gYXMuZmFjdG9yKHRyYWluJFRBUkdFVCkNCg0Kc2V0LnNlZWQoMTIzKQ0KcmFuZG9tX2ZvcmVzdCA8LSByYW5kb21Gb3Jlc3QoVEFSR0VUIH4gbG9nX0FNVF9DUkVESVQgKyBsb2dfQU1UX0FOTlVJVFkgKyBsb2dfQU1UX0dPT0RTX1BSSUNFICsgTkFNRV9DT05UUkFDVF9UWVBFICsgR0VOREVSICsgRkxBR19PV05fQ0FSICsgRkxBR19PV05fUkVBTFRZICsgQ05UX0NISUxEUkVOICsgQU1UX0lOQ09NRV9UT1RBTCArIE5BTUVfSU5DT01FX1RZUEUgKyBOQU1FX0VEVUNBVElPTl9UWVBFICsgTkFNRV9GQU1JTFlfU1RBVFVTICsgTkFNRV9IT1VTSU5HX1RZUEUgKyBSRUdJT05fUkFUSU5HX0NMSUVOVF9XX0NJVFksIGRhdGE9dHJhaW4sIG50cmVlPTUwMCwgbXRyeT00KQ0KcHJpbnQocmFuZG9tX2ZvcmVzdCkgDQp2YXJJbXBQbG90KHJhbmRvbV9mb3Jlc3QsIG4udmFyID0gMTAsIG1haW4gPSAiVG9wIDEwIC0gVmFyaWFibGUiKQ0KaW1wb3J0YW5jZShyYW5kb21fZm9yZXN0KSAgDQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCnByZWRpY3Rpb25zX3JmIDwtIHByZWRpY3QocmFuZG9tX2ZvcmVzdCwgbmV3ZGF0YSA9IHRlc3QpDQoNCnByZWRpY3Rpb25zX3JmIDwtIGFzLmZhY3RvcihwcmVkaWN0aW9uc19yZikNCnRlc3QkVEFSR0VUIDwtIGFzLmZhY3Rvcih0ZXN0JFRBUkdFVCkNCg0KIyBDcmVhciBsYSBtYXRyaXogZGUgY29uZnVzacOzbg0KY29uZnVzaW9uX3JmIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0aW9uc19yZiwgdGVzdCRUQVJHRVQpDQpwcmludChjb25mdXNpb25fcmYpDQoNCnByZWRpY2Npb25fcHJvYl9yZiA8LSBwcmVkaWN0KHJhbmRvbV9mb3Jlc3QsIHRlc3QsIHR5cGUgPSAicHJvYiIpDQpwcmVkaWNjaW9uX3Byb2JfdHJ1ZV9yZiA8LSBwcmVkaWNjaW9uX3Byb2JfcmZbLCAiMSJdDQoNCiMjIEdlbmVyYXIgbGEgY3VydmEgUk9DDQpyb2Nfb2JqX3JmIDwtIHJvYyh0ZXN0JFRBUkdFVCwgcHJlZGljY2lvbl9wcm9iX3RydWVfcmYpDQoNCiMjIERpYnVqYXIgbGEgY3VydmEgUk9DDQpwbG90LnJvYyhyb2Nfb2JqX3JmLCBtYWluPSJDdXJ2YSBST0MiLCBjb2w9ImJsdWUiKQ0KDQojIyBDYWxjdWxhciBlbCBBVUMNCmxpYnJhcnkocFJPQykNCiMgQVVDIC0gU2ltcGxlIExvZ2lzdGljIFJlZ3Jlc3Npb24gTW9kZWwgDQphdWNfcmYgPC0gcm9jKHRlc3QkVEFSR0VULCBwcmVkaWNjaW9uX3Byb2JfcmZbLCAxXSkkYXVjDQphdWNfcmYNCmBgYA0KDQojIyAqKlNWTSoqDQoNClNlIHNlbGVjY2lvbmEgdW5hIHNvbGEgdmFyaWFibGUgZXhwbGljYXRpdmEgYSBjYXVzYSBkZSBsYSBkaW1uZXNpb25hbGlkYWQgZGUgbGEgYmQgeSBwZXJtaXRlIGNhcHRhciB1bmEgcGFydGUgc2lnbmlmaWNhdGl2YSBkZWwgcG9kZXIgcHJlZGljdGl2byBkZWwgY29uanVudG8gZGUgZGF0b3MgYSBjYXVzYSBkZSBsYSBpbmZsdWVuY2lhL2ltcG9ydGFuY2lhIGRlIGxhIHZhcmlhYmxlLCBsbyBxdWUgbGEgY29udmVydGlyw61hIGVuIHVuYSBidWVuYSBjYW5kaWRhdGEgcGFyYSB1biBtb2RlbG8gZGUgdW5hIHNvbGEgdmFyaWFibGUuDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc3ZtX21vZGVsIDwtIHN2bShUQVJHRVQgfiBsb2dfQU1UX0NSRURJVCwgZGF0YSA9IHRyYWluLHR5cGUgPSAiQy1jbGFzc2lmaWNhdGlvbiIsIGtlcm5lbCA9ICJsaW5lYXIiLHNjYWxlID0gRkFMU0UpDQpzdm1fbW9kZWwNCmBgYA0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KcHJlZGljdF9zdm0gPC0gcHJlZGljdChzdm1fbW9kZWwsIG5ld2RhdGEgPSB0ZXN0KQ0KY29uZnVzaW9uX3N2bSA8LSBjb25mdXNpb25NYXRyaXgocHJlZGljdF9zdm0sIHRlc3QkVEFSR0VUKSANCmthcHBhX3N2bSA8LSBjb25mdXNpb25fc3ZtJG92ZXJhbGxbIkthcHBhIl0NCmthcHBhX3N2bQ0KIyB2YWxpZGF0ZSBvdXIgbW9kZWwNCnByZWRpY3Rfc3ZtIDwtIHByZWRpY3Qoc3ZtX21vZGVsLCBuZXdkYXRhID0gdGVzdCkNCmNvbmZ1c2lvbl9zdm0gPC0gY29uZnVzaW9uTWF0cml4KHByZWRpY3Rfc3ZtLCB0ZXN0JFRBUkdFVCkgDQpjb25mdXNpb25fc3ZtDQpgYGANCg0KIyMgKipLLU1FQU5TKioNCkNhYmUgZGVzdGFjYXIgcXVlLCBubyBzZSBvYnR1dmllcm9uIGxhcyBtw6l0cmljYXMgZGUgQVVDLCBST0MgeSBNYXRyaXogZGUgY29uZnVzacOzbiBkZWJpZG8gYSBsYSBuYXR1cmFsZXphIGRlbCBtb2RlbC4gTm8gb2JzdGFudGUsIHNlIGdlbmVyYSBsYSBncsOhZmljYSBkZWwgY29kbyBwYXJhIGRldGVybWluYXIgZWwgbsO6bWVybyBkZSBjbHVzdGVycyBhZGVjdWFkb3MuDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0Kc2V0LnNlZWQoMTIzKQ0KDQojIERlZmluZSBhIGZ1bmN0aW9uIHRvIGNvbXB1dGUgd2l0aGluLWNsdXN0ZXIgc3VtIG9mIHNxdWFyZXMgKFdDU1MpDQp3c3MgPC0gZnVuY3Rpb24oaykgew0KICBrbWVhbnMoZGYxLCBrLCBuc3RhcnQgPSAxMCkkdG90LndpdGhpbnNzDQp9DQoNCiMgSW5pdGlhbGl6ZSB2YXJpYWJsZXMgZm9yIHN0b3JpbmcgV0NTUyB2YWx1ZXMNCndjc3NfdmFsdWVzIDwtIHZlY3RvcigpDQoNCiMgQ29tcHV0ZSBXQ1NTIGZvciBkaWZmZXJlbnQgdmFsdWVzIG9mIGsgKG51bWJlciBvZiBjbHVzdGVycykNCm1heF9rIDwtIDEwICAjIFJlZHVjaWRvIGVsIHJhbmdvIGEgMTANCmZvciAoayBpbiAxOm1heF9rKSB7DQogIHdjc3NfdmFsdWVzW2tdIDwtIHdzcyhrKQ0KfQ0KDQojIFBsb3QgdGhlIGVsYm93IG1ldGhvZCB0byBmaW5kIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycw0KcGxvdCgxOm1heF9rLCB3Y3NzX3ZhbHVlcywgdHlwZSA9ICJiIiwgcGNoID0gMTksIGZyYW1lID0gRkFMU0UsIA0KICAgICB4bGFiID0gIkNsdXN0ZXJzIChLKSIsDQogICAgIHlsYWIgPSAiV0NTUyIsDQogICAgIG1haW4gPSAiTcOpdG9kbyBkZWwgQ29kbyIpDQoNCiMgU2V0IHNlZWQgZm9yIHJlcHJvZHVjaWJpbGl0eQ0Kc2V0LnNlZWQoMTIzKQ0KIyBBY29yZGUgYWwgTcOpdG9kbyBkZSBDb2RvLCBzZSBzZWxlY2Npb25hIGVsIG7Dum1lcm8gZGUgY2x1c3RlcnMgcXVlIGVuIGVzdGUgY2FzbyBzZXLDrWEgMg0KazIgPC0ga21lYW5zKGRmMSwgY2VudGVycyA9IDIsIG5zdGFydCA9IDI1KQ0Kc3RyKGsyKQ0KZnZpel9jbHVzdGVyKGsyLCBkYXRhID0gZGYxKQ0KYGBgDQoNCiMjICoqS05OKiogIA0KQ2FkYSB1bm8gZGUgbG9zIG1vZGVsb3Mgc2lndWllbnRlcyB0aWVuZW4gdW5hIGFsdGEgcHJlY2lzacOzbiwgY29uIHZhbG9yZXMgc3VwZXJpb3JlcyBhbCA5NSUuIEEgcGVzYXIgZGUgZXN0bywgc2UgZGV0ZXJtaW5hIHF1ZSBjb25mb3JtZSBlbCB2YWxvciBkZSBrIGRpc21pbnV5ZSwgbGEgcHJlY2lzacOzbiBkZWwgbW9kZWxvIGF1bWVudGEgbGlnZXJhbWVudGUuIEVzdG8gcHVlZGUgaW5kaWNhciBxdWUgYWwgY29uc2lkZXJhciBtZW5vcyB2ZWNpbm9zIGNlcmNhbm9zLCBlbCBtb2RlbG8gdGllbmRlIGEgY2FwdHVyYXIgbWVqb3IgbGFzIGNhcmFjdGVyw61zdGljYXMgbG9jYWxlcyBkZSBsb3MgZGF0b3MuIFBvciBsbyB0YW50bywgZWwgbW9kZWxvIGtubm4gYSBzZWxlY2Npb25hciBzZXLDrWEga25uX21vZGVsMiBjb24gaz0xLg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoY2xhc3MpDQprbm5fbW9kZWwgPC0ga25uKHRyYWluID0gdHJhaW5bLCAtbmNvbCh0cmFpbildLCB0ZXN0ID0gdGVzdFssIC1uY29sKHRlc3QpXSwgY2wgPSB0cmFpbiRUQVJHRVQsIGsgPSA1KQ0KDQpjb25mdXNpb25fa25uIDwtIHRhYmxlKEFjdHVhbCA9IHRlc3QkVEFSR0VULCBQcmVkaWN0ZWQgPSBrbm5fbW9kZWwpDQpwcmludChjb25mdXNpb25fa25uKQ0KDQphY2N1cmFjeSA8LSBzdW0oZGlhZyhjb25mdXNpb25fa25uKSkgLyBzdW0oY29uZnVzaW9uX2tubikNCnByaW50KHBhc3RlKCJBY2N1cmFjeToiLCBhY2N1cmFjeSkpDQpgYGANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmtubl9tb2RlbDEgPC0ga25uKHRyYWluID0gdHJhaW5bLCAtbmNvbCh0cmFpbildLCB0ZXN0ID0gdGVzdFssIC1uY29sKHRlc3QpXSwgY2wgPSB0cmFpbiRUQVJHRVQsIGsgPSAzKQ0KDQpjb25mdXNpb25fa25uMSA8LSB0YWJsZShBY3R1YWwgPSB0ZXN0JFRBUkdFVCwgUHJlZGljdGVkID0ga25uX21vZGVsMSkNCnByaW50KGNvbmZ1c2lvbl9rbm4xKQ0KDQphY2N1cmFjeTEgPC0gc3VtKGRpYWcoY29uZnVzaW9uX2tubjEpKSAvIHN1bShjb25mdXNpb25fa25uMSkNCnByaW50KHBhc3RlKCJBY2N1cmFjeToiLCBhY2N1cmFjeTEpKQ0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQprbm5fbW9kZWwyIDwtIGtubih0cmFpbiA9IHRyYWluWywgLW5jb2wodHJhaW4pXSwgdGVzdCA9IHRlc3RbLCAtbmNvbCh0ZXN0KV0sIGNsID0gdHJhaW4kVEFSR0VULCBrID0gMSkNCg0KIyBFdmFsdWF0ZSB0aGUgbW9kZWwNCmNvbmZ1c2lvbl9rbm4yIDwtIHRhYmxlKEFjdHVhbCA9IHRlc3QkVEFSR0VULCBQcmVkaWN0ZWQgPSBrbm5fbW9kZWwyKQ0KcHJpbnQoY29uZnVzaW9uX2tubjIpDQoNCiMgQ2FsY3VsYXRlIGFjY3VyYWN5DQphY2N1cmFjeWtubiA8LSBzdW0oZGlhZyhjb25mdXNpb25fa25uMikpIC8gc3VtKGNvbmZ1c2lvbl9rbm4yKQ0KcHJpbnQocGFzdGUoIkFjY3VyYWN5OiIsIGFjY3VyYWN5a25uKSkNCmBgYA0KDQojIyAqKk5hw692ZSBCYXllcyoqDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbmFpdmVfYmF5ZXNfbW9kZWxfYSAgIDwtIG5haXZlQmF5ZXMoVEFSR0VUIH4gLiwgZGF0YSA9IHRyYWluKQ0KDQojIyMgRXZhbHVhciByZXN1bHRhZG9zIGRlIGNsYXNpZmljYWNpb24gDQpwcmVkaWN0ZWQ8LXByZWRpY3QobmFpdmVfYmF5ZXNfbW9kZWxfYSwgYXMuZGF0YS5mcmFtZSh0ZXN0KSkNCmNvbmZ1c2lvbkJheWVzIDwtIGNvbmZ1c2lvbk1hdHJpeCh0ZXN0JFRBUkdFVCwgcHJlZGljdGVkKQ0KY29uZnVzaW9uQmF5ZXMNCmthcHBhX0JheWVzIDwtIGNvbmZ1c2lvbkJheWVzJG92ZXJhbGxbIkthcHBhIl0NCmthcHBhX0JheWVzIA0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQp0cmFpbiAlPiUgZmlsdGVyKFRBUkdFVCA9PSAiMSIpICU+JSBzZWxlY3RfaWYoaXMubnVtZXJpYykgJT4lIGNvcigpICU+JSBjb3JycGxvdDo6Y29ycnBsb3QodHlwZSA9ICJ1cHBlciIpDQoNCiNTZSBzZWxlY2Npb25hbiB2YXJpYWJsZXMgZGUgaW1wb3J0YW5jaWEgYWNvcmRlIGEgbW9kZWxvcyBhbnRlcmlvcmVzDQp0cmFpbiAlPiUgZHBseXI6OnNlbGVjdChBTVRfSU5DT01FX1RPVEFMLCBsb2dfQU1UX0NSRURJVCwgbG9nX0FNVF9BTk5VSVRZLCBsb2dfQU1UX0dPT0RTX1BSSUNFLCBHRU5ERVIsIE5BTUVfSU5DT01FX1RZUEUpICU+JSBnYXRoZXIobWV0cmljLCB2YWx1ZSkgJT4lIGdncGxvdChhZXModmFsdWUsIGZpbGwgPSBtZXRyaWMpKSArIA0KICBnZW9tX2RlbnNpdHkoc2hvdy5sZWdlbmQgPSBGQUxTRSkgKyANCiAgZmFjZXRfd3JhcCh+IG1ldHJpYywgc2NhbGVzID0gImZyZWUiKQ0KYGBgDQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpwcmVkaWN0ZWRfcHJvYnMgPC0gcHJlZGljdChuYWl2ZV9iYXllc19tb2RlbF9hLCBuZXdkYXRhID0gdGVzdCwgdHlwZSA9ICJyYXciKQ0KDQojIEV4dHJhZXIgbGFzIHByb2JhYmlsaWRhZGVzIHByZWRpY2hhcyBkZSBsYSBjbGFzZSBwb3NpdGl2YQ0KcG9zaXRpdmVfcHJvYnMgPC0gcHJlZGljdGVkX3Byb2JzWywgIjEiXQ0KcHJlZGljdGlvbnMgPC0gcHJlZGljdGlvbihwb3NpdGl2ZV9wcm9icywgdGVzdCRUQVJHRVQpDQoNCiMgQVVDDQphdWNfYmF5ZXMgPC0gcGVyZm9ybWFuY2UocHJlZGljdGlvbnMsICJhdWMiKUB5LnZhbHVlc1tbMV1dDQpwcmludChwYXN0ZSgiQVVDOiIsIGF1Y19iYXllcykpDQoNCiMgQ3VydmEgUk9DDQpyb2NfYmF5ZXMgPC0gcGVyZm9ybWFuY2UocHJlZGljdGlvbnMsICJ0cHIiLCAiZnByIikNCnBsb3Qocm9jX2JheWVzLCBtYWluID0gIkN1cnZhIFJPQyIsIGNvbCA9ICJibHVlIikNCmFibGluZShhID0gMCwgYiA9IDEsIGx0eSA9IDIsIGNvbCA9ICJyZWQiKSAgIyBMw60NCmBgYA0KDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiM0NThCMDAiPioqRXZhbHVhY2nDs24geSBTZWxlY2Npw7NuIGRlIE1vZGVsbyBkZSBSZWdyZXNpw7NuKio8L3NwYW4+DQpTZSBvYnNlcnZhbiBsb3Mgc2lndWllbnRlcyBoYWxsYXpnb3MgZW4gcmVsYWNpw7NuIGEgbGEgY29tcGFyYXRpdmEgZGUgbG9zIG1vZGVsb3M6ICANCjEuIExvcyBtb2RlbG9zIFNpbXBsZV9tb2RlbCB5IE11bHRpcGxlX21vZGVsIHRpZW5lbiBsYSBtaXNtYSBwcmVjaXNpw7NuIHF1ZSBlbCBLTk4sIHBlcm8gdGllbmVuIHVuIHZhbG9yIEthcHBhIGRlIDAuMDAwMDAwMDAwLCBsbyBxdWUgaW5kaWNhIHF1ZSBubyBzb24gbWVqb3JlcyBxdWUgZWwgYXphci4NCjIuIEVsIG1vZGVsbyBSYW5kb21fZm9yZXN0IHRpZW5lIHVuYSBwcmVjaXNpw7NuIGRlIDAuOTE2MiwgcGVybyB0aWVuZSB1biB2YWxvciBLYXBwYSBkZSAwLjAwMDIzMDg4MywgbG8gcXVlIGluZGljYSBxdWUgZXMgdW4gcG9jbyBtZWpvciBxdWUgZWwgYXphci4NCjMuIExvcyBtb2RlbG9zIFNpbXBsZUxvZ2lzdGljLCBNdWx0aXBsZUxvZ2lzdGljLCBTVk0geSBEZWNpc2lvblRyZWUgcHJlc2VudGFuIGNpZXRvIHNlc2dvIGVuIHJlbGFjacOzbiBhIGxhIGNsYXNpZmljYWNpw7NuIGRlIHBvc2l0aXZvcyB2ZXJkYWRlcm9zIGRlYmlkbyBhbCBkZXNiYWxhbmNlIGRlIGNsYXNlcy4NCjQuIEVsIG1vZGVsbyBLLU1lYW5zIG5vIHRpZW5lIHVuIHZhbG9yIGRlIHByZWNpc2nDs24sIEthcHBhIG8gQVVDLCBsbyBxdWUgc2lnbmlmaWNhIHF1ZSBubyBzZSBwdWVkZSBjb21wYXJhciBjb24gbG9zIG90cm9zIG1vZGVsb3MuDQo1LiBFbCBtb2RlbG8gS05OIG5vIHRpZW5lIHVuIHZhbG9yIGRlIEthcHBhIG8gQVVDLCBwZXJvIHRpZW5lIHVuIEFjY3VyYWN5IGRlIDAuOTY5IGxvIHF1ZSBzaWduaWZpY2EgcXVlIGVzIHBvc2libGUgcXVlIHRlbmdhIGNpZXJ0byBzZXNnbw0KNi4gRWwgbW9kZWxvIEJheWVzIGVzIHVuIGJ1ZW4gbW9kZWxvIGRlIGNsYXNpZmljYWNpw7NuLCBwZXJvIG5vIGVzIGVsIG1lam9yIGVuIG5pbmd1bmEgZGUgbGFzIHRyZXMgbcOpdHJpY2FzLg0KDQpDb24gYmFzZSBlbiBsYXMgb2JzZXJ2YWNpb25lcyBjb21lbnRhZGFzIGFudGVyaW9ybWVudGUsIHNlIGRldGVybWluYSBxdWUgZWwgbW9kZWxvIFJhbmRvbV9mb3Jlc3QgdGllbmUgdW5hIGFsdGEgcHJlY2lzacOzbiBsbyBxdWUgaW1wbGljYSBsYSBmcmFjY2nDs24gZGUgcHJlZGljY2lvbmVzIHF1ZSBlbCBtb2RlbG8gcmVhbGl6w7MgY29ycmVjdGFtZW50ZS4gTm8gb2JzdGFudGUsIGVsIE11bHRpcGxlX21vZGVsX3dzcyBwb2Ryw61hIHNlciBwcmVmZXJpYmxlIGRlYmlkbyBhIHN1IG1heW9yIEFVQyAgbG8gcXVlIGltcGxpY2EgcXVlIHRpZW5lIG1heW9yIGNhcGFjaWRhZCBkZSBkaXNjcmltaW5hY2nDs24uDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KYWNjdXJhY3lzaW1wbGUgPC0gY29uZnVzaW9uX21hdHJpeCRvdmVyYWxsWyJBY2N1cmFjeSJdDQoNCmFjY3VyYWN5c2ltcGxld3NzIDwtIGNvbmZ1c2lvbl9zaW1wbGVfd3NzJG92ZXJhbGxbIkFjY3VyYWN5Il0NCg0KYWNjdXJhY3ltdWx0aXBsZSA8LSBjb25mdXNpb25fbXVsdGlwbGUkb3ZlcmFsbFsiQWNjdXJhY3kiXQ0KDQphY2N1cmFjeW11bHRpcGxld3NzIDwtIGNvbmZ1c2lvbl9tdWx0aXBsZV93c3Mkb3ZlcmFsbFsiQWNjdXJhY3kiXQ0KDQphY2N1cmFjeXRyZWUgPC0gY29uZnVzaW9uX3RyZWUkb3ZlcmFsbFsiQWNjdXJhY3kiXQ0KDQphY2N1cmFjeXJmIDwtIGNvbmZ1c2lvbl9yZiRvdmVyYWxsWyJBY2N1cmFjeSJdDQoNCmFjY3VyYWN5c3ZtIDwtIGNvbmZ1c2lvbl9zdm0kb3ZlcmFsbFsiQWNjdXJhY3kiXQ0KDQphY2N1cmFjeWJheWVzIDwtIGNvbmZ1c2lvbkJheWVzJG92ZXJhbGxbIkFjY3VyYWN5Il0NCg0KcmVzdWx0YWRvcyA8LSBkYXRhLmZyYW1lKA0KICAiTW9kZWwiID0gYygiU2ltcGxlX21vZGVsIiwgIlNpbXBsZV9tb2RlbF93c3MiLCAiTXVsdGlwbGVfbW9kZWwiLCAiTXVsdGlwbGVfbW9kZWxfd3NzIiwgDQogICAgICAgICAgICAgICJEZWNpc2lvbl90cmVlIiwgIlJhbmRvbV9mb3Jlc3QiLCAiU1ZNIiwgIkJheWVzIiwgIktOTiIsICJLLU1lYW5zIiksDQogICJBY2N1cmFjeSIgPSBjKGFjY3VyYWN5c2ltcGxlLCBhY2N1cmFjeXNpbXBsZXdzcywgYWNjdXJhY3ltdWx0aXBsZSwgYWNjdXJhY3ltdWx0aXBsZXdzcywNCiAgICAgICAgICAgICAgICAgIGFjY3VyYWN5dHJlZSwgYWNjdXJhY3lyZiwgYWNjdXJhY3lzdm0sIGFjY3VyYWN5YmF5ZXMsIGFjY3VyYWN5a25uLCBOQSksDQogICJLYXBwYSIgPSBjKGthcHBhX3NpbXBsZSwga2FwcGFfc2ltcGxlX3dzcywga2FwcGFfbXVsdGlwbGUsIGthcHBfbXVsdGlwbGVfd3NzLCANCiAgICAgICAgICAgICAga2FwcGFfdHJlZSwgY29uZnVzaW9uX3JmJG92ZXJhbGxbIkthcHBhIl0sIGthcHBhX3N2bSwga2FwcGFfQmF5ZXMsIE5BLCBOQSksICANCiAgIkFVQyIgPSBjKDAuNTI2LCAwLjUyNiwgMC42MjQsIDAuNjI1LCBhdWNfdHJlZSwgYXVjX3JmLCBOQSwgYXVjX2JheWVzLCBOQSwgTkEpDQopDQoNCiMgT3JkZW5hciBlbCBkYXRhZnJhbWUgcG9yIEFjY3VyYWN5DQpyZXN1bHRhZG9zDQpgYGANCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6IzQ1OEIwMCI+KipIYWxsYXpnb3MqKjwvc3Bhbj4NCiMjICoqRURBKioNCkVsIGFuw6FsaXNpcyBleHBsb3JhdG9yaW8gZGEgYSBjb25vY2VyIHF1ZSBsYSB2YXJpYWJsZSBkZXBlbmRpZW50ZSAoVEFSR0VUKSB0aWVuZSB1bmEgZnVlcnRlIHJlbGFjacOzbiBsaW5lYWwgc2VndW4gbG9zIGNvZWZpY2llbnRlcyBkZSBjb3JyZWxhY2nDs24gY29uIGxvZ19BTVRfQ1JFRElULCBsb2dfQU1UX0FOTlVJVFkgeSBsb2dfQU1UX0dPT0RTX1BSSUNFLiBBc2ltaXNtbywgc2UgZGV0ZXJtaW5hIHF1ZSB0aWVuZW4gdW4gZWZlY3RvIHBvc2l0aXZvIHkgc2lnbmlmaWNhdGl2byBzb2JyZSBsYSB2YXJpYWJsZSBvYmpldGl2byBsbyBxdWUgc2lnbmlmaWNhIHF1ZSBhIG1lZGlkYSBxdWUgYXVtZW50YSBlbCB2YWxvciBkZSBlc3RhcyB2YXJpYWJsZXMsIHRhbWJpw6luIGF1bWVudGEgZWwgdmFsb3IgZGUgeS4gU2Vnw7puIGVzdG8sIHNlIGVzcGVyYSBxdWU6DQoqIExvcyBjbGllbnRlcyBjb24gdW4gbWF5b3IgbG9nX0FNVF9DUkVESVQgdGllbmVuIHVuIG1heW9yIHJpZXNnbyBkZSBkaWZpY3VsdGFkZXMgZGUgcGFnby4gIA0KKiBMb3MgY2xpZW50ZXMgY29uIHVuIGxvZ19BTVRfQU5OVUlUWSBlbGV2YWRvIHRpZW5lbiBtw6FzIHByb2JhYmlsaWRhZGVzIGRlIHN1ZnJpciBkaWZpY3VsdGFkZXMgZGUgcGFnbywgeWEgcXVlIHRpZW5lbiBxdWUgYWJvbmFyIHVuYSBjYW50aWRhZCBtYXlvciBjYWRhIGHDsW8uICANCiogTG9zIGNsaWVudGVzIHF1ZSBwaWRpZXJvbiB1biBwcsOpc3RhbW8gcGFyYSB1biBhbHRvIGxvZ19BTVRfR09PRFNfUFJJQ0UgdGllbmVuIG3DoXMgcHJvYmFiaWxpZGFkZXMgZGUgdGVuZXIgZGlmaWN1bHRhZGVzIGRlIHBhZ28gZGViaWRvIGEgcXVlIGVzdMOhbiBhc3VtaWVuZG8gdW5hIGRldWRhIG1heW9yIGRlIGxhIHF1ZSBwdWVkZW4gcGVybWl0aXJzZQ0KDQojIyAqKk1vZGVsbyBzZWxlY2Npb25hZG8qKg0KQ29uIGJhc2UgZW4gbG8gYW50ZXJpb3IsIHNlIGRldGVybWluYSBxdWUgZWwgbW9kZWxvIGRlIFJhbmRvbSBGb3Jlc3Qgc2Vyw61hIGVsIGluZGljYWRvIHBhcmEgcHJlZGVjaXIgYSBjYXVzYSBkZSBsYSByb2J1c3RleiB5IGVmaWNpZW5jaWEgcG9yIGVsIGVudHJlbmFtaWVudG8gZGUgbG9zIMOhcmJvbGVzIGRlIGRlY2lzacOzbiBlbiBwYXJhbGVsby4gTWllbnRyYXMgdGFudG8sIGVsIG1vZGVsbyBNdWx0aXBsZV9tb2RlbF93c3Mgc2Vyw61hIGlkZWFsIHBhcmEgY2xhc2lmaWNhciBwdWVzdG8gcXVlLCBpbmRpY2EgdW5hIG1lam9yIGNvbmNvcmRhbmNpYSBlbnRyZSBsYXMgcHJlZGljY2lvbmVzIGRlbCBtb2RlbG8geSBsb3MgdmFsb3JlcyByZWFsZXMsIHBvciBlbmRlLCBtZWpvciBkaXNjcmltaW5hY2nDs24gZGUgY2xhc2VzLiBObyBvYnN0YW50ZSwgYWwgc2VyIHVuIG1vZGVsbyBhanVzdGFkbyBlcyBwcm9iYWJsZSBxdWUgdGllbmRhIGEgc29icmVhcHJlbmRlciBkZSBsb3MgZGF0b3MgbyBleGlzdGEgYWxnw7puIHNlc2dvLg0K