My Code

# Clear workspace: 
rm(list = ls())

# Load some packages: 
library(nnfor)
library(fpp2)
library(tidyverse)
library(magrittr)

# Visualize AirPassengers Data: 

autoplot(AirPassengers) + 
  geom_line(color = "cyan") + 
  geom_point(color = "cyan") + 
  labs(x = NULL, y = NULL, 
       title = "Monthly Airline Passenger Numbers 1949-1960", 
       subtitle = "Data Source: The classic Box & Jenkins airline data.") + 
  theme(
    axis.line = element_blank(),  
    axis.text.x = element_text(color = "white", lineheight = 0.9),  
    axis.text.y = element_text(color = "white", lineheight = 0.9),  
    axis.ticks = element_line(color = "white", size  =  0.2),  
    axis.title.x = element_text(color = "white", margin = margin(0, 10, 0, 0)),  
    axis.title.y = element_text(color = "white", angle = 90, margin = margin(0, 10, 0, 0)),  
    axis.ticks.length = unit(0.3, "lines"),   
    legend.background = element_rect(color = NA, fill = " gray10"),  
    legend.key = element_rect(color = "white",  fill = " gray10"),  
    legend.key.size = unit(1.2, "lines"),  
    legend.key.height = NULL,  
    legend.key.width = NULL,      
    legend.text = element_text(color = "white"),  
    legend.title = element_text(face = "bold", hjust = 0, color = "white"),  
    legend.text.align = NULL,  
    legend.title.align = NULL,  
    legend.direction = "vertical",  
    legend.box = NULL, 
    panel.background = element_rect(fill = "gray10", color  =  NA),  
    panel.border = element_blank(),
    panel.grid.major = element_line(color = "grey35"),  
    panel.grid.minor = element_line(color = "grey20"),  
    panel.spacing = unit(0.5, "lines"),   
    strip.background = element_rect(fill = "grey30", color = "grey10"),  
    strip.text.x = element_text(color = "white"),  
    strip.text.y = element_text(color = "white", angle = -90),  
    plot.background = element_rect(color = "gray10", fill = "gray10"),  
    plot.title = element_text(color = "white", hjust = 0, lineheight = 1.25,
                              margin = margin(2, 2, 2, 2)),  
    plot.subtitle = element_text(color = "white", hjust = 0, margin = margin(2, 2, 2, 2)),  
    plot.caption = element_text(color = "white", hjust = 0),  
    plot.margin = unit(rep(1, 4), "lines"))

# Split data: 

train <- AirPassengers[1:132] %>% ts(frequency = 12)
test <- AirPassengers[133:144] %>% ts(frequency = 12)
h <- length(test)

#======================================================
#  MLP neural networks for Forecasting Time Series
#======================================================

# Prepare a series for train and forecasting: 
tt <- cbind(c(1:(length(train) + h), rep(0, h)))

# Fit a network with no differencing, no univariate lags, and fixed deterministic trend: 
my_mlp <- mlp(train, 
              hd = 10, 
              difforder = 0, 
              lags = 0, 
              xreg = tt, 
              xreg.lags = list(0), 
              xreg.keep = TRUE)

# Make predictions: 
pred <- forecast(my_mlp, h = h, xreg = tt)

# Data Frame for comparing: 

df_mlp <- data_frame(Actual = test %>% as.vector(), 
                     Predicted = pred$mean %>% round(0), 
                     Error = Predicted - Actual,
                     Error_Percent = round(Error / Actual, 2))

df_mlp %>% knitr::kable()
Actual Predicted Error Error_Percent
417 392 -25 -0.06
391 370 -21 -0.05
419 434 15 0.04
461 414 -47 -0.10
472 447 -25 -0.05
535 506 -29 -0.05
622 578 -44 -0.07
606 590 -16 -0.03
508 487 -21 -0.04
461 427 -34 -0.07
390 383 -7 -0.02
432 426 -6 -0.01
# Function calculate some accuracy measures: 
get_accuracy_measures <- function(your_result_df) {
  
  act <- your_result_df %>% pull(Actual)
  pred <- your_result_df %>% pull(Predicted)
  err <- act - pred 
  per_err <- abs(err / act)
  
  # Mean Absolute Error (MAE): 
  mae <- err %>% abs() %>% mean()
  
  # Mean Squared Error (MSE): 
  mse <- mean(err^2)
  
  # Mean Absolute Percentage Error (MAPE): 
  mape <- 100*mean(per_err)
  
  # Return results: 
  return(data_frame(MAE = mae, MSE = mse, MAPE = mape, N = length(act)))
}


#==================================================================
#  Auto ARIMA Approach as proposed by Hyndman and Khandakar (2008)
#==================================================================

my_arima <- auto.arima(train)

# Use the model for forecasting: 
predicted_arima <- forecast(my_arima, h = h)$mean %>% as.vector()


# Data Frame for comparing: 
df_arima <- data_frame(Actual = test %>% as.vector(), 
                       Predicted = predicted_arima %>% round(0), 
                       Error = Predicted - Actual,
                       Error_Percent = round(Error / Actual, 2))

# Model Performance: 
df_arima %>% knitr::kable()
Actual Predicted Error Error_Percent
417 424 7 0.02
391 407 16 0.04
419 471 52 0.12
461 461 0 0.00
472 485 13 0.03
535 537 2 0.00
622 613 -9 -0.01
606 624 18 0.03
508 528 20 0.04
461 472 11 0.02
390 427 37 0.09
432 470 38 0.09
#=======================================
#    My Solution: Ensemble Method 
#=======================================

my_ensemble_predict <- function(your_df, n_ahead) {
  
  N <- length(your_df)
  end1 <- N - n_ahead
  
  train <- your_df[1:end1] %>% ts(frequency = 12)
  test <- your_df[(end1 + 1):N] %>% ts(frequency = 12)
  h <- length(test)
  
  tt <- cbind(c(1:(length(train) + h), rep(0, h)))
  
  my_mlp <- mlp(train, 
                hd = 10, 
                difforder = 0, 
                lags = 0, 
                xreg = tt, 
                xreg.lags = list(0), 
                xreg.keep = TRUE)
  predicted_mlp <- forecast(my_mlp, h = h, xreg = tt)
  
  my_arima <- auto.arima(train)
  predicted_arima <- forecast(my_arima, h = h)$mean %>% as.vector()
  
  ensemble_predicted <- 0.5*(predicted_mlp$mean + predicted_arima)
  
  return(data_frame(Actual = test %>% as.vector(), 
                    Predicted = ensemble_predicted %>% round(0), 
                    Error = Predicted - Actual,
                    Error_Percent = round(Error / Actual, 2)))
} 


# Use the function: 
my_ensemble_predict(AirPassengers, 12) -> df_ensemble
df_ensemble %>% knitr::kable()
Actual Predicted Error Error_Percent
417 408 -9 -0.02
391 390 -1 0.00
419 450 31 0.07
461 440 -21 -0.05
472 466 -6 -0.01
535 521 -14 -0.03
622 596 -26 -0.04
606 609 3 0.00
508 506 -2 0.00
461 453 -8 -0.02
390 407 17 0.04
432 450 18 0.04
# Compare three approaches: 

bind_rows(df_mlp %>% get_accuracy_measures(), 
          df_arima %>% get_accuracy_measures(), 
          df_ensemble %>% get_accuracy_measures()) %>% 
  mutate(Approach = c("MLP", "ARIMA", "Ensemble")) %>% 
  select(Approach, everything()) %>% 
  mutate_if(is.numeric, function(x) {round(x, 2)}) %>% 
  knitr::kable()
Approach MAE MSE MAPE N
MLP 24.17 736.67 5.02 12
ARIMA 18.58 576.75 4.19 12
Ensemble 13.00 256.83 2.80 12

References

  1. Kourentzes, N., Barrow, D. K., & Crone, S. F. (2014). Neural network ensemble operators for time series forecasting. Expert Systems with Applications, 41(9), 4235-4244.
  2. Hyndman, R. J., and Y. Khandakar (2008):“Automatic time series forecasting: The forecast package for R,”. Journal of Statistical Software, 26(3).
  3. Barrow, D. K., Crone, S. F., & Kourentzes, N. (2010, July). An evaluation of neural network ensembles and model selection for time series prediction. In Neural Networks (IJCNN), The 2010 International Joint Conference on (pp. 1-8). IEEE.
LS0tCnRpdGxlOiAiRm9yZWNhc3RpbmcgVGltZSBTZXJpZXM6IEF1dG8gQVJJTUEsIEVuc2VtYmxlIE1ldGhvZCBhbmQgQXJ0aWZpY2lhbCBOZXVyYWwgTmV0d29ya3MiIApzdWJ0aXRsZTogIlIgZm9yIEZ1biIKYXV0aG9yOiAiTmd1eWVuIENoaSBEdW5nIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDogCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgICMgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiAiZmxhdGx5IgogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKLS0tCgpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkKYGBgCgojIE15IENvZGUKCmBgYHtyfQojIENsZWFyIHdvcmtzcGFjZTogCnJtKGxpc3QgPSBscygpKQoKIyBMb2FkIHNvbWUgcGFja2FnZXM6IApsaWJyYXJ5KG5uZm9yKQpsaWJyYXJ5KGZwcDIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KG1hZ3JpdHRyKQoKIyBWaXN1YWxpemUgQWlyUGFzc2VuZ2VycyBEYXRhOiAKCmF1dG9wbG90KEFpclBhc3NlbmdlcnMpICsgCiAgZ2VvbV9saW5lKGNvbG9yID0gImN5YW4iKSArIAogIGdlb21fcG9pbnQoY29sb3IgPSAiY3lhbiIpICsgCiAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwsIAogICAgICAgdGl0bGUgPSAiTW9udGhseSBBaXJsaW5lIFBhc3NlbmdlciBOdW1iZXJzIDE5NDktMTk2MCIsIAogICAgICAgc3VidGl0bGUgPSAiRGF0YSBTb3VyY2U6IFRoZSBjbGFzc2ljIEJveCAmIEplbmtpbnMgYWlybGluZSBkYXRhLiIpICsgCiAgdGhlbWUoCiAgICBheGlzLmxpbmUgPSBlbGVtZW50X2JsYW5rKCksICAKICAgIGF4aXMudGV4dC54ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gIndoaXRlIiwgbGluZWhlaWdodCA9IDAuOSksICAKICAgIGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gIndoaXRlIiwgbGluZWhlaWdodCA9IDAuOSksICAKICAgIGF4aXMudGlja3MgPSBlbGVtZW50X2xpbmUoY29sb3IgPSAid2hpdGUiLCBzaXplICA9ICAwLjIpLCAgCiAgICBheGlzLnRpdGxlLnggPSBlbGVtZW50X3RleHQoY29sb3IgPSAid2hpdGUiLCBtYXJnaW4gPSBtYXJnaW4oMCwgMTAsIDAsIDApKSwgIAogICAgYXhpcy50aXRsZS55ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gIndoaXRlIiwgYW5nbGUgPSA5MCwgbWFyZ2luID0gbWFyZ2luKDAsIDEwLCAwLCAwKSksICAKICAgIGF4aXMudGlja3MubGVuZ3RoID0gdW5pdCgwLjMsICJsaW5lcyIpLCAgIAogICAgbGVnZW5kLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSBOQSwgZmlsbCA9ICIgZ3JheTEwIiksICAKICAgIGxlZ2VuZC5rZXkgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAid2hpdGUiLCAgZmlsbCA9ICIgZ3JheTEwIiksICAKICAgIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMS4yLCAibGluZXMiKSwgIAogICAgbGVnZW5kLmtleS5oZWlnaHQgPSBOVUxMLCAgCiAgICBsZWdlbmQua2V5LndpZHRoID0gTlVMTCwgICAgICAKICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gIndoaXRlIiksICAKICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBoanVzdCA9IDAsIGNvbG9yID0gIndoaXRlIiksICAKICAgIGxlZ2VuZC50ZXh0LmFsaWduID0gTlVMTCwgIAogICAgbGVnZW5kLnRpdGxlLmFsaWduID0gTlVMTCwgIAogICAgbGVnZW5kLmRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIsICAKICAgIGxlZ2VuZC5ib3ggPSBOVUxMLCAKICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJncmF5MTAiLCBjb2xvciAgPSAgTkEpLCAgCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyZXkzNSIpLCAgCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyZXkyMCIpLCAgCiAgICBwYW5lbC5zcGFjaW5nID0gdW5pdCgwLjUsICJsaW5lcyIpLCAgIAogICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gImdyZXkzMCIsIGNvbG9yID0gImdyZXkxMCIpLCAgCiAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3IgPSAid2hpdGUiKSwgIAogICAgc3RyaXAudGV4dC55ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gIndoaXRlIiwgYW5nbGUgPSAtOTApLCAgCiAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiZ3JheTEwIiwgZmlsbCA9ICJncmF5MTAiKSwgIAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJ3aGl0ZSIsIGhqdXN0ID0gMCwgbGluZWhlaWdodCA9IDEuMjUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1hcmdpbiA9IG1hcmdpbigyLCAyLCAyLCAyKSksICAKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoY29sb3IgPSAid2hpdGUiLCBoanVzdCA9IDAsIG1hcmdpbiA9IG1hcmdpbigyLCAyLCAyLCAyKSksICAKICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJ3aGl0ZSIsIGhqdXN0ID0gMCksICAKICAgIHBsb3QubWFyZ2luID0gdW5pdChyZXAoMSwgNCksICJsaW5lcyIpKQoKIyBTcGxpdCBkYXRhOiAKCnRyYWluIDwtIEFpclBhc3NlbmdlcnNbMToxMzJdICU+JSB0cyhmcmVxdWVuY3kgPSAxMikKdGVzdCA8LSBBaXJQYXNzZW5nZXJzWzEzMzoxNDRdICU+JSB0cyhmcmVxdWVuY3kgPSAxMikKaCA8LSBsZW5ndGgodGVzdCkKCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyAgTUxQIG5ldXJhbCBuZXR3b3JrcyBmb3IgRm9yZWNhc3RpbmcgVGltZSBTZXJpZXMKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKIyBQcmVwYXJlIGEgc2VyaWVzIGZvciB0cmFpbiBhbmQgZm9yZWNhc3Rpbmc6IAp0dCA8LSBjYmluZChjKDE6KGxlbmd0aCh0cmFpbikgKyBoKSwgcmVwKDAsIGgpKSkKCiMgRml0IGEgbmV0d29yayB3aXRoIG5vIGRpZmZlcmVuY2luZywgbm8gdW5pdmFyaWF0ZSBsYWdzLCBhbmQgZml4ZWQgZGV0ZXJtaW5pc3RpYyB0cmVuZDogCm15X21scCA8LSBtbHAodHJhaW4sIAogICAgICAgICAgICAgIGhkID0gMTAsIAogICAgICAgICAgICAgIGRpZmZvcmRlciA9IDAsIAogICAgICAgICAgICAgIGxhZ3MgPSAwLCAKICAgICAgICAgICAgICB4cmVnID0gdHQsIAogICAgICAgICAgICAgIHhyZWcubGFncyA9IGxpc3QoMCksIAogICAgICAgICAgICAgIHhyZWcua2VlcCA9IFRSVUUpCgojIE1ha2UgcHJlZGljdGlvbnM6IApwcmVkIDwtIGZvcmVjYXN0KG15X21scCwgaCA9IGgsIHhyZWcgPSB0dCkKCiMgRGF0YSBGcmFtZSBmb3IgY29tcGFyaW5nOiAKCmRmX21scCA8LSBkYXRhX2ZyYW1lKEFjdHVhbCA9IHRlc3QgJT4lIGFzLnZlY3RvcigpLCAKICAgICAgICAgICAgICAgICAgICAgUHJlZGljdGVkID0gcHJlZCRtZWFuICU+JSByb3VuZCgwKSwgCiAgICAgICAgICAgICAgICAgICAgIEVycm9yID0gUHJlZGljdGVkIC0gQWN0dWFsLAogICAgICAgICAgICAgICAgICAgICBFcnJvcl9QZXJjZW50ID0gcm91bmQoRXJyb3IgLyBBY3R1YWwsIDIpKQoKZGZfbWxwICU+JSBrbml0cjo6a2FibGUoKQoKIyBGdW5jdGlvbiBjYWxjdWxhdGUgc29tZSBhY2N1cmFjeSBtZWFzdXJlczogCmdldF9hY2N1cmFjeV9tZWFzdXJlcyA8LSBmdW5jdGlvbih5b3VyX3Jlc3VsdF9kZikgewogIAogIGFjdCA8LSB5b3VyX3Jlc3VsdF9kZiAlPiUgcHVsbChBY3R1YWwpCiAgcHJlZCA8LSB5b3VyX3Jlc3VsdF9kZiAlPiUgcHVsbChQcmVkaWN0ZWQpCiAgZXJyIDwtIGFjdCAtIHByZWQgCiAgcGVyX2VyciA8LSBhYnMoZXJyIC8gYWN0KQogIAogICMgTWVhbiBBYnNvbHV0ZSBFcnJvciAoTUFFKTogCiAgbWFlIDwtIGVyciAlPiUgYWJzKCkgJT4lIG1lYW4oKQogIAogICMgTWVhbiBTcXVhcmVkIEVycm9yIChNU0UpOiAKICBtc2UgPC0gbWVhbihlcnJeMikKICAKICAjIE1lYW4gQWJzb2x1dGUgUGVyY2VudGFnZSBFcnJvciAoTUFQRSk6IAogIG1hcGUgPC0gMTAwKm1lYW4ocGVyX2VycikKICAKICAjIFJldHVybiByZXN1bHRzOiAKICByZXR1cm4oZGF0YV9mcmFtZShNQUUgPSBtYWUsIE1TRSA9IG1zZSwgTUFQRSA9IG1hcGUsIE4gPSBsZW5ndGgoYWN0KSkpCn0KCgojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgIEF1dG8gQVJJTUEgQXBwcm9hY2ggYXMgcHJvcG9zZWQgYnkgSHluZG1hbiBhbmQgS2hhbmRha2FyICgyMDA4KQojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpteV9hcmltYSA8LSBhdXRvLmFyaW1hKHRyYWluKQoKIyBVc2UgdGhlIG1vZGVsIGZvciBmb3JlY2FzdGluZzogCnByZWRpY3RlZF9hcmltYSA8LSBmb3JlY2FzdChteV9hcmltYSwgaCA9IGgpJG1lYW4gJT4lIGFzLnZlY3RvcigpCgoKIyBEYXRhIEZyYW1lIGZvciBjb21wYXJpbmc6IApkZl9hcmltYSA8LSBkYXRhX2ZyYW1lKEFjdHVhbCA9IHRlc3QgJT4lIGFzLnZlY3RvcigpLCAKICAgICAgICAgICAgICAgICAgICAgICBQcmVkaWN0ZWQgPSBwcmVkaWN0ZWRfYXJpbWEgJT4lIHJvdW5kKDApLCAKICAgICAgICAgICAgICAgICAgICAgICBFcnJvciA9IFByZWRpY3RlZCAtIEFjdHVhbCwKICAgICAgICAgICAgICAgICAgICAgICBFcnJvcl9QZXJjZW50ID0gcm91bmQoRXJyb3IgLyBBY3R1YWwsIDIpKQoKIyBNb2RlbCBQZXJmb3JtYW5jZTogCmRmX2FyaW1hICU+JSBrbml0cjo6a2FibGUoKQoKIz09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojICAgIE15IFNvbHV0aW9uOiBFbnNlbWJsZSBNZXRob2QgCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCm15X2Vuc2VtYmxlX3ByZWRpY3QgPC0gZnVuY3Rpb24oeW91cl9kZiwgbl9haGVhZCkgewogIAogIE4gPC0gbGVuZ3RoKHlvdXJfZGYpCiAgZW5kMSA8LSBOIC0gbl9haGVhZAogIAogIHRyYWluIDwtIHlvdXJfZGZbMTplbmQxXSAlPiUgdHMoZnJlcXVlbmN5ID0gMTIpCiAgdGVzdCA8LSB5b3VyX2RmWyhlbmQxICsgMSk6Tl0gJT4lIHRzKGZyZXF1ZW5jeSA9IDEyKQogIGggPC0gbGVuZ3RoKHRlc3QpCiAgCiAgdHQgPC0gY2JpbmQoYygxOihsZW5ndGgodHJhaW4pICsgaCksIHJlcCgwLCBoKSkpCiAgCiAgbXlfbWxwIDwtIG1scCh0cmFpbiwgCiAgICAgICAgICAgICAgICBoZCA9IDEwLCAKICAgICAgICAgICAgICAgIGRpZmZvcmRlciA9IDAsIAogICAgICAgICAgICAgICAgbGFncyA9IDAsIAogICAgICAgICAgICAgICAgeHJlZyA9IHR0LCAKICAgICAgICAgICAgICAgIHhyZWcubGFncyA9IGxpc3QoMCksIAogICAgICAgICAgICAgICAgeHJlZy5rZWVwID0gVFJVRSkKICBwcmVkaWN0ZWRfbWxwIDwtIGZvcmVjYXN0KG15X21scCwgaCA9IGgsIHhyZWcgPSB0dCkKICAKICBteV9hcmltYSA8LSBhdXRvLmFyaW1hKHRyYWluKQogIHByZWRpY3RlZF9hcmltYSA8LSBmb3JlY2FzdChteV9hcmltYSwgaCA9IGgpJG1lYW4gJT4lIGFzLnZlY3RvcigpCiAgCiAgZW5zZW1ibGVfcHJlZGljdGVkIDwtIDAuNSoocHJlZGljdGVkX21scCRtZWFuICsgcHJlZGljdGVkX2FyaW1hKQogIAogIHJldHVybihkYXRhX2ZyYW1lKEFjdHVhbCA9IHRlc3QgJT4lIGFzLnZlY3RvcigpLCAKICAgICAgICAgICAgICAgICAgICBQcmVkaWN0ZWQgPSBlbnNlbWJsZV9wcmVkaWN0ZWQgJT4lIHJvdW5kKDApLCAKICAgICAgICAgICAgICAgICAgICBFcnJvciA9IFByZWRpY3RlZCAtIEFjdHVhbCwKICAgICAgICAgICAgICAgICAgICBFcnJvcl9QZXJjZW50ID0gcm91bmQoRXJyb3IgLyBBY3R1YWwsIDIpKSkKfSAKCgojIFVzZSB0aGUgZnVuY3Rpb246IApteV9lbnNlbWJsZV9wcmVkaWN0KEFpclBhc3NlbmdlcnMsIDEyKSAtPiBkZl9lbnNlbWJsZQpkZl9lbnNlbWJsZSAlPiUga25pdHI6OmthYmxlKCkKCiMgQ29tcGFyZSB0aHJlZSBhcHByb2FjaGVzOiAKCmJpbmRfcm93cyhkZl9tbHAgJT4lIGdldF9hY2N1cmFjeV9tZWFzdXJlcygpLCAKICAgICAgICAgIGRmX2FyaW1hICU+JSBnZXRfYWNjdXJhY3lfbWVhc3VyZXMoKSwgCiAgICAgICAgICBkZl9lbnNlbWJsZSAlPiUgZ2V0X2FjY3VyYWN5X21lYXN1cmVzKCkpICU+JSAKICBtdXRhdGUoQXBwcm9hY2ggPSBjKCJNTFAiLCAiQVJJTUEiLCAiRW5zZW1ibGUiKSkgJT4lIAogIHNlbGVjdChBcHByb2FjaCwgZXZlcnl0aGluZygpKSAlPiUgCiAgbXV0YXRlX2lmKGlzLm51bWVyaWMsIGZ1bmN0aW9uKHgpIHtyb3VuZCh4LCAyKX0pICU+JSAKICBrbml0cjo6a2FibGUoKQpgYGAKCgojIFJlZmVyZW5jZXMKCjEuIEtvdXJlbnR6ZXMsIE4uLCBCYXJyb3csIEQuIEsuLCAmIENyb25lLCBTLiBGLiAoMjAxNCkuIE5ldXJhbCBuZXR3b3JrIGVuc2VtYmxlIG9wZXJhdG9ycyBmb3IgdGltZSBzZXJpZXMgZm9yZWNhc3RpbmcuIEV4cGVydCBTeXN0ZW1zIHdpdGggQXBwbGljYXRpb25zLCA0MSg5KSwgNDIzNS00MjQ0LgoyLiBIeW5kbWFuLCBSLiBKLiwgYW5kIFkuIEtoYW5kYWthciAoMjAwOCk64oCcQXV0b21hdGljIHRpbWUgc2VyaWVzIGZvcmVjYXN0aW5nOiBUaGUgZm9yZWNhc3QgcGFja2FnZSBmb3IgUizigJ0uIEpvdXJuYWwgb2YgU3RhdGlzdGljYWwgU29mdHdhcmUsIDI2KDMpLgozLiBCYXJyb3csIEQuIEsuLCBDcm9uZSwgUy4gRi4sICYgS291cmVudHplcywgTi4gKDIwMTAsIEp1bHkpLiBBbiBldmFsdWF0aW9uIG9mIG5ldXJhbCBuZXR3b3JrIGVuc2VtYmxlcyBhbmQgbW9kZWwgc2VsZWN0aW9uIGZvciB0aW1lIHNlcmllcyBwcmVkaWN0aW9uLiBJbiBOZXVyYWwgTmV0d29ya3MgKElKQ05OKSwgVGhlIDIwMTAgSW50ZXJuYXRpb25hbCBKb2ludCBDb25mZXJlbmNlIG9uIChwcC4gMS04KS4gSUVFRS4KCg==