Cargamos los datos de entrenamiento
titanic_complete_train <- read_csv("~/maestria/tps/sabado/titanic_complete_train.csv")
Parsed with column specification:
cols(
PassengerId = col_integer(),
Survived = col_integer(),
Pclass = col_integer(),
Name = col_character(),
Sex = col_character(),
Age = col_double(),
SibSp = col_integer(),
Parch = col_integer(),
Ticket = col_character(),
Fare = col_double(),
Cabin = col_character(),
Embarked = col_character()
)
glimpse(titanic_complete_train)
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, 22, 23, 24, 25, 26, 27, 2…
$ 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, 1, 0, 0, 1, 0, 0, 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, 3, 3, 1, 3, 3, 1, 1, 3, 2…
$ Name <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. John Bradley (Florence Briggs Thayer)", "Heikkinen, Miss. …
$ Sex <chr> "male", "female", "female", "female", "male", "male", "male", "male", "female", "female", "female", …
$ Age <dbl> 22.00000, 38.00000, 26.00000, 35.00000, 35.00000, 26.50759, 54.00000, 2.00000, 27.00000, 14.00000, 4…
$ 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, 1, 0, 3, 0, 0, 0, 1, 0, 0…
$ 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, 5, 0, 2, 0, 0, 0, 0, 0, 0…
$ Ticket <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", "113803", "373450", "330877", "17463", "349909", "34774…
$ Fare <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625, 21.0750, 11.1333, 30.0708, 16.7000, 26.55…
$ Cabin <chr> NA, "C85", NA, "C123", NA, NA, "E46", NA, NA, NA, "G6", "C103", NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ Embarked <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", "C", "S", "S", "S", "S", "S", "S", "Q", "S", "S", "C", …
Modificamos el dataset para ajustarlo a los requerimientos
titanic_complete_train <- titanic_complete_train %>% select(PassengerId, Survived, Pclass, Sex, Age, SibSp, Parch, Fare, Embarked)
titanic_complete_train <- titanic_complete_train %>% mutate_at(vars(Survived, Pclass, Embarked), factor)
glimpse(titanic_complete_train)
Observations: 891
Variables: 9
$ PassengerId <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 2…
$ Survived <fct> 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, 1, 0, 0, 1, 0, 0, 1, 1, 0…
$ Pclass <fct> 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, 3, 3, 1, 3, 3, 1, 1, 3, 2…
$ Sex <chr> "male", "female", "female", "female", "male", "male", "male", "male", "female", "female", "female", …
$ Age <dbl> 22.00000, 38.00000, 26.00000, 35.00000, 35.00000, 26.50759, 54.00000, 2.00000, 27.00000, 14.00000, 4…
$ 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, 1, 0, 3, 0, 0, 0, 1, 0, 0…
$ 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, 5, 0, 2, 0, 0, 0, 0, 0, 0…
$ Fare <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4583, 51.8625, 21.0750, 11.1333, 30.0708, 16.7000, 26.55…
$ Embarked <fct> S, C, S, S, S, Q, S, S, S, C, S, S, S, S, S, S, Q, S, S, C, S, S, Q, S, S, S, C, S, Q, S, C, C, Q, S…
Exploratorias
Realizamos un gráfico exploratorio completo para ver el comportamiento y las relaciones entre las variables. El color rojo designa a quienes no sobreviven y el azul a los que sí.
ggpairs(titanic_complete_train %>% select(Survived, Pclass, Sex, Age, Fare), mapping = aes(colour = Survived)) + theme(axis.text.x = element_text(angle = 90, hjust = 1)) + theme_bw()

Se observa que las variables Pclass y Sex son buenos discriminantes de Survived, mientras que Age y Fare no.
Analicemos la distribución de la clase de supervivencia
titanic_complete_train %>% group_by(Survived) %>% summarise(numero_casos=n())
El 61% de los pasajeros mueren mientras que el 39% sobreviven. Es un problema con un pequeño desbalance en las clases.
División entre training y validación
Dividimos un 70% del dataset para train y un 30% para test.
train_test <- titanic_complete_train %>% resample_partition(c(train=0.7, validacion=0.3))
train <- train_test$train %>% as_tibble()
validacion <- train_test$validacion %>% as_tibble()
Veamos la distribución de casos en cada uno:
train %>% group_by(Survived) %>% summarise(numero_casos=n())
validacion %>% group_by(Survived) %>% summarise(numero_casos=n())
La distribución de casos tanto para train como para validacion es muy similar a la original.
Problema
Queremos estimar \(P(Survived=Yes|X)=P(X)\) para cada individuo y partir de ello poder definir un punto de corte para predecir quienes son los que van a sobrevivir.
Regresión logística
Usamos la funcion logistica
Creación de modelos
Procedemos a crear los modelos a partir de estas fórmulas
models <- data_frame(logit_formulas) %>% # dataframe a partir del objeto formulas
mutate(models = names(logit_formulas), # columna con los nombres de las formulas
expression = paste(logit_formulas), # columna con las expresiones de las formulas
mod = map(logit_formulas, ~glm(.,family = 'binomial', data = train))) # En mod va a estar el modelo fiteado
Usamos family = ‘binomial’ porque estamos trabajando con un fenómeno que tiene una distribución binomial. Veamos el primero que es el que necesitamos para este punto.
models %>%
filter(models %in% c('pclass_sex_age')) %>%
mutate(tidy = map(mod,tidy)) %>% # Qué realizamos en este paso? Que va a tener esta columna?
unnest(tidy, .drop = TRUE) %>%
mutate(estimate=round(estimate,5),
p.value=round(p.value,4))
Vemos que todos los coeficientes son significativos. Recordando que un coeficiente positivo indica que frente a aumentos de dicha variable la probabilidad aumenta mientras que un coeficiente negativo indica lo contrario:
- El coeficiente del intercept implica que es muy elevada la probabilidad de supervivencia para un miembro de Pclass1 femenino (alrededor de 0.97).
- El coeficiente de Pclass2 negativo indica que disminuye la probabilidad de supervivencia respecto de Pclass1.
- El coeficiente de Pclass3 negativo indica que disminuye su probabilidad de supervivencia respecto de Pclass1 más aún que Pclass2.
- El coeficiente Sexmale negativo indica que ser hombre disminuye la probabilidad de sobrevivir.
- El coeficiente Age es muy cercano a 0, pero indica que a mayor edad, menor la probabilidad de sobrevivir (Mujeres y niños (y ricos!) primero).
Predecimos la probabilidad de sobrevivir de Rose y Jack
rose_jack <- data.frame(Pclass = c("1", "3"), Sex = c("female", "male"), Age = c(17, 20))
prediccion_rose_jack <- augment(x=models$mod$pclass_sex_age, newdata=rose_jack, type.predict='response')
prediccion_rose_jack
Como era de esperar, la probabilidad de sobrevivir de Rose era mucho mayor que la de Jack (la de Rose cercana a 1 mientras que la de Jack a 0.1).
Veamos qué modelo minimiza la deviance.
models <- models %>%
mutate(glance = map(mod,glance))
# Obtener las medidas de evaluacion de interes
models %>%
unnest(glance, .drop = TRUE) %>%
# Calculo de la deviance explicada
mutate(perc_explained_dev = 1-deviance/null.deviance) %>%
select(-c(models, df.null, AIC, BIC)) %>%
arrange(deviance)
El modelo que utiliza todas las variables es el que menor deviance obtiene y mayor porcentaje de la deviance explica.
models %>%
filter(models %in% c('todas')) %>%
mutate(tidy = map(mod,tidy)) %>% # Qué realizamos en este paso? Que va a tener esta columna?
unnest(tidy, .drop = TRUE) %>%
mutate(estimate=round(estimate,5),
p.value=round(p.value,4))
En este modelo el único coeficiente significativo nuevo que se agrega es EmbarkedS, y nuevamente ser de primera clase y sexo femenino da una altísima probabilidad de sobrevivir, mientras que cualquier otra situación la disminuye.
Predecimos con entrenamiento y curvas ROC
# Añadimos las predicciones
models <- models %>%
mutate(pred = map(mod,augment, type.predict = "response"))
#Observaciones con probabilidad más baja
models$pred$todas %>% arrange(.fitted) %>% head(10)
#Observaciones con probabilidades más altas
models$pred$todas %>% arrange(desc(.fitted)) %>% head(10)
Curvas ROC
# Calculamos curvas ROC
roc_todas <- roc(response=models$pred$todas$Survived, predictor=models$pred$todas$.fitted)
Setting levels: control = 0, case = 1
Setting direction: controls < cases
Graficamos
ggroc(list(todas=roc_todas), size=1) + geom_abline(slope = 1, intercept = 1, linetype='dashed') + theme_bw() + labs(title='Curvas ROC', color='Modelo')

print(paste('AUC: Modelo todas', roc_todas$auc))
[1] "AUC: Modelo todas 0.852773131150746"
El AUC es de 0.85, bastante alto. Quizás convendría utilizar precision-recall por ser un dataset con desbalanceo. La sensibilidad aumenta hasta alrededor de 0.7 sin un incremento en los falsos positivos y a partir de ahí encontrar nuevos positivos requiere de muchas falsas detecciones.
Veamos el gráfico de violín:
ggplot(models$pred$todas, aes(x=Survived, y=.fitted, group=Survived,fill=factor(Survived))) +
geom_violin() +
theme_bw() +
guides(fill=FALSE) +
labs(title='Violin plot', subtitle='Modelo todas', y='Predicted probability')

Se observa que las clases se separan muy bien, estándo la mayoría de los que no sobreviven por debajo del 0.5 y los que si por arriba. Podemos usar 0.5 como punto de corte.
Elección del punto de corte
Para elegir el punto de corte vamos a usar el dataset de validación. Predecimos la probabilidad de sobrevivir en validación:
prediccion_validacion <- augment(x=models$mod$todas, newdata=validacion, type.predict='response')
prediccion_validacion
#Observaciones con probabilidades más altas
prediccion_validacion %>% arrange(desc(.fitted)) %>% head(10)
Como sospechábamos, las pasajeras de primera clase más jóvenes tienen las más altas probabilidad de sobrevivir.
#Observaciones con probabilidades más altas
prediccion_validacion %>% arrange(.fitted) %>% head(10)
Mientras que los hombres de tercera más viejos la menor.
Veamos como son las métricas en función del cutoff
prediction_metrics <- function(cutoff, predictions=prediccion_validacion){
tabla <- predictions %>%
mutate(predicted_class=if_else(.fitted>cutoff, 1, 0) %>% as.factor(),
Survived = factor(Survived))
confusionMatrix(table(tabla$predicted_class, tabla$Survived), positive = "1") %>%
tidy() %>%
select(term, estimate) %>%
filter(term %in% c('accuracy', 'sensitivity', 'specificity', 'precision','recall')) %>%
mutate(cutoff=cutoff)
}
cutoffs = seq(0.05,0.95,0.01)
logit_pred= map_dfr(cutoffs, prediction_metrics) %>% mutate(term=as.factor(term))
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= 'Modelo todas', color="")

Vemos que a medida que aumenta el cutoff aumenta la precisión y especificidad y disminuyen la sensibilidad y la accuracy (el recall es lo mismo que la sensibilidad y por eso no se visualiza). Vemos que entre 0.25 y 0.75 de cutoff la accuracy se plancha. El punto de corte depende de qué estemos buscando en la predicción. Podríamos elegir un cutoff en el cruce de precision - recall, alrededor de 0.45, con una buena capacidad de detección y confianza. Si necesitamos más confianza en la predicción, podríamos usar un cutoff de aproximadamente 0.75 pero perdemos bastante capacidad de recall.
Matriz de confusión
tabla <- prediccion_validacion %>%
mutate(predicted_class=if_else(.fitted>0.45, 1, 0) %>% as.factor(),
Survived = factor(Survived))
table(tabla$predicted_class, tabla$Survived)
0 1
0 139 29
1 28 72
Vemos que detectamos correctamente 72 de los 101 sobrevivientes (un 72%). Nos perdemos de detectar 29 sobrevivientes y detectamos erroneamente 28 muertos como sobrevivientes.
Veamos como clasificamos los pasajeros de test
titanic_complete_test <- read_csv("~/maestria/tps/sabado/titanic_complete_test.csv")
Parsed with column specification:
cols(
PassengerId = col_integer(),
Pclass = col_integer(),
Name = col_character(),
Sex = col_character(),
Age = col_double(),
SibSp = col_integer(),
Parch = col_integer(),
Ticket = col_character(),
Fare = col_double(),
Cabin = col_character(),
Embarked = col_character(),
Survived = col_integer()
)
titanic_complete_test <- titanic_complete_test %>% select(PassengerId, Survived, Pclass, Sex, Age, SibSp, Parch, Fare, Embarked)
titanic_complete_test <- titanic_complete_test %>% mutate_at(vars(Survived, Pclass, Embarked), factor)
glimpse(titanic_complete_test)
Observations: 418
Variables: 9
$ PassengerId <int> 892, 893, 894, 895, 896, 897, 898, 899, 900, 901, 902, 903, 904, 905, 906, 907, 908, 909, 910, 911, …
$ 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, 0, 1, 0, 0, 0, 0, 0, 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, 3, 1, 3, 1, 3, 2, 2, 3, 3…
$ Sex <chr> "male", "female", "male", "male", "female", "male", "female", "male", "female", "male", "male", "mal…
$ Age <dbl> 34.50000, 47.00000, 62.00000, 27.00000, 22.00000, 14.00000, 30.00000, 26.00000, 18.00000, 21.00000, …
$ 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, 1, 0, 0, 0, 2, 1, 2, 1, 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, 0, 1, 0, 0, 0, 0, 0, 2, 2…
$ Fare <dbl> 7.8292, 7.0000, 9.6875, 8.6625, 12.2875, 9.2250, 7.6292, 29.0000, 7.2292, 24.1500, 7.8958, 26.0000, …
$ 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, S, C, C, S, C, S, S, S, S…
Predecimos los test con el modelo todas y cutoff 0.45.
prediccion_test <- augment(x=models$mod$todas, newdata=titanic_complete_test, type.predict='response')
prediccion_test
Matriz de confusión
tabla <- prediccion_test %>%
mutate(predicted_class=if_else(.fitted>0.45, 1, 0) %>% as.factor(),
Survived = factor(Survived))
table(tabla$predicted_class, tabla$Survived)
0 1
0 204 43
1 57 114
Estamos detectando correctamente un 53%, bastante menos que con el dataset de validación. Esto es razonable porque usamos validación para encontrar el mejor punto de corte, que no tiene por qué ser el de testing. Nos perdemos 43 sobrevivientes y detectamos falsamente 57 muertos como sobrevivientes.
LS0tCnRpdGxlOiAiUmVncmVzaW9uIExvZ2lzdGljYSIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIHRvYzogeWVzCmF1dGhvcjogIkFuZHLDqXMgUmFiaW5vdmljaCIKZGF0ZTogMTItMTItMjAxOQotLS0KCiNDYXJnYW1vcyBsaWJyZXLDrWFzIHF1ZSB2YW1vcyBhIHVzYXIKCmBgYHtyLCBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KElTTFIpCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KG1vZGVscikKbGlicmFyeShwUk9DKQpsaWJyYXJ5KGNvd3Bsb3QpCmxpYnJhcnkoT25lUikKbGlicmFyeShybGFuZykKbGlicmFyeShjYXJldCkKc2V0LnNlZWQoMTk5MikKYGBgCgojQ2FyZ2Ftb3MgbG9zIGRhdG9zIGRlIGVudHJlbmFtaWVudG8KCmBgYHtyfQp0aXRhbmljX2NvbXBsZXRlX3RyYWluIDwtIHJlYWRfY3N2KCJ+L21hZXN0cmlhL3Rwcy9zYWJhZG8vdGl0YW5pY19jb21wbGV0ZV90cmFpbi5jc3YiKQpnbGltcHNlKHRpdGFuaWNfY29tcGxldGVfdHJhaW4pCmBgYApNb2RpZmljYW1vcyBlbCBkYXRhc2V0IHBhcmEgYWp1c3RhcmxvIGEgbG9zIHJlcXVlcmltaWVudG9zCgpgYGB7cn0KdGl0YW5pY19jb21wbGV0ZV90cmFpbiA8LSB0aXRhbmljX2NvbXBsZXRlX3RyYWluICU+JSBzZWxlY3QoUGFzc2VuZ2VySWQsIFN1cnZpdmVkLCBQY2xhc3MsIFNleCwgQWdlLCBTaWJTcCwgUGFyY2gsIEZhcmUsIEVtYmFya2VkKQp0aXRhbmljX2NvbXBsZXRlX3RyYWluIDwtIHRpdGFuaWNfY29tcGxldGVfdHJhaW4gJT4lIG11dGF0ZV9hdCh2YXJzKFN1cnZpdmVkLCBQY2xhc3MsIEVtYmFya2VkKSwgZmFjdG9yKQpnbGltcHNlKHRpdGFuaWNfY29tcGxldGVfdHJhaW4pCmBgYAoKIyMgRXhwbG9yYXRvcmlhcwoKUmVhbGl6YW1vcyB1biBncsOhZmljbyBleHBsb3JhdG9yaW8gY29tcGxldG8gcGFyYSB2ZXIgZWwgY29tcG9ydGFtaWVudG8geSBsYXMgcmVsYWNpb25lcyBlbnRyZSBsYXMgdmFyaWFibGVzLiBFbCBjb2xvciByb2pvIGRlc2lnbmEgYSBxdWllbmVzIG5vIHNvYnJldml2ZW4geSBlbCBhenVsIGEgbG9zIHF1ZSBzw60uCgpgYGB7ciwgd2FybmluZz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KZ2dwYWlycyh0aXRhbmljX2NvbXBsZXRlX3RyYWluICU+JSBzZWxlY3QoU3Vydml2ZWQsIFBjbGFzcywgU2V4LCBBZ2UsIEZhcmUpLCBtYXBwaW5nID0gYWVzKGNvbG91ciA9IFN1cnZpdmVkKSkgKyB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwLCBoanVzdCA9IDEpKSArIHRoZW1lX2J3KCkKYGBgCgpTZSBvYnNlcnZhIHF1ZSBsYXMgdmFyaWFibGVzIFBjbGFzcyB5IFNleCBzb24gYnVlbm9zIGRpc2NyaW1pbmFudGVzIGRlIFN1cnZpdmVkLCBtaWVudHJhcyBxdWUgQWdlIHkgRmFyZSBuby4KCkFuYWxpY2Vtb3MgbGEgZGlzdHJpYnVjacOzbiBkZSBsYSBjbGFzZSBkZSBzdXBlcnZpdmVuY2lhCgpgYGB7cn0KdGl0YW5pY19jb21wbGV0ZV90cmFpbiAlPiUgZ3JvdXBfYnkoU3Vydml2ZWQpICU+JSBzdW1tYXJpc2UobnVtZXJvX2Nhc29zPW4oKSkKYGBgCgpFbCA2MSUgZGUgbG9zIHBhc2FqZXJvcyBtdWVyZW4gbWllbnRyYXMgcXVlIGVsIDM5JSBzb2JyZXZpdmVuLiBFcyB1biBwcm9ibGVtYSBjb24gdW4gcGVxdWXDsW8gZGVzYmFsYW5jZSBlbiBsYXMgY2xhc2VzLiAKCiMjIyBEaXZpc2nDs24gZW50cmUgdHJhaW5pbmcgeSB2YWxpZGFjacOzbgoKRGl2aWRpbW9zIHVuIDcwJSBkZWwgZGF0YXNldCBwYXJhIHRyYWluIHkgdW4gMzAlIHBhcmEgdGVzdC4KCmBgYHtyfQp0cmFpbl90ZXN0ICA8LSB0aXRhbmljX2NvbXBsZXRlX3RyYWluICU+JSByZXNhbXBsZV9wYXJ0aXRpb24oYyh0cmFpbj0wLjcsIHZhbGlkYWNpb249MC4zKSkKCnRyYWluICAgICAgIDwtIHRyYWluX3Rlc3QkdHJhaW4gJT4lIGFzX3RpYmJsZSgpCnZhbGlkYWNpb24gIDwtIHRyYWluX3Rlc3QkdmFsaWRhY2lvbiAlPiUgYXNfdGliYmxlKCkKYGBgCgpWZWFtb3MgbGEgZGlzdHJpYnVjacOzbiBkZSBjYXNvcyBlbiBjYWRhIHVubzoKYGBge3J9CnRyYWluICAgICAgJT4lIGdyb3VwX2J5KFN1cnZpdmVkKSAlPiUgc3VtbWFyaXNlKG51bWVyb19jYXNvcz1uKCkpCnZhbGlkYWNpb24gJT4lIGdyb3VwX2J5KFN1cnZpdmVkKSAlPiUgc3VtbWFyaXNlKG51bWVyb19jYXNvcz1uKCkpCmBgYApMYSBkaXN0cmlidWNpw7NuIGRlIGNhc29zIHRhbnRvIHBhcmEgdHJhaW4gY29tbyBwYXJhIHZhbGlkYWNpb24gZXMgbXV5IHNpbWlsYXIgYSBsYSBvcmlnaW5hbC4KCgojIyBQcm9ibGVtYQoKUXVlcmVtb3MgZXN0aW1hciAkUChTdXJ2aXZlZD1ZZXN8WCk9UChYKSQgcGFyYSBjYWRhIGluZGl2aWR1byB5IHBhcnRpciBkZSBlbGxvIHBvZGVyIGRlZmluaXIgdW4gcHVudG8gZGUgY29ydGUgcGFyYSBwcmVkZWNpciBxdWllbmVzIHNvbiBsb3MgcXVlIHZhbiBhIHNvYnJldml2aXIuCgojIyMgUmVncmVzacOzbiBsb2fDrXN0aWNhCgpVc2Ftb3MgbGEgKipmdW5jaW9uIGxvZ2lzdGljYSoqCgojIyBDcmVhY2nDs24gZGUgZsOzcm11bGFzLgpHZW5lcmFtb3MgdmFyaWFzIGbDs3JtdWxhcyBwYXJhIHVzYXIgYSBsbyBsYXJnbyBkZWwgdHJhYmFqby4KCmBgYHtyfQpsb2dpdF9mb3JtdWxhcyA8LSBmb3JtdWxhcygucmVzcG9uc2UgPSB+U3Vydml2ZWQsICMgw7puaWNvIGxhZG8gZGVyZWNobyBkZSBsYXMgZm9ybXVsYXMuCiAgICAgICAgICAgICAgICAgICAgICAgICBwY2xhc3Nfc2V4X2FnZSA9IH4gUGNsYXNzICsgU2V4ICsgQWdlLCAKICAgICAgICAgICAgICAgICAgICAgICAgIHBjbGFzc19zZXggPSB+UGNsYXNzICsgU2V4LAogICAgICAgICAgICAgICAgICAgICAgICAgcGNsYXNzID0gflBjbGFzcywgIAogICAgICAgICAgICAgICAgICAgICAgICAgdG9kYXMgPSB+IFN1cnZpdmVkICsgUGNsYXNzICsgU2V4ICsgQWdlICsgU2liU3AgKyBQYXJjaCArIEZhcmUgKyBFbWJhcmtlZAogICAgICAgICAgICAgICAgICAgICAgICAgKQpgYGAKCiMjIENyZWFjacOzbiBkZSBtb2RlbG9zCgpQcm9jZWRlbW9zIGEgY3JlYXIgbG9zIG1vZGVsb3MgYSBwYXJ0aXIgZGUgZXN0YXMgZsOzcm11bGFzCgpgYGB7ciwgd2FybmluZz1GQUxTRX0KbW9kZWxzIDwtIGRhdGFfZnJhbWUobG9naXRfZm9ybXVsYXMpICU+JSAjIGRhdGFmcmFtZSBhIHBhcnRpciBkZWwgb2JqZXRvIGZvcm11bGFzCiAgbXV0YXRlKG1vZGVscyA9IG5hbWVzKGxvZ2l0X2Zvcm11bGFzKSwgIyBjb2x1bW5hIGNvbiBsb3Mgbm9tYnJlcyBkZSBsYXMgZm9ybXVsYXMKICAgICAgICAgZXhwcmVzc2lvbiA9IHBhc3RlKGxvZ2l0X2Zvcm11bGFzKSwgIyBjb2x1bW5hIGNvbiBsYXMgZXhwcmVzaW9uZXMgZGUgbGFzIGZvcm11bGFzCiAgICAgICAgIG1vZCA9IG1hcChsb2dpdF9mb3JtdWxhcywgfmdsbSguLGZhbWlseSA9ICdiaW5vbWlhbCcsIGRhdGEgPSB0cmFpbikpKSAjIEVuIG1vZCB2YSBhIGVzdGFyIGVsIG1vZGVsbyBmaXRlYWRvIApgYGAKClVzYW1vcyBmYW1pbHkgPSAnYmlub21pYWwnIHBvcnF1ZSBlc3RhbW9zIHRyYWJhamFuZG8gY29uIHVuIGZlbsOzbWVubyBxdWUgdGllbmUgdW5hIGRpc3RyaWJ1Y2nDs24gYmlub21pYWwuClZlYW1vcyBlbCBwcmltZXJvIHF1ZSBlcyBlbCBxdWUgbmVjZXNpdGFtb3MgcGFyYSBlc3RlIHB1bnRvLgoKYGBge3IsIHdhcm5pbmc9RkFMU0V9Cm1vZGVscyAlPiUgCiAgZmlsdGVyKG1vZGVscyAlaW4lIGMoJ3BjbGFzc19zZXhfYWdlJykpICU+JQogIG11dGF0ZSh0aWR5ID0gbWFwKG1vZCx0aWR5KSkgJT4lICAjIFF1w6kgcmVhbGl6YW1vcyBlbiBlc3RlIHBhc28/IFF1ZSB2YSBhIHRlbmVyIGVzdGEgY29sdW1uYT8KICB1bm5lc3QodGlkeSwgLmRyb3AgPSBUUlVFKSAlPiUgCiAgbXV0YXRlKGVzdGltYXRlPXJvdW5kKGVzdGltYXRlLDUpLAogICAgICAgICBwLnZhbHVlPXJvdW5kKHAudmFsdWUsNCkpCmBgYAoKVmVtb3MgcXVlIHRvZG9zIGxvcyBjb2VmaWNpZW50ZXMgc29uIHNpZ25pZmljYXRpdm9zLgpSZWNvcmRhbmRvIHF1ZSB1biBjb2VmaWNpZW50ZSBwb3NpdGl2byBpbmRpY2EgcXVlIGZyZW50ZSBhIGF1bWVudG9zIGRlIGRpY2hhIHZhcmlhYmxlIGxhIHByb2JhYmlsaWRhZCBhdW1lbnRhIG1pZW50cmFzIHF1ZSB1biBjb2VmaWNpZW50ZSBuZWdhdGl2byBpbmRpY2EgbG8gY29udHJhcmlvOgoKKiBFbCBjb2VmaWNpZW50ZSBkZWwgaW50ZXJjZXB0IGltcGxpY2EgcXVlIGVzIG11eSBlbGV2YWRhIGxhIHByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhIHBhcmEgdW4gbWllbWJybyBkZSBQY2xhc3MxIGZlbWVuaW5vIChhbHJlZGVkb3IgZGUgMC45NykuIAoqIEVsIGNvZWZpY2llbnRlIGRlIFBjbGFzczIgbmVnYXRpdm8gaW5kaWNhIHF1ZSBkaXNtaW51eWUgbGEgcHJvYmFiaWxpZGFkIGRlIHN1cGVydml2ZW5jaWEgcmVzcGVjdG8gZGUgUGNsYXNzMS4KKiBFbCBjb2VmaWNpZW50ZSBkZSBQY2xhc3MzIG5lZ2F0aXZvIGluZGljYSBxdWUgZGlzbWludXllIHN1IHByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhIHJlc3BlY3RvIGRlIFBjbGFzczEgbcOhcyBhw7puIHF1ZSBQY2xhc3MyLiAKKiBFbCBjb2VmaWNpZW50ZSBTZXhtYWxlIG5lZ2F0aXZvIGluZGljYSBxdWUgc2VyIGhvbWJyZSBkaXNtaW51eWUgbGEgcHJvYmFiaWxpZGFkIGRlIHNvYnJldml2aXIuCiogRWwgY29lZmljaWVudGUgQWdlIGVzIG11eSBjZXJjYW5vIGEgMCwgcGVybyBpbmRpY2EgcXVlIGEgbWF5b3IgZWRhZCwgbWVub3IgbGEgcHJvYmFiaWxpZGFkIGRlIHNvYnJldml2aXIgKE11amVyZXMgeSBuacOxb3MgKHkgcmljb3MhKSBwcmltZXJvKS4KCiMjIyBQcmVkZWNpbW9zIGxhIHByb2JhYmlsaWRhZCBkZSBzb2JyZXZpdmlyIGRlIFJvc2UgeSBKYWNrCgpgYGB7cn0Kcm9zZV9qYWNrIDwtIGRhdGEuZnJhbWUoUGNsYXNzID0gYygiMSIsICIzIiksIFNleCA9IGMoImZlbWFsZSIsICJtYWxlIiksIEFnZSA9IGMoMTcsIDIwKSkKcHJlZGljY2lvbl9yb3NlX2phY2sgPC0gYXVnbWVudCh4PW1vZGVscyRtb2QkcGNsYXNzX3NleF9hZ2UsIG5ld2RhdGE9cm9zZV9qYWNrLCB0eXBlLnByZWRpY3Q9J3Jlc3BvbnNlJykgCnByZWRpY2Npb25fcm9zZV9qYWNrCmBgYApDb21vIGVyYSBkZSBlc3BlcmFyLCBsYSBwcm9iYWJpbGlkYWQgZGUgc29icmV2aXZpciBkZSBSb3NlIGVyYSBtdWNobyBtYXlvciBxdWUgbGEgZGUgSmFjayAobGEgZGUgUm9zZSBjZXJjYW5hIGEgMSBtaWVudHJhcyBxdWUgbGEgZGUgSmFjayBhIDAuMSkuCgojIyMgVmVhbW9zIHF1w6kgbW9kZWxvIG1pbmltaXphIGxhIGRldmlhbmNlLgpgYGB7cn0KbW9kZWxzIDwtIG1vZGVscyAlPiUgCiAgbXV0YXRlKGdsYW5jZSA9IG1hcChtb2QsZ2xhbmNlKSkKCiMgT2J0ZW5lciBsYXMgbWVkaWRhcyBkZSBldmFsdWFjaW9uIGRlIGludGVyZXMKbW9kZWxzICU+JSAKICB1bm5lc3QoZ2xhbmNlLCAuZHJvcCA9IFRSVUUpICU+JQogICMgQ2FsY3VsbyBkZSBsYSBkZXZpYW5jZSBleHBsaWNhZGEKICBtdXRhdGUocGVyY19leHBsYWluZWRfZGV2ID0gMS1kZXZpYW5jZS9udWxsLmRldmlhbmNlKSAlPiUgCiAgc2VsZWN0KC1jKG1vZGVscywgZGYubnVsbCwgQUlDLCBCSUMpKSAlPiUgCiAgYXJyYW5nZShkZXZpYW5jZSkKYGBgCkVsIG1vZGVsbyBxdWUgdXRpbGl6YSB0b2RhcyBsYXMgdmFyaWFibGVzIGVzIGVsIHF1ZSBtZW5vciBkZXZpYW5jZSBvYnRpZW5lIHkgbWF5b3IgcG9yY2VudGFqZSBkZSBsYSBkZXZpYW5jZSBleHBsaWNhLgogIApgYGB7ciwgd2FybmluZz1GQUxTRX0KbW9kZWxzICU+JSAKICBmaWx0ZXIobW9kZWxzICVpbiUgYygndG9kYXMnKSkgJT4lCiAgbXV0YXRlKHRpZHkgPSBtYXAobW9kLHRpZHkpKSAlPiUgICMgUXXDqSByZWFsaXphbW9zIGVuIGVzdGUgcGFzbz8gUXVlIHZhIGEgdGVuZXIgZXN0YSBjb2x1bW5hPwogIHVubmVzdCh0aWR5LCAuZHJvcCA9IFRSVUUpICU+JSAKICBtdXRhdGUoZXN0aW1hdGU9cm91bmQoZXN0aW1hdGUsNSksCiAgICAgICAgIHAudmFsdWU9cm91bmQocC52YWx1ZSw0KSkKYGBgCkVuIGVzdGUgbW9kZWxvIGVsIMO6bmljbyBjb2VmaWNpZW50ZSBzaWduaWZpY2F0aXZvIG51ZXZvIHF1ZSBzZSBhZ3JlZ2EgZXMgRW1iYXJrZWRTLCB5IG51ZXZhbWVudGUgc2VyIGRlIHByaW1lcmEgY2xhc2UgeSBzZXhvIGZlbWVuaW5vIGRhIHVuYSBhbHTDrXNpbWEgcHJvYmFiaWxpZGFkIGRlIHNvYnJldml2aXIsIG1pZW50cmFzIHF1ZSBjdWFscXVpZXIgb3RyYSBzaXR1YWNpw7NuIGxhIGRpc21pbnV5ZS4KCiMjIyBQcmVkZWNpbW9zIGNvbiBlbnRyZW5hbWllbnRvIHkgY3VydmFzIFJPQwoKYGBge3J9CiMgQcOxYWRpbW9zIGxhcyBwcmVkaWNjaW9uZXMKbW9kZWxzIDwtIG1vZGVscyAlPiUgCiAgbXV0YXRlKHByZWQgPSBtYXAobW9kLGF1Z21lbnQsIHR5cGUucHJlZGljdCA9ICJyZXNwb25zZSIpKQoKI09ic2VydmFjaW9uZXMgY29uIHByb2JhYmlsaWRhZCBtw6FzIGJhamEKbW9kZWxzJHByZWQkdG9kYXMgJT4lIGFycmFuZ2UoLmZpdHRlZCkgJT4lIGhlYWQoMTApCmBgYAoKYGBge3J9CiNPYnNlcnZhY2lvbmVzIGNvbiBwcm9iYWJpbGlkYWRlcyBtw6FzIGFsdGFzCm1vZGVscyRwcmVkJHRvZGFzICU+JSBhcnJhbmdlKGRlc2MoLmZpdHRlZCkpICU+JSBoZWFkKDEwKQpgYGAKCgojIyMjIEN1cnZhcyBST0MKCmBgYHtyfQojIENhbGN1bGFtb3MgY3VydmFzIFJPQwpyb2NfdG9kYXMgPC0gcm9jKHJlc3BvbnNlPW1vZGVscyRwcmVkJHRvZGFzJFN1cnZpdmVkLCBwcmVkaWN0b3I9bW9kZWxzJHByZWQkdG9kYXMkLmZpdHRlZCkKCmBgYAoKR3JhZmljYW1vcwoKYGBge3J9Cmdncm9jKGxpc3QodG9kYXM9cm9jX3RvZGFzKSwgc2l6ZT0xKSArIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMSwgbGluZXR5cGU9J2Rhc2hlZCcpICsgdGhlbWVfYncoKSArIGxhYnModGl0bGU9J0N1cnZhcyBST0MnLCBjb2xvcj0nTW9kZWxvJykKCnByaW50KHBhc3RlKCdBVUM6IE1vZGVsbyB0b2RhcycsIHJvY190b2RhcyRhdWMpKQoKYGBgCkVsIEFVQyBlcyBkZSAwLjg1LCBiYXN0YW50ZSBhbHRvLiBRdWl6w6FzIGNvbnZlbmRyw61hIHV0aWxpemFyIHByZWNpc2lvbi1yZWNhbGwgcG9yIHNlciB1biBkYXRhc2V0IGNvbiBkZXNiYWxhbmNlby4KTGEgc2Vuc2liaWxpZGFkIGF1bWVudGEgaGFzdGEgYWxyZWRlZG9yIGRlIDAuNyBzaW4gdW4gaW5jcmVtZW50byBlbiBsb3MgZmFsc29zIHBvc2l0aXZvcyB5IGEgcGFydGlyIGRlIGFow60gZW5jb250cmFyIG51ZXZvcyBwb3NpdGl2b3MgcmVxdWllcmUgZGUgbXVjaGFzIGZhbHNhcyBkZXRlY2Npb25lcy4KClZlYW1vcyBlbCBncsOhZmljbyBkZSB2aW9sw61uOgoKYGBge3J9CmdncGxvdChtb2RlbHMkcHJlZCR0b2RhcywgYWVzKHg9U3Vydml2ZWQsIHk9LmZpdHRlZCwgZ3JvdXA9U3Vydml2ZWQsZmlsbD1mYWN0b3IoU3Vydml2ZWQpKSkgKyAKICBnZW9tX3Zpb2xpbigpICsKICB0aGVtZV9idygpICsKICBndWlkZXMoZmlsbD1GQUxTRSkgKwogIGxhYnModGl0bGU9J1Zpb2xpbiBwbG90Jywgc3VidGl0bGU9J01vZGVsbyB0b2RhcycsIHk9J1ByZWRpY3RlZCBwcm9iYWJpbGl0eScpCmBgYApTZSBvYnNlcnZhIHF1ZSBsYXMgY2xhc2VzIHNlIHNlcGFyYW4gbXV5IGJpZW4sIGVzdMOhbmRvIGxhIG1heW9yw61hIGRlIGxvcyBxdWUgbm8gc29icmV2aXZlbiBwb3IgZGViYWpvIGRlbCAwLjUgeSBsb3MgcXVlIHNpIHBvciBhcnJpYmEuIFBvZGVtb3MgdXNhciAwLjUgY29tbyBwdW50byBkZSBjb3J0ZS4KCiMjIyBFbGVjY2nDs24gZGVsIHB1bnRvIGRlIGNvcnRlCgpQYXJhIGVsZWdpciBlbCBwdW50byBkZSBjb3J0ZSB2YW1vcyBhIHVzYXIgZWwgZGF0YXNldCBkZSB2YWxpZGFjacOzbi4gUHJlZGVjaW1vcyBsYSBwcm9iYWJpbGlkYWQgZGUgc29icmV2aXZpciBlbiB2YWxpZGFjacOzbjoKCmBgYHtyfQpwcmVkaWNjaW9uX3ZhbGlkYWNpb24gPC0gYXVnbWVudCh4PW1vZGVscyRtb2QkdG9kYXMsIG5ld2RhdGE9dmFsaWRhY2lvbiwgdHlwZS5wcmVkaWN0PSdyZXNwb25zZScpIApwcmVkaWNjaW9uX3ZhbGlkYWNpb24KYGBgCgpgYGB7cn0KI09ic2VydmFjaW9uZXMgY29uIHByb2JhYmlsaWRhZGVzIG3DoXMgYWx0YXMKcHJlZGljY2lvbl92YWxpZGFjaW9uICU+JSBhcnJhbmdlKGRlc2MoLmZpdHRlZCkpICU+JSBoZWFkKDEwKQpgYGAKQ29tbyBzb3NwZWNow6FiYW1vcywgbGFzIHBhc2FqZXJhcyBkZSBwcmltZXJhIGNsYXNlIG3DoXMgasOzdmVuZXMgdGllbmVuIGxhcyBtw6FzIGFsdGFzIHByb2JhYmlsaWRhZCBkZSBzb2JyZXZpdmlyLiAKCmBgYHtyfQojT2JzZXJ2YWNpb25lcyBjb24gcHJvYmFiaWxpZGFkZXMgbcOhcyBhbHRhcwpwcmVkaWNjaW9uX3ZhbGlkYWNpb24gJT4lIGFycmFuZ2UoLmZpdHRlZCkgJT4lIGhlYWQoMTApCmBgYApNaWVudHJhcyBxdWUgbG9zIGhvbWJyZXMgZGUgdGVyY2VyYSBtw6FzIHZpZWpvcyBsYSBtZW5vci4KCiMjIyBWZWFtb3MgY29tbyBzb24gbGFzIG3DqXRyaWNhcyBlbiBmdW5jacOzbiBkZWwgY3V0b2ZmCmBgYHtyfQoKcHJlZGljdGlvbl9tZXRyaWNzIDwtIGZ1bmN0aW9uKGN1dG9mZiwgcHJlZGljdGlvbnM9cHJlZGljY2lvbl92YWxpZGFjaW9uKXsKICAgIHRhYmxhIDwtIHByZWRpY3Rpb25zICU+JSAKICAgIG11dGF0ZShwcmVkaWN0ZWRfY2xhc3M9aWZfZWxzZSguZml0dGVkPmN1dG9mZiwgMSwgMCkgJT4lIGFzLmZhY3RvcigpLAogICAgICAgICAgIFN1cnZpdmVkID0gZmFjdG9yKFN1cnZpdmVkKSkKICAKICAgIGNvbmZ1c2lvbk1hdHJpeCh0YWJsZSh0YWJsYSRwcmVkaWN0ZWRfY2xhc3MsIHRhYmxhJFN1cnZpdmVkKSwgcG9zaXRpdmUgPSAiMSIpICU+JQogICAgdGlkeSgpICU+JQogICAgc2VsZWN0KHRlcm0sIGVzdGltYXRlKSAlPiUKICAgIGZpbHRlcih0ZXJtICVpbiUgYygnYWNjdXJhY3knLCAnc2Vuc2l0aXZpdHknLCAnc3BlY2lmaWNpdHknLCAncHJlY2lzaW9uJywncmVjYWxsJykpICU+JQogICAgbXV0YXRlKGN1dG9mZj1jdXRvZmYpCiAgCn0KCmN1dG9mZnMgPSBzZXEoMC4wNSwwLjk1LDAuMDEpCmxvZ2l0X3ByZWQ9IG1hcF9kZnIoY3V0b2ZmcywgcHJlZGljdGlvbl9tZXRyaWNzKSAlPiUgbXV0YXRlKHRlcm09YXMuZmFjdG9yKHRlcm0pKQoKZ2dwbG90KGxvZ2l0X3ByZWQsIGFlcyhjdXRvZmYsZXN0aW1hdGUsIGdyb3VwPXRlcm0sIGNvbG9yPXRlcm0pKSArIGdlb21fbGluZShzaXplPTEpICsKICB0aGVtZV9idygpICsKICBsYWJzKHRpdGxlPSAnQWNjdXJhY3ksIFNlbnNpdGl2aXR5LCBTcGVjaWZpY2l0eSwgUmVjYWxsIHkgUHJlY2lzaW9uJywgc3VidGl0bGU9ICdNb2RlbG8gdG9kYXMnLCBjb2xvcj0iIikKYGBgClZlbW9zIHF1ZSBhIG1lZGlkYSBxdWUgYXVtZW50YSBlbCBjdXRvZmYgYXVtZW50YSBsYSBwcmVjaXNpw7NuIHkgZXNwZWNpZmljaWRhZCB5IGRpc21pbnV5ZW4gbGEgc2Vuc2liaWxpZGFkIHkgbGEgYWNjdXJhY3kgKGVsIHJlY2FsbCBlcyBsbyBtaXNtbyBxdWUgbGEgc2Vuc2liaWxpZGFkIHkgcG9yIGVzbyBubyBzZSB2aXN1YWxpemEpLiBWZW1vcyBxdWUgZW50cmUgMC4yNSB5IDAuNzUgZGUgY3V0b2ZmIGxhIGFjY3VyYWN5IHNlIHBsYW5jaGEuCkVsIHB1bnRvIGRlIGNvcnRlIGRlcGVuZGUgZGUgcXXDqSBlc3RlbW9zIGJ1c2NhbmRvIGVuIGxhIHByZWRpY2Npw7NuLiBQb2Ryw61hbW9zIGVsZWdpciB1biBjdXRvZmYgZW4gZWwgY3J1Y2UgZGUgcHJlY2lzaW9uIC0gcmVjYWxsLCBhbHJlZGVkb3IgZGUgMC40NSwgY29uIHVuYSBidWVuYSBjYXBhY2lkYWQgZGUgZGV0ZWNjacOzbiB5IGNvbmZpYW56YS4gU2kgbmVjZXNpdGFtb3MgbcOhcyBjb25maWFuemEgZW4gbGEgcHJlZGljY2nDs24sIHBvZHLDrWFtb3MgdXNhciB1biBjdXRvZmYgZGUgYXByb3hpbWFkYW1lbnRlIDAuNzUgcGVybyBwZXJkZW1vcyBiYXN0YW50ZSBjYXBhY2lkYWQgZGUgcmVjYWxsLgoKIyMjIE1hdHJpeiBkZSBjb25mdXNpw7NuCmBgYHtyfQp0YWJsYSA8LSBwcmVkaWNjaW9uX3ZhbGlkYWNpb24gJT4lIAptdXRhdGUocHJlZGljdGVkX2NsYXNzPWlmX2Vsc2UoLmZpdHRlZD4wLjQ1LCAxLCAwKSAlPiUgYXMuZmFjdG9yKCksCiAgICAgICBTdXJ2aXZlZCA9IGZhY3RvcihTdXJ2aXZlZCkpCgp0YWJsZSh0YWJsYSRwcmVkaWN0ZWRfY2xhc3MsIHRhYmxhJFN1cnZpdmVkKQpgYGAKVmVtb3MgcXVlIGRldGVjdGFtb3MgY29ycmVjdGFtZW50ZSA3MiBkZSBsb3MgMTAxIHNvYnJldml2aWVudGVzICh1biA3MiUpLiBOb3MgcGVyZGVtb3MgZGUgZGV0ZWN0YXIgMjkgc29icmV2aXZpZW50ZXMgeSBkZXRlY3RhbW9zIGVycm9uZWFtZW50ZSAyOCBtdWVydG9zIGNvbW8gc29icmV2aXZpZW50ZXMuCgojIyMgVmVhbW9zIGNvbW8gY2xhc2lmaWNhbW9zIGxvcyBwYXNhamVyb3MgZGUgdGVzdApgYGB7cn0KdGl0YW5pY19jb21wbGV0ZV90ZXN0IDwtIHJlYWRfY3N2KCJ+L21hZXN0cmlhL3Rwcy9zYWJhZG8vdGl0YW5pY19jb21wbGV0ZV90ZXN0LmNzdiIpCnRpdGFuaWNfY29tcGxldGVfdGVzdCA8LSB0aXRhbmljX2NvbXBsZXRlX3Rlc3QgJT4lIHNlbGVjdChQYXNzZW5nZXJJZCwgU3Vydml2ZWQsIFBjbGFzcywgU2V4LCBBZ2UsIFNpYlNwLCBQYXJjaCwgRmFyZSwgRW1iYXJrZWQpCnRpdGFuaWNfY29tcGxldGVfdGVzdCA8LSB0aXRhbmljX2NvbXBsZXRlX3Rlc3QgJT4lIG11dGF0ZV9hdCh2YXJzKFN1cnZpdmVkLCBQY2xhc3MsIEVtYmFya2VkKSwgZmFjdG9yKQpnbGltcHNlKHRpdGFuaWNfY29tcGxldGVfdGVzdCkKYGBgCgojIyMgUHJlZGVjaW1vcyBsb3MgdGVzdCBjb24gZWwgbW9kZWxvIHRvZGFzIHkgY3V0b2ZmIDAuNDUuCgpgYGB7cn0KcHJlZGljY2lvbl90ZXN0IDwtIGF1Z21lbnQoeD1tb2RlbHMkbW9kJHRvZGFzLCBuZXdkYXRhPXRpdGFuaWNfY29tcGxldGVfdGVzdCwgdHlwZS5wcmVkaWN0PSdyZXNwb25zZScpIApwcmVkaWNjaW9uX3Rlc3QKYGBgCgojIyMgTWF0cml6IGRlIGNvbmZ1c2nDs24KYGBge3J9CnRhYmxhIDwtIHByZWRpY2Npb25fdGVzdCAlPiUgCm11dGF0ZShwcmVkaWN0ZWRfY2xhc3M9aWZfZWxzZSguZml0dGVkPjAuNDUsIDEsIDApICU+JSBhcy5mYWN0b3IoKSwKICAgICAgIFN1cnZpdmVkID0gZmFjdG9yKFN1cnZpdmVkKSkKCnRhYmxlKHRhYmxhJHByZWRpY3RlZF9jbGFzcywgdGFibGEkU3Vydml2ZWQpCmBgYApFc3RhbW9zIGRldGVjdGFuZG8gY29ycmVjdGFtZW50ZSB1biA1MyUsIGJhc3RhbnRlIG1lbm9zIHF1ZSBjb24gZWwgZGF0YXNldCBkZSB2YWxpZGFjacOzbi4gRXN0byBlcyByYXpvbmFibGUgcG9ycXVlIHVzYW1vcyB2YWxpZGFjacOzbiBwYXJhIGVuY29udHJhciBlbCBtZWpvciBwdW50byBkZSBjb3J0ZSwgcXVlIG5vIHRpZW5lIHBvciBxdcOpIHNlciBlbCBkZSB0ZXN0aW5nLiBOb3MgcGVyZGVtb3MgNDMgc29icmV2aXZpZW50ZXMgeSBkZXRlY3RhbW9zIGZhbHNhbWVudGUgNTcgbXVlcnRvcyBjb21vIHNvYnJldml2aWVudGVzLg==