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_data_filtered.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 PREDICCIONES DE VENTAS

4 ARMA

4.1 PRODUCTO 155001

4.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
))

4.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

4.2 PRODUCTO 3929788

4.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
))

4.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

4.3 PRODUCTO 3904152

4.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
))

4.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

4.4 PRODUCTO 155002

4.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
))

4.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

4.5 PRODUCTO 3678055

4.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
))

4.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

4.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

5 REGRESION LINEAL

5.1 MAPA DE CALOR

# Variables numéricas relevantes para el modelo
vars_numericas <- c("Venta", "Precio_Lista_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))
)

5.2 PRODUCTO 155001

5.2.1 DATOS TRAIN 155001

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

5.2.2 DATOS TEST 155001

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

5.2.3 MODELO

# Ajustar modelo con entrenamiento
modelo_regresion_155001 <- lm(
  Venta ~ Precio_Lista_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_Lista_Unitario + Venta_Semana_Anterior + 
##     Cant_Semana_Anterior + Descuento_Semana_Anterior + Costo_Unitario_Semana_Anterior + 
##     Semana, data = datos_155001)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -156906  -64189  -20434   50236  496583 
## 
## Coefficients:
##                                  Estimate Std. Error t value Pr(>|t|)  
## (Intercept)                     3.170e+06  2.111e+06   1.502   0.1368  
## Precio_Lista_Unitario           8.572e+01  8.387e+01   1.022   0.3097  
## Venta_Semana_Anterior           4.461e-01  1.012e+00   0.441   0.6604  
## Cant_Semana_Anterior           -1.726e+02  3.965e+02  -0.435   0.6645  
## Descuento_Semana_Anterior      -3.327e+06  2.304e+06  -1.444   0.1524  
## Costo_Unitario_Semana_Anterior -9.610e+02  4.500e+02  -2.136   0.0356 *
## Semana                          6.378e+02  1.798e+03   0.355   0.7237  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 115400 on 84 degrees of freedom
## Multiple R-squared:  0.07848,    Adjusted R-squared:  0.01266 
## F-statistic: 1.192 on 6 and 84 DF,  p-value: 0.3184
# 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.43 | RMSE: 110897.6
cat("Test  - MAPE:", round(mape_test, 2), "| RMSE:", round(rmse_test, 2), "\n")
## Test  - MAPE: 91.98 | RMSE: 157554.7

5.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
))

5.3 PRODUCTO 3929788

5.3.1 DATOS TRAIN

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

5.3.2 DATOS TEST

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

5.3.3 MODELO

# Ajustar modelo con entrenamiento
modelo_regresion_3929788 <- lm(
  Venta ~ Precio_Lista_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_Lista_Unitario + Venta_Semana_Anterior + 
##     Cant_Semana_Anterior + Descuento_Semana_Anterior + Costo_Unitario_Semana_Anterior + 
##     Semana, data = datos_3929788)
## 
## Residuals:
##    Min     1Q Median     3Q    Max 
## -98573 -40311  -2739  35274 191727 
## 
## Coefficients:
##                                  Estimate Std. Error t value Pr(>|t|)
## (Intercept)                    -7.614e+05  6.171e+05  -1.234    0.221
## Precio_Lista_Unitario           5.303e+03  4.764e+03   1.113    0.269
## Venta_Semana_Anterior          -9.297e-01  1.081e+00  -0.860    0.392
## Cant_Semana_Anterior            2.740e+01  4.032e+01   0.679    0.499
## Descuento_Semana_Anterior       5.774e+05  1.061e+06   0.544    0.588
## Costo_Unitario_Semana_Anterior  3.958e+03  1.429e+04   0.277    0.782
## Semana                          1.098e+02  4.991e+02   0.220    0.826
## 
## Residual standard error: 56810 on 84 degrees of freedom
## Multiple R-squared:  0.2186, Adjusted R-squared:  0.1628 
## F-statistic: 3.917 on 6 and 84 DF,  p-value: 0.001689
# 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: 21.28 | RMSE: 54576.64
cat("Test  - MAPE:", round(mape_test_3929788, 2), "| RMSE:", round(rmse_test_3929788, 2), "\n")
## Test  - MAPE: 31.1 | RMSE: 63311.33

5.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
))

5.4 PRODUCTO 3904152

5.4.1 DATOS TRAIN

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

5.4.2 DATOS TEST

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

5.4.3 MODELO

# Ajustar modelo con entrenamiento
modelo_regresion_3904152 <- lm(
  Venta ~ Precio_Lista_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_Lista_Unitario + Venta_Semana_Anterior + 
##     Cant_Semana_Anterior + Descuento_Semana_Anterior + Costo_Unitario_Semana_Anterior + 
##     Semana, data = datos_3904152)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -133328  -59334  -24284   20018  514946 
## 
## Coefficients:
##                                  Estimate Std. Error t value Pr(>|t|)
## (Intercept)                    -2.374e+06  2.392e+06  -0.993    0.324
## Precio_Lista_Unitario          -6.233e+01  1.130e+02  -0.552    0.583
## Venta_Semana_Anterior           1.315e+00  2.608e+00   0.504    0.615
## Cant_Semana_Anterior           -4.463e+03  7.963e+03  -0.560    0.577
## Descuento_Semana_Anterior       2.897e+06  2.723e+06   1.064    0.290
## Costo_Unitario_Semana_Anterior  5.146e+02  5.884e+02   0.875    0.384
## Semana                          8.626e+02  1.122e+03   0.769    0.444
## 
## Residual standard error: 109800 on 84 degrees of freedom
## Multiple R-squared:  0.03534,    Adjusted R-squared:  -0.03356 
## F-statistic: 0.5129 on 6 and 84 DF,  p-value: 0.797
# 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: 40.71 | RMSE: 105502.9
cat("Test  - MAPE:", round(mape_test_3904152, 2), "| RMSE:", round(rmse_test_3904152, 2), "\n")
## Test  - MAPE: 172.93 | RMSE: 179654

5.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
))

5.5 PRODUCTO 155002

5.5.1 DATOS TRAIN

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

5.5.2 DATOS TEST

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

5.5.3 MODELO

# Ajustar modelo con entrenamiento
modelo_regresion_155002 <- lm(
  Venta ~ Precio_Lista_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_Lista_Unitario + Venta_Semana_Anterior + 
##     Cant_Semana_Anterior + Descuento_Semana_Anterior + Costo_Unitario_Semana_Anterior + 
##     Semana, data = datos_155002)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -134141  -58576  -20356   36079  300149 
## 
## Coefficients:
##                                  Estimate Std. Error t value Pr(>|t|)
## (Intercept)                     1.210e+06  1.178e+06   1.026    0.308
## Precio_Lista_Unitario          -4.437e+01  5.244e+01  -0.846    0.400
## Venta_Semana_Anterior           1.150e+00  8.470e-01   1.358    0.178
## Cant_Semana_Anterior           -3.804e+02  3.487e+02  -1.091    0.278
## Descuento_Semana_Anterior      -1.029e+06  1.284e+06  -0.801    0.425
## Costo_Unitario_Semana_Anterior -1.235e+02  1.701e+02  -0.726    0.470
## Semana                          1.566e+03  1.190e+03   1.315    0.192
## 
## Residual standard error: 85160 on 84 degrees of freedom
## Multiple R-squared:  0.09501,    Adjusted R-squared:  0.03037 
## F-statistic:  1.47 on 6 and 84 DF,  p-value: 0.1985
# 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.16 | RMSE: 81814.8
cat("Test  - MAPE:", round(mape_test_155002, 2), "| RMSE:", round(rmse_test_155002, 2), "\n")
## Test  - MAPE: 115.44 | RMSE: 140809.8

5.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
))

5.6 PRODUCTO 3678055

5.6.1 DATOS TRAIN

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

5.6.2 DATOS TEST

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

5.6.3 MODELO

# Ajustar modelo con entrenamiento
modelo_regresion_3678055 <- lm(
  Venta ~ Precio_Lista_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_Lista_Unitario + Venta_Semana_Anterior + 
##     Cant_Semana_Anterior + Descuento_Semana_Anterior + Costo_Unitario_Semana_Anterior + 
##     Semana, data = datos_3678055)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
## -139903  -47687  -13196   34789  355470 
## 
## Coefficients:
##                                  Estimate Std. Error t value Pr(>|t|)  
## (Intercept)                     1.967e+06  1.509e+06   1.303   0.1961  
## Precio_Lista_Unitario           7.866e+01  4.066e+01   1.935   0.0564 .
## Venta_Semana_Anterior          -2.438e+00  2.009e+00  -1.214   0.2282  
## Cant_Semana_Anterior            1.272e+04  1.029e+04   1.236   0.2199  
## Descuento_Semana_Anterior      -2.960e+06  1.783e+06  -1.660   0.1006  
## Costo_Unitario_Semana_Anterior -2.642e+02  1.985e+02  -1.331   0.1868  
## Semana                         -7.349e+02  5.960e+02  -1.233   0.2210  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 84030 on 84 degrees of freedom
## Multiple R-squared:  0.05395,    Adjusted R-squared:  -0.01363 
## F-statistic: 0.7983 on 6 and 84 DF,  p-value: 0.5739
# 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: 53.36 | RMSE: 80733.94
cat("Test  - MAPE:", round(mape_test_3678055, 2), "| RMSE:", round(rmse_test_3678055, 2), "\n")
## Test  - MAPE: 63.93 | RMSE: 146190.7

5.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
))

5.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.43046 110897.56
155001 Regresión Lineal (Test) 91.97987 157554.70
155002 ARMA 51.33213 82260.20
155002 ARMA (Test) 35.86659 87005.41
155002 Regresión Lineal (Train) 53.15643 81814.80
155002 Regresión Lineal (Test) 115.43616 140809.84
3678055 ARMA 54.27943 82835.18
3678055 ARMA (Test) 53.77550 105823.13
3678055 Regresión Lineal (Train) 53.36169 80733.94
3678055 Regresión Lineal (Test) 63.92659 146190.72
3904152 ARMA 41.54527 99993.25
3904152 ARMA (Test) 18.70572 50062.24
3904152 Regresión Lineal (Train) 40.71452 105502.91
3904152 Regresión Lineal (Test) 172.92634 179653.99
3929788 ARMA 21.74312 57634.89
3929788 ARMA (Test) 26.25704 60053.08
3929788 Regresión Lineal (Train) 21.27972 54576.64
3929788 Regresión Lineal (Test) 31.09822 63311.33

6 RANDOM FOREST

6.1 PRODUCTO 155001

# Entrenamiento
set.seed(123)
modelo_rf_155001 <- randomForest(
  Venta ~ Precio_Lista_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: 20.1 | RMSE: 57033.51
cat("Test  - MAPE:", round(mape_test_rf_155001, 2),  "| RMSE:", round(rmse_test_rf_155001, 2),  "\n")
## Test  - MAPE: 84.28 | RMSE: 149257.5
# 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: -80614.77
cat("SD error:", sd(errores_test_155001), "\n")
## SD error: 130743.9
cat("Min error:", min(errores_test_155001), "\n")
## Min error: -226994.8
cat("Max error:", max(errores_test_155001), "\n")
## Max error: 250340.7
cat("Mediana error:", median(errores_test_155001), "\n")
## Mediana error: -117253.6
# 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()

6.2 PRODUCTO 3929788

# Entrenamiento
set.seed(123)
modelo_rf_3929788 <- randomForest(
  Venta ~ Precio_Lista_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.71 | RMSE: 27422.69
cat("Test  - MAPE:", round(mape_test_rf_3929788, 2),  "| RMSE:", round(rmse_test_rf_3929788, 2),  "\n")
## Test  - MAPE: 27.3 | RMSE: 55819.49
# 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: -17708.08
cat("SD error:", sd(errores_test_3929788), "\n")
## SD error: 55097.72
cat("Min error:", min(errores_test_3929788), "\n")
## Min error: -95314.91
cat("Max error:", max(errores_test_3929788), "\n")
## Max error: 64932.72
cat("Mediana error:", median(errores_test_3929788), "\n")
## Mediana error: -22465.42
# 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()

6.3 PRODUCTO 3904152

# Entrenamiento
set.seed(123)
modelo_rf_3904152 <- randomForest(
  Venta ~ Precio_Lista_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: 21.27 | RMSE: 55972.1
cat("Test  - MAPE:", round(mape_test_rf_3904152, 2),  "| RMSE:", round(rmse_test_rf_3904152, 2),  "\n")
## Test  - MAPE: 100.42 | RMSE: 113611.6
# 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: -79780.87
cat("SD error:", sd(errores_test_3904152), "\n")
## SD error: 84189.19
cat("Min error:", min(errores_test_3904152), "\n")
## Min error: -244974.8
cat("Max error:", max(errores_test_3904152), "\n")
## Max error: 118473.8
cat("Mediana error:", median(errores_test_3904152), "\n")
## Mediana error: -74410.38
# 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()

6.4 PRODUCTO 155002

# Entrenamiento
set.seed(123)
modelo_rf_155002 <- randomForest(
  Venta ~ Precio_Lista_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: 25.66 | RMSE: 41348.42
cat("Test  - MAPE:", round(mape_test_rf_155002, 2),  "| RMSE:", round(rmse_test_rf_155002, 2),  "\n")
## Test  - MAPE: 98.59 | RMSE: 122127.5
# 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: -7257.753
cat("SD error:", sd(errores_test_155002), "\n")
## SD error: 126889.6
cat("Min error:", min(errores_test_155002), "\n")
## Min error: -121491.7
cat("Max error:", max(errores_test_155002), "\n")
## Max error: 230978.1
cat("Mediana error:", median(errores_test_155002), "\n")
## Mediana error: -52340.74
# 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()

6.5 PRODUCTO 3678055

# Entrenamiento
set.seed(123)
modelo_rf_3678055 <- randomForest(
  Venta ~ Precio_Lista_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: 27.52 | RMSE: 43093.85
cat("Test  - MAPE:", round(mape_test_rf_3678055, 2),  "| RMSE:", round(rmse_test_rf_3678055, 2),  "\n")
## Test  - MAPE: 46.34 | RMSE: 104012.2
# 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: 5261.388
cat("SD error:", sd(errores_test_3678055), "\n")
## SD error: 108120.8
cat("Min error:", min(errores_test_3678055), "\n")
## Min error: -106177.6
cat("Max error:", max(errores_test_3678055), "\n")
## Max error: 211300.5
cat("Mediana error:", median(errores_test_3678055), "\n")
## Mediana error: -29431.4
# 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()

6.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.43046 110897.56
155001 Regresión Lineal (Test) 91.97987 157554.70
155001 Random Forest (Train) 20.09631 57033.51
155001 Random Forest (Test) 84.27831 149257.49
155002 ARMA 51.33213 82260.20
155002 ARMA (Test) 35.86659 87005.41
155002 Regresión Lineal (Train) 53.15643 81814.80
155002 Regresión Lineal (Test) 115.43616 140809.84
155002 Random Forest (Train) 25.66472 41348.42
155002 Random Forest (Test) 98.59409 122127.47
3678055 ARMA 54.27943 82835.18
3678055 ARMA (Test) 53.77550 105823.13
3678055 Regresión Lineal (Train) 53.36169 80733.94
3678055 Regresión Lineal (Test) 63.92659 146190.72
3678055 Random Forest (Train) 27.51874 43093.85
3678055 Random Forest (Test) 46.33560 104012.22
3904152 ARMA 41.54527 99993.25
3904152 ARMA (Test) 18.70572 50062.24
3904152 Regresión Lineal (Train) 40.71452 105502.91
3904152 Regresión Lineal (Test) 172.92634 179653.99
3904152 Random Forest (Train) 21.27422 55972.10
3904152 Random Forest (Test) 100.42105 113611.58
3929788 ARMA 21.74312 57634.89
3929788 ARMA (Test) 26.25704 60053.08
3929788 Regresión Lineal (Train) 21.27972 54576.64
3929788 Regresión Lineal (Test) 31.09822 63311.33
3929788 Random Forest (Train) 10.71016 27422.69
3929788 Random Forest (Test) 27.30344 55819.49

7 XGBOOST

7.1 PRODUCTO 155001

7.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)

7.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      40
## 144 0.10         7       1.0              1.0                3   0.1      28
## 50  0.05         5       1.0              0.8                3   0.0      52
## 92  0.05         3       0.8              1.0                1   0.1      52
## 137 0.05         3       1.0              1.0                3   0.1      50
##         rmse
## 14  115100.4
## 144 118133.5
## 50  119155.6
## 92  121703.3
## 137 128796.5

7.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: 40
# 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
)

7.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: 16.95 | RMSE: 70636.75
cat("Test  Split - MAPE:", round(mape_test_split_155001, 2),  "| RMSE:", round(rmse_test_split_155001, 2), "\n")
## Test  Split - MAPE: 32.56 | RMSE: 100938.8

7.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: 16.81 | RMSE: 70510.56
cat("Test  - MAPE:", round(mape_final_test_155001, 2),  "| RMSE:", round(rmse_final_test_155001, 2), "\n")
## Test  - MAPE: 52.38 | RMSE: 118506.1

7.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
))

7.2 PRODUCTO 3929788

7.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)

7.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      73
## 92  0.05         3       0.8              1.0                1   0.1      62
## 14  0.05         5       1.0              0.8                1   0.0      62
## 137 0.05         3       1.0              1.0                3   0.1      60
## 90  0.10         7       1.0              0.8                1   0.1      42
##         rmse
## 50  62368.41
## 92  65177.57
## 14  67389.64
## 137 68110.39
## 90  68267.39

7.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: 73
# 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
)

7.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: 7.8 | RMSE: 24355.45
cat("Test  Split - MAPE:", round(mape_test_split_3929788, 2),  "| RMSE:", round(rmse_test_split_3929788, 2), "\n")
## Test  Split - MAPE: 15.62 | RMSE: 38020.87

7.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: 7.23 | RMSE: 23132.47
cat("Test  - MAPE:", round(mape_final_test_3929788, 2),  "| RMSE:", round(rmse_final_test_3929788, 2), "\n")
## Test  - MAPE: 23.4 | RMSE: 51416.15

7.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
))

7.3 PRODUCTO 3904152

7.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)

7.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
## 14  0.05         5         1              0.8                1   0.0      59
## 90  0.10         7         1              0.8                1   0.1      20
## 137 0.05         3         1              1.0                3   0.1      35
## 50  0.05         5         1              0.8                3   0.0      53
## 144 0.10         7         1              1.0                3   0.1      19
##          rmse
## 14   92882.68
## 90  100172.09
## 137 100746.74
## 50  101240.21
## 144 101622.69

7.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] 1
## 
## $gamma
## [1] 0
cat("Número óptimo de rondas:", mejor_nrounds_3904152, "\n")
## Número óptimo de rondas: 59
# 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
)

7.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: 11.9 | RMSE: 36737.11
cat("Test  Split - MAPE:", round(mape_test_split_3904152, 2),  "| RMSE:", round(rmse_test_split_3904152, 2), "\n")
## Test  Split - MAPE: 34.89 | RMSE: 138370.7

7.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: 11.79 | RMSE: 38513.37
cat("Test  - MAPE:", round(mape_final_test_3904152, 2),  "| RMSE:", round(rmse_final_test_3904152, 2), "\n")
## Test  - MAPE: 48.62 | RMSE: 82356.39

7.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
))

7.4 PRODUCTO 155002

7.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)

7.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
## 92  0.05         3       0.8              1.0                1   0.1      43
## 144 0.10         7       1.0              1.0                3   0.1      27
## 14  0.05         5       1.0              0.8                1   0.0      37
## 90  0.10         7       1.0              0.8                1   0.1      16
## 50  0.05         5       1.0              0.8                3   0.0      35
##         rmse
## 92  90353.92
## 144 91480.70
## 14  93025.91
## 90  93774.01
## 50  96099.56

7.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] 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_155002, "\n")
## Número óptimo de rondas: 43
# 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
)

7.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: 27.01 | RMSE: 57429.86
cat("Test  Split - MAPE:", round(mape_test_split_155002, 2),  "| RMSE:", round(rmse_test_split_155002, 2), "\n")
## Test  Split - MAPE: 37.53 | RMSE: 89854.05

7.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: 27.16 | RMSE: 59553.9
cat("Test  - MAPE:", round(mape_final_test_155002, 2),  "| RMSE:", round(rmse_final_test_155002, 2), "\n")
## Test  - MAPE: 60.72 | RMSE: 122704.9

7.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
))

7.5 PRODUCTO 3678055

7.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)

7.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
## 50  0.05         5       1.0              0.8                3   0.0      49
## 90  0.10         7       1.0              0.8                1   0.1      22
## 144 0.10         7       1.0              1.0                3   0.1      26
## 137 0.05         3       1.0              1.0                3   0.1      57
## 92  0.05         3       0.8              1.0                1   0.1      36
##         rmse
## 50  88366.96
## 90  88801.94
## 144 89648.04
## 137 90489.76
## 92  91050.69

7.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] 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_3678055, "\n")
## Número óptimo de rondas: 49
# 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
)

7.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: 23.33 | RMSE: 51393.09
cat("Test  Split - MAPE:", round(mape_test_split_3678055, 2),  "| RMSE:", round(rmse_test_split_3678055, 2), "\n")
## Test  Split - MAPE: 50.81 | RMSE: 89464.46

7.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: 24 | RMSE: 50249.95
cat("Test  - MAPE:", round(mape_final_test_3678055, 2),  "| RMSE:", round(rmse_final_test_3678055, 2), "\n")
## Test  - MAPE: 37.84 | RMSE: 116478.2

7.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
))

7.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.960773 109593.78
155001 ARMA (Test) 25.142587 67277.26
155001 Regresión Lineal (Train) 39.430456 110897.56
155001 Regresión Lineal (Test) 91.979875 157554.70
155001 Random Forest (Train) 20.096308 57033.51
155001 Random Forest (Test) 84.278314 149257.49
155001 XGBoost (Train) 16.806958 70510.56
155001 XGBoost (Test) 52.377346 118506.06
155002 ARMA 51.332134 82260.20
155002 ARMA (Test) 35.866590 87005.41
155002 Regresión Lineal (Train) 53.156430 81814.80
155002 Regresión Lineal (Test) 115.436164 140809.84
155002 Random Forest (Train) 25.664720 41348.42
155002 Random Forest (Test) 98.594093 122127.47
155002 XGBoost (Train) 27.161236 59553.90
155002 XGBoost (Test) 60.717951 122704.95
3678055 ARMA 54.279431 82835.18
3678055 ARMA (Test) 53.775496 105823.13
3678055 Regresión Lineal (Train) 53.361690 80733.94
3678055 Regresión Lineal (Test) 63.926587 146190.72
3678055 Random Forest (Train) 27.518738 43093.85
3678055 Random Forest (Test) 46.335602 104012.22
3678055 XGBoost (Train) 24.001673 50249.95
3678055 XGBoost (Test) 37.839720 116478.24
3904152 ARMA 41.545269 99993.25
3904152 ARMA (Test) 18.705723 50062.24
3904152 Regresión Lineal (Train) 40.714517 105502.91
3904152 Regresión Lineal (Test) 172.926337 179653.99
3904152 Random Forest (Train) 21.274221 55972.10
3904152 Random Forest (Test) 100.421047 113611.58
3904152 XGBoost (Train) 11.787404 38513.37
3904152 XGBoost (Test) 48.616251 82356.39
3929788 ARMA 21.743123 57634.89
3929788 ARMA (Test) 26.257042 60053.08
3929788 Regresión Lineal (Train) 21.279719 54576.64
3929788 Regresión Lineal (Test) 31.098224 63311.33
3929788 Random Forest (Train) 10.710160 27422.69
3929788 Random Forest (Test) 27.303435 55819.49
3929788 XGBoost (Train) 7.225458 23132.47
3929788 XGBoost (Test) 23.403897 51416.15

8 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)
  )
}

9 ESTIMACIÓN DE PRECIOS

9.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_Lista_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_Lista_Unitario Venta_Semana_Anterior Cant_Semana_Anterior
##    <dbl>                 <dbl>                 <dbl>                <dbl>
## 1      1                 4192.                   NA                    NA
## 2      2                 4192.                78039.                   91
## 3      3                 4192.               169112.                  217
## 4      4                 4192.               155189.                  250
## 5      5                 4192.               208317.                  320
## 6      6                 4192.               205799.                  354
## # ℹ 2 more variables: Descuento_Semana_Anterior <dbl>,
## #   Costo_Unitario_Semana_Anterior <dbl>

9.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%)
  precio_min <- quantile(data_producto$Precio_Lista_Unitario, 0.05, na.rm = TRUE)
  precio_max <- quantile(data_producto$Precio_Lista_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_Lista_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)
}

9.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
)

9.2.2 ESTIMAR PRECIO ÓPTIMO POR PRODUCTO

# Variables del modelo
columnas_modelo <- c(
  "Precio_Lista_Unitario", 
  "Venta_Semana_Anterior", 
  "Cant_Semana_Anterior", 
  "Descuento_Semana_Anterior", 
  "Costo_Unitario_Semana_Anterior", 
  "Semana"
)

# Lista de modelos entrenados
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
)

# Inicializar listas
resultados_simulacion_completa <- list()
tabla_resumen_optimos <- list()

# Función para estimar precio óptimo para SOLO la siguiente semana
encontrar_precio_optimo <- function(product_id) {
  # Filtrar datos históricos del producto
  data_producto <- datos %>% filter(ID_Inventario == product_id)

  # Obtener última semana y última fecha
  ultima_fila <- data_producto %>% arrange(desc(Semana)) %>% slice_head(n = 1)
  ultima_semana <- ultima_fila$Semana
  ultima_fecha <- ultima_fila$Trx_Fecha

  # Simular rango de precios posibles (percentil 5% - 95%)
  precio_min <- quantile(data_producto$Precio_Lista_Unitario, 0.05, na.rm = TRUE)
  precio_max <- quantile(data_producto$Precio_Lista_Unitario, 0.95, na.rm = TRUE)
  precios_simulados <- seq(precio_min, precio_max, length.out = 20)

  # Crear tabla simulada (1 semana + 20 precios)
  simulacion <- data.frame(
    Producto = product_id,
    Semana = ultima_semana + 1,
    Precio_Lista_Unitario = precios_simulados,
    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
  )

  # Predecir ventas con el modelo
  X_sim <- simulacion %>% select(all_of(columnas_modelo)) %>% as.matrix()
  modelo <- modelos_xgb_final[[as.character(product_id)]]
  simulacion$Venta_Predicha <- predict(modelo, newdata = X_sim)

  # Calcular margen
  simulacion$Margen_Esperado <- simulacion$Venta_Predicha - 
    simulacion$Costo_Unitario_Semana_Anterior * simulacion$Cant_Semana_Anterior

  # Guardar simulación completa
  resultados_simulacion_completa[[as.character(product_id)]] <<- simulacion

  # Obtener precio con mayor venta esperada
  mejor_fila <- simulacion %>% filter(Venta_Predicha == max(Venta_Predicha, na.rm = TRUE)) %>% slice_head(n = 1)
  tabla_resumen_optimos[[as.character(product_id)]] <<- mejor_fila %>%
    select(Producto, Trx_Fecha, Precio_Lista_Unitario, Venta_Predicha, Margen_Esperado) %>%
    rename(
      Fecha_Simulada = Trx_Fecha,
      Precio_Optimo = Precio_Lista_Unitario,
      Venta_Esperada = Venta_Predicha
    )

  # Mostrar en consola
  cat("Producto:", product_id, "\n")
  cat("Precio óptimo:", round(mejor_fila$Precio_Lista_Unitario, 2), "\n")
  cat("Venta esperada:", round(mejor_fila$Venta_Predicha, 2), "\n")
  cat("Margen esperado:", round(mejor_fila$Margen_Esperado, 2), "\n")
  cat("Fecha simulada:", as.character(mejor_fila$Trx_Fecha), "\n\n")
}

# Ejecutar simulación para todos los productos
for (id in names(modelos_xgb_final)) {
  encontrar_precio_optimo(id)
}
## Producto: 155001 
## Precio óptimo: 4355.19 
## Venta esperada: 251473.6 
## Margen esperado: 159476.7 
## Fecha simulada: 2025-01-06 
## 
## Producto: 3929788 
## Precio óptimo: 109.08 
## Venta esperada: 210494.3 
## Margen esperado: 112793.9 
## Fecha simulada: 2025-01-06 
## 
## Producto: 3904152 
## Precio óptimo: 9053.8 
## Venta esperada: 159336.3 
## Margen esperado: 96391.84 
## Fecha simulada: 2025-01-06 
## 
## Producto: 155002 
## Precio óptimo: 4845.41 
## Venta esperada: 142955.7 
## Margen esperado: 98476.9 
## Fecha simulada: 2025-01-06 
## 
## Producto: 3678055 
## Precio óptimo: 15968.4 
## Venta esperada: 198139.2 
## Margen esperado: 89879.27 
## Fecha simulada: 2025-01-06
# Tablas finales
tabla_simulacion_completa <- bind_rows(resultados_simulacion_completa)
tabla_precios_optimos <- bind_rows(tabla_resumen_optimos)

# Mostrar tabla resumen
print(tabla_precios_optimos)
##   Producto Fecha_Simulada Precio_Optimo Venta_Esperada Margen_Esperado
## 1   155001     2025-01-06     4355.1946       251473.6       159476.70
## 2  3929788     2025-01-06      109.0774       210494.3       112793.90
## 3  3904152     2025-01-06     9053.8000       159336.3        96391.84
## 4   155002     2025-01-06     4845.4059       142955.7        98476.90
## 5  3678055     2025-01-06    15968.4000       198139.2        89879.27

9.2.3 EXPORTAR A CSV

# Exportar a CSV si deseas
write.csv(tabla_simulacion_completa, "simulaciones_todas.csv", row.names = FALSE)
write.csv(tabla_precios_optimos, "precios_optimos_resumen.csv", row.names = FALSE)

9.3 SIMULACIÓN DE DIFERENTES DESCUENTOS

9.3.1 ENCONTRAR DESCUENTOS RECURRENTES

rangos_descuento <- datos %>%
  group_by(ID_Inventario) %>%
  summarise(
    Desc_min = quantile(Descuento_Semana_Anterior, 0.05, na.rm = TRUE),
    Desc_max = quantile(Descuento_Semana_Anterior, 0.95, na.rm = TRUE)
  )

9.3.2 SIMULAR DIFERENTES DESCUENTOS A LOS PRECIOS ÓPTIMOS

# Crear lista con simulaciones por producto
simular_descuentos <- function(product_id, modelo, precio_optimo, datos_base, n_sim = 10) {
  datos_producto <- datos_base %>% filter(ID_Inventario == as.numeric(product_id))
  
  # Última fila observada (corregido sin slice)
  ultima_fila <- datos_producto %>%
    arrange(desc(Semana)) %>%
    head(1)
  
  # Rango de descuentos históricos
  r <- rangos_descuento %>% filter(ID_Inventario == as.numeric(product_id))
  descuentos_simulados <- seq(r$Desc_min, r$Desc_max, length.out = n_sim)
  
  # Datos simulados
  simulacion <- data.frame(
    Producto = product_id,
    Precio_Lista_Unitario = precio_optimo,
    Descuento_Semana_Anterior = descuentos_simulados
  ) %>%
    mutate(
      Semana = ultima_fila$Semana + 1,
      Trx_Fecha = max(datos_producto$Trx_Fecha, na.rm = TRUE) + 7,
      Precio_Final_Unitario = Precio_Lista_Unitario * (1 - Descuento_Semana_Anterior),
      Venta_Semana_Anterior = ultima_fila$Venta_Semana_Anterior,
      Cant_Semana_Anterior = ultima_fila$Cant_Semana_Anterior,
      Costo_Unitario_Semana_Anterior = ultima_fila$Costo_Unitario_Semana_Anterior
    )
  
  # Variables usadas en el modelo
  columnas_modelo <- c(
    "Precio_Lista_Unitario",
    "Venta_Semana_Anterior",
    "Cant_Semana_Anterior",
    "Descuento_Semana_Anterior",
    "Costo_Unitario_Semana_Anterior",
    "Semana"
  )
  
  # Predicción y margen
  X_sim <- simulacion %>% select(all_of(columnas_modelo)) %>% as.matrix()
  simulacion$Venta_Predicha <- predict(modelo, newdata = X_sim)
  simulacion$Margen_Esperado <- simulacion$Venta_Predicha -
    simulacion$Costo_Unitario_Semana_Anterior * simulacion$Cant_Semana_Anterior
  
  return(simulacion)
}

9.3.3 PREDECIR VENTA PARA CADA DESCUENTO CON MODELO DE XGBOOST

# Generar simulaciones de descuentos para todos los productos con modelo XGBoost
simulaciones_descuentos <- bind_rows(
  lapply(names(modelos_xgb_final), function(id) {
    precio_optimo <- tabla_precios_optimos %>%
      filter(Producto == id) %>%
      pull(Precio_Optimo)

    simular_descuentos(
      product_id = id,
      modelo = modelos_xgb_final[[id]],
      precio_optimo = precio_optimo,
      datos_base = datos
    )
  })
)

# Crear tabla final para análisis o Power BI
tabla_descuentos_final <- simulaciones_descuentos %>%
  select(
    Producto,
    Precio_Lista_Unitario,
    Descuento_Semana_Anterior,
    Precio_Final_Unitario,
    Venta_Predicha,
    Margen_Esperado,
    Semana_Simulada = Semana,
    Trx_Fecha
  )

# Mostrar
print(tabla_descuentos_final)
##    Producto Precio_Lista_Unitario Descuento_Semana_Anterior
## 1    155001             4355.1946                 0.8467751
## 2    155001             4355.1946                 0.8539122
## 3    155001             4355.1946                 0.8610492
## 4    155001             4355.1946                 0.8681863
## 5    155001             4355.1946                 0.8753233
## 6    155001             4355.1946                 0.8824604
## 7    155001             4355.1946                 0.8895974
## 8    155001             4355.1946                 0.8967345
## 9    155001             4355.1946                 0.9038715
## 10   155001             4355.1946                 0.9110086
## 11  3929788              109.0774                 0.5951061
## 12  3929788              109.0774                 0.5982788
## 13  3929788              109.0774                 0.6014515
## 14  3929788              109.0774                 0.6046243
## 15  3929788              109.0774                 0.6077970
## 16  3929788              109.0774                 0.6109697
## 17  3929788              109.0774                 0.6141424
## 18  3929788              109.0774                 0.6173151
## 19  3929788              109.0774                 0.6204879
## 20  3929788              109.0774                 0.6236606
## 21  3904152             9053.8000                 0.6347199
## 22  3904152             9053.8000                 0.6395330
## 23  3904152             9053.8000                 0.6443462
## 24  3904152             9053.8000                 0.6491594
## 25  3904152             9053.8000                 0.6539725
## 26  3904152             9053.8000                 0.6587857
## 27  3904152             9053.8000                 0.6635989
## 28  3904152             9053.8000                 0.6684120
## 29  3904152             9053.8000                 0.6732252
## 30  3904152             9053.8000                 0.6780384
## 31   155002             4845.4059                 0.8495091
## 32   155002             4845.4059                 0.8564245
## 33   155002             4845.4059                 0.8633399
## 34   155002             4845.4059                 0.8702553
## 35   155002             4845.4059                 0.8771707
## 36   155002             4845.4059                 0.8840861
## 37   155002             4845.4059                 0.8910015
## 38   155002             4845.4059                 0.8979169
## 39   155002             4845.4059                 0.9048324
## 40   155002             4845.4059                 0.9117478
## 41  3678055            15968.4000                 0.6340998
## 42  3678055            15968.4000                 0.6392601
## 43  3678055            15968.4000                 0.6444205
## 44  3678055            15968.4000                 0.6495808
## 45  3678055            15968.4000                 0.6547412
## 46  3678055            15968.4000                 0.6599016
## 47  3678055            15968.4000                 0.6650619
## 48  3678055            15968.4000                 0.6702223
## 49  3678055            15968.4000                 0.6753827
## 50  3678055            15968.4000                 0.6805430
##    Precio_Final_Unitario Venta_Predicha Margen_Esperado Semana_Simulada
## 1              667.32415       269381.6       177384.74             106
## 2              636.24090       269381.6       177384.74             106
## 3              605.15765       271662.0       179665.12             106
## 4              574.07440       271662.0       179665.12             106
## 5              542.99115       271662.0       179665.12             106
## 6              511.90791       271662.0       179665.12             106
## 7              480.82466       271662.0       179665.12             106
## 8              449.74141       269093.3       177096.40             106
## 9              418.65816       269093.3       177096.40             106
## 10             387.57491       245710.2       153713.34             106
## 11              44.16476       210494.3       112793.90             106
## 12              43.81868       210196.6       112496.18             106
## 13              43.47261       215483.8       117783.35             106
## 14              43.12654       224006.4       126305.96             106
## 15              42.78047       234689.0       136988.59             106
## 16              42.43440       233632.5       135932.10             106
## 17              42.08832       232267.2       134566.84             106
## 18              41.74225       236398.7       138698.28             106
## 19              41.39618       235936.2       138235.78             106
## 20              41.05011       235936.2       138235.78             106
## 21            3307.17324       163836.4       100891.92             106
## 22            3263.59581       162644.8        99700.36             106
## 23            3220.01837       162644.8        99700.36             106
## 24            3176.44094       162644.8        99700.36             106
## 25            3132.86351       163903.0       100958.55             106
## 26            3089.28607       160996.2        98051.75             106
## 27            3045.70864       159336.3        96391.84             106
## 28            3002.13120       159336.3        96391.84             106
## 29            2958.55377       159336.3        96391.84             106
## 30            2914.97634       142735.7        79791.30             106
## 31             729.18947       125074.2        80595.32             106
## 32             695.68152       136966.0        92487.12             106
## 33             662.17357       136966.0        92487.12             106
## 34             628.66563       136966.0        92487.12             106
## 35             595.15768       143229.6        98750.80             106
## 36             561.64973       143229.6        98750.80             106
## 37             528.14178       139715.8        95236.96             106
## 38             494.63384       139715.8        95236.96             106
## 39             461.12589       137669.7        93190.87             106
## 40             427.61794       142955.7        98476.90             106
## 41            5842.84153       210851.4       102591.55             106
## 42            5760.43877       208761.7       100501.85             106
## 43            5678.03601       210637.1       102377.18             106
## 44            5595.63326       210637.1       102377.18             106
## 45            5513.23050       210452.2       102192.30             106
## 46            5430.82774       205363.9        97104.01             106
## 47            5348.42498       199879.7        91619.85             106
## 48            5266.02223       180325.8        72065.90             106
## 49            5183.61947       179498.3        71238.40             106
## 50            5101.21671       179498.3        71238.40             106
##     Trx_Fecha
## 1  2025-01-06
## 2  2025-01-06
## 3  2025-01-06
## 4  2025-01-06
## 5  2025-01-06
## 6  2025-01-06
## 7  2025-01-06
## 8  2025-01-06
## 9  2025-01-06
## 10 2025-01-06
## 11 2025-01-06
## 12 2025-01-06
## 13 2025-01-06
## 14 2025-01-06
## 15 2025-01-06
## 16 2025-01-06
## 17 2025-01-06
## 18 2025-01-06
## 19 2025-01-06
## 20 2025-01-06
## 21 2025-01-06
## 22 2025-01-06
## 23 2025-01-06
## 24 2025-01-06
## 25 2025-01-06
## 26 2025-01-06
## 27 2025-01-06
## 28 2025-01-06
## 29 2025-01-06
## 30 2025-01-06
## 31 2025-01-06
## 32 2025-01-06
## 33 2025-01-06
## 34 2025-01-06
## 35 2025-01-06
## 36 2025-01-06
## 37 2025-01-06
## 38 2025-01-06
## 39 2025-01-06
## 40 2025-01-06
## 41 2025-01-06
## 42 2025-01-06
## 43 2025-01-06
## 44 2025-01-06
## 45 2025-01-06
## 46 2025-01-06
## 47 2025-01-06
## 48 2025-01-06
## 49 2025-01-06
## 50 2025-01-06

9.3.4 EXPORTAR A CSV

# Exportar la tabla final de descuentos simulados
write.csv(tabla_descuentos_final, "tabla_descuentos_simulados.csv", row.names = FALSE)

cat("Archivo 'tabla_descuentos_simulados.csv' guardado exitosamente en el directorio de trabajo.\n")
## Archivo 'tabla_descuentos_simulados.csv' guardado exitosamente en el directorio de trabajo.
LS0tCnRpdGxlOiAiPHNwYW4gc3R5bGU9J2NvbG9yOiBicm93bjsnPkVWSURFTkNJQSBOT1ZFTTwvc3Bhbj4iCmF1dGhvcjogIkVxdWlwbyA0IgpkYXRlOiAiMjAyNC0wMy0wNCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUgCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRoZW1lOiBqb3VybmFsCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKZWRpdG9yX3M6IAogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQotLS0KCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibGFjazsiPiAgTElCUkVSw41BUyA8L3NwYW4+IApgYGB7cn0KIyBMaWJyZXLDrWFzIG5lY2VzYXJpYXMKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkocmVhZHhsKQpsaWJyYXJ5KHB1cnJyKQpsaWJyYXJ5KGtuaXRyKQojaW5zdGFsbC5wYWNrYWdlcygia2FibGVFeHRyYSIpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeShnZ3Bsb3QyKQojaW5zdGFsbC5wYWNrYWdlcygiaWdyYXBoIikKbGlicmFyeShpZ3JhcGgpCiNpbnN0YWxsLnBhY2thZ2VzKCJmb3JlY2FzdCIpCiNpbnN0YWxsLnBhY2thZ2VzKCJsdWJyaWRhdGUiKQpsaWJyYXJ5KGZvcmVjYXN0KQpsaWJyYXJ5KGx1YnJpZGF0ZSkKbGlicmFyeShjb3JycGxvdCkKbGlicmFyeShSQ29sb3JCcmV3ZXIpCiNpbnN0YWxsLnBhY2thZ2VzKCJnZ2NvcnJwbG90IikKbGlicmFyeShnZ2NvcnJwbG90KQpsaWJyYXJ5KGNhcmV0KQpsaWJyYXJ5KGNhcikKbGlicmFyeShyYW5kb21Gb3Jlc3QpCiNpbnN0YWxsLnBhY2thZ2VzKCJ4Z2Jvb3N0IikKbGlicmFyeSh4Z2Jvb3N0KQojaW5zdGFsbC5wYWNrYWdlcygicGF0Y2h3b3JrIikKbGlicmFyeShwYXRjaHdvcmspCmxpYnJhcnkoZHBseXIpCiNpbnN0YWxsLnBhY2thZ2VzKCJzY2FsZXMiKQpsaWJyYXJ5IChnZ3Bsb3QyKQpgYGAKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibGFjazsiPiAgQ0FSR0EgREUgREFUT1MgPC9zcGFuPiAKYGBge3J9CiMgTGVlciBkYXRvcwpkYXRvcyA8LSByZWFkX2V4Y2VsKCIvVXNlcnMvbWFyaWFkZWxib3NxdWUvRG93bmxvYWRzL25vdmVtX2RhdGFfZmlsdGVyZWQueGxzeCIpICU+JQogIG11dGF0ZShUcnhfRmVjaGEgPSBhcy5EYXRlKFRyeF9GZWNoYSkpCgojIFBhcnRpY2nDs24gdGVtcG9yYWwKdHJhaW4gPC0gZGF0b3MgJT4lIGZpbHRlcihUcnhfRmVjaGEgPj0gYXMuRGF0ZSgiMjAyMy0wMS0wMSIpICYgVHJ4X0ZlY2hhIDwgYXMuRGF0ZSgiMjAyNC0xMC0wMSIpKQp0ZXN0ICA8LSBkYXRvcyAlPiUgZmlsdGVyKFRyeF9GZWNoYSA+PSBhcy5EYXRlKCIyMDI0LTEwLTAxIikgJiBUcnhfRmVjaGEgPD0gYXMuRGF0ZSgiMjAyNC0xMi0zMSIpKQoKIyBWYWxpZGFjacOzbiByw6FwaWRhCnN1bW1hcnkodHJhaW4kVHJ4X0ZlY2hhKQpzdW1tYXJ5KHRlc3QkVHJ4X0ZlY2hhKQpgYGAKCmBgYHtyfQojIFZlciB0b2RvcyBsb3MgcHJvZHVjdG9zIG9yZGVuYWRvcyBwb3IgdmVudGFzIHRvdGFsZXMKdG9wX2lkcyA8LSBkYXRvcyAlPiUKICBncm91cF9ieShJRF9JbnZlbnRhcmlvKSAlPiUKICBzdW1tYXJpc2UoVmVudGFzX1RvdGFsZXMgPSBzdW0oVmVudGEsIG5hLnJtID0gVFJVRSkpICU+JQogIGFycmFuZ2UoZGVzYyhWZW50YXNfVG90YWxlcykpCgpwcmludCgiUHJvZHVjdG9zIG9yZGVuYWRvcyBwb3IgdmVudGFzIHRvdGFsZXM6IikKcHJpbnQodG9wX2lkcykKYGBgCgpgYGB7cn0KZGF0b3NfZmlsdHJhZG9zIDwtIHRyYWluICAjIHVzYW1vcyB0b2RvIGVsIHRyYWluCiMgQ29udGFyIG9ic2VydmFjaW9uZXMgcG9yIHByb2R1Y3RvCmNvbnRlbyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgY291bnQoSURfSW52ZW50YXJpbywgc29ydCA9IFRSVUUpCnByaW50KCJOw7ptZXJvIGRlIHJlZ2lzdHJvcyBwb3IgcHJvZHVjdG8gZW4gZGF0b3NfZmlsdHJhZG9zOiIpCnByaW50KGNvbnRlbykKIyBWYWxpZGFjacOzbiBkZSBxdWUgaGF5YSBkYXRvcwppZiAobnJvdyhkYXRvc19maWx0cmFkb3MpID09IDApIHsKICBzdG9wKCJObyBoYXkgZGF0b3Mgc3VmaWNpZW50ZXMgZW4gZWwgY29uanVudG8gZGUgZW50cmVuYW1pZW50by4iKX0KYGBgCgojIDxzcGFuIHN0eWxlPSJjb2xvcjogYmxhY2s7Ij4gUFJFRElDQ0lPTkVTIERFIFZFTlRBUwojIDxzcGFuIHN0eWxlPSJjb2xvcjogYmxhY2s7Ij4gQVJNQSAKIyMgUFJPRFVDVE8gMTU1MDAxCiMjIyBUUkFJTgpgYGB7ciBhcm1hLTE1NTAwMX0KIyBQcm9kdWN0byAxNTUwMDEKaWRfcHJvZCA8LSAxNTUwMDEKCiMgQXNlZ3VyYXJzZSBkZSBxdWUgbWV0cmljYXNfY29tcGFyYXRpdmFzIGVzdMOpIGluaWNpYWxpemFkYQppZiAoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBSTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgojIEZpbHRyYXIgZGF0b3MgZGVsIHByb2R1Y3RvICh5YSBlc3TDoW4gYWdyZWdhZG9zIHBvciBzZW1hbmEpCnZlbnRhc19zZW1hbmFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IGlkX3Byb2QpICU+JQogIGFycmFuZ2UoVHJ4X0ZlY2hhKSAlPiUKICBzZWxlY3QoVHJ4X0ZlY2hhLCBWZW50YSkKCiMgQ3JlYXIgc2VyaWUgdGVtcG9yYWwgc2VtYW5hbApzZXJpZV90cyA8LSB0cyh2ZW50YXNfc2VtYW5hbGVzJFZlbnRhLCBmcmVxdWVuY3kgPSA1MiwKICAgICAgICAgICAgICAgc3RhcnQgPSBjKHllYXIobWluKHZlbnRhc19zZW1hbmFsZXMkVHJ4X0ZlY2hhKSksCiAgICAgICAgICAgICAgICAgICAgICAgICBpc293ZWVrKG1pbih2ZW50YXNfc2VtYW5hbGVzJFRyeF9GZWNoYSkpKSkKCiMgQWp1c3RhciBtb2RlbG8gQVJNQQptb2RlbG9fYXJtYV8xNTUwMDEgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQpmb3JlY2FzdF9tb2RlbG9fYXJtYV8xNTUwMDEgPC0gZm9yZWNhc3QobW9kZWxvX2FybWFfMTU1MDAxLCBoID0gNCkKCiMgR3LDoWZpY28gZGVsIHByb27Ds3N0aWNvCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsb19hcm1hXzE1NTAwMSkgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gc2VtYW5hbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksCiAgICAgICB4ID0gIlNlbWFuYSIsIHkgPSAiVmVudGFzICgkKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzIGRlIGRlc2VtcGXDsW8KZml0dGVkX2FybWFfMTU1MDAxIDwtIGZpdHRlZChtb2RlbG9fYXJtYV8xNTUwMDEpCm1hcGVfYXJtYV8xNTUwMDEgPC0gbWVhbihhYnMoKHNlcmllX3RzIC0gZml0dGVkX2FybWFfMTU1MDAxKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDAKcm1zZV9hcm1hXzE1NTAwMSA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX2FybWFfMTU1MDAxKV4yKSkKCiMgUmVnaXN0cmFyIGVuIHRhYmxhIGdlbmVyYWwgZGUgbcOpdHJpY2FzCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSBpZF9wcm9kLAogIE1vZGVsbyA9ICJBUk1BIiwKICBNQVBFID0gbWFwZV9hcm1hXzE1NTAwMSwKICBSTVNFID0gcm1zZV9hcm1hXzE1NTAwMQopKQpgYGAKCiMjIyBURVNUIApgYGB7cn0KIyBDcmVhciBjb25qdW50byBkZSBwcnVlYmEgcGFyYSBwcm9kdWN0byAxNTUwMDEgKHNvbG8gY29sdW1uYXMgbmVjZXNhcmlhcyBwYXJhIEFSTUEpCnRlc3RfMTU1MDAxIDwtIHRlc3QgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMTU1MDAxKSAlPiUKICBzZWxlY3QoU2VtYW5hLCBWZW50YSkgJT4lCiAgYXJyYW5nZShTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCmBgYHtyfQojIEV2YWx1YXIgQVJNQSBjb250cmEgZGF0b3MgcmVhbGVzIGRlbCB0ZXN0IChwcmltZXJhcyA0IHNlbWFuYXMgZGVsIHRlc3QpCnZlbnRhc19yZWFsZXNfdGVzdF8xNTUwMDEgPC0gdGVzdF8xNTUwMDEgJT4lCiAgc2xpY2VfaGVhZChuID0gNCkgJT4lCiAgcHVsbChWZW50YSkKCnZlbnRhc19wcmVkaWNoYXNfYXJtYV8xNTUwMDEgPC0gYXMubnVtZXJpYyhmb3JlY2FzdF9tb2RlbG9fYXJtYV8xNTUwMDEkbWVhbikKCiMgQ29tcGFyYXIgc29sbyBzaSBoYXkgNCBzZW1hbmFzIGRpc3BvbmlibGVzCmlmIChsZW5ndGgodmVudGFzX3JlYWxlc190ZXN0XzE1NTAwMSkgPT0gbGVuZ3RoKHZlbnRhc19wcmVkaWNoYXNfYXJtYV8xNTUwMDEpKSB7CiAgCiAgbWFwZV9hcm1hX3Rlc3RfMTU1MDAxIDwtIG1lYW4oYWJzKCh2ZW50YXNfcmVhbGVzX3Rlc3RfMTU1MDAxIC0gdmVudGFzX3ByZWRpY2hhc19hcm1hXzE1NTAwMSkgLwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBtYXgodmVudGFzX3JlYWxlc190ZXN0XzE1NTAwMSwgMC4wMSkpKSAqIDEwMAogIAogIHJtc2VfYXJtYV90ZXN0XzE1NTAwMSA8LSBzcXJ0KG1lYW4oKHZlbnRhc19yZWFsZXNfdGVzdF8xNTUwMDEgLSB2ZW50YXNfcHJlZGljaGFzX2FybWFfMTU1MDAxKV4yKSkKICAKICBjYXQoIk1BUEUgQVJNQSAodGVzdCAtIHByb2R1Y3RvIDE1NTAwMSk6Iiwgcm91bmQobWFwZV9hcm1hX3Rlc3RfMTU1MDAxLCAyKSwgIlxuIikKICBjYXQoIlJNU0UgQVJNQSAodGVzdCAtIHByb2R1Y3RvIDE1NTAwMSk6Iiwgcm91bmQocm1zZV9hcm1hX3Rlc3RfMTU1MDAxLCAyKSwgIlxuIikKICAKICAjIEd1YXJkYXIgZW4gdGFibGEgZGUgbcOpdHJpY2FzCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gaWRfcHJvZCwKICAgIE1vZGVsbyA9ICJBUk1BIChUZXN0KSIsCiAgICBNQVBFID0gbWFwZV9hcm1hX3Rlc3RfMTU1MDAxLAogICAgUk1TRSA9IHJtc2VfYXJtYV90ZXN0XzE1NTAwMQogICkpCiAgCn0gZWxzZSB7CiAgd2FybmluZygiTm8gaGF5IHN1ZmljaWVudGVzIHNlbWFuYXMgZW4gZWwgdGVzdCBwYXJhIGNvbXBhcmFyIGNvbiBlbCBmb3JlY2FzdCBkZSBBUk1BLiIpCn0KYGBgCgojIyBQUk9EVUNUTyAzOTI5Nzg4CiMjIyBUUkFJTgpgYGB7ciBhcm1hLTM5Mjk3ODh9CiMgUHJvZHVjdG8gMzkyOTc4OAppZF9wcm9kIDwtIDM5Mjk3ODgKCiMgRmlsdHJhciBkYXRvcyBkZWwgcHJvZHVjdG8KdmVudGFzX3NlbWFuYWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgYXJyYW5nZShUcnhfRmVjaGEpICU+JQogIHNlbGVjdChUcnhfRmVjaGEsIFZlbnRhKQoKIyBDcmVhciBzZXJpZSB0ZW1wb3JhbCBzZW1hbmFsCnNlcmllX3RzIDwtIHRzKHZlbnRhc19zZW1hbmFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDUyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX3NlbWFuYWxlcyRUcnhfRmVjaGEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGlzb3dlZWsobWluKHZlbnRhc19zZW1hbmFsZXMkVHJ4X0ZlY2hhKSkpKQoKIyBBanVzdGFyIG1vZGVsbyBBUk1BCm1vZGVsb19hcm1hXzM5Mjk3ODggPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQpmb3JlY2FzdF9tb2RlbG9fYXJtYV8zOTI5Nzg4IDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hXzM5Mjk3ODgsIGggPSA0KQoKIyBHcsOhZmljbyBkZWwgcHJvbsOzc3RpY28KYXV0b3Bsb3QoZm9yZWNhc3RfbW9kZWxvX2FybWFfMzkyOTc4OCkgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gc2VtYW5hbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksCiAgICAgICB4ID0gIlNlbWFuYSIsIHkgPSAiVmVudGFzICgkKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCmZpdHRlZF9hcm1hXzM5Mjk3ODggPC0gZml0dGVkKG1vZGVsb19hcm1hXzM5Mjk3ODgpCm1hcGVfYXJtYV8zOTI5Nzg4IDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF9hcm1hXzM5Mjk3ODgpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMApybXNlX2FybWFfMzkyOTc4OCA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX2FybWFfMzkyOTc4OCleMikpCgojIFJlZ2lzdHJhciBtw6l0cmljYXMgZW4gbGEgdGFibGEgZ2VuZXJhbAptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGVfYXJtYV8zOTI5Nzg4LAogIFJNU0UgPSBybXNlX2FybWFfMzkyOTc4OAopKQpgYGAKCiMjIyBURVNUIApgYGB7cn0KIyBDcmVhciBjb25qdW50byBkZSBwcnVlYmEgcGFyYSBwcm9kdWN0byAxNTUwMDEgKHNvbG8gY29sdW1uYXMgbmVjZXNhcmlhcyBwYXJhIEFSTUEpCnRlc3RfMzkyOTc4OCA8LSB0ZXN0ICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM5Mjk3ODgpICU+JQogIHNlbGVjdChTZW1hbmEsIFZlbnRhKSAlPiUKICBhcnJhbmdlKFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKYGBge3J9CiMgRXZhbHVhciBBUk1BIGNvbnRyYSBkYXRvcyByZWFsZXMgZGVsIHRlc3QgKHByaW1lcmFzIDQgc2VtYW5hcyBkZWwgdGVzdCkKdmVudGFzX3JlYWxlc190ZXN0XzM5Mjk3ODggPC0gdGVzdF8zOTI5Nzg4ICU+JQogIHNsaWNlX2hlYWQobiA9IDQpICU+JQogIHB1bGwoVmVudGEpCgp2ZW50YXNfcHJlZGljaGFzX2FybWFfMzkyOTc4OCA8LSBhcy5udW1lcmljKGZvcmVjYXN0X21vZGVsb19hcm1hXzM5Mjk3ODgkbWVhbikKCiMgQ29tcGFyYXIgc29sbyBzaSBoYXkgNCBzZW1hbmFzIGRpc3BvbmlibGVzCmlmIChsZW5ndGgodmVudGFzX3JlYWxlc190ZXN0XzM5Mjk3ODgpID09IGxlbmd0aCh2ZW50YXNfcHJlZGljaGFzX2FybWFfMzkyOTc4OCkpIHsKICAKICBtYXBlX2FybWFfdGVzdF8zOTI5Nzg4IDwtIG1lYW4oYWJzKCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzkyOTc4OCAtIHZlbnRhc19wcmVkaWNoYXNfYXJtYV8zOTI5Nzg4KSAvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG1heCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzkyOTc4OCwgMC4wMSkpKSAqIDEwMAogIAogIHJtc2VfYXJtYV90ZXN0XzM5Mjk3ODggPC0gc3FydChtZWFuKCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzkyOTc4OCAtIHZlbnRhc19wcmVkaWNoYXNfYXJtYV8zOTI5Nzg4KV4yKSkKICAKICBjYXQoIk1BUEUgQVJNQSAodGVzdCAtIHByb2R1Y3RvIDM5Mjk3ODgpOiIsIHJvdW5kKG1hcGVfYXJtYV90ZXN0XzM5Mjk3ODgsIDIpLCAiXG4iKQogIGNhdCgiUk1TRSBBUk1BICh0ZXN0IC0gcHJvZHVjdG8gMzkyOTc4OCk6Iiwgcm91bmQocm1zZV9hcm1hX3Rlc3RfMzkyOTc4OCwgMiksICJcbiIpCiAgCiAgIyBHdWFyZGFyIGVuIHRhYmxhIGRlIG3DqXRyaWNhcwogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGlkX3Byb2QsCiAgICBNb2RlbG8gPSAiQVJNQSAoVGVzdCkiLAogICAgTUFQRSA9IG1hcGVfYXJtYV90ZXN0XzM5Mjk3ODgsCiAgICBSTVNFID0gcm1zZV9hcm1hX3Rlc3RfMzkyOTc4OAogICkpCiAgCn0gZWxzZSB7CiAgd2FybmluZygiTm8gaGF5IHN1ZmljaWVudGVzIHNlbWFuYXMgZW4gZWwgdGVzdCBwYXJhIGNvbXBhcmFyIGNvbiBlbCBmb3JlY2FzdCBkZSBBUk1BLiIpCn0KYGBgCgojIyBQUk9EVUNUTyAzOTA0MTUyCiMjIyBUUkFJTgpgYGB7ciBhcm1hLTM5MDQxNTJ9CiMgUHJvZHVjdG8gMzkwNDE1MgppZF9wcm9kIDwtIDM5MDQxNTIKCiMgRmlsdHJhciBkYXRvcyBkZWwgcHJvZHVjdG8KdmVudGFzX3NlbWFuYWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgYXJyYW5nZShUcnhfRmVjaGEpICU+JQogIHNlbGVjdChUcnhfRmVjaGEsIFZlbnRhKQoKIyBDcmVhciBzZXJpZSB0ZW1wb3JhbCBzZW1hbmFsCnNlcmllX3RzIDwtIHRzKHZlbnRhc19zZW1hbmFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDUyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX3NlbWFuYWxlcyRUcnhfRmVjaGEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGlzb3dlZWsobWluKHZlbnRhc19zZW1hbmFsZXMkVHJ4X0ZlY2hhKSkpKQoKIyBBanVzdGFyIG1vZGVsbyBBUk1BCm1vZGVsb19hcm1hXzM5MDQxNTIgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQpmb3JlY2FzdF9tb2RlbG9fYXJtYV8zOTA0MTUyIDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hXzM5MDQxNTIsIGggPSA0KQoKIyBHcsOhZmljbyBkZWwgcHJvbsOzc3RpY28KYXV0b3Bsb3QoZm9yZWNhc3RfbW9kZWxvX2FybWFfMzkwNDE1MikgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gc2VtYW5hbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksCiAgICAgICB4ID0gIlNlbWFuYSIsIHkgPSAiVmVudGFzICgkKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCmZpdHRlZF9hcm1hXzM5MDQxNTIgPC0gZml0dGVkKG1vZGVsb19hcm1hXzM5MDQxNTIpCm1hcGVfYXJtYV8zOTA0MTUyIDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF9hcm1hXzM5MDQxNTIpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMApybXNlX2FybWFfMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX2FybWFfMzkwNDE1MileMikpCgojIFJlZ2lzdHJhciBtw6l0cmljYXMgZW4gbGEgdGFibGEgZ2VuZXJhbAptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGVfYXJtYV8zOTA0MTUyLAogIFJNU0UgPSBybXNlX2FybWFfMzkwNDE1MgopKQpgYGAKCiMjIyBURVNUIApgYGB7cn0KIyBDcmVhciBjb25qdW50byBkZSBwcnVlYmEgcGFyYSBwcm9kdWN0byAxNTUwMDEgKHNvbG8gY29sdW1uYXMgbmVjZXNhcmlhcyBwYXJhIEFSTUEpCnRlc3RfMzkwNDE1MiA8LSB0ZXN0ICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM5MDQxNTIpICU+JQogIHNlbGVjdChTZW1hbmEsIFZlbnRhKSAlPiUKICBhcnJhbmdlKFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKYGBge3J9CiMgRXZhbHVhciBBUk1BIGNvbnRyYSBkYXRvcyByZWFsZXMgZGVsIHRlc3QgKHByaW1lcmFzIDQgc2VtYW5hcyBkZWwgdGVzdCkKdmVudGFzX3JlYWxlc190ZXN0XzM5MDQxNTIgPC0gdGVzdF8zOTA0MTUyICU+JQogIHNsaWNlX2hlYWQobiA9IDQpICU+JQogIHB1bGwoVmVudGEpCgp2ZW50YXNfcHJlZGljaGFzX2FybWFfMzkwNDE1MiA8LSBhcy5udW1lcmljKGZvcmVjYXN0X21vZGVsb19hcm1hXzM5MDQxNTIkbWVhbikKCiMgQ29tcGFyYXIgc29sbyBzaSBoYXkgNCBzZW1hbmFzIGRpc3BvbmlibGVzCmlmIChsZW5ndGgodmVudGFzX3JlYWxlc190ZXN0XzM5MDQxNTIpID09IGxlbmd0aCh2ZW50YXNfcHJlZGljaGFzX2FybWFfMzkwNDE1MikpIHsKICAKICBtYXBlX2FybWFfdGVzdF8zOTA0MTUyIDwtIG1lYW4oYWJzKCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzkwNDE1MiAtIHZlbnRhc19wcmVkaWNoYXNfYXJtYV8zOTA0MTUyKSAvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG1heCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzkwNDE1MiwgMC4wMSkpKSAqIDEwMAogIAogIHJtc2VfYXJtYV90ZXN0XzM5MDQxNTIgPC0gc3FydChtZWFuKCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzkwNDE1MiAtIHZlbnRhc19wcmVkaWNoYXNfYXJtYV8zOTA0MTUyKV4yKSkKICAKICBjYXQoIk1BUEUgQVJNQSAodGVzdCAtIHByb2R1Y3RvIDM5MDQxNTIpOiIsIHJvdW5kKG1hcGVfYXJtYV90ZXN0XzM5MDQxNTIsIDIpLCAiXG4iKQogIGNhdCgiUk1TRSBBUk1BICh0ZXN0IC0gcHJvZHVjdG8gMzkwNDE1Mik6Iiwgcm91bmQocm1zZV9hcm1hX3Rlc3RfMzkwNDE1MiwgMiksICJcbiIpCiAgCiAgIyBHdWFyZGFyIGVuIHRhYmxhIGRlIG3DqXRyaWNhcwogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGlkX3Byb2QsCiAgICBNb2RlbG8gPSAiQVJNQSAoVGVzdCkiLAogICAgTUFQRSA9IG1hcGVfYXJtYV90ZXN0XzM5MDQxNTIsCiAgICBSTVNFID0gcm1zZV9hcm1hX3Rlc3RfMzkwNDE1MgogICkpCiAgCn0gZWxzZSB7CiAgd2FybmluZygiTm8gaGF5IHN1ZmljaWVudGVzIHNlbWFuYXMgZW4gZWwgdGVzdCBwYXJhIGNvbXBhcmFyIGNvbiBlbCBmb3JlY2FzdCBkZSBBUk1BLiIpCn0KYGBgCgojIyBQUk9EVUNUTyAxNTUwMDIKIyMjIFRSQUlOIApgYGB7ciBhcm1hLTE1NTAwMn0KIyBQcm9kdWN0byAxNTUwMDIKaWRfcHJvZCA8LSAxNTUwMDIKCiMgRmlsdHJhciBkYXRvcyBkZWwgcHJvZHVjdG8KdmVudGFzX3NlbWFuYWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgYXJyYW5nZShUcnhfRmVjaGEpICU+JQogIHNlbGVjdChUcnhfRmVjaGEsIFZlbnRhKQoKIyBDcmVhciBzZXJpZSB0ZW1wb3JhbCBzZW1hbmFsCnNlcmllX3RzIDwtIHRzKHZlbnRhc19zZW1hbmFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDUyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX3NlbWFuYWxlcyRUcnhfRmVjaGEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGlzb3dlZWsobWluKHZlbnRhc19zZW1hbmFsZXMkVHJ4X0ZlY2hhKSkpKQoKIyBBanVzdGFyIG1vZGVsbyBBUk1BCm1vZGVsb19hcm1hXzE1NTAwMiA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpCmZvcmVjYXN0X21vZGVsb19hcm1hXzE1NTAwMiA8LSBmb3JlY2FzdChtb2RlbG9fYXJtYV8xNTUwMDIsIGggPSA0KQoKIyBHcsOhZmljbyBkZWwgcHJvbsOzc3RpY28KYXV0b3Bsb3QoZm9yZWNhc3RfbW9kZWxvX2FybWFfMTU1MDAyKSArCiAgbGFicyh0aXRsZSA9IHBhc3RlKCJQcm9uw7NzdGljbyBzZW1hbmFsIGRlIHZlbnRhcyAtIEFSTUEgKFByb2R1Y3RvIiwgaWRfcHJvZCwgIikiKSwKICAgICAgIHggPSAiU2VtYW5hIiwgeSA9ICJWZW50YXMgKCQpIikgKwogIHRoZW1lX21pbmltYWwoKQoKIyBDYWxjdWxhciBtw6l0cmljYXMKZml0dGVkX2FybWFfMTU1MDAyIDwtIGZpdHRlZChtb2RlbG9fYXJtYV8xNTUwMDIpCm1hcGVfYXJtYV8xNTUwMDIgPC0gbWVhbihhYnMoKHNlcmllX3RzIC0gZml0dGVkX2FybWFfMTU1MDAyKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDAKcm1zZV9hcm1hXzE1NTAwMiA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX2FybWFfMTU1MDAyKV4yKSkKCiMgUmVnaXN0cmFyIG3DqXRyaWNhcyBlbiBsYSB0YWJsYSBnZW5lcmFsCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSBpZF9wcm9kLAogIE1vZGVsbyA9ICJBUk1BIiwKICBNQVBFID0gbWFwZV9hcm1hXzE1NTAwMiwKICBSTVNFID0gcm1zZV9hcm1hXzE1NTAwMgopKQpgYGAKCiMjIyBURVNUIApgYGB7cn0KIyBDcmVhciBjb25qdW50byBkZSBwcnVlYmEgcGFyYSBwcm9kdWN0byAxNTUwMDEgKHNvbG8gY29sdW1uYXMgbmVjZXNhcmlhcyBwYXJhIEFSTUEpCnRlc3RfMTU1MDAyIDwtIHRlc3QgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMTU1MDAyKSAlPiUKICBzZWxlY3QoU2VtYW5hLCBWZW50YSkgJT4lCiAgYXJyYW5nZShTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCmBgYHtyfQojIEV2YWx1YXIgQVJNQSBjb250cmEgZGF0b3MgcmVhbGVzIGRlbCB0ZXN0IChwcmltZXJhcyA0IHNlbWFuYXMgZGVsIHRlc3QpCnZlbnRhc19yZWFsZXNfdGVzdF8xNTUwMDIgPC0gdGVzdF8xNTUwMDIgJT4lCiAgc2xpY2VfaGVhZChuID0gNCkgJT4lCiAgcHVsbChWZW50YSkKCnZlbnRhc19wcmVkaWNoYXNfYXJtYV8xNTUwMDIgPC0gYXMubnVtZXJpYyhmb3JlY2FzdF9tb2RlbG9fYXJtYV8xNTUwMDIkbWVhbikKCiMgQ29tcGFyYXIgc29sbyBzaSBoYXkgNCBzZW1hbmFzIGRpc3BvbmlibGVzCmlmIChsZW5ndGgodmVudGFzX3JlYWxlc190ZXN0XzE1NTAwMikgPT0gbGVuZ3RoKHZlbnRhc19wcmVkaWNoYXNfYXJtYV8xNTUwMDIpKSB7CiAgCiAgbWFwZV9hcm1hX3Rlc3RfMTU1MDAyIDwtIG1lYW4oYWJzKCh2ZW50YXNfcmVhbGVzX3Rlc3RfMTU1MDAyIC0gdmVudGFzX3ByZWRpY2hhc19hcm1hXzE1NTAwMikgLwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHBtYXgodmVudGFzX3JlYWxlc190ZXN0XzE1NTAwMiwgMC4wMSkpKSAqIDEwMAogIAogIHJtc2VfYXJtYV90ZXN0XzE1NTAwMiA8LSBzcXJ0KG1lYW4oKHZlbnRhc19yZWFsZXNfdGVzdF8xNTUwMDIgLSB2ZW50YXNfcHJlZGljaGFzX2FybWFfMTU1MDAyKV4yKSkKICAKICBjYXQoIk1BUEUgQVJNQSAodGVzdCAtIHByb2R1Y3RvIDE1NTAwMik6Iiwgcm91bmQobWFwZV9hcm1hX3Rlc3RfMTU1MDAyLCAyKSwgIlxuIikKICBjYXQoIlJNU0UgQVJNQSAodGVzdCAtIHByb2R1Y3RvIDE1NTAwMik6Iiwgcm91bmQocm1zZV9hcm1hX3Rlc3RfMTU1MDAyLCAyKSwgIlxuIikKICAKICAjIEd1YXJkYXIgZW4gdGFibGEgZGUgbcOpdHJpY2FzCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gaWRfcHJvZCwKICAgIE1vZGVsbyA9ICJBUk1BIChUZXN0KSIsCiAgICBNQVBFID0gbWFwZV9hcm1hX3Rlc3RfMTU1MDAyLAogICAgUk1TRSA9IHJtc2VfYXJtYV90ZXN0XzE1NTAwMgogICkpCiAgCn0gZWxzZSB7CiAgd2FybmluZygiTm8gaGF5IHN1ZmljaWVudGVzIHNlbWFuYXMgZW4gZWwgdGVzdCBwYXJhIGNvbXBhcmFyIGNvbiBlbCBmb3JlY2FzdCBkZSBBUk1BLiIpCn0KYGBgCgojIyBQUk9EVUNUTyAzNjc4MDU1CiMjIyBUUkFJTgpgYGB7ciBhcm1hLTM2NzgwNTV9CiMgUHJvZHVjdG8gMzY3ODA1NQppZF9wcm9kIDwtIDM2NzgwNTUKCiMgRmlsdHJhciBkYXRvcyBkZWwgcHJvZHVjdG8KdmVudGFzX3NlbWFuYWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgYXJyYW5nZShUcnhfRmVjaGEpICU+JQogIHNlbGVjdChUcnhfRmVjaGEsIFZlbnRhKQoKIyBDcmVhciBzZXJpZSB0ZW1wb3JhbCBzZW1hbmFsCnNlcmllX3RzIDwtIHRzKHZlbnRhc19zZW1hbmFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDUyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX3NlbWFuYWxlcyRUcnhfRmVjaGEpKSwKICAgICAgICAgICAgICAgICAgICAgICAgIGlzb3dlZWsobWluKHZlbnRhc19zZW1hbmFsZXMkVHJ4X0ZlY2hhKSkpKQoKIyBBanVzdGFyIG1vZGVsbyBBUk1BCm1vZGVsb19hcm1hXzM2NzgwNTUgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQpmb3JlY2FzdF9tb2RlbG9fYXJtYV8zNjc4MDU1IDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hXzM2NzgwNTUsIGggPSA0KQoKIyBHcsOhZmljbyBkZWwgcHJvbsOzc3RpY28KYXV0b3Bsb3QoZm9yZWNhc3RfbW9kZWxvX2FybWFfMzY3ODA1NSkgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gc2VtYW5hbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksCiAgICAgICB4ID0gIlNlbWFuYSIsIHkgPSAiVmVudGFzICgkKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCmZpdHRlZF9hcm1hXzM2NzgwNTUgPC0gZml0dGVkKG1vZGVsb19hcm1hXzM2NzgwNTUpCm1hcGVfYXJtYV8zNjc4MDU1IDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF9hcm1hXzM2NzgwNTUpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMApybXNlX2FybWFfMzY3ODA1NSA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX2FybWFfMzY3ODA1NSleMikpCgojIFJlZ2lzdHJhciBtw6l0cmljYXMgZW4gbGEgdGFibGEgZ2VuZXJhbAptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGVfYXJtYV8zNjc4MDU1LAogIFJNU0UgPSBybXNlX2FybWFfMzY3ODA1NQopKQpgYGAKCiMjIyBURVNUIApgYGB7cn0KIyBDcmVhciBjb25qdW50byBkZSBwcnVlYmEgcGFyYSBwcm9kdWN0byAxNTUwMDEgKHNvbG8gY29sdW1uYXMgbmVjZXNhcmlhcyBwYXJhIEFSTUEpCnRlc3RfMzY3ODA1NSA8LSB0ZXN0ICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM2NzgwNTUpICU+JQogIHNlbGVjdChTZW1hbmEsIFZlbnRhKSAlPiUKICBhcnJhbmdlKFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKYGBge3J9CiMgRXZhbHVhciBBUk1BIGNvbnRyYSBkYXRvcyByZWFsZXMgZGVsIHRlc3QgKHByaW1lcmFzIDQgc2VtYW5hcyBkZWwgdGVzdCkKdmVudGFzX3JlYWxlc190ZXN0XzM2NzgwNTUgPC0gdGVzdF8zNjc4MDU1ICU+JQogIHNsaWNlX2hlYWQobiA9IDQpICU+JQogIHB1bGwoVmVudGEpCgp2ZW50YXNfcHJlZGljaGFzX2FybWFfMzY3ODA1NSA8LSBhcy5udW1lcmljKGZvcmVjYXN0X21vZGVsb19hcm1hXzM2NzgwNTUkbWVhbikKCiMgQ29tcGFyYXIgc29sbyBzaSBoYXkgNCBzZW1hbmFzIGRpc3BvbmlibGVzCmlmIChsZW5ndGgodmVudGFzX3JlYWxlc190ZXN0XzM2NzgwNTUpID09IGxlbmd0aCh2ZW50YXNfcHJlZGljaGFzX2FybWFfMzY3ODA1NSkpIHsKICAKICBtYXBlX2FybWFfdGVzdF8zNjc4MDU1IDwtIG1lYW4oYWJzKCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzY3ODA1NSAtIHZlbnRhc19wcmVkaWNoYXNfYXJtYV8zNjc4MDU1KSAvCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG1heCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzY3ODA1NSwgMC4wMSkpKSAqIDEwMAogIAogIHJtc2VfYXJtYV90ZXN0XzM2NzgwNTUgPC0gc3FydChtZWFuKCh2ZW50YXNfcmVhbGVzX3Rlc3RfMzY3ODA1NSAtIHZlbnRhc19wcmVkaWNoYXNfYXJtYV8zNjc4MDU1KV4yKSkKICAKICBjYXQoIk1BUEUgQVJNQSAodGVzdCAtIHByb2R1Y3RvIDM2NzgwNTUpOiIsIHJvdW5kKG1hcGVfYXJtYV90ZXN0XzM2NzgwNTUsIDIpLCAiXG4iKQogIGNhdCgiUk1TRSBBUk1BICh0ZXN0IC0gcHJvZHVjdG8gMzY3ODA1NSk6Iiwgcm91bmQocm1zZV9hcm1hX3Rlc3RfMzY3ODA1NSwgMiksICJcbiIpCiAgCiAgIyBHdWFyZGFyIGVuIHRhYmxhIGRlIG3DqXRyaWNhcwogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGlkX3Byb2QsCiAgICBNb2RlbG8gPSAiQVJNQSAoVGVzdCkiLAogICAgTUFQRSA9IG1hcGVfYXJtYV90ZXN0XzM2NzgwNTUsCiAgICBSTVNFID0gcm1zZV9hcm1hX3Rlc3RfMzY3ODA1NQogICkpCiAgCn0gZWxzZSB7CiAgd2FybmluZygiTm8gaGF5IHN1ZmljaWVudGVzIHNlbWFuYXMgZW4gZWwgdGVzdCBwYXJhIGNvbXBhcmFyIGNvbiBlbCBmb3JlY2FzdCBkZSBBUk1BLiIpCn0KYGBgCgojIyBNw4lUUklDQVMgQ09NUEFSQVRJVkFTIEFSTUEKYGBge3J9Cm1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUKICBhcnJhbmdlKFByb2R1Y3RvKSAlPiUKICBrbml0cjo6a2FibGUoY2FwdGlvbiA9ICJNw6l0cmljYXMgQVJNQSBwb3IgcHJvZHVjdG8iKSAlPiUKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsYWNrOyI+IFJFR1JFU0lPTiBMSU5FQUwKIyMgTUFQQSBERSBDQUxPUgpgYGB7ciBtYXBhX2NhbG9yX2NvcnJlbGFjaW9uLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIFZhcmlhYmxlcyBudW3DqXJpY2FzIHJlbGV2YW50ZXMgcGFyYSBlbCBtb2RlbG8KdmFyc19udW1lcmljYXMgPC0gYygiVmVudGEiLCAiUHJlY2lvX0xpc3RhX1VuaXRhcmlvIiwgIlZlbnRhX1NlbWFuYV9BbnRlcmlvciIsCiAgICAgICAgICAgICAgICAgICAgIkNhbnRfU2VtYW5hX0FudGVyaW9yIiwgIkRlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IiLAogICAgICAgICAgICAgICAgICAgICJDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IiKQoKIyBQcmVwYXJhY2nDs24gZGUgbG9zIGRhdG9zCmRhdG9zX2NvciA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgc2VsZWN0KGFsbF9vZih2YXJzX251bWVyaWNhcykpICU+JQogIG5hLm9taXQoKQoKIyBHZW5lcmFyIGxhIG1hdHJpeiBkZSBjb3JyZWxhY2nDs24KbWF0cml6X2NvciA8LSBjb3IoZGF0b3NfY29yKQoKIyBNYXBhIGRlIGNhbG9yIGRlIGNvcnJlbGFjaW9uZXMKZ2djb3JycGxvdChtYXRyaXpfY29yLAogICAgICAgICAgIG1ldGhvZCA9ICJzcXVhcmUiLAogICAgICAgICAgIHR5cGUgPSAidXBwZXIiLAogICAgICAgICAgIGxhYiA9IFRSVUUsCiAgICAgICAgICAgbGFiX3NpemUgPSAyLAogICAgICAgICAgIHRsLmNleCA9IDEwLAogICAgICAgICAgIHRsLnNydCA9IDQ1LAogICAgICAgICAgIGNvbG9ycyA9IGMoIiM2RDlFQzEiLCAid2hpdGUiLCAiI0U0NjcyNiIpLAogICAgICAgICAgIHRpdGxlID0gIk1hcGEgZGUgQ29ycmVsYWNpw7NuIC0gVmFyaWFibGVzIGRlbCBNb2RlbG8iLAogICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KSArCiAgICAgICAgICAgICB0aGVtZSgKICAgICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgICAgICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBoanVzdCA9IDEpKQopCmBgYAoKIyMgUFJPRFVDVE8gMTU1MDAxCiMjIyBEQVRPUyBUUkFJTiAxNTUwMDEKYGBge3J9CiMgRGF0b3MgZGUgZW50cmVuYW1pZW50bwpkYXRvc18xNTUwMDEgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDE1NTAwMSkgJT4lCiAgc2VsZWN0KFZlbnRhLCBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sIFZlbnRhX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ2FudF9TZW1hbmFfQW50ZXJpb3IsIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IsCiAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciwgU2VtYW5hKSAlPiUKICBuYS5vbWl0KCkKYGBgCgojIyMgREFUT1MgVEVTVCAxNTUwMDEKYGBge3J9CiMgRGF0b3MgZGUgcHJ1ZWJhCnRlc3RfMTU1MDAxIDwtIHRlc3QgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMTU1MDAxKSAlPiUKICBzZWxlY3QoVmVudGEsIFByZWNpb19MaXN0YV9Vbml0YXJpbywgVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciwgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLCBTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCiMjIyBNT0RFTE8KYGBge3J9CiMgQWp1c3RhciBtb2RlbG8gY29uIGVudHJlbmFtaWVudG8KbW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEgPC0gbG0oCiAgVmVudGEgfiBQcmVjaW9fTGlzdGFfVW5pdGFyaW8gKwogICAgICAgICAgVmVudGFfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIFNlbWFuYSwKICBkYXRhID0gZGF0b3NfMTU1MDAxCikKCiMgVmVyIHJlc3VtZW4Kc3VtbWFyeShtb2RlbG9fcmVncmVzaW9uXzE1NTAwMSkKCiMgUHJlZGljY2lvbmVzCnByZWRfdHJhaW4gPC0gcHJlZGljdChtb2RlbG9fcmVncmVzaW9uXzE1NTAwMSwgbmV3ZGF0YSA9IGRhdG9zXzE1NTAwMSkKcHJlZF90ZXN0ICA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMTU1MDAxLCBuZXdkYXRhID0gdGVzdF8xNTUwMDEpCgojIE3DqXRyaWNhcyAtIGVudHJlbmFtaWVudG8KbWFwZV90cmFpbiA8LSBtZWFuKGFicygoZGF0b3NfMTU1MDAxJFZlbnRhIC0gcHJlZF90cmFpbikgLyBwbWF4KGRhdG9zXzE1NTAwMSRWZW50YSwgMC4wMSkpKSAqIDEwMApybXNlX3RyYWluIDwtIHNxcnQobWVhbigoZGF0b3NfMTU1MDAxJFZlbnRhIC0gcHJlZF90cmFpbileMikpCgojIE3DqXRyaWNhcyAtIHBydWViYQptYXBlX3Rlc3QgPC0gbWVhbihhYnMoKHRlc3RfMTU1MDAxJFZlbnRhIC0gcHJlZF90ZXN0KSAvIHBtYXgodGVzdF8xNTUwMDEkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0IDwtIHNxcnQobWVhbigodGVzdF8xNTUwMDEkVmVudGEgLSBwcmVkX3Rlc3QpXjIpKQoKIyBNb3N0cmFyIHJlc3VsdGFkb3MKY2F0KCJUcmFpbiAtIE1BUEU6Iiwgcm91bmQobWFwZV90cmFpbiwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90cmFpbiwgMiksICJcbiIpCmNhdCgiVGVzdCAgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdGVzdCwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90ZXN0LCAyKSwgIlxuIikKYGBgCgojIyMgVEFCTEEgREUgTcOJVFJJQ0FTIApgYGB7cn0KIyBBZ3JlZ2FyIG3DqXRyaWNhcyBkZWwgZW50cmVuYW1pZW50bwptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMSIsCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIChUcmFpbikiLAogIE1BUEUgPSBtYXBlX3RyYWluLAogIFJNU0UgPSBybXNlX3RyYWluLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopKQoKIyBBZ3JlZ2FyIG3DqXRyaWNhcyBkZWwgdGVzdAptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMSIsCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfdGVzdCwKICBSTVNFID0gcm1zZV90ZXN0LAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopKQpgYGAKCiMjIFBST0RVQ1RPIDM5Mjk3ODgKIyMjIERBVE9TIFRSQUlOIApgYGB7cn0KIyBEYXRvcyBkZSBlbnRyZW5hbWllbnRvCmRhdG9zXzM5Mjk3ODggPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM5Mjk3ODgpICU+JQogIHNlbGVjdChWZW50YSwgUHJlY2lvX0xpc3RhX1VuaXRhcmlvLCBWZW50YV9TZW1hbmFfQW50ZXJpb3IsCiAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yLCBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IsIFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKIyMjIERBVE9TIFRFU1QKYGBge3J9CiMgRGF0b3MgZGUgcHJ1ZWJhCnRlc3RfMzkyOTc4OCA8LSB0ZXN0ICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM5Mjk3ODgpICU+JQogIHNlbGVjdChWZW50YSwgUHJlY2lvX0xpc3RhX1VuaXRhcmlvLCBWZW50YV9TZW1hbmFfQW50ZXJpb3IsCiAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yLCBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IsIFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKIyMjIE1PREVMTwpgYGB7cn0KIyBBanVzdGFyIG1vZGVsbyBjb24gZW50cmVuYW1pZW50bwptb2RlbG9fcmVncmVzaW9uXzM5Mjk3ODggPC0gbG0oCiAgVmVudGEgfiBQcmVjaW9fTGlzdGFfVW5pdGFyaW8gKwogICAgICAgICAgVmVudGFfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIFNlbWFuYSwKICBkYXRhID0gZGF0b3NfMzkyOTc4OAopCgojIFZlciByZXN1bWVuCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4KQoKIyBQcmVkaWNjaW9uZXMKcHJlZF90cmFpbl8zOTI5Nzg4IDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4LCBuZXdkYXRhID0gZGF0b3NfMzkyOTc4OCkKcHJlZF90ZXN0XzM5Mjk3ODggIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4LCBuZXdkYXRhID0gdGVzdF8zOTI5Nzg4KQoKIyBNw6l0cmljYXMgLSBlbnRyZW5hbWllbnRvCm1hcGVfdHJhaW5fMzkyOTc4OCA8LSBtZWFuKGFicygoZGF0b3NfMzkyOTc4OCRWZW50YSAtIHByZWRfdHJhaW5fMzkyOTc4OCkgLyBwbWF4KGRhdG9zXzM5Mjk3ODgkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90cmFpbl8zOTI5Nzg4IDwtIHNxcnQobWVhbigoZGF0b3NfMzkyOTc4OCRWZW50YSAtIHByZWRfdHJhaW5fMzkyOTc4OCleMikpCgojIE3DqXRyaWNhcyAtIHBydWViYQptYXBlX3Rlc3RfMzkyOTc4OCA8LSBtZWFuKGFicygodGVzdF8zOTI5Nzg4JFZlbnRhIC0gcHJlZF90ZXN0XzM5Mjk3ODgpIC8gcG1heCh0ZXN0XzM5Mjk3ODgkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0XzM5Mjk3ODggPC0gc3FydChtZWFuKCh0ZXN0XzM5Mjk3ODgkVmVudGEgLSBwcmVkX3Rlc3RfMzkyOTc4OCleMikpCgojIE1vc3RyYXIgcmVzdWx0YWRvcwpjYXQoIlRyYWluIC0gTUFQRToiLCByb3VuZChtYXBlX3RyYWluXzM5Mjk3ODgsIDIpLCAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdHJhaW5fMzkyOTc4OCwgMiksICJcbiIpCmNhdCgiVGVzdCAgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdGVzdF8zOTI5Nzg4LCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3RfMzkyOTc4OCwgMiksICJcbiIpCmBgYAoKIyMjIFRBQkxBIERFIE3DiVRSSUNBUwpgYGB7cn0KIyBBZ3JlZ2FyIG3DqXRyaWNhcyBkZWwgZW50cmVuYW1pZW50bwptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5Mjk3ODgiLAogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCAoVHJhaW4pIiwKICBNQVBFID0gbWFwZV90cmFpbl8zOTI5Nzg4LAogIFJNU0UgPSBybXNlX3RyYWluXzM5Mjk3ODgsCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCikpCgojIEFncmVnYXIgbcOpdHJpY2FzIGRlbCB0ZXN0Cm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkyOTc4OCIsCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfdGVzdF8zOTI5Nzg4LAogIFJNU0UgPSBybXNlX3Rlc3RfMzkyOTc4OCwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKSkKYGBgCgojIyBQUk9EVUNUTyAzOTA0MTUyCiMjIyBEQVRPUyBUUkFJTiAKYGBge3J9CiMgRGF0b3MgZGUgZW50cmVuYW1pZW50bwpkYXRvc18zOTA0MTUyIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAzOTA0MTUyKSAlPiUKICBzZWxlY3QoVmVudGEsIFByZWNpb19MaXN0YV9Vbml0YXJpbywgVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciwgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLCBTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCiMjIyBEQVRPUyBURVNUCmBgYHtyfQojIERhdG9zIGRlIHBydWViYQp0ZXN0XzM5MDQxNTIgPC0gdGVzdCAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAzOTA0MTUyKSAlPiUKICBzZWxlY3QoVmVudGEsIFByZWNpb19MaXN0YV9Vbml0YXJpbywgVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciwgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLCBTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCiMjIyBNT0RFTE8KYGBge3J9CiMgQWp1c3RhciBtb2RlbG8gY29uIGVudHJlbmFtaWVudG8KbW9kZWxvX3JlZ3Jlc2lvbl8zOTA0MTUyIDwtIGxtKAogIFZlbnRhIH4gUHJlY2lvX0xpc3RhX1VuaXRhcmlvICsKICAgICAgICAgIFZlbnRhX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBTZW1hbmEsCiAgZGF0YSA9IGRhdG9zXzM5MDQxNTIKKQoKIyBWZXIgcmVzdW1lbgpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MikKCiMgUHJlZGljY2lvbmVzCnByZWRfdHJhaW5fMzkwNDE1MiA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MiwgbmV3ZGF0YSA9IGRhdG9zXzM5MDQxNTIpCnByZWRfdGVzdF8zOTA0MTUyICA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MiwgbmV3ZGF0YSA9IHRlc3RfMzkwNDE1MikKCiMgTcOpdHJpY2FzIC0gZW50cmVuYW1pZW50bwptYXBlX3RyYWluXzM5MDQxNTIgPC0gbWVhbihhYnMoKGRhdG9zXzM5MDQxNTIkVmVudGEgLSBwcmVkX3RyYWluXzM5MDQxNTIpIC8gcG1heChkYXRvc18zOTA0MTUyJFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdHJhaW5fMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKGRhdG9zXzM5MDQxNTIkVmVudGEgLSBwcmVkX3RyYWluXzM5MDQxNTIpXjIpKQoKIyBNw6l0cmljYXMgLSBwcnVlYmEKbWFwZV90ZXN0XzM5MDQxNTIgPC0gbWVhbihhYnMoKHRlc3RfMzkwNDE1MiRWZW50YSAtIHByZWRfdGVzdF8zOTA0MTUyKSAvIHBtYXgodGVzdF8zOTA0MTUyJFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdGVzdF8zOTA0MTUyIDwtIHNxcnQobWVhbigodGVzdF8zOTA0MTUyJFZlbnRhIC0gcHJlZF90ZXN0XzM5MDQxNTIpXjIpKQoKIyBNb3N0cmFyIHJlc3VsdGFkb3MKY2F0KCJUcmFpbiAtIE1BUEU6Iiwgcm91bmQobWFwZV90cmFpbl8zOTA0MTUyLCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3RyYWluXzM5MDQxNTIsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3RfMzkwNDE1MiwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90ZXN0XzM5MDQxNTIsIDIpLCAiXG4iKQpgYGAKCiMjIyBUQUJMQSBERSBNw4lUUklDQVMKYGBge3J9CiMgQWdyZWdhciBtw6l0cmljYXMgZGVsIGVudHJlbmFtaWVudG8KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTA0MTUyIiwKICBNb2RlbG8gPSAiUmVncmVzacOzbiBMaW5lYWwgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfdHJhaW5fMzkwNDE1MiwKICBSTVNFID0gcm1zZV90cmFpbl8zOTA0MTUyLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopKQoKIyBBZ3JlZ2FyIG3DqXRyaWNhcyBkZWwgdGVzdAptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5MDQxNTIiLAogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCAoVGVzdCkiLAogIE1BUEUgPSBtYXBlX3Rlc3RfMzkwNDE1MiwKICBSTVNFID0gcm1zZV90ZXN0XzM5MDQxNTIsCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCikpCmBgYAoKIyMgUFJPRFVDVE8gMTU1MDAyCiMjIyBEQVRPUyBUUkFJTgpgYGB7cn0KIyBEYXRvcyBkZSBlbnRyZW5hbWllbnRvCmRhdG9zXzE1NTAwMiA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMTU1MDAyKSAlPiUKICBzZWxlY3QoVmVudGEsIFByZWNpb19MaXN0YV9Vbml0YXJpbywgVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciwgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLCBTZW1hbmEpICU+JQogIG5hLm9taXQoKQpgYGAKCiMjIyBEQVRPUyBURVNUCmBgYHtyfQojIERhdG9zIGRlIHBydWViYQp0ZXN0XzE1NTAwMiA8LSB0ZXN0ICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDE1NTAwMikgJT4lCiAgc2VsZWN0KFZlbnRhLCBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sIFZlbnRhX1NlbWFuYV9BbnRlcmlvciwKICAgICAgICAgQ2FudF9TZW1hbmFfQW50ZXJpb3IsIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IsCiAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciwgU2VtYW5hKSAlPiUKICBuYS5vbWl0KCkKYGBgCgojIyMgTU9ERUxPCmBgYHtyfQojIEFqdXN0YXIgbW9kZWxvIGNvbiBlbnRyZW5hbWllbnRvCm1vZGVsb19yZWdyZXNpb25fMTU1MDAyIDwtIGxtKAogIFZlbnRhIH4gUHJlY2lvX0xpc3RhX1VuaXRhcmlvICsKICAgICAgICAgIFZlbnRhX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBTZW1hbmEsCiAgZGF0YSA9IGRhdG9zXzE1NTAwMgopCgojIFZlciByZXN1bWVuCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDIpCgojIFByZWRpY2Npb25lcwpwcmVkX3RyYWluXzE1NTAwMiA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMTU1MDAyLCBuZXdkYXRhID0gZGF0b3NfMTU1MDAyKQpwcmVkX3Rlc3RfMTU1MDAyICA8LSBwcmVkaWN0KG1vZGVsb19yZWdyZXNpb25fMTU1MDAyLCBuZXdkYXRhID0gdGVzdF8xNTUwMDIpCgojIE3DqXRyaWNhcyAtIGVudHJlbmFtaWVudG8KbWFwZV90cmFpbl8xNTUwMDIgPC0gbWVhbihhYnMoKGRhdG9zXzE1NTAwMiRWZW50YSAtIHByZWRfdHJhaW5fMTU1MDAyKSAvIHBtYXgoZGF0b3NfMTU1MDAyJFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdHJhaW5fMTU1MDAyIDwtIHNxcnQobWVhbigoZGF0b3NfMTU1MDAyJFZlbnRhIC0gcHJlZF90cmFpbl8xNTUwMDIpXjIpKQoKIyBNw6l0cmljYXMgLSBwcnVlYmEKbWFwZV90ZXN0XzE1NTAwMiA8LSBtZWFuKGFicygodGVzdF8xNTUwMDIkVmVudGEgLSBwcmVkX3Rlc3RfMTU1MDAyKSAvIHBtYXgodGVzdF8xNTUwMDIkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0XzE1NTAwMiA8LSBzcXJ0KG1lYW4oKHRlc3RfMTU1MDAyJFZlbnRhIC0gcHJlZF90ZXN0XzE1NTAwMileMikpCgojIE1vc3RyYXIgcmVzdWx0YWRvcwpjYXQoIlRyYWluIC0gTUFQRToiLCByb3VuZChtYXBlX3RyYWluXzE1NTAwMiwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90cmFpbl8xNTUwMDIsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3RfMTU1MDAyLCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3RfMTU1MDAyLCAyKSwgIlxuIikKYGBgCgojIyMgVEFCTEEgREUgTcOJVFJJQ0FTCmBgYHtyfQojIEFncmVnYXIgbcOpdHJpY2FzIGRlbCBlbnRyZW5hbWllbnRvCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAyIiwKICBNb2RlbG8gPSAiUmVncmVzacOzbiBMaW5lYWwgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfdHJhaW5fMTU1MDAyLAogIFJNU0UgPSBybXNlX3RyYWluXzE1NTAwMiwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKSkKCiMgQWdyZWdhciBtw6l0cmljYXMgZGVsIHRlc3QKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIxNTUwMDIiLAogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCAoVGVzdCkiLAogIE1BUEUgPSBtYXBlX3Rlc3RfMTU1MDAyLAogIFJNU0UgPSBybXNlX3Rlc3RfMTU1MDAyLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopKQpgYGAKCgojIyBQUk9EVUNUTyAzNjc4MDU1CiMjIyBEQVRPUyBUUkFJTgpgYGB7cn0KIyBEYXRvcyBkZSBlbnRyZW5hbWllbnRvCmRhdG9zXzM2NzgwNTUgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM2NzgwNTUpICU+JQogIHNlbGVjdChWZW50YSwgUHJlY2lvX0xpc3RhX1VuaXRhcmlvLCBWZW50YV9TZW1hbmFfQW50ZXJpb3IsCiAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yLCBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IsIFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKIyMjIERBVE9TIFRFU1QKYGBge3J9CiMgRGF0b3MgZGUgcHJ1ZWJhCnRlc3RfMzY3ODA1NSA8LSB0ZXN0ICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM2NzgwNTUpICU+JQogIHNlbGVjdChWZW50YSwgUHJlY2lvX0xpc3RhX1VuaXRhcmlvLCBWZW50YV9TZW1hbmFfQW50ZXJpb3IsCiAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yLCBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLAogICAgICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IsIFNlbWFuYSkgJT4lCiAgbmEub21pdCgpCmBgYAoKIyMjIE1PREVMTwpgYGB7cn0KIyBBanVzdGFyIG1vZGVsbyBjb24gZW50cmVuYW1pZW50bwptb2RlbG9fcmVncmVzaW9uXzM2NzgwNTUgPC0gbG0oCiAgVmVudGEgfiBQcmVjaW9fTGlzdGFfVW5pdGFyaW8gKwogICAgICAgICAgVmVudGFfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIFNlbWFuYSwKICBkYXRhID0gZGF0b3NfMzY3ODA1NQopCgojIFZlciByZXN1bWVuCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1KQoKIyBQcmVkaWNjaW9uZXMKcHJlZF90cmFpbl8zNjc4MDU1IDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1LCBuZXdkYXRhID0gZGF0b3NfMzY3ODA1NSkKcHJlZF90ZXN0XzM2NzgwNTUgIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1LCBuZXdkYXRhID0gdGVzdF8zNjc4MDU1KQoKIyBNw6l0cmljYXMgLSBlbnRyZW5hbWllbnRvCm1hcGVfdHJhaW5fMzY3ODA1NSA8LSBtZWFuKGFicygoZGF0b3NfMzY3ODA1NSRWZW50YSAtIHByZWRfdHJhaW5fMzY3ODA1NSkgLyBwbWF4KGRhdG9zXzM2NzgwNTUkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90cmFpbl8zNjc4MDU1IDwtIHNxcnQobWVhbigoZGF0b3NfMzY3ODA1NSRWZW50YSAtIHByZWRfdHJhaW5fMzY3ODA1NSleMikpCgojIE3DqXRyaWNhcyAtIHBydWViYQptYXBlX3Rlc3RfMzY3ODA1NSA8LSBtZWFuKGFicygodGVzdF8zNjc4MDU1JFZlbnRhIC0gcHJlZF90ZXN0XzM2NzgwNTUpIC8gcG1heCh0ZXN0XzM2NzgwNTUkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0XzM2NzgwNTUgPC0gc3FydChtZWFuKCh0ZXN0XzM2NzgwNTUkVmVudGEgLSBwcmVkX3Rlc3RfMzY3ODA1NSleMikpCgojIE1vc3RyYXIgcmVzdWx0YWRvcwpjYXQoIlRyYWluIC0gTUFQRToiLCByb3VuZChtYXBlX3RyYWluXzM2NzgwNTUsIDIpLCAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdHJhaW5fMzY3ODA1NSwgMiksICJcbiIpCmNhdCgiVGVzdCAgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdGVzdF8zNjc4MDU1LCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3RfMzY3ODA1NSwgMiksICJcbiIpCmBgYAoKIyMjIFRBQkxBIERFIE3DiVRSSUNBUwpgYGB7cn0KIyBBZ3JlZ2FyIG3DqXRyaWNhcyBkZWwgZW50cmVuYW1pZW50bwptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM2NzgwNTUiLAogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCAoVHJhaW4pIiwKICBNQVBFID0gbWFwZV90cmFpbl8zNjc4MDU1LAogIFJNU0UgPSBybXNlX3RyYWluXzM2NzgwNTUsCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCikpCgojIEFncmVnYXIgbcOpdHJpY2FzIGRlbCB0ZXN0Cm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzY3ODA1NSIsCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfdGVzdF8zNjc4MDU1LAogIFJNU0UgPSBybXNlX3Rlc3RfMzY3ODA1NSwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKSkKYGBgCgojIyBNw4lUUklDQVMgQVJNQSBZIFJFRyBMSU5FQUwgCmBgYHtyfQptZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lCiAgYXJyYW5nZShQcm9kdWN0bykgJT4lCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSAiTcOpdHJpY2FzIEFSTUEgJiBSZWdyZXNpb24gTGluZWFsIHBvciBwcm9kdWN0byIpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCgojIDxzcGFuIHN0eWxlPSJjb2xvcjogYmxhY2s7Ij4gUkFORE9NIEZPUkVTVAojIyBQUk9EVUNUTyAxNTUwMDEKYGBge3J9CiMgRW50cmVuYW1pZW50bwpzZXQuc2VlZCgxMjMpCm1vZGVsb19yZl8xNTUwMDEgPC0gcmFuZG9tRm9yZXN0KAogIFZlbnRhIH4gUHJlY2lvX0xpc3RhX1VuaXRhcmlvICsKICAgICAgICAgIFZlbnRhX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBTZW1hbmEsCiAgZGF0YSA9IGRhdG9zXzE1NTAwMSwKICBudHJlZSA9IDUwMCwKICBtdHJ5ID0gZmxvb3Ioc3FydCg2KSksICAjIDYgdmFyaWFibGVzIHByZWRpY3RvcmFzCiAgaW1wb3J0YW5jZSA9IFRSVUUKKQoKIyBQcmVkaWNjaW9uZXMKcHJlZF90cmFpbl9yZl8xNTUwMDEgPC0gcHJlZGljdChtb2RlbG9fcmZfMTU1MDAxLCBuZXdkYXRhID0gZGF0b3NfMTU1MDAxKQpwcmVkX3Rlc3RfcmZfMTU1MDAxICA8LSBwcmVkaWN0KG1vZGVsb19yZl8xNTUwMDEsIG5ld2RhdGEgPSB0ZXN0XzE1NTAwMSkKCiMgTcOpdHJpY2FzCm1hcGVfdHJhaW5fcmZfMTU1MDAxIDwtIG1lYW4oYWJzKChkYXRvc18xNTUwMDEkVmVudGEgLSBwcmVkX3RyYWluX3JmXzE1NTAwMSkgLyBwbWF4KGRhdG9zXzE1NTAwMSRWZW50YSwgMC4wMSkpKSAqIDEwMApybXNlX3RyYWluX3JmXzE1NTAwMSA8LSBzcXJ0KG1lYW4oKGRhdG9zXzE1NTAwMSRWZW50YSAtIHByZWRfdHJhaW5fcmZfMTU1MDAxKV4yKSkKCm1hcGVfdGVzdF9yZl8xNTUwMDEgPC0gbWVhbihhYnMoKHRlc3RfMTU1MDAxJFZlbnRhIC0gcHJlZF90ZXN0X3JmXzE1NTAwMSkgLyBwbWF4KHRlc3RfMTU1MDAxJFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdGVzdF9yZl8xNTUwMDEgPC0gc3FydChtZWFuKCh0ZXN0XzE1NTAwMSRWZW50YSAtIHByZWRfdGVzdF9yZl8xNTUwMDEpXjIpKQoKIyBNb3N0cmFyIHJlc3VsdGFkb3MKY2F0KCJSYW5kb20gRm9yZXN0IC0gUHJvZHVjdG8gMTU1MDAxXG4iKQpjYXQoIlRyYWluIC0gTUFQRToiLCByb3VuZChtYXBlX3RyYWluX3JmXzE1NTAwMSwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90cmFpbl9yZl8xNTUwMDEsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3RfcmZfMTU1MDAxLCAyKSwgICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90ZXN0X3JmXzE1NTAwMSwgMiksICAiXG4iKQoKIyBHdWFyZGFyIG3DqXRyaWNhcyBlbiB0YWJsYQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMSIsCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfdHJhaW5fcmZfMTU1MDAxLAogIFJNU0UgPSBybXNlX3RyYWluX3JmXzE1NTAwMQopKQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIxNTUwMDEiLAogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfdGVzdF9yZl8xNTUwMDEsCiAgUk1TRSA9IHJtc2VfdGVzdF9yZl8xNTUwMDEKKSkKCiMgSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCnZhckltcFBsb3QobW9kZWxvX3JmXzE1NTAwMSwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDEiKQoKIyBPYnNlcnZhZG8gdnMgUHJlZGljaG8gKHRlc3QpCmdncGxvdChkYXRhLmZyYW1lKE9ic2VydmFkbyA9IHRlc3RfMTU1MDAxJFZlbnRhLCBQcmVkaWNobyA9IHByZWRfdGVzdF9yZl8xNTUwMDEpLAogICAgICAgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicyh0aXRsZSA9ICJPYnNlcnZhZG8gdnMgUHJlZGljaG8gLSBQcm9kdWN0byAxNTUwMDEgKHRlc3QgZGF0YSkiLAogICAgICAgeCA9ICJWZW50YSBPYnNlcnZhZGEiLCB5ID0gIlZlbnRhIFByZWRpY2hhIikgKwogIHRoZW1lX21pbmltYWwoKQoKIyBBbsOhbGlzaXMgZGUgZXJyb3JlcyAodGVzdCkKZXJyb3Jlc190ZXN0XzE1NTAwMSA8LSB0ZXN0XzE1NTAwMSRWZW50YSAtIHByZWRfdGVzdF9yZl8xNTUwMDEKaGlzdChlcnJvcmVzX3Rlc3RfMTU1MDAxLCBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDE1NTAwMSAodGVzdCBkYXRhKSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwgY29sID0gInNreWJsdWUiLCBicmVha3MgPSAzMCkKCmNhdCgiTWVkaWEgZXJyb3I6IiwgbWVhbihlcnJvcmVzX3Rlc3RfMTU1MDAxKSwgIlxuIikKY2F0KCJTRCBlcnJvcjoiLCBzZChlcnJvcmVzX3Rlc3RfMTU1MDAxKSwgIlxuIikKY2F0KCJNaW4gZXJyb3I6IiwgbWluKGVycm9yZXNfdGVzdF8xNTUwMDEpLCAiXG4iKQpjYXQoIk1heCBlcnJvcjoiLCBtYXgoZXJyb3Jlc190ZXN0XzE1NTAwMSksICJcbiIpCmNhdCgiTWVkaWFuYSBlcnJvcjoiLCBtZWRpYW4oZXJyb3Jlc190ZXN0XzE1NTAwMSksICJcbiIpCgojIEVycm9yIHZzIFByZWRpY2Npw7NuICh0ZXN0KQpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRfdGVzdF9yZl8xNTUwMDEsIEVycm9yID0gZXJyb3Jlc190ZXN0XzE1NTAwMSksCiAgICAgICBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMTU1MDAxICh0ZXN0IGRhdGEpIiwKICAgICAgIHggPSAiVmVudGEgUHJlZGljaGEiLCB5ID0gIkVycm9yIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCiMjIFBST0RVQ1RPIDM5Mjk3ODgKYGBge3J9CiMgRW50cmVuYW1pZW50bwpzZXQuc2VlZCgxMjMpCm1vZGVsb19yZl8zOTI5Nzg4IDwtIHJhbmRvbUZvcmVzdCgKICBWZW50YSB+IFByZWNpb19MaXN0YV9Vbml0YXJpbyArCiAgICAgICAgICBWZW50YV9TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgQ2FudF9TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgU2VtYW5hLAogIGRhdGEgPSBkYXRvc18zOTI5Nzg4LAogIG50cmVlID0gNTAwLAogIG10cnkgPSBmbG9vcihzcXJ0KDYpKSwgICMgNiB2YXJpYWJsZXMgcHJlZGljdG9yYXMKICBpbXBvcnRhbmNlID0gVFJVRQopCgojIFByZWRpY2Npb25lcwpwcmVkX3RyYWluX3JmXzM5Mjk3ODggPC0gcHJlZGljdChtb2RlbG9fcmZfMzkyOTc4OCwgbmV3ZGF0YSA9IGRhdG9zXzM5Mjk3ODgpCnByZWRfdGVzdF9yZl8zOTI5Nzg4ICA8LSBwcmVkaWN0KG1vZGVsb19yZl8zOTI5Nzg4LCBuZXdkYXRhID0gdGVzdF8zOTI5Nzg4KQoKIyBNw6l0cmljYXMKbWFwZV90cmFpbl9yZl8zOTI5Nzg4IDwtIG1lYW4oYWJzKChkYXRvc18zOTI5Nzg4JFZlbnRhIC0gcHJlZF90cmFpbl9yZl8zOTI5Nzg4KSAvIHBtYXgoZGF0b3NfMzkyOTc4OCRWZW50YSwgMC4wMSkpKSAqIDEwMApybXNlX3RyYWluX3JmXzM5Mjk3ODggPC0gc3FydChtZWFuKChkYXRvc18zOTI5Nzg4JFZlbnRhIC0gcHJlZF90cmFpbl9yZl8zOTI5Nzg4KV4yKSkKCm1hcGVfdGVzdF9yZl8zOTI5Nzg4IDwtIG1lYW4oYWJzKCh0ZXN0XzM5Mjk3ODgkVmVudGEgLSBwcmVkX3Rlc3RfcmZfMzkyOTc4OCkgLyBwbWF4KHRlc3RfMzkyOTc4OCRWZW50YSwgMC4wMSkpKSAqIDEwMApybXNlX3Rlc3RfcmZfMzkyOTc4OCA8LSBzcXJ0KG1lYW4oKHRlc3RfMzkyOTc4OCRWZW50YSAtIHByZWRfdGVzdF9yZl8zOTI5Nzg4KV4yKSkKCiMgTW9zdHJhciByZXN1bHRhZG9zCmNhdCgiUmFuZG9tIEZvcmVzdCAtIFByb2R1Y3RvIDM5Mjk3ODhcbiIpCmNhdCgiVHJhaW4gLSBNQVBFOiIsIHJvdW5kKG1hcGVfdHJhaW5fcmZfMzkyOTc4OCwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90cmFpbl9yZl8zOTI5Nzg4LCAyKSwgIlxuIikKY2F0KCJUZXN0ICAtIE1BUEU6Iiwgcm91bmQobWFwZV90ZXN0X3JmXzM5Mjk3ODgsIDIpLCAgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3RfcmZfMzkyOTc4OCwgMiksICAiXG4iKQoKIyBHdWFyZGFyIG3DqXRyaWNhcyBlbiB0YWJsYQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5Mjk3ODgiLAogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IChUcmFpbikiLAogIE1BUEUgPSBtYXBlX3RyYWluX3JmXzM5Mjk3ODgsCiAgUk1TRSA9IHJtc2VfdHJhaW5fcmZfMzkyOTc4OAopKQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTI5Nzg4IiwKICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCAoVGVzdCkiLAogIE1BUEUgPSBtYXBlX3Rlc3RfcmZfMzkyOTc4OCwKICBSTVNFID0gcm1zZV90ZXN0X3JmXzM5Mjk3ODgKKSkKCiMgSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCnZhckltcFBsb3QobW9kZWxvX3JmXzM5Mjk3ODgsIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMzkyOTc4OCIpCgojIE9ic2VydmFkbyB2cyBQcmVkaWNobyAodGVzdCkKZ2dwbG90KGRhdGEuZnJhbWUoT2JzZXJ2YWRvID0gdGVzdF8zOTI5Nzg4JFZlbnRhLCBQcmVkaWNobyA9IHByZWRfdGVzdF9yZl8zOTI5Nzg4KSwKICAgICAgIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnModGl0bGUgPSAiT2JzZXJ2YWRvIHZzIFByZWRpY2hvIC0gUHJvZHVjdG8gMzkyOTc4OCAodGVzdCBkYXRhKSIsCiAgICAgICB4ID0gIlZlbnRhIE9ic2VydmFkYSIsIHkgPSAiVmVudGEgUHJlZGljaGEiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEFuw6FsaXNpcyBkZSBlcnJvcmVzICh0ZXN0KQplcnJvcmVzX3Rlc3RfMzkyOTc4OCA8LSB0ZXN0XzM5Mjk3ODgkVmVudGEgLSBwcmVkX3Rlc3RfcmZfMzkyOTc4OApoaXN0KGVycm9yZXNfdGVzdF8zOTI5Nzg4LCBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM5Mjk3ODggKHRlc3QgZGF0YSkiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsIGNvbCA9ICJza3libHVlIiwgYnJlYWtzID0gMzApCgpjYXQoIk1lZGlhIGVycm9yOiIsIG1lYW4oZXJyb3Jlc190ZXN0XzM5Mjk3ODgpLCAiXG4iKQpjYXQoIlNEIGVycm9yOiIsIHNkKGVycm9yZXNfdGVzdF8zOTI5Nzg4KSwgIlxuIikKY2F0KCJNaW4gZXJyb3I6IiwgbWluKGVycm9yZXNfdGVzdF8zOTI5Nzg4KSwgIlxuIikKY2F0KCJNYXggZXJyb3I6IiwgbWF4KGVycm9yZXNfdGVzdF8zOTI5Nzg4KSwgIlxuIikKY2F0KCJNZWRpYW5hIGVycm9yOiIsIG1lZGlhbihlcnJvcmVzX3Rlc3RfMzkyOTc4OCksICJcbiIpCgojIEVycm9yIHZzIFByZWRpY2Npw7NuICh0ZXN0KQpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRfdGVzdF9yZl8zOTI5Nzg4LCBFcnJvciA9IGVycm9yZXNfdGVzdF8zOTI5Nzg4KSwKICAgICAgIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnModGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzOTI5Nzg4ICh0ZXN0IGRhdGEpIiwKICAgICAgIHggPSAiVmVudGEgUHJlZGljaGEiLCB5ID0gIkVycm9yIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCiMjIFBST0RVQ1RPIDM5MDQxNTIKYGBge3J9CiMgRW50cmVuYW1pZW50bwpzZXQuc2VlZCgxMjMpCm1vZGVsb19yZl8zOTA0MTUyIDwtIHJhbmRvbUZvcmVzdCgKICBWZW50YSB+IFByZWNpb19MaXN0YV9Vbml0YXJpbyArCiAgICAgICAgICBWZW50YV9TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgQ2FudF9TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgU2VtYW5hLAogIGRhdGEgPSBkYXRvc18zOTA0MTUyLAogIG50cmVlID0gNTAwLAogIG10cnkgPSBmbG9vcihzcXJ0KDYpKSwgICMgNiB2YXJpYWJsZXMgcHJlZGljdG9yYXMKICBpbXBvcnRhbmNlID0gVFJVRQopCgojIFByZWRpY2Npb25lcwpwcmVkX3RyYWluX3JmXzM5MDQxNTIgPC0gcHJlZGljdChtb2RlbG9fcmZfMzkwNDE1MiwgbmV3ZGF0YSA9IGRhdG9zXzM5MDQxNTIpCnByZWRfdGVzdF9yZl8zOTA0MTUyICA8LSBwcmVkaWN0KG1vZGVsb19yZl8zOTA0MTUyLCBuZXdkYXRhID0gdGVzdF8zOTA0MTUyKQoKIyBNw6l0cmljYXMKbWFwZV90cmFpbl9yZl8zOTA0MTUyIDwtIG1lYW4oYWJzKChkYXRvc18zOTA0MTUyJFZlbnRhIC0gcHJlZF90cmFpbl9yZl8zOTA0MTUyKSAvIHBtYXgoZGF0b3NfMzkwNDE1MiRWZW50YSwgMC4wMSkpKSAqIDEwMApybXNlX3RyYWluX3JmXzM5MDQxNTIgPC0gc3FydChtZWFuKChkYXRvc18zOTA0MTUyJFZlbnRhIC0gcHJlZF90cmFpbl9yZl8zOTA0MTUyKV4yKSkKCm1hcGVfdGVzdF9yZl8zOTA0MTUyIDwtIG1lYW4oYWJzKCh0ZXN0XzM5MDQxNTIkVmVudGEgLSBwcmVkX3Rlc3RfcmZfMzkwNDE1MikgLyBwbWF4KHRlc3RfMzkwNDE1MiRWZW50YSwgMC4wMSkpKSAqIDEwMApybXNlX3Rlc3RfcmZfMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKHRlc3RfMzkwNDE1MiRWZW50YSAtIHByZWRfdGVzdF9yZl8zOTA0MTUyKV4yKSkKCiMgTW9zdHJhciByZXN1bHRhZG9zCmNhdCgiUmFuZG9tIEZvcmVzdCAtIFByb2R1Y3RvIDM5MDQxNTJcbiIpCmNhdCgiVHJhaW4gLSBNQVBFOiIsIHJvdW5kKG1hcGVfdHJhaW5fcmZfMzkwNDE1MiwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90cmFpbl9yZl8zOTA0MTUyLCAyKSwgIlxuIikKY2F0KCJUZXN0ICAtIE1BUEU6Iiwgcm91bmQobWFwZV90ZXN0X3JmXzM5MDQxNTIsIDIpLCAgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3RfcmZfMzkwNDE1MiwgMiksICAiXG4iKQoKIyBHdWFyZGFyIG3DqXRyaWNhcyBlbiB0YWJsYQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5MDQxNTIiLAogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IChUcmFpbikiLAogIE1BUEUgPSBtYXBlX3RyYWluX3JmXzM5MDQxNTIsCiAgUk1TRSA9IHJtc2VfdHJhaW5fcmZfMzkwNDE1MgopKQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTA0MTUyIiwKICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCAoVGVzdCkiLAogIE1BUEUgPSBtYXBlX3Rlc3RfcmZfMzkwNDE1MiwKICBSTVNFID0gcm1zZV90ZXN0X3JmXzM5MDQxNTIKKSkKCiMgSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCnZhckltcFBsb3QobW9kZWxvX3JmXzM5MDQxNTIsIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMzkwNDE1MiIpCgojIE9ic2VydmFkbyB2cyBQcmVkaWNobyAodGVzdCkKZ2dwbG90KGRhdGEuZnJhbWUoT2JzZXJ2YWRvID0gdGVzdF8zOTA0MTUyJFZlbnRhLCBQcmVkaWNobyA9IHByZWRfdGVzdF9yZl8zOTA0MTUyKSwKICAgICAgIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnModGl0bGUgPSAiT2JzZXJ2YWRvIHZzIFByZWRpY2hvIC0gUHJvZHVjdG8gMzkwNDE1MiAodGVzdCBkYXRhKSIsCiAgICAgICB4ID0gIlZlbnRhIE9ic2VydmFkYSIsIHkgPSAiVmVudGEgUHJlZGljaGEiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEFuw6FsaXNpcyBkZSBlcnJvcmVzICh0ZXN0KQplcnJvcmVzX3Rlc3RfMzkwNDE1MiA8LSB0ZXN0XzM5MDQxNTIkVmVudGEgLSBwcmVkX3Rlc3RfcmZfMzkwNDE1MgpoaXN0KGVycm9yZXNfdGVzdF8zOTA0MTUyLCBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM5MDQxNTIgKHRlc3QgZGF0YSkiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsIGNvbCA9ICJza3libHVlIiwgYnJlYWtzID0gMzApCgpjYXQoIk1lZGlhIGVycm9yOiIsIG1lYW4oZXJyb3Jlc190ZXN0XzM5MDQxNTIpLCAiXG4iKQpjYXQoIlNEIGVycm9yOiIsIHNkKGVycm9yZXNfdGVzdF8zOTA0MTUyKSwgIlxuIikKY2F0KCJNaW4gZXJyb3I6IiwgbWluKGVycm9yZXNfdGVzdF8zOTA0MTUyKSwgIlxuIikKY2F0KCJNYXggZXJyb3I6IiwgbWF4KGVycm9yZXNfdGVzdF8zOTA0MTUyKSwgIlxuIikKY2F0KCJNZWRpYW5hIGVycm9yOiIsIG1lZGlhbihlcnJvcmVzX3Rlc3RfMzkwNDE1MiksICJcbiIpCgojIEVycm9yIHZzIFByZWRpY2Npw7NuICh0ZXN0KQpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRfdGVzdF9yZl8zOTA0MTUyLCBFcnJvciA9IGVycm9yZXNfdGVzdF8zOTA0MTUyKSwKICAgICAgIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnModGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzOTA0MTUyICh0ZXN0IGRhdGEpIiwKICAgICAgIHggPSAiVmVudGEgUHJlZGljaGEiLCB5ID0gIkVycm9yIikgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCiMjIFBST0RVQ1RPIDE1NTAwMgpgYGB7cn0KIyBFbnRyZW5hbWllbnRvCnNldC5zZWVkKDEyMykKbW9kZWxvX3JmXzE1NTAwMiA8LSByYW5kb21Gb3Jlc3QoCiAgVmVudGEgfiBQcmVjaW9fTGlzdGFfVW5pdGFyaW8gKwogICAgICAgICAgVmVudGFfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IgKwogICAgICAgICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIFNlbWFuYSwKICBkYXRhID0gZGF0b3NfMTU1MDAyLAogIG50cmVlID0gNTAwLAogIG10cnkgPSBmbG9vcihzcXJ0KDYpKSwgICMgNiB2YXJpYWJsZXMgcHJlZGljdG9yYXMKICBpbXBvcnRhbmNlID0gVFJVRQopCgojIFByZWRpY2Npb25lcwpwcmVkX3RyYWluX3JmXzE1NTAwMiA8LSBwcmVkaWN0KG1vZGVsb19yZl8xNTUwMDIsIG5ld2RhdGEgPSBkYXRvc18xNTUwMDIpCnByZWRfdGVzdF9yZl8xNTUwMDIgIDwtIHByZWRpY3QobW9kZWxvX3JmXzE1NTAwMiwgbmV3ZGF0YSA9IHRlc3RfMTU1MDAyKQoKIyBNw6l0cmljYXMKbWFwZV90cmFpbl9yZl8xNTUwMDIgPC0gbWVhbihhYnMoKGRhdG9zXzE1NTAwMiRWZW50YSAtIHByZWRfdHJhaW5fcmZfMTU1MDAyKSAvIHBtYXgoZGF0b3NfMTU1MDAyJFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdHJhaW5fcmZfMTU1MDAyIDwtIHNxcnQobWVhbigoZGF0b3NfMTU1MDAyJFZlbnRhIC0gcHJlZF90cmFpbl9yZl8xNTUwMDIpXjIpKQoKbWFwZV90ZXN0X3JmXzE1NTAwMiA8LSBtZWFuKGFicygodGVzdF8xNTUwMDIkVmVudGEgLSBwcmVkX3Rlc3RfcmZfMTU1MDAyKSAvIHBtYXgodGVzdF8xNTUwMDIkVmVudGEsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0X3JmXzE1NTAwMiA8LSBzcXJ0KG1lYW4oKHRlc3RfMTU1MDAyJFZlbnRhIC0gcHJlZF90ZXN0X3JmXzE1NTAwMileMikpCgojIE1vc3RyYXIgcmVzdWx0YWRvcwpjYXQoIlJhbmRvbSBGb3Jlc3QgLSBQcm9kdWN0byAxNTUwMDJcbiIpCmNhdCgiVHJhaW4gLSBNQVBFOiIsIHJvdW5kKG1hcGVfdHJhaW5fcmZfMTU1MDAyLCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3RyYWluX3JmXzE1NTAwMiwgMiksICJcbiIpCmNhdCgiVGVzdCAgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdGVzdF9yZl8xNTUwMDIsIDIpLCAgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3RfcmZfMTU1MDAyLCAyKSwgICJcbiIpCgojIEd1YXJkYXIgbcOpdHJpY2FzIGVuIHRhYmxhCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAyIiwKICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCAoVHJhaW4pIiwKICBNQVBFID0gbWFwZV90cmFpbl9yZl8xNTUwMDIsCiAgUk1TRSA9IHJtc2VfdHJhaW5fcmZfMTU1MDAyCikpCgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMiIsCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QgKFRlc3QpIiwKICBNQVBFID0gbWFwZV90ZXN0X3JmXzE1NTAwMiwKICBSTVNFID0gcm1zZV90ZXN0X3JmXzE1NTAwMgopKQoKIyBJbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKdmFySW1wUGxvdChtb2RlbG9fcmZfMTU1MDAyLCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDE1NTAwMiIpCgojIE9ic2VydmFkbyB2cyBQcmVkaWNobyAodGVzdCkKZ2dwbG90KGRhdGEuZnJhbWUoT2JzZXJ2YWRvID0gdGVzdF8xNTUwMDIkVmVudGEsIFByZWRpY2hvID0gcHJlZF90ZXN0X3JmXzE1NTAwMiksCiAgICAgICBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKHRpdGxlID0gIk9ic2VydmFkbyB2cyBQcmVkaWNobyAtIFByb2R1Y3RvIDE1NTAwMiAodGVzdCBkYXRhKSIsCiAgICAgICB4ID0gIlZlbnRhIE9ic2VydmFkYSIsIHkgPSAiVmVudGEgUHJlZGljaGEiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEFuw6FsaXNpcyBkZSBlcnJvcmVzICh0ZXN0KQplcnJvcmVzX3Rlc3RfMTU1MDAyIDwtIHRlc3RfMTU1MDAyJFZlbnRhIC0gcHJlZF90ZXN0X3JmXzE1NTAwMgpoaXN0KGVycm9yZXNfdGVzdF8xNTUwMDIsIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMTU1MDAyICh0ZXN0IGRhdGEpIiwKICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLCBjb2wgPSAic2t5Ymx1ZSIsIGJyZWFrcyA9IDMwKQoKY2F0KCJNZWRpYSBlcnJvcjoiLCBtZWFuKGVycm9yZXNfdGVzdF8xNTUwMDIpLCAiXG4iKQpjYXQoIlNEIGVycm9yOiIsIHNkKGVycm9yZXNfdGVzdF8xNTUwMDIpLCAiXG4iKQpjYXQoIk1pbiBlcnJvcjoiLCBtaW4oZXJyb3Jlc190ZXN0XzE1NTAwMiksICJcbiIpCmNhdCgiTWF4IGVycm9yOiIsIG1heChlcnJvcmVzX3Rlc3RfMTU1MDAyKSwgIlxuIikKY2F0KCJNZWRpYW5hIGVycm9yOiIsIG1lZGlhbihlcnJvcmVzX3Rlc3RfMTU1MDAyKSwgIlxuIikKCiMgRXJyb3IgdnMgUHJlZGljY2nDs24gKHRlc3QpCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZF90ZXN0X3JmXzE1NTAwMiwgRXJyb3IgPSBlcnJvcmVzX3Rlc3RfMTU1MDAyKSwKICAgICAgIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnModGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAxNTUwMDIgKHRlc3QgZGF0YSkiLAogICAgICAgeCA9ICJWZW50YSBQcmVkaWNoYSIsIHkgPSAiRXJyb3IiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKIyMgUFJPRFVDVE8gMzY3ODA1NQpgYGB7cn0KIyBFbnRyZW5hbWllbnRvCnNldC5zZWVkKDEyMykKbW9kZWxvX3JmXzM2NzgwNTUgPC0gcmFuZG9tRm9yZXN0KAogIFZlbnRhIH4gUHJlY2lvX0xpc3RhX1VuaXRhcmlvICsKICAgICAgICAgIFZlbnRhX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yICsKICAgICAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciArCiAgICAgICAgICBTZW1hbmEsCiAgZGF0YSA9IGRhdG9zXzM2NzgwNTUsCiAgbnRyZWUgPSA1MDAsCiAgbXRyeSA9IGZsb29yKHNxcnQoNikpLCAgIyA2IHZhcmlhYmxlcyBwcmVkaWN0b3JhcwogIGltcG9ydGFuY2UgPSBUUlVFCikKCiMgUHJlZGljY2lvbmVzCnByZWRfdHJhaW5fcmZfMzY3ODA1NSA8LSBwcmVkaWN0KG1vZGVsb19yZl8zNjc4MDU1LCBuZXdkYXRhID0gZGF0b3NfMzY3ODA1NSkKcHJlZF90ZXN0X3JmXzM2NzgwNTUgIDwtIHByZWRpY3QobW9kZWxvX3JmXzM2NzgwNTUsIG5ld2RhdGEgPSB0ZXN0XzM2NzgwNTUpCgojIE3DqXRyaWNhcwptYXBlX3RyYWluX3JmXzM2NzgwNTUgPC0gbWVhbihhYnMoKGRhdG9zXzM2NzgwNTUkVmVudGEgLSBwcmVkX3RyYWluX3JmXzM2NzgwNTUpIC8gcG1heChkYXRvc18zNjc4MDU1JFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdHJhaW5fcmZfMzY3ODA1NSA8LSBzcXJ0KG1lYW4oKGRhdG9zXzM2NzgwNTUkVmVudGEgLSBwcmVkX3RyYWluX3JmXzM2NzgwNTUpXjIpKQoKbWFwZV90ZXN0X3JmXzM2NzgwNTUgPC0gbWVhbihhYnMoKHRlc3RfMzY3ODA1NSRWZW50YSAtIHByZWRfdGVzdF9yZl8zNjc4MDU1KSAvIHBtYXgodGVzdF8zNjc4MDU1JFZlbnRhLCAwLjAxKSkpICogMTAwCnJtc2VfdGVzdF9yZl8zNjc4MDU1IDwtIHNxcnQobWVhbigodGVzdF8zNjc4MDU1JFZlbnRhIC0gcHJlZF90ZXN0X3JmXzM2NzgwNTUpXjIpKQoKIyBNb3N0cmFyIHJlc3VsdGFkb3MKY2F0KCJSYW5kb20gRm9yZXN0IC0gUHJvZHVjdG8gMzY3ODA1NVxuIikKY2F0KCJUcmFpbiAtIE1BUEU6Iiwgcm91bmQobWFwZV90cmFpbl9yZl8zNjc4MDU1LCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3RyYWluX3JmXzM2NzgwNTUsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3RfcmZfMzY3ODA1NSwgMiksICAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdGVzdF9yZl8zNjc4MDU1LCAyKSwgICJcbiIpCgojIEd1YXJkYXIgbcOpdHJpY2FzIGVuIHRhYmxhCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzY3ODA1NSIsCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfdHJhaW5fcmZfMzY3ODA1NSwKICBSTVNFID0gcm1zZV90cmFpbl9yZl8zNjc4MDU1CikpCgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM2NzgwNTUiLAogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfdGVzdF9yZl8zNjc4MDU1LAogIFJNU0UgPSBybXNlX3Rlc3RfcmZfMzY3ODA1NQopKQoKIyBJbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKdmFySW1wUGxvdChtb2RlbG9fcmZfMzY3ODA1NSwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAzNjc4MDU1IikKCiMgT2JzZXJ2YWRvIHZzIFByZWRpY2hvICh0ZXN0KQpnZ3Bsb3QoZGF0YS5mcmFtZShPYnNlcnZhZG8gPSB0ZXN0XzM2NzgwNTUkVmVudGEsIFByZWRpY2hvID0gcHJlZF90ZXN0X3JmXzM2NzgwNTUpLAogICAgICAgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicyh0aXRsZSA9ICJPYnNlcnZhZG8gdnMgUHJlZGljaG8gLSBQcm9kdWN0byAzNjc4MDU1ICh0ZXN0IGRhdGEpIiwKICAgICAgIHggPSAiVmVudGEgT2JzZXJ2YWRhIiwgeSA9ICJWZW50YSBQcmVkaWNoYSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQW7DoWxpc2lzIGRlIGVycm9yZXMgKHRlc3QpCmVycm9yZXNfdGVzdF8zNjc4MDU1IDwtIHRlc3RfMzY3ODA1NSRWZW50YSAtIHByZWRfdGVzdF9yZl8zNjc4MDU1Cmhpc3QoZXJyb3Jlc190ZXN0XzM2NzgwNTUsIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMzY3ODA1NSAodGVzdCBkYXRhKSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwgY29sID0gInNreWJsdWUiLCBicmVha3MgPSAzMCkKCmNhdCgiTWVkaWEgZXJyb3I6IiwgbWVhbihlcnJvcmVzX3Rlc3RfMzY3ODA1NSksICJcbiIpCmNhdCgiU0QgZXJyb3I6Iiwgc2QoZXJyb3Jlc190ZXN0XzM2NzgwNTUpLCAiXG4iKQpjYXQoIk1pbiBlcnJvcjoiLCBtaW4oZXJyb3Jlc190ZXN0XzM2NzgwNTUpLCAiXG4iKQpjYXQoIk1heCBlcnJvcjoiLCBtYXgoZXJyb3Jlc190ZXN0XzM2NzgwNTUpLCAiXG4iKQpjYXQoIk1lZGlhbmEgZXJyb3I6IiwgbWVkaWFuKGVycm9yZXNfdGVzdF8zNjc4MDU1KSwgIlxuIikKCiMgRXJyb3IgdnMgUHJlZGljY2nDs24gKHRlc3QpCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZF90ZXN0X3JmXzM2NzgwNTUsIEVycm9yID0gZXJyb3Jlc190ZXN0XzM2NzgwNTUpLAogICAgICAgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicyh0aXRsZSA9ICJFcnJvciB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDM2NzgwNTUgKHRlc3QgZGF0YSkiLAogICAgICAgeCA9ICJWZW50YSBQcmVkaWNoYSIsIHkgPSAiRXJyb3IiKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKIyMgTcOJVFJJQ0FTIEFSTUEsIFJFRyBMSU5FQUwgWSBSQU5ET00gRk9SRVNUCmBgYHtyfQptZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lCiAgYXJyYW5nZShQcm9kdWN0bykgJT4lCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSAiTcOpdHJpY2FzIEFSTUEsIFJlZ3Jlc2lvbiBMaW5lYWwgeSBSYW5kb20gRm9yZXN0IHBvciBwcm9kdWN0byIpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsYWNrOyI+IFhHQk9PU1QKIyMgUFJPRFVDVE8gMTU1MDAxCiMjIyBTUExJVCBJTlRFUk5PCmBgYHtyfQojIERpdmlkaXIgbG9zIGRhdG9zIGVuIGVudHJlbmFtaWVudG8gKDgwJSkgeSB2YWxpZGFjacOzbiBpbnRlcm5hICgyMCUpCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCgppbmRpY2VzX3RyYWluXzE1NTAwMSA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zXzE1NTAwMSRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQoKIyBDcmVhciBzdWJjb25qdW50b3MKZGF0b3NfdHJhaW5fMTU1MDAxIDwtIGRhdG9zXzE1NTAwMVtpbmRpY2VzX3RyYWluXzE1NTAwMSwgXQpkYXRvc190ZXN0XzE1NTAwMSAgPC0gZGF0b3NfMTU1MDAxWy1pbmRpY2VzX3RyYWluXzE1NTAwMSwgXQoKIyBNYXRyaWNlcyBwcmVkaWN0b3JhcyB5IG9iamV0aXZvClhfdHJhaW5fMTU1MDAxIDwtIGFzLm1hdHJpeChkYXRvc190cmFpbl8xNTUwMDFbLCBjb2xuYW1lcyhkYXRvc190cmFpbl8xNTUwMDEpICE9ICJWZW50YSJdKQp5X3RyYWluXzE1NTAwMSA8LSBkYXRvc190cmFpbl8xNTUwMDEkVmVudGEKClhfdGVzdF8xNTUwMDEgPC0gYXMubWF0cml4KGRhdG9zX3Rlc3RfMTU1MDAxWywgY29sbmFtZXMoZGF0b3NfdGVzdF8xNTUwMDEpICE9ICJWZW50YSJdKQp5X3Rlc3RfMTU1MDAxIDwtIGRhdG9zX3Rlc3RfMTU1MDAxJFZlbnRhCgojIENvbnZlcnRpciBhIGZvcm1hdG8gRE1hdHJpeCBwYXJhIFhHQm9vc3QKZHRyYWluXzE1NTAwMSA8LSB4Z2IuRE1hdHJpeChkYXRhID0gWF90cmFpbl8xNTUwMDEsIGxhYmVsID0geV90cmFpbl8xNTUwMDEpCmR0ZXN0XzE1NTAwMSAgPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfdGVzdF8xNTUwMDEsIGxhYmVsID0geV90ZXN0XzE1NTAwMSkKYGBgCgojIyMgQlVTUVVFREEgREUgSElQRVJQQVJBTUVUUk9TIENPTiBWQUxJREFDScOTTiBDUlVaQURBCmBgYHtyfQojIENyZWFyIHVuYSByZWppbGxhIGRlIGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvcwpwYXJhbV9ncmlkIDwtIGV4cGFuZC5ncmlkKAogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xKSwgICAgICAgICAgICAgIyBUYXNhIGRlIGFwcmVuZGl6YWplCiAgbWF4X2RlcHRoID0gYygzLCA1LCA3KSwgICAgICAgICAgICAgICAjIFByb2Z1bmRpZGFkIGRlbCDDoXJib2wKICBzdWJzYW1wbGUgPSBjKDAuOCwgMS4wKSwgICAgICAgICAgICAgICMgUHJvcG9yY2nDs24gZGUgbXVlc3RyYXMgcG9yIMOhcmJvbAogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuOCwgMS4wKSwgICAgICAgIyBQcm9wb3JjacOzbiBkZSBjb2x1bW5hcyBwb3Igw6FyYm9sCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMyksICAgICAgICAgICAjIFBlc28gbcOtbmltbyBkZSBub2RvcyBoaWpvcwogIGdhbW1hID0gYygwLCAwLjEpICAgICAgICAgICAgICAgICAgICAgIyBSZWd1bGFyaXphY2nDs24KKQoKIyBSZWR1Y2lyIGNvbWJpbmFjaW9uZXMgcGFyYSBlZmljaWVuY2lhCnNldC5zZWVkKDEyMykKcGFyYW1fZ3JpZF9yZWR1Y2lkYSA8LSBwYXJhbV9ncmlkW3NhbXBsZSgxOm5yb3cocGFyYW1fZ3JpZCksIDEwKSwgXQoKIyBGdW5jacOzbiBwYXJhIGV2YWx1YXIgY2FkYSBjb21iaW5hY2nDs24gY29uIHZhbGlkYWNpw7NuIGNydXphZGEKZXZhbHVhdGVfcGFyYW1zXzE1NTAwMSA8LSBmdW5jdGlvbihwYXJhbXNfcm93KSB7CiAgcGFyYW1zIDwtIGxpc3QoCiAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwKICAgIGV0YSA9IHBhcmFtc19yb3ckZXRhLAogICAgbWF4X2RlcHRoID0gcGFyYW1zX3JvdyRtYXhfZGVwdGgsCiAgICBzdWJzYW1wbGUgPSBwYXJhbXNfcm93JHN1YnNhbXBsZSwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbXNfcm93JGNvbHNhbXBsZV9ieXRyZWUsCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1zX3JvdyRtaW5fY2hpbGRfd2VpZ2h0LAogICAgZ2FtbWEgPSBwYXJhbXNfcm93JGdhbW1hCiAgKQogIAogIGN2IDwtIHhnYi5jdigKICAgIHBhcmFtcyA9IHBhcmFtcywKICAgIGRhdGEgPSBkdHJhaW5fMTU1MDAxLCAgICAjIDwtIHVzYSBETWF0cml4IGNvbiBkYXRvc190cmFpbl8xNTUwMDEKICAgIG5yb3VuZHMgPSAxMDAsCiAgICBuZm9sZCA9IDUsCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAxMCwKICAgIHZlcmJvc2UgPSAwCiAgKQogIAogIGxpc3QoCiAgICBybXNlID0gbWluKGN2JGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKSwKICAgIG5yb3VuZHMgPSB3aGljaC5taW4oY3YkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pLAogICAgcGFyYW1zID0gcGFyYW1zCiAgKQp9CmBgYAoKYGBge3J9CiMgRXZhbHVhciB0b2RhcyBsYXMgY29tYmluYWNpb25lcwpyZXN1bHRhZG9zX2dyaWRfMTU1MDAxIDwtIHBhcmFtX2dyaWRfcmVkdWNpZGEKcmVzdWx0YWRvc19ncmlkXzE1NTAwMSRucm91bmRzIDwtIE5BCnJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEkcm1zZSA8LSBOQQoKY2F0KCJJbmljaWFuZG8gYsO6c3F1ZWRhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBwcm9kdWN0byAxNTUwMDEuLi5cbiIpCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpIHsKICBjYXQoc3ByaW50ZigiRXZhbHVhbmRvIGNvbWJpbmFjacOzbiAlZCBkZSAlZFxuIiwgaSwgbnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSkpCiAgCiAgcmVzdWx0YWRvIDwtIGV2YWx1YXRlX3BhcmFtc18xNTUwMDEocGFyYW1fZ3JpZF9yZWR1Y2lkYVtpLCBdKQogIAogIHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEkbnJvdW5kc1tpXSA8LSByZXN1bHRhZG8kbnJvdW5kcwogIHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEkcm1zZVtpXSA8LSByZXN1bHRhZG8kcm1zZQp9CgojIE9yZGVuYXIgcG9yIG1lam9yIFJNU0UKcmVzdWx0YWRvc19ncmlkXzE1NTAwMSA8LSByZXN1bHRhZG9zX2dyaWRfMTU1MDAxW29yZGVyKHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEkcm1zZSksIF0KCiMgTW9zdHJhciBsYXMgNSBtZWpvcmVzIGNvbWJpbmFjaW9uZXMKY2F0KCJcblRvcCA1IGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvcyAoUHJvZHVjdG8gMTU1MDAxKTpcbiIpCnByaW50KGhlYWQocmVzdWx0YWRvc19ncmlkXzE1NTAwMSwgNSkpCgpgYGAKCiMjIyBFTlRSRU5BIE1PREVMTyBDT04gTUVKT1JFUyBISVBFUlBBUsOBTUVUUk9TIApgYGB7cn0KIyBFeHRyYWVyIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MgZGVsIGdyaWQKbWVqb3Jlc19wYXJhbXNfMTU1MDAxIDwtIGxpc3QoCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogIGV2YWxfbWV0cmljID0gInJtc2UiLAogIGV0YSA9IHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEkZXRhWzFdLAogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEkbWF4X2RlcHRoWzFdLAogIHN1YnNhbXBsZSA9IHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEkc3Vic2FtcGxlWzFdLAogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zX2dyaWRfMTU1MDAxJGNvbHNhbXBsZV9ieXRyZWVbMV0sCiAgbWluX2NoaWxkX3dlaWdodCA9IHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEkbWluX2NoaWxkX3dlaWdodFsxXSwKICBnYW1tYSA9IHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEkZ2FtbWFbMV0KKQoKIyBOw7ptZXJvIMOzcHRpbW8gZGUgcm9uZGFzCm1lam9yX25yb3VuZHNfMTU1MDAxIDwtIHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDEkbnJvdW5kc1sxXQoKY2F0KCJFbnRyZW5hbmRvIG1vZGVsbyBjb24gbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zLi4uXG4iKQpwcmludChtZWpvcmVzX3BhcmFtc18xNTUwMDEpCmNhdCgiTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhczoiLCBtZWpvcl9ucm91bmRzXzE1NTAwMSwgIlxuIikKCiMgRW50cmVuYXIgZWwgbW9kZWxvIGNvbiBsb3MgZGF0b3MgZGUgc3BsaXQgaW50ZXJubwptb2RlbG9feGdiXzE1NTAwMV9zcGxpdCA8LSB4Z2IudHJhaW4oCiAgcGFyYW1zID0gbWVqb3Jlc19wYXJhbXNfMTU1MDAxLAogIGRhdGEgPSBkdHJhaW5fMTU1MDAxLAogIG5yb3VuZHMgPSBtZWpvcl9ucm91bmRzXzE1NTAwMSwKICB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gZHRyYWluXzE1NTAwMSwgdGVzdCA9IGR0ZXN0XzE1NTAwMSksCiAgdmVyYm9zZSA9IDAKKQpgYGAKCiMjIyBNw4lUUklDQVMgSU5URVJOQVMKYGBge3J9CiMgTcOpdHJpY2FzIHNvYnJlIGRhdG9zX3RyYWluICg4MCUpCnByZWRfdHJhaW5fc3BsaXRfMTU1MDAxIDwtIHByZWRpY3QobW9kZWxvX3hnYl8xNTUwMDFfc3BsaXQsIGR0cmFpbl8xNTUwMDEpCm1hcGVfdHJhaW5fc3BsaXRfMTU1MDAxIDwtIG1lYW4oYWJzKCh5X3RyYWluXzE1NTAwMSAtIHByZWRfdHJhaW5fc3BsaXRfMTU1MDAxKSAvIHBtYXgoeV90cmFpbl8xNTUwMDEsIDAuMDEpKSkgKiAxMDAKcm1zZV90cmFpbl9zcGxpdF8xNTUwMDEgPC0gc3FydChtZWFuKCh5X3RyYWluXzE1NTAwMSAtIHByZWRfdHJhaW5fc3BsaXRfMTU1MDAxKV4yKSkKCiMgTcOpdHJpY2FzIHNvYnJlIGRhdG9zX3Rlc3QgKDIwJSkKcHJlZF90ZXN0X3NwbGl0XzE1NTAwMSA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMTU1MDAxX3NwbGl0LCBkdGVzdF8xNTUwMDEpCm1hcGVfdGVzdF9zcGxpdF8xNTUwMDEgPC0gbWVhbihhYnMoKHlfdGVzdF8xNTUwMDEgLSBwcmVkX3Rlc3Rfc3BsaXRfMTU1MDAxKSAvIHBtYXgoeV90ZXN0XzE1NTAwMSwgMC4wMSkpKSAqIDEwMApybXNlX3Rlc3Rfc3BsaXRfMTU1MDAxIDwtIHNxcnQobWVhbigoeV90ZXN0XzE1NTAwMSAtIHByZWRfdGVzdF9zcGxpdF8xNTUwMDEpXjIpKQoKY2F0KCJTcGxpdCBJbnRlcm5vIC0gUHJvZHVjdG8gMTU1MDAxXG4iKQpjYXQoIlRyYWluIFNwbGl0IC0gTUFQRToiLCByb3VuZChtYXBlX3RyYWluX3NwbGl0XzE1NTAwMSwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90cmFpbl9zcGxpdF8xNTUwMDEsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIFNwbGl0IC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3Rfc3BsaXRfMTU1MDAxLCAyKSwgICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90ZXN0X3NwbGl0XzE1NTAwMSwgMiksICJcbiIpCmBgYAoKIyMjIEVOVFJFTkFNSUVOVE8gQ09OIERBVE9TIENPTVBMRVRPUyAKYGBge3J9CiMgUHJlcGFyYXIgbWF0cmljZXMgY29uIHRvZG9zIGxvcyBkYXRvcwpYX2VudGVyb18xNTUwMDEgPC0gYXMubWF0cml4KGRhdG9zXzE1NTAwMVssIGNvbG5hbWVzKGRhdG9zXzE1NTAwMSkgIT0gIlZlbnRhIl0pCnlfZW50ZXJvXzE1NTAwMSA8LSBkYXRvc18xNTUwMDEkVmVudGEKZHRyYWluX2ZpbmFsXzE1NTAwMSA8LSB4Z2IuRE1hdHJpeChkYXRhID0gWF9lbnRlcm9fMTU1MDAxLCBsYWJlbCA9IHlfZW50ZXJvXzE1NTAwMSkKCiMgRW50cmVuYXIgZWwgbW9kZWxvIGZpbmFsCm1vZGVsb194Z2JfZmluYWxfMTU1MDAxIDwtIHhnYi50cmFpbigKICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtc18xNTUwMDEsCiAgZGF0YSA9IGR0cmFpbl9maW5hbF8xNTUwMDEsCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHNfMTU1MDAxLAogIHZlcmJvc2UgPSAwCikKCiMgRXZhbHVhY2nDs24gZW4gZGF0b3NfMTU1MDAxCnByZWRfZmluYWxfdHJhaW5fMTU1MDAxIDwtIHByZWRpY3QobW9kZWxvX3hnYl9maW5hbF8xNTUwMDEsIFhfZW50ZXJvXzE1NTAwMSkKbWFwZV9maW5hbF90cmFpbl8xNTUwMDEgPC0gbWVhbihhYnMoKHlfZW50ZXJvXzE1NTAwMSAtIHByZWRfZmluYWxfdHJhaW5fMTU1MDAxKSAvIHBtYXgoeV9lbnRlcm9fMTU1MDAxLCAwLjAxKSkpICogMTAwCnJtc2VfZmluYWxfdHJhaW5fMTU1MDAxIDwtIHNxcnQobWVhbigoeV9lbnRlcm9fMTU1MDAxIC0gcHJlZF9maW5hbF90cmFpbl8xNTUwMDEpXjIpKQoKIyBFdmFsdWFjacOzbiBlbiB0ZXN0XzE1NTAwMQpYX3Rlc3RfcmVhbF8xNTUwMDEgPC0gYXMubWF0cml4KHRlc3RfMTU1MDAxWywgY29sbmFtZXModGVzdF8xNTUwMDEpICE9ICJWZW50YSJdKQp5X3Rlc3RfcmVhbF8xNTUwMDEgPC0gdGVzdF8xNTUwMDEkVmVudGEKcHJlZF9maW5hbF90ZXN0XzE1NTAwMSA8LSBwcmVkaWN0KG1vZGVsb194Z2JfZmluYWxfMTU1MDAxLCBYX3Rlc3RfcmVhbF8xNTUwMDEpCm1hcGVfZmluYWxfdGVzdF8xNTUwMDEgPC0gbWVhbihhYnMoKHlfdGVzdF9yZWFsXzE1NTAwMSAtIHByZWRfZmluYWxfdGVzdF8xNTUwMDEpIC8gcG1heCh5X3Rlc3RfcmVhbF8xNTUwMDEsIDAuMDEpKSkgKiAxMDAKcm1zZV9maW5hbF90ZXN0XzE1NTAwMSA8LSBzcXJ0KG1lYW4oKHlfdGVzdF9yZWFsXzE1NTAwMSAtIHByZWRfZmluYWxfdGVzdF8xNTUwMDEpXjIpKQoKY2F0KCJNb2RlbG8gRmluYWwgLSBQcm9kdWN0byAxNTUwMDFcbiIpCmNhdCgiVHJhaW4gLSBNQVBFOiIsIHJvdW5kKG1hcGVfZmluYWxfdHJhaW5fMTU1MDAxLCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX2ZpbmFsX3RyYWluXzE1NTAwMSwgMiksICJcbiIpCmNhdCgiVGVzdCAgLSBNQVBFOiIsIHJvdW5kKG1hcGVfZmluYWxfdGVzdF8xNTUwMDEsIDIpLCAgInwgUk1TRToiLCByb3VuZChybXNlX2ZpbmFsX3Rlc3RfMTU1MDAxLCAyKSwgIlxuIikKCmBgYAoKIyMjIFRBQkxBIERFIE3DiVRSSUNBUwpgYGB7cn0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIxNTUwMDEiLAogIE1vZGVsbyA9ICJYR0Jvb3N0IChUcmFpbikiLAogIE1BUEUgPSBtYXBlX2ZpbmFsX3RyYWluXzE1NTAwMSwKICBSTVNFID0gcm1zZV9maW5hbF90cmFpbl8xNTUwMDEKKSkKCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAxIiwKICBNb2RlbG8gPSAiWEdCb29zdCAoVGVzdCkiLAogIE1BUEUgPSBtYXBlX2ZpbmFsX3Rlc3RfMTU1MDAxLAogIFJNU0UgPSBybXNlX2ZpbmFsX3Rlc3RfMTU1MDAxCikpCmBgYAoKIyMgUFJPRFVDVE8gMzkyOTc4OAojIyMgU1BMSVQgSU5URVJOTwpgYGB7cn0KIyBEaXZpZGlyIGxvcyBkYXRvcyBlbiBlbnRyZW5hbWllbnRvICg4MCUpIHkgdmFsaWRhY2nDs24gaW50ZXJuYSAoMjAlKQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAoKaW5kaWNlc190cmFpbl8zOTI5Nzg4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfMzkyOTc4OCRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQoKIyBDcmVhciBzdWJjb25qdW50b3MKZGF0b3NfdHJhaW5fMzkyOTc4OCA8LSBkYXRvc18zOTI5Nzg4W2luZGljZXNfdHJhaW5fMzkyOTc4OCwgXQpkYXRvc190ZXN0XzM5Mjk3ODggIDwtIGRhdG9zXzM5Mjk3ODhbLWluZGljZXNfdHJhaW5fMzkyOTc4OCwgXQoKIyBNYXRyaWNlcyBwcmVkaWN0b3JhcyB5IG9iamV0aXZvClhfdHJhaW5fMzkyOTc4OCA8LSBhcy5tYXRyaXgoZGF0b3NfdHJhaW5fMzkyOTc4OFssIGNvbG5hbWVzKGRhdG9zX3RyYWluXzM5Mjk3ODgpICE9ICJWZW50YSJdKQp5X3RyYWluXzM5Mjk3ODggPC0gZGF0b3NfdHJhaW5fMzkyOTc4OCRWZW50YQoKWF90ZXN0XzM5Mjk3ODggPC0gYXMubWF0cml4KGRhdG9zX3Rlc3RfMzkyOTc4OFssIGNvbG5hbWVzKGRhdG9zX3Rlc3RfMzkyOTc4OCkgIT0gIlZlbnRhIl0pCnlfdGVzdF8zOTI5Nzg4IDwtIGRhdG9zX3Rlc3RfMzkyOTc4OCRWZW50YQoKIyBDb252ZXJ0aXIgYSBmb3JtYXRvIERNYXRyaXggcGFyYSBYR0Jvb3N0CmR0cmFpbl8zOTI5Nzg4IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3RyYWluXzM5Mjk3ODgsIGxhYmVsID0geV90cmFpbl8zOTI5Nzg4KQpkdGVzdF8zOTI5Nzg4ICA8LSB4Z2IuRE1hdHJpeChkYXRhID0gWF90ZXN0XzM5Mjk3ODgsIGxhYmVsID0geV90ZXN0XzM5Mjk3ODgpCmBgYAoKIyMjIEJVU1FVRURBIERFIEhJUEVSUEFSQU1FVFJPUyBDT04gVkFMSURBQ0nDk04gQ1JVWkFEQQpgYGB7cn0KIyBDcmVhciB1bmEgcmVqaWxsYSBkZSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3MKcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgKICBldGEgPSBjKDAuMDEsIDAuMDUsIDAuMSksICAgICAgICAgICAgICMgVGFzYSBkZSBhcHJlbmRpemFqZQogIG1heF9kZXB0aCA9IGMoMywgNSwgNyksICAgICAgICAgICAgICAgIyBQcm9mdW5kaWRhZCBkZWwgw6FyYm9sCiAgc3Vic2FtcGxlID0gYygwLjgsIDEuMCksICAgICAgICAgICAgICAjIFByb3BvcmNpw7NuIGRlIG11ZXN0cmFzIHBvciDDoXJib2wKICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjgsIDEuMCksICAgICAgICMgUHJvcG9yY2nDs24gZGUgY29sdW1uYXMgcG9yIMOhcmJvbAogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMpLCAgICAgICAgICAgIyBQZXNvIG3DrW5pbW8gZGUgbm9kb3MgaGlqb3MKICBnYW1tYSA9IGMoMCwgMC4xKSAgICAgICAgICAgICAgICAgICAgICMgUmVndWxhcml6YWNpw7NuCikKCiMgUmVkdWNpciBjb21iaW5hY2lvbmVzIHBhcmEgZWZpY2llbmNpYQpzZXQuc2VlZCgxMjMpCnBhcmFtX2dyaWRfcmVkdWNpZGEgPC0gcGFyYW1fZ3JpZFtzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAxMCksIF0KCiMgRnVuY2nDs24gcGFyYSBldmFsdWFyIGNhZGEgY29tYmluYWNpw7NuIGNvbiB2YWxpZGFjacOzbiBjcnV6YWRhCmV2YWx1YXRlX3BhcmFtc18zOTI5Nzg4IDwtIGZ1bmN0aW9uKHBhcmFtc19yb3cpIHsKICBwYXJhbXMgPC0gbGlzdCgKICAgIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICAgIGV2YWxfbWV0cmljID0gInJtc2UiLAogICAgZXRhID0gcGFyYW1zX3JvdyRldGEsCiAgICBtYXhfZGVwdGggPSBwYXJhbXNfcm93JG1heF9kZXB0aCwKICAgIHN1YnNhbXBsZSA9IHBhcmFtc19yb3ckc3Vic2FtcGxlLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtc19yb3ckY29sc2FtcGxlX2J5dHJlZSwKICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbXNfcm93JG1pbl9jaGlsZF93ZWlnaHQsCiAgICBnYW1tYSA9IHBhcmFtc19yb3ckZ2FtbWEKICApCiAgCiAgY3YgPC0geGdiLmN2KAogICAgcGFyYW1zID0gcGFyYW1zLAogICAgZGF0YSA9IGR0cmFpbl8zOTI5Nzg4LCAgICAjIDwtIHVzYSBETWF0cml4IGNvbiBkYXRvc190cmFpbl8xNTUwMDEKICAgIG5yb3VuZHMgPSAxMDAsCiAgICBuZm9sZCA9IDUsCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAxMCwKICAgIHZlcmJvc2UgPSAwCiAgKQogIAogIGxpc3QoCiAgICBybXNlID0gbWluKGN2JGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKSwKICAgIG5yb3VuZHMgPSB3aGljaC5taW4oY3YkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pLAogICAgcGFyYW1zID0gcGFyYW1zCiAgKQp9CmBgYAoKYGBge3J9CiMgRXZhbHVhciB0b2RhcyBsYXMgY29tYmluYWNpb25lcwpyZXN1bHRhZG9zX2dyaWRfMzkyOTc4OCA8LSBwYXJhbV9ncmlkX3JlZHVjaWRhCnJlc3VsdGFkb3NfZ3JpZF8zOTI5Nzg4JG5yb3VuZHMgPC0gTkEKcmVzdWx0YWRvc19ncmlkXzM5Mjk3ODgkcm1zZSA8LSBOQQoKY2F0KCJJbmljaWFuZG8gYsO6c3F1ZWRhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBwcm9kdWN0byAzOTI5Nzg4Li4uXG4iKQpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSB7CiAgY2F0KHNwcmludGYoIkV2YWx1YW5kbyBjb21iaW5hY2nDs24gJWQgZGUgJWRcbiIsIGksIG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpKQogIAogIHJlc3VsdGFkbyA8LSBldmFsdWF0ZV9wYXJhbXNfMzkyOTc4OChwYXJhbV9ncmlkX3JlZHVjaWRhW2ksIF0pCiAgCiAgcmVzdWx0YWRvc19ncmlkXzM5Mjk3ODgkbnJvdW5kc1tpXSA8LSByZXN1bHRhZG8kbnJvdW5kcwogIHJlc3VsdGFkb3NfZ3JpZF8zOTI5Nzg4JHJtc2VbaV0gPC0gcmVzdWx0YWRvJHJtc2UKfQoKIyBPcmRlbmFyIHBvciBtZWpvciBSTVNFCnJlc3VsdGFkb3NfZ3JpZF8zOTI5Nzg4IDwtIHJlc3VsdGFkb3NfZ3JpZF8zOTI5Nzg4W29yZGVyKHJlc3VsdGFkb3NfZ3JpZF8zOTI5Nzg4JHJtc2UpLCBdCgojIE1vc3RyYXIgbGFzIDUgbWVqb3JlcyBjb21iaW5hY2lvbmVzCmNhdCgiXG5Ub3AgNSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3MgKFByb2R1Y3RvIDM5Mjk3ODgpOlxuIikKcHJpbnQoaGVhZChyZXN1bHRhZG9zX2dyaWRfMzkyOTc4OCwgNSkpCgpgYGAKCiMjIyBFTlRSRU5BIE1PREVMTyBDT04gTUVKT1JFUyBISVBFUlBBUsOBTUVUUk9TIApgYGB7cn0KIyBFeHRyYWVyIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MgZGVsIGdyaWQKbWVqb3Jlc19wYXJhbXNfMzkyOTc4OCA8LSBsaXN0KAogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICBldmFsX21ldHJpYyA9ICJybXNlIiwKICBldGEgPSByZXN1bHRhZG9zX2dyaWRfMzkyOTc4OCRldGFbMV0sCiAgbWF4X2RlcHRoID0gcmVzdWx0YWRvc19ncmlkXzM5Mjk3ODgkbWF4X2RlcHRoWzFdLAogIHN1YnNhbXBsZSA9IHJlc3VsdGFkb3NfZ3JpZF8zOTI5Nzg4JHN1YnNhbXBsZVsxXSwKICBjb2xzYW1wbGVfYnl0cmVlID0gcmVzdWx0YWRvc19ncmlkXzM5Mjk3ODgkY29sc2FtcGxlX2J5dHJlZVsxXSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvc19ncmlkXzM5Mjk3ODgkbWluX2NoaWxkX3dlaWdodFsxXSwKICBnYW1tYSA9IHJlc3VsdGFkb3NfZ3JpZF8zOTI5Nzg4JGdhbW1hWzFdCikKCiMgTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhcwptZWpvcl9ucm91bmRzXzM5Mjk3ODggPC0gcmVzdWx0YWRvc19ncmlkXzM5Mjk3ODgkbnJvdW5kc1sxXQoKY2F0KCJFbnRyZW5hbmRvIG1vZGVsbyBjb24gbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zLi4uXG4iKQpwcmludChtZWpvcmVzX3BhcmFtc18zOTI5Nzg4KQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kc18zOTI5Nzg4LCAiXG4iKQoKIyBFbnRyZW5hciBlbCBtb2RlbG8gY29uIGxvcyBkYXRvcyBkZSBzcGxpdCBpbnRlcm5vCm1vZGVsb194Z2JfMzkyOTc4OF9zcGxpdCA8LSB4Z2IudHJhaW4oCiAgcGFyYW1zID0gbWVqb3Jlc19wYXJhbXNfMzkyOTc4OCwKICBkYXRhID0gZHRyYWluXzM5Mjk3ODgsCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHNfMzkyOTc4OCwKICB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gZHRyYWluXzM5Mjk3ODgsIHRlc3QgPSBkdGVzdF8zOTI5Nzg4KSwKICB2ZXJib3NlID0gMAopCmBgYAoKIyMjIE3DiVRSSUNBUyBJTlRFUk5BUwpgYGB7cn0KIyBNw6l0cmljYXMgc29icmUgZGF0b3NfdHJhaW4gKDgwJSkKcHJlZF90cmFpbl9zcGxpdF8zOTI5Nzg4IDwtIHByZWRpY3QobW9kZWxvX3hnYl8zOTI5Nzg4X3NwbGl0LCBkdHJhaW5fMzkyOTc4OCkKbWFwZV90cmFpbl9zcGxpdF8zOTI5Nzg4IDwtIG1lYW4oYWJzKCh5X3RyYWluXzM5Mjk3ODggLSBwcmVkX3RyYWluX3NwbGl0XzM5Mjk3ODgpIC8gcG1heCh5X3RyYWluXzM5Mjk3ODgsIDAuMDEpKSkgKiAxMDAKcm1zZV90cmFpbl9zcGxpdF8zOTI5Nzg4IDwtIHNxcnQobWVhbigoeV90cmFpbl8zOTI5Nzg4IC0gcHJlZF90cmFpbl9zcGxpdF8zOTI5Nzg4KV4yKSkKCiMgTcOpdHJpY2FzIHNvYnJlIGRhdG9zX3Rlc3QgKDIwJSkKcHJlZF90ZXN0X3NwbGl0XzM5Mjk3ODggPC0gcHJlZGljdChtb2RlbG9feGdiXzM5Mjk3ODhfc3BsaXQsIGR0ZXN0XzM5Mjk3ODgpCm1hcGVfdGVzdF9zcGxpdF8zOTI5Nzg4IDwtIG1lYW4oYWJzKCh5X3Rlc3RfMzkyOTc4OCAtIHByZWRfdGVzdF9zcGxpdF8zOTI5Nzg4KSAvIHBtYXgoeV90ZXN0XzM5Mjk3ODgsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0X3NwbGl0XzM5Mjk3ODggPC0gc3FydChtZWFuKCh5X3Rlc3RfMzkyOTc4OCAtIHByZWRfdGVzdF9zcGxpdF8zOTI5Nzg4KV4yKSkKCmNhdCgiU3BsaXQgSW50ZXJubyAtIFByb2R1Y3RvIDM5Mjk3ODhcbiIpCmNhdCgiVHJhaW4gU3BsaXQgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdHJhaW5fc3BsaXRfMzkyOTc4OCwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90cmFpbl9zcGxpdF8zOTI5Nzg4LCAyKSwgIlxuIikKY2F0KCJUZXN0ICBTcGxpdCAtIE1BUEU6Iiwgcm91bmQobWFwZV90ZXN0X3NwbGl0XzM5Mjk3ODgsIDIpLCAgInwgUk1TRToiLCByb3VuZChybXNlX3Rlc3Rfc3BsaXRfMzkyOTc4OCwgMiksICJcbiIpCmBgYAoKIyMjIEVOVFJFTkFNSUVOVE8gQ09OIERBVE9TIENPTVBMRVRPUyAKYGBge3J9CiMgUHJlcGFyYXIgbWF0cmljZXMgY29uIHRvZG9zIGxvcyBkYXRvcwpYX2VudGVyb18zOTI5Nzg4IDwtIGFzLm1hdHJpeChkYXRvc18zOTI5Nzg4WywgY29sbmFtZXMoZGF0b3NfMzkyOTc4OCkgIT0gIlZlbnRhIl0pCnlfZW50ZXJvXzM5Mjk3ODggPC0gZGF0b3NfMzkyOTc4OCRWZW50YQpkdHJhaW5fZmluYWxfMzkyOTc4OCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gWF9lbnRlcm9fMzkyOTc4OCwgbGFiZWwgPSB5X2VudGVyb18zOTI5Nzg4KQoKIyBFbnRyZW5hciBlbCBtb2RlbG8gZmluYWwKbW9kZWxvX3hnYl9maW5hbF8zOTI5Nzg4IDwtIHhnYi50cmFpbigKICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtc18zOTI5Nzg4LAogIGRhdGEgPSBkdHJhaW5fZmluYWxfMzkyOTc4OCwKICBucm91bmRzID0gbWVqb3JfbnJvdW5kc18zOTI5Nzg4LAogIHZlcmJvc2UgPSAwCikKCiMgRXZhbHVhY2nDs24gZW4gZGF0b3NfMzkyOTc4OApwcmVkX2ZpbmFsX3RyYWluXzM5Mjk3ODggPC0gcHJlZGljdChtb2RlbG9feGdiX2ZpbmFsXzM5Mjk3ODgsIFhfZW50ZXJvXzM5Mjk3ODgpCm1hcGVfZmluYWxfdHJhaW5fMzkyOTc4OCA8LSBtZWFuKGFicygoeV9lbnRlcm9fMzkyOTc4OCAtIHByZWRfZmluYWxfdHJhaW5fMzkyOTc4OCkgLyBwbWF4KHlfZW50ZXJvXzM5Mjk3ODgsIDAuMDEpKSkgKiAxMDAKcm1zZV9maW5hbF90cmFpbl8zOTI5Nzg4IDwtIHNxcnQobWVhbigoeV9lbnRlcm9fMzkyOTc4OCAtIHByZWRfZmluYWxfdHJhaW5fMzkyOTc4OCleMikpCgojIEV2YWx1YWNpw7NuIGVuIHRlc3RfMzkyOTc4OApYX3Rlc3RfcmVhbF8zOTI5Nzg4IDwtIGFzLm1hdHJpeCh0ZXN0XzM5Mjk3ODhbLCBjb2xuYW1lcyh0ZXN0XzM5Mjk3ODgpICE9ICJWZW50YSJdKQp5X3Rlc3RfcmVhbF8zOTI5Nzg4IDwtIHRlc3RfMzkyOTc4OCRWZW50YQpwcmVkX2ZpbmFsX3Rlc3RfMzkyOTc4OCA8LSBwcmVkaWN0KG1vZGVsb194Z2JfZmluYWxfMzkyOTc4OCwgWF90ZXN0X3JlYWxfMzkyOTc4OCkKbWFwZV9maW5hbF90ZXN0XzM5Mjk3ODggPC0gbWVhbihhYnMoKHlfdGVzdF9yZWFsXzM5Mjk3ODggLSBwcmVkX2ZpbmFsX3Rlc3RfMzkyOTc4OCkgLyBwbWF4KHlfdGVzdF9yZWFsXzM5Mjk3ODgsIDAuMDEpKSkgKiAxMDAKcm1zZV9maW5hbF90ZXN0XzM5Mjk3ODggPC0gc3FydChtZWFuKCh5X3Rlc3RfcmVhbF8zOTI5Nzg4IC0gcHJlZF9maW5hbF90ZXN0XzM5Mjk3ODgpXjIpKQoKY2F0KCJNb2RlbG8gRmluYWwgLSBQcm9kdWN0byAzOTI5Nzg4XG4iKQpjYXQoIlRyYWluIC0gTUFQRToiLCByb3VuZChtYXBlX2ZpbmFsX3RyYWluXzM5Mjk3ODgsIDIpLCAifCBSTVNFOiIsIHJvdW5kKHJtc2VfZmluYWxfdHJhaW5fMzkyOTc4OCwgMiksICJcbiIpCmNhdCgiVGVzdCAgLSBNQVBFOiIsIHJvdW5kKG1hcGVfZmluYWxfdGVzdF8zOTI5Nzg4LCAyKSwgICJ8IFJNU0U6Iiwgcm91bmQocm1zZV9maW5hbF90ZXN0XzM5Mjk3ODgsIDIpLCAiXG4iKQoKYGBgCgojIyMgVEFCTEEgREUgTcOJVFJJQ0FTCmBgYHtyfQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5Mjk3ODgiLAogIE1vZGVsbyA9ICJYR0Jvb3N0IChUcmFpbikiLAogIE1BUEUgPSBtYXBlX2ZpbmFsX3RyYWluXzM5Mjk3ODgsCiAgUk1TRSA9IHJtc2VfZmluYWxfdHJhaW5fMzkyOTc4OAopKQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTI5Nzg4IiwKICBNb2RlbG8gPSAiWEdCb29zdCAoVGVzdCkiLAogIE1BUEUgPSBtYXBlX2ZpbmFsX3Rlc3RfMzkyOTc4OCwKICBSTVNFID0gcm1zZV9maW5hbF90ZXN0XzM5Mjk3ODgKKSkKYGBgCgojIyBQUk9EVUNUTyAzOTA0MTUyCiMjIyBTUExJVCBJTlRFUk5PCmBgYHtyfQojIERpdmlkaXIgbG9zIGRhdG9zIGVuIGVudHJlbmFtaWVudG8gKDgwJSkgeSB2YWxpZGFjacOzbiBpbnRlcm5hICgyMCUpCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCgppbmRpY2VzX3RyYWluXzM5MDQxNTIgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkYXRvc18zOTA0MTUyJFZlbnRhLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpCgojIENyZWFyIHN1YmNvbmp1bnRvcwpkYXRvc190cmFpbl8zOTA0MTUyIDwtIGRhdG9zXzM5MDQxNTJbaW5kaWNlc190cmFpbl8zOTA0MTUyLCBdCmRhdG9zX3Rlc3RfMzkwNDE1MiAgPC0gZGF0b3NfMzkwNDE1MlstaW5kaWNlc190cmFpbl8zOTA0MTUyLCBdCgojIE1hdHJpY2VzIHByZWRpY3RvcmFzIHkgb2JqZXRpdm8KWF90cmFpbl8zOTA0MTUyIDwtIGFzLm1hdHJpeChkYXRvc190cmFpbl8zOTA0MTUyWywgY29sbmFtZXMoZGF0b3NfdHJhaW5fMzkwNDE1MikgIT0gIlZlbnRhIl0pCnlfdHJhaW5fMzkwNDE1MiA8LSBkYXRvc190cmFpbl8zOTA0MTUyJFZlbnRhCgpYX3Rlc3RfMzkwNDE1MiA8LSBhcy5tYXRyaXgoZGF0b3NfdGVzdF8zOTA0MTUyWywgY29sbmFtZXMoZGF0b3NfdGVzdF8zOTA0MTUyKSAhPSAiVmVudGEiXSkKeV90ZXN0XzM5MDQxNTIgPC0gZGF0b3NfdGVzdF8zOTA0MTUyJFZlbnRhCgojIENvbnZlcnRpciBhIGZvcm1hdG8gRE1hdHJpeCBwYXJhIFhHQm9vc3QKZHRyYWluXzM5MDQxNTIgPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfdHJhaW5fMzkwNDE1MiwgbGFiZWwgPSB5X3RyYWluXzM5MDQxNTIpCmR0ZXN0XzM5MDQxNTIgIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3Rlc3RfMzkwNDE1MiwgbGFiZWwgPSB5X3Rlc3RfMzkwNDE1MikKYGBgCgojIyMgQlVTUVVFREEgREUgSElQRVJQQVJBTUVUUk9TIENPTiBWQUxJREFDScOTTiBDUlVaQURBCmBgYHtyfQojIENyZWFyIHVuYSByZWppbGxhIGRlIGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvcwpwYXJhbV9ncmlkIDwtIGV4cGFuZC5ncmlkKAogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xKSwgICAgICAgICAgICAgIyBUYXNhIGRlIGFwcmVuZGl6YWplCiAgbWF4X2RlcHRoID0gYygzLCA1LCA3KSwgICAgICAgICAgICAgICAjIFByb2Z1bmRpZGFkIGRlbCDDoXJib2wKICBzdWJzYW1wbGUgPSBjKDAuOCwgMS4wKSwgICAgICAgICAgICAgICMgUHJvcG9yY2nDs24gZGUgbXVlc3RyYXMgcG9yIMOhcmJvbAogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuOCwgMS4wKSwgICAgICAgIyBQcm9wb3JjacOzbiBkZSBjb2x1bW5hcyBwb3Igw6FyYm9sCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMyksICAgICAgICAgICAjIFBlc28gbcOtbmltbyBkZSBub2RvcyBoaWpvcwogIGdhbW1hID0gYygwLCAwLjEpICAgICAgICAgICAgICAgICAgICAgIyBSZWd1bGFyaXphY2nDs24KKQoKIyBSZWR1Y2lyIGNvbWJpbmFjaW9uZXMgcGFyYSBlZmljaWVuY2lhCnNldC5zZWVkKDEyMykKcGFyYW1fZ3JpZF9yZWR1Y2lkYSA8LSBwYXJhbV9ncmlkW3NhbXBsZSgxOm5yb3cocGFyYW1fZ3JpZCksIDEwKSwgXQoKIyBGdW5jacOzbiBwYXJhIGV2YWx1YXIgY2FkYSBjb21iaW5hY2nDs24gY29uIHZhbGlkYWNpw7NuIGNydXphZGEKZXZhbHVhdGVfcGFyYW1zXzM5MDQxNTIgPC0gZnVuY3Rpb24ocGFyYW1zX3JvdykgewogIHBhcmFtcyA8LSBsaXN0KAogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgICBldGEgPSBwYXJhbXNfcm93JGV0YSwKICAgIG1heF9kZXB0aCA9IHBhcmFtc19yb3ckbWF4X2RlcHRoLAogICAgc3Vic2FtcGxlID0gcGFyYW1zX3JvdyRzdWJzYW1wbGUsCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1zX3JvdyRjb2xzYW1wbGVfYnl0cmVlLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtc19yb3ckbWluX2NoaWxkX3dlaWdodCwKICAgIGdhbW1hID0gcGFyYW1zX3JvdyRnYW1tYQogICkKICAKICBjdiA8LSB4Z2IuY3YoCiAgICBwYXJhbXMgPSBwYXJhbXMsCiAgICBkYXRhID0gZHRyYWluXzM5MDQxNTIsICAgICMgPC0gdXNhIERNYXRyaXggY29uIGRhdG9zX3RyYWluXzE1NTAwMQogICAgbnJvdW5kcyA9IDEwMCwKICAgIG5mb2xkID0gNSwKICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDEwLAogICAgdmVyYm9zZSA9IDAKICApCiAgCiAgbGlzdCgKICAgIHJtc2UgPSBtaW4oY3YkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pLAogICAgbnJvdW5kcyA9IHdoaWNoLm1pbihjdiRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbiksCiAgICBwYXJhbXMgPSBwYXJhbXMKICApCn0KYGBgCgpgYGB7cn0KIyBFdmFsdWFyIHRvZGFzIGxhcyBjb21iaW5hY2lvbmVzCnJlc3VsdGFkb3NfZ3JpZF8zOTA0MTUyIDwtIHBhcmFtX2dyaWRfcmVkdWNpZGEKcmVzdWx0YWRvc19ncmlkXzM5MDQxNTIkbnJvdW5kcyA8LSBOQQpyZXN1bHRhZG9zX2dyaWRfMzkwNDE1MiRybXNlIDwtIE5BCgpjYXQoIkluaWNpYW5kbyBiw7pzcXVlZGEgZGUgaGlwZXJwYXLDoW1ldHJvcyBwYXJhIHByb2R1Y3RvIDM5MDQxNTIuLi5cbiIpCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpIHsKICBjYXQoc3ByaW50ZigiRXZhbHVhbmRvIGNvbWJpbmFjacOzbiAlZCBkZSAlZFxuIiwgaSwgbnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSkpCiAgCiAgcmVzdWx0YWRvIDwtIGV2YWx1YXRlX3BhcmFtc18zOTA0MTUyKHBhcmFtX2dyaWRfcmVkdWNpZGFbaSwgXSkKICAKICByZXN1bHRhZG9zX2dyaWRfMzkwNDE1MiRucm91bmRzW2ldIDwtIHJlc3VsdGFkbyRucm91bmRzCiAgcmVzdWx0YWRvc19ncmlkXzM5MDQxNTIkcm1zZVtpXSA8LSByZXN1bHRhZG8kcm1zZQp9CgojIE9yZGVuYXIgcG9yIG1lam9yIFJNU0UKcmVzdWx0YWRvc19ncmlkXzM5MDQxNTIgPC0gcmVzdWx0YWRvc19ncmlkXzM5MDQxNTJbb3JkZXIocmVzdWx0YWRvc19ncmlkXzM5MDQxNTIkcm1zZSksIF0KCiMgTW9zdHJhciBsYXMgNSBtZWpvcmVzIGNvbWJpbmFjaW9uZXMKY2F0KCJcblRvcCA1IGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvcyAoUHJvZHVjdG8gMzkwNDE1Mik6XG4iKQpwcmludChoZWFkKHJlc3VsdGFkb3NfZ3JpZF8zOTA0MTUyLCA1KSkKCmBgYAoKIyMjIEVOVFJFTkEgTU9ERUxPIENPTiBNRUpPUkVTIEhJUEVSUEFSw4FNRVRST1MgCmBgYHtyfQojIEV4dHJhZXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcyBkZWwgZ3JpZAptZWpvcmVzX3BhcmFtc18zOTA0MTUyIDwtIGxpc3QoCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogIGV2YWxfbWV0cmljID0gInJtc2UiLAogIGV0YSA9IHJlc3VsdGFkb3NfZ3JpZF8zOTA0MTUyJGV0YVsxXSwKICBtYXhfZGVwdGggPSByZXN1bHRhZG9zX2dyaWRfMzkwNDE1MiRtYXhfZGVwdGhbMV0sCiAgc3Vic2FtcGxlID0gcmVzdWx0YWRvc19ncmlkXzM5MDQxNTIkc3Vic2FtcGxlWzFdLAogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zX2dyaWRfMzkwNDE1MiRjb2xzYW1wbGVfYnl0cmVlWzFdLAogIG1pbl9jaGlsZF93ZWlnaHQgPSByZXN1bHRhZG9zX2dyaWRfMzkwNDE1MiRtaW5fY2hpbGRfd2VpZ2h0WzFdLAogIGdhbW1hID0gcmVzdWx0YWRvc19ncmlkXzM5MDQxNTIkZ2FtbWFbMV0KKQoKIyBOw7ptZXJvIMOzcHRpbW8gZGUgcm9uZGFzCm1lam9yX25yb3VuZHNfMzkwNDE1MiA8LSByZXN1bHRhZG9zX2dyaWRfMzkwNDE1MiRucm91bmRzWzFdCgpjYXQoIkVudHJlbmFuZG8gbW9kZWxvIGNvbiBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MuLi5cbiIpCnByaW50KG1lam9yZXNfcGFyYW1zXzM5MDQxNTIpCmNhdCgiTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhczoiLCBtZWpvcl9ucm91bmRzXzM5MDQxNTIsICJcbiIpCgojIEVudHJlbmFyIGVsIG1vZGVsbyBjb24gbG9zIGRhdG9zIGRlIHNwbGl0IGludGVybm8KbW9kZWxvX3hnYl8zOTA0MTUyX3NwbGl0IDwtIHhnYi50cmFpbigKICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtc18zOTA0MTUyLAogIGRhdGEgPSBkdHJhaW5fMzkwNDE1MiwKICBucm91bmRzID0gbWVqb3JfbnJvdW5kc18zOTA0MTUyLAogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW5fMzkwNDE1MiwgdGVzdCA9IGR0ZXN0XzM5MDQxNTIpLAogIHZlcmJvc2UgPSAwCikKYGBgCgojIyMgTcOJVFJJQ0FTIElOVEVSTkFTCmBgYHtyfQojIE3DqXRyaWNhcyBzb2JyZSBkYXRvc190cmFpbiAoODAlKQpwcmVkX3RyYWluX3NwbGl0XzM5MDQxNTIgPC0gcHJlZGljdChtb2RlbG9feGdiXzM5MDQxNTJfc3BsaXQsIGR0cmFpbl8zOTA0MTUyKQptYXBlX3RyYWluX3NwbGl0XzM5MDQxNTIgPC0gbWVhbihhYnMoKHlfdHJhaW5fMzkwNDE1MiAtIHByZWRfdHJhaW5fc3BsaXRfMzkwNDE1MikgLyBwbWF4KHlfdHJhaW5fMzkwNDE1MiwgMC4wMSkpKSAqIDEwMApybXNlX3RyYWluX3NwbGl0XzM5MDQxNTIgPC0gc3FydChtZWFuKCh5X3RyYWluXzM5MDQxNTIgLSBwcmVkX3RyYWluX3NwbGl0XzM5MDQxNTIpXjIpKQoKIyBNw6l0cmljYXMgc29icmUgZGF0b3NfdGVzdCAoMjAlKQpwcmVkX3Rlc3Rfc3BsaXRfMzkwNDE1MiA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMzkwNDE1Ml9zcGxpdCwgZHRlc3RfMzkwNDE1MikKbWFwZV90ZXN0X3NwbGl0XzM5MDQxNTIgPC0gbWVhbihhYnMoKHlfdGVzdF8zOTA0MTUyIC0gcHJlZF90ZXN0X3NwbGl0XzM5MDQxNTIpIC8gcG1heCh5X3Rlc3RfMzkwNDE1MiwgMC4wMSkpKSAqIDEwMApybXNlX3Rlc3Rfc3BsaXRfMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKHlfdGVzdF8zOTA0MTUyIC0gcHJlZF90ZXN0X3NwbGl0XzM5MDQxNTIpXjIpKQoKY2F0KCJTcGxpdCBJbnRlcm5vIC0gUHJvZHVjdG8gMzkwNDE1MlxuIikKY2F0KCJUcmFpbiBTcGxpdCAtIE1BUEU6Iiwgcm91bmQobWFwZV90cmFpbl9zcGxpdF8zOTA0MTUyLCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX3RyYWluX3NwbGl0XzM5MDQxNTIsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIFNwbGl0IC0gTUFQRToiLCByb3VuZChtYXBlX3Rlc3Rfc3BsaXRfMzkwNDE1MiwgMiksICAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdGVzdF9zcGxpdF8zOTA0MTUyLCAyKSwgIlxuIikKYGBgCgojIyMgRU5UUkVOQU1JRU5UTyBDT04gREFUT1MgQ09NUExFVE9TIApgYGB7cn0KIyBQcmVwYXJhciBtYXRyaWNlcyBjb24gdG9kb3MgbG9zIGRhdG9zClhfZW50ZXJvXzM5MDQxNTIgPC0gYXMubWF0cml4KGRhdG9zXzM5MDQxNTJbLCBjb2xuYW1lcyhkYXRvc18zOTA0MTUyKSAhPSAiVmVudGEiXSkKeV9lbnRlcm9fMzkwNDE1MiA8LSBkYXRvc18zOTA0MTUyJFZlbnRhCmR0cmFpbl9maW5hbF8zOTA0MTUyIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX2VudGVyb18zOTA0MTUyLCBsYWJlbCA9IHlfZW50ZXJvXzM5MDQxNTIpCgojIEVudHJlbmFyIGVsIG1vZGVsbyBmaW5hbAptb2RlbG9feGdiX2ZpbmFsXzM5MDQxNTIgPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zXzM5MDQxNTIsCiAgZGF0YSA9IGR0cmFpbl9maW5hbF8zOTA0MTUyLAogIG5yb3VuZHMgPSBtZWpvcl9ucm91bmRzXzM5MDQxNTIsCiAgdmVyYm9zZSA9IDAKKQoKIyBFdmFsdWFjacOzbiBlbiBkYXRvc18zOTA0MTUyCnByZWRfZmluYWxfdHJhaW5fMzkwNDE1MiA8LSBwcmVkaWN0KG1vZGVsb194Z2JfZmluYWxfMzkwNDE1MiwgWF9lbnRlcm9fMzkwNDE1MikKbWFwZV9maW5hbF90cmFpbl8zOTA0MTUyIDwtIG1lYW4oYWJzKCh5X2VudGVyb18zOTA0MTUyIC0gcHJlZF9maW5hbF90cmFpbl8zOTA0MTUyKSAvIHBtYXgoeV9lbnRlcm9fMzkwNDE1MiwgMC4wMSkpKSAqIDEwMApybXNlX2ZpbmFsX3RyYWluXzM5MDQxNTIgPC0gc3FydChtZWFuKCh5X2VudGVyb18zOTA0MTUyIC0gcHJlZF9maW5hbF90cmFpbl8zOTA0MTUyKV4yKSkKCiMgRXZhbHVhY2nDs24gZW4gdGVzdF8zOTA0MTUyClhfdGVzdF9yZWFsXzM5MDQxNTIgPC0gYXMubWF0cml4KHRlc3RfMzkwNDE1MlssIGNvbG5hbWVzKHRlc3RfMzkwNDE1MikgIT0gIlZlbnRhIl0pCnlfdGVzdF9yZWFsXzM5MDQxNTIgPC0gdGVzdF8zOTA0MTUyJFZlbnRhCnByZWRfZmluYWxfdGVzdF8zOTA0MTUyIDwtIHByZWRpY3QobW9kZWxvX3hnYl9maW5hbF8zOTA0MTUyLCBYX3Rlc3RfcmVhbF8zOTA0MTUyKQptYXBlX2ZpbmFsX3Rlc3RfMzkwNDE1MiA8LSBtZWFuKGFicygoeV90ZXN0X3JlYWxfMzkwNDE1MiAtIHByZWRfZmluYWxfdGVzdF8zOTA0MTUyKSAvIHBtYXgoeV90ZXN0X3JlYWxfMzkwNDE1MiwgMC4wMSkpKSAqIDEwMApybXNlX2ZpbmFsX3Rlc3RfMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKHlfdGVzdF9yZWFsXzM5MDQxNTIgLSBwcmVkX2ZpbmFsX3Rlc3RfMzkwNDE1MileMikpCgpjYXQoIk1vZGVsbyBGaW5hbCAtIFByb2R1Y3RvIDM5MDQxNTJcbiIpCmNhdCgiVHJhaW4gLSBNQVBFOiIsIHJvdW5kKG1hcGVfZmluYWxfdHJhaW5fMzkwNDE1MiwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV9maW5hbF90cmFpbl8zOTA0MTUyLCAyKSwgIlxuIikKY2F0KCJUZXN0ICAtIE1BUEU6Iiwgcm91bmQobWFwZV9maW5hbF90ZXN0XzM5MDQxNTIsIDIpLCAgInwgUk1TRToiLCByb3VuZChybXNlX2ZpbmFsX3Rlc3RfMzkwNDE1MiwgMiksICJcbiIpCgpgYGAKCiMjIyBUQUJMQSBERSBNw4lUUklDQVMKYGBge3J9Cm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkwNDE1MiIsCiAgTW9kZWxvID0gIlhHQm9vc3QgKFRyYWluKSIsCiAgTUFQRSA9IG1hcGVfZmluYWxfdHJhaW5fMzkwNDE1MiwKICBSTVNFID0gcm1zZV9maW5hbF90cmFpbl8zOTA0MTUyCikpCgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjM5MDQxNTIiLAogIE1vZGVsbyA9ICJYR0Jvb3N0IChUZXN0KSIsCiAgTUFQRSA9IG1hcGVfZmluYWxfdGVzdF8zOTA0MTUyLAogIFJNU0UgPSBybXNlX2ZpbmFsX3Rlc3RfMzkwNDE1MgopKQpgYGAKCiMjIFBST0RVQ1RPIDE1NTAwMgojIyMgU1BMSVQgSU5URVJOTwpgYGB7cn0KIyBEaXZpZGlyIGxvcyBkYXRvcyBlbiBlbnRyZW5hbWllbnRvICg4MCUpIHkgdmFsaWRhY2nDs24gaW50ZXJuYSAoMjAlKQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAoKaW5kaWNlc190cmFpbl8xNTUwMDIgPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkYXRvc18xNTUwMDIkVmVudGEsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkKCiMgQ3JlYXIgc3ViY29uanVudG9zCmRhdG9zX3RyYWluXzE1NTAwMiA8LSBkYXRvc18xNTUwMDJbaW5kaWNlc190cmFpbl8xNTUwMDIsIF0KZGF0b3NfdGVzdF8xNTUwMDIgIDwtIGRhdG9zXzE1NTAwMlstaW5kaWNlc190cmFpbl8xNTUwMDIsIF0KCiMgTWF0cmljZXMgcHJlZGljdG9yYXMgeSBvYmpldGl2bwpYX3RyYWluXzE1NTAwMiA8LSBhcy5tYXRyaXgoZGF0b3NfdHJhaW5fMTU1MDAyWywgY29sbmFtZXMoZGF0b3NfdHJhaW5fMTU1MDAyKSAhPSAiVmVudGEiXSkKeV90cmFpbl8xNTUwMDIgPC0gZGF0b3NfdHJhaW5fMTU1MDAyJFZlbnRhCgpYX3Rlc3RfMTU1MDAyIDwtIGFzLm1hdHJpeChkYXRvc190ZXN0XzE1NTAwMlssIGNvbG5hbWVzKGRhdG9zX3Rlc3RfMTU1MDAyKSAhPSAiVmVudGEiXSkKeV90ZXN0XzE1NTAwMiA8LSBkYXRvc190ZXN0XzE1NTAwMiRWZW50YQoKIyBDb252ZXJ0aXIgYSBmb3JtYXRvIERNYXRyaXggcGFyYSBYR0Jvb3N0CmR0cmFpbl8xNTUwMDIgPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfdHJhaW5fMTU1MDAyLCBsYWJlbCA9IHlfdHJhaW5fMTU1MDAyKQpkdGVzdF8xNTUwMDIgIDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3Rlc3RfMTU1MDAyLCBsYWJlbCA9IHlfdGVzdF8xNTUwMDIpCmBgYAoKIyMjIEJVU1FVRURBIERFIEhJUEVSUEFSQU1FVFJPUyBDT04gVkFMSURBQ0nDk04gQ1JVWkFEQQpgYGB7cn0KIyBDcmVhciB1bmEgcmVqaWxsYSBkZSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3MKcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgKICBldGEgPSBjKDAuMDEsIDAuMDUsIDAuMSksICAgICAgICAgICAgICMgVGFzYSBkZSBhcHJlbmRpemFqZQogIG1heF9kZXB0aCA9IGMoMywgNSwgNyksICAgICAgICAgICAgICAgIyBQcm9mdW5kaWRhZCBkZWwgw6FyYm9sCiAgc3Vic2FtcGxlID0gYygwLjgsIDEuMCksICAgICAgICAgICAgICAjIFByb3BvcmNpw7NuIGRlIG11ZXN0cmFzIHBvciDDoXJib2wKICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjgsIDEuMCksICAgICAgICMgUHJvcG9yY2nDs24gZGUgY29sdW1uYXMgcG9yIMOhcmJvbAogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMpLCAgICAgICAgICAgIyBQZXNvIG3DrW5pbW8gZGUgbm9kb3MgaGlqb3MKICBnYW1tYSA9IGMoMCwgMC4xKSAgICAgICAgICAgICAgICAgICAgICMgUmVndWxhcml6YWNpw7NuCikKCiMgUmVkdWNpciBjb21iaW5hY2lvbmVzIHBhcmEgZWZpY2llbmNpYQpzZXQuc2VlZCgxMjMpCnBhcmFtX2dyaWRfcmVkdWNpZGEgPC0gcGFyYW1fZ3JpZFtzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAxMCksIF0KCiMgRnVuY2nDs24gcGFyYSBldmFsdWFyIGNhZGEgY29tYmluYWNpw7NuIGNvbiB2YWxpZGFjacOzbiBjcnV6YWRhCmV2YWx1YXRlX3BhcmFtc18xNTUwMDIgPC0gZnVuY3Rpb24ocGFyYW1zX3JvdykgewogIHBhcmFtcyA8LSBsaXN0KAogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgICBldGEgPSBwYXJhbXNfcm93JGV0YSwKICAgIG1heF9kZXB0aCA9IHBhcmFtc19yb3ckbWF4X2RlcHRoLAogICAgc3Vic2FtcGxlID0gcGFyYW1zX3JvdyRzdWJzYW1wbGUsCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1zX3JvdyRjb2xzYW1wbGVfYnl0cmVlLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtc19yb3ckbWluX2NoaWxkX3dlaWdodCwKICAgIGdhbW1hID0gcGFyYW1zX3JvdyRnYW1tYQogICkKICAKICBjdiA8LSB4Z2IuY3YoCiAgICBwYXJhbXMgPSBwYXJhbXMsCiAgICBkYXRhID0gZHRyYWluXzE1NTAwMiwgICAgIyA8LSB1c2EgRE1hdHJpeCBjb24gZGF0b3NfdHJhaW5fMTU1MDAxCiAgICBucm91bmRzID0gMTAwLAogICAgbmZvbGQgPSA1LAogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAsCiAgICB2ZXJib3NlID0gMAogICkKICAKICBsaXN0KAogICAgcm1zZSA9IG1pbihjdiRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbiksCiAgICBucm91bmRzID0gd2hpY2gubWluKGN2JGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKSwKICAgIHBhcmFtcyA9IHBhcmFtcwogICkKfQpgYGAKCmBgYHtyfQojIEV2YWx1YXIgdG9kYXMgbGFzIGNvbWJpbmFjaW9uZXMKcmVzdWx0YWRvc19ncmlkXzE1NTAwMiA8LSBwYXJhbV9ncmlkX3JlZHVjaWRhCnJlc3VsdGFkb3NfZ3JpZF8xNTUwMDIkbnJvdW5kcyA8LSBOQQpyZXN1bHRhZG9zX2dyaWRfMTU1MDAyJHJtc2UgPC0gTkEKCmNhdCgiSW5pY2lhbmRvIGLDunNxdWVkYSBkZSBoaXBlcnBhcsOhbWV0cm9zIHBhcmEgcHJvZHVjdG8gMTU1MDAyLi4uXG4iKQpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSB7CiAgY2F0KHNwcmludGYoIkV2YWx1YW5kbyBjb21iaW5hY2nDs24gJWQgZGUgJWRcbiIsIGksIG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpKQogIAogIHJlc3VsdGFkbyA8LSBldmFsdWF0ZV9wYXJhbXNfMTU1MDAyKHBhcmFtX2dyaWRfcmVkdWNpZGFbaSwgXSkKICAKICByZXN1bHRhZG9zX2dyaWRfMTU1MDAyJG5yb3VuZHNbaV0gPC0gcmVzdWx0YWRvJG5yb3VuZHMKICByZXN1bHRhZG9zX2dyaWRfMTU1MDAyJHJtc2VbaV0gPC0gcmVzdWx0YWRvJHJtc2UKfQoKIyBPcmRlbmFyIHBvciBtZWpvciBSTVNFCnJlc3VsdGFkb3NfZ3JpZF8xNTUwMDIgPC0gcmVzdWx0YWRvc19ncmlkXzE1NTAwMltvcmRlcihyZXN1bHRhZG9zX2dyaWRfMTU1MDAyJHJtc2UpLCBdCgojIE1vc3RyYXIgbGFzIDUgbWVqb3JlcyBjb21iaW5hY2lvbmVzCmNhdCgiXG5Ub3AgNSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3MgKFByb2R1Y3RvIDE1NTAwMik6XG4iKQpwcmludChoZWFkKHJlc3VsdGFkb3NfZ3JpZF8xNTUwMDIsIDUpKQoKYGBgCgojIyMgRU5UUkVOQSBNT0RFTE8gQ09OIE1FSk9SRVMgSElQRVJQQVLDgU1FVFJPUyAKYGBge3J9CiMgRXh0cmFlciBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zIGRlbCBncmlkCm1lam9yZXNfcGFyYW1zXzE1NTAwMiA8LSBsaXN0KAogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICBldmFsX21ldHJpYyA9ICJybXNlIiwKICBldGEgPSByZXN1bHRhZG9zX2dyaWRfMTU1MDAyJGV0YVsxXSwKICBtYXhfZGVwdGggPSByZXN1bHRhZG9zX2dyaWRfMTU1MDAyJG1heF9kZXB0aFsxXSwKICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zX2dyaWRfMTU1MDAyJHN1YnNhbXBsZVsxXSwKICBjb2xzYW1wbGVfYnl0cmVlID0gcmVzdWx0YWRvc19ncmlkXzE1NTAwMiRjb2xzYW1wbGVfYnl0cmVlWzFdLAogIG1pbl9jaGlsZF93ZWlnaHQgPSByZXN1bHRhZG9zX2dyaWRfMTU1MDAyJG1pbl9jaGlsZF93ZWlnaHRbMV0sCiAgZ2FtbWEgPSByZXN1bHRhZG9zX2dyaWRfMTU1MDAyJGdhbW1hWzFdCikKCiMgTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhcwptZWpvcl9ucm91bmRzXzE1NTAwMiA8LSByZXN1bHRhZG9zX2dyaWRfMTU1MDAyJG5yb3VuZHNbMV0KCmNhdCgiRW50cmVuYW5kbyBtb2RlbG8gY29uIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcy4uLlxuIikKcHJpbnQobWVqb3Jlc19wYXJhbXNfMTU1MDAyKQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kc18xNTUwMDIsICJcbiIpCgojIEVudHJlbmFyIGVsIG1vZGVsbyBjb24gbG9zIGRhdG9zIGRlIHNwbGl0IGludGVybm8KbW9kZWxvX3hnYl8xNTUwMDJfc3BsaXQgPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zXzE1NTAwMiwKICBkYXRhID0gZHRyYWluXzE1NTAwMiwKICBucm91bmRzID0gbWVqb3JfbnJvdW5kc18xNTUwMDIsCiAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbl8xNTUwMDIsIHRlc3QgPSBkdGVzdF8xNTUwMDIpLAogIHZlcmJvc2UgPSAwCikKYGBgCgojIyMgTcOJVFJJQ0FTIElOVEVSTkFTCmBgYHtyfQojIE3DqXRyaWNhcyBzb2JyZSBkYXRvc190cmFpbiAoODAlKQpwcmVkX3RyYWluX3NwbGl0XzE1NTAwMiA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMTU1MDAyX3NwbGl0LCBkdHJhaW5fMTU1MDAyKQptYXBlX3RyYWluX3NwbGl0XzE1NTAwMiA8LSBtZWFuKGFicygoeV90cmFpbl8xNTUwMDIgLSBwcmVkX3RyYWluX3NwbGl0XzE1NTAwMikgLyBwbWF4KHlfdHJhaW5fMTU1MDAyLCAwLjAxKSkpICogMTAwCnJtc2VfdHJhaW5fc3BsaXRfMTU1MDAyIDwtIHNxcnQobWVhbigoeV90cmFpbl8xNTUwMDIgLSBwcmVkX3RyYWluX3NwbGl0XzE1NTAwMileMikpCgojIE3DqXRyaWNhcyBzb2JyZSBkYXRvc190ZXN0ICgyMCUpCnByZWRfdGVzdF9zcGxpdF8xNTUwMDIgPC0gcHJlZGljdChtb2RlbG9feGdiXzE1NTAwMl9zcGxpdCwgZHRlc3RfMTU1MDAyKQptYXBlX3Rlc3Rfc3BsaXRfMTU1MDAyIDwtIG1lYW4oYWJzKCh5X3Rlc3RfMTU1MDAyIC0gcHJlZF90ZXN0X3NwbGl0XzE1NTAwMikgLyBwbWF4KHlfdGVzdF8xNTUwMDIsIDAuMDEpKSkgKiAxMDAKcm1zZV90ZXN0X3NwbGl0XzE1NTAwMiA8LSBzcXJ0KG1lYW4oKHlfdGVzdF8xNTUwMDIgLSBwcmVkX3Rlc3Rfc3BsaXRfMTU1MDAyKV4yKSkKCmNhdCgiU3BsaXQgSW50ZXJubyAtIFByb2R1Y3RvIDE1NTAwMlxuIikKY2F0KCJUcmFpbiBTcGxpdCAtIE1BUEU6Iiwgcm91bmQobWFwZV90cmFpbl9zcGxpdF8xNTUwMDIsIDIpLCAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdHJhaW5fc3BsaXRfMTU1MDAyLCAyKSwgIlxuIikKY2F0KCJUZXN0ICBTcGxpdCAtIE1BUEU6Iiwgcm91bmQobWFwZV90ZXN0X3NwbGl0XzE1NTAwMiwgMiksICAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdGVzdF9zcGxpdF8xNTUwMDIsIDIpLCAiXG4iKQpgYGAKCiMjIyBFTlRSRU5BTUlFTlRPIENPTiBEQVRPUyBDT01QTEVUT1MgCmBgYHtyfQojIFByZXBhcmFyIG1hdHJpY2VzIGNvbiB0b2RvcyBsb3MgZGF0b3MKWF9lbnRlcm9fMTU1MDAyIDwtIGFzLm1hdHJpeChkYXRvc18xNTUwMDJbLCBjb2xuYW1lcyhkYXRvc18xNTUwMDIpICE9ICJWZW50YSJdKQp5X2VudGVyb18xNTUwMDIgPC0gZGF0b3NfMTU1MDAyJFZlbnRhCmR0cmFpbl9maW5hbF8xNTUwMDIgPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfZW50ZXJvXzE1NTAwMiwgbGFiZWwgPSB5X2VudGVyb18xNTUwMDIpCgojIEVudHJlbmFyIGVsIG1vZGVsbyBmaW5hbAptb2RlbG9feGdiX2ZpbmFsXzE1NTAwMiA8LSB4Z2IudHJhaW4oCiAgcGFyYW1zID0gbWVqb3Jlc19wYXJhbXNfMTU1MDAyLAogIGRhdGEgPSBkdHJhaW5fZmluYWxfMTU1MDAyLAogIG5yb3VuZHMgPSBtZWpvcl9ucm91bmRzXzE1NTAwMiwKICB2ZXJib3NlID0gMAopCgojIEV2YWx1YWNpw7NuIGVuIGRhdG9zXzE1NTAwMgpwcmVkX2ZpbmFsX3RyYWluXzE1NTAwMiA8LSBwcmVkaWN0KG1vZGVsb194Z2JfZmluYWxfMTU1MDAyLCBYX2VudGVyb18xNTUwMDIpCm1hcGVfZmluYWxfdHJhaW5fMTU1MDAyIDwtIG1lYW4oYWJzKCh5X2VudGVyb18xNTUwMDIgLSBwcmVkX2ZpbmFsX3RyYWluXzE1NTAwMikgLyBwbWF4KHlfZW50ZXJvXzE1NTAwMiwgMC4wMSkpKSAqIDEwMApybXNlX2ZpbmFsX3RyYWluXzE1NTAwMiA8LSBzcXJ0KG1lYW4oKHlfZW50ZXJvXzE1NTAwMiAtIHByZWRfZmluYWxfdHJhaW5fMTU1MDAyKV4yKSkKCiMgRXZhbHVhY2nDs24gZW4gdGVzdF8xNTUwMDIKWF90ZXN0X3JlYWxfMTU1MDAyIDwtIGFzLm1hdHJpeCh0ZXN0XzE1NTAwMlssIGNvbG5hbWVzKHRlc3RfMTU1MDAyKSAhPSAiVmVudGEiXSkKeV90ZXN0X3JlYWxfMTU1MDAyIDwtIHRlc3RfMTU1MDAyJFZlbnRhCnByZWRfZmluYWxfdGVzdF8xNTUwMDIgPC0gcHJlZGljdChtb2RlbG9feGdiX2ZpbmFsXzE1NTAwMiwgWF90ZXN0X3JlYWxfMTU1MDAyKQptYXBlX2ZpbmFsX3Rlc3RfMTU1MDAyIDwtIG1lYW4oYWJzKCh5X3Rlc3RfcmVhbF8xNTUwMDIgLSBwcmVkX2ZpbmFsX3Rlc3RfMTU1MDAyKSAvIHBtYXgoeV90ZXN0X3JlYWxfMTU1MDAyLCAwLjAxKSkpICogMTAwCnJtc2VfZmluYWxfdGVzdF8xNTUwMDIgPC0gc3FydChtZWFuKCh5X3Rlc3RfcmVhbF8xNTUwMDIgLSBwcmVkX2ZpbmFsX3Rlc3RfMTU1MDAyKV4yKSkKCmNhdCgiTW9kZWxvIEZpbmFsIC0gUHJvZHVjdG8gMTU1MDAyXG4iKQpjYXQoIlRyYWluIC0gTUFQRToiLCByb3VuZChtYXBlX2ZpbmFsX3RyYWluXzE1NTAwMiwgMiksICJ8IFJNU0U6Iiwgcm91bmQocm1zZV9maW5hbF90cmFpbl8xNTUwMDIsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIC0gTUFQRToiLCByb3VuZChtYXBlX2ZpbmFsX3Rlc3RfMTU1MDAyLCAyKSwgICJ8IFJNU0U6Iiwgcm91bmQocm1zZV9maW5hbF90ZXN0XzE1NTAwMiwgMiksICJcbiIpCgpgYGAKCiMjIyBUQUJMQSBERSBNw4lUUklDQVMKYGBge3J9Cm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAyIiwKICBNb2RlbG8gPSAiWEdCb29zdCAoVHJhaW4pIiwKICBNQVBFID0gbWFwZV9maW5hbF90cmFpbl8xNTUwMDIsCiAgUk1TRSA9IHJtc2VfZmluYWxfdHJhaW5fMTU1MDAyCikpCgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMiIsCiAgTW9kZWxvID0gIlhHQm9vc3QgKFRlc3QpIiwKICBNQVBFID0gbWFwZV9maW5hbF90ZXN0XzE1NTAwMiwKICBSTVNFID0gcm1zZV9maW5hbF90ZXN0XzE1NTAwMgopKQpgYGAKCiMjIFBST0RVQ1RPIDM2NzgwNTUKIyMjIFNQTElUIElOVEVSTk8KYGBge3J9CiMgRGl2aWRpciBsb3MgZGF0b3MgZW4gZW50cmVuYW1pZW50byAoODAlKSB5IHZhbGlkYWNpw7NuIGludGVybmEgKDIwJSkKc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQKCmluZGljZXNfdHJhaW5fMzY3ODA1NSA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zXzM2NzgwNTUkVmVudGEsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkKCiMgQ3JlYXIgc3ViY29uanVudG9zCmRhdG9zX3RyYWluXzM2NzgwNTUgPC0gZGF0b3NfMzY3ODA1NVtpbmRpY2VzX3RyYWluXzM2NzgwNTUsIF0KZGF0b3NfdGVzdF8zNjc4MDU1ICA8LSBkYXRvc18zNjc4MDU1Wy1pbmRpY2VzX3RyYWluXzM2NzgwNTUsIF0KCiMgTWF0cmljZXMgcHJlZGljdG9yYXMgeSBvYmpldGl2bwpYX3RyYWluXzM2NzgwNTUgPC0gYXMubWF0cml4KGRhdG9zX3RyYWluXzM2NzgwNTVbLCBjb2xuYW1lcyhkYXRvc190cmFpbl8zNjc4MDU1KSAhPSAiVmVudGEiXSkKeV90cmFpbl8zNjc4MDU1IDwtIGRhdG9zX3RyYWluXzM2NzgwNTUkVmVudGEKClhfdGVzdF8zNjc4MDU1IDwtIGFzLm1hdHJpeChkYXRvc190ZXN0XzM2NzgwNTVbLCBjb2xuYW1lcyhkYXRvc190ZXN0XzM2NzgwNTUpICE9ICJWZW50YSJdKQp5X3Rlc3RfMzY3ODA1NSA8LSBkYXRvc190ZXN0XzM2NzgwNTUkVmVudGEKCiMgQ29udmVydGlyIGEgZm9ybWF0byBETWF0cml4IHBhcmEgWEdCb29zdApkdHJhaW5fMzY3ODA1NSA8LSB4Z2IuRE1hdHJpeChkYXRhID0gWF90cmFpbl8zNjc4MDU1LCBsYWJlbCA9IHlfdHJhaW5fMzY3ODA1NSkKZHRlc3RfMzY3ODA1NSAgPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfdGVzdF8zNjc4MDU1LCBsYWJlbCA9IHlfdGVzdF8zNjc4MDU1KQpgYGAKCiMjIyBCVVNRVUVEQSBERSBISVBFUlBBUkFNRVRST1MgQ09OIFZBTElEQUNJw5NOIENSVVpBREEKYGBge3J9CiMgQ3JlYXIgdW5hIHJlamlsbGEgZGUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zCnBhcmFtX2dyaWQgPC0gZXhwYW5kLmdyaWQoCiAgZXRhID0gYygwLjAxLCAwLjA1LCAwLjEpLCAgICAgICAgICAgICAjIFRhc2EgZGUgYXByZW5kaXphamUKICBtYXhfZGVwdGggPSBjKDMsIDUsIDcpLCAgICAgICAgICAgICAgICMgUHJvZnVuZGlkYWQgZGVsIMOhcmJvbAogIHN1YnNhbXBsZSA9IGMoMC44LCAxLjApLCAgICAgICAgICAgICAgIyBQcm9wb3JjacOzbiBkZSBtdWVzdHJhcyBwb3Igw6FyYm9sCiAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC44LCAxLjApLCAgICAgICAjIFByb3BvcmNpw7NuIGRlIGNvbHVtbmFzIHBvciDDoXJib2wKICBtaW5fY2hpbGRfd2VpZ2h0ID0gYygxLCAzKSwgICAgICAgICAgICMgUGVzbyBtw61uaW1vIGRlIG5vZG9zIGhpam9zCiAgZ2FtbWEgPSBjKDAsIDAuMSkgICAgICAgICAgICAgICAgICAgICAjIFJlZ3VsYXJpemFjacOzbgopCgojIFJlZHVjaXIgY29tYmluYWNpb25lcyBwYXJhIGVmaWNpZW5jaWEKc2V0LnNlZWQoMTIzKQpwYXJhbV9ncmlkX3JlZHVjaWRhIDwtIHBhcmFtX2dyaWRbc2FtcGxlKDE6bnJvdyhwYXJhbV9ncmlkKSwgMTApLCBdCgojIEZ1bmNpw7NuIHBhcmEgZXZhbHVhciBjYWRhIGNvbWJpbmFjacOzbiBjb24gdmFsaWRhY2nDs24gY3J1emFkYQpldmFsdWF0ZV9wYXJhbXNfMzY3ODA1NSA8LSBmdW5jdGlvbihwYXJhbXNfcm93KSB7CiAgcGFyYW1zIDwtIGxpc3QoCiAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwKICAgIGV0YSA9IHBhcmFtc19yb3ckZXRhLAogICAgbWF4X2RlcHRoID0gcGFyYW1zX3JvdyRtYXhfZGVwdGgsCiAgICBzdWJzYW1wbGUgPSBwYXJhbXNfcm93JHN1YnNhbXBsZSwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbXNfcm93JGNvbHNhbXBsZV9ieXRyZWUsCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1zX3JvdyRtaW5fY2hpbGRfd2VpZ2h0LAogICAgZ2FtbWEgPSBwYXJhbXNfcm93JGdhbW1hCiAgKQogIAogIGN2IDwtIHhnYi5jdigKICAgIHBhcmFtcyA9IHBhcmFtcywKICAgIGRhdGEgPSBkdHJhaW5fMzY3ODA1NSwgICAgIyA8LSB1c2EgRE1hdHJpeCBjb24gZGF0b3NfdHJhaW5fMTU1MDAxCiAgICBucm91bmRzID0gMTAwLAogICAgbmZvbGQgPSA1LAogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAsCiAgICB2ZXJib3NlID0gMAogICkKICAKICBsaXN0KAogICAgcm1zZSA9IG1pbihjdiRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbiksCiAgICBucm91bmRzID0gd2hpY2gubWluKGN2JGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKSwKICAgIHBhcmFtcyA9IHBhcmFtcwogICkKfQpgYGAKCmBgYHtyfQojIEV2YWx1YXIgdG9kYXMgbGFzIGNvbWJpbmFjaW9uZXMKcmVzdWx0YWRvc19ncmlkXzM2NzgwNTUgPC0gcGFyYW1fZ3JpZF9yZWR1Y2lkYQpyZXN1bHRhZG9zX2dyaWRfMzY3ODA1NSRucm91bmRzIDwtIE5BCnJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1JHJtc2UgPC0gTkEKCmNhdCgiSW5pY2lhbmRvIGLDunNxdWVkYSBkZSBoaXBlcnBhcsOhbWV0cm9zIHBhcmEgcHJvZHVjdG8gMzY3ODA1NS4uLlxuIikKZm9yIChpIGluIDE6bnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSkgewogIGNhdChzcHJpbnRmKCJFdmFsdWFuZG8gY29tYmluYWNpw7NuICVkIGRlICVkXG4iLCBpLCBucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSkKICAKICByZXN1bHRhZG8gPC0gZXZhbHVhdGVfcGFyYW1zXzM2NzgwNTUocGFyYW1fZ3JpZF9yZWR1Y2lkYVtpLCBdKQogIAogIHJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1JG5yb3VuZHNbaV0gPC0gcmVzdWx0YWRvJG5yb3VuZHMKICByZXN1bHRhZG9zX2dyaWRfMzY3ODA1NSRybXNlW2ldIDwtIHJlc3VsdGFkbyRybXNlCn0KCiMgT3JkZW5hciBwb3IgbWVqb3IgUk1TRQpyZXN1bHRhZG9zX2dyaWRfMzY3ODA1NSA8LSByZXN1bHRhZG9zX2dyaWRfMzY3ODA1NVtvcmRlcihyZXN1bHRhZG9zX2dyaWRfMzY3ODA1NSRybXNlKSwgXQoKIyBNb3N0cmFyIGxhcyA1IG1lam9yZXMgY29tYmluYWNpb25lcwpjYXQoIlxuVG9wIDUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zIChQcm9kdWN0byAzNjc4MDU1KTpcbiIpCnByaW50KGhlYWQocmVzdWx0YWRvc19ncmlkXzM2NzgwNTUsIDUpKQoKYGBgCgojIyMgRU5UUkVOQSBNT0RFTE8gQ09OIE1FSk9SRVMgSElQRVJQQVLDgU1FVFJPUyAKYGBge3J9CiMgRXh0cmFlciBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zIGRlbCBncmlkCm1lam9yZXNfcGFyYW1zXzM2NzgwNTUgPC0gbGlzdCgKICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgZXRhID0gcmVzdWx0YWRvc19ncmlkXzM2NzgwNTUkZXRhWzFdLAogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1JG1heF9kZXB0aFsxXSwKICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zX2dyaWRfMzY3ODA1NSRzdWJzYW1wbGVbMV0sCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1JGNvbHNhbXBsZV9ieXRyZWVbMV0sCiAgbWluX2NoaWxkX3dlaWdodCA9IHJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1JG1pbl9jaGlsZF93ZWlnaHRbMV0sCiAgZ2FtbWEgPSByZXN1bHRhZG9zX2dyaWRfMzY3ODA1NSRnYW1tYVsxXQopCgojIE7Dum1lcm8gw7NwdGltbyBkZSByb25kYXMKbWVqb3JfbnJvdW5kc18zNjc4MDU1IDwtIHJlc3VsdGFkb3NfZ3JpZF8zNjc4MDU1JG5yb3VuZHNbMV0KCmNhdCgiRW50cmVuYW5kbyBtb2RlbG8gY29uIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcy4uLlxuIikKcHJpbnQobWVqb3Jlc19wYXJhbXNfMzY3ODA1NSkKY2F0KCJOw7ptZXJvIMOzcHRpbW8gZGUgcm9uZGFzOiIsIG1lam9yX25yb3VuZHNfMzY3ODA1NSwgIlxuIikKCiMgRW50cmVuYXIgZWwgbW9kZWxvIGNvbiBsb3MgZGF0b3MgZGUgc3BsaXQgaW50ZXJubwptb2RlbG9feGdiXzM2NzgwNTVfc3BsaXQgPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zXzM2NzgwNTUsCiAgZGF0YSA9IGR0cmFpbl8zNjc4MDU1LAogIG5yb3VuZHMgPSBtZWpvcl9ucm91bmRzXzM2NzgwNTUsCiAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbl8zNjc4MDU1LCB0ZXN0ID0gZHRlc3RfMzY3ODA1NSksCiAgdmVyYm9zZSA9IDAKKQpgYGAKCiMjIyBNw4lUUklDQVMgSU5URVJOQVMKYGBge3J9CiMgTcOpdHJpY2FzIHNvYnJlIGRhdG9zX3RyYWluICg4MCUpCnByZWRfdHJhaW5fc3BsaXRfMzY3ODA1NSA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMzY3ODA1NV9zcGxpdCwgZHRyYWluXzM2NzgwNTUpCm1hcGVfdHJhaW5fc3BsaXRfMzY3ODA1NSA8LSBtZWFuKGFicygoeV90cmFpbl8zNjc4MDU1IC0gcHJlZF90cmFpbl9zcGxpdF8zNjc4MDU1KSAvIHBtYXgoeV90cmFpbl8zNjc4MDU1LCAwLjAxKSkpICogMTAwCnJtc2VfdHJhaW5fc3BsaXRfMzY3ODA1NSA8LSBzcXJ0KG1lYW4oKHlfdHJhaW5fMzY3ODA1NSAtIHByZWRfdHJhaW5fc3BsaXRfMzY3ODA1NSleMikpCgojIE3DqXRyaWNhcyBzb2JyZSBkYXRvc190ZXN0ICgyMCUpCnByZWRfdGVzdF9zcGxpdF8zNjc4MDU1IDwtIHByZWRpY3QobW9kZWxvX3hnYl8zNjc4MDU1X3NwbGl0LCBkdGVzdF8zNjc4MDU1KQptYXBlX3Rlc3Rfc3BsaXRfMzY3ODA1NSA8LSBtZWFuKGFicygoeV90ZXN0XzM2NzgwNTUgLSBwcmVkX3Rlc3Rfc3BsaXRfMzY3ODA1NSkgLyBwbWF4KHlfdGVzdF8zNjc4MDU1LCAwLjAxKSkpICogMTAwCnJtc2VfdGVzdF9zcGxpdF8zNjc4MDU1IDwtIHNxcnQobWVhbigoeV90ZXN0XzM2NzgwNTUgLSBwcmVkX3Rlc3Rfc3BsaXRfMzY3ODA1NSleMikpCgpjYXQoIlNwbGl0IEludGVybm8gLSBQcm9kdWN0byAzNjc4MDU1XG4iKQpjYXQoIlRyYWluIFNwbGl0IC0gTUFQRToiLCByb3VuZChtYXBlX3RyYWluX3NwbGl0XzM2NzgwNTUsIDIpLCAifCBSTVNFOiIsIHJvdW5kKHJtc2VfdHJhaW5fc3BsaXRfMzY3ODA1NSwgMiksICJcbiIpCmNhdCgiVGVzdCAgU3BsaXQgLSBNQVBFOiIsIHJvdW5kKG1hcGVfdGVzdF9zcGxpdF8zNjc4MDU1LCAyKSwgICJ8IFJNU0U6Iiwgcm91bmQocm1zZV90ZXN0X3NwbGl0XzM2NzgwNTUsIDIpLCAiXG4iKQpgYGAKCiMjIyBFTlRSRU5BTUlFTlRPIENPTiBEQVRPUyBDT01QTEVUT1MgCmBgYHtyfQojIFByZXBhcmFyIG1hdHJpY2VzIGNvbiB0b2RvcyBsb3MgZGF0b3MKWF9lbnRlcm9fMzY3ODA1NSA8LSBhcy5tYXRyaXgoZGF0b3NfMzY3ODA1NVssIGNvbG5hbWVzKGRhdG9zXzM2NzgwNTUpICE9ICJWZW50YSJdKQp5X2VudGVyb18zNjc4MDU1IDwtIGRhdG9zXzM2NzgwNTUkVmVudGEKZHRyYWluX2ZpbmFsXzM2NzgwNTUgPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfZW50ZXJvXzM2NzgwNTUsIGxhYmVsID0geV9lbnRlcm9fMzY3ODA1NSkKCiMgRW50cmVuYXIgZWwgbW9kZWxvIGZpbmFsCm1vZGVsb194Z2JfZmluYWxfMzY3ODA1NSA8LSB4Z2IudHJhaW4oCiAgcGFyYW1zID0gbWVqb3Jlc19wYXJhbXNfMzY3ODA1NSwKICBkYXRhID0gZHRyYWluX2ZpbmFsXzM2NzgwNTUsCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHNfMzY3ODA1NSwKICB2ZXJib3NlID0gMAopCgojIEV2YWx1YWNpw7NuIGVuIGRhdG9zXzM2NzgwNTUKcHJlZF9maW5hbF90cmFpbl8zNjc4MDU1IDwtIHByZWRpY3QobW9kZWxvX3hnYl9maW5hbF8zNjc4MDU1LCBYX2VudGVyb18zNjc4MDU1KQptYXBlX2ZpbmFsX3RyYWluXzM2NzgwNTUgPC0gbWVhbihhYnMoKHlfZW50ZXJvXzM2NzgwNTUgLSBwcmVkX2ZpbmFsX3RyYWluXzM2NzgwNTUpIC8gcG1heCh5X2VudGVyb18zNjc4MDU1LCAwLjAxKSkpICogMTAwCnJtc2VfZmluYWxfdHJhaW5fMzY3ODA1NSA8LSBzcXJ0KG1lYW4oKHlfZW50ZXJvXzM2NzgwNTUgLSBwcmVkX2ZpbmFsX3RyYWluXzM2NzgwNTUpXjIpKQoKIyBFdmFsdWFjacOzbiBlbiB0ZXN0XzM2NzgwNTUKWF90ZXN0X3JlYWxfMzY3ODA1NSA8LSBhcy5tYXRyaXgodGVzdF8zNjc4MDU1WywgY29sbmFtZXModGVzdF8zNjc4MDU1KSAhPSAiVmVudGEiXSkKeV90ZXN0X3JlYWxfMzY3ODA1NSA8LSB0ZXN0XzM2NzgwNTUkVmVudGEKcHJlZF9maW5hbF90ZXN0XzM2NzgwNTUgPC0gcHJlZGljdChtb2RlbG9feGdiX2ZpbmFsXzM2NzgwNTUsIFhfdGVzdF9yZWFsXzM2NzgwNTUpCm1hcGVfZmluYWxfdGVzdF8zNjc4MDU1IDwtIG1lYW4oYWJzKCh5X3Rlc3RfcmVhbF8zNjc4MDU1IC0gcHJlZF9maW5hbF90ZXN0XzM2NzgwNTUpIC8gcG1heCh5X3Rlc3RfcmVhbF8zNjc4MDU1LCAwLjAxKSkpICogMTAwCnJtc2VfZmluYWxfdGVzdF8zNjc4MDU1IDwtIHNxcnQobWVhbigoeV90ZXN0X3JlYWxfMzY3ODA1NSAtIHByZWRfZmluYWxfdGVzdF8zNjc4MDU1KV4yKSkKCmNhdCgiTW9kZWxvIEZpbmFsIC0gUHJvZHVjdG8gMzY3ODA1NVxuIikKY2F0KCJUcmFpbiAtIE1BUEU6Iiwgcm91bmQobWFwZV9maW5hbF90cmFpbl8zNjc4MDU1LCAyKSwgInwgUk1TRToiLCByb3VuZChybXNlX2ZpbmFsX3RyYWluXzM2NzgwNTUsIDIpLCAiXG4iKQpjYXQoIlRlc3QgIC0gTUFQRToiLCByb3VuZChtYXBlX2ZpbmFsX3Rlc3RfMzY3ODA1NSwgMiksICAifCBSTVNFOiIsIHJvdW5kKHJtc2VfZmluYWxfdGVzdF8zNjc4MDU1LCAyKSwgIlxuIikKCmBgYAoKIyMjIFRBQkxBIERFIE3DiVRSSUNBUwpgYGB7cn0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzNjc4MDU1IiwKICBNb2RlbG8gPSAiWEdCb29zdCAoVHJhaW4pIiwKICBNQVBFID0gbWFwZV9maW5hbF90cmFpbl8zNjc4MDU1LAogIFJNU0UgPSBybXNlX2ZpbmFsX3RyYWluXzM2NzgwNTUKKSkKCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzY3ODA1NSIsCiAgTW9kZWxvID0gIlhHQm9vc3QgKFRlc3QpIiwKICBNQVBFID0gbWFwZV9maW5hbF90ZXN0XzM2NzgwNTUsCiAgUk1TRSA9IHJtc2VfZmluYWxfdGVzdF8zNjc4MDU1CikpCmBgYAoKIyMgTcOJVFJJQ0FTIEFSTUEsIFJFRyBMSU5FQUwsIFJBTkRPTSBGT1JFU1QgWSBYR0JPT1NUCmBgYHtyfQptZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lCiAgYXJyYW5nZShQcm9kdWN0bykgJT4lCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSAiTcOpdHJpY2FzIEFSTUEsIFJlZ3Jlc2lvbiBMaW5lYWwsIFJhbmRvbSBGb3Jlc3QgeSBYR0Jvb3N0IHBvciBwcm9kdWN0byIpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCgojIDxzcGFuIHN0eWxlPSJjb2xvcjogYmxhY2s7Ij4gVklTVUFMSVpBQ0nDk04gREUgTcOJVFJJQ0FTCmBgYHtyfQojIENvbG9yZXMgcGVyc29uYWxpemFkb3MgcG9yIHRpcG8gZGUgbW9kZWxvIGJhc2UKY29sb3Jlc19tb2RlbG9zIDwtIGMoCiAgIkFSTUEiID0gIiMxZjc3YjQiLAogICJBUk1BIChUZXN0KSIgPSAiI2FlYzdlOCIsCiAgIlJlZ3Jlc2nDs24gTGluZWFsIChUcmFpbikiID0gIiNmZjdmMGUiLAogICJSZWdyZXNpw7NuIExpbmVhbCAoVGVzdCkiID0gIiNmZmJiNzgiLAogICJSYW5kb20gRm9yZXN0IChUcmFpbikiID0gIiMyY2EwMmMiLAogICJSYW5kb20gRm9yZXN0IChUZXN0KSIgPSAiIzk4ZGY4YSIsCiAgIlhHQm9vc3QgKFRyYWluKSIgPSAiI2Q2MjcyOCIsCiAgIlhHQm9vc3QgKFRlc3QpIiA9ICIjZmY5ODk2IgopCmBgYApgYGB7cn0KIyBPYnRlbmVyIHRvZG9zIGxvcyBwcm9kdWN0b3Mgw7puaWNvcwpwcm9kdWN0b3MgPC0gdW5pcXVlKG1ldHJpY2FzX2NvbXBhcmF0aXZhcyRQcm9kdWN0bykKCiMgQ3JlYXIgZ3LDoWZpY29zIHBvciBwcm9kdWN0bwpmb3IgKHByb2QgaW4gcHJvZHVjdG9zKSB7CiAgZGF0b3NfcHJvZCA8LSBtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSBwcm9kKQogIAogICMgR3LDoWZpY28gZGUgTUFQRQogIHByaW50KAogICAgZ2dwbG90KGRhdG9zX3Byb2QsIGFlcyh4ID0gTW9kZWxvLCB5ID0gTUFQRSwgZmlsbCA9IE1vZGVsbykpICsKICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICAgICAgbGFicygKICAgICAgICB0aXRsZSA9IHBhc3RlKCJNQVBFIHBvciBtb2RlbG8gLSBQcm9kdWN0byIsIHByb2QpLAogICAgICAgIHggPSAiIiwgeSA9ICJNQVBFICglKSIKICAgICAgKSArCiAgICAgIHRoZW1lX21pbmltYWwoKSArCiAgICAgIHRoZW1lKAogICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgICAgICkgKwogICAgICB5bGltKDAsIG1heChkYXRvc19wcm9kJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpCiAgKQogIAogICMgR3LDoWZpY28gZGUgUk1TRQogIHByaW50KAogICAgZ2dwbG90KGRhdG9zX3Byb2QsIGFlcyh4ID0gTW9kZWxvLCB5ID0gUk1TRSwgZmlsbCA9IE1vZGVsbykpICsKICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgICAgIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChSTVNFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICAgICAgbGFicygKICAgICAgICB0aXRsZSA9IHBhc3RlKCJSTVNFIHBvciBtb2RlbG8gLSBQcm9kdWN0byIsIHByb2QpLAogICAgICAgIHggPSAiIiwgeSA9ICJSTVNFIgogICAgICApICsKICAgICAgdGhlbWVfbWluaW1hbCgpICsKICAgICAgdGhlbWUoCiAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICAgICAgKSArCiAgICAgIHlsaW0oMCwgbWF4KGRhdG9zX3Byb2QkUk1TRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkKICApCn0KYGBgCgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsYWNrOyI+IEVTVElNQUNJw5NOIERFIFBSRUNJT1MKIyMgUFJFUEFSQUNJw5NOIERFIERBVE9TCmBgYHtyfQpwcmVwYXJlX3ByaWNlX2RhdGEgPC0gZnVuY3Rpb24oZGYsIHByb2R1Y3RfaWQpIHsKICBwcm9kdWN0X2RhdGEgPC0gZGYgJT4lCiAgICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKSAlPiUKICAgIGFycmFuZ2UoVHJ4X0ZlY2hhKSAlPiUKICAgIHNlbGVjdCgKICAgICAgU2VtYW5hLAogICAgICBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sCiAgICAgIFZlbnRhX1NlbWFuYV9BbnRlcmlvciwKICAgICAgQ2FudF9TZW1hbmFfQW50ZXJpb3IsCiAgICAgIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IsCiAgICAgIENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvcgogICAgKQoKICByZXR1cm4ocHJvZHVjdF9kYXRhKQp9CgojIEFwbGljYXIgbGEgZnVuY2nDs24gYSB0b2RvcyBsb3MgcHJvZHVjdG9zCmlkcyA8LSB1bmlxdWUoZGF0b3MkSURfSW52ZW50YXJpbykKCnByb2R1Y3Rvc19wcmVwYXJhZG9zIDwtIG1hcF9kZihpZHMsIGZ1bmN0aW9uKGlkKSB7CiAgcHJlcGFyZV9wcmljZV9kYXRhKGRhdG9zLCBpZCkKfSkKCmhlYWQocHJvZHVjdG9zX3ByZXBhcmFkb3MpCmBgYAoKIyMgUFJFQ0lPUyBTSU1VTEFET1MKYGBge3J9CnNpbXVsYXJfZXNjZW5hcmlvc19wcmVjaW8gPC0gZnVuY3Rpb24oZGYsIHByb2R1Y3RfaWQsIG4gPSAyMCwgc2VtYW5hc19hX3NpbXVsYXIgPSA4KSB7CiAgIyBGaWx0cmFyIGRhdG9zIGRlbCBwcm9kdWN0bwogIGRhdGFfcHJvZHVjdG8gPC0gZGYgJT4lIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2R1Y3RfaWQpCgogICMgQ2FsY3VsYXIgcmFuZ28gaGlzdMOzcmljbyBkZSBwcmVjaW9zIChwZXJjZW50aWwgNSUgYSA5NSUpCiAgcHJlY2lvX21pbiA8LSBxdWFudGlsZShkYXRhX3Byb2R1Y3RvJFByZWNpb19MaXN0YV9Vbml0YXJpbywgMC4wNSwgbmEucm0gPSBUUlVFKQogIHByZWNpb19tYXggPC0gcXVhbnRpbGUoZGF0YV9wcm9kdWN0byRQcmVjaW9fTGlzdGFfVW5pdGFyaW8sIDAuOTUsIG5hLnJtID0gVFJVRSkKCiAgIyBDcmVhciB2ZWN0b3IgZGUgcHJlY2lvcyBzaW11bGFkb3MKICBwcmVjaW9zX3NpbXVsYWRvcyA8LSBzZXEocHJlY2lvX21pbiwgcHJlY2lvX21heCwgbGVuZ3RoLm91dCA9IG4pCgogICMgT2J0ZW5lciDDumx0aW1hIHNlbWFuYSB5IGZlY2hhCiAgdWx0aW1hX2ZpbGEgPC0gZGF0YV9wcm9kdWN0byAlPiUgYXJyYW5nZShkZXNjKFNlbWFuYSkpICU+JSAuWzEsIF0KICB1bHRpbWFfc2VtYW5hIDwtIHVsdGltYV9maWxhJFNlbWFuYQogIHVsdGltYV9mZWNoYSA8LSB1bHRpbWFfZmlsYSRUcnhfRmVjaGEKCiAgIyBFeHBhbmRpciBjb21iaW5hY2lvbmVzOiBzZW1hbmFzIGZ1dHVyYXMgw5cgcHJlY2lvcyBzaW11bGFkb3MKICBzaW11bGFjaW9uIDwtIGV4cGFuZC5ncmlkKAogICAgU2VtYW5hID0gdWx0aW1hX3NlbWFuYSArIDE6c2VtYW5hc19hX3NpbXVsYXIsCiAgICBQcmVjaW9fTGlzdGFfVW5pdGFyaW8gPSBwcmVjaW9zX3NpbXVsYWRvcwogICkgJT4lCiAgICBtdXRhdGUoCiAgICAgIElEX0ludmVudGFyaW8gPSBwcm9kdWN0X2lkLAogICAgICBWZW50YV9TZW1hbmFfQW50ZXJpb3IgPSB1bHRpbWFfZmlsYSRWZW50YV9TZW1hbmFfQW50ZXJpb3IsCiAgICAgIENhbnRfU2VtYW5hX0FudGVyaW9yID0gdWx0aW1hX2ZpbGEkQ2FudF9TZW1hbmFfQW50ZXJpb3IsCiAgICAgIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IgPSB1bHRpbWFfZmlsYSREZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLAogICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IgPSB1bHRpbWFfZmlsYSRDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IsCiAgICAgIFRyeF9GZWNoYSA9IHVsdGltYV9mZWNoYSArIDcgKiAoU2VtYW5hIC0gdWx0aW1hX3NlbWFuYSkKICAgICkKCiAgcmV0dXJuKHNpbXVsYWNpb24pCn0KCmBgYAoKIyMjIEFQTElDQVIgU0lNVUxBQ0lPTkVTIEEgVE9ET1MgTE9TIFBST0RVQ1RPUwpgYGB7cn0KcHJvZHVjdG9zX2lkcyA8LSB1bmlxdWUoZGF0b3MkSURfSW52ZW50YXJpbykgICMgbyB1c2EgdG9wX2lkcyBzaSB5YSBsb3MgZGVmaW5pc3RlCgpzaW11bGFjaW9uZXNfcHJlY2lvX2xpc3RhIDwtIHNldE5hbWVzKAogIGxhcHBseShwcm9kdWN0b3NfaWRzLCBmdW5jdGlvbihpZCkgc2ltdWxhcl9lc2NlbmFyaW9zX3ByZWNpbyhkYXRvcywgaWQpKSwKICBwcm9kdWN0b3NfaWRzCikKYGBgCgojIyMgRVNUSU1BUiBQUkVDSU8gw5NQVElNTyBQT1IgUFJPRFVDVE8KYGBge3J9CiMgVmFyaWFibGVzIGRlbCBtb2RlbG8KY29sdW1uYXNfbW9kZWxvIDwtIGMoCiAgIlByZWNpb19MaXN0YV9Vbml0YXJpbyIsIAogICJWZW50YV9TZW1hbmFfQW50ZXJpb3IiLCAKICAiQ2FudF9TZW1hbmFfQW50ZXJpb3IiLCAKICAiRGVzY3VlbnRvX1NlbWFuYV9BbnRlcmlvciIsIAogICJDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IiLCAKICAiU2VtYW5hIgopCgojIExpc3RhIGRlIG1vZGVsb3MgZW50cmVuYWRvcwptb2RlbG9zX3hnYl9maW5hbCA8LSBsaXN0KAogICIxNTUwMDEiID0gbW9kZWxvX3hnYl9maW5hbF8xNTUwMDEsCiAgIjM5Mjk3ODgiID0gbW9kZWxvX3hnYl9maW5hbF8zOTI5Nzg4LAogICIzOTA0MTUyIiA9IG1vZGVsb194Z2JfZmluYWxfMzkwNDE1MiwKICAiMTU1MDAyIiA9IG1vZGVsb194Z2JfZmluYWxfMTU1MDAyLAogICIzNjc4MDU1IiA9IG1vZGVsb194Z2JfZmluYWxfMzY3ODA1NQopCgojIEluaWNpYWxpemFyIGxpc3RhcwpyZXN1bHRhZG9zX3NpbXVsYWNpb25fY29tcGxldGEgPC0gbGlzdCgpCnRhYmxhX3Jlc3VtZW5fb3B0aW1vcyA8LSBsaXN0KCkKCiMgRnVuY2nDs24gcGFyYSBlc3RpbWFyIHByZWNpbyDDs3B0aW1vIHBhcmEgU09MTyBsYSBzaWd1aWVudGUgc2VtYW5hCmVuY29udHJhcl9wcmVjaW9fb3B0aW1vIDwtIGZ1bmN0aW9uKHByb2R1Y3RfaWQpIHsKICAjIEZpbHRyYXIgZGF0b3MgaGlzdMOzcmljb3MgZGVsIHByb2R1Y3RvCiAgZGF0YV9wcm9kdWN0byA8LSBkYXRvcyAlPiUgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkKCiAgIyBPYnRlbmVyIMO6bHRpbWEgc2VtYW5hIHkgw7psdGltYSBmZWNoYQogIHVsdGltYV9maWxhIDwtIGRhdGFfcHJvZHVjdG8gJT4lIGFycmFuZ2UoZGVzYyhTZW1hbmEpKSAlPiUgc2xpY2VfaGVhZChuID0gMSkKICB1bHRpbWFfc2VtYW5hIDwtIHVsdGltYV9maWxhJFNlbWFuYQogIHVsdGltYV9mZWNoYSA8LSB1bHRpbWFfZmlsYSRUcnhfRmVjaGEKCiAgIyBTaW11bGFyIHJhbmdvIGRlIHByZWNpb3MgcG9zaWJsZXMgKHBlcmNlbnRpbCA1JSAtIDk1JSkKICBwcmVjaW9fbWluIDwtIHF1YW50aWxlKGRhdGFfcHJvZHVjdG8kUHJlY2lvX0xpc3RhX1VuaXRhcmlvLCAwLjA1LCBuYS5ybSA9IFRSVUUpCiAgcHJlY2lvX21heCA8LSBxdWFudGlsZShkYXRhX3Byb2R1Y3RvJFByZWNpb19MaXN0YV9Vbml0YXJpbywgMC45NSwgbmEucm0gPSBUUlVFKQogIHByZWNpb3Nfc2ltdWxhZG9zIDwtIHNlcShwcmVjaW9fbWluLCBwcmVjaW9fbWF4LCBsZW5ndGgub3V0ID0gMjApCgogICMgQ3JlYXIgdGFibGEgc2ltdWxhZGEgKDEgc2VtYW5hICsgMjAgcHJlY2lvcykKICBzaW11bGFjaW9uIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IHByb2R1Y3RfaWQsCiAgICBTZW1hbmEgPSB1bHRpbWFfc2VtYW5hICsgMSwKICAgIFByZWNpb19MaXN0YV9Vbml0YXJpbyA9IHByZWNpb3Nfc2ltdWxhZG9zLAogICAgVmVudGFfU2VtYW5hX0FudGVyaW9yID0gdWx0aW1hX2ZpbGEkVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgQ2FudF9TZW1hbmFfQW50ZXJpb3IgPSB1bHRpbWFfZmlsYSRDYW50X1NlbWFuYV9BbnRlcmlvciwKICAgIERlc2N1ZW50b19TZW1hbmFfQW50ZXJpb3IgPSB1bHRpbWFfZmlsYSREZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLAogICAgQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yID0gdWx0aW1hX2ZpbGEkQ29zdG9fVW5pdGFyaW9fU2VtYW5hX0FudGVyaW9yLAogICAgVHJ4X0ZlY2hhID0gdWx0aW1hX2ZlY2hhICsgNwogICkKCiAgIyBQcmVkZWNpciB2ZW50YXMgY29uIGVsIG1vZGVsbwogIFhfc2ltIDwtIHNpbXVsYWNpb24gJT4lIHNlbGVjdChhbGxfb2YoY29sdW1uYXNfbW9kZWxvKSkgJT4lIGFzLm1hdHJpeCgpCiAgbW9kZWxvIDwtIG1vZGVsb3NfeGdiX2ZpbmFsW1thcy5jaGFyYWN0ZXIocHJvZHVjdF9pZCldXQogIHNpbXVsYWNpb24kVmVudGFfUHJlZGljaGEgPC0gcHJlZGljdChtb2RlbG8sIG5ld2RhdGEgPSBYX3NpbSkKCiAgIyBDYWxjdWxhciBtYXJnZW4KICBzaW11bGFjaW9uJE1hcmdlbl9Fc3BlcmFkbyA8LSBzaW11bGFjaW9uJFZlbnRhX1ByZWRpY2hhIC0gCiAgICBzaW11bGFjaW9uJENvc3RvX1VuaXRhcmlvX1NlbWFuYV9BbnRlcmlvciAqIHNpbXVsYWNpb24kQ2FudF9TZW1hbmFfQW50ZXJpb3IKCiAgIyBHdWFyZGFyIHNpbXVsYWNpw7NuIGNvbXBsZXRhCiAgcmVzdWx0YWRvc19zaW11bGFjaW9uX2NvbXBsZXRhW1thcy5jaGFyYWN0ZXIocHJvZHVjdF9pZCldXSA8PC0gc2ltdWxhY2lvbgoKICAjIE9idGVuZXIgcHJlY2lvIGNvbiBtYXlvciB2ZW50YSBlc3BlcmFkYQogIG1lam9yX2ZpbGEgPC0gc2ltdWxhY2lvbiAlPiUgZmlsdGVyKFZlbnRhX1ByZWRpY2hhID09IG1heChWZW50YV9QcmVkaWNoYSwgbmEucm0gPSBUUlVFKSkgJT4lIHNsaWNlX2hlYWQobiA9IDEpCiAgdGFibGFfcmVzdW1lbl9vcHRpbW9zW1thcy5jaGFyYWN0ZXIocHJvZHVjdF9pZCldXSA8PC0gbWVqb3JfZmlsYSAlPiUKICAgIHNlbGVjdChQcm9kdWN0bywgVHJ4X0ZlY2hhLCBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sIFZlbnRhX1ByZWRpY2hhLCBNYXJnZW5fRXNwZXJhZG8pICU+JQogICAgcmVuYW1lKAogICAgICBGZWNoYV9TaW11bGFkYSA9IFRyeF9GZWNoYSwKICAgICAgUHJlY2lvX09wdGltbyA9IFByZWNpb19MaXN0YV9Vbml0YXJpbywKICAgICAgVmVudGFfRXNwZXJhZGEgPSBWZW50YV9QcmVkaWNoYQogICAgKQoKICAjIE1vc3RyYXIgZW4gY29uc29sYQogIGNhdCgiUHJvZHVjdG86IiwgcHJvZHVjdF9pZCwgIlxuIikKICBjYXQoIlByZWNpbyDDs3B0aW1vOiIsIHJvdW5kKG1lam9yX2ZpbGEkUHJlY2lvX0xpc3RhX1VuaXRhcmlvLCAyKSwgIlxuIikKICBjYXQoIlZlbnRhIGVzcGVyYWRhOiIsIHJvdW5kKG1lam9yX2ZpbGEkVmVudGFfUHJlZGljaGEsIDIpLCAiXG4iKQogIGNhdCgiTWFyZ2VuIGVzcGVyYWRvOiIsIHJvdW5kKG1lam9yX2ZpbGEkTWFyZ2VuX0VzcGVyYWRvLCAyKSwgIlxuIikKICBjYXQoIkZlY2hhIHNpbXVsYWRhOiIsIGFzLmNoYXJhY3RlcihtZWpvcl9maWxhJFRyeF9GZWNoYSksICJcblxuIikKfQoKIyBFamVjdXRhciBzaW11bGFjacOzbiBwYXJhIHRvZG9zIGxvcyBwcm9kdWN0b3MKZm9yIChpZCBpbiBuYW1lcyhtb2RlbG9zX3hnYl9maW5hbCkpIHsKICBlbmNvbnRyYXJfcHJlY2lvX29wdGltbyhpZCkKfQoKIyBUYWJsYXMgZmluYWxlcwp0YWJsYV9zaW11bGFjaW9uX2NvbXBsZXRhIDwtIGJpbmRfcm93cyhyZXN1bHRhZG9zX3NpbXVsYWNpb25fY29tcGxldGEpCnRhYmxhX3ByZWNpb3Nfb3B0aW1vcyA8LSBiaW5kX3Jvd3ModGFibGFfcmVzdW1lbl9vcHRpbW9zKQoKIyBNb3N0cmFyIHRhYmxhIHJlc3VtZW4KcHJpbnQodGFibGFfcHJlY2lvc19vcHRpbW9zKQpgYGAKIyMjIEVYUE9SVEFSIEEgQ1NWCmBgYHtyfQojIEV4cG9ydGFyIGEgQ1NWIHNpIGRlc2Vhcwp3cml0ZS5jc3YodGFibGFfc2ltdWxhY2lvbl9jb21wbGV0YSwgInNpbXVsYWNpb25lc190b2Rhcy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkKd3JpdGUuY3N2KHRhYmxhX3ByZWNpb3Nfb3B0aW1vcywgInByZWNpb3Nfb3B0aW1vc19yZXN1bWVuLmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKCiMjIFNJTVVMQUNJw5NOIERFIERJRkVSRU5URVMgREVTQ1VFTlRPUwojIyMgRU5DT05UUkFSIERFU0NVRU5UT1MgUkVDVVJSRU5URVMKYGBge3J9CnJhbmdvc19kZXNjdWVudG8gPC0gZGF0b3MgJT4lCiAgZ3JvdXBfYnkoSURfSW52ZW50YXJpbykgJT4lCiAgc3VtbWFyaXNlKAogICAgRGVzY19taW4gPSBxdWFudGlsZShEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLCAwLjA1LCBuYS5ybSA9IFRSVUUpLAogICAgRGVzY19tYXggPSBxdWFudGlsZShEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLCAwLjk1LCBuYS5ybSA9IFRSVUUpCiAgKQpgYGAKCiMjIyBTSU1VTEFSIERJRkVSRU5URVMgREVTQ1VFTlRPUyBBIExPUyBQUkVDSU9TIMOTUFRJTU9TCmBgYHtyfQojIENyZWFyIGxpc3RhIGNvbiBzaW11bGFjaW9uZXMgcG9yIHByb2R1Y3RvCnNpbXVsYXJfZGVzY3VlbnRvcyA8LSBmdW5jdGlvbihwcm9kdWN0X2lkLCBtb2RlbG8sIHByZWNpb19vcHRpbW8sIGRhdG9zX2Jhc2UsIG5fc2ltID0gMTApIHsKICBkYXRvc19wcm9kdWN0byA8LSBkYXRvc19iYXNlICU+JSBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBhcy5udW1lcmljKHByb2R1Y3RfaWQpKQogIAogICMgw5psdGltYSBmaWxhIG9ic2VydmFkYSAoY29ycmVnaWRvIHNpbiBzbGljZSkKICB1bHRpbWFfZmlsYSA8LSBkYXRvc19wcm9kdWN0byAlPiUKICAgIGFycmFuZ2UoZGVzYyhTZW1hbmEpKSAlPiUKICAgIGhlYWQoMSkKICAKICAjIFJhbmdvIGRlIGRlc2N1ZW50b3MgaGlzdMOzcmljb3MKICByIDwtIHJhbmdvc19kZXNjdWVudG8gJT4lIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IGFzLm51bWVyaWMocHJvZHVjdF9pZCkpCiAgZGVzY3VlbnRvc19zaW11bGFkb3MgPC0gc2VxKHIkRGVzY19taW4sIHIkRGVzY19tYXgsIGxlbmd0aC5vdXQgPSBuX3NpbSkKICAKICAjIERhdG9zIHNpbXVsYWRvcwogIHNpbXVsYWNpb24gPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gcHJvZHVjdF9pZCwKICAgIFByZWNpb19MaXN0YV9Vbml0YXJpbyA9IHByZWNpb19vcHRpbW8sCiAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yID0gZGVzY3VlbnRvc19zaW11bGFkb3MKICApICU+JQogICAgbXV0YXRlKAogICAgICBTZW1hbmEgPSB1bHRpbWFfZmlsYSRTZW1hbmEgKyAxLAogICAgICBUcnhfRmVjaGEgPSBtYXgoZGF0b3NfcHJvZHVjdG8kVHJ4X0ZlY2hhLCBuYS5ybSA9IFRSVUUpICsgNywKICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvID0gUHJlY2lvX0xpc3RhX1VuaXRhcmlvICogKDEgLSBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yKSwKICAgICAgVmVudGFfU2VtYW5hX0FudGVyaW9yID0gdWx0aW1hX2ZpbGEkVmVudGFfU2VtYW5hX0FudGVyaW9yLAogICAgICBDYW50X1NlbWFuYV9BbnRlcmlvciA9IHVsdGltYV9maWxhJENhbnRfU2VtYW5hX0FudGVyaW9yLAogICAgICBDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IgPSB1bHRpbWFfZmlsYSRDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IKICAgICkKICAKICAjIFZhcmlhYmxlcyB1c2FkYXMgZW4gZWwgbW9kZWxvCiAgY29sdW1uYXNfbW9kZWxvIDwtIGMoCiAgICAiUHJlY2lvX0xpc3RhX1VuaXRhcmlvIiwKICAgICJWZW50YV9TZW1hbmFfQW50ZXJpb3IiLAogICAgIkNhbnRfU2VtYW5hX0FudGVyaW9yIiwKICAgICJEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yIiwKICAgICJDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IiLAogICAgIlNlbWFuYSIKICApCiAgCiAgIyBQcmVkaWNjacOzbiB5IG1hcmdlbgogIFhfc2ltIDwtIHNpbXVsYWNpb24gJT4lIHNlbGVjdChhbGxfb2YoY29sdW1uYXNfbW9kZWxvKSkgJT4lIGFzLm1hdHJpeCgpCiAgc2ltdWxhY2lvbiRWZW50YV9QcmVkaWNoYSA8LSBwcmVkaWN0KG1vZGVsbywgbmV3ZGF0YSA9IFhfc2ltKQogIHNpbXVsYWNpb24kTWFyZ2VuX0VzcGVyYWRvIDwtIHNpbXVsYWNpb24kVmVudGFfUHJlZGljaGEgLQogICAgc2ltdWxhY2lvbiRDb3N0b19Vbml0YXJpb19TZW1hbmFfQW50ZXJpb3IgKiBzaW11bGFjaW9uJENhbnRfU2VtYW5hX0FudGVyaW9yCiAgCiAgcmV0dXJuKHNpbXVsYWNpb24pCn0KCmBgYAoKIyMjIFBSRURFQ0lSIFZFTlRBIFBBUkEgQ0FEQSBERVNDVUVOVE8gQ09OIE1PREVMTyBERSBYR0JPT1NUCmBgYHtyfQojIEdlbmVyYXIgc2ltdWxhY2lvbmVzIGRlIGRlc2N1ZW50b3MgcGFyYSB0b2RvcyBsb3MgcHJvZHVjdG9zIGNvbiBtb2RlbG8gWEdCb29zdApzaW11bGFjaW9uZXNfZGVzY3VlbnRvcyA8LSBiaW5kX3Jvd3MoCiAgbGFwcGx5KG5hbWVzKG1vZGVsb3NfeGdiX2ZpbmFsKSwgZnVuY3Rpb24oaWQpIHsKICAgIHByZWNpb19vcHRpbW8gPC0gdGFibGFfcHJlY2lvc19vcHRpbW9zICU+JQogICAgICBmaWx0ZXIoUHJvZHVjdG8gPT0gaWQpICU+JQogICAgICBwdWxsKFByZWNpb19PcHRpbW8pCgogICAgc2ltdWxhcl9kZXNjdWVudG9zKAogICAgICBwcm9kdWN0X2lkID0gaWQsCiAgICAgIG1vZGVsbyA9IG1vZGVsb3NfeGdiX2ZpbmFsW1tpZF1dLAogICAgICBwcmVjaW9fb3B0aW1vID0gcHJlY2lvX29wdGltbywKICAgICAgZGF0b3NfYmFzZSA9IGRhdG9zCiAgICApCiAgfSkKKQoKIyBDcmVhciB0YWJsYSBmaW5hbCBwYXJhIGFuw6FsaXNpcyBvIFBvd2VyIEJJCnRhYmxhX2Rlc2N1ZW50b3NfZmluYWwgPC0gc2ltdWxhY2lvbmVzX2Rlc2N1ZW50b3MgJT4lCiAgc2VsZWN0KAogICAgUHJvZHVjdG8sCiAgICBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sCiAgICBEZXNjdWVudG9fU2VtYW5hX0FudGVyaW9yLAogICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLAogICAgVmVudGFfUHJlZGljaGEsCiAgICBNYXJnZW5fRXNwZXJhZG8sCiAgICBTZW1hbmFfU2ltdWxhZGEgPSBTZW1hbmEsCiAgICBUcnhfRmVjaGEKICApCgojIE1vc3RyYXIKcHJpbnQodGFibGFfZGVzY3VlbnRvc19maW5hbCkKYGBgCgojIyMgRVhQT1JUQVIgQSBDU1YKYGBge3J9CiMgRXhwb3J0YXIgbGEgdGFibGEgZmluYWwgZGUgZGVzY3VlbnRvcyBzaW11bGFkb3MKd3JpdGUuY3N2KHRhYmxhX2Rlc2N1ZW50b3NfZmluYWwsICJ0YWJsYV9kZXNjdWVudG9zX3NpbXVsYWRvcy5jc3YiLCByb3cubmFtZXMgPSBGQUxTRSkKCmNhdCgiQXJjaGl2byAndGFibGFfZGVzY3VlbnRvc19zaW11bGFkb3MuY3N2JyBndWFyZGFkbyBleGl0b3NhbWVudGUgZW4gZWwgZGlyZWN0b3JpbyBkZSB0cmFiYWpvLlxuIikKCmBgYAoKCg==