Neural Networks - Redes Neuronales con R

Nombre : Noboa Andres

Introduccion

Que es una Red Neuronal ?

Una red neuronal es un modelo caracterizado por una función de activación, que es utilizada por unidades de procesamiento de información interconectadas para transformar la entrada en salida. Una red neuronal siempre se ha comparado con el sistema nervioso humano. La información pasa a través de unidades interconectadas de forma análoga al paso de la información por las neuronas en los seres humanos. La primera capa de la red neuronal recibe la entrada bruta, la procesa y pasa la información procesada a las capas ocultas. La capa oculta pasa la información a la última capa, que produce la salida. La ventaja de la red neuronal es que es de naturaleza adaptativa. Aprende de la información proporcionada, es decir, se entrena a sí misma a partir de los datos que tienen un resultado conocido y optimiza sus pesos para una mejor predicción en situaciones con resultado desconocido.

Partes de una red neuronal

La idea es sencilla: programar cada parte de nuestra red neuronal por separado. Así iremos introduciendo nuevos conceptos de las redes neuronales. Una vez que tengamos todo programado, probaremos nuestra red neuronal en la clasificación para comprobar si funciona correctamente.

Dicho esto, podríamos decir que una red neuronal tiene tres partes:

La estructura de la red neuronal: en esta parte se define el número de capas, las neuronas por capa, las funciones de activación, etc. En resumen, es el esqueleto de la red.

El Perceptrón

Un perceptrón tiene una o más entradas, un sesgo, una función de activación y una única salida. El perceptrón recibe las entradas, las multiplica por algún peso y luego las pasa a una función de activación para producir una salida.

Propagación hacia delante: en este paso conectaremos todas las partes que ya hemos construido. Al hacerlo, la red neuronal será capaz de lanzarnos una predicción. Sin embargo, el resultado será aleatorio ya que aún no ha aprendido.

Retropropagación: en este paso calcularemos el error de la predicción y retropropagaremos el error y ajustaremos todos los parámetros utilizando el descenso de gradiente. De este modo, la red aprenderá.

H20

H2o: Interfaz R para ‘H2O’, la plataforma escalable de aprendizaje automático de código abierto que ofrece implementaciones en paralelo de muchos algoritmos de aprendizaje automático supervisados y no supervisados, como Modelos lineales generalizados (GLM), Gradient Boosting Machines (incluido XGBoost), Random Forests, Deep Neural Networks ( Deep Learning), Stacked Ensembles, Naive Bayes, Generalized Additive Models (GAM), ANOVA GLM, Cox Proportional Hazards, K-Means, PCA, ModelSelection, Word2Vec, así como un algoritmo de aprendizaje automático completamente automático (H2O AutoML).

Se comienza Cargando libreria H2o:

library(h2o)

Paquetes para el tratamiento de datos y graficos

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ──────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.1 ──
✔ ggplot2 3.3.6     ✔ purrr   0.3.4
✔ tibble  3.1.7     ✔ dplyr   1.0.9
✔ tidyr   1.2.0     ✔ stringr 1.4.0
✔ readr   2.1.2     ✔ forcats 0.5.1
── Conflicts ─────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
library(ggthemes)
library(ggpubr)

Iniciación del cluster

Una vez que la librería H2O ha sido cargada, hay que iniciar el cluster. Para este ejemplo, se emplea un único ordenador del que se utilizan todos sus cores en paralelo.


h2o.init(
  ip       = "localhost",
  nthreads = -1,
  max_mem_size = "6g"
)
 Connection successful!

R is connected to the H2O cluster: 
    H2O cluster uptime:         12 minutes 6 seconds 
    H2O cluster timezone:       America/Guayaquil 
    H2O data parsing timezone:  UTC 
    H2O cluster version:        3.36.1.2 
    H2O cluster version age:    26 days  
    H2O cluster name:           H2O_started_from_R_elegant00_gpw733 
    H2O cluster total nodes:    1 
    H2O cluster total memory:   6.00 GB 
    H2O cluster total cores:    8 
    H2O cluster allowed cores:  8 
    H2O cluster healthy:        TRUE 
    H2O Connection ip:          localhost 
    H2O Connection port:        54321 
    H2O Connection proxy:       NA 
    H2O Internal Security:      FALSE 
    R Version:                  R version 4.2.0 (2022-04-22) 

Se observa la carga correcta del paquete H2o

Cargando Datos Simulados

datos = read_csv('https://raw.githubusercontent.com/JoaquinAmatRodrigo/Estadistica-machine-learning-python/master/data/blobs.csv')
Rows: 1500 Columns: 3
── Column specification ─────────────────────────────────────────────────────────────────────────────────────────────────────
Delimiter: ","
dbl (3): y, x_1, x_2

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
datos <- datos %>% mutate(y = as.factor(y))

ggplot(data = datos, aes(x = x_1, y = x_2, fill = y)) + 
  geom_point(shape = 21, size = 2) +
  theme_fivethirtyeight() + 
  theme(
    legend.position = "none",
    text = element_blank(),
    axis.ticks =  element_blank()
  )

Creando particiones train , test y validation:

df_h2o   <- as.h2o(datos)
particiones <- h2o.splitFrame(data = df_h2o, ratios = c(0.6, 0.2), seed = 123)
df_train <- h2o.assign(data = particiones[[1]], key = "df_train")
df_val  <- h2o.assign(data = particiones[[2]], key = "datos_validacion")
df_test  <- h2o.assign(data = particiones[[3]], key = "df_test")

Observamos el shape o dimension de los datos particionados

library(glue)

glue("Dimension de datos de training {dim(df_train)}")
Dimension de datos de training 918
Dimension de datos de training 3
glue("Dimension de datos de validation {dim(df_val)}")
Dimension de datos de validation 281
Dimension de datos de validation 3
glue("Dimension de datos de testing {dim(df_test)}")
Dimension de datos de testing 301
Dimension de datos de testing 3

Existen 918,281 y 301 datos para training, validacion y testing respectivamente.

Ahora se procedera a crear 4 modelos mediante h20.deeplearning

Prediccion Modelos Originales

# Modelos
# ==============================================================================
modelo_1 <- h2o.deeplearning(
                  x               = c("x_1", "x_2"),
                  y               = "y",
                  distribution    = "multinomial",
                  training_frame  = df_train,
                  standardize     = TRUE,
                  activation      = "Rectifier",
                  adaptive_rate   = FALSE,
                  hidden          = 1,
                  stopping_rounds = 0,
                  epochs          = 1000,
                  seed            = 123,
                  model_id        = "modelo_1"
           )

modelo_2 <- h2o.deeplearning(
                x               = c("x_1", "x_2"),
                y               = "y",
                distribution    = "multinomial",
                training_frame  = df_train,
                standardize     = TRUE,
                activation      = "Rectifier",
                adaptive_rate   = FALSE,
                hidden          = 10,
                stopping_rounds = 0,
                epochs          = 1000,
                seed            = 123,
                model_id        = "modelo_2"
            )

modelo_3 <- h2o.deeplearning(
                  x               = c("x_1", "x_2"),
                  y               = "y",
                  distribution    = "multinomial",
                  training_frame  = df_train,
                  standardize     = TRUE,
                  activation      = "Rectifier",
                  adaptive_rate   = FALSE,
                  hidden          = c(10, 10),
                  stopping_rounds = 0,
                  epochs          = 1000,
                  seed            = 123,
                  model_id        = "modelo_3"
            )

modelo_4 <- h2o.deeplearning(
                  x               = c("x_1", "x_2"),
                  y               = "y",
                  distribution    = "multinomial",
                  training_frame  = df_train,
                  standardize     = TRUE,
                  activation      = "Rectifier",
                  adaptive_rate   = FALSE,
                  hidden          = c(50, 50, 50),
                  stopping_rounds = 0,
                  epochs          = 1000,
                  seed            = 123,
                  model_id        = "modelo_4"
            )

Definicion de Arquitectura Distinta - Mayor Cantidad de Neurons

Definicion de predicciones para los 4 modelos originales:

# Predicciones de cada modelo
# ==============================================================================

grid_predicciones <- expand.grid(
                        x_1 = seq(from = min(datos$x_1), to = max(datos$x_1), length = 75),
                        x_2 = seq(from = min(datos$x_2), to = max(datos$x_2), length = 75)
                     )

grid_predicciones_h2o <- as.h2o(grid_predicciones)


predicciones_1 <- h2o.predict(
                    object  = modelo_1,
                    newdata = grid_predicciones_h2o
                  )

predicciones_2 <- h2o.predict(
                    object  = modelo_2,
                    newdata = grid_predicciones_h2o
                  )

predicciones_3 <- h2o.predict(
                    object  = modelo_3,
                    newdata = grid_predicciones_h2o
                  )

predicciones_4 <- h2o.predict(
                    object  = modelo_4,
                    newdata = grid_predicciones_h2o
                  )


grid_predicciones$modelo_1 <- as.vector(predicciones_1$predict)
grid_predicciones$modelo_2 <- as.vector(predicciones_2$predict)
grid_predicciones$modelo_3 <- as.vector(predicciones_3$predict)
grid_predicciones$modelo_4 <- as.vector(predicciones_4$predict)
# Gráfico de predicciones
# ==============================================================================
p1 <- ggplot(data = grid_predicciones, aes(x = x_1, y = x_2, color = modelo_1)) + 
      geom_point(size = 0.5) +
      theme_fivethirtyeight() +  
      labs(title = "Arquitectura: (5)") +
      theme(legend.position = "none",
            plot.title = element_text(size=11),
            axis.text  = element_blank(),
            axis.title = element_blank(),
            axis.ticks =  element_blank())

p2 <- ggplot(data = grid_predicciones, aes(x = x_1, y = x_2, color =modelo_2)) + 
      geom_point(size = 0.5) +
      labs(title = "Arquitectura: (10)") +
      theme_fivethirtyeight() + 
      theme(legend.position = "none",
            plot.title = element_text(size=11),
            axis.text  = element_blank(),
            axis.title = element_blank(),
            axis.ticks =  element_blank())

p3 <- ggplot(data = grid_predicciones, aes(x = x_1, y = x_2, color = modelo_3)) + 
      geom_point(size = 0.5) +
      labs(title = "Arquitectura: (20, 20)") +
      theme_fivethirtyeight() + 
      theme(legend.position = "none",
            plot.title = element_text(size=11),
            axis.text  = element_blank(),
            axis.title = element_blank(),
            axis.ticks =  element_blank())

p4 <- ggplot(data = grid_predicciones, aes(x = x_1, y = x_2, color = modelo_4)) + 
      geom_point(size = 0.5) +
      labs(title = "Arquitectura: (50, 50, 50)") +
      theme_fivethirtyeight() + 
      theme(legend.position = "none",
            plot.title = element_text(size=11),
            axis.text  = element_blank(),
            axis.title = element_blank(),
            axis.ticks =  element_blank())


ggarrange(p1, p2, p3, p4, nrow = 2, ncol = 2)

Creacion de Nuevos Modelos con mayor cantidad de hidden Layers (Capas Ocultas)

A continuacion , se crea diferentes modelos aplicando una arquitectura con diferentes parametros , en este caso:

  1. Modelo 1 : Modelo Con 6 capas ocultas de 30 neuronas cada una, activacion ReLU sin dropout
  2. Modelo 2 : Modelo con 1 sola capa oculta de 500 neuronas, activacion ReLU con dropout
  3. Modelo 3 : Modelo con 10 capas ocultas de 10 neuronas cada una , activacion tanh sin dropout
  4. Modelo 4 : Modelo con 3 capas ocultas de 10 neuronas cada una , activacion tanh sin dropout

Tanh:

\[ tanh = \frac{e^x – e^-x}{e^x + e^-x}\] Se consideran valores negativos, mientras que en el rango mínimo sigmoideo es 0 pero en Tanh, el rango mínimo es -1. Esta es la razón por la que la función de activación de Tanh también se conoce como función de activación centrada en cero.

Desventaja:

También enfrenta el mismo problema del problema del gradiente de fuga como una función sigmoidea.

# Definicion Modelos Custom
# ==============================================================================


# Modelo 1 
model_1 <- h2o.deeplearning(
                  x               = c("x_1", "x_2"),
                  y               = "y",
                  distribution    = "multinomial",
                  training_frame  = df_train,
                  standardize     = TRUE,
                  activation      = "Rectifier",
                  adaptive_rate   = FALSE,
                  hidden          = c(30,30,30,30,30,30),
                  stopping_rounds = 0,
                  epochs          = 1000,
                  seed            = 123,
                  model_id        = "model_1"
           )


# Modelo 2
model_2 <- h2o.deeplearning(
                  x               = c("x_1", "x_2"),
                  y               = "y",
                  distribution    = "multinomial",
                  training_frame  = df_train,
                  standardize     = TRUE,
                  activation      = "RectifierWithDropout",
                  adaptive_rate   = FALSE,
                  hidden          = 500,
                  stopping_rounds = 0,
                  epochs          = 1000,
                  seed            = 123,
                  model_id        = "model_2"
           )


# Modelo 3
model_3 <- h2o.deeplearning(
                  x               = c("x_1", "x_2"),
                  y               = "y",
                  distribution    = "multinomial",
                  training_frame  = df_train,
                  standardize     = TRUE,
                  activation      = "Tanh",
                  adaptive_rate   = FALSE,
                  hidden          = c(10,10,10,10,10,10,10,10,10,10),
                  stopping_rounds = 0,
                  epochs          = 1000,
                  seed            = 123,
                  model_id        = "model_3"
           )


# Modelo 4
model_4 <- h2o.deeplearning(
                  x               = c("x_1", "x_2"),
                  y               = "y",
                  distribution    = "multinomial",
                  training_frame  = df_train,
                  standardize     = TRUE,
                  activation      = "Tanh",
                  adaptive_rate   = FALSE,
                  hidden          = c(10,10,10),
                  stopping_rounds = 0,
                  epochs          = 1000,
                  seed            = 123,
                  model_id        = "model_4"
           )

Generando predicciones con los nuevos modelos:



grid_new <- expand.grid(

                        x_1 = seq(from = min(datos$x_1), to = max(datos$x_1), length = 75),
                        x_2 = seq(from = min(datos$x_2), to = max(datos$x_2), length = 75)
                     )

grid_new_predict <- as.h2o(grid_new)


predict_1 <- h2o.predict(
                    object  = model_1,
                    newdata = grid_new_predict
                  )

predict_2 <- h2o.predict(
                    object  = model_2,
                    newdata = grid_new_predict
                  )

predict_3 <- h2o.predict(
                    object  = model_3,
                    newdata = grid_new_predict 
                  )

predict_4 <- h2o.predict(
                    object  = model_4,
                    newdata = grid_new_predict
                  )

grid_new$model_1 <- as.vector(predict_1$predict)
grid_new$model_2 <- as.vector(predict_2$predict)
grid_new$model_3 <- as.vector(predict_3$predict)
grid_new$model_4 <- as.vector(predict_4$predict)

Predicciones con nuevos Modelos:

# Gráfico de predicciones
# ==============================================================================
p1 <- ggplot(data = grid_new, aes(x = x_1, y = x_2, color = model_1)) + 
      geom_point(size = 0.5) +
      theme_economist() +  
      labs(title = "Arquitectura: (30,30,30,30,30,30) ReLU") +
      theme(legend.position = "none",
            plot.title = element_text(size=11),
            axis.text  = element_blank(),
            axis.title = element_blank(),
            axis.ticks =  element_blank())

p2 <- ggplot(data = grid_new, aes(x = x_1, y = x_2, color = model_2)) + 
      geom_point(size = 0.5) +
      labs(title = "Arquitectura: Neurona Unica ReLU Dropout ") +
      theme_economist() + 
      theme(legend.position = "none",
            plot.title = element_text(size=11),
            axis.text  = element_blank(),
            axis.title = element_blank(),
            axis.ticks =  element_blank())

p3 <- ggplot(data = grid_new, aes(x = x_1, y = x_2, color = model_3)) + 
      geom_point(size = 0.5) +
      labs(title = "Arquitectura c(10,10,10,10,10,10,10,10,10,10) TanH ") +
      theme_economist() + 
      theme(legend.position = "none",
            plot.title = element_text(size=11),
            axis.text  = element_blank(),
            axis.title = element_blank(),
            axis.ticks =  element_blank())

p4 <- ggplot(data = grid_new, aes(x = x_1, y = x_2, color = model_4)) + 

      geom_point(size = 0.5) +
      labs(title = "Arquitectura: c(10, 10, 10) tanh") +
      theme_economist() + 
      theme(legend.position = "none",
            plot.title = element_text(size=11),
            axis.text  = element_blank(),
            axis.title = element_blank(),
            axis.ticks =  element_blank())


ggarrange(p1, p2, p3, p4, nrow = 2, ncol = 2)

Comparacion de Accuracy entre modelos en el dataset de test:

predict_test <- h2o.predict(object  = model_1,newdata = df_test)
accuracy_1_new <- mean(predict_test["predict"] == df_test["y"])

predict_test <- h2o.predict(object  = model_2,newdata = df_test)
accuracy_2_new <- mean(predict_test["predict"] == df_test["y"])

predict_test <- h2o.predict(object  = model_3,newdata = df_test)
accuracy_3_new <- mean(predict_test["predict"] == df_test["y"])

predict_test <- h2o.predict(object  = model_4,newdata = df_test)
accuracy_4_new <- mean(predict_test["predict"] == df_test["y"])
  glue("Accuracy del Modelo 1 : {accuracy_1} \n
     Accuracy del Modelo 2 : {accuracy_2} \n
     Accuracy del Modelo 3 : {accuracy_3} \n
     Accuracy del Modelo 4 : {accuracy_4} \n
     " )
Accuracy del Modelo 1 : 0.744186046511628 

Accuracy del Modelo 2 : 0.867109634551495 

Accuracy del Modelo 3 : 0.880398671096346 

Accuracy del Modelo 4 : 0.863787375415282 

predict_test <- h2o.predict(object  = model_1,newdata = df_test)
accuracy_1_new <- mean(predict_test["predict"] == df_test["y"])

predict_test <- h2o.predict(object  = model_2,newdata = df_test)
accuracy_2_new <- mean(predict_test["predict"] == df_test["y"])

predict_test <- h2o.predict(object  = model_3,newdata = df_test)
accuracy_3_new <- mean(predict_test["predict"] == df_test["y"])

predict_test <- h2o.predict(object  = model_4,newdata = df_test)
accuracy_4_new <- mean(predict_test["predict"] == df_test["y"])

Imprimiendo el accuracy de cada modelo:

  glue("Accuracy del Modelo 1  Nuevo: {accuracy_1_new} \n
     Accuracy del Modelo 2 Nuevo: {accuracy_2_new} \n
     Accuracy del Modelo 3 Nuevo: {accuracy_3_new} \n
     Accuracy del Modelo 4 Nuevo : {accuracy_4_new} \n
     " )
Accuracy del Modelo 1  Nuevo: 0.857142857142857 

Accuracy del Modelo 2 Nuevo: 0.853820598006645 

Accuracy del Modelo 3 Nuevo: 0.847176079734219 

Accuracy del Modelo 4 Nuevo : 0.863787375415282 

Se grafica todas las accuracies de los modelos planteados:


accuracy <- c(accuracy_1,accuracy_2,accuracy_3,accuracy_4, accuracy_1_new, accuracy_2_new,accuracy_3_new,accuracy_4_new)


model <- c("Modelo 1", "Modelo 2","Modelo 3","Modelo 4", "Modelo 1 nuevo", "Modelo 2 Nuevo","Modelo 3 Nuevo","Modelo 4 Nuevo")




data <- data.frame(accuracy, model)    
ggplot(data,aes(x= reorder(model,-accuracy),y= accuracy, fill=model ),title("Accuracy de Modelos Planteados "))+geom_bar(stat ="identity") + theme(axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1))

Se observa que el mejor creado corresponde al Modelo 3, con un 88% de certeza en los datos de testing, recordando que el modelo 3 posee una arquitectura con de 2 capas ocultas c(10, 10) de 10 neuronas cada una.

LS0tCnRpdGxlOiAiTmV1cmFsIE5ldHdvcmtzIGNvbiBSIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIHBkZl9kb2N1bWVudDogZGVmYXVsdAotLS0KCiMgKk5ldXJhbCBOZXR3b3JrcyogLSBSZWRlcyBOZXVyb25hbGVzIGNvbiBSCgojIyAqKk5vbWJyZSA6KiogTm9ib2EgQW5kcmVzCgojIyMgSW50cm9kdWNjaW9uCgojIyMjIFF1ZSBlcyB1bmEgUmVkIE5ldXJvbmFsID8KCiFbXShodHRwczovL3d3dy5rZG51Z2dldHMuY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy9hbm4taW4taGlkZGVuLW91dC5qcGcpCgpVbmEgcmVkIG5ldXJvbmFsIGVzIHVuIG1vZGVsbyBjYXJhY3Rlcml6YWRvIHBvciB1bmEgZnVuY2nDs24gZGUgYWN0aXZhY2nDs24sIHF1ZSBlcyB1dGlsaXphZGEgcG9yIHVuaWRhZGVzIGRlIHByb2Nlc2FtaWVudG8gZGUgaW5mb3JtYWNpw7NuIGludGVyY29uZWN0YWRhcyBwYXJhIHRyYW5zZm9ybWFyIGxhIGVudHJhZGEgZW4gc2FsaWRhLiBVbmEgcmVkIG5ldXJvbmFsIHNpZW1wcmUgc2UgaGEgY29tcGFyYWRvIGNvbiBlbCBzaXN0ZW1hIG5lcnZpb3NvIGh1bWFuby4gTGEgaW5mb3JtYWNpw7NuIHBhc2EgYSB0cmF2w6lzIGRlIHVuaWRhZGVzIGludGVyY29uZWN0YWRhcyBkZSBmb3JtYSBhbsOhbG9nYSBhbCBwYXNvIGRlIGxhIGluZm9ybWFjacOzbiBwb3IgbGFzIG5ldXJvbmFzIGVuIGxvcyBzZXJlcyBodW1hbm9zLiBMYSBwcmltZXJhIGNhcGEgZGUgbGEgcmVkIG5ldXJvbmFsIHJlY2liZSBsYSBlbnRyYWRhIGJydXRhLCBsYSBwcm9jZXNhIHkgcGFzYSBsYSBpbmZvcm1hY2nDs24gcHJvY2VzYWRhIGEgbGFzIGNhcGFzIG9jdWx0YXMuIExhIGNhcGEgb2N1bHRhIHBhc2EgbGEgaW5mb3JtYWNpw7NuIGEgbGEgw7psdGltYSBjYXBhLCBxdWUgcHJvZHVjZSBsYSBzYWxpZGEuIExhIHZlbnRhamEgZGUgbGEgcmVkIG5ldXJvbmFsIGVzIHF1ZSBlcyBkZSBuYXR1cmFsZXphIGFkYXB0YXRpdmEuIEFwcmVuZGUgZGUgbGEgaW5mb3JtYWNpw7NuIHByb3BvcmNpb25hZGEsIGVzIGRlY2lyLCBzZSBlbnRyZW5hIGEgc8OtIG1pc21hIGEgcGFydGlyIGRlIGxvcyBkYXRvcyBxdWUgdGllbmVuIHVuIHJlc3VsdGFkbyBjb25vY2lkbyB5IG9wdGltaXphIHN1cyBwZXNvcyBwYXJhIHVuYSBtZWpvciBwcmVkaWNjacOzbiBlbiBzaXR1YWNpb25lcyBjb24gcmVzdWx0YWRvIGRlc2Nvbm9jaWRvLgoKKipQYXJ0ZXMgZGUgdW5hIHJlZCBuZXVyb25hbCoqCgpMYSBpZGVhIGVzIHNlbmNpbGxhOiBwcm9ncmFtYXIgY2FkYSBwYXJ0ZSBkZSBudWVzdHJhIHJlZCBuZXVyb25hbCBwb3Igc2VwYXJhZG8uIEFzw60gaXJlbW9zIGludHJvZHVjaWVuZG8gbnVldm9zIGNvbmNlcHRvcyBkZSBsYXMgcmVkZXMgbmV1cm9uYWxlcy4gVW5hIHZleiBxdWUgdGVuZ2Ftb3MgdG9kbyBwcm9ncmFtYWRvLCBwcm9iYXJlbW9zIG51ZXN0cmEgcmVkIG5ldXJvbmFsIGVuIGxhIGNsYXNpZmljYWNpw7NuIHBhcmEgY29tcHJvYmFyIHNpIGZ1bmNpb25hIGNvcnJlY3RhbWVudGUuCgpEaWNobyBlc3RvLCBwb2Ryw61hbW9zIGRlY2lyIHF1ZSB1bmEgcmVkIG5ldXJvbmFsIHRpZW5lIHRyZXMgcGFydGVzOgoKKipMYSBlc3RydWN0dXJhIGRlIGxhIHJlZCBuZXVyb25hbDoqKiBlbiBlc3RhIHBhcnRlIHNlIGRlZmluZSBlbCBuw7ptZXJvIGRlIGNhcGFzLCBsYXMgbmV1cm9uYXMgcG9yIGNhcGEsIGxhcyBmdW5jaW9uZXMgZGUgYWN0aXZhY2nDs24sIGV0Yy4gRW4gcmVzdW1lbiwgZXMgZWwgZXNxdWVsZXRvIGRlIGxhIHJlZC4KCioqRWwgUGVyY2VwdHLDs24qKgoKVW4gcGVyY2VwdHLDs24gdGllbmUgdW5hIG8gbcOhcyBlbnRyYWRhcywgdW4gc2VzZ28sIHVuYSBmdW5jacOzbiBkZSBhY3RpdmFjacOzbiB5IHVuYSDDum5pY2Egc2FsaWRhLiBFbCBwZXJjZXB0csOzbiByZWNpYmUgbGFzIGVudHJhZGFzLCBsYXMgbXVsdGlwbGljYSBwb3IgYWxnw7puIHBlc28geSBsdWVnbyBsYXMgcGFzYSBhIHVuYSBmdW5jacOzbiBkZSBhY3RpdmFjacOzbiBwYXJhIHByb2R1Y2lyIHVuYSBzYWxpZGEuCgohW10oaHR0cHM6Ly93d3cua2RudWdnZXRzLmNvbS93cC1jb250ZW50L3VwbG9hZHMvcGVyY2VwdHJvbi5qcGcpCgoqKlByb3BhZ2FjacOzbiBoYWNpYSBkZWxhbnRlOioqIGVuIGVzdGUgcGFzbyBjb25lY3RhcmVtb3MgdG9kYXMgbGFzIHBhcnRlcyBxdWUgeWEgaGVtb3MgY29uc3RydWlkby4gQWwgaGFjZXJsbywgbGEgcmVkIG5ldXJvbmFsIHNlcsOhIGNhcGF6IGRlIGxhbnphcm5vcyB1bmEgcHJlZGljY2nDs24uIFNpbiBlbWJhcmdvLCBlbCByZXN1bHRhZG8gc2Vyw6EgYWxlYXRvcmlvIHlhIHF1ZSBhw7puIG5vIGhhIGFwcmVuZGlkby4KCioqUmV0cm9wcm9wYWdhY2nDs246KiogZW4gZXN0ZSBwYXNvIGNhbGN1bGFyZW1vcyBlbCBlcnJvciBkZSBsYSBwcmVkaWNjacOzbiB5IHJldHJvcHJvcGFnYXJlbW9zIGVsIGVycm9yIHkgYWp1c3RhcmVtb3MgdG9kb3MgbG9zIHBhcsOhbWV0cm9zIHV0aWxpemFuZG8gZWwgZGVzY2Vuc28gZGUgZ3JhZGllbnRlLiBEZSBlc3RlIG1vZG8sIGxhIHJlZCBhcHJlbmRlcsOhLgoKIyMjIEgyMAoKSDJvOiBJbnRlcmZheiBSIHBhcmEgJ0gyTycsIGxhIHBsYXRhZm9ybWEgZXNjYWxhYmxlIGRlIGFwcmVuZGl6YWplIGF1dG9tw6F0aWNvIGRlIGPDs2RpZ28gYWJpZXJ0byBxdWUgb2ZyZWNlIGltcGxlbWVudGFjaW9uZXMgZW4gcGFyYWxlbG8gZGUgbXVjaG9zIGFsZ29yaXRtb3MgZGUgYXByZW5kaXphamUgYXV0b23DoXRpY28gc3VwZXJ2aXNhZG9zIHkgbm8gc3VwZXJ2aXNhZG9zLCBjb21vIE1vZGVsb3MgbGluZWFsZXMgZ2VuZXJhbGl6YWRvcyAoR0xNKSwgR3JhZGllbnQgQm9vc3RpbmcgTWFjaGluZXMgKGluY2x1aWRvIFhHQm9vc3QpLCBSYW5kb20gRm9yZXN0cywgRGVlcCBOZXVyYWwgTmV0d29ya3MgKCBEZWVwIExlYXJuaW5nKSwgU3RhY2tlZCBFbnNlbWJsZXMsIE5haXZlIEJheWVzLCBHZW5lcmFsaXplZCBBZGRpdGl2ZSBNb2RlbHMgKEdBTSksIEFOT1ZBIEdMTSwgQ294IFByb3BvcnRpb25hbCBIYXphcmRzLCBLLU1lYW5zLCBQQ0EsIE1vZGVsU2VsZWN0aW9uLCBXb3JkMlZlYywgYXPDrSBjb21vIHVuIGFsZ29yaXRtbyBkZSBhcHJlbmRpemFqZSBhdXRvbcOhdGljbyBjb21wbGV0YW1lbnRlIGF1dG9tw6F0aWNvIChIMk8gQXV0b01MKS4KClNlIGNvbWllbnphIENhcmdhbmRvIGxpYnJlcmlhIEgybzoKCmBgYHtyfQpsaWJyYXJ5KGgybykKYGBgCgpQYXF1ZXRlcyBwYXJhIGVsIHRyYXRhbWllbnRvIGRlIGRhdG9zIHkgZ3JhZmljb3MKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeShnZ3B1YnIpCmBgYAoKSW5pY2lhY2nDs24gZGVsIGNsdXN0ZXIKClVuYSB2ZXogcXVlIGxhIGxpYnJlcsOtYSBIMk8gaGEgc2lkbyBjYXJnYWRhLCBoYXkgcXVlIGluaWNpYXIgZWwgY2x1c3Rlci4gUGFyYSBlc3RlIGVqZW1wbG8sIHNlIGVtcGxlYSB1biDDum5pY28gb3JkZW5hZG9yIGRlbCBxdWUgc2UgdXRpbGl6YW4gdG9kb3Mgc3VzIGNvcmVzIGVuIHBhcmFsZWxvLgoKYGBge3J9CgpoMm8uaW5pdCgKICBpcCAgICAgICA9ICJsb2NhbGhvc3QiLAogIG50aHJlYWRzID0gLTEsCiAgbWF4X21lbV9zaXplID0gIjZnIgopCmBgYAoKU2Ugb2JzZXJ2YSBsYSBjYXJnYSBjb3JyZWN0YSBkZWwgcGFxdWV0ZSBIMm8KCiMgQ2FyZ2FuZG8gRGF0b3MgU2ltdWxhZG9zCgpgYGB7cn0KZGF0b3MgPSByZWFkX2NzdignaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL0pvYXF1aW5BbWF0Um9kcmlnby9Fc3RhZGlzdGljYS1tYWNoaW5lLWxlYXJuaW5nLXB5dGhvbi9tYXN0ZXIvZGF0YS9ibG9icy5jc3YnKQoKCmRhdG9zIDwtIGRhdG9zICU+JSBtdXRhdGUoeSA9IGFzLmZhY3Rvcih5KSkKCmdncGxvdChkYXRhID0gZGF0b3MsIGFlcyh4ID0geF8xLCB5ID0geF8yLCBmaWxsID0geSkpICsgCiAgZ2VvbV9wb2ludChzaGFwZSA9IDIxLCBzaXplID0gMikgKwogIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpICsgCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICB0ZXh0ID0gZWxlbWVudF9ibGFuaygpLAogICAgYXhpcy50aWNrcyA9ICBlbGVtZW50X2JsYW5rKCkKICApCgpgYGAKCkNyZWFuZG8gcGFydGljaW9uZXMgdHJhaW4gLCB0ZXN0IHkgdmFsaWRhdGlvbjoKCi0gICBBc2lnbmFuZG8gdW4gcmF0aW8gZGUgODAsMjAgY29uIGBoMm8uc3BsaXRGcmFtZWA6Ci0gICBSYW5kb20gU2VlZCBkZSAxMjMKCmBgYHtyfQpkZl9oMm8gICA8LSBhcy5oMm8oZGF0b3MpCnBhcnRpY2lvbmVzIDwtIGgyby5zcGxpdEZyYW1lKGRhdGEgPSBkZl9oMm8sIHJhdGlvcyA9IGMoMC42LCAwLjIpLCBzZWVkID0gMTIzKQpkZl90cmFpbiA8LSBoMm8uYXNzaWduKGRhdGEgPSBwYXJ0aWNpb25lc1tbMV1dLCBrZXkgPSAiZGZfdHJhaW4iKQpkZl92YWwgIDwtIGgyby5hc3NpZ24oZGF0YSA9IHBhcnRpY2lvbmVzW1syXV0sIGtleSA9ICJkYXRvc192YWxpZGFjaW9uIikKZGZfdGVzdCAgPC0gaDJvLmFzc2lnbihkYXRhID0gcGFydGljaW9uZXNbWzNdXSwga2V5ID0gImRmX3Rlc3QiKQpgYGAKCk9ic2VydmFtb3MgZWwgc2hhcGUgbyBkaW1lbnNpb24gZGUgbG9zIGRhdG9zIHBhcnRpY2lvbmFkb3MKCmBgYHtyfQpsaWJyYXJ5KGdsdWUpCgpnbHVlKCJEaW1lbnNpb24gZGUgZGF0b3MgZGUgdHJhaW5pbmcge2RpbShkZl90cmFpbil9IikKCgpnbHVlKCJEaW1lbnNpb24gZGUgZGF0b3MgZGUgdmFsaWRhdGlvbiB7ZGltKGRmX3ZhbCl9IikKCmdsdWUoIkRpbWVuc2lvbiBkZSBkYXRvcyBkZSB0ZXN0aW5nIHtkaW0oZGZfdGVzdCl9IikKYGBgCgpFeGlzdGVuIDkxOCwyODEgeSAzMDEgZGF0b3MgcGFyYSB0cmFpbmluZywgdmFsaWRhY2lvbiB5IHRlc3RpbmcgcmVzcGVjdGl2YW1lbnRlLgoKQWhvcmEgc2UgcHJvY2VkZXJhIGEgY3JlYXIgNCBtb2RlbG9zIG1lZGlhbnRlIGBoMjAuZGVlcGxlYXJuaW5nYAoKIyMjIFByZWRpY2Npb24gTW9kZWxvcyBPcmlnaW5hbGVzCgpgYGB7cn0KIyBNb2RlbG9zCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Cm1vZGVsb18xIDwtIGgyby5kZWVwbGVhcm5pbmcoCiAgICAgICAgICAgICAgICAgIHggICAgICAgICAgICAgICA9IGMoInhfMSIsICJ4XzIiKSwKICAgICAgICAgICAgICAgICAgeSAgICAgICAgICAgICAgID0gInkiLAogICAgICAgICAgICAgICAgICBkaXN0cmlidXRpb24gICAgPSAibXVsdGlub21pYWwiLAogICAgICAgICAgICAgICAgICB0cmFpbmluZ19mcmFtZSAgPSBkZl90cmFpbiwKICAgICAgICAgICAgICAgICAgc3RhbmRhcmRpemUgICAgID0gVFJVRSwKICAgICAgICAgICAgICAgICAgYWN0aXZhdGlvbiAgICAgID0gIlJlY3RpZmllciIsCiAgICAgICAgICAgICAgICAgIGFkYXB0aXZlX3JhdGUgICA9IEZBTFNFLAogICAgICAgICAgICAgICAgICBoaWRkZW4gICAgICAgICAgPSAxLAogICAgICAgICAgICAgICAgICBzdG9wcGluZ19yb3VuZHMgPSAwLAogICAgICAgICAgICAgICAgICBlcG9jaHMgICAgICAgICAgPSAxMDAwLAogICAgICAgICAgICAgICAgICBzZWVkICAgICAgICAgICAgPSAxMjMsCiAgICAgICAgICAgICAgICAgIG1vZGVsX2lkICAgICAgICA9ICJtb2RlbG9fMSIKICAgICAgICAgICApCgptb2RlbG9fMiA8LSBoMm8uZGVlcGxlYXJuaW5nKAogICAgICAgICAgICAgICAgeCAgICAgICAgICAgICAgID0gYygieF8xIiwgInhfMiIpLAogICAgICAgICAgICAgICAgeSAgICAgICAgICAgICAgID0gInkiLAogICAgICAgICAgICAgICAgZGlzdHJpYnV0aW9uICAgID0gIm11bHRpbm9taWFsIiwKICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lICA9IGRmX3RyYWluLAogICAgICAgICAgICAgICAgc3RhbmRhcmRpemUgICAgID0gVFJVRSwKICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gICAgICA9ICJSZWN0aWZpZXIiLAogICAgICAgICAgICAgICAgYWRhcHRpdmVfcmF0ZSAgID0gRkFMU0UsCiAgICAgICAgICAgICAgICBoaWRkZW4gICAgICAgICAgPSAxMCwKICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDAsCiAgICAgICAgICAgICAgICBlcG9jaHMgICAgICAgICAgPSAxMDAwLAogICAgICAgICAgICAgICAgc2VlZCAgICAgICAgICAgID0gMTIzLAogICAgICAgICAgICAgICAgbW9kZWxfaWQgICAgICAgID0gIm1vZGVsb18yIgogICAgICAgICAgICApCgptb2RlbG9fMyA8LSBoMm8uZGVlcGxlYXJuaW5nKAogICAgICAgICAgICAgICAgICB4ICAgICAgICAgICAgICAgPSBjKCJ4XzEiLCAieF8yIiksCiAgICAgICAgICAgICAgICAgIHkgICAgICAgICAgICAgICA9ICJ5IiwKICAgICAgICAgICAgICAgICAgZGlzdHJpYnV0aW9uICAgID0gIm11bHRpbm9taWFsIiwKICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgID0gZGZfdHJhaW4sCiAgICAgICAgICAgICAgICAgIHN0YW5kYXJkaXplICAgICA9IFRSVUUsCiAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gICAgICA9ICJSZWN0aWZpZXIiLAogICAgICAgICAgICAgICAgICBhZGFwdGl2ZV9yYXRlICAgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgaGlkZGVuICAgICAgICAgID0gYygxMCwgMTApLAogICAgICAgICAgICAgICAgICBzdG9wcGluZ19yb3VuZHMgPSAwLAogICAgICAgICAgICAgICAgICBlcG9jaHMgICAgICAgICAgPSAxMDAwLAogICAgICAgICAgICAgICAgICBzZWVkICAgICAgICAgICAgPSAxMjMsCiAgICAgICAgICAgICAgICAgIG1vZGVsX2lkICAgICAgICA9ICJtb2RlbG9fMyIKICAgICAgICAgICAgKQoKbW9kZWxvXzQgPC0gaDJvLmRlZXBsZWFybmluZygKICAgICAgICAgICAgICAgICAgeCAgICAgICAgICAgICAgID0gYygieF8xIiwgInhfMiIpLAogICAgICAgICAgICAgICAgICB5ICAgICAgICAgICAgICAgPSAieSIsCiAgICAgICAgICAgICAgICAgIGRpc3RyaWJ1dGlvbiAgICA9ICJtdWx0aW5vbWlhbCIsCiAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lICA9IGRmX3RyYWluLAogICAgICAgICAgICAgICAgICBzdGFuZGFyZGl6ZSAgICAgPSBUUlVFLAogICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uICAgICAgPSAiUmVjdGlmaWVyIiwKICAgICAgICAgICAgICAgICAgYWRhcHRpdmVfcmF0ZSAgID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgIGhpZGRlbiAgICAgICAgICA9IGMoNTAsIDUwLCA1MCksCiAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDAsCiAgICAgICAgICAgICAgICAgIGVwb2NocyAgICAgICAgICA9IDEwMDAsCiAgICAgICAgICAgICAgICAgIHNlZWQgICAgICAgICAgICA9IDEyMywKICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgICAgICAgID0gIm1vZGVsb180IgogICAgICAgICAgICApCmBgYAoKIyMjIERlZmluaWNpb24gZGUgQXJxdWl0ZWN0dXJhIERpc3RpbnRhIC0gTWF5b3IgQ2FudGlkYWQgZGUgTmV1cm9ucwoKRGVmaW5pY2lvbiBkZSBwcmVkaWNjaW9uZXMgcGFyYSBsb3MgNCBtb2RlbG9zIG9yaWdpbmFsZXM6CgpgYGB7cn0KIyBQcmVkaWNjaW9uZXMgZGUgY2FkYSBtb2RlbG8KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCmdyaWRfcHJlZGljY2lvbmVzIDwtIGV4cGFuZC5ncmlkKAogICAgICAgICAgICAgICAgICAgICAgICB4XzEgPSBzZXEoZnJvbSA9IG1pbihkYXRvcyR4XzEpLCB0byA9IG1heChkYXRvcyR4XzEpLCBsZW5ndGggPSA3NSksCiAgICAgICAgICAgICAgICAgICAgICAgIHhfMiA9IHNlcShmcm9tID0gbWluKGRhdG9zJHhfMiksIHRvID0gbWF4KGRhdG9zJHhfMiksIGxlbmd0aCA9IDc1KQogICAgICAgICAgICAgICAgICAgICApCgpncmlkX3ByZWRpY2Npb25lc19oMm8gPC0gYXMuaDJvKGdyaWRfcHJlZGljY2lvbmVzKQoKCnByZWRpY2Npb25lc18xIDwtIGgyby5wcmVkaWN0KAogICAgICAgICAgICAgICAgICAgIG9iamVjdCAgPSBtb2RlbG9fMSwKICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gZ3JpZF9wcmVkaWNjaW9uZXNfaDJvCiAgICAgICAgICAgICAgICAgICkKCnByZWRpY2Npb25lc18yIDwtIGgyby5wcmVkaWN0KAogICAgICAgICAgICAgICAgICAgIG9iamVjdCAgPSBtb2RlbG9fMiwKICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gZ3JpZF9wcmVkaWNjaW9uZXNfaDJvCiAgICAgICAgICAgICAgICAgICkKCnByZWRpY2Npb25lc18zIDwtIGgyby5wcmVkaWN0KAogICAgICAgICAgICAgICAgICAgIG9iamVjdCAgPSBtb2RlbG9fMywKICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gZ3JpZF9wcmVkaWNjaW9uZXNfaDJvCiAgICAgICAgICAgICAgICAgICkKCnByZWRpY2Npb25lc180IDwtIGgyby5wcmVkaWN0KAogICAgICAgICAgICAgICAgICAgIG9iamVjdCAgPSBtb2RlbG9fNCwKICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gZ3JpZF9wcmVkaWNjaW9uZXNfaDJvCiAgICAgICAgICAgICAgICAgICkKCgpncmlkX3ByZWRpY2Npb25lcyRtb2RlbG9fMSA8LSBhcy52ZWN0b3IocHJlZGljY2lvbmVzXzEkcHJlZGljdCkKZ3JpZF9wcmVkaWNjaW9uZXMkbW9kZWxvXzIgPC0gYXMudmVjdG9yKHByZWRpY2Npb25lc18yJHByZWRpY3QpCmdyaWRfcHJlZGljY2lvbmVzJG1vZGVsb18zIDwtIGFzLnZlY3RvcihwcmVkaWNjaW9uZXNfMyRwcmVkaWN0KQpncmlkX3ByZWRpY2Npb25lcyRtb2RlbG9fNCA8LSBhcy52ZWN0b3IocHJlZGljY2lvbmVzXzQkcHJlZGljdCkKCmBgYAoKYGBge3J9CiMgR3LDoWZpY28gZGUgcHJlZGljY2lvbmVzCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CnAxIDwtIGdncGxvdChkYXRhID0gZ3JpZF9wcmVkaWNjaW9uZXMsIGFlcyh4ID0geF8xLCB5ID0geF8yLCBjb2xvciA9IG1vZGVsb18xKSkgKyAKICAgICAgZ2VvbV9wb2ludChzaXplID0gMC41KSArCiAgICAgIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpICsgIAogICAgICBsYWJzKHRpdGxlID0gIkFycXVpdGVjdHVyYTogKDUpIikgKwogICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSksCiAgICAgICAgICAgIGF4aXMudGV4dCAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMudGlja3MgPSAgZWxlbWVudF9ibGFuaygpKQoKcDIgPC0gZ2dwbG90KGRhdGEgPSBncmlkX3ByZWRpY2Npb25lcywgYWVzKHggPSB4XzEsIHkgPSB4XzIsIGNvbG9yID1tb2RlbG9fMikpICsgCiAgICAgIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSkgKwogICAgICBsYWJzKHRpdGxlID0gIkFycXVpdGVjdHVyYTogKDEwKSIpICsKICAgICAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKyAKICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTEpLAogICAgICAgICAgICBheGlzLnRleHQgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLnRpY2tzID0gIGVsZW1lbnRfYmxhbmsoKSkKCnAzIDwtIGdncGxvdChkYXRhID0gZ3JpZF9wcmVkaWNjaW9uZXMsIGFlcyh4ID0geF8xLCB5ID0geF8yLCBjb2xvciA9IG1vZGVsb18zKSkgKyAKICAgICAgZ2VvbV9wb2ludChzaXplID0gMC41KSArCiAgICAgIGxhYnModGl0bGUgPSAiQXJxdWl0ZWN0dXJhOiAoMjAsIDIwKSIpICsKICAgICAgdGhlbWVfZml2ZXRoaXJ0eWVpZ2h0KCkgKyAKICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTEpLAogICAgICAgICAgICBheGlzLnRleHQgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLnRpY2tzID0gIGVsZW1lbnRfYmxhbmsoKSkKCnA0IDwtIGdncGxvdChkYXRhID0gZ3JpZF9wcmVkaWNjaW9uZXMsIGFlcyh4ID0geF8xLCB5ID0geF8yLCBjb2xvciA9IG1vZGVsb180KSkgKyAKICAgICAgZ2VvbV9wb2ludChzaXplID0gMC41KSArCiAgICAgIGxhYnModGl0bGUgPSAiQXJxdWl0ZWN0dXJhOiAoNTAsIDUwLCA1MCkiKSArCiAgICAgIHRoZW1lX2ZpdmV0aGlydHllaWdodCgpICsgCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTExKSwKICAgICAgICAgICAgYXhpcy50ZXh0ICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50aWNrcyA9ICBlbGVtZW50X2JsYW5rKCkpCgoKZ2dhcnJhbmdlKHAxLCBwMiwgcDMsIHA0LCBucm93ID0gMiwgbmNvbCA9IDIpCgpgYGAKCiMjIyBDcmVhY2lvbiBkZSBOdWV2b3MgTW9kZWxvcyBjb24gbWF5b3IgY2FudGlkYWQgZGUgaGlkZGVuIExheWVycyAoQ2FwYXMgT2N1bHRhcykKCkEgY29udGludWFjaW9uICwgc2UgY3JlYSBkaWZlcmVudGVzIG1vZGVsb3MgYXBsaWNhbmRvIHVuYSBhcnF1aXRlY3R1cmEgY29uIGRpZmVyZW50ZXMgcGFyYW1ldHJvcyAsIGVuIGVzdGUgY2FzbzoKCjEuICAqKk1vZGVsbyAxKiogOiBNb2RlbG8gQ29uIDYgY2FwYXMgb2N1bHRhcyBkZSAzMCBuZXVyb25hcyBjYWRhIHVuYSwgYWN0aXZhY2lvbiBSZUxVIHNpbiBkcm9wb3V0CjIuICAqKk1vZGVsbyAyKiogOiBNb2RlbG8gY29uIDEgc29sYSBjYXBhIG9jdWx0YSBkZSA1MDAgbmV1cm9uYXMsIGFjdGl2YWNpb24gUmVMVSBjb24gZHJvcG91dAozLiAgKipNb2RlbG8gMyoqIDogTW9kZWxvIGNvbiAxMCBjYXBhcyBvY3VsdGFzIGRlIDEwIG5ldXJvbmFzIGNhZGEgdW5hICwgYWN0aXZhY2lvbiB0YW5oIHNpbiBkcm9wb3V0CjQuICAqKk1vZGVsbyA0KiogOiBNb2RlbG8gY29uIDMgY2FwYXMgb2N1bHRhcyBkZSAxMCBuZXVyb25hcyBjYWRhIHVuYSAsIGFjdGl2YWNpb24gdGFuaCBzaW4gZHJvcG91dAoKKipUYW5oOioqCgokJCB0YW5oID0gXGZyYWN7ZV54IOKAkyBlXi14fXtlXnggKyBlXi14fSQkIFNlIGNvbnNpZGVyYW4gdmFsb3JlcyBuZWdhdGl2b3MsIG1pZW50cmFzIHF1ZSBlbiBlbCByYW5nbyBtw61uaW1vIHNpZ21vaWRlbyBlcyAwIHBlcm8gZW4gVGFuaCwgZWwgcmFuZ28gbcOtbmltbyBlcyAtMS4gRXN0YSBlcyBsYSByYXrDs24gcG9yIGxhIHF1ZSBsYSBmdW5jacOzbiBkZSBhY3RpdmFjacOzbiBkZSBUYW5oIHRhbWJpw6luIHNlIGNvbm9jZSBjb21vIGZ1bmNpw7NuIGRlIGFjdGl2YWNpw7NuIGNlbnRyYWRhIGVuIGNlcm8uCgpEZXN2ZW50YWphOgoKVGFtYmnDqW4gZW5mcmVudGEgZWwgbWlzbW8gcHJvYmxlbWEgZGVsIHByb2JsZW1hIGRlbCBncmFkaWVudGUgZGUgZnVnYSBjb21vIHVuYSBmdW5jacOzbiBzaWdtb2lkZWEuCgpgYGB7cn0KIyBEZWZpbmljaW9uIE1vZGVsb3MgQ3VzdG9tCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgoKIyBNb2RlbG8gMSAKbW9kZWxfMSA8LSBoMm8uZGVlcGxlYXJuaW5nKAogICAgICAgICAgICAgICAgICB4ICAgICAgICAgICAgICAgPSBjKCJ4XzEiLCAieF8yIiksCiAgICAgICAgICAgICAgICAgIHkgICAgICAgICAgICAgICA9ICJ5IiwKICAgICAgICAgICAgICAgICAgZGlzdHJpYnV0aW9uICAgID0gIm11bHRpbm9taWFsIiwKICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgID0gZGZfdHJhaW4sCiAgICAgICAgICAgICAgICAgIHN0YW5kYXJkaXplICAgICA9IFRSVUUsCiAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gICAgICA9ICJSZWN0aWZpZXIiLAogICAgICAgICAgICAgICAgICBhZGFwdGl2ZV9yYXRlICAgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgaGlkZGVuICAgICAgICAgID0gYygzMCwzMCwzMCwzMCwzMCwzMCksCiAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDAsCiAgICAgICAgICAgICAgICAgIGVwb2NocyAgICAgICAgICA9IDEwMDAsCiAgICAgICAgICAgICAgICAgIHNlZWQgICAgICAgICAgICA9IDEyMywKICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgICAgICAgID0gIm1vZGVsXzEiCiAgICAgICAgICAgKQoKCiMgTW9kZWxvIDIKbW9kZWxfMiA8LSBoMm8uZGVlcGxlYXJuaW5nKAogICAgICAgICAgICAgICAgICB4ICAgICAgICAgICAgICAgPSBjKCJ4XzEiLCAieF8yIiksCiAgICAgICAgICAgICAgICAgIHkgICAgICAgICAgICAgICA9ICJ5IiwKICAgICAgICAgICAgICAgICAgZGlzdHJpYnV0aW9uICAgID0gIm11bHRpbm9taWFsIiwKICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgID0gZGZfdHJhaW4sCiAgICAgICAgICAgICAgICAgIHN0YW5kYXJkaXplICAgICA9IFRSVUUsCiAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gICAgICA9ICJSZWN0aWZpZXJXaXRoRHJvcG91dCIsCiAgICAgICAgICAgICAgICAgIGFkYXB0aXZlX3JhdGUgICA9IEZBTFNFLAogICAgICAgICAgICAgICAgICBoaWRkZW4gICAgICAgICAgPSA1MDAsCiAgICAgICAgICAgICAgICAgIHN0b3BwaW5nX3JvdW5kcyA9IDAsCiAgICAgICAgICAgICAgICAgIGVwb2NocyAgICAgICAgICA9IDEwMDAsCiAgICAgICAgICAgICAgICAgIHNlZWQgICAgICAgICAgICA9IDEyMywKICAgICAgICAgICAgICAgICAgbW9kZWxfaWQgICAgICAgID0gIm1vZGVsXzIiCiAgICAgICAgICAgKQoKCiMgTW9kZWxvIDMKbW9kZWxfMyA8LSBoMm8uZGVlcGxlYXJuaW5nKAogICAgICAgICAgICAgICAgICB4ICAgICAgICAgICAgICAgPSBjKCJ4XzEiLCAieF8yIiksCiAgICAgICAgICAgICAgICAgIHkgICAgICAgICAgICAgICA9ICJ5IiwKICAgICAgICAgICAgICAgICAgZGlzdHJpYnV0aW9uICAgID0gIm11bHRpbm9taWFsIiwKICAgICAgICAgICAgICAgICAgdHJhaW5pbmdfZnJhbWUgID0gZGZfdHJhaW4sCiAgICAgICAgICAgICAgICAgIHN0YW5kYXJkaXplICAgICA9IFRSVUUsCiAgICAgICAgICAgICAgICAgIGFjdGl2YXRpb24gICAgICA9ICJUYW5oIiwKICAgICAgICAgICAgICAgICAgYWRhcHRpdmVfcmF0ZSAgID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgIGhpZGRlbiAgICAgICAgICA9IGMoMTAsMTAsMTAsMTAsMTAsMTAsMTAsMTAsMTAsMTApLAogICAgICAgICAgICAgICAgICBzdG9wcGluZ19yb3VuZHMgPSAwLAogICAgICAgICAgICAgICAgICBlcG9jaHMgICAgICAgICAgPSAxMDAwLAogICAgICAgICAgICAgICAgICBzZWVkICAgICAgICAgICAgPSAxMjMsCiAgICAgICAgICAgICAgICAgIG1vZGVsX2lkICAgICAgICA9ICJtb2RlbF8zIgogICAgICAgICAgICkKCgojIE1vZGVsbyA0Cm1vZGVsXzQgPC0gaDJvLmRlZXBsZWFybmluZygKICAgICAgICAgICAgICAgICAgeCAgICAgICAgICAgICAgID0gYygieF8xIiwgInhfMiIpLAogICAgICAgICAgICAgICAgICB5ICAgICAgICAgICAgICAgPSAieSIsCiAgICAgICAgICAgICAgICAgIGRpc3RyaWJ1dGlvbiAgICA9ICJtdWx0aW5vbWlhbCIsCiAgICAgICAgICAgICAgICAgIHRyYWluaW5nX2ZyYW1lICA9IGRmX3RyYWluLAogICAgICAgICAgICAgICAgICBzdGFuZGFyZGl6ZSAgICAgPSBUUlVFLAogICAgICAgICAgICAgICAgICBhY3RpdmF0aW9uICAgICAgPSAiVGFuaCIsCiAgICAgICAgICAgICAgICAgIGFkYXB0aXZlX3JhdGUgICA9IEZBTFNFLAogICAgICAgICAgICAgICAgICBoaWRkZW4gICAgICAgICAgPSBjKDEwLDEwLDEwKSwKICAgICAgICAgICAgICAgICAgc3RvcHBpbmdfcm91bmRzID0gMCwKICAgICAgICAgICAgICAgICAgZXBvY2hzICAgICAgICAgID0gMTAwMCwKICAgICAgICAgICAgICAgICAgc2VlZCAgICAgICAgICAgID0gMTIzLAogICAgICAgICAgICAgICAgICBtb2RlbF9pZCAgICAgICAgPSAibW9kZWxfNCIKICAgICAgICAgICApCgoKCgpgYGAKCiMjIyBHZW5lcmFuZG8gcHJlZGljY2lvbmVzIGNvbiBsb3MgbnVldm9zIG1vZGVsb3M6CgpgYGB7cn0KCgpncmlkX25ldyA8LSBleHBhbmQuZ3JpZCgKCiAgICAgICAgICAgICAgICAgICAgICAgIHhfMSA9IHNlcShmcm9tID0gbWluKGRhdG9zJHhfMSksIHRvID0gbWF4KGRhdG9zJHhfMSksIGxlbmd0aCA9IDc1KSwKICAgICAgICAgICAgICAgICAgICAgICAgeF8yID0gc2VxKGZyb20gPSBtaW4oZGF0b3MkeF8yKSwgdG8gPSBtYXgoZGF0b3MkeF8yKSwgbGVuZ3RoID0gNzUpCiAgICAgICAgICAgICAgICAgICAgICkKCmdyaWRfbmV3X3ByZWRpY3QgPC0gYXMuaDJvKGdyaWRfbmV3KQoKCnByZWRpY3RfMSA8LSBoMm8ucHJlZGljdCgKICAgICAgICAgICAgICAgICAgICBvYmplY3QgID0gbW9kZWxfMSwKICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gZ3JpZF9uZXdfcHJlZGljdAogICAgICAgICAgICAgICAgICApCgpwcmVkaWN0XzIgPC0gaDJvLnByZWRpY3QoCiAgICAgICAgICAgICAgICAgICAgb2JqZWN0ICA9IG1vZGVsXzIsCiAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IGdyaWRfbmV3X3ByZWRpY3QKICAgICAgICAgICAgICAgICAgKQoKcHJlZGljdF8zIDwtIGgyby5wcmVkaWN0KAogICAgICAgICAgICAgICAgICAgIG9iamVjdCAgPSBtb2RlbF8zLAogICAgICAgICAgICAgICAgICAgIG5ld2RhdGEgPSBncmlkX25ld19wcmVkaWN0IAogICAgICAgICAgICAgICAgICApCgpwcmVkaWN0XzQgPC0gaDJvLnByZWRpY3QoCiAgICAgICAgICAgICAgICAgICAgb2JqZWN0ICA9IG1vZGVsXzQsCiAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IGdyaWRfbmV3X3ByZWRpY3QKICAgICAgICAgICAgICAgICAgKQoKZ3JpZF9uZXckbW9kZWxfMSA8LSBhcy52ZWN0b3IocHJlZGljdF8xJHByZWRpY3QpCmdyaWRfbmV3JG1vZGVsXzIgPC0gYXMudmVjdG9yKHByZWRpY3RfMiRwcmVkaWN0KQpncmlkX25ldyRtb2RlbF8zIDwtIGFzLnZlY3RvcihwcmVkaWN0XzMkcHJlZGljdCkKZ3JpZF9uZXckbW9kZWxfNCA8LSBhcy52ZWN0b3IocHJlZGljdF80JHByZWRpY3QpCmBgYAoKIyMjIFByZWRpY2Npb25lcyBjb24gbnVldm9zIE1vZGVsb3M6CgpgYGB7cn0KIyBHcsOhZmljbyBkZSBwcmVkaWNjaW9uZXMKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KcDEgPC0gZ2dwbG90KGRhdGEgPSBncmlkX25ldywgYWVzKHggPSB4XzEsIHkgPSB4XzIsIGNvbG9yID0gbW9kZWxfMSkpICsgCiAgICAgIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSkgKwogICAgICB0aGVtZV9lY29ub21pc3QoKSArICAKICAgICAgbGFicyh0aXRsZSA9ICJBcnF1aXRlY3R1cmE6ICgzMCwzMCwzMCwzMCwzMCwzMCkgUmVMVSIpICsKICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTEpLAogICAgICAgICAgICBheGlzLnRleHQgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLnRpY2tzID0gIGVsZW1lbnRfYmxhbmsoKSkKCnAyIDwtIGdncGxvdChkYXRhID0gZ3JpZF9uZXcsIGFlcyh4ID0geF8xLCB5ID0geF8yLCBjb2xvciA9IG1vZGVsXzIpKSArIAogICAgICBnZW9tX3BvaW50KHNpemUgPSAwLjUpICsKICAgICAgbGFicyh0aXRsZSA9ICJBcnF1aXRlY3R1cmE6IE5ldXJvbmEgVW5pY2EgUmVMVSBEcm9wb3V0ICIpICsKICAgICAgdGhlbWVfZWNvbm9taXN0KCkgKyAKICAgICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemU9MTEpLAogICAgICAgICAgICBheGlzLnRleHQgID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICBheGlzLnRpY2tzID0gIGVsZW1lbnRfYmxhbmsoKSkKCnAzIDwtIGdncGxvdChkYXRhID0gZ3JpZF9uZXcsIGFlcyh4ID0geF8xLCB5ID0geF8yLCBjb2xvciA9IG1vZGVsXzMpKSArIAogICAgICBnZW9tX3BvaW50KHNpemUgPSAwLjUpICsKICAgICAgbGFicyh0aXRsZSA9ICJBcnF1aXRlY3R1cmEgYygxMCwxMCwxMCwxMCwxMCwxMCwxMCwxMCwxMCwxMCkgVGFuSCAiKSArCiAgICAgIHRoZW1lX2Vjb25vbWlzdCgpICsgCiAgICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplPTExKSwKICAgICAgICAgICAgYXhpcy50ZXh0ICA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgYXhpcy50aWNrcyA9ICBlbGVtZW50X2JsYW5rKCkpCgpwNCA8LSBnZ3Bsb3QoZGF0YSA9IGdyaWRfbmV3LCBhZXMoeCA9IHhfMSwgeSA9IHhfMiwgY29sb3IgPSBtb2RlbF80KSkgKyAKCiAgICAgIGdlb21fcG9pbnQoc2l6ZSA9IDAuNSkgKwogICAgICBsYWJzKHRpdGxlID0gIkFycXVpdGVjdHVyYTogYygxMCwgMTAsIDEwKSB0YW5oIikgKwogICAgICB0aGVtZV9lY29ub21pc3QoKSArIAogICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICAgICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZT0xMSksCiAgICAgICAgICAgIGF4aXMudGV4dCAgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X2JsYW5rKCksCiAgICAgICAgICAgIGF4aXMudGlja3MgPSAgZWxlbWVudF9ibGFuaygpKQoKCmdnYXJyYW5nZShwMSwgcDIsIHAzLCBwNCwgbnJvdyA9IDIsIG5jb2wgPSAyKQoKYGBgCgpDb21wYXJhY2lvbiBkZSBBY2N1cmFjeSBlbnRyZSBtb2RlbG9zIGVuIGVsIGRhdGFzZXQgZGUgdGVzdDoKCmBgYHtyfQpwcmVkaWN0X3Rlc3QgPC0gaDJvLnByZWRpY3Qob2JqZWN0ICA9IG1vZGVsXzEsbmV3ZGF0YSA9IGRmX3Rlc3QpCmFjY3VyYWN5XzFfbmV3IDwtIG1lYW4ocHJlZGljdF90ZXN0WyJwcmVkaWN0Il0gPT0gZGZfdGVzdFsieSJdKQoKcHJlZGljdF90ZXN0IDwtIGgyby5wcmVkaWN0KG9iamVjdCAgPSBtb2RlbF8yLG5ld2RhdGEgPSBkZl90ZXN0KQphY2N1cmFjeV8yX25ldyA8LSBtZWFuKHByZWRpY3RfdGVzdFsicHJlZGljdCJdID09IGRmX3Rlc3RbInkiXSkKCnByZWRpY3RfdGVzdCA8LSBoMm8ucHJlZGljdChvYmplY3QgID0gbW9kZWxfMyxuZXdkYXRhID0gZGZfdGVzdCkKYWNjdXJhY3lfM19uZXcgPC0gbWVhbihwcmVkaWN0X3Rlc3RbInByZWRpY3QiXSA9PSBkZl90ZXN0WyJ5Il0pCgpwcmVkaWN0X3Rlc3QgPC0gaDJvLnByZWRpY3Qob2JqZWN0ICA9IG1vZGVsXzQsbmV3ZGF0YSA9IGRmX3Rlc3QpCmFjY3VyYWN5XzRfbmV3IDwtIG1lYW4ocHJlZGljdF90ZXN0WyJwcmVkaWN0Il0gPT0gZGZfdGVzdFsieSJdKQoKYGBgCgpgYGB7cn0KICBnbHVlKCJBY2N1cmFjeSBkZWwgTW9kZWxvIDEgOiB7YWNjdXJhY3lfMX0gXG4KICAgICBBY2N1cmFjeSBkZWwgTW9kZWxvIDIgOiB7YWNjdXJhY3lfMn0gXG4KICAgICBBY2N1cmFjeSBkZWwgTW9kZWxvIDMgOiB7YWNjdXJhY3lfM30gXG4KICAgICBBY2N1cmFjeSBkZWwgTW9kZWxvIDQgOiB7YWNjdXJhY3lfNH0gXG4KICAgICAiICkKCgoKYGBgCgpgYGB7cn0KCnByZWRpY3RfdGVzdCA8LSBoMm8ucHJlZGljdChvYmplY3QgID0gbW9kZWxfMSxuZXdkYXRhID0gZGZfdGVzdCkKYWNjdXJhY3lfMV9uZXcgPC0gbWVhbihwcmVkaWN0X3Rlc3RbInByZWRpY3QiXSA9PSBkZl90ZXN0WyJ5Il0pCgpwcmVkaWN0X3Rlc3QgPC0gaDJvLnByZWRpY3Qob2JqZWN0ICA9IG1vZGVsXzIsbmV3ZGF0YSA9IGRmX3Rlc3QpCmFjY3VyYWN5XzJfbmV3IDwtIG1lYW4ocHJlZGljdF90ZXN0WyJwcmVkaWN0Il0gPT0gZGZfdGVzdFsieSJdKQoKcHJlZGljdF90ZXN0IDwtIGgyby5wcmVkaWN0KG9iamVjdCAgPSBtb2RlbF8zLG5ld2RhdGEgPSBkZl90ZXN0KQphY2N1cmFjeV8zX25ldyA8LSBtZWFuKHByZWRpY3RfdGVzdFsicHJlZGljdCJdID09IGRmX3Rlc3RbInkiXSkKCnByZWRpY3RfdGVzdCA8LSBoMm8ucHJlZGljdChvYmplY3QgID0gbW9kZWxfNCxuZXdkYXRhID0gZGZfdGVzdCkKYWNjdXJhY3lfNF9uZXcgPC0gbWVhbihwcmVkaWN0X3Rlc3RbInByZWRpY3QiXSA9PSBkZl90ZXN0WyJ5Il0pCmBgYAoKSW1wcmltaWVuZG8gZWwgYWNjdXJhY3kgZGUgY2FkYSBtb2RlbG86CgpgYGB7cn0KICBnbHVlKCJBY2N1cmFjeSBkZWwgTW9kZWxvIDEgIE51ZXZvOiB7YWNjdXJhY3lfMV9uZXd9IFxuCiAgICAgQWNjdXJhY3kgZGVsIE1vZGVsbyAyIE51ZXZvOiB7YWNjdXJhY3lfMl9uZXd9IFxuCiAgICAgQWNjdXJhY3kgZGVsIE1vZGVsbyAzIE51ZXZvOiB7YWNjdXJhY3lfM19uZXd9IFxuCiAgICAgQWNjdXJhY3kgZGVsIE1vZGVsbyA0IE51ZXZvIDoge2FjY3VyYWN5XzRfbmV3fSBcbgogICAgICIgKQoKCgpgYGAKClNlIGdyYWZpY2EgdG9kYXMgbGFzIGFjY3VyYWNpZXMgZGUgbG9zIG1vZGVsb3MgcGxhbnRlYWRvczoKCmBgYHtyfQoKYWNjdXJhY3kgPC0gYyhhY2N1cmFjeV8xLGFjY3VyYWN5XzIsYWNjdXJhY3lfMyxhY2N1cmFjeV80LCBhY2N1cmFjeV8xX25ldywgYWNjdXJhY3lfMl9uZXcsYWNjdXJhY3lfM19uZXcsYWNjdXJhY3lfNF9uZXcpCgoKbW9kZWwgPC0gYygiTW9kZWxvIDEiLCAiTW9kZWxvIDIiLCJNb2RlbG8gMyIsIk1vZGVsbyA0IiwgIk1vZGVsbyAxIG51ZXZvIiwgIk1vZGVsbyAyIE51ZXZvIiwiTW9kZWxvIDMgTnVldm8iLCJNb2RlbG8gNCBOdWV2byIpCgoKCgpkYXRhIDwtIGRhdGEuZnJhbWUoYWNjdXJhY3ksIG1vZGVsKSAgICAKZ2dwbG90KGRhdGEsYWVzKHg9IHJlb3JkZXIobW9kZWwsLWFjY3VyYWN5KSx5PSBhY2N1cmFjeSwgZmlsbD1tb2RlbCApLHRpdGxlKCJBY2N1cmFjeSBkZSBNb2RlbG9zIFBsYW50ZWFkb3MgIikpK2dlb21fYmFyKHN0YXQgPSJpZGVudGl0eSIpICsgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgdmp1c3QgPSAwLjUsIGhqdXN0PTEpKQoKYGBgCgoKU2Ugb2JzZXJ2YSBxdWUgZWwgbWVqb3IgY3JlYWRvIGNvcnJlc3BvbmRlIGFsIE1vZGVsbyAzLCBjb24gdW4gODglIGRlIGNlcnRlemEgZW4gbG9zIGRhdG9zIGRlIHRlc3RpbmcsIHJlY29yZGFuZG8gcXVlIGVsIG1vZGVsbyAzIHBvc2VlIHVuYSBhcnF1aXRlY3R1cmEgIGNvbiBkZSAyIGNhcGFzIG9jdWx0YXMgYGMoMTAsIDEwKWAgZGUgMTAgbmV1cm9uYXMgY2FkYSB1bmEuCgo=