Modelo Básico- Clase 1

library(tidyverse)
library(modelr)
options(na.action = na.warn)

datos sim1

El paquete modlr viene con un set de datos de juguete llamado sim1

ggplot(sim1, aes(x, y)) + 
  geom_point()

Se puede ver un patrón fuerte en los datos. Pareciera que el modelo lineal y = a_0 + a_1 * x podría servir.

Modelos al azar

Para empezar, generemos aleatoriamente varios modelos lineales para ver qué pinta tienen. Para eso, podemos usar geom_abline () que toma una pendiente e intercepto como parámetros.

models <- tibble(
  a1 = runif(250, -20, 40),
  a2 = runif(250, -5, 5)
)
ggplot(sim1, aes(x, y)) + 
  geom_abline(aes(intercept = a1, slope = a2), data = models, alpha = 1/4) +
  geom_point() 

A simple vista podemos apreciar que algunos modelos son mejores que otros. Pero necesitamos una forma de cuantificar cuales son los mejores modelos.

distancias

Una forma de definir mejor es pensar en aquel modelo que minimiza la distancia vertical con cada punto:

Para eso, eligamos un modelo cualquiera:

\[ y= 7 + 1.5*x\]

(para que se vean mejor las distancias, corremos un poquito cada punto sobre el eje x)

dist1 <- sim1 %>% 
  mutate(
    dodge = rep(c(-1, 0, 1) / 20, 10),
    x1 = x + dodge,
    pred = 7 + x1 * 1.5
  )
ggplot(dist1, aes(x1, y)) + 
  geom_abline(intercept = 7, slope = 1.5, colour = "grey40") +
  geom_point(colour = "grey40") +
  geom_linerange(aes(ymin = y, ymax = pred), colour = "#3366FF") 

La distancia de cada punto a la recta es la diferencia entre lo que predice nuestro modelo y el valor real

Para computar la distancia, primero necesitamos una función que represente a nuestro modelo:

Ejercicio 1

Crear una función que reciba un vector con los parametros del modelo, y el set de datos, y genere la predicción:

# model1 <- function(a, data) {
# 
#   
#   }
model1(c(7, 1.5), sim1)
 [1]  8.5  8.5  8.5 10.0 10.0 10.0 11.5 11.5 11.5 13.0 13.0 13.0 14.5 14.5 14.5 16.0 16.0 16.0 17.5 17.5 17.5 19.0 19.0 19.0 20.5 20.5 20.5
[28] 22.0 22.0 22.0

Ahora, necesitamos una forma de calcular los residuos y agruparlos. Como se vió en la clase teórica, esto lo vamos a hacer con el error cuadrático medio

Ejercicio 2

Necesitamos una función, con el mismo input que la anterior, que calcule el promedio de los errores cuadráticos (ECM)

\[ECM = \sqrt\frac{\sum_i^n{(\hat{y_i} - y_i)^2}}{n}\]

# measure_distance <- function(mod, data) {
# 
#   }
measure_distance(c(7, 1.5), sim1)
[1] 2.665212

Evaluando los modelos aleatorios

Ahora podemos calcular el ECM para todos los modelos del dataframe models. Para eso utilizamos el paquete purrr, para ejecutar varias veces la misma función sobre varios elementos.

MAP2

La función map toma un input, una función para aplicar, y alguna otra cosa (por ejemplo parametros que necesite la función)

  • map(.x, .f, …)
  • map(VECTOR_OR_LIST_INPUT, FUNCTION_TO_APPLY, OPTIONAL_OTHER_STUFF)

Usamos map2 cuando tenemos que pasar dos input, que se aplican sobre una función:

  • map2(.x, .y, .f, …)
  • map2(INPUT_ONE, INPUT_TWO, FUNCTION_TO_APPLY, OPTIONAL_OTHER_STUFF)

Si tenemos más de dos…

  • pmap(.l, .f, …)
  • pmap(LIST_OF_INPUT_LISTS, FUNCTION_TO_APPLY, OPTIONAL_OTHER_STUFF)

Nosotros tenemos que pasar pasar los valores de a1 y a2 (dos parámetros –> map2), pero como nuestra función toma sólo uno (el vector a), nos armamos una función de ayuda para wrapear a1 y a2

sim1_dist <- function(a1, a2) {
  measure_distance(c(a1, a2), sim1)
}
models <- models %>% 
  mutate(dist = purrr::map2_dbl(a1, a2, sim1_dist))
models

A continuación, superpongamos los 10 mejores modelos a los datos. Coloreamos los modelos por -dist: esta es una manera fácil de asegurarse de que los mejores modelos (es decir, los que tienen la menor distancia) obtengan los colores más brillantes.

ggplot(sim1, aes(x, y)) + 
  geom_point(size = 2, colour = "grey30") + 
  geom_abline(
    aes(intercept = a1, slope = a2, colour = -dist), 
    data = filter(models, rank(dist) <= 10)
  )

También podemos pensar en estos modelos como observaciones y visualizar con un gráfico de dispersión de a1 vsa2, nuevamente coloreado por -dist. Ya no podemos ver directamente cómo se compara el modelo con los datos, pero podemos ver muchos modelos a la vez. Nuevamente, destaqué los 10 mejores modelos, esta vez dibujando círculos rojos debajo de ellos.

ggplot(models, aes(a1, a2)) +
  geom_point(data = filter(models, rank(dist) <= 10), size = 4, colour = "red") +
  geom_point(aes(colour = -dist))

Modelo Básico- Clase 2

óptimo por métodos numéricos

Podríamos imaginar iterativamente haciendo la cuadrícula más fina y fina hasta que nos centramos en el mejor modelo. Pero hay una forma mejor de abordar ese problema: una herramienta de minimización numérica llamada búsqueda Newton-Raphson.

La intuición de Newton-Raphson es bastante simple: Se elige un punto de partida y se busca la pendiente más inclinada. Luego, desciende por esa pendiente un poco, y se repite una y otra vez, hasta que no se puede seguir bajando.

En R, podemos hacer eso con optim ():

  • necesitamos pasarle un vector de puntos iniciales. Elegimos el origen por poner cualquier cosa
  • le pasamos nuestra función de distancia, y los parámetros que nuestra función necesita (data)
optim(c(4,2), measure_distance, data = sim1)
$par
[1] 4.221029 2.051528

$value
[1] 2.128181

$counts
function gradient 
      49       NA 

$convergence
[1] 0

$message
NULL
best <- optim(c(0, 0), measure_distance, data = sim1)
best$par
[1] 4.222248 2.051204
ggplot(sim1, aes(x, y)) + 
  geom_point(size = 2, colour = "grey30") + 
  geom_abline(intercept = best$par[1], slope = best$par[2])

Óptimo para el modelo lineal

Este procedimiento es válido para muchas familias de modelos. Pero para el caso del modelo lineal, conocemos otras formas de resolverlo

Si nuestro modelo es

\[ y = a_1 + a_2x + \epsilon \]

La solución del óptima es

\[ \hat{a_1} = \bar{y} - \hat{a_2}\bar{x} \]

\[ \hat{a_2} = \frac{\sum_i^n (y_i -\bar{y})(x_i -\bar{x})}{\sum_i^n (x_i- \bar{x})} \]

R tiene una función específica para el modelo lineal lm(). Cómo esta función sirve tanto para regresiones lineales simples como múltiples, debemos especificar el modelo en las formulas: y ~ x

sim1_mod <- lm(y ~ x, data = sim1)
coef(sim1_mod)
(Intercept)           x 
   4.220822    2.051533 

Predicciones

Para visualizar las predicciones de un modelo, comenzamos por generar una cuadrícula de valores espaciados uniformemente que cubre la región donde se encuentran nuestros datos. La forma más fácil de hacerlo es usar modelr :: data_grid (). Su primer argumento es un dataframe, y para cada argumento posterior encuentra las variables únicas y luego genera todas las combinaciones:

grid <- sim1 %>% 
  data_grid(x) 
grid

(Cuando tengamos modelos de varias variables, esta función va a ser de mucha utilidad)

A continuación, agregamos predicciones. Usaremos modelr :: add_predictions () que toma un dataframe y un modelo. Agrega las predicciones del modelo a una nueva columna en el dataframe:

grid <- grid %>% 
  add_predictions(sim1_mod) 
grid

(También podríamos agregar predicciones al dataset original)

A continuación, trazamos las predicciones. La ventaja de este enfoque respecto de geom_abline () es que funcionará con cualquier modelo en R, desde el más simple hasta el más complejo.

ggplot(sim1, aes(x)) +
  geom_point(aes(y = y)) +
  geom_line(aes(y = pred), data = grid, colour = "red", size = 1)

Residuos del modelo

Agregamos residuos al DF con add_residuals (), que funciona de forma muy similar a add_predictions (). Sin embargo, tengan en cuenta que usamos el conjunto de datos original, no una cuadrícula fabricada. Esto se debe a que para calcular los residuos necesitamos valores y reales.

sim1 <- sim1 %>% 
  add_residuals(sim1_mod)
sim1

Hay varias formas diferentes de entender lo que nos dicen los residuos sobre el modelo. Una forma es simplemente dibujar un polígono de frecuencia que nos ayude a entender la dispersión de los residuos:

ggplot(sim1, aes(resid)) + 
  geom_freqpoly(binwidth = 0.5)

Noten que en promedio los residuos deben ser un valor muy cercano a 0

mean(sim1$resid)
[1] 5.121872e-15

Otra forma de graficar los residuos s

ggplot(sim1, aes(x, resid)) + 
  geom_ref_line(h = 0, size = 2,colour = "firebrick") +
  geom_point() 

Si los residuos no tienen estructura es que hicimos las cosas bien.


  1. Estas notas estan basadas en http://r4ds.had.co.nz. Gracias Hadley!

  2. basado en https://jennybc.github.io/purrr-tutorial/ls03_map-function-syntax.html, gracias Jenny!

LS0tCnRpdGxlOiAiTW9kZWxvIExpbmVhbCBzaW1wbGVeW0VzdGFzIG5vdGFzIGVzdGFuIGJhc2FkYXMgZW4gaHR0cDovL3I0ZHMuaGFkLmNvLm56LiBHcmFjaWFzIEhhZGxleSFdIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRoZW1lOiBzcGFjZWxhYgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCiAgICBkZl9wcmludDogcGFnZWQKLS0tCgojIE1vZGVsbyBCw6FzaWNvLSBDbGFzZSAxCgoKYGBge3Igc2V0dXAsIG1lc3NhZ2UgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCgpsaWJyYXJ5KG1vZGVscikKb3B0aW9ucyhuYS5hY3Rpb24gPSBuYS53YXJuKQpgYGAKCiMjIGRhdG9zIHNpbTEKCkVsIHBhcXVldGUgbW9kbHIgdmllbmUgY29uIHVuIHNldCBkZSBkYXRvcyBkZSBqdWd1ZXRlIGxsYW1hZG8gc2ltMQoKYGBge3J9CmdncGxvdChzaW0xLCBhZXMoeCwgeSkpICsgCiAgZ2VvbV9wb2ludCgpCmBgYAoKU2UgcHVlZGUgdmVyIHVuIHBhdHLDs24gZnVlcnRlIGVuIGxvcyBkYXRvcy4gUGFyZWNpZXJhIHF1ZSBlbCBtb2RlbG8gbGluZWFsIGB5ID0gYV8wICsgYV8xICogeGAgcG9kcsOtYSBzZXJ2aXIuIAoKIyMgTW9kZWxvcyBhbCBhemFyCgpQYXJhIGVtcGV6YXIsIGdlbmVyZW1vcyBhbGVhdG9yaWFtZW50ZSB2YXJpb3MgbW9kZWxvcyBsaW5lYWxlcyBwYXJhIHZlciBxdcOpIHBpbnRhIHRpZW5lbi4gUGFyYSBlc28sIHBvZGVtb3MgdXNhciBgZ2VvbV9hYmxpbmUgKClgIHF1ZSB0b21hIHVuYSBwZW5kaWVudGUgZSBpbnRlcmNlcHRvIGNvbW8gcGFyw6FtZXRyb3MuIAoKYGBge3J9Cm1vZGVscyA8LSB0aWJibGUoCiAgYTEgPSBydW5pZigyNTAsIC0yMCwgNDApLAogIGEyID0gcnVuaWYoMjUwLCAtNSwgNSkKKQoKZ2dwbG90KHNpbTEsIGFlcyh4LCB5KSkgKyAKICBnZW9tX2FibGluZShhZXMoaW50ZXJjZXB0ID0gYTEsIHNsb3BlID0gYTIpLCBkYXRhID0gbW9kZWxzLCBhbHBoYSA9IDEvNCkgKwogIGdlb21fcG9pbnQoKSAKYGBgCgoKQSBzaW1wbGUgdmlzdGEgcG9kZW1vcyBhcHJlY2lhciBxdWUgYWxndW5vcyBtb2RlbG9zIHNvbiBtZWpvcmVzIHF1ZSBvdHJvcy4gUGVybyBuZWNlc2l0YW1vcyB1bmEgZm9ybWEgZGUgY3VhbnRpZmljYXIgY3VhbGVzIHNvbiBsb3MgX21lam9yZXNfIG1vZGVsb3MuIAoKIyMgZGlzdGFuY2lhcwoKVW5hIGZvcm1hIGRlIGRlZmluaXIgX21lam9yXyBlcyBwZW5zYXIgZW4gYXF1ZWwgbW9kZWxvIHF1ZSBtaW5pbWl6YSBsYSBkaXN0YW5jaWEgdmVydGljYWwgY29uIGNhZGEgcHVudG86CgpQYXJhIGVzbywgZWxpZ2Ftb3MgdW4gbW9kZWxvIGN1YWxxdWllcmE6CgokJCB5PSA3ICsgMS41KngkJAoKKHBhcmEgcXVlIHNlIHZlYW4gbWVqb3IgbGFzIGRpc3RhbmNpYXMsIGNvcnJlbW9zIHVuIHBvcXVpdG8gY2FkYSBwdW50byBzb2JyZSBlbCBlamUgeCkKCmBgYHtyfQpkaXN0MSA8LSBzaW0xICU+JSAKICBtdXRhdGUoCiAgICBkb2RnZSA9IHJlcChjKC0xLCAwLCAxKSAvIDIwLCAxMCksCiAgICB4MSA9IHggKyBkb2RnZSwKICAgIHByZWQgPSA3ICsgeDEgKiAxLjUKICApCgpnZ3Bsb3QoZGlzdDEsIGFlcyh4MSwgeSkpICsgCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gNywgc2xvcGUgPSAxLjUsIGNvbG91ciA9ICJncmV5NDAiKSArCiAgZ2VvbV9wb2ludChjb2xvdXIgPSAiZ3JleTQwIikgKwogIGdlb21fbGluZXJhbmdlKGFlcyh5bWluID0geSwgeW1heCA9IHByZWQpLCBjb2xvdXIgPSAiIzMzNjZGRiIpIApgYGAKCgpMYSBkaXN0YW5jaWEgZGUgY2FkYSBwdW50byBhIGxhIHJlY3RhIGVzIGxhIGRpZmVyZW5jaWEgZW50cmUgbG8gcXVlIHByZWRpY2UgbnVlc3RybyBtb2RlbG8geSBlbCB2YWxvciByZWFsCgoKUGFyYSBjb21wdXRhciBsYSBkaXN0YW5jaWEsIHByaW1lcm8gbmVjZXNpdGFtb3MgdW5hIGZ1bmNpw7NuIHF1ZSByZXByZXNlbnRlIGEgbnVlc3RybyBtb2RlbG86IAoKIyMgRWplcmNpY2lvIDEKCiAgQ3JlYXIgdW5hIGZ1bmNpw7NuIHF1ZSByZWNpYmEgdW4gdmVjdG9yIGNvbiBsb3MgcGFyYW1ldHJvcyBkZWwgbW9kZWxvLCB5IGVsIHNldCBkZSBkYXRvcywgeSBnZW5lcmUgbGEgcHJlZGljY2nDs246CiAgCmBgYHtyfQojIG1vZGVsMSA8LSBmdW5jdGlvbihhLCBkYXRhKSB7CiMgCiMgICAKIyAgIH0KbW9kZWwxKGMoNywgMS41KSwgc2ltMSkKYGBgCgoKCkFob3JhLCBuZWNlc2l0YW1vcyB1bmEgZm9ybWEgZGUgY2FsY3VsYXIgbG9zIHJlc2lkdW9zIHkgYWdydXBhcmxvcy4gQ29tbyBzZSB2acOzIGVuIGxhIGNsYXNlIHRlw7NyaWNhLCBlc3RvIGxvIHZhbW9zIGEgaGFjZXIgY29uIGVsIGVycm9yIGN1YWRyw6F0aWNvIG1lZGlvCgojIyBFamVyY2ljaW8gMgoKTmVjZXNpdGFtb3MgdW5hIGZ1bmNpw7NuLCBjb24gZWwgbWlzbW8gaW5wdXQgcXVlIGxhIGFudGVyaW9yLCBxdWUgY2FsY3VsZSBlbCBwcm9tZWRpbyBkZSBsb3MgZXJyb3JlcyBjdWFkcsOhdGljb3MgKEVDTSkKIAokJEVDTSA9IFxzcXJ0XGZyYWN7XHN1bV9pXm57KFxoYXR7eV9pfSAtIHlfaSleMn19e259JCQKCmBgYHtyfQoKIyBtZWFzdXJlX2Rpc3RhbmNlIDwtIGZ1bmN0aW9uKG1vZCwgZGF0YSkgewojIAojICAgfQptZWFzdXJlX2Rpc3RhbmNlKGMoNywgMS41KSwgc2ltMSkKYGBgCgoKCiMjIEV2YWx1YW5kbyBsb3MgbW9kZWxvcyBhbGVhdG9yaW9zCgpBaG9yYSBwb2RlbW9zIGNhbGN1bGFyIGVsIF9fRUNNX18gcGFyYSB0b2RvcyBsb3MgbW9kZWxvcyBkZWwgZGF0YWZyYW1lIF9tb2RlbHNfLiBQYXJhIGVzbyB1dGlsaXphbW9zIGVsIHBhcXVldGUgX19wdXJycl9fLCBwYXJhIGVqZWN1dGFyIHZhcmlhcyB2ZWNlcyBsYSBtaXNtYSBmdW5jacOzbiBzb2JyZSB2YXJpb3MgZWxlbWVudG9zLiAKCiMjIyBNQVBeW2Jhc2FkbyBlbiBodHRwczovL2plbm55YmMuZ2l0aHViLmlvL3B1cnJyLXR1dG9yaWFsL2xzMDNfbWFwLWZ1bmN0aW9uLXN5bnRheC5odG1sLCBncmFjaWFzIEplbm55IV0KCkxhIGZ1bmNpw7NuIF9fbWFwX18gdG9tYSB1biBpbnB1dCwgdW5hIGZ1bmNpw7NuIHBhcmEgYXBsaWNhciwgeSBhbGd1bmEgb3RyYSBjb3NhIChwb3IgZWplbXBsbyBwYXJhbWV0cm9zIHF1ZSBuZWNlc2l0ZSBsYSBmdW5jacOzbikKCi0gbWFwKC54LCAuZiwgLi4uKQotIG1hcChWRUNUT1JfT1JfTElTVF9JTlBVVCwgRlVOQ1RJT05fVE9fQVBQTFksIE9QVElPTkFMX09USEVSX1NUVUZGKQoKClVzYW1vcyBfX21hcDJfXyBjdWFuZG8gdGVuZW1vcyBxdWUgcGFzYXIgZG9zIGlucHV0LCBxdWUgc2UgYXBsaWNhbiBzb2JyZSB1bmEgZnVuY2nDs246CgotIG1hcDIoLngsIC55LCAuZiwgLi4uKQotIG1hcDIoSU5QVVRfT05FLCBJTlBVVF9UV08sIEZVTkNUSU9OX1RPX0FQUExZLCBPUFRJT05BTF9PVEhFUl9TVFVGRikKClNpIHRlbmVtb3MgbcOhcyBkZSBkb3MuLi4KCi0gcG1hcCgubCwgLmYsIC4uLikKLSBwbWFwKExJU1RfT0ZfSU5QVVRfTElTVFMsIEZVTkNUSU9OX1RPX0FQUExZLCBPUFRJT05BTF9PVEhFUl9TVFVGRikKCgpOb3NvdHJvcyB0ZW5lbW9zIHF1ZSBwYXNhciBwYXNhciBsb3MgdmFsb3JlcyBkZSBhMSB5IGEyIChkb3MgcGFyw6FtZXRyb3MgLS0+IG1hcDIpLCBwZXJvIGNvbW8gbnVlc3RyYSBmdW5jacOzbiB0b21hIHPDs2xvIHVubyAoZWwgdmVjdG9yIGEpLCBub3MgYXJtYW1vcyB1bmEgZnVuY2nDs24gZGUgYXl1ZGEgcGFyYSB3cmFwZWFyIGExIHkgYTIKCgpgYGB7cn0Kc2ltMV9kaXN0IDwtIGZ1bmN0aW9uKGExLCBhMikgewogIG1lYXN1cmVfZGlzdGFuY2UoYyhhMSwgYTIpLCBzaW0xKQp9Cgptb2RlbHMgPC0gbW9kZWxzICU+JSAKICBtdXRhdGUoZGlzdCA9IHB1cnJyOjptYXAyX2RibChhMSwgYTIsIHNpbTFfZGlzdCkpCm1vZGVscwpgYGAKCgpBIGNvbnRpbnVhY2nDs24sIHN1cGVycG9uZ2Ftb3MgbG9zIDEwIG1lam9yZXMgbW9kZWxvcyBhIGxvcyBkYXRvcy4gQ29sb3JlYW1vcyBsb3MgbW9kZWxvcyBwb3IgYC1kaXN0YDogZXN0YSBlcyB1bmEgbWFuZXJhIGbDoWNpbCBkZSBhc2VndXJhcnNlIGRlIHF1ZSBsb3MgbWVqb3JlcyBtb2RlbG9zIChlcyBkZWNpciwgbG9zIHF1ZSB0aWVuZW4gbGEgbWVub3IgZGlzdGFuY2lhKSBvYnRlbmdhbiBsb3MgY29sb3JlcyBtw6FzIGJyaWxsYW50ZXMuCgoKYGBge3J9CmdncGxvdChzaW0xLCBhZXMoeCwgeSkpICsgCiAgZ2VvbV9wb2ludChzaXplID0gMiwgY29sb3VyID0gImdyZXkzMCIpICsgCiAgZ2VvbV9hYmxpbmUoCiAgICBhZXMoaW50ZXJjZXB0ID0gYTEsIHNsb3BlID0gYTIsIGNvbG91ciA9IC1kaXN0KSwgCiAgICBkYXRhID0gZmlsdGVyKG1vZGVscywgcmFuayhkaXN0KSA8PSAxMCkKICApCmBgYAoKClRhbWJpw6luIHBvZGVtb3MgcGVuc2FyIGVuIGVzdG9zIG1vZGVsb3MgY29tbyBvYnNlcnZhY2lvbmVzIHkgdmlzdWFsaXphciBjb24gdW4gZ3LDoWZpY28gZGUgZGlzcGVyc2nDs24gZGUgYGExYCB2c2AgYTJgLCBudWV2YW1lbnRlIGNvbG9yZWFkbyBwb3IgYC1kaXN0YC4gWWEgbm8gcG9kZW1vcyB2ZXIgZGlyZWN0YW1lbnRlIGPDs21vIHNlIGNvbXBhcmEgZWwgbW9kZWxvIGNvbiBsb3MgZGF0b3MsIHBlcm8gcG9kZW1vcyB2ZXIgbXVjaG9zIG1vZGVsb3MgYSBsYSB2ZXouIE51ZXZhbWVudGUsIGRlc3RhcXXDqSBsb3MgMTAgbWVqb3JlcyBtb2RlbG9zLCBlc3RhIHZleiBkaWJ1amFuZG8gY8OtcmN1bG9zIHJvam9zIGRlYmFqbyBkZSBlbGxvcy4KCmBgYHtyfQpnZ3Bsb3QobW9kZWxzLCBhZXMoYTEsIGEyKSkgKwogIGdlb21fcG9pbnQoZGF0YSA9IGZpbHRlcihtb2RlbHMsIHJhbmsoZGlzdCkgPD0gMTApLCBzaXplID0gNCwgY29sb3VyID0gInJlZCIpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSAtZGlzdCkpCmBgYAoKIyMgR3JpZCBzZWFyY2gKCkVuIGx1Z2FyIGRlIHByb2JhciBtdWNob3MgbW9kZWxvcyBhbGVhdG9yaW9zLCBwb2Ryw61hbW9zIHNlciBtw6FzIHNpc3RlbcOhdGljb3MgeSBnZW5lcmFyIHVuYSBjdWFkcsOtY3VsYSBkZSBwdW50b3MgdW5pZm9ybWVtZW50ZSBlc3BhY2lhZGEgKGVzdG8gc2UgZGVub21pbmEgZ3JpZCBzZWFyY2gpLiBFbGVnaW1vcyBsb3MgcGFyw6FtZXRyb3MgZGUgbGEgZ3JpbGxhIGFwcm94aW1hZGFtZW50ZSBtaXJhbmRvIGTDs25kZSBlc3RhYmFuIGxvcyBtZWpvcmVzIG1vZGVsb3MgZW4gZWwgZ3LDoWZpY28gYW50ZXJpb3IuCgoKYGBge3J9CmdyaWQgPC0gZXhwYW5kLmdyaWQoCiAgYTEgPSBzZXEoLTUsIDIwLCBsZW5ndGggPSAyNSksCiAgYTIgPSBzZXEoMSwgMywgbGVuZ3RoID0gMjUpCiAgKSAlPiUgCiAgbXV0YXRlKGRpc3QgPSBwdXJycjo6bWFwMl9kYmwoYTEsIGEyLCBzaW0xX2Rpc3QpKQoKZ3JpZCAlPiUgCiAgZ2dwbG90KGFlcyhhMSwgYTIpKSArCiAgZ2VvbV9wb2ludChkYXRhID0gZmlsdGVyKGdyaWQsIHJhbmsoZGlzdCkgPD0gMTApLCBzaXplID0gNCwgY29sb3VyID0gInJlZCIpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSAtZGlzdCkpIApgYGAKCkN1YW5kbyBzdXBlcnBvbmVtb3MgbG9zIDEwIG1lam9yZXMgbW9kZWxvcyBlbiBsb3MgZGF0b3Mgb3JpZ2luYWxlcywgdG9kb3Mgc2UgdmVuIGJhc3RhbnRlIGJpZW46CgpgYGB7cn0KZ2dwbG90KHNpbTEsIGFlcyh4LCB5KSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAyLCBjb2xvdXIgPSAiZ3JleTMwIikgKyAKICBnZW9tX2FibGluZSgKICAgIGFlcyhpbnRlcmNlcHQgPSBhMSwgc2xvcGUgPSBhMiwgY29sb3VyID0gLWRpc3QpLCAKICAgIGRhdGEgPSBmaWx0ZXIoZ3JpZCwgcmFuayhkaXN0KSA8PSAxMCkKICApCmBgYAoKCiMgTW9kZWxvIELDoXNpY28tIENsYXNlIDIKCiMjIMOzcHRpbW8gcG9yIG3DqXRvZG9zIG51bcOpcmljb3MgCgpQb2Ryw61hbW9zIGltYWdpbmFyIGl0ZXJhdGl2YW1lbnRlIGhhY2llbmRvIGxhIGN1YWRyw61jdWxhIG3DoXMgZmluYSB5IGZpbmEgaGFzdGEgcXVlIG5vcyBjZW50cmFtb3MgZW4gZWwgbWVqb3IgbW9kZWxvLiBQZXJvIGhheSB1bmEgZm9ybWEgbWVqb3IgZGUgYWJvcmRhciBlc2UgcHJvYmxlbWE6IHVuYSBoZXJyYW1pZW50YSBkZSBtaW5pbWl6YWNpw7NuIG51bcOpcmljYSBsbGFtYWRhIGLDunNxdWVkYSBfX05ld3Rvbi1SYXBoc29uX18uCgpMYSBpbnR1aWNpw7NuIGRlIE5ld3Rvbi1SYXBoc29uIGVzIGJhc3RhbnRlIHNpbXBsZTogU2UgZWxpZ2UgdW4gcHVudG8gZGUgcGFydGlkYSB5IHNlIGJ1c2NhIGxhIHBlbmRpZW50ZSBtw6FzIGluY2xpbmFkYS4gTHVlZ28sIGRlc2NpZW5kZSBwb3IgZXNhIHBlbmRpZW50ZSB1biBwb2NvLCB5IHNlIHJlcGl0ZSB1bmEgeSBvdHJhIHZleiwgaGFzdGEgcXVlIG5vIHNlIHB1ZWRlIHNlZ3VpciBiYWphbmRvLiAKCgpFbiBSLCBwb2RlbW9zIGhhY2VyIGVzbyBjb24gYG9wdGltICgpYDoKCi0gbmVjZXNpdGFtb3MgcGFzYXJsZSB1biB2ZWN0b3IgZGUgcHVudG9zIGluaWNpYWxlcy4gRWxlZ2ltb3MgZWwgb3JpZ2VuIHBvciBwb25lciBjdWFscXVpZXIgY29zYQotIGxlIHBhc2Ftb3MgbnVlc3RyYSBmdW5jacOzbiBkZSBkaXN0YW5jaWEsIHkgbG9zIHBhcsOhbWV0cm9zIHF1ZSBudWVzdHJhIGZ1bmNpw7NuIG5lY2VzaXRhIChkYXRhKQoKCmBgYHtyfQpvcHRpbShjKDQsMiksIG1lYXN1cmVfZGlzdGFuY2UsIGRhdGEgPSBzaW0xKQpgYGAKCmBgYHtyfQpiZXN0IDwtIG9wdGltKGMoMCwgMCksIG1lYXN1cmVfZGlzdGFuY2UsIGRhdGEgPSBzaW0xKQpiZXN0JHBhcgoKZ2dwbG90KHNpbTEsIGFlcyh4LCB5KSkgKyAKICBnZW9tX3BvaW50KHNpemUgPSAyLCBjb2xvdXIgPSAiZ3JleTMwIikgKyAKICBnZW9tX2FibGluZShpbnRlcmNlcHQgPSBiZXN0JHBhclsxXSwgc2xvcGUgPSBiZXN0JHBhclsyXSkKYGBgCgoKIyMgw5NwdGltbyBwYXJhIGVsIG1vZGVsbyBsaW5lYWwKCkVzdGUgcHJvY2VkaW1pZW50byBlcyB2w6FsaWRvIHBhcmEgbXVjaGFzIGZhbWlsaWFzIGRlIG1vZGVsb3MuIFBlcm8gcGFyYSBlbCBjYXNvIGRlbCBtb2RlbG8gbGluZWFsLCBjb25vY2Vtb3Mgb3RyYXMgZm9ybWFzIGRlIHJlc29sdmVybG8KCgpTaSBudWVzdHJvIG1vZGVsbyBlcwoKJCQKeSA9IGFfMSArIGFfMnggKyBcZXBzaWxvbgokJAoKTGEgc29sdWNpw7NuIGRlbCDDs3B0aW1hIGVzCgokJApcaGF0e2FfMX0gPSBcYmFye3l9IC0gXGhhdHthXzJ9XGJhcnt4fSAKJCQKCiQkClxoYXR7YV8yfSA9IFxmcmFje1xzdW1faV5uICh5X2kgLVxiYXJ7eX0pKHhfaSAtXGJhcnt4fSl9e1xzdW1faV5uICh4X2ktIFxiYXJ7eH0pfQokJAoKUiB0aWVuZSB1bmEgZnVuY2nDs24gZXNwZWPDrWZpY2EgcGFyYSBlbCBtb2RlbG8gbGluZWFsIGBsbSgpYC4gQ8OzbW8gZXN0YSBmdW5jacOzbiBzaXJ2ZSB0YW50byBwYXJhIHJlZ3Jlc2lvbmVzIGxpbmVhbGVzIHNpbXBsZXMgY29tbyBtw7psdGlwbGVzLCBkZWJlbW9zIGVzcGVjaWZpY2FyIGVsIG1vZGVsbyBlbiBsYXMgX2Zvcm11bGFzXzogYHkgfiB4YAoKCmBgYHtyfQpzaW0xX21vZCA8LSBsbSh5IH4geCwgZGF0YSA9IHNpbTEpCmNvZWYoc2ltMV9tb2QpCmBgYAoKCgojIyBQcmVkaWNjaW9uZXMKCgpQYXJhIHZpc3VhbGl6YXIgbGFzIHByZWRpY2Npb25lcyBkZSB1biBtb2RlbG8sIGNvbWVuemFtb3MgcG9yIGdlbmVyYXIgdW5hIGN1YWRyw61jdWxhIGRlIHZhbG9yZXMgZXNwYWNpYWRvcyB1bmlmb3JtZW1lbnRlIHF1ZSBjdWJyZSBsYSByZWdpw7NuIGRvbmRlIHNlIGVuY3VlbnRyYW4gbnVlc3Ryb3MgZGF0b3MuIExhIGZvcm1hIG3DoXMgZsOhY2lsIGRlIGhhY2VybG8gZXMgdXNhciBgbW9kZWxyIDo6IGRhdGFfZ3JpZCAoKWAuIFN1IHByaW1lciBhcmd1bWVudG8gZXMgdW4gZGF0YWZyYW1lLCB5IHBhcmEgY2FkYSBhcmd1bWVudG8gcG9zdGVyaW9yIGVuY3VlbnRyYSBsYXMgdmFyaWFibGVzIMO6bmljYXMgeSBsdWVnbyBnZW5lcmEgdG9kYXMgbGFzIGNvbWJpbmFjaW9uZXM6CgpgYGB7cn0KZ3JpZCA8LSBzaW0xICU+JSAKICBkYXRhX2dyaWQoeCkgCmdyaWQKYGBgCgooQ3VhbmRvIHRlbmdhbW9zIG1vZGVsb3MgZGUgdmFyaWFzIHZhcmlhYmxlcywgZXN0YSBmdW5jacOzbiB2YSBhIHNlciBkZSBtdWNoYSB1dGlsaWRhZCkKCkEgY29udGludWFjacOzbiwgYWdyZWdhbW9zIHByZWRpY2Npb25lcy4gVXNhcmVtb3MgYG1vZGVsciA6OiBhZGRfcHJlZGljdGlvbnMgKClgIHF1ZSB0b21hIHVuIGRhdGFmcmFtZSB5IHVuIG1vZGVsby4gQWdyZWdhIGxhcyBwcmVkaWNjaW9uZXMgZGVsIG1vZGVsbyBhIHVuYSBudWV2YSBjb2x1bW5hIGVuIGVsIGRhdGFmcmFtZToKCmBgYHtyfQpncmlkIDwtIGdyaWQgJT4lIAogIGFkZF9wcmVkaWN0aW9ucyhzaW0xX21vZCkgCmdyaWQKYGBgCgooVGFtYmnDqW4gcG9kcsOtYW1vcyBhZ3JlZ2FyIHByZWRpY2Npb25lcyBhbCBkYXRhc2V0IG9yaWdpbmFsKQoKIEEgY29udGludWFjacOzbiwgdHJhemFtb3MgbGFzIHByZWRpY2Npb25lcy4gTGEgdmVudGFqYSBkZSBlc3RlIGVuZm9xdWUgcmVzcGVjdG8gZGUgYGdlb21fYWJsaW5lICgpYCBlcyBxdWUgZnVuY2lvbmFyw6EgY29uIF9fY3VhbHF1aWVyX18gbW9kZWxvIGVuIFIsIGRlc2RlIGVsIG3DoXMgc2ltcGxlIGhhc3RhIGVsIG3DoXMgY29tcGxlam8uCgpgYGB7cn0KZ2dwbG90KHNpbTEsIGFlcyh4KSkgKwogIGdlb21fcG9pbnQoYWVzKHkgPSB5KSkgKwogIGdlb21fbGluZShhZXMoeSA9IHByZWQpLCBkYXRhID0gZ3JpZCwgY29sb3VyID0gInJlZCIsIHNpemUgPSAxKQpgYGAKCiMjIFJlc2lkdW9zIGRlbCBtb2RlbG8KCkFncmVnYW1vcyByZXNpZHVvcyBhbCBERiBjb24gYGFkZF9yZXNpZHVhbHMgKClgLCBxdWUgZnVuY2lvbmEgZGUgZm9ybWEgbXV5IHNpbWlsYXIgYSBgYWRkX3ByZWRpY3Rpb25zICgpYC4gU2luIGVtYmFyZ28sIHRlbmdhbiBlbiBjdWVudGEgcXVlIHVzYW1vcyBlbCBjb25qdW50byBkZSBkYXRvcyBvcmlnaW5hbCwgbm8gdW5hIGN1YWRyw61jdWxhIGZhYnJpY2FkYS4gRXN0byBzZSBkZWJlIGEgcXVlIHBhcmEgY2FsY3VsYXIgbG9zIHJlc2lkdW9zIG5lY2VzaXRhbW9zIHZhbG9yZXMgeSByZWFsZXMuCgoKYGBge3J9CnNpbTEgPC0gc2ltMSAlPiUgCiAgYWRkX3Jlc2lkdWFscyhzaW0xX21vZCkKc2ltMQpgYGAKCkhheSB2YXJpYXMgZm9ybWFzIGRpZmVyZW50ZXMgZGUgZW50ZW5kZXIgbG8gcXVlIG5vcyBkaWNlbiBsb3MgcmVzaWR1b3Mgc29icmUgZWwgbW9kZWxvLiBVbmEgZm9ybWEgZXMgc2ltcGxlbWVudGUgZGlidWphciB1biBwb2zDrWdvbm8gZGUgZnJlY3VlbmNpYSBxdWUgbm9zIGF5dWRlIGEgZW50ZW5kZXIgbGEgZGlzcGVyc2nDs24gZGUgbG9zIHJlc2lkdW9zOgoKYGBge3J9CmdncGxvdChzaW0xLCBhZXMocmVzaWQpKSArIAogIGdlb21fZnJlcXBvbHkoYmlud2lkdGggPSAwLjUpCmBgYAoKTm90ZW4gcXVlIGVuIHByb21lZGlvIGxvcyByZXNpZHVvcyBkZWJlbiBzZXIgdW4gdmFsb3IgbXV5IGNlcmNhbm8gYSAwCgpgYGB7cn0KbWVhbihzaW0xJHJlc2lkKQpgYGAKCk90cmEgZm9ybWEgZGUgZ3JhZmljYXIgbG9zIHJlc2lkdW9zIHMKCmBgYHtyfQpnZ3Bsb3Qoc2ltMSwgYWVzKHgsIHJlc2lkKSkgKyAKICBnZW9tX3JlZl9saW5lKGggPSAwLCBzaXplID0gMixjb2xvdXIgPSAiZmlyZWJyaWNrIikgKwogIGdlb21fcG9pbnQoKSAKYGBgCgpTaSBsb3MgcmVzaWR1b3MgX19ubyB0aWVuZW4gZXN0cnVjdHVyYV9fIGVzIHF1ZSBoaWNpbW9zIGxhcyBjb3NhcyBiaWVuLg==