Resumen
El presente artículo evalúa variables relacionadas a niñas y niños
menores de 1 a 3 años de la Región de Ayacucho - Perú, que fueron
atendidos en los establecimientos de salud de Dirección de Regional de
Salud (Diresa) respectiva, 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: AGNES, K-Means y BIRCH; obteniendo que el mejor índice de
silueta lo brinda la metodología …… con (n) grupos debido a que el
perfilado resultante permite observar las diferencias entre infantes,
principalmente por las variables …… y ……
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 y 2 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.
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 ““6
047”” registros de menores de 1 y 2 años y fue tomada de la Plataforma
Nacional de Datos Abiertos .
Unidad de análisis Niñas y niños menores de 1 y 2 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.
Metodología
Utilizamos el método BIRCH (Balanced iterative reducing and
clustering using hierarchies) para determinar grupos de niños de acuerdo
con sus características nutricionales. El objetivo principal de esta
metodología es trabajar la agrupación con grandes cantidades de datos
[1] 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.
Algoritmo Considerando los siguientes valores:
N Puntos 1 22 2 9 3 12 4 15
- Se elige el Treshold (5).
- Se elige el Branching factor (3).
- Se toma el primer valor y se eleva al cuadrado.
- Se eleva al cuadrado ambos valores y se suma.
Valores N Suma Cuadrado 22 1 22 484 9 2 31 565
- El centroide es igual a la suma de los valores sobre N, para el
primer valor el centroide sería: Centroide = 22/1 = 22
- Se aplica la fórmula
………………………..
Entonces D = 13, que es superior al treshold, por lo tanto, se crea
un nuevo clúster
- Trabajando el siguiente valor:
Valores N Suma Cuadrado 1 9 81 12 2 21 225
- Se vuelve a aplicar la fórmula, y se obtiene D = 3, por lo tanto, el
tercer valor se designa al clúster con el centroide más cercano, el cual
es el segundo clúster ya que 12 está más cerca de 9 que de 22.
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.
Configuración inicial
#Configuración del entorno
rm(list = ls())
setwd(dirname(rstudioapi::getActiveDocumentContext()$path))
graphics.off()
options(scipen = 999)
options(digits = 3)
path_python="C:/Users/adwin/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")
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)
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
Datos Perdidos
aya <-ayacucho[,14:23]
plot_missing(aya)

Analisis de Outliers
gg_box_density(aya)


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)


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)
Estandarización de los datos
aya <- as.data.frame(scale(aya))
Determinando factibilidad del cluster
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
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)
# }
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
Análisis cluster jerarquico (hclust {stats})
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
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
|
Obteniendo el cluster con el metodo: Ward.D
Clus_AG_W <- hclust(dis.Data,method="ward.D")
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)
Determinado el número de clusters optimo
re_process=FALSE
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 .

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")
Caracterízando los cluster
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)
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))

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

Obteniendo el cluster con el metodo: average
Clus_AG_AV <- hclust(dis.Data,method="average")
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)
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 .

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")
Caracterízando los cluster
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)
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))

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

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.
Análisis cluster: K – Means
Determinando número óptimo de clusters
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

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")
Caracterízando los cluster
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())
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))

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

Análisis cluster Birch (reticulate)
Configurando entorno de desarrollo
library(reticulate)
use_python(path_python)
aya_p = r_to_py(aya) #r.aya
Cargando librerias de python en R
import pandas as pd
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import Birch
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)
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]
Proporciones por cada cluster
data_total.groupby(0)['Peso'].count()
0
0 2788
1 2404
2 2407
Name: Peso, dtype: int64
Guardamos el objeto con Cluster Birch
Para luego subirlo al R
data_total.to_excel('birch.xlsx')
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
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%")
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))

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

Validando los clusters
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
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
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

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

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
LS0tDQp0aXRsZTogIkFsZ29yaW1vIEJpcnRjaDogRXN0YWRvIG51dHJpY2lvbmFsIGRlIG5pw7FhcyB5IG5pw7FvcyBtZW5vcmVzIGRlIDEgYSAzIGHDsW9zDQogIGRlIGxhIFJlZ2nDs24gZGUgQXlhY3VjaG8gLSBQZXLDuiINCmRhdGU6ICIyMDIyLTEyLTAzIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0Og0KICAgICAgY29sbGFwc2VkOiBubw0KICAgICAgc21vb3RoX3Njcm9sbDogbm8NCiAgICBudW1iZXJfc2VjdGlvbnM6IHllcw0KICAgIGFuY2hvcl9zZWN0aW9uczogeWVzDQogICAgY29kZV9mb2xkaW5nOiBoaWRlDQogICAgY29kZV9kb3dubG9hZDogeWVzDQogICAgdGhlbWU6IHlldGkNCi0tLQ0KPCEtLSBQYXJhbWV0cm9zIGdlbmVyYWxlcyBkZWwgbWFya2RvbnctLT4NCg0KYGBge3IgaW5jbHVkZT1GQUxTRX0NCiNDb25maWd1cmFjacOzbiBkZSBsYXMgY2h1bmsNCmtuaXRyOjpvcHRzX2NodW5rJHNldChtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSxjb21tZW50ID0gTkEpDQpgYGANCg0KDQojICoqUmVzdW1lbioqDQoNCkVsIHByZXNlbnRlIGFydMOtY3VsbyBldmFsw7phIHZhcmlhYmxlcyByZWxhY2lvbmFkYXMgYSBuacOxYXMgeSBuacOxb3MgbWVub3JlcyBkZSAxIGEgMyBhw7FvcyBkZSBsYSBSZWdpw7NuIGRlIEF5YWN1Y2hvIC0gUGVyw7osIHF1ZSBmdWVyb24gYXRlbmRpZG9zIGVuIGxvcyBlc3RhYmxlY2ltaWVudG9zIGRlIHNhbHVkIGRlIERpcmVjY2nDs24gZGUgUmVnaW9uYWwgZGUgU2FsdWQgKERpcmVzYSkgcmVzcGVjdGl2YSwgZWwgYcOxbyAyMDIxLCBjb24gZWwgb2JqZXRvIGRlIG9idGVuZXIgcGVyZmlsZXMgbnV0cmljaW9uYWxlcyBxdWUgcGVybWl0YW4gZm9jYWxpemFyIGVsIHNlcnZpY2lvIGRlIGxvcyBjZW50cm9zIGRlIHNhbHVkIHJlc3BlY3Rpdm9zLiBTZSBhcGxpY2Fyb24gdHJlcyAoMykgbWV0b2RvbG9nw61hcyBkZSBhZ3J1cGFtaWVudG86IEFHTkVTLCBLLU1lYW5zIHkgQklSQ0g7IG9idGVuaWVuZG8gcXVlIGVsIG1lam9yIMOtbmRpY2UgZGUgc2lsdWV0YSBsbyBicmluZGEgbGEgbWV0b2RvbG9nw61hIC4uLi4uLiBjb24gKG4pIGdydXBvcyBkZWJpZG8gYSBxdWUgZWwgcGVyZmlsYWRvIHJlc3VsdGFudGUgcGVybWl0ZSBvYnNlcnZhciBsYXMgZGlmZXJlbmNpYXMgZW50cmUgaW5mYW50ZXMsIHByaW5jaXBhbG1lbnRlIHBvciBsYXMgdmFyaWFibGVzIC4uLi4uLiB5IC4uLi4uLg0KDQoNCiMgKipJbnRyb2R1Y2Npw7NuKioNCg0KTGEgZGVzbnV0cmljacOzbiBjcsOzbmljYSBlbiBsYSBpbmZhbmNpYSBlcyB1bm8gZGUgbG9zIHByaW5jaXBhbGVzIHByb2JsZW1hcyBkZSBzYWx1ZCBww7pibGljYSB5IHVuIGluZGljYWRvciBkZSBkZXNhcnJvbGxvIHNvY2lhbCBkZWwgcGHDrXM7IHRpZW5lIGVmZWN0b3MgaXJyZXZlcnNpYmxlcyBlbiBlbCBkZXNhcnJvbGxvIGRlIGhhYmlsaWRhZGVzIHkgY2FwYWNpZGFkZXMgZW4gbGEgbmnDsWEgeSBlbCBuacOxby4NCg0KRWwgSU5FSSBlbiBzdSBpbmZvcm1lIOKAnFBlcsO6OiBJbmRpY2Fkb3JlcyBkZSByZXN1bHRhZG9zIGRlIGxvcyBwcm9ncmFtYXMgcHJlc3VwdWVzdGFsZXMsIDIwMTUtMjAyMOKAnSBzZcOxYWxhIHF1ZSBlbCAxMi4xJSBkZSBsYSBwb2JsYWNpw7NuIG1lbm9yIGRlIGNpbmNvIGHDsW9zIGRlIGVkYWQgZGVsIHBhw61zIHN1ZnJpw7MgZGVzbnV0cmljacOzbiBjcsOzbmljYSwgY29uIGJhc2UgYWwgcGF0csOzbiBkZSBsYSBPTVMsIHNpZW5kbyAwLjElIG1lbm9yIHF1ZSBlbCAyMDE5IHkgLTIuMyUgcmVzcGVjdG8gYWwgMjAxNS4gQSBzdSB2ZXosIGVsIG1pc21vIGluZm9ybWUgc2XDsWFsYSBxdWUgZWwgNDAlIGRlIG5pw7FvcyB5IG5pw7FhcyBlbnRyZSA2IGEgMzUgbWVzZXMgZW4gZWwgMjAyMCBwcmVzZW50YXJvbiBwcmV2YWxlbmNpYSBkZSBhbmVtaWEuIA0KDQpFbCBkaXNlw7FvIGRlIHBvbMOtdGljYXMgcMO6YmxpY2FzLCB5IGVuIGVzcGVjw61maWNvLCBlbCBkZXNhcnJvbGxvIGRlIHByb2dyYW1hcyB5IHByb3llY3RvcyBzb2NpYWxlcywgcGFyYSBtZWpvcmFyIGVsIGVzdGFkbyBudXRyaWNpb25hbCBkZSBsb3MgbWVub3JlcyBkZSBjaW5jbyBhw7FvcyBlcyB1bmEgcHJpb3JpZGFkIHBhcmEgZWwgZGVzYXJyb2xsbyBuYWNpb25hbDsgc2llbmRvIGltcG9ydGFudGUgbGEgZ2VuZXJhY2nDs24gZGUgaW52ZXN0aWdhY2lvbmVzIHF1ZSBwZXJtaXRhbiBjb25vY2VyIHN1cyBwZXJmaWxlcyBudXRyaWNpb25hbGVzDQoNCkVuIGVzZSBzZW50aWRvIGVsIHByZXNlbnRlIHRyYWJham8gdGllbmUgY29tbyBvYmpldGl2byBsYSBvYnRlbmNpw7NuIGRlIGNsw7pzdGVyZXMgcGFyYSBjb25vY2VyIGxvcyBwZXJmaWxlcyBudXRyaWNpb25hbGVzIGRlIG5pw7FvcyB5IG5pw7FhcyBtZW5vcmVzIGRlIDEgeSAyIGHDsW9zIHF1ZSBmdWVyb24gYXRlbmRpZG9zIGVuIGVzdGFibGVjaW1pZW50b3MgZGUgc2FsdWQgZGUgcHJpbWVyIG5pdmVsIGRlIGxhIERpcmVzYSBBeWFjdWNobyBlbCBhw7FvIDIwMjEsIGNvbiBmaW5lcyBkZSBpZGVudGlmaWNhciBwYXRyb25lcyBxdWUgcGVybWl0YW4gbGEgcGxhbmlmaWNhY2nDs24gZGUgaW50ZXJ2ZW5jaW9uZXMgZm9jYWxpemFkYXMgcXVlIGNvbnRyaWJ1eWFuIGEgbWVqb3JhciBzdSBjYWxpZGFkIGRlIHZpZGEuDQoNCiMgKipEZXNjcmlwY2nDs24gZGUgbG9zIGRhdG9zKioNCg0KTG9zIGRhdG9zIGNvcnJlc3BvbmRlbiBhIGluZm9ybWFjacOzbiBkZSBoaXN0b3JpYXMgY2zDrW5pY2FzIGRlIGxvcyBlc3RhYmxlY2ltaWVudG9zIGRlIHNhbHVkIGRlbCBNaW5pc3RlcmlvIGRlIFNhbHVkIGRlbCBQZXLDuiAoTWluc2EpLCB5IHF1ZSBlcyByZWdpc3RyYWRhIGVuIGVsIFNpc3RlbWEgZGUgSW5mb3JtYWNpw7NuIGRlbCBFc3RhZG8gTnV0cmljaW9uYWwgKFNJRU4pLCBhZG1pbmlzdHJhZG8gcG9yIGVsIENlbnRybyBOYWNpb25hbCBkZSBBbGltZW50YWNpw7NuIHkgTnV0cmljacOzbiAoQ0VOQU4pLCDDs3JnYW5vIGRlIGzDrW5lYSB0w6ljbmljbyBub3JtYXRpdm8gZGVsIEluc3RpdHV0byBOYWNpb25hbCBkZSBTYWx1ZCAoSU5TKS4gTGEgYmFzZSBkZSBkYXRvcyBxdWUgdXNhbW9zIGN1ZW50YSBjb24gdW4gdG90YWwgZGUgIiI2IDA0NyIiIHJlZ2lzdHJvcyBkZSBtZW5vcmVzIGRlIDEgeSAyIGHDsW9zIHkgZnVlIHRvbWFkYSBkZSBsYSBQbGF0YWZvcm1hIE5hY2lvbmFsIGRlIERhdG9zIEFiaWVydG9zIC4NCg0KDQpVbmlkYWQgZGUgYW7DoWxpc2lzDQpOacOxYXMgeSBuacOxb3MgbWVub3JlcyBkZSAxIHkgMiBhw7FvcyBxdWUgZnVlcm9uIGF0ZW5kaWRvcyBlbiBsb3MgZXN0YWJsZWNpbWllbnRvcyBkZSBzYWx1ZCBkZWwgcHJpbWVyIG5pdmVsIGRlIGF0ZW5jacOzbiBkZSBsYSBEaXJlc2EgQXlhY3VjaG8gZWwgYcOxbyAyMDIxLg0KDQpWYXJpYWJsZXMNCkxhcyB2YXJpYWJsZXMgdXRpbGl6YWRhcyBlbiBlbCBhbsOhbGlzaXMgc29uIGxhcyBzaWd1aWVudGVzOg0KDQpWYXJpYWJsZXMgcHJlZGljdG9yYXM6DQoqRWRhZC9tZXNlczogRWRhZCBlbiBtZXNlcyBkZWwgbWVub3IgYXRlbmRpZG8uDQoqUGVzbyAoa2cpDQoqVGFsbGEgKGNtKQ0KKlBUWjogw41uZGljZSBxdWUgY29tcGFyYSBlbCBwZXNvIGRlbCBtZW5vciBjb24gZWwgcGVzbyBlc3BlcmFkbyBwYXJhIHN1IHRhbGxhIHkgcGVybWl0ZSBlc3RhYmxlY2VyIHNpIGhhIG9jdXJyaWRvIHVuYSBww6lyZGlkYS9nYW5hbmNpYSBkZSBwZXNvIGNvcnBvcmFsLg0KKlpURTogw41uZGljZSBxdWUgY29tcGFyYSBsYSB0YWxsYSBkZWwgbWVub3IgY29uIGxhIHRhbGxhIGVzcGVyYWRhIHBhcmEgc3UgZWRhZCB5IHBlcm1pdGUgZXN0YWJsZWNlciBzaSBlc3TDoSBvY3VycmllbmRvIHVuIHJldHJhc28gZW4gZWwgY3JlY2ltaWVudG8uDQoqWlBFOiDDjW5kaWNlIHF1ZSBjb21wYXJhIGVsIHBlc28gZGVsIG1lbm9yIGNvbiBlbCBwZXNvIGVzcGVyYWRvIHBhcmEgc3UgZWRhZCB5IHBlcm1pdGUgZXN0YWJsZWNlciBzaSBlc3TDoSBvY3VycmllbmRvIGRlc251dHJpY2nDs24uDQoqSU1DOiDDjW5kaWNlIGRlIG1hc2EgY29ycG9yYWwNCipIZW1vZ2xvYmluYQ0KKkhlbW9nbG9iaW5hIGFqdXN0YWRhOiBIZW1vZ2xvYmluYSBhanVzdGFkYSBzZWfDum4gbGEgYWx0dXJhIGRlIGxhIGxvY2FsaWRhZCBkZSByZXNpZGVuY2lhIGRlbCBtZW5vci4NCipBbHR1cmFSRU46IE1ldHJvcyBzb2JyZSBlbCBuaXZlbCBkZWwgbWFyIGRlIGxhIGxvY2FsaWRhZCBkZSByZXNpZGVuY2lhIGRlbCBtZW5vci4NCg0KDQojICoqTWV0b2RvbG9nw61hKioNCg0KDQpVdGlsaXphbW9zIGVsIG3DqXRvZG8gQklSQ0ggKEJhbGFuY2VkIGl0ZXJhdGl2ZSByZWR1Y2luZyBhbmQgY2x1c3RlcmluZyB1c2luZyBoaWVyYXJjaGllcykgcGFyYSBkZXRlcm1pbmFyIGdydXBvcyBkZSBuacOxb3MgZGUgYWN1ZXJkbyBjb24gc3VzIGNhcmFjdGVyw61zdGljYXMgbnV0cmljaW9uYWxlcy4gRWwgb2JqZXRpdm8gcHJpbmNpcGFsIGRlIGVzdGEgbWV0b2RvbG9nw61hIGVzIHRyYWJhamFyIGxhIGFncnVwYWNpw7NuIGNvbiBncmFuZGVzIGNhbnRpZGFkZXMgZGUgZGF0b3MgWzFdIGEgdHJhdsOpcyBkZWwgc2lndWllbnRlIHByb2Nlc286DQoNCioJQSBjYWRhIHVubyBkZSBsb3MgY2zDunN0ZXJzIHNlIGxlIGFzaWduYSB1biB2ZWN0b3IgZGUgdmFsb3JlcywgZWwgQ0YgKENsdXN0ZXJpbmcgRmVhdHVyZSkuDQoNCg0KICAtCUNsdXN0ZXJpbmcgRmVhdHVyZTogDQogICAgU2kgc2UgdGllbmUgdW4gc2V0IGRlIE4gZGF0b3MsIGVsIGNsdXN0ZXJpbmcgZmVhdHVyZSBzZSBkZWZpbmUgY29tbzogDQoNCiAgICAgICAgICAgICRDRj0oTixcYmFye0xTfSxTUykkDQoNCkRvbmRlOg0KDQogICRcYmFye0xTfT1cc3VtX3tpPTB9Xm5cYmFye1hfaX0kDQoNCiAgJFNTPVxzdW1fe2k9MH1eblxiYXJ7WF9pfV4yJA0KDQoNCiogU2kgc2UgdGllbmVuIGxvcyBkYXRvcyAkKHhfMSk7KHhfMik7KHhfMyk7KHhfNCkkDQoNCiAgLQlFbCB2YWxvciBOIHNlcsOhIGlndWFsIGEgNA0KICANCiAgLQlFbCB2YWxvciBkZSAkXGJhcntMU30kIGVzIGlndWFsIGEgICQoeF8xK3hfMit4XzMreF80KSQNCiAgDQogIC0gRWwgdmFsb3IgZGUgJFNTJCBzZXLDoSBpZ3VhbCBhICAkKHhfMV4yK3hfMl4yK3hfM14yK3hfNF4yKSQNCiAgDQogIC0JRGUgZXN0YSBmb3JtYSBhbCBmaW5hbCBsb3MgcHVudG9zIGRlIG11ZXN0cmEgZXN0w6FuIHJlcHJlc2VudGFkb3MgY29tbyAkQ0Y9KE4sXGJhcntMU30sU1MpJA0KDQoNCioJTHVlZ28gZGUgY29uc3RydXllIHVuIMOhcmJvbCBkZSBjbMO6c3RlcnMgZW4gb3JkZW4gamVyw6FycXVpY28gZGUgdGFsIG1vZG8gcXVlIGxhcyBob2phcyBwcmV2aWFzIHJlcHJlc2VudGFuIGNsw7pzdGVycyBkZSBtYXlvciB0YW1hw7FvLCB5IHF1ZSBsYSBkaXZpc2nDs24gZGUgZXN0b3MgcHJvZHV6Y2FuIGEgc3UgdmV6IG51ZXZvcyBub2RvcyAoY2zDunN0ZXJzKSBkZSBtZW5vciB0YW1hw7FvLg0KDQoqCUVsIHByb2NlZGltaWVudG8gY3VsbWluYSB1bmEgdmV6IHF1ZSBsb3MgY2zDunN0ZXJzIGFsY2FuY2VuIHVuIHRhbWHDsW8gaW5mZXJpb3IgYSB1biBwYXLDoW1ldHJvIGRhZG8gbyBzZSBsb2dyZSBlbCBuw7ptZXJvIGRlIGNsw7pzdGVycyBkZXNlYWRvIFsyXS4gIA0KDQoqIEVsIGFsZ29yaXRtbyBCaXJjaCBjdWVudGEgY29uIGRvcyBwcmluY2lwYWxlcyBwYXLDoW1ldHJvcw0KICANCiAgLSAgIEJyYW5jaGluZyBmYWN0b3IgKEIpOiBDYW50aWRhZCBtw6F4aW1hIGRlIHN1YmNsw7pzdGVycyBlbiBjYWRhIG5vZG8sIGVuIGNhc28gZGUgICAgICAgICAgIHN1cGVyYXIgZXN0ZSBmYWN0b3IgZWwgbm9kbyBzZSBkaXZpZGUgZW4gZG9zIG5vZG9zIGNvbiBsb3Mgc3ViY2zDunN0ZXJzICAgICAgICAgICAgICAgICAgcmVkaXN0cmlidWlkb3MuIA0KICANCiAgLQlUaHJlc2hvbGQgKFQpOiBFcyBlbCB1bWJyYWwgZXN0YWJsZWNpZG8gcGFyYSBsYSBjcmVhY2nDs24gZGUgdW4gbnVldm8gc3ViY2zDunN0ZXIuIEVzdGFibGVjZXIgZXN0ZSB2YWxvciBiYWpvIGdlbmVyYSBsYSBjcmVhY2nDs24gZGUgbcOhcyBjbMO6c3RlcnMuDQogIA0KQWxnb3JpdG1vDQpDb25zaWRlcmFuZG8gbG9zIHNpZ3VpZW50ZXMgdmFsb3JlczogDQoNCk4JUHVudG9zDQoxCTIyDQoyCTkNCjMJMTINCjQJMTUNCg0KICAxLglTZSBlbGlnZSBlbCBUcmVzaG9sZCAoNSkuDQogIDIuCVNlIGVsaWdlIGVsIEJyYW5jaGluZyBmYWN0b3IgKDMpLg0KICAzLglTZSB0b21hIGVsIHByaW1lciB2YWxvciB5IHNlIGVsZXZhIGFsIGN1YWRyYWRvLg0KICA0LglTZSBlbGV2YSBhbCBjdWFkcmFkbyBhbWJvcyB2YWxvcmVzIHkgc2Ugc3VtYS4gDQoNClZhbG9yZXMJTglTdW1hCUN1YWRyYWRvDQoyMgkxCTIyCTQ4NA0KOQkyCTMxCTU2NQ0KDQogIDUuCUVsIGNlbnRyb2lkZSBlcyBpZ3VhbCBhIGxhIHN1bWEgZGUgbG9zIHZhbG9yZXMgc29icmUgTiwgcGFyYSBlbCBwcmltZXIgDQogICAgICB2YWxvciBlbCBjZW50cm9pZGUgc2Vyw61hOg0KICAgICAgICAgICAgICAgICAgICAgIENlbnRyb2lkZSA9IDIyLzEgPSAyMg0KICA2LglTZSBhcGxpY2EgbGEgZsOzcm11bGENCiAgDQogIC4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uLi4uDQogIA0KICBFbnRvbmNlcyBEID0gMTMsIHF1ZSBlcyBzdXBlcmlvciBhbCB0cmVzaG9sZCwgcG9yIGxvIHRhbnRvLCBzZSBjcmVhIHVuIG51ZXZvIGNsw7pzdGVyDQogIA0KICA3LglUcmFiYWphbmRvIGVsIHNpZ3VpZW50ZSB2YWxvcjoNCiANCiAgVmFsb3JlcwlOCVN1bWEJQ3VhZHJhZG8NCgkxCTkJODENCiAxMgkyCTIxCTIyNQ0KIA0KICA4LglTZSB2dWVsdmUgYSBhcGxpY2FyIGxhIGbDs3JtdWxhLCB5IHNlIG9idGllbmUgRCA9IDMsIHBvciBsbyB0YW50bywgZWwgdGVyY2VyIHZhbG9yIHNlIGRlc2lnbmEgYWwgY2zDunN0ZXIgY29uIGVsIGNlbnRyb2lkZSBtw6FzIGNlcmNhbm8sIGVsIGN1YWwgZXMgZWwgc2VndW5kbyBjbMO6c3RlciB5YSBxdWUgMTIgZXN0w6EgbcOhcyBjZXJjYSBkZSA5IHF1ZSBkZSAyMi4NCiAgDQogIEZhbm55IFJhbWFkaGFuaSBldCBhbCAyMDIwIElPUCBDb25mLiBTZXIuOiBNYXRlci4gU2NpLiBFbmcuIDcyNSAwMTIwOTANCiAgQWxvbnNvIGRlbCBTYXNvLCBKYXZpZXIg4oCcTcOpdG9kb3MgZGUgZGV0ZWNjacOzbiBkZSBhbm9tYWzDrWFzIHkgY2x1c3RlcmluZyBlbiBzZXJpZXMgdGVtcG9yYWxlc+KAnSAyMDIwLCAgICAgICAgICAgU2FudGFuZGVyLCBFc3Bhw7FhLiANCg0KIA0KIA0KDQojICoqQ29uZmlndXJhY2nDs24gaW5pY2lhbCoqDQoNCmBgYHtyfQ0KI0NvbmZpZ3VyYWNpw7NuIGRlbCBlbnRvcm5vDQpybShsaXN0ID0gbHMoKSkNCnNldHdkKGRpcm5hbWUocnN0dWRpb2FwaTo6Z2V0QWN0aXZlRG9jdW1lbnRDb250ZXh0KCkkcGF0aCkpDQpncmFwaGljcy5vZmYoKQ0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpICANCm9wdGlvbnMoZGlnaXRzID0gMykgDQpwYXRoX3B5dGhvbj0iQzovVXNlcnMvYWR3aW4vQW5hY29uZGEzIg0KI0NhcmdhbmRvIHBhcXVldGVzIG5lY2VzYXJpb3MNCmxpYnJhcnkocGFjbWFuKQ0KcF9sb2FkKHJldGljdWxhdGUsIFBlcmZvcm1hbmNlQW5hbHl0aWNzLCBwdXJyciwgc2tpbXIsIGNvcnJwbG90LCBjbHVzdGVyLCBwc3ljaCwgZ2dwbG90MiwgDQogICAgICAgc3RyZWFtLCBlbGxpcHNlLCB0aWN0b2MsZmFjdG9leHRyYSwgTmJDbHVzdCwgQmlvY01hbmFnZXIsIG5hbmlhciwgRGF0YUV4cGxvcmVyLCANCiAgICAgICB0aWR5dmVyc2UsIHB1cnJyLCBkcGx5ciwgcmVhZHhsLCByZWFkcixzdGF0cywgRGVzY1Rvb2xzLCBjbGFzcyxkZXZ0b29scyxpbWFnZXIsa25pdHIsa2FibGVFeHRyYSkjc291cmNlLGNvbXBhcmVHcm91cHMgDQojQ2FyZ2FuZG8gZnVuY2lvbmVzIGRlIHVzdWFyaW8NCnNvdXJjZSgiZnVuY2lvbmVzLlIiKQ0KYGBgDQoNCiMgICoqTGVjdHVyYSBkZSBkYXRvcyAqKg0KDQpgYGB7cn0NCiMgQ2FyZ2Ftb3MgbGEgZGF0YQ0KYXlhY3VjaG8gPC0gcmVhZC5jc3YoIkRhdGEuY3N2IikNCmhlYWRfNT1oZWFkKGF5YWN1Y2hvKQ0Ka2FibGUoaGVhZF81LGNhcHRpb24gPSAiQXlhY3VjaG8iKSAlPiUga2FibGVfc3R5bGluZygic3RyaXBlZCIpICU+JSBzY3JvbGxfYm94KHdpZHRoID0gIjEwMCUiKQ0KYGBgDQoNCmBgYHtyfQ0Kc3RyKGF5YWN1Y2hvKQ0KI3NraW0oYXlhY3VjaG8pDQojZGVzY3JpYmUoYXlhY3VjaG8pDQpgYGANCg0KIyAqKlByZXByb2Nlc2FtaWVudG8gZGUgZGF0b3MqKg0KDQpgYGB7cn0NCiMgRmFjdG9yaXphY2nDs24gZGUgdmFyaWFibGVzIG5vIG7Dum1lcmljYXMNCnZhcmlhYmxlc19mYWMgPC0gYygiRGlyZXNhIiwgIk1pY3JvcmVkIiwgIkVFU1MiLCAiRHB0b19FRVNTIiwgIlByb3ZfRUVTUyIsICJEaXN0X0VFU1MiLCAiUmVuaXByZXNzIiwgIlNleG8iLCAiSnVudG9zIiwgIlNJUyIsICJRYWxpd2FybWEiLCAiQ3JlZCIsICJTdXBsZW1lbnRhY2lvbiIsICJDb25zZWplcmlhIiwgIlNlc2lvbiIpDQpheWFjdWNob1ssdmFyaWFibGVzX2ZhY10gPC0gbGFwcGx5KGF5YWN1Y2hvWyx2YXJpYWJsZXNfZmFjXSwgZmFjdG9yKQ0KI3N0cihheWFjdWNobykgY2FtYmlvDQpgYGANCg0KDQojIyBEYXRvcyBQZXJkaWRvcw0KDQpgYGB7cn0NCmF5YSA8LWF5YWN1Y2hvWywxNDoyM10NCnBsb3RfbWlzc2luZyhheWEpDQpgYGANCg0KIyMgQW5hbGlzaXMgZGUgT3V0bGllcnMNCg0KYGBge3J9DQpnZ19ib3hfZGVuc2l0eShheWEpDQpgYGANCg0KIyMgVHJhdGFtaWVudG8gZGUgZGF0b3MgT3V0bGllcnMNCg0KYGBge3J9DQpjb2xzPWMoJ1Blc28nLCdUYWxsYScsJ0lNQycsJ1BUWicsJ1pURScsJ1pQRScsJ0hlbW9nbG9iaW5hJywnQWx0dXJhUkVOJywnSGVtb2dsb2JpbmFhanVzdGFkYScpDQpheWE9Zm5fb3V0bGllcnMoYXlhLGNvbHMsMiwxLjUpDQpgYGANCg0KYGBge3J9DQpnZ19ib3hfZGVuc2l0eV8yKGF5YSkNCmBgYA0KDQojIyBDb3JyZWxhY2nDs24NCg0KYGBge3J9DQojY29yIDwtIGF5YWN1Y2hvWywxNDoyM10lPiUgY2hhcnQuQ29ycmVsYXRpb24oaGlzdG9ncmFtPVRSVUUsIHBjaD0xNSkgDQpjb3JyZWxhY2lvbjwtcm91bmQoY29yKGF5YWN1Y2hvWywxNDoyM10pLCAyKQ0KY29ycnBsb3QoY29ycmVsYWNpb24sIG1ldGhvZD0ibnVtYmVyIiwgdHlwZT0idXBwZXIiLG51bWJlci5jZXggPSAwLjcyLHRsLmNleCA9IDAuOCxhZGRDb2VmLmNvbCA9IDAuNSkNCmBgYA0KPCEtLSBCYWNrdXAtLT4NCmBgYHtyIGluY2x1ZGU9RkFMU0V9IA0KYXlhX2JrcDwtIGF5YSANCmBgYA0KDQoNCiMjIEVzdGFuZGFyaXphY2nDs24gZGUgbG9zIGRhdG9zDQoNCmBgYHtyfQ0KYXlhIDwtIGFzLmRhdGEuZnJhbWUoc2NhbGUoYXlhKSkNCmBgYA0KDQoNCiMgKipEZXRlcm1pbmFuZG8gZmFjdGliaWxpZGFkIGRlbCBjbHVzdGVyKioNCg0KIyMgTWF0cml6IGRlIGRpc3RhbmNpYSBldWNsaWRpYW5hDQoNCmBgYHtyfQ0KZGlzLkRhdGEgPC0gZGlzdChheWEsIG1ldHJpYyAgPSBjKCJldWNsaWRlYW4iKSkgIyBNYXRyaXogZGUgZGlzdGFuY2lhICNjYW1iaW8NCiNkaXMuRGF0YSAgICA8LSBkYWlzeShheWEsIG1ldHJpYz0gImV1Y2xpZGVhbiIsc3RhbmQgPSBUUlVFKSAjIE1hdHJpeiBkZSBkaXN0YW5jaWENCmBgYA0KDQojIyBWaXN1YWxpemFuZG8gbGEgbWF0cml6IGRlIGRpc3RhbmNpYSBjb24gZnZpel9kaXN0KCkNCg0KYGBge3J9DQogbGlicmFyeShpbWFnZXIpDQogaWYgIChmaWxlLmV4aXN0cygiZ3JhZl9tYXguZGlzdC5wbmciKSkgeyAgICAgICNDYW1iaW8gIA0KICAgICBpbWcgPC0gbG9hZC5pbWFnZSgnZ3JhZl9tYXguZGlzdC5wbmcnKQ0KICAgICBwbG90KGltZykNCiAgfQ0KI2Vsc2V7DQogICMgZnZpel9kaXN0KGRpcy5EYXRhKQ0KICMgfQ0KYGBgDQoNCiMjIEVzdGFkw61zdGljbyBIb3BraW5zDQoNCmBgYHtyfQ0KcmVfcHJvY2Vzcz1GDQppZiAgKGZpbGUuZXhpc3RzKCJyZXMuSG9wa2lucy5SRGF0YSIpICYgIXJlX3Byb2Nlc3MpIHsgICAgICAjQ2FtYmlvICANCiAgICAgbG9hZCgncmVzLkhvcGtpbnMuUkRhdGEnKSANCn1lbHNlew0KICByZXMuSG9wa2lucyA8LSBnZXRfY2x1c3RfdGVuZGVuY3koYXlhLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgbiA9IG5yb3coYXlhKSAtIDEsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICBncmFwaCA9IEZBTFNFLCBzZWVkID0gMjAyMikNCiAgc2F2ZShyZXMuSG9wa2lucyxmaWxlPSdyZXMuSG9wa2lucy5SRGF0YScpDQp9DQpyZXMuSG9wa2lucyRob3BraW5zX3N0YXQNCmBgYA0KDQojICoqQW7DoWxpc2lzIGNsdXN0ZXIgamVyYXJxdWljbyoqIChoY2x1c3Qge3N0YXRzfSkNCg0KIyMgQW5hbGl6YW5kbyBlbCBtZXRvZG8gZGUgZW5sYWNlIG9wdGltbyAoY29lZmljaWVudGUgZGUgYWdsb21lcmFjacOzbikNCiANCmBgYHtyfQ0KbWV0b2Rvcz0gYygic2luZ2xlIiwgImNvbXBsZXRlIiwgImF2ZXJhZ2UiLCAid2FyZC5EIiwgIndhcmQuRDIiKQ0KQ09FRi5BR0woZGlzLkRhdGEsbWV0b2RvcykNCmBgYA0KDQojIyBBbmFsaXphbmRvIGVsIG1ldG9kbyBkZSBlbmxhY2Ugb3B0aW1vIChtYXRyaXogY29mZW5ldGljYSkNCg0KYGBge3J9DQojRGViaWRvIGEgcXVlIGdyYWZpY2FtZW50ZSBubyBlc3RhbiBtdXkgYWxlamFkb3MgY29uIGNvZWZpY2llbnRlcyBkZSBhZ2xvbWVyYWNpb24sIHByb2JhcmVtb3MgZWwgbWV0b2RvIGRlDQojY29ycmVsYWNpb24gY29mZW5ldGljYSBwYXJhIGRlZmluaXIgZWwgbWVqb3IgbWV0b2RvIGRlIGVubGFjZQ0KcmVfcHJvY2Vzcz1GQUxTRQ0KaWYoZmlsZS5leGlzdHMoImRmX2NvZi5SRGF0YSIpICYgIXJlX3Byb2Nlc3MpIHsgICAgICAjQ2FtYmlvICANCiAgICAgbG9hZCgnZGZfY29mLlJEYXRhJykgDQp9ZWxzZXsNCiAgbWV0b2Rvcz1jKCJ3YXJkLkQiLCAid2FyZC5EMiIsImNvbXBsZXRlIiwgImF2ZXJhZ2UiLCAic2luZ2xlIikNCiAgZGZfY29mPWZuX2NvbmZlbmV0aWNvKGRpcy5EYXRhLG1ldG9kb3MpDQogIHNhdmUoZGZfY29mLGZpbGU9J2RmX2NvZi5SRGF0YScpDQp9DQprYWJsZShkZl9jb2YsY2FwdGlvbiA9ICJDb3JyZWxhY2lvbl9jb2ZlbmV0aWNhIiklPiUga2FibGVfc3R5bGluZygic3RyaXBlZCIpDQpgYGANCg0KDQojIyBPYnRlbmllbmRvIGVsIGNsdXN0ZXIgY29uIGVsIG1ldG9kbzogKipXYXJkLkQqKg0KDQpgYGB7cn0NCkNsdXNfQUdfVyA8LSBoY2x1c3QoZGlzLkRhdGEsbWV0aG9kPSJ3YXJkLkQiKQ0KYGBgDQoNCiMjIERldGVybWluYW5kbyBkZSBtYW5lcmEgZ3JhZmljYSBlbCBudW1lcm8gZGUgY2x1c3RlcnMNCg0KYGBge3J9DQpsaWJyYXJ5KGdnaGlnaGxpZ2h0KQ0KbG9uZ2l0dWQ9bGVuZ3RoKENsdXNfQUdfVyRoZWlnaHQpDQphbHR1cmFzIDwtIGRhdGEuZnJhbWUoZXRhcGEgPSAxOmxvbmdpdHVkLCBkaXN0YW5jaWEgPSBDbHVzX0FHX1ckaGVpZ2h0KQ0KZ2dwbG90KGFsdHVyYXMpICsgYWVzKHggPSBldGFwYSwgeSA9IGRpc3RhbmNpYSkgICsNCiAgZ2VvbV9wb2ludCgpICsgZ2VvbV9saW5lKCkgICsgDQogIHNjYWxlX3hfY29udGludW91cyhicmVha3MgPSBzZXEoMSwgbG9uZ2l0dWQsIDEwMDApKSArIA0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSA3NTk3LCBjb2wgPSAicmVkIiwgbHR5ID0gMikgKyANCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCAgPSByb3VuZChkaXN0YW5jaWEsMSkpLA0KICAgICAgICAgICAgc2l6ZSA9IDMsIGhqdXN0PSArMSwgdmp1c3Q9IC0xKSArDQogIHRoZW1lX2NsYXNzaWMoKSAjKyBnZ2hpZ2hsaWdodChkaXN0YW5jaWEgPiAxMSkNCmBgYA0KPCEtLSBWaXN1YWxtZW50ZSBkZXRlcm1pbmFtb3MgcXVlIHBvZHJpYW1vcyB0cmFiYWphciBjb24gMDIgbyAwMyBjbHVzdGVycy0tPg0KDQojIyBEZXRlcm1pbmFkbyBlbCBuw7ptZXJvIGRlIGNsdXN0ZXJzIG9wdGltbw0KDQpgYGB7cn0gDQpyZV9wcm9jZXNzPUZBTFNFDQppZiAgKGZpbGUuZXhpc3RzKCJyZXMubmJjbHVzdFcuUkRhdGEiKSYgIXJlX3Byb2Nlc3MpIHsgICAgICAjQ2FtYmlvICANCiAgICAgbG9hZCgncmVzLm5iY2x1c3RXLlJEYXRhJykgDQp9ZWxzZXsNCiAgc2VlZCA9IDIwMjINCiAgcmVzLm5iY2x1c3RXIDwtIE5iQ2x1c3QoYXlhLCBkaXN0YW5jZSA9ImV1Y2xpZGVhbiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5uYyA9IDIsIG1heC5uYyA9IDgsIG1ldGhvZCA9ICJ3YXJkLkQiLCBpbmRleCA9ImFsbCIpICNDYW1iaW8NCiAgc2F2ZShyZXMubmJjbHVzdFcsZmlsZT0ncmVzLm5iY2x1c3RXLlJEYXRhJykNCn0NCnBhcihtZnJvdz1jKDEsMSkpICANCmZ2aXpfbmJjbHVzdF94KHJlcy5uYmNsdXN0VykNCmBgYA0KIA0KIyMgUmVhbGl6YW5kbyBjbHVzdGVyaW5nIGNvbiBLPTMNCiAgDQpgYGB7cn0NCiMgQ29ydGFuZG8gZW4gMiBjbHVlc3Rlcg0KZ3JwX1dSPWN1dHJlZShDbHVzX0FHX1csIGsgPSAzKQ0KIyBOdW1iZXIgZGUgY2Fzb3MgZW4gY2FkYSBjbHVzdGVyDQp0YWJsZShncnBfV1IpDQojIERlc2NyaXBjacOzbiBkZSBjYWRhIGNsdXN0ZXINCm1lZDwtYWdncmVnYXRlKGF5YV9ia3AsIGJ5PWxpc3QoY2x1c3Rlcj1ncnBfV1IpLCBtZWFuKSAjbWVkaWFzDQprYWJsZShtZWQpICU+JSBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIikgJT4lIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIpDQoja25pdHI6OmthYmxlKG1lZCwgZm9ybWF0ID0gIm1hcmtkb3duIikNCmBgYA0KDQojIyBDYXJhY3RlcsOtemFuZG8gbG9zIGNsdXN0ZXINCg0KIyMjIEdyYWZpY2FuZG8gbG9zIGNsdXN0ZXJzLg0KDQpgYGB7cn0NCiNmdml6X2RlbmQoQ2x1c19BRywgayA9IDIsIGNleCA9IDAuNywgaG9yaXogPSBGQUxTRSwga19jb2xvcnMgPSAiamNvIiwNCiAgICAgICAgICAjcmVjdCA9IFRSVUUsIHJlY3RfYm9yZGVyID0gImpjbyIsIHJlY3RfZmlsbCA9IFRSVUUpDQpgYGANCg0KIyMjIERpYWdyYW1hIGRlIGNhcmFjdGVyaXphY2nDs24gLSBsaW5lYXMNCg0KYGBge3J9DQpkYXRhX3Bsb3Q9c2NhbGUoYXlhX2JrcCkgI1NjYWxlDQojZGF0YV9wbG90PWFwcGx5KGF5YV9ia3AsIDIsIG5vcm1hbGl6ZSkgI01heC1NaW4NCk08LWFzLmRhdGEuZnJhbWUodChyYmluZChhZ2dyZWdhdGUoZGF0YV9wbG90LCBieT1saXN0KGNsdXN0ZXI9Z3JwX1dSKSwgbWVhbilbLC0xXSkpKQ0KYT1hcy52ZWN0b3IoY29sTWVhbnMoZGF0YV9wbG90KSkNCmZpbj1kYXRhLmZyYW1lKE0sYSxuYW1lcyhheWFfYmtwKSk7bmFtZXMoZmluKTwtYygiQ2x1czEiLCJjbHVzMiIsImNsdXMzIiwiTWVkaWEiLCJ2YXIiKQ0KI2Zpbj1kYXRhLmZyYW1lKE0sYSxuYW1lcyhheWFfYmtwKSk7bmFtZXMoZmluKTwtYygiQ2x1czEiLCJjbHVzMiIsIk1lZGlhIiwidmFyIikNCmFsaT1tZWx0KGZpbixpZC52YXJzID0gInZhciIpDQpnZ3Bsb3QoYWxpLCBhZXMoeD12YXIseT1yb3VuZCh2YWx1ZSwxKSxncm91cD12YXJpYWJsZSxjb2xvdXI9dmFyaWFibGUpKSArDQogIGdlb21fcG9pbnQoKSsgZ2VvbV9saW5lKGFlcyhsdHk9dmFyaWFibGUpKSsgZXhwYW5kX2xpbWl0cyh5ID0gYygtMS45LCAxLjkpKSsNCiAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNjAsIHZqdXN0ID0gMC41LCBoanVzdD0xKSkNCg0KDQpgYGANCg0KIyMjIERpYWdyYW1hIGRlIGNhcmFjdGVyaXphY2nDs24gLSBib3hwbG90DQoNCmBgYHtyfQ0KZGQgPC0gY2JpbmQoYXlhX2JrcCwgY2x1c3RlciA9Z3JwX1dSICkNCmRkJGNsdXN0ZXI8LWFzLmZhY3RvcihkZCRjbHVzdGVyKQ0KZGYubSA8LSBtZWx0KGRkLCBpZC52YXIgPSAiY2x1c3RlciIpDQpwIDwtIGdncGxvdChkYXRhID0gZGYubSwgYWVzKHg9dmFyaWFibGUsIHk9dmFsdWUpKSArIA0KICBnZW9tX2JveHBsb3QoYWVzKGZpbGw9Y2x1c3RlcikpKyBmYWNldF93cmFwKCB+IHZhcmlhYmxlLCBzY2FsZXM9ImZyZWUiKSANCnANCmBgYA0KDQojIyBPYnRlbmllbmRvIGVsIGNsdXN0ZXIgY29uIGVsIG1ldG9kbzogKiphdmVyYWdlKioNCg0KYGBge3J9DQpDbHVzX0FHX0FWIDwtIGhjbHVzdChkaXMuRGF0YSxtZXRob2Q9ImF2ZXJhZ2UiKQ0KYGBgDQoNCiMjIERldGVybWluYW5kbyBkZSBtYW5lcmEgZ3JhZmljYSBlbCBudW1lcm8gZGUgY2x1c3RlcnMNCg0KYGBge3J9DQpsaWJyYXJ5KGdnaGlnaGxpZ2h0KQ0KbG9uZ2l0dWQ9bGVuZ3RoKENsdXNfQUdfQVYkaGVpZ2h0KQ0KYWx0dXJhcyA8LSBkYXRhLmZyYW1lKGV0YXBhID0gMTpsb25naXR1ZCwgZGlzdGFuY2lhID0gQ2x1c19BR19BViRoZWlnaHQpDQpnZ3Bsb3QoYWx0dXJhcykgKyBhZXMoeCA9IGV0YXBhLCB5ID0gZGlzdGFuY2lhKSAgKw0KICBnZW9tX3BvaW50KCkgKyBnZW9tX2xpbmUoKSAgKyANCiAgc2NhbGVfeF9jb250aW51b3VzKGJyZWFrcyA9IHNlcSgxLCBsb25naXR1ZCwgMTAwMCkpICsgDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDc1OTcsIGNvbCA9ICJyZWQiLCBsdHkgPSAyKSArIA0KICBnZW9tX3RleHQoYWVzKGxhYmVsICA9IHJvdW5kKGRpc3RhbmNpYSwxKSksDQogICAgICAgICAgICBzaXplID0gMywgaGp1c3Q9ICsxLCB2anVzdD0gLTEpICsNCiAgdGhlbWVfY2xhc3NpYygpICMrIGdnaGlnaGxpZ2h0KGRpc3RhbmNpYSA+IDExKQ0KYGBgDQo8IS0tIFZpc3VhbG1lbnRlIGRldGVybWluYW1vcyBxdWUgcG9kcmlhbW9zIHRyYWJhamFyIGNvbiAwMiBvIDAzIGNsdXN0ZXJzLS0+DQoNCiMjIERldGVybWluYWRvIGVsIG7Dum1lcm8gZGUgY2x1c3RlcnMgb3B0aW1vDQoNCmBgYHtyfSANCnJlX3Byb2Nlc3M9RkFMU0UNCmlmICAoZmlsZS5leGlzdHMoInJlcy5uYmNsdXN0QVYuUkRhdGEiKSYgIXJlX3Byb2Nlc3MpIHsgICAgICAjQ2FtYmlvICANCiAgICAgbG9hZCgncmVzLm5iY2x1c3RBVi5SRGF0YScpIA0KfWVsc2V7DQogIHNlZWQgPSAyMDIyDQogIHJlcy5uYmNsdXN0QVYgPC0gTmJDbHVzdChheWEsIGRpc3RhbmNlID0iZXVjbGlkZWFuIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgbWluLm5jID0gMiwgbWF4Lm5jID0gOCwgbWV0aG9kID0gImF2ZXJhZ2UiLCBpbmRleCA9ImFsbCIpICNDYW1iaW8NCiAgc2F2ZShyZXMubmJjbHVzdEFWLGZpbGU9J3Jlcy5uYmNsdXN0QVYuUkRhdGEnKQ0KfQ0KcGFyKG1mcm93PWMoMSwxKSkgIA0KZnZpel9uYmNsdXN0X3gocmVzLm5iY2x1c3RBVikNCmBgYA0KIA0KIyMgUmVhbGl6YW5kbyBjbHVzdGVyaW5nIGNvbiBLPTINCiAgDQpgYGB7cn0NCiMgQ29ydGFuZG8gZW4gMiBjbHVlc3Rlcg0KZ3JwX0FWPWN1dHJlZShDbHVzX0FHX0FWLCBrID0gMikNCiMgTnVtYmVyIGRlIGNhc29zIGVuIGNhZGEgY2x1c3Rlcg0KdGFibGUoZ3JwX0FWKQ0KIyBEZXNjcmlwY2nDs24gZGUgY2FkYSBjbHVzdGVyDQptZWQ8LWFnZ3JlZ2F0ZShheWFfYmtwLCBieT1saXN0KGNsdXN0ZXI9Z3JwX0FWKSwgbWVhbikgI21lZGlhcw0Ka2FibGUobWVkKSAlPiUga2FibGVfc3R5bGluZygic3RyaXBlZCIpICU+JSBzY3JvbGxfYm94KHdpZHRoID0gIjEwMCUiKQ0KI2tuaXRyOjprYWJsZShtZWQsIGZvcm1hdCA9ICJtYXJrZG93biIpDQpgYGANCg0KIyMgQ2FyYWN0ZXLDrXphbmRvIGxvcyBjbHVzdGVyDQoNCiMjIyBHcmFmaWNhbmRvIGxvcyBjbHVzdGVycy4NCg0KYGBge3J9DQojZnZpel9kZW5kKENsdXNfQUcsIGsgPSAyLCBjZXggPSAwLjcsIGhvcml6ID0gRkFMU0UsIGtfY29sb3JzID0gImpjbyIsDQogICAgICAgICAgI3JlY3QgPSBUUlVFLCByZWN0X2JvcmRlciA9ICJqY28iLCByZWN0X2ZpbGwgPSBUUlVFKQ0KYGBgDQoNCiMjIyBEaWFncmFtYSBkZSBjYXJhY3Rlcml6YWNpw7NuIC0gbGluZWFzDQoNCmBgYHtyfQ0KZGF0YV9wbG90PXNjYWxlKGF5YV9ia3ApICNTY2FsZQ0KI2RhdGFfcGxvdD1hcHBseShheWFfYmtwLCAyLCBub3JtYWxpemUpICNNYXgtTWluDQpNPC1hcy5kYXRhLmZyYW1lKHQocmJpbmQoYWdncmVnYXRlKGRhdGFfcGxvdCwgYnk9bGlzdChjbHVzdGVyPWdycF9BViksIG1lYW4pWywtMV0pKSkNCmE9YXMudmVjdG9yKGNvbE1lYW5zKGRhdGFfcGxvdCkpDQojZmluPWRhdGEuZnJhbWUoTSxhLG5hbWVzKGF5YV9ia3ApKTtuYW1lcyhmaW4pPC1jKCJDbHVzMSIsImNsdXMyIiwiY2x1czMiLCJNZWRpYSIsInZhciIpDQpmaW49ZGF0YS5mcmFtZShNLGEsbmFtZXMoYXlhX2JrcCkpO25hbWVzKGZpbik8LWMoIkNsdXMxIiwiY2x1czIiLCJNZWRpYSIsInZhciIpDQphbGk9bWVsdChmaW4saWQudmFycyA9ICJ2YXIiKQ0KZ2dwbG90KGFsaSwgYWVzKHg9dmFyLHk9cm91bmQodmFsdWUsMSksZ3JvdXA9dmFyaWFibGUsY29sb3VyPXZhcmlhYmxlKSkgKw0KICBnZW9tX3BvaW50KCkrIGdlb21fbGluZShhZXMobHR5PXZhcmlhYmxlKSkrIGV4cGFuZF9saW1pdHMoeSA9IGMoLTEuOSwgMS45KSkrDQogICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDYwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpDQoNCg0KYGBgDQoNCiMjIyBEaWFncmFtYSBkZSBjYXJhY3Rlcml6YWNpw7NuIC0gYm94cGxvdA0KDQpgYGB7cn0NCmRkIDwtIGNiaW5kKGF5YV9ia3AsIGNsdXN0ZXIgPWdycF9BViApDQpkZCRjbHVzdGVyPC1hcy5mYWN0b3IoZGQkY2x1c3RlcikNCmRmLm0gPC0gbWVsdChkZCwgaWQudmFyID0gImNsdXN0ZXIiKQ0KcCA8LSBnZ3Bsb3QoZGF0YSA9IGRmLm0sIGFlcyh4PXZhcmlhYmxlLCB5PXZhbHVlKSkgKyANCiAgZ2VvbV9ib3hwbG90KGFlcyhmaWxsPWNsdXN0ZXIpKSsgZmFjZXRfd3JhcCggfiB2YXJpYWJsZSwgc2NhbGVzPSJmcmVlIikgDQpwDQpgYGANCg0KIyMjIFVzYW5kbyBlbCBpbmRpY2UgZGUgcmFuZCBwYXJhIGRlZmluaXIgZWwgY2x1c3RlciBvcHRpbW8NCg0KYGBge3J9DQpsaWJyYXJ5KGZvc3NpbCkNCnJhbmQuaW5kZXgoZ3JwX1dSLGdycF9BVikNCmBgYA0KU2VndW4gZWwgaW5kaWNlIGRlIFJhbmQsIG5vIHNlIHBvZHJpYSBjb21wcm9iYXIgcXVlIGxhcyBzb2x1Y2lvbmVzICh3YXJkLkQgeSBhdmVyYWdlKSBjbHVzdGVycyBzb24gcGFyZWNpZG9zLCBwb3IgZXNlIG1vdGl2b3MgZWxlZ2lyZW1vcyBlbCBxdWUgdGllbmUgbWF5b3Igc2VycGFyYWPDs24gYSBuaXZlbCBncmFmaWNvIGRlbCBsaW5lYXMuDQoNCiMgKipBbsOhbGlzaXMgY2x1c3RlcjogSyDigJMgTWVhbnMqKg0KDQojIyBEZXRlcm1pbmFuZG8gbsO6bWVybyDDs3B0aW1vIGRlIGNsdXN0ZXJzDQoNCg0KDQojIyMgU2lsaG91ZXR0ZSBtZXRob2QNCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQpyZV9wcm9jZXNzPUZBTFNFDQppZiAgKGZpbGUuZXhpc3RzKCJnZ19zaWwuUkRhdGEiKSAmICFyZV9wcm9jZXNzKSB7ICAgICAgI0NhbWJpbyAgDQogICAgIGxvYWQoJ2dnX3NpbC5SRGF0YScpIA0KfWVsc2V7DQogICBnZ19zaWw9ZnZpel9uYmNsdXN0KGF5YSwga21lYW5zLCBtZXRob2QgPSAic2lsaG91ZXR0ZSIpKw0KICAgICAgICAgIGxhYnMoc3VidGl0bGUgPSAiU2lsaG91ZXR0ZSBtZXRob2QiKQ0KICAgc2F2ZShnZ19zaWwsZmlsZT0nZ2dfc2lsLlJEYXRhJykNCn0NCmdnX3NpbA0KYGBgDQoNCg0KIyMgUmVhbGl6YW5kbyBjbHVzdGVyaW5nIGNvbiBLPTMNCg0KYGBge3J9DQprbS5yZXMgPC0ga21lYW5zKGF5YSwgY2VudGVycz0zLG5zdGFydCA9IDI1KQ0KZ3JwX2ttPWttLnJlcyRjbHVzdGVyDQp0YWJsZShncnBfa20pDQojIERlc2NyaXBjacOzbiBkZSBjYWRhIGNsdXN0ZXINCm1lZDwtYWdncmVnYXRlKGF5YV9ia3AsIGJ5PWxpc3QoY2x1c3Rlcj1ncnBfa20pLCBtZWFuKQ0Ka2FibGUobWVkKSAlPiUga2FibGVfc3R5bGluZygic3RyaXBlZCIpICU+JSBzY3JvbGxfYm94KHdpZHRoID0gIjEwMCUiKQ0KI2tuaXRyOjprYWJsZShtZWQsIGZvcm1hdCA9ICJtYXJrZG93biIpDQpgYGANCg0KIyMgQ2FyYWN0ZXLDrXphbmRvIGxvcyBjbHVzdGVyDQoNCiMjIyBHcmFmaWNhbmRvIGxvcyBjbHVzdGVycy4NCg0KYGBge3J9DQojZnZpel9jbHVzdGVyKGttLnJlcywgZGF0YSA9IGF5YSwNCiAgICAgICAgICAgICMgcGFsZXR0ZSA9ICJqY28iLA0KICAgICAgICAgICAgICNlbGxpcHNlLnR5cGUgPSAiZXVjbGlkIiwgIyBDb25jZW50cmF0aW9uIGVsbGlwc2UNCiAgICAgICAgICAgICAjc3Rhci5wbG90ID0gVFJVRSwgIyBBZGQgc2VnbWVudHMgZnJvbSBjZW50cm9pZHMgdG8gaXRlbXMNCiAgICAgICAgICAgICAjcmVwZWwgPSBUUlVFLCAjIEF2b2lkIGxhYmVsIG92ZXJwbG90dGluZyAoc2xvdykNCiAgICAgICAgICAgICAjZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSkNCmBgYA0KDQojIyMgRGlhZ3JhbWEgZGUgY2FyYWN0ZXJpemFjacOzbiAtIGxpbmVhcw0KDQpgYGB7cn0NCmRhdGFfcGxvdD1zY2FsZShheWFfYmtwKSAjU2NhbGUNCiNkYXRhX3Bsb3Q9YXBwbHkoYXlhX2JrcCwgMiwgbm9ybWFsaXplKSAjTWF4LU1pbg0KTTwtYXMuZGF0YS5mcmFtZSh0KHJiaW5kKGFnZ3JlZ2F0ZShkYXRhX3Bsb3QsIGJ5PWxpc3QoY2x1c3Rlcj1ncnBfa20pLCBtZWFuKVssLTFdKSkpDQphPWFzLnZlY3Rvcihjb2xNZWFucyhkYXRhX3Bsb3QpKQ0KZmluPWRhdGEuZnJhbWUoTSxhLG5hbWVzKGF5YV9ia3ApKTtuYW1lcyhmaW4pPC1jKCJDbHVzMSIsImNsdXMyIiwiY2x1czMiLCJNZWRpYSIsInZhciIpDQojZmluPWRhdGEuZnJhbWUoTSxhLG5hbWVzKGF5YV9ia3ApKTtuYW1lcyhmaW4pPC1jKCJDbHVzMSIsImNsdXMyIiwiTWVkaWEiLCJ2YXIiKQ0KYWxpPW1lbHQoZmluLGlkLnZhcnMgPSAidmFyIikNCmdncGxvdChhbGksIGFlcyh4PXZhcix5PXJvdW5kKHZhbHVlLDEpLGdyb3VwPXZhcmlhYmxlLGNvbG91cj12YXJpYWJsZSkpICsNCiAgZ2VvbV9wb2ludCgpKyBnZW9tX2xpbmUoYWVzKGx0eT12YXJpYWJsZSkpKyBleHBhbmRfbGltaXRzKHkgPSBjKC0xLjksIDEuOSkpKw0KICAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA2MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQ0KYGBgDQoNCiMjIyBEaWFncmFtYSBkZSBjYXJhY3Rlcml6YWNpw7NuIC0gYm94cGxvdA0KDQpgYGB7cn0NCmRkIDwtIGNiaW5kKGF5YV9ia3AsIGNsdXN0ZXIgPWdycF9rbSApDQpkZCRjbHVzdGVyPC1hcy5mYWN0b3IoZGQkY2x1c3RlcikNCmRmLm0gPC0gbWVsdChkZCwgaWQudmFyID0gImNsdXN0ZXIiKQ0KcCA8LSBnZ3Bsb3QoZGF0YSA9IGRmLm0sIGFlcyh4PXZhcmlhYmxlLCB5PXZhbHVlKSkgKyANCiAgZ2VvbV9ib3hwbG90KGFlcyhmaWxsPWNsdXN0ZXIpKSsgZmFjZXRfd3JhcCggfiB2YXJpYWJsZSwgc2NhbGVzPSJmcmVlIikgDQpwDQpgYGANCg0KIyAqKkFuw6FsaXNpcyBjbHVzdGVyIEJpcmNoIChyZXRpY3VsYXRlKSoqDQoNCiMjIENvbmZpZ3VyYW5kbyBlbnRvcm5vIGRlIGRlc2Fycm9sbG8NCg0KYGBge3IgfQ0KbGlicmFyeShyZXRpY3VsYXRlKQ0KdXNlX3B5dGhvbihwYXRoX3B5dGhvbikNCmF5YV9wID0gcl90b19weShheWEpICNyLmF5YQ0KYGBgDQoNCiMjIENhcmdhbmRvIGxpYnJlcmlhcyBkZSBweXRob24gZW4gUg0KDQpgYGB7cHl0aG9ufQ0KaW1wb3J0IHBhbmRhcyBhcyBwZCANCmZyb20gc2tsZWFybi5wcmVwcm9jZXNzaW5nIGltcG9ydCBTdGFuZGFyZFNjYWxlciANCmZyb20gc2tsZWFybi5jbHVzdGVyIGltcG9ydCBCaXJjaCANCmBgYA0KDQojIyBEZWZpbmllbmRvIEJpcmNoIGNvbjogdGhyZXNob2xkPTEsIGJyYW5jaGluZ19mYWN0b3I9Mg0KYGBge3B5dGhvbn0NCmRmID0gci5heWFfcCMgT2J0ZW5pZW5kbyBvYmpldG8gUiB0byBweXRob24NCmJyYyA9IEJpcmNoKHRocmVzaG9sZD0wLjQyLGJyYW5jaGluZ19mYWN0b3I9NTAsbl9jbHVzdGVycz0zKQ0KYnJjLmZpdChkZikNCmBgYA0KDQojIyBSZWFsaXphbmRvIHByZWRpY2Npw7NuDQoNCmBgYHtweXRob259DQpkYXRhX3RvdGFsID0gcGQuY29uY2F0KFtkZixwZC5EYXRhRnJhbWUoYnJjLnByZWRpY3QoZGYpKV0sYXhpcz0xKQ0KZGF0YV90b3RhbC5oZWFkKCkNCmBgYA0KDQojIyBQcm9wb3JjaW9uZXMgcG9yIGNhZGEgY2x1c3Rlcg0KDQpgYGB7cHl0aG9ufQ0KZGF0YV90b3RhbC5ncm91cGJ5KDApWydQZXNvJ10uY291bnQoKQ0KYGBgDQoNCiMjIEd1YXJkYW1vcyBlbCBvYmpldG8gY29uIENsdXN0ZXIgQmlyY2gNClBhcmEgbHVlZ28gc3ViaXJsbyBhbCBSDQpgYGB7cHl0aG9ufQ0KZGF0YV90b3RhbC50b19leGNlbCgnYmlyY2gueGxzeCcpDQpgYGANCg0KIyMgTGVlbW9zIGVsIG9iamV0byBjb24gQ2x1c3RlciBCaXJjaCBlbiBSDQpgYGB7cn0NCmJpIDwtIHJlYWRfZXhjZWwoImJpcmNoLnhsc3giKQ0KYmlyY2ggPC0gYXMuZGF0YS5mcmFtZShiaVssMjoxMl0pDQpuYW1lcyAoYmlyY2gpWzExXSA9ICJjbHVzdGVyIg0KYmlyY2gkY2x1c3RlciA8LSBmYWN0b3IoYmlyY2gkY2x1c3RlciwgbGV2ZWxzID0gYygwLDEsMiksIGxhYmVscyA9IGMoMSwyLDMpKQ0KDQpoZWFkKGJpcmNoKQ0KYGBgDQoNCg0KIyMgQ2FyYWN0ZXLDrXphbmRvIGxvcyBjbHVzdGVyDQpgYGB7cn0NCmRhdGFfcGxvdD1iaXJjaFsxOjEwXQ0KY2x1c3Rlcj1iaXJjaCRjbHVzdGVyDQp0YWJsZShjbHVzdGVyKQ0KIyBEZXNjcmlwY2nDs24gZGUgY2FkYSBjbHVzdGVyDQptZWQ8LWFnZ3JlZ2F0ZShheWEsIGJ5PWxpc3QoY2x1c3Rlcj1jbHVzdGVyKSwgbWVhbikNCg0KbWVkDQoNCiMga25pdHI6OmthYmxlKG1lZCkgJT4lIGthYmxlX3N0eWxpbmcoInN0cmlwZWQiKSAlPiUgc2Nyb2xsX2JveCh3aWR0aCA9ICIxMDAlIikNCg0KYGBgDQoNCiMjIERpYWdyYW1hIGRlIGNhcmFjdGVyaXphY2nDs24gLSBsaW5lYXMNCg0KYGBge3J9DQoNCk08LWFzLmRhdGEuZnJhbWUodChyYmluZChhZ2dyZWdhdGUoZGF0YV9wbG90LCBieT1saXN0KGNsdXN0ZXI9Y2x1c3RlciksIG1lYW4pWywtMV0pKSkNCg0KYT1hcy52ZWN0b3IoY29sTWVhbnMoZGF0YV9wbG90KSkNCmZpbj1kYXRhLmZyYW1lKE0sYSxuYW1lcyhheWFfYmtwKSk7bmFtZXMoZmluKTwtYygiQ2x1czEiLCJDbHVzMiIsIkNsdXMzIiwiTWVkaWEiLCJ2YXIiKQ0KDQphbGk9bWVsdChmaW4saWQudmFycyA9ICJ2YXIiKQ0KZ2dwbG90KGFsaSwgYWVzKHg9dmFyLHk9cm91bmQodmFsdWUsMSksZ3JvdXA9dmFyaWFibGUsY29sb3VyPXZhcmlhYmxlKSkgKw0KICBnZW9tX3BvaW50KCkrIGdlb21fbGluZShhZXMobHR5PXZhcmlhYmxlKSkrIGV4cGFuZF9saW1pdHMoeSA9IGMoLTEuOSwgMS45KSkrDQogICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDYwLCB2anVzdCA9IDAuNSwgaGp1c3Q9MSkpDQpgYGANCg0KIyMjIERpYWdyYW1hIGRlIGNhcmFjdGVyaXphY2nDs24gLSBib3hwbG90DQoNCg0KYGBge3J9DQpkZCA8LSBjYmluZChheWFfYmtwLCBjbHVzdGVyID1jbHVzdGVyICkNCmRkJGNsdXN0ZXI8LWFzLmZhY3RvcihkZCRjbHVzdGVyKQ0KZGYubSA8LSBtZWx0KGRkLCBpZC52YXIgPSAiY2x1c3RlciIpDQpwIDwtIGdncGxvdChkYXRhID0gZGYubSwgYWVzKHg9dmFyaWFibGUsIHk9dmFsdWUpKSArIA0KICBnZW9tX2JveHBsb3QoYWVzKGZpbGw9Y2x1c3RlcikpKyBmYWNldF93cmFwKCB+IHZhcmlhYmxlLCBzY2FsZXM9ImZyZWUiKSANCnANCg0KYGBgDQoNCiMgKipWYWxpZGFuZG8gbG9zIGNsdXN0ZXJzKioNCg0KIyMgSW50ZXJuYSAoY29oZXNpw7NuIHkgc2VwYXJhY2nDs24pDQoNCg0KYGBge3J9DQpyZV9wcm9jZXNzPVRSVUUNCiMgaWYgIChmaWxlLmV4aXN0cygiZGZfaW5kZXhzLlJEYXRhIikgJiAhcmVfcHJvY2VzcykgeyAgICAgICNDYW1iaW8gIA0KICAgICAjIGxvYWQoJ2RmX2luZGV4cy5SRGF0YScpIA0KIyB9ZWxzZXsNCiAgICBsaWJyYXJ5KGNsdXN0ZXJTaW0pDQogICAgbGlicmFyeShjbFZhbGlkKQ0KICAgICNJbmRpY2UgZGUgRGF2aWVzLUJvdWxkaW46IGJ1c2NhbW9zIGVsIHZhbG9yIG1hcyBhbHRvIHBvc2libGUNCiAgICBEQmttXyA8LSBpbmRleC5EQihheWEsIGttLnJlcyRjbHVzdGVyLCBjZW50cm90eXBlcyA9ICJjZW50cm9pZHMiKSREQiAja21lYW5zDQogICAgREJIY18gPC1pbmRleC5EQihheWEsIGdycF9XUiwgZD1kaXMuRGF0YSxjZW50cm90eXBlcz0iY2VudHJvaWRzIikkREIgI2hjbHVzdA0KICAgIERCaXJjaF8gPC1pbmRleC5EQihheWEsIGFzLm51bWVyaWMoY2x1c3RlciksIGNlbnRyb3R5cGVzPSJjZW50cm9pZHMiKSREQiAjQmlyY2gNCiAgICANCiAgICAjSW5kaWNlIGRlIGR1bm46IGJ1c2NhbW9zIGVsIHZhbG9yIG1hcyBiYWpvIHBvc2libGUNCiAgICBEbmttXyA8LSBkdW5uKERhdGEgPSBheWEsIGNsdXN0ZXJzID0ga20ucmVzJGNsdXN0ZXIsIGRpc3RhbmNlID0gTlVMTCkja21lYW5zDQogICAgRG5IY18gPC0gZHVubihkaXMuRGF0YSwgZ3JwX1dSKSAjaGNsdXN0DQogICAgRG5CaXJjaF8gPC0gZHVubihEYXRhID0gYXlhLCBjbHVzdGVycyA9IGFzLm51bWVyaWMoY2x1c3RlciksIGRpc3RhbmNlID0gTlVMTCkjQmlyY2gNCiAgICANCiAgICB0aXBvXz1jKCdrbWVhbnMnLCdoY2x1c3QnLCdCaXJjaCcpDQogICAgZGF2aWVzLmJvdWxkaW5fPWMoREJrbV8sREJIY18sREJpcmNoXykNCiAgICBkdW5uXz1jKERua21fLERuSGNfLERuQmlyY2hfKQ0KICAgIGRmX2luZGV4cz1kYXRhLmZyYW1lKHRpcG9fLGRhdmllcy5ib3VsZGluXyxkdW5uXykNCiAgICBjb2xuYW1lcyhkZl9pbmRleHMpPWMoJ0NsdXN0ZXInLCdEYXZpZXMuQm91bGRpbicsJ0R1bm4nKQ0KICAgIHNhdmUoZGZfaW5kZXhzLGZpbGU9J2RmX2luZGV4cy5SRGF0YScpDQojIH0NCiAgICAjIGRmX2luZGV4cw0KIyBrYWJsZShkZl9pbmRleHMpICU+JSBrYWJsZV9zdHlsaW5nKCJzdHJpcGVkIikgJT4lIHNjcm9sbF9ib3god2lkdGggPSAiMTAwJSIpDQpgYGANCg0KDQpgYGB7cn0NCnNpbGhvdWV0dGU8LXJiaW5kKA0KbWVhbihzaWxob3VldHRlKGFzLm51bWVyaWMoa20ucmVzJGNsdXN0ZXIpICxkaXMuRGF0YSlbLDNdKSwgI2ttZWFucw0KbWVhbihzaWxob3VldHRlKGFzLm51bWVyaWMoZ3JwX1dSKSAsZGlzLkRhdGEpWywzXSksICNoY2x1c3QNCm1lYW4oc2lsaG91ZXR0ZShhcy5udW1lcmljKGJpcmNoJGNsdXN0ZXIpICxkaXMuRGF0YSlbLDNdKSAjQmlyY2gNCikNCmBgYA0KDQpgYGB7cn0NCmNiaW5kKGRmX2luZGV4cywgc2lsaG91ZXR0ZSkNCmBgYA0KDQoNCg0KYGBge3J9DQpheWFjdWNob19maW5hbDwtYXlhY3VjaG8NCmF5YWN1Y2hvX2ZpbmFsJEFuZW1pYTwtaWZfZWxzZShheWFjdWNobyRIZW1vZ2xvYmluYWFqdXN0YWRhKjEwPj0xMTAsICdTaW4gYW5lbWlhJywgDQogICAgICAgIGlmX2Vsc2UoYXlhY3VjaG8kSGVtb2dsb2JpbmFhanVzdGFkYSoxMD49MTAwLCdMZXZlJywNCiAgICAgICAgICAgICAgICBpZl9lbHNlKGF5YWN1Y2hvJEhlbW9nbG9iaW5hYWp1c3RhZGEqMTA+PTcwLCdNb2RlcmFkYScsDQogICAgICAgICAgICAgICAgICAgICAgICAnR3JhdmUnKSkpDQogICAgICAgIA0KYXlhY3VjaG9fZmluYWw9Y2JpbmQoYXlhY3VjaG9fZmluYWwsIGNsdXN0ZXI9a20ucmVzJGNsdXN0ZXIpDQoNCnRhYmxlKGF5YWN1Y2hvX2ZpbmFsJEFuZW1pYSkNCiAgICAgDQoNCmBgYA0KDQoNCg0KDQoNCmBgYHtyfQ0KZzFfRiA8LQ0KICBnZ3Bsb3QobXV0YXRlKGF5YWN1Y2hvX2ZpbmFsLCBjbHVzdGVyID0gZmFjdG9yKGNsdXN0ZXIpKSkgKw0KICBhZXMoY2x1c3RlciwgZmlsbCA9QW5lbWlhICkgKw0KICBnZW9tX2JhcihjbHVzdGVyID0gcG9zaXRpb25fZmlsbCgpKSArDQogIGxhYnModGl0bGU9ImNsdXN0ZXIgc2Vnw7puIEFuZW1pYSIsDQogICAgICAgICAgICB4ID0gTlVMTCwgeSA9ICJQcm9wb3JjacOzbiIpICsNCiAgdGhlbWVfYncoKQ0KZzFfRg0KYGBgDQoNCg0KDQoNCmBgYHtyfQ0KZzJfRiA8LQ0KICBnZ3Bsb3QobXV0YXRlKGF5YWN1Y2hvX2ZpbmFsLCBjbHVzdGVyID0gZmFjdG9yKGNsdXN0ZXIpKSkgKw0KICBhZXMoQW5lbWlhLCBmaWxsID1jbHVzdGVyICkgKw0KICBnZW9tX2JhcihBbmVtaWEgPSBwb3NpdGlvbl9maWxsKCkpICsNCiAgbGFicyh0aXRsZT0iQW5lbWlhIHNlZ8O6biBjbHVzdGVyIiwNCiAgICAgICAgICAgIHggPSBOVUxMLCB5ID0gIlByb3BvcmNpw7NuIikgKw0KICB0aGVtZV9saW5lZHJhdygpDQpnMl9GDQpgYGANCiAgDQogIA0KIyAqKkNvbmNsdXNpb25lcyoqDQoNCi0gTGEgZXZhbHVhY2nDs24gdGFudG8gcG9yIEstcHJvdG90eXBlcyBjb21vIGNvbiBsYSBmdW5jacOzbiBOYkNsdXN0IG5vcyBpbmRpY2EgcXVlIGRlYmVtb3MgdHJhYmFqYXIgY29uIDMgZ3J1cG9zLg0KDQotIExhIG1ldG9kb2xvZ8OtYSBBR05FUyB5IEZ1enp5IEMtTWVhbnMgZW4gbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzIGdlbmVyYW4gY2zDunN0ZXJlcyBjb24gdW4gY29tcG9ydGFtaWVudG8gbXV5IHNpbWlsYXIsIHNpbiBlbWJhcmdvLCBlbiBlbCBjYXNvIGRlIGxhcyB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzIHNlIHRpZW5lbiBkaWZlcmVuY2lhcyBzaWduaWZpY2F0aXZhcy4NCg0KLSBTZSBkZXRlcm1pbmEgbWVkaWFudGUgZWwgZ3LDoWZpY28gZGUgcGVyZmlsZXMgcXVlIGxhIGVkYWQgZ2VzdGFjaW9uYWwgbm8gZ2VuZXJhIHVuIG1heW9yIGFwb3J0ZSBhbCBjb21wb3J0YW1pZW50byBkZSBsb3MgZ3J1cG9zLCB5YSBxdWUgZW4gbG9zIHRyZXMgbG9zIHZhbG9yZXMNCg0KDQotIOKAokZpbmFsbWVudGUgY29uc2lkZXJhbmRvIEFHTkVTLCBzZSB0aWVuZToNCkdydXBvIDE6IEdlc3RhbnRlcyBlbiBzdSBtYXlvcsOtYSBkZSBlbWJhcmF6byBzaW1wbGUsIGRlIFJlZ2lvbmVzIEFwdXLDrW1hYywgSHVhbmNhdmVsaWNhIHkgUGFzY28sIHBvciBsbyBxdWUgdGllbmVuIHVuIG5pdmVsIGRlIGFsdGl0dWQgbWF5b3IgeSB1biBuaXZlbCBkZSBoZW1vZ2xvYmluYSBzdXBlcmlvciBhIGxvcyBkZW3DoXMgZ3J1cG9zLiBHcnVwbyAyOiBHZXN0YW50ZXMgZW4gc3UgbWF5b3LDrWEgZGUgZW1iYXJhem8gbcO6bHRpcGxlLCBkZSBsYSByZWdpw7NuIGRlIEF5YWN1Y2hvLCBwb3IgbG8gcXVlIHRpZW5lbiB1biBuaXZlbCBkZSBhbHRpdHVkIGludGVybWVkaWEgeSBjb24gaGVtb2dsb2JpbmEgSU1DLCBQZXNvLCBQUEcgeSBUYWxsYSBlbiB1biBuaXZlbCBtZWRpby4gR3J1cG8gMzogR2VzdGFudGVzIGNvbiB1biBlbWJhcmF6byBzaW1wbGUgZGUgbGEgcmVnacOzbiBkZSBKdW7DrW4gY29uIGVsDQo=