library(dplyr)
library(ggplot2)
library(prophet)
library(lubridate)

Exploratorias

shopping <- read.csv('local-shopping_prophet.csv') %>% 
  rename(.,visitantes=Total.de.Visitantes) %>%
  mutate(Dia = ymd(Dia))
glimpse(shopping)
Observations: 228
Variables: 3
$ Dia        <date> 2017-09-14, 2017-09-15, 2017-09-16, 2017-09-17, 2017-09-18, 2017-09-19, 2017-09-20, 2017-09-21, 2017-09-22, 2017-09-23, 201...
$ visitantes <int> 589, 696, 1034, 940, 540, 526, 312, 553, 639, 793, 745, 8, 333, 568, 352, 393, 497, 578, 563, 520, 413, 8, 350, 765, 1071, 5...
$ Clima      <fct> Parcialmente Nublado, Soleado, Soleado, Parcialmente Nublado, Soleado, Soleado, Soleado, Nublado, Soleado, Soleado, Parcialm...
ggplot(shopping, aes(Dia, visitantes)) + geom_line() + theme_bw() + labs(title='Visitantes por dia')

 ggplot(shopping, aes(Dia, visitantes)) + geom_line() + geom_point(color='forestgreen') + theme_bw() + labs(title='Visitantes por dia')

ggplot(shopping, aes(Dia, visitantes)) + geom_point(color='forestgreen') + geom_smooth() + theme_bw() + labs(title='Visitantes por dia: Suavizado')

Pruebas LOESS

loess=stats::loess(visitantes~as.numeric(Dia), data = shopping, na.action = 'na.exclude', model = T, span=0.01)
span too small.   fewer data values than degrees of freedom.pseudoinverse used at 17422neighborhood radius 2.135reciprocal condition number  0There are other near singularities as well. 4.5582k-d tree limited by memory. ncmax= 228
shopping['loess']=predict(loess,shopping)
loess_1 = ggplot(shopping, aes(Dia,visitantes)) + geom_point() + geom_line(aes(y=loess), color='firebrick', size=1) + 
  labs(title= "LOESS span:0.01") + theme_bw() 
loess=stats::loess(visitantes~as.numeric(Dia), data = shopping, na.action = 'na.exclude', model = T, span=0.33)
shopping['loess']=predict(loess,shopping)
loess_2 = ggplot(shopping, aes(Dia,visitantes)) + geom_point() + geom_line(aes(y=loess), color='forestgreen', size=1) + 
  labs(title= "LOESS span:0.33") + theme_bw() 
loess=stats::loess(visitantes~as.numeric(Dia), data = shopping, na.action = 'na.exclude', model = T, span=0.66)
shopping['loess']=predict(loess,shopping)
loess_3 = ggplot(shopping, aes(Dia,visitantes)) + geom_point() + geom_line(aes(y=loess), color='steelblue', size=1) + 
  labs(title= "LOESS span:0.66") + theme_bw() 
loess=stats::loess(visitantes~as.numeric(Dia), data = shopping, na.action = 'na.exclude', model = T, span=0.99)
shopping['loess']=predict(loess,shopping)
loess_4 = ggplot(shopping, aes(Dia,visitantes)) + geom_point() + geom_line(aes(y=loess), color='purple', size=1) + 
  labs(title= "LOESS span:0.99") + theme_bw() 
cowplot::plot_grid(loess_1, loess_2, loess_3,loess_4)

Preparacion del dataset

Prophet requiere que le pasemos el dataset con:

# Eliminamos observaciones con menos de 250 visitantes (cuestion de negocio)
shopping[shopping['visitantes']<250,'visitantes'] = NA
# Creamos el dataset
prophet_df = shopping %>% select(Dia, visitantes) %>% rename(., ds=Dia, y=visitantes)

Modelo

Hay muchos parametros para tener en cuenta. Veremos algunos:

Modelo basico

La funcion prophet crea el modelo, podemos pasarle o no el dataframe.

La funcion prophet.fit aplica un modelo creado a un dataframe

# Llamamos solo al modelo
prophet_base=prophet()
# Le pasamos el dataset
prophet_base = fit.prophet(m = prophet_base, prophet_df) 
Disabling yearly seasonality. Run prophet with yearly.seasonality=TRUE to override this.
Disabling daily seasonality. Run prophet with daily.seasonality=TRUE to override this.
Initial log joint probability = -4.68957
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance

Fijense que el modelo automaticamente deshabilita la estacionalidad anual y diaria.

Grafico del modelo

Llamando a plot obtenemos el valor predicho del modelo y el valor original.

Es dificil de notar pero el modelo realiza predicciones aun para los dias en los cuales no hay datos.

plot(prophet_base,fcst=predict(prophet_base, prophet_df)) +theme_bw()

Componentes del modelo

La funcion prophet_plot_components nos devuelve los efectos de los componentes en nuestra variable a predecir

prophet_plot_components(prophet_base, fcst=predict(prophet_base, prophet_df)) +theme_bw()
NULL

Diagnostico del modelo

La funcion cross_validation permite realizar pronosticos realizando un esquema de cross-validation temporal y, a partir de ellos, obtener ciertas metricas de performance.

  • horizon: horizonte del pronostico

  • period: periodo entre fechas de analisis

  • initial: periodo inicial de entrenamiento

cv_base = cross_validation(prophet_base, initial = 45, period = 7, horizon = 15, units = 'days')
Making 24 forecasts with cutoffs between 2017-11-04 and 2018-04-14
Initial log joint probability = -3.06265
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -2.89179
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.42994
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -2.99569
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.58187
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.40797
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -6.14203
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -6.67306
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.38137
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.21596
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.43862
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.36873
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.43537
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.5117
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.60968
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.69348
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.77909
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.8066
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -4.02179
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -4.02246
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -3.99051
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -4.04036
Optimization terminated normally: 
  Convergence detected: relative gradient magnitude is below tolerance
Initial log joint probability = -4.27091
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance
Initial log joint probability = -4.33551
Optimization terminated normally: 
  Convergence detected: relative gradient magnitude is below tolerance
cv_base

La funcion performance_metrics computa varias metricas de performance a partir de un dataframe de cross validation de prophet

performance_metrics(cv_base, rolling_window = 0.5)

Modelo con estacionalidad mensual

La funcion add_seasonality nos permite agregar nuevas estacionalidades. Definimos:

  • m: modelo

  • name: nombre de la estacionalidad

  • period: cantidad de dias del periodo

  • fourier.order: orden de la serie de fourier para modelar la estacionalidad

# Llamamos solo al modelo
prophet_mensual=prophet()
# Agregamos la estacionalidad mensual
prophet_mensual=add_seasonality(prophet_mensual, name='monthly', period=30.5, fourier.order = 4)
# Le pasamos el dataset
prophet_mensual = fit.prophet(m = prophet_mensual, prophet_df) 
Disabling yearly seasonality. Run prophet with yearly.seasonality=TRUE to override this.
Disabling daily seasonality. Run prophet with daily.seasonality=TRUE to override this.
Initial log joint probability = -4.68957
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance

Fijense que el modelo automaticamente deshabilita la estacionalidad anual y diaria.

Grafico del modelo

plot(prophet_mensual,fcst=predict(prophet_mensual, prophet_df)) +theme_bw()

Componentes del modelo

La funcion prophet_plot_components nos devuelve los efectos de los componentes en nuestra variable a predecir

prophet_plot_components(prophet_mensual, fcst=predict(prophet_mensual, prophet_df)) +theme_bw()
NULL

Diagnostico del modelo

La funcion performance_metrics computa varias metricas de performance a partir de un dataframe de cross validation de prophet

performance_metrics(cv_mensual, rolling_window = 0.5)

Modelo completo

Como ultimo paso vamos a agregar las ventas de navidad y ciertos dias de promociones como eventos especiales

Dataframe de eventos

Creamos el dataframe de eventos con: nombre del evento, fechas y una “ventana” para definir si el evento se estira a ciertos dias.

christmas = data.frame(holiday= 'christmas',
  ds=ymd(c('2017-12-16','2017-12-17','2017-12-18',
                        '2017-12-19','2017-12-20','2017-12-21',
                        '2017-12-22','2017-12-23')),
  lower_window= 0,
  upper_window= 0)
big_sales = data.frame(
  holiday= 'big_sales',
  ds= ymd(c('2017-09-16','2017-10-08','2017-10-14',
                        '2017-11-20','2017-12-03','2017-12-30')),
  lower_window= 0,
  upper_window= 0)
holidays= bind_rows(christmas, big_sales)
Unequal factor levels: coercing to characterbinding character and factor vector, coercing into character vectorbinding character and factor vector, coercing into character vector
glimpse(holidays)
Observations: 14
Variables: 4
$ holiday      <chr> "christmas", "christmas", "christmas", "christmas", "christmas", "christmas", "christmas", "christmas", "big_sales", "big_...
$ ds           <date> 2017-12-16, 2017-12-17, 2017-12-18, 2017-12-19, 2017-12-20, 2017-12-21, 2017-12-22, 2017-12-23, 2017-09-16, 2017-10-08, 2...
$ lower_window <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
$ upper_window <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
# Llamamos al modelo con el dataset de eventos
prophet_full=prophet(holidays = holidays)
# Agregamos la estacionalidad mensual
prophet_full=add_seasonality(prophet_full, name='monthly', period=30.5, fourier.order = 4)
# Le pasamos el dataset
prophet_full = fit.prophet(m = prophet_full, prophet_df) 
Disabling yearly seasonality. Run prophet with yearly.seasonality=TRUE to override this.
Disabling daily seasonality. Run prophet with daily.seasonality=TRUE to override this.
Initial log joint probability = -4.68957
Optimization terminated normally: 
  Convergence detected: absolute parameter change was below tolerance

Fijense que el modelo automaticamente deshabilita la estacionalidad anual y diaria.

Grafico del modelo

plot(prophet_full,fcst=predict(prophet_full, prophet_df)) +theme_bw()

Componentes del modelo

La funcion prophet_plot_components nos devuelve los efectos de los componentes en nuestra variable a predecir

prophet_plot_components(prophet_full, fcst=predict(prophet_full, prophet_df)) +theme_bw()
NULL

Diagnostico del modelo

performance_metrics(cv_full, rolling_window = 0.5)

Graficos interactivos

dyplot.prophet(prophet_full, fcst=predict(prophet_full, prophet_df))
LS0tCnRpdGxlOiAiR0FNIGVuIHNlcmllcyBkZSB0aWVtcG86IFByb3BoZXQiCm91dHB1dDogaHRtbF9ub3RlYm9vawphdXRob3I6ICJKdWFuIE1hbnVlbCBCYXJyaW9sYSB5IERpZWdvIEtvemxvd3NraSIKZGF0ZTogMTAtMTEtMjAxOAotLS0KCgoKYGBge3J9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShwcm9waGV0KQpsaWJyYXJ5KGx1YnJpZGF0ZSkKYGBgCgojIyBFeHBsb3JhdG9yaWFzCgpgYGB7cn0Kc2hvcHBpbmcgPC0gcmVhZC5jc3YoJ2xvY2FsLXNob3BwaW5nX3Byb3BoZXQuY3N2JykgJT4lIAogIHJlbmFtZSguLHZpc2l0YW50ZXM9VG90YWwuZGUuVmlzaXRhbnRlcykgJT4lCiAgbXV0YXRlKERpYSA9IHltZChEaWEpKQpnbGltcHNlKHNob3BwaW5nKQpgYGAKCmBgYHtyfQpnZ3Bsb3Qoc2hvcHBpbmcsIGFlcyhEaWEsIHZpc2l0YW50ZXMpKSArIGdlb21fbGluZSgpICsgdGhlbWVfYncoKSArIGxhYnModGl0bGU9J1Zpc2l0YW50ZXMgcG9yIGRpYScpCmBgYAoKYGBge3J9CiBnZ3Bsb3Qoc2hvcHBpbmcsIGFlcyhEaWEsIHZpc2l0YW50ZXMpKSArIGdlb21fbGluZSgpICsgZ2VvbV9wb2ludChjb2xvcj0nZm9yZXN0Z3JlZW4nKSArIHRoZW1lX2J3KCkgKyBsYWJzKHRpdGxlPSdWaXNpdGFudGVzIHBvciBkaWEnKQpgYGAKCmBgYHtyfQpnZ3Bsb3Qoc2hvcHBpbmcsIGFlcyhEaWEsIHZpc2l0YW50ZXMpKSArIGdlb21fcG9pbnQoY29sb3I9J2ZvcmVzdGdyZWVuJykgKyBnZW9tX3Ntb290aCgpICsgdGhlbWVfYncoKSArIGxhYnModGl0bGU9J1Zpc2l0YW50ZXMgcG9yIGRpYTogU3Vhdml6YWRvJykKYGBgCgojIyBQcnVlYmFzIExPRVNTCgpgYGB7cn0KbG9lc3M9c3RhdHM6OmxvZXNzKHZpc2l0YW50ZXN+YXMubnVtZXJpYyhEaWEpLCBkYXRhID0gc2hvcHBpbmcsIG5hLmFjdGlvbiA9ICduYS5leGNsdWRlJywgbW9kZWwgPSBULCBzcGFuPTAuMDEpCnNob3BwaW5nWydsb2VzcyddPXByZWRpY3QobG9lc3Msc2hvcHBpbmcpCmxvZXNzXzEgPSBnZ3Bsb3Qoc2hvcHBpbmcsIGFlcyhEaWEsdmlzaXRhbnRlcykpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9saW5lKGFlcyh5PWxvZXNzKSwgY29sb3I9J2ZpcmVicmljaycsIHNpemU9MSkgKyAKICBsYWJzKHRpdGxlPSAiTE9FU1Mgc3BhbjowLjAxIikgKyB0aGVtZV9idygpIAoKbG9lc3M9c3RhdHM6OmxvZXNzKHZpc2l0YW50ZXN+YXMubnVtZXJpYyhEaWEpLCBkYXRhID0gc2hvcHBpbmcsIG5hLmFjdGlvbiA9ICduYS5leGNsdWRlJywgbW9kZWwgPSBULCBzcGFuPTAuMzMpCnNob3BwaW5nWydsb2VzcyddPXByZWRpY3QobG9lc3Msc2hvcHBpbmcpCmxvZXNzXzIgPSBnZ3Bsb3Qoc2hvcHBpbmcsIGFlcyhEaWEsdmlzaXRhbnRlcykpICsgZ2VvbV9wb2ludCgpICsgZ2VvbV9saW5lKGFlcyh5PWxvZXNzKSwgY29sb3I9J2ZvcmVzdGdyZWVuJywgc2l6ZT0xKSArIAogIGxhYnModGl0bGU9ICJMT0VTUyBzcGFuOjAuMzMiKSArIHRoZW1lX2J3KCkgCgpsb2Vzcz1zdGF0czo6bG9lc3ModmlzaXRhbnRlc35hcy5udW1lcmljKERpYSksIGRhdGEgPSBzaG9wcGluZywgbmEuYWN0aW9uID0gJ25hLmV4Y2x1ZGUnLCBtb2RlbCA9IFQsIHNwYW49MC42NikKc2hvcHBpbmdbJ2xvZXNzJ109cHJlZGljdChsb2VzcyxzaG9wcGluZykKbG9lc3NfMyA9IGdncGxvdChzaG9wcGluZywgYWVzKERpYSx2aXNpdGFudGVzKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX2xpbmUoYWVzKHk9bG9lc3MpLCBjb2xvcj0nc3RlZWxibHVlJywgc2l6ZT0xKSArIAogIGxhYnModGl0bGU9ICJMT0VTUyBzcGFuOjAuNjYiKSArIHRoZW1lX2J3KCkgCgpsb2Vzcz1zdGF0czo6bG9lc3ModmlzaXRhbnRlc35hcy5udW1lcmljKERpYSksIGRhdGEgPSBzaG9wcGluZywgbmEuYWN0aW9uID0gJ25hLmV4Y2x1ZGUnLCBtb2RlbCA9IFQsIHNwYW49MC45OSkKc2hvcHBpbmdbJ2xvZXNzJ109cHJlZGljdChsb2VzcyxzaG9wcGluZykKbG9lc3NfNCA9IGdncGxvdChzaG9wcGluZywgYWVzKERpYSx2aXNpdGFudGVzKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX2xpbmUoYWVzKHk9bG9lc3MpLCBjb2xvcj0ncHVycGxlJywgc2l6ZT0xKSArIAogIGxhYnModGl0bGU9ICJMT0VTUyBzcGFuOjAuOTkiKSArIHRoZW1lX2J3KCkgCgpjb3dwbG90OjpwbG90X2dyaWQobG9lc3NfMSwgbG9lc3NfMiwgbG9lc3NfMyxsb2Vzc180KQpgYGAKCiMjIFByZXBhcmFjaW9uIGRlbCBkYXRhc2V0CgpQcm9waGV0IHJlcXVpZXJlIHF1ZSBsZSBwYXNlbW9zIGVsIGRhdGFzZXQgY29uOgoKICAqICoqZHMqKjogbGEgdmFyaWFibGUgdGVtcG9yYWwgCgogICogKip5Kio6IGxhIHZhcmlhYmxlIGEgcHJlZGVjaXIKCmBgYHtyfQojIEVsaW1pbmFtb3Mgb2JzZXJ2YWNpb25lcyBjb24gbWVub3MgZGUgMjUwIHZpc2l0YW50ZXMgKGN1ZXN0aW9uIGRlIG5lZ29jaW8pCnNob3BwaW5nW3Nob3BwaW5nWyd2aXNpdGFudGVzJ108MjUwLCd2aXNpdGFudGVzJ10gPSBOQQojIENyZWFtb3MgZWwgZGF0YXNldApwcm9waGV0X2RmID0gc2hvcHBpbmcgJT4lIHNlbGVjdChEaWEsIHZpc2l0YW50ZXMpICU+JSByZW5hbWUoLiwgZHM9RGlhLCB5PXZpc2l0YW50ZXMpCmBgYAoKIyMgTW9kZWxvCgpIYXkgbXVjaG9zIHBhcmFtZXRyb3MgcGFyYSB0ZW5lciBlbiBjdWVudGEuIFZlcmVtb3MgYWxndW5vczoKCiAgKiAqKmRmKio6IGRhdGFmcmFtZQogIAogICogKipncm93dGgqKjogdGlwbyBkZSB0ZW5kZW5jaWE6IGxpbmVhbCBvIGxvZ2lzdGljYQoKICAqICoqeWVhcmx5LnNlYXNvbmFsaXR5Kio6IGhheSBlc3RhY2lvbmFsaWRhZCBhbnVhbD8KICAKICAqICoqeWVhcmx5LnNlYXNvbmFsaXR5Kio6IGhheSBlc3RhY2lvbmFsaWRhZCBkaWFyaWE/CiAgCiAgKiAgKipob2xpZGF5cyoqOiBkYXRhZnJhbWUgY29uIGZlY2hhcyBkZSB2YWNhY2lvbmVzL2V2ZW50b3MgZXNwZWNpYWxlcwoKIyMjIE1vZGVsbyBiYXNpY28KCkxhIGZ1bmNpb24gYHByb3BoZXRgIGNyZWEgZWwgbW9kZWxvLCBwb2RlbW9zIHBhc2FybGUgbyBubyBlbCBkYXRhZnJhbWUuCgpMYSBmdW5jaW9uIGBwcm9waGV0LmZpdGAgYXBsaWNhIHVuIG1vZGVsbyBjcmVhZG8gYSB1biBkYXRhZnJhbWUKCmBgYHtyfQojIExsYW1hbW9zIHNvbG8gYWwgbW9kZWxvCnByb3BoZXRfYmFzZT1wcm9waGV0KCkKIyBMZSBwYXNhbW9zIGVsIGRhdGFzZXQKcHJvcGhldF9iYXNlID0gZml0LnByb3BoZXQobSA9IHByb3BoZXRfYmFzZSwgcHJvcGhldF9kZikgCmBgYApGaWplbnNlIHF1ZSBlbCBtb2RlbG8gYXV0b21hdGljYW1lbnRlIGRlc2hhYmlsaXRhIGxhIGVzdGFjaW9uYWxpZGFkIGFudWFsIHkgZGlhcmlhLgoKIyMjIyBHcmFmaWNvIGRlbCBtb2RlbG8KCkxsYW1hbmRvIGEgYHBsb3RgIG9idGVuZW1vcyBlbCB2YWxvciBwcmVkaWNobyBkZWwgbW9kZWxvIHkgZWwgdmFsb3Igb3JpZ2luYWwuCgpFcyBkaWZpY2lsIGRlIG5vdGFyIHBlcm8gZWwgbW9kZWxvIHJlYWxpemEgcHJlZGljY2lvbmVzIGF1biBwYXJhIGxvcyBkaWFzIGVuIGxvcyBjdWFsZXMgbm8gaGF5IGRhdG9zLgoKYGBge3J9CnBsb3QocHJvcGhldF9iYXNlLGZjc3Q9cHJlZGljdChwcm9waGV0X2Jhc2UsIHByb3BoZXRfZGYpKSArdGhlbWVfYncoKQpgYGAKCiMjIyMgQ29tcG9uZW50ZXMgZGVsIG1vZGVsbwoKTGEgZnVuY2lvbiBgcHJvcGhldF9wbG90X2NvbXBvbmVudHNgIG5vcyBkZXZ1ZWx2ZSBsb3MgZWZlY3RvcyBkZSBsb3MgY29tcG9uZW50ZXMgZW4gbnVlc3RyYSB2YXJpYWJsZSBhIHByZWRlY2lyCgpgYGB7cn0KcHJvcGhldF9wbG90X2NvbXBvbmVudHMocHJvcGhldF9iYXNlLCBmY3N0PXByZWRpY3QocHJvcGhldF9iYXNlLCBwcm9waGV0X2RmKSkgK3RoZW1lX2J3KCkKYGBgCgojIyMjIERpYWdub3N0aWNvIGRlbCBtb2RlbG8KCkxhIGZ1bmNpb24gYGNyb3NzX3ZhbGlkYXRpb25gIHBlcm1pdGUgcmVhbGl6YXIgcHJvbm9zdGljb3MgcmVhbGl6YW5kbyB1biBlc3F1ZW1hIGRlIGNyb3NzLXZhbGlkYXRpb24gdGVtcG9yYWwgeSwgYSBwYXJ0aXIgZGUgZWxsb3MsIG9idGVuZXIgY2llcnRhcyBtZXRyaWNhcyBkZSBwZXJmb3JtYW5jZS4KCiAgKiAqKmhvcml6b24qKjogaG9yaXpvbnRlIGRlbCBwcm9ub3N0aWNvCiAgCiAgKiAqKnBlcmlvZCoqOiBwZXJpb2RvIGVudHJlIGZlY2hhcyBkZSBhbmFsaXNpcwogIAogICAqICoqaW5pdGlhbCoqOiBwZXJpb2RvIGluaWNpYWwgZGUgZW50cmVuYW1pZW50bwoKYGBge3J9CmN2X2Jhc2UgPSBjcm9zc192YWxpZGF0aW9uKHByb3BoZXRfYmFzZSwgaW5pdGlhbCA9IDQ1LCBwZXJpb2QgPSA3LCBob3Jpem9uID0gMTUsIHVuaXRzID0gJ2RheXMnKQpjdl9iYXNlCmBgYAoKTGEgZnVuY2lvbiBgcGVyZm9ybWFuY2VfbWV0cmljc2AgY29tcHV0YSB2YXJpYXMgbWV0cmljYXMgZGUgcGVyZm9ybWFuY2UgYSBwYXJ0aXIgZGUgdW4gZGF0YWZyYW1lIGRlIGNyb3NzIHZhbGlkYXRpb24gZGUgcHJvcGhldAoKYGBge3J9CnBlcmZvcm1hbmNlX21ldHJpY3MoY3ZfYmFzZSwgcm9sbGluZ193aW5kb3cgPSAwLjUpCmBgYAojIyMgTW9kZWxvIGNvbiBlc3RhY2lvbmFsaWRhZCBtZW5zdWFsCgpMYSBmdW5jaW9uIGBhZGRfc2Vhc29uYWxpdHlgIG5vcyBwZXJtaXRlIGFncmVnYXIgbnVldmFzIGVzdGFjaW9uYWxpZGFkZXMuIERlZmluaW1vczoKCiAgKiAqKm0qKjogbW9kZWxvCiAgCiAgKiAqKm5hbWUqKjogbm9tYnJlIGRlIGxhIGVzdGFjaW9uYWxpZGFkCiAgCiAgKiAqKnBlcmlvZCoqOiBjYW50aWRhZCBkZSBkaWFzIGRlbCBwZXJpb2RvCiAgCiAgKiAqKmZvdXJpZXIub3JkZXIqKjogb3JkZW4gZGUgbGEgc2VyaWUgZGUgZm91cmllciBwYXJhIG1vZGVsYXIgbGEgZXN0YWNpb25hbGlkYWQKCmBgYHtyfQojIExsYW1hbW9zIHNvbG8gYWwgbW9kZWxvCnByb3BoZXRfbWVuc3VhbD1wcm9waGV0KCkKIyBBZ3JlZ2Ftb3MgbGEgZXN0YWNpb25hbGlkYWQgbWVuc3VhbApwcm9waGV0X21lbnN1YWw9YWRkX3NlYXNvbmFsaXR5KHByb3BoZXRfbWVuc3VhbCwgbmFtZT0nbW9udGhseScsIHBlcmlvZD0zMC41LCBmb3VyaWVyLm9yZGVyID0gNCkKIyBMZSBwYXNhbW9zIGVsIGRhdGFzZXQKcHJvcGhldF9tZW5zdWFsID0gZml0LnByb3BoZXQobSA9IHByb3BoZXRfbWVuc3VhbCwgcHJvcGhldF9kZikgCmBgYApGaWplbnNlIHF1ZSBlbCBtb2RlbG8gYXV0b21hdGljYW1lbnRlIGRlc2hhYmlsaXRhIGxhIGVzdGFjaW9uYWxpZGFkIGFudWFsIHkgZGlhcmlhLgoKIyMjIyBHcmFmaWNvIGRlbCBtb2RlbG8KCmBgYHtyfQpwbG90KHByb3BoZXRfbWVuc3VhbCxmY3N0PXByZWRpY3QocHJvcGhldF9tZW5zdWFsLCBwcm9waGV0X2RmKSkgK3RoZW1lX2J3KCkKYGBgCgojIyMjIENvbXBvbmVudGVzIGRlbCBtb2RlbG8KCkxhIGZ1bmNpb24gYHByb3BoZXRfcGxvdF9jb21wb25lbnRzYCBub3MgZGV2dWVsdmUgbG9zIGVmZWN0b3MgZGUgbG9zIGNvbXBvbmVudGVzIGVuIG51ZXN0cmEgdmFyaWFibGUgYSBwcmVkZWNpcgoKYGBge3J9CnByb3BoZXRfcGxvdF9jb21wb25lbnRzKHByb3BoZXRfbWVuc3VhbCwgZmNzdD1wcmVkaWN0KHByb3BoZXRfbWVuc3VhbCwgcHJvcGhldF9kZikpICt0aGVtZV9idygpCmBgYAoKIyMjIyBEaWFnbm9zdGljbyBkZWwgbW9kZWxvCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIGVjaG89RkFMU0UsIHJlc3VsdHM9J2hpZGUnfQpjdl9tZW5zdWFsID0gY3Jvc3NfdmFsaWRhdGlvbihwcm9waGV0X21lbnN1YWwsIGluaXRpYWwgPSA0NSwgcGVyaW9kID0gNywgaG9yaXpvbiA9IDE1LCB1bml0cyA9ICdkYXlzJykKY3ZfbWVuc3VhbApgYGAKCkxhIGZ1bmNpb24gYHBlcmZvcm1hbmNlX21ldHJpY3NgIGNvbXB1dGEgdmFyaWFzIG1ldHJpY2FzIGRlIHBlcmZvcm1hbmNlIGEgcGFydGlyIGRlIHVuIGRhdGFmcmFtZSBkZSBjcm9zcyB2YWxpZGF0aW9uIGRlIHByb3BoZXQKCmBgYHtyfQpwZXJmb3JtYW5jZV9tZXRyaWNzKGN2X21lbnN1YWwsIHJvbGxpbmdfd2luZG93ID0gMC41KQpgYGAKCiMjIyBNb2RlbG8gY29tcGxldG8gCgpDb21vIHVsdGltbyBwYXNvIHZhbW9zIGEgYWdyZWdhciBsYXMgdmVudGFzIGRlIG5hdmlkYWQgeSBjaWVydG9zIGRpYXMgZGUgcHJvbW9jaW9uZXMgY29tbyBldmVudG9zIGVzcGVjaWFsZXMKCiMjIyMgRGF0YWZyYW1lIGRlIGV2ZW50b3MKCkNyZWFtb3MgZWwgZGF0YWZyYW1lIGRlIGV2ZW50b3MgY29uOiBub21icmUgZGVsIGV2ZW50bywgZmVjaGFzIHkgdW5hICJ2ZW50YW5hIiBwYXJhIGRlZmluaXIgc2kgZWwgZXZlbnRvIHNlIGVzdGlyYSBhIGNpZXJ0b3MgZGlhcy4KCmBgYHtyfQpjaHJpc3RtYXMgPSBkYXRhLmZyYW1lKGhvbGlkYXk9ICdjaHJpc3RtYXMnLAogIGRzPXltZChjKCcyMDE3LTEyLTE2JywnMjAxNy0xMi0xNycsJzIwMTctMTItMTgnLAogICAgICAgICAgICAgICAgICAgICAgICAnMjAxNy0xMi0xOScsJzIwMTctMTItMjAnLCcyMDE3LTEyLTIxJywKICAgICAgICAgICAgICAgICAgICAgICAgJzIwMTctMTItMjInLCcyMDE3LTEyLTIzJykpLAogIGxvd2VyX3dpbmRvdz0gMCwKICB1cHBlcl93aW5kb3c9IDApCgpiaWdfc2FsZXMgPSBkYXRhLmZyYW1lKAogIGhvbGlkYXk9ICdiaWdfc2FsZXMnLAogIGRzPSB5bWQoYygnMjAxNy0wOS0xNicsJzIwMTctMTAtMDgnLCcyMDE3LTEwLTE0JywKICAgICAgICAgICAgICAgICAgICAgICAgJzIwMTctMTEtMjAnLCcyMDE3LTEyLTAzJywnMjAxNy0xMi0zMCcpKSwKICBsb3dlcl93aW5kb3c9IDAsCiAgdXBwZXJfd2luZG93PSAwKQoKaG9saWRheXM9IGJpbmRfcm93cyhjaHJpc3RtYXMsIGJpZ19zYWxlcykKZ2xpbXBzZShob2xpZGF5cykKYGBgCgpgYGB7cn0KIyBMbGFtYW1vcyBhbCBtb2RlbG8gY29uIGVsIGRhdGFzZXQgZGUgZXZlbnRvcwpwcm9waGV0X2Z1bGw9cHJvcGhldChob2xpZGF5cyA9IGhvbGlkYXlzKQojIEFncmVnYW1vcyBsYSBlc3RhY2lvbmFsaWRhZCBtZW5zdWFsCnByb3BoZXRfZnVsbD1hZGRfc2Vhc29uYWxpdHkocHJvcGhldF9mdWxsLCBuYW1lPSdtb250aGx5JywgcGVyaW9kPTMwLjUsIGZvdXJpZXIub3JkZXIgPSA0KQojIExlIHBhc2Ftb3MgZWwgZGF0YXNldApwcm9waGV0X2Z1bGwgPSBmaXQucHJvcGhldChtID0gcHJvcGhldF9mdWxsLCBwcm9waGV0X2RmKSAKYGBgCkZpamVuc2UgcXVlIGVsIG1vZGVsbyBhdXRvbWF0aWNhbWVudGUgZGVzaGFiaWxpdGEgbGEgZXN0YWNpb25hbGlkYWQgYW51YWwgeSBkaWFyaWEuCgojIyMjIEdyYWZpY28gZGVsIG1vZGVsbwoKYGBge3J9CnBsb3QocHJvcGhldF9mdWxsLGZjc3Q9cHJlZGljdChwcm9waGV0X2Z1bGwsIHByb3BoZXRfZGYpKSArdGhlbWVfYncoKQpgYGAKCiMjIyMgQ29tcG9uZW50ZXMgZGVsIG1vZGVsbwoKTGEgZnVuY2lvbiBgcHJvcGhldF9wbG90X2NvbXBvbmVudHNgIG5vcyBkZXZ1ZWx2ZSBsb3MgZWZlY3RvcyBkZSBsb3MgY29tcG9uZW50ZXMgZW4gbnVlc3RyYSB2YXJpYWJsZSBhIHByZWRlY2lyCgpgYGB7cn0KcHJvcGhldF9wbG90X2NvbXBvbmVudHMocHJvcGhldF9mdWxsLCBmY3N0PXByZWRpY3QocHJvcGhldF9mdWxsLCBwcm9waGV0X2RmKSkgK3RoZW1lX2J3KCkKYGBgCgojIyMjIERpYWdub3N0aWNvIGRlbCBtb2RlbG8KCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgZWNobz1GQUxTRSwgcmVzdWx0cz0naGlkZSd9CmN2X2Z1bGwgPSBjcm9zc192YWxpZGF0aW9uKHByb3BoZXRfZnVsbCwgaW5pdGlhbCA9IDQ1LCBwZXJpb2QgPSA3LCBob3Jpem9uID0gMTUsIHVuaXRzID0gJ2RheXMnKQpjdl9mdWxsCmBgYAoKCmBgYHtyfQpwZXJmb3JtYW5jZV9tZXRyaWNzKGN2X2Z1bGwsIHJvbGxpbmdfd2luZG93ID0gMC41KQpgYGAKCiMjIEdyYWZpY29zIGludGVyYWN0aXZvcwoKCmBgYHtyfQpkeXBsb3QucHJvcGhldChwcm9waGV0X2Z1bGwsIGZjc3Q9cHJlZGljdChwcm9waGV0X2Z1bGwsIHByb3BoZXRfZGYpKQpgYGAK