Presentador - LUIS ANTONIO CALVO QUISPE

Técnica: Aprendizaje Automático Basado en Reglas

Caso: Identificación de hongos venenosos

Objetivo: Entrenar un modelo de decisión basado en reglas para clasificar instancias de hongos.

Perfil Linkelind - Google Drive - Video Explicativo - Resumen de Resultados


Descripción del Caso

Se tiene una base de datos que contiene las características de 8124 champiñones, clasificados como comestibles y venenosos (variable objetivo). Se debe construir un modelo que permita predecir el tipo de champiñón en base a estas características (variables independientes) y evaluar los resultados.

Resolución del Caso

Paso 1 : Recopilación de Datos

  • Se cargan los datos a partir del csv mushrooms.
  • Se especifica la opción stringAsFactors como TRUE, la cual era el default en versiones anteriores de R, pero actualmente, el default es FALSE.
  • Esta opción automáticamente convierte los valores de tipo cadena en factores
mushrooms <- read.csv("mushrooms.csv", stringsAsFactors = TRUE)
  • Revisamos el contenido de nuestro dataframe, primero conociendo el volumen de data, teniendo 23 columnas y 8124 registros.
dim(mushrooms)
## [1] 8124   23
  • También podemos ver el nombre de cada columna:
colnames(mushrooms)
##  [1] "type"                     "cap_shape"               
##  [3] "cap_surface"              "cap_color"               
##  [5] "bruises"                  "odor"                    
##  [7] "gill_attachment"          "gill_spacing"            
##  [9] "gill_size"                "gill_color"              
## [11] "stalk_shape"              "stalk_root"              
## [13] "stalk_surface_above_ring" "stalk_surface_below_ring"
## [15] "stalk_color_above_ring"   "stalk_color_below_ring"  
## [17] "veil_type"                "veil_color"              
## [19] "ring_number"              "ring_type"               
## [21] "spore_print_color"        "population"              
## [23] "habitat"
  • Asimismo, podemos revisar los datos cargados …
head(mushrooms)
##        type cap_shape cap_surface cap_color bruises    odor gill_attachment
## 1 poisonous    convex      smooth     brown     yes pungent            free
## 2    edible    convex      smooth    yellow     yes  almond            free
## 3    edible      bell      smooth     white     yes   anise            free
## 4 poisonous    convex       scaly     white     yes pungent            free
## 5    edible    convex      smooth      gray      no    none            free
## 6    edible    convex       scaly    yellow     yes  almond            free
##   gill_spacing gill_size gill_color stalk_shape stalk_root
## 1        close    narrow      black   enlarging      equal
## 2        close     broad      black   enlarging       club
## 3        close     broad      brown   enlarging       club
## 4        close    narrow      brown   enlarging      equal
## 5      crowded     broad      black    tapering      equal
## 6        close     broad      brown   enlarging       club
##   stalk_surface_above_ring stalk_surface_below_ring stalk_color_above_ring
## 1                   smooth                   smooth                  white
## 2                   smooth                   smooth                  white
## 3                   smooth                   smooth                  white
## 4                   smooth                   smooth                  white
## 5                   smooth                   smooth                  white
## 6                   smooth                   smooth                  white
##   stalk_color_below_ring veil_type veil_color ring_number  ring_type
## 1                  white   partial      white         one    pendant
## 2                  white   partial      white         one    pendant
## 3                  white   partial      white         one    pendant
## 4                  white   partial      white         one    pendant
## 5                  white   partial      white         one evanescent
## 6                  white   partial      white         one    pendant
##   spore_print_color population habitat
## 1             black  scattered   urban
## 2             brown   numerous grasses
## 3             brown   numerous meadows
## 4             black  scattered   urban
## 5             brown   abundant grasses
## 6             black   numerous grasses
  • Luego inspeccionaremos a detalle la base conociendo las etiquetas de cada variable:
summary(mushrooms)
##         type        cap_shape     cap_surface     cap_color    bruises   
##  edible   :4208   bell   : 452   fibrous:2320   brown  :2284   no :4748  
##  poisonous:3916   conical:   4   grooves:   4   gray   :1840   yes:3376  
##                   convex :3656   scaly  :3244   red    :1500             
##                   flat   :3152   smooth :2556   yellow :1072             
##                   knobbed: 828                  white  :1040             
##                   sunken :  32                  buff   : 168             
##                                                 (Other): 220             
##       odor      gill_attachment  gill_spacing   gill_size        gill_color  
##  none   :3528   attached: 210   close  :6812   broad :5612   buff     :1728  
##  foul   :2160   free    :7914   crowded:1312   narrow:2512   pink     :1492  
##  fishy  : 576                                                white    :1202  
##  spicy  : 576                                                brown    :1048  
##  almond : 400                                                gray     : 752  
##  anise  : 400                                                chocolate: 732  
##  (Other): 484                                                (Other)  :1170  
##     stalk_shape     stalk_root   stalk_surface_above_ring
##  enlarging:3516   bulbous:3776   fibrous: 552            
##  tapering :4608   club   : 556   scaly  :  24            
##                   equal  :1120   silky  :2372            
##                   missing:2480   smooth :5176            
##                   rooted : 192                           
##                                                          
##                                                          
##  stalk_surface_below_ring stalk_color_above_ring stalk_color_below_ring
##  fibrous: 600             white  :4464           white  :4384          
##  scaly  : 284             pink   :1872           pink   :1872          
##  silky  :2304             gray   : 576           gray   : 576          
##  smooth :4936             brown  : 448           brown  : 512          
##                           buff   : 432           buff   : 432          
##                           orange : 192           orange : 192          
##                           (Other): 140           (Other): 156          
##    veil_type     veil_color   ring_number      ring_type    spore_print_color
##  partial:8124   brown :  96   none:  36   evanescent:2776   white    :2388   
##                 orange:  96   one :7488   flaring   :  48   brown    :1968   
##                 white :7924   two : 600   large     :1296   black    :1872   
##                 yellow:   8               none      :  36   chocolate:1632   
##                                           pendant   :3968   green    :  72   
##                                                             buff     :  48   
##                                                             (Other)  : 144   
##      population      habitat    
##  abundant : 384   grasses:2148  
##  clustered: 340   leaves : 832  
##  numerous : 400   meadows: 292  
##  scattered:1248   paths  :1144  
##  several  :4040   urban  : 368  
##  solitary :1712   waste  : 192  
##                   woods  :3148

Paso 2: Explorar y preparar los datos

str(mushrooms)
## 'data.frame':    8124 obs. of  23 variables:
##  $ type                    : Factor w/ 2 levels "edible","poisonous": 2 1 1 2 1 1 1 1 2 1 ...
##  $ cap_shape               : Factor w/ 6 levels "bell","conical",..: 3 3 1 3 3 3 1 1 3 1 ...
##  $ cap_surface             : Factor w/ 4 levels "fibrous","grooves",..: 4 4 4 3 4 3 4 3 3 4 ...
##  $ cap_color               : Factor w/ 10 levels "brown","buff",..: 1 10 9 9 4 10 9 9 9 10 ...
##  $ bruises                 : Factor w/ 2 levels "no","yes": 2 2 2 2 1 2 2 2 2 2 ...
##  $ odor                    : Factor w/ 9 levels "almond","anise",..: 8 1 2 8 7 1 1 2 8 1 ...
##  $ gill_attachment         : Factor w/ 2 levels "attached","free": 2 2 2 2 2 2 2 2 2 2 ...
##  $ gill_spacing            : Factor w/ 2 levels "close","crowded": 1 1 1 1 2 1 1 1 1 1 ...
##  $ gill_size               : Factor w/ 2 levels "broad","narrow": 2 1 1 2 1 1 1 1 2 1 ...
##  $ gill_color              : Factor w/ 12 levels "black","brown",..: 1 1 2 2 1 2 5 2 8 5 ...
##  $ stalk_shape             : Factor w/ 2 levels "enlarging","tapering": 1 1 1 1 2 1 1 1 1 1 ...
##  $ stalk_root              : Factor w/ 5 levels "bulbous","club",..: 3 2 2 3 3 2 2 2 3 2 ...
##  $ stalk_surface_above_ring: Factor w/ 4 levels "fibrous","scaly",..: 4 4 4 4 4 4 4 4 4 4 ...
##  $ stalk_surface_below_ring: Factor w/ 4 levels "fibrous","scaly",..: 4 4 4 4 4 4 4 4 4 4 ...
##  $ stalk_color_above_ring  : Factor w/ 9 levels "brown","buff",..: 8 8 8 8 8 8 8 8 8 8 ...
##  $ stalk_color_below_ring  : Factor w/ 9 levels "brown","buff",..: 8 8 8 8 8 8 8 8 8 8 ...
##  $ veil_type               : Factor w/ 1 level "partial": 1 1 1 1 1 1 1 1 1 1 ...
##  $ veil_color              : Factor w/ 4 levels "brown","orange",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ ring_number             : Factor w/ 3 levels "none","one","two": 2 2 2 2 2 2 2 2 2 2 ...
##  $ ring_type               : Factor w/ 5 levels "evanescent","flaring",..: 5 5 5 5 1 5 5 5 5 5 ...
##  $ spore_print_color       : Factor w/ 9 levels "black","brown",..: 1 2 2 1 2 1 1 2 1 1 ...
##  $ population              : Factor w/ 6 levels "abundant","clustered",..: 4 3 3 4 1 3 3 4 5 4 ...
##  $ habitat                 : Factor w/ 7 levels "grasses","leaves",..: 5 1 3 5 1 1 3 3 1 3 ...
  • Se explora la estructura de los datos.
  • Se observa que el dataset está compuesto por 8124 observaciones, 22 features o predictores y 1 variable objetivo (type), que indica si es venenoso o no el hongo.
  • Todas las variables han sido transformadas a factores, lo cual indica que el dataset es rico en contenido cualitativo.
  • Se observa que los factores tienen niveles cuya cantidad varía desde 1 hasta 12
mushrooms$veil_type <- NULL
  • Se elimina la característica veil_type porque es un factor de 1 nivel y, por lo tanto, no provee de información que permita distinguir entre los niveles de la variable objetivo.
  • Se confirma que el dataframe contiene una variable menos.
table(mushrooms$type)
## 
##    edible poisonous 
##      4208      3916
  • Se visualiza los totales para la variable objetivo, es decir, cuántos hongos comestibles y venenosos hay en el dataset.
  • Se observa que un poco más de la mitad de hongos del dataset son comestibles.
  • Aproximadamente, el 51.8% de hongos del dataset son comestibles mientras que el 48.2% de las observaciones corresponden a hongos venenosos.

Paso 3: Entrenar un modelo con los datos

library(OneR)
  • Se carga la librería del algoritmo de reglas 1R, que selecciona reglas de decisión sobre una sola característica para clasificar las observaciones.
mushroom_1R <- OneR(type ~ ., data = mushrooms)
  • Se entrena un modelo con el algoritmo 1R usando el dataset completo sin dividirlo en conjuntos de entrenamiento y de prueba.
  • El primer parámetro es la fórmula empleada para el entrenamiento.
  • A la izquierda de ~ va la variable objetivo y el . a la derecha significa que todos las demás variables del dataset se emplearán como predictores.
  • El segundo parámetro indica qué dataset se va a emplear.

Paso 4: Evaluar el rendimiento del modelo

  • Se examina el modelo entrenado.
mushroom_1R
## 
## Call:
## OneR.formula(formula = type ~ ., data = mushrooms)
## 
## Rules:
## If odor = almond   then type = edible
## If odor = anise    then type = edible
## If odor = creosote then type = poisonous
## If odor = fishy    then type = poisonous
## If odor = foul     then type = poisonous
## If odor = musty    then type = poisonous
## If odor = none     then type = edible
## If odor = pungent  then type = poisonous
## If odor = spicy    then type = poisonous
## 
## Accuracy:
## 8004 of 8124 instances classified correctly (98.52%)
  • Se observa que la característica seleccionada es el olor, factor de 9 niveles.
  • La precisión reportada es del 98.52%, lo cual es un valor bastante bueno porque significa que solo el 1.48% de las observaciones ha sido mal clasificado, es decir, de cada 200 observaciones, 3 son clasificadas erróneamente.
mushroom_1R_pred <- predict(mushroom_1R, mushrooms)
  • Se emplea la función predict para obtener las predicciones usando el modelo entrenado (primer parámetro) y el dataset completo (segundo parámetro).
table(actual = mushrooms$type, predicted = mushroom_1R_pred)
##            predicted
## actual      edible poisonous
##   edible      4208         0
##   poisonous    120      3796
  • Se genera una tabla para comparar la variable objetivo predicha con el valor real.
  • Se observa que 8004 (98.52%) observaciones fueron clasificadas correctamente mientras que las clasificaciones erróneas (120) corresponden en su totalidad a hongos venenosos que fueron clasificados incorrectamente como comestibles (1.48%), lo cual supone un peligro por ser falsos negativos

Resumen del Modelo

  • Exactitud: 98.52%
  • Precisión: 100.00%
  • Sensibilidad: 96.94%
  • Especificidad: 100.00%
  • Tasa de falsos positivos: 0.00%. Es la probabilidad de que se clasifique como positivo (venenoso) cuando el valor verdadero sea negativo (comestible).
  • Tasa de falsos negativos: 3.06%. Es la probabilidad de que se clasifique como negativo (comestible) cuando el valor verdadero sea positivo (venenoso).
  • F1 Score: 98.44%

5: Mejorando el Rendimiento del Modelo con RWeka

library(RWeka)
## 
## Attaching package: 'RWeka'
## The following object is masked from 'package:OneR':
## 
##     OneR
  • Se carga la librería RWeka, que contiene implementaciones de algoritmos de aprendizaje automático basados en Java.
mushroom_JRip <- JRip(type ~ ., data = mushrooms)
  • JRip es una implementación del algoritmo RIPPER visto en el pdf de Árboles de decisión y reglas.
  • De manera similar al entrenamiento con OneR, el primer argumento es la fórmula mientras que el segundo hace referencia al dataset a emplear.
mushroom_JRip
## JRIP rules:
## ===========
## 
## (odor = foul) => type=poisonous (2160.0/0.0)
## (gill_size = narrow) and (gill_color = buff) => type=poisonous (1152.0/0.0)
## (gill_size = narrow) and (odor = pungent) => type=poisonous (256.0/0.0)
## (odor = creosote) => type=poisonous (192.0/0.0)
## (spore_print_color = green) => type=poisonous (72.0/0.0)
## (stalk_surface_below_ring = scaly) and (stalk_surface_above_ring = silky) => type=poisonous (68.0/0.0)
## (habitat = leaves) and (cap_color = white) => type=poisonous (8.0/0.0)
## (stalk_color_above_ring = yellow) => type=poisonous (8.0/0.0)
##  => type=edible (4208.0/0.0)
## 
## Number of Rules : 9
  • Se visualiza el modelo resultante del entrenamiento usando RIPPER.
  • El modelo obtenido usando OneR también tenía 9 reglas, pero eran solo sobre una sola característica. En cambio, el modelo obtenido usando JRip emplea condiciones más complejas a manera de conjunciones de condicionales de igualdad.
  • Reporta 4208 casos clasificados como comestibles con 0 errores, por lo que esta implementación del RIPPER nos da el 100% de precisión.
summary(mushroom_JRip)
## 
## === Summary ===
## 
## Correctly Classified Instances        8124              100      %
## Incorrectly Classified Instances         0                0      %
## Kappa statistic                          1     
## Mean absolute error                      0     
## Root mean squared error                  0     
## Relative absolute error                  0      %
## Root relative squared error              0      %
## Total Number of Instances             8124     
## 
## === Confusion Matrix ===
## 
##     a    b   <-- classified as
##  4208    0 |    a = edible
##     0 3916 |    b = poisonous

Resumen del Modelo

  • Exactitud: 100.00%

  • Precisión: 100.00%

  • Sensibilidad: 100.00%

  • Especificidad: 100.00%

  • Tasa de falsos positivos: 0.00%. Es la probabilidad de que se clasifique como positivo (venenoso) cuando el valor verdadero sea negativo (comestible).

  • Tasa de falsos negativos: 0.00%. Es la probabilidad de que se clasifique como negativo (comestible) cuando el valor verdadero sea positivo (venenoso).

  • F1 Score: 100.00%

  • En base a las métricas calculadas, se puede observar una alta exactitud 100%. Además, se puede evidenciar una alta precisión 100% y alta sensibilidad 100%, por lo tanto, el modelo RJip entrenado es altamente confiable en la predicción de la clase venenoso, y también detecta perfectamente la clase (venenoso) respecto a los valores observados sin presencia de falsos negativos

6: Mejorando el Rendimiento del Modelo C5.0 Decisión TREES

summary(library(C50))
##    Length     Class      Mode 
##        10 character character
  • Se carga la librería C50 para aprendizaje de reglas usando árboles de decisión.
mushroom_c5rules <- C5.0(type ~ odor + gill_size, data = mushrooms, rules = TRUE)
  • Se entrena el modelo C5.0 empleando 2 características: el olor y el gill_size, y se especifica el dataset a emplear.
  • Adicionalmente, se indica que se debe entrenar el modelo basándose en reglas con el parámetro rules = TRUE, ya que este algoritmo también se puede usar para entrenar árboles.
summary(mushroom_c5rules)
## 
## Call:
## C5.0.formula(formula = type ~ odor + gill_size, data = mushrooms, rules = TRUE)
## 
## 
## C5.0 [Release 2.07 GPL Edition]      Wed Sep 14 23:33:45 2022
## -------------------------------
## 
## Class specified by attribute `outcome'
## 
## Read 8124 cases (3 attributes) from undefined.data
## 
## Rules:
## 
## Rule 1: (4328/120, lift 1.9)
##  odor in {almond, anise, none}
##  ->  class edible  [0.972]
## 
## Rule 2: (3796, lift 2.1)
##  odor in {creosote, fishy, foul, musty, pungent, spicy}
##  ->  class poisonous  [1.000]
## 
## Default class: edible
## 
## 
## Evaluation on training data (8124 cases):
## 
##          Rules     
##    ----------------
##      No      Errors
## 
##       2  120( 1.5%)   <<
## 
## 
##     (a)   (b)    <-classified as
##    ----  ----
##    4208          (a): class edible
##     120  3796    (b): class poisonous
## 
## 
##  Attribute usage:
## 
##  100.00% odor
## 
## 
## Time: 0.0 secs

Resumen del Modelo

  • Exactitud: 98.52%

  • Precisión: 100.00%

  • Sensibilidad: 96.94%

  • Especificidad: 100.00%

  • Tasa de falsos positivos: 0.00%. Es la probabilidad de que se clasifique como positivo (venenoso) cuando el valor verdadero sea negativo (comestible).

  • Tasa de falsos negativos: 3.06%. Es la probabilidad de que se clasifique como negativo (comestible) cuando el valor verdadero sea positivo (venenoso).

  • F1 Score: 98.44%

  • En base a las métricas calculadas, se puede observar una alta exactitud 98.52%. Además,se puede evidenciar una alta precisión 100% y alta sensibilidad 96.94%, por lo tanto, el modelo C5.0 entrenado es altamente confiable en la predicción de la clase venenoso, y en menor medida detecta la clase (venenoso) respecto a los valores observados ya que presenta falsos negativos, es decir, predice como negativo (comestible) un 3.06% cuando el valor verdadero es positivo (venenoso)

  • En conclusión, el algoritmo C50 usado para entrenar un modelo de aprendizaje basado en reglas usando árboles de decisión no muestra un desempeño superior que el algoritmo 1R, que es más simple, pero esto no significa que en todos los casos será así porque solo se ha especificado 2 características para entrenar el C50.

7: Conclusiones

-Elegiremos como mejor modelo el algoritmo Ripper (RJip), ya que se evidencia que el modelo escogido para entrenar la data maneja perfectamente la clase de la variable objetivo (venenoso/comestible). No hay presencia de errores, con una alta precisión 100% y alta sensibilidad 100%, siendo el modelo altamente confiable en la predicción de la clase venenoso, y también detecta perfectamente la clase venenoso respecto a los valores observados sin presencia de falsos negativos.

-Por otro lado, tanto el modelo One Rule como el árbol de decisión C5.0, si generan error de 1.48%. Y una tasa de falsos negativos del 3.06%, es decir, la probabilidad de que se clasifique como negativo (comestible) cuando el valor verdadero sea positivo (venenoso)

LS0tDQp0aXRsZTogIlByb3llY3RvIEZpbmFsIGRlIFdvcmtzaG9wIg0KYXV0aG9yOiAiQ2Fsdm8gUXVpc3BlLCBMdWlzIEFudG9uaW8iDQpkYXRlOiAiMjAyMi8wOS8xNCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgd29yZF9kb2N1bWVudDogZGVmYXVsdA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KDQo8IS0tIEJpZW52ZW5pZG9zIGFsIFByb3llY3RvIGRlIEx1aXMgQW50b25pbyBDYWx2byBRdWlzcGUgLS0+DQoNCjxjZW50ZXI+IVtdKE1hY2hpbmVMZWFybmluZy5wbmcpe3dpZHRoPSI5MDBweCJ9PC9jZW50ZXI+DQoNCjxjZW50ZXI+DQojIFByZXNlbnRhZG9yIC0gTFVJUyBBTlRPTklPIENBTFZPIFFVSVNQRQ0KPC9jZW50ZXI+DQoNCiMjIyBUw6ljbmljYTogQXByZW5kaXphamUgQXV0b23DoXRpY28gQmFzYWRvIGVuIFJlZ2xhcyAjIyMNCiMjIyMgQ2FzbzogSWRlbnRpZmljYWNpw7NuIGRlIGhvbmdvcyB2ZW5lbm9zb3MgIyMjIw0KIyMjIyBPYmpldGl2bzogRW50cmVuYXIgdW4gbW9kZWxvIGRlIGRlY2lzacOzbiBiYXNhZG8gZW4gcmVnbGFzIHBhcmEgY2xhc2lmaWNhciBpbnN0YW5jaWFzIGRlIGhvbmdvcy4jIyMjDQoNCjxjZW50ZXI+DQpbUGVyZmlsIExpbmtlbGluZF0oaHR0cHM6Ly93d3cubGlua2VkaW4uY29tL2luL2x1aXMtYW50b25pby1jYWx2by1xdWlzcGUtNTdhMzNiMjI5LyAiSW5ncmVzYXIgYWwgUGVyZmlsIGRlbCBBbHVtbm8iKXt0YXJnZXQ9Il9ibGFuayJ9IC0gDQpbR29vZ2xlIERyaXZlXShodHRwczovL2RyaXZlLmdvb2dsZS5jb20vZHJpdmUvZm9sZGVycy8xYmJCR3ZaV1RNOWc3UUpuRDdiTUtndzJnOWpka2ZpVXM/dXNwPXNoYXJpbmcgIkluZ3Jlc2FyIGEgbGEgQ2FycGV0YSBDb21wYXJ0aWRhIil7dGFyZ2V0PSJfYmxhbmsifSAtIA0KW1ZpZGVvIEV4cGxpY2F0aXZvXShodHRwczovL2RyaXZlLmdvb2dsZS5jb20vZmlsZS9kLzFxd01ISERCR2lGQWVvQVJMdkprenVwejZ6a2tDVXE3Mi92aWV3P3VzcD1zaGFyaW5nICJWaXN1YWxpemFyIGVsIFZpZGVvIEV4cGxpY2F0aXZvIil7dGFyZ2V0PSJfYmxhbmsifSAtIA0KW1Jlc3VtZW4gZGUgUmVzdWx0YWRvc10oaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xckc2Tk9vWTVjLXBFeEZ0Y25jSGQtQTduajh6TlJmR2svdmlldz91c3A9c2hhcmluZyAiVmlzdWFsaXphciBlbCByZXN1bWVuIGRlIFJlc3VsdGFkb3MiKXt0YXJnZXQ9Il9ibGFuayJ9DQo8L2NlbnRlcj4NCg0KPGJyPg0KDQo8Y2VudGVyPg0KIyMjIERlc2NyaXBjacOzbiBkZWwgQ2FzbyAjIyMNCjwvY2VudGVyPg0KDQpTZSB0aWVuZSB1bmEgYmFzZSBkZSBkYXRvcyBxdWUgY29udGllbmUgbGFzIGNhcmFjdGVyw61zdGljYXMgZGUgODEyNCBjaGFtcGnDsW9uZXMsIGNsYXNpZmljYWRvcyBjb21vIGNvbWVzdGlibGVzIHkgdmVuZW5vc29zICh2YXJpYWJsZSBvYmpldGl2bykuIFNlIGRlYmUgY29uc3RydWlyIHVuIG1vZGVsbyBxdWUgcGVybWl0YSBwcmVkZWNpciBlbCB0aXBvIGRlIGNoYW1wacOxw7NuIGVuIGJhc2UgYSBlc3RhcyBjYXJhY3RlcsOtc3RpY2FzICh2YXJpYWJsZXMgaW5kZXBlbmRpZW50ZXMpIHkgZXZhbHVhciBsb3MgcmVzdWx0YWRvcy4NCg0KPGNlbnRlcj4NCiMjIyBSZXNvbHVjacOzbiBkZWwgQ2FzbyAjIyMNCjwvY2VudGVyPg0KDQojIyMjIFBhc28gMSA6IFJlY29waWxhY2nDs24gZGUgRGF0b3MNCg0KLSAgIFNlIGNhcmdhbiBsb3MgZGF0b3MgYSBwYXJ0aXIgZGVsIGNzdiBtdXNocm9vbXMuDQotICAgU2UgZXNwZWNpZmljYSBsYSBvcGNpw7NuIHN0cmluZ0FzRmFjdG9ycyBjb21vIFRSVUUsIGxhIGN1YWwgZXJhIGVsIGRlZmF1bHQgZW4gdmVyc2lvbmVzIGFudGVyaW9yZXMgZGUgUiwgcGVybyBhY3R1YWxtZW50ZSwgZWwgZGVmYXVsdCBlcyBGQUxTRS4NCi0gICBFc3RhIG9wY2nDs24gYXV0b23DoXRpY2FtZW50ZSBjb252aWVydGUgbG9zIHZhbG9yZXMgZGUgdGlwbyBjYWRlbmEgZW4gZmFjdG9yZXMNCg0KDQpgYGB7cn0NCm11c2hyb29tcyA8LSByZWFkLmNzdigibXVzaHJvb21zLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBUUlVFKQ0KYGBgDQoNCi0gUmV2aXNhbW9zIGVsIGNvbnRlbmlkbyBkZSBudWVzdHJvIGRhdGFmcmFtZSwgcHJpbWVybyBjb25vY2llbmRvIGVsIHZvbHVtZW4gZGUgZGF0YSwgdGVuaWVuZG8gMjMgY29sdW1uYXMgeSA4MTI0IHJlZ2lzdHJvcy4NCg0KYGBge3J9DQpkaW0obXVzaHJvb21zKQ0KYGBgDQoNCi0gVGFtYmnDqW4gcG9kZW1vcyB2ZXIgZWwgbm9tYnJlIGRlIGNhZGEgY29sdW1uYToNCg0KYGBge3J9DQpjb2xuYW1lcyhtdXNocm9vbXMpDQpgYGANCg0KLSBBc2ltaXNtbywgcG9kZW1vcyByZXZpc2FyIGxvcyBkYXRvcyBjYXJnYWRvcyAuLi4NCg0KYGBge3J9DQpoZWFkKG11c2hyb29tcykNCmBgYA0KDQotIEx1ZWdvIGluc3BlY2Npb25hcmVtb3MgYSBkZXRhbGxlIGxhIGJhc2UgY29ub2NpZW5kbyBsYXMgZXRpcXVldGFzIGRlIGNhZGEgdmFyaWFibGU6DQoNCmBgYHtyfQ0Kc3VtbWFyeShtdXNocm9vbXMpDQpgYGANCg0KIyMjIyBQYXNvIDI6IEV4cGxvcmFyIHkgcHJlcGFyYXIgbG9zIGRhdG9zDQoNCmBgYHtyfQ0Kc3RyKG11c2hyb29tcykNCmBgYA0KDQotIFNlIGV4cGxvcmEgbGEgZXN0cnVjdHVyYSBkZSBsb3MgZGF0b3MuDQotIFNlIG9ic2VydmEgcXVlIGVsIGRhdGFzZXQgZXN0w6EgY29tcHVlc3RvIHBvciA4MTI0IG9ic2VydmFjaW9uZXMsIDIyIGZlYXR1cmVzIG8gcHJlZGljdG9yZXMgeSAxIHZhcmlhYmxlIG9iamV0aXZvICh0eXBlKSwgcXVlIGluZGljYSBzaSBlcyB2ZW5lbm9zbyBvIG5vIGVsIGhvbmdvLiANCi0gVG9kYXMgbGFzIHZhcmlhYmxlcyBoYW4gc2lkbyB0cmFuc2Zvcm1hZGFzIGEgZmFjdG9yZXMsIGxvIGN1YWwgaW5kaWNhIHF1ZSBlbCBkYXRhc2V0IGVzIHJpY28gZW4gY29udGVuaWRvIGN1YWxpdGF0aXZvLg0KLSBTZSBvYnNlcnZhIHF1ZSBsb3MgZmFjdG9yZXMgdGllbmVuIG5pdmVsZXMgY3V5YSBjYW50aWRhZCB2YXLDrWEgZGVzZGUgMSBoYXN0YSAxMg0KDQpgYGB7cn0NCm11c2hyb29tcyR2ZWlsX3R5cGUgPC0gTlVMTA0KYGBgDQoNCi0gU2UgZWxpbWluYSBsYSBjYXJhY3RlcsOtc3RpY2EgdmVpbF90eXBlIHBvcnF1ZSBlcyB1biBmYWN0b3IgZGUgMSBuaXZlbCB5LCBwb3IgbG8gdGFudG8sIG5vIHByb3ZlZSBkZSBpbmZvcm1hY2nDs24gcXVlIHBlcm1pdGEgZGlzdGluZ3VpciBlbnRyZSBsb3Mgbml2ZWxlcyBkZSBsYSB2YXJpYWJsZSBvYmpldGl2by4NCi0gU2UgY29uZmlybWEgcXVlIGVsIGRhdGFmcmFtZSBjb250aWVuZSB1bmEgdmFyaWFibGUgbWVub3MuDQoNCmBgYHtyfQ0KdGFibGUobXVzaHJvb21zJHR5cGUpDQpgYGANCi0gU2UgdmlzdWFsaXphIGxvcyB0b3RhbGVzIHBhcmEgbGEgdmFyaWFibGUgb2JqZXRpdm8sIGVzIGRlY2lyLCBjdcOhbnRvcyBob25nb3MgY29tZXN0aWJsZXMgeSANCnZlbmVub3NvcyBoYXkgZW4gZWwgZGF0YXNldC4gDQotIFNlIG9ic2VydmEgcXVlIHVuIHBvY28gbcOhcyBkZSBsYSBtaXRhZCBkZSBob25nb3MgZGVsIGRhdGFzZXQgc29uIGNvbWVzdGlibGVzLg0KLSBBcHJveGltYWRhbWVudGUsIGVsIDUxLjglIGRlIGhvbmdvcyBkZWwgZGF0YXNldCBzb24gY29tZXN0aWJsZXMgbWllbnRyYXMgcXVlIGVsIDQ4LjIlIGRlIGxhcyBvYnNlcnZhY2lvbmVzIGNvcnJlc3BvbmRlbiBhIGhvbmdvcyB2ZW5lbm9zb3MuDQoNCiMjIyMgUGFzbyAzOiBFbnRyZW5hciB1biBtb2RlbG8gY29uIGxvcyBkYXRvcw0KDQpgYGB7cn0NCmxpYnJhcnkoT25lUikNCmBgYA0KLSBTZSBjYXJnYSBsYSBsaWJyZXLDrWEgZGVsIGFsZ29yaXRtbyBkZSByZWdsYXMgMVIsIHF1ZSBzZWxlY2Npb25hIHJlZ2xhcyBkZSBkZWNpc2nDs24gc29icmUgdW5hIHNvbGEgY2FyYWN0ZXLDrXN0aWNhIHBhcmEgY2xhc2lmaWNhciBsYXMgb2JzZXJ2YWNpb25lcy4NCg0KYGBge3J9DQptdXNocm9vbV8xUiA8LSBPbmVSKHR5cGUgfiAuLCBkYXRhID0gbXVzaHJvb21zKQ0KYGBgDQotIFNlIGVudHJlbmEgdW4gbW9kZWxvIGNvbiBlbCBhbGdvcml0bW8gMVIgdXNhbmRvIGVsIGRhdGFzZXQgY29tcGxldG8gc2luIGRpdmlkaXJsbyBlbiBjb25qdW50b3MgZGUgZW50cmVuYW1pZW50byB5IGRlIHBydWViYS4NCi0gRWwgcHJpbWVyIHBhcsOhbWV0cm8gZXMgbGEgZsOzcm11bGEgZW1wbGVhZGEgcGFyYSBlbCBlbnRyZW5hbWllbnRvLg0KLSBBIGxhIGl6cXVpZXJkYSBkZSB+IHZhIGxhIHZhcmlhYmxlIG9iamV0aXZvIHkgZWwgLiBhIGxhIGRlcmVjaGEgc2lnbmlmaWNhIHF1ZSB0b2RvcyBsYXMgZGVtw6FzIHZhcmlhYmxlcyBkZWwgZGF0YXNldCBzZSBlbXBsZWFyw6FuIGNvbW8gcHJlZGljdG9yZXMuDQotIEVsIHNlZ3VuZG8gcGFyw6FtZXRybyBpbmRpY2EgcXXDqSBkYXRhc2V0IHNlIHZhIGEgZW1wbGVhci4NCg0KIyMjIyBQYXNvIDQ6IEV2YWx1YXIgZWwgcmVuZGltaWVudG8gZGVsIG1vZGVsbw0KDQotIFNlIGV4YW1pbmEgZWwgbW9kZWxvIGVudHJlbmFkby4NCg0KYGBge3J9DQptdXNocm9vbV8xUg0KYGBgDQotIFNlIG9ic2VydmEgcXVlIGxhIGNhcmFjdGVyw61zdGljYSBzZWxlY2Npb25hZGEgZXMgZWwgb2xvciwgZmFjdG9yIGRlIDkgbml2ZWxlcy4NCi0gTGEgcHJlY2lzacOzbiByZXBvcnRhZGEgZXMgZGVsIDk4LjUyJSwgbG8gY3VhbCBlcyB1biB2YWxvciBiYXN0YW50ZSBidWVubyBwb3JxdWUgc2lnbmlmaWNhIHF1ZSBzb2xvIGVsIDEuNDglIGRlIGxhcyBvYnNlcnZhY2lvbmVzIGhhIHNpZG8gbWFsIGNsYXNpZmljYWRvLCBlcyBkZWNpciwgZGUgY2FkYSAyMDAgb2JzZXJ2YWNpb25lcywgMyBzb24gY2xhc2lmaWNhZGFzIGVycsOzbmVhbWVudGUuDQoNCmBgYHtyfQ0KbXVzaHJvb21fMVJfcHJlZCA8LSBwcmVkaWN0KG11c2hyb29tXzFSLCBtdXNocm9vbXMpDQpgYGANCi0gU2UgZW1wbGVhIGxhIGZ1bmNpw7NuIHByZWRpY3QgcGFyYSBvYnRlbmVyIGxhcyBwcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBlbnRyZW5hZG8gKHByaW1lciBwYXLDoW1ldHJvKSB5IGVsIGRhdGFzZXQgY29tcGxldG8gKHNlZ3VuZG8gcGFyw6FtZXRybykuDQoNCmBgYHtyfQ0KdGFibGUoYWN0dWFsID0gbXVzaHJvb21zJHR5cGUsIHByZWRpY3RlZCA9IG11c2hyb29tXzFSX3ByZWQpDQpgYGANCi0gU2UgZ2VuZXJhIHVuYSB0YWJsYSBwYXJhIGNvbXBhcmFyIGxhIHZhcmlhYmxlIG9iamV0aXZvIHByZWRpY2hhIGNvbiBlbCB2YWxvciByZWFsLg0KLSBTZSBvYnNlcnZhIHF1ZSA4MDA0ICg5OC41MiUpIG9ic2VydmFjaW9uZXMgZnVlcm9uIGNsYXNpZmljYWRhcyBjb3JyZWN0YW1lbnRlIA0KbWllbnRyYXMgcXVlIGxhcyBjbGFzaWZpY2FjaW9uZXMgZXJyw7NuZWFzICgxMjApIGNvcnJlc3BvbmRlbiBlbiBzdSB0b3RhbGlkYWQgYSBob25nb3MgdmVuZW5vc29zIHF1ZSBmdWVyb24gY2xhc2lmaWNhZG9zIGluY29ycmVjdGFtZW50ZSBjb21vIGNvbWVzdGlibGVzICgxLjQ4JSksIGxvIGN1YWwgc3Vwb25lIHVuIHBlbGlncm8gcG9yIHNlciBmYWxzb3MgbmVnYXRpdm9zDQoNCg0KIyMjIyAqKlJlc3VtZW4gZGVsIE1vZGVsbyoqICMjIyMNCg0KLSAqKkV4YWN0aXR1ZCoqOiA5OC41MiUNCi0gKipQcmVjaXNpw7NuKio6IDEwMC4wMCUNCi0gKipTZW5zaWJpbGlkYWQqKjogOTYuOTQlDQotICoqRXNwZWNpZmljaWRhZCoqOiAxMDAuMDAlDQotICoqVGFzYSBkZSBmYWxzb3MgcG9zaXRpdm9zKio6IDAuMDAlLiBFcyBsYSBwcm9iYWJpbGlkYWQgZGUgcXVlIHNlIGNsYXNpZmlxdWUgY29tbyBwb3NpdGl2byAodmVuZW5vc28pIGN1YW5kbyBlbCANCnZhbG9yIHZlcmRhZGVybyBzZWEgbmVnYXRpdm8gKGNvbWVzdGlibGUpLg0KLSAqKlRhc2EgZGUgZmFsc29zIG5lZ2F0aXZvcyoqOiAzLjA2JS4gRXMgbGEgcHJvYmFiaWxpZGFkIGRlIHF1ZSBzZSBjbGFzaWZpcXVlIGNvbW8gbmVnYXRpdm8gKGNvbWVzdGlibGUpIGN1YW5kbyANCmVsIHZhbG9yIHZlcmRhZGVybyBzZWEgcG9zaXRpdm8gKHZlbmVub3NvKS4NCi0gKipGMSBTY29yZSoqOiA5OC40NCUNCg0KIyMjIyA1OiBNZWpvcmFuZG8gZWwgUmVuZGltaWVudG8gZGVsIE1vZGVsbyBjb24gUldla2ENCg0KYGBge3J9DQpsaWJyYXJ5KFJXZWthKQ0KYGBgDQotIFNlIGNhcmdhIGxhIGxpYnJlcsOtYSBSV2VrYSwgcXVlIGNvbnRpZW5lIGltcGxlbWVudGFjaW9uZXMgZGUgYWxnb3JpdG1vcyBkZSANCmFwcmVuZGl6YWplIGF1dG9tw6F0aWNvIGJhc2Fkb3MgZW4gSmF2YS4NCg0KYGBge3J9DQptdXNocm9vbV9KUmlwIDwtIEpSaXAodHlwZSB+IC4sIGRhdGEgPSBtdXNocm9vbXMpDQpgYGANCi0gSlJpcCBlcyB1bmEgaW1wbGVtZW50YWNpw7NuIGRlbCBhbGdvcml0bW8gUklQUEVSIHZpc3RvIGVuIGVsIHBkZiBkZSDDgXJib2xlcyBkZSBkZWNpc2nDs24geSByZWdsYXMuDQotIERlIG1hbmVyYSBzaW1pbGFyIGFsIGVudHJlbmFtaWVudG8gY29uIE9uZVIsIGVsIHByaW1lciBhcmd1bWVudG8gZXMgbGEgZsOzcm11bGEgbWllbnRyYXMgcXVlIGVsIHNlZ3VuZG8gaGFjZSByZWZlcmVuY2lhIGFsIGRhdGFzZXQgYSBlbXBsZWFyLg0KDQpgYGB7cn0NCm11c2hyb29tX0pSaXANCmBgYA0KLSBTZSB2aXN1YWxpemEgZWwgbW9kZWxvIHJlc3VsdGFudGUgZGVsIGVudHJlbmFtaWVudG8gdXNhbmRvIFJJUFBFUi4NCi0gRWwgbW9kZWxvIG9idGVuaWRvIHVzYW5kbyBPbmVSIHRhbWJpw6luIHRlbsOtYSA5IHJlZ2xhcywgcGVybyBlcmFuIHNvbG8gc29icmUgdW5hIHNvbGEgY2FyYWN0ZXLDrXN0aWNhLiBFbiBjYW1iaW8sIGVsIG1vZGVsbyBvYnRlbmlkbyB1c2FuZG8gSlJpcCBlbXBsZWEgY29uZGljaW9uZXMgbcOhcyBjb21wbGVqYXMgYSBtYW5lcmEgZGUgY29uanVuY2lvbmVzIGRlIGNvbmRpY2lvbmFsZXMgZGUgaWd1YWxkYWQuDQotIFJlcG9ydGEgNDIwOCBjYXNvcyBjbGFzaWZpY2Fkb3MgY29tbyBjb21lc3RpYmxlcyBjb24gMCBlcnJvcmVzLCBwb3IgbG8gcXVlIGVzdGEgaW1wbGVtZW50YWNpw7NuIGRlbCBSSVBQRVIgbm9zIGRhIGVsIDEwMCUgZGUgcHJlY2lzacOzbi4NCg0KYGBge3J9DQpzdW1tYXJ5KG11c2hyb29tX0pSaXApDQpgYGANCg0KIyMjIyAqKlJlc3VtZW4gZGVsIE1vZGVsbyoqICMjIyMNCg0KLSAqKkV4YWN0aXR1ZCoqOiAxMDAuMDAlDQotICoqUHJlY2lzacOzbioqOiAxMDAuMDAlDQotICoqU2Vuc2liaWxpZGFkKio6IDEwMC4wMCUNCi0gKipFc3BlY2lmaWNpZGFkKio6IDEwMC4wMCUNCi0gKipUYXNhIGRlIGZhbHNvcyBwb3NpdGl2b3MqKjogMC4wMCUuIEVzIGxhIHByb2JhYmlsaWRhZCBkZSBxdWUgc2UgY2xhc2lmaXF1ZSBjb21vIHBvc2l0aXZvICh2ZW5lbm9zbykgY3VhbmRvIGVsIA0KdmFsb3IgdmVyZGFkZXJvIHNlYSBuZWdhdGl2byAoY29tZXN0aWJsZSkuDQotICoqVGFzYSBkZSBmYWxzb3MgbmVnYXRpdm9zKio6IDAuMDAlLiBFcyBsYSBwcm9iYWJpbGlkYWQgZGUgcXVlIHNlIGNsYXNpZmlxdWUgY29tbyBuZWdhdGl2byAoY29tZXN0aWJsZSkgY3VhbmRvIA0KZWwgdmFsb3IgdmVyZGFkZXJvIHNlYSBwb3NpdGl2byAodmVuZW5vc28pLg0KLSAqKkYxIFNjb3JlKio6IDEwMC4wMCUNCg0KLSBFbiBiYXNlIGEgbGFzIG3DqXRyaWNhcyBjYWxjdWxhZGFzLCBzZSBwdWVkZSBvYnNlcnZhciB1bmEgYWx0YSBleGFjdGl0dWQgMTAwJS4gQWRlbcOhcywgc2UgcHVlZGUgZXZpZGVuY2lhciANCnVuYSBhbHRhIHByZWNpc2nDs24gMTAwJSB5IGFsdGEgc2Vuc2liaWxpZGFkIDEwMCUsIHBvciBsbyB0YW50bywgZWwgbW9kZWxvIFJKaXAgZW50cmVuYWRvIGVzIGFsdGFtZW50ZSANCmNvbmZpYWJsZSBlbiBsYSBwcmVkaWNjacOzbiBkZSBsYSBjbGFzZSB2ZW5lbm9zbywgeSB0YW1iacOpbiBkZXRlY3RhIHBlcmZlY3RhbWVudGUgbGEgY2xhc2UgKHZlbmVub3NvKSANCnJlc3BlY3RvIGEgbG9zIHZhbG9yZXMgb2JzZXJ2YWRvcyBzaW4gcHJlc2VuY2lhIGRlIGZhbHNvcyBuZWdhdGl2b3MNCg0KIyMjIyA2OiBNZWpvcmFuZG8gZWwgUmVuZGltaWVudG8gZGVsIE1vZGVsbyBDNS4wIERlY2lzacOzbiBUUkVFUw0KDQpgYGB7cn0NCnN1bW1hcnkobGlicmFyeShDNTApKQ0KYGBgDQotIFNlIGNhcmdhIGxhIGxpYnJlcsOtYSBDNTAgcGFyYSBhcHJlbmRpemFqZSBkZSByZWdsYXMgdXNhbmRvIMOhcmJvbGVzIGRlIGRlY2lzacOzbi4NCg0KYGBge3J9DQptdXNocm9vbV9jNXJ1bGVzIDwtIEM1LjAodHlwZSB+IG9kb3IgKyBnaWxsX3NpemUsIGRhdGEgPSBtdXNocm9vbXMsIHJ1bGVzID0gVFJVRSkNCmBgYA0KLSBTZSBlbnRyZW5hIGVsIG1vZGVsbyBDNS4wIGVtcGxlYW5kbyAyIGNhcmFjdGVyw61zdGljYXM6IGVsIG9sb3IgeSBlbCBnaWxsX3NpemUsIHkgc2UgZXNwZWNpZmljYSBlbCBkYXRhc2V0IGEgZW1wbGVhci4NCi0gQWRpY2lvbmFsbWVudGUsIHNlIGluZGljYSBxdWUgc2UgZGViZSBlbnRyZW5hciBlbCBtb2RlbG8gYmFzw6FuZG9zZSBlbiByZWdsYXMgY29uIGVsIHBhcsOhbWV0cm8gcnVsZXMgPSBUUlVFLCB5YSBxdWUgZXN0ZSBhbGdvcml0bW8gdGFtYmnDqW4gc2UgcHVlZGUgdXNhciBwYXJhIGVudHJlbmFyIMOhcmJvbGVzLg0KDQpgYGB7cn0NCnN1bW1hcnkobXVzaHJvb21fYzVydWxlcykNCmBgYA0KDQojIyMjICoqUmVzdW1lbiBkZWwgTW9kZWxvKiogIyMjIw0KDQoNCi0gKipFeGFjdGl0dWQqKjogOTguNTIlDQotICoqUHJlY2lzacOzbioqOiAxMDAuMDAlDQotICoqU2Vuc2liaWxpZGFkKio6IDk2Ljk0JQ0KLSAqKkVzcGVjaWZpY2lkYWQqKjogMTAwLjAwJQ0KLSAqKlRhc2EgZGUgZmFsc29zIHBvc2l0aXZvcyoqOiAwLjAwJS4gRXMgbGEgcHJvYmFiaWxpZGFkIGRlIHF1ZSBzZSBjbGFzaWZpcXVlIGNvbW8gcG9zaXRpdm8gKHZlbmVub3NvKSBjdWFuZG8gZWwgDQp2YWxvciB2ZXJkYWRlcm8gc2VhIG5lZ2F0aXZvIChjb21lc3RpYmxlKS4NCi0gKipUYXNhIGRlIGZhbHNvcyBuZWdhdGl2b3MqKjogMy4wNiUuIEVzIGxhIHByb2JhYmlsaWRhZCBkZSBxdWUgc2UgY2xhc2lmaXF1ZSBjb21vIG5lZ2F0aXZvIChjb21lc3RpYmxlKSBjdWFuZG8gDQplbCB2YWxvciB2ZXJkYWRlcm8gc2VhIHBvc2l0aXZvICh2ZW5lbm9zbykuDQotICoqRjEgU2NvcmUqKjogOTguNDQlDQoNCi0gRW4gYmFzZSBhIGxhcyBtw6l0cmljYXMgY2FsY3VsYWRhcywgc2UgcHVlZGUgb2JzZXJ2YXIgdW5hIGFsdGEgZXhhY3RpdHVkIDk4LjUyJS4gQWRlbcOhcyxzZSBwdWVkZSBldmlkZW5jaWFyIHVuYSBhbHRhIHByZWNpc2nDs24gMTAwJSB5IGFsdGEgc2Vuc2liaWxpZGFkIDk2Ljk0JSwgcG9yIGxvIHRhbnRvLCBlbCBtb2RlbG8gQzUuMCBlbnRyZW5hZG8gZXMgYWx0YW1lbnRlIGNvbmZpYWJsZSBlbiBsYSBwcmVkaWNjacOzbiBkZSBsYSBjbGFzZSB2ZW5lbm9zbywgeSBlbiBtZW5vciBtZWRpZGEgZGV0ZWN0YSBsYSBjbGFzZSAodmVuZW5vc28pIHJlc3BlY3RvIGEgbG9zIHZhbG9yZXMgb2JzZXJ2YWRvcyB5YSBxdWUgcHJlc2VudGEgZmFsc29zIG5lZ2F0aXZvcywgZXMgZGVjaXIsIHByZWRpY2UgY29tbyBuZWdhdGl2byAoY29tZXN0aWJsZSkgdW4gMy4wNiUgY3VhbmRvIGVsIHZhbG9yIHZlcmRhZGVybyBlcyBwb3NpdGl2byAodmVuZW5vc28pDQoNCi0gRW4gY29uY2x1c2nDs24sIGVsIGFsZ29yaXRtbyBDNTAgdXNhZG8gcGFyYSBlbnRyZW5hciB1biBtb2RlbG8gZGUgYXByZW5kaXphamUgYmFzYWRvIGVuIHJlZ2xhcyB1c2FuZG8gw6FyYm9sZXMgZGUgZGVjaXNpw7NuIG5vIG11ZXN0cmEgdW4gZGVzZW1wZcOxbyBzdXBlcmlvciBxdWUgZWwgYWxnb3JpdG1vIDFSLCBxdWUgZXMgbcOhcyBzaW1wbGUsIHBlcm8gZXN0byBubyBzaWduaWZpY2EgcXVlIGVuIHRvZG9zIGxvcyBjYXNvcyBzZXLDoSBhc8OtIHBvcnF1ZSBzb2xvIHNlIGhhIGVzcGVjaWZpY2FkbyAyIGNhcmFjdGVyw61zdGljYXMgcGFyYSBlbnRyZW5hciBlbCBDNTAuDQoNCiMjIyMgNzogQ29uY2x1c2lvbmVzDQoNCi1FbGVnaXJlbW9zIGNvbW8gbWVqb3IgbW9kZWxvIGVsIGFsZ29yaXRtbyBSaXBwZXIgKFJKaXApLCB5YSBxdWUgc2UgZXZpZGVuY2lhIHF1ZSBlbCBtb2RlbG8gZXNjb2dpZG8gcGFyYSBlbnRyZW5hciBsYSBkYXRhIG1hbmVqYSBwZXJmZWN0YW1lbnRlIGxhIGNsYXNlIGRlIGxhIHZhcmlhYmxlIG9iamV0aXZvICh2ZW5lbm9zby9jb21lc3RpYmxlKS4gTm8gaGF5IHByZXNlbmNpYSBkZSBlcnJvcmVzLCBjb24gdW5hIGFsdGEgcHJlY2lzacOzbiAxMDAlIHkgYWx0YSBzZW5zaWJpbGlkYWQgMTAwJSwgc2llbmRvIGVsIG1vZGVsbyBhbHRhbWVudGUgDQpjb25maWFibGUgZW4gbGEgcHJlZGljY2nDs24gZGUgbGEgY2xhc2UgdmVuZW5vc28sIHkgdGFtYmnDqW4gZGV0ZWN0YSBwZXJmZWN0YW1lbnRlIGxhIGNsYXNlIHZlbmVub3NvIHJlc3BlY3RvIGEgbG9zIHZhbG9yZXMgb2JzZXJ2YWRvcyBzaW4gcHJlc2VuY2lhIGRlIGZhbHNvcyBuZWdhdGl2b3MuDQoNCi1Qb3Igb3RybyBsYWRvLCB0YW50byBlbCBtb2RlbG8gT25lIFJ1bGUgY29tbyBlbCDDoXJib2wgZGUgZGVjaXNpw7NuIEM1LjAsIHNpIGdlbmVyYW4gZXJyb3IgZGUgMS40OCUuIFkgdW5hIHRhc2EgDQpkZSBmYWxzb3MgbmVnYXRpdm9zIGRlbCAzLjA2JSwgZXMgZGVjaXIsIGxhIHByb2JhYmlsaWRhZCBkZSBxdWUgc2UgY2xhc2lmaXF1ZSBjb21vIG5lZ2F0aXZvIChjb21lc3RpYmxlKSANCmN1YW5kbyBlbCB2YWxvciB2ZXJkYWRlcm8gc2VhIHBvc2l0aXZvICh2ZW5lbm9zbykNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0K