Librerias

library(ggplot2)
library(lattice)
library(caret)
## Warning: package 'caret' was built under R version 4.4.1
library(datasets)
library(DataExplorer)
## Warning: package 'DataExplorer' was built under R version 4.4.1
library(kernlab)
## Warning: package 'kernlab' was built under R version 4.4.1
## 
## Attaching package: 'kernlab'
## The following object is masked from 'package:ggplot2':
## 
##     alpha
library(randomForest)
## Warning: package 'randomForest' was built under R version 4.4.1
## randomForest 4.7-1.1
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## The following object is masked from 'package:ggplot2':
## 
##     margin
library(e1071)
## Warning: package 'e1071' was built under R version 4.4.1
library(rpart)

Importar base de datos

df <- read.csv("C:\\Users\\eleyva1\\OneDrive - Steelcase Inc\\Documents\\LIT TEC\\heart (1).csv")

Análisis exploratorio

summary(df)
##       age             sex               cp            trestbps    
##  Min.   :29.00   Min.   :0.0000   Min.   :0.0000   Min.   : 94.0  
##  1st Qu.:48.00   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:120.0  
##  Median :56.00   Median :1.0000   Median :1.0000   Median :130.0  
##  Mean   :54.43   Mean   :0.6956   Mean   :0.9424   Mean   :131.6  
##  3rd Qu.:61.00   3rd Qu.:1.0000   3rd Qu.:2.0000   3rd Qu.:140.0  
##  Max.   :77.00   Max.   :1.0000   Max.   :3.0000   Max.   :200.0  
##       chol          fbs            restecg          thalach     
##  Min.   :126   Min.   :0.0000   Min.   :0.0000   Min.   : 71.0  
##  1st Qu.:211   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:132.0  
##  Median :240   Median :0.0000   Median :1.0000   Median :152.0  
##  Mean   :246   Mean   :0.1493   Mean   :0.5298   Mean   :149.1  
##  3rd Qu.:275   3rd Qu.:0.0000   3rd Qu.:1.0000   3rd Qu.:166.0  
##  Max.   :564   Max.   :1.0000   Max.   :2.0000   Max.   :202.0  
##      exang           oldpeak          slope             ca        
##  Min.   :0.0000   Min.   :0.000   Min.   :0.000   Min.   :0.0000  
##  1st Qu.:0.0000   1st Qu.:0.000   1st Qu.:1.000   1st Qu.:0.0000  
##  Median :0.0000   Median :0.800   Median :1.000   Median :0.0000  
##  Mean   :0.3366   Mean   :1.072   Mean   :1.385   Mean   :0.7541  
##  3rd Qu.:1.0000   3rd Qu.:1.800   3rd Qu.:2.000   3rd Qu.:1.0000  
##  Max.   :1.0000   Max.   :6.200   Max.   :2.000   Max.   :4.0000  
##       thal           target      
##  Min.   :0.000   Min.   :0.0000  
##  1st Qu.:2.000   1st Qu.:0.0000  
##  Median :2.000   Median :1.0000  
##  Mean   :2.324   Mean   :0.5132  
##  3rd Qu.:3.000   3rd Qu.:1.0000  
##  Max.   :3.000   Max.   :1.0000
str(df)
## 'data.frame':    1025 obs. of  14 variables:
##  $ age     : int  52 53 70 61 62 58 58 55 46 54 ...
##  $ sex     : int  1 1 1 1 0 0 1 1 1 1 ...
##  $ cp      : int  0 0 0 0 0 0 0 0 0 0 ...
##  $ trestbps: int  125 140 145 148 138 100 114 160 120 122 ...
##  $ chol    : int  212 203 174 203 294 248 318 289 249 286 ...
##  $ fbs     : int  0 1 0 0 1 0 0 0 0 0 ...
##  $ restecg : int  1 0 1 1 1 0 2 0 0 0 ...
##  $ thalach : int  168 155 125 161 106 122 140 145 144 116 ...
##  $ exang   : int  0 1 1 0 0 0 0 1 0 1 ...
##  $ oldpeak : num  1 3.1 2.6 0 1.9 1 4.4 0.8 0.8 3.2 ...
##  $ slope   : int  2 0 0 2 1 1 0 1 2 1 ...
##  $ ca      : int  2 0 0 1 3 0 3 1 0 2 ...
##  $ thal    : int  3 3 3 3 2 2 1 3 3 2 ...
##  $ target  : int  0 0 0 0 0 1 0 0 0 0 ...
plot_missing(df)

Partir datos en 80/20

# Definir la proporción de entrenamiento (80%)
set.seed(123) 
train_indices <- sample(1:nrow(df), size = 0.8 * nrow(df))

# Crear el conjunto de entrenamiento y prueba
train_set <- df[train_indices, ]
test_set <- df[-train_indices, ]

test_set$exang <- as.factor(test_set$target)
train_set$exang <- as.factor(train_set$target)
test_set$restecg <- as.factor(test_set$target)
train_set$restecg <- as.factor(train_set$target)
test_set$ca <- as.factor(test_set$target)
train_set$ca <- as.factor(train_set$target)
test_set$slope <- as.factor(test_set$target)
train_set$slope <- as.factor(train_set$target)
test_set$age <- as.factor(test_set$target)
train_set$age <- as.factor(train_set$target)
test_set$cp <- as.factor(test_set$target)
train_set$cp <- as.factor(train_set$target)
test_set$fbs <- as.factor(test_set$target)
train_set$fbs <- as.factor(train_set$target)
train_set$sex <- as.factor(train_set$target)
test_set$sex <- as.factor(test_set$target)
train_set$target <- as.factor(train_set$target)
test_set$target <- as.factor(test_set$target)
names(df)
##  [1] "age"      "sex"      "cp"       "trestbps" "chol"     "fbs"     
##  [7] "restecg"  "thalach"  "exang"    "oldpeak"  "slope"    "ca"      
## [13] "thal"     "target"

Métodos para Modelar

Los métodos más utilizados para modelar aprendizaje automático son: SVM: Support Vector Machine o Máquina de Vectores de Soporte. Hay varios subtipos: Lineal (symLinear), Radial (svmRadial), Polinómico (symPoly), etc. * Árbol de Decisión: rpart * Redes Neuronales: nnet * Random Forest o Bosques Aleatorios: rf

1. Modelo con el metodo svmLineal

modelo1 <- train(target ~ ., data=train_set,
                 method = "svmLinear", #Cambiar
                 preProcess = c("scale", "center"),
                 trControl = trainControl (method ="cv", number =10),
                 tuneGrid = data.frame(C=1) #Cambiar
                 )

resultado_entrenamiento1 <- predict(modelo1, train_set)
resultado_prueba1 <- predict(modelo1, test_set)

# Matriz de Confusión del Resultado del Entrenamiento
mcre <- confusionMatrix(resultado_entrenamiento1, train_set$target)

# Matriz de Confusión del Resultado de la Prueba
mcrp <- confusionMatrix(resultado_prueba1, test_set$target)

2. Modelo con el metodo svmRadial

modelo5<- train(target ~ ., data=train_set,
                method = "svmRadial", #Cambiar
                preProcess=c("scale","center"),
                trControl = trainControl(method="cv", number=10),
                tuneGrid = data.frame(sigma=1,C=1) #Cambiar
                )
resultado_entrenamiento2 <- predict(modelo5,train_set)
resultado_prueba2 <- predict(modelo5,test_set)

# Matriz de Confusión del Resultado de Entrenamiento
mcre2 <- confusionMatrix(resultado_entrenamiento2, train_set$target)

# Matriz de Confusión del Resultado de Prueba
mcrp2 <- confusionMatrix(resultado_prueba2, test_set$target)

3. Modelo con método svmPoly

modelo6<- train(target ~ ., data=train_set,
                method = "svmPoly", #Cambiar
                preProcess=c("scale","center"),
                trControl = trainControl(method="cv", number=10),
                tuneGrid = data.frame(degree=1, scale=1, C=1) #Cambiar
                )
resultado_entrenamiento3 <- predict(modelo6,train_set)
resultado_prueba3 <- predict(modelo6,test_set)

# Matriz de Confusión del Resultado de Entrenamiento
mcre3 <- confusionMatrix(resultado_entrenamiento3, train_set$target)

# Matriz de Confusión del Resultado de Prueba
mcrp3 <- confusionMatrix(resultado_prueba3, test_set$target)

4. Modelo Árboles de Decisión

# Definir la fórmula del modelo
modelo4 <- target ~ .

# Entrenar el modelo de árbol de decisión usando rpart
set.seed(123)  # Para reproducibilidad
arbol_model <- rpart(modelo4, data = train_set, method = "class")

# Resumen del modelo entrenado
print(arbol_model)
## n= 820 
## 
## node), split, n, loss, yval, (yprob)
##       * denotes terminal node
## 
## 1) root 820 404 1 (0.4926829 0.5073171)  
##   2) age=0 404   0 0 (1.0000000 0.0000000) *
##   3) age=1 416   0 1 (0.0000000 1.0000000) *
# Visualizar el árbol de decisión
plot(arbol_model)
text(arbol_model, use.n = TRUE)

# Hacer predicciones en el conjunto de prueba
predicciones <- predict(arbol_model, newdata = test_set, type = "class")

# Evaluar el rendimiento del modelo en el conjunto de prueba
matriz_confusion <- confusionMatrix(predicciones, test_set$target)
print(matriz_confusion)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0  95   0
##          1   0 110
##                                      
##                Accuracy : 1          
##                  95% CI : (0.9822, 1)
##     No Information Rate : 0.5366     
##     P-Value [Acc > NIR] : < 2.2e-16  
##                                      
##                   Kappa : 1          
##                                      
##  Mcnemar's Test P-Value : NA         
##                                      
##             Sensitivity : 1.0000     
##             Specificity : 1.0000     
##          Pos Pred Value : 1.0000     
##          Neg Pred Value : 1.0000     
##              Prevalence : 0.4634     
##          Detection Rate : 0.4634     
##    Detection Prevalence : 0.4634     
##       Balanced Accuracy : 1.0000     
##                                      
##        'Positive' Class : 0          
## 

5. Modelo de Redes Neuronales

# Definir la fórmula del modelo
modelo5 <- target ~ .

# Entrenar el modelo de redes neuronales usando nnet
set.seed(123)  # Para reproducibilidad
nnet_model <- train(
  modelo5,
  data = train_set,
  method = "nnet",
  trControl = trainControl(method = "none"),
  trace = FALSE,
  linout = FALSE,
  maxit = 200  # Número máximo de iteraciones
)

# Resumen del modelo entrenado
print(nnet_model)
## Neural Network 
## 
## 820 samples
##  13 predictor
##   2 classes: '0', '1' 
## 
## No pre-processing
## Resampling: None
# Hacer predicciones en el conjunto de prueba
predicciones <- predict(nnet_model, newdata = test_set)

# Evaluar el rendimiento del modelo en el conjunto de prueba
matriz_confusion <- confusionMatrix(predicciones, test_set$target)
print(matriz_confusion)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0   0   0
##          1  95 110
##                                           
##                Accuracy : 0.5366          
##                  95% CI : (0.4658, 0.6063)
##     No Information Rate : 0.5366          
##     P-Value [Acc > NIR] : 0.5286          
##                                           
##                   Kappa : 0               
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 0.0000          
##             Specificity : 1.0000          
##          Pos Pred Value :    NaN          
##          Neg Pred Value : 0.5366          
##              Prevalence : 0.4634          
##          Detection Rate : 0.0000          
##    Detection Prevalence : 0.0000          
##       Balanced Accuracy : 0.5000          
##                                           
##        'Positive' Class : 0               
## 

6. Modelo de Random Forest

# Construir el modelo Random Forest
modelo6 <- randomForest(target ~ ., data = train_set)


resultado_entrenamiento <- predict(modelo6,train_set)
resultado_prueba <- predict(modelo6,test_set)

# Matriz de Confusión
mcre <- confusionMatrix(resultado_entrenamiento, train_set$target) # matriz de confusión del resultado del entrenamiento

mcrp <- confusionMatrix(resultado_prueba,test_set$target) # matriz de confusión del resultado de la prueba

7. Comparacion de matrices

# Supongamos que ya tienes los modelos entrenados y sus predicciones:
# svm_model, arbol_model, nnet_model, rf_model
# Y sus predicciones:
# pred_svm, pred_arbol, pred_nnet, pred_rf

# Crear una lista para almacenar las matrices de confusión
confusion_matrices <- list()

# Calcular la matriz de confusión para cada modelo
confusion_matrices$SVM <- confusionMatrix(predict(modelo1, newdata = test_set), test_set$target)
confusion_matrices$Decision_Tree <- confusionMatrix(predict(arbol_model, newdata = test_set, type = "class"), test_set$target)
confusion_matrices$Neural_Network <- confusionMatrix(predict(nnet_model, newdata = test_set), test_set$target)
confusion_matrices$Random_Forest <- confusionMatrix(predict(modelo6, newdata = test_set), test_set$target)

# Mostrar las matrices de confusión
print(confusion_matrices)
## $SVM
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0  95   0
##          1   0 110
##                                      
##                Accuracy : 1          
##                  95% CI : (0.9822, 1)
##     No Information Rate : 0.5366     
##     P-Value [Acc > NIR] : < 2.2e-16  
##                                      
##                   Kappa : 1          
##                                      
##  Mcnemar's Test P-Value : NA         
##                                      
##             Sensitivity : 1.0000     
##             Specificity : 1.0000     
##          Pos Pred Value : 1.0000     
##          Neg Pred Value : 1.0000     
##              Prevalence : 0.4634     
##          Detection Rate : 0.4634     
##    Detection Prevalence : 0.4634     
##       Balanced Accuracy : 1.0000     
##                                      
##        'Positive' Class : 0          
##                                      
## 
## $Decision_Tree
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0  95   0
##          1   0 110
##                                      
##                Accuracy : 1          
##                  95% CI : (0.9822, 1)
##     No Information Rate : 0.5366     
##     P-Value [Acc > NIR] : < 2.2e-16  
##                                      
##                   Kappa : 1          
##                                      
##  Mcnemar's Test P-Value : NA         
##                                      
##             Sensitivity : 1.0000     
##             Specificity : 1.0000     
##          Pos Pred Value : 1.0000     
##          Neg Pred Value : 1.0000     
##              Prevalence : 0.4634     
##          Detection Rate : 0.4634     
##    Detection Prevalence : 0.4634     
##       Balanced Accuracy : 1.0000     
##                                      
##        'Positive' Class : 0          
##                                      
## 
## $Neural_Network
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0   0   0
##          1  95 110
##                                           
##                Accuracy : 0.5366          
##                  95% CI : (0.4658, 0.6063)
##     No Information Rate : 0.5366          
##     P-Value [Acc > NIR] : 0.5286          
##                                           
##                   Kappa : 0               
##                                           
##  Mcnemar's Test P-Value : <2e-16          
##                                           
##             Sensitivity : 0.0000          
##             Specificity : 1.0000          
##          Pos Pred Value :    NaN          
##          Neg Pred Value : 0.5366          
##              Prevalence : 0.4634          
##          Detection Rate : 0.0000          
##    Detection Prevalence : 0.0000          
##       Balanced Accuracy : 0.5000          
##                                           
##        'Positive' Class : 0               
##                                           
## 
## $Random_Forest
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction   0   1
##          0  95   0
##          1   0 110
##                                      
##                Accuracy : 1          
##                  95% CI : (0.9822, 1)
##     No Information Rate : 0.5366     
##     P-Value [Acc > NIR] : < 2.2e-16  
##                                      
##                   Kappa : 1          
##                                      
##  Mcnemar's Test P-Value : NA         
##                                      
##             Sensitivity : 1.0000     
##             Specificity : 1.0000     
##          Pos Pred Value : 1.0000     
##          Neg Pred Value : 1.0000     
##              Prevalence : 0.4634     
##          Detection Rate : 0.4634     
##    Detection Prevalence : 0.4634     
##       Balanced Accuracy : 1.0000     
##                                      
##        'Positive' Class : 0          
## 
# Comparar las métricas de los modelos
comparison <- data.frame(
  Modelo = c("SVM", "Decision Tree", "Neural Network", "Random Forest"),
  Accuracy = c(confusion_matrices$SVM$overall['Accuracy'],
               confusion_matrices$Decision_Tree$overall['Accuracy'],
               confusion_matrices$Neural_Network$overall['Accuracy'],
               confusion_matrices$Random_Forest$overall['Accuracy']),
  Kappa = c(confusion_matrices$SVM$overall['Kappa'],
            confusion_matrices$Decision_Tree$overall['Kappa'],
            confusion_matrices$Neural_Network$overall['Kappa'],
            confusion_matrices$Random_Forest$overall['Kappa'])
)

# Mostrar la comparación de métricas
print(comparison)
##           Modelo  Accuracy Kappa
## 1            SVM 1.0000000     1
## 2  Decision Tree 1.0000000     1
## 3 Neural Network 0.5365854     0
## 4  Random Forest 1.0000000     1

Conclusiones

Aunque tres de los modelos parecen estar funcionando perfectamente, estos resultados podrían estar ocultando problemas de sobreajuste o de un conjunto de prueba no representativo. La red neuronal claramente no está funcionando bien y podría requerir ajustes adicionales.

LS0tDQp0aXRsZTogIkNBUkVUIOKAkyBFbmZlcm1lZGFkIENhcmRpYWNhIg0KYXV0aG9yOiAiRWR1YXJkbyBMZXl2YSBMdW5hIg0KZGF0ZTogIjIwMjQtMDgtMjEiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCiAgICB0aGVtZTogImNvc21vIg0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlID0gImNvbG9yOiByZWQ7ICI+TGlicmVyaWFzPC9zcGFuPg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGxhdHRpY2UpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShkYXRhc2V0cykNCmxpYnJhcnkoRGF0YUV4cGxvcmVyKQ0KbGlicmFyeShrZXJubGFiKQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQpsaWJyYXJ5KGUxMDcxKQ0KbGlicmFyeShycGFydCkNCmBgYA0KDQojIyA8c3BhbiBzdHlsZSA9ICJjb2xvcjogcmVkOyAiPkltcG9ydGFyIGJhc2UgZGUgZGF0b3M8L3NwYW4+DQpgYGB7cn0NCmRmIDwtIHJlYWQuY3N2KCJDOlxcVXNlcnNcXGVsZXl2YTFcXE9uZURyaXZlIC0gU3RlZWxjYXNlIEluY1xcRG9jdW1lbnRzXFxMSVQgVEVDXFxoZWFydCAoMSkuY3N2IikNCmBgYA0KDQojIyA8c3BhbiBzdHlsZSA9ICJjb2xvcjogcmVkOyAiPkFuw6FsaXNpcyBleHBsb3JhdG9yaW88L3NwYW4+DQpgYGB7cn0NCnN1bW1hcnkoZGYpDQpzdHIoZGYpDQpwbG90X21pc3NpbmcoZGYpDQpgYGANCg0KIyMgPHNwYW4gc3R5bGUgPSAiY29sb3I6IHJlZDsgIj5QYXJ0aXIgZGF0b3MgZW4gODAvMjA8L3NwYW4+DQpgYGB7cn0NCiMgRGVmaW5pciBsYSBwcm9wb3JjacOzbiBkZSBlbnRyZW5hbWllbnRvICg4MCUpDQpzZXQuc2VlZCgxMjMpIA0KdHJhaW5faW5kaWNlcyA8LSBzYW1wbGUoMTpucm93KGRmKSwgc2l6ZSA9IDAuOCAqIG5yb3coZGYpKQ0KDQojIENyZWFyIGVsIGNvbmp1bnRvIGRlIGVudHJlbmFtaWVudG8geSBwcnVlYmENCnRyYWluX3NldCA8LSBkZlt0cmFpbl9pbmRpY2VzLCBdDQp0ZXN0X3NldCA8LSBkZlstdHJhaW5faW5kaWNlcywgXQ0KDQp0ZXN0X3NldCRleGFuZyA8LSBhcy5mYWN0b3IodGVzdF9zZXQkdGFyZ2V0KQ0KdHJhaW5fc2V0JGV4YW5nIDwtIGFzLmZhY3Rvcih0cmFpbl9zZXQkdGFyZ2V0KQ0KdGVzdF9zZXQkcmVzdGVjZyA8LSBhcy5mYWN0b3IodGVzdF9zZXQkdGFyZ2V0KQ0KdHJhaW5fc2V0JHJlc3RlY2cgPC0gYXMuZmFjdG9yKHRyYWluX3NldCR0YXJnZXQpDQp0ZXN0X3NldCRjYSA8LSBhcy5mYWN0b3IodGVzdF9zZXQkdGFyZ2V0KQ0KdHJhaW5fc2V0JGNhIDwtIGFzLmZhY3Rvcih0cmFpbl9zZXQkdGFyZ2V0KQ0KdGVzdF9zZXQkc2xvcGUgPC0gYXMuZmFjdG9yKHRlc3Rfc2V0JHRhcmdldCkNCnRyYWluX3NldCRzbG9wZSA8LSBhcy5mYWN0b3IodHJhaW5fc2V0JHRhcmdldCkNCnRlc3Rfc2V0JGFnZSA8LSBhcy5mYWN0b3IodGVzdF9zZXQkdGFyZ2V0KQ0KdHJhaW5fc2V0JGFnZSA8LSBhcy5mYWN0b3IodHJhaW5fc2V0JHRhcmdldCkNCnRlc3Rfc2V0JGNwIDwtIGFzLmZhY3Rvcih0ZXN0X3NldCR0YXJnZXQpDQp0cmFpbl9zZXQkY3AgPC0gYXMuZmFjdG9yKHRyYWluX3NldCR0YXJnZXQpDQp0ZXN0X3NldCRmYnMgPC0gYXMuZmFjdG9yKHRlc3Rfc2V0JHRhcmdldCkNCnRyYWluX3NldCRmYnMgPC0gYXMuZmFjdG9yKHRyYWluX3NldCR0YXJnZXQpDQp0cmFpbl9zZXQkc2V4IDwtIGFzLmZhY3Rvcih0cmFpbl9zZXQkdGFyZ2V0KQ0KdGVzdF9zZXQkc2V4IDwtIGFzLmZhY3Rvcih0ZXN0X3NldCR0YXJnZXQpDQp0cmFpbl9zZXQkdGFyZ2V0IDwtIGFzLmZhY3Rvcih0cmFpbl9zZXQkdGFyZ2V0KQ0KdGVzdF9zZXQkdGFyZ2V0IDwtIGFzLmZhY3Rvcih0ZXN0X3NldCR0YXJnZXQpDQpgYGANCg0KYGBge3J9DQpuYW1lcyhkZikNCmBgYA0KDQojIDxzcGFuIHN0eWxlID0gImNvbG9yOiByZWQ7ICI+TcOpdG9kb3MgcGFyYSBNb2RlbGFyPC9zcGFuPg0KDQpMb3MgbcOpdG9kb3MgbcOhcyB1dGlsaXphZG9zIHBhcmEgbW9kZWxhciBhcHJlbmRpemFqZSBhdXRvbcOhdGljbyBzb246DQoqKlNWTSoqOg0KKlN1cHBvcnQgVmVjdG9yIE1hY2hpbmUqIG8gTcOhcXVpbmEgZGUgVmVjdG9yZXMgZGUgU29wb3J0ZS4gSGF5IHZhcmlvcyBzdWJ0aXBvczoNCkxpbmVhbCAoc3ltTGluZWFyKSwgUmFkaWFsIChzdm1SYWRpYWwpLCBQb2xpbsOzbWljbyAoc3ltUG9seSksIGV0Yy4NCiogKirDgXJib2wgZGUgRGVjaXNpw7NuKio6IHJwYXJ0DQoqICoqUmVkZXMgTmV1cm9uYWxlcyoqOiBubmV0DQoqICoqUmFuZG9tIEZvcmVzdCoqIG8gQm9zcXVlcyBBbGVhdG9yaW9zOiByZg0KDQojIyA8c3BhbiBzdHlsZSA9ICJjb2xvcjogcmVkOyAiPjEuIE1vZGVsbyBjb24gZWwgbWV0b2RvIHN2bUxpbmVhbDwvc3Bhbj4NCmBgYHtyfQ0KbW9kZWxvMSA8LSB0cmFpbih0YXJnZXQgfiAuLCBkYXRhPXRyYWluX3NldCwNCiAgICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bUxpbmVhciIsICNDYW1iaWFyDQogICAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJzY2FsZSIsICJjZW50ZXIiKSwNCiAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5Db250cm9sIChtZXRob2QgPSJjdiIsIG51bWJlciA9MTApLA0KICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUoQz0xKSAjQ2FtYmlhcg0KICAgICAgICAgICAgICAgICApDQoNCnJlc3VsdGFkb19lbnRyZW5hbWllbnRvMSA8LSBwcmVkaWN0KG1vZGVsbzEsIHRyYWluX3NldCkNCnJlc3VsdGFkb19wcnVlYmExIDwtIHByZWRpY3QobW9kZWxvMSwgdGVzdF9zZXQpDQoNCiMgTWF0cml6IGRlIENvbmZ1c2nDs24gZGVsIFJlc3VsdGFkbyBkZWwgRW50cmVuYW1pZW50bw0KbWNyZSA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX2VudHJlbmFtaWVudG8xLCB0cmFpbl9zZXQkdGFyZ2V0KQ0KDQojIE1hdHJpeiBkZSBDb25mdXNpw7NuIGRlbCBSZXN1bHRhZG8gZGUgbGEgUHJ1ZWJhDQptY3JwIDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fcHJ1ZWJhMSwgdGVzdF9zZXQkdGFyZ2V0KQ0KYGBgDQoNCg0KIyMgPHNwYW4gc3R5bGUgPSAiY29sb3I6IHJlZDsgIj4yLiBNb2RlbG8gY29uIGVsIG1ldG9kbyBzdm1SYWRpYWw8L3NwYW4+DQpgYGB7cn0NCm1vZGVsbzU8LSB0cmFpbih0YXJnZXQgfiAuLCBkYXRhPXRyYWluX3NldCwNCiAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUmFkaWFsIiwgI0NhbWJpYXINCiAgICAgICAgICAgICAgICBwcmVQcm9jZXNzPWMoInNjYWxlIiwiY2VudGVyIiksDQogICAgICAgICAgICAgICAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZD0iY3YiLCBudW1iZXI9MTApLA0KICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gZGF0YS5mcmFtZShzaWdtYT0xLEM9MSkgI0NhbWJpYXINCiAgICAgICAgICAgICAgICApDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50bzIgPC0gcHJlZGljdChtb2RlbG81LHRyYWluX3NldCkNCnJlc3VsdGFkb19wcnVlYmEyIDwtIHByZWRpY3QobW9kZWxvNSx0ZXN0X3NldCkNCg0KIyBNYXRyaXogZGUgQ29uZnVzacOzbiBkZWwgUmVzdWx0YWRvIGRlIEVudHJlbmFtaWVudG8NCm1jcmUyIDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fZW50cmVuYW1pZW50bzIsIHRyYWluX3NldCR0YXJnZXQpDQoNCiMgTWF0cml6IGRlIENvbmZ1c2nDs24gZGVsIFJlc3VsdGFkbyBkZSBQcnVlYmENCm1jcnAyIDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fcHJ1ZWJhMiwgdGVzdF9zZXQkdGFyZ2V0KQ0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlID0gImNvbG9yOiByZWQ7ICI+My4gTW9kZWxvIGNvbiBtw6l0b2RvIHN2bVBvbHk8L3NwYW4+DQpgYGB7cn0NCm1vZGVsbzY8LSB0cmFpbih0YXJnZXQgfiAuLCBkYXRhPXRyYWluX3NldCwNCiAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUG9seSIsICNDYW1iaWFyDQogICAgICAgICAgICAgICAgcHJlUHJvY2Vzcz1jKCJzY2FsZSIsImNlbnRlciIpLA0KICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyYWluQ29udHJvbChtZXRob2Q9ImN2IiwgbnVtYmVyPTEwKSwNCiAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGRhdGEuZnJhbWUoZGVncmVlPTEsIHNjYWxlPTEsIEM9MSkgI0NhbWJpYXINCiAgICAgICAgICAgICAgICApDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50bzMgPC0gcHJlZGljdChtb2RlbG82LHRyYWluX3NldCkNCnJlc3VsdGFkb19wcnVlYmEzIDwtIHByZWRpY3QobW9kZWxvNix0ZXN0X3NldCkNCg0KIyBNYXRyaXogZGUgQ29uZnVzacOzbiBkZWwgUmVzdWx0YWRvIGRlIEVudHJlbmFtaWVudG8NCm1jcmUzIDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fZW50cmVuYW1pZW50bzMsIHRyYWluX3NldCR0YXJnZXQpDQoNCiMgTWF0cml6IGRlIENvbmZ1c2nDs24gZGVsIFJlc3VsdGFkbyBkZSBQcnVlYmENCm1jcnAzIDwtIGNvbmZ1c2lvbk1hdHJpeChyZXN1bHRhZG9fcHJ1ZWJhMywgdGVzdF9zZXQkdGFyZ2V0KQ0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlID0gImNvbG9yOiByZWQ7ICI+NC4gTW9kZWxvIMOBcmJvbGVzIGRlIERlY2lzacOzbjwvc3Bhbj4NCmBgYHtyfQ0KIyBEZWZpbmlyIGxhIGbDs3JtdWxhIGRlbCBtb2RlbG8NCm1vZGVsbzQgPC0gdGFyZ2V0IH4gLg0KDQojIEVudHJlbmFyIGVsIG1vZGVsbyBkZSDDoXJib2wgZGUgZGVjaXNpw7NuIHVzYW5kbyBycGFydA0Kc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQNCmFyYm9sX21vZGVsIDwtIHJwYXJ0KG1vZGVsbzQsIGRhdGEgPSB0cmFpbl9zZXQsIG1ldGhvZCA9ICJjbGFzcyIpDQoNCiMgUmVzdW1lbiBkZWwgbW9kZWxvIGVudHJlbmFkbw0KcHJpbnQoYXJib2xfbW9kZWwpDQoNCiMgVmlzdWFsaXphciBlbCDDoXJib2wgZGUgZGVjaXNpw7NuDQpwbG90KGFyYm9sX21vZGVsKQ0KdGV4dChhcmJvbF9tb2RlbCwgdXNlLm4gPSBUUlVFKQ0KDQojIEhhY2VyIHByZWRpY2Npb25lcyBlbiBlbCBjb25qdW50byBkZSBwcnVlYmENCnByZWRpY2Npb25lcyA8LSBwcmVkaWN0KGFyYm9sX21vZGVsLCBuZXdkYXRhID0gdGVzdF9zZXQsIHR5cGUgPSAiY2xhc3MiKQ0KDQojIEV2YWx1YXIgZWwgcmVuZGltaWVudG8gZGVsIG1vZGVsbyBlbiBlbCBjb25qdW50byBkZSBwcnVlYmENCm1hdHJpel9jb25mdXNpb24gPC0gY29uZnVzaW9uTWF0cml4KHByZWRpY2Npb25lcywgdGVzdF9zZXQkdGFyZ2V0KQ0KcHJpbnQobWF0cml6X2NvbmZ1c2lvbikNCg0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlID0gImNvbG9yOiByZWQ7ICI+NS4gTW9kZWxvIGRlIFJlZGVzIE5ldXJvbmFsZXM8L3NwYW4+DQpgYGB7cn0NCiMgRGVmaW5pciBsYSBmw7NybXVsYSBkZWwgbW9kZWxvDQptb2RlbG81IDwtIHRhcmdldCB+IC4NCg0KIyBFbnRyZW5hciBlbCBtb2RlbG8gZGUgcmVkZXMgbmV1cm9uYWxlcyB1c2FuZG8gbm5ldA0Kc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQNCm5uZXRfbW9kZWwgPC0gdHJhaW4oDQogIG1vZGVsbzUsDQogIGRhdGEgPSB0cmFpbl9zZXQsDQogIG1ldGhvZCA9ICJubmV0IiwNCiAgdHJDb250cm9sID0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJub25lIiksDQogIHRyYWNlID0gRkFMU0UsDQogIGxpbm91dCA9IEZBTFNFLA0KICBtYXhpdCA9IDIwMCAgIyBOw7ptZXJvIG3DoXhpbW8gZGUgaXRlcmFjaW9uZXMNCikNCg0KIyBSZXN1bWVuIGRlbCBtb2RlbG8gZW50cmVuYWRvDQpwcmludChubmV0X21vZGVsKQ0KDQojIEhhY2VyIHByZWRpY2Npb25lcyBlbiBlbCBjb25qdW50byBkZSBwcnVlYmENCnByZWRpY2Npb25lcyA8LSBwcmVkaWN0KG5uZXRfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X3NldCkNCg0KIyBFdmFsdWFyIGVsIHJlbmRpbWllbnRvIGRlbCBtb2RlbG8gZW4gZWwgY29uanVudG8gZGUgcHJ1ZWJhDQptYXRyaXpfY29uZnVzaW9uIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkaWNjaW9uZXMsIHRlc3Rfc2V0JHRhcmdldCkNCnByaW50KG1hdHJpel9jb25mdXNpb24pDQpgYGANCg0KDQojIyA8c3BhbiBzdHlsZSA9ICJjb2xvcjogcmVkOyAiPjYuIE1vZGVsbyBkZSBSYW5kb20gRm9yZXN0PC9zcGFuPg0KYGBge3J9DQojIENvbnN0cnVpciBlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdA0KbW9kZWxvNiA8LSByYW5kb21Gb3Jlc3QodGFyZ2V0IH4gLiwgZGF0YSA9IHRyYWluX3NldCkNCg0KDQpyZXN1bHRhZG9fZW50cmVuYW1pZW50byA8LSBwcmVkaWN0KG1vZGVsbzYsdHJhaW5fc2V0KQ0KcmVzdWx0YWRvX3BydWViYSA8LSBwcmVkaWN0KG1vZGVsbzYsdGVzdF9zZXQpDQoNCiMgTWF0cml6IGRlIENvbmZ1c2nDs24NCm1jcmUgPC0gY29uZnVzaW9uTWF0cml4KHJlc3VsdGFkb19lbnRyZW5hbWllbnRvLCB0cmFpbl9zZXQkdGFyZ2V0KSAjIG1hdHJpeiBkZSBjb25mdXNpw7NuIGRlbCByZXN1bHRhZG8gZGVsIGVudHJlbmFtaWVudG8NCg0KbWNycCA8LSBjb25mdXNpb25NYXRyaXgocmVzdWx0YWRvX3BydWViYSx0ZXN0X3NldCR0YXJnZXQpICMgbWF0cml6IGRlIGNvbmZ1c2nDs24gZGVsIHJlc3VsdGFkbyBkZSBsYSBwcnVlYmENCg0KYGBgDQoNCiMjIDxzcGFuIHN0eWxlID0gImNvbG9yOiByZWQ7ICI+Ny4gQ29tcGFyYWNpb24gZGUgbWF0cmljZXM8L3NwYW4+DQpgYGB7cn0NCiMgU3Vwb25nYW1vcyBxdWUgeWEgdGllbmVzIGxvcyBtb2RlbG9zIGVudHJlbmFkb3MgeSBzdXMgcHJlZGljY2lvbmVzOg0KIyBzdm1fbW9kZWwsIGFyYm9sX21vZGVsLCBubmV0X21vZGVsLCByZl9tb2RlbA0KIyBZIHN1cyBwcmVkaWNjaW9uZXM6DQojIHByZWRfc3ZtLCBwcmVkX2FyYm9sLCBwcmVkX25uZXQsIHByZWRfcmYNCg0KIyBDcmVhciB1bmEgbGlzdGEgcGFyYSBhbG1hY2VuYXIgbGFzIG1hdHJpY2VzIGRlIGNvbmZ1c2nDs24NCmNvbmZ1c2lvbl9tYXRyaWNlcyA8LSBsaXN0KCkNCg0KIyBDYWxjdWxhciBsYSBtYXRyaXogZGUgY29uZnVzacOzbiBwYXJhIGNhZGEgbW9kZWxvDQpjb25mdXNpb25fbWF0cmljZXMkU1ZNIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0KG1vZGVsbzEsIG5ld2RhdGEgPSB0ZXN0X3NldCksIHRlc3Rfc2V0JHRhcmdldCkNCmNvbmZ1c2lvbl9tYXRyaWNlcyREZWNpc2lvbl9UcmVlIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0KGFyYm9sX21vZGVsLCBuZXdkYXRhID0gdGVzdF9zZXQsIHR5cGUgPSAiY2xhc3MiKSwgdGVzdF9zZXQkdGFyZ2V0KQ0KY29uZnVzaW9uX21hdHJpY2VzJE5ldXJhbF9OZXR3b3JrIDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0KG5uZXRfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X3NldCksIHRlc3Rfc2V0JHRhcmdldCkNCmNvbmZ1c2lvbl9tYXRyaWNlcyRSYW5kb21fRm9yZXN0IDwtIGNvbmZ1c2lvbk1hdHJpeChwcmVkaWN0KG1vZGVsbzYsIG5ld2RhdGEgPSB0ZXN0X3NldCksIHRlc3Rfc2V0JHRhcmdldCkNCg0KIyBNb3N0cmFyIGxhcyBtYXRyaWNlcyBkZSBjb25mdXNpw7NuDQpwcmludChjb25mdXNpb25fbWF0cmljZXMpDQoNCiMgQ29tcGFyYXIgbGFzIG3DqXRyaWNhcyBkZSBsb3MgbW9kZWxvcw0KY29tcGFyaXNvbiA8LSBkYXRhLmZyYW1lKA0KICBNb2RlbG8gPSBjKCJTVk0iLCAiRGVjaXNpb24gVHJlZSIsICJOZXVyYWwgTmV0d29yayIsICJSYW5kb20gRm9yZXN0IiksDQogIEFjY3VyYWN5ID0gYyhjb25mdXNpb25fbWF0cmljZXMkU1ZNJG92ZXJhbGxbJ0FjY3VyYWN5J10sDQogICAgICAgICAgICAgICBjb25mdXNpb25fbWF0cmljZXMkRGVjaXNpb25fVHJlZSRvdmVyYWxsWydBY2N1cmFjeSddLA0KICAgICAgICAgICAgICAgY29uZnVzaW9uX21hdHJpY2VzJE5ldXJhbF9OZXR3b3JrJG92ZXJhbGxbJ0FjY3VyYWN5J10sDQogICAgICAgICAgICAgICBjb25mdXNpb25fbWF0cmljZXMkUmFuZG9tX0ZvcmVzdCRvdmVyYWxsWydBY2N1cmFjeSddKSwNCiAgS2FwcGEgPSBjKGNvbmZ1c2lvbl9tYXRyaWNlcyRTVk0kb3ZlcmFsbFsnS2FwcGEnXSwNCiAgICAgICAgICAgIGNvbmZ1c2lvbl9tYXRyaWNlcyREZWNpc2lvbl9UcmVlJG92ZXJhbGxbJ0thcHBhJ10sDQogICAgICAgICAgICBjb25mdXNpb25fbWF0cmljZXMkTmV1cmFsX05ldHdvcmskb3ZlcmFsbFsnS2FwcGEnXSwNCiAgICAgICAgICAgIGNvbmZ1c2lvbl9tYXRyaWNlcyRSYW5kb21fRm9yZXN0JG92ZXJhbGxbJ0thcHBhJ10pDQopDQoNCiMgTW9zdHJhciBsYSBjb21wYXJhY2nDs24gZGUgbcOpdHJpY2FzDQpwcmludChjb21wYXJpc29uKQ0KDQpgYGANCg0KIyMgPHNwYW4gc3R5bGUgPSAiY29sb3I6IHJlZDsgIj5Db25jbHVzaW9uZXM8L3NwYW4+DQpBdW5xdWUgdHJlcyBkZSBsb3MgbW9kZWxvcyBwYXJlY2VuIGVzdGFyIGZ1bmNpb25hbmRvIHBlcmZlY3RhbWVudGUsIGVzdG9zIHJlc3VsdGFkb3MgcG9kcsOtYW4gZXN0YXIgb2N1bHRhbmRvIHByb2JsZW1hcyBkZSBzb2JyZWFqdXN0ZSBvIGRlIHVuIGNvbmp1bnRvIGRlIHBydWViYSBubyByZXByZXNlbnRhdGl2by4gTGEgcmVkIG5ldXJvbmFsIGNsYXJhbWVudGUgbm8gZXN0w6EgZnVuY2lvbmFuZG8gYmllbiB5IHBvZHLDrWEgcmVxdWVyaXIgYWp1c3RlcyBhZGljaW9uYWxlcy4NCg==