1. Objetivos del trabajo

En este trabajo ajustaremos una serie de modelos de regresión logística utilizando un datset sobre pasajeros del Titanic, barco que se hundió en 1912.

El objetivo, en todos los casos, consistirá en predecir la cantidad sobrevivientes al naufragio. Los modelos que ajustemos nos proporcionarán la probabilidad de supervivencia. Elegiremos uno y, luego de establecer un punto de corte, podremos predecir la clase a la que pertenece cada pasajero.

Asimismo, se analizarán las características de los datasets utilizados, como las de los modelos generados.

# se cargan las librerías a utilizar
library("tidyverse")
library("data.table")
library("ggplot2")
library("GGally")
library("modelr")
library("gridExtra")
library("caret")
library("broom")
library("pROC")
# se elige una semilla
set.seed(1912)

2. Preparación de los datos

En primer lugar, se carga el dataset. Este cuenta con 891 registros y 12 variables:

  • PassengerId: Id único por pasajero
  • Survived: sobreviviente (1) o fallecido (0)
  • Pclass: clase del viaje (1ra, 2da o 3ra)
  • Name: nombre del pasajero
  • Sex: sexo del pasajero
  • Age: edad del pasajero
  • SibSp: cantidad de hermanos y/o cónyuge a bordo
  • Parch: cantidad de padres/hijos a bordo
  • Ticket: número de ticket
  • Fare: tarifa pagada por el ticket
  • Cabin: número de cabina
  • Embarked: lugar de abordo (C: Cherbourg, S: Southampton, Q: Queenstown)
# se carga el dataset
dataset <- fread("titanic_complete_train.csv")
glimpse(dataset)
Observations: 891
Variables: 12
$ PassengerId <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21…
$ Survived    <int> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0…
$ Pclass      <int> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, 2, 3, 2, 3, 3, 2, 2, 3, 1, 3…
$ Name        <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. John Bradley (Florence Briggs T…
$ Sex         <chr> "male", "female", "female", "female", "male", "male", "male", "male", "fe…
$ Age         <dbl> 22.00000, 38.00000, 26.00000, 35.00000, 35.00000, 26.50759, 54.00000, 2.0…
$ SibSp       <int> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, 0, 4, 0, 1, 0, 0, 0, 0, 0, 3…
$ Parch       <int> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1…
$ Ticket      <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", "113803", "373450", "330877"…
$ Fare        <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625, 21.0750, 11.13…
$ Cabin       <chr> NA, "C85", NA, "C123", NA, NA, "E46", NA, NA, NA, "G6", "C103", NA, NA, N…
$ Embarked    <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", "C", "S", "S", "S", "S", "S"…

De éstas, nos seleccionamos con PassengerId, Sex, Age, SibSp, Parch, Fare, Pclass, Survived y Embarked, que son las que utilizaremos para el ajuste de los distintos modelos, y convertimos las últimas tres a factores.

El nuevo dataset consta de 891 registros y 9 variables.

# se seleccinan las variables deseadas y se convierten algunas a factores
dataset <- dataset %>% 
  select(PassengerId, Survived, Pclass, Sex, Age, SibSp, Parch, Fare, Embarked) %>% 
  mutate(Survived = as_factor(Survived),
         Pclass = as_factor(Pclass),
         Embarked = as_factor(Embarked))
dataset

Con el objetivo de visualizar la relación entre distintas variables, se realiza un gráfico de ggpairs de los atributos Survived, Pclass, Sex, Age y Fare.

En primer lugar, se observa que la variable Sobreviviente contiene clases desbalanceadas: la cantidad de fallecidos supera a la de sobrevivientes.

Además, al relacionar esta variable con la clase en la cual viajaban los pasajeros, vemos tres agrupaciones. En la primera, posiblemente correspondiente a la primera clase, hay mayor cantidad de sobrevivientes que de fallecidos; en la segunda clase, ambas cantidades parecen ser similares, y en la tercera la relación se presenta a la inversa: hay más fallecidos que sobrevivientes.

Asimismo, al vincular la supervivencia con el sexo de los pasajeros se evidencian dos comportamientos diferenciados: en las mujeres hay más sobrevivientes que fallecidos, y en los hombres ocurre lo contrario.

Si también relacionamos esta información con la que aporta la variable de la edad, vemos que la media de las personas a bordo del Titanic se encontraba entre los 20 y 30 años y que también aquí se encuentra la media de los fallecidos, en su mayoría, varones de la tercera clase.

Apoyamos estas primeras aproximaciones a los datos con tablas que nos permiten confirmar nuestras hipotesis.

variables <- c('Survived','Pclass','Sex','Age','Fare')
ggpairs(dataset[variables],
        mapping = aes(colour= Survived)) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1)) + theme_bw()

# cantidad de sobrevivientes según clase
dataset %>%
  select(Pclass, Survived) %>% 
  group_by(Pclass) %>%
  count(Survived)
# cantidad de sobrevivientes según sexo
dataset %>%
  select(Sex, Survived) %>% 
  group_by(Sex) %>% 
  count(Survived)

También graficamos la distribución de la variable Survived, en la cual se observan 342 sobrevivientes. Esto representa un 38% del total de registros.

ggplot(dataset, 
       aes(x=Survived, fill=Survived)) + 
  geom_bar() +
  geom_text(stat='count', aes(label=..count..), position = position_fill(vjust = 50)) +
  labs(title = "Supervivencia - Distribución de clases",
       y = "Frecuencia absoluta") +
  scale_x_discrete("Supervivencia", breaks = c("0","1"),
                   labels=c("No sobrevivientes","Sobrevivientes")) +
  theme(legend.position = "none")

# porcentaje de sobrevivientes
dist.dataset <- round((342 / nrow(dataset)) * 100,0)
print(paste("Porcentaje de sobrevivientes en el dataset:", dist.dataset,"%"))
[1] "Porcentaje de sobrevivientes en el dataset: 38 %"

Con el objetivo de poder entrenar un modelo y validarlo previamente a realizar nuestras predicciones, dividimos el dataset en un conjunto de entrenamiento (70% de los datos) y otro de validación (30%). Para ello usamos la función resample_partition de modelr.

Constatamos también que ambos conjuntos presentan aproximadamente la misma distribución en la variable Sobreviviente que la que mostró el dataset completo, dado que esta será nuestra clase target y queremos asegurarnos de entrenar y validar con conjuntos de datos semejantes.

# se particiona el dataset en entrenamiento y validación
train_valid <- dataset %>% resample_partition(c(train=0.7,valid=0.3))
trainset <- train_valid$train %>% as_tibble()
validationset <- train_valid$valid %>% as_tibble()
# se grafican las distribuciones de clases de ambos conjuntos
trainplot <- ggplot(trainset, 
       aes(x=Survived, fill=Survived)) + 
  geom_bar() +
  geom_text(stat='count', aes(label=..count..), position = position_fill(vjust = 50)) +
  labs(title = "Conjunto de entrenamiento",
       y = "Frecuencia absoluta") +
  scale_x_discrete("Sobrevivientes", breaks = c("0","1"),
                   labels=c("No sobrevivientes","Sobrevivientes")) +
  theme(legend.position = "none")
validplot <- ggplot(validationset, 
       aes(x=Survived, fill=Survived)) + 
  geom_bar() +
  geom_text(stat='count', aes(label=..count..), position = position_fill(vjust = 22)) +
  labs(title = "Conjunto de validación",
       y = "Frecuencia absoluta") +
  scale_x_discrete("Sobrevivientes", breaks = c("0","1"),
                   labels=c("No sobrevivientes","Sobrevivientes")) +
  theme(legend.position = "none")
grid.arrange(
  trainplot,
  validplot,
  nrow = 1,
  top = "Sobrevivientes - Distribución de clases"
)

# porcentaje de sobrevivientes en set de entrenamiento
dist.train <- round((240 / nrow(trainset)) * 100,0)
print(paste("Porcentaje de sobrevivientes en el conjunto de entrenamiento:", dist.train,"%"))
[1] "Porcentaje de sobrevivientes en el conjunto de entrenamiento: 39 %"
# porcentaje de sobrevivientes en set de validación
dist.val <- round((102 / nrow(validationset)) * 100,0)
print(paste("Proporción de sobrevivientes en el conjunto de validación:", dist.val,"%"))
[1] "Proporción de sobrevivientes en el conjunto de validación: 38 %"

3. Generación de modelos

Procedemos a ajustar 4 modelos de regresión logísitca utilizando el conjunto de entrenamiento:

  • modelo 0: predice la probabilidad de sobrevivir en función de la clase, el sexo y la edad
  • modelo 1: predice la probabilidad de sobrevivir en función de la tarifa pagada por el pasaje y el sexo
  • modelo 2: predice la probabilidad de sobrevivir en función de la clase, el sexo, la edad y la tarifa pagada por el pasaje
  • modelo 3: predice la probabilidad de sobrevivir en función del sexo y la cantidad de padres y/o hijos abordo del barco
# se definen las fórmulas para los 4 modelos
logit_formulae <- formulas(.response = ~Survived,
                           model0 = ~ Pclass + Sex + Age,
                           model1 = ~ Fare + Sex, 
                           model2 = ~ Pclass + Sex + Age + Fare,
                           model3 = ~ Sex + Parch
                         )
# se genera un data frame con la información y los modelos ajustados
models <- data_frame(logit_formulae) %>% 
  mutate(models = names(logit_formulae), 
         expression = paste(logit_formulae), 
         mod = map(logit_formulae, ~glm(.,family = 'binomial', data = trainset))) 
models

Nos detenemos a analizar los coeficientes del primer modelo ajustado, el modelo 0.

En una regresión logística, los coeficientes positivos indican que, al aumentar su variable, la probabilidad predicha aumenta también. Por el contrario, un coeficiente negativo indica que la probabilidad disminuye cuando la variable incrementa su valor.

En el caso particular del modelo 0, la probabilidad de sobrevivir disminuye cuando aumentan la variable Pclass toma los valores 2 o 3 y, en este último caso, la disminución es más pronunciada. Del mismo modo, si el pasajero resulta ser un varón, su probabilidad de sobrevivir se reduce. Y, conforme la persona sea de edad más avanzada, la probabilidad de sobrevivir también decaerá.

summary(models$mod$model0)

Call:
glm(formula = ., family = "binomial", data = trainset)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.6713  -0.7017  -0.4466   0.6619   2.4407  

Coefficients:
             Estimate Std. Error z value Pr(>|z|)    
(Intercept)  3.611951   0.461715   7.823 5.16e-15 ***
Pclass2     -1.188844   0.310697  -3.826 0.000130 ***
Pclass3     -2.512577   0.308365  -8.148 3.70e-16 ***
Sexmale     -2.392464   0.218841 -10.932  < 2e-16 ***
Age         -0.036296   0.009367  -3.875 0.000107 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 830.54  on 622  degrees of freedom
Residual deviance: 577.86  on 618  degrees of freedom
AIC: 587.86

Number of Fisher Scoring iterations: 4

Realizamos dos predicciones que nos permiten ver mejor la relación entre coeficientes y probabilidad predicha. Por un lado, predecimos la probabilidad de sobrevivir de Rose, una mujer de 17 años que viaja en primera clase. Por otro, evaluamos el caso de Jack, varón de 20 años que viaja en tercera clase.

rose <- data.frame(Pclass = as.factor(1), 
                   Sex = "female",
                   Age = 17)
rose.prob <- predict(object = models$mod$model0, newdata = rose, type = "response")
print(paste("Probabilidad de que Rose sobreviva:", round(rose.prob,3), sep= " "))
[1] "Probabilidad de que Rose sobreviva: 0.952"
jack <- data.frame(Pclass = as.factor(3),
                   Sex = "male",
                   Age = 20)
jack.prob <- predict(object = models$mod$model0, newdata = jack, type = "response")
print(paste("Probabilidad de que Jack sobreviva:", round(jack.prob,3), sep = " "))
[1] "Probabilidad de que Jack sobreviva: 0.117"

Como era de esperar, la probabilidad de sobrevivir predicha para Jack es menor. En el caso de Rose, la probabilidad no se ve disminuida por los coeficientes de las vairbales Pclass ni Sex, sino solo por el de Age. Jack, en cambio, no solo se ve afectado por este último (y en mayor medida dado que su edad es mayor), sino también por los coeficientes asociados a Pclass3 (Pclass al adoptar el valor 3) y Sexmale (Sex cuando toma el valor male), que tienen una influencia negativa en la probabilidad predicha para él.

Analicemos brevemente el resto de los modelos.

models %>% 
  filter(grepl('1|2|3', models)) %>% 
  mutate(tidy = map(mod,tidy)) %>%  
  unnest(tidy, .drop = TRUE) %>% 
  mutate(estimate=round(estimate,5),
         p.value=round(p.value,4))
  • Modelo 1: tanto la variable Fare como Sex resultan significativas. Mientras que la variable Fare (la tarifa pagada por el pasaje), al aumentar, incrementa la proabilidad de sobrevivir, la variable Sex produce ele fecto contrario al tomar el valor male.

  • Modelo 2: aquí todas las variables, a excepción de Fare, resultan significativas. El coeficiente asociados a Fare presenta un valor mayor a 0,05, lo que indica que esta variable no aporta información al modelo. En el caso de Pclass, Sex y Age se observa una influencia en la probabilidad similar a la encontrada en el modelo 0.

  • Modelo 3: aquí la variable Sex aporta infromación pero la variable Parch no. En el caso de Sex, tiene la misma influencia sobre la probabilidad predicha que la que tenía en el modelo 0: al adoptar el valor male, reduce la probabilidad. Parch, por su parte, influye negativamente al aumentar su valor.

A continuación, se ordenan los modelos por deviance explicada. Podemos ver que el modelo que minimiza la deviance es el modelo 2, lo que hace que su porcentaje de deviance explicada sea mayor al resto: aproximadamente 30%.

models <- models %>% 
  mutate(glance = map(mod,glance))
models %>% 
  unnest(glance, .drop = TRUE) %>%
  mutate(perc_explained_dev = 1-deviance/null.deviance) %>% 
  select(-c(models, df.null, AIC, BIC)) %>% 
  arrange(deviance)

4. Evaluación del modelo

En este apartado procedemos a evaluar el mejor modelo ajustado en el apartado anterior, esto es, el modelo 2.

En primer lugar, realizamos una curva ROC. En ella graficamos el Ratio de Verdaderos Negativos o specificity en el eje de abscisas y el Ratio de Verdaderos Positivos o sensitivity en el eje de ordenadas, cuyas fórmulas son:

\(sensitivity = \frac{TP}{TP+FN}\)

\(specificity = \frac{TN}{TN+FP}\)

Donde:

  • TP = true positive o verdaderos positivos (casos que el modelo predijo como positivos y realmente lo eran)
  • FP = false positive o falsos positivos (casos que el modelo predijo como positivos y en realidad eran negativos)
  • TN = true negative o verdadernos negativos (casos que el modelo predijo como negativos y realmente lo eran)
  • FN = false negative o falsos negativos (casos que el modelo predijo como negativos y en realidad eran positivos)

La sensitivity, a la cual también se la suele denominar recall, indica la cuántos casos realmente positivos se lograron predecir correctamente, es decir, cuánta cobertura tiene el modelo. Para ello, se considera el total de casos que el modelo predijo como positivos y que realmente lo son (TP) sobre el total de positivos relaes (TP+FN), hayan sido predichos como tales por el modelo (TP) o hayan sido catalogados errónemanete como negativos (FN). En este trabajo, la clase positiva son los sobrevivientes.

La specificity, por otro lado, indica lo mismo pero con respecto a los negativos: cuántos casos que en realidad eran negativos fueron predichos de esta forma por nuestro modelo. Aquí la cuenta que se realiza toma los negativos reales que fueron predichos de tal forma (TN) y los divide por el total real de negativos (TN+FP). En este trabajo, los casos negtivos corresponden a los “no sobrevivientes”.

Además, podemos calcular el área bajo la curva graficada. El modelo 2 cuenta con un área del 83%, por lo que sus predicciones superan las del azar, representado por la línea punteada y cuya área es del 50%.

models <- models %>% 
  mutate(pred= map(mod,augment, type.predict = "response"))
prediction <- models %>% 
  filter(grepl('2', models)) %>% 
  unnest(pred, .drop=TRUE)
 
roc_data <- roc(response=prediction$Survived, predictor=prediction$.fitted)
ggroc(roc_data, size=1, color="turquoise3") + 
  geom_abline(slope = 1, intercept = 1, linetype='dashed') +
  theme_bw() + 
  labs(title='Curva ROC', 
       subtitle = 'Modelo2: Survived ~ Pclass + Fare + Sex + Age')

print(paste('AUC: Modelo2: Survived~Pclass+Embarked+Sex+Age:', roc_data$auc))
[1] "AUC: Modelo2: Survived~Pclass+Embarked+Sex+Age: 0.836390339425587"

Graficamos también un violin plot que nos permita ver la distribución de las clases “sobreviviente” y “no sobreviviente” en relación a la probabilidad predicha por el modelo.

En él se puede observar que, a medida que la probabilidad asignada se incrementa, la clase “no sobreviviente” disminuye su densidad y la clase “sobreviviente” la aumenta. Esto nos ayuda a pensar un posible punto de corte a partir del cual podemos establecer que una persona sobrevive, intentando minimizar además el error de predicción (i.e. catalogar a un “no sobrevivientes” como “sobreviviente” o viceversa).

Dado que el quiebre entre disminución de la densidad de “no sobrevivientes” y aumento de la densidad de “sobrevivientes” parece estar entre 0.30 y 0.45, podría pensarse un punto de corte que esté en ese intervalo.

ggplot(prediction, aes(x=Survived, y=.fitted, group=Survived,fill=factor(Survived))) + 
  geom_violin() +
  theme_bw() +
  guides(fill=FALSE) +
  scale_x_discrete("Supervivencia", breaks = c("0","1"),
                   labels=c("No sobrevivientes","Sobrevivientes")) +
  labs(title='Violin plot', 
       subtitle = 'Modelo2: Survived ~ Pclass + Fare + Sex + Age', 
       y='Probabilidad predicha')

5. Elección del punto de corte

En este apartado procederemos a probar nuestro modelo con los datos que hemos reservado para la validación y, a partir de esos resultados, elegiremos un punto de corte que consideremos apropiado para predecir si alguien sobrevide.

# se testean el modelo con los datos de VALIDACIÓN reservados
valid <- models %>% 
  filter(grepl('2',models)) %>% 
  mutate(val= map(mod,augment, newdata=validationset, type.predict = "response"))
valid.results <-  valid %>%
  unnest(val, .drop=TRUE)
valid.results

Para ayudarnos, realizamos un gráfico donde se visualice accuracy, precision, recall y specificity.

Ya hemos hablado de los últimos dos, detengámonos un momento en los primeros:

\(accuracy = \frac{TP+TN}{TP+FP+TN+FN}\)

\(precision = \frac{TP}{TP+FP}\)

El accuracy permite medir la performance general del modelo. Para ello, se debe calcular cuántos casos fueron predichos correctamente, sean estos positivos (aquí, “sobrevivientes”) o negativos (“no sobrevivientes”), y relacionarlos con el total de casos (TP+FP+TN+FN), de modo que la cuenta resultante refleja proporción de los registros fue predicha de manera correcta respecto del total.

La precision, a su vez, indica cuán exacto es el modelo, dado que podría ocurrir que éste capturase correctamente los casos positivos pero a costa de predecir también como positivos los registros negativos. Para medir esto es que se usa la precision, que calcula la cantidad de verdaderos positivos que predijo el modelo sobre la cantidad total de positivos predichos, sean estos realmente positivos o no.

# función que calcula las métricas de las predicciones de acuerdo a los puntos de corte establecidos
prediction_metrics <- function(cutoff, predictions=valid.results){
  table <- predictions %>% 
    mutate(predicted_class=if_else(.fitted>cutoff, 1, 0) %>% as.factor(),
           Survived= factor(Survived))
  confusionMatrix(table(table$predicted_class, table$Survived), positive = "1") %>%
    tidy() %>%
    select(term, estimate) %>%
    filter(term %in% c('accuracy', 'sensitivity', 'specificity', 'precision','recall')) %>%
    mutate(cutoff=cutoff)
}
# se definen puntos de corte
cutoffs = seq(0.05,0.95,0.01)
logit_pred= map_dfr(cutoffs, prediction_metrics) %>% mutate(term=as.factor(term))
# se seleccionan los puntos de corte con mayor accuracy
cutoff.max_ac <- logit_pred %>% 
  filter(term=='accuracy') %>% 
  filter(estimate == max(estimate)) %>% 
  select(cutoff) 
ggplot(logit_pred, aes(cutoff,estimate, group=term, color=term)) + geom_line(size=1) +
  theme_bw() +
  labs(title= 'Accuracy, Sensitivity, Specificity, Recall y Precision', subtitle= 'Model2 - Sobre conjunto de validación', color="") +
  geom_vline(xintercept = min(cutoff.max_ac), linetype="dotted",  color = "black", size=1) +
  geom_vline(xintercept = max(cutoff.max_ac), linetype="dotted",  color = "black", size=1)

En el gráfico se observa que el mejor accuracy se obtiene entre los puntos de corte 0.45 y 0.46. A partir de allí, la línea que marca el accuracy comienza a decaer.

También es cerca de ese punto donde se intersectan las líneas de precision y recall (o sensitivity). Esta intersección da cuenta de la compensación o tensión en la que se encuentran la cobertura y la exactitud de un modelo: la capacidad de predecir correctamente mayor cantidad de casos positivos (i.e. de tener un mayor recall) está acompañada de una menor precisión, por la cual el modelo predice como positivos registros que en realidad no lo son. Y a medida que esta precisión aumenta, su precisión disminuye, es decir, predice correctamente menor cantidad de casos positivos.

De manera similar, la sensitivity se encuentra en tensión con la specificity: en tanto la primera disminuye, la segunda aumenta como consecuencia de que el modelo, al incrementar los falsos positivos y, en consecuencia, predecir menor cantidad de casos positivos correrctamente, reduce también las predicciones negativas que verdaderamente son de esta clase.

Un punto de corte adecuado para predecir si una persona es o no sobreviviente debería lograr cierta armonía entre estas tensiones. En este trabajo elegimos como punto de corte el valor 0.46 por ser aquel que logra el mayor accuracy con el conjunto de validación y encontrarse más cercano a los puntos donde se intersectan recall, precision y specificity.

A continuación, realizamos la matriz de confusión obtenida con tal punto de corte.

# se establece el punto de corte
sel_cutoff = max(cutoff.max_ac)
table <- valid.results %>% 
  mutate(predicted_class=if_else(.fitted>sel_cutoff, 1, 0) %>% as.factor(), 
         Survived = factor(Survived))
# se crea la matriz de confusión
confusionMatrix(table(table$Survived, table$predicted_class), positive = "1")
Confusion Matrix and Statistics

   
      0   1
  0 141  25
  1  19  83
                                          
               Accuracy : 0.8358          
                 95% CI : (0.7859, 0.8781)
    No Information Rate : 0.597           
    P-Value [Acc > NIR] : <2e-16          
                                          
                  Kappa : 0.6557          
                                          
 Mcnemar's Test P-Value : 0.451           
                                          
            Sensitivity : 0.7685          
            Specificity : 0.8812          
         Pos Pred Value : 0.8137          
         Neg Pred Value : 0.8494          
             Prevalence : 0.4030          
         Detection Rate : 0.3097          
   Detection Prevalence : 0.3806          
      Balanced Accuracy : 0.8249          
                                          
       'Positive' Class : 1               
                                          

Esta matriz nos permite visualizar la cantidad esperada de valores positivos y negativos y las predicciones realizadas por el modelo para dichas clases del siguiente modo:

Negativos esperados Positivos esperados
Negativos predichos Verdaderos negativos Falsos negativos
Positivos predichos Falsos positivos Verdaderos positivos

Así, podemos ver que nuestro modelo predice adecuadamente 141 casos negativos y 83 positivos, y hay 25 casos que cataloga como negativos cuando en realidad son positivos y 19 con los que sucede lo contrario.

6. Testeo

En este apartado procedemos a testear el modelo seleccionado con un nuevo dataset no utilizado para entrenamiento ni testeo.

Cargamos el dataset y realizamos el mismo preprocesamiento que hicimos con el conjunto de entrenamiento: convertimos en factor las variables Survived, Pclass y Embarked.

El conjunto de testeo utilizado cuenta con 418 registros y presenta una distribución de clases similar a aquella con la cual entrenamos el modelo.

# se carga el conjunto de testeo
testset <- fread("titanic_complete_test.csv")
# se convierten las variables a factores
testset <- testset %>% 
  select(PassengerId, Survived, Pclass, Sex, Age, SibSp, Parch, Fare, Embarked) %>% 
  mutate(Survived = as_factor(Survived),
         Pclass = as_factor(Pclass),
         Embarked = as_factor(Embarked))
glimpse(testset)
Observations: 418
Variables: 9
$ PassengerId <int> 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906…
$ Survived    <fct> 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 1…
$ Pclass      <fct> 3, 3, 2, 3, 3, 3, 3, 2, 3, 3, 3, 1, 1, 2, 1, 2, 2, 3, 3, 3, 1, 3, 1, 1, 1…
$ Sex         <chr> "male", "female", "male", "male", "female", "male", "female", "male", "fe…
$ Age         <dbl> 34.50000, 47.00000, 62.00000, 27.00000, 22.00000, 14.00000, 30.00000, 26.…
$ SibSp       <int> 0, 1, 0, 0, 1, 0, 0, 1, 0, 2, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1…
$ Parch       <int> 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 3…
$ Fare        <dbl> 7.8292, 7.0000, 9.6875, 8.6625, 12.2875, 9.2250, 7.6292, 29.0000, 7.2292,…
$ Embarked    <fct> Q, S, Q, S, S, S, Q, S, C, S, S, S, S, S, S, C, Q, C, S, C, C, S, S, C, C…
# se calcula el porcentaje de sobrevivientes en el conjunto de testeo
surv.testset <- nrow(testset[testset$Survived==1,])
dist.testset <- round((surv.testset/nrow(testset))*100,0)
print(paste("Porcentaje de sobrevivientes en el conjunto de testeo:", dist.testset,"%"))
[1] "Porcentaje de sobrevivientes en el conjunto de testeo: 38 %"

Dado que ya hemos elegido el punto de corte y realizamos la validación, volvemos a entrenar nuestro modelo con todo el conjunto de entrenamiento, de modo que este cuente con una mayor cantidad de registros para realizar las predicciones.

# se entrena el modelo 2 con TODOS los datos para ENTRENAMIENTO
model2test <- glm(logit_formulae$model2, family = 'binomial', data = dataset)
# se calculan las predicciones para el conjunto de TESTEO
test.table = augment(x=model2test, newdata=testset, type.predict='response') 
# se realiza la clasificación en clases según el punto de corte elegido
test.table = test.table %>% 
  mutate(predicted_class=if_else(.fitted>sel_cutoff, 1, 0) %>% as.factor(),
         Survived= factor(Survived))
test.table

Finalmente, realizamos la matriz de confusión y obtenemos las métricas para las nuevas predicciones.

Es posible ver que el accuracy disminuyó, lo que era esperable dado que las predicciones se realizaron sobre un conjunto de datos no visto por el modelo previamente. De todos modos, un 75% de los casos de predicen correctamente; de estos, 206 corresponden a registros negativos y representan el 81% del total de casos negativos y 110 corresponden a registros positivos y, de acuerdo a la sentivity, constituyen el 66% del total de positivos.

Se ve de este modo que nuestor modelo predice mejor los casos negativos antes que los poritivos.

# se crea la matriz de confusión
confusionMatrix(table(test.table$Survived, test.table$predicted_class), positive = "1")
Confusion Matrix and Statistics

   
      0   1
  0 206  55
  1  47 110
                                          
               Accuracy : 0.756           
                 95% CI : (0.7119, 0.7964)
    No Information Rate : 0.6053          
    P-Value [Acc > NIR] : 5.475e-11       
                                          
                  Kappa : 0.485           
                                          
 Mcnemar's Test P-Value : 0.4882          
                                          
            Sensitivity : 0.6667          
            Specificity : 0.8142          
         Pos Pred Value : 0.7006          
         Neg Pred Value : 0.7893          
             Prevalence : 0.3947          
         Detection Rate : 0.2632          
   Detection Prevalence : 0.3756          
      Balanced Accuracy : 0.7404          
                                          
       'Positive' Class : 1               
                                          
LS0tCnRpdGxlOiAiVHJhYmFqbyBQcsOhY3RpY28gTsKwMyIKYXV0aG9yOiAiTWFjYXJlbmEgRmVybmFuZGV6IFVycXVpemEiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazogCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgZGVwdGg6IDMKICAgIGluY2x1ZGVzOgogICAgICBiZWZvcmVfYm9keTogaGVhZGVyLmh0bWwKICAgICAgYWZ0ZXJfYm9keTogZm9vdGVyLmh0bWwKLS0tCjwvc3R5bGU+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CmRpdi5tYWluLWNvbnRhaW5lciB7CiAgbWF4LXdpZHRoOiAxNjAwcHg7CiAgbWFyZ2luLWxlZnQ6IGF1dG87CiAgbWFyZ2luLXJpZ2h0OiBhdXRvOwp9CmJvZHkgewp0ZXh0LWFsaWduOiBqdXN0aWZ5fQpoMXsKICBmb250LXNpemU6IDE5cHQ7CiAgbWFyZ2luLXRvcDogNzBweDsKfQpoMnsKICBmb250LXNpemU6IDE3cHQ7Cn0KaDN7CiAgZm9udC1zaXplOiAxNXB0Cn0KPC9zdHlsZT4KCiMgMS4gT2JqZXRpdm9zIGRlbCB0cmFiYWpvCgpFbiBlc3RlIHRyYWJham8gYWp1c3RhcmVtb3MgdW5hIHNlcmllIGRlIG1vZGVsb3MgZGUgcmVncmVzacOzbiBsb2fDrXN0aWNhIHV0aWxpemFuZG8gdW4gZGF0c2V0IHNvYnJlIHBhc2FqZXJvcyBkZWwgVGl0YW5pYywgYmFyY28gcXVlIHNlIGh1bmRpw7MgZW4gMTkxMi4KCkVsIG9iamV0aXZvLCBlbiB0b2RvcyBsb3MgY2Fzb3MsIGNvbnNpc3RpcsOhIGVuIHByZWRlY2lyIGxhIGNhbnRpZGFkIHNvYnJldml2aWVudGVzIGFsIG5hdWZyYWdpby4gCkxvcyBtb2RlbG9zIHF1ZSBhanVzdGVtb3Mgbm9zIHByb3BvcmNpb25hcsOhbiBsYSBwcm9iYWJpbGlkYWQgZGUgc3VwZXJ2aXZlbmNpYS4gRWxlZ2lyZW1vcyB1bm8geSwgbHVlZ28gZGUgZXN0YWJsZWNlciB1biBwdW50byBkZSBjb3J0ZSwgcG9kcmVtb3MgcHJlZGVjaXIgbGEgY2xhc2UgYSBsYSBxdWUgcGVydGVuZWNlIGNhZGEgcGFzYWplcm8uCgpBc2ltaXNtbywgc2UgYW5hbGl6YXLDoW4gbGFzIGNhcmFjdGVyw61zdGljYXMgZGUgbG9zIGRhdGFzZXRzIHV0aWxpemFkb3MsIGNvbW8gbGFzIGRlIGxvcyBtb2RlbG9zIGdlbmVyYWRvcy4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIHNlIGNhcmdhbiBsYXMgbGlicmVyw61hcyBhIHV0aWxpemFyCmxpYnJhcnkoInRpZHl2ZXJzZSIpCmxpYnJhcnkoImRhdGEudGFibGUiKQpsaWJyYXJ5KCJnZ3Bsb3QyIikKbGlicmFyeSgiR0dhbGx5IikKbGlicmFyeSgibW9kZWxyIikKbGlicmFyeSgiZ3JpZEV4dHJhIikKbGlicmFyeSgiY2FyZXQiKQpsaWJyYXJ5KCJicm9vbSIpCmxpYnJhcnkoInBST0MiKQojIHNlIGVsaWdlIHVuYSBzZW1pbGxhCnNldC5zZWVkKDE5MTIpCmBgYAoKIyAyLiBQcmVwYXJhY2nDs24gZGUgbG9zIGRhdG9zCgpFbiBwcmltZXIgbHVnYXIsIHNlIGNhcmdhIGVsIGRhdGFzZXQuIEVzdGUgY3VlbnRhIGNvbiA4OTEgcmVnaXN0cm9zIHkgMTIgdmFyaWFibGVzOgoKIC0gUGFzc2VuZ2VySWQ6IElkIMO6bmljbyBwb3IgcGFzYWplcm8KIC0gU3Vydml2ZWQ6IHNvYnJldml2aWVudGUgKDEpIG8gZmFsbGVjaWRvICgwKQogLSBQY2xhc3M6IGNsYXNlIGRlbCB2aWFqZSAoMXJhLCAyZGEgbyAzcmEpCiAtIE5hbWU6IG5vbWJyZSBkZWwgcGFzYWplcm8KIC0gU2V4OiBzZXhvIGRlbCBwYXNhamVybwogLSBBZ2U6IGVkYWQgZGVsIHBhc2FqZXJvCiAtIFNpYlNwOiBjYW50aWRhZCBkZSBoZXJtYW5vcyB5L28gY8Ozbnl1Z2UgYSBib3JkbwogLSBQYXJjaDogY2FudGlkYWQgZGUgcGFkcmVzL2hpam9zIGEgYm9yZG8KIC0gVGlja2V0OiBuw7ptZXJvIGRlIHRpY2tldAogLSBGYXJlOiB0YXJpZmEgcGFnYWRhIHBvciBlbCB0aWNrZXQKIC0gQ2FiaW46IG7Dum1lcm8gZGUgY2FiaW5hCiAtIEVtYmFya2VkOiBsdWdhciBkZSBhYm9yZG8gKEM6IENoZXJib3VyZywgUzogU291dGhhbXB0b24sIFE6IFF1ZWVuc3Rvd24pCiAKYGBge3J9CiMgc2UgY2FyZ2EgZWwgZGF0YXNldApkYXRhc2V0IDwtIGZyZWFkKCJ0aXRhbmljX2NvbXBsZXRlX3RyYWluLmNzdiIpCgpnbGltcHNlKGRhdGFzZXQpCmBgYAoKRGUgw6lzdGFzLCBub3Mgc2VsZWNjaW9uYW1vcyBjb24gX1Bhc3NlbmdlcklkXywgX1NleF8sIF9BZ2VfLCBfU2liU3BfLCBfUGFyY2hfLCBfRmFyZV8sIF9QY2xhc3NfLCBfU3Vydml2ZWRfIHkgX0VtYmFya2VkXywgcXVlIHNvbiBsYXMgcXVlIHV0aWxpemFyZW1vcyBwYXJhIGVsIGFqdXN0ZSBkZSBsb3MgZGlzdGludG9zIG1vZGVsb3MsIHkgY29udmVydGltb3MgbGFzIMO6bHRpbWFzIHRyZXMgYSBmYWN0b3Jlcy4KCkVsIG51ZXZvIGRhdGFzZXQgY29uc3RhIGRlIDg5MSByZWdpc3Ryb3MgeSA5IHZhcmlhYmxlcy4KCmBgYHtyfQojIHNlIHNlbGVjY2luYW4gbGFzIHZhcmlhYmxlcyBkZXNlYWRhcyB5IHNlIGNvbnZpZXJ0ZW4gYWxndW5hcyBhIGZhY3RvcmVzCmRhdGFzZXQgPC0gZGF0YXNldCAlPiUgCiAgc2VsZWN0KFBhc3NlbmdlcklkLCBTdXJ2aXZlZCwgUGNsYXNzLCBTZXgsIEFnZSwgU2liU3AsIFBhcmNoLCBGYXJlLCBFbWJhcmtlZCkgJT4lIAogIG11dGF0ZShTdXJ2aXZlZCA9IGFzX2ZhY3RvcihTdXJ2aXZlZCksCiAgICAgICAgIFBjbGFzcyA9IGFzX2ZhY3RvcihQY2xhc3MpLAogICAgICAgICBFbWJhcmtlZCA9IGFzX2ZhY3RvcihFbWJhcmtlZCkpCgpkYXRhc2V0CmBgYAoKQ29uIGVsIG9iamV0aXZvIGRlIHZpc3VhbGl6YXIgbGEgcmVsYWNpw7NuIGVudHJlIGRpc3RpbnRhcyB2YXJpYWJsZXMsIHNlIHJlYWxpemEgdW4gZ3LDoWZpY28gZGUgZ2dwYWlycyBkZSBsb3MgYXRyaWJ1dG9zIF9TdXJ2aXZlZF8sIF9QY2xhc3NfLCBfU2V4XywgX0FnZV8geSBfRmFyZV8uCgpFbiBwcmltZXIgbHVnYXIsIHNlIG9ic2VydmEgcXVlIGxhIHZhcmlhYmxlIF9Tb2JyZXZpdmllbnRlXyBjb250aWVuZSBjbGFzZXMgZGVzYmFsYW5jZWFkYXM6IGxhIGNhbnRpZGFkIGRlIGZhbGxlY2lkb3Mgc3VwZXJhIGEgbGEgZGUgc29icmV2aXZpZW50ZXMuCgpBZGVtw6FzLCBhbCByZWxhY2lvbmFyIGVzdGEgdmFyaWFibGUgY29uIGxhIF9jbGFzZSBlbiBsYSBjdWFsIHZpYWphYmFuIGxvcyBwYXNhamVyb3NfLCB2ZW1vcyB0cmVzIGFncnVwYWNpb25lcy4gRW4gbGEgcHJpbWVyYSwgcG9zaWJsZW1lbnRlIGNvcnJlc3BvbmRpZW50ZSBhIGxhIHByaW1lcmEgY2xhc2UsIGhheSBtYXlvciBjYW50aWRhZCBkZSBzb2JyZXZpdmllbnRlcyBxdWUgZGUgZmFsbGVjaWRvczsgZW4gbGEgc2VndW5kYSBjbGFzZSwgYW1iYXMgY2FudGlkYWRlcyBwYXJlY2VuIHNlciBzaW1pbGFyZXMsIHkgZW4gbGEgdGVyY2VyYSBsYSByZWxhY2nDs24gc2UgcHJlc2VudGEgYSBsYSBpbnZlcnNhOiBoYXkgbcOhcyBmYWxsZWNpZG9zIHF1ZSBzb2JyZXZpdmllbnRlcy4KCkFzaW1pc21vLCBhbCB2aW5jdWxhciBsYSBzdXBlcnZpdmVuY2lhIGNvbiBlbCBfc2V4b18gZGUgbG9zIHBhc2FqZXJvcyBzZSBldmlkZW5jaWFuIGRvcyBjb21wb3J0YW1pZW50b3MgZGlmZXJlbmNpYWRvczogZW4gbGFzIG11amVyZXMgaGF5IG3DoXMgc29icmV2aXZpZW50ZXMgcXVlIGZhbGxlY2lkb3MsIHkgZW4gbG9zIGhvbWJyZXMgb2N1cnJlIGxvIGNvbnRyYXJpby4KClNpIHRhbWJpw6luIHJlbGFjaW9uYW1vcyBlc3RhIGluZm9ybWFjacOzbiBjb24gbGEgcXVlIGFwb3J0YSBsYSB2YXJpYWJsZSBkZSBsYSBfZWRhZF8sIHZlbW9zIHF1ZSBsYSBtZWRpYSBkZSBsYXMgcGVyc29uYXMgYSBib3JkbyBkZWwgVGl0YW5pYyBzZSBlbmNvbnRyYWJhIGVudHJlIGxvcyAyMCB5IDMwIGHDsW9zIHkgcXVlIHRhbWJpw6luIGFxdcOtIHNlIGVuY3VlbnRyYSBsYSBtZWRpYSBkZSBsb3MgZmFsbGVjaWRvcywgZW4gc3UgbWF5b3LDrWEsIHZhcm9uZXMgZGUgbGEgdGVyY2VyYSBjbGFzZS4KCkFwb3lhbW9zIGVzdGFzIHByaW1lcmFzIGFwcm94aW1hY2lvbmVzIGEgbG9zIGRhdG9zIGNvbiB0YWJsYXMgcXVlIG5vcyBwZXJtaXRlbiBjb25maXJtYXIgbnVlc3RyYXMgaGlwb3Rlc2lzLgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CnZhcmlhYmxlcyA8LSBjKCdTdXJ2aXZlZCcsJ1BjbGFzcycsJ1NleCcsJ0FnZScsJ0ZhcmUnKQoKZ2dwYWlycyhkYXRhc2V0W3ZhcmlhYmxlc10sCiAgICAgICAgbWFwcGluZyA9IGFlcyhjb2xvdXI9IFN1cnZpdmVkKSkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gOTAsIGhqdXN0ID0gMSkpICsgdGhlbWVfYncoKQpgYGAKCgpgYGB7cn0KIyBjYW50aWRhZCBkZSBzb2JyZXZpdmllbnRlcyBzZWfDum4gY2xhc2UKZGF0YXNldCAlPiUKICBzZWxlY3QoUGNsYXNzLCBTdXJ2aXZlZCkgJT4lIAogIGdyb3VwX2J5KFBjbGFzcykgJT4lCiAgY291bnQoU3Vydml2ZWQpCgojIGNhbnRpZGFkIGRlIHNvYnJldml2aWVudGVzIHNlZ8O6biBzZXhvCmRhdGFzZXQgJT4lCiAgc2VsZWN0KFNleCwgU3Vydml2ZWQpICU+JSAKICBncm91cF9ieShTZXgpICU+JSAKICBjb3VudChTdXJ2aXZlZCkKYGBgCgpUYW1iacOpbiBncmFmaWNhbW9zIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbGEgdmFyaWFibGUgX1N1cnZpdmVkXywgZW4gbGEgY3VhbCBzZSBvYnNlcnZhbiAzNDIgc29icmV2aXZpZW50ZXMuIEVzdG8gcmVwcmVzZW50YSB1biAzOCUgZGVsIHRvdGFsIGRlIHJlZ2lzdHJvcy4KCmBgYHtyfQpnZ3Bsb3QoZGF0YXNldCwgCiAgICAgICBhZXMoeD1TdXJ2aXZlZCwgZmlsbD1TdXJ2aXZlZCkpICsgCiAgZ2VvbV9iYXIoKSArCiAgZ2VvbV90ZXh0KHN0YXQ9J2NvdW50JywgYWVzKGxhYmVsPS4uY291bnQuLiksIHBvc2l0aW9uID0gcG9zaXRpb25fZmlsbCh2anVzdCA9IDUwKSkgKwogIGxhYnModGl0bGUgPSAiU3VwZXJ2aXZlbmNpYSAtIERpc3RyaWJ1Y2nDs24gZGUgY2xhc2VzIiwKICAgICAgIHkgPSAiRnJlY3VlbmNpYSBhYnNvbHV0YSIpICsKICBzY2FsZV94X2Rpc2NyZXRlKCJTdXBlcnZpdmVuY2lhIiwgYnJlYWtzID0gYygiMCIsIjEiKSwKICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJObyBzb2JyZXZpdmllbnRlcyIsIlNvYnJldml2aWVudGVzIikpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCmBgYAoKYGBge3J9CiMgcG9yY2VudGFqZSBkZSBzb2JyZXZpdmllbnRlcwpkaXN0LmRhdGFzZXQgPC0gcm91bmQoKDM0MiAvIG5yb3coZGF0YXNldCkpICogMTAwLDApCnByaW50KHBhc3RlKCJQb3JjZW50YWplIGRlIHNvYnJldml2aWVudGVzIGVuIGVsIGRhdGFzZXQ6IiwgZGlzdC5kYXRhc2V0LCIlIikpCmBgYAoKQ29uIGVsIG9iamV0aXZvIGRlIHBvZGVyIGVudHJlbmFyIHVuIG1vZGVsbyB5IHZhbGlkYXJsbyBwcmV2aWFtZW50ZSBhIHJlYWxpemFyIG51ZXN0cmFzIHByZWRpY2Npb25lcywgZGl2aWRpbW9zIGVsIGRhdGFzZXQgZW4gdW4gY29uanVudG8gZGUgZW50cmVuYW1pZW50byAoNzAlIGRlIGxvcyBkYXRvcykgeSBvdHJvIGRlIHZhbGlkYWNpw7NuICgzMCUpLiBQYXJhIGVsbG8gdXNhbW9zIGxhIGZ1bmNpw7NuICpyZXNhbXBsZV9wYXJ0aXRpb24qIGRlICoqbW9kZWxyKiouCgpDb25zdGF0YW1vcyB0YW1iacOpbiBxdWUgYW1ib3MgY29uanVudG9zIHByZXNlbnRhbiBhcHJveGltYWRhbWVudGUgbGEgbWlzbWEgZGlzdHJpYnVjacOzbiBlbiBsYSB2YXJpYWJsZSBfU29icmV2aXZpZW50ZV8gcXVlIGxhIHF1ZSBtb3N0csOzIGVsIGRhdGFzZXQgY29tcGxldG8sIGRhZG8gcXVlIGVzdGEgc2Vyw6EgbnVlc3RyYSBjbGFzZSB0YXJnZXQgeSBxdWVyZW1vcyBhc2VndXJhcm5vcyBkZSBlbnRyZW5hciB5IHZhbGlkYXIgY29uIGNvbmp1bnRvcyBkZSBkYXRvcyBzZW1lamFudGVzLiAKCmBgYHtyfQojIHNlIHBhcnRpY2lvbmEgZWwgZGF0YXNldCBlbiBlbnRyZW5hbWllbnRvIHkgdmFsaWRhY2nDs24KdHJhaW5fdmFsaWQgPC0gZGF0YXNldCAlPiUgcmVzYW1wbGVfcGFydGl0aW9uKGModHJhaW49MC43LHZhbGlkPTAuMykpCgp0cmFpbnNldCA8LSB0cmFpbl92YWxpZCR0cmFpbiAlPiUgYXNfdGliYmxlKCkKdmFsaWRhdGlvbnNldCA8LSB0cmFpbl92YWxpZCR2YWxpZCAlPiUgYXNfdGliYmxlKCkKCiMgc2UgZ3JhZmljYW4gbGFzIGRpc3RyaWJ1Y2lvbmVzIGRlIGNsYXNlcyBkZSBhbWJvcyBjb25qdW50b3MKdHJhaW5wbG90IDwtIGdncGxvdCh0cmFpbnNldCwgCiAgICAgICBhZXMoeD1TdXJ2aXZlZCwgZmlsbD1TdXJ2aXZlZCkpICsgCiAgZ2VvbV9iYXIoKSArCiAgZ2VvbV90ZXh0KHN0YXQ9J2NvdW50JywgYWVzKGxhYmVsPS4uY291bnQuLiksIHBvc2l0aW9uID0gcG9zaXRpb25fZmlsbCh2anVzdCA9IDUwKSkgKwogIGxhYnModGl0bGUgPSAiQ29uanVudG8gZGUgZW50cmVuYW1pZW50byIsCiAgICAgICB5ID0gIkZyZWN1ZW5jaWEgYWJzb2x1dGEiKSArCiAgc2NhbGVfeF9kaXNjcmV0ZSgiU29icmV2aXZpZW50ZXMiLCBicmVha3MgPSBjKCIwIiwiMSIpLAogICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIk5vIHNvYnJldml2aWVudGVzIiwiU29icmV2aXZpZW50ZXMiKSkgKwogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKCnZhbGlkcGxvdCA8LSBnZ3Bsb3QodmFsaWRhdGlvbnNldCwgCiAgICAgICBhZXMoeD1TdXJ2aXZlZCwgZmlsbD1TdXJ2aXZlZCkpICsgCiAgZ2VvbV9iYXIoKSArCiAgZ2VvbV90ZXh0KHN0YXQ9J2NvdW50JywgYWVzKGxhYmVsPS4uY291bnQuLiksIHBvc2l0aW9uID0gcG9zaXRpb25fZmlsbCh2anVzdCA9IDIyKSkgKwogIGxhYnModGl0bGUgPSAiQ29uanVudG8gZGUgdmFsaWRhY2nDs24iLAogICAgICAgeSA9ICJGcmVjdWVuY2lhIGFic29sdXRhIikgKwogIHNjYWxlX3hfZGlzY3JldGUoIlNvYnJldml2aWVudGVzIiwgYnJlYWtzID0gYygiMCIsIjEiKSwKICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJObyBzb2JyZXZpdmllbnRlcyIsIlNvYnJldml2aWVudGVzIikpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpCgpncmlkLmFycmFuZ2UoCiAgdHJhaW5wbG90LAogIHZhbGlkcGxvdCwKICBucm93ID0gMSwKICB0b3AgPSAiU29icmV2aXZpZW50ZXMgLSBEaXN0cmlidWNpw7NuIGRlIGNsYXNlcyIKKQpgYGAKCmBgYHtyfQojIHBvcmNlbnRhamUgZGUgc29icmV2aXZpZW50ZXMgZW4gc2V0IGRlIGVudHJlbmFtaWVudG8KZGlzdC50cmFpbiA8LSByb3VuZCgoMjQwIC8gbnJvdyh0cmFpbnNldCkpICogMTAwLDApCnByaW50KHBhc3RlKCJQb3JjZW50YWplIGRlIHNvYnJldml2aWVudGVzIGVuIGVsIGNvbmp1bnRvIGRlIGVudHJlbmFtaWVudG86IiwgZGlzdC50cmFpbiwiJSIpKQoKIyBwb3JjZW50YWplIGRlIHNvYnJldml2aWVudGVzIGVuIHNldCBkZSB2YWxpZGFjacOzbgpkaXN0LnZhbCA8LSByb3VuZCgoMTAyIC8gbnJvdyh2YWxpZGF0aW9uc2V0KSkgKiAxMDAsMCkKcHJpbnQocGFzdGUoIlByb3BvcmNpw7NuIGRlIHNvYnJldml2aWVudGVzIGVuIGVsIGNvbmp1bnRvIGRlIHZhbGlkYWNpw7NuOiIsIGRpc3QudmFsLCIlIikpCmBgYAoKIyAzLiBHZW5lcmFjacOzbiBkZSBtb2RlbG9zCgpQcm9jZWRlbW9zIGEgYWp1c3RhciA0IG1vZGVsb3MgZGUgcmVncmVzacOzbiBsb2fDrXNpdGNhIHV0aWxpemFuZG8gZWwgY29uanVudG8gZGUgZW50cmVuYW1pZW50bzoKCi0gKiptb2RlbG8gMDoqKiBwcmVkaWNlIGxhIHByb2JhYmlsaWRhZCBkZSBzb2JyZXZpdmlyIGVuIGZ1bmNpw7NuIGRlIGxhIGNsYXNlLCBlbCBzZXhvIHkgbGEgZWRhZAotICoqbW9kZWxvIDE6KiogcHJlZGljZSBsYSBwcm9iYWJpbGlkYWQgZGUgc29icmV2aXZpciBlbiBmdW5jacOzbiBkZSBsYSB0YXJpZmEgcGFnYWRhIHBvciBlbCBwYXNhamUgeSBlbCBzZXhvCi0gKiptb2RlbG8gMjoqKiBwcmVkaWNlIGxhIHByb2JhYmlsaWRhZCBkZSBzb2JyZXZpdmlyIGVuIGZ1bmNpw7NuIGRlIGxhIGNsYXNlLCBlbCBzZXhvLCBsYSBlZGFkIHkgbGEgdGFyaWZhIHBhZ2FkYSBwb3IgZWwgcGFzYWplCi0gKiptb2RlbG8gMzoqKiBwcmVkaWNlIGxhIHByb2JhYmlsaWRhZCBkZSBzb2JyZXZpdmlyIGVuIGZ1bmNpw7NuIGRlbCBzZXhvIHkgbGEgY2FudGlkYWQgZGUgcGFkcmVzIHkvbyBoaWpvcyBhYm9yZG8gZGVsIGJhcmNvCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBzZSBkZWZpbmVuIGxhcyBmw7NybXVsYXMgcGFyYSBsb3MgNCBtb2RlbG9zCmxvZ2l0X2Zvcm11bGFlIDwtIGZvcm11bGFzKC5yZXNwb25zZSA9IH5TdXJ2aXZlZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwwID0gfiBQY2xhc3MgKyBTZXggKyBBZ2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsMSA9IH4gRmFyZSArIFNleCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIG1vZGVsMiA9IH4gUGNsYXNzICsgU2V4ICsgQWdlICsgRmFyZSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZWwzID0gfiBTZXggKyBQYXJjaAogICAgICAgICAgICAgICAgICAgICAgICAgKQoKIyBzZSBnZW5lcmEgdW4gZGF0YSBmcmFtZSBjb24gbGEgaW5mb3JtYWNpw7NuIHkgbG9zIG1vZGVsb3MgYWp1c3RhZG9zCm1vZGVscyA8LSBkYXRhX2ZyYW1lKGxvZ2l0X2Zvcm11bGFlKSAlPiUgCiAgbXV0YXRlKG1vZGVscyA9IG5hbWVzKGxvZ2l0X2Zvcm11bGFlKSwgCiAgICAgICAgIGV4cHJlc3Npb24gPSBwYXN0ZShsb2dpdF9mb3JtdWxhZSksIAogICAgICAgICBtb2QgPSBtYXAobG9naXRfZm9ybXVsYWUsIH5nbG0oLixmYW1pbHkgPSAnYmlub21pYWwnLCBkYXRhID0gdHJhaW5zZXQpKSkgCgptb2RlbHMKYGBgCgpOb3MgZGV0ZW5lbW9zIGEgYW5hbGl6YXIgbG9zIGNvZWZpY2llbnRlcyBkZWwgcHJpbWVyIG1vZGVsbyBhanVzdGFkbywgZWwgbW9kZWxvIDAuCgpFbiB1bmEgcmVncmVzacOzbiBsb2fDrXN0aWNhLCBsb3MgY29lZmljaWVudGVzIHBvc2l0aXZvcyBpbmRpY2FuIHF1ZSwgYWwgYXVtZW50YXIgc3UgdmFyaWFibGUsIGxhIHByb2JhYmlsaWRhZCBwcmVkaWNoYSBhdW1lbnRhIHRhbWJpw6luLiBQb3IgZWwgY29udHJhcmlvLCB1biBjb2VmaWNpZW50ZSBuZWdhdGl2byBpbmRpY2EgcXVlIGxhIHByb2JhYmlsaWRhZCBkaXNtaW51eWUgY3VhbmRvIGxhIHZhcmlhYmxlIGluY3JlbWVudGEgc3UgdmFsb3IuCgpFbiBlbCBjYXNvIHBhcnRpY3VsYXIgZGVsICoqbW9kZWxvIDAqKiwgbGEgcHJvYmFiaWxpZGFkIGRlIHNvYnJldml2aXIgZGlzbWludXllIGN1YW5kbyBhdW1lbnRhbiBsYSB2YXJpYWJsZSAqUGNsYXNzKiB0b21hIGxvcyB2YWxvcmVzIDIgbyAzIHksIGVuIGVzdGUgw7psdGltbyBjYXNvLCBsYSBkaXNtaW51Y2nDs24gZXMgbcOhcyBwcm9udW5jaWFkYS4KRGVsIG1pc21vIG1vZG8sIHNpIGVsIHBhc2FqZXJvIHJlc3VsdGEgc2VyIHVuIHZhcsOzbiwgc3UgcHJvYmFiaWxpZGFkIGRlIHNvYnJldml2aXIgc2UgcmVkdWNlLgpZLCBjb25mb3JtZSBsYSBwZXJzb25hIHNlYSBkZSBlZGFkIG3DoXMgYXZhbnphZGEsIGxhIHByb2JhYmlsaWRhZCBkZSBzb2JyZXZpdmlyIHRhbWJpw6luIGRlY2FlcsOhLgoKYGBge3J9CnN1bW1hcnkobW9kZWxzJG1vZCRtb2RlbDApCmBgYAoKUmVhbGl6YW1vcyBkb3MgcHJlZGljY2lvbmVzIHF1ZSBub3MgcGVybWl0ZW4gdmVyIG1lam9yIGxhIHJlbGFjacOzbiBlbnRyZSBjb2VmaWNpZW50ZXMgeSBwcm9iYWJpbGlkYWQgcHJlZGljaGEuIFBvciB1biBsYWRvLCBwcmVkZWNpbW9zIGxhIHByb2JhYmlsaWRhZCBkZSBzb2JyZXZpdmlyIGRlIFJvc2UsIHVuYSBtdWplciBkZSAxNyBhw7FvcyBxdWUgdmlhamEgZW4gcHJpbWVyYSBjbGFzZS4gUG9yIG90cm8sIGV2YWx1YW1vcyBlbCBjYXNvIGRlIEphY2ssIHZhcsOzbiBkZSAyMCBhw7FvcyBxdWUgdmlhamEgZW4gdGVyY2VyYSBjbGFzZS4KCmBgYHtyfQpyb3NlIDwtIGRhdGEuZnJhbWUoUGNsYXNzID0gYXMuZmFjdG9yKDEpLCAKICAgICAgICAgICAgICAgICAgIFNleCA9ICJmZW1hbGUiLAogICAgICAgICAgICAgICAgICAgQWdlID0gMTcpCgpyb3NlLnByb2IgPC0gcHJlZGljdChvYmplY3QgPSBtb2RlbHMkbW9kJG1vZGVsMCwgbmV3ZGF0YSA9IHJvc2UsIHR5cGUgPSAicmVzcG9uc2UiKQoKcHJpbnQocGFzdGUoIlByb2JhYmlsaWRhZCBkZSBxdWUgUm9zZSBzb2JyZXZpdmE6Iiwgcm91bmQocm9zZS5wcm9iLDMpLCBzZXA9ICIgIikpCgpqYWNrIDwtIGRhdGEuZnJhbWUoUGNsYXNzID0gYXMuZmFjdG9yKDMpLAogICAgICAgICAgICAgICAgICAgU2V4ID0gIm1hbGUiLAogICAgICAgICAgICAgICAgICAgQWdlID0gMjApCgpqYWNrLnByb2IgPC0gcHJlZGljdChvYmplY3QgPSBtb2RlbHMkbW9kJG1vZGVsMCwgbmV3ZGF0YSA9IGphY2ssIHR5cGUgPSAicmVzcG9uc2UiKQoKcHJpbnQocGFzdGUoIlByb2JhYmlsaWRhZCBkZSBxdWUgSmFjayBzb2JyZXZpdmE6Iiwgcm91bmQoamFjay5wcm9iLDMpLCBzZXAgPSAiICIpKQpgYGAKCkNvbW8gZXJhIGRlIGVzcGVyYXIsIGxhIHByb2JhYmlsaWRhZCBkZSBzb2JyZXZpdmlyIHByZWRpY2hhIHBhcmEgSmFjayBlcyBtZW5vci4gRW4gZWwgY2FzbyBkZSBSb3NlLCBsYSBwcm9iYWJpbGlkYWQgbm8gc2UgdmUgZGlzbWludWlkYSBwb3IgbG9zIGNvZWZpY2llbnRlcyBkZSBsYXMgdmFpcmJhbGVzICpQY2xhc3MqIG5pICAqU2V4Kiwgc2lubyBzb2xvIHBvciBlbCBkZSAqQWdlKi4gSmFjaywgZW4gY2FtYmlvLCBubyBzb2xvIHNlIHZlIGFmZWN0YWRvIHBvciBlc3RlIMO6bHRpbW8gKHkgZW4gbWF5b3IgbWVkaWRhIGRhZG8gcXVlIHN1IGVkYWQgZXMgbWF5b3IpLCBzaW5vIHRhbWJpw6luIHBvciBsb3MgY29lZmljaWVudGVzIGFzb2NpYWRvcyBhICpQY2xhc3MzKiAoKlBjbGFzcyogYWwgYWRvcHRhciBlbCB2YWxvciAzKSB5ICpTZXhtYWxlKiAoKlNleCogY3VhbmRvIHRvbWEgZWwgdmFsb3IgKm1hbGUqKSwgcXVlIHRpZW5lbiB1bmEgaW5mbHVlbmNpYSBuZWdhdGl2YSBlbiBsYSBwcm9iYWJpbGlkYWQgcHJlZGljaGEgcGFyYSDDqWwuCgpBbmFsaWNlbW9zIGJyZXZlbWVudGUgZWwgcmVzdG8gZGUgbG9zIG1vZGVsb3MuIAoKYGBge3J9Cm1vZGVscyAlPiUgCiAgZmlsdGVyKGdyZXBsKCcxfDJ8MycsIG1vZGVscykpICU+JSAKICBtdXRhdGUodGlkeSA9IG1hcChtb2QsdGlkeSkpICU+JSAgCiAgdW5uZXN0KHRpZHksIC5kcm9wID0gVFJVRSkgJT4lIAogIG11dGF0ZShlc3RpbWF0ZT1yb3VuZChlc3RpbWF0ZSw1KSwKICAgICAgICAgcC52YWx1ZT1yb3VuZChwLnZhbHVlLDQpKQpgYGAKCi0gKipNb2RlbG8gMToqKiB0YW50byBsYSB2YXJpYWJsZSAqRmFyZSogY29tbyAqU2V4KiByZXN1bHRhbiBzaWduaWZpY2F0aXZhcy4gTWllbnRyYXMgcXVlIGxhIHZhcmlhYmxlICpGYXJlKiAobGEgdGFyaWZhIHBhZ2FkYSBwb3IgZWwgcGFzYWplKSwgYWwgYXVtZW50YXIsIGluY3JlbWVudGEgbGEgcHJvYWJpbGlkYWQgZGUgc29icmV2aXZpciwgbGEgdmFyaWFibGUgKlNleCogcHJvZHVjZSBlbGUgZmVjdG8gY29udHJhcmlvIGFsIHRvbWFyIGVsIHZhbG9yICptYWxlKi4KCi0gKipNb2RlbG8gMjoqKiBhcXXDrSB0b2RhcyBsYXMgdmFyaWFibGVzLCBhIGV4Y2VwY2nDs24gZGUgKkZhcmUqLCByZXN1bHRhbiBzaWduaWZpY2F0aXZhcy4gRWwgY29lZmljaWVudGUgYXNvY2lhZG9zIGEgKkZhcmUqIHByZXNlbnRhIHVuIHZhbG9yIG1heW9yIGEgMCwwNSwgbG8gcXVlIGluZGljYSBxdWUgZXN0YSB2YXJpYWJsZSBubyBhcG9ydGEgaW5mb3JtYWNpw7NuIGFsIG1vZGVsby4gRW4gZWwgY2FzbyBkZSAqUGNsYXNzKiwgKlNleCogeSAqQWdlKiBzZSBvYnNlcnZhIHVuYSBpbmZsdWVuY2lhIGVuIGxhIHByb2JhYmlsaWRhZCBzaW1pbGFyIGEgbGEgZW5jb250cmFkYSBlbiBlbCBtb2RlbG8gMC4KCi0gKipNb2RlbG8gMzoqKiBhcXXDrSBsYSB2YXJpYWJsZSAqU2V4KiBhcG9ydGEgaW5mcm9tYWNpw7NuIHBlcm8gbGEgdmFyaWFibGUgKlBhcmNoKiBuby4gRW4gZWwgY2FzbyBkZSAqU2V4KiwgdGllbmUgbGEgbWlzbWEgaW5mbHVlbmNpYSBzb2JyZSBsYSBwcm9iYWJpbGlkYWQgcHJlZGljaGEgcXVlIGxhIHF1ZSB0ZW7DrWEgZW4gZWwgbW9kZWxvIDA6IGFsIGFkb3B0YXIgZWwgdmFsb3IgKm1hbGUqLCByZWR1Y2UgbGEgcHJvYmFiaWxpZGFkLiAqUGFyY2gqLCBwb3Igc3UgcGFydGUsIGluZmx1eWUgbmVnYXRpdmFtZW50ZSBhbCBhdW1lbnRhciBzdSB2YWxvci4KCkEgY29udGludWFjacOzbiwgc2Ugb3JkZW5hbiBsb3MgbW9kZWxvcyBwb3IgZGV2aWFuY2UgZXhwbGljYWRhLiBQb2RlbW9zIHZlciBxdWUgZWwgbW9kZWxvIHF1ZSBtaW5pbWl6YSBsYSBkZXZpYW5jZSBlcyBlbCBtb2RlbG8gMiwgbG8gcXVlIGhhY2UgcXVlIHN1IHBvcmNlbnRhamUgZGUgZGV2aWFuY2UgZXhwbGljYWRhIHNlYSBtYXlvciBhbCByZXN0bzogYXByb3hpbWFkYW1lbnRlIDMwJS4KCmBgYHtyfQptb2RlbHMgPC0gbW9kZWxzICU+JSAKICBtdXRhdGUoZ2xhbmNlID0gbWFwKG1vZCxnbGFuY2UpKQoKbW9kZWxzICU+JSAKICB1bm5lc3QoZ2xhbmNlLCAuZHJvcCA9IFRSVUUpICU+JQogIG11dGF0ZShwZXJjX2V4cGxhaW5lZF9kZXYgPSAxLWRldmlhbmNlL251bGwuZGV2aWFuY2UpICU+JSAKICBzZWxlY3QoLWMobW9kZWxzLCBkZi5udWxsLCBBSUMsIEJJQykpICU+JSAKICBhcnJhbmdlKGRldmlhbmNlKQpgYGAKCiMgNC4gRXZhbHVhY2nDs24gZGVsIG1vZGVsbwoKRW4gZXN0ZSBhcGFydGFkbyBwcm9jZWRlbW9zIGEgZXZhbHVhciBlbCBtZWpvciBtb2RlbG8gYWp1c3RhZG8gZW4gZWwgYXBhcnRhZG8gYW50ZXJpb3IsIGVzdG8gZXMsIGVsIG1vZGVsbyAyLiAKCkVuIHByaW1lciBsdWdhciwgcmVhbGl6YW1vcyB1bmEgY3VydmEgUk9DLiBFbiBlbGxhIGdyYWZpY2Ftb3MgZWwgUmF0aW8gZGUgVmVyZGFkZXJvcyBOZWdhdGl2b3MgbyAqc3BlY2lmaWNpdHkqIGVuIGVsIGVqZSBkZSBhYnNjaXNhcyB5IGVsIFJhdGlvIGRlIFZlcmRhZGVyb3MgUG9zaXRpdm9zIG8gKnNlbnNpdGl2aXR5KiBlbiBlbCBlamUgZGUgb3JkZW5hZGFzLCBjdXlhcyBmw7NybXVsYXMgc29uOgoKJHNlbnNpdGl2aXR5ID0gXGZyYWN7VFB9e1RQK0ZOfSQKCiRzcGVjaWZpY2l0eSA9IFxmcmFje1ROfXtUTitGUH0kCgpEb25kZTogCgogIC0gVFAgPSB0cnVlIHBvc2l0aXZlIG8gdmVyZGFkZXJvcyBwb3NpdGl2b3MgCiAgKGNhc29zIHF1ZSBlbCBtb2RlbG8gcHJlZGlqbyBjb21vIHBvc2l0aXZvcyB5IHJlYWxtZW50ZSBsbyBlcmFuKQogIC0gRlAgPSBmYWxzZSBwb3NpdGl2ZSBvIGZhbHNvcyBwb3NpdGl2b3MKICAoY2Fzb3MgcXVlIGVsIG1vZGVsbyBwcmVkaWpvIGNvbW8gcG9zaXRpdm9zIHkgZW4gcmVhbGlkYWQgZXJhbiBuZWdhdGl2b3MpCiAgLSBUTiA9IHRydWUgbmVnYXRpdmUgbyB2ZXJkYWRlcm5vcyBuZWdhdGl2b3MKICAoY2Fzb3MgcXVlIGVsIG1vZGVsbyBwcmVkaWpvIGNvbW8gbmVnYXRpdm9zIHkgcmVhbG1lbnRlIGxvIGVyYW4pCiAgLSBGTiA9IGZhbHNlIG5lZ2F0aXZlIG8gZmFsc29zIG5lZ2F0aXZvcwogIChjYXNvcyBxdWUgZWwgbW9kZWxvIHByZWRpam8gY29tbyBuZWdhdGl2b3MgeSBlbiByZWFsaWRhZCBlcmFuIHBvc2l0aXZvcykKICAKCkxhICpzZW5zaXRpdml0eSosIGEgbGEgY3VhbCB0YW1iacOpbiBzZSBsYSBzdWVsZSBkZW5vbWluYXIgKnJlY2FsbCosIGluZGljYSBsYSBjdcOhbnRvcyBjYXNvcyByZWFsbWVudGUgcG9zaXRpdm9zIHNlIGxvZ3Jhcm9uIHByZWRlY2lyIGNvcnJlY3RhbWVudGUsIGVzIGRlY2lyLCBjdcOhbnRhIGNvYmVydHVyYSB0aWVuZSBlbCBtb2RlbG8uIFBhcmEgZWxsbywgc2UgY29uc2lkZXJhIGVsIHRvdGFsIGRlIGNhc29zIHF1ZSBlbCBtb2RlbG8gcHJlZGlqbyBjb21vIHBvc2l0aXZvcyB5IHF1ZSByZWFsbWVudGUgbG8gc29uIChUUCkgc29icmUgZWwgdG90YWwgZGUgcG9zaXRpdm9zIHJlbGFlcyAoVFArRk4pLCBoYXlhbiBzaWRvIHByZWRpY2hvcyBjb21vIHRhbGVzIHBvciBlbCBtb2RlbG8gKFRQKSBvIGhheWFuIHNpZG8gY2F0YWxvZ2Fkb3MgZXJyw7NuZW1hbmV0ZSBjb21vIG5lZ2F0aXZvcyAoRk4pLiBFbiBlc3RlIHRyYWJham8sIGxhIGNsYXNlIHBvc2l0aXZhIHNvbiBsb3Mgc29icmV2aXZpZW50ZXMuIAoKTGEgKnNwZWNpZmljaXR5KiwgcG9yIG90cm8gbGFkbywgaW5kaWNhIGxvIG1pc21vIHBlcm8gY29uIHJlc3BlY3RvIGEgbG9zIG5lZ2F0aXZvczogY3XDoW50b3MgY2Fzb3MgcXVlIGVuIHJlYWxpZGFkIGVyYW4gbmVnYXRpdm9zIGZ1ZXJvbiBwcmVkaWNob3MgZGUgZXN0YSBmb3JtYSBwb3IgbnVlc3RybyBtb2RlbG8uIEFxdcOtIGxhIGN1ZW50YSBxdWUgc2UgcmVhbGl6YSB0b21hIGxvcyBuZWdhdGl2b3MgcmVhbGVzIHF1ZSBmdWVyb24gcHJlZGljaG9zIGRlIHRhbCBmb3JtYSAoVE4pIHkgbG9zIGRpdmlkZSBwb3IgZWwgdG90YWwgcmVhbCBkZSBuZWdhdGl2b3MgKFROK0ZQKS4gRW4gZXN0ZSB0cmFiYWpvLCBsb3MgY2Fzb3MgbmVndGl2b3MgY29ycmVzcG9uZGVuIGEgbG9zICJubyBzb2JyZXZpdmllbnRlcyIuCgpBZGVtw6FzLCBwb2RlbW9zIGNhbGN1bGFyIGVsIMOhcmVhIGJham8gbGEgY3VydmEgZ3JhZmljYWRhLiBFbCBtb2RlbG8gMiBjdWVudGEgY29uIHVuIMOhcmVhIGRlbCA4MyUsIHBvciBsbyBxdWUgc3VzIHByZWRpY2Npb25lcyBzdXBlcmFuIGxhcyBkZWwgYXphciwgcmVwcmVzZW50YWRvIHBvciBsYSBsw61uZWEgcHVudGVhZGEgeSBjdXlhIMOhcmVhIGVzIGRlbCA1MCUuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbW9kZWxzIDwtIG1vZGVscyAlPiUgCiAgbXV0YXRlKHByZWQ9IG1hcChtb2QsYXVnbWVudCwgdHlwZS5wcmVkaWN0ID0gInJlc3BvbnNlIikpCgpwcmVkaWN0aW9uIDwtIG1vZGVscyAlPiUgCiAgZmlsdGVyKGdyZXBsKCcyJywgbW9kZWxzKSkgJT4lIAogIHVubmVzdChwcmVkLCAuZHJvcD1UUlVFKQogCnJvY19kYXRhIDwtIHJvYyhyZXNwb25zZT1wcmVkaWN0aW9uJFN1cnZpdmVkLCBwcmVkaWN0b3I9cHJlZGljdGlvbiQuZml0dGVkKQoKZ2dyb2Mocm9jX2RhdGEsIHNpemU9MSwgY29sb3I9InR1cnF1b2lzZTMiKSArIAogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMSwgbGluZXR5cGU9J2Rhc2hlZCcpICsKICB0aGVtZV9idygpICsgCiAgbGFicyh0aXRsZT0nQ3VydmEgUk9DJywgCiAgICAgICBzdWJ0aXRsZSA9ICdNb2RlbG8yOiBTdXJ2aXZlZCB+IFBjbGFzcyArIEZhcmUgKyBTZXggKyBBZ2UnKQoKYGBgCgpgYGB7cn0KcHJpbnQocGFzdGUoJ0FVQzogTW9kZWxvMjogU3Vydml2ZWR+UGNsYXNzK0VtYmFya2VkK1NleCtBZ2U6Jywgcm9jX2RhdGEkYXVjKSkKYGBgCgpHcmFmaWNhbW9zIHRhbWJpw6luIHVuIHZpb2xpbiBwbG90IHF1ZSBub3MgcGVybWl0YSB2ZXIgbGEgZGlzdHJpYnVjacOzbiBkZSBsYXMgY2xhc2VzICJzb2JyZXZpdmllbnRlIiB5ICJubyBzb2JyZXZpdmllbnRlIiBlbiByZWxhY2nDs24gYSBsYSBwcm9iYWJpbGlkYWQgcHJlZGljaGEgcG9yIGVsIG1vZGVsby4KCkVuIMOpbCBzZSBwdWVkZSBvYnNlcnZhciBxdWUsIGEgbWVkaWRhIHF1ZSBsYSBwcm9iYWJpbGlkYWQgYXNpZ25hZGEgc2UgaW5jcmVtZW50YSwgbGEgY2xhc2UgIm5vIHNvYnJldml2aWVudGUiIGRpc21pbnV5ZSBzdSBkZW5zaWRhZCB5IGxhIGNsYXNlICJzb2JyZXZpdmllbnRlIiBsYSBhdW1lbnRhLiBFc3RvIG5vcyBheXVkYSBhIHBlbnNhciB1biBwb3NpYmxlIHB1bnRvIGRlIGNvcnRlIGEgcGFydGlyIGRlbCBjdWFsIHBvZGVtb3MgZXN0YWJsZWNlciBxdWUgdW5hIHBlcnNvbmEgc29icmV2aXZlLCBpbnRlbnRhbmRvIG1pbmltaXphciBhZGVtw6FzIGVsIGVycm9yIGRlIHByZWRpY2Npw7NuIChpLmUuIGNhdGFsb2dhciBhIHVuICJubyBzb2JyZXZpdmllbnRlcyIgY29tbyAic29icmV2aXZpZW50ZSIgbyB2aWNldmVyc2EpLiAKCkRhZG8gcXVlIGVsIHF1aWVicmUgZW50cmUgZGlzbWludWNpw7NuIGRlIGxhIGRlbnNpZGFkIGRlICJubyBzb2JyZXZpdmllbnRlcyIgeSBhdW1lbnRvIGRlIGxhIGRlbnNpZGFkIGRlICJzb2JyZXZpdmllbnRlcyIgcGFyZWNlIGVzdGFyIGVudHJlIDAuMzAgeSAwLjQ1LCBwb2Ryw61hIHBlbnNhcnNlIHVuIHB1bnRvIGRlIGNvcnRlIHF1ZSBlc3TDqSBlbiBlc2UgaW50ZXJ2YWxvLgoKYGBge3J9CmdncGxvdChwcmVkaWN0aW9uLCBhZXMoeD1TdXJ2aXZlZCwgeT0uZml0dGVkLCBncm91cD1TdXJ2aXZlZCxmaWxsPWZhY3RvcihTdXJ2aXZlZCkpKSArIAogIGdlb21fdmlvbGluKCkgKwogIHRoZW1lX2J3KCkgKwogIGd1aWRlcyhmaWxsPUZBTFNFKSArCiAgc2NhbGVfeF9kaXNjcmV0ZSgiU3VwZXJ2aXZlbmNpYSIsIGJyZWFrcyA9IGMoIjAiLCIxIiksCiAgICAgICAgICAgICAgICAgICBsYWJlbHM9YygiTm8gc29icmV2aXZpZW50ZXMiLCJTb2JyZXZpdmllbnRlcyIpKSArCiAgbGFicyh0aXRsZT0nVmlvbGluIHBsb3QnLCAKICAgICAgIHN1YnRpdGxlID0gJ01vZGVsbzI6IFN1cnZpdmVkIH4gUGNsYXNzICsgRmFyZSArIFNleCArIEFnZScsIAogICAgICAgeT0nUHJvYmFiaWxpZGFkIHByZWRpY2hhJykKYGBgCgojIDUuIEVsZWNjacOzbiBkZWwgcHVudG8gZGUgY29ydGUKCkVuIGVzdGUgYXBhcnRhZG8gcHJvY2VkZXJlbW9zIGEgcHJvYmFyIG51ZXN0cm8gbW9kZWxvIGNvbiBsb3MgZGF0b3MgcXVlIGhlbW9zIHJlc2VydmFkbyBwYXJhIGxhIHZhbGlkYWNpw7NuIHksIGEgcGFydGlyIGRlIGVzb3MgcmVzdWx0YWRvcywgZWxlZ2lyZW1vcyB1biBwdW50byBkZSBjb3J0ZSBxdWUgY29uc2lkZXJlbW9zIGFwcm9waWFkbyBwYXJhIHByZWRlY2lyIHNpIGFsZ3VpZW4gc29icmV2aWRlLgoKYGBge3J9CiMgc2UgdGVzdGVhbiBlbCBtb2RlbG8gY29uIGxvcyBkYXRvcyBkZSBWQUxJREFDScOTTiByZXNlcnZhZG9zCnZhbGlkIDwtIG1vZGVscyAlPiUgCiAgZmlsdGVyKGdyZXBsKCcyJyxtb2RlbHMpKSAlPiUgCiAgbXV0YXRlKHZhbD0gbWFwKG1vZCxhdWdtZW50LCBuZXdkYXRhPXZhbGlkYXRpb25zZXQsIHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpKQoKdmFsaWQucmVzdWx0cyA8LSAgdmFsaWQgJT4lCiAgdW5uZXN0KHZhbCwgLmRyb3A9VFJVRSkKCnZhbGlkLnJlc3VsdHMKYGBgCgpQYXJhIGF5dWRhcm5vcywgcmVhbGl6YW1vcyB1biBncsOhZmljbyBkb25kZSBzZSB2aXN1YWxpY2UgKmFjY3VyYWN5KiwgKnByZWNpc2lvbiosICpyZWNhbGwqIHkgKnNwZWNpZmljaXR5Ki4KCllhIGhlbW9zIGhhYmxhZG8gZGUgbG9zIMO6bHRpbW9zIGRvcywgZGV0ZW5nw6Ftb25vcyB1biBtb21lbnRvIGVuIGxvcyBwcmltZXJvczoKCiRhY2N1cmFjeSA9IFxmcmFje1RQK1ROfXtUUCtGUCtUTitGTn0kCgokcHJlY2lzaW9uID0gXGZyYWN7VFB9e1RQK0ZQfSQKCkVsICphY2N1cmFjeSogcGVybWl0ZSBtZWRpciBsYSBwZXJmb3JtYW5jZSBnZW5lcmFsIGRlbCBtb2RlbG8uIFBhcmEgZWxsbywgc2UgZGViZSBjYWxjdWxhciBjdcOhbnRvcyBjYXNvcyBmdWVyb24gcHJlZGljaG9zIGNvcnJlY3RhbWVudGUsIHNlYW4gZXN0b3MgcG9zaXRpdm9zIChhcXXDrSwgInNvYnJldml2aWVudGVzIikgbyBuZWdhdGl2b3MgKCJubyBzb2JyZXZpdmllbnRlcyIpLCB5IHJlbGFjaW9uYXJsb3MgY29uIGVsIHRvdGFsIGRlIGNhc29zIChUUCtGUCtUTitGTiksIGRlIG1vZG8gcXVlIGxhIGN1ZW50YSByZXN1bHRhbnRlIHJlZmxlamEgcHJvcG9yY2nDs24gZGUgbG9zIHJlZ2lzdHJvcyBmdWUgcHJlZGljaGEgZGUgbWFuZXJhIGNvcnJlY3RhIHJlc3BlY3RvIGRlbCB0b3RhbC4KCkxhICpwcmVjaXNpb24qLCBhIHN1IHZleiwgaW5kaWNhIGN1w6FuIGV4YWN0byBlcyBlbCBtb2RlbG8sIGRhZG8gcXVlIHBvZHLDrWEgb2N1cnJpciBxdWUgw6lzdGUgY2FwdHVyYXNlIGNvcnJlY3RhbWVudGUgbG9zIGNhc29zIHBvc2l0aXZvcyBwZXJvIGEgY29zdGEgZGUgcHJlZGVjaXIgdGFtYmnDqW4gY29tbyBwb3NpdGl2b3MgbG9zIHJlZ2lzdHJvcyBuZWdhdGl2b3MuIFBhcmEgbWVkaXIgZXN0byBlcyBxdWUgc2UgdXNhIGxhICpwcmVjaXNpb24qLCBxdWUgY2FsY3VsYSBsYSBjYW50aWRhZCBkZSB2ZXJkYWRlcm9zIHBvc2l0aXZvcyBxdWUgcHJlZGlqbyBlbCBtb2RlbG8gc29icmUgbGEgY2FudGlkYWQgdG90YWwgZGUgcG9zaXRpdm9zIHByZWRpY2hvcywgc2VhbiBlc3RvcyByZWFsbWVudGUgcG9zaXRpdm9zIG8gbm8uCgpgYGB7cn0KIyBmdW5jacOzbiBxdWUgY2FsY3VsYSBsYXMgbcOpdHJpY2FzIGRlIGxhcyBwcmVkaWNjaW9uZXMgZGUgYWN1ZXJkbyBhIGxvcyBwdW50b3MgZGUgY29ydGUgZXN0YWJsZWNpZG9zCnByZWRpY3Rpb25fbWV0cmljcyA8LSBmdW5jdGlvbihjdXRvZmYsIHByZWRpY3Rpb25zPXZhbGlkLnJlc3VsdHMpewogIHRhYmxlIDwtIHByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShwcmVkaWN0ZWRfY2xhc3M9aWZfZWxzZSguZml0dGVkPmN1dG9mZiwgMSwgMCkgJT4lIGFzLmZhY3RvcigpLAogICAgICAgICAgIFN1cnZpdmVkPSBmYWN0b3IoU3Vydml2ZWQpKQogIGNvbmZ1c2lvbk1hdHJpeCh0YWJsZSh0YWJsZSRwcmVkaWN0ZWRfY2xhc3MsIHRhYmxlJFN1cnZpdmVkKSwgcG9zaXRpdmUgPSAiMSIpICU+JQogICAgdGlkeSgpICU+JQogICAgc2VsZWN0KHRlcm0sIGVzdGltYXRlKSAlPiUKICAgIGZpbHRlcih0ZXJtICVpbiUgYygnYWNjdXJhY3knLCAnc2Vuc2l0aXZpdHknLCAnc3BlY2lmaWNpdHknLCAncHJlY2lzaW9uJywncmVjYWxsJykpICU+JQogICAgbXV0YXRlKGN1dG9mZj1jdXRvZmYpCn0KCiMgc2UgZGVmaW5lbiBwdW50b3MgZGUgY29ydGUKY3V0b2ZmcyA9IHNlcSgwLjA1LDAuOTUsMC4wMSkKbG9naXRfcHJlZD0gbWFwX2RmcihjdXRvZmZzLCBwcmVkaWN0aW9uX21ldHJpY3MpICU+JSBtdXRhdGUodGVybT1hcy5mYWN0b3IodGVybSkpCgojIHNlIHNlbGVjY2lvbmFuIGxvcyBwdW50b3MgZGUgY29ydGUgY29uIG1heW9yIGFjY3VyYWN5CmN1dG9mZi5tYXhfYWMgPC0gbG9naXRfcHJlZCAlPiUgCiAgZmlsdGVyKHRlcm09PSdhY2N1cmFjeScpICU+JSAKICBmaWx0ZXIoZXN0aW1hdGUgPT0gbWF4KGVzdGltYXRlKSkgJT4lIAogIHNlbGVjdChjdXRvZmYpIAoKZ2dwbG90KGxvZ2l0X3ByZWQsIGFlcyhjdXRvZmYsZXN0aW1hdGUsIGdyb3VwPXRlcm0sIGNvbG9yPXRlcm0pKSArIGdlb21fbGluZShzaXplPTEpICsKICB0aGVtZV9idygpICsKICBsYWJzKHRpdGxlPSAnQWNjdXJhY3ksIFNlbnNpdGl2aXR5LCBTcGVjaWZpY2l0eSwgUmVjYWxsIHkgUHJlY2lzaW9uJywgc3VidGl0bGU9ICdNb2RlbDIgLSBTb2JyZSBjb25qdW50byBkZSB2YWxpZGFjacOzbicsIGNvbG9yPSIiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWluKGN1dG9mZi5tYXhfYWMpLCBsaW5ldHlwZT0iZG90dGVkIiwgIGNvbG9yID0gImJsYWNrIiwgc2l6ZT0xKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gbWF4KGN1dG9mZi5tYXhfYWMpLCBsaW5ldHlwZT0iZG90dGVkIiwgIGNvbG9yID0gImJsYWNrIiwgc2l6ZT0xKQpgYGAKCkVuIGVsIGdyw6FmaWNvIHNlIG9ic2VydmEgcXVlIGVsIG1lam9yIGFjY3VyYWN5IHNlIG9idGllbmUgZW50cmUgbG9zIHB1bnRvcyBkZSBjb3J0ZSAwLjQ1IHkgMC40Ni4gQSBwYXJ0aXIgZGUgYWxsw60sIGxhIGzDrW5lYSBxdWUgbWFyY2EgZWwgYWNjdXJhY3kgY29taWVuemEgYSBkZWNhZXIuIAoKVGFtYmnDqW4gZXMgY2VyY2EgZGUgZXNlIHB1bnRvIGRvbmRlIHNlIGludGVyc2VjdGFuIGxhcyBsw61uZWFzIGRlIHByZWNpc2lvbiB5IHJlY2FsbCAobyBzZW5zaXRpdml0eSkuIEVzdGEgaW50ZXJzZWNjacOzbiBkYSBjdWVudGEgZGUgbGEgY29tcGVuc2FjacOzbiBvIHRlbnNpw7NuIGVuIGxhIHF1ZSBzZSBlbmN1ZW50cmFuIGxhIGNvYmVydHVyYSB5IGxhIGV4YWN0aXR1ZCBkZSB1biBtb2RlbG86IGxhIGNhcGFjaWRhZCBkZSBwcmVkZWNpciBjb3JyZWN0YW1lbnRlIG1heW9yIGNhbnRpZGFkIGRlIGNhc29zIHBvc2l0aXZvcyAoaS5lLiBkZSB0ZW5lciB1biBtYXlvciByZWNhbGwpIGVzdMOhIGFjb21wYcOxYWRhIGRlIHVuYSBtZW5vciBwcmVjaXNpw7NuLCBwb3IgbGEgY3VhbCBlbCBtb2RlbG8gcHJlZGljZSBjb21vIHBvc2l0aXZvcyByZWdpc3Ryb3MgcXVlIGVuIHJlYWxpZGFkIG5vIGxvIHNvbi4gWSBhIG1lZGlkYSBxdWUgZXN0YSBwcmVjaXNpw7NuIGF1bWVudGEsIHN1IHByZWNpc2nDs24gZGlzbWludXllLCBlcyBkZWNpciwgcHJlZGljZSBjb3JyZWN0YW1lbnRlIG1lbm9yIGNhbnRpZGFkIGRlIGNhc29zIHBvc2l0aXZvcy4KCkRlIG1hbmVyYSBzaW1pbGFyLCBsYSBzZW5zaXRpdml0eSBzZSBlbmN1ZW50cmEgZW4gdGVuc2nDs24gY29uIGxhIHNwZWNpZmljaXR5OiBlbiB0YW50byBsYSBwcmltZXJhIGRpc21pbnV5ZSwgbGEgc2VndW5kYSBhdW1lbnRhIGNvbW8gY29uc2VjdWVuY2lhIGRlIHF1ZSBlbCBtb2RlbG8sIGFsIGluY3JlbWVudGFyIGxvcyBmYWxzb3MgcG9zaXRpdm9zIHksIGVuIGNvbnNlY3VlbmNpYSwgcHJlZGVjaXIgbWVub3IgY2FudGlkYWQgZGUgY2Fzb3MgcG9zaXRpdm9zIGNvcnJlcmN0YW1lbnRlLCByZWR1Y2UgdGFtYmnDqW4gbGFzIHByZWRpY2Npb25lcyBuZWdhdGl2YXMgcXVlIHZlcmRhZGVyYW1lbnRlIHNvbiBkZSBlc3RhIGNsYXNlLgoKVW4gcHVudG8gZGUgY29ydGUgYWRlY3VhZG8gcGFyYSBwcmVkZWNpciBzaSB1bmEgcGVyc29uYSBlcyBvIG5vIHNvYnJldml2aWVudGUgZGViZXLDrWEgbG9ncmFyIGNpZXJ0YSBhcm1vbsOtYSBlbnRyZSBlc3RhcyB0ZW5zaW9uZXMuIEVuIGVzdGUgdHJhYmFqbyBlbGVnaW1vcyBjb21vIHB1bnRvIGRlIGNvcnRlIGVsIHZhbG9yIDAuNDYgcG9yIHNlciBhcXVlbCBxdWUgbG9ncmEgZWwgbWF5b3IgYWNjdXJhY3kgY29uIGVsIGNvbmp1bnRvIGRlIHZhbGlkYWNpw7NuIHkgZW5jb250cmFyc2UgbcOhcyBjZXJjYW5vIGEgbG9zIHB1bnRvcyBkb25kZSBzZSBpbnRlcnNlY3RhbiByZWNhbGwsIHByZWNpc2lvbiB5IHNwZWNpZmljaXR5LgoKQSBjb250aW51YWNpw7NuLCByZWFsaXphbW9zIGxhIG1hdHJpeiBkZSBjb25mdXNpw7NuIG9idGVuaWRhIGNvbiB0YWwgcHVudG8gZGUgY29ydGUuCgpgYGB7cn0gCiMgc2UgZXN0YWJsZWNlIGVsIHB1bnRvIGRlIGNvcnRlCnNlbF9jdXRvZmYgPSBtYXgoY3V0b2ZmLm1heF9hYykKCnRhYmxlIDwtIHZhbGlkLnJlc3VsdHMgJT4lIAogIG11dGF0ZShwcmVkaWN0ZWRfY2xhc3M9aWZfZWxzZSguZml0dGVkPnNlbF9jdXRvZmYsIDEsIDApICU+JSBhcy5mYWN0b3IoKSwgCiAgICAgICAgIFN1cnZpdmVkID0gZmFjdG9yKFN1cnZpdmVkKSkKCiMgc2UgY3JlYSBsYSBtYXRyaXogZGUgY29uZnVzacOzbgpjb25mdXNpb25NYXRyaXgodGFibGUodGFibGUkU3Vydml2ZWQsIHRhYmxlJHByZWRpY3RlZF9jbGFzcyksIHBvc2l0aXZlID0gIjEiKQpgYGAKCkVzdGEgbWF0cml6IG5vcyBwZXJtaXRlIHZpc3VhbGl6YXIgbGEgY2FudGlkYWQgZXNwZXJhZGEgZGUgdmFsb3JlcyBwb3NpdGl2b3MgeSBuZWdhdGl2b3MgeSBsYXMgcHJlZGljY2lvbmVzIHJlYWxpemFkYXMgcG9yIGVsIG1vZGVsbyBwYXJhIGRpY2hhcyBjbGFzZXMgZGVsIHNpZ3VpZW50ZSBtb2RvOgoKfCAgICAgICAgICAgICAgICAgICB8IE5lZ2F0aXZvcyBlc3BlcmFkb3MgfCBQb3NpdGl2b3MgZXNwZXJhZG9zIHwKfC0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLXwKfE5lZ2F0aXZvcyBwcmVkaWNob3N8VmVyZGFkZXJvcyBuZWdhdGl2b3MgfCAgRmFsc29zIG5lZ2F0aXZvcyAgIHwKfFBvc2l0aXZvcyBwcmVkaWNob3N8ICBGYWxzb3MgcG9zaXRpdm9zICAgfFZlcmRhZGVyb3MgcG9zaXRpdm9zIHwKCkFzw60sIHBvZGVtb3MgdmVyIHF1ZSBudWVzdHJvIG1vZGVsbyBwcmVkaWNlIGFkZWN1YWRhbWVudGUgMTQxIGNhc29zIG5lZ2F0aXZvcyB5IDgzIHBvc2l0aXZvcywgeSBoYXkgMjUgY2Fzb3MgcXVlIGNhdGFsb2dhIGNvbW8gbmVnYXRpdm9zIGN1YW5kbyBlbiByZWFsaWRhZCBzb24gcG9zaXRpdm9zIHkgMTkgY29uIGxvcyBxdWUgc3VjZWRlIGxvIGNvbnRyYXJpby4KCiMgNi4gVGVzdGVvCgpFbiBlc3RlIGFwYXJ0YWRvIHByb2NlZGVtb3MgYSB0ZXN0ZWFyIGVsIG1vZGVsbyBzZWxlY2Npb25hZG8gY29uIHVuIG51ZXZvIGRhdGFzZXQgbm8gdXRpbGl6YWRvIHBhcmEgZW50cmVuYW1pZW50byBuaSB0ZXN0ZW8uIAoKQ2FyZ2Ftb3MgZWwgZGF0YXNldCB5IHJlYWxpemFtb3MgZWwgbWlzbW8gcHJlcHJvY2VzYW1pZW50byBxdWUgaGljaW1vcyBjb24gZWwgY29uanVudG8gZGUgZW50cmVuYW1pZW50bzogY29udmVydGltb3MgZW4gZmFjdG9yIGxhcyB2YXJpYWJsZXMgKlN1cnZpdmVkKiwgKlBjbGFzcyogeSAqRW1iYXJrZWQqLgoKRWwgY29uanVudG8gZGUgdGVzdGVvIHV0aWxpemFkbyBjdWVudGEgY29uIDQxOCByZWdpc3Ryb3MgeSBwcmVzZW50YSB1bmEgZGlzdHJpYnVjacOzbiBkZSBjbGFzZXMgc2ltaWxhciBhIGFxdWVsbGEgY29uIGxhIGN1YWwgZW50cmVuYW1vcyBlbCBtb2RlbG8uCgpgYGB7cn0KIyBzZSBjYXJnYSBlbCBjb25qdW50byBkZSB0ZXN0ZW8KdGVzdHNldCA8LSBmcmVhZCgidGl0YW5pY19jb21wbGV0ZV90ZXN0LmNzdiIpCgojIHNlIGNvbnZpZXJ0ZW4gbGFzIHZhcmlhYmxlcyBhIGZhY3RvcmVzCnRlc3RzZXQgPC0gdGVzdHNldCAlPiUgCiAgc2VsZWN0KFBhc3NlbmdlcklkLCBTdXJ2aXZlZCwgUGNsYXNzLCBTZXgsIEFnZSwgU2liU3AsIFBhcmNoLCBGYXJlLCBFbWJhcmtlZCkgJT4lIAogIG11dGF0ZShTdXJ2aXZlZCA9IGFzX2ZhY3RvcihTdXJ2aXZlZCksCiAgICAgICAgIFBjbGFzcyA9IGFzX2ZhY3RvcihQY2xhc3MpLAogICAgICAgICBFbWJhcmtlZCA9IGFzX2ZhY3RvcihFbWJhcmtlZCkpCgpnbGltcHNlKHRlc3RzZXQpCmBgYAoKYGBge3J9CiMgc2UgY2FsY3VsYSBlbCBwb3JjZW50YWplIGRlIHNvYnJldml2aWVudGVzIGVuIGVsIGNvbmp1bnRvIGRlIHRlc3RlbwpzdXJ2LnRlc3RzZXQgPC0gbnJvdyh0ZXN0c2V0W3Rlc3RzZXQkU3Vydml2ZWQ9PTEsXSkKZGlzdC50ZXN0c2V0IDwtIHJvdW5kKChzdXJ2LnRlc3RzZXQvbnJvdyh0ZXN0c2V0KSkqMTAwLDApCnByaW50KHBhc3RlKCJQb3JjZW50YWplIGRlIHNvYnJldml2aWVudGVzIGVuIGVsIGNvbmp1bnRvIGRlIHRlc3RlbzoiLCBkaXN0LnRlc3RzZXQsIiUiKSkKYGBgCgpEYWRvIHF1ZSB5YSBoZW1vcyBlbGVnaWRvIGVsIHB1bnRvIGRlIGNvcnRlIHkgcmVhbGl6YW1vcyBsYSB2YWxpZGFjacOzbiwgdm9sdmVtb3MgYSBlbnRyZW5hciBudWVzdHJvIG1vZGVsbyBjb24gdG9kbyBlbCBjb25qdW50byBkZSBlbnRyZW5hbWllbnRvLCBkZSBtb2RvIHF1ZSBlc3RlIGN1ZW50ZSBjb24gdW5hIG1heW9yIGNhbnRpZGFkIGRlIHJlZ2lzdHJvcyBwYXJhIHJlYWxpemFyIGxhcyBwcmVkaWNjaW9uZXMuCgpgYGB7cn0KIyBzZSBlbnRyZW5hIGVsIG1vZGVsbyAyIGNvbiBUT0RPUyBsb3MgZGF0b3MgcGFyYSBFTlRSRU5BTUlFTlRPCm1vZGVsMnRlc3QgPC0gZ2xtKGxvZ2l0X2Zvcm11bGFlJG1vZGVsMiwgZmFtaWx5ID0gJ2Jpbm9taWFsJywgZGF0YSA9IGRhdGFzZXQpCgojIHNlIGNhbGN1bGFuIGxhcyBwcmVkaWNjaW9uZXMgcGFyYSBlbCBjb25qdW50byBkZSBURVNURU8KdGVzdC50YWJsZSA9IGF1Z21lbnQoeD1tb2RlbDJ0ZXN0LCBuZXdkYXRhPXRlc3RzZXQsIHR5cGUucHJlZGljdD0ncmVzcG9uc2UnKSAKCiMgc2UgcmVhbGl6YSBsYSBjbGFzaWZpY2FjacOzbiBlbiBjbGFzZXMgc2Vnw7puIGVsIHB1bnRvIGRlIGNvcnRlIGVsZWdpZG8KdGVzdC50YWJsZSA9IHRlc3QudGFibGUgJT4lIAogIG11dGF0ZShwcmVkaWN0ZWRfY2xhc3M9aWZfZWxzZSguZml0dGVkPnNlbF9jdXRvZmYsIDEsIDApICU+JSBhcy5mYWN0b3IoKSwKICAgICAgICAgU3Vydml2ZWQ9IGZhY3RvcihTdXJ2aXZlZCkpCgp0ZXN0LnRhYmxlCmBgYAoKRmluYWxtZW50ZSwgcmVhbGl6YW1vcyBsYSBtYXRyaXogZGUgY29uZnVzacOzbiB5IG9idGVuZW1vcyBsYXMgbcOpdHJpY2FzIHBhcmEgbGFzIG51ZXZhcyBwcmVkaWNjaW9uZXMuCgpFcyBwb3NpYmxlIHZlciBxdWUgZWwgYWNjdXJhY3kgZGlzbWludXnDsywgbG8gcXVlIGVyYSBlc3BlcmFibGUgZGFkbyBxdWUgbGFzIHByZWRpY2Npb25lcyBzZSByZWFsaXphcm9uIHNvYnJlIHVuIGNvbmp1bnRvIGRlIGRhdG9zIG5vIHZpc3RvIHBvciBlbCBtb2RlbG8gcHJldmlhbWVudGUuIERlIHRvZG9zIG1vZG9zLCB1biA3NSUgZGUgbG9zIGNhc29zIGRlIHByZWRpY2VuIGNvcnJlY3RhbWVudGU7IGRlIGVzdG9zLCAyMDYgY29ycmVzcG9uZGVuIGEgcmVnaXN0cm9zIG5lZ2F0aXZvcyB5IHJlcHJlc2VudGFuIGVsIDgxJSBkZWwgdG90YWwgZGUgY2Fzb3MgbmVnYXRpdm9zIHkgMTEwIGNvcnJlc3BvbmRlbiBhIHJlZ2lzdHJvcyBwb3NpdGl2b3MgeSwgZGUgYWN1ZXJkbyBhIGxhIHNlbnRpdml0eSwgY29uc3RpdHV5ZW4gZWwgNjYlIGRlbCB0b3RhbCBkZSBwb3NpdGl2b3MuIAoKU2UgdmUgZGUgZXN0ZSBtb2RvIHF1ZSBudWVzdG9yIG1vZGVsbyBwcmVkaWNlIG1lam9yIGxvcyBjYXNvcyBuZWdhdGl2b3MgYW50ZXMgcXVlIGxvcyBwb3JpdGl2b3MuCgpgYGB7cn0KIyBzZSBjcmVhIGxhIG1hdHJpeiBkZSBjb25mdXNpw7NuCmNvbmZ1c2lvbk1hdHJpeCh0YWJsZSh0ZXN0LnRhYmxlJFN1cnZpdmVkLCB0ZXN0LnRhYmxlJHByZWRpY3RlZF9jbGFzcyksIHBvc2l0aXZlID0gIjEiKQpgYGAK
 

Hecho por Macarena Fernandez Urquiza

m.fernandezurquiza@gmail.com