# Clear workspace:
rm(list = ls())
# Import packages for data manipulation:
library(tidyverse)
library(magrittr)
# A function for black theme:
my_theme <- function(...) {
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"))
}
# Inspect data path and load data:
my_path <- dir("/home/chidung/Desktop/atm_cash_data", full.names = TRUE)
my_case <- read_csv(my_path[1])
# Create lag 1 variable:
my_case_lag <- my_case %>%
mutate(lag1 = lag(daily_cash, 1)) %>%
na.omit()
# Scale 0-1 for variables:
my_case_lag_scaled <- my_case_lag %>%
mutate_if(is.numeric, function(x) {(x - min(x)) / (max(x) - min(x))})
# Split data:
N <- nrow(my_case_lag_scaled)
train_df <- my_case_lag_scaled %>% slice(1:450)
test_df <- my_case_lag_scaled %>% slice(451:N)
# Specify features and output:
y_train <- train_df %>% pull(2)
x_train <- train_df %>% pull(3)
y_test <- test_df %>% pull(2)
x_test <- test_df %>% pull(3)
#======================================================
# Long Short Term Memory (LSTM) Networks (Version 1)
#======================================================
# Load package keras:
library(keras)
# Reshape the input to 3-dim:
dim(x_train) <- c(length(x_train), 1, 1)
# specify required arguments:
X_shape2 <- dim(x_train)[2]
X_shape3 <- dim(x_train)[3]
batch_size <- 1
units <- 1
# Specify conditions for LSTM Model:
model <- keras_model_sequential() %>%
layer_lstm(units, batch_input_shape = c(batch_size, X_shape2, X_shape3), stateful = TRUE) %>%
layer_dense(units = 1) %>%
compile(loss = "mean_squared_error",
optimizer = optimizer_adam(lr = 0.005, decay = 1e-10),
metrics = c("accuracy"))
# Train / fit LSTM Model:
Epochs <- 10
for(i in 1:Epochs) {
model %>%
fit(x_train,
y_train,
epochs = 1,
batch_size = batch_size,
verbose = 1,
shuffle = FALSE)
model %>% reset_states()
}
# A function for forecasting:
my_predict_from_lstm <- function(k) {
x <- x_test[k]
dim(x) <- c(1, 1, 1)
y_hat <- model %>% predict(x, batch_size = 1)
return(y_hat)
}
# Make predictions:
sapply(1:length(x_test), my_predict_from_lstm) ->> pred_scaled
# A function covert to origin:
convert_to_origin <- function(x) {
x_base <- my_case_lag$daily_cash
y <- x*(max(x_base) - min(x_base)) + min(x_base)
return(y)
}
# Compare actuals vs predictions:
test_df %>%
rename(Actual = daily_cash) %>%
mutate(Predicted = convert_to_origin(pred_scaled), Actual = convert_to_origin(Actual)) %>%
select(-lag1) -> lstm_results
lstm_results %>%
gather(a, b, -DAYID) %>%
ggplot(aes(DAYID, b, color = a)) +
geom_line() +
scale_color_manual(values = c("purple", "orange"), name = "Series:") +
my_theme() +
labs(x = NULL, y = NULL, subtitle = "Approach Used: Long Short Term Memory (LSTM) Networks",
title = "One-step Ahead Prediction for Cash Withdrawals (120 Consecutive Days)")
LS0tCnRpdGxlOiAiRm9yZWNhc3RpbmcgRGFpbHkgQ2FzaCBEZW1hbmQ6IExvbmcgU2hvcnQgVGVybSBNZW1vcnkgdnMgQVJJTUEgQXBycm9hY2giIApzdWJ0aXRsZTogIlIgZm9yIEZ1biIKYXV0aG9yOiAiTmd1eWVuIENoaSBEdW5nIgpvdXRwdXQ6CiAgaHRtbF9kb2N1bWVudDogCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IGhpZGUKICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgICMgbnVtYmVyX3NlY3Rpb25zOiB5ZXMKICAgIHRoZW1lOiAiZmxhdGx5IgogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKLS0tCgpgYGB7ciBzZXR1cCxpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUsIHdhcm5pbmcgPSBGQUxTRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkKYGBgCgpgYGB7cn0KIyBDbGVhciB3b3Jrc3BhY2U6IApybShsaXN0ID0gbHMoKSkKCiMgSW1wb3J0IHBhY2thZ2VzIGZvciBkYXRhIG1hbmlwdWxhdGlvbjogCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KG1hZ3JpdHRyKQoKIyBBIGZ1bmN0aW9uIGZvciBibGFjayB0aGVtZTogCgpteV90aGVtZSA8LSBmdW5jdGlvbiguLi4pIHsKICB0aGVtZSgKICAgIGF4aXMubGluZSA9IGVsZW1lbnRfYmxhbmsoKSwgIAogICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3IgPSAid2hpdGUiLCBsaW5laGVpZ2h0ID0gMC45KSwgIAogICAgYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoY29sb3IgPSAid2hpdGUiLCBsaW5laGVpZ2h0ID0gMC45KSwgIAogICAgYXhpcy50aWNrcyA9IGVsZW1lbnRfbGluZShjb2xvciA9ICJ3aGl0ZSIsIHNpemUgID0gIDAuMiksICAKICAgIGF4aXMudGl0bGUueCA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJ3aGl0ZSIsIG1hcmdpbiA9IG1hcmdpbigwLCAxMCwgMCwgMCkpLCAgCiAgICBheGlzLnRpdGxlLnkgPSBlbGVtZW50X3RleHQoY29sb3IgPSAid2hpdGUiLCBhbmdsZSA9IDkwLCBtYXJnaW4gPSBtYXJnaW4oMCwgMTAsIDAsIDApKSwgIAogICAgYXhpcy50aWNrcy5sZW5ndGggPSB1bml0KDAuMywgImxpbmVzIiksICAgCiAgICBsZWdlbmQuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChjb2xvciA9IE5BLCBmaWxsID0gImdyYXkxMCIpLCAgCiAgICBsZWdlbmQua2V5ID0gZWxlbWVudF9yZWN0KGNvbG9yID0gIndoaXRlIiwgIGZpbGwgPSAiZ3JheTEwIiksICAKICAgIGxlZ2VuZC5rZXkuc2l6ZSA9IHVuaXQoMS4yLCAibGluZXMiKSwgIAogICAgbGVnZW5kLmtleS5oZWlnaHQgPSBOVUxMLCAgCiAgICBsZWdlbmQua2V5LndpZHRoID0gTlVMTCwgICAgICAKICAgIGxlZ2VuZC50ZXh0ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gIndoaXRlIiksICAKICAgIGxlZ2VuZC50aXRsZSA9IGVsZW1lbnRfdGV4dChmYWNlID0gImJvbGQiLCBoanVzdCA9IDAsIGNvbG9yID0gIndoaXRlIiksICAKICAgIGxlZ2VuZC50ZXh0LmFsaWduID0gTlVMTCwgIAogICAgbGVnZW5kLnRpdGxlLmFsaWduID0gTlVMTCwgIAogICAgbGVnZW5kLmRpcmVjdGlvbiA9ICJ2ZXJ0aWNhbCIsICAKICAgIGxlZ2VuZC5ib3ggPSBOVUxMLCAKICAgIHBhbmVsLmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoZmlsbCA9ICJncmF5MTAiLCBjb2xvciAgPSAgTkEpLCAgCiAgICBwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCksCiAgICBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyZXkzNSIpLCAgCiAgICBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9saW5lKGNvbG9yID0gImdyZXkyMCIpLCAgCiAgICBwYW5lbC5zcGFjaW5nID0gdW5pdCgwLjUsICJsaW5lcyIpLCAgIAogICAgc3RyaXAuYmFja2dyb3VuZCA9IGVsZW1lbnRfcmVjdChmaWxsID0gImdyZXkzMCIsIGNvbG9yID0gImdyZXkxMCIpLCAgCiAgICBzdHJpcC50ZXh0LnggPSBlbGVtZW50X3RleHQoY29sb3IgPSAid2hpdGUiKSwgIAogICAgc3RyaXAudGV4dC55ID0gZWxlbWVudF90ZXh0KGNvbG9yID0gIndoaXRlIiwgYW5nbGUgPSAtOTApLCAgCiAgICBwbG90LmJhY2tncm91bmQgPSBlbGVtZW50X3JlY3QoY29sb3IgPSAiZ3JheTEwIiwgZmlsbCA9ICJncmF5MTAiKSwgIAogICAgcGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJ3aGl0ZSIsIGhqdXN0ID0gMCwgbGluZWhlaWdodCA9IDEuMjUsIG1hcmdpbiA9IG1hcmdpbigyLCAyLCAyLCAyKSksICAKICAgIHBsb3Quc3VidGl0bGUgPSBlbGVtZW50X3RleHQoY29sb3IgPSAid2hpdGUiLCBoanVzdCA9IDAsIG1hcmdpbiA9IG1hcmdpbigyLCAyLCAyLCAyKSksICAKICAgIHBsb3QuY2FwdGlvbiA9IGVsZW1lbnRfdGV4dChjb2xvciA9ICJ3aGl0ZSIsIGhqdXN0ID0gMCksICAKICAgIHBsb3QubWFyZ2luID0gdW5pdChyZXAoMSwgNCksICJsaW5lcyIpKQogIAp9CgojIEluc3BlY3QgZGF0YSBwYXRoIGFuZCBsb2FkIGRhdGE6IApteV9wYXRoIDwtIGRpcigiL2hvbWUvY2hpZHVuZy9EZXNrdG9wL2F0bV9jYXNoX2RhdGEiLCBmdWxsLm5hbWVzID0gVFJVRSkKbXlfY2FzZSA8LSByZWFkX2NzdihteV9wYXRoWzFdKQoKIyBDcmVhdGUgbGFnIDEgdmFyaWFibGU6IApteV9jYXNlX2xhZyA8LSBteV9jYXNlICU+JSAKICBtdXRhdGUobGFnMSA9IGxhZyhkYWlseV9jYXNoLCAxKSkgJT4lIAogIG5hLm9taXQoKQoKIyBTY2FsZSAwLTEgZm9yIHZhcmlhYmxlczogCgpteV9jYXNlX2xhZ19zY2FsZWQgPC0gbXlfY2FzZV9sYWcgJT4lIAogIG11dGF0ZV9pZihpcy5udW1lcmljLCBmdW5jdGlvbih4KSB7KHggLSBtaW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSl9KQoKCiMgU3BsaXQgZGF0YTogCk4gPC0gbnJvdyhteV9jYXNlX2xhZ19zY2FsZWQpCnRyYWluX2RmIDwtIG15X2Nhc2VfbGFnX3NjYWxlZCAlPiUgc2xpY2UoMTo0NTApCnRlc3RfZGYgPC0gbXlfY2FzZV9sYWdfc2NhbGVkICU+JSBzbGljZSg0NTE6TikKCiMgU3BlY2lmeSBmZWF0dXJlcyBhbmQgb3V0cHV0OiAKCnlfdHJhaW4gPC0gdHJhaW5fZGYgJT4lIHB1bGwoMikKeF90cmFpbiA8LSB0cmFpbl9kZiAlPiUgcHVsbCgzKQoKeV90ZXN0IDwtIHRlc3RfZGYgJT4lIHB1bGwoMikKeF90ZXN0IDwtIHRlc3RfZGYgJT4lIHB1bGwoMykKCgojPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgIExvbmcgU2hvcnQgVGVybSBNZW1vcnkgKExTVE0pIE5ldHdvcmtzIChWZXJzaW9uIDEpCiM9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiMgTG9hZCBwYWNrYWdlIGtlcmFzOiAKbGlicmFyeShrZXJhcykKCiMgUmVzaGFwZSB0aGUgaW5wdXQgdG8gMy1kaW06IApkaW0oeF90cmFpbikgPC0gYyhsZW5ndGgoeF90cmFpbiksIDEsIDEpCgojIHNwZWNpZnkgcmVxdWlyZWQgYXJndW1lbnRzOiAKWF9zaGFwZTIgPC0gZGltKHhfdHJhaW4pWzJdClhfc2hhcGUzIDwtIGRpbSh4X3RyYWluKVszXQpiYXRjaF9zaXplIDwtIDEgICAgICAgICAgICAgICAgCnVuaXRzIDwtIDEgICAgICAgICAgICAgICAgICAgICAKCiMgU3BlY2lmeSBjb25kaXRpb25zIGZvciBMU1RNIE1vZGVsOiAKCm1vZGVsIDwtIGtlcmFzX21vZGVsX3NlcXVlbnRpYWwoKSAlPiUKICBsYXllcl9sc3RtKHVuaXRzLCBiYXRjaF9pbnB1dF9zaGFwZSA9IGMoYmF0Y2hfc2l6ZSwgWF9zaGFwZTIsIFhfc2hhcGUzKSwgc3RhdGVmdWwgPSBUUlVFKSAlPiUKICBsYXllcl9kZW5zZSh1bml0cyA9IDEpICU+JSAKICBjb21waWxlKGxvc3MgPSAibWVhbl9zcXVhcmVkX2Vycm9yIiwKICAgICAgICAgIG9wdGltaXplciA9IG9wdGltaXplcl9hZGFtKGxyID0gMC4wMDUsIGRlY2F5ID0gMWUtMTApLCAgCiAgICAgICAgICBtZXRyaWNzID0gYygiYWNjdXJhY3kiKSkKCiMgVHJhaW4gLyBmaXQgTFNUTSBNb2RlbDogCgpFcG9jaHMgPC0gMTAgICAKZm9yKGkgaW4gMTpFcG9jaHMpIHsKICBtb2RlbCAlPiUgCiAgICBmaXQoeF90cmFpbiwgCiAgICAgICAgeV90cmFpbiwgCiAgICAgICAgZXBvY2hzID0gMSwKICAgICAgICBiYXRjaF9zaXplID0gYmF0Y2hfc2l6ZSwgCiAgICAgICAgdmVyYm9zZSA9IDEsIAogICAgICAgIHNodWZmbGUgPSBGQUxTRSkKICBtb2RlbCAlPiUgcmVzZXRfc3RhdGVzKCkKfQoKIyBBIGZ1bmN0aW9uIGZvciBmb3JlY2FzdGluZzogCgpteV9wcmVkaWN0X2Zyb21fbHN0bSA8LSBmdW5jdGlvbihrKSB7CiAgeCA8LSB4X3Rlc3Rba10KICBkaW0oeCkgPC0gYygxLCAxLCAxKQogIHlfaGF0IDwtIG1vZGVsICU+JSBwcmVkaWN0KHgsIGJhdGNoX3NpemUgPSAxKQogIHJldHVybih5X2hhdCkKfQoKIyBNYWtlIHByZWRpY3Rpb25zOiAKc2FwcGx5KDE6bGVuZ3RoKHhfdGVzdCksIG15X3ByZWRpY3RfZnJvbV9sc3RtKSAtPj4gcHJlZF9zY2FsZWQKCiMgQSBmdW5jdGlvbiBjb3ZlcnQgdG8gb3JpZ2luOiAKCmNvbnZlcnRfdG9fb3JpZ2luIDwtIGZ1bmN0aW9uKHgpIHsKICB4X2Jhc2UgPC0gbXlfY2FzZV9sYWckZGFpbHlfY2FzaAogIHkgPC0geCoobWF4KHhfYmFzZSkgLSBtaW4oeF9iYXNlKSkgKyBtaW4oeF9iYXNlKQogIHJldHVybih5KQp9CgojIENvbXBhcmUgYWN0dWFscyB2cyBwcmVkaWN0aW9uczogCnRlc3RfZGYgJT4lIAogIHJlbmFtZShBY3R1YWwgPSBkYWlseV9jYXNoKSAlPiUgCiAgbXV0YXRlKFByZWRpY3RlZCA9IGNvbnZlcnRfdG9fb3JpZ2luKHByZWRfc2NhbGVkKSwgQWN0dWFsID0gY29udmVydF90b19vcmlnaW4oQWN0dWFsKSkgJT4lIAogIHNlbGVjdCgtbGFnMSkgLT4gbHN0bV9yZXN1bHRzCgpsc3RtX3Jlc3VsdHMgJT4lIAogIGdhdGhlcihhLCBiLCAtREFZSUQpICU+JSAKICBnZ3Bsb3QoYWVzKERBWUlELCBiLCBjb2xvciA9IGEpKSArIAogIGdlb21fbGluZSgpICsgCiAgc2NhbGVfY29sb3JfbWFudWFsKHZhbHVlcyA9IGMoInB1cnBsZSIsICJvcmFuZ2UiKSwgbmFtZSA9ICJTZXJpZXM6IikgKyAKICBteV90aGVtZSgpICsgCiAgbGFicyh4ID0gTlVMTCwgeSA9IE5VTEwsIHN1YnRpdGxlID0gIkFwcHJvYWNoIFVzZWQ6IExvbmcgU2hvcnQgVGVybSBNZW1vcnkgKExTVE0pIE5ldHdvcmtzIiwgCiAgICAgICB0aXRsZSA9ICJPbmUtc3RlcCBBaGVhZCBQcmVkaWN0aW9uIGZvciBDYXNoIFdpdGhkcmF3YWxzICgxMjAgQ29uc2VjdXRpdmUgRGF5cykiKQoKIz09PT09PT09PT09PT09PT09PT0KIyAgQVJJTUEgQXBwcm9hY2gKIz09PT09PT09PT09PT09PT09PT0KCmxpYnJhcnkoZnBwMikKCnByZWRpY3RfZnJvbV9hcmltYSA8LSBmdW5jdGlvbihrKSB7CiAgCiAgbl9haGVhZCA8LSAxCiAgdHJhaW5fYXJpbWEgPC0gbXlfY2FzZV9sYWcgJT4lIHNsaWNlKCgxICsgayk6KDQ1MCArIGspKSAlPiUgc2VsZWN0KDE6MikKICB0ZXN0X2FyaW1hICA8LSBteV9jYXNlX2xhZyAlPiUgc2xpY2UoNDUwICsgayArIG5fYWhlYWQpICU+JSBzZWxlY3QoMToyKQogIAogICMgQXV0b21hdGljIEFSSU1BIG1vZGVsIGFzIHByb3Bvc2VkIGJ5IEh5bmRtYW4gYW5kIEtoYW5kYWthciAoMjAwOCk6IAogIG15X2FyaW1hIDwtIGF1dG8uYXJpbWEodHJhaW5fYXJpbWFbLCAyXSAlPiUgdHMoc3RhcnQgPSAxKSkKICAKICAjIFVzZSB0aGUgbW9kZWwgZm9yIGZvcmVjYXN0aW5nOiAKICBwcmVkaWN0ZWRfYXJpbWEgPC0gZm9yZWNhc3QobXlfYXJpbWEsIGggPSAxKSRtZWFuICU+JSBhcy52ZWN0b3IoKQogIAogIGFjdHVhbF9wcmVkaWN0ZWRfZGZfdGVzdCA8LSB0ZXN0X2FyaW1hICU+JSAKICAgIG11dGF0ZShwcmVkaWN0ZWQgPSBwcmVkaWN0ZWRfYXJpbWEpIAogIAogIHJldHVybihhY3R1YWxfcHJlZGljdGVkX2RmX3Rlc3QpCiAgCn0KCiMgVXNlIHRoaXMgZnVuY3Rpb246IAoKc3lzdGVtLnRpbWUobGFwcGx5KDA6MTE5LCBwcmVkaWN0X2Zyb21fYXJpbWEpIC0+PiBhcmltYV9yZXN1bHRzKQphcmltYV9yZXN1bHRzIDwtIGRvLmNhbGwoImJpbmRfcm93cyIsIGFyaW1hX3Jlc3VsdHMpCgojIENvbXBhcmUgY2FzaCBkZW1hbmRzIHByZWRpY3RlZCBieSBBUklNQSBhcHByb2FjaCBhbmQgYWN0dWFsczogCgphcmltYV9yZXN1bHRzICU8PiUgIAogIHJlbmFtZShBY3R1YWwgPSBkYWlseV9jYXNoLCBQcmVkaWN0ZWQgPSBwcmVkaWN0ZWQpIAoKYXJpbWFfcmVzdWx0cyAlPiUgCiAgZ2F0aGVyKGEsIGIsIC1EQVlJRCkgJT4lIAogIGdncGxvdChhZXMoREFZSUQsIGIsIGNvbG9yID0gYSkpICsgCiAgZ2VvbV9saW5lKCkgKyAKICBzY2FsZV9jb2xvcl9tYW51YWwodmFsdWVzID0gYygicHVycGxlIiwgIm9yYW5nZSIpLCBuYW1lID0gIlNlcmllczoiKSArIAogIG15X3RoZW1lKCkgKyAKICBsYWJzKHggPSBOVUxMLCB5ID0gTlVMTCwgc3VidGl0bGUgPSAiQXBwcm9hY2ggVXNlZDogQXV0byBBUklNQSIsIAogICAgICAgdGl0bGUgPSAiT25lLXN0ZXAgQWhlYWQgUHJlZGljdGlvbiBmb3IgQ2FzaCBXaXRoZHJhd2FscyAoMTIwIENvbnNlY3V0aXZlIERheXMpIikKCiMgRnVuY3Rpb24gY2FsY3VsYXRlIHNvbWUgYWNjdXJhY3kgbWVhc3VyZXM6IApnZXRfYWNjdXJhY3lfbWVhc3VyZXMgPC0gZnVuY3Rpb24oeW91cl9yZXN1bHRfZGYpIHsKCiAgYWN0IDwtIHlvdXJfcmVzdWx0X2RmICU+JSBwdWxsKEFjdHVhbCkKICBwcmVkIDwtIHlvdXJfcmVzdWx0X2RmICU+JSBwdWxsKFByZWRpY3RlZCkKICBlcnIgPC0gYWN0IC0gcHJlZCAKICBwZXJfZXJyIDwtIGFicyhlcnIgLyBhY3QpCiAgCiAgIyBNZWFuIEFic29sdXRlIEVycm9yIChNQUUpOiAKICBtYWUgPC0gZXJyICU+JSBhYnMoKSAlPiUgbWVhbigpCiAgCiAgIyBNZWFuIFNxdWFyZWQgRXJyb3IgKE1TRSk6IAogIG1zZSA8LSBtZWFuKGVycl4yKQogIAogICMgTWVhbiBBYnNvbHV0ZSBQZXJjZW50YWdlIEVycm9yIChNQVBFKTogCiAgbWFwZSA8LSAxMDAqbWVhbihwZXJfZXJyKQogIAogICMgUmV0dXJuIHJlc3VsdHM6IAogIHJldHVybihkYXRhLmZyYW1lKE1BRSA9IG1hZSwgTVNFID0gbXNlLCBNQVBFID0gbWFwZSwgTiA9IGxlbmd0aChhY3QpKSkKfQoKIyBVc2UgYWJvdmUgZnVuY3Rpb24gZm9yIGNvbXBhcmluZzogCgpiaW5kX3Jvd3MobHN0bV9yZXN1bHRzICU+JSBnZXRfYWNjdXJhY3lfbWVhc3VyZXMoKSwgYXJpbWFfcmVzdWx0cyAlPiUgZ2V0X2FjY3VyYWN5X21lYXN1cmVzKSAlPiUgCiAgbXV0YXRlKEFwcHJvYWNoID0gYygiTFNUTSIsICJBUklNQSIpKSAlPiUgCiAgc2VsZWN0KEFwcHJvYWNoLCBldmVyeXRoaW5nKCkpICU+JSAKICBtdXRhdGVfaWYoaXMubnVtZXJpYywgZnVuY3Rpb24oeCkge3JvdW5kKHgsIDIpfSkgJT4lIAogIGtuaXRyOjprYWJsZSgpCmBgYAoK