1 LIBRERÍAS

# Librerías necesarias
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.1     ✔ tibble    3.2.1
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.0.4     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(readxl)
library(purrr)
library(knitr)
#install.packages("kableExtra")
library(kableExtra)
## 
## Attaching package: 'kableExtra'
## 
## The following object is masked from 'package:dplyr':
## 
##     group_rows
library(ggplot2)
#install.packages("igraph")
library(igraph)
## 
## Attaching package: 'igraph'
## 
## The following objects are masked from 'package:lubridate':
## 
##     %--%, union
## 
## The following objects are masked from 'package:dplyr':
## 
##     as_data_frame, groups, union
## 
## The following objects are masked from 'package:purrr':
## 
##     compose, simplify
## 
## The following object is masked from 'package:tidyr':
## 
##     crossing
## 
## The following object is masked from 'package:tibble':
## 
##     as_data_frame
## 
## The following objects are masked from 'package:stats':
## 
##     decompose, spectrum
## 
## The following object is masked from 'package:base':
## 
##     union
#install.packages("forecast")
#install.packages("lubridate")
library(forecast)
## Registered S3 method overwritten by 'quantmod':
##   method            from
##   as.zoo.data.frame zoo
library(lubridate)
library(corrplot)
## corrplot 0.95 loaded
library(RColorBrewer)
#install.packages("ggcorrplot")
library(ggcorrplot)
library(caret)
## Loading required package: lattice
## 
## Attaching package: 'caret'
## 
## The following object is masked from 'package:purrr':
## 
##     lift
library(car)
## Loading required package: carData
## 
## Attaching package: 'car'
## 
## The following object is masked from 'package:dplyr':
## 
##     recode
## 
## The following object is masked from 'package:purrr':
## 
##     some
library(randomForest)
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## 
## The following object is masked from 'package:dplyr':
## 
##     combine
## 
## The following object is masked from 'package:ggplot2':
## 
##     margin
#install.packages("xgboost")
library(xgboost)
## 
## Attaching package: 'xgboost'
## 
## The following object is masked from 'package:dplyr':
## 
##     slice
#install.packages("patchwork")
library(patchwork)
library(dplyr)
#install.packages("scales")
library (ggplot2)

2 CARGA DE DATOS

# Leer datos
datos <- read_excel("/Users/mariadelbosque/Downloads/Novem_final.xlsx") %>%
  mutate(Trx_Fecha = as.Date(Trx_Fecha))

# Partición temporal
train <- datos %>% filter(Trx_Fecha >= as.Date("2023-01-01") & Trx_Fecha < as.Date("2024-10-01"))
test  <- datos %>% filter(Trx_Fecha >= as.Date("2024-10-01") & Trx_Fecha <= as.Date("2024-12-31"))

# Validación rápida
summary(train$Trx_Fecha)
##         Min.      1st Qu.       Median         Mean      3rd Qu.         Max. 
## "2023-01-02" "2023-06-10" "2023-11-16" "2023-11-16" "2024-04-23" "2024-09-30"
summary(test$Trx_Fecha)
##         Min.      1st Qu.       Median         Mean      3rd Qu.         Max. 
## "2024-10-07" "2024-10-28" "2024-11-18" "2024-11-18" "2024-12-09" "2024-12-30"
# Ver todos los productos ordenados por ventas totales
top_ids <- datos %>%
  group_by(ID_Inventario) %>%
  summarise(Ventas_Totales = sum(Venta, na.rm = TRUE)) %>%
  arrange(desc(Ventas_Totales))

print("Productos ordenados por ventas totales:")
## [1] "Productos ordenados por ventas totales:"
print(top_ids)
## # A tibble: 5 × 2
##   ID_Inventario Ventas_Totales
##           <dbl>          <dbl>
## 1        155001      25110107.
## 2       3929788      22771766.
## 3       3904152      19156601.
## 4        155002      16699118.
## 5       3678055      16686238.
datos_filtrados <- train  # usamos todo el train
# Contar observaciones por producto
conteo <- datos_filtrados %>%
  count(ID_Inventario, sort = TRUE)
print("Número de registros por producto en datos_filtrados:")
## [1] "Número de registros por producto en datos_filtrados:"
print(conteo)
## # A tibble: 5 × 2
##   ID_Inventario     n
##           <dbl> <int>
## 1        155001    92
## 2        155002    92
## 3       3678055    92
## 4       3904152    92
## 5       3929788    92
# Validación de que haya datos
if (nrow(datos_filtrados) == 0) {
  stop("No hay datos suficientes en el conjunto de entrenamiento.")}

3 ESTACIONALIDAD SEMANAL

3.1 TEST DE ESTACIONALIDAD

# Asegúrate de tener Año y Semana en tu base
datos$Trx_Fecha <- as.Date(datos$Trx_Fecha)

datos <- datos %>%
  mutate(
    Anio = isoyear(Trx_Fecha),
    Semana = isoweek(Trx_Fecha)
  )

# Agregamos ventas por producto, semana y año
ventas_semanales <- datos %>%
  group_by(ID_Inventario, Semana, Anio) %>%
  summarise(Venta_Semanal = sum(Venta, na.rm = TRUE), .groups = "drop")

# Calculamos CV por producto y semana
cv_por_semana <- ventas_semanales %>%
  group_by(ID_Inventario, Semana) %>%
  summarise(
    media = mean(Venta_Semanal, na.rm = TRUE),
    sd = sd(Venta_Semanal, na.rm = TRUE),
    .groups = 'drop'
  ) %>%
  mutate(CV = sd / media)

# Promedio del CV semanal por producto + clasificación de estacionalidad
estacionalidad_semanal <- cv_por_semana %>%
  group_by(ID_Inventario) %>%
  summarise(CV_Promedio_Semanal = mean(CV, na.rm = TRUE)) %>%
  mutate(
    Hay_Estacionalidad = ifelse(CV_Promedio_Semanal >= 0.3, "Sí", "No")
  ) %>%
  arrange(desc(CV_Promedio_Semanal))

print(estacionalidad_semanal)
## # A tibble: 5 × 3
##   ID_Inventario CV_Promedio_Semanal Hay_Estacionalidad
##           <dbl>               <dbl> <chr>             
## 1       3678055               0.369 Sí                
## 2        155002               0.298 No                
## 3        155001               0.281 No                
## 4       3904152               0.248 No                
## 5       3929788               0.228 No

3.2 VISUALIZACIÓN DE ESTACIONALIDAD

# Paso 1: Asegurar formato de fecha
datos$Trx_Fecha <- as.Date(datos$Trx_Fecha)

# Paso 2: Crear columnas de Año y Mes
datos <- datos %>%
  mutate(
    Anio = isoyear(Trx_Fecha),
    Semana = isoweek(Trx_Fecha)
  )

# Paso 3: Agregar ventas por producto, año y mes
datos_agregados <- datos %>%
  group_by(ID_Inventario, Anio, Semana) %>%
  summarise(Venta_Semanal = sum(Venta, na.rm = TRUE), .groups = 'drop')

# Paso 4: Generar gráficas por producto con colores personalizados
graficas <- datos_agregados %>%
  group_split(ID_Inventario) %>%
  lapply(function(df_prod) {
    ggplot(df_prod, aes(x = Semana, y = Venta_Semanal, color = as.factor(Anio))) +
      geom_line(size = 1.2) +
      scale_color_manual(
        values = c(
          "2023" = "#68838B",  # Azul grisáceo
          "2024" = "#FF69B4"   # Rosa fuerte
        )
      ) +
      labs(
        title = paste("Estacionalidad Semanal - Producto", unique(df_prod$ID_Inventario)),
        x = "Semana",
        y = "Venta Semanal",
        color = "Año"
      ) +
      theme_minimal(base_size = 13) +
      theme(
        plot.title = element_text(face = "bold"),
        legend.position = "bottom"
      )
  })
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# Paso 5: Mostrar una gráfica como prueba
print(graficas[[1]])

print(graficas[[2]])

print(graficas[[3]])

print(graficas[[4]])

print(graficas[[5]])

4 ESTACIONALIDAD MENSUAL

4.1 TEST DE ESTACIONALIDAD

# Asegúrate de tener Año y Semana en tu base
datos$Trx_Fecha <- as.Date(datos$Trx_Fecha)

datos <- datos %>%
  mutate(
    Anio = isoyear(Trx_Fecha),
    Mes = month(Trx_Fecha)
  )

# Agregamos ventas por producto, semana y año
ventas_mensual <- datos %>%
  group_by(ID_Inventario, Mes, Anio) %>%
  summarise(Venta_Mensual = sum(Venta, na.rm = TRUE), .groups = "drop")

# Calculamos CV por producto y semana
cv_por_mes <- ventas_mensual %>%
  group_by(ID_Inventario, Mes) %>%
  summarise(
    media = mean(Venta_Mensual, na.rm = TRUE),
    sd = sd(Venta_Mensual, na.rm = TRUE),
    .groups = 'drop'
  ) %>%
  mutate(CV = sd / media)

# Promedio del CV semanal por producto + clasificación de estacionalidad
estacionalidad_mensual <- cv_por_mes %>%
  group_by(ID_Inventario) %>%
  summarise(CV_Promedio_Mensual = mean(CV, na.rm = TRUE)) %>%
  mutate(
    Hay_Estacionalidad = ifelse(CV_Promedio_Mensual >= 0.3, "Sí", "No")
  ) %>%
  arrange(desc(CV_Promedio_Mensual))

print(estacionalidad_mensual)
## # A tibble: 5 × 3
##   ID_Inventario CV_Promedio_Mensual Hay_Estacionalidad
##           <dbl>               <dbl> <chr>             
## 1       3678055               0.260 No                
## 2        155002               0.249 No                
## 3       3929788               0.205 No                
## 4       3904152               0.181 No                
## 5        155001               0.172 No

4.2 VISUALIZACIÓN DE ESTACIONALIDAD

# Paso 1: Asegurar formato de fecha
datos$Trx_Fecha <- as.Date(datos$Trx_Fecha)

# Paso 2: Crear columnas de Año y Mes
datos <- datos %>%
  mutate(
    Anio = isoyear(Trx_Fecha),
    Mes = month(Trx_Fecha)
  )

# Paso 3: Agregar ventas por producto, año y mes
datos_agregados <- datos %>%
  group_by(ID_Inventario, Anio, Mes) %>%
  summarise(Venta_Mensual = sum(Venta, na.rm = TRUE), .groups = 'drop')

# Paso 4: Generar gráficas por producto con colores personalizados
graficas <- datos_agregados %>%
  group_split(ID_Inventario) %>%
  lapply(function(df_prod) {
    ggplot(df_prod, aes(x = Mes, y = Venta_Mensual, color = as.factor(Anio))) +
      geom_line(size = 1.2) +
      scale_color_manual(
        values = c(
          "2023" = "#68838B",  # Azul grisáceo
          "2024" = "#FF69B4"   # Rosa fuerte
        )
      ) +
      labs(
        title = paste("Estacionalidad Mensual - Producto", unique(df_prod$ID_Inventario)),
        x = "Mes",
        y = "Venta Mensual",
        color = "Año"
      ) +
      theme_minimal(base_size = 13) +
      theme(
        plot.title = element_text(face = "bold"),
        legend.position = "bottom"
      )
  })

# Paso 5: Mostrar una gráfica como prueba
print(graficas[[1]])

print(graficas[[2]])

print(graficas[[3]])

print(graficas[[4]])

print(graficas[[5]])

5 PREDICCIONES DE VENTAS

6 ARMA

6.1 PRODUCTO 155001

6.1.1 TRAIN

# Producto 155001
id_prod <- 155001

# Asegurarse de que metricas_comparativas esté inicializada
if (!exists("metricas_comparativas")) {
  metricas_comparativas <- data.frame(
    Producto = character(),
    Modelo = character(),
    MAPE = numeric(),
    RMSE = numeric(),
    stringsAsFactors = FALSE
  )
}

# Filtrar datos del producto (ya están agregados por semana)
ventas_semanales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  arrange(Trx_Fecha) %>%
  select(Trx_Fecha, Venta)

# Crear serie temporal semanal
serie_ts <- ts(ventas_semanales$Venta, frequency = 52,
               start = c(year(min(ventas_semanales$Trx_Fecha)),
                         isoweek(min(ventas_semanales$Trx_Fecha))))

# Ajustar modelo ARMA
modelo_arma_155001 <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo_arma_155001 <- forecast(modelo_arma_155001, h = 4)

# Gráfico del pronóstico
autoplot(forecast_modelo_arma_155001) +
  labs(title = paste("Pronóstico semanal de ventas - ARMA (Producto", id_prod, ")"),
       x = "Semana", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas de desempeño
fitted_arma_155001 <- fitted(modelo_arma_155001)
mape_arma_155001 <- mean(abs((serie_ts - fitted_arma_155001) / pmax(serie_ts, 0.01))) * 100
rmse_arma_155001 <- sqrt(mean((serie_ts - fitted_arma_155001)^2))

# Registrar en tabla general de métricas
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape_arma_155001,
  RMSE = rmse_arma_155001
))

6.1.2 TEST

# Crear conjunto de prueba para producto 155001 (solo columnas necesarias para ARMA)
test_155001 <- test %>%
  filter(ID_Inventario == 155001) %>%
  select(Semana, Venta) %>%
  arrange(Semana) %>%
  na.omit()
# Evaluar ARMA contra datos reales del test (primeras 4 semanas del test)
ventas_reales_test_155001 <- test_155001 %>%
  slice_head(n = 4) %>%
  pull(Venta)

ventas_predichas_arma_155001 <- as.numeric(forecast_modelo_arma_155001$mean)

# Comparar solo si hay 4 semanas disponibles
if (length(ventas_reales_test_155001) == length(ventas_predichas_arma_155001)) {
  
  mape_arma_test_155001 <- mean(abs((ventas_reales_test_155001 - ventas_predichas_arma_155001) /
                                      pmax(ventas_reales_test_155001, 0.01))) * 100
  
  rmse_arma_test_155001 <- sqrt(mean((ventas_reales_test_155001 - ventas_predichas_arma_155001)^2))
  
  cat("MAPE ARMA (test - producto 155001):", round(mape_arma_test_155001, 2), "\n")
  cat("RMSE ARMA (test - producto 155001):", round(rmse_arma_test_155001, 2), "\n")
  
  # Guardar en tabla de métricas
  metricas_comparativas <- rbind(metricas_comparativas, data.frame(
    Producto = id_prod,
    Modelo = "ARMA (Test)",
    MAPE = mape_arma_test_155001,
    RMSE = rmse_arma_test_155001
  ))
  
} else {
  warning("No hay suficientes semanas en el test para comparar con el forecast de ARMA.")
}
## MAPE ARMA (test - producto 155001): 25.14 
## RMSE ARMA (test - producto 155001): 67277.26

6.2 PRODUCTO 3929788

6.2.1 TRAIN

# Producto 3929788
id_prod <- 3929788

# Filtrar datos del producto
ventas_semanales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  arrange(Trx_Fecha) %>%
  select(Trx_Fecha, Venta)

# Crear serie temporal semanal
serie_ts <- ts(ventas_semanales$Venta, frequency = 52,
               start = c(year(min(ventas_semanales$Trx_Fecha)),
                         isoweek(min(ventas_semanales$Trx_Fecha))))

# Ajustar modelo ARMA
modelo_arma_3929788 <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo_arma_3929788 <- forecast(modelo_arma_3929788, h = 4)

# Gráfico del pronóstico
autoplot(forecast_modelo_arma_3929788) +
  labs(title = paste("Pronóstico semanal de ventas - ARMA (Producto", id_prod, ")"),
       x = "Semana", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_arma_3929788 <- fitted(modelo_arma_3929788)
mape_arma_3929788 <- mean(abs((serie_ts - fitted_arma_3929788) / pmax(serie_ts, 0.01))) * 100
rmse_arma_3929788 <- sqrt(mean((serie_ts - fitted_arma_3929788)^2))

# Registrar métricas en la tabla general
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape_arma_3929788,
  RMSE = rmse_arma_3929788
))

6.2.2 TEST

# Crear conjunto de prueba para producto 155001 (solo columnas necesarias para ARMA)
test_3929788 <- test %>%
  filter(ID_Inventario == 3929788) %>%
  select(Semana, Venta) %>%
  arrange(Semana) %>%
  na.omit()
# Evaluar ARMA contra datos reales del test (primeras 4 semanas del test)
ventas_reales_test_3929788 <- test_3929788 %>%
  slice_head(n = 4) %>%
  pull(Venta)

ventas_predichas_arma_3929788 <- as.numeric(forecast_modelo_arma_3929788$mean)

# Comparar solo si hay 4 semanas disponibles
if (length(ventas_reales_test_3929788) == length(ventas_predichas_arma_3929788)) {
  
  mape_arma_test_3929788 <- mean(abs((ventas_reales_test_3929788 - ventas_predichas_arma_3929788) /
                                      pmax(ventas_reales_test_3929788, 0.01))) * 100
  
  rmse_arma_test_3929788 <- sqrt(mean((ventas_reales_test_3929788 - ventas_predichas_arma_3929788)^2))
  
  cat("MAPE ARMA (test - producto 3929788):", round(mape_arma_test_3929788, 2), "\n")
  cat("RMSE ARMA (test - producto 3929788):", round(rmse_arma_test_3929788, 2), "\n")
  
  # Guardar en tabla de métricas
  metricas_comparativas <- rbind(metricas_comparativas, data.frame(
    Producto = id_prod,
    Modelo = "ARMA (Test)",
    MAPE = mape_arma_test_3929788,
    RMSE = rmse_arma_test_3929788
  ))
  
} else {
  warning("No hay suficientes semanas en el test para comparar con el forecast de ARMA.")
}
## MAPE ARMA (test - producto 3929788): 26.26 
## RMSE ARMA (test - producto 3929788): 60053.08

6.3 PRODUCTO 3904152

6.3.1 TRAIN

# Producto 3904152
id_prod <- 3904152

# Filtrar datos del producto
ventas_semanales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  arrange(Trx_Fecha) %>%
  select(Trx_Fecha, Venta)

# Crear serie temporal semanal
serie_ts <- ts(ventas_semanales$Venta, frequency = 52,
               start = c(year(min(ventas_semanales$Trx_Fecha)),
                         isoweek(min(ventas_semanales$Trx_Fecha))))

# Ajustar modelo ARMA
modelo_arma_3904152 <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo_arma_3904152 <- forecast(modelo_arma_3904152, h = 4)

# Gráfico del pronóstico
autoplot(forecast_modelo_arma_3904152) +
  labs(title = paste("Pronóstico semanal de ventas - ARMA (Producto", id_prod, ")"),
       x = "Semana", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_arma_3904152 <- fitted(modelo_arma_3904152)
mape_arma_3904152 <- mean(abs((serie_ts - fitted_arma_3904152) / pmax(serie_ts, 0.01))) * 100
rmse_arma_3904152 <- sqrt(mean((serie_ts - fitted_arma_3904152)^2))

# Registrar métricas en la tabla general
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape_arma_3904152,
  RMSE = rmse_arma_3904152
))

6.3.2 TEST

# Crear conjunto de prueba para producto 155001 (solo columnas necesarias para ARMA)
test_3904152 <- test %>%
  filter(ID_Inventario == 3904152) %>%
  select(Semana, Venta) %>%
  arrange(Semana) %>%
  na.omit()
# Evaluar ARMA contra datos reales del test (primeras 4 semanas del test)
ventas_reales_test_3904152 <- test_3904152 %>%
  slice_head(n = 4) %>%
  pull(Venta)

ventas_predichas_arma_3904152 <- as.numeric(forecast_modelo_arma_3904152$mean)

# Comparar solo si hay 4 semanas disponibles
if (length(ventas_reales_test_3904152) == length(ventas_predichas_arma_3904152)) {
  
  mape_arma_test_3904152 <- mean(abs((ventas_reales_test_3904152 - ventas_predichas_arma_3904152) /
                                      pmax(ventas_reales_test_3904152, 0.01))) * 100
  
  rmse_arma_test_3904152 <- sqrt(mean((ventas_reales_test_3904152 - ventas_predichas_arma_3904152)^2))
  
  cat("MAPE ARMA (test - producto 3904152):", round(mape_arma_test_3904152, 2), "\n")
  cat("RMSE ARMA (test - producto 3904152):", round(rmse_arma_test_3904152, 2), "\n")
  
  # Guardar en tabla de métricas
  metricas_comparativas <- rbind(metricas_comparativas, data.frame(
    Producto = id_prod,
    Modelo = "ARMA (Test)",
    MAPE = mape_arma_test_3904152,
    RMSE = rmse_arma_test_3904152
  ))
  
} else {
  warning("No hay suficientes semanas en el test para comparar con el forecast de ARMA.")
}
## MAPE ARMA (test - producto 3904152): 18.71 
## RMSE ARMA (test - producto 3904152): 50062.24

6.4 PRODUCTO 155002

6.4.1 TRAIN

# Producto 155002
id_prod <- 155002

# Filtrar datos del producto
ventas_semanales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  arrange(Trx_Fecha) %>%
  select(Trx_Fecha, Venta)

# Crear serie temporal semanal
serie_ts <- ts(ventas_semanales$Venta, frequency = 52,
               start = c(year(min(ventas_semanales$Trx_Fecha)),
                         isoweek(min(ventas_semanales$Trx_Fecha))))

# Ajustar modelo ARMA
modelo_arma_155002 <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo_arma_155002 <- forecast(modelo_arma_155002, h = 4)

# Gráfico del pronóstico
autoplot(forecast_modelo_arma_155002) +
  labs(title = paste("Pronóstico semanal de ventas - ARMA (Producto", id_prod, ")"),
       x = "Semana", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_arma_155002 <- fitted(modelo_arma_155002)
mape_arma_155002 <- mean(abs((serie_ts - fitted_arma_155002) / pmax(serie_ts, 0.01))) * 100
rmse_arma_155002 <- sqrt(mean((serie_ts - fitted_arma_155002)^2))

# Registrar métricas en la tabla general
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape_arma_155002,
  RMSE = rmse_arma_155002
))

6.4.2 TEST

# Crear conjunto de prueba para producto 155001 (solo columnas necesarias para ARMA)
test_155002 <- test %>%
  filter(ID_Inventario == 155002) %>%
  select(Semana, Venta) %>%
  arrange(Semana) %>%
  na.omit()
# Evaluar ARMA contra datos reales del test (primeras 4 semanas del test)
ventas_reales_test_155002 <- test_155002 %>%
  slice_head(n = 4) %>%
  pull(Venta)

ventas_predichas_arma_155002 <- as.numeric(forecast_modelo_arma_155002$mean)

# Comparar solo si hay 4 semanas disponibles
if (length(ventas_reales_test_155002) == length(ventas_predichas_arma_155002)) {
  
  mape_arma_test_155002 <- mean(abs((ventas_reales_test_155002 - ventas_predichas_arma_155002) /
                                      pmax(ventas_reales_test_155002, 0.01))) * 100
  
  rmse_arma_test_155002 <- sqrt(mean((ventas_reales_test_155002 - ventas_predichas_arma_155002)^2))
  
  cat("MAPE ARMA (test - producto 155002):", round(mape_arma_test_155002, 2), "\n")
  cat("RMSE ARMA (test - producto 155002):", round(rmse_arma_test_155002, 2), "\n")
  
  # Guardar en tabla de métricas
  metricas_comparativas <- rbind(metricas_comparativas, data.frame(
    Producto = id_prod,
    Modelo = "ARMA (Test)",
    MAPE = mape_arma_test_155002,
    RMSE = rmse_arma_test_155002
  ))
  
} else {
  warning("No hay suficientes semanas en el test para comparar con el forecast de ARMA.")
}
## MAPE ARMA (test - producto 155002): 35.87 
## RMSE ARMA (test - producto 155002): 87005.41

6.5 PRODUCTO 3678055

6.5.1 TRAIN

# Producto 3678055
id_prod <- 3678055

# Filtrar datos del producto
ventas_semanales <- datos_filtrados %>%
  filter(ID_Inventario == id_prod) %>%
  arrange(Trx_Fecha) %>%
  select(Trx_Fecha, Venta)

# Crear serie temporal semanal
serie_ts <- ts(ventas_semanales$Venta, frequency = 52,
               start = c(year(min(ventas_semanales$Trx_Fecha)),
                         isoweek(min(ventas_semanales$Trx_Fecha))))

# Ajustar modelo ARMA
modelo_arma_3678055 <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
forecast_modelo_arma_3678055 <- forecast(modelo_arma_3678055, h = 4)

# Gráfico del pronóstico
autoplot(forecast_modelo_arma_3678055) +
  labs(title = paste("Pronóstico semanal de ventas - ARMA (Producto", id_prod, ")"),
       x = "Semana", y = "Ventas ($)") +
  theme_minimal()

# Calcular métricas
fitted_arma_3678055 <- fitted(modelo_arma_3678055)
mape_arma_3678055 <- mean(abs((serie_ts - fitted_arma_3678055) / pmax(serie_ts, 0.01))) * 100
rmse_arma_3678055 <- sqrt(mean((serie_ts - fitted_arma_3678055)^2))

# Registrar métricas en la tabla general
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = id_prod,
  Modelo = "ARMA",
  MAPE = mape_arma_3678055,
  RMSE = rmse_arma_3678055
))

6.5.2 TEST

# Crear conjunto de prueba para producto 155001 (solo columnas necesarias para ARMA)
test_3678055 <- test %>%
  filter(ID_Inventario == 3678055) %>%
  select(Semana, Venta) %>%
  arrange(Semana) %>%
  na.omit()
# Evaluar ARMA contra datos reales del test (primeras 4 semanas del test)
ventas_reales_test_3678055 <- test_3678055 %>%
  slice_head(n = 4) %>%
  pull(Venta)

ventas_predichas_arma_3678055 <- as.numeric(forecast_modelo_arma_3678055$mean)

# Comparar solo si hay 4 semanas disponibles
if (length(ventas_reales_test_3678055) == length(ventas_predichas_arma_3678055)) {
  
  mape_arma_test_3678055 <- mean(abs((ventas_reales_test_3678055 - ventas_predichas_arma_3678055) /
                                      pmax(ventas_reales_test_3678055, 0.01))) * 100
  
  rmse_arma_test_3678055 <- sqrt(mean((ventas_reales_test_3678055 - ventas_predichas_arma_3678055)^2))
  
  cat("MAPE ARMA (test - producto 3678055):", round(mape_arma_test_3678055, 2), "\n")
  cat("RMSE ARMA (test - producto 3678055):", round(rmse_arma_test_3678055, 2), "\n")
  
  # Guardar en tabla de métricas
  metricas_comparativas <- rbind(metricas_comparativas, data.frame(
    Producto = id_prod,
    Modelo = "ARMA (Test)",
    MAPE = mape_arma_test_3678055,
    RMSE = rmse_arma_test_3678055
  ))
  
} else {
  warning("No hay suficientes semanas en el test para comparar con el forecast de ARMA.")
}
## MAPE ARMA (test - producto 3678055): 53.78 
## RMSE ARMA (test - producto 3678055): 105823.1

6.6 MÉTRICAS COMPARATIVAS ARMA

metricas_comparativas %>%
  arrange(Producto) %>%
  knitr::kable(caption = "Métricas ARMA por producto") %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas ARMA por producto
Producto Modelo MAPE RMSE
155001 ARMA 37.96077 109593.78
155001 ARMA (Test) 25.14259 67277.26
155002 ARMA 51.33213 82260.20
155002 ARMA (Test) 35.86659 87005.41
3678055 ARMA 54.27943 82835.18
3678055 ARMA (Test) 53.77550 105823.13
3904152 ARMA 41.54527 99993.25
3904152 ARMA (Test) 18.70572 50062.24
3929788 ARMA 21.74312 57634.89
3929788 ARMA (Test) 26.25704 60053.08

7 REGRESION LINEAL

7.1 MAPA DE CALOR

# Variables numéricas relevantes para el modelo
vars_numericas <- c("Venta", 
                    "Precio_Final_Unitario", 
                    "Venta_Semana_Anterior",
                    "Cant_Semana_Anterior", 
                    "Descuento_Semana_Anterior",
                    "Costo_Unitario_Semana_Anterior")

# Preparación de los datos
datos_cor <- datos_filtrados %>%
  select(all_of(vars_numericas)) %>%
  na.omit()

# Generar la matriz de correlación
matriz_cor <- cor(datos_cor)

# Mapa de calor de correlaciones
ggcorrplot(matriz_cor,
           method = "square",
           type = "upper",
           lab = TRUE,
           lab_size = 2,
           tl.cex = 10,
           tl.srt = 45,
           colors = c("#6D9EC1", "white", "#E46726"),
           title = "Mapa de Correlación - Variables del Modelo",
           ggtheme = theme_minimal(base_size = 14) +
             theme(
               axis.text.x = element_text(angle = 45, hjust = 1),
               axis.text.y = element_text(angle = 0, hjust = 1))
)

7.2 PRODUCTO 155001

7.2.1 DATOS TRAIN 155001

# Datos de entrenamiento
datos_155001 <- datos_filtrados %>%
  filter(ID_Inventario == 155001) %>%
  select(Venta, Precio_Final_Unitario, Venta_Semana_Anterior,
         Cant_Semana_Anterior, Descuento_Semana_Anterior,
         Costo_Unitario_Semana_Anterior, Semana) %>%
  na.omit()

7.2.2 DATOS TEST 155001

test_155001 <- test %>%
  filter(ID_Inventario == 155001) %>%
  select(Venta, Precio_Final_Unitario, Venta_Semana_Anterior,
         Cant_Semana_Anterior, Descuento_Semana_Anterior,
         Costo_Unitario_Semana_Anterior, Semana) %>%
  na.omit()

7.2.3 MODELO

# Ajustar modelo con entrenamiento
modelo_regresion_155001 <- lm(
  Venta ~ Precio_Final_Unitario +
          Venta_Semana_Anterior +
          Cant_Semana_Anterior +
          Descuento_Semana_Anterior +
          Costo_Unitario_Semana_Anterior +
          Semana,
  data = datos_155001
)

# Ver resumen
summary(modelo_regresion_155001)
## 
## Call:
## lm(formula = Venta ~ Precio_Final_Unitario + Venta_Semana_Anterior + 
##     Cant_Semana_Anterior + Descuento_Semana_Anterior + Costo_Unitario_Semana_Anterior + 
##     Semana, data = datos_155001)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -159293  -67596  -17673   54324  500965 
## 
## Coefficients:
##                                  Estimate Std. Error t value Pr(>|t|)  
## (Intercept)                     2.560e+06  2.305e+06   1.111    0.270  
## Precio_Final_Unitario           3.382e+02  6.551e+02   0.516    0.607  
## Venta_Semana_Anterior           9.614e-01  8.326e-01   1.155    0.252  
## Cant_Semana_Anterior           -3.751e+02  3.254e+02  -1.153    0.252  
## Descuento_Semana_Anterior      -2.475e+06  2.427e+06  -1.020    0.311  
## Costo_Unitario_Semana_Anterior -9.635e+02  5.250e+02  -1.835    0.070 .
## Semana                          1.870e+03  1.276e+03   1.465    0.147  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 116000 on 84 degrees of freedom
## Multiple R-squared:  0.06998,    Adjusted R-squared:  0.003546 
## F-statistic: 1.053 on 6 and 84 DF,  p-value: 0.3972
# Predicciones
pred_train <- predict(modelo_regresion_155001, newdata = datos_155001)
pred_test  <- predict(modelo_regresion_155001, newdata = test_155001)

# Métricas - entrenamiento
mape_train <- mean(abs((datos_155001$Venta - pred_train) / pmax(datos_155001$Venta, 0.01))) * 100
rmse_train <- sqrt(mean((datos_155001$Venta - pred_train)^2))

# Métricas - prueba
mape_test <- mean(abs((test_155001$Venta - pred_test) / pmax(test_155001$Venta, 0.01))) * 100
rmse_test <- sqrt(mean((test_155001$Venta - pred_test)^2))

# Mostrar resultados
cat("Train - MAPE:", round(mape_train, 2), "| RMSE:", round(rmse_train, 2), "\n")
## Train - MAPE: 39.93 | RMSE: 111408.3
cat("Test  - MAPE:", round(mape_test, 2), "| RMSE:", round(rmse_test, 2), "\n")
## Test  - MAPE: 89.84 | RMSE: 157598.3

7.2.4 TABLA DE MÉTRICAS

# Agregar métricas del entrenamiento
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155001",
  Modelo = "Regresión Lineal (Train)",
  MAPE = mape_train,
  RMSE = rmse_train,
  stringsAsFactors = FALSE
))

# Agregar métricas del test
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155001",
  Modelo = "Regresión Lineal (Test)",
  MAPE = mape_test,
  RMSE = rmse_test,
  stringsAsFactors = FALSE
))

7.3 PRODUCTO 3929788

7.3.1 DATOS TRAIN

#Datos de entrenamiento
datos_3929788 <- datos_filtrados %>%
  filter(ID_Inventario == 3929788) %>%
  select(Venta, Precio_Final_Unitario, Venta_Semana_Anterior,
         Cant_Semana_Anterior, Descuento_Semana_Anterior,
         Costo_Unitario_Semana_Anterior, Semana) %>%
  na.omit()

7.3.2 DATOS TEST

# Datos de prueba
test_3929788 <- test %>%
  filter(ID_Inventario == 3929788) %>%
  select(Venta, Precio_Final_Unitario, Venta_Semana_Anterior,
         Cant_Semana_Anterior, Descuento_Semana_Anterior,
         Costo_Unitario_Semana_Anterior, Semana) %>%
  na.omit()

7.3.3 MODELO

# Ajustar modelo con entrenamiento
modelo_regresion_3929788 <- lm(
  Venta ~ Precio_Final_Unitario +
          Venta_Semana_Anterior +
          Cant_Semana_Anterior +
          Descuento_Semana_Anterior +
          Costo_Unitario_Semana_Anterior +
          Semana,
  data = datos_3929788
)

# Ver resumen
summary(modelo_regresion_3929788)
## 
## Call:
## lm(formula = Venta ~ Precio_Final_Unitario + Venta_Semana_Anterior + 
##     Cant_Semana_Anterior + Descuento_Semana_Anterior + Costo_Unitario_Semana_Anterior + 
##     Semana, data = datos_3929788)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -103757  -32434   -2506   31479  190600 
## 
## Coefficients:
##                                  Estimate Std. Error t value Pr(>|t|)  
## (Intercept)                    -1.151e+06  5.390e+05  -2.136   0.0356 *
## Precio_Final_Unitario           1.167e+04  6.397e+03   1.824   0.0717 .
## Venta_Semana_Anterior          -9.155e-01  1.056e+00  -0.867   0.3886  
## Cant_Semana_Anterior            2.698e+01  3.946e+01   0.684   0.4960  
## Descuento_Semana_Anterior       1.073e+06  7.733e+05   1.387   0.1691  
## Costo_Unitario_Semana_Anterior  9.620e+03  9.973e+03   0.965   0.3375  
## Semana                         -1.191e+01  4.929e+02  -0.024   0.9808  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 56120 on 84 degrees of freedom
## Multiple R-squared:  0.2373, Adjusted R-squared:  0.1828 
## F-statistic: 4.356 on 6 and 84 DF,  p-value: 0.0007082
# Predicciones
pred_train_3929788 <- predict(modelo_regresion_3929788, newdata = datos_3929788)
pred_test_3929788  <- predict(modelo_regresion_3929788, newdata = test_3929788)

# Métricas - entrenamiento
mape_train_3929788 <- mean(abs((datos_3929788$Venta - pred_train_3929788) / pmax(datos_3929788$Venta, 0.01))) * 100
rmse_train_3929788 <- sqrt(mean((datos_3929788$Venta - pred_train_3929788)^2))

# Métricas - prueba
mape_test_3929788 <- mean(abs((test_3929788$Venta - pred_test_3929788) / pmax(test_3929788$Venta, 0.01))) * 100
rmse_test_3929788 <- sqrt(mean((test_3929788$Venta - pred_test_3929788)^2))

# Mostrar resultados
cat("Train - MAPE:", round(mape_train_3929788, 2), "| RMSE:", round(rmse_train_3929788, 2), "\n")
## Train - MAPE: 20.53 | RMSE: 53920.09
cat("Test  - MAPE:", round(mape_test_3929788, 2), "| RMSE:", round(rmse_test_3929788, 2), "\n")
## Test  - MAPE: 34.22 | RMSE: 68189.37

7.3.4 TABLA DE MÉTRICAS

# Agregar métricas del entrenamiento
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3929788",
  Modelo = "Regresión Lineal (Train)",
  MAPE = mape_train_3929788,
  RMSE = rmse_train_3929788,
  stringsAsFactors = FALSE
))

# Agregar métricas del test
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3929788",
  Modelo = "Regresión Lineal (Test)",
  MAPE = mape_test_3929788,
  RMSE = rmse_test_3929788,
  stringsAsFactors = FALSE
))

7.4 PRODUCTO 3904152

7.4.1 DATOS TRAIN

# Datos de entrenamiento
datos_3904152 <- datos_filtrados %>%
  filter(ID_Inventario == 3904152) %>%
  select(Venta, Precio_Final_Unitario, Venta_Semana_Anterior,
         Cant_Semana_Anterior, Descuento_Semana_Anterior,
         Costo_Unitario_Semana_Anterior, Semana) %>%
  na.omit()

7.4.2 DATOS TEST

# Datos de prueba
test_3904152 <- test %>%
  filter(ID_Inventario == 3904152) %>%
  select(Venta, Precio_Final_Unitario, Venta_Semana_Anterior,
         Cant_Semana_Anterior, Descuento_Semana_Anterior,
         Costo_Unitario_Semana_Anterior, Semana) %>%
  na.omit()

7.4.3 MODELO

# Ajustar modelo con entrenamiento
modelo_regresion_3904152 <- lm(
  Venta ~ Precio_Final_Unitario +
          Venta_Semana_Anterior +
          Cant_Semana_Anterior +
          Descuento_Semana_Anterior +
          Costo_Unitario_Semana_Anterior +
          Semana,
  data = datos_3904152
)

# Ver resumen
summary(modelo_regresion_3904152)
## 
## Call:
## lm(formula = Venta ~ Precio_Final_Unitario + Venta_Semana_Anterior + 
##     Cant_Semana_Anterior + Descuento_Semana_Anterior + Costo_Unitario_Semana_Anterior + 
##     Semana, data = datos_3904152)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -131990  -54677  -29793   30625  529564 
## 
## Coefficients:
##                                  Estimate Std. Error t value Pr(>|t|)
## (Intercept)                    -1.249e+06  2.207e+06  -0.566    0.573
## Precio_Final_Unitario          -3.568e+02  2.237e+02  -1.595    0.114
## Venta_Semana_Anterior           1.096e+00  2.348e+00   0.467    0.642
## Cant_Semana_Anterior           -3.747e+03  7.155e+03  -0.524    0.602
## Descuento_Semana_Anterior       7.452e+05  2.389e+06   0.312    0.756
## Costo_Unitario_Semana_Anterior  8.577e+02  5.544e+02   1.547    0.126
## Semana                          1.395e+03  9.821e+02   1.420    0.159
## 
## Residual standard error: 108400 on 84 degrees of freedom
## Multiple R-squared:  0.06031,    Adjusted R-squared:  -0.006815 
## F-statistic: 0.8985 on 6 and 84 DF,  p-value: 0.5001
# Predicciones
pred_train_3904152 <- predict(modelo_regresion_3904152, newdata = datos_3904152)
pred_test_3904152  <- predict(modelo_regresion_3904152, newdata = test_3904152)

# Métricas - entrenamiento
mape_train_3904152 <- mean(abs((datos_3904152$Venta - pred_train_3904152) / pmax(datos_3904152$Venta, 0.01))) * 100
rmse_train_3904152 <- sqrt(mean((datos_3904152$Venta - pred_train_3904152)^2))

# Métricas - prueba
mape_test_3904152 <- mean(abs((test_3904152$Venta - pred_test_3904152) / pmax(test_3904152$Venta, 0.01))) * 100
rmse_test_3904152 <- sqrt(mean((test_3904152$Venta - pred_test_3904152)^2))

# Mostrar resultados
cat("Train - MAPE:", round(mape_train_3904152, 2), "| RMSE:", round(rmse_train_3904152, 2), "\n")
## Train - MAPE: 38.99 | RMSE: 104128.9
cat("Test  - MAPE:", round(mape_test_3904152, 2), "| RMSE:", round(rmse_test_3904152, 2), "\n")
## Test  - MAPE: 196.4 | RMSE: 209230

7.4.4 TABLA DE MÉTRICAS

# Agregar métricas del entrenamiento
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3904152",
  Modelo = "Regresión Lineal (Train)",
  MAPE = mape_train_3904152,
  RMSE = rmse_train_3904152,
  stringsAsFactors = FALSE
))

# Agregar métricas del test
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3904152",
  Modelo = "Regresión Lineal (Test)",
  MAPE = mape_test_3904152,
  RMSE = rmse_test_3904152,
  stringsAsFactors = FALSE
))

7.5 PRODUCTO 155002

7.5.1 DATOS TRAIN

# Datos de entrenamiento
datos_155002 <- datos_filtrados %>%
  filter(ID_Inventario == 155002) %>%
  select(Venta, Precio_Final_Unitario, Venta_Semana_Anterior,
         Cant_Semana_Anterior, Descuento_Semana_Anterior,
         Costo_Unitario_Semana_Anterior, Semana) %>%
  na.omit()

7.5.2 DATOS TEST

# Datos de prueba
test_155002 <- test %>%
  filter(ID_Inventario == 155002) %>%
  select(Venta, Precio_Final_Unitario, Venta_Semana_Anterior,
         Cant_Semana_Anterior, Descuento_Semana_Anterior,
         Costo_Unitario_Semana_Anterior, Semana) %>%
  na.omit()

7.5.3 MODELO

# Ajustar modelo con entrenamiento
modelo_regresion_155002 <- lm(
  Venta ~ Precio_Final_Unitario +
          Venta_Semana_Anterior +
          Cant_Semana_Anterior +
          Descuento_Semana_Anterior +
          Costo_Unitario_Semana_Anterior +
          Semana,
  data = datos_155002
)

# Ver resumen
summary(modelo_regresion_155002)
## 
## Call:
## lm(formula = Venta ~ Precio_Final_Unitario + Venta_Semana_Anterior + 
##     Cant_Semana_Anterior + Descuento_Semana_Anterior + Costo_Unitario_Semana_Anterior + 
##     Semana, data = datos_155002)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -133199  -55557  -19787   38873  301369 
## 
## Coefficients:
##                                  Estimate Std. Error t value Pr(>|t|)
## (Intercept)                     1.823e+06  1.367e+06   1.334    0.186
## Precio_Final_Unitario          -3.923e+02  3.849e+02  -1.019    0.311
## Venta_Semana_Anterior           1.082e+00  7.425e-01   1.457    0.149
## Cant_Semana_Anterior           -3.451e+02  3.021e+02  -1.142    0.257
## Descuento_Semana_Anterior      -1.732e+06  1.443e+06  -1.201    0.233
## Costo_Unitario_Semana_Anterior -6.269e+01  1.928e+02  -0.325    0.746
## Semana                          1.019e+03  7.571e+02   1.346    0.182
## 
## Residual standard error: 84990 on 84 degrees of freedom
## Multiple R-squared:  0.09845,    Adjusted R-squared:  0.03405 
## F-statistic: 1.529 on 6 and 84 DF,  p-value: 0.1787
# Predicciones
pred_train_155002 <- predict(modelo_regresion_155002, newdata = datos_155002)
pred_test_155002  <- predict(modelo_regresion_155002, newdata = test_155002)

# Métricas - entrenamiento
mape_train_155002 <- mean(abs((datos_155002$Venta - pred_train_155002) / pmax(datos_155002$Venta, 0.01))) * 100
rmse_train_155002 <- sqrt(mean((datos_155002$Venta - pred_train_155002)^2))

# Métricas - prueba
mape_test_155002 <- mean(abs((test_155002$Venta - pred_test_155002) / pmax(test_155002$Venta, 0.01))) * 100
rmse_test_155002 <- sqrt(mean((test_155002$Venta - pred_test_155002)^2))

# Mostrar resultados
cat("Train - MAPE:", round(mape_train_155002, 2), "| RMSE:", round(rmse_train_155002, 2), "\n")
## Train - MAPE: 53.31 | RMSE: 81659.41
cat("Test  - MAPE:", round(mape_test_155002, 2), "| RMSE:", round(rmse_test_155002, 2), "\n")
## Test  - MAPE: 113.63 | RMSE: 140462

7.5.4 TABLA DE MÉTRICAS

# Agregar métricas del entrenamiento
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155002",
  Modelo = "Regresión Lineal (Train)",
  MAPE = mape_train_155002,
  RMSE = rmse_train_155002,
  stringsAsFactors = FALSE
))

# Agregar métricas del test
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155002",
  Modelo = "Regresión Lineal (Test)",
  MAPE = mape_test_155002,
  RMSE = rmse_test_155002,
  stringsAsFactors = FALSE
))

7.6 PRODUCTO 3678055

7.6.1 DATOS TRAIN

# Datos de entrenamiento
datos_3678055 <- datos_filtrados %>%
  filter(ID_Inventario == 3678055) %>%
  select(Venta, Precio_Final_Unitario, Venta_Semana_Anterior,
         Cant_Semana_Anterior, Descuento_Semana_Anterior,
         Costo_Unitario_Semana_Anterior, Semana) %>%
  na.omit()

7.6.2 DATOS TEST

# Datos de prueba
test_3678055 <- test %>%
  filter(ID_Inventario == 3678055) %>%
  select(Venta, Precio_Final_Unitario, Venta_Semana_Anterior,
         Cant_Semana_Anterior, Descuento_Semana_Anterior,
         Costo_Unitario_Semana_Anterior, Semana) %>%
  na.omit()

7.6.3 MODELO

# Ajustar modelo con entrenamiento
modelo_regresion_3678055 <- lm(
  Venta ~ Precio_Final_Unitario +
          Venta_Semana_Anterior +
          Cant_Semana_Anterior +
          Descuento_Semana_Anterior +
          Costo_Unitario_Semana_Anterior +
          Semana,
  data = datos_3678055
)

# Ver resumen
summary(modelo_regresion_3678055)
## 
## Call:
## lm(formula = Venta ~ Precio_Final_Unitario + Venta_Semana_Anterior + 
##     Cant_Semana_Anterior + Descuento_Semana_Anterior + Costo_Unitario_Semana_Anterior + 
##     Semana, data = datos_3678055)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -122909  -51648  -12067   27968  366814 
## 
## Coefficients:
##                                  Estimate Std. Error t value Pr(>|t|)
## (Intercept)                     1.226e+06  1.506e+06   0.814    0.418
## Precio_Final_Unitario          -1.974e+01  9.930e+01  -0.199    0.843
## Venta_Semana_Anterior          -7.800e-01  1.962e+00  -0.398    0.692
## Cant_Semana_Anterior            4.280e+03  1.005e+04   0.426    0.671
## Descuento_Semana_Anterior      -1.270e+06  1.616e+06  -0.786    0.434
## Costo_Unitario_Semana_Anterior -3.479e+01  2.174e+02  -0.160    0.873
## Semana                         -2.750e+01  5.980e+02  -0.046    0.963
## 
## Residual standard error: 85860 on 84 degrees of freedom
## Multiple R-squared:  0.01226,    Adjusted R-squared:  -0.0583 
## F-statistic: 0.1737 on 6 and 84 DF,  p-value: 0.9832
# Predicciones
pred_train_3678055 <- predict(modelo_regresion_3678055, newdata = datos_3678055)
pred_test_3678055  <- predict(modelo_regresion_3678055, newdata = test_3678055)

# Métricas - entrenamiento
mape_train_3678055 <- mean(abs((datos_3678055$Venta - pred_train_3678055) / pmax(datos_3678055$Venta, 0.01))) * 100
rmse_train_3678055 <- sqrt(mean((datos_3678055$Venta - pred_train_3678055)^2))

# Métricas - prueba
mape_test_3678055 <- mean(abs((test_3678055$Venta - pred_test_3678055) / pmax(test_3678055$Venta, 0.01))) * 100
rmse_test_3678055 <- sqrt(mean((test_3678055$Venta - pred_test_3678055)^2))

# Mostrar resultados
cat("Train - MAPE:", round(mape_train_3678055, 2), "| RMSE:", round(rmse_train_3678055, 2), "\n")
## Train - MAPE: 54.29 | RMSE: 82493.59
cat("Test  - MAPE:", round(mape_test_3678055, 2), "| RMSE:", round(rmse_test_3678055, 2), "\n")
## Test  - MAPE: 35.23 | RMSE: 125404.3

7.6.4 TABLA DE MÉTRICAS

# Agregar métricas del entrenamiento
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",
  Modelo = "Regresión Lineal (Train)",
  MAPE = mape_train_3678055,
  RMSE = rmse_train_3678055,
  stringsAsFactors = FALSE
))

# Agregar métricas del test
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",
  Modelo = "Regresión Lineal (Test)",
  MAPE = mape_test_3678055,
  RMSE = rmse_test_3678055,
  stringsAsFactors = FALSE
))

7.7 MÉTRICAS ARMA Y REG LINEAL

metricas_comparativas %>%
  arrange(Producto) %>%
  knitr::kable(caption = "Métricas ARMA & Regresion Lineal por producto") %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas ARMA & Regresion Lineal por producto
Producto Modelo MAPE RMSE
155001 ARMA 37.96077 109593.78
155001 ARMA (Test) 25.14259 67277.26
155001 Regresión Lineal (Train) 39.93493 111408.28
155001 Regresión Lineal (Test) 89.83537 157598.32
155002 ARMA 51.33213 82260.20
155002 ARMA (Test) 35.86659 87005.41
155002 Regresión Lineal (Train) 53.30857 81659.41
155002 Regresión Lineal (Test) 113.63421 140462.00
3678055 ARMA 54.27943 82835.18
3678055 ARMA (Test) 53.77550 105823.13
3678055 Regresión Lineal (Train) 54.28565 82493.59
3678055 Regresión Lineal (Test) 35.22903 125404.28
3904152 ARMA 41.54527 99993.25
3904152 ARMA (Test) 18.70572 50062.24
3904152 Regresión Lineal (Train) 38.98555 104128.86
3904152 Regresión Lineal (Test) 196.40268 209230.00
3929788 ARMA 21.74312 57634.89
3929788 ARMA (Test) 26.25704 60053.08
3929788 Regresión Lineal (Train) 20.52872 53920.09
3929788 Regresión Lineal (Test) 34.22277 68189.37

8 RANDOM FOREST

8.1 PRODUCTO 155001

# Entrenamiento
set.seed(123)
modelo_rf_155001 <- randomForest(
  Venta ~ Precio_Final_Unitario +
          Venta_Semana_Anterior +
          Cant_Semana_Anterior +
          Descuento_Semana_Anterior +
          Costo_Unitario_Semana_Anterior +
          Semana,
  data = datos_155001,
  ntree = 500,
  mtry = floor(sqrt(6)),  # 6 variables predictoras
  importance = TRUE
)

# Predicciones
pred_train_rf_155001 <- predict(modelo_rf_155001, newdata = datos_155001)
pred_test_rf_155001  <- predict(modelo_rf_155001, newdata = test_155001)

# Métricas
mape_train_rf_155001 <- mean(abs((datos_155001$Venta - pred_train_rf_155001) / pmax(datos_155001$Venta, 0.01))) * 100
rmse_train_rf_155001 <- sqrt(mean((datos_155001$Venta - pred_train_rf_155001)^2))

mape_test_rf_155001 <- mean(abs((test_155001$Venta - pred_test_rf_155001) / pmax(test_155001$Venta, 0.01))) * 100
rmse_test_rf_155001 <- sqrt(mean((test_155001$Venta - pred_test_rf_155001)^2))

# Mostrar resultados
cat("Random Forest - Producto 155001\n")
## Random Forest - Producto 155001
cat("Train - MAPE:", round(mape_train_rf_155001, 2), "| RMSE:", round(rmse_train_rf_155001, 2), "\n")
## Train - MAPE: 18.89 | RMSE: 55005.66
cat("Test  - MAPE:", round(mape_test_rf_155001, 2),  "| RMSE:", round(rmse_test_rf_155001, 2),  "\n")
## Test  - MAPE: 77.18 | RMSE: 138820.6
# Guardar métricas en tabla
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155001",
  Modelo = "Random Forest (Train)",
  MAPE = mape_train_rf_155001,
  RMSE = rmse_train_rf_155001
))

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155001",
  Modelo = "Random Forest (Test)",
  MAPE = mape_test_rf_155001,
  RMSE = rmse_test_rf_155001
))

# Importancia de variables
varImpPlot(modelo_rf_155001, main = "Importancia de Variables - Producto 155001")

# Observado vs Predicho (test)
ggplot(data.frame(Observado = test_155001$Venta, Predicho = pred_test_rf_155001),
       aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(slope = 1, intercept = 0, color = "red", linetype = "dashed") +
  labs(title = "Observado vs Predicho - Producto 155001 (test data)",
       x = "Venta Observada", y = "Venta Predicha") +
  theme_minimal()

# Análisis de errores (test)
errores_test_155001 <- test_155001$Venta - pred_test_rf_155001
hist(errores_test_155001, main = "Distribución de Errores - Producto 155001 (test data)",
     xlab = "Error (Observado - Predicho)", col = "skyblue", breaks = 30)

cat("Media error:", mean(errores_test_155001), "\n")
## Media error: -69719.5
cat("SD error:", sd(errores_test_155001), "\n")
## SD error: 124944.8
cat("Min error:", min(errores_test_155001), "\n")
## Min error: -207234.7
cat("Max error:", max(errores_test_155001), "\n")
## Max error: 249075.9
cat("Mediana error:", median(errores_test_155001), "\n")
## Mediana error: -108089.3
# Error vs Predicción (test)
ggplot(data.frame(Predicho = pred_test_rf_155001, Error = errores_test_155001),
       aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(title = "Error vs Predicción - Producto 155001 (test data)",
       x = "Venta Predicha", y = "Error") +
  theme_minimal()

8.2 PRODUCTO 3929788

# Entrenamiento
set.seed(123)
modelo_rf_3929788 <- randomForest(
  Venta ~ Precio_Final_Unitario +
          Venta_Semana_Anterior +
          Cant_Semana_Anterior +
          Descuento_Semana_Anterior +
          Costo_Unitario_Semana_Anterior +
          Semana,
  data = datos_3929788,
  ntree = 500,
  mtry = floor(sqrt(6)),  # 6 variables predictoras
  importance = TRUE
)

# Predicciones
pred_train_rf_3929788 <- predict(modelo_rf_3929788, newdata = datos_3929788)
pred_test_rf_3929788  <- predict(modelo_rf_3929788, newdata = test_3929788)

# Métricas
mape_train_rf_3929788 <- mean(abs((datos_3929788$Venta - pred_train_rf_3929788) / pmax(datos_3929788$Venta, 0.01))) * 100
rmse_train_rf_3929788 <- sqrt(mean((datos_3929788$Venta - pred_train_rf_3929788)^2))

mape_test_rf_3929788 <- mean(abs((test_3929788$Venta - pred_test_rf_3929788) / pmax(test_3929788$Venta, 0.01))) * 100
rmse_test_rf_3929788 <- sqrt(mean((test_3929788$Venta - pred_test_rf_3929788)^2))

# Mostrar resultados
cat("Random Forest - Producto 3929788\n")
## Random Forest - Producto 3929788
cat("Train - MAPE:", round(mape_train_rf_3929788, 2), "| RMSE:", round(rmse_train_rf_3929788, 2), "\n")
## Train - MAPE: 10.27 | RMSE: 27004.1
cat("Test  - MAPE:", round(mape_test_rf_3929788, 2),  "| RMSE:", round(rmse_test_rf_3929788, 2),  "\n")
## Test  - MAPE: 26.96 | RMSE: 56210.34
# Guardar métricas en tabla
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3929788",
  Modelo = "Random Forest (Train)",
  MAPE = mape_train_rf_3929788,
  RMSE = rmse_train_rf_3929788
))

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3929788",
  Modelo = "Random Forest (Test)",
  MAPE = mape_test_rf_3929788,
  RMSE = rmse_test_rf_3929788
))

# Importancia de variables
varImpPlot(modelo_rf_3929788, main = "Importancia de Variables - Producto 3929788")

# Observado vs Predicho (test)
ggplot(data.frame(Observado = test_3929788$Venta, Predicho = pred_test_rf_3929788),
       aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(slope = 1, intercept = 0, color = "red", linetype = "dashed") +
  labs(title = "Observado vs Predicho - Producto 3929788 (test data)",
       x = "Venta Observada", y = "Venta Predicha") +
  theme_minimal()

# Análisis de errores (test)
errores_test_3929788 <- test_3929788$Venta - pred_test_rf_3929788
hist(errores_test_3929788, main = "Distribución de Errores - Producto 3929788 (test data)",
     xlab = "Error (Observado - Predicho)", col = "skyblue", breaks = 30)

cat("Media error:", mean(errores_test_3929788), "\n")
## Media error: -12664.42
cat("SD error:", sd(errores_test_3929788), "\n")
## SD error: 57001.31
cat("Min error:", min(errores_test_3929788), "\n")
## Min error: -96490.54
cat("Max error:", max(errores_test_3929788), "\n")
## Max error: 77535.02
cat("Mediana error:", median(errores_test_3929788), "\n")
## Mediana error: -14091.98
# Error vs Predicción (test)
ggplot(data.frame(Predicho = pred_test_rf_3929788, Error = errores_test_3929788),
       aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(title = "Error vs Predicción - Producto 3929788 (test data)",
       x = "Venta Predicha", y = "Error") +
  theme_minimal()

8.3 PRODUCTO 3904152

# Entrenamiento
set.seed(123)
modelo_rf_3904152 <- randomForest(
  Venta ~ Precio_Final_Unitario +
          Venta_Semana_Anterior +
          Cant_Semana_Anterior +
          Descuento_Semana_Anterior +
          Costo_Unitario_Semana_Anterior +
          Semana,
  data = datos_3904152,
  ntree = 500,
  mtry = floor(sqrt(6)),  # 6 variables predictoras
  importance = TRUE
)

# Predicciones
pred_train_rf_3904152 <- predict(modelo_rf_3904152, newdata = datos_3904152)
pred_test_rf_3904152  <- predict(modelo_rf_3904152, newdata = test_3904152)

# Métricas
mape_train_rf_3904152 <- mean(abs((datos_3904152$Venta - pred_train_rf_3904152) / pmax(datos_3904152$Venta, 0.01))) * 100
rmse_train_rf_3904152 <- sqrt(mean((datos_3904152$Venta - pred_train_rf_3904152)^2))

mape_test_rf_3904152 <- mean(abs((test_3904152$Venta - pred_test_rf_3904152) / pmax(test_3904152$Venta, 0.01))) * 100
rmse_test_rf_3904152 <- sqrt(mean((test_3904152$Venta - pred_test_rf_3904152)^2))

# Mostrar resultados
cat("Random Forest - Producto 3904152\n")
## Random Forest - Producto 3904152
cat("Train - MAPE:", round(mape_train_rf_3904152, 2), "| RMSE:", round(rmse_train_rf_3904152, 2), "\n")
## Train - MAPE: 19.28 | RMSE: 53268.65
cat("Test  - MAPE:", round(mape_test_rf_3904152, 2),  "| RMSE:", round(rmse_test_rf_3904152, 2),  "\n")
## Test  - MAPE: 87.56 | RMSE: 101889.8
# Guardar métricas en tabla
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3904152",
  Modelo = "Random Forest (Train)",
  MAPE = mape_train_rf_3904152,
  RMSE = rmse_train_rf_3904152
))

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3904152",
  Modelo = "Random Forest (Test)",
  MAPE = mape_test_rf_3904152,
  RMSE = rmse_test_rf_3904152
))

# Importancia de variables
varImpPlot(modelo_rf_3904152, main = "Importancia de Variables - Producto 3904152")

# Observado vs Predicho (test)
ggplot(data.frame(Observado = test_3904152$Venta, Predicho = pred_test_rf_3904152),
       aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(slope = 1, intercept = 0, color = "red", linetype = "dashed") +
  labs(title = "Observado vs Predicho - Producto 3904152 (test data)",
       x = "Venta Observada", y = "Venta Predicha") +
  theme_minimal()

# Análisis de errores (test)
errores_test_3904152 <- test_3904152$Venta - pred_test_rf_3904152
hist(errores_test_3904152, main = "Distribución de Errores - Producto 3904152 (test data)",
     xlab = "Error (Observado - Predicho)", col = "skyblue", breaks = 30)

cat("Media error:", mean(errores_test_3904152), "\n")
## Media error: -59820.25
cat("SD error:", sd(errores_test_3904152), "\n")
## SD error: 85848.66
cat("Min error:", min(errores_test_3904152), "\n")
## Min error: -208194.5
cat("Max error:", max(errores_test_3904152), "\n")
## Max error: 151176.3
cat("Mediana error:", median(errores_test_3904152), "\n")
## Mediana error: -67643.5
# Error vs Predicción (test)
ggplot(data.frame(Predicho = pred_test_rf_3904152, Error = errores_test_3904152),
       aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(title = "Error vs Predicción - Producto 3904152 (test data)",
       x = "Venta Predicha", y = "Error") +
  theme_minimal()

8.4 PRODUCTO 155002

# Entrenamiento
set.seed(123)
modelo_rf_155002 <- randomForest(
  Venta ~ Precio_Final_Unitario +
          Venta_Semana_Anterior +
          Cant_Semana_Anterior +
          Descuento_Semana_Anterior +
          Costo_Unitario_Semana_Anterior +
          Semana,
  data = datos_155002,
  ntree = 500,
  mtry = floor(sqrt(6)),  # 6 variables predictoras
  importance = TRUE
)

# Predicciones
pred_train_rf_155002 <- predict(modelo_rf_155002, newdata = datos_155002)
pred_test_rf_155002  <- predict(modelo_rf_155002, newdata = test_155002)

# Métricas
mape_train_rf_155002 <- mean(abs((datos_155002$Venta - pred_train_rf_155002) / pmax(datos_155002$Venta, 0.01))) * 100
rmse_train_rf_155002 <- sqrt(mean((datos_155002$Venta - pred_train_rf_155002)^2))

mape_test_rf_155002 <- mean(abs((test_155002$Venta - pred_test_rf_155002) / pmax(test_155002$Venta, 0.01))) * 100
rmse_test_rf_155002 <- sqrt(mean((test_155002$Venta - pred_test_rf_155002)^2))

# Mostrar resultados
cat("Random Forest - Producto 155002\n")
## Random Forest - Producto 155002
cat("Train - MAPE:", round(mape_train_rf_155002, 2), "| RMSE:", round(rmse_train_rf_155002, 2), "\n")
## Train - MAPE: 24.91 | RMSE: 40622.51
cat("Test  - MAPE:", round(mape_test_rf_155002, 2),  "| RMSE:", round(rmse_test_rf_155002, 2),  "\n")
## Test  - MAPE: 101.53 | RMSE: 122531.1
# Guardar métricas en tabla
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155002",
  Modelo = "Random Forest (Train)",
  MAPE = mape_train_rf_155002,
  RMSE = rmse_train_rf_155002
))

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155002",
  Modelo = "Random Forest (Test)",
  MAPE = mape_test_rf_155002,
  RMSE = rmse_test_rf_155002
))

# Importancia de variables
varImpPlot(modelo_rf_155002, main = "Importancia de Variables - Producto 155002")

# Observado vs Predicho (test)
ggplot(data.frame(Observado = test_155002$Venta, Predicho = pred_test_rf_155002),
       aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(slope = 1, intercept = 0, color = "red", linetype = "dashed") +
  labs(title = "Observado vs Predicho - Producto 155002 (test data)",
       x = "Venta Observada", y = "Venta Predicha") +
  theme_minimal()

# Análisis de errores (test)
errores_test_155002 <- test_155002$Venta - pred_test_rf_155002
hist(errores_test_155002, main = "Distribución de Errores - Producto 155002 (test data)",
     xlab = "Error (Observado - Predicho)", col = "skyblue", breaks = 30)

cat("Media error:", mean(errores_test_155002), "\n")
## Media error: -10056.18
cat("SD error:", sd(errores_test_155002), "\n")
## SD error: 127104.2
cat("Min error:", min(errores_test_155002), "\n")
## Min error: -126419.2
cat("Max error:", max(errores_test_155002), "\n")
## Max error: 224898.8
cat("Mediana error:", median(errores_test_155002), "\n")
## Mediana error: -56067.92
# Error vs Predicción (test)
ggplot(data.frame(Predicho = pred_test_rf_155002, Error = errores_test_155002),
       aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(title = "Error vs Predicción - Producto 155002 (test data)",
       x = "Venta Predicha", y = "Error") +
  theme_minimal()

8.5 PRODUCTO 3678055

# Entrenamiento
set.seed(123)
modelo_rf_3678055 <- randomForest(
  Venta ~ Precio_Final_Unitario +
          Venta_Semana_Anterior +
          Cant_Semana_Anterior +
          Descuento_Semana_Anterior +
          Costo_Unitario_Semana_Anterior +
          Semana,
  data = datos_3678055,
  ntree = 500,
  mtry = floor(sqrt(6)),  # 6 variables predictoras
  importance = TRUE
)

# Predicciones
pred_train_rf_3678055 <- predict(modelo_rf_3678055, newdata = datos_3678055)
pred_test_rf_3678055  <- predict(modelo_rf_3678055, newdata = test_3678055)

# Métricas
mape_train_rf_3678055 <- mean(abs((datos_3678055$Venta - pred_train_rf_3678055) / pmax(datos_3678055$Venta, 0.01))) * 100
rmse_train_rf_3678055 <- sqrt(mean((datos_3678055$Venta - pred_train_rf_3678055)^2))

mape_test_rf_3678055 <- mean(abs((test_3678055$Venta - pred_test_rf_3678055) / pmax(test_3678055$Venta, 0.01))) * 100
rmse_test_rf_3678055 <- sqrt(mean((test_3678055$Venta - pred_test_rf_3678055)^2))

# Mostrar resultados
cat("Random Forest - Producto 3678055\n")
## Random Forest - Producto 3678055
cat("Train - MAPE:", round(mape_train_rf_3678055, 2), "| RMSE:", round(rmse_train_rf_3678055, 2), "\n")
## Train - MAPE: 26.16 | RMSE: 40659.52
cat("Test  - MAPE:", round(mape_test_rf_3678055, 2),  "| RMSE:", round(rmse_test_rf_3678055, 2),  "\n")
## Test  - MAPE: 46.57 | RMSE: 103169.6
# Guardar métricas en tabla
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",
  Modelo = "Random Forest (Train)",
  MAPE = mape_train_rf_3678055,
  RMSE = rmse_train_rf_3678055
))

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",
  Modelo = "Random Forest (Test)",
  MAPE = mape_test_rf_3678055,
  RMSE = rmse_test_rf_3678055
))

# Importancia de variables
varImpPlot(modelo_rf_3678055, main = "Importancia de Variables - Producto 3678055")

# Observado vs Predicho (test)
ggplot(data.frame(Observado = test_3678055$Venta, Predicho = pred_test_rf_3678055),
       aes(x = Observado, y = Predicho)) +
  geom_point(alpha = 0.5) +
  geom_abline(slope = 1, intercept = 0, color = "red", linetype = "dashed") +
  labs(title = "Observado vs Predicho - Producto 3678055 (test data)",
       x = "Venta Observada", y = "Venta Predicha") +
  theme_minimal()

# Análisis de errores (test)
errores_test_3678055 <- test_3678055$Venta - pred_test_rf_3678055
hist(errores_test_3678055, main = "Distribución de Errores - Producto 3678055 (test data)",
     xlab = "Error (Observado - Predicho)", col = "skyblue", breaks = 30)

cat("Media error:", mean(errores_test_3678055), "\n")
## Media error: 4118.975
cat("SD error:", sd(errores_test_3678055), "\n")
## SD error: 107296.7
cat("Min error:", min(errores_test_3678055), "\n")
## Min error: -104929.6
cat("Max error:", max(errores_test_3678055), "\n")
## Max error: 206123.7
cat("Mediana error:", median(errores_test_3678055), "\n")
## Mediana error: -34586.45
# Error vs Predicción (test)
ggplot(data.frame(Predicho = pred_test_rf_3678055, Error = errores_test_3678055),
       aes(x = Predicho, y = Error)) +
  geom_point(alpha = 0.5) +
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
  labs(title = "Error vs Predicción - Producto 3678055 (test data)",
       x = "Venta Predicha", y = "Error") +
  theme_minimal()

8.6 MÉTRICAS ARMA, REG LINEAL Y RANDOM FOREST

metricas_comparativas %>%
  arrange(Producto) %>%
  knitr::kable(caption = "Métricas ARMA, Regresion Lineal y Random Forest por producto") %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas ARMA, Regresion Lineal y Random Forest por producto
Producto Modelo MAPE RMSE
155001 ARMA 37.96077 109593.78
155001 ARMA (Test) 25.14259 67277.26
155001 Regresión Lineal (Train) 39.93493 111408.28
155001 Regresión Lineal (Test) 89.83537 157598.32
155001 Random Forest (Train) 18.88805 55005.66
155001 Random Forest (Test) 77.18471 138820.57
155002 ARMA 51.33213 82260.20
155002 ARMA (Test) 35.86659 87005.41
155002 Regresión Lineal (Train) 53.30857 81659.41
155002 Regresión Lineal (Test) 113.63421 140462.00
155002 Random Forest (Train) 24.90641 40622.51
155002 Random Forest (Test) 101.53221 122531.14
3678055 ARMA 54.27943 82835.18
3678055 ARMA (Test) 53.77550 105823.13
3678055 Regresión Lineal (Train) 54.28565 82493.59
3678055 Regresión Lineal (Test) 35.22903 125404.28
3678055 Random Forest (Train) 26.16327 40659.52
3678055 Random Forest (Test) 46.57144 103169.62
3904152 ARMA 41.54527 99993.25
3904152 ARMA (Test) 18.70572 50062.24
3904152 Regresión Lineal (Train) 38.98555 104128.86
3904152 Regresión Lineal (Test) 196.40268 209230.00
3904152 Random Forest (Train) 19.28014 53268.65
3904152 Random Forest (Test) 87.56263 101889.81
3929788 ARMA 21.74312 57634.89
3929788 ARMA (Test) 26.25704 60053.08
3929788 Regresión Lineal (Train) 20.52872 53920.09
3929788 Regresión Lineal (Test) 34.22277 68189.37
3929788 Random Forest (Train) 10.27272 27004.10
3929788 Random Forest (Test) 26.95816 56210.34

9 XGBOOST

9.1 PRODUCTO 155001

9.1.1 SPLIT INTERNO

# Dividir los datos en entrenamiento (80%) y validación interna (20%)
set.seed(123)  # Para reproducibilidad

indices_train_155001 <- createDataPartition(datos_155001$Venta, p = 0.8, list = FALSE)

# Crear subconjuntos
datos_train_155001 <- datos_155001[indices_train_155001, ]
datos_test_155001  <- datos_155001[-indices_train_155001, ]

# Matrices predictoras y objetivo
X_train_155001 <- as.matrix(datos_train_155001[, colnames(datos_train_155001) != "Venta"])
y_train_155001 <- datos_train_155001$Venta

X_test_155001 <- as.matrix(datos_test_155001[, colnames(datos_test_155001) != "Venta"])
y_test_155001 <- datos_test_155001$Venta

# Convertir a formato DMatrix para XGBoost
dtrain_155001 <- xgb.DMatrix(data = X_train_155001, label = y_train_155001)
dtest_155001  <- xgb.DMatrix(data = X_test_155001, label = y_test_155001)

9.1.2 BUSQUEDA DE HIPERPARAMETROS CON VALIDACIÓN CRUZADA

# Crear una rejilla de combinaciones de hiperparámetros
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1),             # Tasa de aprendizaje
  max_depth = c(3, 5, 7),               # Profundidad del árbol
  subsample = c(0.8, 1.0),              # Proporción de muestras por árbol
  colsample_bytree = c(0.8, 1.0),       # Proporción de columnas por árbol
  min_child_weight = c(1, 3),           # Peso mínimo de nodos hijos
  gamma = c(0, 0.1)                     # Regularización
)

# Reducir combinaciones para eficiencia
set.seed(123)
param_grid_reducida <- param_grid[sample(1:nrow(param_grid), 10), ]

# Función para evaluar cada combinación con validación cruzada
evaluate_params_155001 <- function(params_row) {
  params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = params_row$eta,
    max_depth = params_row$max_depth,
    subsample = params_row$subsample,
    colsample_bytree = params_row$colsample_bytree,
    min_child_weight = params_row$min_child_weight,
    gamma = params_row$gamma
  )
  
  cv <- xgb.cv(
    params = params,
    data = dtrain_155001,    # <- usa DMatrix con datos_train_155001
    nrounds = 100,
    nfold = 5,
    early_stopping_rounds = 10,
    verbose = 0
  )
  
  list(
    rmse = min(cv$evaluation_log$test_rmse_mean),
    nrounds = which.min(cv$evaluation_log$test_rmse_mean),
    params = params
  )
}
# Evaluar todas las combinaciones
resultados_grid_155001 <- param_grid_reducida
resultados_grid_155001$nrounds <- NA
resultados_grid_155001$rmse <- NA

cat("Iniciando búsqueda de hiperparámetros para producto 155001...\n")
## Iniciando búsqueda de hiperparámetros para producto 155001...
for (i in 1:nrow(param_grid_reducida)) {
  cat(sprintf("Evaluando combinación %d de %d\n", i, nrow(param_grid_reducida)))
  
  resultado <- evaluate_params_155001(param_grid_reducida[i, ])
  
  resultados_grid_155001$nrounds[i] <- resultado$nrounds
  resultados_grid_155001$rmse[i] <- resultado$rmse
}
## Evaluando combinación 1 de 10
## Evaluando combinación 2 de 10
## Evaluando combinación 3 de 10
## Evaluando combinación 4 de 10
## Evaluando combinación 5 de 10
## Evaluando combinación 6 de 10
## Evaluando combinación 7 de 10
## Evaluando combinación 8 de 10
## Evaluando combinación 9 de 10
## Evaluando combinación 10 de 10
# Ordenar por mejor RMSE
resultados_grid_155001 <- resultados_grid_155001[order(resultados_grid_155001$rmse), ]

# Mostrar las 5 mejores combinaciones
cat("\nTop 5 combinaciones de hiperparámetros (Producto 155001):\n")
## 
## Top 5 combinaciones de hiperparámetros (Producto 155001):
print(head(resultados_grid_155001, 5))
##      eta max_depth subsample colsample_bytree min_child_weight gamma nrounds
## 14  0.05         5       1.0              0.8                1   0.0      48
## 144 0.10         7       1.0              1.0                3   0.1      32
## 50  0.05         5       1.0              0.8                3   0.0      63
## 137 0.05         3       1.0              1.0                3   0.1      47
## 92  0.05         3       0.8              1.0                1   0.1      55
##         rmse
## 14  113784.0
## 144 117559.9
## 50  119777.3
## 137 122169.4
## 92  127813.1

9.1.3 ENTRENA MODELO CON MEJORES HIPERPARÁMETROS

# Extraer los mejores hiperparámetros del grid
mejores_params_155001 <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados_grid_155001$eta[1],
  max_depth = resultados_grid_155001$max_depth[1],
  subsample = resultados_grid_155001$subsample[1],
  colsample_bytree = resultados_grid_155001$colsample_bytree[1],
  min_child_weight = resultados_grid_155001$min_child_weight[1],
  gamma = resultados_grid_155001$gamma[1]
)

# Número óptimo de rondas
mejor_nrounds_155001 <- resultados_grid_155001$nrounds[1]

cat("Entrenando modelo con mejores hiperparámetros...\n")
## Entrenando modelo con mejores hiperparámetros...
print(mejores_params_155001)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.05
## 
## $max_depth
## [1] 5
## 
## $subsample
## [1] 1
## 
## $colsample_bytree
## [1] 0.8
## 
## $min_child_weight
## [1] 1
## 
## $gamma
## [1] 0
cat("Número óptimo de rondas:", mejor_nrounds_155001, "\n")
## Número óptimo de rondas: 48
# Entrenar el modelo con los datos de split interno
modelo_xgb_155001_split <- xgb.train(
  params = mejores_params_155001,
  data = dtrain_155001,
  nrounds = mejor_nrounds_155001,
  watchlist = list(train = dtrain_155001, test = dtest_155001),
  verbose = 0
)

9.1.4 MÉTRICAS INTERNAS

# Métricas sobre datos_train (80%)
pred_train_split_155001 <- predict(modelo_xgb_155001_split, dtrain_155001)
mape_train_split_155001 <- mean(abs((y_train_155001 - pred_train_split_155001) / pmax(y_train_155001, 0.01))) * 100
rmse_train_split_155001 <- sqrt(mean((y_train_155001 - pred_train_split_155001)^2))

# Métricas sobre datos_test (20%)
pred_test_split_155001 <- predict(modelo_xgb_155001_split, dtest_155001)
mape_test_split_155001 <- mean(abs((y_test_155001 - pred_test_split_155001) / pmax(y_test_155001, 0.01))) * 100
rmse_test_split_155001 <- sqrt(mean((y_test_155001 - pred_test_split_155001)^2))

cat("Split Interno - Producto 155001\n")
## Split Interno - Producto 155001
cat("Train Split - MAPE:", round(mape_train_split_155001, 2), "| RMSE:", round(rmse_train_split_155001, 2), "\n")
## Train Split - MAPE: 13.79 | RMSE: 57460.15
cat("Test  Split - MAPE:", round(mape_test_split_155001, 2),  "| RMSE:", round(rmse_test_split_155001, 2), "\n")
## Test  Split - MAPE: 35.56 | RMSE: 99743.91

9.1.5 ENTRENAMIENTO CON DATOS COMPLETOS

# Preparar matrices con todos los datos
X_entero_155001 <- as.matrix(datos_155001[, colnames(datos_155001) != "Venta"])
y_entero_155001 <- datos_155001$Venta
dtrain_final_155001 <- xgb.DMatrix(data = X_entero_155001, label = y_entero_155001)

# Entrenar el modelo final
modelo_xgb_final_155001 <- xgb.train(
  params = mejores_params_155001,
  data = dtrain_final_155001,
  nrounds = mejor_nrounds_155001,
  verbose = 0
)

# Evaluación en datos_155001
pred_final_train_155001 <- predict(modelo_xgb_final_155001, X_entero_155001)
mape_final_train_155001 <- mean(abs((y_entero_155001 - pred_final_train_155001) / pmax(y_entero_155001, 0.01))) * 100
rmse_final_train_155001 <- sqrt(mean((y_entero_155001 - pred_final_train_155001)^2))

# Evaluación en test_155001
X_test_real_155001 <- as.matrix(test_155001[, colnames(test_155001) != "Venta"])
y_test_real_155001 <- test_155001$Venta
pred_final_test_155001 <- predict(modelo_xgb_final_155001, X_test_real_155001)
mape_final_test_155001 <- mean(abs((y_test_real_155001 - pred_final_test_155001) / pmax(y_test_real_155001, 0.01))) * 100
rmse_final_test_155001 <- sqrt(mean((y_test_real_155001 - pred_final_test_155001)^2))

cat("Modelo Final - Producto 155001\n")
## Modelo Final - Producto 155001
cat("Train - MAPE:", round(mape_final_train_155001, 2), "| RMSE:", round(rmse_final_train_155001, 2), "\n")
## Train - MAPE: 15.15 | RMSE: 59186.13
cat("Test  - MAPE:", round(mape_final_test_155001, 2),  "| RMSE:", round(rmse_final_test_155001, 2), "\n")
## Test  - MAPE: 47.41 | RMSE: 113795.2

9.1.6 TABLA DE MÉTRICAS

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155001",
  Modelo = "XGBoost (Train)",
  MAPE = mape_final_train_155001,
  RMSE = rmse_final_train_155001
))

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155001",
  Modelo = "XGBoost (Test)",
  MAPE = mape_final_test_155001,
  RMSE = rmse_final_test_155001
))

9.2 PRODUCTO 3929788

9.2.1 SPLIT INTERNO

# Dividir los datos en entrenamiento (80%) y validación interna (20%)
set.seed(123)  # Para reproducibilidad

indices_train_3929788 <- createDataPartition(datos_3929788$Venta, p = 0.8, list = FALSE)

# Crear subconjuntos
datos_train_3929788 <- datos_3929788[indices_train_3929788, ]
datos_test_3929788  <- datos_3929788[-indices_train_3929788, ]

# Matrices predictoras y objetivo
X_train_3929788 <- as.matrix(datos_train_3929788[, colnames(datos_train_3929788) != "Venta"])
y_train_3929788 <- datos_train_3929788$Venta

X_test_3929788 <- as.matrix(datos_test_3929788[, colnames(datos_test_3929788) != "Venta"])
y_test_3929788 <- datos_test_3929788$Venta

# Convertir a formato DMatrix para XGBoost
dtrain_3929788 <- xgb.DMatrix(data = X_train_3929788, label = y_train_3929788)
dtest_3929788  <- xgb.DMatrix(data = X_test_3929788, label = y_test_3929788)

9.2.2 BUSQUEDA DE HIPERPARAMETROS CON VALIDACIÓN CRUZADA

# Crear una rejilla de combinaciones de hiperparámetros
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1),             # Tasa de aprendizaje
  max_depth = c(3, 5, 7),               # Profundidad del árbol
  subsample = c(0.8, 1.0),              # Proporción de muestras por árbol
  colsample_bytree = c(0.8, 1.0),       # Proporción de columnas por árbol
  min_child_weight = c(1, 3),           # Peso mínimo de nodos hijos
  gamma = c(0, 0.1)                     # Regularización
)

# Reducir combinaciones para eficiencia
set.seed(123)
param_grid_reducida <- param_grid[sample(1:nrow(param_grid), 10), ]

# Función para evaluar cada combinación con validación cruzada
evaluate_params_3929788 <- function(params_row) {
  params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = params_row$eta,
    max_depth = params_row$max_depth,
    subsample = params_row$subsample,
    colsample_bytree = params_row$colsample_bytree,
    min_child_weight = params_row$min_child_weight,
    gamma = params_row$gamma
  )
  
  cv <- xgb.cv(
    params = params,
    data = dtrain_3929788,    # <- usa DMatrix con datos_train_155001
    nrounds = 100,
    nfold = 5,
    early_stopping_rounds = 10,
    verbose = 0
  )
  
  list(
    rmse = min(cv$evaluation_log$test_rmse_mean),
    nrounds = which.min(cv$evaluation_log$test_rmse_mean),
    params = params
  )
}
# Evaluar todas las combinaciones
resultados_grid_3929788 <- param_grid_reducida
resultados_grid_3929788$nrounds <- NA
resultados_grid_3929788$rmse <- NA

cat("Iniciando búsqueda de hiperparámetros para producto 3929788...\n")
## Iniciando búsqueda de hiperparámetros para producto 3929788...
for (i in 1:nrow(param_grid_reducida)) {
  cat(sprintf("Evaluando combinación %d de %d\n", i, nrow(param_grid_reducida)))
  
  resultado <- evaluate_params_3929788(param_grid_reducida[i, ])
  
  resultados_grid_3929788$nrounds[i] <- resultado$nrounds
  resultados_grid_3929788$rmse[i] <- resultado$rmse
}
## Evaluando combinación 1 de 10
## Evaluando combinación 2 de 10
## Evaluando combinación 3 de 10
## Evaluando combinación 4 de 10
## Evaluando combinación 5 de 10
## Evaluando combinación 6 de 10
## Evaluando combinación 7 de 10
## Evaluando combinación 8 de 10
## Evaluando combinación 9 de 10
## Evaluando combinación 10 de 10
# Ordenar por mejor RMSE
resultados_grid_3929788 <- resultados_grid_3929788[order(resultados_grid_3929788$rmse), ]

# Mostrar las 5 mejores combinaciones
cat("\nTop 5 combinaciones de hiperparámetros (Producto 3929788):\n")
## 
## Top 5 combinaciones de hiperparámetros (Producto 3929788):
print(head(resultados_grid_3929788, 5))
##      eta max_depth subsample colsample_bytree min_child_weight gamma nrounds
## 50  0.05         5       1.0              0.8                3   0.0      54
## 90  0.10         7       1.0              0.8                1   0.1      33
## 144 0.10         7       1.0              1.0                3   0.1      29
## 92  0.05         3       0.8              1.0                1   0.1      48
## 137 0.05         3       1.0              1.0                3   0.1      85
##         rmse
## 50  62577.22
## 90  65352.69
## 144 66586.11
## 92  66701.52
## 137 67238.66

9.2.3 ENTRENA MODELO CON MEJORES HIPERPARÁMETROS

# Extraer los mejores hiperparámetros del grid
mejores_params_3929788 <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados_grid_3929788$eta[1],
  max_depth = resultados_grid_3929788$max_depth[1],
  subsample = resultados_grid_3929788$subsample[1],
  colsample_bytree = resultados_grid_3929788$colsample_bytree[1],
  min_child_weight = resultados_grid_3929788$min_child_weight[1],
  gamma = resultados_grid_3929788$gamma[1]
)

# Número óptimo de rondas
mejor_nrounds_3929788 <- resultados_grid_3929788$nrounds[1]

cat("Entrenando modelo con mejores hiperparámetros...\n")
## Entrenando modelo con mejores hiperparámetros...
print(mejores_params_3929788)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.05
## 
## $max_depth
## [1] 5
## 
## $subsample
## [1] 1
## 
## $colsample_bytree
## [1] 0.8
## 
## $min_child_weight
## [1] 3
## 
## $gamma
## [1] 0
cat("Número óptimo de rondas:", mejor_nrounds_3929788, "\n")
## Número óptimo de rondas: 54
# Entrenar el modelo con los datos de split interno
modelo_xgb_3929788_split <- xgb.train(
  params = mejores_params_3929788,
  data = dtrain_3929788,
  nrounds = mejor_nrounds_3929788,
  watchlist = list(train = dtrain_3929788, test = dtest_3929788),
  verbose = 0
)

9.2.4 MÉTRICAS INTERNAS

# Métricas sobre datos_train (80%)
pred_train_split_3929788 <- predict(modelo_xgb_3929788_split, dtrain_3929788)
mape_train_split_3929788 <- mean(abs((y_train_3929788 - pred_train_split_3929788) / pmax(y_train_3929788, 0.01))) * 100
rmse_train_split_3929788 <- sqrt(mean((y_train_3929788 - pred_train_split_3929788)^2))

# Métricas sobre datos_test (20%)
pred_test_split_3929788 <- predict(modelo_xgb_3929788_split, dtest_3929788)
mape_test_split_3929788 <- mean(abs((y_test_3929788 - pred_test_split_3929788) / pmax(y_test_3929788, 0.01))) * 100
rmse_test_split_3929788 <- sqrt(mean((y_test_3929788 - pred_test_split_3929788)^2))

cat("Split Interno - Producto 3929788\n")
## Split Interno - Producto 3929788
cat("Train Split - MAPE:", round(mape_train_split_3929788, 2), "| RMSE:", round(rmse_train_split_3929788, 2), "\n")
## Train Split - MAPE: 10.72 | RMSE: 34115.99
cat("Test  Split - MAPE:", round(mape_test_split_3929788, 2),  "| RMSE:", round(rmse_test_split_3929788, 2), "\n")
## Test  Split - MAPE: 12.26 | RMSE: 32974.97

9.2.5 ENTRENAMIENTO CON DATOS COMPLETOS

# Preparar matrices con todos los datos
X_entero_3929788 <- as.matrix(datos_3929788[, colnames(datos_3929788) != "Venta"])
y_entero_3929788 <- datos_3929788$Venta
dtrain_final_3929788 <- xgb.DMatrix(data = X_entero_3929788, label = y_entero_3929788)

# Entrenar el modelo final
modelo_xgb_final_3929788 <- xgb.train(
  params = mejores_params_3929788,
  data = dtrain_final_3929788,
  nrounds = mejor_nrounds_3929788,
  verbose = 0
)

# Evaluación en datos_3929788
pred_final_train_3929788 <- predict(modelo_xgb_final_3929788, X_entero_3929788)
mape_final_train_3929788 <- mean(abs((y_entero_3929788 - pred_final_train_3929788) / pmax(y_entero_3929788, 0.01))) * 100
rmse_final_train_3929788 <- sqrt(mean((y_entero_3929788 - pred_final_train_3929788)^2))

# Evaluación en test_3929788
X_test_real_3929788 <- as.matrix(test_3929788[, colnames(test_3929788) != "Venta"])
y_test_real_3929788 <- test_3929788$Venta
pred_final_test_3929788 <- predict(modelo_xgb_final_3929788, X_test_real_3929788)
mape_final_test_3929788 <- mean(abs((y_test_real_3929788 - pred_final_test_3929788) / pmax(y_test_real_3929788, 0.01))) * 100
rmse_final_test_3929788 <- sqrt(mean((y_test_real_3929788 - pred_final_test_3929788)^2))

cat("Modelo Final - Producto 3929788\n")
## Modelo Final - Producto 3929788
cat("Train - MAPE:", round(mape_final_train_3929788, 2), "| RMSE:", round(rmse_final_train_3929788, 2), "\n")
## Train - MAPE: 10.57 | RMSE: 33358.9
cat("Test  - MAPE:", round(mape_final_test_3929788, 2),  "| RMSE:", round(rmse_final_test_3929788, 2), "\n")
## Test  - MAPE: 25.99 | RMSE: 64972.72

9.2.6 TABLA DE MÉTRICAS

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3929788",
  Modelo = "XGBoost (Train)",
  MAPE = mape_final_train_3929788,
  RMSE = rmse_final_train_3929788
))

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3929788",
  Modelo = "XGBoost (Test)",
  MAPE = mape_final_test_3929788,
  RMSE = rmse_final_test_3929788
))

9.3 PRODUCTO 3904152

9.3.1 SPLIT INTERNO

# Dividir los datos en entrenamiento (80%) y validación interna (20%)
set.seed(123)  # Para reproducibilidad

indices_train_3904152 <- createDataPartition(datos_3904152$Venta, p = 0.8, list = FALSE)

# Crear subconjuntos
datos_train_3904152 <- datos_3904152[indices_train_3904152, ]
datos_test_3904152  <- datos_3904152[-indices_train_3904152, ]

# Matrices predictoras y objetivo
X_train_3904152 <- as.matrix(datos_train_3904152[, colnames(datos_train_3904152) != "Venta"])
y_train_3904152 <- datos_train_3904152$Venta

X_test_3904152 <- as.matrix(datos_test_3904152[, colnames(datos_test_3904152) != "Venta"])
y_test_3904152 <- datos_test_3904152$Venta

# Convertir a formato DMatrix para XGBoost
dtrain_3904152 <- xgb.DMatrix(data = X_train_3904152, label = y_train_3904152)
dtest_3904152  <- xgb.DMatrix(data = X_test_3904152, label = y_test_3904152)

9.3.2 BUSQUEDA DE HIPERPARAMETROS CON VALIDACIÓN CRUZADA

# Crear una rejilla de combinaciones de hiperparámetros
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1),             # Tasa de aprendizaje
  max_depth = c(3, 5, 7),               # Profundidad del árbol
  subsample = c(0.8, 1.0),              # Proporción de muestras por árbol
  colsample_bytree = c(0.8, 1.0),       # Proporción de columnas por árbol
  min_child_weight = c(1, 3),           # Peso mínimo de nodos hijos
  gamma = c(0, 0.1)                     # Regularización
)

# Reducir combinaciones para eficiencia
set.seed(123)
param_grid_reducida <- param_grid[sample(1:nrow(param_grid), 10), ]

# Función para evaluar cada combinación con validación cruzada
evaluate_params_3904152 <- function(params_row) {
  params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = params_row$eta,
    max_depth = params_row$max_depth,
    subsample = params_row$subsample,
    colsample_bytree = params_row$colsample_bytree,
    min_child_weight = params_row$min_child_weight,
    gamma = params_row$gamma
  )
  
  cv <- xgb.cv(
    params = params,
    data = dtrain_3904152,    # <- usa DMatrix con datos_train_155001
    nrounds = 100,
    nfold = 5,
    early_stopping_rounds = 10,
    verbose = 0
  )
  
  list(
    rmse = min(cv$evaluation_log$test_rmse_mean),
    nrounds = which.min(cv$evaluation_log$test_rmse_mean),
    params = params
  )
}
# Evaluar todas las combinaciones
resultados_grid_3904152 <- param_grid_reducida
resultados_grid_3904152$nrounds <- NA
resultados_grid_3904152$rmse <- NA

cat("Iniciando búsqueda de hiperparámetros para producto 3904152...\n")
## Iniciando búsqueda de hiperparámetros para producto 3904152...
for (i in 1:nrow(param_grid_reducida)) {
  cat(sprintf("Evaluando combinación %d de %d\n", i, nrow(param_grid_reducida)))
  
  resultado <- evaluate_params_3904152(param_grid_reducida[i, ])
  
  resultados_grid_3904152$nrounds[i] <- resultado$nrounds
  resultados_grid_3904152$rmse[i] <- resultado$rmse
}
## Evaluando combinación 1 de 10
## Evaluando combinación 2 de 10
## Evaluando combinación 3 de 10
## Evaluando combinación 4 de 10
## Evaluando combinación 5 de 10
## Evaluando combinación 6 de 10
## Evaluando combinación 7 de 10
## Evaluando combinación 8 de 10
## Evaluando combinación 9 de 10
## Evaluando combinación 10 de 10
# Ordenar por mejor RMSE
resultados_grid_3904152 <- resultados_grid_3904152[order(resultados_grid_3904152$rmse), ]

# Mostrar las 5 mejores combinaciones
cat("\nTop 5 combinaciones de hiperparámetros (Producto 3904152):\n")
## 
## Top 5 combinaciones de hiperparámetros (Producto 3904152):
print(head(resultados_grid_3904152, 5))
##      eta max_depth subsample colsample_bytree min_child_weight gamma nrounds
## 50  0.05         5       1.0              0.8                3   0.0      49
## 14  0.05         5       1.0              0.8                1   0.0      51
## 92  0.05         3       0.8              1.0                1   0.1      47
## 137 0.05         3       1.0              1.0                3   0.1      41
## 90  0.10         7       1.0              0.8                1   0.1      19
##          rmse
## 50   92982.82
## 14   95795.14
## 92   99107.42
## 137  99518.53
## 90  105856.90

9.3.3 ENTRENA MODELO CON MEJORES HIPERPARÁMETROS

# Extraer los mejores hiperparámetros del grid
mejores_params_3904152 <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados_grid_3904152$eta[1],
  max_depth = resultados_grid_3904152$max_depth[1],
  subsample = resultados_grid_3904152$subsample[1],
  colsample_bytree = resultados_grid_3904152$colsample_bytree[1],
  min_child_weight = resultados_grid_3904152$min_child_weight[1],
  gamma = resultados_grid_3904152$gamma[1]
)

# Número óptimo de rondas
mejor_nrounds_3904152 <- resultados_grid_3904152$nrounds[1]

cat("Entrenando modelo con mejores hiperparámetros...\n")
## Entrenando modelo con mejores hiperparámetros...
print(mejores_params_3904152)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.05
## 
## $max_depth
## [1] 5
## 
## $subsample
## [1] 1
## 
## $colsample_bytree
## [1] 0.8
## 
## $min_child_weight
## [1] 3
## 
## $gamma
## [1] 0
cat("Número óptimo de rondas:", mejor_nrounds_3904152, "\n")
## Número óptimo de rondas: 49
# Entrenar el modelo con los datos de split interno
modelo_xgb_3904152_split <- xgb.train(
  params = mejores_params_3904152,
  data = dtrain_3904152,
  nrounds = mejor_nrounds_3904152,
  watchlist = list(train = dtrain_3904152, test = dtest_3904152),
  verbose = 0
)

9.3.4 MÉTRICAS INTERNAS

# Métricas sobre datos_train (80%)
pred_train_split_3904152 <- predict(modelo_xgb_3904152_split, dtrain_3904152)
mape_train_split_3904152 <- mean(abs((y_train_3904152 - pred_train_split_3904152) / pmax(y_train_3904152, 0.01))) * 100
rmse_train_split_3904152 <- sqrt(mean((y_train_3904152 - pred_train_split_3904152)^2))

# Métricas sobre datos_test (20%)
pred_test_split_3904152 <- predict(modelo_xgb_3904152_split, dtest_3904152)
mape_test_split_3904152 <- mean(abs((y_test_3904152 - pred_test_split_3904152) / pmax(y_test_3904152, 0.01))) * 100
rmse_test_split_3904152 <- sqrt(mean((y_test_3904152 - pred_test_split_3904152)^2))

cat("Split Interno - Producto 3904152\n")
## Split Interno - Producto 3904152
cat("Train Split - MAPE:", round(mape_train_split_3904152, 2), "| RMSE:", round(rmse_train_split_3904152, 2), "\n")
## Train Split - MAPE: 14.45 | RMSE: 52366
cat("Test  Split - MAPE:", round(mape_test_split_3904152, 2),  "| RMSE:", round(rmse_test_split_3904152, 2), "\n")
## Test  Split - MAPE: 32.87 | RMSE: 137420.3

9.3.5 ENTRENAMIENTO CON DATOS COMPLETOS

# Preparar matrices con todos los datos
X_entero_3904152 <- as.matrix(datos_3904152[, colnames(datos_3904152) != "Venta"])
y_entero_3904152 <- datos_3904152$Venta
dtrain_final_3904152 <- xgb.DMatrix(data = X_entero_3904152, label = y_entero_3904152)

# Entrenar el modelo final
modelo_xgb_final_3904152 <- xgb.train(
  params = mejores_params_3904152,
  data = dtrain_final_3904152,
  nrounds = mejor_nrounds_3904152,
  verbose = 0
)

# Evaluación en datos_3904152
pred_final_train_3904152 <- predict(modelo_xgb_final_3904152, X_entero_3904152)
mape_final_train_3904152 <- mean(abs((y_entero_3904152 - pred_final_train_3904152) / pmax(y_entero_3904152, 0.01))) * 100
rmse_final_train_3904152 <- sqrt(mean((y_entero_3904152 - pred_final_train_3904152)^2))

# Evaluación en test_3904152
X_test_real_3904152 <- as.matrix(test_3904152[, colnames(test_3904152) != "Venta"])
y_test_real_3904152 <- test_3904152$Venta
pred_final_test_3904152 <- predict(modelo_xgb_final_3904152, X_test_real_3904152)
mape_final_test_3904152 <- mean(abs((y_test_real_3904152 - pred_final_test_3904152) / pmax(y_test_real_3904152, 0.01))) * 100
rmse_final_test_3904152 <- sqrt(mean((y_test_real_3904152 - pred_final_test_3904152)^2))

cat("Modelo Final - Producto 3904152\n")
## Modelo Final - Producto 3904152
cat("Train - MAPE:", round(mape_final_train_3904152, 2), "| RMSE:", round(rmse_final_train_3904152, 2), "\n")
## Train - MAPE: 15.63 | RMSE: 52020.93
cat("Test  - MAPE:", round(mape_final_test_3904152, 2),  "| RMSE:", round(rmse_final_test_3904152, 2), "\n")
## Test  - MAPE: 48.23 | RMSE: 79333.37

9.3.6 TABLA DE MÉTRICAS

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3904152",
  Modelo = "XGBoost (Train)",
  MAPE = mape_final_train_3904152,
  RMSE = rmse_final_train_3904152
))

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3904152",
  Modelo = "XGBoost (Test)",
  MAPE = mape_final_test_3904152,
  RMSE = rmse_final_test_3904152
))

9.4 PRODUCTO 155002

9.4.1 SPLIT INTERNO

# Dividir los datos en entrenamiento (80%) y validación interna (20%)
set.seed(123)  # Para reproducibilidad

indices_train_155002 <- createDataPartition(datos_155002$Venta, p = 0.8, list = FALSE)

# Crear subconjuntos
datos_train_155002 <- datos_155002[indices_train_155002, ]
datos_test_155002  <- datos_155002[-indices_train_155002, ]

# Matrices predictoras y objetivo
X_train_155002 <- as.matrix(datos_train_155002[, colnames(datos_train_155002) != "Venta"])
y_train_155002 <- datos_train_155002$Venta

X_test_155002 <- as.matrix(datos_test_155002[, colnames(datos_test_155002) != "Venta"])
y_test_155002 <- datos_test_155002$Venta

# Convertir a formato DMatrix para XGBoost
dtrain_155002 <- xgb.DMatrix(data = X_train_155002, label = y_train_155002)
dtest_155002  <- xgb.DMatrix(data = X_test_155002, label = y_test_155002)

9.4.2 BUSQUEDA DE HIPERPARAMETROS CON VALIDACIÓN CRUZADA

# Crear una rejilla de combinaciones de hiperparámetros
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1),             # Tasa de aprendizaje
  max_depth = c(3, 5, 7),               # Profundidad del árbol
  subsample = c(0.8, 1.0),              # Proporción de muestras por árbol
  colsample_bytree = c(0.8, 1.0),       # Proporción de columnas por árbol
  min_child_weight = c(1, 3),           # Peso mínimo de nodos hijos
  gamma = c(0, 0.1)                     # Regularización
)

# Reducir combinaciones para eficiencia
set.seed(123)
param_grid_reducida <- param_grid[sample(1:nrow(param_grid), 10), ]

# Función para evaluar cada combinación con validación cruzada
evaluate_params_155002 <- function(params_row) {
  params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = params_row$eta,
    max_depth = params_row$max_depth,
    subsample = params_row$subsample,
    colsample_bytree = params_row$colsample_bytree,
    min_child_weight = params_row$min_child_weight,
    gamma = params_row$gamma
  )
  
  cv <- xgb.cv(
    params = params,
    data = dtrain_155002,    # <- usa DMatrix con datos_train_155001
    nrounds = 100,
    nfold = 5,
    early_stopping_rounds = 10,
    verbose = 0
  )
  
  list(
    rmse = min(cv$evaluation_log$test_rmse_mean),
    nrounds = which.min(cv$evaluation_log$test_rmse_mean),
    params = params
  )
}
# Evaluar todas las combinaciones
resultados_grid_155002 <- param_grid_reducida
resultados_grid_155002$nrounds <- NA
resultados_grid_155002$rmse <- NA

cat("Iniciando búsqueda de hiperparámetros para producto 155002...\n")
## Iniciando búsqueda de hiperparámetros para producto 155002...
for (i in 1:nrow(param_grid_reducida)) {
  cat(sprintf("Evaluando combinación %d de %d\n", i, nrow(param_grid_reducida)))
  
  resultado <- evaluate_params_155002(param_grid_reducida[i, ])
  
  resultados_grid_155002$nrounds[i] <- resultado$nrounds
  resultados_grid_155002$rmse[i] <- resultado$rmse
}
## Evaluando combinación 1 de 10
## Evaluando combinación 2 de 10
## Evaluando combinación 3 de 10
## Evaluando combinación 4 de 10
## Evaluando combinación 5 de 10
## Evaluando combinación 6 de 10
## Evaluando combinación 7 de 10
## Evaluando combinación 8 de 10
## Evaluando combinación 9 de 10
## Evaluando combinación 10 de 10
# Ordenar por mejor RMSE
resultados_grid_155002 <- resultados_grid_155002[order(resultados_grid_155002$rmse), ]

# Mostrar las 5 mejores combinaciones
cat("\nTop 5 combinaciones de hiperparámetros (Producto 155002):\n")
## 
## Top 5 combinaciones de hiperparámetros (Producto 155002):
print(head(resultados_grid_155002, 5))
##      eta max_depth subsample colsample_bytree min_child_weight gamma nrounds
## 50  0.05         5       1.0              0.8                3   0.0      47
## 14  0.05         5       1.0              0.8                1   0.0      40
## 92  0.05         3       0.8              1.0                1   0.1      59
## 137 0.05         3       1.0              1.0                3   0.1      46
## 90  0.10         7       1.0              0.8                1   0.1      26
##         rmse
## 50  86869.37
## 14  89673.86
## 92  89853.22
## 137 92169.64
## 90  93260.29

9.4.3 ENTRENA MODELO CON MEJORES HIPERPARÁMETROS

# Extraer los mejores hiperparámetros del grid
mejores_params_155002 <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados_grid_155002$eta[1],
  max_depth = resultados_grid_155002$max_depth[1],
  subsample = resultados_grid_155002$subsample[1],
  colsample_bytree = resultados_grid_155002$colsample_bytree[1],
  min_child_weight = resultados_grid_155002$min_child_weight[1],
  gamma = resultados_grid_155002$gamma[1]
)

# Número óptimo de rondas
mejor_nrounds_155002 <- resultados_grid_155002$nrounds[1]

cat("Entrenando modelo con mejores hiperparámetros...\n")
## Entrenando modelo con mejores hiperparámetros...
print(mejores_params_155002)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.05
## 
## $max_depth
## [1] 5
## 
## $subsample
## [1] 1
## 
## $colsample_bytree
## [1] 0.8
## 
## $min_child_weight
## [1] 3
## 
## $gamma
## [1] 0
cat("Número óptimo de rondas:", mejor_nrounds_155002, "\n")
## Número óptimo de rondas: 47
# Entrenar el modelo con los datos de split interno
modelo_xgb_155002_split <- xgb.train(
  params = mejores_params_155002,
  data = dtrain_155002,
  nrounds = mejor_nrounds_155002,
  watchlist = list(train = dtrain_155002, test = dtest_155002),
  verbose = 0
)

9.4.4 MÉTRICAS INTERNAS

# Métricas sobre datos_train (80%)
pred_train_split_155002 <- predict(modelo_xgb_155002_split, dtrain_155002)
mape_train_split_155002 <- mean(abs((y_train_155002 - pred_train_split_155002) / pmax(y_train_155002, 0.01))) * 100
rmse_train_split_155002 <- sqrt(mean((y_train_155002 - pred_train_split_155002)^2))

# Métricas sobre datos_test (20%)
pred_test_split_155002 <- predict(modelo_xgb_155002_split, dtest_155002)
mape_test_split_155002 <- mean(abs((y_test_155002 - pred_test_split_155002) / pmax(y_test_155002, 0.01))) * 100
rmse_test_split_155002 <- sqrt(mean((y_test_155002 - pred_test_split_155002)^2))

cat("Split Interno - Producto 155002\n")
## Split Interno - Producto 155002
cat("Train Split - MAPE:", round(mape_train_split_155002, 2), "| RMSE:", round(rmse_train_split_155002, 2), "\n")
## Train Split - MAPE: 18 | RMSE: 44397.44
cat("Test  Split - MAPE:", round(mape_test_split_155002, 2),  "| RMSE:", round(rmse_test_split_155002, 2), "\n")
## Test  Split - MAPE: 34.89 | RMSE: 91093.81

9.4.5 ENTRENAMIENTO CON DATOS COMPLETOS

# Preparar matrices con todos los datos
X_entero_155002 <- as.matrix(datos_155002[, colnames(datos_155002) != "Venta"])
y_entero_155002 <- datos_155002$Venta
dtrain_final_155002 <- xgb.DMatrix(data = X_entero_155002, label = y_entero_155002)

# Entrenar el modelo final
modelo_xgb_final_155002 <- xgb.train(
  params = mejores_params_155002,
  data = dtrain_final_155002,
  nrounds = mejor_nrounds_155002,
  verbose = 0
)

# Evaluación en datos_155002
pred_final_train_155002 <- predict(modelo_xgb_final_155002, X_entero_155002)
mape_final_train_155002 <- mean(abs((y_entero_155002 - pred_final_train_155002) / pmax(y_entero_155002, 0.01))) * 100
rmse_final_train_155002 <- sqrt(mean((y_entero_155002 - pred_final_train_155002)^2))

# Evaluación en test_155002
X_test_real_155002 <- as.matrix(test_155002[, colnames(test_155002) != "Venta"])
y_test_real_155002 <- test_155002$Venta
pred_final_test_155002 <- predict(modelo_xgb_final_155002, X_test_real_155002)
mape_final_test_155002 <- mean(abs((y_test_real_155002 - pred_final_test_155002) / pmax(y_test_real_155002, 0.01))) * 100
rmse_final_test_155002 <- sqrt(mean((y_test_real_155002 - pred_final_test_155002)^2))

cat("Modelo Final - Producto 155002\n")
## Modelo Final - Producto 155002
cat("Train - MAPE:", round(mape_final_train_155002, 2), "| RMSE:", round(rmse_final_train_155002, 2), "\n")
## Train - MAPE: 17.08 | RMSE: 43397.75
cat("Test  - MAPE:", round(mape_final_test_155002, 2),  "| RMSE:", round(rmse_final_test_155002, 2), "\n")
## Test  - MAPE: 53.71 | RMSE: 122995.6

9.4.6 TABLA DE MÉTRICAS

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155002",
  Modelo = "XGBoost (Train)",
  MAPE = mape_final_train_155002,
  RMSE = rmse_final_train_155002
))

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "155002",
  Modelo = "XGBoost (Test)",
  MAPE = mape_final_test_155002,
  RMSE = rmse_final_test_155002
))

9.5 PRODUCTO 3678055

9.5.1 SPLIT INTERNO

# Dividir los datos en entrenamiento (80%) y validación interna (20%)
set.seed(123)  # Para reproducibilidad

indices_train_3678055 <- createDataPartition(datos_3678055$Venta, p = 0.8, list = FALSE)

# Crear subconjuntos
datos_train_3678055 <- datos_3678055[indices_train_3678055, ]
datos_test_3678055  <- datos_3678055[-indices_train_3678055, ]

# Matrices predictoras y objetivo
X_train_3678055 <- as.matrix(datos_train_3678055[, colnames(datos_train_3678055) != "Venta"])
y_train_3678055 <- datos_train_3678055$Venta

X_test_3678055 <- as.matrix(datos_test_3678055[, colnames(datos_test_3678055) != "Venta"])
y_test_3678055 <- datos_test_3678055$Venta

# Convertir a formato DMatrix para XGBoost
dtrain_3678055 <- xgb.DMatrix(data = X_train_3678055, label = y_train_3678055)
dtest_3678055  <- xgb.DMatrix(data = X_test_3678055, label = y_test_3678055)

9.5.2 BUSQUEDA DE HIPERPARAMETROS CON VALIDACIÓN CRUZADA

# Crear una rejilla de combinaciones de hiperparámetros
param_grid <- expand.grid(
  eta = c(0.01, 0.05, 0.1),             # Tasa de aprendizaje
  max_depth = c(3, 5, 7),               # Profundidad del árbol
  subsample = c(0.8, 1.0),              # Proporción de muestras por árbol
  colsample_bytree = c(0.8, 1.0),       # Proporción de columnas por árbol
  min_child_weight = c(1, 3),           # Peso mínimo de nodos hijos
  gamma = c(0, 0.1)                     # Regularización
)

# Reducir combinaciones para eficiencia
set.seed(123)
param_grid_reducida <- param_grid[sample(1:nrow(param_grid), 10), ]

# Función para evaluar cada combinación con validación cruzada
evaluate_params_3678055 <- function(params_row) {
  params <- list(
    objective = "reg:squarederror",
    eval_metric = "rmse",
    eta = params_row$eta,
    max_depth = params_row$max_depth,
    subsample = params_row$subsample,
    colsample_bytree = params_row$colsample_bytree,
    min_child_weight = params_row$min_child_weight,
    gamma = params_row$gamma
  )
  
  cv <- xgb.cv(
    params = params,
    data = dtrain_3678055,    # <- usa DMatrix con datos_train_155001
    nrounds = 100,
    nfold = 5,
    early_stopping_rounds = 10,
    verbose = 0
  )
  
  list(
    rmse = min(cv$evaluation_log$test_rmse_mean),
    nrounds = which.min(cv$evaluation_log$test_rmse_mean),
    params = params
  )
}
# Evaluar todas las combinaciones
resultados_grid_3678055 <- param_grid_reducida
resultados_grid_3678055$nrounds <- NA
resultados_grid_3678055$rmse <- NA

cat("Iniciando búsqueda de hiperparámetros para producto 3678055...\n")
## Iniciando búsqueda de hiperparámetros para producto 3678055...
for (i in 1:nrow(param_grid_reducida)) {
  cat(sprintf("Evaluando combinación %d de %d\n", i, nrow(param_grid_reducida)))
  
  resultado <- evaluate_params_3678055(param_grid_reducida[i, ])
  
  resultados_grid_3678055$nrounds[i] <- resultado$nrounds
  resultados_grid_3678055$rmse[i] <- resultado$rmse
}
## Evaluando combinación 1 de 10
## Evaluando combinación 2 de 10
## Evaluando combinación 3 de 10
## Evaluando combinación 4 de 10
## Evaluando combinación 5 de 10
## Evaluando combinación 6 de 10
## Evaluando combinación 7 de 10
## Evaluando combinación 8 de 10
## Evaluando combinación 9 de 10
## Evaluando combinación 10 de 10
# Ordenar por mejor RMSE
resultados_grid_3678055 <- resultados_grid_3678055[order(resultados_grid_3678055$rmse), ]

# Mostrar las 5 mejores combinaciones
cat("\nTop 5 combinaciones de hiperparámetros (Producto 3678055):\n")
## 
## Top 5 combinaciones de hiperparámetros (Producto 3678055):
print(head(resultados_grid_3678055, 5))
##      eta max_depth subsample colsample_bytree min_child_weight gamma nrounds
## 92  0.05         3       0.8              1.0                1   0.1      38
## 137 0.05         3       1.0              1.0                3   0.1      42
## 50  0.05         5       1.0              0.8                3   0.0      58
## 90  0.10         7       1.0              0.8                1   0.1      23
## 144 0.10         7       1.0              1.0                3   0.1      24
##         rmse
## 92  88804.88
## 137 89118.86
## 50  89777.18
## 90  90640.12
## 144 92839.62

9.5.3 ENTRENA MODELO CON MEJORES HIPERPARÁMETROS

# Extraer los mejores hiperparámetros del grid
mejores_params_3678055 <- list(
  objective = "reg:squarederror",
  eval_metric = "rmse",
  eta = resultados_grid_3678055$eta[1],
  max_depth = resultados_grid_3678055$max_depth[1],
  subsample = resultados_grid_3678055$subsample[1],
  colsample_bytree = resultados_grid_3678055$colsample_bytree[1],
  min_child_weight = resultados_grid_3678055$min_child_weight[1],
  gamma = resultados_grid_3678055$gamma[1]
)

# Número óptimo de rondas
mejor_nrounds_3678055 <- resultados_grid_3678055$nrounds[1]

cat("Entrenando modelo con mejores hiperparámetros...\n")
## Entrenando modelo con mejores hiperparámetros...
print(mejores_params_3678055)
## $objective
## [1] "reg:squarederror"
## 
## $eval_metric
## [1] "rmse"
## 
## $eta
## [1] 0.05
## 
## $max_depth
## [1] 3
## 
## $subsample
## [1] 0.8
## 
## $colsample_bytree
## [1] 1
## 
## $min_child_weight
## [1] 1
## 
## $gamma
## [1] 0.1
cat("Número óptimo de rondas:", mejor_nrounds_3678055, "\n")
## Número óptimo de rondas: 38
# Entrenar el modelo con los datos de split interno
modelo_xgb_3678055_split <- xgb.train(
  params = mejores_params_3678055,
  data = dtrain_3678055,
  nrounds = mejor_nrounds_3678055,
  watchlist = list(train = dtrain_3678055, test = dtest_3678055),
  verbose = 0
)

9.5.4 MÉTRICAS INTERNAS

# Métricas sobre datos_train (80%)
pred_train_split_3678055 <- predict(modelo_xgb_3678055_split, dtrain_3678055)
mape_train_split_3678055 <- mean(abs((y_train_3678055 - pred_train_split_3678055) / pmax(y_train_3678055, 0.01))) * 100
rmse_train_split_3678055 <- sqrt(mean((y_train_3678055 - pred_train_split_3678055)^2))

# Métricas sobre datos_test (20%)
pred_test_split_3678055 <- predict(modelo_xgb_3678055_split, dtest_3678055)
mape_test_split_3678055 <- mean(abs((y_test_3678055 - pred_test_split_3678055) / pmax(y_test_3678055, 0.01))) * 100
rmse_test_split_3678055 <- sqrt(mean((y_test_3678055 - pred_test_split_3678055)^2))

cat("Split Interno - Producto 3678055\n")
## Split Interno - Producto 3678055
cat("Train Split - MAPE:", round(mape_train_split_3678055, 2), "| RMSE:", round(rmse_train_split_3678055, 2), "\n")
## Train Split - MAPE: 28.62 | RMSE: 64305.89
cat("Test  Split - MAPE:", round(mape_test_split_3678055, 2),  "| RMSE:", round(rmse_test_split_3678055, 2), "\n")
## Test  Split - MAPE: 42 | RMSE: 81885.14

9.5.5 ENTRENAMIENTO CON DATOS COMPLETOS

# Preparar matrices con todos los datos
X_entero_3678055 <- as.matrix(datos_3678055[, colnames(datos_3678055) != "Venta"])
y_entero_3678055 <- datos_3678055$Venta
dtrain_final_3678055 <- xgb.DMatrix(data = X_entero_3678055, label = y_entero_3678055)

# Entrenar el modelo final
modelo_xgb_final_3678055 <- xgb.train(
  params = mejores_params_3678055,
  data = dtrain_final_3678055,
  nrounds = mejor_nrounds_3678055,
  verbose = 0
)

# Evaluación en datos_3678055
pred_final_train_3678055 <- predict(modelo_xgb_final_3678055, X_entero_3678055)
mape_final_train_3678055 <- mean(abs((y_entero_3678055 - pred_final_train_3678055) / pmax(y_entero_3678055, 0.01))) * 100
rmse_final_train_3678055 <- sqrt(mean((y_entero_3678055 - pred_final_train_3678055)^2))

# Evaluación en test_3678055
X_test_real_3678055 <- as.matrix(test_3678055[, colnames(test_3678055) != "Venta"])
y_test_real_3678055 <- test_3678055$Venta
pred_final_test_3678055 <- predict(modelo_xgb_final_3678055, X_test_real_3678055)
mape_final_test_3678055 <- mean(abs((y_test_real_3678055 - pred_final_test_3678055) / pmax(y_test_real_3678055, 0.01))) * 100
rmse_final_test_3678055 <- sqrt(mean((y_test_real_3678055 - pred_final_test_3678055)^2))

cat("Modelo Final - Producto 3678055\n")
## Modelo Final - Producto 3678055
cat("Train - MAPE:", round(mape_final_train_3678055, 2), "| RMSE:", round(rmse_final_train_3678055, 2), "\n")
## Train - MAPE: 30.76 | RMSE: 64641.97
cat("Test  - MAPE:", round(mape_final_test_3678055, 2),  "| RMSE:", round(rmse_final_test_3678055, 2), "\n")
## Test  - MAPE: 37.14 | RMSE: 118112.2

9.5.6 TABLA DE MÉTRICAS

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",
  Modelo = "XGBoost (Train)",
  MAPE = mape_final_train_3678055,
  RMSE = rmse_final_train_3678055
))

metricas_comparativas <- rbind(metricas_comparativas, data.frame(
  Producto = "3678055",
  Modelo = "XGBoost (Test)",
  MAPE = mape_final_test_3678055,
  RMSE = rmse_final_test_3678055
))

9.6 MÉTRICAS ARMA, REG LINEAL, RANDOM FOREST Y XGBOOST

metricas_comparativas %>%
  arrange(Producto) %>%
  knitr::kable(caption = "Métricas ARMA, Regresion Lineal, Random Forest y XGBoost por producto") %>%
  kableExtra::kable_styling(full_width = FALSE)
Métricas ARMA, Regresion Lineal, Random Forest y XGBoost por producto
Producto Modelo MAPE RMSE
155001 ARMA 37.96077 109593.78
155001 ARMA (Test) 25.14259 67277.26
155001 Regresión Lineal (Train) 39.93493 111408.28
155001 Regresión Lineal (Test) 89.83537 157598.32
155001 Random Forest (Train) 18.88805 55005.66
155001 Random Forest (Test) 77.18471 138820.57
155001 XGBoost (Train) 15.15127 59186.13
155001 XGBoost (Test) 47.40820 113795.24
155002 ARMA 51.33213 82260.20
155002 ARMA (Test) 35.86659 87005.41
155002 Regresión Lineal (Train) 53.30857 81659.41
155002 Regresión Lineal (Test) 113.63421 140462.00
155002 Random Forest (Train) 24.90641 40622.51
155002 Random Forest (Test) 101.53221 122531.14
155002 XGBoost (Train) 17.07560 43397.75
155002 XGBoost (Test) 53.71319 122995.60
3678055 ARMA 54.27943 82835.18
3678055 ARMA (Test) 53.77550 105823.13
3678055 Regresión Lineal (Train) 54.28565 82493.59
3678055 Regresión Lineal (Test) 35.22903 125404.28
3678055 Random Forest (Train) 26.16327 40659.52
3678055 Random Forest (Test) 46.57144 103169.62
3678055 XGBoost (Train) 30.76315 64641.97
3678055 XGBoost (Test) 37.13504 118112.21
3904152 ARMA 41.54527 99993.25
3904152 ARMA (Test) 18.70572 50062.24
3904152 Regresión Lineal (Train) 38.98555 104128.86
3904152 Regresión Lineal (Test) 196.40268 209230.00
3904152 Random Forest (Train) 19.28014 53268.65
3904152 Random Forest (Test) 87.56263 101889.81
3904152 XGBoost (Train) 15.63334 52020.93
3904152 XGBoost (Test) 48.22606 79333.37
3929788 ARMA 21.74312 57634.89
3929788 ARMA (Test) 26.25704 60053.08
3929788 Regresión Lineal (Train) 20.52872 53920.09
3929788 Regresión Lineal (Test) 34.22277 68189.37
3929788 Random Forest (Train) 10.27272 27004.10
3929788 Random Forest (Test) 26.95816 56210.34
3929788 XGBoost (Train) 10.57228 33358.90
3929788 XGBoost (Test) 25.98709 64972.72

10 VISUALIZACIÓN DE MÉTRICAS

# Colores personalizados por tipo de modelo base
colores_modelos <- c(
  "ARMA" = "#1f77b4",
  "ARMA (Test)" = "#aec7e8",
  "Regresión Lineal (Train)" = "#ff7f0e",
  "Regresión Lineal (Test)" = "#ffbb78",
  "Random Forest (Train)" = "#2ca02c",
  "Random Forest (Test)" = "#98df8a",
  "XGBoost (Train)" = "#d62728",
  "XGBoost (Test)" = "#ff9896"
)
# Obtener todos los productos únicos
productos <- unique(metricas_comparativas$Producto)

# Crear gráficos por producto
for (prod in productos) {
  datos_prod <- metricas_comparativas %>% filter(Producto == prod)
  
  # Gráfico de MAPE
  print(
    ggplot(datos_prod, aes(x = Modelo, y = MAPE, fill = Modelo)) +
      geom_bar(stat = "identity", width = 0.6) +
      geom_text(aes(label = round(MAPE, 1)), vjust = -0.5, size = 3.5) +
      scale_fill_manual(values = colores_modelos) +
      labs(
        title = paste("MAPE por modelo - Producto", prod),
        x = "", y = "MAPE (%)"
      ) +
      theme_minimal() +
      theme(
        legend.position = "none",
        axis.text.x = element_text(angle = 45, hjust = 1)
      ) +
      ylim(0, max(datos_prod$MAPE, na.rm = TRUE) * 1.1)
  )
  
  # Gráfico de RMSE
  print(
    ggplot(datos_prod, aes(x = Modelo, y = RMSE, fill = Modelo)) +
      geom_bar(stat = "identity", width = 0.6) +
      geom_text(aes(label = round(RMSE, 1)), vjust = -0.5, size = 3.5) +
      scale_fill_manual(values = colores_modelos) +
      labs(
        title = paste("RMSE por modelo - Producto", prod),
        x = "", y = "RMSE"
      ) +
      theme_minimal() +
      theme(
        legend.position = "none",
        axis.text.x = element_text(angle = 45, hjust = 1)
      ) +
      ylim(0, max(datos_prod$RMSE, na.rm = TRUE) * 1.1)
  )
}

11 ESTIMACIÓN DE PRECIOS

11.1 PREPARACIÓN DE DATOS

prepare_price_data <- function(df, product_id) {
  product_data <- df %>%
    filter(ID_Inventario == product_id) %>%
    arrange(Trx_Fecha) %>%
    select(
      Semana,
      Precio_Final_Unitario,
      Venta_Semana_Anterior,
      Cant_Semana_Anterior,
      Descuento_Semana_Anterior,
      Costo_Unitario_Semana_Anterior
    )

  return(product_data)
}

# Aplicar la función a todos los productos
ids <- unique(datos$ID_Inventario)

productos_preparados <- map_df(ids, function(id) {
  prepare_price_data(datos, id)
})

head(productos_preparados)
## # A tibble: 6 × 6
##   Semana Precio_Final_Unitario Venta_Semana_Anterior Cant_Semana_Anterior
##    <dbl>                 <dbl>                 <dbl>                <dbl>
## 1      1                  870.                   NA                    NA
## 2      2                  776.                78039.                   91
## 3      3                  693.               169112.                  217
## 4      4                  650.               155189.                  250
## 5      5                  641.               208317.                  320
## 6      6                  629.               205799.                  354
## # ℹ 2 more variables: Descuento_Semana_Anterior <dbl>,
## #   Costo_Unitario_Semana_Anterior <dbl>

11.2 PRECIOS SIMULADOS

simular_escenarios_precio <- function(df, product_id, n = 20, semanas_a_simular = 8) {
  # Filtrar datos del producto
  data_producto <- df %>% filter(ID_Inventario == product_id)

  # Calcular rango histórico de precios (percentil 5% a 95%) usando Precio_Final_Unitario
  precio_min <- quantile(data_producto$Precio_Final_Unitario, 0.05, na.rm = TRUE)
  precio_max <- quantile(data_producto$Precio_Final_Unitario, 0.95, na.rm = TRUE)

  # Crear vector de precios simulados
  precios_simulados <- seq(precio_min, precio_max, length.out = n)

  # Obtener última semana y fecha
  ultima_fila <- data_producto %>% arrange(desc(Semana)) %>% .[1, ]
  ultima_semana <- ultima_fila$Semana
  ultima_fecha <- ultima_fila$Trx_Fecha

  # Expandir combinaciones: semanas futuras × precios simulados
  simulacion <- expand.grid(
    Semana = ultima_semana + 1:semanas_a_simular,
    Precio_Final_Unitario = precios_simulados  
  ) %>%
    mutate(
      ID_Inventario = product_id,
      Venta_Semana_Anterior = ultima_fila$Venta_Semana_Anterior,
      Cant_Semana_Anterior = ultima_fila$Cant_Semana_Anterior,
      Descuento_Semana_Anterior = ultima_fila$Descuento_Semana_Anterior,
      Costo_Unitario_Semana_Anterior = ultima_fila$Costo_Unitario_Semana_Anterior,
      Trx_Fecha = ultima_fecha + 7 * (Semana - ultima_semana)
    )

  return(simulacion)
}

11.2.1 APLICAR SIMULACIONES A TODOS LOS PRODUCTOS

productos_ids <- unique(datos$ID_Inventario)  # o usa top_ids si ya los definiste

simulaciones_precio_lista <- setNames(
  lapply(productos_ids, function(id) simular_escenarios_precio(datos, id)),
  productos_ids
)

11.2.2 ESTIMAR PRECIO ÓPTIMO POR PRODUCTO

#DEFINE LA FUNCION DE SIMULAR PRECIOS POR PRODUCTO
simular_precios_por_producto <- function(product_id, datos_hist, datos_test, modelos_xgb, columnas_modelo, devolver_todas = FALSE) {
  modelo <- modelos_xgb[[product_id]]
  
  test_producto <- datos_test %>%
    filter(ID_Inventario == as.numeric(product_id)) %>%
    select(
      Semana, Trx_Fecha,
      Precio_Lista_Unitario, Precio_Final_Unitario,
      Venta_Semana_Anterior, Cant_Semana_Anterior,
      Descuento_Semana_Anterior, Costo_Unitario_Semana_Anterior,
      Venta
    ) %>%
    mutate(Producto = product_id) %>%
    drop_na()
  
  precios_hist <- datos_hist %>%
    filter(ID_Inventario == as.numeric(product_id)) %>%
    pull(Precio_Final_Unitario)
  
  precio_min <- quantile(precios_hist, 0.05, na.rm = TRUE)
  precio_max <- quantile(precios_hist, 0.95, na.rm = TRUE)
  precios_simulados <- seq(precio_min, precio_max, length.out = 20)
  
  simulaciones <- test_producto %>%
  rowwise() %>%
  do({
    fila <- .
    sim <- tibble(
      Precio_Final_Unitario = precios_simulados,
      Venta_Semana_Anterior = fila$Venta_Semana_Anterior,
      Cant_Semana_Anterior = fila$Cant_Semana_Anterior,
      Descuento_Semana_Anterior = fila$Descuento_Semana_Anterior,
      Costo_Unitario_Semana_Anterior = fila$Costo_Unitario_Semana_Anterior,
      Semana = fila$Semana
    )
    
    X_sim <- sim %>% select(all_of(columnas_modelo)) %>% as.matrix()
    sim$Venta_Predicha <- predict(modelo, newdata = X_sim)
    
    sim <- sim %>%
      mutate(
        Producto = fila$Producto,
        Trx_Fecha = fila$Trx_Fecha,
        Precio_Lista_Unitario = fila$Precio_Lista_Unitario,
        Precio_Real = fila$Precio_Final_Unitario,
        Venta_Real = fila$Venta,
        Margen_Esperado = Venta_Predicha - Costo_Unitario_Semana_Anterior * Cant_Semana_Anterior,
        Descuento_Sugerido = 1 - Precio_Final_Unitario / Precio_Lista_Unitario,
        Descuento_Real = 1 - Precio_Real / Precio_Lista_Unitario,
        Es_Precio_Optimo = Venta_Predicha == max(Venta_Predicha, na.rm = TRUE)
      )
    
    if (devolver_todas) {
      sim
    } else {
      sim %>% filter(Es_Precio_Optimo) %>% slice_head(n = 1)
    }
  }) %>%
  ungroup()
}
modelos_xgb_final <- list(
  "155001" = modelo_xgb_final_155001,
  "3929788" = modelo_xgb_final_3929788,
  "3904152" = modelo_xgb_final_3904152,
  "155002" = modelo_xgb_final_155002,
  "3678055" = modelo_xgb_final_3678055
)

columnas_modelo <- c(
  "Precio_Final_Unitario", 
  "Venta_Semana_Anterior", 
  "Cant_Semana_Anterior", 
  "Descuento_Semana_Anterior", 
  "Costo_Unitario_Semana_Anterior", 
  "Semana"
)

11.2.3 TABLA DE SIMULACIONES DE PRECIOS

### TABLA DE SIMULACIONES DE PRECIOS
tabla_completa_simulada <- bind_rows(
  lapply(names(modelos_xgb_final), simular_precios_por_producto,
         datos_hist = datos,
         datos_test = test,
         modelos_xgb = modelos_xgb_final,
         columnas_modelo = columnas_modelo,
         devolver_todas = TRUE)
) %>%
  select(
    Producto, Semana, Trx_Fecha,
    Precio_Lista_Unitario,
    Precio_Real,
    Precio_Simulado = Precio_Final_Unitario,
    Descuento_Real,
    Descuento_Sugerido,
    Venta_Real,
    Venta_Predicha,
    Margen_Esperado,
    Es_Precio_Optimo
  )

11.2.4 TABLA DE PRECIOS CON MAYOR VENTA ESPERADA

tabla_comparativa_optimos <- bind_rows(
  lapply(names(modelos_xgb_final), simular_precios_por_producto,
         datos_hist = datos,
         datos_test = test,
         modelos_xgb = modelos_xgb_final,
         columnas_modelo = columnas_modelo,
         devolver_todas = FALSE)
) %>%
  select(
    Producto, Semana, Trx_Fecha,
    Precio_Lista_Unitario,
    Precio_Real,
    Precio_Optimo = Precio_Final_Unitario,
    Descuento_Real,
    Descuento_Sugerido,
    Venta_Real,
    Venta_Predicha,
    Margen_Esperado
  )

11.2.5 TABLA DE PRECIOS CON MAYOR VENTA ESPERADA

### TABLA DE PRECIOS CON MAYOR VENTA ESPERADA
tabla_comparativa_optimos <- bind_rows(
  lapply(names(modelos_xgb_final), simular_precios_por_producto,
         datos_hist = datos,
         datos_test = test,
         modelos_xgb = modelos_xgb_final,
         columnas_modelo = columnas_modelo,
         devolver_todas = FALSE)
) %>%
  mutate(Cantidad_Estimada = Venta_Predicha / Precio_Final_Unitario) %>%  # Precio óptimo
  select(
    Producto, Semana, Trx_Fecha,
    Precio_Lista_Unitario,
    Precio_Real,
    Precio_Optimo = Precio_Final_Unitario,
    Descuento_Real,
    Descuento_Sugerido,
    Venta_Real,
    Venta_Predicha,
    Cantidad_Estimada,
    Margen_Esperado
  )
head(tabla_comparativa_optimos)
## # A tibble: 6 × 12
##   Producto Semana Trx_Fecha  Precio_Lista_Unitario Precio_Real Precio_Optimo
##   <chr>     <dbl> <date>                     <dbl>       <dbl>         <dbl>
## 1 155001       93 2024-10-07                 5600         505.          391.
## 2 155001       94 2024-10-14                 5600         505.          391.
## 3 155001       95 2024-10-21                 5600         503.          404.
## 4 155001       96 2024-10-28                 5600         498.          404.
## 5 155001       97 2024-11-04                 5624.        503.          391.
## 6 155001       98 2024-11-11                 5736.        511.          404.
## # ℹ 6 more variables: Descuento_Real <dbl>, Descuento_Sugerido <dbl>,
## #   Venta_Real <dbl>, Venta_Predicha <dbl>, Cantidad_Estimada <dbl>,
## #   Margen_Esperado <dbl>
head(tabla_completa_simulada)
## # A tibble: 6 × 12
##   Producto Semana Trx_Fecha  Precio_Lista_Unitario Precio_Real Precio_Simulado
##   <chr>     <dbl> <date>                     <dbl>       <dbl>           <dbl>
## 1 155001       93 2024-10-07                  5600        505.            391.
## 2 155001       93 2024-10-07                  5600        505.            404.
## 3 155001       93 2024-10-07                  5600        505.            417.
## 4 155001       93 2024-10-07                  5600        505.            431.
## 5 155001       93 2024-10-07                  5600        505.            444.
## 6 155001       93 2024-10-07                  5600        505.            457.
## # ℹ 6 more variables: Descuento_Real <dbl>, Descuento_Sugerido <dbl>,
## #   Venta_Real <dbl>, Venta_Predicha <dbl>, Margen_Esperado <dbl>,
## #   Es_Precio_Optimo <lgl>

11.2.6 DESCARGAR A CSV

#write.csv(tabla_comparativa_optimos, "tabla_precios_optimos.csv", row.names = FALSE)
#write.csv(tabla_completa_simulada, "tabla_simulaciones_completas.csv", row.names = FALSE)
LS0tCnRpdGxlOiAiPHNwYW4gc3R5bGU9J2NvbG9yOiBibHVlOyc+RVZJREVOQ0lBIE5PVkVNPC9zcGFuPiIKYXV0aG9yOiAiRXF1aXBvIDQiCmRhdGU6ICIyMDI0LTAzLTA0IgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogdHJ1ZSAKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQogICAgdGhlbWU6IGpvdXJuYWwKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQplZGl0b3JfczogCiAgY2h1bmtfb3V0cHV0X3R5cGU6IGNvbnNvbGUKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlCi0tLQoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsYWNrOyI+ICBMSUJSRVLDjUFTIDwvc3Bhbj4gCmBgYHtyfQojIExpYnJlcsOtYXMgbmVjZXNhcmlhcwpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShyZWFkeGwpCmxpYnJhcnkocHVycnIpCmxpYnJhcnkoa25pdHIpCiNpbnN0YWxsLnBhY2thZ2VzKCJrYWJsZUV4dHJhIikKbGlicmFyeShrYWJsZUV4dHJhKQpsaWJyYXJ5KGdncGxvdDIpCiNpbnN0YWxsLnBhY2thZ2VzKCJpZ3JhcGgiKQpsaWJyYXJ5KGlncmFwaCkKI2luc3RhbGwucGFja2FnZXMoImZvcmVjYXN0IikKI2luc3RhbGwucGFja2FnZXMoImx1YnJpZGF0ZSIpCmxpYnJhcnkoZm9yZWNhc3QpCmxpYnJhcnkobHVicmlkYXRlKQpsaWJyYXJ5KGNvcnJwbG90KQpsaWJyYXJ5KFJDb2xvckJyZXdlcikKI2luc3RhbGwucGFja2FnZXMoImdnY29ycnBsb3QiKQpsaWJyYXJ5KGdnY29ycnBsb3QpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkoY2FyKQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKI2luc3RhbGwucGFja2FnZXMoInhnYm9vc3QiKQpsaWJyYXJ5KHhnYm9vc3QpCiNpbnN0YWxsLnBhY2thZ2VzKCJwYXRjaHdvcmsiKQpsaWJyYXJ5KHBhdGNod29yaykKbGlicmFyeShkcGx5cikKI2luc3RhbGwucGFja2FnZXMoInNjYWxlcyIpCmxpYnJhcnkgKGdncGxvdDIpCmBgYAoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsYWNrOyI+ICBDQVJHQSBERSBEQVRPUyA8L3NwYW4+IApgYGB7cn0KIyBMZWVyIGRhdG9zCmRhdG9zIDwtIHJlYWRfZXhjZWwoIi9Vc2Vycy9tYXJpYWRlbGJvc3F1ZS9Eb3dubG9hZHMvTm92ZW1fZmluYWwueGxzeCIpICU+JQogIG11dGF0ZShUcnhfRmVjaGEgPSBhcy5EYXRlKFRyeF9GZWNoYSkpCgojIFBhcnRpY2nDs24gdGVtcG9yYWwKdHJhaW4gPC0gZGF0b3MgJT4lIGZpbHRlcihUcnhfRmVjaGEgPj0gYXMuRGF0ZSgiMjAyMy0wMS0wMSIpICYgVHJ4X0ZlY2hhIDwgYXMuRGF0ZSgiMjAyNC0xMC0wMSIpKQp0ZXN0ICA8LSBkYXRvcyAlPiUgZmlsdGVyKFRyeF9GZWNoYSA+PSBhcy5EYXRlKCIyMDI0LTEwLTAxIikgJiBUcnhfRmVjaGEgPD0gYXMuRGF0ZSgiMjAyNC0xMi0zMSIpKQoKIyBWYWxpZGFjacOzbiByw6FwaWRhCnN1bW1hcnkodHJhaW4kVHJ4X0ZlY2hhKQpzdW1tYXJ5KHRlc3QkVHJ4X0ZlY2hhKQpgYGAKCmBgYHtyfQojIFZlciB0b2RvcyBsb3MgcHJvZHVjdG9zIG9yZGVuYWRvcyBwb3IgdmVudGFzIHRvdGFsZXMKdG9wX2lkcyA8LSBkYXRvcyAlPiUKICBncm91cF9ieShJRF9JbnZlbnRhcmlvKSAlPiUKICBzdW1tYXJpc2UoVmVudGFzX1RvdGFsZXMgPSBzdW0oVmVudGEsIG5hLnJtID0gVFJVRSkpICU+JQogIGFycmFuZ2UoZGVzYyhWZW50YXNfVG90YWxlcykpCgpwcmludCgiUHJvZHVjdG9zIG9yZGVuYWRvcyBwb3IgdmVudGFzIHRvdGFsZXM6IikKcHJpbnQodG9wX2lkcykKYGBgCgpgYGB7cn0KZGF0b3NfZmlsdHJhZG9zIDwtIHRyYWluICAjIHVzYW1vcyB0b2RvIGVsIHRyYWluCiMgQ29udGFyIG9ic2VydmFjaW9uZXMgcG9yIHByb2R1Y3RvCmNvbnRlbyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgY291bnQoSURfSW52ZW50YXJpbywgc29ydCA9IFRSVUUpCnByaW50KCJOw7ptZXJvIGRlIHJlZ2lzdHJvcyBwb3IgcHJvZHVjdG8gZW4gZGF0b3NfZmlsdHJhZG9zOiIpCnByaW50KGNvbnRlbykKIyBWYWxpZGFjacOzbiBkZSBxdWUgaGF5YSBkYXRvcwppZiAobnJvdyhkYXRvc19maWx0cmFkb3MpID09IDApIHsKICBzdG9wKCJObyBoYXkgZGF0b3Mgc3VmaWNpZW50ZXMgZW4gZWwgY29uanVudG8gZGUgZW50cmVuYW1pZW50by4iKX0KYGBgCgojIDxzcGFuIHN0eWxlPSJjb2xvcjogYmxhY2s7Ij4gRVNUQUNJT05BTElEQUQgU0VNQU5BTDwvc3Bhbj4gCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBURVNUIERFIEVTVEFDSU9OQUxJREFECmBgYHtyfQojIEFzZWfDunJhdGUgZGUgdGVuZXIgQcOxbyB5IFNlbWFuYSBlbiB0dSBiYXNlCmRhdG9zJFRyeF9GZWNoYSA8LSBhcy5EYXRlKGRhdG9zJFRyeF9GZWNoYSkKCmRhdG9zIDwtIGRhdG9zICU+JQogIG11dGF0ZSgKICAgIEFuaW8gPSBpc295ZWFyKFRyeF9GZWNoYSksCiAgICBTZW1hbmEgPSBpc293ZWVrKFRyeF9GZWNoYSkKICApCgojIEFncmVnYW1vcyB2ZW50YXMgcG9yIHByb2R1Y3RvLCBzZW1hbmEgeSBhw7FvCnZlbnRhc19zZW1hbmFsZXMgPC0gZGF0b3MgJT4lCiAgZ3JvdXBfYnkoSURfSW52ZW50YXJpbywgU2VtYW5hLCBBbmlvKSAlPiUKICBzdW1tYXJpc2UoVmVudGFfU2VtYW5hbCA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSwgLmdyb3VwcyA9ICJkcm9wIikKCiMgQ2FsY3VsYW1vcyBDViBwb3IgcHJvZHVjdG8geSBzZW1hbmEKY3ZfcG9yX3NlbWFuYSA8LSB2ZW50YXNfc2VtYW5hbGVzICU+JQogIGdyb3VwX2J5KElEX0ludmVudGFyaW8sIFNlbWFuYSkgJT4lCiAgc3VtbWFyaXNlKAogICAgbWVkaWEgPSBtZWFuKFZlbnRhX1NlbWFuYWwsIG5hLnJtID0gVFJVRSksCiAgICBzZCA9IHNkKFZlbnRhX1NlbWFuYWwsIG5hLnJtID0gVFJVRSksCiAgICAuZ3JvdXBzID0gJ2Ryb3AnCiAgKSAlPiUKICBtdXRhdGUoQ1YgPSBzZCAvIG1lZGlhKQoKIyBQcm9tZWRpbyBkZWwgQ1Ygc2VtYW5hbCBwb3IgcHJvZHVjdG8gKyBjbGFzaWZpY2FjacOzbiBkZSBlc3RhY2lvbmFsaWRhZAplc3RhY2lvbmFsaWRhZF9zZW1hbmFsIDwtIGN2X3Bvcl9zZW1hbmEgJT4lCiAgZ3JvdXBfYnkoSURfSW52ZW50YXJpbykgJT4lCiAgc3VtbWFyaXNlKENWX1Byb21lZGlvX1NlbWFuYWwgPSBtZWFuKENWLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBtdXRhdGUoCiAgICBIYXlfRXN0YWNpb25hbGlkYWQgPSBpZmVsc2UoQ1ZfUHJvbWVkaW9fU2VtYW5hbCA+PSAwLjMsICJTw60iLCAiTm8iKQogICkgJT4lCiAgYXJyYW5nZShkZXNjKENWX1Byb21lZGlvX1NlbWFuYWwpKQoKcHJpbnQoZXN0YWNpb25hbGlkYWRfc2VtYW5hbCkKYGBgCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBWSVNVQUxJWkFDScOTTiBERSBFU1RBQ0lPTkFMSURBRApgYGB7cn0KIyBQYXNvIDE6IEFzZWd1cmFyIGZvcm1hdG8gZGUgZmVjaGEKZGF0b3MkVHJ4X0ZlY2hhIDwtIGFzLkRhdGUoZGF0b3MkVHJ4X0ZlY2hhKQoKIyBQYXNvIDI6IENyZWFyIGNvbHVtbmFzIGRlIEHDsW8geSBNZXMKZGF0b3MgPC0gZGF0b3MgJT4lCiAgbXV0YXRlKAogICAgQW5pbyA9IGlzb3llYXIoVHJ4X0ZlY2hhKSwKICAgIFNlbWFuYSA9IGlzb3dlZWsoVHJ4X0ZlY2hhKQogICkKCiMgUGFzbyAzOiBBZ3JlZ2FyIHZlbnRhcyBwb3IgcHJvZHVjdG8sIGHDsW8geSBtZXMKZGF0b3NfYWdyZWdhZG9zIDwtIGRhdG9zICU+JQogIGdyb3VwX2J5KElEX0ludmVudGFyaW8sIEFuaW8sIFNlbWFuYSkgJT4lCiAgc3VtbWFyaXNlKFZlbnRhX1NlbWFuYWwgPSBzdW0oVmVudGEsIG5hLnJtID0gVFJVRSksIC5ncm91cHMgPSAnZHJvcCcpCgojIFBhc28gNDogR2VuZXJhciBncsOhZmljYXMgcG9yIHByb2R1Y3RvIGNvbiBjb2xvcmVzIHBlcnNvbmFsaXphZG9zCmdyYWZpY2FzIDwtIGRhdG9zX2FncmVnYWRvcyAlPiUKICBncm91cF9zcGxpdChJRF9JbnZlbnRhcmlvKSAlPiUKICBsYXBwbHkoZnVuY3Rpb24oZGZfcHJvZCkgewogICAgZ2dwbG90KGRmX3Byb2QsIGFlcyh4ID0gU2VtYW5hLCB5ID0gVmVudGFfU2VtYW5hbCwgY29sb3IgPSBhcy5mYWN0b3IoQW5pbykpKSArCiAgICAgIGdlb21fbGluZShzaXplID0gMS4yKSArCiAgICAgIHNjYWxlX2NvbG9yX21hbnVhbCgKICAgICAgICB2YWx1ZXMgPSBjKAogICAgICAgICAgIjIwMjMiID0gIiM2ODgzOEIiLCAgIyBBenVsIGdyaXPDoWNlbwogICAgICAgICAgIjIwMjQiID0gIiNGRjY5QjQiICAgIyBSb3NhIGZ1ZXJ0ZQogICAgICAgICkKICAgICAgKSArCiAgICAgIGxhYnMoCiAgICAgICAgdGl0bGUgPSBwYXN0ZSgiRXN0YWNpb25hbGlkYWQgU2VtYW5hbCAtIFByb2R1Y3RvIiwgdW5pcXVlKGRmX3Byb2QkSURfSW52ZW50YXJpbykpLAogICAgICAgIHggPSAiU2VtYW5hIiwKICAgICAgICB5ID0gIlZlbnRhIFNlbWFuYWwiLAogICAgICAgIGNvbG9yID0gIkHDsW8iCiAgICAgICkgKwogICAgICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEzKSArCiAgICAgIHRoZW1lKAogICAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIKICAgICAgKQogIH0pCgojIFBhc28gNTogTW9zdHJhciB1bmEgZ3LDoWZpY2EgY29tbyBwcnVlYmEKcHJpbnQoZ3JhZmljYXNbWzFdXSkKcHJpbnQoZ3JhZmljYXNbWzJdXSkKcHJpbnQoZ3JhZmljYXNbWzNdXSkKcHJpbnQoZ3JhZmljYXNbWzRdXSkKcHJpbnQoZ3JhZmljYXNbWzVdXSkKYGBgCgojIDxzcGFuIHN0eWxlPSJjb2xvcjogYmxhY2s7Ij4gRVNUQUNJT05BTElEQUQgTUVOU1VBTDwvc3Bhbj4gCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBURVNUIERFIEVTVEFDSU9OQUxJREFECmBgYHtyfQojIEFzZWfDunJhdGUgZGUgdGVuZXIgQcOxbyB5IFNlbWFuYSBlbiB0dSBiYXNlCmRhdG9zJFRyeF9GZWNoYSA8LSBhcy5EYXRlKGRhdG9zJFRyeF9GZWNoYSkKCmRhdG9zIDwtIGRhdG9zICU+JQogIG11dGF0ZSgKICAgIEFuaW8gPSBpc295ZWFyKFRyeF9GZWNoYSksCiAgICBNZXMgPSBtb250aChUcnhfRmVjaGEpCiAgKQoKIyBBZ3JlZ2Ftb3MgdmVudGFzIHBvciBwcm9kdWN0bywgc2VtYW5hIHkgYcOxbwp2ZW50YXNfbWVuc3VhbCA8LSBkYXRvcyAlPiUKICBncm91cF9ieShJRF9JbnZlbnRhcmlvLCBNZXMsIEFuaW8pICU+JQogIHN1bW1hcmlzZShWZW50YV9NZW5zdWFsID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpLCAuZ3JvdXBzID0gImRyb3AiKQoKIyBDYWxjdWxhbW9zIENWIHBvciBwcm9kdWN0byB5IHNlbWFuYQpjdl9wb3JfbWVzIDwtIHZlbnRhc19tZW5zdWFsICU+JQogIGdyb3VwX2J5KElEX0ludmVudGFyaW8sIE1lcykgJT4lCiAgc3VtbWFyaXNlKAogICAgbWVkaWEgPSBtZWFuKFZlbnRhX01lbnN1YWwsIG5hLnJtID0gVFJVRSksCiAgICBzZCA9IHNkKFZlbnRhX01lbnN1YWwsIG5hLnJtID0gVFJVRSksCiAgICAuZ3JvdXBzID0gJ2Ryb3AnCiAgKSAlPiUKICBtdXRhdGUoQ1YgPSBzZCAvIG1lZGlhKQoKIyBQcm9tZWRpbyBkZWwgQ1Ygc2VtYW5hbCBwb3IgcHJvZHVjdG8gKyBjbGFzaWZpY2FjacOzbiBkZSBlc3RhY2lvbmFsaWRhZAplc3RhY2lvbmFsaWRhZF9tZW5zdWFsIDwtIGN2X3Bvcl9tZXMgJT4lCiAgZ3JvdXBfYnkoSURfSW52ZW50YXJpbykgJT4lCiAgc3VtbWFyaXNlKENWX1Byb21lZGlvX01lbnN1YWwgPSBtZWFuKENWLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBtdXRhdGUoCiAgICBIYXlfRXN0YWNpb25hbGlkYWQgPSBpZmVsc2UoQ1ZfUHJvbWVkaW9fTWVuc3VhbCA+PSAwLjMsICJTw60iLCAiTm8iKQogICkgJT4lCiAgYXJyYW5nZShkZXNjKENWX1Byb21lZGlvX01lbnN1YWwpKQoKcHJpbnQoZXN0YWNpb25hbGlkYWRfbWVuc3VhbCkKYGBgCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBWSVNVQUxJWkFDScOTTiBERSBFU1RBQ0lPTkFMSURBRApgYGB7cn0KIyBQYXNvIDE6IEFzZWd1cmFyIGZvcm1hdG8gZGUgZmVjaGEKZGF0b3MkVHJ4X0ZlY2hhIDwtIGFzLkRhdGUoZGF0b3MkVHJ4X0ZlY2hhKQoKIyBQYXNvIDI6IENyZWFyIGNvbHVtbmFzIGRlIEHDsW8geSBNZXMKZGF0b3MgPC0gZGF0b3MgJT4lCiAgbXV0YXRlKAogICAgQW5pbyA9IGlzb3llYXIoVHJ4X0ZlY2hhKSwKICAgIE1lcyA9IG1vbnRoKFRyeF9GZWNoYSkKICApCgojIFBhc28gMzogQWdyZWdhciB2ZW50YXMgcG9yIHByb2R1Y3RvLCBhw7FvIHkgbWVzCmRhdG9zX2FncmVnYWRvcyA8LSBkYXRvcyAlPiUKICBncm91cF9ieShJRF9JbnZlbnRhcmlvLCBBbmlvLCBNZXMpICU+JQogIHN1bW1hcmlzZShWZW50YV9NZW5zdWFsID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpLCAuZ3JvdXBzID0gJ2Ryb3AnKQoKIyBQYXNvIDQ6IEdlbmVyYXIgZ3LDoWZpY2FzIHBvciBwcm9kdWN0byBjb24gY29sb3JlcyBwZXJzb25hbGl6YWRvcwpncmFmaWNhcyA8LSBkYXRvc19hZ3JlZ2Fkb3MgJT4lCiAgZ3JvdXBfc3BsaXQoSURfSW52ZW50YXJpbykgJT4lCiAgbGFwcGx5KGZ1bmN0aW9uKGRmX3Byb2QpIHsKICAgIGdncGxvdChkZl9wcm9kLCBhZXMoeCA9IE1lcywgeSA9IFZlbnRhX01lbnN1YWwsIGNvbG9yID0gYXMuZmFjdG9yKEFuaW8pKSkgKwogICAgICBnZW9tX2xpbmUoc2l6ZSA9IDEuMikgKwogICAgICBzY2FsZV9jb2xvcl9tYW51YWwoCiAgICAgICAgdmFsdWVzID0gYygKICAgICAgICAgICIyMDIzIiA9ICIjNjg4MzhCIiwgICMgQXp1bCBncmlzw6FjZW8KICAgICAgICAgICIyMDI0IiA9ICIjRkY2OUI0IiAgICMgUm9zYSBmdWVydGUKICAgICAgICApCiAgICAgICkgKwogICAgICBsYWJzKAogICAgICAgIHRpdGxlID0gcGFzdGUoIkVzdGFjaW9uYWxpZGFkIE1lbnN1YWwgLSBQcm9kdWN0byIsIHVuaXF1ZShkZl9wcm9kJElEX0ludmVudGFyaW8pKSwKICAgICAgICB4ID0gIk1lcyIsCiAgICAgICAgeSA9ICJWZW50YSBNZW5zdWFsIiwKICAgICAgICBjb2xvciA9ICJBw7FvIgogICAgICApICsKICAgICAgdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxMykgKwogICAgICB0aGVtZSgKICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iCiAgICAgICkKICB9KQoKIyBQYXNvIDU6IE1vc3RyYXIgdW5hIGdyw6FmaWNhIGNvbW8gcHJ1ZWJhCnByaW50KGdyYWZpY2FzW1sxXV0pCnByaW50KGdyYWZpY2FzW1syXV0pCnByaW50KGdyYWZpY2FzW1szXV0pCnByaW50KGdyYWZpY2FzW1s0XV0pCnByaW50KGdyYWZpY2FzW1s1XV0pCmBgYAoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsYWNrOyI+IFBSRURJQ0NJT05FUyBERSBWRU5UQVMKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsYWNrOyI+IEFSTUEgCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBQUk9EVUNUTyAxNTUwMDEKIyMjIFRSQUlOCmBgYHtyIGFybWEtMTU1MDAxfQojIFByb2R1Y3RvIDE1NTAwMQppZF9wcm9kIDwtIDE1NTAwMQoKIyBBc2VndXJhcnNlIGRlIHF1ZSBtZXRyaWNhc19jb21wYXJhdGl2YXMgZXN0w6kgaW5pY2lhbGl6YWRhCmlmICghZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIFJNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCiMgRmlsdHJhciBkYXRvcyBkZWwgcHJvZHVjdG8gKHlhIGVzdMOhbiBhZ3JlZ2Fkb3MgcG9yIHNlbWFuYSkKdmVudGFzX3NlbWFuYWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgYXJyYW5nZShUcnhfRmVjaGEpICU+JQogIHNlbGVjdChUcnhfRmVjaGEsIFZlbnRhKQoKIyBDcmVhciBzZXJpZSB0ZW1wb3JhbCBzZW1hbmFsCnNlcmllX3RzIDwtIHRzKHZlbnRhc19zZW1hbmFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDUyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX3NlbWFuYWxlcyRUcnhfRmVjaGEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGlzb3dlZWsobWluKHZlbnRhc19zZW1hbmFsZXMkVHJ4X0ZlY2hhKSkpKQoKIyBBanVzdGFyIG1vZGVsbyBBUk1BCm1vZGVsb19hcm1hXzE1NTAwMSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpCmZvcmVjYXN0X21vZGVsb19hcm1hXzE1NTAwMSA8LSBmb3JlY2FzdChtb2RlbG9fYXJtYV8xNTUwMDEsIGggPSA0KQoKIyBHcsOhZmljbyBkZWwgcHJvbsOzc3RpY28KYXV0b3Bsb3QoZm9yZWNhc3RfbW9kZWxvX2FybWFfMTU1MDAxKSArCiAgbGFicyh0aXRsZSA9IHBhc3RlKCJQcm9uw7NzdGljbyBzZW1hbmFsIGRlIHZlbnRhcyAtIEFSTUEgKFByb2R1Y3RvIiwgaWRfcHJvZCwgIikiKSwKICAgICAgIHggPSAiU2VtYW5hIiwgeSA9ICJWZW50YXMgKCQpIikgKwogIHRoZW1lX21pbmltYWwoKQoKIyBDYWxjdWxhciBtw6l0cmljYXMgZGUgZGVzZW1wZcOxbwpmaXR0ZWRfYXJtYV8xNTUwMDEgPC0gZml0dGVkKG1vZGVsb19hcm1hXzE1NTAwMSkKbWFwZV9hcm1hXzE1NTAwMSA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfYXJtYV8xNTUwMDEpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMApybXNlX2FybWFfMTU1MDAxIDwtIHNxcnQobWVhbigoc2VyaWVfdHMgLSBmaXR0ZWRfYXJtYV8xNTUwMDEpXjIpKQoKIyBSZWdpc3RyYXIgZW4gdGFibGEgZ2VuZXJhbCBkZSBtw6l0cmljYXMKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9IGlkX3Byb2QsCiAgTW9kZWxvID0gIkFSTUEiLAogIE1BUEUgPSBtYXBlX2FybWFfMTU1MDAxLAogIFJNU0UgPSBybXNlX2FybWFfMTU1MDAxCikpCmBgYAoKIyMjIFRFU1QgCmBgYHtyfQojIENyZWFyIGNvbmp1bnRvIGRlIHBydWViYSBwYXJhIHByb2R1Y3RvIDE1NTAwMSAoc29sbyBjb2x1bW5hcyBuZWNlc2FyaWFzIHBhcmEgQVJNQSkKdGVzdF8xNTUwMDEgPC0gdGVzdCAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDEpICU+JQogIHNlbGVjdChTZW1hbmEsIFZlbnRhKSAlPiUKICBhcnJhbmdlKFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKYGBge3J9CiMgRXZhbHVhciBBUk1BIGNvbnRyYSBkYXRvcyByZWFsZXMgZGVsIHRlc3QgKHByaW1lcmFzIDQgc2VtYW5hcyBkZWwgdGVzdCkKdmVudGFzX3JlYWxlc190ZXN0XzE1NTAwMSA8LSB0ZXN0XzE1NTAwMSAlPiUKICBzbGljZV9oZWFkKG4gPSA0KSAlPiUKICBwdWxsKFZlbnRhKQoKdmVudGFzX3ByZWRpY2hhc19hcm1hXzE1NTAwMSA8LSBhcy5udW1lcmljKGZvcmVjYXN0X21vZGVsb19hcm1hXzE1NTAwMSRtZWFuKQoKIyBDb21wYXJhciBzb2xvIHNpIGhheSA0IHNlbWFuYXMgZGlzcG9uaWJsZXMKaWYgKGxlbmd0aCh2ZW50YXNfcmVhbGVzX3Rlc3RfMTU1MDAxKSA9PSBsZW5ndGgodmVudGFzX3ByZWRpY2hhc19hcm1hXzE1NTAwMSkpIHsKICAKICBtYXBlX2FybWFfdGVzdF8xNTUwMDEgPC0gbWVhbihhYnMoKHZlbnRhc19yZWFsZXNfdGVzdF8xNTUwMDEgLSB2ZW50YXNfcHJlZGljaGFzX2FybWFfMTU1MDAxKSAvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG1heCh2ZW50YXNfcmVhbGVzX3Rlc3RfMTU1MDAxLCAwLjAxKSkpICogMTAwCiAgCiAgcm1zZV9hcm1hX3Rlc3RfMTU1MDAxIDwtIHNxcnQobWVhbigodmVudGFzX3JlYWxlc190ZXN0XzE1NTAwMSAtIHZlbnRhc19wcmVkaWNoYXNfYXJtYV8xNTUwMDEpXjIpKQogIAogIGNhdCgiTUFQRSBBUk1BICh0ZXN0IC0gcHJvZHVjdG8gMTU1MDAxKToiLCByb3VuZChtYXBlX2FybWFfdGVzdF8xNTUwMDEsIDIpLCAiXG4iKQogIGNhdCgiUk1TRSBBUk1BICh0ZXN0IC0gcHJvZHVjdG8gMTU1MDAxKToiLCByb3VuZChybXNlX2FybWFfdGVzdF8xNTUwMDEsIDIpLCAiXG4iKQogIAogICMgR3VhcmRhciBlbiB0YWJsYSBkZSBtw6l0cmljYXMKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBpZF9wcm9kLAogICAgTW9kZWxvID0gIkFSTUEgKFRlc3QpIiwKICAgIE1BUEUgPSBtYXBlX2FybWFfdGVzdF8xNTUwMDEsCiAgICBSTVNFID0gcm1zZV9hcm1hX3Rlc3RfMTU1MDAxCiAgKSkKICAKfSBlbHNlIHsKICB3YXJuaW5nKCJObyBoYXkgc3VmaWNpZW50ZXMgc2VtYW5hcyBlbiBlbCB0ZXN0IHBhcmEgY29tcGFyYXIgY29uIGVsIGZvcmVjYXN0IGRlIEFSTUEuIikKfQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBQUk9EVUNUTyAzOTI5Nzg4CiMjIyBUUkFJTgpgYGB7ciBhcm1hLTM5Mjk3ODh9CiMgUHJvZHVjdG8gMzkyOTc4OAppZF9wcm9kIDwtIDM5Mjk3ODgKCiMgRmlsdHJhciBkYXRvcyBkZWwgcHJvZHVjdG8KdmVudGFzX3NlbWFuYWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgYXJyYW5nZShUcnhfRmVjaGEpICU+JQogIHNlbGVjdChUcnhfRmVjaGEsIFZlbnRhKQoKIyBDcmVhciBzZXJpZSB0ZW1wb3JhbCBzZW1hbmFsCnNlcmllX3RzIDwtIHRzKHZlbnRhc19zZW1hbmFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDUyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX3NlbWFuYWxlcyRUcnhfRmVjaGEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGlzb3dlZWsobWluKHZlbnRhc19zZW1hbmFsZXMkVHJ4X0ZlY2hhKSkpKQoKIyBBanVzdGFyIG1vZGVsbyBBUk1BCm1vZGVsb19hcm1hXzM5Mjk3ODggPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQpmb3JlY2FzdF9tb2RlbG9fYXJtYV8zOTI5Nzg4IDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hXzM5Mjk3ODgsIGggPSA0KQoKIyBHcsOhZmljbyBkZWwgcHJvbsOzc3RpY28KYXV0b3Bsb3QoZm9yZWNhc3RfbW9kZWxvX2FybWFfMzkyOTc4OCkgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gc2VtYW5hbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksCiAgICAgICB4ID0gIlNlbWFuYSIsIHkgPSAiVmVudGFzICgkKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCmZpdHRlZF9hcm1hXzM5Mjk3ODggPC0gZml0dGVkKG1vZGVsb19hcm1hXzM5Mjk3ODgpCm1hcGVfYXJtYV8zOTI5Nzg4IDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF9hcm1hXzM5Mjk3ODgpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMApybXNlX2FybWFfMzkyOTc4OCA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX2FybWFfMzkyOTc4OCleMikpCgojIFJlZ2lzdHJhciBtw6l0cmljYXMgZW4gbGEgdGFibGEgZ2VuZXJhbAptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGVfYXJtYV8zOTI5Nzg4LAogIFJNU0UgPSBybXNlX2FybWFfMzkyOTc4OAopKQpgYGAKCiMjIyBURVNUIApgYGB7cn0KIyBDcmVhciBjb25qdW50byBkZSBwcnVlYmEgcGFyYSBwcm9kdWN0byAxNTUwMDEgKHNvbG8gY29sdW1uYXMgbmVjZXNhcmlhcyBwYXJhIEFSTUEpCnRlc3RfMzkyOTc4OCA8LSB0ZXN0ICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM5Mjk3ODgpICU+JQogIHNlbGVjdChTZW1hbmEsIFZlbnRhKSAlPiUKICBhcnJhbmdlKFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKYGBge3J9CiMgRXZhbHVhciBBUk1BIGNvbnRyYSBkYXRvcyByZWFsZXMgZGVsIHRlc3QgKHByaW1lcmFzIDQgc2VtYW5hcyBkZWwgdGVzdCkKdmVudGFzX3JlYWxlc190ZXN0XzM5Mjk3ODggPC0gdGVzdF8zOTI5Nzg4ICU+JQogIHNsaWNlX2hlYWQobiA9IDQpICU+JQogIHB1bGwoVmVudGEpCgp2ZW50YXNfcHJlZGljaGFzX2FybWFfMzkyOTc4OCA8LSBhcy5udW1lcmljKGZvcmVjYXN0X21vZGVsb19hcm1hXzM5Mjk3ODgkbWVhbikKCiMgQ29tcGFyYXIgc29sbyBzaSBoYXkgNCBzZW1hbmFzIGRpc3BvbmlibGVzCmlmIChsZW5ndGgodmVudGFzX3JlYWxlc190ZXN0XzM5Mjk3ODgpID09IGxlbmd0aCh2ZW50YXNfcHJlZGljaGFzX2FybWFfMzkyOTc4OCkpIHsKICAKICBtYXBlX2FybWFfdGVzdF8zOTI5Nzg4IDwtIG1lYW4oYWJzKCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzkyOTc4OCAtIHZlbnRhc19wcmVkaWNoYXNfYXJtYV8zOTI5Nzg4KSAvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG1heCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzkyOTc4OCwgMC4wMSkpKSAqIDEwMAogIAogIHJtc2VfYXJtYV90ZXN0XzM5Mjk3ODggPC0gc3FydChtZWFuKCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzkyOTc4OCAtIHZlbnRhc19wcmVkaWNoYXNfYXJtYV8zOTI5Nzg4KV4yKSkKICAKICBjYXQoIk1BUEUgQVJNQSAodGVzdCAtIHByb2R1Y3RvIDM5Mjk3ODgpOiIsIHJvdW5kKG1hcGVfYXJtYV90ZXN0XzM5Mjk3ODgsIDIpLCAiXG4iKQogIGNhdCgiUk1TRSBBUk1BICh0ZXN0IC0gcHJvZHVjdG8gMzkyOTc4OCk6Iiwgcm91bmQocm1zZV9hcm1hX3Rlc3RfMzkyOTc4OCwgMiksICJcbiIpCiAgCiAgIyBHdWFyZGFyIGVuIHRhYmxhIGRlIG3DqXRyaWNhcwogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGlkX3Byb2QsCiAgICBNb2RlbG8gPSAiQVJNQSAoVGVzdCkiLAogICAgTUFQRSA9IG1hcGVfYXJtYV90ZXN0XzM5Mjk3ODgsCiAgICBSTVNFID0gcm1zZV9hcm1hX3Rlc3RfMzkyOTc4OAogICkpCiAgCn0gZWxzZSB7CiAgd2FybmluZygiTm8gaGF5IHN1ZmljaWVudGVzIHNlbWFuYXMgZW4gZWwgdGVzdCBwYXJhIGNvbXBhcmFyIGNvbiBlbCBmb3JlY2FzdCBkZSBBUk1BLiIpCn0KYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHRlYWw7Ij4gUFJPRFVDVE8gMzkwNDE1MgojIyMgVFJBSU4KYGBge3IgYXJtYS0zOTA0MTUyfQojIFByb2R1Y3RvIDM5MDQxNTIKaWRfcHJvZCA8LSAzOTA0MTUyCgojIEZpbHRyYXIgZGF0b3MgZGVsIHByb2R1Y3RvCnZlbnRhc19zZW1hbmFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IGlkX3Byb2QpICU+JQogIGFycmFuZ2UoVHJ4X0ZlY2hhKSAlPiUKICBzZWxlY3QoVHJ4X0ZlY2hhLCBWZW50YSkKCiMgQ3JlYXIgc2VyaWUgdGVtcG9yYWwgc2VtYW5hbApzZXJpZV90cyA8LSB0cyh2ZW50YXNfc2VtYW5hbGVzJFZlbnRhLCBmcmVxdWVuY3kgPSA1MiwKICAgICAgICAgICAgICAgc3RhcnQgPSBjKHllYXIobWluKHZlbnRhc19zZW1hbmFsZXMkVHJ4X0ZlY2hhKSksCiAgICAgICAgICAgICAgICAgICAgICAgICBpc293ZWVrKG1pbih2ZW50YXNfc2VtYW5hbGVzJFRyeF9GZWNoYSkpKSkKCiMgQWp1c3RhciBtb2RlbG8gQVJNQQptb2RlbG9fYXJtYV8zOTA0MTUyIDwtIGF1dG8uYXJpbWEoc2VyaWVfdHMsIHNlYXNvbmFsID0gRkFMU0UsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkKZm9yZWNhc3RfbW9kZWxvX2FybWFfMzkwNDE1MiA8LSBmb3JlY2FzdChtb2RlbG9fYXJtYV8zOTA0MTUyLCBoID0gNCkKCiMgR3LDoWZpY28gZGVsIHByb27Ds3N0aWNvCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsb19hcm1hXzM5MDQxNTIpICsKICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIHNlbWFuYWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLAogICAgICAgeCA9ICJTZW1hbmEiLCB5ID0gIlZlbnRhcyAoJCkiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIENhbGN1bGFyIG3DqXRyaWNhcwpmaXR0ZWRfYXJtYV8zOTA0MTUyIDwtIGZpdHRlZChtb2RlbG9fYXJtYV8zOTA0MTUyKQptYXBlX2FybWFfMzkwNDE1MiA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfYXJtYV8zOTA0MTUyKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDAKcm1zZV9hcm1hXzM5MDQxNTIgPC0gc3FydChtZWFuKChzZXJpZV90cyAtIGZpdHRlZF9hcm1hXzM5MDQxNTIpXjIpKQoKIyBSZWdpc3RyYXIgbcOpdHJpY2FzIGVuIGxhIHRhYmxhIGdlbmVyYWwKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9IGlkX3Byb2QsCiAgTW9kZWxvID0gIkFSTUEiLAogIE1BUEUgPSBtYXBlX2FybWFfMzkwNDE1MiwKICBSTVNFID0gcm1zZV9hcm1hXzM5MDQxNTIKKSkKYGBgCgojIyMgVEVTVCAKYGBge3J9CiMgQ3JlYXIgY29uanVudG8gZGUgcHJ1ZWJhIHBhcmEgcHJvZHVjdG8gMTU1MDAxIChzb2xvIGNvbHVtbmFzIG5lY2VzYXJpYXMgcGFyYSBBUk1BKQp0ZXN0XzM5MDQxNTIgPC0gdGVzdCAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAzOTA0MTUyKSAlPiUKICBzZWxlY3QoU2VtYW5hLCBWZW50YSkgJT4lCiAgYXJyYW5nZShTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCmBgYHtyfQojIEV2YWx1YXIgQVJNQSBjb250cmEgZGF0b3MgcmVhbGVzIGRlbCB0ZXN0IChwcmltZXJhcyA0IHNlbWFuYXMgZGVsIHRlc3QpCnZlbnRhc19yZWFsZXNfdGVzdF8zOTA0MTUyIDwtIHRlc3RfMzkwNDE1MiAlPiUKICBzbGljZV9oZWFkKG4gPSA0KSAlPiUKICBwdWxsKFZlbnRhKQoKdmVudGFzX3ByZWRpY2hhc19hcm1hXzM5MDQxNTIgPC0gYXMubnVtZXJpYyhmb3JlY2FzdF9tb2RlbG9fYXJtYV8zOTA0MTUyJG1lYW4pCgojIENvbXBhcmFyIHNvbG8gc2kgaGF5IDQgc2VtYW5hcyBkaXNwb25pYmxlcwppZiAobGVuZ3RoKHZlbnRhc19yZWFsZXNfdGVzdF8zOTA0MTUyKSA9PSBsZW5ndGgodmVudGFzX3ByZWRpY2hhc19hcm1hXzM5MDQxNTIpKSB7CiAgCiAgbWFwZV9hcm1hX3Rlc3RfMzkwNDE1MiA8LSBtZWFuKGFicygodmVudGFzX3JlYWxlc190ZXN0XzM5MDQxNTIgLSB2ZW50YXNfcHJlZGljaGFzX2FybWFfMzkwNDE1MikgLwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBtYXgodmVudGFzX3JlYWxlc190ZXN0XzM5MDQxNTIsIDAuMDEpKSkgKiAxMDAKICAKICBybXNlX2FybWFfdGVzdF8zOTA0MTUyIDwtIHNxcnQobWVhbigodmVudGFzX3JlYWxlc190ZXN0XzM5MDQxNTIgLSB2ZW50YXNfcHJlZGljaGFzX2FybWFfMzkwNDE1MileMikpCiAgCiAgY2F0KCJNQVBFIEFSTUEgKHRlc3QgLSBwcm9kdWN0byAzOTA0MTUyKToiLCByb3VuZChtYXBlX2FybWFfdGVzdF8zOTA0MTUyLCAyKSwgIlxuIikKICBjYXQoIlJNU0UgQVJNQSAodGVzdCAtIHByb2R1Y3RvIDM5MDQxNTIpOiIsIHJvdW5kKHJtc2VfYXJtYV90ZXN0XzM5MDQxNTIsIDIpLCAiXG4iKQogIAogICMgR3VhcmRhciBlbiB0YWJsYSBkZSBtw6l0cmljYXMKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBpZF9wcm9kLAogICAgTW9kZWxvID0gIkFSTUEgKFRlc3QpIiwKICAgIE1BUEUgPSBtYXBlX2FybWFfdGVzdF8zOTA0MTUyLAogICAgUk1TRSA9IHJtc2VfYXJtYV90ZXN0XzM5MDQxNTIKICApKQogIAp9IGVsc2UgewogIHdhcm5pbmcoIk5vIGhheSBzdWZpY2llbnRlcyBzZW1hbmFzIGVuIGVsIHRlc3QgcGFyYSBjb21wYXJhciBjb24gZWwgZm9yZWNhc3QgZGUgQVJNQS4iKQp9CmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiB0ZWFsOyI+IFBST0RVQ1RPIDE1NTAwMgojIyMgVFJBSU4gCmBgYHtyIGFybWEtMTU1MDAyfQojIFByb2R1Y3RvIDE1NTAwMgppZF9wcm9kIDwtIDE1NTAwMgoKIyBGaWx0cmFyIGRhdG9zIGRlbCBwcm9kdWN0bwp2ZW50YXNfc2VtYW5hbGVzIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBpZF9wcm9kKSAlPiUKICBhcnJhbmdlKFRyeF9GZWNoYSkgJT4lCiAgc2VsZWN0KFRyeF9GZWNoYSwgVmVudGEpCgojIENyZWFyIHNlcmllIHRlbXBvcmFsIHNlbWFuYWwKc2VyaWVfdHMgPC0gdHModmVudGFzX3NlbWFuYWxlcyRWZW50YSwgZnJlcXVlbmN5ID0gNTIsCiAgICAgICAgICAgICAgIHN0YXJ0ID0gYyh5ZWFyKG1pbih2ZW50YXNfc2VtYW5hbGVzJFRyeF9GZWNoYSkpLAogICAgICAgICAgICAgICAgICAgICAgICAgaXNvd2VlayhtaW4odmVudGFzX3NlbWFuYWxlcyRUcnhfRmVjaGEpKSkpCgojIEFqdXN0YXIgbW9kZWxvIEFSTUEKbW9kZWxvX2FybWFfMTU1MDAyIDwtIGF1dG8uYXJpbWEoc2VyaWVfdHMsIHNlYXNvbmFsID0gRkFMU0UsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkKZm9yZWNhc3RfbW9kZWxvX2FybWFfMTU1MDAyIDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hXzE1NTAwMiwgaCA9IDQpCgojIEdyw6FmaWNvIGRlbCBwcm9uw7NzdGljbwphdXRvcGxvdChmb3JlY2FzdF9tb2RlbG9fYXJtYV8xNTUwMDIpICsKICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIHNlbWFuYWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLAogICAgICAgeCA9ICJTZW1hbmEiLCB5ID0gIlZlbnRhcyAoJCkiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIENhbGN1bGFyIG3DqXRyaWNhcwpmaXR0ZWRfYXJtYV8xNTUwMDIgPC0gZml0dGVkKG1vZGVsb19hcm1hXzE1NTAwMikKbWFwZV9hcm1hXzE1NTAwMiA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfYXJtYV8xNTUwMDIpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMApybXNlX2FybWFfMTU1MDAyIDwtIHNxcnQobWVhbigoc2VyaWVfdHMgLSBmaXR0ZWRfYXJtYV8xNTUwMDIpXjIpKQoKIyBSZWdpc3RyYXIgbcOpdHJpY2FzIGVuIGxhIHRhYmxhIGdlbmVyYWwKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9IGlkX3Byb2QsCiAgTW9kZWxvID0gIkFSTUEiLAogIE1BUEUgPSBtYXBlX2FybWFfMTU1MDAyLAogIFJNU0UgPSBybXNlX2FybWFfMTU1MDAyCikpCmBgYAoKIyMjIFRFU1QgCmBgYHtyfQojIENyZWFyIGNvbmp1bnRvIGRlIHBydWViYSBwYXJhIHByb2R1Y3RvIDE1NTAwMSAoc29sbyBjb2x1bW5hcyBuZWNlc2FyaWFzIHBhcmEgQVJNQSkKdGVzdF8xNTUwMDIgPC0gdGVzdCAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDIpICU+JQogIHNlbGVjdChTZW1hbmEsIFZlbnRhKSAlPiUKICBhcnJhbmdlKFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKYGBge3J9CiMgRXZhbHVhciBBUk1BIGNvbnRyYSBkYXRvcyByZWFsZXMgZGVsIHRlc3QgKHByaW1lcmFzIDQgc2VtYW5hcyBkZWwgdGVzdCkKdmVudGFzX3JlYWxlc190ZXN0XzE1NTAwMiA8LSB0ZXN0XzE1NTAwMiAlPiUKICBzbGljZV9oZWFkKG4gPSA0KSAlPiUKICBwdWxsKFZlbnRhKQoKdmVudGFzX3ByZWRpY2hhc19hcm1hXzE1NTAwMiA8LSBhcy5udW1lcmljKGZvcmVjYXN0X21vZGVsb19hcm1hXzE1NTAwMiRtZWFuKQoKIyBDb21wYXJhciBzb2xvIHNpIGhheSA0IHNlbWFuYXMgZGlzcG9uaWJsZXMKaWYgKGxlbmd0aCh2ZW50YXNfcmVhbGVzX3Rlc3RfMTU1MDAyKSA9PSBsZW5ndGgodmVudGFzX3ByZWRpY2hhc19hcm1hXzE1NTAwMikpIHsKICAKICBtYXBlX2FybWFfdGVzdF8xNTUwMDIgPC0gbWVhbihhYnMoKHZlbnRhc19yZWFsZXNfdGVzdF8xNTUwMDIgLSB2ZW50YXNfcHJlZGljaGFzX2FybWFfMTU1MDAyKSAvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG1heCh2ZW50YXNfcmVhbGVzX3Rlc3RfMTU1MDAyLCAwLjAxKSkpICogMTAwCiAgCiAgcm1zZV9hcm1hX3Rlc3RfMTU1MDAyIDwtIHNxcnQobWVhbigodmVudGFzX3JlYWxlc190ZXN0XzE1NTAwMiAtIHZlbnRhc19wcmVkaWNoYXNfYXJtYV8xNTUwMDIpXjIpKQogIAogIGNhdCgiTUFQRSBBUk1BICh0ZXN0IC0gcHJvZHVjdG8gMTU1MDAyKToiLCByb3VuZChtYXBlX2FybWFfdGVzdF8xNTUwMDIsIDIpLCAiXG4iKQogIGNhdCgiUk1TRSBBUk1BICh0ZXN0IC0gcHJvZHVjdG8gMTU1MDAyKToiLCByb3VuZChybXNlX2FybWFfdGVzdF8xNTUwMDIsIDIpLCAiXG4iKQogIAogICMgR3VhcmRhciBlbiB0YWJsYSBkZSBtw6l0cmljYXMKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBpZF9wcm9kLAogICAgTW9kZWxvID0gIkFSTUEgKFRlc3QpIiwKICAgIE1BUEUgPSBtYXBlX2FybWFfdGVzdF8xNTUwMDIsCiAgICBSTVNFID0gcm1zZV9hcm1hX3Rlc3RfMTU1MDAyCiAgKSkKICAKfSBlbHNlIHsKICB3YXJuaW5nKCJObyBoYXkgc3VmaWNpZW50ZXMgc2VtYW5hcyBlbiBlbCB0ZXN0IHBhcmEgY29tcGFyYXIgY29uIGVsIGZvcmVjYXN0IGRlIEFSTUEuIikKfQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBQUk9EVUNUTyAzNjc4MDU1CiMjIyBUUkFJTgpgYGB7ciBhcm1hLTM2NzgwNTV9CiMgUHJvZHVjdG8gMzY3ODA1NQppZF9wcm9kIDwtIDM2NzgwNTUKCiMgRmlsdHJhciBkYXRvcyBkZWwgcHJvZHVjdG8KdmVudGFzX3NlbWFuYWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgYXJyYW5nZShUcnhfRmVjaGEpICU+JQogIHNlbGVjdChUcnhfRmVjaGEsIFZlbnRhKQoKIyBDcmVhciBzZXJpZSB0ZW1wb3JhbCBzZW1hbmFsCnNlcmllX3RzIDwtIHRzKHZlbnRhc19zZW1hbmFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDUyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX3NlbWFuYWxlcyRUcnhfRmVjaGEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGlzb3dlZWsobWluKHZlbnRhc19zZW1hbmFsZXMkVHJ4X0ZlY2hhKSkpKQoKIyBBanVzdGFyIG1vZGVsbyBBUk1BCm1vZGVsb19hcm1hXzM2NzgwNTUgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQpmb3JlY2FzdF9tb2RlbG9fYXJtYV8zNjc4MDU1IDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hXzM2NzgwNTUsIGggPSA0KQoKIyBHcsOhZmljbyBkZWwgcHJvbsOzc3RpY28KYXV0b3Bsb3QoZm9yZWNhc3RfbW9kZWxvX2FybWFfMzY3ODA1NSkgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gc2VtYW5hbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksCiAgICAgICB4ID0gIlNlbWFuYSIsIHkgPSAiVmVudGFzICgkKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCmZpdHRlZF9hcm1hXzM2NzgwNTUgPC0gZml0dGVkKG1vZGVsb19hcm1hXzM2NzgwNTUpCm1hcGVfYXJtYV8zNjc4MDU1IDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF9hcm1hXzM2NzgwNTUpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMApybXNlX2FybWFfMzY3ODA1NSA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX2FybWFfMzY3ODA1NSleMikpCgojIFJlZ2lzdHJhciBtw6l0cmljYXMgZW4gbGEgdGFibGEgZ2VuZXJhbAptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGVfYXJtYV8zNjc4MDU1LAogIFJNU0UgPSBybXNlX2FybWFfMzY3ODA1NQopKQpgYGAKCiMjIyBURVNUIApgYGB7cn0KIyBDcmVhciBjb25qdW50byBkZSBwcnVlYmEgcGFyYSBwcm9kdWN0byAxNTUwMDEgKHNvbG8gY29sdW1uYXMgbmVjZXNhcmlhcyBwYXJhIEFSTUEpCnRlc3RfMzY3ODA1NSA8LSB0ZXN0ICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM2NzgwNTUpICU+JQogIHNlbGVjdChTZW1hbmEsIFZlbnRhKSAlPiUKICBhcnJhbmdlKFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKYGBge3J9CiMgRXZhbHVhciBBUk1BIGNvbnRyYSBkYXRvcyByZWFsZXMgZGVsIHRlc3QgKHByaW1lcmFzIDQgc2VtYW5hcyBkZWwgdGVzdCkKdmVudGFzX3JlYWxlc190ZXN0XzM2NzgwNTUgPC0gdGVzdF8zNjc4MDU1ICU+JQogIHNsaWNlX2hlYWQobiA9IDQpICU+JQogIHB1bGwoVmVudGEpCgp2ZW50YXNfcHJlZGljaGFzX2FybWFfMzY3ODA1NSA8LSBhcy5udW1lcmljKGZvcmVjYXN0X21vZGVsb19hcm1hXzM2NzgwNTUkbWVhbikKCiMgQ29tcGFyYXIgc29sbyBzaSBoYXkgNCBzZW1hbmFzIGRpc3BvbmlibGVzCmlmIChsZW5ndGgodmVudGFzX3JlYWxlc190ZXN0XzM2NzgwNTUpID09IGxlbmd0aCh2ZW50YXNfcHJlZGljaGFzX2FybWFfMzY3ODA1NSkpIHsKICAKICBtYXBlX2FybWFfdGVzdF8zNjc4MDU1IDwtIG1lYW4oYWJzKCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzY3ODA1NSAtIHZlbnRhc19wcmVkaWNoYXNfYXJtYV8zNjc4MDU1KSAvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG1heCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzY3ODA1NSwgMC4wMSkpKSAqIDEwMAogIAogIHJtc2VfYXJtYV90ZXN0XzM2NzgwNTUgPC0gc3FydChtZWFuKCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzY3ODA1NSAtIHZlbnRhc19wcmVkaWNoYXNfYXJtYV8zNjc4MDU1KV4yKSkKICAKICBjYXQoIk1BUEUgQVJNQSAodGVzdCAtIHByb2R1Y3RvIDM2NzgwNTUpOiIsIHJvdW5kKG1hcGVfYXJtYV90ZXN0XzM2NzgwNTUsIDIpLCAiXG4iKQogIGNhdCgiUk1TRSBBUk1BICh0ZXN0IC0gcHJvZHVjdG8gMzY3ODA1NSk6Iiwgcm91bmQocm1zZV9hcm1hX3Rlc3RfMzY3ODA1NSwgMiksICJcbiIpCiAgCiAgIyBHdWFyZGFyIGVuIHRhYmxhIGRlIG3DqXRyaWNhcwogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGlkX3Byb2QsCiAgICBNb2RlbG8gPSAiQVJNQSAoVGVzdCkiLAogICAgTUFQRSA9IG1hcGVfYXJtYV90ZXN0XzM2NzgwNTUsCiAgICBSTVNFID0gcm1zZV9hcm1hX3Rlc3RfMzY3ODA1NQogICkpCiAgCn0gZWxzZSB7CiAgd2FybmluZygiTm8gaGF5IHN1ZmljaWVudGVzIHNlbWFuYXMgZW4gZWwgdGVzdCBwYXJhIGNvbXBhcmFyIGNvbiBlbCBmb3JlY2FzdCBkZSBBUk1BLiIpCn0KYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHRlYWw7Ij4gTcOJVFJJQ0FTIENPTVBBUkFUSVZBUyBBUk1BCmBgYHtyfQptZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lCiAgYXJyYW5nZShQcm9kdWN0bykgJT4lCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSAiTcOpdHJpY2FzIEFSTUEgcG9yIHByb2R1Y3RvIikgJT4lCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpCmBgYAoKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibGFjazsiPiBSRUdSRVNJT04gTElORUFMCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBNQVBBIERFIENBTE9SCmBgYHtyIG1hcGFfY2Fsb3JfY29ycmVsYWNpb24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgVmFyaWFibGVzIG51bcOpcmljYXMgcmVsZXZhbnRlcyBwYXJhIGVsIG1vZGVsbwp2YXJzX251bWVyaWNhcyA8LSBjKCJWZW50YSIsIAogICAgICAgICAgICAgICAgICAgICJQcmVjaW9fRmluYWxfVW5pdGFyaW8iLCAKICAgICAgICAgICAgICAgICAgICAiVmVudGFfU2VtYW5hX0FudGVyaW9yIiwKICAgICAgICAgICAgICAgICAgICAiQ2FudF9TZW1hbmFfQW50ZXJpb3IiLCAKICAgICAgICAgICAgICAgICAgICAiRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciIsCiAgICAgICAgICAgICAgICAgICAgIkNvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciIpCgojIFByZXBhcmFjacOzbiBkZSBsb3MgZGF0b3MKZGF0b3NfY29yIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBzZWxlY3QoYWxsX29mKHZhcnNfbnVtZXJpY2FzKSkgJT4lCiAgbmEub21pdCgpCgojIEdlbmVyYXIgbGEgbWF0cml6IGRlIGNvcnJlbGFjacOzbgptYXRyaXpfY29yIDwtIGNvcihkYXRvc19jb3IpCgojIE1hcGEgZGUgY2Fsb3IgZGUgY29ycmVsYWNpb25lcwpnZ2NvcnJwbG90KG1hdHJpel9jb3IsCiAgICAgICAgICAgbWV0aG9kID0gInNxdWFyZSIsCiAgICAgICAgICAgdHlwZSA9ICJ1cHBlciIsCiAgICAgICAgICAgbGFiID0gVFJVRSwKICAgICAgICAgICBsYWJfc2l6ZSA9IDIsCiAgICAgICAgICAgdGwuY2V4ID0gMTAsCiAgICAgICAgICAgdGwuc3J0ID0gNDUsCiAgICAgICAgICAgY29sb3JzID0gYygiIzZEOUVDMSIsICJ3aGl0ZSIsICIjRTQ2NzI2IiksCiAgICAgICAgICAgdGl0bGUgPSAiTWFwYSBkZSBDb3JyZWxhY2nDs24gLSBWYXJpYWJsZXMgZGVsIE1vZGVsbyIsCiAgICAgICAgICAgZ2d0aGVtZSA9IHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTQpICsKICAgICAgICAgICAgIHRoZW1lKAogICAgICAgICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpLAogICAgICAgICAgICAgICBheGlzLnRleHQueSA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDAsIGhqdXN0ID0gMSkpCikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHRlYWw7Ij4gUFJPRFVDVE8gMTU1MDAxCiMjIyBEQVRPUyBUUkFJTiAxNTUwMDEKYGBge3J9CiMgRGF0b3MgZGUgZW50cmVuYW1pZW50bwpkYXRvc18xNTUwMDEgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDE1NTAwMSkgJT4lCiAgc2VsZWN0KFZlbnRhLCBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIFZlbnRhX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ2FudF9TZW1hbmFfQW50ZXJpb3IsIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IsCiAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciwgU2VtYW5hKSAlPiUKICBuYS5vbWl0KCkKYGBgCgojIyMgREFUT1MgVEVTVCAxNTUwMDEKYGBge3J9CnRlc3RfMTU1MDAxIDwtIHRlc3QgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMTU1MDAxKSAlPiUKICBzZWxlY3QoVmVudGEsIFByZWNpb19GaW5hbF9Vbml0YXJpbywgVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciwgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLCBTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCiMjIyBNT0RFTE8KYGBge3J9CiMgQWp1c3RhciBtb2RlbG8gY29uIGVudHJlbmFtaWVudG8KbW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEgPC0gbG0oCiAgVmVudGEgfiBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKwogICAgICAgICAgVmVudGFfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIFNlbWFuYSwKICBkYXRhID0gZGF0b3NfMTU1MDAxCikKCiMgVmVyIHJlc3VtZW4Kc3VtbWFyeShtb2RlbG9fcmVncmVzaW9uXzE1NTAwMSkKCiMgUHJlZGljY2lvbmVzCnByZWRfdHJhaW4gPC0gcHJlZGljdChtb2RlbG9fcmVncmVzaW9uXzE1NTAwMSwgbmV3ZGF0YSA9IGRhdG9zXzE1NTAwMSkKcHJlZF90ZXN0ICA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMTU1MDAxLCBuZXdkYXRhID0gdGVzdF8xNTUwMDEpCgojIE3DqXRyaWNhcyAtIGVudHJlbmFtaWVudG8KbWFwZV90cmFpbiA8LSBtZWFuKGFicygoZGF0b3NfMTU1MDAxJFZlbnRhIC0gcHJlZF90cmFpbikgLyBwbWF4KGRhdG9zXzE1NTAwMSRWZW50YSwgMC4wMSkpKSAqIDEwMApybXNlX3RyYWluIDwtIHNxcnQobWVhbigoZGF0b3NfMTU1MDAxJFZlbnRhIC0gcHJlZF90cmFpbileMikpCgojIE3DqXRyaWNhcyAtIHBydWViYQptYXBlX3Rlc3QgPC0gbWVhbihhYnMoKHRlc3RfMTU1MDAxJFZlbnRhIC0gcHJlZF90ZXN0KSAvIHBtYXgodGVzdF8xNTUwMDEkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0IDwtIHNxcnQobWVhbigodGVzdF8xNTUwMDEkVmVudGEgLSBwcmVkX3Rlc3QpXjIpKQoKIyBNb3N0cmFyIHJlc3VsdGFkb3MKY2F0KCJUcmFpbiAtIE1BUEU6Iiwgcm91bmQobWFwZV90cmFpbiwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90cmFpbiwgMiksICJcbiIpCmNhdCgiVGVzdCAgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdGVzdCwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90ZXN0LCAyKSwgIlxuIikKYGBgCgojIyMgVEFCTEEgREUgTcOJVFJJQ0FTIApgYGB7cn0KIyBBZ3JlZ2FyIG3DqXRyaWNhcyBkZWwgZW50cmVuYW1pZW50bwptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMSIsCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIChUcmFpbikiLAogIE1BUEUgPSBtYXBlX3RyYWluLAogIFJNU0UgPSBybXNlX3RyYWluLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopKQoKIyBBZ3JlZ2FyIG3DqXRyaWNhcyBkZWwgdGVzdAptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMSIsCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfdGVzdCwKICBSTVNFID0gcm1zZV90ZXN0LAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopKQpgYGAKCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHRlYWw7Ij4gUFJPRFVDVE8gMzkyOTc4OAojIyMgREFUT1MgVFJBSU4gCmBgYHtyfQojRGF0b3MgZGUgZW50cmVuYW1pZW50bwpkYXRvc18zOTI5Nzg4IDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAzOTI5Nzg4KSAlPiUKICBzZWxlY3QoVmVudGEsIFByZWNpb19GaW5hbF9Vbml0YXJpbywgVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciwgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLCBTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCiMjIyBEQVRPUyBURVNUCmBgYHtyfQojIERhdG9zIGRlIHBydWViYQp0ZXN0XzM5Mjk3ODggPC0gdGVzdCAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAzOTI5Nzg4KSAlPiUKICBzZWxlY3QoVmVudGEsIFByZWNpb19GaW5hbF9Vbml0YXJpbywgVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciwgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLCBTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCiMjIyBNT0RFTE8KYGBge3J9CiMgQWp1c3RhciBtb2RlbG8gY29uIGVudHJlbmFtaWVudG8KbW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4IDwtIGxtKAogIFZlbnRhIH4gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsKICAgICAgICAgIFZlbnRhX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBTZW1hbmEsCiAgZGF0YSA9IGRhdG9zXzM5Mjk3ODgKKQoKIyBWZXIgcmVzdW1lbgpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCkKCiMgUHJlZGljY2lvbmVzCnByZWRfdHJhaW5fMzkyOTc4OCA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCwgbmV3ZGF0YSA9IGRhdG9zXzM5Mjk3ODgpCnByZWRfdGVzdF8zOTI5Nzg4ICA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCwgbmV3ZGF0YSA9IHRlc3RfMzkyOTc4OCkKCiMgTcOpdHJpY2FzIC0gZW50cmVuYW1pZW50bwptYXBlX3RyYWluXzM5Mjk3ODggPC0gbWVhbihhYnMoKGRhdG9zXzM5Mjk3ODgkVmVudGEgLSBwcmVkX3RyYWluXzM5Mjk3ODgpIC8gcG1heChkYXRvc18zOTI5Nzg4JFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdHJhaW5fMzkyOTc4OCA8LSBzcXJ0KG1lYW4oKGRhdG9zXzM5Mjk3ODgkVmVudGEgLSBwcmVkX3RyYWluXzM5Mjk3ODgpXjIpKQoKIyBNw6l0cmljYXMgLSBwcnVlYmEKbWFwZV90ZXN0XzM5Mjk3ODggPC0gbWVhbihhYnMoKHRlc3RfMzkyOTc4OCRWZW50YSAtIHByZWRfdGVzdF8zOTI5Nzg4KSAvIHBtYXgodGVzdF8zOTI5Nzg4JFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdGVzdF8zOTI5Nzg4IDwtIHNxcnQobWVhbigodGVzdF8zOTI5Nzg4JFZlbnRhIC0gcHJlZF90ZXN0XzM5Mjk3ODgpXjIpKQoKIyBNb3N0cmFyIHJlc3VsdGFkb3MKY2F0KCJUcmFpbiAtIE1BUEU6Iiwgcm91bmQobWFwZV90cmFpbl8zOTI5Nzg4LCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3RyYWluXzM5Mjk3ODgsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3RfMzkyOTc4OCwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90ZXN0XzM5Mjk3ODgsIDIpLCAiXG4iKQpgYGAKCiMjIyBUQUJMQSBERSBNw4lUUklDQVMKYGBge3J9CiMgQWdyZWdhciBtw6l0cmljYXMgZGVsIGVudHJlbmFtaWVudG8KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTI5Nzg4IiwKICBNb2RlbG8gPSAiUmVncmVzacOzbiBMaW5lYWwgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfdHJhaW5fMzkyOTc4OCwKICBSTVNFID0gcm1zZV90cmFpbl8zOTI5Nzg4LAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopKQoKIyBBZ3JlZ2FyIG3DqXRyaWNhcyBkZWwgdGVzdAptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5Mjk3ODgiLAogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCAoVGVzdCkiLAogIE1BUEUgPSBtYXBlX3Rlc3RfMzkyOTc4OCwKICBSTVNFID0gcm1zZV90ZXN0XzM5Mjk3ODgsCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCikpCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiB0ZWFsOyI+IFBST0RVQ1RPIDM5MDQxNTIKIyMjIERBVE9TIFRSQUlOIApgYGB7cn0KIyBEYXRvcyBkZSBlbnRyZW5hbWllbnRvCmRhdG9zXzM5MDQxNTIgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM5MDQxNTIpICU+JQogIHNlbGVjdChWZW50YSwgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBWZW50YV9TZW1hbmFfQW50ZXJpb3IsCiAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yLCBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IsIFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKIyMjIERBVE9TIFRFU1QKYGBge3J9CiMgRGF0b3MgZGUgcHJ1ZWJhCnRlc3RfMzkwNDE1MiA8LSB0ZXN0ICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM5MDQxNTIpICU+JQogIHNlbGVjdChWZW50YSwgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBWZW50YV9TZW1hbmFfQW50ZXJpb3IsCiAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yLCBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IsIFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKIyMjIE1PREVMTwpgYGB7cn0KIyBBanVzdGFyIG1vZGVsbyBjb24gZW50cmVuYW1pZW50bwptb2RlbG9fcmVncmVzaW9uXzM5MDQxNTIgPC0gbG0oCiAgVmVudGEgfiBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKwogICAgICAgICAgVmVudGFfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIFNlbWFuYSwKICBkYXRhID0gZGF0b3NfMzkwNDE1MgopCgojIFZlciByZXN1bWVuCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8zOTA0MTUyKQoKIyBQcmVkaWNjaW9uZXMKcHJlZF90cmFpbl8zOTA0MTUyIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTA0MTUyLCBuZXdkYXRhID0gZGF0b3NfMzkwNDE1MikKcHJlZF90ZXN0XzM5MDQxNTIgIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTA0MTUyLCBuZXdkYXRhID0gdGVzdF8zOTA0MTUyKQoKIyBNw6l0cmljYXMgLSBlbnRyZW5hbWllbnRvCm1hcGVfdHJhaW5fMzkwNDE1MiA8LSBtZWFuKGFicygoZGF0b3NfMzkwNDE1MiRWZW50YSAtIHByZWRfdHJhaW5fMzkwNDE1MikgLyBwbWF4KGRhdG9zXzM5MDQxNTIkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90cmFpbl8zOTA0MTUyIDwtIHNxcnQobWVhbigoZGF0b3NfMzkwNDE1MiRWZW50YSAtIHByZWRfdHJhaW5fMzkwNDE1MileMikpCgojIE3DqXRyaWNhcyAtIHBydWViYQptYXBlX3Rlc3RfMzkwNDE1MiA8LSBtZWFuKGFicygodGVzdF8zOTA0MTUyJFZlbnRhIC0gcHJlZF90ZXN0XzM5MDQxNTIpIC8gcG1heCh0ZXN0XzM5MDQxNTIkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0XzM5MDQxNTIgPC0gc3FydChtZWFuKCh0ZXN0XzM5MDQxNTIkVmVudGEgLSBwcmVkX3Rlc3RfMzkwNDE1MileMikpCgojIE1vc3RyYXIgcmVzdWx0YWRvcwpjYXQoIlRyYWluIC0gTUFQRToiLCByb3VuZChtYXBlX3RyYWluXzM5MDQxNTIsIDIpLCAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdHJhaW5fMzkwNDE1MiwgMiksICJcbiIpCmNhdCgiVGVzdCAgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdGVzdF8zOTA0MTUyLCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3RfMzkwNDE1MiwgMiksICJcbiIpCmBgYAoKIyMjIFRBQkxBIERFIE3DiVRSSUNBUwpgYGB7cn0KIyBBZ3JlZ2FyIG3DqXRyaWNhcyBkZWwgZW50cmVuYW1pZW50bwptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5MDQxNTIiLAogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCAoVHJhaW4pIiwKICBNQVBFID0gbWFwZV90cmFpbl8zOTA0MTUyLAogIFJNU0UgPSBybXNlX3RyYWluXzM5MDQxNTIsCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCikpCgojIEFncmVnYXIgbcOpdHJpY2FzIGRlbCB0ZXN0Cm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkwNDE1MiIsCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfdGVzdF8zOTA0MTUyLAogIFJNU0UgPSBybXNlX3Rlc3RfMzkwNDE1MiwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKSkKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHRlYWw7Ij4gUFJPRFVDVE8gMTU1MDAyCiMjIyBEQVRPUyBUUkFJTgpgYGB7cn0KIyBEYXRvcyBkZSBlbnRyZW5hbWllbnRvCmRhdG9zXzE1NTAwMiA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMTU1MDAyKSAlPiUKICBzZWxlY3QoVmVudGEsIFByZWNpb19GaW5hbF9Vbml0YXJpbywgVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciwgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLCBTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCiMjIyBEQVRPUyBURVNUCmBgYHtyfQojIERhdG9zIGRlIHBydWViYQp0ZXN0XzE1NTAwMiA8LSB0ZXN0ICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDE1NTAwMikgJT4lCiAgc2VsZWN0KFZlbnRhLCBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIFZlbnRhX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ2FudF9TZW1hbmFfQW50ZXJpb3IsIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IsCiAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciwgU2VtYW5hKSAlPiUKICBuYS5vbWl0KCkKYGBgCgojIyMgTU9ERUxPCmBgYHtyfQojIEFqdXN0YXIgbW9kZWxvIGNvbiBlbnRyZW5hbWllbnRvCm1vZGVsb19yZWdyZXNpb25fMTU1MDAyIDwtIGxtKAogIFZlbnRhIH4gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsKICAgICAgICAgIFZlbnRhX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBTZW1hbmEsCiAgZGF0YSA9IGRhdG9zXzE1NTAwMgopCgojIFZlciByZXN1bWVuCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDIpCgojIFByZWRpY2Npb25lcwpwcmVkX3RyYWluXzE1NTAwMiA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMTU1MDAyLCBuZXdkYXRhID0gZGF0b3NfMTU1MDAyKQpwcmVkX3Rlc3RfMTU1MDAyICA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMTU1MDAyLCBuZXdkYXRhID0gdGVzdF8xNTUwMDIpCgojIE3DqXRyaWNhcyAtIGVudHJlbmFtaWVudG8KbWFwZV90cmFpbl8xNTUwMDIgPC0gbWVhbihhYnMoKGRhdG9zXzE1NTAwMiRWZW50YSAtIHByZWRfdHJhaW5fMTU1MDAyKSAvIHBtYXgoZGF0b3NfMTU1MDAyJFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdHJhaW5fMTU1MDAyIDwtIHNxcnQobWVhbigoZGF0b3NfMTU1MDAyJFZlbnRhIC0gcHJlZF90cmFpbl8xNTUwMDIpXjIpKQoKIyBNw6l0cmljYXMgLSBwcnVlYmEKbWFwZV90ZXN0XzE1NTAwMiA8LSBtZWFuKGFicygodGVzdF8xNTUwMDIkVmVudGEgLSBwcmVkX3Rlc3RfMTU1MDAyKSAvIHBtYXgodGVzdF8xNTUwMDIkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0XzE1NTAwMiA8LSBzcXJ0KG1lYW4oKHRlc3RfMTU1MDAyJFZlbnRhIC0gcHJlZF90ZXN0XzE1NTAwMileMikpCgojIE1vc3RyYXIgcmVzdWx0YWRvcwpjYXQoIlRyYWluIC0gTUFQRToiLCByb3VuZChtYXBlX3RyYWluXzE1NTAwMiwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90cmFpbl8xNTUwMDIsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3RfMTU1MDAyLCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3RfMTU1MDAyLCAyKSwgIlxuIikKYGBgCgojIyMgVEFCTEEgREUgTcOJVFJJQ0FTCmBgYHtyfQojIEFncmVnYXIgbcOpdHJpY2FzIGRlbCBlbnRyZW5hbWllbnRvCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAyIiwKICBNb2RlbG8gPSAiUmVncmVzacOzbiBMaW5lYWwgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfdHJhaW5fMTU1MDAyLAogIFJNU0UgPSBybXNlX3RyYWluXzE1NTAwMiwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKSkKCiMgQWdyZWdhciBtw6l0cmljYXMgZGVsIHRlc3QKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIxNTUwMDIiLAogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCAoVGVzdCkiLAogIE1BUEUgPSBtYXBlX3Rlc3RfMTU1MDAyLAogIFJNU0UgPSBybXNlX3Rlc3RfMTU1MDAyLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopKQpgYGAKCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHRlYWw7Ij4gUFJPRFVDVE8gMzY3ODA1NQojIyMgREFUT1MgVFJBSU4KYGBge3J9CiMgRGF0b3MgZGUgZW50cmVuYW1pZW50bwpkYXRvc18zNjc4MDU1IDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAzNjc4MDU1KSAlPiUKICBzZWxlY3QoVmVudGEsIFByZWNpb19GaW5hbF9Vbml0YXJpbywgVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciwgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLCBTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCiMjIyBEQVRPUyBURVNUCmBgYHtyfQojIERhdG9zIGRlIHBydWViYQp0ZXN0XzM2NzgwNTUgPC0gdGVzdCAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAzNjc4MDU1KSAlPiUKICBzZWxlY3QoVmVudGEsIFByZWNpb19GaW5hbF9Vbml0YXJpbywgVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciwgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLCBTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCiMjIyBNT0RFTE8KYGBge3J9CiMgQWp1c3RhciBtb2RlbG8gY29uIGVudHJlbmFtaWVudG8KbW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1IDwtIGxtKAogIFZlbnRhIH4gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsKICAgICAgICAgIFZlbnRhX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBTZW1hbmEsCiAgZGF0YSA9IGRhdG9zXzM2NzgwNTUKKQoKIyBWZXIgcmVzdW1lbgpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzY3ODA1NSkKCiMgUHJlZGljY2lvbmVzCnByZWRfdHJhaW5fMzY3ODA1NSA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMzY3ODA1NSwgbmV3ZGF0YSA9IGRhdG9zXzM2NzgwNTUpCnByZWRfdGVzdF8zNjc4MDU1ICA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMzY3ODA1NSwgbmV3ZGF0YSA9IHRlc3RfMzY3ODA1NSkKCiMgTcOpdHJpY2FzIC0gZW50cmVuYW1pZW50bwptYXBlX3RyYWluXzM2NzgwNTUgPC0gbWVhbihhYnMoKGRhdG9zXzM2NzgwNTUkVmVudGEgLSBwcmVkX3RyYWluXzM2NzgwNTUpIC8gcG1heChkYXRvc18zNjc4MDU1JFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdHJhaW5fMzY3ODA1NSA8LSBzcXJ0KG1lYW4oKGRhdG9zXzM2NzgwNTUkVmVudGEgLSBwcmVkX3RyYWluXzM2NzgwNTUpXjIpKQoKIyBNw6l0cmljYXMgLSBwcnVlYmEKbWFwZV90ZXN0XzM2NzgwNTUgPC0gbWVhbihhYnMoKHRlc3RfMzY3ODA1NSRWZW50YSAtIHByZWRfdGVzdF8zNjc4MDU1KSAvIHBtYXgodGVzdF8zNjc4MDU1JFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdGVzdF8zNjc4MDU1IDwtIHNxcnQobWVhbigodGVzdF8zNjc4MDU1JFZlbnRhIC0gcHJlZF90ZXN0XzM2NzgwNTUpXjIpKQoKIyBNb3N0cmFyIHJlc3VsdGFkb3MKY2F0KCJUcmFpbiAtIE1BUEU6Iiwgcm91bmQobWFwZV90cmFpbl8zNjc4MDU1LCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3RyYWluXzM2NzgwNTUsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3RfMzY3ODA1NSwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90ZXN0XzM2NzgwNTUsIDIpLCAiXG4iKQpgYGAKCiMjIyBUQUJMQSBERSBNw4lUUklDQVMKYGBge3J9CiMgQWdyZWdhciBtw6l0cmljYXMgZGVsIGVudHJlbmFtaWVudG8KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzNjc4MDU1IiwKICBNb2RlbG8gPSAiUmVncmVzacOzbiBMaW5lYWwgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfdHJhaW5fMzY3ODA1NSwKICBSTVNFID0gcm1zZV90cmFpbl8zNjc4MDU1LAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopKQoKIyBBZ3JlZ2FyIG3DqXRyaWNhcyBkZWwgdGVzdAptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM2NzgwNTUiLAogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCAoVGVzdCkiLAogIE1BUEUgPSBtYXBlX3Rlc3RfMzY3ODA1NSwKICBSTVNFID0gcm1zZV90ZXN0XzM2NzgwNTUsCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCikpCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiB0ZWFsOyI+IE3DiVRSSUNBUyBBUk1BIFkgUkVHIExJTkVBTCAKYGBge3J9Cm1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUKICBhcnJhbmdlKFByb2R1Y3RvKSAlPiUKICBrbml0cjo6a2FibGUoY2FwdGlvbiA9ICJNw6l0cmljYXMgQVJNQSAmIFJlZ3Jlc2lvbiBMaW5lYWwgcG9yIHByb2R1Y3RvIikgJT4lCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpCmBgYAoKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibGFjazsiPiBSQU5ET00gRk9SRVNUCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBQUk9EVUNUTyAxNTUwMDEKYGBge3J9CiMgRW50cmVuYW1pZW50bwpzZXQuc2VlZCgxMjMpCm1vZGVsb19yZl8xNTUwMDEgPC0gcmFuZG9tRm9yZXN0KAogIFZlbnRhIH4gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsKICAgICAgICAgIFZlbnRhX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBTZW1hbmEsCiAgZGF0YSA9IGRhdG9zXzE1NTAwMSwKICBudHJlZSA9IDUwMCwKICBtdHJ5ID0gZmxvb3Ioc3FydCg2KSksICAjIDYgdmFyaWFibGVzIHByZWRpY3RvcmFzCiAgaW1wb3J0YW5jZSA9IFRSVUUKKQoKIyBQcmVkaWNjaW9uZXMKcHJlZF90cmFpbl9yZl8xNTUwMDEgPC0gcHJlZGljdChtb2RlbG9fcmZfMTU1MDAxLCBuZXdkYXRhID0gZGF0b3NfMTU1MDAxKQpwcmVkX3Rlc3RfcmZfMTU1MDAxICA8LSBwcmVkaWN0KG1vZGVsb19yZl8xNTUwMDEsIG5ld2RhdGEgPSB0ZXN0XzE1NTAwMSkKCiMgTcOpdHJpY2FzCm1hcGVfdHJhaW5fcmZfMTU1MDAxIDwtIG1lYW4oYWJzKChkYXRvc18xNTUwMDEkVmVudGEgLSBwcmVkX3RyYWluX3JmXzE1NTAwMSkgLyBwbWF4KGRhdG9zXzE1NTAwMSRWZW50YSwgMC4wMSkpKSAqIDEwMApybXNlX3RyYWluX3JmXzE1NTAwMSA8LSBzcXJ0KG1lYW4oKGRhdG9zXzE1NTAwMSRWZW50YSAtIHByZWRfdHJhaW5fcmZfMTU1MDAxKV4yKSkKCm1hcGVfdGVzdF9yZl8xNTUwMDEgPC0gbWVhbihhYnMoKHRlc3RfMTU1MDAxJFZlbnRhIC0gcHJlZF90ZXN0X3JmXzE1NTAwMSkgLyBwbWF4KHRlc3RfMTU1MDAxJFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdGVzdF9yZl8xNTUwMDEgPC0gc3FydChtZWFuKCh0ZXN0XzE1NTAwMSRWZW50YSAtIHByZWRfdGVzdF9yZl8xNTUwMDEpXjIpKQoKIyBNb3N0cmFyIHJlc3VsdGFkb3MKY2F0KCJSYW5kb20gRm9yZXN0IC0gUHJvZHVjdG8gMTU1MDAxXG4iKQpjYXQoIlRyYWluIC0gTUFQRToiLCByb3VuZChtYXBlX3RyYWluX3JmXzE1NTAwMSwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90cmFpbl9yZl8xNTUwMDEsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3RfcmZfMTU1MDAxLCAyKSwgICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90ZXN0X3JmXzE1NTAwMSwgMiksICAiXG4iKQoKIyBHdWFyZGFyIG3DqXRyaWNhcyBlbiB0YWJsYQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMSIsCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfdHJhaW5fcmZfMTU1MDAxLAogIFJNU0UgPSBybXNlX3RyYWluX3JmXzE1NTAwMQopKQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIxNTUwMDEiLAogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfdGVzdF9yZl8xNTUwMDEsCiAgUk1TRSA9IHJtc2VfdGVzdF9yZl8xNTUwMDEKKSkKCiMgSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCnZhckltcFBsb3QobW9kZWxvX3JmXzE1NTAwMSwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDEiKQoKIyBPYnNlcnZhZG8gdnMgUHJlZGljaG8gKHRlc3QpCmdncGxvdChkYXRhLmZyYW1lKE9ic2VydmFkbyA9IHRlc3RfMTU1MDAxJFZlbnRhLCBQcmVkaWNobyA9IHByZWRfdGVzdF9yZl8xNTUwMDEpLAogICAgICAgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicyh0aXRsZSA9ICJPYnNlcnZhZG8gdnMgUHJlZGljaG8gLSBQcm9kdWN0byAxNTUwMDEgKHRlc3QgZGF0YSkiLAogICAgICAgeCA9ICJWZW50YSBPYnNlcnZhZGEiLCB5ID0gIlZlbnRhIFByZWRpY2hhIikgKwogIHRoZW1lX21pbmltYWwoKQoKIyBBbsOhbGlzaXMgZGUgZXJyb3JlcyAodGVzdCkKZXJyb3Jlc190ZXN0XzE1NTAwMSA8LSB0ZXN0XzE1NTAwMSRWZW50YSAtIHByZWRfdGVzdF9yZl8xNTUwMDEKaGlzdChlcnJvcmVzX3Rlc3RfMTU1MDAxLCBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDE1NTAwMSAodGVzdCBkYXRhKSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwgY29sID0gInNreWJsdWUiLCBicmVha3MgPSAzMCkKCmNhdCgiTWVkaWEgZXJyb3I6IiwgbWVhbihlcnJvcmVzX3Rlc3RfMTU1MDAxKSwgIlxuIikKY2F0KCJTRCBlcnJvcjoiLCBzZChlcnJvcmVzX3Rlc3RfMTU1MDAxKSwgIlxuIikKY2F0KCJNaW4gZXJyb3I6IiwgbWluKGVycm9yZXNfdGVzdF8xNTUwMDEpLCAiXG4iKQpjYXQoIk1heCBlcnJvcjoiLCBtYXgoZXJyb3Jlc190ZXN0XzE1NTAwMSksICJcbiIpCmNhdCgiTWVkaWFuYSBlcnJvcjoiLCBtZWRpYW4oZXJyb3Jlc190ZXN0XzE1NTAwMSksICJcbiIpCgojIEVycm9yIHZzIFByZWRpY2Npw7NuICh0ZXN0KQpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRfdGVzdF9yZl8xNTUwMDEsIEVycm9yID0gZXJyb3Jlc190ZXN0XzE1NTAwMSksCiAgICAgICBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMTU1MDAxICh0ZXN0IGRhdGEpIiwKICAgICAgIHggPSAiVmVudGEgUHJlZGljaGEiLCB5ID0gIkVycm9yIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBQUk9EVUNUTyAzOTI5Nzg4CmBgYHtyfQojIEVudHJlbmFtaWVudG8Kc2V0LnNlZWQoMTIzKQptb2RlbG9fcmZfMzkyOTc4OCA8LSByYW5kb21Gb3Jlc3QoCiAgVmVudGEgfiBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKwogICAgICAgICAgVmVudGFfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIFNlbWFuYSwKICBkYXRhID0gZGF0b3NfMzkyOTc4OCwKICBudHJlZSA9IDUwMCwKICBtdHJ5ID0gZmxvb3Ioc3FydCg2KSksICAjIDYgdmFyaWFibGVzIHByZWRpY3RvcmFzCiAgaW1wb3J0YW5jZSA9IFRSVUUKKQoKIyBQcmVkaWNjaW9uZXMKcHJlZF90cmFpbl9yZl8zOTI5Nzg4IDwtIHByZWRpY3QobW9kZWxvX3JmXzM5Mjk3ODgsIG5ld2RhdGEgPSBkYXRvc18zOTI5Nzg4KQpwcmVkX3Rlc3RfcmZfMzkyOTc4OCAgPC0gcHJlZGljdChtb2RlbG9fcmZfMzkyOTc4OCwgbmV3ZGF0YSA9IHRlc3RfMzkyOTc4OCkKCiMgTcOpdHJpY2FzCm1hcGVfdHJhaW5fcmZfMzkyOTc4OCA8LSBtZWFuKGFicygoZGF0b3NfMzkyOTc4OCRWZW50YSAtIHByZWRfdHJhaW5fcmZfMzkyOTc4OCkgLyBwbWF4KGRhdG9zXzM5Mjk3ODgkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90cmFpbl9yZl8zOTI5Nzg4IDwtIHNxcnQobWVhbigoZGF0b3NfMzkyOTc4OCRWZW50YSAtIHByZWRfdHJhaW5fcmZfMzkyOTc4OCleMikpCgptYXBlX3Rlc3RfcmZfMzkyOTc4OCA8LSBtZWFuKGFicygodGVzdF8zOTI5Nzg4JFZlbnRhIC0gcHJlZF90ZXN0X3JmXzM5Mjk3ODgpIC8gcG1heCh0ZXN0XzM5Mjk3ODgkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0X3JmXzM5Mjk3ODggPC0gc3FydChtZWFuKCh0ZXN0XzM5Mjk3ODgkVmVudGEgLSBwcmVkX3Rlc3RfcmZfMzkyOTc4OCleMikpCgojIE1vc3RyYXIgcmVzdWx0YWRvcwpjYXQoIlJhbmRvbSBGb3Jlc3QgLSBQcm9kdWN0byAzOTI5Nzg4XG4iKQpjYXQoIlRyYWluIC0gTUFQRToiLCByb3VuZChtYXBlX3RyYWluX3JmXzM5Mjk3ODgsIDIpLCAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdHJhaW5fcmZfMzkyOTc4OCwgMiksICJcbiIpCmNhdCgiVGVzdCAgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdGVzdF9yZl8zOTI5Nzg4LCAyKSwgICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90ZXN0X3JmXzM5Mjk3ODgsIDIpLCAgIlxuIikKCiMgR3VhcmRhciBtw6l0cmljYXMgZW4gdGFibGEKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTI5Nzg4IiwKICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCAoVHJhaW4pIiwKICBNQVBFID0gbWFwZV90cmFpbl9yZl8zOTI5Nzg4LAogIFJNU0UgPSBybXNlX3RyYWluX3JmXzM5Mjk3ODgKKSkKCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkyOTc4OCIsCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QgKFRlc3QpIiwKICBNQVBFID0gbWFwZV90ZXN0X3JmXzM5Mjk3ODgsCiAgUk1TRSA9IHJtc2VfdGVzdF9yZl8zOTI5Nzg4CikpCgojIEltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp2YXJJbXBQbG90KG1vZGVsb19yZl8zOTI5Nzg4LCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM5Mjk3ODgiKQoKIyBPYnNlcnZhZG8gdnMgUHJlZGljaG8gKHRlc3QpCmdncGxvdChkYXRhLmZyYW1lKE9ic2VydmFkbyA9IHRlc3RfMzkyOTc4OCRWZW50YSwgUHJlZGljaG8gPSBwcmVkX3Rlc3RfcmZfMzkyOTc4OCksCiAgICAgICBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKHRpdGxlID0gIk9ic2VydmFkbyB2cyBQcmVkaWNobyAtIFByb2R1Y3RvIDM5Mjk3ODggKHRlc3QgZGF0YSkiLAogICAgICAgeCA9ICJWZW50YSBPYnNlcnZhZGEiLCB5ID0gIlZlbnRhIFByZWRpY2hhIikgKwogIHRoZW1lX21pbmltYWwoKQoKIyBBbsOhbGlzaXMgZGUgZXJyb3JlcyAodGVzdCkKZXJyb3Jlc190ZXN0XzM5Mjk3ODggPC0gdGVzdF8zOTI5Nzg4JFZlbnRhIC0gcHJlZF90ZXN0X3JmXzM5Mjk3ODgKaGlzdChlcnJvcmVzX3Rlc3RfMzkyOTc4OCwgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAzOTI5Nzg4ICh0ZXN0IGRhdGEpIiwKICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLCBjb2wgPSAic2t5Ymx1ZSIsIGJyZWFrcyA9IDMwKQoKY2F0KCJNZWRpYSBlcnJvcjoiLCBtZWFuKGVycm9yZXNfdGVzdF8zOTI5Nzg4KSwgIlxuIikKY2F0KCJTRCBlcnJvcjoiLCBzZChlcnJvcmVzX3Rlc3RfMzkyOTc4OCksICJcbiIpCmNhdCgiTWluIGVycm9yOiIsIG1pbihlcnJvcmVzX3Rlc3RfMzkyOTc4OCksICJcbiIpCmNhdCgiTWF4IGVycm9yOiIsIG1heChlcnJvcmVzX3Rlc3RfMzkyOTc4OCksICJcbiIpCmNhdCgiTWVkaWFuYSBlcnJvcjoiLCBtZWRpYW4oZXJyb3Jlc190ZXN0XzM5Mjk3ODgpLCAiXG4iKQoKIyBFcnJvciB2cyBQcmVkaWNjacOzbiAodGVzdCkKZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkX3Rlc3RfcmZfMzkyOTc4OCwgRXJyb3IgPSBlcnJvcmVzX3Rlc3RfMzkyOTc4OCksCiAgICAgICBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzkyOTc4OCAodGVzdCBkYXRhKSIsCiAgICAgICB4ID0gIlZlbnRhIFByZWRpY2hhIiwgeSA9ICJFcnJvciIpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHRlYWw7Ij4gUFJPRFVDVE8gMzkwNDE1MgpgYGB7cn0KIyBFbnRyZW5hbWllbnRvCnNldC5zZWVkKDEyMykKbW9kZWxvX3JmXzM5MDQxNTIgPC0gcmFuZG9tRm9yZXN0KAogIFZlbnRhIH4gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsKICAgICAgICAgIFZlbnRhX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBTZW1hbmEsCiAgZGF0YSA9IGRhdG9zXzM5MDQxNTIsCiAgbnRyZWUgPSA1MDAsCiAgbXRyeSA9IGZsb29yKHNxcnQoNikpLCAgIyA2IHZhcmlhYmxlcyBwcmVkaWN0b3JhcwogIGltcG9ydGFuY2UgPSBUUlVFCikKCiMgUHJlZGljY2lvbmVzCnByZWRfdHJhaW5fcmZfMzkwNDE1MiA8LSBwcmVkaWN0KG1vZGVsb19yZl8zOTA0MTUyLCBuZXdkYXRhID0gZGF0b3NfMzkwNDE1MikKcHJlZF90ZXN0X3JmXzM5MDQxNTIgIDwtIHByZWRpY3QobW9kZWxvX3JmXzM5MDQxNTIsIG5ld2RhdGEgPSB0ZXN0XzM5MDQxNTIpCgojIE3DqXRyaWNhcwptYXBlX3RyYWluX3JmXzM5MDQxNTIgPC0gbWVhbihhYnMoKGRhdG9zXzM5MDQxNTIkVmVudGEgLSBwcmVkX3RyYWluX3JmXzM5MDQxNTIpIC8gcG1heChkYXRvc18zOTA0MTUyJFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdHJhaW5fcmZfMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKGRhdG9zXzM5MDQxNTIkVmVudGEgLSBwcmVkX3RyYWluX3JmXzM5MDQxNTIpXjIpKQoKbWFwZV90ZXN0X3JmXzM5MDQxNTIgPC0gbWVhbihhYnMoKHRlc3RfMzkwNDE1MiRWZW50YSAtIHByZWRfdGVzdF9yZl8zOTA0MTUyKSAvIHBtYXgodGVzdF8zOTA0MTUyJFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdGVzdF9yZl8zOTA0MTUyIDwtIHNxcnQobWVhbigodGVzdF8zOTA0MTUyJFZlbnRhIC0gcHJlZF90ZXN0X3JmXzM5MDQxNTIpXjIpKQoKIyBNb3N0cmFyIHJlc3VsdGFkb3MKY2F0KCJSYW5kb20gRm9yZXN0IC0gUHJvZHVjdG8gMzkwNDE1MlxuIikKY2F0KCJUcmFpbiAtIE1BUEU6Iiwgcm91bmQobWFwZV90cmFpbl9yZl8zOTA0MTUyLCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3RyYWluX3JmXzM5MDQxNTIsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3RfcmZfMzkwNDE1MiwgMiksICAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdGVzdF9yZl8zOTA0MTUyLCAyKSwgICJcbiIpCgojIEd1YXJkYXIgbcOpdHJpY2FzIGVuIHRhYmxhCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkwNDE1MiIsCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfdHJhaW5fcmZfMzkwNDE1MiwKICBSTVNFID0gcm1zZV90cmFpbl9yZl8zOTA0MTUyCikpCgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5MDQxNTIiLAogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfdGVzdF9yZl8zOTA0MTUyLAogIFJNU0UgPSBybXNlX3Rlc3RfcmZfMzkwNDE1MgopKQoKIyBJbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKdmFySW1wUGxvdChtb2RlbG9fcmZfMzkwNDE1MiwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAzOTA0MTUyIikKCiMgT2JzZXJ2YWRvIHZzIFByZWRpY2hvICh0ZXN0KQpnZ3Bsb3QoZGF0YS5mcmFtZShPYnNlcnZhZG8gPSB0ZXN0XzM5MDQxNTIkVmVudGEsIFByZWRpY2hvID0gcHJlZF90ZXN0X3JmXzM5MDQxNTIpLAogICAgICAgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicyh0aXRsZSA9ICJPYnNlcnZhZG8gdnMgUHJlZGljaG8gLSBQcm9kdWN0byAzOTA0MTUyICh0ZXN0IGRhdGEpIiwKICAgICAgIHggPSAiVmVudGEgT2JzZXJ2YWRhIiwgeSA9ICJWZW50YSBQcmVkaWNoYSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQW7DoWxpc2lzIGRlIGVycm9yZXMgKHRlc3QpCmVycm9yZXNfdGVzdF8zOTA0MTUyIDwtIHRlc3RfMzkwNDE1MiRWZW50YSAtIHByZWRfdGVzdF9yZl8zOTA0MTUyCmhpc3QoZXJyb3Jlc190ZXN0XzM5MDQxNTIsIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMzkwNDE1MiAodGVzdCBkYXRhKSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwgY29sID0gInNreWJsdWUiLCBicmVha3MgPSAzMCkKCmNhdCgiTWVkaWEgZXJyb3I6IiwgbWVhbihlcnJvcmVzX3Rlc3RfMzkwNDE1MiksICJcbiIpCmNhdCgiU0QgZXJyb3I6Iiwgc2QoZXJyb3Jlc190ZXN0XzM5MDQxNTIpLCAiXG4iKQpjYXQoIk1pbiBlcnJvcjoiLCBtaW4oZXJyb3Jlc190ZXN0XzM5MDQxNTIpLCAiXG4iKQpjYXQoIk1heCBlcnJvcjoiLCBtYXgoZXJyb3Jlc190ZXN0XzM5MDQxNTIpLCAiXG4iKQpjYXQoIk1lZGlhbmEgZXJyb3I6IiwgbWVkaWFuKGVycm9yZXNfdGVzdF8zOTA0MTUyKSwgIlxuIikKCiMgRXJyb3IgdnMgUHJlZGljY2nDs24gKHRlc3QpCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZF90ZXN0X3JmXzM5MDQxNTIsIEVycm9yID0gZXJyb3Jlc190ZXN0XzM5MDQxNTIpLAogICAgICAgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicyh0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDM5MDQxNTIgKHRlc3QgZGF0YSkiLAogICAgICAgeCA9ICJWZW50YSBQcmVkaWNoYSIsIHkgPSAiRXJyb3IiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiB0ZWFsOyI+IFBST0RVQ1RPIDE1NTAwMgpgYGB7cn0KIyBFbnRyZW5hbWllbnRvCnNldC5zZWVkKDEyMykKbW9kZWxvX3JmXzE1NTAwMiA8LSByYW5kb21Gb3Jlc3QoCiAgVmVudGEgfiBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKwogICAgICAgICAgVmVudGFfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIFNlbWFuYSwKICBkYXRhID0gZGF0b3NfMTU1MDAyLAogIG50cmVlID0gNTAwLAogIG10cnkgPSBmbG9vcihzcXJ0KDYpKSwgICMgNiB2YXJpYWJsZXMgcHJlZGljdG9yYXMKICBpbXBvcnRhbmNlID0gVFJVRQopCgojIFByZWRpY2Npb25lcwpwcmVkX3RyYWluX3JmXzE1NTAwMiA8LSBwcmVkaWN0KG1vZGVsb19yZl8xNTUwMDIsIG5ld2RhdGEgPSBkYXRvc18xNTUwMDIpCnByZWRfdGVzdF9yZl8xNTUwMDIgIDwtIHByZWRpY3QobW9kZWxvX3JmXzE1NTAwMiwgbmV3ZGF0YSA9IHRlc3RfMTU1MDAyKQoKIyBNw6l0cmljYXMKbWFwZV90cmFpbl9yZl8xNTUwMDIgPC0gbWVhbihhYnMoKGRhdG9zXzE1NTAwMiRWZW50YSAtIHByZWRfdHJhaW5fcmZfMTU1MDAyKSAvIHBtYXgoZGF0b3NfMTU1MDAyJFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdHJhaW5fcmZfMTU1MDAyIDwtIHNxcnQobWVhbigoZGF0b3NfMTU1MDAyJFZlbnRhIC0gcHJlZF90cmFpbl9yZl8xNTUwMDIpXjIpKQoKbWFwZV90ZXN0X3JmXzE1NTAwMiA8LSBtZWFuKGFicygodGVzdF8xNTUwMDIkVmVudGEgLSBwcmVkX3Rlc3RfcmZfMTU1MDAyKSAvIHBtYXgodGVzdF8xNTUwMDIkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0X3JmXzE1NTAwMiA8LSBzcXJ0KG1lYW4oKHRlc3RfMTU1MDAyJFZlbnRhIC0gcHJlZF90ZXN0X3JmXzE1NTAwMileMikpCgojIE1vc3RyYXIgcmVzdWx0YWRvcwpjYXQoIlJhbmRvbSBGb3Jlc3QgLSBQcm9kdWN0byAxNTUwMDJcbiIpCmNhdCgiVHJhaW4gLSBNQVBFOiIsIHJvdW5kKG1hcGVfdHJhaW5fcmZfMTU1MDAyLCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3RyYWluX3JmXzE1NTAwMiwgMiksICJcbiIpCmNhdCgiVGVzdCAgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdGVzdF9yZl8xNTUwMDIsIDIpLCAgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3RfcmZfMTU1MDAyLCAyKSwgICJcbiIpCgojIEd1YXJkYXIgbcOpdHJpY2FzIGVuIHRhYmxhCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAyIiwKICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCAoVHJhaW4pIiwKICBNQVBFID0gbWFwZV90cmFpbl9yZl8xNTUwMDIsCiAgUk1TRSA9IHJtc2VfdHJhaW5fcmZfMTU1MDAyCikpCgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMiIsCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QgKFRlc3QpIiwKICBNQVBFID0gbWFwZV90ZXN0X3JmXzE1NTAwMiwKICBSTVNFID0gcm1zZV90ZXN0X3JmXzE1NTAwMgopKQoKIyBJbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKdmFySW1wUGxvdChtb2RlbG9fcmZfMTU1MDAyLCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDE1NTAwMiIpCgojIE9ic2VydmFkbyB2cyBQcmVkaWNobyAodGVzdCkKZ2dwbG90KGRhdGEuZnJhbWUoT2JzZXJ2YWRvID0gdGVzdF8xNTUwMDIkVmVudGEsIFByZWRpY2hvID0gcHJlZF90ZXN0X3JmXzE1NTAwMiksCiAgICAgICBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKHRpdGxlID0gIk9ic2VydmFkbyB2cyBQcmVkaWNobyAtIFByb2R1Y3RvIDE1NTAwMiAodGVzdCBkYXRhKSIsCiAgICAgICB4ID0gIlZlbnRhIE9ic2VydmFkYSIsIHkgPSAiVmVudGEgUHJlZGljaGEiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEFuw6FsaXNpcyBkZSBlcnJvcmVzICh0ZXN0KQplcnJvcmVzX3Rlc3RfMTU1MDAyIDwtIHRlc3RfMTU1MDAyJFZlbnRhIC0gcHJlZF90ZXN0X3JmXzE1NTAwMgpoaXN0KGVycm9yZXNfdGVzdF8xNTUwMDIsIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMTU1MDAyICh0ZXN0IGRhdGEpIiwKICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLCBjb2wgPSAic2t5Ymx1ZSIsIGJyZWFrcyA9IDMwKQoKY2F0KCJNZWRpYSBlcnJvcjoiLCBtZWFuKGVycm9yZXNfdGVzdF8xNTUwMDIpLCAiXG4iKQpjYXQoIlNEIGVycm9yOiIsIHNkKGVycm9yZXNfdGVzdF8xNTUwMDIpLCAiXG4iKQpjYXQoIk1pbiBlcnJvcjoiLCBtaW4oZXJyb3Jlc190ZXN0XzE1NTAwMiksICJcbiIpCmNhdCgiTWF4IGVycm9yOiIsIG1heChlcnJvcmVzX3Rlc3RfMTU1MDAyKSwgIlxuIikKY2F0KCJNZWRpYW5hIGVycm9yOiIsIG1lZGlhbihlcnJvcmVzX3Rlc3RfMTU1MDAyKSwgIlxuIikKCiMgRXJyb3IgdnMgUHJlZGljY2nDs24gKHRlc3QpCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZF90ZXN0X3JmXzE1NTAwMiwgRXJyb3IgPSBlcnJvcmVzX3Rlc3RfMTU1MDAyKSwKICAgICAgIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnModGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAxNTUwMDIgKHRlc3QgZGF0YSkiLAogICAgICAgeCA9ICJWZW50YSBQcmVkaWNoYSIsIHkgPSAiRXJyb3IiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiB0ZWFsOyI+IFBST0RVQ1RPIDM2NzgwNTUKYGBge3J9CiMgRW50cmVuYW1pZW50bwpzZXQuc2VlZCgxMjMpCm1vZGVsb19yZl8zNjc4MDU1IDwtIHJhbmRvbUZvcmVzdCgKICBWZW50YSB+IFByZWNpb19GaW5hbF9Vbml0YXJpbyArCiAgICAgICAgICBWZW50YV9TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgQ2FudF9TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgU2VtYW5hLAogIGRhdGEgPSBkYXRvc18zNjc4MDU1LAogIG50cmVlID0gNTAwLAogIG10cnkgPSBmbG9vcihzcXJ0KDYpKSwgICMgNiB2YXJpYWJsZXMgcHJlZGljdG9yYXMKICBpbXBvcnRhbmNlID0gVFJVRQopCgojIFByZWRpY2Npb25lcwpwcmVkX3RyYWluX3JmXzM2NzgwNTUgPC0gcHJlZGljdChtb2RlbG9fcmZfMzY3ODA1NSwgbmV3ZGF0YSA9IGRhdG9zXzM2NzgwNTUpCnByZWRfdGVzdF9yZl8zNjc4MDU1ICA8LSBwcmVkaWN0KG1vZGVsb19yZl8zNjc4MDU1LCBuZXdkYXRhID0gdGVzdF8zNjc4MDU1KQoKIyBNw6l0cmljYXMKbWFwZV90cmFpbl9yZl8zNjc4MDU1IDwtIG1lYW4oYWJzKChkYXRvc18zNjc4MDU1JFZlbnRhIC0gcHJlZF90cmFpbl9yZl8zNjc4MDU1KSAvIHBtYXgoZGF0b3NfMzY3ODA1NSRWZW50YSwgMC4wMSkpKSAqIDEwMApybXNlX3RyYWluX3JmXzM2NzgwNTUgPC0gc3FydChtZWFuKChkYXRvc18zNjc4MDU1JFZlbnRhIC0gcHJlZF90cmFpbl9yZl8zNjc4MDU1KV4yKSkKCm1hcGVfdGVzdF9yZl8zNjc4MDU1IDwtIG1lYW4oYWJzKCh0ZXN0XzM2NzgwNTUkVmVudGEgLSBwcmVkX3Rlc3RfcmZfMzY3ODA1NSkgLyBwbWF4KHRlc3RfMzY3ODA1NSRWZW50YSwgMC4wMSkpKSAqIDEwMApybXNlX3Rlc3RfcmZfMzY3ODA1NSA8LSBzcXJ0KG1lYW4oKHRlc3RfMzY3ODA1NSRWZW50YSAtIHByZWRfdGVzdF9yZl8zNjc4MDU1KV4yKSkKCiMgTW9zdHJhciByZXN1bHRhZG9zCmNhdCgiUmFuZG9tIEZvcmVzdCAtIFByb2R1Y3RvIDM2NzgwNTVcbiIpCmNhdCgiVHJhaW4gLSBNQVBFOiIsIHJvdW5kKG1hcGVfdHJhaW5fcmZfMzY3ODA1NSwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90cmFpbl9yZl8zNjc4MDU1LCAyKSwgIlxuIikKY2F0KCJUZXN0ICAtIE1BUEU6Iiwgcm91bmQobWFwZV90ZXN0X3JmXzM2NzgwNTUsIDIpLCAgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3RfcmZfMzY3ODA1NSwgMiksICAiXG4iKQoKIyBHdWFyZGFyIG3DqXRyaWNhcyBlbiB0YWJsYQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM2NzgwNTUiLAogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IChUcmFpbikiLAogIE1BUEUgPSBtYXBlX3RyYWluX3JmXzM2NzgwNTUsCiAgUk1TRSA9IHJtc2VfdHJhaW5fcmZfMzY3ODA1NQopKQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzNjc4MDU1IiwKICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCAoVGVzdCkiLAogIE1BUEUgPSBtYXBlX3Rlc3RfcmZfMzY3ODA1NSwKICBSTVNFID0gcm1zZV90ZXN0X3JmXzM2NzgwNTUKKSkKCiMgSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCnZhckltcFBsb3QobW9kZWxvX3JmXzM2NzgwNTUsIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMzY3ODA1NSIpCgojIE9ic2VydmFkbyB2cyBQcmVkaWNobyAodGVzdCkKZ2dwbG90KGRhdGEuZnJhbWUoT2JzZXJ2YWRvID0gdGVzdF8zNjc4MDU1JFZlbnRhLCBQcmVkaWNobyA9IHByZWRfdGVzdF9yZl8zNjc4MDU1KSwKICAgICAgIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnModGl0bGUgPSAiT2JzZXJ2YWRvIHZzIFByZWRpY2hvIC0gUHJvZHVjdG8gMzY3ODA1NSAodGVzdCBkYXRhKSIsCiAgICAgICB4ID0gIlZlbnRhIE9ic2VydmFkYSIsIHkgPSAiVmVudGEgUHJlZGljaGEiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEFuw6FsaXNpcyBkZSBlcnJvcmVzICh0ZXN0KQplcnJvcmVzX3Rlc3RfMzY3ODA1NSA8LSB0ZXN0XzM2NzgwNTUkVmVudGEgLSBwcmVkX3Rlc3RfcmZfMzY3ODA1NQpoaXN0KGVycm9yZXNfdGVzdF8zNjc4MDU1LCBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM2NzgwNTUgKHRlc3QgZGF0YSkiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsIGNvbCA9ICJza3libHVlIiwgYnJlYWtzID0gMzApCgpjYXQoIk1lZGlhIGVycm9yOiIsIG1lYW4oZXJyb3Jlc190ZXN0XzM2NzgwNTUpLCAiXG4iKQpjYXQoIlNEIGVycm9yOiIsIHNkKGVycm9yZXNfdGVzdF8zNjc4MDU1KSwgIlxuIikKY2F0KCJNaW4gZXJyb3I6IiwgbWluKGVycm9yZXNfdGVzdF8zNjc4MDU1KSwgIlxuIikKY2F0KCJNYXggZXJyb3I6IiwgbWF4KGVycm9yZXNfdGVzdF8zNjc4MDU1KSwgIlxuIikKY2F0KCJNZWRpYW5hIGVycm9yOiIsIG1lZGlhbihlcnJvcmVzX3Rlc3RfMzY3ODA1NSksICJcbiIpCgojIEVycm9yIHZzIFByZWRpY2Npw7NuICh0ZXN0KQpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRfdGVzdF9yZl8zNjc4MDU1LCBFcnJvciA9IGVycm9yZXNfdGVzdF8zNjc4MDU1KSwKICAgICAgIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnModGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzNjc4MDU1ICh0ZXN0IGRhdGEpIiwKICAgICAgIHggPSAiVmVudGEgUHJlZGljaGEiLCB5ID0gIkVycm9yIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBNw4lUUklDQVMgQVJNQSwgUkVHIExJTkVBTCBZIFJBTkRPTSBGT1JFU1QKYGBge3J9Cm1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUKICBhcnJhbmdlKFByb2R1Y3RvKSAlPiUKICBrbml0cjo6a2FibGUoY2FwdGlvbiA9ICJNw6l0cmljYXMgQVJNQSwgUmVncmVzaW9uIExpbmVhbCB5IFJhbmRvbSBGb3Jlc3QgcG9yIHByb2R1Y3RvIikgJT4lCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpCmBgYAoKCgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsYWNrOyI+IFhHQk9PU1QKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiB0ZWFsOyI+IFBST0RVQ1RPIDE1NTAwMQojIyMgU1BMSVQgSU5URVJOTwpgYGB7cn0KIyBEaXZpZGlyIGxvcyBkYXRvcyBlbiBlbnRyZW5hbWllbnRvICg4MCUpIHkgdmFsaWRhY2nDs24gaW50ZXJuYSAoMjAlKQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAoKaW5kaWNlc190cmFpbl8xNTUwMDEgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkYXRvc18xNTUwMDEkVmVudGEsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkKCiMgQ3JlYXIgc3ViY29uanVudG9zCmRhdG9zX3RyYWluXzE1NTAwMSA8LSBkYXRvc18xNTUwMDFbaW5kaWNlc190cmFpbl8xNTUwMDEsIF0KZGF0b3NfdGVzdF8xNTUwMDEgIDwtIGRhdG9zXzE1NTAwMVstaW5kaWNlc190cmFpbl8xNTUwMDEsIF0KCiMgTWF0cmljZXMgcHJlZGljdG9yYXMgeSBvYmpldGl2bwpYX3RyYWluXzE1NTAwMSA8LSBhcy5tYXRyaXgoZGF0b3NfdHJhaW5fMTU1MDAxWywgY29sbmFtZXMoZGF0b3NfdHJhaW5fMTU1MDAxKSAhPSAiVmVudGEiXSkKeV90cmFpbl8xNTUwMDEgPC0gZGF0b3NfdHJhaW5fMTU1MDAxJFZlbnRhCgpYX3Rlc3RfMTU1MDAxIDwtIGFzLm1hdHJpeChkYXRvc190ZXN0XzE1NTAwMVssIGNvbG5hbWVzKGRhdG9zX3Rlc3RfMTU1MDAxKSAhPSAiVmVudGEiXSkKeV90ZXN0XzE1NTAwMSA8LSBkYXRvc190ZXN0XzE1NTAwMSRWZW50YQoKIyBDb252ZXJ0aXIgYSBmb3JtYXRvIERNYXRyaXggcGFyYSBYR0Jvb3N0CmR0cmFpbl8xNTUwMDEgPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfdHJhaW5fMTU1MDAxLCBsYWJlbCA9IHlfdHJhaW5fMTU1MDAxKQpkdGVzdF8xNTUwMDEgIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3Rlc3RfMTU1MDAxLCBsYWJlbCA9IHlfdGVzdF8xNTUwMDEpCmBgYAoKIyMjIEJVU1FVRURBIERFIEhJUEVSUEFSQU1FVFJPUyBDT04gVkFMSURBQ0nDk04gQ1JVWkFEQQpgYGB7cn0KIyBDcmVhciB1bmEgcmVqaWxsYSBkZSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3MKcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgKICBldGEgPSBjKDAuMDEsIDAuMDUsIDAuMSksICAgICAgICAgICAgICMgVGFzYSBkZSBhcHJlbmRpemFqZQogIG1heF9kZXB0aCA9IGMoMywgNSwgNyksICAgICAgICAgICAgICAgIyBQcm9mdW5kaWRhZCBkZWwgw6FyYm9sCiAgc3Vic2FtcGxlID0gYygwLjgsIDEuMCksICAgICAgICAgICAgICAjIFByb3BvcmNpw7NuIGRlIG11ZXN0cmFzIHBvciDDoXJib2wKICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjgsIDEuMCksICAgICAgICMgUHJvcG9yY2nDs24gZGUgY29sdW1uYXMgcG9yIMOhcmJvbAogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMpLCAgICAgICAgICAgIyBQZXNvIG3DrW5pbW8gZGUgbm9kb3MgaGlqb3MKICBnYW1tYSA9IGMoMCwgMC4xKSAgICAgICAgICAgICAgICAgICAgICMgUmVndWxhcml6YWNpw7NuCikKCiMgUmVkdWNpciBjb21iaW5hY2lvbmVzIHBhcmEgZWZpY2llbmNpYQpzZXQuc2VlZCgxMjMpCnBhcmFtX2dyaWRfcmVkdWNpZGEgPC0gcGFyYW1fZ3JpZFtzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAxMCksIF0KCiMgRnVuY2nDs24gcGFyYSBldmFsdWFyIGNhZGEgY29tYmluYWNpw7NuIGNvbiB2YWxpZGFjacOzbiBjcnV6YWRhCmV2YWx1YXRlX3BhcmFtc18xNTUwMDEgPC0gZnVuY3Rpb24ocGFyYW1zX3JvdykgewogIHBhcmFtcyA8LSBsaXN0KAogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgICBldGEgPSBwYXJhbXNfcm93JGV0YSwKICAgIG1heF9kZXB0aCA9IHBhcmFtc19yb3ckbWF4X2RlcHRoLAogICAgc3Vic2FtcGxlID0gcGFyYW1zX3JvdyRzdWJzYW1wbGUsCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1zX3JvdyRjb2xzYW1wbGVfYnl0cmVlLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtc19yb3ckbWluX2NoaWxkX3dlaWdodCwKICAgIGdhbW1hID0gcGFyYW1zX3JvdyRnYW1tYQogICkKICAKICBjdiA8LSB4Z2IuY3YoCiAgICBwYXJhbXMgPSBwYXJhbXMsCiAgICBkYXRhID0gZHRyYWluXzE1NTAwMSwgICAgIyA8LSB1c2EgRE1hdHJpeCBjb24gZGF0b3NfdHJhaW5fMTU1MDAxCiAgICBucm91bmRzID0gMTAwLAogICAgbmZvbGQgPSA1LAogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAsCiAgICB2ZXJib3NlID0gMAogICkKICAKICBsaXN0KAogICAgcm1zZSA9IG1pbihjdiRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbiksCiAgICBucm91bmRzID0gd2hpY2gubWluKGN2JGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKSwKICAgIHBhcmFtcyA9IHBhcmFtcwogICkKfQpgYGAKCmBgYHtyfQojIEV2YWx1YXIgdG9kYXMgbGFzIGNvbWJpbmFjaW9uZXMKcmVzdWx0YWRvc19ncmlkXzE1NTAwMSA8LSBwYXJhbV9ncmlkX3JlZHVjaWRhCnJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEkbnJvdW5kcyA8LSBOQQpyZXN1bHRhZG9zX2dyaWRfMTU1MDAxJHJtc2UgPC0gTkEKCmNhdCgiSW5pY2lhbmRvIGLDunNxdWVkYSBkZSBoaXBlcnBhcsOhbWV0cm9zIHBhcmEgcHJvZHVjdG8gMTU1MDAxLi4uXG4iKQpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSB7CiAgY2F0KHNwcmludGYoIkV2YWx1YW5kbyBjb21iaW5hY2nDs24gJWQgZGUgJWRcbiIsIGksIG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpKQogIAogIHJlc3VsdGFkbyA8LSBldmFsdWF0ZV9wYXJhbXNfMTU1MDAxKHBhcmFtX2dyaWRfcmVkdWNpZGFbaSwgXSkKICAKICByZXN1bHRhZG9zX2dyaWRfMTU1MDAxJG5yb3VuZHNbaV0gPC0gcmVzdWx0YWRvJG5yb3VuZHMKICByZXN1bHRhZG9zX2dyaWRfMTU1MDAxJHJtc2VbaV0gPC0gcmVzdWx0YWRvJHJtc2UKfQoKIyBPcmRlbmFyIHBvciBtZWpvciBSTVNFCnJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEgPC0gcmVzdWx0YWRvc19ncmlkXzE1NTAwMVtvcmRlcihyZXN1bHRhZG9zX2dyaWRfMTU1MDAxJHJtc2UpLCBdCgojIE1vc3RyYXIgbGFzIDUgbWVqb3JlcyBjb21iaW5hY2lvbmVzCmNhdCgiXG5Ub3AgNSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3MgKFByb2R1Y3RvIDE1NTAwMSk6XG4iKQpwcmludChoZWFkKHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEsIDUpKQoKYGBgCgojIyMgRU5UUkVOQSBNT0RFTE8gQ09OIE1FSk9SRVMgSElQRVJQQVLDgU1FVFJPUyAKYGBge3J9CiMgRXh0cmFlciBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zIGRlbCBncmlkCm1lam9yZXNfcGFyYW1zXzE1NTAwMSA8LSBsaXN0KAogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICBldmFsX21ldHJpYyA9ICJybXNlIiwKICBldGEgPSByZXN1bHRhZG9zX2dyaWRfMTU1MDAxJGV0YVsxXSwKICBtYXhfZGVwdGggPSByZXN1bHRhZG9zX2dyaWRfMTU1MDAxJG1heF9kZXB0aFsxXSwKICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zX2dyaWRfMTU1MDAxJHN1YnNhbXBsZVsxXSwKICBjb2xzYW1wbGVfYnl0cmVlID0gcmVzdWx0YWRvc19ncmlkXzE1NTAwMSRjb2xzYW1wbGVfYnl0cmVlWzFdLAogIG1pbl9jaGlsZF93ZWlnaHQgPSByZXN1bHRhZG9zX2dyaWRfMTU1MDAxJG1pbl9jaGlsZF93ZWlnaHRbMV0sCiAgZ2FtbWEgPSByZXN1bHRhZG9zX2dyaWRfMTU1MDAxJGdhbW1hWzFdCikKCiMgTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhcwptZWpvcl9ucm91bmRzXzE1NTAwMSA8LSByZXN1bHRhZG9zX2dyaWRfMTU1MDAxJG5yb3VuZHNbMV0KCmNhdCgiRW50cmVuYW5kbyBtb2RlbG8gY29uIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcy4uLlxuIikKcHJpbnQobWVqb3Jlc19wYXJhbXNfMTU1MDAxKQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kc18xNTUwMDEsICJcbiIpCgojIEVudHJlbmFyIGVsIG1vZGVsbyBjb24gbG9zIGRhdG9zIGRlIHNwbGl0IGludGVybm8KbW9kZWxvX3hnYl8xNTUwMDFfc3BsaXQgPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zXzE1NTAwMSwKICBkYXRhID0gZHRyYWluXzE1NTAwMSwKICBucm91bmRzID0gbWVqb3JfbnJvdW5kc18xNTUwMDEsCiAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbl8xNTUwMDEsIHRlc3QgPSBkdGVzdF8xNTUwMDEpLAogIHZlcmJvc2UgPSAwCikKYGBgCgojIyMgTcOJVFJJQ0FTIElOVEVSTkFTCmBgYHtyfQojIE3DqXRyaWNhcyBzb2JyZSBkYXRvc190cmFpbiAoODAlKQpwcmVkX3RyYWluX3NwbGl0XzE1NTAwMSA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMTU1MDAxX3NwbGl0LCBkdHJhaW5fMTU1MDAxKQptYXBlX3RyYWluX3NwbGl0XzE1NTAwMSA8LSBtZWFuKGFicygoeV90cmFpbl8xNTUwMDEgLSBwcmVkX3RyYWluX3NwbGl0XzE1NTAwMSkgLyBwbWF4KHlfdHJhaW5fMTU1MDAxLCAwLjAxKSkpICogMTAwCnJtc2VfdHJhaW5fc3BsaXRfMTU1MDAxIDwtIHNxcnQobWVhbigoeV90cmFpbl8xNTUwMDEgLSBwcmVkX3RyYWluX3NwbGl0XzE1NTAwMSleMikpCgojIE3DqXRyaWNhcyBzb2JyZSBkYXRvc190ZXN0ICgyMCUpCnByZWRfdGVzdF9zcGxpdF8xNTUwMDEgPC0gcHJlZGljdChtb2RlbG9feGdiXzE1NTAwMV9zcGxpdCwgZHRlc3RfMTU1MDAxKQptYXBlX3Rlc3Rfc3BsaXRfMTU1MDAxIDwtIG1lYW4oYWJzKCh5X3Rlc3RfMTU1MDAxIC0gcHJlZF90ZXN0X3NwbGl0XzE1NTAwMSkgLyBwbWF4KHlfdGVzdF8xNTUwMDEsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0X3NwbGl0XzE1NTAwMSA8LSBzcXJ0KG1lYW4oKHlfdGVzdF8xNTUwMDEgLSBwcmVkX3Rlc3Rfc3BsaXRfMTU1MDAxKV4yKSkKCmNhdCgiU3BsaXQgSW50ZXJubyAtIFByb2R1Y3RvIDE1NTAwMVxuIikKY2F0KCJUcmFpbiBTcGxpdCAtIE1BUEU6Iiwgcm91bmQobWFwZV90cmFpbl9zcGxpdF8xNTUwMDEsIDIpLCAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdHJhaW5fc3BsaXRfMTU1MDAxLCAyKSwgIlxuIikKY2F0KCJUZXN0ICBTcGxpdCAtIE1BUEU6Iiwgcm91bmQobWFwZV90ZXN0X3NwbGl0XzE1NTAwMSwgMiksICAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdGVzdF9zcGxpdF8xNTUwMDEsIDIpLCAiXG4iKQpgYGAKCiMjIyBFTlRSRU5BTUlFTlRPIENPTiBEQVRPUyBDT01QTEVUT1MgCmBgYHtyfQojIFByZXBhcmFyIG1hdHJpY2VzIGNvbiB0b2RvcyBsb3MgZGF0b3MKWF9lbnRlcm9fMTU1MDAxIDwtIGFzLm1hdHJpeChkYXRvc18xNTUwMDFbLCBjb2xuYW1lcyhkYXRvc18xNTUwMDEpICE9ICJWZW50YSJdKQp5X2VudGVyb18xNTUwMDEgPC0gZGF0b3NfMTU1MDAxJFZlbnRhCmR0cmFpbl9maW5hbF8xNTUwMDEgPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfZW50ZXJvXzE1NTAwMSwgbGFiZWwgPSB5X2VudGVyb18xNTUwMDEpCgojIEVudHJlbmFyIGVsIG1vZGVsbyBmaW5hbAptb2RlbG9feGdiX2ZpbmFsXzE1NTAwMSA8LSB4Z2IudHJhaW4oCiAgcGFyYW1zID0gbWVqb3Jlc19wYXJhbXNfMTU1MDAxLAogIGRhdGEgPSBkdHJhaW5fZmluYWxfMTU1MDAxLAogIG5yb3VuZHMgPSBtZWpvcl9ucm91bmRzXzE1NTAwMSwKICB2ZXJib3NlID0gMAopCgojIEV2YWx1YWNpw7NuIGVuIGRhdG9zXzE1NTAwMQpwcmVkX2ZpbmFsX3RyYWluXzE1NTAwMSA8LSBwcmVkaWN0KG1vZGVsb194Z2JfZmluYWxfMTU1MDAxLCBYX2VudGVyb18xNTUwMDEpCm1hcGVfZmluYWxfdHJhaW5fMTU1MDAxIDwtIG1lYW4oYWJzKCh5X2VudGVyb18xNTUwMDEgLSBwcmVkX2ZpbmFsX3RyYWluXzE1NTAwMSkgLyBwbWF4KHlfZW50ZXJvXzE1NTAwMSwgMC4wMSkpKSAqIDEwMApybXNlX2ZpbmFsX3RyYWluXzE1NTAwMSA8LSBzcXJ0KG1lYW4oKHlfZW50ZXJvXzE1NTAwMSAtIHByZWRfZmluYWxfdHJhaW5fMTU1MDAxKV4yKSkKCiMgRXZhbHVhY2nDs24gZW4gdGVzdF8xNTUwMDEKWF90ZXN0X3JlYWxfMTU1MDAxIDwtIGFzLm1hdHJpeCh0ZXN0XzE1NTAwMVssIGNvbG5hbWVzKHRlc3RfMTU1MDAxKSAhPSAiVmVudGEiXSkKeV90ZXN0X3JlYWxfMTU1MDAxIDwtIHRlc3RfMTU1MDAxJFZlbnRhCnByZWRfZmluYWxfdGVzdF8xNTUwMDEgPC0gcHJlZGljdChtb2RlbG9feGdiX2ZpbmFsXzE1NTAwMSwgWF90ZXN0X3JlYWxfMTU1MDAxKQptYXBlX2ZpbmFsX3Rlc3RfMTU1MDAxIDwtIG1lYW4oYWJzKCh5X3Rlc3RfcmVhbF8xNTUwMDEgLSBwcmVkX2ZpbmFsX3Rlc3RfMTU1MDAxKSAvIHBtYXgoeV90ZXN0X3JlYWxfMTU1MDAxLCAwLjAxKSkpICogMTAwCnJtc2VfZmluYWxfdGVzdF8xNTUwMDEgPC0gc3FydChtZWFuKCh5X3Rlc3RfcmVhbF8xNTUwMDEgLSBwcmVkX2ZpbmFsX3Rlc3RfMTU1MDAxKV4yKSkKCmNhdCgiTW9kZWxvIEZpbmFsIC0gUHJvZHVjdG8gMTU1MDAxXG4iKQpjYXQoIlRyYWluIC0gTUFQRToiLCByb3VuZChtYXBlX2ZpbmFsX3RyYWluXzE1NTAwMSwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV9maW5hbF90cmFpbl8xNTUwMDEsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIC0gTUFQRToiLCByb3VuZChtYXBlX2ZpbmFsX3Rlc3RfMTU1MDAxLCAyKSwgICJ8IFJNU0U6Iiwgcm91bmQocm1zZV9maW5hbF90ZXN0XzE1NTAwMSwgMiksICJcbiIpCgpgYGAKCiMjIyBUQUJMQSBERSBNw4lUUklDQVMKYGBge3J9Cm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAxIiwKICBNb2RlbG8gPSAiWEdCb29zdCAoVHJhaW4pIiwKICBNQVBFID0gbWFwZV9maW5hbF90cmFpbl8xNTUwMDEsCiAgUk1TRSA9IHJtc2VfZmluYWxfdHJhaW5fMTU1MDAxCikpCgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMSIsCiAgTW9kZWxvID0gIlhHQm9vc3QgKFRlc3QpIiwKICBNQVBFID0gbWFwZV9maW5hbF90ZXN0XzE1NTAwMSwKICBSTVNFID0gcm1zZV9maW5hbF90ZXN0XzE1NTAwMQopKQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBQUk9EVUNUTyAzOTI5Nzg4CiMjIyBTUExJVCBJTlRFUk5PCmBgYHtyfQojIERpdmlkaXIgbG9zIGRhdG9zIGVuIGVudHJlbmFtaWVudG8gKDgwJSkgeSB2YWxpZGFjacOzbiBpbnRlcm5hICgyMCUpCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCgppbmRpY2VzX3RyYWluXzM5Mjk3ODggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkYXRvc18zOTI5Nzg4JFZlbnRhLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpCgojIENyZWFyIHN1YmNvbmp1bnRvcwpkYXRvc190cmFpbl8zOTI5Nzg4IDwtIGRhdG9zXzM5Mjk3ODhbaW5kaWNlc190cmFpbl8zOTI5Nzg4LCBdCmRhdG9zX3Rlc3RfMzkyOTc4OCAgPC0gZGF0b3NfMzkyOTc4OFstaW5kaWNlc190cmFpbl8zOTI5Nzg4LCBdCgojIE1hdHJpY2VzIHByZWRpY3RvcmFzIHkgb2JqZXRpdm8KWF90cmFpbl8zOTI5Nzg4IDwtIGFzLm1hdHJpeChkYXRvc190cmFpbl8zOTI5Nzg4WywgY29sbmFtZXMoZGF0b3NfdHJhaW5fMzkyOTc4OCkgIT0gIlZlbnRhIl0pCnlfdHJhaW5fMzkyOTc4OCA8LSBkYXRvc190cmFpbl8zOTI5Nzg4JFZlbnRhCgpYX3Rlc3RfMzkyOTc4OCA8LSBhcy5tYXRyaXgoZGF0b3NfdGVzdF8zOTI5Nzg4WywgY29sbmFtZXMoZGF0b3NfdGVzdF8zOTI5Nzg4KSAhPSAiVmVudGEiXSkKeV90ZXN0XzM5Mjk3ODggPC0gZGF0b3NfdGVzdF8zOTI5Nzg4JFZlbnRhCgojIENvbnZlcnRpciBhIGZvcm1hdG8gRE1hdHJpeCBwYXJhIFhHQm9vc3QKZHRyYWluXzM5Mjk3ODggPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfdHJhaW5fMzkyOTc4OCwgbGFiZWwgPSB5X3RyYWluXzM5Mjk3ODgpCmR0ZXN0XzM5Mjk3ODggIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3Rlc3RfMzkyOTc4OCwgbGFiZWwgPSB5X3Rlc3RfMzkyOTc4OCkKYGBgCgojIyMgQlVTUVVFREEgREUgSElQRVJQQVJBTUVUUk9TIENPTiBWQUxJREFDScOTTiBDUlVaQURBCmBgYHtyfQojIENyZWFyIHVuYSByZWppbGxhIGRlIGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvcwpwYXJhbV9ncmlkIDwtIGV4cGFuZC5ncmlkKAogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xKSwgICAgICAgICAgICAgIyBUYXNhIGRlIGFwcmVuZGl6YWplCiAgbWF4X2RlcHRoID0gYygzLCA1LCA3KSwgICAgICAgICAgICAgICAjIFByb2Z1bmRpZGFkIGRlbCDDoXJib2wKICBzdWJzYW1wbGUgPSBjKDAuOCwgMS4wKSwgICAgICAgICAgICAgICMgUHJvcG9yY2nDs24gZGUgbXVlc3RyYXMgcG9yIMOhcmJvbAogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuOCwgMS4wKSwgICAgICAgIyBQcm9wb3JjacOzbiBkZSBjb2x1bW5hcyBwb3Igw6FyYm9sCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMyksICAgICAgICAgICAjIFBlc28gbcOtbmltbyBkZSBub2RvcyBoaWpvcwogIGdhbW1hID0gYygwLCAwLjEpICAgICAgICAgICAgICAgICAgICAgIyBSZWd1bGFyaXphY2nDs24KKQoKIyBSZWR1Y2lyIGNvbWJpbmFjaW9uZXMgcGFyYSBlZmljaWVuY2lhCnNldC5zZWVkKDEyMykKcGFyYW1fZ3JpZF9yZWR1Y2lkYSA8LSBwYXJhbV9ncmlkW3NhbXBsZSgxOm5yb3cocGFyYW1fZ3JpZCksIDEwKSwgXQoKIyBGdW5jacOzbiBwYXJhIGV2YWx1YXIgY2FkYSBjb21iaW5hY2nDs24gY29uIHZhbGlkYWNpw7NuIGNydXphZGEKZXZhbHVhdGVfcGFyYW1zXzM5Mjk3ODggPC0gZnVuY3Rpb24ocGFyYW1zX3JvdykgewogIHBhcmFtcyA8LSBsaXN0KAogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgICBldGEgPSBwYXJhbXNfcm93JGV0YSwKICAgIG1heF9kZXB0aCA9IHBhcmFtc19yb3ckbWF4X2RlcHRoLAogICAgc3Vic2FtcGxlID0gcGFyYW1zX3JvdyRzdWJzYW1wbGUsCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1zX3JvdyRjb2xzYW1wbGVfYnl0cmVlLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtc19yb3ckbWluX2NoaWxkX3dlaWdodCwKICAgIGdhbW1hID0gcGFyYW1zX3JvdyRnYW1tYQogICkKICAKICBjdiA8LSB4Z2IuY3YoCiAgICBwYXJhbXMgPSBwYXJhbXMsCiAgICBkYXRhID0gZHRyYWluXzM5Mjk3ODgsICAgICMgPC0gdXNhIERNYXRyaXggY29uIGRhdG9zX3RyYWluXzE1NTAwMQogICAgbnJvdW5kcyA9IDEwMCwKICAgIG5mb2xkID0gNSwKICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDEwLAogICAgdmVyYm9zZSA9IDAKICApCiAgCiAgbGlzdCgKICAgIHJtc2UgPSBtaW4oY3YkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pLAogICAgbnJvdW5kcyA9IHdoaWNoLm1pbihjdiRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbiksCiAgICBwYXJhbXMgPSBwYXJhbXMKICApCn0KYGBgCgpgYGB7cn0KIyBFdmFsdWFyIHRvZGFzIGxhcyBjb21iaW5hY2lvbmVzCnJlc3VsdGFkb3NfZ3JpZF8zOTI5Nzg4IDwtIHBhcmFtX2dyaWRfcmVkdWNpZGEKcmVzdWx0YWRvc19ncmlkXzM5Mjk3ODgkbnJvdW5kcyA8LSBOQQpyZXN1bHRhZG9zX2dyaWRfMzkyOTc4OCRybXNlIDwtIE5BCgpjYXQoIkluaWNpYW5kbyBiw7pzcXVlZGEgZGUgaGlwZXJwYXLDoW1ldHJvcyBwYXJhIHByb2R1Y3RvIDM5Mjk3ODguLi5cbiIpCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpIHsKICBjYXQoc3ByaW50ZigiRXZhbHVhbmRvIGNvbWJpbmFjacOzbiAlZCBkZSAlZFxuIiwgaSwgbnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSkpCiAgCiAgcmVzdWx0YWRvIDwtIGV2YWx1YXRlX3BhcmFtc18zOTI5Nzg4KHBhcmFtX2dyaWRfcmVkdWNpZGFbaSwgXSkKICAKICByZXN1bHRhZG9zX2dyaWRfMzkyOTc4OCRucm91bmRzW2ldIDwtIHJlc3VsdGFkbyRucm91bmRzCiAgcmVzdWx0YWRvc19ncmlkXzM5Mjk3ODgkcm1zZVtpXSA8LSByZXN1bHRhZG8kcm1zZQp9CgojIE9yZGVuYXIgcG9yIG1lam9yIFJNU0UKcmVzdWx0YWRvc19ncmlkXzM5Mjk3ODggPC0gcmVzdWx0YWRvc19ncmlkXzM5Mjk3ODhbb3JkZXIocmVzdWx0YWRvc19ncmlkXzM5Mjk3ODgkcm1zZSksIF0KCiMgTW9zdHJhciBsYXMgNSBtZWpvcmVzIGNvbWJpbmFjaW9uZXMKY2F0KCJcblRvcCA1IGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvcyAoUHJvZHVjdG8gMzkyOTc4OCk6XG4iKQpwcmludChoZWFkKHJlc3VsdGFkb3NfZ3JpZF8zOTI5Nzg4LCA1KSkKCmBgYAoKIyMjIEVOVFJFTkEgTU9ERUxPIENPTiBNRUpPUkVTIEhJUEVSUEFSw4FNRVRST1MgCmBgYHtyfQojIEV4dHJhZXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcyBkZWwgZ3JpZAptZWpvcmVzX3BhcmFtc18zOTI5Nzg4IDwtIGxpc3QoCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogIGV2YWxfbWV0cmljID0gInJtc2UiLAogIGV0YSA9IHJlc3VsdGFkb3NfZ3JpZF8zOTI5Nzg4JGV0YVsxXSwKICBtYXhfZGVwdGggPSByZXN1bHRhZG9zX2dyaWRfMzkyOTc4OCRtYXhfZGVwdGhbMV0sCiAgc3Vic2FtcGxlID0gcmVzdWx0YWRvc19ncmlkXzM5Mjk3ODgkc3Vic2FtcGxlWzFdLAogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zX2dyaWRfMzkyOTc4OCRjb2xzYW1wbGVfYnl0cmVlWzFdLAogIG1pbl9jaGlsZF93ZWlnaHQgPSByZXN1bHRhZG9zX2dyaWRfMzkyOTc4OCRtaW5fY2hpbGRfd2VpZ2h0WzFdLAogIGdhbW1hID0gcmVzdWx0YWRvc19ncmlkXzM5Mjk3ODgkZ2FtbWFbMV0KKQoKIyBOw7ptZXJvIMOzcHRpbW8gZGUgcm9uZGFzCm1lam9yX25yb3VuZHNfMzkyOTc4OCA8LSByZXN1bHRhZG9zX2dyaWRfMzkyOTc4OCRucm91bmRzWzFdCgpjYXQoIkVudHJlbmFuZG8gbW9kZWxvIGNvbiBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MuLi5cbiIpCnByaW50KG1lam9yZXNfcGFyYW1zXzM5Mjk3ODgpCmNhdCgiTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhczoiLCBtZWpvcl9ucm91bmRzXzM5Mjk3ODgsICJcbiIpCgojIEVudHJlbmFyIGVsIG1vZGVsbyBjb24gbG9zIGRhdG9zIGRlIHNwbGl0IGludGVybm8KbW9kZWxvX3hnYl8zOTI5Nzg4X3NwbGl0IDwtIHhnYi50cmFpbigKICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtc18zOTI5Nzg4LAogIGRhdGEgPSBkdHJhaW5fMzkyOTc4OCwKICBucm91bmRzID0gbWVqb3JfbnJvdW5kc18zOTI5Nzg4LAogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW5fMzkyOTc4OCwgdGVzdCA9IGR0ZXN0XzM5Mjk3ODgpLAogIHZlcmJvc2UgPSAwCikKYGBgCgojIyMgTcOJVFJJQ0FTIElOVEVSTkFTCmBgYHtyfQojIE3DqXRyaWNhcyBzb2JyZSBkYXRvc190cmFpbiAoODAlKQpwcmVkX3RyYWluX3NwbGl0XzM5Mjk3ODggPC0gcHJlZGljdChtb2RlbG9feGdiXzM5Mjk3ODhfc3BsaXQsIGR0cmFpbl8zOTI5Nzg4KQptYXBlX3RyYWluX3NwbGl0XzM5Mjk3ODggPC0gbWVhbihhYnMoKHlfdHJhaW5fMzkyOTc4OCAtIHByZWRfdHJhaW5fc3BsaXRfMzkyOTc4OCkgLyBwbWF4KHlfdHJhaW5fMzkyOTc4OCwgMC4wMSkpKSAqIDEwMApybXNlX3RyYWluX3NwbGl0XzM5Mjk3ODggPC0gc3FydChtZWFuKCh5X3RyYWluXzM5Mjk3ODggLSBwcmVkX3RyYWluX3NwbGl0XzM5Mjk3ODgpXjIpKQoKIyBNw6l0cmljYXMgc29icmUgZGF0b3NfdGVzdCAoMjAlKQpwcmVkX3Rlc3Rfc3BsaXRfMzkyOTc4OCA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMzkyOTc4OF9zcGxpdCwgZHRlc3RfMzkyOTc4OCkKbWFwZV90ZXN0X3NwbGl0XzM5Mjk3ODggPC0gbWVhbihhYnMoKHlfdGVzdF8zOTI5Nzg4IC0gcHJlZF90ZXN0X3NwbGl0XzM5Mjk3ODgpIC8gcG1heCh5X3Rlc3RfMzkyOTc4OCwgMC4wMSkpKSAqIDEwMApybXNlX3Rlc3Rfc3BsaXRfMzkyOTc4OCA8LSBzcXJ0KG1lYW4oKHlfdGVzdF8zOTI5Nzg4IC0gcHJlZF90ZXN0X3NwbGl0XzM5Mjk3ODgpXjIpKQoKY2F0KCJTcGxpdCBJbnRlcm5vIC0gUHJvZHVjdG8gMzkyOTc4OFxuIikKY2F0KCJUcmFpbiBTcGxpdCAtIE1BUEU6Iiwgcm91bmQobWFwZV90cmFpbl9zcGxpdF8zOTI5Nzg4LCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3RyYWluX3NwbGl0XzM5Mjk3ODgsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIFNwbGl0IC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3Rfc3BsaXRfMzkyOTc4OCwgMiksICAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdGVzdF9zcGxpdF8zOTI5Nzg4LCAyKSwgIlxuIikKYGBgCgojIyMgRU5UUkVOQU1JRU5UTyBDT04gREFUT1MgQ09NUExFVE9TIApgYGB7cn0KIyBQcmVwYXJhciBtYXRyaWNlcyBjb24gdG9kb3MgbG9zIGRhdG9zClhfZW50ZXJvXzM5Mjk3ODggPC0gYXMubWF0cml4KGRhdG9zXzM5Mjk3ODhbLCBjb2xuYW1lcyhkYXRvc18zOTI5Nzg4KSAhPSAiVmVudGEiXSkKeV9lbnRlcm9fMzkyOTc4OCA8LSBkYXRvc18zOTI5Nzg4JFZlbnRhCmR0cmFpbl9maW5hbF8zOTI5Nzg4IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX2VudGVyb18zOTI5Nzg4LCBsYWJlbCA9IHlfZW50ZXJvXzM5Mjk3ODgpCgojIEVudHJlbmFyIGVsIG1vZGVsbyBmaW5hbAptb2RlbG9feGdiX2ZpbmFsXzM5Mjk3ODggPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zXzM5Mjk3ODgsCiAgZGF0YSA9IGR0cmFpbl9maW5hbF8zOTI5Nzg4LAogIG5yb3VuZHMgPSBtZWpvcl9ucm91bmRzXzM5Mjk3ODgsCiAgdmVyYm9zZSA9IDAKKQoKIyBFdmFsdWFjacOzbiBlbiBkYXRvc18zOTI5Nzg4CnByZWRfZmluYWxfdHJhaW5fMzkyOTc4OCA8LSBwcmVkaWN0KG1vZGVsb194Z2JfZmluYWxfMzkyOTc4OCwgWF9lbnRlcm9fMzkyOTc4OCkKbWFwZV9maW5hbF90cmFpbl8zOTI5Nzg4IDwtIG1lYW4oYWJzKCh5X2VudGVyb18zOTI5Nzg4IC0gcHJlZF9maW5hbF90cmFpbl8zOTI5Nzg4KSAvIHBtYXgoeV9lbnRlcm9fMzkyOTc4OCwgMC4wMSkpKSAqIDEwMApybXNlX2ZpbmFsX3RyYWluXzM5Mjk3ODggPC0gc3FydChtZWFuKCh5X2VudGVyb18zOTI5Nzg4IC0gcHJlZF9maW5hbF90cmFpbl8zOTI5Nzg4KV4yKSkKCiMgRXZhbHVhY2nDs24gZW4gdGVzdF8zOTI5Nzg4ClhfdGVzdF9yZWFsXzM5Mjk3ODggPC0gYXMubWF0cml4KHRlc3RfMzkyOTc4OFssIGNvbG5hbWVzKHRlc3RfMzkyOTc4OCkgIT0gIlZlbnRhIl0pCnlfdGVzdF9yZWFsXzM5Mjk3ODggPC0gdGVzdF8zOTI5Nzg4JFZlbnRhCnByZWRfZmluYWxfdGVzdF8zOTI5Nzg4IDwtIHByZWRpY3QobW9kZWxvX3hnYl9maW5hbF8zOTI5Nzg4LCBYX3Rlc3RfcmVhbF8zOTI5Nzg4KQptYXBlX2ZpbmFsX3Rlc3RfMzkyOTc4OCA8LSBtZWFuKGFicygoeV90ZXN0X3JlYWxfMzkyOTc4OCAtIHByZWRfZmluYWxfdGVzdF8zOTI5Nzg4KSAvIHBtYXgoeV90ZXN0X3JlYWxfMzkyOTc4OCwgMC4wMSkpKSAqIDEwMApybXNlX2ZpbmFsX3Rlc3RfMzkyOTc4OCA8LSBzcXJ0KG1lYW4oKHlfdGVzdF9yZWFsXzM5Mjk3ODggLSBwcmVkX2ZpbmFsX3Rlc3RfMzkyOTc4OCleMikpCgpjYXQoIk1vZGVsbyBGaW5hbCAtIFByb2R1Y3RvIDM5Mjk3ODhcbiIpCmNhdCgiVHJhaW4gLSBNQVBFOiIsIHJvdW5kKG1hcGVfZmluYWxfdHJhaW5fMzkyOTc4OCwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV9maW5hbF90cmFpbl8zOTI5Nzg4LCAyKSwgIlxuIikKY2F0KCJUZXN0ICAtIE1BUEU6Iiwgcm91bmQobWFwZV9maW5hbF90ZXN0XzM5Mjk3ODgsIDIpLCAgInwgUk1TRToiLCByb3VuZChybXNlX2ZpbmFsX3Rlc3RfMzkyOTc4OCwgMiksICJcbiIpCgpgYGAKCiMjIyBUQUJMQSBERSBNw4lUUklDQVMKYGBge3J9Cm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkyOTc4OCIsCiAgTW9kZWxvID0gIlhHQm9vc3QgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfZmluYWxfdHJhaW5fMzkyOTc4OCwKICBSTVNFID0gcm1zZV9maW5hbF90cmFpbl8zOTI5Nzg4CikpCgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5Mjk3ODgiLAogIE1vZGVsbyA9ICJYR0Jvb3N0IChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfZmluYWxfdGVzdF8zOTI5Nzg4LAogIFJNU0UgPSBybXNlX2ZpbmFsX3Rlc3RfMzkyOTc4OAopKQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBQUk9EVUNUTyAzOTA0MTUyCiMjIyBTUExJVCBJTlRFUk5PCmBgYHtyfQojIERpdmlkaXIgbG9zIGRhdG9zIGVuIGVudHJlbmFtaWVudG8gKDgwJSkgeSB2YWxpZGFjacOzbiBpbnRlcm5hICgyMCUpCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCgppbmRpY2VzX3RyYWluXzM5MDQxNTIgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkYXRvc18zOTA0MTUyJFZlbnRhLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpCgojIENyZWFyIHN1YmNvbmp1bnRvcwpkYXRvc190cmFpbl8zOTA0MTUyIDwtIGRhdG9zXzM5MDQxNTJbaW5kaWNlc190cmFpbl8zOTA0MTUyLCBdCmRhdG9zX3Rlc3RfMzkwNDE1MiAgPC0gZGF0b3NfMzkwNDE1MlstaW5kaWNlc190cmFpbl8zOTA0MTUyLCBdCgojIE1hdHJpY2VzIHByZWRpY3RvcmFzIHkgb2JqZXRpdm8KWF90cmFpbl8zOTA0MTUyIDwtIGFzLm1hdHJpeChkYXRvc190cmFpbl8zOTA0MTUyWywgY29sbmFtZXMoZGF0b3NfdHJhaW5fMzkwNDE1MikgIT0gIlZlbnRhIl0pCnlfdHJhaW5fMzkwNDE1MiA8LSBkYXRvc190cmFpbl8zOTA0MTUyJFZlbnRhCgpYX3Rlc3RfMzkwNDE1MiA8LSBhcy5tYXRyaXgoZGF0b3NfdGVzdF8zOTA0MTUyWywgY29sbmFtZXMoZGF0b3NfdGVzdF8zOTA0MTUyKSAhPSAiVmVudGEiXSkKeV90ZXN0XzM5MDQxNTIgPC0gZGF0b3NfdGVzdF8zOTA0MTUyJFZlbnRhCgojIENvbnZlcnRpciBhIGZvcm1hdG8gRE1hdHJpeCBwYXJhIFhHQm9vc3QKZHRyYWluXzM5MDQxNTIgPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfdHJhaW5fMzkwNDE1MiwgbGFiZWwgPSB5X3RyYWluXzM5MDQxNTIpCmR0ZXN0XzM5MDQxNTIgIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3Rlc3RfMzkwNDE1MiwgbGFiZWwgPSB5X3Rlc3RfMzkwNDE1MikKYGBgCgojIyMgQlVTUVVFREEgREUgSElQRVJQQVJBTUVUUk9TIENPTiBWQUxJREFDScOTTiBDUlVaQURBCmBgYHtyfQojIENyZWFyIHVuYSByZWppbGxhIGRlIGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvcwpwYXJhbV9ncmlkIDwtIGV4cGFuZC5ncmlkKAogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xKSwgICAgICAgICAgICAgIyBUYXNhIGRlIGFwcmVuZGl6YWplCiAgbWF4X2RlcHRoID0gYygzLCA1LCA3KSwgICAgICAgICAgICAgICAjIFByb2Z1bmRpZGFkIGRlbCDDoXJib2wKICBzdWJzYW1wbGUgPSBjKDAuOCwgMS4wKSwgICAgICAgICAgICAgICMgUHJvcG9yY2nDs24gZGUgbXVlc3RyYXMgcG9yIMOhcmJvbAogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuOCwgMS4wKSwgICAgICAgIyBQcm9wb3JjacOzbiBkZSBjb2x1bW5hcyBwb3Igw6FyYm9sCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMyksICAgICAgICAgICAjIFBlc28gbcOtbmltbyBkZSBub2RvcyBoaWpvcwogIGdhbW1hID0gYygwLCAwLjEpICAgICAgICAgICAgICAgICAgICAgIyBSZWd1bGFyaXphY2nDs24KKQoKIyBSZWR1Y2lyIGNvbWJpbmFjaW9uZXMgcGFyYSBlZmljaWVuY2lhCnNldC5zZWVkKDEyMykKcGFyYW1fZ3JpZF9yZWR1Y2lkYSA8LSBwYXJhbV9ncmlkW3NhbXBsZSgxOm5yb3cocGFyYW1fZ3JpZCksIDEwKSwgXQoKIyBGdW5jacOzbiBwYXJhIGV2YWx1YXIgY2FkYSBjb21iaW5hY2nDs24gY29uIHZhbGlkYWNpw7NuIGNydXphZGEKZXZhbHVhdGVfcGFyYW1zXzM5MDQxNTIgPC0gZnVuY3Rpb24ocGFyYW1zX3JvdykgewogIHBhcmFtcyA8LSBsaXN0KAogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgICBldGEgPSBwYXJhbXNfcm93JGV0YSwKICAgIG1heF9kZXB0aCA9IHBhcmFtc19yb3ckbWF4X2RlcHRoLAogICAgc3Vic2FtcGxlID0gcGFyYW1zX3JvdyRzdWJzYW1wbGUsCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1zX3JvdyRjb2xzYW1wbGVfYnl0cmVlLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtc19yb3ckbWluX2NoaWxkX3dlaWdodCwKICAgIGdhbW1hID0gcGFyYW1zX3JvdyRnYW1tYQogICkKICAKICBjdiA8LSB4Z2IuY3YoCiAgICBwYXJhbXMgPSBwYXJhbXMsCiAgICBkYXRhID0gZHRyYWluXzM5MDQxNTIsICAgICMgPC0gdXNhIERNYXRyaXggY29uIGRhdG9zX3RyYWluXzE1NTAwMQogICAgbnJvdW5kcyA9IDEwMCwKICAgIG5mb2xkID0gNSwKICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDEwLAogICAgdmVyYm9zZSA9IDAKICApCiAgCiAgbGlzdCgKICAgIHJtc2UgPSBtaW4oY3YkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pLAogICAgbnJvdW5kcyA9IHdoaWNoLm1pbihjdiRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbiksCiAgICBwYXJhbXMgPSBwYXJhbXMKICApCn0KYGBgCgpgYGB7cn0KIyBFdmFsdWFyIHRvZGFzIGxhcyBjb21iaW5hY2lvbmVzCnJlc3VsdGFkb3NfZ3JpZF8zOTA0MTUyIDwtIHBhcmFtX2dyaWRfcmVkdWNpZGEKcmVzdWx0YWRvc19ncmlkXzM5MDQxNTIkbnJvdW5kcyA8LSBOQQpyZXN1bHRhZG9zX2dyaWRfMzkwNDE1MiRybXNlIDwtIE5BCgpjYXQoIkluaWNpYW5kbyBiw7pzcXVlZGEgZGUgaGlwZXJwYXLDoW1ldHJvcyBwYXJhIHByb2R1Y3RvIDM5MDQxNTIuLi5cbiIpCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpIHsKICBjYXQoc3ByaW50ZigiRXZhbHVhbmRvIGNvbWJpbmFjacOzbiAlZCBkZSAlZFxuIiwgaSwgbnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSkpCiAgCiAgcmVzdWx0YWRvIDwtIGV2YWx1YXRlX3BhcmFtc18zOTA0MTUyKHBhcmFtX2dyaWRfcmVkdWNpZGFbaSwgXSkKICAKICByZXN1bHRhZG9zX2dyaWRfMzkwNDE1MiRucm91bmRzW2ldIDwtIHJlc3VsdGFkbyRucm91bmRzCiAgcmVzdWx0YWRvc19ncmlkXzM5MDQxNTIkcm1zZVtpXSA8LSByZXN1bHRhZG8kcm1zZQp9CgojIE9yZGVuYXIgcG9yIG1lam9yIFJNU0UKcmVzdWx0YWRvc19ncmlkXzM5MDQxNTIgPC0gcmVzdWx0YWRvc19ncmlkXzM5MDQxNTJbb3JkZXIocmVzdWx0YWRvc19ncmlkXzM5MDQxNTIkcm1zZSksIF0KCiMgTW9zdHJhciBsYXMgNSBtZWpvcmVzIGNvbWJpbmFjaW9uZXMKY2F0KCJcblRvcCA1IGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvcyAoUHJvZHVjdG8gMzkwNDE1Mik6XG4iKQpwcmludChoZWFkKHJlc3VsdGFkb3NfZ3JpZF8zOTA0MTUyLCA1KSkKCmBgYAoKIyMjIEVOVFJFTkEgTU9ERUxPIENPTiBNRUpPUkVTIEhJUEVSUEFSw4FNRVRST1MgCmBgYHtyfQojIEV4dHJhZXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcyBkZWwgZ3JpZAptZWpvcmVzX3BhcmFtc18zOTA0MTUyIDwtIGxpc3QoCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogIGV2YWxfbWV0cmljID0gInJtc2UiLAogIGV0YSA9IHJlc3VsdGFkb3NfZ3JpZF8zOTA0MTUyJGV0YVsxXSwKICBtYXhfZGVwdGggPSByZXN1bHRhZG9zX2dyaWRfMzkwNDE1MiRtYXhfZGVwdGhbMV0sCiAgc3Vic2FtcGxlID0gcmVzdWx0YWRvc19ncmlkXzM5MDQxNTIkc3Vic2FtcGxlWzFdLAogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zX2dyaWRfMzkwNDE1MiRjb2xzYW1wbGVfYnl0cmVlWzFdLAogIG1pbl9jaGlsZF93ZWlnaHQgPSByZXN1bHRhZG9zX2dyaWRfMzkwNDE1MiRtaW5fY2hpbGRfd2VpZ2h0WzFdLAogIGdhbW1hID0gcmVzdWx0YWRvc19ncmlkXzM5MDQxNTIkZ2FtbWFbMV0KKQoKIyBOw7ptZXJvIMOzcHRpbW8gZGUgcm9uZGFzCm1lam9yX25yb3VuZHNfMzkwNDE1MiA8LSByZXN1bHRhZG9zX2dyaWRfMzkwNDE1MiRucm91bmRzWzFdCgpjYXQoIkVudHJlbmFuZG8gbW9kZWxvIGNvbiBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MuLi5cbiIpCnByaW50KG1lam9yZXNfcGFyYW1zXzM5MDQxNTIpCmNhdCgiTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhczoiLCBtZWpvcl9ucm91bmRzXzM5MDQxNTIsICJcbiIpCgojIEVudHJlbmFyIGVsIG1vZGVsbyBjb24gbG9zIGRhdG9zIGRlIHNwbGl0IGludGVybm8KbW9kZWxvX3hnYl8zOTA0MTUyX3NwbGl0IDwtIHhnYi50cmFpbigKICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtc18zOTA0MTUyLAogIGRhdGEgPSBkdHJhaW5fMzkwNDE1MiwKICBucm91bmRzID0gbWVqb3JfbnJvdW5kc18zOTA0MTUyLAogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW5fMzkwNDE1MiwgdGVzdCA9IGR0ZXN0XzM5MDQxNTIpLAogIHZlcmJvc2UgPSAwCikKYGBgCgojIyMgTcOJVFJJQ0FTIElOVEVSTkFTCmBgYHtyfQojIE3DqXRyaWNhcyBzb2JyZSBkYXRvc190cmFpbiAoODAlKQpwcmVkX3RyYWluX3NwbGl0XzM5MDQxNTIgPC0gcHJlZGljdChtb2RlbG9feGdiXzM5MDQxNTJfc3BsaXQsIGR0cmFpbl8zOTA0MTUyKQptYXBlX3RyYWluX3NwbGl0XzM5MDQxNTIgPC0gbWVhbihhYnMoKHlfdHJhaW5fMzkwNDE1MiAtIHByZWRfdHJhaW5fc3BsaXRfMzkwNDE1MikgLyBwbWF4KHlfdHJhaW5fMzkwNDE1MiwgMC4wMSkpKSAqIDEwMApybXNlX3RyYWluX3NwbGl0XzM5MDQxNTIgPC0gc3FydChtZWFuKCh5X3RyYWluXzM5MDQxNTIgLSBwcmVkX3RyYWluX3NwbGl0XzM5MDQxNTIpXjIpKQoKIyBNw6l0cmljYXMgc29icmUgZGF0b3NfdGVzdCAoMjAlKQpwcmVkX3Rlc3Rfc3BsaXRfMzkwNDE1MiA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMzkwNDE1Ml9zcGxpdCwgZHRlc3RfMzkwNDE1MikKbWFwZV90ZXN0X3NwbGl0XzM5MDQxNTIgPC0gbWVhbihhYnMoKHlfdGVzdF8zOTA0MTUyIC0gcHJlZF90ZXN0X3NwbGl0XzM5MDQxNTIpIC8gcG1heCh5X3Rlc3RfMzkwNDE1MiwgMC4wMSkpKSAqIDEwMApybXNlX3Rlc3Rfc3BsaXRfMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKHlfdGVzdF8zOTA0MTUyIC0gcHJlZF90ZXN0X3NwbGl0XzM5MDQxNTIpXjIpKQoKY2F0KCJTcGxpdCBJbnRlcm5vIC0gUHJvZHVjdG8gMzkwNDE1MlxuIikKY2F0KCJUcmFpbiBTcGxpdCAtIE1BUEU6Iiwgcm91bmQobWFwZV90cmFpbl9zcGxpdF8zOTA0MTUyLCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3RyYWluX3NwbGl0XzM5MDQxNTIsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIFNwbGl0IC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3Rfc3BsaXRfMzkwNDE1MiwgMiksICAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdGVzdF9zcGxpdF8zOTA0MTUyLCAyKSwgIlxuIikKYGBgCgojIyMgRU5UUkVOQU1JRU5UTyBDT04gREFUT1MgQ09NUExFVE9TIApgYGB7cn0KIyBQcmVwYXJhciBtYXRyaWNlcyBjb24gdG9kb3MgbG9zIGRhdG9zClhfZW50ZXJvXzM5MDQxNTIgPC0gYXMubWF0cml4KGRhdG9zXzM5MDQxNTJbLCBjb2xuYW1lcyhkYXRvc18zOTA0MTUyKSAhPSAiVmVudGEiXSkKeV9lbnRlcm9fMzkwNDE1MiA8LSBkYXRvc18zOTA0MTUyJFZlbnRhCmR0cmFpbl9maW5hbF8zOTA0MTUyIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX2VudGVyb18zOTA0MTUyLCBsYWJlbCA9IHlfZW50ZXJvXzM5MDQxNTIpCgojIEVudHJlbmFyIGVsIG1vZGVsbyBmaW5hbAptb2RlbG9feGdiX2ZpbmFsXzM5MDQxNTIgPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zXzM5MDQxNTIsCiAgZGF0YSA9IGR0cmFpbl9maW5hbF8zOTA0MTUyLAogIG5yb3VuZHMgPSBtZWpvcl9ucm91bmRzXzM5MDQxNTIsCiAgdmVyYm9zZSA9IDAKKQoKIyBFdmFsdWFjacOzbiBlbiBkYXRvc18zOTA0MTUyCnByZWRfZmluYWxfdHJhaW5fMzkwNDE1MiA8LSBwcmVkaWN0KG1vZGVsb194Z2JfZmluYWxfMzkwNDE1MiwgWF9lbnRlcm9fMzkwNDE1MikKbWFwZV9maW5hbF90cmFpbl8zOTA0MTUyIDwtIG1lYW4oYWJzKCh5X2VudGVyb18zOTA0MTUyIC0gcHJlZF9maW5hbF90cmFpbl8zOTA0MTUyKSAvIHBtYXgoeV9lbnRlcm9fMzkwNDE1MiwgMC4wMSkpKSAqIDEwMApybXNlX2ZpbmFsX3RyYWluXzM5MDQxNTIgPC0gc3FydChtZWFuKCh5X2VudGVyb18zOTA0MTUyIC0gcHJlZF9maW5hbF90cmFpbl8zOTA0MTUyKV4yKSkKCiMgRXZhbHVhY2nDs24gZW4gdGVzdF8zOTA0MTUyClhfdGVzdF9yZWFsXzM5MDQxNTIgPC0gYXMubWF0cml4KHRlc3RfMzkwNDE1MlssIGNvbG5hbWVzKHRlc3RfMzkwNDE1MikgIT0gIlZlbnRhIl0pCnlfdGVzdF9yZWFsXzM5MDQxNTIgPC0gdGVzdF8zOTA0MTUyJFZlbnRhCnByZWRfZmluYWxfdGVzdF8zOTA0MTUyIDwtIHByZWRpY3QobW9kZWxvX3hnYl9maW5hbF8zOTA0MTUyLCBYX3Rlc3RfcmVhbF8zOTA0MTUyKQptYXBlX2ZpbmFsX3Rlc3RfMzkwNDE1MiA8LSBtZWFuKGFicygoeV90ZXN0X3JlYWxfMzkwNDE1MiAtIHByZWRfZmluYWxfdGVzdF8zOTA0MTUyKSAvIHBtYXgoeV90ZXN0X3JlYWxfMzkwNDE1MiwgMC4wMSkpKSAqIDEwMApybXNlX2ZpbmFsX3Rlc3RfMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKHlfdGVzdF9yZWFsXzM5MDQxNTIgLSBwcmVkX2ZpbmFsX3Rlc3RfMzkwNDE1MileMikpCgpjYXQoIk1vZGVsbyBGaW5hbCAtIFByb2R1Y3RvIDM5MDQxNTJcbiIpCmNhdCgiVHJhaW4gLSBNQVBFOiIsIHJvdW5kKG1hcGVfZmluYWxfdHJhaW5fMzkwNDE1MiwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV9maW5hbF90cmFpbl8zOTA0MTUyLCAyKSwgIlxuIikKY2F0KCJUZXN0ICAtIE1BUEU6Iiwgcm91bmQobWFwZV9maW5hbF90ZXN0XzM5MDQxNTIsIDIpLCAgInwgUk1TRToiLCByb3VuZChybXNlX2ZpbmFsX3Rlc3RfMzkwNDE1MiwgMiksICJcbiIpCgpgYGAKCiMjIyBUQUJMQSBERSBNw4lUUklDQVMKYGBge3J9Cm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkwNDE1MiIsCiAgTW9kZWxvID0gIlhHQm9vc3QgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfZmluYWxfdHJhaW5fMzkwNDE1MiwKICBSTVNFID0gcm1zZV9maW5hbF90cmFpbl8zOTA0MTUyCikpCgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5MDQxNTIiLAogIE1vZGVsbyA9ICJYR0Jvb3N0IChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfZmluYWxfdGVzdF8zOTA0MTUyLAogIFJNU0UgPSBybXNlX2ZpbmFsX3Rlc3RfMzkwNDE1MgopKQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogdGVhbDsiPiBQUk9EVUNUTyAxNTUwMDIKIyMjIFNQTElUIElOVEVSTk8KYGBge3J9CiMgRGl2aWRpciBsb3MgZGF0b3MgZW4gZW50cmVuYW1pZW50byAoODAlKSB5IHZhbGlkYWNpw7NuIGludGVybmEgKDIwJSkKc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQKCmluZGljZXNfdHJhaW5fMTU1MDAyIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfMTU1MDAyJFZlbnRhLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpCgojIENyZWFyIHN1YmNvbmp1bnRvcwpkYXRvc190cmFpbl8xNTUwMDIgPC0gZGF0b3NfMTU1MDAyW2luZGljZXNfdHJhaW5fMTU1MDAyLCBdCmRhdG9zX3Rlc3RfMTU1MDAyICA8LSBkYXRvc18xNTUwMDJbLWluZGljZXNfdHJhaW5fMTU1MDAyLCBdCgojIE1hdHJpY2VzIHByZWRpY3RvcmFzIHkgb2JqZXRpdm8KWF90cmFpbl8xNTUwMDIgPC0gYXMubWF0cml4KGRhdG9zX3RyYWluXzE1NTAwMlssIGNvbG5hbWVzKGRhdG9zX3RyYWluXzE1NTAwMikgIT0gIlZlbnRhIl0pCnlfdHJhaW5fMTU1MDAyIDwtIGRhdG9zX3RyYWluXzE1NTAwMiRWZW50YQoKWF90ZXN0XzE1NTAwMiA8LSBhcy5tYXRyaXgoZGF0b3NfdGVzdF8xNTUwMDJbLCBjb2xuYW1lcyhkYXRvc190ZXN0XzE1NTAwMikgIT0gIlZlbnRhIl0pCnlfdGVzdF8xNTUwMDIgPC0gZGF0b3NfdGVzdF8xNTUwMDIkVmVudGEKCiMgQ29udmVydGlyIGEgZm9ybWF0byBETWF0cml4IHBhcmEgWEdCb29zdApkdHJhaW5fMTU1MDAyIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3RyYWluXzE1NTAwMiwgbGFiZWwgPSB5X3RyYWluXzE1NTAwMikKZHRlc3RfMTU1MDAyICA8LSB4Z2IuRE1hdHJpeChkYXRhID0gWF90ZXN0XzE1NTAwMiwgbGFiZWwgPSB5X3Rlc3RfMTU1MDAyKQpgYGAKCiMjIyBCVVNRVUVEQSBERSBISVBFUlBBUkFNRVRST1MgQ09OIFZBTElEQUNJw5NOIENSVVpBREEKYGBge3J9CiMgQ3JlYXIgdW5hIHJlamlsbGEgZGUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zCnBhcmFtX2dyaWQgPC0gZXhwYW5kLmdyaWQoCiAgZXRhID0gYygwLjAxLCAwLjA1LCAwLjEpLCAgICAgICAgICAgICAjIFRhc2EgZGUgYXByZW5kaXphamUKICBtYXhfZGVwdGggPSBjKDMsIDUsIDcpLCAgICAgICAgICAgICAgICMgUHJvZnVuZGlkYWQgZGVsIMOhcmJvbAogIHN1YnNhbXBsZSA9IGMoMC44LCAxLjApLCAgICAgICAgICAgICAgIyBQcm9wb3JjacOzbiBkZSBtdWVzdHJhcyBwb3Igw6FyYm9sCiAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC44LCAxLjApLCAgICAgICAjIFByb3BvcmNpw7NuIGRlIGNvbHVtbmFzIHBvciDDoXJib2wKICBtaW5fY2hpbGRfd2VpZ2h0ID0gYygxLCAzKSwgICAgICAgICAgICMgUGVzbyBtw61uaW1vIGRlIG5vZG9zIGhpam9zCiAgZ2FtbWEgPSBjKDAsIDAuMSkgICAgICAgICAgICAgICAgICAgICAjIFJlZ3VsYXJpemFjacOzbgopCgojIFJlZHVjaXIgY29tYmluYWNpb25lcyBwYXJhIGVmaWNpZW5jaWEKc2V0LnNlZWQoMTIzKQpwYXJhbV9ncmlkX3JlZHVjaWRhIDwtIHBhcmFtX2dyaWRbc2FtcGxlKDE6bnJvdyhwYXJhbV9ncmlkKSwgMTApLCBdCgojIEZ1bmNpw7NuIHBhcmEgZXZhbHVhciBjYWRhIGNvbWJpbmFjacOzbiBjb24gdmFsaWRhY2nDs24gY3J1emFkYQpldmFsdWF0ZV9wYXJhbXNfMTU1MDAyIDwtIGZ1bmN0aW9uKHBhcmFtc19yb3cpIHsKICBwYXJhbXMgPC0gbGlzdCgKICAgIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICAgIGV2YWxfbWV0cmljID0gInJtc2UiLAogICAgZXRhID0gcGFyYW1zX3JvdyRldGEsCiAgICBtYXhfZGVwdGggPSBwYXJhbXNfcm93JG1heF9kZXB0aCwKICAgIHN1YnNhbXBsZSA9IHBhcmFtc19yb3ckc3Vic2FtcGxlLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtc19yb3ckY29sc2FtcGxlX2J5dHJlZSwKICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbXNfcm93JG1pbl9jaGlsZF93ZWlnaHQsCiAgICBnYW1tYSA9IHBhcmFtc19yb3ckZ2FtbWEKICApCiAgCiAgY3YgPC0geGdiLmN2KAogICAgcGFyYW1zID0gcGFyYW1zLAogICAgZGF0YSA9IGR0cmFpbl8xNTUwMDIsICAgICMgPC0gdXNhIERNYXRyaXggY29uIGRhdG9zX3RyYWluXzE1NTAwMQogICAgbnJvdW5kcyA9IDEwMCwKICAgIG5mb2xkID0gNSwKICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDEwLAogICAgdmVyYm9zZSA9IDAKICApCiAgCiAgbGlzdCgKICAgIHJtc2UgPSBtaW4oY3YkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pLAogICAgbnJvdW5kcyA9IHdoaWNoLm1pbihjdiRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbiksCiAgICBwYXJhbXMgPSBwYXJhbXMKICApCn0KYGBgCgpgYGB7cn0KIyBFdmFsdWFyIHRvZGFzIGxhcyBjb21iaW5hY2lvbmVzCnJlc3VsdGFkb3NfZ3JpZF8xNTUwMDIgPC0gcGFyYW1fZ3JpZF9yZWR1Y2lkYQpyZXN1bHRhZG9zX2dyaWRfMTU1MDAyJG5yb3VuZHMgPC0gTkEKcmVzdWx0YWRvc19ncmlkXzE1NTAwMiRybXNlIDwtIE5BCgpjYXQoIkluaWNpYW5kbyBiw7pzcXVlZGEgZGUgaGlwZXJwYXLDoW1ldHJvcyBwYXJhIHByb2R1Y3RvIDE1NTAwMi4uLlxuIikKZm9yIChpIGluIDE6bnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSkgewogIGNhdChzcHJpbnRmKCJFdmFsdWFuZG8gY29tYmluYWNpw7NuICVkIGRlICVkXG4iLCBpLCBucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSkKICAKICByZXN1bHRhZG8gPC0gZXZhbHVhdGVfcGFyYW1zXzE1NTAwMihwYXJhbV9ncmlkX3JlZHVjaWRhW2ksIF0pCiAgCiAgcmVzdWx0YWRvc19ncmlkXzE1NTAwMiRucm91bmRzW2ldIDwtIHJlc3VsdGFkbyRucm91bmRzCiAgcmVzdWx0YWRvc19ncmlkXzE1NTAwMiRybXNlW2ldIDwtIHJlc3VsdGFkbyRybXNlCn0KCiMgT3JkZW5hciBwb3IgbWVqb3IgUk1TRQpyZXN1bHRhZG9zX2dyaWRfMTU1MDAyIDwtIHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDJbb3JkZXIocmVzdWx0YWRvc19ncmlkXzE1NTAwMiRybXNlKSwgXQoKIyBNb3N0cmFyIGxhcyA1IG1lam9yZXMgY29tYmluYWNpb25lcwpjYXQoIlxuVG9wIDUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zIChQcm9kdWN0byAxNTUwMDIpOlxuIikKcHJpbnQoaGVhZChyZXN1bHRhZG9zX2dyaWRfMTU1MDAyLCA1KSkKCmBgYAoKIyMjIEVOVFJFTkEgTU9ERUxPIENPTiBNRUpPUkVTIEhJUEVSUEFSw4FNRVRST1MgCmBgYHtyfQojIEV4dHJhZXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcyBkZWwgZ3JpZAptZWpvcmVzX3BhcmFtc18xNTUwMDIgPC0gbGlzdCgKICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgZXRhID0gcmVzdWx0YWRvc19ncmlkXzE1NTAwMiRldGFbMV0sCiAgbWF4X2RlcHRoID0gcmVzdWx0YWRvc19ncmlkXzE1NTAwMiRtYXhfZGVwdGhbMV0sCiAgc3Vic2FtcGxlID0gcmVzdWx0YWRvc19ncmlkXzE1NTAwMiRzdWJzYW1wbGVbMV0sCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDIkY29sc2FtcGxlX2J5dHJlZVsxXSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvc19ncmlkXzE1NTAwMiRtaW5fY2hpbGRfd2VpZ2h0WzFdLAogIGdhbW1hID0gcmVzdWx0YWRvc19ncmlkXzE1NTAwMiRnYW1tYVsxXQopCgojIE7Dum1lcm8gw7NwdGltbyBkZSByb25kYXMKbWVqb3JfbnJvdW5kc18xNTUwMDIgPC0gcmVzdWx0YWRvc19ncmlkXzE1NTAwMiRucm91bmRzWzFdCgpjYXQoIkVudHJlbmFuZG8gbW9kZWxvIGNvbiBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MuLi5cbiIpCnByaW50KG1lam9yZXNfcGFyYW1zXzE1NTAwMikKY2F0KCJOw7ptZXJvIMOzcHRpbW8gZGUgcm9uZGFzOiIsIG1lam9yX25yb3VuZHNfMTU1MDAyLCAiXG4iKQoKIyBFbnRyZW5hciBlbCBtb2RlbG8gY29uIGxvcyBkYXRvcyBkZSBzcGxpdCBpbnRlcm5vCm1vZGVsb194Z2JfMTU1MDAyX3NwbGl0IDwtIHhnYi50cmFpbigKICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtc18xNTUwMDIsCiAgZGF0YSA9IGR0cmFpbl8xNTUwMDIsCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHNfMTU1MDAyLAogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW5fMTU1MDAyLCB0ZXN0ID0gZHRlc3RfMTU1MDAyKSwKICB2ZXJib3NlID0gMAopCmBgYAoKIyMjIE3DiVRSSUNBUyBJTlRFUk5BUwpgYGB7cn0KIyBNw6l0cmljYXMgc29icmUgZGF0b3NfdHJhaW4gKDgwJSkKcHJlZF90cmFpbl9zcGxpdF8xNTUwMDIgPC0gcHJlZGljdChtb2RlbG9feGdiXzE1NTAwMl9zcGxpdCwgZHRyYWluXzE1NTAwMikKbWFwZV90cmFpbl9zcGxpdF8xNTUwMDIgPC0gbWVhbihhYnMoKHlfdHJhaW5fMTU1MDAyIC0gcHJlZF90cmFpbl9zcGxpdF8xNTUwMDIpIC8gcG1heCh5X3RyYWluXzE1NTAwMiwgMC4wMSkpKSAqIDEwMApybXNlX3RyYWluX3NwbGl0XzE1NTAwMiA8LSBzcXJ0KG1lYW4oKHlfdHJhaW5fMTU1MDAyIC0gcHJlZF90cmFpbl9zcGxpdF8xNTUwMDIpXjIpKQoKIyBNw6l0cmljYXMgc29icmUgZGF0b3NfdGVzdCAoMjAlKQpwcmVkX3Rlc3Rfc3BsaXRfMTU1MDAyIDwtIHByZWRpY3QobW9kZWxvX3hnYl8xNTUwMDJfc3BsaXQsIGR0ZXN0XzE1NTAwMikKbWFwZV90ZXN0X3NwbGl0XzE1NTAwMiA8LSBtZWFuKGFicygoeV90ZXN0XzE1NTAwMiAtIHByZWRfdGVzdF9zcGxpdF8xNTUwMDIpIC8gcG1heCh5X3Rlc3RfMTU1MDAyLCAwLjAxKSkpICogMTAwCnJtc2VfdGVzdF9zcGxpdF8xNTUwMDIgPC0gc3FydChtZWFuKCh5X3Rlc3RfMTU1MDAyIC0gcHJlZF90ZXN0X3NwbGl0XzE1NTAwMileMikpCgpjYXQoIlNwbGl0IEludGVybm8gLSBQcm9kdWN0byAxNTUwMDJcbiIpCmNhdCgiVHJhaW4gU3BsaXQgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdHJhaW5fc3BsaXRfMTU1MDAyLCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3RyYWluX3NwbGl0XzE1NTAwMiwgMiksICJcbiIpCmNhdCgiVGVzdCAgU3BsaXQgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdGVzdF9zcGxpdF8xNTUwMDIsIDIpLCAgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3Rfc3BsaXRfMTU1MDAyLCAyKSwgIlxuIikKYGBgCgojIyMgRU5UUkVOQU1JRU5UTyBDT04gREFUT1MgQ09NUExFVE9TIApgYGB7cn0KIyBQcmVwYXJhciBtYXRyaWNlcyBjb24gdG9kb3MgbG9zIGRhdG9zClhfZW50ZXJvXzE1NTAwMiA8LSBhcy5tYXRyaXgoZGF0b3NfMTU1MDAyWywgY29sbmFtZXMoZGF0b3NfMTU1MDAyKSAhPSAiVmVudGEiXSkKeV9lbnRlcm9fMTU1MDAyIDwtIGRhdG9zXzE1NTAwMiRWZW50YQpkdHJhaW5fZmluYWxfMTU1MDAyIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX2VudGVyb18xNTUwMDIsIGxhYmVsID0geV9lbnRlcm9fMTU1MDAyKQoKIyBFbnRyZW5hciBlbCBtb2RlbG8gZmluYWwKbW9kZWxvX3hnYl9maW5hbF8xNTUwMDIgPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zXzE1NTAwMiwKICBkYXRhID0gZHRyYWluX2ZpbmFsXzE1NTAwMiwKICBucm91bmRzID0gbWVqb3JfbnJvdW5kc18xNTUwMDIsCiAgdmVyYm9zZSA9IDAKKQoKIyBFdmFsdWFjacOzbiBlbiBkYXRvc18xNTUwMDIKcHJlZF9maW5hbF90cmFpbl8xNTUwMDIgPC0gcHJlZGljdChtb2RlbG9feGdiX2ZpbmFsXzE1NTAwMiwgWF9lbnRlcm9fMTU1MDAyKQptYXBlX2ZpbmFsX3RyYWluXzE1NTAwMiA8LSBtZWFuKGFicygoeV9lbnRlcm9fMTU1MDAyIC0gcHJlZF9maW5hbF90cmFpbl8xNTUwMDIpIC8gcG1heCh5X2VudGVyb18xNTUwMDIsIDAuMDEpKSkgKiAxMDAKcm1zZV9maW5hbF90cmFpbl8xNTUwMDIgPC0gc3FydChtZWFuKCh5X2VudGVyb18xNTUwMDIgLSBwcmVkX2ZpbmFsX3RyYWluXzE1NTAwMileMikpCgojIEV2YWx1YWNpw7NuIGVuIHRlc3RfMTU1MDAyClhfdGVzdF9yZWFsXzE1NTAwMiA8LSBhcy5tYXRyaXgodGVzdF8xNTUwMDJbLCBjb2xuYW1lcyh0ZXN0XzE1NTAwMikgIT0gIlZlbnRhIl0pCnlfdGVzdF9yZWFsXzE1NTAwMiA8LSB0ZXN0XzE1NTAwMiRWZW50YQpwcmVkX2ZpbmFsX3Rlc3RfMTU1MDAyIDwtIHByZWRpY3QobW9kZWxvX3hnYl9maW5hbF8xNTUwMDIsIFhfdGVzdF9yZWFsXzE1NTAwMikKbWFwZV9maW5hbF90ZXN0XzE1NTAwMiA8LSBtZWFuKGFicygoeV90ZXN0X3JlYWxfMTU1MDAyIC0gcHJlZF9maW5hbF90ZXN0XzE1NTAwMikgLyBwbWF4KHlfdGVzdF9yZWFsXzE1NTAwMiwgMC4wMSkpKSAqIDEwMApybXNlX2ZpbmFsX3Rlc3RfMTU1MDAyIDwtIHNxcnQobWVhbigoeV90ZXN0X3JlYWxfMTU1MDAyIC0gcHJlZF9maW5hbF90ZXN0XzE1NTAwMileMikpCgpjYXQoIk1vZGVsbyBGaW5hbCAtIFByb2R1Y3RvIDE1NTAwMlxuIikKY2F0KCJUcmFpbiAtIE1BUEU6Iiwgcm91bmQobWFwZV9maW5hbF90cmFpbl8xNTUwMDIsIDIpLCAifCBSTVNFOiIsIHJvdW5kKHJtc2VfZmluYWxfdHJhaW5fMTU1MDAyLCAyKSwgIlxuIikKY2F0KCJUZXN0ICAtIE1BUEU6Iiwgcm91bmQobWFwZV9maW5hbF90ZXN0XzE1NTAwMiwgMiksICAifCBSTVNFOiIsIHJvdW5kKHJtc2VfZmluYWxfdGVzdF8xNTUwMDIsIDIpLCAiXG4iKQoKYGBgCgojIyMgVEFCTEEgREUgTcOJVFJJQ0FTCmBgYHtyfQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMiIsCiAgTW9kZWxvID0gIlhHQm9vc3QgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfZmluYWxfdHJhaW5fMTU1MDAyLAogIFJNU0UgPSBybXNlX2ZpbmFsX3RyYWluXzE1NTAwMgopKQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIxNTUwMDIiLAogIE1vZGVsbyA9ICJYR0Jvb3N0IChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfZmluYWxfdGVzdF8xNTUwMDIsCiAgUk1TRSA9IHJtc2VfZmluYWxfdGVzdF8xNTUwMDIKKSkKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHRlYWw7Ij4gUFJPRFVDVE8gMzY3ODA1NQojIyMgU1BMSVQgSU5URVJOTwpgYGB7cn0KIyBEaXZpZGlyIGxvcyBkYXRvcyBlbiBlbnRyZW5hbWllbnRvICg4MCUpIHkgdmFsaWRhY2nDs24gaW50ZXJuYSAoMjAlKQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAoKaW5kaWNlc190cmFpbl8zNjc4MDU1IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfMzY3ODA1NSRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQoKIyBDcmVhciBzdWJjb25qdW50b3MKZGF0b3NfdHJhaW5fMzY3ODA1NSA8LSBkYXRvc18zNjc4MDU1W2luZGljZXNfdHJhaW5fMzY3ODA1NSwgXQpkYXRvc190ZXN0XzM2NzgwNTUgIDwtIGRhdG9zXzM2NzgwNTVbLWluZGljZXNfdHJhaW5fMzY3ODA1NSwgXQoKIyBNYXRyaWNlcyBwcmVkaWN0b3JhcyB5IG9iamV0aXZvClhfdHJhaW5fMzY3ODA1NSA8LSBhcy5tYXRyaXgoZGF0b3NfdHJhaW5fMzY3ODA1NVssIGNvbG5hbWVzKGRhdG9zX3RyYWluXzM2NzgwNTUpICE9ICJWZW50YSJdKQp5X3RyYWluXzM2NzgwNTUgPC0gZGF0b3NfdHJhaW5fMzY3ODA1NSRWZW50YQoKWF90ZXN0XzM2NzgwNTUgPC0gYXMubWF0cml4KGRhdG9zX3Rlc3RfMzY3ODA1NVssIGNvbG5hbWVzKGRhdG9zX3Rlc3RfMzY3ODA1NSkgIT0gIlZlbnRhIl0pCnlfdGVzdF8zNjc4MDU1IDwtIGRhdG9zX3Rlc3RfMzY3ODA1NSRWZW50YQoKIyBDb252ZXJ0aXIgYSBmb3JtYXRvIERNYXRyaXggcGFyYSBYR0Jvb3N0CmR0cmFpbl8zNjc4MDU1IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3RyYWluXzM2NzgwNTUsIGxhYmVsID0geV90cmFpbl8zNjc4MDU1KQpkdGVzdF8zNjc4MDU1ICA8LSB4Z2IuRE1hdHJpeChkYXRhID0gWF90ZXN0XzM2NzgwNTUsIGxhYmVsID0geV90ZXN0XzM2NzgwNTUpCmBgYAoKIyMjIEJVU1FVRURBIERFIEhJUEVSUEFSQU1FVFJPUyBDT04gVkFMSURBQ0nDk04gQ1JVWkFEQQpgYGB7cn0KIyBDcmVhciB1bmEgcmVqaWxsYSBkZSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3MKcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgKICBldGEgPSBjKDAuMDEsIDAuMDUsIDAuMSksICAgICAgICAgICAgICMgVGFzYSBkZSBhcHJlbmRpemFqZQogIG1heF9kZXB0aCA9IGMoMywgNSwgNyksICAgICAgICAgICAgICAgIyBQcm9mdW5kaWRhZCBkZWwgw6FyYm9sCiAgc3Vic2FtcGxlID0gYygwLjgsIDEuMCksICAgICAgICAgICAgICAjIFByb3BvcmNpw7NuIGRlIG11ZXN0cmFzIHBvciDDoXJib2wKICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjgsIDEuMCksICAgICAgICMgUHJvcG9yY2nDs24gZGUgY29sdW1uYXMgcG9yIMOhcmJvbAogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMpLCAgICAgICAgICAgIyBQZXNvIG3DrW5pbW8gZGUgbm9kb3MgaGlqb3MKICBnYW1tYSA9IGMoMCwgMC4xKSAgICAgICAgICAgICAgICAgICAgICMgUmVndWxhcml6YWNpw7NuCikKCiMgUmVkdWNpciBjb21iaW5hY2lvbmVzIHBhcmEgZWZpY2llbmNpYQpzZXQuc2VlZCgxMjMpCnBhcmFtX2dyaWRfcmVkdWNpZGEgPC0gcGFyYW1fZ3JpZFtzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAxMCksIF0KCiMgRnVuY2nDs24gcGFyYSBldmFsdWFyIGNhZGEgY29tYmluYWNpw7NuIGNvbiB2YWxpZGFjacOzbiBjcnV6YWRhCmV2YWx1YXRlX3BhcmFtc18zNjc4MDU1IDwtIGZ1bmN0aW9uKHBhcmFtc19yb3cpIHsKICBwYXJhbXMgPC0gbGlzdCgKICAgIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICAgIGV2YWxfbWV0cmljID0gInJtc2UiLAogICAgZXRhID0gcGFyYW1zX3JvdyRldGEsCiAgICBtYXhfZGVwdGggPSBwYXJhbXNfcm93JG1heF9kZXB0aCwKICAgIHN1YnNhbXBsZSA9IHBhcmFtc19yb3ckc3Vic2FtcGxlLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtc19yb3ckY29sc2FtcGxlX2J5dHJlZSwKICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbXNfcm93JG1pbl9jaGlsZF93ZWlnaHQsCiAgICBnYW1tYSA9IHBhcmFtc19yb3ckZ2FtbWEKICApCiAgCiAgY3YgPC0geGdiLmN2KAogICAgcGFyYW1zID0gcGFyYW1zLAogICAgZGF0YSA9IGR0cmFpbl8zNjc4MDU1LCAgICAjIDwtIHVzYSBETWF0cml4IGNvbiBkYXRvc190cmFpbl8xNTUwMDEKICAgIG5yb3VuZHMgPSAxMDAsCiAgICBuZm9sZCA9IDUsCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAxMCwKICAgIHZlcmJvc2UgPSAwCiAgKQogIAogIGxpc3QoCiAgICBybXNlID0gbWluKGN2JGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKSwKICAgIG5yb3VuZHMgPSB3aGljaC5taW4oY3YkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pLAogICAgcGFyYW1zID0gcGFyYW1zCiAgKQp9CmBgYAoKYGBge3J9CiMgRXZhbHVhciB0b2RhcyBsYXMgY29tYmluYWNpb25lcwpyZXN1bHRhZG9zX2dyaWRfMzY3ODA1NSA8LSBwYXJhbV9ncmlkX3JlZHVjaWRhCnJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1JG5yb3VuZHMgPC0gTkEKcmVzdWx0YWRvc19ncmlkXzM2NzgwNTUkcm1zZSA8LSBOQQoKY2F0KCJJbmljaWFuZG8gYsO6c3F1ZWRhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBwcm9kdWN0byAzNjc4MDU1Li4uXG4iKQpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSB7CiAgY2F0KHNwcmludGYoIkV2YWx1YW5kbyBjb21iaW5hY2nDs24gJWQgZGUgJWRcbiIsIGksIG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpKQogIAogIHJlc3VsdGFkbyA8LSBldmFsdWF0ZV9wYXJhbXNfMzY3ODA1NShwYXJhbV9ncmlkX3JlZHVjaWRhW2ksIF0pCiAgCiAgcmVzdWx0YWRvc19ncmlkXzM2NzgwNTUkbnJvdW5kc1tpXSA8LSByZXN1bHRhZG8kbnJvdW5kcwogIHJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1JHJtc2VbaV0gPC0gcmVzdWx0YWRvJHJtc2UKfQoKIyBPcmRlbmFyIHBvciBtZWpvciBSTVNFCnJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1IDwtIHJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1W29yZGVyKHJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1JHJtc2UpLCBdCgojIE1vc3RyYXIgbGFzIDUgbWVqb3JlcyBjb21iaW5hY2lvbmVzCmNhdCgiXG5Ub3AgNSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3MgKFByb2R1Y3RvIDM2NzgwNTUpOlxuIikKcHJpbnQoaGVhZChyZXN1bHRhZG9zX2dyaWRfMzY3ODA1NSwgNSkpCgpgYGAKCiMjIyBFTlRSRU5BIE1PREVMTyBDT04gTUVKT1JFUyBISVBFUlBBUsOBTUVUUk9TIApgYGB7cn0KIyBFeHRyYWVyIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MgZGVsIGdyaWQKbWVqb3Jlc19wYXJhbXNfMzY3ODA1NSA8LSBsaXN0KAogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICBldmFsX21ldHJpYyA9ICJybXNlIiwKICBldGEgPSByZXN1bHRhZG9zX2dyaWRfMzY3ODA1NSRldGFbMV0sCiAgbWF4X2RlcHRoID0gcmVzdWx0YWRvc19ncmlkXzM2NzgwNTUkbWF4X2RlcHRoWzFdLAogIHN1YnNhbXBsZSA9IHJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1JHN1YnNhbXBsZVsxXSwKICBjb2xzYW1wbGVfYnl0cmVlID0gcmVzdWx0YWRvc19ncmlkXzM2NzgwNTUkY29sc2FtcGxlX2J5dHJlZVsxXSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvc19ncmlkXzM2NzgwNTUkbWluX2NoaWxkX3dlaWdodFsxXSwKICBnYW1tYSA9IHJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1JGdhbW1hWzFdCikKCiMgTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhcwptZWpvcl9ucm91bmRzXzM2NzgwNTUgPC0gcmVzdWx0YWRvc19ncmlkXzM2NzgwNTUkbnJvdW5kc1sxXQoKY2F0KCJFbnRyZW5hbmRvIG1vZGVsbyBjb24gbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zLi4uXG4iKQpwcmludChtZWpvcmVzX3BhcmFtc18zNjc4MDU1KQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kc18zNjc4MDU1LCAiXG4iKQoKIyBFbnRyZW5hciBlbCBtb2RlbG8gY29uIGxvcyBkYXRvcyBkZSBzcGxpdCBpbnRlcm5vCm1vZGVsb194Z2JfMzY3ODA1NV9zcGxpdCA8LSB4Z2IudHJhaW4oCiAgcGFyYW1zID0gbWVqb3Jlc19wYXJhbXNfMzY3ODA1NSwKICBkYXRhID0gZHRyYWluXzM2NzgwNTUsCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHNfMzY3ODA1NSwKICB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gZHRyYWluXzM2NzgwNTUsIHRlc3QgPSBkdGVzdF8zNjc4MDU1KSwKICB2ZXJib3NlID0gMAopCmBgYAoKIyMjIE3DiVRSSUNBUyBJTlRFUk5BUwpgYGB7cn0KIyBNw6l0cmljYXMgc29icmUgZGF0b3NfdHJhaW4gKDgwJSkKcHJlZF90cmFpbl9zcGxpdF8zNjc4MDU1IDwtIHByZWRpY3QobW9kZWxvX3hnYl8zNjc4MDU1X3NwbGl0LCBkdHJhaW5fMzY3ODA1NSkKbWFwZV90cmFpbl9zcGxpdF8zNjc4MDU1IDwtIG1lYW4oYWJzKCh5X3RyYWluXzM2NzgwNTUgLSBwcmVkX3RyYWluX3NwbGl0XzM2NzgwNTUpIC8gcG1heCh5X3RyYWluXzM2NzgwNTUsIDAuMDEpKSkgKiAxMDAKcm1zZV90cmFpbl9zcGxpdF8zNjc4MDU1IDwtIHNxcnQobWVhbigoeV90cmFpbl8zNjc4MDU1IC0gcHJlZF90cmFpbl9zcGxpdF8zNjc4MDU1KV4yKSkKCiMgTcOpdHJpY2FzIHNvYnJlIGRhdG9zX3Rlc3QgKDIwJSkKcHJlZF90ZXN0X3NwbGl0XzM2NzgwNTUgPC0gcHJlZGljdChtb2RlbG9feGdiXzM2NzgwNTVfc3BsaXQsIGR0ZXN0XzM2NzgwNTUpCm1hcGVfdGVzdF9zcGxpdF8zNjc4MDU1IDwtIG1lYW4oYWJzKCh5X3Rlc3RfMzY3ODA1NSAtIHByZWRfdGVzdF9zcGxpdF8zNjc4MDU1KSAvIHBtYXgoeV90ZXN0XzM2NzgwNTUsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0X3NwbGl0XzM2NzgwNTUgPC0gc3FydChtZWFuKCh5X3Rlc3RfMzY3ODA1NSAtIHByZWRfdGVzdF9zcGxpdF8zNjc4MDU1KV4yKSkKCmNhdCgiU3BsaXQgSW50ZXJubyAtIFByb2R1Y3RvIDM2NzgwNTVcbiIpCmNhdCgiVHJhaW4gU3BsaXQgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdHJhaW5fc3BsaXRfMzY3ODA1NSwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90cmFpbl9zcGxpdF8zNjc4MDU1LCAyKSwgIlxuIikKY2F0KCJUZXN0ICBTcGxpdCAtIE1BUEU6Iiwgcm91bmQobWFwZV90ZXN0X3NwbGl0XzM2NzgwNTUsIDIpLCAgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3Rfc3BsaXRfMzY3ODA1NSwgMiksICJcbiIpCmBgYAoKIyMjIEVOVFJFTkFNSUVOVE8gQ09OIERBVE9TIENPTVBMRVRPUyAKYGBge3J9CiMgUHJlcGFyYXIgbWF0cmljZXMgY29uIHRvZG9zIGxvcyBkYXRvcwpYX2VudGVyb18zNjc4MDU1IDwtIGFzLm1hdHJpeChkYXRvc18zNjc4MDU1WywgY29sbmFtZXMoZGF0b3NfMzY3ODA1NSkgIT0gIlZlbnRhIl0pCnlfZW50ZXJvXzM2NzgwNTUgPC0gZGF0b3NfMzY3ODA1NSRWZW50YQpkdHJhaW5fZmluYWxfMzY3ODA1NSA8LSB4Z2IuRE1hdHJpeChkYXRhID0gWF9lbnRlcm9fMzY3ODA1NSwgbGFiZWwgPSB5X2VudGVyb18zNjc4MDU1KQoKIyBFbnRyZW5hciBlbCBtb2RlbG8gZmluYWwKbW9kZWxvX3hnYl9maW5hbF8zNjc4MDU1IDwtIHhnYi50cmFpbigKICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtc18zNjc4MDU1LAogIGRhdGEgPSBkdHJhaW5fZmluYWxfMzY3ODA1NSwKICBucm91bmRzID0gbWVqb3JfbnJvdW5kc18zNjc4MDU1LAogIHZlcmJvc2UgPSAwCikKCiMgRXZhbHVhY2nDs24gZW4gZGF0b3NfMzY3ODA1NQpwcmVkX2ZpbmFsX3RyYWluXzM2NzgwNTUgPC0gcHJlZGljdChtb2RlbG9feGdiX2ZpbmFsXzM2NzgwNTUsIFhfZW50ZXJvXzM2NzgwNTUpCm1hcGVfZmluYWxfdHJhaW5fMzY3ODA1NSA8LSBtZWFuKGFicygoeV9lbnRlcm9fMzY3ODA1NSAtIHByZWRfZmluYWxfdHJhaW5fMzY3ODA1NSkgLyBwbWF4KHlfZW50ZXJvXzM2NzgwNTUsIDAuMDEpKSkgKiAxMDAKcm1zZV9maW5hbF90cmFpbl8zNjc4MDU1IDwtIHNxcnQobWVhbigoeV9lbnRlcm9fMzY3ODA1NSAtIHByZWRfZmluYWxfdHJhaW5fMzY3ODA1NSleMikpCgojIEV2YWx1YWNpw7NuIGVuIHRlc3RfMzY3ODA1NQpYX3Rlc3RfcmVhbF8zNjc4MDU1IDwtIGFzLm1hdHJpeCh0ZXN0XzM2NzgwNTVbLCBjb2xuYW1lcyh0ZXN0XzM2NzgwNTUpICE9ICJWZW50YSJdKQp5X3Rlc3RfcmVhbF8zNjc4MDU1IDwtIHRlc3RfMzY3ODA1NSRWZW50YQpwcmVkX2ZpbmFsX3Rlc3RfMzY3ODA1NSA8LSBwcmVkaWN0KG1vZGVsb194Z2JfZmluYWxfMzY3ODA1NSwgWF90ZXN0X3JlYWxfMzY3ODA1NSkKbWFwZV9maW5hbF90ZXN0XzM2NzgwNTUgPC0gbWVhbihhYnMoKHlfdGVzdF9yZWFsXzM2NzgwNTUgLSBwcmVkX2ZpbmFsX3Rlc3RfMzY3ODA1NSkgLyBwbWF4KHlfdGVzdF9yZWFsXzM2NzgwNTUsIDAuMDEpKSkgKiAxMDAKcm1zZV9maW5hbF90ZXN0XzM2NzgwNTUgPC0gc3FydChtZWFuKCh5X3Rlc3RfcmVhbF8zNjc4MDU1IC0gcHJlZF9maW5hbF90ZXN0XzM2NzgwNTUpXjIpKQoKY2F0KCJNb2RlbG8gRmluYWwgLSBQcm9kdWN0byAzNjc4MDU1XG4iKQpjYXQoIlRyYWluIC0gTUFQRToiLCByb3VuZChtYXBlX2ZpbmFsX3RyYWluXzM2NzgwNTUsIDIpLCAifCBSTVNFOiIsIHJvdW5kKHJtc2VfZmluYWxfdHJhaW5fMzY3ODA1NSwgMiksICJcbiIpCmNhdCgiVGVzdCAgLSBNQVBFOiIsIHJvdW5kKG1hcGVfZmluYWxfdGVzdF8zNjc4MDU1LCAyKSwgICJ8IFJNU0U6Iiwgcm91bmQocm1zZV9maW5hbF90ZXN0XzM2NzgwNTUsIDIpLCAiXG4iKQoKYGBgCgojIyMgVEFCTEEgREUgTcOJVFJJQ0FTCmBgYHtyfQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM2NzgwNTUiLAogIE1vZGVsbyA9ICJYR0Jvb3N0IChUcmFpbikiLAogIE1BUEUgPSBtYXBlX2ZpbmFsX3RyYWluXzM2NzgwNTUsCiAgUk1TRSA9IHJtc2VfZmluYWxfdHJhaW5fMzY3ODA1NQopKQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzNjc4MDU1IiwKICBNb2RlbG8gPSAiWEdCb29zdCAoVGVzdCkiLAogIE1BUEUgPSBtYXBlX2ZpbmFsX3Rlc3RfMzY3ODA1NSwKICBSTVNFID0gcm1zZV9maW5hbF90ZXN0XzM2NzgwNTUKKSkKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHRlYWw7Ij4gTcOJVFJJQ0FTIEFSTUEsIFJFRyBMSU5FQUwsIFJBTkRPTSBGT1JFU1QgWSBYR0JPT1NUCmBgYHtyfQptZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lCiAgYXJyYW5nZShQcm9kdWN0bykgJT4lCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSAiTcOpdHJpY2FzIEFSTUEsIFJlZ3Jlc2lvbiBMaW5lYWwsIFJhbmRvbSBGb3Jlc3QgeSBYR0Jvb3N0IHBvciBwcm9kdWN0byIpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCgojIDxzcGFuIHN0eWxlPSJjb2xvcjogYmxhY2s7Ij4gVklTVUFMSVpBQ0nDk04gREUgTcOJVFJJQ0FTCmBgYHtyfQojIENvbG9yZXMgcGVyc29uYWxpemFkb3MgcG9yIHRpcG8gZGUgbW9kZWxvIGJhc2UKY29sb3Jlc19tb2RlbG9zIDwtIGMoCiAgIkFSTUEiID0gIiMxZjc3YjQiLAogICJBUk1BIChUZXN0KSIgPSAiI2FlYzdlOCIsCiAgIlJlZ3Jlc2nDs24gTGluZWFsIChUcmFpbikiID0gIiNmZjdmMGUiLAogICJSZWdyZXNpw7NuIExpbmVhbCAoVGVzdCkiID0gIiNmZmJiNzgiLAogICJSYW5kb20gRm9yZXN0IChUcmFpbikiID0gIiMyY2EwMmMiLAogICJSYW5kb20gRm9yZXN0IChUZXN0KSIgPSAiIzk4ZGY4YSIsCiAgIlhHQm9vc3QgKFRyYWluKSIgPSAiI2Q2MjcyOCIsCiAgIlhHQm9vc3QgKFRlc3QpIiA9ICIjZmY5ODk2IgopCmBgYApgYGB7cn0KIyBPYnRlbmVyIHRvZG9zIGxvcyBwcm9kdWN0b3Mgw7puaWNvcwpwcm9kdWN0b3MgPC0gdW5pcXVlKG1ldHJpY2FzX2NvbXBhcmF0aXZhcyRQcm9kdWN0bykKCiMgQ3JlYXIgZ3LDoWZpY29zIHBvciBwcm9kdWN0bwpmb3IgKHByb2QgaW4gcHJvZHVjdG9zKSB7CiAgZGF0b3NfcHJvZCA8LSBtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSBwcm9kKQogIAogICMgR3LDoWZpY28gZGUgTUFQRQogIHByaW50KAogICAgZ2dwbG90KGRhdG9zX3Byb2QsIGFlcyh4ID0gTW9kZWxvLCB5ID0gTUFQRSwgZmlsbCA9IE1vZGVsbykpICsKICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICAgICAgbGFicygKICAgICAgICB0aXRsZSA9IHBhc3RlKCJNQVBFIHBvciBtb2RlbG8gLSBQcm9kdWN0byIsIHByb2QpLAogICAgICAgIHggPSAiIiwgeSA9ICJNQVBFICglKSIKICAgICAgKSArCiAgICAgIHRoZW1lX21pbmltYWwoKSArCiAgICAgIHRoZW1lKAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgICAgICkgKwogICAgICB5bGltKDAsIG1heChkYXRvc19wcm9kJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpCiAgKQogIAogICMgR3LDoWZpY28gZGUgUk1TRQogIHByaW50KAogICAgZ2dwbG90KGRhdG9zX3Byb2QsIGFlcyh4ID0gTW9kZWxvLCB5ID0gUk1TRSwgZmlsbCA9IE1vZGVsbykpICsKICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChSTVNFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICAgICAgbGFicygKICAgICAgICB0aXRsZSA9IHBhc3RlKCJSTVNFIHBvciBtb2RlbG8gLSBQcm9kdWN0byIsIHByb2QpLAogICAgICAgIHggPSAiIiwgeSA9ICJSTVNFIgogICAgICApICsKICAgICAgdGhlbWVfbWluaW1hbCgpICsKICAgICAgdGhlbWUoCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICAgICAgKSArCiAgICAgIHlsaW0oMCwgbWF4KGRhdG9zX3Byb2QkUk1TRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkKICApCn0KYGBgCgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsYWNrOyI+IEVTVElNQUNJw5NOIERFIFBSRUNJT1MKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiB0ZWFsOyI+IFBSRVBBUkFDScOTTiBERSBEQVRPUwpgYGB7cn0KcHJlcGFyZV9wcmljZV9kYXRhIDwtIGZ1bmN0aW9uKGRmLCBwcm9kdWN0X2lkKSB7CiAgcHJvZHVjdF9kYXRhIDwtIGRmICU+JQogICAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkgJT4lCiAgICBhcnJhbmdlKFRyeF9GZWNoYSkgJT4lCiAgICBzZWxlY3QoCiAgICAgIFNlbWFuYSwKICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLAogICAgICBWZW50YV9TZW1hbmFfQW50ZXJpb3IsCiAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yLAogICAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLAogICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IKICAgICkKCiAgcmV0dXJuKHByb2R1Y3RfZGF0YSkKfQoKIyBBcGxpY2FyIGxhIGZ1bmNpw7NuIGEgdG9kb3MgbG9zIHByb2R1Y3RvcwppZHMgPC0gdW5pcXVlKGRhdG9zJElEX0ludmVudGFyaW8pCgpwcm9kdWN0b3NfcHJlcGFyYWRvcyA8LSBtYXBfZGYoaWRzLCBmdW5jdGlvbihpZCkgewogIHByZXBhcmVfcHJpY2VfZGF0YShkYXRvcywgaWQpCn0pCgpoZWFkKHByb2R1Y3Rvc19wcmVwYXJhZG9zKQoKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHRlYWw7Ij4gUFJFQ0lPUyBTSU1VTEFET1MKYGBge3J9CnNpbXVsYXJfZXNjZW5hcmlvc19wcmVjaW8gPC0gZnVuY3Rpb24oZGYsIHByb2R1Y3RfaWQsIG4gPSAyMCwgc2VtYW5hc19hX3NpbXVsYXIgPSA4KSB7CiAgIyBGaWx0cmFyIGRhdG9zIGRlbCBwcm9kdWN0bwogIGRhdGFfcHJvZHVjdG8gPC0gZGYgJT4lIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2R1Y3RfaWQpCgogICMgQ2FsY3VsYXIgcmFuZ28gaGlzdMOzcmljbyBkZSBwcmVjaW9zIChwZXJjZW50aWwgNSUgYSA5NSUpIHVzYW5kbyBQcmVjaW9fRmluYWxfVW5pdGFyaW8KICBwcmVjaW9fbWluIDwtIHF1YW50aWxlKGRhdGFfcHJvZHVjdG8kUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCAwLjA1LCBuYS5ybSA9IFRSVUUpCiAgcHJlY2lvX21heCA8LSBxdWFudGlsZShkYXRhX3Byb2R1Y3RvJFByZWNpb19GaW5hbF9Vbml0YXJpbywgMC45NSwgbmEucm0gPSBUUlVFKQoKICAjIENyZWFyIHZlY3RvciBkZSBwcmVjaW9zIHNpbXVsYWRvcwogIHByZWNpb3Nfc2ltdWxhZG9zIDwtIHNlcShwcmVjaW9fbWluLCBwcmVjaW9fbWF4LCBsZW5ndGgub3V0ID0gbikKCiAgIyBPYnRlbmVyIMO6bHRpbWEgc2VtYW5hIHkgZmVjaGEKICB1bHRpbWFfZmlsYSA8LSBkYXRhX3Byb2R1Y3RvICU+JSBhcnJhbmdlKGRlc2MoU2VtYW5hKSkgJT4lIC5bMSwgXQogIHVsdGltYV9zZW1hbmEgPC0gdWx0aW1hX2ZpbGEkU2VtYW5hCiAgdWx0aW1hX2ZlY2hhIDwtIHVsdGltYV9maWxhJFRyeF9GZWNoYQoKICAjIEV4cGFuZGlyIGNvbWJpbmFjaW9uZXM6IHNlbWFuYXMgZnV0dXJhcyDDlyBwcmVjaW9zIHNpbXVsYWRvcwogIHNpbXVsYWNpb24gPC0gZXhwYW5kLmdyaWQoCiAgICBTZW1hbmEgPSB1bHRpbWFfc2VtYW5hICsgMTpzZW1hbmFzX2Ffc2ltdWxhciwKICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyA9IHByZWNpb3Nfc2ltdWxhZG9zICAKICApICU+JQogICAgbXV0YXRlKAogICAgICBJRF9JbnZlbnRhcmlvID0gcHJvZHVjdF9pZCwKICAgICAgVmVudGFfU2VtYW5hX0FudGVyaW9yID0gdWx0aW1hX2ZpbGEkVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciA9IHVsdGltYV9maWxhJENhbnRfU2VtYW5hX0FudGVyaW9yLAogICAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yID0gdWx0aW1hX2ZpbGEkRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciwKICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yID0gdWx0aW1hX2ZpbGEkQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLAogICAgICBUcnhfRmVjaGEgPSB1bHRpbWFfZmVjaGEgKyA3ICogKFNlbWFuYSAtIHVsdGltYV9zZW1hbmEpCiAgICApCgogIHJldHVybihzaW11bGFjaW9uKQp9CmBgYAoKIyMjIEFQTElDQVIgU0lNVUxBQ0lPTkVTIEEgVE9ET1MgTE9TIFBST0RVQ1RPUwpgYGB7cn0KcHJvZHVjdG9zX2lkcyA8LSB1bmlxdWUoZGF0b3MkSURfSW52ZW50YXJpbykgICMgbyB1c2EgdG9wX2lkcyBzaSB5YSBsb3MgZGVmaW5pc3RlCgpzaW11bGFjaW9uZXNfcHJlY2lvX2xpc3RhIDwtIHNldE5hbWVzKAogIGxhcHBseShwcm9kdWN0b3NfaWRzLCBmdW5jdGlvbihpZCkgc2ltdWxhcl9lc2NlbmFyaW9zX3ByZWNpbyhkYXRvcywgaWQpKSwKICBwcm9kdWN0b3NfaWRzCikKYGBgCgojIyMgRVNUSU1BUiBQUkVDSU8gw5NQVElNTyBQT1IgUFJPRFVDVE8KYGBge3J9CiNERUZJTkUgTEEgRlVOQ0lPTiBERSBTSU1VTEFSIFBSRUNJT1MgUE9SIFBST0RVQ1RPCnNpbXVsYXJfcHJlY2lvc19wb3JfcHJvZHVjdG8gPC0gZnVuY3Rpb24ocHJvZHVjdF9pZCwgZGF0b3NfaGlzdCwgZGF0b3NfdGVzdCwgbW9kZWxvc194Z2IsIGNvbHVtbmFzX21vZGVsbywgZGV2b2x2ZXJfdG9kYXMgPSBGQUxTRSkgewogIG1vZGVsbyA8LSBtb2RlbG9zX3hnYltbcHJvZHVjdF9pZF1dCiAgCiAgdGVzdF9wcm9kdWN0byA8LSBkYXRvc190ZXN0ICU+JQogICAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gYXMubnVtZXJpYyhwcm9kdWN0X2lkKSkgJT4lCiAgICBzZWxlY3QoCiAgICAgIFNlbWFuYSwgVHJ4X0ZlY2hhLAogICAgICBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sIFByZWNpb19GaW5hbF9Vbml0YXJpbywKICAgICAgVmVudGFfU2VtYW5hX0FudGVyaW9yLCBDYW50X1NlbWFuYV9BbnRlcmlvciwKICAgICAgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciwgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLAogICAgICBWZW50YQogICAgKSAlPiUKICAgIG11dGF0ZShQcm9kdWN0byA9IHByb2R1Y3RfaWQpICU+JQogICAgZHJvcF9uYSgpCiAgCiAgcHJlY2lvc19oaXN0IDwtIGRhdG9zX2hpc3QgJT4lCiAgICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBhcy5udW1lcmljKHByb2R1Y3RfaWQpKSAlPiUKICAgIHB1bGwoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKQogIAogIHByZWNpb19taW4gPC0gcXVhbnRpbGUocHJlY2lvc19oaXN0LCAwLjA1LCBuYS5ybSA9IFRSVUUpCiAgcHJlY2lvX21heCA8LSBxdWFudGlsZShwcmVjaW9zX2hpc3QsIDAuOTUsIG5hLnJtID0gVFJVRSkKICBwcmVjaW9zX3NpbXVsYWRvcyA8LSBzZXEocHJlY2lvX21pbiwgcHJlY2lvX21heCwgbGVuZ3RoLm91dCA9IDIwKQogIAogIHNpbXVsYWNpb25lcyA8LSB0ZXN0X3Byb2R1Y3RvICU+JQogIHJvd3dpc2UoKSAlPiUKICBkbyh7CiAgICBmaWxhIDwtIC4KICAgIHNpbSA8LSB0aWJibGUoCiAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyA9IHByZWNpb3Nfc2ltdWxhZG9zLAogICAgICBWZW50YV9TZW1hbmFfQW50ZXJpb3IgPSBmaWxhJFZlbnRhX1NlbWFuYV9BbnRlcmlvciwKICAgICAgQ2FudF9TZW1hbmFfQW50ZXJpb3IgPSBmaWxhJENhbnRfU2VtYW5hX0FudGVyaW9yLAogICAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yID0gZmlsYSREZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLAogICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IgPSBmaWxhJENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciwKICAgICAgU2VtYW5hID0gZmlsYSRTZW1hbmEKICAgICkKICAgIAogICAgWF9zaW0gPC0gc2ltICU+JSBzZWxlY3QoYWxsX29mKGNvbHVtbmFzX21vZGVsbykpICU+JSBhcy5tYXRyaXgoKQogICAgc2ltJFZlbnRhX1ByZWRpY2hhIDwtIHByZWRpY3QobW9kZWxvLCBuZXdkYXRhID0gWF9zaW0pCiAgICAKICAgIHNpbSA8LSBzaW0gJT4lCiAgICAgIG11dGF0ZSgKICAgICAgICBQcm9kdWN0byA9IGZpbGEkUHJvZHVjdG8sCiAgICAgICAgVHJ4X0ZlY2hhID0gZmlsYSRUcnhfRmVjaGEsCiAgICAgICAgUHJlY2lvX0xpc3RhX1VuaXRhcmlvID0gZmlsYSRQcmVjaW9fTGlzdGFfVW5pdGFyaW8sCiAgICAgICAgUHJlY2lvX1JlYWwgPSBmaWxhJFByZWNpb19GaW5hbF9Vbml0YXJpbywKICAgICAgICBWZW50YV9SZWFsID0gZmlsYSRWZW50YSwKICAgICAgICBNYXJnZW5fRXNwZXJhZG8gPSBWZW50YV9QcmVkaWNoYSAtIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciAqIENhbnRfU2VtYW5hX0FudGVyaW9yLAogICAgICAgIERlc2N1ZW50b19TdWdlcmlkbyA9IDEgLSBQcmVjaW9fRmluYWxfVW5pdGFyaW8gLyBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sCiAgICAgICAgRGVzY3VlbnRvX1JlYWwgPSAxIC0gUHJlY2lvX1JlYWwgLyBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sCiAgICAgICAgRXNfUHJlY2lvX09wdGltbyA9IFZlbnRhX1ByZWRpY2hhID09IG1heChWZW50YV9QcmVkaWNoYSwgbmEucm0gPSBUUlVFKQogICAgICApCiAgICAKICAgIGlmIChkZXZvbHZlcl90b2RhcykgewogICAgICBzaW0KICAgIH0gZWxzZSB7CiAgICAgIHNpbSAlPiUgZmlsdGVyKEVzX1ByZWNpb19PcHRpbW8pICU+JSBzbGljZV9oZWFkKG4gPSAxKQogICAgfQogIH0pICU+JQogIHVuZ3JvdXAoKQp9CmBgYAoKYGBge3J9Cm1vZGVsb3NfeGdiX2ZpbmFsIDwtIGxpc3QoCiAgIjE1NTAwMSIgPSBtb2RlbG9feGdiX2ZpbmFsXzE1NTAwMSwKICAiMzkyOTc4OCIgPSBtb2RlbG9feGdiX2ZpbmFsXzM5Mjk3ODgsCiAgIjM5MDQxNTIiID0gbW9kZWxvX3hnYl9maW5hbF8zOTA0MTUyLAogICIxNTUwMDIiID0gbW9kZWxvX3hnYl9maW5hbF8xNTUwMDIsCiAgIjM2NzgwNTUiID0gbW9kZWxvX3hnYl9maW5hbF8zNjc4MDU1CikKCmNvbHVtbmFzX21vZGVsbyA8LSBjKAogICJQcmVjaW9fRmluYWxfVW5pdGFyaW8iLCAKICAiVmVudGFfU2VtYW5hX0FudGVyaW9yIiwgCiAgIkNhbnRfU2VtYW5hX0FudGVyaW9yIiwgCiAgIkRlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IiLCAKICAiQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yIiwgCiAgIlNlbWFuYSIKKQpgYGAKCiMjIyBUQUJMQSBERSBTSU1VTEFDSU9ORVMgREUgUFJFQ0lPUwpgYGB7cn0KIyMjIFRBQkxBIERFIFNJTVVMQUNJT05FUyBERSBQUkVDSU9TCnRhYmxhX2NvbXBsZXRhX3NpbXVsYWRhIDwtIGJpbmRfcm93cygKICBsYXBwbHkobmFtZXMobW9kZWxvc194Z2JfZmluYWwpLCBzaW11bGFyX3ByZWNpb3NfcG9yX3Byb2R1Y3RvLAogICAgICAgICBkYXRvc19oaXN0ID0gZGF0b3MsCiAgICAgICAgIGRhdG9zX3Rlc3QgPSB0ZXN0LAogICAgICAgICBtb2RlbG9zX3hnYiA9IG1vZGVsb3NfeGdiX2ZpbmFsLAogICAgICAgICBjb2x1bW5hc19tb2RlbG8gPSBjb2x1bW5hc19tb2RlbG8sCiAgICAgICAgIGRldm9sdmVyX3RvZGFzID0gVFJVRSkKKSAlPiUKICBzZWxlY3QoCiAgICBQcm9kdWN0bywgU2VtYW5hLCBUcnhfRmVjaGEsCiAgICBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sCiAgICBQcmVjaW9fUmVhbCwKICAgIFByZWNpb19TaW11bGFkbyA9IFByZWNpb19GaW5hbF9Vbml0YXJpbywKICAgIERlc2N1ZW50b19SZWFsLAogICAgRGVzY3VlbnRvX1N1Z2VyaWRvLAogICAgVmVudGFfUmVhbCwKICAgIFZlbnRhX1ByZWRpY2hhLAogICAgTWFyZ2VuX0VzcGVyYWRvLAogICAgRXNfUHJlY2lvX09wdGltbwogICkKCmBgYAoKIyMjIFRBQkxBIERFIFBSRUNJT1MgQ09OIE1BWU9SIFZFTlRBIEVTUEVSQURBCmBgYHtyfQp0YWJsYV9jb21wYXJhdGl2YV9vcHRpbW9zIDwtIGJpbmRfcm93cygKICBsYXBwbHkobmFtZXMobW9kZWxvc194Z2JfZmluYWwpLCBzaW11bGFyX3ByZWNpb3NfcG9yX3Byb2R1Y3RvLAogICAgICAgICBkYXRvc19oaXN0ID0gZGF0b3MsCiAgICAgICAgIGRhdG9zX3Rlc3QgPSB0ZXN0LAogICAgICAgICBtb2RlbG9zX3hnYiA9IG1vZGVsb3NfeGdiX2ZpbmFsLAogICAgICAgICBjb2x1bW5hc19tb2RlbG8gPSBjb2x1bW5hc19tb2RlbG8sCiAgICAgICAgIGRldm9sdmVyX3RvZGFzID0gRkFMU0UpCikgJT4lCiAgc2VsZWN0KAogICAgUHJvZHVjdG8sIFNlbWFuYSwgVHJ4X0ZlY2hhLAogICAgUHJlY2lvX0xpc3RhX1VuaXRhcmlvLAogICAgUHJlY2lvX1JlYWwsCiAgICBQcmVjaW9fT3B0aW1vID0gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLAogICAgRGVzY3VlbnRvX1JlYWwsCiAgICBEZXNjdWVudG9fU3VnZXJpZG8sCiAgICBWZW50YV9SZWFsLAogICAgVmVudGFfUHJlZGljaGEsCiAgICBNYXJnZW5fRXNwZXJhZG8KICApCmBgYAoKCiMjIyBUQUJMQSBERSBQUkVDSU9TIENPTiBNQVlPUiBWRU5UQSBFU1BFUkFEQQpgYGB7cn0KIyMjIFRBQkxBIERFIFBSRUNJT1MgQ09OIE1BWU9SIFZFTlRBIEVTUEVSQURBCnRhYmxhX2NvbXBhcmF0aXZhX29wdGltb3MgPC0gYmluZF9yb3dzKAogIGxhcHBseShuYW1lcyhtb2RlbG9zX3hnYl9maW5hbCksIHNpbXVsYXJfcHJlY2lvc19wb3JfcHJvZHVjdG8sCiAgICAgICAgIGRhdG9zX2hpc3QgPSBkYXRvcywKICAgICAgICAgZGF0b3NfdGVzdCA9IHRlc3QsCiAgICAgICAgIG1vZGVsb3NfeGdiID0gbW9kZWxvc194Z2JfZmluYWwsCiAgICAgICAgIGNvbHVtbmFzX21vZGVsbyA9IGNvbHVtbmFzX21vZGVsbywKICAgICAgICAgZGV2b2x2ZXJfdG9kYXMgPSBGQUxTRSkKKSAlPiUKICBtdXRhdGUoQ2FudGlkYWRfRXN0aW1hZGEgPSBWZW50YV9QcmVkaWNoYSAvIFByZWNpb19GaW5hbF9Vbml0YXJpbykgJT4lICAjIFByZWNpbyDDs3B0aW1vCiAgc2VsZWN0KAogICAgUHJvZHVjdG8sIFNlbWFuYSwgVHJ4X0ZlY2hhLAogICAgUHJlY2lvX0xpc3RhX1VuaXRhcmlvLAogICAgUHJlY2lvX1JlYWwsCiAgICBQcmVjaW9fT3B0aW1vID0gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLAogICAgRGVzY3VlbnRvX1JlYWwsCiAgICBEZXNjdWVudG9fU3VnZXJpZG8sCiAgICBWZW50YV9SZWFsLAogICAgVmVudGFfUHJlZGljaGEsCiAgICBDYW50aWRhZF9Fc3RpbWFkYSwKICAgIE1hcmdlbl9Fc3BlcmFkbwogICkKYGBgCgpgYGB7cn0KaGVhZCh0YWJsYV9jb21wYXJhdGl2YV9vcHRpbW9zKQpoZWFkKHRhYmxhX2NvbXBsZXRhX3NpbXVsYWRhKQpgYGAKCgojIyMgREVTQ0FSR0FSIEEgQ1NWCmBgYHtyfQojd3JpdGUuY3N2KHRhYmxhX2NvbXBhcmF0aXZhX29wdGltb3MsICJ0YWJsYV9wcmVjaW9zX29wdGltb3MuY3N2Iiwgcm93Lm5hbWVzID0gRkFMU0UpCiN3cml0ZS5jc3YodGFibGFfY29tcGxldGFfc2ltdWxhZGEsICJ0YWJsYV9zaW11bGFjaW9uZXNfY29tcGxldGFzLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKCgo=