hllinas2023

1 Librerías

1.0.1 Para \(k\)-means

El software R dispone de varias funciones de diferentes paquetes para llevar a cabo un análisis de conglomerados:

library(stats)
library(factoextra)
library(cluster)

1.0.2 Para otros análisis

library(aplore3)    #Base de datos para los ejemplos
library(lsm)        #Base de datos para ejemplos y estimaciones del Log-verosimilitud
library(tidyverse)  #Incluye a dplyr y ggplot2
library(stringr)    #Reemplazar caracteres en un data frame
library(outliers)   #outliers::grubbs.test
library(EnvStats)   #EnvStats::rosnerTest
library(DMwR2)      #LOF (Local Outlier Factor)
library(rgl)        #rgl::plot3d
library(corrplot)   #Matriz de correlaciones
library(textshape)  #column_to_rownames
library(openxlsx)   #Librería para escribir archivos de Excel
library(mvtnorm)    #Generar una distribución aleatoria normal multivariante
library(caret)      #caret::confusionMatrix
library(knitr)      #crear tablas con estilo
library(kableExtra) #crear tablas con estilo, pero para html

2 Introducción

  1. El agrupamiento \(k\)-means, propuesto por MacQueen en 1967, es el algoritmo de aprendizaje automático no supervisado más utilizado para dividir un conjunto de datos en un número predefinido de grupos (\(k\) clusters).

  2. Este método clasifica los objetos en varios grupos, asegurando que los objetos dentro del mismo grupo sean lo más similares posible, mientras que los objetos de diferentes grupos sean lo más diferentes posible.

  3. Cada grupo en el clustering \(k\)-means se caracteriza por su centroide, que es la media de los puntos asignados al grupo.

  4. Véase las Figuras 2.1 y 2.2.

**$k$-means**

Figure 2.1: \(k\)-means

**Ejemplo práctico con $k$-means**

Figure 2.2: Ejemplo práctico con \(k\)-means

3 Idea básica del \(k\)-means

  1. La esencia fundamental del clustering \(k\)-means radica en la definición de grupos de tal manera que se minimice la variación total dentro de cada grupo (conocida como variación intra-cluster total).

  2. Hay diversos algoritmos de \(k\)-means disponibles, siendo el algoritmo estándar el de Hartigan-Wong (1979), el cual define la variación total dentro del cluster como la suma de las distancias euclidianas al cuadrado entre los elementos y sus centroides respectivos.

  3. Su objetivo es agrupar \(n\) puntos en \(k\) clústeres de manera que la suma de las distancias al cuadrado de cada punto a su clúster centroide correspondiente sea minimizada.

  4. Los pasos del algoritmo se van a explicar en la sección siguiente.

4 Algoritmo de Hartigan-Wong

4.0.1 \(k\)-means: pasos del algoritmo

El algoritmo \(k\)-means puede resumirse así:

1. Inicialización.

  • Especificar la cantidad de grupos (\(k\)) que se crearán (determinado por el analista).

  • Seleccionar aleatoriamente \(k\) objetos del conjunto de datos como los centroides iniciales (medias) de los grupos.

  • El centroide de un grupo es un vector de longitud \(p\) que contiene las medias de todas las variables para las observaciones en ese grupo, donde \(p\) es el número de variables.

2. Asignación de Clústeres.

  • Asignar cada observación a su centroide más cercano, basándose en la distancia euclidiana entre el objeto y el centroide.

  • Esta etapa se conoce como paso de asignación de grupos.

  • Es importante destacar que, para emplear la distancia de correlación, los datos se ingresan como puntuaciones \(Z\).

3. Actualización de Centroides.

  • Para cada uno de los \(k\) grupos, actualizar el centroide del grupo calculando los nuevos valores medios de todos los puntos de datos en el grupo.

  • Se utiliza el término actualización del centroide del grupo para describir esta fase.

  • Ahora que los centroides han sido recalculados, cada observación se revisa nuevamente para determinar si podría estar más cercana a otro grupo. Todos los objetos se reasignan utilizando los centroides de grupo actualizados.

4. Iteración.

  • Repetir los pasos de asignación y actualización hasta que la asignación de puntos a los clústeres no cambie significativamente entre iteraciones o hasta que se alcance un número máximo de iteraciones.

  • La idea es minimizar de manera iterativa la suma total de cuadrados dentro de los grupos, es decir, iterar los pasos 3 y 4 hasta que las asignaciones de grupo dejen de cambiar o se alcance el número máximo de iteraciones.

  • Por defecto, el software R utiliza 10 como valor predeterminado para el número máximo de iteraciones.

5. Regla de Hartigan.

  • En cada iteración, evaluar si mover un punto de su clúster actual a otro clúster reduce la suma total de cuadrados intra-clúster. Si es así, hacer el movimiento y actualizar los centroides.

6. Covergencia.

  • Los pasos de asignación de grupos y actualización de centroides se repiten de manera iterativa hasta que las asignaciones de grupo dejen de cambiar, es decir, hasta que se logre la convergencia.

  • El algoritmo converge cuando ya no se producen cambios significativos en la asignación de puntos a los clústeres o la suma de cuadrados intra-clúster no puede reducirse más.

  • Esto implica que los grupos formados en la iteración actual son los mismos que los obtenidos en la iteración anterior.

4.0.2 \(k\)-means: sumas de cuadrados

1. \(\text{Withinss}_k\) (Suma de cuadrados dentro del clúster \(k\)).

El llamado vector de suma de cuadrados dentro del clúster \(k\) es definido por:

\[\text{Withinss}_k \,=\; W(C_k) \;=\; \sum\limits_{x_i \in C_k} (x_i -\overline{x}_k)^2\;=\; \sum\limits_{x_i \in C_k} d_i^2\]

2. \(\text{Withinss}_k\) (notaciones).

En la fórmula anterior:

  • \(x_i\) representa un punto de datos que pertenece al grupo \(C_k\).

  • \(\overline{x}_k\) representa la media de los puntos asignados al grupo \(C_k\).

  • \(d_i = x_i - \overline{x}_k\) la distancia del punto \(x_i\) al centroide \(\overline{x}_k\), dentro del clúster \(C_k\).

3. \(\text{Withinss}_k\) (gráfico).

Véase la figura 4.1.

**Withinss$_k$ (vector de suma de cuadrados dentro del clúster $k$)**

Figure 4.1: Withinss\(_k\) (vector de suma de cuadrados dentro del clúster \(k\))

4. \(\text{Withinss}_k\) (asignación).

Cada observación (\(x_i\)) se asigna a un clúster específico de modo que la suma de los cuadrados (SS) de las distancias entre la observación y los centros de clúster asignados \(\overline{x}_k\) sea la menor posible.

5. \(\text{Tot.withinss}\) (variabilidad total dentro de todos los clúster).

  • La suma total de cuadrados dentro de todos los clústers es una medida de qué tan compacto es el agrupamiento (es decir, su calidad).

  • Representa la variabilidad total dentro de todos los clusters.

  • Un valor mayor de \(\text{Tot.withinss}\) indica que los puntos dentro de cada cluster están más dispersos, lo que sugiere una menor calidad de clustering.

  • Se define de la siguiente manera:

\[\text{Tot.withinss} \,=\;\sum\limits_{k=1}^K W(C_k) \;=\; \sum\limits_{k=1}^K\sum\limits_{x_i \in C_k} (x_i -\overline{x}_k)^2\;=\; \sum\limits_{k=1}^K\sum\limits_{x_i \in C_k} d_i^2\]

6. \(\text{Tot.withinss}\) (gráfico).

Véase la figura 4.2.

**Tot.withinss (variabilidad total dentro de todos los clúster)**

Figure 4.2: Tot.withinss (variabilidad total dentro de todos los clúster)


8. Objetivo final.

Nuestro objetivo es minimizar esta suma tanto como sea posible:

\[\min(\text{Tot.withinss}) \,=\;\min \sum\limits_{k=1}^K W(C_k)\]

4.0.3 \(k\)-means: proporción de varianza explicada

1. \(\text{Totss}\) (variabilidad total en todos los datos).

La suma total de cuadrados mide la variabilidad total en los datos y mide la variabilidad total en todos los datos (sin particionar los datos originales, como si solo tuviésemos un solo clúster).

2. \(\text{Totss}\) (definición).

La suma total de cuadrados se define como:

\[\text{Totss} \; =\; \sum\limits_{i=1}^n (x_i - \overline{x})^2\]

3. \(\text{Betweenss}\) (suma de cuadrados entre clústeres).

La suma de cuadrados entre clústeres, que mide la variación debido a las diferencias entre los centroides de los clústeres, se define como:

\[\text{Betweenss} \; = \; \text{Totss} \,-\, \text{Tot.withinss}\]

4. \(\text{Prop.Var}\) (proporción de varianza total).

La proporción de varianza total en los datos que es explicada por la variación entre los centroides de los clústeres (que es explicada por la agrupación de los datos en los clústeres) se calcula así:

\[\text{Prop.Var}\; = \;\frac{\text{Betweenss} }{\text{Totss}} \; = \; 1\;-\; \frac{\text{Tot.withinss}}{\text{Totss}}\]

5. \(\text{Prop.Var}\) (interpretación).

La interpretación de la proporción de varianza explicada es como sigue:

  • Un valor más alto (cercano a 100%) indica que los clústeres formados explican bien la variación total en los datos, sugiriendo que los clústeres están bien definidos y separados.

  • Un valor más bajo sugiere que los clústeres no explican bien la variación en los datos, lo que podría indicar que los clústeres no están bien definidos o que no hay una estructura clara en los datos.

5 Ejemplo: datos

5.0.1 Base de datos

Los datos se recogieron aplicando una encuesta a una muestra de estudiantes universitarios. Es un data frame con 800 observaciones y 66 variables. Con estos datos llevaremos a cabo un PCA.

datosCompleto <- lsm::survey
#datosCompleto <- textshape::column_to_rownames(dat, loc=1)
#datosCompleto %>% remove_rownames %>% column_to_rownames(var="names")   #library(tidyverse)
attach(datosCompleto)
names(datosCompleto)
##  [1] "Observation"  "ID"           "Gender"       "Like"         "Age"         
##  [6] "Smoke"        "Height"       "Weight"       "BMI"          "School"      
## [11] "SES"          "Enrollment"   "Score"        "MotherHeight" "MotherAge"   
## [16] "MotherCHD"    "FatherHeight" "FatherAge"    "FatherCHD"    "Status"      
## [21] "SemAcum"      "Exam1"        "Exam2"        "Exam3"        "Exam4"       
## [26] "ExamAcum"     "Definitive"   "Expense"      "Income"       "Gas"         
## [31] "Course"       "Law"          "Economic"     "Race"         "Region"      
## [36] "EMO1"         "EMO2"         "EMO3"         "EMO4"         "EMO5"        
## [41] "GOAL1"        "GOAL2"        "GOAL3"        "Pre_STAT1"    "Pre_STAT2"   
## [46] "Pre_STAT3"    "Pre_STAT4"    "Post_STAT1"   "Post_STAT2"   "Post_STAT3"  
## [51] "Post_STAT4"   "Pre_IDARE1"   "Pre_IDARE2"   "Pre_IDARE3"   "Pre_IDARE4"  
## [56] "Pre_IDARE5"   "Post_IDARE1"  "Post_IDARE2"  "Post_IDARE3"  "Post_IDARE4" 
## [61] "Post_IDARE5"  "PSICO1"       "PSICO2"       "PSICO3"       "PSICO4"      
## [66] "PSICO5"

5.0.2 Solo datos numéricos

Solo utilizaremos algunas variables numéricas. Los datos deben contener solo variables continuas, ya que el algoritmo \(k\)-means utiliza medias variables. Dado que no queremos que el algoritmo \(k\)-means dependa de una unidad de variable arbitraria, comenzamos escalando los datos utilizando la función scale de R de la siguiente manera:

dat <- datosCompleto[1:100, 21:24]
df <- scale(dat)
head(df)
##         SemAcum       Exam1     Exam2      Exam3
## [1,]  1.4571897 -1.68245049 1.6460816  1.9350576
## [2,] -0.9397758 -0.94735470 1.5482171  0.5559047
## [3,]  1.2918817  0.06340201 0.2759780 -1.2476029
## [4,] -0.2785439 -0.76358075 0.8631653  1.9350576
## [5,]  0.1347260 -0.21225891 0.1781135  1.9350576
## [6,] -1.0224297  0.43094991 1.0588944  1.0863481

6 Ejemplo: stats::kmeans y factorextra

6.0.1 Descripción de la función stats::kmeans

Usaremos la función kmeans del paquete stats. Un formato simple es:

kmeans(X, centers, iter.max = 10, nstart = 1)

Aquí:

  • X: es una matriz numérica, un data frame numérico o un vector numérico.

  • centers: los posibles valores son el número de agrupaciones (\(k\)) o un conjunto de centros de agrupación iniciales (distintos). Si se especifica un número, se elige un conjunto aleatorio de filas (distintas) en x como los centros iniciales.

  • iter.max: número de máximo de iteraciones permitidias. Por defecto es 10.

  • nstart: El número de particiones iniciales aleatorias cuando los centros es un número. Intentar nstart > 1 es a menudo recomendado.

6.0.2 La función factorextra

Para crear un gráfico atractivo de los conglomerados generados con la función kmeans, utilizaremos el paquete factoextra.

library(factoextra)

7 Estimando el número óptimo de clústers.

7.0.1 Pregunta central

El proceso de agrupamiento \(k\)-means requiere que los usuarios indiquen cuántos grupos desean generar. Una pregunta central es: ¿Cómo seleccionar el número adecuado de grupos (\(k\))?

7.0.2 Respuesta a pregunta central

  1. Consiste en realizar el agrupamiento \(k\)-means utilizando diferentes valores de \(k\).

  2. Luego, se grafica la suma de los cuadrados internos (wss) en función del número de grupos.

  3. Normalmente, la ubicación de un quiebre (punto de inflexión) en el gráfico se considera como un indicador del número apropiado de grupos.

7.0.3 Con R

La función fviz_nbclust del paquete factoextra brinda una solución práctica para estimar el número óptimo de grupos.

library(factoextra)
fviz_nbclust(df, kmeans, method = "wss") +
geom_vline(xintercept = 4, linetype = 2)

El gráfico ilustra cómo la variabilidad dentro de los grupos cambia con el número de grupos, \(k\). Esta variabilidad disminuye con \(k\), pero se nota un punto de inflexión o “codo” en \(k = 4\). Este punto sugiere que agregar más grupos después del cuarto no aporta mucho valor. En la siguiente sección, procederemos a clasificar las observaciones en 4 grupos.

8 Calculando el agrupamiento \(k\)-means

8.0.1 set.seed

  1. El algoritmo de agrupamiento \(k\)-means comienza seleccionando k centroides de manera aleatoria, por lo que es recomendable usar la función set.seed para fijar una semilla en el generador de números aleatorios de R.

  2. Esto garantiza que los resultados sean reproducibles, de modo que cualquier lector de este artículo obtenga los mismos resultados que se muestran a continuación.

8.0.2 kmeans

El código R a continuación ejecuta el agrupamiento \(k\)-means con \(k = 4\):

#Calcular k-means con k = 4

set.seed(123)
k <-4
km.res <- kmeans(df, centers = k, nstart = 25)

8.0.3 nstart

  1. Debido a que el resultado final del agrupamiento \(k\)-means depende de las asignaciones iniciales aleatorias, especificamos nstart = 25.

  2. Esto significa que R probará 25 asignaciones iniciales aleatorias diferentes y seleccionará los mejores resultados basados en la variación intra-cluster más baja.

  3. Aunque el valor predeterminado de nstart en R es uno, es altamente recomendable utilizar un valor más grande, como 25 o 50, para obtener resultados más estables.

8.0.4 Resultados finales

print(km.res)
## K-means clustering with 4 clusters of sizes 21, 27, 22, 30
## 
## Cluster means:
##       SemAcum      Exam1      Exam2      Exam3
## 1  1.21709954  0.1027821 -0.8051922  0.1669129
## 2  0.01839816 -0.4334683  0.9320329  0.8663123
## 3 -0.71811281 -0.8930579 -0.6759771 -0.1818939
## 4 -0.34191197  0.9730831  0.2205214 -0.7631313
## 
## Clustering vector:
##   [1] 2 2 1 2 2 2 4 1 1 3 2 3 2 3 1 3 3 1 3 2 2 2 2 2 3 4 4 3 3 2 1 3 1 1 3 4 4
##  [38] 1 4 1 2 4 1 4 3 2 1 4 4 2 4 2 4 1 4 2 3 4 4 3 2 4 4 2 2 4 2 4 1 2 4 3 3 4
##  [75] 1 1 3 4 3 4 3 4 4 3 4 1 1 4 3 4 1 4 2 4 1 2 1 3 2 2
## 
## Within cluster sum of squares by cluster:
## [1] 53.07428 57.81572 39.62427 60.63989
##  (between_SS / total_SS =  46.7 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

8.0.5 Interpretaciones

La salida impresa presenta:

  1. Los promedios o centros de los grupos: una matriz en la que las filas representan el número del grupo (1 a 4) y las columnas representan las variables.

  2. El vector de agrupamiento: Un conjunto de números enteros (de 1 a \(k\)) que señala el grupo al que se asigna cada punto.

9 Medias por clústers

9.0.1 Medias: Observación

Es posible calcular la media de cada variable (SemAcum, Exam1, Exam2 y Exam3) por clúster (1, 2, 3, 4) usando los datos originales. En las siguientes secciones, calcularemos esas 16 medias para los datos:

  1. Si escalar (dat).

  2. Escalados (df).

9.0.2 Usando los datos sin escalar (dat)

aggregate(dat, by=list(cluster=km.res$cluster), mean)
##   cluster  SemAcum    Exam1    Exam2    Exam3
## 1       1 4.104762 3.442857 2.495238 3.333333
## 2       2 3.379630 2.859259 4.270370 3.992593
## 3       3 2.934091 2.359091 2.627273 3.004545
## 4       4 3.161667 4.390000 3.543333 2.456667

9.0.3 Usando los datos escalados (df)

Observe que los resultados que se muestran abajo coinciden con el output de km.res.

aggregate(df, by=list(cluster=km.res$cluster), mean)
##   cluster     SemAcum      Exam1      Exam2      Exam3
## 1       1  1.21709954  0.1027821 -0.8051922  0.1669129
## 2       2  0.01839816 -0.4334683  0.9320329  0.8663123
## 3       3 -0.71811281 -0.8930579 -0.6759771 -0.1818939
## 4       4 -0.34191197  0.9730831  0.2205214 -0.7631313

10 Clasificar observaciones por clústers

Si se desea agregar las clasificaciones de los puntos a los datos originales, se puede ejecutar este código:

df.clasif <- cbind(dat, cluster = km.res$cluster)
head(df.clasif)
##   SemAcum Exam1 Exam2 Exam3 cluster
## 1    4.25   1.5   5.0   5.0       2
## 2    2.80   2.3   4.9   3.7       2
## 3    4.15   3.4   3.6   2.0       1
## 4    3.20   2.5   4.2   5.0       2
## 5    3.45   3.1   3.5   5.0       2
## 6    2.75   3.8   4.4   4.2       2

11 Output de kmeans

11.0.1 kmeans: valores que se pueden obtener

La función kmeans devuelve una lista de componentes que incluyen:

  1. cluster: Un vector de enteros (de 1 a \(k\)) que indica el clúster al que se asigna cada punto.

  2. centers: Una matriz de centros de clúster (medias de clúster).

  3. totss: La suma total de cuadrados y mide la variabilidad total en todos los datos (sin particionar los datos originales, como si solo tuviésemos un solo clúster). Es decir, \[\text{Totss} \; =\; \sum\limits_{i=1}^n(x_i - \overline{x})^2\]

  4. withinss: Vector de suma de cuadrados dentro del clúster, con un componente por clúster. Es decir,

\[\text{Withinss}_k \,=\; W(C_k) \;=\; \sum\limits_{x_i \in C_k} (x_i -\overline{x}_k)^2\]

  1. tot.withinss: Suma total de cuadrados dentro del clúster, es decir,

\[\text{Tot.withinss} \,=\;\sum\limits_{k=1}^K W(C_k) \;=\; \sum\limits_{k=1}^K\sum\limits_{x_i \in C_k} (x_i -\overline{x}_k)^2\]

  1. betweenss: La suma de cuadrados entre clústeres, es decir,

\[\text{Betweenss} \; = \; \text{Totss} \,-\, \text{Tot.withinss}\]

  1. size: El número de observaciones en cada clúster.

11.0.2 kmeans: con nuestros datos

Algunos de estos componentes pueden ser accedidos de la siguiente manera:

  1. size: El número de observaciones \(n_k\) en cada clúster \(k\). Observe: \[n\;=\; \sum\limits_{k=1}^K n_k \]
km.res$size
## [1] 21 27 22 30
  1. centers: Una matriz de centros de clúster (medias de clúster).
km.res$centers
##       SemAcum      Exam1      Exam2      Exam3
## 1  1.21709954  0.1027821 -0.8051922  0.1669129
## 2  0.01839816 -0.4334683  0.9320329  0.8663123
## 3 -0.71811281 -0.8930579 -0.6759771 -0.1818939
## 4 -0.34191197  0.9730831  0.2205214 -0.7631313
  1. cluster: Un vector de enteros (de 1 a \(k\)) que indica el clúster al que se asigna cada punto.
km.res$cluster
##   [1] 2 2 1 2 2 2 4 1 1 3 2 3 2 3 1 3 3 1 3 2 2 2 2 2 3 4 4 3 3 2 1 3 1 1 3 4 4
##  [38] 1 4 1 2 4 1 4 3 2 1 4 4 2 4 2 4 1 4 2 3 4 4 3 2 4 4 2 2 4 2 4 1 2 4 3 3 4
##  [75] 1 1 3 4 3 4 3 4 4 3 4 1 1 4 3 4 1 4 2 4 1 2 1 3 2 2
  1. totss: La suma total de cuadrados, que mide la variabilidad total en todos los datos (sin particionar los datos originales, como si solo tuviésemos un solo clúster). Es decir, \[\text{Totss} \; =\; \sum\limits_{i=1}^n (x_i - \overline{x})^2\]
km.res$totss
## [1] 396
  1. withinss: Vector de suma de cuadrados dentro del clúster, con un componente por clúster. Es decir,

\[\text{Withinss} \,=\; W(C_k) \;=\; \sum\limits_{x_i \in C_k} (x_i -\overline{x}_k)^2\]

km.res$withinss
## [1] 53.07428 57.81572 39.62427 60.63989
  1. tot.withinss: Suma total de cuadrados dentro del clúster, es decir,

\[\text{Tot.withinss} \,=\;\sum\limits_{k=1}^K W(C_k) \;=\; \sum\limits_{k=1}^K\sum\limits_{x_i \in C_k} (x_i -\overline{x}_k)^2\]

km.res$tot.withinss
## [1] 211.1542
  1. betweenss: La suma de cuadrados entre clústeres, que mide la variación debido a las diferencias entre los centroides de los clústeres. Es decir,

\[\text{Betweenss} \; = \; \text{Totss} \,-\, \text{Tot.withinss}\]

km.res$betweenss
## [1] 184.8458

11.0.3 kmeans: proporción de varianza explicada

La proporción de varianza total explicada por la variación entre los centroides de los clústeres se puede calcular así:

\[\text{Prop.Var}\; = \;\frac{\text{Betweenss} }{\text{Totss}} \; = \; 1\;-\; \frac{\text{Tot.withinss}}{\text{Totss}}\] En nuestro ejemplo:

\[\text{Prop.Var}\; = \;\frac{\text{Betweenss} }{\text{Totss}}\times 100 \,\% \; = \; \frac{184.8458}{396}\times 100 \,\% \; = \; 46.68\,\%\]

Se resalta que arriba se calculó el porcentaje de varianza total explicada por la variación entre los centroides de los clústeres. En R se puede calcular como se indica abajo. :

(km.res$betweenss/km.res$totss)*100
## [1] 46.67824

12 Visualizando los clústers \(k\)-means

12.0.1 Introducción

  1. Es recomendable graficar los resultados de los clústeres, ya que esto permite evaluar la elección del número de clústeres y comparar distintos análisis de clústeres.

  2. Ahora deseamos visualizar los datos en un gráfico de dispersión, coloreando cada punto según su asignación al clúster correspondiente.

  3. El reto surge cuando los datos tienen más de dos variables, lo que plantea la pregunta de qué variables seleccionar para el gráfico de dispersión en los ejes x e y.

  4. Una solución es aplicar un algoritmo de reducción de dimensionalidad, como el Análisis de Componentes Principales (PCA), que transforma las cuatro variables en dos nuevas variables (que representan a las originales) que pueden usarse para el gráfico.

  5. En otras palabras, si disponemos de un conjunto de datos multidimensional, una solución es realizar un Análisis de Componentes Principales (PCA) y graficar los puntos de datos según las coordenadas de los dos primeros componentes principales.

12.0.2 Aplicando factorextra::fviz_cluster

  1. La función fviz_cluster del paquete factoextra se puede utilizar para visualizar fácilmente los clústeres k-means.

  2. Esta función toma los resultados de k-means y los datos originales como argumentos.

  3. En el gráfico resultante, las observaciones se representan mediante puntos, utilizando componentes principales si el número de variables es superior a 2.

fviz_cluster(km.res, data=df,
             palette = c("blue", "darkorange4", "red", "magenta4"),
             xlab = FALSE, 
             ylab = FALSE, 
             geom="point")

  1. También es posible dibujar una elipse de concentración alrededor de cada clúster.
fviz_cluster(km.res, data = df,
            palette = c("blue", "darkorange4", "red", "magenta4"),
            ellipse.type = "euclid", # Elipse de concentración
            star.plot = TRUE,        # Segmentos desde centroide a ítems
            repel = TRUE,            # Evitar traslapamientos
            ggtheme = theme_minimal()
            )

13 Fortalezas y debilidades del algoritmo \(k\)-means

13.0.1 Fortalezas

  1. El algoritmo de clustering \(k\)-means es muy sencillo y rápido.

  2. Es eficiente para manejar conjuntos de datos muy grandes.

13.0.2 Debilidades

Sin embargo, presenta algunas debilidades, entre ellas:

  1. Requiere conocimiento previo del número de clusters: El analista debe elegir el número adecuado de clusters (k) de antemano.

  2. Sensibilidad a la selección inicial de centros de clusters: Los resultados finales dependen de la selección aleatoria inicial de los centros de los clusters. ¿Por qué es un problema? Porque cada ejecución del algoritmo puede seleccionar diferentes centros iniciales, lo que puede llevar a resultados de clustering distintos en diferentes ejecuciones del mismo conjunto de datos.

  3. Sensibilidad a los valores atípicos: El algoritmo es vulnerable a los outliers.

  4. Dependencia del orden de los datos: Si se reorganizan los datos, es probable que se obtenga una solución diferente cada vez que se cambie el orden de los datos.

13.0.3 Posibles Soluciones a las debilidades

  1. Para la elección del número de clusters (\(k\)): Ejecutar el algoritmo K-means para un rango de valores de k, por ejemplo, variando k entre 2 y 10. Luego, elegir el mejor \(k\) comparando los resultados de clustering obtenidos para los diferentes valores de \(k\).

  2. Para la sensibilidad a la selección inicial de centros: Ejecutar el algoritmo K-means varias veces con diferentes centros de clusters iniciales. Se selecciona como solución final la ejecución con la menor suma total de cuadrados dentro del cluster.

  3. Para mitigar la influencia de los outliers: Utilizar el algoritmo PAM (Partitioning Around Medoids), que es menos sensible a los valores atípicos.

14 Alternativa al agrupamiento \(k\)-means

  1. Una alternativa robusta al \(k\)-means es el PAM (Partitioning Around Medoids), que se basa en medoides.

  2. El agrupamiento PAM puede calcularse utilizando la función pam del paquete cluster.

  3. La función pamk del paquete fpc es un envoltorio para PAM que también imprime el número sugerido de clusters basado en el ancho promedio óptimo de la silueta.

15 Resumen

  1. El agrupamiento \(k\)-means puede usarse para clasificar observaciones en \(k\) grupos, basándose en su similitud.

  2. Cada grupo está representado por el valor medio de los puntos en el grupo, conocido como el centroide del cluster.

  3. El algoritmo \(k\)-means requiere que los usuarios especifiquen el número de clusters a generar.

  4. La función kmeans del paquete stats en R puede utilizarse para calcular el algoritmo $k4-means.

  5. El formato simplificado es kmeans(x, centers), donde x es el conjunto de datos y centers es el número de clusters a producir.

  6. Después de calcular el agrupamiento \(k\)-means, la función fviz_cluster del paquete factoextra puede usarse para visualizar los resultados.

  7. El formato es fviz_cluster(km.res, data), donde km.res son los resultados de \(k\)-means y data corresponde al conjunto de datos original.

16 Ejercicios

16.0.1 Ejercicio 1

Analicemos el siguiente conjunto de datos. Para su creación, generamos tres clusters utilizando distribuciones normales. Cada punto de datos incluye tres características: un valor x de una variable \(X\) , un valor y de \(Y\), y una clase que etiqueta el punto. Existen tres clases en este conjunto de datos (1, 2 y 3). El objetivo del ejercicio es que los algoritmos de clustering representen estas clases como clusters.

library(mvtnorm)

set.seed(123)
dataset <- {
        #crear las 3 distribuciones
        cluster1 = data.frame(rmvnorm(40, c(0, 0), diag(2)*c(2, 1))) %>% mutate(class=factor(1))
        cluster2 = data.frame(rmvnorm(100, c(3, 3), diag(2)*c(1, 3))) %>% mutate(class=factor(2))
        cluster3 = data.frame(rmvnorm(60, c(6, 6), diag(2))) %>% mutate(class=factor(3))
        #crear el data frame
        data = bind_rows(cluster1, cluster2, cluster3)
        #nombres de las columnas
        names(data) = c("x", "y", "clase")
        #retornar los datos
        data
        }
head(dataset)
##            x           y clase
## 1 -0.7926323 -0.23017749     1
## 2  2.2043464  0.07050839     1
## 3  0.1828405  1.71506499     1
## 4  0.6518339 -1.26506123     1
## 5 -0.9713566 -0.44566197     1
## 6  1.7311131  0.35981383     1

Para ello, realizar los siguientes incisos.

  1. Graficar los datos en un diagrama de dispersión usando ggplot y el argumento color=class para observar la agrupación natural que se intenta replicar.

  2. Aplicar la función fviz_nbclust del paquete factoextra para estimar el número óptimo de grupos.

  3. Defina un nuevo objeto que contenga solo las variables x y y.

df1 <- dataset[c("x", "y")]
head(df1)
##            x           y
## 1 -0.7926323 -0.23017749
## 2  2.2043464  0.07050839
## 3  0.1828405  1.71506499
## 4  0.6518339 -1.26506123
## 5 -0.9713566 -0.44566197
## 6  1.7311131  0.35981383
  1. Ejecutar el agrupamiento \(k\)-means con el número optimal de clústers obtenidos en el inciso anterior (utilizar nstar=20). LLame al nuevo objeto km.res1.

  2. Utilizar la función fviz_cluster para ver los resultados de la agrupación \(k\)-means.

16.0.2 Ejercicio 2

Continuación del ejercicio 1. Considere la situación planteada en el ejercicio 1 y los resultados encontrados allí.

  1. Considere el código de abajo. En la línea de código No. 1, se copia el dataset original dataset como un nuevo dataframe llamado dataset_km. En la línea de código No. 2, se añade una nueva columna llamada clase_pred al nuevo data frame dataset_km. Esta columna se rellena con los valores de las etiquetas de clústeres predichos por el modelo de \(k\)-means (km.res1$cluster). Los valores se convierten en factores (categóricos) usando la función factor. Compare los valores observados (clase) con los predichos (clase_pred).
#1.Copiar el dataset original 
dataset_km <- dataset 

#2. Añade una columna con las clases predichas
dataset_km['clase_pred'] = factor(km.res1$cluster) 
head(dataset_km)
  1. Contruir una tabla de frecuencias agrupadas entre los valores observados (clase) con los predichos (clase_pred) e interprete cada uno de los resultados encontrados en las celdas. En particular, verificar la precisión (accuracy) de la agrupación.

  2. Verificar la precisión (accuracy) de la agrupación en un gráfico. Compare nuevamente los valores observados (clase) con los predichos (clase_pred). Puede utilizar el siguiente código:

#3. El gráfico
dataset_km %>% ggplot(aes(x=x, y=y, shape=clase, color=clase_pred)) +
                      geom_point() +
                      coord_fixed() +
                      scale_shape_manual(values=c(0, 1, 2)) +
                      scale_shape(solid = TRUE)

16.0.3 Ejercicio 3

Continuación de los ejercicios 1 y 2. Considere la situación planteada en el ejercicio 1 y los resultados encontrados en ese ejercicio y en el 2.

Inciso i.

Investigue el concepto de matrix de confusión y las métricas que se obtienen a partir de ella. Explique las más importantes. Puede consultar los siguientes documentos (de mi autoría):

inciso j.

Aplique el código de abajo para obtener la matrix de confusión y las métricas correspondientes. Interprete los resultados obtenidos.

library(caret)
conf_mat <- confusionMatrix(factor(km.res1$cluster), factor(dataset$clase), 
                            mode = "everything", positive="1")
conf_mat

Bibliografía

Consultar el documento RPubs :: Análisis multivariado (bibliografía).

 

 
If you found any ERRORS or have SUGGESTIONS, please report them to my email. Thanks.  
LS0tDQp0aXRsZTogIkFOw4FMSVNJUyBERSBDT05HTE9NRVJBRE9TIg0Kc3VidGl0bGU6IDxoMT4qKkFsZ29yaXRtbyAkayQtbWVhbnMqKjwvaDE+DQoNCmF1dGhvcjogDQogIC0gbmFtZSAgICAgICAgICA6ICJEci4gcmVyLiBuYXQuIEh1bWJlcnRvIExMaW7DoXMgU29sYW5vIg0KICAgIGFmZmlsaWF0aW9uICAgOiAiRGVwYXJ0YW1lbnRvIGRlIE1hdGVtw6F0aWNhcyB5IEVzdGFkw61zdGljYSwgVW5pdmVyc2lkYWQgZGVsIE5vcnRlIChCYXJyYW5xdWlsbGEsIENvbG9tYmlhKSINCiAgICAgI2NvcnJlc3BvbmRpbmcgOiB5ZXMgICAgIyBEZWZpbmUgb25seSBvbmUgY29ycmVzcG9uZGluZyBhdXRob3INCiAgICAgI2FkZHJlc3MgICAgICAgOiAiRGVwYXJ0YW1lbnRvIGRlIE1hdGVtw6F0aWNhcyB5IEVzdGFkw61zdGljYSINCiAgICBlbWFpbCAgICAgICAgIDogfA0KICAgICAgaGxsaW5hc0B1bmlub3J0ZS5lZHUuY28NCiAgICAgIA0KICAgICAgW0Jpb2dyYXBoaWNhbCBza2V0Y2hdKGh0dHBzOi8vcnB1YnMuY29tL2hsbGluYXMvQmlvX1NrZXRjaCkNCiAgICAgIA0KICAgICAgYHIgZm9ybWF0KFN5cy50aW1lKCksICIlZC8lbS8leSIpYCANCiAgICAgIA0KICAgICAjcm9sZTogICAgICAgICAjIENvbnRyaWJ1dG9yc2hpcCByb2xlcyAoZS5nLiwgQ1JlZGlULCBodHRwczovL2Nhc3JhaS5vcmcvY3JlZGl0LykNCiAgIyAgICAtIENvbmNlcHR1YWxpemF0aW9uDQogICMgICAgLSBXcml0aW5nIC0gT3JpZ2luYWwgRHJhZnQgUHJlcGFyYXRpb24NCiAgIyAgICAtIFdyaXRpbmcgLSBSZXZpZXcgJiBFZGl0aW5nDQogIyAtIG5hbWUgICAgICAgICAgOiAiQXV0b3IgbnVtZXJvIDIiDQogIyAgIGFmZmlsaWF0aW9uICAgOiAiMSwyIg0KICMgICByb2xlOg0KICMgICAgIC0gV3JpdGluZyAtIFJldmlldyAmIEVkaXRpbmcNCiAgICAgI2FmZmlsaWF0aW9uOg0KICAjLSBpZCAgICAgICAgICAgIDogIjEiDQogICMgIGluc3RpdHV0aW9uICAgOiAiVW5pdmVyc2lkYWQgZGVsIE5vcnRlIChCYXJyYW5xdWlsbGEsIENvbG9tYmlhKSINCiAgIyFbXShobGxpbmFzLmpwZyl7d2lkdGg9MWlufSANCiAgDQojZGF0ZTogJ2ByIGZvcm1hdChTeXMudGltZSgpLCAiJWQvJW0vJXkiKWAnICAjIHZlciBodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ybWFya2Rvd24tY29va2Jvb2svdXBkYXRlLWRhdGUuaHRtbA0Kb3V0cHV0OiANCiAgICBib29rZG93bjo6aHRtbF9kb2N1bWVudDI6IA0KICAgICAgICAgICNPSk8gU2FsZW4gY2FwaXR1bG9zLCBzZWNjaW9uZXMgeSBUZW9yZW1hcw0KICAgICNib29rZG93bjo6aHRtbF9ib29rOg0KICAgICAgICAgICNPSk8gRVJST1IgU2FsZW4gdGVvcmVtYXMsIHBlcm8gbm8gc2FsZW4gbG9zIGNhcGl0dWxvcyANCiAgICAjaHRtbF9kb2N1bWVudDoNCiAgICAgICAgICB0b2M6IHRydWUgICAgICAjIHRhYmxlIG9mIGNvbnRlbnQgdHJ1ZQ0KICAgICAgICAgIHRvY19kZXB0aDogNCAgICMgdXB0byB0aHJlZSBkZXB0aHMgb2YgaGVhZGluZ3MgKHNwZWNpZmllZCBieSAjLCAjIyBhbmQgIyMjKQ0KICAgICAgICAgIHRvY19mbG9hdDogdHJ1ZSAjQ29uIHRydWUsIHRvYyBzYWxlIGFsIG1hcmdlbiBpenF1aWVyZG8gZGUgbGEgcMOhZ2luYTsgZGUgbG8gY29udHJhcmlvLCBhcnJpYmENCiAgICAgICAgICBjb2xsYXBzZWQ6IGZhbHNlDQogICAgICAgICAgc21vb3RoX3Njcm9sbDogZmFsc2UNCiAgICAgICAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUgICAjIGlmIHlvdSB3YW50IG51bWJlciBzZWN0aW9ucyBhdCBlYWNoIHRhYmxlIGhlYWRlcg0KICAgICAgICAgICN0aGVtZTogc2FuZHN0b25lDQogICAgICAgICAgI3RoZW1lOiB1bml0ZWQgICMgbWFueSBvcHRpb25zIGZvciB0aGVtZSwgdGhpcyBvbmUgaXMgbXkgZmF2b3JpdGUuDQogICAgICAgICAgI3RoZW1lOiBmbGF0bHkgICMgDQogICAgICAgICAgI3RoZW1lOiBjZXJ1bGVhbiAgIyANCiAgICAgICAgICAjaGlnaGxpZ2h0OiB0YW5nbyAgIyBzcGVjaWZpZXMgdGhlIHN5bnRheCBoaWdobGlnaHRpbmcgc3R5bGUNCiAgICAgICAgICAjY3NzOiBTY3JpcHRzIGFjY2Vzb3Jpb3MvZXN0aWxvYm90b24uY3NzDQogICAgICAgICAgI2NzczogbXkuY3NzICAgIyB5b3UgY2FuIGFkZCB5b3VyIGN1c3RvbSBjc3MsIHNob3VsZCBiZSBpbiBzYW1lIGZvbGRlcg0KICAgICAgICAgIGNvZGVfZG93bmxvYWQ6IHRydWUNCiAgICAgICAgICAjaGlnaGxpZ2h0OiB0YW5nbyAgIyBjYW1iaWFyIGNvbG9yIGRlIGxpYnJhcnkgZW4gYXp1bA0KICAgICMgYm9va2Rvd246OmdpdGJvb2s6DQogICAgIyAgICAgIGluY2x1ZGVzOg0KICAgICMgICAgICAgIGluX2hlYWRlcjogaGVhZGVyLmh0bWwNCiAgICAjIGJvb2tkb3duOjpwZGZfYm9vazoNCiAgICAjICAgICAgIGtlZXBfdGV4OiB5ZXMNCiAgICAjIGJvb2tkb3duOjpodG1sX2Jvb2s6DQogICAgIyAgICAgICBjc3M6IHRvYy5jc3MNCiAgICAjIGJvb2tkb3duOjpodG1sX2Jvb2s6DQogICAgIyAgICAgICAgIGluY2x1ZGVzOg0KICAgICMgICAgICAgICAgIGluX2hlYWRlcjogc3R5bGUuY3NzDQogICAgI2Jvb2tkb3duOjpodG1sX2RvY3VtZW50MjogZGVmYXVsdA0KICAgICMgYm9va2Rvd246OnBkZl9kb2N1bWVudDI6DQogICAgIyAgICAgIGtlZXBfdGV4OiB0cnVlDQogICAgI2JpYmxpb2dyYXBoeTogcmVmZXJlbmNlcy5iaWINCiAgICBtYXRoamF4OiAiaHR0cDovL2V4YW1wbGUuY29tL21hdGhqYXgvTWF0aEpheC5qcz9jb25maWc9VGVYLUFNUy1NTUxfSFRNTG9yTU1MIg0KaGVhZGVyLWluY2x1ZGVzOg0KICAgIFx1c2VwYWNrYWdlW3gxMW5hbWVzXXt4Y29sb3J9IA0KICAgIA0KY3NsOiBzY2llbmNlLmNzbA0KI09qbzogU2UgdXRpbGl6YSBsZW5ndWFqZSBZQU1MDQoNCmFic3RyYWN0OiB8DQogKipFbiBbUnB1YnM6OiB0b2NdKGh0dHBzOi8vcnB1YnMuY29tL2hsbGluYXMvdG9jKSBzZSBwdWVkZW4gdmVyIG90cm9zIGRvY3VtZW50b3MgZGUgcG9zaWJsZSBpbnRlcsOpcy4qKg0KICANCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgZmlnLmFsaWduPSJjZW50ZXIiLCAgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSMsDQogICAgICAgICAgICAgICAgICAgICAgI3N0eWxlID0gImNvbG9yOmRhcmtibHVlIg0KICAgICAgICAgICAgICAgICAgICAjIGNsYXNzLnNvdXJjZT0iYmctZGFuZ2VyIiwgY2xhc3Mub3V0cHV0PSJiZy13YXJuaW5nIiAgICNDb2xvcmVzIGRlbnRybyBkZWwgY2h1bmsNCiAgICAgICAgICAgICAgICAgICAgICkNCmxpYnJhcnkocmdsKQ0Ka25pdHI6OmtuaXRfaG9va3Mkc2V0KHdlYmdsID0gaG9va193ZWJnbCkNCmBgYA0KDQoNCg0KDQpgYGB7ciwgZWNobz1GQUxTRSwgZXZhbD1GQUxTRX0NCmh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL3JtYXJrZG93bi9sYW5ndWFnZS1lbmdpbmVzLmh0bWwNCg0KaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvYm9va2Rvd24vbWFya2Rvd24tc3ludGF4Lmh0bWwNCg0KaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvYm9va2Rvd24vYS1zaW5nbGUtZG9jdW1lbnQuaHRtbA0KDQpodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ib29rZG93bi9tYXJrZG93bi1leHRlbnNpb25zLWJ5LWJvb2tkb3duLmh0bWwNCg0KaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvcm1hcmtkb3duL2Jvb2tkb3duLW1hcmtkb3duLmh0bWwgICMgVGVvcmVtcyBhbmQgcHJvb2ZzDQoNCmh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL2Jvb2tkb3duL21hcmtkb3duLWV4dGVuc2lvbnMtYnktYm9va2Rvd24uaHRtbCN0aGVvcmVtcw0KDQpodHRwczovL2Jvb2tkb3duLm9yZy95aWh1aS9ib29rZG93bi9odG1sLmh0bWwNCg0KaHR0cHM6Ly93d3cuZGF0YS10by12aXouY29tLw0KICANCltScHVic10obGluaykNCiAgDQooXCNlcTplYy0pLCAgRWN1YWNpb24gXEByZWYoZXE6ZWMtKSwgRmlndXJhIFxAcmVmKGZpZzpGaWctKSwgVGFibGUgXEByZWYodGFiOm10Y2FycyksIFRoZW9yZW0gXEByZWYodGhtOmJvcmluZykNCg0KDQojIFRpdHVsbyB7I1RpdHVsb1NlY2Npb259ICAgXEByZWYoVGl0dWxvU2VjY2lvbikNCiAgDQojIEZvciBIVE1MLCB3ZSBjYW4gc2V0IGNvbG9yIHdpdGggQ1NTLCBlLmcuLCA8c3BhbiBzdHlsZT0iY29sb3I6IHJlZDsiPnRleHQ8L3NwYW4+DQogIA0KIyBodHRwczovL3JhZGlhbnQtcnN0YXRzLmdpdGh1Yi5pby9kb2NzL21vZGVsL2xvZ2lzdGljLmh0bWwgU2hpbm55IExvZ2l0ICANCiAgDQpgYGANCg0KYGBge3IsIGV2YWw9RkFMU0UsIGVjaG89RkFMU0V9DQojTGEgZm90byB0YW1hw7FvIGPDqWR1bGENCg0KaHRtbHRvb2xzOjppbWcoc3JjID0ga25pdHI6OmltYWdlX3VyaShmaWxlLnBhdGgoUi5ob21lKCJkb2MiKSwgImh0bWwiLCAibG9nby5qcGciKSksIA0KICAgICAgICAgICAgICAgYWx0ID0gJ2hsbGluYXMnLCANCiAgICAgICAgICAgICAgIHN0eWxlID0gJ3Bvc2l0aW9uOmFic29sdXRlOyB0b3A6MDsgcmlnaHQ6MDsgcGFkZGluZzoxMHB4OycgIywNCiAgICAgICAgICAgICAgIHdpZHRoID0gIjIwMHB4IikgICMgQXF1w60gZXNwZWNpZmljYXMgZWwgYW5jaG8gZGVzZWFkbyBlbiBww614ZWxlcyBvIHBvcmNlbnRhamUNCmBgYA0KDQoNCg0KDQpgYGB7ciwgZWNobz1GQUxTRSwgfQ0KIyBMYSBmb3RvIGdyYW5kZQ0KDQpodG1sdG9vbHM6OmltZyhzcmMgPSBrbml0cjo6aW1hZ2VfdXJpKCJobGxpbmFzMjAyMy5qcGciKSwgDQogICAgICAgICAgICAgICBhbHQgPSAnaGxsaW5hczIwMjMnLCANCiAgICAgICAgICAgICAgIHN0eWxlID0gJ3Bvc2l0aW9uOmFic29sdXRlOyB0b3A6MDsgcmlnaHQ6MDsgcGFkZGluZzoxcHg7JywNCiAgICAgICAgICAgICAgIHdpZHRoPSIxNSUiKQ0KYGBgDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgIC0tPg0KDQpgYGB7Y3NzLCBlY2hvPUZBTFNFfQ0KLmNvbHVtbnMge2Rpc3BsYXk6IGZsZXg7fQ0KaDEge2NvbG9yOiBkYXJrYmx1ZTt9DQpoMyB7Y29sb3I6IGRhcmtncmVlbjt9DQpoNCB7Y29sb3I6IGdyZWVuO30NCmBgYA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAgLS0+DQoNCiMgTGlicmVyw61hcw0KDQojIyMgUGFyYSAkayQtbWVhbnMNCg0KDQpgYGB7ciwgZXZhbD1GQUxTRSwgZWNobz1GQUxTRX0NCiMgIGh0dHBzOi8vcnB1YnMuY29tL0FsZW1hLzEwMDA1ODINCg0KIyBodHRwczovL3d3dy5nZWVrc2ZvcmdlZWtzLm9yZy9jb250ZXh0dWFsLW91dGxpZXJzLw0KYGBgDQoNCkVsIHNvZnR3YXJlIFIgZGlzcG9uZSBkZSB2YXJpYXMgZnVuY2lvbmVzIGRlIGRpZmVyZW50ZXMgcGFxdWV0ZXMgcGFyYSBsbGV2YXIgYSBjYWJvIHVuIGFuw6FsaXNpcyBkZSBjb25nbG9tZXJhZG9zOg0KDQpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbGlicmFyeShzdGF0cykNCmxpYnJhcnkoZmFjdG9leHRyYSkNCmxpYnJhcnkoY2x1c3RlcikNCmBgYA0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgIC0tPg0KDQojIyMgUGFyYSBvdHJvcyBhbsOhbGlzaXMNCg0KYGBge3IsICBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBlY2hvPUZBTFNFfQ0KbGlicmFyeShhcGxvcmUzKSAgICAjQmFzZSBkZSBkYXRvcyBwYXJhIGxvcyBlamVtcGxvcw0KbGlicmFyeShsc20pICAgICAgICAjQmFzZSBkZSBkYXRvcyBwYXJhIGVqZW1wbG9zIHkgZXN0aW1hY2lvbmVzIGRlbCBMb2ctdmVyb3NpbWlsaXR1ZA0KbGlicmFyeSh0aWR5dmVyc2UpICAjSW5jbHV5ZSBhIGRwbHlyIHkgZ2dwbG90Mg0KbGlicmFyeShzdHJpbmdyKSAgICAjUmVlbXBsYXphciBjYXJhY3RlcmVzIGVuIHVuIGRhdGEgZnJhbWUNCmxpYnJhcnkob3V0bGllcnMpICAgI291dGxpZXJzOjpncnViYnMudGVzdA0KbGlicmFyeShFbnZTdGF0cykgICAjRW52U3RhdHM6OnJvc25lclRlc3QNCmxpYnJhcnkoRE13UjIpICAgICAgI0xPRiAoTG9jYWwgT3V0bGllciBGYWN0b3IpDQpsaWJyYXJ5KHJnbCkgICAgICAgICNyZ2w6OnBsb3QzZA0KbGlicmFyeShjb3JycGxvdCkgICAjTWF0cml6IGRlIGNvcnJlbGFjaW9uZXMNCmxpYnJhcnkodGV4dHNoYXBlKSAgI2NvbHVtbl90b19yb3duYW1lcw0KbGlicmFyeShvcGVueGxzeCkgICAjTGlicmVyw61hIHBhcmEgZXNjcmliaXIgYXJjaGl2b3MgZGUgRXhjZWwNCmxpYnJhcnkobXZ0bm9ybSkgICAgI0dlbmVyYXIgdW5hIGRpc3RyaWJ1Y2nDs24gYWxlYXRvcmlhIG5vcm1hbCBtdWx0aXZhcmlhbnRlDQpsaWJyYXJ5KGNhcmV0KSAgICAgICNjYXJldDo6Y29uZnVzaW9uTWF0cml4DQpsaWJyYXJ5KGtuaXRyKSAgICAgICNjcmVhciB0YWJsYXMgY29uIGVzdGlsbw0KbGlicmFyeShrYWJsZUV4dHJhKSAjY3JlYXIgdGFibGFzIGNvbiBlc3RpbG8sIHBlcm8gcGFyYSBodG1sDQojb3B0c19rbml0JHNldChldmFsLmFmdGVyID0gJ2ZpZy5jYXAnKQ0KYGBgDQoNCmBgYHtjc3MsIGVjaG89RkFMU0UsIGV2YWw9RkFMU0V9DQojaHR0cHM6Ly9zdGFja292ZXJmbG93LmNvbS9xdWVzdGlvbnMvNDEwMzA0NzcvY2hhbmdpbmctY2h1bmstYmFja2dyb3VuZC1jb2xvci1pbi1ybWFya2Rvd24NCg0KLmJhZENvZGUgew0KYmFja2dyb3VuZC1jb2xvcjogcmVkOw0KfQ0KYGBgDQoNCmBgYHtyLCAgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZXZhbD1GQUxTRX0NCmxpYnJhcnkoYXBsb3JlMykgICAgI0Jhc2UgZGUgZGF0b3MgcGFyYSBsb3MgZWplbXBsb3MNCmxpYnJhcnkobHNtKSAgICAgICAgI0Jhc2UgZGUgZGF0b3MgcGFyYSBlamVtcGxvcyB5IGVzdGltYWNpb25lcyBkZWwgTG9nLXZlcm9zaW1pbGl0dWQNCmxpYnJhcnkodGlkeXZlcnNlKSAgI0luY2x1eWUgYSBkcGx5ciB5IGdncGxvdDINCmxpYnJhcnkoc3RyaW5ncikgICAgI1JlZW1wbGF6YXIgY2FyYWN0ZXJlcyBlbiB1biBkYXRhIGZyYW1lDQpsaWJyYXJ5KG91dGxpZXJzKSAgICNvdXRsaWVyczo6Z3J1YmJzLnRlc3QNCmxpYnJhcnkoRW52U3RhdHMpICAgI0VudlN0YXRzOjpyb3NuZXJUZXN0DQpsaWJyYXJ5KERNd1IyKSAgICAgICNMT0YgKExvY2FsIE91dGxpZXIgRmFjdG9yKQ0KbGlicmFyeShyZ2wpICAgICAgICAjcmdsOjpwbG90M2QNCmxpYnJhcnkoY29ycnBsb3QpICAgI01hdHJpeiBkZSBjb3JyZWxhY2lvbmVzDQpsaWJyYXJ5KHRleHRzaGFwZSkgICNjb2x1bW5fdG9fcm93bmFtZXMNCmxpYnJhcnkob3Blbnhsc3gpICAgI0xpYnJlcsOtYSBwYXJhIGVzY3JpYmlyIGFyY2hpdm9zIGRlIEV4Y2VsDQpsaWJyYXJ5KG12dG5vcm0pICAgICNHZW5lcmFyIHVuYSBkaXN0cmlidWNpw7NuIGFsZWF0b3JpYSBub3JtYWwgbXVsdGl2YXJpYW50ZQ0KbGlicmFyeShjYXJldCkgICAgICAjY2FyZXQ6OmNvbmZ1c2lvbk1hdHJpeA0KbGlicmFyeShrbml0cikgICAgICAjY3JlYXIgdGFibGFzIGNvbiBlc3RpbG8NCmxpYnJhcnkoa2FibGVFeHRyYSkgI2NyZWFyIHRhYmxhcyBjb24gZXN0aWxvLCBwZXJvIHBhcmEgaHRtbA0KYGBgDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMgSW50cm9kdWNjacOzbg0KDQoxLiBFbCBhZ3J1cGFtaWVudG8gJGskLW1lYW5zLCBwcm9wdWVzdG8gcG9yIE1hY1F1ZWVuIGVuIDE5NjcsIGVzIGVsIGFsZ29yaXRtbyBkZSBhcHJlbmRpemFqZSBhdXRvbcOhdGljbyBubyBzdXBlcnZpc2FkbyBtw6FzIHV0aWxpemFkbyBwYXJhIGRpdmlkaXIgdW4gY29uanVudG8gZGUgZGF0b3MgZW4gdW4gbsO6bWVybyBwcmVkZWZpbmlkbyBkZSBncnVwb3MgKCRrJCBjbHVzdGVycykuIA0KDQoyLiBFc3RlIG3DqXRvZG8gY2xhc2lmaWNhIGxvcyBvYmpldG9zIGVuIHZhcmlvcyBncnVwb3MsIGFzZWd1cmFuZG8gcXVlIGxvcyBvYmpldG9zIGRlbnRybyBkZWwgbWlzbW8gZ3J1cG8gc2VhbiBsbyBtw6FzIHNpbWlsYXJlcyBwb3NpYmxlLCBtaWVudHJhcyBxdWUgbG9zIG9iamV0b3MgZGUgZGlmZXJlbnRlcyBncnVwb3Mgc2VhbiBsbyBtw6FzIGRpZmVyZW50ZXMgcG9zaWJsZS4gDQoNCjMuIENhZGEgZ3J1cG8gZW4gZWwgY2x1c3RlcmluZyAkayQtbWVhbnMgc2UgY2FyYWN0ZXJpemEgcG9yIHN1IGNlbnRyb2lkZSwgcXVlIGVzIGxhIG1lZGlhIGRlIGxvcyBwdW50b3MgYXNpZ25hZG9zIGFsIGdydXBvLg0KDQo0LiBWw6lhc2UgbGFzIEZpZ3VyYXMgXEByZWYoZmlnOmttZWFuMSkgeSBcQHJlZihmaWc6a21lYW4yKS4gDQoNCjxjZW50ZXI+DQoNCmBgYHtyIGttZWFuMSwgZWNobz1GQUxTRSwgZmlnLmNhcCA9ICIqKiRrJC1tZWFucyoqIiwgb3V0LndpZHRoID0gIjcwJSJ9DQojIGZpZy53aWR0aCA9IDIwICMgTm8gZnVuY2lvbmEgZXN0YSBvcGNpb24gZW4gZWwgY2h1bmsNCg0KI2h0dHA6Ly96ZXZyb3NzLmNvbS9ibG9nLzIwMTcvMDYvMTkvdGlwcy1hbmQtdHJpY2tzLWZvci13b3JraW5nLXdpdGgtaW1hZ2VzLWFuZC1maWd1cmVzLWluLXItbWFya2Rvd24tZG9jdW1lbnRzLw0KIyBQYWdpbmEgMzU5IGRlIFIyMDE1LUZyaWVuZGx5DQoNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJrbWVhbjEucG5nIikNCg0KI090cmEgbWFuZXJhLCBwZXJvICBzYWxlIGVsIGNhcHRpb246DQojPGNlbnRlcj4NCiMhWygjZmlnOkZpZy1jYXB0aW9uKSBNaSBmaWd1cmFdKE5vbWJyZS5wbmcpe3dpZHRoPTQwMHB4fQ0KIzwvY2VudGVyPg0KYGBgDQo8L2NlbnRlcj4NCg0KPGNlbnRlcj4NCg0KYGBge3Iga21lYW4yLCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIioqRWplbXBsbyBwcsOhY3RpY28gY29uICRrJC1tZWFucyoqIiwgb3V0LndpZHRoID0gIjYwJSJ9DQojIGZpZy53aWR0aCA9IDIwICMgTm8gZnVuY2lvbmEgZXN0YSBvcGNpb24gZW4gZWwgY2h1bmsNCg0KI2h0dHA6Ly96ZXZyb3NzLmNvbS9ibG9nLzIwMTcvMDYvMTkvdGlwcy1hbmQtdHJpY2tzLWZvci13b3JraW5nLXdpdGgtaW1hZ2VzLWFuZC1maWd1cmVzLWluLXItbWFya2Rvd24tZG9jdW1lbnRzLw0KIyBQYWdpbmEgMzU5IGRlIFIyMDE1LUZyaWVuZGx5DQoNCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJrbWVhbjIucG5nIikNCg0KI090cmEgbWFuZXJhLCBwZXJvICBzYWxlIGVsIGNhcHRpb246DQojPGNlbnRlcj4NCiMhWygjZmlnOkZpZy1jYXB0aW9uKSBNaSBmaWd1cmFdKE5vbWJyZS5wbmcpe3dpZHRoPTQwMHB4fQ0KIzwvY2VudGVyPg0KYGBgDQo8L2NlbnRlcj4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyBJZGVhIGLDoXNpY2EgZGVsICRrJC1tZWFucw0KDQoxLiBMYSBlc2VuY2lhIGZ1bmRhbWVudGFsIGRlbCBjbHVzdGVyaW5nICRrJC1tZWFucyByYWRpY2EgZW4gbGEgZGVmaW5pY2nDs24gZGUgZ3J1cG9zIGRlIHRhbCBtYW5lcmEgcXVlIHNlIG1pbmltaWNlIGxhIHZhcmlhY2nDs24gdG90YWwgZGVudHJvIGRlIGNhZGEgZ3J1cG8gKGNvbm9jaWRhIGNvbW8gdmFyaWFjacOzbiBpbnRyYS1jbHVzdGVyIHRvdGFsKS4gDQoNCjIuIEhheSBkaXZlcnNvcyBhbGdvcml0bW9zIGRlICRrJC1tZWFucyBkaXNwb25pYmxlcywgc2llbmRvIGVsICoqYWxnb3JpdG1vIGVzdMOhbmRhciBlbCBkZSBIYXJ0aWdhbi1Xb25nICgxOTc5KSoqLCBlbCBjdWFsIGRlZmluZSBsYSB2YXJpYWNpw7NuIHRvdGFsIGRlbnRybyBkZWwgY2x1c3RlciBjb21vIGxhIHN1bWEgZGUgbGFzIGRpc3RhbmNpYXMgZXVjbGlkaWFuYXMgYWwgY3VhZHJhZG8gZW50cmUgbG9zIGVsZW1lbnRvcyB5IHN1cyBjZW50cm9pZGVzIHJlc3BlY3Rpdm9zLiANCg0KMy4gU3Ugb2JqZXRpdm8gZXMgYWdydXBhciAkbiQgcHVudG9zIGVuICRrJCBjbMO6c3RlcmVzIGRlIG1hbmVyYSBxdWUgKmxhIHN1bWEgZGUgbGFzIGRpc3RhbmNpYXMgYWwgY3VhZHJhZG8qIGRlIGNhZGEgcHVudG8gYSBzdSBjbMO6c3RlciBjZW50cm9pZGUgY29ycmVzcG9uZGllbnRlIHNlYSBtaW5pbWl6YWRhLg0KDQo0LiBMb3MgcGFzb3MgZGVsIGFsZ29yaXRtbyBzZSB2YW4gYSBleHBsaWNhciBlbiBsYSBzZWNjacOzbiBzaWd1aWVudGUuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMgQWxnb3JpdG1vIGRlIEhhcnRpZ2FuLVdvbmcNCg0KDQojIyMgJGskLW1lYW5zOiBwYXNvcyBkZWwgYWxnb3JpdG1vIA0KDQpFbCBhbGdvcml0bW8gJGskLW1lYW5zIHB1ZWRlIHJlc3VtaXJzZSBhc8OtOg0KDQoqKjEuIEluaWNpYWxpemFjacOzbi4qKg0KICANCiAgKyBFc3BlY2lmaWNhciBsYSBjYW50aWRhZCBkZSBncnVwb3MgKCRrJCkgcXVlIHNlIGNyZWFyw6FuIChkZXRlcm1pbmFkbyBwb3IgZWwgYW5hbGlzdGEpLg0KICANCiAgKyBTZWxlY2Npb25hciBhbGVhdG9yaWFtZW50ZSAkayQgb2JqZXRvcyBkZWwgY29uanVudG8gZGUgZGF0b3MgY29tbyBsb3MgY2VudHJvaWRlcyBpbmljaWFsZXMgKG1lZGlhcykgZGUgbG9zIGdydXBvcy4NCiAgDQogICsgRWwgY2VudHJvaWRlIGRlIHVuIGdydXBvIGVzIHVuIHZlY3RvciBkZSBsb25naXR1ZCAkcCQgcXVlIGNvbnRpZW5lIGxhcyBtZWRpYXMgZGUgdG9kYXMgbGFzIHZhcmlhYmxlcyBwYXJhIGxhcyBvYnNlcnZhY2lvbmVzIGVuIGVzZSBncnVwbywgIGRvbmRlICRwJCBlcyBlbCBuw7ptZXJvIGRlIHZhcmlhYmxlcy4NCiAgDQogIA0KICANCioqMi4gQXNpZ25hY2nDs24gZGUgQ2zDunN0ZXJlcy4qKg0KICANCiAgKyBBc2lnbmFyIGNhZGEgb2JzZXJ2YWNpw7NuIGEgc3UgY2VudHJvaWRlIG3DoXMgY2VyY2FubywgYmFzw6FuZG9zZSBlbiBsYSBkaXN0YW5jaWEgZXVjbGlkaWFuYSBlbnRyZSBlbCBvYmpldG8geSBlbCBjZW50cm9pZGUuDQogIA0KICArIEVzdGEgZXRhcGEgc2UgY29ub2NlIGNvbW8gKnBhc28gZGUgYXNpZ25hY2nDs24gZGUgZ3J1cG9zKi4NCiAgDQogICsgRXMgaW1wb3J0YW50ZSBkZXN0YWNhciBxdWUsIHBhcmEgZW1wbGVhciBsYSBkaXN0YW5jaWEgZGUgY29ycmVsYWNpw7NuLCBsb3MgZGF0b3Mgc2UgaW5ncmVzYW4gY29tbyBwdW50dWFjaW9uZXMgJFokLg0KDQoNCioqMy4gQWN0dWFsaXphY2nDs24gZGUgQ2VudHJvaWRlcyoqLg0KICANCiAgKyBQYXJhIGNhZGEgdW5vIGRlIGxvcyAkayQgZ3J1cG9zLCBhY3R1YWxpemFyIGVsIGNlbnRyb2lkZSBkZWwgZ3J1cG8gY2FsY3VsYW5kbyBsb3MgbnVldm9zIHZhbG9yZXMgbWVkaW9zIGRlIHRvZG9zIGxvcyBwdW50b3MgZGUgZGF0b3MgZW4gZWwgZ3J1cG8uIA0KICANCiAgKyBTZSB1dGlsaXphIGVsIHTDqXJtaW5vICphY3R1YWxpemFjacOzbiBkZWwgY2VudHJvaWRlIGRlbCBncnVwbyogcGFyYSBkZXNjcmliaXIgZXN0YSBmYXNlLg0KICANCiAgKyBBaG9yYSBxdWUgbG9zIGNlbnRyb2lkZXMgaGFuIHNpZG8gcmVjYWxjdWxhZG9zLCBjYWRhIG9ic2VydmFjacOzbiBzZSByZXZpc2EgbnVldmFtZW50ZSBwYXJhIGRldGVybWluYXIgc2kgcG9kcsOtYSBlc3RhciBtw6FzIGNlcmNhbmEgYSBvdHJvIGdydXBvLiBUb2RvcyBsb3Mgb2JqZXRvcyBzZSByZWFzaWduYW4gdXRpbGl6YW5kbyBsb3MgY2VudHJvaWRlcyBkZSBncnVwbyBhY3R1YWxpemFkb3MuDQogDQoqKjQuIEl0ZXJhY2nDs24uKiogDQogIA0KICArIFJlcGV0aXIgbG9zIHBhc29zIGRlIGFzaWduYWNpw7NuIHkgYWN0dWFsaXphY2nDs24gaGFzdGEgcXVlIGxhIGFzaWduYWNpw7NuIGRlIHB1bnRvcyBhIGxvcyBjbMO6c3RlcmVzIG5vIGNhbWJpZSBzaWduaWZpY2F0aXZhbWVudGUgZW50cmUgaXRlcmFjaW9uZXMgbyBoYXN0YSBxdWUgc2UgYWxjYW5jZSB1biBuw7ptZXJvIG3DoXhpbW8gZGUgaXRlcmFjaW9uZXMuDQogIA0KICArIExhIGlkZWEgZXMgbWluaW1pemFyIGRlIG1hbmVyYSBpdGVyYXRpdmEgbGEgc3VtYSB0b3RhbCBkZSBjdWFkcmFkb3MgZGVudHJvIGRlIGxvcyBncnVwb3MsIGVzIGRlY2lyLCBpdGVyYXIgbG9zIHBhc29zIDMgeSA0IGhhc3RhIHF1ZSBsYXMgYXNpZ25hY2lvbmVzIGRlIGdydXBvIGRlamVuIGRlIGNhbWJpYXIgbyBzZSBhbGNhbmNlIGVsIG7Dum1lcm8gbcOheGltbyBkZSBpdGVyYWNpb25lcy4gDQogIA0KICArIFBvciBkZWZlY3RvLCBlbCBzb2Z0d2FyZSBgUmAgdXRpbGl6YSAxMCBjb21vIHZhbG9yIHByZWRldGVybWluYWRvIHBhcmEgZWwgbsO6bWVybyBtw6F4aW1vIGRlIGl0ZXJhY2lvbmVzLg0KICANCiAgDQoqKjUuIFJlZ2xhIGRlIEhhcnRpZ2FuLioqDQogIA0KICArIEVuIGNhZGEgaXRlcmFjacOzbiwgZXZhbHVhciBzaSBtb3ZlciB1biBwdW50byBkZSBzdSBjbMO6c3RlciBhY3R1YWwgYSBvdHJvIGNsw7pzdGVyIHJlZHVjZSBsYSBzdW1hIHRvdGFsIGRlIGN1YWRyYWRvcyBpbnRyYS1jbMO6c3Rlci4gU2kgZXMgYXPDrSwgaGFjZXIgZWwgbW92aW1pZW50byB5IGFjdHVhbGl6YXIgbG9zIGNlbnRyb2lkZXMuDQogIA0KDQoqKjYuIENvdmVyZ2VuY2lhLioqDQoNCiAgICsgTG9zIHBhc29zIGRlIGFzaWduYWNpw7NuIGRlIGdydXBvcyB5IGFjdHVhbGl6YWNpw7NuIGRlIGNlbnRyb2lkZXMgc2UgcmVwaXRlbiBkZSBtYW5lcmEgaXRlcmF0aXZhIGhhc3RhIHF1ZSBsYXMgYXNpZ25hY2lvbmVzIGRlIGdydXBvIGRlamVuIGRlIGNhbWJpYXIsIGVzIGRlY2lyLCBoYXN0YSBxdWUgc2UgbG9ncmUgbGEgY29udmVyZ2VuY2lhLg0KDQogKyBFbCBhbGdvcml0bW8gY29udmVyZ2UgY3VhbmRvIHlhIG5vIHNlIHByb2R1Y2VuIGNhbWJpb3Mgc2lnbmlmaWNhdGl2b3MgZW4gbGEgYXNpZ25hY2nDs24gZGUgcHVudG9zIGEgbG9zIGNsw7pzdGVyZXMgbyBsYSBzdW1hIGRlIGN1YWRyYWRvcyBpbnRyYS1jbMO6c3RlciBubyBwdWVkZSByZWR1Y2lyc2UgbcOhcy4NCiANCiArIEVzdG8gaW1wbGljYSBxdWUgbG9zIGdydXBvcyBmb3JtYWRvcyBlbiBsYSBpdGVyYWNpw7NuIGFjdHVhbCBzb24gbG9zIG1pc21vcyBxdWUgbG9zIG9idGVuaWRvcyBlbiBsYSBpdGVyYWNpw7NuIGFudGVyaW9yLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMgJGskLW1lYW5zOiBzdW1hcyBkZSBjdWFkcmFkb3MNCg0KKioxLiAkXHRleHR7V2l0aGluc3N9X2skIChTdW1hIGRlIGN1YWRyYWRvcyBkZW50cm8gZGVsIGNsw7pzdGVyICRrJCkuKioNCg0KRWwgbGxhbWFkbyAqdmVjdG9yIGRlIHN1bWEgZGUgY3VhZHJhZG9zIGRlbnRybyBkZWwgY2zDunN0ZXIgJGskKiAgZXMgZGVmaW5pZG8gcG9yOiANCg0KJCRcdGV4dHtXaXRoaW5zc31fayBcLD1cOyBXKENfaykgXDs9XDsgXHN1bVxsaW1pdHNfe3hfaSBcaW4gQ19rfSAoeF9pIC1cb3ZlcmxpbmV7eH1fayleMlw7PVw7IFxzdW1cbGltaXRzX3t4X2kgXGluIENfa30gZF9pXjIkJA0KDQoqKjIuICRcdGV4dHtXaXRoaW5zc31fayQgKG5vdGFjaW9uZXMpKiouDQoNCkVuIGxhIGbDs3JtdWxhIGFudGVyaW9yOiANCg0KICAgKyAkeF9pJCByZXByZXNlbnRhIHVuIHB1bnRvIGRlIGRhdG9zIHF1ZSBwZXJ0ZW5lY2UgYWwgZ3J1cG8gJENfayQuDQogICAgDQogICArICRcb3ZlcmxpbmV7eH1fayQgcmVwcmVzZW50YSBsYSBtZWRpYSBkZSBsb3MgcHVudG9zIGFzaWduYWRvcyBhbCBncnVwbyAkQ19rJC4NCiAgIA0KICAgKyAkZF9pID0geF9pIC0gXG92ZXJsaW5le3h9X2skIGxhIGRpc3RhbmNpYSBkZWwgcHVudG8gJHhfaSQgYWwgY2VudHJvaWRlICRcb3ZlcmxpbmV7eH1fayQsIGRlbnRybyBkZWwgY2zDunN0ZXIgJENfayQuDQogICANCg0KKiozLiAkXHRleHR7V2l0aGluc3N9X2skIChncsOhZmljbykuKioNCg0KVsOpYXNlIGxhIGZpZ3VyYSBcQHJlZihmaWc6a21lYW4zKS4gDQoNCjxjZW50ZXI+DQoNCmBgYHtyIGttZWFuMywgZWNobz1GQUxTRSwgZmlnLmNhcCA9ICIqKldpdGhpbnNzJF9rJCAodmVjdG9yIGRlIHN1bWEgZGUgY3VhZHJhZG9zIGRlbnRybyBkZWwgY2zDunN0ZXIgJGskKSoqIiwgb3V0LndpZHRoID0gIjEwMCUifQ0KIyBmaWcud2lkdGggPSAyMCAjIE5vIGZ1bmNpb25hIGVzdGEgb3BjaW9uIGVuIGVsIGNodW5rDQoNCiNodHRwOi8vemV2cm9zcy5jb20vYmxvZy8yMDE3LzA2LzE5L3RpcHMtYW5kLXRyaWNrcy1mb3Itd29ya2luZy13aXRoLWltYWdlcy1hbmQtZmlndXJlcy1pbi1yLW1hcmtkb3duLWRvY3VtZW50cy8NCiMgUGFnaW5hIDM1OSBkZSBSMjAxNS1GcmllbmRseQ0KDQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygia21lYW4zLnBuZyIpDQoNCiNPdHJhIG1hbmVyYSwgcGVybyAgc2FsZSBlbCBjYXB0aW9uOg0KIzxjZW50ZXI+DQojIVsoI2ZpZzpGaWctY2FwdGlvbikgTWkgZmlndXJhXShOb21icmUucG5nKXt3aWR0aD00MDBweH0NCiM8L2NlbnRlcj4NCmBgYA0KPC9jZW50ZXI+DQoNCg0KKio0LiAkXHRleHR7V2l0aGluc3N9X2skIChhc2lnbmFjacOzbikuKioNCiAgICANCkNhZGEgb2JzZXJ2YWNpw7NuICgkeF9pJCkgc2UgYXNpZ25hIGEgdW4gY2zDunN0ZXIgZXNwZWPDrWZpY28gZGUgbW9kbyBxdWUgbGEgc3VtYSBkZSBsb3MgY3VhZHJhZG9zIChTUykgZGUgbGFzIGRpc3RhbmNpYXMgZW50cmUgbGEgb2JzZXJ2YWNpw7NuIHkgbG9zIGNlbnRyb3MgZGUgY2zDunN0ZXIgYXNpZ25hZG9zICRcb3ZlcmxpbmV7eH1fayQgc2VhIGxhIG1lbm9yIHBvc2libGUuIA0KDQoNCioqNS4gJFx0ZXh0e1RvdC53aXRoaW5zc30kICh2YXJpYWJpbGlkYWQgdG90YWwgZGVudHJvIGRlIHRvZG9zIGxvcyBjbMO6c3RlcikuKioNCiAgDQogICsgTGEgKnN1bWEgdG90YWwgZGUgY3VhZHJhZG9zIGRlbnRybyBkZSB0b2RvcyBsb3MgY2zDunN0ZXJzKiBlcyB1bmEgbWVkaWRhIGRlIHF1w6kgdGFuIGNvbXBhY3RvIGVzIGVsIGFncnVwYW1pZW50byAoZXMgZGVjaXIsIHN1IGNhbGlkYWQpLiANCiAgDQogICsgUmVwcmVzZW50YSBsYSB2YXJpYWJpbGlkYWQgdG90YWwgZGVudHJvIGRlIHRvZG9zIGxvcyBjbHVzdGVycy4gDQogIA0KICArIFVuIHZhbG9yIG1heW9yIGRlICRcdGV4dHtUb3Qud2l0aGluc3N9JCBpbmRpY2EgcXVlIGxvcyBwdW50b3MgZGVudHJvIGRlIGNhZGEgY2x1c3RlciBlc3TDoW4gbcOhcyBkaXNwZXJzb3MsIGxvIHF1ZSBzdWdpZXJlIHVuYSBtZW5vciBjYWxpZGFkIGRlIGNsdXN0ZXJpbmcuDQogIA0KICArIFNlIGRlZmluZSBkZSBsYSBzaWd1aWVudGUgbWFuZXJhOg0KDQokJFx0ZXh0e1RvdC53aXRoaW5zc30gXCw9XDtcc3VtXGxpbWl0c197az0xfV5LIFcoQ19rKSBcOz1cOyBcc3VtXGxpbWl0c197az0xfV5LXHN1bVxsaW1pdHNfe3hfaSBcaW4gQ19rfSAoeF9pIC1cb3ZlcmxpbmV7eH1fayleMlw7PVw7IFxzdW1cbGltaXRzX3trPTF9Xktcc3VtXGxpbWl0c197eF9pIFxpbiBDX2t9IGRfaV4yJCQNCg0KKio2LiAkXHRleHR7VG90LndpdGhpbnNzfSQgKGdyw6FmaWNvKS4qKg0KDQpWw6lhc2UgbGEgZmlndXJhIFxAcmVmKGZpZzprbWVhbjQpLiANCg0KPGNlbnRlcj4NCg0KYGBge3Iga21lYW40LCBlY2hvPUZBTFNFLCBmaWcuY2FwID0gIioqVG90LndpdGhpbnNzICh2YXJpYWJpbGlkYWQgdG90YWwgZGVudHJvIGRlIHRvZG9zIGxvcyBjbMO6c3RlcikqKiIsIG91dC53aWR0aCA9ICIxMDAlIn0NCiMgZmlnLndpZHRoID0gMjAgIyBObyBmdW5jaW9uYSBlc3RhIG9wY2lvbiBlbiBlbCBjaHVuaw0KDQojaHR0cDovL3pldnJvc3MuY29tL2Jsb2cvMjAxNy8wNi8xOS90aXBzLWFuZC10cmlja3MtZm9yLXdvcmtpbmctd2l0aC1pbWFnZXMtYW5kLWZpZ3VyZXMtaW4tci1tYXJrZG93bi1kb2N1bWVudHMvDQojIFBhZ2luYSAzNTkgZGUgUjIwMTUtRnJpZW5kbHkNCg0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImttZWFuNC5wbmciKQ0KDQojT3RyYSBtYW5lcmEsIHBlcm8gIHNhbGUgZWwgY2FwdGlvbjoNCiM8Y2VudGVyPg0KIyFbKCNmaWc6RmlnLWNhcHRpb24pIE1pIGZpZ3VyYV0oTm9tYnJlLnBuZyl7d2lkdGg9NDAwcHh9DQojPC9jZW50ZXI+DQpgYGANCjwvY2VudGVyPg0KDQoqKioqDQoNCioqOC4gT2JqZXRpdm8gZmluYWwuKioNCg0KTnVlc3RybyBvYmpldGl2byBlcyBtaW5pbWl6YXIgZXN0YSBzdW1hIHRhbnRvIGNvbW8gc2VhIHBvc2libGU6DQoNCiQkXG1pbihcdGV4dHtUb3Qud2l0aGluc3N9KSAgXCw9XDtcbWluIFxzdW1cbGltaXRzX3trPTF9XksgVyhDX2spJCQNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyAkayQtbWVhbnM6IHByb3BvcmNpw7NuIGRlIHZhcmlhbnphIGV4cGxpY2FkYQ0KDQoqKjEuICRcdGV4dHtUb3Rzc30kICh2YXJpYWJpbGlkYWQgdG90YWwgZW4gdG9kb3MgbG9zIGRhdG9zKS4qKg0KDQpMYSBzdW1hIHRvdGFsIGRlIGN1YWRyYWRvcyBtaWRlIGxhIHZhcmlhYmlsaWRhZCB0b3RhbCBlbiBsb3MgZGF0b3MgICB5IG1pZGUgbGEgdmFyaWFiaWxpZGFkIHRvdGFsIGVuIHRvZG9zIGxvcyBkYXRvcyAoc2luIHBhcnRpY2lvbmFyIGxvcyBkYXRvcyBvcmlnaW5hbGVzLCBjb21vIHNpIHNvbG8gdHV2acOpc2Vtb3MgdW4gc29sbyBjbMO6c3RlcikuDQoNCg0KKioyLiAkXHRleHR7VG90c3N9JCAoZGVmaW5pY2nDs24pLioqDQoNCkxhIHN1bWEgdG90YWwgZGUgY3VhZHJhZG9zIHNlIGRlZmluZSBjb21vOiANCg0KJCRcdGV4dHtUb3Rzc30gXDsgPVw7IFxzdW1cbGltaXRzX3tpPTF9Xm4gKHhfaSAtIFxvdmVybGluZXt4fSleMiQkDQoNCioqMy4gJFx0ZXh0e0JldHdlZW5zc30kIChzdW1hIGRlIGN1YWRyYWRvcyBlbnRyZSBjbMO6c3RlcmVzKS4qKg0KDQpMYSBzdW1hIGRlIGN1YWRyYWRvcyBlbnRyZSBjbMO6c3RlcmVzLCBxdWUgbWlkZSBsYSB2YXJpYWNpw7NuIGRlYmlkbyBhIGxhcyBkaWZlcmVuY2lhcyBlbnRyZSBsb3MgY2VudHJvaWRlcyBkZSBsb3MgY2zDunN0ZXJlcywgc2UgZGVmaW5lIGNvbW86IA0KDQokJFx0ZXh0e0JldHdlZW5zc30gXDsgPSBcOyBcdGV4dHtUb3Rzc30gXCwtXCwgIFx0ZXh0e1RvdC53aXRoaW5zc30kJA0KDQoqKjQuICRcdGV4dHtQcm9wLlZhcn0kIChwcm9wb3JjacOzbiBkZSB2YXJpYW56YSB0b3RhbCkuKioNCg0KTGEgcHJvcG9yY2nDs24gZGUgdmFyaWFuemEgdG90YWwgZW4gbG9zIGRhdG9zIHF1ZSBlcyBleHBsaWNhZGEgcG9yIGxhIHZhcmlhY2nDs24gZW50cmUgbG9zIGNlbnRyb2lkZXMgZGUgbG9zIGNsw7pzdGVyZXMgKHF1ZSBlcyBleHBsaWNhZGEgcG9yIGxhIGFncnVwYWNpw7NuIGRlIGxvcyBkYXRvcyBlbiBsb3MgY2zDunN0ZXJlcykgc2UgY2FsY3VsYSBhc8OtOg0KDQokJFx0ZXh0e1Byb3AuVmFyfVw7ID0gXDtcZnJhY3tcdGV4dHtCZXR3ZWVuc3N9ICB9e1x0ZXh0e1RvdHNzfX0gXDsgPSBcOyAxXDstXDsgXGZyYWN7XHRleHR7VG90LndpdGhpbnNzfX17XHRleHR7VG90c3N9fSQkDQoNCioqNS4gJFx0ZXh0e1Byb3AuVmFyfSQgKGludGVycHJldGFjacOzbikuKioNCg0KTGEgaW50ZXJwcmV0YWNpw7NuIGRlIGxhIHByb3BvcmNpw7NuIGRlIHZhcmlhbnphIGV4cGxpY2FkYSBlcyBjb21vIHNpZ3VlOiANCiAgDQogICsgVW4gdmFsb3IgbcOhcyBhbHRvIChjZXJjYW5vIGEgMTAwJSkgaW5kaWNhIHF1ZSBsb3MgY2zDunN0ZXJlcyBmb3JtYWRvcyBleHBsaWNhbiBiaWVuIGxhIHZhcmlhY2nDs24gdG90YWwgZW4gbG9zIGRhdG9zLCBzdWdpcmllbmRvIHF1ZSBsb3MgY2zDunN0ZXJlcyBlc3TDoW4gYmllbiBkZWZpbmlkb3MgeSBzZXBhcmFkb3MuDQogIA0KICArIFVuIHZhbG9yIG3DoXMgYmFqbyBzdWdpZXJlIHF1ZSBsb3MgY2zDunN0ZXJlcyBubyBleHBsaWNhbiBiaWVuIGxhIHZhcmlhY2nDs24gZW4gbG9zIGRhdG9zLCBsbyBxdWUgcG9kcsOtYSBpbmRpY2FyIHF1ZSBsb3MgY2zDunN0ZXJlcyBubyBlc3TDoW4gYmllbiBkZWZpbmlkb3MgbyBxdWUgbm8gaGF5IHVuYSBlc3RydWN0dXJhIGNsYXJhIGVuIGxvcyBkYXRvcy4NCiAgDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIEVqZW1wbG86IGRhdG9zDQoNCiMjIyBCYXNlIGRlIGRhdG9zDQoNCkxvcyBkYXRvcyBzZSByZWNvZ2llcm9uIGFwbGljYW5kbyB1bmEgZW5jdWVzdGEgYSB1bmEgbXVlc3RyYSBkZSBlc3R1ZGlhbnRlcyB1bml2ZXJzaXRhcmlvcy4gRXMgdW4gZGF0YSBmcmFtZSBjb24gODAwIG9ic2VydmFjaW9uZXMgeSA2NiB2YXJpYWJsZXMuIENvbiBlc3RvcyBkYXRvcyBsbGV2YXJlbW9zIGEgY2FibyB1biBQQ0EuIA0KDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KZGF0b3NDb21wbGV0byA8LSBsc206OnN1cnZleQ0KI2RhdG9zQ29tcGxldG8gPC0gdGV4dHNoYXBlOjpjb2x1bW5fdG9fcm93bmFtZXMoZGF0LCBsb2M9MSkNCiNkYXRvc0NvbXBsZXRvICU+JSByZW1vdmVfcm93bmFtZXMgJT4lIGNvbHVtbl90b19yb3duYW1lcyh2YXI9Im5hbWVzIikgICAjbGlicmFyeSh0aWR5dmVyc2UpDQphdHRhY2goZGF0b3NDb21wbGV0bykNCmBgYA0KDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KbmFtZXMoZGF0b3NDb21wbGV0bykNCmBgYA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgU29sbyBkYXRvcyBudW3DqXJpY29zDQoNCg0KU29sbyB1dGlsaXphcmVtb3MgYWxndW5hcyB2YXJpYWJsZXMgbnVtw6lyaWNhcy4gTG9zIGRhdG9zIGRlYmVuIGNvbnRlbmVyIHNvbG8gdmFyaWFibGVzIGNvbnRpbnVhcywgeWEgcXVlIGVsIGFsZ29yaXRtbyAkayQtbWVhbnMgdXRpbGl6YSBtZWRpYXMgdmFyaWFibGVzLiBEYWRvIHF1ZSBubyBxdWVyZW1vcyBxdWUgZWwgYWxnb3JpdG1vICRrJC1tZWFucyBkZXBlbmRhIGRlIHVuYSB1bmlkYWQgZGUgdmFyaWFibGUgYXJiaXRyYXJpYSwgY29tZW56YW1vcyBlc2NhbGFuZG8gbG9zIGRhdG9zIHV0aWxpemFuZG8gbGEgZnVuY2nDs24gYHNjYWxlYCBkZSBSIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6DQoNCg0KYGBge3J9DQpkYXQgPC0gZGF0b3NDb21wbGV0b1sxOjEwMCwgMjE6MjRdDQpkZiA8LSBzY2FsZShkYXQpDQpoZWFkKGRmKQ0KYGBgDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyBFamVtcGxvOiBgc3RhdHM6OmttZWFuc2AgeSBgZmFjdG9yZXh0cmFgDQoNCiMjIyBEZXNjcmlwY2nDs24gZGUgbGEgZnVuY2nDs24gYHN0YXRzOjprbWVhbnNgIA0KDQpVc2FyZW1vcyBsYSBmdW5jacOzbiBga21lYW5zYCBkZWwgcGFxdWV0ZSBgc3RhdHNgLiBVbiBmb3JtYXRvIHNpbXBsZSBlczogDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0Ka21lYW5zKFgsIGNlbnRlcnMsIGl0ZXIubWF4ID0gMTAsIG5zdGFydCA9IDEpDQpgYGANCg0KQXF1w606IA0KICANCiAgKyBgWGA6IGVzIHVuYSBtYXRyaXogbnVtw6lyaWNhLCB1biBkYXRhIGZyYW1lIG51bcOpcmljbyBvIHVuIHZlY3RvciBudW3DqXJpY28uDQogIA0KICArIGBjZW50ZXJzYDogbG9zIHBvc2libGVzIHZhbG9yZXMgc29uIGVsIG7Dum1lcm8gZGUgYWdydXBhY2lvbmVzICgkayQpIG8gdW4gY29uanVudG8gZGUgY2VudHJvcyBkZSBhZ3J1cGFjacOzbiBpbmljaWFsZXMgKGRpc3RpbnRvcykuIFNpIHNlIGVzcGVjaWZpY2EgdW4gbsO6bWVybywgc2UgZWxpZ2UgdW4gY29uanVudG8gYWxlYXRvcmlvIGRlIGZpbGFzIChkaXN0aW50YXMpIGVuIGB4YCBjb21vIGxvcyBjZW50cm9zIGluaWNpYWxlcy4NCiAgDQogICsgYGl0ZXIubWF4YDogbsO6bWVybyBkZSBtw6F4aW1vIGRlIGl0ZXJhY2lvbmVzIHBlcm1pdGlkaWFzLiBQb3IgZGVmZWN0byBlcyAxMC4gDQogIA0KICArIGBuc3RhcnRgOiBFbCBuw7ptZXJvIGRlIHBhcnRpY2lvbmVzIGluaWNpYWxlcyBhbGVhdG9yaWFzIGN1YW5kbyBsb3MgY2VudHJvcyBlcyB1biBuw7ptZXJvLiBJbnRlbnRhciBgbnN0YXJ0YCA+IDEgZXMgYSBtZW51ZG8gcmVjb21lbmRhZG8uDQogIA0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBMYSBmdW5jacOzbiBgZmFjdG9yZXh0cmFgICAgDQoNCg0KUGFyYSBjcmVhciB1biBncsOhZmljbyBhdHJhY3Rpdm8gZGUgbG9zIGNvbmdsb21lcmFkb3MgZ2VuZXJhZG9zIGNvbiBsYSBmdW5jacOzbiBga21lYW5zYCwgdXRpbGl6YXJlbW9zIGVsIHBhcXVldGUgYGZhY3RvZXh0cmFgLg0KDQpgYGB7cn0NCmxpYnJhcnkoZmFjdG9leHRyYSkNCmBgYA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIEVzdGltYW5kbyBlbCBuw7ptZXJvIMOzcHRpbW8gZGUgY2zDunN0ZXJzLg0KDQojIyMgUHJlZ3VudGEgY2VudHJhbA0KDQpFbCBwcm9jZXNvIGRlIGFncnVwYW1pZW50byAkayQtbWVhbnMgcmVxdWllcmUgcXVlIGxvcyB1c3VhcmlvcyBpbmRpcXVlbiBjdcOhbnRvcyBncnVwb3MgZGVzZWFuIGdlbmVyYXIuIFVuYSBwcmVndW50YSBjZW50cmFsIGVzOiAqwr9Dw7NtbyBzZWxlY2Npb25hciBlbCBuw7ptZXJvIGFkZWN1YWRvIGRlIGdydXBvcyAoJGskKT8qDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBSZXNwdWVzdGEgYSBwcmVndW50YSBjZW50cmFsDQoNCjEuIENvbnNpc3RlIGVuIHJlYWxpemFyIGVsIGFncnVwYW1pZW50byAkayQtbWVhbnMgdXRpbGl6YW5kbyBkaWZlcmVudGVzIHZhbG9yZXMgZGUgJGskLiANCg0KMi4gTHVlZ28sIHNlIGdyYWZpY2EgbGEgc3VtYSBkZSBsb3MgY3VhZHJhZG9zIGludGVybm9zICh3c3MpIGVuIGZ1bmNpw7NuIGRlbCBuw7ptZXJvIGRlIGdydXBvcy4gDQoNCjMuIE5vcm1hbG1lbnRlLCBsYSB1YmljYWNpw7NuIGRlIHVuIHF1aWVicmUgKHB1bnRvIGRlIGluZmxleGnDs24pIGVuIGVsIGdyw6FmaWNvIHNlIGNvbnNpZGVyYSBjb21vIHVuIGluZGljYWRvciBkZWwgbsO6bWVybyBhcHJvcGlhZG8gZGUgZ3J1cG9zLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIENvbiBSDQoNCkxhIGZ1bmNpw7NuICBgZnZpel9uYmNsdXN0YCBkZWwgcGFxdWV0ZSBgZmFjdG9leHRyYWAgYnJpbmRhIHVuYSBzb2x1Y2nDs24gcHLDoWN0aWNhIHBhcmEgZXN0aW1hciBlbCBuw7ptZXJvIMOzcHRpbW8gZGUgZ3J1cG9zLg0KDQpgYGB7cn0NCmxpYnJhcnkoZmFjdG9leHRyYSkNCmZ2aXpfbmJjbHVzdChkZiwga21lYW5zLCBtZXRob2QgPSAid3NzIikgKw0KZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gNCwgbGluZXR5cGUgPSAyKQ0KYGBgDQoNCkVsIGdyw6FmaWNvIGlsdXN0cmEgY8OzbW8gbGEgdmFyaWFiaWxpZGFkIGRlbnRybyBkZSBsb3MgZ3J1cG9zIGNhbWJpYSBjb24gZWwgbsO6bWVybyBkZSBncnVwb3MsICRrJC4gRXN0YSB2YXJpYWJpbGlkYWQgZGlzbWludXllIGNvbiAkayQsIHBlcm8gc2Ugbm90YSB1biBwdW50byBkZSBpbmZsZXhpw7NuIG8gImNvZG8iIGVuICRrID0gNCQuIEVzdGUgcHVudG8gc3VnaWVyZSBxdWUgYWdyZWdhciBtw6FzIGdydXBvcyBkZXNwdcOpcyBkZWwgY3VhcnRvIG5vIGFwb3J0YSBtdWNobyB2YWxvci4gRW4gbGEgc2lndWllbnRlIHNlY2Npw7NuLCBwcm9jZWRlcmVtb3MgYSBjbGFzaWZpY2FyIGxhcyBvYnNlcnZhY2lvbmVzIGVuIDQgZ3J1cG9zLg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIENhbGN1bGFuZG8gZWwgYWdydXBhbWllbnRvICRrJC1tZWFucw0KDQojIyMgYHNldC5zZWVkYA0KDQoxLiBFbCBhbGdvcml0bW8gZGUgYWdydXBhbWllbnRvICRrJC1tZWFucyBjb21pZW56YSBzZWxlY2Npb25hbmRvIGsgY2VudHJvaWRlcyBkZSBtYW5lcmEgYWxlYXRvcmlhLCBwb3IgbG8gcXVlIGVzIHJlY29tZW5kYWJsZSB1c2FyIGxhIGZ1bmNpw7NuIGBzZXQuc2VlZGAgcGFyYSBmaWphciB1bmEgc2VtaWxsYSBlbiBlbCBnZW5lcmFkb3IgZGUgbsO6bWVyb3MgYWxlYXRvcmlvcyBkZSBSLiANCg0KMi4gRXN0byBnYXJhbnRpemEgcXVlIGxvcyByZXN1bHRhZG9zIHNlYW4gcmVwcm9kdWNpYmxlcywgZGUgbW9kbyBxdWUgY3VhbHF1aWVyIGxlY3RvciBkZSBlc3RlIGFydMOtY3VsbyBvYnRlbmdhIGxvcyBtaXNtb3MgcmVzdWx0YWRvcyBxdWUgc2UgbXVlc3RyYW4gYSBjb250aW51YWNpw7NuLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIGBrbWVhbnNgDQoNCkVsIGPDs2RpZ28gUiBhIGNvbnRpbnVhY2nDs24gZWplY3V0YSBlbCBhZ3J1cGFtaWVudG8gJGskLW1lYW5zIGNvbiAkayA9IDQkOg0KDQpgYGB7cn0NCiNDYWxjdWxhciBrLW1lYW5zIGNvbiBrID0gNA0KDQpzZXQuc2VlZCgxMjMpDQprIDwtNA0Ka20ucmVzIDwtIGttZWFucyhkZiwgY2VudGVycyA9IGssIG5zdGFydCA9IDI1KQ0KYGBgDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBgbnN0YXJ0YA0KDQoxLiBEZWJpZG8gYSBxdWUgZWwgcmVzdWx0YWRvIGZpbmFsIGRlbCBhZ3J1cGFtaWVudG8gJGskLW1lYW5zIGRlcGVuZGUgZGUgbGFzIGFzaWduYWNpb25lcyBpbmljaWFsZXMgYWxlYXRvcmlhcywgZXNwZWNpZmljYW1vcyBgbnN0YXJ0ID0gMjVgLiANCg0KMi4gRXN0byBzaWduaWZpY2EgcXVlIFIgcHJvYmFyw6EgMjUgYXNpZ25hY2lvbmVzIGluaWNpYWxlcyBhbGVhdG9yaWFzIGRpZmVyZW50ZXMgeSBzZWxlY2Npb25hcsOhIGxvcyBtZWpvcmVzIHJlc3VsdGFkb3MgYmFzYWRvcyBlbiBsYSB2YXJpYWNpw7NuIGludHJhLWNsdXN0ZXIgbcOhcyBiYWphLiANCg0KMy4gQXVucXVlIGVsIHZhbG9yIHByZWRldGVybWluYWRvIGRlIGBuc3RhcnRgIGVuIFIgZXMgdW5vLCBlcyBhbHRhbWVudGUgcmVjb21lbmRhYmxlIHV0aWxpemFyIHVuIHZhbG9yIG3DoXMgZ3JhbmRlLCBjb21vIDI1IG8gNTAsIHBhcmEgb2J0ZW5lciByZXN1bHRhZG9zIG3DoXMgZXN0YWJsZXMuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMjIyBSZXN1bHRhZG9zIGZpbmFsZXMNCg0KYGBge3J9DQpwcmludChrbS5yZXMpDQpgYGANCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIEludGVycHJldGFjaW9uZXMNCg0KTGEgc2FsaWRhIGltcHJlc2EgcHJlc2VudGE6DQoNCjEuICoqTG9zIHByb21lZGlvcyBvIGNlbnRyb3MgZGUgbG9zIGdydXBvcyoqOiB1bmEgbWF0cml6IGVuIGxhIHF1ZSBsYXMgZmlsYXMgcmVwcmVzZW50YW4gZWwgbsO6bWVybyBkZWwgZ3J1cG8gKDEgYSA0KSB5IGxhcyBjb2x1bW5hcyByZXByZXNlbnRhbiBsYXMgdmFyaWFibGVzLg0KDQoyLiAqKkVsIHZlY3RvciBkZSBhZ3J1cGFtaWVudG8qKjogVW4gY29uanVudG8gZGUgbsO6bWVyb3MgZW50ZXJvcyAoZGUgMSBhICRrJCkgcXVlIHNlw7FhbGEgZWwgZ3J1cG8gYWwgcXVlIHNlIGFzaWduYSBjYWRhIHB1bnRvLg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyBNZWRpYXMgcG9yIGNsw7pzdGVycw0KDQojIyMgTWVkaWFzOiBPYnNlcnZhY2nDs24NCg0KRXMgcG9zaWJsZSBjYWxjdWxhciBsYSBtZWRpYSBkZSBjYWRhIHZhcmlhYmxlICAoYFNlbUFjdW1gLCBgRXhhbTFgLCBgRXhhbTJgIHkgYEV4YW0zYCkgcG9yIGNsw7pzdGVyIChgMWAsIGAyYCwgYDNgLCBgNGApIHVzYW5kbyBsb3MgZGF0b3Mgb3JpZ2luYWxlcy4gRW4gbGFzIHNpZ3VpZW50ZXMgc2VjY2lvbmVzLCBjYWxjdWxhcmVtb3MgZXNhcyAxNiBtZWRpYXMgcGFyYSBsb3MgZGF0b3M6IA0KDQoxLiBTaSBlc2NhbGFyIChgZGF0YCkuDQoNCjIuIEVzY2FsYWRvcyAoYGRmYCkuIA0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KIyMjIFVzYW5kbyBsb3MgZGF0b3Mgc2luIGVzY2FsYXIgKGBkYXRgKQ0KDQoNCmBgYHtyfQ0KYWdncmVnYXRlKGRhdCwgYnk9bGlzdChjbHVzdGVyPWttLnJlcyRjbHVzdGVyKSwgbWVhbikNCmBgYA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgVXNhbmRvIGxvcyBkYXRvcyBlc2NhbGFkb3MgKGBkZmApDQoNCk9ic2VydmUgcXVlIGxvcyByZXN1bHRhZG9zIHF1ZSBzZSBtdWVzdHJhbiBhYmFqbyBjb2luY2lkZW4gY29uIGVsIG91dHB1dCBkZSBga20ucmVzYC4gDQoNCmBgYHtyfQ0KYWdncmVnYXRlKGRmLCBieT1saXN0KGNsdXN0ZXI9a20ucmVzJGNsdXN0ZXIpLCBtZWFuKQ0KYGBgDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMgQ2xhc2lmaWNhciBvYnNlcnZhY2lvbmVzIHBvciBjbMO6c3RlcnMNCg0KDQpTaSBzZSBkZXNlYSBhZ3JlZ2FyIGxhcyBjbGFzaWZpY2FjaW9uZXMgZGUgbG9zIHB1bnRvcyBhIGxvcyBkYXRvcyBvcmlnaW5hbGVzLCBzZSBwdWVkZSBlamVjdXRhciBlc3RlIGPDs2RpZ286DQoNCmBgYHtyfQ0KZGYuY2xhc2lmIDwtIGNiaW5kKGRhdCwgY2x1c3RlciA9IGttLnJlcyRjbHVzdGVyKQ0KaGVhZChkZi5jbGFzaWYpDQpgYGANCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCiMgT3V0cHV0IGRlIGBrbWVhbnNgDQoNCiMjIyBga21lYW5zYDogdmFsb3JlcyBxdWUgc2UgcHVlZGVuIG9idGVuZXINCg0KTGEgZnVuY2nDs24gYGttZWFuc2AgZGV2dWVsdmUgdW5hIGxpc3RhIGRlIGNvbXBvbmVudGVzIHF1ZSBpbmNsdXllbjoNCg0KMS4gYGNsdXN0ZXJgOiBVbiB2ZWN0b3IgZGUgZW50ZXJvcyAoZGUgMSBhICRrJCkgcXVlIGluZGljYSBlbCBjbMO6c3RlciBhbCBxdWUgc2UgYXNpZ25hIGNhZGEgcHVudG8uDQoNCjIuIGBjZW50ZXJzYDogVW5hIG1hdHJpeiBkZSBjZW50cm9zIGRlIGNsw7pzdGVyIChtZWRpYXMgZGUgY2zDunN0ZXIpLg0KDQozLiBgdG90c3NgOiBMYSBzdW1hIHRvdGFsIGRlIGN1YWRyYWRvcyB5IG1pZGUgbGEgdmFyaWFiaWxpZGFkIHRvdGFsIGVuIHRvZG9zIGxvcyBkYXRvcyAoc2luIHBhcnRpY2lvbmFyIGxvcyBkYXRvcyBvcmlnaW5hbGVzLCBjb21vIHNpIHNvbG8gdHV2acOpc2Vtb3MgdW4gc29sbyBjbMO6c3RlcikuIEVzIGRlY2lyLCANCiQkXHRleHR7VG90c3N9IFw7ID1cOyBcc3VtXGxpbWl0c197aT0xfV5uKHhfaSAtIFxvdmVybGluZXt4fSleMiQkDQoNCjQuIGB3aXRoaW5zc2A6IFZlY3RvciBkZSBzdW1hIGRlIGN1YWRyYWRvcyBkZW50cm8gZGVsIGNsw7pzdGVyLCBjb24gdW4gY29tcG9uZW50ZSBwb3IgY2zDunN0ZXIuIEVzIGRlY2lyLCANCg0KJCRcdGV4dHtXaXRoaW5zc31fayBcLD1cOyBXKENfaykgXDs9XDsgXHN1bVxsaW1pdHNfe3hfaSBcaW4gQ19rfSAoeF9pIC1cb3ZlcmxpbmV7eH1fayleMiQkDQoNCjUuIGB0b3Qud2l0aGluc3NgOiBTdW1hIHRvdGFsIGRlIGN1YWRyYWRvcyBkZW50cm8gZGVsIGNsw7pzdGVyLCBlcyBkZWNpciwgDQoNCiQkXHRleHR7VG90LndpdGhpbnNzfSBcLD1cO1xzdW1cbGltaXRzX3trPTF9XksgVyhDX2spIFw7PVw7IFxzdW1cbGltaXRzX3trPTF9Xktcc3VtXGxpbWl0c197eF9pIFxpbiBDX2t9ICh4X2kgLVxvdmVybGluZXt4fV9rKV4yJCQNCg0KNi4gYGJldHdlZW5zc2A6IExhIHN1bWEgZGUgY3VhZHJhZG9zIGVudHJlIGNsw7pzdGVyZXMsIGVzIGRlY2lyLCANCg0KJCRcdGV4dHtCZXR3ZWVuc3N9IFw7ID0gXDsgXHRleHR7VG90c3N9IFwsLVwsICBcdGV4dHtUb3Qud2l0aGluc3N9JCQNCg0KNy4gYHNpemVgOiBFbCBuw7ptZXJvIGRlIG9ic2VydmFjaW9uZXMgZW4gY2FkYSBjbMO6c3Rlci4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIyMgYGttZWFuc2A6IGNvbiBudWVzdHJvcyBkYXRvcw0KDQpBbGd1bm9zIGRlIGVzdG9zIGNvbXBvbmVudGVzIHB1ZWRlbiBzZXIgYWNjZWRpZG9zIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6DQoNCg0KDQoxLiBgc2l6ZWA6IEVsIG7Dum1lcm8gZGUgb2JzZXJ2YWNpb25lcyAkbl9rJCBlbiBjYWRhIGNsw7pzdGVyICRrJC4gIE9ic2VydmU6IA0KJCRuXDs9XDsgXHN1bVxsaW1pdHNfe2s9MX1eSyBuX2sgJCQNCg0KYGBge3J9DQprbS5yZXMkc2l6ZQ0KYGBgDQoNCg0KMi4gYGNlbnRlcnNgOiBVbmEgbWF0cml6IGRlIGNlbnRyb3MgZGUgY2zDunN0ZXIgKG1lZGlhcyBkZSBjbMO6c3RlcikuDQoNCg0KYGBge3J9DQprbS5yZXMkY2VudGVycw0KYGBgDQoNCjMuIGBjbHVzdGVyYDogVW4gdmVjdG9yIGRlIGVudGVyb3MgKGRlIDEgYSAkayQpIHF1ZSBpbmRpY2EgZWwgY2zDunN0ZXIgYWwgcXVlIHNlIGFzaWduYSBjYWRhIHB1bnRvLg0KDQpgYGB7cn0NCmttLnJlcyRjbHVzdGVyDQpgYGANCg0KDQo0LiBgdG90c3NgOiBMYSBzdW1hIHRvdGFsIGRlIGN1YWRyYWRvcywgcXVlIG1pZGUgbGEgdmFyaWFiaWxpZGFkIHRvdGFsIGVuIHRvZG9zIGxvcyBkYXRvcyAoc2luIHBhcnRpY2lvbmFyIGxvcyBkYXRvcyBvcmlnaW5hbGVzLCBjb21vIHNpIHNvbG8gdHV2acOpc2Vtb3MgdW4gc29sbyBjbMO6c3RlcikuIEVzIGRlY2lyLCANCiQkXHRleHR7VG90c3N9IFw7ID1cOyBcc3VtXGxpbWl0c197aT0xfV5uICh4X2kgLSBcb3ZlcmxpbmV7eH0pXjIkJA0KDQoNCg0KYGBge3J9DQprbS5yZXMkdG90c3MNCmBgYA0KDQo1LiBgd2l0aGluc3NgOiBWZWN0b3IgZGUgc3VtYSBkZSBjdWFkcmFkb3MgZGVudHJvIGRlbCBjbMO6c3RlciwgY29uIHVuIGNvbXBvbmVudGUgcG9yIGNsw7pzdGVyLiBFcyBkZWNpciwgDQoNCiQkXHRleHR7V2l0aGluc3N9IFwsPVw7IFcoQ19rKSBcOz1cOyBcc3VtXGxpbWl0c197eF9pIFxpbiBDX2t9ICh4X2kgLVxvdmVybGluZXt4fV9rKV4yJCQNCg0KDQoNCmBgYHtyfQ0Ka20ucmVzJHdpdGhpbnNzDQpgYGANCg0KDQo2LiBgdG90LndpdGhpbnNzYDogU3VtYSB0b3RhbCBkZSBjdWFkcmFkb3MgZGVudHJvIGRlbCBjbMO6c3RlciwgZXMgZGVjaXIsIA0KDQokJFx0ZXh0e1RvdC53aXRoaW5zc30gXCw9XDtcc3VtXGxpbWl0c197az0xfV5LIFcoQ19rKSBcOz1cOyBcc3VtXGxpbWl0c197az0xfV5LXHN1bVxsaW1pdHNfe3hfaSBcaW4gQ19rfSAoeF9pIC1cb3ZlcmxpbmV7eH1fayleMiQkDQoNCg0KDQpgYGB7cn0NCmttLnJlcyR0b3Qud2l0aGluc3MNCmBgYA0KDQoNCjcuIGBiZXR3ZWVuc3NgOiBMYSBzdW1hIGRlIGN1YWRyYWRvcyBlbnRyZSBjbMO6c3RlcmVzLCBxdWUgbWlkZSBsYSB2YXJpYWNpw7NuIGRlYmlkbyBhIGxhcyBkaWZlcmVuY2lhcyBlbnRyZSBsb3MgY2VudHJvaWRlcyBkZSBsb3MgY2zDunN0ZXJlcy4gRXMgZGVjaXIsIA0KDQokJFx0ZXh0e0JldHdlZW5zc30gXDsgPSBcOyBcdGV4dHtUb3Rzc30gXCwtXCwgIFx0ZXh0e1RvdC53aXRoaW5zc30kJA0KDQoNCmBgYHtyfQ0Ka20ucmVzJGJldHdlZW5zcw0KYGBgDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIGBrbWVhbnNgOiBwcm9wb3JjacOzbiBkZSB2YXJpYW56YSBleHBsaWNhZGENCg0KTGEgcHJvcG9yY2nDs24gZGUgdmFyaWFuemEgdG90YWwgZXhwbGljYWRhIHBvciBsYSB2YXJpYWNpw7NuIGVudHJlIGxvcyBjZW50cm9pZGVzIGRlIGxvcyBjbMO6c3RlcmVzIHNlIHB1ZWRlIGNhbGN1bGFyIGFzw606DQoNCiQkXHRleHR7UHJvcC5WYXJ9XDsgPSBcO1xmcmFje1x0ZXh0e0JldHdlZW5zc30gIH17XHRleHR7VG90c3N9fSBcOyA9IFw7IDFcOy1cOyBcZnJhY3tcdGV4dHtUb3Qud2l0aGluc3N9fXtcdGV4dHtUb3Rzc319JCQNCkVuIG51ZXN0cm8gZWplbXBsbzogDQoNCiQkXHRleHR7UHJvcC5WYXJ9XDsgPSBcO1xmcmFje1x0ZXh0e0JldHdlZW5zc30gIH17XHRleHR7VG90c3N9fVx0aW1lcyAxMDAgXCxcJSBcOyA9IFw7IFxmcmFjezE4NC44NDU4fXszOTZ9XHRpbWVzIDEwMCBcLFwlIFw7ID0gXDsgNDYuNjhcLFwlJCQNCg0KDQpTZSByZXNhbHRhIHF1ZSBhcnJpYmEgc2UgY2FsY3Vsw7MgZWwgcG9yY2VudGFqZSBkZSB2YXJpYW56YSB0b3RhbCBleHBsaWNhZGEgcG9yIGxhIHZhcmlhY2nDs24gZW50cmUgbG9zIGNlbnRyb2lkZXMgZGUgbG9zIGNsw7pzdGVyZXMuIEVuIFIgc2UgcHVlZGUgY2FsY3VsYXIgY29tbyBzZSBpbmRpY2EgYWJham8uIDogDQoNCg0KDQpgYGB7cn0NCihrbS5yZXMkYmV0d2VlbnNzL2ttLnJlcyR0b3RzcykqMTAwDQpgYGANCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIFZpc3VhbGl6YW5kbyBsb3MgY2zDunN0ZXJzICRrJC1tZWFucw0KDQojIyMgSW50cm9kdWNjacOzbg0KDQoxLiBFcyByZWNvbWVuZGFibGUgZ3JhZmljYXIgbG9zIHJlc3VsdGFkb3MgZGUgbG9zIGNsw7pzdGVyZXMsIHlhIHF1ZSBlc3RvIHBlcm1pdGUgZXZhbHVhciBsYSBlbGVjY2nDs24gZGVsIG7Dum1lcm8gZGUgY2zDunN0ZXJlcyB5IGNvbXBhcmFyIGRpc3RpbnRvcyBhbsOhbGlzaXMgZGUgY2zDunN0ZXJlcy4gDQoNCjIuIEFob3JhIGRlc2VhbW9zIHZpc3VhbGl6YXIgbG9zIGRhdG9zIGVuIHVuIGdyw6FmaWNvIGRlIGRpc3BlcnNpw7NuLCBjb2xvcmVhbmRvIGNhZGEgcHVudG8gc2Vnw7puIHN1IGFzaWduYWNpw7NuIGFsIGNsw7pzdGVyIGNvcnJlc3BvbmRpZW50ZS4NCg0KMy4gRWwgcmV0byBzdXJnZSBjdWFuZG8gbG9zIGRhdG9zIHRpZW5lbiBtw6FzIGRlIGRvcyB2YXJpYWJsZXMsIGxvIHF1ZSBwbGFudGVhIGxhIHByZWd1bnRhIGRlIHF1w6kgdmFyaWFibGVzIHNlbGVjY2lvbmFyIHBhcmEgZWwgZ3LDoWZpY28gZGUgZGlzcGVyc2nDs24gZW4gbG9zIGVqZXMgeCBlIHkuDQoNCjQuIFVuYSBzb2x1Y2nDs24gZXMgYXBsaWNhciB1biBhbGdvcml0bW8gZGUgcmVkdWNjacOzbiBkZSBkaW1lbnNpb25hbGlkYWQsIGNvbW8gZWwgKkFuw6FsaXNpcyBkZSBDb21wb25lbnRlcyBQcmluY2lwYWxlcyogKFBDQSksIHF1ZSB0cmFuc2Zvcm1hIGxhcyBjdWF0cm8gdmFyaWFibGVzIGVuIGRvcyBudWV2YXMgdmFyaWFibGVzIChxdWUgcmVwcmVzZW50YW4gYSBsYXMgb3JpZ2luYWxlcykgcXVlIHB1ZWRlbiB1c2Fyc2UgcGFyYSBlbCBncsOhZmljby4NCg0KNS4gRW4gb3RyYXMgcGFsYWJyYXMsIHNpIGRpc3BvbmVtb3MgZGUgdW4gY29uanVudG8gZGUgZGF0b3MgbXVsdGlkaW1lbnNpb25hbCwgdW5hIHNvbHVjacOzbiBlcyByZWFsaXphciB1biAqQW7DoWxpc2lzIGRlIENvbXBvbmVudGVzIFByaW5jaXBhbGVzKiAoUENBKSB5IGdyYWZpY2FyIGxvcyBwdW50b3MgZGUgZGF0b3Mgc2Vnw7puIGxhcyBjb29yZGVuYWRhcyBkZSBsb3MgZG9zIHByaW1lcm9zIGNvbXBvbmVudGVzIHByaW5jaXBhbGVzLiANCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIEFwbGljYW5kbyBgZmFjdG9yZXh0cmE6OmZ2aXpfY2x1c3RlcmANCg0KMS4gTGEgZnVuY2nDs24gYGZ2aXpfY2x1c3RlcmAgZGVsIHBhcXVldGUgYGZhY3RvZXh0cmFgIHNlIHB1ZWRlIHV0aWxpemFyIHBhcmEgdmlzdWFsaXphciBmw6FjaWxtZW50ZSBsb3MgY2zDunN0ZXJlcyBrLW1lYW5zLiANCg0KMi4gRXN0YSBmdW5jacOzbiB0b21hIGxvcyByZXN1bHRhZG9zIGRlIGstbWVhbnMgeSBsb3MgZGF0b3Mgb3JpZ2luYWxlcyBjb21vIGFyZ3VtZW50b3MuIA0KDQozLiBFbiBlbCBncsOhZmljbyByZXN1bHRhbnRlLCBsYXMgb2JzZXJ2YWNpb25lcyBzZSByZXByZXNlbnRhbiBtZWRpYW50ZSBwdW50b3MsIHV0aWxpemFuZG8gY29tcG9uZW50ZXMgcHJpbmNpcGFsZXMgc2kgZWwgbsO6bWVybyBkZSB2YXJpYWJsZXMgZXMgc3VwZXJpb3IgYSAyLiANCg0KYGBge3J9DQpmdml6X2NsdXN0ZXIoa20ucmVzLCBkYXRhPWRmLA0KICAgICAgICAgICAgIHBhbGV0dGUgPSBjKCJibHVlIiwgImRhcmtvcmFuZ2U0IiwgInJlZCIsICJtYWdlbnRhNCIpLA0KICAgICAgICAgICAgIHhsYWIgPSBGQUxTRSwgDQogICAgICAgICAgICAgeWxhYiA9IEZBTFNFLCANCiAgICAgICAgICAgICBnZW9tPSJwb2ludCIpDQpgYGANCg0KDQo0LiBUYW1iacOpbiBlcyBwb3NpYmxlIGRpYnVqYXIgdW5hIGVsaXBzZSBkZSBjb25jZW50cmFjacOzbiBhbHJlZGVkb3IgZGUgY2FkYSBjbMO6c3Rlci4NCg0KYGBge3J9DQpmdml6X2NsdXN0ZXIoa20ucmVzLCBkYXRhID0gZGYsDQogICAgICAgICAgICBwYWxldHRlID0gYygiYmx1ZSIsICJkYXJrb3JhbmdlNCIsICJyZWQiLCAibWFnZW50YTQiKSwNCiAgICAgICAgICAgIGVsbGlwc2UudHlwZSA9ICJldWNsaWQiLCAjIEVsaXBzZSBkZSBjb25jZW50cmFjacOzbg0KICAgICAgICAgICAgc3Rhci5wbG90ID0gVFJVRSwgICAgICAgICMgU2VnbWVudG9zIGRlc2RlIGNlbnRyb2lkZSBhIMOtdGVtcw0KICAgICAgICAgICAgcmVwZWwgPSBUUlVFLCAgICAgICAgICAgICMgRXZpdGFyIHRyYXNsYXBhbWllbnRvcw0KICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKQ0KICAgICAgICAgICAgKQ0KYGBgDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQoNCiMgRm9ydGFsZXphcyB5IGRlYmlsaWRhZGVzIGRlbCBhbGdvcml0bW8gJGskLW1lYW5zDQoNCiMjIyBGb3J0YWxlemFzDQoNCjEuIEVsIGFsZ29yaXRtbyBkZSBjbHVzdGVyaW5nICRrJC1tZWFucyBlcyBtdXkgc2VuY2lsbG8geSByw6FwaWRvLiANCg0KMi4gRXMgZWZpY2llbnRlIHBhcmEgbWFuZWphciBjb25qdW50b3MgZGUgZGF0b3MgbXV5IGdyYW5kZXMuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgRGViaWxpZGFkZXMNCg0KDQpTaW4gZW1iYXJnbywgcHJlc2VudGEgYWxndW5hcyBkZWJpbGlkYWRlcywgZW50cmUgZWxsYXM6DQoNCjEuICoqUmVxdWllcmUgY29ub2NpbWllbnRvIHByZXZpbyBkZWwgbsO6bWVybyBkZSBjbHVzdGVyczoqKiBFbCBhbmFsaXN0YSBkZWJlIGVsZWdpciBlbCBuw7ptZXJvIGFkZWN1YWRvIGRlIGNsdXN0ZXJzIChrKSBkZSBhbnRlbWFuby4NCg0KMi4gKipTZW5zaWJpbGlkYWQgYSBsYSBzZWxlY2Npw7NuIGluaWNpYWwgZGUgY2VudHJvcyBkZSBjbHVzdGVyczoqKiBMb3MgcmVzdWx0YWRvcyBmaW5hbGVzIGRlcGVuZGVuIGRlIGxhIHNlbGVjY2nDs24gYWxlYXRvcmlhIGluaWNpYWwgZGUgbG9zIGNlbnRyb3MgZGUgbG9zIGNsdXN0ZXJzLiDCv1BvciBxdcOpIGVzIHVuIHByb2JsZW1hPyBQb3JxdWUgY2FkYSBlamVjdWNpw7NuIGRlbCBhbGdvcml0bW8gcHVlZGUgc2VsZWNjaW9uYXIgZGlmZXJlbnRlcyBjZW50cm9zIGluaWNpYWxlcywgbG8gcXVlIHB1ZWRlIGxsZXZhciBhIHJlc3VsdGFkb3MgZGUgY2x1c3RlcmluZyBkaXN0aW50b3MgZW4gZGlmZXJlbnRlcyBlamVjdWNpb25lcyBkZWwgbWlzbW8gY29uanVudG8gZGUgZGF0b3MuDQoNCjMuICoqU2Vuc2liaWxpZGFkIGEgbG9zIHZhbG9yZXMgYXTDrXBpY29zOioqIEVsIGFsZ29yaXRtbyBlcyB2dWxuZXJhYmxlIGEgbG9zIG91dGxpZXJzLg0KDQo0LiAqKkRlcGVuZGVuY2lhIGRlbCBvcmRlbiBkZSBsb3MgZGF0b3M6KiogU2kgc2UgcmVvcmdhbml6YW4gbG9zIGRhdG9zLCBlcyBwcm9iYWJsZSBxdWUgc2Ugb2J0ZW5nYSB1bmEgc29sdWNpw7NuIGRpZmVyZW50ZSBjYWRhIHZleiBxdWUgc2UgY2FtYmllIGVsIG9yZGVuIGRlIGxvcyBkYXRvcy4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBTZXBhcmFkb3IgLS0+DQoNCg0KIyMjIFBvc2libGVzIFNvbHVjaW9uZXMgYSBsYXMgZGViaWxpZGFkZXMNCg0KDQoxLiAqKlBhcmEgbGEgZWxlY2Npw7NuIGRlbCBuw7ptZXJvIGRlIGNsdXN0ZXJzICgkayQpOioqIEVqZWN1dGFyIGVsIGFsZ29yaXRtbyBLLW1lYW5zIHBhcmEgdW4gcmFuZ28gZGUgdmFsb3JlcyBkZSBrLCBwb3IgZWplbXBsbywgdmFyaWFuZG8gayBlbnRyZSAyIHkgMTAuIEx1ZWdvLCBlbGVnaXIgZWwgbWVqb3IgJGskIGNvbXBhcmFuZG8gbG9zIHJlc3VsdGFkb3MgZGUgY2x1c3RlcmluZyBvYnRlbmlkb3MgcGFyYSBsb3MgZGlmZXJlbnRlcyB2YWxvcmVzIGRlICRrJC4NCg0KMi4gKipQYXJhIGxhIHNlbnNpYmlsaWRhZCBhIGxhIHNlbGVjY2nDs24gaW5pY2lhbCBkZSBjZW50cm9zOioqIEVqZWN1dGFyIGVsIGFsZ29yaXRtbyBLLW1lYW5zIHZhcmlhcyB2ZWNlcyBjb24gZGlmZXJlbnRlcyBjZW50cm9zIGRlIGNsdXN0ZXJzIGluaWNpYWxlcy4gU2Ugc2VsZWNjaW9uYSBjb21vIHNvbHVjacOzbiBmaW5hbCBsYSBlamVjdWNpw7NuIGNvbiBsYSBtZW5vciBzdW1hIHRvdGFsIGRlIGN1YWRyYWRvcyBkZW50cm8gZGVsIGNsdXN0ZXIuDQoNCjMuICoqUGFyYSBtaXRpZ2FyIGxhIGluZmx1ZW5jaWEgZGUgbG9zIG91dGxpZXJzOioqIFV0aWxpemFyIGVsIGFsZ29yaXRtbyBQQU0gKFBhcnRpdGlvbmluZyBBcm91bmQgTWVkb2lkcyksIHF1ZSBlcyBtZW5vcyBzZW5zaWJsZSBhIGxvcyB2YWxvcmVzIGF0w61waWNvcy4NCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUgLS0+DQo8IS0tIFNlcGFyYWRvciAtLT4NCg0KDQojIEFsdGVybmF0aXZhIGFsIGFncnVwYW1pZW50byAkayQtbWVhbnMNCg0KMS4gVW5hIGFsdGVybmF0aXZhIHJvYnVzdGEgYWwgJGskLW1lYW5zIGVzIGVsIFBBTSAoUGFydGl0aW9uaW5nIEFyb3VuZCBNZWRvaWRzKSwgcXVlIHNlIGJhc2EgZW4gbWVkb2lkZXMuIA0KDQoyLiBFbCBhZ3J1cGFtaWVudG8gUEFNIHB1ZWRlIGNhbGN1bGFyc2UgdXRpbGl6YW5kbyBsYSBmdW5jacOzbiBgcGFtYCBkZWwgcGFxdWV0ZSBgY2x1c3RlcmAuIA0KDQozLiBMYSBmdW5jacOzbiBgcGFta2AgZGVsIHBhcXVldGUgYGZwY2AgZXMgdW4gZW52b2x0b3JpbyBwYXJhIFBBTSBxdWUgdGFtYmnDqW4gaW1wcmltZSBlbCBuw7ptZXJvIHN1Z2VyaWRvIGRlIGNsdXN0ZXJzIGJhc2FkbyBlbiBlbCBhbmNobyBwcm9tZWRpbyDDs3B0aW1vIGRlIGxhIHNpbHVldGEuDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIFJlc3VtZW4gDQoNCjEuIEVsIGFncnVwYW1pZW50byAkayQtbWVhbnMgcHVlZGUgdXNhcnNlIHBhcmEgY2xhc2lmaWNhciBvYnNlcnZhY2lvbmVzIGVuICRrJCBncnVwb3MsIGJhc8OhbmRvc2UgZW4gc3Ugc2ltaWxpdHVkLiANCg0KMi4gQ2FkYSBncnVwbyBlc3TDoSByZXByZXNlbnRhZG8gcG9yIGVsIHZhbG9yIG1lZGlvIGRlIGxvcyBwdW50b3MgZW4gZWwgZ3J1cG8sIGNvbm9jaWRvIGNvbW8gZWwgKmNlbnRyb2lkZSogZGVsIGNsdXN0ZXIuDQoNCjMuIEVsIGFsZ29yaXRtbyAkayQtbWVhbnMgcmVxdWllcmUgcXVlIGxvcyB1c3VhcmlvcyBlc3BlY2lmaXF1ZW4gZWwgbsO6bWVybyBkZSBjbHVzdGVycyBhIGdlbmVyYXIuIA0KDQo0LiBMYSBmdW5jacOzbiBga21lYW5zYCBkZWwgcGFxdWV0ZSBgc3RhdHNgIGVuIFIgcHVlZGUgdXRpbGl6YXJzZSBwYXJhIGNhbGN1bGFyIGVsIGFsZ29yaXRtbyAkazQtbWVhbnMuDQoNCjUuIEVsIGZvcm1hdG8gc2ltcGxpZmljYWRvIGVzIGBrbWVhbnMoeCwgY2VudGVycylgLCBkb25kZSBgeGAgZXMgZWwgY29uanVudG8gZGUgZGF0b3MgeSBgY2VudGVyc2AgZXMgZWwgbsO6bWVybyBkZSBjbHVzdGVycyBhIHByb2R1Y2lyLg0KDQo2LiBEZXNwdcOpcyBkZSBjYWxjdWxhciBlbCBhZ3J1cGFtaWVudG8gJGskLW1lYW5zLCBsYSBmdW5jacOzbiBgZnZpel9jbHVzdGVyYCBkZWwgcGFxdWV0ZSBgZmFjdG9leHRyYWAgcHVlZGUgdXNhcnNlIHBhcmEgdmlzdWFsaXphciBsb3MgcmVzdWx0YWRvcy4gDQoNCjcuIEVsIGZvcm1hdG8gZXMgYGZ2aXpfY2x1c3RlcihrbS5yZXMsIGRhdGEpYCwgZG9uZGUgYGttLnJlc2Agc29uIGxvcyByZXN1bHRhZG9zIGRlICRrJC1tZWFucyB5IGRhdGEgY29ycmVzcG9uZGUgYWwgY29uanVudG8gZGUgZGF0b3Mgb3JpZ2luYWwuDQoNCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KPCEtLSBDYXDDrXR1bG8gRWplcmNpY2lvcyAtLT4NCg0KIyBFamVyY2ljaW9zDQoNCg0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgRWplcmNpY2lvIDENCg0KDQpgYGB7ciBldmFsPUZBTFNFLCBlY2hvPUZBTFNFfQ0KI2h0dHBzOi8vZGV2ZWxvcGVyLmlibS5jb20vdHV0b3JpYWxzL2F3Yi1jbHVzdGVyLWFuYWx5c2lzLWluLXIvDQpgYGANCg0KQW5hbGljZW1vcyBlbCBzaWd1aWVudGUgY29uanVudG8gZGUgZGF0b3MuIFBhcmEgc3UgY3JlYWNpw7NuLCBnZW5lcmFtb3MgdHJlcyBjbHVzdGVycyB1dGlsaXphbmRvIGRpc3RyaWJ1Y2lvbmVzIG5vcm1hbGVzLiBDYWRhIHB1bnRvIGRlIGRhdG9zIGluY2x1eWUgdHJlcyBjYXJhY3RlcsOtc3RpY2FzOiB1biB2YWxvciBgeGAgZGUgdW5hIHZhcmlhYmxlICRYJCAgLCB1biB2YWxvciBgeWAgZGUgJFkkLCB5IHVuYSBgY2xhc2VgIHF1ZSBldGlxdWV0YSBlbCBwdW50by4gRXhpc3RlbiB0cmVzIGNsYXNlcyBlbiBlc3RlIGNvbmp1bnRvIGRlIGRhdG9zICgxLCAyIHkgMykuIEVsIG9iamV0aXZvIGRlbCBlamVyY2ljaW8gZXMgcXVlIGxvcyBhbGdvcml0bW9zIGRlIGNsdXN0ZXJpbmcgcmVwcmVzZW50ZW4gZXN0YXMgY2xhc2VzIGNvbW8gY2x1c3RlcnMuDQoNCmBgYHtyfQ0KbGlicmFyeShtdnRub3JtKQ0KDQpzZXQuc2VlZCgxMjMpDQpkYXRhc2V0IDwtIHsNCiAgICAgICAgI2NyZWFyIGxhcyAzIGRpc3RyaWJ1Y2lvbmVzDQogICAgICAgIGNsdXN0ZXIxID0gZGF0YS5mcmFtZShybXZub3JtKDQwLCBjKDAsIDApLCBkaWFnKDIpKmMoMiwgMSkpKSAlPiUgbXV0YXRlKGNsYXNzPWZhY3RvcigxKSkNCiAgICAgICAgY2x1c3RlcjIgPSBkYXRhLmZyYW1lKHJtdm5vcm0oMTAwLCBjKDMsIDMpLCBkaWFnKDIpKmMoMSwgMykpKSAlPiUgbXV0YXRlKGNsYXNzPWZhY3RvcigyKSkNCiAgICAgICAgY2x1c3RlcjMgPSBkYXRhLmZyYW1lKHJtdm5vcm0oNjAsIGMoNiwgNiksIGRpYWcoMikpKSAlPiUgbXV0YXRlKGNsYXNzPWZhY3RvcigzKSkNCiAgICAgICAgI2NyZWFyIGVsIGRhdGEgZnJhbWUNCiAgICAgICAgZGF0YSA9IGJpbmRfcm93cyhjbHVzdGVyMSwgY2x1c3RlcjIsIGNsdXN0ZXIzKQ0KICAgICAgICAjbm9tYnJlcyBkZSBsYXMgY29sdW1uYXMNCiAgICAgICAgbmFtZXMoZGF0YSkgPSBjKCJ4IiwgInkiLCAiY2xhc2UiKQ0KICAgICAgICAjcmV0b3JuYXIgbG9zIGRhdG9zDQogICAgICAgIGRhdGENCiAgICAgICAgfQ0KaGVhZChkYXRhc2V0KQ0KYGBgDQoNClBhcmEgZWxsbywgcmVhbGl6YXIgbG9zIHNpZ3VpZW50ZXMgaW5jaXNvcy4NCg0KKGEpIEdyYWZpY2FyIGxvcyBkYXRvcyBlbiB1biBkaWFncmFtYSBkZSBkaXNwZXJzacOzbiB1c2FuZG8gYGdncGxvdGAgeSBlbCBhcmd1bWVudG8gYGNvbG9yPWNsYXNzYCBwYXJhIG9ic2VydmFyIGxhIGFncnVwYWNpw7NuIG5hdHVyYWwgcXVlIHNlIGludGVudGEgcmVwbGljYXIuDQoNCihiKSBBcGxpY2FyIGxhIGZ1bmNpw7NuIGBmdml6X25iY2x1c3RgIGRlbCBwYXF1ZXRlIGBmYWN0b2V4dHJhYCAgcGFyYSBlc3RpbWFyIGVsIG7Dum1lcm8gw7NwdGltbyBkZSBncnVwb3MuDQoNCihjKSBEZWZpbmEgdW4gbnVldm8gb2JqZXRvIHF1ZSBjb250ZW5nYSBzb2xvIGxhcyB2YXJpYWJsZXMgYHhgIHkgYHlgLiANCg0KYGBge3J9DQpkZjEgPC0gZGF0YXNldFtjKCJ4IiwgInkiKV0NCmhlYWQoZGYxKQ0KYGBgDQoNCihkKSBFamVjdXRhciBlbCBhZ3J1cGFtaWVudG8gJGskLW1lYW5zIGNvbiBlbCBuw7ptZXJvIG9wdGltYWwgZGUgY2zDunN0ZXJzIG9idGVuaWRvcyBlbiBlbCBpbmNpc28gYW50ZXJpb3IgKHV0aWxpemFyIGBuc3Rhcj0yMGApLiBMTGFtZSBhbCBudWV2byBvYmpldG8gYGttLnJlczFgLg0KDQooZSkgVXRpbGl6YXIgbGEgZnVuY2nDs24gYGZ2aXpfY2x1c3RlcmAgcGFyYSB2ZXIgbG9zIHJlc3VsdGFkb3MgZGUgbGEgYWdydXBhY2nDs24gJGskLW1lYW5zLg0KDQoNCmBgYHtyLCBldmFsPUZBTFNFLCBlY2hvPUZBTFNFfQ0KbGlicmFyeShtdnRub3JtKQ0KDQpzZXQuc2VlZCgxMjMpDQpkYXRhc2V0IDwtIHsNCiAgICAgICNjcmVhciBsYXMgMyBkaXN0cmlidWNpb25lcw0KICAgICAgY2x1c3RlcjEgPSBkYXRhLmZyYW1lKHJtdm5vcm0oNDAsIGMoMCwgMCksIGRpYWcoMikgKiBjKDIsIDEpKSkgJT4lIG11dGF0ZShjbGFzcz1mYWN0b3IoMSkpDQogICAgICBjbHVzdGVyMiA9IGRhdGEuZnJhbWUocm12bm9ybSgxMDAsIGMoMywgMyksIGRpYWcoMikgKiBjKDEsIDMpKSkgJT4lIG11dGF0ZShjbGFzcz1mYWN0b3IoMikpDQogICAgICBjbHVzdGVyMyA9IGRhdGEuZnJhbWUocm12bm9ybSg2MCwgYyg2LCA2KSwgZGlhZygyKSkpICU+JSBtdXRhdGUoY2xhc3M9ZmFjdG9yKDMpKQ0KDQogICAgICAjY3JlYXIgZWwgZGF0YSBmcmFtZQ0KICAgICAgZGF0YSA9IGJpbmRfcm93cyhjbHVzdGVyMSwgY2x1c3RlcjIsIGNsdXN0ZXIzKQ0KICAgICAgI25vbWJyZXMgZGUgbGFzIGNvbHVtbmFzDQogICAgICBuYW1lcyhkYXRhKSA9IGMoIngiLCAieSIsICJjbGFzZSIpDQogICAgICAjcmV0b3JuYXIgbG9zIGRhdG9zDQogICAgICBkYXRhDQp9DQoNCmhlYWQoZGF0YXNldCkNCg0KIyhhKSBHcmFmaXF1ZSBsb3MgZGF0b3MgZW4gdW4gZGlhZ3JhbWEgZGUgZGlzcGVyc2nDs24gdXNhbmRvIGBnZ3Bsb3RgIHBhcmEgb2JzZXJ2YXIgbGEgYWdydXBhY2nDs24gbmF0dXJhbCBxdWUgc2UgaW50ZW50YSByZXBsaWNhci4NCg0KZGF0YXNldCAlPiUgZ2dwbG90KGFlcyh4PXgsIHk9eSwgY29sb3I9Y2xhc2UpKSArDQogICAgICBnZW9tX3BvaW50KCkgKw0KICAgICAgY29vcmRfZml4ZWQoKSArDQogICAgICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzPWMoMCwgMSwgMikpDQoNCg0KIyhiKSBBcGxpcXVlIGxhIGZ1bmNpw7NuIGBmdml6X25iY2x1c3RgIGRlbCBwYXF1ZXRlIGBmYWN0b2V4dHJhYCAgcGFyYSBlc3RpbWFyIGVsIG7Dum1lcm8gw7NwdGltbyBkZSBncnVwb3MuDQoNCmxpYnJhcnkoZmFjdG9leHRyYSkNCmZ2aXpfbmJjbHVzdChkYXRhc2V0LCBrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiKSArDQpnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSAzLCBsaW5ldHlwZSA9IDIpDQoNCiMoYykgRGVmaW5hIHVuIG51ZXZvIG9iamV0byBxdWUgY29udGVuZ2Egc29sbyBsYXMgdmFyaWFibGVzIGB4YCB5IGB5YC4gDQoNCmRmMSA8LSBkYXRhc2V0W2MoIngiLCAieSIpXQ0KaGVhZChkZjEpDQoNCg0KIyhkKSBFamVjdXRlIGVsIGFncnVwYW1pZW50byAkayQtbWVhbnMgY29uIGVsIG7Dum1lcm8gb3B0aW1hbCBkZSBjbMO6c3RlcnMgb2J0ZW5pZG9zIGVuIGVsIGluY2lzbyBhbnRlcmlvciAodXRpbGl6YXIgYG5zdGFyPTIwYCkuIA0KI0NhbGN1bGFyIGstbWVhbnMgY29uIGsgPSAzDQoNCmsgPC0gMw0Ka20ucmVzMSA8LSBrbWVhbnMoZGYxLCBjZW50ZXJzPWssIG5zdGFydCA9IDIwKQ0Ka20ucmVzMQ0KDQojKGUpIFV0aWxpemFyIGxhIGZ1bmNpw7NuIGBmdml6X2NsdXN0ZXJgIHBhcmEgdmVyIGxvcyByZXN1bHRhZG9zIGRlIGxhIGFncnVwYWNpw7NuICRrJC1tZWFucy4NCiMNCmZ2aXpfY2x1c3RlcihrbS5yZXMxLCBkZjEsIHhsYWIgPSBGQUxTRSwgeWxhYiA9IEZBTFNFLCBnZW9tPSJwb2ludCIpDQoNCmZ2aXpfY2x1c3RlcihrbS5yZXMxLCBkYXRhID0gZGYxLA0KICAgICAgICAgICAgcGFsZXR0ZSA9IGMoImJsdWUiLCAiZGFya29yYW5nZTQiLCAicmVkIiksDQogICAgICAgICAgICBlbGxpcHNlLnR5cGUgPSAiZXVjbGlkIiwgIyBFbGlwc2UgZGUgY29uY2VudHJhY2nDs24NCiAgICAgICAgICAgIHN0YXIucGxvdCA9IFRSVUUsICAgICAgICAjIFNlZ21lbnRvcyBkZXNkZSBjZW50cm9pZGUgYSDDrXRlbXMNCiAgICAgICAgICAgIHJlcGVsID0gVFJVRSwgICAgICAgICAgICAjIEV2aXRhciB0cmFzbGFwYW1pZW50b3MNCiAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkNCiAgICAgICAgICAgICkNCmBgYA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgRWplcmNpY2lvIDINCg0KDQpgYGB7ciBldmFsPUZBTFNFLCBlY2hvPUZBTFNFfQ0KI2h0dHBzOi8vZGV2ZWxvcGVyLmlibS5jb20vdHV0b3JpYWxzL2F3Yi1jbHVzdGVyLWFuYWx5c2lzLWluLXIvDQpgYGANCg0KKipDb250aW51YWNpw7NuIGRlbCBlamVyY2ljaW8gMSoqLiBDb25zaWRlcmUgbGEgc2l0dWFjacOzbiBwbGFudGVhZGEgZW4gZWwgZWplcmNpY2lvIDEgeSBsb3MgcmVzdWx0YWRvcyBlbmNvbnRyYWRvcyBhbGzDrS4gDQoNCihmKSBDb25zaWRlcmUgZWwgY8OzZGlnbyBkZSBhYmFqby4gRW4gbGEgbMOtbmVhIGRlIGPDs2RpZ28gTm8uIDEsIHNlIGNvcGlhIGVsIGRhdGFzZXQgb3JpZ2luYWwgYGRhdGFzZXRgIGNvbW8gdW4gbnVldm8gZGF0YWZyYW1lIGxsYW1hZG8gYGRhdGFzZXRfa21gLiBFbiBsYSBsw61uZWEgZGUgY8OzZGlnbyBOby4gMiwgc2UgYcOxYWRlIHVuYSBudWV2YSBjb2x1bW5hIGxsYW1hZGEgYGNsYXNlX3ByZWRgIGFsIG51ZXZvIGRhdGEgZnJhbWUgYGRhdGFzZXRfa21gLiBFc3RhIGNvbHVtbmEgc2UgcmVsbGVuYSBjb24gbG9zIHZhbG9yZXMgZGUgbGFzIGV0aXF1ZXRhcyBkZSBjbMO6c3RlcmVzIHByZWRpY2hvcyBwb3IgZWwgbW9kZWxvIGRlICRrJC1tZWFucyAoYGttLnJlczEkY2x1c3RlcmApLiBMb3MgdmFsb3JlcyBzZSBjb252aWVydGVuIGVuIGZhY3RvcmVzIChjYXRlZ8Ozcmljb3MpIHVzYW5kbyBsYSBmdW5jacOzbiBgZmFjdG9yYC4gQ29tcGFyZSBsb3MgdmFsb3JlcyBvYnNlcnZhZG9zIChgY2xhc2VgKSBjb24gbG9zIHByZWRpY2hvcyAoYGNsYXNlX3ByZWRgKS4gDQoNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQojMS5Db3BpYXIgZWwgZGF0YXNldCBvcmlnaW5hbCANCmRhdGFzZXRfa20gPC0gZGF0YXNldCANCg0KIzIuIEHDsWFkZSB1bmEgY29sdW1uYSBjb24gbGFzIGNsYXNlcyBwcmVkaWNoYXMNCmRhdGFzZXRfa21bJ2NsYXNlX3ByZWQnXSA9IGZhY3RvcihrbS5yZXMxJGNsdXN0ZXIpIA0KaGVhZChkYXRhc2V0X2ttKQ0KYGBgDQoNCihnKSBDb250cnVpciB1bmEgdGFibGEgZGUgZnJlY3VlbmNpYXMgYWdydXBhZGFzIGVudHJlIGxvcyB2YWxvcmVzIG9ic2VydmFkb3MgKGBjbGFzZWApIGNvbiBsb3MgcHJlZGljaG9zIChgY2xhc2VfcHJlZGApIGUgaW50ZXJwcmV0ZSBjYWRhIHVubyBkZSBsb3MgcmVzdWx0YWRvcyBlbmNvbnRyYWRvcyBlbiBsYXMgY2VsZGFzLiBFbiBwYXJ0aWN1bGFyLCB2ZXJpZmljYXIgbGEgcHJlY2lzacOzbiAoYWNjdXJhY3kpIGRlIGxhIGFncnVwYWNpw7NuLiAgDQoNCg0KKGgpIFZlcmlmaWNhciBsYSBwcmVjaXNpw7NuIChhY2N1cmFjeSkgZGUgbGEgYWdydXBhY2nDs24gZW4gdW4gZ3LDoWZpY28uIENvbXBhcmUgbnVldmFtZW50ZSBsb3MgdmFsb3JlcyBvYnNlcnZhZG9zIChgY2xhc2VgKSBjb24gbG9zIHByZWRpY2hvcyAoYGNsYXNlX3ByZWRgKS4gUHVlZGUgdXRpbGl6YXIgZWwgc2lndWllbnRlIGPDs2RpZ286ICANCg0KYGBge3IsIGV2YWw9RkFMU0V9DQojMy4gRWwgZ3LDoWZpY28NCmRhdGFzZXRfa20gJT4lIGdncGxvdChhZXMoeD14LCB5PXksIHNoYXBlPWNsYXNlLCBjb2xvcj1jbGFzZV9wcmVkKSkgKw0KICAgICAgICAgICAgICAgICAgICAgIGdlb21fcG9pbnQoKSArDQogICAgICAgICAgICAgICAgICAgICAgY29vcmRfZml4ZWQoKSArDQogICAgICAgICAgICAgICAgICAgICAgc2NhbGVfc2hhcGVfbWFudWFsKHZhbHVlcz1jKDAsIDEsIDIpKSArDQogICAgICAgICAgICAgICAgICAgICAgc2NhbGVfc2hhcGUoc29saWQgPSBUUlVFKQ0KYGBgDQoNCg0KYGBge3IsIGV2YWw9RkFMU0UsIGVjaG89RkFMU0V9DQojKGYpIENvbnNpZGVyZSBlbCBjw7NkaWdvIGRlIGFiYWpvLiBFbiBsYSBsw61uZWEgZGUgY8OzZGlnbyBOby4gMSwgc2UgY29waWEgZWwgZGF0YXNldCBvcmlnaW5hbCBgZGF0YXNldGAgY29tbyB1biBudWV2byBkYXRhZnJhbWUgbGxhbWFkbyBgZGF0YXNldF9rbWAuIEVuIGxhIGzDrW5lYSBkZSBjw7NkaWdvIE5vLiAyLCBzZSBhw7FhZGUgdW5hIG51ZXZhIGNvbHVtbmEgbGxhbWFkYSBgY2xhc2VfcHJlZGAgYWwgbnVldm8gZGF0YSBmcmFtZSBgZGF0YXNldF9rbWAuIEVzdGEgY29sdW1uYSBzZSByZWxsZW5hIGNvbiBsb3MgdmFsb3JlcyBkZSBsYXMgZXRpcXVldGFzIGRlIGNsw7pzdGVyZXMgcHJlZGljaG9zIHBvciBlbCBtb2RlbG8gZGUgJGskLW1lYW5zIChga20ucmVzMSRjbHVzdGVyYCkuIExvcyB2YWxvcmVzIHNlIGNvbnZpZXJ0ZW4gZW4gZmFjdG9yZXMgKGNhdGVnw7NyaWNvcykgdXNhbmRvIGxhIGZ1bmNpw7NuIGBmYWN0b3JgLiBDb21wYXJlIGxvcyB2YWxvcmVzIG9ic2VydmFkb3MgKGBjbGFzZWApIGNvbiBsb3MgcHJlZGljaG9zIChgY2xhc2VfcHJlZGApLg0KDQojZjEuQ29waWFyIGVsIGRhdGFzZXQgb3JpZ2luYWwgDQpkYXRhc2V0X2ttIDwtIGRhdGFzZXQgDQoNCiNmMi4gQcOxYWRlIHVuYSBjb2x1bW5hIGNvbiBsYXMgY2xhc2VzIHByZWRpY2hhcw0KZGF0YXNldF9rbVsnY2xhc2VfcHJlZCddID0gZmFjdG9yKGttLnJlczEkY2x1c3RlcikgDQpoZWFkKGRhdGFzZXRfa20pDQoNCiMoZykgQ29udHJ1aXIgdW5hIHRhYmxhIGRlIGZyZWN1ZW5jaWFzIGFncnVwYWRhcyBlbnRyZSBsb3MgdmFsb3JlcyBvYnNlcnZhZG9zIChgY2xhc2VgKSBjb24gbG9zIHByZWRpY2hvcyAoYGNsYXNlX3ByZWRgKSBlIGludGVycHJldGUgY2FkYSB1bm8gZGUgbG9zIHJlc3VsdGFkb3MgZW5jb250cmFkb3MgZW4gbGFzIGNlbGRhcy4gRW4gcGFydGljdWxhciwgdmVyaWZpY2FyIGxhIHByZWNpc2nDs24gKGFjY3VyYWN5KSBkZSBsYSBhZ3J1cGFjacOzbi4gIA0KDQoNCnRhYmxlKGRhdGFzZXRfa20kY2xhc2UsIGRhdGFzZXRfa20kY2xhc2VfcHJlZCkNCg0KDQojKGgpIFZlcmlmaWNhciBsYSBwcmVjaXNpw7NuIChhY2N1cmFjeSkgZGUgbGEgYWdydXBhY2nDs24gZW4gdW4gZ3LDoWZpY28uIENvbXBhcmUgbnVldmFtZW50ZSBsb3MgdmFsb3JlcyBvYnNlcnZhZG9zIChgY2xhc2VgKSBjb24gbG9zIHByZWRpY2hvcyAoYGNsYXNlX3ByZWRgKS4gUHVlZGUgdXRpbGl6YXIgZWwgc2lndWllbnRlIGPDs2RpZ286ICANCg0KIzMuIEVsIGdyw6FmaWNvDQogICAgZGF0YXNldF9rbSAlPiUgZ2dwbG90KGFlcyh4PXgsIHk9eSwgc2hhcGU9Y2xhc2UsIGNvbG9yPWNsYXNlX3ByZWQpKSArDQogICAgICBnZW9tX3BvaW50KCkgKw0KICAgICAgY29vcmRfZml4ZWQoKSArDQogICAgICBzY2FsZV9zaGFwZV9tYW51YWwodmFsdWVzPWMoMCwgMSwgMikpICsNCiAgICAgIHNjYWxlX3NoYXBlKHNvbGlkID0gVFJVRSkNCmBgYA0KDQoNCjwhLS0gJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCjwhLS0gU2VwYXJhZG9yIC0tPg0KDQojIyMgRWplcmNpY2lvIDMNCg0KDQpgYGB7ciBldmFsPUZBTFNFLCBlY2hvPUZBTFNFfQ0KI2h0dHBzOi8vZGV2ZWxvcGVyLmlibS5jb20vdHV0b3JpYWxzL2F3Yi1jbHVzdGVyLWFuYWx5c2lzLWluLXIvDQpgYGANCg0KKipDb250aW51YWNpw7NuIGRlIGxvcyBlamVyY2ljaW9zIDEgeSAyKiouIENvbnNpZGVyZSBsYSBzaXR1YWNpw7NuIHBsYW50ZWFkYSBlbiBlbCBlamVyY2ljaW8gMSB5IGxvcyByZXN1bHRhZG9zIGVuY29udHJhZG9zIGVuIGVzZSBlamVyY2ljaW8geSBlbiBlbCAyLiANCg0KKipJbmNpc28gaS4qKg0KDQpJbnZlc3RpZ3VlIGVsIGNvbmNlcHRvIGRlIG1hdHJpeCBkZSBjb25mdXNpw7NuIHkgbGFzIG3DqXRyaWNhcyBxdWUgc2Ugb2J0aWVuZW4gYSBwYXJ0aXIgZGUgZWxsYS4gRXhwbGlxdWUgbGFzIG3DoXMgaW1wb3J0YW50ZXMuIFB1ZWRlIGNvbnN1bHRhciBsb3Mgc2lndWllbnRlcyBkb2N1bWVudG9zIChkZSBtaSBhdXRvcsOtYSk6IA0KICANCiAgKyBbUlB1YnMgOjogQ2FwLjE5IE1hdHJpeCBkZSBjb25mdXNpw7NuICAocGFyYSBkb3MgY2xhc2VzKV0oaHR0cHM6Ly9ycHVicy5jb20vaGxsaW5hcy9SX0xvZ2l0X0JpbmFyaW9fQ29uZnVzaW9uKS4NCiAgDQogICsgW1JQdWJzIDo6IENhcC4xOCBNYXRyaXggZGUgY29uZnVzacOzbiAgKHBhcmEgdHJlcyBjbGFzZXMpXShodHRwczovL3JwdWJzLmNvbS9obGxpbmFzL1JfTXVsdGkzX0NvbmZ1c2lvbikuDQogIA0KKippbmNpc28gai4qKiANCg0KQXBsaXF1ZSBlbCBjw7NkaWdvIGRlIGFiYWpvIHBhcmEgb2J0ZW5lciBsYSBtYXRyaXggZGUgY29uZnVzacOzbiB5IGxhcyBtw6l0cmljYXMgY29ycmVzcG9uZGllbnRlcy4gSW50ZXJwcmV0ZSBsb3MgcmVzdWx0YWRvcyBvYnRlbmlkb3MuIA0KDQoNCmBgYHtyLCBldmFsPUZBTFNFfQ0KbGlicmFyeShjYXJldCkNCmNvbmZfbWF0IDwtIGNvbmZ1c2lvbk1hdHJpeChmYWN0b3Ioa20ucmVzMSRjbHVzdGVyKSwgZmFjdG9yKGRhdGFzZXQkY2xhc2UpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtb2RlID0gImV2ZXJ5dGhpbmciLCBwb3NpdGl2ZT0iMSIpDQpjb25mX21hdA0KYGBgDQoNCg0KYGBge3IsIGV2YWw9RkFMU0UsICBlY2hvPUZBTFNFfQ0KDQojKGkpIEFwbGlxdWUgZWwgY8OzZGlnbyBkZSBhYmFqbyBwYXJhIG9idGVuZXIgbGEgbWF0cml4IGRlIGNvbmZ1c2nDs24geSBsYXMgbcOpdHJpY2FzIGNvcnJlc3BvbmRpZW50ZXMuIEludGVycHJldGUgbG9zIHJlc3VsdGFkb3Mgb2J0ZW5pZG9zLiANCg0KbGlicmFyeShjYXJldCkNCmtubl9jb25mX21hdCA8LSBjb25mdXNpb25NYXRyaXgoZmFjdG9yKGttLnJlczEkY2x1c3RlciksIGZhY3RvcihkYXRhc2V0JGNsYXNlKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbW9kZSA9ICJldmVyeXRoaW5nIiwgcG9zaXRpdmU9IjEiKQ0KDQprbm5fY29uZl9tYXQNCiAgICANCiAgICANCmBgYA0KDQoNCg0KDQo8IS0tICUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlIC0tPg0KDQo8IS0tIENhcMOtdHVsbyBCaWJsaW9ncmFmw61hLS0+DQoNCg0KIyBCaWJsaW9ncmFmw61hIHsudW5saXN0ZWQgLnVubnVtYmVyZWR9DQogIA0KQ29uc3VsdGFyIGVsIGRvY3VtZW50byBbUlB1YnMgOjogQW7DoWxpc2lzIG11bHRpdmFyaWFkbyAoYmlibGlvZ3JhZsOtYSldKGh0dHBzOi8vcnB1YnMuY29tL2hsbGluYXMvUl9NdWx0aXZhcmlhZG9fQmlibGlvZ3JhZmlhKS4NCg0KPCEtLSAlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSUlJSAtLT4NCg0KJm5ic3A7DQoNCg0KJm5ic3A7DQo8Y2VudGVyPg0Kfn5+DQpJZiB5b3UgZm91bmQgYW55IEVSUk9SUyBvciBoYXZlIFNVR0dFU1RJT05TLCBwbGVhc2UgcmVwb3J0IHRoZW0gdG8gbXkgZW1haWwuIFRoYW5rcy4gIA0Kfn5+DQo8L2NlbnRlcj4NCg0KDQo=