En este artículo se explica y muestra el análisis de correspondencia usando las librerías “FactoMineR” y “factoextra” con base en la explicación mostrada en el artículo “Métodos de componentes principales en R: guía práctica” (https://www.sthda.com/english/articles/31-principal-component-methods-in-r-practical-guide/114-mca-multiple-correspondence-analysis-in-r-essentials/#visualization-and-interpretation)

Cálculo

Primero se instalarán los paquetes necesarios para el análisis y se abrirán las librerias correspondientes

library("FactoMineR")
library("factoextra")

El paquete FactoMineR contiene datos por defecto para correr set de datos establecidos, por lo que se cargarán estos datos.

data(poison)
head(poison[, 1:7], 3)

Los valores contenidos involucran 55 individuos (niños de primaria) y 15 variables en las columnas. Para la finalidad del ejercicio solo se usará solo algunos individuos y variables para hacer el análisis de correspondencia

poison.active <- poison[1:55, 5:15]
head(poison.active[, 1:6], 3)

A continuación se calcularán un “summary” de los valores cargados

summary(poison.active)[,1:4]
      Nausea      Vomiting   Abdominals     Fever   
 Nausea_n:43   Vomit_n:33   Abdo_n:18   Fever_n:20  
 Nausea_y:12   Vomit_y:22   Abdo_y:37   Fever_y:35  

Ahora se realizará el gráfico de la frecuencia de las categorias de las variables

for (i in 1:4) {
  plot(poison.active[,i], main=colnames(poison.active)[i],
       ylab= "Count", col = "steelblue", las = 2)
  
}

Ahora un formato simplificado de la funcion de MCA. En este caso modifiqué “X” por los datos por defecto brindados por la librería

MCA(poison.active, ncp = 5, graph = TRUE)
**Results of the Multiple Correspondence Analysis (MCA)**
The analysis was performed on 55 individuals, described by 11 variables
*The results are available in the following objects:

   name              description                       
1  "$eig"            "eigenvalues"                     
2  "$var"            "results for the variables"       
3  "$var$coord"      "coord. of the categories"        
4  "$var$cos2"       "cos2 for the categories"         
5  "$var$contrib"    "contributions of the categories" 
6  "$var$v.test"     "v-test for the categories"       
7  "$var$eta2"       "coord. of variables"             
8  "$ind"            "results for the individuals"     
9  "$ind$coord"      "coord. for the individuals"      
10 "$ind$cos2"       "cos2 for the individuals"        
11 "$ind$contrib"    "contributions of the individuals"
12 "$call"           "intermediate results"            
13 "$call$marge.col" "weights of columns"              
14 "$call$marge.li"  "weights of rows"                 

Ahora primero se realizará el MCA en las variables o individuos activos:

res.mca <- MCA(poison.active, graph = FALSE)

Ahora imprimir la lista de lo que incluye la función MCA

print(res.mca)
**Results of the Multiple Correspondence Analysis (MCA)**
The analysis was performed on 55 individuals, described by 11 variables
*The results are available in the following objects:

   name              description                       
1  "$eig"            "eigenvalues"                     
2  "$var"            "results for the variables"       
3  "$var$coord"      "coord. of the categories"        
4  "$var$cos2"       "cos2 for the categories"         
5  "$var$contrib"    "contributions of the categories" 
6  "$var$v.test"     "v-test for the categories"       
7  "$var$eta2"       "coord. of variables"             
8  "$ind"            "results for the individuals"     
9  "$ind$coord"      "coord. for the individuals"      
10 "$ind$cos2"       "cos2 for the individuals"        
11 "$ind$contrib"    "contributions of the individuals"
12 "$call"           "intermediate results"            
13 "$call$marge.col" "weights of columns"              
14 "$call$marge.li"  "weights of rows"                 

Visualización e interpretación

En este caso se empleará el paquete “factoextra” para interpretar y visualizar el análisis de correspondencia múltiple.

Valores propios y varianzas

Extracción de varianzas retenidas por los iferentes ejes o dimensiones, usando la función get_eigenvalue() del paquete factoextra

Primero abrimos la libreria

library(factoextra)
eig.val <- get_eigenvalue(res.mca)
head(eig.val)
      eigenvalue variance.percent cumulative.variance.percent
Dim.1 0.33523140        33.523140                    33.52314
Dim.2 0.12913979        12.913979                    46.43712
Dim.3 0.10734849        10.734849                    57.17197
Dim.4 0.09587950         9.587950                    66.75992
Dim.5 0.07883277         7.883277                    74.64319
Dim.6 0.07108981         7.108981                    81.75217

Ahora se visualizará los porcentajes de incercia por cada dimensión del MCA

fviz_screeplot(res.mca, addlabels = TRUE, ylim = c(0,45))

Diagrama de bisectriz

Dibujar el biplot de individuos y categorías de variables

fviz_mca_biplot(res.mca,
                repel = TRUE,
                ggtheme = theme_minimal())

El grafico indica filas= azul; columnas= rojo Entre más alejadas o larga sea la linea que separa cada punto, indica menor similitud

Grafica de valores

get_mca_ var se usa para tomar los resultados de las categorías de las variables. Contiene coordenada Cos 2 y contribución

var <- get_mca_var(res.mca)
var
Multiple Correspondence Analysis Results for variables
 ===================================================
  Name       Description                  
1 "$coord"   "Coordinates for categories" 
2 "$cos2"    "Cos2 for categories"        
3 "$contrib" "contributions of categories"

Acceder a los componentes coordenadas, calidad y contribuciones a los componenetes principales:

head(var$coord)
              Dim 1       Dim 2        Dim 3       Dim 4       Dim 5
Nausea_n  0.2673909  0.12139029 -0.265583253  0.03376130  0.07370500
Nausea_y -0.9581506 -0.43498187  0.951673323 -0.12097801 -0.26410958
Vomit_n   0.4790279 -0.40919465  0.084492799  0.27361142  0.05245250
Vomit_y  -0.7185419  0.61379197 -0.126739198 -0.41041713 -0.07867876
Abdo_n    1.3180221 -0.03574501 -0.005094243 -0.15360951 -0.06986987
Abdo_y   -0.6411999  0.01738946  0.002478280  0.07472895  0.03399075
head(var$cos2)
             Dim 1        Dim 2        Dim 3       Dim 4       Dim 5
Nausea_n 0.2562007 0.0528025759 2.527485e-01 0.004084375 0.019466197
Nausea_y 0.2562007 0.0528025759 2.527485e-01 0.004084375 0.019466197
Vomit_n  0.3442016 0.2511603912 1.070855e-02 0.112294813 0.004126898
Vomit_y  0.3442016 0.2511603912 1.070855e-02 0.112294813 0.004126898
Abdo_n   0.8451157 0.0006215864 1.262496e-05 0.011479077 0.002374929
Abdo_y   0.8451157 0.0006215864 1.262496e-05 0.011479077 0.002374929
head(var$contrib)
             Dim 1       Dim 2        Dim 3      Dim 4      Dim 5
Nausea_n  1.515869  0.81100008 4.670018e+00 0.08449397 0.48977906
Nausea_y  5.431862  2.90608363 1.673423e+01 0.30277007 1.75504164
Vomit_n   3.733667  7.07226253 3.627455e-01 4.25893721 0.19036376
Vomit_y   5.600500 10.60839380 5.441183e-01 6.38840581 0.28554563
Abdo_n   15.417637  0.02943661 7.192511e-04 0.73219636 0.18424268
Abdo_y    7.500472  0.01432051 3.499060e-04 0.35620363 0.08963157

Correlación entre variables y dimensiones principales

fviz_mca_var(res.mca, choice = "mca.cor", 
            repel = TRUE, 
            ggtheme = theme_minimal())

Coordenadas de categorias variables

Se muestran las coordenadas de cada categoría de cada variable

head(round(var$coord, 2),4)
         Dim 1 Dim 2 Dim 3 Dim 4 Dim 5
Nausea_n  0.27  0.12 -0.27  0.03  0.07
Nausea_y -0.96 -0.43  0.95 -0.12 -0.26
Vomit_n   0.48 -0.41  0.08  0.27  0.05
Vomit_y  -0.72  0.61 -0.13 -0.41 -0.08

Ahora se visualizarán las categorías de variables unicamente

fviz_mca_var(res.mca, 
             repel = TRUE,
             ggtheme = theme_minimal())

Ahora, en caso de querer cambiar los colores y la forma de los puntos se puede realizar de la siguiente manera:

fviz_mca_var(res.mca, col.var = "coral", shape.var = 10,
             repel = TRUE)

Las categorías variables con un perfil similar se agrupan. Las categorías de variables correlacionadas negativamente se ubican en lados opuestos del origen del gráfico (cuadrantes opuestos). La distancia entre los puntos de categoría y el origen mide la calidad de la categoría de la variable en el mapa de factores. Los puntos de categoría que están alejados del origen están bien representados en el mapa de factores.

Calidad de representación de categorías variables

Calidad de la representación o también denominada Coseno al cuadrado cos2, mide el grado de asociación entre categorías de variables y un eje determinado,

head(var$cos2, 4)
             Dim 1      Dim 2      Dim 3       Dim 4       Dim 5
Nausea_n 0.2562007 0.05280258 0.25274850 0.004084375 0.019466197
Nausea_y 0.2562007 0.05280258 0.25274850 0.004084375 0.019466197
Vomit_n  0.3442016 0.25116039 0.01070855 0.112294813 0.004126898
Vomit_y  0.3442016 0.25116039 0.01070855 0.112294813 0.004126898

Si una categoría de variable está bien representada por dos dimensiones, la suma de cos2 es cercana a uno.

Ahora se cambiará el color de las categorías de variables en función de si son bajos, medios o altos los valores

fviz_mca_var(res.mca, col.var = "cos2",
             gradient.cols = c("blue", "coral", "lightgreen"), 
             repel = TRUE, # Avoid text overlapping
             ggtheme = theme_classic())

Usando la opción alpha.var = “cos2” se puede cambiar la transparencia de las categorías

fviz_mca_var(res.mca, alpha.var="cos2",
             repel = TRUE,
             ggtheme = theme_classic())

Para visualizar el coseno cuadrado de las categorías de filas en todas las dimensiones se puede emplear la librer+ias “corrplot”

library(corrplot)
Warning: package ‘corrplot’ was built under R version 4.3.3corrplot 0.95 loaded

Ahora crear un gráficos de barras de cos2 usando la función fviz_cos2() de la librería factoextra

fviz_cos2(res.mca, choice = "var", axes = 1:2)

Contribución de las categorías variables a las dimensiones

La contribución de las categorías variables (en %) a la definición de las dimensiones se puede extraer de la siguiente manera:

head(round(var$contrib,2), 4)
         Dim 1 Dim 2 Dim 3 Dim 4 Dim 5
Nausea_n  1.52  0.81  4.67  0.08  0.49
Nausea_y  5.43  2.91 16.73  0.30  1.76
Vomit_n   3.73  7.07  0.36  4.26  0.19
Vomit_y   5.60 10.61  0.54  6.39  0.29

Las categorías de variables con el valor más alto son las que más contribuyen a la definición de las dimensiones. Las categorías de variables que más contribuyen a Dim.1 y Dim.2 son las más importantes para explicar la variabilidad del conjunto de datos.

A continuación se muestran las 15 categorías de variables que más contribuyen a las dimensiones en un gráfico de barras

fviz_contrib(res.mca, choice = "var", axes = 1, top = 15)



fviz_contrib(res.mca, choice = "var", axes = 2, top = 15)

Las contribuciones totales a las dimensiones 1 y 2 se obtienen de la siguiente manera:

fviz_contrib(res.mca, choice = "var", axes = 1:2, top = 15)

La línea discontinua roja del gráfico muestra el valor promedio esperado suponiendo distribuciones uniformes

Se puede observar que:

  • Las categorías Abdo_n, Diarrea_n, Fiebre_n y Mayo_n son las más importantes en la definición de la primera dimensión.
  • Las categorías Courg_n, Potato_n, Vomit_y y Icecream_n son las que más contribuyen a la dimensión 2.

Las categorías de variables más importantes (o contribuyentes) se pueden resaltar en el diagrama de dispersión de la siguiente manera:

fviz_mca_var(res.mca, col.var = "contrib",
             gradient.cols = c("lightblue", "yellow", "coral"),
             repel = TRUE,
             ggtheme = theme_dark()
             )

El gráfico anterior da una idea de a qué polo de las dimensiones contribuyen realmente las categorías.

Es evidente que las categorías Abdo_n, Diarrea_n, Fiebre_n y Mayo_n tienen una contribución importante al polo positivo de la primera dimensión, mientras que las categorías Fiebre_y y Diarrea_y tienen una contribución mayor al polo negativo de la primera dimensión

Nuevamente, es posible contrarlar la transparencia de las categorías según su contribución

fviz_mca_var(res.mca, alpha.var="contrib",
             repel = TRUE,
             ggtheme = theme_light())

Gráfica de individuos

La función get_mca_ind() se utiliza para extraer los resultados de los individuos. Esta función devuelve una lista que contiene las coordenadas, el cos2 y las contribuciones de los individuos:

ind <- get_mca_ind(res.mca)
ind
Multiple Correspondence Analysis Results for individuals
 ===================================================
  Name       Description                       
1 "$coord"   "Coordinates for the individuals" 
2 "$cos2"    "Cos2 for the individuals"        
3 "$contrib" "contributions of the individuals"

Parcelas: calidad y aportación

La función fviz_mca_ind()se utiliza para visualizar únicamente los individuos.También es posible colorear a los individuos según sus valores cos2:

fviz_mca_ind(res.mca, col.ind = "cos2", 
             gradient.cols = c("lightblue", "lightgreen", "coral"),
             repel = TRUE, 
             ggtheme = theme_dark())

A continuación se creará un gráfico de barra de cos 2

fviz_cos2(res.mca, choice = "ind", axes = 1:2, top = 20)

fviz_contrib(res.mca, choice = "ind", axes = 1:2, top = 20)

Colorear los individuos por grupos

A continuación se coloreará a los individuos por grupos utilizando los niveles de la variable “Vomiting”. * El argumento “habillage” se utiliza para especificar la variable factorial para colorear a los individuos por grupos. * Se puede agregar una elipse de concentración alrededor de cada grupo utilizando el argumento “addEllipses = TRUE”. * Si se desea una elipse de confianza alrededor del punto medio de las categorías, se debe usar la función “ellipse.type =”confidence”” * El argumento “palettese” utiliza para cambiar los colores de los grupos.

fviz_mca_ind(res.mca, 
             label = "none", 
             habillage = "Vomiting",
             palette = c("yellow", "red"),
             addEllipses = TRUE, ellipse.type = "confidence",
             ggtheme = theme_dark())

Para especificar el valor del argumento “habillage”, también es posible utilizar el índice de la columna de la siguiente manera “( habillage = 2)”. Además, puede proporcionar una variable de agrupación externa de la siguiente manera: “habillage = poison$Vomiting”

fviz_mca_ind(res.mca, habillage = 2, addEllipses = TRUE, palette = c("coral", "purple1"),ggtheme = theme_gray())


fviz_mca_ind(res.mca, habillage = poison$Vomiting, addEllipses = TRUE, palette = c("coral", "purple1"),ggtheme = theme_gray())

Si se desea colorear individuos usando múltiples variables categóricas al mismo tiempo, utilice la función fviz_ellipses() de la siguiente manera:

fviz_ellipses(res.mca, c("Vomiting", "Fever"),
              geom = "point")
Warning: `gather_()` was deprecated in tidyr 1.2.0.
Please use `gather()` instead.

Para especificar los índices de variables categóricas:

fviz_ellipses(res.mca, 1:4, geom = "point")

Descripción de la dimensión

La función “dimdesc()” se puede utilizar para identificar las variables más correlacionadas con una dimensión dada

res.desc <- dimdesc(res.mca, axes = c(1,2))
res.desc[[1]]

Link between the variable and the categorical variable (1-way anova)
=============================================
                  R2      p.value
Abdominals 0.8451157 4.055640e-23
Diarrhae   0.7994680 3.910776e-20
Fever      0.7846788 2.600566e-19
Mayo       0.3829749 4.756234e-07
Vomiting   0.3442016 2.510738e-06
Nausea     0.2562007 8.062777e-05
Cheese     0.1944181 7.534834e-04

Link between variable and the categories of the categorical variables
================================================================
                      Estimate      p.value
Abdominals=Abdo_n    0.5671866 4.055640e-23
Diarrhae=Diarrhea_n  0.5380920 3.910776e-20
Fever=Fever_n        0.5330918 2.600566e-19
Mayo=Mayo_n          0.4644981 4.756234e-07
Vomiting=Vomit_n     0.3466915 2.510738e-06
Nausea=Nausea_n      0.3547892 8.062777e-05
Cheese=Cheese_n      0.3830043 7.534834e-04
Cheese=Cheese_y     -0.3830043 7.534834e-04
Nausea=Nausea_y     -0.3547892 8.062777e-05
Vomiting=Vomit_y    -0.3466915 2.510738e-06
Mayo=Mayo_y         -0.4644981 4.756234e-07
Fever=Fever_y       -0.5330918 2.600566e-19
Diarrhae=Diarrhea_y -0.5380920 3.910776e-20
Abdominals=Abdo_y   -0.5671866 4.055640e-23
res.desc[[2]]

Link between the variable and the categorical variable (1-way anova)
=============================================
                 R2      p.value
Courgette 0.4464145 2.500166e-08
Potato    0.3957543 2.690662e-07
Vomiting  0.2511604 9.728027e-05
Icecream  0.1409011 4.743927e-03

Link between variable and the categories of the categorical variables
================================================================
                      Estimate      p.value
Courgette=Courg_n    0.4176013 2.500166e-08
Potato=Potato_y      0.4977523 2.690662e-07
Vomiting=Vomit_y     0.1838104 9.728027e-05
Icecream=Icecream_n  0.2597197 4.743927e-03
Icecream=Icecream_y -0.2597197 4.743927e-03
Vomiting=Vomit_n    -0.1838104 9.728027e-05
Potato=Potato_n     -0.4977523 2.690662e-07
Courgette=Courg_y   -0.4176013 2.500166e-08

Especificación en MCA

Para especificar individuos y variables suplementarias, se puede utilizar la función MCA () de la siguiente manera

MCA(poison.active,  ind.sup = NULL, quanti.sup = NULL, quali.sup=NULL,
    graph = TRUE, axes = c(1,2))
**Results of the Multiple Correspondence Analysis (MCA)**
The analysis was performed on 55 individuals, described by 11 variables
*The results are available in the following objects:

   name              description                       
1  "$eig"            "eigenvalues"                     
2  "$var"            "results for the variables"       
3  "$var$coord"      "coord. of the categories"        
4  "$var$cos2"       "cos2 for the categories"         
5  "$var$contrib"    "contributions of the categories" 
6  "$var$v.test"     "v-test for the categories"       
7  "$var$eta2"       "coord. of variables"             
8  "$ind"            "results for the individuals"     
9  "$ind$coord"      "coord. for the individuals"      
10 "$ind$cos2"       "cos2 for the individuals"        
11 "$ind$contrib"    "contributions of the individuals"
12 "$call"           "intermediate results"            
13 "$call$marge.col" "weights of columns"              
14 "$call$marge.li"  "weights of rows"                 

res.mca <- MCA(poison, ind.sup = 53:55, 
               quanti.sup = 1:2, quali.sup = 3:4,  graph=FALSE)

Resultados

Extraer los resultados previstos para individuos/variables suplementarios

res.mca$quali.sup
$coord
             Dim 1         Dim 2       Dim 3        Dim 4       Dim 5
Sick_n  1.41809140  0.0020394048  0.13199139 -0.016036841 -0.08354663
Sick_y -0.63026284 -0.0009064021 -0.05866284  0.007127485  0.03713184
F      -0.03108147  0.1123143957  0.05033124 -0.055927173 -0.06832928
M       0.03356798 -0.1212995474 -0.05435774  0.060401347  0.07379562

$cos2
             Dim 1        Dim 2       Dim 3        Dim 4       Dim 5
Sick_n 0.893770319 1.848521e-06 0.007742990 0.0001143023 0.003102240
Sick_y 0.893770319 1.848521e-06 0.007742990 0.0001143023 0.003102240
F      0.001043342 1.362369e-02 0.002735892 0.0033780765 0.005042401
M      0.001043342 1.362369e-02 0.002735892 0.0033780765 0.005042401

$v.test
            Dim 1        Dim 2      Dim 3       Dim 4      Dim 5
Sick_n  6.7514655  0.009709509  0.6284047 -0.07635063 -0.3977615
Sick_y -6.7514655 -0.009709509 -0.6284047  0.07635063  0.3977615
F      -0.2306739  0.833551410  0.3735378 -0.41506855 -0.5071119
M       0.2306739 -0.833551410 -0.3735378  0.41506855  0.5071119

$eta2
           Dim 1        Dim 2       Dim 3        Dim 4       Dim 5
Sick 0.893770319 1.848521e-06 0.007742990 0.0001143023 0.003102240
Sex  0.001043342 1.362369e-02 0.002735892 0.0033780765 0.005042401
res.mca$quanti
$coord
            Dim 1       Dim 2       Dim 3       Dim 4       Dim 5
Age   0.003934896 -0.00741340 -0.26494536  0.20015501  0.02928483
Time -0.838158507 -0.08330586 -0.08718851 -0.08421599 -0.02316931
res.mca$ind.sup
$coord
        Dim 1     Dim 2      Dim 3      Dim 4      Dim 5
53  1.0835684 0.5172478  0.5794063  0.5390903  0.4553650
54 -0.1249473 0.1417271 -0.1765234 -0.1526587 -0.2779565
55 -0.4315948 0.1270468 -0.2071580 -0.1186804 -0.1891760

$cos2
        Dim 1      Dim 2      Dim 3      Dim 4      Dim 5
53 0.36304957 0.08272764 0.10380536 0.08986204 0.06411692
54 0.03157652 0.04062716 0.06302535 0.04713607 0.15626590
55 0.50232519 0.04352713 0.11572730 0.03798314 0.09650827

Parcelas

A continuación se realizará un biplot de individuos y categorías de variables

fviz_mca_biplot(res.mca, repel = TRUE,
                ggtheme = theme_classic())

  • Los individuos activos están en azul.
  • Los individuos suplementarios están en azul oscuro.
  • Las categorías de variables activas están en rojo
  • Las categorías de variables suplementarias están en verde oscuro.

Para resaltar la correlación entre variables (activas y complementarias) y dimensiones, hay que usar la función “fviz_mca_var()” con el argumento choice = “mca.cor”

fviz_mca_var(res.mca, choice = "mca.cor",
             repel = TRUE)

El código R a continuación traza categorías de variables cualitativas (variables activas y suplementarias):

fviz_mca_var(res.mca, repel = TRUE,
             ggtheme= theme_get())

Para variables cuantitativas complementarias (Tiempo y edad):

fviz_mca_var(res.mca, choice = "quanti.sup",
             ggtheme = theme_update())

Para visualizar personas adicionales:

fviz_mca_ind(res.mca, 
             label = "ind.sup",
             ggtheme = theme_grey())

Filtrar resultados

Si se tienen muchas categorías de individuos o variables, es posible visualizar solo algunas de ellas utilizando los argumentos “select.indy” y “select.var”.

“select.ind”, “select.var”: una selección de categorías de variables o individuos que se extraerán. Los valores permitidos son “NULL” o que “list” contienen los argumentos nombre, cos2 o contrib

fviz_mca_var(res.mca, select.var = list(cos2 = 0.4))


fviz_mca_var(res.mca, select.var= list(cos2 = 10))


name <- list(name = c("Fever_n", "Abdo_y", "Diarrhea_n",
                      "Fever_Y", "Vomit_y", "Vomit_n"))

fviz_mca_var(res.mca, select.var = name)


fviz_mca_biplot(res.mca, select.ind = list(contrib = 5), 
               select.var = list(contrib = 5),
               ggtheme = theme_gray())

Exportar gráficos a archivos PDF/PNG

Inicialmente se seguirán dos paso a seguir: 1. Crear la trama de interés como un objeto

scree.plot <- fviz_eig(res.mca)

biplot.mca <- fviz_mca_biplot(res.mca)
  1. Realizar los gráficos en un único archivo PDF
library(ggpubr)
Warning: package ‘ggpubr’ was built under R version 4.3.3
ggexport(plotlist = list(scree.plot, biplot.mca), 
         filename = "MCA.pdf")

Exportar resultados a archivos txt/csv

Se empleará la función “write.infile()”

write.infile(res.mca, "mca.txt", sep = "\t")
write.infile(res.mca, "mca.csv", sep = ";")

En este ejercicio se describió cómo realizar e interpretar el análisis de correspondencia múltiple (AC). Se calculó el AC utilizando la función MCA (). A continuación, utilizamos el paquete R factoextra para generar una visualización basada en ggplot2 de los resultados del AC.

LS0tDQp0aXRsZTogIkVqZXJjaWNpbyBBbsOhbGlzaXMgZGUgY29ycmVzcG9uZGVuY2lhIC0gTcOpdG9kb3MgbXVsdGl2YXJpYWRvcyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KZGF0ZTogIjgvMDMvMjAyNSINCmF1dGhvcjogIlBhdWxhIEp1bGlhbmEgVmlyZ8O8ZXogR8OzbWV6Ig0KLS0tDQoNCkVuIGVzdGUgYXJ0w61jdWxvIHNlIGV4cGxpY2EgeSBtdWVzdHJhIGVsIGFuw6FsaXNpcyBkZSBjb3JyZXNwb25kZW5jaWEgdXNhbmRvIGxhcyBsaWJyZXLDrWFzICJGYWN0b01pbmVSIiB5ICJmYWN0b2V4dHJhIiBjb24gYmFzZSBlbiBsYSBleHBsaWNhY2nDs24gbW9zdHJhZGEgZW4gZWwgYXJ0w61jdWxvICJNw6l0b2RvcyBkZSBjb21wb25lbnRlcyBwcmluY2lwYWxlcyBlbiBSOiBndcOtYSBwcsOhY3RpY2EiIChodHRwczovL3d3dy5zdGhkYS5jb20vZW5nbGlzaC9hcnRpY2xlcy8zMS1wcmluY2lwYWwtY29tcG9uZW50LW1ldGhvZHMtaW4tci1wcmFjdGljYWwtZ3VpZGUvMTE0LW1jYS1tdWx0aXBsZS1jb3JyZXNwb25kZW5jZS1hbmFseXNpcy1pbi1yLWVzc2VudGlhbHMvI3Zpc3VhbGl6YXRpb24tYW5kLWludGVycHJldGF0aW9uKQ0KDQoNCiMjIyMgKipDw6FsY3VsbyoqDQoNClByaW1lcm8gc2UgaW5zdGFsYXLDoW4gbG9zIHBhcXVldGVzIG5lY2VzYXJpb3MgcGFyYSBlbCBhbsOhbGlzaXMgeSBzZSBhYnJpcsOhbiBsYXMgbGlicmVyaWFzIGNvcnJlc3BvbmRpZW50ZXMNCg0KYGBge3J9DQpsaWJyYXJ5KCJGYWN0b01pbmVSIikNCmxpYnJhcnkoImZhY3RvZXh0cmEiKQ0KYGBgDQoNCkVsIHBhcXVldGUgRmFjdG9NaW5lUiBjb250aWVuZSBkYXRvcyBwb3IgZGVmZWN0byBwYXJhIGNvcnJlciBzZXQgZGUgZGF0b3MgZXN0YWJsZWNpZG9zLCBwb3IgbG8gcXVlIHNlIGNhcmdhcsOhbiBlc3RvcyBkYXRvcy4NCg0KYGBge3J9DQpkYXRhKHBvaXNvbikNCmhlYWQocG9pc29uWywgMTo3XSwgMykNCmBgYA0KTG9zIHZhbG9yZXMgY29udGVuaWRvcyBpbnZvbHVjcmFuIDU1IGluZGl2aWR1b3MgKG5pw7FvcyBkZSBwcmltYXJpYSkgeSAxNSB2YXJpYWJsZXMgZW4gbGFzIGNvbHVtbmFzLiBQYXJhIGxhIGZpbmFsaWRhZCBkZWwgZWplcmNpY2lvIHNvbG8gc2UgdXNhcsOhIHNvbG8gYWxndW5vcyBpbmRpdmlkdW9zIHkgdmFyaWFibGVzIHBhcmEgaGFjZXIgZWwgYW7DoWxpc2lzIGRlIGNvcnJlc3BvbmRlbmNpYQ0KDQpgYGB7cn0NCnBvaXNvbi5hY3RpdmUgPC0gcG9pc29uWzE6NTUsIDU6MTVdDQpoZWFkKHBvaXNvbi5hY3RpdmVbLCAxOjZdLCAzKQ0KYGBgDQoNCkEgY29udGludWFjacOzbiBzZSBjYWxjdWxhcsOhbiB1biAic3VtbWFyeSIgZGUgbG9zIHZhbG9yZXMgY2FyZ2Fkb3MNCg0KYGBge3J9DQpzdW1tYXJ5KHBvaXNvbi5hY3RpdmUpWywxOjRdDQpgYGANCkFob3JhIHNlIHJlYWxpemFyw6EgZWwgZ3LDoWZpY28gZGUgbGEgZnJlY3VlbmNpYSBkZSBsYXMgY2F0ZWdvcmlhcyBkZSBsYXMgdmFyaWFibGVzDQoNCmBgYHtyfQ0KZm9yIChpIGluIDE6NCkgew0KICBwbG90KHBvaXNvbi5hY3RpdmVbLGldLCBtYWluPWNvbG5hbWVzKHBvaXNvbi5hY3RpdmUpW2ldLA0KICAgICAgIHlsYWI9ICJDb3VudCIsIGNvbCA9ICJzdGVlbGJsdWUiLCBsYXMgPSAyKQ0KICANCn0NCmBgYA0KDQoNCkFob3JhIHVuIGZvcm1hdG8gc2ltcGxpZmljYWRvIGRlIGxhIGZ1bmNpb24gZGUgTUNBLiBFbiBlc3RlIGNhc28gbW9kaWZpcXXDqSAiWCIgcG9yIGxvcyBkYXRvcyBwb3IgZGVmZWN0byBicmluZGFkb3MgcG9yIGxhIGxpYnJlcsOtYQ0KYGBge3J9DQpNQ0EocG9pc29uLmFjdGl2ZSwgbmNwID0gNSwgZ3JhcGggPSBUUlVFKQ0KYGBgDQoNCkFob3JhIHByaW1lcm8gc2UgcmVhbGl6YXLDoSBlbCBNQ0EgZW4gbGFzIHZhcmlhYmxlcyBvIGluZGl2aWR1b3MgYWN0aXZvczoNCmBgYHtyfQ0KcmVzLm1jYSA8LSBNQ0EocG9pc29uLmFjdGl2ZSwgZ3JhcGggPSBGQUxTRSkNCmBgYA0KDQpBaG9yYSBpbXByaW1pciBsYSBsaXN0YSBkZSBsbyBxdWUgaW5jbHV5ZSBsYSBmdW5jacOzbiBNQ0ENCmBgYHtyfQ0KcHJpbnQocmVzLm1jYSkNCmBgYA0KDQojIyMgVmlzdWFsaXphY2nDs24gZSBpbnRlcnByZXRhY2nDs24NCg0KRW4gZXN0ZSBjYXNvIHNlIGVtcGxlYXLDoSBlbCBwYXF1ZXRlICJmYWN0b2V4dHJhIiBwYXJhIGludGVycHJldGFyIHkgdmlzdWFsaXphciBlbCBhbsOhbGlzaXMgZGUgY29ycmVzcG9uZGVuY2lhIG3Dumx0aXBsZS4NCg0KIyMjIyAqKlZhbG9yZXMgcHJvcGlvcyB5IHZhcmlhbnphcyoqDQoNCkV4dHJhY2Npw7NuIGRlIHZhcmlhbnphcyByZXRlbmlkYXMgcG9yIGxvcyBpZmVyZW50ZXMgZWplcyBvIGRpbWVuc2lvbmVzLCB1c2FuZG8gbGEgZnVuY2nDs24gZ2V0X2VpZ2VudmFsdWUoKSBkZWwgcGFxdWV0ZSBmYWN0b2V4dHJhDQoNClByaW1lcm8gYWJyaW1vcyBsYSBsaWJyZXJpYQ0KYGBge3J9DQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQpgYGANCg0KDQpgYGB7cn0NCmVpZy52YWwgPC0gZ2V0X2VpZ2VudmFsdWUocmVzLm1jYSkNCmhlYWQoZWlnLnZhbCkNCmBgYA0KDQpBaG9yYSBzZSB2aXN1YWxpemFyw6EgbG9zIHBvcmNlbnRhamVzIGRlIGluY2VyY2lhIHBvciBjYWRhIGRpbWVuc2nDs24gZGVsIE1DQQ0KDQpgYGB7cn0NCmZ2aXpfc2NyZWVwbG90KHJlcy5tY2EsIGFkZGxhYmVscyA9IFRSVUUsIHlsaW0gPSBjKDAsNDUpKQ0KYGBgDQoNCiMjIyMgRGlhZ3JhbWEgZGUgYmlzZWN0cml6DQoNCkRpYnVqYXIgZWwgYmlwbG90IGRlIGluZGl2aWR1b3MgeSBjYXRlZ29yw61hcyBkZSB2YXJpYWJsZXMNCmBgYHtyfQ0KZnZpel9tY2FfYmlwbG90KHJlcy5tY2EsDQogICAgICAgICAgICAgICAgcmVwZWwgPSBUUlVFLA0KICAgICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkpDQpgYGANCkVsIGdyYWZpY28gaW5kaWNhIGZpbGFzPSBhenVsOyBjb2x1bW5hcz0gcm9qbw0KRW50cmUgbcOhcyBhbGVqYWRhcyBvIGxhcmdhIHNlYSBsYSBsaW5lYSBxdWUgc2VwYXJhIGNhZGEgcHVudG8sIGluZGljYSBtZW5vciBzaW1pbGl0dWQNCg0KIyMjIyBHcmFmaWNhIGRlIHZhbG9yZXMNCg0KZ2V0X21jYV8gdmFyIHNlIHVzYSBwYXJhIHRvbWFyIGxvcyByZXN1bHRhZG9zIGRlIGxhcyBjYXRlZ29yw61hcyBkZSBsYXMgdmFyaWFibGVzLiBDb250aWVuZSBjb29yZGVuYWRhIENvcyAyIHkgY29udHJpYnVjacOzbg0KDQpgYGB7cn0NCnZhciA8LSBnZXRfbWNhX3ZhcihyZXMubWNhKQ0KdmFyDQpgYGANCg0KQWNjZWRlciBhIGxvcyBjb21wb25lbnRlcyBjb29yZGVuYWRhcywgY2FsaWRhZCAgeSBjb250cmlidWNpb25lcyBhIGxvcyBjb21wb25lbmV0ZXMgcHJpbmNpcGFsZXM6DQpgYGB7cn0NCmhlYWQodmFyJGNvb3JkKQ0KaGVhZCh2YXIkY29zMikNCmhlYWQodmFyJGNvbnRyaWIpDQpgYGANCg0KIyMjIyAqKkNvcnJlbGFjacOzbiBlbnRyZSB2YXJpYWJsZXMgeSBkaW1lbnNpb25lcyBwcmluY2lwYWxlcyoqDQoNCmBgYHtyfQ0KZnZpel9tY2FfdmFyKHJlcy5tY2EsIGNob2ljZSA9ICJtY2EuY29yIiwgDQogICAgICAgICAgICByZXBlbCA9IFRSVUUsIA0KICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoKSkNCmBgYA0KDQojIyMjIENvb3JkZW5hZGFzIGRlIGNhdGVnb3JpYXMgdmFyaWFibGVzDQoNClNlIG11ZXN0cmFuIGxhcyBjb29yZGVuYWRhcyBkZSBjYWRhIGNhdGVnb3LDrWEgZGUgY2FkYSB2YXJpYWJsZQ0KYGBge3J9DQpoZWFkKHJvdW5kKHZhciRjb29yZCwgMiksNCkNCmBgYA0KDQpBaG9yYSBzZSB2aXN1YWxpemFyw6FuIGxhcyBjYXRlZ29yw61hcyBkZSB2YXJpYWJsZXMgdW5pY2FtZW50ZQ0KYGBge3J9DQpmdml6X21jYV92YXIocmVzLm1jYSwgDQogICAgICAgICAgICAgcmVwZWwgPSBUUlVFLA0KICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKCkpDQpgYGANCg0KQWhvcmEsIGVuIGNhc28gZGUgcXVlcmVyIGNhbWJpYXIgbG9zIGNvbG9yZXMgeSBsYSBmb3JtYSBkZSBsb3MgcHVudG9zIHNlIHB1ZWRlIHJlYWxpemFyIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6DQpgYGB7cn0NCmZ2aXpfbWNhX3ZhcihyZXMubWNhLCBjb2wudmFyID0gImNvcmFsIiwgc2hhcGUudmFyID0gMTAsDQogICAgICAgICAgICAgcmVwZWwgPSBUUlVFKQ0KYGBgDQpMYXMgY2F0ZWdvcsOtYXMgdmFyaWFibGVzIGNvbiB1biBwZXJmaWwgc2ltaWxhciBzZSBhZ3J1cGFuLg0KTGFzIGNhdGVnb3LDrWFzIGRlIHZhcmlhYmxlcyBjb3JyZWxhY2lvbmFkYXMgbmVnYXRpdmFtZW50ZSBzZSB1YmljYW4gZW4gbGFkb3Mgb3B1ZXN0b3MgZGVsIG9yaWdlbiBkZWwgZ3LDoWZpY28gKGN1YWRyYW50ZXMgb3B1ZXN0b3MpLg0KTGEgZGlzdGFuY2lhIGVudHJlIGxvcyBwdW50b3MgZGUgY2F0ZWdvcsOtYSB5IGVsIG9yaWdlbiBtaWRlIGxhIGNhbGlkYWQgZGUgbGEgY2F0ZWdvcsOtYSBkZSBsYSB2YXJpYWJsZSBlbiBlbCBtYXBhIGRlIGZhY3RvcmVzLiBMb3MgcHVudG9zIGRlIGNhdGVnb3LDrWEgcXVlIGVzdMOhbiBhbGVqYWRvcyBkZWwgb3JpZ2VuIGVzdMOhbiBiaWVuIHJlcHJlc2VudGFkb3MgZW4gZWwgbWFwYSBkZSBmYWN0b3Jlcy4NCg0KIyMjIyBDYWxpZGFkIGRlIHJlcHJlc2VudGFjacOzbiBkZSBjYXRlZ29yw61hcyB2YXJpYWJsZXMNCg0KQ2FsaWRhZCBkZSBsYSByZXByZXNlbnRhY2nDs24gbyB0YW1iacOpbiBkZW5vbWluYWRhIENvc2VubyBhbCBjdWFkcmFkbyBjb3MyLCBtaWRlIGVsIGdyYWRvIGRlIGFzb2NpYWNpw7NuIGVudHJlIGNhdGVnb3LDrWFzIGRlIHZhcmlhYmxlcyB5IHVuIGVqZSBkZXRlcm1pbmFkbywNCg0KYGBge3J9DQpoZWFkKHZhciRjb3MyLCA0KQ0KYGBgDQpTaSB1bmEgY2F0ZWdvcsOtYSBkZSB2YXJpYWJsZSBlc3TDoSBiaWVuIHJlcHJlc2VudGFkYSBwb3IgZG9zIGRpbWVuc2lvbmVzLCAqKmxhIHN1bWEgZGUgY29zMiBlcyBjZXJjYW5hIGEgdW5vKiouIA0KDQpBaG9yYSBzZSBjYW1iaWFyw6EgZWwgY29sb3IgZGUgbGFzIGNhdGVnb3LDrWFzIGRlIHZhcmlhYmxlcyBlbiBmdW5jacOzbiBkZSBzaSBzb24gYmFqb3MsIG1lZGlvcyBvIGFsdG9zIGxvcyB2YWxvcmVzDQoNCmBgYHtyfQ0KZnZpel9tY2FfdmFyKHJlcy5tY2EsIGNvbC52YXIgPSAiY29zMiIsDQogICAgICAgICAgICAgZ3JhZGllbnQuY29scyA9IGMoImJsdWUiLCAiY29yYWwiLCAibGlnaHRncmVlbiIpLCANCiAgICAgICAgICAgICByZXBlbCA9IFRSVUUsICMgQXZvaWQgdGV4dCBvdmVybGFwcGluZw0KICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9jbGFzc2ljKCkpDQpgYGANClVzYW5kbyBsYSBvcGNpw7NuIGFscGhhLnZhciA9ICJjb3MyIiBzZSBwdWVkZSBjYW1iaWFyIGxhIHRyYW5zcGFyZW5jaWEgZGUgbGFzIGNhdGVnb3LDrWFzDQoNCmBgYHtyfQ0KZnZpel9tY2FfdmFyKHJlcy5tY2EsIGFscGhhLnZhcj0iY29zMiIsDQogICAgICAgICAgICAgcmVwZWwgPSBUUlVFLA0KICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9jbGFzc2ljKCkpDQpgYGANCg0KDQpQYXJhIHZpc3VhbGl6YXIgZWwgY29zZW5vIGN1YWRyYWRvIGRlIGxhcyBjYXRlZ29yw61hcyBkZSBmaWxhcyBlbiB0b2RhcyBsYXMgZGltZW5zaW9uZXMgc2UgcHVlZGUgZW1wbGVhciBsYSBsaWJyZXIraWFzICJjb3JycGxvdCINCg0KYGBge3J9DQpsaWJyYXJ5KGNvcnJwbG90KQ0KYGBgDQoNCkFob3JhIGNyZWFyIHVuIGdyw6FmaWNvcyBkZSBiYXJyYXMgZGUgY29zMiB1c2FuZG8gbGEgZnVuY2nDs24gZnZpel9jb3MyKCkgZGUgbGEgbGlicmVyw61hIGZhY3RvZXh0cmENCg0KYGBge3J9DQpmdml6X2NvczIocmVzLm1jYSwgY2hvaWNlID0gInZhciIsIGF4ZXMgPSAxOjIpDQpgYGANCg0KIyMjIyBDb250cmlidWNpw7NuIGRlIGxhcyBjYXRlZ29yw61hcyB2YXJpYWJsZXMgYSBsYXMgZGltZW5zaW9uZXMNCg0KTGEgY29udHJpYnVjacOzbiBkZSBsYXMgY2F0ZWdvcsOtYXMgdmFyaWFibGVzIChlbiAlKSBhIGxhIGRlZmluaWNpw7NuIGRlIGxhcyBkaW1lbnNpb25lcyBzZSBwdWVkZSBleHRyYWVyIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6DQpgYGB7cn0NCmhlYWQocm91bmQodmFyJGNvbnRyaWIsMiksIDQpDQpgYGANCkxhcyBjYXRlZ29yw61hcyBkZSB2YXJpYWJsZXMgY29uIGVsIHZhbG9yIG3DoXMgYWx0byBzb24gbGFzIHF1ZSBtw6FzIGNvbnRyaWJ1eWVuIGEgbGEgZGVmaW5pY2nDs24gZGUgbGFzIGRpbWVuc2lvbmVzLiBMYXMgY2F0ZWdvcsOtYXMgZGUgdmFyaWFibGVzIHF1ZSBtw6FzIGNvbnRyaWJ1eWVuIGEgRGltLjEgeSBEaW0uMiBzb24gbGFzIG3DoXMgaW1wb3J0YW50ZXMgcGFyYSBleHBsaWNhciBsYSB2YXJpYWJpbGlkYWQgZGVsIGNvbmp1bnRvIGRlIGRhdG9zLg0KDQoNCkEgY29udGludWFjacOzbiBzZSBtdWVzdHJhbiBsYXMgMTUgY2F0ZWdvcsOtYXMgZGUgdmFyaWFibGVzIHF1ZSBtw6FzIGNvbnRyaWJ1eWVuIGEgbGFzIGRpbWVuc2lvbmVzIGVuIHVuIGdyw6FmaWNvIGRlIGJhcnJhcw0KDQpgYGB7cn0NCmZ2aXpfY29udHJpYihyZXMubWNhLCBjaG9pY2UgPSAidmFyIiwgYXhlcyA9IDEsIHRvcCA9IDE1KQ0KDQoNCmZ2aXpfY29udHJpYihyZXMubWNhLCBjaG9pY2UgPSAidmFyIiwgYXhlcyA9IDIsIHRvcCA9IDE1KQ0KYGBgDQoNCkxhcyBjb250cmlidWNpb25lcyB0b3RhbGVzIGEgbGFzIGRpbWVuc2lvbmVzIDEgeSAyIHNlIG9idGllbmVuIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6DQoNCmBgYHtyfQ0KZnZpel9jb250cmliKHJlcy5tY2EsIGNob2ljZSA9ICJ2YXIiLCBheGVzID0gMToyLCB0b3AgPSAxNSkNCmBgYA0KTGEgbMOtbmVhIGRpc2NvbnRpbnVhIHJvamEgZGVsIGdyw6FmaWNvIG11ZXN0cmEgZWwgdmFsb3IgcHJvbWVkaW8gZXNwZXJhZG8gc3Vwb25pZW5kbyBkaXN0cmlidWNpb25lcyB1bmlmb3JtZXMNCg0KU2UgcHVlZGUgb2JzZXJ2YXIgcXVlOg0KDQoqIExhcyBjYXRlZ29yw61hcyBBYmRvX24sIERpYXJyZWFfbiwgRmllYnJlX24geSBNYXlvX24gc29uIGxhcyBtw6FzIGltcG9ydGFudGVzIGVuIGxhIGRlZmluaWNpw7NuIGRlIGxhIHByaW1lcmEgZGltZW5zacOzbi4NCiogTGFzIGNhdGVnb3LDrWFzIENvdXJnX24sIFBvdGF0b19uLCBWb21pdF95IHkgSWNlY3JlYW1fbiBzb24gbGFzIHF1ZSBtw6FzIGNvbnRyaWJ1eWVuIGEgbGEgZGltZW5zacOzbiAyLg0KDQoNCkxhcyBjYXRlZ29yw61hcyBkZSB2YXJpYWJsZXMgbcOhcyBpbXBvcnRhbnRlcyAobyBjb250cmlidXllbnRlcykgc2UgcHVlZGVuIHJlc2FsdGFyIGVuIGVsIGRpYWdyYW1hIGRlIGRpc3BlcnNpw7NuIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6DQoNCmBgYHtyfQ0KZnZpel9tY2FfdmFyKHJlcy5tY2EsIGNvbC52YXIgPSAiY29udHJpYiIsDQogICAgICAgICAgICAgZ3JhZGllbnQuY29scyA9IGMoImxpZ2h0Ymx1ZSIsICJ5ZWxsb3ciLCAiY29yYWwiKSwNCiAgICAgICAgICAgICByZXBlbCA9IFRSVUUsDQogICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX2RhcmsoKQ0KICAgICAgICAgICAgICkNCmBgYA0KDQpFbCBncsOhZmljbyBhbnRlcmlvciBkYSB1bmEgaWRlYSBkZSBhIHF1w6kgcG9sbyBkZSBsYXMgZGltZW5zaW9uZXMgY29udHJpYnV5ZW4gcmVhbG1lbnRlIGxhcyBjYXRlZ29yw61hcy4NCg0KRXMgZXZpZGVudGUgcXVlIGxhcyBjYXRlZ29yw61hcyBBYmRvX24sIERpYXJyZWFfbiwgRmllYnJlX24geSBNYXlvX24gdGllbmVuIHVuYSBjb250cmlidWNpw7NuIGltcG9ydGFudGUgYWwgcG9sbyBwb3NpdGl2byBkZSBsYSBwcmltZXJhIGRpbWVuc2nDs24sIG1pZW50cmFzIHF1ZSBsYXMgY2F0ZWdvcsOtYXMgRmllYnJlX3kgeSBEaWFycmVhX3kgdGllbmVuIHVuYSBjb250cmlidWNpw7NuIG1heW9yIGFsIHBvbG8gbmVnYXRpdm8gZGUgbGEgcHJpbWVyYSBkaW1lbnNpw7NuDQoNCg0KTnVldmFtZW50ZSwgZXMgcG9zaWJsZSBjb250cmFybGFyIGxhIHRyYW5zcGFyZW5jaWEgZGUgbGFzIGNhdGVnb3LDrWFzIHNlZ8O6biBzdSBjb250cmlidWNpw7NuDQoNCmBgYHtyfQ0KZnZpel9tY2FfdmFyKHJlcy5tY2EsIGFscGhhLnZhcj0iY29udHJpYiIsDQogICAgICAgICAgICAgcmVwZWwgPSBUUlVFLA0KICAgICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9saWdodCgpKQ0KYGBgDQoNCiMjIyMgR3LDoWZpY2EgZGUgaW5kaXZpZHVvcw0KDQpMYSBmdW5jacOzbiBnZXRfbWNhX2luZCgpIHNlIHV0aWxpemEgcGFyYSBleHRyYWVyIGxvcyByZXN1bHRhZG9zIGRlIGxvcyBpbmRpdmlkdW9zLiBFc3RhIGZ1bmNpw7NuIGRldnVlbHZlIHVuYSBsaXN0YSBxdWUgY29udGllbmUgbGFzIGNvb3JkZW5hZGFzLCBlbCBjb3MyIHkgbGFzIGNvbnRyaWJ1Y2lvbmVzIGRlIGxvcyBpbmRpdmlkdW9zOg0KDQpgYGB7cn0NCmluZCA8LSBnZXRfbWNhX2luZChyZXMubWNhKQ0KaW5kDQpgYGANCg0KIyMjIyAqKlBhcmNlbGFzOiBjYWxpZGFkIHkgYXBvcnRhY2nDs24qKg0KDQpMYSBmdW5jacOzbiBmdml6X21jYV9pbmQoKXNlIHV0aWxpemEgcGFyYSB2aXN1YWxpemFyIMO6bmljYW1lbnRlIGxvcyBpbmRpdmlkdW9zLlRhbWJpw6luIGVzIHBvc2libGUgY29sb3JlYXIgYSBsb3MgaW5kaXZpZHVvcyBzZWfDum4gc3VzIHZhbG9yZXMgY29zMjoNCg0KYGBge3J9DQpmdml6X21jYV9pbmQocmVzLm1jYSwgY29sLmluZCA9ICJjb3MyIiwgDQogICAgICAgICAgICAgZ3JhZGllbnQuY29scyA9IGMoImxpZ2h0Ymx1ZSIsICJsaWdodGdyZWVuIiwgImNvcmFsIiksDQogICAgICAgICAgICAgcmVwZWwgPSBUUlVFLCANCiAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfZGFyaygpKQ0KYGBgDQoNCkEgY29udGludWFjacOzbiBzZSBjcmVhcsOhIHVuIGdyw6FmaWNvIGRlIGJhcnJhIGRlIGNvcyAyDQoNCmBgYHtyfQ0KZnZpel9jb3MyKHJlcy5tY2EsIGNob2ljZSA9ICJpbmQiLCBheGVzID0gMToyLCB0b3AgPSAyMCkNCmZ2aXpfY29udHJpYihyZXMubWNhLCBjaG9pY2UgPSAiaW5kIiwgYXhlcyA9IDE6MiwgdG9wID0gMjApDQoNCmBgYA0KDQojIyMjIENvbG9yZWFyIGxvcyBpbmRpdmlkdW9zIHBvciBncnVwb3MNCg0KQSBjb250aW51YWNpw7NuIHNlIGNvbG9yZWFyw6EgYSBsb3MgaW5kaXZpZHVvcyBwb3IgZ3J1cG9zIHV0aWxpemFuZG8gbG9zIG5pdmVsZXMgZGUgbGEgdmFyaWFibGUgIlZvbWl0aW5nIi4NCiogRWwgYXJndW1lbnRvICJoYWJpbGxhZ2UiIHNlIHV0aWxpemEgcGFyYSBlc3BlY2lmaWNhciBsYSB2YXJpYWJsZSBmYWN0b3JpYWwgcGFyYSBjb2xvcmVhciBhIGxvcyBpbmRpdmlkdW9zIHBvciBncnVwb3MuIA0KKiBTZSBwdWVkZSBhZ3JlZ2FyIHVuYSBlbGlwc2UgZGUgY29uY2VudHJhY2nDs24gYWxyZWRlZG9yIGRlIGNhZGEgZ3J1cG8gdXRpbGl6YW5kbyBlbCBhcmd1bWVudG8gImFkZEVsbGlwc2VzID0gVFJVRSIuIA0KKiBTaSBzZSBkZXNlYSB1bmEgZWxpcHNlIGRlIGNvbmZpYW56YSBhbHJlZGVkb3IgZGVsIHB1bnRvIG1lZGlvIGRlIGxhcyBjYXRlZ29yw61hcywgc2UgZGViZSB1c2FyIGxhIGZ1bmNpw7NuICJlbGxpcHNlLnR5cGUgPSAiY29uZmlkZW5jZSIiIA0KKiBFbCBhcmd1bWVudG8gInBhbGV0dGVzZSIgdXRpbGl6YSBwYXJhIGNhbWJpYXIgbG9zIGNvbG9yZXMgZGUgbG9zIGdydXBvcy4NCg0KYGBge3J9DQpmdml6X21jYV9pbmQocmVzLm1jYSwgDQogICAgICAgICAgICAgbGFiZWwgPSAibm9uZSIsIA0KICAgICAgICAgICAgIGhhYmlsbGFnZSA9ICJWb21pdGluZyIsDQogICAgICAgICAgICAgcGFsZXR0ZSA9IGMoInllbGxvdyIsICJyZWQiKSwNCiAgICAgICAgICAgICBhZGRFbGxpcHNlcyA9IFRSVUUsIGVsbGlwc2UudHlwZSA9ICJjb25maWRlbmNlIiwNCiAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfZGFyaygpKQ0KYGBgDQpQYXJhIGVzcGVjaWZpY2FyIGVsIHZhbG9yIGRlbCBhcmd1bWVudG8gImhhYmlsbGFnZSIsIHRhbWJpw6luIGVzIHBvc2libGUgdXRpbGl6YXIgZWwgw61uZGljZSBkZSBsYSBjb2x1bW5hIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmEgIiggaGFiaWxsYWdlID0gMikiLiBBZGVtw6FzLCBwdWVkZSBwcm9wb3JjaW9uYXIgdW5hIHZhcmlhYmxlIGRlIGFncnVwYWNpw7NuIGV4dGVybmEgZGUgbGEgc2lndWllbnRlIG1hbmVyYTogImhhYmlsbGFnZSA9IHBvaXNvbiRWb21pdGluZyINCg0KYGBge3J9DQpmdml6X21jYV9pbmQocmVzLm1jYSwgaGFiaWxsYWdlID0gMiwgYWRkRWxsaXBzZXMgPSBUUlVFLCBwYWxldHRlID0gYygiY29yYWwiLCAicHVycGxlMSIpLGdndGhlbWUgPSB0aGVtZV9ncmF5KCkpDQoNCmZ2aXpfbWNhX2luZChyZXMubWNhLCBoYWJpbGxhZ2UgPSBwb2lzb24kVm9taXRpbmcsIGFkZEVsbGlwc2VzID0gVFJVRSwgcGFsZXR0ZSA9IGMoImNvcmFsIiwgInB1cnBsZTEiKSxnZ3RoZW1lID0gdGhlbWVfZ3JheSgpKQ0KYGBgDQoNClNpIHNlIGRlc2VhIGNvbG9yZWFyIGluZGl2aWR1b3MgdXNhbmRvIG3Dumx0aXBsZXMgdmFyaWFibGVzIGNhdGVnw7NyaWNhcyBhbCBtaXNtbyB0aWVtcG8sIHV0aWxpY2UgbGEgZnVuY2nDs24gZnZpel9lbGxpcHNlcygpIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6DQoNCmBgYHtyfQ0KZnZpel9lbGxpcHNlcyhyZXMubWNhLCBjKCJWb21pdGluZyIsICJGZXZlciIpLA0KICAgICAgICAgICAgICBnZW9tID0gInBvaW50IikNCmBgYA0KDQpQYXJhIGVzcGVjaWZpY2FyIGxvcyDDrW5kaWNlcyBkZSB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzOg0KYGBge3J9DQpmdml6X2VsbGlwc2VzKHJlcy5tY2EsIDE6NCwgZ2VvbSA9ICJwb2ludCIpDQpgYGANCg0KIyMjIyBEZXNjcmlwY2nDs24gZGUgbGEgZGltZW5zacOzbg0KDQpMYSBmdW5jacOzbiAiZGltZGVzYygpIiBzZSBwdWVkZSB1dGlsaXphciBwYXJhIGlkZW50aWZpY2FyIGxhcyB2YXJpYWJsZXMgbcOhcyBjb3JyZWxhY2lvbmFkYXMgY29uIHVuYSBkaW1lbnNpw7NuIGRhZGENCg0KYGBge3J9DQpyZXMuZGVzYyA8LSBkaW1kZXNjKHJlcy5tY2EsIGF4ZXMgPSBjKDEsMikpDQpyZXMuZGVzY1tbMV1dDQpyZXMuZGVzY1tbMl1dDQpgYGANCg0KDQojIyMjICoqRXNwZWNpZmljYWNpw7NuIGVuIE1DQSoqDQoNClBhcmEgZXNwZWNpZmljYXIgaW5kaXZpZHVvcyB5IHZhcmlhYmxlcyBzdXBsZW1lbnRhcmlhcywgc2UgcHVlZGUgdXRpbGl6YXIgbGEgZnVuY2nDs24gTUNBICgpIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmENCg0KYGBge3J9DQpNQ0EocG9pc29uLmFjdGl2ZSwgIGluZC5zdXAgPSBOVUxMLCBxdWFudGkuc3VwID0gTlVMTCwgcXVhbGkuc3VwPU5VTEwsDQogICAgZ3JhcGggPSBUUlVFLCBheGVzID0gYygxLDIpKQ0KYGBgDQoNCmBgYHtyfQ0KcmVzLm1jYSA8LSBNQ0EocG9pc29uLCBpbmQuc3VwID0gNTM6NTUsIA0KICAgICAgICAgICAgICAgcXVhbnRpLnN1cCA9IDE6MiwgcXVhbGkuc3VwID0gMzo0LCAgZ3JhcGg9RkFMU0UpDQpgYGANCg0KIyMjIyBSZXN1bHRhZG9zDQoNCkV4dHJhZXIgbG9zIHJlc3VsdGFkb3MgcHJldmlzdG9zIHBhcmEgaW5kaXZpZHVvcy92YXJpYWJsZXMgc3VwbGVtZW50YXJpb3MNCmBgYHtyfQ0KcmVzLm1jYSRxdWFsaS5zdXANCnJlcy5tY2EkcXVhbnRpDQpyZXMubWNhJGluZC5zdXANCmBgYA0KDQojIyMjIFBhcmNlbGFzDQoNCkEgY29udGludWFjacOzbiBzZSByZWFsaXphcsOhIHVuIGJpcGxvdCBkZSBpbmRpdmlkdW9zIHkgY2F0ZWdvcsOtYXMgZGUgdmFyaWFibGVzDQoNCmBgYHtyfQ0KZnZpel9tY2FfYmlwbG90KHJlcy5tY2EsIHJlcGVsID0gVFJVRSwNCiAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfY2xhc3NpYygpKQ0KYGBgDQoqIExvcyBpbmRpdmlkdW9zIGFjdGl2b3MgZXN0w6FuIGVuIGF6dWwuDQoqIExvcyBpbmRpdmlkdW9zIHN1cGxlbWVudGFyaW9zIGVzdMOhbiBlbiBhenVsIG9zY3Vyby4NCiogTGFzIGNhdGVnb3LDrWFzIGRlIHZhcmlhYmxlcyBhY3RpdmFzIGVzdMOhbiBlbiByb2pvDQoqIExhcyBjYXRlZ29yw61hcyBkZSB2YXJpYWJsZXMgc3VwbGVtZW50YXJpYXMgZXN0w6FuIGVuIHZlcmRlIG9zY3Vyby4NCg0KUGFyYSByZXNhbHRhciBsYSBjb3JyZWxhY2nDs24gZW50cmUgdmFyaWFibGVzIChhY3RpdmFzIHkgY29tcGxlbWVudGFyaWFzKSB5IGRpbWVuc2lvbmVzLCBoYXkgcXVlIHVzYXIgbGEgZnVuY2nDs24gImZ2aXpfbWNhX3ZhcigpIiBjb24gZWwgYXJndW1lbnRvIGNob2ljZSA9IOKAnG1jYS5jb3LigJ0NCg0KYGBge3J9DQpmdml6X21jYV92YXIocmVzLm1jYSwgY2hvaWNlID0gIm1jYS5jb3IiLA0KICAgICAgICAgICAgIHJlcGVsID0gVFJVRSkNCmBgYA0KDQpFbCBjw7NkaWdvIFIgYSBjb250aW51YWNpw7NuIHRyYXphIGNhdGVnb3LDrWFzIGRlIHZhcmlhYmxlcyBjdWFsaXRhdGl2YXMgKHZhcmlhYmxlcyBhY3RpdmFzIHkgc3VwbGVtZW50YXJpYXMpOg0KDQpgYGB7cn0NCmZ2aXpfbWNhX3ZhcihyZXMubWNhLCByZXBlbCA9IFRSVUUsDQogICAgICAgICAgICAgZ2d0aGVtZT0gdGhlbWVfZ2V0KCkpDQpgYGANCg0KUGFyYSB2YXJpYWJsZXMgY3VhbnRpdGF0aXZhcyBjb21wbGVtZW50YXJpYXMgKFRpZW1wbyB5IGVkYWQpOg0KDQpgYGB7cn0NCmZ2aXpfbWNhX3ZhcihyZXMubWNhLCBjaG9pY2UgPSAicXVhbnRpLnN1cCIsDQogICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX3VwZGF0ZSgpKQ0KYGBgDQoNClBhcmEgdmlzdWFsaXphciBwZXJzb25hcyBhZGljaW9uYWxlczoNCg0KYGBge3J9DQpmdml6X21jYV9pbmQocmVzLm1jYSwgDQogICAgICAgICAgICAgbGFiZWwgPSAiaW5kLnN1cCIsDQogICAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX2dyZXkoKSkNCmBgYA0KDQojIyMjIEZpbHRyYXIgcmVzdWx0YWRvcw0KDQpTaSBzZSB0aWVuZW4gbXVjaGFzIGNhdGVnb3LDrWFzIGRlIGluZGl2aWR1b3MgbyB2YXJpYWJsZXMsIGVzIHBvc2libGUgdmlzdWFsaXphciBzb2xvIGFsZ3VuYXMgZGUgZWxsYXMgdXRpbGl6YW5kbyBsb3MgYXJndW1lbnRvcyAic2VsZWN0LmluZHkiIHkgInNlbGVjdC52YXIiLg0KDQoqKiJzZWxlY3QuaW5kIiwgInNlbGVjdC52YXIiKio6IHVuYSBzZWxlY2Npw7NuIGRlIGNhdGVnb3LDrWFzIGRlIHZhcmlhYmxlcyBvIGluZGl2aWR1b3MgcXVlIHNlIGV4dHJhZXLDoW4uIExvcyB2YWxvcmVzIHBlcm1pdGlkb3Mgc29uICJOVUxMIiBvIHF1ZSAibGlzdCIgY29udGllbmVuIGxvcyBhcmd1bWVudG9zIG5vbWJyZSwgY29zMiBvIGNvbnRyaWINCg0KYGBge3J9DQpmdml6X21jYV92YXIocmVzLm1jYSwgc2VsZWN0LnZhciA9IGxpc3QoY29zMiA9IDAuNCkpDQoNCmZ2aXpfbWNhX3ZhcihyZXMubWNhLCBzZWxlY3QudmFyPSBsaXN0KGNvczIgPSAxMCkpDQoNCm5hbWUgPC0gbGlzdChuYW1lID0gYygiRmV2ZXJfbiIsICJBYmRvX3kiLCAiRGlhcnJoZWFfbiIsDQogICAgICAgICAgICAgICAgICAgICAgIkZldmVyX1kiLCAiVm9taXRfeSIsICJWb21pdF9uIikpDQoNCmZ2aXpfbWNhX3ZhcihyZXMubWNhLCBzZWxlY3QudmFyID0gbmFtZSkNCg0KZnZpel9tY2FfYmlwbG90KHJlcy5tY2EsIHNlbGVjdC5pbmQgPSBsaXN0KGNvbnRyaWIgPSA1KSwgDQogICAgICAgICAgICAgICBzZWxlY3QudmFyID0gbGlzdChjb250cmliID0gNSksDQogICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfZ3JheSgpKQ0KYGBgDQoNCiMjIyMgKipFeHBvcnRhciBncsOhZmljb3MgYSBhcmNoaXZvcyBQREYvUE5HKioNCg0KSW5pY2lhbG1lbnRlIHNlIHNlZ3VpcsOhbiBkb3MgcGFzbyBhIHNlZ3VpcjoNCjEuIENyZWFyIGxhIHRyYW1hIGRlIGludGVyw6lzIGNvbW8gdW4gb2JqZXRvDQoNCmBgYHtyfQ0Kc2NyZWUucGxvdCA8LSBmdml6X2VpZyhyZXMubWNhKQ0KDQpiaXBsb3QubWNhIDwtIGZ2aXpfbWNhX2JpcGxvdChyZXMubWNhKQ0KYGBgDQoNCjIuIFJlYWxpemFyIGxvcyBncsOhZmljb3MgZW4gdW4gw7puaWNvIGFyY2hpdm8gUERGIA0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwdWJyKQ0KZ2dleHBvcnQocGxvdGxpc3QgPSBsaXN0KHNjcmVlLnBsb3QsIGJpcGxvdC5tY2EpLCANCiAgICAgICAgIGZpbGVuYW1lID0gIk1DQS5wZGYiKQ0KYGBgDQojIyMjIEV4cG9ydGFyIHJlc3VsdGFkb3MgYSBhcmNoaXZvcyB0eHQvY3N2DQoNClNlIGVtcGxlYXLDoSBsYSBmdW5jacOzbiAid3JpdGUuaW5maWxlKCkiDQoNCmBgYHtyfQ0Kd3JpdGUuaW5maWxlKHJlcy5tY2EsICJtY2EudHh0Iiwgc2VwID0gIlx0IikNCndyaXRlLmluZmlsZShyZXMubWNhLCAibWNhLmNzdiIsIHNlcCA9ICI7IikNCmBgYA0KDQoNCg0KRW4gZXN0ZSBlamVyY2ljaW8gc2UgZGVzY3JpYmnDsyBjw7NtbyByZWFsaXphciBlIGludGVycHJldGFyIGVsIGFuw6FsaXNpcyBkZSBjb3JyZXNwb25kZW5jaWEgbcO6bHRpcGxlIChBQykuIFNlIGNhbGN1bMOzIGVsIEFDIHV0aWxpemFuZG8gbGEgZnVuY2nDs24gTUNBICgpLiBBIGNvbnRpbnVhY2nDs24sIHV0aWxpemFtb3MgZWwgcGFxdWV0ZSBSIGZhY3RvZXh0cmEgcGFyYSBnZW5lcmFyIHVuYSB2aXN1YWxpemFjacOzbiBiYXNhZGEgZW4gZ2dwbG90MiBkZSBsb3MgcmVzdWx0YWRvcyBkZWwgQUMuDQoNCg==