REPORTE TRABAJO 1: SOLUCIÓN DE PROBLEMAS DE OPTIMIZACIÓN CON MÉTODOS HEURÍSTICOS

REDES NEURONALES Y ALGORITMOS BIOINSPIRADOS




Presentado por:

Leonardo Federico Corona Torres
David Escobar Ruiz
Johan Sebastian Robles Rincón
Sebastián Soto Arcila



Profesor: Juan David Ospina Arango

Monitor: Andrés Mauricio Zapata Rincón


University Logo

Universidad Nacional de Colombia
Facultad de Minas
Ingeniería de Sistemas e Informática

02 de mayo de 2025

Introducción

El presente trabajo trata algunos de los métodos más relevantes de optimización numérica basada en manipulaciones del gradiente de una función objetivo (función a optimizar) y también algoritmos heurísticos basados en el comportamiento de la naturaleza, como lo son los algoritmos evolutivos. Todos estos métodos pueden llegar a ser útiles en distintos contextos en los que se busca optimizar una función para algún fin, como encontrar la mejor ruta entre ciudades o optimizar una función de pérdida en un contexto de aprendizaje de máquina. Cada método tiene particularidades interesantes que se van a explorar de manera general por medio de una breve implementación y ejecución con dos funciones muy utiilizadas para probar algoritmos de optimización: la función de Rosenbrock y la función de Rastrigin.

Metodología

Para implementar y documentar la optimización se emplea R Markdown, combinando código R y texto explicativo. Se utilizan librerías para renderizar gráficos y animaciones, librerías para implementar los métodos heurísticos y librerías para manipular los datos.

# DESCOMENTAR PARA INSTALAR PAQUETES FALTANTES
#install.packages("GA")
#install.packages("tidyverse")
#install.packages("viridis")
#install.packages("ggplot2")
#install.packages("gganimate")
#install.packages("gifski")
#install.packages("dplyr")
#install.packages("tidyr")
#install.packages("purrr")
#install.packages("pso")
#install.packages("leaflet")
#install.packages("osrm")
#install.packages("sf")
#install.packages("gor")
# DESCOMENTAR PARA INSTALAR PAQUETES FALTANTES
#install.packages("GA")
#install.packages("tidyverse")
#install.packages("viridis")
#install.packages("ggplot2")
#install.packages("gganimate")
#install.packages("gifski")
#install.packages("dplyr")
#install.packages("tidyr")
#install.packages("purrr")
#install.packages("pso")
#install.packages("leaflet")
#install.packages("osrm")
#install.packages("sf")
#install.packages("gor")

Para los casos 3D (tres variables) es difícil visualizar directamente la función de 3 dimensiones (espacio de 4 variables), por lo que se mostrarán únicamente los puntos óptimos alcanzados.

Parte 1. Optimización numérica

1.1 Selección e implementación de las funciones de prueba

Para observar el comportamiento de cada uno de los métodos de optimización implementados, se optó por la selección de dos funciones de prueba estándar para evaluar algoritmos de optimización: Función de Rosenbrock y Función de Rastrigin. Debido a su complejidad, ambas funciones son indicadores útiles para comparar la eficacia de optimizadores como los AG.

Previo a la implementación de los algoritmos en el lenguaje R y previo a la recopilación de resultados y la derivación de conclusiones a partir de estos, el equipo decidió realizar una breve investigación general de lo que son las funciones de prueba para problemas de optimización. La investigación se realizó de tal forma que, una vez concluída, se pudieran comprender los conceptos hasta el punto de estar en las capacidades responder las siguientes preguntas implícitamente en una breve sección de este trabajo:

  • ¿Qué es una función de prueba en optimización?

  • ¿Para qué se utiliza una función de prueba en optimización?

  • ¿Cómo se pueden clasificar las funciones de prueba?

  • ¿Cuáles son algunas de las funciones de prueba más utilizadas?

A continuación se presenta dicha sección para luego continuar con el estudio más detallado de las funciones escogidas.

1.1.1 Breve explicación de las funciones de prueba para problemas de optimización

Según (Yang, 2010), una función de prueba es una función con unas propiedades especiales que permite probar si el rendimiento de un método de optimización implementado es aceptable bajo las condiciones especiales que impone la función de prueba. 

Esto es especialmente útil para verificar que el método sea eficiente bajo distintas condiciones en las que se espera que se implemente, como por ejemplo, casos en los que la función tiene múltiples mínimos y/o máximos locales.

Según (Molga, 2005), las funciones de prueba se pueden ubicar en una de las siguientes clases, todas siendo funciones continuas:

  • Clase 1: Unimodal, convexa, multidimensional.
  • Clase 2: Multimodal, dos dimensiones con un número pequeño de extremos locales.
  • Clase 3: Multimodal, dos dimensiones con un gran número de extremos locales.
  • Clase 4: Multimodal, multidimensional, con un número amplio de extremos locales.

En el caso de las funciones elegidas, la función de Rosenbrock se clasificaría como Clase 3 y la de Rastrigin como Clase 2.

Como ejemplo, en la Figura 1 se presentan algunas funciones de prueba que no se eligieron para este trabajo, pero que son igual de relevantes, importantes y comúnmente utilizadas en la práctica (Molga, 2005):

  • Función de De Jong (Clase 1).

  • Función de Griewangk (Clase 2).

  • Función de Langermann (Clase 3).

  • Función de Ackley (Clase 4).

1.1.2 Implementación de funciones de graficación

Se definen las funciones a usar para generar los gráficos de cada función de esta sección:

generate_function_graph <- function(func_2d, low_bound, upper_bound) {
  x1 <- seq(low_bound, upper_bound, length.out = 50)
  x2 <- seq(low_bound, upper_bound, length.out = 50)
  f_x <- outer(x1,x2, FUN = func_2d)
  
  colores        <- viridis::magma(n = 100, alpha = 0.7)
  z.facet.center <- (f_x[-1, -1] + f_x[-1, -ncol(f_x)] +
                       f_x[-nrow(f_x), -1] +
                       f_x[-nrow(f_x), -ncol(f_x)])/4
  z.facet.range  <- cut(z.facet.center, 100)
  
  par(mai = c(0,0,0,0))
  persp(x = x1, y = x2, z = f_x,
        shade = 0.8,
        phi = 30,
        theta = 30,
        col = colores[z.facet.range],
        axes = FALSE)
}

generate_level_curves <- function(func_2d, low_bound, upper_bound) {
  n_length <- 100
  x1 <- seq(low_bound, upper_bound, length.out = n_length)
  x2 <- seq(low_bound, upper_bound, length.out = n_length)
  X <- expand.grid(x1, x2)
  z <- func_2d(X[,1], X[,2])
  Z <- matrix(z, ncol = n_length, nrow = n_length)
  contour(
    x = x1,
    y = x2,
    z = Z,
    nlevels = 100,
    las = 1,
    xlab = expression(x[1]),
    ylab = expression(x[2]),
    main = expression(paste(
      "Función de Rosenbrock: ",
      f(x[1],x[2])==100*(x[2]-x[1]^2)^2+(1-x[1])^2
    )),
    sub = "Curvas de nivel de la función"
  )
}

generate_level_curves_2 <- function(func_2d, low_bound, upper_bound) {
  x1 <- seq(low_bound, upper_bound, length.out = 50)
  x2 <- seq(low_bound, upper_bound, length.out = 50)
  
  datos <- expand.grid(x1 = x1, x2 = x2)
  datos <- datos %>%
           mutate(f_x = map2_dbl(x1, x2, .f = func_2d))
  
  f_level_curves <- ggplot(data = datos, aes(x = x1, y = x2, z = f_x)) +
    geom_contour(aes(colour = stat(level)), bins = 30) +
    labs(title = "f(x1,x2) = x1^2 + x2^2") +
    theme_bw() +
    theme(legend.position = "none")
  return(f_level_curves)
}

generate_countour <- function(func_2d, low_bound, upper_bound) {
  n_length <- 100
  x1 <- seq(low_bound, upper_bound, length.out = n_length)
  x2 <- seq(low_bound, upper_bound, length.out = n_length)
  expand.grid(X1 = x1, X2 = x2) %>%
    mutate(Z = func_2d(X1, X2)) %>%
    ggplot(aes(X1, X2, z = Z)) +
    geom_contour() +
    geom_contour_filled()
}

1.1.3 Función de Rosenbrock

Es una función no convexa introducida por Rosenbrock en 1960​ (Wikipedia, s.f., Rosenbrock function). Su paisaje forma un valle curvo estrecho que dificulta la convergencia hacia el mínimo.

Definición en n dimensiones:

\(f(X) = \sum_{i=1}^{n-1}100(X_{i+1}-X_i^2)^2 + (1-X_i)^2\)

Definición en 2D:

\(f(x,y)= 100(y-x^2)^2 + (1-x)^2\)

Definición en 3D:

\(f(x,y,z)=100[(y-x^2)^2+(z-y^2)^2] + (1-x)^2 + (1-y)^2\)

Algunas características y propiedades importantes  son las siguientes:

  • Mínimo global:

    \(x_1,...,x_n =1, f(x_1,...,x_n) = 0\)

  • Dominio de búsqueda: 

    \(-\infty \leq X_i \leq \infty\)

    \(1 \leq i \leq n\)

  • Conocida también como la función banana de Rosenbrock.

  • Tiene forma de valle, el cual es trivial encontrarlo. Sin embargo, la convergencia al mínimo global es difícil.

Estas son algunas hipótesis y expectativas que se tuvieron para el rendimiento de cada método implementado con esta función:

  • Método de descenso de gradiente:

    • Si se ubica el punto inicial sobre las rectas tangentes con mayor gradiente a la función entonces el método llegará más rápido a un mínimo.
    • Por el contrario, si la recta tangente tiene una gradiente más cercana a cero, el método llegará más lentamente, es decir, requerirá de más iteraciones para llegar al punto mínimo.
  • Método de algoritmos evolutivos:

    • Los criterios para seleccionar los individuos a reproducir va a ser muy importante para el rendimiento del método.
  • Método de optimización de partículas:

    • Este método no va a tener muchas dificultades para encontrar el punto óptimo es la función de Rosenbrock.
    • El criterio para parar las iteraciones no va a cambiar mucho los resultados en este caso.
  • Método de evolución diferencial:

    • Se comporta similar a los métodos evolutivos en 2 dimensiones.

La implementación de la función de Rosenbrock en 2, 3 y N dimensiones se muestra a continuación:

generate_countour(f_rosenbrock_2d, -5, 5)

Las gráficas de la función de Rosenbrock en 2 y 3 dimensiones se muestran a continuación:

generate_countour(f_rosenbrock_2d, -5, 5)
generate_level_curves(f_rosenbrock_2d, -5, 5)
generate_function_graph(f_rosenbrock_2d, -5, 5)

1.1.4 Función de Rastrigin

La función Rastrigin (1974) es una función no convexa y altamente multimodal, con numerosos mínimos locales, lo que la hace difícil de optimizar​ (Wikipedia, s.f., Rastrigin function).

Presenta múltiples mínimos locales dispuestos de forma regular​, lo que lo convierte en un desafío típico para algoritmos de optimización.

Definición en n dimensiones:

\(f(X)=An + \sum_{i=1}^n[X_i^2 -A\cos(2\pi X_i)], A=10\)

Definición en 2D:

\(f(x,y)=x^2+y^2+A[2-\cos(2\pi x) - \cos(2\pi y)], A=10\)

Definición en 3D:

\(f(x,y)=x^2+y^2+z^2+A[3-\cos(2\pi x) - \cos(2\pi y)-\cos(2\pi z)], A=10\)

Algunas características y propiedades importantes son las siguientes:

  • Mínimo global:

    • \(f(0,...0)=0\)
  • Dominio de búsqueda:

    • \(-5.12 \leq X_i \leq 5.12\)
  • Particularmente, hallar el mínimo de esta función es un problema difícil debido a la larga cantidad de mínimos locales.

Estas son algunas hipótesis y expectativas que se tuvieron para el rendimiento de cada método implementado con esta función:

  • Método de descenso de gradiente:

    • El éxito del método en alcanzar un mínimo dependerá enormemente del punto inicial elegido debido a los múltiples mínimos locales que tiene esta función.
  • Método de algoritmos evolutivos:

    • Es importante hacer una buena representación de los individuos, ya que el rendimiento puede variar dependiendo de la selección.
    • La población inicial debe ser representativa, diferentes entre ellos y que no sean demasiados. Para esta función en particular encontrar la solución óptima va a depender mucho de la probabilidad de mutación
  • Método de optimización de partículas:

    • La ubicación de la nube de partículas va a afectar el rendimiento en esta función, ya que el algoritmo puede quedarse estancado en un mínimo local si la ubicación no es la mejor.
    • El criterio para detener el algoritmo debe estar bien ajustado, para evitar estancamientos.
  • Método de evolución diferencial:

    • Es más eficiente que los otros métodos evolutivos ya que su fuerte son las múltiples dimensiones.

La implementación de la función de Rastrigin en 2, 3 y N dimensiones se muestra a continuación:

# 2 Dimensiones 
f_rastrigin_2d <- function(x, y) {
  A = 10   
  f_value <- x^2 + y^2 + A*(2 - cos(2*pi*x) - cos(2*pi*y))   
  return(f_value) 
}  

# 3 Dimensiones 
f_rastrigin_3d <- function(x, y, z) {   
  A = 10   
  f_value <- x^2 + y^2 + z^2 + A*(3 - cos(2*pi*x) - cos(2*pi*y) - cos(2*pi*z))   
  return(f_value) 
}

# N Dimensiones
f_rastrigin <-function(x){
  A <- 10
  n <- length(x)
  z <- (A*n) + sum(x^2 - A*cos(2*pi*x))
  return(z)
}

Las gráficas de la función de Rastrigin en 2 y 3 dimensiones se muestran a continuación:

generate_countour(f_rastrigin_2d, -10, 10)
generate_level_curves(f_rastrigin_2d, -10, 10)
generate_level_curves_2(f_rastrigin_2d, -10, 10)
generate_function_graph(f_rastrigin_2d, -10, 10)

1.3 Método de descenso de gradiente

1.3.1 Implementación en R de descenso por gradiente

Implementación de derivada parcial

partial_dev <- function(x,i,fun,h=0.01){
    e <- x*0 # crea un vector de ceros de la misma longitud de x
    e[i] <- h
    y <- (fun(x+e)-fun(x-e))/(2*h)
  return(y)
}

Implementación del gradiente

num_grad <- function(x,fun,h=0.01){
  # x: punto del espacio donde se debe evaluar el gradiente
  # fun: función para la que se desea calcular el gradiente en x
  # h: es el tamaño de ventana para el cálculo de la derivada numérica
  d <- length(x)
  y <- mapply(FUN=partial_dev,i=1:d,MoreArgs=list(x=x,h=h,fun=fun))
  return(y)
}

Implementación de derivada del gradiente

deriv_grad <- function(x,fun,i=1,h=0.01){
  # x: punto en el que se evalúa el gradiente
  # fun: función para la cual se calcula la derivada del gradiente respecto a la íesima componente
  # i: i-ésima componente del vector x con respecto a la que se deriva
    e <- x*0 # crea un vector de ceros de la misma longitud de x
    e[i] <- h
    y <- (num_grad(x+e,fun=fun,h=h)-num_grad(x-e,fun=fun,h=h))/(2*h)
    return(y)
}

Implementación de matriz Hessiana

matriz_hessiana <- function(x,fun,h=0.01){
  # x: punto en el que se evalúa la matriz hessiana
  # fun: función a la que se le calcula la matriz hessiana en x
  # h: es el tamaño de ventana para el cálculo de la derivada numérica
  d <- length(x)
  y <- mapply(FUN=deriv_grad,i=1:d,MoreArgs=list(x=x,h=h,fun=fun),SIMPLIFY = TRUE)
  return(y)
}

Implementación completa de optimizador multivariado por descenso de gradiente

optimizador_mult_numdev <- function(x0,fun,max_eval=100,h=0.01,eta=0.01){
  x <- matrix(NA,ncol =length(x0), nrow = max_eval)
  x[1,] <- x0
  for (i in 2:max_eval){
    num_grad_fun <- num_grad(x[i-1,],fun,h)
    H <- matriz_hessiana(x[i-1,],fun,h)
    cambio <- - eta*solve(H)%*%num_grad_fun
    x[i,] <- x[i-1,] + cambio
    cambio_opt <- sqrt(sum((x[i-1,]-x[i,])^2))
    if (cambio_opt<0.00001){
      break
    }
  }
  return(x[1:i,])
}

1.3.2 Optimización de la función de Rosenbrock en 2 dimensiones

# Ejecución del método
sol_rosen <- optimizador_mult_numdev(f_rosenbrock,x0=c(-4,-4),eta=1)

# Graficación del proceso de optimización
n_length <- 100
x1 <- seq(-5, 5, length.out = n_length)
x2 <- seq(-5, 5, length.out = n_length)
X <- expand.grid(x1, x2)
z <- f_rosenbrock_2d(X[,1], X[,2])
Z <- matrix(z, ncol = n_length, nrow = n_length)
contour(
  x = x1,
  y = x2,
  z = Z,
  nlevels = 100,
  las = 1,
  xlab = expression(x[1]),
  ylab = expression(x[2]),
  main = expression(paste(
    "Función de Rosenbrock: ",
    f(x[1],x[2])==100*(x[2]-x[1]^2)^2+(1-x[1])^2)
  ),
  sub = "Curvas de nivel de la función"
)
lines(sol_rosen, type="b",cex=1.5,col="red")

Animación de la optimización

# Transforma la solución en un data.frame
sol <- as.data.frame(sol_ras2d)
colnames(sol) <- c("x1", "x2")
sol$step <- 1:nrow(sol)

# Prepara los datos de fondo
x1 <- seq(-5, 5, length.out = 100)
x2 <- seq(-5, 5, length.out = 100)
X <- expand.grid(x1 = x1, x2 = x2)

X$z <- f_rosenbrock_2d(X$x1, X$x2)

# Crea la animación
p <- ggplot() +
  geom_contour(data = X, aes(x = x1, y = x2, z = z), bins = 20, color = "gray70") +
  geom_point(data = sol, aes(x = x1, y = x2), color = "red", size = 3) +
  geom_path(data = sol, aes(x = x1, y = x2), color = "red", linewidth = 1) +
  transition_reveal(step) +
  labs(
    title = "Optimización sobre la función de Rosenbrock con descenso del gradiente",
    x = expression(x[1]),
    y = expression(x[2])
  ) +
  theme_minimal()

animate(p, fps = 10, duration = 5, width = 600, height = 500, renderer = gifski_renderer("rosenbrock_opt.gif"))

1.3.3 Optimización de la función de Rosenbrock en 3 dimensiones

# Ejecución del método
sol_rosen <- optimizador_mult_numdev(f_rosenbrock,x0=c(-4,-4,-4),eta=1)

# Graficación del proceso de optimización
n_length <- 100
x1 <- seq(-5, 5, length.out = n_length)
x2 <- seq(-5, 5, length.out = n_length)
x3 <- seq(-5, 5, length.out = n_length)
X <- expand.grid(x1, x2, x3)
z <- f_rosenbrock_3d(X[,1], X[,2], X[,3])
Z <- matrix(z, ncol = n_length, nrow = n_length)
contour(
  x = x1,
  y = x2,
  z = Z,
  nlevels = 100,
  las = 1,
  xlab = expression(x[1]),
  ylab = expression(x[2]),
  main = expression(paste(
    "Función de Rosenbrock: ",
    f(x[1],x[2])==100*(x[2]-x[1]^2)^2+(1-x[1])^2)
  ),
  sub = "Curvas de nivel de la función"
)
lines(sol_rosen, type="b",cex=1.5,col="red")

1.3.4 Optimización de la función de Rastrigin en 2 dimensiones

sol_ras2d <- optimizador_mult_numdev(f_rastrigin,x0=c(4.5,4.5),eta=2)

n_length <- 100
x1 <- seq(-5.12, 5.12, length.out = n_length)
x2 <- seq(-5.12, 5.12, length.out = n_length)
X <- expand.grid(x1, x2)
z <- f_rastrigin_2d(X[,1], X[,2])
Z <- matrix(z, ncol = n_length, nrow = n_length)
contour(
  x = x1,
  y = x2,
  z = Z,
  nlevels = 10,
  las = 1,
  xlab = expression(x[1]),
  ylab = expression(x[2]),
  main = expression(paste(
    "Función de Rastrigin: ",
    f(x[1],x[2])==20 + x[1]^2 - 10*cos(2*pi*x[1]) + x[2]^2 - 10*cos(2*pi*x[2])
  )),
  sub = "Curvas de nivel de la función"
)
lines(sol_ras, type="b",cex=1.5,col="red")

Animación de la optimización

# Transforma la solución en un data.frame
sol <- as.data.frame(sol_ras2d)
colnames(sol) <- c("x1", "x2")
sol$step <- 1:nrow(sol)

#Prepara los datos de fondo
x1 <- seq(-5.12, 5.12, length.out = 100)
x2 <- seq(-5.12, 5.12, length.out = 100)
X <- expand.grid(x1 = x1, x2 = x2)

X$z <- f_rastrigin_2d(X$x1, X$x2)

# Crea la animación
p <- ggplot() +
  geom_contour(data = X, aes(x = x1, y = x2, z = z), bins = 20, color = "gray70") +
  geom_point(data = sol, aes(x = x1, y = x2), color = "red", size = 3) +
  geom_path(data = sol, aes(x = x1, y = x2), color = "red", linewidth = 1) +
  transition_reveal(step) +
  labs(
    title = "Optimización sobre la función de Rastrigin con descenso del gradiente",
    x = expression(x[1]),
    y = expression(x[2])
  ) +
  theme_minimal()

animate(p, fps = 10, duration = 5, width = 600, height = 500, renderer = gifski_renderer("rastrigin_opt.gif"))

1.3.5 Optimización de la función de Rastrigin en 3 dimensiones

# Ejecución del método
sol_ras3d <- optimizador_mult_numdev(f_rastrigin,x0=c(-4,-4,-4),eta=3)

# Graficación del proceso de optimización
n_length <- 100
x1 <- seq(-5.12, 5.12, length.out = n_length)
x2 <- seq(-5.12, 5.12, length.out = n_length)
x3 <- seq(-5.12, 5.12, length.out = n_length)
X <- expand.grid(x1, x2, x3)
z <- f_rastrigin_3d(X[,1], X[,2], X[,3])
Z <- matrix(z, ncol = n_length, nrow = n_length)
contour(
  x = x1,
  y = x2,
  z = Z,
  nlevels = 10,
  las = 1,
  xlab = expression(x[1]),
  ylab = expression(x[2]),
  main = expression(paste(
    "Función de Rastrigin: ",
    f(x[1],x[2])==20 + x[1]^2 - 10*cos(2*pi*x[1]) + x[2]^2 - 10*cos(2*pi*x[2])
  )),
  sub = "Curvas de nivel de la función"
)
lines(sol_rosen, type="b",cex=1.5,col="red")

1.4 Método de evolución diferencial

La evolución diferencial es un algoritmo de optimización inspirado en la evolución biológica. Funciona manteniendo una población de soluciones, y mejorándolas generación tras generación mediante operaciones de mutación, recombinación y selección.

  • Mutación: se combinan 3 individuos distintos de la población para crear una variante.
  • Recombinación: se mezcla esa variante con el individuo actual.
  • Selección: se escoge el mejor entre el original y el nuevo.

Este proceso se repite varias veces hasta encontrar una solución óptima.

1.4.1 Implementación en R de evolución diferencial

evolucion_diferencial <- function(fun_obj, dim = 2, NP = 30, F = 0.8, CR = 0.9,
                                  gens = 100, bounds = c(-5, 5)) {

  # Inicializar población
  poblacion <- matrix(runif(NP * dim, bounds[1], bounds[2]), ncol = dim)
  fitness <- apply(poblacion, 1, fun_obj)

  historial <- numeric(gens)
  mejores <- matrix(NA, gens, dim)

  for (gen in 1:gens) {
    for (i in 1:NP) {
      # Seleccionar 3 índices distintos
      indices <- sample(setdiff(1:NP, i), 3)
      x1 <- poblacion[indices[1], ]
      x2 <- poblacion[indices[2], ]
      x3 <- poblacion[indices[3], ]

      # Mutación
      mutado <- x1 + F * (x2 - x3)

      # Recombinar
      trial <- poblacion[i, ]
      jrand <- sample(1:dim, 1)
      for (j in 1:dim) {
        if (runif(1) < CR || j == jrand) {
          trial[j] <- mutado[j]
        }
      }

      # Selección
      if (fun_obj(trial) < fitness[i]) {
        poblacion[i, ] <- trial
        fitness[i] <- fun_obj(trial)
      }
    }

    # Guardar mejor resultado
    best_idx <- which.min(fitness)
    historial[gen] <- fitness[best_idx]
    mejores[gen, ] <- poblacion[best_idx, ]
  }

  list(mejor = poblacion[which.min(fitness), ],
       valor = min(fitness),
       historial = historial,
       trayectoria = mejores)
}

1.4.2 Optimización de la función de Rosenbrock en 2 dimensiones

# Ejecución del método
set.seed(123)
res_rosen <- evolucion_diferencial(f_rosenbrock, dim = 2)

# Mostrar mejor solución
res_rosen$mejor
res_rosen$valor

# Graficar trayectoria
x1 <- seq(-3, 3, length.out = 100)
x2 <- seq(-3, 3, length.out = 100)
z <- outer(x1, x2, Vectorize(function(x, y) f_rosenbrock(c(x, y))))
contour(x1, x2, z, nlevels = 50,
        main = "Rosenbrock 2D - Trayectoria", xlab = "x", ylab = "y")
lines(res_rosen$trayectoria[,1], res_rosen$trayectoria[,2], col = "red", type = "b")

En este gráfico se muestran las curvas de nivel de la función de Rosenbrock en dos dimensiones. Estas curvas representan líneas donde la función tiene igual valor, y el valle curvado al centro es donde está el mínimo global (en el punto (1,1)(1,1)).

La línea roja representa la trayectoria que siguió el algoritmo de evolución diferencial durante las iteraciones. Se puede observar cómo el enjambre de soluciones se fue acercando progresivamente hacia el mínimo, mejorando su posición en cada generación.

El resultado final obtenido fue: [1] 1.000000 1.000001 Valor mínimo encontrado: 1.91e-12

1.4.3 Optimización de la función de Rosenbrock en 3 dimensiones

# Ejecución del método
res_rosen_3d <- evolucion_diferencial(f_rosenbrock, dim = 3)
res_rosen_3d$mejor
res_rosen_3d$valor

En esta parte del informe se muestran los resultados numéricos para la optimización de las funciones en 3D. Dado que no se puede visualizar fácilmente en una gráfica 3D de trayectoria, se reportan las mejores posiciones y valores obtenido.

1.4.4 Optimización de la función de Rastrigin en 2 dimensiones

# Ejecución del método
set.seed(456)
res_ras <- evolucion_diferencial(f_rastrigin, dim = 2)

# Mostrar mejor solución
res_ras$mejor
res_ras$valor

# Graficar trayectoria
x1 <- seq(-5.12, 5.12, length.out = 100)
x2 <- seq(-5.12, 5.12, length.out = 100)
z <- outer(x1, x2, Vectorize(function(x, y) f_rastrigin(c(x, y))))
contour(x1, x2, z, nlevels = 50,
        main = "Rastrigin 2D - Trayectoria", xlab = "x", ylab = "y")
lines(res_ras$trayectoria[,1], res_ras$trayectoria[,2], col = "blue", type = "b")

Aquí se grafican las curvas de nivel de la función de Rastrigin, que es multimodal, es decir, tiene muchos mínimos locales (patrón ondulado). La búsqueda es mucho más compleja que en Rosenbrock.

La línea azul muestra cómo la evolución diferencial se mueve por el espacio de búsqueda y logra escapar de los mínimos locales hasta acercarse al óptimo global, que se encuentra en (0,0)(0,0).

Resultado obtenido: [1] 2.176697e-06 -2.015785e-07 Valor mínimo: 9.48e-10

1.4.5 Optimización de la función de Rastrigin en 3 dimensiones

# Ejecución del método
res_ras_3d <- evolucion_diferencial(f_rastrigin, dim = 3)
res_ras_3d$mejor
res_ras_3d$valor

En este caso, el algoritmo encontró una solución cercana al mínimo global, aunque no exacta. Esto es esperable, ya que Rastrigin es mucho más difícil en 3D debido a la gran cantidad de mínimos locales.

1.4.6 Conclusiones método de evolución diferencial

El algoritmo logró encontrar soluciones muy cercanas al mínimo global en las funciones de Rosenbrock y Rastrigin, tanto en 2D como en 3D.

En la función de Rosenbrock, se observó una convergencia estable y precisa, especialmente en dos dimensiones.

En la función de Rastrigin, que presenta muchos mínimos locales, el método evitó caer en estos y alcanzó buenos resultados.

El rendimiento se mantuvo sólido en tres dimensiones, aunque con una ligera pérdida de precisión en Rastrigin 3D debido a su complejidad.

Fue más robusto que el descenso por gradiente en funciones multimodales, ya que no necesita derivadas ni depende del punto inicial.

La evolución diferencial demostró ser un método confiable y efectivo para resolver problemas de optimización continua.

1.5 Método de optimización de partículas

El método de optimización por enjambre de partículas (PSO, por sus siglas en inglés) es un algoritmo inspirado en el comportamiento colectivo de animales como bandadas de aves o bancos de peces. Funciona mediante un conjunto de partículas (soluciones potenciales) que exploran el espacio de búsqueda moviéndose en función de su propia experiencia y la de sus vecinas. Cada partícula ajusta su posición y velocidad iterativamente para acercarse a la mejor solución conocida, guiada por su mejor posición histórica y la mejor posición global encontrada por el enjambre. Con el tiempo, las partículas tienden a converger hacia una solución óptima o cercana al óptimo.

1.5.1 Implementación en R de optimización de partículas

Se utiliza el paquete pos para implementar el método de la optimización de partículas. Adicionalmente, se implementa una función adicional para crear las animaciones del método de optimización.

list_to_matrix <- function(data) {
  for (i in 1:length(data)) {
    data[[i]] <- matrix(data[[i]], nrow=2, ncol=12)
  }
  return(data)
}

particle_swarm_optimization <- function(n,func,lower_bounds,upper_bounds) {
  set.seed(2001)
  o_min <- psoptim(rep(NA,n), func, lower=lower_bounds,upper=upper_bounds,control=list(fnscale=1e-8,trace=1,trace.stats=TRUE))
  o_max <- psoptim(rep(NA,n), func, lower=lower_bounds,upper=upper_bounds,control=list(fnscale=-1*(1e-8),trace=1,trace.stats=TRUE,s=30))
  print("=====================SUMMARY=====================")
  print("MINIMIZATION:")
  print("Point:")
  show(o_min$par)
  print("Value:")
  show(func(o_min$par))
  print("MAXIMIZATION")
  print("Point:")
  show(o_max$par)
  print("Value:")
  show(func(o_max$par))
  return(list("o_min"=o_min, "o_max"=o_max))
}

animate_pso <- function(particles_positions, func, gif_name) {
  positions_per_iteration <- list_to_matrix(particles_positions)
  df <- map2_dfr(
    positions_per_iteration,
    .y = seq_along(positions_per_iteration),
    .f = function(mat, iter) {
      tibble(
        particle_id = 1:ncol(mat),
        x = mat[1, ],
        y = mat[2, ],
        iter = iter
      )
    }
  )
  x_seq <- seq(-10, 10, length.out = 100)
  y_seq <- seq(-10, 10, length.out = 100)
  grid <- expand.grid(x = x_seq, y = y_seq)
  grid$z <- with(grid, func(x, y))
  
  p <- ggplot() +
    geom_contour(data = grid, aes(x=x, y=y, z=z),
                 bins = 30, color = "gray") +
    geom_point(data=df, aes(x = x, y = y), color="red", size=2) +
    xlim(-10, 10) + ylim(-10, 10) +  # Adjust limits to your data range
    theme_minimal() +
    transition_manual(frames = iter) +
    labs(title = "Iteration: {current_frame}")
  #p + transition_reveal(agno)
  animate(p, fps = 5, renderer=gifski_renderer())
  anim_save(gif_name,p)
}

1.5.2 Optimización de la función de Rosenbrock en 2 dimensiones

# Parámetros a utilizar
n <- 2
lower_bounds <- -20
upper_bounds <- 20

# Ejecución del método
o_pso <- particle_swarm_optimization(n,f_rosenbrock,lower_bounds,upper_bounds)
# Graficación del proceso de optimización (minimización)
o_min <- o_pso$o_min
o_min_particles_positions <- o_min$stats$x
gif_name <- "pso_rosenbrock_min.gif"
animate_pso(o_min_particles_positions, f_rosenbrock_2d, gif_name)
# Graficación del proceso de optimización (maximización)
o_max <- o_pso$o_max
o_max_particles_positions <- o_max$stats$x
gif_name <- "pso_rosenbrock_max.gif"
animate_pso(o_max_particles_positions, f_rosenbrock_2d, gif_name)

1.5.3 Optimización de la función de Rosenbrock en 3 dimensiones

# Parámetros a utilizar
n <- 3
lower_bounds <- -5
upped_bounds <- 5

# Ejecución del método
o_pso <- particle_swarm_optimization(n,f_rosenbrock,lower_bounds,upper_bounds)
# Graficación del proceso de optimización

1.5.4 Optimización de la función de Rastrigin en 2 dimensiones

# Parámetros a utilizar
n <- 2
lower_bounds <- -5
upped_bounds <- 5

# Ejecución del método
o_pso <- particle_swarm_optimization(n,f_rastrigin,lower_bounds,upper_bounds)
# Graficación del proceso de optimización (minimización)
o_min <- o_pso$o_min
o_min_particles_positions <- o_min$stats$x
gif_name <- "pso_rastrigin_min.gif"
animate_pso(o_min_particles_positions, f_rastrigin_2d, gif_name)
# Graficación del proceso de optimización (maximización)
o_max <- o_pso$o_max
o_max_particles_positions <- o_max$stats$x
gif_name <- "pso_rastrigin_max.gif"
animate_pso(o_max_particles_positions, f_rastrigin_2d, gif_name)

1.5.5 Optimización de la función de Rastrigin en 3 dimensiones

# Parámetros a utilizar
n <- 3
lower_bounds <- -5
upped_bounds <- 5
# Ejecución del método
o_pso <- particle_swarm_optimization(n,f_rastrigin,lower_bounds,upper_bounds)

1.5.6 Conclusiones método de optimización de partículas

En el caso de la optimización maximizando las funciones, llegan muy rápido a una solución óptima (dentro de 10 iteraciones). Por otro lado, la optimización minimizando las funciones tardaba un poco más en llegar al óptimo, pero se acercaba mucho al mínimo global.

Estos resultados pueden deberse a la distribución de las partículas por el espacio de búsqueda, permitiendo una optimización más “abierta” a comparación con el método de descenso de gradiente.

1.6 Método de algoritmos evolutivos

Los algoritmos genéticos (AG) son técnicas de búsqueda heurística basadas en procesos de evolución natural​ . En un AG típico se define una función FITNESS que evalúa la calidad de cada solución candidata (individuo). A partir de una población inicial aleatoria, se iteran ciclos donde se seleccionan individuos más aptos, se combinan sus “genes” mediante cruces (crossover) y se introducen modificaciones aleatorias (mutaciones). Estos operadores evolucionan la población hacia regiones con mejor fitness.

Según (Scrucca, 2013), los GAs han sido exitosos en optimizar funciones continuas (diferenciables o no) y discretas. Entre los operadores genéticos clave se destacan:

  • Selección: elige individuos con mayor fitness para reproducirse, imitando la supervivencia del más apto.

  • Cruce (crossover): combina partes de dos soluciones parentales para generar descendencia, explorando nuevas regiones del espacio de búsqueda.

  • Mutación: altera aleatoriamente parte de un individuo (por ejemplo, cambiando un valor de su vector de variables) para introducir diversidad genética y evitar estancamiento en óptimos locales.

Los algoritmos genéticos (AG) son metaheurísticas inspiradas en procesos evolutivos biológicos, que han demostrado eficacia en la búsqueda global de óptimos en funciones complejas​jstatsoft.org Los AG simulan la selección natural, la recombinación (cruce) y la mutación para iterativamente mejorar un conjunto de soluciones candidatas (población). Estas técnicas estocásticas son adecuadas para funciones no lineales, discontinuas o con múltiples óptimos locales donde los métodos basados en derivadas pueden fallar. Para evaluar la robustez de los GA, se realizarán múltiples ejecuciones independientes y se analizará la dispersión del fitness resultante.

Adicionalmente, se definen algunas funciones para poder mostrar por medio de una animación el proceso de optimización para las funciones de Rosenbrock y de Rastrigin.

animate_ga_optimization <- function(func) {
  # 1. Crear el entorno para almacenar la evolución de la población
  pop_data <- data.frame()
  
  # 2. Ejecutar el algoritmo genético, capturando las poblaciones
  ga_rastrigin <- ga(
    type = "real-valued",
    fitness = function(x) -func(x),
    lower = c(-5.12, -5.12), upper = c(5.12, 5.12),
    popSize = 50, maxiter = 50, run = 50,
    monitor = function(obj) {
      gen <- obj@iter
      pop <- obj@population
      df <- data.frame(
        X1 = pop[, 1],
        X2 = pop[, 2],
        Generacion = gen
      )
      pop_data <<- rbind(pop_data, df)
    }
  )
  
  # 3. Crear grilla para visualizar la función Rastrigin
  x <- seq(-5.12, 5.12, length.out = 100)
  y <- seq(-5.12, 5.12, length.out = 100)
  grid <- expand.grid(X1 = x, X2 = y)
  grid$Z <- apply(grid, 1, func)
  
  # 4. Graficar y animar
  base_plot <- ggplot() +
    geom_raster(data = grid, aes(x = X1, y = X2, fill = Z), interpolate = TRUE) +
    scale_fill_viridis_c() +
    geom_point(data = pop_data, aes(x = X1, y = X2), color = "red", size = 1, alpha = 0.6) +
    labs(title = "Optimización de Rastrigin usando GA", subtitle = "Generación: {closest_state}",
         x = "x1", y = "x2") +
    transition_states(Generacion, transition_length = 2, state_length = 1) +
    theme_minimal()
  
  # 5. Exportar como GIF
  anim_save("optim_rastrigin_ga.gif", animation = animate(base_plot, renderer = gifski_renderer(), fps = 5, width = 600, height = 500))
}

Analizaremos cada función en 2 y 3 dimensiones, graficando su paisaje antes de la optimización y luego aplicando un AG con múltiples corridas para evaluar la robustez de los resultados.

1.6.1 Implementación en R de algoritmos evolutivos

Se utiliza la función ga() del paquete GA. Para problemas de minimización se define la función de fitness como el negativo del valor objetivo, ya que ga() maximiza por defecto. Se especifican los límites de búsqueda.

Los resúmenes en cada ejecución reportan el mejor fitness encontrado (negativo) y la solución óptima en cada ejecución.

1.6.2 Optimización de la función de Rosenbrock en 2 dimensiones

# Ejecución del método
ga_ros2d <- ga(type = "real-valued",
               fitness = function(x) -f_rosenbrock(x),
               lower = c(-5, -5), upper = c(5, 5),
               popSize = 50, maxiter = 100, run = 50)
summary(ga_ros2d)
# Graficación del proceso de optimización
gif_name <- "optim_rosenbrock_ga.gif"
animate_ga_optimization(f_rosenbrock)
set.seed(123)  # semilla reproducible
best_vals_ros <- replicate(30, {
  GA <- ga(type = "real-valued",
           fitness = function(x) -f_rosenbrock(x),
           lower = c(-5, -5), upper = c(5, 5),
           popSize = 50, maxiter = 100, run = 50)
  -GA@fitnessValue  # convertir a valor positivo
})
mean_ros <- mean(best_vals_ros)
sd_ros   <- sd(best_vals_ros)

1.6.3 Optimización de la función de Rosenbrock en 3 dimensiones

# Ejecución del método
ga_ros3d <- ga(type = "real-valued",
               fitness = function(x) -f_rosenbrock(x),
               lower = c(-5, -5, 3), upper = c(5, 5, 3),
               popSize = 50, maxiter = 100, run = 50)
summary(ga_ros3d)
# Realizar 30 ejecuciones independientes para Rosenbrock 3D
set.seed(123)  # semilla reproducible
best_vals_ros3d <- replicate(30, {
  GA <- ga(type = "real-valued",
           fitness = function(x) -f_rosenbrock(x),
           lower = c(-5, -5, 3), upper = c(5, 5, 3),
           popSize = 50, maxiter = 100, run = 50)
  -GA@fitnessValue  # convertir a valor positivo
})
mean_ros3d <- mean(best_vals_ros3d)
sd_ros3d   <- sd(best_vals_ros3d)
#  Rosenbrock 3D, Rastrigin 3D.

1.6.4 Optimización de la función de Rastrigin en 2 dimensiones

# Ejecución del método
ga_ras2d <- ga(type = "real-valued",
               fitness = function(x) -f_rastrigin(x),
               lower = c(-5, -12), upper = c(5, 12),
               popSize = 50, maxiter = 100, run = 50)
summary(ga_ras2d)
# Graficación del proceso de optimización
gif_name <- "optim_rastrigin_ga.gif"
animate_ga_optimization(f_rastrigin)
set.seed(123)  # semilla reproducible
best_vals_ras2d <- replicate(30, {
  GA <- ga(type = "real-valued",
           fitness = function(x) -f_rastrigin(x),
           lower = c(-5, -5), upper = c(5, 5),
           popSize = 50, maxiter = 100, run = 50)
  -GA@fitnessValue  # convertir a valor positivo
})
mean_ras2d <- mean(best_vals_ras2d)
sd_ras2d   <- sd(best_vals_ras2d)

1.6.5 Optimización de la función de Rastrigin en 3 dimensiones

# Ejecución del método
ga_ras3d <- ga(type = "real-valued",
               fitness = function(x) -f_rastrigin(x),
               lower = c(-5, -12,3), upper = c(5, 12,3 ),
               popSize = 50, maxiter = 100, run = 50)
summary(ga_ras3d)
set.seed(123)  # semilla reproducible
best_vals_ras3d <- replicate(30, {
  GA <- ga(type = "real-valued",
           fitness = function(x) -f_rastrigin(x),
           lower = c(-5, -5, 3), upper = c(5, 5, 3),
           popSize = 50, maxiter = 100, run = 50)
  -GA@fitnessValue  # convertir a valor positivo
})
mean_ras3d <- mean(best_vals_ras3d)
sd_ras3d   <- sd(best_vals_ras3d)

1.6.6 Cálculo de estadísticas y análisis

Para evaluar la variabilidad del método estocástico, se repite cada caso al menos 30 veces con semillas distintas. Se registra el mejor valor de fitness (valorizado positivamente) obtenido en cada corrida. Se calcularán estadísticas (media y desviación estándar) del mejor valor de fitness obtenido en 30 ejecuciones independientes de cada caso y se resumirán en una tabla.

Con los vectores de mejores valores (best_vals_ros, etc.), se calculan la media y desviación estándar de cada conjunto de 30 resultados. Por ejemplo, mean_ros y sd_ros arriba y las demas, Para asi presentar los resultados.

library(knitr)
resultados <- data.frame(
  Función   = c("Rosenbrock", "Rastrigin", "Rosenbrock", "Rastrigin"),
  Dimensión = c("2D", "2D", "3D", "3D"),
  Media     = c(mean_ros, mean_ras2d, mean_ros3d, mean_ras3d),
  SD        = c(sd_ros, sd_ras2d, sd_ros3d, sd_ras3d)
)
kable(resultados, caption = "Resumen estadístico (media y desviación estándar) del mejor fitness obtenido tras 30 ejecuciones independientes de cada caso.")

Los resultados de las múltiples ejecuciones se resumen en la Tabla 1. Esta tabla muestra la media y desviación estándar del mejor valor de fitness (recordado que es el valor de la función objetivo en su mínimo global, típicamente cercano a 0) para cada combinación de función y dimensión. Se observa que para Rosenbrock 2D, la media del fitness mínimo es cercana a 0 con baja dispersión, reflejando que el GA normalmente encuentra el mínimo global (0) o cercano. Para Rastrigin 2D, la media también puede acercarse a 0, pero con mayor desviación estándar debido a los múltiples mínimos locales. En 3D ambos problemas suelen mostrar valores medios mayores (más alejados de 0) y mayor variabilidad, lo cual indica una mayor dificultad de búsqueda al aumentar la dimensionalidad.

# tabla de los valores calculados)
library(knitr)
res_df <- data.frame(
  Función   = c("Rosenbrock", "Rastrigin", "Rosenbrock", "Rastrigin"),
  Dimensión = c("2D", "2D", "3D", "3D"),
  Media     = c(mean_ros, mean_ras2d, mean_ros3d, mean_ras3d),
  SD        = c(sd_ros, sd_ras2d, sd_ros3d, sd_ras3d)
)
kable(res_df, caption = "Tabla 1. Estadísticas (media y desviación estándar) del fitness mínimo alcanzado en 30 corridas independientes para cada función y dimensión.")

Tabla 1. Estadísticas (media y desviación estándar) del fitness mínimo alcanzado en 30 corridas independientes para cada función y dimensión.

1.6.7 Conclusiones método de algoritmos evolutivos

Los resultados confirman que el algoritmo genético es capaz de aproximarse a los mínimos globales de ambos problemas en múltiples dimensiones. Como era de esperar, Rastrigin mostró mayor variabilidad en los valores de fitness debido a sus muchos mínimos locales, lo que implica que algunas ejecuciones del GA pueden quedarse atrapadas en óptimos locales alejados del global. En contraste, Rosenbrock (aunque es no convexa) tiende a un único valle principal; por ello, la mayoría de las corridas alcanzaron valores cercanos al mínimo global con menor dispersión. En general se observa que al aumentar la dimensión (de 2D a 3D) la tarea se complica y la media del fitness aumenta (peor óptimo encontrado), reflejando la maldición de la dimensionalidad. El uso de múltiples ejecuciones independientes es esencial para evaluar la robustez de los AG. Debido a su naturaleza estocástica, cada ejecución puede converger a soluciones distintas. Al analizar la media y desviación estándar de los fitness finales se obtiene una medida de fiabilidad del algoritmo: una baja desviación indica resultados consistentes. En la literatura sobre algoritmos genéticos se reconoce que en muchos casos una sola ejecución puede no ser representativa​jstatsoft.org. Aunque un análisis comparativo profundo (p.ej., usando poblaciones más grandes o múltiples corridas en paralelo) queda fuera del alcance de este documento, nuestros resultados ilustran este fenómeno. Este estudio es reproducible: todo el código R necesario está incluido, permitiendo a otros investigadores replicar los experimentos, variar parámetros del GA (tasa de cruce, mutación, tamaño de población, etc.) y comparar con otros algoritmos de optimización.:Conclusiones Se ha presentado una documentación completa de la optimización de las funciones de Rosenbrock y Rastrigin en 2D y 3D empleando algoritmos genéticos en R. Mediante visualizaciones 3D iniciales se ilustraron las características de cada función de prueba. Se implementó el paquete GA para resolver cada caso y se realizaron 30 ejecuciones independientes para evaluar la robustez. Los resultados muestran que el GA puede encontrar aproximaciones al mínimo global en ambos problemas, aunque la función Rastrigin (múltiples mínimos locales) presenta más variabilidad y dificultad, especialmente en 3D.

Parte 2. Optimización combinatoria

2.1 Planteamiento del problema

El problema se puede plantear como una instancia particular del problema del viajero en ciencias de la computación. En la Tabla 2 se muestran las 13 ciudades principales de Colombia con su respectivas latitudes y longitudes.

Latitud Longitud
Bogotá 4.7110 -74.0721
Medellín 6.2442 -75.5812
Cali 3.4516 -76.5320
Barranquilla 10.9685 -74.7813
Cartagena 10.3910 -75.4794
Cúcuta 7.8941 -72.5078
Soledad 10.9264 -74.8055
Ibagué 4.4389 -75.2322
Bucaramanga 7.1193 -73.1227
Villavicencio 4.1420 -73.6298
Santa Marta 11.2408 -74.1990
Manizales 5.0703 -75.5138
Pereira 4.8143 -75.6946

Tabla 2. Ciudades principales de Colombia junto con su latencia y longitud.

El objetivo de este problema es optimizar con respecto a los costos y no a las distancias entre ciudades, por lo que se tienen que considerar los costos de combustible, de peajes y cuánto cobra el vendedor por hora de trabajo. Sin embargo, la distancia afectará al valor de todos estos costos, y por lo tanto hay que considerarla. Adicionalmente, todos estos costos están en función de la distancia en metros, por lo que es necesario realizar la conversión de latitud-longitud a coordenadas en metros. Para esto se usó la librería “sf” para realizar la conversión al sistema de coordenadas planas UTM, el cual representa los puntos del globo en metros.

La tabla de coordenadas en metros utilizando la información de la Tabla 1 se implementa a continuación:

nombre_ciudades <- c("Bogotá", "Medellín", "Cali", "Barranquilla", "Cartagena", 
             "Cúcuta", "Pasto", "Ibagué", "Bucaramanga", "Villavicencio", 
             "Santa Marta", "Manizales", "Pereira")

# Definición de ciudades, latitud y longitud de cada ciudad.
ciudades <- tibble::tibble(
  Ciudad = nombre_ciudades,
  Latitud = c(4.7110, 6.2442, 3.4516, 10.9685, 10.3910, 
              7.8941, 1.2136, 4.4389, 7.1193, 4.1420, 
              11.2408, 5.0703, 4.8143),
  Longitud = c(-74.0721, -75.5812, -76.5320, -74.7813, -75.4794, 
               -72.5078, -77.2811, -75.2322, -73.1227, -73.6298, 
               -74.1990, -75.5138, -75.6946)
)

# Time zone value
# Por simplicidad, se elige el mismo punto común para calcular el timezone.
# Se utiliza la longitud de Bogotá como punto común para calcular el utm_zone.
utm_zone <- floor((-74.0721 + 180)/6) + 1 + 32700

# Convertimos a objeto espacial
puntos <- st_as_sf(ciudades, coords = c("Longitud", "Latitud"), crs = 4326)

puntos_en_utm <- st_transform(puntos, crs=utm_zone)
ciudades_en_metros <- st_coordinates(puntos_en_utm)
tabla_coordenadas_ciudades <- data.frame(
  ciudad = nombre_ciudades,
  coord_x_en_metros = ciudades_en_metros[,1],
  coord_y_en_metros = ciudades_en_metros[,2]
)
tabla_coordenadas_ciudades

Luego, se crea la matriz de distancias en metros entre cada ciudad.

coordenadas_ciudades <- data.matrix(tabla_coordenadas_ciudades[,c(2,3)])
distancias <- compute_distance_matrix(coordenadas_ciudades)
print(distancias)

Ahora, hay que considerar los costos para poder crear una matriz de costos para resolver el problema de optimización.

Con respecto al costo del combustible, este dependerá del vehículo que el vendedor utilizará para realizar las entregas. Para este problema, se utilizará un furgón DFSK C35, el cual tiene un consumo de combustible de 7.6 litros aproximado por cada 100 Km de recorrido, que se puede traducir en 0.000076 litros por cada 1 metro (DFSK, s.f., Especificaciones Furgon C35). Adicionalmente, se considera el precio promedio de la gasolina en Colombia que, segun (La República, 2025), tiene un valor de $15.827 pesos colombianos por galón, que se puede traducir en $4.022 pesos colombianos por litro aproximadamente.

gasto_litro_por_metro <- 0.000076
precio_gasolina_por_litro <- 4.022 
costos_combustible <- distancias * gasto_litro_por_metro * precio_gasolina_por_litro
rownames(costos_combustible) <- nombre_ciudades
colnames(costos_combustible) <- nombre_ciudades
print(costos_combustible)

Considerando el costo de los peajes, se usarán los datos de (Autofact, 2024) para calcular el valor promedio de cada peaje de las ciudades consideradas y se asumirá que el vendedor pagará el valor promedio del peaje de la ciudad de origen y de la ciudad de destino exactamente 1 vez cada que las recorra.

matriz_costos <- costos_vendedor + costos_peajes + costos_combustible
print(matriz_costos)
                 Bogotá  Medellín      Cali Barranquilla Cartagena    Cúcuta     Pasto    Ibagué Bucaramanga Villavicencio Santa Marta Manizales   Pereira
Bogotá             0.00  67240.80  74529.48    137649.07 131493.79  91153.12 113638.21  48319.61    73468.06      37405.20   143474.36  54767.84  58441.36
Medellín       67240.80      0.00  80586.62    112936.95 103200.50  92810.96 126574.14  62860.46    76622.06      79451.17   121635.15  51827.65  57648.70
Cali           74529.48  80586.62      0.00    162359.33 151607.74 134676.25  68676.65  55156.30   116491.41      77704.02   171434.62  61299.49  56681.47
Barranquilla  137649.07 112936.95 162359.33         0.00  38637.58  93566.43 208193.11 143529.48   100192.54     148166.46    33370.49 133613.92 139789.55
Cartagena     131493.79 103200.50 151607.74     38637.58      0.00  96527.86 197108.70 134796.68    99220.12     142737.87    51591.87 124251.54 130100.20
Cúcuta         91153.12  92810.96 134676.25     93566.43  96527.86      0.00 178084.43 108103.99    45478.74      97007.68    93844.11 104121.58 110845.74
Pasto         113638.21 126574.14  68676.65    208193.11 197108.70 178084.43      0.00  98011.17   159865.59     111610.69   217401.50 106738.98 102216.29
Ibagué         48319.61  62860.46  55156.30    143529.48 134796.68 108103.99  98011.17      0.00    89865.26      55455.92   151187.62  41556.84  40924.53
Bucaramanga    73468.06  76622.06 116491.41    100192.54  99220.12  45478.74 159865.59  89865.26        0.00      80445.13   103039.49  86147.79  92820.63
Villavicencio  37405.20  79451.17  77704.02    148166.46 142737.87  97007.68 111610.69  55455.92    80445.13          0.00   153360.70  65113.75  67537.40
Santa Marta   143474.36 121635.15 171434.62     33370.49  51591.87  93844.11 217401.50 151187.62   103039.49     153360.70        0.00 141825.81 148203.21
Manizales      54767.84  51827.65  61299.49    133613.92 124251.54 104121.58 106738.98  41556.84    86147.79      65113.75   141825.81      0.00  36827.52
Pereira        58441.36  57648.70  56681.47    139789.55 130100.20 110845.74 102216.29  40924.53    92820.63      67537.40   148203.21  36827.52      0.00

Por último, se debe de considerar el salario por horas promedio de un vendedor que haga dichos recorridos para entregar los pedidos. Adicional a ser vendedor, esta persona realizará las entregas y la conducción de la mercancía, por lo que también hay que considerar su pago como transportista. Dicho esto, se promediaron salarios de un transportista y un vendedor para poder determinar el salario final de la persona. Según (Talent.com, s.f., Salario medio para Transporte De Carga en Colombia 2025) y (Talent.com, s.f., Salario medio para Vendedor en Colombia 2025), un transportista gana en promedio $9.341 pesos colombianos la hora y un vendedor hace $7.144 la hora; por lo tanto, el salario a utilizar será el $8.242 pesos la hora.

Asumiendo que el vendedor conducirá a una velocidad media de 50 km/h al día, que se podrían traducir en 50000 m/h para facilitar cálculos, se procede a calcular el tiempo de viaje y, junto con esto, cuánto se le pagará al vendedor por cada viaje.

velocidad_media_metros_por_hora <- 50000
salario_vendedor_colombia <- 7144
salario_transportista_colombia <- 9341
salario_final <- (salario_vendedor_colombia + salario_transportista_colombia)/2
costos_vendedor <- distancias*(1/velocidad_media_metros_por_hora)*salario_final
rownames(costos_vendedor) <- nombre_ciudades
colnames(costos_vendedor) <- nombre_ciudades
costos_vendedor

Concluyendo, la matriz de costos simplemente sería el resultado de la suma de las anteriores matrices previamente definidas, y esta será la matriz que se utilizarán en los distintos algoritmos para encontrar la mejor ruta.

matriz_costos <- costos_vendedor + costos_peajes + costos_combustible
print(matriz_costos)

2.2 Implementación Método de Colonia de hormigas

A continuación, se implementa el método de colonia de hormigas para encontrar

# Número de ciudades
n_ciudades <- 13

# Función para normalizar la matriz de costos, ya que search_tour_ants
# funciona con valores normalizados.
# Se optó por usar normalización zscore.
normalize_zscore <- function(m) {
  (m - mean(m)) / sd(m)
}

# Normalización de matriz de costos
matriz_costos_normalizada <- normalize_zscore(matriz_costos)

# Ejecución del método de colonia de hormigas
recorrido_optimizado <- search_tour_ants(matriz_costos_normalizada, n_ciudades, K = 80, N = 40, log=TRUE)
print("MEJOR RECORRIDO: ")
print(recorrido_optimizado$tour)
# Tour más óptimo: 1 10  7  3  8 13 12  2  5  4 11  6  9

# Rutas seleccionadas entre algunas ciudades
print("RUTA IDEAL: ")
print("Bogotá->Villavicencio->Pasto->Cali->Ibagué->Pereira->Manizales->Medellín->Cartagena->Barranquilla->Santa Marta->Cúcuta->Bucaramanga")
pares_rutas <- list(
  c("Bogotá", "Villavicencio"),
  c("Villavicencio", "Pasto"),
  c("Pasto", "Cali"),
  c("Cali", "Ibagué"),
  c("Ibagué", "Pereira"),
  c("Pereira", "Manizales"),
  c("Manizales", "Medellín"),
  c("Medellín", "Cartagena"),
  c("Cartagena", "Barranquilla"),
  c("Barranquilla", "Santa Marta"),
  c("Santa Marta", "Cúcuta"),
  c("Cúcuta", "Bucaramanga")
)

# Convertimos a objeto espacial
puntos <- st_as_sf(ciudades, coords = c("Longitud", "Latitud"), crs = 4326)

# Obtener rutas con osrm
rutas <- list()
for (par in pares_rutas) {
  origen <- puntos[ciudades$Ciudad == par[1], ]
  destino <- puntos[ciudades$Ciudad == par[2], ]
  ruta <- try(osrmRoute(src = origen, dst = destino, ), silent = TRUE)
  if (!inherits(ruta, "try-error")) {
    rutas <- append(rutas, list(ruta))
  }
}

# Crear el mapa
mapa <- leaflet() %>%
  addTiles() %>%
  addCircleMarkers(data = puntos, label = ~Ciudad, radius = 6, color = "blue", fillOpacity = 0.8)

# Añadir las rutas en rojo
for (ruta in rutas) {
  mapa <- mapa %>% addPolylines(data = ruta, color = "red", weight = 3)
}

print("COSTO FINAL DEL RECORRIDO: ")
costos <- c(
  matriz_costos[1,10], #"Bogotá" -> "Villavicencio"
  matriz_costos[10,7], #"Villavicencio" -> "Pasto"
  matriz_costos[7,3], #"Pasto" -> "Cali"
  matriz_costos[3,8], #"Cali" -> "Ibagué"
  matriz_costos[8,13], #"Ibagué" -> "Pereira"
  matriz_costos[13,12], #"Pereira" -> "Manizales"
  matriz_costos[12,2], #"Manizales" -> "Medellín"
  matriz_costos[2,5], #"Medellín" -> "Cartagena"
  matriz_costos[5,4], #"Cartagena" -> "Barranquilla"
  matriz_costos[4,11], #"Barranquilla" -> "Santa Marta"
  matriz_costos[11,6], #"Santa Marta" -> "Cúcuta"
  matriz_costos[6,9] #"Cúcuta" -> "Bucaramanga"
)
print(sum(costos))
mapa

2.3 Conclusiones

Conclusiones finales

Reporte de contribución individual

Leonardo Federico Corona Torres

  • Apoyo en redacción y realización de implementaciones de sección “Método de algoritmos evolutivos”.

  • Apoyo en redacción de implementaciones de graficación en Parte 2 del reporte.

  • Búsqueda e investigación de funciones de prueba a utilizar.

  • Apoyo en redacción y realización de implementaciones de sección “Selección e implementación de las funciones de prueba”.

David Escobar Ruiz

  • Definición y escritura de estructura del reporte.

  • Apoyo en redacción y realización de implementaciones de sección “Selección e implementación de las funciones de prueba”.

  • Apoyo en redaccióny realización de implementaciones de sección “Método de optimización de partículas”.

  • Apoyo en redacción y realización de implementaciones de métodos, planteamiento y preprocesamiento de datos Parte 2 del reporte.

Johan Sebastián Robles Rincón

  • Implementación y prueba del método de evolución diferencial en R para funciones de Rosenbrock y Rastrigin en 2D y 3D.

  • Análisis de resultados numéricos y gráficos.

  • Apoyo en redacción de sección del contenido técnico relacionado con este método de evolucion diferencial en el informe.

Sebastian Soto Arcila

  • Apoyo en redacción y realización de implementaciones de sección “Método de optimización por descenso de gradiente”

  • Apoyo en redacción de sección “Selección e implementación de las funciones de prueba”.

Repositorio de GitHub del proyecto

https://github.com/druiz35/RNABI2025-1-Equipo3/

Bibliografía

Autofact. (2024). Peajes en Colombia: Conoce sus ubicaciones y precios 2024. https://www.autofact.com.co/blog/mi-carro/peajes/peajes-colombia-precios

DFSK. (s.f.). Especificaciones Furgon C35. Recuperado el 2 de mayo de 2025, de http://static.multiaviso.com/vehicle/specs/22-VRZC564XLBE6-dfsk-otros-modelos-2017-furgon-c35.pdf

La República. (2025). PRECIO DE LA GASOLINA. Recuperado el 2 de mayo de 2025, de https://www.larepublica.co/precio-de-la-gasolina

Molga, Smutnicki. (2005). Test functions for optimization needs. https://robertmarks.org/Classes/ENGR5358/Papers/functions.pdf

Talent.com. (s.f.). Salario medio para Transporte De Carga en Colombia 2025. Recuperado el 2 de mayo de 2025, de https://co.talent.com/salary?job=transporte+de+carga

Talent.com. (s.f.). Salario medio para Vendedor en Colombia 2025. Recuperado el 2 de mayo de 2025, de https://co.talent.com/salary?job=vendedor

Wikipedia. (s.f.). Test functions for optimization. Wikipedia. Recuperado el 2 de mayo de 2025, de https://en.wikipedia.org/wiki/Test_functions_for_optimization

Wikipedia. (s.f.). Rosenbrock function. Wikipedia. Recuperado el 2 de mayo de 2025, de https://en.wikipedia.org/wiki/Rosenbrock_

Wikipedia. (s.f.). Rastrigin function. Wikipedia. Recuperado el 2 de mayo de 2025, de https://en.wikipedia.org/wiki/Rastrigin_function

X.-S. Yang, Test problems in optimization, in: Engineering Optimization: An Introduction with Metaheuristic Applications (Eds Xin-She Yang), John Wiley & Sons, (2010)

LS0tDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQotLS0NCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IGZhbHNlDQogICAgY3NzOiBhcGFfc3R5bGUuY3NzDQogICAgdGhlbWU6IHVuaXRlZA0KICAgIGhpZ2hsaWdodDogcHlnbWVudHMNCiAgICBkZl9wcmludDogcGFnZWQNCiAgICBudW1iZXJfc2VjdGlvbnM6IGZhbHNlDQogIHBkZl9kb2N1bWVudDoNCiAgICB0b2M6IGZhbHNlDQotLS0NCg0KYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpDQpgYGANCg0KOjo6IHtzdHlsZT0idGV4dC1hbGlnbjogY2VudGVyOyBjb2xvcjogYmxhY2s7IG1hcmdpbi10b3A6IDYwcHg7In0NCjxoMT5SRVBPUlRFIFRSQUJBSk8gMTogU09MVUNJw5NOIERFIFBST0JMRU1BUyBERSBPUFRJTUlaQUNJw5NOIENPTiBNw4lUT0RPUyBIRVVSw41TVElDT1M8L2gxPg0KDQo8aDI+UkVERVMgTkVVUk9OQUxFUyBZIEFMR09SSVRNT1MgQklPSU5TUElSQURPUzwvaDI+DQoNCjxicj48YnI+PGJyPg0KDQo8cD48c3Ryb25nPlByZXNlbnRhZG8gcG9yOjwvc3Ryb25nPjwvcD4NCg0KPHA+TGVvbmFyZG8gRmVkZXJpY28gQ29yb25hIFRvcnJlczxicj4gRGF2aWQgRXNjb2JhciBSdWl6PGJyPiBgSm9oYW4gU2ViYXN0aWFuIFJvYmxlcyBSaW5jw7NuPGJyPmB7PWh0bWx9U2ViYXN0acOhbiBTb3RvIEFyY2lsYTwvcD4NCg0KPGJyPjxicj4NCg0KPHA+PHN0cm9uZz5Qcm9mZXNvcjo8L3N0cm9uZz4gSnVhbiBEYXZpZCBPc3BpbmEgQXJhbmdvPC9wPg0KDQo8cD48c3Ryb25nPk1vbml0b3I6PC9zdHJvbmc+IEFuZHLDqXMgTWF1cmljaW8gWmFwYXRhIFJpbmPDs248L3A+DQoNCjxicj4gPGltZyBzcmM9ImxvZ29fdW5hbC5wbmciIGFsdD0iVW5pdmVyc2l0eSBMb2dvIiB3aWR0aD0iMTAwcHgiLz4gPGJyPjxicj4NCg0KPHA+VW5pdmVyc2lkYWQgTmFjaW9uYWwgZGUgQ29sb21iaWE8YnI+IEZhY3VsdGFkIGRlIE1pbmFzPGJyPiBJbmdlbmllcsOtYSBkZSBTaXN0ZW1hcyBlIEluZm9ybcOhdGljYTwvcD4NCg0KPHA+PHN0cm9uZz5gciBmb3JtYXQoU3lzLkRhdGUoKSwgIiVkIGRlICVCIGRlICVZIilgPC9zdHJvbmc+PC9wPg0KOjo6DQoNCiMgSW50cm9kdWNjacOzbg0KDQpFbCBwcmVzZW50ZSB0cmFiYWpvIHRyYXRhIGFsZ3Vub3MgZGUgbG9zIG3DqXRvZG9zIG3DoXMgcmVsZXZhbnRlcyBkZSBvcHRpbWl6YWNpw7NuIG51bcOpcmljYSBiYXNhZGEgZW4gbWFuaXB1bGFjaW9uZXMgZGVsIGdyYWRpZW50ZSBkZSB1bmEgZnVuY2nDs24gb2JqZXRpdm8gKGZ1bmNpw7NuIGEgb3B0aW1pemFyKSB5IHRhbWJpw6luIGFsZ29yaXRtb3MgaGV1csOtc3RpY29zIGJhc2Fkb3MgZW4gZWwgY29tcG9ydGFtaWVudG8gZGUgbGEgbmF0dXJhbGV6YSwgY29tbyBsbyBzb24gbG9zIGFsZ29yaXRtb3MgZXZvbHV0aXZvcy4gVG9kb3MgZXN0b3MgbcOpdG9kb3MgcHVlZGVuIGxsZWdhciBhIHNlciDDunRpbGVzIGVuIGRpc3RpbnRvcyBjb250ZXh0b3MgZW4gbG9zIHF1ZSBzZSBidXNjYSBvcHRpbWl6YXIgdW5hIGZ1bmNpw7NuIHBhcmEgYWxnw7puIGZpbiwgY29tbyBlbmNvbnRyYXIgbGEgbWVqb3IgcnV0YSBlbnRyZSBjaXVkYWRlcyBvIG9wdGltaXphciB1bmEgZnVuY2nDs24gZGUgcMOpcmRpZGEgZW4gdW4gY29udGV4dG8gZGUgYXByZW5kaXphamUgZGUgbcOhcXVpbmEuIENhZGEgbcOpdG9kbyB0aWVuZSBwYXJ0aWN1bGFyaWRhZGVzIGludGVyZXNhbnRlcyBxdWUgc2UgdmFuIGEgZXhwbG9yYXIgZGUgbWFuZXJhIGdlbmVyYWwgcG9yIG1lZGlvIGRlIHVuYSBicmV2ZSBpbXBsZW1lbnRhY2nDs24geSBlamVjdWNpw7NuIGNvbiBkb3MgZnVuY2lvbmVzIG11eSB1dGlpbGl6YWRhcyBwYXJhIHByb2JhciBhbGdvcml0bW9zIGRlIG9wdGltaXphY2nDs246IGxhIGZ1bmNpw7NuIGRlIFJvc2VuYnJvY2sgeSBsYSBmdW5jacOzbiBkZSBSYXN0cmlnaW4uDQoNCiMgTWV0b2RvbG9nw61hDQoNClBhcmEgaW1wbGVtZW50YXIgeSBkb2N1bWVudGFyIGxhIG9wdGltaXphY2nDs24gc2UgZW1wbGVhIFIgTWFya2Rvd24sIGNvbWJpbmFuZG8gY8OzZGlnbyBSIHkgdGV4dG8gZXhwbGljYXRpdm8uIFNlIHV0aWxpemFuIGxpYnJlcsOtYXMgcGFyYSByZW5kZXJpemFyIGdyw6FmaWNvcyB5IGFuaW1hY2lvbmVzLCBsaWJyZXLDrWFzIHBhcmEgaW1wbGVtZW50YXIgbG9zIG3DqXRvZG9zIGhldXLDrXN0aWNvcyB5IGxpYnJlcsOtYXMgcGFyYSBtYW5pcHVsYXIgbG9zIGRhdG9zLg0KDQpgYGB7ciBpbnN0YWxhY2lvbiBkZSBsaWJyZXJpYXN9DQojIERFU0NPTUVOVEFSIFBBUkEgSU5TVEFMQVIgUEFRVUVURVMgRkFMVEFOVEVTDQojaW5zdGFsbC5wYWNrYWdlcygiR0EiKSBHZW5ldGljIEFsZ29yaXRobQ0KI2luc3RhbGwucGFja2FnZXMoInBzbyIpIFBhcnRpY2xlIFN3YXJtIE9wdGltaXphdGlvbg0KI2luc3RhbGwucGFja2FnZXMoInRpZHl2ZXJzZSIpDQojaW5zdGFsbC5wYWNrYWdlcygidmlyaWRpcyIpDQojaW5zdGFsbC5wYWNrYWdlcygiZ2dwbG90MiIpDQojaW5zdGFsbC5wYWNrYWdlcygiZ2dhbmltYXRlIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJnaWZza2kiKQ0KI2luc3RhbGwucGFja2FnZXMoImRwbHlyIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5ciIpDQojaW5zdGFsbC5wYWNrYWdlcygicHVycnIiKQ0KI2luc3RhbGwucGFja2FnZXMoImxlYWZsZXQiKQ0KI2luc3RhbGwucGFja2FnZXMoIm9zcm0iKQ0KI2luc3RhbGwucGFja2FnZXMoInNmIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJnb3IiKQ0KDQpgYGANCg0KYGBge3IgbGlicmVyaWFzfQ0KIyBBTEdPUklUTU9TIEdFTsOJVElDT1MNCmxpYnJhcnkoR0EpDQpsaWJyYXJ5KHBzbykNCg0KIyBNQU5JUFVMQUNJw5NOIFkgVklTVUFMSVpBQ0nDk04gREUgREFUT1MNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KHB1cnJyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeSh2aXJpZGlzKQ0KbGlicmFyeShnZ2FuaW1hdGUpDQpsaWJyYXJ5KGdpZnNraSkNCg0KIyBHRU9MT0NBTElaQUNJw5NOIFkgVklTVUFMSVpBQ0nDk04gRVNQQUNJQUwNCmxpYnJhcnkobGVhZmxldCkNCmxpYnJhcnkob3NybSkNCmxpYnJhcnkoc2YpDQoNCiMgQ09MT05JQSBERSBIT1JNSUdBUw0KbGlicmFyeShnb3IpDQpgYGANCg0KUGFyYSBsb3MgY2Fzb3MgM0QgKHRyZXMgdmFyaWFibGVzKSBlcyBkaWbDrWNpbCB2aXN1YWxpemFyIGRpcmVjdGFtZW50ZSBsYSBmdW5jacOzbiBkZSAzIGRpbWVuc2lvbmVzIChlc3BhY2lvIGRlIDQgdmFyaWFibGVzKSwgcG9yIGxvIHF1ZSBzZSBtb3N0cmFyw6FuIMO6bmljYW1lbnRlIGxvcyBwdW50b3Mgw7NwdGltb3MgYWxjYW56YWRvcy4NCg0KIyBQYXJ0ZSAxLiBPcHRpbWl6YWNpw7NuIG51bcOpcmljYQ0KDQojIyAxLjEgU2VsZWNjacOzbiBlIGltcGxlbWVudGFjacOzbiBkZSBsYXMgZnVuY2lvbmVzIGRlIHBydWViYQ0KDQpQYXJhIG9ic2VydmFyIGVsIGNvbXBvcnRhbWllbnRvIGRlIGNhZGEgdW5vIGRlIGxvcyBtw6l0b2RvcyBkZSBvcHRpbWl6YWNpw7NuIGltcGxlbWVudGFkb3MsIHNlIG9wdMOzIHBvciBsYSBzZWxlY2Npw7NuIGRlIGRvcyBmdW5jaW9uZXMgZGUgcHJ1ZWJhIGVzdMOhbmRhciBwYXJhIGV2YWx1YXIgYWxnb3JpdG1vcyBkZSBvcHRpbWl6YWNpw7NuOiBGdW5jacOzbiBkZSBSb3NlbmJyb2NrIHkgRnVuY2nDs24gZGUgUmFzdHJpZ2luLiBEZWJpZG8gYSBzdSBjb21wbGVqaWRhZCwgYW1iYXMgZnVuY2lvbmVzIHNvbiBpbmRpY2Fkb3JlcyDDunRpbGVzIHBhcmEgY29tcGFyYXIgbGEgZWZpY2FjaWEgZGUgb3B0aW1pemFkb3JlcyBjb21vIGxvcyBBRy4NCg0KUHJldmlvIGEgbGEgaW1wbGVtZW50YWNpw7NuIGRlIGxvcyBhbGdvcml0bW9zIGVuIGVsIGxlbmd1YWplIFIgeSBwcmV2aW8gYSBsYSByZWNvcGlsYWNpw7NuIGRlIHJlc3VsdGFkb3MgeSBsYSBkZXJpdmFjacOzbiBkZSBjb25jbHVzaW9uZXMgYSBwYXJ0aXIgZGUgZXN0b3MsIGVsIGVxdWlwbyBkZWNpZGnDsyByZWFsaXphciB1bmEgYnJldmUgaW52ZXN0aWdhY2nDs24gZ2VuZXJhbCBkZSBsbyBxdWUgc29uIGxhcyBmdW5jaW9uZXMgZGUgcHJ1ZWJhIHBhcmEgcHJvYmxlbWFzIGRlIG9wdGltaXphY2nDs24uIExhIGludmVzdGlnYWNpw7NuIHNlIHJlYWxpesOzIGRlIHRhbCBmb3JtYSBxdWUsIHVuYSB2ZXogY29uY2x1w61kYSwgc2UgcHVkaWVyYW4gY29tcHJlbmRlciBsb3MgY29uY2VwdG9zIGhhc3RhIGVsIHB1bnRvIGRlIGVzdGFyIGVuIGxhcyBjYXBhY2lkYWRlcyByZXNwb25kZXIgbGFzIHNpZ3VpZW50ZXMgcHJlZ3VudGFzIGltcGzDrWNpdGFtZW50ZSBlbiB1bmEgYnJldmUgc2VjY2nDs24gZGUgZXN0ZSB0cmFiYWpvOg0KDQotICAgwr9RdcOpIGVzIHVuYSBmdW5jacOzbiBkZSBwcnVlYmEgZW4gb3B0aW1pemFjacOzbj8NCg0KLSAgIMK/UGFyYSBxdcOpIHNlIHV0aWxpemEgdW5hIGZ1bmNpw7NuIGRlIHBydWViYSBlbiBvcHRpbWl6YWNpw7NuPw0KDQotICAgwr9Dw7NtbyBzZSBwdWVkZW4gY2xhc2lmaWNhciBsYXMgZnVuY2lvbmVzIGRlIHBydWViYT8NCg0KLSAgIMK/Q3XDoWxlcyBzb24gYWxndW5hcyBkZSBsYXMgZnVuY2lvbmVzIGRlIHBydWViYSBtw6FzIHV0aWxpemFkYXM/DQoNCkEgY29udGludWFjacOzbiBzZSBwcmVzZW50YSBkaWNoYSBzZWNjacOzbiBwYXJhIGx1ZWdvIGNvbnRpbnVhciBjb24gZWwgZXN0dWRpbyBtw6FzIGRldGFsbGFkbyBkZSBsYXMgZnVuY2lvbmVzIGVzY29naWRhcy4NCg0KIyMjIDEuMS4xIEJyZXZlIGV4cGxpY2FjacOzbiBkZSBsYXMgZnVuY2lvbmVzIGRlIHBydWViYSBwYXJhIHByb2JsZW1hcyBkZSBvcHRpbWl6YWNpw7NuDQoNClNlZ8O6biAoWWFuZywgMjAxMCksIHVuYSBmdW5jacOzbiBkZSBwcnVlYmEgZXMgdW5hIGZ1bmNpw7NuIGNvbiB1bmFzIHByb3BpZWRhZGVzIGVzcGVjaWFsZXMgcXVlIHBlcm1pdGUgcHJvYmFyIHNpIGVsIHJlbmRpbWllbnRvIGRlIHVuIG3DqXRvZG8gZGUgb3B0aW1pemFjacOzbiBpbXBsZW1lbnRhZG8gZXMgYWNlcHRhYmxlIGJham8gbGFzIGNvbmRpY2lvbmVzIGVzcGVjaWFsZXMgcXVlIGltcG9uZSBsYSBmdW5jacOzbiBkZSBwcnVlYmEuwqANCg0KRXN0byBlcyBlc3BlY2lhbG1lbnRlIMO6dGlsIHBhcmEgdmVyaWZpY2FyIHF1ZSBlbCBtw6l0b2RvIHNlYSBlZmljaWVudGUgYmFqbyBkaXN0aW50YXMgY29uZGljaW9uZXMgZW4gbGFzIHF1ZSBzZSBlc3BlcmEgcXVlIHNlIGltcGxlbWVudGUsIGNvbW8gcG9yIGVqZW1wbG8sIGNhc29zIGVuIGxvcyBxdWUgbGEgZnVuY2nDs24gdGllbmUgbcO6bHRpcGxlcyBtw61uaW1vcyB5L28gbcOheGltb3MgbG9jYWxlcy4NCg0KU2Vnw7puIChNb2xnYSwgMjAwNSksIGxhcyBmdW5jaW9uZXMgZGUgcHJ1ZWJhIHNlIHB1ZWRlbiB1YmljYXIgZW4gdW5hIGRlIGxhcyBzaWd1aWVudGVzIGNsYXNlcywgdG9kYXMgc2llbmRvIGZ1bmNpb25lcyBjb250aW51YXM6DQoNCi0gICAqKkNsYXNlIDE6KiogVW5pbW9kYWwsIGNvbnZleGEsIG11bHRpZGltZW5zaW9uYWwuDQotICAgKipDbGFzZSAyOioqIE11bHRpbW9kYWwsIGRvcyBkaW1lbnNpb25lcyBjb24gdW4gbsO6bWVybyBwZXF1ZcOxbyBkZSBleHRyZW1vcyBsb2NhbGVzLg0KLSAgICoqQ2xhc2UgMyoqOiBNdWx0aW1vZGFsLCBkb3MgZGltZW5zaW9uZXMgY29uIHVuIGdyYW4gbsO6bWVybyBkZSBleHRyZW1vcyBsb2NhbGVzLg0KLSAgICoqQ2xhc2UgNDoqKiBNdWx0aW1vZGFsLCBtdWx0aWRpbWVuc2lvbmFsLCBjb24gdW4gbsO6bWVybyBhbXBsaW8gZGUgZXh0cmVtb3MgbG9jYWxlcy4NCg0KRW4gZWwgY2FzbyBkZSBsYXMgZnVuY2lvbmVzIGVsZWdpZGFzLCBsYSBmdW5jacOzbiBkZSBSb3NlbmJyb2NrIHNlIGNsYXNpZmljYXLDrWEgY29tbyBDbGFzZSAzIHkgbGEgZGUgUmFzdHJpZ2luIGNvbW8gQ2xhc2UgMi4NCg0KQ29tbyBlamVtcGxvLCBlbiBsYSBGaWd1cmEgMSBzZSBwcmVzZW50YW4gYWxndW5hcyBmdW5jaW9uZXMgZGUgcHJ1ZWJhIHF1ZSBubyBzZSBlbGlnaWVyb24gcGFyYSBlc3RlIHRyYWJham8sIHBlcm8gcXVlIHNvbiBpZ3VhbCBkZSByZWxldmFudGVzLCBpbXBvcnRhbnRlcyB5IGNvbcO6bm1lbnRlIHV0aWxpemFkYXMgZW4gbGEgcHLDoWN0aWNhIChNb2xnYSwgMjAwNSk6DQoNCi0gICBGdW5jacOzbiBkZSBEZSBKb25nIChDbGFzZSAxKS4NCg0KLSAgIEZ1bmNpw7NuIGRlIEdyaWV3YW5nayAoQ2xhc2UgMikuDQoNCi0gICBGdW5jacOzbiBkZSBMYW5nZXJtYW5uIChDbGFzZSAzKS4NCg0KLSAgIEZ1bmNpw7NuIGRlIEFja2xleSAoQ2xhc2UgNCkuDQoNCiMjIyAxLjEuMiBJbXBsZW1lbnRhY2nDs24gZGUgZnVuY2lvbmVzIGRlIGdyYWZpY2FjacOzbg0KDQpTZSBkZWZpbmVuIGxhcyBmdW5jaW9uZXMgYSB1c2FyIHBhcmEgZ2VuZXJhciBsb3MgZ3LDoWZpY29zIGRlIGNhZGEgZnVuY2nDs24gZGUgZXN0YSBzZWNjacOzbjoNCg0KYGBge3J9DQpnZW5lcmF0ZV9mdW5jdGlvbl9ncmFwaCA8LSBmdW5jdGlvbihmdW5jXzJkLCBsb3dfYm91bmQsIHVwcGVyX2JvdW5kKSB7DQogIHgxIDwtIHNlcShsb3dfYm91bmQsIHVwcGVyX2JvdW5kLCBsZW5ndGgub3V0ID0gNTApDQogIHgyIDwtIHNlcShsb3dfYm91bmQsIHVwcGVyX2JvdW5kLCBsZW5ndGgub3V0ID0gNTApDQogIGZfeCA8LSBvdXRlcih4MSx4MiwgRlVOID0gZnVuY18yZCkNCiAgDQogIGNvbG9yZXMgICAgICAgIDwtIHZpcmlkaXM6Om1hZ21hKG4gPSAxMDAsIGFscGhhID0gMC43KQ0KICB6LmZhY2V0LmNlbnRlciA8LSAoZl94Wy0xLCAtMV0gKyBmX3hbLTEsIC1uY29sKGZfeCldICsNCiAgICAgICAgICAgICAgICAgICAgICAgZl94Wy1ucm93KGZfeCksIC0xXSArDQogICAgICAgICAgICAgICAgICAgICAgIGZfeFstbnJvdyhmX3gpLCAtbmNvbChmX3gpXSkvNA0KICB6LmZhY2V0LnJhbmdlICA8LSBjdXQoei5mYWNldC5jZW50ZXIsIDEwMCkNCiAgDQogIHBhcihtYWkgPSBjKDAsMCwwLDApKQ0KICBwZXJzcCh4ID0geDEsIHkgPSB4MiwgeiA9IGZfeCwNCiAgICAgICAgc2hhZGUgPSAwLjgsDQogICAgICAgIHBoaSA9IDMwLA0KICAgICAgICB0aGV0YSA9IDMwLA0KICAgICAgICBjb2wgPSBjb2xvcmVzW3ouZmFjZXQucmFuZ2VdLA0KICAgICAgICBheGVzID0gRkFMU0UpDQp9DQoNCmdlbmVyYXRlX2xldmVsX2N1cnZlcyA8LSBmdW5jdGlvbihmdW5jXzJkLCBsb3dfYm91bmQsIHVwcGVyX2JvdW5kKSB7DQogIG5fbGVuZ3RoIDwtIDEwMA0KICB4MSA8LSBzZXEobG93X2JvdW5kLCB1cHBlcl9ib3VuZCwgbGVuZ3RoLm91dCA9IG5fbGVuZ3RoKQ0KICB4MiA8LSBzZXEobG93X2JvdW5kLCB1cHBlcl9ib3VuZCwgbGVuZ3RoLm91dCA9IG5fbGVuZ3RoKQ0KICBYIDwtIGV4cGFuZC5ncmlkKHgxLCB4MikNCiAgeiA8LSBmdW5jXzJkKFhbLDFdLCBYWywyXSkNCiAgWiA8LSBtYXRyaXgoeiwgbmNvbCA9IG5fbGVuZ3RoLCBucm93ID0gbl9sZW5ndGgpDQogIGNvbnRvdXIoDQogICAgeCA9IHgxLA0KICAgIHkgPSB4MiwNCiAgICB6ID0gWiwNCiAgICBubGV2ZWxzID0gMTAwLA0KICAgIGxhcyA9IDEsDQogICAgeGxhYiA9IGV4cHJlc3Npb24oeFsxXSksDQogICAgeWxhYiA9IGV4cHJlc3Npb24oeFsyXSksDQogICAgbWFpbiA9IGV4cHJlc3Npb24ocGFzdGUoDQogICAgICAiRnVuY2nDs24gZGUgUm9zZW5icm9jazogIiwNCiAgICAgIGYoeFsxXSx4WzJdKT09MTAwKih4WzJdLXhbMV1eMileMisoMS14WzFdKV4yDQogICAgKSksDQogICAgc3ViID0gIkN1cnZhcyBkZSBuaXZlbCBkZSBsYSBmdW5jacOzbiINCiAgKQ0KfQ0KDQpnZW5lcmF0ZV9sZXZlbF9jdXJ2ZXNfMiA8LSBmdW5jdGlvbihmdW5jXzJkLCBsb3dfYm91bmQsIHVwcGVyX2JvdW5kKSB7DQogIHgxIDwtIHNlcShsb3dfYm91bmQsIHVwcGVyX2JvdW5kLCBsZW5ndGgub3V0ID0gNTApDQogIHgyIDwtIHNlcShsb3dfYm91bmQsIHVwcGVyX2JvdW5kLCBsZW5ndGgub3V0ID0gNTApDQogIA0KICBkYXRvcyA8LSBleHBhbmQuZ3JpZCh4MSA9IHgxLCB4MiA9IHgyKQ0KICBkYXRvcyA8LSBkYXRvcyAlPiUNCiAgICAgICAgICAgbXV0YXRlKGZfeCA9IG1hcDJfZGJsKHgxLCB4MiwgLmYgPSBmdW5jXzJkKSkNCiAgDQogIGZfbGV2ZWxfY3VydmVzIDwtIGdncGxvdChkYXRhID0gZGF0b3MsIGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IGZfeCkpICsNCiAgICBnZW9tX2NvbnRvdXIoYWVzKGNvbG91ciA9IHN0YXQobGV2ZWwpKSwgYmlucyA9IDMwKSArDQogICAgbGFicyh0aXRsZSA9ICJmKHgxLHgyKSA9IHgxXjIgKyB4Ml4yIikgKw0KICAgIHRoZW1lX2J3KCkgKw0KICAgIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCiAgcmV0dXJuKGZfbGV2ZWxfY3VydmVzKQ0KfQ0KDQpnZW5lcmF0ZV9jb3VudG91ciA8LSBmdW5jdGlvbihmdW5jXzJkLCBsb3dfYm91bmQsIHVwcGVyX2JvdW5kKSB7DQogIG5fbGVuZ3RoIDwtIDEwMA0KICB4MSA8LSBzZXEobG93X2JvdW5kLCB1cHBlcl9ib3VuZCwgbGVuZ3RoLm91dCA9IG5fbGVuZ3RoKQ0KICB4MiA8LSBzZXEobG93X2JvdW5kLCB1cHBlcl9ib3VuZCwgbGVuZ3RoLm91dCA9IG5fbGVuZ3RoKQ0KICBleHBhbmQuZ3JpZChYMSA9IHgxLCBYMiA9IHgyKSAlPiUNCiAgICBtdXRhdGUoWiA9IGZ1bmNfMmQoWDEsIFgyKSkgJT4lDQogICAgZ2dwbG90KGFlcyhYMSwgWDIsIHogPSBaKSkgKw0KICAgIGdlb21fY29udG91cigpICsNCiAgICBnZW9tX2NvbnRvdXJfZmlsbGVkKCkNCn0NCmBgYA0KDQojIyMgMS4xLjMgRnVuY2nDs24gZGUgUm9zZW5icm9jaw0KDQpFcyB1bmEgZnVuY2nDs24gbm8gY29udmV4YSBpbnRyb2R1Y2lkYSBwb3IgUm9zZW5icm9jayBlbiAxOTYw4oCLIChXaWtpcGVkaWEsIHMuZi4sIFJvc2VuYnJvY2sgZnVuY3Rpb24pLiBTdSBwYWlzYWplIGZvcm1hIHVuIHZhbGxlIGN1cnZvIGVzdHJlY2hvIHF1ZSBkaWZpY3VsdGEgbGEgY29udmVyZ2VuY2lhIGhhY2lhIGVsIG3DrW5pbW8uDQoNCkRlZmluaWNpw7NuIGVuIG4gZGltZW5zaW9uZXM6DQoNCiRmKFgpID0gXHN1bV97aT0xfV57bi0xfTEwMChYX3tpKzF9LVhfaV4yKV4yICsgKDEtWF9pKV4yJA0KDQpEZWZpbmljacOzbiBlbiAyRDoNCg0KJGYoeCx5KT0gMTAwKHkteF4yKV4yICsgKDEteCleMiQNCg0KRGVmaW5pY2nDs24gZW4gM0Q6DQoNCiRmKHgseSx6KT0xMDBbKHkteF4yKV4yKyh6LXleMileMl0gKyAoMS14KV4yICsgKDEteSleMiQNCg0KQWxndW5hcyBjYXJhY3RlcsOtc3RpY2FzIHkgcHJvcGllZGFkZXMgaW1wb3J0YW50ZXPCoCBzb24gbGFzIHNpZ3VpZW50ZXM6DQoNCi0gICBNw61uaW1vIGdsb2JhbDoNCg0KICAgICR4XzEsLi4uLHhfbiA9MSwgZih4XzEsLi4uLHhfbikgPSAwJA0KDQotICAgRG9taW5pbyBkZSBiw7pzcXVlZGE6wqANCg0KICAgICQtXGluZnR5IFxsZXEgWF9pIFxsZXEgXGluZnR5JA0KDQogICAgJDEgXGxlcSBpIFxsZXEgbiQNCg0KLSAgIENvbm9jaWRhIHRhbWJpw6luIGNvbW8gbGEgZnVuY2nDs24gYmFuYW5hIGRlIFJvc2VuYnJvY2suDQoNCi0gICBUaWVuZSBmb3JtYSBkZSB2YWxsZSwgZWwgY3VhbCBlcyB0cml2aWFsIGVuY29udHJhcmxvLiBTaW4gZW1iYXJnbywgbGEgY29udmVyZ2VuY2lhIGFsIG3DrW5pbW8gZ2xvYmFsIGVzIGRpZsOtY2lsLg0KDQpFc3RhcyBzb24gYWxndW5hcyBoaXDDs3Rlc2lzIHkgZXhwZWN0YXRpdmFzIHF1ZSBzZSB0dXZpZXJvbiBwYXJhIGVsIHJlbmRpbWllbnRvIGRlIGNhZGEgbcOpdG9kbyBpbXBsZW1lbnRhZG8gY29uIGVzdGEgZnVuY2nDs246DQoNCi0gICAqKk3DqXRvZG8gZGUgZGVzY2Vuc28gZGUgZ3JhZGllbnRlOioqDQoNCiAgICAtICAgU2kgc2UgdWJpY2EgZWwgcHVudG8gaW5pY2lhbCBzb2JyZSBsYXMgcmVjdGFzIHRhbmdlbnRlcyBjb24gbWF5b3IgZ3JhZGllbnRlIGEgbGEgZnVuY2nDs24gZW50b25jZXMgZWwgbcOpdG9kbyBsbGVnYXLDoSBtw6FzIHLDoXBpZG8gYSB1biBtw61uaW1vLg0KICAgIC0gICBQb3IgZWwgY29udHJhcmlvLCBzaSBsYSByZWN0YSB0YW5nZW50ZSB0aWVuZSB1bmEgZ3JhZGllbnRlIG3DoXMgY2VyY2FuYSBhIGNlcm8sIGVsIG3DqXRvZG8gbGxlZ2Fyw6EgbcOhcyBsZW50YW1lbnRlLCBlcyBkZWNpciwgcmVxdWVyaXLDoSBkZSBtw6FzIGl0ZXJhY2lvbmVzIHBhcmEgbGxlZ2FyIGFsIHB1bnRvIG3DrW5pbW8uDQoNCi0gICAqKk3DqXRvZG8gZGUgYWxnb3JpdG1vcyBldm9sdXRpdm9zOioqDQoNCiAgICAtICAgTG9zIGNyaXRlcmlvcyBwYXJhIHNlbGVjY2lvbmFyIGxvcyBpbmRpdmlkdW9zIGEgcmVwcm9kdWNpciB2YSBhIHNlciBtdXkgaW1wb3J0YW50ZSBwYXJhIGVsIHJlbmRpbWllbnRvIGRlbCBtw6l0b2RvLg0KDQotICAgKipNw6l0b2RvIGRlIG9wdGltaXphY2nDs24gZGUgcGFydMOtY3VsYXM6KioNCg0KICAgIC0gICBFc3RlIG3DqXRvZG8gbm8gdmEgYSB0ZW5lciBtdWNoYXMgZGlmaWN1bHRhZGVzIHBhcmEgZW5jb250cmFyIGVsIHB1bnRvIMOzcHRpbW8gZXMgbGEgZnVuY2nDs24gZGUgUm9zZW5icm9jay4NCiAgICAtICAgRWwgY3JpdGVyaW8gcGFyYSBwYXJhciBsYXMgaXRlcmFjaW9uZXMgbm8gdmEgYSBjYW1iaWFyIG11Y2hvIGxvcyByZXN1bHRhZG9zIGVuIGVzdGUgY2Fzby4NCg0KLSAgICoqTcOpdG9kbyBkZSBldm9sdWNpw7NuIGRpZmVyZW5jaWFsOioqDQoNCiAgICAtICAgU2UgY29tcG9ydGEgc2ltaWxhciBhIGxvcyBtw6l0b2RvcyBldm9sdXRpdm9zIGVuIDIgZGltZW5zaW9uZXMuDQoNCkxhIGltcGxlbWVudGFjacOzbiBkZSBsYSBmdW5jacOzbiBkZSBSb3NlbmJyb2NrIGVuIDIsIDMgeSBOIGRpbWVuc2lvbmVzIHNlIG11ZXN0cmEgYSBjb250aW51YWNpw7NuOg0KDQpgYGB7cn0NCiMgMiBEaW1lbnNpb25lcw0KZl9yb3NlbmJyb2NrXzJkIDwtIGZ1bmN0aW9uKHgsIHkpIHsgICANCiAgZl92YWx1ZSA8LSAxMDAqKHktKHheMikpXjIgKyAoKDEteCleMikgICANCiAgcmV0dXJuKGZfdmFsdWUpIA0KfSAgDQoNCiMgMyBEaW1lbnNpb25lcw0KZl9yb3NlbmJyb2NrXzNkIDwtIGZ1bmN0aW9uKHgsIHksIHopIHsgICANCiAgZl92YWx1ZSA8LSAxMDAqKCh5LXheMileMiArICh6LXleMileMikgKyAoMS14KV4yICsgKDEteSleMiAgIA0KICByZXR1cm4oZl92YWx1ZSkgDQp9DQoNCiMgTiBEaW1lbnNpb25lcw0KZl9yb3NlbmJyb2NrIDwtIGZ1bmN0aW9uKHgpew0KICB4XzEgPC0gdGFpbCh4LCAtMSkNCiAgeCA8LSBoZWFkKHgsIC0xKQ0KICB6IDwtIHN1bSgoMTAwKigoeF8xLSh4XjIpKV4yKSkrKCgxLXgpXjIpKQ0KICByZXR1cm4oeikNCn0NCmBgYA0KDQpMYXMgZ3LDoWZpY2FzIGRlIGxhIGZ1bmNpw7NuIGRlIFJvc2VuYnJvY2sgZW4gMiB5IDMgZGltZW5zaW9uZXMgc2UgbXVlc3RyYW4gYSBjb250aW51YWNpw7NuOg0KDQpgYGB7cn0NCmdlbmVyYXRlX2NvdW50b3VyKGZfcm9zZW5icm9ja18yZCwgLTUsIDUpDQpgYGANCg0KYGBge3J9DQpnZW5lcmF0ZV9sZXZlbF9jdXJ2ZXMoZl9yb3NlbmJyb2NrXzJkLCAtNSwgNSkNCmBgYA0KDQpgYGB7cn0NCmdlbmVyYXRlX2Z1bmN0aW9uX2dyYXBoKGZfcm9zZW5icm9ja18yZCwgLTUsIDUpDQpgYGANCg0KIyMjIDEuMS40IEZ1bmNpw7NuIGRlIFJhc3RyaWdpbg0KDQpMYSBmdW5jacOzbiBSYXN0cmlnaW4gKDE5NzQpIGVzIHVuYSBmdW5jacOzbiBubyBjb252ZXhhIHkgYWx0YW1lbnRlIG11bHRpbW9kYWwsIGNvbiBudW1lcm9zb3MgbcOtbmltb3MgbG9jYWxlcywgbG8gcXVlIGxhIGhhY2UgZGlmw61jaWwgZGUgb3B0aW1pemFy4oCLIChXaWtpcGVkaWEsIHMuZi4sIFJhc3RyaWdpbiBmdW5jdGlvbikuDQoNClByZXNlbnRhIG3Dumx0aXBsZXMgbcOtbmltb3MgbG9jYWxlcyBkaXNwdWVzdG9zIGRlIGZvcm1hIHJlZ3VsYXLigIssIGxvIHF1ZSBsbyBjb252aWVydGUgZW4gdW4gZGVzYWbDrW8gdMOtcGljbyBwYXJhIGFsZ29yaXRtb3MgZGUgb3B0aW1pemFjacOzbi4NCg0KRGVmaW5pY2nDs24gZW4gbiBkaW1lbnNpb25lczoNCg0KJGYoWCk9QW4gKyBcc3VtX3tpPTF9Xm5bWF9pXjIgLUFcY29zKDJccGkgWF9pKV0sIEE9MTAkDQoNCkRlZmluaWNpw7NuIGVuIDJEOg0KDQokZih4LHkpPXheMit5XjIrQVsyLVxjb3MoMlxwaSB4KSAtIFxjb3MoMlxwaSB5KV0sIEE9MTAkDQoNCkRlZmluaWNpw7NuIGVuIDNEOg0KDQokZih4LHkpPXheMit5XjIrel4yK0FbMy1cY29zKDJccGkgeCkgLSBcY29zKDJccGkgeSktXGNvcygyXHBpIHopXSwgQT0xMCQNCg0KQWxndW5hcyBjYXJhY3RlcsOtc3RpY2FzIHkgcHJvcGllZGFkZXMgaW1wb3J0YW50ZXMgc29uIGxhcyBzaWd1aWVudGVzOg0KDQotICAgTcOtbmltbyBnbG9iYWw6DQoNCiAgICAtICAgJGYoMCwuLi4wKT0wJA0KDQotICAgRG9taW5pbyBkZSBiw7pzcXVlZGE6DQoNCiAgICAtICAgJC01LjEyIFxsZXEgWF9pIFxsZXEgNS4xMiQNCg0KLSAgIFBhcnRpY3VsYXJtZW50ZSwgaGFsbGFyIGVsIG3DrW5pbW8gZGUgZXN0YSBmdW5jacOzbiBlcyB1biBwcm9ibGVtYSBkaWbDrWNpbCBkZWJpZG8gYSBsYSBsYXJnYSBjYW50aWRhZCBkZSBtw61uaW1vcyBsb2NhbGVzLg0KDQpFc3RhcyBzb24gYWxndW5hcyBoaXDDs3Rlc2lzIHkgZXhwZWN0YXRpdmFzIHF1ZSBzZSB0dXZpZXJvbiBwYXJhIGVsIHJlbmRpbWllbnRvIGRlIGNhZGEgbcOpdG9kbyBpbXBsZW1lbnRhZG8gY29uIGVzdGEgZnVuY2nDs246DQoNCi0gICAqKk3DqXRvZG8gZGUgZGVzY2Vuc28gZGUgZ3JhZGllbnRlOioqDQoNCiAgICAtICAgRWwgw6l4aXRvIGRlbCBtw6l0b2RvIGVuIGFsY2FuemFyIHVuIG3DrW5pbW8gZGVwZW5kZXLDoSBlbm9ybWVtZW50ZSBkZWwgcHVudG8gaW5pY2lhbCBlbGVnaWRvIGRlYmlkbyBhIGxvcyBtw7psdGlwbGVzIG3DrW5pbW9zIGxvY2FsZXMgcXVlIHRpZW5lIGVzdGEgZnVuY2nDs24uDQoNCi0gICAqKk3DqXRvZG8gZGUgYWxnb3JpdG1vcyBldm9sdXRpdm9zOioqDQoNCiAgICAtICAgRXMgaW1wb3J0YW50ZSBoYWNlciB1bmEgYnVlbmEgcmVwcmVzZW50YWNpw7NuIGRlIGxvcyBpbmRpdmlkdW9zLCB5YSBxdWUgZWwgcmVuZGltaWVudG8gcHVlZGUgdmFyaWFyIGRlcGVuZGllbmRvIGRlIGxhIHNlbGVjY2nDs24uDQogICAgLSAgIExhIHBvYmxhY2nDs24gaW5pY2lhbCBkZWJlIHNlciByZXByZXNlbnRhdGl2YSwgZGlmZXJlbnRlcyBlbnRyZSBlbGxvcyB5IHF1ZSBubyBzZWFuIGRlbWFzaWFkb3MuIFBhcmEgZXN0YSBmdW5jacOzbiBlbiBwYXJ0aWN1bGFyIGVuY29udHJhciBsYSBzb2x1Y2nDs24gw7NwdGltYSB2YSBhIGRlcGVuZGVyIG11Y2hvIGRlIGxhIHByb2JhYmlsaWRhZCBkZSBtdXRhY2nDs24NCg0KLSAgICoqTcOpdG9kbyBkZSBvcHRpbWl6YWNpw7NuIGRlIHBhcnTDrWN1bGFzOioqDQoNCiAgICAtICAgTGEgdWJpY2FjacOzbiBkZSBsYSBudWJlIGRlIHBhcnTDrWN1bGFzIHZhIGEgYWZlY3RhciBlbCByZW5kaW1pZW50byBlbiBlc3RhIGZ1bmNpw7NuLCB5YSBxdWUgZWwgYWxnb3JpdG1vIHB1ZWRlIHF1ZWRhcnNlIGVzdGFuY2FkbyBlbiB1biBtw61uaW1vIGxvY2FsIHNpIGxhIHViaWNhY2nDs24gbm8gZXMgbGEgbWVqb3IuDQogICAgLSAgIEVsIGNyaXRlcmlvIHBhcmEgZGV0ZW5lciBlbCBhbGdvcml0bW8gZGViZSBlc3RhciBiaWVuIGFqdXN0YWRvLCBwYXJhIGV2aXRhciBlc3RhbmNhbWllbnRvcy4NCg0KLSAgICoqTcOpdG9kbyBkZSBldm9sdWNpw7NuIGRpZmVyZW5jaWFsOioqDQoNCiAgICAtICAgRXMgbcOhcyBlZmljaWVudGUgcXVlIGxvcyBvdHJvcyBtw6l0b2RvcyBldm9sdXRpdm9zIHlhIHF1ZSBzdSBmdWVydGUgc29uIGxhcyBtw7psdGlwbGVzIGRpbWVuc2lvbmVzLg0KDQpMYSBpbXBsZW1lbnRhY2nDs24gZGUgbGEgZnVuY2nDs24gZGUgUmFzdHJpZ2luIGVuIDIsIDMgeSBOIGRpbWVuc2lvbmVzIHNlIG11ZXN0cmEgYSBjb250aW51YWNpw7NuOg0KDQpgYGB7cn0NCiMgMiBEaW1lbnNpb25lcyANCmZfcmFzdHJpZ2luXzJkIDwtIGZ1bmN0aW9uKHgsIHkpIHsNCiAgQSA9IDEwICAgDQogIGZfdmFsdWUgPC0geF4yICsgeV4yICsgQSooMiAtIGNvcygyKnBpKngpIC0gY29zKDIqcGkqeSkpICAgDQogIHJldHVybihmX3ZhbHVlKSANCn0gIA0KDQojIDMgRGltZW5zaW9uZXMgDQpmX3Jhc3RyaWdpbl8zZCA8LSBmdW5jdGlvbih4LCB5LCB6KSB7ICAgDQogIEEgPSAxMCAgIA0KICBmX3ZhbHVlIDwtIHheMiArIHleMiArIHpeMiArIEEqKDMgLSBjb3MoMipwaSp4KSAtIGNvcygyKnBpKnkpIC0gY29zKDIqcGkqeikpICAgDQogIHJldHVybihmX3ZhbHVlKSANCn0NCg0KIyBOIERpbWVuc2lvbmVzDQpmX3Jhc3RyaWdpbiA8LWZ1bmN0aW9uKHgpew0KICBBIDwtIDEwDQogIG4gPC0gbGVuZ3RoKHgpDQogIHogPC0gKEEqbikgKyBzdW0oeF4yIC0gQSpjb3MoMipwaSp4KSkNCiAgcmV0dXJuKHopDQp9DQpgYGANCg0KTGFzIGdyw6FmaWNhcyBkZSBsYSBmdW5jacOzbiBkZSBSYXN0cmlnaW4gZW4gMiB5IDMgZGltZW5zaW9uZXMgc2UgbXVlc3RyYW4gYSBjb250aW51YWNpw7NuOg0KDQpgYGB7cn0NCmdlbmVyYXRlX2NvdW50b3VyKGZfcmFzdHJpZ2luXzJkLCAtMTAsIDEwKQ0KYGBgDQoNCmBgYHtyfQ0KZ2VuZXJhdGVfbGV2ZWxfY3VydmVzKGZfcmFzdHJpZ2luXzJkLCAtMTAsIDEwKQ0KYGBgDQoNCmBgYHtyfQ0KZ2VuZXJhdGVfbGV2ZWxfY3VydmVzXzIoZl9yYXN0cmlnaW5fMmQsIC0xMCwgMTApDQpgYGANCg0KYGBge3J9DQpnZW5lcmF0ZV9mdW5jdGlvbl9ncmFwaChmX3Jhc3RyaWdpbl8yZCwgLTEwLCAxMCkNCmBgYA0KDQojIyAxLjMgTcOpdG9kbyBkZSBkZXNjZW5zbyBkZSBncmFkaWVudGUNCg0KIyMjIDEuMy4xIEltcGxlbWVudGFjacOzbiBlbiBSIGRlIGRlc2NlbnNvIHBvciBncmFkaWVudGUNCg0KIyMjIyBJbXBsZW1lbnRhY2nDs24gZGUgZGVyaXZhZGEgcGFyY2lhbA0KDQpgYGB7cn0NCnBhcnRpYWxfZGV2IDwtIGZ1bmN0aW9uKHgsaSxmdW4saD0wLjAxKXsNCiAgICBlIDwtIHgqMCAjIGNyZWEgdW4gdmVjdG9yIGRlIGNlcm9zIGRlIGxhIG1pc21hIGxvbmdpdHVkIGRlIHgNCiAgICBlW2ldIDwtIGgNCiAgICB5IDwtIChmdW4oeCtlKS1mdW4oeC1lKSkvKDIqaCkNCiAgcmV0dXJuKHkpDQp9DQpgYGANCg0KIyMjIyBJbXBsZW1lbnRhY2nDs24gZGVsIGdyYWRpZW50ZQ0KDQpgYGB7cn0NCm51bV9ncmFkIDwtIGZ1bmN0aW9uKHgsZnVuLGg9MC4wMSl7DQogICMgeDogcHVudG8gZGVsIGVzcGFjaW8gZG9uZGUgc2UgZGViZSBldmFsdWFyIGVsIGdyYWRpZW50ZQ0KICAjIGZ1bjogZnVuY2nDs24gcGFyYSBsYSBxdWUgc2UgZGVzZWEgY2FsY3VsYXIgZWwgZ3JhZGllbnRlIGVuIHgNCiAgIyBoOiBlcyBlbCB0YW1hw7FvIGRlIHZlbnRhbmEgcGFyYSBlbCBjw6FsY3VsbyBkZSBsYSBkZXJpdmFkYSBudW3DqXJpY2ENCiAgZCA8LSBsZW5ndGgoeCkNCiAgeSA8LSBtYXBwbHkoRlVOPXBhcnRpYWxfZGV2LGk9MTpkLE1vcmVBcmdzPWxpc3QoeD14LGg9aCxmdW49ZnVuKSkNCiAgcmV0dXJuKHkpDQp9DQpgYGANCg0KIyMjIyBJbXBsZW1lbnRhY2nDs24gZGUgZGVyaXZhZGEgZGVsIGdyYWRpZW50ZQ0KDQpgYGB7cn0NCmRlcml2X2dyYWQgPC0gZnVuY3Rpb24oeCxmdW4saT0xLGg9MC4wMSl7DQogICMgeDogcHVudG8gZW4gZWwgcXVlIHNlIGV2YWzDumEgZWwgZ3JhZGllbnRlDQogICMgZnVuOiBmdW5jacOzbiBwYXJhIGxhIGN1YWwgc2UgY2FsY3VsYSBsYSBkZXJpdmFkYSBkZWwgZ3JhZGllbnRlIHJlc3BlY3RvIGEgbGEgw61lc2ltYSBjb21wb25lbnRlDQogICMgaTogaS3DqXNpbWEgY29tcG9uZW50ZSBkZWwgdmVjdG9yIHggY29uIHJlc3BlY3RvIGEgbGEgcXVlIHNlIGRlcml2YQ0KICAgIGUgPC0geCowICMgY3JlYSB1biB2ZWN0b3IgZGUgY2Vyb3MgZGUgbGEgbWlzbWEgbG9uZ2l0dWQgZGUgeA0KICAgIGVbaV0gPC0gaA0KICAgIHkgPC0gKG51bV9ncmFkKHgrZSxmdW49ZnVuLGg9aCktbnVtX2dyYWQoeC1lLGZ1bj1mdW4saD1oKSkvKDIqaCkNCiAgICByZXR1cm4oeSkNCn0NCmBgYA0KDQojIyMjIEltcGxlbWVudGFjacOzbiBkZSBtYXRyaXogSGVzc2lhbmENCg0KYGBge3J9DQptYXRyaXpfaGVzc2lhbmEgPC0gZnVuY3Rpb24oeCxmdW4saD0wLjAxKXsNCiAgIyB4OiBwdW50byBlbiBlbCBxdWUgc2UgZXZhbMO6YSBsYSBtYXRyaXogaGVzc2lhbmENCiAgIyBmdW46IGZ1bmNpw7NuIGEgbGEgcXVlIHNlIGxlIGNhbGN1bGEgbGEgbWF0cml6IGhlc3NpYW5hIGVuIHgNCiAgIyBoOiBlcyBlbCB0YW1hw7FvIGRlIHZlbnRhbmEgcGFyYSBlbCBjw6FsY3VsbyBkZSBsYSBkZXJpdmFkYSBudW3DqXJpY2ENCiAgZCA8LSBsZW5ndGgoeCkNCiAgeSA8LSBtYXBwbHkoRlVOPWRlcml2X2dyYWQsaT0xOmQsTW9yZUFyZ3M9bGlzdCh4PXgsaD1oLGZ1bj1mdW4pLFNJTVBMSUZZID0gVFJVRSkNCiAgcmV0dXJuKHkpDQp9DQpgYGANCg0KIyMjIyBJbXBsZW1lbnRhY2nDs24gY29tcGxldGEgZGUgb3B0aW1pemFkb3IgbXVsdGl2YXJpYWRvIHBvciBkZXNjZW5zbyBkZSBncmFkaWVudGUNCg0KYGBge3J9DQpvcHRpbWl6YWRvcl9tdWx0X251bWRldiA8LSBmdW5jdGlvbih4MCxmdW4sbWF4X2V2YWw9MTAwLGg9MC4wMSxldGE9MC4wMSl7DQogIHggPC0gbWF0cml4KE5BLG5jb2wgPWxlbmd0aCh4MCksIG5yb3cgPSBtYXhfZXZhbCkNCiAgeFsxLF0gPC0geDANCiAgZm9yIChpIGluIDI6bWF4X2V2YWwpew0KICAgIG51bV9ncmFkX2Z1biA8LSBudW1fZ3JhZCh4W2ktMSxdLGZ1bixoKQ0KICAgIEggPC0gbWF0cml6X2hlc3NpYW5hKHhbaS0xLF0sZnVuLGgpDQogICAgY2FtYmlvIDwtIC0gZXRhKnNvbHZlKEgpJSolbnVtX2dyYWRfZnVuDQogICAgeFtpLF0gPC0geFtpLTEsXSArIGNhbWJpbw0KICAgIGNhbWJpb19vcHQgPC0gc3FydChzdW0oKHhbaS0xLF0teFtpLF0pXjIpKQ0KICAgIGlmIChjYW1iaW9fb3B0PDAuMDAwMDEpew0KICAgICAgYnJlYWsNCiAgICB9DQogIH0NCiAgcmV0dXJuKHhbMTppLF0pDQp9DQpgYGANCg0KIyMjIDEuMy4yIE9wdGltaXphY2nDs24gZGUgbGEgZnVuY2nDs24gZGUgUm9zZW5icm9jayBlbiAyIGRpbWVuc2lvbmVzDQoNCmBgYHtyfQ0KIyBFamVjdWNpw7NuIGRlbCBtw6l0b2RvDQpzb2xfcm9zZW4gPC0gb3B0aW1pemFkb3JfbXVsdF9udW1kZXYoZl9yb3NlbmJyb2NrLHgwPWMoLTQsLTQpLGV0YT0xKQ0KDQojIEdyYWZpY2FjacOzbiBkZWwgcHJvY2VzbyBkZSBvcHRpbWl6YWNpw7NuDQpuX2xlbmd0aCA8LSAxMDANCngxIDwtIHNlcSgtNSwgNSwgbGVuZ3RoLm91dCA9IG5fbGVuZ3RoKQ0KeDIgPC0gc2VxKC01LCA1LCBsZW5ndGgub3V0ID0gbl9sZW5ndGgpDQpYIDwtIGV4cGFuZC5ncmlkKHgxLCB4MikNCnogPC0gZl9yb3NlbmJyb2NrXzJkKFhbLDFdLCBYWywyXSkNClogPC0gbWF0cml4KHosIG5jb2wgPSBuX2xlbmd0aCwgbnJvdyA9IG5fbGVuZ3RoKQ0KY29udG91cigNCiAgeCA9IHgxLA0KICB5ID0geDIsDQogIHogPSBaLA0KICBubGV2ZWxzID0gMTAwLA0KICBsYXMgPSAxLA0KICB4bGFiID0gZXhwcmVzc2lvbih4WzFdKSwNCiAgeWxhYiA9IGV4cHJlc3Npb24oeFsyXSksDQogIG1haW4gPSBleHByZXNzaW9uKHBhc3RlKA0KICAgICJGdW5jacOzbiBkZSBSb3NlbmJyb2NrOiAiLA0KICAgIGYoeFsxXSx4WzJdKT09MTAwKih4WzJdLXhbMV1eMileMisoMS14WzFdKV4yKQ0KICApLA0KICBzdWIgPSAiQ3VydmFzIGRlIG5pdmVsIGRlIGxhIGZ1bmNpw7NuIg0KKQ0KbGluZXMoc29sX3Jvc2VuLCB0eXBlPSJiIixjZXg9MS41LGNvbD0icmVkIikNCg0KYGBgDQoNCkFuaW1hY2nDs24gZGUgbGEgb3B0aW1pemFjacOzbg0KDQpgYGB7cn0NCiMgVHJhbnNmb3JtYSBsYSBzb2x1Y2nDs24gZW4gdW4gZGF0YS5mcmFtZQ0Kc29sIDwtIGFzLmRhdGEuZnJhbWUoc29sX3JhczJkKQ0KY29sbmFtZXMoc29sKSA8LSBjKCJ4MSIsICJ4MiIpDQpzb2wkc3RlcCA8LSAxOm5yb3coc29sKQ0KDQojIFByZXBhcmEgbG9zIGRhdG9zIGRlIGZvbmRvDQp4MSA8LSBzZXEoLTUsIDUsIGxlbmd0aC5vdXQgPSAxMDApDQp4MiA8LSBzZXEoLTUsIDUsIGxlbmd0aC5vdXQgPSAxMDApDQpYIDwtIGV4cGFuZC5ncmlkKHgxID0geDEsIHgyID0geDIpDQoNClgkeiA8LSBmX3Jvc2VuYnJvY2tfMmQoWCR4MSwgWCR4MikNCg0KIyBDcmVhIGxhIGFuaW1hY2nDs24NCnAgPC0gZ2dwbG90KCkgKw0KICBnZW9tX2NvbnRvdXIoZGF0YSA9IFgsIGFlcyh4ID0geDEsIHkgPSB4MiwgeiA9IHopLCBiaW5zID0gMjAsIGNvbG9yID0gImdyYXk3MCIpICsNCiAgZ2VvbV9wb2ludChkYXRhID0gc29sLCBhZXMoeCA9IHgxLCB5ID0geDIpLCBjb2xvciA9ICJyZWQiLCBzaXplID0gMykgKw0KICBnZW9tX3BhdGgoZGF0YSA9IHNvbCwgYWVzKHggPSB4MSwgeSA9IHgyKSwgY29sb3IgPSAicmVkIiwgbGluZXdpZHRoID0gMSkgKw0KICB0cmFuc2l0aW9uX3JldmVhbChzdGVwKSArDQogIGxhYnMoDQogICAgdGl0bGUgPSAiT3B0aW1pemFjacOzbiBzb2JyZSBsYSBmdW5jacOzbiBkZSBSb3NlbmJyb2NrIGNvbiBkZXNjZW5zbyBkZWwgZ3JhZGllbnRlIiwNCiAgICB4ID0gZXhwcmVzc2lvbih4WzFdKSwNCiAgICB5ID0gZXhwcmVzc2lvbih4WzJdKQ0KICApICsNCiAgdGhlbWVfbWluaW1hbCgpDQoNCmFuaW1hdGUocCwgZnBzID0gMTAsIGR1cmF0aW9uID0gNSwgd2lkdGggPSA2MDAsIGhlaWdodCA9IDUwMCwgcmVuZGVyZXIgPSBnaWZza2lfcmVuZGVyZXIoInJvc2VuYnJvY2tfb3B0LmdpZiIpKQ0KYGBgDQoNCiMjIyAxLjMuMyBPcHRpbWl6YWNpw7NuIGRlIGxhIGZ1bmNpw7NuIGRlIFJvc2VuYnJvY2sgZW4gMyBkaW1lbnNpb25lcw0KDQpgYGB7cn0NCiMgRWplY3VjacOzbiBkZWwgbcOpdG9kbw0Kc29sX3Jvc2VuIDwtIG9wdGltaXphZG9yX211bHRfbnVtZGV2KGZfcm9zZW5icm9jayx4MD1jKC00LC00LC00KSxldGE9MSkNCg0KIyBHcmFmaWNhY2nDs24gZGVsIHByb2Nlc28gZGUgb3B0aW1pemFjacOzbg0Kbl9sZW5ndGggPC0gMTAwDQp4MSA8LSBzZXEoLTUsIDUsIGxlbmd0aC5vdXQgPSBuX2xlbmd0aCkNCngyIDwtIHNlcSgtNSwgNSwgbGVuZ3RoLm91dCA9IG5fbGVuZ3RoKQ0KeDMgPC0gc2VxKC01LCA1LCBsZW5ndGgub3V0ID0gbl9sZW5ndGgpDQpYIDwtIGV4cGFuZC5ncmlkKHgxLCB4MiwgeDMpDQp6IDwtIGZfcm9zZW5icm9ja18zZChYWywxXSwgWFssMl0sIFhbLDNdKQ0KWiA8LSBtYXRyaXgoeiwgbmNvbCA9IG5fbGVuZ3RoLCBucm93ID0gbl9sZW5ndGgpDQpjb250b3VyKA0KICB4ID0geDEsDQogIHkgPSB4MiwNCiAgeiA9IFosDQogIG5sZXZlbHMgPSAxMDAsDQogIGxhcyA9IDEsDQogIHhsYWIgPSBleHByZXNzaW9uKHhbMV0pLA0KICB5bGFiID0gZXhwcmVzc2lvbih4WzJdKSwNCiAgbWFpbiA9IGV4cHJlc3Npb24ocGFzdGUoDQogICAgIkZ1bmNpw7NuIGRlIFJvc2VuYnJvY2s6ICIsDQogICAgZih4WzFdLHhbMl0pPT0xMDAqKHhbMl0teFsxXV4yKV4yKygxLXhbMV0pXjIpDQogICksDQogIHN1YiA9ICJDdXJ2YXMgZGUgbml2ZWwgZGUgbGEgZnVuY2nDs24iDQopDQpsaW5lcyhzb2xfcm9zZW4sIHR5cGU9ImIiLGNleD0xLjUsY29sPSJyZWQiKQ0KYGBgDQoNCiMjIyAxLjMuNCBPcHRpbWl6YWNpw7NuIGRlIGxhIGZ1bmNpw7NuIGRlIFJhc3RyaWdpbiBlbiAyIGRpbWVuc2lvbmVzDQoNCmBgYHtyfQ0Kc29sX3JhczJkIDwtIG9wdGltaXphZG9yX211bHRfbnVtZGV2KGZfcmFzdHJpZ2luLHgwPWMoNC41LDQuNSksZXRhPTIpDQoNCm5fbGVuZ3RoIDwtIDEwMA0KeDEgPC0gc2VxKC01LjEyLCA1LjEyLCBsZW5ndGgub3V0ID0gbl9sZW5ndGgpDQp4MiA8LSBzZXEoLTUuMTIsIDUuMTIsIGxlbmd0aC5vdXQgPSBuX2xlbmd0aCkNClggPC0gZXhwYW5kLmdyaWQoeDEsIHgyKQ0KeiA8LSBmX3Jhc3RyaWdpbl8yZChYWywxXSwgWFssMl0pDQpaIDwtIG1hdHJpeCh6LCBuY29sID0gbl9sZW5ndGgsIG5yb3cgPSBuX2xlbmd0aCkNCmNvbnRvdXIoDQogIHggPSB4MSwNCiAgeSA9IHgyLA0KICB6ID0gWiwNCiAgbmxldmVscyA9IDEwLA0KICBsYXMgPSAxLA0KICB4bGFiID0gZXhwcmVzc2lvbih4WzFdKSwNCiAgeWxhYiA9IGV4cHJlc3Npb24oeFsyXSksDQogIG1haW4gPSBleHByZXNzaW9uKHBhc3RlKA0KICAgICJGdW5jacOzbiBkZSBSYXN0cmlnaW46ICIsDQogICAgZih4WzFdLHhbMl0pPT0yMCArIHhbMV1eMiAtIDEwKmNvcygyKnBpKnhbMV0pICsgeFsyXV4yIC0gMTAqY29zKDIqcGkqeFsyXSkNCiAgKSksDQogIHN1YiA9ICJDdXJ2YXMgZGUgbml2ZWwgZGUgbGEgZnVuY2nDs24iDQopDQpsaW5lcyhzb2xfcmFzLCB0eXBlPSJiIixjZXg9MS41LGNvbD0icmVkIikNCmBgYA0KDQpBbmltYWNpw7NuIGRlIGxhIG9wdGltaXphY2nDs24NCg0KYGBge3J9DQojIFRyYW5zZm9ybWEgbGEgc29sdWNpw7NuIGVuIHVuIGRhdGEuZnJhbWUNCnNvbCA8LSBhcy5kYXRhLmZyYW1lKHNvbF9yYXMyZCkNCmNvbG5hbWVzKHNvbCkgPC0gYygieDEiLCAieDIiKQ0Kc29sJHN0ZXAgPC0gMTpucm93KHNvbCkNCg0KI1ByZXBhcmEgbG9zIGRhdG9zIGRlIGZvbmRvDQp4MSA8LSBzZXEoLTUuMTIsIDUuMTIsIGxlbmd0aC5vdXQgPSAxMDApDQp4MiA8LSBzZXEoLTUuMTIsIDUuMTIsIGxlbmd0aC5vdXQgPSAxMDApDQpYIDwtIGV4cGFuZC5ncmlkKHgxID0geDEsIHgyID0geDIpDQoNClgkeiA8LSBmX3Jhc3RyaWdpbl8yZChYJHgxLCBYJHgyKQ0KDQojIENyZWEgbGEgYW5pbWFjacOzbg0KcCA8LSBnZ3Bsb3QoKSArDQogIGdlb21fY29udG91cihkYXRhID0gWCwgYWVzKHggPSB4MSwgeSA9IHgyLCB6ID0geiksIGJpbnMgPSAyMCwgY29sb3IgPSAiZ3JheTcwIikgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBzb2wsIGFlcyh4ID0geDEsIHkgPSB4MiksIGNvbG9yID0gInJlZCIsIHNpemUgPSAzKSArDQogIGdlb21fcGF0aChkYXRhID0gc29sLCBhZXMoeCA9IHgxLCB5ID0geDIpLCBjb2xvciA9ICJyZWQiLCBsaW5ld2lkdGggPSAxKSArDQogIHRyYW5zaXRpb25fcmV2ZWFsKHN0ZXApICsNCiAgbGFicygNCiAgICB0aXRsZSA9ICJPcHRpbWl6YWNpw7NuIHNvYnJlIGxhIGZ1bmNpw7NuIGRlIFJhc3RyaWdpbiBjb24gZGVzY2Vuc28gZGVsIGdyYWRpZW50ZSIsDQogICAgeCA9IGV4cHJlc3Npb24oeFsxXSksDQogICAgeSA9IGV4cHJlc3Npb24oeFsyXSkNCiAgKSArDQogIHRoZW1lX21pbmltYWwoKQ0KDQphbmltYXRlKHAsIGZwcyA9IDEwLCBkdXJhdGlvbiA9IDUsIHdpZHRoID0gNjAwLCBoZWlnaHQgPSA1MDAsIHJlbmRlcmVyID0gZ2lmc2tpX3JlbmRlcmVyKCJyYXN0cmlnaW5fb3B0LmdpZiIpKQ0KYGBgDQoNCiMjIyAxLjMuNSBPcHRpbWl6YWNpw7NuIGRlIGxhIGZ1bmNpw7NuIGRlIFJhc3RyaWdpbiBlbiAzIGRpbWVuc2lvbmVzDQoNCmBgYHtyfQ0KIyBFamVjdWNpw7NuIGRlbCBtw6l0b2RvDQpzb2xfcmFzM2QgPC0gb3B0aW1pemFkb3JfbXVsdF9udW1kZXYoZl9yYXN0cmlnaW4seDA9YygtNCwtNCwtNCksZXRhPTMpDQoNCiMgR3JhZmljYWNpw7NuIGRlbCBwcm9jZXNvIGRlIG9wdGltaXphY2nDs24NCm5fbGVuZ3RoIDwtIDEwMA0KeDEgPC0gc2VxKC01LjEyLCA1LjEyLCBsZW5ndGgub3V0ID0gbl9sZW5ndGgpDQp4MiA8LSBzZXEoLTUuMTIsIDUuMTIsIGxlbmd0aC5vdXQgPSBuX2xlbmd0aCkNCngzIDwtIHNlcSgtNS4xMiwgNS4xMiwgbGVuZ3RoLm91dCA9IG5fbGVuZ3RoKQ0KWCA8LSBleHBhbmQuZ3JpZCh4MSwgeDIsIHgzKQ0KeiA8LSBmX3Jhc3RyaWdpbl8zZChYWywxXSwgWFssMl0sIFhbLDNdKQ0KWiA8LSBtYXRyaXgoeiwgbmNvbCA9IG5fbGVuZ3RoLCBucm93ID0gbl9sZW5ndGgpDQpjb250b3VyKA0KICB4ID0geDEsDQogIHkgPSB4MiwNCiAgeiA9IFosDQogIG5sZXZlbHMgPSAxMCwNCiAgbGFzID0gMSwNCiAgeGxhYiA9IGV4cHJlc3Npb24oeFsxXSksDQogIHlsYWIgPSBleHByZXNzaW9uKHhbMl0pLA0KICBtYWluID0gZXhwcmVzc2lvbihwYXN0ZSgNCiAgICAiRnVuY2nDs24gZGUgUmFzdHJpZ2luOiAiLA0KICAgIGYoeFsxXSx4WzJdKT09MjAgKyB4WzFdXjIgLSAxMCpjb3MoMipwaSp4WzFdKSArIHhbMl1eMiAtIDEwKmNvcygyKnBpKnhbMl0pDQogICkpLA0KICBzdWIgPSAiQ3VydmFzIGRlIG5pdmVsIGRlIGxhIGZ1bmNpw7NuIg0KKQ0KbGluZXMoc29sX3Jvc2VuLCB0eXBlPSJiIixjZXg9MS41LGNvbD0icmVkIikNCg0KYGBgDQoNCiMjIDEuNCBNw6l0b2RvIGRlIGV2b2x1Y2nDs24gZGlmZXJlbmNpYWwNCg0KTGEgZXZvbHVjacOzbiBkaWZlcmVuY2lhbCBlcyB1biBhbGdvcml0bW8gZGUgb3B0aW1pemFjacOzbiBpbnNwaXJhZG8gZW4gbGEgZXZvbHVjacOzbiBiaW9sw7NnaWNhLiBGdW5jaW9uYSBtYW50ZW5pZW5kbyB1bmEgcG9ibGFjacOzbiBkZSBzb2x1Y2lvbmVzLCB5IG1lam9yw6FuZG9sYXMgZ2VuZXJhY2nDs24gdHJhcyBnZW5lcmFjacOzbiBtZWRpYW50ZSBvcGVyYWNpb25lcyBkZSBtdXRhY2nDs24sIHJlY29tYmluYWNpw7NuIHkgc2VsZWNjacOzbi4NCg0KLSAgICoqTXV0YWNpw7NuKio6IHNlIGNvbWJpbmFuIDMgaW5kaXZpZHVvcyBkaXN0aW50b3MgZGUgbGEgcG9ibGFjacOzbiBwYXJhIGNyZWFyIHVuYSB2YXJpYW50ZS4NCi0gICAqKlJlY29tYmluYWNpw7NuKio6IHNlIG1lemNsYSBlc2EgdmFyaWFudGUgY29uIGVsIGluZGl2aWR1byBhY3R1YWwuDQotICAgKipTZWxlY2Npw7NuKio6IHNlIGVzY29nZSBlbCBtZWpvciBlbnRyZSBlbCBvcmlnaW5hbCB5IGVsIG51ZXZvLg0KDQpFc3RlIHByb2Nlc28gc2UgcmVwaXRlIHZhcmlhcyB2ZWNlcyBoYXN0YSBlbmNvbnRyYXIgdW5hIHNvbHVjacOzbiDDs3B0aW1hLg0KDQojIyMgMS40LjEgSW1wbGVtZW50YWNpw7NuIGVuIFIgZGUgZXZvbHVjacOzbiBkaWZlcmVuY2lhbA0KDQpgYGB7cn0NCmV2b2x1Y2lvbl9kaWZlcmVuY2lhbCA8LSBmdW5jdGlvbihmdW5fb2JqLCBkaW0gPSAyLCBOUCA9IDMwLCBGID0gMC44LCBDUiA9IDAuOSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5zID0gMTAwLCBib3VuZHMgPSBjKC01LCA1KSkgew0KDQogICMgSW5pY2lhbGl6YXIgcG9ibGFjacOzbg0KICBwb2JsYWNpb24gPC0gbWF0cml4KHJ1bmlmKE5QICogZGltLCBib3VuZHNbMV0sIGJvdW5kc1syXSksIG5jb2wgPSBkaW0pDQogIGZpdG5lc3MgPC0gYXBwbHkocG9ibGFjaW9uLCAxLCBmdW5fb2JqKQ0KDQogIGhpc3RvcmlhbCA8LSBudW1lcmljKGdlbnMpDQogIG1lam9yZXMgPC0gbWF0cml4KE5BLCBnZW5zLCBkaW0pDQoNCiAgZm9yIChnZW4gaW4gMTpnZW5zKSB7DQogICAgZm9yIChpIGluIDE6TlApIHsNCiAgICAgICMgU2VsZWNjaW9uYXIgMyDDrW5kaWNlcyBkaXN0aW50b3MNCiAgICAgIGluZGljZXMgPC0gc2FtcGxlKHNldGRpZmYoMTpOUCwgaSksIDMpDQogICAgICB4MSA8LSBwb2JsYWNpb25baW5kaWNlc1sxXSwgXQ0KICAgICAgeDIgPC0gcG9ibGFjaW9uW2luZGljZXNbMl0sIF0NCiAgICAgIHgzIDwtIHBvYmxhY2lvbltpbmRpY2VzWzNdLCBdDQoNCiAgICAgICMgTXV0YWNpw7NuDQogICAgICBtdXRhZG8gPC0geDEgKyBGICogKHgyIC0geDMpDQoNCiAgICAgICMgUmVjb21iaW5hcg0KICAgICAgdHJpYWwgPC0gcG9ibGFjaW9uW2ksIF0NCiAgICAgIGpyYW5kIDwtIHNhbXBsZSgxOmRpbSwgMSkNCiAgICAgIGZvciAoaiBpbiAxOmRpbSkgew0KICAgICAgICBpZiAocnVuaWYoMSkgPCBDUiB8fCBqID09IGpyYW5kKSB7DQogICAgICAgICAgdHJpYWxbal0gPC0gbXV0YWRvW2pdDQogICAgICAgIH0NCiAgICAgIH0NCg0KICAgICAgIyBTZWxlY2Npw7NuDQogICAgICBpZiAoZnVuX29iaih0cmlhbCkgPCBmaXRuZXNzW2ldKSB7DQogICAgICAgIHBvYmxhY2lvbltpLCBdIDwtIHRyaWFsDQogICAgICAgIGZpdG5lc3NbaV0gPC0gZnVuX29iaih0cmlhbCkNCiAgICAgIH0NCiAgICB9DQoNCiAgICAjIEd1YXJkYXIgbWVqb3IgcmVzdWx0YWRvDQogICAgYmVzdF9pZHggPC0gd2hpY2gubWluKGZpdG5lc3MpDQogICAgaGlzdG9yaWFsW2dlbl0gPC0gZml0bmVzc1tiZXN0X2lkeF0NCiAgICBtZWpvcmVzW2dlbiwgXSA8LSBwb2JsYWNpb25bYmVzdF9pZHgsIF0NCiAgfQ0KDQogIGxpc3QobWVqb3IgPSBwb2JsYWNpb25bd2hpY2gubWluKGZpdG5lc3MpLCBdLA0KICAgICAgIHZhbG9yID0gbWluKGZpdG5lc3MpLA0KICAgICAgIGhpc3RvcmlhbCA9IGhpc3RvcmlhbCwNCiAgICAgICB0cmF5ZWN0b3JpYSA9IG1lam9yZXMpDQp9DQpgYGANCg0KIyMjIDEuNC4yIE9wdGltaXphY2nDs24gZGUgbGEgZnVuY2nDs24gZGUgUm9zZW5icm9jayBlbiAyIGRpbWVuc2lvbmVzDQoNCmBgYHtyfQ0KIyBFamVjdWNpw7NuIGRlbCBtw6l0b2RvDQpzZXQuc2VlZCgxMjMpDQpyZXNfcm9zZW4gPC0gZXZvbHVjaW9uX2RpZmVyZW5jaWFsKGZfcm9zZW5icm9jaywgZGltID0gMikNCg0KIyBNb3N0cmFyIG1lam9yIHNvbHVjacOzbg0KcmVzX3Jvc2VuJG1lam9yDQpyZXNfcm9zZW4kdmFsb3INCg0KIyBHcmFmaWNhciB0cmF5ZWN0b3JpYQ0KeDEgPC0gc2VxKC0zLCAzLCBsZW5ndGgub3V0ID0gMTAwKQ0KeDIgPC0gc2VxKC0zLCAzLCBsZW5ndGgub3V0ID0gMTAwKQ0KeiA8LSBvdXRlcih4MSwgeDIsIFZlY3Rvcml6ZShmdW5jdGlvbih4LCB5KSBmX3Jvc2VuYnJvY2soYyh4LCB5KSkpKQ0KY29udG91cih4MSwgeDIsIHosIG5sZXZlbHMgPSA1MCwNCiAgICAgICAgbWFpbiA9ICJSb3NlbmJyb2NrIDJEIC0gVHJheWVjdG9yaWEiLCB4bGFiID0gIngiLCB5bGFiID0gInkiKQ0KbGluZXMocmVzX3Jvc2VuJHRyYXllY3RvcmlhWywxXSwgcmVzX3Jvc2VuJHRyYXllY3RvcmlhWywyXSwgY29sID0gInJlZCIsIHR5cGUgPSAiYiIpDQpgYGANCg0KRW4gZXN0ZSBncsOhZmljbyBzZSBtdWVzdHJhbiBsYXMgY3VydmFzIGRlIG5pdmVsIGRlIGxhIGZ1bmNpw7NuIGRlIFJvc2VuYnJvY2sgZW4gZG9zIGRpbWVuc2lvbmVzLiBFc3RhcyBjdXJ2YXMgcmVwcmVzZW50YW4gbMOtbmVhcyBkb25kZSBsYSBmdW5jacOzbiB0aWVuZSBpZ3VhbCB2YWxvciwgeSBlbCB2YWxsZSBjdXJ2YWRvIGFsIGNlbnRybyBlcyBkb25kZSBlc3TDoSBlbCBtw61uaW1vIGdsb2JhbCAoZW4gZWwgcHVudG8gKDEsMSkoMSwxKSkuDQoNCkxhIGzDrW5lYSByb2phIHJlcHJlc2VudGEgbGEgdHJheWVjdG9yaWEgcXVlIHNpZ3Vpw7MgZWwgYWxnb3JpdG1vIGRlIGV2b2x1Y2nDs24gZGlmZXJlbmNpYWwgZHVyYW50ZSBsYXMgaXRlcmFjaW9uZXMuIFNlIHB1ZWRlIG9ic2VydmFyIGPDs21vIGVsIGVuamFtYnJlIGRlIHNvbHVjaW9uZXMgc2UgZnVlIGFjZXJjYW5kbyBwcm9ncmVzaXZhbWVudGUgaGFjaWEgZWwgbcOtbmltbywgbWVqb3JhbmRvIHN1IHBvc2ljacOzbiBlbiBjYWRhIGdlbmVyYWNpw7NuLg0KDQpFbCByZXN1bHRhZG8gZmluYWwgb2J0ZW5pZG8gZnVlOiBbMV0gMS4wMDAwMDAgMS4wMDAwMDEgVmFsb3IgbcOtbmltbyBlbmNvbnRyYWRvOiAxLjkxZS0xMg0KDQojIyMgMS40LjMgT3B0aW1pemFjacOzbiBkZSBsYSBmdW5jacOzbiBkZSBSb3NlbmJyb2NrIGVuIDMgZGltZW5zaW9uZXMNCg0KYGBge3J9DQojIEVqZWN1Y2nDs24gZGVsIG3DqXRvZG8NCnJlc19yb3Nlbl8zZCA8LSBldm9sdWNpb25fZGlmZXJlbmNpYWwoZl9yb3NlbmJyb2NrLCBkaW0gPSAzKQ0KcmVzX3Jvc2VuXzNkJG1lam9yDQpyZXNfcm9zZW5fM2QkdmFsb3INCmBgYA0KDQpFbiBlc3RhIHBhcnRlIGRlbCBpbmZvcm1lIHNlIG11ZXN0cmFuIGxvcyByZXN1bHRhZG9zIG51bcOpcmljb3MgcGFyYSBsYSBvcHRpbWl6YWNpw7NuIGRlIGxhcyBmdW5jaW9uZXMgZW4gM0QuIERhZG8gcXVlIG5vIHNlIHB1ZWRlIHZpc3VhbGl6YXIgZsOhY2lsbWVudGUgZW4gdW5hIGdyw6FmaWNhIDNEIGRlIHRyYXllY3RvcmlhLCBzZSByZXBvcnRhbiBsYXMgbWVqb3JlcyBwb3NpY2lvbmVzIHkgdmFsb3JlcyBvYnRlbmlkby4NCg0KIyMjIDEuNC40IE9wdGltaXphY2nDs24gZGUgbGEgZnVuY2nDs24gZGUgUmFzdHJpZ2luIGVuIDIgZGltZW5zaW9uZXMNCg0KYGBge3J9DQojIEVqZWN1Y2nDs24gZGVsIG3DqXRvZG8NCnNldC5zZWVkKDQ1NikNCnJlc19yYXMgPC0gZXZvbHVjaW9uX2RpZmVyZW5jaWFsKGZfcmFzdHJpZ2luLCBkaW0gPSAyKQ0KDQojIE1vc3RyYXIgbWVqb3Igc29sdWNpw7NuDQpyZXNfcmFzJG1lam9yDQpyZXNfcmFzJHZhbG9yDQoNCiMgR3JhZmljYXIgdHJheWVjdG9yaWENCngxIDwtIHNlcSgtNS4xMiwgNS4xMiwgbGVuZ3RoLm91dCA9IDEwMCkNCngyIDwtIHNlcSgtNS4xMiwgNS4xMiwgbGVuZ3RoLm91dCA9IDEwMCkNCnogPC0gb3V0ZXIoeDEsIHgyLCBWZWN0b3JpemUoZnVuY3Rpb24oeCwgeSkgZl9yYXN0cmlnaW4oYyh4LCB5KSkpKQ0KY29udG91cih4MSwgeDIsIHosIG5sZXZlbHMgPSA1MCwNCiAgICAgICAgbWFpbiA9ICJSYXN0cmlnaW4gMkQgLSBUcmF5ZWN0b3JpYSIsIHhsYWIgPSAieCIsIHlsYWIgPSAieSIpDQpsaW5lcyhyZXNfcmFzJHRyYXllY3RvcmlhWywxXSwgcmVzX3JhcyR0cmF5ZWN0b3JpYVssMl0sIGNvbCA9ICJibHVlIiwgdHlwZSA9ICJiIikNCmBgYA0KDQpBcXXDrSBzZSBncmFmaWNhbiBsYXMgY3VydmFzIGRlIG5pdmVsIGRlIGxhIGZ1bmNpw7NuIGRlIFJhc3RyaWdpbiwgcXVlIGVzIG11bHRpbW9kYWwsIGVzIGRlY2lyLCB0aWVuZSBtdWNob3MgbcOtbmltb3MgbG9jYWxlcyAocGF0csOzbiBvbmR1bGFkbykuIExhIGLDunNxdWVkYSBlcyBtdWNobyBtw6FzIGNvbXBsZWphIHF1ZSBlbiBSb3NlbmJyb2NrLg0KDQpMYSBsw61uZWEgYXp1bCBtdWVzdHJhIGPDs21vIGxhIGV2b2x1Y2nDs24gZGlmZXJlbmNpYWwgc2UgbXVldmUgcG9yIGVsIGVzcGFjaW8gZGUgYsO6c3F1ZWRhIHkgbG9ncmEgZXNjYXBhciBkZSBsb3MgbcOtbmltb3MgbG9jYWxlcyBoYXN0YSBhY2VyY2Fyc2UgYWwgw7NwdGltbyBnbG9iYWwsIHF1ZSBzZSBlbmN1ZW50cmEgZW4gKDAsMCkoMCwwKS4NCg0KUmVzdWx0YWRvIG9idGVuaWRvOiBbMV0gMi4xNzY2OTdlLTA2IC0yLjAxNTc4NWUtMDcgVmFsb3IgbcOtbmltbzogOS40OGUtMTANCg0KIyMjIDEuNC41IE9wdGltaXphY2nDs24gZGUgbGEgZnVuY2nDs24gZGUgUmFzdHJpZ2luIGVuIDMgZGltZW5zaW9uZXMNCg0KYGBge3J9DQojIEVqZWN1Y2nDs24gZGVsIG3DqXRvZG8NCnJlc19yYXNfM2QgPC0gZXZvbHVjaW9uX2RpZmVyZW5jaWFsKGZfcmFzdHJpZ2luLCBkaW0gPSAzKQ0KcmVzX3Jhc18zZCRtZWpvcg0KcmVzX3Jhc18zZCR2YWxvcg0KYGBgDQoNCkVuIGVzdGUgY2FzbywgZWwgYWxnb3JpdG1vIGVuY29udHLDsyB1bmEgc29sdWNpw7NuIGNlcmNhbmEgYWwgbcOtbmltbyBnbG9iYWwsIGF1bnF1ZSBubyBleGFjdGEuIEVzdG8gZXMgZXNwZXJhYmxlLCB5YSBxdWUgUmFzdHJpZ2luIGVzIG11Y2hvIG3DoXMgZGlmw61jaWwgZW4gM0QgZGViaWRvIGEgbGEgZ3JhbiBjYW50aWRhZCBkZSBtw61uaW1vcyBsb2NhbGVzLg0KDQojIyMgMS40LjYgQ29uY2x1c2lvbmVzIG3DqXRvZG8gZGUgZXZvbHVjacOzbiBkaWZlcmVuY2lhbA0KDQpFbCBhbGdvcml0bW8gbG9ncsOzIGVuY29udHJhciBzb2x1Y2lvbmVzIG11eSBjZXJjYW5hcyBhbCBtw61uaW1vIGdsb2JhbCBlbiBsYXMgZnVuY2lvbmVzIGRlIFJvc2VuYnJvY2sgeSBSYXN0cmlnaW4sIHRhbnRvIGVuIDJEIGNvbW8gZW4gM0QuDQoNCkVuIGxhIGZ1bmNpw7NuIGRlIFJvc2VuYnJvY2ssIHNlIG9ic2VydsOzIHVuYSBjb252ZXJnZW5jaWEgZXN0YWJsZSB5IHByZWNpc2EsIGVzcGVjaWFsbWVudGUgZW4gZG9zIGRpbWVuc2lvbmVzLg0KDQpFbiBsYSBmdW5jacOzbiBkZSBSYXN0cmlnaW4sIHF1ZSBwcmVzZW50YSBtdWNob3MgbcOtbmltb3MgbG9jYWxlcywgZWwgbcOpdG9kbyBldml0w7MgY2FlciBlbiBlc3RvcyB5IGFsY2FuesOzIGJ1ZW5vcyByZXN1bHRhZG9zLg0KDQpFbCByZW5kaW1pZW50byBzZSBtYW50dXZvIHPDs2xpZG8gZW4gdHJlcyBkaW1lbnNpb25lcywgYXVucXVlIGNvbiB1bmEgbGlnZXJhIHDDqXJkaWRhIGRlIHByZWNpc2nDs24gZW4gUmFzdHJpZ2luIDNEIGRlYmlkbyBhIHN1IGNvbXBsZWppZGFkLg0KDQpGdWUgbcOhcyByb2J1c3RvIHF1ZSBlbCBkZXNjZW5zbyBwb3IgZ3JhZGllbnRlIGVuIGZ1bmNpb25lcyBtdWx0aW1vZGFsZXMsIHlhIHF1ZSBubyBuZWNlc2l0YSBkZXJpdmFkYXMgbmkgZGVwZW5kZSBkZWwgcHVudG8gaW5pY2lhbC4NCg0KTGEgZXZvbHVjacOzbiBkaWZlcmVuY2lhbCBkZW1vc3Ryw7Mgc2VyIHVuIG3DqXRvZG8gY29uZmlhYmxlIHkgZWZlY3Rpdm8gcGFyYSByZXNvbHZlciBwcm9ibGVtYXMgZGUgb3B0aW1pemFjacOzbsKgY29udGludWEuDQoNCiMjIDEuNSBNw6l0b2RvIGRlIG9wdGltaXphY2nDs24gZGUgcGFydMOtY3VsYXMNCg0KRWwgbcOpdG9kbyBkZSBvcHRpbWl6YWNpw7NuIHBvciBlbmphbWJyZSBkZSBwYXJ0w61jdWxhcyAoUFNPLCBwb3Igc3VzIHNpZ2xhcyBlbiBpbmdsw6lzKSBlcyB1biBhbGdvcml0bW8gaW5zcGlyYWRvIGVuIGVsIGNvbXBvcnRhbWllbnRvIGNvbGVjdGl2byBkZSBhbmltYWxlcyBjb21vIGJhbmRhZGFzIGRlIGF2ZXMgbyBiYW5jb3MgZGUgcGVjZXMuIEZ1bmNpb25hIG1lZGlhbnRlIHVuIGNvbmp1bnRvIGRlIHBhcnTDrWN1bGFzIChzb2x1Y2lvbmVzIHBvdGVuY2lhbGVzKSBxdWUgZXhwbG9yYW4gZWwgZXNwYWNpbyBkZSBiw7pzcXVlZGEgbW92acOpbmRvc2UgZW4gZnVuY2nDs24gZGUgc3UgcHJvcGlhIGV4cGVyaWVuY2lhIHkgbGEgZGUgc3VzIHZlY2luYXMuIENhZGEgcGFydMOtY3VsYSBhanVzdGEgc3UgcG9zaWNpw7NuIHkgdmVsb2NpZGFkIGl0ZXJhdGl2YW1lbnRlIHBhcmEgYWNlcmNhcnNlIGEgbGEgbWVqb3Igc29sdWNpw7NuIGNvbm9jaWRhLCBndWlhZGEgcG9yIHN1IG1lam9yIHBvc2ljacOzbiBoaXN0w7NyaWNhIHkgbGEgbWVqb3IgcG9zaWNpw7NuIGdsb2JhbCBlbmNvbnRyYWRhIHBvciBlbCBlbmphbWJyZS4gQ29uIGVsIHRpZW1wbywgbGFzIHBhcnTDrWN1bGFzIHRpZW5kZW4gYSBjb252ZXJnZXIgaGFjaWEgdW5hIHNvbHVjacOzbiDDs3B0aW1hIG8gY2VyY2FuYSBhbCDDs3B0aW1vLg0KDQojIyMgMS41LjEgSW1wbGVtZW50YWNpw7NuIGVuIFIgZGUgb3B0aW1pemFjacOzbiBkZSBwYXJ0w61jdWxhcw0KDQpTZSB1dGlsaXphIGVsIHBhcXVldGUgcG9zIHBhcmEgaW1wbGVtZW50YXIgZWwgbcOpdG9kbyBkZSBsYSBvcHRpbWl6YWNpw7NuIGRlIHBhcnTDrWN1bGFzLiBBZGljaW9uYWxtZW50ZSwgc2UgaW1wbGVtZW50YSB1bmEgZnVuY2nDs24gYWRpY2lvbmFsIHBhcmEgY3JlYXIgbGFzIGFuaW1hY2lvbmVzIGRlbCBtw6l0b2RvIGRlIG9wdGltaXphY2nDs24uDQoNCmBgYHtyfQ0KbGlzdF90b19tYXRyaXggPC0gZnVuY3Rpb24oZGF0YSkgew0KICBmb3IgKGkgaW4gMTpsZW5ndGgoZGF0YSkpIHsNCiAgICBkYXRhW1tpXV0gPC0gbWF0cml4KGRhdGFbW2ldXSwgbnJvdz0yLCBuY29sPTEyKQ0KICB9DQogIHJldHVybihkYXRhKQ0KfQ0KDQpwYXJ0aWNsZV9zd2FybV9vcHRpbWl6YXRpb24gPC0gZnVuY3Rpb24obixmdW5jLGxvd2VyX2JvdW5kcyx1cHBlcl9ib3VuZHMpIHsNCiAgc2V0LnNlZWQoMjAwMSkNCiAgb19taW4gPC0gcHNvcHRpbShyZXAoTkEsbiksIGZ1bmMsIGxvd2VyPWxvd2VyX2JvdW5kcyx1cHBlcj11cHBlcl9ib3VuZHMsY29udHJvbD1saXN0KGZuc2NhbGU9MWUtOCx0cmFjZT0xLHRyYWNlLnN0YXRzPVRSVUUpKQ0KICBvX21heCA8LSBwc29wdGltKHJlcChOQSxuKSwgZnVuYywgbG93ZXI9bG93ZXJfYm91bmRzLHVwcGVyPXVwcGVyX2JvdW5kcyxjb250cm9sPWxpc3QoZm5zY2FsZT0tMSooMWUtOCksdHJhY2U9MSx0cmFjZS5zdGF0cz1UUlVFLHM9MzApKQ0KICBwcmludCgiPT09PT09PT09PT09PT09PT09PT09U1VNTUFSWT09PT09PT09PT09PT09PT09PT09PSIpDQogIHByaW50KCJNSU5JTUlaQVRJT046IikNCiAgcHJpbnQoIlBvaW50OiIpDQogIHNob3cob19taW4kcGFyKQ0KICBwcmludCgiVmFsdWU6IikNCiAgc2hvdyhmdW5jKG9fbWluJHBhcikpDQogIHByaW50KCJNQVhJTUlaQVRJT04iKQ0KICBwcmludCgiUG9pbnQ6IikNCiAgc2hvdyhvX21heCRwYXIpDQogIHByaW50KCJWYWx1ZToiKQ0KICBzaG93KGZ1bmMob19tYXgkcGFyKSkNCiAgcmV0dXJuKGxpc3QoIm9fbWluIj1vX21pbiwgIm9fbWF4Ij1vX21heCkpDQp9DQoNCmFuaW1hdGVfcHNvIDwtIGZ1bmN0aW9uKHBhcnRpY2xlc19wb3NpdGlvbnMsIGZ1bmMsIGdpZl9uYW1lKSB7DQogIHBvc2l0aW9uc19wZXJfaXRlcmF0aW9uIDwtIGxpc3RfdG9fbWF0cml4KHBhcnRpY2xlc19wb3NpdGlvbnMpDQogIGRmIDwtIG1hcDJfZGZyKA0KICAgIHBvc2l0aW9uc19wZXJfaXRlcmF0aW9uLA0KICAgIC55ID0gc2VxX2Fsb25nKHBvc2l0aW9uc19wZXJfaXRlcmF0aW9uKSwNCiAgICAuZiA9IGZ1bmN0aW9uKG1hdCwgaXRlcikgew0KICAgICAgdGliYmxlKA0KICAgICAgICBwYXJ0aWNsZV9pZCA9IDE6bmNvbChtYXQpLA0KICAgICAgICB4ID0gbWF0WzEsIF0sDQogICAgICAgIHkgPSBtYXRbMiwgXSwNCiAgICAgICAgaXRlciA9IGl0ZXINCiAgICAgICkNCiAgICB9DQogICkNCiAgeF9zZXEgPC0gc2VxKC0xMCwgMTAsIGxlbmd0aC5vdXQgPSAxMDApDQogIHlfc2VxIDwtIHNlcSgtMTAsIDEwLCBsZW5ndGgub3V0ID0gMTAwKQ0KICBncmlkIDwtIGV4cGFuZC5ncmlkKHggPSB4X3NlcSwgeSA9IHlfc2VxKQ0KICBncmlkJHogPC0gd2l0aChncmlkLCBmdW5jKHgsIHkpKQ0KICANCiAgcCA8LSBnZ3Bsb3QoKSArDQogICAgZ2VvbV9jb250b3VyKGRhdGEgPSBncmlkLCBhZXMoeD14LCB5PXksIHo9eiksDQogICAgICAgICAgICAgICAgIGJpbnMgPSAzMCwgY29sb3IgPSAiZ3JheSIpICsNCiAgICBnZW9tX3BvaW50KGRhdGE9ZGYsIGFlcyh4ID0geCwgeSA9IHkpLCBjb2xvcj0icmVkIiwgc2l6ZT0yKSArDQogICAgeGxpbSgtMTAsIDEwKSArIHlsaW0oLTEwLCAxMCkgKyAgIyBBZGp1c3QgbGltaXRzIHRvIHlvdXIgZGF0YSByYW5nZQ0KICAgIHRoZW1lX21pbmltYWwoKSArDQogICAgdHJhbnNpdGlvbl9tYW51YWwoZnJhbWVzID0gaXRlcikgKw0KICAgIGxhYnModGl0bGUgPSAiSXRlcmF0aW9uOiB7Y3VycmVudF9mcmFtZX0iKQ0KICAjcCArIHRyYW5zaXRpb25fcmV2ZWFsKGFnbm8pDQogIGFuaW1hdGUocCwgZnBzID0gNSwgcmVuZGVyZXI9Z2lmc2tpX3JlbmRlcmVyKCkpDQogIGFuaW1fc2F2ZShnaWZfbmFtZSxwKQ0KfQ0KYGBgDQoNCiMjIyAxLjUuMiBPcHRpbWl6YWNpw7NuIGRlIGxhIGZ1bmNpw7NuIGRlIFJvc2VuYnJvY2sgZW4gMiBkaW1lbnNpb25lcw0KDQpgYGB7cn0NCiMgUGFyw6FtZXRyb3MgYSB1dGlsaXphcg0KbiA8LSAyDQpsb3dlcl9ib3VuZHMgPC0gLTIwDQp1cHBlcl9ib3VuZHMgPC0gMjANCg0KIyBFamVjdWNpw7NuIGRlbCBtw6l0b2RvDQpvX3BzbyA8LSBwYXJ0aWNsZV9zd2FybV9vcHRpbWl6YXRpb24obixmX3Jvc2VuYnJvY2ssbG93ZXJfYm91bmRzLHVwcGVyX2JvdW5kcykNCg0KYGBgDQoNCmBgYHtyfQ0KIyBHcmFmaWNhY2nDs24gZGVsIHByb2Nlc28gZGUgb3B0aW1pemFjacOzbiAobWluaW1pemFjacOzbikNCm9fbWluIDwtIG9fcHNvJG9fbWluDQpvX21pbl9wYXJ0aWNsZXNfcG9zaXRpb25zIDwtIG9fbWluJHN0YXRzJHgNCmdpZl9uYW1lIDwtICJwc29fcm9zZW5icm9ja19taW4uZ2lmIg0KYW5pbWF0ZV9wc28ob19taW5fcGFydGljbGVzX3Bvc2l0aW9ucywgZl9yb3NlbmJyb2NrXzJkLCBnaWZfbmFtZSkNCmBgYA0KDQpgYGB7cn0NCiMgR3JhZmljYWNpw7NuIGRlbCBwcm9jZXNvIGRlIG9wdGltaXphY2nDs24gKG1heGltaXphY2nDs24pDQpvX21heCA8LSBvX3BzbyRvX21heA0Kb19tYXhfcGFydGljbGVzX3Bvc2l0aW9ucyA8LSBvX21heCRzdGF0cyR4DQpnaWZfbmFtZSA8LSAicHNvX3Jvc2VuYnJvY2tfbWF4LmdpZiINCmFuaW1hdGVfcHNvKG9fbWF4X3BhcnRpY2xlc19wb3NpdGlvbnMsIGZfcm9zZW5icm9ja18yZCwgZ2lmX25hbWUpDQpgYGANCg0KIyMjIDEuNS4zIE9wdGltaXphY2nDs24gZGUgbGEgZnVuY2nDs24gZGUgUm9zZW5icm9jayBlbiAzIGRpbWVuc2lvbmVzDQoNCmBgYHtyfQ0KIyBQYXLDoW1ldHJvcyBhIHV0aWxpemFyDQpuIDwtIDMNCmxvd2VyX2JvdW5kcyA8LSAtNQ0KdXBwZWRfYm91bmRzIDwtIDUNCg0KIyBFamVjdWNpw7NuIGRlbCBtw6l0b2RvDQpvX3BzbyA8LSBwYXJ0aWNsZV9zd2FybV9vcHRpbWl6YXRpb24obixmX3Jvc2VuYnJvY2ssbG93ZXJfYm91bmRzLHVwcGVyX2JvdW5kcykNCiMgR3JhZmljYWNpw7NuIGRlbCBwcm9jZXNvIGRlIG9wdGltaXphY2nDs24NCmBgYA0KDQojIyMgMS41LjQgT3B0aW1pemFjacOzbiBkZSBsYSBmdW5jacOzbiBkZSBSYXN0cmlnaW4gZW4gMiBkaW1lbnNpb25lcw0KDQpgYGB7cn0NCiMgUGFyw6FtZXRyb3MgYSB1dGlsaXphcg0KbiA8LSAyDQpsb3dlcl9ib3VuZHMgPC0gLTUNCnVwcGVkX2JvdW5kcyA8LSA1DQoNCiMgRWplY3VjacOzbiBkZWwgbcOpdG9kbw0Kb19wc28gPC0gcGFydGljbGVfc3dhcm1fb3B0aW1pemF0aW9uKG4sZl9yYXN0cmlnaW4sbG93ZXJfYm91bmRzLHVwcGVyX2JvdW5kcykNCmBgYA0KDQpgYGB7cn0NCiMgR3JhZmljYWNpw7NuIGRlbCBwcm9jZXNvIGRlIG9wdGltaXphY2nDs24gKG1pbmltaXphY2nDs24pDQpvX21pbiA8LSBvX3BzbyRvX21pbg0Kb19taW5fcGFydGljbGVzX3Bvc2l0aW9ucyA8LSBvX21pbiRzdGF0cyR4DQpnaWZfbmFtZSA8LSAicHNvX3Jhc3RyaWdpbl9taW4uZ2lmIg0KYW5pbWF0ZV9wc28ob19taW5fcGFydGljbGVzX3Bvc2l0aW9ucywgZl9yYXN0cmlnaW5fMmQsIGdpZl9uYW1lKQ0KYGBgDQoNCmBgYHtyfQ0KIyBHcmFmaWNhY2nDs24gZGVsIHByb2Nlc28gZGUgb3B0aW1pemFjacOzbiAobWF4aW1pemFjacOzbikNCm9fbWF4IDwtIG9fcHNvJG9fbWF4DQpvX21heF9wYXJ0aWNsZXNfcG9zaXRpb25zIDwtIG9fbWF4JHN0YXRzJHgNCmdpZl9uYW1lIDwtICJwc29fcmFzdHJpZ2luX21heC5naWYiDQphbmltYXRlX3BzbyhvX21heF9wYXJ0aWNsZXNfcG9zaXRpb25zLCBmX3Jhc3RyaWdpbl8yZCwgZ2lmX25hbWUpDQpgYGANCg0KIyMjIDEuNS41IE9wdGltaXphY2nDs24gZGUgbGEgZnVuY2nDs24gZGUgUmFzdHJpZ2luIGVuIDMgZGltZW5zaW9uZXMNCg0KYGBge3J9DQojIFBhcsOhbWV0cm9zIGEgdXRpbGl6YXINCm4gPC0gMw0KbG93ZXJfYm91bmRzIDwtIC01DQp1cHBlZF9ib3VuZHMgPC0gNQ0KIyBFamVjdWNpw7NuIGRlbCBtw6l0b2RvDQpvX3BzbyA8LSBwYXJ0aWNsZV9zd2FybV9vcHRpbWl6YXRpb24obixmX3Jhc3RyaWdpbixsb3dlcl9ib3VuZHMsdXBwZXJfYm91bmRzKQ0KYGBgDQoNCiMjIyAxLjUuNiBDb25jbHVzaW9uZXMgbcOpdG9kbyBkZSBvcHRpbWl6YWNpw7NuIGRlIHBhcnTDrWN1bGFzDQoNCkVuIGVsIGNhc28gZGUgbGEgb3B0aW1pemFjacOzbiBtYXhpbWl6YW5kbyBsYXMgZnVuY2lvbmVzLCBsbGVnYW4gbXV5IHLDoXBpZG8gYSB1bmEgc29sdWNpw7NuIMOzcHRpbWEgKGRlbnRybyBkZSAxMCBpdGVyYWNpb25lcykuIFBvciBvdHJvIGxhZG8sIGxhIG9wdGltaXphY2nDs24gbWluaW1pemFuZG8gbGFzIGZ1bmNpb25lcyB0YXJkYWJhIHVuIHBvY28gbcOhcyBlbiBsbGVnYXIgYWwgw7NwdGltbywgcGVybyBzZSBhY2VyY2FiYSBtdWNobyBhbCBtw61uaW1vIGdsb2JhbC4NCg0KRXN0b3MgcmVzdWx0YWRvcyBwdWVkZW4gZGViZXJzZSBhIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbGFzIHBhcnTDrWN1bGFzIHBvciBlbCBlc3BhY2lvIGRlIGLDunNxdWVkYSwgcGVybWl0aWVuZG8gdW5hIG9wdGltaXphY2nDs24gbcOhcyAiYWJpZXJ0YSIgYSBjb21wYXJhY2nDs24gY29uIGVsIG3DqXRvZG8gZGUgZGVzY2Vuc28gZGUgZ3JhZGllbnRlLg0KDQojIyAxLjYgTcOpdG9kbyBkZSBhbGdvcml0bW9zIGV2b2x1dGl2b3MNCg0KTG9zIGFsZ29yaXRtb3MgZ2Vuw6l0aWNvcyAoQUcpIHNvbiB0w6ljbmljYXMgZGUgYsO6c3F1ZWRhIGhldXLDrXN0aWNhIGJhc2FkYXMgZW4gcHJvY2Vzb3MgZGUgZXZvbHVjacOzbiBuYXR1cmFs4oCLIC4gRW4gdW4gQUcgdMOtcGljbyBzZSBkZWZpbmUgdW5hIGZ1bmNpw7NuIEZJVE5FU1MgcXVlIGV2YWzDumEgbGEgY2FsaWRhZCBkZSBjYWRhIHNvbHVjacOzbiBjYW5kaWRhdGEgKGluZGl2aWR1bykuIEEgcGFydGlyIGRlIHVuYSBwb2JsYWNpw7NuIGluaWNpYWwgYWxlYXRvcmlhLCBzZSBpdGVyYW4gY2ljbG9zIGRvbmRlIHNlIHNlbGVjY2lvbmFuIGluZGl2aWR1b3MgbcOhcyBhcHRvcywgc2UgY29tYmluYW4gc3VzIOKAnGdlbmVz4oCdIG1lZGlhbnRlIGNydWNlcyAoY3Jvc3NvdmVyKSB5IHNlIGludHJvZHVjZW4gbW9kaWZpY2FjaW9uZXMgYWxlYXRvcmlhcyAobXV0YWNpb25lcykuIEVzdG9zIG9wZXJhZG9yZXMgZXZvbHVjaW9uYW4gbGEgcG9ibGFjacOzbiBoYWNpYSByZWdpb25lcyBjb24gbWVqb3IgZml0bmVzcy4NCg0KU2Vnw7puIChTY3J1Y2NhLCAyMDEzKSwgbG9zIEdBcyBoYW4gc2lkbyBleGl0b3NvcyBlbiBvcHRpbWl6YXIgZnVuY2lvbmVzIGNvbnRpbnVhcyAoZGlmZXJlbmNpYWJsZXMgbyBubykgeSBkaXNjcmV0YXMuIEVudHJlIGxvcyBvcGVyYWRvcmVzIGdlbsOpdGljb3MgY2xhdmUgc2UgZGVzdGFjYW46DQoNCi0gICBTZWxlY2Npw7NuOiBlbGlnZSBpbmRpdmlkdW9zIGNvbiBtYXlvciBmaXRuZXNzIHBhcmEgcmVwcm9kdWNpcnNlLCBpbWl0YW5kbyBsYSBzdXBlcnZpdmVuY2lhIGRlbCBtw6FzIGFwdG8uDQoNCi0gICBDcnVjZSAoY3Jvc3NvdmVyKTogY29tYmluYSBwYXJ0ZXMgZGUgZG9zIHNvbHVjaW9uZXMgcGFyZW50YWxlcyBwYXJhIGdlbmVyYXIgZGVzY2VuZGVuY2lhLCBleHBsb3JhbmRvIG51ZXZhcyByZWdpb25lcyBkZWwgZXNwYWNpbyBkZSBiw7pzcXVlZGEuDQoNCi0gICBNdXRhY2nDs246IGFsdGVyYSBhbGVhdG9yaWFtZW50ZSBwYXJ0ZSBkZSB1biBpbmRpdmlkdW8gKHBvciBlamVtcGxvLCBjYW1iaWFuZG8gdW4gdmFsb3IgZGUgc3UgdmVjdG9yIGRlIHZhcmlhYmxlcykgcGFyYSBpbnRyb2R1Y2lyIGRpdmVyc2lkYWQgZ2Vuw6l0aWNhIHkgZXZpdGFyIGVzdGFuY2FtaWVudG8gZW4gw7NwdGltb3MgbG9jYWxlcy4NCg0KTG9zIGFsZ29yaXRtb3MgZ2Vuw6l0aWNvcyAoQUcpIHNvbiBtZXRhaGV1csOtc3RpY2FzIGluc3BpcmFkYXMgZW4gcHJvY2Vzb3MgZXZvbHV0aXZvcyBiaW9sw7NnaWNvcywgcXVlIGhhbiBkZW1vc3RyYWRvIGVmaWNhY2lhIGVuIGxhIGLDunNxdWVkYSBnbG9iYWwgZGUgw7NwdGltb3MgZW4gZnVuY2lvbmVzIGNvbXBsZWphc+KAi2pzdGF0c29mdC5vcmcgTG9zIEFHIHNpbXVsYW4gbGEgc2VsZWNjacOzbiBuYXR1cmFsLCBsYSByZWNvbWJpbmFjacOzbiAoY3J1Y2UpIHkgbGEgbXV0YWNpw7NuIHBhcmEgaXRlcmF0aXZhbWVudGUgbWVqb3JhciB1biBjb25qdW50byBkZSBzb2x1Y2lvbmVzIGNhbmRpZGF0YXMgKHBvYmxhY2nDs24pLiBFc3RhcyB0w6ljbmljYXMgZXN0b2PDoXN0aWNhcyBzb24gYWRlY3VhZGFzIHBhcmEgZnVuY2lvbmVzIG5vIGxpbmVhbGVzLCBkaXNjb250aW51YXMgbyBjb24gbcO6bHRpcGxlcyDDs3B0aW1vcyBsb2NhbGVzIGRvbmRlIGxvcyBtw6l0b2RvcyBiYXNhZG9zIGVuIGRlcml2YWRhcyBwdWVkZW4gZmFsbGFyLiBQYXJhIGV2YWx1YXIgbGEgcm9idXN0ZXogZGUgbG9zIEdBLCBzZSByZWFsaXphcsOhbiBtw7psdGlwbGVzIGVqZWN1Y2lvbmVzIGluZGVwZW5kaWVudGVzIHkgc2UgYW5hbGl6YXLDoSBsYSBkaXNwZXJzacOzbiBkZWwgZml0bmVzcyByZXN1bHRhbnRlLg0KDQpBZGljaW9uYWxtZW50ZSwgc2UgZGVmaW5lbiBhbGd1bmFzIGZ1bmNpb25lcyBwYXJhIHBvZGVyIG1vc3RyYXIgcG9yIG1lZGlvIGRlIHVuYSBhbmltYWNpw7NuIGVsIHByb2Nlc28gZGUgb3B0aW1pemFjacOzbiBwYXJhIGxhcyBmdW5jaW9uZXMgZGUgUm9zZW5icm9jayB5IGRlIFJhc3RyaWdpbi4NCg0KYGBge3J9DQphbmltYXRlX2dhX29wdGltaXphdGlvbiA8LSBmdW5jdGlvbihmdW5jKSB7DQogICMgMS4gQ3JlYXIgZWwgZW50b3JubyBwYXJhIGFsbWFjZW5hciBsYSBldm9sdWNpw7NuIGRlIGxhIHBvYmxhY2nDs24NCiAgcG9wX2RhdGEgPC0gZGF0YS5mcmFtZSgpDQogIA0KICAjIDIuIEVqZWN1dGFyIGVsIGFsZ29yaXRtbyBnZW7DqXRpY28sIGNhcHR1cmFuZG8gbGFzIHBvYmxhY2lvbmVzDQogIGdhX3Jhc3RyaWdpbiA8LSBnYSgNCiAgICB0eXBlID0gInJlYWwtdmFsdWVkIiwNCiAgICBmaXRuZXNzID0gZnVuY3Rpb24oeCkgLWZ1bmMoeCksDQogICAgbG93ZXIgPSBjKC01LjEyLCAtNS4xMiksIHVwcGVyID0gYyg1LjEyLCA1LjEyKSwNCiAgICBwb3BTaXplID0gNTAsIG1heGl0ZXIgPSA1MCwgcnVuID0gNTAsDQogICAgbW9uaXRvciA9IGZ1bmN0aW9uKG9iaikgew0KICAgICAgZ2VuIDwtIG9iakBpdGVyDQogICAgICBwb3AgPC0gb2JqQHBvcHVsYXRpb24NCiAgICAgIGRmIDwtIGRhdGEuZnJhbWUoDQogICAgICAgIFgxID0gcG9wWywgMV0sDQogICAgICAgIFgyID0gcG9wWywgMl0sDQogICAgICAgIEdlbmVyYWNpb24gPSBnZW4NCiAgICAgICkNCiAgICAgIHBvcF9kYXRhIDw8LSByYmluZChwb3BfZGF0YSwgZGYpDQogICAgfQ0KICApDQogIA0KICAjIDMuIENyZWFyIGdyaWxsYSBwYXJhIHZpc3VhbGl6YXIgbGEgZnVuY2nDs24gUmFzdHJpZ2luDQogIHggPC0gc2VxKC01LjEyLCA1LjEyLCBsZW5ndGgub3V0ID0gMTAwKQ0KICB5IDwtIHNlcSgtNS4xMiwgNS4xMiwgbGVuZ3RoLm91dCA9IDEwMCkNCiAgZ3JpZCA8LSBleHBhbmQuZ3JpZChYMSA9IHgsIFgyID0geSkNCiAgZ3JpZCRaIDwtIGFwcGx5KGdyaWQsIDEsIGZ1bmMpDQogIA0KICAjIDQuIEdyYWZpY2FyIHkgYW5pbWFyDQogIGJhc2VfcGxvdCA8LSBnZ3Bsb3QoKSArDQogICAgZ2VvbV9yYXN0ZXIoZGF0YSA9IGdyaWQsIGFlcyh4ID0gWDEsIHkgPSBYMiwgZmlsbCA9IFopLCBpbnRlcnBvbGF0ZSA9IFRSVUUpICsNCiAgICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsNCiAgICBnZW9tX3BvaW50KGRhdGEgPSBwb3BfZGF0YSwgYWVzKHggPSBYMSwgeSA9IFgyKSwgY29sb3IgPSAicmVkIiwgc2l6ZSA9IDEsIGFscGhhID0gMC42KSArDQogICAgbGFicyh0aXRsZSA9ICJPcHRpbWl6YWNpw7NuIGRlIFJhc3RyaWdpbiB1c2FuZG8gR0EiLCBzdWJ0aXRsZSA9ICJHZW5lcmFjacOzbjoge2Nsb3Nlc3Rfc3RhdGV9IiwNCiAgICAgICAgIHggPSAieDEiLCB5ID0gIngyIikgKw0KICAgIHRyYW5zaXRpb25fc3RhdGVzKEdlbmVyYWNpb24sIHRyYW5zaXRpb25fbGVuZ3RoID0gMiwgc3RhdGVfbGVuZ3RoID0gMSkgKw0KICAgIHRoZW1lX21pbmltYWwoKQ0KICANCiAgIyA1LiBFeHBvcnRhciBjb21vIEdJRg0KICBhbmltX3NhdmUoIm9wdGltX3Jhc3RyaWdpbl9nYS5naWYiLCBhbmltYXRpb24gPSBhbmltYXRlKGJhc2VfcGxvdCwgcmVuZGVyZXIgPSBnaWZza2lfcmVuZGVyZXIoKSwgZnBzID0gNSwgd2lkdGggPSA2MDAsIGhlaWdodCA9IDUwMCkpDQp9DQoNCmBgYA0KDQpBbmFsaXphcmVtb3MgY2FkYSBmdW5jacOzbiBlbiAyIHkgMyBkaW1lbnNpb25lcywgZ3JhZmljYW5kbyBzdSBwYWlzYWplIGFudGVzIGRlIGxhIG9wdGltaXphY2nDs24geSBsdWVnbyBhcGxpY2FuZG8gdW4gQUcgY29uIG3Dumx0aXBsZXMgY29ycmlkYXMgcGFyYSBldmFsdWFyIGxhIHJvYnVzdGV6IGRlIGxvcyByZXN1bHRhZG9zLg0KDQojIyMgMS42LjEgSW1wbGVtZW50YWNpw7NuIGVuIFIgZGUgYWxnb3JpdG1vcyBldm9sdXRpdm9zDQoNClNlIHV0aWxpemEgbGEgZnVuY2nDs24gZ2EoKSBkZWwgcGFxdWV0ZSBHQS4gUGFyYSBwcm9ibGVtYXMgZGUgbWluaW1pemFjacOzbiBzZSBkZWZpbmUgbGEgZnVuY2nDs24gZGUgZml0bmVzcyBjb21vIGVsIG5lZ2F0aXZvIGRlbCB2YWxvciBvYmpldGl2bywgeWEgcXVlIGdhKCkgbWF4aW1pemEgcG9yIGRlZmVjdG8uIFNlIGVzcGVjaWZpY2FuIGxvcyBsw61taXRlcyBkZSBiw7pzcXVlZGEuDQoNCkxvcyByZXPDum1lbmVzIGVuIGNhZGEgZWplY3VjacOzbiByZXBvcnRhbiBlbCBtZWpvciBmaXRuZXNzIGVuY29udHJhZG8gKG5lZ2F0aXZvKSB5IGxhIHNvbHVjacOzbiDDs3B0aW1hIGVuIGNhZGEgZWplY3VjacOzbi4NCg0KIyMjIDEuNi4yIE9wdGltaXphY2nDs24gZGUgbGEgZnVuY2nDs24gZGUgUm9zZW5icm9jayBlbiAyIGRpbWVuc2lvbmVzDQoNCmBgYHtyfQ0KIyBFamVjdWNpw7NuIGRlbCBtw6l0b2RvDQpnYV9yb3MyZCA8LSBnYSh0eXBlID0gInJlYWwtdmFsdWVkIiwNCiAgICAgICAgICAgICAgIGZpdG5lc3MgPSBmdW5jdGlvbih4KSAtZl9yb3NlbmJyb2NrKHgpLA0KICAgICAgICAgICAgICAgbG93ZXIgPSBjKC01LCAtNSksIHVwcGVyID0gYyg1LCA1KSwNCiAgICAgICAgICAgICAgIHBvcFNpemUgPSA1MCwgbWF4aXRlciA9IDEwMCwgcnVuID0gNTApDQpzdW1tYXJ5KGdhX3JvczJkKQ0KYGBgDQoNCmBgYHtyfQ0KIyBHcmFmaWNhY2nDs24gZGVsIHByb2Nlc28gZGUgb3B0aW1pemFjacOzbg0KZ2lmX25hbWUgPC0gIm9wdGltX3Jvc2VuYnJvY2tfZ2EuZ2lmIg0KYW5pbWF0ZV9nYV9vcHRpbWl6YXRpb24oZl9yb3NlbmJyb2NrKQ0KYGBgDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKSAgIyBzZW1pbGxhIHJlcHJvZHVjaWJsZQ0KYmVzdF92YWxzX3JvcyA8LSByZXBsaWNhdGUoMzAsIHsNCiAgR0EgPC0gZ2EodHlwZSA9ICJyZWFsLXZhbHVlZCIsDQogICAgICAgICAgIGZpdG5lc3MgPSBmdW5jdGlvbih4KSAtZl9yb3NlbmJyb2NrKHgpLA0KICAgICAgICAgICBsb3dlciA9IGMoLTUsIC01KSwgdXBwZXIgPSBjKDUsIDUpLA0KICAgICAgICAgICBwb3BTaXplID0gNTAsIG1heGl0ZXIgPSAxMDAsIHJ1biA9IDUwKQ0KICAtR0FAZml0bmVzc1ZhbHVlICAjIGNvbnZlcnRpciBhIHZhbG9yIHBvc2l0aXZvDQp9KQ0KbWVhbl9yb3MgPC0gbWVhbihiZXN0X3ZhbHNfcm9zKQ0Kc2Rfcm9zICAgPC0gc2QoYmVzdF92YWxzX3JvcykNCmBgYA0KDQojIyMgMS42LjMgT3B0aW1pemFjacOzbiBkZSBsYSBmdW5jacOzbiBkZSBSb3NlbmJyb2NrIGVuIDMgZGltZW5zaW9uZXMNCg0KYGBge3J9DQojIEVqZWN1Y2nDs24gZGVsIG3DqXRvZG8NCmdhX3JvczNkIDwtIGdhKHR5cGUgPSAicmVhbC12YWx1ZWQiLA0KICAgICAgICAgICAgICAgZml0bmVzcyA9IGZ1bmN0aW9uKHgpIC1mX3Jvc2VuYnJvY2soeCksDQogICAgICAgICAgICAgICBsb3dlciA9IGMoLTUsIC01LCAzKSwgdXBwZXIgPSBjKDUsIDUsIDMpLA0KICAgICAgICAgICAgICAgcG9wU2l6ZSA9IDUwLCBtYXhpdGVyID0gMTAwLCBydW4gPSA1MCkNCnN1bW1hcnkoZ2Ffcm9zM2QpDQpgYGANCg0KYGBge3J9DQojIFJlYWxpemFyIDMwIGVqZWN1Y2lvbmVzIGluZGVwZW5kaWVudGVzIHBhcmEgUm9zZW5icm9jayAzRA0Kc2V0LnNlZWQoMTIzKSAgIyBzZW1pbGxhIHJlcHJvZHVjaWJsZQ0KYmVzdF92YWxzX3JvczNkIDwtIHJlcGxpY2F0ZSgzMCwgew0KICBHQSA8LSBnYSh0eXBlID0gInJlYWwtdmFsdWVkIiwNCiAgICAgICAgICAgZml0bmVzcyA9IGZ1bmN0aW9uKHgpIC1mX3Jvc2VuYnJvY2soeCksDQogICAgICAgICAgIGxvd2VyID0gYygtNSwgLTUsIDMpLCB1cHBlciA9IGMoNSwgNSwgMyksDQogICAgICAgICAgIHBvcFNpemUgPSA1MCwgbWF4aXRlciA9IDEwMCwgcnVuID0gNTApDQogIC1HQUBmaXRuZXNzVmFsdWUgICMgY29udmVydGlyIGEgdmFsb3IgcG9zaXRpdm8NCn0pDQptZWFuX3JvczNkIDwtIG1lYW4oYmVzdF92YWxzX3JvczNkKQ0Kc2Rfcm9zM2QgICA8LSBzZChiZXN0X3ZhbHNfcm9zM2QpDQojICBSb3NlbmJyb2NrIDNELCBSYXN0cmlnaW4gM0QuDQpgYGANCg0KIyMjIDEuNi40IE9wdGltaXphY2nDs24gZGUgbGEgZnVuY2nDs24gZGUgUmFzdHJpZ2luIGVuIDIgZGltZW5zaW9uZXMNCg0KYGBge3J9DQojIEVqZWN1Y2nDs24gZGVsIG3DqXRvZG8NCmdhX3JhczJkIDwtIGdhKHR5cGUgPSAicmVhbC12YWx1ZWQiLA0KICAgICAgICAgICAgICAgZml0bmVzcyA9IGZ1bmN0aW9uKHgpIC1mX3Jhc3RyaWdpbih4KSwNCiAgICAgICAgICAgICAgIGxvd2VyID0gYygtNSwgLTEyKSwgdXBwZXIgPSBjKDUsIDEyKSwNCiAgICAgICAgICAgICAgIHBvcFNpemUgPSA1MCwgbWF4aXRlciA9IDEwMCwgcnVuID0gNTApDQpzdW1tYXJ5KGdhX3JhczJkKQ0KDQpgYGANCg0KYGBge3J9DQojIEdyYWZpY2FjacOzbiBkZWwgcHJvY2VzbyBkZSBvcHRpbWl6YWNpw7NuDQpnaWZfbmFtZSA8LSAib3B0aW1fcmFzdHJpZ2luX2dhLmdpZiINCmFuaW1hdGVfZ2Ffb3B0aW1pemF0aW9uKGZfcmFzdHJpZ2luKQ0KYGBgDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTIzKSAgIyBzZW1pbGxhIHJlcHJvZHVjaWJsZQ0KYmVzdF92YWxzX3JhczJkIDwtIHJlcGxpY2F0ZSgzMCwgew0KICBHQSA8LSBnYSh0eXBlID0gInJlYWwtdmFsdWVkIiwNCiAgICAgICAgICAgZml0bmVzcyA9IGZ1bmN0aW9uKHgpIC1mX3Jhc3RyaWdpbih4KSwNCiAgICAgICAgICAgbG93ZXIgPSBjKC01LCAtNSksIHVwcGVyID0gYyg1LCA1KSwNCiAgICAgICAgICAgcG9wU2l6ZSA9IDUwLCBtYXhpdGVyID0gMTAwLCBydW4gPSA1MCkNCiAgLUdBQGZpdG5lc3NWYWx1ZSAgIyBjb252ZXJ0aXIgYSB2YWxvciBwb3NpdGl2bw0KfSkNCm1lYW5fcmFzMmQgPC0gbWVhbihiZXN0X3ZhbHNfcmFzMmQpDQpzZF9yYXMyZCAgIDwtIHNkKGJlc3RfdmFsc19yYXMyZCkNCmBgYA0KDQojIyMgMS42LjUgT3B0aW1pemFjacOzbiBkZSBsYSBmdW5jacOzbiBkZSBSYXN0cmlnaW4gZW4gMyBkaW1lbnNpb25lcw0KDQpgYGB7cn0NCiMgRWplY3VjacOzbiBkZWwgbcOpdG9kbw0KZ2FfcmFzM2QgPC0gZ2EodHlwZSA9ICJyZWFsLXZhbHVlZCIsDQogICAgICAgICAgICAgICBmaXRuZXNzID0gZnVuY3Rpb24oeCkgLWZfcmFzdHJpZ2luKHgpLA0KICAgICAgICAgICAgICAgbG93ZXIgPSBjKC01LCAtMTIsMyksIHVwcGVyID0gYyg1LCAxMiwzICksDQogICAgICAgICAgICAgICBwb3BTaXplID0gNTAsIG1heGl0ZXIgPSAxMDAsIHJ1biA9IDUwKQ0Kc3VtbWFyeShnYV9yYXMzZCkNCmBgYA0KDQpgYGB7cn0NCnNldC5zZWVkKDEyMykgICMgc2VtaWxsYSByZXByb2R1Y2libGUNCmJlc3RfdmFsc19yYXMzZCA8LSByZXBsaWNhdGUoMzAsIHsNCiAgR0EgPC0gZ2EodHlwZSA9ICJyZWFsLXZhbHVlZCIsDQogICAgICAgICAgIGZpdG5lc3MgPSBmdW5jdGlvbih4KSAtZl9yYXN0cmlnaW4oeCksDQogICAgICAgICAgIGxvd2VyID0gYygtNSwgLTUsIDMpLCB1cHBlciA9IGMoNSwgNSwgMyksDQogICAgICAgICAgIHBvcFNpemUgPSA1MCwgbWF4aXRlciA9IDEwMCwgcnVuID0gNTApDQogIC1HQUBmaXRuZXNzVmFsdWUgICMgY29udmVydGlyIGEgdmFsb3IgcG9zaXRpdm8NCn0pDQptZWFuX3JhczNkIDwtIG1lYW4oYmVzdF92YWxzX3JhczNkKQ0Kc2RfcmFzM2QgICA8LSBzZChiZXN0X3ZhbHNfcmFzM2QpDQpgYGANCg0KIyMjIDEuNi42IEPDoWxjdWxvIGRlIGVzdGFkw61zdGljYXMgeSBhbsOhbGlzaXMNCg0KUGFyYSBldmFsdWFyIGxhIHZhcmlhYmlsaWRhZCBkZWwgbcOpdG9kbyBlc3RvY8Ohc3RpY28sIHNlIHJlcGl0ZSBjYWRhIGNhc28gYWwgbWVub3MgMzAgdmVjZXMgY29uIHNlbWlsbGFzIGRpc3RpbnRhcy4gU2UgcmVnaXN0cmEgZWwgbWVqb3IgdmFsb3IgZGUgZml0bmVzcyAodmFsb3JpemFkbyBwb3NpdGl2YW1lbnRlKSBvYnRlbmlkbyBlbiBjYWRhIGNvcnJpZGEuIFNlIGNhbGN1bGFyw6FuIGVzdGFkw61zdGljYXMgKG1lZGlhIHkgZGVzdmlhY2nDs24gZXN0w6FuZGFyKSBkZWwgbWVqb3IgdmFsb3IgZGUgZml0bmVzcyBvYnRlbmlkbyBlbiAzMCBlamVjdWNpb25lcyBpbmRlcGVuZGllbnRlcyBkZSBjYWRhIGNhc28geSBzZSByZXN1bWlyw6FuIGVuIHVuYSB0YWJsYS4NCg0KQ29uIGxvcyB2ZWN0b3JlcyBkZSBtZWpvcmVzIHZhbG9yZXMgKGJlc3RfdmFsc19yb3MsIGV0Yy4pLCBzZSBjYWxjdWxhbiBsYSBtZWRpYSB5IGRlc3ZpYWNpw7NuIGVzdMOhbmRhciBkZSBjYWRhIGNvbmp1bnRvIGRlIDMwIHJlc3VsdGFkb3MuIFBvciBlamVtcGxvLCBtZWFuX3JvcyB5IHNkX3JvcyBhcnJpYmEgeSBsYXMgZGVtYXMsIFBhcmEgYXNpIHByZXNlbnRhciBsb3MgcmVzdWx0YWRvcy4NCg0KYGBge3J9DQpsaWJyYXJ5KGtuaXRyKQ0KcmVzdWx0YWRvcyA8LSBkYXRhLmZyYW1lKA0KICBGdW5jacOzbiAgID0gYygiUm9zZW5icm9jayIsICJSYXN0cmlnaW4iLCAiUm9zZW5icm9jayIsICJSYXN0cmlnaW4iKSwNCiAgRGltZW5zacOzbiA9IGMoIjJEIiwgIjJEIiwgIjNEIiwgIjNEIiksDQogIE1lZGlhICAgICA9IGMobWVhbl9yb3MsIG1lYW5fcmFzMmQsIG1lYW5fcm9zM2QsIG1lYW5fcmFzM2QpLA0KICBTRCAgICAgICAgPSBjKHNkX3Jvcywgc2RfcmFzMmQsIHNkX3JvczNkLCBzZF9yYXMzZCkNCikNCmthYmxlKHJlc3VsdGFkb3MsIGNhcHRpb24gPSAiUmVzdW1lbiBlc3RhZMOtc3RpY28gKG1lZGlhIHkgZGVzdmlhY2nDs24gZXN0w6FuZGFyKSBkZWwgbWVqb3IgZml0bmVzcyBvYnRlbmlkbyB0cmFzIDMwIGVqZWN1Y2lvbmVzIGluZGVwZW5kaWVudGVzIGRlIGNhZGEgY2Fzby4iKQ0KDQpgYGANCg0KTG9zIHJlc3VsdGFkb3MgZGUgbGFzIG3Dumx0aXBsZXMgZWplY3VjaW9uZXMgc2UgcmVzdW1lbiBlbiBsYSBUYWJsYSAxLiBFc3RhIHRhYmxhIG11ZXN0cmEgbGEgbWVkaWEgeSBkZXN2aWFjacOzbiBlc3TDoW5kYXIgZGVsIG1lam9yIHZhbG9yIGRlIGZpdG5lc3MgKHJlY29yZGFkbyBxdWUgZXMgZWwgdmFsb3IgZGUgbGEgZnVuY2nDs24gb2JqZXRpdm8gZW4gc3UgbcOtbmltbyBnbG9iYWwsIHTDrXBpY2FtZW50ZSBjZXJjYW5vIGEgMCkgcGFyYSBjYWRhIGNvbWJpbmFjacOzbiBkZSBmdW5jacOzbiB5IGRpbWVuc2nDs24uIFNlIG9ic2VydmEgcXVlIHBhcmEgUm9zZW5icm9jayAyRCwgbGEgbWVkaWEgZGVsIGZpdG5lc3MgbcOtbmltbyBlcyBjZXJjYW5hIGEgMCBjb24gYmFqYSBkaXNwZXJzacOzbiwgcmVmbGVqYW5kbyBxdWUgZWwgR0Egbm9ybWFsbWVudGUgZW5jdWVudHJhIGVsIG3DrW5pbW8gZ2xvYmFsICgwKSBvIGNlcmNhbm8uIFBhcmEgUmFzdHJpZ2luIDJELCBsYSBtZWRpYSB0YW1iacOpbiBwdWVkZSBhY2VyY2Fyc2UgYSAwLCBwZXJvIGNvbiBtYXlvciBkZXN2aWFjacOzbiBlc3TDoW5kYXIgZGViaWRvIGEgbG9zIG3Dumx0aXBsZXMgbcOtbmltb3MgbG9jYWxlcy4gRW4gM0QgYW1ib3MgcHJvYmxlbWFzIHN1ZWxlbiBtb3N0cmFyIHZhbG9yZXMgbWVkaW9zIG1heW9yZXMgKG3DoXMgYWxlamFkb3MgZGUgMCkgeSBtYXlvciB2YXJpYWJpbGlkYWQsIGxvIGN1YWwgaW5kaWNhIHVuYSBtYXlvciBkaWZpY3VsdGFkIGRlIGLDunNxdWVkYSBhbCBhdW1lbnRhciBsYSBkaW1lbnNpb25hbGlkYWQuDQoNCmBgYHtyfQ0KIyB0YWJsYSBkZSBsb3MgdmFsb3JlcyBjYWxjdWxhZG9zKQ0KbGlicmFyeShrbml0cikNCnJlc19kZiA8LSBkYXRhLmZyYW1lKA0KICBGdW5jacOzbiAgID0gYygiUm9zZW5icm9jayIsICJSYXN0cmlnaW4iLCAiUm9zZW5icm9jayIsICJSYXN0cmlnaW4iKSwNCiAgRGltZW5zacOzbiA9IGMoIjJEIiwgIjJEIiwgIjNEIiwgIjNEIiksDQogIE1lZGlhICAgICA9IGMobWVhbl9yb3MsIG1lYW5fcmFzMmQsIG1lYW5fcm9zM2QsIG1lYW5fcmFzM2QpLA0KICBTRCAgICAgICAgPSBjKHNkX3Jvcywgc2RfcmFzMmQsIHNkX3JvczNkLCBzZF9yYXMzZCkNCikNCmthYmxlKHJlc19kZiwgY2FwdGlvbiA9ICJUYWJsYSAxLiBFc3RhZMOtc3RpY2FzIChtZWRpYSB5IGRlc3ZpYWNpw7NuIGVzdMOhbmRhcikgZGVsIGZpdG5lc3MgbcOtbmltbyBhbGNhbnphZG8gZW4gMzAgY29ycmlkYXMgaW5kZXBlbmRpZW50ZXMgcGFyYSBjYWRhIGZ1bmNpw7NuIHkgZGltZW5zacOzbi4iKQ0KDQpgYGANCg0KKipUYWJsYSAxLioqIEVzdGFkw61zdGljYXMgKG1lZGlhIHkgZGVzdmlhY2nDs24gZXN0w6FuZGFyKSBkZWwgZml0bmVzcyBtw61uaW1vIGFsY2FuemFkbyBlbiAzMCBjb3JyaWRhcyBpbmRlcGVuZGllbnRlcyBwYXJhIGNhZGEgZnVuY2nDs24geSBkaW1lbnNpw7NuLg0KDQojIyMgMS42LjcgQ29uY2x1c2lvbmVzIG3DqXRvZG8gZGUgYWxnb3JpdG1vcyBldm9sdXRpdm9zDQoNCkxvcyByZXN1bHRhZG9zIGNvbmZpcm1hbiBxdWUgZWwgYWxnb3JpdG1vIGdlbsOpdGljbyBlcyBjYXBheiBkZSBhcHJveGltYXJzZSBhIGxvcyBtw61uaW1vcyBnbG9iYWxlcyBkZSBhbWJvcyBwcm9ibGVtYXMgZW4gbcO6bHRpcGxlcyBkaW1lbnNpb25lcy4gQ29tbyBlcmEgZGUgZXNwZXJhciwgUmFzdHJpZ2luIG1vc3Ryw7MgbWF5b3IgdmFyaWFiaWxpZGFkIGVuIGxvcyB2YWxvcmVzIGRlIGZpdG5lc3MgZGViaWRvIGEgc3VzIG11Y2hvcyBtw61uaW1vcyBsb2NhbGVzLCBsbyBxdWUgaW1wbGljYSBxdWUgYWxndW5hcyBlamVjdWNpb25lcyBkZWwgR0EgcHVlZGVuIHF1ZWRhcnNlIGF0cmFwYWRhcyBlbiDDs3B0aW1vcyBsb2NhbGVzIGFsZWphZG9zIGRlbCBnbG9iYWwuIEVuIGNvbnRyYXN0ZSwgUm9zZW5icm9jayAoYXVucXVlIGVzIG5vIGNvbnZleGEpIHRpZW5kZSBhIHVuIMO6bmljbyB2YWxsZSBwcmluY2lwYWw7IHBvciBlbGxvLCBsYSBtYXlvcsOtYSBkZSBsYXMgY29ycmlkYXMgYWxjYW56YXJvbiB2YWxvcmVzIGNlcmNhbm9zIGFsIG3DrW5pbW8gZ2xvYmFsIGNvbiBtZW5vciBkaXNwZXJzacOzbi4gRW4gZ2VuZXJhbCBzZSBvYnNlcnZhIHF1ZSBhbCBhdW1lbnRhciBsYSBkaW1lbnNpw7NuIChkZSAyRCBhIDNEKSBsYSB0YXJlYSBzZSBjb21wbGljYSB5IGxhIG1lZGlhIGRlbCBmaXRuZXNzIGF1bWVudGEgKHBlb3Igw7NwdGltbyBlbmNvbnRyYWRvKSwgcmVmbGVqYW5kbyBsYSBtYWxkaWNpw7NuIGRlIGxhIGRpbWVuc2lvbmFsaWRhZC4gRWwgdXNvIGRlIG3Dumx0aXBsZXMgZWplY3VjaW9uZXMgaW5kZXBlbmRpZW50ZXMgZXMgZXNlbmNpYWwgcGFyYSBldmFsdWFyIGxhIHJvYnVzdGV6IGRlIGxvcyBBRy4gRGViaWRvIGEgc3UgbmF0dXJhbGV6YSBlc3RvY8Ohc3RpY2EsIGNhZGEgZWplY3VjacOzbiBwdWVkZSBjb252ZXJnZXIgYSBzb2x1Y2lvbmVzIGRpc3RpbnRhcy4gQWwgYW5hbGl6YXIgbGEgbWVkaWEgeSBkZXN2aWFjacOzbiBlc3TDoW5kYXIgZGUgbG9zIGZpdG5lc3MgZmluYWxlcyBzZSBvYnRpZW5lIHVuYSBtZWRpZGEgZGUgZmlhYmlsaWRhZCBkZWwgYWxnb3JpdG1vOiB1bmEgYmFqYSBkZXN2aWFjacOzbiBpbmRpY2EgcmVzdWx0YWRvcyBjb25zaXN0ZW50ZXMuIEVuIGxhIGxpdGVyYXR1cmEgc29icmUgYWxnb3JpdG1vcyBnZW7DqXRpY29zIHNlIHJlY29ub2NlIHF1ZSBlbiBtdWNob3MgY2Fzb3MgdW5hIHNvbGEgZWplY3VjacOzbiBwdWVkZSBubyBzZXIgcmVwcmVzZW50YXRpdmHigItqc3RhdHNvZnQub3JnLiBBdW5xdWUgdW4gYW7DoWxpc2lzIGNvbXBhcmF0aXZvIHByb2Z1bmRvIChwLmVqLiwgdXNhbmRvIHBvYmxhY2lvbmVzIG3DoXMgZ3JhbmRlcyBvIG3Dumx0aXBsZXMgY29ycmlkYXMgZW4gcGFyYWxlbG8pIHF1ZWRhIGZ1ZXJhIGRlbCBhbGNhbmNlIGRlIGVzdGUgZG9jdW1lbnRvLCBudWVzdHJvcyByZXN1bHRhZG9zIGlsdXN0cmFuIGVzdGUgZmVuw7NtZW5vLiBFc3RlIGVzdHVkaW8gZXMgcmVwcm9kdWNpYmxlOiB0b2RvIGVsIGPDs2RpZ28gUiBuZWNlc2FyaW8gZXN0w6EgaW5jbHVpZG8sIHBlcm1pdGllbmRvIGEgb3Ryb3MgaW52ZXN0aWdhZG9yZXMgcmVwbGljYXIgbG9zIGV4cGVyaW1lbnRvcywgdmFyaWFyIHBhcsOhbWV0cm9zIGRlbCBHQSAodGFzYSBkZSBjcnVjZSwgbXV0YWNpw7NuLCB0YW1hw7FvIGRlIHBvYmxhY2nDs24sIGV0Yy4pIHkgY29tcGFyYXIgY29uIG90cm9zIGFsZ29yaXRtb3MgZGUgb3B0aW1pemFjacOzbi46Q29uY2x1c2lvbmVzIFNlIGhhIHByZXNlbnRhZG8gdW5hIGRvY3VtZW50YWNpw7NuIGNvbXBsZXRhIGRlIGxhIG9wdGltaXphY2nDs24gZGUgbGFzIGZ1bmNpb25lcyBkZSBSb3NlbmJyb2NrIHkgUmFzdHJpZ2luIGVuIDJEIHkgM0QgZW1wbGVhbmRvIGFsZ29yaXRtb3MgZ2Vuw6l0aWNvcyBlbiBSLiBNZWRpYW50ZSB2aXN1YWxpemFjaW9uZXMgM0QgaW5pY2lhbGVzIHNlIGlsdXN0cmFyb24gbGFzIGNhcmFjdGVyw61zdGljYXMgZGUgY2FkYSBmdW5jacOzbiBkZSBwcnVlYmEuIFNlIGltcGxlbWVudMOzIGVsIHBhcXVldGUgR0EgcGFyYSByZXNvbHZlciBjYWRhIGNhc28geSBzZSByZWFsaXphcm9uIDMwIGVqZWN1Y2lvbmVzIGluZGVwZW5kaWVudGVzIHBhcmEgZXZhbHVhciBsYSByb2J1c3Rlei4gTG9zIHJlc3VsdGFkb3MgbXVlc3RyYW4gcXVlIGVsIEdBIHB1ZWRlIGVuY29udHJhciBhcHJveGltYWNpb25lcyBhbCBtw61uaW1vIGdsb2JhbCBlbiBhbWJvcyBwcm9ibGVtYXMsIGF1bnF1ZSBsYSBmdW5jacOzbiBSYXN0cmlnaW4gKG3Dumx0aXBsZXMgbcOtbmltb3MgbG9jYWxlcykgcHJlc2VudGEgbcOhcyB2YXJpYWJpbGlkYWQgeSBkaWZpY3VsdGFkLCBlc3BlY2lhbG1lbnRlIGVuIDNELg0KDQojIFBhcnRlIDIuIE9wdGltaXphY2nDs24gY29tYmluYXRvcmlhDQoNCiMjIDIuMSBQbGFudGVhbWllbnRvIGRlbCBwcm9ibGVtYQ0KDQpFbCBwcm9ibGVtYSBzZSBwdWVkZSBwbGFudGVhciBjb21vIHVuYSBpbnN0YW5jaWEgcGFydGljdWxhciBkZWwgcHJvYmxlbWEgZGVsIHZpYWplcm8gZW4gY2llbmNpYXMgZGUgbGEgY29tcHV0YWNpw7NuLiBFbiBsYSBUYWJsYSAyIHNlIG11ZXN0cmFuIGxhcyAxMyBjaXVkYWRlcyBwcmluY2lwYWxlcyBkZSBDb2xvbWJpYSBjb24gc3UgcmVzcGVjdGl2YXMgbGF0aXR1ZGVzIHkgbG9uZ2l0dWRlcy4NCg0KfCAgICAgICAgICAgICAgICAgICB8ICAgICAgICAgICAgIHwgICAgICAgICAgICAgIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS18DQp8ICAgICAgICAgICAgICAgICAgIHwgKipMYXRpdHVkKiogfCAqKkxvbmdpdHVkKiogfA0KfCAqKkJvZ290w6EqKiAgICAgICAgfCA0LjcxMTAgICAgICB8IC03NC4wNzIxICAgICB8DQp8ICoqTWVkZWxsw61uKiogICAgICB8IDYuMjQ0MiAgICAgIHwgLTc1LjU4MTIgICAgIHwNCnwgKipDYWxpKiogICAgICAgICAgfCAzLjQ1MTYgICAgICB8IC03Ni41MzIwICAgICB8DQp8ICoqQmFycmFucXVpbGxhKiogIHwgMTAuOTY4NSAgICAgfCAtNzQuNzgxMyAgICAgfA0KfCAqKkNhcnRhZ2VuYSoqICAgICB8IDEwLjM5MTAgICAgIHwgLTc1LjQ3OTQgICAgIHwNCnwgKipDw7pjdXRhKiogICAgICAgIHwgNy44OTQxICAgICAgfCAtNzIuNTA3OCAgICAgfA0KfCAqKlNvbGVkYWQqKiAgICAgICB8IDEwLjkyNjQgICAgIHwgLTc0LjgwNTUgICAgIHwNCnwgKipJYmFndcOpKiogICAgICAgIHwgNC40Mzg5ICAgICAgfCAtNzUuMjMyMiAgICAgfA0KfCAqKkJ1Y2FyYW1hbmdhKiogICB8IDcuMTE5MyAgICAgIHwgLTczLjEyMjcgICAgIHwNCnwgKipWaWxsYXZpY2VuY2lvKiogfCA0LjE0MjAgICAgICB8IC03My42Mjk4ICAgICB8DQp8ICoqU2FudGEgTWFydGEqKiAgIHwgMTEuMjQwOCAgICAgfCAtNzQuMTk5MCAgICAgfA0KfCAqKk1hbml6YWxlcyoqICAgICB8IDUuMDcwMyAgICAgIHwgLTc1LjUxMzggICAgIHwNCnwgKipQZXJlaXJhKiogICAgICAgfCA0LjgxNDMgICAgICB8IC03NS42OTQ2ICAgICB8DQoNCioqVGFibGEgMi4qKiBDaXVkYWRlcyBwcmluY2lwYWxlcyBkZSBDb2xvbWJpYSBqdW50byBjb24gc3UgbGF0ZW5jaWEgeSBsb25naXR1ZC4NCg0KRWwgb2JqZXRpdm8gZGUgZXN0ZSBwcm9ibGVtYSBlcyBvcHRpbWl6YXIgY29uIHJlc3BlY3RvIGEgbG9zIGNvc3RvcyB5IG5vIGEgbGFzIGRpc3RhbmNpYXMgZW50cmUgY2l1ZGFkZXMsIHBvciBsbyBxdWUgc2UgdGllbmVuIHF1ZSBjb25zaWRlcmFyIGxvcyBjb3N0b3MgZGUgY29tYnVzdGlibGUsIGRlIHBlYWplcyB5IGN1w6FudG8gY29icmEgZWwgdmVuZGVkb3IgcG9yIGhvcmEgZGUgdHJhYmFqby4gU2luIGVtYmFyZ28sIGxhIGRpc3RhbmNpYSBhZmVjdGFyw6EgYWwgdmFsb3IgZGUgdG9kb3MgZXN0b3MgY29zdG9zLCB5IHBvciBsbyB0YW50byBoYXkgcXVlIGNvbnNpZGVyYXJsYS4gQWRpY2lvbmFsbWVudGUsIHRvZG9zIGVzdG9zIGNvc3RvcyBlc3TDoW4gZW4gZnVuY2nDs24gZGUgbGEgZGlzdGFuY2lhIGVuIG1ldHJvcywgcG9yIGxvIHF1ZSBlcyBuZWNlc2FyaW8gcmVhbGl6YXIgbGEgY29udmVyc2nDs24gZGUgbGF0aXR1ZC1sb25naXR1ZCBhIGNvb3JkZW5hZGFzIGVuIG1ldHJvcy4gUGFyYSBlc3RvIHNlIHVzw7MgbGEgbGlicmVyw61hICJzZiIgcGFyYSByZWFsaXphciBsYSBjb252ZXJzacOzbiBhbCBzaXN0ZW1hIGRlIGNvb3JkZW5hZGFzIHBsYW5hcyBVVE0sIGVsIGN1YWwgcmVwcmVzZW50YSBsb3MgcHVudG9zIGRlbCBnbG9ibyBlbiBtZXRyb3MuDQoNCkxhIHRhYmxhIGRlIGNvb3JkZW5hZGFzIGVuIG1ldHJvcyB1dGlsaXphbmRvIGxhIGluZm9ybWFjacOzbiBkZSBsYSBUYWJsYSAxIHNlIGltcGxlbWVudGEgYSBjb250aW51YWNpw7NuOg0KDQpgYGB7cn0NCm5vbWJyZV9jaXVkYWRlcyA8LSBjKCJCb2dvdMOhIiwgIk1lZGVsbMOtbiIsICJDYWxpIiwgIkJhcnJhbnF1aWxsYSIsICJDYXJ0YWdlbmEiLCANCiAgICAgICAgICAgICAiQ8O6Y3V0YSIsICJQYXN0byIsICJJYmFndcOpIiwgIkJ1Y2FyYW1hbmdhIiwgIlZpbGxhdmljZW5jaW8iLCANCiAgICAgICAgICAgICAiU2FudGEgTWFydGEiLCAiTWFuaXphbGVzIiwgIlBlcmVpcmEiKQ0KDQojIERlZmluaWNpw7NuIGRlIGNpdWRhZGVzLCBsYXRpdHVkIHkgbG9uZ2l0dWQgZGUgY2FkYSBjaXVkYWQuDQpjaXVkYWRlcyA8LSB0aWJibGU6OnRpYmJsZSgNCiAgQ2l1ZGFkID0gbm9tYnJlX2NpdWRhZGVzLA0KICBMYXRpdHVkID0gYyg0LjcxMTAsIDYuMjQ0MiwgMy40NTE2LCAxMC45Njg1LCAxMC4zOTEwLCANCiAgICAgICAgICAgICAgNy44OTQxLCAxLjIxMzYsIDQuNDM4OSwgNy4xMTkzLCA0LjE0MjAsIA0KICAgICAgICAgICAgICAxMS4yNDA4LCA1LjA3MDMsIDQuODE0MyksDQogIExvbmdpdHVkID0gYygtNzQuMDcyMSwgLTc1LjU4MTIsIC03Ni41MzIwLCAtNzQuNzgxMywgLTc1LjQ3OTQsIA0KICAgICAgICAgICAgICAgLTcyLjUwNzgsIC03Ny4yODExLCAtNzUuMjMyMiwgLTczLjEyMjcsIC03My42Mjk4LCANCiAgICAgICAgICAgICAgIC03NC4xOTkwLCAtNzUuNTEzOCwgLTc1LjY5NDYpDQopDQoNCiMgVGltZSB6b25lIHZhbHVlDQojIFBvciBzaW1wbGljaWRhZCwgc2UgZWxpZ2UgZWwgbWlzbW8gcHVudG8gY29tw7puIHBhcmEgY2FsY3VsYXIgZWwgdGltZXpvbmUuDQojIFNlIHV0aWxpemEgbGEgbG9uZ2l0dWQgZGUgQm9nb3TDoSBjb21vIHB1bnRvIGNvbcO6biBwYXJhIGNhbGN1bGFyIGVsIHV0bV96b25lLg0KdXRtX3pvbmUgPC0gZmxvb3IoKC03NC4wNzIxICsgMTgwKS82KSArIDEgKyAzMjcwMA0KDQojIENvbnZlcnRpbW9zIGEgb2JqZXRvIGVzcGFjaWFsDQpwdW50b3MgPC0gc3RfYXNfc2YoY2l1ZGFkZXMsIGNvb3JkcyA9IGMoIkxvbmdpdHVkIiwgIkxhdGl0dWQiKSwgY3JzID0gNDMyNikNCg0KcHVudG9zX2VuX3V0bSA8LSBzdF90cmFuc2Zvcm0ocHVudG9zLCBjcnM9dXRtX3pvbmUpDQpjaXVkYWRlc19lbl9tZXRyb3MgPC0gc3RfY29vcmRpbmF0ZXMocHVudG9zX2VuX3V0bSkNCnRhYmxhX2Nvb3JkZW5hZGFzX2NpdWRhZGVzIDwtIGRhdGEuZnJhbWUoDQogIGNpdWRhZCA9IG5vbWJyZV9jaXVkYWRlcywNCiAgY29vcmRfeF9lbl9tZXRyb3MgPSBjaXVkYWRlc19lbl9tZXRyb3NbLDFdLA0KICBjb29yZF95X2VuX21ldHJvcyA9IGNpdWRhZGVzX2VuX21ldHJvc1ssMl0NCikNCnRhYmxhX2Nvb3JkZW5hZGFzX2NpdWRhZGVzDQoNCmBgYA0KDQpMdWVnbywgc2UgY3JlYSBsYSBtYXRyaXogZGUgZGlzdGFuY2lhcyBlbiBtZXRyb3MgZW50cmUgY2FkYSBjaXVkYWQuDQoNCmBgYHtyfQ0KY29vcmRlbmFkYXNfY2l1ZGFkZXMgPC0gZGF0YS5tYXRyaXgodGFibGFfY29vcmRlbmFkYXNfY2l1ZGFkZXNbLGMoMiwzKV0pDQpkaXN0YW5jaWFzIDwtIGNvbXB1dGVfZGlzdGFuY2VfbWF0cml4KGNvb3JkZW5hZGFzX2NpdWRhZGVzKQ0KcHJpbnQoZGlzdGFuY2lhcykNCmBgYA0KDQpBaG9yYSwgaGF5IHF1ZSBjb25zaWRlcmFyIGxvcyBjb3N0b3MgcGFyYSBwb2RlciBjcmVhciB1bmEgbWF0cml6IGRlIGNvc3RvcyBwYXJhIHJlc29sdmVyIGVsIHByb2JsZW1hIGRlIG9wdGltaXphY2nDs24uDQoNCkNvbiByZXNwZWN0byBhbCBjb3N0byBkZWwgY29tYnVzdGlibGUsIGVzdGUgZGVwZW5kZXLDoSBkZWwgdmVow61jdWxvIHF1ZSBlbCB2ZW5kZWRvciB1dGlsaXphcsOhIHBhcmEgcmVhbGl6YXIgbGFzIGVudHJlZ2FzLiBQYXJhIGVzdGUgcHJvYmxlbWEsIHNlIHV0aWxpemFyw6EgdW4gZnVyZ8OzbiBERlNLIEMzNSwgZWwgY3VhbCB0aWVuZSB1biBjb25zdW1vIGRlIGNvbWJ1c3RpYmxlIGRlIDcuNiBsaXRyb3MgYXByb3hpbWFkbyBwb3IgY2FkYSAxMDAgS20gZGUgcmVjb3JyaWRvLCBxdWUgc2UgcHVlZGUgdHJhZHVjaXIgZW4gMC4wMDAwNzYgbGl0cm9zIHBvciBjYWRhIDEgbWV0cm8gKERGU0ssIHMuZi4sIEVzcGVjaWZpY2FjaW9uZXMgRnVyZ29uIEMzNSkuIEFkaWNpb25hbG1lbnRlLCBzZSBjb25zaWRlcmEgZWwgcHJlY2lvIHByb21lZGlvIGRlIGxhIGdhc29saW5hIGVuIENvbG9tYmlhIHF1ZSwgc2VndW4gKExhIFJlcMO6YmxpY2EsIDIwMjUpLCB0aWVuZSB1biB2YWxvciBkZSBcJDE1LjgyNyBwZXNvcyBjb2xvbWJpYW5vcyBwb3IgZ2Fsw7NuLCBxdWUgc2UgcHVlZGUgdHJhZHVjaXIgZW4gXCQ0LjAyMiBwZXNvcyBjb2xvbWJpYW5vcyBwb3IgbGl0cm8gYXByb3hpbWFkYW1lbnRlLg0KDQpgYGB7cn0NCmdhc3RvX2xpdHJvX3Bvcl9tZXRybyA8LSAwLjAwMDA3Ng0KcHJlY2lvX2dhc29saW5hX3Bvcl9saXRybyA8LSA0LjAyMiANCmNvc3Rvc19jb21idXN0aWJsZSA8LSBkaXN0YW5jaWFzICogZ2FzdG9fbGl0cm9fcG9yX21ldHJvICogcHJlY2lvX2dhc29saW5hX3Bvcl9saXRybw0Kcm93bmFtZXMoY29zdG9zX2NvbWJ1c3RpYmxlKSA8LSBub21icmVfY2l1ZGFkZXMNCmNvbG5hbWVzKGNvc3Rvc19jb21idXN0aWJsZSkgPC0gbm9tYnJlX2NpdWRhZGVzDQpwcmludChjb3N0b3NfY29tYnVzdGlibGUpDQpgYGANCg0KQ29uc2lkZXJhbmRvIGVsIGNvc3RvIGRlIGxvcyBwZWFqZXMsIHNlIHVzYXLDoW4gbG9zIGRhdG9zIGRlIChBdXRvZmFjdCwgMjAyNCkgcGFyYSBjYWxjdWxhciBlbCB2YWxvciBwcm9tZWRpbyBkZSBjYWRhIHBlYWplIGRlIGxhcyBjaXVkYWRlcyBjb25zaWRlcmFkYXMgeSBzZSBhc3VtaXLDoSBxdWUgZWwgdmVuZGVkb3IgcGFnYXLDoSBlbCB2YWxvciBwcm9tZWRpbyBkZWwgcGVhamUgZGUgbGEgY2l1ZGFkIGRlIG9yaWdlbiB5IGRlIGxhIGNpdWRhZCBkZSBkZXN0aW5vIGV4YWN0YW1lbnRlIDEgdmV6IGNhZGEgcXVlIGxhcyByZWNvcnJhLg0KDQpgYGB7cn0NCnBlYWplcyA9IGMoDQogICMgQ3VuZGluYW1hcmNhIChCb2dvdMOhKQ0KICBtZWFuKGMoMTI4MDAsMTM4MDAsMTY1MDAsMTY1MDAsMTQxMDAsMTEyMDAsMTEyMDAsMTAzMDAsMTAzMDAsNjQwMCwxMjgwMCwxMjgwMCwxMTEwMCwxMTEwMCwxMzMwMCwxMzMwMCwxMjMwMCwxMjMwMCwxMjMwMCwxNjkwMCkpLA0KICAjIEFudGlvcXVpYSAoTWVkZWxsw61uKQ0KICBtZWFuKGMoMTY4MDAsMTIyMDAsMTIxMDAsMjI5MDAsMTU4MDAsMTA1MDAsMTYyMDAsMTA1MDAsMTA5MDAsMjA2MDAsMTgwMDAsMTY3MDAsMTY3MDAsMTQ2MDAsMTU5MDApKSwNCiAgIyBWYWxsZSBkZWwgQ2F1Y2EgKENhbGkpDQogIG1lYW4oYygxNDYwMCwxMTEwMCwxMTEwMCwxMTAwMCwxMTAwMCwxMTAwMCwxMTAwMCwxMTAwMCwxMDMwMCkpLA0KICAjIEF0bMOhbnRpY28gKEJhcnJhbnF1aWxsYSkNCiAgbWVhbihjKDMwMDAsMTcyMDAsODMwMCw4MzAwLDExOTAwLDExOTAwKSksDQogICMgQm9sw612YXIgKENhcnRhZ2VuYSkNCiAgbWVhbihjKDE4NDAwLDE2MzAwLDExMTAwLDExMjAwLDExMjAwLDUxMDAsMTE0MDApKSwNCiAgIyBOb3J0ZSBkZSBTYW50YW5kZXIgKEPDumN1dGEpDQogIG1lYW4oYyg3OTAwLDE5NzAwKSksDQogICMgTmFyacOxbyAoUGFzdG8pDQogIG1lYW4oYygxNDIwMCkpLA0KICAjIFRvbGltYSAoSWJhZ3XDqSkNCiAgbWVhbihjKDEzODAwLDEyNzAwLDE0MjAwLDEzNzAwLDEzNzAwLDEzNzAwLDE1NzAwKSksDQogICMgU2FudGFuZGVyIChCdWNhcmFtYW5nYSkNCiAgbWVhbihjKDE2NzAwLDEwOTAwLDEzNTAwLDEzNTAwLDEzNTAwKSksDQogICMgTWV0YSAoVmlsbGF2aWNlbmNpbykNCiAgbWVhbihjKDIzMDAwLDEzMTAwLDEzMTAwLDY5MDAsNjkwMCwxNjkwMCw4NjAwLDQ4MDApKSwNCiAgIyBNYWdkYWxlbmEgKFNhbnRhIE1hcnRhKQ0KICBtZWFuKGMoMTIzMDAsMTA5MDAsMTE3MDAsMTE3MDApKSwNCiAgIyBDYWxkYXMgKE1hbml6YWxlcykNCiAgbWVhbihjKDE2MTAwLDE0MDAwLDE0NjAwLDE0NjAwLDE0NjAwLDE0NjAwLDIxMDAwLDEwNTAwKSksDQogICMgUmlzYXJhbGRhIChQZXJlaXJhKQ0KICBtZWFuKGMoMTYxMDApKQ0KKQ0KDQpjb3N0b3NfcGVhamVzIDwtIG91dGVyKHBlYWplcywgcGVhamVzLCAiKyIpDQpyb3duYW1lcyhjb3N0b3NfcGVhamVzKSA8LSBub21icmVfY2l1ZGFkZXMNCmNvbG5hbWVzKGNvc3Rvc19wZWFqZXMpIDwtIG5vbWJyZV9jaXVkYWRlcw0KZGlhZyhjb3N0b3NfcGVhamVzKSA8LSByZXAoMCwgMTMpDQpwcmludChjb3N0b3NfcGVhamVzKQ0KYGBgDQoNClBvciDDumx0aW1vLCBzZSBkZWJlIGRlIGNvbnNpZGVyYXIgZWwgc2FsYXJpbyBwb3IgaG9yYXMgcHJvbWVkaW8gZGUgdW4gdmVuZGVkb3IgcXVlIGhhZ2EgZGljaG9zIHJlY29ycmlkb3MgcGFyYSBlbnRyZWdhciBsb3MgcGVkaWRvcy4gQWRpY2lvbmFsIGEgc2VyIHZlbmRlZG9yLCBlc3RhIHBlcnNvbmEgcmVhbGl6YXLDoSBsYXMgZW50cmVnYXMgeSBsYSBjb25kdWNjacOzbiBkZSBsYSBtZXJjYW5jw61hLCBwb3IgbG8gcXVlIHRhbWJpw6luIGhheSBxdWUgY29uc2lkZXJhciBzdSBwYWdvIGNvbW8gdHJhbnNwb3J0aXN0YS4gRGljaG8gZXN0bywgc2UgcHJvbWVkaWFyb24gc2FsYXJpb3MgZGUgdW4gdHJhbnNwb3J0aXN0YSB5IHVuIHZlbmRlZG9yIHBhcmEgcG9kZXIgZGV0ZXJtaW5hciBlbCBzYWxhcmlvIGZpbmFsIGRlIGxhIHBlcnNvbmEuIFNlZ8O6biAoVGFsZW50LmNvbSwgcy5mLiwgKlNhbGFyaW8gbWVkaW8gcGFyYSBUcmFuc3BvcnRlIERlIENhcmdhIGVuIENvbG9tYmlhIDIwMjUqKSB5IChUYWxlbnQuY29tLCBzLmYuLCAqU2FsYXJpbyBtZWRpbyBwYXJhIFZlbmRlZG9yIGVuIENvbG9tYmlhIDIwMjUqKSwgdW4gdHJhbnNwb3J0aXN0YSBnYW5hIGVuIHByb21lZGlvIFwkOS4zNDEgcGVzb3MgY29sb21iaWFub3MgbGEgaG9yYSB5IHVuIHZlbmRlZG9yIGhhY2UgXCQ3LjE0NCBsYSBob3JhOyBwb3IgbG8gdGFudG8sIGVsIHNhbGFyaW8gYSB1dGlsaXphciBzZXLDoSBlbCBcJDguMjQyIHBlc29zIGxhIGhvcmEuDQoNCkFzdW1pZW5kbyBxdWUgZWwgdmVuZGVkb3IgY29uZHVjaXLDoSBhIHVuYSB2ZWxvY2lkYWQgbWVkaWEgZGUgNTAga20vaCBhbCBkw61hLCBxdWUgc2UgcG9kcsOtYW4gdHJhZHVjaXIgZW4gNTAwMDAgbS9oIHBhcmEgZmFjaWxpdGFyIGPDoWxjdWxvcywgc2UgcHJvY2VkZSBhIGNhbGN1bGFyIGVsIHRpZW1wbyBkZSB2aWFqZSB5LCBqdW50byBjb24gZXN0bywgY3XDoW50byBzZSBsZSBwYWdhcsOhIGFsIHZlbmRlZG9yIHBvciBjYWRhIHZpYWplLg0KDQpgYGB7cn0NCnZlbG9jaWRhZF9tZWRpYV9tZXRyb3NfcG9yX2hvcmEgPC0gNTAwMDANCnNhbGFyaW9fdmVuZGVkb3JfY29sb21iaWEgPC0gNzE0NA0Kc2FsYXJpb190cmFuc3BvcnRpc3RhX2NvbG9tYmlhIDwtIDkzNDENCnNhbGFyaW9fZmluYWwgPC0gKHNhbGFyaW9fdmVuZGVkb3JfY29sb21iaWEgKyBzYWxhcmlvX3RyYW5zcG9ydGlzdGFfY29sb21iaWEpLzINCmNvc3Rvc192ZW5kZWRvciA8LSBkaXN0YW5jaWFzKigxL3ZlbG9jaWRhZF9tZWRpYV9tZXRyb3NfcG9yX2hvcmEpKnNhbGFyaW9fZmluYWwNCnJvd25hbWVzKGNvc3Rvc192ZW5kZWRvcikgPC0gbm9tYnJlX2NpdWRhZGVzDQpjb2xuYW1lcyhjb3N0b3NfdmVuZGVkb3IpIDwtIG5vbWJyZV9jaXVkYWRlcw0KY29zdG9zX3ZlbmRlZG9yDQoNCmBgYA0KDQpDb25jbHV5ZW5kbywgbGEgbWF0cml6IGRlIGNvc3RvcyBzaW1wbGVtZW50ZSBzZXLDrWEgZWwgcmVzdWx0YWRvIGRlIGxhIHN1bWEgZGUgbGFzIGFudGVyaW9yZXMgbWF0cmljZXMgcHJldmlhbWVudGUgZGVmaW5pZGFzLCB5IGVzdGEgc2Vyw6EgbGEgbWF0cml6IHF1ZSBzZSB1dGlsaXphcsOhbiBlbiBsb3MgZGlzdGludG9zIGFsZ29yaXRtb3MgcGFyYSBlbmNvbnRyYXIgbGEgbWVqb3IgcnV0YS4NCg0KYGBge3J9DQptYXRyaXpfY29zdG9zIDwtIGNvc3Rvc192ZW5kZWRvciArIGNvc3Rvc19wZWFqZXMgKyBjb3N0b3NfY29tYnVzdGlibGUNCnByaW50KG1hdHJpel9jb3N0b3MpDQpgYGANCg0KIyMgMi4yIEltcGxlbWVudGFjacOzbiBNw6l0b2RvIGRlIENvbG9uaWEgZGUgaG9ybWlnYXMNCg0KQSBjb250aW51YWNpw7NuLCBzZSBpbXBsZW1lbnRhIGVsIG3DqXRvZG8gZGUgY29sb25pYSBkZSBob3JtaWdhcyBwYXJhIGVuY29udHJhcg0KDQpgYGB7cn0NCiMgTsO6bWVybyBkZSBjaXVkYWRlcw0Kbl9jaXVkYWRlcyA8LSAxMw0KDQojIEZ1bmNpw7NuIHBhcmEgbm9ybWFsaXphciBsYSBtYXRyaXogZGUgY29zdG9zLCB5YSBxdWUgc2VhcmNoX3RvdXJfYW50cw0KIyBmdW5jaW9uYSBjb24gdmFsb3JlcyBub3JtYWxpemFkb3MuDQojIFNlIG9wdMOzIHBvciB1c2FyIG5vcm1hbGl6YWNpw7NuIHpzY29yZS4NCm5vcm1hbGl6ZV96c2NvcmUgPC0gZnVuY3Rpb24obSkgew0KICAobSAtIG1lYW4obSkpIC8gc2QobSkNCn0NCg0KIyBOb3JtYWxpemFjacOzbiBkZSBtYXRyaXogZGUgY29zdG9zDQptYXRyaXpfY29zdG9zX25vcm1hbGl6YWRhIDwtIG5vcm1hbGl6ZV96c2NvcmUobWF0cml6X2Nvc3RvcykNCg0KIyBFamVjdWNpw7NuIGRlbCBtw6l0b2RvIGRlIGNvbG9uaWEgZGUgaG9ybWlnYXMNCnJlY29ycmlkb19vcHRpbWl6YWRvIDwtIHNlYXJjaF90b3VyX2FudHMobWF0cml6X2Nvc3Rvc19ub3JtYWxpemFkYSwgbl9jaXVkYWRlcywgSyA9IDgwLCBOID0gNDAsIGxvZz1UUlVFKQ0KcHJpbnQoIk1FSk9SIFJFQ09SUklETzogIikNCnByaW50KHJlY29ycmlkb19vcHRpbWl6YWRvJHRvdXIpDQpgYGANCg0KYGBge3J9DQojIFRvdXIgbcOhcyDDs3B0aW1vOiAxIDEwICA3ICAzICA4IDEzIDEyICAyICA1ICA0IDExICA2ICA5DQoNCiMgUnV0YXMgc2VsZWNjaW9uYWRhcyBlbnRyZSBhbGd1bmFzIGNpdWRhZGVzDQpwcmludCgiUlVUQSBJREVBTDogIikNCnByaW50KCJCb2dvdMOhLT5WaWxsYXZpY2VuY2lvLT5QYXN0by0+Q2FsaS0+SWJhZ3XDqS0+UGVyZWlyYS0+TWFuaXphbGVzLT5NZWRlbGzDrW4tPkNhcnRhZ2VuYS0+QmFycmFucXVpbGxhLT5TYW50YSBNYXJ0YS0+Q8O6Y3V0YS0+QnVjYXJhbWFuZ2EiKQ0KcGFyZXNfcnV0YXMgPC0gbGlzdCgNCiAgYygiQm9nb3TDoSIsICJWaWxsYXZpY2VuY2lvIiksDQogIGMoIlZpbGxhdmljZW5jaW8iLCAiUGFzdG8iKSwNCiAgYygiUGFzdG8iLCAiQ2FsaSIpLA0KICBjKCJDYWxpIiwgIkliYWd1w6kiKSwNCiAgYygiSWJhZ3XDqSIsICJQZXJlaXJhIiksDQogIGMoIlBlcmVpcmEiLCAiTWFuaXphbGVzIiksDQogIGMoIk1hbml6YWxlcyIsICJNZWRlbGzDrW4iKSwNCiAgYygiTWVkZWxsw61uIiwgIkNhcnRhZ2VuYSIpLA0KICBjKCJDYXJ0YWdlbmEiLCAiQmFycmFucXVpbGxhIiksDQogIGMoIkJhcnJhbnF1aWxsYSIsICJTYW50YSBNYXJ0YSIpLA0KICBjKCJTYW50YSBNYXJ0YSIsICJDw7pjdXRhIiksDQogIGMoIkPDumN1dGEiLCAiQnVjYXJhbWFuZ2EiKQ0KKQ0KDQojIENvbnZlcnRpbW9zIGEgb2JqZXRvIGVzcGFjaWFsDQpwdW50b3MgPC0gc3RfYXNfc2YoY2l1ZGFkZXMsIGNvb3JkcyA9IGMoIkxvbmdpdHVkIiwgIkxhdGl0dWQiKSwgY3JzID0gNDMyNikNCg0KIyBPYnRlbmVyIHJ1dGFzIGNvbiBvc3JtDQpydXRhcyA8LSBsaXN0KCkNCmZvciAocGFyIGluIHBhcmVzX3J1dGFzKSB7DQogIG9yaWdlbiA8LSBwdW50b3NbY2l1ZGFkZXMkQ2l1ZGFkID09IHBhclsxXSwgXQ0KICBkZXN0aW5vIDwtIHB1bnRvc1tjaXVkYWRlcyRDaXVkYWQgPT0gcGFyWzJdLCBdDQogIHJ1dGEgPC0gdHJ5KG9zcm1Sb3V0ZShzcmMgPSBvcmlnZW4sIGRzdCA9IGRlc3Rpbm8sICksIHNpbGVudCA9IFRSVUUpDQogIGlmICghaW5oZXJpdHMocnV0YSwgInRyeS1lcnJvciIpKSB7DQogICAgcnV0YXMgPC0gYXBwZW5kKHJ1dGFzLCBsaXN0KHJ1dGEpKQ0KICB9DQp9DQoNCiMgQ3JlYXIgZWwgbWFwYQ0KbWFwYSA8LSBsZWFmbGV0KCkgJT4lDQogIGFkZFRpbGVzKCkgJT4lDQogIGFkZENpcmNsZU1hcmtlcnMoZGF0YSA9IHB1bnRvcywgbGFiZWwgPSB+Q2l1ZGFkLCByYWRpdXMgPSA2LCBjb2xvciA9ICJibHVlIiwgZmlsbE9wYWNpdHkgPSAwLjgpDQoNCiMgQcOxYWRpciBsYXMgcnV0YXMgZW4gcm9qbw0KZm9yIChydXRhIGluIHJ1dGFzKSB7DQogIG1hcGEgPC0gbWFwYSAlPiUgYWRkUG9seWxpbmVzKGRhdGEgPSBydXRhLCBjb2xvciA9ICJyZWQiLCB3ZWlnaHQgPSAzKQ0KfQ0KDQpwcmludCgiQ09TVE8gRklOQUwgREVMIFJFQ09SUklETzogIikNCmNvc3RvcyA8LSBjKA0KICBtYXRyaXpfY29zdG9zWzEsMTBdLCAjIkJvZ290w6EiIC0+ICJWaWxsYXZpY2VuY2lvIg0KICBtYXRyaXpfY29zdG9zWzEwLDddLCAjIlZpbGxhdmljZW5jaW8iIC0+ICJQYXN0byINCiAgbWF0cml6X2Nvc3Rvc1s3LDNdLCAjIlBhc3RvIiAtPiAiQ2FsaSINCiAgbWF0cml6X2Nvc3Rvc1szLDhdLCAjIkNhbGkiIC0+ICJJYmFndcOpIg0KICBtYXRyaXpfY29zdG9zWzgsMTNdLCAjIkliYWd1w6kiIC0+ICJQZXJlaXJhIg0KICBtYXRyaXpfY29zdG9zWzEzLDEyXSwgIyJQZXJlaXJhIiAtPiAiTWFuaXphbGVzIg0KICBtYXRyaXpfY29zdG9zWzEyLDJdLCAjIk1hbml6YWxlcyIgLT4gIk1lZGVsbMOtbiINCiAgbWF0cml6X2Nvc3Rvc1syLDVdLCAjIk1lZGVsbMOtbiIgLT4gIkNhcnRhZ2VuYSINCiAgbWF0cml6X2Nvc3Rvc1s1LDRdLCAjIkNhcnRhZ2VuYSIgLT4gIkJhcnJhbnF1aWxsYSINCiAgbWF0cml6X2Nvc3Rvc1s0LDExXSwgIyJCYXJyYW5xdWlsbGEiIC0+ICJTYW50YSBNYXJ0YSINCiAgbWF0cml6X2Nvc3Rvc1sxMSw2XSwgIyJTYW50YSBNYXJ0YSIgLT4gIkPDumN1dGEiDQogIG1hdHJpel9jb3N0b3NbNiw5XSAjIkPDumN1dGEiIC0+ICJCdWNhcmFtYW5nYSINCikNCnByaW50KHN1bShjb3N0b3MpKQ0KbWFwYQ0KYGBgDQoNCiMjIDIuMyBDb25jbHVzaW9uZXMNCg0KIyBDb25jbHVzaW9uZXMgZmluYWxlcw0KDQojIFJlcG9ydGUgZGUgY29udHJpYnVjacOzbiBpbmRpdmlkdWFsDQoNCiMjIExlb25hcmRvIEZlZGVyaWNvIENvcm9uYSBUb3JyZXMNCg0KLSAgIEFwb3lvIGVuIHJlZGFjY2nDs24geSByZWFsaXphY2nDs24gZGUgaW1wbGVtZW50YWNpb25lcyBkZSBzZWNjacOzbiAiTcOpdG9kbyBkZSBhbGdvcml0bW9zIGV2b2x1dGl2b3MiLg0KDQotICAgQXBveW8gZW4gcmVkYWNjacOzbiBkZSBpbXBsZW1lbnRhY2lvbmVzIGRlIGdyYWZpY2FjacOzbiBlbiBQYXJ0ZSAyIGRlbCByZXBvcnRlLg0KDQotICAgQsO6c3F1ZWRhIGUgaW52ZXN0aWdhY2nDs24gZGUgZnVuY2lvbmVzIGRlIHBydWViYSBhIHV0aWxpemFyLg0KDQotICAgQXBveW8gZW4gcmVkYWNjacOzbiB5IHJlYWxpemFjacOzbiBkZSBpbXBsZW1lbnRhY2lvbmVzIGRlIHNlY2Npw7NuICJTZWxlY2Npw7NuIGUgaW1wbGVtZW50YWNpw7NuIGRlIGxhcyBmdW5jaW9uZXMgZGUgcHJ1ZWJhIi4NCg0KIyMgRGF2aWQgRXNjb2JhciBSdWl6DQoNCi0gICBEZWZpbmljacOzbiB5IGVzY3JpdHVyYSBkZSBlc3RydWN0dXJhIGRlbCByZXBvcnRlLg0KDQotICAgQXBveW8gZW4gcmVkYWNjacOzbiB5IHJlYWxpemFjacOzbiBkZSBpbXBsZW1lbnRhY2lvbmVzIGRlIHNlY2Npw7NuICJTZWxlY2Npw7NuIGUgaW1wbGVtZW50YWNpw7NuIGRlIGxhcyBmdW5jaW9uZXMgZGUgcHJ1ZWJhIi4NCg0KLSAgIEFwb3lvIGVuIHJlZGFjY2nDs255IHJlYWxpemFjacOzbiBkZSBpbXBsZW1lbnRhY2lvbmVzIGRlIHNlY2Npw7NuICJNw6l0b2RvIGRlIG9wdGltaXphY2nDs24gZGUgcGFydMOtY3VsYXMiLg0KDQotICAgQXBveW8gZW4gcmVkYWNjacOzbiB5IHJlYWxpemFjacOzbiBkZSBpbXBsZW1lbnRhY2lvbmVzIGRlIG3DqXRvZG9zLCBwbGFudGVhbWllbnRvIHkgcHJlcHJvY2VzYW1pZW50byBkZSBkYXRvcyBQYXJ0ZSAyIGRlbCByZXBvcnRlLg0KDQojIyBKb2hhbiBTZWJhc3Rpw6FuIFJvYmxlcyBSaW5jw7NuDQoNCi0gICBJbXBsZW1lbnRhY2nDs24geSBwcnVlYmEgZGVsIG3DqXRvZG8gZGUgZXZvbHVjacOzbiBkaWZlcmVuY2lhbCBlbiBSIHBhcmEgZnVuY2lvbmVzIGRlIFJvc2VuYnJvY2sgeSBSYXN0cmlnaW7CoGVuwqAyRMKgecKgM0QuDQoNCi0gICBBbsOhbGlzaXMgZGUgcmVzdWx0YWRvcyBudW3DqXJpY29zIHkgZ3LDoWZpY29zLg0KDQotICAgQXBveW8gZW4gcmVkYWNjacOzbiBkZSBzZWNjacOzbiBkZWwgY29udGVuaWRvIHTDqWNuaWNvIHJlbGFjaW9uYWRvIGNvbiBlc3RlIG3DqXRvZG8gZGUgZXZvbHVjaW9uIGRpZmVyZW5jaWFswqBlbsKgZWzCoGluZm9ybWUuDQoNCiMjIFNlYmFzdGlhbiBTb3RvIEFyY2lsYQ0KDQotICAgQXBveW8gZW4gcmVkYWNjacOzbiB5IHJlYWxpemFjacOzbiBkZSBpbXBsZW1lbnRhY2lvbmVzIGRlIHNlY2Npw7NuICJNw6l0b2RvIGRlIG9wdGltaXphY2nDs24gcG9yIGRlc2NlbnNvIGRlIGdyYWRpZW50ZSINCg0KLSAgIEFwb3lvIGVuIHJlZGFjY2nDs24gZGUgc2VjY2nDs24gIlNlbGVjY2nDs24gZSBpbXBsZW1lbnRhY2nDs24gZGUgbGFzIGZ1bmNpb25lcyBkZSBwcnVlYmEiLg0KDQojIFJlcG9zaXRvcmlvIGRlIEdpdEh1YiBkZWwgcHJveWVjdG8NCg0KW2h0dHBzOi8vZ2l0aHViLmNvbS9kcnVpejM1L1JOQUJJMjAyNS0xLUVxdWlwbzMvXShodHRwczovL2dpdGh1Yi5jb20vZHJ1aXozNS9STkFCSTIwMjUtMS1FcXVpcG8zL3RyZWUvbWFpbil7LnVyaX0NCg0KIyBCaWJsaW9ncmFmw61hDQoNCkF1dG9mYWN0LiAoMjAyNCkuICpQZWFqZXMgZW4gQ29sb21iaWE6IENvbm9jZSBzdXMgdWJpY2FjaW9uZXMgeSBwcmVjaW9zIDIwMjQuKiA8aHR0cHM6Ly93d3cuYXV0b2ZhY3QuY29tLmNvL2Jsb2cvbWktY2Fycm8vcGVhamVzL3BlYWplcy1jb2xvbWJpYS1wcmVjaW9zPg0KDQpERlNLLiAocy5mLikuICpFc3BlY2lmaWNhY2lvbmVzIEZ1cmdvbiBDMzUuKiBSZWN1cGVyYWRvIGVsIDIgZGUgbWF5byBkZSAyMDI1LCBkZSA8aHR0cDovL3N0YXRpYy5tdWx0aWF2aXNvLmNvbS92ZWhpY2xlL3NwZWNzLzIyLVZSWkM1NjRYTEJFNi1kZnNrLW90cm9zLW1vZGVsb3MtMjAxNy1mdXJnb24tYzM1LnBkZj4NCg0KTGEgUmVww7pibGljYS4gKDIwMjUpLiAqUFJFQ0lPIERFIExBIEdBU09MSU5BLiogUmVjdXBlcmFkbyBlbCAyIGRlIG1heW8gZGUgMjAyNSwgZGUgPGh0dHBzOi8vd3d3LmxhcmVwdWJsaWNhLmNvL3ByZWNpby1kZS1sYS1nYXNvbGluYT4NCg0KTW9sZ2EsIFNtdXRuaWNraS4gKDIwMDUpLiAqVGVzdCBmdW5jdGlvbnMgZm9yIG9wdGltaXphdGlvbiBuZWVkcy4qIDxodHRwczovL3JvYmVydG1hcmtzLm9yZy9DbGFzc2VzL0VOR1I1MzU4L1BhcGVycy9mdW5jdGlvbnMucGRmPg0KDQpUYWxlbnQuY29tLiAocy5mLikuICpTYWxhcmlvIG1lZGlvIHBhcmEgVHJhbnNwb3J0ZSBEZSBDYXJnYSBlbiBDb2xvbWJpYSAyMDI1Ki4gUmVjdXBlcmFkbyBlbCAyIGRlIG1heW8gZGUgMjAyNSwgZGUgPGh0dHBzOi8vY28udGFsZW50LmNvbS9zYWxhcnk/am9iPXRyYW5zcG9ydGUrZGUrY2FyZ2E+DQoNClRhbGVudC5jb20uIChzLmYuKS4gKlNhbGFyaW8gbWVkaW8gcGFyYSBWZW5kZWRvciBlbiBDb2xvbWJpYSAyMDI1Ki4gUmVjdXBlcmFkbyBlbCAyIGRlIG1heW8gZGUgMjAyNSwgZGUgPGh0dHBzOi8vY28udGFsZW50LmNvbS9zYWxhcnk/am9iPXZlbmRlZG9yPg0KDQpXaWtpcGVkaWEuIChzLmYuKS4gKlRlc3QgZnVuY3Rpb25zIGZvciBvcHRpbWl6YXRpb24qLiBXaWtpcGVkaWEuIFJlY3VwZXJhZG8gZWwgMiBkZSBtYXlvIGRlIDIwMjUsIGRlIDxodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9UZXN0X2Z1bmN0aW9uc19mb3Jfb3B0aW1pemF0aW9uPg0KDQpXaWtpcGVkaWEuIChzLmYuKS4gKlJvc2VuYnJvY2sgZnVuY3Rpb24qLiBXaWtpcGVkaWEuIFJlY3VwZXJhZG8gZWwgMiBkZSBtYXlvIGRlIDIwMjUsIGRlIFtodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9Sb3NlbmJyb2NrXF9dKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL1Jvc2VuYnJvY2tfZnVuY3Rpb24pey51cml9DQoNCldpa2lwZWRpYS4gKHMuZi4pLiAqUmFzdHJpZ2luIGZ1bmN0aW9uKi4gV2lraXBlZGlhLiBSZWN1cGVyYWRvIGVsIDIgZGUgbWF5byBkZSAyMDI1LCBkZSA8aHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUmFzdHJpZ2luX2Z1bmN0aW9uPg0KDQpYLi1TLiBZYW5nLCBUZXN0IHByb2JsZW1zIGluIG9wdGltaXphdGlvbiwgaW46IEVuZ2luZWVyaW5nIE9wdGltaXphdGlvbjogQW4gSW50cm9kdWN0aW9uIHdpdGggTWV0YWhldXJpc3RpYyBBcHBsaWNhdGlvbnMgKEVkcyBYaW4tU2hlIFlhbmcpLCBKb2huIFdpbGV5ICYgU29ucywgKDIwMTApDQo=