PREDICCIONES DE
VENTAS
PRODUCTO 155001
colnames(datos_filtrados)
## [1] "Trx_Num" "Trx_Fecha" "Origen"
## [4] "Sucursal" "Num_Cliente" "ID_Inventario"
## [7] "Cant" "Linea" "Especifico"
## [10] "Canal_Venta" "Tipo_Precio" "Tipo_Modificador"
## [13] "Venta" "Costo_Venta" "Costo_Devolucion"
## [16] "Ajus_Sistema" "Ajuste_Manual" "Precio_Lista_Unitario"
## [19] "Precio_Final_Unitario" "Semana" "Mes"
## [22] "Ciudad" "Zona" "Devoluciones"
## [25] "Diferencia_Precio" "Descuento_Porcentaje"
# Crear serie de tiempo con base en Ventas en dinero (para producto 155001)
ventas_mensuales <- datos_filtrados %>%
filter(ID_Inventario == 155001) %>%
mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>% # 👈 se fuerza a Date
group_by(Fecha) %>%
summarise(Venta = sum(Venta, na.rm = TRUE)) %>%
arrange(Fecha)
# Crear serie de tiempo
serie_ts <- ts(ventas_mensuales$Venta, frequency = 12, start = c(year(min(ventas_mensuales$Fecha)), month(min(ventas_mensuales$Fecha))))
# Gráfico estacional por mes con Ventas
ggseasonplot(serie_ts, year.labels = TRUE) +
labs(title = "Gráfico estacional por mes", y = "Ventas (dinero)", x = "Mes")

acf(serie_ts)

pacf(serie_ts)

modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
assign(paste0("modelo_arma_", 155001), modelo_arma, envir = .GlobalEnv)
modelo_sarima <- auto.arima(serie_ts, seasonal = TRUE, stepwise = FALSE, approximation = FALSE)
cat("AIC ARMA:", modelo_arma$aic, "\n")
## AIC ARMA: 664.0425
cat("AIC SARIMA:", modelo_sarima$aic, "\n")
## AIC SARIMA: 664.0425
if (modelo_arma$aic < modelo_sarima$aic) {
mejor_modelo <- modelo_arma
modelo_usado <- "ARMA"
} else if (modelo_sarima$aic < modelo_arma$aic) {
mejor_modelo <- modelo_sarima
modelo_usado <- "SARIMA"
} else {
mejor_modelo <- modelo_arma
modelo_usado <- "ARMA (por empate)"
}
forecast_modelo <- forecast(mejor_modelo, h = 3)
autoplot(forecast_modelo) +
labs(title = paste("Pronóstico mensual de ventas -", modelo_usado, "(Producto 155001)"),
x = "Mes", y = "Ventas ($)") +
theme_minimal()

ventas_historicas <- ventas_mensuales %>%
select(Fecha, Venta) %>%
mutate(
Tipo = "Histórico",
IC_Inferior = NA,
IC_Superior = NA
)
predicciones <- data.frame(
Fecha = seq.Date(from = max(ventas_historicas$Fecha) %m+% months(1),
by = "month", length.out = 3),
Venta = as.numeric(forecast_modelo$mean),
IC_Inferior = as.numeric(forecast_modelo$lower[,2]),
IC_Superior = as.numeric(forecast_modelo$upper[,2]),
Tipo = "Pronóstico"
)
tabla_completa <- bind_rows(ventas_historicas, predicciones) %>%
arrange(Fecha)
tabla_completa %>%
kable(digits = 2, caption = "Ventas mensuales y pronóstico - Producto 155001") %>%
kable_styling(full_width = FALSE)
Ventas mensuales y pronóstico - Producto 155001
|
Fecha
|
Venta
|
Tipo
|
IC_Inferior
|
IC_Superior
|
|
2023-01-01
|
765135.7
|
Histórico
|
NA
|
NA
|
|
2023-02-01
|
709227.5
|
Histórico
|
NA
|
NA
|
|
2023-03-01
|
898405.1
|
Histórico
|
NA
|
NA
|
|
2023-04-01
|
843544.8
|
Histórico
|
NA
|
NA
|
|
2023-05-01
|
1103547.4
|
Histórico
|
NA
|
NA
|
|
2023-06-01
|
1229308.2
|
Histórico
|
NA
|
NA
|
|
2023-07-01
|
868233.5
|
Histórico
|
NA
|
NA
|
|
2023-08-01
|
1530224.9
|
Histórico
|
NA
|
NA
|
|
2023-09-01
|
1184488.5
|
Histórico
|
NA
|
NA
|
|
2023-10-01
|
1209917.8
|
Histórico
|
NA
|
NA
|
|
2023-11-01
|
784330.1
|
Histórico
|
NA
|
NA
|
|
2023-12-01
|
765147.0
|
Histórico
|
NA
|
NA
|
|
2024-01-01
|
763581.1
|
Histórico
|
NA
|
NA
|
|
2024-02-01
|
687222.5
|
Histórico
|
NA
|
NA
|
|
2024-03-01
|
740704.4
|
Histórico
|
NA
|
NA
|
|
2024-04-01
|
983570.8
|
Histórico
|
NA
|
NA
|
|
2024-05-01
|
1418016.7
|
Histórico
|
NA
|
NA
|
|
2024-06-01
|
1189873.6
|
Histórico
|
NA
|
NA
|
|
2024-07-01
|
1398347.2
|
Histórico
|
NA
|
NA
|
|
2024-08-01
|
1112443.4
|
Histórico
|
NA
|
NA
|
|
2024-09-01
|
1099078.6
|
Histórico
|
NA
|
NA
|
|
2024-10-01
|
1196470.8
|
Histórico
|
NA
|
NA
|
|
2024-11-01
|
922782.1
|
Histórico
|
NA
|
NA
|
|
2024-12-01
|
847560.6
|
Histórico
|
NA
|
NA
|
|
2025-01-01
|
932653.8
|
Pronóstico
|
489202.8
|
1376105
|
|
2025-02-01
|
969666.8
|
Pronóstico
|
486081.7
|
1453252
|
|
2025-03-01
|
985766.3
|
Pronóstico
|
494956.9
|
1476576
|
# Mostrar resumen del modelo y diagnóstico
cat("Modelo seleccionado:", modelo_usado, "\n")
## Modelo seleccionado: ARMA (por empate)
summary(mejor_modelo)
## Series: serie_ts
## ARIMA(1,0,0) with non-zero mean
##
## Coefficients:
## ar1 mean
## 0.4350 998160.07
## s.e. 0.1839 76353.41
##
## sigma^2 = 5.119e+10: log likelihood = -329.02
## AIC=664.04 AICc=665.24 BIC=667.58
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE ACF1
## Training set 5189.894 216622.3 174665.7 -3.834784 17.48586 1.078845 -0.06891323
checkresiduals(mejor_modelo)

##
## Ljung-Box test
##
## data: Residuals from ARIMA(1,0,0) with non-zero mean
## Q* = 4.5458, df = 4, p-value = 0.3371
##
## Model df: 1. Total lags used: 5
# Usar los valores ajustados del modelo para calcular las métricas
# Obtener los valores ajustados (fitted values)
fitted_values <- fitted(mejor_modelo)
# Calcular RMSE (Root Mean Squared Error)
rmse_modelo <- sqrt(mean((serie_ts - fitted_values)^2))
# Calcular MAPE (Mean Absolute Percentage Error) - con protección contra división por cero
mape_modelo <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
# Calcular MSE (Mean Squared Error)
mse_modelo <- mean((serie_ts - fitted_values)^2)
# Calcular R²
sse_modelo <- sum((serie_ts - fitted_values)^2)
sst_modelo <- sum((serie_ts - mean(serie_ts))^2)
r2_modelo <- 1 - (sse_modelo / sst_modelo)
# Mostrar las métricas
cat("R² del modelo", modelo_usado, "para 155001:", r2_modelo, "\n")
## R² del modelo ARMA (por empate) para 155001: 0.1923463
cat("AIC del modelo", modelo_usado, "para 155001:", mejor_modelo$aic, "\n")
## AIC del modelo ARMA (por empate) para 155001: 664.0425
cat("RMSE del modelo", modelo_usado, "para 155001:", rmse_modelo, "\n")
## RMSE del modelo ARMA (por empate) para 155001: 216622.3
cat("MAPE del modelo", modelo_usado, "para 155001:", mape_modelo, "\n")
## MAPE del modelo ARMA (por empate) para 155001: 17.48586
cat("MSE del modelo", modelo_usado, "para 155001:", mse_modelo, "\n")
## MSE del modelo ARMA (por empate) para 155001: 46925242372
# Guardar métricas ARMA/SARIMA para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155001", # Cambia este ID para cada producto
Modelo = "ARMA/SARIMA",
R2 = r2_modelo,
RMSE = rmse_modelo,
MAPE = mape_modelo,
MSE = mse_modelo,
AIC = mejor_modelo$aic
))
PRODUCTO 3929788
id_producto <- 3929788
ventas_mensuales <- datos_filtrados %>%
filter(ID_Inventario == id_producto) %>%
mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
group_by(Fecha) %>%
summarise(Venta = sum(Venta, na.rm = TRUE), .groups = "drop") %>%
arrange(Fecha)
serie_ts <- ts(ventas_mensuales$Venta,
frequency = 12,
start = c(year(min(ventas_mensuales$Fecha)), month(min(ventas_mensuales$Fecha))))
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
assign(paste0("modelo_arma_", 3929788), modelo_arma, envir = .GlobalEnv)
modelo_sarima <- auto.arima(serie_ts, seasonal = TRUE, stepwise = FALSE, approximation = FALSE)
cat("AIC ARMA:", modelo_arma$aic, "\n")
## AIC ARMA: 619.6674
cat("AIC SARIMA:", modelo_sarima$aic, "\n")
## AIC SARIMA: 619.6674
if (modelo_arma$aic < modelo_sarima$aic) {
mejor_modelo <- modelo_arma
modelo_usado <- "ARMA"
} else if (modelo_sarima$aic < modelo_arma$aic) {
mejor_modelo <- modelo_sarima
modelo_usado <- "SARIMA"
} else {
mejor_modelo <- modelo_arma
modelo_usado <- "ARMA (por empate)"
}
forecast_modelo <- forecast(mejor_modelo, h = 3)
autoplot(forecast_modelo) +
labs(title = paste("Pronóstico mensual de ventas -", modelo_usado, "(Producto 3929788)"),
x = "Mes", y = "Ventas ($)") +
theme_minimal()

predicciones <- data.frame(
Fecha = seq.Date(from = max(ventas_historicas$Fecha) %m+% months(1),
by = "month", length.out = 3),
Venta = as.numeric(forecast_modelo$mean),
IC_Inferior = as.numeric(forecast_modelo$lower[,2]),
IC_Superior = as.numeric(forecast_modelo$upper[,2]),
Tipo = "Pronóstico"
)
tabla_completa <- bind_rows(ventas_historicas, predicciones) %>%
arrange(Fecha)
# Mostrar tabla
tabla_completa %>%
kable(digits = 2, caption = "Ventas mensuales y pronóstico - Producto 3929788") %>%
kable_styling(full_width = FALSE)
Ventas mensuales y pronóstico - Producto 3929788
|
Fecha
|
Venta
|
Tipo
|
IC_Inferior
|
IC_Superior
|
|
2023-01-01
|
765135.7
|
Histórico
|
NA
|
NA
|
|
2023-02-01
|
709227.5
|
Histórico
|
NA
|
NA
|
|
2023-03-01
|
898405.1
|
Histórico
|
NA
|
NA
|
|
2023-04-01
|
843544.8
|
Histórico
|
NA
|
NA
|
|
2023-05-01
|
1103547.4
|
Histórico
|
NA
|
NA
|
|
2023-06-01
|
1229308.2
|
Histórico
|
NA
|
NA
|
|
2023-07-01
|
868233.5
|
Histórico
|
NA
|
NA
|
|
2023-08-01
|
1530224.9
|
Histórico
|
NA
|
NA
|
|
2023-09-01
|
1184488.5
|
Histórico
|
NA
|
NA
|
|
2023-10-01
|
1209917.8
|
Histórico
|
NA
|
NA
|
|
2023-11-01
|
784330.1
|
Histórico
|
NA
|
NA
|
|
2023-12-01
|
765147.0
|
Histórico
|
NA
|
NA
|
|
2024-01-01
|
763581.1
|
Histórico
|
NA
|
NA
|
|
2024-02-01
|
687222.5
|
Histórico
|
NA
|
NA
|
|
2024-03-01
|
740704.4
|
Histórico
|
NA
|
NA
|
|
2024-04-01
|
983570.8
|
Histórico
|
NA
|
NA
|
|
2024-05-01
|
1418016.7
|
Histórico
|
NA
|
NA
|
|
2024-06-01
|
1189873.6
|
Histórico
|
NA
|
NA
|
|
2024-07-01
|
1398347.2
|
Histórico
|
NA
|
NA
|
|
2024-08-01
|
1112443.4
|
Histórico
|
NA
|
NA
|
|
2024-09-01
|
1099078.6
|
Histórico
|
NA
|
NA
|
|
2024-10-01
|
1196470.8
|
Histórico
|
NA
|
NA
|
|
2024-11-01
|
922782.1
|
Histórico
|
NA
|
NA
|
|
2024-12-01
|
847560.6
|
Histórico
|
NA
|
NA
|
|
2025-01-01
|
935659.1
|
Pronóstico
|
633446.1
|
1237872
|
|
2025-02-01
|
932705.6
|
Pronóstico
|
627634.8
|
1237776
|
|
2025-03-01
|
944878.9
|
Pronóstico
|
618678.4
|
1271079
|
# Mostrar resumen del modelo y diagnóstico
cat("Modelo seleccionado:", modelo_usado, "\n")
## Modelo seleccionado: ARMA (por empate)
summary(mejor_modelo)
## Series: serie_ts
## ARIMA(2,1,0)
##
## Coefficients:
## ar1 ar2
## -0.8622 -0.4990
## s.e. 0.1764 0.1841
##
## sigma^2 = 2.378e+10: log likelihood = -306.83
## AIC=619.67 AICc=620.93 BIC=623.07
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 13227.22 144234.5 121979.8 -0.4466725 13.19875 0.5210489
## ACF1
## Training set 0.07347353
checkresiduals(mejor_modelo)

##
## Ljung-Box test
##
## data: Residuals from ARIMA(2,1,0)
## Q* = 3.5633, df = 3, p-value = 0.3126
##
## Model df: 2. Total lags used: 5
# Usar los valores ajustados del modelo para calcular las métricas
# Obtener los valores ajustados (fitted values)
fitted_values <- fitted(mejor_modelo)
# Calcular RMSE (Root Mean Squared Error)
rmse_modelo <- sqrt(mean((serie_ts - fitted_values)^2))
# Calcular MAPE (Mean Absolute Percentage Error) - con protección contra división por cero
mape_modelo <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
# Calcular MSE (Mean Squared Error)
mse_modelo <- mean((serie_ts - fitted_values)^2)
# Calcular R²
sse_modelo <- sum((serie_ts - fitted_values)^2)
sst_modelo <- sum((serie_ts - mean(serie_ts))^2)
r2_modelo <- 1 - (sse_modelo / sst_modelo)
# Mostrar las métricas
cat("R² del modelo", modelo_usado, "para 3929788:", r2_modelo, "\n")
## R² del modelo ARMA (por empate) para 3929788: 0.2466637
cat("AIC del modelo", modelo_usado, "para 3929788:", mejor_modelo$aic, "\n")
## AIC del modelo ARMA (por empate) para 3929788: 619.6674
cat("RMSE del modelo", modelo_usado, "para 3929788:", rmse_modelo, "\n")
## RMSE del modelo ARMA (por empate) para 3929788: 144234.5
cat("MAPE del modelo", modelo_usado, "para 3929788:", mape_modelo, "\n")
## MAPE del modelo ARMA (por empate) para 3929788: 13.19875
cat("MSE del modelo", modelo_usado, "para 3929788:", mse_modelo, "\n")
## MSE del modelo ARMA (por empate) para 3929788: 20803579371
# Guardar métricas ARMA/SARIMA para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3929788", # Cambia este ID para cada producto
Modelo = "ARMA/SARIMA",
R2 = r2_modelo,
RMSE = rmse_modelo,
MAPE = mape_modelo,
MSE = mse_modelo,
AIC = mejor_modelo$aic
))
PRODUCTO 3904152
# Establecer el ID del producto
id_producto <- 3904152
# Agrupar ventas mensuales
ventas_mensuales <- datos_filtrados %>%
filter(ID_Inventario == id_producto) %>%
mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
group_by(Fecha) %>%
summarise(Venta = sum(Venta, na.rm = TRUE), .groups = "drop") %>%
arrange(Fecha)
# Crear serie de tiempo mensual
serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
start = c(year(min(ventas_mensuales$Fecha)), month(min(ventas_mensuales$Fecha))))
# Ajustar modelos ARMA y SARIMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
assign(paste0("modelo_arma_", 3904152), modelo_arma, envir = .GlobalEnv)
modelo_sarima <- auto.arima(serie_ts, seasonal = TRUE, stepwise = FALSE, approximation = FALSE)
# Mostrar AIC de ambos modelos
cat("AIC ARMA:", modelo_arma$aic, "\n")
## AIC ARMA: 647.2636
cat("AIC SARIMA:", modelo_sarima$aic, "\n")
## AIC SARIMA: 647.2636
# Seleccionar el mejor modelo
if (modelo_arma$aic < modelo_sarima$aic) {
mejor_modelo <- modelo_arma
modelo_usado <- "ARMA"
} else if (modelo_sarima$aic < modelo_arma$aic) {
mejor_modelo <- modelo_sarima
modelo_usado <- "SARIMA"
} else {
mejor_modelo <- modelo_arma
modelo_usado <- "ARMA (por empate)"
}
# Generar pronóstico a 3 meses
forecast_modelo <- forecast(mejor_modelo, h = 3)
autoplot(forecast_modelo) +
labs(title = paste("Pronóstico mensual de ventas -", modelo_usado, "(Producto 3904152)"),
x = "Mes", y = "Ventas ($)") +
theme_minimal()

# Construir tabla combinada de histórico y predicción
ventas_historicas <- ventas_mensuales %>%
select(Fecha, Venta) %>%
mutate(Tipo = "Histórico", IC_Inferior = NA, IC_Superior = NA)
predicciones <- data.frame(
Fecha = seq.Date(from = max(ventas_historicas$Fecha) %m+% months(1),
by = "month", length.out = 3),
Venta = as.numeric(forecast_modelo$mean),
IC_Inferior = as.numeric(forecast_modelo$lower[,2]),
IC_Superior = as.numeric(forecast_modelo$upper[,2]),
Tipo = "Pronóstico"
)
tabla_completa <- bind_rows(ventas_historicas, predicciones) %>%
arrange(Fecha)
# Mostrar tabla
tabla_completa %>%
kable(digits = 2, caption = "Ventas mensuales y pronóstico - Producto 3904152") %>%
kable_styling(full_width = FALSE)
Ventas mensuales y pronóstico - Producto 3904152
|
Fecha
|
Venta
|
Tipo
|
IC_Inferior
|
IC_Superior
|
|
2023-01-01
|
799075.1
|
Histórico
|
NA
|
NA
|
|
2023-02-01
|
631883.7
|
Histórico
|
NA
|
NA
|
|
2023-03-01
|
850263.4
|
Histórico
|
NA
|
NA
|
|
2023-04-01
|
653134.2
|
Histórico
|
NA
|
NA
|
|
2023-05-01
|
835093.8
|
Histórico
|
NA
|
NA
|
|
2023-06-01
|
727430.8
|
Histórico
|
NA
|
NA
|
|
2023-07-01
|
489240.9
|
Histórico
|
NA
|
NA
|
|
2023-08-01
|
782323.2
|
Histórico
|
NA
|
NA
|
|
2023-09-01
|
851668.9
|
Histórico
|
NA
|
NA
|
|
2023-10-01
|
680519.4
|
Histórico
|
NA
|
NA
|
|
2023-11-01
|
1136816.4
|
Histórico
|
NA
|
NA
|
|
2023-12-01
|
915402.7
|
Histórico
|
NA
|
NA
|
|
2024-01-01
|
834988.8
|
Histórico
|
NA
|
NA
|
|
2024-02-01
|
767570.1
|
Histórico
|
NA
|
NA
|
|
2024-03-01
|
566158.9
|
Histórico
|
NA
|
NA
|
|
2024-04-01
|
784431.1
|
Histórico
|
NA
|
NA
|
|
2024-05-01
|
914803.8
|
Histórico
|
NA
|
NA
|
|
2024-06-01
|
881416.7
|
Histórico
|
NA
|
NA
|
|
2024-07-01
|
630550.9
|
Histórico
|
NA
|
NA
|
|
2024-08-01
|
1156776.8
|
Histórico
|
NA
|
NA
|
|
2024-09-01
|
796120.5
|
Histórico
|
NA
|
NA
|
|
2024-10-01
|
787514.2
|
Histórico
|
NA
|
NA
|
|
2024-11-01
|
839937.8
|
Histórico
|
NA
|
NA
|
|
2024-12-01
|
507597.4
|
Histórico
|
NA
|
NA
|
|
2025-01-01
|
784196.6
|
Pronóstico
|
464031.8
|
1104362
|
|
2025-02-01
|
784196.6
|
Pronóstico
|
464031.8
|
1104362
|
|
2025-03-01
|
784196.6
|
Pronóstico
|
464031.8
|
1104362
|
# Mostrar resumen del modelo y diagnóstico
cat("Modelo seleccionado:", modelo_usado, "\n")
## Modelo seleccionado: ARMA (por empate)
summary(mejor_modelo)
## Series: serie_ts
## ARIMA(0,0,0) with non-zero mean
##
## Coefficients:
## mean
## 784196.63
## s.e. 32641.98
##
## sigma^2 = 2.668e+10: log likelihood = -321.63
## AIC=647.26 AICc=647.83 BIC=649.62
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set -3.225675e-10 159913 117129.8 -4.420423 16.18578 0.6378203
## ACF1
## Training set -0.1164644
checkresiduals(mejor_modelo)

##
## Ljung-Box test
##
## data: Residuals from ARIMA(0,0,0) with non-zero mean
## Q* = 5.788, df = 5, p-value = 0.3274
##
## Model df: 0. Total lags used: 5
# Usar los valores ajustados del modelo para calcular las métricas
# Obtener los valores ajustados (fitted values)
fitted_values <- fitted(mejor_modelo)
# Calcular RMSE (Root Mean Squared Error)
rmse_modelo <- sqrt(mean((serie_ts - fitted_values)^2))
# Calcular MAPE (Mean Absolute Percentage Error) - con protección contra división por cero
mape_modelo <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
# Calcular MSE (Mean Squared Error)
mse_modelo <- mean((serie_ts - fitted_values)^2)
# Calcular R²
sse_modelo <- sum((serie_ts - fitted_values)^2)
sst_modelo <- sum((serie_ts - mean(serie_ts))^2)
r2_modelo <- 1 - (sse_modelo / sst_modelo)
# Mostrar las métricas
cat("R² del modelo", modelo_usado, "para 3904152:", r2_modelo, "\n")
## R² del modelo ARMA (por empate) para 3904152: 0
cat("AIC del modelo", modelo_usado, "para 3904152:", mejor_modelo$aic, "\n")
## AIC del modelo ARMA (por empate) para 3904152: 647.2636
cat("RMSE del modelo", modelo_usado, "para 3904152:", rmse_modelo, "\n")
## RMSE del modelo ARMA (por empate) para 3904152: 159913
cat("MAPE del modelo", modelo_usado, "para 3904152:", mape_modelo, "\n")
## MAPE del modelo ARMA (por empate) para 3904152: 16.18578
cat("MSE del modelo", modelo_usado, "para 3904152:", mse_modelo, "\n")
## MSE del modelo ARMA (por empate) para 3904152: 25572183540
# Guardar métricas ARMA/SARIMA para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3904152", # Cambia este ID para cada producto
Modelo = "ARMA/SARIMA",
R2 = r2_modelo,
RMSE = rmse_modelo,
MAPE = mape_modelo,
MSE = mse_modelo,
AIC = mejor_modelo$aic
))
PRODUCTO 155002
# Establecer el ID del producto
id_producto <- 155002
# Agrupar ventas mensuales
ventas_mensuales <- datos_filtrados %>%
filter(ID_Inventario == id_producto) %>%
mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
group_by(Fecha) %>%
summarise(Venta = sum(Venta, na.rm = TRUE), .groups = "drop") %>%
arrange(Fecha)
# Crear serie de tiempo mensual
serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
start = c(year(min(ventas_mensuales$Fecha)), month(min(ventas_mensuales$Fecha))))
# Ajustar modelos ARMA y SARIMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
assign(paste0("modelo_arma_", 155002), modelo_arma, envir = .GlobalEnv)
modelo_sarima <- auto.arima(serie_ts, seasonal = TRUE, stepwise = FALSE, approximation = FALSE)
# Mostrar AIC de ambos modelos
cat("AIC ARMA:", modelo_arma$aic, "\n")
## AIC ARMA: 659.9971
cat("AIC SARIMA:", modelo_sarima$aic, "\n")
## AIC SARIMA: 659.9971
# Seleccionar el mejor modelo
if (modelo_arma$aic < modelo_sarima$aic) {
mejor_modelo <- modelo_arma
modelo_usado <- "ARMA"
} else if (modelo_sarima$aic < modelo_arma$aic) {
mejor_modelo <- modelo_sarima
modelo_usado <- "SARIMA"
} else {
mejor_modelo <- modelo_arma
modelo_usado <- "ARMA (por empate)"
}
# Generar pronóstico a 3 meses
forecast_modelo <- forecast(mejor_modelo, h = 3)
autoplot(forecast_modelo) +
labs(title = paste("Pronóstico mensual de ventas -", modelo_usado, "(Producto 155002)"),
x = "Mes", y = "Ventas ($)") +
theme_minimal()

# Construir tabla combinada de histórico y predicción
ventas_historicas <- ventas_mensuales %>%
select(Fecha, Venta) %>%
mutate(Tipo = "Histórico", IC_Inferior = NA, IC_Superior = NA)
predicciones <- data.frame(
Fecha = seq.Date(from = max(ventas_historicas$Fecha) %m+% months(1),
by = "month", length.out = 3),
Venta = as.numeric(forecast_modelo$mean),
IC_Inferior = as.numeric(forecast_modelo$lower[,2]),
IC_Superior = as.numeric(forecast_modelo$upper[,2]),
Tipo = "Pronóstico"
)
tabla_completa <- bind_rows(ventas_historicas, predicciones) %>%
arrange(Fecha)
# Mostrar tabla
tabla_completa %>%
kable(digits = 2, caption = "Ventas mensuales y pronóstico - Producto 155002") %>%
kable_styling(full_width = FALSE)
Ventas mensuales y pronóstico - Producto 155002
|
Fecha
|
Venta
|
Tipo
|
IC_Inferior
|
IC_Superior
|
|
2023-01-01
|
484267.0
|
Histórico
|
NA
|
NA
|
|
2023-02-01
|
356929.8
|
Histórico
|
NA
|
NA
|
|
2023-03-01
|
849531.5
|
Histórico
|
NA
|
NA
|
|
2023-04-01
|
586246.4
|
Histórico
|
NA
|
NA
|
|
2023-05-01
|
870550.9
|
Histórico
|
NA
|
NA
|
|
2023-06-01
|
840797.0
|
Histórico
|
NA
|
NA
|
|
2023-07-01
|
482272.0
|
Histórico
|
NA
|
NA
|
|
2023-08-01
|
1026313.2
|
Histórico
|
NA
|
NA
|
|
2023-09-01
|
686875.0
|
Histórico
|
NA
|
NA
|
|
2023-10-01
|
768111.4
|
Histórico
|
NA
|
NA
|
|
2023-11-01
|
337716.7
|
Histórico
|
NA
|
NA
|
|
2023-12-01
|
494176.2
|
Histórico
|
NA
|
NA
|
|
2024-01-01
|
436061.2
|
Histórico
|
NA
|
NA
|
|
2024-02-01
|
457276.1
|
Histórico
|
NA
|
NA
|
|
2024-03-01
|
492586.4
|
Histórico
|
NA
|
NA
|
|
2024-04-01
|
636181.4
|
Histórico
|
NA
|
NA
|
|
2024-05-01
|
984456.2
|
Histórico
|
NA
|
NA
|
|
2024-06-01
|
977578.8
|
Histórico
|
NA
|
NA
|
|
2024-07-01
|
1149984.8
|
Histórico
|
NA
|
NA
|
|
2024-08-01
|
705081.6
|
Histórico
|
NA
|
NA
|
|
2024-09-01
|
622780.8
|
Histórico
|
NA
|
NA
|
|
2024-10-01
|
742951.7
|
Histórico
|
NA
|
NA
|
|
2024-11-01
|
641666.6
|
Histórico
|
NA
|
NA
|
|
2024-12-01
|
703508.3
|
Histórico
|
NA
|
NA
|
|
2025-01-01
|
653573.9
|
Pronóstico
|
257632.8
|
1049515
|
|
2025-02-01
|
634428.4
|
Pronóstico
|
224044.6
|
1044812
|
|
2025-03-01
|
668385.1
|
Pronóstico
|
205710.5
|
1131060
|
# Mostrar resumen del modelo y diagnóstico
cat("Modelo seleccionado:", modelo_usado, "\n")
## Modelo seleccionado: ARMA (por empate)
summary(mejor_modelo)
## Series: serie_ts
## ARIMA(0,0,2) with non-zero mean
##
## Coefficients:
## ma1 ma2 mean
## 0.2726 0.5396 668385.14
## s.e. 0.1612 0.2136 67923.92
##
## sigma^2 = 4.081e+10: log likelihood = -326
## AIC=660 AICc=662.1 BIC=664.71
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set 5999.422 188967.2 159380.3 -7.596432 25.93817 0.7976991
## ACF1
## Training set -0.01120463
checkresiduals(mejor_modelo)

##
## Ljung-Box test
##
## data: Residuals from ARIMA(0,0,2) with non-zero mean
## Q* = 0.93934, df = 3, p-value = 0.8159
##
## Model df: 2. Total lags used: 5
# Usar los valores ajustados del modelo para calcular las métricas
# Obtener los valores ajustados (fitted values)
fitted_values <- fitted(mejor_modelo)
# Calcular RMSE (Root Mean Squared Error)
rmse_modelo <- sqrt(mean((serie_ts - fitted_values)^2))
# Calcular MAPE (Mean Absolute Percentage Error) - con protección contra división por cero
mape_modelo <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
# Calcular MSE (Mean Squared Error)
mse_modelo <- mean((serie_ts - fitted_values)^2)
# Calcular R²
sse_modelo <- sum((serie_ts - fitted_values)^2)
sst_modelo <- sum((serie_ts - mean(serie_ts))^2)
r2_modelo <- 1 - (sse_modelo / sst_modelo)
# Mostrar las métricas
cat("R² del modelo", modelo_usado, "para 155002:", r2_modelo, "\n")
## R² del modelo ARMA (por empate) para 155002: 0.2341854
cat("AIC del modelo", modelo_usado, "para 155002:", mejor_modelo$aic, "\n")
## AIC del modelo ARMA (por empate) para 155002: 659.9971
cat("RMSE del modelo", modelo_usado, "para 155002:", rmse_modelo, "\n")
## RMSE del modelo ARMA (por empate) para 155002: 188967.2
cat("MAPE del modelo", modelo_usado, "para 155002:", mape_modelo, "\n")
## MAPE del modelo ARMA (por empate) para 155002: 25.93817
cat("MSE del modelo", modelo_usado, "para 155002:", mse_modelo, "\n")
## MSE del modelo ARMA (por empate) para 155002: 35708610199
# Guardar métricas ARMA/SARIMA para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155002", # Cambia este ID para cada producto
Modelo = "ARMA/SARIMA",
R2 = r2_modelo,
RMSE = rmse_modelo,
MAPE = mape_modelo,
MSE = mse_modelo,
AIC = mejor_modelo$aic
))
PRODUCTO 3678055
# Establecer el ID del producto
id_producto <- 3678055
# Agrupar ventas mensuales
ventas_mensuales <- datos_filtrados %>%
filter(ID_Inventario == id_producto) %>%
mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month"))) %>%
group_by(Fecha) %>%
summarise(Venta = sum(Venta, na.rm = TRUE), .groups = "drop") %>%
arrange(Fecha)
# Crear serie de tiempo mensual
serie_ts <- ts(ventas_mensuales$Venta, frequency = 12,
start = c(year(min(ventas_mensuales$Fecha)), month(min(ventas_mensuales$Fecha))))
# Ajustar modelos ARMA y SARIMA
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
assign(paste0("modelo_arma_", 3678055), modelo_arma, envir = .GlobalEnv)
modelo_sarima <- auto.arima(serie_ts, seasonal = TRUE, stepwise = FALSE, approximation = FALSE)
# Mostrar AIC de ambos modelos
cat("AIC ARMA:", modelo_arma$aic, "\n")
## AIC ARMA: 650.1669
cat("AIC SARIMA:", modelo_sarima$aic, "\n")
## AIC SARIMA: 650.1669
# Seleccionar el mejor modelo
if (modelo_arma$aic < modelo_sarima$aic) {
mejor_modelo <- modelo_arma
modelo_usado <- "ARMA"
} else if (modelo_sarima$aic < modelo_arma$aic) {
mejor_modelo <- modelo_sarima
modelo_usado <- "SARIMA"
} else {
mejor_modelo <- modelo_arma
modelo_usado <- "ARMA (por empate)"
}
# Generar pronóstico a 3 meses
forecast_modelo <- forecast(mejor_modelo, h = 3)
autoplot(forecast_modelo) +
labs(title = paste("Pronóstico mensual de ventas -", modelo_usado, "(Producto 3678055)"),
x = "Mes", y = "Ventas ($)") +
theme_minimal()

# Construir tabla combinada de histórico y predicción
ventas_historicas <- ventas_mensuales %>%
select(Fecha, Venta) %>%
mutate(Tipo = "Histórico", IC_Inferior = NA, IC_Superior = NA)
predicciones <- data.frame(
Fecha = seq.Date(from = max(ventas_historicas$Fecha) %m+% months(1),
by = "month", length.out = 3),
Venta = as.numeric(forecast_modelo$mean),
IC_Inferior = as.numeric(forecast_modelo$lower[,2]),
IC_Superior = as.numeric(forecast_modelo$upper[,2]),
Tipo = "Pronóstico"
)
tabla_completa <- bind_rows(ventas_historicas, predicciones) %>%
arrange(Fecha)
# Mostrar tabla
tabla_completa %>%
kable(digits = 2, caption = "Ventas mensuales y pronóstico - Producto 3678055") %>%
kable_styling(full_width = FALSE)
Ventas mensuales y pronóstico - Producto 3678055
|
Fecha
|
Venta
|
Tipo
|
IC_Inferior
|
IC_Superior
|
|
2023-01-01
|
727899.5
|
Histórico
|
NA
|
NA
|
|
2023-02-01
|
485061.1
|
Histórico
|
NA
|
NA
|
|
2023-03-01
|
907476.7
|
Histórico
|
NA
|
NA
|
|
2023-04-01
|
649840.0
|
Histórico
|
NA
|
NA
|
|
2023-05-01
|
550570.2
|
Histórico
|
NA
|
NA
|
|
2023-06-01
|
840594.2
|
Histórico
|
NA
|
NA
|
|
2023-07-01
|
466467.4
|
Histórico
|
NA
|
NA
|
|
2023-08-01
|
721771.1
|
Histórico
|
NA
|
NA
|
|
2023-09-01
|
1054439.7
|
Histórico
|
NA
|
NA
|
|
2023-10-01
|
643177.4
|
Histórico
|
NA
|
NA
|
|
2023-11-01
|
503904.6
|
Histórico
|
NA
|
NA
|
|
2023-12-01
|
507739.0
|
Histórico
|
NA
|
NA
|
|
2024-01-01
|
590547.7
|
Histórico
|
NA
|
NA
|
|
2024-02-01
|
350606.1
|
Histórico
|
NA
|
NA
|
|
2024-03-01
|
656677.1
|
Histórico
|
NA
|
NA
|
|
2024-04-01
|
528473.3
|
Histórico
|
NA
|
NA
|
|
2024-05-01
|
602836.8
|
Histórico
|
NA
|
NA
|
|
2024-06-01
|
588154.1
|
Histórico
|
NA
|
NA
|
|
2024-07-01
|
672666.2
|
Histórico
|
NA
|
NA
|
|
2024-08-01
|
866894.0
|
Histórico
|
NA
|
NA
|
|
2024-09-01
|
803128.3
|
Histórico
|
NA
|
NA
|
|
2024-10-01
|
621479.1
|
Histórico
|
NA
|
NA
|
|
2024-11-01
|
949993.2
|
Histórico
|
NA
|
NA
|
|
2024-12-01
|
847613.8
|
Histórico
|
NA
|
NA
|
|
2025-01-01
|
672417.1
|
Pronóstico
|
332289.1
|
1012545
|
|
2025-02-01
|
672417.1
|
Pronóstico
|
332289.1
|
1012545
|
|
2025-03-01
|
672417.1
|
Pronóstico
|
332289.1
|
1012545
|
# Mostrar resumen del modelo y diagnóstico
cat("Modelo seleccionado:", modelo_usado, "\n")
## Modelo seleccionado: ARMA (por empate)
summary(mejor_modelo)
## Series: serie_ts
## ARIMA(0,0,0) with non-zero mean
##
## Coefficients:
## mean
## 672417.1
## s.e. 34677.7
##
## sigma^2 = 3.012e+10: log likelihood = -323.08
## AIC=650.17 AICc=650.74 BIC=652.52
##
## Training set error measures:
## ME RMSE MAE MPE MAPE MASE
## Training set -3.953264e-10 169884 139025.5 -6.788285 22.22443 0.7072165
## ACF1
## Training set 0.04966062
checkresiduals(mejor_modelo)

##
## Ljung-Box test
##
## data: Residuals from ARIMA(0,0,0) with non-zero mean
## Q* = 3.3443, df = 5, p-value = 0.6471
##
## Model df: 0. Total lags used: 5
# Usar los valores ajustados del modelo para calcular las métricas
# Obtener los valores ajustados (fitted values)
fitted_values <- fitted(mejor_modelo)
# Calcular RMSE (Root Mean Squared Error)
rmse_modelo <- sqrt(mean((serie_ts - fitted_values)^2))
# Calcular MAPE (Mean Absolute Percentage Error) - con protección contra división por cero
mape_modelo <- mean(abs((serie_ts - fitted_values) / pmax(serie_ts, 0.01))) * 100
# Calcular MSE (Mean Squared Error)
mse_modelo <- mean((serie_ts - fitted_values)^2)
# Calcular R²
sse_modelo <- sum((serie_ts - fitted_values)^2)
sst_modelo <- sum((serie_ts - mean(serie_ts))^2)
r2_modelo <- 1 - (sse_modelo / sst_modelo)
# Mostrar las métricas
cat("R² del modelo", modelo_usado, "para 3678055:", r2_modelo, "\n")
## R² del modelo ARMA (por empate) para 3678055: 0
cat("AIC del modelo", modelo_usado, "para 3678055:", mejor_modelo$aic, "\n")
## AIC del modelo ARMA (por empate) para 3678055: 650.1669
cat("RMSE del modelo", modelo_usado, "para 3678055:", rmse_modelo, "\n")
## RMSE del modelo ARMA (por empate) para 3678055: 169884
cat("MAPE del modelo", modelo_usado, "para 3678055:", mape_modelo, "\n")
## MAPE del modelo ARMA (por empate) para 3678055: 22.22443
cat("MSE del modelo", modelo_usado, "para 3678055:", mse_modelo, "\n")
## MSE del modelo ARMA (por empate) para 3678055: 28860588465
# Guardar métricas ARMA/SARIMA para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3678055", # Cambia este ID para cada producto
Modelo = "ARMA/SARIMA",
R2 = r2_modelo,
RMSE = rmse_modelo,
MAPE = mape_modelo,
MSE = mse_modelo,
AIC = mejor_modelo$aic
))
REGRESION LINEAL
MAPA DE CALOR
# Variables numéricas relevantes
vars_numericas <- c("Cant", "Venta", "Costo_Venta", "Costo_Devolucion",
"Ajus_Sistema", "Ajuste_Manual", "Precio_Lista_Unitario",
"Precio_Final_Unitario", "Semana", "Mes", "Descuento_Porcentaje")
# 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)
# Ajuste del gráfico sin mar
ggcorrplot(matriz_cor,
method = "square",
type = "upper",
lab = TRUE,
lab_size = 2, # Mejor tamaño de los coeficientes
tl.cex = 10, # Tamaño de etiquetas más grande
tl.srt = 45, # Rotación de 45° de etiquetas
colors = c("#6D9EC1", "white", "#E46726"),
title = "Mapa de Correlación - Variables Numéricas",
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))
)

PRODUCTO 155001
# Filtrar solo los datos para el producto 155001
datos_155001 <- datos_filtrados %>%
filter(ID_Inventario == 155001) %>%
select(Venta, Cant, Costo_Venta, Costo_Devolucion,
Ajus_Sistema, Ajuste_Manual, Precio_Lista_Unitario,
Precio_Final_Unitario, Semana, Mes, Descuento_Porcentaje, Trx_Fecha) %>%
na.omit() # Eliminar filas con valores NA
# Crear una variable de tiempo continua basada en la fecha
datos_155001 <- datos_155001 %>%
mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")), # Asegúrate de que la fecha esté en formato Date
Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60)) # Tiempo en meses (ajustado por días)
# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_155001)
## # A tibble: 6 × 14
## Venta Cant Costo_Venta Costo_Devolucion Ajus_Sistema Ajuste_Manual
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 -1187. -2 0 958. -2515. -1083.
## 2 1187. 2 1194. 0 -2515. -1083.
## 3 21280 40 23874. 0 -2515. -1145.
## 4 15960 30 17906. 0 -2515. -1145.
## 5 31920 60 35811. 0 -2515. -1145.
## 6 2968 5 2570. 0 -2515. -1083.
## # ℹ 8 more variables: Precio_Lista_Unitario <dbl>, Precio_Final_Unitario <dbl>,
## # Semana <dbl>, Mes <dbl>, Descuento_Porcentaje <dbl>, Trx_Fecha <dttm>,
## # Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_155001 <- lm(Venta ~ Cant + Costo_Venta + Costo_Devolucion + Ajuste_Manual +
Precio_Lista_Unitario + Precio_Final_Unitario + Semana + Mes + Descuento_Porcentaje + Tiempo,
data = datos_155001)
# Ver resumen del modelo
summary(modelo_regresion_155001)
##
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Costo_Devolucion +
## Ajuste_Manual + Precio_Lista_Unitario + Precio_Final_Unitario +
## Semana + Mes + Descuento_Porcentaje + Tiempo, data = datos_155001)
##
## Residuals:
## Min 1Q Median 3Q Max
## -12727.8 -127.9 1.2 136.4 24493.6
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -1.991e+04 3.609e+03 -5.515 3.58e-08 ***
## Cant 1.480e+02 3.347e+00 44.219 < 2e-16 ***
## Costo_Venta 7.063e-01 8.937e-03 79.030 < 2e-16 ***
## Costo_Devolucion -7.222e-01 1.020e-02 -70.778 < 2e-16 ***
## Ajuste_Manual -9.221e-02 1.494e-02 -6.173 6.99e-10 ***
## Precio_Lista_Unitario -3.664e-01 9.459e-02 -3.873 0.000108 ***
## Precio_Final_Unitario 6.711e+00 9.625e-01 6.973 3.34e-12 ***
## Semana -1.337e+00 2.591e+00 -0.516 0.605802
## Mes 2.854e+01 1.147e+01 2.488 0.012868 *
## Descuento_Porcentaje 2.044e+02 3.978e+01 5.139 2.83e-07 ***
## Tiempo 6.316e+04 2.193e+05 0.288 0.773322
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 770.5 on 8651 degrees of freedom
## Multiple R-squared: 0.9887, Adjusted R-squared: 0.9887
## F-statistic: 7.601e+04 on 10 and 8651 DF, p-value: < 2.2e-16
# Ajuste del modelo de regresión lineal
modelo_regresion_155001 <- lm(Venta ~ Cant + Costo_Venta + Costo_Devolucion + Ajuste_Manual +
Precio_Lista_Unitario + Precio_Final_Unitario + Semana + Mes + Descuento_Porcentaje + Tiempo,
data = datos_155001)
# Predicciones usando el modelo ajustado
predicciones_155001 <- predict(modelo_regresion_155001, newdata = datos_155001)
# Calcular R^2 (coeficiente de determinación)
r2_155001 <- summary(modelo_regresion_155001)$r.squared
# Calcular AIC (Akaike Information Criterion)
aic_155001 <- AIC(modelo_regresion_155001)
# Calcular RMSE (Root Mean Squared Error)
rmse_155001 <- sqrt(mean((datos_155001$Venta - predicciones_155001)^2))
# Calcular MAPE (Mean Absolute Percentage Error)
mape_155001 <- mean(abs((datos_155001$Venta - predicciones_155001) / datos_155001$Venta)) * 100
# Calcular MSE (Mean Squared Error)
mse_155001 <- mean((datos_155001$Venta - predicciones_155001)^2)
# Mostrar las métricas
cat("R^2 del modelo de regresión lineal para 155001: ", r2_155001, "\n")
## R^2 del modelo de regresión lineal para 155001: 0.9887468
cat("AIC del modelo de regresión lineal para 155001: ", aic_155001, "\n")
## AIC del modelo de regresión lineal para 155001: 139748.7
cat("RMSE del modelo de regresión lineal para 155001: ", rmse_155001, "\n")
## RMSE del modelo de regresión lineal para 155001: 770.0434
cat("MAPE del modelo de regresión lineal para 155001: ", mape_155001, "\n")
## MAPE del modelo de regresión lineal para 155001: 15.90447
cat("MSE del modelo de regresión lineal para 155001: ", mse_155001, "\n")
## MSE del modelo de regresión lineal para 155001: 592966.8
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_155001)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155001", # Cambia este ID para cada producto
Modelo = "Regresión Lineal",
R2 = r2_155001, # Usa el nombre específico para este producto
RMSE = rmse_155001,
MAPE = mape_155001,
MSE = mse_155001,
AIC = aic_155001
))
PRODUCTO 3929788
# Filtrar solo los datos para el producto 3929788
datos_3929788 <- datos_filtrados %>%
filter(ID_Inventario == 3929788) %>%
select(Venta, Cant, Costo_Venta, Costo_Devolucion,
Ajus_Sistema, Ajuste_Manual, Precio_Lista_Unitario,
Precio_Final_Unitario, Semana, Mes, Descuento_Porcentaje, Trx_Fecha) %>%
na.omit() # Eliminar filas con valores NA
# Crear una variable de tiempo continua basada en la fecha
datos_3929788 <- datos_3929788 %>%
mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")), # Asegúrate de que la fecha esté en formato Date
Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60)) # Tiempo en meses (ajustado por días)
# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_3929788)
## # A tibble: 6 × 14
## Venta Cant Costo_Venta Costo_Devolucion Ajus_Sistema Ajuste_Manual
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 364 10 254. 0 -54.6 0
## 2 242. 6 167. 0 -60.5 0
## 3 -8680 -200 0 6740. -47.6 0
## 4 697. 15 506. 0 -44.5 0
## 5 13020 300 10110. 0 -47.6 0
## 6 2170 50 1685. 0 -47.6 0
## # ℹ 8 more variables: Precio_Lista_Unitario <dbl>, Precio_Final_Unitario <dbl>,
## # Semana <dbl>, Mes <dbl>, Descuento_Porcentaje <dbl>, Trx_Fecha <dttm>,
## # Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_3929788 <- lm(Venta ~ Cant + Costo_Venta + Costo_Devolucion + Ajuste_Manual +
Precio_Lista_Unitario + Precio_Final_Unitario + Semana + Mes + Descuento_Porcentaje + Tiempo,
data = datos_3929788)
# Ver resumen del modelo
summary(modelo_regresion_3929788)
##
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Costo_Devolucion +
## Ajuste_Manual + Precio_Lista_Unitario + Precio_Final_Unitario +
## Semana + Mes + Descuento_Porcentaje + Tiempo, data = datos_3929788)
##
## Residuals:
## Min 1Q Median 3Q Max
## -4344.0 -83.2 -43.7 30.1 3413.2
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.812e+03 3.978e+02 4.555 5.29e-06 ***
## Cant 2.753e+00 2.503e-01 10.996 < 2e-16 ***
## Costo_Venta 1.096e+00 8.318e-03 131.756 < 2e-16 ***
## Costo_Devolucion -1.118e+00 9.204e-03 -121.466 < 2e-16 ***
## Ajuste_Manual 5.962e-01 2.087e-01 2.857 0.004283 **
## Precio_Lista_Unitario 1.180e+01 2.596e+00 4.543 5.59e-06 ***
## Precio_Final_Unitario -2.356e+01 6.225e+00 -3.785 0.000154 ***
## Semana -1.111e+00 5.774e-01 -1.924 0.054327 .
## Mes 8.009e+00 2.571e+00 3.114 0.001847 **
## Descuento_Porcentaje -3.276e+01 6.506e+00 -5.035 4.84e-07 ***
## Tiempo 3.724e+04 5.570e+04 0.669 0.503806
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 222.5 on 13845 degrees of freedom
## Multiple R-squared: 0.995, Adjusted R-squared: 0.995
## F-statistic: 2.755e+05 on 10 and 13845 DF, p-value: < 2.2e-16
# Predicciones usando el modelo ajustado
predicciones_3929788 <- predict(modelo_regresion_3929788, newdata = datos_3929788)
# Calcular R^2 (coeficiente de determinación)
r2_3929788 <- summary(modelo_regresion_3929788)$r.squared
# Calcular AIC (Akaike Information Criterion)
aic_3929788 <- AIC(modelo_regresion_3929788)
# Calcular RMSE (Root Mean Squared Error)
rmse_3929788 <- sqrt(mean((datos_3929788$Venta - predicciones_3929788)^2))
# Calcular MAPE (Mean Absolute Percentage Error)
# Añadimos protección contra división por cero
mape_3929788 <- mean(abs((datos_3929788$Venta - predicciones_3929788) / pmax(datos_3929788$Venta, 0.01))) * 100
# Calcular MSE (Mean Squared Error)
mse_3929788 <- mean((datos_3929788$Venta - predicciones_3929788)^2)
# Mostrar las métricas
cat("R^2 del modelo de regresión lineal para 3929788: ", r2_3929788, "\n")
## R^2 del modelo de regresión lineal para 3929788: 0.9949995
cat("AIC del modelo de regresión lineal para 3929788: ", aic_3929788, "\n")
## AIC del modelo de regresión lineal para 3929788: 189113.8
cat("RMSE del modelo de regresión lineal para 3929788: ", rmse_3929788, "\n")
## RMSE del modelo de regresión lineal para 3929788: 222.3941
cat("MAPE del modelo de regresión lineal para 3929788: ", mape_3929788, "\n")
## MAPE del modelo de regresión lineal para 3929788: 29141.99
cat("MSE del modelo de regresión lineal para 3929788: ", mse_3929788, "\n")
## MSE del modelo de regresión lineal para 3929788: 49459.16
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3929788)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3929788", # Cambia este ID para cada producto
Modelo = "Regresión Lineal",
R2 = r2_3929788, # Usa el nombre específico para este producto
RMSE = rmse_3929788,
MAPE = mape_3929788,
MSE = mse_3929788,
AIC = aic_3929788
))
PRODUCTO 3904152
# Filtrar solo los datos para el producto 3904152
datos_3904152 <- datos_filtrados %>%
filter(ID_Inventario == 3904152) %>%
select(Venta, Cant, Costo_Venta, Costo_Devolucion,
Ajus_Sistema, Ajuste_Manual, Precio_Lista_Unitario,
Precio_Final_Unitario, Semana, Mes, Descuento_Porcentaje, Trx_Fecha) %>%
na.omit() # Eliminar filas con valores NA
# Crear una variable de tiempo continua basada en la fecha
datos_3904152 <- datos_3904152 %>%
mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")), # Asegúrate de que la fecha esté en formato Date
Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60)) # Tiempo en meses (ajustado por días)
# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_3904152)
## # A tibble: 6 × 14
## Venta Cant Costo_Venta Costo_Devolucion Ajus_Sistema Ajuste_Manual
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 3402 1 2462. 0 -5652. 0
## 2 9240 3 7382. 0 -5720. -254.
## 3 3402 1 2461. 0 -5652. 0
## 4 3402 1 2462. 0 -5652. 0
## 5 3402 1 2462. 0 -5652. 0
## 6 30800 10 24563. 0 -5652. -322
## # ℹ 8 more variables: Precio_Lista_Unitario <dbl>, Precio_Final_Unitario <dbl>,
## # Semana <dbl>, Mes <dbl>, Descuento_Porcentaje <dbl>, Trx_Fecha <dttm>,
## # Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_3904152 <- lm(Venta ~ Cant + Costo_Venta + Costo_Devolucion + Ajuste_Manual +
Precio_Lista_Unitario + Precio_Final_Unitario + Semana + Mes + Descuento_Porcentaje + Tiempo,
data = datos_3904152)
# Ver resumen del modelo
summary(modelo_regresion_3904152)
##
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Costo_Devolucion +
## Ajuste_Manual + Precio_Lista_Unitario + Precio_Final_Unitario +
## Semana + Mes + Descuento_Porcentaje + Tiempo, data = datos_3904152)
##
## Residuals:
## Min 1Q Median 3Q Max
## -7518.7 -136.5 -4.9 119.1 3169.4
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 3.355e+04 9.019e+03 3.720 0.000203 ***
## Cant 2.305e+02 6.261e+01 3.681 0.000237 ***
## Costo_Venta 1.187e+00 2.654e-02 44.713 < 2e-16 ***
## Costo_Devolucion -1.201e+00 2.849e-02 -42.150 < 2e-16 ***
## Ajuste_Manual 4.915e-02 2.548e-02 1.929 0.053865 .
## Precio_Lista_Unitario 1.156e+00 5.044e-01 2.292 0.021989 *
## Precio_Final_Unitario -3.647e+00 1.482e+00 -2.461 0.013916 *
## Semana 6.603e+00 2.061e+00 3.204 0.001374 **
## Mes -1.994e+01 9.143e+00 -2.181 0.029278 *
## Descuento_Porcentaje -5.004e+02 1.365e+02 -3.667 0.000250 ***
## Tiempo 2.204e+06 1.514e+05 14.551 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 386.4 on 2605 degrees of freedom
## Multiple R-squared: 0.9987, Adjusted R-squared: 0.9987
## F-statistic: 2.009e+05 on 10 and 2605 DF, p-value: < 2.2e-16
# Predicciones usando el modelo ajustado
predicciones_3904152 <- predict(modelo_regresion_3904152, newdata = datos_3904152)
# Calcular R^2 (coeficiente de determinación)
r2_3904152 <- summary(modelo_regresion_3904152)$r.squared
# Calcular AIC (Akaike Information Criterion)
aic_3904152 <- AIC(modelo_regresion_3904152)
# Calcular RMSE (Root Mean Squared Error)
rmse_3904152 <- sqrt(mean((datos_3904152$Venta - predicciones_3904152)^2))
# Calcular MAPE (Mean Absolute Percentage Error)
# Añadimos protección contra división por cero
mape_3904152 <- mean(abs((datos_3904152$Venta - predicciones_3904152) / pmax(datos_3904152$Venta, 0.01))) * 100
# Calcular MSE (Mean Squared Error)
mse_3904152 <- mean((datos_3904152$Venta - predicciones_3904152)^2)
# Mostrar las métricas
cat("R^2 del modelo de regresión lineal para 3904152: ", r2_3904152, "\n")
## R^2 del modelo de regresión lineal para 3904152: 0.9987051
cat("AIC del modelo de regresión lineal para 3904152: ", aic_3904152, "\n")
## AIC del modelo de regresión lineal para 3904152: 38603.25
cat("RMSE del modelo de regresión lineal para 3904152: ", rmse_3904152, "\n")
## RMSE del modelo de regresión lineal para 3904152: 385.5886
cat("MAPE del modelo de regresión lineal para 3904152: ", mape_3904152, "\n")
## MAPE del modelo de regresión lineal para 3904152: 128319.3
cat("MSE del modelo de regresión lineal para 3904152: ", mse_3904152, "\n")
## MSE del modelo de regresión lineal para 3904152: 148678.6
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3904152)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3904152", # Cambia este ID para cada producto
Modelo = "Regresión Lineal",
R2 = r2_3904152, # Usa el nombre específico para este producto
RMSE = rmse_3904152,
MAPE = mape_3904152,
MSE = mse_3904152,
AIC = aic_3904152
))
PRODUCTO 155002
# Filtrar solo los datos para el producto 155002
datos_155002 <- datos_filtrados %>%
filter(ID_Inventario == 155002) %>%
select(Venta, Cant, Costo_Venta, Costo_Devolucion,
Ajus_Sistema, Ajuste_Manual, Precio_Lista_Unitario,
Precio_Final_Unitario, Semana, Mes, Descuento_Porcentaje, Trx_Fecha) %>%
na.omit() # Eliminar filas con valores NA
# Crear una variable de tiempo continua basada en la fecha
datos_155002 <- datos_155002 %>%
mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")), # Asegúrate de que la fecha esté en formato Date
Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60)) # Tiempo en meses (ajustado por días)
# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_155002)
## # A tibble: 6 × 14
## Venta Cant Costo_Venta Costo_Devolucion Ajus_Sistema Ajuste_Manual
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 5320 10 8247. 0 -2515. -1145.
## 2 5320 10 8247. 0 -2515. -1145.
## 3 2660 5 4124. 0 -2515. -1145.
## 4 630 1 519. 0 -2305. -1256.
## 5 1120 2 1037. 0 -2515. -1117.
## 6 1537. 3 1556. 0 -2515. -1164.
## # ℹ 8 more variables: Precio_Lista_Unitario <dbl>, Precio_Final_Unitario <dbl>,
## # Semana <dbl>, Mes <dbl>, Descuento_Porcentaje <dbl>, Trx_Fecha <dttm>,
## # Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_155002 <- lm(Venta ~ Cant + Costo_Venta + Costo_Devolucion + Ajuste_Manual +
Precio_Lista_Unitario + Precio_Final_Unitario + Semana + Mes + Descuento_Porcentaje + Tiempo,
data = datos_155002)
# Ver resumen del modelo
summary(modelo_regresion_155002)
##
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Costo_Devolucion +
## Ajuste_Manual + Precio_Lista_Unitario + Precio_Final_Unitario +
## Semana + Mes + Descuento_Porcentaje + Tiempo, data = datos_155002)
##
## Residuals:
## Min 1Q Median 3Q Max
## -49757 -156 2 130 49110
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.247e+05 2.839e+04 4.393 1.14e-05 ***
## Cant 2.935e+02 5.054e+00 58.072 < 2e-16 ***
## Costo_Venta 3.077e-01 1.267e-02 24.297 < 2e-16 ***
## Costo_Devolucion -6.061e-01 2.361e-02 -25.674 < 2e-16 ***
## Ajuste_Manual -4.431e-02 2.706e-02 -1.637 0.10158
## Precio_Lista_Unitario 3.206e+00 6.621e-01 4.842 1.32e-06 ***
## Precio_Final_Unitario -3.104e+01 7.417e+00 -4.184 2.90e-05 ***
## Semana -2.151e+00 5.622e+00 -0.383 0.70199
## Mes 1.811e+01 2.470e+01 0.733 0.46359
## Descuento_Porcentaje -1.390e+03 3.116e+02 -4.462 8.29e-06 ***
## Tiempo -1.124e+06 4.005e+05 -2.807 0.00502 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 1137 on 5826 degrees of freedom
## Multiple R-squared: 0.9571, Adjusted R-squared: 0.957
## F-statistic: 1.3e+04 on 10 and 5826 DF, p-value: < 2.2e-16
# Predicciones usando el modelo ajustado
predicciones_155002 <- predict(modelo_regresion_155002, newdata = datos_155002)
# Calcular R^2 (coeficiente de determinación)
r2_155002 <- summary(modelo_regresion_155002)$r.squared
# Calcular AIC (Akaike Information Criterion)
aic_155002 <- AIC(modelo_regresion_155002)
# Calcular RMSE (Root Mean Squared Error)
rmse_155002 <- sqrt(mean((datos_155002$Venta - predicciones_155002)^2))
# Calcular MAPE (Mean Absolute Percentage Error)
# Añadimos protección contra división por cero
mape_155002 <- mean(abs((datos_155002$Venta - predicciones_155002) / pmax(datos_155002$Venta, 0.01))) * 100
# Calcular MSE (Mean Squared Error)
mse_155002 <- mean((datos_155002$Venta - predicciones_155002)^2)
# Mostrar las métricas
cat("R^2 del modelo de regresión lineal para 155002: ", r2_155002, "\n")
## R^2 del modelo de regresión lineal para 155002: 0.9571086
cat("AIC del modelo de regresión lineal para 155002: ", aic_155002, "\n")
## AIC del modelo de regresión lineal para 155002: 98715.09
cat("RMSE del modelo de regresión lineal para 155002: ", rmse_155002, "\n")
## RMSE del modelo de regresión lineal para 155002: 1135.676
cat("MAPE del modelo de regresión lineal para 155002: ", mape_155002, "\n")
## MAPE del modelo de regresión lineal para 155002: 238027.6
cat("MSE del modelo de regresión lineal para 155002: ", mse_155002, "\n")
## MSE del modelo de regresión lineal para 155002: 1289760
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_155002)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155002", # Cambia este ID para cada producto
Modelo = "Regresión Lineal",
R2 = r2_155002, # Usa el nombre específico para este producto
RMSE = rmse_155002,
MAPE = mape_155002,
MSE = mse_155002,
AIC = aic_155002
))
PRODUCTO 3678055
# Filtrar solo los datos para el producto 3678055
datos_3678055 <- datos_filtrados %>%
filter(ID_Inventario == 3678055) %>%
select(Venta, Cant, Costo_Venta, Costo_Devolucion,
Ajus_Sistema, Ajuste_Manual, Precio_Lista_Unitario,
Precio_Final_Unitario, Semana, Mes, Descuento_Porcentaje, Trx_Fecha) %>%
na.omit() # Eliminar filas con valores NA
# Crear una variable de tiempo continua basada en la fecha
datos_3678055 <- datos_3678055 %>%
mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")), # Asegúrate de que la fecha esté en formato Date
Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60)) # Tiempo en meses (ajustado por días)
# Verificar las primeras filas para asegurarnos de que la variable de tiempo esté bien creada
head(datos_3678055)
## # A tibble: 6 × 14
## Venta Cant Costo_Venta Costo_Devolucion Ajus_Sistema Ajuste_Manual
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 36358 7 28807. 0 -9695 -363.
## 2 5670 1 4213. 0 -9582. 0
## 3 10773 2 8232. 0 -9582. -284.
## 4 5670 1 4116. 0 -9582. 0
## 5 5386. 1 4156. 0 -9582. -284.
## 6 5386. 1 4213. 0 -9582. -284.
## # ℹ 8 more variables: Precio_Lista_Unitario <dbl>, Precio_Final_Unitario <dbl>,
## # Semana <dbl>, Mes <dbl>, Descuento_Porcentaje <dbl>, Trx_Fecha <dttm>,
## # Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_3678055 <- lm(Venta ~ Cant + Costo_Venta + Costo_Devolucion + Ajuste_Manual +
Precio_Lista_Unitario + Precio_Final_Unitario + Semana + Mes + Descuento_Porcentaje + Tiempo,
data = datos_3678055)
# Ver resumen del modelo
summary(modelo_regresion_3678055)
##
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Costo_Devolucion +
## Ajuste_Manual + Precio_Lista_Unitario + Precio_Final_Unitario +
## Semana + Mes + Descuento_Porcentaje + Tiempo, data = datos_3678055)
##
## Residuals:
## Min 1Q Median 3Q Max
## -3892.2 -156.4 -8.0 149.7 3675.0
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 6.121e+04 1.245e+04 4.915 9.72e-07 ***
## Cant 1.402e+03 1.045e+02 13.408 < 2e-16 ***
## Costo_Venta 9.479e-01 2.613e-02 36.280 < 2e-16 ***
## Costo_Devolucion -9.928e-01 2.718e-02 -36.524 < 2e-16 ***
## Ajuste_Manual 4.928e-02 2.659e-02 1.853 0.064020 .
## Precio_Lista_Unitario 1.573e+00 4.037e-01 3.895 0.000102 ***
## Precio_Final_Unitario -4.533e+00 1.201e+00 -3.774 0.000166 ***
## Semana 4.904e+00 2.595e+00 1.890 0.058963 .
## Mes -6.678e+00 1.185e+01 -0.563 0.573276
## Descuento_Porcentaje -9.384e+02 1.873e+02 -5.011 5.99e-07 ***
## Tiempo 1.952e+06 2.115e+05 9.231 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 483.7 on 1702 degrees of freedom
## Multiple R-squared: 0.9977, Adjusted R-squared: 0.9977
## F-statistic: 7.446e+04 on 10 and 1702 DF, p-value: < 2.2e-16
#Predicciones usando el modelo ajustado
predicciones_3678055 <- predict(modelo_regresion_3678055, newdata = datos_3678055)
# Calcular R^2 (coeficiente de determinación)
r2_3678055 <- summary(modelo_regresion_3678055)$r.squared
# Calcular AIC (Akaike Information Criterion)
aic_3678055 <- AIC(modelo_regresion_3678055)
# Calcular RMSE (Root Mean Squared Error)
rmse_3678055 <- sqrt(mean((datos_3678055$Venta - predicciones_3678055)^2))
# Calcular MAPE (Mean Absolute Percentage Error)
# Añadimos protección contra división por cero
mape_3678055 <- mean(abs((datos_3678055$Venta - predicciones_3678055) / pmax(datos_3678055$Venta, 0.01))) * 100
# Calcular MSE (Mean Squared Error)
mse_3678055 <- mean((datos_3678055$Venta - predicciones_3678055)^2)
# Mostrar las métricas
cat("R^2 del modelo de regresión lineal para 3678055: ", r2_3678055, "\n")
## R^2 del modelo de regresión lineal para 3678055: 0.9977194
cat("AIC del modelo de regresión lineal para 3678055: ", aic_3678055, "\n")
## AIC del modelo de regresión lineal para 3678055: 26051.77
cat("RMSE del modelo de regresión lineal para 3678055: ", rmse_3678055, "\n")
## RMSE del modelo de regresión lineal para 3678055: 482.1191
cat("MAPE del modelo de regresión lineal para 3678055: ", mape_3678055, "\n")
## MAPE del modelo de regresión lineal para 3678055: 236820.8
cat("MSE del modelo de regresión lineal para 3678055: ", mse_3678055, "\n")
## MSE del modelo de regresión lineal para 3678055: 232438.8
# Diagnóstico de residuos del modelo
par(mfrow = c(2, 2))
plot(modelo_regresion_3678055)

# Guardar métricas de Regresión Lineal para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3678055", # Cambia este ID para cada producto
Modelo = "Regresión Lineal",
R2 = r2_3678055, # Usa el nombre específico para este producto
RMSE = rmse_3678055,
MAPE = mape_3678055,
MSE = mse_3678055,
AIC = aic_3678055
))
ANALISIS DE VARIABLES
IMPORTANTES
# Función simplificada para analizar coeficientes
analizar_coeficientes <- function(modelo, nombre_producto) {
resumen <- summary(modelo)
coef_df <- as.data.frame(resumen$coefficients)
colnames(coef_df) <- c("Estimate", "Std.Error", "t.value", "p.value")
coef_df$Variable <- rownames(coef_df)
coef_df$Producto <- nombre_producto
coef_df$Significativo <- ifelse(coef_df$p.value < 0.05, "Sí", "No")
return(coef_df %>%
select(Producto, Variable, Estimate, p.value, Significativo) %>%
arrange(desc(abs(Estimate))))
}
# Aplicar a cada modelo
coef_155001 <- analizar_coeficientes(modelo_regresion_155001, "155001")
coef_155002 <- analizar_coeficientes(modelo_regresion_155002, "155002")
coef_3678055 <- analizar_coeficientes(modelo_regresion_3678055, "3678055")
coef_3904152 <- analizar_coeficientes(modelo_regresion_3904152, "3904152")
coef_3929788 <- analizar_coeficientes(modelo_regresion_3929788, "3929788")
# Combinar todos los coeficientes
todos_coeficientes <- bind_rows(coef_155001, coef_155002, coef_3678055, coef_3904152, coef_3929788)
# Tabla con variables importantes incluyendo significancia
variables_importantes <- todos_coeficientes %>%
filter(Variable != "(Intercept)") %>%
group_by(Producto) %>%
arrange(Producto, desc(abs(Estimate))) %>%
mutate(Impacto = ifelse(Estimate > 0, "Positivo", "Negativo"))
# Tabla completa con todas las variables importantes
kable(variables_importantes %>%
select(Producto, Variable, Estimate, p.value, Significativo, Impacto),
caption = "Variables importantes por producto",
col.names = c("Producto", "Variable", "Coeficiente", "p-value", "Significativo", "Impacto"),
digits = c(0, 0, 4, 4, 0, 0)) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
Variables importantes por producto
|
Producto
|
Variable
|
Coeficiente
|
p-value
|
Significativo
|
Impacto
|
|
155001
|
Tiempo
|
63161.0840
|
0.7733
|
No
|
Positivo
|
|
155001
|
Descuento_Porcentaje
|
204.3951
|
0.0000
|
Sí
|
Positivo
|
|
155001
|
Cant
|
147.9938
|
0.0000
|
Sí
|
Positivo
|
|
155001
|
Mes
|
28.5405
|
0.0129
|
Sí
|
Positivo
|
|
155001
|
Precio_Final_Unitario
|
6.7112
|
0.0000
|
Sí
|
Positivo
|
|
155001
|
Semana
|
-1.3371
|
0.6058
|
No
|
Negativo
|
|
155001
|
Costo_Devolucion
|
-0.7222
|
0.0000
|
Sí
|
Negativo
|
|
155001
|
Costo_Venta
|
0.7063
|
0.0000
|
Sí
|
Positivo
|
|
155001
|
Precio_Lista_Unitario
|
-0.3664
|
0.0001
|
Sí
|
Negativo
|
|
155001
|
Ajuste_Manual
|
-0.0922
|
0.0000
|
Sí
|
Negativo
|
|
155002
|
Tiempo
|
-1124277.1053
|
0.0050
|
Sí
|
Negativo
|
|
155002
|
Descuento_Porcentaje
|
-1390.3581
|
0.0000
|
Sí
|
Negativo
|
|
155002
|
Cant
|
293.5122
|
0.0000
|
Sí
|
Positivo
|
|
155002
|
Precio_Final_Unitario
|
-31.0372
|
0.0000
|
Sí
|
Negativo
|
|
155002
|
Mes
|
18.1056
|
0.4636
|
No
|
Positivo
|
|
155002
|
Precio_Lista_Unitario
|
3.2062
|
0.0000
|
Sí
|
Positivo
|
|
155002
|
Semana
|
-2.1511
|
0.7020
|
No
|
Negativo
|
|
155002
|
Costo_Devolucion
|
-0.6061
|
0.0000
|
Sí
|
Negativo
|
|
155002
|
Costo_Venta
|
0.3077
|
0.0000
|
Sí
|
Positivo
|
|
155002
|
Ajuste_Manual
|
-0.0443
|
0.1016
|
No
|
Negativo
|
|
3678055
|
Tiempo
|
1952336.4033
|
0.0000
|
Sí
|
Positivo
|
|
3678055
|
Cant
|
1401.6976
|
0.0000
|
Sí
|
Positivo
|
|
3678055
|
Descuento_Porcentaje
|
-938.3801
|
0.0000
|
Sí
|
Negativo
|
|
3678055
|
Mes
|
-6.6784
|
0.5733
|
No
|
Negativo
|
|
3678055
|
Semana
|
4.9042
|
0.0590
|
No
|
Positivo
|
|
3678055
|
Precio_Final_Unitario
|
-4.5331
|
0.0002
|
Sí
|
Negativo
|
|
3678055
|
Precio_Lista_Unitario
|
1.5725
|
0.0001
|
Sí
|
Positivo
|
|
3678055
|
Costo_Devolucion
|
-0.9928
|
0.0000
|
Sí
|
Negativo
|
|
3678055
|
Costo_Venta
|
0.9479
|
0.0000
|
Sí
|
Positivo
|
|
3678055
|
Ajuste_Manual
|
0.0493
|
0.0640
|
No
|
Positivo
|
|
3904152
|
Tiempo
|
2203734.6518
|
0.0000
|
Sí
|
Positivo
|
|
3904152
|
Descuento_Porcentaje
|
-500.4495
|
0.0003
|
Sí
|
Negativo
|
|
3904152
|
Cant
|
230.4975
|
0.0002
|
Sí
|
Positivo
|
|
3904152
|
Mes
|
-19.9406
|
0.0293
|
Sí
|
Negativo
|
|
3904152
|
Semana
|
6.6029
|
0.0014
|
Sí
|
Positivo
|
|
3904152
|
Precio_Final_Unitario
|
-3.6473
|
0.0139
|
Sí
|
Negativo
|
|
3904152
|
Costo_Devolucion
|
-1.2009
|
0.0000
|
Sí
|
Negativo
|
|
3904152
|
Costo_Venta
|
1.1869
|
0.0000
|
Sí
|
Positivo
|
|
3904152
|
Precio_Lista_Unitario
|
1.1561
|
0.0220
|
Sí
|
Positivo
|
|
3904152
|
Ajuste_Manual
|
0.0492
|
0.0539
|
No
|
Positivo
|
|
3929788
|
Tiempo
|
37238.1681
|
0.5038
|
No
|
Positivo
|
|
3929788
|
Descuento_Porcentaje
|
-32.7588
|
0.0000
|
Sí
|
Negativo
|
|
3929788
|
Precio_Final_Unitario
|
-23.5631
|
0.0002
|
Sí
|
Negativo
|
|
3929788
|
Precio_Lista_Unitario
|
11.7953
|
0.0000
|
Sí
|
Positivo
|
|
3929788
|
Mes
|
8.0085
|
0.0018
|
Sí
|
Positivo
|
|
3929788
|
Cant
|
2.7528
|
0.0000
|
Sí
|
Positivo
|
|
3929788
|
Costo_Devolucion
|
-1.1180
|
0.0000
|
Sí
|
Negativo
|
|
3929788
|
Semana
|
-1.1111
|
0.0543
|
No
|
Negativo
|
|
3929788
|
Costo_Venta
|
1.0959
|
0.0000
|
Sí
|
Positivo
|
|
3929788
|
Ajuste_Manual
|
0.5962
|
0.0043
|
Sí
|
Positivo
|
# Tabla resumen con top 3 por producto
top_por_producto <- variables_importantes %>%
group_by(Producto) %>%
slice_head(n = 3) %>%
select(Producto, Variable, Estimate, p.value, Significativo, Impacto)
kable(top_por_producto,
caption = "Top 3 variables más importantes por producto",
col.names = c("Producto", "Variable", "Coeficiente", "p-value", "Significativo", "Impacto"),
digits = c(0, 0, 4, 4, 0, 0)) %>%
kable_styling(bootstrap_options = c("striped", "hover", "condensed"))
Top 3 variables más importantes por producto
|
Producto
|
Variable
|
Coeficiente
|
p-value
|
Significativo
|
Impacto
|
|
155001
|
Tiempo
|
63161.0840
|
0.7733
|
No
|
Positivo
|
|
155001
|
Descuento_Porcentaje
|
204.3951
|
0.0000
|
Sí
|
Positivo
|
|
155001
|
Cant
|
147.9938
|
0.0000
|
Sí
|
Positivo
|
|
155002
|
Tiempo
|
-1124277.1053
|
0.0050
|
Sí
|
Negativo
|
|
155002
|
Descuento_Porcentaje
|
-1390.3581
|
0.0000
|
Sí
|
Negativo
|
|
155002
|
Cant
|
293.5122
|
0.0000
|
Sí
|
Positivo
|
|
3678055
|
Tiempo
|
1952336.4033
|
0.0000
|
Sí
|
Positivo
|
|
3678055
|
Cant
|
1401.6976
|
0.0000
|
Sí
|
Positivo
|
|
3678055
|
Descuento_Porcentaje
|
-938.3801
|
0.0000
|
Sí
|
Negativo
|
|
3904152
|
Tiempo
|
2203734.6518
|
0.0000
|
Sí
|
Positivo
|
|
3904152
|
Descuento_Porcentaje
|
-500.4495
|
0.0003
|
Sí
|
Negativo
|
|
3904152
|
Cant
|
230.4975
|
0.0002
|
Sí
|
Positivo
|
|
3929788
|
Tiempo
|
37238.1681
|
0.5038
|
No
|
Positivo
|
|
3929788
|
Descuento_Porcentaje
|
-32.7588
|
0.0000
|
Sí
|
Negativo
|
|
3929788
|
Precio_Final_Unitario
|
-23.5631
|
0.0002
|
Sí
|
Negativo
|
RANDOM FOREST
PRODUCTO 155001
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155001 %>%
select(-Trx_Fecha, -Fecha)
# Ajustar el modelo Random Forest
set.seed(123) # Para reproducibilidad
modelo_rf_155001 <- randomForest(
Venta ~ .,
data = datos_modelo,
ntree = 500, # Número de árboles
mtry = floor(sqrt(ncol(datos_modelo) - 1)), # Número de variables a considerar en cada split
importance = TRUE # Calcular importancia de variables
)
# Ver resumen del modelo
print(modelo_rf_155001)
##
## Call:
## randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500, mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE)
## Type of random forest: regression
## Number of trees: 500
## No. of variables tried at each split: 3
##
## Mean of squared residuals: 6577519
## % Var explained: 87.52
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_155001, newdata = datos_modelo)
# Calcular métricas
# R² (ya calculado por el modelo)
r2_rf <- 1 - sum((datos_modelo$Venta - predicciones_rf)^2) / sum((datos_modelo$Venta - mean(datos_modelo$Venta))^2)
# AIC aproximado (usando fórmula general para AIC)
n <- nrow(datos_modelo)
k <- length(modelo_rf_155001$forest$ncat) # Número de variables
mse <- mean((datos_modelo$Venta - predicciones_rf)^2)
aic_rf <- n * log(mse) + 2 * k
# RMSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100
# MSE
mse_rf <- mean((datos_modelo$Venta - predicciones_rf)^2)
# Mostrar las métricas
cat("Modelo Random Forest para producto 155001\n")
## Modelo Random Forest para producto 155001
cat("R² del modelo Random Forest:", r2_rf, "\n")
## R² del modelo Random Forest: 0.9784171
cat("AIC aproximado del modelo Random Forest:", aic_rf, "\n")
## AIC aproximado del modelo Random Forest: 120806.1
cat("RMSE del modelo Random Forest:", rmse_rf, "\n")
## RMSE del modelo Random Forest: 1066.428
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 156067.2
cat("MSE del modelo Random Forest:", mse_rf, "\n\n")
## MSE del modelo Random Forest: 1137268
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_155001)
print(importancia_vars)
## %IncMSE IncNodePurity
## Cant 32.181672 187860926924
## Costo_Venta 35.455099 190655807445
## Costo_Devolucion 21.642299 36585857750
## Ajus_Sistema 7.407869 2900824957
## Ajuste_Manual 5.790217 3480956804
## Precio_Lista_Unitario 2.001407 1201653459
## Precio_Final_Unitario 4.439926 6129415777
## Semana 5.350558 4119086241
## Mes 3.082153 2077576769
## Descuento_Porcentaje 3.153115 9151699958
## Tiempo 3.649425 5068042666
# Graficar importancia de variables
varImpPlot(modelo_rf_155001, main = "Importancia de Variables - Producto 155001")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
Observado = datos_modelo$Venta,
Predicho = predicciones_rf
)
ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
geom_point(alpha = 0.5) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
labs(
title = "Valores Observados vs Predicciones - Producto 155001",
x = "Ventas Observadas",
y = "Ventas Predichas"
) +
theme_minimal()

# NUEVOS ANÁLISIS AÑADIDOS
# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores,
main = "Distribución de Errores - Producto 155001",
xlab = "Error (Observado - Predicho)",
col = "skyblue",
breaks = 30)

# Estadísticas descriptivas de los errores
cat("Estadísticas descriptivas de los errores:\n")
## Estadísticas descriptivas de los errores:
cat("Media de errores:", mean(errores), "\n")
## Media de errores: 3.649569
cat("Desviación estándar de errores:", sd(errores), "\n")
## Desviación estándar de errores: 1066.483
cat("Mínimo:", min(errores), "\n")
## Mínimo: -64467.98
cat("Máximo:", max(errores), "\n")
## Máximo: 53659.3
cat("Mediana:", median(errores), "\n")
## Mediana: -1.082734
# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), 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",
x = "Ventas Predichas",
y = "Error (Observado - Predicho)"
) +
theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155001", # Cambia este ID para cada producto
Modelo = "Random Forest",
R2 = r2_rf, # Estas variables son las que se usan en tu código RF
RMSE = rmse_rf,
MAPE = mape_rf,
MSE = mse_rf,
AIC = aic_rf
))
PRODUCTO 3929788
# Crear una variable de tiempo continua basada en la fecha
datos_3929788 <- datos_3929788 %>%
mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")),
Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60)) # Tiempo en meses
# Mostrar un resumen de los datos
summary(datos_3929788)
## Venta Cant Costo_Venta Costo_Devolucion
## Min. :-51244.2 Min. :-1470.00 Min. : 0.0 Min. : 0.00
## 1st Qu.: 395.5 1st Qu.: 10.00 1st Qu.: 278.9 1st Qu.: 0.00
## Median : 871.4 Median : 20.00 Median : 605.4 Median : 0.00
## Mean : 1617.6 Mean : 41.89 Mean : 1288.8 Mean : 20.42
## 3rd Qu.: 2016.0 3rd Qu.: 50.00 3rd Qu.: 1513.4 3rd Qu.: 0.00
## Max. : 53096.4 Max. : 1540.00 Max. :46610.5 Max. :41003.59
## Ajus_Sistema Ajuste_Manual Precio_Lista_Unitario Precio_Final_Unitario
## Min. :-77.00 Min. :-73.920 Min. : 91.0 Min. : 28.00
## 1st Qu.:-65.86 1st Qu.: -4.144 1st Qu.:100.8 1st Qu.: 36.40
## Median :-60.48 Median : 0.000 Median :100.8 Median : 40.32
## Mean :-61.25 Mean : -3.332 Mean :104.3 Mean : 40.84
## 3rd Qu.:-58.46 3rd Qu.: 0.000 3rd Qu.:108.9 3rd Qu.: 43.57
## Max. : 0.00 Max. : 9.856 Max. :109.8 Max. :108.92
## Semana Mes Descuento_Porcentaje
## Min. : 1.00 Min. : 1.000 Min. : 0.00
## 1st Qu.:14.00 1st Qu.: 4.000 1st Qu.:60.00
## Median :26.00 Median : 6.000 Median :60.15
## Mean :26.32 Mean : 6.452 Mean :60.83
## 3rd Qu.:39.00 3rd Qu.: 9.000 3rd Qu.:65.28
## Max. :52.00 Max. :12.000 Max. :71.72
## Trx_Fecha Fecha Tiempo
## Min. :2023-01-02 00:00:00.00 Min. :2023-01-01 Min. :0.000e+00
## 1st Qu.:2023-07-05 00:00:00.00 1st Qu.:2023-07-01 1st Qu.:6.983e-05
## Median :2024-01-12 00:00:00.00 Median :2024-01-01 Median :1.408e-04
## Mean :2024-01-05 21:03:56.95 Mean :2023-12-21 Mean :1.366e-04
## 3rd Qu.:2024-07-08 00:00:00.00 3rd Qu.:2024-07-01 3rd Qu.:2.110e-04
## Max. :2024-12-31 00:00:00.00 Max. :2024-12-01 Max. :2.701e-04
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3929788 %>%
select(-Trx_Fecha, -Fecha)
# Ajustar el modelo Random Forest
set.seed(123) # Para reproducibilidad
modelo_rf_3929788 <- randomForest(
Venta ~ .,
data = datos_modelo,
ntree = 500, # Número de árboles
mtry = floor(sqrt(ncol(datos_modelo) - 1)), # Número de variables a considerar en cada split
importance = TRUE # Calcular importancia de variables
)
# Ver resumen del modelo
print(modelo_rf_3929788)
##
## Call:
## randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500, mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE)
## Type of random forest: regression
## Number of trees: 500
## No. of variables tried at each split: 3
##
## Mean of squared residuals: 250101.6
## % Var explained: 97.47
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_3929788, newdata = datos_modelo)
# Calcular métricas
# R² (ya calculado por el modelo)
r2_rf <- 1 - sum((datos_modelo$Venta - predicciones_rf)^2) / sum((datos_modelo$Venta - mean(datos_modelo$Venta))^2)
# AIC aproximado (usando fórmula general para AIC)
n <- nrow(datos_modelo)
k <- length(modelo_rf_3929788$forest$ncat) # Número de variables
mse <- mean((datos_modelo$Venta - predicciones_rf)^2)
aic_rf <- n * log(mse) + 2 * k
# RMSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100
# MSE
mse_rf <- mean((datos_modelo$Venta - predicciones_rf)^2)
# Mostrar las métricas
cat("Modelo Random Forest para producto 3929788\n")
## Modelo Random Forest para producto 3929788
cat("R² del modelo Random Forest:", r2_rf, "\n")
## R² del modelo Random Forest: 0.9955214
cat("AIC aproximado del modelo Random Forest:", aic_rf, "\n")
## AIC aproximado del modelo Random Forest: 148262.6
cat("RMSE del modelo Random Forest:", rmse_rf, "\n")
## RMSE del modelo Random Forest: 210.4673
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 36102.67
cat("MSE del modelo Random Forest:", mse_rf, "\n\n")
## MSE del modelo Random Forest: 44296.49
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3929788)
print(importancia_vars)
## %IncMSE IncNodePurity
## Cant 34.121128 57057463921
## Costo_Venta 38.822387 63786356271
## Costo_Devolucion 18.552797 3951240037
## Ajus_Sistema 3.670044 989088881
## Ajuste_Manual 5.951986 2224439897
## Precio_Lista_Unitario 2.469394 241849791
## Precio_Final_Unitario 4.922754 1684106754
## Semana 3.120466 476825233
## Mes 2.934183 283856526
## Descuento_Porcentaje 5.595171 4728817852
## Tiempo 3.326394 668536745
# Graficar importancia de variables
varImpPlot(modelo_rf_3929788, main = "Importancia de Variables - Producto 3929788")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
Observado = datos_modelo$Venta,
Predicho = predicciones_rf
)
ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
geom_point(alpha = 0.5) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
labs(
title = "Valores Observados vs Predicciones - Producto 3929788",
x = "Ventas Observadas",
y = "Ventas Predichas"
) +
theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores,
main = "Distribución de Errores - Producto 3929788",
xlab = "Error (Observado - Predicho)",
col = "skyblue",
breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), 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",
x = "Ventas Predichas",
y = "Error (Observado - Predicho)"
) +
theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3929788", # Cambia este ID para cada producto
Modelo = "Random Forest",
R2 = r2_rf, # Estas variables son las que se usan en tu código RF
RMSE = rmse_rf,
MAPE = mape_rf,
MSE = mse_rf,
AIC = aic_rf
))
PRODUCTO 3904152
# Crear una variable de tiempo continua basada en la fecha
datos_3904152 <- datos_3904152 %>%
mutate(Fecha = as.Date(floor_date(Trx_Fecha, "month")),
Tiempo = as.numeric(Fecha - min(Fecha)) / (30 * 24 * 60 * 60)) # Tiempo en meses
# Mostrar un resumen de los datos
summary(datos_3904152)
## Venta Cant Costo_Venta Costo_Devolucion
## Min. :-32928 Min. :-12.000 Min. : 0 Min. : 0.00
## 1st Qu.: 3108 1st Qu.: 1.000 1st Qu.: 2324 1st Qu.: 0.00
## Median : 3402 Median : 1.000 Median : 2468 Median : 0.00
## Mean : 7194 Mean : 2.317 Mean : 5593 Mean : 97.01
## 3rd Qu.: 6804 3rd Qu.: 2.000 3rd Qu.: 5104 3rd Qu.: 0.00
## Max. :169344 Max. : 56.000 Max. :131032 Max. :27188.54
## Ajus_Sistema Ajuste_Manual Precio_Lista_Unitario Precio_Final_Unitario
## Min. :-6919 Min. :-6427.40 Min. : 9054 Min. :2677
## 1st Qu.:-6008 1st Qu.: -168.00 1st Qu.: 9054 1st Qu.:3051
## Median :-5946 Median : 0.00 Median : 9054 Median :3108
## Mean :-5976 Mean : -89.87 Mean : 9212 Mean :3158
## 3rd Qu.:-5817 3rd Qu.: 0.00 3rd Qu.: 9054 3rd Qu.:3318
## Max. :-5432 Max. : 475.25 Max. :10066 Max. :3639
## Semana Mes Descuento_Porcentaje
## Min. : 1.00 Min. : 1.000 Min. :60.00
## 1st Qu.:14.00 1st Qu.: 4.000 1st Qu.:64.43
## Median :26.00 Median : 6.000 Median :65.90
## Mean :26.25 Mean : 6.414 Mean :65.71
## 3rd Qu.:38.00 3rd Qu.: 9.000 3rd Qu.:66.60
## Max. :52.00 Max. :12.000 Max. :70.43
## Trx_Fecha Fecha Tiempo
## Min. :2023-01-02 00:00:00.00 Min. :2023-01-01 Min. :0.000e+00
## 1st Qu.:2023-07-11 18:00:00.00 1st Qu.:2023-07-01 1st Qu.:6.983e-05
## Median :2024-01-16 00:00:00.00 Median :2024-01-01 Median :1.408e-04
## Mean :2024-01-06 15:46:14.30 Mean :2023-12-20 Mean :1.365e-04
## 3rd Qu.:2024-06-28 00:00:00.00 3rd Qu.:2024-06-01 3rd Qu.:1.995e-04
## Max. :2024-12-31 00:00:00.00 Max. :2024-12-01 Max. :2.701e-04
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3904152 %>%
select(-Trx_Fecha, -Fecha)
# Ajustar el modelo Random Forest
set.seed(123) # Para reproducibilidad
modelo_rf_3904152 <- randomForest(
Venta ~ .,
data = datos_modelo,
ntree = 500, # Número de árboles
mtry = floor(sqrt(ncol(datos_modelo) - 1)), # Número de variables a considerar en cada split
importance = TRUE # Calcular importancia de variables
)
# Ver resumen del modelo
print(modelo_rf_3904152)
##
## Call:
## randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500, mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE)
## Type of random forest: regression
## Number of trees: 500
## No. of variables tried at each split: 3
##
## Mean of squared residuals: 1947014
## % Var explained: 98.3
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_3904152, newdata = datos_modelo)
# Calcular métricas
# R² (ya calculado por el modelo)
r2_rf <- 1 - sum((datos_modelo$Venta - predicciones_rf)^2) / sum((datos_modelo$Venta - mean(datos_modelo$Venta))^2)
# AIC aproximado (usando fórmula general para AIC)
n <- nrow(datos_modelo)
k <- length(modelo_rf_3904152$forest$ncat) # Número de variables
mse <- mean((datos_modelo$Venta - predicciones_rf)^2)
aic_rf <- n * log(mse) + 2 * k
# RMSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100
# MSE
mse_rf <- mean((datos_modelo$Venta - predicciones_rf)^2)
# Mostrar las métricas
cat("Modelo Random Forest para producto 3904152\n")
## Modelo Random Forest para producto 3904152
cat("R² del modelo Random Forest:", r2_rf, "\n")
## R² del modelo Random Forest: 0.9948033
cat("AIC aproximado del modelo Random Forest:", aic_rf, "\n")
## AIC aproximado del modelo Random Forest: 34812.59
cat("RMSE del modelo Random Forest:", rmse_rf, "\n")
## RMSE del modelo Random Forest: 772.458
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 148033.1
cat("MSE del modelo Random Forest:", mse_rf, "\n\n")
## MSE del modelo Random Forest: 596691.4
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3904152)
print(importancia_vars)
## %IncMSE IncNodePurity
## Cant 31.662621 130502175657
## Costo_Venta 31.868796 133943109052
## Costo_Devolucion 19.853497 5381566068
## Ajus_Sistema 6.335659 2522829430
## Ajuste_Manual 4.692671 5922819788
## Precio_Lista_Unitario 3.806847 582575127
## Precio_Final_Unitario 4.688881 3571577320
## Semana 4.515624 3043837570
## Mes 2.662071 1839345747
## Descuento_Porcentaje 8.168687 6084941317
## Tiempo 3.156113 1764659151
# Graficar importancia de variables
varImpPlot(modelo_rf_3904152, main = "Importancia de Variables - Producto 3904152")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
Observado = datos_modelo$Venta,
Predicho = predicciones_rf
)
ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
geom_point(alpha = 0.5) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
labs(
title = "Valores Observados vs Predicciones - Producto 3904152",
x = "Ventas Observadas",
y = "Ventas Predichas"
) +
theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores,
main = "Distribución de Errores - Producto 3904152",
xlab = "Error (Observado - Predicho)",
col = "skyblue",
breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), 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",
x = "Ventas Predichas",
y = "Error (Observado - Predicho)"
) +
theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3904152", # Cambia este ID para cada producto
Modelo = "Random Forest",
R2 = r2_rf, # Estas variables son las que se usan en tu código RF
RMSE = rmse_rf,
MAPE = mape_rf,
MSE = mse_rf,
AIC = aic_rf
))
PRODUCTO 155002
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155002 %>%
select(-Trx_Fecha, -Fecha)
# Ajustar el modelo Random Forest
set.seed(123) # Para reproducibilidad
modelo_rf_155002 <- randomForest(
Venta ~ .,
data = datos_modelo,
ntree = 500, # Número de árboles
mtry = floor(sqrt(ncol(datos_modelo) - 1)), # Número de variables a considerar en cada split
importance = TRUE # Calcular importancia de variables
)
# Ver resumen del modelo
print(modelo_rf_155002)
##
## Call:
## randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500, mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE)
## Type of random forest: regression
## Number of trees: 500
## No. of variables tried at each split: 3
##
## Mean of squared residuals: 1738939
## % Var explained: 94.22
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_155002, newdata = datos_modelo)
# Calcular métricas
# R² (ya calculado por el modelo)
r2_rf <- 1 - sum((datos_modelo$Venta - predicciones_rf)^2) / sum((datos_modelo$Venta - mean(datos_modelo$Venta))^2)
# AIC aproximado (usando fórmula general para AIC)
n <- nrow(datos_modelo)
k <- length(modelo_rf_155002$forest$ncat) # Número de variables
mse <- mean((datos_modelo$Venta - predicciones_rf)^2)
aic_rf <- n * log(mse) + 2 * k
# RMSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100
# MSE
mse_rf <- mean((datos_modelo$Venta - predicciones_rf)^2)
# Mostrar las métricas
cat("Modelo Random Forest para producto 155002\n")
## Modelo Random Forest para producto 155002
cat("R² del modelo Random Forest:", r2_rf, "\n")
## R² del modelo Random Forest: 0.9871784
cat("AIC aproximado del modelo Random Forest:", aic_rf, "\n")
## AIC aproximado del modelo Random Forest: 75099.98
cat("RMSE del modelo Random Forest:", rmse_rf, "\n")
## RMSE del modelo Random Forest: 620.9267
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 102855.5
cat("MSE del modelo Random Forest:", mse_rf, "\n\n")
## MSE del modelo Random Forest: 385550
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_155002)
print(importancia_vars)
## %IncMSE IncNodePurity
## Cant 34.217643 80225856934
## Costo_Venta 30.176300 71362120510
## Costo_Devolucion 21.877485 4543864694
## Ajus_Sistema 6.233525 1249869555
## Ajuste_Manual 3.441983 1508329290
## Precio_Lista_Unitario 1.952219 557122624
## Precio_Final_Unitario 6.025291 3680114312
## Semana 8.656020 1745284618
## Mes 4.431888 848273585
## Descuento_Porcentaje 7.085270 6167842697
## Tiempo 3.275418 1922858726
# Graficar importancia de variables
varImpPlot(modelo_rf_155002, main = "Importancia de Variables - Producto 155002")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
Observado = datos_modelo$Venta,
Predicho = predicciones_rf
)
ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
geom_point(alpha = 0.5) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
labs(
title = "Valores Observados vs Predicciones - Producto 155002",
x = "Ventas Observadas",
y = "Ventas Predichas"
) +
theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores,
main = "Distribución de Errores - Producto 155002",
xlab = "Error (Observado - Predicho)",
col = "skyblue",
breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), 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",
x = "Ventas Predichas",
y = "Error (Observado - Predicho)"
) +
theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155002", # Cambia este ID para cada producto
Modelo = "Random Forest",
R2 = r2_rf, # Estas variables son las que se usan en tu código RF
RMSE = rmse_rf,
MAPE = mape_rf,
MSE = mse_rf,
AIC = aic_rf
))
PRODUCTO 3678055
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3678055 %>%
select(-Trx_Fecha, -Fecha)
# Ajustar el modelo Random Forest
set.seed(123) # Para reproducibilidad
modelo_rf_3678055 <- randomForest(
Venta ~ .,
data = datos_modelo,
ntree = 500, # Número de árboles
mtry = floor(sqrt(ncol(datos_modelo) - 1)), # Número de variables a considerar en cada split
importance = TRUE # Calcular importancia de variables
)
# Ver resumen del modelo
print(modelo_rf_3678055)
##
## Call:
## randomForest(formula = Venta ~ ., data = datos_modelo, ntree = 500, mtry = floor(sqrt(ncol(datos_modelo) - 1)), importance = TRUE)
## Type of random forest: regression
## Number of trees: 500
## No. of variables tried at each split: 3
##
## Mean of squared residuals: 3884938
## % Var explained: 96.19
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_3678055, newdata = datos_modelo)
# Calcular métricas
# R² (ya calculado por el modelo)
r2_rf <- 1 - sum((datos_modelo$Venta - predicciones_rf)^2) / sum((datos_modelo$Venta - mean(datos_modelo$Venta))^2)
# AIC aproximado (usando fórmula general para AIC)
n <- nrow(datos_modelo)
k <- length(modelo_rf_3678055$forest$ncat) # Número de variables
mse <- mean((datos_modelo$Venta - predicciones_rf)^2)
aic_rf <- n * log(mse) + 2 * k
# RMSE
rmse_rf <- sqrt(mean((datos_modelo$Venta - predicciones_rf)^2))
# MAPE
mape_rf <- mean(abs((datos_modelo$Venta - predicciones_rf) / pmax(datos_modelo$Venta, 0.01))) * 100
# MSE
mse_rf <- mean((datos_modelo$Venta - predicciones_rf)^2)
# Mostrar las métricas
cat("Modelo Random Forest para producto 3678055\n")
## Modelo Random Forest para producto 3678055
cat("R² del modelo Random Forest:", r2_rf, "\n")
## R² del modelo Random Forest: 0.9918288
cat("AIC aproximado del modelo Random Forest:", aic_rf, "\n")
## AIC aproximado del modelo Random Forest: 23374.57
cat("RMSE del modelo Random Forest:", rmse_rf, "\n")
## RMSE del modelo Random Forest: 912.5829
cat("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 441446.6
cat("MSE del modelo Random Forest:", mse_rf, "\n\n")
## MSE del modelo Random Forest: 832807.5
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3678055)
print(importancia_vars)
## %IncMSE IncNodePurity
## Cant 32.721032 70741263686
## Costo_Venta 36.150548 76682691010
## Costo_Devolucion 23.010335 11104899857
## Ajus_Sistema 3.981929 1209957125
## Ajuste_Manual 4.757892 3880187425
## Precio_Lista_Unitario 2.703863 191443598
## Precio_Final_Unitario 4.912317 1756715727
## Semana 6.247012 2661180659
## Mes 6.067506 1094862408
## Descuento_Porcentaje 4.159380 2066915856
## Tiempo 5.415943 1213139729
# Graficar importancia de variables
varImpPlot(modelo_rf_3678055, main = "Importancia de Variables - Producto 3678055")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
Observado = datos_modelo$Venta,
Predicho = predicciones_rf
)
ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
geom_point(alpha = 0.5) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
labs(
title = "Valores Observados vs Predicciones - Producto 3678055",
x = "Ventas Observadas",
y = "Ventas Predichas"
) +
theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores,
main = "Distribución de Errores - Producto 3678055",
xlab = "Error (Observado - Predicho)",
col = "skyblue",
breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_rf, Error = errores), 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",
x = "Ventas Predichas",
y = "Error (Observado - Predicho)"
) +
theme_minimal()

# Guardar métricas de Random Forest para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3678055", # Cambia este ID para cada producto
Modelo = "Random Forest",
R2 = r2_rf, # Estas variables son las que se usan en tu código RF
RMSE = rmse_rf,
MAPE = mape_rf,
MSE = mse_rf,
AIC = aic_rf
))
XGBOOST
PRODUCTO 155001
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155001 %>%
select(-Trx_Fecha, -Fecha)
# Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123) # Para reproducibilidad
indices_train <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
datos_train <- datos_modelo[indices_train, ]
datos_test <- datos_modelo[-indices_train, ]
# Separar variables predictoras y variable objetivo
X_train <- as.matrix(datos_train[, colnames(datos_train) != "Venta"])
y_train <- datos_train$Venta
X_test <- as.matrix(datos_test[, colnames(datos_test) != "Venta"])
y_test <- datos_test$Venta
# Crear matrices DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = X_train, label = y_train)
dtest <- xgb.DMatrix(data = X_test, label = y_test)
# Definir una rejilla completa de hiperparámetros para búsqueda
param_grid <- expand.grid(
eta = c(0.01, 0.05, 0.1, 0.3), # Learning rate
max_depth = c(3, 5, 7, 9), # Profundidad máxima
subsample = c(0.6, 0.8, 1.0), # Submuestra de observaciones
colsample_bytree = c(0.6, 0.8, 1.0), # Submuestra de variables
min_child_weight = c(1, 3, 5), # Peso mínimo en nodos hijos
gamma = c(0, 0.1, 0.3) # Regularización gamma
)
# Mostrar cuántas combinaciones tenemos
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n")
## Número total de combinaciones de hiperparámetros: 1296
# Para este ejemplo, vamos a limitar el número de combinaciones
# Seleccionando un subconjunto aleatorio de combinaciones (20 combinaciones)
set.seed(123)
if (nrow(param_grid) > 20) {
muestra_indices <- sample(1:nrow(param_grid), 20)
param_grid_reducida <- param_grid[muestra_indices, ]
} else {
param_grid_reducida <- param_grid
}
cat("Número de combinaciones a evaluar:", nrow(param_grid_reducida), "\n")
## Número de combinaciones a evaluar: 20
# Función para evaluar un conjunto de hiperparámetros con validación cruzada
evaluate_params <- 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
)
# Realizar validación cruzada
cv_results <- xgb.cv(
params = params,
data = dtrain,
nrounds = 100,
nfold = 5, # 5-fold validación cruzada
early_stopping_rounds = 10,
verbose = 0
)
# Extraer el mejor RMSE y el número óptimo de rondas
best_rmse <- min(cv_results$evaluation_log$test_rmse_mean)
best_nrounds <- which.min(cv_results$evaluation_log$test_rmse_mean)
return(list(rmse = best_rmse, nrounds = best_nrounds, params = params))
}
# Inicializar tabla para almacenar resultados
resultados_grid <- data.frame(
eta = numeric(nrow(param_grid_reducida)),
max_depth = numeric(nrow(param_grid_reducida)),
subsample = numeric(nrow(param_grid_reducida)),
colsample_bytree = numeric(nrow(param_grid_reducida)),
min_child_weight = numeric(nrow(param_grid_reducida)),
gamma = numeric(nrow(param_grid_reducida)),
nrounds = numeric(nrow(param_grid_reducida)),
rmse = numeric(nrow(param_grid_reducida))
)
# Realizar la búsqueda en cuadrícula (esto puede tardar varios minutos)
cat("Iniciando búsqueda en cuadrícula...\n")
## Iniciando búsqueda en cuadrícula...
for (i in 1:nrow(param_grid_reducida)) {
cat(sprintf("Evaluando combinación %d de %d\n", i, nrow(param_grid_reducida)))
# Obtener fila de parámetros actual
params_row <- param_grid_reducida[i, ]
# Evaluar combinación actual
result <- evaluate_params(params_row)
# Guardar resultados
resultados_grid$eta[i] <- params_row$eta
resultados_grid$max_depth[i] <- params_row$max_depth
resultados_grid$subsample[i] <- params_row$subsample
resultados_grid$colsample_bytree[i] <- params_row$colsample_bytree
resultados_grid$min_child_weight[i] <- params_row$min_child_weight
resultados_grid$gamma[i] <- params_row$gamma
resultados_grid$nrounds[i] <- result$nrounds
resultados_grid$rmse[i] <- result$rmse
}
## Evaluando combinación 1 de 20
## Evaluando combinación 2 de 20
## Evaluando combinación 3 de 20
## Evaluando combinación 4 de 20
## Evaluando combinación 5 de 20
## Evaluando combinación 6 de 20
## Evaluando combinación 7 de 20
## Evaluando combinación 8 de 20
## Evaluando combinación 9 de 20
## Evaluando combinación 10 de 20
## Evaluando combinación 11 de 20
## Evaluando combinación 12 de 20
## Evaluando combinación 13 de 20
## Evaluando combinación 14 de 20
## Evaluando combinación 15 de 20
## Evaluando combinación 16 de 20
## Evaluando combinación 17 de 20
## Evaluando combinación 18 de 20
## Evaluando combinación 19 de 20
## Evaluando combinación 20 de 20
# Ordenar resultados por RMSE (de menor a mayor)
resultados_grid <- resultados_grid[order(resultados_grid$rmse), ]
# Mostrar los 5 mejores conjuntos de hiperparámetros
cat("\nLos 5 mejores conjuntos de hiperparámetros:\n")
##
## Los 5 mejores conjuntos de hiperparámetros:
print(head(resultados_grid, 5))
## eta max_depth subsample colsample_bytree min_child_weight gamma nrounds
## 4 0.05 9 1.0 0.8 1 0.1 100
## 6 0.05 7 0.8 0.8 1 0.3 100
## 2 0.10 9 0.8 0.6 1 0.1 100
## 7 0.05 5 1.0 1.0 3 0.3 100
## 14 0.10 3 0.6 0.6 3 0.3 52
## rmse
## 4 1889.618
## 6 1895.167
## 2 2071.500
## 7 2150.840
## 14 2164.858
# Obtener los mejores hiperparámetros
mejores_params <- list(
objective = "reg:squarederror",
eval_metric = "rmse",
eta = resultados_grid$eta[1],
max_depth = resultados_grid$max_depth[1],
subsample = resultados_grid$subsample[1],
colsample_bytree = resultados_grid$colsample_bytree[1],
min_child_weight = resultados_grid$min_child_weight[1],
gamma = resultados_grid$gamma[1]
)
mejor_nrounds <- resultados_grid$nrounds[1]
cat("\nMejores hiperparámetros encontrados:\n")
##
## Mejores hiperparámetros encontrados:
print(mejores_params)
## $objective
## [1] "reg:squarederror"
##
## $eval_metric
## [1] "rmse"
##
## $eta
## [1] 0.05
##
## $max_depth
## [1] 9
##
## $subsample
## [1] 1
##
## $colsample_bytree
## [1] 0.8
##
## $min_child_weight
## [1] 1
##
## $gamma
## [1] 0.1
cat("Número óptimo de rondas:", mejor_nrounds, "\n")
## Número óptimo de rondas: 100
cat("RMSE en validación cruzada:", resultados_grid$rmse[1], "\n\n")
## RMSE en validación cruzada: 1889.618
# Entrenar el modelo final con los mejores hiperparámetros
modelo_xgb_155001 <- xgb.train(
params = mejores_params,
data = dtrain,
nrounds = mejor_nrounds,
watchlist = list(train = dtrain, test = dtest),
verbose = 0
)
# Hacer predicciones en el conjunto de prueba
predicciones_test <- predict(modelo_xgb_155001, dtest)
# Calcular métricas en el conjunto de prueba
# R²
r2_test <- 1 - sum((y_test - predicciones_test)^2) / sum((y_test - mean(y_test))^2)
# RMSE
rmse_test <- sqrt(mean((y_test - predicciones_test)^2))
# MAPE
mape_test <- mean(abs((y_test - predicciones_test) / pmax(y_test, 0.01))) * 100
# MSE
mse_test <- mean((y_test - predicciones_test)^2)
# AIC aproximado
n <- length(y_test)
k <- length(mejores_params) + 1 # +1 por el número de rondas
aic_test <- n * log(mse_test) + 2 * k
# Mostrar las métricas en el conjunto de prueba
cat("Métricas en el conjunto de prueba:\n")
## Métricas en el conjunto de prueba:
cat("R² del modelo XGBoost:", r2_test, "\n")
## R² del modelo XGBoost: 0.993792
cat("AIC aproximado del modelo XGBoost:", aic_test, "\n")
## AIC aproximado del modelo XGBoost: 21226.88
cat("RMSE del modelo XGBoost:", rmse_test, "\n")
## RMSE del modelo XGBoost: 459.314
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 71065.79
cat("MSE del modelo XGBoost:", mse_test, "\n\n")
## MSE del modelo XGBoost: 210969.4
# Ahora hacer predicciones en el conjunto completo para comparabilidad con otros modelos
X_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completas <- predict(modelo_xgb_155001, X_completo)
# Calcular métricas en el conjunto completo
# R²
r2_completo <- 1 - sum((datos_modelo$Venta - predicciones_completas)^2) / sum((datos_modelo$Venta - mean(datos_modelo$Venta))^2)
# RMSE
rmse_completo <- sqrt(mean((datos_modelo$Venta - predicciones_completas)^2))
# MAPE
mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completas) / pmax(datos_modelo$Venta, 0.01))) * 100
# MSE
mse_completo <- mean((datos_modelo$Venta - predicciones_completas)^2)
# AIC aproximado
n <- nrow(datos_modelo)
aic_completo <- n * log(mse_completo) + 2 * k
# Mostrar las métricas en el conjunto completo
cat("Métricas en el conjunto completo:\n")
## Métricas en el conjunto completo:
cat("R² del modelo XGBoost:", r2_completo, "\n")
## R² del modelo XGBoost: 0.9975129
cat("AIC aproximado del modelo XGBoost:", aic_completo, "\n")
## AIC aproximado del modelo XGBoost: 102085.3
cat("RMSE del modelo XGBoost:", rmse_completo, "\n")
## RMSE del modelo XGBoost: 362.0098
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 53774.38
cat("MSE del modelo XGBoost:", mse_completo, "\n\n")
## MSE del modelo XGBoost: 131051.1
# Importancia de variables
importancia <- xgb.importance(
feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
model = modelo_xgb_155001
)
print(importancia)
## Feature Gain Cover Frequency
## <char> <num> <num> <num>
## 1: Costo_Venta 6.591562e-01 0.2778634347 0.208883994
## 2: Cant 2.774472e-01 0.4172850209 0.243392070
## 3: Costo_Devolucion 5.669484e-02 0.0869725188 0.037812041
## 4: Precio_Final_Unitario 2.640087e-03 0.1340855199 0.209251101
## 5: Tiempo 2.006129e-03 0.0240836191 0.057268722
## 6: Descuento_Porcentaje 7.677418e-04 0.0409373923 0.096182085
## 7: Semana 7.406854e-04 0.0068938659 0.062591777
## 8: Ajuste_Manual 3.313046e-04 0.0027406577 0.035976505
## 9: Ajus_Sistema 1.653475e-04 0.0072293568 0.033406755
## 10: Mes 2.760861e-05 0.0015509527 0.012114537
## 11: Precio_Lista_Unitario 2.282459e-05 0.0003576614 0.003120411
# Graficar importancia de variables
xgb.plot.importance(importance_matrix = importancia,
main = "Importancia de Variables - Producto 155001 (XGBoost)")

# Crear gráfico de valores observados vs predicciones
datos_grafico <- data.frame(
Observado = datos_modelo$Venta,
Predicho = predicciones_completas
)
ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
geom_point(alpha = 0.5) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
labs(
title = "Valores Observados vs Predicciones - Producto 155001 (XGBoost)",
x = "Ventas Observadas",
y = "Ventas Predichas"
) +
theme_minimal()

# Análisis del error
errores <- datos_grafico$Observado - datos_grafico$Predicho
hist(errores,
main = "Distribución de Errores - Producto 155001 (XGBoost)",
xlab = "Error (Observado - Predicho)",
col = "skyblue",
breaks = 30)

# Gráfico del error vs predicción
ggplot(data.frame(Predicho = predicciones_completas, Error = errores), 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 (XGBoost)",
x = "Ventas Predichas",
y = "Error (Observado - Predicho)"
) +
theme_minimal()

# Guardar métricas de XGBoost para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155001", # Cambia este ID para cada producto
Modelo = "XGBoost",
R2 = r2_completo, # Usamos las métricas del conjunto completo
RMSE = rmse_completo,
MAPE = mape_completo,
MSE = mse_completo,
AIC = aic_completo
))
PRODUCTO 3929788
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3929788 %>%
select(-Trx_Fecha, -Fecha)
# Paso 2: Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123) # Para reproducibilidad
train_index <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
train_data <- datos_modelo[train_index, ]
test_data <- datos_modelo[-train_index, ]
# Preparar matrices para XGBoost
train_x <- as.matrix(train_data[, colnames(train_data) != "Venta"])
train_y <- train_data$Venta
test_x <- as.matrix(test_data[, colnames(test_data) != "Venta"])
test_y <- test_data$Venta
# Crear DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)
# Paso 3: Definir la rejilla de hiperparámetros para Grid Search
param_grid <- expand.grid(
eta = c(0.01, 0.05, 0.1, 0.3), # Learning rate
max_depth = c(3, 6, 9), # Profundidad máxima
min_child_weight = c(1, 3, 5), # Peso mínimo de nodo hijo
subsample = c(0.7, 0.9), # Proporción de observaciones
colsample_bytree = c(0.7, 0.9), # Proporción de variables
gamma = c(0, 0.1, 0.3) # Regularización gamma
)
# Mostrar dimensiones de la rejilla
cat("Grid Search para XGBoost - Producto 3929788\n")
## Grid Search para XGBoost - Producto 3929788
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Para este ejemplo, limitar a 12 combinaciones para ahorrar tiempo
# En un escenario real, podrías evaluar todas o usar una estrategia más eficiente
set.seed(456)
if (nrow(param_grid) > 12) {
selected_indices <- sample(1:nrow(param_grid), 12)
param_grid <- param_grid[selected_indices, ]
cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 4: Implementar Grid Search
resultados <- data.frame()
cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid)) {
# Extraer parámetros de la combinación actual
params <- list(
objective = "reg:squarederror", # Objetivo de regresión
eval_metric = "rmse", # Métrica de evaluación
eta = param_grid$eta[i],
max_depth = param_grid$max_depth[i],
min_child_weight = param_grid$min_child_weight[i],
subsample = param_grid$subsample[i],
colsample_bytree = param_grid$colsample_bytree[i],
gamma = param_grid$gamma[i]
)
cat("Evaluando combinación", i, "de", nrow(param_grid), ":\n")
cat(" eta =", params$eta,
", max_depth =", params$max_depth,
", min_child_weight =", params$min_child_weight,
", subsample =", params$subsample,
", colsample_bytree =", params$colsample_bytree,
", gamma =", params$gamma, "\n")
# Validación cruzada para encontrar el número óptimo de iteraciones
cv_model <- xgb.cv(
params = params,
data = dtrain,
nrounds = 200, # Máximo número de iteraciones
nfold = 5, # 5-fold validación cruzada
early_stopping_rounds = 20, # Detener si no hay mejora en 20 rondas
verbose = 0 # Suprimir mensajes
)
# Extraer mejor iteración y su RMSE
best_iteration <- cv_model$best_iteration
best_rmse <- min(cv_model$evaluation_log$test_rmse_mean)
cat(" Mejor iteración:", best_iteration, "\n")
cat(" RMSE en validación cruzada:", best_rmse, "\n\n")
# Guardar resultados
resultado_actual <- data.frame(
eta = params$eta,
max_depth = params$max_depth,
min_child_weight = params$min_child_weight,
subsample = params$subsample,
colsample_bytree = params$colsample_bytree,
gamma = params$gamma,
nrounds = best_iteration,
rmse_cv = best_rmse
)
resultados <- rbind(resultados, resultado_actual)
}
## Evaluando combinación 1 de 12 :
## eta = 0.01 , max_depth = 9 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1
## Mejor iteración: 200
## RMSE en validación cruzada: 751.2185
##
## Evaluando combinación 2 de 12 :
## eta = 0.1 , max_depth = 9 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.3
## Mejor iteración: 70
## RMSE en validación cruzada: 424.3901
##
## Evaluando combinación 3 de 12 :
## eta = 0.05 , max_depth = 3 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0
## Mejor iteración: 200
## RMSE en validación cruzada: 211.6451
##
## Evaluando combinación 4 de 12 :
## eta = 0.3 , max_depth = 9 , min_child_weight = 5 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1
## Mejor iteración: 28
## RMSE en validación cruzada: 409.3103
##
## Evaluando combinación 5 de 12 :
## eta = 0.1 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1
## Mejor iteración: 55
## RMSE en validación cruzada: 405.7941
##
## Evaluando combinación 6 de 12 :
## eta = 0.01 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1
## Mejor iteración: 200
## RMSE en validación cruzada: 760.2486
##
## Evaluando combinación 7 de 12 :
## eta = 0.05 , max_depth = 3 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.1
## Mejor iteración: 192
## RMSE en validación cruzada: 472.5772
##
## Evaluando combinación 8 de 12 :
## eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.7 , gamma = 0.1
## Mejor iteración: 136
## RMSE en validación cruzada: 401.7257
##
## Evaluando combinación 9 de 12 :
## eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.3
## Mejor iteración: 197
## RMSE en validación cruzada: 420.6656
##
## Evaluando combinación 10 de 12 :
## eta = 0.1 , max_depth = 9 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3
## Mejor iteración: 200
## RMSE en validación cruzada: 370.6516
##
## Evaluando combinación 11 de 12 :
## eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0
## Mejor iteración: 131
## RMSE en validación cruzada: 373.5056
##
## Evaluando combinación 12 de 12 :
## eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3
## Mejor iteración: 89
## RMSE en validación cruzada: 457.2215
# Ordenar resultados por RMSE (de menor a mayor)
resultados <- resultados[order(resultados$rmse_cv), ]
# Paso 5: Mostrar resultados del Grid Search
cat("Resultados del Grid Search ordenados por RMSE:\n")
## Resultados del Grid Search ordenados por RMSE:
print(resultados)
## eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 3 0.05 3 1 0.9 0.7 0.0 200
## 10 0.10 9 1 0.9 0.7 0.3 200
## 11 0.05 6 3 0.7 0.9 0.0 131
## 8 0.10 3 3 0.7 0.7 0.1 136
## 5 0.10 6 5 0.9 0.9 0.1 55
## 4 0.30 9 5 0.7 0.9 0.1 28
## 9 0.05 6 3 0.7 0.9 0.3 197
## 2 0.10 9 3 0.9 0.9 0.3 70
## 12 0.10 3 3 0.9 0.7 0.3 89
## 7 0.05 3 5 0.9 0.7 0.1 192
## 1 0.01 9 3 0.7 0.9 0.1 200
## 6 0.01 6 5 0.9 0.9 0.1 200
## rmse_cv
## 3 211.6451
## 10 370.6516
## 11 373.5056
## 8 401.7257
## 5 405.7941
## 4 409.3103
## 9 420.6656
## 2 424.3901
## 12 457.2215
## 7 472.5772
## 1 751.2185
## 6 760.2486
# Visualizar resultados del Grid Search
ggplot(resultados, aes(x = reorder(paste("Comb", 1:nrow(resultados)), rmse_cv), y = rmse_cv)) +
geom_bar(stat = "identity", fill = "steelblue") +
labs(
title = "Resultados del Grid Search - Producto 3929788",
x = "Combinación de Hiperparámetros",
y = "RMSE en Validación Cruzada"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Paso 6: Seleccionar los mejores hiperparámetros
mejores_params <- list(
objective = "reg:squarederror",
eval_metric = "rmse",
eta = resultados$eta[1],
max_depth = resultados$max_depth[1],
min_child_weight = resultados$min_child_weight[1],
subsample = resultados$subsample[1],
colsample_bytree = resultados$colsample_bytree[1],
gamma = resultados$gamma[1]
)
mejor_nrounds <- resultados$nrounds[1]
cat("\nMejores hiperparámetros encontrados:\n")
##
## Mejores hiperparámetros encontrados:
print(mejores_params)
## $objective
## [1] "reg:squarederror"
##
## $eval_metric
## [1] "rmse"
##
## $eta
## [1] 0.05
##
## $max_depth
## [1] 3
##
## $min_child_weight
## [1] 1
##
## $subsample
## [1] 0.9
##
## $colsample_bytree
## [1] 0.7
##
## $gamma
## [1] 0
cat("Número óptimo de rondas:", mejor_nrounds, "\n\n")
## Número óptimo de rondas: 200
# Paso 7: Entrenar el modelo final con los mejores hiperparámetros
cat("Entrenando modelo final con los mejores hiperparámetros...\n")
## Entrenando modelo final con los mejores hiperparámetros...
modelo_final <- xgb.train(
params = mejores_params,
data = dtrain,
nrounds = mejor_nrounds,
watchlist = list(train = dtrain, test = dtest),
verbose = 0
)
# GUARDAR EL MODELO CON NOMBRE ESPERADO
modelo_xgb_3929788 <- modelo_final
# Paso 8: Evaluar el modelo
# Predicciones en conjunto de prueba
predicciones_test <- predict(modelo_final, dtest)
# Calcular métricas
# R²
r2_test <- 1 - sum((test_y - predicciones_test)^2) / sum((test_y - mean(test_y))^2)
# RMSE
rmse_test <- sqrt(mean((test_y - predicciones_test)^2))
# MAPE
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100
# MSE
mse_test <- mean((test_y - predicciones_test)^2)
# AIC aproximado
n_test <- length(test_y)
k <- length(mejores_params) + 1 # +1 por número de iteraciones
aic_test <- n_test * log(mse_test) + 2 * k
# Mostrar métricas en conjunto de prueba
cat("\nMétricas en conjunto de prueba:\n")
##
## Métricas en conjunto de prueba:
cat("R² del modelo XGBoost:", r2_test, "\n")
## R² del modelo XGBoost: 0.9966196
cat("AIC aproximado del modelo XGBoost:", aic_test, "\n")
## AIC aproximado del modelo XGBoost: 27939.83
cat("RMSE del modelo XGBoost:", rmse_test, "\n")
## RMSE del modelo XGBoost: 154.4765
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 16396.45
cat("MSE del modelo XGBoost:", mse_test, "\n\n")
## MSE del modelo XGBoost: 23863
# Paso 9: Predicciones en el conjunto completo
# Hacer predicciones en todo el conjunto de datos para comparación con otros modelos
x_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completo <- predict(modelo_final, x_completo)
# Calcular métricas en conjunto completo
r2_completo <- 1 - sum((datos_modelo$Venta - predicciones_completo)^2) /
sum((datos_modelo$Venta - mean(datos_modelo$Venta))^2)
rmse_completo <- sqrt(mean((datos_modelo$Venta - predicciones_completo)^2))
mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completo) /
pmax(datos_modelo$Venta, 0.01))) * 100
mse_completo <- mean((datos_modelo$Venta - predicciones_completo)^2)
n_completo <- nrow(datos_modelo)
aic_completo <- n_completo * log(mse_completo) + 2 * k
# Mostrar métricas en conjunto completo
cat("Métricas en conjunto completo:\n")
## Métricas en conjunto completo:
cat("R² del modelo XGBoost:", r2_completo, "\n")
## R² del modelo XGBoost: 0.998458
cat("AIC aproximado del modelo XGBoost:", aic_completo, "\n")
## AIC aproximado del modelo XGBoost: 133485.3
cat("RMSE del modelo XGBoost:", rmse_completo, "\n")
## RMSE del modelo XGBoost: 123.4982
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 13959.96
cat("MSE del modelo XGBoost:", mse_completo, "\n\n")
## MSE del modelo XGBoost: 15251.79
# Paso 10: Análisis de importancia de variables
# Calcular importancia de variables
importancia <- xgb.importance(
feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
model = modelo_final
)
# Mostrar importancia de variables
cat("Importancia de variables:\n")
## Importancia de variables:
print(importancia)
## Feature Gain Cover Frequency
## <char> <num> <num> <num>
## 1: Costo_Venta 7.123661e-01 3.317789e-01 0.284259984
## 2: Cant 2.198062e-01 2.826904e-01 0.261550509
## 3: Descuento_Porcentaje 3.938096e-02 1.609342e-01 0.158966327
## 4: Costo_Devolucion 2.203304e-02 9.260983e-02 0.078308536
## 5: Precio_Final_Unitario 2.343646e-03 5.262014e-02 0.080657792
## 6: Ajuste_Manual 1.891911e-03 5.959906e-02 0.049334377
## 7: Tiempo 1.359638e-03 1.769412e-03 0.018010963
## 8: Ajus_Sistema 5.389589e-04 1.694105e-02 0.034455756
## 9: Mes 1.386984e-04 3.691844e-05 0.004698512
## 10: Semana 1.039325e-04 1.475067e-04 0.024275646
## 11: Precio_Lista_Unitario 3.696803e-05 8.726785e-04 0.005481597
# Graficar importancia de variables
xgb.plot.importance(importance_matrix = importancia,
main = "Importancia de Variables - Producto 3929788 (XGBoost)")

# Paso 11: Visualizaciones para evaluación
# Gráfico 1: Valores observados vs predichos
datos_grafico <- data.frame(
Observado = datos_modelo$Venta,
Predicho = predicciones_completo
)
ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
geom_point(alpha = 0.5) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
labs(
title = "Valores Observados vs Predicciones - Producto 3929788 (XGBoost)",
x = "Ventas Observadas",
y = "Ventas Predichas"
) +
theme_minimal()

# Gráfico 2: Análisis de residuos
errores <- datos_modelo$Venta - predicciones_completo
# Histograma de errores
hist(errores,
main = "Distribución de Errores - Producto 3929788 (XGBoost)",
xlab = "Error (Observado - Predicho)",
col = "skyblue",
breaks = 30)

# Gráfico 3: Errores vs Predicciones
ggplot(data.frame(Predicho = predicciones_completo, Error = errores), 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 (XGBoost)",
x = "Ventas Predichas",
y = "Error (Observado - Predicho)"
) +
theme_minimal()

# Guardar métricas de XGBoost para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3929788", # Cambia este ID para cada producto
Modelo = "XGBoost",
R2 = r2_completo, # Usamos las métricas del conjunto completo
RMSE = rmse_completo,
MAPE = mape_completo,
MSE = mse_completo,
AIC = aic_completo
))
PRODUCTO 3904152
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3904152 %>%
select(-Trx_Fecha, -Fecha)
# Paso 2: Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123) # Para reproducibilidad
train_index <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
train_data <- datos_modelo[train_index, ]
test_data <- datos_modelo[-train_index, ]
# Preparar matrices para XGBoost
train_x <- as.matrix(train_data[, colnames(train_data) != "Venta"])
train_y <- train_data$Venta
test_x <- as.matrix(test_data[, colnames(test_data) != "Venta"])
test_y <- test_data$Venta
# Crear DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)
# Paso 3: Definir la rejilla de hiperparámetros para Grid Search
param_grid <- expand.grid(
eta = c(0.01, 0.05, 0.1, 0.3), # Learning rate
max_depth = c(3, 6, 9), # Profundidad máxima
min_child_weight = c(1, 3, 5), # Peso mínimo de nodo hijo
subsample = c(0.7, 0.9), # Proporción de observaciones
colsample_bytree = c(0.7, 0.9), # Proporción de variables
gamma = c(0, 0.1, 0.3) # Regularización gamma
)
# Mostrar dimensiones de la rejilla
cat("Grid Search para XGBoost - Producto 3904152\n")
## Grid Search para XGBoost - Producto 3904152
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Para este ejemplo, limitar a 12 combinaciones para ahorrar tiempo
# En un escenario real, podrías evaluar todas o usar una estrategia más eficiente
set.seed(456)
if (nrow(param_grid) > 12) {
selected_indices <- sample(1:nrow(param_grid), 12)
param_grid <- param_grid[selected_indices, ]
cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 4: Implementar Grid Search
resultados <- data.frame()
cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid)) {
# Extraer parámetros de la combinación actual
params <- list(
objective = "reg:squarederror", # Objetivo de regresión
eval_metric = "rmse", # Métrica de evaluación
eta = param_grid$eta[i],
max_depth = param_grid$max_depth[i],
min_child_weight = param_grid$min_child_weight[i],
subsample = param_grid$subsample[i],
colsample_bytree = param_grid$colsample_bytree[i],
gamma = param_grid$gamma[i]
)
cat("Evaluando combinación", i, "de", nrow(param_grid), ":\n")
cat(" eta =", params$eta,
", max_depth =", params$max_depth,
", min_child_weight =", params$min_child_weight,
", subsample =", params$subsample,
", colsample_bytree =", params$colsample_bytree,
", gamma =", params$gamma, "\n")
# Validación cruzada para encontrar el número óptimo de iteraciones
cv_model <- xgb.cv(
params = params,
data = dtrain,
nrounds = 200, # Máximo número de iteraciones
nfold = 5, # 5-fold validación cruzada
early_stopping_rounds = 20, # Detener si no hay mejora en 20 rondas
verbose = 0 # Suprimir mensajes
)
# Extraer mejor iteración y su RMSE
best_iteration <- cv_model$best_iteration
best_rmse <- min(cv_model$evaluation_log$test_rmse_mean)
cat(" Mejor iteración:", best_iteration, "\n")
cat(" RMSE en validación cruzada:", best_rmse, "\n\n")
# Guardar resultados
resultado_actual <- data.frame(
eta = params$eta,
max_depth = params$max_depth,
min_child_weight = params$min_child_weight,
subsample = params$subsample,
colsample_bytree = params$colsample_bytree,
gamma = params$gamma,
nrounds = best_iteration,
rmse_cv = best_rmse
)
resultados <- rbind(resultados, resultado_actual)
}
## Evaluando combinación 1 de 12 :
## eta = 0.01 , max_depth = 9 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1
## Mejor iteración: 200
## RMSE en validación cruzada: 2669.012
##
## Evaluando combinación 2 de 12 :
## eta = 0.1 , max_depth = 9 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.3
## Mejor iteración: 110
## RMSE en validación cruzada: 870.9675
##
## Evaluando combinación 3 de 12 :
## eta = 0.05 , max_depth = 3 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0
## Mejor iteración: 200
## RMSE en validación cruzada: 771.561
##
## Evaluando combinación 4 de 12 :
## eta = 0.3 , max_depth = 9 , min_child_weight = 5 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1
## Mejor iteración: 53
## RMSE en validación cruzada: 1534.337
##
## Evaluando combinación 5 de 12 :
## eta = 0.1 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1
## Mejor iteración: 78
## RMSE en validación cruzada: 1766.884
##
## Evaluando combinación 6 de 12 :
## eta = 0.01 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1
## Mejor iteración: 200
## RMSE en validación cruzada: 2739.709
##
## Evaluando combinación 7 de 12 :
## eta = 0.05 , max_depth = 3 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.1
## Mejor iteración: 156
## RMSE en validación cruzada: 1421.265
##
## Evaluando combinación 8 de 12 :
## eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.7 , gamma = 0.1
## Mejor iteración: 115
## RMSE en validación cruzada: 1033.285
##
## Evaluando combinación 9 de 12 :
## eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.3
## Mejor iteración: 200
## RMSE en validación cruzada: 932.666
##
## Evaluando combinación 10 de 12 :
## eta = 0.1 , max_depth = 9 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3
## Mejor iteración: 200
## RMSE en validación cruzada: 883.0343
##
## Evaluando combinación 11 de 12 :
## eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0
## Mejor iteración: 196
## RMSE en validación cruzada: 1016.626
##
## Evaluando combinación 12 de 12 :
## eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3
## Mejor iteración: 200
## RMSE en validación cruzada: 888.3243
# Ordenar resultados por RMSE (de menor a mayor)
resultados <- resultados[order(resultados$rmse_cv), ]
# Paso 5: Mostrar resultados del Grid Search
cat("Resultados del Grid Search ordenados por RMSE:\n")
## Resultados del Grid Search ordenados por RMSE:
print(resultados)
## eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 3 0.05 3 1 0.9 0.7 0.0 200
## 2 0.10 9 3 0.9 0.9 0.3 110
## 10 0.10 9 1 0.9 0.7 0.3 200
## 12 0.10 3 3 0.9 0.7 0.3 200
## 9 0.05 6 3 0.7 0.9 0.3 200
## 11 0.05 6 3 0.7 0.9 0.0 196
## 8 0.10 3 3 0.7 0.7 0.1 115
## 7 0.05 3 5 0.9 0.7 0.1 156
## 4 0.30 9 5 0.7 0.9 0.1 53
## 5 0.10 6 5 0.9 0.9 0.1 78
## 1 0.01 9 3 0.7 0.9 0.1 200
## 6 0.01 6 5 0.9 0.9 0.1 200
## rmse_cv
## 3 771.5610
## 2 870.9675
## 10 883.0343
## 12 888.3243
## 9 932.6660
## 11 1016.6261
## 8 1033.2845
## 7 1421.2648
## 4 1534.3370
## 5 1766.8844
## 1 2669.0122
## 6 2739.7087
# Visualizar resultados del Grid Search
ggplot(resultados, aes(x = reorder(paste("Comb", 1:nrow(resultados)), rmse_cv), y = rmse_cv)) +
geom_bar(stat = "identity", fill = "steelblue") +
labs(
title = "Resultados del Grid Search - Producto 3904152",
x = "Combinación de Hiperparámetros",
y = "RMSE en Validación Cruzada"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Paso 6: Seleccionar los mejores hiperparámetros
mejores_params <- list(
objective = "reg:squarederror",
eval_metric = "rmse",
eta = resultados$eta[1],
max_depth = resultados$max_depth[1],
min_child_weight = resultados$min_child_weight[1],
subsample = resultados$subsample[1],
colsample_bytree = resultados$colsample_bytree[1],
gamma = resultados$gamma[1]
)
mejor_nrounds <- resultados$nrounds[1]
cat("\nMejores hiperparámetros encontrados:\n")
##
## Mejores hiperparámetros encontrados:
print(mejores_params)
## $objective
## [1] "reg:squarederror"
##
## $eval_metric
## [1] "rmse"
##
## $eta
## [1] 0.05
##
## $max_depth
## [1] 3
##
## $min_child_weight
## [1] 1
##
## $subsample
## [1] 0.9
##
## $colsample_bytree
## [1] 0.7
##
## $gamma
## [1] 0
cat("Número óptimo de rondas:", mejor_nrounds, "\n\n")
## Número óptimo de rondas: 200
# Paso 7: Entrenar el modelo final con los mejores hiperparámetros
cat("Entrenando modelo final con los mejores hiperparámetros...\n")
## Entrenando modelo final con los mejores hiperparámetros...
modelo_final <- xgb.train(
params = mejores_params,
data = dtrain,
nrounds = mejor_nrounds,
watchlist = list(train = dtrain, test = dtest),
verbose = 0
)
modelo_xgb_3904152 <- modelo_final
# Paso 8: Evaluar el modelo
# Predicciones en conjunto de prueba
predicciones_test <- predict(modelo_final, dtest)
# Calcular métricas
# R²
r2_test <- 1 - sum((test_y - predicciones_test)^2) / sum((test_y - mean(test_y))^2)
# RMSE
rmse_test <- sqrt(mean((test_y - predicciones_test)^2))
# MAPE
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100
# MSE
mse_test <- mean((test_y - predicciones_test)^2)
# AIC aproximado
n_test <- length(test_y)
k <- length(mejores_params) + 1 # +1 por número de iteraciones
aic_test <- n_test * log(mse_test) + 2 * k
# Mostrar métricas en conjunto de prueba
cat("\nMétricas en conjunto de prueba:\n")
##
## Métricas en conjunto de prueba:
cat("R² del modelo XGBoost:", r2_test, "\n")
## R² del modelo XGBoost: 0.9972744
cat("AIC aproximado del modelo XGBoost:", aic_test, "\n")
## AIC aproximado del modelo XGBoost: 6589.783
cat("RMSE del modelo XGBoost:", rmse_test, "\n")
## RMSE del modelo XGBoost: 541.7537
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 107031.1
cat("MSE del modelo XGBoost:", mse_test, "\n\n")
## MSE del modelo XGBoost: 293497.1
# Paso 9: Predicciones en el conjunto completo
# Hacer predicciones en todo el conjunto de datos para comparación con otros modelos
x_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completo <- predict(modelo_final, x_completo)
# Calcular métricas en conjunto completo
r2_completo <- 1 - sum((datos_modelo$Venta - predicciones_completo)^2) /
sum((datos_modelo$Venta - mean(datos_modelo$Venta))^2)
rmse_completo <- sqrt(mean((datos_modelo$Venta - predicciones_completo)^2))
mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completo) /
pmax(datos_modelo$Venta, 0.01))) * 100
mse_completo <- mean((datos_modelo$Venta - predicciones_completo)^2)
n_completo <- nrow(datos_modelo)
aic_completo <- n_completo * log(mse_completo) + 2 * k
# Mostrar métricas en conjunto completo
cat("Métricas en conjunto completo:\n")
## Métricas en conjunto completo:
cat("R² del modelo XGBoost:", r2_completo, "\n")
## R² del modelo XGBoost: 0.9991788
cat("AIC aproximado del modelo XGBoost:", aic_completo, "\n")
## AIC aproximado del modelo XGBoost: 29982.01
cat("RMSE del modelo XGBoost:", rmse_completo, "\n")
## RMSE del modelo XGBoost: 307.067
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 62039.2
cat("MSE del modelo XGBoost:", mse_completo, "\n\n")
## MSE del modelo XGBoost: 94290.15
# Paso 10: Análisis de importancia de variables
# Calcular importancia de variables
importancia <- xgb.importance(
feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
model = modelo_final
)
# Mostrar importancia de variables
cat("Importancia de variables:\n")
## Importancia de variables:
print(importancia)
## Feature Gain Cover Frequency
## <char> <num> <num> <num>
## 1: Costo_Venta 4.958624e-01 0.306705901 0.261395349
## 2: Cant 4.845957e-01 0.371796393 0.381395349
## 3: Ajuste_Manual 1.030077e-02 0.053372344 0.065116279
## 4: Costo_Devolucion 7.596802e-03 0.090740600 0.097674419
## 5: Precio_Final_Unitario 1.281356e-03 0.091286968 0.091162791
## 6: Descuento_Porcentaje 1.251617e-04 0.044511784 0.038139535
## 7: Ajus_Sistema 7.973488e-05 0.005084683 0.013953488
## 8: Precio_Lista_Unitario 5.646915e-05 0.004122117 0.007441860
## 9: Tiempo 4.569661e-05 0.028213702 0.030697674
## 10: Mes 3.530739e-05 0.002225324 0.006511628
## 11: Semana 2.059710e-05 0.001940185 0.006511628
# Graficar importancia de variables
xgb.plot.importance(importance_matrix = importancia,
main = "Importancia de Variables - Producto 3904152 (XGBoost)")

# Paso 11: Visualizaciones para evaluación
# Gráfico 1: Valores observados vs predichos
datos_grafico <- data.frame(
Observado = datos_modelo$Venta,
Predicho = predicciones_completo
)
ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
geom_point(alpha = 0.5) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
labs(
title = "Valores Observados vs Predicciones - Producto 3904152 (XGBoost)",
x = "Ventas Observadas",
y = "Ventas Predichas"
) +
theme_minimal()

# Gráfico 2: Análisis de residuos
errores <- datos_modelo$Venta - predicciones_completo
# Histograma de errores
hist(errores,
main = "Distribución de Errores - Producto 3904152 (XGBoost)",
xlab = "Error (Observado - Predicho)",
col = "skyblue",
breaks = 30)

# Gráfico 3: Errores vs Predicciones
ggplot(data.frame(Predicho = predicciones_completo, Error = errores), 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 (XGBoost)",
x = "Ventas Predichas",
y = "Error (Observado - Predicho)"
) +
theme_minimal()

# Guardar métricas de XGBoost para producto
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3904152", # Cambia este ID para cada producto
Modelo = "XGBoost",
R2 = r2_completo, # Usamos las métricas del conjunto completo
RMSE = rmse_completo,
MAPE = mape_completo,
MSE = mse_completo,
AIC = aic_completo
))
PRODUCTO 155002
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_155002 %>%
select(-Trx_Fecha, -Fecha)
# Paso 2: Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123) # Para reproducibilidad
train_index <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
train_data <- datos_modelo[train_index, ]
test_data <- datos_modelo[-train_index, ]
# Preparar matrices para XGBoost
train_x <- as.matrix(train_data[, colnames(train_data) != "Venta"])
train_y <- train_data$Venta
test_x <- as.matrix(test_data[, colnames(test_data) != "Venta"])
test_y <- test_data$Venta
# Crear DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)
# Paso 3: Definir la rejilla de hiperparámetros para Grid Search
param_grid <- expand.grid(
eta = c(0.01, 0.05, 0.1, 0.3), # Learning rate
max_depth = c(3, 6, 9), # Profundidad máxima
min_child_weight = c(1, 3, 5), # Peso mínimo de nodo hijo
subsample = c(0.7, 0.9), # Proporción de observaciones
colsample_bytree = c(0.7, 0.9), # Proporción de variables
gamma = c(0, 0.1, 0.3) # Regularización gamma
)
# Mostrar dimensiones de la rejilla
cat("Grid Search para XGBoost - Producto 155002\n")
## Grid Search para XGBoost - Producto 155002
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Para este ejemplo, limitar a 12 combinaciones para ahorrar tiempo
# En un escenario real, podrías evaluar todas o usar una estrategia más eficiente
set.seed(456)
if (nrow(param_grid) > 12) {
selected_indices <- sample(1:nrow(param_grid), 12)
param_grid <- param_grid[selected_indices, ]
cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 4: Implementar Grid Search
resultados <- data.frame()
cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid)) {
# Extraer parámetros de la combinación actual
params <- list(
objective = "reg:squarederror", # Objetivo de regresión
eval_metric = "rmse", # Métrica de evaluación
eta = param_grid$eta[i],
max_depth = param_grid$max_depth[i],
min_child_weight = param_grid$min_child_weight[i],
subsample = param_grid$subsample[i],
colsample_bytree = param_grid$colsample_bytree[i],
gamma = param_grid$gamma[i]
)
cat("Evaluando combinación", i, "de", nrow(param_grid), ":\n")
cat(" eta =", params$eta,
", max_depth =", params$max_depth,
", min_child_weight =", params$min_child_weight,
", subsample =", params$subsample,
", colsample_bytree =", params$colsample_bytree,
", gamma =", params$gamma, "\n")
# Validación cruzada para encontrar el número óptimo de iteraciones
cv_model <- xgb.cv(
params = params,
data = dtrain,
nrounds = 200, # Máximo número de iteraciones
nfold = 5, # 5-fold validación cruzada
early_stopping_rounds = 20, # Detener si no hay mejora en 20 rondas
verbose = 0 # Suprimir mensajes
)
# Extraer mejor iteración y su RMSE
best_iteration <- cv_model$best_iteration
best_rmse <- min(cv_model$evaluation_log$test_rmse_mean)
cat(" Mejor iteración:", best_iteration, "\n")
cat(" RMSE en validación cruzada:", best_rmse, "\n\n")
# Guardar resultados
resultado_actual <- data.frame(
eta = params$eta,
max_depth = params$max_depth,
min_child_weight = params$min_child_weight,
subsample = params$subsample,
colsample_bytree = params$colsample_bytree,
gamma = params$gamma,
nrounds = best_iteration,
rmse_cv = best_rmse
)
resultados <- rbind(resultados, resultado_actual)
}
## Evaluando combinación 1 de 12 :
## eta = 0.01 , max_depth = 9 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1
## Mejor iteración: 200
## RMSE en validación cruzada: 1462.166
##
## Evaluando combinación 2 de 12 :
## eta = 0.1 , max_depth = 9 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.3
## Mejor iteración: 39
## RMSE en validación cruzada: 1056.725
##
## Evaluando combinación 3 de 12 :
## eta = 0.05 , max_depth = 3 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0
## Mejor iteración: 200
## RMSE en validación cruzada: 1024.607
##
## Evaluando combinación 4 de 12 :
## eta = 0.3 , max_depth = 9 , min_child_weight = 5 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.1
## Mejor iteración: 36
## RMSE en validación cruzada: 898.2435
##
## Evaluando combinación 5 de 12 :
## eta = 0.1 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1
## Mejor iteración: 49
## RMSE en validación cruzada: 1050.465
##
## Evaluando combinación 6 de 12 :
## eta = 0.01 , max_depth = 6 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.9 , gamma = 0.1
## Mejor iteración: 200
## RMSE en validación cruzada: 1404.477
##
## Evaluando combinación 7 de 12 :
## eta = 0.05 , max_depth = 3 , min_child_weight = 5 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.1
## Mejor iteración: 199
## RMSE en validación cruzada: 990.1106
##
## Evaluando combinación 8 de 12 :
## eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.7 , gamma = 0.1
## Mejor iteración: 116
## RMSE en validación cruzada: 948.4436
##
## Evaluando combinación 9 de 12 :
## eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0.3
## Mejor iteración: 108
## RMSE en validación cruzada: 939.3893
##
## Evaluando combinación 10 de 12 :
## eta = 0.1 , max_depth = 9 , min_child_weight = 1 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3
## Mejor iteración: 200
## RMSE en validación cruzada: 1006.193
##
## Evaluando combinación 11 de 12 :
## eta = 0.05 , max_depth = 6 , min_child_weight = 3 , subsample = 0.7 , colsample_bytree = 0.9 , gamma = 0
## Mejor iteración: 112
## RMSE en validación cruzada: 977.0436
##
## Evaluando combinación 12 de 12 :
## eta = 0.1 , max_depth = 3 , min_child_weight = 3 , subsample = 0.9 , colsample_bytree = 0.7 , gamma = 0.3
## Mejor iteración: 110
## RMSE en validación cruzada: 1086.591
# Ordenar resultados por RMSE (de menor a mayor)
resultados <- resultados[order(resultados$rmse_cv), ]
# Paso 5: Mostrar resultados del Grid Search
cat("Resultados del Grid Search ordenados por RMSE:\n")
## Resultados del Grid Search ordenados por RMSE:
print(resultados)
## eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 4 0.30 9 5 0.7 0.9 0.1 36
## 9 0.05 6 3 0.7 0.9 0.3 108
## 8 0.10 3 3 0.7 0.7 0.1 116
## 11 0.05 6 3 0.7 0.9 0.0 112
## 7 0.05 3 5 0.9 0.7 0.1 199
## 10 0.10 9 1 0.9 0.7 0.3 200
## 3 0.05 3 1 0.9 0.7 0.0 200
## 5 0.10 6 5 0.9 0.9 0.1 49
## 2 0.10 9 3 0.9 0.9 0.3 39
## 12 0.10 3 3 0.9 0.7 0.3 110
## 6 0.01 6 5 0.9 0.9 0.1 200
## 1 0.01 9 3 0.7 0.9 0.1 200
## rmse_cv
## 4 898.2435
## 9 939.3893
## 8 948.4436
## 11 977.0436
## 7 990.1106
## 10 1006.1932
## 3 1024.6074
## 5 1050.4653
## 2 1056.7249
## 12 1086.5913
## 6 1404.4773
## 1 1462.1661
# Visualizar resultados del Grid Search
ggplot(resultados, aes(x = reorder(paste("Comb", 1:nrow(resultados)), rmse_cv), y = rmse_cv)) +
geom_bar(stat = "identity", fill = "steelblue") +
labs(
title = "Resultados del Grid Search - Producto 155002",
x = "Combinación de Hiperparámetros",
y = "RMSE en Validación Cruzada"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Paso 6: Seleccionar los mejores hiperparámetros
mejores_params <- list(
objective = "reg:squarederror",
eval_metric = "rmse",
eta = resultados$eta[1],
max_depth = resultados$max_depth[1],
min_child_weight = resultados$min_child_weight[1],
subsample = resultados$subsample[1],
colsample_bytree = resultados$colsample_bytree[1],
gamma = resultados$gamma[1]
)
mejor_nrounds <- resultados$nrounds[1]
cat("\nMejores hiperparámetros encontrados:\n")
##
## Mejores hiperparámetros encontrados:
print(mejores_params)
## $objective
## [1] "reg:squarederror"
##
## $eval_metric
## [1] "rmse"
##
## $eta
## [1] 0.3
##
## $max_depth
## [1] 9
##
## $min_child_weight
## [1] 5
##
## $subsample
## [1] 0.7
##
## $colsample_bytree
## [1] 0.9
##
## $gamma
## [1] 0.1
cat("Número óptimo de rondas:", mejor_nrounds, "\n\n")
## Número óptimo de rondas: 36
# Paso 7: Entrenar el modelo final con los mejores hiperparámetros
cat("Entrenando modelo final con los mejores hiperparámetros...\n")
## Entrenando modelo final con los mejores hiperparámetros...
modelo_final <- xgb.train(
params = mejores_params,
data = dtrain,
nrounds = mejor_nrounds,
watchlist = list(train = dtrain, test = dtest),
verbose = 0
)
modelo_xgb_155002 <- modelo_final
# Paso 8: Evaluar el modelo
# Predicciones en conjunto de prueba
predicciones_test <- predict(modelo_final, dtest)
# Calcular métricas
# R²
r2_test <- 1 - sum((test_y - predicciones_test)^2) / sum((test_y - mean(test_y))^2)
# RMSE
rmse_test <- sqrt(mean((test_y - predicciones_test)^2))
# MAPE
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100
# MSE
mse_test <- mean((test_y - predicciones_test)^2)
# AIC aproximado
n_test <- length(test_y)
k <- length(mejores_params) + 1 # +1 por número de iteraciones
aic_test <- n_test * log(mse_test) + 2 * k
# Mostrar métricas en conjunto de prueba
cat("\nMétricas en conjunto de prueba:\n")
##
## Métricas en conjunto de prueba:
cat("R² del modelo XGBoost:", r2_test, "\n")
## R² del modelo XGBoost: 0.901764
cat("AIC aproximado del modelo XGBoost:", aic_test, "\n")
## AIC aproximado del modelo XGBoost: 17368.54
cat("RMSE del modelo XGBoost:", rmse_test, "\n")
## RMSE del modelo XGBoost: 1703.085
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 133913.3
cat("MSE del modelo XGBoost:", mse_test, "\n\n")
## MSE del modelo XGBoost: 2900499
# Paso 9: Predicciones en el conjunto completo
# Hacer predicciones en todo el conjunto de datos para comparación con otros modelos
x_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completo <- predict(modelo_final, x_completo)
# Calcular métricas en conjunto completo
r2_completo <- 1 - sum((datos_modelo$Venta - predicciones_completo)^2) /
sum((datos_modelo$Venta - mean(datos_modelo$Venta))^2)
rmse_completo <- sqrt(mean((datos_modelo$Venta - predicciones_completo)^2))
mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completo) /
pmax(datos_modelo$Venta, 0.01))) * 100
mse_completo <- mean((datos_modelo$Venta - predicciones_completo)^2)
n_completo <- nrow(datos_modelo)
aic_completo <- n_completo * log(mse_completo) + 2 * k
# Mostrar métricas en conjunto completo
cat("Métricas en conjunto completo:\n")
## Métricas en conjunto completo:
cat("R² del modelo XGBoost:", r2_completo, "\n")
## R² del modelo XGBoost: 0.9676146
cat("AIC aproximado del modelo XGBoost:", aic_completo, "\n")
## AIC aproximado del modelo XGBoost: 80504.41
cat("RMSE del modelo XGBoost:", rmse_completo, "\n")
## RMSE del modelo XGBoost: 986.8337
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 131676.5
cat("MSE del modelo XGBoost:", mse_completo, "\n\n")
## MSE del modelo XGBoost: 973840.8
# Paso 10: Análisis de importancia de variables
# Calcular importancia de variables
importancia <- xgb.importance(
feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
model = modelo_final
)
# Mostrar importancia de variables
cat("Importancia de variables:\n")
## Importancia de variables:
print(importancia)
## Feature Gain Cover Frequency
## <char> <num> <num> <num>
## 1: Cant 5.958077e-01 0.3536267587 0.23432682
## 2: Costo_Venta 3.691236e-01 0.3066105490 0.24460432
## 3: Costo_Devolucion 1.462394e-02 0.0573419036 0.03288798
## 4: Precio_Final_Unitario 1.351768e-02 0.1707959048 0.24357657
## 5: Descuento_Porcentaje 5.412442e-03 0.0839152662 0.07810894
## 6: Ajus_Sistema 8.240049e-04 0.0116977756 0.02055498
## 7: Ajuste_Manual 3.006125e-04 0.0052008521 0.04316547
## 8: Semana 2.101618e-04 0.0036903929 0.05960946
## 9: Tiempo 1.407921e-04 0.0062971531 0.03288798
## 10: Mes 3.906278e-05 0.0008234439 0.01027749
# Graficar importancia de variables
xgb.plot.importance(importance_matrix = importancia,
main = "Importancia de Variables - Producto 155002 (XGBoost)")

# Paso 11: Visualizaciones para evaluación
# Gráfico 1: Valores observados vs predichos
datos_grafico <- data.frame(
Observado = datos_modelo$Venta,
Predicho = predicciones_completo
)
ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
geom_point(alpha = 0.5) +
geom_abline(intercept = 0, slope = 1, color = "red", linetype = "dashed") +
labs(
title = "Valores Observados vs Predicciones - Producto 155002 (XGBoost)",
x = "Ventas Observadas",
y = "Ventas Predichas"
) +
theme_minimal()

# Gráfico 2: Análisis de residuos
errores <- datos_modelo$Venta - predicciones_completo
# Histograma de errores
hist(errores,
main = "Distribución de Errores - Producto 155002 (XGBoost)",
xlab = "Error (Observado - Predicho)",
col = "skyblue",
breaks = 30)

# Gráfico 3: Errores vs Predicciones
ggplot(data.frame(Predicho = predicciones_completo, Error = errores), 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 (XGBoost)",
x = "Ventas Predichas",
y = "Error (Observado - Predicho)"
) +
theme_minimal()

# Guardar métricas de XGBoost para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155002", # Cambia este ID para cada producto
Modelo = "XGBoost",
R2 = r2_completo, # Usamos las métricas del conjunto completo
RMSE = rmse_completo,
MAPE = mape_completo,
MSE = mse_completo,
AIC = aic_completo
))
PRODUCTO 3678055
# Preparar datos para el modelo (eliminar columnas no necesarias)
datos_modelo <- datos_3678055 %>%
select(-Trx_Fecha, -Fecha)
# Paso 2: Dividir los datos en conjuntos de entrenamiento (80%) y prueba (20%)
set.seed(123) # Para reproducibilidad
train_index <- createDataPartition(datos_modelo$Venta, p = 0.8, list = FALSE)
train_data <- datos_modelo[train_index, ]
test_data <- datos_modelo[-train_index, ]
# Preparar matrices para XGBoost
train_x <- as.matrix(train_data[, colnames(train_data) != "Venta"])
train_y <- train_data$Venta
test_x <- as.matrix(test_data[, colnames(test_data) != "Venta"])
test_y <- test_data$Venta
# Crear DMatrix para XGBoost
dtrain <- xgb.DMatrix(data = train_x, label = train_y)
dtest <- xgb.DMatrix(data = test_x, label = test_y)
# Paso 3: Definir la rejilla de hiperparámetros
param_grid <- expand.grid(
eta = c(0.01, 0.05, 0.1, 0.3),
max_depth = c(3, 6, 9),
min_child_weight = c(1, 3, 5),
subsample = c(0.7, 0.9),
colsample_bytree = c(0.7, 0.9),
gamma = c(0, 0.1, 0.3)
)
cat("Grid Search para XGBoost - Producto 3678055\n")
## Grid Search para XGBoost - Producto 3678055
cat("Número total de combinaciones de hiperparámetros:", nrow(param_grid), "\n\n")
## Número total de combinaciones de hiperparámetros: 432
# Selección aleatoria de 12 combinaciones (si se desea limitar)
set.seed(456)
if (nrow(param_grid) > 12) {
param_grid <- param_grid[sample(1:nrow(param_grid), 12), ]
cat("Seleccionando 12 combinaciones aleatorias para evaluación.\n\n")
}
## Seleccionando 12 combinaciones aleatorias para evaluación.
# Paso 4: Implementar Grid Search
resultados <- data.frame()
cat("Iniciando Grid Search...\n")
## Iniciando Grid Search...
for (i in 1:nrow(param_grid)) {
params <- list(
objective = "reg:squarederror",
eval_metric = "rmse",
eta = param_grid$eta[i],
max_depth = param_grid$max_depth[i],
min_child_weight = param_grid$min_child_weight[i],
subsample = param_grid$subsample[i],
colsample_bytree = param_grid$colsample_bytree[i],
gamma = param_grid$gamma[i]
)
cat("Evaluando combinación", i, "de", nrow(param_grid), "...\n")
cv_model <- xgb.cv(
params = params,
data = dtrain,
nrounds = 200,
nfold = 5,
early_stopping_rounds = 20,
verbose = 0
)
best_iteration <- cv_model$best_iteration
best_rmse <- min(cv_model$evaluation_log$test_rmse_mean)
resultado_actual <- data.frame(
eta = params$eta,
max_depth = params$max_depth,
min_child_weight = params$min_child_weight,
subsample = params$subsample,
colsample_bytree = params$colsample_bytree,
gamma = params$gamma,
nrounds = best_iteration,
rmse_cv = best_rmse
)
resultados <- rbind(resultados, resultado_actual)
}
## Evaluando combinación 1 de 12 ...
## Evaluando combinación 2 de 12 ...
## Evaluando combinación 3 de 12 ...
## Evaluando combinación 4 de 12 ...
## Evaluando combinación 5 de 12 ...
## Evaluando combinación 6 de 12 ...
## Evaluando combinación 7 de 12 ...
## Evaluando combinación 8 de 12 ...
## Evaluando combinación 9 de 12 ...
## Evaluando combinación 10 de 12 ...
## Evaluando combinación 11 de 12 ...
## Evaluando combinación 12 de 12 ...
resultados <- resultados[order(resultados$rmse_cv), ]
# Paso 5: Mostrar resultados
print(resultados)
## eta max_depth min_child_weight subsample colsample_bytree gamma nrounds
## 3 0.05 3 1 0.9 0.7 0.0 200
## 9 0.05 6 3 0.7 0.9 0.3 182
## 2 0.10 9 3 0.9 0.9 0.3 119
## 11 0.05 6 3 0.7 0.9 0.0 196
## 8 0.10 3 3 0.7 0.7 0.1 127
## 10 0.10 9 1 0.9 0.7 0.3 200
## 12 0.10 3 3 0.9 0.7 0.3 128
## 7 0.05 3 5 0.9 0.7 0.1 200
## 5 0.10 6 5 0.9 0.9 0.1 198
## 4 0.30 9 5 0.7 0.9 0.1 64
## 6 0.01 6 5 0.9 0.9 0.1 200
## 1 0.01 9 3 0.7 0.9 0.1 200
## rmse_cv
## 3 587.0080
## 9 626.8935
## 2 679.6671
## 11 717.8518
## 8 723.4110
## 10 732.5299
## 12 762.6510
## 7 915.1378
## 5 917.7234
## 4 1010.7786
## 6 2418.2220
## 1 2419.4043
# Visualización
ggplot(resultados, aes(x = reorder(paste("Comb", 1:nrow(resultados)), rmse_cv), y = rmse_cv)) +
geom_bar(stat = "identity", fill = "steelblue") +
labs(
title = "Resultados del Grid Search - Producto 3678055",
x = "Combinación de Hiperparámetros",
y = "RMSE"
) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))

# Paso 6: Seleccionar los mejores hiperparámetros
mejores_params <- list(
objective = "reg:squarederror",
eval_metric = "rmse",
eta = resultados$eta[1],
max_depth = resultados$max_depth[1],
min_child_weight = resultados$min_child_weight[1],
subsample = resultados$subsample[1],
colsample_bytree = resultados$colsample_bytree[1],
gamma = resultados$gamma[1]
)
mejor_nrounds <- resultados$nrounds[1]
# Paso 7: Entrenar modelo final
modelo_final <- xgb.train(
params = mejores_params,
data = dtrain,
nrounds = mejor_nrounds,
watchlist = list(train = dtrain, test = dtest),
verbose = 0
)
modelo_xgb_3678055 <- modelo_final
# Paso 8: Evaluar modelo
predicciones_test <- predict(modelo_final, dtest)
r2_test <- 1 - sum((test_y - predicciones_test)^2) / sum((test_y - mean(test_y))^2)
rmse_test <- sqrt(mean((test_y - predicciones_test)^2))
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100
mse_test <- mean((test_y - predicciones_test)^2)
aic_test <- length(test_y) * log(mse_test) + 2 * (length(mejores_params) + 1)
# Paso 9: Predicción en todo el conjunto
x_completo <- as.matrix(datos_modelo[, colnames(datos_modelo) != "Venta"])
predicciones_completo <- predict(modelo_final, x_completo)
r2_completo <- 1 - sum((datos_modelo$Venta - predicciones_completo)^2) /
sum((datos_modelo$Venta - mean(datos_modelo$Venta))^2)
rmse_completo <- sqrt(mean((datos_modelo$Venta - predicciones_completo)^2))
mape_completo <- mean(abs((datos_modelo$Venta - predicciones_completo) / pmax(datos_modelo$Venta, 0.01))) * 100
mse_completo <- mean((datos_modelo$Venta - predicciones_completo)^2)
aic_completo <- nrow(datos_modelo) * log(mse_completo) + 2 * (length(mejores_params) + 1)
# Paso 10: Importancia de variables
importancia <- xgb.importance(
feature_names = colnames(datos_modelo)[colnames(datos_modelo) != "Venta"],
model = modelo_final
)
xgb.plot.importance(importancia,
main = "Importancia de Variables - Producto 3678055 (XGBoost)")

# Paso 11: Gráficos de evaluación
datos_grafico <- data.frame(Observado = datos_modelo$Venta, Predicho = predicciones_completo)
ggplot(datos_grafico, aes(x = Observado, y = Predicho)) +
geom_point(alpha = 0.5) +
geom_abline(slope = 1, intercept = 0, linetype = "dashed", color = "red") +
labs(title = "Observado vs Predicho - Producto 3678055", x = "Venta Observada", y = "Venta Predicha") +
theme_minimal()

errores <- datos_modelo$Venta - predicciones_completo
hist(errores,
main = "Distribución de Errores - Producto 3678055 (XGBoost)",
xlab = "Error (Observado - Predicho)",
col = "skyblue", breaks = 30)

ggplot(data.frame(Predicho = predicciones_completo, Error = errores), aes(x = Predicho, y = Error)) +
geom_point(alpha = 0.5) +
geom_hline(yintercept = 0, color = "red", linetype = "dashed") +
labs(title = "Errores vs Predicción - Producto 3678055", x = "Venta Predicha", y = "Error") +
theme_minimal()

# Guardar métricas de XGBoost para producto 155001
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
R2 = numeric(),
RMSE = numeric(),
MAPE = numeric(),
MSE = numeric(),
AIC = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3678055", # Cambia este ID para cada producto
Modelo = "XGBoost",
R2 = r2_completo, # Usamos las métricas del conjunto completo
RMSE = rmse_completo,
MAPE = mape_completo,
MSE = mse_completo,
AIC = aic_completo
))
Visualización de
Métricas
# Definir los colores para cada modelo
colores_modelos <- c(
"ARMA/SARIMA" = "#1f77b4", # Azul
"Regresión Lineal" = "#ff7f0e", # Naranja
"Random Forest" = "#2ca02c", # Verde
"XGBoost" = "#d62728" # Rojo
)
PRODUCTO 155001
# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 155001:")
## [1] "Datos actuales para el producto 155001:"
print(metricas_comparativas %>% filter(Producto == "155001"))
## Producto Modelo R2 RMSE MAPE MSE
## 1 155001 ARMA/SARIMA 0.1923463 216622.3497 17.48586 4.692524e+10
## 2 155001 Regresión Lineal 0.9887468 770.0434 15.90447 5.929668e+05
## 3 155001 Random Forest 0.9784171 1066.4276 156067.23553 1.137268e+06
## 4 155001 XGBoost 0.9975129 362.0098 53774.37537 1.310511e+05
## AIC
## 1 664.0425
## 2 139748.7359
## 3 120806.1350
## 4 102085.3116
# Crear un dataframe manualmente con los 4 modelos para el producto 155001
# (con valores de ejemplo si es necesario)
datos_155001_completo <- data.frame(
Producto = rep("155001", 4),
Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
stringsAsFactors = FALSE
)
# Unir con los datos existentes
datos_155001_completo <- left_join(
datos_155001_completo,
metricas_comparativas %>% filter(Producto == "155001"),
by = c("Producto", "Modelo")
)
# Ahora asigna valores para las métricas de los modelos faltantes
# Si tienes los valores, reemplaza los 0 con los valores correctos
# O toma nota de cuáles son NA para reemplazarlos con los valores reales
# Valores para Regresión Lineal (reemplaza estos con los valores reales)
if (is.na(datos_155001_completo$R2[2])) {
datos_155001_completo$R2[2] <- r2_155001 # O el valor correcto
}
if (is.na(datos_155001_completo$RMSE[2])) {
datos_155001_completo$RMSE[2] <- rmse_155001 # O el valor correcto
}
if (is.na(datos_155001_completo$MAPE[2])) {
datos_155001_completo$MAPE[2] <- mape_155001 # O el valor correcto
}
if (is.na(datos_155001_completo$MSE[2])) {
datos_155001_completo$MSE[2] <- mse_155001 # O el valor correcto
}
if (is.na(datos_155001_completo$AIC[2])) {
datos_155001_completo$AIC[2] <- aic_155001 # O el valor correcto
}
# Valores para Random Forest (reemplaza estos con los valores reales)
# Si ya ejecutaste la sección de Random Forest para el producto 155001,
# usa las variables r2_rf, rmse_rf, etc.
if (is.na(datos_155001_completo$R2[3]) && exists("r2_rf")) {
datos_155001_completo$R2[3] <- r2_rf
}
if (is.na(datos_155001_completo$RMSE[3]) && exists("rmse_rf")) {
datos_155001_completo$RMSE[3] <- rmse_rf
}
if (is.na(datos_155001_completo$MAPE[3]) && exists("mape_rf")) {
datos_155001_completo$MAPE[3] <- mape_rf
}
if (is.na(datos_155001_completo$MSE[3]) && exists("mse_rf")) {
datos_155001_completo$MSE[3] <- mse_rf
}
if (is.na(datos_155001_completo$AIC[3]) && exists("aic_rf")) {
datos_155001_completo$AIC[3] <- aic_rf
}
# Valores para XGBoost (reemplaza estos con los valores reales)
# Si ya ejecutaste la sección de XGBoost para el producto 155001,
# usa las variables r2_completo, rmse_completo, etc.
if (is.na(datos_155001_completo$R2[4]) && exists("r2_completo")) {
datos_155001_completo$R2[4] <- r2_completo
}
if (is.na(datos_155001_completo$RMSE[4]) && exists("rmse_completo")) {
datos_155001_completo$RMSE[4] <- rmse_completo
}
if (is.na(datos_155001_completo$MAPE[4]) && exists("mape_completo")) {
datos_155001_completo$MAPE[4] <- mape_completo
}
if (is.na(datos_155001_completo$MSE[4]) && exists("mse_completo")) {
datos_155001_completo$MSE[4] <- mse_completo
}
if (is.na(datos_155001_completo$AIC[4]) && exists("aic_completo")) {
datos_155001_completo$AIC[4] <- aic_completo
}
# Ver los datos completos
print("Datos completos para el producto 155001:")
## [1] "Datos completos para el producto 155001:"
print(datos_155001_completo)
## Producto Modelo R2 RMSE MAPE MSE
## 1 155001 ARMA/SARIMA 0.1923463 216622.3497 17.48586 4.692524e+10
## 2 155001 Regresión Lineal 0.9887468 770.0434 15.90447 5.929668e+05
## 3 155001 Random Forest 0.9784171 1066.4276 156067.23553 1.137268e+06
## 4 155001 XGBoost 0.9975129 362.0098 53774.37537 1.310511e+05
## AIC
## 1 664.0425
## 2 139748.7359
## 3 120806.1350
## 4 102085.3116
# Crear el gráfico con los datos completos
ggplot(datos_155001_completo, aes(x = Modelo, y = R2, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(R2, 3)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 155001",
subtitle = "Métrica: R² (valores más altos indican mejor ajuste)",
x = "",
y = "R²"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_155001_completo$R2, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para RMSE
ggplot(datos_155001_completo, 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 = "Comparación de modelos para Producto 155001",
subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
x = "",
y = "RMSE"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_155001_completo$RMSE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para MAPE
ggplot(datos_155001_completo, 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 = "Comparación de modelos para Producto 155001",
subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
x = "",
y = "MAPE (%)"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_155001_completo$MAPE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para MSE
ggplot(datos_155001_completo, aes(x = Modelo, y = MSE, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(MSE, 1)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 155001",
subtitle = "Métrica: MSE (valores más bajos indican mejor precisión)",
x = "",
y = "MSE"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_155001_completo$MSE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para AIC
ggplot(datos_155001_completo, aes(x = Modelo, y = AIC, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(AIC, 1)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 155001",
subtitle = "Métrica: AIC (valores más bajos indican mejor modelo)",
x = "",
y = "AIC"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_155001_completo$AIC, na.rm = TRUE) * 1.1) # Ajustar el límite Y

PRODUCTO 3929788
# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 3929788:")
## [1] "Datos actuales para el producto 3929788:"
print(metricas_comparativas %>% filter(Producto == "3929788"))
## Producto Modelo R2 RMSE MAPE MSE
## 1 3929788 ARMA/SARIMA 0.2466637 144234.4597 13.19875 2.080358e+10
## 2 3929788 Regresión Lineal 0.9949995 222.3941 29141.99432 4.945916e+04
## 3 3929788 Random Forest 0.9955214 210.4673 36102.66985 4.429649e+04
## 4 3929788 XGBoost 0.9984580 123.4982 13959.95972 1.525179e+04
## AIC
## 1 619.6674
## 2 189113.7773
## 3 148262.6435
## 4 133485.2602
# Crear un dataframe manualmente con los 4 modelos para el producto 3929788
datos_3929788_completo <- data.frame(
Producto = rep("3929788", 4),
Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
stringsAsFactors = FALSE
)
# Unir con los datos existentes
datos_3929788_completo <- left_join(
datos_3929788_completo,
metricas_comparativas %>% filter(Producto == "3929788"),
by = c("Producto", "Modelo")
)
# Ahora asigna valores para las métricas de los modelos faltantes
# Valores para Regresión Lineal
if (is.na(datos_3929788_completo$R2[2])) {
datos_3929788_completo$R2[2] <- r2_3929788
}
if (is.na(datos_3929788_completo$RMSE[2])) {
datos_3929788_completo$RMSE[2] <- rmse_3929788
}
if (is.na(datos_3929788_completo$MAPE[2])) {
datos_3929788_completo$MAPE[2] <- mape_3929788
}
if (is.na(datos_3929788_completo$MSE[2])) {
datos_3929788_completo$MSE[2] <- mse_3929788
}
if (is.na(datos_3929788_completo$AIC[2])) {
datos_3929788_completo$AIC[2] <- aic_3929788
}
# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 3929788
if (is.na(datos_3929788_completo$R2[3]) && exists("r2_rf")) {
datos_3929788_completo$R2[3] <- r2_rf
}
if (is.na(datos_3929788_completo$RMSE[3]) && exists("rmse_rf")) {
datos_3929788_completo$RMSE[3] <- rmse_rf
}
if (is.na(datos_3929788_completo$MAPE[3]) && exists("mape_rf")) {
datos_3929788_completo$MAPE[3] <- mape_rf
}
if (is.na(datos_3929788_completo$MSE[3]) && exists("mse_rf")) {
datos_3929788_completo$MSE[3] <- mse_rf
}
if (is.na(datos_3929788_completo$AIC[3]) && exists("aic_rf")) {
datos_3929788_completo$AIC[3] <- aic_rf
}
# Valores para XGBoost
if (is.na(datos_3929788_completo$R2[4]) && exists("r2_completo")) {
datos_3929788_completo$R2[4] <- r2_completo
}
if (is.na(datos_3929788_completo$RMSE[4]) && exists("rmse_completo")) {
datos_3929788_completo$RMSE[4] <- rmse_completo
}
if (is.na(datos_3929788_completo$MAPE[4]) && exists("mape_completo")) {
datos_3929788_completo$MAPE[4] <- mape_completo
}
if (is.na(datos_3929788_completo$MSE[4]) && exists("mse_completo")) {
datos_3929788_completo$MSE[4] <- mse_completo
}
if (is.na(datos_3929788_completo$AIC[4]) && exists("aic_completo")) {
datos_3929788_completo$AIC[4] <- aic_completo
}
# Ver los datos completos
print("Datos completos para el producto 3929788:")
## [1] "Datos completos para el producto 3929788:"
print(datos_3929788_completo)
## Producto Modelo R2 RMSE MAPE MSE
## 1 3929788 ARMA/SARIMA 0.2466637 144234.4597 13.19875 2.080358e+10
## 2 3929788 Regresión Lineal 0.9949995 222.3941 29141.99432 4.945916e+04
## 3 3929788 Random Forest 0.9955214 210.4673 36102.66985 4.429649e+04
## 4 3929788 XGBoost 0.9984580 123.4982 13959.95972 1.525179e+04
## AIC
## 1 619.6674
## 2 189113.7773
## 3 148262.6435
## 4 133485.2602
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4",
"Regresión Lineal" = "#ff7f0e",
"Random Forest" = "#2ca02c",
"XGBoost" = "#d62728")
# Gráfico para R²
ggplot(datos_3929788_completo, aes(x = Modelo, y = R2, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(R2, 3)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 3929788",
subtitle = "Métrica: R² (valores más altos indican mejor ajuste)",
x = "",
y = "R²"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3929788_completo$R2, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para RMSE
ggplot(datos_3929788_completo, 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 = "Comparación de modelos para Producto 3929788",
subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
x = "",
y = "RMSE"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3929788_completo$RMSE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para MAPE
ggplot(datos_3929788_completo, 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 = "Comparación de modelos para Producto 3929788",
subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
x = "",
y = "MAPE (%)"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3929788_completo$MAPE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para MSE
ggplot(datos_3929788_completo, aes(x = Modelo, y = MSE, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(MSE, 1)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 3929788",
subtitle = "Métrica: MSE (valores más bajos indican mejor precisión)",
x = "",
y = "MSE"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3929788_completo$MSE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para AIC
ggplot(datos_3929788_completo, aes(x = Modelo, y = AIC, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(AIC, 1)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 3929788",
subtitle = "Métrica: AIC (valores más bajos indican mejor modelo)",
x = "",
y = "AIC"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3929788_completo$AIC, na.rm = TRUE) * 1.1) # Ajustar el límite Y

PRODUCTO 3904152
# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 3904152:")
## [1] "Datos actuales para el producto 3904152:"
print(metricas_comparativas %>% filter(Producto == "3904152"))
## Producto Modelo R2 RMSE MAPE MSE
## 1 3904152 ARMA/SARIMA 0.0000000 159913.0499 16.18578 2.557218e+10
## 2 3904152 Regresión Lineal 0.9987051 385.5886 128319.29509 1.486786e+05
## 3 3904152 Random Forest 0.9948033 772.4580 148033.10769 5.966914e+05
## 4 3904152 XGBoost 0.9991788 307.0670 62039.19987 9.429015e+04
## AIC
## 1 647.2636
## 2 38603.2484
## 3 34812.5905
## 4 29982.0092
# Crear un dataframe manualmente con los 4 modelos para el producto 3904152
datos_3904152_completo <- data.frame(
Producto = rep("3904152", 4),
Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
stringsAsFactors = FALSE
)
# Unir con los datos existentes
datos_3904152_completo <- left_join(
datos_3904152_completo,
metricas_comparativas %>% filter(Producto == "3904152"),
by = c("Producto", "Modelo")
)
# Ahora asigna valores para las métricas de los modelos faltantes
# Valores para Regresión Lineal
if (is.na(datos_3904152_completo$R2[2])) {
datos_3904152_completo$R2[2] <- r2_3904152
}
if (is.na(datos_3904152_completo$RMSE[2])) {
datos_3904152_completo$RMSE[2] <- rmse_3904152
}
if (is.na(datos_3904152_completo$MAPE[2])) {
datos_3904152_completo$MAPE[2] <- mape_3904152
}
if (is.na(datos_3904152_completo$MSE[2])) {
datos_3904152_completo$MSE[2] <- mse_3904152
}
if (is.na(datos_3904152_completo$AIC[2])) {
datos_3904152_completo$AIC[2] <- aic_3904152
}
# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 3904152
if (is.na(datos_3904152_completo$R2[3]) && exists("r2_rf")) {
datos_3904152_completo$R2[3] <- r2_rf
}
if (is.na(datos_3904152_completo$RMSE[3]) && exists("rmse_rf")) {
datos_3904152_completo$RMSE[3] <- rmse_rf
}
if (is.na(datos_3904152_completo$MAPE[3]) && exists("mape_rf")) {
datos_3904152_completo$MAPE[3] <- mape_rf
}
if (is.na(datos_3904152_completo$MSE[3]) && exists("mse_rf")) {
datos_3904152_completo$MSE[3] <- mse_rf
}
if (is.na(datos_3904152_completo$AIC[3]) && exists("aic_rf")) {
datos_3904152_completo$AIC[3] <- aic_rf
}
# Valores para XGBoost
if (is.na(datos_3904152_completo$R2[4]) && exists("r2_completo")) {
datos_3904152_completo$R2[4] <- r2_completo
}
if (is.na(datos_3904152_completo$RMSE[4]) && exists("rmse_completo")) {
datos_3904152_completo$RMSE[4] <- rmse_completo
}
if (is.na(datos_3904152_completo$MAPE[4]) && exists("mape_completo")) {
datos_3904152_completo$MAPE[4] <- mape_completo
}
if (is.na(datos_3904152_completo$MSE[4]) && exists("mse_completo")) {
datos_3904152_completo$MSE[4] <- mse_completo
}
if (is.na(datos_3904152_completo$AIC[4]) && exists("aic_completo")) {
datos_3904152_completo$AIC[4] <- aic_completo
}
# Ver los datos completos
print("Datos completos para el producto 3904152:")
## [1] "Datos completos para el producto 3904152:"
print(datos_3904152_completo)
## Producto Modelo R2 RMSE MAPE MSE
## 1 3904152 ARMA/SARIMA 0.0000000 159913.0499 16.18578 2.557218e+10
## 2 3904152 Regresión Lineal 0.9987051 385.5886 128319.29509 1.486786e+05
## 3 3904152 Random Forest 0.9948033 772.4580 148033.10769 5.966914e+05
## 4 3904152 XGBoost 0.9991788 307.0670 62039.19987 9.429015e+04
## AIC
## 1 647.2636
## 2 38603.2484
## 3 34812.5905
## 4 29982.0092
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4",
"Regresión Lineal" = "#ff7f0e",
"Random Forest" = "#2ca02c",
"XGBoost" = "#d62728")
# Gráfico para R²
ggplot(datos_3904152_completo, aes(x = Modelo, y = R2, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(R2, 3)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 3904152",
subtitle = "Métrica: R² (valores más altos indican mejor ajuste)",
x = "",
y = "R²"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3904152_completo$R2, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para RMSE
ggplot(datos_3904152_completo, 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 = "Comparación de modelos para Producto 3904152",
subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
x = "",
y = "RMSE"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3904152_completo$RMSE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para MAPE
ggplot(datos_3904152_completo, 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 = "Comparación de modelos para Producto 3904152",
subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
x = "",
y = "MAPE (%)"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3904152_completo$MAPE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para MSE
ggplot(datos_3904152_completo, aes(x = Modelo, y = MSE, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(MSE, 1)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 3904152",
subtitle = "Métrica: MSE (valores más bajos indican mejor precisión)",
x = "",
y = "MSE"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3904152_completo$MSE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para AIC
ggplot(datos_3904152_completo, aes(x = Modelo, y = AIC, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(AIC, 1)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 3904152",
subtitle = "Métrica: AIC (valores más bajos indican mejor modelo)",
x = "",
y = "AIC"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3904152_completo$AIC, na.rm = TRUE) * 1.1) # Ajustar el límite Y

PRODUCTO 155002
# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 155002:")
## [1] "Datos actuales para el producto 155002:"
print(metricas_comparativas %>% filter(Producto == "155002"))
## Producto Modelo R2 RMSE MAPE MSE
## 1 155002 ARMA/SARIMA 0.2341854 188967.2199 25.93817 3.570861e+10
## 2 155002 Regresión Lineal 0.9571086 1135.6762 238027.63306 1.289760e+06
## 3 155002 Random Forest 0.9871784 620.9267 102855.51366 3.855500e+05
## 4 155002 XGBoost 0.9676146 986.8337 131676.50029 9.738408e+05
## AIC
## 1 659.9971
## 2 98715.0862
## 3 75099.9813
## 4 80504.4111
# Crear un dataframe manualmente con los 4 modelos para el producto 155002
datos_155002_completo <- data.frame(
Producto = rep("155002", 4),
Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
stringsAsFactors = FALSE
)
# Unir con los datos existentes
datos_155002_completo <- left_join(
datos_155002_completo,
metricas_comparativas %>% filter(Producto == "155002"),
by = c("Producto", "Modelo")
)
# Ahora asigna valores para las métricas de los modelos faltantes
# Valores para Regresión Lineal
if (is.na(datos_155002_completo$R2[2])) {
datos_155002_completo$R2[2] <- r2_155002
}
if (is.na(datos_155002_completo$RMSE[2])) {
datos_155002_completo$RMSE[2] <- rmse_155002
}
if (is.na(datos_155002_completo$MAPE[2])) {
datos_155002_completo$MAPE[2] <- mape_155002
}
if (is.na(datos_155002_completo$MSE[2])) {
datos_155002_completo$MSE[2] <- mse_155002
}
if (is.na(datos_155002_completo$AIC[2])) {
datos_155002_completo$AIC[2] <- aic_155002
}
# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 155002
if (is.na(datos_155002_completo$R2[3]) && exists("r2_rf")) {
datos_155002_completo$R2[3] <- r2_rf
}
if (is.na(datos_155002_completo$RMSE[3]) && exists("rmse_rf")) {
datos_155002_completo$RMSE[3] <- rmse_rf
}
if (is.na(datos_155002_completo$MAPE[3]) && exists("mape_rf")) {
datos_155002_completo$MAPE[3] <- mape_rf
}
if (is.na(datos_155002_completo$MSE[3]) && exists("mse_rf")) {
datos_155002_completo$MSE[3] <- mse_rf
}
if (is.na(datos_155002_completo$AIC[3]) && exists("aic_rf")) {
datos_155002_completo$AIC[3] <- aic_rf
}
# Valores para XGBoost
if (is.na(datos_155002_completo$R2[4]) && exists("r2_completo")) {
datos_155002_completo$R2[4] <- r2_completo
}
if (is.na(datos_155002_completo$RMSE[4]) && exists("rmse_completo")) {
datos_155002_completo$RMSE[4] <- rmse_completo
}
if (is.na(datos_155002_completo$MAPE[4]) && exists("mape_completo")) {
datos_155002_completo$MAPE[4] <- mape_completo
}
if (is.na(datos_155002_completo$MSE[4]) && exists("mse_completo")) {
datos_155002_completo$MSE[4] <- mse_completo
}
if (is.na(datos_155002_completo$AIC[4]) && exists("aic_completo")) {
datos_155002_completo$AIC[4] <- aic_completo
}
# Ver los datos completos
print("Datos completos para el producto 155002:")
## [1] "Datos completos para el producto 155002:"
print(datos_155002_completo)
## Producto Modelo R2 RMSE MAPE MSE
## 1 155002 ARMA/SARIMA 0.2341854 188967.2199 25.93817 3.570861e+10
## 2 155002 Regresión Lineal 0.9571086 1135.6762 238027.63306 1.289760e+06
## 3 155002 Random Forest 0.9871784 620.9267 102855.51366 3.855500e+05
## 4 155002 XGBoost 0.9676146 986.8337 131676.50029 9.738408e+05
## AIC
## 1 659.9971
## 2 98715.0862
## 3 75099.9813
## 4 80504.4111
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4",
"Regresión Lineal" = "#ff7f0e",
"Random Forest" = "#2ca02c",
"XGBoost" = "#d62728")
# Gráfico para R²
ggplot(datos_155002_completo, aes(x = Modelo, y = R2, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(R2, 3)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 155002",
subtitle = "Métrica: R² (valores más altos indican mejor ajuste)",
x = "",
y = "R²"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_155002_completo$R2, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para RMSE
ggplot(datos_155002_completo, 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 = "Comparación de modelos para Producto 155002",
subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
x = "",
y = "RMSE"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_155002_completo$RMSE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para MAPE
ggplot(datos_155002_completo, 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 = "Comparación de modelos para Producto 155002",
subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
x = "",
y = "MAPE (%)"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_155002_completo$MAPE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para MSE
ggplot(datos_155002_completo, aes(x = Modelo, y = MSE, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(MSE, 1)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 155002",
subtitle = "Métrica: MSE (valores más bajos indican mejor precisión)",
x = "",
y = "MSE"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_155002_completo$MSE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para AIC
ggplot(datos_155002_completo, aes(x = Modelo, y = AIC, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(AIC, 1)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 155002",
subtitle = "Métrica: AIC (valores más bajos indican mejor modelo)",
x = "",
y = "AIC"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_155002_completo$AIC, na.rm = TRUE) * 1.1) # Ajustar el límite Y

PRODUCTO 3678055
# Primero, veamos qué datos tenemos realmente
print("Datos actuales para el producto 3678055:")
## [1] "Datos actuales para el producto 3678055:"
print(metricas_comparativas %>% filter(Producto == "3678055"))
## Producto Modelo R2 RMSE MAPE MSE
## 1 3678055 ARMA/SARIMA 0.0000000 169884.0442 22.22443 2.886059e+10
## 2 3678055 Regresión Lineal 0.9977194 482.1191 236820.81905 2.324388e+05
## 3 3678055 Random Forest 0.9918288 912.5829 441446.58433 8.328075e+05
## 4 3678055 XGBoost 0.9859269 1197.6358 351831.75705 1.434331e+06
## AIC
## 1 650.1669
## 2 26051.7663
## 3 23374.5716
## 4 24301.8467
# Crear un dataframe manualmente con los 4 modelos para el producto 3678055
datos_3678055_completo <- data.frame(
Producto = rep("3678055", 4),
Modelo = c("ARMA/SARIMA", "Regresión Lineal", "Random Forest", "XGBoost"),
stringsAsFactors = FALSE
)
# Unir con los datos existentes
datos_3678055_completo <- left_join(
datos_3678055_completo,
metricas_comparativas %>% filter(Producto == "3678055"),
by = c("Producto", "Modelo")
)
# Ahora asigna valores para las métricas de los modelos faltantes
# Valores para Regresión Lineal
if (is.na(datos_3678055_completo$R2[2])) {
datos_3678055_completo$R2[2] <- r2_3678055
}
if (is.na(datos_3678055_completo$RMSE[2])) {
datos_3678055_completo$RMSE[2] <- rmse_3678055
}
if (is.na(datos_3678055_completo$MAPE[2])) {
datos_3678055_completo$MAPE[2] <- mape_3678055
}
if (is.na(datos_3678055_completo$MSE[2])) {
datos_3678055_completo$MSE[2] <- mse_3678055
}
if (is.na(datos_3678055_completo$AIC[2])) {
datos_3678055_completo$AIC[2] <- aic_3678055
}
# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 3678055
if (is.na(datos_3678055_completo$R2[3]) && exists("r2_rf")) {
datos_3678055_completo$R2[3] <- r2_rf
}
if (is.na(datos_3678055_completo$RMSE[3]) && exists("rmse_rf")) {
datos_3678055_completo$RMSE[3] <- rmse_rf
}
if (is.na(datos_3678055_completo$MAPE[3]) && exists("mape_rf")) {
datos_3678055_completo$MAPE[3] <- mape_rf
}
if (is.na(datos_3678055_completo$MSE[3]) && exists("mse_rf")) {
datos_3678055_completo$MSE[3] <- mse_rf
}
if (is.na(datos_3678055_completo$AIC[3]) && exists("aic_rf")) {
datos_3678055_completo$AIC[3] <- aic_rf
}
# Valores para XGBoost
if (is.na(datos_3678055_completo$R2[4]) && exists("r2_completo")) {
datos_3678055_completo$R2[4] <- r2_completo
}
if (is.na(datos_3678055_completo$RMSE[4]) && exists("rmse_completo")) {
datos_3678055_completo$RMSE[4] <- rmse_completo
}
if (is.na(datos_3678055_completo$MAPE[4]) && exists("mape_completo")) {
datos_3678055_completo$MAPE[4] <- mape_completo
}
if (is.na(datos_3678055_completo$MSE[4]) && exists("mse_completo")) {
datos_3678055_completo$MSE[4] <- mse_completo
}
if (is.na(datos_3678055_completo$AIC[4]) && exists("aic_completo")) {
datos_3678055_completo$AIC[4] <- aic_completo
}
# Ver los datos completos
print("Datos completos para el producto 3678055:")
## [1] "Datos completos para el producto 3678055:"
print(datos_3678055_completo)
## Producto Modelo R2 RMSE MAPE MSE
## 1 3678055 ARMA/SARIMA 0.0000000 169884.0442 22.22443 2.886059e+10
## 2 3678055 Regresión Lineal 0.9977194 482.1191 236820.81905 2.324388e+05
## 3 3678055 Random Forest 0.9918288 912.5829 441446.58433 8.328075e+05
## 4 3678055 XGBoost 0.9859269 1197.6358 351831.75705 1.434331e+06
## AIC
## 1 650.1669
## 2 26051.7663
## 3 23374.5716
## 4 24301.8467
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4",
"Regresión Lineal" = "#ff7f0e",
"Random Forest" = "#2ca02c",
"XGBoost" = "#d62728")
# Gráfico para R²
ggplot(datos_3678055_completo, aes(x = Modelo, y = R2, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(R2, 3)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 3678055",
subtitle = "Métrica: R² (valores más altos indican mejor ajuste)",
x = "",
y = "R²"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3678055_completo$R2, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para RMSE
ggplot(datos_3678055_completo, 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 = "Comparación de modelos para Producto 3678055",
subtitle = "Métrica: RMSE (valores más bajos indican mejor precisión)",
x = "",
y = "RMSE"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3678055_completo$RMSE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para MAPE
ggplot(datos_3678055_completo, 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 = "Comparación de modelos para Producto 3678055",
subtitle = "Métrica: MAPE (valores más bajos indican mejor precisión)",
x = "",
y = "MAPE (%)"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3678055_completo$MAPE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para MSE
ggplot(datos_3678055_completo, aes(x = Modelo, y = MSE, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(MSE, 1)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 3678055",
subtitle = "Métrica: MSE (valores más bajos indican mejor precisión)",
x = "",
y = "MSE"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3678055_completo$MSE, na.rm = TRUE) * 1.1) # Ajustar el límite Y

# Gráfico para AIC
ggplot(datos_3678055_completo, aes(x = Modelo, y = AIC, fill = Modelo)) +
geom_bar(stat = "identity", width = 0.6) +
geom_text(aes(label = round(AIC, 1)), vjust = -0.5, size = 3.5) +
scale_fill_manual(values = colores_modelos) +
labs(
title = "Comparación de modelos para Producto 3678055",
subtitle = "Métrica: AIC (valores más bajos indican mejor modelo)",
x = "",
y = "AIC"
) +
theme_minimal() +
theme(
legend.position = "none",
plot.title = element_text(size = 12, face = "bold"),
plot.subtitle = element_text(size = 10),
axis.text.x = element_text(angle = 45, hjust = 1)
) +
ylim(0, max(datos_3678055_completo$AIC, na.rm = TRUE) * 1.1) # Ajustar el límite Y

ESTIMACIÓN DE
PRECIOS
Preparación de
datos
# Función para preparar datos de un producto
prepare_price_data <- function(df, product_id) {
product_data <- df %>%
filter(ID_Inventario == product_id) %>%
arrange(Trx_Fecha) %>%
select(
Trx_Fecha, Precio_Final_Unitario, Cant, Venta,
Costo_Venta, Descuento_Porcentaje, Semana, Mes
) %>%
mutate(
Dia_Semana = wday(Trx_Fecha),
Mes_Num = month(Trx_Fecha),
Anio = year(Trx_Fecha),
Dias_Desde_Inicio = as.numeric(difftime(Trx_Fecha, min(Trx_Fecha), units = "days")),
Margen_Unitario = (Venta / Cant) - (Costo_Venta / Cant),
Precio_Unitario_Calc = Venta / Cant,
ID_Inventario = product_id
)
return(product_data)
}
# Asegúrate de que 'datos' sea tu data.frame cargado correctamente
# Por ejemplo, si vienes de un archivo .csv:
# datos <- read.csv("archivo.csv")
# 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)
})
# Mostrar una parte del resultado
head(productos_preparados)
## # A tibble: 6 × 15
## Trx_Fecha Precio_Final_Unitario Cant Venta Costo_Venta
## <dttm> <dbl> <dbl> <dbl> <dbl>
## 1 2023-01-02 00:00:00 980 1 980 727.
## 2 2023-01-03 00:00:00 728 1 728 905.
## 3 2023-01-03 00:00:00 840 6 5040 3598.
## 4 2023-01-04 00:00:00 1120 1 1120 577.
## 5 2023-01-04 00:00:00 728 8 5824 6619.
## 6 2023-01-04 00:00:00 980 10 9800 7273.
## # ℹ 10 more variables: Descuento_Porcentaje <dbl>, Semana <dbl>, Mes <dbl>,
## # Dia_Semana <dbl>, Mes_Num <dbl>, Anio <dbl>, Dias_Desde_Inicio <dbl>,
## # Margen_Unitario <dbl>, Precio_Unitario_Calc <dbl>, ID_Inventario <dbl>
# Vector con productos (debe ir primero)
productos_ids <- top_ids
# Función para entrenar modelo ARMA por producto
train_arma_model <- function(data, product_id) {
library(forecast) # Asegúrate de cargar forecast si no está cargado aún
product_data <- data %>% filter(ID_Inventario == product_id)
serie_ts <- ts(product_data$Venta, frequency = 12)
modelo_arma <- auto.arima(serie_ts, seasonal = FALSE, stepwise = FALSE, approximation = FALSE)
return(modelo_arma)
}
# Crear lista de modelos ARMA por producto
modelos_arma_lista <- setNames(
lapply(productos_ids, function(id) train_arma_model(datos, id)),
as.character(productos_ids)
)
# Función para modelo regresión lineal
train_reg_model <- function(data, product_id) {
product_data <- data %>% filter(ID_Inventario == product_id)
modelo_reg <- lm(Venta ~ Precio_Final_Unitario, data = product_data)
return(modelo_reg)
}
# Función para modelo Random Forest
train_rf_model <- function(data, product_id) {
product_data <- data %>% filter(ID_Inventario == product_id)
predictors <- c("Precio_Final_Unitario", "Cant", "Descuento_Porcentaje")
rf_data <- product_data %>% select(all_of(predictors), Venta)
modelo_rf <- randomForest(Venta ~ ., data = rf_data, ntree = 100)
return(modelo_rf)
}
# Función para modelo XGBoost
train_xgb_model <- function(data, product_id) {
product_data <- data %>% filter(ID_Inventario == product_id)
predictors <- c("Precio_Final_Unitario", "Cant", "Descuento_Porcentaje")
train_matrix <- xgb.DMatrix(data = as.matrix(product_data[, predictors]), label = product_data$Venta)
params <- list(objective = "reg:squarederror")
modelo_xgb <- xgb.train(params = params, data = train_matrix, nrounds = 50, verbose = 0)
return(modelo_xgb)
}
# Crear listas de modelos
modelos_reg_lista <- setNames(lapply(productos_ids, function(id) train_reg_model(datos, id)), as.character(productos_ids))
modelos_rf_lista <- setNames(lapply(productos_ids, function(id) train_rf_model(datos, id)), as.character(productos_ids))
modelos_xgb_lista <- setNames(lapply(productos_ids, function(id) train_xgb_model(datos, id)), as.character(productos_ids))
Entrenar modelos de
predicción de precios
# Función para entrenar modelos de predicción de precios
train_price_models <- function(data, product_id, test_size = 0.2) {
price_data <- prepare_price_data(data, product_id) %>%
drop_na() %>%
select(
Precio_Final_Unitario,
Cant, Costo_Venta, Descuento_Porcentaje,
Dia_Semana, Mes_Num, Anio, Dias_Desde_Inicio,
Margen_Unitario
)
# Evitar fallos si hay muy pocos datos
if (nrow(price_data) < 10) {
warning(paste("Producto", product_id, "tiene menos de 10 registros. Se omite."))
return(NULL)
}
set.seed(123)
train_index <- createDataPartition(price_data$Precio_Final_Unitario, p = 1 - test_size, list = FALSE)
train_data <- price_data[train_index, ]
test_data <- price_data[-train_index, ]
# 1. Regresión Lineal
lm_model <- lm(Precio_Final_Unitario ~ ., data = train_data)
# 2. Random Forest
rf_model <- randomForest(
Precio_Final_Unitario ~ .,
data = train_data,
ntree = 500,
importance = TRUE
)
# 3. XGBoost
features <- setdiff(names(train_data), "Precio_Final_Unitario")
x_train <- as.matrix(train_data[, features])
y_train <- train_data$Precio_Final_Unitario
x_test <- as.matrix(test_data[, features])
y_test <- test_data$Precio_Final_Unitario
dtrain <- xgb.DMatrix(data = x_train, label = y_train)
dtest <- xgb.DMatrix(data = x_test, label = y_test)
xgb_params <- list(
objective = "reg:squarederror",
eval_metric = "rmse",
eta = 0.1,
max_depth = 6,
min_child_weight = 3,
subsample = 0.8,
colsample_bytree = 0.8
)
xgb_model <- xgb.train(
params = xgb_params,
data = dtrain,
nrounds = 100,
watchlist = list(train = dtrain, test = dtest),
early_stopping_rounds = 10,
verbose = 0
)
# Evaluación
lm_pred <- predict(lm_model, newdata = test_data)
rf_pred <- predict(rf_model, newdata = test_data)
xgb_pred <- predict(xgb_model, x_test)
lm_rmse <- sqrt(mean((lm_pred - test_data$Precio_Final_Unitario)^2))
rf_rmse <- sqrt(mean((rf_pred - test_data$Precio_Final_Unitario)^2))
xgb_rmse <- sqrt(mean((xgb_pred - test_data$Precio_Final_Unitario)^2))
lm_r2 <- 1 - sum((test_data$Precio_Final_Unitario - lm_pred)^2) /
sum((test_data$Precio_Final_Unitario - mean(test_data$Precio_Final_Unitario))^2)
rf_r2 <- 1 - sum((test_data$Precio_Final_Unitario - rf_pred)^2) /
sum((test_data$Precio_Final_Unitario - mean(test_data$Precio_Final_Unitario))^2)
xgb_r2 <- 1 - sum((test_data$Precio_Final_Unitario - xgb_pred)^2) /
sum((test_data$Precio_Final_Unitario - mean(test_data$Precio_Final_Unitario))^2)
metrics <- data.frame(
Model = c("Linear Regression", "Random Forest", "XGBoost"),
RMSE = c(lm_rmse, rf_rmse, xgb_rmse),
R2 = c(lm_r2, rf_r2, xgb_r2)
)
return(list(metrics = metrics))
}
# IDs de los 5 productos a modelar
productos_ids <- c(155001, 3929788, 3904152, 155002, 3678055)
# Aplicar modelo a cada producto
resultados_modelos <- map(productos_ids, function(id) {
resultado <- train_price_models(datos, product_id = id)
if (!is.null(resultado)) {
resultado$metrics %>% mutate(ID_Inventario = id)
} else {
NULL
}
}) %>% compact() %>% bind_rows()
# Mostrar resultados
resultados_modelos
## Model RMSE R2 ID_Inventario
## 1 Linear Regression 27.8569974 0.9339966 155001
## 2 Random Forest 8.8116639 0.9933959 155001
## 3 XGBoost 4.6368050 0.9981713 155001
## 4 Linear Regression 1.0354097 0.9723146 3929788
## 5 Random Forest 0.7375616 0.9859517 3929788
## 6 XGBoost 0.3441816 0.9969408 3929788
## 7 Linear Regression 78.6144671 0.8453402 3904152
## 8 Random Forest 16.2846491 0.9933636 3904152
## 9 XGBoost 13.1932289 0.9956441 3904152
## 10 Linear Regression 26.4586449 0.9325659 155002
## 11 Random Forest 15.3168150 0.9774014 155002
## 12 XGBoost 6.8971036 0.9954178 155002
## 13 Linear Regression 144.4530566 0.8041448 3678055
## 14 Random Forest 31.0470451 0.9909526 3678055
## 15 XGBoost 20.8998837 0.9959001 3678055
# Lista con los IDs de productos (puedes usar top_ids que ya definiste)
productos_ids <- top_ids
# Entrenar modelos para cada producto y guardar en lista
modelos_precio_lista <- setNames(
lapply(productos_ids, function(id) train_price_models(datos, id)),
as.character(productos_ids)
)
Estimar precios
óptimos
estimate_optimal_prices <- function(data, product_id, price_models, demand_models = NULL, future_dates = NULL) {
price_steps <- 20
best_price_model_idx <- which.max(price_models$metrics$R2)
best_price_model_name <- price_models$metrics$Model[best_price_model_idx]
product_data <- data %>% filter(ID_Inventario == product_id)
min_price <- min(product_data$Precio_Final_Unitario, na.rm = TRUE)
max_price <- max(product_data$Precio_Final_Unitario, na.rm = TRUE)
price_range <- seq(min_price, max_price, length.out = price_steps)
future_scenarios <- data.frame()
for (future_date in future_dates) {
future_date <- as.Date(future_date)
mes_actual <- lubridate::month(future_date)
mes_data <- product_data %>% filter(lubridate::month(Trx_Fecha) == mes_actual)
if (nrow(mes_data) < 5) mes_data <- product_data
costo_mes <- median(mes_data$Costo_Venta, na.rm = TRUE)
cant_mes <- median(mes_data$Cant, na.rm = TRUE)
desc_mes <- median(mes_data$Descuento_Porcentaje, na.rm = TRUE)
if (is.na(costo_mes)) costo_mes <- median(product_data$Costo_Venta, na.rm = TRUE)
if (is.na(cant_mes) || cant_mes == 0) cant_mes <- median(product_data$Cant, na.rm = TRUE)
if (is.na(desc_mes)) desc_mes <- median(product_data$Descuento_Porcentaje, na.rm = TRUE)
date_df <- data.frame(
Trx_Fecha = rep(future_date, price_steps),
Precio_Final_Unitario = price_range,
Cant = cant_mes,
Costo_Venta = costo_mes,
Descuento_Porcentaje = desc_mes,
Dia_Semana = lubridate::wday(future_date),
Mes_Num = mes_actual,
Anio = lubridate::year(future_date),
Dias_Desde_Inicio = as.numeric(difftime(future_date, min(product_data$Trx_Fecha), units = "days")),
Margen_Unitario = NA
)
future_scenarios <- rbind(future_scenarios, date_df)
}
future_scenarios$Margen_Unitario <- future_scenarios$Precio_Final_Unitario -
(future_scenarios$Costo_Venta / future_scenarios$Cant)
product_data <- product_data %>% arrange(Trx_Fecha)
elasticity_df <- product_data %>%
filter(!is.na(Cant) & !is.na(Precio_Final_Unitario)) %>%
mutate(
P_lag = lag(Precio_Final_Unitario),
Q_lag = lag(Cant),
dP = Precio_Final_Unitario - P_lag,
dQ = Cant - Q_lag,
elasticity_point = (dQ / Q_lag) / (dP / P_lag)
) %>%
filter(!is.na(elasticity_point), is.finite(elasticity_point))
elasticity <- median(elasticity_df$elasticity_point, na.rm = TRUE)
if (is.na(elasticity) || !is.finite(elasticity)) elasticity <- 1.5
results <- future_scenarios %>%
mutate(Venta_Esperada = 0, Margen_Total = 0)
for (i in 1:nrow(results)) {
baseline_price <- median(product_data$Precio_Final_Unitario, na.rm = TRUE)
price_ratio <- baseline_price / results$Precio_Final_Unitario[i]
adjusted_quantity <- results$Cant[i] * (price_ratio ^ elasticity)
results$Venta_Esperada[i] <- results$Precio_Final_Unitario[i] * adjusted_quantity
results$Margen_Total[i] <- adjusted_quantity * results$Margen_Unitario[i]
}
optimal_prices <- results %>%
group_by(Trx_Fecha) %>%
slice_max(Venta_Esperada, n = 1) %>%
select(Trx_Fecha, Precio_Optimal = Precio_Final_Unitario, Venta_Esperada, Margen_Total)
return(list(
resultados = results,
precios_optimos = optimal_prices,
elasticidad = elasticity
))
}
Visualizar
resultados
dates_future <- seq.Date(as.Date("2025-01-01"), by = "month", length.out = 6)
precios_optimos_lista <- list()
for (id in productos_ids) {
cat("Estimando precios óptimos para producto:", id, "\n")
modelo_precio <- modelos_precio_lista[[as.character(id)]]
if (!is.null(modelo_precio)) {
precios_optimos_lista[[as.character(id)]] <- estimate_optimal_prices(
data = datos,
product_id = id,
price_models = modelo_precio,
future_dates = dates_future
)
}
}
## Estimando precios óptimos para producto: 155001
## Estimando precios óptimos para producto: 3929788
## Estimando precios óptimos para producto: 3904152
## Estimando precios óptimos para producto: 155002
## Estimando precios óptimos para producto: 3678055
graficas_individuales <- list()
for (id in names(precios_optimos_lista)) {
df_optimo <- precios_optimos_lista[[id]]$precios_optimos
p <- ggplot(df_optimo, aes(x = Trx_Fecha, y = Precio_Optimal)) +
geom_line(color = "#1f77b4", linewidth = 1.2) +
geom_point(color = "#1f77b4", size = 2) +
labs(
title = paste("Precio Óptimo por Mes - Producto", id),
x = "Fecha",
y = "Precio Óptimo"
) +
scale_x_date(date_labels = "%b %Y", date_breaks = "1 month") +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 45, hjust = 1)
)
graficas_individuales[[id]] <- p
}
for (id in names(graficas_individuales)) {
print(graficas_individuales[[id]])
}





Integración de
precios óptimos y modelos
integrate_with_existing_models <- function(data, product_id, price_opt_results,
arma_model = NULL, reg_model = NULL,
rf_model = NULL, xgb_model = NULL) {
optimal_prices <- price_opt_results[[as.character(product_id)]]$precios_optimos
if (is.null(optimal_prices) || nrow(optimal_prices) == 0) {
warning(paste("No se encontraron precios óptimos para el producto", product_id))
return(data.frame())
}
future_data <- data.frame(
Fecha = optimal_prices$Trx_Fecha,
Precio_Final_Unitario = optimal_prices$Precio_Optimal
)
hist_data <- data %>%
filter(ID_Inventario == product_id) %>%
arrange(Trx_Fecha)
future_features <- data.frame()
for (i in 1:nrow(optimal_prices)) {
future_date <- optimal_prices$Trx_Fecha[i]
future_price <- optimal_prices$Precio_Optimal[i]
mes_data <- hist_data %>% filter(lubridate::month(Trx_Fecha) == lubridate::month(future_date))
if (nrow(mes_data) < 5) mes_data <- hist_data
avg_features <- mes_data %>%
summarise(
Cant = median(Cant, na.rm = TRUE),
Costo_Venta = median(Costo_Venta, na.rm = TRUE),
Costo_Devolucion = median(Costo_Devolucion, na.rm = TRUE),
Ajus_Sistema = median(Ajus_Sistema, na.rm = TRUE),
Ajuste_Manual = median(Ajuste_Manual, na.rm = TRUE),
Precio_Lista_Unitario = median(Precio_Lista_Unitario, na.rm = TRUE),
Descuento_Porcentaje = median(Descuento_Porcentaje, na.rm = TRUE),
Semana = lubridate::week(future_date),
Mes = lubridate::month(future_date),
Dia_Semana = lubridate::wday(future_date),
Tiempo = as.numeric(difftime(future_date, min(hist_data$Trx_Fecha), units = "days")) / 30
)
avg_features$Precio_Final_Unitario <- future_price
avg_features$Trx_Fecha <- future_date
future_features <- rbind(future_features, avg_features)
}
if (!is.null(arma_model)) {
arma_forecast <- forecast(arma_model, h = nrow(optimal_prices))
future_data$Venta_ARMA <- as.numeric(arma_forecast$mean)
ref_price <- median(hist_data$Precio_Final_Unitario, na.rm = TRUE)
elasticity <- 1.5
future_data$Venta_ARMA_Ajustada <- future_data$Venta_ARMA *
(ref_price / future_data$Precio_Final_Unitario)^elasticity
}
if (!is.null(reg_model)) {
tryCatch({
future_data$Venta_RegLineal <- predict(reg_model, newdata = future_features)
}, error = function(e) {
future_data$Venta_RegLineal <- NA
})
}
if (!is.null(rf_model)) {
tryCatch({
future_data$Venta_RandomForest <- predict(rf_model, newdata = future_features)
}, error = function(e) {
future_data$Venta_RandomForest <- NA
})
}
if (!is.null(xgb_model)) {
tryCatch({
features <- xgb_model$feature_names
if (is.null(features)) {
features <- setdiff(names(future_features), "Venta")
}
xgb_matrix <- as.matrix(future_features[, features, drop = FALSE])
future_data$Venta_XGBoost <- predict(xgb_model, xgb_matrix)
}, error = function(e) {
future_data$Venta_XGBoost <- NA
})
}
avg_cost_per_unit <- median(hist_data$Costo_Venta / hist_data$Cant, na.rm = TRUE)
for (model in c("ARMA_Ajustada", "RegLineal", "RandomForest", "XGBoost")) {
vcol <- paste0("Venta_", model)
if (vcol %in% names(future_data)) {
ucol <- paste0("Unidades_", model)
ccol <- paste0("Costo_", model)
mcol <- paste0("Margen_", model)
future_data[[ucol]] <- future_data[[vcol]] / future_data$Precio_Final_Unitario
future_data[[ccol]] <- future_data[[ucol]] * avg_cost_per_unit
future_data[[mcol]] <- future_data[[vcol]] - future_data[[ccol]]
}
}
pred_cols <- c("Venta_ARMA_Ajustada", "Venta_RegLineal", "Venta_RandomForest", "Venta_XGBoost")
pred_cols <- pred_cols[pred_cols %in% names(future_data)]
tryCatch({
if (length(pred_cols) > 0 && ncol(future_data[, pred_cols, drop = FALSE]) > 0) {
future_data$Venta_Consenso <- rowMeans(future_data[, pred_cols, drop = FALSE], na.rm = TRUE)
future_data$Unidades_Consenso <- future_data$Venta_Consenso / future_data$Precio_Final_Unitario
future_data$Costo_Consenso <- future_data$Unidades_Consenso * avg_cost_per_unit
future_data$Margen_Consenso <- future_data$Venta_Consenso - future_data$Costo_Consenso
}
}, error = function(e) {
warning(paste("No se pudo calcular el consenso para producto", product_id, ":", e$message))
})
return(future_data)
}
resultados_futuros_lista <- list()
for (id in productos_ids) {
cat("Integrando modelos para producto:", id, "\n")
resultado <- integrate_with_existing_models(
data = datos,
product_id = id,
price_opt_results = precios_optimos_lista,
arma_model = modelos_arma_lista[[as.character(id)]],
reg_model = modelos_reg_lista[[as.character(id)]],
rf_model = modelos_rf_lista[[as.character(id)]],
xgb_model = modelos_xgb_lista[[as.character(id)]]
)
resultados_futuros_lista[[as.character(id)]] <- resultado
}
## Integrando modelos para producto: 155001
## Integrando modelos para producto: 3929788
## Integrando modelos para producto: 3904152
## Integrando modelos para producto: 155002
## Integrando modelos para producto: 3678055
Pipeline
correcto
corregir_formato_fechas <- function(datos) {
if ("Trx_Fecha" %in% colnames(datos)) {
datos$Trx_Fecha_Original <- datos$Trx_Fecha
if (is.character(datos$Trx_Fecha) &&
any(grepl("^\\d{7}-\\d{2}-\\d{2}$", datos$Trx_Fecha))) {
cat("Corrigiendo formato de fechas extraño...\n")
datos$Trx_Fecha <- sapply(datos$Trx_Fecha, function(fecha) {
if (is.na(fecha) || !is.character(fecha)) return(NA)
partes <- strsplit(fecha, "-")[[1]]
if (length(partes) == 3) {
fecha_corregida <- paste("2023", partes[2], partes[3], sep = "-")
return(fecha_corregida)
} else {
return(NA)
}
})
datos$Trx_Fecha <- as.Date(datos$Trx_Fecha)
cat("Fechas corregidas exitosamente.\n")
} else if (!inherits(datos$Trx_Fecha, "Date")) {
cat("Intentando convertir fechas a formato Date...\n")
datos$Trx_Fecha <- as.Date(datos$Trx_Fecha)
}
}
return(datos)
}
# Aplicar la corrección a tu dataframe antes de usarlo
datos_filtrados <- corregir_formato_fechas(datos_filtrados)
## Intentando convertir fechas a formato Date...
dates_future <- seq.Date(as.Date("2023-01-01"), by = "month", length.out = 6)
precios_optimos_lista <- list()
for (id in productos_ids) {
cat("Estimando precios óptimos para producto:", id, "\n")
modelo_precio <- modelos_precio_lista[[as.character(id)]]
if (!is.null(modelo_precio)) {
precios_optimos_lista[[as.character(id)]] <- estimate_optimal_prices(
data = datos_filtrados,
product_id = id,
price_models = modelo_precio,
future_dates = dates_future
)
}
}
## Estimando precios óptimos para producto: 155001
## Estimando precios óptimos para producto: 3929788
## Estimando precios óptimos para producto: 3904152
## Estimando precios óptimos para producto: 155002
## Estimando precios óptimos para producto: 3678055
for (id in names(precios_optimos_lista)) {
df_optimo <- precios_optimos_lista[[id]]$precios_optimos
if (!inherits(df_optimo$Trx_Fecha, "Date")) {
df_optimo$Trx_Fecha <- as.Date(df_optimo$Trx_Fecha)
}
p <- ggplot(df_optimo, aes(x = Trx_Fecha, y = Precio_Optimal)) +
geom_line(color = "#1f77b4", linewidth = 1.2) +
geom_point(color = "#1f77b4", size = 2) +
labs(
title = paste("Precio Óptimo por Mes - Producto", id),
x = "Fecha",
y = "Precio Óptimo"
) +
scale_x_date(date_labels = "%b %Y", date_breaks = "1 month") +
theme_minimal(base_size = 12) +
theme(
plot.title = element_text(face = "bold"),
axis.text.x = element_text(angle = 45, hjust = 1)
)
print(p)
}





# Función para correr optimización de precios para todos los productos
run_price_optimization <- function(data, product_ids, future_dates = NULL, modelos_precio_lista = NULL) {
if (is.null(future_dates)) {
future_dates <- seq.Date(Sys.Date(), by = "month", length.out = 6)
}
precios_optimos_lista <- list()
for (id in product_ids) {
cat("Estimando precios óptimos para producto:", id, "\n")
price_model <- NULL
if (!is.null(modelos_precio_lista)) {
price_model <- modelos_precio_lista[[as.character(id)]]
}
precios_optimos_lista[[as.character(id)]] <- estimate_optimal_prices(
data = data,
product_id = id,
price_models = price_model,
future_dates = future_dates
)
}
return(precios_optimos_lista)
}
# Función principal que integra todo el pipeline
run_complete_analysis <- function(data, top_ids, modelos_arma, modelos_reg, modelos_rf, modelos_xgb, modelos_precio_lista = NULL) {
# 1. Ejecutar optimización de precios para todos los productos
all_results <- run_price_optimization(data, top_ids, modelos_precio_lista = modelos_precio_lista)
# 2. Integrar con modelos existentes para cada producto
integrated_results <- list()
for (i in seq_along(top_ids)) {
pid <- top_ids[i]
pid_str <- as.character(pid)
arma_model <- if(length(modelos_arma) >= i) modelos_arma[[i]] else NULL
reg_model <- if(length(modelos_reg) >= i) modelos_reg[[i]] else NULL
rf_model <- if(length(modelos_rf) >= i) modelos_rf[[i]] else NULL
xgb_model <- if(length(modelos_xgb) >= i) modelos_xgb[[i]] else NULL
future_predictions <- integrate_with_existing_models(
data = data,
product_id = pid,
price_opt_results = all_results,
arma_model = arma_model,
reg_model = reg_model,
rf_model = rf_model,
xgb_model = xgb_model
)
integrated_results[[pid_str]] <- future_predictions
if (nrow(future_predictions) > 0) {
p_sales <- ggplot(future_predictions)
if ("Venta_ARMA_Ajustada" %in% names(future_predictions)) {
p_sales <- p_sales + geom_point(aes(x = Fecha, y = Venta_ARMA_Ajustada, color = "ARMA"), size = 3, na.rm = TRUE)
}
if ("Venta_RegLineal" %in% names(future_predictions)) {
p_sales <- p_sales + geom_point(aes(x = Fecha, y = Venta_RegLineal, color = "Regresión Lineal"), size = 3, na.rm = TRUE)
}
if ("Venta_RandomForest" %in% names(future_predictions)) {
p_sales <- p_sales + geom_point(aes(x = Fecha, y = Venta_RandomForest, color = "Random Forest"), size = 3, na.rm = TRUE)
}
if ("Venta_XGBoost" %in% names(future_predictions)) {
p_sales <- p_sales + geom_point(aes(x = Fecha, y = Venta_XGBoost, color = "XGBoost"), size = 3, na.rm = TRUE)
}
if ("Venta_Consenso" %in% names(future_predictions)) {
p_sales <- p_sales + geom_line(aes(x = Fecha, y = Venta_Consenso, color = "Consenso"), size = 1.5)
}
p_sales <- p_sales +
labs(
title = paste("Predicciones de ventas con precios óptimos - Producto", pid),
x = "Fecha",
y = "Ventas estimadas ($)",
color = "Modelo"
) +
theme_minimal() +
theme(
plot.title = element_text(face = "bold"),
axis.title = element_text(face = "bold"),
legend.position = "bottom"
)
p_margins <- ggplot(future_predictions)
if ("Margen_Consenso" %in% names(future_predictions)) {
p_margins <- p_margins +
geom_col(aes(x = Fecha, y = Margen_Consenso), fill = "steelblue", width = 15) +
geom_text(aes(x = Fecha, y = Margen_Consenso, label = round(Margen_Consenso, 0)),
vjust = -0.5, size = 3.5)
}
p_margins <- p_margins +
labs(
title = paste("Margen esperado con precios óptimos - Producto", pid),
x = "Fecha",
y = "Margen estimado ($)"
) +
theme_minimal() +
theme(
plot.title = element_text(face = "bold"),
axis.title = element_text(face = "bold")
)
all_results[[pid_str]]$integrated_plots <- list(
sales = p_sales,
margins = p_margins
)
}
}
# 3. Visualizar resultados comparativos
all_optimal_prices <- data.frame()
for (pid in top_ids) {
pid_str <- as.character(pid)
if (pid_str %in% names(all_results)) {
opt_prices <- all_results[[pid_str]]$precios_optimos %>%
mutate(ID_Inventario = pid)
all_optimal_prices <- rbind(all_optimal_prices, opt_prices)
}
}
p_comparison <- ggplot(all_optimal_prices,
aes(x = Trx_Fecha, y = Precio_Optimal, color = factor(ID_Inventario))) +
geom_line(size = 1.2) +
geom_point(size = 3) +
labs(
title = "Comparación de Precios Óptimos por Producto",
x = "Fecha",
y = "Precio Óptimo",
color = "ID Producto"
) +
theme_minimal() +
theme(
plot.title = element_text(face = "bold"),
axis.title = element_text(face = "bold"),
legend.position = "bottom"
)
metricas_optimas <- data.frame()
for (pid in top_ids) {
pid_str <- as.character(pid)
if (pid_str %in% names(integrated_results)) {
pred_data <- integrated_results[[pid_str]]
if ("Margen_Consenso" %in% names(pred_data)) {
metrics_row <- data.frame(
ID_Inventario = pid,
Precio_Promedio = mean(pred_data$Precio_Final_Unitario, na.rm = TRUE),
Venta_Total = sum(pred_data$Venta_Consenso, na.rm = TRUE),
Margen_Total = sum(pred_data$Margen_Consenso, na.rm = TRUE),
Margen_Porcentual = 100 * sum(pred_data$Margen_Consenso, na.rm = TRUE) /
sum(pred_data$Venta_Consenso, na.rm = TRUE)
)
metricas_optimas <- rbind(metricas_optimas, metrics_row)
}
}
}
return(list(
resultados = all_results,
integracion = integrated_results,
precios_optimos = all_optimal_prices,
metricas_optimas = metricas_optimas,
grafico_comparativo = p_comparison
))
}
# Ejecutar el análisis completo
resultado_completo <- run_complete_analysis(
data = datos,
top_ids = productos_ids,
modelos_arma = modelos_arma_lista,
modelos_reg = modelos_reg_lista,
modelos_rf = modelos_rf_lista,
modelos_xgb = modelos_xgb_lista,
modelos_precio_lista = modelos_precio_lista # Pasa esta lista si la tienes, o NULL
)
## Estimando precios óptimos para producto: 155001
## Estimando precios óptimos para producto: 3929788
## Estimando precios óptimos para producto: 3904152
## Estimando precios óptimos para producto: 155002
## Estimando precios óptimos para producto: 3678055
Gráfico comparativo
de precios óptimos por producto:
# Mostrar métricas si estás en modo interactivo
if (interactive()) View(resultado_completo$metricas_optimas)
cat("Gráfico comparativo de precios óptimos por producto:\n")
## Gráfico comparativo de precios óptimos por producto:
print(resultado_completo$grafico_comparativo)

LS0tDQp0aXRsZTogIjxzcGFuIHN0eWxlPSdjb2xvcjogYnJvd247Jz5FVklERU5DSUEgTk9WRU08L3NwYW4+Ig0KYXV0aG9yOiAiRXF1aXBvIDQiDQpkYXRlOiAiMjAyNC0wMy0wNCINCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUgDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIHRoZW1lOiBqb3VybmFsDQogICAgbnVtYmVyX3NlY3Rpb25zOiB0cnVlDQplZGl0b3JfczogDQogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlDQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlDQotLS0NCg0KIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsYWNrOyI+ICBMaWJyZXJpYXMgPC9zcGFuPiANCg0KDQpgYGB7cn0NCiMgTGlicmVyw61hcyBuZWNlc2FyaWFzDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkocmVhZHhsKQ0KbGlicmFyeShwdXJycikNCmxpYnJhcnkoa25pdHIpDQojaW5zdGFsbC5wYWNrYWdlcygia2FibGVFeHRyYSIpDQpsaWJyYXJ5KGthYmxlRXh0cmEpDQpsaWJyYXJ5KGdncGxvdDIpDQojaW5zdGFsbC5wYWNrYWdlcygiaWdyYXBoIikNCmxpYnJhcnkoaWdyYXBoKQ0KI2luc3RhbGwucGFja2FnZXMoImZvcmVjYXN0IikNCiNpbnN0YWxsLnBhY2thZ2VzKCJsdWJyaWRhdGUiKQ0KbGlicmFyeShmb3JlY2FzdCkNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShjb3JycGxvdCkNCmxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KI2luc3RhbGwucGFja2FnZXMoImdnY29ycnBsb3QiKQ0KbGlicmFyeShnZ2NvcnJwbG90KQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoY2FyKQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQojaW5zdGFsbC5wYWNrYWdlcygieGdib29zdCIpDQpsaWJyYXJ5KHhnYm9vc3QpDQojaW5zdGFsbC5wYWNrYWdlcygicGF0Y2h3b3JrIikNCmxpYnJhcnkocGF0Y2h3b3JrKQ0KYGBgDQoNCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibGFjazsiPiAgQ2FyZ2EgZGUgZGF0b3MgPC9zcGFuPiANCg0KYGBge3J9DQojIENhcmdhciBhcmNoaXZvIEV4Y2VsIGRlc2RlIHJ1dGEgbG9jYWwNCnJ1dGEgPC0gIkM6XFxVc2Vyc1xcYWxtYWlcXERvd25sb2Fkc1xcdG9wXzVfcHJvZHVjdHMueGxzeCINCmRhdG9zIDwtIHJlYWRfZXhjZWwocnV0YSkNCg0KIyBWaXN0YSBnZW5lcmFsDQpoZWFkKGRhdG9zKQ0Kc3RyKGRhdG9zKQ0KYGBgDQoNCmBgYHtyfQ0KIyBPYnRlbmVyIGxvcyA1IHByb2R1Y3RvcyBtw6FzIHZlbmRpZG9zIChwb3IgdmFsb3IpDQp0b3BfaWRzIDwtIGRhdG9zICU+JQ0KICBncm91cF9ieShJRF9JbnZlbnRhcmlvKSAlPiUNCiAgc3VtbWFyaXNlKFZlbnRhc19Ub3RhbGVzID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgYXJyYW5nZShkZXNjKFZlbnRhc19Ub3RhbGVzKSkgJT4lDQogIHNsaWNlX2hlYWQobiA9IDUpICU+JQ0KICBwdWxsKElEX0ludmVudGFyaW8pDQoNCnByaW50KCJUb3AgNSBwcm9kdWN0b3MgbcOhcyB2ZW5kaWRvcyAoSURfSW52ZW50YXJpbyk6IikNCnByaW50KHRvcF9pZHMpDQoNCmBgYA0KDQpgYGB7cn0NCiMgRmlsdHJhciBkYXRvcyB2w6FsaWRvcw0KZGF0b3NfZmlsdHJhZG9zIDwtIGRhdG9zICU+JQ0KICBmaWx0ZXIoSURfSW52ZW50YXJpbyAlaW4lIHRvcF9pZHMpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKFByZWNpb19GaW5hbF9Vbml0YXJpbykpDQoNCiMgQ29udGFyIG9ic2VydmFjaW9uZXMgcG9yIHByb2R1Y3RvDQpjb250ZW8gPC0gZGF0b3NfZmlsdHJhZG9zICU+JQ0KICBjb3VudChJRF9JbnZlbnRhcmlvLCBzb3J0ID0gVFJVRSkNCg0KcHJpbnQoIk7Dum1lcm8gZGUgcmVnaXN0cm9zIHBvciBwcm9kdWN0byBlbiBkYXRvc19maWx0cmFkb3M6IikNCnByaW50KGNvbnRlbykNCg0KIyBWZXJpZmljYSBzaSBoYXkgc3VmaWNpZW50ZXMgZGF0b3MNCmlmIChucm93KGRhdG9zX2ZpbHRyYWRvcykgPT0gMCkgew0KICBzdG9wKCJObyBoYXkgZGF0b3Mgc3VmaWNpZW50ZXMgbHVlZ28gZGUgZmlsdHJhciBwb3IgdG9wX2lkcyB5IHByZWNpb3MgdsOhbGlkb3MuIikNCn0NCg0KYGBgDQoNCmBgYHtyfQ0KIyBDb21iaW5hY2lvbmVzIGRlIHBhcmVzDQpwcm9kdWN0b3MgPC0gdW5pcXVlKGRhdG9zX2ZpbHRyYWRvcyRJRF9JbnZlbnRhcmlvKQ0KcGFyZXNfcHJvZHVjdG9zIDwtIGNvbWJuKHByb2R1Y3RvcywgMiwgc2ltcGxpZnkgPSBGQUxTRSkNCg0KIyBJbmljaWFsaXphciByZXN1bHRhZG9zDQpyZXN1bHRhZG9zX2tzIDwtIG1hcF9kZihwYXJlc19wcm9kdWN0b3MsIGZ1bmN0aW9uKHBhcikgew0KICBwcm9kMSA8LSBwYXJbMV0NCiAgcHJvZDIgPC0gcGFyWzJdDQogIA0KICBwcmVjaW9zMSA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogICAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZDEpICU+JQ0KICAgIHB1bGwoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKQ0KICANCiAgcHJlY2lvczIgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQ0KICAgIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2QyKSAlPiUNCiAgICBwdWxsKFByZWNpb19GaW5hbF9Vbml0YXJpbykNCiAgDQogIHByaW50KHBhc3RlKCJDb21wYXJhbmRvIHByb2R1Y3RvcyIsIHByb2QxLCAidnMiLCBwcm9kMikpDQogIHByaW50KHBhc3RlKCJDYW50aWRhZCBkZSBwcmVjaW9zOiIsIGxlbmd0aChwcmVjaW9zMSksICJ5IiwgbGVuZ3RoKHByZWNpb3MyKSkpDQogIA0KICBpZiAobGVuZ3RoKHByZWNpb3MxKSA+PSA1ICYgbGVuZ3RoKHByZWNpb3MyKSA+PSA1KSB7DQogICAgcHJ1ZWJhIDwtIHN1cHByZXNzV2FybmluZ3Moa3MudGVzdChwcmVjaW9zMSwgcHJlY2lvczIpKQ0KICAgIGRhdGEuZnJhbWUoDQogICAgICBQcm9kdWN0b18xID0gcHJvZDEsDQogICAgICBQcm9kdWN0b18yID0gcHJvZDIsDQogICAgICBEID0gcm91bmQocHJ1ZWJhJHN0YXRpc3RpYywgNCksDQogICAgICBwX3ZhbHVlID0gcm91bmQocHJ1ZWJhJHAudmFsdWUsIDQpLA0KICAgICAgQ29uY2x1c2lvbiA9IGlmZWxzZShwcnVlYmEkcC52YWx1ZSA+IDAuMDUsICJEaXN0cmlidWNpb25lcyBzaW1pbGFyZXMiLCAiRGlzdHJpYnVjaW9uZXMgZGlmZXJlbnRlcyIpDQogICAgKQ0KICB9IGVsc2Ugew0KICAgIGRhdGEuZnJhbWUoDQogICAgICBQcm9kdWN0b18xID0gcHJvZDEsDQogICAgICBQcm9kdWN0b18yID0gcHJvZDIsDQogICAgICBEID0gTkEsDQogICAgICBwX3ZhbHVlID0gTkEsDQogICAgICBDb25jbHVzaW9uID0gIkRhdG9zIGluc3VmaWNpZW50ZXMiDQogICAgKQ0KICB9DQp9KQ0KDQpwcmludCgiUmVzdWx0YWRvcyBkZSBsYSBwcnVlYmEgS1M6IikNCnByaW50KHJlc3VsdGFkb3Nfa3MpDQoNCmBgYA0KDQpgYGB7cn0NCg0KIyBGaWx0cmFyIGxvcyBwcm9kdWN0b3MNCmRmXzE1NTAwMSA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDE1NTAwMSkgJT4lDQogIHNlbGVjdChQcmVjaW9fRmluYWxfVW5pdGFyaW8pICU+JQ0KICBtdXRhdGUoUHJvZHVjdG8gPSAiMTU1MDAxIikNCg0KZGZfMTU1MDAyIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUNCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMTU1MDAyKSAlPiUNCiAgc2VsZWN0KFByZWNpb19GaW5hbF9Vbml0YXJpbykgJT4lDQogIG11dGF0ZShQcm9kdWN0byA9ICIxNTUwMDIiKQ0KDQojIFVuaXIgZW4gdW4gc29sbyBkYXRhZnJhbWUNCmRmX2VjZGYgPC0gYmluZF9yb3dzKGRmXzE1NTAwMSwgZGZfMTU1MDAyKQ0KDQojIEdyYWZpY2FyIEVDREYNCmdncGxvdChkZl9lY2RmLCBhZXMoeCA9IFByZWNpb19GaW5hbF9Vbml0YXJpbywgY29sb3IgPSBQcm9kdWN0bykpICsNCiAgc3RhdF9lY2RmKGdlb20gPSAic3RlcCIsIHNpemUgPSAxKSArDQogIGxhYnModGl0bGUgPSAiRUNERiBkZSBQcmVjaW8gRmluYWwgVW5pdGFyaW86IFByb2R1Y3RvcyAxNTUwMDEgdnMgMTU1MDAyIiwNCiAgICAgICB4ID0gIlByZWNpbyBGaW5hbCBVbml0YXJpbyIsDQogICAgICAgeSA9ICJGdW5jacOzbiBkZSBEaXN0cmlidWNpw7NuIEFjdW11bGFkYSAoRUNERikiLA0KICAgICAgIGNvbG9yID0gIlByb2R1Y3RvIikgKw0KICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KQ0KDQpgYGANCg0KDQo8IS0tIEFSTUEgLS0+DQojIEFSTUEgDQoNCiMgUFJFRElDQ0lPTkVTIERFIFZFTlRBUw0KDQoNCjwhLS0gUFJPRFVDVE8gMTU1MDAxIC0tPg0KIyMgUFJPRFVDVE8gMTU1MDAxDQpgYGB7cn0NCmNvbG5hbWVzKGRhdG9zX2ZpbHRyYWRvcykNCg0KYGBgDQoNCmBgYHtyfQ0KIyBDcmVhciBzZXJpZSBkZSB0aWVtcG8gY29uIGJhc2UgZW4gVmVudGFzIGVuIGRpbmVybyAocGFyYSBwcm9kdWN0byAxNTUwMDEpDQp2ZW50YXNfbWVuc3VhbGVzIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUNCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gMTU1MDAxKSAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpKSAlPiUgICMg8J+RiCBzZSBmdWVyemEgYSBEYXRlDQogIGdyb3VwX2J5KEZlY2hhKSAlPiUNCiAgc3VtbWFyaXNlKFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgYXJyYW5nZShGZWNoYSkNCg0KIyBDcmVhciBzZXJpZSBkZSB0aWVtcG8NCnNlcmllX3RzIDwtIHRzKHZlbnRhc19tZW5zdWFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDEyLCBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpLCBtb250aChtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpKSkNCg0KIyBHcsOhZmljbyBlc3RhY2lvbmFsIHBvciBtZXMgY29uIFZlbnRhcw0KZ2dzZWFzb25wbG90KHNlcmllX3RzLCB5ZWFyLmxhYmVscyA9IFRSVUUpICsNCiAgbGFicyh0aXRsZSA9ICJHcsOhZmljbyBlc3RhY2lvbmFsIHBvciBtZXMiLCB5ID0gIlZlbnRhcyAoZGluZXJvKSIsIHggPSAiTWVzIikNCmBgYA0KDQpgYGB7cn0NCmFjZihzZXJpZV90cykNCnBhY2Yoc2VyaWVfdHMpDQpgYGANCg0KDQpgYGB7cn0NCm1vZGVsb19hcm1hIDwtIGF1dG8uYXJpbWEoc2VyaWVfdHMsIHNlYXNvbmFsID0gRkFMU0UsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkNCmFzc2lnbihwYXN0ZTAoIm1vZGVsb19hcm1hXyIsIDE1NTAwMSksIG1vZGVsb19hcm1hLCBlbnZpciA9IC5HbG9iYWxFbnYpDQoNCm1vZGVsb19zYXJpbWEgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBUUlVFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpDQoNCmNhdCgiQUlDIEFSTUE6IiwgbW9kZWxvX2FybWEkYWljLCAiXG4iKQ0KY2F0KCJBSUMgU0FSSU1BOiIsIG1vZGVsb19zYXJpbWEkYWljLCAiXG4iKQ0KDQppZiAobW9kZWxvX2FybWEkYWljIDwgbW9kZWxvX3NhcmltYSRhaWMpIHsNCiAgbWVqb3JfbW9kZWxvIDwtIG1vZGVsb19hcm1hDQogIG1vZGVsb191c2FkbyA8LSAiQVJNQSINCn0gZWxzZSBpZiAobW9kZWxvX3NhcmltYSRhaWMgPCBtb2RlbG9fYXJtYSRhaWMpIHsNCiAgbWVqb3JfbW9kZWxvIDwtIG1vZGVsb19zYXJpbWENCiAgbW9kZWxvX3VzYWRvIDwtICJTQVJJTUEiDQp9IGVsc2Ugew0KICBtZWpvcl9tb2RlbG8gPC0gbW9kZWxvX2FybWENCiAgbW9kZWxvX3VzYWRvIDwtICJBUk1BIChwb3IgZW1wYXRlKSINCn0NCg0KZm9yZWNhc3RfbW9kZWxvIDwtIGZvcmVjYXN0KG1lam9yX21vZGVsbywgaCA9IDMpDQoNCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKw0KICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0iLCBtb2RlbG9fdXNhZG8sICIoUHJvZHVjdG8gMTU1MDAxKSIpLA0KICAgICAgIHggPSAiTWVzIiwgeSA9ICJWZW50YXMgKCQpIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KYGBgDQoNCmBgYHtyfQ0KdmVudGFzX2hpc3RvcmljYXMgPC0gdmVudGFzX21lbnN1YWxlcyAlPiUNCiAgc2VsZWN0KEZlY2hhLCBWZW50YSkgJT4lDQogIG11dGF0ZSgNCiAgICBUaXBvID0gIkhpc3TDs3JpY28iLA0KICAgIElDX0luZmVyaW9yID0gTkEsDQogICAgSUNfU3VwZXJpb3IgPSBOQQ0KICApDQoNCnByZWRpY2Npb25lcyA8LSBkYXRhLmZyYW1lKA0KICBGZWNoYSA9IHNlcS5EYXRlKGZyb20gPSBtYXgodmVudGFzX2hpc3RvcmljYXMkRmVjaGEpICVtKyUgbW9udGhzKDEpLA0KICAgICAgICAgICAgICAgICAgIGJ5ID0gIm1vbnRoIiwgbGVuZ3RoLm91dCA9IDMpLA0KICBWZW50YSA9IGFzLm51bWVyaWMoZm9yZWNhc3RfbW9kZWxvJG1lYW4pLA0KICBJQ19JbmZlcmlvciA9IGFzLm51bWVyaWMoZm9yZWNhc3RfbW9kZWxvJGxvd2VyWywyXSksDQogIElDX1N1cGVyaW9yID0gYXMubnVtZXJpYyhmb3JlY2FzdF9tb2RlbG8kdXBwZXJbLDJdKSwNCiAgVGlwbyA9ICJQcm9uw7NzdGljbyINCikNCg0KdGFibGFfY29tcGxldGEgPC0gYmluZF9yb3dzKHZlbnRhc19oaXN0b3JpY2FzLCBwcmVkaWNjaW9uZXMpICU+JQ0KICBhcnJhbmdlKEZlY2hhKQ0KDQp0YWJsYV9jb21wbGV0YSAlPiUNCiAga2FibGUoZGlnaXRzID0gMiwgY2FwdGlvbiA9ICJWZW50YXMgbWVuc3VhbGVzIHkgcHJvbsOzc3RpY28gLSBQcm9kdWN0byAxNTUwMDEiKSAlPiUNCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpDQoNCmBgYA0KDQpgYGB7cn0NCiMgTW9zdHJhciByZXN1bWVuIGRlbCBtb2RlbG8geSBkaWFnbsOzc3RpY28NCmNhdCgiTW9kZWxvIHNlbGVjY2lvbmFkbzoiLCBtb2RlbG9fdXNhZG8sICJcbiIpDQpzdW1tYXJ5KG1lam9yX21vZGVsbykNCmNoZWNrcmVzaWR1YWxzKG1lam9yX21vZGVsbykNCg0KYGBgDQoNCmBgYHtyfQ0KIyBVc2FyIGxvcyB2YWxvcmVzIGFqdXN0YWRvcyBkZWwgbW9kZWxvIHBhcmEgY2FsY3VsYXIgbGFzIG3DqXRyaWNhcw0KDQojIE9idGVuZXIgbG9zIHZhbG9yZXMgYWp1c3RhZG9zIChmaXR0ZWQgdmFsdWVzKQ0KZml0dGVkX3ZhbHVlcyA8LSBmaXR0ZWQobWVqb3JfbW9kZWxvKQ0KDQojIENhbGN1bGFyIFJNU0UgKFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yKQ0Kcm1zZV9tb2RlbG8gPC0gc3FydChtZWFuKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpXjIpKQ0KDQojIENhbGN1bGFyIE1BUEUgKE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvcikgLSBjb24gcHJvdGVjY2nDs24gY29udHJhIGRpdmlzacOzbiBwb3IgY2Vybw0KbWFwZV9tb2RlbG8gPC0gbWVhbihhYnMoKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcykgLyBwbWF4KHNlcmllX3RzLCAwLjAxKSkpICogMTAwDQoNCiMgQ2FsY3VsYXIgTVNFIChNZWFuIFNxdWFyZWQgRXJyb3IpDQptc2VfbW9kZWxvIDwtIG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikNCg0KIyBDYWxjdWxhciBSwrINCnNzZV9tb2RlbG8gPC0gc3VtKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpXjIpDQpzc3RfbW9kZWxvIDwtIHN1bSgoc2VyaWVfdHMgLSBtZWFuKHNlcmllX3RzKSleMikNCnIyX21vZGVsbyA8LSAxIC0gKHNzZV9tb2RlbG8gLyBzc3RfbW9kZWxvKQ0KDQojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcw0KY2F0KCJSwrIgZGVsIG1vZGVsbyIsIG1vZGVsb191c2FkbywgInBhcmEgMTU1MDAxOiIsIHIyX21vZGVsbywgIlxuIikNCmNhdCgiQUlDIGRlbCBtb2RlbG8iLCBtb2RlbG9fdXNhZG8sICJwYXJhIDE1NTAwMToiLCBtZWpvcl9tb2RlbG8kYWljLCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8iLCBtb2RlbG9fdXNhZG8sICJwYXJhIDE1NTAwMToiLCBybXNlX21vZGVsbywgIlxuIikNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIiwgbW9kZWxvX3VzYWRvLCAicGFyYSAxNTUwMDE6IiwgbWFwZV9tb2RlbG8sICJcbiIpDQpjYXQoIk1TRSBkZWwgbW9kZWxvIiwgbW9kZWxvX3VzYWRvLCAicGFyYSAxNTUwMDE6IiwgbXNlX21vZGVsbywgIlxuIikNCmBgYA0KDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgQVJNQS9TQVJJTUEgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgUjIgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIE1TRSA9IG51bWVyaWMoKSwNCiAgICBBSUMgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMTU1MDAxIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvDQogIE1vZGVsbyA9ICJBUk1BL1NBUklNQSIsDQogIFIyID0gcjJfbW9kZWxvLA0KICBSTVNFID0gcm1zZV9tb2RlbG8sDQogIE1BUEUgPSBtYXBlX21vZGVsbywNCiAgTVNFID0gbXNlX21vZGVsbywNCiAgQUlDID0gbWVqb3JfbW9kZWxvJGFpYw0KKSkNCmBgYA0KDQoNCg0KPCEtLSBQUk9EVUNUTyAzOTI5Nzg4IC0tPg0KIyMgUFJPRFVDVE8gMzkyOTc4OA0KYGBge3J9DQppZF9wcm9kdWN0byA8LSAzOTI5Nzg4DQoNCnZlbnRhc19tZW5zdWFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQ0KICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBpZF9wcm9kdWN0bykgJT4lDQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lDQogIGdyb3VwX2J5KEZlY2hhKSAlPiUNCiAgc3VtbWFyaXNlKFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpLCAuZ3JvdXBzID0gImRyb3AiKSAlPiUNCiAgYXJyYW5nZShGZWNoYSkNCg0Kc2VyaWVfdHMgPC0gdHModmVudGFzX21lbnN1YWxlcyRWZW50YSwNCiAgICAgICAgICAgICAgIGZyZXF1ZW5jeSA9IDEyLA0KICAgICAgICAgICAgICAgc3RhcnQgPSBjKHllYXIobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSwgbW9udGgobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSkpDQpgYGANCg0KYGBge3J9DQptb2RlbG9fYXJtYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpDQphc3NpZ24ocGFzdGUwKCJtb2RlbG9fYXJtYV8iLCAzOTI5Nzg4KSwgbW9kZWxvX2FybWEsIGVudmlyID0gLkdsb2JhbEVudikNCg0KDQptb2RlbG9fc2FyaW1hIDwtIGF1dG8uYXJpbWEoc2VyaWVfdHMsIHNlYXNvbmFsID0gVFJVRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQ0KDQpjYXQoIkFJQyBBUk1BOiIsIG1vZGVsb19hcm1hJGFpYywgIlxuIikNCmNhdCgiQUlDIFNBUklNQToiLCBtb2RlbG9fc2FyaW1hJGFpYywgIlxuIikNCg0KaWYgKG1vZGVsb19hcm1hJGFpYyA8IG1vZGVsb19zYXJpbWEkYWljKSB7DQogIG1lam9yX21vZGVsbyA8LSBtb2RlbG9fYXJtYQ0KICBtb2RlbG9fdXNhZG8gPC0gIkFSTUEiDQp9IGVsc2UgaWYgKG1vZGVsb19zYXJpbWEkYWljIDwgbW9kZWxvX2FybWEkYWljKSB7DQogIG1lam9yX21vZGVsbyA8LSBtb2RlbG9fc2FyaW1hDQogIG1vZGVsb191c2FkbyA8LSAiU0FSSU1BIg0KfSBlbHNlIHsNCiAgbWVqb3JfbW9kZWxvIDwtIG1vZGVsb19hcm1hDQogIG1vZGVsb191c2FkbyA8LSAiQVJNQSAocG9yIGVtcGF0ZSkiDQp9DQpmb3JlY2FzdF9tb2RlbG8gPC0gZm9yZWNhc3QobWVqb3JfbW9kZWxvLCBoID0gMykNCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKw0KICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0iLCBtb2RlbG9fdXNhZG8sICIoUHJvZHVjdG8gMzkyOTc4OCkiKSwNCiAgICAgICB4ID0gIk1lcyIsIHkgPSAiVmVudGFzICgkKSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCmBgYA0KDQpgYGB7cn0NCnByZWRpY2Npb25lcyA8LSBkYXRhLmZyYW1lKA0KICBGZWNoYSA9IHNlcS5EYXRlKGZyb20gPSBtYXgodmVudGFzX2hpc3RvcmljYXMkRmVjaGEpICVtKyUgbW9udGhzKDEpLA0KICAgICAgICAgICAgICAgICAgIGJ5ID0gIm1vbnRoIiwgbGVuZ3RoLm91dCA9IDMpLA0KICBWZW50YSA9IGFzLm51bWVyaWMoZm9yZWNhc3RfbW9kZWxvJG1lYW4pLA0KICBJQ19JbmZlcmlvciA9IGFzLm51bWVyaWMoZm9yZWNhc3RfbW9kZWxvJGxvd2VyWywyXSksDQogIElDX1N1cGVyaW9yID0gYXMubnVtZXJpYyhmb3JlY2FzdF9tb2RlbG8kdXBwZXJbLDJdKSwNCiAgVGlwbyA9ICJQcm9uw7NzdGljbyINCikNCg0KdGFibGFfY29tcGxldGEgPC0gYmluZF9yb3dzKHZlbnRhc19oaXN0b3JpY2FzLCBwcmVkaWNjaW9uZXMpICU+JQ0KICBhcnJhbmdlKEZlY2hhKQ0KDQojIE1vc3RyYXIgdGFibGENCnRhYmxhX2NvbXBsZXRhICU+JQ0KICBrYWJsZShkaWdpdHMgPSAyLCBjYXB0aW9uID0gIlZlbnRhcyBtZW5zdWFsZXMgeSBwcm9uw7NzdGljbyAtIFByb2R1Y3RvIDM5Mjk3ODgiKSAlPiUNCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpDQpgYGANCg0KYGBge3J9DQojIE1vc3RyYXIgcmVzdW1lbiBkZWwgbW9kZWxvIHkgZGlhZ27Ds3N0aWNvDQpjYXQoIk1vZGVsbyBzZWxlY2Npb25hZG86IiwgbW9kZWxvX3VzYWRvLCAiXG4iKQ0Kc3VtbWFyeShtZWpvcl9tb2RlbG8pDQpjaGVja3Jlc2lkdWFscyhtZWpvcl9tb2RlbG8pDQpgYGANCg0KDQpgYGB7cn0NCiMgVXNhciBsb3MgdmFsb3JlcyBhanVzdGFkb3MgZGVsIG1vZGVsbyBwYXJhIGNhbGN1bGFyIGxhcyBtw6l0cmljYXMNCg0KIyBPYnRlbmVyIGxvcyB2YWxvcmVzIGFqdXN0YWRvcyAoZml0dGVkIHZhbHVlcykNCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKG1lam9yX21vZGVsbykNCg0KIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikNCnJtc2VfbW9kZWxvIDwtIHNxcnQobWVhbigoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKSkNCg0KIyBDYWxjdWxhciBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpIC0gY29uIHByb3RlY2Npw7NuIGNvbnRyYSBkaXZpc2nDs24gcG9yIGNlcm8NCm1hcGVfbW9kZWxvIDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMA0KDQojIENhbGN1bGFyIE1TRSAoTWVhbiBTcXVhcmVkIEVycm9yKQ0KbXNlX21vZGVsbyA8LSBtZWFuKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpXjIpDQoNCiMgQ2FsY3VsYXIgUsKyDQpzc2VfbW9kZWxvIDwtIHN1bSgoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKQ0Kc3N0X21vZGVsbyA8LSBzdW0oKHNlcmllX3RzIC0gbWVhbihzZXJpZV90cykpXjIpDQpyMl9tb2RlbG8gPC0gMSAtIChzc2VfbW9kZWxvIC8gc3N0X21vZGVsbykNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiUsKyIGRlbCBtb2RlbG8iLCBtb2RlbG9fdXNhZG8sICJwYXJhIDM5Mjk3ODg6IiwgcjJfbW9kZWxvLCAiXG4iKQ0KY2F0KCJBSUMgZGVsIG1vZGVsbyIsIG1vZGVsb191c2FkbywgInBhcmEgMzkyOTc4ODoiLCBtZWpvcl9tb2RlbG8kYWljLCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8iLCBtb2RlbG9fdXNhZG8sICJwYXJhIDM5Mjk3ODg6Iiwgcm1zZV9tb2RlbG8sICJcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyIsIG1vZGVsb191c2FkbywgInBhcmEgMzkyOTc4ODoiLCBtYXBlX21vZGVsbywgIlxuIikNCmNhdCgiTVNFIGRlbCBtb2RlbG8iLCBtb2RlbG9fdXNhZG8sICJwYXJhIDM5Mjk3ODg6IiwgbXNlX21vZGVsbywgIlxuIikNCmBgYA0KDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgQVJNQS9TQVJJTUEgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgUjIgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIE1TRSA9IG51bWVyaWMoKSwNCiAgICBBSUMgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMzkyOTc4OCIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bw0KICBNb2RlbG8gPSAiQVJNQS9TQVJJTUEiLA0KICBSMiA9IHIyX21vZGVsbywNCiAgUk1TRSA9IHJtc2VfbW9kZWxvLA0KICBNQVBFID0gbWFwZV9tb2RlbG8sDQogIE1TRSA9IG1zZV9tb2RlbG8sDQogIEFJQyA9IG1lam9yX21vZGVsbyRhaWMNCikpDQpgYGANCg0KDQoNCg0KPCEtLSBQUk9EVUNUTyAzOTA0MTUyIC0tPg0KIyMgUFJPRFVDVE8gMzkwNDE1Mg0KYGBge3J9DQojIEVzdGFibGVjZXIgZWwgSUQgZGVsIHByb2R1Y3RvDQppZF9wcm9kdWN0byA8LSAzOTA0MTUyDQoNCiMgQWdydXBhciB2ZW50YXMgbWVuc3VhbGVzDQp2ZW50YXNfbWVuc3VhbGVzIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUNCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZHVjdG8pICU+JQ0KICBtdXRhdGUoRmVjaGEgPSBhcy5EYXRlKGZsb29yX2RhdGUoVHJ4X0ZlY2hhLCAibW9udGgiKSkpICU+JQ0KICBncm91cF9ieShGZWNoYSkgJT4lDQogIHN1bW1hcmlzZShWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogIGFycmFuZ2UoRmVjaGEpDQoNCiMgQ3JlYXIgc2VyaWUgZGUgdGllbXBvIG1lbnN1YWwNCnNlcmllX3RzIDwtIHRzKHZlbnRhc19tZW5zdWFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDEyLA0KICAgICAgICAgICAgICAgc3RhcnQgPSBjKHllYXIobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSwgbW9udGgobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSkpDQoNCmBgYA0KDQoNCmBgYHtyfQ0KIyBBanVzdGFyIG1vZGVsb3MgQVJNQSB5IFNBUklNQQ0KbW9kZWxvX2FybWEgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQ0KYXNzaWduKHBhc3RlMCgibW9kZWxvX2FybWFfIiwgMzkwNDE1MiksIG1vZGVsb19hcm1hLCBlbnZpciA9IC5HbG9iYWxFbnYpDQoNCg0KbW9kZWxvX3NhcmltYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IFRSVUUsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkNCg0KIyBNb3N0cmFyIEFJQyBkZSBhbWJvcyBtb2RlbG9zDQpjYXQoIkFJQyBBUk1BOiIsIG1vZGVsb19hcm1hJGFpYywgIlxuIikNCmNhdCgiQUlDIFNBUklNQToiLCBtb2RlbG9fc2FyaW1hJGFpYywgIlxuIikNCg0KIyBTZWxlY2Npb25hciBlbCBtZWpvciBtb2RlbG8NCmlmIChtb2RlbG9fYXJtYSRhaWMgPCBtb2RlbG9fc2FyaW1hJGFpYykgew0KICBtZWpvcl9tb2RlbG8gPC0gbW9kZWxvX2FybWENCiAgbW9kZWxvX3VzYWRvIDwtICJBUk1BIg0KfSBlbHNlIGlmIChtb2RlbG9fc2FyaW1hJGFpYyA8IG1vZGVsb19hcm1hJGFpYykgew0KICBtZWpvcl9tb2RlbG8gPC0gbW9kZWxvX3NhcmltYQ0KICBtb2RlbG9fdXNhZG8gPC0gIlNBUklNQSINCn0gZWxzZSB7DQogIG1lam9yX21vZGVsbyA8LSBtb2RlbG9fYXJtYQ0KICBtb2RlbG9fdXNhZG8gPC0gIkFSTUEgKHBvciBlbXBhdGUpIg0KfQ0KDQojIEdlbmVyYXIgcHJvbsOzc3RpY28gYSAzIG1lc2VzDQpmb3JlY2FzdF9tb2RlbG8gPC0gZm9yZWNhc3QobWVqb3JfbW9kZWxvLCBoID0gMykNCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKw0KICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0iLCBtb2RlbG9fdXNhZG8sICIoUHJvZHVjdG8gMzkwNDE1MikiKSwNCiAgICAgICB4ID0gIk1lcyIsIHkgPSAiVmVudGFzICgkKSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3J9DQojIENvbnN0cnVpciB0YWJsYSBjb21iaW5hZGEgZGUgaGlzdMOzcmljbyB5IHByZWRpY2Npw7NuDQp2ZW50YXNfaGlzdG9yaWNhcyA8LSB2ZW50YXNfbWVuc3VhbGVzICU+JQ0KICBzZWxlY3QoRmVjaGEsIFZlbnRhKSAlPiUNCiAgbXV0YXRlKFRpcG8gPSAiSGlzdMOzcmljbyIsIElDX0luZmVyaW9yID0gTkEsIElDX1N1cGVyaW9yID0gTkEpDQoNCnByZWRpY2Npb25lcyA8LSBkYXRhLmZyYW1lKA0KICBGZWNoYSA9IHNlcS5EYXRlKGZyb20gPSBtYXgodmVudGFzX2hpc3RvcmljYXMkRmVjaGEpICVtKyUgbW9udGhzKDEpLA0KICAgICAgICAgICAgICAgICAgIGJ5ID0gIm1vbnRoIiwgbGVuZ3RoLm91dCA9IDMpLA0KICBWZW50YSA9IGFzLm51bWVyaWMoZm9yZWNhc3RfbW9kZWxvJG1lYW4pLA0KICBJQ19JbmZlcmlvciA9IGFzLm51bWVyaWMoZm9yZWNhc3RfbW9kZWxvJGxvd2VyWywyXSksDQogIElDX1N1cGVyaW9yID0gYXMubnVtZXJpYyhmb3JlY2FzdF9tb2RlbG8kdXBwZXJbLDJdKSwNCiAgVGlwbyA9ICJQcm9uw7NzdGljbyINCikNCg0KdGFibGFfY29tcGxldGEgPC0gYmluZF9yb3dzKHZlbnRhc19oaXN0b3JpY2FzLCBwcmVkaWNjaW9uZXMpICU+JQ0KICBhcnJhbmdlKEZlY2hhKQ0KDQojIE1vc3RyYXIgdGFibGENCnRhYmxhX2NvbXBsZXRhICU+JQ0KICBrYWJsZShkaWdpdHMgPSAyLCBjYXB0aW9uID0gIlZlbnRhcyBtZW5zdWFsZXMgeSBwcm9uw7NzdGljbyAtIFByb2R1Y3RvIDM5MDQxNTIiKSAlPiUNCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpDQpgYGANCg0KYGBge3J9DQojIE1vc3RyYXIgcmVzdW1lbiBkZWwgbW9kZWxvIHkgZGlhZ27Ds3N0aWNvDQpjYXQoIk1vZGVsbyBzZWxlY2Npb25hZG86IiwgbW9kZWxvX3VzYWRvLCAiXG4iKQ0Kc3VtbWFyeShtZWpvcl9tb2RlbG8pDQpjaGVja3Jlc2lkdWFscyhtZWpvcl9tb2RlbG8pDQpgYGANCg0KYGBge3J9DQojIFVzYXIgbG9zIHZhbG9yZXMgYWp1c3RhZG9zIGRlbCBtb2RlbG8gcGFyYSBjYWxjdWxhciBsYXMgbcOpdHJpY2FzDQoNCiMgT2J0ZW5lciBsb3MgdmFsb3JlcyBhanVzdGFkb3MgKGZpdHRlZCB2YWx1ZXMpDQpmaXR0ZWRfdmFsdWVzIDwtIGZpdHRlZChtZWpvcl9tb2RlbG8pDQoNCiMgQ2FsY3VsYXIgUk1TRSAoUm9vdCBNZWFuIFNxdWFyZWQgRXJyb3IpDQpybXNlX21vZGVsbyA8LSBzcXJ0KG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikpDQoNCiMgQ2FsY3VsYXIgTUFQRSAoTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yKSAtIGNvbiBwcm90ZWNjacOzbiBjb250cmEgZGl2aXNpw7NuIHBvciBjZXJvDQptYXBlX21vZGVsbyA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDANCg0KIyBDYWxjdWxhciBNU0UgKE1lYW4gU3F1YXJlZCBFcnJvcikNCm1zZV9tb2RlbG8gPC0gbWVhbigoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKQ0KDQojIENhbGN1bGFyIFLCsg0Kc3NlX21vZGVsbyA8LSBzdW0oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikNCnNzdF9tb2RlbG8gPC0gc3VtKChzZXJpZV90cyAtIG1lYW4oc2VyaWVfdHMpKV4yKQ0KcjJfbW9kZWxvIDwtIDEgLSAoc3NlX21vZGVsbyAvIHNzdF9tb2RlbG8pDQoNCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzDQpjYXQoIlLCsiBkZWwgbW9kZWxvIiwgbW9kZWxvX3VzYWRvLCAicGFyYSAzOTA0MTUyOiIsIHIyX21vZGVsbywgIlxuIikNCmNhdCgiQUlDIGRlbCBtb2RlbG8iLCBtb2RlbG9fdXNhZG8sICJwYXJhIDM5MDQxNTI6IiwgbWVqb3JfbW9kZWxvJGFpYywgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIiwgbW9kZWxvX3VzYWRvLCAicGFyYSAzOTA0MTUyOiIsIHJtc2VfbW9kZWxvLCAiXG4iKQ0KY2F0KCJNQVBFIGRlbCBtb2RlbG8iLCBtb2RlbG9fdXNhZG8sICJwYXJhIDM5MDQxNTI6IiwgbWFwZV9tb2RlbG8sICJcbiIpDQpjYXQoIk1TRSBkZWwgbW9kZWxvIiwgbW9kZWxvX3VzYWRvLCAicGFyYSAzOTA0MTUyOiIsIG1zZV9tb2RlbG8sICJcbiIpDQpgYGANCg0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIEFSTUEvU0FSSU1BIHBhcmEgcHJvZHVjdG8gMTU1MDAxDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIFIyID0gbnVtZXJpYygpLA0KICAgIFJNU0UgPSBudW1lcmljKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBNU0UgPSBudW1lcmljKCksDQogICAgQUlDID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gIjM5MDQxNTIiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8NCiAgTW9kZWxvID0gIkFSTUEvU0FSSU1BIiwNCiAgUjIgPSByMl9tb2RlbG8sDQogIFJNU0UgPSBybXNlX21vZGVsbywNCiAgTUFQRSA9IG1hcGVfbW9kZWxvLA0KICBNU0UgPSBtc2VfbW9kZWxvLA0KICBBSUMgPSBtZWpvcl9tb2RlbG8kYWljDQopKQ0KYGBgDQoNCjwhLS0gUFJPRFVDVE8gMTU1MDAyIC0tPg0KIyMgUFJPRFVDVE8gMTU1MDAyDQpgYGB7cn0NCiMgRXN0YWJsZWNlciBlbCBJRCBkZWwgcHJvZHVjdG8NCmlkX3Byb2R1Y3RvIDwtIDE1NTAwMg0KDQojIEFncnVwYXIgdmVudGFzIG1lbnN1YWxlcw0KdmVudGFzX21lbnN1YWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IGlkX3Byb2R1Y3RvKSAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpKSAlPiUNCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQ0KICBzdW1tYXJpc2UoVmVudGEgPSBzdW0oVmVudGEsIG5hLnJtID0gVFJVRSksIC5ncm91cHMgPSAiZHJvcCIpICU+JQ0KICBhcnJhbmdlKEZlY2hhKQ0KDQojIENyZWFyIHNlcmllIGRlIHRpZW1wbyBtZW5zdWFsDQpzZXJpZV90cyA8LSB0cyh2ZW50YXNfbWVuc3VhbGVzJFZlbnRhLCBmcmVxdWVuY3kgPSAxMiwNCiAgICAgICAgICAgICAgIHN0YXJ0ID0gYyh5ZWFyKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSksIG1vbnRoKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSkpKQ0KYGBgDQoNCmBgYHtyfQ0KIyBBanVzdGFyIG1vZGVsb3MgQVJNQSB5IFNBUklNQQ0KbW9kZWxvX2FybWEgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQ0KYXNzaWduKHBhc3RlMCgibW9kZWxvX2FybWFfIiwgMTU1MDAyKSwgbW9kZWxvX2FybWEsIGVudmlyID0gLkdsb2JhbEVudikNCg0KbW9kZWxvX3NhcmltYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IFRSVUUsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkNCg0KIyBNb3N0cmFyIEFJQyBkZSBhbWJvcyBtb2RlbG9zDQpjYXQoIkFJQyBBUk1BOiIsIG1vZGVsb19hcm1hJGFpYywgIlxuIikNCmNhdCgiQUlDIFNBUklNQToiLCBtb2RlbG9fc2FyaW1hJGFpYywgIlxuIikNCg0KIyBTZWxlY2Npb25hciBlbCBtZWpvciBtb2RlbG8NCmlmIChtb2RlbG9fYXJtYSRhaWMgPCBtb2RlbG9fc2FyaW1hJGFpYykgew0KICBtZWpvcl9tb2RlbG8gPC0gbW9kZWxvX2FybWENCiAgbW9kZWxvX3VzYWRvIDwtICJBUk1BIg0KfSBlbHNlIGlmIChtb2RlbG9fc2FyaW1hJGFpYyA8IG1vZGVsb19hcm1hJGFpYykgew0KICBtZWpvcl9tb2RlbG8gPC0gbW9kZWxvX3NhcmltYQ0KICBtb2RlbG9fdXNhZG8gPC0gIlNBUklNQSINCn0gZWxzZSB7DQogIG1lam9yX21vZGVsbyA8LSBtb2RlbG9fYXJtYQ0KICBtb2RlbG9fdXNhZG8gPC0gIkFSTUEgKHBvciBlbXBhdGUpIg0KfQ0KDQojIEdlbmVyYXIgcHJvbsOzc3RpY28gYSAzIG1lc2VzDQpmb3JlY2FzdF9tb2RlbG8gPC0gZm9yZWNhc3QobWVqb3JfbW9kZWxvLCBoID0gMykNCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKw0KICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0iLCBtb2RlbG9fdXNhZG8sICIoUHJvZHVjdG8gMTU1MDAyKSIpLA0KICAgICAgIHggPSAiTWVzIiwgeSA9ICJWZW50YXMgKCQpIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpgYGB7cn0NCiMgQ29uc3RydWlyIHRhYmxhIGNvbWJpbmFkYSBkZSBoaXN0w7NyaWNvIHkgcHJlZGljY2nDs24NCnZlbnRhc19oaXN0b3JpY2FzIDwtIHZlbnRhc19tZW5zdWFsZXMgJT4lDQogIHNlbGVjdChGZWNoYSwgVmVudGEpICU+JQ0KICBtdXRhdGUoVGlwbyA9ICJIaXN0w7NyaWNvIiwgSUNfSW5mZXJpb3IgPSBOQSwgSUNfU3VwZXJpb3IgPSBOQSkNCg0KcHJlZGljY2lvbmVzIDwtIGRhdGEuZnJhbWUoDQogIEZlY2hhID0gc2VxLkRhdGUoZnJvbSA9IG1heCh2ZW50YXNfaGlzdG9yaWNhcyRGZWNoYSkgJW0rJSBtb250aHMoMSksDQogICAgICAgICAgICAgICAgICAgYnkgPSAibW9udGgiLCBsZW5ndGgub3V0ID0gMyksDQogIFZlbnRhID0gYXMubnVtZXJpYyhmb3JlY2FzdF9tb2RlbG8kbWVhbiksDQogIElDX0luZmVyaW9yID0gYXMubnVtZXJpYyhmb3JlY2FzdF9tb2RlbG8kbG93ZXJbLDJdKSwNCiAgSUNfU3VwZXJpb3IgPSBhcy5udW1lcmljKGZvcmVjYXN0X21vZGVsbyR1cHBlclssMl0pLA0KICBUaXBvID0gIlByb27Ds3N0aWNvIg0KKQ0KDQp0YWJsYV9jb21wbGV0YSA8LSBiaW5kX3Jvd3ModmVudGFzX2hpc3RvcmljYXMsIHByZWRpY2Npb25lcykgJT4lDQogIGFycmFuZ2UoRmVjaGEpDQoNCiMgTW9zdHJhciB0YWJsYQ0KdGFibGFfY29tcGxldGEgJT4lDQogIGthYmxlKGRpZ2l0cyA9IDIsIGNhcHRpb24gPSAiVmVudGFzIG1lbnN1YWxlcyB5IHByb27Ds3N0aWNvIC0gUHJvZHVjdG8gMTU1MDAyIikgJT4lDQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQ0KDQpgYGANCg0KYGBge3J9DQojIE1vc3RyYXIgcmVzdW1lbiBkZWwgbW9kZWxvIHkgZGlhZ27Ds3N0aWNvDQpjYXQoIk1vZGVsbyBzZWxlY2Npb25hZG86IiwgbW9kZWxvX3VzYWRvLCAiXG4iKQ0Kc3VtbWFyeShtZWpvcl9tb2RlbG8pDQpjaGVja3Jlc2lkdWFscyhtZWpvcl9tb2RlbG8pDQoNCmBgYA0KDQpgYGB7cn0NCiMgVXNhciBsb3MgdmFsb3JlcyBhanVzdGFkb3MgZGVsIG1vZGVsbyBwYXJhIGNhbGN1bGFyIGxhcyBtw6l0cmljYXMNCg0KIyBPYnRlbmVyIGxvcyB2YWxvcmVzIGFqdXN0YWRvcyAoZml0dGVkIHZhbHVlcykNCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKG1lam9yX21vZGVsbykNCg0KIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikNCnJtc2VfbW9kZWxvIDwtIHNxcnQobWVhbigoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKSkNCg0KIyBDYWxjdWxhciBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpIC0gY29uIHByb3RlY2Npw7NuIGNvbnRyYSBkaXZpc2nDs24gcG9yIGNlcm8NCm1hcGVfbW9kZWxvIDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMA0KDQojIENhbGN1bGFyIE1TRSAoTWVhbiBTcXVhcmVkIEVycm9yKQ0KbXNlX21vZGVsbyA8LSBtZWFuKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpXjIpDQoNCiMgQ2FsY3VsYXIgUsKyDQpzc2VfbW9kZWxvIDwtIHN1bSgoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKQ0Kc3N0X21vZGVsbyA8LSBzdW0oKHNlcmllX3RzIC0gbWVhbihzZXJpZV90cykpXjIpDQpyMl9tb2RlbG8gPC0gMSAtIChzc2VfbW9kZWxvIC8gc3N0X21vZGVsbykNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiUsKyIGRlbCBtb2RlbG8iLCBtb2RlbG9fdXNhZG8sICJwYXJhIDE1NTAwMjoiLCByMl9tb2RlbG8sICJcbiIpDQpjYXQoIkFJQyBkZWwgbW9kZWxvIiwgbW9kZWxvX3VzYWRvLCAicGFyYSAxNTUwMDI6IiwgbWVqb3JfbW9kZWxvJGFpYywgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIiwgbW9kZWxvX3VzYWRvLCAicGFyYSAxNTUwMDI6Iiwgcm1zZV9tb2RlbG8sICJcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyIsIG1vZGVsb191c2FkbywgInBhcmEgMTU1MDAyOiIsIG1hcGVfbW9kZWxvLCAiXG4iKQ0KY2F0KCJNU0UgZGVsIG1vZGVsbyIsIG1vZGVsb191c2FkbywgInBhcmEgMTU1MDAyOiIsIG1zZV9tb2RlbG8sICJcbiIpDQoNCmBgYA0KDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgQVJNQS9TQVJJTUEgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgUjIgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIE1TRSA9IG51bWVyaWMoKSwNCiAgICBBSUMgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMTU1MDAyIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvDQogIE1vZGVsbyA9ICJBUk1BL1NBUklNQSIsDQogIFIyID0gcjJfbW9kZWxvLA0KICBSTVNFID0gcm1zZV9tb2RlbG8sDQogIE1BUEUgPSBtYXBlX21vZGVsbywNCiAgTVNFID0gbXNlX21vZGVsbywNCiAgQUlDID0gbWVqb3JfbW9kZWxvJGFpYw0KKSkNCmBgYA0KDQoNCjwhLS1Qcm9kdWN0byAzNjc4MDU1IC0tPg0KIyMgUFJPRFVDVE8gMzY3ODA1NQ0KYGBge3J9DQojIEVzdGFibGVjZXIgZWwgSUQgZGVsIHByb2R1Y3RvDQppZF9wcm9kdWN0byA8LSAzNjc4MDU1DQoNCiMgQWdydXBhciB2ZW50YXMgbWVuc3VhbGVzDQp2ZW50YXNfbWVuc3VhbGVzIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUNCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZHVjdG8pICU+JQ0KICBtdXRhdGUoRmVjaGEgPSBhcy5EYXRlKGZsb29yX2RhdGUoVHJ4X0ZlY2hhLCAibW9udGgiKSkpICU+JQ0KICBncm91cF9ieShGZWNoYSkgJT4lDQogIHN1bW1hcmlzZShWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSwgLmdyb3VwcyA9ICJkcm9wIikgJT4lDQogIGFycmFuZ2UoRmVjaGEpDQoNCiMgQ3JlYXIgc2VyaWUgZGUgdGllbXBvIG1lbnN1YWwNCnNlcmllX3RzIDwtIHRzKHZlbnRhc19tZW5zdWFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDEyLA0KICAgICAgICAgICAgICAgc3RhcnQgPSBjKHllYXIobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSwgbW9udGgobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSkpDQpgYGANCg0KYGBge3J9DQojIEFqdXN0YXIgbW9kZWxvcyBBUk1BIHkgU0FSSU1BDQptb2RlbG9fYXJtYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpDQphc3NpZ24ocGFzdGUwKCJtb2RlbG9fYXJtYV8iLCAzNjc4MDU1KSwgbW9kZWxvX2FybWEsIGVudmlyID0gLkdsb2JhbEVudikNCg0KbW9kZWxvX3NhcmltYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IFRSVUUsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkNCg0KIyBNb3N0cmFyIEFJQyBkZSBhbWJvcyBtb2RlbG9zDQpjYXQoIkFJQyBBUk1BOiIsIG1vZGVsb19hcm1hJGFpYywgIlxuIikNCmNhdCgiQUlDIFNBUklNQToiLCBtb2RlbG9fc2FyaW1hJGFpYywgIlxuIikNCg0KIyBTZWxlY2Npb25hciBlbCBtZWpvciBtb2RlbG8NCmlmIChtb2RlbG9fYXJtYSRhaWMgPCBtb2RlbG9fc2FyaW1hJGFpYykgew0KICBtZWpvcl9tb2RlbG8gPC0gbW9kZWxvX2FybWENCiAgbW9kZWxvX3VzYWRvIDwtICJBUk1BIg0KfSBlbHNlIGlmIChtb2RlbG9fc2FyaW1hJGFpYyA8IG1vZGVsb19hcm1hJGFpYykgew0KICBtZWpvcl9tb2RlbG8gPC0gbW9kZWxvX3NhcmltYQ0KICBtb2RlbG9fdXNhZG8gPC0gIlNBUklNQSINCn0gZWxzZSB7DQogIG1lam9yX21vZGVsbyA8LSBtb2RlbG9fYXJtYQ0KICBtb2RlbG9fdXNhZG8gPC0gIkFSTUEgKHBvciBlbXBhdGUpIg0KfQ0KDQojIEdlbmVyYXIgcHJvbsOzc3RpY28gYSAzIG1lc2VzDQpmb3JlY2FzdF9tb2RlbG8gPC0gZm9yZWNhc3QobWVqb3JfbW9kZWxvLCBoID0gMykNCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKw0KICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0iLCBtb2RlbG9fdXNhZG8sICIoUHJvZHVjdG8gMzY3ODA1NSkiKSwNCiAgICAgICB4ID0gIk1lcyIsIHkgPSAiVmVudGFzICgkKSIpICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCmBgYA0KDQpgYGB7cn0NCiMgQ29uc3RydWlyIHRhYmxhIGNvbWJpbmFkYSBkZSBoaXN0w7NyaWNvIHkgcHJlZGljY2nDs24NCnZlbnRhc19oaXN0b3JpY2FzIDwtIHZlbnRhc19tZW5zdWFsZXMgJT4lDQogIHNlbGVjdChGZWNoYSwgVmVudGEpICU+JQ0KICBtdXRhdGUoVGlwbyA9ICJIaXN0w7NyaWNvIiwgSUNfSW5mZXJpb3IgPSBOQSwgSUNfU3VwZXJpb3IgPSBOQSkNCg0KcHJlZGljY2lvbmVzIDwtIGRhdGEuZnJhbWUoDQogIEZlY2hhID0gc2VxLkRhdGUoZnJvbSA9IG1heCh2ZW50YXNfaGlzdG9yaWNhcyRGZWNoYSkgJW0rJSBtb250aHMoMSksDQogICAgICAgICAgICAgICAgICAgYnkgPSAibW9udGgiLCBsZW5ndGgub3V0ID0gMyksDQogIFZlbnRhID0gYXMubnVtZXJpYyhmb3JlY2FzdF9tb2RlbG8kbWVhbiksDQogIElDX0luZmVyaW9yID0gYXMubnVtZXJpYyhmb3JlY2FzdF9tb2RlbG8kbG93ZXJbLDJdKSwNCiAgSUNfU3VwZXJpb3IgPSBhcy5udW1lcmljKGZvcmVjYXN0X21vZGVsbyR1cHBlclssMl0pLA0KICBUaXBvID0gIlByb27Ds3N0aWNvIg0KKQ0KDQp0YWJsYV9jb21wbGV0YSA8LSBiaW5kX3Jvd3ModmVudGFzX2hpc3RvcmljYXMsIHByZWRpY2Npb25lcykgJT4lDQogIGFycmFuZ2UoRmVjaGEpDQoNCiMgTW9zdHJhciB0YWJsYQ0KdGFibGFfY29tcGxldGEgJT4lDQogIGthYmxlKGRpZ2l0cyA9IDIsIGNhcHRpb24gPSAiVmVudGFzIG1lbnN1YWxlcyB5IHByb27Ds3N0aWNvIC0gUHJvZHVjdG8gMzY3ODA1NSIpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkNCmBgYA0KYGBge3J9DQojIE1vc3RyYXIgcmVzdW1lbiBkZWwgbW9kZWxvIHkgZGlhZ27Ds3N0aWNvDQpjYXQoIk1vZGVsbyBzZWxlY2Npb25hZG86IiwgbW9kZWxvX3VzYWRvLCAiXG4iKQ0Kc3VtbWFyeShtZWpvcl9tb2RlbG8pDQpjaGVja3Jlc2lkdWFscyhtZWpvcl9tb2RlbG8pDQoNCmBgYA0KDQpgYGB7cn0NCiMgVXNhciBsb3MgdmFsb3JlcyBhanVzdGFkb3MgZGVsIG1vZGVsbyBwYXJhIGNhbGN1bGFyIGxhcyBtw6l0cmljYXMNCg0KIyBPYnRlbmVyIGxvcyB2YWxvcmVzIGFqdXN0YWRvcyAoZml0dGVkIHZhbHVlcykNCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKG1lam9yX21vZGVsbykNCg0KIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikNCnJtc2VfbW9kZWxvIDwtIHNxcnQobWVhbigoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKSkNCg0KIyBDYWxjdWxhciBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpIC0gY29uIHByb3RlY2Npw7NuIGNvbnRyYSBkaXZpc2nDs24gcG9yIGNlcm8NCm1hcGVfbW9kZWxvIDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMA0KDQojIENhbGN1bGFyIE1TRSAoTWVhbiBTcXVhcmVkIEVycm9yKQ0KbXNlX21vZGVsbyA8LSBtZWFuKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpXjIpDQoNCiMgQ2FsY3VsYXIgUsKyDQpzc2VfbW9kZWxvIDwtIHN1bSgoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKQ0Kc3N0X21vZGVsbyA8LSBzdW0oKHNlcmllX3RzIC0gbWVhbihzZXJpZV90cykpXjIpDQpyMl9tb2RlbG8gPC0gMSAtIChzc2VfbW9kZWxvIC8gc3N0X21vZGVsbykNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiUsKyIGRlbCBtb2RlbG8iLCBtb2RlbG9fdXNhZG8sICJwYXJhIDM2NzgwNTU6IiwgcjJfbW9kZWxvLCAiXG4iKQ0KY2F0KCJBSUMgZGVsIG1vZGVsbyIsIG1vZGVsb191c2FkbywgInBhcmEgMzY3ODA1NToiLCBtZWpvcl9tb2RlbG8kYWljLCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8iLCBtb2RlbG9fdXNhZG8sICJwYXJhIDM2NzgwNTU6Iiwgcm1zZV9tb2RlbG8sICJcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyIsIG1vZGVsb191c2FkbywgInBhcmEgMzY3ODA1NToiLCBtYXBlX21vZGVsbywgIlxuIikNCmNhdCgiTVNFIGRlbCBtb2RlbG8iLCBtb2RlbG9fdXNhZG8sICJwYXJhIDM2NzgwNTU6IiwgbXNlX21vZGVsbywgIlxuIikNCg0KYGBgDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgQVJNQS9TQVJJTUEgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgUjIgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIE1TRSA9IG51bWVyaWMoKSwNCiAgICBBSUMgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMzY3ODA1NSIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bw0KICBNb2RlbG8gPSAiQVJNQS9TQVJJTUEiLA0KICBSMiA9IHIyX21vZGVsbywNCiAgUk1TRSA9IHJtc2VfbW9kZWxvLA0KICBNQVBFID0gbWFwZV9tb2RlbG8sDQogIE1TRSA9IG1zZV9tb2RlbG8sDQogIEFJQyA9IG1lam9yX21vZGVsbyRhaWMNCikpDQpgYGANCg0KIyBSRUdSRVNJT04gTElORUFMDQojIyBNQVBBIERFIENBTE9SDQoNCmBgYHtyIG1hcGFfY2Fsb3JfY29ycmVsYWNpb24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIFZhcmlhYmxlcyBudW3DqXJpY2FzIHJlbGV2YW50ZXMNCnZhcnNfbnVtZXJpY2FzIDwtIGMoIkNhbnQiLCAiVmVudGEiLCAiQ29zdG9fVmVudGEiLCAiQ29zdG9fRGV2b2x1Y2lvbiIsDQogICAgICAgICAgICAgICAgICAgICJBanVzX1Npc3RlbWEiLCAiQWp1c3RlX01hbnVhbCIsICJQcmVjaW9fTGlzdGFfVW5pdGFyaW8iLA0KICAgICAgICAgICAgICAgICAgICAiUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIiwgIlNlbWFuYSIsICJNZXMiLCAiRGVzY3VlbnRvX1BvcmNlbnRhamUiKQ0KDQojIFByZXBhcmFjacOzbiBkZSBsb3MgZGF0b3MNCmRhdG9zX2NvciA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogIHNlbGVjdChhbGxfb2YodmFyc19udW1lcmljYXMpKSAlPiUNCiAgbmEub21pdCgpDQoNCiMgR2VuZXJhciBsYSBtYXRyaXogZGUgY29ycmVsYWNpw7NuDQptYXRyaXpfY29yIDwtIGNvcihkYXRvc19jb3IpDQoNCiMgQWp1c3RlIGRlbCBncsOhZmljbyBzaW4gbWFyDQpnZ2NvcnJwbG90KG1hdHJpel9jb3IsDQogICAgICAgICAgIG1ldGhvZCA9ICJzcXVhcmUiLA0KICAgICAgICAgICB0eXBlID0gInVwcGVyIiwNCiAgICAgICAgICAgbGFiID0gVFJVRSwgDQogICAgICAgICAgIGxhYl9zaXplID0gMiwgICAgICAgICAgICAgICAgICAgIyBNZWpvciB0YW1hw7FvIGRlIGxvcyBjb2VmaWNpZW50ZXMNCiAgICAgICAgICAgdGwuY2V4ID0gMTAsICAgICAgICAgICAgICAgICAgICAjIFRhbWHDsW8gZGUgZXRpcXVldGFzIG3DoXMgZ3JhbmRlDQogICAgICAgICAgIHRsLnNydCA9IDQ1LCAgICAgICAgICAgICAgICAgICAgIyBSb3RhY2nDs24gZGUgNDXCsCBkZSBldGlxdWV0YXMNCiAgICAgICAgICAgY29sb3JzID0gYygiIzZEOUVDMSIsICJ3aGl0ZSIsICIjRTQ2NzI2IiksDQogICAgICAgICAgIHRpdGxlID0gIk1hcGEgZGUgQ29ycmVsYWNpw7NuIC0gVmFyaWFibGVzIE51bcOpcmljYXMiLA0KICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbChiYXNlX3NpemUgPSAxNCkgKw0KICAgICAgICAgICAgIHRoZW1lKA0KICAgICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwNCiAgICAgICAgICAgICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gMCwgaGp1c3QgPSAxKSkNCikNCmBgYA0KDQojIyBQUk9EVUNUTyAxNTUwMDENCmBgYHtyfQ0KIyBGaWx0cmFyIHNvbG8gbG9zIGRhdG9zIHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAxDQpkYXRvc18xNTUwMDEgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQ0KICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDEpICU+JQ0KICBzZWxlY3QoVmVudGEsIENhbnQsIENvc3RvX1ZlbnRhLCBDb3N0b19EZXZvbHVjaW9uLA0KICAgICAgICAgQWp1c19TaXN0ZW1hLCBBanVzdGVfTWFudWFsLCBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sDQogICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIFNlbWFuYSwgTWVzLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgVHJ4X0ZlY2hhKSAlPiUNCiAgbmEub21pdCgpICAjIEVsaW1pbmFyIGZpbGFzIGNvbiB2YWxvcmVzIE5BDQoNCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIGRlIHRpZW1wbyBjb250aW51YSBiYXNhZGEgZW4gbGEgZmVjaGENCmRhdG9zXzE1NTAwMSA8LSBkYXRvc18xNTUwMDEgJT4lDQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSwgICAjIEFzZWfDunJhdGUgZGUgcXVlIGxhIGZlY2hhIGVzdMOpIGVuIGZvcm1hdG8gRGF0ZQ0KICAgICAgICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gKDMwICogMjQgKiA2MCAqIDYwKSkgICMgVGllbXBvIGVuIG1lc2VzIChhanVzdGFkbyBwb3IgZMOtYXMpDQoNCiMgVmVyaWZpY2FyIGxhcyBwcmltZXJhcyBmaWxhcyBwYXJhIGFzZWd1cmFybm9zIGRlIHF1ZSBsYSB2YXJpYWJsZSBkZSB0aWVtcG8gZXN0w6kgYmllbiBjcmVhZGENCmhlYWQoZGF0b3NfMTU1MDAxKQ0KYGBgDQoNCmBgYHtyfQ0KIyBBanVzdGFyIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbA0KbW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKyBDb3N0b19EZXZvbHVjaW9uICsgQWp1c3RlX01hbnVhbCArDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlY2lvX0xpc3RhX1VuaXRhcmlvICsgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsgU2VtYW5hICsgTWVzICsgRGVzY3VlbnRvX1BvcmNlbnRhamUgKyBUaWVtcG8sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRvc18xNTUwMDEpDQoNCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbw0Kc3VtbWFyeShtb2RlbG9fcmVncmVzaW9uXzE1NTAwMSkNCmBgYA0KDQpgYGB7cn0NCiMgQWp1c3RlIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwNCm1vZGVsb19yZWdyZXNpb25fMTU1MDAxIDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsgQ29zdG9fRGV2b2x1Y2lvbiArIEFqdXN0ZV9NYW51YWwgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19MaXN0YV9Vbml0YXJpbyArIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIFNlbWFuYSArIE1lcyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMTU1MDAxKQ0KDQojIFByZWRpY2Npb25lcyB1c2FuZG8gZWwgbW9kZWxvIGFqdXN0YWRvDQpwcmVkaWNjaW9uZXNfMTU1MDAxIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEsIG5ld2RhdGEgPSBkYXRvc18xNTUwMDEpDQoNCiMgQ2FsY3VsYXIgUl4yIChjb2VmaWNpZW50ZSBkZSBkZXRlcm1pbmFjacOzbikNCnIyXzE1NTAwMSA8LSBzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMTU1MDAxKSRyLnNxdWFyZWQNCg0KIyBDYWxjdWxhciBBSUMgKEFrYWlrZSBJbmZvcm1hdGlvbiBDcml0ZXJpb24pDQphaWNfMTU1MDAxIDwtIEFJQyhtb2RlbG9fcmVncmVzaW9uXzE1NTAwMSkNCg0KIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikNCnJtc2VfMTU1MDAxIDwtIHNxcnQobWVhbigoZGF0b3NfMTU1MDAxJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMSleMikpDQoNCiMgQ2FsY3VsYXIgTUFQRSAoTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yKQ0KbWFwZV8xNTUwMDEgPC0gbWVhbihhYnMoKGRhdG9zXzE1NTAwMSRWZW50YSAtIHByZWRpY2Npb25lc18xNTUwMDEpIC8gZGF0b3NfMTU1MDAxJFZlbnRhKSkgKiAxMDANCg0KIyBDYWxjdWxhciBNU0UgKE1lYW4gU3F1YXJlZCBFcnJvcikNCm1zZV8xNTUwMDEgPC0gbWVhbigoZGF0b3NfMTU1MDAxJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMSleMikNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiUl4yIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAxNTUwMDE6ICIsIHIyXzE1NTAwMSwgIlxuIikNCmNhdCgiQUlDIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAxNTUwMDE6ICIsIGFpY18xNTUwMDEsICJcbiIpDQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDE1NTAwMTogIiwgcm1zZV8xNTUwMDEsICJcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDE1NTAwMTogIiwgbWFwZV8xNTUwMDEsICJcbiIpDQpjYXQoIk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMTU1MDAxOiAiLCBtc2VfMTU1MDAxLCAiXG4iKQ0KDQojIERpYWduw7NzdGljbyBkZSByZXNpZHVvcyBkZWwgbW9kZWxvDQpwYXIobWZyb3cgPSBjKDIsIDIpKQ0KcGxvdChtb2RlbG9fcmVncmVzaW9uXzE1NTAwMSkNCmBgYA0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJlZ3Jlc2nDs24gTGluZWFsIHBhcmEgcHJvZHVjdG8gMTU1MDAxDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIFIyID0gbnVtZXJpYygpLA0KICAgIFJNU0UgPSBudW1lcmljKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBNU0UgPSBudW1lcmljKCksDQogICAgQUlDID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gIjE1NTAwMSIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bw0KICBNb2RlbG8gPSAiUmVncmVzacOzbiBMaW5lYWwiLA0KICBSMiA9IHIyXzE1NTAwMSwgICMgVXNhIGVsIG5vbWJyZSBlc3BlY8OtZmljbyBwYXJhIGVzdGUgcHJvZHVjdG8NCiAgUk1TRSA9IHJtc2VfMTU1MDAxLA0KICBNQVBFID0gbWFwZV8xNTUwMDEsDQogIE1TRSA9IG1zZV8xNTUwMDEsDQogIEFJQyA9IGFpY18xNTUwMDENCikpDQpgYGANCg0KDQojIyBQUk9EVUNUTyAzOTI5Nzg4DQoNCmBgYHtyfQ0KIyBGaWx0cmFyIHNvbG8gbG9zIGRhdG9zIHBhcmEgZWwgcHJvZHVjdG8gMzkyOTc4OA0KZGF0b3NfMzkyOTc4OCA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM5Mjk3ODgpICU+JQ0KICBzZWxlY3QoVmVudGEsIENhbnQsIENvc3RvX1ZlbnRhLCBDb3N0b19EZXZvbHVjaW9uLA0KICAgICAgICAgQWp1c19TaXN0ZW1hLCBBanVzdGVfTWFudWFsLCBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sDQogICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIFNlbWFuYSwgTWVzLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgVHJ4X0ZlY2hhKSAlPiUNCiAgbmEub21pdCgpICAjIEVsaW1pbmFyIGZpbGFzIGNvbiB2YWxvcmVzIE5BDQoNCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIGRlIHRpZW1wbyBjb250aW51YSBiYXNhZGEgZW4gbGEgZmVjaGENCmRhdG9zXzM5Mjk3ODggPC0gZGF0b3NfMzkyOTc4OCAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCAgICMgQXNlZ8O6cmF0ZSBkZSBxdWUgbGEgZmVjaGEgZXN0w6kgZW4gZm9ybWF0byBEYXRlDQogICAgICAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAoMzAgKiAyNCAqIDYwICogNjApKSAgIyBUaWVtcG8gZW4gbWVzZXMgKGFqdXN0YWRvIHBvciBkw61hcykNCg0KIyBWZXJpZmljYXIgbGFzIHByaW1lcmFzIGZpbGFzIHBhcmEgYXNlZ3VyYXJub3MgZGUgcXVlIGxhIHZhcmlhYmxlIGRlIHRpZW1wbyBlc3TDqSBiaWVuIGNyZWFkYQ0KaGVhZChkYXRvc18zOTI5Nzg4KQ0KYGBgDQoNCmBgYHtyfQ0KIyBBanVzdGFyIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbA0KbW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4IDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsgQ29zdG9fRGV2b2x1Y2lvbiArIEFqdXN0ZV9NYW51YWwgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19MaXN0YV9Vbml0YXJpbyArIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIFNlbWFuYSArIE1lcyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMzkyOTc4OCkNCg0KIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvDQpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCkNCmBgYA0KDQpgYGB7cn0NCiMgUHJlZGljY2lvbmVzIHVzYW5kbyBlbCBtb2RlbG8gYWp1c3RhZG8NCnByZWRpY2Npb25lc18zOTI5Nzg4IDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4LCBuZXdkYXRhID0gZGF0b3NfMzkyOTc4OCkNCg0KIyBDYWxjdWxhciBSXjIgKGNvZWZpY2llbnRlIGRlIGRldGVybWluYWNpw7NuKQ0KcjJfMzkyOTc4OCA8LSBzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCkkci5zcXVhcmVkDQoNCiMgQ2FsY3VsYXIgQUlDIChBa2Fpa2UgSW5mb3JtYXRpb24gQ3JpdGVyaW9uKQ0KYWljXzM5Mjk3ODggPC0gQUlDKG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCkNCg0KIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikNCnJtc2VfMzkyOTc4OCA8LSBzcXJ0KG1lYW4oKGRhdG9zXzM5Mjk3ODgkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzkyOTc4OCleMikpDQoNCiMgQ2FsY3VsYXIgTUFQRSAoTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yKQ0KIyBBw7FhZGltb3MgcHJvdGVjY2nDs24gY29udHJhIGRpdmlzacOzbiBwb3IgY2Vybw0KbWFwZV8zOTI5Nzg4IDwtIG1lYW4oYWJzKChkYXRvc18zOTI5Nzg4JFZlbnRhIC0gcHJlZGljY2lvbmVzXzM5Mjk3ODgpIC8gcG1heChkYXRvc18zOTI5Nzg4JFZlbnRhLCAwLjAxKSkpICogMTAwDQoNCiMgQ2FsY3VsYXIgTVNFIChNZWFuIFNxdWFyZWQgRXJyb3IpDQptc2VfMzkyOTc4OCA8LSBtZWFuKChkYXRvc18zOTI5Nzg4JFZlbnRhIC0gcHJlZGljY2lvbmVzXzM5Mjk3ODgpXjIpDQoNCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzDQpjYXQoIlJeMiBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMzkyOTc4ODogIiwgcjJfMzkyOTc4OCwgIlxuIikNCmNhdCgiQUlDIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTI5Nzg4OiAiLCBhaWNfMzkyOTc4OCwgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMzkyOTc4ODogIiwgcm1zZV8zOTI5Nzg4LCAiXG4iKQ0KY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTI5Nzg4OiAiLCBtYXBlXzM5Mjk3ODgsICJcbiIpDQpjYXQoIk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMzkyOTc4ODogIiwgbXNlXzM5Mjk3ODgsICJcbiIpDQoNCiMgRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdW9zIGRlbCBtb2RlbG8NCnBhcihtZnJvdyA9IGMoMiwgMikpDQpwbG90KG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCkNCmBgYA0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJlZ3Jlc2nDs24gTGluZWFsIHBhcmEgcHJvZHVjdG8gMTU1MDAxDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIFIyID0gbnVtZXJpYygpLA0KICAgIFJNU0UgPSBudW1lcmljKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBNU0UgPSBudW1lcmljKCksDQogICAgQUlDID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gIjM5Mjk3ODgiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8NCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwNCiAgUjIgPSByMl8zOTI5Nzg4LCAgIyBVc2EgZWwgbm9tYnJlIGVzcGVjw61maWNvIHBhcmEgZXN0ZSBwcm9kdWN0bw0KICBSTVNFID0gcm1zZV8zOTI5Nzg4LA0KICBNQVBFID0gbWFwZV8zOTI5Nzg4LA0KICBNU0UgPSBtc2VfMzkyOTc4OCwNCiAgQUlDID0gYWljXzM5Mjk3ODgNCikpDQpgYGANCg0KDQojIyBQUk9EVUNUTyAzOTA0MTUyDQoNCmBgYHtyfQ0KIyBGaWx0cmFyIHNvbG8gbG9zIGRhdG9zIHBhcmEgZWwgcHJvZHVjdG8gMzkwNDE1Mg0KZGF0b3NfMzkwNDE1MiA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM5MDQxNTIpICU+JQ0KICBzZWxlY3QoVmVudGEsIENhbnQsIENvc3RvX1ZlbnRhLCBDb3N0b19EZXZvbHVjaW9uLA0KICAgICAgICAgQWp1c19TaXN0ZW1hLCBBanVzdGVfTWFudWFsLCBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sDQogICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIFNlbWFuYSwgTWVzLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgVHJ4X0ZlY2hhKSAlPiUNCiAgbmEub21pdCgpICAjIEVsaW1pbmFyIGZpbGFzIGNvbiB2YWxvcmVzIE5BDQoNCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIGRlIHRpZW1wbyBjb250aW51YSBiYXNhZGEgZW4gbGEgZmVjaGENCmRhdG9zXzM5MDQxNTIgPC0gZGF0b3NfMzkwNDE1MiAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCAgICMgQXNlZ8O6cmF0ZSBkZSBxdWUgbGEgZmVjaGEgZXN0w6kgZW4gZm9ybWF0byBEYXRlDQogICAgICAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAoMzAgKiAyNCAqIDYwICogNjApKSAgIyBUaWVtcG8gZW4gbWVzZXMgKGFqdXN0YWRvIHBvciBkw61hcykNCg0KIyBWZXJpZmljYXIgbGFzIHByaW1lcmFzIGZpbGFzIHBhcmEgYXNlZ3VyYXJub3MgZGUgcXVlIGxhIHZhcmlhYmxlIGRlIHRpZW1wbyBlc3TDqSBiaWVuIGNyZWFkYQ0KaGVhZChkYXRvc18zOTA0MTUyKQ0KYGBgDQoNCmBgYHtyfQ0KIyBBanVzdGFyIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbA0KbW9kZWxvX3JlZ3Jlc2lvbl8zOTA0MTUyIDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsgQ29zdG9fRGV2b2x1Y2lvbiArIEFqdXN0ZV9NYW51YWwgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19MaXN0YV9Vbml0YXJpbyArIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIFNlbWFuYSArIE1lcyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMzkwNDE1MikNCg0KIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvDQpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MikNCmBgYA0KDQpgYGB7cn0NCiMgUHJlZGljY2lvbmVzIHVzYW5kbyBlbCBtb2RlbG8gYWp1c3RhZG8NCnByZWRpY2Npb25lc18zOTA0MTUyIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTA0MTUyLCBuZXdkYXRhID0gZGF0b3NfMzkwNDE1MikNCg0KIyBDYWxjdWxhciBSXjIgKGNvZWZpY2llbnRlIGRlIGRldGVybWluYWNpw7NuKQ0KcjJfMzkwNDE1MiA8LSBzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1Mikkci5zcXVhcmVkDQoNCiMgQ2FsY3VsYXIgQUlDIChBa2Fpa2UgSW5mb3JtYXRpb24gQ3JpdGVyaW9uKQ0KYWljXzM5MDQxNTIgPC0gQUlDKG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MikNCg0KIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikNCnJtc2VfMzkwNDE1MiA8LSBzcXJ0KG1lYW4oKGRhdG9zXzM5MDQxNTIkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzkwNDE1MileMikpDQoNCiMgQ2FsY3VsYXIgTUFQRSAoTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yKQ0KIyBBw7FhZGltb3MgcHJvdGVjY2nDs24gY29udHJhIGRpdmlzacOzbiBwb3IgY2Vybw0KbWFwZV8zOTA0MTUyIDwtIG1lYW4oYWJzKChkYXRvc18zOTA0MTUyJFZlbnRhIC0gcHJlZGljY2lvbmVzXzM5MDQxNTIpIC8gcG1heChkYXRvc18zOTA0MTUyJFZlbnRhLCAwLjAxKSkpICogMTAwDQoNCiMgQ2FsY3VsYXIgTVNFIChNZWFuIFNxdWFyZWQgRXJyb3IpDQptc2VfMzkwNDE1MiA8LSBtZWFuKChkYXRvc18zOTA0MTUyJFZlbnRhIC0gcHJlZGljY2lvbmVzXzM5MDQxNTIpXjIpDQoNCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzDQpjYXQoIlJeMiBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMzkwNDE1MjogIiwgcjJfMzkwNDE1MiwgIlxuIikNCmNhdCgiQUlDIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTA0MTUyOiAiLCBhaWNfMzkwNDE1MiwgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMzkwNDE1MjogIiwgcm1zZV8zOTA0MTUyLCAiXG4iKQ0KY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTA0MTUyOiAiLCBtYXBlXzM5MDQxNTIsICJcbiIpDQpjYXQoIk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMzkwNDE1MjogIiwgbXNlXzM5MDQxNTIsICJcbiIpDQoNCiMgRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdW9zIGRlbCBtb2RlbG8NCnBhcihtZnJvdyA9IGMoMiwgMikpDQpwbG90KG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MikNCmBgYA0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJlZ3Jlc2nDs24gTGluZWFsIHBhcmEgcHJvZHVjdG8gMTU1MDAxDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIFIyID0gbnVtZXJpYygpLA0KICAgIFJNU0UgPSBudW1lcmljKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBNU0UgPSBudW1lcmljKCksDQogICAgQUlDID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gIjM5MDQxNTIiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8NCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwNCiAgUjIgPSByMl8zOTA0MTUyLCAgIyBVc2EgZWwgbm9tYnJlIGVzcGVjw61maWNvIHBhcmEgZXN0ZSBwcm9kdWN0bw0KICBSTVNFID0gcm1zZV8zOTA0MTUyLA0KICBNQVBFID0gbWFwZV8zOTA0MTUyLA0KICBNU0UgPSBtc2VfMzkwNDE1MiwNCiAgQUlDID0gYWljXzM5MDQxNTINCikpDQpgYGANCg0KDQojIyBQUk9EVUNUTyAxNTUwMDINCg0KYGBge3J9DQojIEZpbHRyYXIgc29sbyBsb3MgZGF0b3MgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDINCmRhdG9zXzE1NTAwMiA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDE1NTAwMikgJT4lDQogIHNlbGVjdChWZW50YSwgQ2FudCwgQ29zdG9fVmVudGEsIENvc3RvX0Rldm9sdWNpb24sDQogICAgICAgICBBanVzX1Npc3RlbWEsIEFqdXN0ZV9NYW51YWwsIFByZWNpb19MaXN0YV9Vbml0YXJpbywNCiAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywgU2VtYW5hLCBNZXMsIERlc2N1ZW50b19Qb3JjZW50YWplLCBUcnhfRmVjaGEpICU+JQ0KICBuYS5vbWl0KCkgICMgRWxpbWluYXIgZmlsYXMgY29uIHZhbG9yZXMgTkENCg0KIyBDcmVhciB1bmEgdmFyaWFibGUgZGUgdGllbXBvIGNvbnRpbnVhIGJhc2FkYSBlbiBsYSBmZWNoYQ0KZGF0b3NfMTU1MDAyIDwtIGRhdG9zXzE1NTAwMiAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCAgICMgQXNlZ8O6cmF0ZSBkZSBxdWUgbGEgZmVjaGEgZXN0w6kgZW4gZm9ybWF0byBEYXRlDQogICAgICAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAoMzAgKiAyNCAqIDYwICogNjApKSAgIyBUaWVtcG8gZW4gbWVzZXMgKGFqdXN0YWRvIHBvciBkw61hcykNCg0KIyBWZXJpZmljYXIgbGFzIHByaW1lcmFzIGZpbGFzIHBhcmEgYXNlZ3VyYXJub3MgZGUgcXVlIGxhIHZhcmlhYmxlIGRlIHRpZW1wbyBlc3TDqSBiaWVuIGNyZWFkYQ0KaGVhZChkYXRvc18xNTUwMDIpDQpgYGANCg0KDQpgYGB7cn0NCiMgQWp1c3RhciBlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwNCm1vZGVsb19yZWdyZXNpb25fMTU1MDAyIDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsgQ29zdG9fRGV2b2x1Y2lvbiArIEFqdXN0ZV9NYW51YWwgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19MaXN0YV9Vbml0YXJpbyArIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIFNlbWFuYSArIE1lcyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMTU1MDAyKQ0KDQojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8NCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDIpDQpgYGANCg0KYGBge3J9DQojIFByZWRpY2Npb25lcyB1c2FuZG8gZWwgbW9kZWxvIGFqdXN0YWRvDQpwcmVkaWNjaW9uZXNfMTU1MDAyIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDIsIG5ld2RhdGEgPSBkYXRvc18xNTUwMDIpDQoNCiMgQ2FsY3VsYXIgUl4yIChjb2VmaWNpZW50ZSBkZSBkZXRlcm1pbmFjacOzbikNCnIyXzE1NTAwMiA8LSBzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMTU1MDAyKSRyLnNxdWFyZWQNCg0KIyBDYWxjdWxhciBBSUMgKEFrYWlrZSBJbmZvcm1hdGlvbiBDcml0ZXJpb24pDQphaWNfMTU1MDAyIDwtIEFJQyhtb2RlbG9fcmVncmVzaW9uXzE1NTAwMikNCg0KIyBDYWxjdWxhciBSTVNFIChSb290IE1lYW4gU3F1YXJlZCBFcnJvcikNCnJtc2VfMTU1MDAyIDwtIHNxcnQobWVhbigoZGF0b3NfMTU1MDAyJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMileMikpDQoNCiMgQ2FsY3VsYXIgTUFQRSAoTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yKQ0KIyBBw7FhZGltb3MgcHJvdGVjY2nDs24gY29udHJhIGRpdmlzacOzbiBwb3IgY2Vybw0KbWFwZV8xNTUwMDIgPC0gbWVhbihhYnMoKGRhdG9zXzE1NTAwMiRWZW50YSAtIHByZWRpY2Npb25lc18xNTUwMDIpIC8gcG1heChkYXRvc18xNTUwMDIkVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KIyBDYWxjdWxhciBNU0UgKE1lYW4gU3F1YXJlZCBFcnJvcikNCm1zZV8xNTUwMDIgPC0gbWVhbigoZGF0b3NfMTU1MDAyJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMileMikNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiUl4yIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAxNTUwMDI6ICIsIHIyXzE1NTAwMiwgIlxuIikNCmNhdCgiQUlDIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAxNTUwMDI6ICIsIGFpY18xNTUwMDIsICJcbiIpDQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDE1NTAwMjogIiwgcm1zZV8xNTUwMDIsICJcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDE1NTAwMjogIiwgbWFwZV8xNTUwMDIsICJcbiIpDQpjYXQoIk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMTU1MDAyOiAiLCBtc2VfMTU1MDAyLCAiXG4iKQ0KDQojIERpYWduw7NzdGljbyBkZSByZXNpZHVvcyBkZWwgbW9kZWxvDQpwYXIobWZyb3cgPSBjKDIsIDIpKQ0KcGxvdChtb2RlbG9fcmVncmVzaW9uXzE1NTAwMikNCmBgYA0KDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmVncmVzacOzbiBMaW5lYWwgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgUjIgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIE1TRSA9IG51bWVyaWMoKSwNCiAgICBBSUMgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMTU1MDAyIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvDQogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCIsDQogIFIyID0gcjJfMTU1MDAyLCAgIyBVc2EgZWwgbm9tYnJlIGVzcGVjw61maWNvIHBhcmEgZXN0ZSBwcm9kdWN0bw0KICBSTVNFID0gcm1zZV8xNTUwMDIsDQogIE1BUEUgPSBtYXBlXzE1NTAwMiwNCiAgTVNFID0gbXNlXzE1NTAwMiwNCiAgQUlDID0gYWljXzE1NTAwMg0KKSkNCmBgYA0KDQoNCiMjIFBST0RVQ1RPIDM2NzgwNTUNCmBgYHtyfQ0KIyBGaWx0cmFyIHNvbG8gbG9zIGRhdG9zIHBhcmEgZWwgcHJvZHVjdG8gMzY3ODA1NQ0KZGF0b3NfMzY3ODA1NSA8LSBkYXRvc19maWx0cmFkb3MgJT4lDQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM2NzgwNTUpICU+JQ0KICBzZWxlY3QoVmVudGEsIENhbnQsIENvc3RvX1ZlbnRhLCBDb3N0b19EZXZvbHVjaW9uLA0KICAgICAgICAgQWp1c19TaXN0ZW1hLCBBanVzdGVfTWFudWFsLCBQcmVjaW9fTGlzdGFfVW5pdGFyaW8sDQogICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIFNlbWFuYSwgTWVzLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgVHJ4X0ZlY2hhKSAlPiUNCiAgbmEub21pdCgpICAjIEVsaW1pbmFyIGZpbGFzIGNvbiB2YWxvcmVzIE5BDQoNCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIGRlIHRpZW1wbyBjb250aW51YSBiYXNhZGEgZW4gbGEgZmVjaGENCmRhdG9zXzM2NzgwNTUgPC0gZGF0b3NfMzY3ODA1NSAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCAgICMgQXNlZ8O6cmF0ZSBkZSBxdWUgbGEgZmVjaGEgZXN0w6kgZW4gZm9ybWF0byBEYXRlDQogICAgICAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAoMzAgKiAyNCAqIDYwICogNjApKSAgIyBUaWVtcG8gZW4gbWVzZXMgKGFqdXN0YWRvIHBvciBkw61hcykNCg0KIyBWZXJpZmljYXIgbGFzIHByaW1lcmFzIGZpbGFzIHBhcmEgYXNlZ3VyYXJub3MgZGUgcXVlIGxhIHZhcmlhYmxlIGRlIHRpZW1wbyBlc3TDqSBiaWVuIGNyZWFkYQ0KaGVhZChkYXRvc18zNjc4MDU1KQ0KYGBgDQoNCmBgYHtyfQ0KIyBBanVzdGFyIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbA0KbW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1IDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsgQ29zdG9fRGV2b2x1Y2lvbiArIEFqdXN0ZV9NYW51YWwgKw0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19MaXN0YV9Vbml0YXJpbyArIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIFNlbWFuYSArIE1lcyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMzY3ODA1NSkNCg0KIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvDQpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzY3ODA1NSkNCmBgYA0KDQpgYGB7cn0NCiNQcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBhanVzdGFkbw0KcHJlZGljY2lvbmVzXzM2NzgwNTUgPC0gcHJlZGljdChtb2RlbG9fcmVncmVzaW9uXzM2NzgwNTUsIG5ld2RhdGEgPSBkYXRvc18zNjc4MDU1KQ0KDQojIENhbGN1bGFyIFJeMiAoY29lZmljaWVudGUgZGUgZGV0ZXJtaW5hY2nDs24pDQpyMl8zNjc4MDU1IDwtIHN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1KSRyLnNxdWFyZWQNCg0KIyBDYWxjdWxhciBBSUMgKEFrYWlrZSBJbmZvcm1hdGlvbiBDcml0ZXJpb24pDQphaWNfMzY3ODA1NSA8LSBBSUMobW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1KQ0KDQojIENhbGN1bGFyIFJNU0UgKFJvb3QgTWVhbiBTcXVhcmVkIEVycm9yKQ0Kcm1zZV8zNjc4MDU1IDwtIHNxcnQobWVhbigoZGF0b3NfMzY3ODA1NSRWZW50YSAtIHByZWRpY2Npb25lc18zNjc4MDU1KV4yKSkNCg0KIyBDYWxjdWxhciBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpDQojIEHDsWFkaW1vcyBwcm90ZWNjacOzbiBjb250cmEgZGl2aXNpw7NuIHBvciBjZXJvDQptYXBlXzM2NzgwNTUgPC0gbWVhbihhYnMoKGRhdG9zXzM2NzgwNTUkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzY3ODA1NSkgLyBwbWF4KGRhdG9zXzM2NzgwNTUkVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KIyBDYWxjdWxhciBNU0UgKE1lYW4gU3F1YXJlZCBFcnJvcikNCm1zZV8zNjc4MDU1IDwtIG1lYW4oKGRhdG9zXzM2NzgwNTUkVmVudGEgLSBwcmVkaWNjaW9uZXNfMzY3ODA1NSleMikNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiUl4yIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzNjc4MDU1OiAiLCByMl8zNjc4MDU1LCAiXG4iKQ0KY2F0KCJBSUMgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDM2NzgwNTU6ICIsIGFpY18zNjc4MDU1LCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzNjc4MDU1OiAiLCBybXNlXzM2NzgwNTUsICJcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDM2NzgwNTU6ICIsIG1hcGVfMzY3ODA1NSwgIlxuIikNCmNhdCgiTVNFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzNjc4MDU1OiAiLCBtc2VfMzY3ODA1NSwgIlxuIikNCg0KIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbw0KcGFyKG1mcm93ID0gYygyLCAyKSkNCnBsb3QobW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1KQ0KYGBgDQoNCmBgYHtyfQ0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSZWdyZXNpw7NuIExpbmVhbCBwYXJhIHByb2R1Y3RvIDE1NTAwMQ0KaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsNCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoDQogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBSMiA9IG51bWVyaWMoKSwNCiAgICBSTVNFID0gbnVtZXJpYygpLA0KICAgIE1BUEUgPSBudW1lcmljKCksDQogICAgTVNFID0gbnVtZXJpYygpLA0KICAgIEFJQyA9IG51bWVyaWMoKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9ICIzNjc4MDU1IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvDQogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCIsDQogIFIyID0gcjJfMzY3ODA1NSwgICMgVXNhIGVsIG5vbWJyZSBlc3BlY8OtZmljbyBwYXJhIGVzdGUgcHJvZHVjdG8NCiAgUk1TRSA9IHJtc2VfMzY3ODA1NSwNCiAgTUFQRSA9IG1hcGVfMzY3ODA1NSwNCiAgTVNFID0gbXNlXzM2NzgwNTUsDQogIEFJQyA9IGFpY18zNjc4MDU1DQopKQ0KYGBgDQoNCiMjIEFOQUxJU0lTIERFIFZBUklBQkxFUyBJTVBPUlRBTlRFUw0KYGBge3J9DQojIEZ1bmNpw7NuIHNpbXBsaWZpY2FkYSBwYXJhIGFuYWxpemFyIGNvZWZpY2llbnRlcw0KYW5hbGl6YXJfY29lZmljaWVudGVzIDwtIGZ1bmN0aW9uKG1vZGVsbywgbm9tYnJlX3Byb2R1Y3RvKSB7DQogIHJlc3VtZW4gPC0gc3VtbWFyeShtb2RlbG8pDQogIGNvZWZfZGYgPC0gYXMuZGF0YS5mcmFtZShyZXN1bWVuJGNvZWZmaWNpZW50cykNCiAgY29sbmFtZXMoY29lZl9kZikgPC0gYygiRXN0aW1hdGUiLCAiU3RkLkVycm9yIiwgInQudmFsdWUiLCAicC52YWx1ZSIpDQogIGNvZWZfZGYkVmFyaWFibGUgPC0gcm93bmFtZXMoY29lZl9kZikNCiAgY29lZl9kZiRQcm9kdWN0byA8LSBub21icmVfcHJvZHVjdG8NCiAgY29lZl9kZiRTaWduaWZpY2F0aXZvIDwtIGlmZWxzZShjb2VmX2RmJHAudmFsdWUgPCAwLjA1LCAiU8OtIiwgIk5vIikNCiAgDQogIHJldHVybihjb2VmX2RmICU+JQ0KICAgICAgICAgICBzZWxlY3QoUHJvZHVjdG8sIFZhcmlhYmxlLCBFc3RpbWF0ZSwgcC52YWx1ZSwgU2lnbmlmaWNhdGl2bykgJT4lDQogICAgICAgICAgIGFycmFuZ2UoZGVzYyhhYnMoRXN0aW1hdGUpKSkpDQp9DQoNCiMgQXBsaWNhciBhIGNhZGEgbW9kZWxvDQpjb2VmXzE1NTAwMSA8LSBhbmFsaXphcl9jb2VmaWNpZW50ZXMobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEsICIxNTUwMDEiKQ0KY29lZl8xNTUwMDIgPC0gYW5hbGl6YXJfY29lZmljaWVudGVzKG1vZGVsb19yZWdyZXNpb25fMTU1MDAyLCAiMTU1MDAyIikNCmNvZWZfMzY3ODA1NSA8LSBhbmFsaXphcl9jb2VmaWNpZW50ZXMobW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1LCAiMzY3ODA1NSIpDQpjb2VmXzM5MDQxNTIgPC0gYW5hbGl6YXJfY29lZmljaWVudGVzKG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MiwgIjM5MDQxNTIiKQ0KY29lZl8zOTI5Nzg4IDwtIGFuYWxpemFyX2NvZWZpY2llbnRlcyhtb2RlbG9fcmVncmVzaW9uXzM5Mjk3ODgsICIzOTI5Nzg4IikNCg0KIyBDb21iaW5hciB0b2RvcyBsb3MgY29lZmljaWVudGVzDQp0b2Rvc19jb2VmaWNpZW50ZXMgPC0gYmluZF9yb3dzKGNvZWZfMTU1MDAxLCBjb2VmXzE1NTAwMiwgY29lZl8zNjc4MDU1LCBjb2VmXzM5MDQxNTIsIGNvZWZfMzkyOTc4OCkNCg0KIyBUYWJsYSBjb24gdmFyaWFibGVzIGltcG9ydGFudGVzIGluY2x1eWVuZG8gc2lnbmlmaWNhbmNpYQ0KdmFyaWFibGVzX2ltcG9ydGFudGVzIDwtIHRvZG9zX2NvZWZpY2llbnRlcyAlPiUNCiAgZmlsdGVyKFZhcmlhYmxlICE9ICIoSW50ZXJjZXB0KSIpICU+JQ0KICBncm91cF9ieShQcm9kdWN0bykgJT4lDQogIGFycmFuZ2UoUHJvZHVjdG8sIGRlc2MoYWJzKEVzdGltYXRlKSkpICU+JQ0KICBtdXRhdGUoSW1wYWN0byA9IGlmZWxzZShFc3RpbWF0ZSA+IDAsICJQb3NpdGl2byIsICJOZWdhdGl2byIpKQ0KDQojIFRhYmxhIGNvbXBsZXRhIGNvbiB0b2RhcyBsYXMgdmFyaWFibGVzIGltcG9ydGFudGVzDQprYWJsZSh2YXJpYWJsZXNfaW1wb3J0YW50ZXMgJT4lIA0KICAgICAgICBzZWxlY3QoUHJvZHVjdG8sIFZhcmlhYmxlLCBFc3RpbWF0ZSwgcC52YWx1ZSwgU2lnbmlmaWNhdGl2bywgSW1wYWN0byksDQogICAgICBjYXB0aW9uID0gIlZhcmlhYmxlcyBpbXBvcnRhbnRlcyBwb3IgcHJvZHVjdG8iLA0KICAgICAgY29sLm5hbWVzID0gYygiUHJvZHVjdG8iLCAiVmFyaWFibGUiLCAiQ29lZmljaWVudGUiLCAicC12YWx1ZSIsICJTaWduaWZpY2F0aXZvIiwgIkltcGFjdG8iKSwNCiAgICAgIGRpZ2l0cyA9IGMoMCwgMCwgNCwgNCwgMCwgMCkpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkNCg0KIyBUYWJsYSByZXN1bWVuIGNvbiB0b3AgMyBwb3IgcHJvZHVjdG8NCnRvcF9wb3JfcHJvZHVjdG8gPC0gdmFyaWFibGVzX2ltcG9ydGFudGVzICU+JQ0KICBncm91cF9ieShQcm9kdWN0bykgJT4lDQogIHNsaWNlX2hlYWQobiA9IDMpICU+JQ0KICBzZWxlY3QoUHJvZHVjdG8sIFZhcmlhYmxlLCBFc3RpbWF0ZSwgcC52YWx1ZSwgU2lnbmlmaWNhdGl2bywgSW1wYWN0bykNCg0Ka2FibGUodG9wX3Bvcl9wcm9kdWN0bywNCiAgICAgIGNhcHRpb24gPSAiVG9wIDMgdmFyaWFibGVzIG3DoXMgaW1wb3J0YW50ZXMgcG9yIHByb2R1Y3RvIiwNCiAgICAgIGNvbC5uYW1lcyA9IGMoIlByb2R1Y3RvIiwgIlZhcmlhYmxlIiwgIkNvZWZpY2llbnRlIiwgInAtdmFsdWUiLCAiU2lnbmlmaWNhdGl2byIsICJJbXBhY3RvIiksDQogICAgICBkaWdpdHMgPSBjKDAsIDAsIDQsIDQsIDAsIDApKSAlPiUNCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGMoInN0cmlwZWQiLCAiaG92ZXIiLCAiY29uZGVuc2VkIikpDQpgYGANCg0KDQojIFJBTkRPTSBGT1JFU1QNCg0KDQojIyBQUk9EVUNUTyAxNTUwMDENCmBgYHtyfQ0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykNCmRhdG9zX21vZGVsbyA8LSBkYXRvc18xNTUwMDEgJT4lDQogIHNlbGVjdCgtVHJ4X0ZlY2hhLCAtRmVjaGEpDQoNCiMgQWp1c3RhciBlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdA0Kc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQNCm1vZGVsb19yZl8xNTUwMDEgPC0gcmFuZG9tRm9yZXN0KA0KICBWZW50YSB+IC4sIA0KICBkYXRhID0gZGF0b3NfbW9kZWxvLA0KICBudHJlZSA9IDUwMCwgICAgICAgICAgIyBOw7ptZXJvIGRlIMOhcmJvbGVzDQogIG10cnkgPSBmbG9vcihzcXJ0KG5jb2woZGF0b3NfbW9kZWxvKSAtIDEpKSwgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMgYSBjb25zaWRlcmFyIGVuIGNhZGEgc3BsaXQNCiAgaW1wb3J0YW5jZSA9IFRSVUUgICAgICMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQopDQoNCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbw0KcHJpbnQobW9kZWxvX3JmXzE1NTAwMSkNCg0KIyBPYnRlbmVyIHByZWRpY2Npb25lcw0KcHJlZGljY2lvbmVzX3JmIDwtIHByZWRpY3QobW9kZWxvX3JmXzE1NTAwMSwgbmV3ZGF0YSA9IGRhdG9zX21vZGVsbykNCg0KIyBDYWxjdWxhciBtw6l0cmljYXMNCiMgUsKyICh5YSBjYWxjdWxhZG8gcG9yIGVsIG1vZGVsbykNCnIyX3JmIDwtIDEgLSBzdW0oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZileMikgLyBzdW0oKGRhdG9zX21vZGVsbyRWZW50YSAtIG1lYW4oZGF0b3NfbW9kZWxvJFZlbnRhKSleMikNCg0KIyBBSUMgYXByb3hpbWFkbyAodXNhbmRvIGbDs3JtdWxhIGdlbmVyYWwgcGFyYSBBSUMpDQpuIDwtIG5yb3coZGF0b3NfbW9kZWxvKQ0KayA8LSBsZW5ndGgobW9kZWxvX3JmXzE1NTAwMSRmb3Jlc3QkbmNhdCkgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMNCm1zZSA8LSBtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpDQphaWNfcmYgPC0gbiAqIGxvZyhtc2UpICsgMiAqIGsNCg0KIyBSTVNFDQpybXNlX3JmIDwtIHNxcnQobWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKV4yKSkNCg0KIyBNQVBFDQptYXBlX3JmIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KIyBNU0UNCm1zZV9yZiA8LSBtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpDQoNCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzDQpjYXQoIk1vZGVsbyBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAxXG4iKQ0KY2F0KCJSwrIgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIHIyX3JmLCAiXG4iKQ0KY2F0KCJBSUMgYXByb3hpbWFkbyBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgYWljX3JmLCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBybXNlX3JmLCAiXG4iKQ0KY2F0KCJNQVBFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBtYXBlX3JmLCAiXG4iKQ0KY2F0KCJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1zZV9yZiwgIlxuXG4iKQ0KDQojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzE1NTAwMSkNCnByaW50KGltcG9ydGFuY2lhX3ZhcnMpDQoNCiMgR3JhZmljYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQp2YXJJbXBQbG90KG1vZGVsb19yZl8xNTUwMDEsIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMTU1MDAxIikNCg0KIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzDQpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoDQogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwNCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYNCikNCg0KZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDE1NTAwMSIsDQogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsDQogICAgeSA9ICJWZW50YXMgUHJlZGljaGFzIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgTlVFVk9TIEFOw4FMSVNJUyBBw5FBRElET1MNCg0KIyBBbsOhbGlzaXMgZGVsIGVycm9yDQplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobw0KaGlzdChlcnJvcmVzLCANCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAxNTUwMDEiLA0KICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLA0KICAgICBjb2wgPSAic2t5Ymx1ZSIsDQogICAgIGJyZWFrcyA9IDMwKQ0KDQojIEVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIGRlIGxvcyBlcnJvcmVzDQpjYXQoIkVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIGRlIGxvcyBlcnJvcmVzOlxuIikNCmNhdCgiTWVkaWEgZGUgZXJyb3JlczoiLCBtZWFuKGVycm9yZXMpLCAiXG4iKQ0KY2F0KCJEZXN2aWFjacOzbiBlc3TDoW5kYXIgZGUgZXJyb3JlczoiLCBzZChlcnJvcmVzKSwgIlxuIikNCmNhdCgiTcOtbmltbzoiLCBtaW4oZXJyb3JlcyksICJcbiIpDQpjYXQoIk3DoXhpbW86IiwgbWF4KGVycm9yZXMpLCAiXG4iKQ0KY2F0KCJNZWRpYW5hOiIsIG1lZGlhbihlcnJvcmVzKSwgIlxuIikNCg0KIyBHcsOhZmljbyBkZWwgZXJyb3IgdnMgcHJlZGljY2nDs24NCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMTU1MDAxIiwNCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLA0KICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCmBgYHtyfQ0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAxDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIFIyID0gbnVtZXJpYygpLA0KICAgIFJNU0UgPSBudW1lcmljKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBNU0UgPSBudW1lcmljKCksDQogICAgQUlDID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gIjE1NTAwMSIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bw0KICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCIsDQogIFIyID0gcjJfcmYsICAjIEVzdGFzIHZhcmlhYmxlcyBzb24gbGFzIHF1ZSBzZSB1c2FuIGVuIHR1IGPDs2RpZ28gUkYNCiAgUk1TRSA9IHJtc2VfcmYsDQogIE1BUEUgPSBtYXBlX3JmLA0KICBNU0UgPSBtc2VfcmYsDQogIEFJQyA9IGFpY19yZg0KKSkNCmBgYA0KDQojIyBQUk9EVUNUTyAzOTI5Nzg4DQpgYGB7cn0NCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIGRlIHRpZW1wbyBjb250aW51YSBiYXNhZGEgZW4gbGEgZmVjaGENCmRhdG9zXzM5Mjk3ODggPC0gZGF0b3NfMzkyOTc4OCAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCANCiAgICAgICAgIFRpZW1wbyA9IGFzLm51bWVyaWMoRmVjaGEgLSBtaW4oRmVjaGEpKSAvICgzMCAqIDI0ICogNjAgKiA2MCkpICAjIFRpZW1wbyBlbiBtZXNlcw0KDQojIE1vc3RyYXIgdW4gcmVzdW1lbiBkZSBsb3MgZGF0b3MNCnN1bW1hcnkoZGF0b3NfMzkyOTc4OCkNCg0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykNCmRhdG9zX21vZGVsbyA8LSBkYXRvc18zOTI5Nzg4ICU+JQ0KICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQ0KDQojIEFqdXN0YXIgZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3QNCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkDQptb2RlbG9fcmZfMzkyOTc4OCA8LSByYW5kb21Gb3Jlc3QoDQogIFZlbnRhIH4gLiwgDQogIGRhdGEgPSBkYXRvc19tb2RlbG8sDQogIG50cmVlID0gNTAwLCAgICAgICAgICAjIE7Dum1lcm8gZGUgw6FyYm9sZXMNCiAgbXRyeSA9IGZsb29yKHNxcnQobmNvbChkYXRvc19tb2RlbG8pIC0gMSkpLCAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcyBhIGNvbnNpZGVyYXIgZW4gY2FkYSBzcGxpdA0KICBpbXBvcnRhbmNlID0gVFJVRSAgICAgIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCikNCg0KIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvDQpwcmludChtb2RlbG9fcmZfMzkyOTc4OCkNCg0KIyBPYnRlbmVyIHByZWRpY2Npb25lcw0KcHJlZGljY2lvbmVzX3JmIDwtIHByZWRpY3QobW9kZWxvX3JmXzM5Mjk3ODgsIG5ld2RhdGEgPSBkYXRvc19tb2RlbG8pDQoNCiMgQ2FsY3VsYXIgbcOpdHJpY2FzDQojIFLCsiAoeWEgY2FsY3VsYWRvIHBvciBlbCBtb2RlbG8pDQpyMl9yZiA8LSAxIC0gc3VtKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpIC8gc3VtKChkYXRvc19tb2RlbG8kVmVudGEgLSBtZWFuKGRhdG9zX21vZGVsbyRWZW50YSkpXjIpDQoNCiMgQUlDIGFwcm94aW1hZG8gKHVzYW5kbyBmw7NybXVsYSBnZW5lcmFsIHBhcmEgQUlDKQ0KbiA8LSBucm93KGRhdG9zX21vZGVsbykNCmsgPC0gbGVuZ3RoKG1vZGVsb19yZl8zOTI5Nzg4JGZvcmVzdCRuY2F0KSAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcw0KbXNlIDwtIG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZileMikNCmFpY19yZiA8LSBuICogbG9nKG1zZSkgKyAyICogaw0KDQojIFJNU0UNCnJtc2VfcmYgPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpKQ0KDQojIE1BUEUNCm1hcGVfcmYgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZikgLyBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMA0KDQojIE1TRQ0KbXNlX3JmIDwtIG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZileMikNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiTW9kZWxvIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAzOTI5Nzg4XG4iKQ0KY2F0KCJSwrIgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIHIyX3JmLCAiXG4iKQ0KY2F0KCJBSUMgYXByb3hpbWFkbyBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgYWljX3JmLCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBybXNlX3JmLCAiXG4iKQ0KY2F0KCJNQVBFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBtYXBlX3JmLCAiXG4iKQ0KY2F0KCJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1zZV9yZiwgIlxuXG4iKQ0KDQojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzM5Mjk3ODgpDQpwcmludChpbXBvcnRhbmNpYV92YXJzKQ0KDQojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KdmFySW1wUGxvdChtb2RlbG9fcmZfMzkyOTc4OCwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAzOTI5Nzg4IikNCg0KIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzDQpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoDQogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwNCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYNCikNCg0KZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM5Mjk3ODgiLA0KICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLA0KICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQojIEFuw6FsaXNpcyBkZWwgZXJyb3INCmVycm9yZXMgPC0gZGF0b3NfZ3JhZmljbyRPYnNlcnZhZG8gLSBkYXRvc19ncmFmaWNvJFByZWRpY2hvDQpoaXN0KGVycm9yZXMsIA0KICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM5Mjk3ODgiLA0KICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLA0KICAgICBjb2wgPSAic2t5Ymx1ZSIsDQogICAgIGJyZWFrcyA9IDMwKQ0KDQojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbg0KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzOTI5Nzg4IiwNCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLA0KICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCmBgYHtyfQ0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAxDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIFIyID0gbnVtZXJpYygpLA0KICAgIFJNU0UgPSBudW1lcmljKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBNU0UgPSBudW1lcmljKCksDQogICAgQUlDID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gIjM5Mjk3ODgiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8NCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QiLA0KICBSMiA9IHIyX3JmLCAgIyBFc3RhcyB2YXJpYWJsZXMgc29uIGxhcyBxdWUgc2UgdXNhbiBlbiB0dSBjw7NkaWdvIFJGDQogIFJNU0UgPSBybXNlX3JmLA0KICBNQVBFID0gbWFwZV9yZiwNCiAgTVNFID0gbXNlX3JmLA0KICBBSUMgPSBhaWNfcmYNCikpDQpgYGANCg0KDQojIyBQUk9EVUNUTyAzOTA0MTUyDQpgYGB7cn0NCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIGRlIHRpZW1wbyBjb250aW51YSBiYXNhZGEgZW4gbGEgZmVjaGENCmRhdG9zXzM5MDQxNTIgPC0gZGF0b3NfMzkwNDE1MiAlPiUNCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCANCiAgICAgICAgIFRpZW1wbyA9IGFzLm51bWVyaWMoRmVjaGEgLSBtaW4oRmVjaGEpKSAvICgzMCAqIDI0ICogNjAgKiA2MCkpICAjIFRpZW1wbyBlbiBtZXNlcw0KDQojIE1vc3RyYXIgdW4gcmVzdW1lbiBkZSBsb3MgZGF0b3MNCnN1bW1hcnkoZGF0b3NfMzkwNDE1MikNCg0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykNCmRhdG9zX21vZGVsbyA8LSBkYXRvc18zOTA0MTUyICU+JQ0KICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQ0KDQojIEFqdXN0YXIgZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3QNCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkDQptb2RlbG9fcmZfMzkwNDE1MiA8LSByYW5kb21Gb3Jlc3QoDQogIFZlbnRhIH4gLiwgDQogIGRhdGEgPSBkYXRvc19tb2RlbG8sDQogIG50cmVlID0gNTAwLCAgICAgICAgICAjIE7Dum1lcm8gZGUgw6FyYm9sZXMNCiAgbXRyeSA9IGZsb29yKHNxcnQobmNvbChkYXRvc19tb2RlbG8pIC0gMSkpLCAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcyBhIGNvbnNpZGVyYXIgZW4gY2FkYSBzcGxpdA0KICBpbXBvcnRhbmNlID0gVFJVRSAgICAgIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCikNCg0KIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvDQpwcmludChtb2RlbG9fcmZfMzkwNDE1MikNCg0KIyBPYnRlbmVyIHByZWRpY2Npb25lcw0KcHJlZGljY2lvbmVzX3JmIDwtIHByZWRpY3QobW9kZWxvX3JmXzM5MDQxNTIsIG5ld2RhdGEgPSBkYXRvc19tb2RlbG8pDQoNCiMgQ2FsY3VsYXIgbcOpdHJpY2FzDQojIFLCsiAoeWEgY2FsY3VsYWRvIHBvciBlbCBtb2RlbG8pDQpyMl9yZiA8LSAxIC0gc3VtKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpIC8gc3VtKChkYXRvc19tb2RlbG8kVmVudGEgLSBtZWFuKGRhdG9zX21vZGVsbyRWZW50YSkpXjIpDQoNCiMgQUlDIGFwcm94aW1hZG8gKHVzYW5kbyBmw7NybXVsYSBnZW5lcmFsIHBhcmEgQUlDKQ0KbiA8LSBucm93KGRhdG9zX21vZGVsbykNCmsgPC0gbGVuZ3RoKG1vZGVsb19yZl8zOTA0MTUyJGZvcmVzdCRuY2F0KSAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcw0KbXNlIDwtIG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZileMikNCmFpY19yZiA8LSBuICogbG9nKG1zZSkgKyAyICogaw0KDQojIFJNU0UNCnJtc2VfcmYgPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpKQ0KDQojIE1BUEUNCm1hcGVfcmYgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZikgLyBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMA0KDQojIE1TRQ0KbXNlX3JmIDwtIG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZileMikNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiTW9kZWxvIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAzOTA0MTUyXG4iKQ0KY2F0KCJSwrIgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIHIyX3JmLCAiXG4iKQ0KY2F0KCJBSUMgYXByb3hpbWFkbyBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgYWljX3JmLCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBybXNlX3JmLCAiXG4iKQ0KY2F0KCJNQVBFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBtYXBlX3JmLCAiXG4iKQ0KY2F0KCJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1zZV9yZiwgIlxuXG4iKQ0KDQojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzM5MDQxNTIpDQpwcmludChpbXBvcnRhbmNpYV92YXJzKQ0KDQojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KdmFySW1wUGxvdChtb2RlbG9fcmZfMzkwNDE1MiwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAzOTA0MTUyIikNCg0KIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzDQpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoDQogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwNCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYNCikNCg0KZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM5MDQxNTIiLA0KICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLA0KICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQojIEFuw6FsaXNpcyBkZWwgZXJyb3INCmVycm9yZXMgPC0gZGF0b3NfZ3JhZmljbyRPYnNlcnZhZG8gLSBkYXRvc19ncmFmaWNvJFByZWRpY2hvDQpoaXN0KGVycm9yZXMsIA0KICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM5MDQxNTIiLA0KICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLA0KICAgICBjb2wgPSAic2t5Ymx1ZSIsDQogICAgIGJyZWFrcyA9IDMwKQ0KDQojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbg0KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzOTA0MTUyIiwNCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLA0KICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCmBgYHtyfQ0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAxDQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgew0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgNCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLA0KICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLA0KICAgIFIyID0gbnVtZXJpYygpLA0KICAgIFJNU0UgPSBudW1lcmljKCksDQogICAgTUFQRSA9IG51bWVyaWMoKSwNCiAgICBNU0UgPSBudW1lcmljKCksDQogICAgQUlDID0gbnVtZXJpYygpLA0KICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KICApDQp9DQoNCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gIjM5MDQxNTIiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8NCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QiLA0KICBSMiA9IHIyX3JmLCAgIyBFc3RhcyB2YXJpYWJsZXMgc29uIGxhcyBxdWUgc2UgdXNhbiBlbiB0dSBjw7NkaWdvIFJGDQogIFJNU0UgPSBybXNlX3JmLA0KICBNQVBFID0gbWFwZV9yZiwNCiAgTVNFID0gbXNlX3JmLA0KICBBSUMgPSBhaWNfcmYNCikpDQpgYGANCg0KDQojIyBQUk9EVUNUTyAxNTUwMDINCg0KYGBge3J9DQojIFByZXBhcmFyIGRhdG9zIHBhcmEgZWwgbW9kZWxvIChlbGltaW5hciBjb2x1bW5hcyBubyBuZWNlc2FyaWFzKQ0KZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzE1NTAwMiAlPiUNCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkNCg0KIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0DQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZA0KbW9kZWxvX3JmXzE1NTAwMiA8LSByYW5kb21Gb3Jlc3QoDQogIFZlbnRhIH4gLiwgDQogIGRhdGEgPSBkYXRvc19tb2RlbG8sDQogIG50cmVlID0gNTAwLCAgICAgICAgICAjIE7Dum1lcm8gZGUgw6FyYm9sZXMNCiAgbXRyeSA9IGZsb29yKHNxcnQobmNvbChkYXRvc19tb2RlbG8pIC0gMSkpLCAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcyBhIGNvbnNpZGVyYXIgZW4gY2FkYSBzcGxpdA0KICBpbXBvcnRhbmNlID0gVFJVRSAgICAgIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCikNCg0KIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvDQpwcmludChtb2RlbG9fcmZfMTU1MDAyKQ0KDQojIE9idGVuZXIgcHJlZGljY2lvbmVzDQpwcmVkaWNjaW9uZXNfcmYgPC0gcHJlZGljdChtb2RlbG9fcmZfMTU1MDAyLCBuZXdkYXRhID0gZGF0b3NfbW9kZWxvKQ0KDQojIENhbGN1bGFyIG3DqXRyaWNhcw0KIyBSwrIgKHlhIGNhbGN1bGFkbyBwb3IgZWwgbW9kZWxvKQ0KcjJfcmYgPC0gMSAtIHN1bSgoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKV4yKSAvIHN1bSgoZGF0b3NfbW9kZWxvJFZlbnRhIC0gbWVhbihkYXRvc19tb2RlbG8kVmVudGEpKV4yKQ0KDQojIEFJQyBhcHJveGltYWRvICh1c2FuZG8gZsOzcm11bGEgZ2VuZXJhbCBwYXJhIEFJQykNCm4gPC0gbnJvdyhkYXRvc19tb2RlbG8pDQprIDwtIGxlbmd0aChtb2RlbG9fcmZfMTU1MDAyJGZvcmVzdCRuY2F0KSAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcw0KbXNlIDwtIG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZileMikNCmFpY19yZiA8LSBuICogbG9nKG1zZSkgKyAyICogaw0KDQojIFJNU0UNCnJtc2VfcmYgPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpKQ0KDQojIE1BUEUNCm1hcGVfcmYgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZikgLyBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMA0KDQojIE1TRQ0KbXNlX3JmIDwtIG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZileMikNCg0KIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMNCmNhdCgiTW9kZWxvIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAxNTUwMDJcbiIpDQpjYXQoIlLCsiBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgcjJfcmYsICJcbiIpDQpjYXQoIkFJQyBhcHJveGltYWRvIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBhaWNfcmYsICJcbiIpDQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIHJtc2VfcmYsICJcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1hcGVfcmYsICJcbiIpDQpjYXQoIk1TRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgbXNlX3JmLCAiXG5cbiIpDQoNCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCmltcG9ydGFuY2lhX3ZhcnMgPC0gaW1wb3J0YW5jZShtb2RlbG9fcmZfMTU1MDAyKQ0KcHJpbnQoaW1wb3J0YW5jaWFfdmFycykNCg0KIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCnZhckltcFBsb3QobW9kZWxvX3JmXzE1NTAwMiwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDIiKQ0KDQojIENyZWFyIGdyw6FmaWNvIGRlIHZhbG9yZXMgb2JzZXJ2YWRvcyB2cyBwcmVkaWNjaW9uZXMNCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgNCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLA0KICBQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZg0KKQ0KDQpnZ3Bsb3QoZGF0b3NfZ3JhZmljbywgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMTU1MDAyIiwNCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwNCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBBbsOhbGlzaXMgZGVsIGVycm9yDQplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobw0KaGlzdChlcnJvcmVzLCANCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAxNTUwMDIiLA0KICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLA0KICAgICBjb2wgPSAic2t5Ymx1ZSIsDQogICAgIGJyZWFrcyA9IDMwKQ0KDQojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbg0KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAxNTUwMDIiLA0KICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsDQogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQpgYGANCg0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgUjIgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIE1TRSA9IG51bWVyaWMoKSwNCiAgICBBSUMgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMTU1MDAyIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvDQogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IiwNCiAgUjIgPSByMl9yZiwgICMgRXN0YXMgdmFyaWFibGVzIHNvbiBsYXMgcXVlIHNlIHVzYW4gZW4gdHUgY8OzZGlnbyBSRg0KICBSTVNFID0gcm1zZV9yZiwNCiAgTUFQRSA9IG1hcGVfcmYsDQogIE1TRSA9IG1zZV9yZiwNCiAgQUlDID0gYWljX3JmDQopKQ0KYGBgDQoNCg0KIyMgUFJPRFVDVE8gMzY3ODA1NQ0KDQpgYGB7cn0NCiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpDQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMzY3ODA1NSAlPiUNCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkNCg0KIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0DQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZA0KbW9kZWxvX3JmXzM2NzgwNTUgPC0gcmFuZG9tRm9yZXN0KA0KICBWZW50YSB+IC4sIA0KICBkYXRhID0gZGF0b3NfbW9kZWxvLA0KICBudHJlZSA9IDUwMCwgICAgICAgICAgIyBOw7ptZXJvIGRlIMOhcmJvbGVzDQogIG10cnkgPSBmbG9vcihzcXJ0KG5jb2woZGF0b3NfbW9kZWxvKSAtIDEpKSwgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMgYSBjb25zaWRlcmFyIGVuIGNhZGEgc3BsaXQNCiAgaW1wb3J0YW5jZSA9IFRSVUUgICAgICMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQopDQoNCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbw0KcHJpbnQobW9kZWxvX3JmXzM2NzgwNTUpDQoNCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMNCnByZWRpY2Npb25lc19yZiA8LSBwcmVkaWN0KG1vZGVsb19yZl8zNjc4MDU1LCBuZXdkYXRhID0gZGF0b3NfbW9kZWxvKQ0KDQojIENhbGN1bGFyIG3DqXRyaWNhcw0KIyBSwrIgKHlhIGNhbGN1bGFkbyBwb3IgZWwgbW9kZWxvKQ0KcjJfcmYgPC0gMSAtIHN1bSgoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKV4yKSAvIHN1bSgoZGF0b3NfbW9kZWxvJFZlbnRhIC0gbWVhbihkYXRvc19tb2RlbG8kVmVudGEpKV4yKQ0KDQojIEFJQyBhcHJveGltYWRvICh1c2FuZG8gZsOzcm11bGEgZ2VuZXJhbCBwYXJhIEFJQykNCm4gPC0gbnJvdyhkYXRvc19tb2RlbG8pDQprIDwtIGxlbmd0aChtb2RlbG9fcmZfMzY3ODA1NSRmb3Jlc3QkbmNhdCkgICMgTsO6bWVybyBkZSB2YXJpYWJsZXMNCm1zZSA8LSBtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpDQphaWNfcmYgPC0gbiAqIGxvZyhtc2UpICsgMiAqIGsNCg0KIyBSTVNFDQpybXNlX3JmIDwtIHNxcnQobWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKV4yKSkNCg0KIyBNQVBFDQptYXBlX3JmIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KIyBNU0UNCm1zZV9yZiA8LSBtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpDQoNCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzDQpjYXQoIk1vZGVsbyBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMzY3ODA1NVxuIikNCmNhdCgiUsKyIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCByMl9yZiwgIlxuIikNCmNhdCgiQUlDIGFwcm94aW1hZG8gZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIGFpY19yZiwgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6Iiwgcm1zZV9yZiwgIlxuIikNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgbWFwZV9yZiwgIlxuIikNCmNhdCgiTVNFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBtc2VfcmYsICJcblxuIikNCg0KIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KaW1wb3J0YW5jaWFfdmFycyA8LSBpbXBvcnRhbmNlKG1vZGVsb19yZl8zNjc4MDU1KQ0KcHJpbnQoaW1wb3J0YW5jaWFfdmFycykNCg0KIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCnZhckltcFBsb3QobW9kZWxvX3JmXzM2NzgwNTUsIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMzY3ODA1NSIpDQoNCiMgQ3JlYXIgZ3LDoWZpY28gZGUgdmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2Npb25lcw0KZGF0b3NfZ3JhZmljbyA8LSBkYXRhLmZyYW1lKA0KICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsDQogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmDQopDQoNCmdncGxvdChkYXRvc19ncmFmaWNvLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKw0KICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArDQogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlZhbG9yZXMgT2JzZXJ2YWRvcyB2cyBQcmVkaWNjaW9uZXMgLSBQcm9kdWN0byAzNjc4MDU1IiwNCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwNCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBBbsOhbGlzaXMgZGVsIGVycm9yDQplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobw0KaGlzdChlcnJvcmVzLCANCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAzNjc4MDU1IiwNCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwNCiAgICAgY29sID0gInNreWJsdWUiLA0KICAgICBicmVha3MgPSAzMCkNCg0KIyBHcsOhZmljbyBkZWwgZXJyb3IgdnMgcHJlZGljY2nDs24NCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzY3ODA1NSIsDQogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwNCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgUjIgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIE1TRSA9IG51bWVyaWMoKSwNCiAgICBBSUMgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMzY3ODA1NSIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bw0KICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCIsDQogIFIyID0gcjJfcmYsICAjIEVzdGFzIHZhcmlhYmxlcyBzb24gbGFzIHF1ZSBzZSB1c2FuIGVuIHR1IGPDs2RpZ28gUkYNCiAgUk1TRSA9IHJtc2VfcmYsDQogIE1BUEUgPSBtYXBlX3JmLA0KICBNU0UgPSBtc2VfcmYsDQogIEFJQyA9IGFpY19yZg0KKSkNCmBgYA0KDQoNCiMgWEdCT09TVA0KDQojIyBQUk9EVUNUTyAxNTUwMDENCmBgYHtyfQ0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykNCmRhdG9zX21vZGVsbyA8LSBkYXRvc18xNTUwMDEgJT4lDQogIHNlbGVjdCgtVHJ4X0ZlY2hhLCAtRmVjaGEpDQoNCiMgRGl2aWRpciBsb3MgZGF0b3MgZW4gY29uanVudG9zIGRlIGVudHJlbmFtaWVudG8gKDgwJSkgeSBwcnVlYmEgKDIwJSkNCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkDQppbmRpY2VzX3RyYWluIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfbW9kZWxvJFZlbnRhLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpDQpkYXRvc190cmFpbiA8LSBkYXRvc19tb2RlbG9baW5kaWNlc190cmFpbiwgXQ0KZGF0b3NfdGVzdCA8LSBkYXRvc19tb2RlbG9bLWluZGljZXNfdHJhaW4sIF0NCg0KIyBTZXBhcmFyIHZhcmlhYmxlcyBwcmVkaWN0b3JhcyB5IHZhcmlhYmxlIG9iamV0aXZvDQpYX3RyYWluIDwtIGFzLm1hdHJpeChkYXRvc190cmFpblssIGNvbG5hbWVzKGRhdG9zX3RyYWluKSAhPSAiVmVudGEiXSkNCnlfdHJhaW4gPC0gZGF0b3NfdHJhaW4kVmVudGENCg0KWF90ZXN0IDwtIGFzLm1hdHJpeChkYXRvc190ZXN0WywgY29sbmFtZXMoZGF0b3NfdGVzdCkgIT0gIlZlbnRhIl0pDQp5X3Rlc3QgPC0gZGF0b3NfdGVzdCRWZW50YQ0KDQojIENyZWFyIG1hdHJpY2VzIERNYXRyaXggcGFyYSBYR0Jvb3N0DQpkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfdHJhaW4sIGxhYmVsID0geV90cmFpbikNCmR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSBYX3Rlc3QsIGxhYmVsID0geV90ZXN0KQ0KDQojIERlZmluaXIgdW5hIHJlamlsbGEgY29tcGxldGEgZGUgaGlwZXJwYXLDoW1ldHJvcyBwYXJhIGLDunNxdWVkYQ0KcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgNCiAgZXRhID0gYygwLjAxLCAwLjA1LCAwLjEsIDAuMyksICAgICAgICAgIyBMZWFybmluZyByYXRlDQogIG1heF9kZXB0aCA9IGMoMywgNSwgNywgOSksICAgICAgICAgICAgICMgUHJvZnVuZGlkYWQgbcOheGltYQ0KICBzdWJzYW1wbGUgPSBjKDAuNiwgMC44LCAxLjApLCAgICAgICAgICAjIFN1Ym11ZXN0cmEgZGUgb2JzZXJ2YWNpb25lcw0KICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjYsIDAuOCwgMS4wKSwgICAjIFN1Ym11ZXN0cmEgZGUgdmFyaWFibGVzDQogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMsIDUpLCAgICAgICAgICMgUGVzbyBtw61uaW1vIGVuIG5vZG9zIGhpam9zDQogIGdhbW1hID0gYygwLCAwLjEsIDAuMykgICAgICAgICAgICAgICAgICMgUmVndWxhcml6YWNpw7NuIGdhbW1hDQopDQoNCiMgTW9zdHJhciBjdcOhbnRhcyBjb21iaW5hY2lvbmVzIHRlbmVtb3MNCmNhdCgiTsO6bWVybyB0b3RhbCBkZSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3M6IiwgbnJvdyhwYXJhbV9ncmlkKSwgIlxuIikNCg0KIyBQYXJhIGVzdGUgZWplbXBsbywgdmFtb3MgYSBsaW1pdGFyIGVsIG7Dum1lcm8gZGUgY29tYmluYWNpb25lcw0KIyBTZWxlY2Npb25hbmRvIHVuIHN1YmNvbmp1bnRvIGFsZWF0b3JpbyBkZSBjb21iaW5hY2lvbmVzICgyMCBjb21iaW5hY2lvbmVzKQ0Kc2V0LnNlZWQoMTIzKQ0KaWYgKG5yb3cocGFyYW1fZ3JpZCkgPiAyMCkgew0KICBtdWVzdHJhX2luZGljZXMgPC0gc2FtcGxlKDE6bnJvdyhwYXJhbV9ncmlkKSwgMjApDQogIHBhcmFtX2dyaWRfcmVkdWNpZGEgPC0gcGFyYW1fZ3JpZFttdWVzdHJhX2luZGljZXMsIF0NCn0gZWxzZSB7DQogIHBhcmFtX2dyaWRfcmVkdWNpZGEgPC0gcGFyYW1fZ3JpZA0KfQ0KDQpjYXQoIk7Dum1lcm8gZGUgY29tYmluYWNpb25lcyBhIGV2YWx1YXI6IiwgbnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSwgIlxuIikNCg0KIyBGdW5jacOzbiBwYXJhIGV2YWx1YXIgdW4gY29uanVudG8gZGUgaGlwZXJwYXLDoW1ldHJvcyBjb24gdmFsaWRhY2nDs24gY3J1emFkYQ0KZXZhbHVhdGVfcGFyYW1zIDwtIGZ1bmN0aW9uKHBhcmFtc19yb3cpIHsNCiAgcGFyYW1zIDwtIGxpc3QoDQogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLA0KICAgIGV2YWxfbWV0cmljID0gInJtc2UiLA0KICAgIGV0YSA9IHBhcmFtc19yb3ckZXRhLA0KICAgIG1heF9kZXB0aCA9IHBhcmFtc19yb3ckbWF4X2RlcHRoLA0KICAgIHN1YnNhbXBsZSA9IHBhcmFtc19yb3ckc3Vic2FtcGxlLA0KICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbXNfcm93JGNvbHNhbXBsZV9ieXRyZWUsDQogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtc19yb3ckbWluX2NoaWxkX3dlaWdodCwNCiAgICBnYW1tYSA9IHBhcmFtc19yb3ckZ2FtbWENCiAgKQ0KICANCiAgIyBSZWFsaXphciB2YWxpZGFjacOzbiBjcnV6YWRhDQogIGN2X3Jlc3VsdHMgPC0geGdiLmN2KA0KICAgIHBhcmFtcyA9IHBhcmFtcywNCiAgICBkYXRhID0gZHRyYWluLA0KICAgIG5yb3VuZHMgPSAxMDAsDQogICAgbmZvbGQgPSA1LCAgIyA1LWZvbGQgdmFsaWRhY2nDs24gY3J1emFkYQ0KICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDEwLA0KICAgIHZlcmJvc2UgPSAwDQogICkNCiAgDQogICMgRXh0cmFlciBlbCBtZWpvciBSTVNFIHkgZWwgbsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhcw0KICBiZXN0X3Jtc2UgPC0gbWluKGN2X3Jlc3VsdHMkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pDQogIGJlc3RfbnJvdW5kcyA8LSB3aGljaC5taW4oY3ZfcmVzdWx0cyRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbikNCiAgDQogIHJldHVybihsaXN0KHJtc2UgPSBiZXN0X3Jtc2UsIG5yb3VuZHMgPSBiZXN0X25yb3VuZHMsIHBhcmFtcyA9IHBhcmFtcykpDQp9DQoNCiMgSW5pY2lhbGl6YXIgdGFibGEgcGFyYSBhbG1hY2VuYXIgcmVzdWx0YWRvcw0KcmVzdWx0YWRvc19ncmlkIDwtIGRhdGEuZnJhbWUoDQogIGV0YSA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksDQogIG1heF9kZXB0aCA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksDQogIHN1YnNhbXBsZSA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksDQogIGNvbHNhbXBsZV9ieXRyZWUgPSBudW1lcmljKG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwNCiAgZ2FtbWEgPSBudW1lcmljKG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpLA0KICBucm91bmRzID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwNCiAgcm1zZSA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSkNCikNCg0KIyBSZWFsaXphciBsYSBiw7pzcXVlZGEgZW4gY3VhZHLDrWN1bGEgKGVzdG8gcHVlZGUgdGFyZGFyIHZhcmlvcyBtaW51dG9zKQ0KY2F0KCJJbmljaWFuZG8gYsO6c3F1ZWRhIGVuIGN1YWRyw61jdWxhLi4uXG4iKQ0KDQpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSB7DQogIGNhdChzcHJpbnRmKCJFdmFsdWFuZG8gY29tYmluYWNpw7NuICVkIGRlICVkXG4iLCBpLCBucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSkNCiAgDQogICMgT2J0ZW5lciBmaWxhIGRlIHBhcsOhbWV0cm9zIGFjdHVhbA0KICBwYXJhbXNfcm93IDwtIHBhcmFtX2dyaWRfcmVkdWNpZGFbaSwgXQ0KICANCiAgIyBFdmFsdWFyIGNvbWJpbmFjacOzbiBhY3R1YWwNCiAgcmVzdWx0IDwtIGV2YWx1YXRlX3BhcmFtcyhwYXJhbXNfcm93KQ0KICANCiAgIyBHdWFyZGFyIHJlc3VsdGFkb3MNCiAgcmVzdWx0YWRvc19ncmlkJGV0YVtpXSA8LSBwYXJhbXNfcm93JGV0YQ0KICByZXN1bHRhZG9zX2dyaWQkbWF4X2RlcHRoW2ldIDwtIHBhcmFtc19yb3ckbWF4X2RlcHRoDQogIHJlc3VsdGFkb3NfZ3JpZCRzdWJzYW1wbGVbaV0gPC0gcGFyYW1zX3JvdyRzdWJzYW1wbGUNCiAgcmVzdWx0YWRvc19ncmlkJGNvbHNhbXBsZV9ieXRyZWVbaV0gPC0gcGFyYW1zX3JvdyRjb2xzYW1wbGVfYnl0cmVlDQogIHJlc3VsdGFkb3NfZ3JpZCRtaW5fY2hpbGRfd2VpZ2h0W2ldIDwtIHBhcmFtc19yb3ckbWluX2NoaWxkX3dlaWdodA0KICByZXN1bHRhZG9zX2dyaWQkZ2FtbWFbaV0gPC0gcGFyYW1zX3JvdyRnYW1tYQ0KICByZXN1bHRhZG9zX2dyaWQkbnJvdW5kc1tpXSA8LSByZXN1bHQkbnJvdW5kcw0KICByZXN1bHRhZG9zX2dyaWQkcm1zZVtpXSA8LSByZXN1bHQkcm1zZQ0KfQ0KDQojIE9yZGVuYXIgcmVzdWx0YWRvcyBwb3IgUk1TRSAoZGUgbWVub3IgYSBtYXlvcikNCnJlc3VsdGFkb3NfZ3JpZCA8LSByZXN1bHRhZG9zX2dyaWRbb3JkZXIocmVzdWx0YWRvc19ncmlkJHJtc2UpLCBdDQoNCiMgTW9zdHJhciBsb3MgNSBtZWpvcmVzIGNvbmp1bnRvcyBkZSBoaXBlcnBhcsOhbWV0cm9zDQpjYXQoIlxuTG9zIDUgbWVqb3JlcyBjb25qdW50b3MgZGUgaGlwZXJwYXLDoW1ldHJvczpcbiIpDQpwcmludChoZWFkKHJlc3VsdGFkb3NfZ3JpZCwgNSkpDQoNCiMgT2J0ZW5lciBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zDQptZWpvcmVzX3BhcmFtcyA8LSBsaXN0KA0KICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsDQogIGV2YWxfbWV0cmljID0gInJtc2UiLA0KICBldGEgPSByZXN1bHRhZG9zX2dyaWQkZXRhWzFdLA0KICBtYXhfZGVwdGggPSByZXN1bHRhZG9zX2dyaWQkbWF4X2RlcHRoWzFdLA0KICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zX2dyaWQkc3Vic2FtcGxlWzFdLA0KICBjb2xzYW1wbGVfYnl0cmVlID0gcmVzdWx0YWRvc19ncmlkJGNvbHNhbXBsZV9ieXRyZWVbMV0sDQogIG1pbl9jaGlsZF93ZWlnaHQgPSByZXN1bHRhZG9zX2dyaWQkbWluX2NoaWxkX3dlaWdodFsxXSwNCiAgZ2FtbWEgPSByZXN1bHRhZG9zX2dyaWQkZ2FtbWFbMV0NCikNCg0KbWVqb3JfbnJvdW5kcyA8LSByZXN1bHRhZG9zX2dyaWQkbnJvdW5kc1sxXQ0KDQpjYXQoIlxuTWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zIGVuY29udHJhZG9zOlxuIikNCnByaW50KG1lam9yZXNfcGFyYW1zKQ0KY2F0KCJOw7ptZXJvIMOzcHRpbW8gZGUgcm9uZGFzOiIsIG1lam9yX25yb3VuZHMsICJcbiIpDQpjYXQoIlJNU0UgZW4gdmFsaWRhY2nDs24gY3J1emFkYToiLCByZXN1bHRhZG9zX2dyaWQkcm1zZVsxXSwgIlxuXG4iKQ0KDQojIEVudHJlbmFyIGVsIG1vZGVsbyBmaW5hbCBjb24gbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcw0KbW9kZWxvX3hnYl8xNTUwMDEgPC0geGdiLnRyYWluKA0KICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtcywNCiAgZGF0YSA9IGR0cmFpbiwNCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsDQogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW4sIHRlc3QgPSBkdGVzdCksDQogIHZlcmJvc2UgPSAwDQopDQoNCiMgSGFjZXIgcHJlZGljY2lvbmVzIGVuIGVsIGNvbmp1bnRvIGRlIHBydWViYQ0KcHJlZGljY2lvbmVzX3Rlc3QgPC0gcHJlZGljdChtb2RlbG9feGdiXzE1NTAwMSwgZHRlc3QpDQoNCiMgQ2FsY3VsYXIgbcOpdHJpY2FzIGVuIGVsIGNvbmp1bnRvIGRlIHBydWViYQ0KIyBSwrINCnIyX3Rlc3QgPC0gMSAtIHN1bSgoeV90ZXN0IC0gcHJlZGljY2lvbmVzX3Rlc3QpXjIpIC8gc3VtKCh5X3Rlc3QgLSBtZWFuKHlfdGVzdCkpXjIpDQoNCiMgUk1TRQ0Kcm1zZV90ZXN0IDwtIHNxcnQobWVhbigoeV90ZXN0IC0gcHJlZGljY2lvbmVzX3Rlc3QpXjIpKQ0KDQojIE1BUEUNCm1hcGVfdGVzdCA8LSBtZWFuKGFicygoeV90ZXN0IC0gcHJlZGljY2lvbmVzX3Rlc3QpIC8gcG1heCh5X3Rlc3QsIDAuMDEpKSkgKiAxMDANCg0KIyBNU0UNCm1zZV90ZXN0IDwtIG1lYW4oKHlfdGVzdCAtIHByZWRpY2Npb25lc190ZXN0KV4yKQ0KDQojIEFJQyBhcHJveGltYWRvDQpuIDwtIGxlbmd0aCh5X3Rlc3QpDQprIDwtIGxlbmd0aChtZWpvcmVzX3BhcmFtcykgKyAxICAjICsxIHBvciBlbCBuw7ptZXJvIGRlIHJvbmRhcw0KYWljX3Rlc3QgPC0gbiAqIGxvZyhtc2VfdGVzdCkgKyAyICogaw0KDQojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcyBlbiBlbCBjb25qdW50byBkZSBwcnVlYmENCmNhdCgiTcOpdHJpY2FzIGVuIGVsIGNvbmp1bnRvIGRlIHBydWViYTpcbiIpDQpjYXQoIlLCsiBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgcjJfdGVzdCwgIlxuIikNCmNhdCgiQUlDIGFwcm94aW1hZG8gZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIGFpY190ZXN0LCAiXG4iKQ0KY2F0KCJSTVNFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBybXNlX3Rlc3QsICJcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIG1hcGVfdGVzdCwgIlxuIikNCmNhdCgiTVNFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtc2VfdGVzdCwgIlxuXG4iKQ0KDQojIEFob3JhIGhhY2VyIHByZWRpY2Npb25lcyBlbiBlbCBjb25qdW50byBjb21wbGV0byBwYXJhIGNvbXBhcmFiaWxpZGFkIGNvbiBvdHJvcyBtb2RlbG9zDQpYX2NvbXBsZXRvIDwtIGFzLm1hdHJpeChkYXRvc19tb2RlbG9bLCBjb2xuYW1lcyhkYXRvc19tb2RlbG8pICE9ICJWZW50YSJdKQ0KcHJlZGljY2lvbmVzX2NvbXBsZXRhcyA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMTU1MDAxLCBYX2NvbXBsZXRvKQ0KDQojIENhbGN1bGFyIG3DqXRyaWNhcyBlbiBlbCBjb25qdW50byBjb21wbGV0bw0KIyBSwrINCnIyX2NvbXBsZXRvIDwtIDEgLSBzdW0oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0YXMpXjIpIC8gc3VtKChkYXRvc19tb2RlbG8kVmVudGEgLSBtZWFuKGRhdG9zX21vZGVsbyRWZW50YSkpXjIpDQoNCiMgUk1TRQ0Kcm1zZV9jb21wbGV0byA8LSBzcXJ0KG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0YXMpXjIpKQ0KDQojIE1BUEUNCm1hcGVfY29tcGxldG8gPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0YXMpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KIyBNU0UNCm1zZV9jb21wbGV0byA8LSBtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldGFzKV4yKQ0KDQojIEFJQyBhcHJveGltYWRvDQpuIDwtIG5yb3coZGF0b3NfbW9kZWxvKQ0KYWljX2NvbXBsZXRvIDwtIG4gKiBsb2cobXNlX2NvbXBsZXRvKSArIDIgKiBrDQoNCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzIGVuIGVsIGNvbmp1bnRvIGNvbXBsZXRvDQpjYXQoIk3DqXRyaWNhcyBlbiBlbCBjb25qdW50byBjb21wbGV0bzpcbiIpDQpjYXQoIlLCsiBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgcjJfY29tcGxldG8sICJcbiIpDQpjYXQoIkFJQyBhcHJveGltYWRvIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBhaWNfY29tcGxldG8sICJcbiIpDQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHJtc2VfY29tcGxldG8sICJcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIG1hcGVfY29tcGxldG8sICJcbiIpDQpjYXQoIk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbXNlX2NvbXBsZXRvLCAiXG5cbiIpDQoNCiMgSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQppbXBvcnRhbmNpYSA8LSB4Z2IuaW1wb3J0YW5jZSgNCiAgZmVhdHVyZV9uYW1lcyA9IGNvbG5hbWVzKGRhdG9zX21vZGVsbylbY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSwNCiAgbW9kZWwgPSBtb2RlbG9feGdiXzE1NTAwMQ0KKQ0KcHJpbnQoaW1wb3J0YW5jaWEpDQoNCiMgR3JhZmljYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQp4Z2IucGxvdC5pbXBvcnRhbmNlKGltcG9ydGFuY2VfbWF0cml4ID0gaW1wb3J0YW5jaWEsIA0KICAgICAgICAgICAgICAgICAgIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMTU1MDAxIChYR0Jvb3N0KSIpDQoNCiMgQ3JlYXIgZ3LDoWZpY28gZGUgdmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2Npb25lcw0KZGF0b3NfZ3JhZmljbyA8LSBkYXRhLmZyYW1lKA0KICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsDQogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRhcw0KKQ0KDQpnZ3Bsb3QoZGF0b3NfZ3JhZmljbywgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMTU1MDAxIChYR0Jvb3N0KSIsDQogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsDQogICAgeSA9ICJWZW50YXMgUHJlZGljaGFzIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCiMgQW7DoWxpc2lzIGRlbCBlcnJvcg0KZXJyb3JlcyA8LSBkYXRvc19ncmFmaWNvJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY28kUHJlZGljaG8NCmhpc3QoZXJyb3JlcywgDQogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMTU1MDAxIChYR0Jvb3N0KSIsDQogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsDQogICAgIGNvbCA9ICJza3libHVlIiwNCiAgICAgYnJlYWtzID0gMzApDQoNCiMgR3LDoWZpY28gZGVsIGVycm9yIHZzIHByZWRpY2Npw7NuDQpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19jb21wbGV0YXMsIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAxNTUwMDEgKFhHQm9vc3QpIiwNCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLA0KICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgZGUgWEdCb29zdCBwYXJhIHByb2R1Y3RvIDE1NTAwMQ0KaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsNCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoDQogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBSMiA9IG51bWVyaWMoKSwNCiAgICBSTVNFID0gbnVtZXJpYygpLA0KICAgIE1BUEUgPSBudW1lcmljKCksDQogICAgTVNFID0gbnVtZXJpYygpLA0KICAgIEFJQyA9IG51bWVyaWMoKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9ICIxNTUwMDEiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8NCiAgTW9kZWxvID0gIlhHQm9vc3QiLA0KICBSMiA9IHIyX2NvbXBsZXRvLCAgIyBVc2Ftb3MgbGFzIG3DqXRyaWNhcyBkZWwgY29uanVudG8gY29tcGxldG8NCiAgUk1TRSA9IHJtc2VfY29tcGxldG8sDQogIE1BUEUgPSBtYXBlX2NvbXBsZXRvLA0KICBNU0UgPSBtc2VfY29tcGxldG8sDQogIEFJQyA9IGFpY19jb21wbGV0bw0KKSkNCmBgYA0KDQojIyBQUk9EVUNUTyAzOTI5Nzg4DQoNCmBgYHtyfQ0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykNCmRhdG9zX21vZGVsbyA8LSBkYXRvc18zOTI5Nzg4ICU+JQ0KICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQ0KDQojIFBhc28gMjogRGl2aWRpciBsb3MgZGF0b3MgZW4gY29uanVudG9zIGRlIGVudHJlbmFtaWVudG8gKDgwJSkgeSBwcnVlYmEgKDIwJSkNCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkDQp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zX21vZGVsbyRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQ0KdHJhaW5fZGF0YSA8LSBkYXRvc19tb2RlbG9bdHJhaW5faW5kZXgsIF0NCnRlc3RfZGF0YSA8LSBkYXRvc19tb2RlbG9bLXRyYWluX2luZGV4LCBdDQoNCiMgUHJlcGFyYXIgbWF0cmljZXMgcGFyYSBYR0Jvb3N0DQp0cmFpbl94IDwtIGFzLm1hdHJpeCh0cmFpbl9kYXRhWywgY29sbmFtZXModHJhaW5fZGF0YSkgIT0gIlZlbnRhIl0pDQp0cmFpbl95IDwtIHRyYWluX2RhdGEkVmVudGENCg0KdGVzdF94IDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGFbLCBjb2xuYW1lcyh0ZXN0X2RhdGEpICE9ICJWZW50YSJdKQ0KdGVzdF95IDwtIHRlc3RfZGF0YSRWZW50YQ0KDQojIENyZWFyIERNYXRyaXggcGFyYSBYR0Jvb3N0DQpkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRyYWluX3gsIGxhYmVsID0gdHJhaW5feSkNCmR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0ZXN0X3gsIGxhYmVsID0gdGVzdF95KQ0KDQojIFBhc28gMzogRGVmaW5pciBsYSByZWppbGxhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBHcmlkIFNlYXJjaA0KcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgNCiAgZXRhID0gYygwLjAxLCAwLjA1LCAwLjEsIDAuMyksICAgICAgICAgICMgTGVhcm5pbmcgcmF0ZQ0KICBtYXhfZGVwdGggPSBjKDMsIDYsIDkpLCAgICAgICAgICAgICAgICAgIyBQcm9mdW5kaWRhZCBtw6F4aW1hDQogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMsIDUpLCAgICAgICAgICAjIFBlc28gbcOtbmltbyBkZSBub2RvIGhpam8NCiAgc3Vic2FtcGxlID0gYygwLjcsIDAuOSksICAgICAgICAgICAgICAgICMgUHJvcG9yY2nDs24gZGUgb2JzZXJ2YWNpb25lcw0KICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjcsIDAuOSksICAgICAgICAgIyBQcm9wb3JjacOzbiBkZSB2YXJpYWJsZXMNCiAgZ2FtbWEgPSBjKDAsIDAuMSwgMC4zKSAgICAgICAgICAgICAgICAgICMgUmVndWxhcml6YWNpw7NuIGdhbW1hDQopDQoNCiMgTW9zdHJhciBkaW1lbnNpb25lcyBkZSBsYSByZWppbGxhDQpjYXQoIkdyaWQgU2VhcmNoIHBhcmEgWEdCb29zdCAtIFByb2R1Y3RvIDM5Mjk3ODhcbiIpDQpjYXQoIk7Dum1lcm8gdG90YWwgZGUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zOiIsIG5yb3cocGFyYW1fZ3JpZCksICJcblxuIikNCg0KIyBQYXJhIGVzdGUgZWplbXBsbywgbGltaXRhciBhIDEyIGNvbWJpbmFjaW9uZXMgcGFyYSBhaG9ycmFyIHRpZW1wbw0KIyBFbiB1biBlc2NlbmFyaW8gcmVhbCwgcG9kcsOtYXMgZXZhbHVhciB0b2RhcyBvIHVzYXIgdW5hIGVzdHJhdGVnaWEgbcOhcyBlZmljaWVudGUNCnNldC5zZWVkKDQ1NikNCmlmIChucm93KHBhcmFtX2dyaWQpID4gMTIpIHsNCiAgc2VsZWN0ZWRfaW5kaWNlcyA8LSBzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAxMikNCiAgcGFyYW1fZ3JpZCA8LSBwYXJhbV9ncmlkW3NlbGVjdGVkX2luZGljZXMsIF0NCiAgY2F0KCJTZWxlY2Npb25hbmRvIDEyIGNvbWJpbmFjaW9uZXMgYWxlYXRvcmlhcyBwYXJhIGV2YWx1YWNpw7NuLlxuXG4iKQ0KfQ0KDQojIFBhc28gNDogSW1wbGVtZW50YXIgR3JpZCBTZWFyY2gNCnJlc3VsdGFkb3MgPC0gZGF0YS5mcmFtZSgpDQoNCmNhdCgiSW5pY2lhbmRvIEdyaWQgU2VhcmNoLi4uXG4iKQ0KDQpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWQpKSB7DQogICMgRXh0cmFlciBwYXLDoW1ldHJvcyBkZSBsYSBjb21iaW5hY2nDs24gYWN0dWFsDQogIHBhcmFtcyA8LSBsaXN0KA0KICAgIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwgICAgICAjIE9iamV0aXZvIGRlIHJlZ3Jlc2nDs24NCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwgICAgICAgICAgICAgICAjIE3DqXRyaWNhIGRlIGV2YWx1YWNpw7NuDQogICAgZXRhID0gcGFyYW1fZ3JpZCRldGFbaV0sDQogICAgbWF4X2RlcHRoID0gcGFyYW1fZ3JpZCRtYXhfZGVwdGhbaV0sDQogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtX2dyaWQkbWluX2NoaWxkX3dlaWdodFtpXSwNCiAgICBzdWJzYW1wbGUgPSBwYXJhbV9ncmlkJHN1YnNhbXBsZVtpXSwNCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1fZ3JpZCRjb2xzYW1wbGVfYnl0cmVlW2ldLA0KICAgIGdhbW1hID0gcGFyYW1fZ3JpZCRnYW1tYVtpXQ0KICApDQogIA0KICBjYXQoIkV2YWx1YW5kbyBjb21iaW5hY2nDs24iLCBpLCAiZGUiLCBucm93KHBhcmFtX2dyaWQpLCAiOlxuIikNCiAgY2F0KCIgIGV0YSA9IiwgcGFyYW1zJGV0YSwgDQogICAgICAiLCBtYXhfZGVwdGggPSIsIHBhcmFtcyRtYXhfZGVwdGgsIA0KICAgICAgIiwgbWluX2NoaWxkX3dlaWdodCA9IiwgcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsIA0KICAgICAgIiwgc3Vic2FtcGxlID0iLCBwYXJhbXMkc3Vic2FtcGxlLCANCiAgICAgICIsIGNvbHNhbXBsZV9ieXRyZWUgPSIsIHBhcmFtcyRjb2xzYW1wbGVfYnl0cmVlLA0KICAgICAgIiwgZ2FtbWEgPSIsIHBhcmFtcyRnYW1tYSwgIlxuIikNCiAgDQogICMgVmFsaWRhY2nDs24gY3J1emFkYSBwYXJhIGVuY29udHJhciBlbCBuw7ptZXJvIMOzcHRpbW8gZGUgaXRlcmFjaW9uZXMNCiAgY3ZfbW9kZWwgPC0geGdiLmN2KA0KICAgIHBhcmFtcyA9IHBhcmFtcywNCiAgICBkYXRhID0gZHRyYWluLA0KICAgIG5yb3VuZHMgPSAyMDAsICAgICAgICAgICAgICAgICAgICAjIE3DoXhpbW8gbsO6bWVybyBkZSBpdGVyYWNpb25lcw0KICAgIG5mb2xkID0gNSwgICAgICAgICAgICAgICAgICAgICAgICAjIDUtZm9sZCB2YWxpZGFjacOzbiBjcnV6YWRhDQogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMjAsICAgICAgICMgRGV0ZW5lciBzaSBubyBoYXkgbWVqb3JhIGVuIDIwIHJvbmRhcw0KICAgIHZlcmJvc2UgPSAwICAgICAgICAgICAgICAgICAgICAgICAjIFN1cHJpbWlyIG1lbnNhamVzDQogICkNCiAgDQogICMgRXh0cmFlciBtZWpvciBpdGVyYWNpw7NuIHkgc3UgUk1TRQ0KICBiZXN0X2l0ZXJhdGlvbiA8LSBjdl9tb2RlbCRiZXN0X2l0ZXJhdGlvbg0KICBiZXN0X3Jtc2UgPC0gbWluKGN2X21vZGVsJGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKQ0KICANCiAgY2F0KCIgIE1lam9yIGl0ZXJhY2nDs246IiwgYmVzdF9pdGVyYXRpb24sICJcbiIpDQogIGNhdCgiICBSTVNFIGVuIHZhbGlkYWNpw7NuIGNydXphZGE6IiwgYmVzdF9ybXNlLCAiXG5cbiIpDQogIA0KICAjIEd1YXJkYXIgcmVzdWx0YWRvcw0KICByZXN1bHRhZG9fYWN0dWFsIDwtIGRhdGEuZnJhbWUoDQogICAgZXRhID0gcGFyYW1zJGV0YSwNCiAgICBtYXhfZGVwdGggPSBwYXJhbXMkbWF4X2RlcHRoLA0KICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbXMkbWluX2NoaWxkX3dlaWdodCwNCiAgICBzdWJzYW1wbGUgPSBwYXJhbXMkc3Vic2FtcGxlLA0KICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbXMkY29sc2FtcGxlX2J5dHJlZSwNCiAgICBnYW1tYSA9IHBhcmFtcyRnYW1tYSwNCiAgICBucm91bmRzID0gYmVzdF9pdGVyYXRpb24sDQogICAgcm1zZV9jdiA9IGJlc3Rfcm1zZQ0KICApDQogIA0KICByZXN1bHRhZG9zIDwtIHJiaW5kKHJlc3VsdGFkb3MsIHJlc3VsdGFkb19hY3R1YWwpDQp9DQoNCiMgT3JkZW5hciByZXN1bHRhZG9zIHBvciBSTVNFIChkZSBtZW5vciBhIG1heW9yKQ0KcmVzdWx0YWRvcyA8LSByZXN1bHRhZG9zW29yZGVyKHJlc3VsdGFkb3Mkcm1zZV9jdiksIF0NCg0KIyBQYXNvIDU6IE1vc3RyYXIgcmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2gNCmNhdCgiUmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2ggb3JkZW5hZG9zIHBvciBSTVNFOlxuIikNCnByaW50KHJlc3VsdGFkb3MpDQoNCiMgVmlzdWFsaXphciByZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaA0KZ2dwbG90KHJlc3VsdGFkb3MsIGFlcyh4ID0gcmVvcmRlcihwYXN0ZSgiQ29tYiIsIDE6bnJvdyhyZXN1bHRhZG9zKSksIHJtc2VfY3YpLCB5ID0gcm1zZV9jdikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIC0gUHJvZHVjdG8gMzkyOTc4OCIsDQogICAgeCA9ICJDb21iaW5hY2nDs24gZGUgSGlwZXJwYXLDoW1ldHJvcyIsDQogICAgeSA9ICJSTVNFIGVuIFZhbGlkYWNpw7NuIENydXphZGEiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQojIFBhc28gNjogU2VsZWNjaW9uYXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcw0KbWVqb3Jlc19wYXJhbXMgPC0gbGlzdCgNCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLA0KICBldmFsX21ldHJpYyA9ICJybXNlIiwNCiAgZXRhID0gcmVzdWx0YWRvcyRldGFbMV0sDQogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3MkbWF4X2RlcHRoWzFdLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvcyRtaW5fY2hpbGRfd2VpZ2h0WzFdLA0KICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zJHN1YnNhbXBsZVsxXSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3MkY29sc2FtcGxlX2J5dHJlZVsxXSwNCiAgZ2FtbWEgPSByZXN1bHRhZG9zJGdhbW1hWzFdDQopDQoNCm1lam9yX25yb3VuZHMgPC0gcmVzdWx0YWRvcyRucm91bmRzWzFdDQoNCmNhdCgiXG5NZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MgZW5jb250cmFkb3M6XG4iKQ0KcHJpbnQobWVqb3Jlc19wYXJhbXMpDQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kcywgIlxuXG4iKQ0KDQojIFBhc28gNzogRW50cmVuYXIgZWwgbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zDQpjYXQoIkVudHJlbmFuZG8gbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zLi4uXG4iKQ0KDQptb2RlbG9fZmluYWwgPC0geGdiLnRyYWluKA0KICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtcywNCiAgZGF0YSA9IGR0cmFpbiwNCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsDQogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW4sIHRlc3QgPSBkdGVzdCksDQogIHZlcmJvc2UgPSAwDQopDQoNCiMgR1VBUkRBUiBFTCBNT0RFTE8gQ09OIE5PTUJSRSBFU1BFUkFETw0KbW9kZWxvX3hnYl8zOTI5Nzg4IDwtIG1vZGVsb19maW5hbA0KDQojIFBhc28gODogRXZhbHVhciBlbCBtb2RlbG8NCiMgUHJlZGljY2lvbmVzIGVuIGNvbmp1bnRvIGRlIHBydWViYQ0KcHJlZGljY2lvbmVzX3Rlc3QgPC0gcHJlZGljdChtb2RlbG9fZmluYWwsIGR0ZXN0KQ0KDQojIENhbGN1bGFyIG3DqXRyaWNhcw0KIyBSwrINCnIyX3Rlc3QgPC0gMSAtIHN1bSgodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpXjIpIC8gc3VtKCh0ZXN0X3kgLSBtZWFuKHRlc3RfeSkpXjIpDQoNCiMgUk1TRQ0Kcm1zZV90ZXN0IDwtIHNxcnQobWVhbigodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpXjIpKQ0KDQojIE1BUEUNCm1hcGVfdGVzdCA8LSBtZWFuKGFicygodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpIC8gcG1heCh0ZXN0X3ksIDAuMDEpKSkgKiAxMDANCg0KIyBNU0UNCm1zZV90ZXN0IDwtIG1lYW4oKHRlc3RfeSAtIHByZWRpY2Npb25lc190ZXN0KV4yKQ0KDQojIEFJQyBhcHJveGltYWRvDQpuX3Rlc3QgPC0gbGVuZ3RoKHRlc3RfeSkNCmsgPC0gbGVuZ3RoKG1lam9yZXNfcGFyYW1zKSArIDEgICMgKzEgcG9yIG7Dum1lcm8gZGUgaXRlcmFjaW9uZXMNCmFpY190ZXN0IDwtIG5fdGVzdCAqIGxvZyhtc2VfdGVzdCkgKyAyICogaw0KDQojIE1vc3RyYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGRlIHBydWViYQ0KY2F0KCJcbk3DqXRyaWNhcyBlbiBjb25qdW50byBkZSBwcnVlYmE6XG4iKQ0KY2F0KCJSwrIgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHIyX3Rlc3QsICJcbiIpDQpjYXQoIkFJQyBhcHJveGltYWRvIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBhaWNfdGVzdCwgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6Iiwgcm1zZV90ZXN0LCAiXG4iKQ0KY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX3Rlc3QsICJcbiIpDQpjYXQoIk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbXNlX3Rlc3QsICJcblxuIikNCg0KIyBQYXNvIDk6IFByZWRpY2Npb25lcyBlbiBlbCBjb25qdW50byBjb21wbGV0bw0KIyBIYWNlciBwcmVkaWNjaW9uZXMgZW4gdG9kbyBlbCBjb25qdW50byBkZSBkYXRvcyBwYXJhIGNvbXBhcmFjacOzbiBjb24gb3Ryb3MgbW9kZWxvcw0KeF9jb21wbGV0byA8LSBhcy5tYXRyaXgoZGF0b3NfbW9kZWxvWywgY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSkNCnByZWRpY2Npb25lc19jb21wbGV0byA8LSBwcmVkaWN0KG1vZGVsb19maW5hbCwgeF9jb21wbGV0bykNCg0KIyBDYWxjdWxhciBtw6l0cmljYXMgZW4gY29uanVudG8gY29tcGxldG8NCnIyX2NvbXBsZXRvIDwtIDEgLSBzdW0oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0byleMikgLyANCiAgc3VtKChkYXRvc19tb2RlbG8kVmVudGEgLSBtZWFuKGRhdG9zX21vZGVsbyRWZW50YSkpXjIpDQoNCnJtc2VfY29tcGxldG8gPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pXjIpKQ0KDQptYXBlX2NvbXBsZXRvIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pIC8gDQogICAgICAgICAgICAgICAgICAgICAgICBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMA0KDQptc2VfY29tcGxldG8gPC0gbWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvKV4yKQ0KDQpuX2NvbXBsZXRvIDwtIG5yb3coZGF0b3NfbW9kZWxvKQ0KYWljX2NvbXBsZXRvIDwtIG5fY29tcGxldG8gKiBsb2cobXNlX2NvbXBsZXRvKSArIDIgKiBrDQoNCiMgTW9zdHJhciBtw6l0cmljYXMgZW4gY29uanVudG8gY29tcGxldG8NCmNhdCgiTcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvOlxuIikNCmNhdCgiUsKyIGRlbCBtb2RlbG8gWEdCb29zdDoiLCByMl9jb21wbGV0bywgIlxuIikNCmNhdCgiQUlDIGFwcm94aW1hZG8gZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIGFpY19jb21wbGV0bywgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6Iiwgcm1zZV9jb21wbGV0bywgIlxuIikNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV9jb21wbGV0bywgIlxuIikNCmNhdCgiTVNFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtc2VfY29tcGxldG8sICJcblxuIikNCg0KIyBQYXNvIDEwOiBBbsOhbGlzaXMgZGUgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQojIENhbGN1bGFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KaW1wb3J0YW5jaWEgPC0geGdiLmltcG9ydGFuY2UoDQogIGZlYXR1cmVfbmFtZXMgPSBjb2xuYW1lcyhkYXRvc19tb2RlbG8pW2NvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0sDQogIG1vZGVsID0gbW9kZWxvX2ZpbmFsDQopDQoNCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCmNhdCgiSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzOlxuIikNCnByaW50KGltcG9ydGFuY2lhKQ0KDQojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KeGdiLnBsb3QuaW1wb3J0YW5jZShpbXBvcnRhbmNlX21hdHJpeCA9IGltcG9ydGFuY2lhLCANCiAgICAgICAgICAgICAgICAgICBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM5Mjk3ODggKFhHQm9vc3QpIikNCg0KIyBQYXNvIDExOiBWaXN1YWxpemFjaW9uZXMgcGFyYSBldmFsdWFjacOzbg0KIyBHcsOhZmljbyAxOiBWYWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljaG9zDQpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoDQogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwNCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8NCikNCg0KZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM5Mjk3ODggKFhHQm9vc3QpIiwNCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwNCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBHcsOhZmljbyAyOiBBbsOhbGlzaXMgZGUgcmVzaWR1b3MNCmVycm9yZXMgPC0gZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvDQoNCiMgSGlzdG9ncmFtYSBkZSBlcnJvcmVzDQpoaXN0KGVycm9yZXMsIA0KICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM5Mjk3ODggKFhHQm9vc3QpIiwNCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwNCiAgICAgY29sID0gInNreWJsdWUiLA0KICAgICBicmVha3MgPSAzMCkNCg0KIyBHcsOhZmljbyAzOiBFcnJvcmVzIHZzIFByZWRpY2Npb25lcw0KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8sIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzOTI5Nzg4IChYR0Jvb3N0KSIsDQogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwNCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFhHQm9vc3QgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgUjIgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIE1TRSA9IG51bWVyaWMoKSwNCiAgICBBSUMgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMzkyOTc4OCIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bw0KICBNb2RlbG8gPSAiWEdCb29zdCIsDQogIFIyID0gcjJfY29tcGxldG8sICAjIFVzYW1vcyBsYXMgbcOpdHJpY2FzIGRlbCBjb25qdW50byBjb21wbGV0bw0KICBSTVNFID0gcm1zZV9jb21wbGV0bywNCiAgTUFQRSA9IG1hcGVfY29tcGxldG8sDQogIE1TRSA9IG1zZV9jb21wbGV0bywNCiAgQUlDID0gYWljX2NvbXBsZXRvDQopKQ0KYGBgDQoNCiMjIFBST0RVQ1RPIDM5MDQxNTINCmBgYHtyfQ0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykNCmRhdG9zX21vZGVsbyA8LSBkYXRvc18zOTA0MTUyICU+JQ0KICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQ0KDQojIFBhc28gMjogRGl2aWRpciBsb3MgZGF0b3MgZW4gY29uanVudG9zIGRlIGVudHJlbmFtaWVudG8gKDgwJSkgeSBwcnVlYmEgKDIwJSkNCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkDQp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zX21vZGVsbyRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQ0KdHJhaW5fZGF0YSA8LSBkYXRvc19tb2RlbG9bdHJhaW5faW5kZXgsIF0NCnRlc3RfZGF0YSA8LSBkYXRvc19tb2RlbG9bLXRyYWluX2luZGV4LCBdDQoNCiMgUHJlcGFyYXIgbWF0cmljZXMgcGFyYSBYR0Jvb3N0DQp0cmFpbl94IDwtIGFzLm1hdHJpeCh0cmFpbl9kYXRhWywgY29sbmFtZXModHJhaW5fZGF0YSkgIT0gIlZlbnRhIl0pDQp0cmFpbl95IDwtIHRyYWluX2RhdGEkVmVudGENCg0KdGVzdF94IDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGFbLCBjb2xuYW1lcyh0ZXN0X2RhdGEpICE9ICJWZW50YSJdKQ0KdGVzdF95IDwtIHRlc3RfZGF0YSRWZW50YQ0KDQojIENyZWFyIERNYXRyaXggcGFyYSBYR0Jvb3N0DQpkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRyYWluX3gsIGxhYmVsID0gdHJhaW5feSkNCmR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0ZXN0X3gsIGxhYmVsID0gdGVzdF95KQ0KDQojIFBhc28gMzogRGVmaW5pciBsYSByZWppbGxhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBHcmlkIFNlYXJjaA0KcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgNCiAgZXRhID0gYygwLjAxLCAwLjA1LCAwLjEsIDAuMyksICAgICAgICAgICMgTGVhcm5pbmcgcmF0ZQ0KICBtYXhfZGVwdGggPSBjKDMsIDYsIDkpLCAgICAgICAgICAgICAgICAgIyBQcm9mdW5kaWRhZCBtw6F4aW1hDQogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMsIDUpLCAgICAgICAgICAjIFBlc28gbcOtbmltbyBkZSBub2RvIGhpam8NCiAgc3Vic2FtcGxlID0gYygwLjcsIDAuOSksICAgICAgICAgICAgICAgICMgUHJvcG9yY2nDs24gZGUgb2JzZXJ2YWNpb25lcw0KICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjcsIDAuOSksICAgICAgICAgIyBQcm9wb3JjacOzbiBkZSB2YXJpYWJsZXMNCiAgZ2FtbWEgPSBjKDAsIDAuMSwgMC4zKSAgICAgICAgICAgICAgICAgICMgUmVndWxhcml6YWNpw7NuIGdhbW1hDQopDQoNCiMgTW9zdHJhciBkaW1lbnNpb25lcyBkZSBsYSByZWppbGxhDQpjYXQoIkdyaWQgU2VhcmNoIHBhcmEgWEdCb29zdCAtIFByb2R1Y3RvIDM5MDQxNTJcbiIpDQpjYXQoIk7Dum1lcm8gdG90YWwgZGUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zOiIsIG5yb3cocGFyYW1fZ3JpZCksICJcblxuIikNCg0KIyBQYXJhIGVzdGUgZWplbXBsbywgbGltaXRhciBhIDEyIGNvbWJpbmFjaW9uZXMgcGFyYSBhaG9ycmFyIHRpZW1wbw0KIyBFbiB1biBlc2NlbmFyaW8gcmVhbCwgcG9kcsOtYXMgZXZhbHVhciB0b2RhcyBvIHVzYXIgdW5hIGVzdHJhdGVnaWEgbcOhcyBlZmljaWVudGUNCnNldC5zZWVkKDQ1NikNCmlmIChucm93KHBhcmFtX2dyaWQpID4gMTIpIHsNCiAgc2VsZWN0ZWRfaW5kaWNlcyA8LSBzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAxMikNCiAgcGFyYW1fZ3JpZCA8LSBwYXJhbV9ncmlkW3NlbGVjdGVkX2luZGljZXMsIF0NCiAgY2F0KCJTZWxlY2Npb25hbmRvIDEyIGNvbWJpbmFjaW9uZXMgYWxlYXRvcmlhcyBwYXJhIGV2YWx1YWNpw7NuLlxuXG4iKQ0KfQ0KDQojIFBhc28gNDogSW1wbGVtZW50YXIgR3JpZCBTZWFyY2gNCnJlc3VsdGFkb3MgPC0gZGF0YS5mcmFtZSgpDQoNCmNhdCgiSW5pY2lhbmRvIEdyaWQgU2VhcmNoLi4uXG4iKQ0KDQpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWQpKSB7DQogICMgRXh0cmFlciBwYXLDoW1ldHJvcyBkZSBsYSBjb21iaW5hY2nDs24gYWN0dWFsDQogIHBhcmFtcyA8LSBsaXN0KA0KICAgIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwgICAgICAjIE9iamV0aXZvIGRlIHJlZ3Jlc2nDs24NCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwgICAgICAgICAgICAgICAjIE3DqXRyaWNhIGRlIGV2YWx1YWNpw7NuDQogICAgZXRhID0gcGFyYW1fZ3JpZCRldGFbaV0sDQogICAgbWF4X2RlcHRoID0gcGFyYW1fZ3JpZCRtYXhfZGVwdGhbaV0sDQogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtX2dyaWQkbWluX2NoaWxkX3dlaWdodFtpXSwNCiAgICBzdWJzYW1wbGUgPSBwYXJhbV9ncmlkJHN1YnNhbXBsZVtpXSwNCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gcGFyYW1fZ3JpZCRjb2xzYW1wbGVfYnl0cmVlW2ldLA0KICAgIGdhbW1hID0gcGFyYW1fZ3JpZCRnYW1tYVtpXQ0KICApDQogIA0KICBjYXQoIkV2YWx1YW5kbyBjb21iaW5hY2nDs24iLCBpLCAiZGUiLCBucm93KHBhcmFtX2dyaWQpLCAiOlxuIikNCiAgY2F0KCIgIGV0YSA9IiwgcGFyYW1zJGV0YSwgDQogICAgICAiLCBtYXhfZGVwdGggPSIsIHBhcmFtcyRtYXhfZGVwdGgsIA0KICAgICAgIiwgbWluX2NoaWxkX3dlaWdodCA9IiwgcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsIA0KICAgICAgIiwgc3Vic2FtcGxlID0iLCBwYXJhbXMkc3Vic2FtcGxlLCANCiAgICAgICIsIGNvbHNhbXBsZV9ieXRyZWUgPSIsIHBhcmFtcyRjb2xzYW1wbGVfYnl0cmVlLA0KICAgICAgIiwgZ2FtbWEgPSIsIHBhcmFtcyRnYW1tYSwgIlxuIikNCiAgDQogICMgVmFsaWRhY2nDs24gY3J1emFkYSBwYXJhIGVuY29udHJhciBlbCBuw7ptZXJvIMOzcHRpbW8gZGUgaXRlcmFjaW9uZXMNCiAgY3ZfbW9kZWwgPC0geGdiLmN2KA0KICAgIHBhcmFtcyA9IHBhcmFtcywNCiAgICBkYXRhID0gZHRyYWluLA0KICAgIG5yb3VuZHMgPSAyMDAsICAgICAgICAgICAgICAgICAgICAjIE3DoXhpbW8gbsO6bWVybyBkZSBpdGVyYWNpb25lcw0KICAgIG5mb2xkID0gNSwgICAgICAgICAgICAgICAgICAgICAgICAjIDUtZm9sZCB2YWxpZGFjacOzbiBjcnV6YWRhDQogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMjAsICAgICAgICMgRGV0ZW5lciBzaSBubyBoYXkgbWVqb3JhIGVuIDIwIHJvbmRhcw0KICAgIHZlcmJvc2UgPSAwICAgICAgICAgICAgICAgICAgICAgICAjIFN1cHJpbWlyIG1lbnNhamVzDQogICkNCiAgDQogICMgRXh0cmFlciBtZWpvciBpdGVyYWNpw7NuIHkgc3UgUk1TRQ0KICBiZXN0X2l0ZXJhdGlvbiA8LSBjdl9tb2RlbCRiZXN0X2l0ZXJhdGlvbg0KICBiZXN0X3Jtc2UgPC0gbWluKGN2X21vZGVsJGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKQ0KICANCiAgY2F0KCIgIE1lam9yIGl0ZXJhY2nDs246IiwgYmVzdF9pdGVyYXRpb24sICJcbiIpDQogIGNhdCgiICBSTVNFIGVuIHZhbGlkYWNpw7NuIGNydXphZGE6IiwgYmVzdF9ybXNlLCAiXG5cbiIpDQogIA0KICAjIEd1YXJkYXIgcmVzdWx0YWRvcw0KICByZXN1bHRhZG9fYWN0dWFsIDwtIGRhdGEuZnJhbWUoDQogICAgZXRhID0gcGFyYW1zJGV0YSwNCiAgICBtYXhfZGVwdGggPSBwYXJhbXMkbWF4X2RlcHRoLA0KICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbXMkbWluX2NoaWxkX3dlaWdodCwNCiAgICBzdWJzYW1wbGUgPSBwYXJhbXMkc3Vic2FtcGxlLA0KICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbXMkY29sc2FtcGxlX2J5dHJlZSwNCiAgICBnYW1tYSA9IHBhcmFtcyRnYW1tYSwNCiAgICBucm91bmRzID0gYmVzdF9pdGVyYXRpb24sDQogICAgcm1zZV9jdiA9IGJlc3Rfcm1zZQ0KICApDQogIA0KICByZXN1bHRhZG9zIDwtIHJiaW5kKHJlc3VsdGFkb3MsIHJlc3VsdGFkb19hY3R1YWwpDQp9DQoNCiMgT3JkZW5hciByZXN1bHRhZG9zIHBvciBSTVNFIChkZSBtZW5vciBhIG1heW9yKQ0KcmVzdWx0YWRvcyA8LSByZXN1bHRhZG9zW29yZGVyKHJlc3VsdGFkb3Mkcm1zZV9jdiksIF0NCg0KIyBQYXNvIDU6IE1vc3RyYXIgcmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2gNCmNhdCgiUmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2ggb3JkZW5hZG9zIHBvciBSTVNFOlxuIikNCnByaW50KHJlc3VsdGFkb3MpDQoNCiMgVmlzdWFsaXphciByZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaA0KZ2dwbG90KHJlc3VsdGFkb3MsIGFlcyh4ID0gcmVvcmRlcihwYXN0ZSgiQ29tYiIsIDE6bnJvdyhyZXN1bHRhZG9zKSksIHJtc2VfY3YpLCB5ID0gcm1zZV9jdikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIC0gUHJvZHVjdG8gMzkwNDE1MiIsDQogICAgeCA9ICJDb21iaW5hY2nDs24gZGUgSGlwZXJwYXLDoW1ldHJvcyIsDQogICAgeSA9ICJSTVNFIGVuIFZhbGlkYWNpw7NuIENydXphZGEiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQ0KDQojIFBhc28gNjogU2VsZWNjaW9uYXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcw0KbWVqb3Jlc19wYXJhbXMgPC0gbGlzdCgNCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLA0KICBldmFsX21ldHJpYyA9ICJybXNlIiwNCiAgZXRhID0gcmVzdWx0YWRvcyRldGFbMV0sDQogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3MkbWF4X2RlcHRoWzFdLA0KICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvcyRtaW5fY2hpbGRfd2VpZ2h0WzFdLA0KICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zJHN1YnNhbXBsZVsxXSwNCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3MkY29sc2FtcGxlX2J5dHJlZVsxXSwNCiAgZ2FtbWEgPSByZXN1bHRhZG9zJGdhbW1hWzFdDQopDQoNCm1lam9yX25yb3VuZHMgPC0gcmVzdWx0YWRvcyRucm91bmRzWzFdDQoNCmNhdCgiXG5NZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MgZW5jb250cmFkb3M6XG4iKQ0KcHJpbnQobWVqb3Jlc19wYXJhbXMpDQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kcywgIlxuXG4iKQ0KDQojIFBhc28gNzogRW50cmVuYXIgZWwgbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zDQpjYXQoIkVudHJlbmFuZG8gbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zLi4uXG4iKQ0KDQptb2RlbG9fZmluYWwgPC0geGdiLnRyYWluKA0KICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtcywNCiAgZGF0YSA9IGR0cmFpbiwNCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsDQogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW4sIHRlc3QgPSBkdGVzdCksDQogIHZlcmJvc2UgPSAwDQopDQoNCm1vZGVsb194Z2JfMzkwNDE1MiA8LSBtb2RlbG9fZmluYWwNCg0KDQojIFBhc28gODogRXZhbHVhciBlbCBtb2RlbG8NCiMgUHJlZGljY2lvbmVzIGVuIGNvbmp1bnRvIGRlIHBydWViYQ0KcHJlZGljY2lvbmVzX3Rlc3QgPC0gcHJlZGljdChtb2RlbG9fZmluYWwsIGR0ZXN0KQ0KDQojIENhbGN1bGFyIG3DqXRyaWNhcw0KIyBSwrINCnIyX3Rlc3QgPC0gMSAtIHN1bSgodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpXjIpIC8gc3VtKCh0ZXN0X3kgLSBtZWFuKHRlc3RfeSkpXjIpDQoNCiMgUk1TRQ0Kcm1zZV90ZXN0IDwtIHNxcnQobWVhbigodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpXjIpKQ0KDQojIE1BUEUNCm1hcGVfdGVzdCA8LSBtZWFuKGFicygodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpIC8gcG1heCh0ZXN0X3ksIDAuMDEpKSkgKiAxMDANCg0KIyBNU0UNCm1zZV90ZXN0IDwtIG1lYW4oKHRlc3RfeSAtIHByZWRpY2Npb25lc190ZXN0KV4yKQ0KDQojIEFJQyBhcHJveGltYWRvDQpuX3Rlc3QgPC0gbGVuZ3RoKHRlc3RfeSkNCmsgPC0gbGVuZ3RoKG1lam9yZXNfcGFyYW1zKSArIDEgICMgKzEgcG9yIG7Dum1lcm8gZGUgaXRlcmFjaW9uZXMNCmFpY190ZXN0IDwtIG5fdGVzdCAqIGxvZyhtc2VfdGVzdCkgKyAyICogaw0KDQojIE1vc3RyYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGRlIHBydWViYQ0KY2F0KCJcbk3DqXRyaWNhcyBlbiBjb25qdW50byBkZSBwcnVlYmE6XG4iKQ0KY2F0KCJSwrIgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHIyX3Rlc3QsICJcbiIpDQpjYXQoIkFJQyBhcHJveGltYWRvIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBhaWNfdGVzdCwgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6Iiwgcm1zZV90ZXN0LCAiXG4iKQ0KY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX3Rlc3QsICJcbiIpDQpjYXQoIk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbXNlX3Rlc3QsICJcblxuIikNCg0KIyBQYXNvIDk6IFByZWRpY2Npb25lcyBlbiBlbCBjb25qdW50byBjb21wbGV0bw0KIyBIYWNlciBwcmVkaWNjaW9uZXMgZW4gdG9kbyBlbCBjb25qdW50byBkZSBkYXRvcyBwYXJhIGNvbXBhcmFjacOzbiBjb24gb3Ryb3MgbW9kZWxvcw0KeF9jb21wbGV0byA8LSBhcy5tYXRyaXgoZGF0b3NfbW9kZWxvWywgY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSkNCnByZWRpY2Npb25lc19jb21wbGV0byA8LSBwcmVkaWN0KG1vZGVsb19maW5hbCwgeF9jb21wbGV0bykNCg0KIyBDYWxjdWxhciBtw6l0cmljYXMgZW4gY29uanVudG8gY29tcGxldG8NCnIyX2NvbXBsZXRvIDwtIDEgLSBzdW0oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0byleMikgLyANCiAgc3VtKChkYXRvc19tb2RlbG8kVmVudGEgLSBtZWFuKGRhdG9zX21vZGVsbyRWZW50YSkpXjIpDQoNCnJtc2VfY29tcGxldG8gPC0gc3FydChtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pXjIpKQ0KDQptYXBlX2NvbXBsZXRvIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pIC8gDQogICAgICAgICAgICAgICAgICAgICAgICBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMA0KDQptc2VfY29tcGxldG8gPC0gbWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvKV4yKQ0KDQpuX2NvbXBsZXRvIDwtIG5yb3coZGF0b3NfbW9kZWxvKQ0KYWljX2NvbXBsZXRvIDwtIG5fY29tcGxldG8gKiBsb2cobXNlX2NvbXBsZXRvKSArIDIgKiBrDQoNCiMgTW9zdHJhciBtw6l0cmljYXMgZW4gY29uanVudG8gY29tcGxldG8NCmNhdCgiTcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvOlxuIikNCmNhdCgiUsKyIGRlbCBtb2RlbG8gWEdCb29zdDoiLCByMl9jb21wbGV0bywgIlxuIikNCmNhdCgiQUlDIGFwcm94aW1hZG8gZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIGFpY19jb21wbGV0bywgIlxuIikNCmNhdCgiUk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6Iiwgcm1zZV9jb21wbGV0bywgIlxuIikNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV9jb21wbGV0bywgIlxuIikNCmNhdCgiTVNFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtc2VfY29tcGxldG8sICJcblxuIikNCg0KIyBQYXNvIDEwOiBBbsOhbGlzaXMgZGUgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQojIENhbGN1bGFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KaW1wb3J0YW5jaWEgPC0geGdiLmltcG9ydGFuY2UoDQogIGZlYXR1cmVfbmFtZXMgPSBjb2xuYW1lcyhkYXRvc19tb2RlbG8pW2NvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0sDQogIG1vZGVsID0gbW9kZWxvX2ZpbmFsDQopDQoNCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCmNhdCgiSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzOlxuIikNCnByaW50KGltcG9ydGFuY2lhKQ0KDQojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KeGdiLnBsb3QuaW1wb3J0YW5jZShpbXBvcnRhbmNlX21hdHJpeCA9IGltcG9ydGFuY2lhLCANCiAgICAgICAgICAgICAgICAgICBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM5MDQxNTIgKFhHQm9vc3QpIikNCg0KIyBQYXNvIDExOiBWaXN1YWxpemFjaW9uZXMgcGFyYSBldmFsdWFjacOzbg0KIyBHcsOhZmljbyAxOiBWYWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljaG9zDQpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoDQogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwNCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8NCikNCg0KZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM5MDQxNTIgKFhHQm9vc3QpIiwNCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwNCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBHcsOhZmljbyAyOiBBbsOhbGlzaXMgZGUgcmVzaWR1b3MNCmVycm9yZXMgPC0gZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvDQoNCiMgSGlzdG9ncmFtYSBkZSBlcnJvcmVzDQpoaXN0KGVycm9yZXMsIA0KICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDM5MDQxNTIgKFhHQm9vc3QpIiwNCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwNCiAgICAgY29sID0gInNreWJsdWUiLA0KICAgICBicmVha3MgPSAzMCkNCg0KIyBHcsOhZmljbyAzOiBFcnJvcmVzIHZzIFByZWRpY2Npb25lcw0KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8sIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzOTA0MTUyIChYR0Jvb3N0KSIsDQogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwNCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KDQpgYGB7cn0NCiMgR3VhcmRhciBtw6l0cmljYXMgZGUgWEdCb29zdCBwYXJhIHByb2R1Y3RvIA0KaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsNCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoDQogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwNCiAgICBSMiA9IG51bWVyaWMoKSwNCiAgICBSTVNFID0gbnVtZXJpYygpLA0KICAgIE1BUEUgPSBudW1lcmljKCksDQogICAgTVNFID0gbnVtZXJpYygpLA0KICAgIEFJQyA9IG51bWVyaWMoKSwNCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCiAgKQ0KfQ0KDQptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9ICIzOTA0MTUyIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvDQogIE1vZGVsbyA9ICJYR0Jvb3N0IiwNCiAgUjIgPSByMl9jb21wbGV0bywgICMgVXNhbW9zIGxhcyBtw6l0cmljYXMgZGVsIGNvbmp1bnRvIGNvbXBsZXRvDQogIFJNU0UgPSBybXNlX2NvbXBsZXRvLA0KICBNQVBFID0gbWFwZV9jb21wbGV0bywNCiAgTVNFID0gbXNlX2NvbXBsZXRvLA0KICBBSUMgPSBhaWNfY29tcGxldG8NCikpDQpgYGANCg0KIyMgUFJPRFVDVE8gMTU1MDAyDQpgYGB7cn0NCiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpDQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMTU1MDAyICU+JQ0KICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQ0KDQojIFBhc28gMjogRGl2aWRpciBsb3MgZGF0b3MgZW4gY29uanVudG9zIGRlIGVudHJlbmFtaWVudG8gKDgwJSkgeSBwcnVlYmEgKDIwJSkNCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkDQp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zX21vZGVsbyRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQ0KdHJhaW5fZGF0YSA8LSBkYXRvc19tb2RlbG9bdHJhaW5faW5kZXgsIF0NCnRlc3RfZGF0YSA8LSBkYXRvc19tb2RlbG9bLXRyYWluX2luZGV4LCBdDQoNCiMgUHJlcGFyYXIgbWF0cmljZXMgcGFyYSBYR0Jvb3N0DQp0cmFpbl94IDwtIGFzLm1hdHJpeCh0cmFpbl9kYXRhWywgY29sbmFtZXModHJhaW5fZGF0YSkgIT0gIlZlbnRhIl0pDQp0cmFpbl95IDwtIHRyYWluX2RhdGEkVmVudGENCg0KdGVzdF94IDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGFbLCBjb2xuYW1lcyh0ZXN0X2RhdGEpICE9ICJWZW50YSJdKQ0KdGVzdF95IDwtIHRlc3RfZGF0YSRWZW50YQ0KDQojIENyZWFyIERNYXRyaXggcGFyYSBYR0Jvb3N0DQpkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRyYWluX3gsIGxhYmVsID0gdHJhaW5feSkNCmR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0ZXN0X3gsIGxhYmVsID0gdGVzdF95KQ0KDQojIFBhc28gMzogRGVmaW5pciBsYSByZWppbGxhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBHcmlkIFNlYXJjaA0KcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgNCiAgZXRhID0gYygwLjAxLCAwLjA1LCAwLjEsIDAuMyksICAgICAgICAgICMgTGVhcm5pbmcgcmF0ZQ0KICBtYXhfZGVwdGggPSBjKDMsIDYsIDkpLCAgICAgICAgICAgICAgICAgIyBQcm9mdW5kaWRhZCBtw6F4aW1hDQogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMsIDUpLCAgICAgICAgICAjIFBlc28gbcOtbmltbyBkZSBub2RvIGhpam8NCiAgc3Vic2FtcGxlID0gYygwLjcsIDAuOSksICAgICAgICAgICAgICAgICMgUHJvcG9yY2nDs24gZGUgb2JzZXJ2YWNpb25lcw0KICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjcsIDAuOSksICAgICAgICAgIyBQcm9wb3JjacOzbiBkZSB2YXJpYWJsZXMNCiAgZ2FtbWEgPSBjKDAsIDAuMSwgMC4zKSAgICAgICAgICAgICAgICAgICMgUmVndWxhcml6YWNpw7NuIGdhbW1hDQopDQoNCiMgTW9zdHJhciBkaW1lbnNpb25lcyBkZSBsYSByZWppbGxhDQpjYXQoIkdyaWQgU2VhcmNoIHBhcmEgWEdCb29zdCAtIFByb2R1Y3RvIDE1NTAwMlxuIikNCmNhdCgiTsO6bWVybyB0b3RhbCBkZSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3M6IiwgbnJvdyhwYXJhbV9ncmlkKSwgIlxuXG4iKQ0KDQojIFBhcmEgZXN0ZSBlamVtcGxvLCBsaW1pdGFyIGEgMTIgY29tYmluYWNpb25lcyBwYXJhIGFob3JyYXIgdGllbXBvDQojIEVuIHVuIGVzY2VuYXJpbyByZWFsLCBwb2Ryw61hcyBldmFsdWFyIHRvZGFzIG8gdXNhciB1bmEgZXN0cmF0ZWdpYSBtw6FzIGVmaWNpZW50ZQ0Kc2V0LnNlZWQoNDU2KQ0KaWYgKG5yb3cocGFyYW1fZ3JpZCkgPiAxMikgew0KICBzZWxlY3RlZF9pbmRpY2VzIDwtIHNhbXBsZSgxOm5yb3cocGFyYW1fZ3JpZCksIDEyKQ0KICBwYXJhbV9ncmlkIDwtIHBhcmFtX2dyaWRbc2VsZWN0ZWRfaW5kaWNlcywgXQ0KICBjYXQoIlNlbGVjY2lvbmFuZG8gMTIgY29tYmluYWNpb25lcyBhbGVhdG9yaWFzIHBhcmEgZXZhbHVhY2nDs24uXG5cbiIpDQp9DQoNCiMgUGFzbyA0OiBJbXBsZW1lbnRhciBHcmlkIFNlYXJjaA0KcmVzdWx0YWRvcyA8LSBkYXRhLmZyYW1lKCkNCg0KY2F0KCJJbmljaWFuZG8gR3JpZCBTZWFyY2guLi5cbiIpDQoNCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZCkpIHsNCiAgIyBFeHRyYWVyIHBhcsOhbWV0cm9zIGRlIGxhIGNvbWJpbmFjacOzbiBhY3R1YWwNCiAgcGFyYW1zIDwtIGxpc3QoDQogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLCAgICAgICMgT2JqZXRpdm8gZGUgcmVncmVzacOzbg0KICAgIGV2YWxfbWV0cmljID0gInJtc2UiLCAgICAgICAgICAgICAgICMgTcOpdHJpY2EgZGUgZXZhbHVhY2nDs24NCiAgICBldGEgPSBwYXJhbV9ncmlkJGV0YVtpXSwNCiAgICBtYXhfZGVwdGggPSBwYXJhbV9ncmlkJG1heF9kZXB0aFtpXSwNCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1fZ3JpZCRtaW5fY2hpbGRfd2VpZ2h0W2ldLA0KICAgIHN1YnNhbXBsZSA9IHBhcmFtX2dyaWQkc3Vic2FtcGxlW2ldLA0KICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbV9ncmlkJGNvbHNhbXBsZV9ieXRyZWVbaV0sDQogICAgZ2FtbWEgPSBwYXJhbV9ncmlkJGdhbW1hW2ldDQogICkNCiAgDQogIGNhdCgiRXZhbHVhbmRvIGNvbWJpbmFjacOzbiIsIGksICJkZSIsIG5yb3cocGFyYW1fZ3JpZCksICI6XG4iKQ0KICBjYXQoIiAgZXRhID0iLCBwYXJhbXMkZXRhLCANCiAgICAgICIsIG1heF9kZXB0aCA9IiwgcGFyYW1zJG1heF9kZXB0aCwgDQogICAgICAiLCBtaW5fY2hpbGRfd2VpZ2h0ID0iLCBwYXJhbXMkbWluX2NoaWxkX3dlaWdodCwgDQogICAgICAiLCBzdWJzYW1wbGUgPSIsIHBhcmFtcyRzdWJzYW1wbGUsIA0KICAgICAgIiwgY29sc2FtcGxlX2J5dHJlZSA9IiwgcGFyYW1zJGNvbHNhbXBsZV9ieXRyZWUsDQogICAgICAiLCBnYW1tYSA9IiwgcGFyYW1zJGdhbW1hLCAiXG4iKQ0KICANCiAgIyBWYWxpZGFjacOzbiBjcnV6YWRhIHBhcmEgZW5jb250cmFyIGVsIG7Dum1lcm8gw7NwdGltbyBkZSBpdGVyYWNpb25lcw0KICBjdl9tb2RlbCA8LSB4Z2IuY3YoDQogICAgcGFyYW1zID0gcGFyYW1zLA0KICAgIGRhdGEgPSBkdHJhaW4sDQogICAgbnJvdW5kcyA9IDIwMCwgICAgICAgICAgICAgICAgICAgICMgTcOheGltbyBuw7ptZXJvIGRlIGl0ZXJhY2lvbmVzDQogICAgbmZvbGQgPSA1LCAgICAgICAgICAgICAgICAgICAgICAgICMgNS1mb2xkIHZhbGlkYWNpw7NuIGNydXphZGENCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAyMCwgICAgICAgIyBEZXRlbmVyIHNpIG5vIGhheSBtZWpvcmEgZW4gMjAgcm9uZGFzDQogICAgdmVyYm9zZSA9IDAgICAgICAgICAgICAgICAgICAgICAgICMgU3VwcmltaXIgbWVuc2FqZXMNCiAgKQ0KICANCiAgIyBFeHRyYWVyIG1lam9yIGl0ZXJhY2nDs24geSBzdSBSTVNFDQogIGJlc3RfaXRlcmF0aW9uIDwtIGN2X21vZGVsJGJlc3RfaXRlcmF0aW9uDQogIGJlc3Rfcm1zZSA8LSBtaW4oY3ZfbW9kZWwkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pDQogIA0KICBjYXQoIiAgTWVqb3IgaXRlcmFjacOzbjoiLCBiZXN0X2l0ZXJhdGlvbiwgIlxuIikNCiAgY2F0KCIgIFJNU0UgZW4gdmFsaWRhY2nDs24gY3J1emFkYToiLCBiZXN0X3Jtc2UsICJcblxuIikNCiAgDQogICMgR3VhcmRhciByZXN1bHRhZG9zDQogIHJlc3VsdGFkb19hY3R1YWwgPC0gZGF0YS5mcmFtZSgNCiAgICBldGEgPSBwYXJhbXMkZXRhLA0KICAgIG1heF9kZXB0aCA9IHBhcmFtcyRtYXhfZGVwdGgsDQogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtcyRtaW5fY2hpbGRfd2VpZ2h0LA0KICAgIHN1YnNhbXBsZSA9IHBhcmFtcyRzdWJzYW1wbGUsDQogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtcyRjb2xzYW1wbGVfYnl0cmVlLA0KICAgIGdhbW1hID0gcGFyYW1zJGdhbW1hLA0KICAgIG5yb3VuZHMgPSBiZXN0X2l0ZXJhdGlvbiwNCiAgICBybXNlX2N2ID0gYmVzdF9ybXNlDQogICkNCiAgDQogIHJlc3VsdGFkb3MgPC0gcmJpbmQocmVzdWx0YWRvcywgcmVzdWx0YWRvX2FjdHVhbCkNCn0NCg0KIyBPcmRlbmFyIHJlc3VsdGFkb3MgcG9yIFJNU0UgKGRlIG1lbm9yIGEgbWF5b3IpDQpyZXN1bHRhZG9zIDwtIHJlc3VsdGFkb3Nbb3JkZXIocmVzdWx0YWRvcyRybXNlX2N2KSwgXQ0KDQojIFBhc28gNTogTW9zdHJhciByZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaA0KY2F0KCJSZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaCBvcmRlbmFkb3MgcG9yIFJNU0U6XG4iKQ0KcHJpbnQocmVzdWx0YWRvcykNCg0KIyBWaXN1YWxpemFyIHJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoDQpnZ3Bsb3QocmVzdWx0YWRvcywgYWVzKHggPSByZW9yZGVyKHBhc3RlKCJDb21iIiwgMTpucm93KHJlc3VsdGFkb3MpKSwgcm1zZV9jdiksIHkgPSBybXNlX2N2KSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJzdGVlbGJsdWUiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiUmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2ggLSBQcm9kdWN0byAxNTUwMDIiLA0KICAgIHggPSAiQ29tYmluYWNpw7NuIGRlIEhpcGVycGFyw6FtZXRyb3MiLA0KICAgIHkgPSAiUk1TRSBlbiBWYWxpZGFjacOzbiBDcnV6YWRhIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCg0KIyBQYXNvIDY6IFNlbGVjY2lvbmFyIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MNCm1lam9yZXNfcGFyYW1zIDwtIGxpc3QoDQogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwNCiAgZXZhbF9tZXRyaWMgPSAicm1zZSIsDQogIGV0YSA9IHJlc3VsdGFkb3MkZXRhWzFdLA0KICBtYXhfZGVwdGggPSByZXN1bHRhZG9zJG1heF9kZXB0aFsxXSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IHJlc3VsdGFkb3MkbWluX2NoaWxkX3dlaWdodFsxXSwNCiAgc3Vic2FtcGxlID0gcmVzdWx0YWRvcyRzdWJzYW1wbGVbMV0sDQogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zJGNvbHNhbXBsZV9ieXRyZWVbMV0sDQogIGdhbW1hID0gcmVzdWx0YWRvcyRnYW1tYVsxXQ0KKQ0KDQptZWpvcl9ucm91bmRzIDwtIHJlc3VsdGFkb3MkbnJvdW5kc1sxXQ0KDQpjYXQoIlxuTWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zIGVuY29udHJhZG9zOlxuIikNCnByaW50KG1lam9yZXNfcGFyYW1zKQ0KY2F0KCJOw7ptZXJvIMOzcHRpbW8gZGUgcm9uZGFzOiIsIG1lam9yX25yb3VuZHMsICJcblxuIikNCg0KIyBQYXNvIDc6IEVudHJlbmFyIGVsIG1vZGVsbyBmaW5hbCBjb24gbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcw0KY2F0KCJFbnRyZW5hbmRvIG1vZGVsbyBmaW5hbCBjb24gbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcy4uLlxuIikNCg0KbW9kZWxvX2ZpbmFsIDwtIHhnYi50cmFpbigNCiAgcGFyYW1zID0gbWVqb3Jlc19wYXJhbXMsDQogIGRhdGEgPSBkdHJhaW4sDQogIG5yb3VuZHMgPSBtZWpvcl9ucm91bmRzLA0KICB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gZHRyYWluLCB0ZXN0ID0gZHRlc3QpLA0KICB2ZXJib3NlID0gMA0KKQ0KDQptb2RlbG9feGdiXzE1NTAwMiA8LSBtb2RlbG9fZmluYWwNCg0KIyBQYXNvIDg6IEV2YWx1YXIgZWwgbW9kZWxvDQojIFByZWRpY2Npb25lcyBlbiBjb25qdW50byBkZSBwcnVlYmENCnByZWRpY2Npb25lc190ZXN0IDwtIHByZWRpY3QobW9kZWxvX2ZpbmFsLCBkdGVzdCkNCg0KIyBDYWxjdWxhciBtw6l0cmljYXMNCiMgUsKyDQpyMl90ZXN0IDwtIDEgLSBzdW0oKHRlc3RfeSAtIHByZWRpY2Npb25lc190ZXN0KV4yKSAvIHN1bSgodGVzdF95IC0gbWVhbih0ZXN0X3kpKV4yKQ0KDQojIFJNU0UNCnJtc2VfdGVzdCA8LSBzcXJ0KG1lYW4oKHRlc3RfeSAtIHByZWRpY2Npb25lc190ZXN0KV4yKSkNCg0KIyBNQVBFDQptYXBlX3Rlc3QgPC0gbWVhbihhYnMoKHRlc3RfeSAtIHByZWRpY2Npb25lc190ZXN0KSAvIHBtYXgodGVzdF95LCAwLjAxKSkpICogMTAwDQoNCiMgTVNFDQptc2VfdGVzdCA8LSBtZWFuKCh0ZXN0X3kgLSBwcmVkaWNjaW9uZXNfdGVzdCleMikNCg0KIyBBSUMgYXByb3hpbWFkbw0Kbl90ZXN0IDwtIGxlbmd0aCh0ZXN0X3kpDQprIDwtIGxlbmd0aChtZWpvcmVzX3BhcmFtcykgKyAxICAjICsxIHBvciBuw7ptZXJvIGRlIGl0ZXJhY2lvbmVzDQphaWNfdGVzdCA8LSBuX3Rlc3QgKiBsb2cobXNlX3Rlc3QpICsgMiAqIGsNCg0KIyBNb3N0cmFyIG3DqXRyaWNhcyBlbiBjb25qdW50byBkZSBwcnVlYmENCmNhdCgiXG5Nw6l0cmljYXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhOlxuIikNCmNhdCgiUsKyIGRlbCBtb2RlbG8gWEdCb29zdDoiLCByMl90ZXN0LCAiXG4iKQ0KY2F0KCJBSUMgYXByb3hpbWFkbyBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgYWljX3Rlc3QsICJcbiIpDQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHJtc2VfdGVzdCwgIlxuIikNCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV90ZXN0LCAiXG4iKQ0KY2F0KCJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIG1zZV90ZXN0LCAiXG5cbiIpDQoNCiMgUGFzbyA5OiBQcmVkaWNjaW9uZXMgZW4gZWwgY29uanVudG8gY29tcGxldG8NCiMgSGFjZXIgcHJlZGljY2lvbmVzIGVuIHRvZG8gZWwgY29uanVudG8gZGUgZGF0b3MgcGFyYSBjb21wYXJhY2nDs24gY29uIG90cm9zIG1vZGVsb3MNCnhfY29tcGxldG8gPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb1ssIGNvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0pDQpwcmVkaWNjaW9uZXNfY29tcGxldG8gPC0gcHJlZGljdChtb2RlbG9fZmluYWwsIHhfY29tcGxldG8pDQoNCiMgQ2FsY3VsYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvDQpyMl9jb21wbGV0byA8LSAxIC0gc3VtKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pXjIpIC8gDQogIHN1bSgoZGF0b3NfbW9kZWxvJFZlbnRhIC0gbWVhbihkYXRvc19tb2RlbG8kVmVudGEpKV4yKQ0KDQpybXNlX2NvbXBsZXRvIDwtIHNxcnQobWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvKV4yKSkNCg0KbWFwZV9jb21wbGV0byA8LSBtZWFuKGFicygoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvKSAvIA0KICAgICAgICAgICAgICAgICAgICAgICAgcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDANCg0KbXNlX2NvbXBsZXRvIDwtIG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0byleMikNCg0Kbl9jb21wbGV0byA8LSBucm93KGRhdG9zX21vZGVsbykNCmFpY19jb21wbGV0byA8LSBuX2NvbXBsZXRvICogbG9nKG1zZV9jb21wbGV0bykgKyAyICogaw0KDQojIE1vc3RyYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvDQpjYXQoIk3DqXRyaWNhcyBlbiBjb25qdW50byBjb21wbGV0bzpcbiIpDQpjYXQoIlLCsiBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgcjJfY29tcGxldG8sICJcbiIpDQpjYXQoIkFJQyBhcHJveGltYWRvIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBhaWNfY29tcGxldG8sICJcbiIpDQpjYXQoIlJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIHJtc2VfY29tcGxldG8sICJcbiIpDQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIG1hcGVfY29tcGxldG8sICJcbiIpDQpjYXQoIk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbXNlX2NvbXBsZXRvLCAiXG5cbiIpDQoNCiMgUGFzbyAxMDogQW7DoWxpc2lzIGRlIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcw0KIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCmltcG9ydGFuY2lhIDwtIHhnYi5pbXBvcnRhbmNlKA0KICBmZWF0dXJlX25hbWVzID0gY29sbmFtZXMoZGF0b3NfbW9kZWxvKVtjb2xuYW1lcyhkYXRvc19tb2RlbG8pICE9ICJWZW50YSJdLA0KICBtb2RlbCA9IG1vZGVsb19maW5hbA0KKQ0KDQojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQpjYXQoIkltcG9ydGFuY2lhIGRlIHZhcmlhYmxlczpcbiIpDQpwcmludChpbXBvcnRhbmNpYSkNCg0KIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMNCnhnYi5wbG90LmltcG9ydGFuY2UoaW1wb3J0YW5jZV9tYXRyaXggPSBpbXBvcnRhbmNpYSwgDQogICAgICAgICAgICAgICAgICAgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDIgKFhHQm9vc3QpIikNCg0KIyBQYXNvIDExOiBWaXN1YWxpemFjaW9uZXMgcGFyYSBldmFsdWFjacOzbg0KIyBHcsOhZmljbyAxOiBWYWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljaG9zDQpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoDQogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwNCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8NCikNCg0KZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDE1NTAwMiAoWEdCb29zdCkiLA0KICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLA0KICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQojIEdyw6FmaWNvIDI6IEFuw6FsaXNpcyBkZSByZXNpZHVvcw0KZXJyb3JlcyA8LSBkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8NCg0KIyBIaXN0b2dyYW1hIGRlIGVycm9yZXMNCmhpc3QoZXJyb3JlcywgDQogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMTU1MDAyIChYR0Jvb3N0KSIsDQogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsDQogICAgIGNvbCA9ICJza3libHVlIiwNCiAgICAgYnJlYWtzID0gMzApDQoNCiMgR3LDoWZpY28gMzogRXJyb3JlcyB2cyBQcmVkaWNjaW9uZXMNCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMTU1MDAyIChYR0Jvb3N0KSIsDQogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwNCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCmBgYA0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFhHQm9vc3QgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgUjIgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIE1TRSA9IG51bWVyaWMoKSwNCiAgICBBSUMgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMTU1MDAyIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvDQogIE1vZGVsbyA9ICJYR0Jvb3N0IiwNCiAgUjIgPSByMl9jb21wbGV0bywgICMgVXNhbW9zIGxhcyBtw6l0cmljYXMgZGVsIGNvbmp1bnRvIGNvbXBsZXRvDQogIFJNU0UgPSBybXNlX2NvbXBsZXRvLA0KICBNQVBFID0gbWFwZV9jb21wbGV0bywNCiAgTVNFID0gbXNlX2NvbXBsZXRvLA0KICBBSUMgPSBhaWNfY29tcGxldG8NCikpDQpgYGANCg0KIyMgUFJPRFVDVE8gMzY3ODA1NQ0KDQpgYGB7cn0NCiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpDQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMzY3ODA1NSAlPiUNCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkNCg0KIyBQYXNvIDI6IERpdmlkaXIgbG9zIGRhdG9zIGVuIGNvbmp1bnRvcyBkZSBlbnRyZW5hbWllbnRvICg4MCUpIHkgcHJ1ZWJhICgyMCUpDQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZA0KdHJhaW5faW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkYXRvc19tb2RlbG8kVmVudGEsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkNCnRyYWluX2RhdGEgPC0gZGF0b3NfbW9kZWxvW3RyYWluX2luZGV4LCBdDQp0ZXN0X2RhdGEgPC0gZGF0b3NfbW9kZWxvWy10cmFpbl9pbmRleCwgXQ0KDQojIFByZXBhcmFyIG1hdHJpY2VzIHBhcmEgWEdCb29zdA0KdHJhaW5feCA8LSBhcy5tYXRyaXgodHJhaW5fZGF0YVssIGNvbG5hbWVzKHRyYWluX2RhdGEpICE9ICJWZW50YSJdKQ0KdHJhaW5feSA8LSB0cmFpbl9kYXRhJFZlbnRhDQoNCnRlc3RfeCA8LSBhcy5tYXRyaXgodGVzdF9kYXRhWywgY29sbmFtZXModGVzdF9kYXRhKSAhPSAiVmVudGEiXSkNCnRlc3RfeSA8LSB0ZXN0X2RhdGEkVmVudGENCg0KIyBDcmVhciBETWF0cml4IHBhcmEgWEdCb29zdA0KZHRyYWluIDwtIHhnYi5ETWF0cml4KGRhdGEgPSB0cmFpbl94LCBsYWJlbCA9IHRyYWluX3kpDQpkdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gdGVzdF94LCBsYWJlbCA9IHRlc3RfeSkNCg0KIyBQYXNvIDM6IERlZmluaXIgbGEgcmVqaWxsYSBkZSBoaXBlcnBhcsOhbWV0cm9zDQpwYXJhbV9ncmlkIDwtIGV4cGFuZC5ncmlkKA0KICBldGEgPSBjKDAuMDEsIDAuMDUsIDAuMSwgMC4zKSwNCiAgbWF4X2RlcHRoID0gYygzLCA2LCA5KSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMywgNSksDQogIHN1YnNhbXBsZSA9IGMoMC43LCAwLjkpLA0KICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjcsIDAuOSksDQogIGdhbW1hID0gYygwLCAwLjEsIDAuMykNCikNCg0KY2F0KCJHcmlkIFNlYXJjaCBwYXJhIFhHQm9vc3QgLSBQcm9kdWN0byAzNjc4MDU1XG4iKQ0KY2F0KCJOw7ptZXJvIHRvdGFsIGRlIGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvczoiLCBucm93KHBhcmFtX2dyaWQpLCAiXG5cbiIpDQoNCiMgU2VsZWNjacOzbiBhbGVhdG9yaWEgZGUgMTIgY29tYmluYWNpb25lcyAoc2kgc2UgZGVzZWEgbGltaXRhcikNCnNldC5zZWVkKDQ1NikNCmlmIChucm93KHBhcmFtX2dyaWQpID4gMTIpIHsNCiAgcGFyYW1fZ3JpZCA8LSBwYXJhbV9ncmlkW3NhbXBsZSgxOm5yb3cocGFyYW1fZ3JpZCksIDEyKSwgXQ0KICBjYXQoIlNlbGVjY2lvbmFuZG8gMTIgY29tYmluYWNpb25lcyBhbGVhdG9yaWFzIHBhcmEgZXZhbHVhY2nDs24uXG5cbiIpDQp9DQoNCiMgUGFzbyA0OiBJbXBsZW1lbnRhciBHcmlkIFNlYXJjaA0KcmVzdWx0YWRvcyA8LSBkYXRhLmZyYW1lKCkNCmNhdCgiSW5pY2lhbmRvIEdyaWQgU2VhcmNoLi4uXG4iKQ0KDQpmb3IgKGkgaW4gMTpucm93KHBhcmFtX2dyaWQpKSB7DQogIHBhcmFtcyA8LSBsaXN0KA0KICAgIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwNCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwNCiAgICBldGEgPSBwYXJhbV9ncmlkJGV0YVtpXSwNCiAgICBtYXhfZGVwdGggPSBwYXJhbV9ncmlkJG1heF9kZXB0aFtpXSwNCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1fZ3JpZCRtaW5fY2hpbGRfd2VpZ2h0W2ldLA0KICAgIHN1YnNhbXBsZSA9IHBhcmFtX2dyaWQkc3Vic2FtcGxlW2ldLA0KICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbV9ncmlkJGNvbHNhbXBsZV9ieXRyZWVbaV0sDQogICAgZ2FtbWEgPSBwYXJhbV9ncmlkJGdhbW1hW2ldDQogICkNCiAgDQogIGNhdCgiRXZhbHVhbmRvIGNvbWJpbmFjacOzbiIsIGksICJkZSIsIG5yb3cocGFyYW1fZ3JpZCksICIuLi5cbiIpDQogIA0KICBjdl9tb2RlbCA8LSB4Z2IuY3YoDQogICAgcGFyYW1zID0gcGFyYW1zLA0KICAgIGRhdGEgPSBkdHJhaW4sDQogICAgbnJvdW5kcyA9IDIwMCwNCiAgICBuZm9sZCA9IDUsDQogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMjAsDQogICAgdmVyYm9zZSA9IDANCiAgKQ0KICANCiAgYmVzdF9pdGVyYXRpb24gPC0gY3ZfbW9kZWwkYmVzdF9pdGVyYXRpb24NCiAgYmVzdF9ybXNlIDwtIG1pbihjdl9tb2RlbCRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbikNCiAgDQogIHJlc3VsdGFkb19hY3R1YWwgPC0gZGF0YS5mcmFtZSgNCiAgICBldGEgPSBwYXJhbXMkZXRhLA0KICAgIG1heF9kZXB0aCA9IHBhcmFtcyRtYXhfZGVwdGgsDQogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtcyRtaW5fY2hpbGRfd2VpZ2h0LA0KICAgIHN1YnNhbXBsZSA9IHBhcmFtcyRzdWJzYW1wbGUsDQogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtcyRjb2xzYW1wbGVfYnl0cmVlLA0KICAgIGdhbW1hID0gcGFyYW1zJGdhbW1hLA0KICAgIG5yb3VuZHMgPSBiZXN0X2l0ZXJhdGlvbiwNCiAgICBybXNlX2N2ID0gYmVzdF9ybXNlDQogICkNCiAgDQogIHJlc3VsdGFkb3MgPC0gcmJpbmQocmVzdWx0YWRvcywgcmVzdWx0YWRvX2FjdHVhbCkNCn0NCg0KcmVzdWx0YWRvcyA8LSByZXN1bHRhZG9zW29yZGVyKHJlc3VsdGFkb3Mkcm1zZV9jdiksIF0NCg0KIyBQYXNvIDU6IE1vc3RyYXIgcmVzdWx0YWRvcw0KcHJpbnQocmVzdWx0YWRvcykNCg0KIyBWaXN1YWxpemFjacOzbg0KZ2dwbG90KHJlc3VsdGFkb3MsIGFlcyh4ID0gcmVvcmRlcihwYXN0ZSgiQ29tYiIsIDE6bnJvdyhyZXN1bHRhZG9zKSksIHJtc2VfY3YpLCB5ID0gcm1zZV9jdikpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIC0gUHJvZHVjdG8gMzY3ODA1NSIsDQogICAgeCA9ICJDb21iaW5hY2nDs24gZGUgSGlwZXJwYXLDoW1ldHJvcyIsDQogICAgeSA9ICJSTVNFIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkNCg0KIyBQYXNvIDY6IFNlbGVjY2lvbmFyIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MNCm1lam9yZXNfcGFyYW1zIDwtIGxpc3QoDQogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwNCiAgZXZhbF9tZXRyaWMgPSAicm1zZSIsDQogIGV0YSA9IHJlc3VsdGFkb3MkZXRhWzFdLA0KICBtYXhfZGVwdGggPSByZXN1bHRhZG9zJG1heF9kZXB0aFsxXSwNCiAgbWluX2NoaWxkX3dlaWdodCA9IHJlc3VsdGFkb3MkbWluX2NoaWxkX3dlaWdodFsxXSwNCiAgc3Vic2FtcGxlID0gcmVzdWx0YWRvcyRzdWJzYW1wbGVbMV0sDQogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zJGNvbHNhbXBsZV9ieXRyZWVbMV0sDQogIGdhbW1hID0gcmVzdWx0YWRvcyRnYW1tYVsxXQ0KKQ0KDQptZWpvcl9ucm91bmRzIDwtIHJlc3VsdGFkb3MkbnJvdW5kc1sxXQ0KDQojIFBhc28gNzogRW50cmVuYXIgbW9kZWxvIGZpbmFsDQptb2RlbG9fZmluYWwgPC0geGdiLnRyYWluKA0KICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtcywNCiAgZGF0YSA9IGR0cmFpbiwNCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsDQogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW4sIHRlc3QgPSBkdGVzdCksDQogIHZlcmJvc2UgPSAwDQopDQoNCm1vZGVsb194Z2JfMzY3ODA1NSA8LSBtb2RlbG9fZmluYWwNCg0KDQojIFBhc28gODogRXZhbHVhciBtb2RlbG8NCnByZWRpY2Npb25lc190ZXN0IDwtIHByZWRpY3QobW9kZWxvX2ZpbmFsLCBkdGVzdCkNCnIyX3Rlc3QgPC0gMSAtIHN1bSgodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpXjIpIC8gc3VtKCh0ZXN0X3kgLSBtZWFuKHRlc3RfeSkpXjIpDQpybXNlX3Rlc3QgPC0gc3FydChtZWFuKCh0ZXN0X3kgLSBwcmVkaWNjaW9uZXNfdGVzdCleMikpDQptYXBlX3Rlc3QgPC0gbWVhbihhYnMoKHRlc3RfeSAtIHByZWRpY2Npb25lc190ZXN0KSAvIHBtYXgodGVzdF95LCAwLjAxKSkpICogMTAwDQptc2VfdGVzdCA8LSBtZWFuKCh0ZXN0X3kgLSBwcmVkaWNjaW9uZXNfdGVzdCleMikNCmFpY190ZXN0IDwtIGxlbmd0aCh0ZXN0X3kpICogbG9nKG1zZV90ZXN0KSArIDIgKiAobGVuZ3RoKG1lam9yZXNfcGFyYW1zKSArIDEpDQoNCiMgUGFzbyA5OiBQcmVkaWNjacOzbiBlbiB0b2RvIGVsIGNvbmp1bnRvDQp4X2NvbXBsZXRvIDwtIGFzLm1hdHJpeChkYXRvc19tb2RlbG9bLCBjb2xuYW1lcyhkYXRvc19tb2RlbG8pICE9ICJWZW50YSJdKQ0KcHJlZGljY2lvbmVzX2NvbXBsZXRvIDwtIHByZWRpY3QobW9kZWxvX2ZpbmFsLCB4X2NvbXBsZXRvKQ0KDQpyMl9jb21wbGV0byA8LSAxIC0gc3VtKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pXjIpIC8gDQogIHN1bSgoZGF0b3NfbW9kZWxvJFZlbnRhIC0gbWVhbihkYXRvc19tb2RlbG8kVmVudGEpKV4yKQ0KDQpybXNlX2NvbXBsZXRvIDwtIHNxcnQobWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvKV4yKSkNCm1hcGVfY29tcGxldG8gPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0bykgLyBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMA0KbXNlX2NvbXBsZXRvIDwtIG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0byleMikNCmFpY19jb21wbGV0byA8LSBucm93KGRhdG9zX21vZGVsbykgKiBsb2cobXNlX2NvbXBsZXRvKSArIDIgKiAobGVuZ3RoKG1lam9yZXNfcGFyYW1zKSArIDEpDQoNCiMgUGFzbyAxMDogSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzDQppbXBvcnRhbmNpYSA8LSB4Z2IuaW1wb3J0YW5jZSgNCiAgZmVhdHVyZV9uYW1lcyA9IGNvbG5hbWVzKGRhdG9zX21vZGVsbylbY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSwNCiAgbW9kZWwgPSBtb2RlbG9fZmluYWwNCikNCnhnYi5wbG90LmltcG9ydGFuY2UoaW1wb3J0YW5jaWEsIA0KICAgICAgICAgICAgICAgICAgICBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM2NzgwNTUgKFhHQm9vc3QpIikNCg0KIyBQYXNvIDExOiBHcsOhZmljb3MgZGUgZXZhbHVhY2nDs24NCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZShPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvKQ0KDQpnZ3Bsb3QoZGF0b3NfZ3JhZmljbywgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsNCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKw0KICBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDAsIGxpbmV0eXBlID0gImRhc2hlZCIsIGNvbG9yID0gInJlZCIpICsNCiAgbGFicyh0aXRsZSA9ICJPYnNlcnZhZG8gdnMgUHJlZGljaG8gLSBQcm9kdWN0byAzNjc4MDU1IiwgeCA9ICJWZW50YSBPYnNlcnZhZGEiLCB5ID0gIlZlbnRhIFByZWRpY2hhIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KZXJyb3JlcyA8LSBkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8NCg0KaGlzdChlcnJvcmVzLCANCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAzNjc4MDU1IChYR0Jvb3N0KSIsDQogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsDQogICAgIGNvbCA9ICJza3libHVlIiwgYnJlYWtzID0gMzApDQoNCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArDQogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBsYWJzKHRpdGxlID0gIkVycm9yZXMgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzNjc4MDU1IiwgeCA9ICJWZW50YSBQcmVkaWNoYSIsIHkgPSAiRXJyb3IiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQpgYGANCg0KYGBge3J9DQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFhHQm9vc3QgcGFyYSBwcm9kdWN0byAxNTUwMDENCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7DQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKA0KICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksDQogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksDQogICAgUjIgPSBudW1lcmljKCksDQogICAgUk1TRSA9IG51bWVyaWMoKSwNCiAgICBNQVBFID0gbnVtZXJpYygpLA0KICAgIE1TRSA9IG51bWVyaWMoKSwNCiAgICBBSUMgPSBudW1lcmljKCksDQogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQogICkNCn0NCg0KbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSAiMzY3ODA1NSIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bw0KICBNb2RlbG8gPSAiWEdCb29zdCIsDQogIFIyID0gcjJfY29tcGxldG8sICAjIFVzYW1vcyBsYXMgbcOpdHJpY2FzIGRlbCBjb25qdW50byBjb21wbGV0bw0KICBSTVNFID0gcm1zZV9jb21wbGV0bywNCiAgTUFQRSA9IG1hcGVfY29tcGxldG8sDQogIE1TRSA9IG1zZV9jb21wbGV0bywNCiAgQUlDID0gYWljX2NvbXBsZXRvDQopKQ0KYGBgDQoNCiMgVmlzdWFsaXphY2nDs24gZGUgTcOpdHJpY2FzDQoNCmBgYHtyfQ0KIyBEZWZpbmlyIGxvcyBjb2xvcmVzIHBhcmEgY2FkYSBtb2RlbG8NCmNvbG9yZXNfbW9kZWxvcyA8LSBjKA0KICAiQVJNQS9TQVJJTUEiID0gIiMxZjc3YjQiLCAgICAjIEF6dWwNCiAgIlJlZ3Jlc2nDs24gTGluZWFsIiA9ICIjZmY3ZjBlIiwgIyBOYXJhbmphDQogICJSYW5kb20gRm9yZXN0IiA9ICIjMmNhMDJjIiwgICAjIFZlcmRlDQogICJYR0Jvb3N0IiA9ICIjZDYyNzI4IiAgICAgICAgICMgUm9qbw0KKQ0KYGBgDQoNCiMjIFBST0RVQ1RPIDE1NTAwMQ0KYGBge3J9DQojIFByaW1lcm8sIHZlYW1vcyBxdcOpIGRhdG9zIHRlbmVtb3MgcmVhbG1lbnRlDQpwcmludCgiRGF0b3MgYWN0dWFsZXMgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDE6IikNCnByaW50KG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIxNTUwMDEiKSkNCg0KIyBDcmVhciB1biBkYXRhZnJhbWUgbWFudWFsbWVudGUgY29uIGxvcyA0IG1vZGVsb3MgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDENCiMgKGNvbiB2YWxvcmVzIGRlIGVqZW1wbG8gc2kgZXMgbmVjZXNhcmlvKQ0KZGF0b3NfMTU1MDAxX2NvbXBsZXRvIDwtIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gcmVwKCIxNTUwMDEiLCA0KSwNCiAgTW9kZWxvID0gYygiQVJNQS9TQVJJTUEiLCAiUmVncmVzacOzbiBMaW5lYWwiLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IiksDQogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KKQ0KDQojIFVuaXIgY29uIGxvcyBkYXRvcyBleGlzdGVudGVzDQpkYXRvc18xNTUwMDFfY29tcGxldG8gPC0gbGVmdF9qb2luKA0KICBkYXRvc18xNTUwMDFfY29tcGxldG8sDQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIxNTUwMDEiKSwNCiAgYnkgPSBjKCJQcm9kdWN0byIsICJNb2RlbG8iKQ0KKQ0KDQojIEFob3JhIGFzaWduYSB2YWxvcmVzIHBhcmEgbGFzIG3DqXRyaWNhcyBkZSBsb3MgbW9kZWxvcyBmYWx0YW50ZXMNCiMgU2kgdGllbmVzIGxvcyB2YWxvcmVzLCByZWVtcGxhemEgbG9zIDAgY29uIGxvcyB2YWxvcmVzIGNvcnJlY3Rvcw0KIyBPIHRvbWEgbm90YSBkZSBjdcOhbGVzIHNvbiBOQSBwYXJhIHJlZW1wbGF6YXJsb3MgY29uIGxvcyB2YWxvcmVzIHJlYWxlcw0KDQojIFZhbG9yZXMgcGFyYSBSZWdyZXNpw7NuIExpbmVhbCAocmVlbXBsYXphIGVzdG9zIGNvbiBsb3MgdmFsb3JlcyByZWFsZXMpDQppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJFIyWzJdKSkgew0KICBkYXRvc18xNTUwMDFfY29tcGxldG8kUjJbMl0gPC0gcjJfMTU1MDAxICAjIE8gZWwgdmFsb3IgY29ycmVjdG8NCn0NCmlmIChpcy5uYShkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRVsyXSkpIHsNCiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvJFJNU0VbMl0gPC0gcm1zZV8xNTUwMDEgICMgTyBlbCB2YWxvciBjb3JyZWN0bw0KfQ0KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMV9jb21wbGV0byRNQVBFWzJdKSkgew0KICBkYXRvc18xNTUwMDFfY29tcGxldG8kTUFQRVsyXSA8LSBtYXBlXzE1NTAwMSAgIyBPIGVsIHZhbG9yIGNvcnJlY3RvDQp9DQppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1TRVsyXSkpIHsNCiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1TRVsyXSA8LSBtc2VfMTU1MDAxICAjIE8gZWwgdmFsb3IgY29ycmVjdG8NCn0NCmlmIChpcy5uYShkYXRvc18xNTUwMDFfY29tcGxldG8kQUlDWzJdKSkgew0KICBkYXRvc18xNTUwMDFfY29tcGxldG8kQUlDWzJdIDwtIGFpY18xNTUwMDEgICMgTyBlbCB2YWxvciBjb3JyZWN0bw0KfQ0KDQojIFZhbG9yZXMgcGFyYSBSYW5kb20gRm9yZXN0IChyZWVtcGxhemEgZXN0b3MgY29uIGxvcyB2YWxvcmVzIHJlYWxlcykNCiMgU2kgeWEgZWplY3V0YXN0ZSBsYSBzZWNjacOzbiBkZSBSYW5kb20gRm9yZXN0IHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAxLA0KIyB1c2EgbGFzIHZhcmlhYmxlcyByMl9yZiwgcm1zZV9yZiwgZXRjLg0KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMV9jb21wbGV0byRSMlszXSkgJiYgZXhpc3RzKCJyMl9yZiIpKSB7DQogIGRhdG9zXzE1NTAwMV9jb21wbGV0byRSMlszXSA8LSByMl9yZg0KfQ0KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMV9jb21wbGV0byRSTVNFWzNdKSAmJiBleGlzdHMoInJtc2VfcmYiKSkgew0KICBkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRVszXSA8LSBybXNlX3JmDQp9DQppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbM10pICYmIGV4aXN0cygibWFwZV9yZiIpKSB7DQogIGRhdG9zXzE1NTAwMV9jb21wbGV0byRNQVBFWzNdIDwtIG1hcGVfcmYNCn0NCmlmIChpcy5uYShkYXRvc18xNTUwMDFfY29tcGxldG8kTVNFWzNdKSAmJiBleGlzdHMoIm1zZV9yZiIpKSB7DQogIGRhdG9zXzE1NTAwMV9jb21wbGV0byRNU0VbM10gPC0gbXNlX3JmDQp9DQppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJEFJQ1szXSkgJiYgZXhpc3RzKCJhaWNfcmYiKSkgew0KICBkYXRvc18xNTUwMDFfY29tcGxldG8kQUlDWzNdIDwtIGFpY19yZg0KfQ0KDQojIFZhbG9yZXMgcGFyYSBYR0Jvb3N0IChyZWVtcGxhemEgZXN0b3MgY29uIGxvcyB2YWxvcmVzIHJlYWxlcykNCiMgU2kgeWEgZWplY3V0YXN0ZSBsYSBzZWNjacOzbiBkZSBYR0Jvb3N0IHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAxLA0KIyB1c2EgbGFzIHZhcmlhYmxlcyByMl9jb21wbGV0bywgcm1zZV9jb21wbGV0bywgZXRjLg0KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMV9jb21wbGV0byRSMls0XSkgJiYgZXhpc3RzKCJyMl9jb21wbGV0byIpKSB7DQogIGRhdG9zXzE1NTAwMV9jb21wbGV0byRSMls0XSA8LSByMl9jb21wbGV0bw0KfQ0KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMV9jb21wbGV0byRSTVNFWzRdKSAmJiBleGlzdHMoInJtc2VfY29tcGxldG8iKSkgew0KICBkYXRvc18xNTUwMDFfY29tcGxldG8kUk1TRVs0XSA8LSBybXNlX2NvbXBsZXRvDQp9DQppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbNF0pICYmIGV4aXN0cygibWFwZV9jb21wbGV0byIpKSB7DQogIGRhdG9zXzE1NTAwMV9jb21wbGV0byRNQVBFWzRdIDwtIG1hcGVfY29tcGxldG8NCn0NCmlmIChpcy5uYShkYXRvc18xNTUwMDFfY29tcGxldG8kTVNFWzRdKSAmJiBleGlzdHMoIm1zZV9jb21wbGV0byIpKSB7DQogIGRhdG9zXzE1NTAwMV9jb21wbGV0byRNU0VbNF0gPC0gbXNlX2NvbXBsZXRvDQp9DQppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJEFJQ1s0XSkgJiYgZXhpc3RzKCJhaWNfY29tcGxldG8iKSkgew0KICBkYXRvc18xNTUwMDFfY29tcGxldG8kQUlDWzRdIDwtIGFpY19jb21wbGV0bw0KfQ0KDQojIFZlciBsb3MgZGF0b3MgY29tcGxldG9zDQpwcmludCgiRGF0b3MgY29tcGxldG9zIHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAxOiIpDQpwcmludChkYXRvc18xNTUwMDFfY29tcGxldG8pDQoNCiMgQ3JlYXIgZWwgZ3LDoWZpY28gY29uIGxvcyBkYXRvcyBjb21wbGV0b3MNCmdncGxvdChkYXRvc18xNTUwMDFfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gUjIsIGZpbGwgPSBNb2RlbG8pKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoUjIsIDMpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMTU1MDAxIiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogUsKyICh2YWxvcmVzIG3DoXMgYWx0b3MgaW5kaWNhbiBtZWpvciBhanVzdGUpIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJSwrIiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18xNTUwMDFfY29tcGxldG8kUjIsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZDQoNCiMgR3LDoWZpY28gcGFyYSBSTVNFDQpnZ3Bsb3QoZGF0b3NfMTU1MDAxX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IFJNU0UsIGZpbGwgPSBNb2RlbG8pKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoUk1TRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAxNTUwMDEiLA0KICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBSTVNFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiUk1TRSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkNCiAgKSArDQogIHlsaW0oMCwgbWF4KGRhdG9zXzE1NTAwMV9jb21wbGV0byRSTVNFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KDQojIEdyw6FmaWNvIHBhcmEgTUFQRQ0KZ2dwbG90KGRhdG9zXzE1NTAwMV9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNQVBFLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1BUEUsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMTU1MDAxIiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogTUFQRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLA0KICAgIHggPSAiIiwNCiAgICB5ID0gIk1BUEUgKCUpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZDQoNCiMgR3LDoWZpY28gcGFyYSBNU0UNCmdncGxvdChkYXRvc18xNTUwMDFfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gTVNFLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1TRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAxNTUwMDEiLA0KICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBNU0UgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJNU0UiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18xNTUwMDFfY29tcGxldG8kTVNFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KDQojIEdyw6FmaWNvIHBhcmEgQUlDDQpnZ3Bsb3QoZGF0b3NfMTU1MDAxX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IEFJQywgZmlsbCA9IE1vZGVsbykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChBSUMsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMTU1MDAxIiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogQUlDICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBtb2RlbG8pIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJBSUMiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18xNTUwMDFfY29tcGxldG8kQUlDLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KYGBgDQoNCiMjIFBST0RVQ1RPIDM5Mjk3ODgNCmBgYHtyfQ0KIyBQcmltZXJvLCB2ZWFtb3MgcXXDqSBkYXRvcyB0ZW5lbW9zIHJlYWxtZW50ZQ0KcHJpbnQoIkRhdG9zIGFjdHVhbGVzIHBhcmEgZWwgcHJvZHVjdG8gMzkyOTc4ODoiKQ0KcHJpbnQobWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjM5Mjk3ODgiKSkNCg0KIyBDcmVhciB1biBkYXRhZnJhbWUgbWFudWFsbWVudGUgY29uIGxvcyA0IG1vZGVsb3MgcGFyYSBlbCBwcm9kdWN0byAzOTI5Nzg4DQpkYXRvc18zOTI5Nzg4X2NvbXBsZXRvIDwtIGRhdGEuZnJhbWUoDQogIFByb2R1Y3RvID0gcmVwKCIzOTI5Nzg4IiwgNCksDQogIE1vZGVsbyA9IGMoIkFSTUEvU0FSSU1BIiwgIlJlZ3Jlc2nDs24gTGluZWFsIiwgIlJhbmRvbSBGb3Jlc3QiLCAiWEdCb29zdCIpLA0KICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UNCikNCg0KIyBVbmlyIGNvbiBsb3MgZGF0b3MgZXhpc3RlbnRlcw0KZGF0b3NfMzkyOTc4OF9jb21wbGV0byA8LSBsZWZ0X2pvaW4oDQogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8sDQogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIzOTI5Nzg4IiksDQogIGJ5ID0gYygiUHJvZHVjdG8iLCAiTW9kZWxvIikNCikNCg0KIyBBaG9yYSBhc2lnbmEgdmFsb3JlcyBwYXJhIGxhcyBtw6l0cmljYXMgZGUgbG9zIG1vZGVsb3MgZmFsdGFudGVzDQojIFZhbG9yZXMgcGFyYSBSZWdyZXNpw7NuIExpbmVhbA0KaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kUjJbMl0pKSB7DQogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kUjJbMl0gPC0gcjJfMzkyOTc4OA0KfQ0KaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kUk1TRVsyXSkpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSTVNFWzJdIDwtIHJtc2VfMzkyOTc4OA0KfQ0KaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRVsyXSkpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNQVBFWzJdIDwtIG1hcGVfMzkyOTc4OA0KfQ0KaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTVNFWzJdKSkgew0KICBkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1TRVsyXSA8LSBtc2VfMzkyOTc4OA0KfQ0KaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kQUlDWzJdKSkgew0KICBkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJEFJQ1syXSA8LSBhaWNfMzkyOTc4OA0KfQ0KDQojIFZhbG9yZXMgcGFyYSBSYW5kb20gRm9yZXN0DQojIFNpIHlhIGVqZWN1dGFzdGUgbGEgc2VjY2nDs24gZGUgUmFuZG9tIEZvcmVzdCBwYXJhIGVsIHByb2R1Y3RvIDM5Mjk3ODgNCmlmIChpcy5uYShkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJFIyWzNdKSAmJiBleGlzdHMoInIyX3JmIikpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSMlszXSA8LSByMl9yZg0KfQ0KaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kUk1TRVszXSkgJiYgZXhpc3RzKCJybXNlX3JmIikpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSTVNFWzNdIDwtIHJtc2VfcmYNCn0NCmlmIChpcy5uYShkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1BUEVbM10pICYmIGV4aXN0cygibWFwZV9yZiIpKSB7DQogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRVszXSA8LSBtYXBlX3JmDQp9DQppZiAoaXMubmEoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNU0VbM10pICYmIGV4aXN0cygibXNlX3JmIikpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNU0VbM10gPC0gbXNlX3JmDQp9DQppZiAoaXMubmEoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRBSUNbM10pICYmIGV4aXN0cygiYWljX3JmIikpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRBSUNbM10gPC0gYWljX3JmDQp9DQoNCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QNCmlmIChpcy5uYShkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJFIyWzRdKSAmJiBleGlzdHMoInIyX2NvbXBsZXRvIikpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSMls0XSA8LSByMl9jb21wbGV0bw0KfQ0KaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kUk1TRVs0XSkgJiYgZXhpc3RzKCJybXNlX2NvbXBsZXRvIikpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSTVNFWzRdIDwtIHJtc2VfY29tcGxldG8NCn0NCmlmIChpcy5uYShkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1BUEVbNF0pICYmIGV4aXN0cygibWFwZV9jb21wbGV0byIpKSB7DQogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRVs0XSA8LSBtYXBlX2NvbXBsZXRvDQp9DQppZiAoaXMubmEoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNU0VbNF0pICYmIGV4aXN0cygibXNlX2NvbXBsZXRvIikpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNU0VbNF0gPC0gbXNlX2NvbXBsZXRvDQp9DQppZiAoaXMubmEoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRBSUNbNF0pICYmIGV4aXN0cygiYWljX2NvbXBsZXRvIikpIHsNCiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRBSUNbNF0gPC0gYWljX2NvbXBsZXRvDQp9DQoNCiMgVmVyIGxvcyBkYXRvcyBjb21wbGV0b3MNCnByaW50KCJEYXRvcyBjb21wbGV0b3MgcGFyYSBlbCBwcm9kdWN0byAzOTI5Nzg4OiIpDQpwcmludChkYXRvc18zOTI5Nzg4X2NvbXBsZXRvKQ0KDQojIERlZmluaXIgY29sb3JlcyBwYXJhIGxvcyBtb2RlbG9zDQpjb2xvcmVzX21vZGVsb3MgPC0gYygiQVJNQS9TQVJJTUEiID0gIiMxZjc3YjQiLCANCiAgICAgICAgICAgICAgICAgICAgICJSZWdyZXNpw7NuIExpbmVhbCIgPSAiI2ZmN2YwZSIsIA0KICAgICAgICAgICAgICAgICAgICAgIlJhbmRvbSBGb3Jlc3QiID0gIiMyY2EwMmMiLCANCiAgICAgICAgICAgICAgICAgICAgICJYR0Jvb3N0IiA9ICIjZDYyNzI4IikNCg0KIyBHcsOhZmljbyBwYXJhIFLCsg0KZ2dwbG90KGRhdG9zXzM5Mjk3ODhfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gUjIsIGZpbGwgPSBNb2RlbG8pKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoUjIsIDMpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMzkyOTc4OCIsDQogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IFLCsiAodmFsb3JlcyBtw6FzIGFsdG9zIGluZGljYW4gbWVqb3IgYWp1c3RlKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiUsKyIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRSMiwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkNCg0KIyBHcsOhZmljbyBwYXJhIFJNU0UNCmdncGxvdChkYXRvc18zOTI5Nzg4X2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IFJNU0UsIGZpbGwgPSBNb2RlbG8pKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoUk1TRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAzOTI5Nzg4IiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogUk1TRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLA0KICAgIHggPSAiIiwNCiAgICB5ID0gIlJNU0UiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJFJNU0UsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZDQoNCiMgR3LDoWZpY28gcGFyYSBNQVBFDQpnZ3Bsb3QoZGF0b3NfMzkyOTc4OF9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNQVBFLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1BUEUsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMzkyOTc4OCIsDQogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IE1BUEUgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJNQVBFICglKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkNCiAgKSArDQogIHlsaW0oMCwgbWF4KGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkNCg0KIyBHcsOhZmljbyBwYXJhIE1TRQ0KZ2dwbG90KGRhdG9zXzM5Mjk3ODhfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gTVNFLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1TRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAzOTI5Nzg4IiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogTVNFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiTVNFIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNU0UsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZDQoNCiMgR3LDoWZpY28gcGFyYSBBSUMNCmdncGxvdChkYXRvc18zOTI5Nzg4X2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IEFJQywgZmlsbCA9IE1vZGVsbykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChBSUMsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMzkyOTc4OCIsDQogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IEFJQyAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgbW9kZWxvKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiQUlDIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRBSUMsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZDQpgYGANCg0KIyMgUFJPRFVDVE8gMzkwNDE1Mg0KYGBge3J9DQojIFByaW1lcm8sIHZlYW1vcyBxdcOpIGRhdG9zIHRlbmVtb3MgcmVhbG1lbnRlDQpwcmludCgiRGF0b3MgYWN0dWFsZXMgcGFyYSBlbCBwcm9kdWN0byAzOTA0MTUyOiIpDQpwcmludChtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMzkwNDE1MiIpKQ0KDQojIENyZWFyIHVuIGRhdGFmcmFtZSBtYW51YWxtZW50ZSBjb24gbG9zIDQgbW9kZWxvcyBwYXJhIGVsIHByb2R1Y3RvIDM5MDQxNTINCmRhdG9zXzM5MDQxNTJfY29tcGxldG8gPC0gZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSByZXAoIjM5MDQxNTIiLCA0KSwNCiAgTW9kZWxvID0gYygiQVJNQS9TQVJJTUEiLCAiUmVncmVzacOzbiBMaW5lYWwiLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IiksDQogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQ0KKQ0KDQojIFVuaXIgY29uIGxvcyBkYXRvcyBleGlzdGVudGVzDQpkYXRvc18zOTA0MTUyX2NvbXBsZXRvIDwtIGxlZnRfam9pbigNCiAgZGF0b3NfMzkwNDE1Ml9jb21wbGV0bywNCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjM5MDQxNTIiKSwNCiAgYnkgPSBjKCJQcm9kdWN0byIsICJNb2RlbG8iKQ0KKQ0KDQojIEFob3JhIGFzaWduYSB2YWxvcmVzIHBhcmEgbGFzIG3DqXRyaWNhcyBkZSBsb3MgbW9kZWxvcyBmYWx0YW50ZXMNCiMgVmFsb3JlcyBwYXJhIFJlZ3Jlc2nDs24gTGluZWFsDQppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRSMlsyXSkpIHsNCiAgZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRSMlsyXSA8LSByMl8zOTA0MTUyDQp9DQppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRSTVNFWzJdKSkgew0KICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFJNU0VbMl0gPC0gcm1zZV8zOTA0MTUyDQp9DQppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNQVBFWzJdKSkgew0KICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1BUEVbMl0gPC0gbWFwZV8zOTA0MTUyDQp9DQppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNU0VbMl0pKSB7DQogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTVNFWzJdIDwtIG1zZV8zOTA0MTUyDQp9DQppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRBSUNbMl0pKSB7DQogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8kQUlDWzJdIDwtIGFpY18zOTA0MTUyDQp9DQoNCiMgVmFsb3JlcyBwYXJhIFJhbmRvbSBGb3Jlc3QNCiMgU2kgeWEgZWplY3V0YXN0ZSBsYSBzZWNjacOzbiBkZSBSYW5kb20gRm9yZXN0IHBhcmEgZWwgcHJvZHVjdG8gMzkwNDE1Mg0KaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kUjJbM10pICYmIGV4aXN0cygicjJfcmYiKSkgew0KICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFIyWzNdIDwtIHIyX3JmDQp9DQppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRSTVNFWzNdKSAmJiBleGlzdHMoInJtc2VfcmYiKSkgew0KICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFJNU0VbM10gPC0gcm1zZV9yZg0KfQ0KaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTUFQRVszXSkgJiYgZXhpc3RzKCJtYXBlX3JmIikpIHsNCiAgZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNQVBFWzNdIDwtIG1hcGVfcmYNCn0NCmlmIChpcy5uYShkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1TRVszXSkgJiYgZXhpc3RzKCJtc2VfcmYiKSkgew0KICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1TRVszXSA8LSBtc2VfcmYNCn0NCmlmIChpcy5uYShkYXRvc18zOTA0MTUyX2NvbXBsZXRvJEFJQ1szXSkgJiYgZXhpc3RzKCJhaWNfcmYiKSkgew0KICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJEFJQ1szXSA8LSBhaWNfcmYNCn0NCg0KIyBWYWxvcmVzIHBhcmEgWEdCb29zdA0KaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kUjJbNF0pICYmIGV4aXN0cygicjJfY29tcGxldG8iKSkgew0KICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFIyWzRdIDwtIHIyX2NvbXBsZXRvDQp9DQppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRSTVNFWzRdKSAmJiBleGlzdHMoInJtc2VfY29tcGxldG8iKSkgew0KICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFJNU0VbNF0gPC0gcm1zZV9jb21wbGV0bw0KfQ0KaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTUFQRVs0XSkgJiYgZXhpc3RzKCJtYXBlX2NvbXBsZXRvIikpIHsNCiAgZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNQVBFWzRdIDwtIG1hcGVfY29tcGxldG8NCn0NCmlmIChpcy5uYShkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1TRVs0XSkgJiYgZXhpc3RzKCJtc2VfY29tcGxldG8iKSkgew0KICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1TRVs0XSA8LSBtc2VfY29tcGxldG8NCn0NCmlmIChpcy5uYShkYXRvc18zOTA0MTUyX2NvbXBsZXRvJEFJQ1s0XSkgJiYgZXhpc3RzKCJhaWNfY29tcGxldG8iKSkgew0KICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJEFJQ1s0XSA8LSBhaWNfY29tcGxldG8NCn0NCg0KIyBWZXIgbG9zIGRhdG9zIGNvbXBsZXRvcw0KcHJpbnQoIkRhdG9zIGNvbXBsZXRvcyBwYXJhIGVsIHByb2R1Y3RvIDM5MDQxNTI6IikNCnByaW50KGRhdG9zXzM5MDQxNTJfY29tcGxldG8pDQoNCiMgRGVmaW5pciBjb2xvcmVzIHBhcmEgbG9zIG1vZGVsb3MNCmNvbG9yZXNfbW9kZWxvcyA8LSBjKCJBUk1BL1NBUklNQSIgPSAiIzFmNzdiNCIsIA0KICAgICAgICAgICAgICAgICAgICAgIlJlZ3Jlc2nDs24gTGluZWFsIiA9ICIjZmY3ZjBlIiwgDQogICAgICAgICAgICAgICAgICAgICAiUmFuZG9tIEZvcmVzdCIgPSAiIzJjYTAyYyIsIA0KICAgICAgICAgICAgICAgICAgICAgIlhHQm9vc3QiID0gIiNkNjI3MjgiKQ0KDQojIEdyw6FmaWNvIHBhcmEgUsKyDQpnZ3Bsb3QoZGF0b3NfMzkwNDE1Ml9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBSMiwgZmlsbCA9IE1vZGVsbykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChSMiwgMykpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAzOTA0MTUyIiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogUsKyICh2YWxvcmVzIG3DoXMgYWx0b3MgaW5kaWNhbiBtZWpvciBhanVzdGUpIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJSwrIiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18zOTA0MTUyX2NvbXBsZXRvJFIyLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KDQojIEdyw6FmaWNvIHBhcmEgUk1TRQ0KZ2dwbG90KGRhdG9zXzM5MDQxNTJfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gUk1TRSwgZmlsbCA9IE1vZGVsbykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChSTVNFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM5MDQxNTIiLA0KICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBSTVNFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiUk1TRSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkNCiAgKSArDQogIHlsaW0oMCwgbWF4KGRhdG9zXzM5MDQxNTJfY29tcGxldG8kUk1TRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkNCg0KIyBHcsOhZmljbyBwYXJhIE1BUEUNCmdncGxvdChkYXRvc18zOTA0MTUyX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IE1BUEUsIGZpbGwgPSBNb2RlbG8pKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoTUFQRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAzOTA0MTUyIiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogTUFQRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLA0KICAgIHggPSAiIiwNCiAgICB5ID0gIk1BUEUgKCUpIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNQVBFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KDQojIEdyw6FmaWNvIHBhcmEgTVNFDQpnZ3Bsb3QoZGF0b3NfMzkwNDE1Ml9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNU0UsIGZpbGwgPSBNb2RlbG8pKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoTVNFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM5MDQxNTIiLA0KICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBNU0UgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJNU0UiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1TRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkNCg0KIyBHcsOhZmljbyBwYXJhIEFJQw0KZ2dwbG90KGRhdG9zXzM5MDQxNTJfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gQUlDLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKEFJQywgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAzOTA0MTUyIiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogQUlDICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBtb2RlbG8pIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJBSUMiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18zOTA0MTUyX2NvbXBsZXRvJEFJQywgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkNCmBgYA0KDQojIyBQUk9EVUNUTyAxNTUwMDINCmBgYHtyfQ0KIyBQcmltZXJvLCB2ZWFtb3MgcXXDqSBkYXRvcyB0ZW5lbW9zIHJlYWxtZW50ZQ0KcHJpbnQoIkRhdG9zIGFjdHVhbGVzIHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAyOiIpDQpwcmludChtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMTU1MDAyIikpDQoNCiMgQ3JlYXIgdW4gZGF0YWZyYW1lIG1hbnVhbG1lbnRlIGNvbiBsb3MgNCBtb2RlbG9zIHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAyDQpkYXRvc18xNTUwMDJfY29tcGxldG8gPC0gZGF0YS5mcmFtZSgNCiAgUHJvZHVjdG8gPSByZXAoIjE1NTAwMiIsIDQpLA0KICBNb2RlbG8gPSBjKCJBUk1BL1NBUklNQSIsICJSZWdyZXNpw7NuIExpbmVhbCIsICJSYW5kb20gRm9yZXN0IiwgIlhHQm9vc3QiKSwNCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQopDQoNCiMgVW5pciBjb24gbG9zIGRhdG9zIGV4aXN0ZW50ZXMNCmRhdG9zXzE1NTAwMl9jb21wbGV0byA8LSBsZWZ0X2pvaW4oDQogIGRhdG9zXzE1NTAwMl9jb21wbGV0bywNCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjE1NTAwMiIpLA0KICBieSA9IGMoIlByb2R1Y3RvIiwgIk1vZGVsbyIpDQopDQoNCiMgQWhvcmEgYXNpZ25hIHZhbG9yZXMgcGFyYSBsYXMgbcOpdHJpY2FzIGRlIGxvcyBtb2RlbG9zIGZhbHRhbnRlcw0KIyBWYWxvcmVzIHBhcmEgUmVncmVzacOzbiBMaW5lYWwNCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kUjJbMl0pKSB7DQogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRSMlsyXSA8LSByMl8xNTUwMDINCn0NCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kUk1TRVsyXSkpIHsNCiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvJFJNU0VbMl0gPC0gcm1zZV8xNTUwMDINCn0NCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kTUFQRVsyXSkpIHsNCiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEVbMl0gPC0gbWFwZV8xNTUwMDINCn0NCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kTVNFWzJdKSkgew0KICBkYXRvc18xNTUwMDJfY29tcGxldG8kTVNFWzJdIDwtIG1zZV8xNTUwMDINCn0NCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kQUlDWzJdKSkgew0KICBkYXRvc18xNTUwMDJfY29tcGxldG8kQUlDWzJdIDwtIGFpY18xNTUwMDINCn0NCg0KIyBWYWxvcmVzIHBhcmEgUmFuZG9tIEZvcmVzdA0KIyBTaSB5YSBlamVjdXRhc3RlIGxhIHNlY2Npw7NuIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDINCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kUjJbM10pICYmIGV4aXN0cygicjJfcmYiKSkgew0KICBkYXRvc18xNTUwMDJfY29tcGxldG8kUjJbM10gPC0gcjJfcmYNCn0NCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kUk1TRVszXSkgJiYgZXhpc3RzKCJybXNlX3JmIikpIHsNCiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvJFJNU0VbM10gPC0gcm1zZV9yZg0KfQ0KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMl9jb21wbGV0byRNQVBFWzNdKSAmJiBleGlzdHMoIm1hcGVfcmYiKSkgew0KICBkYXRvc18xNTUwMDJfY29tcGxldG8kTUFQRVszXSA8LSBtYXBlX3JmDQp9DQppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1TRVszXSkgJiYgZXhpc3RzKCJtc2VfcmYiKSkgew0KICBkYXRvc18xNTUwMDJfY29tcGxldG8kTVNFWzNdIDwtIG1zZV9yZg0KfQ0KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMl9jb21wbGV0byRBSUNbM10pICYmIGV4aXN0cygiYWljX3JmIikpIHsNCiAgZGF0b3NfMTU1MDAyX2NvbXBsZXRvJEFJQ1szXSA8LSBhaWNfcmYNCn0NCg0KIyBWYWxvcmVzIHBhcmEgWEdCb29zdA0KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMl9jb21wbGV0byRSMls0XSkgJiYgZXhpc3RzKCJyMl9jb21wbGV0byIpKSB7DQogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRSMls0XSA8LSByMl9jb21wbGV0bw0KfQ0KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMl9jb21wbGV0byRSTVNFWzRdKSAmJiBleGlzdHMoInJtc2VfY29tcGxldG8iKSkgew0KICBkYXRvc18xNTUwMDJfY29tcGxldG8kUk1TRVs0XSA8LSBybXNlX2NvbXBsZXRvDQp9DQppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEVbNF0pICYmIGV4aXN0cygibWFwZV9jb21wbGV0byIpKSB7DQogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRNQVBFWzRdIDwtIG1hcGVfY29tcGxldG8NCn0NCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kTVNFWzRdKSAmJiBleGlzdHMoIm1zZV9jb21wbGV0byIpKSB7DQogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRNU0VbNF0gPC0gbXNlX2NvbXBsZXRvDQp9DQppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJEFJQ1s0XSkgJiYgZXhpc3RzKCJhaWNfY29tcGxldG8iKSkgew0KICBkYXRvc18xNTUwMDJfY29tcGxldG8kQUlDWzRdIDwtIGFpY19jb21wbGV0bw0KfQ0KDQojIFZlciBsb3MgZGF0b3MgY29tcGxldG9zDQpwcmludCgiRGF0b3MgY29tcGxldG9zIHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAyOiIpDQpwcmludChkYXRvc18xNTUwMDJfY29tcGxldG8pDQoNCiMgRGVmaW5pciBjb2xvcmVzIHBhcmEgbG9zIG1vZGVsb3MNCmNvbG9yZXNfbW9kZWxvcyA8LSBjKCJBUk1BL1NBUklNQSIgPSAiIzFmNzdiNCIsIA0KICAgICAgICAgICAgICAgICAgICAgIlJlZ3Jlc2nDs24gTGluZWFsIiA9ICIjZmY3ZjBlIiwgDQogICAgICAgICAgICAgICAgICAgICAiUmFuZG9tIEZvcmVzdCIgPSAiIzJjYTAyYyIsIA0KICAgICAgICAgICAgICAgICAgICAgIlhHQm9vc3QiID0gIiNkNjI3MjgiKQ0KDQojIEdyw6FmaWNvIHBhcmEgUsKyDQpnZ3Bsb3QoZGF0b3NfMTU1MDAyX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IFIyLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKFIyLCAzKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDE1NTAwMiIsDQogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IFLCsiAodmFsb3JlcyBtw6FzIGFsdG9zIGluZGljYW4gbWVqb3IgYWp1c3RlKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiUsKyIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJFIyLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KDQojIEdyw6FmaWNvIHBhcmEgUk1TRQ0KZ2dwbG90KGRhdG9zXzE1NTAwMl9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBSTVNFLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKFJNU0UsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMTU1MDAyIiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogUk1TRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLA0KICAgIHggPSAiIiwNCiAgICB5ID0gIlJNU0UiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18xNTUwMDJfY29tcGxldG8kUk1TRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkNCg0KIyBHcsOhZmljbyBwYXJhIE1BUEUNCmdncGxvdChkYXRvc18xNTUwMDJfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gTUFQRSwgZmlsbCA9IE1vZGVsbykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDE1NTAwMiIsDQogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IE1BUEUgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJNQVBFICglKSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkNCiAgKSArDQogIHlsaW0oMCwgbWF4KGRhdG9zXzE1NTAwMl9jb21wbGV0byRNQVBFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KDQojIEdyw6FmaWNvIHBhcmEgTVNFDQpnZ3Bsb3QoZGF0b3NfMTU1MDAyX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IE1TRSwgZmlsbCA9IE1vZGVsbykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNU0UsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMTU1MDAyIiwNCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogTVNFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiTVNFIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1TRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkNCg0KIyBHcsOhZmljbyBwYXJhIEFJQw0KZ2dwbG90KGRhdG9zXzE1NTAwMl9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBBSUMsIGZpbGwgPSBNb2RlbG8pKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoQUlDLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDE1NTAwMiIsDQogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IEFJQyAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgbW9kZWxvKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiQUlDIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJEFJQywgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkNCmBgYA0KDQojIyBQUk9EVUNUTyAzNjc4MDU1DQpgYGB7cn0NCiMgUHJpbWVybywgdmVhbW9zIHF1w6kgZGF0b3MgdGVuZW1vcyByZWFsbWVudGUNCnByaW50KCJEYXRvcyBhY3R1YWxlcyBwYXJhIGVsIHByb2R1Y3RvIDM2NzgwNTU6IikNCnByaW50KG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIzNjc4MDU1IikpDQoNCiMgQ3JlYXIgdW4gZGF0YWZyYW1lIG1hbnVhbG1lbnRlIGNvbiBsb3MgNCBtb2RlbG9zIHBhcmEgZWwgcHJvZHVjdG8gMzY3ODA1NQ0KZGF0b3NfMzY3ODA1NV9jb21wbGV0byA8LSBkYXRhLmZyYW1lKA0KICBQcm9kdWN0byA9IHJlcCgiMzY3ODA1NSIsIDQpLA0KICBNb2RlbG8gPSBjKCJBUk1BL1NBUklNQSIsICJSZWdyZXNpw7NuIExpbmVhbCIsICJSYW5kb20gRm9yZXN0IiwgIlhHQm9vc3QiKSwNCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFDQopDQoNCiMgVW5pciBjb24gbG9zIGRhdG9zIGV4aXN0ZW50ZXMNCmRhdG9zXzM2NzgwNTVfY29tcGxldG8gPC0gbGVmdF9qb2luKA0KICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvLA0KICBtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMzY3ODA1NSIpLA0KICBieSA9IGMoIlByb2R1Y3RvIiwgIk1vZGVsbyIpDQopDQoNCiMgQWhvcmEgYXNpZ25hIHZhbG9yZXMgcGFyYSBsYXMgbcOpdHJpY2FzIGRlIGxvcyBtb2RlbG9zIGZhbHRhbnRlcw0KIyBWYWxvcmVzIHBhcmEgUmVncmVzacOzbiBMaW5lYWwNCmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFIyWzJdKSkgew0KICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFIyWzJdIDwtIHIyXzM2NzgwNTUNCn0NCmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFJNU0VbMl0pKSB7DQogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kUk1TRVsyXSA8LSBybXNlXzM2NzgwNTUNCn0NCmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEVbMl0pKSB7DQogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTUFQRVsyXSA8LSBtYXBlXzM2NzgwNTUNCn0NCmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1TRVsyXSkpIHsNCiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0byRNU0VbMl0gPC0gbXNlXzM2NzgwNTUNCn0NCmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJEFJQ1syXSkpIHsNCiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0byRBSUNbMl0gPC0gYWljXzM2NzgwNTUNCn0NCg0KIyBWYWxvcmVzIHBhcmEgUmFuZG9tIEZvcmVzdA0KIyBTaSB5YSBlamVjdXRhc3RlIGxhIHNlY2Npw7NuIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBlbCBwcm9kdWN0byAzNjc4MDU1DQppZiAoaXMubmEoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRSMlszXSkgJiYgZXhpc3RzKCJyMl9yZiIpKSB7DQogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kUjJbM10gPC0gcjJfcmYNCn0NCmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFJNU0VbM10pICYmIGV4aXN0cygicm1zZV9yZiIpKSB7DQogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kUk1TRVszXSA8LSBybXNlX3JmDQp9DQppZiAoaXMubmEoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRNQVBFWzNdKSAmJiBleGlzdHMoIm1hcGVfcmYiKSkgew0KICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEVbM10gPC0gbWFwZV9yZg0KfQ0KaWYgKGlzLm5hKGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTVNFWzNdKSAmJiBleGlzdHMoIm1zZV9yZiIpKSB7DQogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTVNFWzNdIDwtIG1zZV9yZg0KfQ0KaWYgKGlzLm5hKGRhdG9zXzM2NzgwNTVfY29tcGxldG8kQUlDWzNdKSAmJiBleGlzdHMoImFpY19yZiIpKSB7DQogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kQUlDWzNdIDwtIGFpY19yZg0KfQ0KDQojIFZhbG9yZXMgcGFyYSBYR0Jvb3N0DQppZiAoaXMubmEoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRSMls0XSkgJiYgZXhpc3RzKCJyMl9jb21wbGV0byIpKSB7DQogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kUjJbNF0gPC0gcjJfY29tcGxldG8NCn0NCmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJFJNU0VbNF0pICYmIGV4aXN0cygicm1zZV9jb21wbGV0byIpKSB7DQogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kUk1TRVs0XSA8LSBybXNlX2NvbXBsZXRvDQp9DQppZiAoaXMubmEoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRNQVBFWzRdKSAmJiBleGlzdHMoIm1hcGVfY29tcGxldG8iKSkgew0KICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEVbNF0gPC0gbWFwZV9jb21wbGV0bw0KfQ0KaWYgKGlzLm5hKGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTVNFWzRdKSAmJiBleGlzdHMoIm1zZV9jb21wbGV0byIpKSB7DQogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTVNFWzRdIDwtIG1zZV9jb21wbGV0bw0KfQ0KaWYgKGlzLm5hKGRhdG9zXzM2NzgwNTVfY29tcGxldG8kQUlDWzRdKSAmJiBleGlzdHMoImFpY19jb21wbGV0byIpKSB7DQogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kQUlDWzRdIDwtIGFpY19jb21wbGV0bw0KfQ0KDQojIFZlciBsb3MgZGF0b3MgY29tcGxldG9zDQpwcmludCgiRGF0b3MgY29tcGxldG9zIHBhcmEgZWwgcHJvZHVjdG8gMzY3ODA1NToiKQ0KcHJpbnQoZGF0b3NfMzY3ODA1NV9jb21wbGV0bykNCg0KIyBEZWZpbmlyIGNvbG9yZXMgcGFyYSBsb3MgbW9kZWxvcw0KY29sb3Jlc19tb2RlbG9zIDwtIGMoIkFSTUEvU0FSSU1BIiA9ICIjMWY3N2I0IiwgDQogICAgICAgICAgICAgICAgICAgICAiUmVncmVzacOzbiBMaW5lYWwiID0gIiNmZjdmMGUiLCANCiAgICAgICAgICAgICAgICAgICAgICJSYW5kb20gRm9yZXN0IiA9ICIjMmNhMDJjIiwgDQogICAgICAgICAgICAgICAgICAgICAiWEdCb29zdCIgPSAiI2Q2MjcyOCIpDQoNCiMgR3LDoWZpY28gcGFyYSBSwrINCmdncGxvdChkYXRvc18zNjc4MDU1X2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IFIyLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKFIyLCAzKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM2NzgwNTUiLA0KICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBSwrIgKHZhbG9yZXMgbcOhcyBhbHRvcyBpbmRpY2FuIG1lam9yIGFqdXN0ZSkiLA0KICAgIHggPSAiIiwNCiAgICB5ID0gIlLCsiINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkNCiAgKSArDQogIHlsaW0oMCwgbWF4KGRhdG9zXzM2NzgwNTVfY29tcGxldG8kUjIsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZDQoNCiMgR3LDoWZpY28gcGFyYSBSTVNFDQpnZ3Bsb3QoZGF0b3NfMzY3ODA1NV9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBSTVNFLCBmaWxsID0gTW9kZWxvKSkgKw0KICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsNCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKFJNU0UsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMzY3ODA1NSIsDQogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IFJNU0UgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwNCiAgICB4ID0gIiIsDQogICAgeSA9ICJSTVNFIg0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpICsNCiAgdGhlbWUoDQogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLA0KICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwNCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksDQogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICApICsNCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRSTVNFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KDQojIEdyw6FmaWNvIHBhcmEgTUFQRQ0KZ2dwbG90KGRhdG9zXzM2NzgwNTVfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gTUFQRSwgZmlsbCA9IE1vZGVsbykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM2NzgwNTUiLA0KICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBNQVBFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsDQogICAgeCA9ICIiLA0KICAgIHkgPSAiTUFQRSAoJSkiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICB0aGVtZSgNCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsDQogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLA0KICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwNCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICkgKw0KICB5bGltKDAsIG1heChkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZDQoNCiMgR3LDoWZpY28gcGFyYSBNU0UNCmdncGxvdChkYXRvc18zNjc4MDU1X2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IE1TRSwgZmlsbCA9IE1vZGVsbykpICsNCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArDQogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNU0UsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArDQogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMzY3ODA1NSIsDQogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IE1TRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLA0KICAgIHggPSAiIiwNCiAgICB5ID0gIk1TRSINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkNCiAgKSArDQogIHlsaW0oMCwgbWF4KGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTVNFLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KDQojIEdyw6FmaWNvIHBhcmEgQUlDDQpnZ3Bsb3QoZGF0b3NfMzY3ODA1NV9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBBSUMsIGZpbGwgPSBNb2RlbG8pKSArDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKw0KICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoQUlDLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKw0KICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM2NzgwNTUiLA0KICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBBSUMgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIG1vZGVsbykiLA0KICAgIHggPSAiIiwNCiAgICB5ID0gIkFJQyINCiAgKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKA0KICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwNCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksDQogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLA0KICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkNCiAgKSArDQogIHlsaW0oMCwgbWF4KGRhdG9zXzM2NzgwNTVfY29tcGxldG8kQUlDLCBuYS5ybSA9IFRSVUUpICogMS4xKSAgIyBBanVzdGFyIGVsIGzDrW1pdGUgWQ0KYGBgDQoNCiMgRVNUSU1BQ0nDk04gREUgUFJFQ0lPUw0KDQojIyMgUHJlcGFyYWNpw7NuIGRlIGRhdG9zDQpgYGB7cn0NCiMgRnVuY2nDs24gcGFyYSBwcmVwYXJhciBkYXRvcyBkZSB1biBwcm9kdWN0bw0KcHJlcGFyZV9wcmljZV9kYXRhIDwtIGZ1bmN0aW9uKGRmLCBwcm9kdWN0X2lkKSB7DQogIHByb2R1Y3RfZGF0YSA8LSBkZiAlPiUNCiAgICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKSAlPiUNCiAgICBhcnJhbmdlKFRyeF9GZWNoYSkgJT4lDQogICAgc2VsZWN0KA0KICAgICAgVHJ4X0ZlY2hhLCBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIENhbnQsIFZlbnRhLCANCiAgICAgIENvc3RvX1ZlbnRhLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgU2VtYW5hLCBNZXMNCiAgICApICU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIERpYV9TZW1hbmEgPSB3ZGF5KFRyeF9GZWNoYSksDQogICAgICBNZXNfTnVtID0gbW9udGgoVHJ4X0ZlY2hhKSwNCiAgICAgIEFuaW8gPSB5ZWFyKFRyeF9GZWNoYSksDQogICAgICBEaWFzX0Rlc2RlX0luaWNpbyA9IGFzLm51bWVyaWMoZGlmZnRpbWUoVHJ4X0ZlY2hhLCBtaW4oVHJ4X0ZlY2hhKSwgdW5pdHMgPSAiZGF5cyIpKSwNCiAgICAgIE1hcmdlbl9Vbml0YXJpbyA9IChWZW50YSAvIENhbnQpIC0gKENvc3RvX1ZlbnRhIC8gQ2FudCksDQogICAgICBQcmVjaW9fVW5pdGFyaW9fQ2FsYyA9IFZlbnRhIC8gQ2FudCwNCiAgICAgIElEX0ludmVudGFyaW8gPSBwcm9kdWN0X2lkDQogICAgKQ0KICANCiAgcmV0dXJuKHByb2R1Y3RfZGF0YSkNCn0NCg0KIyBBc2Vnw7pyYXRlIGRlIHF1ZSAnZGF0b3MnIHNlYSB0dSBkYXRhLmZyYW1lIGNhcmdhZG8gY29ycmVjdGFtZW50ZQ0KIyBQb3IgZWplbXBsbywgc2kgdmllbmVzIGRlIHVuIGFyY2hpdm8gLmNzdjoNCiMgZGF0b3MgPC0gcmVhZC5jc3YoImFyY2hpdm8uY3N2IikNCg0KIyBBcGxpY2FyIGxhIGZ1bmNpw7NuIGEgdG9kb3MgbG9zIHByb2R1Y3Rvcw0KaWRzIDwtIHVuaXF1ZShkYXRvcyRJRF9JbnZlbnRhcmlvKQ0KDQpwcm9kdWN0b3NfcHJlcGFyYWRvcyA8LSBtYXBfZGYoaWRzLCBmdW5jdGlvbihpZCkgew0KICBwcmVwYXJlX3ByaWNlX2RhdGEoZGF0b3MsIGlkKQ0KfSkNCg0KIyBNb3N0cmFyIHVuYSBwYXJ0ZSBkZWwgcmVzdWx0YWRvDQpoZWFkKHByb2R1Y3Rvc19wcmVwYXJhZG9zKQ0KYGBgDQoNCg0KDQoNCmBgYHtyfQ0KIyBWZWN0b3IgY29uIHByb2R1Y3RvcyAoZGViZSBpciBwcmltZXJvKQ0KcHJvZHVjdG9zX2lkcyA8LSB0b3BfaWRzDQoNCiMgRnVuY2nDs24gcGFyYSBlbnRyZW5hciBtb2RlbG8gQVJNQSBwb3IgcHJvZHVjdG8NCnRyYWluX2FybWFfbW9kZWwgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCkgew0KICBsaWJyYXJ5KGZvcmVjYXN0KSAgIyBBc2Vnw7pyYXRlIGRlIGNhcmdhciBmb3JlY2FzdCBzaSBubyBlc3TDoSBjYXJnYWRvIGHDum4NCiAgcHJvZHVjdF9kYXRhIDwtIGRhdGEgJT4lIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2R1Y3RfaWQpDQogIHNlcmllX3RzIDwtIHRzKHByb2R1Y3RfZGF0YSRWZW50YSwgZnJlcXVlbmN5ID0gMTIpDQogIG1vZGVsb19hcm1hIDwtIGF1dG8uYXJpbWEoc2VyaWVfdHMsIHNlYXNvbmFsID0gRkFMU0UsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkNCiAgcmV0dXJuKG1vZGVsb19hcm1hKQ0KfQ0KDQojIENyZWFyIGxpc3RhIGRlIG1vZGVsb3MgQVJNQSBwb3IgcHJvZHVjdG8NCm1vZGVsb3NfYXJtYV9saXN0YSA8LSBzZXROYW1lcygNCiAgbGFwcGx5KHByb2R1Y3Rvc19pZHMsIGZ1bmN0aW9uKGlkKSB0cmFpbl9hcm1hX21vZGVsKGRhdG9zLCBpZCkpLA0KICBhcy5jaGFyYWN0ZXIocHJvZHVjdG9zX2lkcykNCikNCg0KIyBGdW5jacOzbiBwYXJhIG1vZGVsbyByZWdyZXNpw7NuIGxpbmVhbA0KdHJhaW5fcmVnX21vZGVsIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQpIHsNCiAgcHJvZHVjdF9kYXRhIDwtIGRhdGEgJT4lIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2R1Y3RfaWQpDQogIG1vZGVsb19yZWcgPC0gbG0oVmVudGEgfiBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIGRhdGEgPSBwcm9kdWN0X2RhdGEpDQogIHJldHVybihtb2RlbG9fcmVnKQ0KfQ0KDQojIEZ1bmNpw7NuIHBhcmEgbW9kZWxvIFJhbmRvbSBGb3Jlc3QNCnRyYWluX3JmX21vZGVsIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQpIHsNCiAgcHJvZHVjdF9kYXRhIDwtIGRhdGEgJT4lIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2R1Y3RfaWQpDQogIHByZWRpY3RvcnMgPC0gYygiUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIiwgIkNhbnQiLCAiRGVzY3VlbnRvX1BvcmNlbnRhamUiKQ0KICByZl9kYXRhIDwtIHByb2R1Y3RfZGF0YSAlPiUgc2VsZWN0KGFsbF9vZihwcmVkaWN0b3JzKSwgVmVudGEpDQogIG1vZGVsb19yZiA8LSByYW5kb21Gb3Jlc3QoVmVudGEgfiAuLCBkYXRhID0gcmZfZGF0YSwgbnRyZWUgPSAxMDApDQogIHJldHVybihtb2RlbG9fcmYpDQp9DQoNCiMgRnVuY2nDs24gcGFyYSBtb2RlbG8gWEdCb29zdA0KdHJhaW5feGdiX21vZGVsIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQpIHsNCiAgcHJvZHVjdF9kYXRhIDwtIGRhdGEgJT4lIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2R1Y3RfaWQpDQogIHByZWRpY3RvcnMgPC0gYygiUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIiwgIkNhbnQiLCAiRGVzY3VlbnRvX1BvcmNlbnRhamUiKQ0KICB0cmFpbl9tYXRyaXggPC0geGdiLkRNYXRyaXgoZGF0YSA9IGFzLm1hdHJpeChwcm9kdWN0X2RhdGFbLCBwcmVkaWN0b3JzXSksIGxhYmVsID0gcHJvZHVjdF9kYXRhJFZlbnRhKQ0KICBwYXJhbXMgPC0gbGlzdChvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIpDQogIG1vZGVsb194Z2IgPC0geGdiLnRyYWluKHBhcmFtcyA9IHBhcmFtcywgZGF0YSA9IHRyYWluX21hdHJpeCwgbnJvdW5kcyA9IDUwLCB2ZXJib3NlID0gMCkNCiAgcmV0dXJuKG1vZGVsb194Z2IpDQp9DQoNCiMgQ3JlYXIgbGlzdGFzIGRlIG1vZGVsb3MNCm1vZGVsb3NfcmVnX2xpc3RhIDwtIHNldE5hbWVzKGxhcHBseShwcm9kdWN0b3NfaWRzLCBmdW5jdGlvbihpZCkgdHJhaW5fcmVnX21vZGVsKGRhdG9zLCBpZCkpLCBhcy5jaGFyYWN0ZXIocHJvZHVjdG9zX2lkcykpDQptb2RlbG9zX3JmX2xpc3RhIDwtIHNldE5hbWVzKGxhcHBseShwcm9kdWN0b3NfaWRzLCBmdW5jdGlvbihpZCkgdHJhaW5fcmZfbW9kZWwoZGF0b3MsIGlkKSksIGFzLmNoYXJhY3Rlcihwcm9kdWN0b3NfaWRzKSkNCm1vZGVsb3NfeGdiX2xpc3RhIDwtIHNldE5hbWVzKGxhcHBseShwcm9kdWN0b3NfaWRzLCBmdW5jdGlvbihpZCkgdHJhaW5feGdiX21vZGVsKGRhdG9zLCBpZCkpLCBhcy5jaGFyYWN0ZXIocHJvZHVjdG9zX2lkcykpDQpgYGANCg0KIyMjIEVudHJlbmFyIG1vZGVsb3MgZGUgcHJlZGljY2nDs24gZGUgcHJlY2lvcw0KYGBge3J9DQojIEZ1bmNpw7NuIHBhcmEgZW50cmVuYXIgbW9kZWxvcyBkZSBwcmVkaWNjacOzbiBkZSBwcmVjaW9zDQp0cmFpbl9wcmljZV9tb2RlbHMgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCwgdGVzdF9zaXplID0gMC4yKSB7DQogIHByaWNlX2RhdGEgPC0gcHJlcGFyZV9wcmljZV9kYXRhKGRhdGEsIHByb2R1Y3RfaWQpICU+JQ0KICAgIGRyb3BfbmEoKSAlPiUNCiAgICBzZWxlY3QoDQogICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sDQogICAgICBDYW50LCBDb3N0b19WZW50YSwgRGVzY3VlbnRvX1BvcmNlbnRhamUsDQogICAgICBEaWFfU2VtYW5hLCBNZXNfTnVtLCBBbmlvLCBEaWFzX0Rlc2RlX0luaWNpbywNCiAgICAgIE1hcmdlbl9Vbml0YXJpbw0KICAgICkNCg0KICAjIEV2aXRhciBmYWxsb3Mgc2kgaGF5IG11eSBwb2NvcyBkYXRvcw0KICBpZiAobnJvdyhwcmljZV9kYXRhKSA8IDEwKSB7DQogICAgd2FybmluZyhwYXN0ZSgiUHJvZHVjdG8iLCBwcm9kdWN0X2lkLCAidGllbmUgbWVub3MgZGUgMTAgcmVnaXN0cm9zLiBTZSBvbWl0ZS4iKSkNCiAgICByZXR1cm4oTlVMTCkNCiAgfQ0KDQogIHNldC5zZWVkKDEyMykNCiAgdHJhaW5faW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihwcmljZV9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbywgcCA9IDEgLSB0ZXN0X3NpemUsIGxpc3QgPSBGQUxTRSkNCiAgdHJhaW5fZGF0YSA8LSBwcmljZV9kYXRhW3RyYWluX2luZGV4LCBdDQogIHRlc3RfZGF0YSA8LSBwcmljZV9kYXRhWy10cmFpbl9pbmRleCwgXQ0KDQogICMgMS4gUmVncmVzacOzbiBMaW5lYWwNCiAgbG1fbW9kZWwgPC0gbG0oUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIH4gLiwgZGF0YSA9IHRyYWluX2RhdGEpDQoNCiAgIyAyLiBSYW5kb20gRm9yZXN0DQogIHJmX21vZGVsIDwtIHJhbmRvbUZvcmVzdCgNCiAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gfiAuLA0KICAgIGRhdGEgPSB0cmFpbl9kYXRhLA0KICAgIG50cmVlID0gNTAwLA0KICAgIGltcG9ydGFuY2UgPSBUUlVFDQogICkNCg0KICAjIDMuIFhHQm9vc3QNCiAgZmVhdHVyZXMgPC0gc2V0ZGlmZihuYW1lcyh0cmFpbl9kYXRhKSwgIlByZWNpb19GaW5hbF9Vbml0YXJpbyIpDQogIHhfdHJhaW4gPC0gYXMubWF0cml4KHRyYWluX2RhdGFbLCBmZWF0dXJlc10pDQogIHlfdHJhaW4gPC0gdHJhaW5fZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8NCiAgeF90ZXN0IDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGFbLCBmZWF0dXJlc10pDQogIHlfdGVzdCA8LSB0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvDQogIGR0cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0geF90cmFpbiwgbGFiZWwgPSB5X3RyYWluKQ0KICBkdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0geF90ZXN0LCBsYWJlbCA9IHlfdGVzdCkNCg0KICB4Z2JfcGFyYW1zIDwtIGxpc3QoDQogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLA0KICAgIGV2YWxfbWV0cmljID0gInJtc2UiLA0KICAgIGV0YSA9IDAuMSwNCiAgICBtYXhfZGVwdGggPSA2LA0KICAgIG1pbl9jaGlsZF93ZWlnaHQgPSAzLA0KICAgIHN1YnNhbXBsZSA9IDAuOCwNCiAgICBjb2xzYW1wbGVfYnl0cmVlID0gMC44DQogICkNCg0KICB4Z2JfbW9kZWwgPC0geGdiLnRyYWluKA0KICAgIHBhcmFtcyA9IHhnYl9wYXJhbXMsDQogICAgZGF0YSA9IGR0cmFpbiwNCiAgICBucm91bmRzID0gMTAwLA0KICAgIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW4sIHRlc3QgPSBkdGVzdCksDQogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAsDQogICAgdmVyYm9zZSA9IDANCiAgKQ0KDQogICMgRXZhbHVhY2nDs24NCiAgbG1fcHJlZCA8LSBwcmVkaWN0KGxtX21vZGVsLCBuZXdkYXRhID0gdGVzdF9kYXRhKQ0KICByZl9wcmVkIDwtIHByZWRpY3QocmZfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpDQogIHhnYl9wcmVkIDwtIHByZWRpY3QoeGdiX21vZGVsLCB4X3Rlc3QpDQoNCiAgbG1fcm1zZSA8LSBzcXJ0KG1lYW4oKGxtX3ByZWQgLSB0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKV4yKSkNCiAgcmZfcm1zZSA8LSBzcXJ0KG1lYW4oKHJmX3ByZWQgLSB0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKV4yKSkNCiAgeGdiX3Jtc2UgPC0gc3FydChtZWFuKCh4Z2JfcHJlZCAtIHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8pXjIpKQ0KDQogIGxtX3IyIDwtIDEgLSBzdW0oKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8gLSBsbV9wcmVkKV4yKSAvDQogICAgc3VtKCh0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIC0gbWVhbih0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKSleMikNCiAgcmZfcjIgPC0gMSAtIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIHJmX3ByZWQpXjIpIC8NCiAgICBzdW0oKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8gLSBtZWFuKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8pKV4yKQ0KICB4Z2JfcjIgPC0gMSAtIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIHhnYl9wcmVkKV4yKSAvDQogICAgc3VtKCh0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIC0gbWVhbih0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKSleMikNCg0KICBtZXRyaWNzIDwtIGRhdGEuZnJhbWUoDQogICAgTW9kZWwgPSBjKCJMaW5lYXIgUmVncmVzc2lvbiIsICJSYW5kb20gRm9yZXN0IiwgIlhHQm9vc3QiKSwNCiAgICBSTVNFID0gYyhsbV9ybXNlLCByZl9ybXNlLCB4Z2Jfcm1zZSksDQogICAgUjIgPSBjKGxtX3IyLCByZl9yMiwgeGdiX3IyKQ0KICApDQoNCiAgcmV0dXJuKGxpc3QobWV0cmljcyA9IG1ldHJpY3MpKQ0KfQ0KDQojIElEcyBkZSBsb3MgNSBwcm9kdWN0b3MgYSBtb2RlbGFyDQpwcm9kdWN0b3NfaWRzIDwtIGMoMTU1MDAxLCAzOTI5Nzg4LCAzOTA0MTUyLCAxNTUwMDIsIDM2NzgwNTUpDQoNCiMgQXBsaWNhciBtb2RlbG8gYSBjYWRhIHByb2R1Y3RvDQpyZXN1bHRhZG9zX21vZGVsb3MgPC0gbWFwKHByb2R1Y3Rvc19pZHMsIGZ1bmN0aW9uKGlkKSB7DQogIHJlc3VsdGFkbyA8LSB0cmFpbl9wcmljZV9tb2RlbHMoZGF0b3MsIHByb2R1Y3RfaWQgPSBpZCkNCiAgaWYgKCFpcy5udWxsKHJlc3VsdGFkbykpIHsNCiAgICByZXN1bHRhZG8kbWV0cmljcyAlPiUgbXV0YXRlKElEX0ludmVudGFyaW8gPSBpZCkNCiAgfSBlbHNlIHsNCiAgICBOVUxMDQogIH0NCn0pICU+JSBjb21wYWN0KCkgJT4lIGJpbmRfcm93cygpDQoNCiMgTW9zdHJhciByZXN1bHRhZG9zDQpyZXN1bHRhZG9zX21vZGVsb3MNCmBgYA0KDQpgYGB7cn0NCiMgTGlzdGEgY29uIGxvcyBJRHMgZGUgcHJvZHVjdG9zIChwdWVkZXMgdXNhciB0b3BfaWRzIHF1ZSB5YSBkZWZpbmlzdGUpDQpwcm9kdWN0b3NfaWRzIDwtIHRvcF9pZHMNCg0KIyBFbnRyZW5hciBtb2RlbG9zIHBhcmEgY2FkYSBwcm9kdWN0byB5IGd1YXJkYXIgZW4gbGlzdGENCm1vZGVsb3NfcHJlY2lvX2xpc3RhIDwtIHNldE5hbWVzKA0KICBsYXBwbHkocHJvZHVjdG9zX2lkcywgZnVuY3Rpb24oaWQpIHRyYWluX3ByaWNlX21vZGVscyhkYXRvcywgaWQpKSwNCiAgYXMuY2hhcmFjdGVyKHByb2R1Y3Rvc19pZHMpDQopDQpgYGANCg0KIyMjIEVzdGltYXIgcHJlY2lvcyDDs3B0aW1vcw0KYGBge3J9DQplc3RpbWF0ZV9vcHRpbWFsX3ByaWNlcyA8LSBmdW5jdGlvbihkYXRhLCBwcm9kdWN0X2lkLCBwcmljZV9tb2RlbHMsIGRlbWFuZF9tb2RlbHMgPSBOVUxMLCBmdXR1cmVfZGF0ZXMgPSBOVUxMKSB7DQogIHByaWNlX3N0ZXBzIDwtIDIwDQogIA0KICBiZXN0X3ByaWNlX21vZGVsX2lkeCA8LSB3aGljaC5tYXgocHJpY2VfbW9kZWxzJG1ldHJpY3MkUjIpDQogIGJlc3RfcHJpY2VfbW9kZWxfbmFtZSA8LSBwcmljZV9tb2RlbHMkbWV0cmljcyRNb2RlbFtiZXN0X3ByaWNlX21vZGVsX2lkeF0NCiAgDQogIHByb2R1Y3RfZGF0YSA8LSBkYXRhICU+JSBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKQ0KICBtaW5fcHJpY2UgPC0gbWluKHByb2R1Y3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSkNCiAgbWF4X3ByaWNlIDwtIG1heChwcm9kdWN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBuYS5ybSA9IFRSVUUpDQogIHByaWNlX3JhbmdlIDwtIHNlcShtaW5fcHJpY2UsIG1heF9wcmljZSwgbGVuZ3RoLm91dCA9IHByaWNlX3N0ZXBzKQ0KICANCiAgZnV0dXJlX3NjZW5hcmlvcyA8LSBkYXRhLmZyYW1lKCkNCiAgDQogIGZvciAoZnV0dXJlX2RhdGUgaW4gZnV0dXJlX2RhdGVzKSB7DQogICAgZnV0dXJlX2RhdGUgPC0gYXMuRGF0ZShmdXR1cmVfZGF0ZSkNCiAgICBtZXNfYWN0dWFsIDwtIGx1YnJpZGF0ZTo6bW9udGgoZnV0dXJlX2RhdGUpDQogICAgDQogICAgbWVzX2RhdGEgPC0gcHJvZHVjdF9kYXRhICU+JSBmaWx0ZXIobHVicmlkYXRlOjptb250aChUcnhfRmVjaGEpID09IG1lc19hY3R1YWwpDQogICAgaWYgKG5yb3cobWVzX2RhdGEpIDwgNSkgbWVzX2RhdGEgPC0gcHJvZHVjdF9kYXRhDQogICAgDQogICAgY29zdG9fbWVzIDwtIG1lZGlhbihtZXNfZGF0YSRDb3N0b19WZW50YSwgbmEucm0gPSBUUlVFKQ0KICAgIGNhbnRfbWVzIDwtIG1lZGlhbihtZXNfZGF0YSRDYW50LCBuYS5ybSA9IFRSVUUpDQogICAgZGVzY19tZXMgPC0gbWVkaWFuKG1lc19kYXRhJERlc2N1ZW50b19Qb3JjZW50YWplLCBuYS5ybSA9IFRSVUUpDQogICAgDQogICAgaWYgKGlzLm5hKGNvc3RvX21lcykpIGNvc3RvX21lcyA8LSBtZWRpYW4ocHJvZHVjdF9kYXRhJENvc3RvX1ZlbnRhLCBuYS5ybSA9IFRSVUUpDQogICAgaWYgKGlzLm5hKGNhbnRfbWVzKSB8fCBjYW50X21lcyA9PSAwKSBjYW50X21lcyA8LSBtZWRpYW4ocHJvZHVjdF9kYXRhJENhbnQsIG5hLnJtID0gVFJVRSkNCiAgICBpZiAoaXMubmEoZGVzY19tZXMpKSBkZXNjX21lcyA8LSBtZWRpYW4ocHJvZHVjdF9kYXRhJERlc2N1ZW50b19Qb3JjZW50YWplLCBuYS5ybSA9IFRSVUUpDQogICAgDQogICAgZGF0ZV9kZiA8LSBkYXRhLmZyYW1lKA0KICAgICAgVHJ4X0ZlY2hhID0gcmVwKGZ1dHVyZV9kYXRlLCBwcmljZV9zdGVwcyksDQogICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gPSBwcmljZV9yYW5nZSwNCiAgICAgIENhbnQgPSBjYW50X21lcywNCiAgICAgIENvc3RvX1ZlbnRhID0gY29zdG9fbWVzLA0KICAgICAgRGVzY3VlbnRvX1BvcmNlbnRhamUgPSBkZXNjX21lcywNCiAgICAgIERpYV9TZW1hbmEgPSBsdWJyaWRhdGU6OndkYXkoZnV0dXJlX2RhdGUpLA0KICAgICAgTWVzX051bSA9IG1lc19hY3R1YWwsDQogICAgICBBbmlvID0gbHVicmlkYXRlOjp5ZWFyKGZ1dHVyZV9kYXRlKSwNCiAgICAgIERpYXNfRGVzZGVfSW5pY2lvID0gYXMubnVtZXJpYyhkaWZmdGltZShmdXR1cmVfZGF0ZSwgbWluKHByb2R1Y3RfZGF0YSRUcnhfRmVjaGEpLCB1bml0cyA9ICJkYXlzIikpLA0KICAgICAgTWFyZ2VuX1VuaXRhcmlvID0gTkENCiAgICApDQogICAgDQogICAgZnV0dXJlX3NjZW5hcmlvcyA8LSByYmluZChmdXR1cmVfc2NlbmFyaW9zLCBkYXRlX2RmKQ0KICB9DQogIA0KICBmdXR1cmVfc2NlbmFyaW9zJE1hcmdlbl9Vbml0YXJpbyA8LSBmdXR1cmVfc2NlbmFyaW9zJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtICANCiAgICAoZnV0dXJlX3NjZW5hcmlvcyRDb3N0b19WZW50YSAvIGZ1dHVyZV9zY2VuYXJpb3MkQ2FudCkNCiAgDQogIHByb2R1Y3RfZGF0YSA8LSBwcm9kdWN0X2RhdGEgJT4lIGFycmFuZ2UoVHJ4X0ZlY2hhKQ0KICBlbGFzdGljaXR5X2RmIDwtIHByb2R1Y3RfZGF0YSAlPiUNCiAgICBmaWx0ZXIoIWlzLm5hKENhbnQpICYgIWlzLm5hKFByZWNpb19GaW5hbF9Vbml0YXJpbykpICU+JQ0KICAgIG11dGF0ZSgNCiAgICAgIFBfbGFnID0gbGFnKFByZWNpb19GaW5hbF9Vbml0YXJpbyksDQogICAgICBRX2xhZyA9IGxhZyhDYW50KSwNCiAgICAgIGRQID0gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIC0gUF9sYWcsDQogICAgICBkUSA9IENhbnQgLSBRX2xhZywNCiAgICAgIGVsYXN0aWNpdHlfcG9pbnQgPSAoZFEgLyBRX2xhZykgLyAoZFAgLyBQX2xhZykNCiAgICApICU+JQ0KICAgIGZpbHRlcighaXMubmEoZWxhc3RpY2l0eV9wb2ludCksIGlzLmZpbml0ZShlbGFzdGljaXR5X3BvaW50KSkNCiAgDQogIGVsYXN0aWNpdHkgPC0gbWVkaWFuKGVsYXN0aWNpdHlfZGYkZWxhc3RpY2l0eV9wb2ludCwgbmEucm0gPSBUUlVFKQ0KICBpZiAoaXMubmEoZWxhc3RpY2l0eSkgfHwgIWlzLmZpbml0ZShlbGFzdGljaXR5KSkgZWxhc3RpY2l0eSA8LSAxLjUNCiAgDQogIHJlc3VsdHMgPC0gZnV0dXJlX3NjZW5hcmlvcyAlPiUNCiAgICBtdXRhdGUoVmVudGFfRXNwZXJhZGEgPSAwLCBNYXJnZW5fVG90YWwgPSAwKQ0KICANCiAgZm9yIChpIGluIDE6bnJvdyhyZXN1bHRzKSkgew0KICAgIGJhc2VsaW5lX3ByaWNlIDwtIG1lZGlhbihwcm9kdWN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBuYS5ybSA9IFRSVUUpDQogICAgcHJpY2VfcmF0aW8gPC0gYmFzZWxpbmVfcHJpY2UgLyByZXN1bHRzJFByZWNpb19GaW5hbF9Vbml0YXJpb1tpXQ0KICAgIA0KICAgIGFkanVzdGVkX3F1YW50aXR5IDwtIHJlc3VsdHMkQ2FudFtpXSAqIChwcmljZV9yYXRpbyBeIGVsYXN0aWNpdHkpDQogICAgcmVzdWx0cyRWZW50YV9Fc3BlcmFkYVtpXSA8LSByZXN1bHRzJFByZWNpb19GaW5hbF9Vbml0YXJpb1tpXSAqIGFkanVzdGVkX3F1YW50aXR5DQogICAgcmVzdWx0cyRNYXJnZW5fVG90YWxbaV0gPC0gYWRqdXN0ZWRfcXVhbnRpdHkgKiByZXN1bHRzJE1hcmdlbl9Vbml0YXJpb1tpXQ0KICB9DQogIA0KICBvcHRpbWFsX3ByaWNlcyA8LSByZXN1bHRzICU+JQ0KICAgIGdyb3VwX2J5KFRyeF9GZWNoYSkgJT4lDQogICAgc2xpY2VfbWF4KFZlbnRhX0VzcGVyYWRhLCBuID0gMSkgJT4lDQogICAgc2VsZWN0KFRyeF9GZWNoYSwgUHJlY2lvX09wdGltYWwgPSBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIFZlbnRhX0VzcGVyYWRhLCBNYXJnZW5fVG90YWwpDQogIA0KICByZXR1cm4obGlzdCgNCiAgICByZXN1bHRhZG9zID0gcmVzdWx0cywNCiAgICBwcmVjaW9zX29wdGltb3MgPSBvcHRpbWFsX3ByaWNlcywNCiAgICBlbGFzdGljaWRhZCA9IGVsYXN0aWNpdHkNCiAgKSkNCn0NCmBgYA0KDQojIyMgVmlzdWFsaXphciByZXN1bHRhZG9zDQpgYGB7cn0NCmRhdGVzX2Z1dHVyZSA8LSBzZXEuRGF0ZShhcy5EYXRlKCIyMDI1LTAxLTAxIiksIGJ5ID0gIm1vbnRoIiwgbGVuZ3RoLm91dCA9IDYpDQpwcmVjaW9zX29wdGltb3NfbGlzdGEgPC0gbGlzdCgpDQoNCmZvciAoaWQgaW4gcHJvZHVjdG9zX2lkcykgew0KICBjYXQoIkVzdGltYW5kbyBwcmVjaW9zIMOzcHRpbW9zIHBhcmEgcHJvZHVjdG86IiwgaWQsICJcbiIpDQogIG1vZGVsb19wcmVjaW8gPC0gbW9kZWxvc19wcmVjaW9fbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXQ0KDQogIGlmICghaXMubnVsbChtb2RlbG9fcHJlY2lvKSkgew0KICAgIHByZWNpb3Nfb3B0aW1vc19saXN0YVtbYXMuY2hhcmFjdGVyKGlkKV1dIDwtIGVzdGltYXRlX29wdGltYWxfcHJpY2VzKA0KICAgICAgZGF0YSA9IGRhdG9zLA0KICAgICAgcHJvZHVjdF9pZCA9IGlkLA0KICAgICAgcHJpY2VfbW9kZWxzID0gbW9kZWxvX3ByZWNpbywNCiAgICAgIGZ1dHVyZV9kYXRlcyA9IGRhdGVzX2Z1dHVyZQ0KICAgICkNCiAgfQ0KfQ0KYGBgDQoNCmBgYHtyfQ0KZ3JhZmljYXNfaW5kaXZpZHVhbGVzIDwtIGxpc3QoKQ0KDQpmb3IgKGlkIGluIG5hbWVzKHByZWNpb3Nfb3B0aW1vc19saXN0YSkpIHsNCiAgZGZfb3B0aW1vIDwtIHByZWNpb3Nfb3B0aW1vc19saXN0YVtbaWRdXSRwcmVjaW9zX29wdGltb3MNCg0KICBwIDwtIGdncGxvdChkZl9vcHRpbW8sIGFlcyh4ID0gVHJ4X0ZlY2hhLCB5ID0gUHJlY2lvX09wdGltYWwpKSArDQogICAgZ2VvbV9saW5lKGNvbG9yID0gIiMxZjc3YjQiLCBsaW5ld2lkdGggPSAxLjIpICsNCiAgICBnZW9tX3BvaW50KGNvbG9yID0gIiMxZjc3YjQiLCBzaXplID0gMikgKw0KICAgIGxhYnMoDQogICAgICB0aXRsZSA9IHBhc3RlKCJQcmVjaW8gw5NwdGltbyBwb3IgTWVzIC0gUHJvZHVjdG8iLCBpZCksDQogICAgICB4ID0gIkZlY2hhIiwNCiAgICAgIHkgPSAiUHJlY2lvIMOTcHRpbW8iDQogICAgKSArDQogICAgc2NhbGVfeF9kYXRlKGRhdGVfbGFiZWxzID0gIiViICVZIiwgZGF0ZV9icmVha3MgPSAiMSBtb250aCIpICsNCiAgICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDEyKSArDQogICAgdGhlbWUoDQogICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQ0KICAgICkNCg0KICBncmFmaWNhc19pbmRpdmlkdWFsZXNbW2lkXV0gPC0gcA0KfQ0KDQpmb3IgKGlkIGluIG5hbWVzKGdyYWZpY2FzX2luZGl2aWR1YWxlcykpIHsNCiAgcHJpbnQoZ3JhZmljYXNfaW5kaXZpZHVhbGVzW1tpZF1dKQ0KfQ0KYGBgDQoNCiMjIyBJbnRlZ3JhY2nDs24gZGUgcHJlY2lvcyDDs3B0aW1vcyB5IG1vZGVsb3MNCmBgYHtyfQ0KaW50ZWdyYXRlX3dpdGhfZXhpc3RpbmdfbW9kZWxzIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQsIHByaWNlX29wdF9yZXN1bHRzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcm1hX21vZGVsID0gTlVMTCwgcmVnX21vZGVsID0gTlVMTCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmZfbW9kZWwgPSBOVUxMLCB4Z2JfbW9kZWwgPSBOVUxMKSB7DQogIG9wdGltYWxfcHJpY2VzIDwtIHByaWNlX29wdF9yZXN1bHRzW1thcy5jaGFyYWN0ZXIocHJvZHVjdF9pZCldXSRwcmVjaW9zX29wdGltb3MNCiAgDQogIGlmIChpcy5udWxsKG9wdGltYWxfcHJpY2VzKSB8fCBucm93KG9wdGltYWxfcHJpY2VzKSA9PSAwKSB7DQogICAgd2FybmluZyhwYXN0ZSgiTm8gc2UgZW5jb250cmFyb24gcHJlY2lvcyDDs3B0aW1vcyBwYXJhIGVsIHByb2R1Y3RvIiwgcHJvZHVjdF9pZCkpDQogICAgcmV0dXJuKGRhdGEuZnJhbWUoKSkNCiAgfQ0KICANCiAgZnV0dXJlX2RhdGEgPC0gZGF0YS5mcmFtZSgNCiAgICBGZWNoYSA9IG9wdGltYWxfcHJpY2VzJFRyeF9GZWNoYSwNCiAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gPSBvcHRpbWFsX3ByaWNlcyRQcmVjaW9fT3B0aW1hbA0KICApDQogIA0KICBoaXN0X2RhdGEgPC0gZGF0YSAlPiUgDQogICAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkgJT4lDQogICAgYXJyYW5nZShUcnhfRmVjaGEpDQogIA0KICBmdXR1cmVfZmVhdHVyZXMgPC0gZGF0YS5mcmFtZSgpDQogIA0KICBmb3IgKGkgaW4gMTpucm93KG9wdGltYWxfcHJpY2VzKSkgew0KICAgIGZ1dHVyZV9kYXRlIDwtIG9wdGltYWxfcHJpY2VzJFRyeF9GZWNoYVtpXQ0KICAgIGZ1dHVyZV9wcmljZSA8LSBvcHRpbWFsX3ByaWNlcyRQcmVjaW9fT3B0aW1hbFtpXQ0KICAgIA0KICAgIG1lc19kYXRhIDwtIGhpc3RfZGF0YSAlPiUgZmlsdGVyKGx1YnJpZGF0ZTo6bW9udGgoVHJ4X0ZlY2hhKSA9PSBsdWJyaWRhdGU6Om1vbnRoKGZ1dHVyZV9kYXRlKSkNCiAgICBpZiAobnJvdyhtZXNfZGF0YSkgPCA1KSBtZXNfZGF0YSA8LSBoaXN0X2RhdGENCiAgICANCiAgICBhdmdfZmVhdHVyZXMgPC0gbWVzX2RhdGEgJT4lIA0KICAgICAgc3VtbWFyaXNlKA0KICAgICAgICBDYW50ID0gbWVkaWFuKENhbnQsIG5hLnJtID0gVFJVRSksDQogICAgICAgIENvc3RvX1ZlbnRhID0gbWVkaWFuKENvc3RvX1ZlbnRhLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICBDb3N0b19EZXZvbHVjaW9uID0gbWVkaWFuKENvc3RvX0Rldm9sdWNpb24sIG5hLnJtID0gVFJVRSksDQogICAgICAgIEFqdXNfU2lzdGVtYSA9IG1lZGlhbihBanVzX1Npc3RlbWEsIG5hLnJtID0gVFJVRSksDQogICAgICAgIEFqdXN0ZV9NYW51YWwgPSBtZWRpYW4oQWp1c3RlX01hbnVhbCwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgUHJlY2lvX0xpc3RhX1VuaXRhcmlvID0gbWVkaWFuKFByZWNpb19MaXN0YV9Vbml0YXJpbywgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgRGVzY3VlbnRvX1BvcmNlbnRhamUgPSBtZWRpYW4oRGVzY3VlbnRvX1BvcmNlbnRhamUsIG5hLnJtID0gVFJVRSksDQogICAgICAgIFNlbWFuYSA9IGx1YnJpZGF0ZTo6d2VlayhmdXR1cmVfZGF0ZSksDQogICAgICAgIE1lcyA9IGx1YnJpZGF0ZTo6bW9udGgoZnV0dXJlX2RhdGUpLA0KICAgICAgICBEaWFfU2VtYW5hID0gbHVicmlkYXRlOjp3ZGF5KGZ1dHVyZV9kYXRlKSwNCiAgICAgICAgVGllbXBvID0gYXMubnVtZXJpYyhkaWZmdGltZShmdXR1cmVfZGF0ZSwgbWluKGhpc3RfZGF0YSRUcnhfRmVjaGEpLCB1bml0cyA9ICJkYXlzIikpIC8gMzANCiAgICAgICkNCiAgICANCiAgICBhdmdfZmVhdHVyZXMkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIDwtIGZ1dHVyZV9wcmljZQ0KICAgIGF2Z19mZWF0dXJlcyRUcnhfRmVjaGEgPC0gZnV0dXJlX2RhdGUNCiAgICANCiAgICBmdXR1cmVfZmVhdHVyZXMgPC0gcmJpbmQoZnV0dXJlX2ZlYXR1cmVzLCBhdmdfZmVhdHVyZXMpDQogIH0NCiAgDQogIGlmICghaXMubnVsbChhcm1hX21vZGVsKSkgew0KICAgIGFybWFfZm9yZWNhc3QgPC0gZm9yZWNhc3QoYXJtYV9tb2RlbCwgaCA9IG5yb3cob3B0aW1hbF9wcmljZXMpKQ0KICAgIGZ1dHVyZV9kYXRhJFZlbnRhX0FSTUEgPC0gYXMubnVtZXJpYyhhcm1hX2ZvcmVjYXN0JG1lYW4pDQogICAgcmVmX3ByaWNlIDwtIG1lZGlhbihoaXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBuYS5ybSA9IFRSVUUpDQogICAgZWxhc3RpY2l0eSA8LSAxLjUNCiAgICBmdXR1cmVfZGF0YSRWZW50YV9BUk1BX0FqdXN0YWRhIDwtIGZ1dHVyZV9kYXRhJFZlbnRhX0FSTUEgKiANCiAgICAgIChyZWZfcHJpY2UgLyBmdXR1cmVfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8pXmVsYXN0aWNpdHkNCiAgfQ0KICANCiAgaWYgKCFpcy5udWxsKHJlZ19tb2RlbCkpIHsNCiAgICB0cnlDYXRjaCh7DQogICAgICBmdXR1cmVfZGF0YSRWZW50YV9SZWdMaW5lYWwgPC0gcHJlZGljdChyZWdfbW9kZWwsIG5ld2RhdGEgPSBmdXR1cmVfZmVhdHVyZXMpDQogICAgfSwgZXJyb3IgPSBmdW5jdGlvbihlKSB7DQogICAgICBmdXR1cmVfZGF0YSRWZW50YV9SZWdMaW5lYWwgPC0gTkENCiAgICB9KQ0KICB9DQogIA0KICBpZiAoIWlzLm51bGwocmZfbW9kZWwpKSB7DQogICAgdHJ5Q2F0Y2goew0KICAgICAgZnV0dXJlX2RhdGEkVmVudGFfUmFuZG9tRm9yZXN0IDwtIHByZWRpY3QocmZfbW9kZWwsIG5ld2RhdGEgPSBmdXR1cmVfZmVhdHVyZXMpDQogICAgfSwgZXJyb3IgPSBmdW5jdGlvbihlKSB7DQogICAgICBmdXR1cmVfZGF0YSRWZW50YV9SYW5kb21Gb3Jlc3QgPC0gTkENCiAgICB9KQ0KICB9DQogIA0KICBpZiAoIWlzLm51bGwoeGdiX21vZGVsKSkgew0KICAgIHRyeUNhdGNoKHsNCiAgICAgIGZlYXR1cmVzIDwtIHhnYl9tb2RlbCRmZWF0dXJlX25hbWVzDQogICAgICBpZiAoaXMubnVsbChmZWF0dXJlcykpIHsNCiAgICAgICAgZmVhdHVyZXMgPC0gc2V0ZGlmZihuYW1lcyhmdXR1cmVfZmVhdHVyZXMpLCAiVmVudGEiKQ0KICAgICAgfQ0KICAgICAgeGdiX21hdHJpeCA8LSBhcy5tYXRyaXgoZnV0dXJlX2ZlYXR1cmVzWywgZmVhdHVyZXMsIGRyb3AgPSBGQUxTRV0pDQogICAgICBmdXR1cmVfZGF0YSRWZW50YV9YR0Jvb3N0IDwtIHByZWRpY3QoeGdiX21vZGVsLCB4Z2JfbWF0cml4KQ0KICAgIH0sIGVycm9yID0gZnVuY3Rpb24oZSkgew0KICAgICAgZnV0dXJlX2RhdGEkVmVudGFfWEdCb29zdCA8LSBOQQ0KICAgIH0pDQogIH0NCiAgDQogIGF2Z19jb3N0X3Blcl91bml0IDwtIG1lZGlhbihoaXN0X2RhdGEkQ29zdG9fVmVudGEgLyBoaXN0X2RhdGEkQ2FudCwgbmEucm0gPSBUUlVFKQ0KICANCiAgZm9yIChtb2RlbCBpbiBjKCJBUk1BX0FqdXN0YWRhIiwgIlJlZ0xpbmVhbCIsICJSYW5kb21Gb3Jlc3QiLCAiWEdCb29zdCIpKSB7DQogICAgdmNvbCA8LSBwYXN0ZTAoIlZlbnRhXyIsIG1vZGVsKQ0KICAgIGlmICh2Y29sICVpbiUgbmFtZXMoZnV0dXJlX2RhdGEpKSB7DQogICAgICB1Y29sIDwtIHBhc3RlMCgiVW5pZGFkZXNfIiwgbW9kZWwpDQogICAgICBjY29sIDwtIHBhc3RlMCgiQ29zdG9fIiwgbW9kZWwpDQogICAgICBtY29sIDwtIHBhc3RlMCgiTWFyZ2VuXyIsIG1vZGVsKQ0KICAgICAgDQogICAgICBmdXR1cmVfZGF0YVtbdWNvbF1dIDwtIGZ1dHVyZV9kYXRhW1t2Y29sXV0gLyBmdXR1cmVfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8NCiAgICAgIGZ1dHVyZV9kYXRhW1tjY29sXV0gPC0gZnV0dXJlX2RhdGFbW3Vjb2xdXSAqIGF2Z19jb3N0X3Blcl91bml0DQogICAgICBmdXR1cmVfZGF0YVtbbWNvbF1dIDwtIGZ1dHVyZV9kYXRhW1t2Y29sXV0gLSBmdXR1cmVfZGF0YVtbY2NvbF1dDQogICAgfQ0KICB9DQogIA0KICBwcmVkX2NvbHMgPC0gYygiVmVudGFfQVJNQV9BanVzdGFkYSIsICJWZW50YV9SZWdMaW5lYWwiLCAiVmVudGFfUmFuZG9tRm9yZXN0IiwgIlZlbnRhX1hHQm9vc3QiKQ0KICBwcmVkX2NvbHMgPC0gcHJlZF9jb2xzW3ByZWRfY29scyAlaW4lIG5hbWVzKGZ1dHVyZV9kYXRhKV0NCiAgDQogIHRyeUNhdGNoKHsNCiAgICBpZiAobGVuZ3RoKHByZWRfY29scykgPiAwICYmIG5jb2woZnV0dXJlX2RhdGFbLCBwcmVkX2NvbHMsIGRyb3AgPSBGQUxTRV0pID4gMCkgew0KICAgICAgZnV0dXJlX2RhdGEkVmVudGFfQ29uc2Vuc28gPC0gcm93TWVhbnMoZnV0dXJlX2RhdGFbLCBwcmVkX2NvbHMsIGRyb3AgPSBGQUxTRV0sIG5hLnJtID0gVFJVRSkNCiAgICAgIGZ1dHVyZV9kYXRhJFVuaWRhZGVzX0NvbnNlbnNvIDwtIGZ1dHVyZV9kYXRhJFZlbnRhX0NvbnNlbnNvIC8gZnV0dXJlX2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvDQogICAgICBmdXR1cmVfZGF0YSRDb3N0b19Db25zZW5zbyA8LSBmdXR1cmVfZGF0YSRVbmlkYWRlc19Db25zZW5zbyAqIGF2Z19jb3N0X3Blcl91bml0DQogICAgICBmdXR1cmVfZGF0YSRNYXJnZW5fQ29uc2Vuc28gPC0gZnV0dXJlX2RhdGEkVmVudGFfQ29uc2Vuc28gLSBmdXR1cmVfZGF0YSRDb3N0b19Db25zZW5zbw0KICAgIH0NCiAgfSwgZXJyb3IgPSBmdW5jdGlvbihlKSB7DQogICAgd2FybmluZyhwYXN0ZSgiTm8gc2UgcHVkbyBjYWxjdWxhciBlbCBjb25zZW5zbyBwYXJhIHByb2R1Y3RvIiwgcHJvZHVjdF9pZCwgIjoiLCBlJG1lc3NhZ2UpKQ0KICB9KQ0KICANCiAgcmV0dXJuKGZ1dHVyZV9kYXRhKQ0KfQ0KDQpyZXN1bHRhZG9zX2Z1dHVyb3NfbGlzdGEgPC0gbGlzdCgpDQoNCmZvciAoaWQgaW4gcHJvZHVjdG9zX2lkcykgew0KICBjYXQoIkludGVncmFuZG8gbW9kZWxvcyBwYXJhIHByb2R1Y3RvOiIsIGlkLCAiXG4iKQ0KDQogIHJlc3VsdGFkbyA8LSBpbnRlZ3JhdGVfd2l0aF9leGlzdGluZ19tb2RlbHMoDQogICAgZGF0YSA9IGRhdG9zLA0KICAgIHByb2R1Y3RfaWQgPSBpZCwNCiAgICBwcmljZV9vcHRfcmVzdWx0cyA9IHByZWNpb3Nfb3B0aW1vc19saXN0YSwNCiAgICBhcm1hX21vZGVsID0gbW9kZWxvc19hcm1hX2xpc3RhW1thcy5jaGFyYWN0ZXIoaWQpXV0sDQogICAgcmVnX21vZGVsID0gbW9kZWxvc19yZWdfbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXSwNCiAgICByZl9tb2RlbCA9IG1vZGVsb3NfcmZfbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXSwNCiAgICB4Z2JfbW9kZWwgPSBtb2RlbG9zX3hnYl9saXN0YVtbYXMuY2hhcmFjdGVyKGlkKV1dDQogICkNCg0KICByZXN1bHRhZG9zX2Z1dHVyb3NfbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXSA8LSByZXN1bHRhZG8NCn0NCmBgYA0KDQojIyMgUGlwZWxpbmUgY29ycmVjdG8NCmBgYHtyfQ0KY29ycmVnaXJfZm9ybWF0b19mZWNoYXMgPC0gZnVuY3Rpb24oZGF0b3MpIHsNCiAgaWYgKCJUcnhfRmVjaGEiICVpbiUgY29sbmFtZXMoZGF0b3MpKSB7DQogICAgZGF0b3MkVHJ4X0ZlY2hhX09yaWdpbmFsIDwtIGRhdG9zJFRyeF9GZWNoYQ0KDQogICAgaWYgKGlzLmNoYXJhY3RlcihkYXRvcyRUcnhfRmVjaGEpICYmDQogICAgICAgIGFueShncmVwbCgiXlxcZHs3fS1cXGR7Mn0tXFxkezJ9JCIsIGRhdG9zJFRyeF9GZWNoYSkpKSB7DQoNCiAgICAgIGNhdCgiQ29ycmlnaWVuZG8gZm9ybWF0byBkZSBmZWNoYXMgZXh0cmHDsW8uLi5cbiIpDQoNCiAgICAgIGRhdG9zJFRyeF9GZWNoYSA8LSBzYXBwbHkoZGF0b3MkVHJ4X0ZlY2hhLCBmdW5jdGlvbihmZWNoYSkgew0KICAgICAgICBpZiAoaXMubmEoZmVjaGEpIHx8ICFpcy5jaGFyYWN0ZXIoZmVjaGEpKSByZXR1cm4oTkEpDQoNCiAgICAgICAgcGFydGVzIDwtIHN0cnNwbGl0KGZlY2hhLCAiLSIpW1sxXV0NCiAgICAgICAgaWYgKGxlbmd0aChwYXJ0ZXMpID09IDMpIHsNCiAgICAgICAgICBmZWNoYV9jb3JyZWdpZGEgPC0gcGFzdGUoIjIwMjMiLCBwYXJ0ZXNbMl0sIHBhcnRlc1szXSwgc2VwID0gIi0iKQ0KICAgICAgICAgIHJldHVybihmZWNoYV9jb3JyZWdpZGEpDQogICAgICAgIH0gZWxzZSB7DQogICAgICAgICAgcmV0dXJuKE5BKQ0KICAgICAgICB9DQogICAgICB9KQ0KDQogICAgICBkYXRvcyRUcnhfRmVjaGEgPC0gYXMuRGF0ZShkYXRvcyRUcnhfRmVjaGEpDQogICAgICBjYXQoIkZlY2hhcyBjb3JyZWdpZGFzIGV4aXRvc2FtZW50ZS5cbiIpDQogICAgfSBlbHNlIGlmICghaW5oZXJpdHMoZGF0b3MkVHJ4X0ZlY2hhLCAiRGF0ZSIpKSB7DQogICAgICBjYXQoIkludGVudGFuZG8gY29udmVydGlyIGZlY2hhcyBhIGZvcm1hdG8gRGF0ZS4uLlxuIikNCiAgICAgIGRhdG9zJFRyeF9GZWNoYSA8LSBhcy5EYXRlKGRhdG9zJFRyeF9GZWNoYSkNCiAgICB9DQogIH0NCiAgcmV0dXJuKGRhdG9zKQ0KfQ0KDQojIEFwbGljYXIgbGEgY29ycmVjY2nDs24gYSB0dSBkYXRhZnJhbWUgYW50ZXMgZGUgdXNhcmxvDQpkYXRvc19maWx0cmFkb3MgPC0gY29ycmVnaXJfZm9ybWF0b19mZWNoYXMoZGF0b3NfZmlsdHJhZG9zKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0ZXNfZnV0dXJlIDwtIHNlcS5EYXRlKGFzLkRhdGUoIjIwMjMtMDEtMDEiKSwgYnkgPSAibW9udGgiLCBsZW5ndGgub3V0ID0gNikNCnByZWNpb3Nfb3B0aW1vc19saXN0YSA8LSBsaXN0KCkNCg0KZm9yIChpZCBpbiBwcm9kdWN0b3NfaWRzKSB7DQogIGNhdCgiRXN0aW1hbmRvIHByZWNpb3Mgw7NwdGltb3MgcGFyYSBwcm9kdWN0bzoiLCBpZCwgIlxuIikNCg0KICBtb2RlbG9fcHJlY2lvIDwtIG1vZGVsb3NfcHJlY2lvX2xpc3RhW1thcy5jaGFyYWN0ZXIoaWQpXV0NCg0KICBpZiAoIWlzLm51bGwobW9kZWxvX3ByZWNpbykpIHsNCiAgICBwcmVjaW9zX29wdGltb3NfbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXSA8LSBlc3RpbWF0ZV9vcHRpbWFsX3ByaWNlcygNCiAgICAgIGRhdGEgPSBkYXRvc19maWx0cmFkb3MsDQogICAgICBwcm9kdWN0X2lkID0gaWQsDQogICAgICBwcmljZV9tb2RlbHMgPSBtb2RlbG9fcHJlY2lvLA0KICAgICAgZnV0dXJlX2RhdGVzID0gZGF0ZXNfZnV0dXJlDQogICAgKQ0KICB9DQp9DQpgYGANCg0KYGBge3J9DQpmb3IgKGlkIGluIG5hbWVzKHByZWNpb3Nfb3B0aW1vc19saXN0YSkpIHsNCiAgZGZfb3B0aW1vIDwtIHByZWNpb3Nfb3B0aW1vc19saXN0YVtbaWRdXSRwcmVjaW9zX29wdGltb3MNCg0KICBpZiAoIWluaGVyaXRzKGRmX29wdGltbyRUcnhfRmVjaGEsICJEYXRlIikpIHsNCiAgICBkZl9vcHRpbW8kVHJ4X0ZlY2hhIDwtIGFzLkRhdGUoZGZfb3B0aW1vJFRyeF9GZWNoYSkNCiAgfQ0KDQogIHAgPC0gZ2dwbG90KGRmX29wdGltbywgYWVzKHggPSBUcnhfRmVjaGEsIHkgPSBQcmVjaW9fT3B0aW1hbCkpICsNCiAgICBnZW9tX2xpbmUoY29sb3IgPSAiIzFmNzdiNCIsIGxpbmV3aWR0aCA9IDEuMikgKw0KICAgIGdlb21fcG9pbnQoY29sb3IgPSAiIzFmNzdiNCIsIHNpemUgPSAyKSArDQogICAgbGFicygNCiAgICAgIHRpdGxlID0gcGFzdGUoIlByZWNpbyDDk3B0aW1vIHBvciBNZXMgLSBQcm9kdWN0byIsIGlkKSwNCiAgICAgIHggPSAiRmVjaGEiLA0KICAgICAgeSA9ICJQcmVjaW8gw5NwdGltbyINCiAgICApICsNCiAgICBzY2FsZV94X2RhdGUoZGF0ZV9sYWJlbHMgPSAiJWIgJVkiLCBkYXRlX2JyZWFrcyA9ICIxIG1vbnRoIikgKw0KICAgIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpICsNCiAgICB0aGVtZSgNCiAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpDQogICAgKQ0KDQogIHByaW50KHApDQp9DQpgYGANCg0KYGBge3J9DQojIEZ1bmNpw7NuIHBhcmEgY29ycmVyIG9wdGltaXphY2nDs24gZGUgcHJlY2lvcyBwYXJhIHRvZG9zIGxvcyBwcm9kdWN0b3MNCnJ1bl9wcmljZV9vcHRpbWl6YXRpb24gPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZHMsIGZ1dHVyZV9kYXRlcyA9IE5VTEwsIG1vZGVsb3NfcHJlY2lvX2xpc3RhID0gTlVMTCkgew0KICBpZiAoaXMubnVsbChmdXR1cmVfZGF0ZXMpKSB7DQogICAgZnV0dXJlX2RhdGVzIDwtIHNlcS5EYXRlKFN5cy5EYXRlKCksIGJ5ID0gIm1vbnRoIiwgbGVuZ3RoLm91dCA9IDYpDQogIH0NCiAgDQogIHByZWNpb3Nfb3B0aW1vc19saXN0YSA8LSBsaXN0KCkNCiAgDQogIGZvciAoaWQgaW4gcHJvZHVjdF9pZHMpIHsNCiAgICBjYXQoIkVzdGltYW5kbyBwcmVjaW9zIMOzcHRpbW9zIHBhcmEgcHJvZHVjdG86IiwgaWQsICJcbiIpDQogICAgDQogICAgcHJpY2VfbW9kZWwgPC0gTlVMTA0KICAgIGlmICghaXMubnVsbChtb2RlbG9zX3ByZWNpb19saXN0YSkpIHsNCiAgICAgIHByaWNlX21vZGVsIDwtIG1vZGVsb3NfcHJlY2lvX2xpc3RhW1thcy5jaGFyYWN0ZXIoaWQpXV0NCiAgICB9DQogICAgDQogICAgcHJlY2lvc19vcHRpbW9zX2xpc3RhW1thcy5jaGFyYWN0ZXIoaWQpXV0gPC0gZXN0aW1hdGVfb3B0aW1hbF9wcmljZXMoDQogICAgICBkYXRhID0gZGF0YSwNCiAgICAgIHByb2R1Y3RfaWQgPSBpZCwNCiAgICAgIHByaWNlX21vZGVscyA9IHByaWNlX21vZGVsLA0KICAgICAgZnV0dXJlX2RhdGVzID0gZnV0dXJlX2RhdGVzDQogICAgKQ0KICB9DQogIA0KICByZXR1cm4ocHJlY2lvc19vcHRpbW9zX2xpc3RhKQ0KfQ0KDQoNCiMgRnVuY2nDs24gcHJpbmNpcGFsIHF1ZSBpbnRlZ3JhIHRvZG8gZWwgcGlwZWxpbmUNCnJ1bl9jb21wbGV0ZV9hbmFseXNpcyA8LSBmdW5jdGlvbihkYXRhLCB0b3BfaWRzLCBtb2RlbG9zX2FybWEsIG1vZGVsb3NfcmVnLCBtb2RlbG9zX3JmLCBtb2RlbG9zX3hnYiwgbW9kZWxvc19wcmVjaW9fbGlzdGEgPSBOVUxMKSB7DQogICMgMS4gRWplY3V0YXIgb3B0aW1pemFjacOzbiBkZSBwcmVjaW9zIHBhcmEgdG9kb3MgbG9zIHByb2R1Y3Rvcw0KICBhbGxfcmVzdWx0cyA8LSBydW5fcHJpY2Vfb3B0aW1pemF0aW9uKGRhdGEsIHRvcF9pZHMsIG1vZGVsb3NfcHJlY2lvX2xpc3RhID0gbW9kZWxvc19wcmVjaW9fbGlzdGEpDQogIA0KICAjIDIuIEludGVncmFyIGNvbiBtb2RlbG9zIGV4aXN0ZW50ZXMgcGFyYSBjYWRhIHByb2R1Y3RvDQogIGludGVncmF0ZWRfcmVzdWx0cyA8LSBsaXN0KCkNCiAgDQogIGZvciAoaSBpbiBzZXFfYWxvbmcodG9wX2lkcykpIHsNCiAgICBwaWQgPC0gdG9wX2lkc1tpXQ0KICAgIHBpZF9zdHIgPC0gYXMuY2hhcmFjdGVyKHBpZCkNCiAgICANCiAgICBhcm1hX21vZGVsIDwtIGlmKGxlbmd0aChtb2RlbG9zX2FybWEpID49IGkpIG1vZGVsb3NfYXJtYVtbaV1dIGVsc2UgTlVMTA0KICAgIHJlZ19tb2RlbCA8LSBpZihsZW5ndGgobW9kZWxvc19yZWcpID49IGkpIG1vZGVsb3NfcmVnW1tpXV0gZWxzZSBOVUxMDQogICAgcmZfbW9kZWwgPC0gaWYobGVuZ3RoKG1vZGVsb3NfcmYpID49IGkpIG1vZGVsb3NfcmZbW2ldXSBlbHNlIE5VTEwNCiAgICB4Z2JfbW9kZWwgPC0gaWYobGVuZ3RoKG1vZGVsb3NfeGdiKSA+PSBpKSBtb2RlbG9zX3hnYltbaV1dIGVsc2UgTlVMTA0KICAgIA0KICAgIGZ1dHVyZV9wcmVkaWN0aW9ucyA8LSBpbnRlZ3JhdGVfd2l0aF9leGlzdGluZ19tb2RlbHMoDQogICAgICBkYXRhID0gZGF0YSwNCiAgICAgIHByb2R1Y3RfaWQgPSBwaWQsDQogICAgICBwcmljZV9vcHRfcmVzdWx0cyA9IGFsbF9yZXN1bHRzLA0KICAgICAgYXJtYV9tb2RlbCA9IGFybWFfbW9kZWwsDQogICAgICByZWdfbW9kZWwgPSByZWdfbW9kZWwsDQogICAgICByZl9tb2RlbCA9IHJmX21vZGVsLA0KICAgICAgeGdiX21vZGVsID0geGdiX21vZGVsDQogICAgKQ0KICAgIA0KICAgIGludGVncmF0ZWRfcmVzdWx0c1tbcGlkX3N0cl1dIDwtIGZ1dHVyZV9wcmVkaWN0aW9ucw0KICAgIA0KICAgIGlmIChucm93KGZ1dHVyZV9wcmVkaWN0aW9ucykgPiAwKSB7DQogICAgICBwX3NhbGVzIDwtIGdncGxvdChmdXR1cmVfcHJlZGljdGlvbnMpDQogICAgICANCiAgICAgIGlmICgiVmVudGFfQVJNQV9BanVzdGFkYSIgJWluJSBuYW1lcyhmdXR1cmVfcHJlZGljdGlvbnMpKSB7DQogICAgICAgIHBfc2FsZXMgPC0gcF9zYWxlcyArIGdlb21fcG9pbnQoYWVzKHggPSBGZWNoYSwgeSA9IFZlbnRhX0FSTUFfQWp1c3RhZGEsIGNvbG9yID0gIkFSTUEiKSwgc2l6ZSA9IDMsIG5hLnJtID0gVFJVRSkNCiAgICAgIH0NCiAgICAgIGlmICgiVmVudGFfUmVnTGluZWFsIiAlaW4lIG5hbWVzKGZ1dHVyZV9wcmVkaWN0aW9ucykpIHsNCiAgICAgICAgcF9zYWxlcyA8LSBwX3NhbGVzICsgZ2VvbV9wb2ludChhZXMoeCA9IEZlY2hhLCB5ID0gVmVudGFfUmVnTGluZWFsLCBjb2xvciA9ICJSZWdyZXNpw7NuIExpbmVhbCIpLCBzaXplID0gMywgbmEucm0gPSBUUlVFKQ0KICAgICAgfQ0KICAgICAgaWYgKCJWZW50YV9SYW5kb21Gb3Jlc3QiICVpbiUgbmFtZXMoZnV0dXJlX3ByZWRpY3Rpb25zKSkgew0KICAgICAgICBwX3NhbGVzIDwtIHBfc2FsZXMgKyBnZW9tX3BvaW50KGFlcyh4ID0gRmVjaGEsIHkgPSBWZW50YV9SYW5kb21Gb3Jlc3QsIGNvbG9yID0gIlJhbmRvbSBGb3Jlc3QiKSwgc2l6ZSA9IDMsIG5hLnJtID0gVFJVRSkNCiAgICAgIH0NCiAgICAgIGlmICgiVmVudGFfWEdCb29zdCIgJWluJSBuYW1lcyhmdXR1cmVfcHJlZGljdGlvbnMpKSB7DQogICAgICAgIHBfc2FsZXMgPC0gcF9zYWxlcyArIGdlb21fcG9pbnQoYWVzKHggPSBGZWNoYSwgeSA9IFZlbnRhX1hHQm9vc3QsIGNvbG9yID0gIlhHQm9vc3QiKSwgc2l6ZSA9IDMsIG5hLnJtID0gVFJVRSkNCiAgICAgIH0NCiAgICAgIGlmICgiVmVudGFfQ29uc2Vuc28iICVpbiUgbmFtZXMoZnV0dXJlX3ByZWRpY3Rpb25zKSkgew0KICAgICAgICBwX3NhbGVzIDwtIHBfc2FsZXMgKyBnZW9tX2xpbmUoYWVzKHggPSBGZWNoYSwgeSA9IFZlbnRhX0NvbnNlbnNvLCBjb2xvciA9ICJDb25zZW5zbyIpLCBzaXplID0gMS41KQ0KICAgICAgfQ0KICAgICAgDQogICAgICBwX3NhbGVzIDwtIHBfc2FsZXMgKw0KICAgICAgICBsYWJzKA0KICAgICAgICAgIHRpdGxlID0gcGFzdGUoIlByZWRpY2Npb25lcyBkZSB2ZW50YXMgY29uIHByZWNpb3Mgw7NwdGltb3MgLSBQcm9kdWN0byIsIHBpZCksDQogICAgICAgICAgeCA9ICJGZWNoYSIsDQogICAgICAgICAgeSA9ICJWZW50YXMgZXN0aW1hZGFzICgkKSIsDQogICAgICAgICAgY29sb3IgPSAiTW9kZWxvIg0KICAgICAgICApICsNCiAgICAgICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICAgICAgdGhlbWUoDQogICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJib3R0b20iDQogICAgICAgICkNCiAgICAgIA0KICAgICAgcF9tYXJnaW5zIDwtIGdncGxvdChmdXR1cmVfcHJlZGljdGlvbnMpDQogICAgICANCiAgICAgIGlmICgiTWFyZ2VuX0NvbnNlbnNvIiAlaW4lIG5hbWVzKGZ1dHVyZV9wcmVkaWN0aW9ucykpIHsNCiAgICAgICAgcF9tYXJnaW5zIDwtIHBfbWFyZ2lucyArIA0KICAgICAgICAgIGdlb21fY29sKGFlcyh4ID0gRmVjaGEsIHkgPSBNYXJnZW5fQ29uc2Vuc28pLCBmaWxsID0gInN0ZWVsYmx1ZSIsIHdpZHRoID0gMTUpICsNCiAgICAgICAgICBnZW9tX3RleHQoYWVzKHggPSBGZWNoYSwgeSA9IE1hcmdlbl9Db25zZW5zbywgbGFiZWwgPSByb3VuZChNYXJnZW5fQ29uc2Vuc28sIDApKSwNCiAgICAgICAgICAgICAgICAgICAgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KQ0KICAgICAgfQ0KICAgICAgDQogICAgICBwX21hcmdpbnMgPC0gcF9tYXJnaW5zICsNCiAgICAgICAgbGFicygNCiAgICAgICAgICB0aXRsZSA9IHBhc3RlKCJNYXJnZW4gZXNwZXJhZG8gY29uIHByZWNpb3Mgw7NwdGltb3MgLSBQcm9kdWN0byIsIHBpZCksDQogICAgICAgICAgeCA9ICJGZWNoYSIsDQogICAgICAgICAgeSA9ICJNYXJnZW4gZXN0aW1hZG8gKCQpIg0KICAgICAgICApICsNCiAgICAgICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICAgICAgdGhlbWUoDQogICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwNCiAgICAgICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpDQogICAgICAgICkNCiAgICAgIA0KICAgICAgYWxsX3Jlc3VsdHNbW3BpZF9zdHJdXSRpbnRlZ3JhdGVkX3Bsb3RzIDwtIGxpc3QoDQogICAgICAgIHNhbGVzID0gcF9zYWxlcywNCiAgICAgICAgbWFyZ2lucyA9IHBfbWFyZ2lucw0KICAgICAgKQ0KICAgIH0NCiAgfQ0KICANCiAgIyAzLiBWaXN1YWxpemFyIHJlc3VsdGFkb3MgY29tcGFyYXRpdm9zDQogIGFsbF9vcHRpbWFsX3ByaWNlcyA8LSBkYXRhLmZyYW1lKCkNCiAgDQogIGZvciAocGlkIGluIHRvcF9pZHMpIHsNCiAgICBwaWRfc3RyIDwtIGFzLmNoYXJhY3RlcihwaWQpDQogICAgaWYgKHBpZF9zdHIgJWluJSBuYW1lcyhhbGxfcmVzdWx0cykpIHsNCiAgICAgIG9wdF9wcmljZXMgPC0gYWxsX3Jlc3VsdHNbW3BpZF9zdHJdXSRwcmVjaW9zX29wdGltb3MgJT4lDQogICAgICAgIG11dGF0ZShJRF9JbnZlbnRhcmlvID0gcGlkKQ0KICAgICAgDQogICAgICBhbGxfb3B0aW1hbF9wcmljZXMgPC0gcmJpbmQoYWxsX29wdGltYWxfcHJpY2VzLCBvcHRfcHJpY2VzKQ0KICAgIH0NCiAgfQ0KICANCiAgcF9jb21wYXJpc29uIDwtIGdncGxvdChhbGxfb3B0aW1hbF9wcmljZXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgYWVzKHggPSBUcnhfRmVjaGEsIHkgPSBQcmVjaW9fT3B0aW1hbCwgY29sb3IgPSBmYWN0b3IoSURfSW52ZW50YXJpbykpKSArDQogICAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsNCiAgICBnZW9tX3BvaW50KHNpemUgPSAzKSArDQogICAgbGFicygNCiAgICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBQcmVjaW9zIMOTcHRpbW9zIHBvciBQcm9kdWN0byIsDQogICAgICB4ID0gIkZlY2hhIiwNCiAgICAgIHkgPSAiUHJlY2lvIMOTcHRpbW8iLA0KICAgICAgY29sb3IgPSAiSUQgUHJvZHVjdG8iDQogICAgKSArDQogICAgdGhlbWVfbWluaW1hbCgpICsNCiAgICB0aGVtZSgNCiAgICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksDQogICAgICBheGlzLnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLA0KICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSINCiAgICApDQogIA0KICBtZXRyaWNhc19vcHRpbWFzIDwtIGRhdGEuZnJhbWUoKQ0KICANCiAgZm9yIChwaWQgaW4gdG9wX2lkcykgew0KICAgIHBpZF9zdHIgPC0gYXMuY2hhcmFjdGVyKHBpZCkNCiAgICBpZiAocGlkX3N0ciAlaW4lIG5hbWVzKGludGVncmF0ZWRfcmVzdWx0cykpIHsNCiAgICAgIHByZWRfZGF0YSA8LSBpbnRlZ3JhdGVkX3Jlc3VsdHNbW3BpZF9zdHJdXQ0KICAgICAgDQogICAgICBpZiAoIk1hcmdlbl9Db25zZW5zbyIgJWluJSBuYW1lcyhwcmVkX2RhdGEpKSB7DQogICAgICAgIG1ldHJpY3Nfcm93IDwtIGRhdGEuZnJhbWUoDQogICAgICAgICAgSURfSW52ZW50YXJpbyA9IHBpZCwNCiAgICAgICAgICBQcmVjaW9fUHJvbWVkaW8gPSBtZWFuKHByZWRfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgVmVudGFfVG90YWwgPSBzdW0ocHJlZF9kYXRhJFZlbnRhX0NvbnNlbnNvLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgIE1hcmdlbl9Ub3RhbCA9IHN1bShwcmVkX2RhdGEkTWFyZ2VuX0NvbnNlbnNvLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgIE1hcmdlbl9Qb3JjZW50dWFsID0gMTAwICogc3VtKHByZWRfZGF0YSRNYXJnZW5fQ29uc2Vuc28sIG5hLnJtID0gVFJVRSkgLw0KICAgICAgICAgICAgc3VtKHByZWRfZGF0YSRWZW50YV9Db25zZW5zbywgbmEucm0gPSBUUlVFKQ0KICAgICAgICApDQogICAgICAgIA0KICAgICAgICBtZXRyaWNhc19vcHRpbWFzIDwtIHJiaW5kKG1ldHJpY2FzX29wdGltYXMsIG1ldHJpY3Nfcm93KQ0KICAgICAgfQ0KICAgIH0NCiAgfQ0KICANCiAgcmV0dXJuKGxpc3QoDQogICAgcmVzdWx0YWRvcyA9IGFsbF9yZXN1bHRzLA0KICAgIGludGVncmFjaW9uID0gaW50ZWdyYXRlZF9yZXN1bHRzLA0KICAgIHByZWNpb3Nfb3B0aW1vcyA9IGFsbF9vcHRpbWFsX3ByaWNlcywNCiAgICBtZXRyaWNhc19vcHRpbWFzID0gbWV0cmljYXNfb3B0aW1hcywNCiAgICBncmFmaWNvX2NvbXBhcmF0aXZvID0gcF9jb21wYXJpc29uDQogICkpDQp9DQoNCg0KIyBFamVjdXRhciBlbCBhbsOhbGlzaXMgY29tcGxldG8NCnJlc3VsdGFkb19jb21wbGV0byA8LSBydW5fY29tcGxldGVfYW5hbHlzaXMoDQogIGRhdGEgPSBkYXRvcywNCiAgdG9wX2lkcyA9IHByb2R1Y3Rvc19pZHMsDQogIG1vZGVsb3NfYXJtYSA9IG1vZGVsb3NfYXJtYV9saXN0YSwNCiAgbW9kZWxvc19yZWcgPSBtb2RlbG9zX3JlZ19saXN0YSwNCiAgbW9kZWxvc19yZiA9IG1vZGVsb3NfcmZfbGlzdGEsDQogIG1vZGVsb3NfeGdiID0gbW9kZWxvc194Z2JfbGlzdGEsDQogIG1vZGVsb3NfcHJlY2lvX2xpc3RhID0gbW9kZWxvc19wcmVjaW9fbGlzdGEgIyBQYXNhIGVzdGEgbGlzdGEgc2kgbGEgdGllbmVzLCBvIE5VTEwNCikNCmBgYA0KDQojIyMgR3LDoWZpY28gY29tcGFyYXRpdm8gZGUgcHJlY2lvcyDDs3B0aW1vcyBwb3IgcHJvZHVjdG86DQoNCmBgYHtyfQ0KIyBNb3N0cmFyIG3DqXRyaWNhcyBzaSBlc3TDoXMgZW4gbW9kbyBpbnRlcmFjdGl2bw0KaWYgKGludGVyYWN0aXZlKCkpIFZpZXcocmVzdWx0YWRvX2NvbXBsZXRvJG1ldHJpY2FzX29wdGltYXMpDQoNCmNhdCgiR3LDoWZpY28gY29tcGFyYXRpdm8gZGUgcHJlY2lvcyDDs3B0aW1vcyBwb3IgcHJvZHVjdG86XG4iKQ0KYGBgDQoNCmBgYHtyfQ0KcHJpbnQocmVzdWx0YWRvX2NvbXBsZXRvJGdyYWZpY29fY29tcGFyYXRpdm8pDQpgYGANCg0KIyBQcmVkaWNpw7NuIGRlIHZlbnRhcyBjb24gcHJlY2lvcyBvcHRpbW9zIHBvciBwcm9kdWN0bw0KDQoNCmBgYHtyfQ0KY2F0KCJHcsOhZmljb3MgaW5kaXZpZHVhbGVzIHBvciBwcm9kdWN0bzpcbiIpDQpgYGANCg0KDQpgYGB7cn0NCmZvciAocGlkIGluIG5hbWVzKHJlc3VsdGFkb19jb21wbGV0byRyZXN1bHRhZG9zKSkgew0KICBwbG90cyA8LSByZXN1bHRhZG9fY29tcGxldG8kcmVzdWx0YWRvc1tbcGlkXV0kaW50ZWdyYXRlZF9wbG90cw0KICBpZiAoIWlzLm51bGwocGxvdHMpKSB7DQogICAgY2F0KHBhc3RlMCgiIyMgUHJvZHVjdG86ICIsIHBpZCwgIlxuXG4iKSkNCiAgICBwcmludChwbG90cyRzYWxlcykNCiAgICBwcmludChwbG90cyRtYXJnaW5zKQ0KICAgIA0KICAgICMgU2VwYXJhY2nDs24gdmlzdWFsIG9wY2lvbmFsDQogICAgY2F0KCJcbi0tLVxuXG4iKQ0KICB9DQp9DQpgYGANCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=