1 Resumen

El presente proyecto evalúa variables relacionadas a niñas y niños menores de 1 a 3 años de la Región de Ayacucho, que fueron atendidos en los establecimientos de salud de Dirección de Regional de Salud (Diresa) respectiva, en el año 2021, con el objeto de obtener perfiles nutricionales que permitan focalizar el servicio de los centros de salud respectivos. Se aplicaron tres (3) metodologías de agrupamiento: Cluster Jerarquico, cluster basado en particiones y algoritmo BIRCH.

2 Introducción

La desnutrición crónica en la infancia es uno de los principales problemas de salud pública y un indicador de desarrollo social del país; tiene efectos irreversibles en el desarrollo de habilidades y capacidades en la niña y el niño.

El INEI en su informe “Perú: Indicadores de resultados de los programas presupuestales, 2015-2020” señala que el 12.1% de la población menor de cinco años de edad del país sufrió desnutrición crónica, con base al patrón de la OMS, siendo 0.1% menor que el 2019 y -2.3% respecto al 2015. A su vez, el mismo informe señala que el 40% de niños y niñas entre 6 a 35 meses en el 2020 presentaron prevalencia de anemia.

El diseño de políticas públicas, y en específico, el desarrollo de programas y proyectos sociales, para mejorar el estado nutricional de los menores de cinco años es una prioridad para el desarrollo nacional; siendo importante la generación de investigaciones que permitan conocer sus perfiles nutricionales

En ese sentido el presente trabajo tiene como objetivo la obtención de clústeres para conocer los perfiles nutricionales de niños y niñas menores de 1 a 3 años que fueron atendidos en establecimientos de salud de primer nivel de la Diresa Ayacucho el año 2021, con fines de identificar patrones que permitan la planificación de intervenciones focalizadas que contribuyan a mejorar su calidad de vida.

3 Descripción de los datos

Los datos corresponden a información de historias clínicas de los establecimientos de salud del Ministerio de Salud del Perú (Minsa), y que es registrada en el Sistema de Información del Estado Nutricional (SIEN), administrado por el Centro Nacional de Alimentación y Nutrición (CENAN), órgano de línea técnico normativo del Instituto Nacional de Salud (INS). La base de datos que usamos cuenta con un total de ““7,599”” registros de menores de 1 a 3 años y fue tomada de la Plataforma Nacional de Datos Abiertos.

Fuente

Unidad de análisis Niñas y niños menores de 1 a 3 años que fueron atendidos en los establecimientos de salud del primer nivel de atención de la Diresa Ayacucho el año 2021.

Variables Las variables utilizadas en el análisis son las siguientes:

Variables predictoras: Edad/meses: Edad en meses del menor atendido. Peso (kg) Talla (cm) PTZ: Índice que compara el peso del menor con el peso esperado para su talla y permite establecer si ha ocurrido una pérdida/ganancia de peso corporal. ZTE: Índice que compara la talla del menor con la talla esperada para su edad y permite establecer si está ocurriendo un retraso en el crecimiento. ZPE: Índice que compara el peso del menor con el peso esperado para su edad y permite establecer si está ocurriendo desnutrición. IMC: Índice de masa corporal Hemoglobina Hemoglobina ajustada: Hemoglobina ajustada según la altura de la localidad de residencia del menor. AlturaREN: Metros sobre el nivel del mar de la localidad de residencia del menor.

4 Metodología

Utilizamos el algoritmo BIRCH (Balanced iterative reducing and clustering using hierarchies) y lo comparamos con los algoritmos K-meansy AGNES para determinar grupos de niños de acuerdo con sus características nutricionales.

El objetivo principal del algoritmo BIRCH es trabajar la clusterizacion con un gran volumen de datos a través del siguiente proceso:

  • A cada uno de los clústers se le asigna un vector de valores, el CF (Clustering Feature).

  • Clustering Feature: Si se tiene un set de N datos, el clustering feature se define como:

\(CF=(N,\bar{LS},SS)\)

Donde:

\(\bar{LS}=\sum_{i=0}^n\bar{X_i}\)

\(SS=\sum_{i=0}^n\bar{X_i}^2\)

  • Si se tienen los datos \((x_1);(x_2);(x_3);(x_4)\)

    • El valor N será igual a 4

    • El valor de \(\bar{LS}\) es igual a \((x_1+x_2+x_3+x_4)\)

    • El valor de \(SS\) será igual a \((x_1^2+x_2^2+x_3^2+x_4^2)\)

    • De esta forma al final los puntos de muestra están representados como \(CF=(N,\bar{LS},SS)\)

  • Luego de construye un árbol de clústers en orden jerárquico de tal modo que las hojas previas representan clústers de mayor tamaño, y que la división de estos produzcan a su vez nuevos nodos (clústers) de menor tamaño.

  • El procedimiento culmina una vez que los clústers alcancen un tamaño inferior a un parámetro dado o se logre el número de clústers deseado [2].

  • El algoritmo Birch cuenta con dos principales parámetros

    • Branching factor (B): Cantidad máxima de subclústers en cada nodo, en caso de superar este factor el nodo se divide en dos nodos con los subclústers redistribuidos.

    • Threshold (T): Es el umbral establecido para la creación de un nuevo subclúster. Establecer este valor bajo genera la creación de más clústers.

Ejemplo del Algoritmo

Considerando los siguientes valores:

N Puntos
1 22
2 9
3 12
4 15

Paso 1: Se elige el Treshold (5).

Paso 2: Se elige el Branching factor (3).

Paso 3: Se toma el primer valor y se eleva al cuadrado.

Paso 4: Se eleva al cuadrado ambos valores y se suma.

Valores N Suma Cuadrado
22 1 22 484
9 2 31 565

Paso 5: El centroide es igual a la suma de los valores sobre N, para el primer valor el centroide sería:

\(Centroide_0 = 22/1 = 22\)

Paso 6: Se aplica la fórmula

\(D=\sqrt{\frac{\sum_{i=1}^n\sum_{j=1}^n(x_i-x_j)^2}{n(n-1)}}\)

\(D=\sqrt{\frac{2n(SS)-2(LS)^2}{n(n-1)}}\)

\(D=\sqrt{\frac{2*2(565)-2(31)^2}{2(2-1)}}\)

Entonces \(D=13\), que es superior al treshold, por lo tanto, se crea un nuevo clúster.

Paso 7: Trabajando el siguiente valor: 12

\(Centroide_0 = 22/1 = 22\)

\(Centroide_1 = 9/1 = 9\)

El punto 12 esta más cerca a 9.

Valores N Suma Cuadrado
9 1 9 81
12 2 21 225

\(D=\sqrt{\frac{2*2(225)-2(21)^2}{2(2-1)}}\)

\(D=3\)

Como \(D=3<5\), se juntan los puntos 9 y 12.

Paso 8: Trabajando el siguiente valor: 15

\(Centroide_2 = (9+12)/2 = 10.5\)

\(Centroide_0 = (22)/1 = 22\)

El punto 12 esta más cerca al \(Centroide_2=10.5\).

Valores N Suma Cuadrado
9, 12 2 21 225
9, 12, 15 3 36 450

\(D=\sqrt{\frac{2*3(450)-2(36)^2}{2(2-1)}}\)

\(D=4.24\)

Como \(D=4.24<5\), se juntan los puntos 9, 12 y 15.

\(Centroide_3 = (9+12+15)/3 = 12\)

\(Centroide_0 = (22)/1 = 22\)

Ejemplo en Python

#Configuración del entorno
rm(list = ls())
setwd(dirname(rstudioapi::getActiveDocumentContext()$path))
graphics.off()
options(scipen = 999)  
options(digits = 3) 
path_python="C:/Users/msarsozaa/Anaconda3"
#Cargando paquetes necesarios
library(pacman)
p_load(reticulate, PerformanceAnalytics, purrr, skimr, corrplot, cluster, psych, ggplot2, 
       stream, ellipse, tictoc,factoextra, NbClust, BiocManager, naniar, DataExplorer, 
       tidyverse, purrr, dplyr, readxl, readr,stats, DescTools, class,devtools,imager,knitr,kableExtra)#source,compareGroups 
#Cargando funciones de usuario
source("funciones.R")
library(reticulate)
use_python(path_python)
import pandas as pd
from sklearn.cluster import Birch
df_ejemplo = pd.DataFrame([22,9,12,15],columns=['Puntos'])

brc0 = Birch(threshold=5,
            branching_factor=3
            )
brc0.fit(df_ejemplo)
Birch(branching_factor=3, threshold=5)

C:\Users\MSARSO~1\ANACON~1\lib\site-packages\sklearn\cluster\_birch.py:717: ConvergenceWarning: Number of subclusters found (2) by BIRCH is less than (3). Decrease the threshold.
  warnings.warn(
pd.concat([df_ejemplo,pd.DataFrame(brc0.predict(df_ejemplo),columns=['Cluster'])],axis=1)
   Puntos  Cluster
0      22        0
1       9        1
2      12        1
3      15        1

5 Lectura de datos

# Cargamos la data
ayacucho <- read.csv("Data.csv")
head_5=head(ayacucho)
kable(head_5,caption = "Ayacucho") %>% kable_styling("striped") %>% scroll_box(width = "100%")
Ayacucho
Diresa Microred EESS Dpto_EESS Prov_EESS Dist_EESS Renipress FechaAtencion Sexo FechaNacimiento Juntos SIS Qaliwarma EdadMeses Peso Talla IMC PTZ ZTE ZPE Hemoglobina AlturaREN Hemoglobinaajustada Cred Suplementacion Consejeria Sesion
AYACUCHO SANTA ROSA I-1 - 00003765 - PUESTO DE SALUD COMUNPIARI AYACUCHO LA MAR SANTA ROSA 3765 12/31/2021 F 12/31/2019 NA NA NA 24 12.0 76.0 15.8 2.53 -3.02 0.35 10.7 330 10.7 1 0 0 0
AYACUCHO SAN MARTIN I-3 - 00003762 - CENTRO DE SALUD SAN MARTIN AYACUCHO LA MAR ANCO 3762 10/06/2021 F 3/24/2020 NA NA NA 19 11.8 79.7 14.9 1.80 -0.49 1.09 11.5 3215 9.4 1 0 0 0
AYACUCHO PAMPA CANGALLO I-4 - 00003507 - CENTRO DE SALUD PAMPA CANGALLO AYACUCHO CANGALLO LOS MOROCHUCOS 3507 6/14/2021 M 05/05/2020 NA NA NA 13 8.9 73.3 12.1 -0.34 -1.63 -1.01 14.4 3330 12.1 1 1 0 0
AYACUCHO NO PERTENECE A NINGUNA MICRORED II-E - 00003575 - HOSPITAL JESUS NAZARENO AYACUCHO HUAMANGA JESUS NAZARENO 3575 8/27/2021 M 5/27/2020 NA NA NA 15 8.4 74.7 11.2 -1.44 -1.76 -1.86 12.4 2780 10.8 0 1 0 0
AYACUCHO SAN MARTIN I-3 - 00003762 - CENTRO DE SALUD SAN MARTIN AYACUCHO LA MAR ANCO 3762 10/30/2021 M 7/25/2020 NA NA NA 15 10.5 76.4 13.7 0.82 -1.16 0.11 11.7 3215 9.6 0 1 0 0
AYACUCHO SANTA ROSA I-4 - 00003764 - SANTA ROSA AYACUCHO LA MAR SANTA ROSA 3764 9/20/2021 F 1/20/2019 NA NA NA 32 13.5 88.8 15.2 0.99 -0.94 0.24 11.6 330 11.6 1 1 0 0
str(ayacucho)
'data.frame':   7599 obs. of  27 variables:
 $ Diresa             : chr  "AYACUCHO" "AYACUCHO" "AYACUCHO" "AYACUCHO" ...
 $ Microred           : chr  "SANTA ROSA" "SAN MARTIN" "PAMPA CANGALLO" "NO PERTENECE A NINGUNA MICRORED" ...
 $ EESS               : chr  "I-1   - 00003765 - PUESTO DE SALUD COMUNPIARI" "I-3   - 00003762 - CENTRO DE SALUD SAN MARTIN" "I-4   - 00003507 - CENTRO DE SALUD PAMPA CANGALLO" "II-E  - 00003575 - HOSPITAL  JESUS NAZARENO" ...
 $ Dpto_EESS          : chr  "AYACUCHO" "AYACUCHO" "AYACUCHO" "AYACUCHO" ...
 $ Prov_EESS          : chr  "LA MAR" "LA MAR" "CANGALLO" "HUAMANGA" ...
 $ Dist_EESS          : chr  "SANTA ROSA" "ANCO" "LOS MOROCHUCOS" "JESUS NAZARENO" ...
 $ Renipress          : int  3765 3762 3507 3575 3762 3764 3600 3782 3783 3603 ...
 $ FechaAtencion      : chr  "12/31/2021" "10/06/2021" "6/14/2021" "8/27/2021" ...
 $ Sexo               : chr  "F" "F" "M" "M" ...
 $ FechaNacimiento    : chr  "12/31/2019" "3/24/2020" "05/05/2020" "5/27/2020" ...
 $ Juntos             : int  NA NA NA NA NA NA NA NA NA NA ...
 $ SIS                : int  NA NA NA NA NA NA NA NA NA NA ...
 $ Qaliwarma          : int  NA NA NA NA NA NA NA NA NA NA ...
 $ EdadMeses          : int  24 19 13 15 15 32 19 27 19 30 ...
 $ Peso               : num  12 11.8 8.9 8.4 10.5 ...
 $ Talla              : num  76 79.7 73.3 74.7 76.4 88.8 77 80 77 90.5 ...
 $ IMC                : num  15.8 14.9 12.1 11.2 13.7 ...
 $ PTZ                : num  2.53 1.8 -0.34 -1.44 0.82 0.99 -0.5 0.07 -0.63 0.82 ...
 $ ZTE                : num  -3.02 -0.49 -1.63 -1.76 -1.16 -0.94 -2.12 -2.45 -1.54 -0.31 ...
 $ ZPE                : num  0.35 1.09 -1.01 -1.86 0.11 0.24 -1.35 -1.33 -1.2 0.44 ...
 $ Hemoglobina        : num  10.7 11.5 14.4 12.4 11.7 ...
 $ AlturaREN          : int  330 3215 3330 2780 3215 330 2800 3499 3499 2734 ...
 $ Hemoglobinaajustada: num  10.7 9.4 12.1 10.8 9.6 ...
 $ Cred               : int  1 1 1 0 0 1 1 1 0 1 ...
 $ Suplementacion     : int  0 0 1 1 1 1 0 0 0 1 ...
 $ Consejeria         : int  0 0 0 0 0 0 0 0 0 1 ...
 $ Sesion             : int  0 0 0 0 0 0 0 0 0 0 ...
#skim(ayacucho)
#describe(ayacucho)

6 Preprocesamiento de datos

# Factorización de variables no númericas
variables_fac <- c("Diresa", "Microred", "EESS", "Dpto_EESS", "Prov_EESS", "Dist_EESS", "Renipress", "Sexo", "Juntos", "SIS", "Qaliwarma", "Cred", "Suplementacion", "Consejeria", "Sesion")
ayacucho[,variables_fac] <- lapply(ayacucho[,variables_fac], factor)
#str(ayacucho) cambio

6.1 Datos Perdidos

aya <-ayacucho[,14:23]
plot_missing(aya)

El análisis muestra que las variables seleccionadas no presentan valores perdidos o faltantes, por lo tanto, no se requiere realizar un procedimiento de imputación.

6.2 Analisis de Outliers

gg_box_density(aya)

A partir de los gráficos anteriores se observa que todas las variables a excepción de EdadMeses poseen valores outliers y extremos en algunos casos, como las variables hemoglobina y AlturaRen y Hemoglobinaajustada.

6.3 Tratamiento de datos Outliers

cols=c('Peso','Talla','IMC','PTZ','ZTE','ZPE','Hemoglobina','AlturaREN','Hemoglobinaajustada')
aya=fn_outliers(aya,cols,2,1.5)
gg_box_density_2(aya)

El procedimiento para tratar los valores outliers y extremos consistió en acotar los valores superiores y inferiores de las variables mediante el principio de 1.5 veces el rango intercuartílico, sumando este valor al percentil 75 para definir el valor superior de la variable y restando el valor indicado al percentil 25 para definir el valor inferior máximo aceptado.

Los valores que fueron superiores o inferiores a los valores en el rango definidos fueron reemplazados por el máximo y mínimo calculado de dicho rango.

6.4 Correlación

#cor <- ayacucho[,14:23]%>% chart.Correlation(histogram=TRUE, pch=15) 
correlacion<-round(cor(ayacucho[,14:23]), 2)
corrplot(correlacion, method="number", type="upper",number.cex = 0.72,tl.cex = 0.8,addCoef.col = 0.5)

De acuerdo con el análisis de correlación de Pearson se interpreta que no existen relación lineal negativa entre las variables.

Existe una correlación lineal positiva fuerte entre las variables:

EdadMeses y Talla: 0.86 Peso y Talla: 0.84 Peso e IMC: 0.92 IMC y PTZ: 0.82 IMC y ZPE: 0.78 PTZ y ZPE: 0.84 Hemoglobina y Hemoglobinaajustada: 0.84

Existe una correlación lineal positiva moderada entre las variables: EdadMeses y Peso: 0.69 EdadMeses y IMC: 0.44 Peso y PTZ: 0.55 Peso y ZTE: 0.42 Peso y ZPE: 0.61 Talla y ZPE: 0.42 Hemoglobina y AlturaRen: 0.44

6.5 Estandarización de los datos

aya <- as.data.frame(scale(aya))

7 Determinando factibilidad del cluster

7.1 Matriz de distancia euclidiana

dis.Data <- dist(aya, metric  = c("euclidean")) # Matriz de distancia #cambio
#dis.Data    <- daisy(aya, metric= "euclidean",stand = TRUE) # Matriz de distancia

7.2 Visualizando la matriz de distancia con fviz_dist()

 library(imager)
 if  (file.exists("graf_max.dist.png")) {      #Cambio  
     img <- load.image('graf_max.dist.png')
     plot(img)
  }

#else{
  # fviz_dist(dis.Data)
 # }

Se realizó el cálculo y visualización la matriz de distancia euclidiana utilizando las funciones fviz_dist () en el paquete factoextra r. Se observan áreas sombreadas de color rojo que indican la cercanía de las observaciones en base al cálculo de distancia indicado. Esto permite inferir que el set de datos es agrupable.

7.3 Estadístico Hopkins

re_process=F
if  (file.exists("res.Hopkins.RData") & !re_process) {      #Cambio  
     load('res.Hopkins.RData') 
}else{
  res.Hopkins <- get_clust_tendency(aya, 
                          n = nrow(aya) - 1, 
                          graph = FALSE, seed = 2022)
  save(res.Hopkins,file='res.Hopkins.RData')
}
res.Hopkins$hopkins_stat
[1] 0.853

Se realizó la prueba estadística de Hopkins, utilizando 0,5 como el umbral para determinar que es poco probable que el set de datos tenga conglomerados estadísticamente significativos. Observamos que el valor resultante de 0.853 se acerca a 1, entonces podemos concluir que el conjunto de datos es significativamente agrupable.

8 Análisis cluster jerarquico (hclust {stats})

8.1 Analizando el metodo de enlace optimo (coeficiente de aglomeración)

metodos= c("single", "complete", "average", "ward.D", "ward.D2")
COEF.AGL(dis.Data,metodos)

  single complete  average   ward.D  ward.D2 
   0.788    0.954    0.888    1.000    0.997 

Hacemos uso de la comparación de los coeficientes de aglomeración para determinar el número de conglomerados y proponer el mejor esquema de agrupación a partir de los diferentes resultados obtenidos al variar todas las combinaciones de métodos de enlace: “single”, “complete”, “average”, “ward.D”, “ward.D2”. Con base en el resultado en la gráfica se puede indicar que el enlace optimo es el de: Ward.D

8.2 Analizando el metodo de enlace optimo (matriz cofenetica)

#Debido a que graficamente no estan muy alejados con coeficientes de aglomeracion, probaremos el metodo de
#correlacion cofenetica para definir el mejor metodo de enlace
re_process=FALSE
if(file.exists("df_cof.RData") & !re_process) {      #Cambio  
     load('df_cof.RData') 
}else{
  metodos=c("ward.D", "ward.D2","complete", "average", "single")
  df_cof=fn_confenetico(dis.Data,metodos)
  save(df_cof,file='df_cof.RData')
}
kable(df_cof,caption = "Correlacion_cofenetica")%>% kable_styling("striped")
Correlacion_cofenetica
Metodo Correl
average 0.551
single 0.464
ward.D2 0.393
ward.D 0.360
complete 0.350

Realizamos el análisis de la matriz cofenetica para determinar la similaridad entre la matriz de distancia original (euclidiana) y la matriz de las uniones de las observaciones en los cluster jerárquicos efectuados con los métodos de enlaces iniciales. Como resultado se observa que no hay una correlación fuerte pero el método average presenta la mayor correlación. Se decide evaluar el análisis de cluster jerárquico con ambos métodos: Ward.D y average.

8.3 Obteniendo el cluster con el metodo: Ward.D

Clus_AG_W <- hclust(dis.Data,method="ward.D")

8.4 Determinando de manera grafica el numero de clusters

library(gghighlight)
longitud=length(Clus_AG_W$height)
alturas <- data.frame(etapa = 1:longitud, distancia = Clus_AG_W$height)
ggplot(alturas) + aes(x = etapa, y = distancia)  +
  geom_point() + geom_line()  + 
  scale_x_continuous(breaks = seq(1, longitud, 1000)) + 
  geom_vline(xintercept = 7597, col = "red", lty = 2) + 
  geom_text(aes(label  = round(distancia,1)),
            size = 3, hjust= +1, vjust= -1) +
  theme_classic() #+ gghighlight(distancia > 11)

El método grafico nos sugiere la existencia de 2 a 5 agrupaciones.

8.5 Determinado el número de clusters optimo

re_process=F
if  (file.exists("res.nbclustW.RData")& !re_process) {      #Cambio  
     load('res.nbclustW.RData') 
}else{
  seed = 2022
  res.nbclustW <- NbClust(aya, distance ="euclidean", 
                         min.nc = 2, max.nc = 8, method = "ward.D", index ="all") #Cambio
  save(res.nbclustW,file='res.nbclustW.RData')
}
par(mfrow=c(1,1))  
fviz_nbclust_x(res.nbclustW)
Among all indices: 
===================
* 2 proposed  0 as the best number of clusters
* 1 proposed  1 as the best number of clusters
* 6 proposed  2 as the best number of clusters
* 8 proposed  3 as the best number of clusters
* 1 proposed  4 as the best number of clusters
* 1 proposed  5 as the best number of clusters
* 3 proposed  6 as the best number of clusters
* 2 proposed  8 as the best number of clusters
* 2 proposed  NA's as the best number of clusters

Conclusion
=========================
* According to the majority rule, the best number of clusters is  3 .

Mediante el uso de paquete NbClust podemos determinar el mejor número de agrupaciones tomando en cuenta los diferentes resultados obtenidos al variar todas las combinaciones la cantidad de clúster deseados para el método de enlace elegido: Ward.D.

8.6 Realizando clustering con K=3

# Cortando en 2 cluester
grp_WR=cutree(Clus_AG_W, k = 3)
# Number de casos en cada cluster
table(grp_WR)
grp_WR
   1    2    3 
3361 2919 1319 
# Descripción de cada cluster
med<-aggregate(aya_bkp, by=list(cluster=grp_WR), mean) #medias
kable(med) %>% kable_styling("striped") %>% scroll_box(width = "100%")
cluster EdadMeses Peso Talla IMC PTZ ZTE ZPE Hemoglobina AlturaREN Hemoglobinaajustada
1 26.5 12.66 86.2 14.7 0.785 -0.673 0.240 13.3 2797 11.8
2 19.9 9.99 78.7 12.7 -0.149 -1.399 -0.796 12.5 2827 11.0
3 30.9 11.32 85.6 13.2 -0.400 -1.803 -1.273 13.7 2986 11.9
#knitr::kable(med, format = "markdown")

Se procedió a cortar la agrupación general (dendograma) en 03 clústers tomando como base el resultado óptimo de Nbclust y obtenemos la cantidad de observaciones por cada uno de ellos.

8.7 Caracterízando los cluster

8.7.1 Graficando los clusters.

#fviz_dend(Clus_AG, k = 2, cex = 0.7, horiz = FALSE, k_colors = "jco",
          #rect = TRUE, rect_border = "jco", rect_fill = TRUE)

8.7.2 Diagrama de caracterización - lineas

data_plot=scale(aya_bkp) #Scale
#data_plot=apply(aya_bkp, 2, normalize) #Max-Min
M<-as.data.frame(t(rbind(aggregate(data_plot, by=list(cluster=grp_WR), mean)[,-1])))
a=as.vector(colMeans(data_plot))
fin=data.frame(M,a,names(aya_bkp));names(fin)<-c("Clus1","clus2","clus3","Media","var")
#fin=data.frame(M,a,names(aya_bkp));names(fin)<-c("Clus1","clus2","Media","var")
ali=melt(fin,id.vars = "var")
ggplot(ali, aes(x=var,y=round(value,1),group=variable,colour=variable)) +
  geom_point()+ geom_line(aes(lty=variable))+ expand_limits(y = c(-1.9, 1.9))+
   theme(axis.text.x = element_text(angle = 60, vjust = 0.5, hjust=1))

Se calcula los promedios de las observaciones con la base de datos original agrupado por cada clúster. Se observa que el clúster 01 tiene mayor cantidad de individuos y estos se caracterizan por tener mayor peso, talla, el mínimo retraso en su crecimiento, un índice nutricional positivo, mayor índice de masa corporal y viven a una altura menor. Por otro lado, se puede observar que el clúster 03 posee los individuos de mayor edad, pero, el peso, el índice de masa corporal, talla es menor al clúster 01 y poseen mayor desnutrición.

8.7.3 Diagrama de caracterización - boxplot

dd <- cbind(aya_bkp, cluster =grp_WR )
dd$cluster<-as.factor(dd$cluster)
df.m <- melt(dd, id.var = "cluster")
p <- ggplot(data = df.m, aes(x=variable, y=value)) + 
  geom_boxplot(aes(fill=cluster))+ facet_wrap( ~ variable, scales="free") 
p

Las gráficas permiten analizar los clústeres de manera individual a través de sus variables. Por ejemplo, el clúster con mayor altura de residencia es el número 03. El clúster con menor talla 03, el clúster con mayor índice de masa corporal es el clúster 01.

8.8 Obteniendo el cluster con el metodo: average

Clus_AG_AV <- hclust(dis.Data,method="average")

8.9 Determinando de manera grafica el numero de clusters

library(gghighlight)
longitud=length(Clus_AG_AV$height)
alturas <- data.frame(etapa = 1:longitud, distancia = Clus_AG_AV$height)
ggplot(alturas) + aes(x = etapa, y = distancia)  +
  geom_point() + geom_line()  + 
  scale_x_continuous(breaks = seq(1, longitud, 1000)) + 
  geom_vline(xintercept = 7597, col = "red", lty = 2) + 
  geom_text(aes(label  = round(distancia,1)),
            size = 3, hjust= +1, vjust= -1) +
  theme_classic() #+ gghighlight(distancia > 11)

El método grafico nos sugiere la existencia de 2 a 5 agrupaciones.

8.10 Determinado el número de clusters optimo

re_process=FALSE
if  (file.exists("res.nbclustAV.RData")& !re_process) {      #Cambio  
     load('res.nbclustAV.RData') 
}else{
  seed = 2022
  res.nbclustAV <- NbClust(aya, distance ="euclidean", 
                         min.nc = 2, max.nc = 8, method = "average", index ="all") #Cambio
  save(res.nbclustAV,file='res.nbclustAV.RData')
}
par(mfrow=c(1,1))  
fviz_nbclust_x(res.nbclustAV)
Among all indices: 
===================
* 2 proposed  0 as the best number of clusters
* 1 proposed  1 as the best number of clusters
* 9 proposed  2 as the best number of clusters
* 8 proposed  3 as the best number of clusters
* 2 proposed  7 as the best number of clusters
* 4 proposed  8 as the best number of clusters

Conclusion
=========================
* According to the majority rule, the best number of clusters is  2 .

los diferentes resultados obtenidos al variar todas las combinaciones la cantidad de clúster deseados para el método de enlace elegido: average.

8.11 Realizando clustering con K=2

# Cortando en 2 cluester
grp_AV=cutree(Clus_AG_AV, k = 2)
# Number de casos en cada cluster
table(grp_AV)
grp_AV
   1    2 
7218  381 
# Descripción de cada cluster
med<-aggregate(aya_bkp, by=list(cluster=grp_AV), mean) #medias
kable(med) %>% kable_styling("striped") %>% scroll_box(width = "100%")
cluster EdadMeses Peso Talla IMC PTZ ZTE ZPE Hemoglobina AlturaREN Hemoglobinaajustada
1 24.6 11.2 83.0 13.5 0.133 -1.192 -0.507 13 2841 11.5
2 27.2 14.4 87.9 16.3 1.875 -0.321 1.211 13 2844 11.5
#knitr::kable(med, format = "markdown")

Se realiza el corte de la agrupación general (dendograma) en 02 clústeres, tomando como base el resultado óptimo de Nbclust y obtenemos la cantidad de observaciones por cada uno de ellos. Se puede observar que el resultado presenta dos grupos, con la mayor cantidad de individuos agrupados en clúster 01. ## Caracterízando los cluster

8.11.1 Graficando los clusters.

#fviz_dend(Clus_AG, k = 2, cex = 0.7, horiz = FALSE, k_colors = "jco",
          #rect = TRUE, rect_border = "jco", rect_fill = TRUE)

8.11.2 Diagrama de caracterización - lineas

data_plot=scale(aya_bkp) #Scale
#data_plot=apply(aya_bkp, 2, normalize) #Max-Min
M<-as.data.frame(t(rbind(aggregate(data_plot, by=list(cluster=grp_AV), mean)[,-1])))
a=as.vector(colMeans(data_plot))
#fin=data.frame(M,a,names(aya_bkp));names(fin)<-c("Clus1","clus2","clus3","Media","var")
fin=data.frame(M,a,names(aya_bkp));names(fin)<-c("Clus1","clus2","Media","var")
ali=melt(fin,id.vars = "var")
ggplot(ali, aes(x=var,y=round(value,1),group=variable,colour=variable)) +
  geom_point()+ geom_line(aes(lty=variable))+ expand_limits(y = c(-1.9, 1.9))+
   theme(axis.text.x = element_text(angle = 60, vjust = 0.5, hjust=1))

El cálculo del promedio por cada grupo no permite diferenciarlos correctamente con una marcada diferencia en muchas variables.

8.11.3 Diagrama de caracterización - boxplot

dd <- cbind(aya_bkp, cluster =grp_AV )
dd$cluster<-as.factor(dd$cluster)
df.m <- melt(dd, id.var = "cluster")
p <- ggplot(data = df.m, aes(x=variable, y=value)) + 
  geom_boxplot(aes(fill=cluster))+ facet_wrap( ~ variable, scales="free") 
p

8.11.4 Usando el indice de rand para definir el cluster optimo

library(fossil)
rand.index(grp_WR,grp_AV)
[1] 0.39

Segun el indice de Rand, no se podria comprobar que las soluciones (ward.D y average) clusters son parecidos, por ese motivos elegiremos el que tiene mayor serparacón a nivel grafico del lineas.

9 Análisis cluster: K – Means

9.1 Determinando número óptimo de clusters

9.1.1 Silhouette method

set.seed(123)
re_process=FALSE
if  (file.exists("gg_sil.RData") & !re_process) {      #Cambio  
     load('gg_sil.RData') 
}else{
   gg_sil=fviz_nbclust(aya, kmeans, method = "silhouette")+
          labs(subtitle = "Silhouette method")
   save(gg_sil,file='gg_sil.RData')
}
gg_sil

El resultado del índice de silueta establece que para K-means es óptimo utilizar 3 clústeres, de este modo se generaron 3 clústeres con 2962, 2210 y 2427 respectivamente.

9.1.2 Elbow method (WSS)

set.seed(123)
re_process=FALSE
if  (file.exists("gg_WSS.RData") & !re_process) {      #Cambio  
     load('gg_WSS.RData') 
}else{
   gg_WSS=fviz_nbclust(aya, kmeans, method = "wss") + geom_vline(xintercept = 3, linetype = 2)+
          labs(subtitle = "Elbow method")
   save(gg_WSS,file='gg_WSS.RData')
}
gg_WSS

9.2 Realizando clustering con K=3

km.res <- kmeans(aya, centers=3,nstart = 25)
grp_km=km.res$cluster
table(grp_km)
grp_km
   1    2    3 
2962 2210 2427 
# Descripción de cada cluster
med<-aggregate(aya_bkp, by=list(cluster=grp_km), mean)
kable(med) %>% kable_styling("striped") %>% scroll_box(width = "100%")
cluster EdadMeses Peso Talla IMC PTZ ZTE ZPE Hemoglobina AlturaREN Hemoglobinaajustada
1 18.6 9.77 77.6 12.6 -0.089 -1.436 -0.765 12.6 2841 11.1
2 26.7 13.19 86.9 15.1 1.089 -0.496 0.547 13.0 2769 11.6
3 30.5 11.77 86.7 13.6 -0.191 -1.390 -0.882 13.6 2909 12.0
#knitr::kable(med, format = "markdown")

9.3 Caracterízando los cluster

9.3.1 Graficando los clusters.

#fviz_cluster(km.res, data = aya,
            # palette = "jco",
             #ellipse.type = "euclid", # Concentration ellipse
             #star.plot = TRUE, # Add segments from centroids to items
             #repel = TRUE, # Avoid label overplotting (slow)
             #ggtheme = theme_minimal())

9.3.2 Diagrama de caracterización - lineas

data_plot=scale(aya_bkp) #Scale
#data_plot=apply(aya_bkp, 2, normalize) #Max-Min
M<-as.data.frame(t(rbind(aggregate(data_plot, by=list(cluster=grp_km), mean)[,-1])))
a=as.vector(colMeans(data_plot))
fin=data.frame(M,a,names(aya_bkp));names(fin)<-c("Clus1","clus2","clus3","Media","var")
#fin=data.frame(M,a,names(aya_bkp));names(fin)<-c("Clus1","clus2","Media","var")
ali=melt(fin,id.vars = "var")
ggplot(ali, aes(x=var,y=round(value,1),group=variable,colour=variable)) +
  geom_point()+ geom_line(aes(lty=variable))+ expand_limits(y = c(-1.9, 1.9))+
   theme(axis.text.x = element_text(angle = 60, vjust = 0.5, hjust=1))

Con respecto a la caracterización de los clústeres, se desprende del gráfico que el valor más cercano entre clústeres es la altura, mientras que el valor más alejado es el peso.

9.3.3 Diagrama de caracterización - boxplot

dd <- cbind(aya_bkp, cluster =grp_km )
dd$cluster<-as.factor(dd$cluster)
df.m <- melt(dd, id.var = "cluster")
p <- ggplot(data = df.m, aes(x=variable, y=value)) + 
  geom_boxplot(aes(fill=cluster))+ facet_wrap( ~ variable, scales="free") 
p

El gráfico de cajas indica que la variable más cercana entre clústers es la alturaREN, y la más lejana es el IMC.

Para la clusterización utilizando el algoritmo Birch, se hizo uso de un Threshold igual a 0.42 y un Branching Factor igual a 50.

10 Análisis cluster Birch (reticulate)

10.1 Configurando entorno de desarrollo

library(reticulate)
use_python(path_python)
aya_p = r_to_py(aya) #r.aya

10.2 Cargando librerias de python en R

import pandas as pd 
from sklearn.preprocessing import StandardScaler 
from sklearn.cluster import Birch 

10.3 Definiendo Birch con: threshold=1, branching_factor=2

df = r.aya_p# Obteniendo objeto R to python
brc = Birch(threshold=0.42,branching_factor=50,n_clusters=3)
brc.fit(df)
Birch(threshold=0.42)

10.4 Realizando predicción

data_total = pd.concat([df,pd.DataFrame(brc.predict(df))],axis=1)
data_total.head()
   EdadMeses      Peso     Talla  ...  AlturaREN  Hemoglobinaajustada  0
0  -0.103463  0.338491 -1.171154  ...  -1.921536            -0.782780  2
1  -0.794094  0.247707 -0.569719  ...   1.022897            -2.052721  2
2  -1.622850 -1.420439 -1.610039  ...   1.337883             0.584849  1
3  -1.346598 -1.704137 -1.382469  ...  -0.168571            -0.685092  1
4  -1.346598 -0.523952 -1.106134  ...   1.022897            -1.857346  2

[5 rows x 11 columns]

10.5 Proporciones por cada cluster

data_total.groupby(0)['Peso'].count()
0
0    2788
1    2404
2    2407
Name: Peso, dtype: int64

La proporción para los 3 clústeres es de 2788, 2404 y 2407 respectivamente.

10.6 Guardamos el objeto con Cluster Birch

Para luego subirlo al R

data_total.to_excel('birch.xlsx')

10.7 Leemos el objeto con Cluster Birch en R

bi <- read_excel("birch.xlsx")
birch <- as.data.frame(bi[,2:12])
names (birch)[11] = "cluster"
birch$cluster <- factor(birch$cluster, levels = c(0,1,2), labels = c(1,2,3))

head(birch)
  EdadMeses   Peso  Talla    IMC    PTZ     ZTE    ZPE Hemoglobina AlturaREN
1    -0.103  0.338 -1.171  1.593  2.522 -2.0201  0.848      -1.968    -1.922
2    -0.794  0.248 -0.570  0.899  1.725  0.7099  1.663      -1.296     1.023
3    -1.623 -1.420 -1.610 -1.116 -0.612 -0.5202 -0.649       1.138     1.338
4    -1.347 -1.704 -1.382 -1.782 -1.813 -0.6605 -1.585      -0.541    -0.169
5    -1.347 -0.524 -1.106  0.054  0.654 -0.0131  0.584      -1.128     1.023
6     1.002  1.190  0.909  1.157  0.840  0.2243  0.727      -1.212    -1.922
  Hemoglobinaajustada cluster
1             -0.7828       3
2             -2.0527       3
3              0.5848       2
4             -0.6851       2
5             -1.8573       3
6              0.0964       3

10.8 Caracterízando los cluster

data_plot=birch[1:10]
cluster=birch$cluster
table(cluster)
cluster
   1    2    3 
2788 2404 2407 
# Descripción de cada cluster
med<-aggregate(aya, by=list(cluster=cluster), mean)

med
  cluster EdadMeses    Peso   Talla    IMC    PTZ    ZTE    ZPE Hemoglobina
1       1     0.658  0.7457  0.7675  0.590  0.213  0.346  0.321      0.6561
2       2    -0.618 -0.9573 -0.8110 -0.913 -0.605 -0.546 -0.711      0.0541
3       3    -0.144  0.0924 -0.0789  0.229  0.358  0.144  0.338     -0.8140
  AlturaREN Hemoglobinaajustada
1     0.122               0.643
2     0.270              -0.146
3    -0.411              -0.599
# knitr::kable(med) %>% kable_styling("striped") %>% scroll_box(width = "100%")

10.9 Diagrama de caracterización - lineas

M<-as.data.frame(t(rbind(aggregate(data_plot, by=list(cluster=cluster), mean)[,-1])))

a=as.vector(colMeans(data_plot))
fin=data.frame(M,a,names(aya_bkp));names(fin)<-c("Clus1","Clus2","Clus3","Media","var")

ali=melt(fin,id.vars = "var")
ggplot(ali, aes(x=var,y=round(value,1),group=variable,colour=variable)) +
  geom_point()+ geom_line(aes(lty=variable))+ expand_limits(y = c(-1.9, 1.9))+
   theme(axis.text.x = element_text(angle = 60, vjust = 0.5, hjust=1))

No existen diferencias tan marcadas en AlturaREN entre el cluster 1 y 2. En PTZ, ZPE y ZTE tampoco hay diferencias marcadas entre el cluster 1 y 2. Mientras que diferencias más marcadas entre Hemoglobina, HemoglobinaAjustada, IMC, Peso y Talla

10.9.1 Diagrama de caracterización - boxplot

dd <- cbind(aya_bkp, cluster =cluster )
dd$cluster<-as.factor(dd$cluster)
df.m <- melt(dd, id.var = "cluster")
p <- ggplot(data = df.m, aes(x=variable, y=value)) + 
  geom_boxplot(aes(fill=cluster))+ facet_wrap( ~ variable, scales="free") 
p

El diagrama de cajas indica que la variable con los valores más cercanos entre clústers es la AlturaREN, mientras que el más alejado es la Edad en Meses.

11 Validando los clusters

11.1 Interna (cohesión y separación)

re_process=TRUE
# if  (file.exists("df_indexs.RData") & !re_process) {      #Cambio  
     # load('df_indexs.RData') 
# }else{
    library(clusterSim)
    library(clValid)
    #Indice de Davies-Bouldin: buscamos el valor mas alto posible
    DBkm_ <- index.DB(aya, km.res$cluster, centrotypes = "centroids")$DB #kmeans
    DBHc_ <-index.DB(aya, grp_WR, d=dis.Data,centrotypes="centroids")$DB #hclust
    DBirch_ <-index.DB(aya, as.numeric(cluster), centrotypes="centroids")$DB #Birch
    
    #Indice de dunn: buscamos el valor mas bajo posible
    Dnkm_ <- dunn(Data = aya, clusters = km.res$cluster, distance = NULL)#kmeans
    DnHc_ <- dunn(dis.Data, grp_WR) #hclust
    DnBirch_ <- dunn(Data = aya, clusters = as.numeric(cluster), distance = NULL)#Birch
    
    tipo_=c('kmeans','hclust','Birch')
    davies.bouldin_=c(DBkm_,DBHc_,DBirch_)
    dunn_=c(Dnkm_,DnHc_,DnBirch_)
    df_indexs=data.frame(tipo_,davies.bouldin_,dunn_)
    colnames(df_indexs)=c('Cluster','Davies.Bouldin','Dunn')
    save(df_indexs,file='df_indexs.RData')
# }
    # df_indexs
# kable(df_indexs) %>% kable_styling("striped") %>% scroll_box(width = "100%")
silhouette<-rbind(
mean(silhouette(as.numeric(km.res$cluster) ,dis.Data)[,3]), #kmeans
mean(silhouette(as.numeric(grp_WR) ,dis.Data)[,3]), #hclust
mean(silhouette(as.numeric(birch$cluster) ,dis.Data)[,3]) #Birch
)
cbind(df_indexs, silhouette)
  Cluster Davies.Bouldin   Dunn silhouette
1  kmeans           1.74 0.0100      0.184
2  hclust           1.81 0.0287      0.125
3   Birch           2.17 0.0174      0.134

De acuerdo Davies.Bouldin el mayor valor es método es kmeans, respecto a Dunn el menor valor es hclust y con respecto a silhouette el mejor es k-means.

Creamos una nueva variable llamada Anemia en base a Hemoglobinaajustada para analizarlos por grupos de resultados de K-means.

ayacucho_final<-ayacucho
ayacucho_final$Anemia<-if_else(ayacucho$Hemoglobinaajustada*10>=110, 'Sin anemia', 
        if_else(ayacucho$Hemoglobinaajustada*10>=100,'Leve',
                if_else(ayacucho$Hemoglobinaajustada*10>=70,'Moderada',
                        'Grave')))
        
ayacucho_final=cbind(ayacucho_final, cluster=km.res$cluster)

table(ayacucho_final$Anemia)

     Grave       Leve   Moderada Sin anemia 
        11       1186        645       5757 

Tomando los resultados de K-means

g1_F <-
  ggplot(mutate(ayacucho_final, cluster = factor(cluster))) +
  aes(cluster, fill =Anemia ) +
  geom_bar(cluster = position_fill()) +
  labs(title="cluster según Anemia",
            x = NULL, y = "Proporción") +
  theme_bw()
g1_F

Vemos que el mayor grupo de niños con desnutrición moderada y leve esta en el cluster 1, seguido del cluster 2 y por ultimo el cluster 3. El mayor número de niños sin anemia esta en el cluster 3.

Del mismo modo transponiendo los ejes del grafico anterior, el cluster 1 tiene la mayor proporción de niños con anemia leve, lo mismo ocurre en anemia moderada.

g2_F <-
  ggplot(mutate(ayacucho_final, cluster = factor(cluster))) +
  aes(Anemia, fill =cluster ) +
  geom_bar(Anemia = position_fill()) +
  labs(title="Anemia según cluster",
            x = NULL, y = "Proporción") +
  theme_linedraw()
g2_F

12 Conclusiones

  • La evaluación tanto por K-prototypes como con la función NbClust nos indica que debemos trabajar con 3 grupos.

  • La metodología AGNES y Fuzzy C-Means en las variables numéricas generan clústeres con un comportamiento muy similar, sin embargo, en el caso de las variables categóricas se tienen diferencias significativas.

  • Se determina mediante el gráfico de perfiles que la edad gestacional no genera un mayor aporte al comportamiento de los grupos, ya que en los tres los valores

  • Finalmente considerando AGNES, se tiene: Grupo 1: Gestantes en su mayoría de embarazo simple, de Regiones Apurímac, Huancavelica y Pasco, por lo que tienen un nivel de altitud mayor y un nivel de hemoglobina superior a los demás grupos. Grupo 2: Gestantes en su mayoría de embarazo múltiple, de la región de Ayacucho, por lo que tienen un nivel de altitud intermedia y con hemoglobina IMC, Peso, PPG y Talla en un nivel medio. Grupo 3: Gestantes con un embarazo simple de la región de Junín con el

    GRUPO ATRIBUTO AGNES K-Means BIRCH
    Grupo 1 Cantidad 3,361 2,962 2,788
    % 44.2% 39.0% 36.7%
    Grupo 2 Cantidad 2,919 2,210 2,404
    % 38.4% 29.1% 31.6%
    Grupo 3 Cantidad 1,319 2,427 2,407
    % 17.4% 31.9% 31.7%

13 Referencias

  • Fanny Ramadhani et al 2020 IOP Conf. Ser.: Mater. Sci. Eng. 725 012090

  • Alonso del Saso, Javier “Métodos de detección de anomalías y clustering en series temporales” 2020, Santander, España.

LS0tDQp0aXRsZTogIkFsZ29yaW1vIEJpcnRjaDogRXN0YWRvIG51dHJpY2lvbmFsIGRlIG5pw7FhcyB5IG5pw7FvcyBtZW5vcmVzIGRlIDEgYSAzIGHDsW9zDQogIGRlIGxhIFJlZ2nDs24gZGUgQXlhY3VjaG8gLSBQZXLDuiINCmRhdGU6ICIyMDIyLTEyLTAzIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiBubw0KICAgICAgc21vb3RoX3Njcm9sbDogbm8NCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIGFuY2hvcl9zZWN0aW9uczogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgdGhlbWU6IHlldGkNCiAgd29yZF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KLS0tDQo8IS0tIFBhcmFtZXRyb3MgZ2VuZXJhbGVzIGRlbCBtYXJrZG9udy0tPg0KDQpgYGB7ciBpbmNsdWRlPUZBTFNFfQ0KI0NvbmZpZ3VyYWNpw7NuIGRlIGxhcyBjaHVuaw0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFLGNvbW1lbnQgPSBOQSkNCmBgYA0KDQoNCg0KDQojICoqUmVzdW1lbioqDQoNCkVsIHByZXNlbnRlIHByb3llY3RvIGV2YWzDumEgdmFyaWFibGVzIHJlbGFjaW9uYWRhcyBhIG5pw7FhcyB5IG5pw7FvcyBtZW5vcmVzIGRlIDEgYSAzIGHDsW9zIGRlIGxhIFJlZ2nDs24gZGUgQXlhY3VjaG8sIHF1ZSBmdWVyb24gYXRlbmRpZG9zIGVuIGxvcyBlc3RhYmxlY2ltaWVudG9zIGRlIHNhbHVkIGRlIERpcmVjY2nDs24gZGUgUmVnaW9uYWwgZGUgU2FsdWQgKERpcmVzYSkgcmVzcGVjdGl2YSwgZW4gZWwgYcOxbyAyMDIxLCBjb24gZWwgb2JqZXRvIGRlIG9idGVuZXIgcGVyZmlsZXMgbnV0cmljaW9uYWxlcyBxdWUgcGVybWl0YW4gZm9jYWxpemFyIGVsIHNlcnZpY2lvIGRlIGxvcyBjZW50cm9zIGRlIHNhbHVkIHJlc3BlY3Rpdm9zLiBTZSBhcGxpY2Fyb24gdHJlcyAoMykgbWV0b2RvbG9nw61hcyBkZSBhZ3J1cGFtaWVudG86IENsdXN0ZXIgSmVyYXJxdWljbywgY2x1c3RlciBiYXNhZG8gZW4gcGFydGljaW9uZXMgeSBhbGdvcml0bW8gQklSQ0guDQoNCg0KIyAqKkludHJvZHVjY2nDs24qKg0KDQpMYSBkZXNudXRyaWNpw7NuIGNyw7NuaWNhIGVuIGxhIGluZmFuY2lhIGVzIHVubyBkZSBsb3MgcHJpbmNpcGFsZXMgcHJvYmxlbWFzIGRlIHNhbHVkIHDDumJsaWNhIHkgdW4gaW5kaWNhZG9yIGRlIGRlc2Fycm9sbG8gc29jaWFsIGRlbCBwYcOtczsgdGllbmUgZWZlY3RvcyBpcnJldmVyc2libGVzIGVuIGVsIGRlc2Fycm9sbG8gZGUgaGFiaWxpZGFkZXMgeSBjYXBhY2lkYWRlcyBlbiBsYSBuacOxYSB5IGVsIG5pw7FvLg0KDQpFbCBJTkVJIGVuIHN1IGluZm9ybWUg4oCcUGVyw7o6IEluZGljYWRvcmVzIGRlIHJlc3VsdGFkb3MgZGUgbG9zIHByb2dyYW1hcyBwcmVzdXB1ZXN0YWxlcywgMjAxNS0yMDIw4oCdIHNlw7FhbGEgcXVlIGVsIDEyLjElIGRlIGxhIHBvYmxhY2nDs24gbWVub3IgZGUgY2luY28gYcOxb3MgZGUgZWRhZCBkZWwgcGHDrXMgc3VmcmnDsyBkZXNudXRyaWNpw7NuIGNyw7NuaWNhLCBjb24gYmFzZSBhbCBwYXRyw7NuIGRlIGxhIE9NUywgc2llbmRvIDAuMSUgbWVub3IgcXVlIGVsIDIwMTkgeSAtMi4zJSByZXNwZWN0byBhbCAyMDE1LiBBIHN1IHZleiwgZWwgbWlzbW8gaW5mb3JtZSBzZcOxYWxhIHF1ZSBlbCA0MCUgZGUgbmnDsW9zIHkgbmnDsWFzIGVudHJlIDYgYSAzNSBtZXNlcyBlbiBlbCAyMDIwIHByZXNlbnRhcm9uIHByZXZhbGVuY2lhIGRlIGFuZW1pYS4gDQoNCkVsIGRpc2XDsW8gZGUgcG9sw610aWNhcyBww7pibGljYXMsIHkgZW4gZXNwZWPDrWZpY28sIGVsIGRlc2Fycm9sbG8gZGUgcHJvZ3JhbWFzIHkgcHJveWVjdG9zIHNvY2lhbGVzLCBwYXJhIG1lam9yYXIgZWwgZXN0YWRvIG51dHJpY2lvbmFsIGRlIGxvcyBtZW5vcmVzIGRlIGNpbmNvIGHDsW9zIGVzIHVuYSBwcmlvcmlkYWQgcGFyYSBlbCBkZXNhcnJvbGxvIG5hY2lvbmFsOyBzaWVuZG8gaW1wb3J0YW50ZSBsYSBnZW5lcmFjacOzbiBkZSBpbnZlc3RpZ2FjaW9uZXMgcXVlIHBlcm1pdGFuIGNvbm9jZXIgc3VzIHBlcmZpbGVzIG51dHJpY2lvbmFsZXMNCg0KRW4gZXNlIHNlbnRpZG8gZWwgcHJlc2VudGUgdHJhYmFqbyB0aWVuZSBjb21vIG9iamV0aXZvIGxhIG9idGVuY2nDs24gZGUgY2zDunN0ZXJlcyBwYXJhIGNvbm9jZXIgbG9zIHBlcmZpbGVzIG51dHJpY2lvbmFsZXMgZGUgbmnDsW9zIHkgbmnDsWFzIG1lbm9yZXMgZGUgMSBhIDMgYcOxb3MgcXVlIGZ1ZXJvbiBhdGVuZGlkb3MgZW4gZXN0YWJsZWNpbWllbnRvcyBkZSBzYWx1ZCBkZSBwcmltZXIgbml2ZWwgZGUgbGEgRGlyZXNhIEF5YWN1Y2hvIGVsIGHDsW8gMjAyMSwgY29uIGZpbmVzIGRlIGlkZW50aWZpY2FyIHBhdHJvbmVzIHF1ZSBwZXJtaXRhbiBsYSBwbGFuaWZpY2FjacOzbiBkZSBpbnRlcnZlbmNpb25lcyBmb2NhbGl6YWRhcyBxdWUgY29udHJpYnV5YW4gYSBtZWpvcmFyIHN1IGNhbGlkYWQgZGUgdmlkYS4NCg0KIyAqKkRlc2NyaXBjacOzbiBkZSBsb3MgZGF0b3MqKg0KDQpMb3MgZGF0b3MgY29ycmVzcG9uZGVuIGEgaW5mb3JtYWNpw7NuIGRlIGhpc3RvcmlhcyBjbMOtbmljYXMgZGUgbG9zIGVzdGFibGVjaW1pZW50b3MgZGUgc2FsdWQgZGVsIE1pbmlzdGVyaW8gZGUgU2FsdWQgZGVsIFBlcsO6IChNaW5zYSksIHkgcXVlIGVzIHJlZ2lzdHJhZGEgZW4gZWwgU2lzdGVtYSBkZSBJbmZvcm1hY2nDs24gZGVsIEVzdGFkbyBOdXRyaWNpb25hbCAoU0lFTiksIGFkbWluaXN0cmFkbyBwb3IgZWwgQ2VudHJvIE5hY2lvbmFsIGRlIEFsaW1lbnRhY2nDs24geSBOdXRyaWNpw7NuIChDRU5BTiksIMOzcmdhbm8gZGUgbMOtbmVhIHTDqWNuaWNvIG5vcm1hdGl2byBkZWwgSW5zdGl0dXRvIE5hY2lvbmFsIGRlIFNhbHVkIChJTlMpLiBMYSBiYXNlIGRlIGRhdG9zIHF1ZSB1c2Ftb3MgY3VlbnRhIGNvbiB1biB0b3RhbCBkZSAiIjcsNTk5IiIgcmVnaXN0cm9zIGRlIG1lbm9yZXMgZGUgMSBhIDMgYcOxb3MgeSBmdWUgdG9tYWRhIGRlIGxhIFBsYXRhZm9ybWEgTmFjaW9uYWwgZGUgRGF0b3MgQWJpZXJ0b3MuDQoNCg0KW0Z1ZW50ZV0oaHR0cHM6Ly9kYXRvcy5pbnMuZ29iLnBlL2RhdGFzZXQvc2lzdGVtYS1kZS1pbmZvcm1hY2lvbi1kZWwtZXN0YWRvLW51dHJpY2lvbmFsLWRlLW5pbm9zLXktZ2VzdGFudGVzLXBlcnUtaW5zLWNlbmFuLTIwMTktMjAyMCkNCg0KDQpVbmlkYWQgZGUgYW7DoWxpc2lzDQpOacOxYXMgeSBuacOxb3MgbWVub3JlcyBkZSAxIGEgMyBhw7FvcyBxdWUgZnVlcm9uIGF0ZW5kaWRvcyBlbiBsb3MgZXN0YWJsZWNpbWllbnRvcyBkZSBzYWx1ZCBkZWwgcHJpbWVyIG5pdmVsIGRlIGF0ZW5jacOzbiBkZSBsYSBEaXJlc2EgQXlhY3VjaG8gZWwgYcOxbyAyMDIxLg0KDQpWYXJpYWJsZXMNCkxhcyB2YXJpYWJsZXMgdXRpbGl6YWRhcyBlbiBlbCBhbsOhbGlzaXMgc29uIGxhcyBzaWd1aWVudGVzOg0KDQpWYXJpYWJsZXMgcHJlZGljdG9yYXM6DQoqRWRhZC9tZXNlczogRWRhZCBlbiBtZXNlcyBkZWwgbWVub3IgYXRlbmRpZG8uDQoqUGVzbyAoa2cpDQoqVGFsbGEgKGNtKQ0KKlBUWjogw41uZGljZSBxdWUgY29tcGFyYSBlbCBwZXNvIGRlbCBtZW5vciBjb24gZWwgcGVzbyBlc3BlcmFkbyBwYXJhIHN1IHRhbGxhIHkgcGVybWl0ZSBlc3RhYmxlY2VyIHNpIGhhIG9jdXJyaWRvIHVuYSBww6lyZGlkYS9nYW5hbmNpYSBkZSBwZXNvIGNvcnBvcmFsLg0KKlpURTogw41uZGljZSBxdWUgY29tcGFyYSBsYSB0YWxsYSBkZWwgbWVub3IgY29uIGxhIHRhbGxhIGVzcGVyYWRhIHBhcmEgc3UgZWRhZCB5IHBlcm1pdGUgZXN0YWJsZWNlciBzaSBlc3TDoSBvY3VycmllbmRvIHVuIHJldHJhc28gZW4gZWwgY3JlY2ltaWVudG8uDQoqWlBFOiDDjW5kaWNlIHF1ZSBjb21wYXJhIGVsIHBlc28gZGVsIG1lbm9yIGNvbiBlbCBwZXNvIGVzcGVyYWRvIHBhcmEgc3UgZWRhZCB5IHBlcm1pdGUgZXN0YWJsZWNlciBzaSBlc3TDoSBvY3VycmllbmRvIGRlc251dHJpY2nDs24uDQoqSU1DOiDDjW5kaWNlIGRlIG1hc2EgY29ycG9yYWwNCipIZW1vZ2xvYmluYQ0KKkhlbW9nbG9iaW5hIGFqdXN0YWRhOiBIZW1vZ2xvYmluYSBhanVzdGFkYSBzZWfDum4gbGEgYWx0dXJhIGRlIGxhIGxvY2FsaWRhZCBkZSByZXNpZGVuY2lhIGRlbCBtZW5vci4NCipBbHR1cmFSRU46IE1ldHJvcyBzb2JyZSBlbCBuaXZlbCBkZWwgbWFyIGRlIGxhIGxvY2FsaWRhZCBkZSByZXNpZGVuY2lhIGRlbCBtZW5vci4NCg0KDQojICoqTWV0b2RvbG9nw61hKioNCg0KDQpVdGlsaXphbW9zIGVsIGFsZ29yaXRtbyBCSVJDSCAoQmFsYW5jZWQgaXRlcmF0aXZlIHJlZHVjaW5nIGFuZCBjbHVzdGVyaW5nIHVzaW5nIGhpZXJhcmNoaWVzKSB5IGxvIGNvbXBhcmFtb3MgY29uIGxvcyBhbGdvcml0bW9zIEstbWVhbnN5IEFHTkVTICBwYXJhIGRldGVybWluYXIgZ3J1cG9zIGRlIG5pw7FvcyBkZSBhY3VlcmRvIGNvbiBzdXMgY2FyYWN0ZXLDrXN0aWNhcyBudXRyaWNpb25hbGVzLg0KDQpFbCBvYmpldGl2byBwcmluY2lwYWwgZGVsIGFsZ29yaXRtbyAqKkJJUkNIKiogZXMgdHJhYmFqYXIgbGEgY2x1c3Rlcml6YWNpb24gY29uIHVuIGdyYW4gdm9sdW1lbiBkZSBkYXRvcyBhIHRyYXbDqXMgZGVsIHNpZ3VpZW50ZSBwcm9jZXNvOg0KDQoNCioJQSBjYWRhIHVubyBkZSBsb3MgY2zDunN0ZXJzIHNlIGxlIGFzaWduYSB1biB2ZWN0b3IgZGUgdmFsb3JlcywgZWwgQ0YgKENsdXN0ZXJpbmcgRmVhdHVyZSkuDQoNCg0KICAtCUNsdXN0ZXJpbmcgRmVhdHVyZTogDQogICAgU2kgc2UgdGllbmUgdW4gc2V0IGRlIE4gZGF0b3MsIGVsIGNsdXN0ZXJpbmcgZmVhdHVyZSBzZSBkZWZpbmUgY29tbzogDQoNCiAgJENGPShOLFxiYXJ7TFN9LFNTKSQNCg0KRG9uZGU6DQoNCiAgJFxiYXJ7TFN9PVxzdW1fe2k9MH1eblxiYXJ7WF9pfSQNCg0KICAkU1M9XHN1bV97aT0wfV5uXGJhcntYX2l9XjIkDQoNCg0KKiBTaSBzZSB0aWVuZW4gbG9zIGRhdG9zICQoeF8xKTsoeF8yKTsoeF8zKTsoeF80KSQNCg0KICAtCUVsIHZhbG9yIE4gc2Vyw6EgaWd1YWwgYSA0DQogIA0KICAtCUVsIHZhbG9yIGRlICRcYmFye0xTfSQgZXMgaWd1YWwgYSAgJCh4XzEreF8yK3hfMyt4XzQpJA0KICANCiAgLSBFbCB2YWxvciBkZSAkU1MkIHNlcsOhIGlndWFsIGEgICQoeF8xXjIreF8yXjIreF8zXjIreF80XjIpJA0KICANCiAgLQlEZSBlc3RhIGZvcm1hIGFsIGZpbmFsIGxvcyBwdW50b3MgZGUgbXVlc3RyYSBlc3TDoW4gcmVwcmVzZW50YWRvcyBjb21vICRDRj0oTixcYmFye0xTfSxTUykkDQoNCg0KKglMdWVnbyBkZSBjb25zdHJ1eWUgdW4gw6FyYm9sIGRlIGNsw7pzdGVycyBlbiBvcmRlbiBqZXLDoXJxdWljbyBkZSB0YWwgbW9kbyBxdWUgbGFzIGhvamFzIHByZXZpYXMgcmVwcmVzZW50YW4gY2zDunN0ZXJzIGRlIG1heW9yIHRhbWHDsW8sIHkgcXVlIGxhIGRpdmlzacOzbiBkZSBlc3RvcyBwcm9kdXpjYW4gYSBzdSB2ZXogbnVldm9zIG5vZG9zIChjbMO6c3RlcnMpIGRlIG1lbm9yIHRhbWHDsW8uDQoNCioJRWwgcHJvY2VkaW1pZW50byBjdWxtaW5hIHVuYSB2ZXogcXVlIGxvcyBjbMO6c3RlcnMgYWxjYW5jZW4gdW4gdGFtYcOxbyBpbmZlcmlvciBhIHVuIHBhcsOhbWV0cm8gZGFkbyBvIHNlIGxvZ3JlIGVsIG7Dum1lcm8gZGUgY2zDunN0ZXJzIGRlc2VhZG8gWzJdLiAgDQoNCiogRWwgYWxnb3JpdG1vIEJpcmNoIGN1ZW50YSBjb24gZG9zIHByaW5jaXBhbGVzIHBhcsOhbWV0cm9zDQogIA0KICAtIEJyYW5jaGluZyBmYWN0b3IgKEIpOiBDYW50aWRhZCBtw6F4aW1hIGRlIHN1YmNsw7pzdGVycyBlbiBjYWRhIG5vZG8sIGVuIGNhc28gZGUgICAgICAgICAgIHN1cGVyYXIgZXN0ZSBmYWN0b3IgZWwgbm9kbyBzZSBkaXZpZGUgZW4gZG9zIG5vZG9zIGNvbiBsb3Mgc3ViY2zDunN0ZXJzICAgICAgICAgICAgICAgICAgcmVkaXN0cmlidWlkb3MuIA0KICANCiAgLQlUaHJlc2hvbGQgKFQpOiBFcyBlbCB1bWJyYWwgZXN0YWJsZWNpZG8gcGFyYSBsYSBjcmVhY2nDs24gZGUgdW4gbnVldm8gc3ViY2zDunN0ZXIuIEVzdGFibGVjZXIgZXN0ZSB2YWxvciBiYWpvIGdlbmVyYSBsYSBjcmVhY2nDs24gZGUgbcOhcyBjbMO6c3RlcnMuDQoNCiAgDQoqKkVqZW1wbG8gZGVsIEFsZ29yaXRtbyoqDQoNCkNvbnNpZGVyYW5kbyBsb3Mgc2lndWllbnRlcyB2YWxvcmVzOg0KDQoNCiAgfCBOCXwgUHVudG9zIHwNCiAgfDotLTp8Oi0tLS06fA0KICB8IDEJfCAyMiB8DQogIHwgMgl8IDkgfA0KICB8IDMJfCAxMiB8DQogIHwgNAl8IDE1IHwNCg0KDQoqKlBhc28gMToqKiBTZSBlbGlnZSBlbCBUcmVzaG9sZCAoNSkuDQoNCioqUGFzbyAyOioqIFNlIGVsaWdlIGVsIEJyYW5jaGluZyBmYWN0b3IgKDMpLg0KDQoqKlBhc28gMzoqKiBTZSB0b21hIGVsIHByaW1lciB2YWxvciB5IHNlIGVsZXZhIGFsIGN1YWRyYWRvLg0KDQoqKlBhc28gNDoqKiBTZSBlbGV2YSBhbCBjdWFkcmFkbyBhbWJvcyB2YWxvcmVzIHkgc2Ugc3VtYS4NCg0KDQogIHxWYWxvcmVzIHwJTgl8IFN1bWEJfCBDdWFkcmFkbw0KICB8Oi0tOnw6LS06fDotLTp8Oi0tOnwNCiAgfDIyCXwxCXwyMnwJNDg0fA0KICB8OQl8Mgl8MzF8CTU2NXwNCg0KDQoqKlBhc28gNToqKiBFbCBjZW50cm9pZGUgZXMgaWd1YWwgYSBsYSBzdW1hIGRlIGxvcyB2YWxvcmVzIHNvYnJlIE4sIHBhcmEgZWwgcHJpbWVyIHZhbG9yIGVsIGNlbnRyb2lkZSBzZXLDrWE6DQogIA0KICAkQ2VudHJvaWRlXzAgPSAyMi8xID0gMjIkDQogIA0KICANCioqUGFzbyA2OioqIFNlIGFwbGljYSBsYSBmw7NybXVsYQ0KICANCiREPVxzcXJ0e1xmcmFje1xzdW1fe2k9MX1eblxzdW1fe2o9MX1ebih4X2kteF9qKV4yfXtuKG4tMSl9fSQNCg0KJEQ9XHNxcnR7XGZyYWN7Mm4oU1MpLTIoTFMpXjJ9e24obi0xKX19JA0KDQokRD1cc3FydHtcZnJhY3syKjIoNTY1KS0yKDMxKV4yfXsyKDItMSl9fSQNCiAgDQpFbnRvbmNlcyAkRD0xMyQsIHF1ZSBlcyBzdXBlcmlvciBhbCB0cmVzaG9sZCwgcG9yIGxvIHRhbnRvLCBzZSBjcmVhIHVuIG51ZXZvIGNsw7pzdGVyLg0KICANCiAgDQoqKlBhc28gNzoqKiBUcmFiYWphbmRvIGVsIHNpZ3VpZW50ZSB2YWxvcjogMTINCiAgDQogICRDZW50cm9pZGVfMCA9IDIyLzEgPSAyMiQNCiAgDQogICRDZW50cm9pZGVfMSA9IDkvMSA9IDkkDQogIA0KICBFbCBwdW50byAxMiBlc3RhIG3DoXMgY2VyY2EgYSA5Lg0KICANCiAgfFZhbG9yZXMgfAlOCXwgU3VtYQl8IEN1YWRyYWRvDQogIHw6LS06fDotLTp8Oi0tOnw6LS06fA0KICB8IDkgfCAxCXwgOQl8IDgxfA0KICB8IDEyCXwgMgl8MjEJfCAyMjV8DQogDQogICREPVxzcXJ0e1xmcmFjezIqMigyMjUpLTIoMjEpXjJ9ezIoMi0xKX19JA0KICANCiAgJEQ9MyQNCiAgDQogIENvbW8gJEQ9Mzw1JCwgc2UganVudGFuIGxvcyBwdW50b3MgOSB5IDEyLg0KICANCg0KKipQYXNvIDg6KiogVHJhYmFqYW5kbyBlbCBzaWd1aWVudGUgdmFsb3I6IDE1DQogIA0KICAkQ2VudHJvaWRlXzIgPSAoOSsxMikvMiA9IDEwLjUkDQogIA0KICAkQ2VudHJvaWRlXzAgPSAoMjIpLzEgPSAyMiQNCiAgDQogIEVsIHB1bnRvIDEyIGVzdGEgbcOhcyBjZXJjYSBhbCAkQ2VudHJvaWRlXzI9MTAuNSQuDQogIA0KICB8VmFsb3JlcyB8CU4JfCBTdW1hCXwgQ3VhZHJhZG8NCiAgfDotLTp8Oi0tOnw6LS06fDotLTp8DQogIHwgOSwgMTIJfCAyCXwyMQl8IDIyNXwNCiAgfCA5LCAxMiwgMTUJfCAzCXwgMzYJfCA0NTB8DQogDQogICREPVxzcXJ0e1xmcmFjezIqMyg0NTApLTIoMzYpXjJ9ezIoMi0xKX19JA0KICANCiAgJEQ9NC4yNCQNCiAgDQogIENvbW8gJEQ9NC4yNDw1JCwgc2UganVudGFuIGxvcyBwdW50b3MgOSwgMTIgeSAxNS4NCiAgDQogICRDZW50cm9pZGVfMyA9ICg5KzEyKzE1KS8zID0gMTIkDQogIA0KICAkQ2VudHJvaWRlXzAgPSAoMjIpLzEgPSAyMiQNCiAgICANCioqRWplbXBsbyBlbiBQeXRob24qKg0KDQpgYGB7cn0NCiNDb25maWd1cmFjacOzbiBkZWwgZW50b3Jubw0Kcm0obGlzdCA9IGxzKCkpDQpzZXR3ZChkaXJuYW1lKHJzdHVkaW9hcGk6OmdldEFjdGl2ZURvY3VtZW50Q29udGV4dCgpJHBhdGgpKQ0KZ3JhcGhpY3Mub2ZmKCkNCm9wdGlvbnMoc2NpcGVuID0gOTk5KSAgDQpvcHRpb25zKGRpZ2l0cyA9IDMpIA0KcGF0aF9weXRob249IkM6L1VzZXJzL21zYXJzb3phYS9BbmFjb25kYTMiDQojQ2FyZ2FuZG8gcGFxdWV0ZXMgbmVjZXNhcmlvcw0KbGlicmFyeShwYWNtYW4pDQpwX2xvYWQocmV0aWN1bGF0ZSwgUGVyZm9ybWFuY2VBbmFseXRpY3MsIHB1cnJyLCBza2ltciwgY29ycnBsb3QsIGNsdXN0ZXIsIHBzeWNoLCBnZ3Bsb3QyLCANCiAgICAgICBzdHJlYW0sIGVsbGlwc2UsIHRpY3RvYyxmYWN0b2V4dHJhLCBOYkNsdXN0LCBCaW9jTWFuYWdlciwgbmFuaWFyLCBEYXRhRXhwbG9yZXIsIA0KICAgICAgIHRpZHl2ZXJzZSwgcHVycnIsIGRwbHlyLCByZWFkeGwsIHJlYWRyLHN0YXRzLCBEZXNjVG9vbHMsIGNsYXNzLGRldnRvb2xzLGltYWdlcixrbml0cixrYWJsZUV4dHJhKSNzb3VyY2UsY29tcGFyZUdyb3VwcyANCiNDYXJnYW5kbyBmdW5jaW9uZXMgZGUgdXN1YXJpbw0Kc291cmNlKCJmdW5jaW9uZXMuUiIpDQpgYGANCg0KYGBge3IgfQ0KbGlicmFyeShyZXRpY3VsYXRlKQ0KdXNlX3B5dGhvbihwYXRoX3B5dGhvbikNCmBgYA0KDQpgYGB7cHl0aG9ufQ0KaW1wb3J0IHBhbmRhcyBhcyBwZA0KZnJvbSBza2xlYXJuLmNsdXN0ZXIgaW1wb3J0IEJpcmNoDQpkZl9lamVtcGxvID0gcGQuRGF0YUZyYW1lKFsyMiw5LDEyLDE1XSxjb2x1bW5zPVsnUHVudG9zJ10pDQoNCmJyYzAgPSBCaXJjaCh0aHJlc2hvbGQ9NSwNCiAgICAgICAgICAgIGJyYW5jaGluZ19mYWN0b3I9Mw0KICAgICAgICAgICAgKQ0KYnJjMC5maXQoZGZfZWplbXBsbykNCg0KcGQuY29uY2F0KFtkZl9lamVtcGxvLHBkLkRhdGFGcmFtZShicmMwLnByZWRpY3QoZGZfZWplbXBsbyksY29sdW1ucz1bJ0NsdXN0ZXInXSldLGF4aXM9MSkNCg0KYGBgDQogDQoNCg0KDQojICAqKkxlY3R1cmEgZGUgZGF0b3MgKioNCg0KYGBge3J9DQojIENhcmdhbW9zIGxhIGRhdGENCmF5YWN1Y2hvIDwtIHJlYWQuY3N2KCJEYXRhLmNzdiIpDQpoZWFkXzU9aGVhZChheWFjdWNobykNCmthYmxlKGhlYWRfNSxjYXB0aW9uID0gIkF5YWN1Y2hvIikgJT4lIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiKSAlPiUgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIikNCmBgYA0KDQpgYGB7cn0NCnN0cihheWFjdWNobykNCiNza2ltKGF5YWN1Y2hvKQ0KI2Rlc2NyaWJlKGF5YWN1Y2hvKQ0KYGBgDQoNCiMgKipQcmVwcm9jZXNhbWllbnRvIGRlIGRhdG9zKioNCg0KYGBge3J9DQojIEZhY3Rvcml6YWNpw7NuIGRlIHZhcmlhYmxlcyBubyBuw7ptZXJpY2FzDQp2YXJpYWJsZXNfZmFjIDwtIGMoIkRpcmVzYSIsICJNaWNyb3JlZCIsICJFRVNTIiwgIkRwdG9fRUVTUyIsICJQcm92X0VFU1MiLCAiRGlzdF9FRVNTIiwgIlJlbmlwcmVzcyIsICJTZXhvIiwgIkp1bnRvcyIsICJTSVMiLCAiUWFsaXdhcm1hIiwgIkNyZWQiLCAiU3VwbGVtZW50YWNpb24iLCAiQ29uc2VqZXJpYSIsICJTZXNpb24iKQ0KYXlhY3VjaG9bLHZhcmlhYmxlc19mYWNdIDwtIGxhcHBseShheWFjdWNob1ssdmFyaWFibGVzX2ZhY10sIGZhY3RvcikNCiNzdHIoYXlhY3VjaG8pIGNhbWJpbw0KYGBgDQoNCg0KIyMgRGF0b3MgUGVyZGlkb3MNCg0KYGBge3J9DQpheWEgPC1heWFjdWNob1ssMTQ6MjNdDQpwbG90X21pc3NpbmcoYXlhKQ0KYGBgDQoNCl9FbCBhbsOhbGlzaXMgbXVlc3RyYSBxdWUgbGFzIHZhcmlhYmxlcyBzZWxlY2Npb25hZGFzIG5vIHByZXNlbnRhbiB2YWxvcmVzIHBlcmRpZG9zIG8gZmFsdGFudGVzLCBwb3IgbG8gdGFudG8sIG5vIHNlIHJlcXVpZXJlIHJlYWxpemFyIHVuIHByb2NlZGltaWVudG8gZGUgaW1wdXRhY2nDs24uXw0KDQojIyBBbmFsaXNpcyBkZSBPdXRsaWVycw0KDQpgYGB7cn0NCmdnX2JveF9kZW5zaXR5KGF5YSkNCmBgYA0KDQpfQSBwYXJ0aXIgZGUgbG9zIGdyw6FmaWNvcyBhbnRlcmlvcmVzIHNlIG9ic2VydmEgcXVlIHRvZGFzIGxhcyB2YXJpYWJsZXMgYSBleGNlcGNpw7NuIGRlIEVkYWRNZXNlcyBwb3NlZW4gdmFsb3JlcyBvdXRsaWVycyB5IGV4dHJlbW9zIGVuIGFsZ3Vub3MgY2Fzb3MsIGNvbW8gbGFzIHZhcmlhYmxlcyBoZW1vZ2xvYmluYSB5IEFsdHVyYVJlbiB5IEhlbW9nbG9iaW5hYWp1c3RhZGEuXw0KDQoNCiMjIFRyYXRhbWllbnRvIGRlIGRhdG9zIE91dGxpZXJzDQoNCmBgYHtyfQ0KY29scz1jKCdQZXNvJywnVGFsbGEnLCdJTUMnLCdQVFonLCdaVEUnLCdaUEUnLCdIZW1vZ2xvYmluYScsJ0FsdHVyYVJFTicsJ0hlbW9nbG9iaW5hYWp1c3RhZGEnKQ0KYXlhPWZuX291dGxpZXJzKGF5YSxjb2xzLDIsMS41KQ0KYGBgDQoNCmBgYHtyfQ0KZ2dfYm94X2RlbnNpdHlfMihheWEpDQpgYGANCg0KX0VsIHByb2NlZGltaWVudG8gcGFyYSB0cmF0YXIgbG9zIHZhbG9yZXMgb3V0bGllcnMgeSBleHRyZW1vcyBjb25zaXN0acOzIGVuIGFjb3RhciBsb3MgdmFsb3JlcyBzdXBlcmlvcmVzIHkgaW5mZXJpb3JlcyBkZSBsYXMgdmFyaWFibGVzIG1lZGlhbnRlIGVsIHByaW5jaXBpbyBkZSAxLjUgdmVjZXMgZWwgcmFuZ28gaW50ZXJjdWFydMOtbGljbywgc3VtYW5kbyBlc3RlIHZhbG9yIGFsIHBlcmNlbnRpbCA3NSBwYXJhIGRlZmluaXIgZWwgdmFsb3Igc3VwZXJpb3IgZGUgbGEgdmFyaWFibGUgeSByZXN0YW5kbyBlbCB2YWxvciBpbmRpY2FkbyBhbCBwZXJjZW50aWwgMjUgcGFyYSBkZWZpbmlyIGVsIHZhbG9yIGluZmVyaW9yIG3DoXhpbW8gYWNlcHRhZG8uXw0KDQpfTG9zIHZhbG9yZXMgcXVlIGZ1ZXJvbiBzdXBlcmlvcmVzIG8gaW5mZXJpb3JlcyBhIGxvcyB2YWxvcmVzIGVuIGVsIHJhbmdvIGRlZmluaWRvcyBmdWVyb24gcmVlbXBsYXphZG9zIHBvciBlbCBtw6F4aW1vIHkgbcOtbmltbyBjYWxjdWxhZG8gZGUgZGljaG8gcmFuZ28uXw0KDQojIyBDb3JyZWxhY2nDs24NCg0KYGBge3J9DQojY29yIDwtIGF5YWN1Y2hvWywxNDoyM10lPiUgY2hhcnQuQ29ycmVsYXRpb24oaGlzdG9ncmFtPVRSVUUsIHBjaD0xNSkgDQpjb3JyZWxhY2lvbjwtcm91bmQoY29yKGF5YWN1Y2hvWywxNDoyM10pLCAyKQ0KY29ycnBsb3QoY29ycmVsYWNpb24sIG1ldGhvZD0ibnVtYmVyIiwgdHlwZT0idXBwZXIiLG51bWJlci5jZXggPSAwLjcyLHRsLmNleCA9IDAuOCxhZGRDb2VmLmNvbCA9IDAuNSkNCmBgYA0KDQpfRGUgYWN1ZXJkbyBjb24gZWwgYW7DoWxpc2lzIGRlIGNvcnJlbGFjacOzbiBkZSBQZWFyc29uIHNlIGludGVycHJldGEgcXVlIG5vIGV4aXN0ZW4gcmVsYWNpw7NuIGxpbmVhbCBuZWdhdGl2YSBlbnRyZSBsYXMgdmFyaWFibGVzLl8NCg0KX0V4aXN0ZSB1bmEgY29ycmVsYWNpw7NuIGxpbmVhbCBwb3NpdGl2YSBmdWVydGUgZW50cmUgbGFzIHZhcmlhYmxlczpfDQoNCl9FZGFkTWVzZXMgeSBUYWxsYTogMC44Nl8NCl9QZXNvIHkgVGFsbGE6IDAuODRfDQpfUGVzbyBlIElNQzogMC45Ml8NCl9JTUMgeSBQVFo6IDAuODJfDQpfSU1DIHkgWlBFOiAwLjc4Xw0KX1BUWiB5IFpQRTogMC44NF8NCl9IZW1vZ2xvYmluYSB5IEhlbW9nbG9iaW5hYWp1c3RhZGE6IDAuODRfDQoNCl9FeGlzdGUgdW5hIGNvcnJlbGFjacOzbiBsaW5lYWwgcG9zaXRpdmEgbW9kZXJhZGEgZW50cmUgbGFzIHZhcmlhYmxlczpfDQpfRWRhZE1lc2VzIHkgUGVzbzogMC42OV8NCl9FZGFkTWVzZXMgeSBJTUM6IDAuNDRfDQpfUGVzbyB5IFBUWjogMC41NV8NCl9QZXNvIHkgWlRFOiAwLjQyXw0KX1Blc28geSBaUEU6IDAuNjFfDQpfVGFsbGEgeSBaUEU6IDAuNDJfDQpfSGVtb2dsb2JpbmEgeSBBbHR1cmFSZW46IDAuNDRfDQoNCg0KDQo8IS0tIEJhY2t1cC0tPg0KYGBge3IgaW5jbHVkZT1GQUxTRX0gDQpheWFfYmtwPC0gYXlhIA0KYGBgDQoNCg0KIyMgRXN0YW5kYXJpemFjacOzbiBkZSBsb3MgZGF0b3MNCg0KYGBge3J9DQpheWEgPC0gYXMuZGF0YS5mcmFtZShzY2FsZShheWEpKQ0KYGBgDQoNCg0KIyAqKkRldGVybWluYW5kbyBmYWN0aWJpbGlkYWQgZGVsIGNsdXN0ZXIqKg0KDQojIyBNYXRyaXogZGUgZGlzdGFuY2lhIGV1Y2xpZGlhbmENCg0KYGBge3J9DQpkaXMuRGF0YSA8LSBkaXN0KGF5YSwgbWV0cmljICA9IGMoImV1Y2xpZGVhbiIpKSAjIE1hdHJpeiBkZSBkaXN0YW5jaWEgI2NhbWJpbw0KI2Rpcy5EYXRhICAgIDwtIGRhaXN5KGF5YSwgbWV0cmljPSAiZXVjbGlkZWFuIixzdGFuZCA9IFRSVUUpICMgTWF0cml6IGRlIGRpc3RhbmNpYQ0KYGBgDQoNCiMjIFZpc3VhbGl6YW5kbyBsYSBtYXRyaXogZGUgZGlzdGFuY2lhIGNvbiBmdml6X2Rpc3QoKQ0KDQpgYGB7cn0NCiBsaWJyYXJ5KGltYWdlcikNCiBpZiAgKGZpbGUuZXhpc3RzKCJncmFmX21heC5kaXN0LnBuZyIpKSB7ICAgICAgI0NhbWJpbyAgDQogICAgIGltZyA8LSBsb2FkLmltYWdlKCdncmFmX21heC5kaXN0LnBuZycpDQogICAgIHBsb3QoaW1nKQ0KICB9DQojZWxzZXsNCiAgIyBmdml6X2Rpc3QoZGlzLkRhdGEpDQogIyB9DQpgYGANCg0KX1NlIHJlYWxpesOzIGVsIGPDoWxjdWxvIHkgdmlzdWFsaXphY2nDs24gbGEgbWF0cml6IGRlIGRpc3RhbmNpYSBldWNsaWRpYW5hIHV0aWxpemFuZG8gbGFzIGZ1bmNpb25lcyBmdml6X2Rpc3QgKCkgZW4gZWwgcGFxdWV0ZSBmYWN0b2V4dHJhIHIuXw0KX1NlIG9ic2VydmFuIMOhcmVhcyBzb21icmVhZGFzIGRlIGNvbG9yIHJvam8gcXVlIGluZGljYW4gbGEgY2VyY2Fuw61hIGRlIGxhcyBvYnNlcnZhY2lvbmVzIGVuIGJhc2UgYWwgY8OhbGN1bG8gZGUgZGlzdGFuY2lhIGluZGljYWRvLiBFc3RvIHBlcm1pdGUgaW5mZXJpciBxdWUgZWwgc2V0IGRlIGRhdG9zIGVzIGFncnVwYWJsZS5fDQoNCg0KIyMgRXN0YWTDrXN0aWNvIEhvcGtpbnMNCg0KYGBge3J9DQpyZV9wcm9jZXNzPUYNCmlmICAoZmlsZS5leGlzdHMoInJlcy5Ib3BraW5zLlJEYXRhIikgJiAhcmVfcHJvY2VzcykgeyAgICAgICNDYW1iaW8gIA0KICAgICBsb2FkKCdyZXMuSG9wa2lucy5SRGF0YScpIA0KfWVsc2V7DQogIHJlcy5Ib3BraW5zIDwtIGdldF9jbHVzdF90ZW5kZW5jeShheWEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gbnJvdyhheWEpIC0gMSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGdyYXBoID0gRkFMU0UsIHNlZWQgPSAyMDIyKQ0KICBzYXZlKHJlcy5Ib3BraW5zLGZpbGU9J3Jlcy5Ib3BraW5zLlJEYXRhJykNCn0NCnJlcy5Ib3BraW5zJGhvcGtpbnNfc3RhdA0KYGBgDQoNCl9TZSByZWFsaXrDsyBsYSBwcnVlYmEgZXN0YWTDrXN0aWNhIGRlIEhvcGtpbnMsIHV0aWxpemFuZG8gMCw1IGNvbW8gZWwgdW1icmFsIHBhcmEgZGV0ZXJtaW5hciBxdWUgZXMgcG9jbyBwcm9iYWJsZSBxdWUgZWwgc2V0IGRlIGRhdG9zIHRlbmdhIGNvbmdsb21lcmFkb3MgZXN0YWTDrXN0aWNhbWVudGUgc2lnbmlmaWNhdGl2b3MuIE9ic2VydmFtb3MgcXVlIGVsIHZhbG9yIHJlc3VsdGFudGUgZGUgMC44NTMgc2UgYWNlcmNhIGEgMSwgZW50b25jZXMgcG9kZW1vcyBjb25jbHVpciBxdWUgZWwgY29uanVudG8gZGUgZGF0b3MgZXMgc2lnbmlmaWNhdGl2YW1lbnRlIGFncnVwYWJsZS5fDQoNCiMgKipBbsOhbGlzaXMgY2x1c3RlciBqZXJhcnF1aWNvKiogKGhjbHVzdCB7c3RhdHN9KQ0KDQojIyBBbmFsaXphbmRvIGVsIG1ldG9kbyBkZSBlbmxhY2Ugb3B0aW1vIChjb2VmaWNpZW50ZSBkZSBhZ2xvbWVyYWNpw7NuKQ0KIA0KYGBge3J9DQptZXRvZG9zPSBjKCJzaW5nbGUiLCAiY29tcGxldGUiLCAiYXZlcmFnZSIsICJ3YXJkLkQiLCAid2FyZC5EMiIpDQpDT0VGLkFHTChkaXMuRGF0YSxtZXRvZG9zKQ0KYGBgDQoNCl9IYWNlbW9zIHVzbyBkZSBsYSBjb21wYXJhY2nDs24gZGUgbG9zIGNvZWZpY2llbnRlcyBkZSBhZ2xvbWVyYWNpw7NuIHBhcmEgZGV0ZXJtaW5hciBlbCBuw7ptZXJvIGRlIGNvbmdsb21lcmFkb3MgeSBwcm9wb25lciBlbCBtZWpvciBlc3F1ZW1hIGRlIGFncnVwYWNpw7NuIGEgcGFydGlyIGRlIGxvcyBkaWZlcmVudGVzIHJlc3VsdGFkb3Mgb2J0ZW5pZG9zIGFsIHZhcmlhciB0b2RhcyBsYXMgY29tYmluYWNpb25lcyBkZSBtw6l0b2RvcyBkZSBlbmxhY2U6ICJzaW5nbGUiLCAiY29tcGxldGUiLCAiYXZlcmFnZSIsICJ3YXJkLkQiLCAid2FyZC5EMiIuIENvbiBiYXNlIGVuIGVsIHJlc3VsdGFkbyBlbiBsYSBncsOhZmljYSBzZSBwdWVkZSBpbmRpY2FyIHF1ZSBlbCBlbmxhY2Ugb3B0aW1vIGVzIGVsIGRlOiBXYXJkLkRfDQoNCiMjIEFuYWxpemFuZG8gZWwgbWV0b2RvIGRlIGVubGFjZSBvcHRpbW8gKG1hdHJpeiBjb2ZlbmV0aWNhKQ0KDQpgYGB7cn0NCiNEZWJpZG8gYSBxdWUgZ3JhZmljYW1lbnRlIG5vIGVzdGFuIG11eSBhbGVqYWRvcyBjb24gY29lZmljaWVudGVzIGRlIGFnbG9tZXJhY2lvbiwgcHJvYmFyZW1vcyBlbCBtZXRvZG8gZGUNCiNjb3JyZWxhY2lvbiBjb2ZlbmV0aWNhIHBhcmEgZGVmaW5pciBlbCBtZWpvciBtZXRvZG8gZGUgZW5sYWNlDQpyZV9wcm9jZXNzPUZBTFNFDQppZihmaWxlLmV4aXN0cygiZGZfY29mLlJEYXRhIikgJiAhcmVfcHJvY2VzcykgeyAgICAgICNDYW1iaW8gIA0KICAgICBsb2FkKCdkZl9jb2YuUkRhdGEnKSANCn1lbHNlew0KICBtZXRvZG9zPWMoIndhcmQuRCIsICJ3YXJkLkQyIiwiY29tcGxldGUiLCAiYXZlcmFnZSIsICJzaW5nbGUiKQ0KICBkZl9jb2Y9Zm5fY29uZmVuZXRpY28oZGlzLkRhdGEsbWV0b2RvcykNCiAgc2F2ZShkZl9jb2YsZmlsZT0nZGZfY29mLlJEYXRhJykNCn0NCmthYmxlKGRmX2NvZixjYXB0aW9uID0gIkNvcnJlbGFjaW9uX2NvZmVuZXRpY2EiKSU+JSBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIikNCmBgYA0KDQpfUmVhbGl6YW1vcyBlbCBhbsOhbGlzaXMgZGUgbGEgbWF0cml6IGNvZmVuZXRpY2EgcGFyYSBkZXRlcm1pbmFyIGxhIHNpbWlsYXJpZGFkIGVudHJlIGxhIG1hdHJpeiBkZSBkaXN0YW5jaWEgb3JpZ2luYWwgKGV1Y2xpZGlhbmEpIHkgbGEgbWF0cml6IGRlIGxhcyB1bmlvbmVzIGRlIGxhcyBvYnNlcnZhY2lvbmVzIGVuIGxvcyBjbHVzdGVyIGplcsOhcnF1aWNvcyBlZmVjdHVhZG9zIGNvbiBsb3MgbcOpdG9kb3MgZGUgZW5sYWNlcyBpbmljaWFsZXMuIENvbW8gcmVzdWx0YWRvIHNlIG9ic2VydmEgcXVlIG5vIGhheSB1bmEgY29ycmVsYWNpw7NuIGZ1ZXJ0ZSBwZXJvIGVsIG3DqXRvZG8gYXZlcmFnZSBwcmVzZW50YSBsYSBtYXlvciBjb3JyZWxhY2nDs24uIFNlIGRlY2lkZSBldmFsdWFyIGVsIGFuw6FsaXNpcyBkZSBjbHVzdGVyIGplcsOhcnF1aWNvIGNvbiBhbWJvcyBtw6l0b2RvczogV2FyZC5EIHkgYXZlcmFnZS5fDQoNCiMjIE9idGVuaWVuZG8gZWwgY2x1c3RlciBjb24gZWwgbWV0b2RvOiAqKldhcmQuRCoqDQoNCmBgYHtyfQ0KQ2x1c19BR19XIDwtIGhjbHVzdChkaXMuRGF0YSxtZXRob2Q9IndhcmQuRCIpDQpgYGANCg0KIyMgRGV0ZXJtaW5hbmRvIGRlIG1hbmVyYSBncmFmaWNhIGVsIG51bWVybyBkZSBjbHVzdGVycw0KDQoNCg0KYGBge3J9DQpsaWJyYXJ5KGdnaGlnaGxpZ2h0KQ0KbG9uZ2l0dWQ9bGVuZ3RoKENsdXNfQUdfVyRoZWlnaHQpDQphbHR1cmFzIDwtIGRhdGEuZnJhbWUoZXRhcGEgPSAxOmxvbmdpdHVkLCBkaXN0YW5jaWEgPSBDbHVzX0FHX1ckaGVpZ2h0KQ0KZ2dwbG90KGFsdHVyYXMpICsgYWVzKHggPSBldGFwYSwgeSA9IGRpc3RhbmNpYSkgICsNCiAgZ2VvbV9wb2ludCgpICsgZ2VvbV9saW5lKCkgICsgDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwgbG9uZ2l0dWQsIDEwMDApKSArIA0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA3NTk3LCBjb2wgPSAicmVkIiwgbHR5ID0gMikgKyANCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCAgPSByb3VuZChkaXN0YW5jaWEsMSkpLA0KICAgICAgICAgICAgc2l6ZSA9IDMsIGhqdXN0PSArMSwgdmp1c3Q9IC0xKSArDQogIHRoZW1lX2NsYXNzaWMoKSAjKyBnZ2hpZ2hsaWdodChkaXN0YW5jaWEgPiAxMSkNCmBgYA0KPCEtLSBWaXN1YWxtZW50ZSBkZXRlcm1pbmFtb3MgcXVlIHBvZHJpYW1vcyB0cmFiYWphciBjb24gMDIgbyAwMyBjbHVzdGVycy0tPg0KDQpfRWwgbcOpdG9kbyBncmFmaWNvIG5vcyBzdWdpZXJlIGxhIGV4aXN0ZW5jaWEgZGUgMiBhIDUgYWdydXBhY2lvbmVzLl8NCg0KDQojIyBEZXRlcm1pbmFkbyBlbCBuw7ptZXJvIGRlIGNsdXN0ZXJzIG9wdGltbw0KDQpgYGB7cn0gDQpyZV9wcm9jZXNzPUYNCmlmICAoZmlsZS5leGlzdHMoInJlcy5uYmNsdXN0Vy5SRGF0YSIpJiAhcmVfcHJvY2VzcykgeyAgICAgICNDYW1iaW8gIA0KICAgICBsb2FkKCdyZXMubmJjbHVzdFcuUkRhdGEnKSANCn1lbHNlew0KICBzZWVkID0gMjAyMg0KICByZXMubmJjbHVzdFcgPC0gTmJDbHVzdChheWEsIGRpc3RhbmNlID0iZXVjbGlkZWFuIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgbWluLm5jID0gMiwgbWF4Lm5jID0gOCwgbWV0aG9kID0gIndhcmQuRCIsIGluZGV4ID0iYWxsIikgI0NhbWJpbw0KICBzYXZlKHJlcy5uYmNsdXN0VyxmaWxlPSdyZXMubmJjbHVzdFcuUkRhdGEnKQ0KfQ0KcGFyKG1mcm93PWMoMSwxKSkgIA0KZnZpel9uYmNsdXN0X3gocmVzLm5iY2x1c3RXKQ0KYGBgDQoNCl9NZWRpYW50ZSBlbCB1c28gZGUgcGFxdWV0ZSBOYkNsdXN0IHBvZGVtb3MgZGV0ZXJtaW5hciBlbCBtZWpvciBuw7ptZXJvIGRlIGFncnVwYWNpb25lcyB0b21hbmRvIGVuIGN1ZW50YSBsb3MgZGlmZXJlbnRlcyByZXN1bHRhZG9zIG9idGVuaWRvcyBhbCB2YXJpYXIgdG9kYXMgbGFzIGNvbWJpbmFjaW9uZXMgbGEgY2FudGlkYWQgZGUgY2zDunN0ZXIgZGVzZWFkb3MgcGFyYSBlbCBtw6l0b2RvIGRlIGVubGFjZSBlbGVnaWRvOiBXYXJkLkQuXw0KDQojIyBSZWFsaXphbmRvIGNsdXN0ZXJpbmcgY29uIEs9Mw0KICANCmBgYHtyfQ0KIyBDb3J0YW5kbyBlbiAyIGNsdWVzdGVyDQpncnBfV1I9Y3V0cmVlKENsdXNfQUdfVywgayA9IDMpDQojIE51bWJlciBkZSBjYXNvcyBlbiBjYWRhIGNsdXN0ZXINCnRhYmxlKGdycF9XUikNCiMgRGVzY3JpcGNpw7NuIGRlIGNhZGEgY2x1c3Rlcg0KbWVkPC1hZ2dyZWdhdGUoYXlhX2JrcCwgYnk9bGlzdChjbHVzdGVyPWdycF9XUiksIG1lYW4pICNtZWRpYXMNCmthYmxlKG1lZCkgJT4lIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiKSAlPiUgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIikNCiNrbml0cjo6a2FibGUobWVkLCBmb3JtYXQgPSAibWFya2Rvd24iKQ0KYGBgDQoNCl9TZSBwcm9jZWRpw7MgYSBjb3J0YXIgbGEgYWdydXBhY2nDs24gZ2VuZXJhbCAoZGVuZG9ncmFtYSkgZW4gMDMgY2zDunN0ZXJzIHRvbWFuZG8gY29tbyBiYXNlIGVsIHJlc3VsdGFkbyDDs3B0aW1vIGRlIE5iY2x1c3QgeSBvYnRlbmVtb3MgbGEgY2FudGlkYWQgZGUgb2JzZXJ2YWNpb25lcyBwb3IgY2FkYSB1bm8gZGUgZWxsb3MuXw0KDQoNCiMjIENhcmFjdGVyw616YW5kbyBsb3MgY2x1c3Rlcg0KDQojIyMgR3JhZmljYW5kbyBsb3MgY2x1c3RlcnMuDQoNCmBgYHtyfQ0KI2Z2aXpfZGVuZChDbHVzX0FHLCBrID0gMiwgY2V4ID0gMC43LCBob3JpeiA9IEZBTFNFLCBrX2NvbG9ycyA9ICJqY28iLA0KICAgICAgICAgICNyZWN0ID0gVFJVRSwgcmVjdF9ib3JkZXIgPSAiamNvIiwgcmVjdF9maWxsID0gVFJVRSkNCmBgYA0KDQojIyMgRGlhZ3JhbWEgZGUgY2FyYWN0ZXJpemFjacOzbiAtIGxpbmVhcw0KDQpgYGB7cn0NCmRhdGFfcGxvdD1zY2FsZShheWFfYmtwKSAjU2NhbGUNCiNkYXRhX3Bsb3Q9YXBwbHkoYXlhX2JrcCwgMiwgbm9ybWFsaXplKSAjTWF4LU1pbg0KTTwtYXMuZGF0YS5mcmFtZSh0KHJiaW5kKGFnZ3JlZ2F0ZShkYXRhX3Bsb3QsIGJ5PWxpc3QoY2x1c3Rlcj1ncnBfV1IpLCBtZWFuKVssLTFdKSkpDQphPWFzLnZlY3Rvcihjb2xNZWFucyhkYXRhX3Bsb3QpKQ0KZmluPWRhdGEuZnJhbWUoTSxhLG5hbWVzKGF5YV9ia3ApKTtuYW1lcyhmaW4pPC1jKCJDbHVzMSIsImNsdXMyIiwiY2x1czMiLCJNZWRpYSIsInZhciIpDQojZmluPWRhdGEuZnJhbWUoTSxhLG5hbWVzKGF5YV9ia3ApKTtuYW1lcyhmaW4pPC1jKCJDbHVzMSIsImNsdXMyIiwiTWVkaWEiLCJ2YXIiKQ0KYWxpPW1lbHQoZmluLGlkLnZhcnMgPSAidmFyIikNCmdncGxvdChhbGksIGFlcyh4PXZhcix5PXJvdW5kKHZhbHVlLDEpLGdyb3VwPXZhcmlhYmxlLGNvbG91cj12YXJpYWJsZSkpICsNCiAgZ2VvbV9wb2ludCgpKyBnZW9tX2xpbmUoYWVzKGx0eT12YXJpYWJsZSkpKyBleHBhbmRfbGltaXRzKHkgPSBjKC0xLjksIDEuOSkpKw0KICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA2MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQ0KDQoNCmBgYA0KDQoNCl9TZSBjYWxjdWxhIGxvcyBwcm9tZWRpb3MgZGUgbGFzIG9ic2VydmFjaW9uZXMgY29uIGxhIGJhc2UgZGUgZGF0b3Mgb3JpZ2luYWwgYWdydXBhZG8gcG9yIGNhZGEgY2zDunN0ZXIuXw0KX1NlIG9ic2VydmEgcXVlIGVsIGNsw7pzdGVyIDAxIHRpZW5lIG1heW9yIGNhbnRpZGFkIGRlIGluZGl2aWR1b3MgeSBlc3RvcyBzZSBjYXJhY3Rlcml6YW4gcG9yIHRlbmVyIG1heW9yIHBlc28sIHRhbGxhLCBlbCBtw61uaW1vIHJldHJhc28gZW4gc3UgY3JlY2ltaWVudG8sIHVuIMOtbmRpY2UgbnV0cmljaW9uYWwgcG9zaXRpdm8sIG1heW9yIMOtbmRpY2UgZGUgbWFzYSBjb3Jwb3JhbCB5IHZpdmVuIGEgdW5hIGFsdHVyYSBtZW5vci5fDQpfUG9yIG90cm8gbGFkbywgc2UgcHVlZGUgb2JzZXJ2YXIgcXVlIGVsIGNsw7pzdGVyIDAzIHBvc2VlIGxvcyBpbmRpdmlkdW9zIGRlIG1heW9yIGVkYWQsIHBlcm8sIGVsIHBlc28sIGVsIMOtbmRpY2UgZGUgbWFzYSBjb3Jwb3JhbCwgdGFsbGEgZXMgbWVub3IgYWwgY2zDunN0ZXIgMDEgeSBwb3NlZW4gbWF5b3IgZGVzbnV0cmljacOzbi5fDQoNCg0KIyMjIERpYWdyYW1hIGRlIGNhcmFjdGVyaXphY2nDs24gLSBib3hwbG90DQoNCmBgYHtyfQ0KZGQgPC0gY2JpbmQoYXlhX2JrcCwgY2x1c3RlciA9Z3JwX1dSICkNCmRkJGNsdXN0ZXI8LWFzLmZhY3RvcihkZCRjbHVzdGVyKQ0KZGYubSA8LSBtZWx0KGRkLCBpZC52YXIgPSAiY2x1c3RlciIpDQpwIDwtIGdncGxvdChkYXRhID0gZGYubSwgYWVzKHg9dmFyaWFibGUsIHk9dmFsdWUpKSArIA0KICBnZW9tX2JveHBsb3QoYWVzKGZpbGw9Y2x1c3RlcikpKyBmYWNldF93cmFwKCB+IHZhcmlhYmxlLCBzY2FsZXM9ImZyZWUiKSANCnANCmBgYA0KDQpfTGFzIGdyw6FmaWNhcyBwZXJtaXRlbiBhbmFsaXphciBsb3MgY2zDunN0ZXJlcyBkZSBtYW5lcmEgaW5kaXZpZHVhbCBhIHRyYXbDqXMgZGUgc3VzIHZhcmlhYmxlcy4gUG9yIGVqZW1wbG8sIGVsIGNsw7pzdGVyIGNvbiBtYXlvciBhbHR1cmEgZGUgcmVzaWRlbmNpYSBlcyBlbCBuw7ptZXJvIDAzLiBFbCBjbMO6c3RlciBjb24gbWVub3IgdGFsbGEgMDMsIGVsIGNsw7pzdGVyIGNvbiBtYXlvciDDrW5kaWNlIGRlIG1hc2EgY29ycG9yYWwgZXMgZWwgY2zDunN0ZXIgMDEuXw0KDQojIyBPYnRlbmllbmRvIGVsIGNsdXN0ZXIgY29uIGVsIG1ldG9kbzogKiphdmVyYWdlKioNCg0KYGBge3J9DQpDbHVzX0FHX0FWIDwtIGhjbHVzdChkaXMuRGF0YSxtZXRob2Q9ImF2ZXJhZ2UiKQ0KYGBgDQoNCiMjIERldGVybWluYW5kbyBkZSBtYW5lcmEgZ3JhZmljYSBlbCBudW1lcm8gZGUgY2x1c3RlcnMNCg0KYGBge3J9DQpsaWJyYXJ5KGdnaGlnaGxpZ2h0KQ0KbG9uZ2l0dWQ9bGVuZ3RoKENsdXNfQUdfQVYkaGVpZ2h0KQ0KYWx0dXJhcyA8LSBkYXRhLmZyYW1lKGV0YXBhID0gMTpsb25naXR1ZCwgZGlzdGFuY2lhID0gQ2x1c19BR19BViRoZWlnaHQpDQpnZ3Bsb3QoYWx0dXJhcykgKyBhZXMoeCA9IGV0YXBhLCB5ID0gZGlzdGFuY2lhKSAgKw0KICBnZW9tX3BvaW50KCkgKyBnZW9tX2xpbmUoKSAgKyANCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgxLCBsb25naXR1ZCwgMTAwMCkpICsgDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDc1OTcsIGNvbCA9ICJyZWQiLCBsdHkgPSAyKSArIA0KICBnZW9tX3RleHQoYWVzKGxhYmVsICA9IHJvdW5kKGRpc3RhbmNpYSwxKSksDQogICAgICAgICAgICBzaXplID0gMywgaGp1c3Q9ICsxLCB2anVzdD0gLTEpICsNCiAgdGhlbWVfY2xhc3NpYygpICMrIGdnaGlnaGxpZ2h0KGRpc3RhbmNpYSA+IDExKQ0KYGBgDQo8IS0tIFZpc3VhbG1lbnRlIGRldGVybWluYW1vcyBxdWUgcG9kcmlhbW9zIHRyYWJhamFyIGNvbiAwMiBvIDAzIGNsdXN0ZXJzLS0+DQoNCl9FbCBtw6l0b2RvIGdyYWZpY28gbm9zIHN1Z2llcmUgbGEgZXhpc3RlbmNpYSBkZSAyIGEgNSBhZ3J1cGFjaW9uZXMuXw0KDQoNCiMjIERldGVybWluYWRvIGVsIG7Dum1lcm8gZGUgY2x1c3RlcnMgb3B0aW1vDQoNCmBgYHtyfSANCnJlX3Byb2Nlc3M9RkFMU0UNCmlmICAoZmlsZS5leGlzdHMoInJlcy5uYmNsdXN0QVYuUkRhdGEiKSYgIXJlX3Byb2Nlc3MpIHsgICAgICAjQ2FtYmlvICANCiAgICAgbG9hZCgncmVzLm5iY2x1c3RBVi5SRGF0YScpIA0KfWVsc2V7DQogIHNlZWQgPSAyMDIyDQogIHJlcy5uYmNsdXN0QVYgPC0gTmJDbHVzdChheWEsIGRpc3RhbmNlID0iZXVjbGlkZWFuIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgbWluLm5jID0gMiwgbWF4Lm5jID0gOCwgbWV0aG9kID0gImF2ZXJhZ2UiLCBpbmRleCA9ImFsbCIpICNDYW1iaW8NCiAgc2F2ZShyZXMubmJjbHVzdEFWLGZpbGU9J3Jlcy5uYmNsdXN0QVYuUkRhdGEnKQ0KfQ0KcGFyKG1mcm93PWMoMSwxKSkgIA0KZnZpel9uYmNsdXN0X3gocmVzLm5iY2x1c3RBVikNCmBgYA0KDQpfbG9zIGRpZmVyZW50ZXMgcmVzdWx0YWRvcyBvYnRlbmlkb3MgYWwgdmFyaWFyIHRvZGFzIGxhcyBjb21iaW5hY2lvbmVzIGxhIGNhbnRpZGFkIGRlIGNsw7pzdGVyIGRlc2VhZG9zIHBhcmEgZWwgbcOpdG9kbyBkZSBlbmxhY2UgZWxlZ2lkbzogYXZlcmFnZS5fDQoNCiMjIFJlYWxpemFuZG8gY2x1c3RlcmluZyBjb24gSz0yDQogIA0KYGBge3J9DQojIENvcnRhbmRvIGVuIDIgY2x1ZXN0ZXINCmdycF9BVj1jdXRyZWUoQ2x1c19BR19BViwgayA9IDIpDQojIE51bWJlciBkZSBjYXNvcyBlbiBjYWRhIGNsdXN0ZXINCnRhYmxlKGdycF9BVikNCiMgRGVzY3JpcGNpw7NuIGRlIGNhZGEgY2x1c3Rlcg0KbWVkPC1hZ2dyZWdhdGUoYXlhX2JrcCwgYnk9bGlzdChjbHVzdGVyPWdycF9BViksIG1lYW4pICNtZWRpYXMNCmthYmxlKG1lZCkgJT4lIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiKSAlPiUgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIikNCiNrbml0cjo6a2FibGUobWVkLCBmb3JtYXQgPSAibWFya2Rvd24iKQ0KYGBgDQpfU2UgcmVhbGl6YSBlbCBjb3J0ZSBkZSBsYSBhZ3J1cGFjacOzbiBnZW5lcmFsIChkZW5kb2dyYW1hKSBlbiAwMiBjbMO6c3RlcmVzLCB0b21hbmRvIGNvbW8gYmFzZSBlbCByZXN1bHRhZG8gw7NwdGltbyBkZSBOYmNsdXN0IHkgb2J0ZW5lbW9zIGxhIGNhbnRpZGFkIGRlIG9ic2VydmFjaW9uZXMgcG9yIGNhZGEgdW5vIGRlIGVsbG9zLiBTZSBwdWVkZSBvYnNlcnZhciBxdWUgZWwgcmVzdWx0YWRvIHByZXNlbnRhIGRvcyBncnVwb3MsIGNvbiBsYSBtYXlvciBjYW50aWRhZCBkZSBpbmRpdmlkdW9zIGFncnVwYWRvcyBlbiBjbMO6c3RlciAwMS5fDQojIyBDYXJhY3RlcsOtemFuZG8gbG9zIGNsdXN0ZXINCg0KIyMjIEdyYWZpY2FuZG8gbG9zIGNsdXN0ZXJzLg0KDQpgYGB7cn0NCiNmdml6X2RlbmQoQ2x1c19BRywgayA9IDIsIGNleCA9IDAuNywgaG9yaXogPSBGQUxTRSwga19jb2xvcnMgPSAiamNvIiwNCiAgICAgICAgICAjcmVjdCA9IFRSVUUsIHJlY3RfYm9yZGVyID0gImpjbyIsIHJlY3RfZmlsbCA9IFRSVUUpDQpgYGANCg0KIyMjIERpYWdyYW1hIGRlIGNhcmFjdGVyaXphY2nDs24gLSBsaW5lYXMNCg0KYGBge3J9DQpkYXRhX3Bsb3Q9c2NhbGUoYXlhX2JrcCkgI1NjYWxlDQojZGF0YV9wbG90PWFwcGx5KGF5YV9ia3AsIDIsIG5vcm1hbGl6ZSkgI01heC1NaW4NCk08LWFzLmRhdGEuZnJhbWUodChyYmluZChhZ2dyZWdhdGUoZGF0YV9wbG90LCBieT1saXN0KGNsdXN0ZXI9Z3JwX0FWKSwgbWVhbilbLC0xXSkpKQ0KYT1hcy52ZWN0b3IoY29sTWVhbnMoZGF0YV9wbG90KSkNCiNmaW49ZGF0YS5mcmFtZShNLGEsbmFtZXMoYXlhX2JrcCkpO25hbWVzKGZpbik8LWMoIkNsdXMxIiwiY2x1czIiLCJjbHVzMyIsIk1lZGlhIiwidmFyIikNCmZpbj1kYXRhLmZyYW1lKE0sYSxuYW1lcyhheWFfYmtwKSk7bmFtZXMoZmluKTwtYygiQ2x1czEiLCJjbHVzMiIsIk1lZGlhIiwidmFyIikNCmFsaT1tZWx0KGZpbixpZC52YXJzID0gInZhciIpDQpnZ3Bsb3QoYWxpLCBhZXMoeD12YXIseT1yb3VuZCh2YWx1ZSwxKSxncm91cD12YXJpYWJsZSxjb2xvdXI9dmFyaWFibGUpKSArDQogIGdlb21fcG9pbnQoKSsgZ2VvbV9saW5lKGFlcyhsdHk9dmFyaWFibGUpKSsgZXhwYW5kX2xpbWl0cyh5ID0gYygtMS45LCAxLjkpKSsNCiAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNjAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkNCg0KDQpgYGANCl9FbCBjw6FsY3VsbyBkZWwgcHJvbWVkaW8gcG9yIGNhZGEgZ3J1cG8gbm8gcGVybWl0ZSBkaWZlcmVuY2lhcmxvcyBjb3JyZWN0YW1lbnRlIGNvbiB1bmEgbWFyY2FkYSBkaWZlcmVuY2lhIGVuIG11Y2hhcyB2YXJpYWJsZXMuXw0KDQoNCg0KDQojIyMgRGlhZ3JhbWEgZGUgY2FyYWN0ZXJpemFjacOzbiAtIGJveHBsb3QNCg0KYGBge3J9DQpkZCA8LSBjYmluZChheWFfYmtwLCBjbHVzdGVyID1ncnBfQVYgKQ0KZGQkY2x1c3RlcjwtYXMuZmFjdG9yKGRkJGNsdXN0ZXIpDQpkZi5tIDwtIG1lbHQoZGQsIGlkLnZhciA9ICJjbHVzdGVyIikNCnAgPC0gZ2dwbG90KGRhdGEgPSBkZi5tLCBhZXMoeD12YXJpYWJsZSwgeT12YWx1ZSkpICsgDQogIGdlb21fYm94cGxvdChhZXMoZmlsbD1jbHVzdGVyKSkrIGZhY2V0X3dyYXAoIH4gdmFyaWFibGUsIHNjYWxlcz0iZnJlZSIpIA0KcA0KYGBgDQoNCiMjIyBVc2FuZG8gZWwgaW5kaWNlIGRlIHJhbmQgcGFyYSBkZWZpbmlyIGVsIGNsdXN0ZXIgb3B0aW1vDQoNCmBgYHtyfQ0KbGlicmFyeShmb3NzaWwpDQpyYW5kLmluZGV4KGdycF9XUixncnBfQVYpDQpgYGANClNlZ3VuIGVsIGluZGljZSBkZSBSYW5kLCBubyBzZSBwb2RyaWEgY29tcHJvYmFyIHF1ZSBsYXMgc29sdWNpb25lcyAod2FyZC5EIHkgYXZlcmFnZSkgY2x1c3RlcnMgc29uIHBhcmVjaWRvcywgcG9yIGVzZSBtb3Rpdm9zIGVsZWdpcmVtb3MgZWwgcXVlIHRpZW5lIG1heW9yIHNlcnBhcmFjw7NuIGEgbml2ZWwgZ3JhZmljbyBkZWwgbGluZWFzLg0KDQojICoqQW7DoWxpc2lzIGNsdXN0ZXI6IEsg4oCTIE1lYW5zKioNCg0KIyMgRGV0ZXJtaW5hbmRvIG7Dum1lcm8gw7NwdGltbyBkZSBjbHVzdGVycw0KDQoNCg0KIyMjIFNpbGhvdWV0dGUgbWV0aG9kDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKQ0KcmVfcHJvY2Vzcz1GQUxTRQ0KaWYgIChmaWxlLmV4aXN0cygiZ2dfc2lsLlJEYXRhIikgJiAhcmVfcHJvY2VzcykgeyAgICAgICNDYW1iaW8gIA0KICAgICBsb2FkKCdnZ19zaWwuUkRhdGEnKSANCn1lbHNlew0KICAgZ2dfc2lsPWZ2aXpfbmJjbHVzdChheWEsIGttZWFucywgbWV0aG9kID0gInNpbGhvdWV0dGUiKSsNCiAgICAgICAgICBsYWJzKHN1YnRpdGxlID0gIlNpbGhvdWV0dGUgbWV0aG9kIikNCiAgIHNhdmUoZ2dfc2lsLGZpbGU9J2dnX3NpbC5SRGF0YScpDQp9DQpnZ19zaWwNCmBgYA0KDQpfRWwgcmVzdWx0YWRvIGRlbCDDrW5kaWNlIGRlIHNpbHVldGEgZXN0YWJsZWNlIHF1ZSBwYXJhIEstbWVhbnMgZXMgw7NwdGltbyB1dGlsaXphciAzIGNsw7pzdGVyZXMsIGRlIGVzdGUgbW9kbyBzZSBnZW5lcmFyb24gMyBjbMO6c3RlcmVzIGNvbiAyOTYyLCAyMjEwIHkgMjQyNyByZXNwZWN0aXZhbWVudGUuXw0KDQojIyMgRWxib3cgbWV0aG9kIChXU1MpDQpgYGB7cn0NCnNldC5zZWVkKDEyMykNCnJlX3Byb2Nlc3M9RkFMU0UNCmlmICAoZmlsZS5leGlzdHMoImdnX1dTUy5SRGF0YSIpICYgIXJlX3Byb2Nlc3MpIHsgICAgICAjQ2FtYmlvICANCiAgICAgbG9hZCgnZ2dfV1NTLlJEYXRhJykgDQp9ZWxzZXsNCiAgIGdnX1dTUz1mdml6X25iY2x1c3QoYXlhLCBrbWVhbnMsIG1ldGhvZCA9ICJ3c3MiKSArIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDMsIGxpbmV0eXBlID0gMikrDQogICAgICAgICAgbGFicyhzdWJ0aXRsZSA9ICJFbGJvdyBtZXRob2QiKQ0KICAgc2F2ZShnZ19XU1MsZmlsZT0nZ2dfV1NTLlJEYXRhJykNCn0NCmdnX1dTUw0KYGBgDQoNCg0KIyMgUmVhbGl6YW5kbyBjbHVzdGVyaW5nIGNvbiBLPTMNCg0KYGBge3J9DQprbS5yZXMgPC0ga21lYW5zKGF5YSwgY2VudGVycz0zLG5zdGFydCA9IDI1KQ0KZ3JwX2ttPWttLnJlcyRjbHVzdGVyDQp0YWJsZShncnBfa20pDQojIERlc2NyaXBjacOzbiBkZSBjYWRhIGNsdXN0ZXINCm1lZDwtYWdncmVnYXRlKGF5YV9ia3AsIGJ5PWxpc3QoY2x1c3Rlcj1ncnBfa20pLCBtZWFuKQ0Ka2FibGUobWVkKSAlPiUga2FibGVfc3R5bGluZygic3RyaXBlZCIpICU+JSBzY3JvbGxfYm94KHdpZHRoID0gIjEwMCUiKQ0KI2tuaXRyOjprYWJsZShtZWQsIGZvcm1hdCA9ICJtYXJrZG93biIpDQpgYGANCg0KIyMgQ2FyYWN0ZXLDrXphbmRvIGxvcyBjbHVzdGVyDQoNCiMjIyBHcmFmaWNhbmRvIGxvcyBjbHVzdGVycy4NCg0KYGBge3J9DQojZnZpel9jbHVzdGVyKGttLnJlcywgZGF0YSA9IGF5YSwNCiAgICAgICAgICAgICMgcGFsZXR0ZSA9ICJqY28iLA0KICAgICAgICAgICAgICNlbGxpcHNlLnR5cGUgPSAiZXVjbGlkIiwgIyBDb25jZW50cmF0aW9uIGVsbGlwc2UNCiAgICAgICAgICAgICAjc3Rhci5wbG90ID0gVFJVRSwgIyBBZGQgc2VnbWVudHMgZnJvbSBjZW50cm9pZHMgdG8gaXRlbXMNCiAgICAgICAgICAgICAjcmVwZWwgPSBUUlVFLCAjIEF2b2lkIGxhYmVsIG92ZXJwbG90dGluZyAoc2xvdykNCiAgICAgICAgICAgICAjZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSkNCmBgYA0KDQojIyMgRGlhZ3JhbWEgZGUgY2FyYWN0ZXJpemFjacOzbiAtIGxpbmVhcw0KDQpgYGB7cn0NCmRhdGFfcGxvdD1zY2FsZShheWFfYmtwKSAjU2NhbGUNCiNkYXRhX3Bsb3Q9YXBwbHkoYXlhX2JrcCwgMiwgbm9ybWFsaXplKSAjTWF4LU1pbg0KTTwtYXMuZGF0YS5mcmFtZSh0KHJiaW5kKGFnZ3JlZ2F0ZShkYXRhX3Bsb3QsIGJ5PWxpc3QoY2x1c3Rlcj1ncnBfa20pLCBtZWFuKVssLTFdKSkpDQphPWFzLnZlY3Rvcihjb2xNZWFucyhkYXRhX3Bsb3QpKQ0KZmluPWRhdGEuZnJhbWUoTSxhLG5hbWVzKGF5YV9ia3ApKTtuYW1lcyhmaW4pPC1jKCJDbHVzMSIsImNsdXMyIiwiY2x1czMiLCJNZWRpYSIsInZhciIpDQojZmluPWRhdGEuZnJhbWUoTSxhLG5hbWVzKGF5YV9ia3ApKTtuYW1lcyhmaW4pPC1jKCJDbHVzMSIsImNsdXMyIiwiTWVkaWEiLCJ2YXIiKQ0KYWxpPW1lbHQoZmluLGlkLnZhcnMgPSAidmFyIikNCmdncGxvdChhbGksIGFlcyh4PXZhcix5PXJvdW5kKHZhbHVlLDEpLGdyb3VwPXZhcmlhYmxlLGNvbG91cj12YXJpYWJsZSkpICsNCiAgZ2VvbV9wb2ludCgpKyBnZW9tX2xpbmUoYWVzKGx0eT12YXJpYWJsZSkpKyBleHBhbmRfbGltaXRzKHkgPSBjKC0xLjksIDEuOSkpKw0KICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA2MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQ0KYGBgDQpfQ29uIHJlc3BlY3RvIGEgbGEgY2FyYWN0ZXJpemFjacOzbiBkZSBsb3MgY2zDunN0ZXJlcywgc2UgZGVzcHJlbmRlIGRlbCBncsOhZmljbyBxdWUgZWwgdmFsb3IgbcOhcyBjZXJjYW5vIGVudHJlIGNsw7pzdGVyZXMgZXMgbGEgYWx0dXJhLCBtaWVudHJhcyBxdWUgZWwgdmFsb3IgbcOhcyBhbGVqYWRvIGVzIGVsIHBlc28uXw0KDQoNCiMjIyBEaWFncmFtYSBkZSBjYXJhY3Rlcml6YWNpw7NuIC0gYm94cGxvdA0KDQpgYGB7cn0NCmRkIDwtIGNiaW5kKGF5YV9ia3AsIGNsdXN0ZXIgPWdycF9rbSApDQpkZCRjbHVzdGVyPC1hcy5mYWN0b3IoZGQkY2x1c3RlcikNCmRmLm0gPC0gbWVsdChkZCwgaWQudmFyID0gImNsdXN0ZXIiKQ0KcCA8LSBnZ3Bsb3QoZGF0YSA9IGRmLm0sIGFlcyh4PXZhcmlhYmxlLCB5PXZhbHVlKSkgKyANCiAgZ2VvbV9ib3hwbG90KGFlcyhmaWxsPWNsdXN0ZXIpKSsgZmFjZXRfd3JhcCggfiB2YXJpYWJsZSwgc2NhbGVzPSJmcmVlIikgDQpwDQpgYGANCl9FbCBncsOhZmljbyBkZSBjYWphcyBpbmRpY2EgcXVlIGxhIHZhcmlhYmxlIG3DoXMgY2VyY2FuYSBlbnRyZSBjbMO6c3RlcnMgZXMgbGEgYWx0dXJhUkVOLCB5IGxhIG3DoXMgbGVqYW5hIGVzIGVsIElNQy5fDQoNCg0KX1BhcmEgbGEgY2x1c3Rlcml6YWNpw7NuIHV0aWxpemFuZG8gZWwgYWxnb3JpdG1vIEJpcmNoLCBzZSBoaXpvIHVzbyBkZSB1biBUaHJlc2hvbGQgaWd1YWwgYSAwLjQyIHkgdW4gQnJhbmNoaW5nIEZhY3RvciBpZ3VhbCBhIDUwLiBfDQoNCg0KIyAqKkFuw6FsaXNpcyBjbHVzdGVyIEJpcmNoIChyZXRpY3VsYXRlKSoqDQoNCiMjIENvbmZpZ3VyYW5kbyBlbnRvcm5vIGRlIGRlc2Fycm9sbG8NCg0KYGBge3IgfQ0KbGlicmFyeShyZXRpY3VsYXRlKQ0KdXNlX3B5dGhvbihwYXRoX3B5dGhvbikNCmF5YV9wID0gcl90b19weShheWEpICNyLmF5YQ0KYGBgDQoNCiMjIENhcmdhbmRvIGxpYnJlcmlhcyBkZSBweXRob24gZW4gUg0KDQpgYGB7cHl0aG9ufQ0KaW1wb3J0IHBhbmRhcyBhcyBwZCANCmZyb20gc2tsZWFybi5wcmVwcm9jZXNzaW5nIGltcG9ydCBTdGFuZGFyZFNjYWxlciANCmZyb20gc2tsZWFybi5jbHVzdGVyIGltcG9ydCBCaXJjaCANCmBgYA0KDQojIyBEZWZpbmllbmRvIEJpcmNoIGNvbjogdGhyZXNob2xkPTEsIGJyYW5jaGluZ19mYWN0b3I9Mg0KYGBge3B5dGhvbn0NCmRmID0gci5heWFfcCMgT2J0ZW5pZW5kbyBvYmpldG8gUiB0byBweXRob24NCmJyYyA9IEJpcmNoKHRocmVzaG9sZD0wLjQyLGJyYW5jaGluZ19mYWN0b3I9NTAsbl9jbHVzdGVycz0zKQ0KYnJjLmZpdChkZikNCmBgYA0KDQojIyBSZWFsaXphbmRvIHByZWRpY2Npw7NuDQoNCmBgYHtweXRob259DQpkYXRhX3RvdGFsID0gcGQuY29uY2F0KFtkZixwZC5EYXRhRnJhbWUoYnJjLnByZWRpY3QoZGYpKV0sYXhpcz0xKQ0KZGF0YV90b3RhbC5oZWFkKCkNCmBgYA0KDQojIyBQcm9wb3JjaW9uZXMgcG9yIGNhZGEgY2x1c3Rlcg0KDQpgYGB7cHl0aG9ufQ0KZGF0YV90b3RhbC5ncm91cGJ5KDApWydQZXNvJ10uY291bnQoKQ0KYGBgDQoNCl9MYSBwcm9wb3JjacOzbiBwYXJhIGxvcyAzIGNsw7pzdGVyZXMgZXMgZGUgMjc4OCwgMjQwNCB5IDI0MDcgcmVzcGVjdGl2YW1lbnRlLl8NCg0KDQojIyBHdWFyZGFtb3MgZWwgb2JqZXRvIGNvbiBDbHVzdGVyIEJpcmNoDQpQYXJhIGx1ZWdvIHN1YmlybG8gYWwgUg0KYGBge3B5dGhvbn0NCmRhdGFfdG90YWwudG9fZXhjZWwoJ2JpcmNoLnhsc3gnKQ0KYGBgDQoNCiMjIExlZW1vcyBlbCBvYmpldG8gY29uIENsdXN0ZXIgQmlyY2ggZW4gUg0KYGBge3J9DQpiaSA8LSByZWFkX2V4Y2VsKCJiaXJjaC54bHN4IikNCmJpcmNoIDwtIGFzLmRhdGEuZnJhbWUoYmlbLDI6MTJdKQ0KbmFtZXMgKGJpcmNoKVsxMV0gPSAiY2x1c3RlciINCmJpcmNoJGNsdXN0ZXIgPC0gZmFjdG9yKGJpcmNoJGNsdXN0ZXIsIGxldmVscyA9IGMoMCwxLDIpLCBsYWJlbHMgPSBjKDEsMiwzKSkNCg0KaGVhZChiaXJjaCkNCmBgYA0KDQoNCiMjIENhcmFjdGVyw616YW5kbyBsb3MgY2x1c3Rlcg0KYGBge3J9DQpkYXRhX3Bsb3Q9YmlyY2hbMToxMF0NCmNsdXN0ZXI9YmlyY2gkY2x1c3Rlcg0KdGFibGUoY2x1c3RlcikNCiMgRGVzY3JpcGNpw7NuIGRlIGNhZGEgY2x1c3Rlcg0KbWVkPC1hZ2dyZWdhdGUoYXlhLCBieT1saXN0KGNsdXN0ZXI9Y2x1c3RlciksIG1lYW4pDQoNCm1lZA0KDQojIGtuaXRyOjprYWJsZShtZWQpICU+JSBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIikgJT4lIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIpDQoNCmBgYA0KDQojIyBEaWFncmFtYSBkZSBjYXJhY3Rlcml6YWNpw7NuIC0gbGluZWFzDQoNCmBgYHtyfQ0KDQpNPC1hcy5kYXRhLmZyYW1lKHQocmJpbmQoYWdncmVnYXRlKGRhdGFfcGxvdCwgYnk9bGlzdChjbHVzdGVyPWNsdXN0ZXIpLCBtZWFuKVssLTFdKSkpDQoNCmE9YXMudmVjdG9yKGNvbE1lYW5zKGRhdGFfcGxvdCkpDQpmaW49ZGF0YS5mcmFtZShNLGEsbmFtZXMoYXlhX2JrcCkpO25hbWVzKGZpbik8LWMoIkNsdXMxIiwiQ2x1czIiLCJDbHVzMyIsIk1lZGlhIiwidmFyIikNCg0KYWxpPW1lbHQoZmluLGlkLnZhcnMgPSAidmFyIikNCmdncGxvdChhbGksIGFlcyh4PXZhcix5PXJvdW5kKHZhbHVlLDEpLGdyb3VwPXZhcmlhYmxlLGNvbG91cj12YXJpYWJsZSkpICsNCiAgZ2VvbV9wb2ludCgpKyBnZW9tX2xpbmUoYWVzKGx0eT12YXJpYWJsZSkpKyBleHBhbmRfbGltaXRzKHkgPSBjKC0xLjksIDEuOSkpKw0KICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA2MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQ0KYGBgDQoNCg0KX05vIGV4aXN0ZW4gZGlmZXJlbmNpYXMgdGFuIG1hcmNhZGFzIGVuIEFsdHVyYVJFTiBlbnRyZSBlbCBjbHVzdGVyIDEgeSAyLiBFbiBQVFosIFpQRSB5IFpURSB0YW1wb2NvIGhheSBkaWZlcmVuY2lhcyBtYXJjYWRhcyBlbnRyZSBlbCBjbHVzdGVyIDEgeSAyLiBNaWVudHJhcyBxdWUgZGlmZXJlbmNpYXMgbcOhcyBtYXJjYWRhcyBlbnRyZSBIZW1vZ2xvYmluYSwgSGVtb2dsb2JpbmFBanVzdGFkYSwgSU1DLCBQZXNvIHkgVGFsbGFfDQoNCg0KDQojIyMgRGlhZ3JhbWEgZGUgY2FyYWN0ZXJpemFjacOzbiAtIGJveHBsb3QNCg0KDQpgYGB7cn0NCmRkIDwtIGNiaW5kKGF5YV9ia3AsIGNsdXN0ZXIgPWNsdXN0ZXIgKQ0KZGQkY2x1c3RlcjwtYXMuZmFjdG9yKGRkJGNsdXN0ZXIpDQpkZi5tIDwtIG1lbHQoZGQsIGlkLnZhciA9ICJjbHVzdGVyIikNCnAgPC0gZ2dwbG90KGRhdGEgPSBkZi5tLCBhZXMoeD12YXJpYWJsZSwgeT12YWx1ZSkpICsgDQogIGdlb21fYm94cGxvdChhZXMoZmlsbD1jbHVzdGVyKSkrIGZhY2V0X3dyYXAoIH4gdmFyaWFibGUsIHNjYWxlcz0iZnJlZSIpIA0KcA0KDQpgYGANCg0KDQpfRWwgZGlhZ3JhbWEgZGUgY2FqYXMgaW5kaWNhIHF1ZSBsYSB2YXJpYWJsZSBjb24gbG9zIHZhbG9yZXMgbcOhcyBjZXJjYW5vcyBlbnRyZSBjbMO6c3RlcnMgZXMgbGEgQWx0dXJhUkVOLCBtaWVudHJhcyBxdWUgZWwgbcOhcyBhbGVqYWRvIGVzIGxhIEVkYWQgZW4gTWVzZXMuXw0KDQoNCiMgKipWYWxpZGFuZG8gbG9zIGNsdXN0ZXJzKioNCg0KIyMgSW50ZXJuYSAoY29oZXNpw7NuIHkgc2VwYXJhY2nDs24pDQoNCg0KYGBge3J9DQpyZV9wcm9jZXNzPVRSVUUNCiMgaWYgIChmaWxlLmV4aXN0cygiZGZfaW5kZXhzLlJEYXRhIikgJiAhcmVfcHJvY2VzcykgeyAgICAgICNDYW1iaW8gIA0KICAgICAjIGxvYWQoJ2RmX2luZGV4cy5SRGF0YScpIA0KIyB9ZWxzZXsNCiAgICBsaWJyYXJ5KGNsdXN0ZXJTaW0pDQogICAgbGlicmFyeShjbFZhbGlkKQ0KICAgICNJbmRpY2UgZGUgRGF2aWVzLUJvdWxkaW46IGJ1c2NhbW9zIGVsIHZhbG9yIG1hcyBhbHRvIHBvc2libGUNCiAgICBEQmttXyA8LSBpbmRleC5EQihheWEsIGttLnJlcyRjbHVzdGVyLCBjZW50cm90eXBlcyA9ICJjZW50cm9pZHMiKSREQiAja21lYW5zDQogICAgREJIY18gPC1pbmRleC5EQihheWEsIGdycF9XUiwgZD1kaXMuRGF0YSxjZW50cm90eXBlcz0iY2VudHJvaWRzIikkREIgI2hjbHVzdA0KICAgIERCaXJjaF8gPC1pbmRleC5EQihheWEsIGFzLm51bWVyaWMoY2x1c3RlciksIGNlbnRyb3R5cGVzPSJjZW50cm9pZHMiKSREQiAjQmlyY2gNCiAgICANCiAgICAjSW5kaWNlIGRlIGR1bm46IGJ1c2NhbW9zIGVsIHZhbG9yIG1hcyBiYWpvIHBvc2libGUNCiAgICBEbmttXyA8LSBkdW5uKERhdGEgPSBheWEsIGNsdXN0ZXJzID0ga20ucmVzJGNsdXN0ZXIsIGRpc3RhbmNlID0gTlVMTCkja21lYW5zDQogICAgRG5IY18gPC0gZHVubihkaXMuRGF0YSwgZ3JwX1dSKSAjaGNsdXN0DQogICAgRG5CaXJjaF8gPC0gZHVubihEYXRhID0gYXlhLCBjbHVzdGVycyA9IGFzLm51bWVyaWMoY2x1c3RlciksIGRpc3RhbmNlID0gTlVMTCkjQmlyY2gNCiAgICANCiAgICB0aXBvXz1jKCdrbWVhbnMnLCdoY2x1c3QnLCdCaXJjaCcpDQogICAgZGF2aWVzLmJvdWxkaW5fPWMoREJrbV8sREJIY18sREJpcmNoXykNCiAgICBkdW5uXz1jKERua21fLERuSGNfLERuQmlyY2hfKQ0KICAgIGRmX2luZGV4cz1kYXRhLmZyYW1lKHRpcG9fLGRhdmllcy5ib3VsZGluXyxkdW5uXykNCiAgICBjb2xuYW1lcyhkZl9pbmRleHMpPWMoJ0NsdXN0ZXInLCdEYXZpZXMuQm91bGRpbicsJ0R1bm4nKQ0KICAgIHNhdmUoZGZfaW5kZXhzLGZpbGU9J2RmX2luZGV4cy5SRGF0YScpDQojIH0NCiAgICAjIGRmX2luZGV4cw0KIyBrYWJsZShkZl9pbmRleHMpICU+JSBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIikgJT4lIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIpDQpgYGANCg0KDQpgYGB7cn0NCnNpbGhvdWV0dGU8LXJiaW5kKA0KbWVhbihzaWxob3VldHRlKGFzLm51bWVyaWMoa20ucmVzJGNsdXN0ZXIpICxkaXMuRGF0YSlbLDNdKSwgI2ttZWFucw0KbWVhbihzaWxob3VldHRlKGFzLm51bWVyaWMoZ3JwX1dSKSAsZGlzLkRhdGEpWywzXSksICNoY2x1c3QNCm1lYW4oc2lsaG91ZXR0ZShhcy5udW1lcmljKGJpcmNoJGNsdXN0ZXIpICxkaXMuRGF0YSlbLDNdKSAjQmlyY2gNCikNCmBgYA0KDQpgYGB7cn0NCmNiaW5kKGRmX2luZGV4cywgc2lsaG91ZXR0ZSkNCmBgYA0KDQpEZSBhY3VlcmRvIERhdmllcy5Cb3VsZGluIGVsIG1heW9yIHZhbG9yIGVzIG3DqXRvZG8gZXMga21lYW5zLCByZXNwZWN0byBhIER1bm4gZWwgbWVub3IgdmFsb3IgZXMgaGNsdXN0IHkgY29uIHJlc3BlY3RvIGEgc2lsaG91ZXR0ZSBlbCBtZWpvciBlcyBrLW1lYW5zLg0KDQpDcmVhbW9zIHVuYSBudWV2YSB2YXJpYWJsZSBsbGFtYWRhIEFuZW1pYSBlbiBiYXNlIGEgSGVtb2dsb2JpbmFhanVzdGFkYSBwYXJhIGFuYWxpemFybG9zIHBvciBncnVwb3MgZGUgcmVzdWx0YWRvcyBkZSBLLW1lYW5zLg0KDQpgYGB7cn0NCmF5YWN1Y2hvX2ZpbmFsPC1heWFjdWNobw0KYXlhY3VjaG9fZmluYWwkQW5lbWlhPC1pZl9lbHNlKGF5YWN1Y2hvJEhlbW9nbG9iaW5hYWp1c3RhZGEqMTA+PTExMCwgJ1NpbiBhbmVtaWEnLCANCiAgICAgICAgaWZfZWxzZShheWFjdWNobyRIZW1vZ2xvYmluYWFqdXN0YWRhKjEwPj0xMDAsJ0xldmUnLA0KICAgICAgICAgICAgICAgIGlmX2Vsc2UoYXlhY3VjaG8kSGVtb2dsb2JpbmFhanVzdGFkYSoxMD49NzAsJ01vZGVyYWRhJywNCiAgICAgICAgICAgICAgICAgICAgICAgICdHcmF2ZScpKSkNCiAgICAgICAgDQpheWFjdWNob19maW5hbD1jYmluZChheWFjdWNob19maW5hbCwgY2x1c3Rlcj1rbS5yZXMkY2x1c3RlcikNCg0KdGFibGUoYXlhY3VjaG9fZmluYWwkQW5lbWlhKQ0KICAgICANCg0KYGBgDQoNCg0KVG9tYW5kbyBsb3MgcmVzdWx0YWRvcyBkZSBLLW1lYW5zDQoNCg0KYGBge3J9DQpnMV9GIDwtDQogIGdncGxvdChtdXRhdGUoYXlhY3VjaG9fZmluYWwsIGNsdXN0ZXIgPSBmYWN0b3IoY2x1c3RlcikpKSArDQogIGFlcyhjbHVzdGVyLCBmaWxsID1BbmVtaWEgKSArDQogIGdlb21fYmFyKGNsdXN0ZXIgPSBwb3NpdGlvbl9maWxsKCkpICsNCiAgbGFicyh0aXRsZT0iY2x1c3RlciBzZWfDum4gQW5lbWlhIiwNCiAgICAgICAgICAgIHggPSBOVUxMLCB5ID0gIlByb3BvcmNpw7NuIikgKw0KICB0aGVtZV9idygpDQpnMV9GDQpgYGANCg0KVmVtb3MgcXVlIGVsIG1heW9yIGdydXBvIGRlIG5pw7FvcyBjb24gZGVzbnV0cmljacOzbiBtb2RlcmFkYSB5IGxldmUgZXN0YSBlbiBlbCBjbHVzdGVyIDEsIHNlZ3VpZG8gZGVsIGNsdXN0ZXIgMiB5IHBvciB1bHRpbW8gZWwgY2x1c3RlciAzLiBFbCBtYXlvciBuw7ptZXJvIGRlIG5pw7FvcyBzaW4gYW5lbWlhIGVzdGEgZW4gZWwgY2x1c3RlciAzLg0KDQoNCkRlbCBtaXNtbyBtb2RvIHRyYW5zcG9uaWVuZG8gbG9zIGVqZXMgZGVsIGdyYWZpY28gYW50ZXJpb3IsIGVsIGNsdXN0ZXIgMSB0aWVuZSBsYSBtYXlvciBwcm9wb3JjacOzbiBkZSBuacOxb3MgY29uIGFuZW1pYSBsZXZlLCBsbyBtaXNtbyBvY3VycmUgZW4gYW5lbWlhIG1vZGVyYWRhLg0KDQoNCmBgYHtyfQ0KZzJfRiA8LQ0KICBnZ3Bsb3QobXV0YXRlKGF5YWN1Y2hvX2ZpbmFsLCBjbHVzdGVyID0gZmFjdG9yKGNsdXN0ZXIpKSkgKw0KICBhZXMoQW5lbWlhLCBmaWxsID1jbHVzdGVyICkgKw0KICBnZW9tX2JhcihBbmVtaWEgPSBwb3NpdGlvbl9maWxsKCkpICsNCiAgbGFicyh0aXRsZT0iQW5lbWlhIHNlZ8O6biBjbHVzdGVyIiwNCiAgICAgICAgICAgIHggPSBOVUxMLCB5ID0gIlByb3BvcmNpw7NuIikgKw0KICB0aGVtZV9saW5lZHJhdygpDQpnMl9GDQpgYGANCiAgDQogIA0KIyAqKkNvbmNsdXNpb25lcyoqDQoNCi0gTGEgZXZhbHVhY2nDs24gdGFudG8gcG9yIEstcHJvdG90eXBlcyBjb21vIGNvbiBsYSBmdW5jacOzbiBOYkNsdXN0IG5vcyBpbmRpY2EgcXVlIGRlYmVtb3MgdHJhYmFqYXIgY29uIDMgZ3J1cG9zLg0KDQotIExhIG1ldG9kb2xvZ8OtYSBBR05FUyB5IEZ1enp5IEMtTWVhbnMgZW4gbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzIGdlbmVyYW4gY2zDunN0ZXJlcyBjb24gdW4gY29tcG9ydGFtaWVudG8gbXV5IHNpbWlsYXIsIHNpbiBlbWJhcmdvLCBlbiBlbCBjYXNvIGRlIGxhcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzIHNlIHRpZW5lbiBkaWZlcmVuY2lhcyBzaWduaWZpY2F0aXZhcy4NCg0KLSBTZSBkZXRlcm1pbmEgbWVkaWFudGUgZWwgZ3LDoWZpY28gZGUgcGVyZmlsZXMgcXVlIGxhIGVkYWQgZ2VzdGFjaW9uYWwgbm8gZ2VuZXJhIHVuIG1heW9yIGFwb3J0ZSBhbCBjb21wb3J0YW1pZW50byBkZSBsb3MgZ3J1cG9zLCB5YSBxdWUgZW4gbG9zIHRyZXMgbG9zIHZhbG9yZXMNCg0KLSBGaW5hbG1lbnRlIGNvbnNpZGVyYW5kbyBBR05FUywgc2UgdGllbmU6DQpHcnVwbyAxOiBHZXN0YW50ZXMgZW4gc3UgbWF5b3LDrWEgZGUgZW1iYXJhem8gc2ltcGxlLCBkZSBSZWdpb25lcyBBcHVyw61tYWMsIEh1YW5jYXZlbGljYSB5IFBhc2NvLCBwb3IgbG8gcXVlIHRpZW5lbiB1biBuaXZlbCBkZSBhbHRpdHVkIG1heW9yIHkgdW4gbml2ZWwgZGUgaGVtb2dsb2JpbmEgc3VwZXJpb3IgYSBsb3MgZGVtw6FzIGdydXBvcy4gR3J1cG8gMjogR2VzdGFudGVzIGVuIHN1IG1heW9yw61hIGRlIGVtYmFyYXpvIG3Dumx0aXBsZSwgZGUgbGEgcmVnacOzbiBkZSBBeWFjdWNobywgcG9yIGxvIHF1ZSB0aWVuZW4gdW4gbml2ZWwgZGUgYWx0aXR1ZCBpbnRlcm1lZGlhIHkgY29uIGhlbW9nbG9iaW5hIElNQywgUGVzbywgUFBHIHkgVGFsbGEgZW4gdW4gbml2ZWwgbWVkaW8uIEdydXBvIDM6IEdlc3RhbnRlcyBjb24gdW4gZW1iYXJhem8gc2ltcGxlIGRlIGxhIHJlZ2nDs24gZGUgSnVuw61uIGNvbiBlbA0KDQogIHwgIEdSVVBPICB8IEFUUklCVVRPIHwgQUdORVMgfCBLLU1lYW5zIHwgQklSQ0ggfA0KICB8Oi0tLS0tLS06fDotLS0tLS0tLTp8Oi0tLS0tOnw6LS0tLS0tLTp8Oi0tLS0tOnwNCiAgfCBHcnVwbyAxIHwgQ2FudGlkYWQgfCAzLDM2MSB8ICAyLDk2MiAgfCAyLDc4OCB8DQogIHwgICAgICAgICB8ICAgICAlICAgIHwgNDQuMiUgfCAgMzkuMCUgIHwgMzYuNyUgfA0KICB8IEdydXBvIDIgfCBDYW50aWRhZCB8IDIsOTE5IHwgIDIsMjEwICB8IDIsNDA0IHwNCiAgfCAgICAgICAgIHwgICAgICUgICAgfCAzOC40JSB8ICAyOS4xJSAgfCAzMS42JSB8DQogIHwgR3J1cG8gMyB8IENhbnRpZGFkIHwgMSwzMTkgfCAgMiw0MjcgIHwgMiw0MDcgfA0KICB8ICAgICAgICAgfCAgICAgJSAgICB8IDE3LjQlIHwgIDMxLjklICB8IDMxLjclIHwNCg0KDQoNCiMgKipSZWZlcmVuY2lhcyoqDQotIEZhbm55IFJhbWFkaGFuaSBldCBhbCAyMDIwIElPUCBDb25mLiBTZXIuOiBNYXRlci4gU2NpLiBFbmcuIDcyNSAwMTIwOTANCg0KLSBBbG9uc28gZGVsIFNhc28sIEphdmllciDigJxNw6l0b2RvcyBkZSBkZXRlY2Npw7NuIGRlIGFub21hbMOtYXMgeSBjbHVzdGVyaW5nIGVuIHNlcmllcyB0ZW1wb3JhbGVz4oCdIDIwMjAsIFNhbnRhbmRlciwgRXNwYcOxYS4gDQoNCg0K