Archivo publicado: https://rpubs.com/sebascj/1215805


Tecnológico de Costa Rica

Curso: Introducción a la Programación con R.

Trabajo de Investigación 2: Librería ggplot2

Profesor: Luis Fernando Castro

Estudiantes:

Maricruz Vargas Ramírez

Laura Rodríguez Vargas

María Gabriela Severino Calderón

Sebastián Cabezas Jiménez


Introducción

Como parte de la investigación a la librería ggplot2 de R, se consultó el artículo “Una breve introducción a ggplot2” de José Ramón Berrendero (Universidad Autónoma de Madrid), quien introdujo de manera puntual y clara a esta librería. Los puntos introductorios que se dan a conocer con este artículo, sirven para descomponer el análisis y se expone a continuación.

La librería ggplot2 de R es un sistema organizado de visualización de datos. Forma parte del conjunto de librerías llamado tidyverse. En este documento se introduce su uso, principalmente a través de ejemplos.

Los elementos necesarios para representar un gráfico con ggplot2 son los siguientes:

Normalmente estos elementos se van añadiendo de forma consecutiva en distintas capas (layers). Para añadir una nueva capa se usa el signo +. La estructura general del código para obtener un gráfico es esta:

ggplot(data = 'nombre del fichero de datos') + geom_nombre1(aes(aesthetics1=var1, aesthetics2=var2, ...)) + geom_nombre2(...)

El comando ggplot se usa para generar el sistema de coordenadas (por defecto, rectangulares) y posteriormente vamos añadiendo los geoms con sus correspondientes aesthetics. En principio los aesthetics se pueden asignar individualmente para cada geom.

Considerando estos elementos es que iniciaremos nuestra investigación. Se van a contemplar varios puntos sobre la librería ggplot2, como su historia, funciones principales, descripción de las capas que conforman un gráfico y su utilización, ejemplos de uso, y graficaremos el histograma de cada una de las variables numéricas del dataset Iris.


Desarrollo

La librería ggplot2 se ha consolidado como una de las librerías más robustas y ampliamente utilizadas para la visualización de datos en el lenguaje de programación R. Formando parte integral del ecosistema tidyverse, esta herramienta ofrece a los usuarios la posibilidad de crear gráficos complejos de forma intuitiva y eficiente. En esta investigación, se abordará la historia de ggplot2, sus funciones más destacadas, la estructura de capas que compone un gráfico y se proporcionarán ejemplos prácticos para demostrar su utilidad en el contexto del análisis de datos.

1. Historia de la librería

La librería ggplot2 fue desarrollada por Hadley Wickham en 2005 durante sus estudios de doctorado en Estadística. Wickham se inspiró en el concepto de la Gramática de los Gráficos (“Grammar of Graphics”) de Leland Wilkinson, una teoría que sistematiza la construcción de gráficos estadísticos mediante la división en componentes fundamentales, tales como datos, geometrías y estéticas. Esta gramática aporta un marco coherente para la creación y descripción de visualizaciones, lo que permitió a Wickham concebir ggplot2 no solo como una herramienta para generar gráficos, sino también como un medio para mejorar la comprensión y comunicación de datos a través de visualizaciones claras y efectivas (Wickham, 2016).

Desde su lanzamiento inicial, ggplot2 ha evolucionado hasta convertirse en un estándar para la visualización de datos en R, apreciada por su flexibilidad, capacidad de personalización y el apoyo de una comunidad activa. Al ser una librería de código abierto, ha experimentado numerosas actualizaciones y expansiones, manteniendo su relevancia tanto en ámbitos académicos como en el sector industrial.

2. Funciones principales

La filosofía de ggplot2 se fundamenta en la gramática de los gráficos, lo que implica que los gráficos se construyen mediante la combinación de capas sucesivas, que incluyen datos, geometrías (geoms), estéticas (aesthetics), facetas, y otros elementos. A continuación, se detallan algunas de las funciones más representativas de la librería:

  1. ggplot(): Es la función central utilizada para iniciar un gráfico, estableciendo el conjunto de datos y las estéticas globales, como los ejes x e y.
ggplot(data = mtcars, aes(x = wt, y = mpg))
  1. geom_*: Las funciones geométricas, como geom_point(), geom_line(), geom_bar(), etc., se emplean para añadir elementos visuales que representan los datos. Cada función geométrica produce una forma visual específica, como puntos, líneas o barras.
ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point()
  1. aes(): La función aes() se utiliza para asignar variables del conjunto de datos a propiedades visuales del gráfico, como la posición en los ejes, el color, el tamaño, etc.
ggplot(mtcars, aes(x = wt, y = mpg, color = factor(cyl))) + geom_point()
  1. facet_*: Las funciones de facetado, como facet_wrap() y facet_grid(), permiten crear subgráficos dentro de un gráfico principal, basados en los valores de una o más variables, lo cual es útil para comparar diferentes subconjuntos de datos.
ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point() + facet_wrap(\~ cyl)
  1. labs() y ggtitle(): Estas funciones son utilizadas para añadir etiquetas y títulos a los gráficos, mejorando así su claridad y presentación.
ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point() + ggtitle("Relación entre Peso y Consumo de Combustible")
  1. theme(): La función theme() ofrece la posibilidad de personalizar la apariencia del gráfico, permitiendo modificar elementos como los ej es, los textos y el fondo.
ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point() + theme_minimal()
  1. scale_*(): Ajusta las escalas y mapeos estéticos, como colores, tamaños, y forma.

  2. coord_*(): Modifica el sistema de coordenadas del gráfico. Útil para cambiar la perspectiva, ajustar la proporción, o transformar ejes.

Estas funciones permiten construir gráficos desde lo más simple hasta lo más complejo, personalizando cada aspecto del gráfico para ajustarlo a las necesidades de la visualización.

3. Descripción de las capas que conforman un gráfico y su utilización

La construcción de gráficos en ggplot2 se basa en la adición de múltiples capas, cada una de las cuales contribuye con un elemento específico al gráfico final:

  • Capa de Datos (data): Esta capa corresponde al conjunto de datos utilizado para construir el gráfico. Se especifica en la función ggplot() o directamente en las funciones geométricas.

  • Capa de Estética (aes): Determina cómo se asignan las variables a las propiedades visuales del gráfico, tales como la posición (ejes x e y), el color, el tamaño, la forma, etc. Esta capa se define dentro de ggplot() o se puede añadir a nivel de capa geométrica.

  • Capa Geométrica (geom): Representa la forma visual de los datos en el gráfico. Cada tipo de gráfico (puntos, barras, líneas, etc.) corresponde a una función geométrica específica (geom_point(), geom_bar(), geom_line(), etc.).

  • Capa de Escalas (scale_): Ajusta cómo se representan las asignaciones estéticas, como la escala de colores, tamaños, o ejes. Permite personalizar la transformación y los límites de los datos visualizados

  • Capa Estadística (stat): ggplot2 incorpora funciones estadísticas predeterminadas que permiten calcular y representar visualmente estadísticas sobre los datos. Aunque muchas funciones geométricas tienen una función estadística por defecto, esta puede ser modificada según las necesidades.

  • Capa de Facetado (facet): Facilita la creación de gráficos divididos en múltiples paneles según los valores de una o más variables, lo que ayuda a comparar distintos subconjuntos de datos.

  • Capa de Coordenadas (coord): Define el sistema de coordenadas del gráfico. El sistema predeterminado es el cartesiano (coord_cartesian()), aunque también se pueden usar coordenadas polares (coord_polar()), entre otras opciones.

  • Capa de Etiquetas y Títulos (labs() y ggtitle()): Añade o modifica las etiquetas del gráfico, como los títulos de los ejes, el título principal del gráfico, y las etiquetas de las leyendas.

  • Capa de Tema (theme): Permite la personalización de elementos del gráfico que no están relacionados directamente con los datos, como fondos, bordes y textos de los ejes. Existen temas predefinidos como theme_minimal() o theme_classic(), que ofrecen estilos gráficos que pueden ajustarse aún más.

Resumen del Proceso

Al construir un gráfico en ggplot2, generalmente sigues este proceso:

  1. Define los datos y las asignaciones estéticas (ggplot() y aes()).

  2. Añade geometrías para visualizar los datos (geom_*).

  3. Ajusta escalas y coordenadas si es necesario (scale_, coord_).

  4. Añade facetas si deseas dividir el gráfico en múltiples subgráficos (facet_*).

  5. Personaliza etiquetas y títulos (labs()).

  6. Ajusta el tema del gráfico (theme_*).

Estas capas pueden ser combinadas de diferentes maneras para crear gráficos altamente personalizados y complejos en ggplot2.

4. Ejemplos de uso

Esta herramienta de visualización facilita la creación de gráficos complejos a partir de datos almacenados en marcos de datos. Dicha herramienta, tiene una gran variedad de funciones para utilizar y seleccionar las variables a representar y ajustar la apariencia de los gráficos. Este paquete es efectivo en situaciones que se trabaja con datos estructurados, como por ejemplo, cuando cada variable que se va a utilizar tiene una columna y cada observación tiene una fila. Crear visualizaciones precisas y eficientes será más sencillo si los datos están bien organizados.

Una vez escritos todos los códigos necesarios en R para cargar la información precisa del análisis que se deseaba en primera instancia, se debe de ejecutar el codigo ggplot(data =, mapping = aes ()) + que es útil para diferentes tipos de gráficos (). Según Nallar (n.d.) “usamos ggplot() y data para indicar a partir de qué datos se debe crear la gráfica. Luego, aes() (aesthetic) para seleccionar las variables a graficar y como presentarlas, e.g. ejes x e y o características como tamaño, forma, color, etc.”

Ejemplo 1:

ggplot(data = data, aes(x = shannon, y = evenness_camargo)) + geom_point()

Figura 1
Figura 1

Nallar, E. C., PhD. (n.d.). 6 Visualización de datos usando ggplot2 | Diseño experimental y análisis de datos. https://www.castrolab.org/teaching/data_analysis/visualizacion-de-datos-usando-ggplot2.html

Con respecto al grafico anterior se debe de saber que cualquier parámetro que se indique en esta función cuando configura un gráfico con ggplot(), se aplica a todas las capas del gráfico como se explicó anteriormente. Esto incluye mapeos estéticos como las variables para los ejes x e y que se definen con aes(). Por lo tanto, todas las capas (geoms) que se agreguen se considerarán cualquier ajuste hecho en ggplot(). Por otro lado, las funciones geométricas también se pueden utilizar para establecer parámetros específicos para cada capa. Estos parámetros solo afectan la capa en cuestión, sin alterar las configuraciones globales de ggplot(). Es de suma importancia tener en cuenta que el uso del signo + nos ayuda o permite agregar nuevas capas o cambiar las configuraciones de ggplot2. Como se puede observar con el ejemplo anterior, este símbolo se coloca al final de cada línea que contiene una función y se utiliza para encadenar varias funciones.

Ejemplo 2:

ggplot(data = data, aes(x = geo_loc_name, y = observed)) +
geom_boxplot()

Los boxplots o gráficos de caja son útiles para poder visualizar la distribución de los datos de acuerdo con una variable o condición de interés

Figura 2
Figura 2

Nallar, E. C., PhD. (n.d.). 6 Visualización de datos usando ggplot2 | Diseño experimental y análisis de datos. https://www.castrolab.org/teaching/data_analysis/visualizacion-de-datos-usando-ggplot2.html

Es importante saber que se puede utilizar geom_boxplot para generar un diagrama de cajas (box plot) en ggplot2 si tiene un frame de datos con una variable numérica. Este codigo nos ayuda específicamente a crear gráficos de caja que muestran la distribución de un conjunto de datos con medianas, cuartiles y valores posibles atípicos. Para hacerlo, al construir el gráfico, se debe especificar la variable numérica dentro de la función aes(), también conocida como mapeo estético. Un aspecto importante de tener en cuenta a la hora de graficar geom_boxplot() de ggplot2 es que según Coder, (2024) “alternativamente puedes establecer x =”“. Esto eliminará los valores del eje X y hará la caja más estrecha.” Estas y muchas alternativas existentes nos ayudan a crear el gráfico personalizada considerando todas nuestras preferencias respecto a cómo queremos que sea el resultado final.

Como se puede observar ggplot2 es una herramienta útil para la visualización de datos en R que permite crear gráficos complejos y personalizados a partir de marcos de datos. Su diseño, en el que cada columna representa una variable y cada fila una observación, destaca su capacidad para manejar datos estructurados de manera eficiente. La herramienta facilita la creación de visualizaciones precisas y efectivas al permitir la selección, representación y modificación de variables.

5. Histograma sobre las variables numéricas del dataset Iris

Se procede a graficar el histograma de cada una de las variables numéricas del dataset Iris usando ggplot2.

  1. Instalar paquetes.

  2. Install.packages(“ggplot2”).

  3. Install.packages(“tidyr”).

  4. Cargar las librerías necesarias.

tidyr se emplea para convertir el dataset iris a un formato largo (o “long format”), que es especialmente útil cuando se desea crear gráficos faceteados o comparar múltiples variables.

pivot_longer convierte el dataset iris de un formato ancho a un formato largo. En el código, se especifica que todas las columnas excepto ‘Species’ deben ser transformadas en dos columnas: Variable (contendrá el nombre de la variable original) y Value (contendrá los valores correspondientes).

  1. Graficar histogramas para cada variable numérica con personalizaciones.
ggplot(iris_long, aes(x = Value, fill = Species)) +  
  geom_histogram(binwidth = 0.3, color = "black", alpha = 0.7, position = "dodge") + 
  facet_wrap(~Variable, scales = "free_x") + 
  labs(
    title = "Variables numéricas dataset Iris", 
    x = "Valor", 
    y = "Frecuencia"
  ) + 
  theme_bw(base_size = 14, base_family = "Times New Roman") + 
  theme( plot.title = element_text(color = "purple", size = 16, face = "bold", hjust = 0.5),
    axis.title.x = element_text(color = "purple", size = 14, face = "bold"),
    axis.title.y = element_text(color = "purple", size = 14, face = "bold"),
    strip.background = element_rect(fill = "black"),
    strip.text = element_text(color = "white", size = 12, face = "bold")
  ) + 
  geom_density(aes(x = Value), color = "turquoise", size = 1, alpha = 0.6)

El resultado será una serie de histogramas que muestran la distribución de las cuatro variables numéricas en el dataset iris.


Conclusión

En conclusión, ggplot2 es una herramienta fundamental en el análisis y visualización de datos en R, debido a su estructura basada en capas y su gran versatilidad. Desde su creación, ha sido objeto de continuas mejoras, lo que ha permitido que siga siendo relevante tanto para usuarios novatos como para expertos en ciencia de datos. El dominio de su gramática de gráficos brinda a los analistas y científicos de datos una poderosa herramienta para explorar, visualizar y comunicar datos de manera efectiva.


Bibliografía

Berrendero, J. R. (n.d.). Una breve introducción a ggplot2. Universidad Autónoma de Madrid. https://verso.mat.uam.es/~joser.berrendero/R/introggplot2.html

Wickham, H. (2016). ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York.

Nallar, E. C., PhD. (n.d.). 6 Visualización de datos usando ggplot2 | Diseño experimental y análisis de datos. https://www.castrolab.org/teaching/data_analysis/visualizacion-de-datos-usando-ggplot2.html

Coder, R. (2024, January 4). Box plot en ggplot2. R CHARTS | Una Colección De Gráficos Hechos Con El Lenguaje De Programación R. https://r-charts.com/es/distribucion/box-plot-ggplot2/

LS0tDQp0aXRsZTogIkxpYnJlcsOtYSBnZ3Bsb3QyIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCiAgaHRtbF9kb2N1bWVudDogDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogIHdvcmRfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQoqKkFyY2hpdm8gcHVibGljYWRvOioqIDxodHRwczovL3JwdWJzLmNvbS9zZWJhc2NqLzEyMTU4MDU+DQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQoqKlRlY25vbMOzZ2ljbyBkZSBDb3N0YSBSaWNhKioNCg0KKipDdXJzbzoqKiBJbnRyb2R1Y2Npw7NuIGEgbGEgUHJvZ3JhbWFjacOzbiBjb24gUi4NCg0KKipUcmFiYWpvIGRlIEludmVzdGlnYWNpw7NuIDI6KiogTGlicmVyw61hIGdncGxvdDINCg0KKipQcm9mZXNvcjoqKiBMdWlzIEZlcm5hbmRvIENhc3Rybw0KDQoqKkVzdHVkaWFudGVzOioqDQoNCk1hcmljcnV6IFZhcmdhcyBSYW3DrXJleg0KDQpMYXVyYSBSb2Ryw61ndWV6IFZhcmdhcw0KDQpNYXLDrWEgR2FicmllbGEgU2V2ZXJpbm8gQ2FsZGVyw7NuDQoNClNlYmFzdGnDoW4gQ2FiZXphcyBKaW3DqW5leg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyAqKkludHJvZHVjY2nDs24qKg0KDQpDb21vIHBhcnRlIGRlIGxhIGludmVzdGlnYWNpw7NuIGEgbGEgbGlicmVyw61hIGdncGxvdDIgZGUgUiwgc2UgY29uc3VsdMOzIGVsIGFydMOtY3VsbyDigJxVbmEgYnJldmUgaW50cm9kdWNjacOzbiBhIGdncGxvdDLigJ0gZGUgSm9zw6kgUmFtw7NuIEJlcnJlbmRlcm8gKFVuaXZlcnNpZGFkIEF1dMOzbm9tYSBkZSBNYWRyaWQpLCBxdWllbiBpbnRyb2R1am8gZGUgbWFuZXJhIHB1bnR1YWwgeSBjbGFyYSBhIGVzdGEgbGlicmVyw61hLiBMb3MgcHVudG9zIGludHJvZHVjdG9yaW9zIHF1ZSBzZSBkYW4gYSBjb25vY2VyIGNvbiBlc3RlIGFydMOtY3Vsbywgc2lydmVuIHBhcmEgZGVzY29tcG9uZXIgZWwgYW7DoWxpc2lzIHkgc2UgZXhwb25lIGEgY29udGludWFjacOzbi4NCg0KTGEgbGlicmVyw61hIGdncGxvdDIgZGUgUiBlcyB1biBzaXN0ZW1hIG9yZ2FuaXphZG8gZGUgdmlzdWFsaXphY2nDs24gZGUgZGF0b3MuIEZvcm1hIHBhcnRlIGRlbCBjb25qdW50byBkZSBsaWJyZXLDrWFzIGxsYW1hZG8gdGlkeXZlcnNlLiBFbiBlc3RlIGRvY3VtZW50byBzZSBpbnRyb2R1Y2Ugc3UgdXNvLCBwcmluY2lwYWxtZW50ZSBhIHRyYXbDqXMgZGUgZWplbXBsb3MuDQoNCkxvcyBlbGVtZW50b3MgbmVjZXNhcmlvcyBwYXJhIHJlcHJlc2VudGFyIHVuIGdyw6FmaWNvIGNvbiBnZ3Bsb3QyIHNvbiBsb3Mgc2lndWllbnRlczoNCg0KLSAgIFVuIGRhdGEgZnJhbWUgcXVlIGNvbnRpZW5lIGxvcyBkYXRvcyBxdWUgc2UgcXVpZXJlbiB2aXN1YWxpemFyLg0KDQotICAgTG9zIGFlc3RoZXRpY3MsIGVzIGRlY2lyLCB1bmEgbGlzdGEgZGUgcmVsYWNpb25lcyBlbnRyZSBsYXMgdmFyaWFibGVzIGRlbCBmaWNoZXJvIGRlIGRhdG9zIHkgZGV0ZXJtaW5hZG9zIGFzcGVjdG9zIGRlbCBncsOhZmljbyAoY29tbyBwb3IgZWplbXBsbyBjb29yZGVuYWRhcywgZm9ybWFzIG8gY29sb3JlcykuDQoNCi0gICBMb3MgZ2VvbXMsIHF1ZSBlc3BlY2lmaWNhbiBsb3MgZWxlbWVudG9zIGdlb23DqXRyaWNvcyAocHVudG9zLCBsw61uZWFzLCBjw61yY3Vsb3MsIGV0Yy4pIHF1ZSBzZSB2YW4gYSByZXByZXNlbnRhci4NCg0KTm9ybWFsbWVudGUgZXN0b3MgZWxlbWVudG9zIHNlIHZhbiBhw7FhZGllbmRvIGRlIGZvcm1hIGNvbnNlY3V0aXZhIGVuIGRpc3RpbnRhcyBjYXBhcyAobGF5ZXJzKS4gUGFyYSBhw7FhZGlyIHVuYSBudWV2YSBjYXBhIHNlIHVzYSBlbCBzaWdubyArLiBMYSBlc3RydWN0dXJhIGdlbmVyYWwgZGVsIGPDs2RpZ28gcGFyYSBvYnRlbmVyIHVuIGdyw6FmaWNvIGVzIGVzdGE6DQoNCmBgYCByDQpnZ3Bsb3QoZGF0YSA9ICdub21icmUgZGVsIGZpY2hlcm8gZGUgZGF0b3MnKSArIGdlb21fbm9tYnJlMShhZXMoYWVzdGhldGljczE9dmFyMSwgYWVzdGhldGljczI9dmFyMiwgLi4uKSkgKyBnZW9tX25vbWJyZTIoLi4uKQ0KYGBgDQoNCkVsIGNvbWFuZG8gZ2dwbG90IHNlIHVzYSBwYXJhIGdlbmVyYXIgZWwgc2lzdGVtYSBkZSBjb29yZGVuYWRhcyAocG9yIGRlZmVjdG8sIHJlY3Rhbmd1bGFyZXMpIHkgcG9zdGVyaW9ybWVudGUgdmFtb3MgYcOxYWRpZW5kbyBsb3MgZ2VvbXMgY29uIHN1cyBjb3JyZXNwb25kaWVudGVzIGFlc3RoZXRpY3MuIEVuIHByaW5jaXBpbyBsb3MgYWVzdGhldGljcyBzZSBwdWVkZW4gYXNpZ25hciBpbmRpdmlkdWFsbWVudGUgcGFyYSBjYWRhIGdlb20uDQoNCkNvbnNpZGVyYW5kbyBlc3RvcyBlbGVtZW50b3MgZXMgcXVlIGluaWNpYXJlbW9zIG51ZXN0cmEgaW52ZXN0aWdhY2nDs24uIFNlIHZhbiBhIGNvbnRlbXBsYXIgdmFyaW9zIHB1bnRvcyBzb2JyZSBsYSBsaWJyZXLDrWEgZ2dwbG90MiwgY29tbyBzdSBoaXN0b3JpYSwgZnVuY2lvbmVzIHByaW5jaXBhbGVzLCBkZXNjcmlwY2nDs24gZGUgbGFzIGNhcGFzIHF1ZSBjb25mb3JtYW4gdW4gZ3LDoWZpY28geSBzdSB1dGlsaXphY2nDs24sIGVqZW1wbG9zIGRlIHVzbywgeSBncmFmaWNhcmVtb3MgZWwgaGlzdG9ncmFtYSBkZSBjYWRhIHVuYSBkZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMgZGVsIGRhdGFzZXQgSXJpcy4NCg0KLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCiMgKipEZXNhcnJvbGxvKioNCg0KTGEgbGlicmVyw61hIGdncGxvdDIgc2UgaGEgY29uc29saWRhZG8gY29tbyB1bmEgZGUgbGFzIGxpYnJlcsOtYXMgbcOhcyByb2J1c3RhcyB5IGFtcGxpYW1lbnRlIHV0aWxpemFkYXMgcGFyYSBsYSB2aXN1YWxpemFjacOzbiBkZSBkYXRvcyBlbiBlbCBsZW5ndWFqZSBkZSBwcm9ncmFtYWNpw7NuIFIuIEZvcm1hbmRvIHBhcnRlIGludGVncmFsIGRlbCBlY29zaXN0ZW1hIHRpZHl2ZXJzZSwgZXN0YSBoZXJyYW1pZW50YSBvZnJlY2UgYSBsb3MgdXN1YXJpb3MgbGEgcG9zaWJpbGlkYWQgZGUgY3JlYXIgZ3LDoWZpY29zIGNvbXBsZWpvcyBkZSBmb3JtYSBpbnR1aXRpdmEgeSBlZmljaWVudGUuIEVuIGVzdGEgaW52ZXN0aWdhY2nDs24sIHNlIGFib3JkYXLDoSBsYSBoaXN0b3JpYSBkZSBnZ3Bsb3QyLCBzdXMgZnVuY2lvbmVzIG3DoXMgZGVzdGFjYWRhcywgbGEgZXN0cnVjdHVyYSBkZSBjYXBhcyBxdWUgY29tcG9uZSB1biBncsOhZmljbyB5IHNlIHByb3BvcmNpb25hcsOhbiBlamVtcGxvcyBwcsOhY3RpY29zIHBhcmEgZGVtb3N0cmFyIHN1IHV0aWxpZGFkIGVuIGVsIGNvbnRleHRvIGRlbCBhbsOhbGlzaXMgZGUgZGF0b3MuDQoNCiMjICoqMS4gSGlzdG9yaWEgZGUgbGEgbGlicmVyw61hKioNCg0KTGEgbGlicmVyw61hIGdncGxvdDIgZnVlIGRlc2Fycm9sbGFkYSBwb3IgSGFkbGV5IFdpY2toYW0gZW4gMjAwNSBkdXJhbnRlIHN1cyBlc3R1ZGlvcyBkZSBkb2N0b3JhZG8gZW4gRXN0YWTDrXN0aWNhLiBXaWNraGFtIHNlIGluc3BpcsOzIGVuIGVsIGNvbmNlcHRvIGRlIGxhIEdyYW3DoXRpY2EgZGUgbG9zIEdyw6FmaWNvcyAoIkdyYW1tYXIgb2YgR3JhcGhpY3MiKSBkZSBMZWxhbmQgV2lsa2luc29uLCB1bmEgdGVvcsOtYSBxdWUgc2lzdGVtYXRpemEgbGEgY29uc3RydWNjacOzbiBkZSBncsOhZmljb3MgZXN0YWTDrXN0aWNvcyBtZWRpYW50ZSBsYSBkaXZpc2nDs24gZW4gY29tcG9uZW50ZXMgZnVuZGFtZW50YWxlcywgdGFsZXMgY29tbyBkYXRvcywgZ2VvbWV0csOtYXMgeSBlc3TDqXRpY2FzLiBFc3RhIGdyYW3DoXRpY2EgYXBvcnRhIHVuIG1hcmNvIGNvaGVyZW50ZSBwYXJhIGxhIGNyZWFjacOzbiB5IGRlc2NyaXBjacOzbiBkZSB2aXN1YWxpemFjaW9uZXMsIGxvIHF1ZSBwZXJtaXRpw7MgYSBXaWNraGFtIGNvbmNlYmlyIGdncGxvdDIgbm8gc29sbyBjb21vIHVuYSBoZXJyYW1pZW50YSBwYXJhIGdlbmVyYXIgZ3LDoWZpY29zLCBzaW5vIHRhbWJpw6luIGNvbW8gdW4gbWVkaW8gcGFyYSBtZWpvcmFyIGxhIGNvbXByZW5zacOzbiB5IGNvbXVuaWNhY2nDs24gZGUgZGF0b3MgYSB0cmF2w6lzIGRlIHZpc3VhbGl6YWNpb25lcyBjbGFyYXMgeSBlZmVjdGl2YXMgKFdpY2toYW0sIDIwMTYpLg0KDQpEZXNkZSBzdSBsYW56YW1pZW50byBpbmljaWFsLCBnZ3Bsb3QyIGhhIGV2b2x1Y2lvbmFkbyBoYXN0YSBjb252ZXJ0aXJzZSBlbiB1biBlc3TDoW5kYXIgcGFyYSBsYSB2aXN1YWxpemFjacOzbiBkZSBkYXRvcyBlbiBSLCBhcHJlY2lhZGEgcG9yIHN1IGZsZXhpYmlsaWRhZCwgY2FwYWNpZGFkIGRlIHBlcnNvbmFsaXphY2nDs24geSBlbCBhcG95byBkZSB1bmEgY29tdW5pZGFkIGFjdGl2YS4gQWwgc2VyIHVuYSBsaWJyZXLDrWEgZGUgY8OzZGlnbyBhYmllcnRvLCBoYSBleHBlcmltZW50YWRvIG51bWVyb3NhcyBhY3R1YWxpemFjaW9uZXMgeSBleHBhbnNpb25lcywgbWFudGVuaWVuZG8gc3UgcmVsZXZhbmNpYSB0YW50byBlbiDDoW1iaXRvcyBhY2Fkw6ltaWNvcyBjb21vIGVuIGVsIHNlY3RvciBpbmR1c3RyaWFsLg0KDQojIyAqKjIuIEZ1bmNpb25lcyBwcmluY2lwYWxlcyoqDQoNCkxhIGZpbG9zb2bDrWEgZGUgZ2dwbG90MiBzZSBmdW5kYW1lbnRhIGVuIGxhIGdyYW3DoXRpY2EgZGUgbG9zIGdyw6FmaWNvcywgbG8gcXVlIGltcGxpY2EgcXVlIGxvcyBncsOhZmljb3Mgc2UgY29uc3RydXllbiBtZWRpYW50ZSBsYSBjb21iaW5hY2nDs24gZGUgY2FwYXMgc3VjZXNpdmFzLCBxdWUgaW5jbHV5ZW4gZGF0b3MsIGdlb21ldHLDrWFzIChnZW9tcyksIGVzdMOpdGljYXMgKGFlc3RoZXRpY3MpLCBmYWNldGFzLCB5IG90cm9zIGVsZW1lbnRvcy4gQSBjb250aW51YWNpw7NuLCBzZSBkZXRhbGxhbiBhbGd1bmFzIGRlIGxhcyBmdW5jaW9uZXMgbcOhcyByZXByZXNlbnRhdGl2YXMgZGUgbGEgbGlicmVyw61hOg0KDQoxLiAgZ2dwbG90KCk6IEVzIGxhIGZ1bmNpw7NuIGNlbnRyYWwgdXRpbGl6YWRhIHBhcmEgaW5pY2lhciB1biBncsOhZmljbywgZXN0YWJsZWNpZW5kbyBlbCBjb25qdW50byBkZSBkYXRvcyB5IGxhcyBlc3TDqXRpY2FzIGdsb2JhbGVzLCBjb21vIGxvcyBlamVzIHggZSB5Lg0KDQpgYGB7cn0NCmdncGxvdChkYXRhID0gbXRjYXJzLCBhZXMoeCA9IHd0LCB5ID0gbXBnKSkNCmBgYA0KDQoyLiAgZ2VvbVxfXCo6IExhcyBmdW5jaW9uZXMgZ2VvbcOpdHJpY2FzLCBjb21vIGdlb21fcG9pbnQoKSwgZ2VvbV9saW5lKCksIGdlb21fYmFyKCksIGV0Yy4sIHNlIGVtcGxlYW4gcGFyYSBhw7FhZGlyIGVsZW1lbnRvcyB2aXN1YWxlcyBxdWUgcmVwcmVzZW50YW4gbG9zIGRhdG9zLiBDYWRhIGZ1bmNpw7NuIGdlb23DqXRyaWNhIHByb2R1Y2UgdW5hIGZvcm1hIHZpc3VhbCBlc3BlY8OtZmljYSwgY29tbyBwdW50b3MsIGzDrW5lYXMgbyBiYXJyYXMuDQoNCmBgYHtyfQ0KZ2dwbG90KG10Y2FycywgYWVzKHggPSB3dCwgeSA9IG1wZykpICsgZ2VvbV9wb2ludCgpDQpgYGANCg0KMy4gIGFlcygpOiBMYSBmdW5jacOzbiBhZXMoKSBzZSB1dGlsaXphIHBhcmEgYXNpZ25hciB2YXJpYWJsZXMgZGVsIGNvbmp1bnRvIGRlIGRhdG9zIGEgcHJvcGllZGFkZXMgdmlzdWFsZXMgZGVsIGdyw6FmaWNvLCBjb21vIGxhIHBvc2ljacOzbiBlbiBsb3MgZWplcywgZWwgY29sb3IsIGVsIHRhbWHDsW8sIGV0Yy4NCg0KYGBge3J9DQpnZ3Bsb3QobXRjYXJzLCBhZXMoeCA9IHd0LCB5ID0gbXBnLCBjb2xvciA9IGZhY3RvcihjeWwpKSkgKyBnZW9tX3BvaW50KCkNCmBgYA0KDQo0LiAgZmFjZXRcX1wqOiBMYXMgZnVuY2lvbmVzIGRlIGZhY2V0YWRvLCBjb21vIGZhY2V0X3dyYXAoKSB5IGZhY2V0X2dyaWQoKSwgcGVybWl0ZW4gY3JlYXIgc3ViZ3LDoWZpY29zIGRlbnRybyBkZSB1biBncsOhZmljbyBwcmluY2lwYWwsIGJhc2Fkb3MgZW4gbG9zIHZhbG9yZXMgZGUgdW5hIG8gbcOhcyB2YXJpYWJsZXMsIGxvIGN1YWwgZXMgw7p0aWwgcGFyYSBjb21wYXJhciBkaWZlcmVudGVzIHN1YmNvbmp1bnRvcyBkZSBkYXRvcy4NCg0KYGBge3J9DQpnZ3Bsb3QobXRjYXJzLCBhZXMoeCA9IHd0LCB5ID0gbXBnKSkgKyBnZW9tX3BvaW50KCkgKyBmYWNldF93cmFwKFx+IGN5bCkNCmBgYA0KDQo1LiAgbGFicygpIHkgZ2d0aXRsZSgpOiBFc3RhcyBmdW5jaW9uZXMgc29uIHV0aWxpemFkYXMgcGFyYSBhw7FhZGlyIGV0aXF1ZXRhcyB5IHTDrXR1bG9zIGEgbG9zIGdyw6FmaWNvcywgbWVqb3JhbmRvIGFzw60gc3UgY2xhcmlkYWQgeSBwcmVzZW50YWNpw7NuLg0KDQpgYGB7cn0NCmdncGxvdChtdGNhcnMsIGFlcyh4ID0gd3QsIHkgPSBtcGcpKSArIGdlb21fcG9pbnQoKSArIGdndGl0bGUoIlJlbGFjacOzbiBlbnRyZSBQZXNvIHkgQ29uc3VtbyBkZSBDb21idXN0aWJsZSIpDQpgYGANCg0KNi4gIHRoZW1lKCk6IExhIGZ1bmNpw7NuIHRoZW1lKCkgb2ZyZWNlIGxhIHBvc2liaWxpZGFkIGRlIHBlcnNvbmFsaXphciBsYSBhcGFyaWVuY2lhIGRlbCBncsOhZmljbywgcGVybWl0aWVuZG8gbW9kaWZpY2FyIGVsZW1lbnRvcyBjb21vIGxvcyBlaiBlcywgbG9zIHRleHRvcyB5IGVsIGZvbmRvLg0KDQpgYGB7cn0NCmdncGxvdChtdGNhcnMsIGFlcyh4ID0gd3QsIHkgPSBtcGcpKSArIGdlb21fcG9pbnQoKSArIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCjcuICBzY2FsZVxfXCooKTogQWp1c3RhIGxhcyBlc2NhbGFzIHkgbWFwZW9zIGVzdMOpdGljb3MsIGNvbW8gY29sb3JlcywgdGFtYcOxb3MsIHkgZm9ybWEuDQoNCjguICBjb29yZFxfXCooKTogTW9kaWZpY2EgZWwgc2lzdGVtYSBkZSBjb29yZGVuYWRhcyBkZWwgZ3LDoWZpY28uIMOadGlsIHBhcmEgY2FtYmlhciBsYSBwZXJzcGVjdGl2YSwgYWp1c3RhciBsYSBwcm9wb3JjacOzbiwgbyB0cmFuc2Zvcm1hciBlamVzLg0KDQpFc3RhcyBmdW5jaW9uZXMgcGVybWl0ZW4gY29uc3RydWlyIGdyw6FmaWNvcyBkZXNkZSBsbyBtw6FzIHNpbXBsZSBoYXN0YSBsbyBtw6FzIGNvbXBsZWpvLCBwZXJzb25hbGl6YW5kbyBjYWRhIGFzcGVjdG8gZGVsIGdyw6FmaWNvIHBhcmEgYWp1c3RhcmxvIGEgbGFzIG5lY2VzaWRhZGVzIGRlIGxhIHZpc3VhbGl6YWNpw7NuLg0KDQojIyAqKjMuIERlc2NyaXBjacOzbiBkZSBsYXMgY2FwYXMgcXVlIGNvbmZvcm1hbiB1biBncsOhZmljbyB5IHN1IHV0aWxpemFjacOzbioqDQoNCkxhIGNvbnN0cnVjY2nDs24gZGUgZ3LDoWZpY29zIGVuIGdncGxvdDIgc2UgYmFzYSBlbiBsYSBhZGljacOzbiBkZSBtw7psdGlwbGVzIGNhcGFzLCBjYWRhIHVuYSBkZSBsYXMgY3VhbGVzIGNvbnRyaWJ1eWUgY29uIHVuIGVsZW1lbnRvIGVzcGVjw61maWNvIGFsIGdyw6FmaWNvIGZpbmFsOg0KDQotICAgKipDYXBhIGRlIERhdG9zIChkYXRhKToqKiBFc3RhIGNhcGEgY29ycmVzcG9uZGUgYWwgY29uanVudG8gZGUgZGF0b3MgdXRpbGl6YWRvIHBhcmEgY29uc3RydWlyIGVsIGdyw6FmaWNvLiBTZSBlc3BlY2lmaWNhIGVuIGxhIGZ1bmNpw7NuIGdncGxvdCgpIG8gZGlyZWN0YW1lbnRlIGVuIGxhcyBmdW5jaW9uZXMgZ2VvbcOpdHJpY2FzLg0KDQotICAgKipDYXBhIGRlIEVzdMOpdGljYSAoYWVzKToqKiBEZXRlcm1pbmEgY8OzbW8gc2UgYXNpZ25hbiBsYXMgdmFyaWFibGVzIGEgbGFzIHByb3BpZWRhZGVzIHZpc3VhbGVzIGRlbCBncsOhZmljbywgdGFsZXMgY29tbyBsYSBwb3NpY2nDs24gKGVqZXMgeCBlIHkpLCBlbCBjb2xvciwgZWwgdGFtYcOxbywgbGEgZm9ybWEsIGV0Yy4gRXN0YSBjYXBhIHNlIGRlZmluZSBkZW50cm8gZGUgZ2dwbG90KCkgbyBzZSBwdWVkZSBhw7FhZGlyIGEgbml2ZWwgZGUgY2FwYSBnZW9tw6l0cmljYS4NCg0KLSAgICoqQ2FwYSBHZW9tw6l0cmljYSAoZ2VvbSk6KiogUmVwcmVzZW50YSBsYSBmb3JtYSB2aXN1YWwgZGUgbG9zIGRhdG9zIGVuIGVsIGdyw6FmaWNvLiBDYWRhIHRpcG8gZGUgZ3LDoWZpY28gKHB1bnRvcywgYmFycmFzLCBsw61uZWFzLCBldGMuKSBjb3JyZXNwb25kZSBhIHVuYSBmdW5jacOzbiBnZW9tw6l0cmljYSBlc3BlY8OtZmljYSAoZ2VvbV9wb2ludCgpLCBnZW9tX2JhcigpLCBnZW9tX2xpbmUoKSwgZXRjLikuDQoNCi0gICAqKkNhcGEgZGUgRXNjYWxhcyAoc2NhbGVcXyk6KiogQWp1c3RhIGPDs21vIHNlIHJlcHJlc2VudGFuIGxhcyBhc2lnbmFjaW9uZXMgZXN0w6l0aWNhcywgY29tbyBsYSBlc2NhbGEgZGUgY29sb3JlcywgdGFtYcOxb3MsIG8gZWplcy4gUGVybWl0ZSBwZXJzb25hbGl6YXIgbGEgdHJhbnNmb3JtYWNpw7NuIHkgbG9zIGzDrW1pdGVzIGRlIGxvcyBkYXRvcyB2aXN1YWxpemFkb3MNCg0KLSAgICoqQ2FwYSBFc3RhZMOtc3RpY2EgKHN0YXQpOioqIGdncGxvdDIgaW5jb3Jwb3JhIGZ1bmNpb25lcyBlc3RhZMOtc3RpY2FzIHByZWRldGVybWluYWRhcyBxdWUgcGVybWl0ZW4gY2FsY3VsYXIgeSByZXByZXNlbnRhciB2aXN1YWxtZW50ZSBlc3RhZMOtc3RpY2FzIHNvYnJlIGxvcyBkYXRvcy4gQXVucXVlIG11Y2hhcyBmdW5jaW9uZXMgZ2VvbcOpdHJpY2FzIHRpZW5lbiB1bmEgZnVuY2nDs24gZXN0YWTDrXN0aWNhIHBvciBkZWZlY3RvLCBlc3RhIHB1ZWRlIHNlciBtb2RpZmljYWRhIHNlZ8O6biBsYXMgbmVjZXNpZGFkZXMuDQoNCi0gICAqKkNhcGEgZGUgRmFjZXRhZG8gKGZhY2V0KToqKiBGYWNpbGl0YSBsYSBjcmVhY2nDs24gZGUgZ3LDoWZpY29zIGRpdmlkaWRvcyBlbiBtw7psdGlwbGVzIHBhbmVsZXMgc2Vnw7puIGxvcyB2YWxvcmVzIGRlIHVuYSBvIG3DoXMgdmFyaWFibGVzLCBsbyBxdWUgYXl1ZGEgYSBjb21wYXJhciBkaXN0aW50b3Mgc3ViY29uanVudG9zIGRlIGRhdG9zLg0KDQotICAgKipDYXBhIGRlIENvb3JkZW5hZGFzIChjb29yZCk6KiogRGVmaW5lIGVsIHNpc3RlbWEgZGUgY29vcmRlbmFkYXMgZGVsIGdyw6FmaWNvLiBFbCBzaXN0ZW1hIHByZWRldGVybWluYWRvIGVzIGVsIGNhcnRlc2lhbm8gKGNvb3JkX2NhcnRlc2lhbigpKSwgYXVucXVlIHRhbWJpw6luIHNlIHB1ZWRlbiB1c2FyIGNvb3JkZW5hZGFzIHBvbGFyZXMgKGNvb3JkX3BvbGFyKCkpLCBlbnRyZSBvdHJhcyBvcGNpb25lcy4NCg0KLSAgICoqQ2FwYSBkZSBFdGlxdWV0YXMgeSBUw610dWxvcyAobGFicygpIHkgZ2d0aXRsZSgpKToqKiBBw7FhZGUgbyBtb2RpZmljYSBsYXMgZXRpcXVldGFzIGRlbCBncsOhZmljbywgY29tbyBsb3MgdMOtdHVsb3MgZGUgbG9zIGVqZXMsIGVsIHTDrXR1bG8gcHJpbmNpcGFsIGRlbCBncsOhZmljbywgeSBsYXMgZXRpcXVldGFzIGRlIGxhcyBsZXllbmRhcy4NCg0KLSAgICoqQ2FwYSBkZSBUZW1hICh0aGVtZSk6KiogUGVybWl0ZSBsYSBwZXJzb25hbGl6YWNpw7NuIGRlIGVsZW1lbnRvcyBkZWwgZ3LDoWZpY28gcXVlIG5vIGVzdMOhbiByZWxhY2lvbmFkb3MgZGlyZWN0YW1lbnRlIGNvbiBsb3MgZGF0b3MsIGNvbW8gZm9uZG9zLCBib3JkZXMgeSB0ZXh0b3MgZGUgbG9zIGVqZXMuIEV4aXN0ZW4gdGVtYXMgcHJlZGVmaW5pZG9zIGNvbW8gdGhlbWVfbWluaW1hbCgpIG8gdGhlbWVfY2xhc3NpYygpLCBxdWUgb2ZyZWNlbiBlc3RpbG9zIGdyw6FmaWNvcyBxdWUgcHVlZGVuIGFqdXN0YXJzZSBhw7puIG3DoXMuDQoNCiMjIyAqKlJlc3VtZW4gZGVsIFByb2Nlc28qKg0KDQpBbCBjb25zdHJ1aXIgdW4gZ3LDoWZpY28gZW4gZ2dwbG90MiwgZ2VuZXJhbG1lbnRlIHNpZ3VlcyBlc3RlIHByb2Nlc286DQoNCjEuICBEZWZpbmUgbG9zIGRhdG9zIHkgbGFzIGFzaWduYWNpb25lcyBlc3TDqXRpY2FzIChnZ3Bsb3QoKSB5IGFlcygpKS4NCg0KMi4gIEHDsWFkZSBnZW9tZXRyw61hcyBwYXJhIHZpc3VhbGl6YXIgbG9zIGRhdG9zIChnZW9tXF9cKikuDQoNCjMuICBBanVzdGEgZXNjYWxhcyB5IGNvb3JkZW5hZGFzIHNpIGVzIG5lY2VzYXJpbyAoc2NhbGVcXyosIGNvb3JkXF8qKS4NCg0KNC4gIEHDsWFkZSBmYWNldGFzIHNpIGRlc2VhcyBkaXZpZGlyIGVsIGdyw6FmaWNvIGVuIG3Dumx0aXBsZXMgc3ViZ3LDoWZpY29zIChmYWNldFxfXCopLg0KDQo1LiAgUGVyc29uYWxpemEgZXRpcXVldGFzIHkgdMOtdHVsb3MgKGxhYnMoKSkuDQoNCjYuICBBanVzdGEgZWwgdGVtYSBkZWwgZ3LDoWZpY28gKHRoZW1lXF9cKikuDQoNCkVzdGFzIGNhcGFzIHB1ZWRlbiBzZXIgY29tYmluYWRhcyBkZSBkaWZlcmVudGVzIG1hbmVyYXMgcGFyYSBjcmVhciBncsOhZmljb3MgYWx0YW1lbnRlIHBlcnNvbmFsaXphZG9zIHkgY29tcGxlam9zIGVuIGdncGxvdDIuDQoNCiMjICoqNC4gRWplbXBsb3MgZGUgdXNvKioNCg0KRXN0YSBoZXJyYW1pZW50YSBkZSB2aXN1YWxpemFjacOzbiBmYWNpbGl0YSBsYSBjcmVhY2nDs24gZGUgZ3LDoWZpY29zIGNvbXBsZWpvcyBhIHBhcnRpciBkZSBkYXRvcyBhbG1hY2VuYWRvcyBlbiBtYXJjb3MgZGUgZGF0b3MuIERpY2hhIGhlcnJhbWllbnRhLCB0aWVuZSB1bmEgZ3JhbiB2YXJpZWRhZCBkZSBmdW5jaW9uZXMgcGFyYSB1dGlsaXphciB5IHNlbGVjY2lvbmFyIGxhcyB2YXJpYWJsZXMgYSByZXByZXNlbnRhciB5IGFqdXN0YXIgbGEgYXBhcmllbmNpYSBkZSBsb3MgZ3LDoWZpY29zLiBFc3RlIHBhcXVldGUgZXMgZWZlY3Rpdm8gZW4gc2l0dWFjaW9uZXMgcXVlIHNlIHRyYWJhamEgY29uIGRhdG9zIGVzdHJ1Y3R1cmFkb3MsIGNvbW8gcG9yIGVqZW1wbG8sIGN1YW5kbyBjYWRhIHZhcmlhYmxlIHF1ZSBzZSB2YSBhIHV0aWxpemFyIHRpZW5lIHVuYSBjb2x1bW5hIHkgY2FkYSBvYnNlcnZhY2nDs24gdGllbmUgdW5hIGZpbGEuIENyZWFyIHZpc3VhbGl6YWNpb25lcyBwcmVjaXNhcyB5IGVmaWNpZW50ZXMgc2Vyw6EgbcOhcyBzZW5jaWxsbyBzaSBsb3MgZGF0b3MgZXN0w6FuIGJpZW4gb3JnYW5pemFkb3MuDQoNClVuYSB2ZXogZXNjcml0b3MgdG9kb3MgbG9zIGPDs2RpZ29zIG5lY2VzYXJpb3MgZW4gUiBwYXJhIGNhcmdhciBsYSBpbmZvcm1hY2nDs24gcHJlY2lzYSBkZWwgYW7DoWxpc2lzIHF1ZSBzZSBkZXNlYWJhIGVuIHByaW1lcmEgaW5zdGFuY2lhLCBzZSBkZWJlIGRlIGVqZWN1dGFyIGVsIGNvZGlnbyBnZ3Bsb3QoZGF0YSA9LCBtYXBwaW5nID0gYWVzICgpKSArIHF1ZSBlcyDDunRpbCBwYXJhIGRpZmVyZW50ZXMgdGlwb3MgZGUgZ3LDoWZpY29zICgpLiBTZWfDum4gTmFsbGFyIChuLmQuKSDigJx1c2Ftb3MgZ2dwbG90KCkgeSBkYXRhIHBhcmEgaW5kaWNhciBhIHBhcnRpciBkZSBxdcOpIGRhdG9zIHNlIGRlYmUgY3JlYXIgbGEgZ3LDoWZpY2EuIEx1ZWdvLCBhZXMoKSAoYWVzdGhldGljKSBwYXJhIHNlbGVjY2lvbmFyIGxhcyB2YXJpYWJsZXMgYSBncmFmaWNhciB5IGNvbW8gcHJlc2VudGFybGFzLCBlLmcuIGVqZXMgeCBlIHkgbyBjYXJhY3RlcsOtc3RpY2FzIGNvbW8gdGFtYcOxbywgZm9ybWEsIGNvbG9yLCBldGMu4oCdDQoNCioqRWplbXBsbyAxOioqDQoNCipnZ3Bsb3QoZGF0YSA9IGRhdGEsIGFlcyh4ID0gc2hhbm5vbiwgeSA9IGV2ZW5uZXNzX2NhbWFyZ28pKSArIGdlb21fcG9pbnQoKSoNCg0KIVsqKkZpZ3VyYSAxKipdKEZpZ3VyYSUyMDEucG5nKQ0KDQpOYWxsYXIsIEUuIEMuLCBQaEQuIChuLmQuKS4gNiBWaXN1YWxpemFjacOzbiBkZSBkYXRvcyB1c2FuZG8gZ2dwbG90MiBcfCBEaXNlw7FvIGV4cGVyaW1lbnRhbCB5IGFuw6FsaXNpcyBkZSBkYXRvcy4gPGh0dHBzOi8vd3d3LmNhc3Ryb2xhYi5vcmcvdGVhY2hpbmcvZGF0YV9hbmFseXNpcy92aXN1YWxpemFjaW9uLWRlLWRhdG9zLXVzYW5kby1nZ3Bsb3QyLmh0bWw+DQoNCkNvbiByZXNwZWN0byBhbCBncmFmaWNvIGFudGVyaW9yIHNlIGRlYmUgZGUgc2FiZXIgcXVlIGN1YWxxdWllciBwYXLDoW1ldHJvIHF1ZSBzZSBpbmRpcXVlIGVuIGVzdGEgZnVuY2nDs24gY3VhbmRvIGNvbmZpZ3VyYSB1biBncsOhZmljbyBjb24gZ2dwbG90KCksIHNlIGFwbGljYSBhIHRvZGFzIGxhcyBjYXBhcyBkZWwgZ3LDoWZpY28gY29tbyBzZSBleHBsaWPDsyBhbnRlcmlvcm1lbnRlLiBFc3RvIGluY2x1eWUgbWFwZW9zIGVzdMOpdGljb3MgY29tbyBsYXMgdmFyaWFibGVzIHBhcmEgbG9zIGVqZXMgeCBlIHkgcXVlIHNlIGRlZmluZW4gY29uIGFlcygpLiBQb3IgbG8gdGFudG8sIHRvZGFzIGxhcyBjYXBhcyAoZ2VvbXMpIHF1ZSBzZSBhZ3JlZ3VlbiBzZSBjb25zaWRlcmFyw6FuIGN1YWxxdWllciBhanVzdGUgaGVjaG8gZW4gZ2dwbG90KCkuIFBvciBvdHJvIGxhZG8sIGxhcyBmdW5jaW9uZXMgZ2VvbcOpdHJpY2FzIHRhbWJpw6luIHNlIHB1ZWRlbiB1dGlsaXphciBwYXJhIGVzdGFibGVjZXIgcGFyw6FtZXRyb3MgZXNwZWPDrWZpY29zIHBhcmEgY2FkYSBjYXBhLiBFc3RvcyBwYXLDoW1ldHJvcyBzb2xvIGFmZWN0YW4gbGEgY2FwYSBlbiBjdWVzdGnDs24sIHNpbiBhbHRlcmFyIGxhcyBjb25maWd1cmFjaW9uZXMgZ2xvYmFsZXMgZGUgZ2dwbG90KCkuIEVzIGRlIHN1bWEgaW1wb3J0YW5jaWEgdGVuZXIgZW4gY3VlbnRhIHF1ZSBlbCB1c28gZGVsIHNpZ25vICsgbm9zIGF5dWRhIG8gcGVybWl0ZSBhZ3JlZ2FyIG51ZXZhcyBjYXBhcyBvIGNhbWJpYXIgbGFzIGNvbmZpZ3VyYWNpb25lcyBkZSBnZ3Bsb3QyLiBDb21vIHNlIHB1ZWRlIG9ic2VydmFyIGNvbiBlbCBlamVtcGxvIGFudGVyaW9yLCBlc3RlIHPDrW1ib2xvIHNlIGNvbG9jYSBhbCBmaW5hbCBkZSBjYWRhIGzDrW5lYSBxdWUgY29udGllbmUgdW5hIGZ1bmNpw7NuIHkgc2UgdXRpbGl6YSBwYXJhIGVuY2FkZW5hciB2YXJpYXMgZnVuY2lvbmVzLg0KDQoqKkVqZW1wbG8gMjoqKg0KDQoqZ2dwbG90KGRhdGEgPSBkYXRhLCBhZXMoeCA9IGdlb19sb2NfbmFtZSwgeSA9IG9ic2VydmVkKSkgK1wNCmdlb21fYm94cGxvdCgpKg0KDQpMb3MgYm94cGxvdHMgbyBncsOhZmljb3MgZGUgY2FqYSBzb24gw7p0aWxlcyBwYXJhIHBvZGVyIHZpc3VhbGl6YXIgbGEgZGlzdHJpYnVjacOzbiBkZSBsb3MgZGF0b3MgZGUgYWN1ZXJkbyBjb24gdW5hIHZhcmlhYmxlIG8gY29uZGljacOzbiBkZSBpbnRlcsOpcw0KDQohWyoqRmlndXJhIDIqKl0oRmlndXJhJTIwMi5wbmcpDQoNCk5hbGxhciwgRS4gQy4sIFBoRC4gKG4uZC4pLiA2IFZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zIHVzYW5kbyBnZ3Bsb3QyIFx8IERpc2XDsW8gZXhwZXJpbWVudGFsIHkgYW7DoWxpc2lzIGRlIGRhdG9zLiA8aHR0cHM6Ly93d3cuY2FzdHJvbGFiLm9yZy90ZWFjaGluZy9kYXRhX2FuYWx5c2lzL3Zpc3VhbGl6YWNpb24tZGUtZGF0b3MtdXNhbmRvLWdncGxvdDIuaHRtbD4NCg0KRXMgaW1wb3J0YW50ZSBzYWJlciBxdWUgc2UgcHVlZGUgdXRpbGl6YXIgZ2VvbV9ib3hwbG90IHBhcmEgZ2VuZXJhciB1biBkaWFncmFtYSBkZSBjYWphcyAoYm94IHBsb3QpIGVuIGdncGxvdDIgc2kgdGllbmUgdW4gZnJhbWUgZGUgZGF0b3MgY29uIHVuYSB2YXJpYWJsZSBudW3DqXJpY2EuIEVzdGUgY29kaWdvIG5vcyBheXVkYSBlc3BlY8OtZmljYW1lbnRlIGEgY3JlYXIgZ3LDoWZpY29zIGRlIGNhamEgcXVlIG11ZXN0cmFuIGxhIGRpc3RyaWJ1Y2nDs24gZGUgdW4gY29uanVudG8gZGUgZGF0b3MgY29uIG1lZGlhbmFzLCBjdWFydGlsZXMgeSB2YWxvcmVzIHBvc2libGVzIGF0w61waWNvcy4gUGFyYSBoYWNlcmxvLCBhbCBjb25zdHJ1aXIgZWwgZ3LDoWZpY28sIHNlIGRlYmUgZXNwZWNpZmljYXIgbGEgdmFyaWFibGUgbnVtw6lyaWNhIGRlbnRybyBkZSBsYSBmdW5jacOzbiBhZXMoKSwgdGFtYmnDqW4gY29ub2NpZGEgY29tbyBtYXBlbyBlc3TDqXRpY28uIFVuIGFzcGVjdG8gaW1wb3J0YW50ZSBkZSB0ZW5lciBlbiBjdWVudGEgYSBsYSBob3JhIGRlIGdyYWZpY2FyIGdlb21fYm94cGxvdCgpIGRlIGdncGxvdDIgZXMgcXVlIHNlZ8O6biBDb2RlciwgKDIwMjQpIOKAnGFsdGVybmF0aXZhbWVudGUgcHVlZGVzIGVzdGFibGVjZXIgeCA9ICIiLiBFc3RvIGVsaW1pbmFyw6EgbG9zIHZhbG9yZXMgZGVsIGVqZSBYIHkgaGFyw6EgbGEgY2FqYSBtw6FzIGVzdHJlY2hhLuKAnSBFc3RhcyB5IG11Y2hhcyBhbHRlcm5hdGl2YXMgZXhpc3RlbnRlcyBub3MgYXl1ZGFuIGEgY3JlYXIgZWwgZ3LDoWZpY28gcGVyc29uYWxpemFkYSBjb25zaWRlcmFuZG8gdG9kYXMgbnVlc3RyYXMgcHJlZmVyZW5jaWFzIHJlc3BlY3RvIGEgY8OzbW8gcXVlcmVtb3MgcXVlIHNlYSBlbCByZXN1bHRhZG8gZmluYWwuDQoNCkNvbW8gc2UgcHVlZGUgb2JzZXJ2YXIgZ2dwbG90MiBlcyB1bmEgaGVycmFtaWVudGEgw7p0aWwgcGFyYSBsYSB2aXN1YWxpemFjacOzbiBkZSBkYXRvcyBlbiBSIHF1ZSBwZXJtaXRlIGNyZWFyIGdyw6FmaWNvcyBjb21wbGVqb3MgeSBwZXJzb25hbGl6YWRvcyBhIHBhcnRpciBkZSBtYXJjb3MgZGUgZGF0b3MuIFN1IGRpc2XDsW8sIGVuIGVsIHF1ZSBjYWRhIGNvbHVtbmEgcmVwcmVzZW50YSB1bmEgdmFyaWFibGUgeSBjYWRhIGZpbGEgdW5hIG9ic2VydmFjacOzbiwgZGVzdGFjYSBzdSBjYXBhY2lkYWQgcGFyYSBtYW5lamFyIGRhdG9zIGVzdHJ1Y3R1cmFkb3MgZGUgbWFuZXJhIGVmaWNpZW50ZS4gTGEgaGVycmFtaWVudGEgZmFjaWxpdGEgbGEgY3JlYWNpw7NuIGRlIHZpc3VhbGl6YWNpb25lcyBwcmVjaXNhcyB5IGVmZWN0aXZhcyBhbCBwZXJtaXRpciBsYSBzZWxlY2Npw7NuLCByZXByZXNlbnRhY2nDs24geSBtb2RpZmljYWNpw7NuIGRlIHZhcmlhYmxlcy4NCg0KIyMgKio1LiBIaXN0b2dyYW1hIHNvYnJlIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcyBkZWwgZGF0YXNldCBJcmlzKioNCg0KU2UgcHJvY2VkZSBhIGdyYWZpY2FyIGVsIGhpc3RvZ3JhbWEgZGUgY2FkYSB1bmEgZGUgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzIGRlbCBkYXRhc2V0IElyaXMgdXNhbmRvIGdncGxvdDIuDQoNCjEuICBJbnN0YWxhciBwYXF1ZXRlcy4NCg0KMi4gIEluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKS4NCg0KMy4gIEluc3RhbGwucGFja2FnZXMoInRpZHlyIikuDQoNCjQuICBDYXJnYXIgbGFzIGxpYnJlcsOtYXMgbmVjZXNhcmlhcy4NCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpIA0KDQpsaWJyYXJ5KHRpZHlyKQ0KYGBgDQoNCnRpZHlyIHNlIGVtcGxlYSBwYXJhIGNvbnZlcnRpciBlbCBkYXRhc2V0IGlyaXMgYSB1biBmb3JtYXRvIGxhcmdvIChvICJsb25nIGZvcm1hdCIpLCBxdWUgZXMgZXNwZWNpYWxtZW50ZSDDunRpbCBjdWFuZG8gc2UgZGVzZWEgY3JlYXIgZ3LDoWZpY29zIGZhY2V0ZWFkb3MgbyBjb21wYXJhciBtw7psdGlwbGVzIHZhcmlhYmxlcy4NCg0KYGBge3J9DQppcmlzX2xvbmcgPC0gcGl2b3RfbG9uZ2VyKGlyaXMsIGNvbHMgPSAtU3BlY2llcywgbmFtZXNfdG8gPSAiVmFyaWFibGUiLCB2YWx1ZXNfdG8gPSAiVmFsdWUiKSANCmBgYA0KDQpwaXZvdF9sb25nZXIgY29udmllcnRlIGVsIGRhdGFzZXQgaXJpcyBkZSB1biBmb3JtYXRvIGFuY2hvIGEgdW4gZm9ybWF0byBsYXJnby4gRW4gZWwgY8OzZGlnbywgc2UgZXNwZWNpZmljYSBxdWUgdG9kYXMgbGFzIGNvbHVtbmFzIGV4Y2VwdG8gJ1NwZWNpZXMnIGRlYmVuIHNlciB0cmFuc2Zvcm1hZGFzIGVuIGRvcyBjb2x1bW5hczogVmFyaWFibGUgKGNvbnRlbmRyw6EgZWwgbm9tYnJlIGRlIGxhIHZhcmlhYmxlIG9yaWdpbmFsKSB5IFZhbHVlIChjb250ZW5kcsOhIGxvcyB2YWxvcmVzIGNvcnJlc3BvbmRpZW50ZXMpLg0KDQo1LiAgR3JhZmljYXIgaGlzdG9ncmFtYXMgcGFyYSBjYWRhIHZhcmlhYmxlIG51bcOpcmljYSBjb24gcGVyc29uYWxpemFjaW9uZXMuDQoNCmBgYHtyIEMzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1UUlVFfQ0KZ2dwbG90KGlyaXNfbG9uZywgYWVzKHggPSBWYWx1ZSwgZmlsbCA9IFNwZWNpZXMpKSArICANCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAwLjMsIGNvbG9yID0gImJsYWNrIiwgYWxwaGEgPSAwLjcsIHBvc2l0aW9uID0gImRvZGdlIikgKyANCiAgZmFjZXRfd3JhcCh+VmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlX3giKSArIA0KICBsYWJzKA0KICAgIHRpdGxlID0gIlZhcmlhYmxlcyBudW3DqXJpY2FzIGRhdGFzZXQgSXJpcyIsIA0KICAgIHggPSAiVmFsb3IiLCANCiAgICB5ID0gIkZyZWN1ZW5jaWEiDQogICkgKyANCiAgdGhlbWVfYncoYmFzZV9zaXplID0gMTQsIGJhc2VfZmFtaWx5ID0gIlRpbWVzIE5ldyBSb21hbiIpICsgDQogIHRoZW1lKCBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGNvbG9yID0gInB1cnBsZSIsIHNpemUgPSAxNiwgZmFjZSA9ICJib2xkIiwgaGp1c3QgPSAwLjUpLA0KICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJwdXJwbGUiLCBzaXplID0gMTQsIGZhY2UgPSAiYm9sZCIpLA0KICAgIGF4aXMudGl0bGUueSA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJwdXJwbGUiLCBzaXplID0gMTQsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHN0cmlwLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJibGFjayIpLA0KICAgIHN0cmlwLnRleHQgPSBlbGVtZW50X3RleHQoY29sb3IgPSAid2hpdGUiLCBzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpDQogICkgKyANCiAgZ2VvbV9kZW5zaXR5KGFlcyh4ID0gVmFsdWUpLCBjb2xvciA9ICJ0dXJxdW9pc2UiLCBzaXplID0gMSwgYWxwaGEgPSAwLjYpDQoNCmBgYA0KDQohW10oSGlzdG9ncmFtYS5qZmlmKQ0KDQpFbCByZXN1bHRhZG8gc2Vyw6EgdW5hIHNlcmllIGRlIGhpc3RvZ3JhbWFzIHF1ZSBtdWVzdHJhbiBsYSBkaXN0cmlidWNpw7NuIGRlIGxhcyBjdWF0cm8gdmFyaWFibGVzIG51bcOpcmljYXMgZW4gZWwgZGF0YXNldCBpcmlzLg0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyAqKkNvbmNsdXNpw7NuKioNCg0KRW4gY29uY2x1c2nDs24sIGdncGxvdDIgZXMgdW5hIGhlcnJhbWllbnRhIGZ1bmRhbWVudGFsIGVuIGVsIGFuw6FsaXNpcyB5IHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zIGVuIFIsIGRlYmlkbyBhIHN1IGVzdHJ1Y3R1cmEgYmFzYWRhIGVuIGNhcGFzIHkgc3UgZ3JhbiB2ZXJzYXRpbGlkYWQuIERlc2RlIHN1IGNyZWFjacOzbiwgaGEgc2lkbyBvYmpldG8gZGUgY29udGludWFzIG1lam9yYXMsIGxvIHF1ZSBoYSBwZXJtaXRpZG8gcXVlIHNpZ2Egc2llbmRvIHJlbGV2YW50ZSB0YW50byBwYXJhIHVzdWFyaW9zIG5vdmF0b3MgY29tbyBwYXJhIGV4cGVydG9zIGVuIGNpZW5jaWEgZGUgZGF0b3MuIEVsIGRvbWluaW8gZGUgc3UgZ3JhbcOhdGljYSBkZSBncsOhZmljb3MgYnJpbmRhIGEgbG9zIGFuYWxpc3RhcyB5IGNpZW50w61maWNvcyBkZSBkYXRvcyB1bmEgcG9kZXJvc2EgaGVycmFtaWVudGEgcGFyYSBleHBsb3JhciwgdmlzdWFsaXphciB5IGNvbXVuaWNhciBkYXRvcyBkZSBtYW5lcmEgZWZlY3RpdmEuDQoNCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQojICoqQmlibGlvZ3JhZsOtYSoqDQoNCkJlcnJlbmRlcm8sIEouIFIuIChuLmQuKS4gVW5hIGJyZXZlIGludHJvZHVjY2nDs24gYSBnZ3Bsb3QyLiBVbml2ZXJzaWRhZCBBdXTDs25vbWEgZGUgTWFkcmlkLiA8aHR0cHM6Ly92ZXJzby5tYXQudWFtLmVzL35qb3Nlci5iZXJyZW5kZXJvL1IvaW50cm9nZ3Bsb3QyLmh0bWw+DQoNCldpY2toYW0sIEguICgyMDE2KS4gZ2dwbG90MjogRWxlZ2FudCBHcmFwaGljcyBmb3IgRGF0YSBBbmFseXNpcy4gU3ByaW5nZXItVmVybGFnIE5ldyBZb3JrLg0KDQpOYWxsYXIsIEUuIEMuLCBQaEQuIChuLmQuKS4gNiBWaXN1YWxpemFjacOzbiBkZSBkYXRvcyB1c2FuZG8gZ2dwbG90MiBcfCBEaXNlw7FvIGV4cGVyaW1lbnRhbCB5IGFuw6FsaXNpcyBkZSBkYXRvcy4gPGh0dHBzOi8vd3d3LmNhc3Ryb2xhYi5vcmcvdGVhY2hpbmcvZGF0YV9hbmFseXNpcy92aXN1YWxpemFjaW9uLWRlLWRhdG9zLXVzYW5kby1nZ3Bsb3QyLmh0bWw+DQoNCkNvZGVyLCBSLiAoMjAyNCwgSmFudWFyeSA0KS4gQm94IHBsb3QgZW4gZ2dwbG90Mi4gUiBDSEFSVFMgXHwgVW5hIENvbGVjY2nDs24gRGUgR3LDoWZpY29zIEhlY2hvcyBDb24gRWwgTGVuZ3VhamUgRGUgUHJvZ3JhbWFjacOzbiBSLiA8aHR0cHM6Ly9yLWNoYXJ0cy5jb20vZXMvZGlzdHJpYnVjaW9uL2JveC1wbG90LWdncGxvdDIvPg0K