1. ¿Por qué este Notebook?

Este es un R Markdown Notebook que ilustra estadísticas agrícolas para el departamento del Valle del Cauca en Colombia. El presente cuaderno de R ayuda a los estudiantes de la Universidad Nacional de Colombia que se encuentran cursando el curso de Geomática Básica a entender conceptos básicos de geomática aplicables a la agronomía.

2. Funcionalidades de GIS.

La exploración de estadísticas no espaciales es esencial para comprender que ocurre en los territorios. Muchas librerias de R, particularmente tidyverse y dplyr son muy útiles para explorar y resumir estadísticas. Por otro lado, las operaciones geoespaciales pueden mejorar nuestro entendimiento acerca de múltiples problemas que afectan las regoiones geográficas. Un buen ejemplo es cuando se desea averiguar la ubicación de ciertos municipios que tienen rendimientos de cosecha óptimos. Para realizar esta exploración se necesita unir datos no espaciales con datos espaciales. Esta labor se realizó anteriormente en QGIS, ahora se utilizará R para llevarla acabo. Adicionalmente, se puede explorar uniones espaciales. Estas opciones se basan en la interaccion de dos objetos espaciales, a menudo polígonos y puntos. Hay muchas formas de unir objetos, las cuales pueden incluir opciones específicas como: cerca, dentro, toques, etc. Comencemos removiendo el contenido de la memoria:

rm(list=ls())

Ahora, instalemos las librerias necesarias. En el siguiente chunk, se indica que solo sean instalados los paquetes que no han sido previamente instalados.

list.of.packages <- c("here", "tidyverse", "rgeos", "maptools", "raster", "sf",  "viridis", "rnaturalearth", "GSODR", "ggrepel", "cowplot")
new.packages <- list.of.packages[!(list.of.packages %in% installed.packages()[,"Package"])]
if(length(new.packages)) install.packages(new.packages)

Ahora, carguemos las librerias.

library(here)
library(tidyverse)
library(rgeos)
library(maptools)
library(raster)
library(sf)
library(viridis)
library(rnaturalearth)
library(GSODR)
library(ggrepel)
library(cowplot)

3. Exploración de estadísticas agrícolas en el Valle del Cauca

Previamente he descargado datos estadísticos en formato csv de la página oficial del Ministerio de Agricultura y Desarrollo Rural en mi computador. Después, en Excel, eliminé información no correspondiente al departamento del Valle del Cacua. Guardé el archivo unicamente con información correspondiente a mi departamento en un documento llamado EVA_Valle2.csv, luego lo cargué dentro de una carpeta llamada agro en RStudio Cloud. Leamos el archivo csv con “Estadísticas municipales agropecuarias” para el Valle del Cauca

datos <- read_csv("./agro/EVA_Valle2.csv")
Parsed with column specification:
cols(
  COD_DEP = col_double(),
  DEPARTAMENTO = col_character(),
  COD_MUN = col_double(),
  MUNICIPIO = col_character(),
  GRUPO = col_character(),
  SUBGRUPO = col_character(),
  CULTIVO = col_character(),
  YEAR = col_double(),
  Area_Siembra = col_double(),
  Area_Cosecha = col_double(),
  Produccion = col_double(),
  Rendimiento = col_double(),
  ESTADO = col_character(),
  CICLO = col_character()
)

Chequeemos cuales son los atributos de datos: tanto al principio (head) como al final (tail)

head(datos)
tail(datos)

Se recalca que cada municipio cuenta con estadísticas acerca del área sembrada, área cosechada y rendimiento de los cultivos que allí se producen en diferentes años. En la tabla no hay unidades, sin embargo, si se verifica el archivio csv original se encuentra que las unidades para el área son las hectáreas y para el rendimiento son Ton/ha. Usaremos la libreria (dplyr) para explorar los contenidos de los datos del objeto. Para esto, primero obtenemos un sumario de rendimiento por el grupo y municipio:

datos %>%
  group_by(MUNICIPIO, GRUPO) %>%
  summarise(rend_prom = mean(Rendimiento, na.rm = TRUE)) -> rend_resumen
### Let's visualize only the first six records
head(rend_resumen)

De igual forma, podemos calcular el rendimiento promedio por grupo en los municipios del Valle del Cauca:

datos %>%
  group_by(GRUPO) %>%
  summarise(rend_dep = mean(Rendimiento, na.rm = TRUE)) -> rend_valledelcauca

rend_valledelcauca

Notemos que los rendimientos más altos corresponden a CEREALES, FIBRAS Y FLORES Y FORRAJES. Ahora veamos cuales son los municipios con mayor rendimiento para cada grupo de cultivos en el año 2018:

datos %>% 
  filter(YEAR==2018) %>% 
  group_by(GRUPO, MUNICIPIO) %>%
  summarize(max_rend = max(Rendimiento, na.rm = TRUE)) %>%
    slice(which.max(max_rend)) -> rend_max_18

rend_max_18

En seguida miraremos cuales son los municipios con mayor área cosechada para cada grupo de cultivos en 2018:

datos %>% 
  filter(YEAR==2018) %>% 
  group_by(GRUPO, MUNICIPIO) %>%
  summarize(max_area_cosecha = max(Area_Cosecha, na.rm = TRUE)) %>%
    slice(which.max(max_area_cosecha)) -> area_cosecha_max

area_cosecha_max

Notemos que el áremas más grande cosechada en el año 2018 para Jamundí corresponde a Cereales, pues esta zona, ubicada en el sur del departamento, se siembran grandes extensiones de arroz. Seleccionemos la producción de arroz (toneladas) en Jamundí para cada año:

datos %>% 
  filter(MUNICIPIO=="JAMUNDI" & SUBGRUPO=="ARROZ") %>% 
  group_by(YEAR, CULTIVO) ->  jamundi_arroz

jamundi_arroz

Haremos una exploración gráfica básica:

g <- ggplot(aes(x=YEAR, y=Produccion/1000), data = jamundi_arroz) + geom_bar(stat='identity') + labs(y='Producción de arroz [Ton x 1000]')
g + ggtitle("Evolución de la produccion de arroz en Jamundi desde 2007 al 2018") + labs(caption= "Basado en datos del DANE, 2018")

Ahora miraremos que cultivos tuvieron la mayor área cosechada en el año 2018:

datos %>% 
  filter(YEAR==2018) %>% 
  group_by(GRUPO) %>%
  summarize(sum_area_cosecha = sum(Area_Cosecha, na.rm = TRUE)) %>%
     arrange(desc(sum_area_cosecha)) -> total_area_cosecha

total_area_cosecha

Observamos que otros cultivos permanentes corresponden a la mayor área cosechada en el 2018 en el Valle del Cauca. Para observar que cultivos pertenecen a este grupoo, puede buscar información en la página oficial del DANE, entidad responsable de la planeación, levantamiento, procesamiento, análisis y difusión de las estadísticas oficiales de Colombia. De igual forma, podemos buscar esta información en los mismos datos.

datos %>%
  filter(GRUPO=="OTROS PERMANENTES" & YEAR==2018) %>%
  group_by(CULTIVO) %>%
  summarize(sum_cosecha = sum(Area_Cosecha, na.rm = TRUE)) %>%
     arrange(desc(sum_cosecha)) -> total_cosecha


total_cosecha

Resaltamos que la caña de azucar es de total importancia en la agricultura vallecaucana debido a la gran cantidad de ingenios azucareros que se encuentran en esta región. A continuación veamos cuales son los municipios con mayor área cosechada para cada cultivo permanente en 2018:

datos %>% 
  filter(YEAR==2018 & GRUPO=="OTROS PERMANENTES") %>% 
  group_by(CULTIVO, MUNICIPIO) %>%
  summarize(max_area2 = max(Area_Cosecha, na.rm = TRUE)) %>%
    slice(which.max(max_area2)) -> area_cosecha2

area_cosecha2

Regresemos a los datos de cultivos permanentes. Antes de plotear, necesitamos agregar al atributo total_area_cosecha, un nuevo campo con abreviaturas para cada GRUPO de cultivos para que el plot no luzca desordenado.

total_area_cosecha$CROP <- abbreviate(total_area_cosecha$GRUPO, 3)

Ahora es tiempo de plotear:

g <- ggplot(aes(x=CROP, y=sum_area_cosecha), data = total_area_cosecha) + geom_bar(stat='identity') + labs(y='Area Total Cosechada [Ha]')
g+ ggtitle("Area total cosechada por grupos de cultivos en el 2018 para el Valle") + theme(plot.title = element_text(hjust = 0.5)) +
   labs(caption= "Basado en datos del DANE, 2018")

Se puede obtener más información correspondiente al departamento, especificamente de diferentes cultivos que son importantes principalemente para la economía de este.

4. Uniendo estadísticas agrícolas a municipios

Previamente he subido a RStudio Cloud los datos administrativos del Valle del Cauca. Empecemos leyendo los datos usando la libreria sf

ant_munic <- sf::st_read("./valledelcauca/ADMINISTRATIVO/MGN_MPIO_POLITICO.shp")
Reading layer `MGN_MPIO_POLITICO' from data source `/cloud/project/valledelcauca/ADMINISTRATIVO/MGN_MPIO_POLITICO.shp' using driver `ESRI Shapefile'
Simple feature collection with 42 features and 9 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -77.54977 ymin: 3.091239 xmax: -75.70724 ymax: 5.047394
CRS:            4326

¿Qué hay en ant_munic?

ant_munic
Simple feature collection with 42 features and 9 fields
geometry type:  MULTIPOLYGON
dimension:      XY
bbox:           xmin: -77.54977 ymin: 3.091239 xmax: -75.70724 ymax: 5.047394
CRS:            4326
First 10 features:
   DPTO_CCDGO MPIO_CCDGO   MPIO_CNMBR                           MPIO_CRSLC MPIO_NAREA
1          76      76001         CALI                                 1536  563.04276
2          76      76036    ANDALUCÍA     Ordenanza 38 de Abril 25 de 1921  110.46042
3          76      76041 ANSERMANUEVO                 Ordenanza 29 de 1925  305.45118
4          76      76054      ARGELIA                 Ordenanza 15 de 1956   90.79604
5          76      76111         BUGA                                 1555  825.86513
6          76      76113 BUGALAGRANDE                                 1791  396.78132
7          76      76122   CAICEDONIA Ordenanza 21 del 20 de Abril de 1923  166.98369
8          76      76126       CALIMA     Ordenanza 49 de Junio 23 de 1939  793.49323
9          76      76130   CANDELARIA                                 1797  296.46056
10         76      76147      CARTAGO                                 1863  248.16005
   MPIO_NANO      DPTO_CNMBR Shape_Leng  Shape_Area                       geometry
1       2017 VALLE DEL CAUCA  1.1636697 0.045733211 MULTIPOLYGON (((-76.59175 3...
2       2017 VALLE DEL CAUCA  0.6651004 0.008984851 MULTIPOLYGON (((-76.22406 4...
3       2017 VALLE DEL CAUCA  0.9460442 0.024871077 MULTIPOLYGON (((-76.01558 4...
4       2017 VALLE DEL CAUCA  0.4538261 0.007391017 MULTIPOLYGON (((-76.14316 4...
5       2017 VALLE DEL CAUCA  2.0063716 0.067157337 MULTIPOLYGON (((-76.31608 3...
6       2017 VALLE DEL CAUCA  1.0336063 0.032279294 MULTIPOLYGON (((-76.15131 4...
7       2017 VALLE DEL CAUCA  0.6465900 0.013590500 MULTIPOLYGON (((-75.8539 4....
8       2017 VALLE DEL CAUCA  1.5441977 0.064484981 MULTIPOLYGON (((-76.51747 4...
9       2017 VALLE DEL CAUCA  0.8709866 0.024086066 MULTIPOLYGON (((-76.30455 3...
10      2017 VALLE DEL CAUCA  0.8955580 0.020211481 MULTIPOLYGON (((-75.94518 4...

Debemos tener en cuenta que ant_munic es una colección de características simples. Tambien debemos tener en cuenta que los datos utilizan un sistema de referencia de coordenas geográficas WGS1984 (4326 epsg código ) Podemos usar la función left_join para unir los municipios y las estadísticas agrícolas seleccionadas. Necesitamos un atributo común (o variable compartida) para poder hacer la unión. El mejor atributo es una identificación.

datos %>% filter (MUNICIPIO =="CALI") ->  cali_datos
cali_datos
class(cali_datos$COD_MUN)
[1] "numeric"

Para poder realizar la unión, necesitamos cambiar tanto el tipo de datos como el contenido del código que identifica a cada municipio. Para esta labor, es una buena opción crear una copia de los datos estadísticos originales con el fin de cualquier movimiento en falso no perjudique la información original. Avancemos paso a paso:

datos2 <- datos
datos2$TEMP <-  as.character(datos2$COD_MUN)
datos2$MPIO_CCDGO <- as.factor(paste(datos2$TEMP, sep=""))
head(datos2)

Asegurarse de verificar, en el objeto de datos 2, las caracteristicas del nuevo atributo MPIO_CCDGO Ahora, filtremos un solo año y seleccionemos dos atributos relevantes:

datos2 %>% filter(CULTIVO == "CAFE")  -> datos3
head(datos3)
class(datos3)
[1] "spec_tbl_df" "tbl_df"      "tbl"         "data.frame" 
datos4 <- datos3 %>% dplyr::select(MUNICIPIO, MPIO_CCDGO, YEAR, Produccion, Rendimiento)
datos4
datos4 %>% 
  gather("YEAR", "Produccion", "Rendimiento" , key = variable, value = number)
head(datos4)

Esta es una tarea clave. Implica varios pasos para poder convertir la tabla de atributos de formato largo a ancho.

datos4 %>% 
  group_by(MPIO_CCDGO) %>% 
  mutate(Visit = 1:n()) %>% 
  gather("YEAR", "Produccion", "Rendimiento", key = variable, value = number) %>% 
  unite(combi, variable, Visit) %>%
  spread(combi, number) -> datos5
head(datos5)
tail(datos5)

Haremos una copia de la colección de características simples (en caso de un movimiento falso)

ant_munic2 <- ant_munic

Ahora es tiempo de unir:

ant_munic_stat = left_join(ant_munic2, datos5, by="MPIO_CCDGO")
summary(ant_munic_stat)
 DPTO_CCDGO   MPIO_CCDGO        MPIO_CNMBR   MPIO_CRSLC   MPIO_NAREA        MPIO_NANO   
 76:42      76001  : 1   ALCALÁ      : 1   1536   : 2   Min.   :  41.86   Min.   :2017  
            76020  : 1   ANDALUCÍA   : 1   1824   : 2   1st Qu.: 191.01   1st Qu.:2017  
            76036  : 1   ANSERMANUEVO: 1   1864   : 2   Median : 266.71   Median :2017  
            76041  : 1   ARGELIA     : 1   1555   : 1   Mean   : 492.04   Mean   :2017  
            76054  : 1   BOLÍVAR     : 1   1576   : 1   3rd Qu.: 431.81   3rd Qu.:2017  
            76100  : 1   BUENAVENTURA: 1   1630   : 1   Max.   :6292.50   Max.   :2017  
            (Other):36   (Other)     :36   (Other):33                                   
           DPTO_CNMBR   Shape_Leng       Shape_Area        MUNICIPIO          Produccion_1 
 VALLE DEL CAUCA:42   Min.   :0.4538   Min.   :0.003409   Length:42          Min.   :  76  
                      1st Qu.:0.7223   1st Qu.:0.015552   Class :character   1st Qu.: 405  
                      Median :0.9059   Median :0.021696   Mode  :character   Median : 800  
                      Mean   :1.1602   Mean   :0.039988                      Mean   :1785  
                      3rd Qu.:1.3207   3rd Qu.:0.035099                      3rd Qu.:1910  
                      Max.   :6.5953   Max.   :0.510779                      Max.   :8284  
                                                                             NA's   :3     
 Produccion_10  Produccion_11    Produccion_12     Produccion_2     Produccion_3 
 Min.   : 162   Min.   : 142.0   Min.   : 139.0   Min.   :  85.0   Min.   :  83  
 1st Qu.: 472   1st Qu.: 410.5   1st Qu.: 398.5   1st Qu.: 369.5   1st Qu.: 340  
 Median : 745   Median : 780.0   Median : 748.0   Median : 785.0   Median : 872  
 Mean   :1463   Mean   :1325.3   Mean   :1273.5   Mean   :1683.8   Mean   :1608  
 3rd Qu.:1550   3rd Qu.:1657.0   3rd Qu.:1734.5   3rd Qu.:1960.5   3rd Qu.:1553  
 Max.   :5895   Max.   :6212.0   Max.   :5906.0   Max.   :8284.0   Max.   :8764  
 NA's   :3      NA's   :3        NA's   :3        NA's   :3        NA's   :3     
  Produccion_4    Produccion_5     Produccion_6     Produccion_7   Produccion_8 
 Min.   :   90   Min.   :  86.0   Min.   : 100.0   Min.   :  75   Min.   : 100  
 1st Qu.:  414   1st Qu.: 420.5   1st Qu.: 415.5   1st Qu.: 287   1st Qu.: 351  
 Median :  861   Median : 992.0   Median : 776.0   Median : 528   Median : 670  
 Mean   : 1782   Mean   :1678.9   Mean   :1569.0   Mean   :1101   Mean   :1277  
 3rd Qu.: 1913   3rd Qu.:1908.0   3rd Qu.:1812.0   3rd Qu.:1019   3rd Qu.:1200  
 Max.   :10154   Max.   :8377.0   Max.   :8587.0   Max.   :5161   Max.   :5757  
 NA's   :3       NA's   :3        NA's   :3        NA's   :3      NA's   :3     
  Produccion_9  Rendimiento_1    Rendimiento_10  Rendimiento_11   Rendimiento_12  
 Min.   : 158   Min.   :  1.00   Min.   :  1.0   Min.   :  9.00   Min.   : 62.00  
 1st Qu.: 461   1st Qu.:  8.00   1st Qu.: 96.0   1st Qu.: 67.00   1st Qu.: 78.00  
 Median : 772   Median : 13.00   Median :104.0   Median : 84.00   Median : 86.00  
 Mean   :1476   Mean   : 41.31   Mean   : 93.1   Mean   : 90.59   Mean   : 99.46  
 3rd Qu.:1544   3rd Qu.: 76.00   3rd Qu.:107.5   3rd Qu.:116.00   3rd Qu.:119.00  
 Max.   :6108   Max.   :119.00   Max.   :127.0   Max.   :182.00   Max.   :187.00  
 NA's   :3      NA's   :3        NA's   :3       NA's   :3        NA's   :3       
 Rendimiento_2    Rendimiento_3    Rendimiento_4    Rendimiento_5    Rendimiento_6   
 Min.   :  1.00   Min.   :  1.00   Min.   :  1.00   Min.   :  1.00   Min.   :  1.00  
 1st Qu.:  9.00   1st Qu.:  6.50   1st Qu.:  7.50   1st Qu.:  7.50   1st Qu.:  5.00  
 Median : 44.00   Median : 33.00   Median : 13.00   Median : 12.00   Median :  8.00  
 Mean   : 48.03   Mean   : 47.51   Mean   : 43.54   Mean   : 43.28   Mean   : 25.41  
 3rd Qu.: 81.00   3rd Qu.: 86.00   3rd Qu.: 85.50   3rd Qu.: 86.50   3rd Qu.: 54.00  
 Max.   :143.00   Max.   :141.00   Max.   :141.00   Max.   :141.00   Max.   :102.00  
 NA's   :3        NA's   :3        NA's   :3        NA's   :3        NA's   :3       
 Rendimiento_7    Rendimiento_8    Rendimiento_9        YEAR_1        YEAR_10    
 Min.   :  4.00   Min.   :  7.00   Min.   :  1.00   Min.   :2007   Min.   :2016  
 1st Qu.: 48.00   1st Qu.: 44.50   1st Qu.: 94.00   1st Qu.:2007   1st Qu.:2016  
 Median : 64.00   Median : 78.00   Median :102.00   Median :2007   Median :2016  
 Mean   : 61.08   Mean   : 65.95   Mean   : 92.23   Mean   :2007   Mean   :2016  
 3rd Qu.: 72.00   3rd Qu.: 88.00   3rd Qu.:105.50   3rd Qu.:2007   3rd Qu.:2016  
 Max.   :121.00   Max.   :131.00   Max.   :123.00   Max.   :2007   Max.   :2016  
 NA's   :3        NA's   :3        NA's   :3        NA's   :3      NA's   :3     
    YEAR_11        YEAR_12         YEAR_2         YEAR_3         YEAR_4         YEAR_5    
 Min.   :2017   Min.   :2018   Min.   :2008   Min.   :2009   Min.   :2010   Min.   :2011  
 1st Qu.:2017   1st Qu.:2018   1st Qu.:2008   1st Qu.:2009   1st Qu.:2010   1st Qu.:2011  
 Median :2017   Median :2018   Median :2008   Median :2009   Median :2010   Median :2011  
 Mean   :2017   Mean   :2018   Mean   :2008   Mean   :2009   Mean   :2010   Mean   :2011  
 3rd Qu.:2017   3rd Qu.:2018   3rd Qu.:2008   3rd Qu.:2009   3rd Qu.:2010   3rd Qu.:2011  
 Max.   :2017   Max.   :2018   Max.   :2008   Max.   :2009   Max.   :2010   Max.   :2011  
 NA's   :3      NA's   :3      NA's   :3      NA's   :3      NA's   :3      NA's   :3     
     YEAR_6         YEAR_7         YEAR_8         YEAR_9              geometry 
 Min.   :2012   Min.   :2013   Min.   :2014   Min.   :2015   MULTIPOLYGON :42  
 1st Qu.:2012   1st Qu.:2013   1st Qu.:2014   1st Qu.:2015   epsg:4326    : 0  
 Median :2012   Median :2013   Median :2014   Median :2015   +proj=long...: 0  
 Mean   :2012   Mean   :2013   Mean   :2014   Mean   :2015                     
 3rd Qu.:2012   3rd Qu.:2013   3rd Qu.:2014   3rd Qu.:2015                     
 Max.   :2012   Max.   :2013   Max.   :2014   Max.   :2015                     
 NA's   :3      NA's   :3      NA's   :3      NA's   :3                        

5. Ploteo

Se hará un plot para los municipios con su producción de café correspondiente para un solo año:

bins <- c(0, 250, 500, 1000, 2000, 5000, 10000, 15000)
pal <- colorBin("YlOrRd", domain = ant_munic_stat$PProduccion_12,bins = bins)

  mapa <- leaflet(data = ant_munic_stat) %>%
  addTiles() %>%
  addPolygons(label = ~Produccion_12,
              popup = ~MPIO_CNMBR,
              fillColor = ~pal(Produccion_12),
              color = "#444444",
              weight = 1,
              smoothFactor = 0.5,
              opacity = 1.0,
              fillOpacity = 0.5,
              highlightOptions = highlightOptions(color = "white", weight = 2, bringToFront = TRUE)
              ) %>%
  addProviderTiles(providers$OpenStreetMap) %>%
  addLegend("bottomright", pal = pal, values = ~Produccion_12,
    title = "Produccion de Café en el Valle [Ton] (2018)",
    opacity = 1
  )
mapa
LS0tCnRpdGxlOiAiRXN0YWRpc3RpY2FzIG11bmljaXBhbGVzIGRlIGxhIGFncmljdWx0dXJhIGVuIGVsIFZhbGxlIGRlbCBDdWFjYSIKYXV0aG9yOiAiTmljb2xhcyBDaWZ1ZW50ZXMiCmRhdGU6ICIzMCBkZSBNYXJ6byBkZWwgMjAyMCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMgMS4gwr9Qb3IgcXXDqSBlc3RlIE5vdGVib29rPwoKRXN0ZSBlcyB1biBSIE1hcmtkb3duIE5vdGVib29rIHF1ZSBpbHVzdHJhIGVzdGFkw61zdGljYXMgYWdyw61jb2xhcyBwYXJhIGVsIGRlcGFydGFtZW50byBkZWwgVmFsbGUgZGVsIENhdWNhIGVuIENvbG9tYmlhLgpFbCBwcmVzZW50ZSBjdWFkZXJubyBkZSBSIGF5dWRhIGEgbG9zIGVzdHVkaWFudGVzIGRlIGxhIFVuaXZlcnNpZGFkIE5hY2lvbmFsIGRlIENvbG9tYmlhIHF1ZSBzZSBlbmN1ZW50cmFuIGN1cnNhbmRvIGVsIGN1cnNvIGRlIEdlb23DoXRpY2EgQsOhc2ljYSBhIGVudGVuZGVyIGNvbmNlcHRvcyBiw6FzaWNvcyBkZSBnZW9tw6F0aWNhIGFwbGljYWJsZXMgYSBsYSBhZ3Jvbm9tw61hLgoKIyMgMi4gRnVuY2lvbmFsaWRhZGVzIGRlIEdJUy4KCkxhIGV4cGxvcmFjacOzbiBkZSBlc3RhZMOtc3RpY2FzIG5vIGVzcGFjaWFsZXMgZXMgZXNlbmNpYWwgcGFyYSBjb21wcmVuZGVyIHF1ZSBvY3VycmUgZW4gbG9zIHRlcnJpdG9yaW9zLiBNdWNoYXMgbGlicmVyaWFzIGRlIFIsIHBhcnRpY3VsYXJtZW50ZSBfdGlkeXZlcnNlXyB5IF9kcGx5cl8gc29uIG11eSDDunRpbGVzIHBhcmEgZXhwbG9yYXIgeSByZXN1bWlyIGVzdGFkw61zdGljYXMuClBvciBvdHJvIGxhZG8sIGxhcyBvcGVyYWNpb25lcyBnZW9lc3BhY2lhbGVzIHB1ZWRlbiBtZWpvcmFyIG51ZXN0cm8gZW50ZW5kaW1pZW50byBhY2VyY2EgZGUgbcO6bHRpcGxlcyBwcm9ibGVtYXMgcXVlIGFmZWN0YW4gbGFzIHJlZ29pb25lcyBnZW9ncsOhZmljYXMuIFVuIGJ1ZW4gZWplbXBsbyBlcyBjdWFuZG8gc2UgZGVzZWEgYXZlcmlndWFyIGxhIHViaWNhY2nDs24gZGUgY2llcnRvcyBtdW5pY2lwaW9zIHF1ZSB0aWVuZW4gcmVuZGltaWVudG9zIGRlIGNvc2VjaGEgw7NwdGltb3MuIFBhcmEgcmVhbGl6YXIgZXN0YSBleHBsb3JhY2nDs24gc2UgbmVjZXNpdGEgdW5pciBkYXRvcyBfX25vIGVzcGFjaWFsZXNfXyBjb24gZGF0b3MgX19lc3BhY2lhbGVzX18uIEVzdGEgbGFib3Igc2UgcmVhbGl6w7MgYW50ZXJpb3JtZW50ZSBlbiBRR0lTLCBhaG9yYSBzZSB1dGlsaXphcsOhIFIgcGFyYSBsbGV2YXJsYSBhY2Fiby4KQWRpY2lvbmFsbWVudGUsIHNlIHB1ZWRlIGV4cGxvcmFyIHVuaW9uZXMgZXNwYWNpYWxlcy4gRXN0YXMgb3BjaW9uZXMgc2UgYmFzYW4gZW4gbGEgaW50ZXJhY2Npb24gZGUgZG9zIG9iamV0b3MgZXNwYWNpYWxlcywgYSBtZW51ZG8gcG9sw61nb25vcyB5IHB1bnRvcy4gSGF5IG11Y2hhcyBmb3JtYXMgZGUgdW5pciBvYmpldG9zLCBsYXMgY3VhbGVzIHB1ZWRlbiBpbmNsdWlyIG9wY2lvbmVzIGVzcGVjw61maWNhcyBjb21vOiBjZXJjYSwgZGVudHJvLCB0b3F1ZXMsIGV0Yy4KQ29tZW5jZW1vcyByZW1vdmllbmRvIGVsIGNvbnRlbmlkbyBkZSBsYSBtZW1vcmlhOgoKYGBge3J9CnJtKGxpc3Q9bHMoKSkKYGBgCkFob3JhLCBpbnN0YWxlbW9zIGxhcyBsaWJyZXJpYXMgbmVjZXNhcmlhcy4gRW4gZWwgc2lndWllbnRlIGNodW5rLCBzZSBpbmRpY2EgcXVlIHNvbG8gc2VhbiBpbnN0YWxhZG9zIGxvcyBwYXF1ZXRlcyBxdWUgbm8gaGFuIHNpZG8gcHJldmlhbWVudGUgaW5zdGFsYWRvcy4KYGBge3J9Cmxpc3Qub2YucGFja2FnZXMgPC0gYygiaGVyZSIsICJ0aWR5dmVyc2UiLCAicmdlb3MiLCAibWFwdG9vbHMiLCAicmFzdGVyIiwgInNmIiwgICJ2aXJpZGlzIiwgInJuYXR1cmFsZWFydGgiLCAiR1NPRFIiLCAiZ2dyZXBlbCIsICJjb3dwbG90IikKbmV3LnBhY2thZ2VzIDwtIGxpc3Qub2YucGFja2FnZXNbIShsaXN0Lm9mLnBhY2thZ2VzICVpbiUgaW5zdGFsbGVkLnBhY2thZ2VzKClbLCJQYWNrYWdlIl0pXQppZihsZW5ndGgobmV3LnBhY2thZ2VzKSkgaW5zdGFsbC5wYWNrYWdlcyhuZXcucGFja2FnZXMpCmBgYApBaG9yYSwgY2FyZ3VlbW9zIGxhcyBsaWJyZXJpYXMuCmBgYHtyfQpsaWJyYXJ5KGhlcmUpCmBgYApgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKYGBge3J9CmxpYnJhcnkocmdlb3MpCmBgYApgYGB7cn0KbGlicmFyeShtYXB0b29scykKYGBgCgpgYGB7cn0KbGlicmFyeShyYXN0ZXIpCmBgYAoKYGBge3J9CmxpYnJhcnkoc2YpCmBgYAoKYGBge3J9CmxpYnJhcnkodmlyaWRpcykKYGBgCgpgYGB7cn0KbGlicmFyeShybmF0dXJhbGVhcnRoKQpsaWJyYXJ5KEdTT0RSKQpsaWJyYXJ5KGdncmVwZWwpCmxpYnJhcnkoY293cGxvdCkKYGBgCgojIyAzLiBFeHBsb3JhY2nDs24gZGUgZXN0YWTDrXN0aWNhcyBhZ3LDrWNvbGFzIGVuIGVsIFZhbGxlIGRlbCBDYXVjYQoKUHJldmlhbWVudGUgaGUgZGVzY2FyZ2FkbyBkYXRvcyBlc3RhZMOtc3RpY29zIGVuIGZvcm1hdG8gY3N2IGRlIGxhIHDDoWdpbmEgb2ZpY2lhbCBkZWwgTWluaXN0ZXJpbyBkZSBBZ3JpY3VsdHVyYSB5IERlc2Fycm9sbG8gUnVyYWwgZW4gbWkgY29tcHV0YWRvci4gRGVzcHXDqXMsIGVuIEV4Y2VsLCBlbGltaW7DqSBpbmZvcm1hY2nDs24gbm8gY29ycmVzcG9uZGllbnRlIGFsIGRlcGFydGFtZW50byBkZWwgVmFsbGUgZGVsIENhY3VhLiBHdWFyZMOpIGVsIGFyY2hpdm8gdW5pY2FtZW50ZSBjb24gaW5mb3JtYWNpw7NuIGNvcnJlc3BvbmRpZW50ZSBhIG1pIGRlcGFydGFtZW50byBlbiB1biBkb2N1bWVudG8gbGxhbWFkbyBFVkFfVmFsbGUyLmNzdiwgbHVlZ28gbG8gY2FyZ3XDqSBkZW50cm8gZGUgdW5hIGNhcnBldGEgbGxhbWFkYSBhZ3JvIGVuIFJTdHVkaW8gQ2xvdWQuCkxlYW1vcyBlbCBhcmNoaXZvIGNzdiBjb24gIkVzdGFkw61zdGljYXMgbXVuaWNpcGFsZXMgYWdyb3BlY3VhcmlhcyIgcGFyYSBlbCBWYWxsZSBkZWwgQ2F1Y2EKCmBgYHtyfQpkYXRvcyA8LSByZWFkX2NzdigiLi9hZ3JvL0VWQV9WYWxsZTIuY3N2IikKYGBgCkNoZXF1ZWVtb3MgY3VhbGVzIHNvbiBsb3MgYXRyaWJ1dG9zIGRlIF9kYXRvc186IHRhbnRvIGFsIHByaW5jaXBpbyAoaGVhZCkgY29tbyBhbCBmaW5hbCAodGFpbCkKYGBge3J9CmhlYWQoZGF0b3MpCmBgYApgYGB7cn0KdGFpbChkYXRvcykKYGBgCgpTZSByZWNhbGNhIHF1ZSBjYWRhIG11bmljaXBpbyBjdWVudGEgY29uIGVzdGFkw61zdGljYXMgYWNlcmNhIGRlbCDDoXJlYSBzZW1icmFkYSwgw6FyZWEgY29zZWNoYWRhIHkgcmVuZGltaWVudG8gZGUgbG9zIGN1bHRpdm9zIHF1ZSBhbGzDrSBzZSBwcm9kdWNlbiBlbiBkaWZlcmVudGVzIGHDsW9zLgpFbiBsYSB0YWJsYSBubyBoYXkgdW5pZGFkZXMsIHNpbiBlbWJhcmdvLCBzaSBzZSB2ZXJpZmljYSBlbCBhcmNoaXZpbyBjc3Ygb3JpZ2luYWwgc2UgZW5jdWVudHJhIHF1ZSBsYXMgdW5pZGFkZXMgcGFyYSBlbCDDoXJlYSBzb24gbGFzIGhlY3TDoXJlYXMgeSBwYXJhIGVsIHJlbmRpbWllbnRvIHNvbiBUb24vaGEuClVzYXJlbW9zIGxhIGxpYnJlcmlhIChkcGx5cikgcGFyYSBleHBsb3JhciBsb3MgY29udGVuaWRvcyBkZSBsb3MgZGF0b3MgZGVsIG9iamV0by4KUGFyYSBlc3RvLCBwcmltZXJvIG9idGVuZW1vcyB1biBzdW1hcmlvIGRlIHJlbmRpbWllbnRvIHBvciBlbCBncnVwbyB5IG11bmljaXBpbzoKYGBge3J9CmRhdG9zICU+JQogIGdyb3VwX2J5KE1VTklDSVBJTywgR1JVUE8pICU+JQogIHN1bW1hcmlzZShyZW5kX3Byb20gPSBtZWFuKFJlbmRpbWllbnRvLCBuYS5ybSA9IFRSVUUpKSAtPiByZW5kX3Jlc3VtZW4KIyMjIExldCdzIHZpc3VhbGl6ZSBvbmx5IHRoZSBmaXJzdCBzaXggcmVjb3JkcwpoZWFkKHJlbmRfcmVzdW1lbikKYGBgCkRlIGlndWFsIGZvcm1hLCBwb2RlbW9zIGNhbGN1bGFyIGVsIHJlbmRpbWllbnRvIHByb21lZGlvIHBvciBncnVwbyBlbiBsb3MgbXVuaWNpcGlvcyBkZWwgVmFsbGUgZGVsIENhdWNhOgpgYGB7cn0KZGF0b3MgJT4lCiAgZ3JvdXBfYnkoR1JVUE8pICU+JQogIHN1bW1hcmlzZShyZW5kX2RlcCA9IG1lYW4oUmVuZGltaWVudG8sIG5hLnJtID0gVFJVRSkpIC0+IHJlbmRfdmFsbGVkZWxjYXVjYQoKcmVuZF92YWxsZWRlbGNhdWNhCmBgYApOb3RlbW9zIHF1ZSBsb3MgcmVuZGltaWVudG9zIG3DoXMgYWx0b3MgY29ycmVzcG9uZGVuIGEgQ0VSRUFMRVMsIEZJQlJBUyBZIEZMT1JFUyBZIEZPUlJBSkVTLgpBaG9yYSB2ZWFtb3MgY3VhbGVzIHNvbiBsb3MgbXVuaWNpcGlvcyBjb24gbWF5b3IgcmVuZGltaWVudG8gcGFyYSBjYWRhIGdydXBvIGRlIGN1bHRpdm9zIGVuIGVsIGHDsW8gMjAxODoKYGBge3J9CmRhdG9zICU+JSAKICBmaWx0ZXIoWUVBUj09MjAxOCkgJT4lIAogIGdyb3VwX2J5KEdSVVBPLCBNVU5JQ0lQSU8pICU+JQogIHN1bW1hcml6ZShtYXhfcmVuZCA9IG1heChSZW5kaW1pZW50bywgbmEucm0gPSBUUlVFKSkgJT4lCiAgICBzbGljZSh3aGljaC5tYXgobWF4X3JlbmQpKSAtPiByZW5kX21heF8xOAoKcmVuZF9tYXhfMTgKYGBgCgpFbiBzZWd1aWRhIG1pcmFyZW1vcyBjdWFsZXMgc29uIGxvcyBtdW5pY2lwaW9zIGNvbiBtYXlvciDDoXJlYSBjb3NlY2hhZGEgcGFyYSBjYWRhIGdydXBvIGRlIGN1bHRpdm9zIGVuIDIwMTg6CgpgYGB7cn0KZGF0b3MgJT4lIAogIGZpbHRlcihZRUFSPT0yMDE4KSAlPiUgCiAgZ3JvdXBfYnkoR1JVUE8sIE1VTklDSVBJTykgJT4lCiAgc3VtbWFyaXplKG1heF9hcmVhX2Nvc2VjaGEgPSBtYXgoQXJlYV9Db3NlY2hhLCBuYS5ybSA9IFRSVUUpKSAlPiUKICAgIHNsaWNlKHdoaWNoLm1heChtYXhfYXJlYV9jb3NlY2hhKSkgLT4gYXJlYV9jb3NlY2hhX21heAoKYXJlYV9jb3NlY2hhX21heApgYGAKCk5vdGVtb3MgcXVlIGVsIMOhcmVtYXMgbcOhcyBncmFuZGUgY29zZWNoYWRhIGVuIGVsIGHDsW8gMjAxOCBwYXJhIEphbXVuZMOtIGNvcnJlc3BvbmRlIGEgQ2VyZWFsZXMsIHB1ZXMgZXN0YSB6b25hLCB1YmljYWRhIGVuIGVsIHN1ciBkZWwgZGVwYXJ0YW1lbnRvLCBzZSBzaWVtYnJhbiBncmFuZGVzIGV4dGVuc2lvbmVzIGRlIGFycm96LgpTZWxlY2Npb25lbW9zIGxhIHByb2R1Y2Npw7NuIGRlIGFycm96ICh0b25lbGFkYXMpIGVuIEphbXVuZMOtIHBhcmEgY2FkYSBhw7FvOgoKYGBge3J9CmRhdG9zICU+JSAKICBmaWx0ZXIoTVVOSUNJUElPPT0iSkFNVU5ESSIgJiBTVUJHUlVQTz09IkFSUk9aIikgJT4lIAogIGdyb3VwX2J5KFlFQVIsIENVTFRJVk8pIC0+ICBqYW11bmRpX2Fycm96CgpqYW11bmRpX2Fycm96CmBgYAoKSGFyZW1vcyB1bmEgZXhwbG9yYWNpw7NuIGdyw6FmaWNhIGLDoXNpY2E6CgpgYGB7cn0KZyA8LSBnZ3Bsb3QoYWVzKHg9WUVBUiwgeT1Qcm9kdWNjaW9uLzEwMDApLCBkYXRhID0gamFtdW5kaV9hcnJveikgKyBnZW9tX2JhcihzdGF0PSdpZGVudGl0eScpICsgbGFicyh5PSdQcm9kdWNjacOzbiBkZSBhcnJveiBbVG9uIHggMTAwMF0nKQpgYGAKYGBge3J9CmcgKyBnZ3RpdGxlKCJFdm9sdWNpw7NuIGRlIGxhIHByb2R1Y2Npb24gZGUgYXJyb3ogZW4gSmFtdW5kaSBkZXNkZSAyMDA3IGFsIDIwMTgiKSArIGxhYnMoY2FwdGlvbj0gIkJhc2FkbyBlbiBkYXRvcyBkZWwgREFORSwgMjAxOCIpCmBgYAoKQWhvcmEgbWlyYXJlbW9zIHF1ZSBjdWx0aXZvcyB0dXZpZXJvbiBsYSBtYXlvciDDoXJlYSBjb3NlY2hhZGEgZW4gZWwgYcOxbyAyMDE4OgoKYGBge3J9CmRhdG9zICU+JSAKICBmaWx0ZXIoWUVBUj09MjAxOCkgJT4lIAogIGdyb3VwX2J5KEdSVVBPKSAlPiUKICBzdW1tYXJpemUoc3VtX2FyZWFfY29zZWNoYSA9IHN1bShBcmVhX0Nvc2VjaGEsIG5hLnJtID0gVFJVRSkpICU+JQogICAgIGFycmFuZ2UoZGVzYyhzdW1fYXJlYV9jb3NlY2hhKSkgLT4gdG90YWxfYXJlYV9jb3NlY2hhCgp0b3RhbF9hcmVhX2Nvc2VjaGEKYGBgCgpPYnNlcnZhbW9zIHF1ZSBvdHJvcyBjdWx0aXZvcyBwZXJtYW5lbnRlcyBjb3JyZXNwb25kZW4gYSBsYSBtYXlvciDDoXJlYSBjb3NlY2hhZGEgZW4gZWwgMjAxOCBlbiBlbCBWYWxsZSBkZWwgQ2F1Y2EuIFBhcmEgb2JzZXJ2YXIgcXVlIGN1bHRpdm9zIHBlcnRlbmVjZW4gYSBlc3RlIGdydXBvbywgcHVlZGUgYnVzY2FyIGluZm9ybWFjacOzbiBlbiBsYSBww6FnaW5hIG9maWNpYWwgZGVsIERBTkUsIGVudGlkYWQgcmVzcG9uc2FibGUgZGUgbGEgcGxhbmVhY2nDs24sIGxldmFudGFtaWVudG8sIHByb2Nlc2FtaWVudG8sIGFuw6FsaXNpcyB5IGRpZnVzacOzbiBkZSBsYXMgZXN0YWTDrXN0aWNhcyBvZmljaWFsZXMgZGUgQ29sb21iaWEuCkRlIGlndWFsIGZvcm1hLCBwb2RlbW9zIGJ1c2NhciBlc3RhIGluZm9ybWFjacOzbiBlbiBsb3MgbWlzbW9zIGRhdG9zLgoKYGBge3J9CmRhdG9zICU+JQogIGZpbHRlcihHUlVQTz09Ik9UUk9TIFBFUk1BTkVOVEVTIiAmIFlFQVI9PTIwMTgpICU+JQogIGdyb3VwX2J5KENVTFRJVk8pICU+JQogIHN1bW1hcml6ZShzdW1fY29zZWNoYSA9IHN1bShBcmVhX0Nvc2VjaGEsIG5hLnJtID0gVFJVRSkpICU+JQogICAgIGFycmFuZ2UoZGVzYyhzdW1fY29zZWNoYSkpIC0+IHRvdGFsX2Nvc2VjaGEKCgp0b3RhbF9jb3NlY2hhCmBgYAoKUmVzYWx0YW1vcyBxdWUgbGEgY2HDsWEgZGUgYXp1Y2FyIGVzIGRlIHRvdGFsIGltcG9ydGFuY2lhIGVuIGxhIGFncmljdWx0dXJhIHZhbGxlY2F1Y2FuYSBkZWJpZG8gYSBsYSBncmFuIGNhbnRpZGFkIGRlIGluZ2VuaW9zIGF6dWNhcmVyb3MgcXVlIHNlIGVuY3VlbnRyYW4gZW4gZXN0YSByZWdpw7NuLgpBIGNvbnRpbnVhY2nDs24gdmVhbW9zIGN1YWxlcyBzb24gbG9zIG11bmljaXBpb3MgY29uIG1heW9yIMOhcmVhIGNvc2VjaGFkYSBwYXJhIGNhZGEgY3VsdGl2byBwZXJtYW5lbnRlIGVuIDIwMTg6CgpgYGB7cn0KZGF0b3MgJT4lIAogIGZpbHRlcihZRUFSPT0yMDE4ICYgR1JVUE89PSJPVFJPUyBQRVJNQU5FTlRFUyIpICU+JSAKICBncm91cF9ieShDVUxUSVZPLCBNVU5JQ0lQSU8pICU+JQogIHN1bW1hcml6ZShtYXhfYXJlYTIgPSBtYXgoQXJlYV9Db3NlY2hhLCBuYS5ybSA9IFRSVUUpKSAlPiUKICAgIHNsaWNlKHdoaWNoLm1heChtYXhfYXJlYTIpKSAtPiBhcmVhX2Nvc2VjaGEyCgphcmVhX2Nvc2VjaGEyCmBgYAoKUmVncmVzZW1vcyBhIGxvcyBkYXRvcyBkZSBjdWx0aXZvcyBwZXJtYW5lbnRlcy4gQW50ZXMgZGUgcGxvdGVhciwgbmVjZXNpdGFtb3MgYWdyZWdhciBhbCBhdHJpYnV0byB0b3RhbF9hcmVhX2Nvc2VjaGEsIHVuIG51ZXZvIGNhbXBvIGNvbiBhYnJldmlhdHVyYXMgcGFyYSBjYWRhIEdSVVBPIGRlIGN1bHRpdm9zIHBhcmEgcXVlIGVsIHBsb3Qgbm8gbHV6Y2EgZGVzb3JkZW5hZG8uCgpgYGB7cn0KdG90YWxfYXJlYV9jb3NlY2hhJENST1AgPC0gYWJicmV2aWF0ZSh0b3RhbF9hcmVhX2Nvc2VjaGEkR1JVUE8sIDMpCmBgYAoKQWhvcmEgZXMgdGllbXBvIGRlIHBsb3RlYXI6CgpgYGB7cn0KZyA8LSBnZ3Bsb3QoYWVzKHg9Q1JPUCwgeT1zdW1fYXJlYV9jb3NlY2hhKSwgZGF0YSA9IHRvdGFsX2FyZWFfY29zZWNoYSkgKyBnZW9tX2JhcihzdGF0PSdpZGVudGl0eScpICsgbGFicyh5PSdBcmVhIFRvdGFsIENvc2VjaGFkYSBbSGFdJykKYGBgCgpgYGB7cn0KZysgZ2d0aXRsZSgiQXJlYSB0b3RhbCBjb3NlY2hhZGEgcG9yIGdydXBvcyBkZSBjdWx0aXZvcyBlbiBlbCAyMDE4IHBhcmEgZWwgVmFsbGUiKSArIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArCiAgIGxhYnMoY2FwdGlvbj0gIkJhc2FkbyBlbiBkYXRvcyBkZWwgREFORSwgMjAxOCIpCmBgYAoKU2UgcHVlZGUgb2J0ZW5lciBtw6FzIGluZm9ybWFjacOzbiBjb3JyZXNwb25kaWVudGUgYWwgZGVwYXJ0YW1lbnRvLCBlc3BlY2lmaWNhbWVudGUgZGUgZGlmZXJlbnRlcyBjdWx0aXZvcyBxdWUgc29uIGltcG9ydGFudGVzIHByaW5jaXBhbGVtZW50ZSBwYXJhIGxhIGVjb25vbcOtYSBkZSBlc3RlLgoKIyMgNC4gVW5pZW5kbyBlc3RhZMOtc3RpY2FzIGFncsOtY29sYXMgYSBtdW5pY2lwaW9zCgpQcmV2aWFtZW50ZSBoZSBzdWJpZG8gYSBSU3R1ZGlvIENsb3VkIGxvcyBkYXRvcyBhZG1pbmlzdHJhdGl2b3MgZGVsIFZhbGxlIGRlbCBDYXVjYS4KRW1wZWNlbW9zIGxleWVuZG8gbG9zIGRhdG9zIHVzYW5kbyBsYSBsaWJyZXJpYSBfc2ZfCgpgYGB7cn0KYW50X211bmljIDwtIHNmOjpzdF9yZWFkKCIuL3ZhbGxlZGVsY2F1Y2EvQURNSU5JU1RSQVRJVk8vTUdOX01QSU9fUE9MSVRJQ08uc2hwIikKYGBgCgrCv1F1w6kgaGF5IGVuIGFudF9tdW5pYz8KCmBgYHtyfQphbnRfbXVuaWMKYGBgCgpEZWJlbW9zIHRlbmVyIGVuIGN1ZW50YSBxdWUgX2FudF9tdW5pY18gZXMgdW5hIGNvbGVjY2nDs24gZGUgY2FyYWN0ZXLDrXN0aWNhcyBzaW1wbGVzLgpUYW1iaWVuIGRlYmVtb3MgdGVuZXIgZW4gY3VlbnRhIHF1ZSBsb3MgZGF0b3MgdXRpbGl6YW4gdW4gc2lzdGVtYSBkZSByZWZlcmVuY2lhIGRlIGNvb3JkZW5hcyBnZW9ncsOhZmljYXMgV0dTMTk4NCAoNDMyNiBlcHNnIGPDs2RpZ28gKQpQb2RlbW9zIHVzYXIgbGEgZnVuY2nDs24gX2xlZnRfam9pbl8gcGFyYSB1bmlyIGxvcyBtdW5pY2lwaW9zIHkgbGFzIGVzdGFkw61zdGljYXMgYWdyw61jb2xhcyBzZWxlY2Npb25hZGFzLgpOZWNlc2l0YW1vcyB1biBhdHJpYnV0byBjb23Dum4gKG8gdmFyaWFibGUgY29tcGFydGlkYSkgcGFyYSBwb2RlciBoYWNlciBsYSB1bmnDs24uIEVsIG1lam9yIGF0cmlidXRvIGVzIHVuYSBpZGVudGlmaWNhY2nDs24uCmBgYHtyfQpkYXRvcyAlPiUgZmlsdGVyIChNVU5JQ0lQSU8gPT0iQ0FMSSIpIC0+ICBjYWxpX2RhdG9zCmBgYAoKYGBge3J9CmNhbGlfZGF0b3MKYGBgCgpgYGB7cn0KY2xhc3MoY2FsaV9kYXRvcyRDT0RfTVVOKQpgYGAKClBhcmEgcG9kZXIgcmVhbGl6YXIgbGEgdW5pw7NuLCBuZWNlc2l0YW1vcyBjYW1iaWFyIHRhbnRvIGVsIHRpcG8gZGUgZGF0b3MgY29tbyBlbCBjb250ZW5pZG8gZGVsIGPDs2RpZ28gcXVlIGlkZW50aWZpY2EgYSBjYWRhIG11bmljaXBpby4gUGFyYSBlc3RhIGxhYm9yLCBlcyB1bmEgYnVlbmEgb3BjacOzbiBjcmVhciB1bmEgY29waWEgZGUgbG9zIGRhdG9zIGVzdGFkw61zdGljb3Mgb3JpZ2luYWxlcyBjb24gZWwgZmluIGRlIGN1YWxxdWllciBtb3ZpbWllbnRvIGVuIGZhbHNvIG5vIHBlcmp1ZGlxdWUgbGEgaW5mb3JtYWNpw7NuIG9yaWdpbmFsLgpBdmFuY2Vtb3MgcGFzbyBhIHBhc286CgpgYGB7cn0KZGF0b3MyIDwtIGRhdG9zCmBgYAoKYGBge3J9CmRhdG9zMiRURU1QIDwtICBhcy5jaGFyYWN0ZXIoZGF0b3MyJENPRF9NVU4pCmBgYAoKYGBge3J9CmRhdG9zMiRNUElPX0NDREdPIDwtIGFzLmZhY3RvcihwYXN0ZShkYXRvczIkVEVNUCwgc2VwPSIiKSkKYGBgCgpgYGB7cn0KaGVhZChkYXRvczIpCmBgYAoKQXNlZ3VyYXJzZSBkZSB2ZXJpZmljYXIsIGVuIGVsIG9iamV0byBkZSBkYXRvcyAyLCBsYXMgY2FyYWN0ZXJpc3RpY2FzIGRlbCBudWV2byBhdHJpYnV0byBNUElPX0NDREdPCkFob3JhLCBmaWx0cmVtb3MgdW4gc29sbyBhw7FvIHkgc2VsZWNjaW9uZW1vcyBkb3MgYXRyaWJ1dG9zIHJlbGV2YW50ZXM6CgpgYGB7cn0KZGF0b3MyICU+JSBmaWx0ZXIoQ1VMVElWTyA9PSAiQ0FGRSIpICAtPiBkYXRvczMKYGBgCgpgYGB7cn0KaGVhZChkYXRvczMpCmBgYAoKYGBge3J9CmNsYXNzKGRhdG9zMykKYGBgCgpgYGB7cn0KZGF0b3M0IDwtIGRhdG9zMyAlPiUgZHBseXI6OnNlbGVjdChNVU5JQ0lQSU8sIE1QSU9fQ0NER08sIFlFQVIsIFByb2R1Y2Npb24sIFJlbmRpbWllbnRvKQpgYGAKCmBgYHtyfQpkYXRvczQKYGBgCgpgYGB7cn0KZGF0b3M0ICU+JSAKICBnYXRoZXIoIllFQVIiLCAiUHJvZHVjY2lvbiIsICJSZW5kaW1pZW50byIgLCBrZXkgPSB2YXJpYWJsZSwgdmFsdWUgPSBudW1iZXIpCmBgYAoKYGBge3J9CmhlYWQoZGF0b3M0KQpgYGAKCkVzdGEgZXMgdW5hIHRhcmVhIGNsYXZlLiBJbXBsaWNhIHZhcmlvcyBwYXNvcyBwYXJhIHBvZGVyIGNvbnZlcnRpciBsYSB0YWJsYSBkZSBhdHJpYnV0b3MgZGUgZm9ybWF0byBsYXJnbyBhIGFuY2hvLgoKYGBge3J9CmRhdG9zNCAlPiUgCiAgZ3JvdXBfYnkoTVBJT19DQ0RHTykgJT4lIAogIG11dGF0ZShWaXNpdCA9IDE6bigpKSAlPiUgCiAgZ2F0aGVyKCJZRUFSIiwgIlByb2R1Y2Npb24iLCAiUmVuZGltaWVudG8iLCBrZXkgPSB2YXJpYWJsZSwgdmFsdWUgPSBudW1iZXIpICU+JSAKICB1bml0ZShjb21iaSwgdmFyaWFibGUsIFZpc2l0KSAlPiUKICBzcHJlYWQoY29tYmksIG51bWJlcikgLT4gZGF0b3M1CmBgYAoKYGBge3J9CmhlYWQoZGF0b3M1KQpgYGAKCmBgYHtyfQp0YWlsKGRhdG9zNSkKYGBgCgpIYXJlbW9zIHVuYSBjb3BpYSBkZSBsYSBjb2xlY2Npw7NuIGRlIGNhcmFjdGVyw61zdGljYXMgc2ltcGxlcyAoZW4gY2FzbyBkZSB1biBtb3ZpbWllbnRvIGZhbHNvKQoKYGBge3J9CmFudF9tdW5pYzIgPC0gYW50X211bmljCmBgYAoKQWhvcmEgZXMgX190aWVtcG8gZGUgdW5pcl9fOgoKYGBge3J9CmFudF9tdW5pY19zdGF0ID0gbGVmdF9qb2luKGFudF9tdW5pYzIsIGRhdG9zNSwgYnk9Ik1QSU9fQ0NER08iKQpgYGAKCmBgYHtyfQpzdW1tYXJ5KGFudF9tdW5pY19zdGF0KQpgYGAKCiMjIDUuIFBsb3RlbwoKU2UgaGFyw6EgdW4gcGxvdCBwYXJhIGxvcyBtdW5pY2lwaW9zIGNvbiBzdSBwcm9kdWNjacOzbiBkZSBjYWbDqSBjb3JyZXNwb25kaWVudGUgcGFyYSB1biBzb2xvIGHDsW86CgpgYGB7cn0KYmlucyA8LSBjKDAsIDI1MCwgNTAwLCAxMDAwLCAyMDAwLCA1MDAwLCAxMDAwMCwgMTUwMDApCnBhbCA8LSBjb2xvckJpbigiWWxPclJkIiwgZG9tYWluID0gYW50X211bmljX3N0YXQkUFByb2R1Y2Npb25fMTIsYmlucyA9IGJpbnMpCgogIG1hcGEgPC0gbGVhZmxldChkYXRhID0gYW50X211bmljX3N0YXQpICU+JQogIGFkZFRpbGVzKCkgJT4lCiAgYWRkUG9seWdvbnMobGFiZWwgPSB+UHJvZHVjY2lvbl8xMiwKICAgICAgICAgICAgICBwb3B1cCA9IH5NUElPX0NOTUJSLAogICAgICAgICAgICAgIGZpbGxDb2xvciA9IH5wYWwoUHJvZHVjY2lvbl8xMiksCiAgICAgICAgICAgICAgY29sb3IgPSAiIzQ0NDQ0NCIsCiAgICAgICAgICAgICAgd2VpZ2h0ID0gMSwKICAgICAgICAgICAgICBzbW9vdGhGYWN0b3IgPSAwLjUsCiAgICAgICAgICAgICAgb3BhY2l0eSA9IDEuMCwKICAgICAgICAgICAgICBmaWxsT3BhY2l0eSA9IDAuNSwKICAgICAgICAgICAgICBoaWdobGlnaHRPcHRpb25zID0gaGlnaGxpZ2h0T3B0aW9ucyhjb2xvciA9ICJ3aGl0ZSIsIHdlaWdodCA9IDIsIGJyaW5nVG9Gcm9udCA9IFRSVUUpCiAgICAgICAgICAgICAgKSAlPiUKICBhZGRQcm92aWRlclRpbGVzKHByb3ZpZGVycyRPcGVuU3RyZWV0TWFwKSAlPiUKICBhZGRMZWdlbmQoImJvdHRvbXJpZ2h0IiwgcGFsID0gcGFsLCB2YWx1ZXMgPSB+UHJvZHVjY2lvbl8xMiwKICAgIHRpdGxlID0gIlByb2R1Y2Npb24gZGUgQ2Fmw6kgZW4gZWwgVmFsbGUgW1Rvbl0gKDIwMTgpIiwKICAgIG9wYWNpdHkgPSAxCiAgKQpgYGAKCmBgYHtyfQptYXBhCmBgYAoKCgoKCgo=