REGRESION LINEAL
MAPA DE CALOR
# Variables numƩricas relevantes
vars_numericas <- c("Cant", "Venta", "Costo_Venta",
"Precio_Final_Unitario", "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,
Precio_Final_Unitario, 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 Ć 8
## Venta Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 1187. 2 1194. 594. 85.8
## 2 21280 40 23874. 532 87.3
## 3 15960 30 17906. 532 87.3
## 4 31920 60 35811. 532 87.3
## 5 2968 5 2570. 594. 85.8
## 6 1187. 2 958. 594. 85.8
## # ā¹ 3 more variables: Trx_Fecha <dttm>, Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_155001 <- lm(Venta ~ Cant + Costo_Venta +
Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
data = datos_155001)
# Ver resumen del modelo
summary(modelo_regresion_155001)
##
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario +
## Descuento_Porcentaje + Tiempo, data = datos_155001)
##
## Residuals:
## Min 1Q Median 3Q Max
## -12228.9 -128.0 35.3 100.1 24517.4
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) -3.319e+03 1.165e+03 -2.849 0.0044 **
## Cant 1.578e+02 3.300e+00 47.839 <2e-16 ***
## Costo_Venta 6.802e-01 8.789e-03 77.388 <2e-16 ***
## Precio_Final_Unitario 2.859e+00 2.544e-01 11.241 <2e-16 ***
## Descuento_Porcentaje 2.326e+01 1.199e+01 1.939 0.0525 .
## Tiempo 8.990e+04 1.940e+05 0.463 0.6431
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 742.9 on 8553 degrees of freedom
## Multiple R-squared: 0.9879, Adjusted R-squared: 0.9879
## F-statistic: 1.393e+05 on 5 and 8553 DF, p-value: < 2.2e-16
# Ajuste del modelo de regresión lineal
modelo_regresion_155001 <- lm(Venta ~ Cant + Costo_Venta +
Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
data = datos_155001)
# Predicciones usando el modelo ajustado
predicciones_155001 <- predict(modelo_regresion_155001, newdata = datos_155001)
# 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("MAPE del modelo de regresión lineal para 155001: ", mape_155001, "\n")
## MAPE del modelo de regresión lineal para 155001: 14.49668
cat("MSE del modelo de regresión lineal para 155001: ", mse_155001, "\n")
## MSE del modelo de regresión lineal para 155001: 551515.6
# 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(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155001", # Cambia este ID para cada producto
Modelo = "Regresión Lineal",
MAPE = mape_155001,
MSE = mse_155001
))
PRODUCTO 3929788
# Filtrar solo los datos para el producto 3929788
datos_3929788 <- datos_filtrados %>%
filter(ID_Inventario == 3929788) %>%
select(Venta, Cant, Costo_Venta,
Precio_Final_Unitario, 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 Ć 8
## Venta Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 364 10 254. 36.4 60
## 2 242. 6 167. 40.3 60
## 3 697. 15 506. 46.5 48.9
## 4 13020 300 10110. 43.4 52.3
## 5 2170 50 1685. 43.4 52.3
## 6 434 10 337. 43.4 52.3
## # ā¹ 3 more variables: Trx_Fecha <dttm>, Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_3929788 <- lm(Venta ~ Cant + Costo_Venta +
Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
data = datos_3929788)
# Ver resumen del modelo
summary(modelo_regresion_3929788)
##
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario +
## Descuento_Porcentaje + Tiempo, data = datos_3929788)
##
## Residuals:
## Min 1Q Median 3Q Max
## -4348.4 -81.8 -45.4 27.9 3441.5
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 5.154e+02 1.690e+02 3.049 0.0023 **
## Cant 2.552e+00 2.503e-01 10.193 < 2e-16 ***
## Costo_Venta 1.102e+00 8.312e-03 132.603 < 2e-16 ***
## Precio_Final_Unitario 8.877e-01 1.674e+00 0.530 0.5960
## Descuento_Porcentaje -7.636e+00 1.750e+00 -4.364 1.29e-05 ***
## Tiempo 2.082e+05 4.365e+04 4.771 1.85e-06 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 218.9 on 13711 degrees of freedom
## Multiple R-squared: 0.9949, Adjusted R-squared: 0.9949
## F-statistic: 5.393e+05 on 5 and 13711 DF, p-value: < 2.2e-16
# Predicciones usando el modelo ajustado
predicciones_3929788 <- predict(modelo_regresion_3929788, newdata = datos_3929788)
# 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("MAPE del modelo de regresión lineal para 3929788: ", mape_3929788, "\n")
## MAPE del modelo de regresión lineal para 3929788: 22.37678
cat("MSE del modelo de regresión lineal para 3929788: ", mse_3929788, "\n")
## MSE del modelo de regresión lineal para 3929788: 47902.12
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3929788", # Cambia este ID para cada producto
Modelo = "Regresión Lineal",
MAPE = mape_3929788,
MSE = mse_3929788
))
PRODUCTO 3904152
# Filtrar solo los datos para el producto 3904152
datos_3904152 <- datos_filtrados %>%
filter(ID_Inventario == 3904152) %>%
select(Venta, Cant, Costo_Venta,
Precio_Final_Unitario, 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 Ć 8
## Venta Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 3402 1 2462. 3402 62.4
## 2 9240 3 7382. 3080 66.0
## 3 3402 1 2461. 3402 62.4
## 4 3402 1 2462. 3402 62.4
## 5 3402 1 2462. 3402 62.4
## 6 30800 10 24563. 3080 66.0
## # ā¹ 3 more variables: Trx_Fecha <dttm>, Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_3904152 <- lm(Venta ~ Cant + Costo_Venta +
Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
data = datos_3904152)
# Ver resumen del modelo
summary(modelo_regresion_3904152)
##
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario +
## Descuento_Porcentaje + Tiempo, data = datos_3904152)
##
## Residuals:
## Min 1Q Median 3Q Max
## -7591.2 -149.6 -11.9 114.8 3142.7
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.279e+04 1.005e+03 12.720 < 2e-16 ***
## Cant 2.651e+02 5.929e+01 4.470 8.16e-06 ***
## Costo_Venta 1.172e+00 2.513e-02 46.650 < 2e-16 ***
## Precio_Final_Unitario -1.734e-01 9.940e-02 -1.744 0.0812 .
## Descuento_Porcentaje -1.888e+02 1.108e+01 -17.049 < 2e-16 ***
## Tiempo 2.313e+06 1.487e+05 15.561 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 376.9 on 2550 degrees of freedom
## Multiple R-squared: 0.9987, Adjusted R-squared: 0.9987
## F-statistic: 4.059e+05 on 5 and 2550 DF, p-value: < 2.2e-16
# Predicciones usando el modelo ajustado
predicciones_3904152 <- predict(modelo_regresion_3904152, newdata = datos_3904152)
# 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("MAPE del modelo de regresión lineal para 3904152: ", mape_3904152, "\n")
## MAPE del modelo de regresión lineal para 3904152: 3.343441
cat("MSE del modelo de regresión lineal para 3904152: ", mse_3904152, "\n")
## MSE del modelo de regresión lineal para 3904152: 141741.2
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3904152", # Cambia este ID para cada producto
Modelo = "Regresión Lineal",
MAPE = mape_3904152,
MSE = mse_3904152
))
PRODUCTO 155002
# Filtrar solo los datos para el producto 155002
datos_155002 <- datos_filtrados %>%
filter(ID_Inventario == 155002) %>%
select(Venta, Cant, Costo_Venta,
Precio_Final_Unitario, 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 Ć 8
## Venta Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 5320 10 8247. 532 87.3
## 2 5320 10 8247. 532 87.3
## 3 2660 5 4124. 532 87.3
## 4 630 1 519. 630 85.0
## 5 1120 2 1037. 560 86.6
## 6 1537. 3 1556. 512. 87.8
## # ā¹ 3 more variables: Trx_Fecha <dttm>, Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_155002 <- lm(Venta ~ Cant + Costo_Venta +
Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
data = datos_155002)
# Ver resumen del modelo
summary(modelo_regresion_155002)
##
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario +
## Descuento_Porcentaje + Tiempo, data = datos_155002)
##
## Residuals:
## Min 1Q Median 3Q Max
## -11980 -167 55 154 46301
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 8.521e+02 1.894e+03 0.450 0.652854
## Cant 2.992e+02 3.945e+00 75.856 < 2e-16 ***
## Costo_Venta 3.025e-01 9.834e-03 30.758 < 2e-16 ***
## Precio_Final_Unitario 3.681e+00 3.980e-01 9.250 < 2e-16 ***
## Descuento_Porcentaje -2.875e+01 1.957e+01 -1.469 0.141855
## Tiempo 9.982e+05 3.004e+05 3.323 0.000896 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 890.7 on 5746 degrees of freedom
## Multiple R-squared: 0.9723, Adjusted R-squared: 0.9723
## F-statistic: 4.039e+04 on 5 and 5746 DF, p-value: < 2.2e-16
# Predicciones usando el modelo ajustado
predicciones_155002 <- predict(modelo_regresion_155002, newdata = datos_155002)
# 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("MAPE del modelo de regresión lineal para 155002: ", mape_155002, "\n")
## MAPE del modelo de regresión lineal para 155002: 19.918
cat("MSE del modelo de regresión lineal para 155002: ", mse_155002, "\n")
## MSE del modelo de regresión lineal para 155002: 792516.1
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155002", # Cambia este ID para cada producto
Modelo = "Regresión Lineal",
MAPE = mape_155002,
MSE = mse_155002
))
PRODUCTO 3678055
# Filtrar solo los datos para el producto 3678055
datos_3678055 <- datos_filtrados %>%
filter(ID_Inventario == 3678055) %>%
select(Venta, Cant, Costo_Venta,
Precio_Final_Unitario, 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 Ć 8
## Venta Cant Costo_Venta Precio_Final_Unitario Descuento_Porcentaje
## <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 36358 7 28807. 5194 65.9
## 2 5670 1 4213. 5670 62.8
## 3 10773 2 8232. 5386. 64.7
## 4 5670 1 4116. 5670 62.8
## 5 5386. 1 4156. 5386. 64.7
## 6 5386. 1 4213. 5386. 64.7
## # ā¹ 3 more variables: Trx_Fecha <dttm>, Fecha <date>, Tiempo <dbl>
# Ajustar el modelo de regresión lineal
modelo_regresion_3678055 <- lm(Venta ~ Cant + Costo_Venta +
Precio_Final_Unitario + Descuento_Porcentaje + Tiempo,
data = datos_3678055)
# Ver resumen del modelo
summary(modelo_regresion_3678055)
##
## Call:
## lm(formula = Venta ~ Cant + Costo_Venta + Precio_Final_Unitario +
## Descuento_Porcentaje + Tiempo, data = datos_3678055)
##
## Residuals:
## Min 1Q Median 3Q Max
## -3921.4 -165.9 -7.8 170.0 3631.6
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 1.260e+04 1.396e+03 9.024 < 2e-16 ***
## Cant 1.570e+03 1.017e+02 15.439 < 2e-16 ***
## Costo_Venta 9.057e-01 2.541e-02 35.648 < 2e-16 ***
## Precio_Final_Unitario 2.424e-01 8.578e-02 2.826 0.00477 **
## Descuento_Porcentaje -2.127e+02 1.512e+01 -14.061 < 2e-16 ***
## Tiempo 2.034e+06 2.032e+05 10.009 < 2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 461.5 on 1654 degrees of freedom
## Multiple R-squared: 0.9976, Adjusted R-squared: 0.9976
## F-statistic: 1.376e+05 on 5 and 1654 DF, p-value: < 2.2e-16
#Predicciones usando el modelo ajustado
predicciones_3678055 <- predict(modelo_regresion_3678055, newdata = datos_3678055)
# 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("MAPE del modelo de regresión lineal para 3678055: ", mape_3678055, "\n")
## MAPE del modelo de regresión lineal para 3678055: 2.900802
cat("MSE del modelo de regresión lineal para 3678055: ", mse_3678055, "\n")
## MSE del modelo de regresión lineal para 3678055: 212205.3
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3678055", # Cambia este ID para cada producto
Modelo = "Regresión Lineal",
MAPE = mape_3678055,
MSE = mse_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
|
89900.3830
|
0.6431
|
No
|
Positivo
|
|
155001
|
Cant
|
157.8442
|
0.0000
|
SĆ
|
Positivo
|
|
155001
|
Descuento_Porcentaje
|
23.2560
|
0.0525
|
No
|
Positivo
|
|
155001
|
Precio_Final_Unitario
|
2.8592
|
0.0000
|
SĆ
|
Positivo
|
|
155001
|
Costo_Venta
|
0.6802
|
0.0000
|
SĆ
|
Positivo
|
|
155002
|
Tiempo
|
998207.9076
|
0.0009
|
SĆ
|
Positivo
|
|
155002
|
Cant
|
299.2286
|
0.0000
|
SĆ
|
Positivo
|
|
155002
|
Descuento_Porcentaje
|
-28.7504
|
0.1419
|
No
|
Negativo
|
|
155002
|
Precio_Final_Unitario
|
3.6812
|
0.0000
|
SĆ
|
Positivo
|
|
155002
|
Costo_Venta
|
0.3025
|
0.0000
|
SĆ
|
Positivo
|
|
3678055
|
Tiempo
|
2034282.2796
|
0.0000
|
SĆ
|
Positivo
|
|
3678055
|
Cant
|
1570.0692
|
0.0000
|
SĆ
|
Positivo
|
|
3678055
|
Descuento_Porcentaje
|
-212.6746
|
0.0000
|
SĆ
|
Negativo
|
|
3678055
|
Costo_Venta
|
0.9057
|
0.0000
|
SĆ
|
Positivo
|
|
3678055
|
Precio_Final_Unitario
|
0.2425
|
0.0048
|
SĆ
|
Positivo
|
|
3904152
|
Tiempo
|
2313419.2297
|
0.0000
|
SĆ
|
Positivo
|
|
3904152
|
Cant
|
265.0523
|
0.0000
|
SĆ
|
Positivo
|
|
3904152
|
Descuento_Porcentaje
|
-188.8303
|
0.0000
|
SĆ
|
Negativo
|
|
3904152
|
Costo_Venta
|
1.1722
|
0.0000
|
SĆ
|
Positivo
|
|
3904152
|
Precio_Final_Unitario
|
-0.1734
|
0.0812
|
No
|
Negativo
|
|
3929788
|
Tiempo
|
208247.8173
|
0.0000
|
SĆ
|
Positivo
|
|
3929788
|
Descuento_Porcentaje
|
-7.6365
|
0.0000
|
SĆ
|
Negativo
|
|
3929788
|
Cant
|
2.5517
|
0.0000
|
SĆ
|
Positivo
|
|
3929788
|
Costo_Venta
|
1.1022
|
0.0000
|
SĆ
|
Positivo
|
|
3929788
|
Precio_Final_Unitario
|
0.8877
|
0.5960
|
No
|
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
|
89900.3830
|
0.6431
|
No
|
Positivo
|
|
155001
|
Cant
|
157.8442
|
0.0000
|
SĆ
|
Positivo
|
|
155001
|
Descuento_Porcentaje
|
23.2560
|
0.0525
|
No
|
Positivo
|
|
155002
|
Tiempo
|
998207.9076
|
0.0009
|
SĆ
|
Positivo
|
|
155002
|
Cant
|
299.2286
|
0.0000
|
SĆ
|
Positivo
|
|
155002
|
Descuento_Porcentaje
|
-28.7504
|
0.1419
|
No
|
Negativo
|
|
3678055
|
Tiempo
|
2034282.2796
|
0.0000
|
SĆ
|
Positivo
|
|
3678055
|
Cant
|
1570.0692
|
0.0000
|
SĆ
|
Positivo
|
|
3678055
|
Descuento_Porcentaje
|
-212.6746
|
0.0000
|
SĆ
|
Negativo
|
|
3904152
|
Tiempo
|
2313419.2297
|
0.0000
|
SĆ
|
Positivo
|
|
3904152
|
Cant
|
265.0523
|
0.0000
|
SĆ
|
Positivo
|
|
3904152
|
Descuento_Porcentaje
|
-188.8303
|
0.0000
|
SĆ
|
Negativo
|
|
3929788
|
Tiempo
|
208247.8173
|
0.0000
|
SĆ
|
Positivo
|
|
3929788
|
Descuento_Porcentaje
|
-7.6365
|
0.0000
|
SĆ
|
Negativo
|
|
3929788
|
Cant
|
2.5517
|
0.0000
|
SĆ
|
Positivo
|
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: 2
##
## Mean of squared residuals: 1527551
## % Var explained: 96.64
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_155001, newdata = datos_modelo)
# Calcular mƩtricas
# 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("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 0.4085599
cat("MSE del modelo Random Forest:", mse_rf, "\n\n")
## MSE del modelo Random Forest: 387271.7
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_155001)
print(importancia_vars)
## %IncMSE IncNodePurity
## Cant 30.450104 161568380699
## Costo_Venta 35.858281 202647475601
## Precio_Final_Unitario 2.363057 5466467501
## Descuento_Porcentaje 2.374052 9340162988
## Tiempo 3.107877 4882101135
# 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: 10.78726
cat("Desviación estÔndar de errores:", sd(errores), "\n")
## Desviación estÔndar de errores: 622.2545
cat("MĆnimo:", min(errores), "\n")
## MĆnimo: -3000.569
cat("MƔximo:", max(errores), "\n")
## MƔximo: 47173.6
cat("Mediana:", median(errores), "\n")
## Mediana: -0.03039354
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155001", # Cambia este ID para cada producto
Modelo = "Random Forest",
MAPE = mape_rf,
MSE = mse_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 Precio_Final_Unitario
## Min. : 30.52 Min. : 1.00 Min. : 25.36 Min. : 28.00
## 1st Qu.: 403.20 1st Qu.: 10.00 1st Qu.: 280.17 1st Qu.: 36.40
## Median : 873.60 Median : 20.00 Median : 623.38 Median : 40.32
## Mean : 1660.11 Mean : 42.99 Mean : 1301.82 Mean : 40.83
## 3rd Qu.: 2016.00 3rd Qu.: 50.00 3rd Qu.: 1513.40 3rd Qu.: 43.57
## Max. :53096.40 Max. :1540.00 Max. :46610.46 Max. :108.92
## Descuento_Porcentaje Trx_Fecha Fecha
## Min. : 0.00 Min. :2023-01-02 00:00:00.00 Min. :2023-01-01
## 1st Qu.:60.00 1st Qu.:2023-07-05 00:00:00.00 1st Qu.:2023-07-01
## Median :60.19 Median :2024-01-15 00:00:00.00 Median :2024-01-01
## Mean :60.84 Mean :2024-01-06 20:09:02.74 Mean :2023-12-21
## 3rd Qu.:65.28 3rd Qu.:2024-07-09 00:00:00.00 3rd Qu.:2024-07-01
## Max. :71.72 Max. :2024-12-31 00:00:00.00 Max. :2024-12-01
## Tiempo
## Min. :0.000e+00
## 1st Qu.:6.983e-05
## Median :1.408e-04
## Mean :1.370e-04
## 3rd Qu.:2.110e-04
## 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: 2
##
## Mean of squared residuals: 19754.14
## % Var explained: 99.79
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_3929788, newdata = datos_modelo)
# Calcular mƩtricas
# 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("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 0.4490773
cat("MSE del modelo Random Forest:", mse_rf, "\n\n")
## MSE del modelo Random Forest: 4061.364
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3929788)
print(importancia_vars)
## %IncMSE IncNodePurity
## Cant 34.276494 56601065324
## Costo_Venta 39.131339 68121001903
## Precio_Final_Unitario 2.395103 1273355507
## Descuento_Porcentaje 9.421761 3916614655
## Tiempo 1.643341 378918548
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3929788", # Cambia este ID para cada producto
Modelo = "Random Forest",
MAPE = mape_rf,
MSE = mse_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 Precio_Final_Unitario
## Min. : 2677 Min. : 1.000 Min. : 2250 Min. :2677
## 1st Qu.: 3108 1st Qu.: 1.000 1st Qu.: 2331 1st Qu.:3051
## Median : 3402 Median : 1.000 Median : 2472 Median :3108
## Mean : 7495 Mean : 2.413 Mean : 5724 Mean :3158
## 3rd Qu.: 7140 3rd Qu.: 2.000 3rd Qu.: 5194 3rd Qu.:3318
## Max. :169344 Max. :56.000 Max. :131032 Max. :3639
## Descuento_Porcentaje Trx_Fecha Fecha
## Min. :60.00 Min. :2023-01-02 00:00:00.00 Min. :2023-01-01
## 1st Qu.:64.43 1st Qu.:2023-07-12 00:00:00.00 1st Qu.:2023-07-01
## Median :65.90 Median :2024-01-16 00:00:00.00 Median :2024-01-01
## Mean :65.72 Mean :2024-01-06 20:44:30.42 Mean :2023-12-21
## 3rd Qu.:66.60 3rd Qu.:2024-06-28 00:00:00.00 3rd Qu.:2024-06-01
## Max. :70.43 Max. :2024-12-31 00:00:00.00 Max. :2024-12-01
## Tiempo
## Min. :0.000e+00
## 1st Qu.:6.983e-05
## Median :1.408e-04
## Mean :1.366e-04
## 3rd Qu.:1.995e-04
## 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: 2
##
## Mean of squared residuals: 912601.7
## % Var explained: 99.19
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_3904152, newdata = datos_modelo)
# Calcular mƩtricas
# 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("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 0.2195905
cat("MSE del modelo Random Forest:", mse_rf, "\n\n")
## MSE del modelo Random Forest: 233644.4
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3904152)
print(importancia_vars)
## %IncMSE IncNodePurity
## Cant 27.857528 127827976697
## Costo_Venta 33.273998 150734822975
## Precio_Final_Unitario 3.859049 3595343640
## Descuento_Porcentaje 4.653136 5848187484
## Tiempo 2.155004 1653911972
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3904152", # Cambia este ID para cada producto
Modelo = "Random Forest",
MAPE = mape_rf,
MSE = mse_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: 2
##
## Mean of squared residuals: 975724.1
## % Var explained: 96.59
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_155002, newdata = datos_modelo)
# Calcular mƩtricas
# 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("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 0.550092
cat("MSE del modelo Random Forest:", mse_rf, "\n\n")
## MSE del modelo Random Forest: 183402.8
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_155002)
print(importancia_vars)
## %IncMSE IncNodePurity
## Cant 35.795786 81092951910
## Costo_Venta 31.078376 72643004789
## Precio_Final_Unitario 14.132964 3540837939
## Descuento_Porcentaje 6.103098 5382968927
## Tiempo 7.484308 1781157778
# 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 3678055
if(!exists("metricas_comparativas")) {
metricas_comparativas <- data.frame(
Producto = character(),
Modelo = character(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3678055", # Cambia este ID para cada producto
Modelo = "Random Forest",
MAPE = mape_rf,
MSE = mse_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: 2
##
## Mean of squared residuals: 839721.5
## % Var explained: 99.05
# Obtener predicciones
predicciones_rf <- predict(modelo_rf_3678055, newdata = datos_modelo)
# Calcular mƩtricas
# 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("MAPE del modelo Random Forest:", mape_rf, "\n")
## MAPE del modelo Random Forest: 0.2172073
cat("MSE del modelo Random Forest:", mse_rf, "\n\n")
## MSE del modelo Random Forest: 230895.8
# Mostrar importancia de variables
importancia_vars <- importance(modelo_rf_3678055)
print(importancia_vars)
## %IncMSE IncNodePurity
## Cant 27.985712 59615600800
## Costo_Venta 36.667397 81742000969
## Precio_Final_Unitario 6.416070 1542604712
## Descuento_Porcentaje 5.379649 1721440173
## Tiempo 4.174552 1044473588
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3678055", # Cambia este ID para cada producto
Modelo = "Random Forest",
MAPE = mape_rf,
MSE = mse_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
## 2 0.10 9 0.8 0.6 1 0.1 100
## 3 0.10 3 1.0 0.6 3 0.0 95
## 6 0.05 7 0.8 0.8 1 0.3 100
## 7 0.05 5 1.0 1.0 3 0.3 100
## rmse
## 4 885.4099
## 2 894.0994
## 3 961.1106
## 6 978.7279
## 7 996.1521
# 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: 885.4099
# 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
# MAPE
mape_test <- mean(abs((y_test - predicciones_test) / pmax(y_test, 0.01))) * 100
# MSE
mse_test <- mean((y_test - predicciones_test)^2)
# 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("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 1.460781
cat("MSE del modelo XGBoost:", mse_test, "\n\n")
## MSE del modelo XGBoost: 308975
# 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
# 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)
# Mostrar las mƩtricas en el conjunto completo
cat("MƩtricas en el conjunto completo:\n")
## MƩtricas en el conjunto completo:
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 1.099712
cat("MSE del modelo XGBoost:", mse_completo, "\n\n")
## MSE del modelo XGBoost: 103809.3
# 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 0.6024075757 0.36205133 0.26368635
## 2: Cant 0.3935043144 0.31805813 0.21109252
## 3: Precio_Final_Unitario 0.0034464656 0.23581258 0.31926847
## 4: Descuento_Porcentaje 0.0004900942 0.05944982 0.11403299
## 5: Tiempo 0.0001515501 0.02462814 0.09191967
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155001", # Cambia este ID para cada producto
Modelo = "XGBoost",
MAPE = mape_completo,
MSE = mse_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: 582.1593
##
## 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: 200
## RMSE en validación cruzada: 118.5624
##
## 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: 170.2107
##
## 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: 44
## RMSE en validación cruzada: 133.5118
##
## 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: 200
## RMSE en validación cruzada: 123.7198
##
## 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: 572.307
##
## 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: 200
## RMSE en validación cruzada: 155.1739
##
## 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: 200
## RMSE en validación cruzada: 137.9191
##
## 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: 111.4454
##
## 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: 158.1116
##
## 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: 200
## RMSE en validación cruzada: 117.3367
##
## 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: 148.92
# 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
## 9 0.05 6 3 0.7 0.9 0.3 200
## 11 0.05 6 3 0.7 0.9 0.0 200
## 2 0.10 9 3 0.9 0.9 0.3 200
## 5 0.10 6 5 0.9 0.9 0.1 200
## 4 0.30 9 5 0.7 0.9 0.1 44
## 8 0.10 3 3 0.7 0.7 0.1 200
## 12 0.10 3 3 0.9 0.7 0.3 200
## 7 0.05 3 5 0.9 0.7 0.1 200
## 10 0.10 9 1 0.9 0.7 0.3 200
## 3 0.05 3 1 0.9 0.7 0.0 200
## 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
## 9 111.4454
## 11 117.3367
## 2 118.5624
## 5 123.7198
## 4 133.5118
## 8 137.9191
## 12 148.9200
## 7 155.1739
## 10 158.1116
## 3 170.2107
## 6 572.3070
## 1 582.1593
# 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] 6
##
## $min_child_weight
## [1] 3
##
## $subsample
## [1] 0.7
##
## $colsample_bytree
## [1] 0.9
##
## $gamma
## [1] 0.3
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
# MAPE
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100
# MSE
mse_test <- mean((test_y - predicciones_test)^2)
# Mostrar mƩtricas en conjunto de prueba
cat("\nMƩtricas en conjunto de prueba:\n")
##
## MƩtricas en conjunto de prueba:
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 2.397552
cat("MSE del modelo XGBoost:", mse_test, "\n\n")
## MSE del modelo XGBoost: 4468.535
# 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
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)
# Mostrar mƩtricas en conjunto completo
cat("MƩtricas en conjunto completo:\n")
## MƩtricas en conjunto completo:
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 2.320439
cat("MSE del modelo XGBoost:", mse_completo, "\n\n")
## MSE del modelo XGBoost: 2374.986
# 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 6.879414e-01 0.379835107 0.30601660
## 2: Cant 3.092763e-01 0.348868818 0.23838174
## 3: Precio_Final_Unitario 1.889956e-03 0.158134574 0.21058091
## 4: Descuento_Porcentaje 7.966024e-04 0.108656527 0.16680498
## 5: Tiempo 9.574946e-05 0.004504973 0.07821577
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3929788", # Cambia este ID para cada producto
Modelo = "XGBoost",
MAPE = mape_completo,
MSE = mse_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: 2600.543
##
## 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: 94
## RMSE en validación cruzada: 755.4539
##
## 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: 713.1244
##
## 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: 51
## RMSE en validación cruzada: 1264.852
##
## 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: 80
## RMSE en validación cruzada: 1131.861
##
## 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: 3122.179
##
## 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: 196
## RMSE en validación cruzada: 1533.801
##
## 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: 165
## RMSE en validación cruzada: 1176.676
##
## 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: 172
## RMSE en validación cruzada: 955.5953
##
## 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: 719.8701
##
## 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: 193
## RMSE en validación cruzada: 766.8847
##
## 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: 124
## RMSE en validación cruzada: 790.5288
# 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
## 2 0.10 9 3 0.9 0.9 0.3 94
## 11 0.05 6 3 0.7 0.9 0.0 193
## 12 0.10 3 3 0.9 0.7 0.3 124
## 9 0.05 6 3 0.7 0.9 0.3 172
## 5 0.10 6 5 0.9 0.9 0.1 80
## 8 0.10 3 3 0.7 0.7 0.1 165
## 4 0.30 9 5 0.7 0.9 0.1 51
## 7 0.05 3 5 0.9 0.7 0.1 196
## 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 713.1244
## 10 719.8701
## 2 755.4539
## 11 766.8847
## 12 790.5288
## 9 955.5953
## 5 1131.8614
## 8 1176.6762
## 4 1264.8522
## 7 1533.8006
## 1 2600.5432
## 6 3122.1785
# 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
# MAPE
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100
# MSE
mse_test <- mean((test_y - predicciones_test)^2)
# Mostrar mƩtricas en conjunto de prueba
cat("\nMƩtricas en conjunto de prueba:\n")
##
## MƩtricas en conjunto de prueba:
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 1.898915
cat("MSE del modelo XGBoost:", mse_test, "\n\n")
## MSE del modelo XGBoost: 213902.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
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)
# Mostrar mƩtricas en conjunto completo
cat("MƩtricas en conjunto completo:\n")
## MƩtricas en conjunto completo:
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 1.70523
cat("MSE del modelo XGBoost:", mse_completo, "\n\n")
## MSE del modelo XGBoost: 78512.4
# 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 0.583969652 0.34587937 0.32933579
## 2: Costo_Venta 0.396423232 0.33601261 0.29059041
## 3: Precio_Final_Unitario 0.011807682 0.11761283 0.15498155
## 4: Descuento_Porcentaje 0.006789784 0.13345140 0.14022140
## 5: Tiempo 0.001009650 0.06704379 0.08487085
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3904152", # Cambia este ID para cada producto
Modelo = "XGBoost",
MAPE = mape_completo,
MSE = mse_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: 1374.842
##
## 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: 40
## RMSE en validación cruzada: 971.5163
##
## 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: 867.7989
##
## 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: 15
## RMSE en validación cruzada: 994.0331
##
## 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: 45
## RMSE en validación cruzada: 980.8653
##
## 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: 1368.716
##
## 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: 200
## RMSE en validación cruzada: 958.9118
##
## 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: 159
## RMSE en validación cruzada: 916.4004
##
## 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: 121
## RMSE en validación cruzada: 1021.42
##
## 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: 932.0638
##
## 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: 200
## RMSE en validación cruzada: 876.858
##
## 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: 116
## RMSE en validación cruzada: 955.6755
# 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
## 11 0.05 6 3 0.7 0.9 0.0 200
## 8 0.10 3 3 0.7 0.7 0.1 159
## 10 0.10 9 1 0.9 0.7 0.3 200
## 12 0.10 3 3 0.9 0.7 0.3 116
## 7 0.05 3 5 0.9 0.7 0.1 200
## 2 0.10 9 3 0.9 0.9 0.3 40
## 5 0.10 6 5 0.9 0.9 0.1 45
## 4 0.30 9 5 0.7 0.9 0.1 15
## 9 0.05 6 3 0.7 0.9 0.3 121
## 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 867.7989
## 11 876.8580
## 8 916.4004
## 10 932.0638
## 12 955.6755
## 7 958.9118
## 2 971.5163
## 5 980.8653
## 4 994.0331
## 9 1021.4196
## 6 1368.7160
## 1 1374.8418
# 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.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_155002 <- modelo_final
# Paso 8: Evaluar el modelo
# Predicciones en conjunto de prueba
predicciones_test <- predict(modelo_final, dtest)
# Calcular mƩtricas
# MAPE
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100
# MSE
mse_test <- mean((test_y - predicciones_test)^2)
# Mostrar mƩtricas en conjunto de prueba
cat("\nMƩtricas en conjunto de prueba:\n")
##
## MƩtricas en conjunto de prueba:
cat("MAPE del modelo XGBoost:", mape_test, "\n")
## MAPE del modelo XGBoost: 7.240866
cat("MSE del modelo XGBoost:", mse_test, "\n\n")
## MSE del modelo XGBoost: 583253.4
# 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
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)
# Mostrar mƩtricas en conjunto completo
cat("MƩtricas en conjunto completo:\n")
## MƩtricas en conjunto completo:
cat("MAPE del modelo XGBoost:", mape_completo, "\n")
## MAPE del modelo XGBoost: 7.033567
cat("MSE del modelo XGBoost:", mse_completo, "\n\n")
## MSE del modelo XGBoost: 183831.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 0.557384451 0.40946988 0.35258621
## 2: Costo_Venta 0.403565071 0.25647279 0.24310345
## 3: Precio_Final_Unitario 0.021156515 0.15789577 0.18965517
## 4: Descuento_Porcentaje 0.014753997 0.13444452 0.14482759
## 5: Tiempo 0.003139967 0.04171705 0.06982759
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "155002", # Cambia este ID para cada producto
Modelo = "XGBoost",
MAPE = mape_completo,
MSE = mse_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
## 2 0.10 9 3 0.9 0.9 0.3 64
## 3 0.05 3 1 0.9 0.7 0.0 200
## 10 0.10 9 1 0.9 0.7 0.3 200
## 8 0.10 3 3 0.7 0.7 0.1 100
## 9 0.05 6 3 0.7 0.9 0.3 176
## 11 0.05 6 3 0.7 0.9 0.0 120
## 12 0.10 3 3 0.9 0.7 0.3 199
## 5 0.10 6 5 0.9 0.9 0.1 52
## 7 0.05 3 5 0.9 0.7 0.1 147
## 4 0.30 9 5 0.7 0.9 0.1 32
## 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
## 2 646.8068
## 3 661.7726
## 10 664.3754
## 8 674.4297
## 9 685.7602
## 11 692.0617
## 12 694.4776
## 5 949.4118
## 7 974.9420
## 4 1059.0507
## 1 2439.7500
## 6 2471.2850
# 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)
mape_test <- mean(abs((test_y - predicciones_test) / pmax(test_y, 0.01))) * 100
mse_test <- mean((test_y - predicciones_test)^2)
# 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)
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)
# 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(),
MAPE = numeric(),
MSE = numeric(),
stringsAsFactors = FALSE
)
}
metricas_comparativas <- rbind(metricas_comparativas, data.frame(
Producto = "3678055", # Cambia este ID para cada producto
Modelo = "XGBoost",
MAPE = mape_completo,
MSE = mse_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 MAPE MSE
## 1 155001 ARMA 17.6816429 5.018641e+10
## 2 155001 Regresión Lineal 14.4966756 5.515156e+05
## 3 155001 Random Forest 0.4085599 3.872717e+05
## 4 155001 XGBoost 1.0997118 1.038093e+05
# 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$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
}
# 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$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
}
# 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$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
}
# Ver los datos completos
print("Datos completos para el producto 155001:")
## [1] "Datos completos para el producto 155001:"
print(datos_155001_completo)
## Producto Modelo MAPE MSE
## 1 155001 ARMA/SARIMA NA NA
## 2 155001 Regresión Lineal 14.4966756 551515.6
## 3 155001 Random Forest 0.4085599 387271.7
## 4 155001 XGBoost 1.0997118 103809.3
# 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)
)
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# 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
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

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 MAPE MSE
## 1 3929788 ARMA 12.8370507 2.110452e+10
## 2 3929788 Regresión Lineal 22.3767801 4.790212e+04
## 3 3929788 Random Forest 0.4490773 4.061364e+03
## 4 3929788 XGBoost 2.3204394 2.374986e+03
# 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$MAPE[2])) {
datos_3929788_completo$MAPE[2] <- mape_3929788
}
if (is.na(datos_3929788_completo$MSE[2])) {
datos_3929788_completo$MSE[2] <- mse_3929788
}
# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 3929788
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
}
# Valores para XGBoost
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
}
# Ver los datos completos
print("Datos completos para el producto 3929788:")
## [1] "Datos completos para el producto 3929788:"
print(datos_3929788_completo)
## Producto Modelo MAPE MSE
## 1 3929788 ARMA/SARIMA NA NA
## 2 3929788 Regresión Lineal 22.3767801 47902.121
## 3 3929788 Random Forest 0.4490773 4061.364
## 4 3929788 XGBoost 2.3204394 2374.986
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4",
"Regresión Lineal" = "#ff7f0e",
"Random Forest" = "#2ca02c",
"XGBoost" = "#d62728")
# 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
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# 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
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

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 MAPE MSE
## 1 3904152 ARMA 15.3567919 2.464310e+10
## 2 3904152 Regresión Lineal 3.3434409 1.417412e+05
## 3 3904152 Random Forest 0.2195905 2.336444e+05
## 4 3904152 XGBoost 1.7052297 7.851240e+04
# 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$MAPE[2])) {
datos_3904152_completo$MAPE[2] <- mape_3904152
}
if (is.na(datos_3904152_completo$MSE[2])) {
datos_3904152_completo$MSE[2] <- mse_3904152
}
# Valores para Random Forest
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
}
# Valores para XGBoost
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
}
# Ver los datos completos
print("Datos completos para el producto 3904152:")
## [1] "Datos completos para el producto 3904152:"
print(datos_3904152_completo)
## Producto Modelo MAPE MSE
## 1 3904152 ARMA/SARIMA NA NA
## 2 3904152 Regresión Lineal 3.3434409 141741.2
## 3 3904152 Random Forest 0.2195905 233644.4
## 4 3904152 XGBoost 1.7052297 78512.4
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4",
"Regresión Lineal" = "#ff7f0e",
"Random Forest" = "#2ca02c",
"XGBoost" = "#d62728")
# 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
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# 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
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

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 MAPE MSE
## 1 155002 ARMA 25.833028 3.691080e+10
## 2 155002 Regresión Lineal 19.918004 7.925161e+05
## 3 155002 XGBoost 7.033567 1.838318e+05
# 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$MAPE[2])) {
datos_155002_completo$MAPE[2] <- mape_155002
}
if (is.na(datos_155002_completo$MSE[2])) {
datos_155002_completo$MSE[2] <- mse_155002
}
# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 155002
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
}
# Valores para XGBoost
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
}
# Ver los datos completos
print("Datos completos para el producto 155002:")
## [1] "Datos completos para el producto 155002:"
print(datos_155002_completo)
## Producto Modelo MAPE MSE
## 1 155002 ARMA/SARIMA NA NA
## 2 155002 Regresión Lineal 19.9180037 792516.1
## 3 155002 Random Forest 0.2172073 230895.8
## 4 155002 XGBoost 7.0335667 183831.8
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4",
"Regresión Lineal" = "#ff7f0e",
"Random Forest" = "#2ca02c",
"XGBoost" = "#d62728")
# 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
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# 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
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

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 MAPE MSE
## 1 3678055 ARMA 22.7377450 3.134333e+10
## 2 3678055 Regresión Lineal 2.9008020 2.122053e+05
## 3 3678055 Random Forest 0.5500920 1.834028e+05
## 4 3678055 Random Forest 0.2172073 2.308958e+05
## 5 3678055 XGBoost 0.2564149 2.464722e+05
# 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$MAPE[2])) {
datos_3678055_completo$MAPE[2] <- mape_3678055
}
if (is.na(datos_3678055_completo$MSE[2])) {
datos_3678055_completo$MSE[2] <- mse_3678055
}
# Valores para Random Forest
# Si ya ejecutaste la sección de Random Forest para el producto 3678055
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
}
# Valores para XGBoost
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
}
# Ver los datos completos
print("Datos completos para el producto 3678055:")
## [1] "Datos completos para el producto 3678055:"
print(datos_3678055_completo)
## Producto Modelo MAPE MSE
## 1 3678055 ARMA/SARIMA NA NA
## 2 3678055 Regresión Lineal 2.9008020 212205.3
## 3 3678055 Random Forest 0.5500920 183402.8
## 4 3678055 Random Forest 0.2172073 230895.8
## 5 3678055 XGBoost 0.2564149 246472.2
# Definir colores para los modelos
colores_modelos <- c("ARMA/SARIMA" = "#1f77b4",
"Regresión Lineal" = "#ff7f0e",
"Random Forest" = "#2ca02c",
"XGBoost" = "#d62728")
# 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
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_bar()`).
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

# 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
## Warning: Removed 1 row containing missing values or values outside the scale range
## (`geom_bar()`).
## Removed 1 row containing missing values or values outside the scale range
## (`geom_text()`).

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 30.2314808 0.9301839 155001
## 2 Random Forest 20.4235912 0.9681360 155001
## 3 XGBoost 10.4223865 0.9917020 155001
## 4 Linear Regression 0.9961090 0.9745183 3929788
## 5 Random Forest 0.7372416 0.9860417 3929788
## 6 XGBoost 0.3352396 0.9971138 3929788
## 7 Linear Regression 63.2610620 0.8980785 3904152
## 8 Random Forest 12.2140718 0.9962006 3904152
## 9 XGBoost 11.1592529 0.9968285 3904152
## 10 Linear Regression 25.7977520 0.9226069 155002
## 11 Random Forest 10.3007416 0.9876611 155002
## 12 XGBoost 5.1695872 0.9968922 155002
## 13 Linear Regression 110.6053375 0.8924438 3678055
## 14 Random Forest 30.7411253 0.9916915 3678055
## 15 XGBoost 18.7278222 0.9969164 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),
Precio_Lista_Unitario = median(Precio_Lista_Unitario, na.rm = TRUE),
Descuento_Porcentaje = median(Descuento_Porcentaje, na.rm = TRUE),
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)

LS0tCnRpdGxlOiAiPHNwYW4gc3R5bGU9J2NvbG9yOiBicm93bjsnPkVWSURFTkNJQSBOT1ZFTTwvc3Bhbj4iCmF1dGhvcjogIkVxdWlwbyA0IgpkYXRlOiAiMjAyNC0wMy0wNCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUgCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIHRoZW1lOiBqb3VybmFsCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUKZWRpdG9yX3M6IAogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlCmVkaXRvcl9vcHRpb25zOiAKICBjaHVua19vdXRwdXRfdHlwZTogY29uc29sZQotLS0KCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibGFjazsiPiAgTGlicmVyaWFzIDwvc3Bhbj4gCgoKYGBge3J9CiMgTGlicmVyw61hcyBuZWNlc2FyaWFzCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHJlYWR4bCkKbGlicmFyeShwdXJycikKbGlicmFyeShrbml0cikKI2luc3RhbGwucGFja2FnZXMoImthYmxlRXh0cmEiKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoZ2dwbG90MikKI2luc3RhbGwucGFja2FnZXMoImlncmFwaCIpCmxpYnJhcnkoaWdyYXBoKQojaW5zdGFsbC5wYWNrYWdlcygiZm9yZWNhc3QiKQojaW5zdGFsbC5wYWNrYWdlcygibHVicmlkYXRlIikKbGlicmFyeShmb3JlY2FzdCkKbGlicmFyeShsdWJyaWRhdGUpCmxpYnJhcnkoY29ycnBsb3QpCmxpYnJhcnkoUkNvbG9yQnJld2VyKQojaW5zdGFsbC5wYWNrYWdlcygiZ2djb3JycGxvdCIpCmxpYnJhcnkoZ2djb3JycGxvdCkKbGlicmFyeShjYXJldCkKbGlicmFyeShjYXIpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQojaW5zdGFsbC5wYWNrYWdlcygieGdib29zdCIpCmxpYnJhcnkoeGdib29zdCkKI2luc3RhbGwucGFja2FnZXMoInBhdGNod29yayIpCmxpYnJhcnkocGF0Y2h3b3JrKQpgYGAKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibGFjazsiPiAgQ2FyZ2EgZGUgZGF0b3MgPC9zcGFuPiAKCmBgYHtyfQojIENhcmdhciBhcmNoaXZvIEV4Y2VsIGRlc2RlIHJ1dGEgbG9jYWwKcnV0YSA8LSAiL1VzZXJzL29zY2FyY2FzdGFuZWRhZ2FyY2lhL0Rvd25sb2Fkcy9JQSBjb24gaW1wYWN0byBlbXByZXNhcmlhbC9maWx0ZXJlZF9kYXRhLnhsc3giCmRhdG9zIDwtIHJlYWRfZXhjZWwocnV0YSkKCiMgVmlzdGEgZ2VuZXJhCmhlYWQoZGF0b3MpCnN0cihkYXRvcykKYGBgCgpgYGB7cn0KIyBPYnRlbmVyIGxvcyA1IHByb2R1Y3RvcyBtw6FzIHZlbmRpZG9zIChwb3IgdmFsb3IpCnRvcF9pZHMgPC0gZGF0b3MgJT4lCiAgZ3JvdXBfYnkoSURfSW52ZW50YXJpbykgJT4lCiAgc3VtbWFyaXNlKFZlbnRhc19Ub3RhbGVzID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKGRlc2MoVmVudGFzX1RvdGFsZXMpKSAlPiUKICBzbGljZV9oZWFkKG4gPSA1KSAlPiUKICBwdWxsKElEX0ludmVudGFyaW8pCgpwcmludCgiVG9wIDUgcHJvZHVjdG9zIG3DoXMgdmVuZGlkb3MgKElEX0ludmVudGFyaW8pOiIpCnByaW50KHRvcF9pZHMpCgpgYGAKCmBgYHtyfQojIEZpbHRyYXIgZGF0b3MgdsOhbGlkb3MKZGF0b3NfZmlsdHJhZG9zIDwtIGRhdG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvICVpbiUgdG9wX2lkcykgJT4lCiAgZmlsdGVyKCFpcy5uYShQcmVjaW9fRmluYWxfVW5pdGFyaW8pKQoKIyBDb250YXIgb2JzZXJ2YWNpb25lcyBwb3IgcHJvZHVjdG8KY29udGVvIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBjb3VudChJRF9JbnZlbnRhcmlvLCBzb3J0ID0gVFJVRSkKCnByaW50KCJOw7ptZXJvIGRlIHJlZ2lzdHJvcyBwb3IgcHJvZHVjdG8gZW4gZGF0b3NfZmlsdHJhZG9zOiIpCnByaW50KGNvbnRlbykKCiMgVmVyaWZpY2Egc2kgaGF5IHN1ZmljaWVudGVzIGRhdG9zCmlmIChucm93KGRhdG9zX2ZpbHRyYWRvcykgPT0gMCkgewogIHN0b3AoIk5vIGhheSBkYXRvcyBzdWZpY2llbnRlcyBsdWVnbyBkZSBmaWx0cmFyIHBvciB0b3BfaWRzIHkgcHJlY2lvcyB2w6FsaWRvcy4iKQp9CgpgYGAKCmBgYHtyfQojIENvbWJpbmFjaW9uZXMgZGUgcGFyZXMKcHJvZHVjdG9zIDwtIHVuaXF1ZShkYXRvc19maWx0cmFkb3MkSURfSW52ZW50YXJpbykKcGFyZXNfcHJvZHVjdG9zIDwtIGNvbWJuKHByb2R1Y3RvcywgMiwgc2ltcGxpZnkgPSBGQUxTRSkKCiMgSW5pY2lhbGl6YXIgcmVzdWx0YWRvcwpyZXN1bHRhZG9zX2tzIDwtIG1hcF9kZihwYXJlc19wcm9kdWN0b3MsIGZ1bmN0aW9uKHBhcikgewogIHByb2QxIDwtIHBhclsxXQogIHByb2QyIDwtIHBhclsyXQogIAogIHByZWNpb3MxIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICAgIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2QxKSAlPiUKICAgIHB1bGwoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKQogIAogIHByZWNpb3MyIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICAgIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IHByb2QyKSAlPiUKICAgIHB1bGwoUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKQogIAogIHByaW50KHBhc3RlKCJDb21wYXJhbmRvIHByb2R1Y3RvcyIsIHByb2QxLCAidnMiLCBwcm9kMikpCiAgcHJpbnQocGFzdGUoIkNhbnRpZGFkIGRlIHByZWNpb3M6IiwgbGVuZ3RoKHByZWNpb3MxKSwgInkiLCBsZW5ndGgocHJlY2lvczIpKSkKICAKICBpZiAobGVuZ3RoKHByZWNpb3MxKSA+PSA1ICYgbGVuZ3RoKHByZWNpb3MyKSA+PSA1KSB7CiAgICBwcnVlYmEgPC0gc3VwcHJlc3NXYXJuaW5ncyhrcy50ZXN0KHByZWNpb3MxLCBwcmVjaW9zMikpCiAgICBkYXRhLmZyYW1lKAogICAgICBQcm9kdWN0b18xID0gcHJvZDEsCiAgICAgIFByb2R1Y3RvXzIgPSBwcm9kMiwKICAgICAgRCA9IHJvdW5kKHBydWViYSRzdGF0aXN0aWMsIDQpLAogICAgICBwX3ZhbHVlID0gcm91bmQocHJ1ZWJhJHAudmFsdWUsIDQpLAogICAgICBDb25jbHVzaW9uID0gaWZlbHNlKHBydWViYSRwLnZhbHVlID4gMC4wNSwgIkRpc3RyaWJ1Y2lvbmVzIHNpbWlsYXJlcyIsICJEaXN0cmlidWNpb25lcyBkaWZlcmVudGVzIikKICAgICkKICB9IGVsc2UgewogICAgZGF0YS5mcmFtZSgKICAgICAgUHJvZHVjdG9fMSA9IHByb2QxLAogICAgICBQcm9kdWN0b18yID0gcHJvZDIsCiAgICAgIEQgPSBOQSwKICAgICAgcF92YWx1ZSA9IE5BLAogICAgICBDb25jbHVzaW9uID0gIkRhdG9zIGluc3VmaWNpZW50ZXMiCiAgICApCiAgfQp9KQoKcHJpbnQoIlJlc3VsdGFkb3MgZGUgbGEgcHJ1ZWJhIEtTOiIpCnByaW50KHJlc3VsdGFkb3Nfa3MpCgpgYGAKCmBgYHtyfQoKIyBGaWx0cmFyIGxvcyBwcm9kdWN0b3MKZGZfMTU1MDAxIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDEpICU+JQogIHNlbGVjdChQcmVjaW9fRmluYWxfVW5pdGFyaW8pICU+JQogIG11dGF0ZShQcm9kdWN0byA9ICIxNTUwMDEiKQoKZGZfMTU1MDAyIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDIpICU+JQogIHNlbGVjdChQcmVjaW9fRmluYWxfVW5pdGFyaW8pICU+JQogIG11dGF0ZShQcm9kdWN0byA9ICIxNTUwMDIiKQoKIyBVbmlyIGVuIHVuIHNvbG8gZGF0YWZyYW1lCmRmX2VjZGYgPC0gYmluZF9yb3dzKGRmXzE1NTAwMSwgZGZfMTU1MDAyKQoKIyBHcmFmaWNhciBFQ0RGCmdncGxvdChkZl9lY2RmLCBhZXMoeCA9IFByZWNpb19GaW5hbF9Vbml0YXJpbywgY29sb3IgPSBQcm9kdWN0bykpICsKICBzdGF0X2VjZGYoZ2VvbSA9ICJzdGVwIiwgc2l6ZSA9IDEpICsKICBsYWJzKHRpdGxlID0gIkVDREYgZGUgUHJlY2lvIEZpbmFsIFVuaXRhcmlvOiBQcm9kdWN0b3MgMTU1MDAxIHZzIDE1NTAwMiIsCiAgICAgICB4ID0gIlByZWNpbyBGaW5hbCBVbml0YXJpbyIsCiAgICAgICB5ID0gIkZ1bmNpw7NuIGRlIERpc3RyaWJ1Y2nDs24gQWN1bXVsYWRhIChFQ0RGKSIsCiAgICAgICBjb2xvciA9ICJQcm9kdWN0byIpICsKICB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KQoKYGBgCgoKPCEtLSBBUk1BIC0tPgojIEFSTUEgCgojIFBSRURJQ0NJT05FUyBERSBWRU5UQVMKCgo8IS0tIFBST0RVQ1RPIDE1NTAwMSAtLT4KIyMgUFJPRFVDVE8gMTU1MDAxCgpgYGB7ciBhcm1hLTE1NTAwMX0KIyBQcm9kdWN0byAxNTUwMDEKaWRfcHJvZCA8LSAxNTUwMDEKCiMgQ3JlYXIgbGEgc2VyaWUgZGUgdGllbXBvIG1lbnN1YWwKdmVudGFzX21lbnN1YWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpKSAlPiUKICBncm91cF9ieShGZWNoYSkgJT4lCiAgc3VtbWFyaXNlKFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKEZlY2hhKQoKc2VyaWVfdHMgPC0gdHModmVudGFzX21lbnN1YWxlcyRWZW50YSwgZnJlcXVlbmN5ID0gMTIsCiAgICAgICAgICAgICAgIHN0YXJ0ID0gYyh5ZWFyKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgbW9udGgobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSkpCgojIE1vZGVsbyBBUk1BCm1vZGVsb19hcm1hIDwtIGF1dG8uYXJpbWEoc2VyaWVfdHMsIHNlYXNvbmFsID0gRkFMU0UsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkKZm9yZWNhc3RfbW9kZWxvIDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hLCBoID0gMykKCiMgR3LDoWZpY28gZGVsIHByb27Ds3N0aWNvCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gbWVuc3VhbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksCiAgICAgICB4ID0gIk1lcyIsIHkgPSAiVmVudGFzICgkKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKG1vZGVsb19hcm1hKQptYXBlIDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMAptc2UgPC0gbWVhbigoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKQoKIyBDcmVhciB0YWJsYSBkZSBtw6l0cmljYXMKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSBpZF9wcm9kLAogIE1vZGVsbyA9ICJBUk1BIiwKICBNQVBFID0gbWFwZSwKICBNU0UgPSBtc2UKKSkKCiMgTW9zdHJhciB0YWJsYSBwYXJhIGVzdGUgcHJvZHVjdG8KdGFpbChtZXRyaWNhc19jb21wYXJhdGl2YXMsIDEpICU+JQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gcGFzdGUoIk3DqXRyaWNhcyBkZWwgbW9kZWxvIEFSTUEgcGFyYSBQcm9kdWN0byIsIGlkX3Byb2QpKSAlPiUKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgoKPCEtLSBQUk9EVUNUTyAzOTI5Nzg4IC0tPgojIyBQUk9EVUNUTyAzOTI5Nzg4CgpgYGB7ciBhcm1hLTM5Mjk3ODh9CiMgUHJvZHVjdG8gMzkyOTc4OAoKaWRfcHJvZCA8LSAzOTI5Nzg4CgojIENyZWFyIGxhIHNlcmllIGRlIHRpZW1wbyBtZW5zdWFsCnZlbnRhc19tZW5zdWFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IGlkX3Byb2QpICU+JQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQogIHN1bW1hcmlzZShWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZShGZWNoYSkKCnNlcmllX3RzIDwtIHRzKHZlbnRhc19tZW5zdWFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDEyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIG1vbnRoKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSkpKQoKIyBNb2RlbG8gQVJNQQptb2RlbG9fYXJtYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpCmZvcmVjYXN0X21vZGVsbyA8LSBmb3JlY2FzdChtb2RlbG9fYXJtYSwgaCA9IDMpCgojIEdyw6FmaWNvIGRlbCBwcm9uw7NzdGljbwphdXRvcGxvdChmb3JlY2FzdF9tb2RlbG8pICsKICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLAogICAgICAgeCA9ICJNZXMiLCB5ID0gIlZlbnRhcyAoJCkiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIENhbGN1bGFyIG3DqXRyaWNhcwpmaXR0ZWRfdmFsdWVzIDwtIGZpdHRlZChtb2RlbG9fYXJtYSkKbWFwZSA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDAKbXNlIDwtIG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikKCiMgQ3JlYXIgdGFibGEgZGUgbcOpdHJpY2FzCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGUsCiAgTVNFID0gbXNlCikpCgojIE1vc3RyYXIgdGFibGEgcGFyYSBlc3RlIHByb2R1Y3RvCnRhaWwobWV0cmljYXNfY29tcGFyYXRpdmFzLCAxKSAlPiUKICBrbml0cjo6a2FibGUoY2FwdGlvbiA9IHBhc3RlKCJNw6l0cmljYXMgZGVsIG1vZGVsbyBBUk1BIHBhcmEgUHJvZHVjdG8iLCBpZF9wcm9kKSkgJT4lCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpCmBgYAoKCjwhLS0gUFJPRFVDVE8gMzkwNDE1MiAtLT4KIyMgUFJPRFVDVE8gMzkwNDE1MgoKYGBge3IgYXJtYS0zOTA0MTUyfQojIFByb2R1Y3RvIDM5MDQxNTIKaWRfcHJvZCA8LSAzOTA0MTUyCgojIENyZWFyIGxhIHNlcmllIGRlIHRpZW1wbyBtZW5zdWFsCnZlbnRhc19tZW5zdWFsZXMgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IGlkX3Byb2QpICU+JQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSkgJT4lCiAgZ3JvdXBfYnkoRmVjaGEpICU+JQogIHN1bW1hcmlzZShWZW50YSA9IHN1bShWZW50YSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgYXJyYW5nZShGZWNoYSkKCnNlcmllX3RzIDwtIHRzKHZlbnRhc19tZW5zdWFsZXMkVmVudGEsIGZyZXF1ZW5jeSA9IDEyLAogICAgICAgICAgICAgICBzdGFydCA9IGMoeWVhcihtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpLCAKICAgICAgICAgICAgICAgICAgICAgICAgIG1vbnRoKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSkpKQoKIyBNb2RlbG8gQVJNQQptb2RlbG9fYXJtYSA8LSBhdXRvLmFyaW1hKHNlcmllX3RzLCBzZWFzb25hbCA9IEZBTFNFLCBzdGVwd2lzZSA9IEZBTFNFLCBhcHByb3hpbWF0aW9uID0gRkFMU0UpCmZvcmVjYXN0X21vZGVsbyA8LSBmb3JlY2FzdChtb2RlbG9fYXJtYSwgaCA9IDMpCgojIEdyw6FmaWNvIGRlbCBwcm9uw7NzdGljbwphdXRvcGxvdChmb3JlY2FzdF9tb2RlbG8pICsKICBsYWJzKHRpdGxlID0gcGFzdGUoIlByb27Ds3N0aWNvIG1lbnN1YWwgZGUgdmVudGFzIC0gQVJNQSAoUHJvZHVjdG8iLCBpZF9wcm9kLCAiKSIpLAogICAgICAgeCA9ICJNZXMiLCB5ID0gIlZlbnRhcyAoJCkiKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIENhbGN1bGFyIG3DqXRyaWNhcwpmaXR0ZWRfdmFsdWVzIDwtIGZpdHRlZChtb2RlbG9fYXJtYSkKbWFwZSA8LSBtZWFuKGFicygoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKSAvIHBtYXgoc2VyaWVfdHMsIDAuMDEpKSkgKiAxMDAKbXNlIDwtIG1lYW4oKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcyleMikKCiMgQ3JlYXIgdGFibGEgZGUgbcOpdHJpY2FzCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gaWRfcHJvZCwKICBNb2RlbG8gPSAiQVJNQSIsCiAgTUFQRSA9IG1hcGUsCiAgTVNFID0gbXNlCikpCgojIE1vc3RyYXIgdGFibGEgcGFyYSBlc3RlIHByb2R1Y3RvCnRhaWwobWV0cmljYXNfY29tcGFyYXRpdmFzLCAxKSAlPiUKICBrbml0cjo6a2FibGUoY2FwdGlvbiA9IHBhc3RlKCJNw6l0cmljYXMgZGVsIG1vZGVsbyBBUk1BIHBhcmEgUHJvZHVjdG8iLCBpZF9wcm9kKSkgJT4lCiAga2FibGVFeHRyYTo6a2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpCmBgYAoKCjwhLS0gUFJPRFVDVE8gMTU1MDAyIC0tPgojIyBQUk9EVUNUTyAxNTUwMDIKCmBgYHtyIGFybWEtMTU1MDAyfQojIFByb2R1Y3RvIDE1NTAwMgppZF9wcm9kIDwtIDE1NTAwMgoKIyBDcmVhciBsYSBzZXJpZSBkZSB0aWVtcG8gbWVuc3VhbAp2ZW50YXNfbWVuc3VhbGVzIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBpZF9wcm9kKSAlPiUKICBtdXRhdGUoRmVjaGEgPSBhcy5EYXRlKGZsb29yX2RhdGUoVHJ4X0ZlY2hhLCAibW9udGgiKSkpICU+JQogIGdyb3VwX2J5KEZlY2hhKSAlPiUKICBzdW1tYXJpc2UoVmVudGEgPSBzdW0oVmVudGEsIG5hLnJtID0gVFJVRSkpICU+JQogIGFycmFuZ2UoRmVjaGEpCgpzZXJpZV90cyA8LSB0cyh2ZW50YXNfbWVuc3VhbGVzJFZlbnRhLCBmcmVxdWVuY3kgPSAxMiwKICAgICAgICAgICAgICAgc3RhcnQgPSBjKHllYXIobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICBtb250aChtaW4odmVudGFzX21lbnN1YWxlcyRGZWNoYSkpKSkKCiMgTW9kZWxvIEFSTUEKbW9kZWxvX2FybWEgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQpmb3JlY2FzdF9tb2RlbG8gPC0gZm9yZWNhc3QobW9kZWxvX2FybWEsIGggPSAzKQoKIyBHcsOhZmljbyBkZWwgcHJvbsOzc3RpY28KYXV0b3Bsb3QoZm9yZWNhc3RfbW9kZWxvKSArCiAgbGFicyh0aXRsZSA9IHBhc3RlKCJQcm9uw7NzdGljbyBtZW5zdWFsIGRlIHZlbnRhcyAtIEFSTUEgKFByb2R1Y3RvIiwgaWRfcHJvZCwgIikiKSwKICAgICAgIHggPSAiTWVzIiwgeSA9ICJWZW50YXMgKCQpIikgKwogIHRoZW1lX21pbmltYWwoKQoKIyBDYWxjdWxhciBtw6l0cmljYXMKZml0dGVkX3ZhbHVlcyA8LSBmaXR0ZWQobW9kZWxvX2FybWEpCm1hcGUgPC0gbWVhbihhYnMoKHNlcmllX3RzIC0gZml0dGVkX3ZhbHVlcykgLyBwbWF4KHNlcmllX3RzLCAwLjAxKSkpICogMTAwCm1zZSA8LSBtZWFuKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpXjIpCgojIENyZWFyIHRhYmxhIGRlIG3DqXRyaWNhcwppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIE1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9IGlkX3Byb2QsCiAgTW9kZWxvID0gIkFSTUEiLAogIE1BUEUgPSBtYXBlLAogIE1TRSA9IG1zZQopKQoKIyBNb3N0cmFyIHRhYmxhIHBhcmEgZXN0ZSBwcm9kdWN0bwp0YWlsKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgMSkgJT4lCiAga25pdHI6OmthYmxlKGNhcHRpb24gPSBwYXN0ZSgiTcOpdHJpY2FzIGRlbCBtb2RlbG8gQVJNQSBwYXJhIFByb2R1Y3RvIiwgaWRfcHJvZCkpICU+JQogIGthYmxlRXh0cmE6OmthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCgo8IS0tUHJvZHVjdG8gMzY3ODA1NSAtLT4KIyMgUFJPRFVDVE8gMzY3ODA1NQpgYGB7ciBhcm1hLTM2NzgwNTV9CiMgUHJvZHVjdG8gMzY3ODA1NQppZF9wcm9kIDwtIDM2NzgwNTUKCiMgQ3JlYXIgbGEgc2VyaWUgZGUgdGllbXBvIG1lbnN1YWwKdmVudGFzX21lbnN1YWxlcyA8LSBkYXRvc19maWx0cmFkb3MgJT4lCiAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gaWRfcHJvZCkgJT4lCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpKSAlPiUKICBncm91cF9ieShGZWNoYSkgJT4lCiAgc3VtbWFyaXNlKFZlbnRhID0gc3VtKFZlbnRhLCBuYS5ybSA9IFRSVUUpKSAlPiUKICBhcnJhbmdlKEZlY2hhKQoKc2VyaWVfdHMgPC0gdHModmVudGFzX21lbnN1YWxlcyRWZW50YSwgZnJlcXVlbmN5ID0gMTIsCiAgICAgICAgICAgICAgIHN0YXJ0ID0gYyh5ZWFyKG1pbih2ZW50YXNfbWVuc3VhbGVzJEZlY2hhKSksIAogICAgICAgICAgICAgICAgICAgICAgICAgbW9udGgobWluKHZlbnRhc19tZW5zdWFsZXMkRmVjaGEpKSkpCgojIE1vZGVsbyBBUk1BCm1vZGVsb19hcm1hIDwtIGF1dG8uYXJpbWEoc2VyaWVfdHMsIHNlYXNvbmFsID0gRkFMU0UsIHN0ZXB3aXNlID0gRkFMU0UsIGFwcHJveGltYXRpb24gPSBGQUxTRSkKZm9yZWNhc3RfbW9kZWxvIDwtIGZvcmVjYXN0KG1vZGVsb19hcm1hLCBoID0gMykKCiMgR3LDoWZpY28gZGVsIHByb27Ds3N0aWNvCmF1dG9wbG90KGZvcmVjYXN0X21vZGVsbykgKwogIGxhYnModGl0bGUgPSBwYXN0ZSgiUHJvbsOzc3RpY28gbWVuc3VhbCBkZSB2ZW50YXMgLSBBUk1BIChQcm9kdWN0byIsIGlkX3Byb2QsICIpIiksCiAgICAgICB4ID0gIk1lcyIsIHkgPSAiVmVudGFzICgkKSIpICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCmZpdHRlZF92YWx1ZXMgPC0gZml0dGVkKG1vZGVsb19hcm1hKQptYXBlIDwtIG1lYW4oYWJzKChzZXJpZV90cyAtIGZpdHRlZF92YWx1ZXMpIC8gcG1heChzZXJpZV90cywgMC4wMSkpKSAqIDEwMAptc2UgPC0gbWVhbigoc2VyaWVfdHMgLSBmaXR0ZWRfdmFsdWVzKV4yKQoKIyBDcmVhciB0YWJsYSBkZSBtw6l0cmljYXMKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSBpZF9wcm9kLAogIE1vZGVsbyA9ICJBUk1BIiwKICBNQVBFID0gbWFwZSwKICBNU0UgPSBtc2UKKSkKCiMgTW9zdHJhciB0YWJsYSBwYXJhIGVzdGUgcHJvZHVjdG8KdGFpbChtZXRyaWNhc19jb21wYXJhdGl2YXMsIDEpICU+JQogIGtuaXRyOjprYWJsZShjYXB0aW9uID0gcGFzdGUoIk3DqXRyaWNhcyBkZWwgbW9kZWxvIEFSTUEgcGFyYSBQcm9kdWN0byIsIGlkX3Byb2QpKSAlPiUKICBrYWJsZUV4dHJhOjprYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgoKIyBSRUdSRVNJT04gTElORUFMCiMjIE1BUEEgREUgQ0FMT1IKCmBgYHtyIG1hcGFfY2Fsb3JfY29ycmVsYWNpb24sIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgVmFyaWFibGVzIG51bcOpcmljYXMgcmVsZXZhbnRlcwp2YXJzX251bWVyaWNhcyA8LSBjKCJDYW50IiwgIlZlbnRhIiwgIkNvc3RvX1ZlbnRhIiwKICAgICAgICAgICAgICAgICAgICAiUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIiwgIkRlc2N1ZW50b19Qb3JjZW50YWplIikKCiMgUHJlcGFyYWNpw7NuIGRlIGxvcyBkYXRvcwpkYXRvc19jb3IgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIHNlbGVjdChhbGxfb2YodmFyc19udW1lcmljYXMpKSAlPiUKICBuYS5vbWl0KCkKCiMgR2VuZXJhciBsYSBtYXRyaXogZGUgY29ycmVsYWNpw7NuCm1hdHJpel9jb3IgPC0gY29yKGRhdG9zX2NvcikKCiMgQWp1c3RlIGRlbCBncsOhZmljbyBzaW4gbWFyCmdnY29ycnBsb3QobWF0cml6X2NvciwKICAgICAgICAgICBtZXRob2QgPSAic3F1YXJlIiwKICAgICAgICAgICB0eXBlID0gInVwcGVyIiwKICAgICAgICAgICBsYWIgPSBUUlVFLCAKICAgICAgICAgICBsYWJfc2l6ZSA9IDIsICAgICAgICAgICAgICAgICAgICMgTWVqb3IgdGFtYcOxbyBkZSBsb3MgY29lZmljaWVudGVzCiAgICAgICAgICAgdGwuY2V4ID0gMTAsICAgICAgICAgICAgICAgICAgICAjIFRhbWHDsW8gZGUgZXRpcXVldGFzIG3DoXMgZ3JhbmRlCiAgICAgICAgICAgdGwuc3J0ID0gNDUsICAgICAgICAgICAgICAgICAgICAjIFJvdGFjacOzbiBkZSA0NcKwIGRlIGV0aXF1ZXRhcwogICAgICAgICAgIGNvbG9ycyA9IGMoIiM2RDlFQzEiLCAid2hpdGUiLCAiI0U0NjcyNiIpLAogICAgICAgICAgIHRpdGxlID0gIk1hcGEgZGUgQ29ycmVsYWNpw7NuIC0gVmFyaWFibGVzIE51bcOpcmljYXMiLAogICAgICAgICAgIGdndGhlbWUgPSB0aGVtZV9taW5pbWFsKGJhc2Vfc2l6ZSA9IDE0KSArCiAgICAgICAgICAgICB0aGVtZSgKICAgICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSwKICAgICAgICAgICAgICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoYW5nbGUgPSAwLCBoanVzdCA9IDEpKQopCmBgYAoKIyMgUFJPRFVDVE8gMTU1MDAxCmBgYHtyfQojIEZpbHRyYXIgc29sbyBsb3MgZGF0b3MgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDEKZGF0b3NfMTU1MDAxIDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAxNTUwMDEpICU+JQogIHNlbGVjdChWZW50YSwgQ2FudCwgQ29zdG9fVmVudGEsCiAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywgRGVzY3VlbnRvX1BvcmNlbnRhamUsIFRyeF9GZWNoYSkgJT4lCiAgbmEub21pdCgpICAjIEVsaW1pbmFyIGZpbGFzIGNvbiB2YWxvcmVzIE5BCgojIENyZWFyIHVuYSB2YXJpYWJsZSBkZSB0aWVtcG8gY29udGludWEgYmFzYWRhIGVuIGxhIGZlY2hhCmRhdG9zXzE1NTAwMSA8LSBkYXRvc18xNTUwMDEgJT4lCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCAgICMgQXNlZ8O6cmF0ZSBkZSBxdWUgbGEgZmVjaGEgZXN0w6kgZW4gZm9ybWF0byBEYXRlCiAgICAgICAgIFRpZW1wbyA9IGFzLm51bWVyaWMoRmVjaGEgLSBtaW4oRmVjaGEpKSAvICgzMCAqIDI0ICogNjAgKiA2MCkpICAjIFRpZW1wbyBlbiBtZXNlcyAoYWp1c3RhZG8gcG9yIGTDrWFzKQoKIyBWZXJpZmljYXIgbGFzIHByaW1lcmFzIGZpbGFzIHBhcmEgYXNlZ3VyYXJub3MgZGUgcXVlIGxhIHZhcmlhYmxlIGRlIHRpZW1wbyBlc3TDqSBiaWVuIGNyZWFkYQpoZWFkKGRhdG9zXzE1NTAwMSkKCmBgYAoKYGBge3J9CiMgQWp1c3RhciBlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwKbW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMTU1MDAxKQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEpCmBgYAoKYGBge3J9CiMgQWp1c3RlIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwKbW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMTU1MDAxKQoKIyBQcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBhanVzdGFkbwpwcmVkaWNjaW9uZXNfMTU1MDAxIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEsIG5ld2RhdGEgPSBkYXRvc18xNTUwMDEpCgojIENhbGN1bGFyIE1BUEUgKE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvcikKbWFwZV8xNTUwMDEgPC0gbWVhbihhYnMoKGRhdG9zXzE1NTAwMSRWZW50YSAtIHByZWRpY2Npb25lc18xNTUwMDEpIC8gZGF0b3NfMTU1MDAxJFZlbnRhKSkgKiAxMDAKCiMgQ2FsY3VsYXIgTVNFIChNZWFuIFNxdWFyZWQgRXJyb3IpCm1zZV8xNTUwMDEgPC0gbWVhbigoZGF0b3NfMTU1MDAxJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMSleMikKCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzCmNhdCgiTUFQRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMTU1MDAxOiAiLCBtYXBlXzE1NTAwMSwgIlxuIikKY2F0KCJNU0UgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDE1NTAwMTogIiwgbXNlXzE1NTAwMSwgIlxuIikKCiMgRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdW9zIGRlbCBtb2RlbG8KcGFyKG1mcm93ID0gYygyLCAyKSkKcGxvdChtb2RlbG9fcmVncmVzaW9uXzE1NTAwMSkKYGBgCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJlZ3Jlc2nDs24gTGluZWFsIHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMTU1MDAxIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwKICBNQVBFID0gbWFwZV8xNTUwMDEsCiAgTVNFID0gbXNlXzE1NTAwMQopKQpgYGAKCgojIyBQUk9EVUNUTyAzOTI5Nzg4CgpgYGB7cn0KIyBGaWx0cmFyIHNvbG8gbG9zIGRhdG9zIHBhcmEgZWwgcHJvZHVjdG8gMzkyOTc4OApkYXRvc18zOTI5Nzg4IDwtIGRhdG9zX2ZpbHRyYWRvcyAlPiUKICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSAzOTI5Nzg4KSAlPiUKICBzZWxlY3QoVmVudGEsIENhbnQsIENvc3RvX1ZlbnRhLAogICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIERlc2N1ZW50b19Qb3JjZW50YWplLCBUcnhfRmVjaGEpICU+JQogIG5hLm9taXQoKSAgIyBFbGltaW5hciBmaWxhcyBjb24gdmFsb3JlcyBOQQoKIyBDcmVhciB1bmEgdmFyaWFibGUgZGUgdGllbXBvIGNvbnRpbnVhIGJhc2FkYSBlbiBsYSBmZWNoYQpkYXRvc18zOTI5Nzg4IDwtIGRhdG9zXzM5Mjk3ODggJT4lCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCAgICMgQXNlZ8O6cmF0ZSBkZSBxdWUgbGEgZmVjaGEgZXN0w6kgZW4gZm9ybWF0byBEYXRlCiAgICAgICAgIFRpZW1wbyA9IGFzLm51bWVyaWMoRmVjaGEgLSBtaW4oRmVjaGEpKSAvICgzMCAqIDI0ICogNjAgKiA2MCkpICAjIFRpZW1wbyBlbiBtZXNlcyAoYWp1c3RhZG8gcG9yIGTDrWFzKQoKIyBWZXJpZmljYXIgbGFzIHByaW1lcmFzIGZpbGFzIHBhcmEgYXNlZ3VyYXJub3MgZGUgcXVlIGxhIHZhcmlhYmxlIGRlIHRpZW1wbyBlc3TDqSBiaWVuIGNyZWFkYQpoZWFkKGRhdG9zXzM5Mjk3ODgpCmBgYAoKYGBge3J9CiMgQWp1c3RhciBlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwKbW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4IDwtIGxtKFZlbnRhIH4gQ2FudCArIENvc3RvX1ZlbnRhICsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvICsgRGVzY3VlbnRvX1BvcmNlbnRhamUgKyBUaWVtcG8sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRvc18zOTI5Nzg4KQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4KQpgYGAKCmBgYHtyfQojIFByZWRpY2Npb25lcyB1c2FuZG8gZWwgbW9kZWxvIGFqdXN0YWRvCnByZWRpY2Npb25lc18zOTI5Nzg4IDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTI5Nzg4LCBuZXdkYXRhID0gZGF0b3NfMzkyOTc4OCkKCiMgQ2FsY3VsYXIgTUFQRSAoTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yKQojIEHDsWFkaW1vcyBwcm90ZWNjacOzbiBjb250cmEgZGl2aXNpw7NuIHBvciBjZXJvCm1hcGVfMzkyOTc4OCA8LSBtZWFuKGFicygoZGF0b3NfMzkyOTc4OCRWZW50YSAtIHByZWRpY2Npb25lc18zOTI5Nzg4KSAvIHBtYXgoZGF0b3NfMzkyOTc4OCRWZW50YSwgMC4wMSkpKSAqIDEwMAoKIyBDYWxjdWxhciBNU0UgKE1lYW4gU3F1YXJlZCBFcnJvcikKbXNlXzM5Mjk3ODggPC0gbWVhbigoZGF0b3NfMzkyOTc4OCRWZW50YSAtIHByZWRpY2Npb25lc18zOTI5Nzg4KV4yKQoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTI5Nzg4OiAiLCBtYXBlXzM5Mjk3ODgsICJcbiIpCmNhdCgiTVNFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTI5Nzg4OiAiLCBtc2VfMzkyOTc4OCwgIlxuIikKCiMgRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdW9zIGRlbCBtb2RlbG8KcGFyKG1mcm93ID0gYygyLCAyKSkKcGxvdChtb2RlbG9fcmVncmVzaW9uXzM5Mjk3ODgpCmBgYApgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSZWdyZXNpw7NuIExpbmVhbCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIE1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTI5Nzg4IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwKICBNQVBFID0gbWFwZV8zOTI5Nzg4LAogIE1TRSA9IG1zZV8zOTI5Nzg4CikpCmBgYAoKCiMjIFBST0RVQ1RPIDM5MDQxNTIKCmBgYHtyfQojIEZpbHRyYXIgc29sbyBsb3MgZGF0b3MgcGFyYSBlbCBwcm9kdWN0byAzOTA0MTUyCmRhdG9zXzM5MDQxNTIgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM5MDQxNTIpICU+JQogIHNlbGVjdChWZW50YSwgQ2FudCwgQ29zdG9fVmVudGEsCiAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywgRGVzY3VlbnRvX1BvcmNlbnRhamUsIFRyeF9GZWNoYSkgJT4lCiAgbmEub21pdCgpICAjIEVsaW1pbmFyIGZpbGFzIGNvbiB2YWxvcmVzIE5BCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIGRlIHRpZW1wbyBjb250aW51YSBiYXNhZGEgZW4gbGEgZmVjaGEKZGF0b3NfMzkwNDE1MiA8LSBkYXRvc18zOTA0MTUyICU+JQogIG11dGF0ZShGZWNoYSA9IGFzLkRhdGUoZmxvb3JfZGF0ZShUcnhfRmVjaGEsICJtb250aCIpKSwgICAjIEFzZWfDunJhdGUgZGUgcXVlIGxhIGZlY2hhIGVzdMOpIGVuIGZvcm1hdG8gRGF0ZQogICAgICAgICBUaWVtcG8gPSBhcy5udW1lcmljKEZlY2hhIC0gbWluKEZlY2hhKSkgLyAoMzAgKiAyNCAqIDYwICogNjApKSAgIyBUaWVtcG8gZW4gbWVzZXMgKGFqdXN0YWRvIHBvciBkw61hcykKCiMgVmVyaWZpY2FyIGxhcyBwcmltZXJhcyBmaWxhcyBwYXJhIGFzZWd1cmFybm9zIGRlIHF1ZSBsYSB2YXJpYWJsZSBkZSB0aWVtcG8gZXN0w6kgYmllbiBjcmVhZGEKaGVhZChkYXRvc18zOTA0MTUyKQpgYGAKCmBgYHtyfQojIEFqdXN0YXIgZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsCm1vZGVsb19yZWdyZXNpb25fMzkwNDE1MiA8LSBsbShWZW50YSB+IENhbnQgKyBDb3N0b19WZW50YSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRvc18zOTA0MTUyKQoKIyBWZXIgcmVzdW1lbiBkZWwgbW9kZWxvCnN1bW1hcnkobW9kZWxvX3JlZ3Jlc2lvbl8zOTA0MTUyKQpgYGAKCmBgYHtyfQojIFByZWRpY2Npb25lcyB1c2FuZG8gZWwgbW9kZWxvIGFqdXN0YWRvCnByZWRpY2Npb25lc18zOTA0MTUyIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zOTA0MTUyLCBuZXdkYXRhID0gZGF0b3NfMzkwNDE1MikKCiMgQ2FsY3VsYXIgTUFQRSAoTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yKQojIEHDsWFkaW1vcyBwcm90ZWNjacOzbiBjb250cmEgZGl2aXNpw7NuIHBvciBjZXJvCm1hcGVfMzkwNDE1MiA8LSBtZWFuKGFicygoZGF0b3NfMzkwNDE1MiRWZW50YSAtIHByZWRpY2Npb25lc18zOTA0MTUyKSAvIHBtYXgoZGF0b3NfMzkwNDE1MiRWZW50YSwgMC4wMSkpKSAqIDEwMAoKIyBDYWxjdWxhciBNU0UgKE1lYW4gU3F1YXJlZCBFcnJvcikKbXNlXzM5MDQxNTIgPC0gbWVhbigoZGF0b3NfMzkwNDE1MiRWZW50YSAtIHByZWRpY2Npb25lc18zOTA0MTUyKV4yKQoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTA0MTUyOiAiLCBtYXBlXzM5MDQxNTIsICJcbiIpCmNhdCgiTVNFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAzOTA0MTUyOiAiLCBtc2VfMzkwNDE1MiwgIlxuIikKCiMgRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdW9zIGRlbCBtb2RlbG8KcGFyKG1mcm93ID0gYygyLCAyKSkKcGxvdChtb2RlbG9fcmVncmVzaW9uXzM5MDQxNTIpCmBgYApgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSZWdyZXNpw7NuIExpbmVhbCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIE1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzOTA0MTUyIiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwKICBNQVBFID0gbWFwZV8zOTA0MTUyLAogIE1TRSA9IG1zZV8zOTA0MTUyCikpCmBgYAoKCiMjIFBST0RVQ1RPIDE1NTAwMgoKYGBge3J9CiMgRmlsdHJhciBzb2xvIGxvcyBkYXRvcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMgpkYXRvc18xNTUwMDIgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDE1NTAwMikgJT4lCiAgc2VsZWN0KFZlbnRhLCBDYW50LCBDb3N0b19WZW50YSwKICAgICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBEZXNjdWVudG9fUG9yY2VudGFqZSwgVHJ4X0ZlY2hhKSAlPiUKICBuYS5vbWl0KCkgICMgRWxpbWluYXIgZmlsYXMgY29uIHZhbG9yZXMgTkEKCiMgQ3JlYXIgdW5hIHZhcmlhYmxlIGRlIHRpZW1wbyBjb250aW51YSBiYXNhZGEgZW4gbGEgZmVjaGEKZGF0b3NfMTU1MDAyIDwtIGRhdG9zXzE1NTAwMiAlPiUKICBtdXRhdGUoRmVjaGEgPSBhcy5EYXRlKGZsb29yX2RhdGUoVHJ4X0ZlY2hhLCAibW9udGgiKSksICAgIyBBc2Vnw7pyYXRlIGRlIHF1ZSBsYSBmZWNoYSBlc3TDqSBlbiBmb3JtYXRvIERhdGUKICAgICAgICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gKDMwICogMjQgKiA2MCAqIDYwKSkgICMgVGllbXBvIGVuIG1lc2VzIChhanVzdGFkbyBwb3IgZMOtYXMpCgojIFZlcmlmaWNhciBsYXMgcHJpbWVyYXMgZmlsYXMgcGFyYSBhc2VndXJhcm5vcyBkZSBxdWUgbGEgdmFyaWFibGUgZGUgdGllbXBvIGVzdMOpIGJpZW4gY3JlYWRhCmhlYWQoZGF0b3NfMTU1MDAyKQpgYGAKCgpgYGB7cn0KIyBBanVzdGFyIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbAptb2RlbG9fcmVncmVzaW9uXzE1NTAwMiA8LSBsbShWZW50YSB+IENhbnQgKyBDb3N0b19WZW50YSArCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbyArIERlc2N1ZW50b19Qb3JjZW50YWplICsgVGllbXBvLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBkYXRvc18xNTUwMDIpCgojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8Kc3VtbWFyeShtb2RlbG9fcmVncmVzaW9uXzE1NTAwMikKYGBgCgpgYGB7cn0KIyBQcmVkaWNjaW9uZXMgdXNhbmRvIGVsIG1vZGVsbyBhanVzdGFkbwpwcmVkaWNjaW9uZXNfMTU1MDAyIDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDIsIG5ld2RhdGEgPSBkYXRvc18xNTUwMDIpCgojIENhbGN1bGFyIE1BUEUgKE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvcikKIyBBw7FhZGltb3MgcHJvdGVjY2nDs24gY29udHJhIGRpdmlzacOzbiBwb3IgY2VybwptYXBlXzE1NTAwMiA8LSBtZWFuKGFicygoZGF0b3NfMTU1MDAyJFZlbnRhIC0gcHJlZGljY2lvbmVzXzE1NTAwMikgLyBwbWF4KGRhdG9zXzE1NTAwMiRWZW50YSwgMC4wMSkpKSAqIDEwMAoKIyBDYWxjdWxhciBNU0UgKE1lYW4gU3F1YXJlZCBFcnJvcikKbXNlXzE1NTAwMiA8LSBtZWFuKChkYXRvc18xNTUwMDIkVmVudGEgLSBwcmVkaWNjaW9uZXNfMTU1MDAyKV4yKQoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNQVBFIGRlbCBtb2RlbG8gZGUgcmVncmVzacOzbiBsaW5lYWwgcGFyYSAxNTUwMDI6ICIsIG1hcGVfMTU1MDAyLCAiXG4iKQpjYXQoIk1TRSBkZWwgbW9kZWxvIGRlIHJlZ3Jlc2nDs24gbGluZWFsIHBhcmEgMTU1MDAyOiAiLCBtc2VfMTU1MDAyLCAiXG4iKQoKIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbwpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KG1vZGVsb19yZWdyZXNpb25fMTU1MDAyKQpgYGAKCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJlZ3Jlc2nDs24gTGluZWFsIHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMiIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJSZWdyZXNpw7NuIExpbmVhbCIsCiAgTUFQRSA9IG1hcGVfMTU1MDAyLAogIE1TRSA9IG1zZV8xNTUwMDIKICApKQpgYGAKCgojIyBQUk9EVUNUTyAzNjc4MDU1CmBgYHtyfQojIEZpbHRyYXIgc29sbyBsb3MgZGF0b3MgcGFyYSBlbCBwcm9kdWN0byAzNjc4MDU1CmRhdG9zXzM2NzgwNTUgPC0gZGF0b3NfZmlsdHJhZG9zICU+JQogIGZpbHRlcihJRF9JbnZlbnRhcmlvID09IDM2NzgwNTUpICU+JQogIHNlbGVjdChWZW50YSwgQ2FudCwgQ29zdG9fVmVudGEsCiAgICAgICAgIFByZWNpb19GaW5hbF9Vbml0YXJpbywgRGVzY3VlbnRvX1BvcmNlbnRhamUsIFRyeF9GZWNoYSkgJT4lCiAgbmEub21pdCgpICAjIEVsaW1pbmFyIGZpbGFzIGNvbiB2YWxvcmVzIE5BCgojIENyZWFyIHVuYSB2YXJpYWJsZSBkZSB0aWVtcG8gY29udGludWEgYmFzYWRhIGVuIGxhIGZlY2hhCmRhdG9zXzM2NzgwNTUgPC0gZGF0b3NfMzY3ODA1NSAlPiUKICBtdXRhdGUoRmVjaGEgPSBhcy5EYXRlKGZsb29yX2RhdGUoVHJ4X0ZlY2hhLCAibW9udGgiKSksICAgIyBBc2Vnw7pyYXRlIGRlIHF1ZSBsYSBmZWNoYSBlc3TDqSBlbiBmb3JtYXRvIERhdGUKICAgICAgICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gKDMwICogMjQgKiA2MCAqIDYwKSkgICMgVGllbXBvIGVuIG1lc2VzIChhanVzdGFkbyBwb3IgZMOtYXMpCgojIFZlcmlmaWNhciBsYXMgcHJpbWVyYXMgZmlsYXMgcGFyYSBhc2VndXJhcm5vcyBkZSBxdWUgbGEgdmFyaWFibGUgZGUgdGllbXBvIGVzdMOpIGJpZW4gY3JlYWRhCmhlYWQoZGF0b3NfMzY3ODA1NSkKYGBgCgpgYGB7cn0KIyBBanVzdGFyIGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbAptb2RlbG9fcmVncmVzaW9uXzM2NzgwNTUgPC0gbG0oVmVudGEgfiBDYW50ICsgQ29zdG9fVmVudGEgKwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gKyBEZXNjdWVudG9fUG9yY2VudGFqZSArIFRpZW1wbywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gZGF0b3NfMzY3ODA1NSkKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpzdW1tYXJ5KG1vZGVsb19yZWdyZXNpb25fMzY3ODA1NSkKYGBgCgpgYGB7cn0KI1ByZWRpY2Npb25lcyB1c2FuZG8gZWwgbW9kZWxvIGFqdXN0YWRvCnByZWRpY2Npb25lc18zNjc4MDU1IDwtIHByZWRpY3QobW9kZWxvX3JlZ3Jlc2lvbl8zNjc4MDU1LCBuZXdkYXRhID0gZGF0b3NfMzY3ODA1NSkKIyBDYWxjdWxhciBNQVBFIChNZWFuIEFic29sdXRlIFBlcmNlbnRhZ2UgRXJyb3IpCiMgQcOxYWRpbW9zIHByb3RlY2Npw7NuIGNvbnRyYSBkaXZpc2nDs24gcG9yIGNlcm8KbWFwZV8zNjc4MDU1IDwtIG1lYW4oYWJzKChkYXRvc18zNjc4MDU1JFZlbnRhIC0gcHJlZGljY2lvbmVzXzM2NzgwNTUpIC8gcG1heChkYXRvc18zNjc4MDU1JFZlbnRhLCAwLjAxKSkpICogMTAwCgojIENhbGN1bGFyIE1TRSAoTWVhbiBTcXVhcmVkIEVycm9yKQptc2VfMzY3ODA1NSA8LSBtZWFuKChkYXRvc18zNjc4MDU1JFZlbnRhIC0gcHJlZGljY2lvbmVzXzM2NzgwNTUpXjIpCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcwpjYXQoIk1BUEUgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDM2NzgwNTU6ICIsIG1hcGVfMzY3ODA1NSwgIlxuIikKY2F0KCJNU0UgZGVsIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxpbmVhbCBwYXJhIDM2NzgwNTU6ICIsIG1zZV8zNjc4MDU1LCAiXG4iKQoKIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1b3MgZGVsIG1vZGVsbwpwYXIobWZyb3cgPSBjKDIsIDIpKQpwbG90KG1vZGVsb19yZWdyZXNpb25fMzY3ODA1NSkKYGBgCgpgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSZWdyZXNpw7NuIExpbmVhbCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIE1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzNjc4MDU1IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJlZ3Jlc2nDs24gTGluZWFsIiwKICBNQVBFID0gbWFwZV8zNjc4MDU1LAogIE1TRSA9IG1zZV8zNjc4MDU1CikpCmBgYAoKIyMgQU5BTElTSVMgREUgVkFSSUFCTEVTIElNUE9SVEFOVEVTCmBgYHtyfQojIEZ1bmNpw7NuIHNpbXBsaWZpY2FkYSBwYXJhIGFuYWxpemFyIGNvZWZpY2llbnRlcwphbmFsaXphcl9jb2VmaWNpZW50ZXMgPC0gZnVuY3Rpb24obW9kZWxvLCBub21icmVfcHJvZHVjdG8pIHsKICByZXN1bWVuIDwtIHN1bW1hcnkobW9kZWxvKQogIGNvZWZfZGYgPC0gYXMuZGF0YS5mcmFtZShyZXN1bWVuJGNvZWZmaWNpZW50cykKICBjb2xuYW1lcyhjb2VmX2RmKSA8LSBjKCJFc3RpbWF0ZSIsICJTdGQuRXJyb3IiLCAidC52YWx1ZSIsICJwLnZhbHVlIikKICBjb2VmX2RmJFZhcmlhYmxlIDwtIHJvd25hbWVzKGNvZWZfZGYpCiAgY29lZl9kZiRQcm9kdWN0byA8LSBub21icmVfcHJvZHVjdG8KICBjb2VmX2RmJFNpZ25pZmljYXRpdm8gPC0gaWZlbHNlKGNvZWZfZGYkcC52YWx1ZSA8IDAuMDUsICJTw60iLCAiTm8iKQogIAogIHJldHVybihjb2VmX2RmICU+JQogICAgICAgICAgIHNlbGVjdChQcm9kdWN0bywgVmFyaWFibGUsIEVzdGltYXRlLCBwLnZhbHVlLCBTaWduaWZpY2F0aXZvKSAlPiUKICAgICAgICAgICBhcnJhbmdlKGRlc2MoYWJzKEVzdGltYXRlKSkpKQp9CgojIEFwbGljYXIgYSBjYWRhIG1vZGVsbwpjb2VmXzE1NTAwMSA8LSBhbmFsaXphcl9jb2VmaWNpZW50ZXMobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDEsICIxNTUwMDEiKQpjb2VmXzE1NTAwMiA8LSBhbmFsaXphcl9jb2VmaWNpZW50ZXMobW9kZWxvX3JlZ3Jlc2lvbl8xNTUwMDIsICIxNTUwMDIiKQpjb2VmXzM2NzgwNTUgPC0gYW5hbGl6YXJfY29lZmljaWVudGVzKG1vZGVsb19yZWdyZXNpb25fMzY3ODA1NSwgIjM2NzgwNTUiKQpjb2VmXzM5MDQxNTIgPC0gYW5hbGl6YXJfY29lZmljaWVudGVzKG1vZGVsb19yZWdyZXNpb25fMzkwNDE1MiwgIjM5MDQxNTIiKQpjb2VmXzM5Mjk3ODggPC0gYW5hbGl6YXJfY29lZmljaWVudGVzKG1vZGVsb19yZWdyZXNpb25fMzkyOTc4OCwgIjM5Mjk3ODgiKQoKIyBDb21iaW5hciB0b2RvcyBsb3MgY29lZmljaWVudGVzCnRvZG9zX2NvZWZpY2llbnRlcyA8LSBiaW5kX3Jvd3MoY29lZl8xNTUwMDEsIGNvZWZfMTU1MDAyLCBjb2VmXzM2NzgwNTUsIGNvZWZfMzkwNDE1MiwgY29lZl8zOTI5Nzg4KQoKIyBUYWJsYSBjb24gdmFyaWFibGVzIGltcG9ydGFudGVzIGluY2x1eWVuZG8gc2lnbmlmaWNhbmNpYQp2YXJpYWJsZXNfaW1wb3J0YW50ZXMgPC0gdG9kb3NfY29lZmljaWVudGVzICU+JQogIGZpbHRlcihWYXJpYWJsZSAhPSAiKEludGVyY2VwdCkiKSAlPiUKICBncm91cF9ieShQcm9kdWN0bykgJT4lCiAgYXJyYW5nZShQcm9kdWN0bywgZGVzYyhhYnMoRXN0aW1hdGUpKSkgJT4lCiAgbXV0YXRlKEltcGFjdG8gPSBpZmVsc2UoRXN0aW1hdGUgPiAwLCAiUG9zaXRpdm8iLCAiTmVnYXRpdm8iKSkKCiMgVGFibGEgY29tcGxldGEgY29uIHRvZGFzIGxhcyB2YXJpYWJsZXMgaW1wb3J0YW50ZXMKa2FibGUodmFyaWFibGVzX2ltcG9ydGFudGVzICU+JSAKICAgICAgICBzZWxlY3QoUHJvZHVjdG8sIFZhcmlhYmxlLCBFc3RpbWF0ZSwgcC52YWx1ZSwgU2lnbmlmaWNhdGl2bywgSW1wYWN0byksCiAgICAgIGNhcHRpb24gPSAiVmFyaWFibGVzIGltcG9ydGFudGVzIHBvciBwcm9kdWN0byIsCiAgICAgIGNvbC5uYW1lcyA9IGMoIlByb2R1Y3RvIiwgIlZhcmlhYmxlIiwgIkNvZWZpY2llbnRlIiwgInAtdmFsdWUiLCAiU2lnbmlmaWNhdGl2byIsICJJbXBhY3RvIiksCiAgICAgIGRpZ2l0cyA9IGMoMCwgMCwgNCwgNCwgMCwgMCkpICU+JQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKQoKIyBUYWJsYSByZXN1bWVuIGNvbiB0b3AgMyBwb3IgcHJvZHVjdG8KdG9wX3Bvcl9wcm9kdWN0byA8LSB2YXJpYWJsZXNfaW1wb3J0YW50ZXMgJT4lCiAgZ3JvdXBfYnkoUHJvZHVjdG8pICU+JQogIHNsaWNlX2hlYWQobiA9IDMpICU+JQogIHNlbGVjdChQcm9kdWN0bywgVmFyaWFibGUsIEVzdGltYXRlLCBwLnZhbHVlLCBTaWduaWZpY2F0aXZvLCBJbXBhY3RvKQoKa2FibGUodG9wX3Bvcl9wcm9kdWN0bywKICAgICAgY2FwdGlvbiA9ICJUb3AgMyB2YXJpYWJsZXMgbcOhcyBpbXBvcnRhbnRlcyBwb3IgcHJvZHVjdG8iLAogICAgICBjb2wubmFtZXMgPSBjKCJQcm9kdWN0byIsICJWYXJpYWJsZSIsICJDb2VmaWNpZW50ZSIsICJwLXZhbHVlIiwgIlNpZ25pZmljYXRpdm8iLCAiSW1wYWN0byIpLAogICAgICBkaWdpdHMgPSBjKDAsIDAsIDQsIDQsIDAsIDApKSAlPiUKICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkKYGBgCgoKIyBSQU5ET00gRk9SRVNUCgoKIyMgUFJPRFVDVE8gMTU1MDAxCmBgYHtyLCBmaWcud2lkdGg9OCwgZmlnLmhlaWdodD02fQojIFByZXBhcmFyIGRhdG9zIHBhcmEgZWwgbW9kZWxvIChlbGltaW5hciBjb2x1bW5hcyBubyBuZWNlc2FyaWFzKQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMTU1MDAxICU+JQogIHNlbGVjdCgtVHJ4X0ZlY2hhLCAtRmVjaGEpCgojIEFqdXN0YXIgZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3QKc2V0LnNlZWQoMTIzKSAgIyBQYXJhIHJlcHJvZHVjaWJpbGlkYWQKbW9kZWxvX3JmXzE1NTAwMSA8LSByYW5kb21Gb3Jlc3QoCiAgVmVudGEgfiAuLCAKICBkYXRhID0gZGF0b3NfbW9kZWxvLAogIG50cmVlID0gNTAwLCAgICAgICAgICAjIE7Dum1lcm8gZGUgw6FyYm9sZXMKICBtdHJ5ID0gZmxvb3Ioc3FydChuY29sKGRhdG9zX21vZGVsbykgLSAxKSksICAjIE7Dum1lcm8gZGUgdmFyaWFibGVzIGEgY29uc2lkZXJhciBlbiBjYWRhIHNwbGl0CiAgaW1wb3J0YW5jZSA9IFRSVUUgICAgICMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCikKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpwcmludChtb2RlbG9fcmZfMTU1MDAxKQoKIyBPYnRlbmVyIHByZWRpY2Npb25lcwpwcmVkaWNjaW9uZXNfcmYgPC0gcHJlZGljdChtb2RlbG9fcmZfMTU1MDAxLCBuZXdkYXRhID0gZGF0b3NfbW9kZWxvKQoKIyBDYWxjdWxhciBtw6l0cmljYXMKIyBNQVBFCm1hcGVfcmYgPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZikgLyBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMAoKIyBNU0UKbXNlX3JmIDwtIG1lYW4oKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19yZileMikKCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzCmNhdCgiTW9kZWxvIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAxNTUwMDFcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgbWFwZV9yZiwgIlxuIikKY2F0KCJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1zZV9yZiwgIlxuXG4iKQoKIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzE1NTAwMSkKcHJpbnQoaW1wb3J0YW5jaWFfdmFycykKCiMgR3JhZmljYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCnZhckltcFBsb3QobW9kZWxvX3JmXzE1NTAwMSwgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDEiKQoKIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgKICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYKKQoKZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMTU1MDAxIiwKICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLAogICAgeSA9ICJWZW50YXMgUHJlZGljaGFzIgogICkgKwogIHRoZW1lX21pbmltYWwoKQoKIyBOVUVWT1MgQU7DgUxJU0lTIEHDkUFESURPUwoKIyBBbsOhbGlzaXMgZGVsIGVycm9yCmVycm9yZXMgPC0gZGF0b3NfZ3JhZmljbyRPYnNlcnZhZG8gLSBkYXRvc19ncmFmaWNvJFByZWRpY2hvCmhpc3QoZXJyb3JlcywgCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAxNTUwMDEiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIGJyZWFrcyA9IDMwKQoKIyBFc3RhZMOtc3RpY2FzIGRlc2NyaXB0aXZhcyBkZSBsb3MgZXJyb3JlcwpjYXQoIkVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIGRlIGxvcyBlcnJvcmVzOlxuIikKY2F0KCJNZWRpYSBkZSBlcnJvcmVzOiIsIG1lYW4oZXJyb3JlcyksICJcbiIpCmNhdCgiRGVzdmlhY2nDs24gZXN0w6FuZGFyIGRlIGVycm9yZXM6Iiwgc2QoZXJyb3JlcyksICJcbiIpCmNhdCgiTcOtbmltbzoiLCBtaW4oZXJyb3JlcyksICJcbiIpCmNhdCgiTcOheGltbzoiLCBtYXgoZXJyb3JlcyksICJcbiIpCmNhdCgiTWVkaWFuYToiLCBtZWRpYW4oZXJyb3JlcyksICJcbiIpCgojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbgpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZiwgRXJyb3IgPSBlcnJvcmVzKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMTU1MDAxIiwKICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIE1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIxNTUwMDEiLCAgIyBDYW1iaWEgZXN0ZSBJRCBwYXJhIGNhZGEgcHJvZHVjdG8KICBNb2RlbG8gPSAiUmFuZG9tIEZvcmVzdCIsCiAgTUFQRSA9IG1hcGVfcmYsCiAgTVNFID0gbXNlX3JmCikpCmBgYAoKIyMgUFJPRFVDVE8gMzkyOTc4OApgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KIyBDcmVhciB1bmEgdmFyaWFibGUgZGUgdGllbXBvIGNvbnRpbnVhIGJhc2FkYSBlbiBsYSBmZWNoYQpkYXRvc18zOTI5Nzg4IDwtIGRhdG9zXzM5Mjk3ODggJT4lCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCAKICAgICAgICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gKDMwICogMjQgKiA2MCAqIDYwKSkgICMgVGllbXBvIGVuIG1lc2VzCgojIE1vc3RyYXIgdW4gcmVzdW1lbiBkZSBsb3MgZGF0b3MKc3VtbWFyeShkYXRvc18zOTI5Nzg4KQoKIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzM5Mjk3ODggJT4lCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkKCiMgQWp1c3RhciBlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdApzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAptb2RlbG9fcmZfMzkyOTc4OCA8LSByYW5kb21Gb3Jlc3QoCiAgVmVudGEgfiAuLCAKICBkYXRhID0gZGF0b3NfbW9kZWxvLAogIG50cmVlID0gNTAwLCAgICAgICAgICAjIE7Dum1lcm8gZGUgw6FyYm9sZXMKICBtdHJ5ID0gZmxvb3Ioc3FydChuY29sKGRhdG9zX21vZGVsbykgLSAxKSksICAjIE7Dum1lcm8gZGUgdmFyaWFibGVzIGEgY29uc2lkZXJhciBlbiBjYWRhIHNwbGl0CiAgaW1wb3J0YW5jZSA9IFRSVUUgICAgICMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCikKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpwcmludChtb2RlbG9fcmZfMzkyOTc4OCkKCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMKcHJlZGljY2lvbmVzX3JmIDwtIHByZWRpY3QobW9kZWxvX3JmXzM5Mjk3ODgsIG5ld2RhdGEgPSBkYXRvc19tb2RlbG8pCgojIENhbGN1bGFyIG3DqXRyaWNhcwojIE1BUEUKbWFwZV9yZiA8LSBtZWFuKGFicygoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKSAvIHBtYXgoZGF0b3NfbW9kZWxvJFZlbnRhLCAwLjAxKSkpICogMTAwCgojIE1TRQptc2VfcmYgPC0gbWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKV4yKQoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNb2RlbG8gUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDM5Mjk3ODhcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgbWFwZV9yZiwgIlxuIikKY2F0KCJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1zZV9yZiwgIlxuXG4iKQoKIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzM5Mjk3ODgpCnByaW50KGltcG9ydGFuY2lhX3ZhcnMpCgojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp2YXJJbXBQbG90KG1vZGVsb19yZl8zOTI5Nzg4LCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM5Mjk3ODgiKQoKIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgKICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYKKQoKZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMzkyOTc4OCIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQW7DoWxpc2lzIGRlbCBlcnJvcgplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobwpoaXN0KGVycm9yZXMsIAogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMzkyOTc4OCIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbgpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZiwgRXJyb3IgPSBlcnJvcmVzKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzkyOTc4OCIsCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLAogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAxNTUwMDEKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkyOTc4OCIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IiwKICBNQVBFID0gbWFwZV9yZiwKICBNU0UgPSBtc2VfcmYKKSkKYGBgCgoKIyMgUFJPRFVDVE8gMzkwNDE1MgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KIyBDcmVhciB1bmEgdmFyaWFibGUgZGUgdGllbXBvIGNvbnRpbnVhIGJhc2FkYSBlbiBsYSBmZWNoYQpkYXRvc18zOTA0MTUyIDwtIGRhdG9zXzM5MDQxNTIgJT4lCiAgbXV0YXRlKEZlY2hhID0gYXMuRGF0ZShmbG9vcl9kYXRlKFRyeF9GZWNoYSwgIm1vbnRoIikpLCAKICAgICAgICAgVGllbXBvID0gYXMubnVtZXJpYyhGZWNoYSAtIG1pbihGZWNoYSkpIC8gKDMwICogMjQgKiA2MCAqIDYwKSkgICMgVGllbXBvIGVuIG1lc2VzCgojIE1vc3RyYXIgdW4gcmVzdW1lbiBkZSBsb3MgZGF0b3MKc3VtbWFyeShkYXRvc18zOTA0MTUyKQoKIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzM5MDQxNTIgJT4lCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkKCiMgQWp1c3RhciBlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdApzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAptb2RlbG9fcmZfMzkwNDE1MiA8LSByYW5kb21Gb3Jlc3QoCiAgVmVudGEgfiAuLCAKICBkYXRhID0gZGF0b3NfbW9kZWxvLAogIG50cmVlID0gNTAwLCAgICAgICAgICAjIE7Dum1lcm8gZGUgw6FyYm9sZXMKICBtdHJ5ID0gZmxvb3Ioc3FydChuY29sKGRhdG9zX21vZGVsbykgLSAxKSksICAjIE7Dum1lcm8gZGUgdmFyaWFibGVzIGEgY29uc2lkZXJhciBlbiBjYWRhIHNwbGl0CiAgaW1wb3J0YW5jZSA9IFRSVUUgICAgICMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCikKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpwcmludChtb2RlbG9fcmZfMzkwNDE1MikKCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMKcHJlZGljY2lvbmVzX3JmIDwtIHByZWRpY3QobW9kZWxvX3JmXzM5MDQxNTIsIG5ld2RhdGEgPSBkYXRvc19tb2RlbG8pCgojIENhbGN1bGFyIG3DqXRyaWNhcwojIE1BUEUKbWFwZV9yZiA8LSBtZWFuKGFicygoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKSAvIHBtYXgoZGF0b3NfbW9kZWxvJFZlbnRhLCAwLjAxKSkpICogMTAwCgojIE1TRQptc2VfcmYgPC0gbWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKV4yKQoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNb2RlbG8gUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDM5MDQxNTJcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgbWFwZV9yZiwgIlxuIikKY2F0KCJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1zZV9yZiwgIlxuXG4iKQoKIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzM5MDQxNTIpCnByaW50KGltcG9ydGFuY2lhX3ZhcnMpCgojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp2YXJJbXBQbG90KG1vZGVsb19yZl8zOTA0MTUyLCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM5MDQxNTIiKQoKIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgKICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYKKQoKZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMzkwNDE1MiIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQW7DoWxpc2lzIGRlbCBlcnJvcgplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobwpoaXN0KGVycm9yZXMsIAogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMzkwNDE1MiIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbgpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZiwgRXJyb3IgPSBlcnJvcmVzKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzkwNDE1MiIsCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLAogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBwcm9kdWN0byAxNTUwMDEKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkwNDE1MiIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJSYW5kb20gRm9yZXN0IiwKICBNQVBFID0gbWFwZV9yZiwKICBNU0UgPSBtc2VfcmYKKSkKYGBgCgoKIyMgUFJPRFVDVE8gMTU1MDAyCgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzE1NTAwMiAlPiUKICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQoKIyBBanVzdGFyIGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0CnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCm1vZGVsb19yZl8xNTUwMDIgPC0gcmFuZG9tRm9yZXN0KAogIFZlbnRhIH4gLiwgCiAgZGF0YSA9IGRhdG9zX21vZGVsbywKICBudHJlZSA9IDUwMCwgICAgICAgICAgIyBOw7ptZXJvIGRlIMOhcmJvbGVzCiAgbXRyeSA9IGZsb29yKHNxcnQobmNvbChkYXRvc19tb2RlbG8pIC0gMSkpLCAgIyBOw7ptZXJvIGRlIHZhcmlhYmxlcyBhIGNvbnNpZGVyYXIgZW4gY2FkYSBzcGxpdAogIGltcG9ydGFuY2UgPSBUUlVFICAgICAjIENhbGN1bGFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwopCgojIFZlciByZXN1bWVuIGRlbCBtb2RlbG8KcHJpbnQobW9kZWxvX3JmXzE1NTAwMikKCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMKcHJlZGljY2lvbmVzX3JmIDwtIHByZWRpY3QobW9kZWxvX3JmXzE1NTAwMiwgbmV3ZGF0YSA9IGRhdG9zX21vZGVsbykKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCiMgTUFQRQptYXBlX3JmIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgTVNFCm1zZV9yZiA8LSBtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfcmYpXjIpCgojIE1vc3RyYXIgbGFzIG3DqXRyaWNhcwpjYXQoIk1vZGVsbyBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMTU1MDAyXG4iKQpjYXQoIk1BUEUgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1hcGVfcmYsICJcbiIpCmNhdCgiTVNFIGRlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdDoiLCBtc2VfcmYsICJcblxuIikKCiMgTW9zdHJhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKaW1wb3J0YW5jaWFfdmFycyA8LSBpbXBvcnRhbmNlKG1vZGVsb19yZl8xNTUwMDIpCnByaW50KGltcG9ydGFuY2lhX3ZhcnMpCgojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp2YXJJbXBQbG90KG1vZGVsb19yZl8xNTUwMDIsIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMTU1MDAyIikKCiMgQ3JlYXIgZ3LDoWZpY28gZGUgdmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2Npb25lcwpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmCikKCmdncGxvdChkYXRvc19ncmFmaWNvLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDE1NTAwMiIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQW7DoWxpc2lzIGRlbCBlcnJvcgplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobwpoaXN0KGVycm9yZXMsIAogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMTU1MDAyIiwKICAgICB4bGFiID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiLAogICAgIGNvbCA9ICJza3libHVlIiwKICAgICBicmVha3MgPSAzMCkKCiMgR3LDoWZpY28gZGVsIGVycm9yIHZzIHByZWRpY2Npw7NuCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX3JmLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAxNTUwMDIiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBSYW5kb20gRm9yZXN0IHBhcmEgcHJvZHVjdG8gMzY3ODA1NQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIE1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzNjc4MDU1IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QiLAogIE1BUEUgPSBtYXBlX3JmLAogIE1TRSA9IG1zZV9yZgopKQpgYGAKCgojIyBQUk9EVUNUTyAzNjc4MDU1CgpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9Nn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzM2NzgwNTUgJT4lCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkKCiMgQWp1c3RhciBlbCBtb2RlbG8gUmFuZG9tIEZvcmVzdApzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAptb2RlbG9fcmZfMzY3ODA1NSA8LSByYW5kb21Gb3Jlc3QoCiAgVmVudGEgfiAuLCAKICBkYXRhID0gZGF0b3NfbW9kZWxvLAogIG50cmVlID0gNTAwLCAgICAgICAgICAjIE7Dum1lcm8gZGUgw6FyYm9sZXMKICBtdHJ5ID0gZmxvb3Ioc3FydChuY29sKGRhdG9zX21vZGVsbykgLSAxKSksICAjIE7Dum1lcm8gZGUgdmFyaWFibGVzIGEgY29uc2lkZXJhciBlbiBjYWRhIHNwbGl0CiAgaW1wb3J0YW5jZSA9IFRSVUUgICAgICMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCikKCiMgVmVyIHJlc3VtZW4gZGVsIG1vZGVsbwpwcmludChtb2RlbG9fcmZfMzY3ODA1NSkKCiMgT2J0ZW5lciBwcmVkaWNjaW9uZXMKcHJlZGljY2lvbmVzX3JmIDwtIHByZWRpY3QobW9kZWxvX3JmXzM2NzgwNTUsIG5ld2RhdGEgPSBkYXRvc19tb2RlbG8pCgojIENhbGN1bGFyIG3DqXRyaWNhcwojIE1BUEUKbWFwZV9yZiA8LSBtZWFuKGFicygoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKSAvIHBtYXgoZGF0b3NfbW9kZWxvJFZlbnRhLCAwLjAxKSkpICogMTAwCgojIE1TRQptc2VfcmYgPC0gbWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX3JmKV4yKQoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMKY2F0KCJNb2RlbG8gUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDM2NzgwNTVcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFJhbmRvbSBGb3Jlc3Q6IiwgbWFwZV9yZiwgIlxuIikKY2F0KCJNU0UgZGVsIG1vZGVsbyBSYW5kb20gRm9yZXN0OiIsIG1zZV9yZiwgIlxuXG4iKQoKIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYV92YXJzIDwtIGltcG9ydGFuY2UobW9kZWxvX3JmXzM2NzgwNTUpCnByaW50KGltcG9ydGFuY2lhX3ZhcnMpCgojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp2YXJJbXBQbG90KG1vZGVsb19yZl8zNjc4MDU1LCBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDM2NzgwNTUiKQoKIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgKICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfcmYKKQoKZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMzY3ODA1NSIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgQW7DoWxpc2lzIGRlbCBlcnJvcgplcnJvcmVzIDwtIGRhdG9zX2dyYWZpY28kT2JzZXJ2YWRvIC0gZGF0b3NfZ3JhZmljbyRQcmVkaWNobwpoaXN0KGVycm9yZXMsIAogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMzY3ODA1NSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEdyw6FmaWNvIGRlbCBlcnJvciB2cyBwcmVkaWNjacOzbgpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19yZiwgRXJyb3IgPSBlcnJvcmVzKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzY3ODA1NSIsCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLAogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgUmFuZG9tIEZvcmVzdCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIE1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzNjc4MDU1IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlJhbmRvbSBGb3Jlc3QiLAogIE1BUEUgPSBtYXBlX3JmLAogIE1TRSA9IG1zZV9yZgopKQpgYGAKCgojIFhHQk9PU1QKCiMjIFBST0RVQ1RPIDE1NTAwMQpgYGB7cn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzE1NTAwMSAlPiUKICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQoKIyBEaXZpZGlyIGxvcyBkYXRvcyBlbiBjb25qdW50b3MgZGUgZW50cmVuYW1pZW50byAoODAlKSB5IHBydWViYSAoMjAlKQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAppbmRpY2VzX3RyYWluIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfbW9kZWxvJFZlbnRhLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpCmRhdG9zX3RyYWluIDwtIGRhdG9zX21vZGVsb1tpbmRpY2VzX3RyYWluLCBdCmRhdG9zX3Rlc3QgPC0gZGF0b3NfbW9kZWxvWy1pbmRpY2VzX3RyYWluLCBdCgojIFNlcGFyYXIgdmFyaWFibGVzIHByZWRpY3RvcmFzIHkgdmFyaWFibGUgb2JqZXRpdm8KWF90cmFpbiA8LSBhcy5tYXRyaXgoZGF0b3NfdHJhaW5bLCBjb2xuYW1lcyhkYXRvc190cmFpbikgIT0gIlZlbnRhIl0pCnlfdHJhaW4gPC0gZGF0b3NfdHJhaW4kVmVudGEKClhfdGVzdCA8LSBhcy5tYXRyaXgoZGF0b3NfdGVzdFssIGNvbG5hbWVzKGRhdG9zX3Rlc3QpICE9ICJWZW50YSJdKQp5X3Rlc3QgPC0gZGF0b3NfdGVzdCRWZW50YQoKIyBDcmVhciBtYXRyaWNlcyBETWF0cml4IHBhcmEgWEdCb29zdApkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfdHJhaW4sIGxhYmVsID0geV90cmFpbikKZHRlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IFhfdGVzdCwgbGFiZWwgPSB5X3Rlc3QpCgojIERlZmluaXIgdW5hIHJlamlsbGEgY29tcGxldGEgZGUgaGlwZXJwYXLDoW1ldHJvcyBwYXJhIGLDunNxdWVkYQpwYXJhbV9ncmlkIDwtIGV4cGFuZC5ncmlkKAogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xLCAwLjMpLCAgICAgICAgICMgTGVhcm5pbmcgcmF0ZQogIG1heF9kZXB0aCA9IGMoMywgNSwgNywgOSksICAgICAgICAgICAgICMgUHJvZnVuZGlkYWQgbcOheGltYQogIHN1YnNhbXBsZSA9IGMoMC42LCAwLjgsIDEuMCksICAgICAgICAgICMgU3VibXVlc3RyYSBkZSBvYnNlcnZhY2lvbmVzCiAgY29sc2FtcGxlX2J5dHJlZSA9IGMoMC42LCAwLjgsIDEuMCksICAgIyBTdWJtdWVzdHJhIGRlIHZhcmlhYmxlcwogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMsIDUpLCAgICAgICAgICMgUGVzbyBtw61uaW1vIGVuIG5vZG9zIGhpam9zCiAgZ2FtbWEgPSBjKDAsIDAuMSwgMC4zKSAgICAgICAgICAgICAgICAgIyBSZWd1bGFyaXphY2nDs24gZ2FtbWEKKQoKIyBNb3N0cmFyIGN1w6FudGFzIGNvbWJpbmFjaW9uZXMgdGVuZW1vcwpjYXQoIk7Dum1lcm8gdG90YWwgZGUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zOiIsIG5yb3cocGFyYW1fZ3JpZCksICJcbiIpCgojIFBhcmEgZXN0ZSBlamVtcGxvLCB2YW1vcyBhIGxpbWl0YXIgZWwgbsO6bWVybyBkZSBjb21iaW5hY2lvbmVzCiMgU2VsZWNjaW9uYW5kbyB1biBzdWJjb25qdW50byBhbGVhdG9yaW8gZGUgY29tYmluYWNpb25lcyAoMjAgY29tYmluYWNpb25lcykKc2V0LnNlZWQoMTIzKQppZiAobnJvdyhwYXJhbV9ncmlkKSA+IDIwKSB7CiAgbXVlc3RyYV9pbmRpY2VzIDwtIHNhbXBsZSgxOm5yb3cocGFyYW1fZ3JpZCksIDIwKQogIHBhcmFtX2dyaWRfcmVkdWNpZGEgPC0gcGFyYW1fZ3JpZFttdWVzdHJhX2luZGljZXMsIF0KfSBlbHNlIHsKICBwYXJhbV9ncmlkX3JlZHVjaWRhIDwtIHBhcmFtX2dyaWQKfQoKY2F0KCJOw7ptZXJvIGRlIGNvbWJpbmFjaW9uZXMgYSBldmFsdWFyOiIsIG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSksICJcbiIpCgojIEZ1bmNpw7NuIHBhcmEgZXZhbHVhciB1biBjb25qdW50byBkZSBoaXBlcnBhcsOhbWV0cm9zIGNvbiB2YWxpZGFjacOzbiBjcnV6YWRhCmV2YWx1YXRlX3BhcmFtcyA8LSBmdW5jdGlvbihwYXJhbXNfcm93KSB7CiAgcGFyYW1zIDwtIGxpc3QoCiAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwKICAgIGV0YSA9IHBhcmFtc19yb3ckZXRhLAogICAgbWF4X2RlcHRoID0gcGFyYW1zX3JvdyRtYXhfZGVwdGgsCiAgICBzdWJzYW1wbGUgPSBwYXJhbXNfcm93JHN1YnNhbXBsZSwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbXNfcm93JGNvbHNhbXBsZV9ieXRyZWUsCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1zX3JvdyRtaW5fY2hpbGRfd2VpZ2h0LAogICAgZ2FtbWEgPSBwYXJhbXNfcm93JGdhbW1hCiAgKQogIAogICMgUmVhbGl6YXIgdmFsaWRhY2nDs24gY3J1emFkYQogIGN2X3Jlc3VsdHMgPC0geGdiLmN2KAogICAgcGFyYW1zID0gcGFyYW1zLAogICAgZGF0YSA9IGR0cmFpbiwKICAgIG5yb3VuZHMgPSAxMDAsCiAgICBuZm9sZCA9IDUsICAjIDUtZm9sZCB2YWxpZGFjacOzbiBjcnV6YWRhCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAxMCwKICAgIHZlcmJvc2UgPSAwCiAgKQogIAogICMgRXh0cmFlciBlbCBtZWpvciBSTVNFIHkgZWwgbsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhcwogIGJlc3Rfcm1zZSA8LSBtaW4oY3ZfcmVzdWx0cyRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbikKICBiZXN0X25yb3VuZHMgPC0gd2hpY2gubWluKGN2X3Jlc3VsdHMkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pCiAgCiAgcmV0dXJuKGxpc3Qocm1zZSA9IGJlc3Rfcm1zZSwgbnJvdW5kcyA9IGJlc3RfbnJvdW5kcywgcGFyYW1zID0gcGFyYW1zKSkKfQoKIyBJbmljaWFsaXphciB0YWJsYSBwYXJhIGFsbWFjZW5hciByZXN1bHRhZG9zCnJlc3VsdGFkb3NfZ3JpZCA8LSBkYXRhLmZyYW1lKAogIGV0YSA9IG51bWVyaWMobnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSksCiAgbWF4X2RlcHRoID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwKICBzdWJzYW1wbGUgPSBudW1lcmljKG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpLAogIGNvbHNhbXBsZV9ieXRyZWUgPSBudW1lcmljKG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpLAogIG1pbl9jaGlsZF93ZWlnaHQgPSBudW1lcmljKG5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpLAogIGdhbW1hID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwKICBucm91bmRzID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKSwKICBybXNlID0gbnVtZXJpYyhucm93KHBhcmFtX2dyaWRfcmVkdWNpZGEpKQopCgojIFJlYWxpemFyIGxhIGLDunNxdWVkYSBlbiBjdWFkcsOtY3VsYSAoZXN0byBwdWVkZSB0YXJkYXIgdmFyaW9zIG1pbnV0b3MpCmNhdCgiSW5pY2lhbmRvIGLDunNxdWVkYSBlbiBjdWFkcsOtY3VsYS4uLlxuIikKCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZF9yZWR1Y2lkYSkpIHsKICBjYXQoc3ByaW50ZigiRXZhbHVhbmRvIGNvbWJpbmFjacOzbiAlZCBkZSAlZFxuIiwgaSwgbnJvdyhwYXJhbV9ncmlkX3JlZHVjaWRhKSkpCiAgCiAgIyBPYnRlbmVyIGZpbGEgZGUgcGFyw6FtZXRyb3MgYWN0dWFsCiAgcGFyYW1zX3JvdyA8LSBwYXJhbV9ncmlkX3JlZHVjaWRhW2ksIF0KICAKICAjIEV2YWx1YXIgY29tYmluYWNpw7NuIGFjdHVhbAogIHJlc3VsdCA8LSBldmFsdWF0ZV9wYXJhbXMocGFyYW1zX3JvdykKICAKICAjIEd1YXJkYXIgcmVzdWx0YWRvcwogIHJlc3VsdGFkb3NfZ3JpZCRldGFbaV0gPC0gcGFyYW1zX3JvdyRldGEKICByZXN1bHRhZG9zX2dyaWQkbWF4X2RlcHRoW2ldIDwtIHBhcmFtc19yb3ckbWF4X2RlcHRoCiAgcmVzdWx0YWRvc19ncmlkJHN1YnNhbXBsZVtpXSA8LSBwYXJhbXNfcm93JHN1YnNhbXBsZQogIHJlc3VsdGFkb3NfZ3JpZCRjb2xzYW1wbGVfYnl0cmVlW2ldIDwtIHBhcmFtc19yb3ckY29sc2FtcGxlX2J5dHJlZQogIHJlc3VsdGFkb3NfZ3JpZCRtaW5fY2hpbGRfd2VpZ2h0W2ldIDwtIHBhcmFtc19yb3ckbWluX2NoaWxkX3dlaWdodAogIHJlc3VsdGFkb3NfZ3JpZCRnYW1tYVtpXSA8LSBwYXJhbXNfcm93JGdhbW1hCiAgcmVzdWx0YWRvc19ncmlkJG5yb3VuZHNbaV0gPC0gcmVzdWx0JG5yb3VuZHMKICByZXN1bHRhZG9zX2dyaWQkcm1zZVtpXSA8LSByZXN1bHQkcm1zZQp9CgojIE9yZGVuYXIgcmVzdWx0YWRvcyBwb3IgUk1TRSAoZGUgbWVub3IgYSBtYXlvcikKcmVzdWx0YWRvc19ncmlkIDwtIHJlc3VsdGFkb3NfZ3JpZFtvcmRlcihyZXN1bHRhZG9zX2dyaWQkcm1zZSksIF0KCiMgTW9zdHJhciBsb3MgNSBtZWpvcmVzIGNvbmp1bnRvcyBkZSBoaXBlcnBhcsOhbWV0cm9zCmNhdCgiXG5Mb3MgNSBtZWpvcmVzIGNvbmp1bnRvcyBkZSBoaXBlcnBhcsOhbWV0cm9zOlxuIikKcHJpbnQoaGVhZChyZXN1bHRhZG9zX2dyaWQsIDUpKQoKIyBPYnRlbmVyIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MKbWVqb3Jlc19wYXJhbXMgPC0gbGlzdCgKICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgZXRhID0gcmVzdWx0YWRvc19ncmlkJGV0YVsxXSwKICBtYXhfZGVwdGggPSByZXN1bHRhZG9zX2dyaWQkbWF4X2RlcHRoWzFdLAogIHN1YnNhbXBsZSA9IHJlc3VsdGFkb3NfZ3JpZCRzdWJzYW1wbGVbMV0sCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3NfZ3JpZCRjb2xzYW1wbGVfYnl0cmVlWzFdLAogIG1pbl9jaGlsZF93ZWlnaHQgPSByZXN1bHRhZG9zX2dyaWQkbWluX2NoaWxkX3dlaWdodFsxXSwKICBnYW1tYSA9IHJlc3VsdGFkb3NfZ3JpZCRnYW1tYVsxXQopCgptZWpvcl9ucm91bmRzIDwtIHJlc3VsdGFkb3NfZ3JpZCRucm91bmRzWzFdCgpjYXQoIlxuTWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zIGVuY29udHJhZG9zOlxuIikKcHJpbnQobWVqb3Jlc19wYXJhbXMpCmNhdCgiTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhczoiLCBtZWpvcl9ucm91bmRzLCAiXG4iKQpjYXQoIlJNU0UgZW4gdmFsaWRhY2nDs24gY3J1emFkYToiLCByZXN1bHRhZG9zX2dyaWQkcm1zZVsxXSwgIlxuXG4iKQoKIyBFbnRyZW5hciBlbCBtb2RlbG8gZmluYWwgY29uIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MKbW9kZWxvX3hnYl8xNTUwMDEgPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zLAogIGRhdGEgPSBkdHJhaW4sCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsCiAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbiwgdGVzdCA9IGR0ZXN0KSwKICB2ZXJib3NlID0gMAopCgojIEhhY2VyIHByZWRpY2Npb25lcyBlbiBlbCBjb25qdW50byBkZSBwcnVlYmEKcHJlZGljY2lvbmVzX3Rlc3QgPC0gcHJlZGljdChtb2RlbG9feGdiXzE1NTAwMSwgZHRlc3QpCgojIENhbGN1bGFyIG3DqXRyaWNhcyBlbiBlbCBjb25qdW50byBkZSBwcnVlYmEKIyBNQVBFCm1hcGVfdGVzdCA8LSBtZWFuKGFicygoeV90ZXN0IC0gcHJlZGljY2lvbmVzX3Rlc3QpIC8gcG1heCh5X3Rlc3QsIDAuMDEpKSkgKiAxMDAKCiMgTVNFCm1zZV90ZXN0IDwtIG1lYW4oKHlfdGVzdCAtIHByZWRpY2Npb25lc190ZXN0KV4yKQoKIyBNb3N0cmFyIGxhcyBtw6l0cmljYXMgZW4gZWwgY29uanVudG8gZGUgcHJ1ZWJhCmNhdCgiTcOpdHJpY2FzIGVuIGVsIGNvbmp1bnRvIGRlIHBydWViYTpcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV90ZXN0LCAiXG4iKQpjYXQoIk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbXNlX3Rlc3QsICJcblxuIikKCiMgQWhvcmEgaGFjZXIgcHJlZGljY2lvbmVzIGVuIGVsIGNvbmp1bnRvIGNvbXBsZXRvIHBhcmEgY29tcGFyYWJpbGlkYWQgY29uIG90cm9zIG1vZGVsb3MKWF9jb21wbGV0byA8LSBhcy5tYXRyaXgoZGF0b3NfbW9kZWxvWywgY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSkKcHJlZGljY2lvbmVzX2NvbXBsZXRhcyA8LSBwcmVkaWN0KG1vZGVsb194Z2JfMTU1MDAxLCBYX2NvbXBsZXRvKQoKIyBDYWxjdWxhciBtw6l0cmljYXMgZW4gZWwgY29uanVudG8gY29tcGxldG8KIyBNQVBFCm1hcGVfY29tcGxldG8gPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0YXMpIC8gcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKCiMgTVNFCm1zZV9jb21wbGV0byA8LSBtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldGFzKV4yKQoKCiMgTW9zdHJhciBsYXMgbcOpdHJpY2FzIGVuIGVsIGNvbmp1bnRvIGNvbXBsZXRvCmNhdCgiTcOpdHJpY2FzIGVuIGVsIGNvbmp1bnRvIGNvbXBsZXRvOlxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX2NvbXBsZXRvLCAiXG4iKQpjYXQoIk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbXNlX2NvbXBsZXRvLCAiXG5cbiIpCgojIEltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwppbXBvcnRhbmNpYSA8LSB4Z2IuaW1wb3J0YW5jZSgKICBmZWF0dXJlX25hbWVzID0gY29sbmFtZXMoZGF0b3NfbW9kZWxvKVtjb2xuYW1lcyhkYXRvc19tb2RlbG8pICE9ICJWZW50YSJdLAogIG1vZGVsID0gbW9kZWxvX3hnYl8xNTUwMDEKKQpwcmludChpbXBvcnRhbmNpYSkKCiMgR3JhZmljYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCnhnYi5wbG90LmltcG9ydGFuY2UoaW1wb3J0YW5jZV9tYXRyaXggPSBpbXBvcnRhbmNpYSwgCiAgICAgICAgICAgICAgICAgICBtYWluID0gIkltcG9ydGFuY2lhIGRlIFZhcmlhYmxlcyAtIFByb2R1Y3RvIDE1NTAwMSAoWEdCb29zdCkiKQoKIyBDcmVhciBncsOhZmljbyBkZSB2YWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljY2lvbmVzCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgKICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldGFzCikKCmdncGxvdChkYXRvc19ncmFmaWNvLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDE1NTAwMSAoWEdCb29zdCkiLAogICAgeCA9ICJWZW50YXMgT2JzZXJ2YWRhcyIsCiAgICB5ID0gIlZlbnRhcyBQcmVkaWNoYXMiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCgojIEFuw6FsaXNpcyBkZWwgZXJyb3IKZXJyb3JlcyA8LSBkYXRvc19ncmFmaWNvJE9ic2VydmFkbyAtIGRhdG9zX2dyYWZpY28kUHJlZGljaG8KaGlzdChlcnJvcmVzLCAKICAgICBtYWluID0gIkRpc3RyaWJ1Y2nDs24gZGUgRXJyb3JlcyAtIFByb2R1Y3RvIDE1NTAwMSAoWEdCb29zdCkiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIGJyZWFrcyA9IDMwKQoKIyBHcsOhZmljbyBkZWwgZXJyb3IgdnMgcHJlZGljY2nDs24KZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldGFzLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAxNTUwMDEgKFhHQm9vc3QpIiwKICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYApgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBYR0Jvb3N0IHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMSIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJYR0Jvb3N0IiwKICBNQVBFID0gbWFwZV9jb21wbGV0bywKICBNU0UgPSBtc2VfY29tcGxldG8KKSkKYGBgCgojIyBQUk9EVUNUTyAzOTI5Nzg4CgpgYGB7cn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzM5Mjk3ODggJT4lCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkKCiMgUGFzbyAyOiBEaXZpZGlyIGxvcyBkYXRvcyBlbiBjb25qdW50b3MgZGUgZW50cmVuYW1pZW50byAoODAlKSB5IHBydWViYSAoMjAlKQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zX21vZGVsbyRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQp0cmFpbl9kYXRhIDwtIGRhdG9zX21vZGVsb1t0cmFpbl9pbmRleCwgXQp0ZXN0X2RhdGEgPC0gZGF0b3NfbW9kZWxvWy10cmFpbl9pbmRleCwgXQoKIyBQcmVwYXJhciBtYXRyaWNlcyBwYXJhIFhHQm9vc3QKdHJhaW5feCA8LSBhcy5tYXRyaXgodHJhaW5fZGF0YVssIGNvbG5hbWVzKHRyYWluX2RhdGEpICE9ICJWZW50YSJdKQp0cmFpbl95IDwtIHRyYWluX2RhdGEkVmVudGEKCnRlc3RfeCA8LSBhcy5tYXRyaXgodGVzdF9kYXRhWywgY29sbmFtZXModGVzdF9kYXRhKSAhPSAiVmVudGEiXSkKdGVzdF95IDwtIHRlc3RfZGF0YSRWZW50YQoKIyBDcmVhciBETWF0cml4IHBhcmEgWEdCb29zdApkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRyYWluX3gsIGxhYmVsID0gdHJhaW5feSkKZHRlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRlc3RfeCwgbGFiZWwgPSB0ZXN0X3kpCgojIFBhc28gMzogRGVmaW5pciBsYSByZWppbGxhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBHcmlkIFNlYXJjaApwYXJhbV9ncmlkIDwtIGV4cGFuZC5ncmlkKAogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xLCAwLjMpLCAgICAgICAgICAjIExlYXJuaW5nIHJhdGUKICBtYXhfZGVwdGggPSBjKDMsIDYsIDkpLCAgICAgICAgICAgICAgICAgIyBQcm9mdW5kaWRhZCBtw6F4aW1hCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMywgNSksICAgICAgICAgICMgUGVzbyBtw61uaW1vIGRlIG5vZG8gaGlqbwogIHN1YnNhbXBsZSA9IGMoMC43LCAwLjkpLCAgICAgICAgICAgICAgICAjIFByb3BvcmNpw7NuIGRlIG9ic2VydmFjaW9uZXMKICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjcsIDAuOSksICAgICAgICAgIyBQcm9wb3JjacOzbiBkZSB2YXJpYWJsZXMKICBnYW1tYSA9IGMoMCwgMC4xLCAwLjMpICAgICAgICAgICAgICAgICAgIyBSZWd1bGFyaXphY2nDs24gZ2FtbWEKKQoKIyBNb3N0cmFyIGRpbWVuc2lvbmVzIGRlIGxhIHJlamlsbGEKY2F0KCJHcmlkIFNlYXJjaCBwYXJhIFhHQm9vc3QgLSBQcm9kdWN0byAzOTI5Nzg4XG4iKQpjYXQoIk7Dum1lcm8gdG90YWwgZGUgY29tYmluYWNpb25lcyBkZSBoaXBlcnBhcsOhbWV0cm9zOiIsIG5yb3cocGFyYW1fZ3JpZCksICJcblxuIikKCiMgUGFyYSBlc3RlIGVqZW1wbG8sIGxpbWl0YXIgYSAxMiBjb21iaW5hY2lvbmVzIHBhcmEgYWhvcnJhciB0aWVtcG8KIyBFbiB1biBlc2NlbmFyaW8gcmVhbCwgcG9kcsOtYXMgZXZhbHVhciB0b2RhcyBvIHVzYXIgdW5hIGVzdHJhdGVnaWEgbcOhcyBlZmljaWVudGUKc2V0LnNlZWQoNDU2KQppZiAobnJvdyhwYXJhbV9ncmlkKSA+IDEyKSB7CiAgc2VsZWN0ZWRfaW5kaWNlcyA8LSBzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAxMikKICBwYXJhbV9ncmlkIDwtIHBhcmFtX2dyaWRbc2VsZWN0ZWRfaW5kaWNlcywgXQogIGNhdCgiU2VsZWNjaW9uYW5kbyAxMiBjb21iaW5hY2lvbmVzIGFsZWF0b3JpYXMgcGFyYSBldmFsdWFjacOzbi5cblxuIikKfQoKIyBQYXNvIDQ6IEltcGxlbWVudGFyIEdyaWQgU2VhcmNoCnJlc3VsdGFkb3MgPC0gZGF0YS5mcmFtZSgpCgpjYXQoIkluaWNpYW5kbyBHcmlkIFNlYXJjaC4uLlxuIikKCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZCkpIHsKICAjIEV4dHJhZXIgcGFyw6FtZXRyb3MgZGUgbGEgY29tYmluYWNpw7NuIGFjdHVhbAogIHBhcmFtcyA8LSBsaXN0KAogICAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLCAgICAgICMgT2JqZXRpdm8gZGUgcmVncmVzacOzbgogICAgZXZhbF9tZXRyaWMgPSAicm1zZSIsICAgICAgICAgICAgICAgIyBNw6l0cmljYSBkZSBldmFsdWFjacOzbgogICAgZXRhID0gcGFyYW1fZ3JpZCRldGFbaV0sCiAgICBtYXhfZGVwdGggPSBwYXJhbV9ncmlkJG1heF9kZXB0aFtpXSwKICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbV9ncmlkJG1pbl9jaGlsZF93ZWlnaHRbaV0sCiAgICBzdWJzYW1wbGUgPSBwYXJhbV9ncmlkJHN1YnNhbXBsZVtpXSwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbV9ncmlkJGNvbHNhbXBsZV9ieXRyZWVbaV0sCiAgICBnYW1tYSA9IHBhcmFtX2dyaWQkZ2FtbWFbaV0KICApCiAgCiAgY2F0KCJFdmFsdWFuZG8gY29tYmluYWNpw7NuIiwgaSwgImRlIiwgbnJvdyhwYXJhbV9ncmlkKSwgIjpcbiIpCiAgY2F0KCIgIGV0YSA9IiwgcGFyYW1zJGV0YSwgCiAgICAgICIsIG1heF9kZXB0aCA9IiwgcGFyYW1zJG1heF9kZXB0aCwgCiAgICAgICIsIG1pbl9jaGlsZF93ZWlnaHQgPSIsIHBhcmFtcyRtaW5fY2hpbGRfd2VpZ2h0LCAKICAgICAgIiwgc3Vic2FtcGxlID0iLCBwYXJhbXMkc3Vic2FtcGxlLCAKICAgICAgIiwgY29sc2FtcGxlX2J5dHJlZSA9IiwgcGFyYW1zJGNvbHNhbXBsZV9ieXRyZWUsCiAgICAgICIsIGdhbW1hID0iLCBwYXJhbXMkZ2FtbWEsICJcbiIpCiAgCiAgIyBWYWxpZGFjacOzbiBjcnV6YWRhIHBhcmEgZW5jb250cmFyIGVsIG7Dum1lcm8gw7NwdGltbyBkZSBpdGVyYWNpb25lcwogIGN2X21vZGVsIDwtIHhnYi5jdigKICAgIHBhcmFtcyA9IHBhcmFtcywKICAgIGRhdGEgPSBkdHJhaW4sCiAgICBucm91bmRzID0gMjAwLCAgICAgICAgICAgICAgICAgICAgIyBNw6F4aW1vIG7Dum1lcm8gZGUgaXRlcmFjaW9uZXMKICAgIG5mb2xkID0gNSwgICAgICAgICAgICAgICAgICAgICAgICAjIDUtZm9sZCB2YWxpZGFjacOzbiBjcnV6YWRhCiAgICBlYXJseV9zdG9wcGluZ19yb3VuZHMgPSAyMCwgICAgICAgIyBEZXRlbmVyIHNpIG5vIGhheSBtZWpvcmEgZW4gMjAgcm9uZGFzCiAgICB2ZXJib3NlID0gMCAgICAgICAgICAgICAgICAgICAgICAgIyBTdXByaW1pciBtZW5zYWplcwogICkKICAKICAjIEV4dHJhZXIgbWVqb3IgaXRlcmFjacOzbiB5IHN1IFJNU0UKICBiZXN0X2l0ZXJhdGlvbiA8LSBjdl9tb2RlbCRiZXN0X2l0ZXJhdGlvbgogIGJlc3Rfcm1zZSA8LSBtaW4oY3ZfbW9kZWwkZXZhbHVhdGlvbl9sb2ckdGVzdF9ybXNlX21lYW4pCiAgCiAgY2F0KCIgIE1lam9yIGl0ZXJhY2nDs246IiwgYmVzdF9pdGVyYXRpb24sICJcbiIpCiAgY2F0KCIgIFJNU0UgZW4gdmFsaWRhY2nDs24gY3J1emFkYToiLCBiZXN0X3Jtc2UsICJcblxuIikKICAKICAjIEd1YXJkYXIgcmVzdWx0YWRvcwogIHJlc3VsdGFkb19hY3R1YWwgPC0gZGF0YS5mcmFtZSgKICAgIGV0YSA9IHBhcmFtcyRldGEsCiAgICBtYXhfZGVwdGggPSBwYXJhbXMkbWF4X2RlcHRoLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtcyRtaW5fY2hpbGRfd2VpZ2h0LAogICAgc3Vic2FtcGxlID0gcGFyYW1zJHN1YnNhbXBsZSwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbXMkY29sc2FtcGxlX2J5dHJlZSwKICAgIGdhbW1hID0gcGFyYW1zJGdhbW1hLAogICAgbnJvdW5kcyA9IGJlc3RfaXRlcmF0aW9uLAogICAgcm1zZV9jdiA9IGJlc3Rfcm1zZQogICkKICAKICByZXN1bHRhZG9zIDwtIHJiaW5kKHJlc3VsdGFkb3MsIHJlc3VsdGFkb19hY3R1YWwpCn0KCiMgT3JkZW5hciByZXN1bHRhZG9zIHBvciBSTVNFIChkZSBtZW5vciBhIG1heW9yKQpyZXN1bHRhZG9zIDwtIHJlc3VsdGFkb3Nbb3JkZXIocmVzdWx0YWRvcyRybXNlX2N2KSwgXQoKIyBQYXNvIDU6IE1vc3RyYXIgcmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2gKY2F0KCJSZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaCBvcmRlbmFkb3MgcG9yIFJNU0U6XG4iKQpwcmludChyZXN1bHRhZG9zKQoKIyBWaXN1YWxpemFyIHJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoCmdncGxvdChyZXN1bHRhZG9zLCBhZXMoeCA9IHJlb3JkZXIocGFzdGUoIkNvbWIiLCAxOm5yb3cocmVzdWx0YWRvcykpLCBybXNlX2N2KSwgeSA9IHJtc2VfY3YpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJSZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaCAtIFByb2R1Y3RvIDM5Mjk3ODgiLAogICAgeCA9ICJDb21iaW5hY2nDs24gZGUgSGlwZXJwYXLDoW1ldHJvcyIsCiAgICB5ID0gIlJNU0UgZW4gVmFsaWRhY2nDs24gQ3J1emFkYSIKICApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkpCgojIFBhc28gNjogU2VsZWNjaW9uYXIgbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcwptZWpvcmVzX3BhcmFtcyA8LSBsaXN0KAogIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICBldmFsX21ldHJpYyA9ICJybXNlIiwKICBldGEgPSByZXN1bHRhZG9zJGV0YVsxXSwKICBtYXhfZGVwdGggPSByZXN1bHRhZG9zJG1heF9kZXB0aFsxXSwKICBtaW5fY2hpbGRfd2VpZ2h0ID0gcmVzdWx0YWRvcyRtaW5fY2hpbGRfd2VpZ2h0WzFdLAogIHN1YnNhbXBsZSA9IHJlc3VsdGFkb3Mkc3Vic2FtcGxlWzFdLAogIGNvbHNhbXBsZV9ieXRyZWUgPSByZXN1bHRhZG9zJGNvbHNhbXBsZV9ieXRyZWVbMV0sCiAgZ2FtbWEgPSByZXN1bHRhZG9zJGdhbW1hWzFdCikKCm1lam9yX25yb3VuZHMgPC0gcmVzdWx0YWRvcyRucm91bmRzWzFdCgpjYXQoIlxuTWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zIGVuY29udHJhZG9zOlxuIikKcHJpbnQobWVqb3Jlc19wYXJhbXMpCmNhdCgiTsO6bWVybyDDs3B0aW1vIGRlIHJvbmRhczoiLCBtZWpvcl9ucm91bmRzLCAiXG5cbiIpCgojIFBhc28gNzogRW50cmVuYXIgZWwgbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zCmNhdCgiRW50cmVuYW5kbyBtb2RlbG8gZmluYWwgY29uIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MuLi5cbiIpCgptb2RlbG9fZmluYWwgPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zLAogIGRhdGEgPSBkdHJhaW4sCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsCiAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbiwgdGVzdCA9IGR0ZXN0KSwKICB2ZXJib3NlID0gMAopCgojIEdVQVJEQVIgRUwgTU9ERUxPIENPTiBOT01CUkUgRVNQRVJBRE8KbW9kZWxvX3hnYl8zOTI5Nzg4IDwtIG1vZGVsb19maW5hbAoKIyBQYXNvIDg6IEV2YWx1YXIgZWwgbW9kZWxvCiMgUHJlZGljY2lvbmVzIGVuIGNvbmp1bnRvIGRlIHBydWViYQpwcmVkaWNjaW9uZXNfdGVzdCA8LSBwcmVkaWN0KG1vZGVsb19maW5hbCwgZHRlc3QpCgojIENhbGN1bGFyIG3DqXRyaWNhcwojIE1BUEUKbWFwZV90ZXN0IDwtIG1lYW4oYWJzKCh0ZXN0X3kgLSBwcmVkaWNjaW9uZXNfdGVzdCkgLyBwbWF4KHRlc3RfeSwgMC4wMSkpKSAqIDEwMAoKIyBNU0UKbXNlX3Rlc3QgPC0gbWVhbigodGVzdF95IC0gcHJlZGljY2lvbmVzX3Rlc3QpXjIpCgojIE1vc3RyYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGRlIHBydWViYQpjYXQoIlxuTcOpdHJpY2FzIGVuIGNvbmp1bnRvIGRlIHBydWViYTpcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV90ZXN0LCAiXG4iKQpjYXQoIk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbXNlX3Rlc3QsICJcblxuIikKCiMgUGFzbyA5OiBQcmVkaWNjaW9uZXMgZW4gZWwgY29uanVudG8gY29tcGxldG8KIyBIYWNlciBwcmVkaWNjaW9uZXMgZW4gdG9kbyBlbCBjb25qdW50byBkZSBkYXRvcyBwYXJhIGNvbXBhcmFjacOzbiBjb24gb3Ryb3MgbW9kZWxvcwp4X2NvbXBsZXRvIDwtIGFzLm1hdHJpeChkYXRvc19tb2RlbG9bLCBjb2xuYW1lcyhkYXRvc19tb2RlbG8pICE9ICJWZW50YSJdKQpwcmVkaWNjaW9uZXNfY29tcGxldG8gPC0gcHJlZGljdChtb2RlbG9fZmluYWwsIHhfY29tcGxldG8pCgojIENhbGN1bGFyIG3DqXRyaWNhcyBlbiBjb25qdW50byBjb21wbGV0bwptYXBlX2NvbXBsZXRvIDwtIG1lYW4oYWJzKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pIC8gCiAgICAgICAgICAgICAgICAgICAgICAgIHBtYXgoZGF0b3NfbW9kZWxvJFZlbnRhLCAwLjAxKSkpICogMTAwCgptc2VfY29tcGxldG8gPC0gbWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvKV4yKQoKIyBNb3N0cmFyIG3DqXRyaWNhcyBlbiBjb25qdW50byBjb21wbGV0bwpjYXQoIk3DqXRyaWNhcyBlbiBjb25qdW50byBjb21wbGV0bzpcbiIpCmNhdCgiTUFQRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbWFwZV9jb21wbGV0bywgIlxuIikKY2F0KCJNU0UgZGVsIG1vZGVsbyBYR0Jvb3N0OiIsIG1zZV9jb21wbGV0bywgIlxuXG4iKQoKIyBQYXNvIDEwOiBBbsOhbGlzaXMgZGUgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCiMgQ2FsY3VsYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCmltcG9ydGFuY2lhIDwtIHhnYi5pbXBvcnRhbmNlKAogIGZlYXR1cmVfbmFtZXMgPSBjb2xuYW1lcyhkYXRvc19tb2RlbG8pW2NvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0sCiAgbW9kZWwgPSBtb2RlbG9fZmluYWwKKQoKIyBNb3N0cmFyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwpjYXQoIkltcG9ydGFuY2lhIGRlIHZhcmlhYmxlczpcbiIpCnByaW50KGltcG9ydGFuY2lhKQoKIyBHcmFmaWNhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKeGdiLnBsb3QuaW1wb3J0YW5jZShpbXBvcnRhbmNlX21hdHJpeCA9IGltcG9ydGFuY2lhLCAKICAgICAgICAgICAgICAgICAgIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMzkyOTc4OCAoWEdCb29zdCkiKQoKIyBQYXNvIDExOiBWaXN1YWxpemFjaW9uZXMgcGFyYSBldmFsdWFjacOzbgojIEdyw6FmaWNvIDE6IFZhbG9yZXMgb2JzZXJ2YWRvcyB2cyBwcmVkaWNob3MKZGF0b3NfZ3JhZmljbyA8LSBkYXRhLmZyYW1lKAogIE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwKICBQcmVkaWNobyA9IHByZWRpY2Npb25lc19jb21wbGV0bwopCgpnZ3Bsb3QoZGF0b3NfZ3JhZmljbywgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gMCwgc2xvcGUgPSAxLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlZhbG9yZXMgT2JzZXJ2YWRvcyB2cyBQcmVkaWNjaW9uZXMgLSBQcm9kdWN0byAzOTI5Nzg4IChYR0Jvb3N0KSIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgR3LDoWZpY28gMjogQW7DoWxpc2lzIGRlIHJlc2lkdW9zCmVycm9yZXMgPC0gZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvCgojIEhpc3RvZ3JhbWEgZGUgZXJyb3JlcwpoaXN0KGVycm9yZXMsIAogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMzkyOTc4OCAoWEdCb29zdCkiLAogICAgIHhsYWIgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIsCiAgICAgY29sID0gInNreWJsdWUiLAogICAgIGJyZWFrcyA9IDMwKQoKIyBHcsOhZmljbyAzOiBFcnJvcmVzIHZzIFByZWRpY2Npb25lcwpnZ3Bsb3QoZGF0YS5mcmFtZShQcmVkaWNobyA9IHByZWRpY2Npb25lc19jb21wbGV0bywgRXJyb3IgPSBlcnJvcmVzKSwgYWVzKHggPSBQcmVkaWNobywgeSA9IEVycm9yKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiLCBsaW5ldHlwZSA9ICJkYXNoZWQiKSArCiAgbGFicygKICAgIHRpdGxlID0gIkVycm9yIHZzIFByZWRpY2Npw7NuIC0gUHJvZHVjdG8gMzkyOTc4OCAoWEdCb29zdCkiLAogICAgeCA9ICJWZW50YXMgUHJlZGljaGFzIiwKICAgIHkgPSAiRXJyb3IgKE9ic2VydmFkbyAtIFByZWRpY2hvKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFhHQm9vc3QgcGFyYSBwcm9kdWN0byAxNTUwMDEKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkyOTc4OCIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJYR0Jvb3N0IiwKICBNQVBFID0gbWFwZV9jb21wbGV0bywKICBNU0UgPSBtc2VfY29tcGxldG8KKSkKYGBgCgojIyBQUk9EVUNUTyAzOTA0MTUyCmBgYHtyfQojIFByZXBhcmFyIGRhdG9zIHBhcmEgZWwgbW9kZWxvIChlbGltaW5hciBjb2x1bW5hcyBubyBuZWNlc2FyaWFzKQpkYXRvc19tb2RlbG8gPC0gZGF0b3NfMzkwNDE1MiAlPiUKICBzZWxlY3QoLVRyeF9GZWNoYSwgLUZlY2hhKQoKIyBQYXNvIDI6IERpdmlkaXIgbG9zIGRhdG9zIGVuIGNvbmp1bnRvcyBkZSBlbnRyZW5hbWllbnRvICg4MCUpIHkgcHJ1ZWJhICgyMCUpCnNldC5zZWVkKDEyMykgICMgUGFyYSByZXByb2R1Y2liaWxpZGFkCnRyYWluX2luZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGF0b3NfbW9kZWxvJFZlbnRhLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpCnRyYWluX2RhdGEgPC0gZGF0b3NfbW9kZWxvW3RyYWluX2luZGV4LCBdCnRlc3RfZGF0YSA8LSBkYXRvc19tb2RlbG9bLXRyYWluX2luZGV4LCBdCgojIFByZXBhcmFyIG1hdHJpY2VzIHBhcmEgWEdCb29zdAp0cmFpbl94IDwtIGFzLm1hdHJpeCh0cmFpbl9kYXRhWywgY29sbmFtZXModHJhaW5fZGF0YSkgIT0gIlZlbnRhIl0pCnRyYWluX3kgPC0gdHJhaW5fZGF0YSRWZW50YQoKdGVzdF94IDwtIGFzLm1hdHJpeCh0ZXN0X2RhdGFbLCBjb2xuYW1lcyh0ZXN0X2RhdGEpICE9ICJWZW50YSJdKQp0ZXN0X3kgPC0gdGVzdF9kYXRhJFZlbnRhCgojIENyZWFyIERNYXRyaXggcGFyYSBYR0Jvb3N0CmR0cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0gdHJhaW5feCwgbGFiZWwgPSB0cmFpbl95KQpkdGVzdCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gdGVzdF94LCBsYWJlbCA9IHRlc3RfeSkKCiMgUGFzbyAzOiBEZWZpbmlyIGxhIHJlamlsbGEgZGUgaGlwZXJwYXLDoW1ldHJvcyBwYXJhIEdyaWQgU2VhcmNoCnBhcmFtX2dyaWQgPC0gZXhwYW5kLmdyaWQoCiAgZXRhID0gYygwLjAxLCAwLjA1LCAwLjEsIDAuMyksICAgICAgICAgICMgTGVhcm5pbmcgcmF0ZQogIG1heF9kZXB0aCA9IGMoMywgNiwgOSksICAgICAgICAgICAgICAgICAjIFByb2Z1bmRpZGFkIG3DoXhpbWEKICBtaW5fY2hpbGRfd2VpZ2h0ID0gYygxLCAzLCA1KSwgICAgICAgICAgIyBQZXNvIG3DrW5pbW8gZGUgbm9kbyBoaWpvCiAgc3Vic2FtcGxlID0gYygwLjcsIDAuOSksICAgICAgICAgICAgICAgICMgUHJvcG9yY2nDs24gZGUgb2JzZXJ2YWNpb25lcwogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuNywgMC45KSwgICAgICAgICAjIFByb3BvcmNpw7NuIGRlIHZhcmlhYmxlcwogIGdhbW1hID0gYygwLCAwLjEsIDAuMykgICAgICAgICAgICAgICAgICAjIFJlZ3VsYXJpemFjacOzbiBnYW1tYQopCgojIE1vc3RyYXIgZGltZW5zaW9uZXMgZGUgbGEgcmVqaWxsYQpjYXQoIkdyaWQgU2VhcmNoIHBhcmEgWEdCb29zdCAtIFByb2R1Y3RvIDM5MDQxNTJcbiIpCmNhdCgiTsO6bWVybyB0b3RhbCBkZSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3M6IiwgbnJvdyhwYXJhbV9ncmlkKSwgIlxuXG4iKQoKIyBQYXJhIGVzdGUgZWplbXBsbywgbGltaXRhciBhIDEyIGNvbWJpbmFjaW9uZXMgcGFyYSBhaG9ycmFyIHRpZW1wbwojIEVuIHVuIGVzY2VuYXJpbyByZWFsLCBwb2Ryw61hcyBldmFsdWFyIHRvZGFzIG8gdXNhciB1bmEgZXN0cmF0ZWdpYSBtw6FzIGVmaWNpZW50ZQpzZXQuc2VlZCg0NTYpCmlmIChucm93KHBhcmFtX2dyaWQpID4gMTIpIHsKICBzZWxlY3RlZF9pbmRpY2VzIDwtIHNhbXBsZSgxOm5yb3cocGFyYW1fZ3JpZCksIDEyKQogIHBhcmFtX2dyaWQgPC0gcGFyYW1fZ3JpZFtzZWxlY3RlZF9pbmRpY2VzLCBdCiAgY2F0KCJTZWxlY2Npb25hbmRvIDEyIGNvbWJpbmFjaW9uZXMgYWxlYXRvcmlhcyBwYXJhIGV2YWx1YWNpw7NuLlxuXG4iKQp9CgojIFBhc28gNDogSW1wbGVtZW50YXIgR3JpZCBTZWFyY2gKcmVzdWx0YWRvcyA8LSBkYXRhLmZyYW1lKCkKCmNhdCgiSW5pY2lhbmRvIEdyaWQgU2VhcmNoLi4uXG4iKQoKZm9yIChpIGluIDE6bnJvdyhwYXJhbV9ncmlkKSkgewogICMgRXh0cmFlciBwYXLDoW1ldHJvcyBkZSBsYSBjb21iaW5hY2nDs24gYWN0dWFsCiAgcGFyYW1zIDwtIGxpc3QoCiAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsICAgICAgIyBPYmpldGl2byBkZSByZWdyZXNpw7NuCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwgICAgICAgICAgICAgICAjIE3DqXRyaWNhIGRlIGV2YWx1YWNpw7NuCiAgICBldGEgPSBwYXJhbV9ncmlkJGV0YVtpXSwKICAgIG1heF9kZXB0aCA9IHBhcmFtX2dyaWQkbWF4X2RlcHRoW2ldLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtX2dyaWQkbWluX2NoaWxkX3dlaWdodFtpXSwKICAgIHN1YnNhbXBsZSA9IHBhcmFtX2dyaWQkc3Vic2FtcGxlW2ldLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtX2dyaWQkY29sc2FtcGxlX2J5dHJlZVtpXSwKICAgIGdhbW1hID0gcGFyYW1fZ3JpZCRnYW1tYVtpXQogICkKICAKICBjYXQoIkV2YWx1YW5kbyBjb21iaW5hY2nDs24iLCBpLCAiZGUiLCBucm93KHBhcmFtX2dyaWQpLCAiOlxuIikKICBjYXQoIiAgZXRhID0iLCBwYXJhbXMkZXRhLCAKICAgICAgIiwgbWF4X2RlcHRoID0iLCBwYXJhbXMkbWF4X2RlcHRoLCAKICAgICAgIiwgbWluX2NoaWxkX3dlaWdodCA9IiwgcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsIAogICAgICAiLCBzdWJzYW1wbGUgPSIsIHBhcmFtcyRzdWJzYW1wbGUsIAogICAgICAiLCBjb2xzYW1wbGVfYnl0cmVlID0iLCBwYXJhbXMkY29sc2FtcGxlX2J5dHJlZSwKICAgICAgIiwgZ2FtbWEgPSIsIHBhcmFtcyRnYW1tYSwgIlxuIikKICAKICAjIFZhbGlkYWNpw7NuIGNydXphZGEgcGFyYSBlbmNvbnRyYXIgZWwgbsO6bWVybyDDs3B0aW1vIGRlIGl0ZXJhY2lvbmVzCiAgY3ZfbW9kZWwgPC0geGdiLmN2KAogICAgcGFyYW1zID0gcGFyYW1zLAogICAgZGF0YSA9IGR0cmFpbiwKICAgIG5yb3VuZHMgPSAyMDAsICAgICAgICAgICAgICAgICAgICAjIE3DoXhpbW8gbsO6bWVybyBkZSBpdGVyYWNpb25lcwogICAgbmZvbGQgPSA1LCAgICAgICAgICAgICAgICAgICAgICAgICMgNS1mb2xkIHZhbGlkYWNpw7NuIGNydXphZGEKICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDIwLCAgICAgICAjIERldGVuZXIgc2kgbm8gaGF5IG1lam9yYSBlbiAyMCByb25kYXMKICAgIHZlcmJvc2UgPSAwICAgICAgICAgICAgICAgICAgICAgICAjIFN1cHJpbWlyIG1lbnNhamVzCiAgKQogIAogICMgRXh0cmFlciBtZWpvciBpdGVyYWNpw7NuIHkgc3UgUk1TRQogIGJlc3RfaXRlcmF0aW9uIDwtIGN2X21vZGVsJGJlc3RfaXRlcmF0aW9uCiAgYmVzdF9ybXNlIDwtIG1pbihjdl9tb2RlbCRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbikKICAKICBjYXQoIiAgTWVqb3IgaXRlcmFjacOzbjoiLCBiZXN0X2l0ZXJhdGlvbiwgIlxuIikKICBjYXQoIiAgUk1TRSBlbiB2YWxpZGFjacOzbiBjcnV6YWRhOiIsIGJlc3Rfcm1zZSwgIlxuXG4iKQogIAogICMgR3VhcmRhciByZXN1bHRhZG9zCiAgcmVzdWx0YWRvX2FjdHVhbCA8LSBkYXRhLmZyYW1lKAogICAgZXRhID0gcGFyYW1zJGV0YSwKICAgIG1heF9kZXB0aCA9IHBhcmFtcyRtYXhfZGVwdGgsCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsCiAgICBzdWJzYW1wbGUgPSBwYXJhbXMkc3Vic2FtcGxlLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtcyRjb2xzYW1wbGVfYnl0cmVlLAogICAgZ2FtbWEgPSBwYXJhbXMkZ2FtbWEsCiAgICBucm91bmRzID0gYmVzdF9pdGVyYXRpb24sCiAgICBybXNlX2N2ID0gYmVzdF9ybXNlCiAgKQogIAogIHJlc3VsdGFkb3MgPC0gcmJpbmQocmVzdWx0YWRvcywgcmVzdWx0YWRvX2FjdHVhbCkKfQoKIyBPcmRlbmFyIHJlc3VsdGFkb3MgcG9yIFJNU0UgKGRlIG1lbm9yIGEgbWF5b3IpCnJlc3VsdGFkb3MgPC0gcmVzdWx0YWRvc1tvcmRlcihyZXN1bHRhZG9zJHJtc2VfY3YpLCBdCgojIFBhc28gNTogTW9zdHJhciByZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaApjYXQoIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIG9yZGVuYWRvcyBwb3IgUk1TRTpcbiIpCnByaW50KHJlc3VsdGFkb3MpCgojIFZpc3VhbGl6YXIgcmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2gKZ2dwbG90KHJlc3VsdGFkb3MsIGFlcyh4ID0gcmVvcmRlcihwYXN0ZSgiQ29tYiIsIDE6bnJvdyhyZXN1bHRhZG9zKSksIHJtc2VfY3YpLCB5ID0gcm1zZV9jdikpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJzdGVlbGJsdWUiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIC0gUHJvZHVjdG8gMzkwNDE1MiIsCiAgICB4ID0gIkNvbWJpbmFjacOzbiBkZSBIaXBlcnBhcsOhbWV0cm9zIiwKICAgIHkgPSAiUk1TRSBlbiBWYWxpZGFjacOzbiBDcnV6YWRhIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkKCiMgUGFzbyA2OiBTZWxlY2Npb25hciBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zCm1lam9yZXNfcGFyYW1zIDwtIGxpc3QoCiAgb2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiLAogIGV2YWxfbWV0cmljID0gInJtc2UiLAogIGV0YSA9IHJlc3VsdGFkb3MkZXRhWzFdLAogIG1heF9kZXB0aCA9IHJlc3VsdGFkb3MkbWF4X2RlcHRoWzFdLAogIG1pbl9jaGlsZF93ZWlnaHQgPSByZXN1bHRhZG9zJG1pbl9jaGlsZF93ZWlnaHRbMV0sCiAgc3Vic2FtcGxlID0gcmVzdWx0YWRvcyRzdWJzYW1wbGVbMV0sCiAgY29sc2FtcGxlX2J5dHJlZSA9IHJlc3VsdGFkb3MkY29sc2FtcGxlX2J5dHJlZVsxXSwKICBnYW1tYSA9IHJlc3VsdGFkb3MkZ2FtbWFbMV0KKQoKbWVqb3JfbnJvdW5kcyA8LSByZXN1bHRhZG9zJG5yb3VuZHNbMV0KCmNhdCgiXG5NZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MgZW5jb250cmFkb3M6XG4iKQpwcmludChtZWpvcmVzX3BhcmFtcykKY2F0KCJOw7ptZXJvIMOzcHRpbW8gZGUgcm9uZGFzOiIsIG1lam9yX25yb3VuZHMsICJcblxuIikKCiMgUGFzbyA3OiBFbnRyZW5hciBlbCBtb2RlbG8gZmluYWwgY29uIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MKY2F0KCJFbnRyZW5hbmRvIG1vZGVsbyBmaW5hbCBjb24gbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcy4uLlxuIikKCm1vZGVsb19maW5hbCA8LSB4Z2IudHJhaW4oCiAgcGFyYW1zID0gbWVqb3Jlc19wYXJhbXMsCiAgZGF0YSA9IGR0cmFpbiwKICBucm91bmRzID0gbWVqb3JfbnJvdW5kcywKICB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gZHRyYWluLCB0ZXN0ID0gZHRlc3QpLAogIHZlcmJvc2UgPSAwCikKCm1vZGVsb194Z2JfMzkwNDE1MiA8LSBtb2RlbG9fZmluYWwKCgojIFBhc28gODogRXZhbHVhciBlbCBtb2RlbG8KIyBQcmVkaWNjaW9uZXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhCnByZWRpY2Npb25lc190ZXN0IDwtIHByZWRpY3QobW9kZWxvX2ZpbmFsLCBkdGVzdCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCiMgTUFQRQptYXBlX3Rlc3QgPC0gbWVhbihhYnMoKHRlc3RfeSAtIHByZWRpY2Npb25lc190ZXN0KSAvIHBtYXgodGVzdF95LCAwLjAxKSkpICogMTAwCgojIE1TRQptc2VfdGVzdCA8LSBtZWFuKCh0ZXN0X3kgLSBwcmVkaWNjaW9uZXNfdGVzdCleMikKCiMgTW9zdHJhciBtw6l0cmljYXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhCmNhdCgiXG5Nw6l0cmljYXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhOlxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX3Rlc3QsICJcbiIpCmNhdCgiTVNFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtc2VfdGVzdCwgIlxuXG4iKQoKIyBQYXNvIDk6IFByZWRpY2Npb25lcyBlbiBlbCBjb25qdW50byBjb21wbGV0bwojIEhhY2VyIHByZWRpY2Npb25lcyBlbiB0b2RvIGVsIGNvbmp1bnRvIGRlIGRhdG9zIHBhcmEgY29tcGFyYWNpw7NuIGNvbiBvdHJvcyBtb2RlbG9zCnhfY29tcGxldG8gPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb1ssIGNvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0pCnByZWRpY2Npb25lc19jb21wbGV0byA8LSBwcmVkaWN0KG1vZGVsb19maW5hbCwgeF9jb21wbGV0bykKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvCm1hcGVfY29tcGxldG8gPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0bykgLyAKICAgICAgICAgICAgICAgICAgICAgICAgcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKCm1zZV9jb21wbGV0byA8LSBtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pXjIpCgojIE1vc3RyYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvCmNhdCgiTcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvOlxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX2NvbXBsZXRvLCAiXG4iKQpjYXQoIk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbXNlX2NvbXBsZXRvLCAiXG5cbiIpCgojIFBhc28gMTA6IEFuw6FsaXNpcyBkZSBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKaW1wb3J0YW5jaWEgPC0geGdiLmltcG9ydGFuY2UoCiAgZmVhdHVyZV9uYW1lcyA9IGNvbG5hbWVzKGRhdG9zX21vZGVsbylbY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSwKICBtb2RlbCA9IG1vZGVsb19maW5hbAopCgojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCmNhdCgiSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzOlxuIikKcHJpbnQoaW1wb3J0YW5jaWEpCgojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp4Z2IucGxvdC5pbXBvcnRhbmNlKGltcG9ydGFuY2VfbWF0cml4ID0gaW1wb3J0YW5jaWEsIAogICAgICAgICAgICAgICAgICAgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAzOTA0MTUyIChYR0Jvb3N0KSIpCgojIFBhc28gMTE6IFZpc3VhbGl6YWNpb25lcyBwYXJhIGV2YWx1YWNpw7NuCiMgR3LDoWZpY28gMTogVmFsb3JlcyBvYnNlcnZhZG9zIHZzIHByZWRpY2hvcwpkYXRvc19ncmFmaWNvIDwtIGRhdGEuZnJhbWUoCiAgT2JzZXJ2YWRvID0gZGF0b3NfbW9kZWxvJFZlbnRhLAogIFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvCikKCmdncGxvdChkYXRvc19ncmFmaWNvLCBhZXMoeCA9IE9ic2VydmFkbywgeSA9IFByZWRpY2hvKSkgKwogIGdlb21fcG9pbnQoYWxwaGEgPSAwLjUpICsKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSAwLCBzbG9wZSA9IDEsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiVmFsb3JlcyBPYnNlcnZhZG9zIHZzIFByZWRpY2Npb25lcyAtIFByb2R1Y3RvIDM5MDQxNTIgKFhHQm9vc3QpIiwKICAgIHggPSAiVmVudGFzIE9ic2VydmFkYXMiLAogICAgeSA9ICJWZW50YXMgUHJlZGljaGFzIgogICkgKwogIHRoZW1lX21pbmltYWwoKQoKIyBHcsOhZmljbyAyOiBBbsOhbGlzaXMgZGUgcmVzaWR1b3MKZXJyb3JlcyA8LSBkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8KCiMgSGlzdG9ncmFtYSBkZSBlcnJvcmVzCmhpc3QoZXJyb3JlcywgCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAzOTA0MTUyIChYR0Jvb3N0KSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEdyw6FmaWNvIDM6IEVycm9yZXMgdnMgUHJlZGljY2lvbmVzCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAzOTA0MTUyIChYR0Jvb3N0KSIsCiAgICB4ID0gIlZlbnRhcyBQcmVkaWNoYXMiLAogICAgeSA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIgogICkgKwogIHRoZW1lX21pbmltYWwoKQpgYGAKCmBgYHtyfQojIEd1YXJkYXIgbcOpdHJpY2FzIGRlIFhHQm9vc3QgcGFyYSBwcm9kdWN0byAKaWYoIWV4aXN0cygibWV0cmljYXNfY29tcGFyYXRpdmFzIikpIHsKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gZGF0YS5mcmFtZSgKICAgIFByb2R1Y3RvID0gY2hhcmFjdGVyKCksCiAgICBNb2RlbG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1BUEUgPSBudW1lcmljKCksCiAgICBNU0UgPSBudW1lcmljKCksCiAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKICApCn0KCm1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSByYmluZChtZXRyaWNhc19jb21wYXJhdGl2YXMsIGRhdGEuZnJhbWUoCiAgUHJvZHVjdG8gPSAiMzkwNDE1MiIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJYR0Jvb3N0IiwKICBNQVBFID0gbWFwZV9jb21wbGV0bywKICBNU0UgPSBtc2VfY29tcGxldG8KKSkKYGBgCgojIyBQUk9EVUNUTyAxNTUwMDIKYGBge3J9CiMgUHJlcGFyYXIgZGF0b3MgcGFyYSBlbCBtb2RlbG8gKGVsaW1pbmFyIGNvbHVtbmFzIG5vIG5lY2VzYXJpYXMpCmRhdG9zX21vZGVsbyA8LSBkYXRvc18xNTUwMDIgJT4lCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkKCiMgUGFzbyAyOiBEaXZpZGlyIGxvcyBkYXRvcyBlbiBjb25qdW50b3MgZGUgZW50cmVuYW1pZW50byAoODAlKSB5IHBydWViYSAoMjAlKQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zX21vZGVsbyRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQp0cmFpbl9kYXRhIDwtIGRhdG9zX21vZGVsb1t0cmFpbl9pbmRleCwgXQp0ZXN0X2RhdGEgPC0gZGF0b3NfbW9kZWxvWy10cmFpbl9pbmRleCwgXQoKIyBQcmVwYXJhciBtYXRyaWNlcyBwYXJhIFhHQm9vc3QKdHJhaW5feCA8LSBhcy5tYXRyaXgodHJhaW5fZGF0YVssIGNvbG5hbWVzKHRyYWluX2RhdGEpICE9ICJWZW50YSJdKQp0cmFpbl95IDwtIHRyYWluX2RhdGEkVmVudGEKCnRlc3RfeCA8LSBhcy5tYXRyaXgodGVzdF9kYXRhWywgY29sbmFtZXModGVzdF9kYXRhKSAhPSAiVmVudGEiXSkKdGVzdF95IDwtIHRlc3RfZGF0YSRWZW50YQoKIyBDcmVhciBETWF0cml4IHBhcmEgWEdCb29zdApkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRyYWluX3gsIGxhYmVsID0gdHJhaW5feSkKZHRlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRlc3RfeCwgbGFiZWwgPSB0ZXN0X3kpCgojIFBhc28gMzogRGVmaW5pciBsYSByZWppbGxhIGRlIGhpcGVycGFyw6FtZXRyb3MgcGFyYSBHcmlkIFNlYXJjaApwYXJhbV9ncmlkIDwtIGV4cGFuZC5ncmlkKAogIGV0YSA9IGMoMC4wMSwgMC4wNSwgMC4xLCAwLjMpLCAgICAgICAgICAjIExlYXJuaW5nIHJhdGUKICBtYXhfZGVwdGggPSBjKDMsIDYsIDkpLCAgICAgICAgICAgICAgICAgIyBQcm9mdW5kaWRhZCBtw6F4aW1hCiAgbWluX2NoaWxkX3dlaWdodCA9IGMoMSwgMywgNSksICAgICAgICAgICMgUGVzbyBtw61uaW1vIGRlIG5vZG8gaGlqbwogIHN1YnNhbXBsZSA9IGMoMC43LCAwLjkpLCAgICAgICAgICAgICAgICAjIFByb3BvcmNpw7NuIGRlIG9ic2VydmFjaW9uZXMKICBjb2xzYW1wbGVfYnl0cmVlID0gYygwLjcsIDAuOSksICAgICAgICAgIyBQcm9wb3JjacOzbiBkZSB2YXJpYWJsZXMKICBnYW1tYSA9IGMoMCwgMC4xLCAwLjMpICAgICAgICAgICAgICAgICAgIyBSZWd1bGFyaXphY2nDs24gZ2FtbWEKKQoKIyBNb3N0cmFyIGRpbWVuc2lvbmVzIGRlIGxhIHJlamlsbGEKY2F0KCJHcmlkIFNlYXJjaCBwYXJhIFhHQm9vc3QgLSBQcm9kdWN0byAxNTUwMDJcbiIpCmNhdCgiTsO6bWVybyB0b3RhbCBkZSBjb21iaW5hY2lvbmVzIGRlIGhpcGVycGFyw6FtZXRyb3M6IiwgbnJvdyhwYXJhbV9ncmlkKSwgIlxuXG4iKQoKIyBQYXJhIGVzdGUgZWplbXBsbywgbGltaXRhciBhIDEyIGNvbWJpbmFjaW9uZXMgcGFyYSBhaG9ycmFyIHRpZW1wbwojIEVuIHVuIGVzY2VuYXJpbyByZWFsLCBwb2Ryw61hcyBldmFsdWFyIHRvZGFzIG8gdXNhciB1bmEgZXN0cmF0ZWdpYSBtw6FzIGVmaWNpZW50ZQpzZXQuc2VlZCg0NTYpCmlmIChucm93KHBhcmFtX2dyaWQpID4gMTIpIHsKICBzZWxlY3RlZF9pbmRpY2VzIDwtIHNhbXBsZSgxOm5yb3cocGFyYW1fZ3JpZCksIDEyKQogIHBhcmFtX2dyaWQgPC0gcGFyYW1fZ3JpZFtzZWxlY3RlZF9pbmRpY2VzLCBdCiAgY2F0KCJTZWxlY2Npb25hbmRvIDEyIGNvbWJpbmFjaW9uZXMgYWxlYXRvcmlhcyBwYXJhIGV2YWx1YWNpw7NuLlxuXG4iKQp9CgojIFBhc28gNDogSW1wbGVtZW50YXIgR3JpZCBTZWFyY2gKcmVzdWx0YWRvcyA8LSBkYXRhLmZyYW1lKCkKCmNhdCgiSW5pY2lhbmRvIEdyaWQgU2VhcmNoLi4uXG4iKQoKZm9yIChpIGluIDE6bnJvdyhwYXJhbV9ncmlkKSkgewogICMgRXh0cmFlciBwYXLDoW1ldHJvcyBkZSBsYSBjb21iaW5hY2nDs24gYWN0dWFsCiAgcGFyYW1zIDwtIGxpc3QoCiAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsICAgICAgIyBPYmpldGl2byBkZSByZWdyZXNpw7NuCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwgICAgICAgICAgICAgICAjIE3DqXRyaWNhIGRlIGV2YWx1YWNpw7NuCiAgICBldGEgPSBwYXJhbV9ncmlkJGV0YVtpXSwKICAgIG1heF9kZXB0aCA9IHBhcmFtX2dyaWQkbWF4X2RlcHRoW2ldLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtX2dyaWQkbWluX2NoaWxkX3dlaWdodFtpXSwKICAgIHN1YnNhbXBsZSA9IHBhcmFtX2dyaWQkc3Vic2FtcGxlW2ldLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtX2dyaWQkY29sc2FtcGxlX2J5dHJlZVtpXSwKICAgIGdhbW1hID0gcGFyYW1fZ3JpZCRnYW1tYVtpXQogICkKICAKICBjYXQoIkV2YWx1YW5kbyBjb21iaW5hY2nDs24iLCBpLCAiZGUiLCBucm93KHBhcmFtX2dyaWQpLCAiOlxuIikKICBjYXQoIiAgZXRhID0iLCBwYXJhbXMkZXRhLCAKICAgICAgIiwgbWF4X2RlcHRoID0iLCBwYXJhbXMkbWF4X2RlcHRoLCAKICAgICAgIiwgbWluX2NoaWxkX3dlaWdodCA9IiwgcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsIAogICAgICAiLCBzdWJzYW1wbGUgPSIsIHBhcmFtcyRzdWJzYW1wbGUsIAogICAgICAiLCBjb2xzYW1wbGVfYnl0cmVlID0iLCBwYXJhbXMkY29sc2FtcGxlX2J5dHJlZSwKICAgICAgIiwgZ2FtbWEgPSIsIHBhcmFtcyRnYW1tYSwgIlxuIikKICAKICAjIFZhbGlkYWNpw7NuIGNydXphZGEgcGFyYSBlbmNvbnRyYXIgZWwgbsO6bWVybyDDs3B0aW1vIGRlIGl0ZXJhY2lvbmVzCiAgY3ZfbW9kZWwgPC0geGdiLmN2KAogICAgcGFyYW1zID0gcGFyYW1zLAogICAgZGF0YSA9IGR0cmFpbiwKICAgIG5yb3VuZHMgPSAyMDAsICAgICAgICAgICAgICAgICAgICAjIE3DoXhpbW8gbsO6bWVybyBkZSBpdGVyYWNpb25lcwogICAgbmZvbGQgPSA1LCAgICAgICAgICAgICAgICAgICAgICAgICMgNS1mb2xkIHZhbGlkYWNpw7NuIGNydXphZGEKICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDIwLCAgICAgICAjIERldGVuZXIgc2kgbm8gaGF5IG1lam9yYSBlbiAyMCByb25kYXMKICAgIHZlcmJvc2UgPSAwICAgICAgICAgICAgICAgICAgICAgICAjIFN1cHJpbWlyIG1lbnNhamVzCiAgKQogIAogICMgRXh0cmFlciBtZWpvciBpdGVyYWNpw7NuIHkgc3UgUk1TRQogIGJlc3RfaXRlcmF0aW9uIDwtIGN2X21vZGVsJGJlc3RfaXRlcmF0aW9uCiAgYmVzdF9ybXNlIDwtIG1pbihjdl9tb2RlbCRldmFsdWF0aW9uX2xvZyR0ZXN0X3Jtc2VfbWVhbikKICAKICBjYXQoIiAgTWVqb3IgaXRlcmFjacOzbjoiLCBiZXN0X2l0ZXJhdGlvbiwgIlxuIikKICBjYXQoIiAgUk1TRSBlbiB2YWxpZGFjacOzbiBjcnV6YWRhOiIsIGJlc3Rfcm1zZSwgIlxuXG4iKQogIAogICMgR3VhcmRhciByZXN1bHRhZG9zCiAgcmVzdWx0YWRvX2FjdHVhbCA8LSBkYXRhLmZyYW1lKAogICAgZXRhID0gcGFyYW1zJGV0YSwKICAgIG1heF9kZXB0aCA9IHBhcmFtcyRtYXhfZGVwdGgsCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gcGFyYW1zJG1pbl9jaGlsZF93ZWlnaHQsCiAgICBzdWJzYW1wbGUgPSBwYXJhbXMkc3Vic2FtcGxlLAogICAgY29sc2FtcGxlX2J5dHJlZSA9IHBhcmFtcyRjb2xzYW1wbGVfYnl0cmVlLAogICAgZ2FtbWEgPSBwYXJhbXMkZ2FtbWEsCiAgICBucm91bmRzID0gYmVzdF9pdGVyYXRpb24sCiAgICBybXNlX2N2ID0gYmVzdF9ybXNlCiAgKQogIAogIHJlc3VsdGFkb3MgPC0gcmJpbmQocmVzdWx0YWRvcywgcmVzdWx0YWRvX2FjdHVhbCkKfQoKIyBPcmRlbmFyIHJlc3VsdGFkb3MgcG9yIFJNU0UgKGRlIG1lbm9yIGEgbWF5b3IpCnJlc3VsdGFkb3MgPC0gcmVzdWx0YWRvc1tvcmRlcihyZXN1bHRhZG9zJHJtc2VfY3YpLCBdCgojIFBhc28gNTogTW9zdHJhciByZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaApjYXQoIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIG9yZGVuYWRvcyBwb3IgUk1TRTpcbiIpCnByaW50KHJlc3VsdGFkb3MpCgojIFZpc3VhbGl6YXIgcmVzdWx0YWRvcyBkZWwgR3JpZCBTZWFyY2gKZ2dwbG90KHJlc3VsdGFkb3MsIGFlcyh4ID0gcmVvcmRlcihwYXN0ZSgiQ29tYiIsIDE6bnJvdyhyZXN1bHRhZG9zKSksIHJtc2VfY3YpLCB5ID0gcm1zZV9jdikpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5IiwgZmlsbCA9ICJzdGVlbGJsdWUiKSArCiAgbGFicygKICAgIHRpdGxlID0gIlJlc3VsdGFkb3MgZGVsIEdyaWQgU2VhcmNoIC0gUHJvZHVjdG8gMTU1MDAyIiwKICAgIHggPSAiQ29tYmluYWNpw7NuIGRlIEhpcGVycGFyw6FtZXRyb3MiLAogICAgeSA9ICJSTVNFIGVuIFZhbGlkYWNpw7NuIENydXphZGEiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQoKIyBQYXNvIDY6IFNlbGVjY2lvbmFyIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MKbWVqb3Jlc19wYXJhbXMgPC0gbGlzdCgKICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgZXRhID0gcmVzdWx0YWRvcyRldGFbMV0sCiAgbWF4X2RlcHRoID0gcmVzdWx0YWRvcyRtYXhfZGVwdGhbMV0sCiAgbWluX2NoaWxkX3dlaWdodCA9IHJlc3VsdGFkb3MkbWluX2NoaWxkX3dlaWdodFsxXSwKICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zJHN1YnNhbXBsZVsxXSwKICBjb2xzYW1wbGVfYnl0cmVlID0gcmVzdWx0YWRvcyRjb2xzYW1wbGVfYnl0cmVlWzFdLAogIGdhbW1hID0gcmVzdWx0YWRvcyRnYW1tYVsxXQopCgptZWpvcl9ucm91bmRzIDwtIHJlc3VsdGFkb3MkbnJvdW5kc1sxXQoKY2F0KCJcbk1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcyBlbmNvbnRyYWRvczpcbiIpCnByaW50KG1lam9yZXNfcGFyYW1zKQpjYXQoIk7Dum1lcm8gw7NwdGltbyBkZSByb25kYXM6IiwgbWVqb3JfbnJvdW5kcywgIlxuXG4iKQoKIyBQYXNvIDc6IEVudHJlbmFyIGVsIG1vZGVsbyBmaW5hbCBjb24gbG9zIG1lam9yZXMgaGlwZXJwYXLDoW1ldHJvcwpjYXQoIkVudHJlbmFuZG8gbW9kZWxvIGZpbmFsIGNvbiBsb3MgbWVqb3JlcyBoaXBlcnBhcsOhbWV0cm9zLi4uXG4iKQoKbW9kZWxvX2ZpbmFsIDwtIHhnYi50cmFpbigKICBwYXJhbXMgPSBtZWpvcmVzX3BhcmFtcywKICBkYXRhID0gZHRyYWluLAogIG5yb3VuZHMgPSBtZWpvcl9ucm91bmRzLAogIHdhdGNobGlzdCA9IGxpc3QodHJhaW4gPSBkdHJhaW4sIHRlc3QgPSBkdGVzdCksCiAgdmVyYm9zZSA9IDAKKQoKbW9kZWxvX3hnYl8xNTUwMDIgPC0gbW9kZWxvX2ZpbmFsCgojIFBhc28gODogRXZhbHVhciBlbCBtb2RlbG8KIyBQcmVkaWNjaW9uZXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhCnByZWRpY2Npb25lc190ZXN0IDwtIHByZWRpY3QobW9kZWxvX2ZpbmFsLCBkdGVzdCkKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzCiMgTUFQRQptYXBlX3Rlc3QgPC0gbWVhbihhYnMoKHRlc3RfeSAtIHByZWRpY2Npb25lc190ZXN0KSAvIHBtYXgodGVzdF95LCAwLjAxKSkpICogMTAwCgojIE1TRQptc2VfdGVzdCA8LSBtZWFuKCh0ZXN0X3kgLSBwcmVkaWNjaW9uZXNfdGVzdCleMikKCiMgTW9zdHJhciBtw6l0cmljYXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhCmNhdCgiXG5Nw6l0cmljYXMgZW4gY29uanVudG8gZGUgcHJ1ZWJhOlxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX3Rlc3QsICJcbiIpCmNhdCgiTVNFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtc2VfdGVzdCwgIlxuXG4iKQoKIyBQYXNvIDk6IFByZWRpY2Npb25lcyBlbiBlbCBjb25qdW50byBjb21wbGV0bwojIEhhY2VyIHByZWRpY2Npb25lcyBlbiB0b2RvIGVsIGNvbmp1bnRvIGRlIGRhdG9zIHBhcmEgY29tcGFyYWNpw7NuIGNvbiBvdHJvcyBtb2RlbG9zCnhfY29tcGxldG8gPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb1ssIGNvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0pCnByZWRpY2Npb25lc19jb21wbGV0byA8LSBwcmVkaWN0KG1vZGVsb19maW5hbCwgeF9jb21wbGV0bykKCiMgQ2FsY3VsYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvCm1hcGVfY29tcGxldG8gPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0bykgLyAKICAgICAgICAgICAgICAgICAgICAgICAgcG1heChkYXRvc19tb2RlbG8kVmVudGEsIDAuMDEpKSkgKiAxMDAKCm1zZV9jb21wbGV0byA8LSBtZWFuKChkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8pXjIpCgojIE1vc3RyYXIgbcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvCmNhdCgiTcOpdHJpY2FzIGVuIGNvbmp1bnRvIGNvbXBsZXRvOlxuIikKY2F0KCJNQVBFIGRlbCBtb2RlbG8gWEdCb29zdDoiLCBtYXBlX2NvbXBsZXRvLCAiXG4iKQpjYXQoIk1TRSBkZWwgbW9kZWxvIFhHQm9vc3Q6IiwgbXNlX2NvbXBsZXRvLCAiXG5cbiIpCgojIFBhc28gMTA6IEFuw6FsaXNpcyBkZSBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKIyBDYWxjdWxhciBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKaW1wb3J0YW5jaWEgPC0geGdiLmltcG9ydGFuY2UoCiAgZmVhdHVyZV9uYW1lcyA9IGNvbG5hbWVzKGRhdG9zX21vZGVsbylbY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSwKICBtb2RlbCA9IG1vZGVsb19maW5hbAopCgojIE1vc3RyYXIgaW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzCmNhdCgiSW1wb3J0YW5jaWEgZGUgdmFyaWFibGVzOlxuIikKcHJpbnQoaW1wb3J0YW5jaWEpCgojIEdyYWZpY2FyIGltcG9ydGFuY2lhIGRlIHZhcmlhYmxlcwp4Z2IucGxvdC5pbXBvcnRhbmNlKGltcG9ydGFuY2VfbWF0cml4ID0gaW1wb3J0YW5jaWEsIAogICAgICAgICAgICAgICAgICAgbWFpbiA9ICJJbXBvcnRhbmNpYSBkZSBWYXJpYWJsZXMgLSBQcm9kdWN0byAxNTUwMDIgKFhHQm9vc3QpIikKCiMgUGFzbyAxMTogVmlzdWFsaXphY2lvbmVzIHBhcmEgZXZhbHVhY2nDs24KIyBHcsOhZmljbyAxOiBWYWxvcmVzIG9ic2VydmFkb3MgdnMgcHJlZGljaG9zCmRhdG9zX2dyYWZpY28gPC0gZGF0YS5mcmFtZSgKICBPYnNlcnZhZG8gPSBkYXRvc19tb2RlbG8kVmVudGEsCiAgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8KKQoKZ2dwbG90KGRhdG9zX2dyYWZpY28sIGFlcyh4ID0gT2JzZXJ2YWRvLCB5ID0gUHJlZGljaG8pKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJWYWxvcmVzIE9ic2VydmFkb3MgdnMgUHJlZGljY2lvbmVzIC0gUHJvZHVjdG8gMTU1MDAyIChYR0Jvb3N0KSIsCiAgICB4ID0gIlZlbnRhcyBPYnNlcnZhZGFzIiwKICAgIHkgPSAiVmVudGFzIFByZWRpY2hhcyIKICApICsKICB0aGVtZV9taW5pbWFsKCkKCiMgR3LDoWZpY28gMjogQW7DoWxpc2lzIGRlIHJlc2lkdW9zCmVycm9yZXMgPC0gZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvCgojIEhpc3RvZ3JhbWEgZGUgZXJyb3JlcwpoaXN0KGVycm9yZXMsIAogICAgIG1haW4gPSAiRGlzdHJpYnVjacOzbiBkZSBFcnJvcmVzIC0gUHJvZHVjdG8gMTU1MDAyIChYR0Jvb3N0KSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsCiAgICAgYnJlYWtzID0gMzApCgojIEdyw6FmaWNvIDM6IEVycm9yZXMgdnMgUHJlZGljY2lvbmVzCmdncGxvdChkYXRhLmZyYW1lKFByZWRpY2hvID0gcHJlZGljY2lvbmVzX2NvbXBsZXRvLCBFcnJvciA9IGVycm9yZXMpLCBhZXMoeCA9IFByZWRpY2hvLCB5ID0gRXJyb3IpKSArCiAgZ2VvbV9wb2ludChhbHBoYSA9IDAuNSkgKwogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIsIGxpbmV0eXBlID0gImRhc2hlZCIpICsKICBsYWJzKAogICAgdGl0bGUgPSAiRXJyb3IgdnMgUHJlZGljY2nDs24gLSBQcm9kdWN0byAxNTUwMDIgKFhHQm9vc3QpIiwKICAgIHggPSAiVmVudGFzIFByZWRpY2hhcyIsCiAgICB5ID0gIkVycm9yIChPYnNlcnZhZG8gLSBQcmVkaWNobykiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpCmBgYApgYGB7cn0KIyBHdWFyZGFyIG3DqXRyaWNhcyBkZSBYR0Jvb3N0IHBhcmEgcHJvZHVjdG8gMTU1MDAxCmlmKCFleGlzdHMoIm1ldHJpY2FzX2NvbXBhcmF0aXZhcyIpKSB7CiAgbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIGRhdGEuZnJhbWUoCiAgICBQcm9kdWN0byA9IGNoYXJhY3RlcigpLAogICAgTW9kZWxvID0gY2hhcmFjdGVyKCksCiAgICBNQVBFID0gbnVtZXJpYygpLAogICAgTVNFID0gbnVtZXJpYygpLAogICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCiAgKQp9CgptZXRyaWNhc19jb21wYXJhdGl2YXMgPC0gcmJpbmQobWV0cmljYXNfY29tcGFyYXRpdmFzLCBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gIjE1NTAwMiIsICAjIENhbWJpYSBlc3RlIElEIHBhcmEgY2FkYSBwcm9kdWN0bwogIE1vZGVsbyA9ICJYR0Jvb3N0IiwKICBNQVBFID0gbWFwZV9jb21wbGV0bywKICBNU0UgPSBtc2VfY29tcGxldG8KKSkKYGBgCgojIyBQUk9EVUNUTyAzNjc4MDU1CgpgYGB7cn0KIyBQcmVwYXJhciBkYXRvcyBwYXJhIGVsIG1vZGVsbyAoZWxpbWluYXIgY29sdW1uYXMgbm8gbmVjZXNhcmlhcykKZGF0b3NfbW9kZWxvIDwtIGRhdG9zXzM2NzgwNTUgJT4lCiAgc2VsZWN0KC1UcnhfRmVjaGEsIC1GZWNoYSkKCiMgUGFzbyAyOiBEaXZpZGlyIGxvcyBkYXRvcyBlbiBjb25qdW50b3MgZGUgZW50cmVuYW1pZW50byAoODAlKSB5IHBydWViYSAoMjAlKQpzZXQuc2VlZCgxMjMpICAjIFBhcmEgcmVwcm9kdWNpYmlsaWRhZAp0cmFpbl9pbmRleCA8LSBjcmVhdGVEYXRhUGFydGl0aW9uKGRhdG9zX21vZGVsbyRWZW50YSwgcCA9IDAuOCwgbGlzdCA9IEZBTFNFKQp0cmFpbl9kYXRhIDwtIGRhdG9zX21vZGVsb1t0cmFpbl9pbmRleCwgXQp0ZXN0X2RhdGEgPC0gZGF0b3NfbW9kZWxvWy10cmFpbl9pbmRleCwgXQoKIyBQcmVwYXJhciBtYXRyaWNlcyBwYXJhIFhHQm9vc3QKdHJhaW5feCA8LSBhcy5tYXRyaXgodHJhaW5fZGF0YVssIGNvbG5hbWVzKHRyYWluX2RhdGEpICE9ICJWZW50YSJdKQp0cmFpbl95IDwtIHRyYWluX2RhdGEkVmVudGEKCnRlc3RfeCA8LSBhcy5tYXRyaXgodGVzdF9kYXRhWywgY29sbmFtZXModGVzdF9kYXRhKSAhPSAiVmVudGEiXSkKdGVzdF95IDwtIHRlc3RfZGF0YSRWZW50YQoKIyBDcmVhciBETWF0cml4IHBhcmEgWEdCb29zdApkdHJhaW4gPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRyYWluX3gsIGxhYmVsID0gdHJhaW5feSkKZHRlc3QgPC0geGdiLkRNYXRyaXgoZGF0YSA9IHRlc3RfeCwgbGFiZWwgPSB0ZXN0X3kpCgojIFBhc28gMzogRGVmaW5pciBsYSByZWppbGxhIGRlIGhpcGVycGFyw6FtZXRyb3MKcGFyYW1fZ3JpZCA8LSBleHBhbmQuZ3JpZCgKICBldGEgPSBjKDAuMDEsIDAuMDUsIDAuMSwgMC4zKSwKICBtYXhfZGVwdGggPSBjKDMsIDYsIDkpLAogIG1pbl9jaGlsZF93ZWlnaHQgPSBjKDEsIDMsIDUpLAogIHN1YnNhbXBsZSA9IGMoMC43LCAwLjkpLAogIGNvbHNhbXBsZV9ieXRyZWUgPSBjKDAuNywgMC45KSwKICBnYW1tYSA9IGMoMCwgMC4xLCAwLjMpCikKCmNhdCgiR3JpZCBTZWFyY2ggcGFyYSBYR0Jvb3N0IC0gUHJvZHVjdG8gMzY3ODA1NVxuIikKY2F0KCJOw7ptZXJvIHRvdGFsIGRlIGNvbWJpbmFjaW9uZXMgZGUgaGlwZXJwYXLDoW1ldHJvczoiLCBucm93KHBhcmFtX2dyaWQpLCAiXG5cbiIpCgojIFNlbGVjY2nDs24gYWxlYXRvcmlhIGRlIDEyIGNvbWJpbmFjaW9uZXMgKHNpIHNlIGRlc2VhIGxpbWl0YXIpCnNldC5zZWVkKDQ1NikKaWYgKG5yb3cocGFyYW1fZ3JpZCkgPiAxMikgewogIHBhcmFtX2dyaWQgPC0gcGFyYW1fZ3JpZFtzYW1wbGUoMTpucm93KHBhcmFtX2dyaWQpLCAxMiksIF0KICBjYXQoIlNlbGVjY2lvbmFuZG8gMTIgY29tYmluYWNpb25lcyBhbGVhdG9yaWFzIHBhcmEgZXZhbHVhY2nDs24uXG5cbiIpCn0KCiMgUGFzbyA0OiBJbXBsZW1lbnRhciBHcmlkIFNlYXJjaApyZXN1bHRhZG9zIDwtIGRhdGEuZnJhbWUoKQpjYXQoIkluaWNpYW5kbyBHcmlkIFNlYXJjaC4uLlxuIikKCmZvciAoaSBpbiAxOm5yb3cocGFyYW1fZ3JpZCkpIHsKICBwYXJhbXMgPC0gbGlzdCgKICAgIG9iamVjdGl2ZSA9ICJyZWc6c3F1YXJlZGVycm9yIiwKICAgIGV2YWxfbWV0cmljID0gInJtc2UiLAogICAgZXRhID0gcGFyYW1fZ3JpZCRldGFbaV0sCiAgICBtYXhfZGVwdGggPSBwYXJhbV9ncmlkJG1heF9kZXB0aFtpXSwKICAgIG1pbl9jaGlsZF93ZWlnaHQgPSBwYXJhbV9ncmlkJG1pbl9jaGlsZF93ZWlnaHRbaV0sCiAgICBzdWJzYW1wbGUgPSBwYXJhbV9ncmlkJHN1YnNhbXBsZVtpXSwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbV9ncmlkJGNvbHNhbXBsZV9ieXRyZWVbaV0sCiAgICBnYW1tYSA9IHBhcmFtX2dyaWQkZ2FtbWFbaV0KICApCiAgCiAgY2F0KCJFdmFsdWFuZG8gY29tYmluYWNpw7NuIiwgaSwgImRlIiwgbnJvdyhwYXJhbV9ncmlkKSwgIi4uLlxuIikKICAKICBjdl9tb2RlbCA8LSB4Z2IuY3YoCiAgICBwYXJhbXMgPSBwYXJhbXMsCiAgICBkYXRhID0gZHRyYWluLAogICAgbnJvdW5kcyA9IDIwMCwKICAgIG5mb2xkID0gNSwKICAgIGVhcmx5X3N0b3BwaW5nX3JvdW5kcyA9IDIwLAogICAgdmVyYm9zZSA9IDAKICApCiAgCiAgYmVzdF9pdGVyYXRpb24gPC0gY3ZfbW9kZWwkYmVzdF9pdGVyYXRpb24KICBiZXN0X3Jtc2UgPC0gbWluKGN2X21vZGVsJGV2YWx1YXRpb25fbG9nJHRlc3Rfcm1zZV9tZWFuKQogIAogIHJlc3VsdGFkb19hY3R1YWwgPC0gZGF0YS5mcmFtZSgKICAgIGV0YSA9IHBhcmFtcyRldGEsCiAgICBtYXhfZGVwdGggPSBwYXJhbXMkbWF4X2RlcHRoLAogICAgbWluX2NoaWxkX3dlaWdodCA9IHBhcmFtcyRtaW5fY2hpbGRfd2VpZ2h0LAogICAgc3Vic2FtcGxlID0gcGFyYW1zJHN1YnNhbXBsZSwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSBwYXJhbXMkY29sc2FtcGxlX2J5dHJlZSwKICAgIGdhbW1hID0gcGFyYW1zJGdhbW1hLAogICAgbnJvdW5kcyA9IGJlc3RfaXRlcmF0aW9uLAogICAgcm1zZV9jdiA9IGJlc3Rfcm1zZQogICkKICAKICByZXN1bHRhZG9zIDwtIHJiaW5kKHJlc3VsdGFkb3MsIHJlc3VsdGFkb19hY3R1YWwpCn0KCnJlc3VsdGFkb3MgPC0gcmVzdWx0YWRvc1tvcmRlcihyZXN1bHRhZG9zJHJtc2VfY3YpLCBdCgojIFBhc28gNTogTW9zdHJhciByZXN1bHRhZG9zCnByaW50KHJlc3VsdGFkb3MpCgojIFZpc3VhbGl6YWNpw7NuCmdncGxvdChyZXN1bHRhZG9zLCBhZXMoeCA9IHJlb3JkZXIocGFzdGUoIkNvbWIiLCAxOm5yb3cocmVzdWx0YWRvcykpLCBybXNlX2N2KSwgeSA9IHJtc2VfY3YpKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic3RlZWxibHVlIikgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJSZXN1bHRhZG9zIGRlbCBHcmlkIFNlYXJjaCAtIFByb2R1Y3RvIDM2NzgwNTUiLAogICAgeCA9ICJDb21iaW5hY2nDs24gZGUgSGlwZXJwYXLDoW1ldHJvcyIsCiAgICB5ID0gIlJNU0UiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpKQoKIyBQYXNvIDY6IFNlbGVjY2lvbmFyIGxvcyBtZWpvcmVzIGhpcGVycGFyw6FtZXRyb3MKbWVqb3Jlc19wYXJhbXMgPC0gbGlzdCgKICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgZXZhbF9tZXRyaWMgPSAicm1zZSIsCiAgZXRhID0gcmVzdWx0YWRvcyRldGFbMV0sCiAgbWF4X2RlcHRoID0gcmVzdWx0YWRvcyRtYXhfZGVwdGhbMV0sCiAgbWluX2NoaWxkX3dlaWdodCA9IHJlc3VsdGFkb3MkbWluX2NoaWxkX3dlaWdodFsxXSwKICBzdWJzYW1wbGUgPSByZXN1bHRhZG9zJHN1YnNhbXBsZVsxXSwKICBjb2xzYW1wbGVfYnl0cmVlID0gcmVzdWx0YWRvcyRjb2xzYW1wbGVfYnl0cmVlWzFdLAogIGdhbW1hID0gcmVzdWx0YWRvcyRnYW1tYVsxXQopCgptZWpvcl9ucm91bmRzIDwtIHJlc3VsdGFkb3MkbnJvdW5kc1sxXQoKIyBQYXNvIDc6IEVudHJlbmFyIG1vZGVsbyBmaW5hbAptb2RlbG9fZmluYWwgPC0geGdiLnRyYWluKAogIHBhcmFtcyA9IG1lam9yZXNfcGFyYW1zLAogIGRhdGEgPSBkdHJhaW4sCiAgbnJvdW5kcyA9IG1lam9yX25yb3VuZHMsCiAgd2F0Y2hsaXN0ID0gbGlzdCh0cmFpbiA9IGR0cmFpbiwgdGVzdCA9IGR0ZXN0KSwKICB2ZXJib3NlID0gMAopCgptb2RlbG9feGdiXzM2NzgwNTUgPC0gbW9kZWxvX2ZpbmFsCgoKIyBQYXNvIDg6IEV2YWx1YXIgbW9kZWxvCnByZWRpY2Npb25lc190ZXN0IDwtIHByZWRpY3QobW9kZWxvX2ZpbmFsLCBkdGVzdCkKbWFwZV90ZXN0IDwtIG1lYW4oYWJzKCh0ZXN0X3kgLSBwcmVkaWNjaW9uZXNfdGVzdCkgLyBwbWF4KHRlc3RfeSwgMC4wMSkpKSAqIDEwMAptc2VfdGVzdCA8LSBtZWFuKCh0ZXN0X3kgLSBwcmVkaWNjaW9uZXNfdGVzdCleMikKCiMgUGFzbyA5OiBQcmVkaWNjacOzbiBlbiB0b2RvIGVsIGNvbmp1bnRvCnhfY29tcGxldG8gPC0gYXMubWF0cml4KGRhdG9zX21vZGVsb1ssIGNvbG5hbWVzKGRhdG9zX21vZGVsbykgIT0gIlZlbnRhIl0pCnByZWRpY2Npb25lc19jb21wbGV0byA8LSBwcmVkaWN0KG1vZGVsb19maW5hbCwgeF9jb21wbGV0bykKCm1hcGVfY29tcGxldG8gPC0gbWVhbihhYnMoKGRhdG9zX21vZGVsbyRWZW50YSAtIHByZWRpY2Npb25lc19jb21wbGV0bykgLyBwbWF4KGRhdG9zX21vZGVsbyRWZW50YSwgMC4wMSkpKSAqIDEwMAptc2VfY29tcGxldG8gPC0gbWVhbigoZGF0b3NfbW9kZWxvJFZlbnRhIC0gcHJlZGljY2lvbmVzX2NvbXBsZXRvKV4yKQoKIyBQYXNvIDEwOiBJbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMKaW1wb3J0YW5jaWEgPC0geGdiLmltcG9ydGFuY2UoCiAgZmVhdHVyZV9uYW1lcyA9IGNvbG5hbWVzKGRhdG9zX21vZGVsbylbY29sbmFtZXMoZGF0b3NfbW9kZWxvKSAhPSAiVmVudGEiXSwKICBtb2RlbCA9IG1vZGVsb19maW5hbAopCnhnYi5wbG90LmltcG9ydGFuY2UoaW1wb3J0YW5jaWEsIAogICAgICAgICAgICAgICAgICAgIG1haW4gPSAiSW1wb3J0YW5jaWEgZGUgVmFyaWFibGVzIC0gUHJvZHVjdG8gMzY3ODA1NSAoWEdCb29zdCkiKQoKIyBQYXNvIDExOiBHcsOhZmljb3MgZGUgZXZhbHVhY2nDs24KZGF0b3NfZ3JhZmljbyA8LSBkYXRhLmZyYW1lKE9ic2VydmFkbyA9IGRhdG9zX21vZGVsbyRWZW50YSwgUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8pCgpnZ3Bsb3QoZGF0b3NfZ3JhZmljbywgYWVzKHggPSBPYnNlcnZhZG8sIHkgPSBQcmVkaWNobykpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAwLCBsaW5ldHlwZSA9ICJkYXNoZWQiLCBjb2xvciA9ICJyZWQiKSArCiAgbGFicyh0aXRsZSA9ICJPYnNlcnZhZG8gdnMgUHJlZGljaG8gLSBQcm9kdWN0byAzNjc4MDU1IiwgeCA9ICJWZW50YSBPYnNlcnZhZGEiLCB5ID0gIlZlbnRhIFByZWRpY2hhIikgKwogIHRoZW1lX21pbmltYWwoKQoKZXJyb3JlcyA8LSBkYXRvc19tb2RlbG8kVmVudGEgLSBwcmVkaWNjaW9uZXNfY29tcGxldG8KCmhpc3QoZXJyb3JlcywgCiAgICAgbWFpbiA9ICJEaXN0cmlidWNpw7NuIGRlIEVycm9yZXMgLSBQcm9kdWN0byAzNjc4MDU1IChYR0Jvb3N0KSIsCiAgICAgeGxhYiA9ICJFcnJvciAoT2JzZXJ2YWRvIC0gUHJlZGljaG8pIiwKICAgICBjb2wgPSAic2t5Ymx1ZSIsIGJyZWFrcyA9IDMwKQoKZ2dwbG90KGRhdGEuZnJhbWUoUHJlZGljaG8gPSBwcmVkaWNjaW9uZXNfY29tcGxldG8sIEVycm9yID0gZXJyb3JlcyksIGFlcyh4ID0gUHJlZGljaG8sIHkgPSBFcnJvcikpICsKICBnZW9tX3BvaW50KGFscGhhID0gMC41KSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIiwgbGluZXR5cGUgPSAiZGFzaGVkIikgKwogIGxhYnModGl0bGUgPSAiRXJyb3JlcyB2cyBQcmVkaWNjacOzbiAtIFByb2R1Y3RvIDM2NzgwNTUiLCB4ID0gIlZlbnRhIFByZWRpY2hhIiwgeSA9ICJFcnJvciIpICsKICB0aGVtZV9taW5pbWFsKCkKCmBgYAoKYGBge3J9CiMgR3VhcmRhciBtw6l0cmljYXMgZGUgWEdCb29zdCBwYXJhIHByb2R1Y3RvIDE1NTAwMQppZighZXhpc3RzKCJtZXRyaWNhc19jb21wYXJhdGl2YXMiKSkgewogIG1ldHJpY2FzX2NvbXBhcmF0aXZhcyA8LSBkYXRhLmZyYW1lKAogICAgUHJvZHVjdG8gPSBjaGFyYWN0ZXIoKSwKICAgIE1vZGVsbyA9IGNoYXJhY3RlcigpLAogICAgTUFQRSA9IG51bWVyaWMoKSwKICAgIE1TRSA9IG51bWVyaWMoKSwKICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQogICkKfQoKbWV0cmljYXNfY29tcGFyYXRpdmFzIDwtIHJiaW5kKG1ldHJpY2FzX2NvbXBhcmF0aXZhcywgZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9ICIzNjc4MDU1IiwgICMgQ2FtYmlhIGVzdGUgSUQgcGFyYSBjYWRhIHByb2R1Y3RvCiAgTW9kZWxvID0gIlhHQm9vc3QiLAogIE1BUEUgPSBtYXBlX2NvbXBsZXRvLAogIE1TRSA9IG1zZV9jb21wbGV0bwopKQpgYGAKCiMgVmlzdWFsaXphY2nDs24gZGUgTcOpdHJpY2FzCgpgYGB7cn0KIyBEZWZpbmlyIGxvcyBjb2xvcmVzIHBhcmEgY2FkYSBtb2RlbG8KY29sb3Jlc19tb2RlbG9zIDwtIGMoCiAgIkFSTUEvU0FSSU1BIiA9ICIjMWY3N2I0IiwgICAgIyBBenVsCiAgIlJlZ3Jlc2nDs24gTGluZWFsIiA9ICIjZmY3ZjBlIiwgIyBOYXJhbmphCiAgIlJhbmRvbSBGb3Jlc3QiID0gIiMyY2EwMmMiLCAgICMgVmVyZGUKICAiWEdCb29zdCIgPSAiI2Q2MjcyOCIgICAgICAgICAjIFJvam8KKQpgYGAKCiMjIFBST0RVQ1RPIDE1NTAwMQpgYGB7cn0KIyBQcmltZXJvLCB2ZWFtb3MgcXXDqSBkYXRvcyB0ZW5lbW9zIHJlYWxtZW50ZQpwcmludCgiRGF0b3MgYWN0dWFsZXMgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDE6IikKcHJpbnQobWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjE1NTAwMSIpKQoKIyBDcmVhciB1biBkYXRhZnJhbWUgbWFudWFsbWVudGUgY29uIGxvcyA0IG1vZGVsb3MgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDEKIyAoY29uIHZhbG9yZXMgZGUgZWplbXBsbyBzaSBlcyBuZWNlc2FyaW8pCmRhdG9zXzE1NTAwMV9jb21wbGV0byA8LSBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gcmVwKCIxNTUwMDEiLCA0KSwKICBNb2RlbG8gPSBjKCJBUk1BL1NBUklNQSIsICJSZWdyZXNpw7NuIExpbmVhbCIsICJSYW5kb20gRm9yZXN0IiwgIlhHQm9vc3QiKSwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UKKQoKIyBVbmlyIGNvbiBsb3MgZGF0b3MgZXhpc3RlbnRlcwpkYXRvc18xNTUwMDFfY29tcGxldG8gPC0gbGVmdF9qb2luKAogIGRhdG9zXzE1NTAwMV9jb21wbGV0bywKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMTU1MDAxIiksCiAgYnkgPSBjKCJQcm9kdWN0byIsICJNb2RlbG8iKQopCgojIEFob3JhIGFzaWduYSB2YWxvcmVzIHBhcmEgbGFzIG3DqXRyaWNhcyBkZSBsb3MgbW9kZWxvcyBmYWx0YW50ZXMKIyBTaSB0aWVuZXMgbG9zIHZhbG9yZXMsIHJlZW1wbGF6YSBsb3MgMCBjb24gbG9zIHZhbG9yZXMgY29ycmVjdG9zCiMgTyB0b21hIG5vdGEgZGUgY3XDoWxlcyBzb24gTkEgcGFyYSByZWVtcGxhemFybG9zIGNvbiBsb3MgdmFsb3JlcyByZWFsZXMKCiMgVmFsb3JlcyBwYXJhIFJlZ3Jlc2nDs24gTGluZWFsIChyZWVtcGxhemEgZXN0b3MgY29uIGxvcyB2YWxvcmVzIHJlYWxlcykKaWYgKGlzLm5hKGRhdG9zXzE1NTAwMV9jb21wbGV0byRNQVBFWzJdKSkgewogIGRhdG9zXzE1NTAwMV9jb21wbGV0byRNQVBFWzJdIDwtIG1hcGVfMTU1MDAxICAjIE8gZWwgdmFsb3IgY29ycmVjdG8KfQppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1TRVsyXSkpIHsKICBkYXRvc18xNTUwMDFfY29tcGxldG8kTVNFWzJdIDwtIG1zZV8xNTUwMDEgICMgTyBlbCB2YWxvciBjb3JyZWN0bwp9CgojIFZhbG9yZXMgcGFyYSBSYW5kb20gRm9yZXN0IChyZWVtcGxhemEgZXN0b3MgY29uIGxvcyB2YWxvcmVzIHJlYWxlcykKIyBTaSB5YSBlamVjdXRhc3RlIGxhIHNlY2Npw7NuIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDEsCiMgdXNhIGxhcyB2YXJpYWJsZXMgcjJfcmYsIHJtc2VfcmYsIGV0Yy4KaWYgKGlzLm5hKGRhdG9zXzE1NTAwMV9jb21wbGV0byRNQVBFWzNdKSAmJiBleGlzdHMoIm1hcGVfcmYiKSkgewogIGRhdG9zXzE1NTAwMV9jb21wbGV0byRNQVBFWzNdIDwtIG1hcGVfcmYKfQppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1TRVszXSkgJiYgZXhpc3RzKCJtc2VfcmYiKSkgewogIGRhdG9zXzE1NTAwMV9jb21wbGV0byRNU0VbM10gPC0gbXNlX3JmCn0KCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QgKHJlZW1wbGF6YSBlc3RvcyBjb24gbG9zIHZhbG9yZXMgcmVhbGVzKQojIFNpIHlhIGVqZWN1dGFzdGUgbGEgc2VjY2nDs24gZGUgWEdCb29zdCBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMSwKIyB1c2EgbGFzIHZhcmlhYmxlcyByMl9jb21wbGV0bywgcm1zZV9jb21wbGV0bywgZXRjLgppZiAoaXMubmEoZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbNF0pICYmIGV4aXN0cygibWFwZV9jb21wbGV0byIpKSB7CiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1BUEVbNF0gPC0gbWFwZV9jb21wbGV0bwp9CmlmIChpcy5uYShkYXRvc18xNTUwMDFfY29tcGxldG8kTVNFWzRdKSAmJiBleGlzdHMoIm1zZV9jb21wbGV0byIpKSB7CiAgZGF0b3NfMTU1MDAxX2NvbXBsZXRvJE1TRVs0XSA8LSBtc2VfY29tcGxldG8KfQoKIyBWZXIgbG9zIGRhdG9zIGNvbXBsZXRvcwpwcmludCgiRGF0b3MgY29tcGxldG9zIHBhcmEgZWwgcHJvZHVjdG8gMTU1MDAxOiIpCnByaW50KGRhdG9zXzE1NTAwMV9jb21wbGV0bykKCiMgR3LDoWZpY28gcGFyYSBNQVBFCmdncGxvdChkYXRvc18xNTUwMDFfY29tcGxldG8sIGFlcyh4ID0gTW9kZWxvLCB5ID0gTUFQRSwgZmlsbCA9IE1vZGVsbykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoTUFQRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAxNTUwMDEiLAogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IE1BUEUgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwKICAgIHggPSAiIiwKICAgIHkgPSAiTUFQRSAoJSkiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApIAoKIyBHcsOhZmljbyBwYXJhIE1TRQpnZ3Bsb3QoZGF0b3NfMTU1MDAxX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IE1TRSwgZmlsbCA9IE1vZGVsbykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoTVNFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDE1NTAwMSIsCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogTVNFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsCiAgICB4ID0gIiIsCiAgICB5ID0gIk1TRSIKICApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQogICkgKwogIHlsaW0oMCwgbWF4KGRhdG9zXzE1NTAwMV9jb21wbGV0byRNU0UsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCmBgYAoKCiMjIFBST0RVQ1RPIDM5Mjk3ODgKYGBge3J9CiMgUHJpbWVybywgdmVhbW9zIHF1w6kgZGF0b3MgdGVuZW1vcyByZWFsbWVudGUKcHJpbnQoIkRhdG9zIGFjdHVhbGVzIHBhcmEgZWwgcHJvZHVjdG8gMzkyOTc4ODoiKQpwcmludChtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMzkyOTc4OCIpKQoKIyBDcmVhciB1biBkYXRhZnJhbWUgbWFudWFsbWVudGUgY29uIGxvcyA0IG1vZGVsb3MgcGFyYSBlbCBwcm9kdWN0byAzOTI5Nzg4CmRhdG9zXzM5Mjk3ODhfY29tcGxldG8gPC0gZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9IHJlcCgiMzkyOTc4OCIsIDQpLAogIE1vZGVsbyA9IGMoIkFSTUEvU0FSSU1BIiwgIlJlZ3Jlc2nDs24gTGluZWFsIiwgIlJhbmRvbSBGb3Jlc3QiLCAiWEdCb29zdCIpLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopCgojIFVuaXIgY29uIGxvcyBkYXRvcyBleGlzdGVudGVzCmRhdG9zXzM5Mjk3ODhfY29tcGxldG8gPC0gbGVmdF9qb2luKAogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8sCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjM5Mjk3ODgiKSwKICBieSA9IGMoIlByb2R1Y3RvIiwgIk1vZGVsbyIpCikKCiMgQWhvcmEgYXNpZ25hIHZhbG9yZXMgcGFyYSBsYXMgbcOpdHJpY2FzIGRlIGxvcyBtb2RlbG9zIGZhbHRhbnRlcwojIFZhbG9yZXMgcGFyYSBSZWdyZXNpw7NuIExpbmVhbAppZiAoaXMubmEoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNQVBFWzJdKSkgewogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRVsyXSA8LSBtYXBlXzM5Mjk3ODgKfQppZiAoaXMubmEoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNU0VbMl0pKSB7CiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNU0VbMl0gPC0gbXNlXzM5Mjk3ODgKfQojIFZhbG9yZXMgcGFyYSBSYW5kb20gRm9yZXN0CiMgU2kgeWEgZWplY3V0YXN0ZSBsYSBzZWNjacOzbiBkZSBSYW5kb20gRm9yZXN0IHBhcmEgZWwgcHJvZHVjdG8gMzkyOTc4OAppZiAoaXMubmEoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNQVBFWzNdKSAmJiBleGlzdHMoIm1hcGVfcmYiKSkgewogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRVszXSA8LSBtYXBlX3JmCn0KaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTVNFWzNdKSAmJiBleGlzdHMoIm1zZV9yZiIpKSB7CiAgZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNU0VbM10gPC0gbXNlX3JmCn0KCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QKaWYgKGlzLm5hKGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTUFQRVs0XSkgJiYgZXhpc3RzKCJtYXBlX2NvbXBsZXRvIikpIHsKICBkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1BUEVbNF0gPC0gbWFwZV9jb21wbGV0bwp9CmlmIChpcy5uYShkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1TRVs0XSkgJiYgZXhpc3RzKCJtc2VfY29tcGxldG8iKSkgewogIGRhdG9zXzM5Mjk3ODhfY29tcGxldG8kTVNFWzRdIDwtIG1zZV9jb21wbGV0bwp9CgojIFZlciBsb3MgZGF0b3MgY29tcGxldG9zCnByaW50KCJEYXRvcyBjb21wbGV0b3MgcGFyYSBlbCBwcm9kdWN0byAzOTI5Nzg4OiIpCnByaW50KGRhdG9zXzM5Mjk3ODhfY29tcGxldG8pCgojIERlZmluaXIgY29sb3JlcyBwYXJhIGxvcyBtb2RlbG9zCmNvbG9yZXNfbW9kZWxvcyA8LSBjKCJBUk1BL1NBUklNQSIgPSAiIzFmNzdiNCIsIAogICAgICAgICAgICAgICAgICAgICAiUmVncmVzacOzbiBMaW5lYWwiID0gIiNmZjdmMGUiLCAKICAgICAgICAgICAgICAgICAgICAgIlJhbmRvbSBGb3Jlc3QiID0gIiMyY2EwMmMiLCAKICAgICAgICAgICAgICAgICAgICAgIlhHQm9vc3QiID0gIiNkNjI3MjgiKQoKIyBHcsOhZmljbyBwYXJhIE1TRQpnZ3Bsb3QoZGF0b3NfMzkyOTc4OF9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNU0UsIGZpbGwgPSBNb2RlbG8pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1TRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAzOTI5Nzg4IiwKICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBNU0UgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwKICAgIHggPSAiIiwKICAgIHkgPSAiTVNFIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgKSArCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzkyOTc4OF9jb21wbGV0byRNU0UsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCgojIEdyw6FmaWNvIHBhcmEgTUFQRQpnZ3Bsb3QoZGF0b3NfMzkyOTc4OF9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNQVBFLCBmaWxsID0gTW9kZWxvKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM5Mjk3ODgiLAogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IE1BUEUgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwKICAgIHggPSAiIiwKICAgIHkgPSAiTUFQRSAoJSkiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApICsKICB5bGltKDAsIG1heChkYXRvc18zOTI5Nzg4X2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCgpgYGAKCiMjIFBST0RVQ1RPIDM5MDQxNTIKYGBge3J9CiMgUHJpbWVybywgdmVhbW9zIHF1w6kgZGF0b3MgdGVuZW1vcyByZWFsbWVudGUKcHJpbnQoIkRhdG9zIGFjdHVhbGVzIHBhcmEgZWwgcHJvZHVjdG8gMzkwNDE1MjoiKQpwcmludChtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMzkwNDE1MiIpKQoKIyBDcmVhciB1biBkYXRhZnJhbWUgbWFudWFsbWVudGUgY29uIGxvcyA0IG1vZGVsb3MgcGFyYSBlbCBwcm9kdWN0byAzOTA0MTUyCmRhdG9zXzM5MDQxNTJfY29tcGxldG8gPC0gZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9IHJlcCgiMzkwNDE1MiIsIDQpLAogIE1vZGVsbyA9IGMoIkFSTUEvU0FSSU1BIiwgIlJlZ3Jlc2nDs24gTGluZWFsIiwgIlJhbmRvbSBGb3Jlc3QiLCAiWEdCb29zdCIpLAogIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRQopCgojIFVuaXIgY29uIGxvcyBkYXRvcyBleGlzdGVudGVzCmRhdG9zXzM5MDQxNTJfY29tcGxldG8gPC0gbGVmdF9qb2luKAogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8sCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjM5MDQxNTIiKSwKICBieSA9IGMoIlByb2R1Y3RvIiwgIk1vZGVsbyIpCikKCiMgQWhvcmEgYXNpZ25hIHZhbG9yZXMgcGFyYSBsYXMgbcOpdHJpY2FzIGRlIGxvcyBtb2RlbG9zIGZhbHRhbnRlcwojIFZhbG9yZXMgcGFyYSBSZWdyZXNpw7NuIExpbmVhbAppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNQVBFWzJdKSkgewogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTUFQRVsyXSA8LSBtYXBlXzM5MDQxNTIKfQppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNU0VbMl0pKSB7CiAgZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNU0VbMl0gPC0gbXNlXzM5MDQxNTIKfQoKIyBWYWxvcmVzIHBhcmEgUmFuZG9tIEZvcmVzdAppZiAoaXMubmEoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNQVBFWzNdKSAmJiBleGlzdHMoIm1hcGVfcmYiKSkgewogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTUFQRVszXSA8LSBtYXBlX3JmCn0KaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTVNFWzNdKSAmJiBleGlzdHMoIm1zZV9yZiIpKSB7CiAgZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNU0VbM10gPC0gbXNlX3JmCn0KCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QKaWYgKGlzLm5hKGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTUFQRVs0XSkgJiYgZXhpc3RzKCJtYXBlX2NvbXBsZXRvIikpIHsKICBkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1BUEVbNF0gPC0gbWFwZV9jb21wbGV0bwp9CmlmIChpcy5uYShkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1TRVs0XSkgJiYgZXhpc3RzKCJtc2VfY29tcGxldG8iKSkgewogIGRhdG9zXzM5MDQxNTJfY29tcGxldG8kTVNFWzRdIDwtIG1zZV9jb21wbGV0bwp9CgojIFZlciBsb3MgZGF0b3MgY29tcGxldG9zCnByaW50KCJEYXRvcyBjb21wbGV0b3MgcGFyYSBlbCBwcm9kdWN0byAzOTA0MTUyOiIpCnByaW50KGRhdG9zXzM5MDQxNTJfY29tcGxldG8pCgojIERlZmluaXIgY29sb3JlcyBwYXJhIGxvcyBtb2RlbG9zCmNvbG9yZXNfbW9kZWxvcyA8LSBjKCJBUk1BL1NBUklNQSIgPSAiIzFmNzdiNCIsIAogICAgICAgICAgICAgICAgICAgICAiUmVncmVzacOzbiBMaW5lYWwiID0gIiNmZjdmMGUiLCAKICAgICAgICAgICAgICAgICAgICAgIlJhbmRvbSBGb3Jlc3QiID0gIiMyY2EwMmMiLCAKICAgICAgICAgICAgICAgICAgICAgIlhHQm9vc3QiID0gIiNkNjI3MjgiKQoKIyBHcsOhZmljbyBwYXJhIE1TRQpnZ3Bsb3QoZGF0b3NfMzkwNDE1Ml9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNU0UsIGZpbGwgPSBNb2RlbG8pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1TRSwgMSkpLCB2anVzdCA9IC0wLjUsIHNpemUgPSAzLjUpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcmVzX21vZGVsb3MpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MgcGFyYSBQcm9kdWN0byAzOTA0MTUyIiwKICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBNU0UgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwKICAgIHggPSAiIiwKICAgIHkgPSAiTVNFIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgKSArCiAgeWxpbSgwLCBtYXgoZGF0b3NfMzkwNDE1Ml9jb21wbGV0byRNU0UsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCgojIEdyw6FmaWNvIHBhcmEgTUFQRQpnZ3Bsb3QoZGF0b3NfMzkwNDE1Ml9jb21wbGV0bywgYWVzKHggPSBNb2RlbG8sIHkgPSBNQVBFLCBmaWxsID0gTW9kZWxvKSkgKwogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLCB3aWR0aCA9IDAuNikgKwogIGdlb21fdGV4dChhZXMobGFiZWwgPSByb3VuZChNQVBFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM5MDQxNTIiLAogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IE1BUEUgKHZhbG9yZXMgbcOhcyBiYWpvcyBpbmRpY2FuIG1lam9yIHByZWNpc2nDs24pIiwKICAgIHggPSAiIiwKICAgIHkgPSAiTUFQRSAoJSkiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApICsKICB5bGltKDAsIG1heChkYXRvc18zOTA0MTUyX2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCmBgYAoKIyMgUFJPRFVDVE8gMTU1MDAyCmBgYHtyfQojIFByaW1lcm8sIHZlYW1vcyBxdcOpIGRhdG9zIHRlbmVtb3MgcmVhbG1lbnRlCnByaW50KCJEYXRvcyBhY3R1YWxlcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMjoiKQpwcmludChtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMTU1MDAyIikpCgojIENyZWFyIHVuIGRhdGFmcmFtZSBtYW51YWxtZW50ZSBjb24gbG9zIDQgbW9kZWxvcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMgpkYXRvc18xNTUwMDJfY29tcGxldG8gPC0gZGF0YS5mcmFtZSgKICBQcm9kdWN0byA9IHJlcCgiMTU1MDAyIiwgNCksCiAgTW9kZWxvID0gYygiQVJNQS9TQVJJTUEiLCAiUmVncmVzacOzbiBMaW5lYWwiLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IiksCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCikKCiMgVW5pciBjb24gbG9zIGRhdG9zIGV4aXN0ZW50ZXMKZGF0b3NfMTU1MDAyX2NvbXBsZXRvIDwtIGxlZnRfam9pbigKICBkYXRvc18xNTUwMDJfY29tcGxldG8sCiAgbWV0cmljYXNfY29tcGFyYXRpdmFzICU+JSBmaWx0ZXIoUHJvZHVjdG8gPT0gIjE1NTAwMiIpLAogIGJ5ID0gYygiUHJvZHVjdG8iLCAiTW9kZWxvIikKKQoKIyBBaG9yYSBhc2lnbmEgdmFsb3JlcyBwYXJhIGxhcyBtw6l0cmljYXMgZGUgbG9zIG1vZGVsb3MgZmFsdGFudGVzCiMgVmFsb3JlcyBwYXJhIFJlZ3Jlc2nDs24gTGluZWFsCmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kTUFQRVsyXSkpIHsKICBkYXRvc18xNTUwMDJfY29tcGxldG8kTUFQRVsyXSA8LSBtYXBlXzE1NTAwMgp9CmlmIChpcy5uYShkYXRvc18xNTUwMDJfY29tcGxldG8kTVNFWzJdKSkgewogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRNU0VbMl0gPC0gbXNlXzE1NTAwMgp9CiMgVmFsb3JlcyBwYXJhIFJhbmRvbSBGb3Jlc3QKIyBTaSB5YSBlamVjdXRhc3RlIGxhIHNlY2Npw7NuIGRlIFJhbmRvbSBGb3Jlc3QgcGFyYSBlbCBwcm9kdWN0byAxNTUwMDIKaWYgKGlzLm5hKGRhdG9zXzE1NTAwMl9jb21wbGV0byRNQVBFWzNdKSAmJiBleGlzdHMoIm1hcGVfcmYiKSkgewogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRNQVBFWzNdIDwtIG1hcGVfcmYKfQppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1TRVszXSkgJiYgZXhpc3RzKCJtc2VfcmYiKSkgewogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRNU0VbM10gPC0gbXNlX3JmCn0KCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QKaWYgKGlzLm5hKGRhdG9zXzE1NTAwMl9jb21wbGV0byRNQVBFWzRdKSAmJiBleGlzdHMoIm1hcGVfY29tcGxldG8iKSkgewogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRNQVBFWzRdIDwtIG1hcGVfY29tcGxldG8KfQppZiAoaXMubmEoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1TRVs0XSkgJiYgZXhpc3RzKCJtc2VfY29tcGxldG8iKSkgewogIGRhdG9zXzE1NTAwMl9jb21wbGV0byRNU0VbNF0gPC0gbXNlX2NvbXBsZXRvCn0KCiMgVmVyIGxvcyBkYXRvcyBjb21wbGV0b3MKcHJpbnQoIkRhdG9zIGNvbXBsZXRvcyBwYXJhIGVsIHByb2R1Y3RvIDE1NTAwMjoiKQpwcmludChkYXRvc18xNTUwMDJfY29tcGxldG8pCgojIERlZmluaXIgY29sb3JlcyBwYXJhIGxvcyBtb2RlbG9zCmNvbG9yZXNfbW9kZWxvcyA8LSBjKCJBUk1BL1NBUklNQSIgPSAiIzFmNzdiNCIsIAogICAgICAgICAgICAgICAgICAgICAiUmVncmVzacOzbiBMaW5lYWwiID0gIiNmZjdmMGUiLCAKICAgICAgICAgICAgICAgICAgICAgIlJhbmRvbSBGb3Jlc3QiID0gIiMyY2EwMmMiLCAKICAgICAgICAgICAgICAgICAgICAgIlhHQm9vc3QiID0gIiNkNjI3MjgiKQoKIyBHcsOhZmljbyBwYXJhIE1TRQpnZ3Bsb3QoZGF0b3NfMTU1MDAyX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IE1TRSwgZmlsbCA9IE1vZGVsbykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoTVNFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDE1NTAwMiIsCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogTVNFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsCiAgICB4ID0gIiIsCiAgICB5ID0gIk1TRSIKICApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQogICkgKwogIHlsaW0oMCwgbWF4KGRhdG9zXzE1NTAwMl9jb21wbGV0byRNU0UsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCgojIEdyw6FmaWNvIHBhcmEgTUFQRQpnZ3Bsb3QoZGF0b3NfMTU1MDAyX2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IE1BUEUsIGZpbGwgPSBNb2RlbG8pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1BUEUsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArCiAgbGFicygKICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMTU1MDAyIiwKICAgIHN1YnRpdGxlID0gIk3DqXRyaWNhOiBNQVBFICh2YWxvcmVzIG3DoXMgYmFqb3MgaW5kaWNhbiBtZWpvciBwcmVjaXNpw7NuKSIsCiAgICB4ID0gIiIsCiAgICB5ID0gIk1BUEUgKCUpIgogICkgKwogIHRoZW1lX21pbmltYWwoKSArCiAgdGhlbWUoCiAgICBsZWdlbmQucG9zaXRpb24gPSAibm9uZSIsCiAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMiwgZmFjZSA9ICJib2xkIiksCiAgICBwbG90LnN1YnRpdGxlID0gZWxlbWVudF90ZXh0KHNpemUgPSAxMCksCiAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgKSArCiAgeWxpbSgwLCBtYXgoZGF0b3NfMTU1MDAyX2NvbXBsZXRvJE1BUEUsIG5hLnJtID0gVFJVRSkgKiAxLjEpICAjIEFqdXN0YXIgZWwgbMOtbWl0ZSBZCmBgYAoKIyMgUFJPRFVDVE8gMzY3ODA1NQpgYGB7cn0KIyBQcmltZXJvLCB2ZWFtb3MgcXXDqSBkYXRvcyB0ZW5lbW9zIHJlYWxtZW50ZQpwcmludCgiRGF0b3MgYWN0dWFsZXMgcGFyYSBlbCBwcm9kdWN0byAzNjc4MDU1OiIpCnByaW50KG1ldHJpY2FzX2NvbXBhcmF0aXZhcyAlPiUgZmlsdGVyKFByb2R1Y3RvID09ICIzNjc4MDU1IikpCgojIENyZWFyIHVuIGRhdGFmcmFtZSBtYW51YWxtZW50ZSBjb24gbG9zIDQgbW9kZWxvcyBwYXJhIGVsIHByb2R1Y3RvIDM2NzgwNTUKZGF0b3NfMzY3ODA1NV9jb21wbGV0byA8LSBkYXRhLmZyYW1lKAogIFByb2R1Y3RvID0gcmVwKCIzNjc4MDU1IiwgNCksCiAgTW9kZWxvID0gYygiQVJNQS9TQVJJTUEiLCAiUmVncmVzacOzbiBMaW5lYWwiLCAiUmFuZG9tIEZvcmVzdCIsICJYR0Jvb3N0IiksCiAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFCikKCiMgVW5pciBjb24gbG9zIGRhdG9zIGV4aXN0ZW50ZXMKZGF0b3NfMzY3ODA1NV9jb21wbGV0byA8LSBsZWZ0X2pvaW4oCiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0bywKICBtZXRyaWNhc19jb21wYXJhdGl2YXMgJT4lIGZpbHRlcihQcm9kdWN0byA9PSAiMzY3ODA1NSIpLAogIGJ5ID0gYygiUHJvZHVjdG8iLCAiTW9kZWxvIikKKQoKIyBBaG9yYSBhc2lnbmEgdmFsb3JlcyBwYXJhIGxhcyBtw6l0cmljYXMgZGUgbG9zIG1vZGVsb3MgZmFsdGFudGVzCiMgVmFsb3JlcyBwYXJhIFJlZ3Jlc2nDs24gTGluZWFsCmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEVbMl0pKSB7CiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0byRNQVBFWzJdIDwtIG1hcGVfMzY3ODA1NQp9CmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1TRVsyXSkpIHsKICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1TRVsyXSA8LSBtc2VfMzY3ODA1NQp9CgojIFZhbG9yZXMgcGFyYSBSYW5kb20gRm9yZXN0CiMgU2kgeWEgZWplY3V0YXN0ZSBsYSBzZWNjacOzbiBkZSBSYW5kb20gRm9yZXN0IHBhcmEgZWwgcHJvZHVjdG8gMzY3ODA1NQppZiAoaXMubmEoZGF0b3NfMzY3ODA1NV9jb21wbGV0byRNQVBFWzNdKSAmJiBleGlzdHMoIm1hcGVfcmYiKSkgewogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTUFQRVszXSA8LSBtYXBlX3JmCn0KaWYgKGlzLm5hKGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTVNFWzNdKSAmJiBleGlzdHMoIm1zZV9yZiIpKSB7CiAgZGF0b3NfMzY3ODA1NV9jb21wbGV0byRNU0VbM10gPC0gbXNlX3JmCn0KCiMgVmFsb3JlcyBwYXJhIFhHQm9vc3QKaWYgKGlzLm5hKGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTUFQRVs0XSkgJiYgZXhpc3RzKCJtYXBlX2NvbXBsZXRvIikpIHsKICBkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1BUEVbNF0gPC0gbWFwZV9jb21wbGV0bwp9CmlmIChpcy5uYShkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1TRVs0XSkgJiYgZXhpc3RzKCJtc2VfY29tcGxldG8iKSkgewogIGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTVNFWzRdIDwtIG1zZV9jb21wbGV0bwp9CgojIFZlciBsb3MgZGF0b3MgY29tcGxldG9zCnByaW50KCJEYXRvcyBjb21wbGV0b3MgcGFyYSBlbCBwcm9kdWN0byAzNjc4MDU1OiIpCnByaW50KGRhdG9zXzM2NzgwNTVfY29tcGxldG8pCgojIERlZmluaXIgY29sb3JlcyBwYXJhIGxvcyBtb2RlbG9zCmNvbG9yZXNfbW9kZWxvcyA8LSBjKCJBUk1BL1NBUklNQSIgPSAiIzFmNzdiNCIsIAogICAgICAgICAgICAgICAgICAgICAiUmVncmVzacOzbiBMaW5lYWwiID0gIiNmZjdmMGUiLCAKICAgICAgICAgICAgICAgICAgICAgIlJhbmRvbSBGb3Jlc3QiID0gIiMyY2EwMmMiLCAKICAgICAgICAgICAgICAgICAgICAgIlhHQm9vc3QiID0gIiNkNjI3MjgiKQojIEdyw6FmaWNvIHBhcmEgTVNFCmdncGxvdChkYXRvc18zNjc4MDU1X2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IE1TRSwgZmlsbCA9IE1vZGVsbykpICsKICBnZW9tX2JhcihzdGF0ID0gImlkZW50aXR5Iiwgd2lkdGggPSAwLjYpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gcm91bmQoTVNFLCAxKSksIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkgKwogIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcyA9IGNvbG9yZXNfbW9kZWxvcykgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJDb21wYXJhY2nDs24gZGUgbW9kZWxvcyBwYXJhIFByb2R1Y3RvIDM2NzgwNTUiLAogICAgc3VidGl0bGUgPSAiTcOpdHJpY2E6IE1TRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLAogICAgeCA9ICIiLAogICAgeSA9ICJNU0UiCiAgKSArCiAgdGhlbWVfbWluaW1hbCgpICsKICB0aGVtZSgKICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIiwKICAgIHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEyLCBmYWNlID0gImJvbGQiKSwKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDEwKSwKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGFuZ2xlID0gNDUsIGhqdXN0ID0gMSkKICApICsKICB5bGltKDAsIG1heChkYXRvc18zNjc4MDU1X2NvbXBsZXRvJE1TRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkKCiMgR3LDoWZpY28gcGFyYSBNQVBFCmdncGxvdChkYXRvc18zNjc4MDU1X2NvbXBsZXRvLCBhZXMoeCA9IE1vZGVsbywgeSA9IE1BUEUsIGZpbGwgPSBNb2RlbG8pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIHdpZHRoID0gMC42KSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IHJvdW5kKE1BUEUsIDEpKSwgdmp1c3QgPSAtMC41LCBzaXplID0gMy41KSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gY29sb3Jlc19tb2RlbG9zKSArCiAgbGFicygKICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBtb2RlbG9zIHBhcmEgUHJvZHVjdG8gMzY3ODA1NSIsCiAgICBzdWJ0aXRsZSA9ICJNw6l0cmljYTogTUFQRSAodmFsb3JlcyBtw6FzIGJham9zIGluZGljYW4gbWVqb3IgcHJlY2lzacOzbikiLAogICAgeCA9ICIiLAogICAgeSA9ICJNQVBFICglKSIKICApICsKICB0aGVtZV9taW5pbWFsKCkgKwogIHRoZW1lKAogICAgbGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiLAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTIsIGZhY2UgPSAiYm9sZCIpLAogICAgcGxvdC5zdWJ0aXRsZSA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApLAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKQogICkgKwogIHlsaW0oMCwgbWF4KGRhdG9zXzM2NzgwNTVfY29tcGxldG8kTUFQRSwgbmEucm0gPSBUUlVFKSAqIDEuMSkgICMgQWp1c3RhciBlbCBsw61taXRlIFkKYGBgCgojIEVTVElNQUNJw5NOIERFIFBSRUNJT1MKCiMjIyBQcmVwYXJhY2nDs24gZGUgZGF0b3MKYGBge3J9CiMgRnVuY2nDs24gcGFyYSBwcmVwYXJhciBkYXRvcyBkZSB1biBwcm9kdWN0bwpwcmVwYXJlX3ByaWNlX2RhdGEgPC0gZnVuY3Rpb24oZGYsIHByb2R1Y3RfaWQpIHsKICBwcm9kdWN0X2RhdGEgPC0gZGYgJT4lCiAgICBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKSAlPiUKICAgIGFycmFuZ2UoVHJ4X0ZlY2hhKSAlPiUKICAgIHNlbGVjdCgKICAgICAgVHJ4X0ZlY2hhLCBQcmVjaW9fRmluYWxfVW5pdGFyaW8sIENhbnQsIFZlbnRhLCAKICAgICAgQ29zdG9fVmVudGEsIERlc2N1ZW50b19Qb3JjZW50YWplLCBTZW1hbmEsIE1lcwogICAgKSAlPiUKICAgIG11dGF0ZSgKICAgICAgRGlhX1NlbWFuYSA9IHdkYXkoVHJ4X0ZlY2hhKSwKICAgICAgTWVzX051bSA9IG1vbnRoKFRyeF9GZWNoYSksCiAgICAgIEFuaW8gPSB5ZWFyKFRyeF9GZWNoYSksCiAgICAgIERpYXNfRGVzZGVfSW5pY2lvID0gYXMubnVtZXJpYyhkaWZmdGltZShUcnhfRmVjaGEsIG1pbihUcnhfRmVjaGEpLCB1bml0cyA9ICJkYXlzIikpLAogICAgICBNYXJnZW5fVW5pdGFyaW8gPSAoVmVudGEgLyBDYW50KSAtIChDb3N0b19WZW50YSAvIENhbnQpLAogICAgICBQcmVjaW9fVW5pdGFyaW9fQ2FsYyA9IFZlbnRhIC8gQ2FudCwKICAgICAgSURfSW52ZW50YXJpbyA9IHByb2R1Y3RfaWQKICAgICkKICAKICByZXR1cm4ocHJvZHVjdF9kYXRhKQp9CgojIEFzZWfDunJhdGUgZGUgcXVlICdkYXRvcycgc2VhIHR1IGRhdGEuZnJhbWUgY2FyZ2FkbyBjb3JyZWN0YW1lbnRlCiMgUG9yIGVqZW1wbG8sIHNpIHZpZW5lcyBkZSB1biBhcmNoaXZvIC5jc3Y6CiMgZGF0b3MgPC0gcmVhZC5jc3YoImFyY2hpdm8uY3N2IikKCiMgQXBsaWNhciBsYSBmdW5jacOzbiBhIHRvZG9zIGxvcyBwcm9kdWN0b3MKaWRzIDwtIHVuaXF1ZShkYXRvcyRJRF9JbnZlbnRhcmlvKQoKcHJvZHVjdG9zX3ByZXBhcmFkb3MgPC0gbWFwX2RmKGlkcywgZnVuY3Rpb24oaWQpIHsKICBwcmVwYXJlX3ByaWNlX2RhdGEoZGF0b3MsIGlkKQp9KQoKIyBNb3N0cmFyIHVuYSBwYXJ0ZSBkZWwgcmVzdWx0YWRvCmhlYWQocHJvZHVjdG9zX3ByZXBhcmFkb3MpCmBgYAoKCgoKYGBge3J9CiMgVmVjdG9yIGNvbiBwcm9kdWN0b3MgKGRlYmUgaXIgcHJpbWVybykKcHJvZHVjdG9zX2lkcyA8LSB0b3BfaWRzCgojIEZ1bmNpw7NuIHBhcmEgZW50cmVuYXIgbW9kZWxvIEFSTUEgcG9yIHByb2R1Y3RvCnRyYWluX2FybWFfbW9kZWwgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCkgewogIGxpYnJhcnkoZm9yZWNhc3QpICAjIEFzZWfDunJhdGUgZGUgY2FyZ2FyIGZvcmVjYXN0IHNpIG5vIGVzdMOhIGNhcmdhZG8gYcO6bgogIHByb2R1Y3RfZGF0YSA8LSBkYXRhICU+JSBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKQogIHNlcmllX3RzIDwtIHRzKHByb2R1Y3RfZGF0YSRWZW50YSwgZnJlcXVlbmN5ID0gMTIpCiAgbW9kZWxvX2FybWEgPC0gYXV0by5hcmltYShzZXJpZV90cywgc2Vhc29uYWwgPSBGQUxTRSwgc3RlcHdpc2UgPSBGQUxTRSwgYXBwcm94aW1hdGlvbiA9IEZBTFNFKQogIHJldHVybihtb2RlbG9fYXJtYSkKfQoKIyBDcmVhciBsaXN0YSBkZSBtb2RlbG9zIEFSTUEgcG9yIHByb2R1Y3RvCm1vZGVsb3NfYXJtYV9saXN0YSA8LSBzZXROYW1lcygKICBsYXBwbHkocHJvZHVjdG9zX2lkcywgZnVuY3Rpb24oaWQpIHRyYWluX2FybWFfbW9kZWwoZGF0b3MsIGlkKSksCiAgYXMuY2hhcmFjdGVyKHByb2R1Y3Rvc19pZHMpCikKCiMgRnVuY2nDs24gcGFyYSBtb2RlbG8gcmVncmVzacOzbiBsaW5lYWwKdHJhaW5fcmVnX21vZGVsIDwtIGZ1bmN0aW9uKGRhdGEsIHByb2R1Y3RfaWQpIHsKICBwcm9kdWN0X2RhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkKICBtb2RlbG9fcmVnIDwtIGxtKFZlbnRhIH4gUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLCBkYXRhID0gcHJvZHVjdF9kYXRhKQogIHJldHVybihtb2RlbG9fcmVnKQp9CgojIEZ1bmNpw7NuIHBhcmEgbW9kZWxvIFJhbmRvbSBGb3Jlc3QKdHJhaW5fcmZfbW9kZWwgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCkgewogIHByb2R1Y3RfZGF0YSA8LSBkYXRhICU+JSBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKQogIHByZWRpY3RvcnMgPC0gYygiUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIiwgIkNhbnQiLCAiRGVzY3VlbnRvX1BvcmNlbnRhamUiKQogIHJmX2RhdGEgPC0gcHJvZHVjdF9kYXRhICU+JSBzZWxlY3QoYWxsX29mKHByZWRpY3RvcnMpLCBWZW50YSkKICBtb2RlbG9fcmYgPC0gcmFuZG9tRm9yZXN0KFZlbnRhIH4gLiwgZGF0YSA9IHJmX2RhdGEsIG50cmVlID0gMTAwKQogIHJldHVybihtb2RlbG9fcmYpCn0KCiMgRnVuY2nDs24gcGFyYSBtb2RlbG8gWEdCb29zdAp0cmFpbl94Z2JfbW9kZWwgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCkgewogIHByb2R1Y3RfZGF0YSA8LSBkYXRhICU+JSBmaWx0ZXIoSURfSW52ZW50YXJpbyA9PSBwcm9kdWN0X2lkKQogIHByZWRpY3RvcnMgPC0gYygiUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIiwgIkNhbnQiLCAiRGVzY3VlbnRvX1BvcmNlbnRhamUiKQogIHRyYWluX21hdHJpeCA8LSB4Z2IuRE1hdHJpeChkYXRhID0gYXMubWF0cml4KHByb2R1Y3RfZGF0YVssIHByZWRpY3RvcnNdKSwgbGFiZWwgPSBwcm9kdWN0X2RhdGEkVmVudGEpCiAgcGFyYW1zIDwtIGxpc3Qob2JqZWN0aXZlID0gInJlZzpzcXVhcmVkZXJyb3IiKQogIG1vZGVsb194Z2IgPC0geGdiLnRyYWluKHBhcmFtcyA9IHBhcmFtcywgZGF0YSA9IHRyYWluX21hdHJpeCwgbnJvdW5kcyA9IDUwLCB2ZXJib3NlID0gMCkKICByZXR1cm4obW9kZWxvX3hnYikKfQoKIyBDcmVhciBsaXN0YXMgZGUgbW9kZWxvcwptb2RlbG9zX3JlZ19saXN0YSA8LSBzZXROYW1lcyhsYXBwbHkocHJvZHVjdG9zX2lkcywgZnVuY3Rpb24oaWQpIHRyYWluX3JlZ19tb2RlbChkYXRvcywgaWQpKSwgYXMuY2hhcmFjdGVyKHByb2R1Y3Rvc19pZHMpKQptb2RlbG9zX3JmX2xpc3RhIDwtIHNldE5hbWVzKGxhcHBseShwcm9kdWN0b3NfaWRzLCBmdW5jdGlvbihpZCkgdHJhaW5fcmZfbW9kZWwoZGF0b3MsIGlkKSksIGFzLmNoYXJhY3Rlcihwcm9kdWN0b3NfaWRzKSkKbW9kZWxvc194Z2JfbGlzdGEgPC0gc2V0TmFtZXMobGFwcGx5KHByb2R1Y3Rvc19pZHMsIGZ1bmN0aW9uKGlkKSB0cmFpbl94Z2JfbW9kZWwoZGF0b3MsIGlkKSksIGFzLmNoYXJhY3Rlcihwcm9kdWN0b3NfaWRzKSkKYGBgCgojIyMgRW50cmVuYXIgbW9kZWxvcyBkZSBwcmVkaWNjacOzbiBkZSBwcmVjaW9zCmBgYHtyfQojIEZ1bmNpw7NuIHBhcmEgZW50cmVuYXIgbW9kZWxvcyBkZSBwcmVkaWNjacOzbiBkZSBwcmVjaW9zCnRyYWluX3ByaWNlX21vZGVscyA8LSBmdW5jdGlvbihkYXRhLCBwcm9kdWN0X2lkLCB0ZXN0X3NpemUgPSAwLjIpIHsKICBwcmljZV9kYXRhIDwtIHByZXBhcmVfcHJpY2VfZGF0YShkYXRhLCBwcm9kdWN0X2lkKSAlPiUKICAgIGRyb3BfbmEoKSAlPiUKICAgIHNlbGVjdCgKICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvLAogICAgICBDYW50LCBDb3N0b19WZW50YSwgRGVzY3VlbnRvX1BvcmNlbnRhamUsCiAgICAgIERpYV9TZW1hbmEsIE1lc19OdW0sIEFuaW8sIERpYXNfRGVzZGVfSW5pY2lvLAogICAgICBNYXJnZW5fVW5pdGFyaW8KICAgICkKCiAgIyBFdml0YXIgZmFsbG9zIHNpIGhheSBtdXkgcG9jb3MgZGF0b3MKICBpZiAobnJvdyhwcmljZV9kYXRhKSA8IDEwKSB7CiAgICB3YXJuaW5nKHBhc3RlKCJQcm9kdWN0byIsIHByb2R1Y3RfaWQsICJ0aWVuZSBtZW5vcyBkZSAxMCByZWdpc3Ryb3MuIFNlIG9taXRlLiIpKQogICAgcmV0dXJuKE5VTEwpCiAgfQoKICBzZXQuc2VlZCgxMjMpCiAgdHJhaW5faW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihwcmljZV9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbywgcCA9IDEgLSB0ZXN0X3NpemUsIGxpc3QgPSBGQUxTRSkKICB0cmFpbl9kYXRhIDwtIHByaWNlX2RhdGFbdHJhaW5faW5kZXgsIF0KICB0ZXN0X2RhdGEgPC0gcHJpY2VfZGF0YVstdHJhaW5faW5kZXgsIF0KCiAgIyAxLiBSZWdyZXNpw7NuIExpbmVhbAogIGxtX21vZGVsIDwtIGxtKFByZWNpb19GaW5hbF9Vbml0YXJpbyB+IC4sIGRhdGEgPSB0cmFpbl9kYXRhKQoKICAjIDIuIFJhbmRvbSBGb3Jlc3QKICByZl9tb2RlbCA8LSByYW5kb21Gb3Jlc3QoCiAgICBQcmVjaW9fRmluYWxfVW5pdGFyaW8gfiAuLAogICAgZGF0YSA9IHRyYWluX2RhdGEsCiAgICBudHJlZSA9IDUwMCwKICAgIGltcG9ydGFuY2UgPSBUUlVFCiAgKQoKICAjIDMuIFhHQm9vc3QKICBmZWF0dXJlcyA8LSBzZXRkaWZmKG5hbWVzKHRyYWluX2RhdGEpLCAiUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIikKICB4X3RyYWluIDwtIGFzLm1hdHJpeCh0cmFpbl9kYXRhWywgZmVhdHVyZXNdKQogIHlfdHJhaW4gPC0gdHJhaW5fZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8KICB4X3Rlc3QgPC0gYXMubWF0cml4KHRlc3RfZGF0YVssIGZlYXR1cmVzXSkKICB5X3Rlc3QgPC0gdGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbwogIGR0cmFpbiA8LSB4Z2IuRE1hdHJpeChkYXRhID0geF90cmFpbiwgbGFiZWwgPSB5X3RyYWluKQogIGR0ZXN0IDwtIHhnYi5ETWF0cml4KGRhdGEgPSB4X3Rlc3QsIGxhYmVsID0geV90ZXN0KQoKICB4Z2JfcGFyYW1zIDwtIGxpc3QoCiAgICBvYmplY3RpdmUgPSAicmVnOnNxdWFyZWRlcnJvciIsCiAgICBldmFsX21ldHJpYyA9ICJybXNlIiwKICAgIGV0YSA9IDAuMSwKICAgIG1heF9kZXB0aCA9IDYsCiAgICBtaW5fY2hpbGRfd2VpZ2h0ID0gMywKICAgIHN1YnNhbXBsZSA9IDAuOCwKICAgIGNvbHNhbXBsZV9ieXRyZWUgPSAwLjgKICApCgogIHhnYl9tb2RlbCA8LSB4Z2IudHJhaW4oCiAgICBwYXJhbXMgPSB4Z2JfcGFyYW1zLAogICAgZGF0YSA9IGR0cmFpbiwKICAgIG5yb3VuZHMgPSAxMDAsCiAgICB3YXRjaGxpc3QgPSBsaXN0KHRyYWluID0gZHRyYWluLCB0ZXN0ID0gZHRlc3QpLAogICAgZWFybHlfc3RvcHBpbmdfcm91bmRzID0gMTAsCiAgICB2ZXJib3NlID0gMAogICkKCiAgIyBFdmFsdWFjacOzbgogIGxtX3ByZWQgPC0gcHJlZGljdChsbV9tb2RlbCwgbmV3ZGF0YSA9IHRlc3RfZGF0YSkKICByZl9wcmVkIDwtIHByZWRpY3QocmZfbW9kZWwsIG5ld2RhdGEgPSB0ZXN0X2RhdGEpCiAgeGdiX3ByZWQgPC0gcHJlZGljdCh4Z2JfbW9kZWwsIHhfdGVzdCkKCiAgbG1fcm1zZSA8LSBzcXJ0KG1lYW4oKGxtX3ByZWQgLSB0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKV4yKSkKICByZl9ybXNlIDwtIHNxcnQobWVhbigocmZfcHJlZCAtIHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8pXjIpKQogIHhnYl9ybXNlIDwtIHNxcnQobWVhbigoeGdiX3ByZWQgLSB0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKV4yKSkKCiAgbG1fcjIgPC0gMSAtIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIGxtX3ByZWQpXjIpIC8KICAgIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIG1lYW4odGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbykpXjIpCiAgcmZfcjIgPC0gMSAtIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIHJmX3ByZWQpXjIpIC8KICAgIHN1bSgodGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbyAtIG1lYW4odGVzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbykpXjIpCiAgeGdiX3IyIDwtIDEgLSBzdW0oKHRlc3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8gLSB4Z2JfcHJlZCleMikgLwogICAgc3VtKCh0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIC0gbWVhbih0ZXN0X2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKSleMikKCiAgbWV0cmljcyA8LSBkYXRhLmZyYW1lKAogICAgTW9kZWwgPSBjKCJMaW5lYXIgUmVncmVzc2lvbiIsICJSYW5kb20gRm9yZXN0IiwgIlhHQm9vc3QiKSwKICAgIFJNU0UgPSBjKGxtX3Jtc2UsIHJmX3Jtc2UsIHhnYl9ybXNlKSwKICAgIFIyID0gYyhsbV9yMiwgcmZfcjIsIHhnYl9yMikKICApCgogIHJldHVybihsaXN0KG1ldHJpY3MgPSBtZXRyaWNzKSkKfQoKIyBJRHMgZGUgbG9zIDUgcHJvZHVjdG9zIGEgbW9kZWxhcgpwcm9kdWN0b3NfaWRzIDwtIGMoMTU1MDAxLCAzOTI5Nzg4LCAzOTA0MTUyLCAxNTUwMDIsIDM2NzgwNTUpCgojIEFwbGljYXIgbW9kZWxvIGEgY2FkYSBwcm9kdWN0bwpyZXN1bHRhZG9zX21vZGVsb3MgPC0gbWFwKHByb2R1Y3Rvc19pZHMsIGZ1bmN0aW9uKGlkKSB7CiAgcmVzdWx0YWRvIDwtIHRyYWluX3ByaWNlX21vZGVscyhkYXRvcywgcHJvZHVjdF9pZCA9IGlkKQogIGlmICghaXMubnVsbChyZXN1bHRhZG8pKSB7CiAgICByZXN1bHRhZG8kbWV0cmljcyAlPiUgbXV0YXRlKElEX0ludmVudGFyaW8gPSBpZCkKICB9IGVsc2UgewogICAgTlVMTAogIH0KfSkgJT4lIGNvbXBhY3QoKSAlPiUgYmluZF9yb3dzKCkKCiMgTW9zdHJhciByZXN1bHRhZG9zCnJlc3VsdGFkb3NfbW9kZWxvcwpgYGAKCmBgYHtyfQojIExpc3RhIGNvbiBsb3MgSURzIGRlIHByb2R1Y3RvcyAocHVlZGVzIHVzYXIgdG9wX2lkcyBxdWUgeWEgZGVmaW5pc3RlKQpwcm9kdWN0b3NfaWRzIDwtIHRvcF9pZHMKCiMgRW50cmVuYXIgbW9kZWxvcyBwYXJhIGNhZGEgcHJvZHVjdG8geSBndWFyZGFyIGVuIGxpc3RhCm1vZGVsb3NfcHJlY2lvX2xpc3RhIDwtIHNldE5hbWVzKAogIGxhcHBseShwcm9kdWN0b3NfaWRzLCBmdW5jdGlvbihpZCkgdHJhaW5fcHJpY2VfbW9kZWxzKGRhdG9zLCBpZCkpLAogIGFzLmNoYXJhY3Rlcihwcm9kdWN0b3NfaWRzKQopCmBgYAoKIyMjIEVzdGltYXIgcHJlY2lvcyDDs3B0aW1vcwpgYGB7cn0KZXN0aW1hdGVfb3B0aW1hbF9wcmljZXMgPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZCwgcHJpY2VfbW9kZWxzLCBkZW1hbmRfbW9kZWxzID0gTlVMTCwgZnV0dXJlX2RhdGVzID0gTlVMTCkgewogIHByaWNlX3N0ZXBzIDwtIDIwCiAgCiAgYmVzdF9wcmljZV9tb2RlbF9pZHggPC0gd2hpY2gubWF4KHByaWNlX21vZGVscyRtZXRyaWNzJFIyKQogIGJlc3RfcHJpY2VfbW9kZWxfbmFtZSA8LSBwcmljZV9tb2RlbHMkbWV0cmljcyRNb2RlbFtiZXN0X3ByaWNlX21vZGVsX2lkeF0KICAKICBwcm9kdWN0X2RhdGEgPC0gZGF0YSAlPiUgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkKICBtaW5fcHJpY2UgPC0gbWluKHByb2R1Y3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSkKICBtYXhfcHJpY2UgPC0gbWF4KHByb2R1Y3RfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8sIG5hLnJtID0gVFJVRSkKICBwcmljZV9yYW5nZSA8LSBzZXEobWluX3ByaWNlLCBtYXhfcHJpY2UsIGxlbmd0aC5vdXQgPSBwcmljZV9zdGVwcykKICAKICBmdXR1cmVfc2NlbmFyaW9zIDwtIGRhdGEuZnJhbWUoKQogIAogIGZvciAoZnV0dXJlX2RhdGUgaW4gZnV0dXJlX2RhdGVzKSB7CiAgICBmdXR1cmVfZGF0ZSA8LSBhcy5EYXRlKGZ1dHVyZV9kYXRlKQogICAgbWVzX2FjdHVhbCA8LSBsdWJyaWRhdGU6Om1vbnRoKGZ1dHVyZV9kYXRlKQogICAgCiAgICBtZXNfZGF0YSA8LSBwcm9kdWN0X2RhdGEgJT4lIGZpbHRlcihsdWJyaWRhdGU6Om1vbnRoKFRyeF9GZWNoYSkgPT0gbWVzX2FjdHVhbCkKICAgIGlmIChucm93KG1lc19kYXRhKSA8IDUpIG1lc19kYXRhIDwtIHByb2R1Y3RfZGF0YQogICAgCiAgICBjb3N0b19tZXMgPC0gbWVkaWFuKG1lc19kYXRhJENvc3RvX1ZlbnRhLCBuYS5ybSA9IFRSVUUpCiAgICBjYW50X21lcyA8LSBtZWRpYW4obWVzX2RhdGEkQ2FudCwgbmEucm0gPSBUUlVFKQogICAgZGVzY19tZXMgPC0gbWVkaWFuKG1lc19kYXRhJERlc2N1ZW50b19Qb3JjZW50YWplLCBuYS5ybSA9IFRSVUUpCiAgICAKICAgIGlmIChpcy5uYShjb3N0b19tZXMpKSBjb3N0b19tZXMgPC0gbWVkaWFuKHByb2R1Y3RfZGF0YSRDb3N0b19WZW50YSwgbmEucm0gPSBUUlVFKQogICAgaWYgKGlzLm5hKGNhbnRfbWVzKSB8fCBjYW50X21lcyA9PSAwKSBjYW50X21lcyA8LSBtZWRpYW4ocHJvZHVjdF9kYXRhJENhbnQsIG5hLnJtID0gVFJVRSkKICAgIGlmIChpcy5uYShkZXNjX21lcykpIGRlc2NfbWVzIDwtIG1lZGlhbihwcm9kdWN0X2RhdGEkRGVzY3VlbnRvX1BvcmNlbnRhamUsIG5hLnJtID0gVFJVRSkKICAgIAogICAgZGF0ZV9kZiA8LSBkYXRhLmZyYW1lKAogICAgICBUcnhfRmVjaGEgPSByZXAoZnV0dXJlX2RhdGUsIHByaWNlX3N0ZXBzKSwKICAgICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvID0gcHJpY2VfcmFuZ2UsCiAgICAgIENhbnQgPSBjYW50X21lcywKICAgICAgQ29zdG9fVmVudGEgPSBjb3N0b19tZXMsCiAgICAgIERlc2N1ZW50b19Qb3JjZW50YWplID0gZGVzY19tZXMsCiAgICAgIERpYV9TZW1hbmEgPSBsdWJyaWRhdGU6OndkYXkoZnV0dXJlX2RhdGUpLAogICAgICBNZXNfTnVtID0gbWVzX2FjdHVhbCwKICAgICAgQW5pbyA9IGx1YnJpZGF0ZTo6eWVhcihmdXR1cmVfZGF0ZSksCiAgICAgIERpYXNfRGVzZGVfSW5pY2lvID0gYXMubnVtZXJpYyhkaWZmdGltZShmdXR1cmVfZGF0ZSwgbWluKHByb2R1Y3RfZGF0YSRUcnhfRmVjaGEpLCB1bml0cyA9ICJkYXlzIikpLAogICAgICBNYXJnZW5fVW5pdGFyaW8gPSBOQQogICAgKQogICAgCiAgICBmdXR1cmVfc2NlbmFyaW9zIDwtIHJiaW5kKGZ1dHVyZV9zY2VuYXJpb3MsIGRhdGVfZGYpCiAgfQogIAogIGZ1dHVyZV9zY2VuYXJpb3MkTWFyZ2VuX1VuaXRhcmlvIDwtIGZ1dHVyZV9zY2VuYXJpb3MkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvIC0gIAogICAgKGZ1dHVyZV9zY2VuYXJpb3MkQ29zdG9fVmVudGEgLyBmdXR1cmVfc2NlbmFyaW9zJENhbnQpCiAgCiAgcHJvZHVjdF9kYXRhIDwtIHByb2R1Y3RfZGF0YSAlPiUgYXJyYW5nZShUcnhfRmVjaGEpCiAgZWxhc3RpY2l0eV9kZiA8LSBwcm9kdWN0X2RhdGEgJT4lCiAgICBmaWx0ZXIoIWlzLm5hKENhbnQpICYgIWlzLm5hKFByZWNpb19GaW5hbF9Vbml0YXJpbykpICU+JQogICAgbXV0YXRlKAogICAgICBQX2xhZyA9IGxhZyhQcmVjaW9fRmluYWxfVW5pdGFyaW8pLAogICAgICBRX2xhZyA9IGxhZyhDYW50KSwKICAgICAgZFAgPSBQcmVjaW9fRmluYWxfVW5pdGFyaW8gLSBQX2xhZywKICAgICAgZFEgPSBDYW50IC0gUV9sYWcsCiAgICAgIGVsYXN0aWNpdHlfcG9pbnQgPSAoZFEgLyBRX2xhZykgLyAoZFAgLyBQX2xhZykKICAgICkgJT4lCiAgICBmaWx0ZXIoIWlzLm5hKGVsYXN0aWNpdHlfcG9pbnQpLCBpcy5maW5pdGUoZWxhc3RpY2l0eV9wb2ludCkpCiAgCiAgZWxhc3RpY2l0eSA8LSBtZWRpYW4oZWxhc3RpY2l0eV9kZiRlbGFzdGljaXR5X3BvaW50LCBuYS5ybSA9IFRSVUUpCiAgaWYgKGlzLm5hKGVsYXN0aWNpdHkpIHx8ICFpcy5maW5pdGUoZWxhc3RpY2l0eSkpIGVsYXN0aWNpdHkgPC0gMS41CiAgCiAgcmVzdWx0cyA8LSBmdXR1cmVfc2NlbmFyaW9zICU+JQogICAgbXV0YXRlKFZlbnRhX0VzcGVyYWRhID0gMCwgTWFyZ2VuX1RvdGFsID0gMCkKICAKICBmb3IgKGkgaW4gMTpucm93KHJlc3VsdHMpKSB7CiAgICBiYXNlbGluZV9wcmljZSA8LSBtZWRpYW4ocHJvZHVjdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbywgbmEucm0gPSBUUlVFKQogICAgcHJpY2VfcmF0aW8gPC0gYmFzZWxpbmVfcHJpY2UgLyByZXN1bHRzJFByZWNpb19GaW5hbF9Vbml0YXJpb1tpXQogICAgCiAgICBhZGp1c3RlZF9xdWFudGl0eSA8LSByZXN1bHRzJENhbnRbaV0gKiAocHJpY2VfcmF0aW8gXiBlbGFzdGljaXR5KQogICAgcmVzdWx0cyRWZW50YV9Fc3BlcmFkYVtpXSA8LSByZXN1bHRzJFByZWNpb19GaW5hbF9Vbml0YXJpb1tpXSAqIGFkanVzdGVkX3F1YW50aXR5CiAgICByZXN1bHRzJE1hcmdlbl9Ub3RhbFtpXSA8LSBhZGp1c3RlZF9xdWFudGl0eSAqIHJlc3VsdHMkTWFyZ2VuX1VuaXRhcmlvW2ldCiAgfQogIAogIG9wdGltYWxfcHJpY2VzIDwtIHJlc3VsdHMgJT4lCiAgICBncm91cF9ieShUcnhfRmVjaGEpICU+JQogICAgc2xpY2VfbWF4KFZlbnRhX0VzcGVyYWRhLCBuID0gMSkgJT4lCiAgICBzZWxlY3QoVHJ4X0ZlY2hhLCBQcmVjaW9fT3B0aW1hbCA9IFByZWNpb19GaW5hbF9Vbml0YXJpbywgVmVudGFfRXNwZXJhZGEsIE1hcmdlbl9Ub3RhbCkKICAKICByZXR1cm4obGlzdCgKICAgIHJlc3VsdGFkb3MgPSByZXN1bHRzLAogICAgcHJlY2lvc19vcHRpbW9zID0gb3B0aW1hbF9wcmljZXMsCiAgICBlbGFzdGljaWRhZCA9IGVsYXN0aWNpdHkKICApKQp9CmBgYAoKIyMjIFZpc3VhbGl6YXIgcmVzdWx0YWRvcwpgYGB7cn0KZGF0ZXNfZnV0dXJlIDwtIHNlcS5EYXRlKGFzLkRhdGUoIjIwMjUtMDEtMDEiKSwgYnkgPSAibW9udGgiLCBsZW5ndGgub3V0ID0gNikKcHJlY2lvc19vcHRpbW9zX2xpc3RhIDwtIGxpc3QoKQoKZm9yIChpZCBpbiBwcm9kdWN0b3NfaWRzKSB7CiAgY2F0KCJFc3RpbWFuZG8gcHJlY2lvcyDDs3B0aW1vcyBwYXJhIHByb2R1Y3RvOiIsIGlkLCAiXG4iKQogIG1vZGVsb19wcmVjaW8gPC0gbW9kZWxvc19wcmVjaW9fbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXQoKICBpZiAoIWlzLm51bGwobW9kZWxvX3ByZWNpbykpIHsKICAgIHByZWNpb3Nfb3B0aW1vc19saXN0YVtbYXMuY2hhcmFjdGVyKGlkKV1dIDwtIGVzdGltYXRlX29wdGltYWxfcHJpY2VzKAogICAgICBkYXRhID0gZGF0b3MsCiAgICAgIHByb2R1Y3RfaWQgPSBpZCwKICAgICAgcHJpY2VfbW9kZWxzID0gbW9kZWxvX3ByZWNpbywKICAgICAgZnV0dXJlX2RhdGVzID0gZGF0ZXNfZnV0dXJlCiAgICApCiAgfQp9CmBgYAoKYGBge3J9CmdyYWZpY2FzX2luZGl2aWR1YWxlcyA8LSBsaXN0KCkKCmZvciAoaWQgaW4gbmFtZXMocHJlY2lvc19vcHRpbW9zX2xpc3RhKSkgewogIGRmX29wdGltbyA8LSBwcmVjaW9zX29wdGltb3NfbGlzdGFbW2lkXV0kcHJlY2lvc19vcHRpbW9zCgogIHAgPC0gZ2dwbG90KGRmX29wdGltbywgYWVzKHggPSBUcnhfRmVjaGEsIHkgPSBQcmVjaW9fT3B0aW1hbCkpICsKICAgIGdlb21fbGluZShjb2xvciA9ICIjMWY3N2I0IiwgbGluZXdpZHRoID0gMS4yKSArCiAgICBnZW9tX3BvaW50KGNvbG9yID0gIiMxZjc3YjQiLCBzaXplID0gMikgKwogICAgbGFicygKICAgICAgdGl0bGUgPSBwYXN0ZSgiUHJlY2lvIMOTcHRpbW8gcG9yIE1lcyAtIFByb2R1Y3RvIiwgaWQpLAogICAgICB4ID0gIkZlY2hhIiwKICAgICAgeSA9ICJQcmVjaW8gw5NwdGltbyIKICAgICkgKwogICAgc2NhbGVfeF9kYXRlKGRhdGVfbGFiZWxzID0gIiViICVZIiwgZGF0ZV9icmVha3MgPSAiMSBtb250aCIpICsKICAgIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpICsKICAgIHRoZW1lKAogICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLAogICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgICApCgogIGdyYWZpY2FzX2luZGl2aWR1YWxlc1tbaWRdXSA8LSBwCn0KCmZvciAoaWQgaW4gbmFtZXMoZ3JhZmljYXNfaW5kaXZpZHVhbGVzKSkgewogIHByaW50KGdyYWZpY2FzX2luZGl2aWR1YWxlc1tbaWRdXSkKfQpgYGAKCiMjIyBJbnRlZ3JhY2nDs24gZGUgcHJlY2lvcyDDs3B0aW1vcyB5IG1vZGVsb3MKYGBge3J9CmludGVncmF0ZV93aXRoX2V4aXN0aW5nX21vZGVscyA8LSBmdW5jdGlvbihkYXRhLCBwcm9kdWN0X2lkLCBwcmljZV9vcHRfcmVzdWx0cywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhcm1hX21vZGVsID0gTlVMTCwgcmVnX21vZGVsID0gTlVMTCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZl9tb2RlbCA9IE5VTEwsIHhnYl9tb2RlbCA9IE5VTEwpIHsKICBvcHRpbWFsX3ByaWNlcyA8LSBwcmljZV9vcHRfcmVzdWx0c1tbYXMuY2hhcmFjdGVyKHByb2R1Y3RfaWQpXV0kcHJlY2lvc19vcHRpbW9zCiAgCiAgaWYgKGlzLm51bGwob3B0aW1hbF9wcmljZXMpIHx8IG5yb3cob3B0aW1hbF9wcmljZXMpID09IDApIHsKICAgIHdhcm5pbmcocGFzdGUoIk5vIHNlIGVuY29udHJhcm9uIHByZWNpb3Mgw7NwdGltb3MgcGFyYSBlbCBwcm9kdWN0byIsIHByb2R1Y3RfaWQpKQogICAgcmV0dXJuKGRhdGEuZnJhbWUoKSkKICB9CiAgCiAgZnV0dXJlX2RhdGEgPC0gZGF0YS5mcmFtZSgKICAgIEZlY2hhID0gb3B0aW1hbF9wcmljZXMkVHJ4X0ZlY2hhLAogICAgUHJlY2lvX0ZpbmFsX1VuaXRhcmlvID0gb3B0aW1hbF9wcmljZXMkUHJlY2lvX09wdGltYWwKICApCiAgCiAgaGlzdF9kYXRhIDwtIGRhdGEgJT4lIAogICAgZmlsdGVyKElEX0ludmVudGFyaW8gPT0gcHJvZHVjdF9pZCkgJT4lCiAgICBhcnJhbmdlKFRyeF9GZWNoYSkKICAKICBmdXR1cmVfZmVhdHVyZXMgPC0gZGF0YS5mcmFtZSgpCiAgCiAgZm9yIChpIGluIDE6bnJvdyhvcHRpbWFsX3ByaWNlcykpIHsKICAgIGZ1dHVyZV9kYXRlIDwtIG9wdGltYWxfcHJpY2VzJFRyeF9GZWNoYVtpXQogICAgZnV0dXJlX3ByaWNlIDwtIG9wdGltYWxfcHJpY2VzJFByZWNpb19PcHRpbWFsW2ldCiAgICAKICAgIG1lc19kYXRhIDwtIGhpc3RfZGF0YSAlPiUgZmlsdGVyKGx1YnJpZGF0ZTo6bW9udGgoVHJ4X0ZlY2hhKSA9PSBsdWJyaWRhdGU6Om1vbnRoKGZ1dHVyZV9kYXRlKSkKICAgIGlmIChucm93KG1lc19kYXRhKSA8IDUpIG1lc19kYXRhIDwtIGhpc3RfZGF0YQogICAgCiAgICBhdmdfZmVhdHVyZXMgPC0gbWVzX2RhdGEgJT4lIAogICAgICBzdW1tYXJpc2UoCiAgICAgICAgQ2FudCA9IG1lZGlhbihDYW50LCBuYS5ybSA9IFRSVUUpLAogICAgICAgIENvc3RvX1ZlbnRhID0gbWVkaWFuKENvc3RvX1ZlbnRhLCBuYS5ybSA9IFRSVUUpLAogICAgICAgIENvc3RvX0Rldm9sdWNpb24gPSBtZWRpYW4oQ29zdG9fRGV2b2x1Y2lvbiwgbmEucm0gPSBUUlVFKSwKICAgICAgICBQcmVjaW9fTGlzdGFfVW5pdGFyaW8gPSBtZWRpYW4oUHJlY2lvX0xpc3RhX1VuaXRhcmlvLCBuYS5ybSA9IFRSVUUpLAogICAgICAgIERlc2N1ZW50b19Qb3JjZW50YWplID0gbWVkaWFuKERlc2N1ZW50b19Qb3JjZW50YWplLCBuYS5ybSA9IFRSVUUpLAogICAgICAgIFRpZW1wbyA9IGFzLm51bWVyaWMoZGlmZnRpbWUoZnV0dXJlX2RhdGUsIG1pbihoaXN0X2RhdGEkVHJ4X0ZlY2hhKSwgdW5pdHMgPSAiZGF5cyIpKSAvIDMwCiAgICAgICkKICAgIAogICAgYXZnX2ZlYXR1cmVzJFByZWNpb19GaW5hbF9Vbml0YXJpbyA8LSBmdXR1cmVfcHJpY2UKICAgIGF2Z19mZWF0dXJlcyRUcnhfRmVjaGEgPC0gZnV0dXJlX2RhdGUKICAgIAogICAgZnV0dXJlX2ZlYXR1cmVzIDwtIHJiaW5kKGZ1dHVyZV9mZWF0dXJlcywgYXZnX2ZlYXR1cmVzKQogIH0KICAKICBpZiAoIWlzLm51bGwoYXJtYV9tb2RlbCkpIHsKICAgIGFybWFfZm9yZWNhc3QgPC0gZm9yZWNhc3QoYXJtYV9tb2RlbCwgaCA9IG5yb3cob3B0aW1hbF9wcmljZXMpKQogICAgZnV0dXJlX2RhdGEkVmVudGFfQVJNQSA8LSBhcy5udW1lcmljKGFybWFfZm9yZWNhc3QkbWVhbikKICAgIHJlZl9wcmljZSA8LSBtZWRpYW4oaGlzdF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbywgbmEucm0gPSBUUlVFKQogICAgZWxhc3RpY2l0eSA8LSAxLjUKICAgIGZ1dHVyZV9kYXRhJFZlbnRhX0FSTUFfQWp1c3RhZGEgPC0gZnV0dXJlX2RhdGEkVmVudGFfQVJNQSAqIAogICAgICAocmVmX3ByaWNlIC8gZnV0dXJlX2RhdGEkUHJlY2lvX0ZpbmFsX1VuaXRhcmlvKV5lbGFzdGljaXR5CiAgfQogIAogIGlmICghaXMubnVsbChyZWdfbW9kZWwpKSB7CiAgICB0cnlDYXRjaCh7CiAgICAgIGZ1dHVyZV9kYXRhJFZlbnRhX1JlZ0xpbmVhbCA8LSBwcmVkaWN0KHJlZ19tb2RlbCwgbmV3ZGF0YSA9IGZ1dHVyZV9mZWF0dXJlcykKICAgIH0sIGVycm9yID0gZnVuY3Rpb24oZSkgewogICAgICBmdXR1cmVfZGF0YSRWZW50YV9SZWdMaW5lYWwgPC0gTkEKICAgIH0pCiAgfQogIAogIGlmICghaXMubnVsbChyZl9tb2RlbCkpIHsKICAgIHRyeUNhdGNoKHsKICAgICAgZnV0dXJlX2RhdGEkVmVudGFfUmFuZG9tRm9yZXN0IDwtIHByZWRpY3QocmZfbW9kZWwsIG5ld2RhdGEgPSBmdXR1cmVfZmVhdHVyZXMpCiAgICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsKICAgICAgZnV0dXJlX2RhdGEkVmVudGFfUmFuZG9tRm9yZXN0IDwtIE5BCiAgICB9KQogIH0KICAKICBpZiAoIWlzLm51bGwoeGdiX21vZGVsKSkgewogICAgdHJ5Q2F0Y2goewogICAgICBmZWF0dXJlcyA8LSB4Z2JfbW9kZWwkZmVhdHVyZV9uYW1lcwogICAgICBpZiAoaXMubnVsbChmZWF0dXJlcykpIHsKICAgICAgICBmZWF0dXJlcyA8LSBzZXRkaWZmKG5hbWVzKGZ1dHVyZV9mZWF0dXJlcyksICJWZW50YSIpCiAgICAgIH0KICAgICAgeGdiX21hdHJpeCA8LSBhcy5tYXRyaXgoZnV0dXJlX2ZlYXR1cmVzWywgZmVhdHVyZXMsIGRyb3AgPSBGQUxTRV0pCiAgICAgIGZ1dHVyZV9kYXRhJFZlbnRhX1hHQm9vc3QgPC0gcHJlZGljdCh4Z2JfbW9kZWwsIHhnYl9tYXRyaXgpCiAgICB9LCBlcnJvciA9IGZ1bmN0aW9uKGUpIHsKICAgICAgZnV0dXJlX2RhdGEkVmVudGFfWEdCb29zdCA8LSBOQQogICAgfSkKICB9CiAgCiAgYXZnX2Nvc3RfcGVyX3VuaXQgPC0gbWVkaWFuKGhpc3RfZGF0YSRDb3N0b19WZW50YSAvIGhpc3RfZGF0YSRDYW50LCBuYS5ybSA9IFRSVUUpCiAgCiAgZm9yIChtb2RlbCBpbiBjKCJBUk1BX0FqdXN0YWRhIiwgIlJlZ0xpbmVhbCIsICJSYW5kb21Gb3Jlc3QiLCAiWEdCb29zdCIpKSB7CiAgICB2Y29sIDwtIHBhc3RlMCgiVmVudGFfIiwgbW9kZWwpCiAgICBpZiAodmNvbCAlaW4lIG5hbWVzKGZ1dHVyZV9kYXRhKSkgewogICAgICB1Y29sIDwtIHBhc3RlMCgiVW5pZGFkZXNfIiwgbW9kZWwpCiAgICAgIGNjb2wgPC0gcGFzdGUwKCJDb3N0b18iLCBtb2RlbCkKICAgICAgbWNvbCA8LSBwYXN0ZTAoIk1hcmdlbl8iLCBtb2RlbCkKICAgICAgCiAgICAgIGZ1dHVyZV9kYXRhW1t1Y29sXV0gPC0gZnV0dXJlX2RhdGFbW3Zjb2xdXSAvIGZ1dHVyZV9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbwogICAgICBmdXR1cmVfZGF0YVtbY2NvbF1dIDwtIGZ1dHVyZV9kYXRhW1t1Y29sXV0gKiBhdmdfY29zdF9wZXJfdW5pdAogICAgICBmdXR1cmVfZGF0YVtbbWNvbF1dIDwtIGZ1dHVyZV9kYXRhW1t2Y29sXV0gLSBmdXR1cmVfZGF0YVtbY2NvbF1dCiAgICB9CiAgfQogIAogIHByZWRfY29scyA8LSBjKCJWZW50YV9BUk1BX0FqdXN0YWRhIiwgIlZlbnRhX1JlZ0xpbmVhbCIsICJWZW50YV9SYW5kb21Gb3Jlc3QiLCAiVmVudGFfWEdCb29zdCIpCiAgcHJlZF9jb2xzIDwtIHByZWRfY29sc1twcmVkX2NvbHMgJWluJSBuYW1lcyhmdXR1cmVfZGF0YSldCiAgCiAgdHJ5Q2F0Y2goewogICAgaWYgKGxlbmd0aChwcmVkX2NvbHMpID4gMCAmJiBuY29sKGZ1dHVyZV9kYXRhWywgcHJlZF9jb2xzLCBkcm9wID0gRkFMU0VdKSA+IDApIHsKICAgICAgZnV0dXJlX2RhdGEkVmVudGFfQ29uc2Vuc28gPC0gcm93TWVhbnMoZnV0dXJlX2RhdGFbLCBwcmVkX2NvbHMsIGRyb3AgPSBGQUxTRV0sIG5hLnJtID0gVFJVRSkKICAgICAgZnV0dXJlX2RhdGEkVW5pZGFkZXNfQ29uc2Vuc28gPC0gZnV0dXJlX2RhdGEkVmVudGFfQ29uc2Vuc28gLyBmdXR1cmVfZGF0YSRQcmVjaW9fRmluYWxfVW5pdGFyaW8KICAgICAgZnV0dXJlX2RhdGEkQ29zdG9fQ29uc2Vuc28gPC0gZnV0dXJlX2RhdGEkVW5pZGFkZXNfQ29uc2Vuc28gKiBhdmdfY29zdF9wZXJfdW5pdAogICAgICBmdXR1cmVfZGF0YSRNYXJnZW5fQ29uc2Vuc28gPC0gZnV0dXJlX2RhdGEkVmVudGFfQ29uc2Vuc28gLSBmdXR1cmVfZGF0YSRDb3N0b19Db25zZW5zbwogICAgfQogIH0sIGVycm9yID0gZnVuY3Rpb24oZSkgewogICAgd2FybmluZyhwYXN0ZSgiTm8gc2UgcHVkbyBjYWxjdWxhciBlbCBjb25zZW5zbyBwYXJhIHByb2R1Y3RvIiwgcHJvZHVjdF9pZCwgIjoiLCBlJG1lc3NhZ2UpKQogIH0pCiAgCiAgcmV0dXJuKGZ1dHVyZV9kYXRhKQp9CgpyZXN1bHRhZG9zX2Z1dHVyb3NfbGlzdGEgPC0gbGlzdCgpCgpmb3IgKGlkIGluIHByb2R1Y3Rvc19pZHMpIHsKICBjYXQoIkludGVncmFuZG8gbW9kZWxvcyBwYXJhIHByb2R1Y3RvOiIsIGlkLCAiXG4iKQoKICByZXN1bHRhZG8gPC0gaW50ZWdyYXRlX3dpdGhfZXhpc3RpbmdfbW9kZWxzKAogICAgZGF0YSA9IGRhdG9zLAogICAgcHJvZHVjdF9pZCA9IGlkLAogICAgcHJpY2Vfb3B0X3Jlc3VsdHMgPSBwcmVjaW9zX29wdGltb3NfbGlzdGEsCiAgICBhcm1hX21vZGVsID0gbW9kZWxvc19hcm1hX2xpc3RhW1thcy5jaGFyYWN0ZXIoaWQpXV0sCiAgICByZWdfbW9kZWwgPSBtb2RlbG9zX3JlZ19saXN0YVtbYXMuY2hhcmFjdGVyKGlkKV1dLAogICAgcmZfbW9kZWwgPSBtb2RlbG9zX3JmX2xpc3RhW1thcy5jaGFyYWN0ZXIoaWQpXV0sCiAgICB4Z2JfbW9kZWwgPSBtb2RlbG9zX3hnYl9saXN0YVtbYXMuY2hhcmFjdGVyKGlkKV1dCiAgKQoKICByZXN1bHRhZG9zX2Z1dHVyb3NfbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXSA8LSByZXN1bHRhZG8KfQpgYGAKCiMjIyBQaXBlbGluZSBjb3JyZWN0bwpgYGB7cn0KY29ycmVnaXJfZm9ybWF0b19mZWNoYXMgPC0gZnVuY3Rpb24oZGF0b3MpIHsKICBpZiAoIlRyeF9GZWNoYSIgJWluJSBjb2xuYW1lcyhkYXRvcykpIHsKICAgIGRhdG9zJFRyeF9GZWNoYV9PcmlnaW5hbCA8LSBkYXRvcyRUcnhfRmVjaGEKCiAgICBpZiAoaXMuY2hhcmFjdGVyKGRhdG9zJFRyeF9GZWNoYSkgJiYKICAgICAgICBhbnkoZ3JlcGwoIl5cXGR7N30tXFxkezJ9LVxcZHsyfSQiLCBkYXRvcyRUcnhfRmVjaGEpKSkgewoKICAgICAgY2F0KCJDb3JyaWdpZW5kbyBmb3JtYXRvIGRlIGZlY2hhcyBleHRyYcOxby4uLlxuIikKCiAgICAgIGRhdG9zJFRyeF9GZWNoYSA8LSBzYXBwbHkoZGF0b3MkVHJ4X0ZlY2hhLCBmdW5jdGlvbihmZWNoYSkgewogICAgICAgIGlmIChpcy5uYShmZWNoYSkgfHwgIWlzLmNoYXJhY3RlcihmZWNoYSkpIHJldHVybihOQSkKCiAgICAgICAgcGFydGVzIDwtIHN0cnNwbGl0KGZlY2hhLCAiLSIpW1sxXV0KICAgICAgICBpZiAobGVuZ3RoKHBhcnRlcykgPT0gMykgewogICAgICAgICAgZmVjaGFfY29ycmVnaWRhIDwtIHBhc3RlKCIyMDIzIiwgcGFydGVzWzJdLCBwYXJ0ZXNbM10sIHNlcCA9ICItIikKICAgICAgICAgIHJldHVybihmZWNoYV9jb3JyZWdpZGEpCiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgIHJldHVybihOQSkKICAgICAgICB9CiAgICAgIH0pCgogICAgICBkYXRvcyRUcnhfRmVjaGEgPC0gYXMuRGF0ZShkYXRvcyRUcnhfRmVjaGEpCiAgICAgIGNhdCgiRmVjaGFzIGNvcnJlZ2lkYXMgZXhpdG9zYW1lbnRlLlxuIikKICAgIH0gZWxzZSBpZiAoIWluaGVyaXRzKGRhdG9zJFRyeF9GZWNoYSwgIkRhdGUiKSkgewogICAgICBjYXQoIkludGVudGFuZG8gY29udmVydGlyIGZlY2hhcyBhIGZvcm1hdG8gRGF0ZS4uLlxuIikKICAgICAgZGF0b3MkVHJ4X0ZlY2hhIDwtIGFzLkRhdGUoZGF0b3MkVHJ4X0ZlY2hhKQogICAgfQogIH0KICByZXR1cm4oZGF0b3MpCn0KCiMgQXBsaWNhciBsYSBjb3JyZWNjacOzbiBhIHR1IGRhdGFmcmFtZSBhbnRlcyBkZSB1c2FybG8KZGF0b3NfZmlsdHJhZG9zIDwtIGNvcnJlZ2lyX2Zvcm1hdG9fZmVjaGFzKGRhdG9zX2ZpbHRyYWRvcykKYGBgCgpgYGB7cn0KZGF0ZXNfZnV0dXJlIDwtIHNlcS5EYXRlKGFzLkRhdGUoIjIwMjMtMDEtMDEiKSwgYnkgPSAibW9udGgiLCBsZW5ndGgub3V0ID0gNikKcHJlY2lvc19vcHRpbW9zX2xpc3RhIDwtIGxpc3QoKQoKZm9yIChpZCBpbiBwcm9kdWN0b3NfaWRzKSB7CiAgY2F0KCJFc3RpbWFuZG8gcHJlY2lvcyDDs3B0aW1vcyBwYXJhIHByb2R1Y3RvOiIsIGlkLCAiXG4iKQoKICBtb2RlbG9fcHJlY2lvIDwtIG1vZGVsb3NfcHJlY2lvX2xpc3RhW1thcy5jaGFyYWN0ZXIoaWQpXV0KCiAgaWYgKCFpcy5udWxsKG1vZGVsb19wcmVjaW8pKSB7CiAgICBwcmVjaW9zX29wdGltb3NfbGlzdGFbW2FzLmNoYXJhY3RlcihpZCldXSA8LSBlc3RpbWF0ZV9vcHRpbWFsX3ByaWNlcygKICAgICAgZGF0YSA9IGRhdG9zX2ZpbHRyYWRvcywKICAgICAgcHJvZHVjdF9pZCA9IGlkLAogICAgICBwcmljZV9tb2RlbHMgPSBtb2RlbG9fcHJlY2lvLAogICAgICBmdXR1cmVfZGF0ZXMgPSBkYXRlc19mdXR1cmUKICAgICkKICB9Cn0KYGBgCgpgYGB7cn0KZm9yIChpZCBpbiBuYW1lcyhwcmVjaW9zX29wdGltb3NfbGlzdGEpKSB7CiAgZGZfb3B0aW1vIDwtIHByZWNpb3Nfb3B0aW1vc19saXN0YVtbaWRdXSRwcmVjaW9zX29wdGltb3MKCiAgaWYgKCFpbmhlcml0cyhkZl9vcHRpbW8kVHJ4X0ZlY2hhLCAiRGF0ZSIpKSB7CiAgICBkZl9vcHRpbW8kVHJ4X0ZlY2hhIDwtIGFzLkRhdGUoZGZfb3B0aW1vJFRyeF9GZWNoYSkKICB9CgogIHAgPC0gZ2dwbG90KGRmX29wdGltbywgYWVzKHggPSBUcnhfRmVjaGEsIHkgPSBQcmVjaW9fT3B0aW1hbCkpICsKICAgIGdlb21fbGluZShjb2xvciA9ICIjMWY3N2I0IiwgbGluZXdpZHRoID0gMS4yKSArCiAgICBnZW9tX3BvaW50KGNvbG9yID0gIiMxZjc3YjQiLCBzaXplID0gMikgKwogICAgbGFicygKICAgICAgdGl0bGUgPSBwYXN0ZSgiUHJlY2lvIMOTcHRpbW8gcG9yIE1lcyAtIFByb2R1Y3RvIiwgaWQpLAogICAgICB4ID0gIkZlY2hhIiwKICAgICAgeSA9ICJQcmVjaW8gw5NwdGltbyIKICAgICkgKwogICAgc2NhbGVfeF9kYXRlKGRhdGVfbGFiZWxzID0gIiViICVZIiwgZGF0ZV9icmVha3MgPSAiMSBtb250aCIpICsKICAgIHRoZW1lX21pbmltYWwoYmFzZV9zaXplID0gMTIpICsKICAgIHRoZW1lKAogICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLAogICAgICBheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdCA9IDEpCiAgICApCgogIHByaW50KHApCn0KYGBgCgpgYGB7cn0KIyBGdW5jacOzbiBwYXJhIGNvcnJlciBvcHRpbWl6YWNpw7NuIGRlIHByZWNpb3MgcGFyYSB0b2RvcyBsb3MgcHJvZHVjdG9zCnJ1bl9wcmljZV9vcHRpbWl6YXRpb24gPC0gZnVuY3Rpb24oZGF0YSwgcHJvZHVjdF9pZHMsIGZ1dHVyZV9kYXRlcyA9IE5VTEwsIG1vZGVsb3NfcHJlY2lvX2xpc3RhID0gTlVMTCkgewogIGlmIChpcy5udWxsKGZ1dHVyZV9kYXRlcykpIHsKICAgIGZ1dHVyZV9kYXRlcyA8LSBzZXEuRGF0ZShTeXMuRGF0ZSgpLCBieSA9ICJtb250aCIsIGxlbmd0aC5vdXQgPSA2KQogIH0KICAKICBwcmVjaW9zX29wdGltb3NfbGlzdGEgPC0gbGlzdCgpCiAgCiAgZm9yIChpZCBpbiBwcm9kdWN0X2lkcykgewogICAgY2F0KCJFc3RpbWFuZG8gcHJlY2lvcyDDs3B0aW1vcyBwYXJhIHByb2R1Y3RvOiIsIGlkLCAiXG4iKQogICAgCiAgICBwcmljZV9tb2RlbCA8LSBOVUxMCiAgICBpZiAoIWlzLm51bGwobW9kZWxvc19wcmVjaW9fbGlzdGEpKSB7CiAgICAgIHByaWNlX21vZGVsIDwtIG1vZGVsb3NfcHJlY2lvX2xpc3RhW1thcy5jaGFyYWN0ZXIoaWQpXV0KICAgIH0KICAgIAogICAgcHJlY2lvc19vcHRpbW9zX2xpc3RhW1thcy5jaGFyYWN0ZXIoaWQpXV0gPC0gZXN0aW1hdGVfb3B0aW1hbF9wcmljZXMoCiAgICAgIGRhdGEgPSBkYXRhLAogICAgICBwcm9kdWN0X2lkID0gaWQsCiAgICAgIHByaWNlX21vZGVscyA9IHByaWNlX21vZGVsLAogICAgICBmdXR1cmVfZGF0ZXMgPSBmdXR1cmVfZGF0ZXMKICAgICkKICB9CiAgCiAgcmV0dXJuKHByZWNpb3Nfb3B0aW1vc19saXN0YSkKfQoKCiMgRnVuY2nDs24gcHJpbmNpcGFsIHF1ZSBpbnRlZ3JhIHRvZG8gZWwgcGlwZWxpbmUKcnVuX2NvbXBsZXRlX2FuYWx5c2lzIDwtIGZ1bmN0aW9uKGRhdGEsIHRvcF9pZHMsIG1vZGVsb3NfYXJtYSwgbW9kZWxvc19yZWcsIG1vZGVsb3NfcmYsIG1vZGVsb3NfeGdiLCBtb2RlbG9zX3ByZWNpb19saXN0YSA9IE5VTEwpIHsKICAjIDEuIEVqZWN1dGFyIG9wdGltaXphY2nDs24gZGUgcHJlY2lvcyBwYXJhIHRvZG9zIGxvcyBwcm9kdWN0b3MKICBhbGxfcmVzdWx0cyA8LSBydW5fcHJpY2Vfb3B0aW1pemF0aW9uKGRhdGEsIHRvcF9pZHMsIG1vZGVsb3NfcHJlY2lvX2xpc3RhID0gbW9kZWxvc19wcmVjaW9fbGlzdGEpCiAgCiAgIyAyLiBJbnRlZ3JhciBjb24gbW9kZWxvcyBleGlzdGVudGVzIHBhcmEgY2FkYSBwcm9kdWN0bwogIGludGVncmF0ZWRfcmVzdWx0cyA8LSBsaXN0KCkKICAKICBmb3IgKGkgaW4gc2VxX2Fsb25nKHRvcF9pZHMpKSB7CiAgICBwaWQgPC0gdG9wX2lkc1tpXQogICAgcGlkX3N0ciA8LSBhcy5jaGFyYWN0ZXIocGlkKQogICAgCiAgICBhcm1hX21vZGVsIDwtIGlmKGxlbmd0aChtb2RlbG9zX2FybWEpID49IGkpIG1vZGVsb3NfYXJtYVtbaV1dIGVsc2UgTlVMTAogICAgcmVnX21vZGVsIDwtIGlmKGxlbmd0aChtb2RlbG9zX3JlZykgPj0gaSkgbW9kZWxvc19yZWdbW2ldXSBlbHNlIE5VTEwKICAgIHJmX21vZGVsIDwtIGlmKGxlbmd0aChtb2RlbG9zX3JmKSA+PSBpKSBtb2RlbG9zX3JmW1tpXV0gZWxzZSBOVUxMCiAgICB4Z2JfbW9kZWwgPC0gaWYobGVuZ3RoKG1vZGVsb3NfeGdiKSA+PSBpKSBtb2RlbG9zX3hnYltbaV1dIGVsc2UgTlVMTAogICAgCiAgICBmdXR1cmVfcHJlZGljdGlvbnMgPC0gaW50ZWdyYXRlX3dpdGhfZXhpc3RpbmdfbW9kZWxzKAogICAgICBkYXRhID0gZGF0YSwKICAgICAgcHJvZHVjdF9pZCA9IHBpZCwKICAgICAgcHJpY2Vfb3B0X3Jlc3VsdHMgPSBhbGxfcmVzdWx0cywKICAgICAgYXJtYV9tb2RlbCA9IGFybWFfbW9kZWwsCiAgICAgIHJlZ19tb2RlbCA9IHJlZ19tb2RlbCwKICAgICAgcmZfbW9kZWwgPSByZl9tb2RlbCwKICAgICAgeGdiX21vZGVsID0geGdiX21vZGVsCiAgICApCiAgICAKICAgIGludGVncmF0ZWRfcmVzdWx0c1tbcGlkX3N0cl1dIDwtIGZ1dHVyZV9wcmVkaWN0aW9ucwogICAgCiAgICBpZiAobnJvdyhmdXR1cmVfcHJlZGljdGlvbnMpID4gMCkgewogICAgICBwX3NhbGVzIDwtIGdncGxvdChmdXR1cmVfcHJlZGljdGlvbnMpCiAgICAgIAogICAgICBpZiAoIlZlbnRhX0FSTUFfQWp1c3RhZGEiICVpbiUgbmFtZXMoZnV0dXJlX3ByZWRpY3Rpb25zKSkgewogICAgICAgIHBfc2FsZXMgPC0gcF9zYWxlcyArIGdlb21fcG9pbnQoYWVzKHggPSBGZWNoYSwgeSA9IFZlbnRhX0FSTUFfQWp1c3RhZGEsIGNvbG9yID0gIkFSTUEiKSwgc2l6ZSA9IDMsIG5hLnJtID0gVFJVRSkKICAgICAgfQogICAgICBpZiAoIlZlbnRhX1JlZ0xpbmVhbCIgJWluJSBuYW1lcyhmdXR1cmVfcHJlZGljdGlvbnMpKSB7CiAgICAgICAgcF9zYWxlcyA8LSBwX3NhbGVzICsgZ2VvbV9wb2ludChhZXMoeCA9IEZlY2hhLCB5ID0gVmVudGFfUmVnTGluZWFsLCBjb2xvciA9ICJSZWdyZXNpw7NuIExpbmVhbCIpLCBzaXplID0gMywgbmEucm0gPSBUUlVFKQogICAgICB9CiAgICAgIGlmICgiVmVudGFfUmFuZG9tRm9yZXN0IiAlaW4lIG5hbWVzKGZ1dHVyZV9wcmVkaWN0aW9ucykpIHsKICAgICAgICBwX3NhbGVzIDwtIHBfc2FsZXMgKyBnZW9tX3BvaW50KGFlcyh4ID0gRmVjaGEsIHkgPSBWZW50YV9SYW5kb21Gb3Jlc3QsIGNvbG9yID0gIlJhbmRvbSBGb3Jlc3QiKSwgc2l6ZSA9IDMsIG5hLnJtID0gVFJVRSkKICAgICAgfQogICAgICBpZiAoIlZlbnRhX1hHQm9vc3QiICVpbiUgbmFtZXMoZnV0dXJlX3ByZWRpY3Rpb25zKSkgewogICAgICAgIHBfc2FsZXMgPC0gcF9zYWxlcyArIGdlb21fcG9pbnQoYWVzKHggPSBGZWNoYSwgeSA9IFZlbnRhX1hHQm9vc3QsIGNvbG9yID0gIlhHQm9vc3QiKSwgc2l6ZSA9IDMsIG5hLnJtID0gVFJVRSkKICAgICAgfQogICAgICBpZiAoIlZlbnRhX0NvbnNlbnNvIiAlaW4lIG5hbWVzKGZ1dHVyZV9wcmVkaWN0aW9ucykpIHsKICAgICAgICBwX3NhbGVzIDwtIHBfc2FsZXMgKyBnZW9tX2xpbmUoYWVzKHggPSBGZWNoYSwgeSA9IFZlbnRhX0NvbnNlbnNvLCBjb2xvciA9ICJDb25zZW5zbyIpLCBzaXplID0gMS41KQogICAgICB9CiAgICAgIAogICAgICBwX3NhbGVzIDwtIHBfc2FsZXMgKwogICAgICAgIGxhYnMoCiAgICAgICAgICB0aXRsZSA9IHBhc3RlKCJQcmVkaWNjaW9uZXMgZGUgdmVudGFzIGNvbiBwcmVjaW9zIMOzcHRpbW9zIC0gUHJvZHVjdG8iLCBwaWQpLAogICAgICAgICAgeCA9ICJGZWNoYSIsCiAgICAgICAgICB5ID0gIlZlbnRhcyBlc3RpbWFkYXMgKCQpIiwKICAgICAgICAgIGNvbG9yID0gIk1vZGVsbyIKICAgICAgICApICsKICAgICAgICB0aGVtZV9taW5pbWFsKCkgKwogICAgICAgIHRoZW1lKAogICAgICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwKICAgICAgICAgIGF4aXMudGl0bGUgPSBlbGVtZW50X3RleHQoZmFjZSA9ICJib2xkIiksCiAgICAgICAgICBsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIgogICAgICAgICkKICAgICAgCiAgICAgIHBfbWFyZ2lucyA8LSBnZ3Bsb3QoZnV0dXJlX3ByZWRpY3Rpb25zKQogICAgICAKICAgICAgaWYgKCJNYXJnZW5fQ29uc2Vuc28iICVpbiUgbmFtZXMoZnV0dXJlX3ByZWRpY3Rpb25zKSkgewogICAgICAgIHBfbWFyZ2lucyA8LSBwX21hcmdpbnMgKyAKICAgICAgICAgIGdlb21fY29sKGFlcyh4ID0gRmVjaGEsIHkgPSBNYXJnZW5fQ29uc2Vuc28pLCBmaWxsID0gInN0ZWVsYmx1ZSIsIHdpZHRoID0gMTUpICsKICAgICAgICAgIGdlb21fdGV4dChhZXMoeCA9IEZlY2hhLCB5ID0gTWFyZ2VuX0NvbnNlbnNvLCBsYWJlbCA9IHJvdW5kKE1hcmdlbl9Db25zZW5zbywgMCkpLAogICAgICAgICAgICAgICAgICAgIHZqdXN0ID0gLTAuNSwgc2l6ZSA9IDMuNSkKICAgICAgfQogICAgICAKICAgICAgcF9tYXJnaW5zIDwtIHBfbWFyZ2lucyArCiAgICAgICAgbGFicygKICAgICAgICAgIHRpdGxlID0gcGFzdGUoIk1hcmdlbiBlc3BlcmFkbyBjb24gcHJlY2lvcyDDs3B0aW1vcyAtIFByb2R1Y3RvIiwgcGlkKSwKICAgICAgICAgIHggPSAiRmVjaGEiLAogICAgICAgICAgeSA9ICJNYXJnZW4gZXN0aW1hZG8gKCQpIgogICAgICAgICkgKwogICAgICAgIHRoZW1lX21pbmltYWwoKSArCiAgICAgICAgdGhlbWUoCiAgICAgICAgICBwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpLAogICAgICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKQogICAgICAgICkKICAgICAgCiAgICAgIGFsbF9yZXN1bHRzW1twaWRfc3RyXV0kaW50ZWdyYXRlZF9wbG90cyA8LSBsaXN0KAogICAgICAgIHNhbGVzID0gcF9zYWxlcywKICAgICAgICBtYXJnaW5zID0gcF9tYXJnaW5zCiAgICAgICkKICAgIH0KICB9CiAgCiAgIyAzLiBWaXN1YWxpemFyIHJlc3VsdGFkb3MgY29tcGFyYXRpdm9zCiAgYWxsX29wdGltYWxfcHJpY2VzIDwtIGRhdGEuZnJhbWUoKQogIAogIGZvciAocGlkIGluIHRvcF9pZHMpIHsKICAgIHBpZF9zdHIgPC0gYXMuY2hhcmFjdGVyKHBpZCkKICAgIGlmIChwaWRfc3RyICVpbiUgbmFtZXMoYWxsX3Jlc3VsdHMpKSB7CiAgICAgIG9wdF9wcmljZXMgPC0gYWxsX3Jlc3VsdHNbW3BpZF9zdHJdXSRwcmVjaW9zX29wdGltb3MgJT4lCiAgICAgICAgbXV0YXRlKElEX0ludmVudGFyaW8gPSBwaWQpCiAgICAgIAogICAgICBhbGxfb3B0aW1hbF9wcmljZXMgPC0gcmJpbmQoYWxsX29wdGltYWxfcHJpY2VzLCBvcHRfcHJpY2VzKQogICAgfQogIH0KICAKICBwX2NvbXBhcmlzb24gPC0gZ2dwbG90KGFsbF9vcHRpbWFsX3ByaWNlcywKICAgICAgICAgICAgICAgICAgICAgICAgIGFlcyh4ID0gVHJ4X0ZlY2hhLCB5ID0gUHJlY2lvX09wdGltYWwsIGNvbG9yID0gZmFjdG9yKElEX0ludmVudGFyaW8pKSkgKwogICAgZ2VvbV9saW5lKHNpemUgPSAxLjIpICsKICAgIGdlb21fcG9pbnQoc2l6ZSA9IDMpICsKICAgIGxhYnMoCiAgICAgIHRpdGxlID0gIkNvbXBhcmFjacOzbiBkZSBQcmVjaW9zIMOTcHRpbW9zIHBvciBQcm9kdWN0byIsCiAgICAgIHggPSAiRmVjaGEiLAogICAgICB5ID0gIlByZWNpbyDDk3B0aW1vIiwKICAgICAgY29sb3IgPSAiSUQgUHJvZHVjdG8iCiAgICApICsKICAgIHRoZW1lX21pbmltYWwoKSArCiAgICB0aGVtZSgKICAgICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwKICAgICAgYXhpcy50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiKSwKICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIKICAgICkKICAKICBtZXRyaWNhc19vcHRpbWFzIDwtIGRhdGEuZnJhbWUoKQogIAogIGZvciAocGlkIGluIHRvcF9pZHMpIHsKICAgIHBpZF9zdHIgPC0gYXMuY2hhcmFjdGVyKHBpZCkKICAgIGlmIChwaWRfc3RyICVpbiUgbmFtZXMoaW50ZWdyYXRlZF9yZXN1bHRzKSkgewogICAgICBwcmVkX2RhdGEgPC0gaW50ZWdyYXRlZF9yZXN1bHRzW1twaWRfc3RyXV0KICAgICAgCiAgICAgIGlmICgiTWFyZ2VuX0NvbnNlbnNvIiAlaW4lIG5hbWVzKHByZWRfZGF0YSkpIHsKICAgICAgICBtZXRyaWNzX3JvdyA8LSBkYXRhLmZyYW1lKAogICAgICAgICAgSURfSW52ZW50YXJpbyA9IHBpZCwKICAgICAgICAgIFByZWNpb19Qcm9tZWRpbyA9IG1lYW4ocHJlZF9kYXRhJFByZWNpb19GaW5hbF9Vbml0YXJpbywgbmEucm0gPSBUUlVFKSwKICAgICAgICAgIFZlbnRhX1RvdGFsID0gc3VtKHByZWRfZGF0YSRWZW50YV9Db25zZW5zbywgbmEucm0gPSBUUlVFKSwKICAgICAgICAgIE1hcmdlbl9Ub3RhbCA9IHN1bShwcmVkX2RhdGEkTWFyZ2VuX0NvbnNlbnNvLCBuYS5ybSA9IFRSVUUpLAogICAgICAgICAgTWFyZ2VuX1BvcmNlbnR1YWwgPSAxMDAgKiBzdW0ocHJlZF9kYXRhJE1hcmdlbl9Db25zZW5zbywgbmEucm0gPSBUUlVFKSAvCiAgICAgICAgICAgIHN1bShwcmVkX2RhdGEkVmVudGFfQ29uc2Vuc28sIG5hLnJtID0gVFJVRSkKICAgICAgICApCiAgICAgICAgCiAgICAgICAgbWV0cmljYXNfb3B0aW1hcyA8LSByYmluZChtZXRyaWNhc19vcHRpbWFzLCBtZXRyaWNzX3JvdykKICAgICAgfQogICAgfQogIH0KICAKICByZXR1cm4obGlzdCgKICAgIHJlc3VsdGFkb3MgPSBhbGxfcmVzdWx0cywKICAgIGludGVncmFjaW9uID0gaW50ZWdyYXRlZF9yZXN1bHRzLAogICAgcHJlY2lvc19vcHRpbW9zID0gYWxsX29wdGltYWxfcHJpY2VzLAogICAgbWV0cmljYXNfb3B0aW1hcyA9IG1ldHJpY2FzX29wdGltYXMsCiAgICBncmFmaWNvX2NvbXBhcmF0aXZvID0gcF9jb21wYXJpc29uCiAgKSkKfQoKCiMgRWplY3V0YXIgZWwgYW7DoWxpc2lzIGNvbXBsZXRvCnJlc3VsdGFkb19jb21wbGV0byA8LSBydW5fY29tcGxldGVfYW5hbHlzaXMoCiAgZGF0YSA9IGRhdG9zLAogIHRvcF9pZHMgPSBwcm9kdWN0b3NfaWRzLAogIG1vZGVsb3NfYXJtYSA9IG1vZGVsb3NfYXJtYV9saXN0YSwKICBtb2RlbG9zX3JlZyA9IG1vZGVsb3NfcmVnX2xpc3RhLAogIG1vZGVsb3NfcmYgPSBtb2RlbG9zX3JmX2xpc3RhLAogIG1vZGVsb3NfeGdiID0gbW9kZWxvc194Z2JfbGlzdGEsCiAgbW9kZWxvc19wcmVjaW9fbGlzdGEgPSBtb2RlbG9zX3ByZWNpb19saXN0YSAjIFBhc2EgZXN0YSBsaXN0YSBzaSBsYSB0aWVuZXMsIG8gTlVMTAopCmBgYAoKIyMjIEdyw6FmaWNvIGNvbXBhcmF0aXZvIGRlIHByZWNpb3Mgw7NwdGltb3MgcG9yIHByb2R1Y3RvOgoKYGBge3J9CiMgTW9zdHJhciBtw6l0cmljYXMgc2kgZXN0w6FzIGVuIG1vZG8gaW50ZXJhY3Rpdm8KaWYgKGludGVyYWN0aXZlKCkpIFZpZXcocmVzdWx0YWRvX2NvbXBsZXRvJG1ldHJpY2FzX29wdGltYXMpCgpjYXQoIkdyw6FmaWNvIGNvbXBhcmF0aXZvIGRlIHByZWNpb3Mgw7NwdGltb3MgcG9yIHByb2R1Y3RvOlxuIikKYGBgCgpgYGB7cn0KcHJpbnQocmVzdWx0YWRvX2NvbXBsZXRvJGdyYWZpY29fY29tcGFyYXRpdm8pCmBgYAoKIyBQcmVkaWNpw7NuIGRlIHZlbnRhcyBjb24gcHJlY2lvcyBvcHRpbW9zIHBvciBwcm9kdWN0bwoKCmBgYHtyfQpjYXQoIkdyw6FmaWNvcyBpbmRpdmlkdWFsZXMgcG9yIHByb2R1Y3RvOlxuIikKYGBgCgoKYGBge3J9CmZvciAocGlkIGluIG5hbWVzKHJlc3VsdGFkb19jb21wbGV0byRyZXN1bHRhZG9zKSkgewogIHBsb3RzIDwtIHJlc3VsdGFkb19jb21wbGV0byRyZXN1bHRhZG9zW1twaWRdXSRpbnRlZ3JhdGVkX3Bsb3RzCiAgaWYgKCFpcy5udWxsKHBsb3RzKSkgewogICAgY2F0KHBhc3RlMCgiIyMgUHJvZHVjdG86ICIsIHBpZCwgIlxuXG4iKSkKICAgIHByaW50KHBsb3RzJHNhbGVzKQogICAgcHJpbnQocGxvdHMkbWFyZ2lucykKICAgIAogICAgIyBTZXBhcmFjacOzbiB2aXN1YWwgb3BjaW9uYWwKICAgIGNhdCgiXG4tLS1cblxuIikKICB9Cn0KYGBgCgoKCgoKCgoKCgoK