1. Introducción

Hemos visto en tutoriales anteriores como cargar, manejar, arreglar y visualizar datos à la tidyverse. Una vez somos capaces de manejar y arreglar nuestros datos con R podemos pasar a intentar efectuar un análisis “realista” con un conjunto de datos.

En este tutorial y siguientes, vamos a utilizar un conjunto de datos sobre nacimientos de bebes en España para mostrar algunas técnicas de EDA y modelización. No se mostrarán los detalles técnicos de las técnicas, sino solo su aplicación práctica y, a veces, la interpretación de los resultados.

Antes de proceder a la estimación de modelos estadísticos formales o contrastar hipótesis se suele realizar una etapa conocida como análisis exploratorio. El análisis exploratorio (EDA) es una parte importante de todo análisis de datos. Suele tener lugar antes de la especificación y estimación de modelos estadísticos formales. Su objetivo último es “comprender” los datos y las relaciones existentes entre las variables.

Una de las máximas de un data scientist es, como señalan aquí, “Know Your Data”. Además nos explican que:

Data exploration is the art of looking at your data, rapidly generating hypotheses, quickly testing them, then repeating again and again and again. The goal of data exploration is to generate many promising leads that you can later explore in more depth.

With exploratory data analysis, you’ll combine visualisation and transformation with your curiosity and scepticism to ask and answer interesting questions about data

Un buen ejemplo de qué es un análisis exploratorio son los “screencast” de David Robinson: cada semana graba un vídeo con un análisis rápido de un conjunto de datos. Puedes verlos, en su canal de youtube ,aquí. En este repo de Github tienes la transcripción del código, en ficheros .Rmd que acaba utilizando en los screencasts. Como hay mucha gente que sigue sus vídeos hay, como explican en este post, varias personas que mantienen un listado diseccionando las tareas que hace David en cada screencast, puedes encontrarlos aquí y aquí.

Por último, un libro, bueno un bookdown, sobre EDA: Exploratory Data Analysis with R de Roger Peng.

¿Qué queremos saber?

Generalmente un estudio cuantitativo comienza por una pregunta(s) que guía el análisis. En nuestro caso, el objetivo del tutorial es meramente mostrar algunas técnicas/funciones útiles en la fase de exploración de datos (EDA) de un estudio, aún así, nuestro “estudio” estará guiado por varias preguntas relacionadas con los bebes; por ejemplo: 1) vamos a intentar ver/contestar si los bebitos nacen con más peso que las bebitas 2) intentaremos contestar a la pregunta(s) de si determinadas características de las madres/padres, como la edad o el nivel educativo, afectan al peso de los recién nacidos.

El siguiente paso de todo análisis empírico es la búsqueda de un conjunto de datos que permita (hasta cierto grado de confianza) dar una respuesta a las preguntas planteadas. ¿De donde sacamos esos datos? En nuestro caso, utilizaremos la estadística de nacimientos del INE. Los detalles en la sección siguiente.



2. Los datos

Origen de los datos

Los datos provienen del INE, concretamente de la Estadística de nacimientos. Como puedes ver en la web del INE hay una serie de tablas que muestran resultados para las principales variables; por ejemplo, hay una tabla llamada “Nacimientos por edad de la madre, mes y sexo”. Estas tablas están muy bien para mostrar los principales resultados de la Estadística de nacimientos, pero nosotros queremos analizar “de verdad” los datos, así que tendremos que descargarnos los microdatos de la encuesta.

En los microdatos de la Estadística de Nacimientos hay datos sobre: (i) Nacimientos, (ii) Muertes fetales tardías y (iii) Partos. Vamos a usar los ficheros de datos sobre PARTOS.

Hay un fichero para cada año desde 1975, pero como han habido diversos cambios en la metodología de la estadística, solo vamos a trabajar los ficheros de los años 2007 a 2018 que comparten diccionario.

Me descargue (a mano) los ficheros de microdatos de nacimientos para los años 2007 a 2018. Los datos están en formato texto, y cada registro es una cadena larga de caracteres. Los datos van acompañados de un diccionario que sirve para poder separar las cadenas de caracteres en los valores de cada variable. Os ahorro los detalles del proceso.

Los datos eran de partos, pero los preparé para que cada fila pertenezca a los datos de un solo bebe: cada bebe tiene su propia fila.

Cargando el fichero de datos y dicc

Mi ordenador tuvo problemas para mover el fichero con todos los datos (2007 a 2018), y, como además, el objetivo del tutorial es tan sólo mostrar algunas técnicas y funciones útiles para EDA, por lo que solamente se utilizarán los datos referentes a 2017. Adicionalmente, se restringió la muestra a partos con un solo bebe que habían tenido lugar en un centro sanitario.

df <- rio::import(here::here("datos", "df_bebes_2017.rds"))             #- 374.933 x 13
df_dicc <- rio::import(here::here("datos", "df_bebes_2017_dicc.xlsx"))  #- 13 x 18

Con el anterior chunk hemos cargado el fichero de datos df, que cuenta con 374.933 registros de bebes y 13 columnas o variables para cada bebe.



3. Cosas de EDA

Hemos visto en tutoriales anteriores como cargar, arreglar y manejar datos con R à la tidyverse. En este tutorial vamos a ver algunos paquetes y funciones que conviene conocer porque nos pueden ayudar a acelerar el análisis exploratorio inicial de nuestros datos.

En este repo de Github, Mateusz Staniak mantiene un listado de paquetes y recursos relacionados con la EDA.

En este post y en este artículo nos hablan del paquete autoEDA que trata de automatizar el proceso inicial exploratorio de datos (EDA). Además el post nos muestra los 11 paquetes de R, relacionados con la EDA, más descargados de CRAN. El objetivo de estos 11/12 paquetes es similar, tratar de automatizar/facilitar el análisis preliminar o exploratorio de los datos (EDA). Evidentemente automatizar totalmente la EDA es muy-muy complicado, por no decir imposible, pero al menos estos paquetes (y otros muchos) contienen funciones que nos pueden ayudar en las primeras etapas de nuestros análisis de datos.

Por ejemplo, en este post, Laura Ellis, nos explica como hacer EDA en R mostrándonos algunas de sus funciones/trucos favoritos como la función skimr::skim(), visdat::vis_dat() o DataExplorer::create_report(). El post tuvo una segunda parte (aquí), donde Laura nos explica el uso de algunas funciones útiles para EDA del paquete inspectdf como por ejemplo: inspectdf::inspect_types(), inspectdf::inspect_na, etc…

En este otro post, Sharla Gelfand nos habla sobre estrategias para afrontar la EDA cuando se trabaja con un conjunto de datos nuevo. Sharla utiliza, entre otros, los paquetes visdat, skimr y assertr.

DE entre los muchos paquetes y funciones relacionados con EDA, conviene conocer, en mi opinión, estos1:


3.1 Manipular los nombres de las variables

Con los datos de bebes no vamos a cambiar los nombres, ya que son los nombres que les dio el INE y que figuran en el diccionario oficial, pero muchas veces hay que cambiar/arreglar los nombres de las variables, así que vamos a hacerlo con nuestro df, aunque al final volveremos a utilizar los nombres originales.

Podemos acceder a los nombres de las columnas de un data.frame con la función base::names(). Veámoslo:

nombres_originales <- names(df)  #- almacenamos los nombres originales en el vector nombres_originales

Veamos cuales son los nombres de las variables de df:

names(df)
#>  [1] "MESPAR"     "MUNPAR"     "MUNREM"     "NORMA.f"    "CESAREA.f" 
#>  [6] "SEXO1"      "SEMANAS"    "PESON1"     "EDADM"      "EDADP"     
#> [11] "ESTUDIOM.f" "ESTUDIOP.f" "PAISNXM"

Si quisieramos, por ejemplo, cambiar el nombre de la segunda variable, podemos hacerlo con:

names(df)[2] <- "nuevo_nombre_variable_2"
names(df)
#>  [1] "MESPAR"                  "nuevo_nombre_variable_2"
#>  [3] "MUNREM"                  "NORMA.f"                
#>  [5] "CESAREA.f"               "SEXO1"                  
#>  [7] "SEMANAS"                 "PESON1"                 
#>  [9] "EDADM"                   "EDADP"                  
#> [11] "ESTUDIOM.f"              "ESTUDIOP.f"             
#> [13] "PAISNXM"

Si tuviésemos un data.frame con nombres realmente extraños, incluso con nombres “no sintácticos”, y necesitamos arreglarlos de forma rápida, podemos hacerlo con la función janitor::clean_names(). Arregla los nombres de las variables de forma automática, además tiene algunas opciones para hacerlo como más te guste.

janitor::clean_names(df) %>% names()
#>  [1] "mespar"                  "nuevo_nombre_variable_2"
#>  [3] "munrem"                  "norma_f"                
#>  [5] "cesarea_f"               "sexo1"                  
#>  [7] "semanas"                 "peson1"                 
#>  [9] "edadm"                   "edadp"                  
#> [11] "estudiom_f"              "estudiop_f"             
#> [13] "paisnxm"

Volvamos a dejar los nombres originales de df:

names(df) <- nombres_originales


3.2 Estructura y tipo de variables

Siempre-siempre hay que saber de que tipo son nuestras variables. Esto se puede hacer de muchas formas. Os muestro dos:

a) con str()

str(df)            #- str() muestra la estructura interna de un objeto R
#> tibble [374,933 × 13] (S3: tbl_df/tbl/data.frame)
#>  $ MESPAR    : Factor w/ 12 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ...
#>  $ MUNPAR    : chr [1:374933] "28079" "26036" "14021" "47186" ...
#>  $ MUNREM    : chr [1:374933] NA "26018" "14021" "47186" ...
#>  $ NORMA.f   : Factor w/ 2 levels "Parto con complicaciones",..: 1 2 1 1 1 1 2 1 1 1 ...
#>  $ CESAREA.f : Factor w/ 3 levels "Con cesárea",..: 1 2 1 1 1 1 2 1 1 1 ...
#>  $ SEXO1     : Factor w/ 2 levels "bebita","bebito": 2 1 2 2 1 1 2 2 2 2 ...
#>  $ SEMANAS   : num [1:374933] 26 26 26 25 26 26 23 27 NA 28 ...
#>  $ PESON1    : num [1:374933] 420 500 540 560 592 600 600 600 635 650 ...
#>  $ EDADM     : num [1:374933] 39 30 31 36 42 41 36 32 34 40 ...
#>  $ EDADP     : num [1:374933] NA 32 31 36 42 44 39 30 26 47 ...
#>  $ ESTUDIOM.f: Factor w/ 3 levels "Primarios","Medios",..: NA 1 3 1 3 1 NA 1 1 NA ...
#>  $ ESTUDIOP.f: Factor w/ 3 levels "Primarios","Medios",..: NA 2 3 1 3 1 NA 1 1 NA ...
#>  $ PAISNXM   : chr [1:374933] "122" "228" "108" "407" ...


b) con inspectdf::inspect_types()

inspectdf::inspect_types(df)   #- muestra de que tipo son las variables
type cnt pcnt col_name
factor 6 46.15385 MESPAR , NORMA.f , CESAREA.f , SEXO1 , ESTUDIOM.f, ESTUDIOP.f
numeric 4 30.76923 SEMANAS, PESON1 , EDADM , EDADP
character 3 23.07692 MUNPAR , MUNREM , PAISNXM


c) 2 funciones útiles del paquete pjpv2020.01

La función pjpv2020.01::pjp_f_estadisticos_basicos(). Sí, el nombre de la función es un poco largo, pero es que es un paquete para uso personal. Yo estoy acostumbrado a usarla. Da un resumen rápido de las variables del data.frame:

zz <- pjpv2020.01::pjp_f_estadisticos_basicos(df) 
gt::gt(zz)
variable q_zeros p_zeros q_na p_na type unique min max mean sd NN NN_ok
MESPAR 0 0 0 0.00 factor 12 NA NA NA NA 374933 374933
MUNPAR 0 0 7821 2.09 character 613 NA NA NA NA 374933 367112
MUNREM 0 0 68464 18.26 character 750 NA NA NA NA 374933 306469
NORMA.f 0 0 0 0.00 factor 2 NA NA NA NA 374933 374933
CESAREA.f 0 0 0 0.00 factor 2 NA NA NA NA 374933 374933
SEXO1 0 0 0 0.00 factor 2 NA NA NA NA 374933 374933
SEMANAS 0 0 42842 11.43 numeric 27 20 46 39.12 1.76 374933 332091
PESON1 0 0 17290 4.61 numeric 2889 300 6350 3248.34 506.24 374933 357643
EDADM 0 0 0 0.00 numeric 46 12 58 32.47 5.60 374933 374933
EDADP 0 0 11665 3.11 numeric 64 14 77 35.44 6.10 374933 363268
ESTUDIOM.f 0 0 137206 36.59 factor 3 NA NA NA NA 374933 237727
ESTUDIOP.f 0 0 134748 35.94 factor 3 NA NA NA NA 374933 240185
PAISNXM 0 0 2971 0.79 character 64 NA NA NA NA 374933 371962


Muchas veces también me es muy útil la función pjpv2020.01::pjp_f_unique_values() porque devuelve un df con los valores únicos de cada variable:

zz <- pjpv2020.01::pjp_f_unique_values(df, truncate = TRUE, nn_truncate = 47) 
gt::gt(zz)
variables nn_unique unique_values
MESPAR 12 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 1
MUNPAR 614 NA - 01059 - 02003 - 02009 - 02025 - 02037 - 02
MUNREM 751 NA - 01002 - 01036 - 01059 - 02003 - 02009 - 02
NORMA.f 2 Parto con complicaciones - Parto normal
CESAREA.f 2 Con cesárea - Sin cesárea
SEXO1 2 bebita - bebito
SEMANAS 28 NA - 20 - 21 - 22 - 23 - 24 - 25 - 26 - 27 - 28
PESON1 2890 NA - 300 - 410 - 420 - 430 - 440 - 450 - 465 -
EDADM 46 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21
EDADP 65 NA - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22
ESTUDIOM.f 4 NA - Primarios - Medios - Universidad
ESTUDIOP.f 4 NA - Primarios - Medios - Universidad
PAISNXM 65 NA - 102 - 103 - 104 - 107 - 108 - 110 - 112 -
  • La función dplyr::distinct() es muy útil para ver los con valores únicos de una o varias variables. Por ejemplo:
zz <- df %>% distinct(NORMA.f, CESAREA.f)
zz %>% gt::gt()
NORMA.f CESAREA.f
Parto con complicaciones Con cesárea
Parto normal Sin cesárea
Parto normal Con cesárea
Parto con complicaciones Sin cesárea


3.3 Estadísticos básicos de las variables

a) con pjpv2020.01

Ya he dicho que yo suelo utilizar estas 2 funciones:

zz <- pjpv2020.01::pjp_f_estadisticos_basicos(df)   #- estadísticos básicos del df
zz <- pjpv2020.01::pjp_f_unique_values(df)          #- valores únicos de cada variable de df


b) con summarytools

La función summarytools::dfSummary() da un informe/resumen de las variables de un data.frame muy útil. Es muy útil para hacerse una idea rápida de las propiedades de las variables, pero tiene la pega de que el output es muy voluminosos para mostrarlo aquí, así que si quieres verlo tendrás que ejecutar el anterior chunk de forma interactiva.

zz <- summarytools::dfSummary(df) #- genera un fichero con un resumen útil y agradable de ver 
summarytools::view(zz)  #- para visualizar el informe


c) con skimr

Otra alternativa es usar `skimr::skim(df). Ocurre lo mismo: como su output es voluminoso, no lo voy a mostrar aquí.

skimr::skim(df)


3.4 NA’s

Siempre hay que chequear la existencia de NA’s en nuestro df. NA representa una característica que existe pero no sabemos su valor En R, los valores no disponibles se representan con NA. Se puede chequear si un valor es NA con la función is.na(). En datos generados por otros paquetes estadísticos, los valores ausentes pueden estar codificados con diferentes valores como por ejemplo “-999”, “N/A”, un espacio en blanco, etc …

Tres paquetes que nos pueden ayudar en esta tarea son DataExplorer, visdat y naniar.

DataExplorer::plot_missing(df)

naniar::gg_miss_var(df, show_pct = TRUE)        

El paquete visdat es útil para ver los NA’s pero el siguiente chunk no nos va a funcionar para las 374.933 filas que tiene nuestro df.

visdat::vis_dat(df) #- no funciona , hay demasiadas filas

Pero veamos como funcionaría para, por ejemplo, las primeras 1.000 filas de df:

df %>% slice(1:1000) %>% visdat::vis_dat()

  • Algo parecido pero con visdat::vis_miss()
df %>% slice(1:1000) %>% visdat::vis_miss(cluster = TRUE) 

  • Otra forma de visualizar la co-ocurrencia de NA’s en las variables de df:
naniar::gg_miss_upset(df)


trabajando con los NA's

  • Por ejemplo, podemos querer seleccionar las variables que tienen NA’s:
zz_con_NAs  <- df %>% select(where(anyNA))


  • A lo mejor, nos puede interesar ver la ocurrencia de NA’s para los grupos/categorías de una variable categórica de df, por ejemplo los estudios de la madre(ESTUDIOM.f)
naniar::gg_miss_var(df, facet = ESTUDIOM.f, show_pct = TRUE) #- faceted por la variable ESTUDIOM.f

¿Que hacemos con los NA’s?

Pues no está claro, depende … pero supongamos que queremos dejar nuestro df SIN NA’s;

zz <- df %>% tidyr::drop_na()              #- con tidyr

#- otras formas de hacer lo mismo ---
zz <- df[complete.cases(df), ]             #- con base-R 
zz <- df %>% filter(complete.cases(.))     #- con dplyr y base-R

Si quitáramos todos los registros/bebes que tienen algún NA en alguna de las 13 variables, nos quedaríamos con 152.286 registros.


Se puede refinar la eliminación de NA's. Puedes ser que igual Igual nos interesase eliminar sólo las filas que tengan un NA en una determinada variable, por ejemplo, si quisieramos quitar las filas que tuviesen NA en la variable PESO1 y/o en la variable SEMANAS, lo haríamos así:

zz <- df %>% tidyr::drop_na(c(PESON1, SEMANAS))   #- quito filas con NA en PESON1 o en SEMANAS


  • Imagina que hubiese una fila o una columna con todos sus valores NA. Lógicamente habría que eliminarlas. Se puede hacer fácil con `janitor::remove_empty()
zz <- df %>% janitor::remove_empty(c("rows", "cols"))    #- quita variables y filas vacías

br>

3.6 Variables numéricas

Vamos a ver algunas funciones útiles para hacer un análisis inicial de las variables numéricas.

Si quisieramos crear un df sólo con las variables numéricas:

df_numeric <- df %>% select_if(is.numeric)


a) Estadísticos descriptivos

Por ejemplo, summarytools::descr() nos ayuda a calcular rápidamente estadísticos descriptivos de las variables numéricas.

summarytools::descr(df, style = "rmarkdown")
summarytools::descr(df)
#> Descriptive Statistics  
#> df  
#> N: 374933  
#> 
#>                         EDADM       EDADP      PESON1     SEMANAS
#> ----------------- ----------- ----------- ----------- -----------
#>              Mean       32.47       35.44     3248.34       39.12
#>           Std.Dev        5.60        6.10      506.24        1.76
#>               Min       12.00       14.00      300.00       20.00
#>                Q1       29.00       32.00     2970.00       38.00
#>            Median       33.00       35.00     3260.00       39.00
#>                Q3       36.00       39.00     3570.00       40.00
#>               Max       58.00       77.00     6350.00       46.00
#>               MAD        5.93        5.93      444.78        1.48
#>               IQR        7.00        7.00      600.00        2.00
#>                CV        0.17        0.17        0.16        0.04
#>          Skewness       -0.41        0.18       -0.56       -2.30
#>       SE.Skewness        0.00        0.00        0.00        0.00
#>          Kurtosis        0.03        0.97        2.12       11.20
#>           N.Valid   374933.00   363268.00   357643.00   332091.00
#>         Pct.Valid      100.00       96.89       95.39       88.57


b) Histogramas o f. de densidad

En general, los métodos estadísticos requieren que las variables sigan una determinada distribución. Esto puede chequearse formalmente o utilizar histogramas y/o funciones de densidad estimadas.

Podemos usar el paquete DataExplorer para obtener histogramas o gráficos de densidad para las variables numéricas.

  • Histogramas:
DataExplorer::plot_histogram(df, ncol = 2)

  • Funciones de densidad estimadas:
DataExplorer::plot_density(df, ncol = 2)


c) Matriz de correlaciones

Otro aspecto importante de un análisis exploratorio consiste en analizar la existencia de relaciones entre las variables del conjunto de datos. Esto puede hacerse de diversas maneras, dos de las más sencillas y usadas son los gráficos de dispersión y las matrices de correlación.

Podemos obtener la matriz de correlaciones de diversas formas:

  • Primero con la función cor() de R-base:
stats::cor(df_numeric, use = "pairwise.complete.obs") %>%  #- devuelve una matriz, no un df
      round(. , 2)
#>         SEMANAS PESON1 EDADM EDADP
#> SEMANAS    1.00   0.55 -0.02 -0.01
#> PESON1     0.55   1.00  0.01  0.03
#> EDADM     -0.02   0.01  1.00  0.65
#> EDADP     -0.01   0.03  0.65  1.00
#df_numeric %>% GGally::ggcorr(label = TRUE)
df_numeric %>% GGally::ggpairs()

  • Ahora con la función corrr::correlate()
df %>% select_if(is.numeric) %>% 
       corrr::correlate() %>% 
       pjpv2020.01::pjp_f_decimales(nn = 2) %>%  
       gt::gt()
term SEMANAS PESON1 EDADM EDADP
SEMANAS NA 0.55 -0.02 -0.01
PESON1 0.55 NA 0.01 0.03
EDADM -0.02 0.01 NA 0.65
EDADP -0.01 0.03 0.65 NA


  • Otra forma más de visualizar las correlaciones, con inspectdf::inspect_cor()
df %>% inspectdf::inspect_cor()
col_1 col_2 corr p_value lower upper pcnt_nna
EDADP EDADM 0.6466554 0.0000000 0.6447594 0.6485435 96.88878
PESON1 SEMANAS 0.5538819 0.0000000 0.5514923 0.5562625 86.54826
EDADP PESON1 0.0286436 0.0000000 0.0253196 0.0319670 92.59441
EDADM SEMANAS -0.0224419 0.0000000 -0.0258410 -0.0190422 88.57343
EDADP SEMANAS -0.0094270 0.0000001 -0.0128786 -0.0059752 85.98070
EDADM PESON1 0.0062598 0.0001814 0.0029825 0.0095370 95.38851
  • Además, después, con inspectdf::show_plot() se pueden visualizar las correlaciones en un gráfico:
df %>% inspectdf::inspect_cor() %>% inspectdf::show_plot()


  • El paquete correlation facilita el cálculo de diferentes estadísticos de correlación, por defecto calcula el coeficiente de correlación de Pearson:
correlation::correlation(df_numeric)    #- remotes::install_github("easystats/correlation")
Parameter1 Parameter2 r CI_low CI_high t df p Method n_Obs
SEMANAS PESON1 0.5538819 0.5514923 0.5562625 378.955289 324496 0.0000000 Pearson 324498
SEMANAS EDADM -0.0224419 -0.0258410 -0.0190422 -12.935864 332089 0.0000000 Pearson 332091
SEMANAS EDADP -0.0094270 -0.0128786 -0.0059752 -5.352658 322368 0.0000002 Pearson 322370
PESON1 EDADM 0.0062598 0.0029825 0.0095370 3.743631 357641 0.0001814 Pearson 357643
PESON1 EDADP 0.0286436 0.0253196 0.0319670 16.883950 347165 0.0000000 Pearson 347167
EDADM EDADP 0.6466554 0.6447594 0.6485435 510.957420 363266 0.0000000 Pearson 363268


d) Boxplots frente a una v. categórica

Para ver diferencias en la distribución de las variables numéricas frente a una variable categórica es muy útil DataExplorer::plot_boxplot(). Por ejemplo frente a la variable ESTUDIOM.f:

DataExplorer::plot_boxplot(df, by = "ESTUDIOM.f")


O, por ejemplo, frente a la variable CESAREA.f

my_vv <- names(df)[3]
my_vv <- "CESAREA.f"
DataExplorer::plot_boxplot(df, by = my_vv)


  • También se pueden hacer boxplots de una variables numérica frente a 2 categóricas:
df %>% explore::explore(EDADM, ESTUDIOM.f, target = CESAREA.f)


  • Con explore::explore_all() se muestran gráficos de todas las variables del df frente a una variable target u objetivo:
df %>% select(NORMA.f, EDADM, PESON1,  ESTUDIOM.f, CESAREA.f) %>% explore::explore_all(target = CESAREA.f)

Veamos la relación entre “Parto normal” y CESAREA.f:

df %>% janitor::tabyl(NORMA.f, CESAREA.f) %>% janitor::adorn_percentages()
NORMA.f Con cesárea Sin cesárea Otros_xx
Parto con complicaciones 0.7292581 0.2707419 0
Parto normal 0.1796008 0.8203992 0


e) Scatterplots entre variables numéricas

DataExplorer::plot_scatterplot() nos permite hacer rápidamente un scatterplot de todas las variables del df frente a una variable, por ejemplo el peso del bebe: PESON1

my_vv <- "PESON1"
DataExplorer::plot_scatterplot(df, by = my_vv, sampled_rows = 500L)



3.7 Variables categóricas

Si necesitásemos un df con todas las variables categóricas, ¿cómo lo obtendrías?

df %>% select_if(is.numeric) %>% names()      #- old fashion
#> [1] "SEMANAS" "PESON1"  "EDADM"   "EDADP"
df %>% select(where(is.numeric)) %>% names()  
#> [1] "SEMANAS" "PESON1"  "EDADM"   "EDADP"

¿Y si quisieras seleccionar las variables no-numéricas?

df %>% select_if(!(is.numeric(.))) #- NO funciona
df %>% select_if(~ !is.numeric(.)) %>% names()     #- anonymous function but select_if
df %>% select(where(~ !is.numeric(.))) %>% names() #- anonymous functions & where
df %>% select(where(purrr::negate(is.numeric)))  %>% names()   #- purrr::negate()!!!!

Para visualizar rápidamente los valores de las variables categóricas, tenemos inspectdf::inspect_cat(), que nos devuelve un gráfico con la distribución de todas las variables categóricas.

inspectdf::inspect_cat(df) %>% inspectdf::show_plot(high_cardinality = 1)


  • También podemos hacerlo con DataExplorer::plot_bar():
DataExplorer::plot_bar(df)



3.8 Paquetes con shiny

a) burro

El paquete burro:

burro attempts to make EDA accessible to a larger audience by exposing datasets as a simple Shiny App

#- devtools::install_github("laderast/burro")
df_small <- df %>% slice(1:1000)
burro::explore_data(df_small, outcome_var = colnames(df))


b) explore

El paquete explore.

Instead of learning a lot of R syntax before you can explore data, the explore package enables you to have instant success. You can start with just one function - explore() - and learn other R syntax later step by step

  • Podemos crear un shiny para explorar nuestro df completo:
explore::explore(df)
  • o solo explorar una o varias variables.
df %>% explore::explore(CESAREA.f)
df %>% explore::explore(EDADM, ESTUDIOM.f, target = CESAREA.f)


c) ExPanDaR

El paqueteExPanDaR también permite explorar los datos a través de un shiny. Explican su funcionamiento en este post y este otro.

library(ExPanDaR) #- remotes::install_github("joachim-gassen/ExPanDaR")

ExPanD(mtcars)




4. Tablas

Muchas veces hay que presentar resultados básicos como una tabla de casos o de frecuencias. para ello tenemos muchas posibilidades:

4.1 Tablas con summarytools

El paquete summarytools.

summarytools is an R package providing tools to neatly and quickly summarize data. It can also make R a little easier to learn and to use, especially for data cleaning and preliminary analysis.

  • La función freq() provee tablas con conteos y frecuencias
summarytools::freq(df$SEXO1, style = "rmarkdown")

Frequencies

df$SEXO1

Type: Factor

  Freq % Valid % Valid Cum. % Total % Total Cum.
bebita 181708 48.46 48.46 48.46 48.46
bebito 193225 51.54 100.00 51.54 100.00
<NA> 0 0.00 100.00
Total 374933 100.00 100.00 100.00 100.00


  • tabulación cruzada entre dos variables categóricas:
summarytools::ctable(df$SEXO1, df$CESAREA.f)

Cross-Tabulation, Row Proportions
SEXO1 * CESAREA.f
Data Frame: df

CESAREA.f Con cesárea Sin cesárea Otros_xx Total
SEXO1
bebita 43247 (23.8%) 138461 (76.2%) 0 (0.0%) 181708 (100.0%)
bebito 49763 (25.8%) 143462 (74.2%) 0 (0.0%) 193225 (100.0%)
Total 93010 (24.8%) 281923 (75.2%) 0 (0.0%) 374933 (100.0%)


  • Incluso se pueden hacer test chi-cuadrado
summarytools::ctable(df$ESTUDIOM.f, df$NORMA.f, chisq = TRUE)

Cross-Tabulation, Row Proportions
ESTUDIOM.f * NORMA.f
Data Frame: df

NORMA.f Parto con complicaciones Parto normal Total
ESTUDIOM.f
Primarios 11898 (12.6%) 82201 (87.4%) 94099 (100.0%)
Medios 8394 (12.8%) 56953 (87.2%) 65347 (100.0%)
Universidad 8553 (10.9%) 69728 (89.1%) 78281 (100.0%)
17860 (13.0%) 119346 (87.0%) 137206 (100.0%)
Total 46705 (12.5%) 328228 (87.5%) 374933 (100.0%)
Chi.squared df p.value
161.1 2 0


4.2 Tablas con janitor

La verdad es que janitor es un paquete fantástico!!!

janitor has simple functions for examining and cleaning dirty data. It was built with beginning and intermediate R users in mind and is optimized for user-friendliness. Advanced R users can already do everything covered here, but with janitor they can do it faster and save their thinking for the fun stuff.

  • hacer (y dar formato) a tablas de 1, 2 o 3 variables:
df %>% janitor::tabyl(CESAREA.f) %>% gt::gt()
CESAREA.f n percent
Con cesárea 93010 0.248071
Sin cesárea 281923 0.751929
Otros_xx 0 0.000000
df %>% janitor::tabyl(CESAREA.f, ESTUDIOM.f) %>% gt::gt()
CESAREA.f Primarios Medios Universidad NA_
Con cesárea 22486 17446 20401 32677
Sin cesárea 71613 47901 57880 104529
Otros_xx 0 0 0 0


df %>% janitor::tabyl(CESAREA.f, ESTUDIOM.f, SEXO1)  #- xq no podemos usar gt::gt()
#> $bebita
#>    CESAREA.f Primarios Medios Universidad   NA_
#>  Con cesárea     10497   8059        9489 15202
#>  Sin cesárea     35456  23486       28150 51369
#>     Otros_xx         0      0           0     0
#> 
#> $bebito
#>    CESAREA.f Primarios Medios Universidad   NA_
#>  Con cesárea     11989   9387       10912 17475
#>  Sin cesárea     36157  24415       29730 53160
#>     Otros_xx         0      0           0     0


4.3 Tablas con R-base

Las tablas con R-base tampoco están mal. El problema que tienen es que los resultados no se almacenan en data.frames, si no en matrices.

table(df$CESAREA.f, df$ESTUDIOM.f, useNA = "always") 
#>              
#>               Primarios Medios Universidad   <NA>
#>   Con cesárea     22486  17446       20401  32677
#>   Sin cesárea     71613  47901       57880 104529
#>   Otros_xx            0      0           0      0
#>   <NA>                0      0           0      0
  • Como se almacenan en matrices, para poder graficarlas con gt primero las hemos de convertir a data.frame y casi seguro que habrá que hacer uso de pivot_wider()
my_tabla <- table(df$CESAREA.f, df$ESTUDIOM.f) 
my_tabla %>% as.data.frame() %>% 
            pivot_wider(names_from = 2, values_from = 3) %>% 
            gt::gt()
Var1 Primarios Medios Universidad
Con cesárea 22486 17446 20401
Sin cesárea 71613 47901 57880
Otros_xx 0 0 0


  • mosaicplot(), un gráfico que me gusta del sistema de R-base
zz <- prop.table(my_tabla, 1)
zz <- zz[1:2,]   #- tengo que hacer esto xq hay NA's en la tabla
mosaicplot(t(zz), color = TRUE, main = "% de Cesáreas para niveles educativos de la madre")



5. Contrastes

Evidentemente R permite implementar múltiples técnicas y modelos estadísticos, así como hacer múltiples y variados contrastes de hipótesis. En esta pagina web tenéis un buena introducción a estos tema. Veamos algún ejemplo:

t.test(df$PESON1, mu = 3250)
#> 
#>  One Sample t-test
#> 
#> data:  df$PESON1
#> t = -1.9636, df = 357642, p-value = 0.04958
#> alternative hypothesis: true mean is not equal to 3250
#> 95 percent confidence interval:
#>  3246.679 3249.997
#> sample estimates:
#> mean of x 
#>  3248.338
t.test(df$PESON1 ~ df$CESAREA.f)
#> 
#>  Welch Two Sample t-test
#> 
#> data:  df$PESON1 by df$CESAREA.f
#> t = -21.261, df = 125952, p-value < 0.00000000000000022
#> alternative hypothesis: true difference in means is not equal to 0
#> 95 percent confidence interval:
#>  -51.55217 -42.84971
#> sample estimates:
#> mean in group Con cesárea mean in group Sin cesárea 
#>                  3212.853                  3260.053
  • correlación entre dos variables cuantitativas
library(Hmisc)
Hmisc::rcorr(as.matrix(df_numeric)) 
  • un ejemplo para ver si hay diferencias en el peso en función de los estudios de la madre (!!!!)
library(purrr)
library(broom)
df %>% group_by(ESTUDIOM.f) %>% 
  summarise(t_test = list(t.test(PESON1))) %>% 
  mutate(tidied = map(t_test, tidy)) %>% 
  tidyr::unnest(tidied) %>% 
  ggplot(aes(estimate, ESTUDIOM.f)) + geom_point() +
  geom_errorbarh(aes(xmin = conf.low, xmax = conf.high)) + 
  labs(title = "Peso del bebe para diferentes niveles educativos de la madre")

chisq.test(df$CESAREA.f, df$SEXO1)
#> 
#>  Pearson's Chi-squared test with Yates' continuity correction
#> 
#> data:  df$CESAREA.f and df$SEXO1
#> X-squared = 191.51, df = 1, p-value < 0.00000000000000022
chisq.test(df$CESAREA.f, df$ESTUDIOM.f) 
#> 
#>  Pearson's Chi-squared test
#> 
#> data:  df$CESAREA.f and df$ESTUDIOM.f
#> X-squared = 188.48, df = 2, p-value < 0.00000000000000022
  • Aquí puedes encontrar un curso sobre como implementar los contrastes estadísticos más habituales con R.

En este post, Jonas Kristoffer Lindeløv, nos presenta un cuadro con los contrastes de hipótesis más frecuentes y cómo efectuar esos contrastes en el contexto de los modelos de regresión con R.



6. Modelos

En este apartado vamos a ver (un poco2) como estimar modelos lineales y no lineales con R. Para una introducción a la estimación de modelos en R puedes ir aquí.

Además, tenemos/tengo suerte de que me puedo apoyar en lo que ya habéis visto en Econometría y otras asignaturas. El objetivo, aparte de ver como se pueden hacer análisis de regresión con R, es ir preparando el camino para introducir algo de Machine Learning.

Antes restrinjamos aún un poco más el df con los datos sobre bebes:

df_m <- df %>% select(PESON1, SEMANAS, SEXO1, CESAREA.f, EDADM, EDADP, ESTUDIOM.f, ESTUDIOP.f)
df_m <- df_m %>% drop_na()

6.1 Modelos lineales

La función para estimar modelos lineales es lm(), así que vamos a utilizarla para estimar nuestro primer modelo con R. Estimaremos un modelo lineal con variable a explicar el peso del bebe (PESON1) en función de todas las demás variables en df_m.

mod_1 <- lm(PESON1 ~ . , data = df_m)
summary(mod_1)
#> 
#> Call:
#> lm(formula = PESON1 ~ ., data = df_m)
#> 
#> Residuals:
#>     Min      1Q  Median      3Q     Max 
#> -2784.6  -270.8   -12.2   258.8  3063.5 
#> 
#> Coefficients:
#>                         Estimate Std. Error  t value             Pr(>|t|)    
#> (Intercept)           -3088.6457    22.1993 -139.133 < 0.0000000000000002 ***
#> SEMANAS                 158.5187     0.5426  292.158 < 0.0000000000000002 ***
#> SEXO1bebito             134.4620     1.9022   70.686 < 0.0000000000000002 ***
#> CESAREA.fSin cesárea    -13.5279     2.2083   -6.126       0.000000000903 ***
#> EDADM                    -0.7358     0.2414   -3.048             0.002306 ** 
#> EDADP                     3.0648     0.2055   14.913 < 0.0000000000000002 ***
#> ESTUDIOM.fMedios         -9.7726     2.5635   -3.812             0.000138 ***
#> ESTUDIOM.fUniversidad   -10.3297     2.8377   -3.640             0.000273 ***
#> ESTUDIOP.fMedios         11.9535     2.4401    4.899       0.000000965227 ***
#> ESTUDIOP.fUniversidad    25.1406     2.9862    8.419 < 0.0000000000000002 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 420.2 on 195579 degrees of freedom
#> Multiple R-squared:  0.3167, Adjusted R-squared:  0.3166 
#> F-statistic: 1.007e+04 on 9 and 195579 DF,  p-value: < 0.00000000000000022

Generalmente las variables categóricas se introducen en los modelos mediante variables dummies. Crear dummies en R es sencillo, solo tienes que tener los datos como factor o como texto y R creará las dummies por ti cuando introduzcas la variable en lm(). Eso sí, es más fácil elegir la categoría de referencia si la variable es un factor.

Para entender como fija los regresores para las variables categóricas, mira esto:

levels(df$SEXO1)
#> [1] "bebita" "bebito"
levels(df$ESTUDIOM.f)
#> [1] "Primarios"   "Medios"      "Universidad"

Si necesitas cambiar la categoría de referencia siempre puedes usar forcast::fct_relevel()

zz <- forcats::fct_relevel(df$SEXO1, "bebito")
levels(zz)
#> [1] "bebito" "bebita"

Veamos que hay en el objeto mod_1

str(mod_1)
#listviewer::jsonedit(mod_1, mode = "view") ## Interactive option

Especificación del modelo

Lógicamente, a veces querremos seleccionar las variables explicativas:

mod_1 <- lm(PESON1 ~ SEMANAS, data = df_m)
mod_1 <- lm(PESON1 ~ SEMANAS + SEXO1 + CESAREA.f + EDADM , data = df_m)
mod_1 <- lm(log(PESON1) ~ log(SEMANAS), data = df_m)

summary(mod_1)
#> 
#> Call:
#> lm(formula = log(PESON1) ~ log(SEMANAS), data = df_m)
#> 
#> Residuals:
#>      Min       1Q   Median       3Q      Max 
#> -1.73125 -0.08115  0.00386  0.08791  1.52234 
#> 
#> Coefficients:
#>               Estimate Std. Error t value            Pr(>|t|)    
#> (Intercept)  -0.245233   0.024450  -10.03 <0.0000000000000002 ***
#> log(SEMANAS)  2.269906   0.006671  340.27 <0.0000000000000002 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 0.142 on 195587 degrees of freedom
#> Multiple R-squared:  0.3718, Adjusted R-squared:  0.3718 
#> F-statistic: 1.158e+05 on 1 and 195587 DF,  p-value: < 0.00000000000000022

Si queremos introducir interacciones entre los regresores, podemos usar el operador :, aunque casi mejor hacerlo directamente con I():

mod_1 <- lm(PESON1 ~ SEMANAS + SEMANAS:EDADM, data = df_m)
mod_1 <- lm(PESON1 ~ SEMANAS + I(SEMANAS*EDADM), data = df_m)

summary(mod_1)
#> 
#> Call:
#> lm(formula = PESON1 ~ SEMANAS + I(SEMANAS * EDADM), data = df_m)
#> 
#> Residuals:
#>      Min       1Q   Median       3Q      Max 
#> -2780.80  -275.75   -13.51   262.58  3118.43 
#> 
#> Coefficients:
#>                       Estimate  Std. Error t value            Pr(>|t|)    
#> (Intercept)        -2898.86012    21.38108  -135.6 <0.0000000000000002 ***
#> SEMANAS              155.79835     0.56164   277.4 <0.0000000000000002 ***
#> I(SEMANAS * EDADM)     0.04972     0.00448    11.1 <0.0000000000000002 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 425.9 on 195586 degrees of freedom
#> Multiple R-squared:  0.298,  Adjusted R-squared:  0.2979 
#> F-statistic: 4.15e+04 on 2 and 195586 DF,  p-value: < 0.00000000000000022

Si queremos introducir las variables originales y también las interacciones entre ellas, podemos hacerlo directamente o o utilizar el operador *:

mod_1 <- lm(PESON1 ~ SEMANAS + EDADM + SEMANAS:EDADM, data = df_m)
mod_1 <- lm(PESON1 ~ SEMANAS + EDADM + I(SEMANAS*EDADM), data = df_m)
mod_1 <- lm(PESON1 ~ SEMANAS*EDADM, data = df_m)
mod_1 <- lm(PESON1 ~ SEMANAS*ESTUDIOM.f, data = df_m)

summary(mod_1)
#> 
#> Call:
#> lm(formula = PESON1 ~ SEMANAS * ESTUDIOM.f, data = df_m)
#> 
#> Residuals:
#>      Min       1Q   Median       3Q      Max 
#> -2792.55  -276.51   -14.41   263.49  3113.84 
#> 
#> Coefficients:
#>                                 Estimate Std. Error t value            Pr(>|t|)
#> (Intercept)                   -2862.7148    33.5901 -85.225 <0.0000000000000002
#> SEMANAS                         156.3815     0.8593 181.996 <0.0000000000000002
#> ESTUDIOM.fMedios               -104.1508    52.0312  -2.002              0.0453
#> ESTUDIOM.fUniversidad             6.3027    51.2415   0.123              0.9021
#> SEMANAS:ESTUDIOM.fMedios          2.6794     1.3301   2.014              0.0440
#> SEMANAS:ESTUDIOM.fUniversidad     0.1272     1.3093   0.097              0.9226
#>                                  
#> (Intercept)                   ***
#> SEMANAS                       ***
#> ESTUDIOM.fMedios              *  
#> ESTUDIOM.fUniversidad            
#> SEMANAS:ESTUDIOM.fMedios      *  
#> SEMANAS:ESTUDIOM.fUniversidad    
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 426 on 195583 degrees of freedom
#> Multiple R-squared:  0.2976, Adjusted R-squared:  0.2976 
#> F-statistic: 1.658e+04 on 5 and 195583 DF,  p-value: < 0.00000000000000022

Recuerda que si queremos introducir algún regresor que sea la multiplicación de dos variables, tendremos que hacerlo con I()

mod_1 <- lm(PESON1 ~ SEMANAS + I(SEMANAS*SEMANAS), data = df_m)

summary(mod_1)
#> 
#> Call:
#> lm(formula = PESON1 ~ SEMANAS + I(SEMANAS * SEMANAS), data = df_m)
#> 
#> Residuals:
#>     Min      1Q  Median      3Q     Max 
#> -2794.4  -274.4   -14.4   261.9  3609.5 
#> 
#> Coefficients:
#>                        Estimate Std. Error t value            Pr(>|t|)    
#> (Intercept)          -5978.7888   151.5234  -39.46 <0.0000000000000002 ***
#> SEMANAS                324.5984     8.1524   39.82 <0.0000000000000002 ***
#> I(SEMANAS * SEMANAS)    -2.2567     0.1097  -20.57 <0.0000000000000002 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 425.6 on 195586 degrees of freedom
#> Multiple R-squared:  0.299,  Adjusted R-squared:  0.299 
#> F-statistic: 4.172e+04 on 2 and 195586 DF,  p-value: < 0.00000000000000022

También puede sernos de utilidad la función poly()

mod_1 <- lm(PESON1 ~ poly(SEMANAS, degree = 3), data = df_m)

summary(mod_1)
#> 
#> Call:
#> lm(formula = PESON1 ~ poly(SEMANAS, degree = 3), data = df_m)
#> 
#> Residuals:
#>     Min      1Q  Median      3Q     Max 
#> -2804.1  -270.2   -10.2   260.8  3316.1 
#> 
#> Coefficients:
#>                               Estimate  Std. Error t value            Pr(>|t|)
#> (Intercept)                  3254.6209      0.9586 3395.14 <0.0000000000000002
#> poly(SEMANAS, degree = 3)1 122613.9643    423.9502  289.22 <0.0000000000000002
#> poly(SEMANAS, degree = 3)2  -8755.4731    423.9502  -20.65 <0.0000000000000002
#> poly(SEMANAS, degree = 3)3 -16393.2469    423.9502  -38.67 <0.0000000000000002
#>                               
#> (Intercept)                ***
#> poly(SEMANAS, degree = 3)1 ***
#> poly(SEMANAS, degree = 3)2 ***
#> poly(SEMANAS, degree = 3)3 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 424 on 195585 degrees of freedom
#> Multiple R-squared:  0.3043, Adjusted R-squared:  0.3043 
#> F-statistic: 2.852e+04 on 3 and 195585 DF,  p-value: < 0.00000000000000022


Resultados de estimación

Una vez que sabemos la sintaxis de stats::lm(), vamos a ver como podemos acceder a la información que devuelve lm(). Ya hemos visto que los resultados se almacenan en una lista, así que podemos utilizar los operadores habituales de las listas para acceder a la información. Por ejemplo:

zz_betas     <- mod_1[[1]]

zz_residuals <- mod_1[[2]]
zz_residuals <- mod_1[["residuals"]]
zz_residuals <- mod_1$residuals

zz_betas_1   <- mod_1[[1]]      #- [[ ]] doble corchete
zz_betas_2 <- mod_1[1]          #- []    corchete simple

zz_betas_1a <- mod_1[["coefficients"]]   #- doble corchete 
zz_betas_1c <- mod_1$coefficients        #- $

zz_betas_2a <- mod_1["coefficients"]     #- single corchete

Afortunadamente ya hay construidas algunas funciones para manipular/ver los resultados de estimación. Por ejemplo:

mod_1 <- lm(PESON1 ~ SEMANAS + EDADM + SEXO1 , data = df_m)

summary(mod_1)                   #- tabla resumen
summary(mod_1)$coefficients      #- tabla resumen con los coeficientes
coefficients(mod_1)              #- coeficientes estimados
confint(mod_1, level = 0.95)     #- Intervalos de confianza para los coeficientes
#fitted(mod_1)                   #- predicciones (y-sombrero, y-hat)
#residuals(mod_1)                #- vector de residuos
#model.matrix(mod_1)             #- extract the model matrix
anova(mod_1)                     #- ANOVA
vcov(mod_1)                      #- matriz de var-cov para los coeficientes 
#influence(mod_1)                #- regression diagnostics 
# diagnostic plots
layout(matrix(c(1, 2, 3, 4), 2, 2))   #- optional 4 graphs/page
plot(mod_1)                      #-
library(ggfortify)
autoplot(mod_1, which = 1:6, ncol = 2, colour = "steelblue")


Más utilidades

Hay muchas más funciones para valorar la idoneidad de un modelo. Por ejemplo aquí.


El paquete GGally permite hacer muchos análisis, por ejemplo:

  • gráfico de los coeficientes estimados
GGally::ggcoef(mod_1)

  • y muchas más cosas
df_jugar <- df_m %>% select(EDADM, SEXO1, SEMANAS)
GGally::ggpairs(df_jugar)


Predicciones

Para hacer predicciones para observaciones fuera de la muestra has de usar la función predict(). Has de proporcionar las observaciones a predecir como un df.

nuevas_observaciones <- df_m %>% slice(c(3, 44, 444))

predict(mod_1, newdata = nuevas_observaciones)  #- predicciones puntuales
predict(mod_1, newdata = nuevas_observaciones, type = 'response', se.fit = TRUE)  #- tb errores estándar  predictions
predict(mod_1, newdata = nuevas_observaciones, interval = "confidence")  #- intervalo (para el valor esperado)
predict(mod_1, newdata = nuevas_observaciones, interval = "prediction")  #- intervalo (para valores individuales)


Errores estándar robustos

Lo habitual es que al estimar un modelo, tengamos situaciones de heterocedasticidad, clustering etc … Podemos ajustar los errores estándar a estas situaciones.

El paquete de referencia para estos temas solía ser sandwich, aunque quizás ya haya ocupado su lugar el paquete estimatr.

Por ejemplo se pueden obtener errores estándar robustos con estimatr::lm_robust()3.

#- install.packages("emmeans")
mod_1 <- lm(PESON1 ~ SEMANAS, data = df_m)
mod_1_ee <- estimatr::lm_robust(PESON1 ~ SEMANAS, data = df_m)


Paquete broom

Un opción interesante es utilizar el paquete broom. Este paquete tiene 3 funciones útiles:

  • tidy()

  • augment()

  • glance()

zz <- broom::tidy(mod_1, conf.int = TRUE)
zz %>% pjpv2020.01::pjp_f_decimales(nn = 2)  %>% gt::gt()
term estimate std.error statistic p.value conf.low conf.high
(Intercept) 3254.62 0.96 3395.14 0 3252.74 3256.50
poly(SEMANAS, degree = 3)1 122613.96 423.95 289.22 0 121783.03 123444.90
poly(SEMANAS, degree = 3)2 -8755.47 423.95 -20.65 0 -9586.41 -7924.54
poly(SEMANAS, degree = 3)3 -16393.25 423.95 -38.67 0 -17224.18 -15562.31
mod_1 %>% broom::glance() %>% select(adj.r.squared, p.value)
adj.r.squared p.value
0.3043378 0
broom::glance(mod_1)
r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC deviance df.residual nobs
0.3043485 0.3043378 423.9502 28522.9 0 3 -1460765 2921540 2921591 35153224020 195585 195589
zz <- broom::augment(mod_1)
  • un ejemplo sencillo en el que se ve la utilidad de broom
mod_1 %>% broom::tidy() %>% filter(p.value < 0.05)
term estimate std.error statistic p.value
(Intercept) 3254.621 0.9586114 3395.14100 0
poly(SEMANAS, degree = 3)1 122613.964 423.9501651 289.21787 0
poly(SEMANAS, degree = 3)2 -8755.473 423.9501651 -20.65213 0
poly(SEMANAS, degree = 3)3 -16393.247 423.9501651 -38.66786 0
  • Un ejemplo de uso de broom, pero antes vamos a recordar alguna cosa de ggplot2:
ggplot(data = df_m, mapping = aes(x = EDADM, y = PESON1,  color = ESTUDIOM.f)) +
      geom_point(alpha = 0.1) +  geom_smooth(method = "lm")

ggplot(data = df_m, mapping = aes(x = EDADM, y = PESON1,  color = SEXO1)) +
      geom_point(alpha = 0.1) +  geom_smooth(method = "lm")

ggplot(data = df_m, mapping = aes(x = EDADM, y = PESON1,  color = SEXO1)) +
      geom_point(alpha = 0.1) +  geom_smooth()

Ahora sí viene el ejemplo en que se usa el paquete broom

mod_1 <- lm(PESON1 ~ EDADM + SEXO1 , data = df_m)
summary(mod_1)
#> 
#> Call:
#> lm(formula = PESON1 ~ EDADM + SEXO1, data = df_m)
#> 
#> Residuals:
#>      Min       1Q   Median       3Q      Max 
#> -2908.94  -278.67    16.88   314.39  3038.84 
#> 
#> Coefficients:
#>              Estimate Std. Error t value             Pr(>|t|)    
#> (Intercept) 3173.6776     6.9225 458.456 < 0.0000000000000002 ***
#> EDADM          0.5554     0.2072   2.681              0.00735 ** 
#> SEXO1bebito  121.9336     2.2832  53.405 < 0.0000000000000002 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> Residual standard error: 504.6 on 195586 degrees of freedom
#> Multiple R-squared:  0.01441,    Adjusted R-squared:  0.0144 
#> F-statistic:  1430 on 2 and 195586 DF,  p-value: < 0.00000000000000022

td_mod_1 <- mod_1 %>% broom::augment(data = df_m) 

td_mod_1 %>% ggplot(mapping = aes(x = EDADM, y = PESON1, color = SEXO1)) +
                geom_point(alpha = 0.1) +
                geom_line(aes(y = .fitted, group = SEXO1))

td_mod_1 %>% ggplot(mapping = aes(x = EDADM, y = .fitted,  color = SEXO1)) +
                geom_line(aes(group = SEXO1)) 

(!!!!) Por ejemplo también permite fácilmente estimar modelos por grupos:

library(broom)
df_m %>% group_by(SEXO1) %>% do(tidy(lm(PESON1 ~ SEMANAS, .)))
SEXO1 term estimate std.error statistic p.value
bebita (Intercept) -2744.6382 30.4932289 -90.00812 0
bebita SEMANAS 151.6970 0.7784646 194.86689 0
bebito (Intercept) -3078.8921 29.2617194 -105.21911 0
bebito SEMANAS 163.6906 0.7484992 218.69176 0

O hacer gráficos de los intervalos de los coeficientes:

td_mod_1 <- tidy(mod_1, conf.int = TRUE)
ggplot(td_mod_1, aes(estimate, term, color = term)) +
    geom_point() +
    geom_errorbarh(aes(xmin = conf.low, xmax = conf.high)) +
    geom_vline(xintercept = 0)

o este:

mod_1 %>% augment(data = df_m) %>%
 ggplot(mapping = aes(x = SEMANAS , y = .fitted, color = SEXO1)) +
   geom_point(mapping = aes(y = PESON1), alpha = 0.1) +
   geom_line()

o este:

mod_1 %>% augment(data = df_m) %>%
 ggplot(mapping = aes(x = SEMANAS , y = .fitted, color = SEXO1)) +
   geom_point(mapping = aes(y = PESON1), alpha = 0.1) +
   geom_line() +
  facet_wrap(vars(ESTUDIOM.f))


Comparación de modelos

mod_1 <- lm(PESON1 ~ SEMANAS + EDADM,  data = df_m)
mod_2 <- lm(PESON1 ~ SEMANAS + EDADM + I(SEMANAS*EDADM), data = df_m)

anova(mod_1, mod_2)
Res.Df RSS Df Sum of Sq F Pr(>F)
195586 35476827424 NA NA NA NA
195585 35475031137 1 1796287 9.903494 0.0016499
AIC(mod_1, mod_2)
df AIC
mod_1 4 2923330
mod_2 5 2923323
lmtest::lrtest(mod_1, mod_2)    
#Df LogLik Df Chisq Pr(>Chisq)
4 -1461661 NA NA NA
5 -1461656 1 9.903446 0.0016497

(!!!) Vamos a ordenar los modelos en función de su AIC. Para ello vamos a crear una lista con modelos

modelos <- list(mod_1 <- lm(PESON1 ~ SEMANAS + EDADM,  data = df_m),
                mod_2 <- lm(PESON1 ~ SEMANAS + EDADM + I(SEMANAS*EDADM), data = df_m)  )

modelos_ordered_AIC <- purrr::map_df(modelos, broom::glance, .id = "model") %>% arrange(AIC)

modelos_ordered_AIC %>% gt::gt()
model r.squared adj.r.squared sigma statistic p.value df logLik AIC BIC deviance df.residual nobs
2 0.2979802 0.2979694 425.8863 27672.75 0 3 -1461656 2923323 2923373 35475031137 195585 195589
1 0.2979446 0.2979374 425.8959 41502.28 0 2 -1461661 2923330 2923371 35476827424 195586 195589
  • una tabla con broom (antes creamos una función (!!!!)):
my_kable <- function(df){ gt::gt(mutate_if(df, is.numeric, round, 2)) }

tidy(mod_1) %>% my_kable
term estimate std.error statistic p.value
(Intercept) -2961.01 22.27 -132.97 0
SEMANAS 157.41 0.55 288.09 0
EDADM 1.92 0.17 10.96 0


El paquete modelr

Con el paquete modelr podemos fácilmente comparar las predicciones de varios modelos:

zz <- df_m %>% modelr::gather_predictions(mod_1, mod_2)

Para ver mejor las predicciones de los 2 modelos habrá que pasar zz a formato ancho:

zz1 <- pivot_wider(zz, names_from = model, values_from = pred)


6.2 Modelos GLM

Por ejemplo un Logit:

mod_logit <- glm(CESAREA.f ~ PESON1 + SEMANAS + EDADM + ESTUDIOM.f, family = binomial(link = "logit"), data = df_m)

summary(mod_logit)
#> 
#> Call:
#> glm(formula = CESAREA.f ~ PESON1 + SEMANAS + EDADM + ESTUDIOM.f, 
#>     family = binomial(link = "logit"), data = df_m)
#> 
#> Deviance Residuals: 
#>     Min       1Q   Median       3Q      Max  
#> -2.2698  -1.1990   0.7021   0.7882   2.0867  
#> 
#> Coefficients:
#>                          Estimate  Std. Error z value             Pr(>|z|)    
#> (Intercept)           -2.73466194  0.12159914 -22.489 < 0.0000000000000002 ***
#> PESON1                -0.00009625  0.00001227  -7.845  0.00000000000000432 ***
#> SEMANAS                0.14435995  0.00345507  41.782 < 0.0000000000000002 ***
#> EDADM                 -0.04652829  0.00104477 -44.534 < 0.0000000000000002 ***
#> ESTUDIOM.fMedios      -0.03820180  0.01340958  -2.849              0.00439 ** 
#> ESTUDIOM.fUniversidad  0.08638628  0.01325995   6.515  0.00000000007277406 ***
#> ---
#> Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#> 
#> (Dispersion parameter for binomial family taken to be 1)
#> 
#>     Null deviance: 221737  on 195588  degrees of freedom
#> Residual deviance: 217286  on 195583  degrees of freedom
#> AIC: 217298
#> 
#> Number of Fisher Scoring iterations: 4


En los modelos lineales, calcular efectos marginales es sencillo, pero el Logit es un modelo no lineal. Para calcular efectos marginales en modelos no lineales podemos usar el paquete margins. Por ejemplo, calculemos el Efecto marginal medio o average marginal effect (AME) en mod_logit.

mod_1_AME <- mod_logit %>% margins::margins() %>% summary() 
mod_1_AME
factor AME SE z p lower upper
EDADM -0.0086132 0.0001907 -45.166365 0.0000000 -0.0089869 -0.0082394
ESTUDIOM.fMedios -0.0072001 0.0025291 -2.846869 0.0044152 -0.0121571 -0.0022431
ESTUDIOM.fUniversidad 0.0158127 0.0024263 6.517178 0.0000000 0.0110572 0.0205682
PESON1 -0.0000178 0.0000023 -7.849686 0.0000000 -0.0000223 -0.0000134
SEMANAS 0.0267234 0.0006307 42.371560 0.0000000 0.0254873 0.0279595

Si queremos calcular efectos marginales para unos valores concretos de los regresores

mod_logit %>% margins::margins(at = list(SEMANAS = c(25, 35), ESTUDIOM.f = c("Primarios", "Medios", "Universidad")),  variables = "EDADM" ) 

Si prefieres visualizarlo, utiliza margins::cplot():

margins::cplot(mod_logit, x = "ESTUDIOM.f", dx = "EDADM", what = "effect", drop = TRUE)
margins::cplot(mod_logit, x = "ESTUDIOM.f", dx = "EDADM")

El pkg ggeffects

El paquete ggeffects proporciona otra forma de calcular efectos marginales en modelos de regresión:

Results of regression models are typically presented as tables that are easy to understand. For more complex models that include interaction or quadratic / spline terms, tables with numbers are less helpful and difficult to interpret. In such cases, marginal effects are far easier to understand. In particular, the visualization of marginal effects allows to intuitively get the idea of how predictors and outcome are associated, even for complex models.

No lo he probado aún, pero se puede usar en una gran variedad de modelos. Estimated Marginal Means and Marginal Effects from Regression Models


6.3 Tablas para modelos

Con sjPLot y friends …

m1 <- lm(PESON1 ~ SEMANAS + SEXO1 + EDADM + EDADP.2, data = df)
m2 <- lm(PESON1 ~ SEMANAS + SEXO1 + EDADM + EDADP.2 + ESTUDIOM.f + ESTUDIOP.f, data = df)
sjPlot::tab_model(m1)
sjPlot::plot_model(m1, sort.est = TRUE)
sjPlot::tab_model(m1, m2)


Con stargazer

mod_1 <- lm(PESON1 ~ SEMANAS + SEXO1 , data = df_m)
stargazer::stargazer(mod_1, type = "html")
Dependent variable:
PESON1
SEMANAS 158.119***
(0.540)
SEXO1bebito 134.645***
(1.904)
Constant -2,995.949***
(21.163)
Observations 195,589
R2 0.315
Adjusted R2 0.315
Residual Std. Error 420.682 (df = 195586)
F Statistic 44,976.540*** (df = 2; 195586)
Note: p<0.1; p<0.05; p<0.01


Con modelsummary

#remotes::install_github('vincentarelbundock/modelsummary')
library(modelsummary)

mys_modelitos <- list()
mys_modelitos[["PESON1:  OLS 1"]]    <-  lm(PESON1    ~ SEMANAS + SEXO1,            df_m)
mys_modelitos[["PESON1:  OLS 2"]]    <-  lm(PESON1    ~ SEMANAS + SEXO1 + EDADM , df_m)
mys_modelitos[["CESAREA: Logit 1"]]  <- glm(CESAREA.f ~ SEMANAS + SEXO1 , data = df_m, family = binomial(link = "logit"))

mm <- msummary(mys_modelitos, title = "Resultados de estimación")
mm
Resultados de estimación
PESON1: OLS 1 PESON1: OLS 2 CESAREA: Logit 1
(Intercept) -2995.949 -3063.898 -3.971
(21.163) (22.037) (0.110)
SEMANAS 158.119 158.277 0.131
(0.540) (0.540) (0.003)
SEXO1bebito 134.645 134.620 -0.105
(1.904) (1.903) (0.010)
EDADM 1.903
(0.173)
Num.Obs. 195589 195589 195589
R2 0.315 0.315
R2 Adj. 0.315 0.315
AIC 2918512.0 2918392.7 219454.5
BIC 2918552.7 2918443.6 219485.1
Log.Lik. -1459251.990 -1459191.341 -109724.269
F 44976.535 30043.250


Con reports

  • informes con el paquete reports
library(report) #- devtools::install_github("neuropsychology/report")
my_model <- lm(PESON1 ~ SEMANAS + SEXO1, df_m)
rr <- report(my_model, target = 1)
rr


report::as.report(rr)


Recuerda también que se puede obtener la ecuación (en latex) de un modelo con:

library(equatiomatic)  #- remotes::install_github("datalorax/equatiomatic")
extract_eq(mod_1)
extract_eq(mod_1, use_coefs = TRUE)


6.4 Otros modelos/técnicas

En realidad sólo voy a insistir en que con R se pueden implementar una gran variedad de modelos y técnicas estadísticas. Puedes ver algunos ejemplos en este libro. Hay materiales excelentes, tanto libros, como tutoriales, posts, etc … que puedes encontrar fácilmente en internet.






6.5 Machine Learning

Simplemente señalar que es un área de estudio enorme y en constante evolución. En R hay varios entornos/enfoques para hacer ML. Creo que el que más futuro tiene es tidymodels. Un post introductorio sobre tidymodels. Un ejemplo de uso de tidymodels. Otro ejemplo.

Simplemente algunas referencias:





  1. Seguro que se me olvidan muchos; además el ecosistema R está en constante evolución, así que seguro que surgen mejoras↩︎

  2. La verdad es que con la cantidad de materiales fantásticos que hay sobre R, no hay necesidad de explicar o escribir sobre todos los temas↩︎

  3. Por defecto, el paquete usa Eicker-Huber-White robust standard errors, habitualmente conocidos como errores estándar “HC2”. Se pueden especificar otros métodos con la opción se_type. Por ejemplo se puede utilizar el método usado por defecto en Stata. Aquí puedes encontrar porque los errores estándar de Stata difieren de los usados en R y Phyton↩︎

LS0tCnRpdGxlOiAiVW4gcG9jbyBkZSBFREEgeSBtb2RlbG9zIChXSVApIgphdXRob3I6ICJQZWRybyBKLiBQw6lyZXogKHBlZHJvLmoucGVyZXpAdXYuZXMpLiBVbml2ZXJzaXRhdCBkZSBWYWzDqG5jaWEgPGJyPiA8YnI+IFdlYiBkZWwgY3Vyc286IDxodHRwczovL3BlcmV6cDQ0LmdpdGh1Yi5pby9pbnRyby1kcy0yMC0yMS13ZWIvPiIKZGF0ZTogIk5vdmllbWJyZSBkZSAyMDIwIChhY3R1YWxpemFkbyBlbCBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkLSVtLSVZJylgKSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICAjY29kZV9mb2xkaW5nOiBzaG93CiAgICBjc3M6ICFleHByIGhlcmU6OmhlcmUoImFzc2V0cyIsICJzdHlsZXNfcGpwLmNzcyIpCiAgICB0aGVtZTogcGFwZXIKICAgIGhpZ2hsaWdodDogdGV4dG1hdGUgCiAgICB0b2M6IHRydWUKICAgIHRvY19kZXB0aDogMyAKICAgIHRvY19mbG9hdDogCiAgICAgIGNvbGxhcHNlZDogdHJ1ZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgaW5jbHVkZXM6CiAgICAgIGFmdGVyX2JvZHk6ICFleHByIGhlcmU6OmhlcmUoImFzc2V0cyIsICJmb290ZXIuaHRtbCIpIAogICAgICBpbl9oZWFkZXI6IAogICAgICAgIC0gIWV4cHIgaGVyZTo6aGVyZSgiYXNzZXRzIiwgImdvb2dsZS1hbmFseXRpY3MuaHRtbCIpIAogICAgICAgIC0gIWV4cHIgaGVyZTo6aGVyZSgiYXNzZXRzIiwgImZhdmljb24tc29sLmh0bWwiKQogICAgZGZfcHJpbnQ6IGthYmxlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQotLS0KCmBgYHtyIGNodW5rX3NldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgICBjYWNoZSA9IEZBTFNFLCBjYWNoZS5wYXRoID0gIi9jYWNoZXMvIiwgY29tbWVudCA9ICIjPiIsCiAgICAgICAgICAgICAgICAgICAgICAjZmlnLndpZHRoID0gNywgZmlnLmhlaWdodD0gNywgICAKICAgICAgICAgICAgICAgICAgICAgICNvdXQud2lkdGggPSA3LCBvdXQuaGVpZ2h0ID0gNywKICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gVFJVRSwgIGZpZy5zaG93ID0gImhvbGQiLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmFzcCA9IDcvOSwgb3V0LndpZHRoID0gIjk1JSIsIGZpZy5hbGlnbiA9ICJjZW50ZXIiKQpgYGAKCmBgYHtyIG9wdGlvbnNfc2V0dXAsIGVjaG8gPSBGQUxTRX0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpICMtIHBhcmEgcXVpdGFyIGxhIG5vdGFjacOzbiBjaWVudMOtZmljYQpgYGAKCmBgYHtyIHNldHVwLCBlY2hvID0gRkFMU0V9CmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoaGVyZSkKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocGF0Y2h3b3JrKQpgYGAKCmBgYHtyIGtsaXBweSwgZWNobyA9IEZBTFNFfQprbGlwcHk6OmtsaXBweShwb3NpdGlvbiA9IGMoInRvcCIsICJyaWdodCIpKSAjLSByZW1vdGVzOjppbnN0YWxsX2dpdGh1Yigicmxlc3VyL2tsaXBweSIpCmBgYAoKCmBgYHtyLCBpbmNsdWRlID0gRkFMU0V9Cm9wdGlvbnMoaHRtbHRvb2xzLmRpci52ZXJzaW9uID0gRkFMU0UpCiNrbml0cjo6b3B0c19jaHVuayRzZXQoZmlnLnJldGluYSA9IDMsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCBlY2hvPUZBTFNFLCBvdXQud2lkdGggPSAiODUlIikKbGlicmFyeShtZXRhdGhpcykKbWV0YSgpICU+JSBtZXRhX25hbWUoImdpdGh1Yi1yZXBvIiA9ICJwZXJlenA0NC9pbnRyby1kcy0yMC0yMS13ZWIiKSAlPiUgCiAgbWV0YV9zb2NpYWwoCiAgICB0aXRsZSA9ICJBbsOhbGlzaXMgZXhwbG9yYXRvcmlvIGRlIGRhdG9zIGNvbiBSIiwKICAgIGRlc2NyaXB0aW9uID0gcGFzdGUoCiAgICAgICJBbsOhbGlzaXMgZXhwbG9yYXRvcmlvIGRlIGRhdG9zIGNvbiBSIiksCiAgICB1cmwgPSAiaHR0cHM6Ly9wZXJlenA0NC5naXRodWIuaW8vaW50cm8tZHMtMjAtMjEtd2ViL3R1dG9yaWFsZXMvdHRfMTBfRURBLWNvbi1SLmh0bWwiLAogICAgb2dfdHlwZSA9ICJ3ZWJzaXRlIiwKICAgIG9nX2F1dGhvciA9ICJQZWRybyBKLiBQw6lyZXoiCiAgKQpgYGAKCi0tLS0tLS0tLS0tLS0KCjxicj4KCiMgMS4gSW50cm9kdWNjacOzbgoKSGVtb3MgdmlzdG8gZW4gdHV0b3JpYWxlcyBhbnRlcmlvcmVzIGNvbW8gY2FyZ2FyLCBtYW5lamFyLCBhcnJlZ2xhciB5IHZpc3VhbGl6YXIgZGF0b3Mgw6AgbGEgdGlkeXZlcnNlLiBVbmEgdmV6IHNvbW9zIGNhcGFjZXMgZGUgbWFuZWphciB5IGFycmVnbGFyIG51ZXN0cm9zIGRhdG9zIGNvbiBSIHBvZGVtb3MgcGFzYXIgYSBpbnRlbnRhciBlZmVjdHVhciB1biBhbsOhbGlzaXMgInJlYWxpc3RhIiBjb24gdW4gY29uanVudG8gZGUgZGF0b3MuIAoKCkVuIGVzdGUgdHV0b3JpYWwgeSBzaWd1aWVudGVzLCB2YW1vcyBhIHV0aWxpemFyIHVuIGNvbmp1bnRvIGRlIGRhdG9zIHNvYnJlIG5hY2ltaWVudG9zIGRlIGJlYmVzIGVuIEVzcGHDsWEgcGFyYSBtb3N0cmFyIGFsZ3VuYXMgdMOpY25pY2FzIGRlIEVEQSB5IG1vZGVsaXphY2nDs24uIE5vIHNlIG1vc3RyYXLDoW4gbG9zIGRldGFsbGVzIHTDqWNuaWNvcyBkZSBsYXMgdMOpY25pY2FzLCBzaW5vIHNvbG8gc3UgYXBsaWNhY2nDs24gcHLDoWN0aWNhIHksIGEgdmVjZXMsIGxhIGludGVycHJldGFjacOzbiBkZSBsb3MgcmVzdWx0YWRvcy4KCgpBbnRlcyBkZSBwcm9jZWRlciBhIGxhIGVzdGltYWNpw7NuIGRlIG1vZGVsb3MgZXN0YWTDrXN0aWNvcyBmb3JtYWxlcyBvIGNvbnRyYXN0YXIgaGlww7N0ZXNpcyBzZSBzdWVsZSByZWFsaXphciB1bmEgZXRhcGEgY29ub2NpZGEgY29tbyAqKmFuw6FsaXNpcyBleHBsb3JhdG9yaW8qKi4gRWwgYW7DoWxpc2lzIGV4cGxvcmF0b3JpbyAoRURBKSBlcyB1bmEgcGFydGUgaW1wb3J0YW50ZSBkZSB0b2RvIGFuw6FsaXNpcyBkZSBkYXRvcy4gU3VlbGUgdGVuZXIgbHVnYXIgYW50ZXMgZGUgbGEgZXNwZWNpZmljYWNpw7NuIHkgZXN0aW1hY2nDs24gZGUgbW9kZWxvcyBlc3RhZMOtc3RpY29zIGZvcm1hbGVzLiBTdSBvYmpldGl2byDDumx0aW1vIGVzICJjb21wcmVuZGVyIiBsb3MgZGF0b3MgeSBsYXMgcmVsYWNpb25lcyBleGlzdGVudGVzIGVudHJlIGxhcyB2YXJpYWJsZXMuCgpVbmEgZGUgbGFzIG3DoXhpbWFzIGRlIHVuIGRhdGEgc2NpZW50aXN0IGVzLCBjb21vIHNlw7FhbGFuIFthcXXDrV0oaHR0cHM6Ly9tZWRpdW0uY29tL0BSYW5keV9BdS9kYXRhLXNjaWVuY2UtZm91bmRhdGlvbnMta25vdy15b3VyLWRhdGEtcmVhbGx5LXJlYWxseS1rbm93LWl0LWE2YmI5N2ViOTkxYz9zb3VyY2U9ZnJpZW5kc19saW5rJnNrPTQyZjFjMDI4ODNlNzQ0ZGY3ZGJiNjE4MzczMzEyMjQ0KSwgIioqS25vdyBZb3VyIERhdGEqKiIuIEFkZW3DoXMgbm9zIGV4cGxpY2FuIHF1ZToKCgo+IERhdGEgZXhwbG9yYXRpb24gaXMgdGhlIGFydCBvZiBsb29raW5nIGF0IHlvdXIgZGF0YSwgcmFwaWRseSBnZW5lcmF0aW5nIGh5cG90aGVzZXMsIHF1aWNrbHkgdGVzdGluZyB0aGVtLCB0aGVuIHJlcGVhdGluZyBhZ2FpbiBhbmQgYWdhaW4gYW5kIGFnYWluLiBUaGUgZ29hbCBvZiBkYXRhIGV4cGxvcmF0aW9uIGlzIHRvIGdlbmVyYXRlIG1hbnkgcHJvbWlzaW5nIGxlYWRzIHRoYXQgeW91IGNhbiBsYXRlciBleHBsb3JlIGluIG1vcmUgZGVwdGguCgo+IFdpdGggIGV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMsIHlvdeKAmWxsIGNvbWJpbmUgdmlzdWFsaXNhdGlvbiBhbmQgdHJhbnNmb3JtYXRpb24gd2l0aCB5b3VyIGN1cmlvc2l0eSBhbmQgc2NlcHRpY2lzbSB0byBhc2sgYW5kIGFuc3dlciBpbnRlcmVzdGluZyBxdWVzdGlvbnMgYWJvdXQgZGF0YQoKVW4gYnVlbiBlamVtcGxvIGRlIHF1w6kgZXMgdW4gYW7DoWxpc2lzIGV4cGxvcmF0b3JpbyBzb24gbG9zICJzY3JlZW5jYXN0IiBkZSBEYXZpZCBSb2JpbnNvbjogY2FkYSBzZW1hbmEgZ3JhYmEgdW4gdsOtZGVvIGNvbiB1biBhbsOhbGlzaXMgcsOhcGlkbyBkZSB1biBjb25qdW50byBkZSBkYXRvcy4gUHVlZGVzIHZlcmxvcywgZW4gc3UgY2FuYWwgZGUgeW91dHViZSAsW2FxdcOtXShodHRwczovL3d3dy55b3V0dWJlLmNvbS91c2VyL3NhZmU0ZGVtb2NyYWN5L3ZpZGVvcykuIEVuIFtlc3RlIHJlcG9dKGh0dHBzOi8vZ2l0aHViLmNvbS9kZ3J0d28vZGF0YS1zY3JlZW5jYXN0cykgZGUgR2l0aHViIHRpZW5lcyBsYSB0cmFuc2NyaXBjacOzbiBkZWwgY8OzZGlnbywgZW4gZmljaGVyb3MgYC5SbWRgIHF1ZSBhY2FiYSB1dGlsaXphbmRvIGVuIGxvcyBzY3JlZW5jYXN0cy4gQ29tbyBoYXkgbXVjaGEgZ2VudGUgcXVlIHNpZ3VlIHN1cyB2w61kZW9zIGhheSwgY29tbyBleHBsaWNhbiBlbiBbZXN0ZSBwb3N0XShodHRwczovL3BhdWx2YW5kZXJsYWtlbi5jb20vMjAyMC8wNi8xNi9kYXZpZC1yb2JpbnNvbnMtci1wcm9ncmFtbWluZy1zY3JlZW5jYXN0cy8pLCB2YXJpYXMgcGVyc29uYXMgcXVlIG1hbnRpZW5lbiB1biBsaXN0YWRvIGRpc2VjY2lvbmFuZG8gbGFzIHRhcmVhcyBxdWUgaGFjZSBEYXZpZCBlbiBjYWRhIHNjcmVlbmNhc3QsIHB1ZWRlcyBlbmNvbnRyYXJsb3MgW2FxdcOtXShodHRwczovL2dpdGh1Yi5jb20vZGdydHdvL2RhdGEtc2NyZWVuY2FzdHMpIHkgW2FxdcOtXShodHRwczovL3BhdWx2YW5kZXJsYWtlbi5jb20vMjAyMC8wNi8xNi9kYXZpZC1yb2JpbnNvbnMtci1wcm9ncmFtbWluZy1zY3JlZW5jYXN0cy8pLgoKClBvciDDumx0aW1vLCB1biBsaWJybywgYnVlbm8gdW4gKmJvb2tkb3duKiwgc29icmUgRURBOiBbRXhwbG9yYXRvcnkgRGF0YSBBbmFseXNpcyB3aXRoIFJdKGh0dHBzOi8vYm9va2Rvd24ub3JnL3JkcGVuZy9leGRhdGEvKSBkZSBSb2dlciBQZW5nLgoKCgojIyDCv1F1w6kgcXVlcmVtb3Mgc2FiZXI/CgpHZW5lcmFsbWVudGUgdW4gZXN0dWRpbyBjdWFudGl0YXRpdm8gY29taWVuemEgcG9yIHVuYSBwcmVndW50YShzKSBxdWUgZ3XDrWEgZWwgYW7DoWxpc2lzLiBFbiBudWVzdHJvIGNhc28sIGVsIG9iamV0aXZvIGRlbCB0dXRvcmlhbCBlcyBtZXJhbWVudGUgbW9zdHJhciBhbGd1bmFzIHTDqWNuaWNhcy9mdW5jaW9uZXMgw7p0aWxlcyBlbiBsYSBmYXNlIGRlIGV4cGxvcmFjacOzbiBkZSBkYXRvcyAoRURBKSBkZSB1biBlc3R1ZGlvLCBhw7puIGFzw60sIG51ZXN0cm8gImVzdHVkaW8iIGVzdGFyw6EgZ3VpYWRvIHBvciB2YXJpYXMgcHJlZ3VudGFzIHJlbGFjaW9uYWRhcyBjb24gbG9zIGJlYmVzOyBwb3IgZWplbXBsbzogMSkgdmFtb3MgYSBpbnRlbnRhciB2ZXIvY29udGVzdGFyIHNpIGxvcyBiZWJpdG9zIG5hY2VuIGNvbiBtw6FzIHBlc28gcXVlIGxhcyBiZWJpdGFzIDIpIGludGVudGFyZW1vcyBjb250ZXN0YXIgYSBsYSBwcmVndW50YShzKSBkZSBzaSBkZXRlcm1pbmFkYXMgY2FyYWN0ZXLDrXN0aWNhcyBkZSBsYXMgbWFkcmVzL3BhZHJlcywgY29tbyBsYSBlZGFkIG8gZWwgbml2ZWwgZWR1Y2F0aXZvLCBhZmVjdGFuIGFsIHBlc28gZGUgbG9zIHJlY2nDqW4gbmFjaWRvcy4KCkVsIHNpZ3VpZW50ZSBwYXNvIGRlIHRvZG8gYW7DoWxpc2lzIGVtcMOtcmljbyBlcyBsYSBiw7pzcXVlZGEgZGUgdW4gY29uanVudG8gZGUgZGF0b3MgcXVlIHBlcm1pdGEgKGhhc3RhIGNpZXJ0byBncmFkbyBkZSBjb25maWFuemEpIGRhciB1bmEgcmVzcHVlc3RhIGEgbGFzIHByZWd1bnRhcyBwbGFudGVhZGFzLiDCv0RlIGRvbmRlIHNhY2Ftb3MgZXNvcyBkYXRvcz8gRW4gbnVlc3RybyBjYXNvLCB1dGlsaXphcmVtb3MgbGEgZXN0YWTDrXN0aWNhIGRlIG5hY2ltaWVudG9zIGRlbCBJTkUuIExvcyBkZXRhbGxlcyBlbiBsYSBzZWNjacOzbiBzaWd1aWVudGUuCgotLS0tLS0tLS0tLS0tCgo8YnI+CgojIDIuIExvcyBkYXRvcwoKIyMgT3JpZ2VuIGRlIGxvcyBkYXRvcwoKTG9zIGRhdG9zIHByb3ZpZW5lbiBkZWwgKipJTkUqKiwgY29uY3JldGFtZW50ZSBkZSBsYSBbKipFc3RhZMOtc3RpY2EgZGUgbmFjaW1pZW50b3MqKl0oaHR0cDovL3d3dy5pbmUuZXMvZHluZ3MvSU5FYmFzZS9lcy9vcGVyYWNpb24uaHRtP2M9RXN0YWRpc3RpY2FfQyZjaWQ9MTI1NDczNjE3NzAwNyZtZW51PXJlc3VsdGFkb3Mmc2VjYz0xMjU0NzM2MTk1NDQzJmlkcD0xMjU0NzM1NTczMDAyKS4gQ29tbyBwdWVkZXMgdmVyIGVuIGxhIHdlYiBkZWwgSU5FIGhheSB1bmEgc2VyaWUgZGUgdGFibGFzIHF1ZSBtdWVzdHJhbiByZXN1bHRhZG9zIHBhcmEgbGFzIHByaW5jaXBhbGVzIHZhcmlhYmxlczsgcG9yIGVqZW1wbG8sIGhheSB1bmEgdGFibGEgbGxhbWFkYSBbIk5hY2ltaWVudG9zIHBvciBlZGFkIGRlIGxhIG1hZHJlLCBtZXMgeSBzZXhvIl0oaHR0cHM6Ly93d3cuaW5lLmVzL2pheGkvVGFibGEuaHRtP3BhdGg9L3QyMC9lMzAxL3Byb3ZpL2wwLyZmaWxlPTAxMDAxLnB4Jkw9MCkuIEVzdGFzIHRhYmxhcyBlc3TDoW4gbXV5IGJpZW4gcGFyYSBtb3N0cmFyIGxvcyBwcmluY2lwYWxlcyByZXN1bHRhZG9zIGRlIGxhIEVzdGFkw61zdGljYSBkZSBuYWNpbWllbnRvcywgcGVybyBub3NvdHJvcyBxdWVyZW1vcyBhbmFsaXphciAiZGUgdmVyZGFkIiBsb3MgZGF0b3MsIGFzw60gcXVlIHRlbmRyZW1vcyBxdWUgZGVzY2FyZ2Fybm9zIGxvcyBbKiptaWNyb2RhdG9zKiogZGUgbGEgZW5jdWVzdGFdKGh0dHBzOi8vd3d3LmluZS5lcy9keW5ncy9JTkViYXNlL2VzL29wZXJhY2lvbi5odG0/Yz1Fc3RhZGlzdGljYV9DJmNpZD0xMjU0NzM2MTc3MDA3Jm1lbnU9cmVzdWx0YWRvcyZzZWNjPTEyNTQ3MzYxOTU0NDMmaWRwPTEyNTQ3MzU1NzMwMDIjIXRhYnMtMTI1NDczNjE5NTQ0MykuCgpFbiBsb3MgbWljcm9kYXRvcyBkZSBsYSBFc3RhZMOtc3RpY2EgZGUgTmFjaW1pZW50b3MgaGF5IGRhdG9zIHNvYnJlOiAoaSkgTmFjaW1pZW50b3MsIChpaSkgTXVlcnRlcyBmZXRhbGVzIHRhcmTDrWFzIHkgKGlpaSkgUGFydG9zLiBWYW1vcyBhIHVzYXIgbG9zIGZpY2hlcm9zIGRlIGRhdG9zIHNvYnJlICoqUEFSVE9TKiouCgpIYXkgdW4gZmljaGVybyBwYXJhIGNhZGEgYcOxbyBkZXNkZSAxOTc1LCBwZXJvIGNvbW8gaGFuIGhhYmlkbyBkaXZlcnNvcyBjYW1iaW9zIGVuIGxhIG1ldG9kb2xvZ8OtYSBkZSBsYSBlc3RhZMOtc3RpY2EsIHNvbG8gdmFtb3MgYSB0cmFiYWphciBsb3MgZmljaGVyb3MgZGUgbG9zIGHDsW9zICoqMjAwNyBhIDIwMTgqKiBxdWUgY29tcGFydGVuIGRpY2Npb25hcmlvLiAKCk1lIGRlc2Nhcmd1ZSAoYSBtYW5vKSBsb3MgZmljaGVyb3MgZGUgbWljcm9kYXRvcyBkZSBuYWNpbWllbnRvcyBwYXJhIGxvcyBhw7FvcyAyMDA3IGEgMjAxOC4gTG9zIGRhdG9zIGVzdMOhbiBlbiBmb3JtYXRvIHRleHRvLCB5IGNhZGEgcmVnaXN0cm8gZXMgdW5hIGNhZGVuYSBsYXJnYSBkZSBjYXJhY3RlcmVzLiBMb3MgZGF0b3MgdmFuIGFjb21wYcOxYWRvcyBkZSB1biBkaWNjaW9uYXJpbyBxdWUgc2lydmUgcGFyYSBwb2RlciBzZXBhcmFyIGxhcyBjYWRlbmFzIGRlIGNhcmFjdGVyZXMgZW4gbG9zIHZhbG9yZXMgZGUgY2FkYSB2YXJpYWJsZS4gT3MgYWhvcnJvIGxvcyBkZXRhbGxlcyBkZWwgcHJvY2Vzby4gCgpMb3MgZGF0b3MgZXJhbiBkZSBwYXJ0b3MsIHBlcm8gbG9zIHByZXBhcsOpIHBhcmEgcXVlIGNhZGEgZmlsYSBwZXJ0ZW5lemNhIGEgbG9zIGRhdG9zIGRlIHVuIHNvbG8gYmViZTogY2FkYSBiZWJlIHRpZW5lIHN1IHByb3BpYSBmaWxhLgoKCiMjIENhcmdhbmRvIGVsIGZpY2hlcm8gZGUgZGF0b3MgeSBkaWNjCgpNaSBvcmRlbmFkb3IgdHV2byBwcm9ibGVtYXMgcGFyYSBtb3ZlciBlbCBmaWNoZXJvIGNvbiB0b2RvcyBsb3MgZGF0b3MgKDIwMDcgYSAyMDE4KSwgeSwgY29tbyBhZGVtw6FzLCBlbCBvYmpldGl2byBkZWwgdHV0b3JpYWwgZXMgdGFuIHPDs2xvIG1vc3RyYXIgYWxndW5hcyB0w6ljbmljYXMgeSBmdW5jaW9uZXMgw7p0aWxlcyBwYXJhIEVEQSwgcG9yIGxvIHF1ZSBzb2xhbWVudGUgc2UgdXRpbGl6YXLDoW4gbG9zIGRhdG9zIHJlZmVyZW50ZXMgYSAyMDE3LiBBZGljaW9uYWxtZW50ZSwgc2UgcmVzdHJpbmdpw7MgbGEgbXVlc3RyYSBhIHBhcnRvcyBjb24gdW4gc29sbyBiZWJlIHF1ZSBoYWLDrWFuIHRlbmlkbyBsdWdhciBlbiB1biBjZW50cm8gc2FuaXRhcmlvLgoKYGBge3J9CmRmIDwtIHJpbzo6aW1wb3J0KGhlcmU6OmhlcmUoImRhdG9zIiwgImRmX2JlYmVzXzIwMTcucmRzIikpICAgICAgICAgICAgICMtIDM3NC45MzMgeCAxMwpkZl9kaWNjIDwtIHJpbzo6aW1wb3J0KGhlcmU6OmhlcmUoImRhdG9zIiwgImRmX2JlYmVzXzIwMTdfZGljYy54bHN4IikpICAjLSAxMyB4IDE4CmBgYAoKQ29uIGVsIGFudGVyaW9yIGNodW5rIGhlbW9zIGNhcmdhZG8gZWwgZmljaGVybyBkZSBkYXRvcyBgZGZgLCBxdWUgY3VlbnRhIGNvbiBgciBmb3JtYXQobnJvdyhkZiksIGJpZy5tYXJrID0gIi4iKWAgcmVnaXN0cm9zIGRlIGJlYmVzIHkgYHIgbGVuZ3RoKGRmKWAgY29sdW1uYXMgbyB2YXJpYWJsZXMgcGFyYSBjYWRhIGJlYmUuCgoKLS0tLS0tLS0tLS0KCjxicj4KCiMgMy4gQ29zYXMgZGUgRURBCgpIZW1vcyB2aXN0byBlbiB0dXRvcmlhbGVzIGFudGVyaW9yZXMgY29tbyBjYXJnYXIsIGFycmVnbGFyIHkgbWFuZWphciBkYXRvcyBjb24gUiAqw6AgbGEgdGlkeXZlcnNlKi4gRW4gZXN0ZSB0dXRvcmlhbCB2YW1vcyBhIHZlciAqKmFsZ3Vub3MgcGFxdWV0ZXMgeSBmdW5jaW9uZXMgcXVlIGNvbnZpZW5lIGNvbm9jZXIgcG9ycXVlIG5vcyBwdWVkZW4gYXl1ZGFyIGEgYWNlbGVyYXIgZWwgYW7DoWxpc2lzIGV4cGxvcmF0b3JpbyBpbmljaWFsIGRlIG51ZXN0cm9zIGRhdG9zKiouCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgb3V0LndpZHRoID0gIjkwJSIsIGZpZy5hbGlnbiA9ICJkZWZhdWx0In0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1hZ2VuZXMiLCAiaW1nX3R0XzEwLTAxLnBuZyIpKQpgYGAKCgpFbiBlc3RlIFtyZXBvIGRlIEdpdGh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL21zdGFuaWFrL2F1dG9FREEtcmVzb3VyY2VzKSwgTWF0ZXVzeiBTdGFuaWFrIG1hbnRpZW5lIHVuIGxpc3RhZG8gZGUgcGFxdWV0ZXMgeSByZWN1cnNvcyByZWxhY2lvbmFkb3MgY29uIGxhIEVEQS4KCkVuIFtlc3RlIHBvc3RdKGh0dHA6Ly9zbWFydGVycG9sYW5kLnBsL2luZGV4LnBocC8yMDE5LzA0L2V4cGxvcmUtdGhlLWxhbmRzY2FwZS1vZi1yLXBhY2thZ2VzLWZvci1hdXRvbWF0ZWQtZGF0YS1leHBsb3JhdGlvbi8pIHkgZW4gW2VzdGUgYXJ0w61jdWxvXShodHRwczovL2FyeGl2Lm9yZy9wZGYvMTkwNC4wMjEwMS5wZGYpIG5vcyBoYWJsYW4gZGVsIHBhcXVldGUgW2BhdXRvRURBYF0oaHR0cHM6Ly9naXRodWIuY29tL1hhbmRlckhvcm4vYXV0b0VEQSkgcXVlIHRyYXRhIGRlIGF1dG9tYXRpemFyIGVsIHByb2Nlc28gaW5pY2lhbCBleHBsb3JhdG9yaW8gZGUgZGF0b3MgKEVEQSkuIEFkZW3DoXMgZWwgcG9zdCBub3MgbXVlc3RyYSBsb3MgMTEgcGFxdWV0ZXMgZGUgUiwgcmVsYWNpb25hZG9zIGNvbiBsYSBFREEsIG3DoXMgZGVzY2FyZ2Fkb3MgZGUgQ1JBTi4gRWwgb2JqZXRpdm8gZGUgZXN0b3MgMTEvMTIgcGFxdWV0ZXMgZXMgc2ltaWxhciwgdHJhdGFyIGRlIGF1dG9tYXRpemFyL2ZhY2lsaXRhciBlbCBhbsOhbGlzaXMgcHJlbGltaW5hciBvIGV4cGxvcmF0b3JpbyBkZSBsb3MgZGF0b3MgKEVEQSkuIEV2aWRlbnRlbWVudGUgYXV0b21hdGl6YXIgdG90YWxtZW50ZSBsYSBFREEgZXMgbXV5LW11eSBjb21wbGljYWRvLCBwb3Igbm8gZGVjaXIgaW1wb3NpYmxlLCBwZXJvIGFsIG1lbm9zIGVzdG9zIHBhcXVldGVzICh5IG90cm9zIG11Y2hvcykgY29udGllbmVuIGZ1bmNpb25lcyBxdWUgbm9zIHB1ZWRlbiBheXVkYXIgZW4gbGFzIHByaW1lcmFzIGV0YXBhcyBkZSBudWVzdHJvcyBhbsOhbGlzaXMgZGUgZGF0b3MuCgpQb3IgZWplbXBsbywgZW4gW2VzdGUgcG9zdF0oaHR0cHM6Ly93d3cubGl0dGxlbWlzc2RhdGEuY29tL2Jsb2cvc2ltcGxlLWVkYSksIExhdXJhIEVsbGlzLCBub3MgZXhwbGljYSBjb21vIGhhY2VyIEVEQSBlbiBSIG1vc3Ryw6FuZG9ub3MgYWxndW5hcyBkZSBzdXMgZnVuY2lvbmVzL3RydWNvcyBmYXZvcml0b3MgY29tbyBsYSBmdW5jacOzbiBgc2tpbXI6OnNraW0oKWAsIGB2aXNkYXQ6OnZpc19kYXQoKWAgbyBgRGF0YUV4cGxvcmVyOjpjcmVhdGVfcmVwb3J0KClgLiBFbCBwb3N0IHR1dm8gdW5hIHNlZ3VuZGEgcGFydGUgKFthcXXDrV0oaHR0cHM6Ly93d3cubGl0dGxlbWlzc2RhdGEuY29tL2Jsb2cvaW5zcGVjdGRmKSksIGRvbmRlIExhdXJhIG5vcyBleHBsaWNhIGVsIHVzbyBkZSBhbGd1bmFzIGZ1bmNpb25lcyDDunRpbGVzIHBhcmEgRURBIGRlbCBwYXF1ZXRlIFtgaW5zcGVjdGRmYF0oaHR0cHM6Ly9naXRodWIuY29tL2FsYXN0YWlycnVzaHdvcnRoL2luc3BlY3RkZikgY29tbyBwb3IgZWplbXBsbzogYGluc3BlY3RkZjo6aW5zcGVjdF90eXBlcygpYCwgYGluc3BlY3RkZjo6aW5zcGVjdF9uYWAsIGV0Yy4uLgoKRW4gZXN0ZSBbb3RybyBwb3N0XShodHRwczovL3NoYXJsYS5wYXJ0eS9wb3N0cy9uZXctZGF0YS1zdHJhdGVnaWVzLyksIFNoYXJsYSBHZWxmYW5kIG5vcyBoYWJsYSBzb2JyZSBlc3RyYXRlZ2lhcyBwYXJhIGFmcm9udGFyIGxhIEVEQSBjdWFuZG8gc2UgdHJhYmFqYSBjb24gdW4gY29uanVudG8gZGUgZGF0b3MgbnVldm8uIFNoYXJsYSB1dGlsaXphLCBlbnRyZSBvdHJvcywgbG9zIHBhcXVldGVzIFtgdmlzZGF0YF0oaHR0cDovL3Zpc2RhdC5uanRpZXJuZXkuY29tLyksIFtgc2tpbXJgXShodHRwczovL3JvcGVuc2NpLmdpdGh1Yi5pby9za2ltci8pIHkgW2Bhc3NlcnRyYF0oaHR0cHM6Ly9kb2NzLnJvcGVuc2NpLm9yZy9hc3NlcnRyLykuCgoKREUgZW50cmUgbG9zIG11Y2hvcyBwYXF1ZXRlcyB5IGZ1bmNpb25lcyByZWxhY2lvbmFkb3MgY29uIEVEQSwgY29udmllbmUgY29ub2NlciwgZW4gbWkgb3BpbmnDs24sIGVzdG9zXltTZWd1cm8gcXVlIHNlIG1lIG9sdmlkYW4gbXVjaG9zOyBhZGVtw6FzIGVsIGVjb3Npc3RlbWEgUiBlc3TDoSBlbiBjb25zdGFudGUgZXZvbHVjacOzbiwgYXPDrSBxdWUgc2VndXJvIHF1ZSBzdXJnZW4gbWVqb3Jhc106CgoKPGJyPgoKIyMgMy4xIE1hbmlwdWxhciBsb3Mgbm9tYnJlcyBkZSBsYXMgdmFyaWFibGVzCgpDb24gbG9zIGRhdG9zIGRlIGJlYmVzIG5vIHZhbW9zIGEgY2FtYmlhciBsb3Mgbm9tYnJlcywgeWEgcXVlIHNvbiBsb3Mgbm9tYnJlcyBxdWUgbGVzIGRpbyBlbCBJTkUgeSBxdWUgZmlndXJhbiBlbiBlbCBkaWNjaW9uYXJpbyBvZmljaWFsLCBwZXJvIG11Y2hhcyB2ZWNlcyBoYXkgcXVlIGNhbWJpYXIvYXJyZWdsYXIgbG9zIG5vbWJyZXMgZGUgbGFzIHZhcmlhYmxlcywgYXPDrSBxdWUgdmFtb3MgYSBoYWNlcmxvIGNvbiBudWVzdHJvIGBkZmAsIGF1bnF1ZSBhbCBmaW5hbCB2b2x2ZXJlbW9zIGEgdXRpbGl6YXIgbG9zIG5vbWJyZXMgb3JpZ2luYWxlcy4KClBvZGVtb3MgYWNjZWRlciBhIGxvcyBub21icmVzIGRlIGxhcyBjb2x1bW5hcyBkZSB1biBkYXRhLmZyYW1lIGNvbiBsYSBmdW5jacOzbiBgYmFzZTo6bmFtZXMoKWAuIFZlw6Ftb3NsbzoKCgpgYGB7cn0Kbm9tYnJlc19vcmlnaW5hbGVzIDwtIG5hbWVzKGRmKSAgIy0gYWxtYWNlbmFtb3MgbG9zIG5vbWJyZXMgb3JpZ2luYWxlcyBlbiBlbCB2ZWN0b3Igbm9tYnJlc19vcmlnaW5hbGVzCmBgYAoKVmVhbW9zIGN1YWxlcyBzb24gbG9zIG5vbWJyZXMgZGUgbGFzIHZhcmlhYmxlcyBkZSBgZGZgOgoKYGBge3J9Cm5hbWVzKGRmKQpgYGAKCgpTaSBxdWlzaWVyYW1vcywgcG9yIGVqZW1wbG8sIGNhbWJpYXIgZWwgbm9tYnJlIGRlIGxhIHNlZ3VuZGEgdmFyaWFibGUsIHBvZGVtb3MgaGFjZXJsbyBjb246CgoKYGBge3J9Cm5hbWVzKGRmKVsyXSA8LSAibnVldm9fbm9tYnJlX3ZhcmlhYmxlXzIiCm5hbWVzKGRmKQpgYGAKCgpTaSB0dXZpw6lzZW1vcyB1biBkYXRhLmZyYW1lIGNvbiBub21icmVzIHJlYWxtZW50ZSBleHRyYcOxb3MsIGluY2x1c28gY29uIG5vbWJyZXMgIm5vIHNpbnTDoWN0aWNvcyIsIHkgbmVjZXNpdGFtb3MgYXJyZWdsYXJsb3MgZGUgZm9ybWEgcsOhcGlkYSwgcG9kZW1vcyBoYWNlcmxvIGNvbiBsYSBmdW5jacOzbiBgamFuaXRvcjo6Y2xlYW5fbmFtZXMoKWAuIEFycmVnbGEgbG9zIG5vbWJyZXMgZGUgbGFzIHZhcmlhYmxlcyBkZSBmb3JtYSBhdXRvbcOhdGljYSwgYWRlbcOhcyB0aWVuZSBhbGd1bmFzIG9wY2lvbmVzIHBhcmEgaGFjZXJsbyBjb21vIG3DoXMgdGUgZ3VzdGUuCgpgYGB7cn0KamFuaXRvcjo6Y2xlYW5fbmFtZXMoZGYpICU+JSBuYW1lcygpCmBgYAoKClZvbHZhbW9zIGEgZGVqYXIgbG9zIG5vbWJyZXMgb3JpZ2luYWxlcyBkZSBgZGZgOgoKYGBge3J9Cm5hbWVzKGRmKSA8LSBub21icmVzX29yaWdpbmFsZXMKYGBgCgpgYGB7ciwgZWNobyA9IEZBTFNFfQpybShub21icmVzX29yaWdpbmFsZXMpCmBgYAoKLS0tLS0tLS0tLS0tLS0KCjxicj4KCiMjIDMuMiBFc3RydWN0dXJhIHkgdGlwbyBkZSB2YXJpYWJsZXMKClNpZW1wcmUtc2llbXByZSBoYXkgcXVlIHNhYmVyIGRlIHF1ZSB0aXBvIHNvbiBudWVzdHJhcyB2YXJpYWJsZXMuIEVzdG8gc2UgcHVlZGUgaGFjZXIgZGUgbXVjaGFzIGZvcm1hcy4gT3MgbXVlc3RybyBkb3M6CgojIyMgYSkgY29uIGBzdHIoKWAKCmBgYHtyfQpzdHIoZGYpICAgICAgICAgICAgIy0gc3RyKCkgbXVlc3RyYSBsYSBlc3RydWN0dXJhIGludGVybmEgZGUgdW4gb2JqZXRvIFIKYGBgCgo8YnI+CgojIyMgYikgY29uIGBpbnNwZWN0ZGY6Omluc3BlY3RfdHlwZXMoKWAKCmBgYHtyfQppbnNwZWN0ZGY6Omluc3BlY3RfdHlwZXMoZGYpICAgIy0gbXVlc3RyYSBkZSBxdWUgdGlwbyBzb24gbGFzIHZhcmlhYmxlcwpgYGAKCjxicj4KCiMjIyBjKSAyIGZ1bmNpb25lcyDDunRpbGVzIGRlbCBwYXF1ZXRlIGBwanB2MjAyMC4wMWAKCkxhIGZ1bmNpw7NuIGBwanB2MjAyMC4wMTo6cGpwX2ZfZXN0YWRpc3RpY29zX2Jhc2ljb3MoKWAuIFPDrSwgZWwgbm9tYnJlIGRlIGxhIGZ1bmNpw7NuIGVzIHVuIHBvY28gbGFyZ28sIHBlcm8gZXMgcXVlIGVzIHVuIHBhcXVldGUgcGFyYSB1c28gcGVyc29uYWwuIFlvIGVzdG95IGFjb3N0dW1icmFkbyBhIHVzYXJsYS4gRGEgdW4gcmVzdW1lbiByw6FwaWRvIGRlIGxhcyB2YXJpYWJsZXMgZGVsIGRhdGEuZnJhbWU6CgpgYGB7cn0KenogPC0gcGpwdjIwMjAuMDE6OnBqcF9mX2VzdGFkaXN0aWNvc19iYXNpY29zKGRmKSAKZ3Q6Omd0KHp6KQpgYGAKCgoKPGJyPgoKTXVjaGFzIHZlY2VzIHRhbWJpw6luIG1lIGVzIG11eSDDunRpbCBsYSBmdW5jacOzbiBgcGpwdjIwMjAuMDE6OnBqcF9mX3VuaXF1ZV92YWx1ZXMoKWAgcG9ycXVlIGRldnVlbHZlIHVuIGRmIGNvbiBsb3MgdmFsb3JlcyDDum5pY29zIGRlIGNhZGEgdmFyaWFibGU6CgoKYGBge3J9Cnp6IDwtIHBqcHYyMDIwLjAxOjpwanBfZl91bmlxdWVfdmFsdWVzKGRmLCB0cnVuY2F0ZSA9IFRSVUUsIG5uX3RydW5jYXRlID0gNDcpIApndDo6Z3QoenopCmBgYAoKCi0gTGEgZnVuY2nDs24gYGRwbHlyOjpkaXN0aW5jdCgpYCBlcyBtdXkgw7p0aWwgcGFyYSB2ZXIgbG9zIGNvbiB2YWxvcmVzIMO6bmljb3MgZGUgdW5hIG8gdmFyaWFzIHZhcmlhYmxlcy4gUG9yIGVqZW1wbG86CgoKYGBge3J9Cnp6IDwtIGRmICU+JSBkaXN0aW5jdChOT1JNQS5mLCBDRVNBUkVBLmYpCnp6ICU+JSBndDo6Z3QoKQpgYGAKCgoKLS0tLS0tLS0tLS0tLS0KCjxicj4KCiMjIDMuMyBFc3RhZMOtc3RpY29zIGLDoXNpY29zIGRlIGxhcyB2YXJpYWJsZXMKCiMjIyBhKSBjb24gYHBqcHYyMDIwLjAxYAoKWWEgIGhlIGRpY2hvIHF1ZSB5byBzdWVsbyB1dGlsaXphciBlc3RhcyAyIGZ1bmNpb25lczoKYGBge3IsIGV2YWwgPSBGQUxTRX0KenogPC0gcGpwdjIwMjAuMDE6OnBqcF9mX2VzdGFkaXN0aWNvc19iYXNpY29zKGRmKSAgICMtIGVzdGFkw61zdGljb3MgYsOhc2ljb3MgZGVsIGRmCnp6IDwtIHBqcHYyMDIwLjAxOjpwanBfZl91bmlxdWVfdmFsdWVzKGRmKSAgICAgICAgICAjLSB2YWxvcmVzIMO6bmljb3MgZGUgY2FkYSB2YXJpYWJsZSBkZSBkZgpgYGAKCjxicj4KCiMjIyBiKSBjb24gYHN1bW1hcnl0b29sc2AKCkxhIGZ1bmNpw7NuIGBzdW1tYXJ5dG9vbHM6OmRmU3VtbWFyeSgpYCBkYSB1biBpbmZvcm1lL3Jlc3VtZW4gZGUgbGFzIHZhcmlhYmxlcyBkZSB1biBkYXRhLmZyYW1lIG11eSDDunRpbC4gRXMgbXV5IMO6dGlsIHBhcmEgaGFjZXJzZSB1bmEgaWRlYSByw6FwaWRhIGRlIGxhcyBwcm9waWVkYWRlcyBkZSBsYXMgdmFyaWFibGVzLCBwZXJvIHRpZW5lIGxhIHBlZ2EgZGUgcXVlIGVsIG91dHB1dCBlcyBtdXkgdm9sdW1pbm9zb3MgcGFyYSBtb3N0cmFybG8gYXF1w60sIGFzw60gcXVlIHNpIHF1aWVyZXMgdmVybG8gdGVuZHLDoXMgcXVlIGVqZWN1dGFyIGVsIGFudGVyaW9yIGNodW5rIGRlIGZvcm1hIGludGVyYWN0aXZhLgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KenogPC0gc3VtbWFyeXRvb2xzOjpkZlN1bW1hcnkoZGYpICMtIGdlbmVyYSB1biBmaWNoZXJvIGNvbiB1biByZXN1bWVuIMO6dGlsIHkgYWdyYWRhYmxlIGRlIHZlciAKc3VtbWFyeXRvb2xzOjp2aWV3KHp6KSAgIy0gcGFyYSB2aXN1YWxpemFyIGVsIGluZm9ybWUKYGBgCgo8YnI+CgojIyMgYykgY29uIGBza2ltcmAKCk90cmEgYWx0ZXJuYXRpdmEgZXMgdXNhciBgc2tpbXI6OnNraW0oZGYpLiBPY3VycmUgbG8gbWlzbW86IGNvbW8gc3Ugb3V0cHV0IGVzIHZvbHVtaW5vc28sIG5vIGxvIHZveSBhIG1vc3RyYXIgYXF1w60uCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpza2ltcjo6c2tpbShkZikKYGBgCgotLS0tLS0tLS0tLS0tLS0tLS0tCgo8YnI+CgoKIyMgMy40IE5BJ3MKClNpZW1wcmUgaGF5IHF1ZSBjaGVxdWVhciBsYSBleGlzdGVuY2lhIGRlICoqTkEncyoqIGVuIG51ZXN0cm8gZGYuIE5BIHJlcHJlc2VudGEgdW5hIGNhcmFjdGVyw61zdGljYSBxdWUgZXhpc3RlIHBlcm8gbm8gc2FiZW1vcyBzdSB2YWxvciBFbiBSLCBsb3MgdmFsb3JlcyBubyBkaXNwb25pYmxlcyBzZSByZXByZXNlbnRhbiBjb24gYE5BYC4gU2UgcHVlZGUgY2hlcXVlYXIgc2kgdW4gdmFsb3IgZXMgTkEgY29uIGxhIGZ1bmNpw7NuIGBpcy5uYSgpYC4gRW4gZGF0b3MgZ2VuZXJhZG9zIHBvciBvdHJvcyBwYXF1ZXRlcyBlc3RhZMOtc3RpY29zLCBsb3MgdmFsb3JlcyBhdXNlbnRlcyBwdWVkZW4gZXN0YXIgY29kaWZpY2Fkb3MgY29uIGRpZmVyZW50ZXMgdmFsb3JlcyBjb21vIHBvciBlamVtcGxvICItOTk5IiwgIk4vQSIsIHVuIGVzcGFjaW8gZW4gYmxhbmNvLCBldGMgLi4uCgpUcmVzIHBhcXVldGVzIHF1ZSBub3MgcHVlZGVuIGF5dWRhciBlbiBlc3RhIHRhcmVhIHNvbiBgRGF0YUV4cGxvcmVyYCwgYHZpc2RhdGAgeSBgbmFuaWFyYC4KCgpgYGB7cn0KRGF0YUV4cGxvcmVyOjpwbG90X21pc3NpbmcoZGYpCmBgYAoKCmBgYHtyfQpuYW5pYXI6OmdnX21pc3NfdmFyKGRmLCBzaG93X3BjdCA9IFRSVUUpICAgICAgICAKYGBgCgoKRWwgcGFxdWV0ZSBgdmlzZGF0YCBlcyDDunRpbCBwYXJhIHZlciBsb3MgTkEncyBwZXJvIGVsIHNpZ3VpZW50ZSBjaHVuayBubyBub3MgdmEgYSBmdW5jaW9uYXIgcGFyYSBsYXMgYHIgZm9ybWF0KG5yb3coZGYpLCBiaWcubWFyayA9ICIuIiApYCBmaWxhcyBxdWUgdGllbmUgbnVlc3RybyBkZi4KCmBgYHtyLCBldmFsID0gRkFMU0V9CnZpc2RhdDo6dmlzX2RhdChkZikgIy0gbm8gZnVuY2lvbmEgLCBoYXkgZGVtYXNpYWRhcyBmaWxhcwpgYGAKClBlcm8gdmVhbW9zICBjb21vIGZ1bmNpb25hcsOtYSBwYXJhLCBwb3IgZWplbXBsbywgbGFzIHByaW1lcmFzIDEuMDAwIGZpbGFzIGRlIGRmOgoKYGBge3J9CmRmICU+JSBzbGljZSgxOjEwMDApICU+JSB2aXNkYXQ6OnZpc19kYXQoKQpgYGAKCi0gQWxnbyBwYXJlY2lkbyBwZXJvIGNvbiBgdmlzZGF0Ojp2aXNfbWlzcygpYAoKYGBge3J9CmRmICU+JSBzbGljZSgxOjEwMDApICU+JSB2aXNkYXQ6OnZpc19taXNzKGNsdXN0ZXIgPSBUUlVFKSAKYGBgCgotIE90cmEgZm9ybWEgZGUgdmlzdWFsaXphciBsYSAqKmNvLW9jdXJyZW5jaWEgZGUgTkEncyoqIGVuIGxhcyB2YXJpYWJsZXMgZGUgZGY6CgoKYGBge3J9Cm5hbmlhcjo6Z2dfbWlzc191cHNldChkZikKYGBgCgo8YnI+CgoKIyMjIHRyYWJhamFuZG8gY29uIGxvcyBgTkEnc2AKCi0gUG9yIGVqZW1wbG8sIHBvZGVtb3MgcXVlcmVyIHNlbGVjY2lvbmFyIGxhcyB2YXJpYWJsZXMgcXVlIHRpZW5lbiBOQSdzOgoKYGBge3J9Cnp6X2Nvbl9OQXMgIDwtIGRmICU+JSBzZWxlY3Qod2hlcmUoYW55TkEpKQpgYGAKCjxicj4KCi0gQSBsbyBtZWpvciwgbm9zIHB1ZWRlIGludGVyZXNhciB2ZXIgbGEgb2N1cnJlbmNpYSBkZSBOQSdzIHBhcmEgbG9zIGdydXBvcy9jYXRlZ29yw61hcyBkZSB1bmEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgZGUgZGYsIHBvciBlamVtcGxvIGxvcyBlc3R1ZGlvcyBkZSBsYSBtYWRyZShgRVNUVURJT00uZmApCgoKYGBge3J9Cm5hbmlhcjo6Z2dfbWlzc192YXIoZGYsIGZhY2V0ID0gRVNUVURJT00uZiwgc2hvd19wY3QgPSBUUlVFKSAjLSBmYWNldGVkIHBvciBsYSB2YXJpYWJsZSBFU1RVRElPTS5mCmBgYAoKCgpgYGB7ciwgZWNobyA9IEZBTFNFfQpybShsaXN0ID0gbHMocGF0dGVybiA9ICJeenoiKSkgICMtIGJvcnJhIGxvcyBvYmpldG9zIGN1eW8gbm9tYnJlIGVtcGllY2UgcG9yIHp6CmBgYAoKCgoKIyMjIMK/UXVlIGhhY2Vtb3MgY29uIGxvcyBOQSdzPwoKUHVlcyBubyBlc3TDoSBjbGFybywgZGVwZW5kZSAuLi4gcGVybyBzdXBvbmdhbW9zIHF1ZSBxdWVyZW1vcyBkZWphciBudWVzdHJvIGRmICoqU0lOIE5BJ3M7KiogCgoKYGBge3J9Cnp6IDwtIGRmICU+JSB0aWR5cjo6ZHJvcF9uYSgpICAgICAgICAgICAgICAjLSBjb24gdGlkeXIKCiMtIG90cmFzIGZvcm1hcyBkZSBoYWNlciBsbyBtaXNtbyAtLS0KenogPC0gZGZbY29tcGxldGUuY2FzZXMoZGYpLCBdICAgICAgICAgICAgICMtIGNvbiBiYXNlLVIgCnp6IDwtIGRmICU+JSBmaWx0ZXIoY29tcGxldGUuY2FzZXMoLikpICAgICAjLSBjb24gZHBseXIgeSBiYXNlLVIKYGBgCgoKU2kgcXVpdMOhcmFtb3MgdG9kb3MgbG9zIHJlZ2lzdHJvcy9iZWJlcyBxdWUgdGllbmVuIGFsZ8O6biBOQSBlbiBhbGd1bmEgZGUgbGFzIGByIG5jb2woZGYpYCB2YXJpYWJsZXMsIG5vcyBxdWVkYXLDrWFtb3MgY29uIGByIGZvcm1hdChucm93KHp6KSwgYmlnLm1hcmsgPSAiLiIpYCByZWdpc3Ryb3MuCgo8YnI+CgpTZSBwdWVkZSByZWZpbmFyIGxhIGVsaW1pbmFjacOzbiBkZSBgTkEnc2AuIFB1ZWRlcyBzZXIgcXVlIGlndWFsIElndWFsIG5vcyBpbnRlcmVzYXNlIGVsaW1pbmFyIHPDs2xvIGxhcyBmaWxhcyBxdWUgdGVuZ2FuIHVuIGBOQWAgZW4gdW5hIGRldGVybWluYWRhIHZhcmlhYmxlLCBwb3IgZWplbXBsbywgc2kgcXVpc2llcmFtb3MgcXVpdGFyIGxhcyBmaWxhcyBxdWUgdHV2aWVzZW4gTkEgZW4gbGEgdmFyaWFibGUgUEVTTzEgeS9vIGVuIGxhIHZhcmlhYmxlIFNFTUFOQVMsIGxvIGhhcsOtYW1vcyBhc8OtOgoKYGBge3J9Cnp6IDwtIGRmICU+JSB0aWR5cjo6ZHJvcF9uYShjKFBFU09OMSwgU0VNQU5BUykpICAgIy0gcXVpdG8gZmlsYXMgY29uIE5BIGVuIFBFU09OMSBvIGVuIFNFTUFOQVMKYGBgCgo8YnI+CgotIEltYWdpbmEgcXVlIGh1Ymllc2UgdW5hIGZpbGEgbyB1bmEgY29sdW1uYSBjb24gdG9kb3Mgc3VzIHZhbG9yZXMgTkEuIEzDs2dpY2FtZW50ZSBoYWJyw61hIHF1ZSBlbGltaW5hcmxhcy4gU2UgcHVlZGUgaGFjZXIgZsOhY2lsIGNvbiBgamFuaXRvcjo6cmVtb3ZlX2VtcHR5KCkKCmBgYHtyLCBldmFsID0gRkFMU0V9Cnp6IDwtIGRmICU+JSBqYW5pdG9yOjpyZW1vdmVfZW1wdHkoYygicm93cyIsICJjb2xzIikpICAgICMtIHF1aXRhIHZhcmlhYmxlcyB5IGZpbGFzIHZhY8OtYXMKYGBgCgoKCmBgYHtyLCBlY2hvID0gRkFMU0V9CnJtKGxpc3QgPSBscyhwYXR0ZXJuID0gIl56eiIpKSAgIy0gYm9ycmEgbG9zIG9iamV0b3MgY3V5byBub21icmUgZW1waWVjZSBwb3IgenoKYGBgCgotLS0tLS0tLS0tLS0tCgpicj4KCiMjIDMuNiBWYXJpYWJsZXMgbnVtw6lyaWNhcwoKVmFtb3MgYSB2ZXIgYWxndW5hcyBmdW5jaW9uZXMgw7p0aWxlcyBwYXJhIGhhY2VyIHVuIGFuw6FsaXNpcyBpbmljaWFsIGRlICBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMuCgpTaSBxdWlzaWVyYW1vcyBjcmVhciB1biBkZiAgc8OzbG8gY29uIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhczoKCgpgYGB7cn0KZGZfbnVtZXJpYyA8LSBkZiAlPiUgc2VsZWN0X2lmKGlzLm51bWVyaWMpCmBgYAoKCjxicj4KCiMjIyBhKSBFc3RhZMOtc3RpY29zIGRlc2NyaXB0aXZvcwoKUG9yIGVqZW1wbG8sIGBzdW1tYXJ5dG9vbHM6OmRlc2NyKClgIG5vcyBheXVkYSBhIGNhbGN1bGFyIHLDoXBpZGFtZW50ZSBlc3RhZMOtc3RpY29zIGRlc2NyaXB0aXZvcyBkZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMuCgpgYGB7ciwgcmVzdWx0cyA9ICJhc2lzIiwgZXZhbCA9IEZBTFNFfQpzdW1tYXJ5dG9vbHM6OmRlc2NyKGRmLCBzdHlsZSA9ICJybWFya2Rvd24iKQpgYGAKCgpgYGB7cn0Kc3VtbWFyeXRvb2xzOjpkZXNjcihkZikKYGBgCgo8YnI+CgojIyMgYikgSGlzdG9ncmFtYXMgbyBmLiBkZSBkZW5zaWRhZAoKRW4gZ2VuZXJhbCwgbG9zIG3DqXRvZG9zIGVzdGFkw61zdGljb3MgcmVxdWllcmVuIHF1ZSBsYXMgdmFyaWFibGVzIHNpZ2FuIHVuYSBkZXRlcm1pbmFkYSBkaXN0cmlidWNpw7NuLiBFc3RvIHB1ZWRlIGNoZXF1ZWFyc2UgZm9ybWFsbWVudGUgbyB1dGlsaXphciBoaXN0b2dyYW1hcyB5L28gZnVuY2lvbmVzIGRlIGRlbnNpZGFkIGVzdGltYWRhcy4KCgpQb2RlbW9zIHVzYXIgZWwgcGFxdWV0ZSBgRGF0YUV4cGxvcmVyYCAgcGFyYSBvYnRlbmVyIGhpc3RvZ3JhbWFzIG8gZ3LDoWZpY29zIGRlIGRlbnNpZGFkIHBhcmEgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzLgoKLSBIaXN0b2dyYW1hczoKCgpgYGB7cn0KRGF0YUV4cGxvcmVyOjpwbG90X2hpc3RvZ3JhbShkZiwgbmNvbCA9IDIpCmBgYAoKLSBGdW5jaW9uZXMgZGUgZGVuc2lkYWQgZXN0aW1hZGFzOgoKYGBge3J9CkRhdGFFeHBsb3Jlcjo6cGxvdF9kZW5zaXR5KGRmLCBuY29sID0gMikKYGBgCgo8YnI+CgojIyMgYykgTWF0cml6IGRlIGNvcnJlbGFjaW9uZXMKCk90cm8gYXNwZWN0byBpbXBvcnRhbnRlIGRlIHVuIGFuw6FsaXNpcyBleHBsb3JhdG9yaW8gY29uc2lzdGUgZW4gYW5hbGl6YXIgbGEgZXhpc3RlbmNpYSBkZSByZWxhY2lvbmVzIGVudHJlIGxhcyB2YXJpYWJsZXMgZGVsIGNvbmp1bnRvIGRlIGRhdG9zLiBFc3RvIHB1ZWRlIGhhY2Vyc2UgZGUgZGl2ZXJzYXMgbWFuZXJhcywgZG9zIGRlIGxhcyBtw6FzIHNlbmNpbGxhcyB5IHVzYWRhcyBzb24gbG9zIGdyw6FmaWNvcyBkZSBkaXNwZXJzacOzbiB5IGxhcyBtYXRyaWNlcyBkZSBjb3JyZWxhY2nDs24uCgpQb2RlbW9zIG9idGVuZXIgbGEgbWF0cml6IGRlIGNvcnJlbGFjaW9uZXMgZGUgZGl2ZXJzYXMgZm9ybWFzOgoKCi0gUHJpbWVybyBjb24gbGEgZnVuY2nDs24gYGNvcigpYCBkZSBSLWJhc2U6CgpgYGB7cn0Kc3RhdHM6OmNvcihkZl9udW1lcmljLCB1c2UgPSAicGFpcndpc2UuY29tcGxldGUub2JzIikgJT4lICAjLSBkZXZ1ZWx2ZSB1bmEgbWF0cml6LCBubyB1biBkZgogICAgICByb3VuZCguICwgMikKYGBgCgoKCmBgYHtyfQojZGZfbnVtZXJpYyAlPiUgR0dhbGx5OjpnZ2NvcnIobGFiZWwgPSBUUlVFKQpkZl9udW1lcmljICU+JSBHR2FsbHk6OmdncGFpcnMoKQpgYGAKCgotIEFob3JhIGNvbiBsYSBmdW5jacOzbiBgY29ycnI6OmNvcnJlbGF0ZSgpYAoKYGBge3J9CmRmICU+JSBzZWxlY3RfaWYoaXMubnVtZXJpYykgJT4lIAogICAgICAgY29ycnI6OmNvcnJlbGF0ZSgpICU+JSAKICAgICAgIHBqcHYyMDIwLjAxOjpwanBfZl9kZWNpbWFsZXMobm4gPSAyKSAlPiUgIAogICAgICAgZ3Q6Omd0KCkKYGBgCgoKPGJyPgoKLSBPdHJhIGZvcm1hIG3DoXMgZGUgdmlzdWFsaXphciBsYXMgY29ycmVsYWNpb25lcywgY29uIGBpbnNwZWN0ZGY6Omluc3BlY3RfY29yKClgCgpgYGB7cn0KZGYgJT4lIGluc3BlY3RkZjo6aW5zcGVjdF9jb3IoKQpgYGAKCi0gQWRlbcOhcywgZGVzcHXDqXMsIGNvbiBgaW5zcGVjdGRmOjpzaG93X3Bsb3QoKWAgc2UgcHVlZGVuIHZpc3VhbGl6YXIgbGFzIGNvcnJlbGFjaW9uZXMgZW4gdW4gZ3LDoWZpY286CgpgYGB7cn0KZGYgJT4lIGluc3BlY3RkZjo6aW5zcGVjdF9jb3IoKSAlPiUgaW5zcGVjdGRmOjpzaG93X3Bsb3QoKQpgYGAKCjxicj4KCgotIEVsIHBhcXVldGUgW2NvcnJlbGF0aW9uXShodHRwczovL2Vhc3lzdGF0cy5naXRodWIuaW8vY29ycmVsYXRpb24vKSBmYWNpbGl0YSBlbCBjw6FsY3VsbyBkZSBkaWZlcmVudGVzIGVzdGFkw61zdGljb3MgZGUgY29ycmVsYWNpw7NuLCBwb3IgZGVmZWN0byBjYWxjdWxhIGVsIGNvZWZpY2llbnRlIGRlIGNvcnJlbGFjacOzbiBkZSBQZWFyc29uOgoKYGBge3J9CmNvcnJlbGF0aW9uOjpjb3JyZWxhdGlvbihkZl9udW1lcmljKSAgICAjLSByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigiZWFzeXN0YXRzL2NvcnJlbGF0aW9uIikKYGBgCgo8YnI+CgojIyMgZCkgQm94cGxvdHMgZnJlbnRlIGEgdW5hIHYuIGNhdGVnw7NyaWNhCgpQYXJhIHZlciBkaWZlcmVuY2lhcyBlbiBsYSBkaXN0cmlidWNpw7NuIGRlIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcyBmcmVudGUgYSB1bmEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgZXMgbXV5IMO6dGlsIGBEYXRhRXhwbG9yZXI6OnBsb3RfYm94cGxvdCgpYC4gUG9yIGVqZW1wbG8gZnJlbnRlIGEgbGEgdmFyaWFibGUgYEVTVFVESU9NLmZgOiAKCgpgYGB7cn0KRGF0YUV4cGxvcmVyOjpwbG90X2JveHBsb3QoZGYsIGJ5ID0gIkVTVFVESU9NLmYiKQpgYGAKCjxicj4KCk8sIHBvciBlamVtcGxvLCBmcmVudGUgYSBsYSB2YXJpYWJsZSBgQ0VTQVJFQS5mYAoKYGBge3J9Cm15X3Z2IDwtIG5hbWVzKGRmKVszXQpteV92diA8LSAiQ0VTQVJFQS5mIgpEYXRhRXhwbG9yZXI6OnBsb3RfYm94cGxvdChkZiwgYnkgPSBteV92dikKYGBgCgo8YnI+CgotIFRhbWJpw6luIHNlIHB1ZWRlbiBoYWNlciBib3hwbG90cyBkZSB1bmEgdmFyaWFibGVzIG51bcOpcmljYSBmcmVudGUgYSAyIGNhdGVnw7NyaWNhczoKCmBgYHtyfQpkZiAlPiUgZXhwbG9yZTo6ZXhwbG9yZShFREFETSwgRVNUVURJT00uZiwgdGFyZ2V0ID0gQ0VTQVJFQS5mKQpgYGAKCjxicj4KCi0gQ29uIGBleHBsb3JlOjpleHBsb3JlX2FsbCgpYCBzZSBtdWVzdHJhbiBncsOhZmljb3MgZGUgdG9kYXMgbGFzIHZhcmlhYmxlcyBkZWwgZGYgZnJlbnRlIGEgdW5hIHZhcmlhYmxlIHRhcmdldCB1IG9iamV0aXZvOgoKYGBge3J9CmRmICU+JSBzZWxlY3QoTk9STUEuZiwgRURBRE0sIFBFU09OMSwgIEVTVFVESU9NLmYsIENFU0FSRUEuZikgJT4lIGV4cGxvcmU6OmV4cGxvcmVfYWxsKHRhcmdldCA9IENFU0FSRUEuZikKYGBgCgpWZWFtb3MgbGEgcmVsYWNpw7NuIGVudHJlICJQYXJ0byBub3JtYWwiIHkgQ0VTQVJFQS5mOgoKYGBge3J9CmRmICU+JSBqYW5pdG9yOjp0YWJ5bChOT1JNQS5mLCBDRVNBUkVBLmYpICU+JSBqYW5pdG9yOjphZG9ybl9wZXJjZW50YWdlcygpCmBgYAoKCjxicj4KCgojIyMgZSkgU2NhdHRlcnBsb3RzIGVudHJlIHZhcmlhYmxlcyBudW3DqXJpY2FzCgoKYERhdGFFeHBsb3Jlcjo6cGxvdF9zY2F0dGVycGxvdCgpYCBub3MgcGVybWl0ZSBoYWNlciByw6FwaWRhbWVudGUgdW4gc2NhdHRlcnBsb3QgZGUgdG9kYXMgbGFzIHZhcmlhYmxlcyBkZWwgZGYgZnJlbnRlIGEgdW5hIHZhcmlhYmxlLCBwb3IgZWplbXBsbyBlbCBwZXNvIGRlbCBiZWJlOiBgUEVTT04xYAoKCmBgYHtyfQpteV92diA8LSAiUEVTT04xIgpEYXRhRXhwbG9yZXI6OnBsb3Rfc2NhdHRlcnBsb3QoZGYsIGJ5ID0gbXlfdnYsIHNhbXBsZWRfcm93cyA9IDUwMEwpCmBgYAoKLS0tLS0tLS0tLS0tLS0tLQoKPGJyPgoKCiMjIDMuNyBWYXJpYWJsZXMgY2F0ZWfDs3JpY2FzCgoKU2kgbmVjZXNpdMOhc2Vtb3MgdW4gZGYgY29uIHRvZGFzIGxhcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzLCDCv2PDs21vIGxvIG9idGVuZHLDrWFzPwoKCmBgYHtyfQpkZiAlPiUgc2VsZWN0X2lmKGlzLm51bWVyaWMpICU+JSBuYW1lcygpICAgICAgIy0gb2xkIGZhc2hpb24KZGYgJT4lIHNlbGVjdCh3aGVyZShpcy5udW1lcmljKSkgJT4lIG5hbWVzKCkgIApgYGAKCsK/WSBzaSBxdWlzaWVyYXMgc2VsZWNjaW9uYXIgbGFzIHZhcmlhYmxlcyBuby1udW3DqXJpY2FzPwoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZGYgJT4lIHNlbGVjdF9pZighKGlzLm51bWVyaWMoLikpKSAjLSBOTyBmdW5jaW9uYQpkZiAlPiUgc2VsZWN0X2lmKH4gIWlzLm51bWVyaWMoLikpICU+JSBuYW1lcygpICAgICAjLSBhbm9ueW1vdXMgZnVuY3Rpb24gYnV0IHNlbGVjdF9pZgpkZiAlPiUgc2VsZWN0KHdoZXJlKH4gIWlzLm51bWVyaWMoLikpKSAlPiUgbmFtZXMoKSAjLSBhbm9ueW1vdXMgZnVuY3Rpb25zICYgd2hlcmUKZGYgJT4lIHNlbGVjdCh3aGVyZShwdXJycjo6bmVnYXRlKGlzLm51bWVyaWMpKSkgICU+JSBuYW1lcygpICAgIy0gcHVycnI6Om5lZ2F0ZSgpISEhIQpgYGAKCgpQYXJhIHZpc3VhbGl6YXIgcsOhcGlkYW1lbnRlIGxvcyB2YWxvcmVzIGRlIGxhcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzLCB0ZW5lbW9zIGBpbnNwZWN0ZGY6Omluc3BlY3RfY2F0KClgLCBxdWUgbm9zIGRldnVlbHZlIHVuIGdyw6FmaWNvIGNvbiBsYSBkaXN0cmlidWNpw7NuIGRlIHRvZGFzIGxhcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzLgoKCgpgYGB7cn0KaW5zcGVjdGRmOjppbnNwZWN0X2NhdChkZikgJT4lIGluc3BlY3RkZjo6c2hvd19wbG90KGhpZ2hfY2FyZGluYWxpdHkgPSAxKQpgYGAKCjxicj4KCi0gVGFtYmnDqW4gcG9kZW1vcyBoYWNlcmxvIGNvbiBgRGF0YUV4cGxvcmVyOjpwbG90X2JhcigpYDoKCgpgYGB7cn0KRGF0YUV4cGxvcmVyOjpwbG90X2JhcihkZikKYGBgCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tCgoKIyMgMy44IFBhcXVldGVzIGNvbiBzaGlueQoKCiMjIyBhKSBgYnVycm9gCgpFbCBwYXF1ZXRlIFtgYnVycm9gXShodHRwczovL2dpdGh1Yi5jb20vbGFkZXJhc3QvYnVycm8pOgoKPiBidXJybyBhdHRlbXB0cyB0byBtYWtlIEVEQSBhY2Nlc3NpYmxlIHRvIGEgbGFyZ2VyIGF1ZGllbmNlIGJ5IGV4cG9zaW5nIGRhdGFzZXRzIGFzIGEgc2ltcGxlICoqU2hpbnkgQXBwKiogCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KIy0gZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJsYWRlcmFzdC9idXJybyIpCmRmX3NtYWxsIDwtIGRmICU+JSBzbGljZSgxOjEwMDApCmJ1cnJvOjpleHBsb3JlX2RhdGEoZGZfc21hbGwsIG91dGNvbWVfdmFyID0gY29sbmFtZXMoZGYpKQpgYGAKCjxicj4KCiMjIyBiKSBgZXhwbG9yZWAKCkVsIHBhcXVldGUgW2BleHBsb3JlYF0oaHR0cHM6Ly9naXRodWIuY29tL3JvbGtyYS9leHBsb3JlKS4KCj4gSW5zdGVhZCBvZiBsZWFybmluZyBhIGxvdCBvZiBSIHN5bnRheCBiZWZvcmUgeW91IGNhbiBleHBsb3JlIGRhdGEsIHRoZSBleHBsb3JlIHBhY2thZ2UgZW5hYmxlcyB5b3UgdG8gaGF2ZSBpbnN0YW50IHN1Y2Nlc3MuIFlvdSBjYW4gc3RhcnQgd2l0aCBqdXN0IG9uZSBmdW5jdGlvbiAtIGV4cGxvcmUoKSAtIGFuZCBsZWFybiBvdGhlciBSIHN5bnRheCBsYXRlciBzdGVwIGJ5IHN0ZXAKCgotIFBvZGVtb3MgY3JlYXIgdW4gc2hpbnkgcGFyYSBleHBsb3JhciBudWVzdHJvIGRmIGNvbXBsZXRvOgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZXhwbG9yZTo6ZXhwbG9yZShkZikKYGBgCgotIG8gc29sbyBleHBsb3JhciB1bmEgbyB2YXJpYXMgdmFyaWFibGVzLgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZGYgJT4lIGV4cGxvcmU6OmV4cGxvcmUoQ0VTQVJFQS5mKQpkZiAlPiUgZXhwbG9yZTo6ZXhwbG9yZShFREFETSwgRVNUVURJT00uZiwgdGFyZ2V0ID0gQ0VTQVJFQS5mKQpgYGAKCjxicj4KCiMjIyBjKSBgRXhQYW5EYVJgCgpFbCBwYXF1ZXRlW0V4UGFuRGFSXShodHRwczovL2pvYWNoaW0tZ2Fzc2VuLmdpdGh1Yi5pby9FeFBhbkRhUi8pIHRhbWJpw6luIHBlcm1pdGUgZXhwbG9yYXIgbG9zIGRhdG9zIGEgdHJhdsOpcyBkZSB1biBzaGlueS4gRXhwbGljYW4gc3UgZnVuY2lvbmFtaWVudG8gZW4gW2VzdGUgcG9zdF0oaHR0cHM6Ly9qb2FjaGltLWdhc3Nlbi5naXRodWIuaW8vMjAxOS8xMi9leHBsb3JlLXlvdXItZGF0YS13aXRoLWV4cGFuZC8pIHkgW2VzdGUgb3Ryb10oaHR0cHM6Ly9qb2FjaGltLWdhc3Nlbi5naXRodWIuaW8vMjAxOS8wNC9jdXN0b21pemUteW91ci1pbnRlcmFjdGl2ZS1lZGEtZXhwbG9yZS10aGUtZnVlbC1lY29ub215LW9mLXRoZS11LnMuLWNhci1tYXJrZXQvKS4KCmBgYHtyLCBldmFsID0gRkFMU0V9CmxpYnJhcnkoRXhQYW5EYVIpICMtIHJlbW90ZXM6Omluc3RhbGxfZ2l0aHViKCJqb2FjaGltLWdhc3Nlbi9FeFBhbkRhUiIpCgpFeFBhbkQobXRjYXJzKQpgYGAKCgo8YnI+CgotLS0tLS0tLS0tLS0tLQoKPGJyPgoKCiMgNC4gVGFibGFzIAoKCk11Y2hhcyB2ZWNlcyBoYXkgcXVlIHByZXNlbnRhciByZXN1bHRhZG9zIGLDoXNpY29zIGNvbW8gdW5hIHRhYmxhIGRlIGNhc29zIG8gZGUgZnJlY3VlbmNpYXMuIHBhcmEgZWxsbyB0ZW5lbW9zIG11Y2hhcyBwb3NpYmlsaWRhZGVzOgoKIyMgNC4xIFRhYmxhcyBjb24gYHN1bW1hcnl0b29sc2AKCkVsIHBhcXVldGUgW2BzdW1tYXJ5dG9vbHNgXShodHRwczovL2dpdGh1Yi5jb20vZGNvbXRvaXMvc3VtbWFyeXRvb2xzKS4gCgo+IGBzdW1tYXJ5dG9vbHNgIGlzIGFuIFIgcGFja2FnZSBwcm92aWRpbmcgdG9vbHMgdG8gbmVhdGx5IGFuZCBxdWlja2x5IHN1bW1hcml6ZSBkYXRhLiBJdCBjYW4gYWxzbyBtYWtlIFIgYSBsaXR0bGUgZWFzaWVyIHRvIGxlYXJuIGFuZCB0byB1c2UsIGVzcGVjaWFsbHkgZm9yIGRhdGEgY2xlYW5pbmcgYW5kIHByZWxpbWluYXJ5IGFuYWx5c2lzLiAKCgotIExhIGZ1bmNpw7NuIGBmcmVxKClgIHByb3ZlZSB0YWJsYXMgY29uIGNvbnRlb3MgeSBmcmVjdWVuY2lhcwoKCmBgYHtyLCByZXN1bHRzID0gImFzaXMifQpzdW1tYXJ5dG9vbHM6OmZyZXEoZGYkU0VYTzEsIHN0eWxlID0gInJtYXJrZG93biIpCmBgYAoKPGJyPgoKLSB0YWJ1bGFjacOzbiBjcnV6YWRhIGVudHJlIGRvcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzOgoKYGBge3IsIHJlc3VsdHMgPSAiYXNpcyJ9CnN1bW1hcnl0b29sczo6Y3RhYmxlKGRmJFNFWE8xLCBkZiRDRVNBUkVBLmYpCmBgYAoKPGJyPgoKLSBJbmNsdXNvIHNlIHB1ZWRlbiBoYWNlciB0ZXN0IGNoaS1jdWFkcmFkbwoKCmBgYHtyLCByZXN1bHRzID0gImFzaXMifQpzdW1tYXJ5dG9vbHM6OmN0YWJsZShkZiRFU1RVRElPTS5mLCBkZiROT1JNQS5mLCBjaGlzcSA9IFRSVUUpCmBgYAoKLS0tLS0tLS0KCjxicj4KCgojIyA0LjIgVGFibGFzIGNvbiBgamFuaXRvcmAKCgpMYSB2ZXJkYWQgZXMgcXVlIFtgamFuaXRvcmBdKGh0dHA6Ly9zZmlya2UuZ2l0aHViLmlvL2phbml0b3IvKSBlcyB1biBwYXF1ZXRlIGZhbnTDoXN0aWNvISEhIAoKPiBqYW5pdG9yIGhhcyBzaW1wbGUgZnVuY3Rpb25zIGZvciBleGFtaW5pbmcgYW5kIGNsZWFuaW5nIGRpcnR5IGRhdGEuIEl0IHdhcyBidWlsdCB3aXRoIGJlZ2lubmluZyBhbmQgaW50ZXJtZWRpYXRlIFIgdXNlcnMgaW4gbWluZCBhbmQgaXMgb3B0aW1pemVkIGZvciB1c2VyLWZyaWVuZGxpbmVzcy4gQWR2YW5jZWQgUiB1c2VycyBjYW4gYWxyZWFkeSBkbyBldmVyeXRoaW5nIGNvdmVyZWQgaGVyZSwgYnV0IHdpdGggamFuaXRvciB0aGV5IGNhbiBkbyBpdCBmYXN0ZXIgYW5kIHNhdmUgdGhlaXIgdGhpbmtpbmcgZm9yIHRoZSBmdW4gc3R1ZmYuCgoKCi0gIGhhY2VyICh5IGRhciBmb3JtYXRvKSBhIHRhYmxhcyBkZSAxLCAyIG8gMyB2YXJpYWJsZXM6CgpgYGB7cn0KZGYgJT4lIGphbml0b3I6OnRhYnlsKENFU0FSRUEuZikgJT4lIGd0OjpndCgpCmBgYAoKCmBgYHtyfQpkZiAlPiUgamFuaXRvcjo6dGFieWwoQ0VTQVJFQS5mLCBFU1RVRElPTS5mKSAlPiUgZ3Q6Omd0KCkKYGBgCgo8YnI+CgpgYGB7cn0KZGYgJT4lIGphbml0b3I6OnRhYnlsKENFU0FSRUEuZiwgRVNUVURJT00uZiwgU0VYTzEpICAjLSB4cSBubyBwb2RlbW9zIHVzYXIgZ3Q6Omd0KCkKYGBgCgotLS0tLS0tLS0tLQoKPGJyPgoKIyMgNC4zIFRhYmxhcyBjb24gUi1iYXNlCgpMYXMgdGFibGFzIGNvbiBSLWJhc2UgdGFtcG9jbyBlc3TDoW4gbWFsLiBFbCBwcm9ibGVtYSBxdWUgdGllbmVuIGVzIHF1ZSBsb3MgcmVzdWx0YWRvcyBubyBzZSBhbG1hY2VuYW4gZW4gZGF0YS5mcmFtZXMsIHNpIG5vIGVuIG1hdHJpY2VzLgoKYGBge3J9CnRhYmxlKGRmJENFU0FSRUEuZiwgZGYkRVNUVURJT00uZiwgdXNlTkEgPSAiYWx3YXlzIikgCmBgYAoKLSBDb21vIHNlIGFsbWFjZW5hbiBlbiBtYXRyaWNlcywgcGFyYSBwb2RlciBncmFmaWNhcmxhcyBjb24gYGd0YCBwcmltZXJvIGxhcyBoZW1vcyBkZSBjb252ZXJ0aXIgYSBkYXRhLmZyYW1lIHkgY2FzaSBzZWd1cm8gcXVlIGhhYnLDoSBxdWUgaGFjZXIgdXNvIGRlIGBwaXZvdF93aWRlcigpYAoKYGBge3J9Cm15X3RhYmxhIDwtIHRhYmxlKGRmJENFU0FSRUEuZiwgZGYkRVNUVURJT00uZikgCm15X3RhYmxhICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIAogICAgICAgICAgICBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gMiwgdmFsdWVzX2Zyb20gPSAzKSAlPiUgCiAgICAgICAgICAgIGd0OjpndCgpCmBgYAoKPGJyPgoKLSBgbW9zYWljcGxvdCgpYCwgdW4gZ3LDoWZpY28gcXVlIG1lIGd1c3RhIGRlbCBzaXN0ZW1hIGRlIFItYmFzZQoKYGBge3J9Cnp6IDwtIHByb3AudGFibGUobXlfdGFibGEsIDEpCnp6IDwtIHp6WzE6MixdICAgIy0gdGVuZ28gcXVlIGhhY2VyIGVzdG8geHEgaGF5IE5BJ3MgZW4gbGEgdGFibGEKbW9zYWljcGxvdCh0KHp6KSwgY29sb3IgPSBUUlVFLCBtYWluID0gIiUgZGUgQ2Vzw6FyZWFzIHBhcmEgbml2ZWxlcyBlZHVjYXRpdm9zIGRlIGxhIG1hZHJlIikKYGBgCgoKLS0tLS0tLS0tLS0tLS0tLS0KCjxicj4KCgojIDUuIENvbnRyYXN0ZXMgCgoKRXZpZGVudGVtZW50ZSBSIHBlcm1pdGUgaW1wbGVtZW50YXIgbcO6bHRpcGxlcyB0w6ljbmljYXMgeSBtb2RlbG9zIGVzdGFkw61zdGljb3MsIGFzw60gY29tbyBoYWNlciBtw7psdGlwbGVzIHkgdmFyaWFkb3MgY29udHJhc3RlcyBkZSBoaXDDs3Rlc2lzLiBFbiBbZXN0YSBwYWdpbmEgd2ViXShodHRwczovL3d3dy5zdGF0bWV0aG9kcy5uZXQvc3RhdHMvaW5kZXguaHRtbCkgdGVuw6lpcyB1biBidWVuYSBpbnRyb2R1Y2Npw7NuIGEgZXN0b3MgdGVtYS4gVmVhbW9zIGFsZ8O6biBlamVtcGxvOgoKCi0gYHQtdGVzdGA6IDxodHRwczovL3N0YXRpc3RpY3MuYmVya2VsZXkuZWR1L2NvbXB1dGluZy9yLXQtdGVzdHM+CgpgYGB7ciwgZXZhbCA9IFRSVUV9CnQudGVzdChkZiRQRVNPTjEsIG11ID0gMzI1MCkKdC50ZXN0KGRmJFBFU09OMSB+IGRmJENFU0FSRUEuZikKYGBgCgoKLSBjb3JyZWxhY2nDs24gZW50cmUgZG9zIHZhcmlhYmxlcyBjdWFudGl0YXRpdmFzCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KEhtaXNjKQpIbWlzYzo6cmNvcnIoYXMubWF0cml4KGRmX251bWVyaWMpKSAKYGBgCgoKCi0gdW4gZWplbXBsbyBwYXJhIHZlciBzaSBoYXkgZGlmZXJlbmNpYXMgZW4gZWwgcGVzbyBlbiBmdW5jacOzbiBkZSBsb3MgZXN0dWRpb3MgZGUgbGEgbWFkcmUgKCEhISEpCgpgYGB7cn0KbGlicmFyeShwdXJycikKbGlicmFyeShicm9vbSkKZGYgJT4lIGdyb3VwX2J5KEVTVFVESU9NLmYpICU+JSAKICBzdW1tYXJpc2UodF90ZXN0ID0gbGlzdCh0LnRlc3QoUEVTT04xKSkpICU+JSAKICBtdXRhdGUodGlkaWVkID0gbWFwKHRfdGVzdCwgdGlkeSkpICU+JSAKICB0aWR5cjo6dW5uZXN0KHRpZGllZCkgJT4lIAogIGdncGxvdChhZXMoZXN0aW1hdGUsIEVTVFVESU9NLmYpKSArIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9lcnJvcmJhcmgoYWVzKHhtaW4gPSBjb25mLmxvdywgeG1heCA9IGNvbmYuaGlnaCkpICsgCiAgbGFicyh0aXRsZSA9ICJQZXNvIGRlbCBiZWJlIHBhcmEgZGlmZXJlbnRlcyBuaXZlbGVzIGVkdWNhdGl2b3MgZGUgbGEgbWFkcmUiKQpgYGAKCgoKCi0gYGNoaS1zcXVhcmVkIHRlc3RgOiA8aHR0cHM6Ly9kYXRhLWZsYWlyLnRyYWluaW5nL2Jsb2dzL2NoaS1zcXVhcmUtdGVzdC1pbi1yLz4KCgpgYGB7cn0KY2hpc3EudGVzdChkZiRDRVNBUkVBLmYsIGRmJFNFWE8xKQpjaGlzcS50ZXN0KGRmJENFU0FSRUEuZiwgZGYkRVNUVURJT00uZikgCmBgYAoKCgotIFtBcXXDrV0oaHR0cHM6Ly9tZ2ltb25kLmdpdGh1Yi5pby9TdGF0cy1pbi1SL2luZGV4Lmh0bWwpIHB1ZWRlcyBlbmNvbnRyYXIgdW4gY3Vyc28gc29icmUgY29tbyBpbXBsZW1lbnRhciBsb3MgY29udHJhc3RlcyBlc3RhZMOtc3RpY29zIG3DoXMgaGFiaXR1YWxlcyBjb24gUi4KCgpFbiBbZXN0ZSBwb3N0XShodHRwczovL2xpbmRlbG9ldi5naXRodWIuaW8vdGVzdHMtYXMtbGluZWFyLyksIEpvbmFzIEtyaXN0b2ZmZXIgTGluZGVsw7h2LCBub3MgcHJlc2VudGEgdW4gY3VhZHJvIGNvbiBsb3MgY29udHJhc3RlcyBkZSBoaXDDs3Rlc2lzIG3DoXMgZnJlY3VlbnRlcyB5IGPDs21vIGVmZWN0dWFyIGVzb3MgY29udHJhc3RlcyBlbiBlbCBjb250ZXh0byBkZSBsb3MgbW9kZWxvcyBkZSByZWdyZXNpw7NuIGNvbiBSLgoKCjxicj4KCi0tLS0tLS0tLS0tLS0tLS0tLQoKCiMgNi4gTW9kZWxvcwoKCkVuIGVzdGUgYXBhcnRhZG8gdmFtb3MgYSB2ZXIgKHVuIHBvY29eW0xhIHZlcmRhZCBlcyBxdWUgY29uIGxhIGNhbnRpZGFkIGRlIG1hdGVyaWFsZXMgZmFudMOhc3RpY29zIHF1ZSBoYXkgc29icmUgUiwgbm8gaGF5IG5lY2VzaWRhZCBkZSBleHBsaWNhciBvIGVzY3JpYmlyIHNvYnJlIHRvZG9zIGxvcyB0ZW1hc10pIGNvbW8gZXN0aW1hciBtb2RlbG9zIGxpbmVhbGVzIHkgbm8gbGluZWFsZXMgY29uIFIuIFBhcmEgdW5hIGludHJvZHVjY2nDs24gYSBsYSBlc3RpbWFjacOzbiBkZSBtb2RlbG9zIGVuIFIgcHVlZGVzIGlyIFthcXXDrV0oaHR0cHM6Ly9tLWNsYXJrLmdpdGh1Yi5pby9SLW1vZGVscy8jaW50cm9kdWN0aW9uKS4KCgpBZGVtw6FzLCB0ZW5lbW9zL3RlbmdvIHN1ZXJ0ZSBkZSBxdWUgbWUgcHVlZG8gYXBveWFyIGVuIGxvIHF1ZSB5YSBoYWLDqWlzIHZpc3RvIGVuIEVjb25vbWV0csOtYSB5IG90cmFzIGFzaWduYXR1cmFzLiBFbCBvYmpldGl2bywgYXBhcnRlIGRlIHZlciBjb21vIHNlIHB1ZWRlbiBoYWNlciBhbsOhbGlzaXMgZGUgcmVncmVzacOzbiBjb24gUiwgZXMgaXIgcHJlcGFyYW5kbyBlbCBjYW1pbm8gcGFyYSBpbnRyb2R1Y2lyIGFsZ28gZGUgTWFjaGluZSBMZWFybmluZy4KCkFudGVzIHJlc3RyaW5qYW1vcyBhw7puIHVuIHBvY28gbcOhcyBlbCBkZiBjb24gbG9zIGRhdG9zIHNvYnJlIGJlYmVzOgoKCmBgYHtyfQpkZl9tIDwtIGRmICU+JSBzZWxlY3QoUEVTT04xLCBTRU1BTkFTLCBTRVhPMSwgQ0VTQVJFQS5mLCBFREFETSwgRURBRFAsIEVTVFVESU9NLmYsIEVTVFVESU9QLmYpCmRmX20gPC0gZGZfbSAlPiUgZHJvcF9uYSgpCmBgYAoKCgojIyA2LjEgTW9kZWxvcyBsaW5lYWxlcwoKTGEgZnVuY2nDs24gcGFyYSBlc3RpbWFyIG1vZGVsb3MgbGluZWFsZXMgZXMgYGxtKClgLCBhc8OtIHF1ZSB2YW1vcyBhIHV0aWxpemFybGEgcGFyYSBlc3RpbWFyIG51ZXN0cm8gcHJpbWVyIG1vZGVsbyBjb24gUi4gRXN0aW1hcmVtb3MgdW4gbW9kZWxvIGxpbmVhbCBjb24gdmFyaWFibGUgYSBleHBsaWNhciBlbCBwZXNvIGRlbCBiZWJlIChgUEVTT04xYCkgZW4gZnVuY2nDs24gZGUgdG9kYXMgbGFzIGRlbcOhcyB2YXJpYWJsZXMgZW4gYGRmX21gLgoKCgpgYGB7ciwgZXZhbCA9IFRSVUV9Cm1vZF8xIDwtIGxtKFBFU09OMSB+IC4gLCBkYXRhID0gZGZfbSkKc3VtbWFyeShtb2RfMSkKYGBgCgoKR2VuZXJhbG1lbnRlIGxhcyAqKnZhcmlhYmxlcyBjYXRlZ8OzcmljYXMqKiBzZSBpbnRyb2R1Y2VuIGVuIGxvcyBtb2RlbG9zIG1lZGlhbnRlICoqdmFyaWFibGVzIGR1bW1pZXMqKi4gQ3JlYXIgZHVtbWllcyBlbiBSIGVzIHNlbmNpbGxvLCBzb2xvIHRpZW5lcyBxdWUgdGVuZXIgbG9zIGRhdG9zIGNvbW8gZmFjdG9yIG8gY29tbyB0ZXh0byB5IFIgY3JlYXLDoSBsYXMgZHVtbWllcyBwb3IgdGkgY3VhbmRvIGludHJvZHV6Y2FzIGxhIHZhcmlhYmxlIGVuIGBsbSgpYC4gRXNvIHPDrSwgZXMgbcOhcyBmw6FjaWwgZWxlZ2lyIGxhIGNhdGVnb3LDrWEgZGUgcmVmZXJlbmNpYSBzaSBsYSB2YXJpYWJsZSBlcyB1biBmYWN0b3IuCgpQYXJhIGVudGVuZGVyIGNvbW8gZmlqYSBsb3MgcmVncmVzb3JlcyBwYXJhIGxhcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzLCBtaXJhIGVzdG86CiAKYGBge3J9CmxldmVscyhkZiRTRVhPMSkKbGV2ZWxzKGRmJEVTVFVESU9NLmYpCmBgYAogClNpIG5lY2VzaXRhcyBjYW1iaWFyIGxhIGNhdGVnb3LDrWEgZGUgcmVmZXJlbmNpYSBzaWVtcHJlIHB1ZWRlcyB1c2FyIGBmb3JjYXN0OjpmY3RfcmVsZXZlbCgpYAoKYGBge3J9Cnp6IDwtIGZvcmNhdHM6OmZjdF9yZWxldmVsKGRmJFNFWE8xLCAiYmViaXRvIikKbGV2ZWxzKHp6KQpgYGAKCgpWZWFtb3MgcXVlIGhheSBlbiBlbCBvYmpldG8gYG1vZF8xYAoKCmBgYHtyLCBldmFsID0gRkFMU0V9CnN0cihtb2RfMSkKI2xpc3R2aWV3ZXI6Ompzb25lZGl0KG1vZF8xLCBtb2RlID0gInZpZXciKSAjIyBJbnRlcmFjdGl2ZSBvcHRpb24KYGBgCgoKIyMjIyBFc3BlY2lmaWNhY2nDs24gZGVsIG1vZGVsbwoKTMOzZ2ljYW1lbnRlLCBhIHZlY2VzIHF1ZXJyZW1vcyBzZWxlY2Npb25hciBsYXMgdmFyaWFibGVzIGV4cGxpY2F0aXZhczoKCmBgYHtyLCBldmFsID0gVFJVRX0KbW9kXzEgPC0gbG0oUEVTT04xIH4gU0VNQU5BUywgZGF0YSA9IGRmX20pCm1vZF8xIDwtIGxtKFBFU09OMSB+IFNFTUFOQVMgKyBTRVhPMSArIENFU0FSRUEuZiArIEVEQURNICwgZGF0YSA9IGRmX20pCm1vZF8xIDwtIGxtKGxvZyhQRVNPTjEpIH4gbG9nKFNFTUFOQVMpLCBkYXRhID0gZGZfbSkKCnN1bW1hcnkobW9kXzEpCmBgYAoKClNpIHF1ZXJlbW9zIGludHJvZHVjaXIgaW50ZXJhY2Npb25lcyBlbnRyZSBsb3MgcmVncmVzb3JlcywgcG9kZW1vcyB1c2FyIGVsIG9wZXJhZG9yIGA6YCwgYXVucXVlIGNhc2kgbWVqb3IgaGFjZXJsbyBkaXJlY3RhbWVudGUgY29uIGBJKClgOgoKYGBge3J9Cm1vZF8xIDwtIGxtKFBFU09OMSB+IFNFTUFOQVMgKyBTRU1BTkFTOkVEQURNLCBkYXRhID0gZGZfbSkKbW9kXzEgPC0gbG0oUEVTT04xIH4gU0VNQU5BUyArIEkoU0VNQU5BUypFREFETSksIGRhdGEgPSBkZl9tKQoKc3VtbWFyeShtb2RfMSkKYGBgCgoKCgpTaSBxdWVyZW1vcyBpbnRyb2R1Y2lyIGxhcyB2YXJpYWJsZXMgb3JpZ2luYWxlcyB5IHRhbWJpw6luIGxhcyBpbnRlcmFjY2lvbmVzIGVudHJlIGVsbGFzLCBwb2RlbW9zIGhhY2VybG8gZGlyZWN0YW1lbnRlIG8gbyB1dGlsaXphciBlbCBvcGVyYWRvciBgKmA6CgoKYGBge3J9Cm1vZF8xIDwtIGxtKFBFU09OMSB+IFNFTUFOQVMgKyBFREFETSArIFNFTUFOQVM6RURBRE0sIGRhdGEgPSBkZl9tKQptb2RfMSA8LSBsbShQRVNPTjEgfiBTRU1BTkFTICsgRURBRE0gKyBJKFNFTUFOQVMqRURBRE0pLCBkYXRhID0gZGZfbSkKbW9kXzEgPC0gbG0oUEVTT04xIH4gU0VNQU5BUypFREFETSwgZGF0YSA9IGRmX20pCm1vZF8xIDwtIGxtKFBFU09OMSB+IFNFTUFOQVMqRVNUVURJT00uZiwgZGF0YSA9IGRmX20pCgpzdW1tYXJ5KG1vZF8xKQpgYGAKClJlY3VlcmRhIHF1ZSBzaSBxdWVyZW1vcyBpbnRyb2R1Y2lyIGFsZ8O6biByZWdyZXNvciBxdWUgc2VhIGxhIG11bHRpcGxpY2FjacOzbiBkZSBkb3MgdmFyaWFibGVzLCB0ZW5kcmVtb3MgcXVlIGhhY2VybG8gY29uIGBJKClgCgoKYGBge3J9Cm1vZF8xIDwtIGxtKFBFU09OMSB+IFNFTUFOQVMgKyBJKFNFTUFOQVMqU0VNQU5BUyksIGRhdGEgPSBkZl9tKQoKc3VtbWFyeShtb2RfMSkKYGBgCgoKVGFtYmnDqW4gcHVlZGUgc2Vybm9zIGRlIHV0aWxpZGFkIGxhIGZ1bmNpw7NuIGBwb2x5KClgCgpgYGB7cn0KbW9kXzEgPC0gbG0oUEVTT04xIH4gcG9seShTRU1BTkFTLCBkZWdyZWUgPSAzKSwgZGF0YSA9IGRmX20pCgpzdW1tYXJ5KG1vZF8xKQpgYGAKCjxicj4KCiMjIyMgUmVzdWx0YWRvcyBkZSBlc3RpbWFjacOzbgoKClVuYSB2ZXogcXVlIHNhYmVtb3MgbGEgc2ludGF4aXMgZGUgYHN0YXRzOjpsbSgpYCwgdmFtb3MgYSB2ZXIgY29tbyBwb2RlbW9zIGFjY2VkZXIgYSBsYSBpbmZvcm1hY2nDs24gcXVlIGRldnVlbHZlIGBsbSgpYC4gWWEgaGVtb3MgdmlzdG8gcXVlIGxvcyByZXN1bHRhZG9zIHNlIGFsbWFjZW5hbiBlbiB1bmEgbGlzdGEsIGFzw60gcXVlIHBvZGVtb3MgdXRpbGl6YXIgbG9zIG9wZXJhZG9yZXMgaGFiaXR1YWxlcyBkZSBsYXMgbGlzdGFzIHBhcmEgYWNjZWRlciBhIGxhIGluZm9ybWFjacOzbi4gUG9yIGVqZW1wbG86CgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KenpfYmV0YXMgICAgIDwtIG1vZF8xW1sxXV0KCnp6X3Jlc2lkdWFscyA8LSBtb2RfMVtbMl1dCnp6X3Jlc2lkdWFscyA8LSBtb2RfMVtbInJlc2lkdWFscyJdXQp6el9yZXNpZHVhbHMgPC0gbW9kXzEkcmVzaWR1YWxzCgp6el9iZXRhc18xICAgPC0gbW9kXzFbWzFdXSAgICAgICMtIFtbIF1dIGRvYmxlIGNvcmNoZXRlCnp6X2JldGFzXzIgPC0gbW9kXzFbMV0gICAgICAgICAgIy0gW10gICAgY29yY2hldGUgc2ltcGxlCgp6el9iZXRhc18xYSA8LSBtb2RfMVtbImNvZWZmaWNpZW50cyJdXSAgICMtIGRvYmxlIGNvcmNoZXRlIAp6el9iZXRhc18xYyA8LSBtb2RfMSRjb2VmZmljaWVudHMgICAgICAgICMtICQKCnp6X2JldGFzXzJhIDwtIG1vZF8xWyJjb2VmZmljaWVudHMiXSAgICAgIy0gc2luZ2xlIGNvcmNoZXRlCmBgYAoKQWZvcnR1bmFkYW1lbnRlIHlhIGhheSBjb25zdHJ1aWRhcyBhbGd1bmFzIGZ1bmNpb25lcyBwYXJhIG1hbmlwdWxhci92ZXIgbG9zIHJlc3VsdGFkb3MgZGUgZXN0aW1hY2nDs24uIFBvciBlamVtcGxvOgoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQptb2RfMSA8LSBsbShQRVNPTjEgfiBTRU1BTkFTICsgRURBRE0gKyBTRVhPMSAsIGRhdGEgPSBkZl9tKQoKc3VtbWFyeShtb2RfMSkgICAgICAgICAgICAgICAgICAgIy0gdGFibGEgcmVzdW1lbgpzdW1tYXJ5KG1vZF8xKSRjb2VmZmljaWVudHMgICAgICAjLSB0YWJsYSByZXN1bWVuIGNvbiBsb3MgY29lZmljaWVudGVzCmNvZWZmaWNpZW50cyhtb2RfMSkgICAgICAgICAgICAgICMtIGNvZWZpY2llbnRlcyBlc3RpbWFkb3MKY29uZmludChtb2RfMSwgbGV2ZWwgPSAwLjk1KSAgICAgIy0gSW50ZXJ2YWxvcyBkZSBjb25maWFuemEgcGFyYSBsb3MgY29lZmljaWVudGVzCiNmaXR0ZWQobW9kXzEpICAgICAgICAgICAgICAgICAgICMtIHByZWRpY2Npb25lcyAoeS1zb21icmVybywgeS1oYXQpCiNyZXNpZHVhbHMobW9kXzEpICAgICAgICAgICAgICAgICMtIHZlY3RvciBkZSByZXNpZHVvcwojbW9kZWwubWF0cml4KG1vZF8xKSAgICAgICAgICAgICAjLSBleHRyYWN0IHRoZSBtb2RlbCBtYXRyaXgKYW5vdmEobW9kXzEpICAgICAgICAgICAgICAgICAgICAgIy0gQU5PVkEKdmNvdihtb2RfMSkgICAgICAgICAgICAgICAgICAgICAgIy0gbWF0cml6IGRlIHZhci1jb3YgcGFyYSBsb3MgY29lZmljaWVudGVzIAojaW5mbHVlbmNlKG1vZF8xKSAgICAgICAgICAgICAgICAjLSByZWdyZXNzaW9uIGRpYWdub3N0aWNzIAojIGRpYWdub3N0aWMgcGxvdHMKbGF5b3V0KG1hdHJpeChjKDEsIDIsIDMsIDQpLCAyLCAyKSkgICAjLSBvcHRpb25hbCA0IGdyYXBocy9wYWdlCnBsb3QobW9kXzEpICAgICAgICAgICAgICAgICAgICAgICMtCmxpYnJhcnkoZ2dmb3J0aWZ5KQphdXRvcGxvdChtb2RfMSwgd2hpY2ggPSAxOjYsIG5jb2wgPSAyLCBjb2xvdXIgPSAic3RlZWxibHVlIikKYGBgCgo8YnI+CgoKIyMjIyBNw6FzIHV0aWxpZGFkZXMKCkhheSBtdWNoYXMgbcOhcyBmdW5jaW9uZXMgcGFyYSB2YWxvcmFyIGxhIGlkb25laWRhZCBkZSB1biBtb2RlbG8uIFBvciBlamVtcGxvIFthcXXDrV0oaHR0cHM6Ly93d3cuc3RhdG1ldGhvZHMubmV0L3N0YXRzL3JkaWFnbm9zdGljcy5odG1sKS4KCjxicj4KCkVsIHBhcXVldGUgW2BHR2FsbHlgXShodHRwczovL2dnb2JpLmdpdGh1Yi5pby9nZ2FsbHkvKSBwZXJtaXRlIGhhY2VyIG11Y2hvcyBhbsOhbGlzaXMsIHBvciBlamVtcGxvOgoKLSBncsOhZmljbyBkZSBsb3MgY29lZmljaWVudGVzIGVzdGltYWRvcwoKCmBgYHtyfQpHR2FsbHk6OmdnY29lZihtb2RfMSkKYGBgCgotIHkgbXVjaGFzIG3DoXMgY29zYXMKCmBgYHtyfQpkZl9qdWdhciA8LSBkZl9tICU+JSBzZWxlY3QoRURBRE0sIFNFWE8xLCBTRU1BTkFTKQpHR2FsbHk6OmdncGFpcnMoZGZfanVnYXIpCmBgYAoKPGJyPgoKCiMjIyMgUHJlZGljY2lvbmVzCgpQYXJhIGhhY2VyICoqcHJlZGljY2lvbmVzKiogcGFyYSBvYnNlcnZhY2lvbmVzIGZ1ZXJhIGRlIGxhIG11ZXN0cmEgaGFzIGRlIHVzYXIgbGEgZnVuY2nDs24gYHByZWRpY3QoKWAuIEhhcyBkZSBwcm9wb3JjaW9uYXIgbGFzIG9ic2VydmFjaW9uZXMgYSBwcmVkZWNpciBjb21vIHVuIGRmLgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbnVldmFzX29ic2VydmFjaW9uZXMgPC0gZGZfbSAlPiUgc2xpY2UoYygzLCA0NCwgNDQ0KSkKCnByZWRpY3QobW9kXzEsIG5ld2RhdGEgPSBudWV2YXNfb2JzZXJ2YWNpb25lcykgICMtIHByZWRpY2Npb25lcyBwdW50dWFsZXMKcHJlZGljdChtb2RfMSwgbmV3ZGF0YSA9IG51ZXZhc19vYnNlcnZhY2lvbmVzLCB0eXBlID0gJ3Jlc3BvbnNlJywgc2UuZml0ID0gVFJVRSkgICMtIHRiIGVycm9yZXMgZXN0w6FuZGFyICBwcmVkaWN0aW9ucwpwcmVkaWN0KG1vZF8xLCBuZXdkYXRhID0gbnVldmFzX29ic2VydmFjaW9uZXMsIGludGVydmFsID0gImNvbmZpZGVuY2UiKSAgIy0gaW50ZXJ2YWxvIChwYXJhIGVsIHZhbG9yIGVzcGVyYWRvKQpwcmVkaWN0KG1vZF8xLCBuZXdkYXRhID0gbnVldmFzX29ic2VydmFjaW9uZXMsIGludGVydmFsID0gInByZWRpY3Rpb24iKSAgIy0gaW50ZXJ2YWxvIChwYXJhIHZhbG9yZXMgaW5kaXZpZHVhbGVzKQpgYGAKCjxicj4KCiMjIyMgRXJyb3JlcyBlc3TDoW5kYXIgcm9idXN0b3MKCkxvIGhhYml0dWFsIGVzIHF1ZSBhbCBlc3RpbWFyIHVuIG1vZGVsbywgdGVuZ2Ftb3Mgc2l0dWFjaW9uZXMgZGUgaGV0ZXJvY2VkYXN0aWNpZGFkLCBjbHVzdGVyaW5nIGV0YyAuLi4gUG9kZW1vcyBhanVzdGFyIGxvcyBlcnJvcmVzIGVzdMOhbmRhciBhIGVzdGFzIHNpdHVhY2lvbmVzLiAKCkVsIHBhcXVldGUgZGUgcmVmZXJlbmNpYSBwYXJhIGVzdG9zIHRlbWFzIHNvbMOtYSBzZXIgW2BzYW5kd2ljaGBdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9zYW5kd2ljaC9pbmRleC5odG1sKSwgYXVucXVlIHF1aXrDoXMgeWEgaGF5YSBvY3VwYWRvIHN1IGx1Z2FyIGVsIHBhcXVldGUgW2Blc3RpbWF0cmBdKGh0dHBzOi8vZGVjbGFyZWRlc2lnbi5vcmcvci9lc3RpbWF0ci8pLiAKClBvciBlamVtcGxvIHNlIHB1ZWRlbiBvYnRlbmVyIGVycm9yZXMgZXN0w6FuZGFyIHJvYnVzdG9zIGNvbiBgZXN0aW1hdHI6OmxtX3JvYnVzdCgpYF5bUG9yIGRlZmVjdG8sIGVsIHBhcXVldGUgdXNhIEVpY2tlci1IdWJlci1XaGl0ZSByb2J1c3Qgc3RhbmRhcmQgZXJyb3JzLCBoYWJpdHVhbG1lbnRlIGNvbm9jaWRvcyBjb21vIGVycm9yZXMgZXN0w6FuZGFyIOKAnEhDMuKAnS4gU2UgcHVlZGVuIGVzcGVjaWZpY2FyIG90cm9zIG3DqXRvZG9zIGNvbiBsYSBvcGNpw7NuIGBzZV90eXBlYC4gUG9yIGVqZW1wbG8gc2UgcHVlZGUgdXRpbGl6YXIgZWwgbcOpdG9kbyB1c2FkbyBwb3IgZGVmZWN0byBlbiBTdGF0YS4gW0FxdcOtXShodHRwczovL2RlY2xhcmVkZXNpZ24ub3JnL3IvZXN0aW1hdHIvYXJ0aWNsZXMvc3RhdGEtd2xzLWhhdC5odG1sKSBwdWVkZXMgZW5jb250cmFyIHBvcnF1ZSBsb3MgZXJyb3JlcyBlc3TDoW5kYXIgZGUgU3RhdGEgZGlmaWVyZW4gZGUgbG9zIHVzYWRvcyBlbiBSIHkgUGh5dG9uXS4gIAoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQojLSBpbnN0YWxsLnBhY2thZ2VzKCJlbW1lYW5zIikKbW9kXzEgPC0gbG0oUEVTT04xIH4gU0VNQU5BUywgZGF0YSA9IGRmX20pCm1vZF8xX2VlIDwtIGVzdGltYXRyOjpsbV9yb2J1c3QoUEVTT04xIH4gU0VNQU5BUywgZGF0YSA9IGRmX20pCmBgYAoKPGJyPgoKCiMjIyMgUGFxdWV0ZSBgYnJvb21gCgoKVW4gb3BjacOzbiBpbnRlcmVzYW50ZSBlcyB1dGlsaXphciBlbCBwYXF1ZXRlIFtgYnJvb21gXShodHRwczovL2Jyb29tLnRpZHl2ZXJzZS5vcmcvKS4gRXN0ZSBwYXF1ZXRlIHRpZW5lIDMgZnVuY2lvbmVzIMO6dGlsZXM6CgogIC0gYHRpZHkoKWAKICAKICAtIGBhdWdtZW50KClgCiAgCiAgLSBgZ2xhbmNlKClgCgoKYGBge3J9Cnp6IDwtIGJyb29tOjp0aWR5KG1vZF8xLCBjb25mLmludCA9IFRSVUUpCnp6ICU+JSBwanB2MjAyMC4wMTo6cGpwX2ZfZGVjaW1hbGVzKG5uID0gMikgICU+JSBndDo6Z3QoKQpgYGAKCgpgYGB7cn0KbW9kXzEgJT4lIGJyb29tOjpnbGFuY2UoKSAlPiUgc2VsZWN0KGFkai5yLnNxdWFyZWQsIHAudmFsdWUpCmJyb29tOjpnbGFuY2UobW9kXzEpCmBgYAoKCmBgYHtyfQp6eiA8LSBicm9vbTo6YXVnbWVudChtb2RfMSkKYGBgCgoKCi0gdW4gZWplbXBsbyBzZW5jaWxsbyBlbiBlbCBxdWUgc2UgdmUgbGEgdXRpbGlkYWQgZGUgYGJyb29tYAoKYGBge3J9Cm1vZF8xICU+JSBicm9vbTo6dGlkeSgpICU+JSBmaWx0ZXIocC52YWx1ZSA8IDAuMDUpCmBgYAoKCi0gVW4gZWplbXBsbyBkZSB1c28gZGUgYGJyb29tYCwgcGVybyBhbnRlcyB2YW1vcyBhIHJlY29yZGFyIGFsZ3VuYSBjb3NhIGRlIGBnZ3Bsb3QyYDoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpnZ3Bsb3QoZGF0YSA9IGRmX20sIG1hcHBpbmcgPSBhZXMoeCA9IEVEQURNLCB5ID0gUEVTT04xLCAgY29sb3IgPSBFU1RVRElPTS5mKSkgKwogICAgICBnZW9tX3BvaW50KGFscGhhID0gMC4xKSArICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQoKZ2dwbG90KGRhdGEgPSBkZl9tLCBtYXBwaW5nID0gYWVzKHggPSBFREFETSwgeSA9IFBFU09OMSwgIGNvbG9yID0gU0VYTzEpKSArCiAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjEpICsgIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpCgpnZ3Bsb3QoZGF0YSA9IGRmX20sIG1hcHBpbmcgPSBhZXMoeCA9IEVEQURNLCB5ID0gUEVTT04xLCAgY29sb3IgPSBTRVhPMSkpICsKICAgICAgZ2VvbV9wb2ludChhbHBoYSA9IDAuMSkgKyAgZ2VvbV9zbW9vdGgoKQpgYGAKCgpBaG9yYSBzw60gdmllbmUgZWwgZWplbXBsbyBlbiBxdWUgc2UgdXNhIGVsIHBhcXVldGUgYGJyb29tYAoKCmBgYHtyfQptb2RfMSA8LSBsbShQRVNPTjEgfiBFREFETSArIFNFWE8xICwgZGF0YSA9IGRmX20pCnN1bW1hcnkobW9kXzEpCgp0ZF9tb2RfMSA8LSBtb2RfMSAlPiUgYnJvb206OmF1Z21lbnQoZGF0YSA9IGRmX20pIAoKdGRfbW9kXzEgJT4lIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBFREFETSwgeSA9IFBFU09OMSwgY29sb3IgPSBTRVhPMSkpICsKICAgICAgICAgICAgICAgIGdlb21fcG9pbnQoYWxwaGEgPSAwLjEpICsKICAgICAgICAgICAgICAgIGdlb21fbGluZShhZXMoeSA9IC5maXR0ZWQsIGdyb3VwID0gU0VYTzEpKQpgYGAKCgpgYGB7cn0KdGRfbW9kXzEgJT4lIGdncGxvdChtYXBwaW5nID0gYWVzKHggPSBFREFETSwgeSA9IC5maXR0ZWQsICBjb2xvciA9IFNFWE8xKSkgKwogICAgICAgICAgICAgICAgZ2VvbV9saW5lKGFlcyhncm91cCA9IFNFWE8xKSkgCmBgYAoKKCEhISEpIFBvciBlamVtcGxvIHRhbWJpw6luIHBlcm1pdGUgZsOhY2lsbWVudGUgZXN0aW1hciBtb2RlbG9zIHBvciBncnVwb3M6CiAKYGBge3J9CmxpYnJhcnkoYnJvb20pCmRmX20gJT4lIGdyb3VwX2J5KFNFWE8xKSAlPiUgZG8odGlkeShsbShQRVNPTjEgfiBTRU1BTkFTLCAuKSkpCmBgYAogCgpPIGhhY2VyIGdyw6FmaWNvcyBkZSBsb3MgaW50ZXJ2YWxvcyBkZSBsb3MgY29lZmljaWVudGVzOgoKYGBge3J9CnRkX21vZF8xIDwtIHRpZHkobW9kXzEsIGNvbmYuaW50ID0gVFJVRSkKZ2dwbG90KHRkX21vZF8xLCBhZXMoZXN0aW1hdGUsIHRlcm0sIGNvbG9yID0gdGVybSkpICsKICAgIGdlb21fcG9pbnQoKSArCiAgICBnZW9tX2Vycm9yYmFyaChhZXMoeG1pbiA9IGNvbmYubG93LCB4bWF4ID0gY29uZi5oaWdoKSkgKwogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMCkKYGBgCiAKIG8gZXN0ZToKIAogCmBgYHtyfQptb2RfMSAlPiUgYXVnbWVudChkYXRhID0gZGZfbSkgJT4lCiBnZ3Bsb3QobWFwcGluZyA9IGFlcyh4ID0gU0VNQU5BUyAsIHkgPSAuZml0dGVkLCBjb2xvciA9IFNFWE8xKSkgKwogICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeSA9IFBFU09OMSksIGFscGhhID0gMC4xKSArCiAgIGdlb21fbGluZSgpCmBgYAoKIG8gZXN0ZToKIApgYGB7cn0KbW9kXzEgJT4lIGF1Z21lbnQoZGF0YSA9IGRmX20pICU+JQogZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IFNFTUFOQVMgLCB5ID0gLmZpdHRlZCwgY29sb3IgPSBTRVhPMSkpICsKICAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHkgPSBQRVNPTjEpLCBhbHBoYSA9IDAuMSkgKwogICBnZW9tX2xpbmUoKSArCiAgZmFjZXRfd3JhcCh2YXJzKEVTVFVESU9NLmYpKQpgYGAKIAogCiA8YnI+CgogCiAKIyMjIENvbXBhcmFjacOzbiBkZSBtb2RlbG9zCgpgYGB7cn0KbW9kXzEgPC0gbG0oUEVTT04xIH4gU0VNQU5BUyArIEVEQURNLCAgZGF0YSA9IGRmX20pCm1vZF8yIDwtIGxtKFBFU09OMSB+IFNFTUFOQVMgKyBFREFETSArIEkoU0VNQU5BUypFREFETSksIGRhdGEgPSBkZl9tKQoKYW5vdmEobW9kXzEsIG1vZF8yKQpgYGAKCgpgYGB7cn0KQUlDKG1vZF8xLCBtb2RfMikKYGBgCgoKYGBge3J9CmxtdGVzdDo6bHJ0ZXN0KG1vZF8xLCBtb2RfMikgICAgCmBgYAoKKCEhISkgIFZhbW9zIGEgb3JkZW5hciBsb3MgbW9kZWxvcyBlbiBmdW5jacOzbiBkZSBzdSBBSUMuIFBhcmEgZWxsbyB2YW1vcyBhIGNyZWFyIHVuYSBsaXN0YSBjb24gbW9kZWxvcwoKYGBge3J9Cm1vZGVsb3MgPC0gbGlzdChtb2RfMSA8LSBsbShQRVNPTjEgfiBTRU1BTkFTICsgRURBRE0sICBkYXRhID0gZGZfbSksCiAgICAgICAgICAgICAgICBtb2RfMiA8LSBsbShQRVNPTjEgfiBTRU1BTkFTICsgRURBRE0gKyBJKFNFTUFOQVMqRURBRE0pLCBkYXRhID0gZGZfbSkgICkKCm1vZGVsb3Nfb3JkZXJlZF9BSUMgPC0gcHVycnI6Om1hcF9kZihtb2RlbG9zLCBicm9vbTo6Z2xhbmNlLCAuaWQgPSAibW9kZWwiKSAlPiUgYXJyYW5nZShBSUMpCgptb2RlbG9zX29yZGVyZWRfQUlDICU+JSBndDo6Z3QoKQpgYGAKCi0gdW5hIHRhYmxhIGNvbiBgYnJvb21gIChhbnRlcyBjcmVhbW9zIHVuYSBmdW5jacOzbiAoISEhISkpOgoKCmBgYHtyfQpteV9rYWJsZSA8LSBmdW5jdGlvbihkZil7IGd0OjpndChtdXRhdGVfaWYoZGYsIGlzLm51bWVyaWMsIHJvdW5kLCAyKSkgfQoKdGlkeShtb2RfMSkgJT4lIG15X2thYmxlCmBgYAoKCjxicj4KCgojIyMgRWwgcGFxdWV0ZSBgbW9kZWxyYAoKQ29uIGVsIHBhcXVldGUgW2Btb2RlbHJgXShodHRwczovL21vZGVsci50aWR5dmVyc2Uub3JnL2luZGV4Lmh0bWwpIHBvZGVtb3MgKmbDoWNpbG1lbnRlKiBjb21wYXJhciBsYXMgcHJlZGljY2lvbmVzIGRlIHZhcmlvcyBtb2RlbG9zOiAKCgpgYGB7cn0KenogPC0gZGZfbSAlPiUgbW9kZWxyOjpnYXRoZXJfcHJlZGljdGlvbnMobW9kXzEsIG1vZF8yKQpgYGAKClBhcmEgdmVyIG1lam9yIGxhcyBwcmVkaWNjaW9uZXMgZGUgbG9zIDIgbW9kZWxvcyBoYWJyw6EgcXVlIHBhc2FyIHp6IGEgZm9ybWF0byBhbmNobzoKCmBgYHtyfQp6ejEgPC0gcGl2b3Rfd2lkZXIoenosIG5hbWVzX2Zyb20gPSBtb2RlbCwgdmFsdWVzX2Zyb20gPSBwcmVkKQpgYGAKCi0tLS0tLS0tLS0tLS0KCjxicj4KCgojIyA2LjIgTW9kZWxvcyBHTE0KClBvciBlamVtcGxvIHVuIExvZ2l0OgoKCmBgYHtyfQptb2RfbG9naXQgPC0gZ2xtKENFU0FSRUEuZiB+IFBFU09OMSArIFNFTUFOQVMgKyBFREFETSArIEVTVFVESU9NLmYsIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSAibG9naXQiKSwgZGF0YSA9IGRmX20pCgpzdW1tYXJ5KG1vZF9sb2dpdCkKYGBgCgoKPGJyPgoKRW4gbG9zIG1vZGVsb3MgbGluZWFsZXMsIGNhbGN1bGFyIGVmZWN0b3MgbWFyZ2luYWxlcyBlcyBzZW5jaWxsbywgcGVybyBlbCBMb2dpdCBlcyB1biBtb2RlbG8gbm8gbGluZWFsLiBQYXJhIGNhbGN1bGFyIGVmZWN0b3MgbWFyZ2luYWxlcyBlbiBtb2RlbG9zIG5vIGxpbmVhbGVzIHBvZGVtb3MgdXNhciBlbCBwYXF1ZXRlIFtgbWFyZ2luc2BdKGh0dHBzOi8vZ2l0aHViLmNvbS9sZWVwZXIvbWFyZ2lucykuIFBvciBlamVtcGxvLCBjYWxjdWxlbW9zIGVsIEVmZWN0byBtYXJnaW5hbCBtZWRpbyBvIGF2ZXJhZ2UgbWFyZ2luYWwgZWZmZWN0IChBTUUpIGVuIGBtb2RfbG9naXRgLgoKCmBgYHtyfQptb2RfMV9BTUUgPC0gbW9kX2xvZ2l0ICU+JSBtYXJnaW5zOjptYXJnaW5zKCkgJT4lIHN1bW1hcnkoKSAKbW9kXzFfQU1FCmBgYAoKU2kgcXVlcmVtb3MgY2FsY3VsYXIgZWZlY3RvcyBtYXJnaW5hbGVzIHBhcmEgdW5vcyB2YWxvcmVzIGNvbmNyZXRvcyBkZSBsb3MgcmVncmVzb3JlcwoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbW9kX2xvZ2l0ICU+JSBtYXJnaW5zOjptYXJnaW5zKGF0ID0gbGlzdChTRU1BTkFTID0gYygyNSwgMzUpLCBFU1RVRElPTS5mID0gYygiUHJpbWFyaW9zIiwgIk1lZGlvcyIsICJVbml2ZXJzaWRhZCIpKSwgIHZhcmlhYmxlcyA9ICJFREFETSIgKSAKYGBgCgoKU2kgcHJlZmllcmVzIHZpc3VhbGl6YXJsbywgdXRpbGl6YSBgbWFyZ2luczo6Y3Bsb3QoKWA6CgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KbWFyZ2luczo6Y3Bsb3QobW9kX2xvZ2l0LCB4ID0gIkVTVFVESU9NLmYiLCBkeCA9ICJFREFETSIsIHdoYXQgPSAiZWZmZWN0IiwgZHJvcCA9IFRSVUUpCm1hcmdpbnM6OmNwbG90KG1vZF9sb2dpdCwgeCA9ICJFU1RVRElPTS5mIiwgZHggPSAiRURBRE0iKQpgYGAKCiMjIyBFbCBwa2cgYGdnZWZmZWN0c2AKCkVsIHBhcXVldGUgW2BnZ2VmZmVjdHNgXShodHRwczovL3N0cmVuZ2VqYWNrZS5naXRodWIuaW8vZ2dlZmZlY3RzLykgcHJvcG9yY2lvbmEgb3RyYSBmb3JtYSBkZSBjYWxjdWxhciBlZmVjdG9zIG1hcmdpbmFsZXMgZW4gbW9kZWxvcyBkZSByZWdyZXNpw7NuOgoKPiBSZXN1bHRzIG9mIHJlZ3Jlc3Npb24gbW9kZWxzIGFyZSB0eXBpY2FsbHkgcHJlc2VudGVkIGFzIHRhYmxlcyB0aGF0IGFyZSBlYXN5IHRvIHVuZGVyc3RhbmQuIEZvciBtb3JlIGNvbXBsZXggbW9kZWxzIHRoYXQgaW5jbHVkZSBpbnRlcmFjdGlvbiBvciBxdWFkcmF0aWMgLyBzcGxpbmUgdGVybXMsIHRhYmxlcyB3aXRoIG51bWJlcnMgYXJlIGxlc3MgaGVscGZ1bCBhbmQgZGlmZmljdWx0IHRvIGludGVycHJldC4gSW4gc3VjaCBjYXNlcywgbWFyZ2luYWwgZWZmZWN0cyBhcmUgZmFyIGVhc2llciB0byB1bmRlcnN0YW5kLiBJbiBwYXJ0aWN1bGFyLCB0aGUgdmlzdWFsaXphdGlvbiBvZiBtYXJnaW5hbCBlZmZlY3RzIGFsbG93cyB0byBpbnR1aXRpdmVseSBnZXQgdGhlIGlkZWEgb2YgaG93IHByZWRpY3RvcnMgYW5kIG91dGNvbWUgYXJlIGFzc29jaWF0ZWQsIGV2ZW4gZm9yIGNvbXBsZXggbW9kZWxzLgoKCk5vIGxvIGhlIHByb2JhZG8gYcO6biwgcGVybyBzZSBwdWVkZSB1c2FyIGVuIHVuYSBncmFuIHZhcmllZGFkIGRlIG1vZGVsb3MuCkVzdGltYXRlZCBNYXJnaW5hbCBNZWFucyBhbmQgTWFyZ2luYWwgRWZmZWN0cyBmcm9tIFJlZ3Jlc3Npb24gTW9kZWxzCgoKCjxicj4KCiMjIDYuMyBUYWJsYXMgcGFyYSBtb2RlbG9zCgoKCiMjIyBDb24gYHNqUExvdGAgeSBmcmllbmRzIC4uLgoKCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQptMSA8LSBsbShQRVNPTjEgfiBTRU1BTkFTICsgU0VYTzEgKyBFREFETSArIEVEQURQLjIsIGRhdGEgPSBkZikKbTIgPC0gbG0oUEVTT04xIH4gU0VNQU5BUyArIFNFWE8xICsgRURBRE0gKyBFREFEUC4yICsgRVNUVURJT00uZiArIEVTVFVESU9QLmYsIGRhdGEgPSBkZikKc2pQbG90Ojp0YWJfbW9kZWwobTEpCnNqUGxvdDo6cGxvdF9tb2RlbChtMSwgc29ydC5lc3QgPSBUUlVFKQpzalBsb3Q6OnRhYl9tb2RlbChtMSwgbTIpCmBgYAoKCjxicj4KCiMjIyBDb24gYHN0YXJnYXplcmAKCmBgYHtyLCByZXN1bHRzID0gImFzaXMifQptb2RfMSA8LSBsbShQRVNPTjEgfiBTRU1BTkFTICsgU0VYTzEgLCBkYXRhID0gZGZfbSkKc3RhcmdhemVyOjpzdGFyZ2F6ZXIobW9kXzEsIHR5cGUgPSAiaHRtbCIpCmBgYAoKCmBgYHtyLCBldmFsID0gRkFMU0UsIGVjaG8gPSBGQUxTRX0KbW9kXzEgPC0gbG0oUEVTT04xIH4gU0VNQU5BUyArIFNFWE8xICwgZGF0YSA9IGRmX20pCnN0YXJnYXplcjo6c3RhcmdhemVyKG1vZF8xLCB0eXBlID0gInRleHQiKQpgYGAKCjxicj4KCgojIyMgQ29uIGBtb2RlbHN1bW1hcnlgCgoKYGBge3J9CiNyZW1vdGVzOjppbnN0YWxsX2dpdGh1YigndmluY2VudGFyZWxidW5kb2NrL21vZGVsc3VtbWFyeScpCmxpYnJhcnkobW9kZWxzdW1tYXJ5KQoKbXlzX21vZGVsaXRvcyA8LSBsaXN0KCkKbXlzX21vZGVsaXRvc1tbIlBFU09OMTogIE9MUyAxIl1dICAgIDwtICBsbShQRVNPTjEgICAgfiBTRU1BTkFTICsgU0VYTzEsICAgICAgICAgICAgZGZfbSkKbXlzX21vZGVsaXRvc1tbIlBFU09OMTogIE9MUyAyIl1dICAgIDwtICBsbShQRVNPTjEgICAgfiBTRU1BTkFTICsgU0VYTzEgKyBFREFETSAsIGRmX20pCm15c19tb2RlbGl0b3NbWyJDRVNBUkVBOiBMb2dpdCAxIl1dICA8LSBnbG0oQ0VTQVJFQS5mIH4gU0VNQU5BUyArIFNFWE8xICwgZGF0YSA9IGRmX20sIGZhbWlseSA9IGJpbm9taWFsKGxpbmsgPSAibG9naXQiKSkKCm1tIDwtIG1zdW1tYXJ5KG15c19tb2RlbGl0b3MsIHRpdGxlID0gIlJlc3VsdGFkb3MgZGUgZXN0aW1hY2nDs24iKQptbQpgYGAKCjxicj4KCiMjIyBDb24gW2ByZXBvcnRzYF0oaHR0cHM6Ly9naXRodWIuY29tL3RyaW5rZXIvcmVwb3J0cykKCi0gaW5mb3JtZXMgY29uIGVsIHBhcXVldGUgYHJlcG9ydHNgCgpgYGB7ciwgcmVzdWx0cyA9ICJhc2lzIiwgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KHJlcG9ydCkgIy0gZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJuZXVyb3BzeWNob2xvZ3kvcmVwb3J0IikKbXlfbW9kZWwgPC0gbG0oUEVTT04xIH4gU0VNQU5BUyArIFNFWE8xLCBkZl9tKQpyciA8LSByZXBvcnQobXlfbW9kZWwsIHRhcmdldCA9IDEpCnJyCmBgYAoKPGJyPgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KcmVwb3J0Ojphcy5yZXBvcnQocnIpCmBgYAoKPGJyPgoKUmVjdWVyZGEgdGFtYmnDqW4gcXVlIHNlIHB1ZWRlIG9idGVuZXIgbGEgZWN1YWNpw7NuIChlbiBsYXRleCkgZGUgdW4gbW9kZWxvIGNvbjoKIAogCmBgYHtyLCBldmFsID0gRkFMU0V9CmxpYnJhcnkoZXF1YXRpb21hdGljKSAgIy0gcmVtb3Rlczo6aW5zdGFsbF9naXRodWIoImRhdGFsb3JheC9lcXVhdGlvbWF0aWMiKQpleHRyYWN0X2VxKG1vZF8xKQpleHRyYWN0X2VxKG1vZF8xLCB1c2VfY29lZnMgPSBUUlVFKQpgYGAKCjxicj4KCgojIyA2LjQgT3Ryb3MgbW9kZWxvcy90w6ljbmljYXMKCkVuIHJlYWxpZGFkIHPDs2xvIHZveSBhIGluc2lzdGlyIGVuIHF1ZSBjb24gUiBzZSBwdWVkZW4gaW1wbGVtZW50YXIgdW5hIGdyYW4gdmFyaWVkYWQgZGUgbW9kZWxvcyB5IHTDqWNuaWNhcyBlc3RhZMOtc3RpY2FzLiBQdWVkZXMgdmVyIGFsZ3Vub3MgZWplbXBsb3MgZW4gW2VzdGUgbGlicm9dKGh0dHBzOi8vbS1jbGFyay5naXRodWIuaW8vUi1tb2RlbHMvI2ludHJvZHVjdGlvbikuCkhheSBtYXRlcmlhbGVzIGV4Y2VsZW50ZXMsIHRhbnRvIGxpYnJvcywgY29tbyB0dXRvcmlhbGVzLCBwb3N0cywgZXRjIC4uLiAgcXVlIHB1ZWRlcyBlbmNvbnRyYXIgZsOhY2lsbWVudGUgZW4gaW50ZXJuZXQuCgoKPGJyPgoKPGJsb2NrcXVvdGUgY2xhc3M9InR3aXR0ZXItdHdlZXQiIGRhdGEtZG50PSJ0cnVlIiBkYXRhLXRoZW1lPSJkYXJrIj48cCBsYW5nPSJlbiIgZGlyPSJsdHIiPk15IDxhIGhyZWY9Imh0dHBzOi8vdHdpdHRlci5jb20vaGFzaHRhZy9yc3RhdHM/c3JjPWhhc2gmYW1wO3JlZl9zcmM9dHdzcmMlNUV0ZnciPiNyc3RhdHM8L2E+IGxlYXJuaW5nIHBhdGg6PGJyPjxicj4xLiBJbnN0YWxsIFI8YnI+Mi4gSW5zdGFsbCBSU3R1ZGlvPGJyPjMuIEdvb2dsZSAmcXVvdDtIb3cgZG8gSSBbVEhJTkcgSSBXQU5UIFRPIERPXSBpbiBSPyZxdW90Ozxicj48YnI+UmVwZWF0IHN0ZXAgMyBhZCBpbmZpbml0dW0uPC9wPiZtZGFzaDsgSmVzc2UgTW9zdGlwYWsgKEBraWVyaXNpKSA8YSBocmVmPSJodHRwczovL3R3aXR0ZXIuY29tL2tpZXJpc2kvc3RhdHVzLzg5ODUzNDc0MDA1MTA2Mjc4NT9yZWZfc3JjPXR3c3JjJTVFdGZ3Ij5BdWd1c3QgMTgsIDIwMTc8L2E+PC9ibG9ja3F1b3RlPiA8c2NyaXB0IGFzeW5jIHNyYz0iaHR0cHM6Ly9wbGF0Zm9ybS50d2l0dGVyLmNvbS93aWRnZXRzLmpzIiBjaGFyc2V0PSJ1dGYtOCI+PC9zY3JpcHQ+IAoKPGJyPgoKIAo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCjxicj4KCiMjIDYuNSBNYWNoaW5lIExlYXJuaW5nCgpTaW1wbGVtZW50ZSBzZcOxYWxhciBxdWUgZXMgdW4gw6FyZWEgZGUgZXN0dWRpbyBlbm9ybWUgeSBlbiBjb25zdGFudGUgZXZvbHVjacOzbi4gRW4gUiBoYXkgdmFyaW9zIGVudG9ybm9zL2VuZm9xdWVzIHBhcmEgaGFjZXIgTUwuIENyZW8gcXVlIGVsIHF1ZSBtw6FzIGZ1dHVybyB0aWVuZSBlcyBbYHRpZHltb2RlbHNgXShodHRwczovL3d3dy50aWR5bW9kZWxzLm9yZy8pLiBVbiBbcG9zdCBpbnRyb2R1Y3RvcmlvXShodHRwczovL21lZ2hhbi5yYmluZC5pby9wb3N0L3RpZHltb2RlbHMtaW50cm8vKSBzb2JyZSB0aWR5bW9kZWxzLiBbVW4gZWplbXBsb10oaHR0cHM6Ly93d3cudGlkeW1vZGVscy5vcmcvc3RhcnQvY2FzZS1zdHVkeS8pIGRlIHVzbyBkZSB0aWR5bW9kZWxzLiBbT3RybyBlamVtcGxvXShodHRwczovL3Njb3RpbmFzdGF0cy5yYmluZC5pby8yMDIwLzA3LzMwL2hvdGVsLWJvb2tpbmdzLXRpZHltb2RlbHMvKS4KClNpbXBsZW1lbnRlIGFsZ3VuYXMgcmVmZXJlbmNpYXM6CgotIFtNYWNoaW5lIExlYXJuaW5nIGZvciBFdmVyeW9uZV0oaHR0cHM6Ly92YXMzay5jb20vYmxvZy9tYWNoaW5lX2xlYXJuaW5nLykuIFVuIHBvc3QgaW50cm9kdWN0b3Jpby4KCi0gW0hhbmRzLU9uIE1hY2hpbmUgTGVhcm5pbmcgd2l0aCBSXShodHRwczovL2JyYWRsZXlib2VobWtlLmdpdGh1Yi5pby9IT01ML0RULmh0bWwpLiBVbiBidWVuIGJvb2tkb3duCgotIFsxMDEgTWFjaGluZSBMZWFybmluZyBBbGdvcml0aG1zIGZvciBEYXRhIFNjaWVuY2Ugd2l0aCBDaGVhdCBTaGVldHNdKGh0dHBzOi8vYmxvZy5kYXRhc2NpZW5jZWRvam8uY29tL21hY2hpbmUtbGVhcm5pbmctYWxnb3JpdGhtcy8pLiBDaGVhdHNoZWV0IGRlIGFsZ29yaXRtb3MgTUwuCgotIFsxMDEgRGF0YSBTY2llbmNlIEludGVydmlldyBRdWVzdGlvbnMsIEFuc3dlcnMsIGFuZCBLZXkgQ29uY2VwdHNdKGh0dHBzOi8vYmxvZy5kYXRhc2NpZW5jZWRvam8uY29tL2RhdGEtc2NpZW5jZS1pbnRlcnZpZXctcXVlc3Rpb25zLykuIFBvc3QgY29uIGNvc2FzIHF1ZSAqKiJkZWJlcsOtYXMiKiogc2FiZXIgc2kgcXVpZXJlcyB1biB0cmFiYWpvIGNvbW8gRGF0YSBTY2llbnRpc3QuCgotIFtNYWNoaW5lIGxlYXJuaW5nIGVzc2VudGlhbHNdKGh0dHBzOi8vYmlvZGF0YXNjaWVuY2UuZ2l0aHViLmlvL3N0YXRjb21wL21sL2Vzc2VudGlhbHMuaHRtbCkuIFVuIHRlbWEgZGUgdW4gY3Vyc28gc29icmUgTUwuCgotIFtJbnRyb2R1Y3Rpb24gdG8gTWFjaGluZSBMZWFybmluZyB3aXRoIFJdKGh0dHA6Ly93d3cubXBpYS5kZS9ob21lcy9kZ291bGllci9NTENsYXNzZXMvQ291cnNlJTIwLSUyMEludHJvZHVjdGlvbiUyMHRvJTIwTWFjaGluZSUyMExlYXJuaW5nJTIwZm9yJTIwU2NpZW50aXN0cyUyMHdpdGglMjBSLmh0bWwjY2hhcHRlcl8yOl9wZXJmb3JtYW5jZV9tZWFzdXJlcykuIE90cm8gY3Vyc28gc29icmUgTUwuCgotIFtNYWNoaW5lIExlYXJuaW5nXShodHRwczovL20tY2xhcmsuZ2l0aHViLmlvL2ludHJvZHVjdGlvbi10by1tYWNoaW5lLWxlYXJuaW5nL2NvbmNlcHRzLmh0bWwpLiBCb29rZG93biBjZW50cmFkbyBlbiBleHBsaWNhciBsYXMgcHJpbmNpcGFsZXMgaWRlYXMvY29uY2VwdG9zIGRlIE1MLgoKCi0gW01hY2hpbmUgTGVhcm5pbmcgY29uIFIgeSBjYXJldF0oaHR0cHM6Ly93d3cuY2llbmNpYWRlZGF0b3MubmV0L2RvY3VtZW50b3MvNDFfbWFjaGluZV9sZWFybmluZ19jb25fcl95X2NhcmV0KXBvciBKb2FxdcOtbiBBbWF0IFJvZHJpZ28uIEEgcGVzYXIgZGUgcXVlIGVsIHBhcXVldGUgYGNhcmV0YCBoYSBzaWRvIHN1c3RpdHVpZG8gcG9yIGB0aWR5bW9kZWxzYCwgY29udGludWEgc2llbmRvIHVuYSBidWVuYSBlbnRyYWRhIGEgTUwgY29uIFIgeSBlbiBjYXN0ZWxsYW5vLCBwb3IgSm9hcXXDrW4gQW1hdCBSb2RyaWdvLiAgCgotIFtBIEdlbnRsZSBJbnRyb2R1Y3Rpb24gdG8gdGlkeW1vZGVsc10oaHR0cHM6Ly9ydmlld3MucnN0dWRpby5jb20vMjAxOS8wNi8xOS9hLWdlbnRsZS1pbnRyby10by10aWR5bW9kZWxzLykuIAoKCi0gW0Nob29zaW5nIHRoZSByaWdodCBlc3RpbWF0b3JdKGh0dHBzOi8vc2Npa2l0LWxlYXJuLm9yZy9zdGFibGUvdHV0b3JpYWwvbWFjaGluZV9sZWFybmluZ19tYXAvaW5kZXguaHRtbCkuIEd1w61hICBkZSBgc2NpdGl0IGxlYXJuYCBwYXJhIHNlbGVjY2nDs24gZGUgbW9kZWxvLgoKLSBbVW5hIGludHJvZHVjY2nDs24gdmlzdWFsIGFsIG1hY2hpbmUgbGVhcm5pbmcgLSBJXShodHRwOi8vd3d3LnIyZDMudXMvdW5hLWludHJvZHVjY2lvbi12aXN1YWwtYWwtbWFjaGluZS1sZWFybmluZy0xLykuIFBvc3QgaW50cm9kdWN0b3JpbyBwZXJvIG11eSBib25pdG8gdmlzdWFsbWVudGUgc29icmUgTUwuIFtBcXXDrV0oaHR0cDovL3d3dy5yMmQzLnVzL3Zpc3VhbC1pbnRyby10by1tYWNoaW5lLWxlYXJuaW5nLXBhcnQtMi8pIGxhIHNlZ3VuZGEgcGFydGUuCgoKLSBbUjogTUxSLCBEZWNpc2lvbiBUcmVlcyBhbmQgUmFuZG9tIEZvcmVzdCB0byBQcmVkaWN0IE1QRyBmb3IgMjAxOSBWZWhpY2xlc10oaHR0cHM6Ly9ibG9nLmFscGhhLWFuYWx5c2lzLmNvbS8yMDE5LzA2L3ByZWRpY3RpbmctbXBnLWZvci0yMDE5LXZlaGljbGVzLXVzaW5nLXIuaHRtbCkuIERlc3B1w6lzIGxvIGltcGxlbWVudGFuIGVuIFtUZW5zb3JGbG93XShodHRwczovL2Jsb2cuYWxwaGEtYW5hbHlzaXMuY29tLzIwMTkvMDgvci10ZW5zb3JmbG93LW11bHRpcGxlLWxpbmVhci1yZWdyZXNzaW9uLmh0bWwpCgo8YnI+PGJyPjxicj4KCgoK