Trabajo Final Machine Learning
Introducción:
Este análisis forma parte de la evaluación final del módulo Machine
Learning. La idea es llevar adelante un análisis de los siniestros en la
Ciudad de Buenos Aires utilizando la técnica de Regresión Logística para
su clasificación. Este trabajo se encuentra disponible en mi blog personal
Objetivo:
Visualizar espacialmente los siniestros considerados de gravedad
ocurridos en la Ciudad Autónoma de Buenos Aires utilizando como fuente
los datos abiertos publicados por el municipio.
Analizar y modelar bajo la lógica de clasificación mediante regresión
logística la naturaleza de los accidentes de transito y su mortalidad
durante el período 2019-2023.
Metodología:
Para llevar adelante los objetivos planteados y, considerando que lo
que se busca es encontrar un modelo de clasificación se utilizará el
conjunto de técnicas específicas de regresión logística. Esta técnica se
utiliza para clasificar una variable respuesta que, en este caso será
dicotómica. Para ello, resultará de gran utilidad los resultados y el
análisis de las estadísticas del modelo problematizando el grado de
mortalidad en los siniestros acontecidos en CABA.
Fuentes de datos:
A los fines de los ideas planteadas, trabajaremos con los datos
de siniestros ocurridos durante el período 2019-2023. Según las
notas metodológicas, las categorías de la variable “gravedad” varian
en:
GRAVE: Toda persona que cuya lesión exige la hospitalización de al
menos 24 hs o una atención especializada, como fracturas, conmoción,
shock grave y laceraciones importantes. MORTAL: Víctima que fallece
dentro de los 30 días de producido el siniestro vial por causas directa
o indirectamente atribuibles al hecho. SD: Sin datos sobre la gravedad
de las lesiones provocadas. A efectos analíticos, los casos sin datos se
corresponden con una alta probabilidad a casos leves.
Procederemos a considerar sólamente los casos declarados como
“graves” o “mortales” a efectos de entrenar un modelo que permita
clasificarlos.
Instalamos librerias
rm(list = ls())
options(scipen = 999)
###################################################################################
# Instalamos librerías de trabajo
if (!require("pacman")) install.packages("pacman")
pacman::p_load("lubridate",
"tidyverse",
"leaflet",
"sf",
"tidymodels",
"spatialsample",
"themis",
"ggmap",
"showtext",
"ggtext")
###################################################################################
Limpieza y transformación
A los fines de abordar las distintas formas de analizar los datos
generaremos dos datasets, uno espacial para mapear los sucesos y otro
dataset sin generar geometrías espaciales para construir el modelo. Se
advierte que, debido a los filtros aplicados, el dataset espacial cuenta
con 1507 casos, mientras que el dataset sin considerar geometrías
espaciales contiene 1844 sinisetros.
Levantamos los datos y realizamos una limpieza y recodificación de
las variables de interés haciendo hincapié en la variable objetivo que
es “gravedad” la cual en una primera vista, tiene datos en mayúsculas y
minúsculas en un claro error de ingreso de datos. Tambien procedemos a
recodificar la variable hora (hh) según los momentos del día.
siniestros_geo <- read.csv("data/siniestros_viales_hechos.csv", sep = ";") %>%
filter(!latitud %in% c("SD", "","sd"),
!gravedad %in% c("SD","sd")) %>%
mutate(longitud = as.numeric(longitud),
latitud = as.numeric(latitud),
hh = as.numeric(hh),
"HoraRec" = case_when(hh >= 0 & hh < 6 ~ "Madrugada",
hh >= 6 & hh < 12 ~ "Mañana",
hh >= 12 & hh < 18 ~ "Tarde",
TRUE ~ "Noche" ),
gravedad = case_when(gravedad %in% c("GRAVE","grave") ~ "Grave",
gravedad == "MORTAL" ~ "Mortal"))%>%
filter(!is.na(latitud)) %>%
st_as_sf(coords =c("longitud", "latitud"), crs = 4326) %>%
select(!c(direccion_normalizada,fecha,calle, altura, cruce, otra_direccion, hora, comuna, tipo_de_calle, geocodificacion_caba,tipo_de_dato, participantes))
siniestros <- read.csv("data/siniestros_viales_hechos.csv", sep = ";") %>%
filter(!gravedad %in% c("SD","sd")) %>%
mutate(longitud = as.numeric(longitud),
latitud = as.numeric(latitud),
hh = as.numeric(hh),
"HoraRec" = case_when(hh >= 0 & hh < 6 ~ "Madrugada",
hh >= 6 & hh < 12 ~ "Mañana",
hh >= 12 & hh < 18 ~ "Tarde",
TRUE ~ "Noche" ),
gravedad = case_when(gravedad %in% c("GRAVE","grave") ~ "Grave",
gravedad == "MORTAL" ~ "Mortal"))%>%
select(!c(direccion_normalizada,fecha,calle, altura, cruce, otra_direccion, hora, comuna, tipo_de_calle, geocodificacion_caba,tipo_de_dato, participantes))
comunas <- st_read("data/comunas.geoson",
crs = 4326)
## Reading layer `comunas' from data source
## `C:\Users\Guille\Documents\Proyectos\FLACSO\Machine Learning\tp final\data\comunas.geoson'
## using driver `GeoJSON'
## Simple feature collection with 15 features and 6 fields
## Geometry type: MULTIPOLYGON
## Dimension: XY
## Bounding box: xmin: -58.53152 ymin: -34.70529 xmax: -58.33516 ymax: -34.52649
## Geodetic CRS: WGS 84
tabla_1 <- siniestros %>%
group_by(aaaa, gravedad) %>%
summarise(tot = n()) %>%
mutate(porcentaje = round(tot/sum(tot),4)) %>%
mutate(Total_Acum = cumsum(tot)) %>%
mutate(Porcentaje_Acum = cumsum(porcentaje))
ggplot(tabla_1, aes(x = aaaa, y = tot, fill = gravedad))+
geom_col(position = "dodge")+
geom_text(aes(label = percent(porcentaje)), vjust = -0.5, check_overlap = TRUE, position = position_dodge(width = .9), size = 3)+
scale_fill_manual(values = c("#f55951","#000000"))+
theme_minimal()+
labs(
title = "<span style = 'color:#272643;'> Evolución anual de siniestros: <span style = 'color:#f55951;'> Graves <span style = 'color:#543c52;'> - <span style = 'color:#000000;'> Mortales",
subtitle = "",
caption = "<b>Fuente</b>: ReNaBaP",
x = "Año",
y = "Barrios Populares"
)+
theme(
legend.position = "none",
plot.title = element_markdown(face = "bold", size = 15),
plot.caption = element_markdown(size = 12),
)

ggplot()+
geom_sf(data=comunas)+
geom_sf(data=siniestros_geo, aes(color=gravedad), alpha=0.75, show.legend = TRUE)+
scale_color_manual(values = c("#f55951","#000000"))+
theme_minimal()+
theme_void()+
labs(
title = "<span style = 'color:#272643;'> Siniestros viales - CABA 2019-2023",
caption = "<b>Fuente</b>: Datos Abiertos CABA",
y = "",
x = ""
)+
theme(
plot.title = element_markdown(size = 20, hjust = 0.5),
plot.caption= element_markdown(size = 12),
legend.position = "top",
legend.title = element_blank(),
legend.box.margin = margin(11, 16, 6, 6)
)+
facet_wrap(~aaaa, nrow = 1)+
guides(fill=guide_legend(nrow=1,byrow=TRUE))
Al
aplicar filtros vinculados a eliminar registros sin georeferenciación,
el año 2019 sólo contiene casos mortales. Los años siguientes contienen
una mayor cantidad de casos de gravedad manteniendo ciertos patrones
para los siniestros fatales.
A continuación, se muestra un mapa interactivo para visualizar la
totalidad de casos registrados en el dataset con geometrías
espaciales.
factpal <- colorFactor(palette = c("#f55951","#000000"),
levels = siniestros_geo$gravedad)
oceanIcons <- iconList(
ship = makeIcon(
"https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Medical_hospital_emergency_beds.svg/240px-Medical_hospital_emergency_beds.svg.png",
"https://upload.wikimedia.org/wikipedia/commons/thumb/9/9a/Medical_hospital_emergency_beds.svg/240px-Medical_hospital_emergency_beds.svg.png",
18,
18
),
pirate = makeIcon(
"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Maki2-danger-24.svg/240px-Maki2-danger-24.svg.png",
"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c3/Maki2-danger-24.svg/24px-Maki2-danger-24.svg.png",
24,
24
)
)
mapa_interactivo <- leaflet() %>%
addTiles() %>%
addMarkers(data = siniestros_geo, icon = ~oceanIcons, clusterOptions = markerClusterOptions(),popup = ~ glue::glue("<b>Estado</b>:{gravedad}<br/>
<b>Año:</b>{aaaa}<br/>
<b>Cantidad de Víctimas:</b>{n_victimas}<br/>
<b>Hora:</b>{hh}<br/>"))
mapa_interactivo
Modelado
En primer lugar procedemos a la confección de set de entrenamientos y
testeos considerando 0.8 como proporción y tomando el dataseet que no
contiene coordenadas geográficas.
#seteo la semilla
set.seed(123)
#divido los datos
sini_split <- initial_split(siniestros, prop = 0.80, strata = gravedad) #estratificamos segun las clases
sini_train <- training(sini_split)
sini_test <- testing(sini_split)
Folds con validación cruzada
set.seed(234)
sini_folds <- vfold_cv(sini_train, strata = gravedad)
sini_folds
sini_train
sini_test
sini_train %>%
count(gravedad)
sini_test %>%
count(gravedad)
Se verifica que las proporciones correspondientes a la distribución
de las categorías de la variable “gravedad” se conservan.
Receta
El siguiente paso es determinar la receta del modelo tomano
“gravedad” como la variable a calsificar.
logistic_recipe <-
recipe(formula = gravedad ~ ., data = sini_train) %>%
update_role(id_hecho, new_role = "id") %>%
step_unknown(all_nominal_predictors()) %>%
step_other(all_nominal_predictors(), threshold = 0.03) %>%
step_downsample(gravedad)
Modelización
Especificamos el modelo, en este caso, una regresión logística.
logistic_spec <- logistic_reg()
Workflow
Agregamos al Workflow la receta y la especificación del modelo.
log_workflow <- workflow() %>%
add_recipe(logistic_recipe) %>%
add_model(logistic_spec)
Entrenamiento del algoritmo
Con la función fit_resamples() procedemos a hacer el ajuste
correspondiente.
doParallel::registerDoParallel()
set.seed(7443)
logistic_rs <-
fit_resamples(log_workflow,
resamples = sini_folds,
control = control_resamples(save_pred = TRUE)
)
Predicción
En esta etapa con la función last_fit vamos a seleccionar el mejor
modelo para predecir en el set de datos de TEST.
final_fitted <- last_fit(log_workflow, sini_split)
final_fitted
Métricas
final_fitted %>%
collect_metrics()
Las métricas del modelo final arrojan cifras aceptables para la
precisión (78 %) y valores buenos para el área bajo la curva ROC.
Tambien muestra valores bajos para brier_class
Predicciones del modelo
final_fitted %>%
collect_predictions()
Matriz de confusión
Para finalizar y completar la consigna, vamos a crear una matriz de
confusión con las predicciones para contrastarla con los casos
reales.
final_fitted %>%
collect_predictions() %>%
conf_mat(gravedad, .pred_class)
## Truth
## Prediction Grave Mortal
## Grave 155 16
## Mortal 47 66
Como podemos ver la diagonal principal contiene la mayoría de los
casos, dejando en valores bajos los falsos positivos y los falsos
negativos.
Conclusión
A partir del empleo de técnicas de modelado, pudimos arribar una
problemática severa de la movilidad urbana como lo son los siniestros.
Con datos abiertos y técnicas de visualización y regresión logística
construimos un modelo que, a pesar de la escasa cantidad de datos,
obtuvo cifras de precisión considerables.
LS0tDQp0aXRsZTogIlRyYWJham8gRmluYWwgTWFjaGluZSBMZWFybmluZyINCmF1dGhvcjogIkp1YW4gR3VpbGxlcm1vIEZlcmNoZXJvIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIGNvZGVfZm9sZGluZzogaGlkZQ0KICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSkNCm9wdGlvbnMoc2NpcGVuID0gOTk5KQ0KYGBgDQoNCiMgVHJhYmFqbyBGaW5hbCBNYWNoaW5lIExlYXJuaW5nDQoNCiMjIEludHJvZHVjY2nDs246DQoNCkVzdGUgYW7DoWxpc2lzIGZvcm1hIHBhcnRlIGRlIGxhIGV2YWx1YWNpw7NuIGZpbmFsIGRlbCBtw7NkdWxvIE1hY2hpbmUgTGVhcm5pbmcuIExhIGlkZWEgZXMgbGxldmFyIGFkZWxhbnRlIHVuIGFuw6FsaXNpcyBkZSBsb3Mgc2luaWVzdHJvcyBlbiBsYSBDaXVkYWQgZGUgQnVlbm9zIEFpcmVzIHV0aWxpemFuZG8gbGEgdMOpY25pY2EgZGUgUmVncmVzacOzbiBMb2fDrXN0aWNhIHBhcmEgc3UgY2xhc2lmaWNhY2nDs24uIEVzdGUgdHJhYmFqbyBzZSBlbmN1ZW50cmEgZGlzcG9uaWJsZSBlbiBtaSBbYmxvZyBwZXJzb25hbF0oaHR0cHM6Ly9ycHVicy5jb20vR3VpbGxlRmVyY2hlcm8vdHBfbWwpDQoNCiMjIyBPYmpldGl2bzoNCg0KVmlzdWFsaXphciBlc3BhY2lhbG1lbnRlIGxvcyBzaW5pZXN0cm9zIGNvbnNpZGVyYWRvcyBkZSBncmF2ZWRhZCBvY3Vycmlkb3MgZW4gbGEgQ2l1ZGFkIEF1dMOzbm9tYSBkZSBCdWVub3MgQWlyZXMgdXRpbGl6YW5kbyBjb21vIGZ1ZW50ZSBsb3MgZGF0b3MgYWJpZXJ0b3MgcHVibGljYWRvcyBwb3IgZWwgbXVuaWNpcGlvLg0KDQpBbmFsaXphciB5IG1vZGVsYXIgYmFqbyBsYSBsw7NnaWNhIGRlIGNsYXNpZmljYWNpw7NuIG1lZGlhbnRlIHJlZ3Jlc2nDs24gbG9nw61zdGljYSBsYSBuYXR1cmFsZXphIGRlIGxvcyBhY2NpZGVudGVzIGRlIHRyYW5zaXRvIHkgc3UgbW9ydGFsaWRhZCBkdXJhbnRlIGVsIHBlcsOtb2RvIDIwMTktMjAyMy4NCg0KDQojIyMgTWV0b2RvbG9nw61hOg0KDQpQYXJhIGxsZXZhciBhZGVsYW50ZSBsb3Mgb2JqZXRpdm9zIHBsYW50ZWFkb3MgeSwgY29uc2lkZXJhbmRvIHF1ZSBsbyBxdWUgc2UgYnVzY2EgZXMgZW5jb250cmFyIHVuIG1vZGVsbyBkZSBjbGFzaWZpY2FjacOzbiBzZSB1dGlsaXphcsOhIGVsIGNvbmp1bnRvIGRlIHTDqWNuaWNhcyBlc3BlY8OtZmljYXMgZGUgcmVncmVzacOzbiBsb2fDrXN0aWNhLiBFc3RhIHTDqWNuaWNhIHNlIHV0aWxpemEgcGFyYSBjbGFzaWZpY2FyIHVuYSB2YXJpYWJsZSByZXNwdWVzdGEgcXVlLCBlbiBlc3RlIGNhc28gc2Vyw6EgZGljb3TDs21pY2EuIFBhcmEgZWxsbywgcmVzdWx0YXLDoSBkZSBncmFuIHV0aWxpZGFkIGxvcyByZXN1bHRhZG9zIHkgZWwgYW7DoWxpc2lzIGRlIGxhcyBlc3RhZMOtc3RpY2FzIGRlbCBtb2RlbG8gcHJvYmxlbWF0aXphbmRvIGVsIGdyYWRvIGRlIG1vcnRhbGlkYWQgZW4gbG9zIHNpbmllc3Ryb3MgYWNvbnRlY2lkb3MgZW4gQ0FCQS4NCg0KIyMjIEZ1ZW50ZXMgZGUgZGF0b3M6DQoNCkEgbG9zIGZpbmVzIGRlIGxvcyBpZGVhcyBwbGFudGVhZGFzLCB0cmFiYWphcmVtb3MgY29uIGxvcyBbZGF0b3MgZGUgc2luaWVzdHJvc10oaHR0cHM6Ly9kYXRhLmJ1ZW5vc2FpcmVzLmdvYi5hci9kYXRhc2V0L3ZpY3RpbWFzLXNpbmllc3Ryb3MtdmlhbGVzKSBvY3Vycmlkb3MgZHVyYW50ZSBlbCBwZXLDrW9kbyAyMDE5LTIwMjMuIFNlZ8O6biBsYXMgbm90YXMgbWV0b2RvbMOzZ2ljYXMsIGxhcyBjYXRlZ29yw61hcyBkZSBsYSB2YXJpYWJsZSAiZ3JhdmVkYWQiIHZhcmlhbiBlbjoNCg0KR1JBVkU6IFRvZGEgcGVyc29uYSBxdWUgY3V5YSBsZXNpw7NuIGV4aWdlIGxhIGhvc3BpdGFsaXphY2nDs24gZGUgYWwgbWVub3MgMjQgaHMgbyB1bmEgYXRlbmNpw7NuIGVzcGVjaWFsaXphZGEsIGNvbW8gZnJhY3R1cmFzLCBjb25tb2Npw7NuLCBzaG9jayBncmF2ZSB5IGxhY2VyYWNpb25lcyBpbXBvcnRhbnRlcy4NCk1PUlRBTDogVsOtY3RpbWEgcXVlIGZhbGxlY2UgZGVudHJvIGRlIGxvcyAzMCBkw61hcyBkZSBwcm9kdWNpZG8gZWwgc2luaWVzdHJvIHZpYWwgcG9yIGNhdXNhcyBkaXJlY3RhIG8gaW5kaXJlY3RhbWVudGUgYXRyaWJ1aWJsZXMgYWwgaGVjaG8uDQpTRDogU2luIGRhdG9zIHNvYnJlIGxhIGdyYXZlZGFkIGRlIGxhcyBsZXNpb25lcyBwcm92b2NhZGFzLiBBIGVmZWN0b3MgYW5hbMOtdGljb3MsIGxvcyBjYXNvcyBzaW4gZGF0b3Mgc2UgY29ycmVzcG9uZGVuIGNvbg0KdW5hIGFsdGEgcHJvYmFiaWxpZGFkIGEgY2Fzb3MgbGV2ZXMuDQoNClByb2NlZGVyZW1vcyBhIGNvbnNpZGVyYXIgc8OzbGFtZW50ZSBsb3MgY2Fzb3MgZGVjbGFyYWRvcyBjb21vICJncmF2ZXMiIG8gIm1vcnRhbGVzIiBhIGVmZWN0b3MgZGUgZW50cmVuYXIgdW4gbW9kZWxvIHF1ZSBwZXJtaXRhIGNsYXNpZmljYXJsb3MuIA0KDQoNCiMjIEluc3RhbGFtb3MgbGlicmVyaWFzDQoNCmBgYHtyIGxpYnJlcmlhcywgd2FybmluZz1GQUxTRX0NCg0Kcm0obGlzdCA9IGxzKCkpIA0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQojIEluc3RhbGFtb3MgbGlicmVyw61hcyBkZSB0cmFiYWpvDQppZiAoIXJlcXVpcmUoInBhY21hbiIpKSBpbnN0YWxsLnBhY2thZ2VzKCJwYWNtYW4iKQ0KcGFjbWFuOjpwX2xvYWQoImx1YnJpZGF0ZSIsDQogICAgICAgICAgICAgICAidGlkeXZlcnNlIiwNCiAgICAgICAgICAgICAgICJsZWFmbGV0IiwNCiAgICAgICAgICAgICAgICJzZiIsDQogICAgICAgICAgICAgICAidGlkeW1vZGVscyIsDQogICAgICAgICAgICAgICAic3BhdGlhbHNhbXBsZSIsDQogICAgICAgICAgICAgICAidGhlbWlzIiwNCiAgICAgICAgICAgICAgICJnZ21hcCIsDQogICAgICAgICAgICAgICAic2hvd3RleHQiLA0KICAgICAgICAgICAgICAgImdndGV4dCIpDQoNCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpgYGANCg0KDQojIyBMaW1waWV6YSB5IHRyYW5zZm9ybWFjacOzbg0KDQpBIGxvcyBmaW5lcyBkZSBhYm9yZGFyIGxhcyBkaXN0aW50YXMgZm9ybWFzIGRlIGFuYWxpemFyIGxvcyBkYXRvcyBnZW5lcmFyZW1vcyBkb3MgZGF0YXNldHMsIHVubyBlc3BhY2lhbCBwYXJhIG1hcGVhciBsb3Mgc3VjZXNvcyB5IG90cm8gZGF0YXNldCBzaW4gZ2VuZXJhciBnZW9tZXRyw61hcyBlc3BhY2lhbGVzIHBhcmEgY29uc3RydWlyIGVsIG1vZGVsby4gU2UgYWR2aWVydGUgcXVlLCBkZWJpZG8gYSBsb3MgZmlsdHJvcyBhcGxpY2Fkb3MsIGVsIGRhdGFzZXQgZXNwYWNpYWwgY3VlbnRhIGNvbiAxNTA3IGNhc29zLCBtaWVudHJhcyBxdWUgZWwgZGF0YXNldCBzaW4gY29uc2lkZXJhciBnZW9tZXRyw61hcyBlc3BhY2lhbGVzIGNvbnRpZW5lIDE4NDQgc2luaXNldHJvcy4gDQoNCkxldmFudGFtb3MgbG9zIGRhdG9zIHkgcmVhbGl6YW1vcyB1bmEgbGltcGllemEgeSByZWNvZGlmaWNhY2nDs24gZGUgbGFzIHZhcmlhYmxlcyBkZSBpbnRlcsOpcyBoYWNpZW5kbyBoaW5jYXBpw6kgZW4gbGEgdmFyaWFibGUgb2JqZXRpdm8gcXVlIGVzICJncmF2ZWRhZCIgbGEgY3VhbCBlbiB1bmEgcHJpbWVyYSB2aXN0YSwgdGllbmUgZGF0b3MgZW4gbWF5w7pzY3VsYXMgeSBtaW7DunNjdWxhcyBlbiB1biBjbGFybyBlcnJvciBkZSBpbmdyZXNvIGRlIGRhdG9zLiBUYW1iaWVuIHByb2NlZGVtb3MgYSByZWNvZGlmaWNhciBsYSB2YXJpYWJsZSBob3JhIChoaCkgc2Vnw7puIGxvcyBtb21lbnRvcyBkZWwgZMOtYS4gDQoNCmBgYHtyIGRhdGEsIHdhcm5pbmc9RkFMU0V9DQoNCnNpbmllc3Ryb3NfZ2VvIDwtIHJlYWQuY3N2KCJkYXRhL3Npbmllc3Ryb3NfdmlhbGVzX2hlY2hvcy5jc3YiLCBzZXAgPSAiOyIpICU+JSANCiAgZmlsdGVyKCFsYXRpdHVkICVpbiUgYygiU0QiLCAiIiwic2QiKSwNCiAgICAgICAgICFncmF2ZWRhZCAlaW4lIGMoIlNEIiwic2QiKSkgJT4lIA0KICBtdXRhdGUobG9uZ2l0dWQgPSBhcy5udW1lcmljKGxvbmdpdHVkKSwNCiAgICAgICAgIGxhdGl0dWQgPSBhcy5udW1lcmljKGxhdGl0dWQpLA0KICAgICAgICAgaGggPSBhcy5udW1lcmljKGhoKSwNCiAgICAgICAgICJIb3JhUmVjIiA9IGNhc2Vfd2hlbihoaCA+PSAwICYgaGggPCA2IH4gIk1hZHJ1Z2FkYSIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhoID49IDYgJiBoaCA8IDEyIH4gIk1hw7FhbmEiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhoID49IDEyICYgaGggPCAxOCB+ICJUYXJkZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVFJVRSB+ICJOb2NoZSIgKSwNCiAgICAgICAgIGdyYXZlZGFkID0gY2FzZV93aGVuKGdyYXZlZGFkICVpbiUgIGMoIkdSQVZFIiwiZ3JhdmUiKSB+ICJHcmF2ZSIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBncmF2ZWRhZCA9PSAiTU9SVEFMIiB+ICJNb3J0YWwiKSklPiUNCiAgZmlsdGVyKCFpcy5uYShsYXRpdHVkKSkgJT4lIA0KICBzdF9hc19zZihjb29yZHMgPWMoImxvbmdpdHVkIiwgImxhdGl0dWQiKSwgY3JzID0gNDMyNikgJT4lIA0KICBzZWxlY3QoIWMoZGlyZWNjaW9uX25vcm1hbGl6YWRhLGZlY2hhLGNhbGxlLCBhbHR1cmEsIGNydWNlLCBvdHJhX2RpcmVjY2lvbiwgaG9yYSwgY29tdW5hLCB0aXBvX2RlX2NhbGxlLCBnZW9jb2RpZmljYWNpb25fY2FiYSx0aXBvX2RlX2RhdG8sIHBhcnRpY2lwYW50ZXMpKQ0KDQoNCnNpbmllc3Ryb3MgPC0gcmVhZC5jc3YoImRhdGEvc2luaWVzdHJvc192aWFsZXNfaGVjaG9zLmNzdiIsIHNlcCA9ICI7IikgJT4lDQogIGZpbHRlcighZ3JhdmVkYWQgJWluJSBjKCJTRCIsInNkIikpICU+JSANCiAgbXV0YXRlKGxvbmdpdHVkID0gYXMubnVtZXJpYyhsb25naXR1ZCksDQogICAgICAgICBsYXRpdHVkID0gYXMubnVtZXJpYyhsYXRpdHVkKSwNCiAgICAgICAgIGhoID0gYXMubnVtZXJpYyhoaCksDQogICAgICAgICAiSG9yYVJlYyIgPSBjYXNlX3doZW4oaGggPj0gMCAmIGhoIDwgNiB+ICJNYWRydWdhZGEiLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaCA+PSA2ICYgaGggPCAxMiB+ICJNYcOxYW5hIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoaCA+PSAxMiAmIGhoIDwgMTggfiAiVGFyZGUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFRSVUUgfiAiTm9jaGUiICksDQogICAgICAgICBncmF2ZWRhZCA9IGNhc2Vfd2hlbihncmF2ZWRhZCAlaW4lICBjKCJHUkFWRSIsImdyYXZlIikgfiAiR3JhdmUiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ3JhdmVkYWQgPT0gIk1PUlRBTCIgfiAiTW9ydGFsIikpJT4lDQogIHNlbGVjdCghYyhkaXJlY2Npb25fbm9ybWFsaXphZGEsZmVjaGEsY2FsbGUsIGFsdHVyYSwgY3J1Y2UsIG90cmFfZGlyZWNjaW9uLCBob3JhLCBjb211bmEsIHRpcG9fZGVfY2FsbGUsIGdlb2NvZGlmaWNhY2lvbl9jYWJhLHRpcG9fZGVfZGF0bywgcGFydGljaXBhbnRlcykpDQoNCg0KY29tdW5hcyA8LSBzdF9yZWFkKCJkYXRhL2NvbXVuYXMuZ2Vvc29uIiwgDQogICAgICAgICAgICAgICAgICBjcnMgPSA0MzI2KQ0KDQoNCmBgYA0KDQoNCmBgYHtyIGdncGxvdDEsIHdhcm5pbmc9RkFMU0V9DQoNCnRhYmxhXzEgPC0gc2luaWVzdHJvcyAlPiUgDQogIGdyb3VwX2J5KGFhYWEsIGdyYXZlZGFkKSAlPiUgDQogIHN1bW1hcmlzZSh0b3QgPSBuKCkpICU+JSANCiAgbXV0YXRlKHBvcmNlbnRhamUgPSByb3VuZCh0b3Qvc3VtKHRvdCksNCkpICU+JSANCiAgbXV0YXRlKFRvdGFsX0FjdW0gPSBjdW1zdW0odG90KSkgJT4lIA0KICBtdXRhdGUoUG9yY2VudGFqZV9BY3VtID0gY3Vtc3VtKHBvcmNlbnRhamUpKQ0KDQpnZ3Bsb3QodGFibGFfMSwgYWVzKHggPSBhYWFhLCB5ID0gdG90LCBmaWxsID0gZ3JhdmVkYWQpKSsNCiAgZ2VvbV9jb2wocG9zaXRpb24gPSAiZG9kZ2UiKSsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHBlcmNlbnQocG9yY2VudGFqZSkpLCB2anVzdCA9IC0wLjUsIGNoZWNrX292ZXJsYXAgPSBUUlVFLCBwb3NpdGlvbiA9IHBvc2l0aW9uX2RvZGdlKHdpZHRoID0gLjkpLCBzaXplID0gMykrDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGMoIiNmNTU5NTEiLCIjMDAwMDAwIikpKw0KICB0aGVtZV9taW5pbWFsKCkrDQogIGxhYnMoDQogICAgdGl0bGUgPSAiPHNwYW4gc3R5bGUgPSAnY29sb3I6IzI3MjY0MzsnPiBFdm9sdWNpw7NuIGFudWFsIGRlIHNpbmllc3Ryb3M6IDxzcGFuIHN0eWxlID0gJ2NvbG9yOiNmNTU5NTE7Jz4gR3JhdmVzIDxzcGFuIHN0eWxlID0gJ2NvbG9yOiM1NDNjNTI7Jz4gLSA8c3BhbiBzdHlsZSA9ICdjb2xvcjojMDAwMDAwOyc+IE1vcnRhbGVzIiwNCiAgICBzdWJ0aXRsZSA9ICIiLA0KICAgIGNhcHRpb24gPSAiPGI+RnVlbnRlPC9iPjogUmVOYUJhUCIsDQogICAgeCA9ICJBw7FvIiwNCiAgICB5ID0gIkJhcnJpb3MgUG9wdWxhcmVzIg0KICApKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfbWFya2Rvd24oZmFjZSA9ICJib2xkIiwgc2l6ZSA9IDE1KSwNCiAgICBwbG90LmNhcHRpb24gPSBlbGVtZW50X21hcmtkb3duKHNpemUgPSAxMiksDQogICkNCg0KDQpgYGANCg0KDQoNCg0KDQpgYGB7ciBtYXBhIGdncGxvdCwgd2FybmluZz1GQUxTRX0NCg0KZ2dwbG90KCkrDQogIGdlb21fc2YoZGF0YT1jb211bmFzKSsNCiAgZ2VvbV9zZihkYXRhPXNpbmllc3Ryb3NfZ2VvLCBhZXMoY29sb3I9Z3JhdmVkYWQpLCBhbHBoYT0wLjc1LCBzaG93LmxlZ2VuZCA9IFRSVUUpKw0KICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygiI2Y1NTk1MSIsIiMwMDAwMDAiKSkrDQogIHRoZW1lX21pbmltYWwoKSsNCiAgdGhlbWVfdm9pZCgpKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIjxzcGFuIHN0eWxlID0gJ2NvbG9yOiMyNzI2NDM7Jz4gU2luaWVzdHJvcyB2aWFsZXMgLSBDQUJBIDIwMTktMjAyMyIsDQogICAgY2FwdGlvbiA9ICI8Yj5GdWVudGU8L2I+OiBEYXRvcyBBYmllcnRvcyBDQUJBIiwNCiAgICB5ID0gIiIsDQogICAgeCA9ICIiDQogICkrDQogIHRoZW1lKA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X21hcmtkb3duKHNpemUgPSAyMCwgaGp1c3QgPSAwLjUpLA0KICAgIHBsb3QuY2FwdGlvbj0gZWxlbWVudF9tYXJrZG93bihzaXplID0gMTIpLA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJ0b3AiLA0KICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwNCiAgICBsZWdlbmQuYm94Lm1hcmdpbiA9IG1hcmdpbigxMSwgMTYsIDYsIDYpDQogICkrDQogIGZhY2V0X3dyYXAofmFhYWEsIG5yb3cgPSAxKSsNCiAgZ3VpZGVzKGZpbGw9Z3VpZGVfbGVnZW5kKG5yb3c9MSxieXJvdz1UUlVFKSkNCg0KDQpgYGANCkFsIGFwbGljYXIgZmlsdHJvcyB2aW5jdWxhZG9zIGEgZWxpbWluYXIgcmVnaXN0cm9zIHNpbiBnZW9yZWZlcmVuY2lhY2nDs24sIGVsIGHDsW8gMjAxOSBzw7NsbyBjb250aWVuZSBjYXNvcyBtb3J0YWxlcy4gTG9zIGHDsW9zIHNpZ3VpZW50ZXMgY29udGllbmVuIHVuYSBtYXlvciBjYW50aWRhZCBkZSBjYXNvcyBkZSBncmF2ZWRhZCBtYW50ZW5pZW5kbyBjaWVydG9zIHBhdHJvbmVzIHBhcmEgbG9zIHNpbmllc3Ryb3MgZmF0YWxlcy4gDQoNCkEgY29udGludWFjacOzbiwgc2UgbXVlc3RyYSB1biBtYXBhIGludGVyYWN0aXZvIHBhcmEgdmlzdWFsaXphciBsYSB0b3RhbGlkYWQgZGUgY2Fzb3MgcmVnaXN0cmFkb3MgIGVuIGVsIGRhdGFzZXQgY29uIGdlb21ldHLDrWFzIGVzcGFjaWFsZXMuIA0KDQpgYGB7ciBtYXBhIGRlc2NyaXB0aXZvLCB3YXJuaW5nPUZBTFNFfQ0KDQpmYWN0cGFsIDwtIGNvbG9yRmFjdG9yKHBhbGV0dGUgPSBjKCIjZjU1OTUxIiwiIzAwMDAwMCIpLCANCiAgICAgICAgICAgICAgIGxldmVscyA9IHNpbmllc3Ryb3NfZ2VvJGdyYXZlZGFkKQ0KDQpvY2Vhbkljb25zIDwtIGljb25MaXN0KA0KICBzaGlwID0gbWFrZUljb24oDQogICAgImh0dHBzOi8vdXBsb2FkLndpa2ltZWRpYS5vcmcvd2lraXBlZGlhL2NvbW1vbnMvdGh1bWIvOS85YS9NZWRpY2FsX2hvc3BpdGFsX2VtZXJnZW5jeV9iZWRzLnN2Zy8yNDBweC1NZWRpY2FsX2hvc3BpdGFsX2VtZXJnZW5jeV9iZWRzLnN2Zy5wbmciLA0KICAgICJodHRwczovL3VwbG9hZC53aWtpbWVkaWEub3JnL3dpa2lwZWRpYS9jb21tb25zL3RodW1iLzkvOWEvTWVkaWNhbF9ob3NwaXRhbF9lbWVyZ2VuY3lfYmVkcy5zdmcvMjQwcHgtTWVkaWNhbF9ob3NwaXRhbF9lbWVyZ2VuY3lfYmVkcy5zdmcucG5nIiwNCiAgICAxOCwNCiAgICAxOA0KICApLA0KICBwaXJhdGUgPSBtYWtlSWNvbigNCiAgICAiaHR0cHM6Ly91cGxvYWQud2lraW1lZGlhLm9yZy93aWtpcGVkaWEvY29tbW9ucy90aHVtYi9jL2MzL01ha2kyLWRhbmdlci0yNC5zdmcvMjQwcHgtTWFraTItZGFuZ2VyLTI0LnN2Zy5wbmciLA0KICAgICJodHRwczovL3VwbG9hZC53aWtpbWVkaWEub3JnL3dpa2lwZWRpYS9jb21tb25zL3RodW1iL2MvYzMvTWFraTItZGFuZ2VyLTI0LnN2Zy8yNHB4LU1ha2kyLWRhbmdlci0yNC5zdmcucG5nIiwNCiAgICAyNCwNCiAgICAyNA0KICApDQopDQoNCg0KIG1hcGFfaW50ZXJhY3Rpdm8gPC0gbGVhZmxldCgpICU+JQ0KICAgICBhZGRUaWxlcygpICU+JQ0KICAgICBhZGRNYXJrZXJzKGRhdGEgPSBzaW5pZXN0cm9zX2dlbywgaWNvbiA9IH5vY2Vhbkljb25zLCBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCkscG9wdXAgPSB+IGdsdWU6OmdsdWUoIjxiPkVzdGFkbzwvYj46e2dyYXZlZGFkfTxici8+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxiPkHDsW86PC9iPnthYWFhfTxici8+DQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxiPkNhbnRpZGFkIGRlIFbDrWN0aW1hczo8L2I+e25fdmljdGltYXN9PGJyLz4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGI+SG9yYTo8L2I+e2hofTxici8+IikpDQoNCm1hcGFfaW50ZXJhY3Rpdm8NCg0KDQpgYGANCg0KIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQoNCiMjIE1vZGVsYWRvDQoNCkVuIHByaW1lciBsdWdhciBwcm9jZWRlbW9zIGEgbGEgY29uZmVjY2nDs24gZGUgc2V0IGRlIGVudHJlbmFtaWVudG9zIHkgdGVzdGVvcyBjb25zaWRlcmFuZG8gMC44IGNvbW8gcHJvcG9yY2nDs24geSB0b21hbmRvIGVsIGRhdGFzZWV0IHF1ZSBubyBjb250aWVuZSBjb29yZGVuYWRhcyBnZW9ncsOhZmljYXMuIA0KDQpgYGB7ciBzcGwsIHdhcm5pbmc9RkFMU0V9DQojc2V0ZW8gbGEgc2VtaWxsYQ0Kc2V0LnNlZWQoMTIzKQ0KI2RpdmlkbyBsb3MgZGF0b3MNCnNpbmlfc3BsaXQgPC0gaW5pdGlhbF9zcGxpdChzaW5pZXN0cm9zLCBwcm9wID0gMC44MCwgc3RyYXRhID0gZ3JhdmVkYWQpICNlc3RyYXRpZmljYW1vcyBzZWd1biBsYXMgY2xhc2VzIA0Kc2luaV90cmFpbiA8LSB0cmFpbmluZyhzaW5pX3NwbGl0KQ0Kc2luaV90ZXN0IDwtIHRlc3Rpbmcoc2luaV9zcGxpdCkNCmBgYA0KDQojIyBGb2xkcyBjb24gdmFsaWRhY2nDs24gY3J1emFkYQ0KDQpgYGB7ciBzcGxfZiwgd2FybmluZz1GQUxTRX0NCnNldC5zZWVkKDIzNCkNCnNpbmlfZm9sZHMgPC0gdmZvbGRfY3Yoc2luaV90cmFpbiwgc3RyYXRhID0gZ3JhdmVkYWQpDQpzaW5pX2ZvbGRzDQpgYGANCg0KDQpgYGB7cn0NCnNpbmlfdHJhaW4NCmBgYA0KDQpgYGB7cn0NCnNpbmlfdGVzdCANCmBgYA0KDQoNCmBgYHtyfQ0Kc2luaV90cmFpbiAlPiUNCiAgY291bnQoZ3JhdmVkYWQpDQpgYGANCg0KDQoNCmBgYHtyfQ0Kc2luaV90ZXN0ICU+JQ0KICBjb3VudChncmF2ZWRhZCkNCmBgYA0KDQpTZSB2ZXJpZmljYSBxdWUgbGFzIHByb3BvcmNpb25lcyBjb3JyZXNwb25kaWVudGVzIGEgbGEgZGlzdHJpYnVjacOzbiBkZSBsYXMgY2F0ZWdvcsOtYXMgZGUgbGEgdmFyaWFibGUgImdyYXZlZGFkIiBzZSBjb25zZXJ2YW4uDQoNCiMjIFJlY2V0YQ0KDQpFbCBzaWd1aWVudGUgcGFzbyBlcyBkZXRlcm1pbmFyIGxhIHJlY2V0YSBkZWwgbW9kZWxvIHRvbWFubyAiZ3JhdmVkYWQiIGNvbW8gbGEgdmFyaWFibGUgYSBjYWxzaWZpY2FyLiANCg0KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxvZ2lzdGljX3JlY2lwZSA8LQ0KICByZWNpcGUoZm9ybXVsYSA9IGdyYXZlZGFkIH4gLiwgZGF0YSA9IHNpbmlfdHJhaW4pICU+JQ0KICB1cGRhdGVfcm9sZShpZF9oZWNobywgbmV3X3JvbGUgPSAiaWQiKSAlPiUNCiAgc3RlcF91bmtub3duKGFsbF9ub21pbmFsX3ByZWRpY3RvcnMoKSkgJT4lDQogIHN0ZXBfb3RoZXIoYWxsX25vbWluYWxfcHJlZGljdG9ycygpLCB0aHJlc2hvbGQgPSAwLjAzKSAlPiUNCiAgc3RlcF9kb3duc2FtcGxlKGdyYXZlZGFkKQ0KYGBgDQoNCg0KDQojIyBNb2RlbGl6YWNpw7NuDQoNCkVzcGVjaWZpY2Ftb3MgZWwgbW9kZWxvLCBlbiBlc3RlIGNhc28sIHVuYSByZWdyZXNpw7NuIGxvZ8Otc3RpY2EuIA0KDQpgYGB7cn0NCmxvZ2lzdGljX3NwZWMgPC0gbG9naXN0aWNfcmVnKCkNCmBgYA0KDQoNCg0KIyMgV29ya2Zsb3cNCg0KQWdyZWdhbW9zIGFsIFdvcmtmbG93IGxhIHJlY2V0YSB5IGxhIGVzcGVjaWZpY2FjacOzbiBkZWwgbW9kZWxvLiANCg0KYGBge3J9DQpsb2dfd29ya2Zsb3cgPC0gd29ya2Zsb3coKSAlPiUNCiAgYWRkX3JlY2lwZShsb2dpc3RpY19yZWNpcGUpICU+JQ0KICBhZGRfbW9kZWwobG9naXN0aWNfc3BlYykNCg0KYGBgDQoNCg0KDQoNCiMjIEVudHJlbmFtaWVudG8gZGVsIGFsZ29yaXRtbw0KDQpDb24gbGEgZnVuY2nDs24gZml0X3Jlc2FtcGxlcygpIHByb2NlZGVtb3MgYSBoYWNlciBlbCBhanVzdGUgY29ycmVzcG9uZGllbnRlLg0KDQoNCmBgYHtyfQ0KZG9QYXJhbGxlbDo6cmVnaXN0ZXJEb1BhcmFsbGVsKCkgDQpzZXQuc2VlZCg3NDQzKQ0KbG9naXN0aWNfcnMgPC0NCiAgZml0X3Jlc2FtcGxlcyhsb2dfd29ya2Zsb3csDQogICAgcmVzYW1wbGVzID0gc2luaV9mb2xkcywNCiAgICBjb250cm9sID0gY29udHJvbF9yZXNhbXBsZXMoc2F2ZV9wcmVkID0gVFJVRSkNCiAgKQ0KDQpgYGANCg0KDQojIyBQZXJmb3JtYW5jZQ0KDQpgYGB7cn0NCmxvZ2lzdGljX3JzICU+JQ0KICBjb2xsZWN0X21ldHJpY3MoKQ0KYGBgDQoNCkxhIHBlcmZvcm1hbmNlIG11ZXN0cmEgcmVzdWx0YWRvcyBhY2VwdGFibGVzIGVuIGVsIHNldCBkZSBlbnRyZW5hbWllbnRvLiAgTGEgcHJlY2lzacOzbiBjYXNpIGFsY2FuemEgZWwgODAlLCBtaWVudHJhcyBxdWUgZWwgw6FyZWEgYmFqbyBsYSBjdXJ2YSBST0MgbGxlZ2EgYWwgdmFsb3IgZGUgMC44OC4gRWwgYnJpZXJfY2xhc3MgYXJyb2phIHZhbG9yZXMgYmFqb3MsIGxvIGN1YWwgZXMgYnVlbm8gcGFyYSBudWVzdHJvIG1vZGVsbw0KDQoNCmBgYHtyfQ0KY29sbGVjdF9wcmVkaWN0aW9ucyhsb2dpc3RpY19ycykgJT4lDQogIGdyb3VwX2J5KGlkKSAlPiUNCiAgcm9jX2N1cnZlKGdyYXZlZGFkLCAucHJlZF9HcmF2ZSkgJT4lDQogIGF1dG9wbG90KCkNCmBgYA0KDQojIyBQcmVkaWNjacOzbg0KDQpFbiBlc3RhIGV0YXBhIGNvbiBsYSBmdW5jacOzbiBsYXN0X2ZpdCB2YW1vcyBhIHNlbGVjY2lvbmFyIGVsIG1lam9yIG1vZGVsbyBwYXJhIHByZWRlY2lyIGVuIGVsIHNldCBkZSBkYXRvcyBkZSBURVNULiANCg0KDQpgYGB7cn0NCmZpbmFsX2ZpdHRlZCA8LSBsYXN0X2ZpdChsb2dfd29ya2Zsb3csIHNpbmlfc3BsaXQpDQpmaW5hbF9maXR0ZWQgDQpgYGANCg0KDQojIyBNw6l0cmljYXMNCg0KDQoNCmBgYHtyfQ0KZmluYWxfZml0dGVkICU+JQ0KICBjb2xsZWN0X21ldHJpY3MoKQ0KYGBgDQoNCg0KTGFzIG3DqXRyaWNhcyBkZWwgbW9kZWxvIGZpbmFsIGFycm9qYW4gY2lmcmFzIGFjZXB0YWJsZXMgcGFyYSBsYSBwcmVjaXNpw7NuICg3OCAlKSB5IHZhbG9yZXMgYnVlbm9zIHBhcmEgZWwgw6FyZWEgYmFqbyBsYSBjdXJ2YSBST0MuIFRhbWJpZW4gbXVlc3RyYSB2YWxvcmVzIGJham9zIHBhcmEgYnJpZXJfY2xhc3MNCg0KIyMgUHJlZGljY2lvbmVzIGRlbCBtb2RlbG8NCg0KYGBge3J9DQpmaW5hbF9maXR0ZWQgJT4lDQogIGNvbGxlY3RfcHJlZGljdGlvbnMoKQ0KYGBgDQoNCiMjIE1hdHJpeiBkZSBjb25mdXNpw7NuDQoNClBhcmEgZmluYWxpemFyIHkgY29tcGxldGFyIGxhIGNvbnNpZ25hLCB2YW1vcyBhIGNyZWFyIHVuYSBtYXRyaXogZGUgY29uZnVzacOzbiBjb24gbGFzIHByZWRpY2Npb25lcyBwYXJhIGNvbnRyYXN0YXJsYSBjb24gbG9zIGNhc29zIHJlYWxlcy4gDQoNCg0KYGBge3J9DQpmaW5hbF9maXR0ZWQgJT4lDQogIGNvbGxlY3RfcHJlZGljdGlvbnMoKSAlPiUNCiAgY29uZl9tYXQoZ3JhdmVkYWQsIC5wcmVkX2NsYXNzKSANCmBgYA0KDQpDb21vIHBvZGVtb3MgdmVyIGxhIGRpYWdvbmFsIHByaW5jaXBhbCBjb250aWVuZSBsYSBtYXlvcsOtYSBkZSBsb3MgY2Fzb3MsIGRlamFuZG8gZW4gdmFsb3JlcyBiYWpvcyBsb3MgZmFsc29zIHBvc2l0aXZvcyB5IGxvcyBmYWxzb3MgbmVnYXRpdm9zLg0KDQoNCiMjIENvbmNsdXNpw7NuDQoNCkEgcGFydGlyIGRlbCBlbXBsZW8gZGUgdMOpY25pY2FzIGRlIG1vZGVsYWRvLCBwdWRpbW9zIGFycmliYXIgdW5hIHByb2JsZW3DoXRpY2Egc2V2ZXJhIGRlIGxhIG1vdmlsaWRhZCB1cmJhbmEgY29tbyBsbyBzb24gbG9zIHNpbmllc3Ryb3MuIENvbiBkYXRvcyBhYmllcnRvcyB5IHTDqWNuaWNhcyBkZSB2aXN1YWxpemFjacOzbiB5IHJlZ3Jlc2nDs24gbG9nw61zdGljYSBjb25zdHJ1aW1vcyB1biBtb2RlbG8gcXVlLCBhIHBlc2FyIGRlIGxhIGVzY2FzYSBjYW50aWRhZCBkZSBkYXRvcywgb2J0dXZvIGNpZnJhcyBkZSBwcmVjaXNpw7NuIGNvbnNpZGVyYWJsZXMuIA0KDQoNCg0KDQoNCg0K